blob: 78743da78d8102f52a7c658c18872d7e6a7b50a9 [file] [log] [blame]
崔向南464e19d2025-06-05 17:46:27 +08001import React, { useState, useEffect } from 'react';
2import { Button, Descriptions, List, message, Tag } from 'antd';
3import axios from 'axios'; // 新增 axios 导入
4import { useParams } from 'umi';
5
6import { getBountyDetail, getProfile } from '@/services/bounty/bounty';
7import { downloadAttachment,adoptSubmission } from '@/services/bounty/bounty';
8
9
10
11const BountyDetail: React.FC = () => {
12 const [bounty, setBounty] = useState<API.BountyDetail>({} as API.BountyDetail);
13 const [loading, setLoading] = useState(false); // 新增加载状态
14 const [currentUserId, setCurrentUserId] = useState<number | null>(null);
15
16 console.log('当前用户id:',currentUserId);
17 console.log('悬赏发布用户id:',bounty.creator_id);
18
19 const handleAdoptSubmission = async (submissionId: number, currentStatus: number) => {
20 console.log('【采纳请求】', {
21 submissionId,
22 currentStatus,
23 bounty: bounty,
24 currentUserId
25 });
26
27 if (currentStatus === 1) return;
28
29 try {
30 const res = await adoptSubmission(submissionId);
31 console.log('【采纳响应】', {
32 status: res.status,
33 data: res.data,
34 headers: res.headers
35 });
36
37 if (res.code === 200) {
38 message.success('采纳成功');
zhaoyumaof8f81842025-06-09 00:00:46 +080039 setBounty((prev: API.BountyDetail) => {
崔向南464e19d2025-06-05 17:46:27 +080040 console.log('【状态更新】', {
41 old: prev.submissions,
42 new: prev.submissions?.map(sub =>
43 sub.id === submissionId ? { ...sub, status: 1 } : sub
44 )
45 });
46
47 return {
48 ...prev,
49 submissions: prev.submissions?.map(sub =>
50 sub.id === submissionId ? { ...sub, status: 1 } : sub
51 )
zhaoyumaof8f81842025-06-09 00:00:46 +080052 } as API.BountyDetail;
崔向南464e19d2025-06-05 17:46:27 +080053 });
54 } else {
55 message.error(`采纳失败: ${res.msg}`);
56 }
57 } catch (error: any) {
58 console.error('【采纳错误】', {
59 error: error,
60 response: error.response?.data,
61 status: error.response?.status
62 });
63
64 message.error(`网络异常: ${error.message}`);
65 }
66 };
67
68 const handleDownload = async (attachmentPath: string, submissionUserId: number) => {
69 try {
70
71 // ✅ 新增权限校验
72 if (!currentUserId || (currentUserId !== bounty.creator_id && currentUserId !== submissionUserId)) {
73 message.error('无权查看此附件');
74 return;
75 }
76
77 const blob = await downloadAttachment(attachmentPath);
zhaoyumaof8f81842025-06-09 00:00:46 +080078 const url = (window as any).URL.createObjectURL(blob);
崔向南464e19d2025-06-05 17:46:27 +080079 const a = document.createElement('a');
80 a.href = url;
81 a.download = attachmentPath.split('/').pop() || 'file';
82 document.body.appendChild(a);
83 a.click();
84 a.remove();
zhaoyumaof8f81842025-06-09 00:00:46 +080085 (window as any).URL.revokeObjectURL(url);
崔向南464e19d2025-06-05 17:46:27 +080086 } catch (err) {
87 message.error('下载附件失败,请重试');
88 console.error('下载附件失败:', err);
89 }
90 };
91
92
93
94 useEffect(() => {
95 const fetchProfile = async () => {
96 try {
97 const res = await getProfile(); // 调用后端接口
98 if (res && res.code === 200) {
99 setCurrentUserId(res.data.userId); // 从接口获取 userId
100 } else {
101 message.error('获取用户信息失败');
102 }
103 } catch (err) {
104 console.error('获取用户信息失败:', err);
105 }
106 };
zhaoyumaof8f81842025-06-09 00:00:46 +0800107 fetchProfile().then(r => {});
崔向南464e19d2025-06-05 17:46:27 +0800108 }, []);
109
110
111 const { id } = useParams<{ id: string }>();
112
113 // 修改加载方法(适配统一请求)✅
114 useEffect(() => {
115 if (!id) return;
116
117 const loadBountyDetail = async () => {
118 try {
119 setLoading(true);
120 const res = await getBountyDetail(id);
121 console.log('【详情响应】原始数据:', res); // 👈 关键日志
122
123 if (res && res.code === 200) {
124 setBounty(res.data);
125 // 👇 新增:检查 submissions 数据结构
126 console.log('【submissions 数据】:', res.data.submissions);
127 } else {
128 throw new Error('响应结构异常');
129 }
130 } catch (err) {
131 console.error('【详情请求】错误:', err);
132 } finally {
133 setLoading(false);
134 }
135 };
136
zhaoyumaof8f81842025-06-09 00:00:46 +0800137 loadBountyDetail().then(r => {});
崔向南464e19d2025-06-05 17:46:27 +0800138 }, [id]);
139
140 return (
141 <div className="page-container">
142 <h2>悬赏详情</h2>
143
144 {/* 基础信息 */}
145 <Descriptions title="悬赏信息" bordered>
146 <Descriptions.Item label="标题">{bounty.title}</Descriptions.Item>
147 <Descriptions.Item label="发布者ID">{bounty.creator_id}</Descriptions.Item> // 新增字段
148 <Descriptions.Item label="奖励">{bounty.reward}</Descriptions.Item>
149 <Descriptions.Item label="状态">
150 {bounty.status === 0 ? '进行中' : bounty.status === 1 ? '已完成' : '已关闭'}
151 </Descriptions.Item>
152 <Descriptions.Item label="截止时间">{bounty.deadline}</Descriptions.Item>
153 <Descriptions.Item label="描述" span={3}>
154 {bounty.description}
155 </Descriptions.Item>
156 </Descriptions>
157
158 {/* 回复列表 */}
159 {bounty.submissions && (
160 <div style={{ marginTop: 24 }}>
161 <h3>回复列表</h3>
162 <List
163 dataSource={bounty.submissions}
164 renderItem={(item) => (
165 <List.Item>
166 <List.Item.Meta
167 title={`回复人ID:${item.userId}`}
168 description={
169 <>
170 {item.content}
171 {/* 状态标签 */}
172 <span style={{ marginLeft: 16 }}>
173 {item.status === 1 ? (
174 <Tag color="green">已采纳</Tag>
175 ) : (
176 <Tag color="red">未被采纳</Tag>
177 )}
178 </span>
179 </>
180 }
181 />
182
183 {/* 发布者操作按钮 */}
184 {currentUserId === bounty.creator_id && (
185 <Button
186 type="primary"
187 size="small"
188 onClick={() => handleAdoptSubmission(item.id, item.status)}
189 disabled={item.status === 1}
190 >
191 {item.status === 1 ? '已采纳' : '采纳'}
192 </Button>
193 )}
194
195 {/* 附件下载 */}
196 {item.attachment && currentUserId === bounty.creator_id && (
197 <a onClick={(e) => handleDownload(item.attachment, item.userId)} style={{ marginLeft: 8 }}>
198 查看附件
199 </a>
200 )}
201 </List.Item>
202 )}
203 />
204 </div>
205 )}
206 </div>
207 );
208};
209
210// 定义类型(建议单独放在src/types.d.ts中)
211declare namespace API {
212 export interface BountyDetail {
213 id: number;
214 title: string;
215 creator_id: number; // ✅ 新增:发布者ID
216 description: string;
217 reward: number;
218 deadline: string;
219 status: number;
220 submissions: Array<{
221 id: number;
222 userId: number; // ✅ 替换 id 为 userId
223 username: string;
224 content: string;
225 attachment: string;
226 status: number;
227 }>;
228 }
229}
230
231export default BountyDetail;