301 lines
9.5 KiB
TypeScript
301 lines
9.5 KiB
TypeScript
|
|
import React, { useState } from 'react';
|
|||
|
|
import { useNavigate, useParams } from 'react-router-dom';
|
|||
|
|
import { useQuery } from '@tanstack/react-query';
|
|||
|
|
import {
|
|||
|
|
Card,
|
|||
|
|
Steps,
|
|||
|
|
Row,
|
|||
|
|
Col,
|
|||
|
|
Statistic,
|
|||
|
|
Typography,
|
|||
|
|
Upload,
|
|||
|
|
Button,
|
|||
|
|
Space,
|
|||
|
|
Tag,
|
|||
|
|
Descriptions,
|
|||
|
|
Progress,
|
|||
|
|
Alert,
|
|||
|
|
Divider,
|
|||
|
|
message,
|
|||
|
|
} from 'antd';
|
|||
|
|
import {
|
|||
|
|
CloudUploadOutlined,
|
|||
|
|
ScanOutlined,
|
|||
|
|
MergeCellsOutlined,
|
|||
|
|
ApartmentOutlined,
|
|||
|
|
AuditOutlined,
|
|||
|
|
FileTextOutlined,
|
|||
|
|
InboxOutlined,
|
|||
|
|
RightOutlined,
|
|||
|
|
} from '@ant-design/icons';
|
|||
|
|
import { fetchCase, fetchImages, fetchTransactions, fetchAssessments, uploadImages } from '../../services/api';
|
|||
|
|
|
|||
|
|
const { Dragger } = Upload;
|
|||
|
|
|
|||
|
|
const Workspace: React.FC = () => {
|
|||
|
|
const { id = '1' } = useParams();
|
|||
|
|
const navigate = useNavigate();
|
|||
|
|
const [currentStep, setCurrentStep] = useState(3);
|
|||
|
|
|
|||
|
|
const { data: currentCase } = useQuery({ queryKey: ['case', id], queryFn: () => fetchCase(id) });
|
|||
|
|
const { data: imagesData } = useQuery({ queryKey: ['images', id], queryFn: () => fetchImages(id) });
|
|||
|
|
const { data: txData } = useQuery({ queryKey: ['transactions', id], queryFn: () => fetchTransactions(id) });
|
|||
|
|
const { data: assessData } = useQuery({ queryKey: ['assessments', id], queryFn: () => fetchAssessments(id) });
|
|||
|
|
|
|||
|
|
const images = imagesData ?? [];
|
|||
|
|
const txList = txData?.items ?? [];
|
|||
|
|
const assessments = assessData?.items ?? [];
|
|||
|
|
|
|||
|
|
const highConfirm = assessments.filter((a) => a.confidenceLevel === 'high').length;
|
|||
|
|
const pendingReview = assessments.filter((a) => a.reviewStatus === 'pending').length;
|
|||
|
|
|
|||
|
|
if (!currentCase) return null;
|
|||
|
|
|
|||
|
|
const steps = [
|
|||
|
|
{
|
|||
|
|
title: '上传截图',
|
|||
|
|
icon: <CloudUploadOutlined />,
|
|||
|
|
description: `${images.length} 张已上传`,
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
title: 'OCR识别',
|
|||
|
|
icon: <ScanOutlined />,
|
|||
|
|
description: `${images.filter((i: any) => i.ocrStatus === 'done').length}/${images.length} 已完成`,
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
title: '交易归并',
|
|||
|
|
icon: <MergeCellsOutlined />,
|
|||
|
|
description: `${txList.length} 笔交易`,
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
title: '资金分析',
|
|||
|
|
icon: <ApartmentOutlined />,
|
|||
|
|
description: '已生成路径图',
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
title: '认定复核',
|
|||
|
|
icon: <AuditOutlined />,
|
|||
|
|
description: `${pendingReview} 笔待复核`,
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
title: '报告导出',
|
|||
|
|
icon: <FileTextOutlined />,
|
|||
|
|
description: '待生成',
|
|||
|
|
},
|
|||
|
|
];
|
|||
|
|
|
|||
|
|
return (
|
|||
|
|
<div>
|
|||
|
|
<Card style={{ marginBottom: 24 }}>
|
|||
|
|
<Row justify="space-between" align="middle">
|
|||
|
|
<Col>
|
|||
|
|
<Space direction="vertical" size={4}>
|
|||
|
|
<Space>
|
|||
|
|
<Typography.Title level={4} style={{ margin: 0 }}>
|
|||
|
|
{currentCase.title}
|
|||
|
|
</Typography.Title>
|
|||
|
|
<Tag color="orange">待复核</Tag>
|
|||
|
|
</Space>
|
|||
|
|
<Typography.Text type="secondary">
|
|||
|
|
{currentCase.caseNo} · 承办人:{currentCase.handler} · 受害人:{currentCase.victimName}
|
|||
|
|
</Typography.Text>
|
|||
|
|
</Space>
|
|||
|
|
</Col>
|
|||
|
|
<Col>
|
|||
|
|
<Statistic
|
|||
|
|
title="当前识别被骗金额"
|
|||
|
|
value={currentCase.totalAmount}
|
|||
|
|
precision={2}
|
|||
|
|
prefix="¥"
|
|||
|
|
valueStyle={{ color: '#cf1322', fontSize: 28 }}
|
|||
|
|
/>
|
|||
|
|
</Col>
|
|||
|
|
</Row>
|
|||
|
|
</Card>
|
|||
|
|
|
|||
|
|
<Card style={{ marginBottom: 24 }}>
|
|||
|
|
<Steps
|
|||
|
|
current={currentStep}
|
|||
|
|
items={steps}
|
|||
|
|
onChange={(v) => setCurrentStep(v)}
|
|||
|
|
/>
|
|||
|
|
</Card>
|
|||
|
|
|
|||
|
|
<Row gutter={24}>
|
|||
|
|
<Col span={16}>
|
|||
|
|
<Card
|
|||
|
|
title="快速上传截图"
|
|||
|
|
style={{ marginBottom: 24 }}
|
|||
|
|
extra={
|
|||
|
|
<Typography.Text type="secondary">
|
|||
|
|
支持 JPG/PNG,可批量拖拽
|
|||
|
|
</Typography.Text>
|
|||
|
|
}
|
|||
|
|
>
|
|||
|
|
<Dragger
|
|||
|
|
multiple
|
|||
|
|
accept="image/*"
|
|||
|
|
showUploadList={false}
|
|||
|
|
beforeUpload={(file, fileList) => {
|
|||
|
|
uploadImages(id, fileList as unknown as File[])
|
|||
|
|
.then(() => message.success('截图上传成功'))
|
|||
|
|
.catch(() => message.error('上传失败'));
|
|||
|
|
return false;
|
|||
|
|
}}
|
|||
|
|
style={{ padding: '20px 0' }}
|
|||
|
|
>
|
|||
|
|
<p className="ant-upload-drag-icon">
|
|||
|
|
<InboxOutlined style={{ fontSize: 48, color: '#1677ff' }} />
|
|||
|
|
</p>
|
|||
|
|
<p className="ant-upload-text">
|
|||
|
|
点击或拖拽手机账单截图到此区域
|
|||
|
|
</p>
|
|||
|
|
<p className="ant-upload-hint">
|
|||
|
|
支持微信、支付宝、银行APP、数字钱包等多种来源截图
|
|||
|
|
</p>
|
|||
|
|
</Dragger>
|
|||
|
|
</Card>
|
|||
|
|
|
|||
|
|
<Card title="处理进度">
|
|||
|
|
<Row gutter={[24, 16]}>
|
|||
|
|
<Col span={8}>
|
|||
|
|
<div style={{ textAlign: 'center' }}>
|
|||
|
|
<Progress
|
|||
|
|
type="circle"
|
|||
|
|
percent={Math.round(
|
|||
|
|
images.length ? (images.filter((i: any) => i.ocrStatus === 'done').length /
|
|||
|
|
images.length) *
|
|||
|
|
100 : 0,
|
|||
|
|
)}
|
|||
|
|
size={80}
|
|||
|
|
/>
|
|||
|
|
<div style={{ marginTop: 8 }}>
|
|||
|
|
<Typography.Text>OCR 识别率</Typography.Text>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</Col>
|
|||
|
|
<Col span={8}>
|
|||
|
|
<div style={{ textAlign: 'center' }}>
|
|||
|
|
<Progress
|
|||
|
|
type="circle"
|
|||
|
|
percent={Math.round(
|
|||
|
|
txList.length ? (txList.filter((t) => !t.isDuplicate).length /
|
|||
|
|
txList.length) *
|
|||
|
|
100 : 0,
|
|||
|
|
)}
|
|||
|
|
size={80}
|
|||
|
|
strokeColor="#52c41a"
|
|||
|
|
/>
|
|||
|
|
<div style={{ marginTop: 8 }}>
|
|||
|
|
<Typography.Text>去重有效率</Typography.Text>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</Col>
|
|||
|
|
<Col span={8}>
|
|||
|
|
<div style={{ textAlign: 'center' }}>
|
|||
|
|
<Progress
|
|||
|
|
type="circle"
|
|||
|
|
percent={Math.round(
|
|||
|
|
assessments.length ? (highConfirm / assessments.length) * 100 : 0,
|
|||
|
|
)}
|
|||
|
|
size={80}
|
|||
|
|
strokeColor="#fa8c16"
|
|||
|
|
/>
|
|||
|
|
<div style={{ marginTop: 8 }}>
|
|||
|
|
<Typography.Text>高置信占比</Typography.Text>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</Col>
|
|||
|
|
</Row>
|
|||
|
|
</Card>
|
|||
|
|
</Col>
|
|||
|
|
|
|||
|
|
<Col span={8}>
|
|||
|
|
<Card title="案件概况" style={{ marginBottom: 24 }}>
|
|||
|
|
<Descriptions column={1} size="small">
|
|||
|
|
<Descriptions.Item label="截图总数">
|
|||
|
|
{images.length} 张
|
|||
|
|
</Descriptions.Item>
|
|||
|
|
<Descriptions.Item label="涉及APP">
|
|||
|
|
<Space>
|
|||
|
|
<Tag color="green">微信</Tag>
|
|||
|
|
<Tag color="blue">支付宝</Tag>
|
|||
|
|
<Tag color="purple">银行</Tag>
|
|||
|
|
<Tag>数字钱包</Tag>
|
|||
|
|
</Space>
|
|||
|
|
</Descriptions.Item>
|
|||
|
|
<Descriptions.Item label="提取交易数">
|
|||
|
|
{txList.length} 笔
|
|||
|
|
</Descriptions.Item>
|
|||
|
|
<Descriptions.Item label="去重后交易">
|
|||
|
|
{txList.filter((t) => !t.isDuplicate).length} 笔
|
|||
|
|
</Descriptions.Item>
|
|||
|
|
<Descriptions.Item label="涉诈对手方">2 个</Descriptions.Item>
|
|||
|
|
<Descriptions.Item label="待复核">
|
|||
|
|
<Typography.Text type="warning">
|
|||
|
|
{pendingReview} 笔
|
|||
|
|
</Typography.Text>
|
|||
|
|
</Descriptions.Item>
|
|||
|
|
</Descriptions>
|
|||
|
|
</Card>
|
|||
|
|
|
|||
|
|
<Card title="快捷操作">
|
|||
|
|
<Space direction="vertical" style={{ width: '100%' }}>
|
|||
|
|
<Button
|
|||
|
|
block
|
|||
|
|
onClick={() => navigate('/cases/1/screenshots')}
|
|||
|
|
icon={<RightOutlined />}
|
|||
|
|
>
|
|||
|
|
查看截图与 OCR 结果
|
|||
|
|
</Button>
|
|||
|
|
<Button
|
|||
|
|
block
|
|||
|
|
onClick={() => navigate('/cases/1/transactions')}
|
|||
|
|
icon={<RightOutlined />}
|
|||
|
|
>
|
|||
|
|
查看交易归并
|
|||
|
|
</Button>
|
|||
|
|
<Button
|
|||
|
|
block
|
|||
|
|
onClick={() => navigate('/cases/1/analysis')}
|
|||
|
|
icon={<RightOutlined />}
|
|||
|
|
>
|
|||
|
|
查看资金分析
|
|||
|
|
</Button>
|
|||
|
|
<Button
|
|||
|
|
block
|
|||
|
|
type="primary"
|
|||
|
|
onClick={() => navigate('/cases/1/review')}
|
|||
|
|
icon={<RightOutlined />}
|
|||
|
|
>
|
|||
|
|
进入认定复核
|
|||
|
|
</Button>
|
|||
|
|
</Space>
|
|||
|
|
</Card>
|
|||
|
|
|
|||
|
|
{pendingReview > 0 && (
|
|||
|
|
<Alert
|
|||
|
|
message={`有 ${pendingReview} 笔交易待人工确认`}
|
|||
|
|
description="系统已完成自动分析,请进入认定复核页面审阅并确认结果。"
|
|||
|
|
type="warning"
|
|||
|
|
showIcon
|
|||
|
|
style={{ marginTop: 24 }}
|
|||
|
|
action={
|
|||
|
|
<Button
|
|||
|
|
size="small"
|
|||
|
|
type="primary"
|
|||
|
|
onClick={() => navigate('/cases/1/review')}
|
|||
|
|
>
|
|||
|
|
立即复核
|
|||
|
|
</Button>
|
|||
|
|
}
|
|||
|
|
/>
|
|||
|
|
)}
|
|||
|
|
</Col>
|
|||
|
|
</Row>
|
|||
|
|
</div>
|
|||
|
|
);
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
export default Workspace;
|