first commit
This commit is contained in:
256
frontend/src/pages/cases/CaseList.tsx
Normal file
256
frontend/src/pages/cases/CaseList.tsx
Normal 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;
|
||||
Reference in New Issue
Block a user