import React, { useMemo, useState } from 'react'; import { useNavigate, useParams } from 'react-router-dom'; import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { App, Card, Steps, Row, Col, Statistic, Typography, Upload, Button, Space, Tag, Descriptions, Progress, Alert, } from 'antd'; import { CloudUploadOutlined, ScanOutlined, MergeCellsOutlined, ApartmentOutlined, AuditOutlined, FileTextOutlined, InboxOutlined, RightOutlined, PlayCircleOutlined, ThunderboltOutlined, } from '@ant-design/icons'; import { fetchCase, fetchImages, fetchTransactions, fetchAssessments, uploadImages, startCaseOcr, triggerAnalysis } from '../../services/api'; import type { EvidenceImage } from '../../types'; const { Dragger } = Upload; const Workspace: React.FC = () => { const { id = '1' } = useParams(); const navigate = useNavigate(); const queryClient = useQueryClient(); const { message } = App.useApp(); const [uploadingCount, setUploadingCount] = useState(0); 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 ocrMutation = useMutation({ mutationFn: () => startCaseOcr(id, false), onMutate: () => { message.open({ key: 'workspace-ocr', type: 'loading', content: '正在提交 OCR 任务...', duration: 0, }); }, onSuccess: (res) => { message.open({ key: 'workspace-ocr', type: 'success', content: res.message, }); queryClient.invalidateQueries({ queryKey: ['images', id] }); }, onError: () => { message.open({ key: 'workspace-ocr', type: 'error', content: 'OCR任务提交失败', }); }, }); const analysisMutation = useMutation({ mutationFn: () => triggerAnalysis(id), onMutate: () => { message.open({ key: 'workspace-analysis', type: 'loading', content: '正在提交案件分析任务...', duration: 0, }); }, onSuccess: (res) => { message.open({ key: 'workspace-analysis', type: 'success', content: res.message || '分析任务已提交', }); queryClient.invalidateQueries({ queryKey: ['assessments', id] }); queryClient.invalidateQueries({ queryKey: ['suggestions', id] }); queryClient.invalidateQueries({ queryKey: ['transactions', id] }); queryClient.invalidateQueries({ queryKey: ['flows', id] }); queryClient.invalidateQueries({ queryKey: ['case', id] }); }, onError: () => { message.open({ key: 'workspace-analysis', type: 'error', content: '案件分析提交失败', }); }, }); 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; const currentStep = useMemo(() => { if (images.length === 0) return 0; const doneCount = images.filter((i: EvidenceImage) => i.ocrStatus === 'done').length; if (doneCount < images.length) return 1; if (txList.length === 0) return 2; if (assessments.length === 0) return 3; if (pendingReview > 0) return 4; return 5; }, [images, txList.length, assessments.length, pendingReview]); if (!currentCase) return null; const steps = [ { title: '上传截图', icon: , description: `${images.length} 张已上传`, }, { title: 'OCR识别', icon: , description: `${images.filter((i: EvidenceImage) => i.ocrStatus === 'done').length}/${images.length} 已完成`, }, { title: '交易归并', icon: , description: `${txList.length} 笔交易`, }, { title: '资金分析', icon: , description: assessments.length > 0 ? `已完成,${assessments.length} 笔认定` : '待分析', }, { title: '认定复核', icon: , description: `${pendingReview} 笔待复核`, }, { title: '报告导出', icon: , description: '待生成', }, ]; return (
{currentCase.title} 待复核 {currentCase.caseNo} · 承办人:{currentCase.handler} · 受害人:{currentCase.victimName} 支持 JPG/PNG,可批量拖拽 } > { setUploadingCount((c) => c + 1); message.open({ key: 'img-upload', type: 'loading', content: `正在上传截图(队列中 ${uploadingCount + 1} 张)...`, duration: 0, }); uploadImages(id, [file as File]) .then(() => { message.success('截图上传成功'); }) .then(() => { queryClient.invalidateQueries({ queryKey: ['images', id] }); queryClient.invalidateQueries({ queryKey: ['case', id] }); }) .catch(() => { message.error('上传失败'); }) .finally(() => { setUploadingCount((c) => { const next = Math.max(0, c - 1); if (next === 0) { message.destroy('img-upload'); } else { message.open({ key: 'img-upload', type: 'loading', content: `正在上传截图(队列中 ${next} 张)...`, duration: 0, }); } return next; }); }); return false; }} style={{ padding: '20px 0' }} >

点击或拖拽手机账单截图到此区域

支持微信、支付宝、银行APP、数字钱包等多种来源截图

{uploadingCount > 0 && ( 当前有 {uploadingCount} 张截图正在上传,请稍候... )}
i.ocrStatus === 'done').length / images.length) * 100 : 0, )} size={80} />
OCR 识别率
!t.isDuplicate).length / txList.length) * 100 : 0, )} size={80} strokeColor="#52c41a" />
去重有效率
高置信占比
{images.length} 张 微信 支付宝 银行 数字钱包 {txList.length} 笔 {txList.filter((t) => !t.isDuplicate).length} 笔 2 个 {pendingReview} 笔 {pendingReview > 0 && ( navigate(`/cases/${id}/review`)} > 立即复核 } /> )}
); }; export default Workspace;