diff --git a/src/views/fishery/device/index.vue b/src/views/fishery/device/index.vue index be77173..79244ce 100644 --- a/src/views/fishery/device/index.vue +++ b/src/views/fishery/device/index.vue @@ -141,6 +141,9 @@ - + + +
+ + + + + 搜索 +
+ + + + + + + + + + + + +
+
+
+ + + + + + + + + + + +
+
+ + +
+ + + (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(proxy?.useDict('aqu_device_type', 'open_close', 'connect_voltage_type', 'yes_no', 'loop_type', 'is_linked_ctrl', 'interval_type')); const deviceList = ref([]); const buttonLoading = ref(false); @@ -1450,6 +1526,189 @@ const closeSwitchTimingCtrlDialog = () => { switchTimingCtrlList.value = []; }; +// ===================== 历史曲线 ===================== +const historyDialogVisible = ref(false); +const historyDevice = ref(null); +const historyLoading = ref(false); +const historyDateRange = ref([]); +const historyActiveTab = ref('dissolvedOxygen'); +const historyChartType = ref('line'); +const historyChartRef = ref(); +const historyData = ref([]); +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}
${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(); });