blob: 5e0a634f51e6ac6964fff3a1944f62e2bb98ce28 [file] [log] [blame]
LaoeGaoci85307e62025-05-30 23:28:42 +08001'use client';
LaoeGaocid0773912025-06-09 00:38:40 +08002import React, { useEffect, useRef, useState } from 'react';
3import { Image } from "primereact/image";
4import { Button } from "primereact/button";
5import { Avatar } from "primereact/avatar";
6import { ButtonGroup } from "primereact/buttongroup";
7import { InputText } from "primereact/inputtext";
lfmylbhf789e7172025-06-06 18:37:21 +08008import { Dialog } from 'primereact/dialog';
9// 引入图标
10import 'primeicons/primeicons.css';
11// 引入火苗图标
12import Fire from '@icon-park/react/lib/icons/Fire';
13import SmilingFace from '@icon-park/react/lib/icons/SmilingFace';
14// 消息提醒
15import { Toast } from 'primereact/toast';
16// 接口传输
17import axios from "axios";
18// 页面跳转
19import { useParams } from 'next/navigation'
20import { useRouter } from 'next/navigation';
21import Link from 'next/link';
22// 回复评论
23import { OverlayPanel } from 'primereact/overlaypanel';
24import { Sidebar } from 'primereact/sidebar';
25// 分页
26import { Paginator, PaginatorPageChangeEvent } from 'primereact/paginator';
LaoeGaocid0773912025-06-09 00:38:40 +080027import { useLocalStorage } from '../../../hook/useLocalStorage';
lfmylbhf789e7172025-06-06 18:37:21 +080028// 样式
29import './resource-detail.scss';
LaoeGaocid0773912025-06-09 00:38:40 +080030interface User {
31 Id: number;
32}
lfmylbhf789e7172025-06-06 18:37:21 +080033// 种子
34interface Torrent {
LaoeGaocid0773912025-06-09 00:38:40 +080035 torrentRecordId: number;
lfmylbhf789e7172025-06-06 18:37:21 +080036 torrentUrl: string;
LaoeGaocid0773912025-06-09 00:38:40 +080037 infoHash: string;
38 uploadTime: string;
39 uploaderUserId: number;
lfmylbhf789e7172025-06-06 18:37:21 +080040}
41
42// 资源版本
43interface ResourceVersion {
LaoeGaocid0773912025-06-09 00:38:40 +080044 resourceVersionId: string; // 资源版本id
lfmylbhf789e7172025-06-06 18:37:21 +080045 resourceVersionName: string; // 资源版本名称
46 compatibleVersions: string[]; // 兼容的游戏版本列表
47 torrentList: Torrent[]; // 种子列表
48}
49
50// 资源信息
51interface Resource {
LaoeGaocid0773912025-06-09 00:38:40 +080052 resourceId: number;
lfmylbhf789e7172025-06-06 18:37:21 +080053 resourceName: string; // 资源标题
LaoeGaocid0773912025-06-09 00:38:40 +080054 resourcePicture: string; // 资源照片网址
lfmylbhf789e7172025-06-06 18:37:21 +080055 resourceSummary: string; // 资源简介(一句话)
LaoeGaocid0773912025-06-09 00:38:40 +080056 resourceDetail: string; // 资源介绍
57 uploadTime: string; // 上传时间
lfmylbhf789e7172025-06-06 18:37:21 +080058 lastUpdateTime: string; // 最近更新时间
LaoeGaocid0773912025-06-09 00:38:40 +080059 price: number;
lfmylbhf789e7172025-06-06 18:37:21 +080060 downloads: number; // 下载数
LaoeGaocid0773912025-06-09 00:38:40 +080061 likes: number; // 点赞数
62 collections: number; // 收藏数
63 comments: number; // 评论数
64 seeds: number; // 种子数
65 classify: string; // 资源分类(材质包:resourcePack,模组:mod,整合包:modPack ,地图:map
lfmylbhf789e7172025-06-06 18:37:21 +080066 hot: number; // 资源热度
67 gameplayList: string[]; // 资源标签
LaoeGaocid0773912025-06-09 00:38:40 +080068 resourceVersionList: ResourceVersion[]; // 资源版本列表
69 isCollect: boolean; // 是否被收藏
70 isLike: boolean; // 是否被点赞
71 isPurchase: boolean; // 是否被购买
72 isUpload: boolean; // 是否是该用户上传的
lfmylbhf789e7172025-06-06 18:37:21 +080073 userId: number; // 资源上传者的id
74}
75
76// 评论信息
77interface Comment {
78 commentId: number;
79 userId: number | null;
80 replyId: number;
81 resourceId: number;
82 reawardId: number;
83 content: string;
84 createAt: string;
85}
86// 评论列表
87interface CommentList {
88 total: number; // 评论总数
89 records: Comment[]; // 当前页评论数组
90}
91
92// 用户信息
93interface UserInfo {
94 userId: number;
95 username: string;
96 avatar: string;
97 signature: string;
98}
99// 新评论接口
100interface NewComment {
101 userId: number;
102 threadId: number;
103 resourceId: number;
104 rewardId: number;
105 replyId: number;
106 content: string;
107 createAt: string;
108}
109
110// 资源作者
111interface ResourceAuthor {
112 userId: number;
113 username: string;
114 avatar: string;
115 signature: string;
116}
117
118// 关注
119interface Subscriber {
120 userId: number;
LaoeGaocid0773912025-06-09 00:38:40 +0800121 username: string;
lfmylbhf789e7172025-06-06 18:37:21 +0800122}
123
124// 关注列表
125interface SubscriberList {
126 userList: Subscriber[];
127}
128
129export default function ResourceDetail() {
LaoeGaocid0773912025-06-09 00:38:40 +0800130 const user = useLocalStorage<User>('user');
131 const userId: number = user?.Id ?? -1;
lfmylbhf789e7172025-06-06 18:37:21 +0800132 // 获取URL参数
133 const params = useParams<{ resourceId: string }>();
134 const resourceId = decodeURIComponent(params.resourceId); // 防止中文路径乱码
135 // 页面跳转
136 const router = useRouter();
137
138 // 资源信息
139 const [resource, setResource] = useState<Resource | null>(null);
140 // 资源作者信息
141 const [resourceAuthor, setResourceAuthor] = useState<ResourceAuthor>();
142 // 资源作者id
143 const [resourceAuthorId, setResourceAuthorId] = useState<number>(0);
144 // 关注列表
145 const [subscriberList, setSubscriberList] = useState<SubscriberList>();
146 // 添加本地关注状态
147 const [isSubscribed, setIsSubscribed] = useState(false);
148 // 添加本地收藏状态
149 const [isCollected, setIsCollected] = useState(false);
150 const [collectionCount, setCollectionCount] = useState(0);
151 // 添加本地点赞状态
152 const [isLiked, setIsLiked] = useState(false);
153 const [likeCount, setLikeCount] = useState(0);
154 // 发帖人信息
155 const [userInfo, setUserInfo] = useState<UserInfo | null>(null);
156 // 评论人信息
157 const [commentUserInfos, setCommentUserInfos] = useState<Map<number, UserInfo>>(new Map());
158 //评论
159 const [comments, setComments] = useState<Comment[]>([]);
160 const [commentValue, setCommentValue] = useState<string>('');
161 const [totalComments, setTotalComments] = useState<number>(0);
162 // 回复
163 const [replyValue, setReplyValue] = useState<string>('');
164 const [visibleReply, setVisibleReply] = useState<boolean>(false);// 回复评论可视
165 // 评论选择框
166 const ops = useRef<OverlayPanel[]>([]);
167 // 购买弹窗状态
168 const [visible, setVisible] = useState<boolean>(false);
169 // 消息提醒
170 const toast = useRef<Toast>(null);
171
172 // 分页
173 const [first, setFirst] = useState<number>(0);
174 const [rows, setRows] = useState<number>(5);
175 const onPageChange = (event: PaginatorPageChangeEvent) => {
176 setFirst(event.first);
177 setRows(event.rows);
178 };
179
180 useEffect(() => {
181 fetchResourceInfo();
182 }, []);
183
184
185 // 获取资源信息
186 const fetchResourceInfo = async () => {
187 try {
188 // console.log(resourceId);
LaoeGaocid0773912025-06-09 00:38:40 +0800189 const response = await axios.get<Resource>(process.env.PUBLIC_URL + `/resource/info`, {
190 params: { resourceId: resourceId, userId }
lfmylbhf789e7172025-06-06 18:37:21 +0800191 });
192 console.log('获取资源信息:', response.data);
193 setResource(response.data);
194
195 // 初始化本地收藏状态
196 if (response.data) {
197 setIsCollected(response.data.isCollect);
198 setCollectionCount(response.data.collections);
199 }
200
201 setResourceAuthorId(response.data.userId);
202 } catch (err) {
203 console.error('获取资源信息失败', err);
204 toast.current?.show({ severity: 'error', summary: 'error', detail: "获取资源信息失败" });
205 }
206 };
207
208 // 获取到资源作者id时,获取资源作者信息
209 useEffect(() => {
210 fetchResourceAuthor();
211 }, [resourceAuthorId]);
212
213 // 获取资源作者信息
214 const fetchResourceAuthor = async () => {
215 try {
216 // console.log(resourceId);
217 // console.log(resourceAuthorId);
LaoeGaocid0773912025-06-09 00:38:40 +0800218 const response = await axios.get<ResourceAuthor>(process.env.PUBLIC_URL + `/user/info`, {
lfmylbhf789e7172025-06-06 18:37:21 +0800219 params: { userId: resourceAuthorId }
220 });
221 console.log('获取资源作者信息:', response.data);
222 setResourceAuthor(response.data);
223 // setResourceAuthorId(response.data.userId);
224 } catch (err) {
225 console.error('获取资源作者信息失败', err);
226 toast.current?.show({ severity: 'error', summary: 'error', detail: "获取资源作者信息失败" });
227 }
228 };
229
230 useEffect(() => {
231 fetchSubscriber();
232 }, []);
233
234 // 获取正在浏览资源的用户的关注列表
235 const fetchSubscriber = async () => {
236 try {
237 const response = await axios.get<SubscriberList>(process.env.PUBLIC_URL + `/user/subscriber`, {
LaoeGaocid0773912025-06-09 00:38:40 +0800238 params: { userId }
lfmylbhf789e7172025-06-06 18:37:21 +0800239 });
240 console.log("关注列表:", response.data);
241 setSubscriberList(response.data);
242 } catch (err) {
243 console.error('获取浏览用户关注列表失败', err);
244 toast.current?.show({ severity: 'error', summary: 'error', detail: "获取浏览用户关注列表失败" });
245 }
246 }
247
248 useEffect(() => {
249 if (!resource?.userId || !subscriberList?.userList) return;
250
251 const authorId = resource.userId;
252 // 设置 isSubscribed 状态
253 const subscribed = subscriberList.userList.some(user => user.userId === authorId);
254 setIsSubscribed(subscribed);
255 }, [subscriberList, resource]);
256
257 // 若浏览用户与资源作者是同一人,则不显示关注按钮。若不是同一人,则显示按钮
258 const handleSubscribe = () => {
259 // 资源作者 ID
260 const authorId = resource?.userId;
261 // 当前登录用户 ID
LaoeGaocid0773912025-06-09 00:38:40 +0800262 const currentUserId = userId;
lfmylbhf789e7172025-06-06 18:37:21 +0800263
264 // 资源作者与浏览用户是同一人,不显示按钮
LaoeGaocid0773912025-06-09 00:38:40 +0800265 if (!authorId || authorId == currentUserId) {
lfmylbhf789e7172025-06-06 18:37:21 +0800266 return null;
267 }
268
269 return isSubscribed ? (
LaoeGaocid0773912025-06-09 00:38:40 +0800270 // 如果已关注,显示“取消关注”按钮
271 <Button
272 label="取消关注"
273 onClick={async () => {
274 try {
275 const response = await axios.delete(
276 process.env.PUBLIC_URL + '/user/subscription',
277 {
278 params: { userId: currentUserId, followerId: authorId }
lfmylbhf789e7172025-06-06 18:37:21 +0800279 }
LaoeGaocid0773912025-06-09 00:38:40 +0800280 );
281
282 if (response.status === 200) {
283 setIsSubscribed(false); // 🔥立刻更新按钮状态
284 toast.current?.show({
285 severity: 'success',
286 summary: '取消成功',
287 detail: '已取消关注该用户',
288 });
289
290 fetchSubscriber(); // 重新拉取完整关注列表
291 }
292 } catch (error) {
293 console.error('取消关注失败:', error);
294 toast.current?.show({
295 severity: 'error',
296 summary: '错误',
297 detail: '取消关注失败',
298 });
299 }
300 }}
301 />
lfmylbhf789e7172025-06-06 18:37:21 +0800302 ) : (
LaoeGaocid0773912025-06-09 00:38:40 +0800303 // 若未关注,则显示关注按钮
304 <Button
305 label="关注"
306 onClick={async () => {
307 try {
308 const postData = {
309 userId: currentUserId,
310 followerId: authorId,
311 };
312 const response = await axios.post(
313 process.env.PUBLIC_URL + '/user/subscription',
314 postData
315 );
lfmylbhf789e7172025-06-06 18:37:21 +0800316
LaoeGaocid0773912025-06-09 00:38:40 +0800317 if (response.status === 200) {
318 setIsSubscribed(true); // 🔥立刻更新按钮状态
319 toast.current?.show({
320 severity: 'success',
321 summary: '关注成功',
322 detail: '已成功关注该用户',
323 });
lfmylbhf789e7172025-06-06 18:37:21 +0800324
LaoeGaocid0773912025-06-09 00:38:40 +0800325 fetchSubscriber(); // 刷新列表
326 }
327 } catch (error) {
328 console.error('关注失败:', error);
329 toast.current?.show({
330 severity: 'error',
331 summary: '错误',
332 detail: '关注失败',
333 });
334 }
335 }}
336 />
lfmylbhf789e7172025-06-06 18:37:21 +0800337 );
lfmylbhf789e7172025-06-06 18:37:21 +0800338 }
339
340 // 判断该资源是否已被购买, 返回不同的购买按钮
341 const isPurchase = () => {
342 // 作者本人查看资源,不显示购买按钮
LaoeGaocid0773912025-06-09 00:38:40 +0800343 if (resource?.userId == userId) {
344 return;
lfmylbhf789e7172025-06-06 18:37:21 +0800345 }
346
347 // 该资源已被购买
348 if (resource?.isPurchase) {
349 return (
LaoeGaocid0773912025-06-09 00:38:40 +0800350 <Button label="已购买" style={{
351 width: "120px", height: "44px",
352 borderRadius: "20px 0 0 20px",
353 }} disabled={true} />
lfmylbhf789e7172025-06-06 18:37:21 +0800354 )
355 } else {
356 // 该资源未被购买
357 return (
LaoeGaocid0773912025-06-09 00:38:40 +0800358 <Button
359 label="立即购买"
360 style={{
361 width: "120px", height: "44px",
362 borderRadius: "20px 0 0 20px",
363 }}
364 onClick={() => setVisible(true)}
365 />
lfmylbhf789e7172025-06-06 18:37:21 +0800366 )
367 }
368 }
369
370 // 购买按钮接口
371 const handlePurchase = async () => {
372 try {
373 const postData = {
LaoeGaocid0773912025-06-09 00:38:40 +0800374 userId, // 记得用户登录状态获取
lfmylbhf789e7172025-06-06 18:37:21 +0800375 resourceId: resource?.resourceId
376 };
377 // 发送POST请求
378 const response = await axios.post(process.env.PUBLIC_URL + '/resource/purchase', postData);
379
380 if (response.status === 200) {
381 toast.current?.show({ severity: 'success', summary: 'Success', detail: '成功购买资源' });
382 // 购买成功
383 setVisible(false);
384 // 刷新购买按钮
385 isPurchase();
386 } else if (response.status === 412) {
387 toast.current?.show({ severity: 'error', summary: 'error', detail: '积分不足,购买失败' });
388 }
389 } catch (error) {
390 console.error('购买资源失败:', error);
391 toast.current?.show({ severity: 'error', summary: 'error', detail: '购买资源失败' });
392 }
393 };
394
395 // 处理收藏操作
396 const handleCollection = async () => {
397 const newCollectionState = !isCollected;
398 const newCollectionCount = newCollectionState ? collectionCount + 1 : collectionCount - 1;
399
400 // 立即更新本地状态
401 setIsCollected(newCollectionState);
402 setCollectionCount(newCollectionCount);
403
404 try {
405 if (newCollectionState) {
406 // 收藏操作
LaoeGaocid0773912025-06-09 00:38:40 +0800407 await axios.post(process.env.PUBLIC_URL + `/resource/collection`, {
408 params: { resourceId: resourceId, userId }
lfmylbhf789e7172025-06-06 18:37:21 +0800409 });
410 console.log('收藏资源');
411 } else {
412 // 取消收藏操作
LaoeGaocid0773912025-06-09 00:38:40 +0800413 await axios.delete(process.env.PUBLIC_URL + `/resource/collection`, {
414 params: { resourceId: resourceId, userId }
lfmylbhf789e7172025-06-06 18:37:21 +0800415 });
416 console.log('取消收藏资源');
417 }
418 } catch (err) {
419 console.error(newCollectionState ? '收藏资源失败' : '取消收藏失败', err);
420 toast.current?.show({
421 severity: 'error',
422 summary: 'error',
423 detail: newCollectionState ? "收藏资源失败" : "取消收藏失败"
424 });
425
426 // 如果请求失败,回滚状态
427 setIsCollected(!newCollectionState);
428 setCollectionCount(newCollectionState ? collectionCount - 1 : collectionCount + 1);
429 }
430 };
431
432 // 处理点赞行为
433 const handleLike = async () => {
434 const newLikeState = !isLiked;
435 const newLikeCount = newLikeState ? likeCount + 1 : likeCount - 1;
436
437 // 立即更新本地状态
438 setIsLiked(newLikeState);
439 setLikeCount(newLikeCount);
440
441 try {
442 if (newLikeState) {
443 // 点赞操作
LaoeGaocid0773912025-06-09 00:38:40 +0800444 await axios.post(process.env.PUBLIC_URL + `/resource/like`, {
445 params: { resourceId: resourceId, userId }
lfmylbhf789e7172025-06-06 18:37:21 +0800446 });
447 console.log('点赞资源');
448 } else {
449 // 取消点赞操作
LaoeGaocid0773912025-06-09 00:38:40 +0800450 await axios.delete(process.env.PUBLIC_URL + `/resource/like`, {
451 params: { resourceId: resourceId, userId }
lfmylbhf789e7172025-06-06 18:37:21 +0800452 });
453 console.log('取消点赞资源');
454 }
455 } catch (err) {
456 console.error(newLikeState ? '点赞资源失败' : '取消点赞失败', err);
457 toast.current?.show({
458 severity: 'error',
459 summary: 'error',
460 detail: newLikeState ? "点赞资源失败" : "取消点赞失败"
461 });
462
463 // 如果请求失败,回滚状态
464 setIsLiked(!newLikeState);
465 setLikeCount(newLikeState ? newLikeCount - 1 : newLikeCount + 1);
466 }
467 };
468
469 // 格式化数字显示 (3000 -> 3k)
470 const formatCount = (count?: number): string => {
471 if (count == null) return "0"; // 同时处理 undefined/null
472
473 const absCount = Math.abs(count); // 处理负数
474
475 const format = (num: number, suffix: string) => {
476 const fixed = num.toFixed(1);
477 return fixed.endsWith('.0')
LaoeGaocid0773912025-06-09 00:38:40 +0800478 ? `${Math.floor(num)}${suffix}`
479 : `${fixed}${suffix}`;
lfmylbhf789e7172025-06-06 18:37:21 +0800480 };
481
482 if (absCount >= 1e6) return format(count / 1e6, "m");
483 if (absCount >= 1e3) return format(count / 1e3, "k");
484 return count.toString();
485 };
486
487 // 获取发帖人
488 useEffect(() => {
489 if (!resource) return;
490 // 发帖人
LaoeGaocid0773912025-06-09 00:38:40 +0800491 axios.get(process.env.PUBLIC_URL + `/user/info?userId=${resource?.userId}`)
492 .then(res => setUserInfo(res.data))
493 .catch(console.error);
lfmylbhf789e7172025-06-06 18:37:21 +0800494 }, [resource]);
495
496 // 当 resourceId 或分页参数变化时重新拉评论
497 useEffect(() => {
498 if (!resourceId) return;
499
500 fetchComments();
501 }, [resourceId, first, rows]);
502
503
504 //通过评论ID获取评论人信息
505 const getReplyUserName = (replyId: number) => {
506 if (replyId == null || replyId == 0) return '';
507 const replyComment = comments.find(comment => comment.commentId === replyId);
508 if (!replyComment?.userId) return '匿名用户';
509 return "回复 " + commentUserInfos.get(replyComment.userId)?.username || '匿名用户';
510 };
511
512 // 获取该资源的评论
513 const fetchComments = async () => {
514 try {
515 const pageNumber = first / rows + 1;
516 console.log("当前页" + pageNumber + "size" + rows);
517 const response = await axios.get<CommentList>(
LaoeGaocid0773912025-06-09 00:38:40 +0800518 process.env.PUBLIC_URL + `/comments`, {
519 params: { id: resourceId, pageNumber, rows, type: 'resource' }
520 }
lfmylbhf789e7172025-06-06 18:37:21 +0800521 );
522 console.log('获取评论列表:', response.data.records);
523 setComments(response.data.records);
524 setTotalComments(response.data.total);
525 // 拉取评论对应用户信息
526 response.data.records.forEach(comment => {
527 if (comment.userId != null && !commentUserInfos.has(comment.userId)) {
528 axios.get<UserInfo>(
LaoeGaocid0773912025-06-09 00:38:40 +0800529 process.env.PUBLIC_URL + `/user/info`,
530 { params: { userId: comment.userId } }
lfmylbhf789e7172025-06-06 18:37:21 +0800531 ).then(res => {
532 setCommentUserInfos(prev => new Map(prev).set(comment.userId!, res.data));
533 });
534 }
535 });
536 } catch (err) {
537 console.error('获取评论失败', err);
538 toast.current?.show({ severity: 'error', summary: 'error', detail: '获取评论失败' });
539 }
540 };
541
542 // 回复评论接口
543 const publishReply = async (commentId: number) => {
544 if (!replyValue.trim() || !resource) return;
545 console.log('发布评论:', commentId);
546 // console.log(typeof resourceId);
547 try {
548 const newComment: NewComment = {
LaoeGaocid0773912025-06-09 00:38:40 +0800549 userId,
lfmylbhf789e7172025-06-06 18:37:21 +0800550 rewardId: 0,
551 threadId: 0,
552 resourceId: resource.resourceId,
553 replyId: commentId,
554 content: commentValue,
555 createAt: new Date().toISOString().slice(0, 19).replace('T', ' ')
556 };
557
LaoeGaocid0773912025-06-09 00:38:40 +0800558 const response = await axios.post(process.env.PUBLIC_URL + '/comment', newComment);
lfmylbhf789e7172025-06-06 18:37:21 +0800559
560 if (response.status === 200) {
561 toast.current?.show({ severity: 'success', summary: 'Success', detail: '回复成功' });
562 // 更新评论列表
563 fetchComments();
564 setVisibleReply(false)
565 // 清空输入框
566 setReplyValue('');
567 }
568 } catch (error) {
569 console.error('发布评论失败:', error);
570 toast.current?.show({ severity: 'error', summary: 'error', detail: '回复失败' });
571 }
572 };
573
574 // 发布评论接口
575 const publishComment = async () => {
576 if (!commentValue.trim() || !resource) return;
577
578 try {
579 const newComment: NewComment = {
LaoeGaocid0773912025-06-09 00:38:40 +0800580 userId,
lfmylbhf789e7172025-06-06 18:37:21 +0800581 rewardId: 0,
582 threadId: 0,
583 resourceId: resource.resourceId,
584 replyId: 0, // 直接评论,不是回复
585 content: commentValue,
586 createAt: new Date().toISOString().slice(0, 19).replace('T', ' ')
587 };
588
LaoeGaocid0773912025-06-09 00:38:40 +0800589 const response = await axios.post(process.env.PUBLIC_URL + '/comment', newComment);
lfmylbhf789e7172025-06-06 18:37:21 +0800590
591 if (response.status === 200) {
592 toast.current?.show({ severity: 'success', summary: 'Success', detail: '评论成功' });
593 // 更新评论列表
594 fetchComments();
595 // 清空输入框
596 setCommentValue('');
597 }
598 } catch (error) {
599 console.error('发布评论失败:', error);
600 toast.current?.show({ severity: 'error', summary: 'error', detail: '发布评论失败' });
601 }
602 };
603
604 // 删除评论接口
605 const deleteComment = async (commentId: number) => {
606 if (!resourceId) return;
607
608 try {
609 // 调用 DELETE 接口,URL 中最后一段是要删除的 commentId
610 const response = await axios.delete(
LaoeGaocid0773912025-06-09 00:38:40 +0800611 process.env.PUBLIC_URL + `/comment?commentId=${commentId}`
lfmylbhf789e7172025-06-06 18:37:21 +0800612 );
613
614 if (response.status === 200) {
615 fetchComments();
616 toast.current?.show({ severity: 'success', summary: 'Success', detail: '删除评论成功' });
617 } else {
618 toast.current?.show({ severity: 'error', summary: 'error', detail: '删除评论失败' });
619 console.error('删除评论失败,状态码:', response.status);
620 }
621 } catch (error) {
622 console.error('删除评论接口报错:', error);
623 }
624 };
625
626 const ReplyHeader = (
LaoeGaocid0773912025-06-09 00:38:40 +0800627 <div className="flex align-items-center gap-1">
628 <h3>回复评论</h3>
629 </div>
LaoeGaoci85307e62025-05-30 23:28:42 +0800630 );
lfmylbhf789e7172025-06-06 18:37:21 +0800631 if (!resourceId || !userInfo) return <div>Loading...</div>;
LaoeGaoci85307e62025-05-30 23:28:42 +0800632
LaoeGaocid0773912025-06-09 00:38:40 +0800633 return (
634 <div className="resource-detail-container">
635 <Toast ref={toast}></Toast>
636 {/*资源标题*/}
637 <div className="resource-header">
638 {resource?.resourceName}
lfmylbhf789e7172025-06-06 18:37:21 +0800639 </div>
LaoeGaocid0773912025-06-09 00:38:40 +0800640 {/*资源详细信息*/}
641 <div className="resource-info">
642 <Image
643 src={(process.env.NEXT_PUBLIC_NGINX_URL! + resource?.resourcePicture)}
644 alt={resource?.resourceName}
645 width="540px"
646 height="300px"
647 />
648 <div className="resource-info-detail">
649 {/*资源热度*/}
650 <div className="resource-hot">
651 <Fire theme="outline" size="50" fill="#f5a623" strokeWidth={3} />
652 <span className="resource-hot-data">{resource?.hot}</span>
653 </div>
654
655 {/*资源标签*/}
656 <div className="resource-label">
657 {resource?.gameplayList.map((tag, index) => (
658 <Button
659 key={index} label={tag}
660 className="resource-label-button"
661 onClick={() => {
662 router.push(`/resource/classification/`)
663 }}
664 />
665 ))}
666 </div>
667
668 {/*资源浏览量和下载量*/}
669 <div className="resource-data">
670 <div className="resource-data-container">
671 <i className="pi pi-download" />
672 <span className="resource-data-container-number">下载量:{formatCount(resource?.downloads)}</span>
673 </div>
674 </div>
675
676 {/*资源发布时间和更新时间*/}
677 <div className="resource-time">
678 <div className="resource-time-data">
679 发布时间:{resource?.uploadTime}
680 </div>
681 <div className="resource-time-data">
682 更新时间:{resource?.lastUpdateTime}
683 </div>
684 </div>
685 </div>
686 </div>
687 {/*资源总结*/}
688 <div className="resource-summary">
689 {resource?.resourceSummary}
690 </div>
691 {/*关注作者、点赞、抽藏、购买资源*/}
692 <div className="resource-operation">
693 <div className="resource-author">
694 <Avatar
695 image={`${process.env.NEXT_PUBLIC_NGINX_URL}/users/${resourceAuthor?.avatar}`}
696 shape="circle"
697 style={{ width: "60px", height: "60px" }}
698 />
699 <span className="resource-author-name">{resourceAuthor?.username}</span>
700
701 {handleSubscribe()}
702 </div>
703
704 <div className="resource-operation-detail">
705 <div className="resource-operation-detail-data">
706 <i
707 className={isCollected ? "pi pi-star-fill" : "pi pi-star"}
708 onClick={handleCollection}
709 style={{
710 cursor: 'pointer',
711 fontSize: '30px',
712 color: isCollected ? 'rgba(82, 102, 101, 1)' : 'inherit',
713 transition: 'color 0.3s ease'
714 }}
715 />
716 <span>{formatCount(collectionCount)}</span>
717 </div>
718
719
720
721 <div className="resource-operation-detail-data">
722 {isLiked ? <SmilingFace
723 theme="filled"
724 size="30"
725 fill="#526665"
726 strokeWidth={5}
727 onClick={handleLike}
728 style={{ cursor: 'pointer' }}
729 />
730 : <SmilingFace
731 theme="outline"
732 size="30"
733 fill="#526665"
734 strokeWidth={5}
735 onClick={handleLike}
736 style={{ cursor: 'pointer' }}
737 />
738 }
739 <span>{formatCount(likeCount)}</span>
740 </div>
741
742 <ButtonGroup >
743 {isPurchase()}
744 <Button label={"$" + resource?.price} style={{
745 height: "44px", background: "rgba(82, 102, 101, 1)",
746 borderStyle: "solid", borderWidth: "1px", borderColor: "rgba(82, 102, 101, 1)",
747 borderRadius: "0 20px 20px 0", fontSize: "26px",
748 }} disabled={true} />
749 </ButtonGroup>
750 </div>
751 </div>
752 {/*资源详情*/}
753 <div className="resource-detail">
754 <h1 className="resource-detail-title">资源详情</h1>
755 <div className="resource-detail-text">
756 {resource?.resourceDetail}
757 </div>
758 </div>
759 {/* 评论列表 */}
760 <div className="comments-section">
761 <div className="comments-header">
762 <h2>评论 ({totalComments})</h2>
763 <Link href="/community" className="no-underline">进入社区</Link>
764 </div>
765 <div className="comments-input">
766 <Avatar image={process.env.NEXT_PUBLIC_NGINX_URL + "users/" + userInfo.avatar} size="large" shape="circle" />
767 <InputText value={commentValue} placeholder="发布你的评论" onChange={(e) => setCommentValue(e.target.value)} />
768 <Button label="发布评论" onClick={publishComment} disabled={!commentValue.trim()} />
769 </div>
770 <div className="comments-list">
771 {comments.map((comment, index) => (
772 <div key={comment.commentId} className="comment-item">
773 <div className="comment-user">
774 <Avatar
775 image={comment.userId ? process.env.NEXT_PUBLIC_NGINX_URL + "users/" + commentUserInfos.get(comment.userId)?.avatar : '/default-avatar.png'}
776 size="normal"
777 shape="circle"
778 />
779 <div className="comment-meta">
780 <span className="username">
781 {comment.userId ? commentUserInfos.get(comment.userId)?.username : '匿名用户'}
782 </span>
783 <div className="comment-time">
784 <span className="floor">#{first + index + 1}楼</span>
785 <span className="time">{comment.createAt}</span>
786 </div>
787 </div>
788 <i className='pi pi-ellipsis-v' onClick={(e) => ops.current[index].toggle(e)} />
789 </div>
790 <div className="comment-content">
791 {<span className="reply-to">{getReplyUserName(comment.replyId)}</span>}
792 <p>{comment.content}</p>
793 </div>
794 <OverlayPanel // 回调 ref:把实例放到 ops.current 对应的位置
795 ref={el => {
796 if (el) ops.current[index] = el;
797 }}>
798 <Button label="回复" text size="small" onClick={() => setVisibleReply(true)} />
799 {comment.userId === userId &&
800 <Button
801 label="删除"
802 text
803 size="small"
804 onClick={() => { console.log('Deleting comment:', comment.commentId, 'by user:', comment.userId); deleteComment(comment.commentId) }}
805 />
806 }
807 </OverlayPanel>
808 <Sidebar className='reply' header={ReplyHeader} visible={visibleReply} position="bottom" onHide={() => setVisibleReply(false)}>
809 <div className="reply-input">
810 <Avatar image={process.env.NEXT_PUBLIC_NGINX_URL + "users/" + userInfo.avatar} size="large" shape="circle" />
811 <InputText value={replyValue} placeholder="发布你的评论" onChange={(e) => setReplyValue(e.target.value)} />
812 <Button label="发布评论" onClick={() => publishReply(comment.commentId)} disabled={!replyValue.trim()} />
813 </div>
814 </Sidebar>
815 </div>
816 ))}
817 {totalComments > 5 && (<Paginator className="Paginator" first={first} rows={rows} totalRecords={totalComments} rowsPerPageOptions={[5, 10]} onPageChange={onPageChange} />)}
818 </div>
819 </div>
820
821 {/*用户购买资源弹窗*/}
822 <Dialog
823 header="购买资源"
824 visible={visible}
825 onHide={() => setVisible(false)}
826 className="purchase-dialog"
827 modal
828 footer={
829 <div className="dialog-footer">
830 <Button label="购买" icon="pi pi-check" onClick={handlePurchase} autoFocus />
831 <Button label="取消" icon="pi pi-times" onClick={() => setVisible(false)} className="p-button-text" />
832 </div>
833 }
834 >
835 <div className="form-text">
836 购买该资源需要{resource?.price}积分,是否购买?
837 </div>
838 </Dialog>
839 </div>
lfmylbhf789e7172025-06-06 18:37:21 +0800840 )
841}
842