| | |
| | | pageTotal: 0, |
| | | listLoading: false, |
| | | analyzeLoading: false, |
| | | exportingPdf: false, |
| | | selectedWrkNoMap: {}, |
| | | analysis: createEmptyAnalysis(), |
| | | analysisReady: false, |
| | |
| | | } |
| | | }); |
| | | }, |
| | | 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(); |
| | |
| | | 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: { |
| | |
| | | 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); |
| | | }, |
| | |
| | | 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: { |
| | |
| | | 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); |
| | | }, |
| | |
| | | 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", |
| | |
| | | type: "value", |
| | | axisLabel: { |
| | | formatter: function (value) { |
| | | return Math.round((value || 0) / 1000) + "s"; |
| | | return self.formatChartSeconds(value); |
| | | } |
| | | } |
| | | }, |
| | |
| | | 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); |
| | | }, |
| | |
| | | } |
| | | 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 "--"; |