merge conflict

Change-Id: I4c784028cd7aa8e612563eac0fce3dda91631638
diff --git a/Dockerfile b/Dockerfile
index 385cb2f..ae06f8b 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -10,5 +10,7 @@
 FROM nginx:alpine
 COPY --from=build /app/dist  /usr/share/nginx/html
 COPY nginx.conf /etc/nginx/conf.d/default.conf
+
 EXPOSE 3004
+
 CMD ["nginx", "-g", "daemon off;"]
\ No newline at end of file
diff --git a/config/routes.ts b/config/routes.ts
index f9e4fcb..b468a04 100644
--- a/config/routes.ts
+++ b/config/routes.ts
@@ -90,6 +90,34 @@
     ]
   },
   {
+    path: '/bounty',
+    name: '悬赏管理',
+    icon: 'read',
+    routes: [
+      {
+        path: '/bounty/list',
+        component: '@/pages/Bounty/List',
+      },
+      {
+        path: '/bounty/detail/:id',
+        component: '@/pages/Bounty/Detail',
+      },
+      {
+        name: 'bountyPublish',  // 发布悬赏页面名称
+        path: '/bounty/publish',  // 访问路径
+        component: './Bounty/BountyPublish'  // 对应页面文件路径(相对于 src/pages)
+      },
+      {
+        name: 'bountyReply',  // 回复悬赏页面名称
+        path: '/bounty/reply',  // 访问路径
+        component: './Bounty/BountyReply'  // 对应页面文件路径
+      }
+
+    ],
+  },
+
+
+  {
     name: '帖子中心',
     icon: 'read',
     path: '/post/center',
diff --git a/gitpush.bat b/gitpush.bat
new file mode 100644
index 0000000..dffd610
--- /dev/null
+++ b/gitpush.bat
@@ -0,0 +1,5 @@
+@echo off
+SET /P commit="Enter commit message: "
+git add .
+git commit -m "%commit%"
+git push HEAD:refs/for/master
diff --git a/nginx.conf b/nginx.conf
index a2b5e67..8673316 100644
--- a/nginx.conf
+++ b/nginx.conf
@@ -4,6 +4,7 @@
 
 server {
     listen 3004;  # 修改为Docker Compose中映射的前端端口(与docker-compose.yml保持一致)
+
     server_name _;  # 允许所有域名访问
 
     # 静态资源处理
diff --git "a/public/\346\230\237\347\251\272.gif" "b/public/\346\230\237\347\251\272.gif"
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ "b/public/\346\230\237\347\251\272.gif"
diff --git a/src/locales/zh-CN/system/menu.ts b/src/locales/zh-CN/system/menu.ts
index 8a01e58..b06ec93 100644
--- a/src/locales/zh-CN/system/menu.ts
+++ b/src/locales/zh-CN/system/menu.ts
@@ -19,4 +19,7 @@
 	'system.menu.update_by': '更新者',
 	'system.menu.update_time': '更新时间',
 	'system.menu.remark': '备注',
+	'system.menu.bounty': '悬赏管理',        // 父级菜单名称
+  	'system.menu.bounty.publish': '发布悬赏',  // 子菜单1
+  	'system.menu.bounty.reply': '回复悬赏'
 };
diff --git a/src/pages/Bounty/BountyManage.tsx b/src/pages/Bounty/BountyManage.tsx
new file mode 100644
index 0000000..609e6c6
--- /dev/null
+++ b/src/pages/Bounty/BountyManage.tsx
@@ -0,0 +1,70 @@
+import React, { useState, useEffect } from 'react';
+import { Table, Button, message, Space } from 'antd';
+import axios from 'axios'; // 新增 axios 导入
+
+const BountyManage: React.FC = () => {
+  const [dataSource, setDataSource] = useState<API.Bounty[]>([]);
+
+  // 加载悬赏列表(修改请求方式)
+  useEffect(() => {
+    const loadBountyList = async () => {
+      try {
+        const res = await axios.get('/api/bounties', { params: { status: [0, 1, 2].join(',') } }); // 替换 get 为 axios.get
+        if (res.data.code === 200) {
+          setDataSource(res.data.data.records || []);
+        }
+      } catch (err) {
+        message.error('加载悬赏列表失败');
+      }
+    };
+    loadBountyList();
+  }, []);
+
+  // 操作列(修改请求方式)
+  const handleClose = async (id: number) => {
+    try {
+      //这个接口哪来的??
+      const res = await axios.post('/api/bounties/close', { id }); // 替换 post 为 axios.post
+      if (res.data.code === 200) {
+        message.success('悬赏已关闭');
+        setDataSource(dataSource.map(item => item.id === id ? { ...item, status: 2 } : item));
+      }
+    } catch (err) {
+      message.error('关闭失败');
+    }
+  };
+
+  const columns = [
+    { title: '我是manage标题', dataIndex: 'title' },
+    { title: '奖励', dataIndex: 'reward' },
+    {
+      title: '状态',
+      dataIndex: 'status',
+      // 显式声明 status 为 number 类型
+      render: (status: number) => status === 0 ? '进行中' : status === 1 ? '已完成' : '已关闭'
+    },
+    {
+      title: '操作',
+      // 显式声明 record 为 API.Bounty 类型(需确保 API.Bounty 已在 types.d.ts 中定义)
+      render: (value: unknown, record: API.Bounty) => (
+        <Space>
+          <Button type="link" onClick={() => handleClose(record.id)}>关闭</Button>
+        </Space>
+      )
+    }
+  ];
+
+  return (
+    <div className="page-container">
+      <h2>悬赏管理</h2>
+      <Table
+        dataSource={dataSource}
+        columns={columns}
+        rowKey="id"
+        pagination={{ pageSize: 10 }}
+      />
+    </div>
+  );
+};
+
+export default BountyManage;
diff --git a/src/pages/Bounty/BountyPublish.tsx b/src/pages/Bounty/BountyPublish.tsx
new file mode 100644
index 0000000..b2d121b
--- /dev/null
+++ b/src/pages/Bounty/BountyPublish.tsx
@@ -0,0 +1,116 @@
+import React from 'react';
+import { Form, Input, InputNumber, DatePicker, Button, message } from 'antd';
+import axios from 'axios';
+import { publishBounty } from '@/services/bounty/bounty'; // 新增 axios 导入
+
+interface BountyPublishProps {
+  onSuccess?: () => void; // 提交成功回调
+  onCancel?: () => void;  // 取消操作回调
+}
+
+
+
+const BountyPublish: React.FC<BountyPublishProps> = ({ onSuccess, onCancel }) => {
+  const [form] = Form.useForm();
+
+  // ✅ 替换 axios 请求为服务方法调用
+  const handleSubmit = async (values: {
+    title: string;
+    description: string;
+    reward: number;
+    deadline: string;
+  }) => {
+    try {
+      // 使用服务层方法
+      const res = await publishBounty(values);
+
+      if (res) {
+        //message.success('悬赏发布成功!');
+        form.resetFields();
+        onSuccess?.();
+      } else {
+        message.error(res.msg || '发布失败,请重试');
+      }
+    } catch (err) {
+      console.error('发布失败:', err);
+      message.error('网络请求失败,请检查网络');
+    }
+  };
+
+
+  return (
+    <div className="page-container">
+      <h2>发布新悬赏</h2>
+      <Form
+        form={form}
+        layout="vertical"
+        onFinish={handleSubmit}
+        requiredMark="optional"
+        style={{ maxWidth: 600 }}
+      >
+        {/* 标题 */}
+        <Form.Item
+          name="title"
+          label="悬赏标题"
+          rules={[{ required: true, message: '请输入悬赏标题' }]}
+        >
+          <Input placeholder="请输入悬赏标题" />
+        </Form.Item>
+
+        {/* 描述 */}
+        <Form.Item
+          name="description"
+          label="悬赏描述"
+          rules={[{ required: true, message: '请输入悬赏描述' }]}
+        >
+          <Input.TextArea rows={4} placeholder="请输入悬赏描述" />
+        </Form.Item>
+
+        {/* 奖励 */}
+        <Form.Item
+          name="reward"
+          label="悬赏奖励"
+          rules={[
+            { required: true, message: '请输入奖励数值' },
+            { type: 'number', min: 1, message: '奖励必须大于0' },
+          ]}
+        >
+          <InputNumber min={1} placeholder="请输入奖励数值" style={{ width: '100%' }} />
+        </Form.Item>
+
+        {/* 截止时间 */}
+        <Form.Item
+          name="deadline"
+          label="截止时间"
+          rules={[{ required: true, message: '请选择截止时间' }]}
+        >
+          <DatePicker
+            showTime
+            format="YYYY-MM-DD HH:mm:ss"
+            placeholder="选择截止时间"
+            style={{ width: '100%' }}
+          />
+        </Form.Item>
+
+        {/* 提交按钮 */}
+        <Form.Item>
+          <Button type="primary" htmlType="submit">
+            发布悬赏
+          </Button>
+          <Button
+            type="default"
+            onClick={() => {
+              form.resetFields();
+              onCancel?.(); // 🔥 取消时触发回调
+            }}
+            style={{ marginLeft: 16 }}
+          >
+            重置
+          </Button>
+        </Form.Item>
+      </Form>
+    </div>
+  );
+};
+
+export default BountyPublish;
diff --git a/src/pages/Bounty/BountyReply.tsx b/src/pages/Bounty/BountyReply.tsx
new file mode 100644
index 0000000..c347686
--- /dev/null
+++ b/src/pages/Bounty/BountyReply.tsx
@@ -0,0 +1,154 @@
+import React, { useState, useEffect } from 'react';
+import { Form, Input, Select, Button, Upload, message } from 'antd';
+import axios from 'axios'; // 新增 axios 导入
+import { submitBountyReply, uploadBountyAttachment } from '@/services/bounty/bounty'; // ✅ 新增导入上传接口
+const { Option } = Select;
+interface BountyReplyProps {
+  bountyId: number;    // 需要回复的悬赏ID
+  onSuccess?: () => void; // 提交成功回调
+  onCancel?: () => void;  // 取消操作回调
+}
+
+const BountyReply: React.FC<BountyReplyProps> = ({
+                                                   bountyId,
+                                                   onSuccess,
+                                                   onCancel
+                                                 }) => {
+  const [form] = Form.useForm();
+  const [bountyList, setBountyList] = useState<{ id: number; title: string }[]>([]);
+  const [uploading, setUploading] = useState(false); // ✅ 新增上传状态
+  // 加载可回复的悬赏列表(修改请求方式)
+  useEffect(() => {
+    const loadBountyList = async () => {
+      try {
+        const res = await axios.get('/api/bounties', { params: { status: 0 } }); // 替换 get 为 axios.get
+        if (res.data.code === 200) {
+          setBountyList(res.data.data.records || []);
+        }
+      } catch (err) {
+        message.error('加载悬赏列表失败');
+      }
+    };
+    loadBountyList();
+  }, []);
+
+  // 附件上传逻辑(修改请求方式)
+  const handleUpload = async (file: File) => {
+    try {
+      setUploading(true);
+      // 1. 上传文件获取路径
+      const filePath = await uploadBountyAttachment(file);
+      // 2. 保存文件对象到表单(用于后续提交)
+      form.setFieldsValue({ attachment: filePath, file }); // ✅ 同时保存路径和文件对象
+      return { url: filePath };
+    } catch (err) {
+      message.error('上传失败');
+      return { error: new Error('上传失败') };
+    } finally {
+      setUploading(false);
+    }
+  };
+
+  // 提交回复逻辑(修改请求方式)
+  // ✅ 替换 axios 请求为服务方法调用
+  const handleSubmit = async (values: { content: string; attachment?: string }) => {
+    try {
+      // 从表单获取文件对象(需在 handleUpload 中保存)
+      const file = form.getFieldValue('file'); // ✅ 获取文件对象
+
+      const res = await submitBountyReply({
+        bountyId,
+        ...values,
+        file, // ✅ 传递文件对象
+      });
+
+      if (res?.code === 200) {
+        message.success('提交成功');
+        form.resetFields();
+        onSuccess?.();
+      } else {
+        throw new Error('接口异常: ' + JSON.stringify(res));
+      }
+    } catch (err) {
+      console.error('提交失败:', err);
+      message.error('提交失败,请重试');
+    }
+  };
+
+  // @ts-ignore
+  // @ts-ignore
+  // @ts-ignore
+  // @ts-ignore
+  // @ts-ignore
+  // @ts-ignore
+  return (
+    <div className="page-container">
+      <h2>回复悬赏</h2>
+      <Form
+        form={form}
+        layout="vertical"
+        onFinish={handleSubmit}
+        requiredMark="optional"
+        style={{ maxWidth: 600 }}
+      >
+        {/* 选择悬赏(默认选中传入的bountyId) */}
+        <Form.Item
+          name="bountyId"
+          label="选择悬赏"
+          rules={[{ required: true, message: '请选择要回复的悬赏' }]}
+          initialValue={bountyId}
+        >
+          <Select disabled={!!bountyId} placeholder="请选择要回复的悬赏">
+            {bountyList.map((item) => (
+              <Option key={item.id} value={item.id}>
+                {item.title}
+              </Option>
+            ))}
+          </Select>
+        </Form.Item>
+
+        {/* 回复内容(保持原有逻辑) */}
+        <Form.Item
+          name="content"
+          label="回复内容"
+          rules={[{ required: true, message: '请输入回复内容' }]}
+        >
+          <Input.TextArea rows={4} placeholder="请输入回复内容" />
+        </Form.Item>
+
+        {/* 附件上传(保持原有逻辑) */}
+        <Form.Item name="attachment" label="附件上传">
+          <Upload
+            customRequest={({ file, onSuccess, onError }) => {
+              handleUpload(file as File); // 显式断言 file 为 File 类型
+            }} // 使用服务层上传
+            maxCount={1}
+            accept=".doc,.docx,.pdf,.zip"
+            showUploadList={false} // 隐藏默认上传列表
+          >
+            <Button loading={uploading}>上传附件</Button>
+          </Upload>
+        </Form.Item>
+
+        {/* 提交按钮 */}
+        <Form.Item>
+          <Button type="primary" htmlType="submit">
+            提交回复
+          </Button>
+          <Button
+            type="default"
+            onClick={() => {
+              form.resetFields();
+              onCancel?.(); // 🔥 取消时触发回调
+            }}
+            style={{ marginLeft: 16 }}
+          >
+            取消
+          </Button>
+        </Form.Item>
+      </Form>
+    </div>
+  );
+};
+
+export default BountyReply;
diff --git a/src/pages/Bounty/Detail.tsx b/src/pages/Bounty/Detail.tsx
new file mode 100644
index 0000000..41a25df
--- /dev/null
+++ b/src/pages/Bounty/Detail.tsx
@@ -0,0 +1,231 @@
+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 => {
+          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
+            )
+          };
+        });
+      } 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.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.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();
+  }, []);
+
+
+  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();
+  }, [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;
diff --git a/src/pages/Bounty/List.tsx b/src/pages/Bounty/List.tsx
new file mode 100644
index 0000000..ca789c5
--- /dev/null
+++ b/src/pages/Bounty/List.tsx
@@ -0,0 +1,183 @@
+import React, { useState, useEffect } from 'react';
+import { Table, Button, message, Space, Modal } from 'antd';
+import axios from 'axios'; // 新增 axios 导入
+import { useNavigate } from 'umi';
+
+import { getBountyList } from '@/services/bounty/bounty';
+import BountyPublish from './BountyPublish';
+import BountyReply from './BountyReply';
+const BountyList: React.FC = () => {
+  const [dataSource, setDataSource] = useState<API.Bounty[]>([]);
+  const [loading, setLoading] = useState(false);
+  const [publishVisible, setPublishVisible] = useState(false);
+  const [replyVisible, setReplyVisible] = useState(false);
+  const [selectedId, setSelectedId] = useState<number | null>(null);
+
+  const navigate = useNavigate();
+
+  // 加载悬赏列表(修改请求方式)
+
+    const loadBountyList = async () => {
+      try {
+        setLoading(true);
+        const res = await getBountyList();
+
+        // 添加详细日志输出
+        console.log('接口原始响应:', res);
+
+        // 修改数据处理逻辑以适配实际数据结构
+        if (res && Array.isArray(res)) {
+          // 当接口直接返回数组时
+          setDataSource(res);
+        } else if (res && res.code === 200) {
+          // 当接口返回标准分页结构时
+          setDataSource(res.data.records || res.data || []);
+        } else {
+          throw new Error('接口返回异常结构');
+        }
+      } catch (err) {
+        console.error('加载数据异常:', err);
+        console.error('完整错误:', err);
+
+        // 添加网络错误处理
+        // @ts-ignore
+        if (err?.response?.status === 401) {
+          message.error('认证失效,请重新登录');
+          navigate('/login');
+        } else {
+          message.error('加载悬赏列表失败');
+        }
+      } finally {
+        setLoading(false);
+      }
+    };
+
+  useEffect(() => {
+    loadBountyList();
+  }, []);
+
+  // ✅ 发布成功回调(普通函数,无需useEffect)
+  const handlePublishSuccess = () => {
+    setPublishVisible(false);
+    loadBountyList(); // 重新加载数据
+    message.success('悬赏发布成功');
+  };
+
+  // ✅ 回复成功回调(普通函数,无需useEffect)
+  const handleReplySuccess = () => {
+    setReplyVisible(false);
+    loadBountyList(); // 重新加载数据
+    message.success('回复提交成功');
+  };
+
+
+  // 跳转详情页
+  const goDetail = (id: number) => {
+    navigate(`/bounty/detail/${id}`);
+  };
+
+  // 表格列配置
+  const columns = [
+    {
+      title: '标题',
+      dataIndex: 'title',
+      width: 200,
+      ellipsis: true,
+    },
+    {
+      title: '发布者ID',
+      dataIndex: 'creator_id', // ✅ 新增列
+      width: 100,
+      align: 'center' as const,
+    },
+    {
+      title: '奖励',
+      dataIndex: 'reward',
+      width: 100,
+      align: 'center' as const,
+    },
+    {
+      title: '状态',
+      dataIndex: 'status',
+      width: 100,
+      render: (status: number) => (
+        status === 0 ? '未回复' : status === 1 ? '已回复' : '已关闭'
+      )
+    },
+    {
+      title: '操作',
+      width: 160,
+      render: (value: unknown, record: API.Bounty) => (
+        <Space>
+          <Button
+            type="link"
+            onClick={() => navigate(`/bounty/detail/${record.id}`)}
+          >
+            查看详情
+          </Button>
+          <Button
+            type="link"
+            onClick={() => {
+              setSelectedId(record.id);
+              setReplyVisible(true);
+            }}
+          >
+            回复悬赏
+          </Button>
+        </Space>
+      )
+    }
+  ];
+
+  return (
+    <div className="page-container">
+      {/* 顶部操作区 */}
+      <div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: 16 }}>
+        <h2>悬赏列表</h2>
+        {/* 发布按钮 */}
+        <Button
+          type="primary"
+          onClick={() => setPublishVisible(true)}
+        >
+          发布悬赏
+        </Button>
+      </div>
+
+      {/* 悬赏列表 */}
+      <Table
+        dataSource={dataSource}
+        columns={columns}
+        rowKey="id"
+        pagination={{ pageSize: 10 }}
+        loading={loading}
+      />
+
+      {/* 发布悬赏模态框 */}
+      <Modal
+        title="发布新悬赏"
+        open={publishVisible}
+        onCancel={() => setPublishVisible(false)}
+        footer={null}
+      >
+        <BountyPublish onSuccess={handlePublishSuccess} />
+      </Modal>
+
+      {/* 回复悬赏模态框 */}
+      <Modal
+        title="回复悬赏"
+        open={replyVisible}
+        onCancel={() => setReplyVisible(false)}
+        footer={null}
+      >
+        {selectedId && (
+          <BountyReply
+            bountyId={selectedId}
+            onSuccess={handleReplySuccess}
+          />
+        )}
+      </Modal>
+    </div>
+  );
+};
+
+export default BountyList;
diff --git a/src/pages/User/Login/index.tsx b/src/pages/User/Login/index.tsx
index a18f785..233cb3d 100644
--- a/src/pages/User/Login/index.tsx
+++ b/src/pages/User/Login/index.tsx
@@ -167,7 +167,18 @@
   }, []);
 
   return (
-    <div className={containerClassName}>
+    <div
+      className={containerClassName}
+      style={{
+        backgroundImage:
+          "url('https://images.unsplash.com/photo-1462331940025-496dfbfc7564?auto=format&fit=crop&w=1500&q=80')",
+        backgroundSize: 'cover',
+        backgroundPosition: 'center',
+        minHeight: '100vh',
+        position: 'relative',
+        overflow: 'hidden',
+      }}
+    >
       <Helmet>
         <title>
           {intl.formatMessage({
@@ -178,31 +189,86 @@
         </title>
       </Helmet>
       <Lang />
+      {/* 星空粒子特效层 */}
+      <div
+        style={{
+          position: 'absolute',
+          inset: 0,
+          zIndex: 0,
+          pointerEvents: 'none',
+          background: 'radial-gradient(ellipse at bottom, #1b2735 0%, #090a0f 100%)',
+        }}
+      >
+        {/* 可以集成粒子库如 tsParticles 或者简单用 CSS 动画 */}
+        <svg width="100%" height="100%">
+          {[...Array(60)].map((_, i) => (
+            <circle
+              key={i}
+              cx={Math.random() * 1600}
+              cy={Math.random() * 900}
+              r={Math.random() * 1.5 + 0.5}
+              fill="#fff"
+              opacity={Math.random() * 0.8 + 0.2}
+            />
+          ))}
+        </svg>
+      </div>
       <div
         style={{
           flex: '1',
           padding: '32px 0',
+          display: 'flex',
+          alignItems: 'center',
+          justifyContent: 'center',
+          minHeight: '100vh',
+          position: 'relative',
+          zIndex: 1,
         }}
       >
         <LoginForm
           contentStyle={{
-            minWidth: 280,
-            maxWidth: '75vw',
+            minWidth: 320,
+            maxWidth: 400,
+            background: 'rgba(25, 34, 54, 0.92)',
+            borderRadius: 16,
+            boxShadow: '0 8px 32px 0 rgba(31, 38, 135, 0.37)',
+            border: '1px solid rgba(255,255,255,0.18)',
+            padding: '32px 32px 24px 32px',
+            color: '#fff',
           }}
-          logo={<img alt="logo" src="/logo.svg" />}
-          title="Ant Design"
-          subTitle={intl.formatMessage({ id: 'pages.layouts.userLayout.title' })}
+          // logo={
+          //   <img
+          //     alt="logo"
+          //     src="/planet-logo.svg"
+          //     style={{
+          //       width: 64,
+          //       height: 64,
+          //       filter: 'drop-shadow(0 0 8px #fff8) drop-shadow(0 0 16px #6cf)',
+          //       marginBottom: 8,
+          //     }}
+          //   />
+          // }
+          title={
+            <span style={{ color: '#fff', fontWeight: 700, fontSize: 28, letterSpacing: 2 }}>
+              PT星球
+            </span>
+          }
+          subTitle={
+            <span style={{ color: '#b3c7f9', fontSize: 16 }}>
+              探索你的专属星球,畅享PT世界
+            </span>
+          }
           initialValues={{
             autoLogin: true,
           }}
-          actions={[
-            <FormattedMessage
-              key="loginWith"
-              id="pages.login.loginWith"
-              defaultMessage="其他登录方式"
-            />,
-            <ActionIcons key="icons" />,
-          ]}
+          // actions={[
+          //   <FormattedMessage
+          //     key="loginWith"
+          //     id="pages.login.loginWith"
+          //     defaultMessage="其他登录方式"
+          //   />,
+          //   <ActionIcons key="icons" />,
+          // ]}
           onFinish={async (values) => {
             await handleSubmit(values as API.LoginParams);
           }}
@@ -214,19 +280,28 @@
             items={[
               {
                 key: 'account',
-                label: intl.formatMessage({
-                  id: 'pages.login.accountLogin.tab',
-                  defaultMessage: '账户密码登录',
-                }),
+                label: (
+                  <span style={{ color: '#fff' }}>
+                    {intl.formatMessage({
+                      id: 'pages.login.accountLogin.tab',
+                      defaultMessage: '账户密码登录',
+                    })}
+                  </span>
+                ),
               },
               {
                 key: 'mobile',
-                label: intl.formatMessage({
-                  id: 'pages.login.phoneLogin.tab',
-                  defaultMessage: '手机号登录',
-                }),
+                label: (
+                  <span style={{ color: '#fff' }}>
+                    {intl.formatMessage({
+                      id: 'pages.login.phoneLogin.tab',
+                      defaultMessage: '手机号登录',
+                    })}
+                  </span>
+                ),
               },
             ]}
+            style={{ marginBottom: 24 }}
           />
 
           {code !== 200 && loginType === 'account' && (
@@ -244,7 +319,8 @@
                 initialValue="admin"
                 fieldProps={{
                   size: 'large',
-                  prefix: <UserOutlined />,
+                  prefix: <UserOutlined style={{ color: '#6cf' }} />,
+                  style: { background: 'rgba(255,255,255,0.08)', color: '#fff' },
                 }}
                 placeholder={intl.formatMessage({
                   id: 'pages.login.username.placeholder',
@@ -267,7 +343,8 @@
                 initialValue="admin123"
                 fieldProps={{
                   size: 'large',
-                  prefix: <LockOutlined />,
+                  prefix: <LockOutlined style={{ color: '#6cf' }} />,
+                  style: { background: 'rgba(255,255,255,0.08)', color: '#fff' },
                 }}
                 placeholder={intl.formatMessage({
                   id: 'pages.login.password.placeholder',
@@ -290,6 +367,8 @@
                   <ProFormText
                     style={{
                       float: 'right',
+                      background: 'rgba(255,255,255,0.08)',
+                      color: '#fff',
                     }}
                     name="code"
                     placeholder={intl.formatMessage({
@@ -319,6 +398,9 @@
                       cursor: 'pointer',
                       paddingLeft: '10px',
                       width: '100px',
+                      borderRadius: 8,
+                      boxShadow: '0 0 8px #6cf8',
+                      background: '#fff',
                     }}
                     preview={false}
                     onClick={() => getCaptchaCode()}
@@ -328,13 +410,16 @@
             </>
           )}
 
-          {code !== 200 && loginType === 'mobile' && <LoginMessage content="验证码错误" />}
+          {code !== 200 && loginType === 'mobile' && (
+            <LoginMessage content="验证码错误" />
+          )}
           {type === 'mobile' && (
             <>
               <ProFormText
                 fieldProps={{
                   size: 'large',
-                  prefix: <MobileOutlined />,
+                  prefix: <MobileOutlined style={{ color: '#6cf' }} />,
+                  style: { background: 'rgba(255,255,255,0.08)', color: '#fff' },
                 }}
                 name="mobile"
                 placeholder={intl.formatMessage({
@@ -365,7 +450,8 @@
               <ProFormCaptcha
                 fieldProps={{
                   size: 'large',
-                  prefix: <LockOutlined />,
+                  prefix: <LockOutlined style={{ color: '#6cf' }} />,
+                  style: { background: 'rgba(255,255,255,0.08)', color: '#fff' },
                 }}
                 captchaProps={{
                   size: 'large',
@@ -413,14 +499,23 @@
           <div
             style={{
               marginBottom: 24,
+              color: '#b3c7f9',
             }}
           >
-            <ProFormCheckbox noStyle name="autoLogin">
-              <FormattedMessage id="pages.login.rememberMe" defaultMessage="自动登录" />
+            <ProFormCheckbox>
+              <a
+                style={{
+                  float: 'right',
+                  color: '#fff',
+                  fontSize: 14,
+                }}>
+              <FormattedMessage id="pages.login.rememberMe"  defaultMessage="自动登录" />
+              </a>
             </ProFormCheckbox>
             <a
               style={{
                 float: 'right',
+                color: '#6cf',
               }}
             >
               <FormattedMessage id="pages.login.forgotPassword" defaultMessage="忘记密码" />
diff --git a/src/services/bounty/bounty.ts b/src/services/bounty/bounty.ts
new file mode 100644
index 0000000..9c1ef34
--- /dev/null
+++ b/src/services/bounty/bounty.ts
@@ -0,0 +1,120 @@
+// src/services/bounty.ts
+import { request } from '@umijs/max';
+
+// 查询悬赏列表
+export async function getBountyList(params?: Record<string, any>) {
+  return request('/api/bounties', {
+    method: 'GET',
+    headers: {
+      'Content-Type': 'application/json;charset=UTF-8',
+    },
+    params,
+  });
+}
+
+export async function getBountyDetail(id: number | string) {
+  return request(`/api/bounties/${id}`, {
+    method: 'GET',
+    headers: {
+      'Content-Type': 'application/json;charset=UTF-8',
+    },
+  }).then(data => ({ code: 200, data })); // ✅ 自动包装成标准结构
+}
+
+// 新增:发布悬赏
+export async function publishBounty(params: {
+  title: string;
+  description: string;
+  reward: number;
+  deadline: string;
+}) {
+  return request('/api/bounties/publish', {
+    method: 'POST',
+    headers: {
+      'Content-Type': 'application/json;charset=UTF-8',
+    },
+    data: {
+      ...params,
+      status: 0
+    },
+  });
+}
+
+// 新增:上传附件接口
+export async function uploadBountyAttachment(file: File) {
+  const formData = new FormData();
+  formData.append('file', file);
+
+  return request('/api/bounty-submissions/upload', {
+    method: 'POST',
+    data: formData,
+    // ✅ 移除手动设置 Content-Type
+    // headers: {
+    //   'Content-Type': 'multipart/form-data', // ❌ 浏览器会自动设置 boundary
+    // },
+  });
+}
+
+// 修改:提交悬赏回复(不再处理文件)
+// src/services/bounty/bounty.ts
+// src/services/bounty/bounty.ts
+// src/services/bounty/bounty.ts
+export async function submitBountyReply(params: {
+  bountyId: number;
+  content: string;
+  attachment?: string;
+  file?: File;
+}) {
+  console.log('【提交请求】/api/bounty-submissions 参数:', params);
+
+  const formData = new FormData();
+  formData.append('submission', new Blob([JSON.stringify(params)], { type: 'application/json' }));
+  if (params.file) {
+    formData.append('file', params.file);
+  }
+
+  return request('/api/bounty-submissions', {
+    method: 'POST',
+    data: formData,
+  }).then(res => {
+    console.log('【接口响应】/api/bounty-submissions:', res);
+    // ✅ 确保返回统一结构
+    return {
+      code: res.code || (res ? 200 : 500),
+      data: res.data || res,
+      msg: res.message || '操作成功',
+    };
+  });
+}
+
+
+export async function downloadAttachment(attachmentPath: string): Promise<Blob> {
+  // ✅ 提取文件名
+  const filename = attachmentPath.split('/').pop() || 'file';
+
+  return request(`/api/bounty-submissions/download`, {
+    method: 'GET',
+    params: {
+      filename, // ✅ 只传递文件名
+    },
+    responseType: 'blob',
+  });
+}
+
+export async function getProfile() {
+  return request('/api/system/user/profile', {
+    method: 'GET',
+    headers: {
+      'Content-Type': 'application/json;charset=UTF-8',
+    },
+  });
+}
+
+export async function adoptSubmission(submissionId: number) {
+  return request(`/api/bounty-submissions/${submissionId}/adopt`, {
+    method: 'PUT',
+    headers: {
+      'Content-Type': 'application/json;charset=UTF-8',
+    },
+  });
+}
diff --git a/src/services/post/index.ts b/src/services/post/index.ts
index 3452095..13e5bae 100644
--- a/src/services/post/index.ts
+++ b/src/services/post/index.ts
@@ -1,3 +1,4 @@
+// @ts-ignore
 import { request } from '@umijs/max';
 
 export interface PostListParams {
@@ -26,7 +27,7 @@
 
 // 获取帖子列表
 export async function getPostList(params: PostListParams) {
-    console.log('getPostList', params);    
+    console.log('getPostList', params);
   return request<API.TableDataInfo>('/api/post-center/list', {
     method: 'GET',
     params,
@@ -138,7 +139,7 @@
     method: 'POST',
     data: file,
   });
-} 
+}
 
 // 删除图片
 export async function deleteImage(filename: string): Promise<API.AjaxResult> {
@@ -259,4 +260,4 @@
     method: 'PUT',
     data: { action, postId, reason },
   });
-} 
\ No newline at end of file
+}
diff --git a/src/types.d.ts b/src/types.d.ts
new file mode 100644
index 0000000..d65e0e6
--- /dev/null
+++ b/src/types.d.ts
@@ -0,0 +1,14 @@
+//如果需要定义 API 类型(如接口返回的用户信息、列表数据等),推荐:
+
+declare namespace API {
+  // 补充 Bounty 类型定义(根据实际接口返回字段调整)
+  export interface Bounty {
+    id: number;
+    title: string;
+    description: string;
+    reward: number;
+    deadline: string;
+    status: number; // 0-进行中,1-已完成,2-已关闭
+    creator_id: number;
+  }
+}