增加了悬赏,标签查看,评论页面,标签上传后端有问题,评论还没跟后端连,优化了一些小界面

Change-Id: I44f5ef2eb0a8ebd91a4b3b3b446f897bea41435f
diff --git a/react-ui/src/pages/Reward/components/UpdateForm.tsx b/react-ui/src/pages/Reward/components/UpdateForm.tsx
new file mode 100644
index 0000000..526b946
--- /dev/null
+++ b/react-ui/src/pages/Reward/components/UpdateForm.tsx
@@ -0,0 +1,122 @@
+import { Modal, Form, Input, InputNumber, Select } from 'antd';
+import React from 'react';
+import { useIntl } from 'umi';
+import { RewardItem } from '../data';
+export type FormValueType = {
+    rewardId?: number;
+    title?: string;
+    description?: string;
+    amount?: number;
+    status?: string;
+    remark?: string;
+} & Partial<RewardItem>;
+
+export type UpdateFormProps = {
+    onCancel: (flag?: boolean, formVals?: FormValueType) => void;
+    onSubmit: (values: FormValueType) => Promise<void>;
+    open: boolean;
+    values: Partial<RewardItem>;
+    statusOptions: any;
+    readOnly?: boolean;
+};
+
+const UpdateForm: React.FC<UpdateFormProps> = (props) => {
+    const [form] = Form.useForm();
+
+    const { statusOptions } = props;
+
+    const intl = useIntl();
+
+    React.useEffect(() => {
+        form.resetFields();
+        form.setFieldsValue({
+            rewardId: props.values.rewardId,
+            title: props.values.title,
+            description: props.values.description,
+            amount: props.values.amount,
+            status: props.values.status,
+            remark: props.values.remark,
+        });
+    }, [form, props]);
+
+    const handleOk = () => {
+        form.submit();
+    };
+    const handleCancel = () => {
+        props.onCancel();
+        form.resetFields();
+    };
+    const handleFinish = async (values: FormValueType) => {
+        props.onSubmit(values);
+    };
+
+    return (
+        <Modal
+            width={640}
+            title={props.readOnly ? '查看悬赏' : (props.values.rewardId ? '编辑悬赏' : '新增悬赏')}
+            open={props.open}
+            onOk={handleOk}
+            footer={props.readOnly ? null : undefined}
+            onCancel={handleCancel}
+        >
+            <Form
+                form={form}
+                onFinish={handleFinish}
+                initialValues={props.values}
+                labelCol={{ span: 6 }}
+                wrapperCol={{ span: 18 }}
+            >
+                <Form.Item name="rewardId" hidden={true}>
+                    <Input />
+                </Form.Item>
+                <Form.Item
+                    name="title"
+                    label="悬赏标题"
+                    rules={[{ required: true, message: '请输入悬赏标题!' }]}
+                >
+                    <Input placeholder="请输入悬赏标题" disabled={props.readOnly} />
+                </Form.Item>
+                <Form.Item
+                    name="description"
+                    label="悬赏描述"
+                    rules={[{ required: true, message: '请输入悬赏描述!' }]}
+                >
+                    <Input.TextArea rows={4} placeholder="请输入悬赏描述" disabled={props.readOnly} />
+                </Form.Item>
+                <Form.Item
+                    name="amount"
+                    label="悬赏金额"
+                    rules={[{ required: true, message: '请输入悬赏金额!' }]}
+                >
+                    <InputNumber
+                        style={{ width: '100%' }}
+                        min={0}
+                        precision={2}
+                        placeholder="请输入悬赏金额"
+                        disabled={props.readOnly}
+                    />
+                </Form.Item>
+                {/* <Form.Item
+                    name="status"
+                    label="悬赏状态"
+                >
+                    <Select
+                        placeholder="请选择悬赏状态"
+                        options={statusOptions.map((item: any) => ({
+                            value: item.dictValue,
+                            label: item.dictLabel,
+                        }))}
+                    />
+                </Form.Item> */}
+                <Form.Item
+                    name="remark"
+                    label="备注"
+                >
+                    <Input.TextArea rows={2} placeholder="请输入备注" disabled={props.readOnly} />
+                </Form.Item>
+            </Form>
+        </Modal>
+    );
+};
+
+export default UpdateForm;
\ No newline at end of file
diff --git a/react-ui/src/pages/Reward/data.d.ts b/react-ui/src/pages/Reward/data.d.ts
new file mode 100644
index 0000000..6dbec7b
--- /dev/null
+++ b/react-ui/src/pages/Reward/data.d.ts
@@ -0,0 +1,38 @@
+export interface RewardItem {
+    /** 悬赏ID */
+    rewardId?: number;
+    /** 悬赏标题 */
+    title: string;
+    /** 悬赏描述 */
+    description: string;
+    /** 悬赏金额 */
+    amount: number;
+    /** 悬赏状态(0进行中 1已完成 2已取消) */
+    status?: string;
+    /** 发布者ID */
+    publisherId?: number;
+    /** 接受者ID */
+    accepterId?: number;
+    /** 创建时间 */
+    createTime?: string;
+    /** 更新时间 */
+    updateTime?: string;
+    /** 备注 */
+    remark?: string;
+}
+
+export interface RewardListParams {
+    /** 当前的页码 */
+    pageNum?: number;
+    /** 页面的容量 */
+    pageSize?: number;
+    /** 悬赏标题 */
+    title?: string;
+    /** 悬赏状态 */
+    status?: string;
+    /** 开始时间 */
+    'params[beginTime]'?: string;
+    /** 结束时间 */
+    'params[endTime]'?: string;
+}
+
diff --git a/react-ui/src/pages/Reward/index.tsx b/react-ui/src/pages/Reward/index.tsx
new file mode 100644
index 0000000..231415c
--- /dev/null
+++ b/react-ui/src/pages/Reward/index.tsx
@@ -0,0 +1,338 @@
+import { ExclamationCircleOutlined, PlusOutlined, DeleteOutlined } from '@ant-design/icons';
+import { Button, message, Modal, Switch } from 'antd';
+import React, { useRef, useState, useEffect } from 'react';
+import { FormattedMessage, useIntl } from 'umi';
+import { FooterToolbar, PageContainer } from '@ant-design/pro-layout';
+import type { ActionType, ProColumns } from '@ant-design/pro-table';
+import ProTable from '@ant-design/pro-table';
+import type { FormInstance } from 'antd';
+import { getRewardList, removeReward, updateReward, addReward } from './service';
+import UpdateForm from './components/UpdateForm';
+import { getDictValueEnum } from '@/services/system/dict';
+import DictTag from '@/components/DictTag';
+import { useAccess } from 'umi';
+import { RewardItem, RewardListParams } from './data';
+
+/**
+ * 删除节点
+ *
+ * @param selectedRows
+ */
+const handleRemove = async (selectedRows: RewardItem[]) => {
+    const hide = message.loading('正在删除');
+    if (!selectedRows) return true;
+    try {
+        await removeReward(selectedRows.map((row) => row.rewardId).join(','));
+        hide();
+        message.success('删除成功');
+        return true;
+    } catch (error) {
+        hide();
+        message.error('删除失败,请重试');
+        return false;
+    }
+};
+
+const handleUpdate = async (fields: RewardItem) => {
+    const hide = message.loading('正在更新');
+    try {
+        const resp = await updateReward(fields);
+        hide();
+        if (resp.code === 200) {
+            message.success('更新成功');
+        } else {
+            message.error(resp.msg);
+        }
+        return true;
+    } catch (error) {
+        hide();
+        message.error('配置失败请重试!');
+        return false;
+    }
+};
+
+const handleAdd = async (fields: RewardItem) => {
+    const hide = message.loading('正在添加');
+    try {
+        const resp = await addReward(fields);
+        hide();
+        if (resp.code === 200) {
+            message.success('添加成功');
+        } else {
+            message.error(resp.msg);
+        }
+        return true;
+    } catch (error) {
+        hide();
+        message.error('配置失败请重试!');
+        return false;
+    }
+};
+
+const RewardTableList: React.FC = () => {
+    const formTableRef = useRef<FormInstance>();
+
+    const [modalVisible, setModalVisible] = useState<boolean>(false);
+    const [readOnly, setReadOnly] = useState<boolean>(false);
+
+    const actionRef = useRef<ActionType>();
+    const [currentRow, setCurrentRow] = useState<RewardItem>();
+    const [selectedRows, setSelectedRows] = useState<RewardItem[]>([]);
+
+    const [statusOptions, setStatusOptions] = useState<any>([]);
+
+    const access = useAccess();
+
+    /** 国际化配置 */
+    const intl = useIntl();
+
+    useEffect(() => {
+        getDictValueEnum('reward_status').then((data) => {
+            setStatusOptions(data);
+        });
+    }, []);
+
+    const columns: ProColumns<RewardItem>[] = [
+        {
+            title: '悬赏ID',
+            dataIndex: 'rewardId',
+            valueType: 'text',
+            hideInSearch: true,
+        },
+        {
+            title: '悬赏标题',
+            dataIndex: 'title',
+            valueType: 'text',
+        },
+        {
+            title: '悬赏金额',
+            dataIndex: 'amount',
+            valueType: 'money',
+            hideInSearch: true,
+        },
+        // {
+        //     title: '悬赏状态',
+        //     dataIndex: 'status',
+        //     valueType: 'select',
+        //     valueEnum: statusOptions,
+        //     render: (_, record) => {
+        //         return (<DictTag enums={statusOptions} value={record.status} />);
+        //     },
+        // },
+        {
+            title: '发布时间',
+            dataIndex: 'createTime',
+            valueType: 'dateRange',
+            render: (_, record) => {
+                return (<span>{record.createTime?.toString()}</span>);
+            },
+            search: {
+                transform: (value) => {
+                    return {
+                        'params[beginTime]': value[0],
+                        'params[endTime]': value[1],
+                    };
+                },
+            },
+        },
+        {
+            title: '备注',
+            dataIndex: 'remark',
+            valueType: 'text',
+            hideInSearch: true,
+        },
+        {
+            title: <FormattedMessage id="pages.searchTable.titleOption" defaultMessage="操作" />,
+            dataIndex: 'option',
+            width: '220px',
+            valueType: 'option',
+            render: (_, record) => [
+                <Button
+                    type="link"
+                    size="small"
+                    key="view"
+                    onClick={() => {
+                        setModalVisible(true);
+                        setCurrentRow(record);
+                        // 设置只读模式
+                        setReadOnly(true);
+                    }}
+                >
+                    查看
+                </Button>,
+                <Button
+                    type="link"
+                    size="small"
+                    key="edit"
+                    hidden={!access.hasPerms('reward:reward:edit')}
+                    onClick={() => {
+                        setModalVisible(true);
+                        setCurrentRow(record);
+                        setReadOnly(false);
+                    }}
+                >
+                    编辑
+                </Button>,
+                <Button
+                    type="link"
+                    size="small"
+                    danger
+                    key="batchRemove"
+                    hidden={!access.hasPerms('reward:reward:remove')}
+                    onClick={async () => {
+                        Modal.confirm({
+                            title: '删除',
+                            content: '确定删除该项吗?',
+                            okText: '确认',
+                            cancelText: '取消',
+                            onOk: async () => {
+                                const success = await handleRemove([record]);
+                                if (success) {
+                                    if (actionRef.current) {
+                                        actionRef.current.reload();
+                                    }
+                                }
+                            },
+                        });
+                    }}
+                >
+                    删除
+                </Button>,
+            ],
+        },
+    ];
+
+    return (
+        <PageContainer>
+            <div style={{ width: '100%', float: 'right' }}>
+                <ProTable<RewardItem>
+                    headerTitle={intl.formatMessage({
+                        id: 'pages.searchTable.title',
+                        defaultMessage: '信息',
+                    })}
+                    actionRef={actionRef}
+                    formRef={formTableRef}
+                    rowKey="rewardId"
+                    key="rewardList"
+                    search={{
+                        labelWidth: 120,
+                    }}
+                    toolBarRender={() => [
+                        <Button
+                            type="primary"
+                            key="add"
+                            hidden={!access.hasPerms('reward:reward:add')}
+                            onClick={async () => {
+                                setCurrentRow(undefined);
+                                setModalVisible(true);
+                            }}
+                        >
+                            <PlusOutlined /> <FormattedMessage id="pages.searchTable.new" defaultMessage="新建" />
+                        </Button>,
+                        <Button
+                            type="primary"
+                            key="remove"
+                            danger
+                            hidden={selectedRows?.length === 0 || !access.hasPerms('reward:reward:remove')}
+                            onClick={async () => {
+                                Modal.confirm({
+                                    title: '是否确认删除所选数据项?',
+                                    icon: <ExclamationCircleOutlined />,
+                                    content: '请谨慎操作',
+                                    async onOk() {
+                                        const success = await handleRemove(selectedRows);
+                                        if (success) {
+                                            setSelectedRows([]);
+                                            actionRef.current?.reloadAndRest?.();
+                                        }
+                                    },
+                                    onCancel() { },
+                                });
+                            }}
+                        >
+                            <DeleteOutlined />
+                            <FormattedMessage id="pages.searchTable.delete" defaultMessage="删除" />
+                        </Button>,
+                    ]}
+                    request={(params) =>
+                        getRewardList({ ...params } as RewardListParams).then((res) => {
+                            return {
+                                data: res.rows,
+                                total: res.total,
+                                success: true,
+                            };
+                        })
+                    }
+                    columns={columns}
+                    rowSelection={{
+                        onChange: (_, selectedRows) => {
+                            setSelectedRows(selectedRows);
+                        },
+                    }}
+                />
+            </div>
+            {selectedRows?.length > 0 && (
+                <FooterToolbar
+                    extra={
+                        <div>
+                            <FormattedMessage id="pages.searchTable.chosen" defaultMessage="已选择" />
+                            <a style={{ fontWeight: 600 }}>{selectedRows.length}</a>
+                            <FormattedMessage id="pages.searchTable.item" defaultMessage="项" />
+                        </div>
+                    }
+                >
+                    <Button
+                        key="remove"
+                        danger
+                        hidden={!access.hasPerms('reward:reward:remove')}
+                        onClick={async () => {
+                            Modal.confirm({
+                                title: '删除',
+                                content: '确定删除该项吗?',
+                                okText: '确认',
+                                cancelText: '取消',
+                                onOk: async () => {
+                                    const success = await handleRemove(selectedRows);
+                                    if (success) {
+                                        setSelectedRows([]);
+                                        actionRef.current?.reloadAndRest?.();
+                                    }
+                                },
+                            });
+                        }}
+                    >
+                        <FormattedMessage id="pages.searchTable.batchDeletion" defaultMessage="批量删除" />
+                    </Button>
+                </FooterToolbar>
+            )}
+            <UpdateForm
+                readOnly={readOnly}
+                onSubmit={async (values) => {
+                    let success = false;
+                    if (values.rewardId) {
+                        success = await handleUpdate({ ...values } as RewardItem);
+                    } else {
+                        success = await handleAdd({ ...values } as RewardItem);
+                    }
+                    if (success) {
+                        setModalVisible(false);
+                        setCurrentRow(undefined);
+                        if (actionRef.current) {
+                            actionRef.current.reload();
+                        }
+                    }
+                }}
+                onCancel={() => {
+                    setModalVisible(false);
+                    setCurrentRow(undefined);
+                    setReadOnly(false);
+                }}
+                open={modalVisible}
+                values={currentRow || {}}
+                statusOptions={statusOptions}
+            />
+        </PageContainer>
+    );
+};
+
+export default RewardTableList;
\ No newline at end of file
diff --git a/react-ui/src/pages/Reward/service.ts b/react-ui/src/pages/Reward/service.ts
new file mode 100644
index 0000000..7550cf9
--- /dev/null
+++ b/react-ui/src/pages/Reward/service.ts
@@ -0,0 +1,49 @@
+import { request } from '@umijs/max';
+import type {
+    RewardItem,
+    RewardListParams,
+} from '@/pages/Reward/data'; // 假设你把 data.d.ts 放这里
+
+/** 获取悬赏任务列表 */
+export async function getRewardList(params?: RewardListParams) {
+    const queryString = params
+        ? `?${new URLSearchParams(params as Record<string, any>).toString()}`
+        : '';
+    const response = await request(`/api/reward/list${queryString}`, {
+        method: 'get',
+    });
+    if (!response || response.length === 0) {
+        return [{ id: 1, name: '虚假任务1', description: '这是一个虚假的任务描述' }, { id: 2, name: '虚假任务2', description: '这是另一个虚假的任务描述' }];
+    }
+    return response;
+}
+
+/** 获取悬赏任务详细信息 */
+export async function getReward(rewardId: number) {
+    return request(`/api/reward/${rewardId}`, {
+        method: 'get',
+    });
+}
+
+/** 新增悬赏任务 */
+export async function addReward(params: RewardItem) {
+    return request('/api/reward', {
+        method: 'post',
+        data: params,
+    });
+}
+
+/** 修改悬赏任务 */
+export async function updateReward(params: RewardItem) {
+    return request('/api/reward', {
+        method: 'put',
+        data: params,
+    });
+}
+
+/** 删除悬赏任务 */
+export async function removeReward(ids: string) {
+    return request(`/api/reward/${ids}`, {
+        method: 'delete',
+    });
+}
\ No newline at end of file
diff --git a/react-ui/src/pages/Torrent/Comments/data.d.ts b/react-ui/src/pages/Torrent/Comments/data.d.ts
new file mode 100644
index 0000000..922cb29
--- /dev/null
+++ b/react-ui/src/pages/Torrent/Comments/data.d.ts
@@ -0,0 +1,15 @@
+/** 种子评论表 */
+export interface SysTorrentComment {
+    /** 评论ID */
+    commentId: number;
+    /** 种子ID */
+    torrentId: number;
+    /** 评论用户ID */
+    userId: number;
+    /** 评论内容 */
+    content: string;
+    /** 创建时间 */
+    createTime: Date;
+    /** 父评论ID,用于回复 */
+    parentId: number;
+}
diff --git a/react-ui/src/pages/Torrent/Comments/index.tsx b/react-ui/src/pages/Torrent/Comments/index.tsx
new file mode 100644
index 0000000..9a9f25a
--- /dev/null
+++ b/react-ui/src/pages/Torrent/Comments/index.tsx
@@ -0,0 +1,194 @@
+import React, { useState, useEffect } from 'react';
+import { useParams, useNavigate } from 'react-router-dom';
+import { Button, Card, Form, Input, List, message, Avatar } from 'antd';
+import { ArrowLeftOutlined } from '@ant-design/icons';
+import { Layout } from 'antd';
+
+const { Content } = Layout;
+const { TextArea } = Input;
+
+interface CommentItem {
+    id: number;
+    content: string;
+    createTime: string;
+    createBy: string;
+    torrentId: number;
+}
+
+const TorrentComments: React.FC = () => {
+    const { torrentId } = useParams<{ torrentId: string }>();
+    const navigate = useNavigate();
+    const [form] = Form.useForm();
+    const [comments, setComments] = useState<CommentItem[]>([]);
+    const [submitting, setSubmitting] = useState(false);
+
+    // 获取评论列表
+    const fetchComments = async () => {
+        try {
+            // 模拟评论数据
+            const mockComments: CommentItem[] = [
+                {
+                    id: 1,
+                    content: '这个种子非常好,下载速度很快!',
+                    createTime: '2024-01-15 14:30:00',
+                    createBy: '张三',
+                    torrentId: Number(torrentId)
+                },
+                {
+                    id: 2,
+                    content: '资源质量很高,感谢分享。',
+                    createTime: '2024-01-15 15:20:00',
+                    createBy: '李四',
+                    torrentId: Number(torrentId)
+                },
+                {
+                    id: 3,
+                    content: '这个版本很完整,推荐下载!',
+                    createTime: '2024-01-15 16:45:00',
+                    createBy: '王五',
+                    torrentId: Number(torrentId)
+                },
+                {
+                    id: 1,
+                    content: '这个种子非常好,下载速度很快!',
+                    createTime: '2024-01-15 14:30:00',
+                    createBy: '张三',
+                    torrentId: Number(torrentId)
+                },
+                {
+                    id: 2,
+                    content: '资源质量很高,感谢分享。',
+                    createTime: '2024-01-15 15:20:00',
+                    createBy: '李四',
+                    torrentId: Number(torrentId)
+                },
+                {
+                    id: 3,
+                    content: '这个版本很完整,推荐下载!',
+                    createTime: '2024-01-15 16:45:00',
+                    createBy: '王五',
+                    torrentId: Number(torrentId)
+                }
+            ];
+            setComments(mockComments);
+        } catch (error) {
+            message.error('获取评论失败');
+        }
+    };
+
+    useEffect(() => {
+        if (torrentId) {
+            fetchComments();
+        }
+    }, [torrentId]);
+
+    // 提交评论
+    const handleSubmit = async () => {
+        try {
+            const values = await form.validateFields();
+            setSubmitting(true);
+
+            // TODO: 替换为实际的API调用
+            // await addComment({
+            //   torrentId: Number(torrentId),
+            //   content: values.content,
+            // });
+
+            message.success('评论成功');
+            form.resetFields();
+            fetchComments(); // 刷新评论列表
+        } catch (error) {
+            message.error('评论失败');
+        } finally {
+            setSubmitting(false);
+        }
+    };
+
+    return (
+        <Content style={{
+            height: '100vh',
+            display: 'flex',
+            flexDirection: 'column',
+            overflow: 'hidden' // 防止内容溢出
+        }}>
+            {/* 顶部标题栏 */}
+            <div style={{
+                padding: '16px',
+                borderBottom: '1px solid #f0f0f0',
+                display: 'flex',
+                alignItems: 'center',
+                backgroundColor: '#fff',
+                zIndex: 10
+            }}>
+                <Button
+                    type="link"
+                    icon={<ArrowLeftOutlined />}
+                    onClick={() => navigate(-1)}
+                    style={{ marginRight: '10px', padding: 0 }}
+                />
+                <span style={{ fontSize: '16px', fontWeight: 'bold' }}>种子评论</span>
+            </div>
+
+            {/* 评论列表区域 - 可滚动 */}
+            <div style={{
+                flex: 1,
+                overflowY: 'auto',
+                padding: '0 16px',
+                paddingBottom: '16px'
+            }}>
+                <List
+                    className="comment-list"
+                    itemLayout="horizontal"
+                    dataSource={comments}
+                    renderItem={(item) => (
+                        <List.Item>
+                            <List.Item.Meta
+                                avatar={<Avatar>{item.createBy[0]}</Avatar>}
+                                title={item.createBy}
+                                description={
+                                    <div>
+                                        <div>{item.content}</div>
+                                        <div style={{ color: '#8c8c8c', fontSize: '12px', marginTop: '8px' }}>
+                                            {item.createTime}
+                                        </div>
+                                    </div>
+                                }
+                            />
+                        </List.Item>
+                    )}
+                />
+            </div>
+
+            {/* 评论输入框 - 固定在父组件底部 */}
+            <div style={{
+                position: 'relative',
+                padding: '16px',
+                backgroundColor: '#fff',
+                borderTop: '1px solid #f0f0f0',
+                boxShadow: '0 -2px 8px rgba(0, 0, 0, 0.06)'
+            }}>
+                <Form form={form}>
+                    <Form.Item
+                        name="content"
+                        rules={[{ required: true, message: '请输入评论内容' }]}
+                        style={{ marginBottom: '12px' }}
+                    >
+                        <TextArea rows={3} placeholder="请输入您的评论" />
+                    </Form.Item>
+                    <Form.Item style={{ marginBottom: 0, textAlign: 'right' }}>
+                        <Button
+                            htmlType="submit"
+                            loading={submitting}
+                            onClick={handleSubmit}
+                            type="primary"
+                        >
+                            提交评论
+                        </Button>
+                    </Form.Item>
+                </Form>
+            </div>
+        </Content>
+    );
+};
+
+export default TorrentComments;
\ No newline at end of file
diff --git a/react-ui/src/pages/Torrent/Comments/service.ts b/react-ui/src/pages/Torrent/Comments/service.ts
new file mode 100644
index 0000000..f4243b6
--- /dev/null
+++ b/react-ui/src/pages/Torrent/Comments/service.ts
@@ -0,0 +1,30 @@
+import { request } from '@umijs/max';
+import type { SysTorrentComment } from '@/pages/Torrent/Comments/data';
+
+/** 获取种子评论列表 */
+export async function listComments(torrentId: number) {
+    return request<SysTorrentComment[]>(`/api/system/torrent/comment/list`, {
+        method: 'get',
+        params: { torrentId },
+    });
+}
+
+/** 新增评论 */
+export async function addComment(data: {
+    torrentId: number;
+    userId: number;
+    content: string;
+    parentId?: number;
+}) {
+    return request(`/api/system/torrent/comment`, {
+        method: 'post',
+        data,
+    });
+}
+
+/** 删除评论 */
+export async function deleteComment(commentId: number) {
+    return request(`/api/system/torrent/comment/${commentId}`, {
+        method: 'delete',
+    });
+}
diff --git a/react-ui/src/pages/Torrent/index.tsx b/react-ui/src/pages/Torrent/index.tsx
index bb058ae..30b7ef3 100644
--- a/react-ui/src/pages/Torrent/index.tsx
+++ b/react-ui/src/pages/Torrent/index.tsx
@@ -1,5 +1,5 @@
-import React, { useRef, useState } from 'react';
-import { PlusOutlined, EditOutlined, DeleteOutlined, EyeOutlined, UploadOutlined } from '@ant-design/icons';
+import React, { useRef, useState, useEffect } from 'react';
+import { PlusOutlined, EditOutlined, DeleteOutlined, EyeOutlined, UploadOutlined, DownloadOutlined, CommentOutlined } from '@ant-design/icons';
 import {
   Button,
   Modal,
@@ -13,21 +13,29 @@
   Layout,
   Upload,
   UploadProps,
+  Select,
+  Tag, // 导入Tag组件用于显示标签
 } from 'antd';
 import { ProTable, ActionType, ProColumns, ProDescriptions, ProDescriptionsItemProps } from '@ant-design/pro-components';
-import type { BtTorrent } from './data';
+import { useNavigate } from 'react-router-dom';
+import type { BtTorrent, BtTorrentTag } from './data';
 import {
   listBtTorrent,
   getBtTorrent,
   addBtTorrent,
   updateBtTorrent,
   removeBtTorrent,
-  uploadTorrent, // Function to handle torrent upload
+  uploadTorrent,
+  downloadTorrent,
+  addBtTorrentTag,
+  listBtTorrentTags,
+  getBtTorrentTag
 } from './service';
 
 const { Content } = Layout;
 
 const BtTorrentPage: React.FC = () => {
+  const navigate = useNavigate();
   const actionRef = useRef<ActionType>();
   const [form] = Form.useForm();
   const [modalVisible, setModalVisible] = useState(false);
@@ -35,19 +43,38 @@
   const [current, setCurrent] = useState<Partial<BtTorrent>>({});
   const [uploadModalVisible, setUploadModalVisible] = useState(false); // State for upload modal
   const [uploadFile, setUploadFile] = useState<File | null>(null); // State to store selected file
+  const [uploadForm] = Form.useForm(); // Form for upload modal
+  const [torrentTags, setTorrentTags] = useState<BtTorrentTag[]>([]); // 修改为数组类型来存储多个标签
 
   // Columns for the ProTable (the table displaying torrents)
   const columns: ProColumns<BtTorrent>[] = [
     {
       title: '种子ID',
       dataIndex: 'torrentId',
-      hideInForm: true,
       render: (dom, entity) => (
         <a
           onClick={async () => {
-            const res = await getBtTorrent(entity.torrentId!);
-            setCurrent(res);
-            setDrawerVisible(true);
+            try {
+              // 获取详细信息
+              const res = await getBtTorrent(entity.torrentId!);
+              console.log('获取的种子详情:', res); // 调试用
+
+              // 确保res是对象类型并且包含数据
+              if (res && typeof res === 'object') {
+                // 如果API返回了data包装,则提取data
+                const torrentData = res.data ? res.data : res;
+                setCurrent(torrentData);
+
+                // 先设置当前种子,然后获取标签
+                await handleGetTags(entity.torrentId!);
+                setDrawerVisible(true);
+              } else {
+                message.error('获取种子详情格式错误');
+              }
+            } catch (error) {
+              console.error('获取种子详情出错:', error);
+              message.error('获取种子详情失败');
+            }
           }}
         >
           {dom}
@@ -55,36 +82,56 @@
       ),
     },
     { title: '名称', dataIndex: 'name' },
-    { title: 'infoHash', dataIndex: 'infoHash' },
+    // { title: 'infoHash', dataIndex: 'infoHash' },
     { title: '大小 (bytes)', dataIndex: 'length', valueType: 'digit' },
-    { title: '分片大小', dataIndex: 'pieceLength', valueType: 'digit' },
-    { title: '片段数', dataIndex: 'piecesCount', valueType: 'digit' },
-    { title: '创建工具', dataIndex: 'createdBy', hideInSearch: true },
+    // { title: '分片大小', dataIndex: 'pieceLength', valueType: 'digit' },
+    // { title: '片段数', dataIndex: 'piecesCount', valueType: 'digit' },
+    { title: '创建人', dataIndex: 'createdBy', hideInSearch: true },
     { title: '上传时间', dataIndex: 'uploadTime', valueType: 'dateTime', hideInSearch: true },
     {
       title: '操作',
       valueType: 'option',
       render: (_, record) => [
-        <Button key="view" type="link" icon={<EyeOutlined />} onClick={() => {
-          setCurrent(record);
-          setDrawerVisible(true);
+        <Button key="view" type="link" icon={<EyeOutlined />} onClick={async () => {
+          try {
+            // 获取详细信息
+            const res = await getBtTorrent(record.torrentId!);
+            console.log('获取的种子详情:', res); // 调试用
+
+            // 确保res是对象类型并且包含数据
+            if (res && typeof res === 'object') {
+              // 如果API返回了data包装,则提取data
+              const torrentData = res.data ? res.data : res;
+              setCurrent(torrentData);
+
+              // 获取标签
+              await handleGetTags(record.torrentId!);
+              setDrawerVisible(true);
+            } else {
+              message.error('获取种子详情格式错误');
+            }
+          } catch (error) {
+            console.error('获取种子详情出错:', error);
+            message.error('获取详情失败');
+          }
         }}>查看</Button>,
-        <Button key="edit" type="link" icon={<EditOutlined />} onClick={() => {
-          setCurrent(record);
-          form.setFieldsValue(record);
-          setModalVisible(true);
-        }}>编辑</Button>,
-        <Button key="delete" type="link" icon={<DeleteOutlined />} danger onClick={() => {
-          Modal.confirm({
-            title: '删除确认',
-            content: '确定删除该种子?',
-            onOk: async () => {
-              await removeBtTorrent([record.torrentId!]);
-              message.success('删除成功');
-              actionRef.current?.reload();
-            },
-          });
-        }}>删除</Button>,
+        <Button key="download" type="link" icon={<DownloadOutlined />} onClick={async () => {
+          try {
+            const blob = await downloadTorrent(record.torrentId!);
+            const url = window.URL.createObjectURL(blob);
+            const link = document.createElement('a');
+            link.href = url;
+            link.download = `${record.name}.torrent`;
+            document.body.appendChild(link);
+            link.click();
+            document.body.removeChild(link);
+            window.URL.revokeObjectURL(url);
+            message.success('下载成功');
+          } catch (error: any) {
+            message.error('下载失败');
+          }
+        }}>下载</Button>,
+
       ],
     },
   ];
@@ -115,16 +162,19 @@
         throw new Error('请选择一个文件');
       }
 
-      // Call the uploadTorrent function to upload the file
+      const values = await uploadForm.validateFields();
+      console.log(file);
+      // Call the uploadTorrent function to upload the file with additional info
       await uploadTorrent(file);
 
       // Show a success message
       message.success('文件上传成功');
 
-      // Close the upload modal
+      // Close the upload modal and reset form
       setUploadModalVisible(false);
+      uploadForm.resetFields();
 
-      // Optionally reload the table or perform other actions (e.g., refresh list)
+      // Reload the table
       actionRef.current?.reload();
 
     } catch (error) {
@@ -132,6 +182,64 @@
     }
   };
 
+  // 修改获取标签的函数,处理API特定的响应格式
+  const handleGetTags = async (id: number) => {
+    try {
+      // 根据API的响应格式,获取rows数组中的标签
+      const response = await listBtTorrentTags({ torrentId: id });
+      console.log('API标签响应:', response);
+
+      // 检查响应格式并提取rows数组
+      if (response && response.rows && Array.isArray(response.rows)) {
+        setTorrentTags(response.rows);
+        console.log('设置标签:', response.rows);
+      } else {
+        console.log('未找到标签或格式不符');
+        setTorrentTags([]);
+      }
+    } catch (error) {
+      console.error('获取标签失败:', error);
+      message.error('获取标签失败');
+      setTorrentTags([]);
+    }
+  };
+
+  useEffect(() => {
+    if (current?.torrentId) {
+      handleGetTags(current.torrentId);
+    } else {
+      setTorrentTags([]); // 清空标签当没有选中种子时
+    }
+  }, [current]);
+
+  // 渲染标签列表的函数
+  const renderTags = () => {
+    if (!torrentTags || torrentTags.length === 0) {
+      return <span style={{ color: '#999' }}>暂无标签</span>;
+    }
+
+    // 定义一些可能的标签颜色
+    const tagColors = ['blue', 'green', 'cyan', 'purple', 'magenta', 'orange', 'gold', 'lime'];
+
+    return (
+      <div style={{ display: 'flex', flexWrap: 'wrap', gap: '8px' }}>
+        {torrentTags.map((tag, index) => {
+          // 根据索引轮换颜色
+          const colorIndex = index % tagColors.length;
+          return (
+            <Tag
+              key={tag.id || tag.torrentId || index}
+              color={tagColors[colorIndex]}
+              style={{ margin: '0 4px 4px 0', padding: '2px 8px' }}
+            >
+              {tag.tag}
+            </Tag>
+          );
+        })}
+      </div>
+    );
+  };
+
   return (
     <Content>
       <Card bordered={false}>
@@ -142,22 +250,10 @@
           search={{ labelWidth: 100 }}
           toolBarRender={() => [
             <Button
-              key="add"
-              type="primary"
-              icon={<PlusOutlined />}
-              onClick={() => {
-                form.resetFields();
-                setCurrent({});
-                setModalVisible(true);
-              }}
-            >
-              新增
-            </Button>,
-            <Button
               key="upload"
               type="primary"
               icon={<UploadOutlined />}
-              onClick={() => setUploadModalVisible(true)} // Show the upload modal
+              onClick={() => setUploadModalVisible(true)}
             >
               上传种子文件
             </Button>
@@ -202,37 +298,155 @@
         {/* 上传种子文件的Modal */}
         <Modal
           title="上传种子文件"
-          visible={uploadModalVisible}
-          onCancel={() => setUploadModalVisible(false)}
-          footer={null}
+          open={uploadModalVisible}
+          onCancel={() => {
+            setUploadModalVisible(false);
+            uploadForm.resetFields();
+          }}
+          onOk={() => {
+            if (uploadFile) {
+              handleFileUpload(uploadFile);
+            } else {
+              message.error('请选择文件');
+            }
+          }}
         >
-          <Upload
-            customRequest={({ file, onSuccess, onError }) => {
-              setUploadFile(file);
-              handleFileUpload(file);
-              onSuccess?.();
-            }}
-            showUploadList={false}
-            accept=".torrent"
-          >
-            <Button icon={<UploadOutlined />}>点击上传 .torrent 文件</Button>
-          </Upload>
+          <Form form={uploadForm} layout="vertical">
+            <Form.Item
+              name="file"
+              label="种子文件"
+              rules={[{ required: true, message: '请选择种子文件' }]}
+            >
+              <Upload
+                customRequest={({ file, onSuccess }) => {
+                  setUploadFile(file as File);
+                  onSuccess?.();
+                }}
+                showUploadList={true}
+                maxCount={1}
+                accept=".torrent"
+                onRemove={() => setUploadFile(null)}
+              >
+                <Button icon={<UploadOutlined />}>选择 .torrent 文件</Button>
+              </Upload>
+            </Form.Item>
+            <Form.Item
+              name="description"
+              label="介绍"
+              rules={[{ required: true, message: '请输入种子介绍' }]}
+            >
+              <Input.TextArea rows={4} placeholder="请输入种子文件的详细介绍" />
+            </Form.Item>
+            <Form.Item
+              name="tags"
+              label="标签"
+              rules={[{ required: true, message: '请输入标签' }]}
+            >
+              <Select
+                mode="tags"
+                style={{ width: '100%' }}
+                placeholder="请输入标签,按回车键确认"
+                tokenSeparators={[',']}
+              />
+            </Form.Item>
+          </Form>
         </Modal>
 
         {/* 详情抽屉 */}
         <Drawer
-          width={500}
+          width={600}
           open={drawerVisible}
           onClose={() => setDrawerVisible(false)}
           title="种子详情"
+          extra={
+            <Button
+              type="primary"
+              icon={<CommentOutlined />}
+              onClick={() => navigate(`/torrent/comments/${current.torrentId}`)}
+            >
+              查看评论
+            </Button>
+          }
         >
           {current && (
-            <ProDescriptions<BtTorrent>
-              column={1}
-              title={current.name}
-              request={async () => ({ data: current })}
-              columns={columns as ProDescriptionsItemProps<BtTorrent>[]}
-            />
+            <>
+              {/* 不要使用request属性,直接使用dataSource */}
+              <ProDescriptions<BtTorrent>
+                column={1}
+                title={current.name}
+                dataSource={current}
+                columns={[
+                  {
+                    title: '种子ID',
+                    dataIndex: 'torrentId',
+                    valueType: 'text'
+                  },
+                  {
+                    title: '名称',
+                    dataIndex: 'name',
+                    valueType: 'text'
+                  },
+                  {
+                    title: 'infoHash',
+                    dataIndex: 'infoHash',
+                    valueType: 'text',
+                    copyable: true
+                  },
+                  {
+                    title: '大小 (bytes)',
+                    dataIndex: 'length',
+                    valueType: 'digit',
+
+                  },
+                  {
+                    title: '分片大小',
+                    dataIndex: 'pieceLength',
+                    valueType: 'digit'
+                  },
+                  {
+                    title: '片段数',
+                    dataIndex: 'piecesCount',
+                    valueType: 'digit'
+                  },
+                  {
+                    title: '创建人',
+                    dataIndex: 'createdBy',
+                    valueType: 'text'
+                  },
+                  {
+                    title: '上传时间',
+                    dataIndex: 'uploadTime',
+                    valueType: 'dateTime'
+                  },
+                  {
+                    title: '标签',
+                    dataIndex: 'tags',
+                    render: () => renderTags()
+                  }
+                ] as ProDescriptionsItemProps<BtTorrent>[]}
+              />
+
+              {/* 如果需要显示额外信息,可以在这里添加 */}
+              {current.description && (
+                <div style={{ marginTop: 16 }}>
+                  <h3>介绍</h3>
+                  <p>{current.description}</p>
+                </div>
+              )}
+
+              {/* 添加调试信息 - 开发时使用,生产环境可以移除 */}
+              {/* <div style={{ marginTop: 20, background: '#f5f5f5', padding: 10, borderRadius: 4 }}>
+                <h4>调试信息:</h4>
+                <p>当前种子ID: {current.torrentId}</p>
+                <p>标签数量: {torrentTags?.length || 0}</p>
+                <details>
+                  <summary>查看完整数据</summary>
+                  <pre style={{ maxHeight: 200, overflow: 'auto' }}>{JSON.stringify(current, null, 2)}</pre>
+                  <h5>标签数据:</h5>
+                  <pre style={{ maxHeight: 200, overflow: 'auto' }}>{JSON.stringify(torrentTags, null, 2)}</pre>
+                </details>
+              </div> */}
+            </>
           )}
         </Drawer>
       </Card>
@@ -240,4 +454,4 @@
   );
 };
 
-export default BtTorrentPage;
+export default BtTorrentPage;
\ No newline at end of file
diff --git a/react-ui/src/pages/Torrent/service.ts b/react-ui/src/pages/Torrent/service.ts
index 8e15618..e526151 100644
--- a/react-ui/src/pages/Torrent/service.ts
+++ b/react-ui/src/pages/Torrent/service.ts
@@ -6,6 +6,14 @@
   BtTorrentTag,
 } from '@/pages/Torrent/data'; // 假设你把 data.d.ts 放这里
 
+/** 下载种子文件 */
+export async function downloadTorrent(torrentId: number) {
+  return request(`/api/system/torrent/download/${torrentId}`, {
+    method: 'get',
+    responseType: 'blob',
+  });
+}
+
 // ================================
 // 种子主表(bt_torrent)接口
 // ================================
@@ -62,6 +70,7 @@
 
 /** 获取文件详情 */
 export async function getBtTorrentFile(id: number) {
+
   return request<BtTorrentFile>(`/api/system/file/${id}`, {
     method: 'get',
   });
diff --git a/react-ui/src/pages/User/Center/index.tsx b/react-ui/src/pages/User/Center/index.tsx
index 2ce308c..7272fa3 100644
--- a/react-ui/src/pages/User/Center/index.tsx
+++ b/react-ui/src/pages/User/Center/index.tsx
@@ -14,6 +14,7 @@
 import AvatarCropper from './components/AvatarCropper';
 import { useRequest } from '@umijs/max';
 import { getUserInfo } from '@/services/session';
+import { getUserRateInfo } from '@/services/system/user';
 import { PageLoading } from '@ant-design/pro-components';
 
 const operationTabList = [
@@ -38,20 +39,25 @@
 export type tabKeyType = 'base' | 'password';
 
 const Center: React.FC = () => {
-  
+
   const [tabKey, setTabKey] = useState<tabKeyType>('base');
-  
+
   const [cropperModalOpen, setCropperModalOpen] = useState<boolean>(false);
-  
+
   //  获取用户信息
   const { data: userInfo, loading } = useRequest(async () => {
-    return { data: await getUserInfo()};
+    return { data: await getUserInfo() };
+  });
+
+  const { data: userRateInfo } = useRequest(async () => {
+    return { data: await getUserRateInfo() };
   });
   if (loading) {
     return <div>loading...</div>;
   }
 
-  const currentUser = userInfo?.user;
+  const currentUser = { ...userInfo?.user, ...userRateInfo?.data };
+
 
   //  渲染用户信息
   const renderUserInfo = ({
@@ -60,6 +66,10 @@
     email,
     sex,
     dept,
+    uploadCount,
+    downloadCount,
+    rateCount,
+    score,
   }: Partial<API.CurrentUser>) => {
     return (
       <List>
@@ -109,6 +119,39 @@
         </List.Item>
         <List.Item>
           <div>
+            <MailOutlined
+              style={{
+                marginRight: 8,
+              }}
+            />
+            上传量
+          </div>
+          <div>{uploadCount}</div>
+        </List.Item>
+        <List.Item>
+          <div>
+            <MailOutlined
+              style={{
+                marginRight: 8,
+              }}
+            />
+            下载量
+          </div>
+          <div>{downloadCount}</div>
+        </List.Item>
+        <List.Item>
+          <div>
+            <MailOutlined
+              style={{
+                marginRight: 8,
+              }}
+            />
+            分享率
+          </div>
+          <div>{rateCount}</div>
+        </List.Item>
+        {/* <List.Item>
+          <div>
             <ClusterOutlined
               style={{
                 marginRight: 8,
@@ -117,6 +160,17 @@
             部门
           </div>
           <div>{dept?.deptName}</div>
+        </List.Item> */}
+        <List.Item>
+          <div>
+            <MailOutlined
+              style={{
+                marginRight: 8,
+              }}
+            />
+            积分
+          </div>
+          <div>{score}</div>
         </List.Item>
       </List>
     );
@@ -147,13 +201,13 @@
             loading={loading}
           >
             {!loading && (
-              <div style={{ textAlign: "center"}}>
-                <div className={styles.avatarHolder} onClick={()=>{setCropperModalOpen(true)}}>
+              <div style={{ textAlign: "center" }}>
+                <div className={styles.avatarHolder} onClick={() => { setCropperModalOpen(true) }}>
                   <img alt="" src={currentUser.avatar} />
                 </div>
                 {renderUserInfo(currentUser)}
                 <Divider dashed />
-                <div className={styles.team}>
+                {/* <div className={styles.team}>
                   <div className={styles.teamTitle}>角色</div>
                   <Row gutter={36}>
                     {currentUser.roles &&
@@ -168,7 +222,7 @@
                         </Col>
                       ))}
                   </Row>
-                </div>
+                </div> */}
               </div>
             )}
           </Card>
@@ -188,7 +242,7 @@
       </Row>
       <AvatarCropper
         onFinished={() => {
-          setCropperModalOpen(false);     
+          setCropperModalOpen(false);
         }}
         open={cropperModalOpen}
         data={currentUser.avatar}