fix: 设备管理,列表操作新增曲线图查询弹框。
This commit is contained in:
@@ -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();
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user