fix: 历史监测数据,增加Excel导出功能。

This commit is contained in:
tianyongbao
2026-06-25 15:42:21 +08:00
parent 887c6a00a3
commit b0c8057f8d
2 changed files with 252 additions and 0 deletions

View File

@@ -943,6 +943,9 @@
<el-tooltip content="下载" placement="top">
<el-button icon="Download" @click="downloadHistoryChart" />
</el-tooltip>
<el-tooltip content="导出Excel" placement="top">
<el-button icon="Document" :disabled="historyData.length === 0" @click="exportHistoryExcel" />
</el-tooltip>
</el-button-group>
</div>
</div>
@@ -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<any>(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, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;');
};
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 => `<th>${escapeHistoryExcelHtml(column.unit ? `${column.label}(${column.unit})` : column.label)}</th>`)
.join('');
const rowsHtml = historyData.value
.map(item => {
const cells = historyExportColumns
.map(column => `<td style="mso-number-format:'\\@';">${escapeHistoryExcelHtml(column.getValue(item))}</td>`)
.join('');
return `<tr>${cells}</tr>`;
})
.join('');
const worksheet = `<!DOCTYPE html>
<html xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:x="urn:schemas-microsoft-com:office:excel">
<head>
<meta charset="UTF-8" />
<!--[if gte mso 9]>
<xml>
<x:ExcelWorkbook>
<x:ExcelWorksheets>
<x:ExcelWorksheet>
<x:Name>监测历史记录</x:Name>
<x:WorksheetOptions></x:WorksheetOptions>
</x:ExcelWorksheet>
</x:ExcelWorksheets>
</x:ExcelWorkbook>
</xml>
<![endif]-->
<style>
table { border-collapse: collapse; }
th, td { border: 1px solid #dcdfe6; padding: 6px 10px; white-space: nowrap; }
th { background: #f5f7fa; font-weight: bold; }
</style>
</head>
<body>
<table>
<thead><tr>${headerHtml}</tr></thead>
<tbody>${rowsHtml}</tbody>
</table>
</body>
</html>`;
const blob = new Blob([worksheet], { type: 'application/vnd.ms-excel;charset=utf-8' });
FileSaver.saveAs(blob, getHistoryExportFileName());
};
/** 关闭历史曲线对话框 */
const closeHistoryDialog = () => {
historyDialogVisible.value = false;

View File

@@ -42,6 +42,7 @@
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
<el-button type="warning" plain icon="Download" :disabled="historyData.length === 0" @click="handleExport">导出 Excel</el-button>
</el-form-item>
</el-form>
</el-card>
@@ -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, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;');
};
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 => `<th>${escapeHtml(column.unit ? `${column.label}(${column.unit})` : column.label)}</th>`)
.join('');
const rowsHtml = historyData.value
.map(item => {
const cells = exportColumns
.map(column => `<td style="mso-number-format:'\\@';">${escapeHtml(column.getValue(item))}</td>`)
.join('');
return `<tr>${cells}</tr>`;
})
.join('');
const worksheet = `<!DOCTYPE html>
<html xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:x="urn:schemas-microsoft-com:office:excel">
<head>
<meta charset="UTF-8" />
<!--[if gte mso 9]>
<xml>
<x:ExcelWorkbook>
<x:ExcelWorksheets>
<x:ExcelWorksheet>
<x:Name>监测历史记录</x:Name>
<x:WorksheetOptions></x:WorksheetOptions>
</x:ExcelWorksheet>
</x:ExcelWorksheets>
</x:ExcelWorkbook>
</xml>
<![endif]-->
<style>
table { border-collapse: collapse; }
th, td { border: 1px solid #dcdfe6; padding: 6px 10px; white-space: nowrap; }
th { background: #f5f7fa; font-weight: bold; }
</style>
</head>
<body>
<table>
<thead><tr>${headerHtml}</tr></thead>
<tbody>${rowsHtml}</tbody>
</table>
</body>
</html>`;
const blob = new Blob([worksheet], { type: 'application/vnd.ms-excel;charset=utf-8' });
FileSaver.saveAs(blob, getExportFileName());
};
/** 标签页切换 */