fix: 历史监测数据,增加Excel导出功能。
This commit is contained in:
@@ -943,6 +943,9 @@
|
|||||||
<el-tooltip content="下载" placement="top">
|
<el-tooltip content="下载" placement="top">
|
||||||
<el-button icon="Download" @click="downloadHistoryChart" />
|
<el-button icon="Download" @click="downloadHistoryChart" />
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
|
<el-tooltip content="导出Excel" placement="top">
|
||||||
|
<el-button icon="Document" :disabled="historyData.length === 0" @click="exportHistoryExcel" />
|
||||||
|
</el-tooltip>
|
||||||
</el-button-group>
|
</el-button-group>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -1009,6 +1012,7 @@ import { DeviceCorrectRecordVO } from '@/api/fishery/deviceCorrectRecord/types';
|
|||||||
import { getHistoryData } from '@/api/fishery/monitorHistory';
|
import { getHistoryData } from '@/api/fishery/monitorHistory';
|
||||||
import type { DeviceSensorData } from '@/api/fishery/monitorHistory/types';
|
import type { DeviceSensorData } from '@/api/fishery/monitorHistory/types';
|
||||||
import * as echarts from 'echarts';
|
import * as echarts from 'echarts';
|
||||||
|
import FileSaver from 'file-saver';
|
||||||
|
|
||||||
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
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'));
|
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' }
|
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, '>')
|
||||||
|
.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) => {
|
const handleViewHistory = (row: DeviceVO) => {
|
||||||
historyDevice.value = row;
|
historyDevice.value = row;
|
||||||
@@ -1843,6 +1914,61 @@ const downloadHistoryChart = () => {
|
|||||||
link.click();
|
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 = () => {
|
const closeHistoryDialog = () => {
|
||||||
historyDialogVisible.value = false;
|
historyDialogVisible.value = false;
|
||||||
|
|||||||
@@ -42,6 +42,7 @@
|
|||||||
<el-form-item>
|
<el-form-item>
|
||||||
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
|
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
|
||||||
<el-button icon="Refresh" @click="resetQuery">重置</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-item>
|
||||||
</el-form>
|
</el-form>
|
||||||
</el-card>
|
</el-card>
|
||||||
@@ -184,6 +185,7 @@
|
|||||||
import { ref, reactive, onMounted, nextTick, computed } from 'vue';
|
import { ref, reactive, onMounted, nextTick, computed } from 'vue';
|
||||||
import * as echarts from 'echarts';
|
import * as echarts from 'echarts';
|
||||||
import type { EChartsOption } from 'echarts';
|
import type { EChartsOption } from 'echarts';
|
||||||
|
import FileSaver from 'file-saver';
|
||||||
import { getHistoryData } from '@/api/fishery/monitorHistory';
|
import { getHistoryData } from '@/api/fishery/monitorHistory';
|
||||||
import type { DeviceSensorDataQueryBo, DeviceSensorData } from '@/api/fishery/monitorHistory/types';
|
import type { DeviceSensorDataQueryBo, DeviceSensorData } from '@/api/fishery/monitorHistory/types';
|
||||||
import { listDevice } from '@/api/fishery/device';
|
import { listDevice } from '@/api/fishery/device';
|
||||||
@@ -262,6 +264,73 @@ const tabConfig = {
|
|||||||
battery: { label: '电量', unit: '%', color: '#9a60b4' }
|
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, '>')
|
||||||
|
.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 () => {
|
const getHistoryList = async () => {
|
||||||
// 验证是否选择了设备
|
// 验证是否选择了设备
|
||||||
@@ -331,6 +400,63 @@ const resetQuery = () => {
|
|||||||
endTime: '',
|
endTime: '',
|
||||||
intervalType: '3'
|
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());
|
||||||
};
|
};
|
||||||
|
|
||||||
/** 标签页切换 */
|
/** 标签页切换 */
|
||||||
|
|||||||
Reference in New Issue
Block a user