blob: 77d11a6fdf3c746611cc5b011083cdb0df681a9e [file] [log] [blame]
Seamher11a11b32025-06-09 23:11:58 +08001"use client";
LaoeGaoci388f7762025-05-29 22:24:35 +08002
3import React, { useEffect, useState, useRef } from "react";
Seamher11a11b32025-06-09 23:11:58 +08004import { InputText } from "primereact/inputtext";
5import { Button } from "primereact/button";
6import { Card } from "primereact/card";
7import { Image } from "primereact/image";
LaoeGaoci388f7762025-05-29 22:24:35 +08008// 页面跳转
Seamher11a11b32025-06-09 23:11:58 +08009import { useRouter } from "next/navigation";
LaoeGaoci388f7762025-05-29 22:24:35 +080010// 分页
Seamher11a11b32025-06-09 23:11:58 +080011import { Paginator, type PaginatorPageChangeEvent } from "primereact/paginator";
LaoeGaoci388f7762025-05-29 22:24:35 +080012// 消息提醒
Seamher11a11b32025-06-09 23:11:58 +080013import { Toast } from "primereact/toast";
LaoeGaoci388f7762025-05-29 22:24:35 +080014// 发布帖子
Seamher11a11b32025-06-09 23:11:58 +080015import { Dialog } from "primereact/dialog";
16import { FileUpload } from "primereact/fileupload";
17import { InputTextarea } from "primereact/inputtextarea";
LaoeGaoci388f7762025-05-29 22:24:35 +080018// 接口传输
Seamher11a11b32025-06-09 23:11:58 +080019import axios from "axios";
LaoeGaoci388f7762025-05-29 22:24:35 +080020// 防抖函数
Seamher11a11b32025-06-09 23:11:58 +080021import { debounce } from "lodash";
22import { TabView, TabPanel } from "primereact/tabview";
23import { useLocalStorage } from "../hook/useLocalStorage";
LaoeGaoci388f7762025-05-29 22:24:35 +080024// 样式
Seamher11a11b32025-06-09 23:11:58 +080025import "./reward.scss";
LaoeGaocid0773912025-06-09 00:38:40 +080026interface User {
Seamher11a11b32025-06-09 23:11:58 +080027 Id: number;
LaoeGaocid0773912025-06-09 00:38:40 +080028}
LaoeGaoci388f7762025-05-29 22:24:35 +080029// 悬赏列表数据
30interface Reward {
Seamher11a11b32025-06-09 23:11:58 +080031 rewardId: number;
32 userId: number;
33 rewardPicture: string;
34 rewardName: string;
35 createAt: string;
36 rewardDescription: string;
37 price: number;
LaoeGaoci388f7762025-05-29 22:24:35 +080038}
39interface RewardList {
Seamher11a11b32025-06-09 23:11:58 +080040 total: number; // 总记录数
41 records: Reward[];
LaoeGaoci388f7762025-05-29 22:24:35 +080042}
43
LaoeGaoci388f7762025-05-29 22:24:35 +080044// 社区详情页面
45export default function RewardDetailPage() {
Seamher11a11b32025-06-09 23:11:58 +080046 const user = useLocalStorage<User>("user");
47 const userId: number = user?.Id ?? -1;
48 // 页面跳转
49 const router = useRouter();
50 // 帖子列表数据
51 const [rewards, setRewards] = useState<Reward[]>([]);
52 const [totalRewards, setTotalRewards] = useState<number>(0);
53 const [activeOption, setActiveOption] = useState<number>(0); // 0表示"赏金最高",1表示"最新发布"
54 const options = ["赏金最高", "最新发布"];
55 // 搜索框
56 const [searchValue, setSearchValue] = useState("");
57 const debouncedSearch = useRef(
58 debounce((value: string) => {
59 setSearchValue(value);
60 }, 600)
61 ).current;
62 // 消息提醒
63 const toast = useRef<Toast>(null);
64 // 分页
65 const [first, setFirst] = useState(0);
66 const [rows, setRows] = useState(5);
67 const onPageChange = (event: PaginatorPageChangeEvent) => {
68 setFirst(event.first);
69 setRows(event.rows);
70 };
LaoeGaoci388f7762025-05-29 22:24:35 +080071
Seamher11a11b32025-06-09 23:11:58 +080072 // 获取悬赏列表
73 useEffect(() => {
74 fetchRewards();
75 }, [first, rows, searchValue, activeOption]);
76
77 const fetchRewards = async () => {
78 try {
79 const pageNumber = first / rows + 1;
80 console.log(
81 "当前页" +
82 pageNumber +
83 "size" +
84 rows +
85 "搜索内容" +
86 searchValue +
87 "排序方式" +
88 activeOption
89 );
90 const response = await axios.get<RewardList>(
91 process.env.PUBLIC_URL + `/reward`,
92 {
93 params: {
94 pageNumber,
95 rows,
96 searchValue,
97 option: options[activeOption],
98 },
99 }
100 );
101 console.log("获取悬赏列表:", response.data.records);
102 setRewards(response.data.records);
103 setTotalRewards(response.data.total); // 假设返回的总数
104 } catch (err) {
105 console.error("获取悬赏失败", err);
106 toast.current?.show({
107 severity: "error",
108 summary: "error",
109 detail: "获取悬赏失败",
110 });
111 }
112 };
113
114 // 发布悬赏弹窗
115 const [visible, setVisible] = useState(false);
116 const [formData, setFormData] = useState({
117 rewardName: "",
118 rewardDescription: "",
119 rewardPicture: "",
120 price: "",
121 });
122
123 // 发布悬赏接口
124 const handleSubmit = async () => {
125 try {
126 const currentDate = new Date().toISOString();
127 const postData = {
128 userId, // 记得用户登录状态获取
129 rewardPicture: formData.rewardPicture,
130 rewardName: formData.rewardName,
131 rewardDescription: formData.rewardDescription,
132 createAt: currentDate.slice(0, 10),
133 lastUpdateAt: currentDate.slice(0, 10),
134 price: Number(formData.price),
135 };
136 // 发送POST请求
137 const response = await axios.post(
138 process.env.PUBLIC_URL + "/reward",
139 postData
140 );
141
142 if (response.status === 200) {
143 toast.current?.show({
144 severity: "success",
145 summary: "Success",
146 detail: "悬赏发布成功",
147 });
148 // 发帖成功
149 setVisible(false);
150 // 重置表单
151 setFormData({
152 rewardName: "",
153 rewardDescription: "",
154 rewardPicture: "",
155 price: "",
156 });
157 // 可以刷新帖子列表
LaoeGaoci388f7762025-05-29 22:24:35 +0800158 fetchRewards();
Seamher11a11b32025-06-09 23:11:58 +0800159 }
160 } catch (error) {
161 console.error("发布悬赏失败:", error);
162 toast.current?.show({
163 severity: "error",
164 summary: "error",
165 detail: "悬赏发布失败",
166 });
167 }
168 };
169 return (
170 <div className="reward">
171 <Toast ref={toast}></Toast>
172 {/* 悬赏标题和介绍 */}
173 <div className="reward-header">
174 <div className="title-section">
175 <h1>悬赏排行</h1>
LaoeGaoci388f7762025-05-29 22:24:35 +0800176 </div>
Seamher11a11b32025-06-09 23:11:58 +0800177 <div className="input">
178 <div className="searchBar">
179 <i className="pi pi-search" />
180 <InputText
181 type="search"
182 className="search-helper"
183 placeholder="搜索你的目标悬赏"
184 onChange={(e) => {
185 const target = e.target as HTMLInputElement;
186 debouncedSearch(target.value);
187 }}
188 />
189 </div>
190 <div className="reward-buttons">
191 <Button
192 label="我的悬赏"
193 onClick={() => router.push(`/user/悬赏`)}
194 />
195 <Button label="发布悬赏" onClick={() => setVisible(true)} />
196 </div>
197 </div>
198 </div>
199
200 {/* 悬赏列表 */}
201 <TabView
202 activeIndex={activeOption}
203 onTabChange={(e) => setActiveOption(e.index)}
204 >
205 <TabPanel header={options[0]}>
206 <div className="rewards-list">
207 {rewards.map((reward) => (
208 <Card
209 key={reward.rewardId}
210 className="rewards-list-card"
211 onClick={() =>
212 router.push(`/reward/reward-detail/${reward.rewardId}`)
213 }
214 >
215 <Image
216 alt="avatar"
217 src={reward.rewardPicture}
218 className="reward-avatar"
219 width="250"
220 height="140"
221 />
222 <div className="reward-header">
223 <div className="reward-content">
224 <h3>{reward.rewardName}</h3>
225 </div>
226 <div className="reward-states">
227 <span className="price">$: {reward.price}</span>
228 <Button label="提交悬赏" />
229 </div>
230 </div>
231 </Card>
232 ))}
233 {totalRewards > 5 && (
234 <Paginator
235 className="Paginator"
236 first={first}
237 rows={rows}
238 totalRecords={totalRewards}
239 rowsPerPageOptions={[5, 10]}
240 onPageChange={onPageChange}
241 />
242 )}
243 </div>
244 </TabPanel>
245 <TabPanel header={options[1]}>
246 <div className="rewards-list">
247 {rewards.map((reward) => (
248 <Card
249 key={reward.rewardId}
250 className="rewards-list-card"
251 onClick={() =>
252 router.push(`/reward/reward-detail/${reward.rewardId}`)
253 }
254 >
255 <Image
256 alt="avatar"
257 src={reward.rewardPicture}
258 className="reward-avatar"
259 width="250"
260 height="140"
261 />
262 <div className="reward-header">
263 <div className="reward-content">
264 <h3>{reward.rewardName}</h3>
265 </div>
266 <div className="reward-states">
267 <span className="price">$: {reward.price}</span>
268 <Button label="提交悬赏" />
269 </div>
270 </div>
271 </Card>
272 ))}
273 {totalRewards > 5 && (
274 <Paginator
275 className="Paginator"
276 first={first}
277 rows={rows}
278 totalRecords={totalRewards}
279 rowsPerPageOptions={[5, 10]}
280 onPageChange={onPageChange}
281 />
282 )}
283 </div>
284 </TabPanel>
285 </TabView>
286 {/* 发布悬赏弹窗 */}
287 <Dialog
288 header="发布新悬赏"
289 visible={visible}
290 onHide={() => setVisible(false)}
291 className="publish-dialog"
292 modal
293 footer={
294 <div className="dialog-footer">
295 <Button
296 label="发布"
297 icon="pi pi-check"
298 onClick={handleSubmit}
299 autoFocus
300 />
301 <Button
302 label="取消"
303 icon="pi pi-times"
304 onClick={() => setVisible(false)}
305 className="p-button-text"
306 />
307 </div>
308 }
309 >
310 <div className="publish-form">
311 <div className="form-field">
312 <label htmlFor="title">标题</label>
313 <InputText
314 id="title"
315 value={formData.rewardName}
316 onChange={(e) =>
317 setFormData((prev) => ({ ...prev, rewardName: e.target.value }))
318 }
319 placeholder="请输入悬赏标题"
320 className="w-full"
321 />
322 </div>
323
324 <div className="form-field">
325 <label htmlFor="content">内容</label>
326 <InputTextarea
327 id="content"
328 value={formData.rewardDescription}
329 onChange={(e) =>
330 setFormData((prev) => ({
331 ...prev,
332 rewardDescription: e.target.value,
333 }))
334 }
335 rows={5}
336 placeholder="请输入悬赏需求"
337 className="w-full"
338 />
339 </div>
340 <div className="form-field">
341 <label htmlFor="price">赏金</label>
342 <InputText
343 id="price"
344 value={formData.price}
345 onChange={(e) =>
346 setFormData((prev) => ({ ...prev, price: e.target.value }))
347 }
348 placeholder="请输入赏金金额"
349 className="w-full"
350 />
351 </div>
352 <div className="form-field">
353 <label>封面图片</label>
354 <FileUpload
355 mode="advanced"
356 name="file"
357 customUpload
358 uploadHandler={async (e) => {
359 const formData = new FormData();
360 formData.append("file", e.files[0]);
361
362 try {
363 const res = await axios.post(
364 `${process.env.PUBLIC_URL}/file`,
365 formData
366 );
367
368 const fileUrl = res.data;
369 console.log(fileUrl);
370 setFormData((prev) => ({ ...prev, rewardPicture: fileUrl }));
371 toast.current?.show({
372 severity: "success",
373 summary: "上传成功",
374 });
375 } catch (error) {
376 console.log(error);
377 toast.current?.show({
378 severity: "error",
379 summary: "上传失败",
380 });
381 }
382 }}
383 auto
384 accept="image/*"
385 chooseLabel="上传帖子封面"
386 />
387 </div>
388 </div>
389 </Dialog>
390 </div>
391 );
392}