fix resource buy & download
Change-Id: I3c350410eb424ada9e757e04c8498637740073db
diff --git a/Dockerfile b/Dockerfile
index 8be866d..e901b14 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -31,6 +31,8 @@
# 设置环境变量
ENV NODE_ENV=production
ENV PORT=3009
+ENV NEXT_PUBLIC_NGINX_URL=http://team9.10813352.xyz:5009/upload
+ENV PUBLIC_URL=http://team9.10813352.xyz:5009/api
# 暴露端口
EXPOSE 3009
diff --git "a/src/app/resource/resource-detail/\133resourceId\135/page.tsx" "b/src/app/resource/resource-detail/\133resourceId\135/page.tsx"
index 22abfdb..baec9a6 100644
--- "a/src/app/resource/resource-detail/\133resourceId\135/page.tsx"
+++ "b/src/app/resource/resource-detail/\133resourceId\135/page.tsx"
@@ -570,6 +570,26 @@
}
};
+ const buyResource = async () => {
+ console.log("Buy Resource")
+ try {
+ const response = await axios.post(process.env.PUBLIC_URL + '/resource/purchase', {
+ userId,
+ resourceId
+ });
+
+ if (response.status === 200) {
+ toast.current?.show({ severity: 'success', summary: 'Success', detail: '购买成功' });
+ } else {
+ toast.current?.show({ severity: 'error', summary: 'error', detail: '购买失败' });
+
+ }
+ } catch (error) {
+ console.error('购买失败:', error);
+ toast.current?.show({ severity: 'error', summary: 'error', detail: '购买失败' });
+ }
+ }
+
// 发布评论接口
const publishComment = async () => {
if (!commentValue.trim() || !resource) return;
@@ -739,12 +759,11 @@
</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} />
+ }} onClick={buyResource} />
</ButtonGroup>
</div>
</div>
diff --git a/src/app/user/page.tsx b/src/app/user/page.tsx
index 6f29b02..0402059 100644
--- a/src/app/user/page.tsx
+++ b/src/app/user/page.tsx
@@ -1,1125 +1,1382 @@
-'use client';
+"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 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 { Dialog } from 'primereact/dialog';
+import { Dialog } from "primereact/dialog";
import { InputText } from "primereact/inputtext";
import { InputTextarea } from "primereact/inputtextarea";
import { FileUpload } from "primereact/fileupload";
// 资源分类
import { RadioButton, RadioButtonChangeEvent } from "primereact/radiobutton";
// 资源标签
-import { MultiSelect, MultiSelectChangeEvent } from 'primereact/multiselect';
+import { MultiSelect, MultiSelectChangeEvent } from "primereact/multiselect";
// 浮动按钮
-import { SpeedDial } from 'primereact/speeddial';
+import { SpeedDial } from "primereact/speeddial";
// 评分图标
-import { Fire } from '@icon-park/react';
+import { Fire } from "@icon-park/react";
// 消息提醒
-import { Toast } from 'primereact/toast';
+import { Toast } from "primereact/toast";
// 页面跳转
import { useRouter } from "next/navigation";
// 类型转换
import { toNumber } from "lodash";
// 分页
-import { Paginator, type PaginatorPageChangeEvent } from 'primereact/paginator';
+import { Paginator, type PaginatorPageChangeEvent } from "primereact/paginator";
// 接口传输
import axios from "axios";
-import { useLocalStorage } from '../hook/useLocalStorage';
+import { useLocalStorage } from "../hook/useLocalStorage";
// 样式
-import './user.scss';
+import "./user.scss";
interface User {
- Id: number;
-};
+ Id: number;
+}
// 用户信息
interface UserInfo {
- userId: number;
- username: string;
- password: string;
- avatar: string;
- followerCount: number;// 粉丝数
- subscriberCount: number;// 关注数
- signature: string;// 个性签名
- uploadAmount: number;
- purchaseAmount: number;
- credits: number;
+ 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[]; // 上传资源类型百分比列表,按材质包、模组、整合包、地图的顺序返回
+ 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
+ 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[];
+ records: Resource[];
}
// 帖子
interface Thread {
- threadId: number;
- userId: number;
- threadPicture: string;
- title: string;
- likes: number;
- createAt: string;
+ threadId: number;
+ userId: number;
+ threadPicture: string;
+ title: string;
+ likes: number;
+ createAt: string;
}
// 发布帖子列表
interface ThreadList {
- records: Thread[];
- total: number;
- pages: number;
- current: number;
- size: number;
+ records: Thread[];
+ total: number;
+ pages: number;
+ current: number;
+ size: number;
}
// 悬赏
interface Reward {
- rewardId: number;
- userId: number;
- rewardPicture: string;
- rewardName: string;
- createAt: string;
- rewardDescription: string;
- price: number;
+ rewardId: number;
+ userId: number;
+ rewardPicture: string;
+ rewardName: string;
+ createAt: string;
+ rewardDescription: string;
+ price: number;
}
// 我的悬赏列表
interface RewardList {
- rewardList: Reward[];
- total: number; // 总记录数
+ rewardList: Reward[];
+ total: number; // 总记录数
}
// 资源标签
interface GameplayOption {
- name: string;
- code: number;
+ name: string;
+ code: number;
}
// 资源标签选项
const gameplayOptions: GameplayOption[] = [
- { name: '科技', code: 1 },
- { name: '魔法', code: 2 },
- { name: '建筑', code: 3 },
- { name: '风景', code: 4 },
- { name: '竞技', code: 5 },
- { name: '生存', code: 6 },
- { name: '冒险', code: 7 },
- { name: '跑酷', code: 8 },
- { name: '艺术', code: 9 },
- { name: '剧情', code: 10 },
- { name: '社交', code: 11 },
- { name: '策略', code: 12 },
- { name: '极限', code: 13 }
+ { name: "科技", code: 1 },
+ { name: "魔法", code: 2 },
+ { name: "建筑", code: 3 },
+ { name: "风景", code: 4 },
+ { name: "竞技", code: 5 },
+ { name: "生存", code: 6 },
+ { name: "冒险", code: 7 },
+ { name: "跑酷", code: 8 },
+ { name: "艺术", code: 9 },
+ { name: "剧情", code: 10 },
+ { name: "社交", code: 11 },
+ { name: "策略", code: 12 },
+ { name: "极限", code: 13 },
];
export default function UserPage() {
- const user = useLocalStorage<User>('user');
- const userId: number = user?.Id ?? -1;
+ const user = useLocalStorage<User>("user");
+ const userId: number = user?.Id ?? -1;
- // 路由
- const router = useRouter();
- // 发布资源列表
- const [resourceList, setResourceList] = useState<Resource[]>([]);
- // 用户信息
- const [userInfo, setUserInfo] = useState<UserInfo>();
- // 用户数据
- const [userData, setUserData] = useState<UserData>();
- // 消息提醒
- const toast = useRef<Toast>(null);
- // 资源标签
- const [selectedGameplay, setSelectedGameplay] = useState<GameplayOption[]>([]);
- // 资源封面路径
- const [resourcePictureUrl, setResourcePictureUrl] = useState<string>('');
- // 主页发布帖子列表
- const [homePageThread, setHomePageThread] = useState<ThreadList>();
- // 我的帖子列表
- const [threadList, setThreadList] = useState<Thread[]>([]);
- // 我的悬赏列表
- const [rewardList, setRewardList] = useState<Reward[]>([]);
- // 控制Tab切换
- const [activeIndex, setActiveIndex] = useState(0);
- // 帖子分页
- const [threadFirst, setThreadFirst] = useState(0);
- const [threadRows, setThreadRows] = useState(6);
- const [totalThreads, setTotalThreads] = useState<number>(0);
- const onThreadPageChange = (event: PaginatorPageChangeEvent) => {
- setThreadFirst(event.first);
- setThreadRows(event.rows);
- };
- const [torrentUrl, setTorrentUrl] = useState<string>(''); // 上传 .torrent 后得到的 URL
+ // 路由
+ const router = useRouter();
+ // 发布资源列表
+ const [resourceList, setResourceList] = useState<Resource[]>([]);
+ // 用户信息
+ const [userInfo, setUserInfo] = useState<UserInfo>();
+ // 用户数据
+ const [userData, setUserData] = useState<UserData>();
+ // 消息提醒
+ const toast = useRef<Toast>(null);
+ // 资源标签
+ const [selectedGameplay, setSelectedGameplay] = useState<GameplayOption[]>(
+ []
+ );
+ // 资源封面路径
+ const [resourcePictureUrl, setResourcePictureUrl] = useState<string>("");
+ // 主页发布帖子列表
+ const [homePageThread, setHomePageThread] = useState<ThreadList>();
+ // 我的帖子列表
+ const [threadList, setThreadList] = useState<Thread[]>([]);
+ // 我的悬赏列表
+ const [rewardList, setRewardList] = useState<Reward[]>([]);
+ // 控制Tab切换
+ const [activeIndex, setActiveIndex] = useState(0);
+ // 帖子分页
+ const [threadFirst, setThreadFirst] = useState(0);
+ const [threadRows, setThreadRows] = useState(6);
+ const [totalThreads, setTotalThreads] = useState<number>(0);
+ const onThreadPageChange = (event: PaginatorPageChangeEvent) => {
+ setThreadFirst(event.first);
+ setThreadRows(event.rows);
+ };
+ const [torrentUrl, setTorrentUrl] = useState<string>(""); // 上传 .torrent 后得到的 URL
- // 悬赏分页
- const [rewardFirst, setRewardFirst] = useState(0);
- const [rewardRows, setRewardRows] = useState(5);
- const [totalRewards, setTotalRewards] = useState<number>(0);
- const onRewardPageChange = (event: PaginatorPageChangeEvent) => {
- setRewardFirst(event.first);
- setRewardRows(event.rows);
- };
+ // 悬赏分页
+ const [rewardFirst, setRewardFirst] = useState(0);
+ const [rewardRows, setRewardRows] = useState(5);
+ const [totalRewards, setTotalRewards] = useState<number>(0);
+ const onRewardPageChange = (event: PaginatorPageChangeEvent) => {
+ setRewardFirst(event.first);
+ setRewardRows(event.rows);
+ };
- useEffect(() => {
- fetchUserInfo();
- fetchUserData();
- fetchResourceList();
- }, []);
+ useEffect(() => {
+ fetchUserInfo();
+ fetchUserData();
+ fetchResourceList();
+ }, []);
- // 获取用户信息
- const fetchUserInfo = async () => {
- try {
- const response = await axios.get<UserInfo>(process.env.PUBLIC_URL + `/user/info`, {
- params: { userId }
- });
- console.log('获取用户信息:', response.data);
- setUserInfo(response.data);
- } catch (err) {
- console.error('获取用户信息失败', err);
- toast.current?.show({ severity: 'error', summary: 'error', detail: '获取用户信息失败' });
+ // 获取用户信息
+ const fetchUserInfo = async () => {
+ try {
+ const response = await axios.get<UserInfo>(
+ process.env.PUBLIC_URL + `/user/info`,
+ {
+ params: { userId },
}
- };
-
- // 获取用户数据
- const fetchUserData = async () => {
- try {
- const response = await axios.get<UserData>(process.env.PUBLIC_URL + `/user/data`, {
- params: { userId }
- });
- 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, pageNumber: 1, rows: 3 }
- });
- console.log('获取发布资源列表:', response.data.records);
- setResourceList(response.data.records);
- } catch (err) {
- console.error('获取发布资源失败', err);
- toast.current?.show({ severity: 'error', summary: 'error', detail: '获取发布资源失败' });
- }
- };
-
- useEffect(() => {
- fetchHomePageThread();
- }, []);
-
- // 获取主页部分的发布帖子
- const fetchHomePageThread = async () => {
- try {
- const response = await axios.get<ThreadList>(process.env.PUBLIC_URL + `/user/thread`, {
- params: { userId: userId, pageNumber: 1, rows: 3 }
- })
- console.log('获取主页发布帖子:', response.data);
- setHomePageThread(response.data);
-
- } catch (err) {
- console.error('获取主页发布帖子失败', err);
- toast.current?.show({ severity: 'error', summary: 'error', detail: '获取主页发布帖子失败' });
- }
+ );
+ console.log("获取用户信息:", response.data);
+ setUserInfo(response.data);
+ } catch (err) {
+ console.error("获取用户信息失败", err);
+ toast.current?.show({
+ severity: "error",
+ summary: "error",
+ detail: "获取用户信息失败",
+ });
}
+ };
- useEffect(() => {
- fetchThreadList();
- }, [threadFirst, threadRows]);
-
- // 获取用户发布的所有帖子
- const fetchThreadList = async () => {
- try {
- const pageNumber = threadFirst / threadRows + 1;
- const response = await axios.get<ThreadList>(process.env.PUBLIC_URL + `/user/thread`, {
- params: { userId: userId, pageNumber: pageNumber, rows: threadRows }
- })
- console.log('获取我的帖子:', response.data);
- setThreadList(response.data.records);
- setTotalThreads(response.data.total)
- } 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 },
}
+ );
+ console.log("获取用户数据:", response.data);
+ setUserData(response.data);
+ } catch (err) {
+ console.error("获取用户数据失败", err);
+ toast.current?.show({
+ severity: "error",
+ summary: "error",
+ detail: "获取用户数据失败",
+ });
}
+ };
- useEffect(() => {
+ // 格式化数字显示 (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, pageNumber: 1, rows: 3 },
+ }
+ );
+ console.log("获取发布资源列表:", response.data.records);
+ setResourceList(response.data.records);
+ } catch (err) {
+ console.error("获取发布资源失败", err);
+ toast.current?.show({
+ severity: "error",
+ summary: "error",
+ detail: "获取发布资源失败",
+ });
+ }
+ };
+
+ useEffect(() => {
+ fetchHomePageThread();
+ }, []);
+
+ // 获取主页部分的发布帖子
+ const fetchHomePageThread = async () => {
+ try {
+ const response = await axios.get<ThreadList>(
+ process.env.PUBLIC_URL + `/user/thread`,
+ {
+ params: { userId: userId, pageNumber: 1, rows: 3 },
+ }
+ );
+ console.log("获取主页发布帖子:", response.data);
+ setHomePageThread(response.data);
+ } catch (err) {
+ console.error("获取主页发布帖子失败", err);
+ toast.current?.show({
+ severity: "error",
+ summary: "error",
+ detail: "获取主页发布帖子失败",
+ });
+ }
+ };
+
+ useEffect(() => {
+ fetchThreadList();
+ }, [threadFirst, threadRows]);
+
+ // 获取用户发布的所有帖子
+ const fetchThreadList = async () => {
+ try {
+ const pageNumber = threadFirst / threadRows + 1;
+ const response = await axios.get<ThreadList>(
+ process.env.PUBLIC_URL + `/user/thread`,
+ {
+ params: { userId: userId, pageNumber: pageNumber, rows: threadRows },
+ }
+ );
+ console.log("获取我的帖子:", response.data);
+ setThreadList(response.data.records);
+ setTotalThreads(response.data.total);
+ } catch (err) {
+ console.error("获取我的帖子失败", err);
+ toast.current?.show({
+ severity: "error",
+ summary: "error",
+ detail: "获取我的帖子失败",
+ });
+ }
+ };
+
+ useEffect(() => {
+ fetchRewardList();
+ }, [rewardFirst, rewardRows]);
+
+ // 获取用户发布的所有悬赏
+ const fetchRewardList = async () => {
+ try {
+ const pageNumber = rewardFirst / rewardRows + 1;
+ const response = await axios.get<RewardList>(
+ process.env.PUBLIC_URL + `/user/reward`,
+ {
+ params: { userId: userId, pageNumber: pageNumber, rows: rewardRows },
+ }
+ );
+ console.log("获取我的悬赏:", response.data);
+ setRewardList(response.data.rewardList);
+ setTotalRewards(response.data.total);
+ } 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={() => setVisible(true)} />
+ ),
+ },
+ {
+ template: () => <Button label="编辑悬赏" />,
+ },
+ ];
+
+ // 发布资源弹窗
+ const [visible, setVisible] = useState(false);
+ const [resourceFormData, setResourceFormData] = useState({
+ resource: {
+ resourceName: "",
+ resourcePicture: "",
+ resourceSummary: "",
+ resourceDetail: "",
+ uploadTime: "",
+ lastUpdateTime: "",
+ price: "",
+ classify: "",
+ },
+ resourceVersionName: "",
+ gameplayList: [""],
+ completeRewardId: null,
+ userId: 0,
+ });
+ const [ingredient, setIngredient] = useState<string>("");
+
+ // 删除悬赏弹窗
+ const [deleteVisible, setDeleteVisible] = useState(false);
+ // 要删除悬赏的id
+ const [deleteRewardId, setDeleteResourceId] = useState<number>(0);
+ // 处理删除悬赏接口
+ const handleDeleteSubmit = async () => {
+ try {
+ // 发送DELETE请求
+ const response = await axios.delete(process.env.PUBLIC_URL + `/reward`, {
+ params: { rewardId: deleteRewardId },
+ });
+ console.log("用户" + userId + "要删除" + deleteRewardId + "号悬赏");
+
+ if (response.status === 204) {
+ console.log("用户成功删除悬赏");
+ toast.current?.show({
+ severity: "success",
+ summary: "Success",
+ detail: "删除悬赏成功",
+ });
+ setDeleteVisible(false);
+ // 重新拉取资源列表
fetchRewardList();
- }, [rewardFirst, rewardRows]);
-
- // 获取用户发布的所有悬赏
- const fetchRewardList = async () => {
- try {
- const pageNumber = rewardFirst / rewardRows + 1;
- const response = await axios.get<RewardList>(process.env.PUBLIC_URL + `/user/reward`, {
- params: { userId: userId, pageNumber: pageNumber, rows: rewardRows }
- })
- console.log('获取我的悬赏:', response.data);
- setRewardList(response.data.rewardList);
- setTotalRewards(response.data.total)
- } catch (err) {
- console.error('获取我的悬赏失败', err);
- toast.current?.show({ severity: 'error', summary: 'error', detail: '获取我的悬赏失败' });
- }
+ }
+ } catch (error) {
+ console.error("资源删除失败:", error);
+ 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={() => setVisible(true)} />
- )
- },
- {
- template: () => (
- <Button label="编辑悬赏" />
- )
- }
- ];
+ // 编辑悬赏弹窗
+ const [editVisible, setEditVisible] = useState(false);
+ // 悬赏封面路径
+ const [rewardPictureUrl, setRewardPictureUrl] = useState<string>("");
+ const [editRewardFormData, setEditRewardFormData] = useState({
+ rewardId: 0,
+ rewardName: "",
+ price: "",
+ rewardDescription: "",
+ });
- // 发布资源弹窗
- const [visible, setVisible] = useState(false);
- const [resourceFormData, setResourceFormData] = useState({
+ // 处理编辑资源接口
+ const handleEditSubmit = async () => {
+ try {
+ const postData = {
+ rewardId: editRewardFormData.rewardId,
+ rewardName: editRewardFormData.rewardName,
+ rewardPicture: rewardPictureUrl,
+ price: toNumber(editRewardFormData.price),
+ rewardDescription: editRewardFormData.rewardDescription,
+ };
+ // 发送POST请求
+ const response = await axios.put(
+ process.env.PUBLIC_URL + "/reward/info",
+ postData
+ );
+ console.log("编辑悬赏的信息:", postData);
+
+ if (response.status === 200) {
+ toast.current?.show({
+ severity: "success",
+ summary: "Success",
+ detail: "悬赏编辑成功",
+ });
+ // 编辑成功
+ setEditVisible(false);
+ fetchRewardList();
+ }
+ } catch (error) {
+ console.error("悬赏编辑失败:", error);
+ toast.current?.show({
+ severity: "error",
+ summary: "error",
+ detail: "悬赏编辑失败",
+ });
+ }
+ };
+
+ // 上传资源接口
+ const handleSubmit = async () => {
+ try {
+ // 规定用户必须输入的内容
+ if (resourceFormData.resource.resourceName == "") {
+ toast.current?.show({
+ severity: "info",
+ summary: "error",
+ detail: "缺少资源名称",
+ });
+ return;
+ }
+ if (resourceFormData.resource.resourceSummary == "") {
+ toast.current?.show({
+ severity: "info",
+ summary: "error",
+ detail: "缺少资源简介",
+ });
+ return;
+ }
+ if (resourceFormData.resourceVersionName == "") {
+ toast.current?.show({
+ severity: "info",
+ summary: "error",
+ detail: "缺少资源版本名",
+ });
+ return;
+ }
+ if (resourceFormData.resource.price == "") {
+ toast.current?.show({
+ severity: "info",
+ summary: "error",
+ detail: "缺少资源价格",
+ });
+ return;
+ }
+ if (resourceFormData.resource.classify == "") {
+ toast.current?.show({
+ severity: "info",
+ summary: "error",
+ detail: "缺少资源分类",
+ });
+ return;
+ }
+ if (
+ resourceFormData.gameplayList.length === 1 &&
+ resourceFormData.gameplayList[0] === ""
+ ) {
+ toast.current?.show({
+ severity: "info",
+ summary: "error",
+ detail: "缺少资源标签",
+ });
+ return;
+ }
+ if (resourcePictureUrl === "") {
+ toast.current?.show({
+ severity: "info",
+ summary: "error",
+ detail: "缺少资源封面",
+ });
+ return;
+ }
+
+ const currentDate = new Date().toISOString().split("T")[0];
+ const postData = {
resource: {
- resourceName: '',
- resourcePicture: '',
- resourceSummary: '',
- resourceDetail: '',
- uploadTime: '',
- lastUpdateTime: '',
- price: '',
- classify: '',
+ resourceName: resourceFormData.resource.resourceName,
+ resourcePicture: resourcePictureUrl,
+ resourceSummary: resourceFormData.resource.resourceSummary,
+ resourceDetail: resourceFormData.resource.resourceDetail,
+ uploadTime: currentDate,
+ lastUpdateTime: currentDate,
+ price: toNumber(resourceFormData.resource.price),
+ classify: resourceFormData.resource.classify,
},
- gameplayList: [''],
+ gameplayList: resourceFormData.gameplayList,
+ resourceVersionName: resourceFormData.resourceVersionName,
completeRewardId: null,
- userId: 0,
- });
- const [ingredient, setIngredient] = useState<string>('');
+ userId, // 记得用户登录状态获取
+ };
+ // 发送POST请求
+ const response = await axios.post(
+ process.env.PUBLIC_URL + "/resource",
+ postData
+ );
+ console.log("上传资源的信息:", postData);
- // 删除悬赏弹窗
- const [deleteVisible, setDeleteVisible] = useState(false);
- // 要删除悬赏的id
- const [deleteRewardId, setDeleteResourceId] = useState<number>(0);
- // 处理删除悬赏接口
- const handleDeleteSubmit = async () => {
+ if (response.status === 200) {
+ toast.current?.show({
+ severity: "success",
+ summary: "Success",
+ detail: "资源上传成功",
+ });
+ console.log(torrentUrl);
try {
- // 发送DELETE请求
- const response = await axios.delete(process.env.PUBLIC_URL + `/reward`, {
- params: { rewardId: deleteRewardId },
- });
- console.log("用户" + userId + "要删除" + deleteRewardId + "号悬赏");
-
- if (response.status === 204) {
- console.log("用户成功删除悬赏");
- toast.current?.show({ severity: 'success', summary: 'Success', detail: '删除悬赏成功' });
- setDeleteVisible(false);
- // 重新拉取资源列表
- fetchRewardList();
+ const versionResponse = await axios.post(
+ `${process.env.PUBLIC_URL}/resource/version`,
+ {
+ resourceVersionName: postData.resourceVersionName,
+ resourceId: Number(response.data),
}
- } catch (error) {
- console.error('资源删除失败:', error);
- toast.current?.show({ severity: 'error', summary: 'error', detail: '密码错误,资源删除失败' });
+ );
+ // 上传资源文件
+ const btPayload = {
+ torrentUrl: torrentUrl,
+ infoHash: "8",
+ uploadTime: currentDate,
+ uploaderUserId: userId,
+ resourceVersionId: Number(versionResponse.data),
+ };
+
+ await axios.post(`${process.env.PUBLIC_URL}/file/bt`, btPayload);
+ toast.current?.show({
+ severity: "success",
+ summary: "资源登记成功",
+ detail: "已同步种子信息",
+ });
+ } catch (btError) {
+ console.error("种子登记失败:", btError);
+ toast.current?.show({
+ severity: "warn",
+ summary: "资源已上传",
+ detail: "但种子登记失败",
+ });
}
- };
+ // 上传成功
+ setVisible(false);
+ // 重置表单
+ setResourceFormData({
+ resource: {
+ resourceName: "",
+ resourcePicture: "",
+ resourceSummary: "",
+ resourceDetail: "",
+ uploadTime: "",
+ lastUpdateTime: "",
+ price: "",
+ classify: "",
+ },
+ resourceVersionName: "",
+ gameplayList: [],
+ completeRewardId: null,
+ userId: 0,
+ });
+ // 重置资源分类
+ setIngredient("");
+ // 重置资源标签
+ setSelectedGameplay([]);
+ // 重置资源封面
+ setResourcePictureUrl("");
+ setTorrentUrl("");
+ }
+ } catch (error) {
+ console.error("资源上传失败:", error);
+ toast.current?.show({
+ severity: "error",
+ summary: "error",
+ detail: "资源上传失败",
+ });
+ }
+ };
- // 编辑悬赏弹窗
- const [editVisible, setEditVisible] = useState(false);
- // 悬赏封面路径
- const [rewardPictureUrl, setRewardPictureUrl] = useState<string>('');
- const [editRewardFormData, setEditRewardFormData] = useState({
- rewardId: 0,
- rewardName: '',
- price: '',
- rewardDescription: '',
- });
+ return (
+ <div className="user-container">
+ <Toast ref={toast}></Toast>
+ {/*个人信息*/}
+ <div className="user-profile-card">
+ <Avatar
+ image={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>
- // 处理编辑资源接口
- const handleEditSubmit = async () => {
- try {
- const postData = {
- rewardId: editRewardFormData.rewardId,
- rewardName: editRewardFormData.rewardName,
- rewardPicture: rewardPictureUrl,
- price: toNumber(editRewardFormData.price),
- rewardDescription: editRewardFormData.rewardDescription,
- };
- // 发送POST请求
- const response = await axios.put(process.env.PUBLIC_URL + '/reward/info', postData);
- console.log("编辑悬赏的信息:", postData);
+ <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>
- if (response.status === 200) {
- toast.current?.show({ severity: 'success', summary: 'Success', detail: '悬赏编辑成功' });
- // 编辑成功
- setEditVisible(false);
- fetchRewardList();
- }
- } catch (error) {
- console.error('悬赏编辑失败:', error);
- toast.current?.show({ severity: 'error', summary: 'error', detail: '悬赏编辑失败' });
+ <Button label="关注" className="action-button" />
+ </div>
+ </div>
+
+ {/*个人内容*/}
+ <TabView
+ activeIndex={activeIndex}
+ onTabChange={(e) => setActiveIndex(e.index)}
+ >
+ <TabPanel header="主页">
+ {/*发布资源*/}
+ <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={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={() => setActiveIndex(1)} />
+ </div>
+ <div className="resource-grid">
+ {homePageThread?.records.map((homePageThread) => (
+ <Card
+ key={homePageThread.threadId}
+ className="resource-card"
+ onClick={() =>
+ router.push(
+ `/community/thread-detail/${homePageThread.threadId}`
+ )
+ }
+ >
+ <Image
+ src={homePageThread.threadPicture}
+ alt={homePageThread.title}
+ width="368"
+ height="200"
+ />
+ <div className="card-content">
+ <h3>{homePageThread.title}</h3>
+ <div className="view-count">
+ <Fire theme="outline" size="16" fill="#FF8D1A" />
+ <span>{homePageThread.likes}</span>
+ </div>
+ </div>
+ </Card>
+ ))}
+ </div>
+ </div>
+ </TabPanel>
+
+ <TabPanel header="帖子">
+ {/*我的帖子*/}
+ <div className="homepage-item">
+ <div className="section-header">
+ <h1>我的帖子</h1>
+ </div>
+ <div className="resource-grid">
+ {threadList.map((threadList) => (
+ <Card
+ key={threadList.threadId}
+ className="resource-card"
+ onClick={() =>
+ router.push(
+ `/community/thread-detail/${threadList.threadId}`
+ )
+ }
+ >
+ <Image
+ src={threadList.threadPicture}
+ alt={threadList.title}
+ width="368"
+ height="200"
+ />
+ <div className="card-content">
+ <h3>{threadList.title}</h3>
+ <div className="view-count">
+ <Fire theme="outline" size="16" fill="#FF8D1A" />
+ <span>{threadList.likes}</span>
+ </div>
+ </div>
+ </Card>
+ ))}
+ </div>
+ {totalThreads > 6 && (
+ <Paginator
+ className="Paginator"
+ first={threadFirst}
+ rows={threadRows}
+ totalRecords={totalThreads}
+ rowsPerPageOptions={[6, 12]}
+ onPageChange={onThreadPageChange}
+ />
+ )}
+ </div>
+ </TabPanel>
+
+ <TabPanel header="收藏"></TabPanel>
+
+ <TabPanel header="数据"></TabPanel>
+
+ <TabPanel header="悬赏">
+ <div className="section-header">
+ <h1>我的悬赏</h1>
+ </div>
+
+ <div className="resource-list">
+ {rewardList.map((rewardItem) => (
+ <Card
+ key={rewardItem.rewardId}
+ className="resources-list-card"
+ onClick={() =>
+ router.push(`/reward/reward-detail/${rewardItem.rewardId}`)
+ }
+ >
+ <Image
+ alt="avatar"
+ src={"rewards/" + rewardItem.rewardPicture}
+ className="resource-avatar"
+ width="250"
+ height="140"
+ />
+ <div className="resource-header">
+ <div className="resource-content">
+ <h3>{rewardItem.rewardName}</h3>
+ </div>
+
+ <div className="resource-operation">
+ <Button
+ label="编辑"
+ onClick={(e) => {
+ e.stopPropagation(); // 关键修复:阻止事件冒泡,避免触发Card的点击事件
+ setEditVisible(true);
+
+ setEditRewardFormData({
+ rewardId: rewardItem.rewardId,
+ rewardName: rewardItem.rewardName,
+ price: rewardItem.price.toString(),
+ rewardDescription: rewardItem.rewardDescription,
+ });
+ console.log("用户编辑悬赏");
+ }}
+ />
+ <Button
+ label="删除"
+ onClick={(e) => {
+ e.stopPropagation(); // 关键修复:阻止事件冒泡,避免触发Card的点击事件
+ setDeleteResourceId(rewardItem.rewardId);
+ setDeleteVisible(true);
+ }}
+ style={{ backgroundColor: "rgba(255, 87, 51, 1)" }}
+ />
+ </div>
+ </div>
+ </Card>
+ ))}
+
+ {totalRewards > 5 && (
+ <Paginator
+ className="Paginator"
+ first={rewardFirst}
+ rows={rewardRows}
+ totalRecords={totalRewards}
+ rowsPerPageOptions={[5, 10]}
+ onPageChange={onRewardPageChange}
+ />
+ )}
+ </div>
+ </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>
+
+ {/*发布资源弹窗*/}
+ <Dialog
+ header="发布资源"
+ visible={visible}
+ onHide={() => setVisible(false)}
+ className="publish-dialog"
+ modal
+ footer={
+ <div className="dialog-footer">
+ <Button
+ label="发布"
+ icon="pi pi-check"
+ onClick={handleSubmit}
+ autoFocus
+ />
+ <Button
+ label="取消"
+ icon="pi pi-times"
+ onClick={() => setVisible(false)}
+ className="p-button-text"
+ />
+ </div>
}
- };
+ >
+ <div className="publish-form">
+ <div className="form-field">
+ <div className="form-field-header">
+ <span className="form-field-sign">*</span>
+ <label htmlFor="name">资源名称</label>
+ </div>
+ <InputText
+ id="name"
+ value={resourceFormData.resource.resourceName}
+ onChange={(e) =>
+ setResourceFormData((prev) => ({
+ ...prev, // 复制顶层所有属性
+ resource: {
+ ...prev.resource, // 复制resource对象的所有属性
+ resourceName: e.target.value, // 只更新resourceName
+ },
+ }))
+ }
+ placeholder="请输入资源名称"
+ className="w-full"
+ />
+ </div>
+ <div className="form-field">
+ <div className="form-field-header">
+ <span className="form-field-sign">*</span>
+ <label htmlFor="summary">资源简介</label>
+ </div>
+ <InputText
+ id="summary"
+ value={resourceFormData.resource.resourceSummary}
+ onChange={(e) =>
+ setResourceFormData((prev) => ({
+ ...prev, // 复制顶层所有属性
+ resource: {
+ ...prev.resource, // 复制resource对象的所有属性
+ resourceSummary: e.target.value,
+ },
+ }))
+ }
+ placeholder="请输入资源简介(一句话)"
+ className="w-full"
+ />
+ </div>
+ <div className="form-field">
+ <div className="form-field-header">
+ <span className="form-field-sign">*</span>
+ <label htmlFor="summary">资源版本名</label>
+ </div>
+ <InputText
+ id="summary"
+ value={resourceFormData.resourceVersionName}
+ onChange={(e) =>
+ setResourceFormData((prev) => ({
+ ...prev, // 复制顶层所有属性
+ resourceVersionName: e.target.value,
+ }))
+ }
+ placeholder="请输入资源版本名"
+ className="w-full"
+ />
+ </div>
+ <div className="form-field">
+ <div className="form-field-header">
+ <label htmlFor="detail">资源介绍</label>
+ </div>
+ <InputTextarea
+ id="detail"
+ value={resourceFormData.resource.resourceDetail}
+ onChange={(e) =>
+ setResourceFormData((prev) => ({
+ ...prev, // 复制顶层所有属性
+ resource: {
+ ...prev.resource, // 复制resource对象的所有属性
+ resourceDetail: e.target.value,
+ },
+ }))
+ }
+ rows={5}
+ placeholder="请输入资源介绍"
+ className="w-full"
+ />
+ </div>
+ <div className="form-field">
+ <div className="form-field-header">
+ <span className="form-field-sign">*</span>
+ <label htmlFor="price">价格</label>
+ </div>
+ <InputText
+ id="price"
+ value={resourceFormData.resource.price}
+ onChange={(e) =>
+ setResourceFormData((prev) => ({
+ ...prev, // 复制顶层所有属性
+ resource: {
+ ...prev.resource, // 复制resource对象的所有属性
+ price: e.target.value,
+ },
+ }))
+ }
+ placeholder="请输入资源价格"
+ className="w-full"
+ />
+ </div>
+ <div className="form-field">
+ <div className="form-field-header">
+ <span className="form-field-sign">*</span>
+ <label htmlFor="classify">资源分类(请选择一项)</label>
+ </div>
+ <div className="form-field-classify">
+ <div className="flex align-items-center">
+ <RadioButton
+ inputId="ingredient1"
+ name="pizza"
+ value="resourcePack"
+ onChange={(e: RadioButtonChangeEvent) => {
+ setResourceFormData((prev) => ({
+ ...prev, // 复制顶层所有属性
+ resource: {
+ ...prev.resource, // 复制resource对象的所有属性
+ classify: e.target.value,
+ },
+ }));
+ setIngredient(e.value);
+ console.log(ingredient);
+ // console.log(resourceFormData.resource.classify);
+ }}
+ checked={ingredient === "resourcePack"}
+ />
+ <label htmlFor="ingredient1" className="ml-2">
+ 材质包
+ </label>
+ </div>
+ <div className="flex align-items-center">
+ <RadioButton
+ inputId="ingredient2"
+ name="pizza"
+ value="modPack"
+ onChange={(e: RadioButtonChangeEvent) => {
+ setResourceFormData((prev) => ({
+ ...prev, // 复制顶层所有属性
+ resource: {
+ ...prev.resource, // 复制resource对象的所有属性
+ classify: e.target.value,
+ },
+ }));
+ setIngredient(e.value);
+ }}
+ checked={ingredient === "modPack"}
+ />
+ <label htmlFor="ingredient2" className="ml-2">
+ 整合包
+ </label>
+ </div>
+ <div className="flex align-items-center">
+ <RadioButton
+ inputId="ingredient3"
+ name="pizza"
+ value="mod"
+ onChange={(e: RadioButtonChangeEvent) => {
+ setResourceFormData((prev) => ({
+ ...prev, // 复制顶层所有属性
+ resource: {
+ ...prev.resource, // 复制resource对象的所有属性
+ classify: e.target.value,
+ },
+ }));
+ setIngredient(e.value);
+ }}
+ checked={ingredient === "mod"}
+ />
+ <label htmlFor="ingredient3" className="ml-2">
+ 模组
+ </label>
+ </div>
+ <div className="flex align-items-center">
+ <RadioButton
+ inputId="ingredient4"
+ name="pizza"
+ value="map"
+ onChange={(e: RadioButtonChangeEvent) => {
+ setResourceFormData((prev) => ({
+ ...prev, // 复制顶层所有属性
+ resource: {
+ ...prev.resource, // 复制resource对象的所有属性
+ classify: e.target.value,
+ },
+ }));
+ setIngredient(e.value);
+ }}
+ checked={ingredient === "map"}
+ />
+ <label htmlFor="ingredient4" className="ml-2">
+ 地图
+ </label>
+ </div>
+ </div>
+ </div>
+ <div className="form-field">
+ <div className="form-field-header">
+ <span className="form-field-sign">*</span>
+ <label htmlFor="gameplayList">资源标签</label>
+ </div>
+ <MultiSelect
+ value={selectedGameplay}
+ onChange={(e: MultiSelectChangeEvent) => {
+ const selectedOptions = e.value as GameplayOption[];
+ // 提取选中项的 name 属性组成字符串数组
+ const selectedNames = selectedOptions.map((item) => item.name);
- // 上传资源接口
- const handleSubmit = async () => {
- try {
- // 规定用户必须输入的内容
- if (resourceFormData.resource.resourceName == '') {
- toast.current?.show({ severity: 'info', summary: 'error', detail: '缺少资源名称' });
- return;
- }
- if (resourceFormData.resource.resourceSummary == '') {
- toast.current?.show({ severity: 'info', summary: 'error', detail: '缺少资源简介' });
- return;
- }
- if (resourceFormData.resource.price == '') {
- toast.current?.show({ severity: 'info', summary: 'error', detail: '缺少资源价格' });
- return;
- }
- if (resourceFormData.resource.classify == '') {
- toast.current?.show({ severity: 'info', summary: 'error', detail: '缺少资源分类' });
- return;
- }
- if (
- resourceFormData.gameplayList.length === 1 &&
- resourceFormData.gameplayList[0] === ''
- ) {
- toast.current?.show({ severity: 'info', summary: 'error', detail: '缺少资源标签' });
- return;
- }
- if (resourcePictureUrl === '') {
- toast.current?.show({ severity: 'info', summary: 'error', detail: '缺少资源封面' });
- return;
- }
-
- const currentDate = new Date().toISOString().split('T')[0];
- const postData = {
- resource: {
- resourceName: resourceFormData.resource.resourceName,
- resourcePicture: resourcePictureUrl,
- resourceSummary: resourceFormData.resource.resourceSummary,
- resourceDetail: resourceFormData.resource.resourceDetail,
- uploadTime: currentDate,
- lastUpdateTime: currentDate,
- price: toNumber(resourceFormData.resource.price),
- classify: resourceFormData.resource.classify,
- },
- gameplayList: resourceFormData.gameplayList,
- completeRewardId: null,
- userId, // 记得用户登录状态获取
- };
- // 发送POST请求
- const response = await axios.post(process.env.PUBLIC_URL + '/resource', postData);
- console.log("上传资源的信息:", postData);
-
- if (response.status === 200) {
- toast.current?.show({ severity: 'success', summary: 'Success', detail: '资源上传成功' });
- console.log(torrentUrl)
- // 上传资源文件
- const btPayload = {
- torrentUrl: torrentUrl,
- infoHash: '8',
- uploadTime: currentDate,
- uploaderUserId: userId,
- resourceVersionId: 1
- };
+ setResourceFormData((prev) => ({
+ ...prev,
+ gameplayList: selectedNames,
+ }));
+ setSelectedGameplay(selectedOptions);
+ }}
+ options={gameplayOptions}
+ display="chip"
+ optionLabel="name"
+ placeholder="请选择资源标签"
+ className="w-full md:w-20rem"
+ />
+ </div>
+ <div className="form-field">
+ <div className="form-field-header">
+ <span className="form-field-sign">*</span>
+ <label>封面图片</label>
+ </div>
+ <FileUpload
+ mode="advanced"
+ name="resource-image"
+ customUpload
+ uploadHandler={async (e) => {
+ const formData = new FormData();
+ formData.append("file", e.files[0]);
try {
- await axios.post(`${process.env.PUBLIC_URL}/file/bt`, btPayload);
- toast.current?.show({ severity: 'success', summary: '资源登记成功', detail: '已同步种子信息' });
- } catch (btError) {
- console.error("种子登记失败:", btError);
- toast.current?.show({ severity: 'warn', summary: '资源已上传', detail: '但种子登记失败' });
+ const res = await axios.post(
+ `${process.env.PUBLIC_URL}/file`,
+ formData
+ );
+
+ const fileUrl = res.data.url;
+ console.log(fileUrl);
+ setResourcePictureUrl(fileUrl);
+ toast.current?.show({
+ severity: "success",
+ summary: "上传成功",
+ });
+ } catch (error) {
+ console.log(error);
+ toast.current?.show({
+ severity: "error",
+ summary: "上传失败",
+ });
}
-
- // 上传成功
- setVisible(false);
- // 重置表单
- setResourceFormData({
- resource: {
- resourceName: '',
- resourcePicture: '',
- resourceSummary: '',
- resourceDetail: '',
- uploadTime: '',
- lastUpdateTime: '',
- price: '',
- classify: '',
- },
- gameplayList: [],
- completeRewardId: null,
- userId: 0,
- });
- // 重置资源分类
- setIngredient("");
- // 重置资源标签
- setSelectedGameplay([]);
- // 重置资源封面
- setResourcePictureUrl('');
- setTorrentUrl('');
- }
- } catch (error) {
- console.error('资源上传失败:', error);
- toast.current?.show({ severity: 'error', summary: 'error', detail: '资源上传失败' });
- }
- };
-
- return (
- <div className="user-container">
- <Toast ref={toast}></Toast>
- {/*个人信息*/}
- <div className="user-profile-card">
- <Avatar
- image={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>
+ }}
+ auto
+ chooseLabel="上传资源封面"
+ />
+ </div>
+ <div className="form-field">
+ <div className="form-field-header">
+ <span className="form-field-sign">*</span>
+ <label>上传资源文件</label>
</div>
+ <FileUpload
+ mode="advanced"
+ name="resource-file"
+ customUpload
+ uploadHandler={async (e) => {
+ const formData = new FormData();
+ formData.append("file", e.files[0]);
- {/*个人内容*/}
- <TabView activeIndex={activeIndex} onTabChange={(e) => setActiveIndex(e.index)}>
- <TabPanel header="主页" >
- {/*发布资源*/}
- <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={ 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={() => setActiveIndex(1)}
- />
- </div>
- <div className="resource-grid">
- {homePageThread?.records.map((homePageThread) => (
- <Card key={homePageThread.threadId} className="resource-card" onClick={() => router.push(`/community/thread-detail/${homePageThread.threadId}`)}>
- <Image
- src={ homePageThread.threadPicture}
- alt={homePageThread.title}
- width="368"
- height="200"
- />
- <div className="card-content">
- <h3>{homePageThread.title}</h3>
- <div className="view-count">
- <Fire theme="outline" size="16" fill="#FF8D1A" />
- <span>{homePageThread.likes}</span>
- </div>
- </div>
- </Card>
- ))}
- </div>
- </div>
- </TabPanel>
-
- <TabPanel header="帖子">
- {/*我的帖子*/}
- <div className="homepage-item">
- <div className="section-header">
- <h1>我的帖子</h1>
- </div>
- <div className="resource-grid">
- {threadList.map((threadList) => (
- <Card key={threadList.threadId} className="resource-card" onClick={() => router.push(`/community/thread-detail/${threadList.threadId}`)}>
- <Image
- src={ threadList.threadPicture}
- alt={threadList.title}
- width="368"
- height="200"
- />
- <div className="card-content">
- <h3>{threadList.title}</h3>
- <div className="view-count">
- <Fire theme="outline" size="16" fill="#FF8D1A" />
- <span>{threadList.likes}</span>
- </div>
- </div>
- </Card>
- ))}
- </div>
- {totalThreads > 6 && <Paginator
- className="Paginator"
- first={threadFirst}
- rows={threadRows}
- totalRecords={totalThreads}
- rowsPerPageOptions={[6, 12]}
- onPageChange={onThreadPageChange}
- />}
- </div>
- </TabPanel>
-
- <TabPanel header="收藏">
-
- </TabPanel>
-
- <TabPanel header="数据">
-
- </TabPanel>
-
- <TabPanel header="悬赏">
- <div className="section-header">
- <h1>我的悬赏</h1>
- </div>
-
- <div className="resource-list">
- {rewardList.map((rewardItem) => (
- <Card key={rewardItem.rewardId} className="resources-list-card"
- onClick={() => router.push(`/reward/reward-detail/${rewardItem.rewardId}`)}>
- <Image alt="avatar"
- src={ "rewards/" + rewardItem.rewardPicture}
- className="resource-avatar" width="250" height="140"/>
- <div className="resource-header">
- <div className="resource-content">
- <h3>{rewardItem.rewardName}</h3>
- </div>
-
- <div className="resource-operation">
- <Button
- label="编辑"
- onClick={(e) => {
- e.stopPropagation(); // 关键修复:阻止事件冒泡,避免触发Card的点击事件
- setEditVisible(true);
-
- setEditRewardFormData({
- rewardId: rewardItem.rewardId,
- rewardName: rewardItem.rewardName,
- price: rewardItem.price.toString(),
- rewardDescription: rewardItem.rewardDescription,
- });
- console.log("用户编辑悬赏");
- }}
- />
- <Button
- label="删除"
- onClick={(e) => {
- e.stopPropagation(); // 关键修复:阻止事件冒泡,避免触发Card的点击事件
- setDeleteResourceId(rewardItem.rewardId);
- setDeleteVisible(true);
- }}
- style={{ backgroundColor: "rgba(255, 87, 51, 1)" }}
- />
- </div>
- </div>
- </Card>
- ))}
-
- {totalRewards > 5 && <Paginator
- className="Paginator"
- first={rewardFirst}
- rows={rewardRows}
- totalRecords={totalRewards}
- rowsPerPageOptions={[5, 10]}
- onPageChange={onRewardPageChange}
- />}
- </div>
- </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>
-
- {/*发布资源弹窗*/}
- <Dialog
- header="发布资源"
- visible={visible}
- onHide={() => setVisible(false)}
- className="publish-dialog"
- modal
- footer={
- <div className="dialog-footer">
- <Button label="发布" icon="pi pi-check" onClick={handleSubmit} autoFocus />
- <Button label="取消" icon="pi pi-times" onClick={() => setVisible(false)} className="p-button-text" />
- </div>
+ try {
+ const res = await axios.post(
+ `${process.env.PUBLIC_URL}/file`,
+ formData
+ );
+ const fileUrl = res.data;
+ console.log("上传的 torrent 文件 URL:", fileUrl);
+ setTorrentUrl(fileUrl);
+ toast.current?.show({
+ severity: "success",
+ summary: "上传成功",
+ detail: "资源文件已上传",
+ });
+ } catch (error) {
+ console.error("上传资源文件失败:", error);
+ toast.current?.show({
+ severity: "error",
+ summary: "上传失败",
+ detail: "资源文件上传失败",
+ });
}
- >
- <div className="publish-form">
- <div className="form-field">
- <div className="form-field-header">
- <span className="form-field-sign">*</span>
- <label htmlFor="name">资源名称</label>
- </div>
- <InputText
- id="name"
- value={resourceFormData.resource.resourceName}
- onChange={(e) => setResourceFormData(prev => ({
- ...prev, // 复制顶层所有属性
- resource: {
- ...prev.resource, // 复制resource对象的所有属性
- resourceName: e.target.value // 只更新resourceName
- }
- }))}
- placeholder="请输入资源名称"
- className="w-full"
- />
- </div>
- <div className="form-field">
- <div className="form-field-header">
- <span className="form-field-sign">*</span>
- <label htmlFor="summary">资源简介</label>
- </div>
- <InputText
- id="summary"
- value={resourceFormData.resource.resourceSummary}
- onChange={(e) => setResourceFormData(prev => ({
- ...prev, // 复制顶层所有属性
- resource: {
- ...prev.resource, // 复制resource对象的所有属性
- resourceSummary: e.target.value
- }
- }))}
- placeholder="请输入资源简介(一句话)"
- className="w-full"
- />
- </div>
- <div className="form-field">
- <div className="form-field-header">
- <label htmlFor="detail">资源介绍</label>
- </div>
- <InputTextarea
- id="detail"
- value={resourceFormData.resource.resourceDetail}
- onChange={(e) => setResourceFormData(prev => ({
- ...prev, // 复制顶层所有属性
- resource: {
- ...prev.resource, // 复制resource对象的所有属性
- resourceDetail: e.target.value
- }
- }))}
- rows={5}
- placeholder="请输入资源介绍"
- className="w-full"
- />
- </div>
- <div className="form-field">
- <div className="form-field-header">
- <span className="form-field-sign">*</span>
- <label htmlFor="price">价格</label>
- </div>
- <InputText
- id="price"
- value={resourceFormData.resource.price}
- onChange={(e) => setResourceFormData(prev => ({
- ...prev, // 复制顶层所有属性
- resource: {
- ...prev.resource, // 复制resource对象的所有属性
- price: e.target.value
- }
- }))}
- placeholder="请输入资源价格"
- className="w-full"
- />
- </div>
- <div className="form-field">
- <div className="form-field-header">
- <span className="form-field-sign">*</span>
- <label htmlFor="classify">资源分类(请选择一项)</label>
- </div>
- <div className="form-field-classify">
- <div className="flex align-items-center">
- <RadioButton
- inputId="ingredient1"
- name="pizza"
- value="resourcePack"
- onChange={(e: RadioButtonChangeEvent) => {
- setResourceFormData(prev => ({
- ...prev, // 复制顶层所有属性
- resource: {
- ...prev.resource, // 复制resource对象的所有属性
- classify: e.target.value
- }
- }));
- setIngredient(e.value);
- console.log(ingredient);
- // console.log(resourceFormData.resource.classify);
- }}
- checked={ingredient === 'resourcePack'}
- />
- <label htmlFor="ingredient1" className="ml-2">材质包</label>
- </div>
- <div className="flex align-items-center">
- <RadioButton
- inputId="ingredient2"
- name="pizza"
- value="modPack"
- onChange={(e: RadioButtonChangeEvent) => {
- setResourceFormData(prev => ({
- ...prev, // 复制顶层所有属性
- resource: {
- ...prev.resource, // 复制resource对象的所有属性
- classify: e.target.value
- }
- }));
- setIngredient(e.value);
- }}
- checked={ingredient === 'modPack'}
- />
- <label htmlFor="ingredient2" className="ml-2">整合包</label>
- </div>
- <div className="flex align-items-center">
- <RadioButton
- inputId="ingredient3"
- name="pizza"
- value="mod"
- onChange={(e: RadioButtonChangeEvent) => {
- setResourceFormData(prev => ({
- ...prev, // 复制顶层所有属性
- resource: {
- ...prev.resource, // 复制resource对象的所有属性
- classify: e.target.value
- }
- }));
- setIngredient(e.value);
- }}
- checked={ingredient === 'mod'}
- />
- <label htmlFor="ingredient3" className="ml-2">模组</label>
- </div>
- <div className="flex align-items-center">
- <RadioButton
- inputId="ingredient4"
- name="pizza"
- value="map"
- onChange={(e: RadioButtonChangeEvent) => {
- setResourceFormData(prev => ({
- ...prev, // 复制顶层所有属性
- resource: {
- ...prev.resource, // 复制resource对象的所有属性
- classify: e.target.value
- }
- }));
- setIngredient(e.value);
- }}
- checked={ingredient === 'map'}
- />
- <label htmlFor="ingredient4" className="ml-2">地图</label>
- </div>
- </div>
- </div>
- <div className="form-field">
- <div className="form-field-header">
- <span className="form-field-sign">*</span>
- <label htmlFor="gameplayList">资源标签</label>
- </div>
- <MultiSelect
- value={selectedGameplay}
- onChange={(e: MultiSelectChangeEvent) => {
- const selectedOptions = e.value as GameplayOption[];
- // 提取选中项的 name 属性组成字符串数组
- const selectedNames = selectedOptions.map(item => item.name);
-
- setResourceFormData(prev => ({
- ...prev,
- gameplayList: selectedNames
- }));
- setSelectedGameplay(selectedOptions);
- }}
- options={gameplayOptions}
- display="chip"
- optionLabel="name"
- placeholder="请选择资源标签"
- className="w-full md:w-20rem"
- />
- </div>
- <div className="form-field">
- <div className="form-field-header">
- <span className="form-field-sign">*</span>
- <label>封面图片</label>
- </div>
- <FileUpload
- mode="advanced"
- name="resource-image"
- customUpload
- uploadHandler={async (e) => {
- const formData = new FormData();
- formData.append("file", e.files[0]);
-
- try {
- const res = await axios.post(`${process.env.PUBLIC_URL}/file`, formData);
-
- const fileUrl = res.data.url;
- console.log(fileUrl);
- setResourcePictureUrl(fileUrl);
- toast.current?.show({ severity: 'success', summary: '上传成功' });
- } catch (error) {
- console.log(error);
- toast.current?.show({ severity: 'error', summary: '上传失败' });
- }
- }}
- auto
- chooseLabel="上传资源封面"
- />
- </div>
- <div className="form-field">
- <div className="form-field-header">
- <span className="form-field-sign">*</span>
- <label>上传资源文件</label>
- </div>
- <FileUpload
- mode="advanced"
- name="resource-file"
- customUpload
- uploadHandler={async (e) => {
- const formData = new FormData();
- formData.append("file", e.files[0]);
-
- try {
- const res = await axios.post(`${process.env.PUBLIC_URL}/file`, formData);
- const fileUrl = res.data.url;
- console.log("上传的 torrent 文件 URL:", fileUrl);
- setTorrentUrl(fileUrl);
- toast.current?.show({ severity: 'success', summary: '上传成功', detail: '资源文件已上传' });
- } catch (error) {
- console.error("上传资源文件失败:", error);
- toast.current?.show({ severity: 'error', summary: '上传失败', detail: '资源文件上传失败' });
- }
- }}
- auto
- chooseLabel="上传资源文件(.torrent)"
- />
- </div>
-
- </div>
- </Dialog>
-
- {/*删除悬赏弹窗*/}
- <Dialog
- header="删除悬赏"
- visible={deleteVisible}
- onHide={() => setDeleteVisible(false)}
- className="resource-delete-dialog"
- modal
- footer={
- <div className="dialog-footer">
- <Button label="确认" icon="pi pi-check" onClick={handleDeleteSubmit} autoFocus />
- <Button label="取消" icon="pi pi-times" onClick={() => setDeleteVisible(false)} className="p-button-text" />
- </div>
- }
- >
- <div className="dialog-form">
- <span style={{ marginBottom: "10px" }}>
- 确认是否删除该悬赏?
- </span>
- </div>
- </Dialog>
-
- {/*编辑资源弹窗*/}
- <Dialog
- header="编辑资源"
- visible={editVisible}
- onHide={() => setEditVisible(false)}
- className="resource-edit-dialog"
- modal
- footer={
- <div className="dialog-footer">
- <Button label="确认" icon="pi pi-check" onClick={handleEditSubmit} autoFocus />
- <Button label="取消" icon="pi pi-times" onClick={() => setEditVisible(false)} className="p-button-text" />
- </div>
- }
- >
- <div className="dialog-form">
- <div className="form-field">
- <div className="form-field-header">
- <label htmlFor="name">更改标题</label>
- </div>
- <InputText
- id="name"
- value={editRewardFormData.rewardName}
- onChange={(e) => setEditRewardFormData(prev => ({
- ...prev,
- rewardName: e.target.value
- }))}
- className="w-full"
- />
- </div>
- <div className="form-field">
- <div className="form-field-header">
- <label htmlFor="price">更改定价</label>
- </div>
- <InputText
- id="price"
- value={editRewardFormData.price}
- onChange={(e) => setEditRewardFormData(prev => ({
- ...prev,
- price: e.target.value
- }))}
- className="w-full"
- />
- </div>
- <div className="form-field">
- <div className="form-field-header">
- <label htmlFor="description">更改需求</label>
- </div>
- <InputTextarea
- id="description"
- value={editRewardFormData.rewardDescription}
- onChange={(e) => setEditRewardFormData(prev => ({
- ...prev,
- rewardDescription: e.target.value
- }))}
- rows={5}
- className="w-full"
- />
- </div>
- <div className="form-field">
- <div className="form-field-header">
- <span className="form-field-sign">*</span>
- <label>封面图片</label>
- </div>
- <FileUpload
- mode="advanced"
- name="reward-image"
- customUpload
- uploadHandler={async (e) => {
- const formData = new FormData();
- formData.append("file", e.files[0]);
-
- try {
- const res = await axios.post(`${process.env.PUBLIC_URL}/file`, formData);
-
- const fileUrl = res.data.url;
- console.log(fileUrl);
- setRewardPictureUrl(fileUrl);
- toast.current?.show({ severity: 'success', summary: '上传成功' });
- } catch (error) {
- console.log(error);
- toast.current?.show({ severity: 'error', summary: '上传失败' });
- }
- }}
- auto
- accept="image/*"
- chooseLabel="选择悬赏封面"
- />
- </div>
- </div>
- </Dialog>
+ }}
+ auto
+ chooseLabel="上传资源文件(.torrent)"
+ />
+ </div>
</div>
- );
-};
+ </Dialog>
+ {/*删除悬赏弹窗*/}
+ <Dialog
+ header="删除悬赏"
+ visible={deleteVisible}
+ onHide={() => setDeleteVisible(false)}
+ className="resource-delete-dialog"
+ modal
+ footer={
+ <div className="dialog-footer">
+ <Button
+ label="确认"
+ icon="pi pi-check"
+ onClick={handleDeleteSubmit}
+ autoFocus
+ />
+ <Button
+ label="取消"
+ icon="pi pi-times"
+ onClick={() => setDeleteVisible(false)}
+ className="p-button-text"
+ />
+ </div>
+ }
+ >
+ <div className="dialog-form">
+ <span style={{ marginBottom: "10px" }}>确认是否删除该悬赏?</span>
+ </div>
+ </Dialog>
+
+ {/*编辑资源弹窗*/}
+ <Dialog
+ header="编辑资源"
+ visible={editVisible}
+ onHide={() => setEditVisible(false)}
+ className="resource-edit-dialog"
+ modal
+ footer={
+ <div className="dialog-footer">
+ <Button
+ label="确认"
+ icon="pi pi-check"
+ onClick={handleEditSubmit}
+ autoFocus
+ />
+ <Button
+ label="取消"
+ icon="pi pi-times"
+ onClick={() => setEditVisible(false)}
+ className="p-button-text"
+ />
+ </div>
+ }
+ >
+ <div className="dialog-form">
+ <div className="form-field">
+ <div className="form-field-header">
+ <label htmlFor="name">更改标题</label>
+ </div>
+ <InputText
+ id="name"
+ value={editRewardFormData.rewardName}
+ onChange={(e) =>
+ setEditRewardFormData((prev) => ({
+ ...prev,
+ rewardName: e.target.value,
+ }))
+ }
+ className="w-full"
+ />
+ </div>
+ <div className="form-field">
+ <div className="form-field-header">
+ <label htmlFor="price">更改定价</label>
+ </div>
+ <InputText
+ id="price"
+ value={editRewardFormData.price}
+ onChange={(e) =>
+ setEditRewardFormData((prev) => ({
+ ...prev,
+ price: e.target.value,
+ }))
+ }
+ className="w-full"
+ />
+ </div>
+ <div className="form-field">
+ <div className="form-field-header">
+ <label htmlFor="description">更改需求</label>
+ </div>
+ <InputTextarea
+ id="description"
+ value={editRewardFormData.rewardDescription}
+ onChange={(e) =>
+ setEditRewardFormData((prev) => ({
+ ...prev,
+ rewardDescription: e.target.value,
+ }))
+ }
+ rows={5}
+ className="w-full"
+ />
+ </div>
+ <div className="form-field">
+ <div className="form-field-header">
+ <span className="form-field-sign">*</span>
+ <label>封面图片</label>
+ </div>
+ <FileUpload
+ mode="advanced"
+ name="reward-image"
+ customUpload
+ uploadHandler={async (e) => {
+ const formData = new FormData();
+ formData.append("file", e.files[0]);
+
+ try {
+ const res = await axios.post(
+ `${process.env.PUBLIC_URL}/file`,
+ formData
+ );
+
+ const fileUrl = res.data.url;
+ console.log(fileUrl);
+ setRewardPictureUrl(fileUrl);
+ toast.current?.show({
+ severity: "success",
+ summary: "上传成功",
+ });
+ } catch (error) {
+ console.log(error);
+ toast.current?.show({
+ severity: "error",
+ summary: "上传失败",
+ });
+ }
+ }}
+ auto
+ accept="image/*"
+ chooseLabel="选择悬赏封面"
+ />
+ </div>
+ </div>
+ </Dialog>
+ </div>
+ );
+}