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