/** * 质控报告抽屉组件 * * 功能: * - 展示质控报告摘要 * - 展示严重问题和警告问题列表 * - 展示表单统计 * - 支持导出 XML 格式报告 */ import React, { useState, useEffect } from 'react'; import { Drawer, Tabs, Statistic, Row, Col, Card, Table, Tag, Space, Button, Spin, Empty, Typography, message, Progress, Tooltip, } from 'antd'; import { DownloadOutlined, ReloadOutlined, ExclamationCircleOutlined, WarningOutlined, CheckCircleOutlined, FileTextOutlined, BarChartOutlined, } from '@ant-design/icons'; import type { QcReport } from '../../api/iitProjectApi'; import * as iitProjectApi from '../../api/iitProjectApi'; const { Text } = Typography; const { TabPane } = Tabs; interface QcReportDrawerProps { open: boolean; onClose: () => void; projectId: string; projectName: string; } const QcReportDrawer: React.FC = ({ open, onClose, projectId, projectName, }) => { const [loading, setLoading] = useState(false); const [refreshing, setRefreshing] = useState(false); const [report, setReport] = useState(null); // 加载报告 const loadReport = async (forceRefresh = false) => { if (forceRefresh) { setRefreshing(true); } else { setLoading(true); } try { let data: QcReport; if (forceRefresh) { data = await iitProjectApi.refreshQcReport(projectId); message.success('报告已刷新'); } else { data = await iitProjectApi.getQcReport(projectId, 'json') as QcReport; } setReport(data); } catch (error: any) { message.error(error.message || '加载报告失败'); } finally { setLoading(false); setRefreshing(false); } }; // 导出 XML 报告(导出前自动刷新,确保获取最新数据) const handleExportXml = async () => { try { message.loading({ content: '正在生成最新报告...', key: 'export' }); // 1. 先刷新报告(确保获取最新质控结果) await iitProjectApi.refreshQcReport(projectId); // 2. 获取刷新后的 XML 报告 const xmlData = await iitProjectApi.getQcReport(projectId, 'xml') as string; // 3. 创建 Blob 并下载 const blob = new Blob([xmlData], { type: 'application/xml' }); const url = URL.createObjectURL(blob); const link = document.createElement('a'); link.href = url; const now = new Date(); const dateStr = now.toISOString().split('T')[0]; const timeStr = now.toTimeString().slice(0, 5).replace(':', ''); // HHMM 格式 link.download = `qc-report-${projectId}-${dateStr}-${timeStr}.xml`; document.body.appendChild(link); link.click(); document.body.removeChild(link); URL.revokeObjectURL(url); message.success({ content: '报告已导出', key: 'export' }); } catch (error: any) { message.error({ content: error.message || '导出失败', key: 'export' }); } }; useEffect(() => { if (open) { loadReport(); } }, [open, projectId]); // 渲染摘要 const renderSummary = () => { if (!report) return null; const { summary } = report; // V2.1: 防护空值 if (!summary) { return ( ); } // 安全获取数值 const totalRecords = summary.totalRecords ?? 0; const passRate = summary.passRate ?? 0; const criticalIssues = summary.criticalIssues ?? 0; const warningIssues = summary.warningIssues ?? 0; const completedRecords = summary.completedRecords ?? 0; return (
= 80 ? '#52c41a' : '#ff4d4f' }} /> } /> } /> 完成记录 0 ? Math.round((completedRecords / totalRecords) * 100) : 0} status="active" /> 待确认 报告生成时间: {report.generatedAt ? new Date(report.generatedAt).toLocaleString() : '-'}
最后质控时间: {summary.lastQcTime ? new Date(summary.lastQcTime).toLocaleString() : '-'}
); }; // 渲染问题列表 const renderIssues = (issues: QcReport['criticalIssues'] | QcReport['warningIssues'], type: 'critical' | 'warning') => { const columns = [ { title: '记录 ID', dataIndex: 'recordId', key: 'recordId', width: 100, render: (text: string) => {text}, }, { title: '规则', dataIndex: 'ruleName', key: 'ruleName', width: 150, ellipsis: true, }, { title: '问题描述', dataIndex: 'message', key: 'message', ellipsis: true, render: (text: string) => ( {text} ), }, { title: '字段', dataIndex: 'field', key: 'field', width: 120, render: (text: string) => text ? {text} : '-', }, { title: '检测时间', dataIndex: 'detectedAt', key: 'detectedAt', width: 140, render: (text: string) => text ? new Date(text).toLocaleDateString() : '-', }, ]; return ( `${record.recordId}-${record.ruleId}-${index}`} size="small" pagination={{ pageSize: 10 }} locale={{ emptyText: ( ), }} /> ); }; // 渲染表单统计 const renderFormStats = () => { if (!report?.formStats?.length) { return ; } const columns = [ { title: '表单', dataIndex: 'formLabel', key: 'formLabel', }, { title: '检查数', dataIndex: 'totalChecks', key: 'totalChecks', width: 80, align: 'center' as const, }, { title: '通过', dataIndex: 'passed', key: 'passed', width: 80, align: 'center' as const, render: (text: number) => {text}, }, { title: '失败', dataIndex: 'failed', key: 'failed', width: 80, align: 'center' as const, render: (text: number) => {text}, }, { title: '通过率', dataIndex: 'passRate', key: 'passRate', width: 120, render: (rate: number) => ( = 80 ? 'success' : rate >= 60 ? 'normal' : 'exception'} /> ), }, ]; return (
); }; return ( 质控报告 - {projectName} } placement="right" width={800} open={open} onClose={onClose} extra={ } > {loading ? (
正在生成报告...
) : report ? ( 摘要 } key="summary" > {renderSummary()} 严重问题 {(report.criticalIssues?.length ?? 0) > 0 && ( {report.criticalIssues?.length ?? 0} )} } key="critical" > {renderIssues(report.criticalIssues ?? [], 'critical')} 警告问题 {(report.warningIssues?.length ?? 0) > 0 && ( {report.warningIssues?.length ?? 0} )} } key="warning" > {renderIssues(report.warningIssues ?? [], 'warning')} 表单统计 } key="forms" > {renderFormStats()} ) : ( )}
); }; export default QcReportDrawer;