2026-03-11 16:28:04 +08:00
|
|
|
|
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
|
2026-03-12 20:04:27 +08:00
|
|
|
|
type="default"
|
2026-03-11 16:28:04 +08:00
|
|
|
|
size="small"
|
2026-03-12 20:04:27 +08:00
|
|
|
|
style={{
|
|
|
|
|
|
background: '#e6f4ff',
|
|
|
|
|
|
borderColor: '#91caff',
|
|
|
|
|
|
color: '#0958d9',
|
|
|
|
|
|
fontWeight: 600,
|
|
|
|
|
|
boxShadow: '0 1px 2px rgba(22, 119, 255, 0.12)',
|
|
|
|
|
|
}}
|
2026-03-11 16:28:04 +08:00
|
|
|
|
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;
|