blob: 6f29b02b133ae0c73f0416319e72b05023a31a3e [file] [log] [blame]
lfmylbhf789e7172025-06-06 18:37:21 +08001'use client';
2
3import React, { useEffect, useState, useRef } from 'react';
4import { TabView, TabPanel } from 'primereact/tabview';
5import { Avatar } from 'primereact/avatar';
6import { Button } from 'primereact/button';
7import { Card } from 'primereact/card';
LaoeGaocid0773912025-06-09 00:38:40 +08008import { Image } from "primereact/image";
lfmylbhf1783a092025-06-07 20:05:22 +08009// 发布资源
10import { Dialog } from 'primereact/dialog';
LaoeGaocid0773912025-06-09 00:38:40 +080011import { InputText } from "primereact/inputtext";
12import { InputTextarea } from "primereact/inputtextarea";
13import { FileUpload } from "primereact/fileupload";
lfmylbhf1783a092025-06-07 20:05:22 +080014// 资源分类
15import { RadioButton, RadioButtonChangeEvent } from "primereact/radiobutton";
16// 资源标签
17import { MultiSelect, MultiSelectChangeEvent } from 'primereact/multiselect';
lfmylbhf789e7172025-06-06 18:37:21 +080018// 浮动按钮
19import { SpeedDial } from 'primereact/speeddial';
20// 评分图标
21import { Fire } from '@icon-park/react';
22// 消息提醒
23import { Toast } from 'primereact/toast';
24// 页面跳转
25import { useRouter } from "next/navigation";
lfmylbhfa9fa5c82025-06-09 00:34:49 +080026// 类型转换
LaoeGaoci68253852025-06-09 22:42:18 +080027import { toNumber } from "lodash";
lfmylbhfa9fa5c82025-06-09 00:34:49 +080028// 分页
29import { Paginator, type PaginatorPageChangeEvent } from 'primereact/paginator';
lfmylbhf789e7172025-06-06 18:37:21 +080030
31// 接口传输
32import axios from "axios";
LaoeGaocid0773912025-06-09 00:38:40 +080033import { useLocalStorage } from '../hook/useLocalStorage';
lfmylbhf789e7172025-06-06 18:37:21 +080034// 样式
35import './user.scss';
lfmylbhf789e7172025-06-06 18:37:21 +080036
LaoeGaocid0773912025-06-09 00:38:40 +080037interface User {
38 Id: number;
39};
40
lfmylbhf789e7172025-06-06 18:37:21 +080041// 用户信息
42interface UserInfo {
43 userId: number;
44 username: string;
45 password: string;
46 avatar: string;
47 followerCount: number;// 粉丝数
48 subscriberCount: number;// 关注数
LaoeGaocid0773912025-06-09 00:38:40 +080049 signature: string;// 个性签名
lfmylbhf789e7172025-06-06 18:37:21 +080050 uploadAmount: number;
51 purchaseAmount: number;
52 credits: number;
53}
54
55// 用户数据
56interface UserData {
LaoeGaocid0773912025-06-09 00:38:40 +080057 subscriberCount: number; // 关注数
58 uploadAmount: number; // 上传量(资源个数)
59 beDownloadedAmount: number; // 上传资源被下载量
lfmylbhf789e7172025-06-06 18:37:21 +080060 seedPercentageList: number[]; // 上传资源类型百分比列表,按材质包、模组、整合包、地图的顺序返回
61}
62
lfmylbhf1783a092025-06-07 20:05:22 +080063// 用户发布过的资源
lfmylbhf789e7172025-06-06 18:37:21 +080064interface Resource {
65 resourceId: number;
66 resourceName: string;
LaoeGaocid0773912025-06-09 00:38:40 +080067 resourcePicture: string;
68 resourceSummary: string; // 资源简介(一句话)
lfmylbhf789e7172025-06-06 18:37:21 +080069 resourceDetail: string; // 资源介绍
70 uploadTime: string; // 上传时间
LaoeGaocid0773912025-06-09 00:38:40 +080071 lastUpdateTime: string; // 最近更新时间
72 price: number;
73 downloads: number;
74 likes: number;
lfmylbhf789e7172025-06-06 18:37:21 +080075 collections: number;
76 comments: number;
LaoeGaocid0773912025-06-09 00:38:40 +080077 seeds: number; // 种子数
lfmylbhf789e7172025-06-06 18:37:21 +080078 classify: string; // 资源分类(材质包:resourcePack,模组:mod,整合包:modPack ,地图:map
79}
80
lfmylbhf1783a092025-06-07 20:05:22 +080081// 用户发布过的资源列表
lfmylbhf789e7172025-06-06 18:37:21 +080082interface ResourceList {
83 records: Resource[];
84}
85
lfmylbhfa9fa5c82025-06-09 00:34:49 +080086// 帖子
87interface Thread {
88 threadId: number;
LaoeGaoci68253852025-06-09 22:42:18 +080089 userId: number;
90 threadPicture: string;
lfmylbhfa9fa5c82025-06-09 00:34:49 +080091 title: string;
LaoeGaoci68253852025-06-09 22:42:18 +080092 likes: number;
93 createAt: string;
lfmylbhfa9fa5c82025-06-09 00:34:49 +080094}
95
96// 发布帖子列表
97interface ThreadList {
98 records: Thread[];
99 total: number;
100 pages: number;
101 current: number;
102 size: number;
103}
104
105// 悬赏
106interface Reward {
107 rewardId: number;
108 userId: number;
109 rewardPicture: string;
110 rewardName: string;
111 createAt: string;
112 rewardDescription: string;
113 price: number;
114}
115
116// 我的悬赏列表
117interface RewardList {
118 rewardList: Reward[];
119 total: number; // 总记录数
120}
121
lfmylbhf1783a092025-06-07 20:05:22 +0800122// 资源标签
123interface GameplayOption {
124 name: string;
125 code: number;
126}
127
128// 资源标签选项
129const gameplayOptions: GameplayOption[] = [
130 { name: '科技', code: 1 },
131 { name: '魔法', code: 2 },
132 { name: '建筑', code: 3 },
133 { name: '风景', code: 4 },
134 { name: '竞技', code: 5 },
135 { name: '生存', code: 6 },
136 { name: '冒险', code: 7 },
137 { name: '跑酷', code: 8 },
138 { name: '艺术', code: 9 },
139 { name: '剧情', code: 10 },
140 { name: '社交', code: 11 },
141 { name: '策略', code: 12 },
142 { name: '极限', code: 13 }
143];
144
lfmylbhf789e7172025-06-06 18:37:21 +0800145export default function UserPage() {
LaoeGaocid0773912025-06-09 00:38:40 +0800146 const user = useLocalStorage<User>('user');
147 const userId: number = user?.Id ?? -1;
148
lfmylbhf789e7172025-06-06 18:37:21 +0800149 // 路由
150 const router = useRouter();
151 // 发布资源列表
152 const [resourceList, setResourceList] = useState<Resource[]>([]);
153 // 用户信息
154 const [userInfo, setUserInfo] = useState<UserInfo>();
155 // 用户数据
156 const [userData, setUserData] = useState<UserData>();
157 // 消息提醒
158 const toast = useRef<Toast>(null);
lfmylbhf1783a092025-06-07 20:05:22 +0800159 // 资源标签
160 const [selectedGameplay, setSelectedGameplay] = useState<GameplayOption[]>([]);
lfmylbhfa9fa5c82025-06-09 00:34:49 +0800161 // 资源封面路径
162 const [resourcePictureUrl, setResourcePictureUrl] = useState<string>('');
163 // 主页发布帖子列表
164 const [homePageThread, setHomePageThread] = useState<ThreadList>();
165 // 我的帖子列表
166 const [threadList, setThreadList] = useState<Thread[]>([]);
167 // 我的悬赏列表
168 const [rewardList, setRewardList] = useState<Reward[]>([]);
169 // 控制Tab切换
170 const [activeIndex, setActiveIndex] = useState(0);
171 // 帖子分页
172 const [threadFirst, setThreadFirst] = useState(0);
173 const [threadRows, setThreadRows] = useState(6);
174 const [totalThreads, setTotalThreads] = useState<number>(0);
175 const onThreadPageChange = (event: PaginatorPageChangeEvent) => {
176 setThreadFirst(event.first);
177 setThreadRows(event.rows);
178 };
LaoeGaoci68253852025-06-09 22:42:18 +0800179 const [torrentUrl, setTorrentUrl] = useState<string>(''); // 上传 .torrent 后得到的 URL
180
lfmylbhfa9fa5c82025-06-09 00:34:49 +0800181 // 悬赏分页
182 const [rewardFirst, setRewardFirst] = useState(0);
183 const [rewardRows, setRewardRows] = useState(5);
184 const [totalRewards, setTotalRewards] = useState<number>(0);
185 const onRewardPageChange = (event: PaginatorPageChangeEvent) => {
186 setRewardFirst(event.first);
187 setRewardRows(event.rows);
188 };
lfmylbhf789e7172025-06-06 18:37:21 +0800189
190 useEffect(() => {
191 fetchUserInfo();
192 fetchUserData();
193 fetchResourceList();
194 }, []);
195
196 // 获取用户信息
197 const fetchUserInfo = async () => {
198 try {
199 const response = await axios.get<UserInfo>(process.env.PUBLIC_URL + `/user/info`, {
LaoeGaocid0773912025-06-09 00:38:40 +0800200 params: { userId }
lfmylbhf789e7172025-06-06 18:37:21 +0800201 });
202 console.log('获取用户信息:', response.data);
203 setUserInfo(response.data);
204 } catch (err) {
205 console.error('获取用户信息失败', err);
206 toast.current?.show({ severity: 'error', summary: 'error', detail: '获取用户信息失败' });
207 }
208 };
209
210 // 获取用户数据
211 const fetchUserData = async () => {
212 try {
213 const response = await axios.get<UserData>(process.env.PUBLIC_URL + `/user/data`, {
LaoeGaocid0773912025-06-09 00:38:40 +0800214 params: { userId }
lfmylbhf789e7172025-06-06 18:37:21 +0800215 });
216 console.log('获取用户数据:', response.data);
217 setUserData(response.data);
218 } catch (err) {
219 console.error('获取用户数据失败', err);
220 toast.current?.show({ severity: 'error', summary: 'error', detail: '获取用户数据失败' });
221 }
222 };
223
224 // 格式化数字显示 (3000 -> 3k)
225 const formatCount = (count?: number): string => {
226 if (count == null) return "0"; // 同时处理 undefined/null
227
228 const absCount = Math.abs(count); // 处理负数
229
230 const format = (num: number, suffix: string) => {
231 const fixed = num.toFixed(1);
232 return fixed.endsWith('.0')
233 ? `${Math.floor(num)}${suffix}`
234 : `${fixed}${suffix}`;
235 };
236
237 if (absCount >= 1e6) return format(count / 1e6, "m");
238 if (absCount >= 1e3) return format(count / 1e3, "k");
239 return count.toString();
240 };
241
242 // 获取发布资源
243 const fetchResourceList = async () => {
244 try {
LaoeGaocid0773912025-06-09 00:38:40 +0800245 const response = await axios.get<ResourceList>(process.env.PUBLIC_URL + `/user/upload`, {
246 params: { userId, pageNumber: 1, rows: 3 }
lfmylbhf789e7172025-06-06 18:37:21 +0800247 });
248 console.log('获取发布资源列表:', response.data.records);
249 setResourceList(response.data.records);
lfmylbhf789e7172025-06-06 18:37:21 +0800250 } catch (err) {
251 console.error('获取发布资源失败', err);
252 toast.current?.show({ severity: 'error', summary: 'error', detail: '获取发布资源失败' });
253 }
254 };
255
lfmylbhfa9fa5c82025-06-09 00:34:49 +0800256 useEffect(() => {
257 fetchHomePageThread();
258 }, []);
259
260 // 获取主页部分的发布帖子
261 const fetchHomePageThread = async () => {
262 try {
263 const response = await axios.get<ThreadList>(process.env.PUBLIC_URL + `/user/thread`, {
LaoeGaoci68253852025-06-09 22:42:18 +0800264 params: { userId: userId, pageNumber: 1, rows: 3 }
lfmylbhfa9fa5c82025-06-09 00:34:49 +0800265 })
266 console.log('获取主页发布帖子:', response.data);
267 setHomePageThread(response.data);
268
269 } catch (err) {
270 console.error('获取主页发布帖子失败', err);
271 toast.current?.show({ severity: 'error', summary: 'error', detail: '获取主页发布帖子失败' });
272 }
273 }
274
275 useEffect(() => {
276 fetchThreadList();
277 }, [threadFirst, threadRows]);
278
279 // 获取用户发布的所有帖子
280 const fetchThreadList = async () => {
281 try {
282 const pageNumber = threadFirst / threadRows + 1;
283 const response = await axios.get<ThreadList>(process.env.PUBLIC_URL + `/user/thread`, {
LaoeGaoci68253852025-06-09 22:42:18 +0800284 params: { userId: userId, pageNumber: pageNumber, rows: threadRows }
lfmylbhfa9fa5c82025-06-09 00:34:49 +0800285 })
286 console.log('获取我的帖子:', response.data);
287 setThreadList(response.data.records);
288 setTotalThreads(response.data.total)
LaoeGaoci68253852025-06-09 22:42:18 +0800289 } catch (err) {
lfmylbhfa9fa5c82025-06-09 00:34:49 +0800290 console.error('获取我的帖子失败', err);
291 toast.current?.show({ severity: 'error', summary: 'error', detail: '获取我的帖子失败' });
292 }
293 }
294
295 useEffect(() => {
296 fetchRewardList();
297 }, [rewardFirst, rewardRows]);
298
299 // 获取用户发布的所有悬赏
300 const fetchRewardList = async () => {
301 try {
302 const pageNumber = rewardFirst / rewardRows + 1;
303 const response = await axios.get<RewardList>(process.env.PUBLIC_URL + `/user/reward`, {
LaoeGaoci68253852025-06-09 22:42:18 +0800304 params: { userId: userId, pageNumber: pageNumber, rows: rewardRows }
lfmylbhfa9fa5c82025-06-09 00:34:49 +0800305 })
306 console.log('获取我的悬赏:', response.data);
307 setRewardList(response.data.rewardList);
308 setTotalRewards(response.data.total)
LaoeGaoci68253852025-06-09 22:42:18 +0800309 } catch (err) {
lfmylbhfa9fa5c82025-06-09 00:34:49 +0800310 console.error('获取我的悬赏失败', err);
311 toast.current?.show({ severity: 'error', summary: 'error', detail: '获取我的悬赏失败' });
312 }
313 }
314
lfmylbhf789e7172025-06-06 18:37:21 +0800315 // 浮动按钮的子模块
316 const actions = [
317 {
318 template: () => (
LaoeGaocid0773912025-06-09 00:38:40 +0800319 <Button label="管理资源" onClick={() => router.push(`/user/manage/resources/`)} />
lfmylbhf789e7172025-06-06 18:37:21 +0800320 )
321 },
322 {
323 template: () => (
LaoeGaocid0773912025-06-09 00:38:40 +0800324 <Button label="已购资源" onClick={() => router.push(`/user/purchased-resources/`)} />
lfmylbhf789e7172025-06-06 18:37:21 +0800325 )
326 },
327 {
328 template: () => (
LaoeGaocid0773912025-06-09 00:38:40 +0800329 <Button label="发布资源" onClick={() => setVisible(true)} />
lfmylbhf789e7172025-06-06 18:37:21 +0800330 )
331 },
332 {
333 template: () => (
lfmylbhf839e5a12025-06-09 18:22:25 +0800334 <Button label="编辑悬赏" />
lfmylbhf789e7172025-06-06 18:37:21 +0800335 )
336 }
337 ];
338
lfmylbhf1783a092025-06-07 20:05:22 +0800339 // 发布资源弹窗
340 const [visible, setVisible] = useState(false);
341 const [resourceFormData, setResourceFormData] = useState({
342 resource: {
343 resourceName: '',
344 resourcePicture: '',
345 resourceSummary: '',
346 resourceDetail: '',
347 uploadTime: '',
348 lastUpdateTime: '',
349 price: '',
350 classify: '',
351 },
352 gameplayList: [''],
353 completeRewardId: null,
354 userId: 0,
355 });
356 const [ingredient, setIngredient] = useState<string>('');
357
lfmylbhfa9fa5c82025-06-09 00:34:49 +0800358 // 删除悬赏弹窗
359 const [deleteVisible, setDeleteVisible] = useState(false);
360 // 要删除悬赏的id
LaoeGaoci68253852025-06-09 22:42:18 +0800361 const [deleteRewardId, setDeleteResourceId] = useState<number>(0);
lfmylbhfa9fa5c82025-06-09 00:34:49 +0800362 // 处理删除悬赏接口
363 const handleDeleteSubmit = async () => {
364 try {
365 // 发送DELETE请求
366 const response = await axios.delete(process.env.PUBLIC_URL + `/reward`, {
LaoeGaoci68253852025-06-09 22:42:18 +0800367 params: { rewardId: deleteRewardId },
lfmylbhfa9fa5c82025-06-09 00:34:49 +0800368 });
LaoeGaoci40197d52025-06-09 17:15:52 +0800369 console.log("用户" + userId + "要删除" + deleteRewardId + "号悬赏");
lfmylbhf1783a092025-06-07 20:05:22 +0800370
lfmylbhfa9fa5c82025-06-09 00:34:49 +0800371 if (response.status === 204) {
372 console.log("用户成功删除悬赏");
LaoeGaoci68253852025-06-09 22:42:18 +0800373 toast.current?.show({ severity: 'success', summary: 'Success', detail: '删除悬赏成功' });
lfmylbhfa9fa5c82025-06-09 00:34:49 +0800374 setDeleteVisible(false);
375 // 重新拉取资源列表
376 fetchRewardList();
377 }
378 } catch (error) {
379 console.error('资源删除失败:', error);
380 toast.current?.show({ severity: 'error', summary: 'error', detail: '密码错误,资源删除失败' });
381 }
382 };
383
384
385 // 编辑悬赏弹窗
386 const [editVisible, setEditVisible] = useState(false);
387 // 悬赏封面路径
388 const [rewardPictureUrl, setRewardPictureUrl] = useState<string>('');
389 const [editRewardFormData, setEditRewardFormData] = useState({
390 rewardId: 0,
391 rewardName: '',
392 price: '',
393 rewardDescription: '',
394 });
395
396 // 处理编辑资源接口
397 const handleEditSubmit = async () => {
398 try {
399 const postData = {
400 rewardId: editRewardFormData.rewardId,
401 rewardName: editRewardFormData.rewardName,
402 rewardPicture: rewardPictureUrl,
403 price: toNumber(editRewardFormData.price),
404 rewardDescription: editRewardFormData.rewardDescription,
405 };
406 // 发送POST请求
407 const response = await axios.put(process.env.PUBLIC_URL + '/reward/info', postData);
408 console.log("编辑悬赏的信息:", postData);
409
410 if (response.status === 200) {
411 toast.current?.show({ severity: 'success', summary: 'Success', detail: '悬赏编辑成功' });
412 // 编辑成功
413 setEditVisible(false);
414 fetchRewardList();
415 }
416 } catch (error) {
417 console.error('悬赏编辑失败:', error);
418 toast.current?.show({ severity: 'error', summary: 'error', detail: '悬赏编辑失败' });
419 }
lfmylbhf1783a092025-06-07 20:05:22 +0800420 };
421
422 // 上传资源接口
423 const handleSubmit = async () => {
424 try {
425 // 规定用户必须输入的内容
426 if (resourceFormData.resource.resourceName == '') {
427 toast.current?.show({ severity: 'info', summary: 'error', detail: '缺少资源名称' });
428 return;
429 }
430 if (resourceFormData.resource.resourceSummary == '') {
431 toast.current?.show({ severity: 'info', summary: 'error', detail: '缺少资源简介' });
432 return;
433 }
434 if (resourceFormData.resource.price == '') {
435 toast.current?.show({ severity: 'info', summary: 'error', detail: '缺少资源价格' });
436 return;
437 }
438 if (resourceFormData.resource.classify == '') {
439 toast.current?.show({ severity: 'info', summary: 'error', detail: '缺少资源分类' });
440 return;
441 }
442 if (
443 resourceFormData.gameplayList.length === 1 &&
444 resourceFormData.gameplayList[0] === ''
445 ) {
446 toast.current?.show({ severity: 'info', summary: 'error', detail: '缺少资源标签' });
447 return;
448 }
lfmylbhfa9fa5c82025-06-09 00:34:49 +0800449 if (resourcePictureUrl === '') {
lfmylbhf1783a092025-06-07 20:05:22 +0800450 toast.current?.show({ severity: 'info', summary: 'error', detail: '缺少资源封面' });
451 return;
452 }
453
454 const currentDate = new Date().toISOString().split('T')[0];
455 const postData = {
456 resource: {
457 resourceName: resourceFormData.resource.resourceName,
lfmylbhfa9fa5c82025-06-09 00:34:49 +0800458 resourcePicture: resourcePictureUrl,
lfmylbhf1783a092025-06-07 20:05:22 +0800459 resourceSummary: resourceFormData.resource.resourceSummary,
460 resourceDetail: resourceFormData.resource.resourceDetail,
461 uploadTime: currentDate,
462 lastUpdateTime: currentDate,
463 price: toNumber(resourceFormData.resource.price),
464 classify: resourceFormData.resource.classify,
465 },
466 gameplayList: resourceFormData.gameplayList,
467 completeRewardId: null,
LaoeGaocid0773912025-06-09 00:38:40 +0800468 userId, // 记得用户登录状态获取
lfmylbhf1783a092025-06-07 20:05:22 +0800469 };
470 // 发送POST请求
471 const response = await axios.post(process.env.PUBLIC_URL + '/resource', postData);
472 console.log("上传资源的信息:", postData);
473
474 if (response.status === 200) {
475 toast.current?.show({ severity: 'success', summary: 'Success', detail: '资源上传成功' });
LaoeGaoci68253852025-06-09 22:42:18 +0800476 console.log(torrentUrl)
477 // 上传资源文件
478 const btPayload = {
479 torrentUrl: torrentUrl,
480 infoHash: '8',
481 uploadTime: currentDate,
482 uploaderUserId: userId,
483 resourceVersionId: 1
484 };
485
486 try {
487 await axios.post(`${process.env.PUBLIC_URL}/file/bt`, btPayload);
488 toast.current?.show({ severity: 'success', summary: '资源登记成功', detail: '已同步种子信息' });
489 } catch (btError) {
490 console.error("种子登记失败:", btError);
491 toast.current?.show({ severity: 'warn', summary: '资源已上传', detail: '但种子登记失败' });
492 }
493
lfmylbhf1783a092025-06-07 20:05:22 +0800494 // 上传成功
495 setVisible(false);
496 // 重置表单
497 setResourceFormData({
498 resource: {
499 resourceName: '',
500 resourcePicture: '',
501 resourceSummary: '',
502 resourceDetail: '',
503 uploadTime: '',
504 lastUpdateTime: '',
505 price: '',
506 classify: '',
507 },
508 gameplayList: [],
509 completeRewardId: null,
510 userId: 0,
511 });
512 // 重置资源分类
513 setIngredient("");
514 // 重置资源标签
515 setSelectedGameplay([]);
lfmylbhfa9fa5c82025-06-09 00:34:49 +0800516 // 重置资源封面
517 setResourcePictureUrl('');
LaoeGaoci68253852025-06-09 22:42:18 +0800518 setTorrentUrl('');
lfmylbhf1783a092025-06-07 20:05:22 +0800519 }
520 } catch (error) {
521 console.error('资源上传失败:', error);
522 toast.current?.show({ severity: 'error', summary: 'error', detail: '资源上传失败' });
523 }
524 };
525
lfmylbhf789e7172025-06-06 18:37:21 +0800526 return (
527 <div className="user-container">
lfmylbhf1783a092025-06-07 20:05:22 +0800528 <Toast ref={toast}></Toast>
lfmylbhf789e7172025-06-06 18:37:21 +0800529 {/*个人信息*/}
530 <div className="user-profile-card">
531 <Avatar
Seamherbb14ecb2025-06-09 22:37:20 +0800532 image={userInfo?.avatar}
lfmylbhf789e7172025-06-06 18:37:21 +0800533 className="user-avatar"
534 shape="circle"
535 />
536 <div className="user-info">
537 <div className="user-detail-info">
538 <div className="name-container">
539 <h2 className="name">{userInfo?.username}</h2>
540 <span className="signature">{userInfo?.signature}</span>
541 </div>
542
543 <div className="stats-container">
544 <div className="stats">
545 <span className="stats-label">粉丝:</span>
546 <span className="stats-value">{userInfo?.followerCount}</span>
547 </div>
548 <div className="stats">
549 <span className="stats-label">累计上传量:</span>
550 <span className="stats-value">{formatCount(userData?.uploadAmount)}</span>
551 </div>
552 <div className="stats">
553 <span className="stats-label">关注:</span>
554 <span className="stats-value">{userInfo?.subscriberCount}</span>
555 </div>
556 <div className="stats">
557 <span className="stats-label">累计被下载量:</span>
558 <span className="stats-value">{formatCount(userData?.beDownloadedAmount)}</span>
559 </div>
560 </div>
561 </div>
562
LaoeGaocid0773912025-06-09 00:38:40 +0800563 <Button label="关注" className="action-button" />
lfmylbhf789e7172025-06-06 18:37:21 +0800564 </div>
565 </div>
566
567 {/*个人内容*/}
lfmylbhfa9fa5c82025-06-09 00:34:49 +0800568 <TabView activeIndex={activeIndex} onTabChange={(e) => setActiveIndex(e.index)}>
569 <TabPanel header="主页" >
lfmylbhf789e7172025-06-06 18:37:21 +0800570 {/*发布资源*/}
571 <div className="homepage-item">
572 <div className="section-header">
573 <h1>发布资源</h1>
574 <Button
575 label="显示更多"
576 link
577 onClick={() => router.push('/user/manage/resources/')}
578 />
579 </div>
580 <div className="resource-grid">
581 {resourceList.map((resourceList) => (
582 <Card key={resourceList.resourceId} className="resource-card" onClick={() => router.push(`/resource/resource-detail/${resourceList.resourceId}`)}>
583 <Image
Seamherbb14ecb2025-06-09 22:37:20 +0800584 src={ resourceList.resourcePicture}
lfmylbhf789e7172025-06-06 18:37:21 +0800585 alt={resourceList.resourceName}
586 width="368"
587 height="200"
588 />
589 <div className="card-content">
590 <h3>{resourceList.resourceName}</h3>
591 <div className="view-count">
592 <Fire theme="outline" size="16" fill="#FF8D1A" />
593 <span>{resourceList.likes}</span>
594 </div>
595 </div>
596 </Card>
597 ))}
598 </div>
599 </div>
600
601 {/*发布帖子*/}
602 <div className="homepage-item">
603 <div className="section-header">
604 <h1>发布帖子</h1>
605 <Button
606 label="显示更多"
607 link
lfmylbhfa9fa5c82025-06-09 00:34:49 +0800608 onClick={() => setActiveIndex(1)}
lfmylbhf789e7172025-06-06 18:37:21 +0800609 />
610 </div>
611 <div className="resource-grid">
lfmylbhfa9fa5c82025-06-09 00:34:49 +0800612 {homePageThread?.records.map((homePageThread) => (
613 <Card key={homePageThread.threadId} className="resource-card" onClick={() => router.push(`/community/thread-detail/${homePageThread.threadId}`)}>
614 <Image
Seamherbb14ecb2025-06-09 22:37:20 +0800615 src={ homePageThread.threadPicture}
lfmylbhfa9fa5c82025-06-09 00:34:49 +0800616 alt={homePageThread.title}
617 width="368"
618 height="200"
619 />
620 <div className="card-content">
621 <h3>{homePageThread.title}</h3>
622 <div className="view-count">
623 <Fire theme="outline" size="16" fill="#FF8D1A" />
624 <span>{homePageThread.likes}</span>
625 </div>
626 </div>
627 </Card>
628 ))}
lfmylbhf789e7172025-06-06 18:37:21 +0800629 </div>
630 </div>
631 </TabPanel>
632
lfmylbhf789e7172025-06-06 18:37:21 +0800633 <TabPanel header="帖子">
lfmylbhfa9fa5c82025-06-09 00:34:49 +0800634 {/*我的帖子*/}
635 <div className="homepage-item">
636 <div className="section-header">
637 <h1>我的帖子</h1>
638 </div>
639 <div className="resource-grid">
640 {threadList.map((threadList) => (
641 <Card key={threadList.threadId} className="resource-card" onClick={() => router.push(`/community/thread-detail/${threadList.threadId}`)}>
642 <Image
Seamherbb14ecb2025-06-09 22:37:20 +0800643 src={ threadList.threadPicture}
lfmylbhfa9fa5c82025-06-09 00:34:49 +0800644 alt={threadList.title}
645 width="368"
646 height="200"
647 />
648 <div className="card-content">
649 <h3>{threadList.title}</h3>
650 <div className="view-count">
651 <Fire theme="outline" size="16" fill="#FF8D1A" />
652 <span>{threadList.likes}</span>
653 </div>
654 </div>
655 </Card>
656 ))}
657 </div>
658 {totalThreads > 6 && <Paginator
659 className="Paginator"
660 first={threadFirst}
661 rows={threadRows}
662 totalRecords={totalThreads}
663 rowsPerPageOptions={[6, 12]}
664 onPageChange={onThreadPageChange}
665 />}
666 </div>
lfmylbhf789e7172025-06-06 18:37:21 +0800667 </TabPanel>
668
669 <TabPanel header="收藏">
670
671 </TabPanel>
672
673 <TabPanel header="数据">
674
675 </TabPanel>
676
677 <TabPanel header="悬赏">
lfmylbhffc9518b2025-06-09 00:37:41 +0800678 <div className="section-header">
679 <h1>我的悬赏</h1>
680 </div>
681
lfmylbhfa9fa5c82025-06-09 00:34:49 +0800682 <div className="resource-list">
683 {rewardList.map((rewardItem) => (
684 <Card key={rewardItem.rewardId} className="resources-list-card"
LaoeGaoci68253852025-06-09 22:42:18 +0800685 onClick={() => router.push(`/reward/reward-detail/${rewardItem.rewardId}`)}>
lfmylbhfa9fa5c82025-06-09 00:34:49 +0800686 <Image alt="avatar"
Seamherbb14ecb2025-06-09 22:37:20 +0800687 src={ "rewards/" + rewardItem.rewardPicture}
lfmylbhfa9fa5c82025-06-09 00:34:49 +0800688 className="resource-avatar" width="250" height="140"/>
689 <div className="resource-header">
690 <div className="resource-content">
691 <h3>{rewardItem.rewardName}</h3>
692 </div>
lfmylbhf789e7172025-06-06 18:37:21 +0800693
lfmylbhfa9fa5c82025-06-09 00:34:49 +0800694 <div className="resource-operation">
695 <Button
696 label="编辑"
697 onClick={(e) => {
698 e.stopPropagation(); // 关键修复:阻止事件冒泡,避免触发Card的点击事件
699 setEditVisible(true);
700
701 setEditRewardFormData({
702 rewardId: rewardItem.rewardId,
703 rewardName: rewardItem.rewardName,
704 price: rewardItem.price.toString(),
705 rewardDescription: rewardItem.rewardDescription,
706 });
707 console.log("用户编辑悬赏");
708 }}
709 />
710 <Button
711 label="删除"
712 onClick={(e) => {
713 e.stopPropagation(); // 关键修复:阻止事件冒泡,避免触发Card的点击事件
714 setDeleteResourceId(rewardItem.rewardId);
715 setDeleteVisible(true);
716 }}
LaoeGaoci68253852025-06-09 22:42:18 +0800717 style={{ backgroundColor: "rgba(255, 87, 51, 1)" }}
lfmylbhfa9fa5c82025-06-09 00:34:49 +0800718 />
719 </div>
720 </div>
721 </Card>
722 ))}
723
724 {totalRewards > 5 && <Paginator
725 className="Paginator"
726 first={rewardFirst}
727 rows={rewardRows}
728 totalRecords={totalRewards}
729 rowsPerPageOptions={[5, 10]}
730 onPageChange={onRewardPageChange}
731 />}
732 </div>
lfmylbhf789e7172025-06-06 18:37:21 +0800733 </TabPanel>
734 </TabView>
735
736 {/*浮动按钮*/}
737 <div className="card">
738 <SpeedDial
739 model={actions}
740 direction="up"
741 style={{ position: 'fixed', bottom: '2rem', right: '2rem' }}
742 showIcon="pi pi-plus"
743 hideIcon="pi pi-times"
744 buttonClassName="custom-speeddial-button"
745 />
746 </div>
747
lfmylbhf1783a092025-06-07 20:05:22 +0800748 {/*发布资源弹窗*/}
749 <Dialog
750 header="发布资源"
751 visible={visible}
752 onHide={() => setVisible(false)}
753 className="publish-dialog"
754 modal
755 footer={
756 <div className="dialog-footer">
757 <Button label="发布" icon="pi pi-check" onClick={handleSubmit} autoFocus />
758 <Button label="取消" icon="pi pi-times" onClick={() => setVisible(false)} className="p-button-text" />
759 </div>
760 }
761 >
762 <div className="publish-form">
763 <div className="form-field">
764 <div className="form-field-header">
765 <span className="form-field-sign">*</span>
766 <label htmlFor="name">资源名称</label>
767 </div>
768 <InputText
769 id="name"
770 value={resourceFormData.resource.resourceName}
771 onChange={(e) => setResourceFormData(prev => ({
772 ...prev, // 复制顶层所有属性
773 resource: {
774 ...prev.resource, // 复制resource对象的所有属性
775 resourceName: e.target.value // 只更新resourceName
776 }
777 }))}
778 placeholder="请输入资源名称"
779 className="w-full"
lfmylbhf1783a092025-06-07 20:05:22 +0800780 />
781 </div>
782 <div className="form-field">
783 <div className="form-field-header">
784 <span className="form-field-sign">*</span>
785 <label htmlFor="summary">资源简介</label>
786 </div>
787 <InputText
788 id="summary"
789 value={resourceFormData.resource.resourceSummary}
790 onChange={(e) => setResourceFormData(prev => ({
791 ...prev, // 复制顶层所有属性
792 resource: {
793 ...prev.resource, // 复制resource对象的所有属性
794 resourceSummary: e.target.value
795 }
796 }))}
797 placeholder="请输入资源简介(一句话)"
798 className="w-full"
lfmylbhf1783a092025-06-07 20:05:22 +0800799 />
800 </div>
801 <div className="form-field">
802 <div className="form-field-header">
803 <label htmlFor="detail">资源介绍</label>
804 </div>
805 <InputTextarea
806 id="detail"
807 value={resourceFormData.resource.resourceDetail}
808 onChange={(e) => setResourceFormData(prev => ({
809 ...prev, // 复制顶层所有属性
810 resource: {
811 ...prev.resource, // 复制resource对象的所有属性
812 resourceDetail: e.target.value
813 }
814 }))}
815 rows={5}
816 placeholder="请输入资源介绍"
817 className="w-full"
lfmylbhf1783a092025-06-07 20:05:22 +0800818 />
819 </div>
820 <div className="form-field">
821 <div className="form-field-header">
822 <span className="form-field-sign">*</span>
823 <label htmlFor="price">价格</label>
824 </div>
825 <InputText
826 id="price"
827 value={resourceFormData.resource.price}
828 onChange={(e) => setResourceFormData(prev => ({
829 ...prev, // 复制顶层所有属性
830 resource: {
831 ...prev.resource, // 复制resource对象的所有属性
832 price: e.target.value
833 }
834 }))}
835 placeholder="请输入资源价格"
836 className="w-full"
837 />
838 </div>
839 <div className="form-field">
840 <div className="form-field-header">
841 <span className="form-field-sign">*</span>
842 <label htmlFor="classify">资源分类(请选择一项)</label>
843 </div>
844 <div className="form-field-classify">
845 <div className="flex align-items-center">
846 <RadioButton
847 inputId="ingredient1"
848 name="pizza"
849 value="resourcePack"
850 onChange={(e: RadioButtonChangeEvent) => {
851 setResourceFormData(prev => ({
852 ...prev, // 复制顶层所有属性
853 resource: {
854 ...prev.resource, // 复制resource对象的所有属性
855 classify: e.target.value
856 }
857 }));
858 setIngredient(e.value);
859 console.log(ingredient);
860 // console.log(resourceFormData.resource.classify);
861 }}
862 checked={ingredient === 'resourcePack'}
863 />
864 <label htmlFor="ingredient1" className="ml-2">材质包</label>
865 </div>
866 <div className="flex align-items-center">
867 <RadioButton
868 inputId="ingredient2"
869 name="pizza"
870 value="modPack"
871 onChange={(e: RadioButtonChangeEvent) => {
872 setResourceFormData(prev => ({
873 ...prev, // 复制顶层所有属性
874 resource: {
875 ...prev.resource, // 复制resource对象的所有属性
876 classify: e.target.value
877 }
878 }));
879 setIngredient(e.value);
880 }}
lfmylbhf1783a092025-06-07 20:05:22 +0800881 checked={ingredient === 'modPack'}
882 />
883 <label htmlFor="ingredient2" className="ml-2">整合包</label>
884 </div>
885 <div className="flex align-items-center">
886 <RadioButton
887 inputId="ingredient3"
888 name="pizza"
889 value="mod"
890 onChange={(e: RadioButtonChangeEvent) => {
891 setResourceFormData(prev => ({
892 ...prev, // 复制顶层所有属性
893 resource: {
894 ...prev.resource, // 复制resource对象的所有属性
895 classify: e.target.value
896 }
897 }));
898 setIngredient(e.value);
899 }}
lfmylbhf1783a092025-06-07 20:05:22 +0800900 checked={ingredient === 'mod'}
901 />
902 <label htmlFor="ingredient3" className="ml-2">模组</label>
903 </div>
904 <div className="flex align-items-center">
905 <RadioButton
906 inputId="ingredient4"
907 name="pizza"
908 value="map"
909 onChange={(e: RadioButtonChangeEvent) => {
910 setResourceFormData(prev => ({
911 ...prev, // 复制顶层所有属性
912 resource: {
913 ...prev.resource, // 复制resource对象的所有属性
914 classify: e.target.value
915 }
916 }));
917 setIngredient(e.value);
918 }}
lfmylbhf1783a092025-06-07 20:05:22 +0800919 checked={ingredient === 'map'}
920 />
921 <label htmlFor="ingredient4" className="ml-2">地图</label>
922 </div>
923 </div>
924 </div>
925 <div className="form-field">
926 <div className="form-field-header">
927 <span className="form-field-sign">*</span>
928 <label htmlFor="gameplayList">资源标签</label>
929 </div>
930 <MultiSelect
931 value={selectedGameplay}
932 onChange={(e: MultiSelectChangeEvent) => {
933 const selectedOptions = e.value as GameplayOption[];
934 // 提取选中项的 name 属性组成字符串数组
935 const selectedNames = selectedOptions.map(item => item.name);
936
937 setResourceFormData(prev => ({
938 ...prev,
939 gameplayList: selectedNames
940 }));
941 setSelectedGameplay(selectedOptions);
942 }}
943 options={gameplayOptions}
944 display="chip"
945 optionLabel="name"
946 placeholder="请选择资源标签"
lfmylbhf1783a092025-06-07 20:05:22 +0800947 className="w-full md:w-20rem"
948 />
949 </div>
950 <div className="form-field">
951 <div className="form-field-header">
952 <span className="form-field-sign">*</span>
953 <label>封面图片</label>
954 </div>
955 <FileUpload
lfmylbhfa9fa5c82025-06-09 00:34:49 +0800956 mode="advanced"
lfmylbhf1783a092025-06-07 20:05:22 +0800957 name="resource-image"
lfmylbhfa9fa5c82025-06-09 00:34:49 +0800958 customUpload
959 uploadHandler={async (e) => {
960 const formData = new FormData();
961 formData.append("file", e.files[0]);
962
963 try {
964 const res = await axios.post(`${process.env.PUBLIC_URL}/file`, formData);
965
966 const fileUrl = res.data.url;
967 console.log(fileUrl);
968 setResourcePictureUrl(fileUrl);
969 toast.current?.show({ severity: 'success', summary: '上传成功' });
970 } catch (error) {
971 console.log(error);
972 toast.current?.show({ severity: 'error', summary: '上传失败' });
973 }
974 }}
975 auto
lfmylbhfa9fa5c82025-06-09 00:34:49 +0800976 chooseLabel="上传资源封面"
lfmylbhf1783a092025-06-07 20:05:22 +0800977 />
978 </div>
LaoeGaoci68253852025-06-09 22:42:18 +0800979 <div className="form-field">
980 <div className="form-field-header">
981 <span className="form-field-sign">*</span>
982 <label>上传资源文件</label>
983 </div>
984 <FileUpload
985 mode="advanced"
986 name="resource-file"
987 customUpload
988 uploadHandler={async (e) => {
989 const formData = new FormData();
990 formData.append("file", e.files[0]);
991
992 try {
993 const res = await axios.post(`${process.env.PUBLIC_URL}/file`, formData);
994 const fileUrl = res.data.url;
995 console.log("上传的 torrent 文件 URL:", fileUrl);
996 setTorrentUrl(fileUrl);
997 toast.current?.show({ severity: 'success', summary: '上传成功', detail: '资源文件已上传' });
998 } catch (error) {
999 console.error("上传资源文件失败:", error);
1000 toast.current?.show({ severity: 'error', summary: '上传失败', detail: '资源文件上传失败' });
1001 }
1002 }}
1003 auto
1004 chooseLabel="上传资源文件(.torrent)"
1005 />
1006 </div>
1007
lfmylbhf1783a092025-06-07 20:05:22 +08001008 </div>
1009 </Dialog>
1010
lfmylbhfa9fa5c82025-06-09 00:34:49 +08001011 {/*删除悬赏弹窗*/}
1012 <Dialog
1013 header="删除悬赏"
1014 visible={deleteVisible}
1015 onHide={() => setDeleteVisible(false)}
1016 className="resource-delete-dialog"
1017 modal
1018 footer={
1019 <div className="dialog-footer">
1020 <Button label="确认" icon="pi pi-check" onClick={handleDeleteSubmit} autoFocus />
1021 <Button label="取消" icon="pi pi-times" onClick={() => setDeleteVisible(false)} className="p-button-text" />
1022 </div>
1023 }
1024 >
1025 <div className="dialog-form">
LaoeGaoci68253852025-06-09 22:42:18 +08001026 <span style={{ marginBottom: "10px" }}>
lfmylbhfa9fa5c82025-06-09 00:34:49 +08001027 确认是否删除该悬赏?
1028 </span>
lfmylbhfa9fa5c82025-06-09 00:34:49 +08001029 </div>
1030 </Dialog>
1031
1032 {/*编辑资源弹窗*/}
1033 <Dialog
1034 header="编辑资源"
1035 visible={editVisible}
1036 onHide={() => setEditVisible(false)}
1037 className="resource-edit-dialog"
1038 modal
1039 footer={
1040 <div className="dialog-footer">
1041 <Button label="确认" icon="pi pi-check" onClick={handleEditSubmit} autoFocus />
1042 <Button label="取消" icon="pi pi-times" onClick={() => setEditVisible(false)} className="p-button-text" />
1043 </div>
1044 }
1045 >
1046 <div className="dialog-form">
1047 <div className="form-field">
1048 <div className="form-field-header">
1049 <label htmlFor="name">更改标题</label>
1050 </div>
1051 <InputText
1052 id="name"
1053 value={editRewardFormData.rewardName}
1054 onChange={(e) => setEditRewardFormData(prev => ({
1055 ...prev,
LaoeGaoci68253852025-06-09 22:42:18 +08001056 rewardName: e.target.value
lfmylbhfa9fa5c82025-06-09 00:34:49 +08001057 }))}
1058 className="w-full"
1059 />
1060 </div>
1061 <div className="form-field">
1062 <div className="form-field-header">
lfmylbhffc9518b2025-06-09 00:37:41 +08001063 <label htmlFor="price">更改定价</label>
lfmylbhfa9fa5c82025-06-09 00:34:49 +08001064 </div>
1065 <InputText
lfmylbhffc9518b2025-06-09 00:37:41 +08001066 id="price"
lfmylbhfa9fa5c82025-06-09 00:34:49 +08001067 value={editRewardFormData.price}
1068 onChange={(e) => setEditRewardFormData(prev => ({
1069 ...prev,
LaoeGaoci68253852025-06-09 22:42:18 +08001070 price: e.target.value
lfmylbhfa9fa5c82025-06-09 00:34:49 +08001071 }))}
1072 className="w-full"
1073 />
1074 </div>
1075 <div className="form-field">
1076 <div className="form-field-header">
lfmylbhffc9518b2025-06-09 00:37:41 +08001077 <label htmlFor="description">更改需求</label>
lfmylbhfa9fa5c82025-06-09 00:34:49 +08001078 </div>
1079 <InputTextarea
lfmylbhffc9518b2025-06-09 00:37:41 +08001080 id="description"
lfmylbhfa9fa5c82025-06-09 00:34:49 +08001081 value={editRewardFormData.rewardDescription}
1082 onChange={(e) => setEditRewardFormData(prev => ({
1083 ...prev,
LaoeGaoci68253852025-06-09 22:42:18 +08001084 rewardDescription: e.target.value
lfmylbhfa9fa5c82025-06-09 00:34:49 +08001085 }))}
1086 rows={5}
1087 className="w-full"
1088 />
1089 </div>
1090 <div className="form-field">
1091 <div className="form-field-header">
1092 <span className="form-field-sign">*</span>
1093 <label>封面图片</label>
1094 </div>
1095 <FileUpload
1096 mode="advanced"
lfmylbhffc9518b2025-06-09 00:37:41 +08001097 name="reward-image"
lfmylbhfa9fa5c82025-06-09 00:34:49 +08001098 customUpload
1099 uploadHandler={async (e) => {
1100 const formData = new FormData();
1101 formData.append("file", e.files[0]);
1102
1103 try {
1104 const res = await axios.post(`${process.env.PUBLIC_URL}/file`, formData);
1105
1106 const fileUrl = res.data.url;
1107 console.log(fileUrl);
1108 setRewardPictureUrl(fileUrl);
1109 toast.current?.show({ severity: 'success', summary: '上传成功' });
1110 } catch (error) {
1111 console.log(error);
1112 toast.current?.show({ severity: 'error', summary: '上传失败' });
1113 }
1114 }}
1115 auto
1116 accept="image/*"
1117 chooseLabel="选择悬赏封面"
1118 />
1119 </div>
1120 </div>
1121 </Dialog>
lfmylbhf789e7172025-06-06 18:37:21 +08001122 </div>
1123 );
1124};
1125