#
Junjie
22 小时以前 a42c95b399fc0d9162e714d5cd5156c53fff2cc6
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 "--";