blob: 986f6ec69df326eb9439d0edaa3a173ac355db02 [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// 类型转换
27import {toNumber} from "lodash";
28// 分页
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;
89 userId: number;
90 threadPicture: string;
91 title: string;
92 likes: number;
93 createAt: string;
94}
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 };
179 // 悬赏分页
180 const [rewardFirst, setRewardFirst] = useState(0);
181 const [rewardRows, setRewardRows] = useState(5);
182 const [totalRewards, setTotalRewards] = useState<number>(0);
183 const onRewardPageChange = (event: PaginatorPageChangeEvent) => {
184 setRewardFirst(event.first);
185 setRewardRows(event.rows);
186 };
lfmylbhf789e7172025-06-06 18:37:21 +0800187
188 useEffect(() => {
189 fetchUserInfo();
190 fetchUserData();
191 fetchResourceList();
192 }, []);
193
194 // 获取用户信息
195 const fetchUserInfo = async () => {
196 try {
197 const response = await axios.get<UserInfo>(process.env.PUBLIC_URL + `/user/info`, {
LaoeGaocid0773912025-06-09 00:38:40 +0800198 params: { userId }
lfmylbhf789e7172025-06-06 18:37:21 +0800199 });
200 console.log('获取用户信息:', response.data);
201 setUserInfo(response.data);
202 } catch (err) {
203 console.error('获取用户信息失败', err);
204 toast.current?.show({ severity: 'error', summary: 'error', detail: '获取用户信息失败' });
205 }
206 };
207
208 // 获取用户数据
209 const fetchUserData = async () => {
210 try {
211 const response = await axios.get<UserData>(process.env.PUBLIC_URL + `/user/data`, {
LaoeGaocid0773912025-06-09 00:38:40 +0800212 params: { userId }
lfmylbhf789e7172025-06-06 18:37:21 +0800213 });
214 console.log('获取用户数据:', response.data);
215 setUserData(response.data);
216 } catch (err) {
217 console.error('获取用户数据失败', err);
218 toast.current?.show({ severity: 'error', summary: 'error', detail: '获取用户数据失败' });
219 }
220 };
221
222 // 格式化数字显示 (3000 -> 3k)
223 const formatCount = (count?: number): string => {
224 if (count == null) return "0"; // 同时处理 undefined/null
225
226 const absCount = Math.abs(count); // 处理负数
227
228 const format = (num: number, suffix: string) => {
229 const fixed = num.toFixed(1);
230 return fixed.endsWith('.0')
231 ? `${Math.floor(num)}${suffix}`
232 : `${fixed}${suffix}`;
233 };
234
235 if (absCount >= 1e6) return format(count / 1e6, "m");
236 if (absCount >= 1e3) return format(count / 1e3, "k");
237 return count.toString();
238 };
239
240 // 获取发布资源
241 const fetchResourceList = async () => {
242 try {
LaoeGaocid0773912025-06-09 00:38:40 +0800243 const response = await axios.get<ResourceList>(process.env.PUBLIC_URL + `/user/upload`, {
244 params: { userId, pageNumber: 1, rows: 3 }
lfmylbhf789e7172025-06-06 18:37:21 +0800245 });
246 console.log('获取发布资源列表:', response.data.records);
247 setResourceList(response.data.records);
lfmylbhf789e7172025-06-06 18:37:21 +0800248 } catch (err) {
249 console.error('获取发布资源失败', err);
250 toast.current?.show({ severity: 'error', summary: 'error', detail: '获取发布资源失败' });
251 }
252 };
253
lfmylbhfa9fa5c82025-06-09 00:34:49 +0800254 useEffect(() => {
255 fetchHomePageThread();
256 }, []);
257
258 // 获取主页部分的发布帖子
259 const fetchHomePageThread = async () => {
260 try {
261 const response = await axios.get<ThreadList>(process.env.PUBLIC_URL + `/user/thread`, {
LaoeGaoci40197d52025-06-09 17:15:52 +0800262 params: { userId: userId, pageNumber: 1, rows: 3 }
lfmylbhfa9fa5c82025-06-09 00:34:49 +0800263 })
264 console.log('获取主页发布帖子:', response.data);
265 setHomePageThread(response.data);
266
267 } catch (err) {
268 console.error('获取主页发布帖子失败', err);
269 toast.current?.show({ severity: 'error', summary: 'error', detail: '获取主页发布帖子失败' });
270 }
271 }
272
273 useEffect(() => {
274 fetchThreadList();
275 }, [threadFirst, threadRows]);
276
277 // 获取用户发布的所有帖子
278 const fetchThreadList = async () => {
279 try {
280 const pageNumber = threadFirst / threadRows + 1;
281 const response = await axios.get<ThreadList>(process.env.PUBLIC_URL + `/user/thread`, {
LaoeGaoci40197d52025-06-09 17:15:52 +0800282 params: { userId: userId, pageNumber: pageNumber, rows: threadRows }
lfmylbhfa9fa5c82025-06-09 00:34:49 +0800283 })
284 console.log('获取我的帖子:', response.data);
285 setThreadList(response.data.records);
286 setTotalThreads(response.data.total)
287 }catch (err) {
288 console.error('获取我的帖子失败', err);
289 toast.current?.show({ severity: 'error', summary: 'error', detail: '获取我的帖子失败' });
290 }
291 }
292
293 useEffect(() => {
294 fetchRewardList();
295 }, [rewardFirst, rewardRows]);
296
297 // 获取用户发布的所有悬赏
298 const fetchRewardList = async () => {
299 try {
300 const pageNumber = rewardFirst / rewardRows + 1;
301 const response = await axios.get<RewardList>(process.env.PUBLIC_URL + `/user/reward`, {
LaoeGaoci40197d52025-06-09 17:15:52 +0800302 params: { userId: userId, pageNumber: pageNumber, rows: rewardRows }
lfmylbhfa9fa5c82025-06-09 00:34:49 +0800303 })
304 console.log('获取我的悬赏:', response.data);
305 setRewardList(response.data.rewardList);
306 setTotalRewards(response.data.total)
307 }catch (err) {
308 console.error('获取我的悬赏失败', err);
309 toast.current?.show({ severity: 'error', summary: 'error', detail: '获取我的悬赏失败' });
310 }
311 }
312
lfmylbhf789e7172025-06-06 18:37:21 +0800313 // 浮动按钮的子模块
314 const actions = [
315 {
316 template: () => (
LaoeGaocid0773912025-06-09 00:38:40 +0800317 <Button label="管理资源" onClick={() => router.push(`/user/manage/resources/`)} />
lfmylbhf789e7172025-06-06 18:37:21 +0800318 )
319 },
320 {
321 template: () => (
LaoeGaocid0773912025-06-09 00:38:40 +0800322 <Button label="已购资源" onClick={() => router.push(`/user/purchased-resources/`)} />
lfmylbhf789e7172025-06-06 18:37:21 +0800323 )
324 },
325 {
326 template: () => (
LaoeGaocid0773912025-06-09 00:38:40 +0800327 <Button label="发布资源" onClick={() => setVisible(true)} />
lfmylbhf789e7172025-06-06 18:37:21 +0800328 )
329 },
330 {
331 template: () => (
lfmylbhf839e5a12025-06-09 18:22:25 +0800332 <Button label="编辑悬赏" />
lfmylbhf789e7172025-06-06 18:37:21 +0800333 )
334 }
335 ];
336
lfmylbhf1783a092025-06-07 20:05:22 +0800337 // 发布资源弹窗
338 const [visible, setVisible] = useState(false);
339 const [resourceFormData, setResourceFormData] = useState({
340 resource: {
341 resourceName: '',
342 resourcePicture: '',
343 resourceSummary: '',
344 resourceDetail: '',
345 uploadTime: '',
346 lastUpdateTime: '',
347 price: '',
348 classify: '',
349 },
350 gameplayList: [''],
351 completeRewardId: null,
352 userId: 0,
353 });
354 const [ingredient, setIngredient] = useState<string>('');
355
lfmylbhfa9fa5c82025-06-09 00:34:49 +0800356 // 删除悬赏弹窗
357 const [deleteVisible, setDeleteVisible] = useState(false);
358 // 要删除悬赏的id
359 const [deleteRewardId, setDeleteResourceId] = useState<number>(0);
360 // 处理删除悬赏接口
361 const handleDeleteSubmit = async () => {
362 try {
363 // 发送DELETE请求
364 const response = await axios.delete(process.env.PUBLIC_URL + `/reward`, {
365 params: {rewardId: deleteRewardId},
366 });
LaoeGaoci40197d52025-06-09 17:15:52 +0800367 console.log("用户" + userId + "要删除" + deleteRewardId + "号悬赏");
lfmylbhf1783a092025-06-07 20:05:22 +0800368
lfmylbhfa9fa5c82025-06-09 00:34:49 +0800369 if (response.status === 204) {
370 console.log("用户成功删除悬赏");
371 toast.current?.show({severity: 'success', summary: 'Success', detail: '删除悬赏成功'});
372 setDeleteVisible(false);
373 // 重新拉取资源列表
374 fetchRewardList();
375 }
376 } catch (error) {
377 console.error('资源删除失败:', error);
378 toast.current?.show({ severity: 'error', summary: 'error', detail: '密码错误,资源删除失败' });
379 }
380 };
381
382
383 // 编辑悬赏弹窗
384 const [editVisible, setEditVisible] = useState(false);
385 // 悬赏封面路径
386 const [rewardPictureUrl, setRewardPictureUrl] = useState<string>('');
387 const [editRewardFormData, setEditRewardFormData] = useState({
388 rewardId: 0,
389 rewardName: '',
390 price: '',
391 rewardDescription: '',
392 });
393
394 // 处理编辑资源接口
395 const handleEditSubmit = async () => {
396 try {
397 const postData = {
398 rewardId: editRewardFormData.rewardId,
399 rewardName: editRewardFormData.rewardName,
400 rewardPicture: rewardPictureUrl,
401 price: toNumber(editRewardFormData.price),
402 rewardDescription: editRewardFormData.rewardDescription,
403 };
404 // 发送POST请求
405 const response = await axios.put(process.env.PUBLIC_URL + '/reward/info', postData);
406 console.log("编辑悬赏的信息:", postData);
407
408 if (response.status === 200) {
409 toast.current?.show({ severity: 'success', summary: 'Success', detail: '悬赏编辑成功' });
410 // 编辑成功
411 setEditVisible(false);
412 fetchRewardList();
413 }
414 } catch (error) {
415 console.error('悬赏编辑失败:', error);
416 toast.current?.show({ severity: 'error', summary: 'error', detail: '悬赏编辑失败' });
417 }
lfmylbhf1783a092025-06-07 20:05:22 +0800418 };
419
420 // 上传资源接口
421 const handleSubmit = async () => {
422 try {
423 // 规定用户必须输入的内容
424 if (resourceFormData.resource.resourceName == '') {
425 toast.current?.show({ severity: 'info', summary: 'error', detail: '缺少资源名称' });
426 return;
427 }
428 if (resourceFormData.resource.resourceSummary == '') {
429 toast.current?.show({ severity: 'info', summary: 'error', detail: '缺少资源简介' });
430 return;
431 }
432 if (resourceFormData.resource.price == '') {
433 toast.current?.show({ severity: 'info', summary: 'error', detail: '缺少资源价格' });
434 return;
435 }
436 if (resourceFormData.resource.classify == '') {
437 toast.current?.show({ severity: 'info', summary: 'error', detail: '缺少资源分类' });
438 return;
439 }
440 if (
441 resourceFormData.gameplayList.length === 1 &&
442 resourceFormData.gameplayList[0] === ''
443 ) {
444 toast.current?.show({ severity: 'info', summary: 'error', detail: '缺少资源标签' });
445 return;
446 }
lfmylbhfa9fa5c82025-06-09 00:34:49 +0800447 if (resourcePictureUrl === '') {
lfmylbhf1783a092025-06-07 20:05:22 +0800448 toast.current?.show({ severity: 'info', summary: 'error', detail: '缺少资源封面' });
449 return;
450 }
451
452 const currentDate = new Date().toISOString().split('T')[0];
453 const postData = {
454 resource: {
455 resourceName: resourceFormData.resource.resourceName,
lfmylbhfa9fa5c82025-06-09 00:34:49 +0800456 resourcePicture: resourcePictureUrl,
lfmylbhf1783a092025-06-07 20:05:22 +0800457 resourceSummary: resourceFormData.resource.resourceSummary,
458 resourceDetail: resourceFormData.resource.resourceDetail,
459 uploadTime: currentDate,
460 lastUpdateTime: currentDate,
461 price: toNumber(resourceFormData.resource.price),
462 classify: resourceFormData.resource.classify,
463 },
464 gameplayList: resourceFormData.gameplayList,
465 completeRewardId: null,
LaoeGaocid0773912025-06-09 00:38:40 +0800466 userId, // 记得用户登录状态获取
lfmylbhf1783a092025-06-07 20:05:22 +0800467 };
468 // 发送POST请求
469 const response = await axios.post(process.env.PUBLIC_URL + '/resource', postData);
470 console.log("上传资源的信息:", postData);
471
472 if (response.status === 200) {
473 toast.current?.show({ severity: 'success', summary: 'Success', detail: '资源上传成功' });
474 // 上传成功
475 setVisible(false);
476 // 重置表单
477 setResourceFormData({
478 resource: {
479 resourceName: '',
480 resourcePicture: '',
481 resourceSummary: '',
482 resourceDetail: '',
483 uploadTime: '',
484 lastUpdateTime: '',
485 price: '',
486 classify: '',
487 },
488 gameplayList: [],
489 completeRewardId: null,
490 userId: 0,
491 });
492 // 重置资源分类
493 setIngredient("");
494 // 重置资源标签
495 setSelectedGameplay([]);
lfmylbhfa9fa5c82025-06-09 00:34:49 +0800496 // 重置资源封面
497 setResourcePictureUrl('');
lfmylbhf1783a092025-06-07 20:05:22 +0800498 }
499 } catch (error) {
500 console.error('资源上传失败:', error);
501 toast.current?.show({ severity: 'error', summary: 'error', detail: '资源上传失败' });
502 }
503 };
504
lfmylbhf789e7172025-06-06 18:37:21 +0800505 return (
506 <div className="user-container">
lfmylbhf1783a092025-06-07 20:05:22 +0800507 <Toast ref={toast}></Toast>
lfmylbhf789e7172025-06-06 18:37:21 +0800508 {/*个人信息*/}
509 <div className="user-profile-card">
510 <Avatar
Seamherbb14ecb2025-06-09 22:37:20 +0800511 image={userInfo?.avatar}
lfmylbhf789e7172025-06-06 18:37:21 +0800512 className="user-avatar"
513 shape="circle"
514 />
515 <div className="user-info">
516 <div className="user-detail-info">
517 <div className="name-container">
518 <h2 className="name">{userInfo?.username}</h2>
519 <span className="signature">{userInfo?.signature}</span>
520 </div>
521
522 <div className="stats-container">
523 <div className="stats">
524 <span className="stats-label">粉丝:</span>
525 <span className="stats-value">{userInfo?.followerCount}</span>
526 </div>
527 <div className="stats">
528 <span className="stats-label">累计上传量:</span>
529 <span className="stats-value">{formatCount(userData?.uploadAmount)}</span>
530 </div>
531 <div className="stats">
532 <span className="stats-label">关注:</span>
533 <span className="stats-value">{userInfo?.subscriberCount}</span>
534 </div>
535 <div className="stats">
536 <span className="stats-label">累计被下载量:</span>
537 <span className="stats-value">{formatCount(userData?.beDownloadedAmount)}</span>
538 </div>
539 </div>
540 </div>
541
LaoeGaocid0773912025-06-09 00:38:40 +0800542 <Button label="关注" className="action-button" />
lfmylbhf789e7172025-06-06 18:37:21 +0800543 </div>
544 </div>
545
546 {/*个人内容*/}
lfmylbhfa9fa5c82025-06-09 00:34:49 +0800547 <TabView activeIndex={activeIndex} onTabChange={(e) => setActiveIndex(e.index)}>
548 <TabPanel header="主页" >
lfmylbhf789e7172025-06-06 18:37:21 +0800549 {/*发布资源*/}
550 <div className="homepage-item">
551 <div className="section-header">
552 <h1>发布资源</h1>
553 <Button
554 label="显示更多"
555 link
556 onClick={() => router.push('/user/manage/resources/')}
557 />
558 </div>
559 <div className="resource-grid">
560 {resourceList.map((resourceList) => (
561 <Card key={resourceList.resourceId} className="resource-card" onClick={() => router.push(`/resource/resource-detail/${resourceList.resourceId}`)}>
562 <Image
Seamherbb14ecb2025-06-09 22:37:20 +0800563 src={ resourceList.resourcePicture}
lfmylbhf789e7172025-06-06 18:37:21 +0800564 alt={resourceList.resourceName}
565 width="368"
566 height="200"
567 />
568 <div className="card-content">
569 <h3>{resourceList.resourceName}</h3>
570 <div className="view-count">
571 <Fire theme="outline" size="16" fill="#FF8D1A" />
572 <span>{resourceList.likes}</span>
573 </div>
574 </div>
575 </Card>
576 ))}
577 </div>
578 </div>
579
580 {/*发布帖子*/}
581 <div className="homepage-item">
582 <div className="section-header">
583 <h1>发布帖子</h1>
584 <Button
585 label="显示更多"
586 link
lfmylbhfa9fa5c82025-06-09 00:34:49 +0800587 onClick={() => setActiveIndex(1)}
lfmylbhf789e7172025-06-06 18:37:21 +0800588 />
589 </div>
590 <div className="resource-grid">
lfmylbhfa9fa5c82025-06-09 00:34:49 +0800591 {homePageThread?.records.map((homePageThread) => (
592 <Card key={homePageThread.threadId} className="resource-card" onClick={() => router.push(`/community/thread-detail/${homePageThread.threadId}`)}>
593 <Image
Seamherbb14ecb2025-06-09 22:37:20 +0800594 src={ homePageThread.threadPicture}
lfmylbhfa9fa5c82025-06-09 00:34:49 +0800595 alt={homePageThread.title}
596 width="368"
597 height="200"
598 />
599 <div className="card-content">
600 <h3>{homePageThread.title}</h3>
601 <div className="view-count">
602 <Fire theme="outline" size="16" fill="#FF8D1A" />
603 <span>{homePageThread.likes}</span>
604 </div>
605 </div>
606 </Card>
607 ))}
lfmylbhf789e7172025-06-06 18:37:21 +0800608 </div>
609 </div>
610 </TabPanel>
611
lfmylbhf789e7172025-06-06 18:37:21 +0800612 <TabPanel header="帖子">
lfmylbhfa9fa5c82025-06-09 00:34:49 +0800613 {/*我的帖子*/}
614 <div className="homepage-item">
615 <div className="section-header">
616 <h1>我的帖子</h1>
617 </div>
618 <div className="resource-grid">
619 {threadList.map((threadList) => (
620 <Card key={threadList.threadId} className="resource-card" onClick={() => router.push(`/community/thread-detail/${threadList.threadId}`)}>
621 <Image
Seamherbb14ecb2025-06-09 22:37:20 +0800622 src={ threadList.threadPicture}
lfmylbhfa9fa5c82025-06-09 00:34:49 +0800623 alt={threadList.title}
624 width="368"
625 height="200"
626 />
627 <div className="card-content">
628 <h3>{threadList.title}</h3>
629 <div className="view-count">
630 <Fire theme="outline" size="16" fill="#FF8D1A" />
631 <span>{threadList.likes}</span>
632 </div>
633 </div>
634 </Card>
635 ))}
636 </div>
637 {totalThreads > 6 && <Paginator
638 className="Paginator"
639 first={threadFirst}
640 rows={threadRows}
641 totalRecords={totalThreads}
642 rowsPerPageOptions={[6, 12]}
643 onPageChange={onThreadPageChange}
644 />}
645 </div>
lfmylbhf789e7172025-06-06 18:37:21 +0800646 </TabPanel>
647
648 <TabPanel header="收藏">
649
650 </TabPanel>
651
652 <TabPanel header="数据">
653
654 </TabPanel>
655
656 <TabPanel header="悬赏">
lfmylbhffc9518b2025-06-09 00:37:41 +0800657 <div className="section-header">
658 <h1>我的悬赏</h1>
659 </div>
660
lfmylbhfa9fa5c82025-06-09 00:34:49 +0800661 <div className="resource-list">
662 {rewardList.map((rewardItem) => (
663 <Card key={rewardItem.rewardId} className="resources-list-card"
664 onClick={() => router.push(`/reward/reward-detail/${rewardItem.rewardId}`)}>
665 <Image alt="avatar"
Seamherbb14ecb2025-06-09 22:37:20 +0800666 src={ "rewards/" + rewardItem.rewardPicture}
lfmylbhfa9fa5c82025-06-09 00:34:49 +0800667 className="resource-avatar" width="250" height="140"/>
668 <div className="resource-header">
669 <div className="resource-content">
670 <h3>{rewardItem.rewardName}</h3>
671 </div>
lfmylbhf789e7172025-06-06 18:37:21 +0800672
lfmylbhfa9fa5c82025-06-09 00:34:49 +0800673 <div className="resource-operation">
674 <Button
675 label="编辑"
676 onClick={(e) => {
677 e.stopPropagation(); // 关键修复:阻止事件冒泡,避免触发Card的点击事件
678 setEditVisible(true);
679
680 setEditRewardFormData({
681 rewardId: rewardItem.rewardId,
682 rewardName: rewardItem.rewardName,
683 price: rewardItem.price.toString(),
684 rewardDescription: rewardItem.rewardDescription,
685 });
686 console.log("用户编辑悬赏");
687 }}
688 />
689 <Button
690 label="删除"
691 onClick={(e) => {
692 e.stopPropagation(); // 关键修复:阻止事件冒泡,避免触发Card的点击事件
693 setDeleteResourceId(rewardItem.rewardId);
694 setDeleteVisible(true);
695 }}
696 style={{backgroundColor: "rgba(255, 87, 51, 1)"}}
697 />
698 </div>
699 </div>
700 </Card>
701 ))}
702
703 {totalRewards > 5 && <Paginator
704 className="Paginator"
705 first={rewardFirst}
706 rows={rewardRows}
707 totalRecords={totalRewards}
708 rowsPerPageOptions={[5, 10]}
709 onPageChange={onRewardPageChange}
710 />}
711 </div>
lfmylbhf789e7172025-06-06 18:37:21 +0800712 </TabPanel>
713 </TabView>
714
715 {/*浮动按钮*/}
716 <div className="card">
717 <SpeedDial
718 model={actions}
719 direction="up"
720 style={{ position: 'fixed', bottom: '2rem', right: '2rem' }}
721 showIcon="pi pi-plus"
722 hideIcon="pi pi-times"
723 buttonClassName="custom-speeddial-button"
724 />
725 </div>
726
lfmylbhf1783a092025-06-07 20:05:22 +0800727 {/*发布资源弹窗*/}
728 <Dialog
729 header="发布资源"
730 visible={visible}
731 onHide={() => setVisible(false)}
732 className="publish-dialog"
733 modal
734 footer={
735 <div className="dialog-footer">
736 <Button label="发布" icon="pi pi-check" onClick={handleSubmit} autoFocus />
737 <Button label="取消" icon="pi pi-times" onClick={() => setVisible(false)} className="p-button-text" />
738 </div>
739 }
740 >
741 <div className="publish-form">
742 <div className="form-field">
743 <div className="form-field-header">
744 <span className="form-field-sign">*</span>
745 <label htmlFor="name">资源名称</label>
746 </div>
747 <InputText
748 id="name"
749 value={resourceFormData.resource.resourceName}
750 onChange={(e) => setResourceFormData(prev => ({
751 ...prev, // 复制顶层所有属性
752 resource: {
753 ...prev.resource, // 复制resource对象的所有属性
754 resourceName: e.target.value // 只更新resourceName
755 }
756 }))}
757 placeholder="请输入资源名称"
758 className="w-full"
lfmylbhf1783a092025-06-07 20:05:22 +0800759 />
760 </div>
761 <div className="form-field">
762 <div className="form-field-header">
763 <span className="form-field-sign">*</span>
764 <label htmlFor="summary">资源简介</label>
765 </div>
766 <InputText
767 id="summary"
768 value={resourceFormData.resource.resourceSummary}
769 onChange={(e) => setResourceFormData(prev => ({
770 ...prev, // 复制顶层所有属性
771 resource: {
772 ...prev.resource, // 复制resource对象的所有属性
773 resourceSummary: e.target.value
774 }
775 }))}
776 placeholder="请输入资源简介(一句话)"
777 className="w-full"
lfmylbhf1783a092025-06-07 20:05:22 +0800778 />
779 </div>
780 <div className="form-field">
781 <div className="form-field-header">
782 <label htmlFor="detail">资源介绍</label>
783 </div>
784 <InputTextarea
785 id="detail"
786 value={resourceFormData.resource.resourceDetail}
787 onChange={(e) => setResourceFormData(prev => ({
788 ...prev, // 复制顶层所有属性
789 resource: {
790 ...prev.resource, // 复制resource对象的所有属性
791 resourceDetail: e.target.value
792 }
793 }))}
794 rows={5}
795 placeholder="请输入资源介绍"
796 className="w-full"
lfmylbhf1783a092025-06-07 20:05:22 +0800797 />
798 </div>
799 <div className="form-field">
800 <div className="form-field-header">
801 <span className="form-field-sign">*</span>
802 <label htmlFor="price">价格</label>
803 </div>
804 <InputText
805 id="price"
806 value={resourceFormData.resource.price}
807 onChange={(e) => setResourceFormData(prev => ({
808 ...prev, // 复制顶层所有属性
809 resource: {
810 ...prev.resource, // 复制resource对象的所有属性
811 price: e.target.value
812 }
813 }))}
814 placeholder="请输入资源价格"
815 className="w-full"
816 />
817 </div>
818 <div className="form-field">
819 <div className="form-field-header">
820 <span className="form-field-sign">*</span>
821 <label htmlFor="classify">资源分类(请选择一项)</label>
822 </div>
823 <div className="form-field-classify">
824 <div className="flex align-items-center">
825 <RadioButton
826 inputId="ingredient1"
827 name="pizza"
828 value="resourcePack"
829 onChange={(e: RadioButtonChangeEvent) => {
830 setResourceFormData(prev => ({
831 ...prev, // 复制顶层所有属性
832 resource: {
833 ...prev.resource, // 复制resource对象的所有属性
834 classify: e.target.value
835 }
836 }));
837 setIngredient(e.value);
838 console.log(ingredient);
839 // console.log(resourceFormData.resource.classify);
840 }}
841 checked={ingredient === 'resourcePack'}
842 />
843 <label htmlFor="ingredient1" className="ml-2">材质包</label>
844 </div>
845 <div className="flex align-items-center">
846 <RadioButton
847 inputId="ingredient2"
848 name="pizza"
849 value="modPack"
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 }}
lfmylbhf1783a092025-06-07 20:05:22 +0800860 checked={ingredient === 'modPack'}
861 />
862 <label htmlFor="ingredient2" className="ml-2">整合包</label>
863 </div>
864 <div className="flex align-items-center">
865 <RadioButton
866 inputId="ingredient3"
867 name="pizza"
868 value="mod"
869 onChange={(e: RadioButtonChangeEvent) => {
870 setResourceFormData(prev => ({
871 ...prev, // 复制顶层所有属性
872 resource: {
873 ...prev.resource, // 复制resource对象的所有属性
874 classify: e.target.value
875 }
876 }));
877 setIngredient(e.value);
878 }}
lfmylbhf1783a092025-06-07 20:05:22 +0800879 checked={ingredient === 'mod'}
880 />
881 <label htmlFor="ingredient3" className="ml-2">模组</label>
882 </div>
883 <div className="flex align-items-center">
884 <RadioButton
885 inputId="ingredient4"
886 name="pizza"
887 value="map"
888 onChange={(e: RadioButtonChangeEvent) => {
889 setResourceFormData(prev => ({
890 ...prev, // 复制顶层所有属性
891 resource: {
892 ...prev.resource, // 复制resource对象的所有属性
893 classify: e.target.value
894 }
895 }));
896 setIngredient(e.value);
897 }}
lfmylbhf1783a092025-06-07 20:05:22 +0800898 checked={ingredient === 'map'}
899 />
900 <label htmlFor="ingredient4" className="ml-2">地图</label>
901 </div>
902 </div>
903 </div>
904 <div className="form-field">
905 <div className="form-field-header">
906 <span className="form-field-sign">*</span>
907 <label htmlFor="gameplayList">资源标签</label>
908 </div>
909 <MultiSelect
910 value={selectedGameplay}
911 onChange={(e: MultiSelectChangeEvent) => {
912 const selectedOptions = e.value as GameplayOption[];
913 // 提取选中项的 name 属性组成字符串数组
914 const selectedNames = selectedOptions.map(item => item.name);
915
916 setResourceFormData(prev => ({
917 ...prev,
918 gameplayList: selectedNames
919 }));
920 setSelectedGameplay(selectedOptions);
921 }}
922 options={gameplayOptions}
923 display="chip"
924 optionLabel="name"
925 placeholder="请选择资源标签"
lfmylbhf1783a092025-06-07 20:05:22 +0800926 className="w-full md:w-20rem"
927 />
928 </div>
929 <div className="form-field">
930 <div className="form-field-header">
931 <span className="form-field-sign">*</span>
932 <label>封面图片</label>
933 </div>
934 <FileUpload
lfmylbhfa9fa5c82025-06-09 00:34:49 +0800935 mode="advanced"
lfmylbhf1783a092025-06-07 20:05:22 +0800936 name="resource-image"
lfmylbhfa9fa5c82025-06-09 00:34:49 +0800937 customUpload
938 uploadHandler={async (e) => {
939 const formData = new FormData();
940 formData.append("file", e.files[0]);
941
942 try {
943 const res = await axios.post(`${process.env.PUBLIC_URL}/file`, formData);
944
945 const fileUrl = res.data.url;
946 console.log(fileUrl);
947 setResourcePictureUrl(fileUrl);
948 toast.current?.show({ severity: 'success', summary: '上传成功' });
949 } catch (error) {
950 console.log(error);
951 toast.current?.show({ severity: 'error', summary: '上传失败' });
952 }
953 }}
954 auto
lfmylbhf1783a092025-06-07 20:05:22 +0800955 accept="image/*"
lfmylbhfa9fa5c82025-06-09 00:34:49 +0800956 chooseLabel="上传资源封面"
lfmylbhf1783a092025-06-07 20:05:22 +0800957 />
958 </div>
959 </div>
960 </Dialog>
961
lfmylbhfa9fa5c82025-06-09 00:34:49 +0800962 {/*删除悬赏弹窗*/}
963 <Dialog
964 header="删除悬赏"
965 visible={deleteVisible}
966 onHide={() => setDeleteVisible(false)}
967 className="resource-delete-dialog"
968 modal
969 footer={
970 <div className="dialog-footer">
971 <Button label="确认" icon="pi pi-check" onClick={handleDeleteSubmit} autoFocus />
972 <Button label="取消" icon="pi pi-times" onClick={() => setDeleteVisible(false)} className="p-button-text" />
973 </div>
974 }
975 >
976 <div className="dialog-form">
lfmylbhf839e5a12025-06-09 18:22:25 +0800977 <span style={{marginBottom: "10px"}}>
lfmylbhfa9fa5c82025-06-09 00:34:49 +0800978 确认是否删除该悬赏?
979 </span>
lfmylbhfa9fa5c82025-06-09 00:34:49 +0800980 </div>
981 </Dialog>
982
983 {/*编辑资源弹窗*/}
984 <Dialog
985 header="编辑资源"
986 visible={editVisible}
987 onHide={() => setEditVisible(false)}
988 className="resource-edit-dialog"
989 modal
990 footer={
991 <div className="dialog-footer">
992 <Button label="确认" icon="pi pi-check" onClick={handleEditSubmit} autoFocus />
993 <Button label="取消" icon="pi pi-times" onClick={() => setEditVisible(false)} className="p-button-text" />
994 </div>
995 }
996 >
997 <div className="dialog-form">
998 <div className="form-field">
999 <div className="form-field-header">
1000 <label htmlFor="name">更改标题</label>
1001 </div>
1002 <InputText
1003 id="name"
1004 value={editRewardFormData.rewardName}
1005 onChange={(e) => setEditRewardFormData(prev => ({
1006 ...prev,
1007 rewardName: e.target.value
1008 }))}
1009 className="w-full"
1010 />
1011 </div>
1012 <div className="form-field">
1013 <div className="form-field-header">
lfmylbhffc9518b2025-06-09 00:37:41 +08001014 <label htmlFor="price">更改定价</label>
lfmylbhfa9fa5c82025-06-09 00:34:49 +08001015 </div>
1016 <InputText
lfmylbhffc9518b2025-06-09 00:37:41 +08001017 id="price"
lfmylbhfa9fa5c82025-06-09 00:34:49 +08001018 value={editRewardFormData.price}
1019 onChange={(e) => setEditRewardFormData(prev => ({
1020 ...prev,
1021 price: e.target.value
1022 }))}
1023 className="w-full"
1024 />
1025 </div>
1026 <div className="form-field">
1027 <div className="form-field-header">
lfmylbhffc9518b2025-06-09 00:37:41 +08001028 <label htmlFor="description">更改需求</label>
lfmylbhfa9fa5c82025-06-09 00:34:49 +08001029 </div>
1030 <InputTextarea
lfmylbhffc9518b2025-06-09 00:37:41 +08001031 id="description"
lfmylbhfa9fa5c82025-06-09 00:34:49 +08001032 value={editRewardFormData.rewardDescription}
1033 onChange={(e) => setEditRewardFormData(prev => ({
1034 ...prev,
1035 rewardDescription: e.target.value
1036 }))}
1037 rows={5}
1038 className="w-full"
1039 />
1040 </div>
1041 <div className="form-field">
1042 <div className="form-field-header">
1043 <span className="form-field-sign">*</span>
1044 <label>封面图片</label>
1045 </div>
1046 <FileUpload
1047 mode="advanced"
lfmylbhffc9518b2025-06-09 00:37:41 +08001048 name="reward-image"
lfmylbhfa9fa5c82025-06-09 00:34:49 +08001049 customUpload
1050 uploadHandler={async (e) => {
1051 const formData = new FormData();
1052 formData.append("file", e.files[0]);
1053
1054 try {
1055 const res = await axios.post(`${process.env.PUBLIC_URL}/file`, formData);
1056
1057 const fileUrl = res.data.url;
1058 console.log(fileUrl);
1059 setRewardPictureUrl(fileUrl);
1060 toast.current?.show({ severity: 'success', summary: '上传成功' });
1061 } catch (error) {
1062 console.log(error);
1063 toast.current?.show({ severity: 'error', summary: '上传失败' });
1064 }
1065 }}
1066 auto
1067 accept="image/*"
1068 chooseLabel="选择悬赏封面"
1069 />
1070 </div>
1071 </div>
1072 </Dialog>
lfmylbhf789e7172025-06-06 18:37:21 +08001073 </div>
1074 );
1075};
1076