From adc9abd145c24f2d3e7033bb738e1e8641eaf4cf Mon Sep 17 00:00:00 2001 From: riku <risaku@163.com> Date: 星期二, 02 九月 2025 17:30:43 +0800 Subject: [PATCH] 2025.9.2 --- src/utils/chart/chart-option.js | 49 +++++-- src/utils/doc.js | 44 ++++++- public/underway_season_report.docx | 0 src/components.d.ts | 1 src/utils/chart/chart-to-img.js | 54 +++++++++ src/views/historymode/component/MissionReport.vue | 163 ++++++++++++++++++++++++-- 6 files changed, 272 insertions(+), 39 deletions(-) diff --git a/public/underway_season_report.docx b/public/underway_season_report.docx index 1b28d15..95ff1f6 100644 --- a/public/underway_season_report.docx +++ b/public/underway_season_report.docx Binary files differ diff --git a/src/components.d.ts b/src/components.d.ts index 462a661..0fc11c1 100644 --- a/src/components.d.ts +++ b/src/components.d.ts @@ -37,6 +37,7 @@ ElForm: typeof import('element-plus/es')['ElForm'] ElFormItem: typeof import('element-plus/es')['ElFormItem'] ElIcon: typeof import('element-plus/es')['ElIcon'] + ElImage: typeof import('element-plus/es')['ElImage'] ElInput: typeof import('element-plus/es')['ElInput'] ElLink: typeof import('element-plus/es')['ElLink'] ElOption: typeof import('element-plus/es')['ElOption'] diff --git a/src/utils/chart/chart-option.js b/src/utils/chart/chart-option.js index 3395e29..89a9380 100644 --- a/src/utils/chart/chart-option.js +++ b/src/utils/chart/chart-option.js @@ -153,9 +153,29 @@ } // 鎶樼嚎鍥� -function smallLineOption(_xAxis, _series, yMinInterval) { +function smallLineOption( + _xAxis, + _series, + yMinInterval, + mode = 'dark', + tag, + animation = true, + defaultGrid, + title +) { var fontSize = fGetChartFontSize(); + const _grid = defaultGrid + ? defaultGrid + : { left: '12%', right: '2%', top: '7%', bottom: '30%' }; return { + title: { + text: title, + textStyle: { + color: mode == 'dark' ? '#ffffff' : '#000000' + }, + left: 'center' + }, + animation: animation, animationEasing: 'elasticOut', animationDelayUpdate: function (idx) { return idx * 5; @@ -165,42 +185,37 @@ fontSize: fontSize } }, - grid: { - left: '12%', - right: '2%', - top: '7%', - bottom: '30%' - }, + grid: _grid, legend: { show: false }, xAxis: [ { show: true, - // name: '鏃堕棿', + name: tag ? '鏃堕棿' : '', // type: 'time', data: _xAxis, axisLabel: { textStyle: { fontSize: fontSize }, - color: '#ffffff', - textBorderColor: '#fff' + color: mode == 'dark' ? '#ffffff' : '#000000', + textBorderColor: mode == 'dark' ? '#fff' : '#000000' }, axisTick: { lineStyle: { - color: 'white' + color: mode == 'dark' ? '#ffffff' : '#000000' }, intervel: 0, inside: false }, nameTextStyle: { - color: '#ffffff' + color: mode == 'dark' ? '#ffffff' : '#000000' }, axisLine: { lineStyle: { - color: '#ffffff' + color: mode == 'dark' ? '#ffffff' : '#000000' } } }, @@ -208,7 +223,7 @@ ], yAxis: [ { - // name: '娴撳害(渭g/m鲁)', + name: tag ? '娴撳害(渭g/m鲁)' : '', // type: 'time', axisLabel: { textStyle: { @@ -218,13 +233,13 @@ axisLine: { show: true, lineStyle: { - color: 'white' + color: mode == 'dark' ? '#ffffff' : '#000000' } }, axisTick: { show: false, lineStyle: { - color: 'white' + color: mode == 'dark' ? '#ffffff' : '#000000' } }, splitLine: { @@ -243,7 +258,7 @@ axisLine: { show: true, lineStyle: { - color: 'white' + color: mode == 'dark' ? '#ffffff' : '#000000' } } } diff --git a/src/utils/chart/chart-to-img.js b/src/utils/chart/chart-to-img.js new file mode 100644 index 0000000..71e197c --- /dev/null +++ b/src/utils/chart/chart-to-img.js @@ -0,0 +1,54 @@ +import * as echarts from 'echarts'; +import { smallLineOption, baseVisualMap } from '@/utils/chart/chart-option'; + +/** + * 浣跨敤echarts鐢熸垚鍥捐〃骞惰浆鎹负base64鍥剧墖 + * @returns {string} 鍥捐〃鐨刡ase64缂栫爜鍥剧墖 + */ +function generateEchartsImage(params, exceptionIndexArr, yMinInterval) { + // 1. 鍒涘缓涓存椂DOM鍏冪礌 + const div = document.createElement('div'); + // div.style.width = '330px'; + // div.style.height = '160px'; + div.style.width = '800px'; + div.style.height = '400px'; + document.body.appendChild(div); + + // 2. 鍒濆鍖杄charts瀹炰緥 + const myChart = echarts.init(div); + + // 3. 鍑嗗娴嬭瘯鏁版嵁 + const { xAxis, series } = params; + const option = smallLineOption( + xAxis, + series, + yMinInterval, + 'light', + true, + false, + { left: '7%', right: '7%', top: '10%', bottom: '10%' }, + series.name + ); + if (exceptionIndexArr) { + const visualMap = baseVisualMap(exceptionIndexArr); + option.visualMap = visualMap; + } + + // 4. 璁剧疆鍥捐〃閰嶇疆椤� + myChart.setOption(option); + + // 5. 灏嗗浘琛ㄨ浆鎹负base64鍥剧墖 + const imageBase64 = myChart.getDataURL({ + type: 'png', + pixelRatio: 2, // 鎻愰珮鍥剧墖娓呮櫚搴� + backgroundColor: '#fff' + }); + + // 6. 閿�姣佸疄渚嬪苟绉婚櫎涓存椂DOM + myChart.dispose(); + document.body.removeChild(div); + + return imageBase64; +} + +export default { generateEchartsImage }; diff --git a/src/utils/doc.js b/src/utils/doc.js index 8082330..fb15036 100644 --- a/src/utils/doc.js +++ b/src/utils/doc.js @@ -5,6 +5,33 @@ import FileSaver from 'file-saver'; /** + * 灏哹ase64鏍煎紡鍥剧墖杞崲涓篈rrayBuffer + * @param {string} base64Str - base64鏍煎紡鍥剧墖瀛楃涓诧紙鍙寘鍚玠ata URL鍓嶇紑锛� + * @returns {ArrayBuffer} 杞崲鍚庣殑ArrayBuffer瀵硅薄 + */ +function base64ToArrayBuffer(base64Str) { + // 绉婚櫎data URL鍓嶇紑锛堝鏋滃瓨鍦級 + const base64Content = base64Str.replace(/^data:image\/\w+;base64,/, ''); + + // 澶勭悊URL瀹夊叏鐨刡ase64瀛楃 + const safeBase64 = base64Content.replace(/-/g, '+').replace(/_/g, '/'); + + // 瑙g爜base64瀛楃涓� + const binaryStr = atob(safeBase64); + + // 杞崲涓篣int8Array + const byteLength = binaryStr.length; + const uint8Array = new Uint8Array(byteLength); + + for (let i = 0; i < byteLength; i++) { + uint8Array[i] = binaryStr.charCodeAt(i); + } + + // 杩斿洖ArrayBuffer + return uint8Array.buffer; +} + +/** * 绛夋瘮渚嬬缉鏀惧浘鐗� * 鏍规嵁鍥剧墖鐨勯暱瀹芥瘮杩涜涓嶅悓鏂瑰紡鐨勭缉鏀� * 濡傛灉瀹藉害澶т簬楂樺害锛堟í鎷嶅浘鐗囷級锛屽垯鎸夌収璁惧畾楂樺害绛夋瘮缂╂斁锛� @@ -122,12 +149,17 @@ getImage(tagValue) { // In this case tagValue will be a URL tagValue = "https://docxtemplater.com/puffin.png" return new Promise(function (resolve, reject) { - JSZipUtils.getBinaryContent(tagValue, function (error, content) { - if (error) { - return reject(error); - } - return resolve(content); - }); + if (tagValue.indexOf('http') == 0) { + JSZipUtils.getBinaryContent(tagValue, function (error, content) { + if (error) { + return reject(error); + } + return resolve(content); + }); + } else if (tagValue.indexOf('data:image') == 0) { + const buffer = base64ToArrayBuffer(tagValue); + return resolve(buffer); + } }); }, diff --git a/src/views/historymode/component/MissionReport.vue b/src/views/historymode/component/MissionReport.vue index 32fd62f..41e34ec 100644 --- a/src/views/historymode/component/MissionReport.vue +++ b/src/views/historymode/component/MissionReport.vue @@ -23,8 +23,19 @@ > 涓嬭浇鎶ュ憡 </el-button> + <el-button + type="primary" + class="el-button-custom" + @click="handleGenerateImg" + :loading="docLoading" + > + 鐢熸垚鍥剧墖 + </el-button> </el-form-item> </el-form> + <el-form-item> + <el-image :src="base64Url" fit="fill" :preview-src-list="[base64Url]" /> + </el-form-item> </CardDialog> </template> <script setup> @@ -32,6 +43,11 @@ import moment from 'moment'; import dataAnalysisApi from '@/api/dataAnalysisApi'; import { exportDocx } from '@/utils/doc'; +import { radioOptions } from '@/constant/radio-options'; +import { TYPE0 } from '@/constant/device-type'; +import { FactorDatas } from '@/model/FactorDatas'; +import factorDataParser from '@/utils/chart/factor-data-parser'; +import chartToImg from '@/utils/chart/chart-to-img'; const formObj = ref({ timeArray: [new Date('2025-07-01T00:00:00'), new Date('2025-08-31T23:59:59')], @@ -39,6 +55,8 @@ }); const docLoading = ref(false); + +const base64Url = ref(null); const params = computed(() => { return { @@ -81,27 +99,64 @@ ], missionDetailList: [ { + _index: 1, _startTime: '2025骞�07鏈�29鏃�', _time: '09:00鑷�14:30', _kilometres: '1000', _keyScene: '1涓浗鎺х偣锛堥潤瀹夌洃娴嬬珯锛夊拰2涓競鎺х偣锛堝拰鐢颁腑瀛︺�佸競鍖楅珮鏂帮級', - _dataStat: - 'PM鈧�.鈧咃紙鑼冨洿30鈥�35 渭g/m鲁锛屽潎鍊�35.51 渭g/m鲁锛夈�丳M鈧佲個锛堣寖鍥�25鈥�68 渭g/m鲁锛屽潎鍊�38 渭g/m鲁锛夈�丯O鈧傦紙鑼冨洿22鈥�54 渭g/m鲁锛屽潎鍊�32 渭g/m鲁锛夈�丆O锛堣寖鍥�2.08鈥�6.39 mg/m鲁锛屽潎鍊�3.398 mg/m鲁锛夊拰NO锛堣寖鍥�1鈥�106 渭g/m鲁锛屽潎鍊�20.97 渭g/m鲁锛�', + _dataStatistics: [ + { + factor: 'PM10', + minValue: 25, + maxValue: 68, + avgValue: 38 + } + ], aqi: 30, pollutionDegree: '浼�' + } + ], + clueByAreaList: [ + { + _index: 1, + _area: '鏌愭煇鍖哄煙鍛ㄨ竟', + clueByFactorList: [ + { + factor: 'PM鈧�.鈧�', + clues: [ + { + _factorNames: 'PM2.5', + _time: '10:22:28 - 10:22:34', + _riskRegion: '闀垮畞鍖烘竻婧矾鍙箰涓滆矾', + _exceptionType: '蹇�熶笂鍗�', + _chart: '', + _conclusion: + '鍦�10:22:28鑷�10:22:34涔嬮棿锛屽嚭鐜板揩閫熶笂鍗囷紝VOC鏈�浣庡�间负135.95渭g/m鲁锛屾渶楂樺�间负135.95渭g/m鲁锛屽潎鍊间负135.95渭g/m鲁锛屽彂鐜�3涓闄╂簮锛屽寘鍚�2涓姞娌圭珯锛�1涓苯淇��', + _scenes: + '1.涓婃捣渚濆痉姹借溅缁翠慨鏈夐檺鍏徃锛屾苯淇紒涓氾紝浣嶄簬涓婃捣甯傞暱瀹佸尯鍖楄櫣璺�1079鍙凤紝璺濅粰闇炵珯1887绫炽�俓r\n鈥︹��' + } + ] + } + ] } ] }; const handleClick = () => { - generateMissionSummary(params.value).then((res) => { - // generateDocx(); - generateMissionList(params.value).then((res) => { - generateMissionDetail(params.value).then((res) => { - // generateClueByRiskArea(params.value).then((res) => {}); + docLoading.value = true; + generateMissionSummary(params.value).then(() => { + generateMissionList(params.value).then(() => { + generateMissionDetail(params.value).then(() => { + generateClueByRiskArea(params.value).then(() => { + generateDocx(); + }); }); }); }); +}; + +const handleGenerateImg = () => { + generateClueByRiskArea(params.value).then(() => {}); }; function generateMissionSummary(param) { @@ -136,7 +191,7 @@ item._time = formatDateTimeRange(item.startTime, item.endTime); item._airQulity = `AQI锛�${item.aqi}锛�${item.pollutionDegree}锛塦; item._abnormalFactors = item.abnormalFactors - .map((factor) => factor.des) + .map((factor) => factor) .join('銆�'); return item; }); @@ -145,8 +200,9 @@ function generateMissionDetail(param) { return dataAnalysisApi.fetchMissionDetail(param).then((res) => { - templateParam.missionDetailList = res.data.map((item) => { + templateParam.missionDetailList = res.data.map((item, index) => { const t = formatDateTimeRange(item.startTime, item.endTime).split(' '); + item._index = index + 1; item._startTime = t[0]; item._time = t[1]; item._kilometres = Math.round(item.kilometres / 1000); @@ -156,7 +212,7 @@ if (!keySceneMap.has(e.type)) { keySceneMap.set(e.type, { scenes: [], count: 0 }); } - keySceneMap.get(e.type).scenes.push(e.scene); + keySceneMap.get(e.type).scenes.push(e); keySceneMap.get(e.type).count++; }); item._keyScene = [...keySceneMap] @@ -165,12 +221,17 @@ `${info.count}涓�${type}锛�${info.scenes.map((s) => s.name).join('銆�')}锛塦 ) .join('銆�'); - item._dataStat = item.dataStatistic + item._dataStat = item.dataStatistics .map( (e) => `${e.factor.des}锛堣寖鍥�${e.minValue}鈥�${e.maxValue}渭g/m鲁锛屽潎鍊�${e.avgValue}渭g/m鲁锛塦 ) .join('銆�'); + + const factorNames = radioOptions(TYPE0).map((e) => e.name); + item._dataStatistics = item.dataStatistics.filter((e) => { + return factorNames.indexOf(e.factor) != -1; + }); return item; }); @@ -178,19 +239,89 @@ } function generateClueByRiskArea(param) { - return dataAnalysisApi.fetchClueByRiskArea(param).then((res) => {}); + return dataAnalysisApi.fetchClueByRiskArea(param).then((res) => { + templateParam.clueByAreaList = res.data.map((item, index) => { + return { + _index: index + 1, + _area: item.sceneInfo.name + '鍛ㄨ竟', + clueByFactorList: item.clueByFactorList.map((cbf) => { + return { + factor: cbf.factor, + clues: cbf.clues.map((clue) => { + return { + _factorNames: Object.keys(clue.pollutedData.statisticMap) + .map((e) => e) + .join('銆�'), + _time: + moment(clue.pollutedData.startTime).format('HH:mm:ss') + + ' - ' + + moment(clue.pollutedData.endTime).format('HH:mm:ss'), + _riskRegion: clue.pollutedArea.address + ? clue.pollutedArea.address + : '', + _exceptionType: clue.pollutedData.exception, + _images: generateChartImg(clue.pollutedData), + _conclusion: clue.pollutedSource.conclusion, + _scenes: + clue.pollutedSource.sceneList.length > 0 + ? clue.pollutedSource.sceneList + .map( + (s, index) => + `${index + 1}. ${s.name}锛�${s.type}锛屼綅浜�${s.location}锛岃窛${s.closestStation.name}${parseInt(s.length)}绫筹紱` + ) + .join('\n') + : '鏃�' + }; + }) + }; + }) + }; + }); + }); +} + +function generateChartImg(pollutedData) { + const exceptionIndexArr = []; + pollutedData.dataVoList.forEach((e) => { + const i = pollutedData.historyDataList.findIndex((v) => v.time == e.time); + exceptionIndexArr.push([i - 1 < 0 ? 0 : i - 1, i]); + }); + + const factorDatas = new FactorDatas(); + const images = []; + factorDatas.setData(pollutedData.historyDataList, 0, () => { + for (const key in pollutedData.statisticMap) { + const value = pollutedData.statisticMap[key]; + const _chartOptions = factorDataParser.parseData(factorDatas, [ + { + label: value.factorName, + name: value.factorName, + value: value.factorId + '' + } + ]); + _chartOptions.forEach((o) => { + images.push({ + url: chartToImg.generateEchartsImage(o, exceptionIndexArr, 20) + }); + }); + + if (base64Url.value == null) { + base64Url.value = images[0].url; + } + } + }); + return images; } function generateDocx() { - docLoading.value = true; exportDocx( '/underway_season_report.docx', templateParam, `璧拌埅瀛e害鎶ュ憡.docx`, { - horizontalHeight: 368, - verticalWidth: 266, - scale: 1.367 + horizontalHeight: 250, + verticalWidth: 568, + scale: 2 } ).finally(() => (docLoading.value = false)); } -- Gitblit v1.9.3