2026-05-09 17:04:48 +08:00
|
|
|
|
<!-- SidePanelItem.vue -->
|
|
|
|
|
|
<template>
|
2026-06-16 11:36:17 +08:00
|
|
|
|
<SidePanelItem
|
|
|
|
|
|
title="水温监测"
|
|
|
|
|
|
:select="select"
|
|
|
|
|
|
:scopeDate="datetimePicker"
|
|
|
|
|
|
@update-values="handlePanelChange1"
|
|
|
|
|
|
>
|
|
|
|
|
|
<div class="chart-wrapper">
|
|
|
|
|
|
<a-spin :spinning="loading">
|
|
|
|
|
|
<!-- 始终渲染图表容器,确保有固定宽高 -->
|
|
|
|
|
|
<div ref="chartRef" class="water-temp-chart"></div>
|
|
|
|
|
|
<!-- 无数据时显示Empty,但不影响容器尺寸 -->
|
|
|
|
|
|
<div v-if="!loading && !hasData" class="empty-overlay">
|
|
|
|
|
|
<a-empty description="暂无数据" />
|
2026-06-03 17:10:34 +08:00
|
|
|
|
</div>
|
2026-06-16 11:36:17 +08:00
|
|
|
|
</a-spin>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</SidePanelItem>
|
2026-05-09 17:04:48 +08:00
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<script lang="ts" setup>
|
2026-06-03 17:10:34 +08:00
|
|
|
|
import { ref, onMounted, onUnmounted, nextTick, watch } from 'vue';
|
2026-05-09 17:04:48 +08:00
|
|
|
|
import * as echarts from 'echarts';
|
|
|
|
|
|
import type { EChartsOption } from 'echarts';
|
|
|
|
|
|
import SidePanelItem from '@/components/SidePanelItem/index.vue';
|
2026-06-16 11:36:17 +08:00
|
|
|
|
import { useJidiSelectEventStore } from '@/store/modules/jidiSelectEvent';
|
2026-06-03 17:10:34 +08:00
|
|
|
|
import { msstbprptGetKendoList, sdrvwtsGetKendoList } from '@/api/qxd';
|
2026-06-16 11:36:17 +08:00
|
|
|
|
import { useModelStore } from '@/store/modules/model';
|
|
|
|
|
|
import dayjs from 'dayjs';
|
2026-05-09 17:04:48 +08:00
|
|
|
|
// 定义组件名
|
|
|
|
|
|
defineOptions({
|
2026-06-16 11:36:17 +08:00
|
|
|
|
name: 'qixidishuiwenbianhua'
|
2026-05-09 17:04:48 +08:00
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// ==================== 响应式变量定义 ====================
|
2026-06-03 17:10:34 +08:00
|
|
|
|
const JidiSelectEventStore = useJidiSelectEventStore();
|
2026-05-09 17:04:48 +08:00
|
|
|
|
// 图表容器引用
|
|
|
|
|
|
const chartRef = ref<HTMLDivElement>();
|
|
|
|
|
|
let chartInstance: echarts.ECharts | null = null;
|
2026-06-16 11:36:17 +08:00
|
|
|
|
const baseid = ref('');
|
2026-06-03 17:10:34 +08:00
|
|
|
|
|
|
|
|
|
|
// Loading 状态和数据状态
|
|
|
|
|
|
const loading = ref(false);
|
|
|
|
|
|
const hasData = ref(true); // 初始化为 true,因为 initChart 时会渲染
|
2026-05-09 17:04:48 +08:00
|
|
|
|
|
|
|
|
|
|
// 选择器配置
|
|
|
|
|
|
const select = ref({
|
2026-06-16 11:36:17 +08:00
|
|
|
|
show: true,
|
|
|
|
|
|
value: undefined,
|
|
|
|
|
|
options: [],
|
|
|
|
|
|
picker: undefined,
|
|
|
|
|
|
format: undefined
|
2026-05-09 17:04:48 +08:00
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 日期选择器配置
|
2026-06-03 17:10:34 +08:00
|
|
|
|
const datetimePicker: any = ref({
|
2026-06-16 11:36:17 +08:00
|
|
|
|
show: true,
|
|
|
|
|
|
value: (() => {
|
|
|
|
|
|
const now = new Date();
|
|
|
|
|
|
const currentMonth = `${now.getFullYear()}-${String(
|
|
|
|
|
|
now.getMonth() + 1
|
|
|
|
|
|
).padStart(2, '0')}`;
|
|
|
|
|
|
const lastMonth = new Date(now.getFullYear(), now.getMonth() - 1, 1);
|
|
|
|
|
|
const lastMonthStr = `${lastMonth.getFullYear()}-${String(
|
|
|
|
|
|
lastMonth.getMonth() + 1
|
|
|
|
|
|
).padStart(2, '0')}`;
|
|
|
|
|
|
return [lastMonthStr, currentMonth];
|
|
|
|
|
|
})(),
|
|
|
|
|
|
format: 'YY-MM',
|
|
|
|
|
|
picker: 'month' as const,
|
|
|
|
|
|
options: []
|
2026-05-09 17:04:48 +08:00
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 图表数据
|
|
|
|
|
|
const echartsData = ref<any[]>([]);
|
2026-06-03 17:10:34 +08:00
|
|
|
|
const modelStore = useModelStore();
|
2026-05-09 17:04:48 +08:00
|
|
|
|
// 单位配置
|
|
|
|
|
|
const unita = ref('℃');
|
|
|
|
|
|
|
|
|
|
|
|
// 颜色配置 - 使用HSL色彩空间生成舒适的颜色
|
2026-06-16 11:36:17 +08:00
|
|
|
|
const Color = [
|
|
|
|
|
|
'#5470c6',
|
|
|
|
|
|
'#91cc75',
|
|
|
|
|
|
'#fac858',
|
|
|
|
|
|
'#ee6666',
|
|
|
|
|
|
'#73c0de',
|
|
|
|
|
|
'#3ba272',
|
|
|
|
|
|
'#fc8452',
|
|
|
|
|
|
'#9a60b4'
|
|
|
|
|
|
];
|
2026-05-09 17:04:48 +08:00
|
|
|
|
|
|
|
|
|
|
// ==================== 工具函数 ====================
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 生成模拟数据 - 季节性水温变化
|
|
|
|
|
|
*/
|
|
|
|
|
|
const generateMockData = () => {
|
2026-06-16 11:36:17 +08:00
|
|
|
|
const data = [];
|
|
|
|
|
|
const baseTemp = 15; // 基础温度
|
|
|
|
|
|
|
|
|
|
|
|
for (let i = 0; i < 12; i++) {
|
|
|
|
|
|
const month = String(i + 1).padStart(2, '0');
|
|
|
|
|
|
// 模拟季节性变化:夏季高,冬季低
|
|
|
|
|
|
// 正弦波模拟:峰值在7-8月,谷值在1-2月
|
|
|
|
|
|
const temp =
|
|
|
|
|
|
baseTemp +
|
|
|
|
|
|
Math.sin((i / 12) * Math.PI * 2 - Math.PI / 2) * 10 +
|
|
|
|
|
|
Math.random() * 2;
|
|
|
|
|
|
|
|
|
|
|
|
data.push({
|
|
|
|
|
|
dt: `2024-${month}`,
|
|
|
|
|
|
wt: parseFloat(temp.toFixed(1))
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
2026-05-09 17:04:48 +08:00
|
|
|
|
|
2026-06-16 11:36:17 +08:00
|
|
|
|
return data;
|
2026-05-09 17:04:48 +08:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 初始化图表
|
|
|
|
|
|
*/
|
|
|
|
|
|
const initChart = () => {
|
2026-06-16 11:36:17 +08:00
|
|
|
|
if (!chartRef.value) {
|
|
|
|
|
|
console.warn('图表容器未找到');
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
2026-05-09 17:04:48 +08:00
|
|
|
|
|
2026-06-16 11:36:17 +08:00
|
|
|
|
// 销毁已存在的实例
|
|
|
|
|
|
if (chartInstance) {
|
|
|
|
|
|
chartInstance.dispose();
|
|
|
|
|
|
}
|
2026-05-09 17:04:48 +08:00
|
|
|
|
|
2026-06-16 11:36:17 +08:00
|
|
|
|
// 创建新实例
|
|
|
|
|
|
chartInstance = echarts.init(chartRef.value);
|
2026-05-09 17:04:48 +08:00
|
|
|
|
|
2026-06-16 11:36:17 +08:00
|
|
|
|
// 绑定数据点点击事件
|
|
|
|
|
|
chartInstance.on('click', handleDataPointClick);
|
2026-06-03 17:10:34 +08:00
|
|
|
|
|
2026-06-16 11:36:17 +08:00
|
|
|
|
// 更新图表
|
|
|
|
|
|
updateChart();
|
2026-05-09 17:04:48 +08:00
|
|
|
|
|
2026-06-16 11:36:17 +08:00
|
|
|
|
// 监听窗口大小变化
|
|
|
|
|
|
window.addEventListener('resize', handleResize);
|
2026-05-09 17:04:48 +08:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 更新图表配置
|
|
|
|
|
|
*/
|
|
|
|
|
|
const updateChart = () => {
|
2026-06-16 11:36:17 +08:00
|
|
|
|
if (!chartInstance) return;
|
|
|
|
|
|
|
|
|
|
|
|
const _legendData = [`水温(${unita.value})`];
|
|
|
|
|
|
|
|
|
|
|
|
// 处理空数据情况
|
|
|
|
|
|
const hasData = echartsData.value && echartsData.value.length > 0;
|
|
|
|
|
|
|
|
|
|
|
|
const option: EChartsOption = {
|
|
|
|
|
|
tooltip: {
|
|
|
|
|
|
trigger: 'axis',
|
|
|
|
|
|
backgroundColor: 'rgba(0, 0, 0, 0.5)',
|
|
|
|
|
|
borderColor: 'transparent',
|
|
|
|
|
|
textStyle: {
|
|
|
|
|
|
color: '#ffffff'
|
|
|
|
|
|
},
|
|
|
|
|
|
formatter: function (params: any) {
|
|
|
|
|
|
if (!params || params.length === 0) return '';
|
|
|
|
|
|
|
|
|
|
|
|
let res = `${params[0].name} <br/>`;
|
|
|
|
|
|
|
|
|
|
|
|
for (const item of params) {
|
|
|
|
|
|
const seriesName = item.seriesName ?? '';
|
|
|
|
|
|
let regx = /\(([^()]+?)\)/;
|
|
|
|
|
|
let unit = seriesName.match(regx);
|
|
|
|
|
|
// 数据保留一位小数
|
|
|
|
|
|
const finalValue =
|
|
|
|
|
|
item.value !== undefined && item.value !== null
|
|
|
|
|
|
? Number(item.value).toFixed(1)
|
|
|
|
|
|
: '-';
|
|
|
|
|
|
|
|
|
|
|
|
if (item.value !== undefined && item.value !== null) {
|
|
|
|
|
|
res += `<span style="background: ${
|
|
|
|
|
|
item.color
|
|
|
|
|
|
}; height:10px; width: 10px; border-radius: 50%;display: inline-block;margin-right:10px;"></span> ${
|
|
|
|
|
|
item.seriesName
|
|
|
|
|
|
} ${finalValue}${unit?.[1] || ''} <br/>`;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return res;
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
// axisPointer 配置:鼠标经过时显示阴影块
|
|
|
|
|
|
axisPointer: {
|
|
|
|
|
|
type: 'shadow',
|
|
|
|
|
|
label: {
|
|
|
|
|
|
show: false
|
|
|
|
|
|
},
|
|
|
|
|
|
shadowStyle: {
|
|
|
|
|
|
color: 'rgba(84, 112, 198, 0.2)'
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
color: Color,
|
|
|
|
|
|
legend: {
|
|
|
|
|
|
data: _legendData,
|
|
|
|
|
|
top: 0,
|
|
|
|
|
|
left: 'center',
|
|
|
|
|
|
selectedMode: 'multiple'
|
|
|
|
|
|
},
|
|
|
|
|
|
grid: {
|
|
|
|
|
|
top: 25,
|
|
|
|
|
|
left: '3%',
|
|
|
|
|
|
right: '3%',
|
|
|
|
|
|
bottom: '3%',
|
|
|
|
|
|
containLabel: true
|
|
|
|
|
|
},
|
|
|
|
|
|
dataZoom: {
|
|
|
|
|
|
type: 'inside',
|
|
|
|
|
|
start: 0,
|
|
|
|
|
|
end: 100
|
|
|
|
|
|
},
|
|
|
|
|
|
xAxis: [
|
|
|
|
|
|
{
|
|
|
|
|
|
type: 'category',
|
|
|
|
|
|
data: echartsData.value?.map((item: any) => item.dt) || [],
|
|
|
|
|
|
boundaryGap: true,
|
|
|
|
|
|
// X轴标签配置 - 最多显示4个标签,强制显示第一个
|
|
|
|
|
|
axisLabel: {
|
|
|
|
|
|
show: true,
|
|
|
|
|
|
rotate: 0, // 不倾斜
|
|
|
|
|
|
interval: (index: number, value: string): boolean => {
|
|
|
|
|
|
const total = echartsData.value?.length || 0;
|
|
|
|
|
|
if (total <= 4) return true; // 数据少于4条,全部显示
|
|
|
|
|
|
|
|
|
|
|
|
// 强制显示第一个
|
|
|
|
|
|
if (index === 0) return true;
|
|
|
|
|
|
|
|
|
|
|
|
// 其余均匀分布3个标签(总共4个)
|
|
|
|
|
|
const step = Math.floor((total - 1) / 3);
|
|
|
|
|
|
return index % step === 0 && index < total;
|
|
|
|
|
|
},
|
|
|
|
|
|
formatter: function (value: string) {
|
|
|
|
|
|
// 显示完整日期 "YYYY-MM-DD" 格式
|
|
|
|
|
|
return value;
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
// X轴网格线(垂直方向)
|
|
|
|
|
|
splitLine: {
|
|
|
|
|
|
show: true,
|
|
|
|
|
|
lineStyle: {
|
|
|
|
|
|
type: 'solid',
|
|
|
|
|
|
color: '#e0e0e0'
|
|
|
|
|
|
}
|
2026-05-09 17:04:48 +08:00
|
|
|
|
},
|
2026-06-16 11:36:17 +08:00
|
|
|
|
// X轴的axisPointer配置:鼠标经过时显示阴影块
|
2026-05-09 17:04:48 +08:00
|
|
|
|
axisPointer: {
|
2026-06-16 11:36:17 +08:00
|
|
|
|
type: 'shadow',
|
|
|
|
|
|
shadowStyle: {
|
|
|
|
|
|
color: 'rgba(84, 112, 198, 0.2)'
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
],
|
|
|
|
|
|
yAxis: [
|
|
|
|
|
|
{
|
|
|
|
|
|
type: 'value',
|
|
|
|
|
|
// 移除 name,避免与 legend 重复,在刻度上显示单位
|
|
|
|
|
|
name: _legendData[0], // 显示 "水温(℃)"
|
|
|
|
|
|
nameLocation: 'end', // 显示在 Y 轴顶部
|
|
|
|
|
|
nameGap: 8, // 与轴线距离 20px
|
|
|
|
|
|
nameTextStyle: {
|
|
|
|
|
|
color: '#333333',
|
|
|
|
|
|
fontSize: 12
|
2026-05-09 17:04:48 +08:00
|
|
|
|
},
|
2026-06-16 11:36:17 +08:00
|
|
|
|
// 计算 Y 轴范围:最小值向下取整,最大值向上取整
|
|
|
|
|
|
min:
|
|
|
|
|
|
hasData && echartsData.value.length > 0
|
|
|
|
|
|
? Math.floor(Math.min(...echartsData.value.map(item => item.wt)))
|
|
|
|
|
|
: undefined,
|
|
|
|
|
|
max:
|
|
|
|
|
|
hasData && echartsData.value.length > 0
|
|
|
|
|
|
? Math.ceil(Math.max(...echartsData.value.map(item => item.wt)))
|
|
|
|
|
|
: undefined,
|
|
|
|
|
|
// 确保显示刻度和标签,保持整数
|
|
|
|
|
|
axisLabel: {
|
|
|
|
|
|
show: true,
|
|
|
|
|
|
color: '#333',
|
|
|
|
|
|
formatter: (value: number) => `${Math.round(value)} ℃` // 取整并显示单位
|
2026-05-09 17:04:48 +08:00
|
|
|
|
},
|
2026-06-16 11:36:17 +08:00
|
|
|
|
axisTick: {
|
|
|
|
|
|
show: true
|
2026-05-09 17:04:48 +08:00
|
|
|
|
},
|
2026-06-16 11:36:17 +08:00
|
|
|
|
axisLine: {
|
|
|
|
|
|
show: true,
|
|
|
|
|
|
lineStyle: {
|
|
|
|
|
|
color: '#333'
|
|
|
|
|
|
}
|
2026-05-09 17:04:48 +08:00
|
|
|
|
},
|
2026-06-16 11:36:17 +08:00
|
|
|
|
splitLine: {
|
|
|
|
|
|
show: true,
|
|
|
|
|
|
lineStyle: {
|
|
|
|
|
|
type: 'solid'
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
// 确保 Y 轴刻度为整数
|
|
|
|
|
|
minInterval: 1,
|
|
|
|
|
|
maxInterval: 1
|
|
|
|
|
|
}
|
|
|
|
|
|
],
|
|
|
|
|
|
series: [
|
|
|
|
|
|
{
|
|
|
|
|
|
name: _legendData[0],
|
|
|
|
|
|
data: hasData
|
|
|
|
|
|
? echartsData.value?.map((item: any) => item.wt) || []
|
|
|
|
|
|
: [],
|
|
|
|
|
|
type: 'line',
|
|
|
|
|
|
smooth: true,
|
|
|
|
|
|
symbol: 'circle',
|
|
|
|
|
|
symbolSize: 4,
|
|
|
|
|
|
lineStyle: {
|
|
|
|
|
|
width: 2
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
]
|
|
|
|
|
|
};
|
|
|
|
|
|
// 使用完全替换模式更新配置
|
|
|
|
|
|
chartInstance.setOption(option, true);
|
2026-05-09 17:04:48 +08:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 处理窗口大小变化
|
|
|
|
|
|
*/
|
|
|
|
|
|
const handleResize = () => {
|
2026-06-16 11:36:17 +08:00
|
|
|
|
if (chartInstance) {
|
|
|
|
|
|
chartInstance.resize();
|
|
|
|
|
|
}
|
2026-05-09 17:04:48 +08:00
|
|
|
|
};
|
|
|
|
|
|
|
2026-06-03 17:10:34 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 处理数据点点击事件
|
|
|
|
|
|
*/
|
|
|
|
|
|
const handleDataPointClick = (params: any) => {
|
2026-06-16 11:36:17 +08:00
|
|
|
|
console.log('=== 数据点点击事件 ===');
|
|
|
|
|
|
console.log('点击参数:', params);
|
|
|
|
|
|
|
|
|
|
|
|
if (!params || !params.dataIndex) {
|
|
|
|
|
|
console.warn('无效的数据点点击');
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const dataIndex = params.dataIndex;
|
|
|
|
|
|
const dataItem = echartsData.value[dataIndex];
|
|
|
|
|
|
|
|
|
|
|
|
if (dataItem) {
|
|
|
|
|
|
console.log('点击的数据:', {
|
|
|
|
|
|
日期: dataItem.dt,
|
|
|
|
|
|
水温: dataItem.wt,
|
|
|
|
|
|
测站代码: dataItem.stcd,
|
|
|
|
|
|
索引: dataIndex
|
|
|
|
|
|
});
|
|
|
|
|
|
modelStore.modalVisible = true;
|
|
|
|
|
|
modelStore.params.sttp = 'WT';
|
|
|
|
|
|
modelStore.title = '水温监测';
|
|
|
|
|
|
modelStore.params.stcd = dataItem.stcd;
|
|
|
|
|
|
modelStore.showStcdSelector = true;
|
|
|
|
|
|
modelStore.stcdOptions = select.value.options;
|
|
|
|
|
|
modelStore.filter.rangeTm = [
|
|
|
|
|
|
dayjs(dataItem.dt).startOf('day').format('YYYY-MM-DD HH:mm:ss'),
|
|
|
|
|
|
dayjs(dataItem.dt).endOf('day').format('YYYY-MM-DD HH:mm:ss')
|
|
|
|
|
|
];
|
|
|
|
|
|
}
|
2026-06-03 17:10:34 +08:00
|
|
|
|
};
|
|
|
|
|
|
|
2026-05-09 17:04:48 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 加载模拟数据
|
|
|
|
|
|
*/
|
|
|
|
|
|
const loadData = async () => {
|
2026-06-16 11:36:17 +08:00
|
|
|
|
try {
|
|
|
|
|
|
// 生成模拟数据
|
|
|
|
|
|
echartsData.value = generateMockData();
|
|
|
|
|
|
|
|
|
|
|
|
// 数据加载完成后更新图表
|
|
|
|
|
|
await nextTick();
|
|
|
|
|
|
updateChart();
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('加载水温数据失败:', error);
|
|
|
|
|
|
}
|
2026-05-09 17:04:48 +08:00
|
|
|
|
};
|
|
|
|
|
|
|
2026-06-03 17:10:34 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 加载图表数据
|
|
|
|
|
|
* @param startDate 开始日期 (格式: YYYY-MM-DD HH:mm:ss)
|
|
|
|
|
|
* @param endDate 结束日期 (格式: YYYY-MM-DD HH:mm:ss)
|
|
|
|
|
|
* @param stcd 测站代码
|
|
|
|
|
|
*/
|
2026-06-16 11:36:17 +08:00
|
|
|
|
const getChartData = async (
|
|
|
|
|
|
startDate: string,
|
|
|
|
|
|
endDate: string,
|
|
|
|
|
|
stcd: string
|
|
|
|
|
|
) => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
console.log('=== 开始加载图表数据 ===');
|
|
|
|
|
|
console.log('请求参数:', { startDate, endDate, stcd });
|
|
|
|
|
|
|
|
|
|
|
|
// 设置 loading 状态
|
|
|
|
|
|
loading.value = true;
|
|
|
|
|
|
hasData.value = false;
|
|
|
|
|
|
|
|
|
|
|
|
// TODO: 实现API调用逻辑
|
|
|
|
|
|
// 参考选中的代码结构,构建请求参数
|
|
|
|
|
|
const params = {
|
|
|
|
|
|
filter: {
|
|
|
|
|
|
logic: 'and',
|
|
|
|
|
|
filters: [
|
|
|
|
|
|
{
|
|
|
|
|
|
field: 'DT',
|
|
|
|
|
|
operator: 'gte',
|
|
|
|
|
|
value: startDate // 例如: "2026-05-01 00:00:00"
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
field: 'DT',
|
|
|
|
|
|
operator: 'lte',
|
|
|
|
|
|
value: endDate // 例如: "2026-06-30 23:59:59"
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
field: 'stcd',
|
|
|
|
|
|
operator: 'eq',
|
|
|
|
|
|
dataType: 'string',
|
|
|
|
|
|
value: stcd
|
|
|
|
|
|
}
|
|
|
|
|
|
]
|
|
|
|
|
|
},
|
|
|
|
|
|
sort: [
|
|
|
|
|
|
{
|
|
|
|
|
|
field: 'dt',
|
|
|
|
|
|
dir: 'asc'
|
2026-06-03 17:10:34 +08:00
|
|
|
|
}
|
2026-06-16 11:36:17 +08:00
|
|
|
|
]
|
|
|
|
|
|
};
|
2026-06-03 17:10:34 +08:00
|
|
|
|
|
2026-06-16 11:36:17 +08:00
|
|
|
|
// 调用实际的API获取数据
|
|
|
|
|
|
const res = await sdrvwtsGetKendoList(params);
|
|
|
|
|
|
console.log('API返回结果:', res);
|
2026-06-04 11:42:07 +08:00
|
|
|
|
|
2026-06-16 11:36:17 +08:00
|
|
|
|
let data = res?.data?.data || [];
|
|
|
|
|
|
console.log('原始数据条数:', data.length);
|
2026-06-04 11:42:07 +08:00
|
|
|
|
|
2026-06-16 11:36:17 +08:00
|
|
|
|
// 处理空数据情况
|
|
|
|
|
|
if (!data || data.length === 0) {
|
|
|
|
|
|
echartsData.value = [];
|
|
|
|
|
|
hasData.value = false;
|
|
|
|
|
|
await nextTick();
|
|
|
|
|
|
updateChart();
|
|
|
|
|
|
console.warn('未查询到水温数据');
|
|
|
|
|
|
return;
|
2026-06-03 17:10:34 +08:00
|
|
|
|
}
|
2026-06-16 11:36:17 +08:00
|
|
|
|
|
|
|
|
|
|
// 转换数据格式:提取日期部分用于X轴显示,并进行数据验证和类型转换
|
|
|
|
|
|
echartsData.value = data
|
|
|
|
|
|
.map(item => {
|
|
|
|
|
|
// 验证必要字段
|
|
|
|
|
|
if (!item.dt || typeof item.dt !== 'string') {
|
|
|
|
|
|
console.warn('无效的日期数据:', item);
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 确保 wt 是数值类型
|
|
|
|
|
|
const wt = typeof item.wt === 'number' ? item.wt : parseFloat(item.wt);
|
|
|
|
|
|
if (isNaN(wt)) {
|
|
|
|
|
|
console.warn('无效的水温数据:', item);
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
dt: item.dt.substring(0, 10), // 提取 "2026-05-02" 格式
|
|
|
|
|
|
wt: wt, // 确保是 number 类型
|
|
|
|
|
|
stcd: item.stcd || stcd // 保留 stcd 字段,如果 API 返回中没有则使用传入的 stcd
|
|
|
|
|
|
};
|
|
|
|
|
|
})
|
|
|
|
|
|
.filter(Boolean); // 过滤掉无效数据
|
|
|
|
|
|
|
|
|
|
|
|
console.log('转换后的图表数据:', echartsData.value);
|
|
|
|
|
|
console.log('数据类型检查:', {
|
|
|
|
|
|
第一条数据: echartsData.value[0],
|
|
|
|
|
|
dt类型: typeof echartsData.value[0]?.dt,
|
|
|
|
|
|
wt类型: typeof echartsData.value[0]?.wt,
|
|
|
|
|
|
wt값: echartsData.value[0]?.wt
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 判断是否有数据
|
|
|
|
|
|
hasData.value = echartsData.value.length > 0;
|
|
|
|
|
|
console.log('hasData 设置为:', hasData.value);
|
|
|
|
|
|
|
|
|
|
|
|
// 数据加载完成后更新图表
|
|
|
|
|
|
await nextTick();
|
|
|
|
|
|
updateChart();
|
|
|
|
|
|
console.log('✅ 图表数据加载完成');
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('获取图表数据失败:', error);
|
|
|
|
|
|
hasData.value = false;
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
// 关闭 loading 状态
|
|
|
|
|
|
await nextTick();
|
|
|
|
|
|
console.log('关闭 loading 状态');
|
|
|
|
|
|
loading.value = false;
|
|
|
|
|
|
}
|
2026-06-03 17:10:34 +08:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 将 YY-MM 格式转换为完整的日期时间范围
|
|
|
|
|
|
* @param dateRange ['26-05', '26-06'] 格式的数组
|
|
|
|
|
|
* @returns { startDate: string, endDate: string } 格式化后的日期范围
|
|
|
|
|
|
*/
|
|
|
|
|
|
const convertDateRange = (dateRange: string[]) => {
|
2026-06-16 11:36:17 +08:00
|
|
|
|
if (!dateRange || dateRange.length !== 2) {
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
2026-06-18 09:29:15 +08:00
|
|
|
|
//
|
2026-06-16 11:36:17 +08:00
|
|
|
|
|
|
|
|
|
|
const [startStr, endStr] = dateRange;
|
|
|
|
|
|
|
|
|
|
|
|
// 获取今天的日期(日部分)
|
|
|
|
|
|
const today = new Date();
|
|
|
|
|
|
const currentDay = today.getDate();
|
|
|
|
|
|
|
|
|
|
|
|
// 解析开始日期 YY-MM
|
|
|
|
|
|
const [startYearStr, startMonthStr] = startStr.split('-');
|
|
|
|
|
|
const startYear = parseInt(startYearStr);
|
|
|
|
|
|
const startMonth = parseInt(startMonthStr);
|
|
|
|
|
|
const fullStartYear = startYear < 100 ? 2000 + startYear : startYear;
|
|
|
|
|
|
|
|
|
|
|
|
// 解析结束日期 YY-MM
|
|
|
|
|
|
const [endYearStr, endMonthStr] = endStr.split('-');
|
|
|
|
|
|
const endYear = parseInt(endYearStr);
|
|
|
|
|
|
const endMonth = parseInt(endMonthStr);
|
|
|
|
|
|
const fullEndYear = endYear < 100 ? 2000 + endYear : endYear;
|
|
|
|
|
|
|
|
|
|
|
|
// 开始日期:基于第一个值的年月 + 今天的日期 + 00:00:00
|
|
|
|
|
|
// 需要处理月份天数边界问题(如今天是31号,但2月只有28天)
|
|
|
|
|
|
const startMonthLastDay = new Date(fullStartYear, startMonth, 0).getDate();
|
|
|
|
|
|
const actualStartDay = Math.min(currentDay, startMonthLastDay);
|
|
|
|
|
|
const startDate = `${fullStartYear}-${String(startMonth).padStart(
|
|
|
|
|
|
2,
|
|
|
|
|
|
'0'
|
|
|
|
|
|
)}-${String(actualStartDay).padStart(2, '0')} 00:00:00`;
|
|
|
|
|
|
|
|
|
|
|
|
// 结束日期:基于第二个值的年月 + 今天的日期 + 23:59:59
|
|
|
|
|
|
// 同样需要处理月份天数边界问题
|
|
|
|
|
|
const endMonthLastDay = new Date(fullEndYear, endMonth, 0).getDate();
|
|
|
|
|
|
const actualEndDay = Math.min(currentDay, endMonthLastDay);
|
|
|
|
|
|
const endDate = `${fullEndYear}-${String(endMonth).padStart(2, '0')}-${String(
|
|
|
|
|
|
actualEndDay
|
|
|
|
|
|
).padStart(2, '0')} 23:59:59`;
|
|
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
startDate,
|
|
|
|
|
|
endDate
|
|
|
|
|
|
};
|
2026-06-03 17:10:34 +08:00
|
|
|
|
};
|
|
|
|
|
|
|
2026-06-16 11:36:17 +08:00
|
|
|
|
const handlePanelChange1 = async data => {
|
|
|
|
|
|
console.log('=== 用户交互触发 ===');
|
|
|
|
|
|
console.log('当前所有控件状态:', data);
|
|
|
|
|
|
// scopeDate: ['26-05', '26-06']
|
|
|
|
|
|
// select: "0836000011"
|
|
|
|
|
|
if (data.scopeDate && data.scopeDate.length > 0 && data.select) {
|
|
|
|
|
|
console.log('参数验证通过,开始加载数据');
|
|
|
|
|
|
// 转换日期格式并加载数据
|
|
|
|
|
|
const dateRange = convertDateRange(data.scopeDate);
|
|
|
|
|
|
if (dateRange) {
|
|
|
|
|
|
console.log('日期转换结果:', dateRange);
|
|
|
|
|
|
await getChartData(dateRange.startDate, dateRange.endDate, data.select);
|
2026-06-03 17:10:34 +08:00
|
|
|
|
} else {
|
2026-06-16 11:36:17 +08:00
|
|
|
|
console.error('日期转换失败');
|
2026-06-03 17:10:34 +08:00
|
|
|
|
}
|
2026-06-16 11:36:17 +08:00
|
|
|
|
} else {
|
|
|
|
|
|
loading.value = false;
|
|
|
|
|
|
hasData.value = false;
|
|
|
|
|
|
// 清空图表数据
|
|
|
|
|
|
echartsData.value = [];
|
|
|
|
|
|
// 更新图表显示空状态
|
|
|
|
|
|
await nextTick();
|
|
|
|
|
|
updateChart();
|
|
|
|
|
|
console.warn('参数不完整:', {
|
|
|
|
|
|
scopeDate: data.scopeDate,
|
|
|
|
|
|
select: data.select
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
2026-06-03 17:10:34 +08:00
|
|
|
|
//获取水温监测下拉框
|
|
|
|
|
|
const getselsectData = async () => {
|
2026-06-16 11:36:17 +08:00
|
|
|
|
let params = {
|
|
|
|
|
|
filter: {
|
|
|
|
|
|
logic: 'and',
|
|
|
|
|
|
filters: [
|
|
|
|
|
|
baseid.value != 'all'
|
|
|
|
|
|
? {
|
|
|
|
|
|
field: 'baseId',
|
|
|
|
|
|
operator: 'eq',
|
|
|
|
|
|
dataType: 'string',
|
|
|
|
|
|
value: baseid.value
|
|
|
|
|
|
}
|
|
|
|
|
|
: null,
|
|
|
|
|
|
{
|
|
|
|
|
|
field: 'fhstcd',
|
|
|
|
|
|
operator: 'isnotnull'
|
2026-06-03 17:10:34 +08:00
|
|
|
|
},
|
2026-06-16 11:36:17 +08:00
|
|
|
|
{
|
|
|
|
|
|
logic: 'or',
|
|
|
|
|
|
filters: [
|
|
|
|
|
|
{
|
|
|
|
|
|
field: 'sttpCode',
|
|
|
|
|
|
operator: 'eq',
|
|
|
|
|
|
dataType: 'string',
|
|
|
|
|
|
value: 'WTRV'
|
|
|
|
|
|
}
|
|
|
|
|
|
]
|
2026-06-03 17:10:34 +08:00
|
|
|
|
}
|
2026-06-16 11:36:17 +08:00
|
|
|
|
].filter(Boolean)
|
|
|
|
|
|
},
|
|
|
|
|
|
select: ['stcd', 'stnm', 'fhstcd', 'fhstnm', 'sttpCode']
|
|
|
|
|
|
};
|
|
|
|
|
|
let res = await msstbprptGetKendoList(params);
|
|
|
|
|
|
console.log(res);
|
|
|
|
|
|
if (!res?.data?.data || res.data.data.length == 0) {
|
|
|
|
|
|
select.value.value = '';
|
|
|
|
|
|
select.value.options = [];
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
select.value.value = res.data.data[0].stcd;
|
|
|
|
|
|
select.value.options = res.data.data.map((item: any) => {
|
|
|
|
|
|
return {
|
|
|
|
|
|
label: item.stnm,
|
|
|
|
|
|
value: item.stcd
|
|
|
|
|
|
};
|
|
|
|
|
|
});
|
2026-06-18 09:29:15 +08:00
|
|
|
|
//
|
2026-06-03 17:10:34 +08:00
|
|
|
|
};
|
|
|
|
|
|
////
|
|
|
|
|
|
watch(
|
2026-06-16 11:36:17 +08:00
|
|
|
|
() => JidiSelectEventStore.selectedItem,
|
|
|
|
|
|
newVal => {
|
|
|
|
|
|
baseid.value = newVal.wbsCode;
|
|
|
|
|
|
getselsectData();
|
|
|
|
|
|
},
|
|
|
|
|
|
{ deep: true, immediate: true }
|
2026-06-03 17:10:34 +08:00
|
|
|
|
);
|
2026-05-09 17:04:48 +08:00
|
|
|
|
// ==================== 生命周期钩子 ====================
|
|
|
|
|
|
|
|
|
|
|
|
// 页面加载时执行
|
|
|
|
|
|
onMounted(() => {
|
2026-06-16 11:36:17 +08:00
|
|
|
|
// 双重等待确保DOM和样式计算完成
|
|
|
|
|
|
nextTick(() => {
|
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
|
// 校验容器尺寸
|
|
|
|
|
|
if (chartRef.value) {
|
|
|
|
|
|
const rect = chartRef.value.getBoundingClientRect();
|
|
|
|
|
|
if (rect.width > 0 && rect.height > 0) {
|
|
|
|
|
|
initChart();
|
|
|
|
|
|
// 注释掉模拟数据加载,等待用户交互后加载真实数据
|
|
|
|
|
|
// loadData();
|
|
|
|
|
|
console.log('✅ 图表初始化完成,等待用户交互加载数据');
|
|
|
|
|
|
} else {
|
|
|
|
|
|
console.warn('图表容器尺寸为0,启动重试机制');
|
|
|
|
|
|
// 指数退避重试
|
|
|
|
|
|
let retryCount = 0;
|
|
|
|
|
|
const maxRetries = 5;
|
|
|
|
|
|
const retryInit = () => {
|
|
|
|
|
|
if (retryCount < maxRetries) {
|
|
|
|
|
|
retryCount++;
|
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
|
const newRect = chartRef.value?.getBoundingClientRect();
|
|
|
|
|
|
if (newRect && newRect.width > 0 && newRect.height > 0) {
|
|
|
|
|
|
initChart();
|
|
|
|
|
|
// loadData();
|
|
|
|
|
|
console.log('✅ 图表初始化完成(重试成功)');
|
2026-05-09 17:04:48 +08:00
|
|
|
|
} else {
|
2026-06-16 11:36:17 +08:00
|
|
|
|
retryInit();
|
2026-05-09 17:04:48 +08:00
|
|
|
|
}
|
2026-06-16 11:36:17 +08:00
|
|
|
|
}, 50 * Math.pow(2, retryCount));
|
|
|
|
|
|
} else {
|
|
|
|
|
|
console.error('图表容器初始化失败,已达到最大重试次数');
|
2026-05-09 17:04:48 +08:00
|
|
|
|
}
|
2026-06-16 11:36:17 +08:00
|
|
|
|
};
|
|
|
|
|
|
retryInit();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}, 50);
|
|
|
|
|
|
});
|
2026-05-09 17:04:48 +08:00
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 组件卸载时清理
|
|
|
|
|
|
onUnmounted(() => {
|
2026-06-16 11:36:17 +08:00
|
|
|
|
// 移除事件监听
|
|
|
|
|
|
window.removeEventListener('resize', handleResize);
|
|
|
|
|
|
|
|
|
|
|
|
// 销毁图表实例
|
|
|
|
|
|
if (chartInstance) {
|
|
|
|
|
|
// 解绑数据点点击事件
|
|
|
|
|
|
chartInstance.off('click', handleDataPointClick);
|
|
|
|
|
|
chartInstance.dispose();
|
|
|
|
|
|
chartInstance = null;
|
|
|
|
|
|
}
|
2026-05-09 17:04:48 +08:00
|
|
|
|
});
|
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
<style lang="scss" scoped>
|
2026-06-03 17:10:34 +08:00
|
|
|
|
.chart-wrapper {
|
2026-06-16 11:36:17 +08:00
|
|
|
|
width: 100%;
|
|
|
|
|
|
height: 231px;
|
|
|
|
|
|
min-height: 231px;
|
|
|
|
|
|
position: relative;
|
|
|
|
|
|
/* 为empty-overlay提供定位上下文 */
|
|
|
|
|
|
|
|
|
|
|
|
.water-temp-chart {
|
|
|
|
|
|
width: 100% !important;
|
|
|
|
|
|
height: 100% !important;
|
|
|
|
|
|
min-width: 100%;
|
2026-05-09 17:04:48 +08:00
|
|
|
|
min-height: 231px;
|
2026-06-16 11:36:17 +08:00
|
|
|
|
}
|
2026-06-03 17:10:34 +08:00
|
|
|
|
|
2026-06-16 11:36:17 +08:00
|
|
|
|
.empty-overlay {
|
|
|
|
|
|
position: absolute;
|
|
|
|
|
|
top: 0;
|
|
|
|
|
|
left: 0;
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
height: 100%;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: center;
|
|
|
|
|
|
background: rgba(255, 255, 255);
|
|
|
|
|
|
z-index: 10;
|
|
|
|
|
|
}
|
2026-05-09 17:04:48 +08:00
|
|
|
|
}
|
2026-06-16 11:36:17 +08:00
|
|
|
|
</style>
|