first commit

This commit is contained in:
2026-03-11 16:28:04 +08:00
commit c0f9ddabbf
101 changed files with 11601 additions and 0 deletions

View File

@@ -0,0 +1,256 @@
import React, { useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import {
Card,
Table,
Button,
Tag,
Space,
Input,
Typography,
Modal,
Form,
Row,
Col,
Statistic,
message,
} from 'antd';
import {
PlusOutlined,
SearchOutlined,
FolderOpenOutlined,
ClockCircleOutlined,
CheckCircleOutlined,
ExclamationCircleOutlined,
} from '@ant-design/icons';
import type { ColumnsType } from 'antd/es/table';
import type { CaseRecord, CaseStatus } from '../../types';
import { fetchCases, createCase } from '../../services/api';
const statusConfig: Record<CaseStatus, { color: string; label: string }> = {
pending: { color: 'default', label: '待处理' },
uploading: { color: 'processing', label: '上传中' },
analyzing: { color: 'blue', label: '分析中' },
reviewing: { color: 'orange', label: '待复核' },
completed: { color: 'green', label: '已完成' },
};
const CaseList: React.FC = () => {
const navigate = useNavigate();
const qc = useQueryClient();
const [createOpen, setCreateOpen] = useState(false);
const [form] = Form.useForm();
const [search, setSearch] = useState('');
const { data, isLoading } = useQuery({
queryKey: ['cases', search],
queryFn: () => fetchCases({ search: search || undefined }),
});
const cases = data?.items ?? [];
const createMutation = useMutation({
mutationFn: createCase,
onSuccess: () => {
message.success('案件创建成功');
qc.invalidateQueries({ queryKey: ['cases'] });
setCreateOpen(false);
form.resetFields();
},
});
const totalCases = cases.length;
const pendingReview = cases.filter((c) => c.status === 'reviewing').length;
const completedCount = cases.filter((c) => c.status === 'completed').length;
const analyzingCount = cases.filter(
(c) => c.status === 'analyzing' || c.status === 'uploading',
).length;
const columns: ColumnsType<CaseRecord> = [
{
title: '案件编号',
dataIndex: 'caseNo',
width: 180,
render: (text, record) => (
<a onClick={() => navigate(`/cases/${record.id}/workspace`)}>{text}</a>
),
},
{ title: '案件名称', dataIndex: 'title', ellipsis: true },
{ title: '受害人', dataIndex: 'victimName', width: 100 },
{ title: '承办人', dataIndex: 'handler', width: 100 },
{
title: '状态',
dataIndex: 'status',
width: 100,
render: (s: CaseStatus) => (
<Tag color={statusConfig[s].color}>{statusConfig[s].label}</Tag>
),
},
{
title: '截图数',
dataIndex: 'imageCount',
width: 80,
align: 'center',
},
{
title: '识别金额(元)',
dataIndex: 'totalAmount',
width: 140,
align: 'right',
render: (v: number) =>
v > 0 ? (
<Typography.Text strong style={{ color: '#cf1322' }}>
¥{v.toLocaleString('zh-CN', { minimumFractionDigits: 2 })}
</Typography.Text>
) : (
<Typography.Text type="secondary">-</Typography.Text>
),
},
{
title: '更新时间',
dataIndex: 'updatedAt',
width: 170,
},
{
title: '操作',
width: 160,
render: (_, record) => (
<Space>
<Button
type="link"
size="small"
onClick={() => navigate(`/cases/${record.id}/workspace`)}
>
</Button>
</Space>
),
},
];
return (
<div>
<Row gutter={16} style={{ marginBottom: 24 }}>
<Col span={6}>
<Card variant="borderless">
<Statistic
title="全部案件"
value={totalCases}
prefix={<FolderOpenOutlined />}
/>
</Card>
</Col>
<Col span={6}>
<Card variant="borderless">
<Statistic
title="处理中"
value={analyzingCount}
prefix={<ClockCircleOutlined />}
valueStyle={{ color: '#1677ff' }}
/>
</Card>
</Col>
<Col span={6}>
<Card variant="borderless">
<Statistic
title="待复核"
value={pendingReview}
prefix={<ExclamationCircleOutlined />}
valueStyle={{ color: '#fa8c16' }}
/>
</Card>
</Col>
<Col span={6}>
<Card variant="borderless">
<Statistic
title="已完成"
value={completedCount}
prefix={<CheckCircleOutlined />}
valueStyle={{ color: '#52c41a' }}
/>
</Card>
</Col>
</Row>
<Card
title="案件列表"
extra={
<Space>
<Input
placeholder="搜索案件编号、名称"
prefix={<SearchOutlined />}
style={{ width: 240 }}
allowClear
onPressEnter={(e) => setSearch((e.target as HTMLInputElement).value)}
onChange={(e) => !e.target.value && setSearch('')}
/>
<Button
type="primary"
icon={<PlusOutlined />}
onClick={() => setCreateOpen(true)}
>
</Button>
</Space>
}
>
<Table
rowKey="id"
columns={columns}
dataSource={cases}
loading={isLoading}
pagination={{ pageSize: 10, showSizeChanger: true, showTotal: (t) => `${t}` }}
/>
</Card>
<Modal
title="新建案件"
open={createOpen}
onCancel={() => setCreateOpen(false)}
onOk={() => {
form.validateFields().then((values) => createMutation.mutate(values));
}}
confirmLoading={createMutation.isPending}
okText="创建"
cancelText="取消"
destroyOnClose
>
<Form form={form} layout="vertical" style={{ marginTop: 16 }}>
<Form.Item
label="案件编号"
name="caseNo"
rules={[{ required: true, message: '请输入案件编号' }]}
>
<Input placeholder="如ZA-2026-001XXX" />
</Form.Item>
<Form.Item
label="案件名称"
name="title"
rules={[{ required: true, message: '请输入案件名称' }]}
>
<Input placeholder="如:张某被电信诈骗案" />
</Form.Item>
<Row gutter={16}>
<Col span={12}>
<Form.Item
label="受害人姓名"
name="victimName"
rules={[{ required: true, message: '请输入受害人姓名' }]}
>
<Input />
</Form.Item>
</Col>
<Col span={12}>
<Form.Item label="承办人" name="handler">
<Input />
</Form.Item>
</Col>
</Row>
</Form>
</Modal>
</div>
);
};
export default CaseList;