first commit
This commit is contained in:
329
frontend/src/pages/reports/Reports.tsx
Normal file
329
frontend/src/pages/reports/Reports.tsx
Normal file
@@ -0,0 +1,329 @@
|
||||
import React, { 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,
|
||||
Select,
|
||||
Checkbox,
|
||||
message,
|
||||
Steps,
|
||||
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';
|
||||
|
||||
const Reports: React.FC = () => {
|
||||
const { id = '1' } = useParams();
|
||||
const qc = useQueryClient();
|
||||
const [generated, setGenerated] = useState(false);
|
||||
|
||||
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: (reportType: string) => generateReport(id, { report_type: reportType }),
|
||||
onSuccess: () => {
|
||||
setGenerated(true);
|
||||
qc.invalidateQueries({ queryKey: ['reports', id] });
|
||||
message.success('报告生成成功');
|
||||
},
|
||||
});
|
||||
|
||||
if (!currentCase) return null;
|
||||
|
||||
const historyColumns: ColumnsType<(typeof mockReports)[0]> = [
|
||||
{
|
||||
title: '类型',
|
||||
dataIndex: 'type',
|
||||
width: 100,
|
||||
render: (t: string) => {
|
||||
const map: Record<string, { icon: React.ReactNode; label: string; color: string }> = {
|
||||
pdf: { icon: <FilePdfOutlined />, label: 'PDF', color: 'red' },
|
||||
excel: { icon: <FileExcelOutlined />, label: 'Excel', color: 'green' },
|
||||
word: { icon: <FileWordOutlined />, label: 'Word', color: 'blue' },
|
||||
};
|
||||
const cfg = map[t] || map.pdf;
|
||||
return <Tag icon={cfg.icon} color={cfg.color}>{cfg.label}</Tag>;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '版本',
|
||||
dataIndex: 'version',
|
||||
width: 80,
|
||||
render: (v: number) => `v${v}`,
|
||||
},
|
||||
{
|
||||
title: '生成时间',
|
||||
dataIndex: 'createdAt',
|
||||
width: 180,
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
width: 120,
|
||||
render: () => (
|
||||
<Space>
|
||||
<Button type="link" size="small" icon={<DownloadOutlined />}>
|
||||
下载
|
||||
</Button>
|
||||
</Space>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Row gutter={16} style={{ marginBottom: 24 }}>
|
||||
<Col span={8}>
|
||||
<Card variant="borderless">
|
||||
<Statistic
|
||||
title="已确认被骗金额"
|
||||
value={totalConfirmed}
|
||||
precision={2}
|
||||
prefix="¥"
|
||||
valueStyle={{ color: '#cf1322', fontSize: 24 }}
|
||||
/>
|
||||
</Card>
|
||||
</Col>
|
||||
<Col span={8}>
|
||||
<Card variant="borderless">
|
||||
<Statistic
|
||||
title="已确认交易笔数"
|
||||
value={confirmedAssessments.length}
|
||||
suffix="笔"
|
||||
prefix={<CheckCircleOutlined />}
|
||||
valueStyle={{ color: '#52c41a' }}
|
||||
/>
|
||||
</Card>
|
||||
</Col>
|
||||
<Col span={8}>
|
||||
<Card variant="borderless">
|
||||
<Statistic
|
||||
title="历史报告"
|
||||
value={reportsList.length}
|
||||
suffix="份"
|
||||
prefix={<HistoryOutlined />}
|
||||
/>
|
||||
</Card>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
<Row gutter={24}>
|
||||
<Col span={14}>
|
||||
<Card
|
||||
title={
|
||||
<Space>
|
||||
<FileTextOutlined />
|
||||
<span>生成报告</span>
|
||||
</Space>
|
||||
}
|
||||
style={{ marginBottom: 24 }}
|
||||
>
|
||||
<Descriptions column={2} size="small" style={{ marginBottom: 24 }}>
|
||||
<Descriptions.Item label="案件编号">
|
||||
{currentCase.caseNo}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="案件名称">
|
||||
{currentCase.title}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="受害人">
|
||||
{currentCase.victimName}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="承办人">
|
||||
{currentCase.handler}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="已确认金额">
|
||||
<Typography.Text strong style={{ color: '#cf1322' }}>
|
||||
¥{totalConfirmed.toLocaleString('zh-CN', { minimumFractionDigits: 2 })}
|
||||
</Typography.Text>
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="确认笔数">
|
||||
{confirmedAssessments.length} 笔
|
||||
</Descriptions.Item>
|
||||
</Descriptions>
|
||||
|
||||
<Divider />
|
||||
|
||||
<Typography.Text strong>导出格式:</Typography.Text>
|
||||
<div style={{ margin: '12px 0 24px' }}>
|
||||
<Space size={16}>
|
||||
<Card
|
||||
hoverable
|
||||
style={{ width: 140, textAlign: 'center' }}
|
||||
styles={{ body: { padding: 16 } }}
|
||||
>
|
||||
<FileExcelOutlined style={{ fontSize: 32, color: '#52c41a' }} />
|
||||
<div style={{ marginTop: 8 }}>
|
||||
<Typography.Text>Excel 汇总表</Typography.Text>
|
||||
</div>
|
||||
<div>
|
||||
<Checkbox defaultChecked>选择</Checkbox>
|
||||
</div>
|
||||
</Card>
|
||||
<Card
|
||||
hoverable
|
||||
style={{ width: 140, textAlign: 'center' }}
|
||||
styles={{ body: { padding: 16 } }}
|
||||
>
|
||||
<FilePdfOutlined style={{ fontSize: 32, color: '#cf1322' }} />
|
||||
<div style={{ marginTop: 8 }}>
|
||||
<Typography.Text>PDF 报告</Typography.Text>
|
||||
</div>
|
||||
<div>
|
||||
<Checkbox defaultChecked>选择</Checkbox>
|
||||
</div>
|
||||
</Card>
|
||||
<Card
|
||||
hoverable
|
||||
style={{ width: 140, textAlign: 'center' }}
|
||||
styles={{ body: { padding: 16 } }}
|
||||
>
|
||||
<FileWordOutlined style={{ fontSize: 32, color: '#1677ff' }} />
|
||||
<div style={{ marginTop: 8 }}>
|
||||
<Typography.Text>Word 文书</Typography.Text>
|
||||
</div>
|
||||
<div>
|
||||
<Checkbox>选择</Checkbox>
|
||||
</div>
|
||||
</Card>
|
||||
</Space>
|
||||
</div>
|
||||
|
||||
<Typography.Text strong>报告内容:</Typography.Text>
|
||||
<div style={{ margin: '12px 0 24px' }}>
|
||||
<Space direction="vertical">
|
||||
<Checkbox defaultChecked>被骗金额汇总表</Checkbox>
|
||||
<Checkbox defaultChecked>交易明细清单(含证据索引)</Checkbox>
|
||||
<Checkbox defaultChecked>资金流转路径图</Checkbox>
|
||||
<Checkbox defaultChecked>交易时间轴</Checkbox>
|
||||
<Checkbox defaultChecked>认定理由与排除说明</Checkbox>
|
||||
<Checkbox>笔录辅助问询建议</Checkbox>
|
||||
<Checkbox>原始截图附件</Checkbox>
|
||||
</Space>
|
||||
</div>
|
||||
|
||||
{!generated ? (
|
||||
<Button
|
||||
type="primary"
|
||||
size="large"
|
||||
icon={<FileTextOutlined />}
|
||||
loading={genMutation.isPending}
|
||||
onClick={() => genMutation.mutate('excel')}
|
||||
block
|
||||
>
|
||||
{genMutation.isPending ? '正在生成报告...' : '生成报告'}
|
||||
</Button>
|
||||
) : (
|
||||
<Result
|
||||
status="success"
|
||||
title="报告已生成"
|
||||
subTitle="您可以下载或打印以下报告文件"
|
||||
extra={[
|
||||
<Button
|
||||
key="excel"
|
||||
icon={<DownloadOutlined />}
|
||||
onClick={() => message.info('演示模式:下载 Excel')}
|
||||
>
|
||||
下载 Excel
|
||||
</Button>,
|
||||
<Button
|
||||
key="pdf"
|
||||
type="primary"
|
||||
icon={<DownloadOutlined />}
|
||||
onClick={() => message.info('演示模式:下载 PDF')}
|
||||
>
|
||||
下载 PDF
|
||||
</Button>,
|
||||
<Button
|
||||
key="print"
|
||||
icon={<PrinterOutlined />}
|
||||
onClick={() => message.info('演示模式:打印')}
|
||||
>
|
||||
打印
|
||||
</Button>,
|
||||
]}
|
||||
/>
|
||||
)}
|
||||
</Card>
|
||||
</Col>
|
||||
|
||||
<Col span={10}>
|
||||
<Card
|
||||
title={
|
||||
<Space>
|
||||
<HistoryOutlined />
|
||||
<span>历史报告</span>
|
||||
</Space>
|
||||
}
|
||||
style={{ marginBottom: 24 }}
|
||||
>
|
||||
<Table
|
||||
rowKey="id"
|
||||
columns={historyColumns}
|
||||
dataSource={reportsList}
|
||||
pagination={false}
|
||||
size="small"
|
||||
/>
|
||||
</Card>
|
||||
|
||||
<Card title="报告预览" style={{ minHeight: 300 }}>
|
||||
<div
|
||||
style={{
|
||||
background: '#fafafa',
|
||||
border: '1px dashed #d9d9d9',
|
||||
borderRadius: 8,
|
||||
padding: 24,
|
||||
textAlign: 'center',
|
||||
minHeight: 240,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
}}
|
||||
>
|
||||
<FileTextOutlined style={{ fontSize: 48, color: '#bfbfbf' }} />
|
||||
<Typography.Text type="secondary" style={{ marginTop: 12 }}>
|
||||
{generated
|
||||
? '点击左侧"下载"查看完整报告'
|
||||
: '生成报告后可在此预览'}
|
||||
</Typography.Text>
|
||||
</div>
|
||||
</Card>
|
||||
</Col>
|
||||
</Row>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Reports;
|
||||
Reference in New Issue
Block a user