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,300 @@
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;