blob: 4baef171c16593518f004e2d0e4d6497e8b20850 [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`, {
262 params: { userId: 22301010, pageNumber: 1, rows: 3 }
263 })
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`, {
282 params: { userId: 22301010, pageNumber: pageNumber, rows: threadRows }
283 })
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`, {
302 params: { userId: 22301010, pageNumber: pageNumber, rows: rewardRows }
303 })
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: () => (
LaoeGaocid0773912025-06-09 00:38:40 +0800332 <Button label="编辑悬赏" onClick={() => router.push(`/user/manage/resources/`)} />
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 });
367 console.log("用户" + 22301010 + "要删除" + 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 // fetchResourceList();
500 }
501 } catch (error) {
502 console.error('资源上传失败:', error);
503 toast.current?.show({ severity: 'error', summary: 'error', detail: '资源上传失败' });
504 }
505 };
506
lfmylbhf789e7172025-06-06 18:37:21 +0800507 return (
508 <div className="user-container">
lfmylbhf1783a092025-06-07 20:05:22 +0800509 <Toast ref={toast}></Toast>
lfmylbhf789e7172025-06-06 18:37:21 +0800510 {/*个人信息*/}
511 <div className="user-profile-card">
512 <Avatar
513 image={`${process.env.NEXT_PUBLIC_NGINX_URL}/users/${userInfo?.avatar}`}
514 className="user-avatar"
515 shape="circle"
516 />
517 <div className="user-info">
518 <div className="user-detail-info">
519 <div className="name-container">
520 <h2 className="name">{userInfo?.username}</h2>
521 <span className="signature">{userInfo?.signature}</span>
522 </div>
523
524 <div className="stats-container">
525 <div className="stats">
526 <span className="stats-label">粉丝:</span>
527 <span className="stats-value">{userInfo?.followerCount}</span>
528 </div>
529 <div className="stats">
530 <span className="stats-label">累计上传量:</span>
531 <span className="stats-value">{formatCount(userData?.uploadAmount)}</span>
532 </div>
533 <div className="stats">
534 <span className="stats-label">关注:</span>
535 <span className="stats-value">{userInfo?.subscriberCount}</span>
536 </div>
537 <div className="stats">
538 <span className="stats-label">累计被下载量:</span>
539 <span className="stats-value">{formatCount(userData?.beDownloadedAmount)}</span>
540 </div>
541 </div>
542 </div>
543
LaoeGaocid0773912025-06-09 00:38:40 +0800544 <Button label="关注" className="action-button" />
lfmylbhf789e7172025-06-06 18:37:21 +0800545 </div>
546 </div>
547
548 {/*个人内容*/}
lfmylbhfa9fa5c82025-06-09 00:34:49 +0800549 <TabView activeIndex={activeIndex} onTabChange={(e) => setActiveIndex(e.index)}>
550 <TabPanel header="主页" >
lfmylbhf789e7172025-06-06 18:37:21 +0800551 {/*发布资源*/}
552 <div className="homepage-item">
553 <div className="section-header">
554 <h1>发布资源</h1>
555 <Button
556 label="显示更多"
557 link
558 onClick={() => router.push('/user/manage/resources/')}
559 />
560 </div>
561 <div className="resource-grid">
562 {resourceList.map((resourceList) => (
563 <Card key={resourceList.resourceId} className="resource-card" onClick={() => router.push(`/resource/resource-detail/${resourceList.resourceId}`)}>
564 <Image
565 src={process.env.NEXT_PUBLIC_NGINX_URL + resourceList.resourcePicture}
566 alt={resourceList.resourceName}
567 width="368"
568 height="200"
569 />
570 <div className="card-content">
571 <h3>{resourceList.resourceName}</h3>
572 <div className="view-count">
573 <Fire theme="outline" size="16" fill="#FF8D1A" />
574 <span>{resourceList.likes}</span>
575 </div>
576 </div>
577 </Card>
578 ))}
579 </div>
580 </div>
581
582 {/*发布帖子*/}
583 <div className="homepage-item">
584 <div className="section-header">
585 <h1>发布帖子</h1>
586 <Button
587 label="显示更多"
588 link
lfmylbhfa9fa5c82025-06-09 00:34:49 +0800589 onClick={() => setActiveIndex(1)}
lfmylbhf789e7172025-06-06 18:37:21 +0800590 />
591 </div>
592 <div className="resource-grid">
lfmylbhfa9fa5c82025-06-09 00:34:49 +0800593 {homePageThread?.records.map((homePageThread) => (
594 <Card key={homePageThread.threadId} className="resource-card" onClick={() => router.push(`/community/thread-detail/${homePageThread.threadId}`)}>
595 <Image
596 src={process.env.NEXT_PUBLIC_NGINX_URL + homePageThread.threadPicture}
597 alt={homePageThread.title}
598 width="368"
599 height="200"
600 />
601 <div className="card-content">
602 <h3>{homePageThread.title}</h3>
603 <div className="view-count">
604 <Fire theme="outline" size="16" fill="#FF8D1A" />
605 <span>{homePageThread.likes}</span>
606 </div>
607 </div>
608 </Card>
609 ))}
lfmylbhf789e7172025-06-06 18:37:21 +0800610 </div>
611 </div>
612 </TabPanel>
613
lfmylbhfa9fa5c82025-06-09 00:34:49 +0800614 {/*<TabPanel header="发布">*/}
lfmylbhf789e7172025-06-06 18:37:21 +0800615
lfmylbhfa9fa5c82025-06-09 00:34:49 +0800616 {/*</TabPanel>*/}
lfmylbhf789e7172025-06-06 18:37:21 +0800617
618 <TabPanel header="帖子">
lfmylbhfa9fa5c82025-06-09 00:34:49 +0800619 {/*我的帖子*/}
620 <div className="homepage-item">
621 <div className="section-header">
622 <h1>我的帖子</h1>
623 </div>
624 <div className="resource-grid">
625 {threadList.map((threadList) => (
626 <Card key={threadList.threadId} className="resource-card" onClick={() => router.push(`/community/thread-detail/${threadList.threadId}`)}>
627 <Image
628 src={process.env.NEXT_PUBLIC_NGINX_URL + threadList.threadPicture}
629 alt={threadList.title}
630 width="368"
631 height="200"
632 />
633 <div className="card-content">
634 <h3>{threadList.title}</h3>
635 <div className="view-count">
636 <Fire theme="outline" size="16" fill="#FF8D1A" />
637 <span>{threadList.likes}</span>
638 </div>
639 </div>
640 </Card>
641 ))}
642 </div>
643 {totalThreads > 6 && <Paginator
644 className="Paginator"
645 first={threadFirst}
646 rows={threadRows}
647 totalRecords={totalThreads}
648 rowsPerPageOptions={[6, 12]}
649 onPageChange={onThreadPageChange}
650 />}
651 </div>
lfmylbhf789e7172025-06-06 18:37:21 +0800652 </TabPanel>
653
654 <TabPanel header="收藏">
655
656 </TabPanel>
657
658 <TabPanel header="数据">
659
660 </TabPanel>
661
662 <TabPanel header="悬赏">
lfmylbhffc9518b2025-06-09 00:37:41 +0800663 <div className="section-header">
664 <h1>我的悬赏</h1>
665 </div>
666
lfmylbhfa9fa5c82025-06-09 00:34:49 +0800667 <div className="resource-list">
668 {rewardList.map((rewardItem) => (
669 <Card key={rewardItem.rewardId} className="resources-list-card"
670 onClick={() => router.push(`/reward/reward-detail/${rewardItem.rewardId}`)}>
671 <Image alt="avatar"
672 src={process.env.NEXT_PUBLIC_NGINX_URL + "rewards/" + rewardItem.rewardPicture}
673 className="resource-avatar" width="250" height="140"/>
674 <div className="resource-header">
675 <div className="resource-content">
676 <h3>{rewardItem.rewardName}</h3>
677 </div>
lfmylbhf789e7172025-06-06 18:37:21 +0800678
lfmylbhfa9fa5c82025-06-09 00:34:49 +0800679 <div className="resource-operation">
680 <Button
681 label="编辑"
682 onClick={(e) => {
683 e.stopPropagation(); // 关键修复:阻止事件冒泡,避免触发Card的点击事件
684 setEditVisible(true);
685
686 setEditRewardFormData({
687 rewardId: rewardItem.rewardId,
688 rewardName: rewardItem.rewardName,
689 price: rewardItem.price.toString(),
690 rewardDescription: rewardItem.rewardDescription,
691 });
692 console.log("用户编辑悬赏");
693 }}
694 />
695 <Button
696 label="删除"
697 onClick={(e) => {
698 e.stopPropagation(); // 关键修复:阻止事件冒泡,避免触发Card的点击事件
699 setDeleteResourceId(rewardItem.rewardId);
700 setDeleteVisible(true);
701 }}
702 style={{backgroundColor: "rgba(255, 87, 51, 1)"}}
703 />
704 </div>
705 </div>
706 </Card>
707 ))}
708
709 {totalRewards > 5 && <Paginator
710 className="Paginator"
711 first={rewardFirst}
712 rows={rewardRows}
713 totalRecords={totalRewards}
714 rowsPerPageOptions={[5, 10]}
715 onPageChange={onRewardPageChange}
716 />}
717 </div>
lfmylbhf789e7172025-06-06 18:37:21 +0800718 </TabPanel>
719 </TabView>
720
721 {/*浮动按钮*/}
722 <div className="card">
723 <SpeedDial
724 model={actions}
725 direction="up"
726 style={{ position: 'fixed', bottom: '2rem', right: '2rem' }}
727 showIcon="pi pi-plus"
728 hideIcon="pi pi-times"
729 buttonClassName="custom-speeddial-button"
730 />
731 </div>
732
lfmylbhf1783a092025-06-07 20:05:22 +0800733 {/*发布资源弹窗*/}
734 <Dialog
735 header="发布资源"
736 visible={visible}
737 onHide={() => setVisible(false)}
738 className="publish-dialog"
739 modal
740 footer={
741 <div className="dialog-footer">
742 <Button label="发布" icon="pi pi-check" onClick={handleSubmit} autoFocus />
743 <Button label="取消" icon="pi pi-times" onClick={() => setVisible(false)} className="p-button-text" />
744 </div>
745 }
746 >
747 <div className="publish-form">
748 <div className="form-field">
749 <div className="form-field-header">
750 <span className="form-field-sign">*</span>
751 <label htmlFor="name">资源名称</label>
752 </div>
753 <InputText
754 id="name"
755 value={resourceFormData.resource.resourceName}
756 onChange={(e) => setResourceFormData(prev => ({
757 ...prev, // 复制顶层所有属性
758 resource: {
759 ...prev.resource, // 复制resource对象的所有属性
760 resourceName: e.target.value // 只更新resourceName
761 }
762 }))}
763 placeholder="请输入资源名称"
764 className="w-full"
lfmylbhf1783a092025-06-07 20:05:22 +0800765 />
766 </div>
767 <div className="form-field">
768 <div className="form-field-header">
769 <span className="form-field-sign">*</span>
770 <label htmlFor="summary">资源简介</label>
771 </div>
772 <InputText
773 id="summary"
774 value={resourceFormData.resource.resourceSummary}
775 onChange={(e) => setResourceFormData(prev => ({
776 ...prev, // 复制顶层所有属性
777 resource: {
778 ...prev.resource, // 复制resource对象的所有属性
779 resourceSummary: e.target.value
780 }
781 }))}
782 placeholder="请输入资源简介(一句话)"
783 className="w-full"
lfmylbhf1783a092025-06-07 20:05:22 +0800784 />
785 </div>
786 <div className="form-field">
787 <div className="form-field-header">
788 <label htmlFor="detail">资源介绍</label>
789 </div>
790 <InputTextarea
791 id="detail"
792 value={resourceFormData.resource.resourceDetail}
793 onChange={(e) => setResourceFormData(prev => ({
794 ...prev, // 复制顶层所有属性
795 resource: {
796 ...prev.resource, // 复制resource对象的所有属性
797 resourceDetail: e.target.value
798 }
799 }))}
800 rows={5}
801 placeholder="请输入资源介绍"
802 className="w-full"
lfmylbhf1783a092025-06-07 20:05:22 +0800803 />
804 </div>
805 <div className="form-field">
806 <div className="form-field-header">
807 <span className="form-field-sign">*</span>
808 <label htmlFor="price">价格</label>
809 </div>
810 <InputText
811 id="price"
812 value={resourceFormData.resource.price}
813 onChange={(e) => setResourceFormData(prev => ({
814 ...prev, // 复制顶层所有属性
815 resource: {
816 ...prev.resource, // 复制resource对象的所有属性
817 price: e.target.value
818 }
819 }))}
820 placeholder="请输入资源价格"
821 className="w-full"
822 />
823 </div>
824 <div className="form-field">
825 <div className="form-field-header">
826 <span className="form-field-sign">*</span>
827 <label htmlFor="classify">资源分类(请选择一项)</label>
828 </div>
829 <div className="form-field-classify">
830 <div className="flex align-items-center">
831 <RadioButton
832 inputId="ingredient1"
833 name="pizza"
834 value="resourcePack"
835 onChange={(e: RadioButtonChangeEvent) => {
836 setResourceFormData(prev => ({
837 ...prev, // 复制顶层所有属性
838 resource: {
839 ...prev.resource, // 复制resource对象的所有属性
840 classify: e.target.value
841 }
842 }));
843 setIngredient(e.value);
844 console.log(ingredient);
845 // console.log(resourceFormData.resource.classify);
846 }}
847 checked={ingredient === 'resourcePack'}
848 />
849 <label htmlFor="ingredient1" className="ml-2">材质包</label>
850 </div>
851 <div className="flex align-items-center">
852 <RadioButton
853 inputId="ingredient2"
854 name="pizza"
855 value="modPack"
856 onChange={(e: RadioButtonChangeEvent) => {
857 setResourceFormData(prev => ({
858 ...prev, // 复制顶层所有属性
859 resource: {
860 ...prev.resource, // 复制resource对象的所有属性
861 classify: e.target.value
862 }
863 }));
864 setIngredient(e.value);
865 }}
866 // onChange={(e: RadioButtonChangeEvent) => setIngredient(e.value)}
867 checked={ingredient === 'modPack'}
868 />
869 <label htmlFor="ingredient2" className="ml-2">整合包</label>
870 </div>
871 <div className="flex align-items-center">
872 <RadioButton
873 inputId="ingredient3"
874 name="pizza"
875 value="mod"
876 onChange={(e: RadioButtonChangeEvent) => {
877 setResourceFormData(prev => ({
878 ...prev, // 复制顶层所有属性
879 resource: {
880 ...prev.resource, // 复制resource对象的所有属性
881 classify: e.target.value
882 }
883 }));
884 setIngredient(e.value);
885 }}
886 // onChange={(e: RadioButtonChangeEvent) => setIngredient(e.value)}
887 checked={ingredient === 'mod'}
888 />
889 <label htmlFor="ingredient3" className="ml-2">模组</label>
890 </div>
891 <div className="flex align-items-center">
892 <RadioButton
893 inputId="ingredient4"
894 name="pizza"
895 value="map"
896 onChange={(e: RadioButtonChangeEvent) => {
897 setResourceFormData(prev => ({
898 ...prev, // 复制顶层所有属性
899 resource: {
900 ...prev.resource, // 复制resource对象的所有属性
901 classify: e.target.value
902 }
903 }));
904 setIngredient(e.value);
905 }}
906 // onChange={(e: RadioButtonChangeEvent) => setIngredient(e.value)}
907 checked={ingredient === 'map'}
908 />
909 <label htmlFor="ingredient4" className="ml-2">地图</label>
910 </div>
911 </div>
912 </div>
913 <div className="form-field">
914 <div className="form-field-header">
915 <span className="form-field-sign">*</span>
916 <label htmlFor="gameplayList">资源标签</label>
917 </div>
918 <MultiSelect
919 value={selectedGameplay}
920 onChange={(e: MultiSelectChangeEvent) => {
921 const selectedOptions = e.value as GameplayOption[];
922 // 提取选中项的 name 属性组成字符串数组
923 const selectedNames = selectedOptions.map(item => item.name);
924
925 setResourceFormData(prev => ({
926 ...prev,
927 gameplayList: selectedNames
928 }));
929 setSelectedGameplay(selectedOptions);
930 }}
931 options={gameplayOptions}
932 display="chip"
933 optionLabel="name"
934 placeholder="请选择资源标签"
935 // maxSelectedLabels={3}
936 className="w-full md:w-20rem"
937 />
938 </div>
939 <div className="form-field">
940 <div className="form-field-header">
941 <span className="form-field-sign">*</span>
942 <label>封面图片</label>
943 </div>
944 <FileUpload
lfmylbhfa9fa5c82025-06-09 00:34:49 +0800945 mode="advanced"
lfmylbhf1783a092025-06-07 20:05:22 +0800946 name="resource-image"
lfmylbhfa9fa5c82025-06-09 00:34:49 +0800947 customUpload
948 uploadHandler={async (e) => {
949 const formData = new FormData();
950 formData.append("file", e.files[0]);
951
952 try {
953 const res = await axios.post(`${process.env.PUBLIC_URL}/file`, formData);
954
955 const fileUrl = res.data.url;
956 console.log(fileUrl);
957 setResourcePictureUrl(fileUrl);
958 toast.current?.show({ severity: 'success', summary: '上传成功' });
959 } catch (error) {
960 console.log(error);
961 toast.current?.show({ severity: 'error', summary: '上传失败' });
962 }
963 }}
964 auto
lfmylbhf1783a092025-06-07 20:05:22 +0800965 accept="image/*"
lfmylbhfa9fa5c82025-06-09 00:34:49 +0800966 chooseLabel="上传资源封面"
967
968 // accept="image/*"
969 // maxFileSize={10000000000}
970 // chooseLabel="选择资源封面"
971 // className="w-full"
972 // onUpload={onUpload}
lfmylbhf1783a092025-06-07 20:05:22 +0800973 />
974 </div>
975 </div>
976 </Dialog>
977
lfmylbhfa9fa5c82025-06-09 00:34:49 +0800978 {/*删除悬赏弹窗*/}
979 <Dialog
980 header="删除悬赏"
981 visible={deleteVisible}
982 onHide={() => setDeleteVisible(false)}
983 className="resource-delete-dialog"
984 modal
985 footer={
986 <div className="dialog-footer">
987 <Button label="确认" icon="pi pi-check" onClick={handleDeleteSubmit} autoFocus />
988 <Button label="取消" icon="pi pi-times" onClick={() => setDeleteVisible(false)} className="p-button-text" />
989 </div>
990 }
991 >
992 <div className="dialog-form">
993 <span>
994 确认是否删除该悬赏?
995 </span>
996 {/*<div className="form-field">*/}
997 {/* <div className="form-field-header">*/}
998 {/* <label htmlFor="name">密码</label>*/}
999 {/* </div>*/}
1000 {/* <Password*/}
1001 {/* id="passwrod"*/}
1002 {/* value={deleteResourceFormData.password}*/}
1003 {/* onChange={(e) => setDeleteResourceFormData(prev => ({*/}
1004 {/* ...prev,*/}
1005 {/* password: e.target.value*/}
1006 {/* }))}*/}
1007 {/* placeholder="请输入密码"*/}
1008 {/* className="w-full"*/}
1009 {/* toggleMask*/}
1010 {/* />*/}
1011 {/*</div>*/}
1012 </div>
1013 </Dialog>
1014
1015 {/*编辑资源弹窗*/}
1016 <Dialog
1017 header="编辑资源"
1018 visible={editVisible}
1019 onHide={() => setEditVisible(false)}
1020 className="resource-edit-dialog"
1021 modal
1022 footer={
1023 <div className="dialog-footer">
1024 <Button label="确认" icon="pi pi-check" onClick={handleEditSubmit} autoFocus />
1025 <Button label="取消" icon="pi pi-times" onClick={() => setEditVisible(false)} className="p-button-text" />
1026 </div>
1027 }
1028 >
1029 <div className="dialog-form">
1030 <div className="form-field">
1031 <div className="form-field-header">
1032 <label htmlFor="name">更改标题</label>
1033 </div>
1034 <InputText
1035 id="name"
1036 value={editRewardFormData.rewardName}
1037 onChange={(e) => setEditRewardFormData(prev => ({
1038 ...prev,
1039 rewardName: e.target.value
1040 }))}
1041 className="w-full"
1042 />
1043 </div>
1044 <div className="form-field">
1045 <div className="form-field-header">
lfmylbhffc9518b2025-06-09 00:37:41 +08001046 <label htmlFor="price">更改定价</label>
lfmylbhfa9fa5c82025-06-09 00:34:49 +08001047 </div>
1048 <InputText
lfmylbhffc9518b2025-06-09 00:37:41 +08001049 id="price"
lfmylbhfa9fa5c82025-06-09 00:34:49 +08001050 value={editRewardFormData.price}
1051 onChange={(e) => setEditRewardFormData(prev => ({
1052 ...prev,
1053 price: e.target.value
1054 }))}
1055 className="w-full"
1056 />
1057 </div>
1058 <div className="form-field">
1059 <div className="form-field-header">
lfmylbhffc9518b2025-06-09 00:37:41 +08001060 <label htmlFor="description">更改需求</label>
lfmylbhfa9fa5c82025-06-09 00:34:49 +08001061 </div>
1062 <InputTextarea
lfmylbhffc9518b2025-06-09 00:37:41 +08001063 id="description"
lfmylbhfa9fa5c82025-06-09 00:34:49 +08001064 value={editRewardFormData.rewardDescription}
1065 onChange={(e) => setEditRewardFormData(prev => ({
1066 ...prev,
1067 rewardDescription: e.target.value
1068 }))}
1069 rows={5}
1070 className="w-full"
1071 />
1072 </div>
1073 <div className="form-field">
1074 <div className="form-field-header">
1075 <span className="form-field-sign">*</span>
1076 <label>封面图片</label>
1077 </div>
1078 <FileUpload
1079 mode="advanced"
lfmylbhffc9518b2025-06-09 00:37:41 +08001080 name="reward-image"
lfmylbhfa9fa5c82025-06-09 00:34:49 +08001081 customUpload
1082 uploadHandler={async (e) => {
1083 const formData = new FormData();
1084 formData.append("file", e.files[0]);
1085
1086 try {
1087 const res = await axios.post(`${process.env.PUBLIC_URL}/file`, formData);
1088
1089 const fileUrl = res.data.url;
1090 console.log(fileUrl);
1091 setRewardPictureUrl(fileUrl);
1092 toast.current?.show({ severity: 'success', summary: '上传成功' });
1093 } catch (error) {
1094 console.log(error);
1095 toast.current?.show({ severity: 'error', summary: '上传失败' });
1096 }
1097 }}
1098 auto
1099 accept="image/*"
1100 chooseLabel="选择悬赏封面"
1101 />
1102 </div>
1103 </div>
1104 </Dialog>
lfmylbhf789e7172025-06-06 18:37:21 +08001105 </div>
1106 );
1107};
1108