fix: 设备管理,列表操作新增曲线图查询弹框。

This commit is contained in:
tianyongbao
2026-03-08 17:00:39 +08:00
parent 6d46125e7f
commit 21018c8f9e

View File

@@ -141,6 +141,9 @@
<el-table-column label="电量电话告警下限" align="center" prop="batteryWarnLower" />
<el-table-column label="操作" align="center" fixed="right" width="240" class-name="small-padding fixed-width">
<template #default="scope">
<el-tooltip content="曲线图" placement="top">
<el-button link type="primary" icon="TrendCharts" @click="handleViewHistory(scope.row)" v-hasPermi="['fishery:monitorHistory:list']"></el-button>
</el-tooltip>
<el-tooltip content="开关信息" placement="top" v-if="scope.row.deviceType === 2">
<el-button link type="primary" icon="Operation" @click="handleViewSwitch(scope.row)" v-hasPermi="['fishery:deviceSwitch:list']"></el-button>
</el-tooltip>
@@ -750,9 +753,79 @@
</template>
</el-dialog>
<!-- 开关定时控制对话框 -->
<!-- 历史曲线对话框 -->
<el-dialog
:title="`定时控制 - ${currentSwitch?.switchName || ''} (序号:${currentSwitch?.index || ''})`"
:title="`历史曲线 - ${historyDevice?.deviceName || ''} (${historyDevice?.serialNum || ''})`"
v-model="historyDialogVisible"
width="1100px"
append-to-body
@close="closeHistoryDialog"
@opened="onHistoryDialogOpened"
>
<!-- 日期范围和时间间隔 -->
<div style="display: flex; align-items: center; gap: 12px; margin-bottom: 16px; flex-wrap: wrap;">
<el-date-picker
v-model="historyDateRange"
type="daterange"
range-separator="-"
start-placeholder="开始日期"
end-placeholder="结束日期"
value-format="YYYY-MM-DD"
style="width: 280px"
/>
<el-select v-model="historyQueryParams.intervalType" placeholder="请选择时间间隔" clearable style="width: 200px">
<el-option
v-for="dict in interval_type"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
<el-button type="primary" icon="Search" @click="handleHistoryQuery">搜索</el-button>
</div>
<el-tabs v-model="historyActiveTab" @tab-change="updateHistoryChart">
<el-tab-pane label="溶解氧(Mg/L)" name="dissolvedOxygen"></el-tab-pane>
<el-tab-pane label="水温(℃)" name="temperature"></el-tab-pane>
<el-tab-pane label="饱和度(%)" name="saturability"></el-tab-pane>
<el-tab-pane label="PH(Ph)" name="ph"></el-tab-pane>
<el-tab-pane label="盐度(%)" name="salinity"></el-tab-pane>
<el-tab-pane label="参比值" name="treference"></el-tab-pane>
<el-tab-pane label="荧光值" name="tfluorescence"></el-tab-pane>
<el-tab-pane label="电量(%)" name="battery"></el-tab-pane>
</el-tabs>
<div style="position: relative;">
<div
ref="historyChartRef"
v-loading="historyLoading"
style="width: 100%; height: 420px;"
></div>
<div style="position: absolute; top: 0; right: 0; z-index: 10;">
<el-button-group>
<el-tooltip content="折线图" placement="top">
<el-button :type="historyChartType === 'line' ? 'primary' : ''" icon="TrendCharts" @click="changeHistoryChartType('line')" />
</el-tooltip>
<el-tooltip content="柱状图" placement="top">
<el-button :type="historyChartType === 'bar' ? 'primary' : ''" icon="Histogram" @click="changeHistoryChartType('bar')" />
</el-tooltip>
<el-tooltip content="下载" placement="top">
<el-button icon="Download" @click="downloadHistoryChart" />
</el-tooltip>
</el-button-group>
</div>
</div>
<template #footer>
<div class="dialog-footer">
<el-button @click="closeHistoryDialog"> </el-button>
</div>
</template>
</el-dialog>
<!-- 开关定时控制对话框 -->
<el-dialog
:title="`定时控制 - ${currentSwitch?.switchName || ''} (序号:${currentSwitch?.index || ''})`"
v-model="switchTimingCtrlDialogVisible"
width="800px"
append-to-body
@@ -802,9 +875,12 @@ import { listTimingCtrl } from '@/api/fishery/timingCtrl';
import { TimingCtrlVO } from '@/api/fishery/timingCtrl/types';
import { listDeviceCorrectRecord } from '@/api/fishery/deviceCorrectRecord';
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';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const { aqu_device_type, open_close, connect_voltage_type, yes_no, loop_type,is_linked_ctrl } = toRefs<any>(proxy?.useDict('aqu_device_type', 'open_close', 'connect_voltage_type', 'yes_no', 'loop_type', 'is_linked_ctrl'));
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 deviceList = ref<DeviceVO[]>([]);
const buttonLoading = ref(false);
@@ -1450,6 +1526,189 @@ const closeSwitchTimingCtrlDialog = () => {
switchTimingCtrlList.value = [];
};
// ===================== 历史曲线 =====================
const historyDialogVisible = ref(false);
const historyDevice = ref<DeviceVO | null>(null);
const historyLoading = ref(false);
const historyDateRange = ref<string[]>([]);
const historyActiveTab = ref<string>('dissolvedOxygen');
const historyChartType = ref<string>('line');
const historyChartRef = ref<HTMLElement>();
const historyData = ref<DeviceSensorData[]>([]);
let historyChartInstance: echarts.ECharts | null = null;
const historyQueryParams = reactive({
serialNum: '',
startTime: '',
endTime: '',
intervalType: '3'
});
const historyTabConfig = {
dissolvedOxygen: { label: '溶解氧', unit: 'Mg/L', color: '#5470c6' },
temperature: { label: '水温', unit: '℃', color: '#91cc75' },
saturability: { label: '饱和度', unit: '%', color: '#fac858' },
ph: { label: 'PH', unit: 'Ph', color: '#ee6666' },
salinity: { label: '盐度', unit: '%', color: '#73c0de' },
treference: { label: '参比值', unit: '', color: '#3ba272' },
tfluorescence: { label: '荧光值', unit: '', color: '#fc8452' },
battery: { label: '电量', unit: '%', color: '#9a60b4' }
};
/** 查看历史曲线 */
const handleViewHistory = (row: DeviceVO) => {
historyDevice.value = row;
historyQueryParams.serialNum = row.serialNum || '';
historyActiveTab.value = 'dissolvedOxygen';
historyChartType.value = 'line';
historyData.value = [];
// 默认最近7天
const end = new Date();
const start = new Date();
start.setDate(start.getDate() - 7);
const fmt = (d: Date) => `${d.getFullYear()}-${String(d.getMonth()+1).padStart(2,'0')}-${String(d.getDate()).padStart(2,'0')}`;
historyDateRange.value = [fmt(start), fmt(end)];
historyDialogVisible.value = true;
};
/** 对话框打开后初始化图表并查询 */
const onHistoryDialogOpened = async () => {
await nextTick();
initHistoryChart();
await fetchHistoryData();
};
/** 初始化图表 */
const initHistoryChart = () => {
if (!historyChartRef.value) return;
if (historyChartInstance) {
historyChartInstance.dispose();
}
historyChartInstance = echarts.init(historyChartRef.value);
};
/** 获取历史数据 */
const fetchHistoryData = async () => {
if (!historyQueryParams.serialNum) return;
historyLoading.value = true;
try {
if (historyDateRange.value && historyDateRange.value.length === 2) {
historyQueryParams.startTime = historyDateRange.value[0];
historyQueryParams.endTime = historyDateRange.value[1];
}
const res = await getHistoryData(historyQueryParams);
if (Array.isArray(res)) {
historyData.value = res;
} else if (res.data && Array.isArray(res.data)) {
historyData.value = res.data;
} else {
historyData.value = [];
}
await nextTick();
updateHistoryChart();
if (historyData.value.length === 0) {
proxy?.$modal.msgWarning('未查询到数据,请检查日期范围是否正确');
}
} catch {
proxy?.$modal.msgError('查询历史数据失败');
} finally {
historyLoading.value = false;
}
};
/** 搜索 */
const handleHistoryQuery = () => {
fetchHistoryData();
};
/** 更新图表 */
const updateHistoryChart = () => {
if (!historyChartInstance) {
initHistoryChart();
if (!historyChartInstance) return;
}
if (historyData.value.length === 0) {
historyChartInstance.setOption({
title: { text: '暂无数据', left: 'center', top: 'center', textStyle: { color: '#999', fontSize: 20 } },
tooltip: {}, grid: {}, xAxis: { show: false }, yAxis: { show: false }, series: []
}, true);
return;
}
const config = historyTabConfig[historyActiveTab.value as keyof typeof historyTabConfig];
const xData = historyData.value.map(item => {
const d = new Date(item.time.replace(' ', 'T'));
return `${String(d.getMonth()+1).padStart(2,'0')}-${String(d.getDate()).padStart(2,'0')} ${String(d.getHours()).padStart(2,'0')}:${String(d.getMinutes()).padStart(2,'0')}:${String(d.getSeconds()).padStart(2,'0')}`;
});
const yData = historyData.value.map(item => {
const v = item[historyActiveTab.value as keyof DeviceSensorData];
return typeof v === 'number' ? v : 0;
});
historyChartInstance.setOption({
tooltip: {
trigger: 'axis',
axisPointer: { type: 'cross' },
formatter: (params: any) => {
const p = params[0];
return `${p.axisValue}<br/>${config.label}: ${p.value} ${config.unit}`;
}
},
grid: { left: '3%', right: '4%', bottom: '3%', containLabel: true },
xAxis: {
type: 'category',
boundaryGap: historyChartType.value === 'bar',
data: xData,
axisLabel: { interval: Math.floor(xData.length / 20), rotate: 20, fontSize: 11 }
},
yAxis: {
type: 'value',
name: `${config.label}(${config.unit})`,
axisLabel: { formatter: `{value} ${config.unit}` }
},
series: [{
name: config.label,
type: historyChartType.value,
data: yData,
smooth: historyChartType.value === 'line',
symbol: 'circle',
symbolSize: historyChartType.value === 'line' ? 0 : 6,
itemStyle: { color: config.color },
lineStyle: historyChartType.value === 'line' ? { width: 2 } : undefined,
areaStyle: historyChartType.value === 'line' ? { opacity: 0.1 } : undefined,
emphasis: historyChartType.value === 'line' ? {
symbol: 'circle', symbolSize: 12,
itemStyle: { color: config.color, borderColor: '#fff', borderWidth: 3, shadowBlur: 8, shadowColor: config.color }
} : undefined
}]
}, true);
};
/** 切换图表类型 */
const changeHistoryChartType = (type: string) => {
historyChartType.value = type;
updateHistoryChart();
};
/** 下载图表 */
const downloadHistoryChart = () => {
if (!historyChartInstance) return;
const url = historyChartInstance.getDataURL({ type: 'png', pixelRatio: 2, backgroundColor: '#fff' });
const link = document.createElement('a');
link.download = `历史曲线_${historyQueryParams.serialNum}_${new Date().getTime()}.png`;
link.href = url;
link.click();
};
/** 关闭历史曲线对话框 */
const closeHistoryDialog = () => {
historyDialogVisible.value = false;
historyDevice.value = null;
historyData.value = [];
if (historyChartInstance) {
historyChartInstance.dispose();
historyChartInstance = null;
}
};
// ===================== 历史曲线 end =====================
onMounted(() => {
getList();
});