blob: 8201bc5119767ae0569ddf9c35050cc15ef5b998 [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';
8import {Image} from "primereact/image";
lfmylbhf1783a092025-06-07 20:05:22 +08009// 发布资源
10import { Dialog } from 'primereact/dialog';
11import {InputText} from "primereact/inputtext";
12import {InputTextarea} from "primereact/inputtextarea";
13import {FileUpload} from "primereact/fileupload";
14// 资源分类
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";
33
34// 样式
35import './user.scss';
lfmylbhf789e7172025-06-06 18:37:21 +080036
37// 用户信息
38interface UserInfo {
39 userId: number;
40 username: string;
41 password: string;
42 avatar: string;
43 followerCount: number;// 粉丝数
44 subscriberCount: number;// 关注数
45 signature: string;// 个性签名
46 uploadAmount: number;
47 purchaseAmount: number;
48 credits: number;
49}
50
51// 用户数据
52interface UserData {
53 subscriberCount: number; // 关注数
54 uploadAmount: number; // 上传量(资源个数)
55 beDownloadedAmount: number; // 上传资源被下载量
56 seedPercentageList: number[]; // 上传资源类型百分比列表,按材质包、模组、整合包、地图的顺序返回
57}
58
lfmylbhf1783a092025-06-07 20:05:22 +080059// 用户发布过的资源
lfmylbhf789e7172025-06-06 18:37:21 +080060interface Resource {
61 resourceId: number;
62 resourceName: string;
63 resourcePicture: string;
64 resourceSummary: string; // 资源简介(一句话)
65 resourceDetail: string; // 资源介绍
66 uploadTime: string; // 上传时间
67 lastUpdateTime: string; // 最近更新时间
68 price: number;
69 downloads: number;
70 likes: number;
71 collections: number;
72 comments: number;
73 seeds: number; // 种子数
74 classify: string; // 资源分类(材质包:resourcePack,模组:mod,整合包:modPack ,地图:map
75}
76
lfmylbhf1783a092025-06-07 20:05:22 +080077// 用户发布过的资源列表
lfmylbhf789e7172025-06-06 18:37:21 +080078interface ResourceList {
79 records: Resource[];
80}
81
lfmylbhfa9fa5c82025-06-09 00:34:49 +080082// 帖子
83interface Thread {
84 threadId: number;
85 userId: number;
86 threadPicture: string;
87 title: string;
88 likes: number;
89 createAt: string;
90}
91
92// 发布帖子列表
93interface ThreadList {
94 records: Thread[];
95 total: number;
96 pages: number;
97 current: number;
98 size: number;
99}
100
101// 悬赏
102interface Reward {
103 rewardId: number;
104 userId: number;
105 rewardPicture: string;
106 rewardName: string;
107 createAt: string;
108 rewardDescription: string;
109 price: number;
110}
111
112// 我的悬赏列表
113interface RewardList {
114 rewardList: Reward[];
115 total: number; // 总记录数
116}
117
lfmylbhf1783a092025-06-07 20:05:22 +0800118// 资源标签
119interface GameplayOption {
120 name: string;
121 code: number;
122}
123
124// 资源标签选项
125const gameplayOptions: GameplayOption[] = [
126 { name: '科技', code: 1 },
127 { name: '魔法', code: 2 },
128 { name: '建筑', code: 3 },
129 { name: '风景', code: 4 },
130 { name: '竞技', code: 5 },
131 { name: '生存', code: 6 },
132 { name: '冒险', code: 7 },
133 { name: '跑酷', code: 8 },
134 { name: '艺术', code: 9 },
135 { name: '剧情', code: 10 },
136 { name: '社交', code: 11 },
137 { name: '策略', code: 12 },
138 { name: '极限', code: 13 }
139];
140
lfmylbhf789e7172025-06-06 18:37:21 +0800141export default function UserPage() {
142 // 路由
143 const router = useRouter();
144 // 发布资源列表
145 const [resourceList, setResourceList] = useState<Resource[]>([]);
146 // 用户信息
147 const [userInfo, setUserInfo] = useState<UserInfo>();
148 // 用户数据
149 const [userData, setUserData] = useState<UserData>();
150 // 消息提醒
151 const toast = useRef<Toast>(null);
lfmylbhf1783a092025-06-07 20:05:22 +0800152 // 资源标签
153 const [selectedGameplay, setSelectedGameplay] = useState<GameplayOption[]>([]);
lfmylbhfa9fa5c82025-06-09 00:34:49 +0800154 // 资源封面路径
155 const [resourcePictureUrl, setResourcePictureUrl] = useState<string>('');
156 // 主页发布帖子列表
157 const [homePageThread, setHomePageThread] = useState<ThreadList>();
158 // 我的帖子列表
159 const [threadList, setThreadList] = useState<Thread[]>([]);
160 // 我的悬赏列表
161 const [rewardList, setRewardList] = useState<Reward[]>([]);
162 // 控制Tab切换
163 const [activeIndex, setActiveIndex] = useState(0);
164 // 帖子分页
165 const [threadFirst, setThreadFirst] = useState(0);
166 const [threadRows, setThreadRows] = useState(6);
167 const [totalThreads, setTotalThreads] = useState<number>(0);
168 const onThreadPageChange = (event: PaginatorPageChangeEvent) => {
169 setThreadFirst(event.first);
170 setThreadRows(event.rows);
171 };
172 // 悬赏分页
173 const [rewardFirst, setRewardFirst] = useState(0);
174 const [rewardRows, setRewardRows] = useState(5);
175 const [totalRewards, setTotalRewards] = useState<number>(0);
176 const onRewardPageChange = (event: PaginatorPageChangeEvent) => {
177 setRewardFirst(event.first);
178 setRewardRows(event.rows);
179 };
lfmylbhf789e7172025-06-06 18:37:21 +0800180
181 useEffect(() => {
182 fetchUserInfo();
183 fetchUserData();
184 fetchResourceList();
185 }, []);
186
187 // 获取用户信息
188 const fetchUserInfo = async () => {
189 try {
190 const response = await axios.get<UserInfo>(process.env.PUBLIC_URL + `/user/info`, {
191 params: { userId: 22301010 }
192 });
193 console.log('获取用户信息:', response.data);
194 setUserInfo(response.data);
195 } catch (err) {
196 console.error('获取用户信息失败', err);
197 toast.current?.show({ severity: 'error', summary: 'error', detail: '获取用户信息失败' });
198 }
199 };
200
201 // 获取用户数据
202 const fetchUserData = async () => {
203 try {
204 const response = await axios.get<UserData>(process.env.PUBLIC_URL + `/user/data`, {
205 params: { userId: 22301010 }
206 });
207 console.log('获取用户数据:', response.data);
208 setUserData(response.data);
209 } catch (err) {
210 console.error('获取用户数据失败', err);
211 toast.current?.show({ severity: 'error', summary: 'error', detail: '获取用户数据失败' });
212 }
213 };
214
215 // 格式化数字显示 (3000 -> 3k)
216 const formatCount = (count?: number): string => {
217 if (count == null) return "0"; // 同时处理 undefined/null
218
219 const absCount = Math.abs(count); // 处理负数
220
221 const format = (num: number, suffix: string) => {
222 const fixed = num.toFixed(1);
223 return fixed.endsWith('.0')
224 ? `${Math.floor(num)}${suffix}`
225 : `${fixed}${suffix}`;
226 };
227
228 if (absCount >= 1e6) return format(count / 1e6, "m");
229 if (absCount >= 1e3) return format(count / 1e3, "k");
230 return count.toString();
231 };
232
233 // 获取发布资源
234 const fetchResourceList = async () => {
235 try {
236 const response = await axios.get<ResourceList>(process.env.PUBLIC_URL +`/user/upload`, {
237 params: { userId: 22301010, pageNumber: 1, rows: 3 }
238 });
239 console.log('获取发布资源列表:', response.data.records);
240 setResourceList(response.data.records);
lfmylbhf789e7172025-06-06 18:37:21 +0800241 } catch (err) {
242 console.error('获取发布资源失败', err);
243 toast.current?.show({ severity: 'error', summary: 'error', detail: '获取发布资源失败' });
244 }
245 };
246
lfmylbhfa9fa5c82025-06-09 00:34:49 +0800247 useEffect(() => {
248 fetchHomePageThread();
249 }, []);
250
251 // 获取主页部分的发布帖子
252 const fetchHomePageThread = async () => {
253 try {
254 const response = await axios.get<ThreadList>(process.env.PUBLIC_URL + `/user/thread`, {
255 params: { userId: 22301010, pageNumber: 1, rows: 3 }
256 })
257 console.log('获取主页发布帖子:', response.data);
258 setHomePageThread(response.data);
259
260 } catch (err) {
261 console.error('获取主页发布帖子失败', err);
262 toast.current?.show({ severity: 'error', summary: 'error', detail: '获取主页发布帖子失败' });
263 }
264 }
265
266 useEffect(() => {
267 fetchThreadList();
268 }, [threadFirst, threadRows]);
269
270 // 获取用户发布的所有帖子
271 const fetchThreadList = async () => {
272 try {
273 const pageNumber = threadFirst / threadRows + 1;
274 const response = await axios.get<ThreadList>(process.env.PUBLIC_URL + `/user/thread`, {
275 params: { userId: 22301010, pageNumber: pageNumber, rows: threadRows }
276 })
277 console.log('获取我的帖子:', response.data);
278 setThreadList(response.data.records);
279 setTotalThreads(response.data.total)
280 }catch (err) {
281 console.error('获取我的帖子失败', err);
282 toast.current?.show({ severity: 'error', summary: 'error', detail: '获取我的帖子失败' });
283 }
284 }
285
286 useEffect(() => {
287 fetchRewardList();
288 }, [rewardFirst, rewardRows]);
289
290 // 获取用户发布的所有悬赏
291 const fetchRewardList = async () => {
292 try {
293 const pageNumber = rewardFirst / rewardRows + 1;
294 const response = await axios.get<RewardList>(process.env.PUBLIC_URL + `/user/reward`, {
295 params: { userId: 22301010, pageNumber: pageNumber, rows: rewardRows }
296 })
297 console.log('获取我的悬赏:', response.data);
298 setRewardList(response.data.rewardList);
299 setTotalRewards(response.data.total)
300 }catch (err) {
301 console.error('获取我的悬赏失败', err);
302 toast.current?.show({ severity: 'error', summary: 'error', detail: '获取我的悬赏失败' });
303 }
304 }
305
lfmylbhf789e7172025-06-06 18:37:21 +0800306 // 浮动按钮的子模块
307 const actions = [
308 {
309 template: () => (
310 <Button label="管理资源" onClick={() => router.push(`/user/manage/resources/`)}/>
311 )
312 },
313 {
314 template: () => (
315 <Button label="已购资源" onClick={() => router.push(`/user/purchased-resources/`)}/>
316 )
317 },
318 {
319 template: () => (
lfmylbhf1783a092025-06-07 20:05:22 +0800320 <Button label="发布资源" onClick={() => setVisible(true)}/>
lfmylbhf789e7172025-06-06 18:37:21 +0800321 )
322 },
323 {
324 template: () => (
325 <Button label="编辑悬赏" onClick={() => router.push(`/user/manage/resources/`)}/>
326 )
327 }
328 ];
329
lfmylbhf1783a092025-06-07 20:05:22 +0800330 // 发布资源弹窗
331 const [visible, setVisible] = useState(false);
332 const [resourceFormData, setResourceFormData] = useState({
333 resource: {
334 resourceName: '',
335 resourcePicture: '',
336 resourceSummary: '',
337 resourceDetail: '',
338 uploadTime: '',
339 lastUpdateTime: '',
340 price: '',
341 classify: '',
342 },
343 gameplayList: [''],
344 completeRewardId: null,
345 userId: 0,
346 });
347 const [ingredient, setIngredient] = useState<string>('');
348
lfmylbhfa9fa5c82025-06-09 00:34:49 +0800349 // 删除悬赏弹窗
350 const [deleteVisible, setDeleteVisible] = useState(false);
351 // 要删除悬赏的id
352 const [deleteRewardId, setDeleteResourceId] = useState<number>(0);
353 // 处理删除悬赏接口
354 const handleDeleteSubmit = async () => {
355 try {
356 // 发送DELETE请求
357 const response = await axios.delete(process.env.PUBLIC_URL + `/reward`, {
358 params: {rewardId: deleteRewardId},
359 });
360 console.log("用户" + 22301010 + "要删除" + deleteRewardId + "号悬赏");
lfmylbhf1783a092025-06-07 20:05:22 +0800361
lfmylbhfa9fa5c82025-06-09 00:34:49 +0800362 if (response.status === 204) {
363 console.log("用户成功删除悬赏");
364 toast.current?.show({severity: 'success', summary: 'Success', detail: '删除悬赏成功'});
365 setDeleteVisible(false);
366 // 重新拉取资源列表
367 fetchRewardList();
368 }
369 } catch (error) {
370 console.error('资源删除失败:', error);
371 toast.current?.show({ severity: 'error', summary: 'error', detail: '密码错误,资源删除失败' });
372 }
373 };
374
375
376 // 编辑悬赏弹窗
377 const [editVisible, setEditVisible] = useState(false);
378 // 悬赏封面路径
379 const [rewardPictureUrl, setRewardPictureUrl] = useState<string>('');
380 const [editRewardFormData, setEditRewardFormData] = useState({
381 rewardId: 0,
382 rewardName: '',
383 price: '',
384 rewardDescription: '',
385 });
386
387 // 处理编辑资源接口
388 const handleEditSubmit = async () => {
389 try {
390 const postData = {
391 rewardId: editRewardFormData.rewardId,
392 rewardName: editRewardFormData.rewardName,
393 rewardPicture: rewardPictureUrl,
394 price: toNumber(editRewardFormData.price),
395 rewardDescription: editRewardFormData.rewardDescription,
396 };
397 // 发送POST请求
398 const response = await axios.put(process.env.PUBLIC_URL + '/reward/info', postData);
399 console.log("编辑悬赏的信息:", postData);
400
401 if (response.status === 200) {
402 toast.current?.show({ severity: 'success', summary: 'Success', detail: '悬赏编辑成功' });
403 // 编辑成功
404 setEditVisible(false);
405 fetchRewardList();
406 }
407 } catch (error) {
408 console.error('悬赏编辑失败:', error);
409 toast.current?.show({ severity: 'error', summary: 'error', detail: '悬赏编辑失败' });
410 }
lfmylbhf1783a092025-06-07 20:05:22 +0800411 };
412
413 // 上传资源接口
414 const handleSubmit = async () => {
415 try {
416 // 规定用户必须输入的内容
417 if (resourceFormData.resource.resourceName == '') {
418 toast.current?.show({ severity: 'info', summary: 'error', detail: '缺少资源名称' });
419 return;
420 }
421 if (resourceFormData.resource.resourceSummary == '') {
422 toast.current?.show({ severity: 'info', summary: 'error', detail: '缺少资源简介' });
423 return;
424 }
425 if (resourceFormData.resource.price == '') {
426 toast.current?.show({ severity: 'info', summary: 'error', detail: '缺少资源价格' });
427 return;
428 }
429 if (resourceFormData.resource.classify == '') {
430 toast.current?.show({ severity: 'info', summary: 'error', detail: '缺少资源分类' });
431 return;
432 }
433 if (
434 resourceFormData.gameplayList.length === 1 &&
435 resourceFormData.gameplayList[0] === ''
436 ) {
437 toast.current?.show({ severity: 'info', summary: 'error', detail: '缺少资源标签' });
438 return;
439 }
lfmylbhfa9fa5c82025-06-09 00:34:49 +0800440 if (resourcePictureUrl === '') {
lfmylbhf1783a092025-06-07 20:05:22 +0800441 toast.current?.show({ severity: 'info', summary: 'error', detail: '缺少资源封面' });
442 return;
443 }
444
445 const currentDate = new Date().toISOString().split('T')[0];
446 const postData = {
447 resource: {
448 resourceName: resourceFormData.resource.resourceName,
lfmylbhfa9fa5c82025-06-09 00:34:49 +0800449 resourcePicture: resourcePictureUrl,
lfmylbhf1783a092025-06-07 20:05:22 +0800450 resourceSummary: resourceFormData.resource.resourceSummary,
451 resourceDetail: resourceFormData.resource.resourceDetail,
452 uploadTime: currentDate,
453 lastUpdateTime: currentDate,
454 price: toNumber(resourceFormData.resource.price),
455 classify: resourceFormData.resource.classify,
456 },
457 gameplayList: resourceFormData.gameplayList,
458 completeRewardId: null,
459 userId: 22301010, // 记得用户登录状态获取
460 };
461 // 发送POST请求
462 const response = await axios.post(process.env.PUBLIC_URL + '/resource', postData);
463 console.log("上传资源的信息:", postData);
464
465 if (response.status === 200) {
466 toast.current?.show({ severity: 'success', summary: 'Success', detail: '资源上传成功' });
467 // 上传成功
468 setVisible(false);
469 // 重置表单
470 setResourceFormData({
471 resource: {
472 resourceName: '',
473 resourcePicture: '',
474 resourceSummary: '',
475 resourceDetail: '',
476 uploadTime: '',
477 lastUpdateTime: '',
478 price: '',
479 classify: '',
480 },
481 gameplayList: [],
482 completeRewardId: null,
483 userId: 0,
484 });
485 // 重置资源分类
486 setIngredient("");
487 // 重置资源标签
488 setSelectedGameplay([]);
lfmylbhfa9fa5c82025-06-09 00:34:49 +0800489 // 重置资源封面
490 setResourcePictureUrl('');
lfmylbhf1783a092025-06-07 20:05:22 +0800491 // 可以刷新资源列表
492 // fetchResourceList();
493 }
494 } catch (error) {
495 console.error('资源上传失败:', error);
496 toast.current?.show({ severity: 'error', summary: 'error', detail: '资源上传失败' });
497 }
498 };
499
lfmylbhf789e7172025-06-06 18:37:21 +0800500 return (
501 <div className="user-container">
lfmylbhf1783a092025-06-07 20:05:22 +0800502 <Toast ref={toast}></Toast>
lfmylbhf789e7172025-06-06 18:37:21 +0800503 {/*个人信息*/}
504 <div className="user-profile-card">
505 <Avatar
506 image={`${process.env.NEXT_PUBLIC_NGINX_URL}/users/${userInfo?.avatar}`}
507 className="user-avatar"
508 shape="circle"
509 />
510 <div className="user-info">
511 <div className="user-detail-info">
512 <div className="name-container">
513 <h2 className="name">{userInfo?.username}</h2>
514 <span className="signature">{userInfo?.signature}</span>
515 </div>
516
517 <div className="stats-container">
518 <div className="stats">
519 <span className="stats-label">粉丝:</span>
520 <span className="stats-value">{userInfo?.followerCount}</span>
521 </div>
522 <div className="stats">
523 <span className="stats-label">累计上传量:</span>
524 <span className="stats-value">{formatCount(userData?.uploadAmount)}</span>
525 </div>
526 <div className="stats">
527 <span className="stats-label">关注:</span>
528 <span className="stats-value">{userInfo?.subscriberCount}</span>
529 </div>
530 <div className="stats">
531 <span className="stats-label">累计被下载量:</span>
532 <span className="stats-value">{formatCount(userData?.beDownloadedAmount)}</span>
533 </div>
534 </div>
535 </div>
536
537 <Button label="关注" className="action-button"/>
538 </div>
539 </div>
540
541 {/*个人内容*/}
lfmylbhfa9fa5c82025-06-09 00:34:49 +0800542 <TabView activeIndex={activeIndex} onTabChange={(e) => setActiveIndex(e.index)}>
543 <TabPanel header="主页" >
lfmylbhf789e7172025-06-06 18:37:21 +0800544 {/*发布资源*/}
545 <div className="homepage-item">
546 <div className="section-header">
547 <h1>发布资源</h1>
548 <Button
549 label="显示更多"
550 link
551 onClick={() => router.push('/user/manage/resources/')}
552 />
553 </div>
554 <div className="resource-grid">
555 {resourceList.map((resourceList) => (
556 <Card key={resourceList.resourceId} className="resource-card" onClick={() => router.push(`/resource/resource-detail/${resourceList.resourceId}`)}>
557 <Image
558 src={process.env.NEXT_PUBLIC_NGINX_URL + resourceList.resourcePicture}
559 alt={resourceList.resourceName}
560 width="368"
561 height="200"
562 />
563 <div className="card-content">
564 <h3>{resourceList.resourceName}</h3>
565 <div className="view-count">
566 <Fire theme="outline" size="16" fill="#FF8D1A" />
567 <span>{resourceList.likes}</span>
568 </div>
569 </div>
570 </Card>
571 ))}
572 </div>
573 </div>
574
575 {/*发布帖子*/}
576 <div className="homepage-item">
577 <div className="section-header">
578 <h1>发布帖子</h1>
579 <Button
580 label="显示更多"
581 link
lfmylbhfa9fa5c82025-06-09 00:34:49 +0800582 onClick={() => setActiveIndex(1)}
lfmylbhf789e7172025-06-06 18:37:21 +0800583 />
584 </div>
585 <div className="resource-grid">
lfmylbhfa9fa5c82025-06-09 00:34:49 +0800586 {homePageThread?.records.map((homePageThread) => (
587 <Card key={homePageThread.threadId} className="resource-card" onClick={() => router.push(`/community/thread-detail/${homePageThread.threadId}`)}>
588 <Image
589 src={process.env.NEXT_PUBLIC_NGINX_URL + homePageThread.threadPicture}
590 alt={homePageThread.title}
591 width="368"
592 height="200"
593 />
594 <div className="card-content">
595 <h3>{homePageThread.title}</h3>
596 <div className="view-count">
597 <Fire theme="outline" size="16" fill="#FF8D1A" />
598 <span>{homePageThread.likes}</span>
599 </div>
600 </div>
601 </Card>
602 ))}
lfmylbhf789e7172025-06-06 18:37:21 +0800603 </div>
604 </div>
605 </TabPanel>
606
lfmylbhfa9fa5c82025-06-09 00:34:49 +0800607 {/*<TabPanel header="发布">*/}
lfmylbhf789e7172025-06-06 18:37:21 +0800608
lfmylbhfa9fa5c82025-06-09 00:34:49 +0800609 {/*</TabPanel>*/}
lfmylbhf789e7172025-06-06 18:37:21 +0800610
611 <TabPanel header="帖子">
lfmylbhfa9fa5c82025-06-09 00:34:49 +0800612 {/*我的帖子*/}
613 <div className="homepage-item">
614 <div className="section-header">
615 <h1>我的帖子</h1>
616 </div>
617 <div className="resource-grid">
618 {threadList.map((threadList) => (
619 <Card key={threadList.threadId} className="resource-card" onClick={() => router.push(`/community/thread-detail/${threadList.threadId}`)}>
620 <Image
621 src={process.env.NEXT_PUBLIC_NGINX_URL + threadList.threadPicture}
622 alt={threadList.title}
623 width="368"
624 height="200"
625 />
626 <div className="card-content">
627 <h3>{threadList.title}</h3>
628 <div className="view-count">
629 <Fire theme="outline" size="16" fill="#FF8D1A" />
630 <span>{threadList.likes}</span>
631 </div>
632 </div>
633 </Card>
634 ))}
635 </div>
636 {totalThreads > 6 && <Paginator
637 className="Paginator"
638 first={threadFirst}
639 rows={threadRows}
640 totalRecords={totalThreads}
641 rowsPerPageOptions={[6, 12]}
642 onPageChange={onThreadPageChange}
643 />}
644 </div>
lfmylbhf789e7172025-06-06 18:37:21 +0800645 </TabPanel>
646
647 <TabPanel header="收藏">
648
649 </TabPanel>
650
651 <TabPanel header="数据">
652
653 </TabPanel>
654
655 <TabPanel header="悬赏">
lfmylbhffc9518b2025-06-09 00:37:41 +0800656 <div className="section-header">
657 <h1>我的悬赏</h1>
658 </div>
659
lfmylbhfa9fa5c82025-06-09 00:34:49 +0800660 <div className="resource-list">
661 {rewardList.map((rewardItem) => (
662 <Card key={rewardItem.rewardId} className="resources-list-card"
663 onClick={() => router.push(`/reward/reward-detail/${rewardItem.rewardId}`)}>
664 <Image alt="avatar"
665 src={process.env.NEXT_PUBLIC_NGINX_URL + "rewards/" + rewardItem.rewardPicture}
666 className="resource-avatar" width="250" height="140"/>
667 <div className="resource-header">
668 <div className="resource-content">
669 <h3>{rewardItem.rewardName}</h3>
670 </div>
lfmylbhf789e7172025-06-06 18:37:21 +0800671
lfmylbhfa9fa5c82025-06-09 00:34:49 +0800672 <div className="resource-operation">
673 <Button
674 label="编辑"
675 onClick={(e) => {
676 e.stopPropagation(); // 关键修复:阻止事件冒泡,避免触发Card的点击事件
677 setEditVisible(true);
678
679 setEditRewardFormData({
680 rewardId: rewardItem.rewardId,
681 rewardName: rewardItem.rewardName,
682 price: rewardItem.price.toString(),
683 rewardDescription: rewardItem.rewardDescription,
684 });
685 console.log("用户编辑悬赏");
686 }}
687 />
688 <Button
689 label="删除"
690 onClick={(e) => {
691 e.stopPropagation(); // 关键修复:阻止事件冒泡,避免触发Card的点击事件
692 setDeleteResourceId(rewardItem.rewardId);
693 setDeleteVisible(true);
694 }}
695 style={{backgroundColor: "rgba(255, 87, 51, 1)"}}
696 />
697 </div>
698 </div>
699 </Card>
700 ))}
701
702 {totalRewards > 5 && <Paginator
703 className="Paginator"
704 first={rewardFirst}
705 rows={rewardRows}
706 totalRecords={totalRewards}
707 rowsPerPageOptions={[5, 10]}
708 onPageChange={onRewardPageChange}
709 />}
710 </div>
lfmylbhf789e7172025-06-06 18:37:21 +0800711 </TabPanel>
712 </TabView>
713
714 {/*浮动按钮*/}
715 <div className="card">
716 <SpeedDial
717 model={actions}
718 direction="up"
719 style={{ position: 'fixed', bottom: '2rem', right: '2rem' }}
720 showIcon="pi pi-plus"
721 hideIcon="pi pi-times"
722 buttonClassName="custom-speeddial-button"
723 />
724 </div>
725
lfmylbhf1783a092025-06-07 20:05:22 +0800726 {/*发布资源弹窗*/}
727 <Dialog
728 header="发布资源"
729 visible={visible}
730 onHide={() => setVisible(false)}
731 className="publish-dialog"
732 modal
733 footer={
734 <div className="dialog-footer">
735 <Button label="发布" icon="pi pi-check" onClick={handleSubmit} autoFocus />
736 <Button label="取消" icon="pi pi-times" onClick={() => setVisible(false)} className="p-button-text" />
737 </div>
738 }
739 >
740 <div className="publish-form">
741 <div className="form-field">
742 <div className="form-field-header">
743 <span className="form-field-sign">*</span>
744 <label htmlFor="name">资源名称</label>
745 </div>
746 <InputText
747 id="name"
748 value={resourceFormData.resource.resourceName}
749 onChange={(e) => setResourceFormData(prev => ({
750 ...prev, // 复制顶层所有属性
751 resource: {
752 ...prev.resource, // 复制resource对象的所有属性
753 resourceName: e.target.value // 只更新resourceName
754 }
755 }))}
756 placeholder="请输入资源名称"
757 className="w-full"
lfmylbhf1783a092025-06-07 20:05:22 +0800758 />
759 </div>
760 <div className="form-field">
761 <div className="form-field-header">
762 <span className="form-field-sign">*</span>
763 <label htmlFor="summary">资源简介</label>
764 </div>
765 <InputText
766 id="summary"
767 value={resourceFormData.resource.resourceSummary}
768 onChange={(e) => setResourceFormData(prev => ({
769 ...prev, // 复制顶层所有属性
770 resource: {
771 ...prev.resource, // 复制resource对象的所有属性
772 resourceSummary: e.target.value
773 }
774 }))}
775 placeholder="请输入资源简介(一句话)"
776 className="w-full"
lfmylbhf1783a092025-06-07 20:05:22 +0800777 />
778 </div>
779 <div className="form-field">
780 <div className="form-field-header">
781 <label htmlFor="detail">资源介绍</label>
782 </div>
783 <InputTextarea
784 id="detail"
785 value={resourceFormData.resource.resourceDetail}
786 onChange={(e) => setResourceFormData(prev => ({
787 ...prev, // 复制顶层所有属性
788 resource: {
789 ...prev.resource, // 复制resource对象的所有属性
790 resourceDetail: e.target.value
791 }
792 }))}
793 rows={5}
794 placeholder="请输入资源介绍"
795 className="w-full"
lfmylbhf1783a092025-06-07 20:05:22 +0800796 />
797 </div>
798 <div className="form-field">
799 <div className="form-field-header">
800 <span className="form-field-sign">*</span>
801 <label htmlFor="price">价格</label>
802 </div>
803 <InputText
804 id="price"
805 value={resourceFormData.resource.price}
806 onChange={(e) => setResourceFormData(prev => ({
807 ...prev, // 复制顶层所有属性
808 resource: {
809 ...prev.resource, // 复制resource对象的所有属性
810 price: e.target.value
811 }
812 }))}
813 placeholder="请输入资源价格"
814 className="w-full"
815 />
816 </div>
817 <div className="form-field">
818 <div className="form-field-header">
819 <span className="form-field-sign">*</span>
820 <label htmlFor="classify">资源分类(请选择一项)</label>
821 </div>
822 <div className="form-field-classify">
823 <div className="flex align-items-center">
824 <RadioButton
825 inputId="ingredient1"
826 name="pizza"
827 value="resourcePack"
828 onChange={(e: RadioButtonChangeEvent) => {
829 setResourceFormData(prev => ({
830 ...prev, // 复制顶层所有属性
831 resource: {
832 ...prev.resource, // 复制resource对象的所有属性
833 classify: e.target.value
834 }
835 }));
836 setIngredient(e.value);
837 console.log(ingredient);
838 // console.log(resourceFormData.resource.classify);
839 }}
840 checked={ingredient === 'resourcePack'}
841 />
842 <label htmlFor="ingredient1" className="ml-2">材质包</label>
843 </div>
844 <div className="flex align-items-center">
845 <RadioButton
846 inputId="ingredient2"
847 name="pizza"
848 value="modPack"
849 onChange={(e: RadioButtonChangeEvent) => {
850 setResourceFormData(prev => ({
851 ...prev, // 复制顶层所有属性
852 resource: {
853 ...prev.resource, // 复制resource对象的所有属性
854 classify: e.target.value
855 }
856 }));
857 setIngredient(e.value);
858 }}
859 // onChange={(e: RadioButtonChangeEvent) => setIngredient(e.value)}
860 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 }}
879 // onChange={(e: RadioButtonChangeEvent) => setIngredient(e.value)}
880 checked={ingredient === 'mod'}
881 />
882 <label htmlFor="ingredient3" className="ml-2">模组</label>
883 </div>
884 <div className="flex align-items-center">
885 <RadioButton
886 inputId="ingredient4"
887 name="pizza"
888 value="map"
889 onChange={(e: RadioButtonChangeEvent) => {
890 setResourceFormData(prev => ({
891 ...prev, // 复制顶层所有属性
892 resource: {
893 ...prev.resource, // 复制resource对象的所有属性
894 classify: e.target.value
895 }
896 }));
897 setIngredient(e.value);
898 }}
899 // onChange={(e: RadioButtonChangeEvent) => setIngredient(e.value)}
900 checked={ingredient === 'map'}
901 />
902 <label htmlFor="ingredient4" className="ml-2">地图</label>
903 </div>
904 </div>
905 </div>
906 <div className="form-field">
907 <div className="form-field-header">
908 <span className="form-field-sign">*</span>
909 <label htmlFor="gameplayList">资源标签</label>
910 </div>
911 <MultiSelect
912 value={selectedGameplay}
913 onChange={(e: MultiSelectChangeEvent) => {
914 const selectedOptions = e.value as GameplayOption[];
915 // 提取选中项的 name 属性组成字符串数组
916 const selectedNames = selectedOptions.map(item => item.name);
917
918 setResourceFormData(prev => ({
919 ...prev,
920 gameplayList: selectedNames
921 }));
922 setSelectedGameplay(selectedOptions);
923 }}
924 options={gameplayOptions}
925 display="chip"
926 optionLabel="name"
927 placeholder="请选择资源标签"
928 // maxSelectedLabels={3}
929 className="w-full md:w-20rem"
930 />
931 </div>
932 <div className="form-field">
933 <div className="form-field-header">
934 <span className="form-field-sign">*</span>
935 <label>封面图片</label>
936 </div>
937 <FileUpload
lfmylbhfa9fa5c82025-06-09 00:34:49 +0800938 mode="advanced"
lfmylbhf1783a092025-06-07 20:05:22 +0800939 name="resource-image"
lfmylbhfa9fa5c82025-06-09 00:34:49 +0800940 customUpload
941 uploadHandler={async (e) => {
942 const formData = new FormData();
943 formData.append("file", e.files[0]);
944
945 try {
946 const res = await axios.post(`${process.env.PUBLIC_URL}/file`, formData);
947
948 const fileUrl = res.data.url;
949 console.log(fileUrl);
950 setResourcePictureUrl(fileUrl);
951 toast.current?.show({ severity: 'success', summary: '上传成功' });
952 } catch (error) {
953 console.log(error);
954 toast.current?.show({ severity: 'error', summary: '上传失败' });
955 }
956 }}
957 auto
lfmylbhf1783a092025-06-07 20:05:22 +0800958 accept="image/*"
lfmylbhfa9fa5c82025-06-09 00:34:49 +0800959 chooseLabel="上传资源封面"
960
961 // accept="image/*"
962 // maxFileSize={10000000000}
963 // chooseLabel="选择资源封面"
964 // className="w-full"
965 // onUpload={onUpload}
lfmylbhf1783a092025-06-07 20:05:22 +0800966 />
967 </div>
968 </div>
969 </Dialog>
970
lfmylbhfa9fa5c82025-06-09 00:34:49 +0800971 {/*删除悬赏弹窗*/}
972 <Dialog
973 header="删除悬赏"
974 visible={deleteVisible}
975 onHide={() => setDeleteVisible(false)}
976 className="resource-delete-dialog"
977 modal
978 footer={
979 <div className="dialog-footer">
980 <Button label="确认" icon="pi pi-check" onClick={handleDeleteSubmit} autoFocus />
981 <Button label="取消" icon="pi pi-times" onClick={() => setDeleteVisible(false)} className="p-button-text" />
982 </div>
983 }
984 >
985 <div className="dialog-form">
986 <span>
987 确认是否删除该悬赏?
988 </span>
989 {/*<div className="form-field">*/}
990 {/* <div className="form-field-header">*/}
991 {/* <label htmlFor="name">密码</label>*/}
992 {/* </div>*/}
993 {/* <Password*/}
994 {/* id="passwrod"*/}
995 {/* value={deleteResourceFormData.password}*/}
996 {/* onChange={(e) => setDeleteResourceFormData(prev => ({*/}
997 {/* ...prev,*/}
998 {/* password: e.target.value*/}
999 {/* }))}*/}
1000 {/* placeholder="请输入密码"*/}
1001 {/* className="w-full"*/}
1002 {/* toggleMask*/}
1003 {/* />*/}
1004 {/*</div>*/}
1005 </div>
1006 </Dialog>
1007
1008 {/*编辑资源弹窗*/}
1009 <Dialog
1010 header="编辑资源"
1011 visible={editVisible}
1012 onHide={() => setEditVisible(false)}
1013 className="resource-edit-dialog"
1014 modal
1015 footer={
1016 <div className="dialog-footer">
1017 <Button label="确认" icon="pi pi-check" onClick={handleEditSubmit} autoFocus />
1018 <Button label="取消" icon="pi pi-times" onClick={() => setEditVisible(false)} className="p-button-text" />
1019 </div>
1020 }
1021 >
1022 <div className="dialog-form">
1023 <div className="form-field">
1024 <div className="form-field-header">
1025 <label htmlFor="name">更改标题</label>
1026 </div>
1027 <InputText
1028 id="name"
1029 value={editRewardFormData.rewardName}
1030 onChange={(e) => setEditRewardFormData(prev => ({
1031 ...prev,
1032 rewardName: e.target.value
1033 }))}
1034 className="w-full"
1035 />
1036 </div>
1037 <div className="form-field">
1038 <div className="form-field-header">
lfmylbhffc9518b2025-06-09 00:37:41 +08001039 <label htmlFor="price">更改定价</label>
lfmylbhfa9fa5c82025-06-09 00:34:49 +08001040 </div>
1041 <InputText
lfmylbhffc9518b2025-06-09 00:37:41 +08001042 id="price"
lfmylbhfa9fa5c82025-06-09 00:34:49 +08001043 value={editRewardFormData.price}
1044 onChange={(e) => setEditRewardFormData(prev => ({
1045 ...prev,
1046 price: e.target.value
1047 }))}
1048 className="w-full"
1049 />
1050 </div>
1051 <div className="form-field">
1052 <div className="form-field-header">
lfmylbhffc9518b2025-06-09 00:37:41 +08001053 <label htmlFor="description">更改需求</label>
lfmylbhfa9fa5c82025-06-09 00:34:49 +08001054 </div>
1055 <InputTextarea
lfmylbhffc9518b2025-06-09 00:37:41 +08001056 id="description"
lfmylbhfa9fa5c82025-06-09 00:34:49 +08001057 value={editRewardFormData.rewardDescription}
1058 onChange={(e) => setEditRewardFormData(prev => ({
1059 ...prev,
1060 rewardDescription: e.target.value
1061 }))}
1062 rows={5}
1063 className="w-full"
1064 />
1065 </div>
1066 <div className="form-field">
1067 <div className="form-field-header">
1068 <span className="form-field-sign">*</span>
1069 <label>封面图片</label>
1070 </div>
1071 <FileUpload
1072 mode="advanced"
lfmylbhffc9518b2025-06-09 00:37:41 +08001073 name="reward-image"
lfmylbhfa9fa5c82025-06-09 00:34:49 +08001074 customUpload
1075 uploadHandler={async (e) => {
1076 const formData = new FormData();
1077 formData.append("file", e.files[0]);
1078
1079 try {
1080 const res = await axios.post(`${process.env.PUBLIC_URL}/file`, formData);
1081
1082 const fileUrl = res.data.url;
1083 console.log(fileUrl);
1084 setRewardPictureUrl(fileUrl);
1085 toast.current?.show({ severity: 'success', summary: '上传成功' });
1086 } catch (error) {
1087 console.log(error);
1088 toast.current?.show({ severity: 'error', summary: '上传失败' });
1089 }
1090 }}
1091 auto
1092 accept="image/*"
1093 chooseLabel="选择悬赏封面"
1094 />
1095 </div>
1096 </div>
1097 </Dialog>
lfmylbhf789e7172025-06-06 18:37:21 +08001098 </div>
1099 );
1100};
1101