From a42c95b399fc0d9162e714d5cd5156c53fff2cc6 Mon Sep 17 00:00:00 2001
From: Junjie <fallin.jie@qq.com>
Date: 星期日, 22 三月 2026 18:12:28 +0800
Subject: [PATCH] #

---
 src/main/webapp/static/js/wrkAnalysis/wrkAnalysis.js |  325 ++++++++++++++++++++++++++++++++++++++++++++++++++++--
 1 files changed, 312 insertions(+), 13 deletions(-)

diff --git a/src/main/webapp/static/js/wrkAnalysis/wrkAnalysis.js b/src/main/webapp/static/js/wrkAnalysis/wrkAnalysis.js
index 0214de8..b1904c5 100644
--- a/src/main/webapp/static/js/wrkAnalysis/wrkAnalysis.js
+++ b/src/main/webapp/static/js/wrkAnalysis/wrkAnalysis.js
@@ -62,6 +62,7 @@
                 pageTotal: 0,
                 listLoading: false,
                 analyzeLoading: false,
+                exportingPdf: false,
                 selectedWrkNoMap: {},
                 analysis: createEmptyAnalysis(),
                 analysisReady: false,
@@ -269,6 +270,247 @@
                     }
                 });
             },
+            exportAnalysisPdf: function () {
+                var self = this;
+                if (!this.analysisReady) {
+                    this.$message.warning("璇峰厛鎵ц鍒嗘瀽锛屽啀瀵煎嚭PDF");
+                    return;
+                }
+                if (!window.html2canvas || !window.jspdf || !window.jspdf.jsPDF) {
+                    this.$message.error("PDF瀵煎嚭缁勪欢鍔犺浇澶辫触");
+                    return;
+                }
+                this.exportingPdf = true;
+                this.$nextTick(function () {
+                    self.resizeCharts();
+                    window.setTimeout(function () {
+                        self.generatePdf();
+                    }, 300);
+                });
+            },
+            generatePdf: function () {
+                var self = this;
+                var visualRoot = this.$refs.analysisVisualRoot;
+                var detailRoot = this.$refs.exportDetailRoot;
+                var detailTable = this.$refs.exportDetailTable;
+                var cleanup = function () {
+                    self.exportingPdf = false;
+                    self.$nextTick(function () {
+                        self.resizeCharts();
+                    });
+                };
+                if (!visualRoot || !detailRoot || !detailTable) {
+                    this.$message.error("鏈壘鍒板彲瀵煎嚭鐨勫垎鏋愬尯鍩�");
+                    cleanup();
+                    return;
+                }
+                var jsPDF = window.jspdf.jsPDF;
+                var pdf = new jsPDF("p", "mm", "a4");
+                Promise.all([
+                    window.html2canvas(visualRoot, this.buildCaptureOptions(visualRoot)),
+                    window.html2canvas(detailRoot, this.buildCaptureOptions(detailRoot))
+                ]).then(function (results) {
+                    self.appendCanvasSlicesToPdf(pdf, results[0], {
+                        margin: 8,
+                        startY: 8,
+                        addNewPage: false
+                    });
+                    self.appendDetailTableToPdf(pdf, results[1], detailRoot, detailTable, 8);
+                    pdf.save(self.buildPdfFileName());
+                    self.$message.success("PDF瀵煎嚭鎴愬姛");
+                    cleanup();
+                }).catch(function (error) {
+                    console.error(error);
+                    self.$message.error("PDF瀵煎嚭澶辫触");
+                    cleanup();
+                });
+            },
+            buildCaptureOptions: function (target) {
+                return {
+                    scale: 2,
+                    useCORS: true,
+                    backgroundColor: "#ffffff",
+                    logging: false,
+                    scrollX: 0,
+                    scrollY: -window.scrollY,
+                    width: target.scrollWidth,
+                    height: target.scrollHeight,
+                    windowWidth: Math.max(document.documentElement.clientWidth, target.scrollWidth),
+                    windowHeight: Math.max(document.documentElement.clientHeight, target.scrollHeight)
+                };
+            },
+            appendCanvasSlicesToPdf: function (pdf, canvas, options) {
+                var settings = options || {};
+                var pageWidth = pdf.internal.pageSize.getWidth();
+                var pageHeight = pdf.internal.pageSize.getHeight();
+                var margin = settings.margin == null ? 8 : settings.margin;
+                var startY = settings.startY == null ? margin : settings.startY;
+                var usableWidth = pageWidth - margin * 2;
+                var pxPerMm = canvas.width / usableWidth;
+                var renderedHeight = 0;
+                var currentY = startY;
+                var pageCanvas = document.createElement("canvas");
+                var pageContext = pageCanvas.getContext("2d");
+                while (renderedHeight < canvas.height) {
+                    var currentPageHeightPx = Math.max(1, Math.floor((pageHeight - margin - currentY) * pxPerMm));
+                    var sliceHeight = Math.min(currentPageHeightPx, canvas.height - renderedHeight);
+                    pageCanvas.width = canvas.width;
+                    pageCanvas.height = sliceHeight;
+                    pageContext.fillStyle = "#ffffff";
+                    pageContext.fillRect(0, 0, pageCanvas.width, pageCanvas.height);
+                    pageContext.drawImage(
+                        canvas,
+                        0,
+                        renderedHeight,
+                        canvas.width,
+                        sliceHeight,
+                        0,
+                        0,
+                        pageCanvas.width,
+                        pageCanvas.height
+                    );
+                    if (renderedHeight === 0 && settings.addNewPage) {
+                        pdf.addPage();
+                    } else if (renderedHeight > 0) {
+                        pdf.addPage();
+                        currentY = margin;
+                    }
+                    pdf.addImage(
+                        pageCanvas.toDataURL("image/jpeg", 0.95),
+                        "JPEG",
+                        margin,
+                        currentY,
+                        usableWidth,
+                        sliceHeight / pxPerMm,
+                        undefined,
+                        "FAST"
+                    );
+                    renderedHeight += sliceHeight;
+                    currentY = margin;
+                }
+            },
+            appendDetailTableToPdf: function (pdf, rootCanvas, detailRoot, detailTable, margin) {
+                var tbody = detailTable && detailTable.tBodies && detailTable.tBodies[0];
+                var rows = tbody ? Array.prototype.slice.call(tbody.rows) : [];
+                if (!rows.length) {
+                    return;
+                }
+                var pageWidth = pdf.internal.pageSize.getWidth();
+                var pageHeight = pdf.internal.pageSize.getHeight();
+                var usableWidth = pageWidth - margin * 2;
+                var usableHeight = pageHeight - margin * 2;
+                var rootRect = detailRoot.getBoundingClientRect();
+                var tableRect = detailTable.getBoundingClientRect();
+                var scale = rootCanvas.width / rootRect.width;
+                var pxPerMm = rootCanvas.width / usableWidth;
+                var pageHeightPx = Math.max(1, Math.floor(usableHeight * pxPerMm));
+                var titleHeightPx = Math.max(0, Math.round((tableRect.top - rootRect.top) * scale));
+                var headerHeightPx = Math.max(1, Math.round(detailTable.tHead.getBoundingClientRect().height * scale));
+                var rowBounds = rows.map(function (row) {
+                    var rect = row.getBoundingClientRect();
+                    return {
+                        top: Math.round((rect.top - tableRect.top) * scale),
+                        bottom: Math.round((rect.bottom - tableRect.top) * scale)
+                    };
+                });
+                var firstPage = true;
+                var startIndex = 0;
+                while (startIndex < rowBounds.length) {
+                    var bodyCapacityPx = pageHeightPx - headerHeightPx - (firstPage ? titleHeightPx : 0);
+                    var endIndex = this.findLastFittingRowIndex(rowBounds, startIndex, bodyCapacityPx);
+                    var pageCanvas = this.createDetailPageCanvas(
+                        rootCanvas,
+                        titleHeightPx,
+                        headerHeightPx,
+                        rowBounds[startIndex].top,
+                        rowBounds[endIndex].bottom,
+                        firstPage
+                    );
+                    pdf.addPage();
+                    pdf.addImage(
+                        pageCanvas.toDataURL("image/jpeg", 0.95),
+                        "JPEG",
+                        margin,
+                        margin,
+                        usableWidth,
+                        pageCanvas.height / pxPerMm,
+                        undefined,
+                        "FAST"
+                    );
+                    firstPage = false;
+                    startIndex = endIndex + 1;
+                }
+            },
+            findLastFittingRowIndex: function (rowBounds, startIndex, bodyCapacityPx) {
+                var lastIndex = startIndex;
+                for (var i = startIndex; i < rowBounds.length; i++) {
+                    if (rowBounds[i].bottom - rowBounds[startIndex].top > bodyCapacityPx) {
+                        break;
+                    }
+                    lastIndex = i;
+                }
+                return lastIndex;
+            },
+            createDetailPageCanvas: function (rootCanvas, titleHeightPx, headerHeightPx, bodyStartPx, bodyEndPx, includeTitle) {
+                var width = rootCanvas.width;
+                var titleHeight = includeTitle ? titleHeightPx : 0;
+                var bodyHeight = Math.max(1, bodyEndPx - bodyStartPx);
+                var pageCanvas = document.createElement("canvas");
+                var pageContext = pageCanvas.getContext("2d");
+                pageCanvas.width = width;
+                pageCanvas.height = titleHeight + headerHeightPx + bodyHeight;
+                pageContext.fillStyle = "#ffffff";
+                pageContext.fillRect(0, 0, pageCanvas.width, pageCanvas.height);
+                var offsetY = 0;
+                if (titleHeight > 0) {
+                    pageContext.drawImage(
+                        rootCanvas,
+                        0,
+                        0,
+                        width,
+                        titleHeight,
+                        0,
+                        0,
+                        width,
+                        titleHeight
+                    );
+                    offsetY += titleHeight;
+                }
+                pageContext.drawImage(
+                    rootCanvas,
+                    0,
+                    titleHeightPx,
+                    width,
+                    headerHeightPx,
+                    0,
+                    offsetY,
+                    width,
+                    headerHeightPx
+                );
+                offsetY += headerHeightPx;
+                pageContext.drawImage(
+                    rootCanvas,
+                    0,
+                    titleHeightPx + bodyStartPx,
+                    width,
+                    bodyHeight,
+                    0,
+                    offsetY,
+                    width,
+                    bodyHeight
+                );
+                return pageCanvas;
+            },
+            buildPdfFileName: function () {
+                var now = new Date();
+                return "浠诲姟鎵ц鍒嗘瀽_" +
+                    now.getFullYear() +
+                    this.pad(now.getMonth() + 1) +
+                    this.pad(now.getDate()) + "_" +
+                    this.pad(now.getHours()) +
+                    this.pad(now.getMinutes()) +
+                    this.pad(now.getSeconds()) + ".pdf";
+            },
             updateCharts: function () {
                 if (!this.analysisReady) {
                     this.disposeCharts();
@@ -298,9 +540,22 @@
                 if (!this.charts.duration) {
                     return;
                 }
+                var self = this;
                 var rows = this.analysis.durationCompare || [];
                 this.charts.duration.setOption({
-                    tooltip: { trigger: "axis" },
+                    tooltip: {
+                        trigger: "axis",
+                        formatter: function (params) {
+                            if (!params || !params.length) {
+                                return "";
+                            }
+                            var lines = [params[0].axisValue];
+                            params.forEach(function (item) {
+                                lines.push(item.marker + item.seriesName + ": " + self.formatChartSeconds(item.value));
+                            });
+                            return lines.join("<br>");
+                        }
+                    },
                     legend: { data: ["绔欑偣鑰楁椂", "鍫嗗灈鏈鸿�楁椂", "鎬昏�楁椂"] },
                     grid: { left: 50, right: 20, top: 40, bottom: 70 },
                     xAxis: {
@@ -312,14 +567,14 @@
                         type: "value",
                         axisLabel: {
                             formatter: function (value) {
-                                return Math.round((value || 0) / 1000) + "s";
+                                return self.formatChartSeconds(value);
                             }
                         }
                     },
                     series: [
-                        { name: "绔欑偣鑰楁椂", type: "bar", barMaxWidth: 28, data: rows.map(function (item) { return item.stationDurationMs || 0; }) },
-                        { name: "鍫嗗灈鏈鸿�楁椂", type: "bar", barMaxWidth: 28, data: rows.map(function (item) { return item.craneDurationMs || 0; }) },
-                        { name: "鎬昏�楁椂", type: "bar", barMaxWidth: 28, data: rows.map(function (item) { return item.totalDurationMs || 0; }) }
+                        { name: "绔欑偣鑰楁椂", type: "bar", barMaxWidth: 28, data: rows.map(function (item) { return self.toChartSeconds(item.stationDurationMs); }) },
+                        { name: "鍫嗗灈鏈鸿�楁椂", type: "bar", barMaxWidth: 28, data: rows.map(function (item) { return self.toChartSeconds(item.craneDurationMs); }) },
+                        { name: "鎬昏�楁椂", type: "bar", barMaxWidth: 28, data: rows.map(function (item) { return self.toChartSeconds(item.totalDurationMs); }) }
                     ]
                 }, true);
             },
@@ -327,9 +582,22 @@
                 if (!this.charts.trend) {
                     return;
                 }
+                var self = this;
                 var rows = this.analysis.trend || [];
                 this.charts.trend.setOption({
-                    tooltip: { trigger: "axis" },
+                    tooltip: {
+                        trigger: "axis",
+                        formatter: function (params) {
+                            if (!params || !params.length) {
+                                return "";
+                            }
+                            var lines = [params[0].axisValue];
+                            params.forEach(function (item) {
+                                lines.push(item.marker + item.seriesName + ": " + self.formatChartSeconds(item.value));
+                            });
+                            return lines.join("<br>");
+                        }
+                    },
                     legend: { data: ["骞冲潎鎬昏�楁椂", "骞冲潎绔欑偣鑰楁椂", "骞冲潎鍫嗗灈鏈鸿�楁椂"] },
                     grid: { left: 50, right: 20, top: 40, bottom: 70 },
                     xAxis: {
@@ -341,14 +609,14 @@
                         type: "value",
                         axisLabel: {
                             formatter: function (value) {
-                                return Math.round((value || 0) / 1000) + "s";
+                                return self.formatChartSeconds(value);
                             }
                         }
                     },
                     series: [
-                        { name: "骞冲潎鎬昏�楁椂", type: "line", smooth: true, data: rows.map(function (item) { return item.avgTotalDurationMs || 0; }) },
-                        { name: "骞冲潎绔欑偣鑰楁椂", type: "line", smooth: true, data: rows.map(function (item) { return item.avgStationDurationMs || 0; }) },
-                        { name: "骞冲潎鍫嗗灈鏈鸿�楁椂", type: "line", smooth: true, data: rows.map(function (item) { return item.avgCraneDurationMs || 0; }) }
+                        { name: "骞冲潎鎬昏�楁椂", type: "line", smooth: true, data: rows.map(function (item) { return self.toChartSeconds(item.avgTotalDurationMs); }) },
+                        { name: "骞冲潎绔欑偣鑰楁椂", type: "line", smooth: true, data: rows.map(function (item) { return self.toChartSeconds(item.avgStationDurationMs); }) },
+                        { name: "骞冲潎鍫嗗灈鏈鸿�楁椂", type: "line", smooth: true, data: rows.map(function (item) { return self.toChartSeconds(item.avgCraneDurationMs); }) }
                     ]
                 }, true);
             },
@@ -372,9 +640,22 @@
                 if (!this.charts.faultDuration) {
                     return;
                 }
+                var self = this;
                 var rows = this.analysis.faultDuration || [];
                 this.charts.faultDuration.setOption({
-                    tooltip: { trigger: "axis" },
+                    tooltip: {
+                        trigger: "axis",
+                        formatter: function (params) {
+                            if (!params || !params.length) {
+                                return "";
+                            }
+                            var lines = [params[0].axisValue];
+                            params.forEach(function (item) {
+                                lines.push(item.marker + item.seriesName + ": " + self.formatChartSeconds(item.value));
+                            });
+                            return lines.join("<br>");
+                        }
+                    },
                     grid: { left: 50, right: 20, top: 20, bottom: 40 },
                     xAxis: {
                         type: "category",
@@ -384,7 +665,7 @@
                         type: "value",
                         axisLabel: {
                             formatter: function (value) {
-                                return Math.round((value || 0) / 1000) + "s";
+                                return self.formatChartSeconds(value);
                             }
                         }
                     },
@@ -392,7 +673,7 @@
                         name: "鏁呴殰鑰楁椂",
                         type: "bar",
                         barMaxWidth: 36,
-                        data: rows.map(function (item) { return item.value || 0; })
+                        data: rows.map(function (item) { return self.toChartSeconds(item.value); })
                     }]
                 }, true);
             },
@@ -450,6 +731,24 @@
                 }
                 return num.toLocaleString("zh-CN");
             },
+            toChartSeconds: function (value) {
+                var num = Number(value || 0);
+                if (!isFinite(num)) {
+                    return 0;
+                }
+                return Number((num / 1000).toFixed(3));
+            },
+            formatChartSeconds: function (value) {
+                var num = Number(value || 0);
+                if (!isFinite(num)) {
+                    return "0s";
+                }
+                var text = String(num);
+                if (text.indexOf(".") >= 0) {
+                    text = text.replace(/0+$/, "").replace(/\.$/, "");
+                }
+                return text + "s";
+            },
             formatDuration: function (value) {
                 if (value === null || value === undefined || value === "") {
                     return "--";

--
Gitblit v1.9.1