import React, { useCallback, useEffect, useState } from 'react'; import { useParams } from 'react-router-dom'; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { Card, Button, Space, Typography, Row, Col, Statistic, Table, Tag, Divider, Descriptions, Checkbox, message, Result, } from 'antd'; import { FileTextOutlined, FileExcelOutlined, FilePdfOutlined, FileWordOutlined, DownloadOutlined, PrinterOutlined, HistoryOutlined, CheckCircleOutlined, } from '@ant-design/icons'; import type { ColumnsType } from 'antd/es/table'; import { fetchCase, fetchAssessments, fetchReports, generateReport, getReportDownloadUrl } from '../../services/api'; type ContentKeys = | 'include_summary' | 'include_transactions' | 'include_flow_chart' | 'include_timeline' | 'include_reasons' | 'include_inquiry' | 'include_screenshots'; const contentOptions: Array<{ key: ContentKeys; label: string; defaultOn: boolean }> = [ { key: 'include_summary', label: '被骗金额汇总表', defaultOn: true }, { key: 'include_transactions', label: '交易明细清单(含证据索引)', defaultOn: true }, { key: 'include_flow_chart', label: '资金流转路径图', defaultOn: true }, { key: 'include_timeline', label: '交易时间轴', defaultOn: true }, { key: 'include_reasons', label: '认定理由与排除说明', defaultOn: true }, { key: 'include_inquiry', label: '笔录辅助问询建议', defaultOn: false }, { key: 'include_screenshots', label: '原始截图附件', defaultOn: false }, ]; const STORAGE_PREFIX = 'report-content-'; const loadContentSelection = (caseId: string): Record => { try { const raw = localStorage.getItem(`${STORAGE_PREFIX}${caseId}`); if (raw) return JSON.parse(raw); } catch { /* ignore */ } const defaults: Record = {}; contentOptions.forEach((o) => { defaults[o.key] = o.defaultOn; }); return defaults as Record; }; const saveContentSelection = (caseId: string, sel: Record) => { try { localStorage.setItem(`${STORAGE_PREFIX}${caseId}`, JSON.stringify(sel)); } catch { /* ignore */ } }; const Reports: React.FC = () => { const { id = '1' } = useParams(); const qc = useQueryClient(); const [generated, setGenerated] = useState(false); const [selectedFormats, setSelectedFormats] = useState>([ 'excel', 'pdf', ]); const [contentSel, setContentSel] = useState>(() => loadContentSelection(id)); useEffect(() => { setContentSel(loadContentSelection(id)); }, [id]); const toggleContent = useCallback((key: ContentKeys, checked: boolean) => { setContentSel((prev) => { const next = { ...prev, [key]: checked }; saveContentSelection(id, next); return next; }); }, [id]); const { data: currentCase } = useQuery({ queryKey: ['case', id], queryFn: () => fetchCase(id) }); const { data: assessData } = useQuery({ queryKey: ['assessments', id], queryFn: () => fetchAssessments(id) }); const { data: reportsData } = useQuery({ queryKey: ['reports', id], queryFn: () => fetchReports(id) }); const allAssessments = assessData?.items ?? []; const reportsList = reportsData?.items ?? []; const confirmedAssessments = allAssessments.filter( (a) => a.reviewStatus === 'confirmed' && a.assessedAmount > 0, ); const totalConfirmed = confirmedAssessments.reduce( (s, a) => s + a.assessedAmount, 0, ); const genMutation = useMutation({ mutationFn: async (reportTypes: Array<'excel' | 'pdf' | 'word'>) => { const result = await Promise.all( reportTypes.map((reportType) => generateReport(id, { report_type: reportType, ...contentSel }), ), ); return result; }, onSuccess: (_res, vars) => { setGenerated(true); qc.invalidateQueries({ queryKey: ['reports', id] }); message.success(`报告生成成功:${vars.map((v) => v.toUpperCase()).join(' / ')}`); }, onError: () => { message.error('报告生成失败'); }, }); if (!currentCase) return null; const latestReportByType = reportsList.reduce>((acc, report) => { if (!acc[report.reportType]) { acc[report.reportType] = report; } return acc; }, {}); const historyColumns: ColumnsType<(typeof reportsList)[0]> = [ { title: '类型', dataIndex: 'reportType', width: 100, render: (t: string) => { const map: Record = { pdf: { icon: , label: 'PDF', color: 'red' }, excel: { icon: , label: 'Excel', color: 'green' }, word: { icon: , label: 'Word', color: 'blue' }, }; const cfg = map[t] || map.pdf; return {cfg.label}; }, }, { title: '版本', dataIndex: 'version', width: 80, render: (v: number) => `v${v}`, }, { title: '生成时间', dataIndex: 'createdAt', width: 180, }, { title: '操作', width: 120, render: (_, report) => ( ), }, ]; return (
} valueStyle={{ color: '#52c41a' }} /> } /> 生成报告 } style={{ marginBottom: 24 }} > {currentCase.caseNo} {currentCase.title} {currentCase.victimName} {currentCase.handler} ¥{totalConfirmed.toLocaleString('zh-CN', { minimumFractionDigits: 2 })} {confirmedAssessments.length} 笔 导出格式:
Excel 汇总表
{ setSelectedFormats((prev) => e.target.checked ? Array.from(new Set([...prev, 'excel'])) : prev.filter((x) => x !== 'excel'), ); }} > 选择
PDF 报告
{ setSelectedFormats((prev) => e.target.checked ? Array.from(new Set([...prev, 'pdf'])) : prev.filter((x) => x !== 'pdf'), ); }} > 选择
Word 文书
{ setSelectedFormats((prev) => e.target.checked ? Array.from(new Set([...prev, 'word'])) : prev.filter((x) => x !== 'word'), ); }} > 选择
报告内容:
{contentOptions.map((opt) => ( toggleContent(opt.key, e.target.checked)} > {opt.label} ))}
{!generated ? ( ) : ( } href={latestReportByType.excel ? getReportDownloadUrl(latestReportByType.excel.id) : undefined} target="_blank" disabled={!latestReportByType.excel} > 下载 Excel , , , , ]} /> )}
历史报告 } style={{ marginBottom: 24 }} >
{generated ? '点击左侧"下载"查看完整报告' : '生成报告后可在此预览'}
); }; export default Reports;