| import React, { useState, useEffect } from 'react'; |
| import { Button, Descriptions, List, message, Tag } from 'antd'; |
| import axios from 'axios'; // 新增 axios 导入 |
| import { useParams } from 'umi'; |
| |
| import { getBountyDetail, getProfile } from '@/services/bounty/bounty'; |
| import { downloadAttachment,adoptSubmission } from '@/services/bounty/bounty'; |
| |
| |
| |
| const BountyDetail: React.FC = () => { |
| const [bounty, setBounty] = useState<API.BountyDetail>({} as API.BountyDetail); |
| const [loading, setLoading] = useState(false); // 新增加载状态 |
| const [currentUserId, setCurrentUserId] = useState<number | null>(null); |
| |
| console.log('当前用户id:',currentUserId); |
| console.log('悬赏发布用户id:',bounty.creator_id); |
| |
| const handleAdoptSubmission = async (submissionId: number, currentStatus: number) => { |
| console.log('【采纳请求】', { |
| submissionId, |
| currentStatus, |
| bounty: bounty, |
| currentUserId |
| }); |
| |
| if (currentStatus === 1) return; |
| |
| try { |
| const res = await adoptSubmission(submissionId); |
| console.log('【采纳响应】', { |
| status: res.status, |
| data: res.data, |
| headers: res.headers |
| }); |
| |
| if (res.code === 200) { |
| message.success('采纳成功'); |
| setBounty((prev: API.BountyDetail) => { |
| console.log('【状态更新】', { |
| old: prev.submissions, |
| new: prev.submissions?.map(sub => |
| sub.id === submissionId ? { ...sub, status: 1 } : sub |
| ) |
| }); |
| |
| return { |
| ...prev, |
| submissions: prev.submissions?.map(sub => |
| sub.id === submissionId ? { ...sub, status: 1 } : sub |
| ) |
| } as API.BountyDetail; |
| }); |
| } else { |
| message.error(`采纳失败: ${res.msg}`); |
| } |
| } catch (error: any) { |
| console.error('【采纳错误】', { |
| error: error, |
| response: error.response?.data, |
| status: error.response?.status |
| }); |
| |
| message.error(`网络异常: ${error.message}`); |
| } |
| }; |
| |
| const handleDownload = async (attachmentPath: string, submissionUserId: number) => { |
| try { |
| |
| // ✅ 新增权限校验 |
| if (!currentUserId || (currentUserId !== bounty.creator_id && currentUserId !== submissionUserId)) { |
| message.error('无权查看此附件'); |
| return; |
| } |
| |
| const blob = await downloadAttachment(attachmentPath); |
| const url = (window as any).URL.createObjectURL(blob); |
| const a = document.createElement('a'); |
| a.href = url; |
| a.download = attachmentPath.split('/').pop() || 'file'; |
| document.body.appendChild(a); |
| a.click(); |
| a.remove(); |
| (window as any).URL.revokeObjectURL(url); |
| } catch (err) { |
| message.error('下载附件失败,请重试'); |
| console.error('下载附件失败:', err); |
| } |
| }; |
| |
| |
| |
| useEffect(() => { |
| const fetchProfile = async () => { |
| try { |
| const res = await getProfile(); // 调用后端接口 |
| if (res && res.code === 200) { |
| setCurrentUserId(res.data.userId); // 从接口获取 userId |
| } else { |
| message.error('获取用户信息失败'); |
| } |
| } catch (err) { |
| console.error('获取用户信息失败:', err); |
| } |
| }; |
| fetchProfile().then(r => {}); |
| }, []); |
| |
| |
| const { id } = useParams<{ id: string }>(); |
| |
| // 修改加载方法(适配统一请求)✅ |
| useEffect(() => { |
| if (!id) return; |
| |
| const loadBountyDetail = async () => { |
| try { |
| setLoading(true); |
| const res = await getBountyDetail(id); |
| console.log('【详情响应】原始数据:', res); // 👈 关键日志 |
| |
| if (res && res.code === 200) { |
| setBounty(res.data); |
| // 👇 新增:检查 submissions 数据结构 |
| console.log('【submissions 数据】:', res.data.submissions); |
| } else { |
| throw new Error('响应结构异常'); |
| } |
| } catch (err) { |
| console.error('【详情请求】错误:', err); |
| } finally { |
| setLoading(false); |
| } |
| }; |
| |
| loadBountyDetail().then(r => {}); |
| }, [id]); |
| |
| return ( |
| <div className="page-container"> |
| <h2>悬赏详情</h2> |
| |
| {/* 基础信息 */} |
| <Descriptions title="悬赏信息" bordered> |
| <Descriptions.Item label="标题">{bounty.title}</Descriptions.Item> |
| <Descriptions.Item label="发布者ID">{bounty.creator_id}</Descriptions.Item> // ✅ 新增字段 |
| <Descriptions.Item label="奖励">{bounty.reward}</Descriptions.Item> |
| <Descriptions.Item label="状态"> |
| {bounty.status === 0 ? '进行中' : bounty.status === 1 ? '已完成' : '已关闭'} |
| </Descriptions.Item> |
| <Descriptions.Item label="截止时间">{bounty.deadline}</Descriptions.Item> |
| <Descriptions.Item label="描述" span={3}> |
| {bounty.description} |
| </Descriptions.Item> |
| </Descriptions> |
| |
| {/* 回复列表 */} |
| {bounty.submissions && ( |
| <div style={{ marginTop: 24 }}> |
| <h3>回复列表</h3> |
| <List |
| dataSource={bounty.submissions} |
| renderItem={(item) => ( |
| <List.Item> |
| <List.Item.Meta |
| title={`回复人ID:${item.userId}`} |
| description={ |
| <> |
| {item.content} |
| {/* 状态标签 */} |
| <span style={{ marginLeft: 16 }}> |
| {item.status === 1 ? ( |
| <Tag color="green">已采纳</Tag> |
| ) : ( |
| <Tag color="red">未被采纳</Tag> |
| )} |
| </span> |
| </> |
| } |
| /> |
| |
| {/* 发布者操作按钮 */} |
| {currentUserId === bounty.creator_id && ( |
| <Button |
| type="primary" |
| size="small" |
| onClick={() => handleAdoptSubmission(item.id, item.status)} |
| disabled={item.status === 1} |
| > |
| {item.status === 1 ? '已采纳' : '采纳'} |
| </Button> |
| )} |
| |
| {/* 附件下载 */} |
| {item.attachment && currentUserId === bounty.creator_id && ( |
| <a onClick={(e) => handleDownload(item.attachment, item.userId)} style={{ marginLeft: 8 }}> |
| 查看附件 |
| </a> |
| )} |
| </List.Item> |
| )} |
| /> |
| </div> |
| )} |
| </div> |
| ); |
| }; |
| |
| // 定义类型(建议单独放在src/types.d.ts中) |
| declare namespace API { |
| export interface BountyDetail { |
| id: number; |
| title: string; |
| creator_id: number; // ✅ 新增:发布者ID |
| description: string; |
| reward: number; |
| deadline: string; |
| status: number; |
| submissions: Array<{ |
| id: number; |
| userId: number; // ✅ 替换 id 为 userId |
| username: string; |
| content: string; |
| attachment: string; |
| status: number; |
| }>; |
| } |
| } |
| |
| export default BountyDetail; |