"use client"; import React, { useCallback, useEffect, useMemo, useRef, useState, } from "react"; import { Alert, Box, Button, Chip, GlobalStyles, Paper, Portal, Stack, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, Typography, } from "@mui/material"; import { DescriptionOutlined, PrintOutlined, } from "@mui/icons-material"; import { NETWORK_NAME } from "@config/config"; import { clearPipeDiameterCache, getCachedPipeDiameters, getPipeDiameterDisplay, getPipeDiameterQueryKey, loadPipeDiameters, type PipeDiameterMap, } from "./schemePipeDiameters"; import { SchemeRecord, ValveIsolationResult } from "./types"; import { getAffectedNodeCount } from "./valveIsolationScope"; import PanelEmptyState from "@components/olmap/common/PanelEmptyState"; interface AnalysisReportProps { scheme: SchemeRecord | null; valveResult: ValveIsolationResult | null; disabledValves: string[]; generatedAt: Date | null; } interface ReportDocumentProps extends AnalysisReportProps { diameters: PipeDiameterMap; loadingDiameters: boolean; } const REPORT_BLUE = "#0b4f87"; const REPORT_INK = "#172435"; const REPORT_MUTED = "#5f6f80"; const REPORT_LINE = "#d8e0e8"; export const clearAnalysisReportDiameterCache = clearPipeDiameterCache; const formatDateTime = (value: Date | string | null | undefined) => { 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 formatDuration = (seconds: number | undefined) => { if (!Number.isFinite(seconds) || seconds === undefined || seconds < 0) { return "未记录"; } const hours = Math.floor(seconds / 3600); const minutes = Math.floor((seconds % 3600) / 60); const remainingSeconds = seconds % 60; return [ hours ? `${hours} 小时` : "", minutes ? `${minutes} 分钟` : "", remainingSeconds || (!hours && !minutes) ? `${remainingSeconds} 秒` : "", ] .filter(Boolean) .join(" "); }; export const matchesValveAnalysis = ( scheme: SchemeRecord | null, valveResult: ValveIsolationResult | null, ) => { const schemePipeIds = new Set(scheme?.schemeDetail?.burst_ID ?? []); const accidentElements = valveResult?.accident_elements ?? []; return ( valveResult !== null && accidentElements.length > 0 && accidentElements.every((pipeId) => schemePipeIds.has(pipeId)) ); }; const SectionTitle = ({ children }: { children: React.ReactNode }) => ( {children} ); const IdList = ({ values, emptyText = "无", }: { values: string[] | undefined; emptyText?: string; }) => values?.length ? ( {values.map((value) => ( ))} ) : ( {emptyText} ); const ReportDocument: React.FC = ({ scheme, valveResult, disabledValves, generatedAt, diameters, loadingDiameters, }) => { if (!scheme) return null; const pipeIds = scheme.schemeDetail?.burst_ID ?? []; const burstSizes = scheme.schemeDetail?.burst_size ?? []; const matchedValveResult = matchesValveAnalysis(scheme, valveResult) ? valveResult : null; const duration = scheme.schemeDetail?.modify_total_duration; const valveDetailRows: Array<[string, string[] | undefined]> = matchedValveResult ? [ ["已分析事故管段", matchedValveResult.accident_elements], ["必关阀门", matchedValveResult.must_close_valves], ["可选阀门", matchedValveResult.optional_valves], ["不可用阀门", disabledValves], ] : []; if (matchedValveResult?.isolatable) { valveDetailRows.push(["受影响节点", matchedValveResult.affected_nodes]); } return ( 爆管分析报告 报告编号:BA-{scheme.id} 生成时间:{formatDateTime(generatedAt)} 方案概况 {[ ["管网", NETWORK_NAME], ["方案名称", scheme.schemeName], ["方案创建人", scheme.username || "未记录"], ["方案创建时间", formatDateTime(scheme.create_time)], ["模拟开始时间", formatDateTime(scheme.startTime)], ["模拟持续时间", formatDuration(duration)], ].map(([label, value]) => ( {label} {value} ))} 爆管模拟参数 序号 爆管管段 管径 爆管面积 {pipeIds.length ? ( pipeIds.map((pipeId, index) => ( {index + 1} {pipeId} {getPipeDiameterDisplay( [pipeId], diameters, loadingDiameters, )} {Number.isFinite(burstSizes[index]) ? `${burstSizes[index]} cm²` : "未记录"} )) ) : ( 未记录爆管管段 )}
分析结论 本方案记录了 {pipeIds.length} 条爆管管段,模拟持续时间为 {formatDuration(duration)}。 {matchedValveResult ? ` 当前关阀分析判定事故管段${ matchedValveResult.isolatable ? "可以" : "无法" }有效隔离。` : " 当前会话尚未对本方案执行匹配的关阀分析。"} 关阀分析 {matchedValveResult ? ( {[ ["隔离结论", matchedValveResult.isolatable ? "可隔离" : "不可隔离"], ["必关阀门", `${matchedValveResult.must_close_valves?.length ?? 0} 个`], ["受影响节点", `${getAffectedNodeCount(matchedValveResult)} 个`], ].map(([label, value]) => ( {label} {value} ))} {valveDetailRows.map(([label, values]) => ( {label} ))} {!matchedValveResult.isolatable && ( 不可隔离,未生成受影响节点清单。 )} ) : ( 本方案尚未执行关阀分析。可在“关阀分析”页签完成分析后重新查看报告。 )}
); }; const AnalysisReport: React.FC = ({ scheme, valveResult, disabledValves, generatedAt, }) => { const pipeIds = useMemo( () => scheme?.schemeDetail?.burst_ID ?? [], [scheme], ); const diameterQueryKey = getPipeDiameterQueryKey(pipeIds); const cachedDiameters = getCachedPipeDiameters(pipeIds); const [diameterState, setDiameterState] = useState<{ queryKey: string | null; values: PipeDiameterMap; }>({ queryKey: null, values: {} }); const [printReady, setPrintReady] = useState(false); const printStateRef = useRef<{ title: string } | null>(null); const diameters = useMemo( () => diameterState.queryKey === diameterQueryKey ? diameterState.values : cachedDiameters ?? {}, [cachedDiameters, diameterQueryKey, diameterState], ); const loadingDiameters = pipeIds.length > 0 && diameterState.queryKey !== diameterQueryKey && !cachedDiameters; useEffect(() => { if (!pipeIds.length) { return; } if (getCachedPipeDiameters(pipeIds)) { return; } let cancelled = false; loadPipeDiameters(pipeIds) .then((nextDiameters) => { if (!cancelled) { setDiameterState({ queryKey: diameterQueryKey, values: nextDiameters, }); } }) .catch((error) => { console.error("查询分析报告管径失败:", error); if (!cancelled) { setDiameterState({ queryKey: diameterQueryKey, values: Object.fromEntries( pipeIds.map((pipeId) => [pipeId, null]), ), }); } }); return () => { cancelled = true; }; }, [diameterQueryKey, pipeIds]); const restoreAfterPrint = useCallback(() => { if (printStateRef.current) { document.title = printStateRef.current.title; printStateRef.current = null; document.body.classList.remove("burst-analysis-report-printing"); } setPrintReady(false); }, []); useEffect( () => () => { restoreAfterPrint(); }, [restoreAfterPrint], ); const handlePrint = () => { if (!scheme || printStateRef.current) return; printStateRef.current = { title: document.title }; document.title = `爆管分析报告-${scheme.schemeName}`; document.body.classList.add("burst-analysis-report-printing"); setPrintReady(true); }; useEffect(() => { if (!printReady) return; window.addEventListener("afterprint", restoreAfterPrint, { once: true }); try { window.print(); } catch (error) { console.error("打印爆管分析报告失败:", error); window.setTimeout(restoreAfterPrint, 0); } return () => { window.removeEventListener("afterprint", restoreAfterPrint); }; }, [printReady, restoreAfterPrint]); const reportDocument = useMemo( () => ( ), [ diameters, disabledValves, generatedAt, loadingDiameters, scheme, valveResult, ], ); if (!scheme) { return ( } title="尚未选择分析方案" description="请在“方案查询”中打开一个方案,再查看分析报告。" /> ); } return ( <> *:not(.burst-analysis-report-print-root)": { display: "none !important" }, ".burst-analysis-report-print-root": { display: "block !important", width: "210mm !important", minHeight: "297mm !important", backgroundColor: "#fff !important", }, ".burst-analysis-report-print-root > div": { padding: "14mm 16mm !important", }, }, }} /> {reportDocument} {printReady && ( )} ); }; export default AnalysisReport;