add resource detail and user part interface
Change-Id: I83541b5dc80bde465878cdccdbc77431b56dbff6
diff --git "a/src/app/resource/resource-detail/\133resourceId\135/page.tsx" "b/src/app/resource/resource-detail/\133resourceId\135/page.tsx"
index c48b626..8d28596 100644
--- "a/src/app/resource/resource-detail/\133resourceId\135/page.tsx"
+++ "b/src/app/resource/resource-detail/\133resourceId\135/page.tsx"
@@ -1,12 +1,905 @@
'use client';
-import React from 'react';
+import React, {useEffect, useRef, useState} from 'react';
+import {Image} from "primereact/image";
+import {Button} from "primereact/button";
+import {Avatar} from "primereact/avatar";
+import {ButtonGroup} from "primereact/buttongroup";
+import {InputText} from "primereact/inputtext";
+import { Dialog } from 'primereact/dialog';
+// 引入图标
+import 'primeicons/primeicons.css';
+// 引入火苗图标
+import Fire from '@icon-park/react/lib/icons/Fire';
+import SmilingFace from '@icon-park/react/lib/icons/SmilingFace';
+// 消息提醒
+import { Toast } from 'primereact/toast';
+// 接口传输
+import axios from "axios";
+// 页面跳转
+import { useParams } from 'next/navigation'
+import { useRouter } from 'next/navigation';
+import Link from 'next/link';
+// 回复评论
+import { OverlayPanel } from 'primereact/overlaypanel';
+import { Sidebar } from 'primereact/sidebar';
+// 分页
+import { Paginator, PaginatorPageChangeEvent } from 'primereact/paginator';
-const EmptyPage: React.FC = () => {
- return (
- <div className="p-d-flex p-jc-center p-ai-center" style={{ height: '100vh' }}>
- {"一个空页面"}
- </div>
+// 样式
+import './resource-detail.scss';
+
+// 种子
+interface Torrent {
+ torrentRecordId: number;
+ torrentUrl: string;
+ infoHash: string;
+ uploadTime: string;
+ uploaderUserId: number;
+}
+
+// 资源版本
+interface ResourceVersion {
+ resourceVersionId: string; // 资源版本id
+ resourceVersionName: string; // 资源版本名称
+ compatibleVersions: string[]; // 兼容的游戏版本列表
+ torrentList: Torrent[]; // 种子列表
+}
+
+// 资源信息
+interface Resource {
+ resourceId: number;
+ resourceName: string; // 资源标题
+ resourcePicture: string; // 资源照片网址
+ resourceSummary: string; // 资源简介(一句话)
+ resourceDetail: string; // 资源介绍
+ uploadTime: string; // 上传时间
+ lastUpdateTime: string; // 最近更新时间
+ price: number;
+ downloads: number; // 下载数
+ likes: number; // 点赞数
+ collections: number; // 收藏数
+ comments: number; // 评论数
+ seeds: number; // 种子数
+ classify: string; // 资源分类(材质包:resourcePack,模组:mod,整合包:modPack ,地图:map
+ hot: number; // 资源热度
+ gameplayList: string[]; // 资源标签
+ resourceVersionList: ResourceVersion[]; // 资源版本列表
+ isCollect: boolean; // 是否被收藏
+ isLike: boolean; // 是否被点赞
+ isPurchase: boolean; // 是否被购买
+ isUpload: boolean; // 是否是该用户上传的
+ userId: number; // 资源上传者的id
+}
+
+// 评论信息
+interface Comment {
+ commentId: number;
+ userId: number | null;
+ replyId: number;
+ resourceId: number;
+ reawardId: number;
+ content: string;
+ createAt: string;
+}
+// 评论列表
+interface CommentList {
+ total: number; // 评论总数
+ records: Comment[]; // 当前页评论数组
+}
+
+// 用户信息
+interface UserInfo {
+ userId: number;
+ username: string;
+ avatar: string;
+ signature: string;
+}
+// 新评论接口
+interface NewComment {
+ userId: number;
+ threadId: number;
+ resourceId: number;
+ rewardId: number;
+ replyId: number;
+ content: string;
+ createAt: string;
+}
+
+// 资源作者
+interface ResourceAuthor {
+ userId: number;
+ username: string;
+ avatar: string;
+ signature: string;
+}
+
+// 关注
+interface Subscriber {
+ userId: number;
+ username: string;
+}
+
+// 关注列表
+interface SubscriberList {
+ userList: Subscriber[];
+}
+
+export default function ResourceDetail() {
+ // 获取URL参数
+ const params = useParams<{ resourceId: string }>();
+ const resourceId = decodeURIComponent(params.resourceId); // 防止中文路径乱码
+ // 页面跳转
+ const router = useRouter();
+
+ // 资源信息
+ const [resource, setResource] = useState<Resource | null>(null);
+ // 资源作者信息
+ const [resourceAuthor, setResourceAuthor] = useState<ResourceAuthor>();
+ // 资源作者id
+ const [resourceAuthorId, setResourceAuthorId] = useState<number>(0);
+ // 关注列表
+ const [subscriberList, setSubscriberList] = useState<SubscriberList>();
+ // 添加本地关注状态
+ const [isSubscribed, setIsSubscribed] = useState(false);
+ // 添加本地收藏状态
+ const [isCollected, setIsCollected] = useState(false);
+ const [collectionCount, setCollectionCount] = useState(0);
+ // 添加本地点赞状态
+ const [isLiked, setIsLiked] = useState(false);
+ const [likeCount, setLikeCount] = useState(0);
+ // 发帖人信息
+ const [userInfo, setUserInfo] = useState<UserInfo | null>(null);
+ // 评论人信息
+ const [commentUserInfos, setCommentUserInfos] = useState<Map<number, UserInfo>>(new Map());
+ //评论
+ const [comments, setComments] = useState<Comment[]>([]);
+ const [commentValue, setCommentValue] = useState<string>('');
+ const [totalComments, setTotalComments] = useState<number>(0);
+ // 回复
+ const [replyValue, setReplyValue] = useState<string>('');
+ const [visibleReply, setVisibleReply] = useState<boolean>(false);// 回复评论可视
+ // 评论选择框
+ const ops = useRef<OverlayPanel[]>([]);
+ // 购买弹窗状态
+ const [visible, setVisible] = useState<boolean>(false);
+ // 消息提醒
+ const toast = useRef<Toast>(null);
+
+ // 分页
+ const [first, setFirst] = useState<number>(0);
+ const [rows, setRows] = useState<number>(5);
+ const onPageChange = (event: PaginatorPageChangeEvent) => {
+ setFirst(event.first);
+ setRows(event.rows);
+ };
+
+ useEffect(() => {
+ fetchResourceInfo();
+ }, []);
+
+
+ // 获取资源信息
+ const fetchResourceInfo = async () => {
+ try {
+ // console.log(resourceId);
+ const response = await axios.get<Resource>(process.env.PUBLIC_URL +`/resource/info`, {
+ params: { resourceId: resourceId, userId: 22301010 }
+ });
+ console.log('获取资源信息:', response.data);
+ setResource(response.data);
+
+ // 初始化本地收藏状态
+ if (response.data) {
+ setIsCollected(response.data.isCollect);
+ setCollectionCount(response.data.collections);
+ }
+
+ setResourceAuthorId(response.data.userId);
+ } catch (err) {
+ console.error('获取资源信息失败', err);
+ toast.current?.show({ severity: 'error', summary: 'error', detail: "获取资源信息失败" });
+ }
+ };
+
+ // 获取到资源作者id时,获取资源作者信息
+ useEffect(() => {
+ fetchResourceAuthor();
+ }, [resourceAuthorId]);
+
+ // 获取资源作者信息
+ const fetchResourceAuthor = async () => {
+ try {
+ // console.log(resourceId);
+ // console.log(resourceAuthorId);
+ const response = await axios.get<ResourceAuthor>(process.env.PUBLIC_URL +`/user/info`, {
+ params: { userId: resourceAuthorId }
+ });
+ console.log('获取资源作者信息:', response.data);
+ setResourceAuthor(response.data);
+ // setResourceAuthorId(response.data.userId);
+ } catch (err) {
+ console.error('获取资源作者信息失败', err);
+ toast.current?.show({ severity: 'error', summary: 'error', detail: "获取资源作者信息失败" });
+ }
+ };
+
+ useEffect(() => {
+ fetchSubscriber();
+ }, []);
+
+ // 获取正在浏览资源的用户的关注列表
+ const fetchSubscriber = async () => {
+ try {
+ const response = await axios.get<SubscriberList>(process.env.PUBLIC_URL + `/user/subscriber`, {
+ params: {userId: 223010100}
+ });
+ console.log("关注列表:", response.data);
+ setSubscriberList(response.data);
+ } catch (err) {
+ console.error('获取浏览用户关注列表失败', err);
+ toast.current?.show({ severity: 'error', summary: 'error', detail: "获取浏览用户关注列表失败" });
+ }
+ }
+
+ useEffect(() => {
+ if (!resource?.userId || !subscriberList?.userList) return;
+
+ const authorId = resource.userId;
+ // 设置 isSubscribed 状态
+ const subscribed = subscriberList.userList.some(user => user.userId === authorId);
+ setIsSubscribed(subscribed);
+ }, [subscriberList, resource]);
+
+ // 若浏览用户与资源作者是同一人,则不显示关注按钮。若不是同一人,则显示按钮
+ const handleSubscribe = () => {
+ // 资源作者 ID
+ const authorId = resource?.userId;
+ // 当前登录用户 ID
+ const currentUserId = 223010100;
+
+ // 资源作者与浏览用户是同一人,不显示按钮
+ if (!authorId || authorId == currentUserId){
+ return null;
+ }
+
+ return isSubscribed ? (
+ // 如果已关注,显示“取消关注”按钮
+ <Button
+ label="取消关注"
+ onClick={async () => {
+ try {
+ const response = await axios.delete(
+ process.env.PUBLIC_URL + '/user/subscription',
+ {
+ params: { userId: currentUserId, followerId: authorId }
+ }
+ );
+
+ if (response.status === 200) {
+ setIsSubscribed(false); // 🔥立刻更新按钮状态
+ toast.current?.show({
+ severity: 'success',
+ summary: '取消成功',
+ detail: '已取消关注该用户',
+ });
+
+ fetchSubscriber(); // 重新拉取完整关注列表
+ }
+ } catch (error) {
+ console.error('取消关注失败:', error);
+ toast.current?.show({
+ severity: 'error',
+ summary: '错误',
+ detail: '取消关注失败',
+ });
+ }
+ }}
+ />
+ ) : (
+ // 若未关注,则显示关注按钮
+ <Button
+ label="关注"
+ onClick={async () => {
+ try {
+ const postData = {
+ userId: currentUserId,
+ followerId: authorId,
+ };
+ const response = await axios.post(
+ process.env.PUBLIC_URL + '/user/subscription',
+ postData
+ );
+
+ if (response.status === 200) {
+ setIsSubscribed(true); // 🔥立刻更新按钮状态
+ toast.current?.show({
+ severity: 'success',
+ summary: '关注成功',
+ detail: '已成功关注该用户',
+ });
+
+ fetchSubscriber(); // 刷新列表
+ }
+ } catch (error) {
+ console.error('关注失败:', error);
+ toast.current?.show({
+ severity: 'error',
+ summary: '错误',
+ detail: '关注失败',
+ });
+ }
+ }}
+ />
+ );
+
+
+ // if (isSubscribed) {
+ // return (
+ // <Button
+ // label="取消关注"
+ // onClick={async () => {
+ // try {
+ // const response = await axios.delete(
+ // process.env.PUBLIC_URL + '/user/subscription', {
+ // params: {userId: currentUserId, followerId: authorId}
+ // }
+ // );
+ //
+ // if (response.status === 200) {
+ // toast.current?.show({
+ // severity: 'success',
+ // summary: '取消成功',
+ // detail: '已取消关注该用户',
+ // });
+ // // 重新拉取关注列表
+ // fetchSubscriber();
+ // }
+ // } catch (error) {
+ // console.error('取消关注失败:', error);
+ // toast.current?.show({
+ // severity: 'error',
+ // summary: '错误',
+ // detail: '取消关注失败',
+ // });
+ // }
+ // }}
+ // />
+ // );
+ // } else {
+ // // 未关注,显示“关注”按钮
+ // return (
+ // <Button
+ // label="关注"
+ // onClick={async () => {
+ // try {
+ // const postData = {
+ // userId: currentUserId,
+ // followerId: authorId,
+ // };
+ // const response = await axios.post(
+ // process.env.PUBLIC_URL + '/user/subscription',
+ // postData
+ // );
+ //
+ // if (response.status === 200) {
+ // toast.current?.show({
+ // severity: 'success',
+ // summary: '关注成功',
+ // detail: '已成功关注该用户',
+ // });
+ // // 重新拉取关注列表
+ // fetchSubscriber();
+ // }
+ // } catch (error) {
+ // console.error('关注失败:', error);
+ // toast.current?.show({
+ // severity: 'error',
+ // summary: '错误',
+ // detail: '关注失败',
+ // });
+ // }
+ // }}
+ // />
+ // );
+ // }
+ }
+
+ // 判断该资源是否已被购买, 返回不同的购买按钮
+ const isPurchase = () => {
+ // 作者本人查看资源,不显示购买按钮
+ if (resource?.userId == 223010100) {
+ return ;
+ }
+
+ // 该资源已被购买
+ if (resource?.isPurchase) {
+ return (
+ <Button label="已购买" style={{width:"120px", height:"44px",
+ borderRadius:"20px 0 0 20px",}} disabled={true}/>
+ )
+ } else {
+ // 该资源未被购买
+ return (
+ <Button
+ label="立即购买"
+ style={{width:"120px", height:"44px",
+ borderRadius:"20px 0 0 20px",}}
+ onClick={() => setVisible(true)}
+ />
+ )
+ }
+ }
+
+ // 购买按钮接口
+ const handlePurchase = async () => {
+ try {
+ const postData = {
+ userId: 223010100, // 记得用户登录状态获取
+ resourceId: resource?.resourceId
+ };
+ // 发送POST请求
+ const response = await axios.post(process.env.PUBLIC_URL + '/resource/purchase', postData);
+
+ if (response.status === 200) {
+ toast.current?.show({ severity: 'success', summary: 'Success', detail: '成功购买资源' });
+ // 购买成功
+ setVisible(false);
+ // 刷新购买按钮
+ isPurchase();
+ } else if (response.status === 412) {
+ toast.current?.show({ severity: 'error', summary: 'error', detail: '积分不足,购买失败' });
+ }
+ } catch (error) {
+ console.error('购买资源失败:', error);
+ toast.current?.show({ severity: 'error', summary: 'error', detail: '购买资源失败' });
+ }
+ };
+
+ // 处理收藏操作
+ const handleCollection = async () => {
+ const newCollectionState = !isCollected;
+ const newCollectionCount = newCollectionState ? collectionCount + 1 : collectionCount - 1;
+
+ // 立即更新本地状态
+ setIsCollected(newCollectionState);
+ setCollectionCount(newCollectionCount);
+
+ try {
+ if (newCollectionState) {
+ // 收藏操作
+ await axios.post(process.env.PUBLIC_URL +`/resource/collection`, {
+ params: { resourceId: resourceId, userId: 22301010 }
+ });
+ console.log('收藏资源');
+ } else {
+ // 取消收藏操作
+ await axios.delete(process.env.PUBLIC_URL +`/resource/collection`, {
+ params: { resourceId: resourceId, userId: 22301010 }
+ });
+ console.log('取消收藏资源');
+ }
+ } catch (err) {
+ console.error(newCollectionState ? '收藏资源失败' : '取消收藏失败', err);
+ toast.current?.show({
+ severity: 'error',
+ summary: 'error',
+ detail: newCollectionState ? "收藏资源失败" : "取消收藏失败"
+ });
+
+ // 如果请求失败,回滚状态
+ setIsCollected(!newCollectionState);
+ setCollectionCount(newCollectionState ? collectionCount - 1 : collectionCount + 1);
+ }
+ };
+
+ // 处理点赞行为
+ const handleLike = async () => {
+ const newLikeState = !isLiked;
+ const newLikeCount = newLikeState ? likeCount + 1 : likeCount - 1;
+
+ // 立即更新本地状态
+ setIsLiked(newLikeState);
+ setLikeCount(newLikeCount);
+
+ try {
+ if (newLikeState) {
+ // 点赞操作
+ await axios.post(process.env.PUBLIC_URL +`/resource/like`, {
+ params: { resourceId: resourceId, userId: 22301010 }
+ });
+ console.log('点赞资源');
+ } else {
+ // 取消点赞操作
+ await axios.delete(process.env.PUBLIC_URL +`/resource/like`, {
+ params: { resourceId: resourceId, userId: 22301010 }
+ });
+ console.log('取消点赞资源');
+ }
+ } catch (err) {
+ console.error(newLikeState ? '点赞资源失败' : '取消点赞失败', err);
+ toast.current?.show({
+ severity: 'error',
+ summary: 'error',
+ detail: newLikeState ? "点赞资源失败" : "取消点赞失败"
+ });
+
+ // 如果请求失败,回滚状态
+ setIsLiked(!newLikeState);
+ setLikeCount(newLikeState ? newLikeCount - 1 : newLikeCount + 1);
+ }
+ };
+
+ // 格式化数字显示 (3000 -> 3k)
+ const formatCount = (count?: number): string => {
+ if (count == null) return "0"; // 同时处理 undefined/null
+
+ const absCount = Math.abs(count); // 处理负数
+
+ const format = (num: number, suffix: string) => {
+ const fixed = num.toFixed(1);
+ return fixed.endsWith('.0')
+ ? `${Math.floor(num)}${suffix}`
+ : `${fixed}${suffix}`;
+ };
+
+ if (absCount >= 1e6) return format(count / 1e6, "m");
+ if (absCount >= 1e3) return format(count / 1e3, "k");
+ return count.toString();
+ };
+
+ // 获取发帖人
+ useEffect(() => {
+ if (!resource) return;
+ // 发帖人
+ axios.get(process.env.PUBLIC_URL +`/user/info?userId=${resource?.userId}`)
+ .then(res => setUserInfo(res.data))
+ .catch(console.error);
+ }, [resource]);
+
+ // 当 resourceId 或分页参数变化时重新拉评论
+ useEffect(() => {
+ if (!resourceId) return;
+
+ fetchComments();
+ }, [resourceId, first, rows]);
+
+
+ //通过评论ID获取评论人信息
+ const getReplyUserName = (replyId: number) => {
+ if (replyId == null || replyId == 0) return '';
+ const replyComment = comments.find(comment => comment.commentId === replyId);
+ if (!replyComment?.userId) return '匿名用户';
+ return "回复 " + commentUserInfos.get(replyComment.userId)?.username || '匿名用户';
+ };
+
+ // 获取该资源的评论
+ const fetchComments = async () => {
+ try {
+ const pageNumber = first / rows + 1;
+ console.log("当前页" + pageNumber + "size" + rows);
+ const response = await axios.get<CommentList>(
+ process.env.PUBLIC_URL +`/comments`, {
+ params: { id: resourceId, pageNumber, rows, type: 'resource' }
+ }
+ );
+ console.log('获取评论列表:', response.data.records);
+ setComments(response.data.records);
+ setTotalComments(response.data.total);
+ // 拉取评论对应用户信息
+ response.data.records.forEach(comment => {
+ if (comment.userId != null && !commentUserInfos.has(comment.userId)) {
+ axios.get<UserInfo>(
+ process.env.PUBLIC_URL +`/user/info`,
+ { params: { userId: comment.userId } }
+ ).then(res => {
+ setCommentUserInfos(prev => new Map(prev).set(comment.userId!, res.data));
+ });
+ }
+ });
+ } catch (err) {
+ console.error('获取评论失败', err);
+ toast.current?.show({ severity: 'error', summary: 'error', detail: '获取评论失败' });
+ }
+ };
+
+ // 回复评论接口
+ const publishReply = async (commentId: number) => {
+ if (!replyValue.trim() || !resource) return;
+ console.log('发布评论:', commentId);
+ // console.log(typeof resourceId);
+ try {
+ const newComment: NewComment = {
+ userId: 22301145,
+ rewardId: 0,
+ threadId: 0,
+ resourceId: resource.resourceId,
+ replyId: commentId,
+ content: commentValue,
+ createAt: new Date().toISOString().slice(0, 19).replace('T', ' ')
+ };
+
+ const response = await axios.post(process.env.PUBLIC_URL +'/comment', newComment);
+
+ if (response.status === 200) {
+ toast.current?.show({ severity: 'success', summary: 'Success', detail: '回复成功' });
+ // 更新评论列表
+ fetchComments();
+ setVisibleReply(false)
+ // 清空输入框
+ setReplyValue('');
+ }
+ } catch (error) {
+ console.error('发布评论失败:', error);
+ toast.current?.show({ severity: 'error', summary: 'error', detail: '回复失败' });
+ }
+ };
+
+ // 发布评论接口
+ const publishComment = async () => {
+ if (!commentValue.trim() || !resource) return;
+
+ try {
+ const newComment: NewComment = {
+ userId: 22301145,
+ rewardId: 0,
+ threadId: 0,
+ resourceId: resource.resourceId,
+ replyId: 0, // 直接评论,不是回复
+ content: commentValue,
+ createAt: new Date().toISOString().slice(0, 19).replace('T', ' ')
+ };
+
+ const response = await axios.post(process.env.PUBLIC_URL +'/comment', newComment);
+
+ if (response.status === 200) {
+ toast.current?.show({ severity: 'success', summary: 'Success', detail: '评论成功' });
+ // 更新评论列表
+ fetchComments();
+ // 清空输入框
+ setCommentValue('');
+ }
+ } catch (error) {
+ console.error('发布评论失败:', error);
+ toast.current?.show({ severity: 'error', summary: 'error', detail: '发布评论失败' });
+ }
+ };
+
+ // 删除评论接口
+ const deleteComment = async (commentId: number) => {
+ if (!resourceId) return;
+
+ try {
+ // 调用 DELETE 接口,URL 中最后一段是要删除的 commentId
+ const response = await axios.delete(
+ process.env.PUBLIC_URL +`/comment?commentId=${commentId}`
+ );
+
+ if (response.status === 200) {
+ fetchComments();
+ toast.current?.show({ severity: 'success', summary: 'Success', detail: '删除评论成功' });
+ } else {
+ toast.current?.show({ severity: 'error', summary: 'error', detail: '删除评论失败' });
+ console.error('删除评论失败,状态码:', response.status);
+ }
+ } catch (error) {
+ console.error('删除评论接口报错:', error);
+ }
+ };
+
+ const ReplyHeader = (
+ <div className="flex align-items-center gap-1">
+ <h3>回复评论</h3>
+ </div>
);
-};
+ if (!resourceId || !userInfo) return <div>Loading...</div>;
-export default EmptyPage;
+ return(
+ <div className="resource-detail-container">
+ <Toast ref={toast}></Toast>
+ {/*资源标题*/}
+ <div className="resource-header">
+ {resource?.resourceName}
+ </div>
+ {/*资源详细信息*/}
+ <div className="resource-info">
+ <Image
+ src={(process.env.NEXT_PUBLIC_NGINX_URL! + resource?.resourcePicture)}
+ alt= {resource?.resourceName}
+ width="540px"
+ height="300px"
+ />
+ <div className="resource-info-detail">
+ {/*资源热度*/}
+ <div className="resource-hot">
+ <Fire theme="outline" size="50" fill="#f5a623" strokeWidth={3}/>
+ <span className="resource-hot-data">{resource?.hot}</span>
+ </div>
+
+ {/*资源标签*/}
+ <div className="resource-label">
+ {resource?.gameplayList.map((tag, index) => (
+ <Button
+ key={index} label={tag}
+ className="resource-label-button"
+ onClick={() => {
+ router.push(`/resource/classification/`)
+ }}
+ />
+ ))}
+ </div>
+
+ {/*资源浏览量和下载量*/}
+ <div className="resource-data">
+ <div className="resource-data-container">
+ <i className="pi pi-download"/>
+ <span className="resource-data-container-number">下载量:{formatCount(resource?.downloads)}</span>
+ </div>
+ </div>
+
+ {/*资源发布时间和更新时间*/}
+ <div className="resource-time">
+ <div className="resource-time-data">
+ 发布时间:{resource?.uploadTime}
+ </div>
+ <div className="resource-time-data">
+ 更新时间:{resource?.lastUpdateTime}
+ </div>
+ </div>
+ </div>
+ </div>
+ {/*资源总结*/}
+ <div className="resource-summary">
+ {resource?.resourceSummary}
+ </div>
+ {/*关注作者、点赞、抽藏、购买资源*/}
+ <div className="resource-operation">
+ <div className="resource-author">
+ <Avatar
+ image={`${process.env.NEXT_PUBLIC_NGINX_URL}/users/${resourceAuthor?.avatar}`}
+ shape="circle"
+ style={{ width: "60px", height: "60px" }}
+ />
+ <span className="resource-author-name">{resourceAuthor?.username}</span>
+
+ {handleSubscribe()}
+ </div>
+
+ <div className="resource-operation-detail">
+ <div className="resource-operation-detail-data">
+ <i
+ className={isCollected ? "pi pi-star-fill" : "pi pi-star"}
+ onClick={handleCollection}
+ style={{
+ cursor: 'pointer',
+ fontSize: '30px',
+ color: isCollected ? 'rgba(82, 102, 101, 1)' : 'inherit',
+ transition: 'color 0.3s ease'
+ }}
+ />
+ <span>{formatCount(collectionCount)}</span>
+ </div>
+
+
+
+ <div className="resource-operation-detail-data">
+ {isLiked ? <SmilingFace
+ theme="filled"
+ size="30"
+ fill="#526665"
+ strokeWidth={5}
+ onClick={handleLike}
+ style={{ cursor: 'pointer' }}
+ />
+ : <SmilingFace
+ theme="outline"
+ size="30"
+ fill="#526665"
+ strokeWidth={5}
+ onClick={handleLike}
+ style={{ cursor: 'pointer' }}
+ />
+ }
+ <span>{formatCount(likeCount)}</span>
+ </div>
+
+ <ButtonGroup >
+ {isPurchase()}
+ <Button label={"$" + resource?.price} style={{
+ height:"44px", background:"rgba(82, 102, 101, 1)",
+ borderStyle:"solid", borderWidth:"1px", borderColor:"rgba(82, 102, 101, 1)",
+ borderRadius:"0 20px 20px 0", fontSize:"26px",
+ }} disabled={true}/>
+ </ButtonGroup>
+ </div>
+ </div>
+ {/*资源详情*/}
+ <div className="resource-detail">
+ <h1 className="resource-detail-title">资源详情</h1>
+ <div className="resource-detail-text">
+ {resource?.resourceDetail}
+ </div>
+ </div>
+ {/* 评论列表 */}
+ <div className="comments-section">
+ <div className="comments-header">
+ <h2>评论 ({totalComments})</h2>
+ <Link href="/community" className="no-underline">进入社区</Link>
+ </div>
+ <div className="comments-input">
+ <Avatar image={process.env.NEXT_PUBLIC_NGINX_URL + "users/" + userInfo.avatar} size="large" shape="circle" />
+ <InputText value={commentValue} placeholder="发布你的评论" onChange={(e) => setCommentValue(e.target.value)} />
+ <Button label="发布评论" onClick={publishComment} disabled={!commentValue.trim()} />
+ </div>
+ <div className="comments-list">
+ {comments.map((comment, index) => (
+ <div key={comment.commentId} className="comment-item">
+ <div className="comment-user">
+ <Avatar
+ image={comment.userId ? process.env.NEXT_PUBLIC_NGINX_URL + "users/" + commentUserInfos.get(comment.userId)?.avatar : '/default-avatar.png'}
+ size="normal"
+ shape="circle"
+ />
+ <div className="comment-meta">
+ <span className="username">
+ {comment.userId ? commentUserInfos.get(comment.userId)?.username : '匿名用户'}
+ </span>
+ <div className="comment-time">
+ <span className="floor">#{first + index + 1}楼</span>
+ <span className="time">{comment.createAt}</span>
+ </div>
+ </div>
+ <i className='pi pi-ellipsis-v' onClick={(e) => ops.current[index].toggle(e)} />
+ </div>
+ <div className="comment-content">
+ {<span className="reply-to">{getReplyUserName(comment.replyId)}</span>}
+ <p>{comment.content}</p>
+ </div>
+ <OverlayPanel // 回调 ref:把实例放到 ops.current 对应的位置
+ ref={el => {
+ if (el) ops.current[index] = el;
+ }}>
+ <Button label="回复" text size="small" onClick={() => setVisibleReply(true)} />
+ {comment.userId === 22301010 &&
+ <Button
+ label="删除"
+ text
+ size="small"
+ onClick={() => { console.log('Deleting comment:', comment.commentId, 'by user:', comment.userId); deleteComment(comment.commentId) }}
+ />
+ }
+ </OverlayPanel>
+ <Sidebar className='reply' header={ReplyHeader} visible={visibleReply} position="bottom" onHide={() => setVisibleReply(false)}>
+ <div className="reply-input">
+ <Avatar image={process.env.NEXT_PUBLIC_NGINX_URL + "users/" + userInfo.avatar} size="large" shape="circle" />
+ <InputText value={replyValue} placeholder="发布你的评论" onChange={(e) => setReplyValue(e.target.value)} />
+ <Button label="发布评论" onClick={() => publishReply(comment.commentId)} disabled={!replyValue.trim()} />
+ </div>
+ </Sidebar>
+ </div>
+ ))}
+ {totalComments > 5 && (<Paginator className="Paginator" first={first} rows={rows} totalRecords={totalComments} rowsPerPageOptions={[5, 10]} onPageChange={onPageChange} />)}
+ </div>
+ </div>
+
+ {/*用户购买资源弹窗*/}
+ <Dialog
+ header="购买资源"
+ visible={visible}
+ onHide={() => setVisible(false)}
+ className="purchase-dialog"
+ modal
+ footer={
+ <div className="dialog-footer">
+ <Button label="购买" icon="pi pi-check" onClick={handlePurchase} autoFocus />
+ <Button label="取消" icon="pi pi-times" onClick={() => setVisible(false)} className="p-button-text" />
+ </div>
+ }
+ >
+ <div className="form-text">
+ 购买该资源需要{resource?.price}积分,是否购买?
+ </div>
+ </Dialog>
+ </div>
+ )
+}
+
diff --git "a/src/app/resource/resource-detail/\133resourceId\135/resource-detail.scss" "b/src/app/resource/resource-detail/\133resourceId\135/resource-detail.scss"
new file mode 100644
index 0000000..3df502d
--- /dev/null
+++ "b/src/app/resource/resource-detail/\133resourceId\135/resource-detail.scss"
@@ -0,0 +1,266 @@
+//全局容器样式
+.resource-detail-container {
+ max-width: 1200px;
+ margin: 0 auto;
+ padding: 0 2rem;
+
+ //资源标题样式
+ .resource-header {
+ font-size: 34px;
+ color: rgba(63, 74, 73, 1);
+ margin-top: 22px;
+ margin-bottom: 20px;
+ }
+
+ //资源信息样式
+ .resource-info {
+ display: flex;
+
+ .resource-info-detail {
+ display: flex;
+ flex-direction: column;
+ margin-left: 35px;
+ justify-content: space-around;
+
+ //资源热度样式
+ .resource-hot {
+ display: flex;
+
+ .resource-hot-data {
+ font-size: 28px;
+ color: rgba(255, 87, 51, 1);
+ margin: auto 0 auto 20px;
+ }
+ }
+
+ //资源标签样式
+ .resource-label {
+ .resource-label-button {
+ margin-right: 15px;
+ width: 80px;
+ height: 34px;
+ border-radius: 15px;
+ border: 1px solid rgba(166, 166, 166, 1);
+ background-color: rgba(166, 166, 166, 1);
+ }
+ }
+
+ //浏览量和下载量样式
+ .resource-data {
+ //margin-top: 10px;
+ color: rgba(82, 102, 101, 1);
+
+ .pi-eye {
+ font-size: 21px;
+ }
+
+ .pi-download {
+ font-size: 21px;
+ }
+
+ .resource-data-container-number{
+ font-size: 24px;
+ margin-left: 8px;
+ }
+ }
+
+ //发布时间和更新时间样式
+ .resource-time {
+ //margin-top: 10px;
+ color: rgba(82, 102, 101, 1);
+ }
+ }
+ }
+
+
+ //资源简介
+ .resource-summary {
+ margin-top: 15px;
+ margin-bottom: 15px;
+ color: rgba(28, 46, 43, 1);
+ font-size: 18px;
+ }
+
+ //资源详情样式
+ .resource-detail {
+ white-space: pre-line; /* 保留换行符 */
+ line-height: 25px; /* 增加行高增强可读性 */
+ }
+
+ //资源操作部分的样式
+ .resource-operation {
+ display: flex;
+ justify-content: space-between;
+ margin-bottom: 40px;
+ margin-top: 40px;
+
+ .resource-author {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ width: 380px;
+
+ .resource-author-name {
+ display: flex;
+ justify-content: center;
+ font-size: 19px;
+ color: rgba(82, 102, 101, 1);
+ width: 200px;
+ }
+
+ button {
+ height: 40px;
+ width: 110px;
+ background-color: rgba(82, 102, 101, 1);
+ border: 1px solid rgba(82, 102, 101, 1);
+ border-radius: 15px;
+ }
+ }
+
+ .resource-operation-detail {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ //height: 44px;
+
+ .resource-operation-detail-data {
+ margin-right: 55px;
+ display: flex;
+ align-items: center;
+
+ span{
+ margin-left:4px;
+ font-size: 17px;
+ }
+ }
+ }
+ }
+
+ //资源详情样式
+ .resource-detail {
+ .resource-detail-title {
+ color: rgba(82, 102, 101, 1);
+ margin-bottom: 25px;
+ margin-left: 0;
+ }
+
+ .resource-detail-text {
+ padding-left: 30px;
+ color: rgba(28, 46, 43, 1);
+ }
+ }
+
+ // 评论区域
+ .comments-section {
+ .comments-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+
+ h2 {
+ font-size: 1.5rem;
+ color: #2d3748;
+ margin-bottom: 1.5rem;
+ }
+ }
+
+ // 评论输入区
+ .comments-input {
+ display: flex;
+ align-items: center;
+ gap: 1rem;
+ padding: 1rem;
+ border-radius: 0.5rem;
+
+ .p-inputtext {
+ flex: 1; // 输入框占据剩余空间
+ height: 3rem;
+ }
+
+ .p-button {
+ height: 3rem;
+ }
+ }
+
+ // 评论列表
+ .comments-list {
+ display: flex;
+ flex-direction: column;
+ margin-top: 1rem;
+ gap: 0.5rem;
+
+ .comment-item {
+ padding: 1.5rem;
+ border-radius: 0.5rem;
+
+ .comment-user {
+ display: flex;
+ align-items: center;
+ gap: 1rem;
+ margin-bottom: 1rem;
+
+ .comment-meta {
+ display: flex;
+ align-items: center;
+ width: 100%;
+ justify-content: space-between;
+ gap: 0.5rem;
+
+ .comment-time {
+ justify-content: space-between;
+ gap: 0.75rem
+ }
+
+ .username {
+ margin-left: 0.5rem;
+ font-weight: 600;
+ color: #2d3748;
+ }
+
+ .floor {
+ color: #718096;
+ margin-right: 0.75rem;
+ font-size: 0.875rem;
+ }
+
+ .time {
+ color: #a0aec0;
+ font-size: 0.875rem;
+ }
+ }
+ }
+
+ .comment-content {
+ padding-left: 3.5rem;
+
+ .reply-to {
+ display: inline-block;
+ color: #93C4C1;
+ font-size: 0.875rem;
+ margin-bottom: 0.5rem;
+ }
+
+ p {
+ color: #4a5568;
+ margin: 0;
+ line-height: 1.5;
+ }
+ }
+ }
+ }
+ }
+
+ // 购买弹窗样式
+ .purchase-dialog {
+ .form-text {
+ }
+ }
+}
+
+// 取消进入社区的下划线
+.no-underline {
+ text-decoration: none;
+ color: rgba(0, 186, 173, 1);
+ font-size: 18px;
+}
+
diff --git a/src/app/user/page.tsx b/src/app/user/page.tsx
new file mode 100644
index 0000000..0af92a4
--- /dev/null
+++ b/src/app/user/page.tsx
@@ -0,0 +1,356 @@
+'use client';
+
+import React, { useEffect, useState, useRef } from 'react';
+import { TabView, TabPanel } from 'primereact/tabview';
+import { Avatar } from 'primereact/avatar';
+import { Button } from 'primereact/button';
+import { Card } from 'primereact/card';
+import {Image} from "primereact/image";
+// 浮动按钮
+import { SpeedDial } from 'primereact/speeddial';
+// 评分图标
+import { Fire } from '@icon-park/react';
+// 消息提醒
+import { Toast } from 'primereact/toast';
+// 页面跳转
+import { useRouter } from "next/navigation";
+// import Link from 'next/link';
+
+// 接口传输
+import axios from "axios";
+
+// 样式
+import './user.scss';
+
+
+
+// 用户信息
+interface UserInfo {
+ userId: number;
+ username: string;
+ password: string;
+ avatar: string;
+ followerCount: number;// 粉丝数
+ subscriberCount: number;// 关注数
+ signature: string;// 个性签名
+ uploadAmount: number;
+ purchaseAmount: number;
+ credits: number;
+}
+
+// 用户数据
+interface UserData {
+ subscriberCount: number; // 关注数
+ uploadAmount: number; // 上传量(资源个数)
+ beDownloadedAmount: number; // 上传资源被下载量
+ seedPercentageList: number[]; // 上传资源类型百分比列表,按材质包、模组、整合包、地图的顺序返回
+}
+
+// 用户发布的资源
+interface Resource {
+ resourceId: number;
+ resourceName: string;
+ resourcePicture: string;
+ resourceSummary: string; // 资源简介(一句话)
+ resourceDetail: string; // 资源介绍
+ uploadTime: string; // 上传时间
+ lastUpdateTime: string; // 最近更新时间
+ price: number;
+ downloads: number;
+ likes: number;
+ collections: number;
+ comments: number;
+ seeds: number; // 种子数
+ classify: string; // 资源分类(材质包:resourcePack,模组:mod,整合包:modPack ,地图:map
+}
+
+// 用户发布的资源列表
+interface ResourceList {
+ records: Resource[];
+}
+
+export default function UserPage() {
+ // 路由
+ const router = useRouter();
+ // 发布资源列表
+ const [resourceList, setResourceList] = useState<Resource[]>([]);
+ // 用户信息
+ const [userInfo, setUserInfo] = useState<UserInfo>();
+ // 用户数据
+ const [userData, setUserData] = useState<UserData>();
+ // 消息提醒
+ const toast = useRef<Toast>(null);
+
+
+ useEffect(() => {
+ fetchUserInfo();
+ fetchUserData();
+ fetchResourceList();
+ }, []);
+
+ // 获取用户信息
+ const fetchUserInfo = async () => {
+ try {
+ const response = await axios.get<UserInfo>(process.env.PUBLIC_URL + `/user/info`, {
+ params: { userId: 22301010 }
+ });
+ console.log('获取用户信息:', response.data);
+ setUserInfo(response.data);
+ } catch (err) {
+ console.error('获取用户信息失败', err);
+ toast.current?.show({ severity: 'error', summary: 'error', detail: '获取用户信息失败' });
+ }
+ };
+
+ // 获取用户数据
+ const fetchUserData = async () => {
+ try {
+ const response = await axios.get<UserData>(process.env.PUBLIC_URL + `/user/data`, {
+ params: { userId: 22301010 }
+ });
+ console.log('获取用户数据:', response.data);
+ setUserData(response.data);
+ } catch (err) {
+ console.error('获取用户数据失败', err);
+ toast.current?.show({ severity: 'error', summary: 'error', detail: '获取用户数据失败' });
+ }
+ };
+
+ // 格式化数字显示 (3000 -> 3k)
+ const formatCount = (count?: number): string => {
+ if (count == null) return "0"; // 同时处理 undefined/null
+
+ const absCount = Math.abs(count); // 处理负数
+
+ const format = (num: number, suffix: string) => {
+ const fixed = num.toFixed(1);
+ return fixed.endsWith('.0')
+ ? `${Math.floor(num)}${suffix}`
+ : `${fixed}${suffix}`;
+ };
+
+ if (absCount >= 1e6) return format(count / 1e6, "m");
+ if (absCount >= 1e3) return format(count / 1e3, "k");
+ return count.toString();
+ };
+
+ // 获取发布资源
+ const fetchResourceList = async () => {
+ try {
+ const response = await axios.get<ResourceList>(process.env.PUBLIC_URL +`/user/upload`, {
+ params: { userId: 22301010, pageNumber: 1, rows: 3 }
+ });
+ console.log('获取发布资源列表:', response.data.records);
+ setResourceList(response.data.records);
+
+ // const imgUrl = `${process.env.NEXT_PUBLIC_NGINX_URL}/${resourceList[0].resourcePicture}`;
+ // console.log("Image URL:", imgUrl);
+ } catch (err) {
+ console.error('获取发布资源失败', err);
+ toast.current?.show({ severity: 'error', summary: 'error', detail: '获取发布资源失败' });
+ }
+ };
+
+ // 浮动按钮的子模块
+ const actions = [
+ {
+ template: () => (
+ <Button label="管理资源" onClick={() => router.push(`/user/manage/resources/`)}/>
+ )
+ },
+ {
+ template: () => (
+ <Button label="已购资源" onClick={() => router.push(`/user/purchased-resources/`)}/>
+ )
+ },
+ {
+ template: () => (
+ <Button label="发布资源" onClick={() => router.push(`/user/manage/resources/`)}/>
+ )
+ },
+ {
+ template: () => (
+ <Button label="编辑悬赏" onClick={() => router.push(`/user/manage/resources/`)}/>
+ )
+ }
+ ];
+
+
+
+
+ return (
+ <div className="user-container">
+
+ {/*个人信息*/}
+ <div className="user-profile-card">
+ <Avatar
+ image={`${process.env.NEXT_PUBLIC_NGINX_URL}/users/${userInfo?.avatar}`}
+ className="user-avatar"
+ shape="circle"
+ />
+ <div className="user-info">
+ <div className="user-detail-info">
+ <div className="name-container">
+ <h2 className="name">{userInfo?.username}</h2>
+ <span className="signature">{userInfo?.signature}</span>
+ </div>
+
+ <div className="stats-container">
+ <div className="stats">
+ <span className="stats-label">粉丝:</span>
+ <span className="stats-value">{userInfo?.followerCount}</span>
+ </div>
+ <div className="stats">
+ <span className="stats-label">累计上传量:</span>
+ <span className="stats-value">{formatCount(userData?.uploadAmount)}</span>
+ </div>
+ <div className="stats">
+ <span className="stats-label">关注:</span>
+ <span className="stats-value">{userInfo?.subscriberCount}</span>
+ </div>
+ <div className="stats">
+ <span className="stats-label">累计被下载量:</span>
+ <span className="stats-value">{formatCount(userData?.beDownloadedAmount)}</span>
+ </div>
+ </div>
+ </div>
+
+ <Button label="关注" className="action-button"/>
+ </div>
+ </div>
+
+ {/*个人内容*/}
+ <TabView>
+ <TabPanel header="主页">
+ {/*推荐资源*/}
+ <div className="homepage-item">
+ <div className="section-header">
+ <h1>推荐资源</h1>
+ <Button
+ label="显示更多"
+ link
+ onClick={() => router.push('/resource/recommend/模组')}
+ />
+ </div>
+ <div className="resource-grid">
+ {/*{mods.map((mod) => (*/}
+ {/* <Card key={mod.resourceId} className="resource-card" onClick={() => router.push(`/resource/resource-detail/${mod.resourceId}`)}>*/}
+ {/* <Image*/}
+ {/* src={process.env.NEXT_PUBLIC_NGINX_URL + mod.resourcePicture}*/}
+ {/* alt={mod.resourceName}*/}
+ {/* width="368"*/}
+ {/* height="200"*/}
+ {/* />*/}
+ {/* <div className="card-content">*/}
+ {/* <h3>{mod.resourceName}</h3>*/}
+ {/* <div className="view-count">*/}
+ {/* <Fire theme="outline" size="16" fill="#FF8D1A" />*/}
+ {/* <span>{mod.likes}</span>*/}
+ {/* </div>*/}
+ {/* </div>*/}
+ {/* </Card>*/}
+ {/*))}*/}
+ </div>
+ </div>
+
+ {/*发布资源*/}
+ <div className="homepage-item">
+ <div className="section-header">
+ <h1>发布资源</h1>
+ <Button
+ label="显示更多"
+ link
+ onClick={() => router.push('/user/manage/resources/')}
+ />
+ </div>
+ <div className="resource-grid">
+ {resourceList.map((resourceList) => (
+ <Card key={resourceList.resourceId} className="resource-card" onClick={() => router.push(`/resource/resource-detail/${resourceList.resourceId}`)}>
+ <Image
+ src={process.env.NEXT_PUBLIC_NGINX_URL + resourceList.resourcePicture}
+ alt={resourceList.resourceName}
+ width="368"
+ height="200"
+ />
+ <div className="card-content">
+ <h3>{resourceList.resourceName}</h3>
+ <div className="view-count">
+ <Fire theme="outline" size="16" fill="#FF8D1A" />
+ <span>{resourceList.likes}</span>
+ </div>
+ </div>
+ </Card>
+ ))}
+ </div>
+ </div>
+
+ {/*发布帖子*/}
+ <div className="homepage-item">
+ <div className="section-header">
+ <h1>发布帖子</h1>
+ <Button
+ label="显示更多"
+ link
+ onClick={() => router.push('/resource/recommend/模组')}
+ />
+ </div>
+ <div className="resource-grid">
+
+ {/*{mods.map((mod) => (*/}
+ {/* <Card key={mod.resourceId} className="resource-card" onClick={() => router.push(`/resource/resource-detail/${mod.resourceId}`)}>*/}
+ {/* <Image*/}
+ {/* src={process.env.NEXT_PUBLIC_NGINX_URL + mod.resourcePicture}*/}
+ {/* alt={mod.resourceName}*/}
+ {/* width="368"*/}
+ {/* height="200"*/}
+ {/* />*/}
+ {/* <div className="card-content">*/}
+ {/* <h3>{mod.resourceName}</h3>*/}
+ {/* <div className="view-count">*/}
+ {/* <Fire theme="outline" size="16" fill="#FF8D1A" />*/}
+ {/* <span>{mod.likes}</span>*/}
+ {/* </div>*/}
+ {/* </div>*/}
+ {/* </Card>*/}
+ {/*))}*/}
+ </div>
+ </div>
+ </TabPanel>
+
+ <TabPanel header="发布">
+
+ </TabPanel>
+
+ <TabPanel header="帖子">
+
+ </TabPanel>
+
+ <TabPanel header="收藏">
+
+ </TabPanel>
+
+ <TabPanel header="数据">
+
+ </TabPanel>
+
+ <TabPanel header="悬赏">
+
+ </TabPanel>
+ </TabView>
+
+ {/*浮动按钮*/}
+ <div className="card">
+ <SpeedDial
+ model={actions}
+ direction="up"
+ style={{ position: 'fixed', bottom: '2rem', right: '2rem' }}
+ showIcon="pi pi-plus"
+ hideIcon="pi pi-times"
+ buttonClassName="custom-speeddial-button"
+ />
+ </div>
+
+ </div>
+ );
+};
+
diff --git a/src/app/user/user.scss b/src/app/user/user.scss
new file mode 100644
index 0000000..0d044bc
--- /dev/null
+++ b/src/app/user/user.scss
@@ -0,0 +1,172 @@
+//全局容器样式
+.user-container {
+ max-width: 1200px;
+ margin: 0 auto;
+ padding: 0 2rem;
+
+ .p-tabview-title {
+ font-size: 20px;
+ }
+}
+
+//个人信息样式
+.user-profile-card {
+ display: flex;
+
+ .user-avatar {
+ height: 200px;
+ width: 200px;
+ margin: 30px;
+ }
+ .user-info {
+ width: 796px;
+ position: relative;
+ margin-top: 22px;
+
+ .user-detail-info {
+ display: flex;
+ justify-content: space-between;
+
+ .name-container {
+ width: 750px;
+ margin-left: 1rem;
+ .name {
+ font-size: 43px;
+ margin-bottom: 3px;
+ }
+
+ .signature {
+ margin-top: 0;
+ font-size: 20px;
+ }
+ }
+
+ .stats-container {
+ display: flex;
+ flex-wrap: wrap; // 允许换行
+ margin-top: 60px;
+
+ .stats {
+ width: 50%;
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ margin-bottom: 16px;
+
+ // 左上角:粉丝
+ &:nth-child(1) {
+ justify-content: flex-start; // 左对齐
+ }
+
+ // 右上角:累计上传量
+ &:nth-child(2) {
+ justify-content: flex-end; // 右对齐
+ }
+
+ // 左下角:关注
+ &:nth-child(3) {
+ justify-content: flex-start; // 左对齐
+ }
+
+ // 右下角:累计被下载量
+ &:nth-child(4) {
+ justify-content: flex-end; // 右对齐
+ }
+
+ .stats-label {
+ font-size: 18px;
+ color: #666;
+ margin-right: 4px;
+ white-space: nowrap;
+ }
+
+ .stats-value {
+ font-size: 20px;
+ font-weight: bold;
+ color: #333;
+ }
+ }
+ }
+ }
+
+ .action-button {
+ align-self: flex-end; // 底部对齐
+ margin-top: auto; // 自动上边距将其推到底部
+ // 固定位置:
+ position: absolute;
+ right: 0;
+ bottom: 10px;
+ }
+ }
+}
+
+
+//每一模块的首部样式
+.section-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-top: 20px;
+}
+
+//每个模块中的资源样式
+.resource-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
+ gap: 1rem;
+
+ .resource-card {
+ transition: transform 0.2s ease;
+ cursor: pointer;
+ box-shadow: none !important;
+
+ .p-image {
+ img {
+ border-radius: 0.5rem 0.5rem 0 0;
+ object-fit: cover;
+ }
+ }
+
+ .p-card-body {
+ padding: 0;
+ }
+
+ .p-card-content {
+ padding: 0;
+ }
+
+ &:hover {
+ transform: translateY(-4px);
+ }
+
+ .card-content {
+ display: flex;
+ flex-direction: column;
+ position: relative;
+ margin-left: 16px;
+ margin-right: 16px;
+ margin-bottom: 16px;
+
+ h3 {
+ margin: 1rem 0;
+ font-size: 1rem;
+ line-height: 1.5;
+ color: #2d3748;
+ }
+
+ .view-count {
+ position: absolute;
+ bottom: 0rem;
+ right: 0rem;
+ display: flex;
+ align-items: center;
+ gap: 0.5rem;
+ color: #718096;
+ font-size: 0.9rem;
+ }
+ }
+ }
+}
+
+
+