新增邀请码页面,新增添加好友功能

Change-Id: Ifa0a5c355ab3693eecfe919de06fa9bef8171695
diff --git a/react-ui/src/pages/Invite/data.d.ts b/react-ui/src/pages/Invite/data.d.ts
new file mode 100644
index 0000000..2a07e7a
--- /dev/null
+++ b/react-ui/src/pages/Invite/data.d.ts
@@ -0,0 +1,12 @@
+// data.ts 建议的类型定义
+export interface GetInviteCodeResponse {
+    code: number;
+    msg: string;
+    data: string; // 邀请码字符串
+}
+
+export interface GetUserByInviteCodeResponse {
+    code: number;
+    msg: string;
+    data: number; // 用户ID
+}
\ No newline at end of file
diff --git a/react-ui/src/pages/Invite/index.tsx b/react-ui/src/pages/Invite/index.tsx
new file mode 100644
index 0000000..56989ff
--- /dev/null
+++ b/react-ui/src/pages/Invite/index.tsx
@@ -0,0 +1,300 @@
+import React, { useState, useEffect } from 'react';
+import { useModel } from 'umi';
+import { Card, Input, Button, Space, Typography, Row, Col, message } from 'antd';
+import { CopyOutlined, UserAddOutlined, GiftOutlined, ShareAltOutlined } from '@ant-design/icons';
+import { getUserInviteCode, getUserByInviteCode } from './service';
+
+const { Title, Text, Paragraph } = Typography;
+
+const InvitePage: React.FC = () => {
+    const [myInviteCode, setMyInviteCode] = useState<string>('');
+    const [inputInviteCode, setInputInviteCode] = useState<string>('');
+    const [loading, setLoading] = useState<boolean>(false);
+    const [submitting, setSubmitting] = useState<boolean>(false);
+
+    // 获取当前用户信息
+    const { initialState } = useModel('@@initialState');
+    const userId = initialState?.currentUser?.userId || '';
+
+    // 获取我的邀请码
+    const fetchMyInviteCode = async () => {
+        if (!userId) {
+            message.error('用户信息获取失败');
+            return;
+        }
+
+        setLoading(true);
+        try {
+            const response = await getUserInviteCode();
+            if (response.code === 200) {
+                // 根据后端返回的数据结构调整
+                setMyInviteCode(response.msg);
+            } else {
+                message.error(response.msg || '获取邀请码失败');
+            }
+        } catch (error) {
+            message.error('获取邀请码失败');
+        } finally {
+            setLoading(false);
+        }
+    };
+
+    // 复制邀请码
+    const copyInviteCode = () => {
+        if (myInviteCode) {
+            navigator.clipboard.writeText(myInviteCode).then(() => {
+                message.success('邀请码已复制到剪贴板');
+            }).catch(() => {
+                message.error('复制失败,请手动复制');
+            });
+        }
+    };
+
+    // 查询邀请码对应的用户
+    const handleSubmitInviteCode = async () => {
+        if (!inputInviteCode.trim()) {
+            message.warning('请输入邀请码');
+            return;
+        }
+
+        setSubmitting(true);
+        try {
+            const response = await getUserByInviteCode(inputInviteCode.trim());
+
+            if (response.code === 200) {
+                message.success(`邀请码有效,对应用户ID: ${response.data}`);
+                setInputInviteCode('');
+                // 这里可以根据业务需求进行后续操作,比如关注用户等
+            } else {
+                message.error(response.msg || '邀请码无效');
+            }
+        } catch (error) {
+            message.error('查询邀请码失败');
+        } finally {
+            setSubmitting(false);
+        }
+    };
+
+    useEffect(() => {
+        fetchMyInviteCode();
+    }, []);
+
+    return (
+        <div
+            style={{
+                padding: '32px 24px',
+                maxWidth: '800px',
+                margin: '0 auto',
+                minHeight: '100vh',
+                background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
+            }}
+        >
+            {/* 头部标题区域 */}
+            <div style={{ textAlign: 'center', marginBottom: '40px' }}>
+                <GiftOutlined
+                    style={{
+                        fontSize: '48px',
+                        color: '#fff',
+                        marginBottom: '16px',
+                        filter: 'drop-shadow(0 4px 8px rgba(0,0,0,0.2))'
+                    }}
+                />
+                <Title
+                    level={1}
+                    style={{
+                        color: '#fff',
+                        margin: 0,
+                        fontSize: '32px',
+                        fontWeight: 600,
+                        textShadow: '0 2px 4px rgba(0,0,0,0.3)'
+                    }}
+                >
+                    邀请码管理
+                </Title>
+                <Paragraph
+                    style={{
+                        color: 'rgba(255,255,255,0.8)',
+                        fontSize: '16px',
+                        margin: '8px 0 0 0'
+                    }}
+                >
+                    邀请好友,共享美好体验
+                </Paragraph>
+            </div>
+
+            <Row gutter={[24, 24]}>
+                {/* 我的邀请码 */}
+                <Col xs={24} lg={12}>
+                    <Card
+                        title={
+                            <Space>
+                                <ShareAltOutlined style={{ color: '#1890ff' }} />
+                                <span style={{ color: '#1890ff', fontWeight: 600 }}>我的邀请码</span>
+                            </Space>
+                        }
+                        style={{
+                            height: '100%',
+                            borderRadius: '16px',
+                            boxShadow: '0 8px 32px rgba(0,0,0,0.12)',
+                            border: 'none',
+                            background: 'rgba(255,255,255,0.95)',
+                            backdropFilter: 'blur(10px)'
+                        }}
+                        bodyStyle={{ padding: '24px' }}
+                    >
+                        <Space direction="vertical" style={{ width: '100%' }} size="large">
+                            <div style={{
+                                padding: '20px',
+                                background: 'linear-gradient(135deg, #e3f2fd 0%, #f3e5f5 100%)',
+                                borderRadius: '12px',
+                                textAlign: 'center'
+                            }}>
+                                <Text
+                                    type="secondary"
+                                    style={{
+                                        fontSize: '14px',
+                                        display: 'block',
+                                        marginBottom: '16px'
+                                    }}
+                                >
+                                    分享下方邀请码给好友,邀请他们加入
+                                </Text>
+                                <div style={{
+                                    background: '#fff',
+                                    padding: '16px',
+                                    borderRadius: '8px',
+                                    border: '2px dashed #d9d9d9',
+                                    marginBottom: '16px'
+                                }}>
+                                    <Text
+                                        copyable={false}
+                                        style={{
+                                            fontSize: '24px',
+                                            fontWeight: 'bold',
+                                            color: '#1890ff',
+                                            fontFamily: 'Monaco, monospace',
+                                            letterSpacing: '2px'
+                                        }}
+                                    >
+                                        {loading ? '加载中...' : (myInviteCode || '暂无邀请码')}
+                                    </Text>
+                                </div>
+                                <Button
+                                    type="primary"
+                                    size="large"
+                                    icon={<CopyOutlined />}
+                                    onClick={copyInviteCode}
+                                    disabled={!myInviteCode}
+                                    style={{
+                                        borderRadius: '8px',
+                                        height: '44px',
+                                        fontSize: '16px',
+                                        fontWeight: 500,
+                                        boxShadow: '0 4px 12px rgba(24, 144, 255, 0.3)'
+                                    }}
+                                    block
+                                >
+                                    复制邀请码
+                                </Button>
+                            </div>
+                        </Space>
+                    </Card>
+                </Col>
+
+                {/* 输入邀请码 */}
+                <Col xs={24} lg={12}>
+                    <Card
+                        title={
+                            <Space>
+                                <UserAddOutlined style={{ color: '#52c41a' }} />
+                                <span style={{ color: '#52c41a', fontWeight: 600 }}>查询邀请码</span>
+                            </Space>
+                        }
+                        style={{
+                            height: '100%',
+                            borderRadius: '16px',
+                            boxShadow: '0 8px 32px rgba(0,0,0,0.12)',
+                            border: 'none',
+                            background: 'rgba(255,255,255,0.95)',
+                            backdropFilter: 'blur(10px)'
+                        }}
+                        bodyStyle={{ padding: '24px' }}
+                    >
+                        <Space direction="vertical" style={{ width: '100%' }} size="large">
+                            <div style={{
+                                padding: '20px',
+                                background: 'linear-gradient(135deg, #f6ffed 0%, #e6f7ff 100%)',
+                                borderRadius: '12px'
+                            }}>
+                                <Text
+                                    type="secondary"
+                                    style={{
+                                        fontSize: '14px',
+                                        display: 'block',
+                                        marginBottom: '20px',
+                                        textAlign: 'center'
+                                    }}
+                                >
+                                    输入邀请码
+                                </Text>
+                                <Space direction="vertical" style={{ width: '100%' }} size="middle">
+                                    <Input
+                                        value={inputInviteCode}
+                                        onChange={(e) => setInputInviteCode(e.target.value)}
+                                        placeholder="请输入邀请码"
+                                        size="large"
+                                        style={{
+                                            borderRadius: '8px',
+                                            fontSize: '16px',
+                                            height: '48px',
+                                            textAlign: 'center',
+                                            fontFamily: 'Monaco, monospace',
+                                            letterSpacing: '1px'
+                                        }}
+                                        onPressEnter={handleSubmitInviteCode}
+                                    />
+                                    <Button
+                                        type="primary"
+                                        size="large"
+                                        icon={<UserAddOutlined />}
+                                        onClick={handleSubmitInviteCode}
+                                        loading={submitting}
+                                        disabled={!inputInviteCode.trim()}
+                                        style={{
+                                            borderRadius: '8px',
+                                            height: '44px',
+                                            fontSize: '16px',
+                                            fontWeight: 500,
+                                            background: '#52c41a',
+                                            borderColor: '#52c41a',
+                                            boxShadow: '0 4px 12px rgba(82, 196, 26, 0.3)'
+                                        }}
+                                        block
+                                    >
+                                        邀请码
+                                    </Button>
+                                </Space>
+                            </div>
+                        </Space>
+                    </Card>
+                </Col>
+            </Row>
+
+            {/* 底部装饰 */}
+            <div style={{
+                textAlign: 'center',
+                marginTop: '40px',
+                padding: '20px',
+                background: 'rgba(255,255,255,0.1)',
+                borderRadius: '12px',
+                backdropFilter: 'blur(10px)'
+            }}>
+                <Text style={{ color: 'rgba(255,255,255,0.7)', fontSize: '14px' }}>
+                    通过邀请码连接更多朋友,一起享受精彩体验
+                </Text>
+            </div>
+        </div>
+    );
+};
+
+export default InvitePage;
\ No newline at end of file
diff --git a/react-ui/src/pages/Invite/service.ts b/react-ui/src/pages/Invite/service.ts
new file mode 100644
index 0000000..e6cf9af
--- /dev/null
+++ b/react-ui/src/pages/Invite/service.ts
@@ -0,0 +1,19 @@
+import { request } from '@umijs/max';
+import type {
+    GetInviteCodeResponse,
+    GetUserByInviteCodeResponse
+} from './data';
+
+// 获取用户邀请码
+export async function getUserInviteCode(): Promise<GetInviteCodeResponse> {
+    return request('/api/system/user/invite/code', {
+        method: 'GET',  // 修改为 GET 请求
+    });
+}
+
+// 根据邀请码获取用户信息
+export async function getUserByInviteCode(code: string): Promise<GetUserByInviteCodeResponse> {
+    return request(`/api/system/user/invite/user/${code}`, {
+        method: 'GET',  // 修改为 GET 请求,并使用路径参数
+    });
+}
\ No newline at end of file
diff --git a/react-ui/src/pages/Message/index.tsx b/react-ui/src/pages/Message/index.tsx
index 04f9881..9ae9467 100644
--- a/react-ui/src/pages/Message/index.tsx
+++ b/react-ui/src/pages/Message/index.tsx
@@ -1,8 +1,10 @@
 import React, { useState, useEffect } from 'react';
-import { Card, List, Avatar, Input, Button, Row, Col, message } from 'antd';
+import { Card, List, Avatar, Input, Button, Row, Col, message, Modal, Form } from 'antd';
+import { UserAddOutlined, SearchOutlined } from '@ant-design/icons';
 import { SysUserMessage, ChatContact } from './data.d';
-import { getChatContactList, getChatHistory, sendMessage, getUserInfo } from './service';
+import { getChatContactList, getChatHistory, sendMessage, getUserInfo, addFriend } from './service';
 import './index.less';
+import { useModel } from 'umi';
 
 const MessagePage: React.FC = () => {
     const [chatContacts, setChatContacts] = useState<ChatContact[]>([]);
@@ -11,6 +13,12 @@
     const [chatMessages, setChatMessages] = useState<SysUserMessage[]>([]);
     const [loading, setLoading] = useState(false);
     const [sending, setSending] = useState(false);
+    const { initialState } = useModel('@@initialState');
+    const ccuserId = initialState?.currentUser?.userId || '';
+    // 添加好友相关状态
+    const [addFriendVisible, setAddFriendVisible] = useState(false);
+    const [addFriendLoading, setAddFriendLoading] = useState(false);
+    const [form] = Form.useForm();
 
     // 获取聊天对象列表
     const fetchChatContacts = async () => {
@@ -36,7 +44,7 @@
             setLoading(true);
             const response = await getChatHistory({
                 userId,
-                currentUserId: 1, // 假设当前用户ID为1,实际应该从用户状态获取
+                currentUserId: Number(ccuserId), // 假设当前用户ID为1,实际应该从用户状态获取
                 pageSize: 100 // 获取最近100条消息
             });
 
@@ -110,6 +118,26 @@
         }
     };
 
+    // 添加好友
+    const handleAddFriend = async (values: { username: string }) => {
+        try {
+            setAddFriendLoading(true);
+            await addFriend({ userId: Number(ccuserId), authorUsername: values.username });
+            message.success('好友添加成功');
+
+            // 重新获取聊天对象列表
+            await fetchChatContacts();
+
+            // 关闭弹窗并重置表单
+            setAddFriendVisible(false);
+            form.resetFields();
+        } catch (error) {
+            message.error('添加好友失败,请检查用户名是否正确');
+        } finally {
+            setAddFriendLoading(false);
+        }
+    };
+
     // 选择聊天对象
     const handleSelectUser = (userId: number) => {
         setSelectedUserId(userId);
@@ -137,7 +165,6 @@
     // 获取用户名
     const getUserName = (userId: number) => {
         const contact = chatContacts.find(c => c.userId === userId);
-
         return contact?.nickName || `用户${userId}`;
     };
 
@@ -159,7 +186,19 @@
                 {/* 左侧聊天对象列表 */}
                 <Col span={8}>
                     <Card
-                        title="聊天列表"
+                        title={
+                            <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
+                                <span>聊天列表</span>
+                                <Button
+                                    type="primary"
+                                    icon={<UserAddOutlined />}
+                                    size="small"
+                                    onClick={() => setAddFriendVisible(true)}
+                                >
+                                    添加好友
+                                </Button>
+                            </div>
+                        }
                         bordered={false}
                         style={{ height: '100%' }}
                         loading={loading && !selectedUserId}
@@ -180,11 +219,11 @@
                                     className={selectedUserId === contact.userId ? 'selected' : ''}
                                 >
                                     <List.Item.Meta
-                                        // avatar={
-                                        //     <Avatar size="large">
-                                        //         {contact.nickName.charAt(0)}
-                                        //     </Avatar>
-                                        // }
+                                        avatar={
+                                            <Avatar size="large">
+                                                {contact.nickName.charAt(0).toUpperCase()}
+                                            </Avatar>
+                                        }
                                         title={
                                             <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
                                                 <span style={{
@@ -355,6 +394,59 @@
                     </div>
                 </Col>
             </Row>
+
+            {/* 添加好友弹窗 */}
+            <Modal
+                title="添加好友"
+                open={addFriendVisible}
+                onCancel={() => {
+                    setAddFriendVisible(false);
+                    form.resetFields();
+                }}
+                footer={null}
+                width={400}
+            >
+                <Form
+                    form={form}
+                    layout="vertical"
+                    onFinish={handleAddFriend}
+                    style={{ marginTop: '20px' }}
+                >
+                    <Form.Item
+                        label="用户名"
+                        name="username"
+                        rules={[
+                            { required: true, message: '请输入用户名' },
+                            { min: 2, message: '用户名至少2个字符' },
+                            { max: 20, message: '用户名最多20个字符' }
+                        ]}
+                    >
+                        <Input
+                            placeholder="请输入要添加的用户名"
+                            prefix={<SearchOutlined />}
+                            size="large"
+                        />
+                    </Form.Item>
+                    <Form.Item style={{ marginBottom: 0, textAlign: 'right' }}>
+                        <Button
+                            onClick={() => {
+                                setAddFriendVisible(false);
+                                form.resetFields();
+                            }}
+                            style={{ marginRight: '8px' }}
+                        >
+                            取消
+                        </Button>
+                        <Button
+                            type="primary"
+                            htmlType="submit"
+                            loading={addFriendLoading}
+                        >
+                            添加
+                        </Button>
+                    </Form.Item>
+                </Form>
+            </Modal>
         </div>
     );
 };
diff --git a/react-ui/src/pages/Message/service.ts b/react-ui/src/pages/Message/service.ts
index c5bc356..4eb3840 100644
--- a/react-ui/src/pages/Message/service.ts
+++ b/react-ui/src/pages/Message/service.ts
@@ -112,6 +112,20 @@
     }
 }
 
+// 添加好友
+export async function addFriend(params: { userId: number, authorUsername: string }) {
+    try {
+        return await request('api/system/user/follow', {
+            method: 'post',
+            data: params,
+        });
+    } catch (error) {
+        // 发送消息失败时记录错误但不返回默认数据,让组件处理错误
+        console.error('发送消息接口异常:', error);
+        throw error; // 重新抛出错误,让调用方处理
+    }
+};
+
 /** 获取用户信息(用于聊天对象显示) */
 export async function getUserInfo(userId: number) {
     // 默认用户信息
diff --git a/react-ui/src/pages/Reward/components/UpdateForm.tsx b/react-ui/src/pages/Reward/components/UpdateForm.tsx
index 526b946..a7d9444 100644
--- a/react-ui/src/pages/Reward/components/UpdateForm.tsx
+++ b/react-ui/src/pages/Reward/components/UpdateForm.tsx
@@ -85,8 +85,8 @@
                 </Form.Item>
                 <Form.Item
                     name="amount"
-                    label="悬赏金额"
-                    rules={[{ required: true, message: '请输入悬赏金额!' }]}
+                    label="悬赏积分"
+                    rules={[{ required: true, message: '请输入悬赏积分!' }]}
                 >
                     <InputNumber
                         style={{ width: '100%' }}
diff --git a/react-ui/src/pages/Reward/index.tsx b/react-ui/src/pages/Reward/index.tsx
index e55cfc1..42a9bac 100644
--- a/react-ui/src/pages/Reward/index.tsx
+++ b/react-ui/src/pages/Reward/index.tsx
@@ -1,4 +1,4 @@
-import { ExclamationCircleOutlined, PlusOutlined, DeleteOutlined, UploadOutlined } from '@ant-design/icons';
+import { ExclamationCircleOutlined, PlusOutlined, DeleteOutlined, UploadOutlined, TrophyOutlined } from '@ant-design/icons';
 import { Button, message, Modal, Switch, Upload, Form } from 'antd';
 import React, { useRef, useState, useEffect } from 'react';
 import { FormattedMessage, useIntl } from 'umi';
@@ -223,10 +223,21 @@
             valueType: 'text',
         },
         {
-            title: '悬赏金额',
+            title: (
+                <span>
+                    <TrophyOutlined style={{ marginRight: 4, color: '#faad14' }} />
+                    悬赏积分
+                </span>
+            ),
             dataIndex: 'amount',
-            valueType: 'money',
+            valueType: 'digit',
             hideInSearch: true,
+            render: (_, record) => (
+                <span style={{ color: '#faad14', fontWeight: 'bold' }}>
+                    <TrophyOutlined style={{ marginRight: 4 }} />
+                    {record.amount}
+                </span>
+            ),
         },
         // {
         //     title: '悬赏状态',
@@ -283,6 +294,7 @@
                     size="small"
                     key="accept"
                     style={{ color: '#52c41a' }}
+                    icon={<TrophyOutlined />}
                     onClick={() => {
                         setCurrentAcceptReward(record);
                         setAcceptModalVisible(true);
@@ -468,7 +480,12 @@
 
             {/* 接悬赏模态框 */}
             <Modal
-                title={`接悬赏 - ${currentAcceptReward?.title || ''}`}
+                title={
+                    <span>
+                        <TrophyOutlined style={{ color: '#faad14', marginRight: 8 }} />
+                        接悬赏 - {currentAcceptReward?.title || ''}
+                    </span>
+                }
                 open={acceptModalVisible}
                 onOk={handleAcceptSubmit}
                 onCancel={() => {
@@ -482,7 +499,13 @@
             >
                 <div style={{ marginBottom: 16 }}>
                     <p><strong>悬赏标题:</strong>{currentAcceptReward?.title}</p>
-                    <p><strong>悬赏金额:</strong>¥{currentAcceptReward?.amount}</p>
+                    <p>
+                        <strong>悬赏积分:</strong>
+                        <span style={{ color: '#faad14', fontWeight: 'bold' }}>
+                            <TrophyOutlined style={{ marginRight: 4 }} />
+                            {currentAcceptReward?.amount}
+                        </span>
+                    </p>
                     <p><strong>备注:</strong>{currentAcceptReward?.remark || '无'}</p>
                 </div>
 
diff --git a/react-ui/src/pages/Torrent/Comments/index.tsx b/react-ui/src/pages/Torrent/Comments/index.tsx
index 05c5165..3dd3c68 100644
--- a/react-ui/src/pages/Torrent/Comments/index.tsx
+++ b/react-ui/src/pages/Torrent/Comments/index.tsx
@@ -5,7 +5,6 @@
 import { Layout } from 'antd';
 import { listComments, addComment } from './service';
 import { responseSysTorrentComment, SysTorrentComment } from './data';
-
 const { Content } = Layout;
 const { TextArea } = Input;