举报的管理员前端

Change-Id: Ieeaa92b556ccc31539c4e0df6bfcc87625ed08a8
diff --git a/src/api/__tests__/complain.test.js b/src/api/__tests__/complain.test.js
new file mode 100644
index 0000000..94a06c6
--- /dev/null
+++ b/src/api/__tests__/complain.test.js
@@ -0,0 +1,96 @@
+const axios = require('axios');
+jest.mock('axios');
+
+const {
+    createComplain,
+    deleteComplain,
+    updateComplain,
+    getComplainsByTargetUser,
+    getComplainsByPostingUser,
+    getAllComplains,
+    getComplainDetailById
+} = require('../complain');
+
+describe('Complain API Tests', () => {
+    beforeEach(() => {
+        jest.clearAllMocks();
+    });
+
+    test('createComplain should post complain data', async () => {
+        const mockData = { data: { complainid: 1 } };
+        const complainPayload = { puse: 1, duser: 2, content: 'test', torrentid: 99 };
+        axios.post.mockResolvedValue(mockData);
+
+        const response = await createComplain(complainPayload);
+
+        expect(axios.post).toHaveBeenCalledWith(
+            'http://localhost:8080/complain/create',
+            complainPayload
+        );
+        expect(response).toEqual(mockData.data);
+    });
+
+    test('deleteComplain should send delete request', async () => {
+        const mockData = { data: true };
+        axios.delete.mockResolvedValue(mockData);
+
+        const response = await deleteComplain(1);
+
+        expect(axios.delete).toHaveBeenCalledWith('http://localhost:8080/complain/delete/1');
+        expect(response).toBe(true);
+    });
+
+    test('updateComplain should put complain data', async () => {
+        const complainPayload = { complainid: 1, content: 'updated' };
+        const mockData = { data: true };
+        axios.put.mockResolvedValue(mockData);
+
+        const response = await updateComplain(complainPayload);
+
+        expect(axios.put).toHaveBeenCalledWith(
+            'http://localhost:8080/complain/update',
+            complainPayload
+        );
+        expect(response).toBe(true);
+    });
+
+    test('getComplainsByTargetUser should fetch complains by duser', async () => {
+        const mockData = { data: [{ complainid: 1, duser: 2 }] };
+        axios.get.mockResolvedValue(mockData);
+
+        const response = await getComplainsByTargetUser(2);
+
+        expect(axios.get).toHaveBeenCalledWith('http://localhost:8080/complain/target/2');
+        expect(response).toEqual(mockData.data);
+    });
+
+    test('getComplainsByPostingUser should fetch complains by puse', async () => {
+        const mockData = { data: [{ complainid: 1, puse: 1 }] };
+        axios.get.mockResolvedValue(mockData);
+
+        const response = await getComplainsByPostingUser(1);
+
+        expect(axios.get).toHaveBeenCalledWith('http://localhost:8080/complain/from/1');
+        expect(response).toEqual(mockData.data);
+    });
+
+    test('getAllComplains should fetch all complains', async () => {
+        const mockData = { data: [{ complainid: 1 }] };
+        axios.get.mockResolvedValue(mockData);
+
+        const response = await getAllComplains();
+
+        expect(axios.get).toHaveBeenCalledWith('http://localhost:8080/complain/all');
+        expect(response).toEqual(mockData.data);
+    });
+
+    test('getComplainDetailById should fetch detailed complain info', async () => {
+        const mockData = { data: { complainid: 1, puse: 1, duser: 2 } };
+        axios.get.mockResolvedValue(mockData);
+
+        const response = await getComplainDetailById(1);
+
+        expect(axios.get).toHaveBeenCalledWith('http://localhost:8080/complain/detail/1');
+        expect(response).toEqual(mockData.data);
+    });
+});
diff --git a/src/api/complain.js b/src/api/complain.js
new file mode 100644
index 0000000..8ac3e03
--- /dev/null
+++ b/src/api/complain.js
@@ -0,0 +1,80 @@
+import axios from 'axios';
+
+const BASE_URL = 'http://localhost:8080/complain';
+
+// 创建投诉
+export const createComplain = async (complainData) => {
+    try {
+        const response = await axios.post(`${BASE_URL}/create`, complainData);
+        return response.data;
+    } catch (error) {
+        console.error('创建投诉失败:', error);
+        throw error;
+    }
+};
+
+// 删除投诉(根据 complainid)
+export const deleteComplain = async (complainid) => {
+    try {
+        const response = await axios.delete(`${BASE_URL}/delete/${complainid}`);
+        return response.data;
+    } catch (error) {
+        console.error('删除投诉失败:', error);
+        throw error;
+    }
+};
+
+// 更新投诉
+export const updateComplain = async (complainData) => {
+    try {
+        const response = await axios.put(`${BASE_URL}/update`, complainData);
+        return response.data;
+    } catch (error) {
+        console.error('更新投诉失败:', error);
+        throw error;
+    }
+};
+
+// 获取某个被投诉用户的所有投诉记录(根据 duser)
+export const getComplainsByTargetUser = async (duser) => {
+    try {
+        const response = await axios.get(`${BASE_URL}/target/${duser}`);
+        return response.data;
+    } catch (error) {
+        console.error('获取被投诉用户的投诉记录失败:', error);
+        throw error;
+    }
+};
+
+// 获取某个投诉发起者的所有投诉记录(根据 puse)
+export const getComplainsByPostingUser = async (puse) => {
+    try {
+        const response = await axios.get(`${BASE_URL}/from/${puse}`);
+        return response.data;
+    } catch (error) {
+        console.error('获取用户提交的投诉记录失败:', error);
+        throw error;
+    }
+};
+
+// ✅ 获取所有投诉记录
+export const getAllComplains = async () => {
+    try {
+        const response = await axios.get(`${BASE_URL}/all`);
+        return response.data;
+    } catch (error) {
+        console.error('获取所有投诉记录失败:', error);
+        throw error;
+    }
+};
+
+// ✅ 根据投诉 ID 获取详情(包含投诉用户、被投诉用户、种子号等)
+export const getComplainDetailById = async (complainid) => {
+    try {
+        const response = await axios.get(`${BASE_URL}/detail/${complainid}`);
+        return response.data;
+    } catch (error) {
+        console.error('获取投诉详情失败:', error);
+        throw error;
+    }
+};
diff --git a/src/components/ComplainAdminPanel.jsx b/src/components/ComplainAdminPanel.jsx
new file mode 100644
index 0000000..a7985be
--- /dev/null
+++ b/src/components/ComplainAdminPanel.jsx
@@ -0,0 +1,149 @@
+import React, { useEffect, useState } from 'react';
+import {
+    Table,
+    Button,
+    Modal,
+    message,
+    Tag,
+    Space,
+    Tooltip,
+} from 'antd';
+import { ExclamationCircleOutlined } from '@ant-design/icons';
+import {
+    getAllComplains,
+    deleteComplain,
+    // 预留:你后续可以新增处理投诉的API
+} from '../api/complain';
+import { useNavigate } from 'react-router-dom';
+
+const { confirm } = Modal;
+
+const ComplainAdminPanel = () => {
+    const [complains, setComplains] = useState([]);
+    const [loading, setLoading] = useState(false);
+    const navigate = useNavigate();
+
+    const fetchComplains = async () => {
+        setLoading(true);
+        try {
+            const data = await getAllComplains();
+            setComplains(data);
+        } catch (error) {
+            message.error('获取投诉记录失败');
+        } finally {
+            setLoading(false);
+        }
+    };
+
+    useEffect(() => {
+        fetchComplains();
+    }, []);
+
+    const showDeleteConfirm = (complainid) => {
+        confirm({
+            title: '确认删除该投诉记录吗?',
+            icon: <ExclamationCircleOutlined />,
+            okText: '删除',
+            okType: 'danger',
+            cancelText: '取消',
+            onOk() {
+                handleDelete(complainid);
+            },
+        });
+    };
+
+    const handleDelete = async (complainid) => {
+        try {
+            const success = await deleteComplain(complainid);
+            if (success) {
+                message.success('删除成功');
+                fetchComplains();
+            } else {
+                message.error('删除失败');
+            }
+        } catch {
+            message.error('删除请求失败');
+        }
+    };
+
+    const handleProcess = (complain) => {
+        const { complainid, duser, torrentid } = complain;
+        navigate(`/complain-process/${complainid}`, {
+            state: { complainid, duser, torrentid },
+        });
+    };
+
+    const columns = [
+        {
+            title: '投诉ID',
+            dataIndex: 'complainid',
+            key: 'complainid',
+            width: 80,
+            fixed: 'left',
+        },
+        {
+            title: '投诉人ID',
+            dataIndex: 'puse',
+            key: 'puse',
+            width: 120,
+        },
+        {
+            title: '被投诉人ID',
+            dataIndex: 'duser',
+            key: 'duser',
+            width: 120,
+        },
+        {
+            title: '投诉内容',
+            dataIndex: 'content',
+            key: 'content',
+            ellipsis: { showTitle: false },
+            render: (text) => (
+                <Tooltip placement="topLeft" title={text}>
+                    {text}
+                </Tooltip>
+            ),
+        },
+        {
+            title: '相关种子ID',
+            dataIndex: 'torrentid',
+            key: 'torrentid',
+            width: 120,
+            render: (val) => val ?? <Tag color="default">无</Tag>,
+        },
+        {
+            title: '操作',
+            key: 'action',
+            fixed: 'right',
+            width: 150,
+            render: (_, record) => (
+                <Space size="middle">
+                    <Button type="primary" onClick={() => handleProcess(record)}>
+                        处理
+                    </Button>
+                    <Button danger onClick={() => showDeleteConfirm(record.complainid)}>
+                        删除
+                    </Button>
+                </Space>
+            ),
+        },
+    ];
+
+    return (
+        <div style={{ padding: 20 }}>
+            <h2 style={{ marginBottom: 20 }}>投诉管理面板</h2>
+            <Table
+                rowKey="complainid"
+                columns={columns}
+                dataSource={complains}
+                loading={loading}
+                scroll={{ x: 1000 }}
+                pagination={{ pageSize: 10 }}
+                bordered
+                size="middle"
+            />
+        </div>
+    );
+};
+
+export default ComplainAdminPanel;