"use client"; import React, { useEffect, useMemo, useRef } from "react"; import ReactECharts from "echarts-for-react"; import { Box, Button, Dialog, GlobalStyles, IconButton, Stack, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, Typography, } from "@mui/material"; import { Close, PrintOutlined, WaterDropOutlined, } from "@mui/icons-material"; import { PredictionResult } from "./types"; import { buildHealthRiskReportData } from "./healthRiskReport"; interface HealthRiskReportProps { open: boolean; onClose: () => void; predictionResults: PredictionResult[]; currentYear: number; analysisQueryTime: string | null; networkName: string; generatedAt: Date; } const REPORT_BLUE = "#0b4f87"; const REPORT_INK = "#172435"; const REPORT_MUTED = "#5f6f80"; const REPORT_LINE = "#d8e0e8"; const reportFontFamily = '-apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Microsoft YaHei", sans-serif'; const formatDateTime = (value: Date | string | null) => { if (!value) return "未记录"; const date = value instanceof Date ? value : new Date(value); if (Number.isNaN(date.getTime())) return "未记录"; return new Intl.DateTimeFormat("zh-CN", { year: "numeric", month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit", second: "2-digit", hour12: false, }).format(date); }; const formatPercent = (value: number | null, fractionDigits = 1) => value === null ? "无有效数据" : `${(value * 100).toFixed(fractionDigits)}%`; const SectionTitle = ({ children }: { children: React.ReactNode }) => ( ); const HealthRiskReport: React.FC = ({ open, onClose, predictionResults, currentYear, analysisQueryTime, networkName, generatedAt, }) => { const printStateRef = useRef<{ title: string } | null>(null); const reportData = useMemo( () => buildHealthRiskReportData(predictionResults, currentYear), [currentYear, predictionResults], ); useEffect( () => () => { if (printStateRef.current) { document.title = printStateRef.current.title; document.body.classList.remove("health-risk-report-printing"); } }, [], ); const restoreAfterPrint = () => { if (!printStateRef.current) return; document.title = printStateRef.current.title; printStateRef.current = null; document.body.classList.remove("health-risk-report-printing"); }; const handlePrint = () => { if (printStateRef.current) return; printStateRef.current = { title: document.title }; document.title = `管网健康风险体检报告-${networkName || "未命名管网"}-${new Intl.DateTimeFormat( "zh-CN", { year: "numeric", month: "2-digit", day: "2-digit" }, ) .format(generatedAt) .replaceAll("/", "-")}`; document.body.classList.add("health-risk-report-printing"); window.addEventListener("afterprint", restoreAfterPrint, { once: true }); window.print(); }; const distributionOption = { animation: false, grid: { top: 4, right: 38, bottom: 28, left: 148 }, xAxis: { type: "value", name: "管段数", minInterval: 1, axisLine: { lineStyle: { color: "#8b99a8" } }, axisLabel: { color: REPORT_MUTED, fontSize: 10 }, splitLine: { lineStyle: { color: "#e7edf2" } }, nameTextStyle: { color: REPORT_MUTED, fontSize: 10 }, }, yAxis: { type: "category", inverse: true, data: reportData.distribution.map((item) => item.label), axisLine: { show: false }, axisTick: { show: false }, axisLabel: { color: REPORT_INK, fontSize: 10 }, }, tooltip: { show: false }, series: [ { type: "bar", barMaxWidth: 14, data: reportData.distribution.map((item) => ({ value: item.count, itemStyle: { color: item.color }, })), label: { show: true, position: "right", color: REPORT_INK, fontSize: 10, }, }, ], }; const trendOption = { animation: false, grid: { top: 24, right: 24, bottom: 38, left: 48 }, xAxis: { type: "category", name: "预测年份", boundaryGap: false, data: reportData.trend.map((item) => item.year), axisLine: { lineStyle: { color: "#8b99a8" } }, axisLabel: { color: REPORT_MUTED, fontSize: 10, interval: Math.max(0, Math.ceil(reportData.trend.length / 9) - 1), }, nameTextStyle: { color: REPORT_MUTED, fontSize: 10 }, }, yAxis: { type: "value", name: "较高及以上风险管段数", minInterval: 1, axisLine: { show: false }, axisLabel: { color: REPORT_MUTED, fontSize: 10 }, splitLine: { lineStyle: { color: "#e7edf2" } }, nameTextStyle: { color: REPORT_MUTED, fontSize: 10 }, }, tooltip: { show: false }, series: [ { type: "line", smooth: true, symbol: "none", lineStyle: { color: "#c83d3d", width: 2.5 }, areaStyle: { color: "rgba(200, 61, 61, 0.08)" }, data: reportData.trend.map((item) => item.count), }, ], }; const metricItems = [ { label: "分析管段", value: reportData.totalCount.toLocaleString("zh-CN") }, { label: "有效结果", value: reportData.validCount.toLocaleString("zh-CN") }, { label: "平均生存概率", value: formatPercent(reportData.averageProbability), }, { label: "较高及以上风险", value: `${reportData.highRiskCount.toLocaleString("zh-CN")} 条`, detail: formatPercent(reportData.highRiskRatio), danger: reportData.highRiskCount > 0, }, ]; return ( <> *:not(.health-risk-report-dialog)": { display: "none !important", }, ".health-risk-report-dialog": { position: "static !important", display: "block !important", }, ".health-risk-report-dialog .MuiBackdrop-root, .health-risk-report-actions": { display: "none !important", }, ".health-risk-report-dialog .MuiDialog-container": { display: "block !important", height: "auto !important", }, ".health-risk-report-dialog .MuiDialog-paper": { width: "210mm !important", maxWidth: "none !important", minHeight: "297mm !important", maxHeight: "none !important", margin: "0 !important", overflow: "visible !important", boxShadow: "none !important", }, ".health-risk-report-preview": { padding: "0 !important", overflow: "visible !important", backgroundColor: "#fff !important", }, "#health-risk-print-root": { width: "210mm !important", minHeight: "297mm !important", padding: "12mm !important", boxShadow: "none !important", printColorAdjust: "exact", WebkitPrintColorAdjust: "exact", }, ".health-risk-report-section, .health-risk-report-metrics": { breakInside: "avoid", pageBreakInside: "avoid", }, ".health-risk-report-table-row": { breakInside: "avoid", pageBreakInside: "avoid", }, }, }} /> 报告预览 A4 纵向,可在打印窗口另存为 PDF 管网健康风险体检报告 Pipeline health risk assessment {reportData.highRiskCount > 0 ? "需重点关注" : "持续监测"} {[ ["管网项目", networkName || "未命名管网"], ["预测时点", `第 ${currentYear} 年`], ["分析数据时间", formatDateTime(analysisQueryTime)], ["报告生成时间", formatDateTime(generatedAt)], ].map(([label, value]) => ( {label} {value} ))} 总体结论 0 ? "#fff4f1" : "#eff8f4", px: 2.25, py: 1.75, }} > {reportData.conclusion} {reportData.invalidCount > 0 && ( 另有 {reportData.invalidCount} 条管段在当前预测年份缺少有效结果,未纳入本期统计。 )} {metricItems.map((item, index) => ( {item.label} {item.value} {item.detail && ( 占有效结果 {item.detail} )} ))} 当前年份风险分布 生存概率越低,表示模型预测风险越高。统计口径与地图风险图例一致。 重点风险趋势 展示各预测年份中生存概率不高于 0.3 的管段数量。 重点风险管段 按当前预测年份的生存概率从低到高排列,最多展示 20 条。 {reportData.priorityPipes.length > 0 ? ( 序号 管段编号 生存概率 风险等级 {reportData.priorityPipes.map((pipe, index) => ( {index + 1} {pipe.linkId} {formatPercent(pipe.probability, 2)} ))}
) : ( 当前预测年份没有较高及以上风险管段。 )}
处置建议 {reportData.recommendations.map((recommendation) => ( {recommendation} ))} TJWater 管网分析平台 风险阈值:生存概率 ≤ 0.3
); }; export default HealthRiskReport;