blob: 1172aebae046caf53794841de37296e88d265696 [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="悬赏">
lfmylbhfa9fa5c82025-06-09 00:34:49 +0800656 <div className="resource-list">
657 {rewardList.map((rewardItem) => (
658 <Card key={rewardItem.rewardId} className="resources-list-card"
659 onClick={() => router.push(`/reward/reward-detail/${rewardItem.rewardId}`)}>
660 <Image alt="avatar"
661 src={process.env.NEXT_PUBLIC_NGINX_URL + "rewards/" + rewardItem.rewardPicture}
662 className="resource-avatar" width="250" height="140"/>
663 <div className="resource-header">
664 <div className="resource-content">
665 <h3>{rewardItem.rewardName}</h3>
666 </div>
lfmylbhf789e7172025-06-06 18:37:21 +0800667
lfmylbhfa9fa5c82025-06-09 00:34:49 +0800668 <div className="resource-operation">
669 <Button
670 label="编辑"
671 onClick={(e) => {
672 e.stopPropagation(); // 关键修复:阻止事件冒泡,避免触发Card的点击事件
673 setEditVisible(true);
674
675 setEditRewardFormData({
676 rewardId: rewardItem.rewardId,
677 rewardName: rewardItem.rewardName,
678 price: rewardItem.price.toString(),
679 rewardDescription: rewardItem.rewardDescription,
680 });
681 console.log("用户编辑悬赏");
682 }}
683 />
684 <Button
685 label="删除"
686 onClick={(e) => {
687 e.stopPropagation(); // 关键修复:阻止事件冒泡,避免触发Card的点击事件
688 setDeleteResourceId(rewardItem.rewardId);
689 setDeleteVisible(true);
690 }}
691 style={{backgroundColor: "rgba(255, 87, 51, 1)"}}
692 />
693 </div>
694 </div>
695 </Card>
696 ))}
697
698 {totalRewards > 5 && <Paginator
699 className="Paginator"
700 first={rewardFirst}
701 rows={rewardRows}
702 totalRecords={totalRewards}
703 rowsPerPageOptions={[5, 10]}
704 onPageChange={onRewardPageChange}
705 />}
706 </div>
lfmylbhf789e7172025-06-06 18:37:21 +0800707 </TabPanel>
708 </TabView>
709
710 {/*浮动按钮*/}
711 <div className="card">
712 <SpeedDial
713 model={actions}
714 direction="up"
715 style={{ position: 'fixed', bottom: '2rem', right: '2rem' }}
716 showIcon="pi pi-plus"
717 hideIcon="pi pi-times"
718 buttonClassName="custom-speeddial-button"
719 />
720 </div>
721
lfmylbhf1783a092025-06-07 20:05:22 +0800722 {/*发布资源弹窗*/}
723 <Dialog
724 header="发布资源"
725 visible={visible}
726 onHide={() => setVisible(false)}
727 className="publish-dialog"
728 modal
729 footer={
730 <div className="dialog-footer">
731 <Button label="发布" icon="pi pi-check" onClick={handleSubmit} autoFocus />
732 <Button label="取消" icon="pi pi-times" onClick={() => setVisible(false)} className="p-button-text" />
733 </div>
734 }
735 >
736 <div className="publish-form">
737 <div className="form-field">
738 <div className="form-field-header">
739 <span className="form-field-sign">*</span>
740 <label htmlFor="name">资源名称</label>
741 </div>
742 <InputText
743 id="name"
744 value={resourceFormData.resource.resourceName}
745 onChange={(e) => setResourceFormData(prev => ({
746 ...prev, // 复制顶层所有属性
747 resource: {
748 ...prev.resource, // 复制resource对象的所有属性
749 resourceName: e.target.value // 只更新resourceName
750 }
751 }))}
752 placeholder="请输入资源名称"
753 className="w-full"
lfmylbhf1783a092025-06-07 20:05:22 +0800754 />
755 </div>
756 <div className="form-field">
757 <div className="form-field-header">
758 <span className="form-field-sign">*</span>
759 <label htmlFor="summary">资源简介</label>
760 </div>
761 <InputText
762 id="summary"
763 value={resourceFormData.resource.resourceSummary}
764 onChange={(e) => setResourceFormData(prev => ({
765 ...prev, // 复制顶层所有属性
766 resource: {
767 ...prev.resource, // 复制resource对象的所有属性
768 resourceSummary: e.target.value
769 }
770 }))}
771 placeholder="请输入资源简介(一句话)"
772 className="w-full"
lfmylbhf1783a092025-06-07 20:05:22 +0800773 />
774 </div>
775 <div className="form-field">
776 <div className="form-field-header">
777 <label htmlFor="detail">资源介绍</label>
778 </div>
779 <InputTextarea
780 id="detail"
781 value={resourceFormData.resource.resourceDetail}
782 onChange={(e) => setResourceFormData(prev => ({
783 ...prev, // 复制顶层所有属性
784 resource: {
785 ...prev.resource, // 复制resource对象的所有属性
786 resourceDetail: e.target.value
787 }
788 }))}
789 rows={5}
790 placeholder="请输入资源介绍"
791 className="w-full"
lfmylbhf1783a092025-06-07 20:05:22 +0800792 />
793 </div>
794 <div className="form-field">
795 <div className="form-field-header">
796 <span className="form-field-sign">*</span>
797 <label htmlFor="price">价格</label>
798 </div>
799 <InputText
800 id="price"
801 value={resourceFormData.resource.price}
802 onChange={(e) => setResourceFormData(prev => ({
803 ...prev, // 复制顶层所有属性
804 resource: {
805 ...prev.resource, // 复制resource对象的所有属性
806 price: e.target.value
807 }
808 }))}
809 placeholder="请输入资源价格"
810 className="w-full"
811 />
812 </div>
813 <div className="form-field">
814 <div className="form-field-header">
815 <span className="form-field-sign">*</span>
816 <label htmlFor="classify">资源分类(请选择一项)</label>
817 </div>
818 <div className="form-field-classify">
819 <div className="flex align-items-center">
820 <RadioButton
821 inputId="ingredient1"
822 name="pizza"
823 value="resourcePack"
824 onChange={(e: RadioButtonChangeEvent) => {
825 setResourceFormData(prev => ({
826 ...prev, // 复制顶层所有属性
827 resource: {
828 ...prev.resource, // 复制resource对象的所有属性
829 classify: e.target.value
830 }
831 }));
832 setIngredient(e.value);
833 console.log(ingredient);
834 // console.log(resourceFormData.resource.classify);
835 }}
836 checked={ingredient === 'resourcePack'}
837 />
838 <label htmlFor="ingredient1" className="ml-2">材质包</label>
839 </div>
840 <div className="flex align-items-center">
841 <RadioButton
842 inputId="ingredient2"
843 name="pizza"
844 value="modPack"
845 onChange={(e: RadioButtonChangeEvent) => {
846 setResourceFormData(prev => ({
847 ...prev, // 复制顶层所有属性
848 resource: {
849 ...prev.resource, // 复制resource对象的所有属性
850 classify: e.target.value
851 }
852 }));
853 setIngredient(e.value);
854 }}
855 // onChange={(e: RadioButtonChangeEvent) => setIngredient(e.value)}
856 checked={ingredient === 'modPack'}
857 />
858 <label htmlFor="ingredient2" className="ml-2">整合包</label>
859 </div>
860 <div className="flex align-items-center">
861 <RadioButton
862 inputId="ingredient3"
863 name="pizza"
864 value="mod"
865 onChange={(e: RadioButtonChangeEvent) => {
866 setResourceFormData(prev => ({
867 ...prev, // 复制顶层所有属性
868 resource: {
869 ...prev.resource, // 复制resource对象的所有属性
870 classify: e.target.value
871 }
872 }));
873 setIngredient(e.value);
874 }}
875 // onChange={(e: RadioButtonChangeEvent) => setIngredient(e.value)}
876 checked={ingredient === 'mod'}
877 />
878 <label htmlFor="ingredient3" className="ml-2">模组</label>
879 </div>
880 <div className="flex align-items-center">
881 <RadioButton
882 inputId="ingredient4"
883 name="pizza"
884 value="map"
885 onChange={(e: RadioButtonChangeEvent) => {
886 setResourceFormData(prev => ({
887 ...prev, // 复制顶层所有属性
888 resource: {
889 ...prev.resource, // 复制resource对象的所有属性
890 classify: e.target.value
891 }
892 }));
893 setIngredient(e.value);
894 }}
895 // onChange={(e: RadioButtonChangeEvent) => setIngredient(e.value)}
896 checked={ingredient === 'map'}
897 />
898 <label htmlFor="ingredient4" className="ml-2">地图</label>
899 </div>
900 </div>
901 </div>
902 <div className="form-field">
903 <div className="form-field-header">
904 <span className="form-field-sign">*</span>
905 <label htmlFor="gameplayList">资源标签</label>
906 </div>
907 <MultiSelect
908 value={selectedGameplay}
909 onChange={(e: MultiSelectChangeEvent) => {
910 const selectedOptions = e.value as GameplayOption[];
911 // 提取选中项的 name 属性组成字符串数组
912 const selectedNames = selectedOptions.map(item => item.name);
913
914 setResourceFormData(prev => ({
915 ...prev,
916 gameplayList: selectedNames
917 }));
918 setSelectedGameplay(selectedOptions);
919 }}
920 options={gameplayOptions}
921 display="chip"
922 optionLabel="name"
923 placeholder="请选择资源标签"
924 // maxSelectedLabels={3}
925 className="w-full md:w-20rem"
926 />
927 </div>
928 <div className="form-field">
929 <div className="form-field-header">
930 <span className="form-field-sign">*</span>
931 <label>封面图片</label>
932 </div>
933 <FileUpload
lfmylbhfa9fa5c82025-06-09 00:34:49 +0800934 mode="advanced"
lfmylbhf1783a092025-06-07 20:05:22 +0800935 name="resource-image"
lfmylbhfa9fa5c82025-06-09 00:34:49 +0800936 customUpload
937 uploadHandler={async (e) => {
938 const formData = new FormData();
939 formData.append("file", e.files[0]);
940
941 try {
942 const res = await axios.post(`${process.env.PUBLIC_URL}/file`, formData);
943
944 const fileUrl = res.data.url;
945 console.log(fileUrl);
946 setResourcePictureUrl(fileUrl);
947 toast.current?.show({ severity: 'success', summary: '上传成功' });
948 } catch (error) {
949 console.log(error);
950 toast.current?.show({ severity: 'error', summary: '上传失败' });
951 }
952 }}
953 auto
lfmylbhf1783a092025-06-07 20:05:22 +0800954 accept="image/*"
lfmylbhfa9fa5c82025-06-09 00:34:49 +0800955 chooseLabel="上传资源封面"
956
957 // accept="image/*"
958 // maxFileSize={10000000000}
959 // chooseLabel="选择资源封面"
960 // className="w-full"
961 // onUpload={onUpload}
lfmylbhf1783a092025-06-07 20:05:22 +0800962 />
963 </div>
964 </div>
965 </Dialog>
966
lfmylbhfa9fa5c82025-06-09 00:34:49 +0800967 {/*删除悬赏弹窗*/}
968 <Dialog
969 header="删除悬赏"
970 visible={deleteVisible}
971 onHide={() => setDeleteVisible(false)}
972 className="resource-delete-dialog"
973 modal
974 footer={
975 <div className="dialog-footer">
976 <Button label="确认" icon="pi pi-check" onClick={handleDeleteSubmit} autoFocus />
977 <Button label="取消" icon="pi pi-times" onClick={() => setDeleteVisible(false)} className="p-button-text" />
978 </div>
979 }
980 >
981 <div className="dialog-form">
982 <span>
983 确认是否删除该悬赏?
984 </span>
985 {/*<div className="form-field">*/}
986 {/* <div className="form-field-header">*/}
987 {/* <label htmlFor="name">密码</label>*/}
988 {/* </div>*/}
989 {/* <Password*/}
990 {/* id="passwrod"*/}
991 {/* value={deleteResourceFormData.password}*/}
992 {/* onChange={(e) => setDeleteResourceFormData(prev => ({*/}
993 {/* ...prev,*/}
994 {/* password: e.target.value*/}
995 {/* }))}*/}
996 {/* placeholder="请输入密码"*/}
997 {/* className="w-full"*/}
998 {/* toggleMask*/}
999 {/* />*/}
1000 {/*</div>*/}
1001 </div>
1002 </Dialog>
1003
1004 {/*编辑资源弹窗*/}
1005 <Dialog
1006 header="编辑资源"
1007 visible={editVisible}
1008 onHide={() => setEditVisible(false)}
1009 className="resource-edit-dialog"
1010 modal
1011 footer={
1012 <div className="dialog-footer">
1013 <Button label="确认" icon="pi pi-check" onClick={handleEditSubmit} autoFocus />
1014 <Button label="取消" icon="pi pi-times" onClick={() => setEditVisible(false)} className="p-button-text" />
1015 </div>
1016 }
1017 >
1018 <div className="dialog-form">
1019 <div className="form-field">
1020 <div className="form-field-header">
1021 <label htmlFor="name">更改标题</label>
1022 </div>
1023 <InputText
1024 id="name"
1025 value={editRewardFormData.rewardName}
1026 onChange={(e) => setEditRewardFormData(prev => ({
1027 ...prev,
1028 rewardName: e.target.value
1029 }))}
1030 className="w-full"
1031 />
1032 </div>
1033 <div className="form-field">
1034 <div className="form-field-header">
1035 <label htmlFor="summary">更改定价</label>
1036 </div>
1037 <InputText
1038 id="summary"
1039 value={editRewardFormData.price}
1040 onChange={(e) => setEditRewardFormData(prev => ({
1041 ...prev,
1042 price: e.target.value
1043 }))}
1044 className="w-full"
1045 />
1046 </div>
1047 <div className="form-field">
1048 <div className="form-field-header">
1049 <label htmlFor="detail">更改需求</label>
1050 </div>
1051 <InputTextarea
1052 id="detail"
1053 value={editRewardFormData.rewardDescription}
1054 onChange={(e) => setEditRewardFormData(prev => ({
1055 ...prev,
1056 rewardDescription: e.target.value
1057 }))}
1058 rows={5}
1059 className="w-full"
1060 />
1061 </div>
1062 <div className="form-field">
1063 <div className="form-field-header">
1064 <span className="form-field-sign">*</span>
1065 <label>封面图片</label>
1066 </div>
1067 <FileUpload
1068 mode="advanced"
1069 name="resource-image"
1070 customUpload
1071 uploadHandler={async (e) => {
1072 const formData = new FormData();
1073 formData.append("file", e.files[0]);
1074
1075 try {
1076 const res = await axios.post(`${process.env.PUBLIC_URL}/file`, formData);
1077
1078 const fileUrl = res.data.url;
1079 console.log(fileUrl);
1080 setRewardPictureUrl(fileUrl);
1081 toast.current?.show({ severity: 'success', summary: '上传成功' });
1082 } catch (error) {
1083 console.log(error);
1084 toast.current?.show({ severity: 'error', summary: '上传失败' });
1085 }
1086 }}
1087 auto
1088 accept="image/*"
1089 chooseLabel="选择悬赏封面"
1090 />
1091 </div>
1092 </div>
1093 </Dialog>
lfmylbhf789e7172025-06-06 18:37:21 +08001094 </div>
1095 );
1096};
1097