blob: b53d93c7313e8fc6a72870f57aca09e3ff5b9284 [file] [log] [blame]
LaoeGaoci388f7762025-05-29 22:24:35 +08001'use client';
2
3import React, { useEffect, useState, useRef } from "react";
4import { InputText } from 'primereact/inputtext';
5import { Button } from 'primereact/button';
6import { Card } from 'primereact/card';
7import { Image } from 'primereact/image';
8// 页面跳转
9import { useRouter } from 'next/navigation';
10// 分页
11import { Paginator, type PaginatorPageChangeEvent } from 'primereact/paginator';
12// 消息提醒
13import { Toast } from 'primereact/toast';
14// 发布帖子
15import { Dialog } from 'primereact/dialog';
16import { FileUpload } from 'primereact/fileupload';
17import { InputTextarea } from 'primereact/inputtextarea';
18// 接口传输
19import axios from 'axios';
20// 防抖函数
21import { debounce } from 'lodash';
22import { TabView, TabPanel } from 'primereact/tabview';
LaoeGaocid0773912025-06-09 00:38:40 +080023import { useLocalStorage } from '../hook/useLocalStorage';
LaoeGaoci388f7762025-05-29 22:24:35 +080024// 样式
25import './reward.scss';
LaoeGaocid0773912025-06-09 00:38:40 +080026interface User {
27 Id: number;
28}
LaoeGaoci388f7762025-05-29 22:24:35 +080029// 悬赏列表数据
30interface Reward {
31 rewardId: number;
32 userId: number;
33 rewardPicture: string;
34 rewardName: string;
35 createAt: string;
36 rewardDescription: string;
37 price: number;
38}
39interface RewardList {
40 total: number; // 总记录数
41 records: Reward[];
42}
43
44
45// 社区详情页面
46export default function RewardDetailPage() {
LaoeGaocid0773912025-06-09 00:38:40 +080047 const user = useLocalStorage<User>('user');
48 const userId: number = user?.Id ?? -1;
LaoeGaoci388f7762025-05-29 22:24:35 +080049 // 页面跳转
50 const router = useRouter();
51 // 帖子列表数据
52 const [rewards, setRewards] = useState<Reward[]>([]);
53 const [totalRewards, setTotalRewards] = useState<number>(0);
54 const [activeOption, setActiveOption] = useState<number>(0); // 0表示"赏金最高",1表示"最新发布"
55 const options = ["赏金最高", "最新发布"];
56 // 搜索框
57 const [searchValue, setSearchValue] = useState("");
58 const debouncedSearch = useRef(
59 debounce((value: string) => {
60 setSearchValue(value);
61 }, 600)
62 ).current;
63 // 消息提醒
64 const toast = useRef<Toast>(null);
65 // 分页
66 const [first, setFirst] = useState(0);
67 const [rows, setRows] = useState(5);
68 const onPageChange = (event: PaginatorPageChangeEvent) => {
69 setFirst(event.first);
70 setRows(event.rows);
71 };
72
73 // 获取悬赏列表
74 useEffect(() => {
75 fetchRewards();
76 }, [first, rows, searchValue, activeOption]);
77
78 const fetchRewards = async () => {
79 try {
80 const pageNumber = first / rows + 1;
81 console.log("当前页" + pageNumber + "size" + rows + "搜索内容" + searchValue + "排序方式" + activeOption);
82 const response = await axios.get<RewardList>(
83 process.env.PUBLIC_URL + `/reward`, {
84 params: { pageNumber, rows, searchValue, option: options[activeOption] }
85 }
86 );
87 console.log('获取悬赏列表:', response.data.records);
88 setRewards(response.data.records);
89 setTotalRewards(response.data.total); // 假设返回的总数
90 } catch (err) {
91 console.error('获取悬赏失败', err);
92 toast.current?.show({ severity: 'error', summary: 'error', detail: '获取悬赏失败' });
93 }
94 };
95
96 // 发布悬赏弹窗
97 const [visible, setVisible] = useState(false);
98 const [formData, setFormData] = useState({
99 rewardName: '',
100 rewardDescription: '',
101 rewardPicture: '',
102 price: ''
103 });
104 // 图片上传消息通知
105 const onUpload = () => {
106 toast.current?.show({ severity: 'info', summary: 'Success', detail: 'File Uploaded' });
107 };
108
109 // 发布悬赏接口
110 const handleSubmit = async () => {
111 try {
112 const currentDate = new Date().toISOString();
113 const postData = {
LaoeGaocid0773912025-06-09 00:38:40 +0800114 userId, // 记得用户登录状态获取
LaoeGaoci388f7762025-05-29 22:24:35 +0800115 rewardPicture: formData.rewardPicture,
116 rewardName: formData.rewardName,
117 rewardDescription: formData.rewardDescription,
118 createdAt: currentDate,
119 price: formData.price
120 };
121 // 发送POST请求
122 const response = await axios.post(process.env.PUBLIC_URL + '/reward', postData);
123
124 if (response.status === 200) {
125 toast.current?.show({ severity: 'success', summary: 'Success', detail: '悬赏发布成功' });
126 // 发帖成功
127 setVisible(false);
128 // 重置表单
129 setFormData({
130 rewardName: '',
131 rewardDescription: '',
132 rewardPicture: '',
133 price: ''
134 });
135 // 可以刷新帖子列表
136 fetchRewards();
137 }
138 } catch (error) {
139 console.error('发布悬赏失败:', error);
140 toast.current?.show({ severity: 'error', summary: 'error', detail: '悬赏发布失败' });
141 }
142 };
143 return (
144 <div className="reward">
145 <Toast ref={toast}></Toast>
146 {/* 悬赏标题和介绍 */}
147 <div className="reward-header">
148 <div className="title-section">
149 <h1>悬赏排行</h1>
150 </div>
151 <div className="input">
152 <div className="searchBar">
153 <i className="pi pi-search" />
154 <InputText type="search" className="search-helper" placeholder="搜索你的目标悬赏" onChange={(e) => { const target = e.target as HTMLInputElement; debouncedSearch(target.value); }} />
155 </div>
156 <div className="reward-buttons">
157 <Button label="我的悬赏" onClick={() => router.push(`/user/悬赏`)} />
158 <Button label="发布悬赏" onClick={() => setVisible(true)} />
159 </div>
160 </div>
161 </div>
162
163 {/* 悬赏列表 */}
164 <TabView activeIndex={activeOption} onTabChange={(e) => setActiveOption(e.index)}>
165 <TabPanel header={options[0]} >
166 <div className="rewards-list">
167 {rewards.map((reward) => (
168 <Card key={reward.rewardId} className="rewards-list-card" onClick={() => router.push(`/reward/reward-detail/${reward.rewardId}`)}>
169 <Image alt="avatar" src={process.env.NEXT_PUBLIC_NGINX_URL + "rewards/" + reward.rewardPicture} className="reward-avatar" width="250" height="140" />
170 <div className="reward-header">
171 <div className="reward-content">
172 <h3>{reward.rewardName}</h3>
173 </div>
174 <div className="reward-states">
175 <span className="price">$: {reward.price}</span>
176 <Button label="提交悬赏" />
177 </div>
178 </div>
179 </Card>
180 ))}
181 {totalRewards > 5 && <Paginator className="Paginator" first={first} rows={rows} totalRecords={totalRewards} rowsPerPageOptions={[5, 10]} onPageChange={onPageChange} />}
182 </div>
183 </TabPanel>
184 <TabPanel header={options[1]}>
185 <div className="rewards-list">
186 {rewards.map((reward) => (
187 <Card key={reward.rewardId} className="rewards-list-card" onClick={() => router.push(`/reward/reward-detail/${reward.rewardId}`)}>
188 <Image alt="avatar" src={process.env.NEXT_PUBLIC_NGINX_URL + reward.rewardPicture} className="reward-avatar" width="250" height="140" />
189 <div className="reward-header">
190 <div className="reward-content">
191 <h3>{reward.rewardName}</h3>
192 </div>
193 <div className="reward-states">
194 <span className="price">$: {reward.price}</span>
195 <Button label="提交悬赏" />
196 </div>
197 </div>
198 </Card>
199 ))}
200 {totalRewards > 5 && <Paginator className="Paginator" first={first} rows={rows} totalRecords={totalRewards} rowsPerPageOptions={[5, 10]} onPageChange={onPageChange} />}
201 </div>
202 </TabPanel>
203 </TabView>
204 {/* 发布悬赏弹窗 */}
205 <Dialog
206 header="发布新悬赏"
207 visible={visible}
208 onHide={() => setVisible(false)}
209 className="publish-dialog"
210 modal
211 footer={
212 <div className="dialog-footer">
213 <Button label="发布" icon="pi pi-check" onClick={handleSubmit} autoFocus />
214 <Button label="取消" icon="pi pi-times" onClick={() => setVisible(false)} className="p-button-text" />
215 </div>
216 }
217 >
218 <div className="publish-form">
219 <div className="form-field">
220 <label htmlFor="title">标题</label>
221 <InputText
222 id="title"
223 value={formData.rewardName}
224 onChange={(e) => setFormData(prev => ({ ...prev, title: e.target.value }))}
225 placeholder="请输入悬赏标题"
226 className="w-full"
227 />
228 </div>
229
230 <div className="form-field">
231 <label htmlFor="content">内容</label>
232 <InputTextarea
233 id="content"
234 value={formData.rewardDescription}
235 onChange={(e) => setFormData(prev => ({ ...prev, content: e.target.value }))}
236 rows={5}
237 placeholder="请输入悬赏需求"
238 className="w-full"
239 />
240 </div>
241 <div className="form-field">
242 <label htmlFor="price">赏金</label>
243 <InputText
244 id="price"
245 value={formData.price}
246 onChange={(e) => setFormData(prev => ({ ...prev, price: e.target.value }))}
247 placeholder="请输入赏金金额"
248 className="w-full"
249 />
250 </div>
251 <div className="form-field">
252 <label>封面图片</label>
253 <FileUpload
254 mode="basic"
255 name="thread-image"
LaoeGaocid0773912025-06-09 00:38:40 +0800256 url={process.env.PUBLIC_URL + "/file"} // 与后端交互的URL
LaoeGaoci388f7762025-05-29 22:24:35 +0800257 accept="image/*"
258 maxFileSize={10000000000}
259 chooseLabel="选择悬赏封面"
260 className="w-full"
261 onUpload={onUpload}
262 />
263 </div>
264 </div>
265 </Dialog>
266 </div>
267 );
268}