From b0c8057f8d303dd7fb1374ccd1488fee485b75f2 Mon Sep 17 00:00:00 2001 From: tianyongbao Date: Thu, 25 Jun 2026 15:42:21 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E5=8E=86=E5=8F=B2=E7=9B=91=E6=B5=8B?= =?UTF-8?q?=E6=95=B0=E6=8D=AE=EF=BC=8C=E5=A2=9E=E5=8A=A0Excel=E5=AF=BC?= =?UTF-8?q?=E5=87=BA=E5=8A=9F=E8=83=BD=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/views/fishery/device/index.vue | 126 +++++++++++++++++++++ src/views/fishery/monitorHistory/index.vue | 126 +++++++++++++++++++++ 2 files changed, 252 insertions(+) diff --git a/src/views/fishery/device/index.vue b/src/views/fishery/device/index.vue index 067e302..760612c 100644 --- a/src/views/fishery/device/index.vue +++ b/src/views/fishery/device/index.vue @@ -943,6 +943,9 @@ + + + @@ -1009,6 +1012,7 @@ import { DeviceCorrectRecordVO } from '@/api/fishery/deviceCorrectRecord/types'; import { getHistoryData } from '@/api/fishery/monitorHistory'; import type { DeviceSensorData } from '@/api/fishery/monitorHistory/types'; import * as echarts from 'echarts'; +import FileSaver from 'file-saver'; const { proxy } = getCurrentInstance() as ComponentInternalInstance; const { aqu_device_type, open_close, connect_voltage_type, yes_no, loop_type, is_linked_ctrl, interval_type } = toRefs(proxy?.useDict('aqu_device_type', 'open_close', 'connect_voltage_type', 'yes_no', 'loop_type', 'is_linked_ctrl', 'interval_type')); @@ -1700,6 +1704,73 @@ const historyTabConfig = { battery: { label: '电量', unit: '%', color: '#9a60b4' } }; +const historyExportColumns: Array<{ label: string; unit?: string; getValue: (item: DeviceSensorData) => unknown }> = [ + { label: '监测时间', getValue: item => item.time }, + { label: '塘口', getValue: item => (item as DeviceSensorData & { pondName?: string }).pondName || historyDevice.value?.pondName || '' }, + { label: '设备名称', getValue: item => item.deviceName || historyDevice.value?.deviceName || '' }, + { label: '设备编号', getValue: item => item.serialNum || historyDevice.value?.serialNum || historyQueryParams.serialNum || '' }, + { label: '溶解氧', unit: 'Mg/L', getValue: item => item.dissolvedOxygen }, + { label: '水温', unit: '℃', getValue: item => item.temperature }, + { label: '饱和度', unit: '%', getValue: item => item.saturability } +]; + +const escapeHistoryExcelHtml = (value: unknown) => { + if (value === null || value === undefined) { + return ''; + } + return String(value) + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"'); +}; + +const getHistoryExportFileName = () => { + const serialNum = historyQueryParams.serialNum || 'all'; + const startTime = historyQueryParams.startTime || historyDateRange.value?.[0] || ''; + const endTime = historyQueryParams.endTime || historyDateRange.value?.[1] || ''; + const dateText = startTime && endTime ? `_${startTime}_${endTime}` : ''; + return `监测历史记录_${serialNum}${dateText}_${new Date().getTime()}.xls`; +}; + +const getHistoryExportLimit = () => { + const intervalType = Number(historyQueryParams.intervalType || 1); + if (intervalType === 1) { + return { months: 1, message: '实时数据最多只能导出1个月内的数据' }; + } + if (intervalType === 2 || intervalType === 3) { + return { months: 3, message: '10分钟和30分钟数据最多只能导出3个月内的数据' }; + } + return { months: 6, message: '1小时以上数据最多只能导出6个月内的数据' }; +}; + +const validateHistoryExportDateRange = () => { + const startTime = historyQueryParams.startTime || historyDateRange.value?.[0]; + const endTime = historyQueryParams.endTime || historyDateRange.value?.[1]; + if (!startTime || !endTime) { + proxy?.$modal.msgWarning('请先选择导出时间范围'); + return false; + } + + const startDate = new Date(`${startTime} 00:00:00`.replace(/-/g, '/')); + const endDate = new Date(`${endTime} 23:59:59`.replace(/-/g, '/')); + if (Number.isNaN(startDate.getTime()) || Number.isNaN(endDate.getTime()) || startDate > endDate) { + proxy?.$modal.msgWarning('导出时间范围不正确,请重新选择'); + return false; + } + + const limit = getHistoryExportLimit(); + const maxEndDate = new Date(startDate); + maxEndDate.setMonth(maxEndDate.getMonth() + limit.months); + maxEndDate.setHours(23, 59, 59, 999); + if (endDate > maxEndDate) { + proxy?.$modal.msgWarning(limit.message); + return false; + } + + return true; +}; + /** 查看历史曲线 */ const handleViewHistory = (row: DeviceVO) => { historyDevice.value = row; @@ -1843,6 +1914,61 @@ const downloadHistoryChart = () => { link.click(); }; +/** 导出历史数据 */ +const exportHistoryExcel = () => { + if (historyData.value.length === 0) { + proxy?.$modal.msgWarning('暂无可导出的历史数据,请先查询'); + return; + } + if (!validateHistoryExportDateRange()) { + return; + } + + const headerHtml = historyExportColumns + .map(column => `${escapeHistoryExcelHtml(column.unit ? `${column.label}(${column.unit})` : column.label)}`) + .join(''); + const rowsHtml = historyData.value + .map(item => { + const cells = historyExportColumns + .map(column => `${escapeHistoryExcelHtml(column.getValue(item))}`) + .join(''); + return `${cells}`; + }) + .join(''); + const worksheet = ` + + + + + + + + + ${headerHtml} + ${rowsHtml} +
+ +`; + + const blob = new Blob([worksheet], { type: 'application/vnd.ms-excel;charset=utf-8' }); + FileSaver.saveAs(blob, getHistoryExportFileName()); +}; + /** 关闭历史曲线对话框 */ const closeHistoryDialog = () => { historyDialogVisible.value = false; diff --git a/src/views/fishery/monitorHistory/index.vue b/src/views/fishery/monitorHistory/index.vue index 93fa1e8..254b02d 100644 --- a/src/views/fishery/monitorHistory/index.vue +++ b/src/views/fishery/monitorHistory/index.vue @@ -42,6 +42,7 @@ 搜索 重置 + 导出 Excel @@ -184,6 +185,7 @@ import { ref, reactive, onMounted, nextTick, computed } from 'vue'; import * as echarts from 'echarts'; import type { EChartsOption } from 'echarts'; +import FileSaver from 'file-saver'; import { getHistoryData } from '@/api/fishery/monitorHistory'; import type { DeviceSensorDataQueryBo, DeviceSensorData } from '@/api/fishery/monitorHistory/types'; import { listDevice } from '@/api/fishery/device'; @@ -262,6 +264,73 @@ const tabConfig = { battery: { label: '电量', unit: '%', color: '#9a60b4' } }; +const exportColumns: Array<{ label: string; unit?: string; getValue: (item: DeviceSensorData) => unknown }> = [ + { label: '监测时间', getValue: item => item.time }, + { label: '塘口', getValue: item => (item as DeviceSensorData & { pondName?: string }).pondName || selectedDevice.value?.pondName || '' }, + { label: '设备名称', getValue: item => item.deviceName || selectedDevice.value?.deviceName || '' }, + { label: '设备编号', getValue: item => item.serialNum || selectedDevice.value?.serialNum || queryParams.value.serialNum || '' }, + { label: '溶解氧', unit: 'Mg/L', getValue: item => item.dissolvedOxygen }, + { label: '水温', unit: '℃', getValue: item => item.temperature }, + { label: '饱和度', unit: '%', getValue: item => item.saturability } +]; + +const escapeHtml = (value: unknown) => { + if (value === null || value === undefined) { + return ''; + } + return String(value) + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"'); +}; + +const getExportFileName = () => { + const serialNum = queryParams.value.serialNum || 'all'; + const startTime = queryParams.value.startTime || dateRange.value?.[0] || ''; + const endTime = queryParams.value.endTime || dateRange.value?.[1] || ''; + const dateText = startTime && endTime ? `_${startTime}_${endTime}` : ''; + return `监测历史记录_${serialNum}${dateText}_${new Date().getTime()}.xls`; +}; + +const getExportLimit = () => { + const intervalType = Number(queryParams.value.intervalType || 1); + if (intervalType === 1) { + return { months: 1, message: '实时数据最多只能导出1个月内的数据' }; + } + if (intervalType === 2 || intervalType === 3) { + return { months: 3, message: '10分钟和30分钟数据最多只能导出3个月内的数据' }; + } + return { months: 6, message: '1小时以上数据最多只能导出6个月内的数据' }; +}; + +const validateExportDateRange = () => { + const startTime = queryParams.value.startTime || dateRange.value?.[0]; + const endTime = queryParams.value.endTime || dateRange.value?.[1]; + if (!startTime || !endTime) { + proxy?.$modal.msgWarning('请先选择导出时间范围'); + return false; + } + + const startDate = new Date(`${startTime} 00:00:00`.replace(/-/g, '/')); + const endDate = new Date(`${endTime} 23:59:59`.replace(/-/g, '/')); + if (Number.isNaN(startDate.getTime()) || Number.isNaN(endDate.getTime()) || startDate > endDate) { + proxy?.$modal.msgWarning('导出时间范围不正确,请重新选择'); + return false; + } + + const limit = getExportLimit(); + const maxEndDate = new Date(startDate); + maxEndDate.setMonth(maxEndDate.getMonth() + limit.months); + maxEndDate.setHours(23, 59, 59, 999); + if (endDate > maxEndDate) { + proxy?.$modal.msgWarning(limit.message); + return false; + } + + return true; +}; + /** 查询历史数据 */ const getHistoryList = async () => { // 验证是否选择了设备 @@ -331,6 +400,63 @@ const resetQuery = () => { endTime: '', intervalType: '3' }; + historyData.value = []; + updateChart(); +}; + +/** 导出查询结果 */ +const handleExport = () => { + if (historyData.value.length === 0) { + proxy?.$modal.msgWarning('暂无可导出的历史数据,请先查询'); + return; + } + if (!validateExportDateRange()) { + return; + } + + const headerHtml = exportColumns + .map(column => `${escapeHtml(column.unit ? `${column.label}(${column.unit})` : column.label)}`) + .join(''); + const rowsHtml = historyData.value + .map(item => { + const cells = exportColumns + .map(column => `${escapeHtml(column.getValue(item))}`) + .join(''); + return `${cells}`; + }) + .join(''); + const worksheet = ` + + + + + + + + + ${headerHtml} + ${rowsHtml} +
+ +`; + + const blob = new Blob([worksheet], { type: 'application/vnd.ms-excel;charset=utf-8' }); + FileSaver.saveAs(blob, getExportFileName()); }; /** 标签页切换 */