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