2026-05-09 17:04:48 +08:00
|
|
|
|
<!-- SidePanelItem.vue -->
|
|
|
|
|
|
<template>
|
2026-06-03 17:10:34 +08:00
|
|
|
|
<SidePanelItem title="水文监测" :select="select" :scopeDate="datetimePicker" @update-values="handlePanelChange1">
|
|
|
|
|
|
<div class="chart-wrapper">
|
|
|
|
|
|
<a-spin :spinning="loading">
|
|
|
|
|
|
<!-- 始终渲染图表容器,确保有固定宽高 -->
|
|
|
|
|
|
<div ref="chartRef" class="echarts-chart"></div>
|
|
|
|
|
|
<!-- 无数据时显示Empty,但不影响容器尺寸 -->
|
|
|
|
|
|
<div v-if="!loading && !hasData" class="empty-overlay">
|
|
|
|
|
|
<a-empty description="暂无数据" />
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</a-spin>
|
2026-05-09 17:04:48 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</SidePanelItem>
|
|
|
|
|
|
</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-03 17:10:34 +08:00
|
|
|
|
import { useJidiSelectEventStore } from "@/store/modules/jidiSelectEvent";
|
|
|
|
|
|
import { msstbprptGetKendoList, sdriverdaysGetKendoList } from '@/api/qxd';
|
|
|
|
|
|
import { useModelStore } from "@/store/modules/model";
|
2026-05-09 17:04:48 +08:00
|
|
|
|
// 定义组件名
|
|
|
|
|
|
defineOptions({
|
|
|
|
|
|
name: 'qixidiliuliangbianhua'
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// ==================== 选择器配置 ====================
|
2026-06-03 17:10:34 +08:00
|
|
|
|
const JidiSelectEventStore = useJidiSelectEventStore();
|
|
|
|
|
|
const baseid = ref('')
|
|
|
|
|
|
const modelStore = useModelStore();
|
|
|
|
|
|
|
|
|
|
|
|
// Loading 状态和数据状态
|
|
|
|
|
|
const loading = ref(false);
|
|
|
|
|
|
const hasData = ref(true); // 初始化为 true,因为 initChart 时会渲染
|
|
|
|
|
|
|
2026-05-09 17:04:48 +08:00
|
|
|
|
const select = ref({
|
|
|
|
|
|
show: true,
|
|
|
|
|
|
value: undefined,
|
|
|
|
|
|
options: [],
|
|
|
|
|
|
picker: undefined,
|
|
|
|
|
|
format: undefined
|
|
|
|
|
|
});
|
2026-05-12 08:47:27 +08:00
|
|
|
|
// 日期选择器配置
|
2026-06-03 17:10:34 +08:00
|
|
|
|
const datetimePicker: any = ref({
|
2026-05-12 08:47:27 +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 chartRef = ref<HTMLElement | null>(null);
|
|
|
|
|
|
let chartInstance: echarts.ECharts | null = null;
|
|
|
|
|
|
|
2026-06-03 17:10:34 +08:00
|
|
|
|
// 图表数据(初始为空数组)
|
|
|
|
|
|
const echartsData = ref<any[]>([]);
|
2026-05-09 17:04:48 +08:00
|
|
|
|
|
|
|
|
|
|
// 图表配置引用(用于 legendselectchanged 时读取最新配置)
|
|
|
|
|
|
const echartOptionRef = ref<EChartsOption>({});
|
|
|
|
|
|
|
|
|
|
|
|
// ==================== 工具函数:omit ====================
|
|
|
|
|
|
const omit = (obj: any, key: string) => {
|
|
|
|
|
|
const newObj = { ...obj };
|
|
|
|
|
|
delete newObj[key];
|
|
|
|
|
|
return newObj;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// ==================== 多Y轴动态布局算法 ====================
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 根据图例选中状态动态调整 Y 轴布局和 grid 边距
|
|
|
|
|
|
* @param selected - 图例选中状态对象 { '系列名': boolean }
|
|
|
|
|
|
* @param options - ECharts 配置对象
|
|
|
|
|
|
*
|
|
|
|
|
|
* 规则:
|
|
|
|
|
|
* - 左侧最多 1 个 Y 轴(第一个可见系列)
|
|
|
|
|
|
* - 右侧可以显示多个 Y 轴(其余可见系列)
|
|
|
|
|
|
*/
|
|
|
|
|
|
const yAxisShowDynamic = (selected: Record<string, boolean>, options: any) => {
|
|
|
|
|
|
const allShow = options.yAxis?.filter((item: any) => item.show);
|
|
|
|
|
|
const showCount = allShow?.length || 0;
|
|
|
|
|
|
|
|
|
|
|
|
// 没有显示的Y轴
|
|
|
|
|
|
if (showCount === 0) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 只有一个Y轴:统一置于左侧
|
|
|
|
|
|
if (showCount === 1) {
|
|
|
|
|
|
options.grid = omit(options.grid, 'right');
|
2026-06-03 17:10:34 +08:00
|
|
|
|
options.grid.left = '60px';
|
2026-05-09 17:04:48 +08:00
|
|
|
|
|
|
|
|
|
|
options.yAxis = options.yAxis.map((item: any) => {
|
|
|
|
|
|
if (item.show) {
|
|
|
|
|
|
return {
|
|
|
|
|
|
...item,
|
|
|
|
|
|
position: 'left',
|
|
|
|
|
|
offset: 0
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
return item;
|
|
|
|
|
|
});
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 两个及以上Y轴:左侧1个,右侧其余
|
|
|
|
|
|
if (showCount >= 2) {
|
|
|
|
|
|
let leftIndex = 0; // 左侧Y轴索引
|
|
|
|
|
|
let rightIndex = 0; // 右侧Y轴计数
|
|
|
|
|
|
|
|
|
|
|
|
options.yAxis = options.yAxis.map((item: any) => {
|
|
|
|
|
|
if (!item.show) {
|
|
|
|
|
|
return item;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 第一个可见的Y轴放在左侧
|
|
|
|
|
|
if (leftIndex === 0) {
|
|
|
|
|
|
leftIndex++;
|
|
|
|
|
|
options.grid = omit(options.grid, 'right');
|
2026-06-03 17:10:34 +08:00
|
|
|
|
options.grid.left = '60px';
|
2026-05-09 17:04:48 +08:00
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
...item,
|
|
|
|
|
|
position: 'left',
|
|
|
|
|
|
offset: 0
|
|
|
|
|
|
};
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 其余Y轴放在右侧
|
|
|
|
|
|
rightIndex++;
|
|
|
|
|
|
|
|
|
|
|
|
// 右侧有多个Y轴时,需要增加右边距并设置偏移
|
|
|
|
|
|
if (rightIndex > 1) {
|
|
|
|
|
|
options.grid.right = '100px';
|
|
|
|
|
|
return {
|
|
|
|
|
|
...item,
|
|
|
|
|
|
position: 'right',
|
|
|
|
|
|
offset: 60
|
|
|
|
|
|
};
|
|
|
|
|
|
} else {
|
|
|
|
|
|
options.grid.right = '60px';
|
|
|
|
|
|
return {
|
|
|
|
|
|
...item,
|
|
|
|
|
|
position: 'right',
|
|
|
|
|
|
offset: 0
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// ==================== 图表初始化 ====================
|
|
|
|
|
|
const initChart = () => {
|
|
|
|
|
|
if (!chartRef.value) {
|
|
|
|
|
|
console.warn('图表容器未就绪');
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 初始化 ECharts 实例
|
|
|
|
|
|
chartInstance = echarts.init(chartRef.value);
|
|
|
|
|
|
|
|
|
|
|
|
// 监听图例选择变化事件
|
|
|
|
|
|
chartInstance.on('legendselectchanged', (params: any) => {
|
|
|
|
|
|
const { selected } = params;
|
|
|
|
|
|
|
2026-06-03 17:10:34 +08:00
|
|
|
|
// ✅ 方案A:手动构建新配置对象,保留所有函数引用(包括formatter)
|
|
|
|
|
|
const currentOptions = echartOptionRef.value as any;
|
2026-05-09 17:04:48 +08:00
|
|
|
|
|
2026-06-03 17:10:34 +08:00
|
|
|
|
const options: any = {
|
|
|
|
|
|
...currentOptions,
|
|
|
|
|
|
legend: {
|
|
|
|
|
|
...currentOptions.legend,
|
|
|
|
|
|
selected: selected
|
|
|
|
|
|
},
|
|
|
|
|
|
yAxis: Array.isArray(currentOptions.yAxis)
|
|
|
|
|
|
? currentOptions.yAxis.map((item: any) => {
|
|
|
|
|
|
let isShow = true;
|
|
|
|
|
|
for (const key in selected) {
|
|
|
|
|
|
if (key === item.name) {
|
|
|
|
|
|
isShow = selected[key];
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return { ...item, show: isShow }; // ✅ 保留formatter等函数
|
|
|
|
|
|
})
|
|
|
|
|
|
: currentOptions.yAxis
|
|
|
|
|
|
};
|
2026-05-09 17:04:48 +08:00
|
|
|
|
|
|
|
|
|
|
// 触发布局调整
|
|
|
|
|
|
yAxisShowDynamic(selected, options);
|
|
|
|
|
|
|
|
|
|
|
|
// 完全替换模式更新配置
|
|
|
|
|
|
chartInstance?.setOption(options, true);
|
|
|
|
|
|
echartOptionRef.value = options;
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 监听数据点点击事件
|
|
|
|
|
|
chartInstance.on('click', (params: any) => {
|
2026-06-03 17:10:34 +08:00
|
|
|
|
// ✅ 判断是否点击的是数据点(series中的点)
|
|
|
|
|
|
if (params.componentType === 'series') {
|
|
|
|
|
|
modelStore.modalVisible = true;
|
|
|
|
|
|
modelStore.params.sttp = "fh_zq_point";
|
|
|
|
|
|
modelStore.title = "水文监测";
|
|
|
|
|
|
modelStore.params.stcd = select.value.value;
|
2026-06-03 18:25:57 +08:00
|
|
|
|
modelStore.showStcdSelector = true
|
|
|
|
|
|
modelStore.stcdOptions = select.value.options
|
2026-06-03 17:10:34 +08:00
|
|
|
|
}
|
2026-05-09 17:04:48 +08:00
|
|
|
|
});
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// ==================== 图表配置生成 ====================
|
|
|
|
|
|
const updateChart = () => {
|
|
|
|
|
|
if (!chartInstance) return;
|
|
|
|
|
|
|
2026-06-03 17:10:34 +08:00
|
|
|
|
// ✅ 计算数据统计值(用于Y轴动态范围)
|
|
|
|
|
|
const qValues = echartsData.value.map(item => item.q).filter(v => v != null && !isNaN(v));
|
|
|
|
|
|
const zValues = echartsData.value.map(item => item.z).filter(v => v != null && !isNaN(v));
|
|
|
|
|
|
const vValues = echartsData.value.map(item => item.v).filter(v => v != null && !isNaN(v));
|
|
|
|
|
|
|
|
|
|
|
|
const minQ = qValues.length > 0 ? Math.min(...qValues) : 0;
|
|
|
|
|
|
const maxQ = qValues.length > 0 ? Math.max(...qValues) : 0;
|
|
|
|
|
|
const minZ = zValues.length > 0 ? Math.min(...zValues) : 0;
|
|
|
|
|
|
const maxZ = zValues.length > 0 ? Math.max(...zValues) : 0;
|
|
|
|
|
|
const minV = vValues.length > 0 ? Math.min(...vValues) : 0;
|
|
|
|
|
|
const maxV = vValues.length > 0 ? Math.max(...vValues) : 0;
|
2026-05-09 17:04:48 +08:00
|
|
|
|
|
2026-06-03 17:10:34 +08:00
|
|
|
|
// ✅ 颜色和Y轴配置
|
|
|
|
|
|
const Color = ['#9556a4', '#56c2e3', '#78c300'];
|
|
|
|
|
|
|
|
|
|
|
|
const _legendData = ['流量(m³/s)', '水位(m)', '流速(m/s)'];
|
2026-05-09 17:04:48 +08:00
|
|
|
|
|
|
|
|
|
|
const setting: EChartsOption = {
|
|
|
|
|
|
// Tooltip 配置
|
|
|
|
|
|
tooltip: {
|
|
|
|
|
|
trigger: 'axis',
|
|
|
|
|
|
backgroundColor: 'rgba(0, 0, 0, 0.5)',
|
|
|
|
|
|
borderColor: 'transparent',
|
|
|
|
|
|
textStyle: {
|
|
|
|
|
|
color: '#ffffff'
|
|
|
|
|
|
},
|
|
|
|
|
|
valueFormatter: (value: any) => (value === undefined ? '-' : value),
|
|
|
|
|
|
formatter: (params: any) => {
|
|
|
|
|
|
let res = `${params[0].name}<br/>`;
|
|
|
|
|
|
params.forEach((item: any) => {
|
|
|
|
|
|
const seriesName = item.seriesName ?? '';
|
|
|
|
|
|
const regx = /\(([^()]+?)\)/;
|
|
|
|
|
|
const unit = seriesName.match(regx);
|
|
|
|
|
|
|
|
|
|
|
|
if (item.value !== undefined && item.value !== null) {
|
2026-06-03 17:10:34 +08:00
|
|
|
|
// ✅ 保留两位小数
|
|
|
|
|
|
const formattedValue = Number(item.value).toFixed(2);
|
|
|
|
|
|
res += `<span style="background: ${item.color}; height:10px; width: 10px; border-radius: 50%; display: inline-block; margin-right:10px;"></span>${item.seriesName} ${formattedValue}${unit?.[1] || ''}<br/>`;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
res += `<span style="background: ${item.color}; height:10px; width: 10px; border-radius: 50%; display: inline-block; margin-right:10px;"></span>${item.seriesName} -${unit?.[1] || ''}<br/>`;
|
2026-05-09 17:04:48 +08:00
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
return res;
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
|
2026-06-03 17:10:34 +08:00
|
|
|
|
// ✅ 颜色配置:流量-紫色,水位-青色,流速-绿色
|
2026-05-09 17:04:48 +08:00
|
|
|
|
color: Color,
|
|
|
|
|
|
|
|
|
|
|
|
// 图例配置
|
|
|
|
|
|
legend: {
|
|
|
|
|
|
data: _legendData,
|
|
|
|
|
|
selectedMode: 'multiple',
|
|
|
|
|
|
top: 0,
|
|
|
|
|
|
left: 'center',
|
|
|
|
|
|
itemGap: 15,
|
|
|
|
|
|
// 默认只选中"水温",其他系列隐藏
|
|
|
|
|
|
selected: {
|
2026-06-03 17:10:34 +08:00
|
|
|
|
'流速(m/s)': false,
|
|
|
|
|
|
'流量(m³/s)': true,
|
2026-05-09 17:04:48 +08:00
|
|
|
|
'水位(m)': false
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
axisPointer: {
|
|
|
|
|
|
type: 'shadow',
|
|
|
|
|
|
label: {
|
|
|
|
|
|
show: false
|
|
|
|
|
|
},
|
|
|
|
|
|
shadowStyle: {
|
|
|
|
|
|
color: 'rgba(84, 112, 198, 0.2)'
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
// 网格配置 - 为三个Y轴预留空间
|
|
|
|
|
|
grid: {
|
|
|
|
|
|
left: '0px',
|
|
|
|
|
|
right: '60px',
|
|
|
|
|
|
bottom: '10%',
|
|
|
|
|
|
top: '65px'
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// 数据缩放配置
|
|
|
|
|
|
dataZoom: {
|
|
|
|
|
|
type: 'inside',
|
|
|
|
|
|
start: 0,
|
|
|
|
|
|
end: 100
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// X轴配置
|
|
|
|
|
|
xAxis: {
|
|
|
|
|
|
type: 'category',
|
|
|
|
|
|
data: echartsData.value.map(item => item.dt),
|
|
|
|
|
|
boundaryGap: true,
|
|
|
|
|
|
axisLabel: {
|
2026-06-03 17:10:34 +08:00
|
|
|
|
show: true,
|
|
|
|
|
|
// ✅ X轴标签显示策略:最多显示5个标签,强制显示首尾
|
|
|
|
|
|
interval: (index: number) => {
|
|
|
|
|
|
const total = echartsData.value.length;
|
|
|
|
|
|
if (total <= 5) return true; // 数据少于等于5个,全部显示
|
|
|
|
|
|
if (index === 0 || index === total - 1) return true; // 首尾必显
|
|
|
|
|
|
|
|
|
|
|
|
// 中间标签均匀分布
|
|
|
|
|
|
const step = Math.floor((total - 1) / 3); // 中间显示3个
|
|
|
|
|
|
return index % step === 0;
|
|
|
|
|
|
}
|
2026-05-09 17:04:48 +08:00
|
|
|
|
},
|
|
|
|
|
|
axisLine: {
|
|
|
|
|
|
show: true
|
|
|
|
|
|
},
|
|
|
|
|
|
splitLine: {
|
|
|
|
|
|
show: true,
|
|
|
|
|
|
lineStyle: {
|
|
|
|
|
|
type: 'solid',
|
|
|
|
|
|
color: '#e0e0e0'
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
axisPointer: {
|
|
|
|
|
|
type: 'shadow',
|
|
|
|
|
|
shadowStyle: {
|
|
|
|
|
|
color: 'rgba(84, 112, 198, 0.2)'
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
|
2026-06-03 17:10:34 +08:00
|
|
|
|
// Y轴配置 - 三个独立Y轴(按期望顺序:流量、水位、流速)
|
2026-05-09 17:04:48 +08:00
|
|
|
|
yAxis: [
|
|
|
|
|
|
{
|
|
|
|
|
|
type: 'value',
|
2026-06-03 17:10:34 +08:00
|
|
|
|
name: '流量(m³/s)',
|
2026-05-09 17:04:48 +08:00
|
|
|
|
position: 'left',
|
|
|
|
|
|
offset: 0,
|
|
|
|
|
|
show: true,
|
2026-06-03 17:10:34 +08:00
|
|
|
|
// ✅ 动态范围:floor(min) ~ ceil(max)+5
|
|
|
|
|
|
min: qValues.length > 0 ? Math.floor(minQ) : undefined,
|
|
|
|
|
|
max: qValues.length > 0 ? Math.ceil(maxQ) : undefined,
|
2026-05-09 17:04:48 +08:00
|
|
|
|
splitLine: {
|
|
|
|
|
|
show: true,
|
|
|
|
|
|
lineStyle: {
|
|
|
|
|
|
type: 'solid',
|
|
|
|
|
|
color: '#e0e0e0'
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
axisTick: {
|
|
|
|
|
|
show: true
|
|
|
|
|
|
},
|
|
|
|
|
|
axisLabel: {
|
|
|
|
|
|
show: true,
|
|
|
|
|
|
formatter: '{value}',
|
2026-06-03 17:10:34 +08:00
|
|
|
|
color: '#9556a4'
|
2026-05-09 17:04:48 +08:00
|
|
|
|
},
|
|
|
|
|
|
axisLine: {
|
|
|
|
|
|
show: true,
|
|
|
|
|
|
lineStyle: {
|
|
|
|
|
|
color: '#6e7079',
|
|
|
|
|
|
width: 2
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
nameTextStyle: {
|
2026-06-03 17:10:34 +08:00
|
|
|
|
color: '#9556a4',
|
2026-05-09 17:04:48 +08:00
|
|
|
|
fontSize: 12
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
type: 'value',
|
2026-06-03 17:10:34 +08:00
|
|
|
|
name: '水位(m)',
|
2026-05-09 17:04:48 +08:00
|
|
|
|
position: 'left',
|
|
|
|
|
|
offset: 60,
|
|
|
|
|
|
show: false,
|
2026-06-03 17:10:34 +08:00
|
|
|
|
// ✅ 动态范围:min-0.01 ~ max+0.04
|
|
|
|
|
|
min: zValues.length > 0 ? minZ : undefined,
|
|
|
|
|
|
max: zValues.length > 0 ? maxZ : undefined,
|
2026-05-09 17:04:48 +08:00
|
|
|
|
splitLine: { show: false },
|
|
|
|
|
|
axisTick: {
|
|
|
|
|
|
show: true
|
|
|
|
|
|
},
|
|
|
|
|
|
axisLabel: {
|
|
|
|
|
|
show: true,
|
2026-06-03 17:10:34 +08:00
|
|
|
|
// ✅ Y轴刻度保留两位小数(使用函数形式)
|
|
|
|
|
|
formatter: (value: any) => value.toFixed(2),
|
|
|
|
|
|
color: '#56c2e3'
|
2026-05-09 17:04:48 +08:00
|
|
|
|
},
|
|
|
|
|
|
axisLine: {
|
|
|
|
|
|
show: true,
|
|
|
|
|
|
lineStyle: {
|
|
|
|
|
|
color: '#6e7079',
|
|
|
|
|
|
width: 2
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
nameTextStyle: {
|
2026-06-03 17:10:34 +08:00
|
|
|
|
color: '#56c2e3',
|
2026-05-09 17:04:48 +08:00
|
|
|
|
fontSize: 12
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
type: 'value',
|
2026-06-03 17:10:34 +08:00
|
|
|
|
name: '流速(m/s)',
|
2026-05-09 17:04:48 +08:00
|
|
|
|
position: 'right',
|
|
|
|
|
|
offset: 0,
|
|
|
|
|
|
show: false,
|
2026-06-03 17:10:34 +08:00
|
|
|
|
// ✅ 动态范围:min-0.2 ~ max+0.5
|
|
|
|
|
|
min: vValues.length > 0 ? minV - 0.02 : undefined,
|
|
|
|
|
|
max: vValues.length > 0 ? maxV + 0.02 : undefined,
|
2026-05-09 17:04:48 +08:00
|
|
|
|
splitLine: { show: false },
|
|
|
|
|
|
axisTick: {
|
|
|
|
|
|
show: true
|
|
|
|
|
|
},
|
|
|
|
|
|
axisLabel: {
|
|
|
|
|
|
show: true,
|
2026-06-03 17:10:34 +08:00
|
|
|
|
// ✅ Y轴刻度保留三位小数
|
|
|
|
|
|
formatter: (value: any) => value.toFixed(3),
|
|
|
|
|
|
color: '#78c300'
|
2026-05-09 17:04:48 +08:00
|
|
|
|
},
|
|
|
|
|
|
axisLine: {
|
|
|
|
|
|
show: true,
|
|
|
|
|
|
lineStyle: {
|
|
|
|
|
|
color: '#6e7079',
|
|
|
|
|
|
width: 2
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
nameTextStyle: {
|
2026-06-03 17:10:34 +08:00
|
|
|
|
color: '#78c300',
|
2026-05-09 17:04:48 +08:00
|
|
|
|
fontSize: 12
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
],
|
|
|
|
|
|
|
2026-06-03 17:10:34 +08:00
|
|
|
|
// 系列配置(按期望顺序:流量、水位、流速)
|
2026-05-09 17:04:48 +08:00
|
|
|
|
series: [
|
2026-06-03 17:10:34 +08:00
|
|
|
|
|
2026-05-09 17:04:48 +08:00
|
|
|
|
{
|
2026-06-03 17:10:34 +08:00
|
|
|
|
name: '流量(m³/s)',
|
|
|
|
|
|
data: echartsData.value.map(item => item.q),
|
2026-05-09 17:04:48 +08:00
|
|
|
|
type: 'line',
|
|
|
|
|
|
smooth: true,
|
2026-06-03 17:10:34 +08:00
|
|
|
|
// connectNulls: true,
|
|
|
|
|
|
symbolSize: 3,
|
2026-05-09 17:04:48 +08:00
|
|
|
|
symbol: 'circle',
|
|
|
|
|
|
itemStyle: {
|
2026-06-03 17:10:34 +08:00
|
|
|
|
color: '#9556a4'
|
2026-05-09 17:04:48 +08:00
|
|
|
|
},
|
|
|
|
|
|
lineStyle: {
|
2026-06-03 17:10:34 +08:00
|
|
|
|
width: 2
|
2026-05-09 17:04:48 +08:00
|
|
|
|
},
|
|
|
|
|
|
yAxisIndex: 0
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
2026-06-03 17:10:34 +08:00
|
|
|
|
name: '水位(m)',
|
|
|
|
|
|
data: echartsData.value.map(item => item.z),
|
2026-05-09 17:04:48 +08:00
|
|
|
|
type: 'line',
|
|
|
|
|
|
smooth: true,
|
2026-06-03 17:10:34 +08:00
|
|
|
|
// connectNulls: true,
|
|
|
|
|
|
symbolSize: 3,
|
2026-05-09 17:04:48 +08:00
|
|
|
|
symbol: 'circle',
|
|
|
|
|
|
itemStyle: {
|
2026-06-03 17:10:34 +08:00
|
|
|
|
color: '#56c2e3'
|
2026-05-09 17:04:48 +08:00
|
|
|
|
},
|
|
|
|
|
|
lineStyle: {
|
2026-06-03 17:10:34 +08:00
|
|
|
|
width: 2
|
2026-05-09 17:04:48 +08:00
|
|
|
|
},
|
|
|
|
|
|
yAxisIndex: 1
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
2026-06-03 17:10:34 +08:00
|
|
|
|
name: '流速(m/s)',
|
|
|
|
|
|
data: echartsData.value.map(item => item.v),
|
2026-05-09 17:04:48 +08:00
|
|
|
|
type: 'line',
|
|
|
|
|
|
smooth: true,
|
2026-06-03 17:10:34 +08:00
|
|
|
|
// connectNulls: true,
|
|
|
|
|
|
symbolSize: 3,
|
2026-05-09 17:04:48 +08:00
|
|
|
|
symbol: 'circle',
|
|
|
|
|
|
itemStyle: {
|
2026-06-03 17:10:34 +08:00
|
|
|
|
color: '#78c300'
|
2026-05-09 17:04:48 +08:00
|
|
|
|
},
|
|
|
|
|
|
lineStyle: {
|
2026-06-03 17:10:34 +08:00
|
|
|
|
width: 2
|
2026-05-09 17:04:48 +08:00
|
|
|
|
},
|
|
|
|
|
|
yAxisIndex: 2
|
2026-06-03 17:10:34 +08:00
|
|
|
|
},
|
2026-05-09 17:04:48 +08:00
|
|
|
|
]
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 保存配置引用
|
|
|
|
|
|
echartOptionRef.value = setting;
|
|
|
|
|
|
|
|
|
|
|
|
// 完全替换模式更新图表
|
|
|
|
|
|
chartInstance.setOption(setting, true);
|
|
|
|
|
|
|
2026-06-03 17:10:34 +08:00
|
|
|
|
// ✅ 初始化时应用动态布局算法,确保只有流量Y轴显示
|
2026-05-09 17:04:48 +08:00
|
|
|
|
yAxisShowDynamic({
|
2026-06-03 17:10:34 +08:00
|
|
|
|
'流量(m³/s)': true,
|
|
|
|
|
|
'水位(m)': false,
|
|
|
|
|
|
'流速(m/s)': false
|
2026-05-09 17:04:48 +08:00
|
|
|
|
}, setting);
|
|
|
|
|
|
|
|
|
|
|
|
// 再次应用调整后的配置
|
|
|
|
|
|
chartInstance.setOption(setting, true);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// ==================== 生命周期钩子 ====================
|
|
|
|
|
|
onMounted(async () => {
|
|
|
|
|
|
// 双重等待机制:nextTick + setTimeout
|
|
|
|
|
|
await nextTick();
|
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
|
initChart();
|
|
|
|
|
|
updateChart();
|
|
|
|
|
|
}, 50);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
onUnmounted(() => {
|
|
|
|
|
|
// 销毁图表实例,防止内存泄漏
|
|
|
|
|
|
if (chartInstance) {
|
|
|
|
|
|
chartInstance.dispose();
|
|
|
|
|
|
chartInstance = null;
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
2026-06-03 17:10:34 +08:00
|
|
|
|
//获取水温监测下拉框
|
|
|
|
|
|
const getselsectData = async () => {
|
|
|
|
|
|
let params = {
|
|
|
|
|
|
"filter": {
|
|
|
|
|
|
"logic": "and",
|
|
|
|
|
|
"filters": [
|
|
|
|
|
|
baseid.value != 'all' ? {
|
|
|
|
|
|
"field": "baseId",
|
|
|
|
|
|
"operator": "eq",
|
|
|
|
|
|
"dataType": "string",
|
|
|
|
|
|
"value": baseid.value
|
|
|
|
|
|
} : null,
|
|
|
|
|
|
{
|
|
|
|
|
|
"field": "fhstcd",
|
|
|
|
|
|
"operator": "isnotnull"
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
"logic": "or",
|
|
|
|
|
|
"filters": [
|
|
|
|
|
|
{
|
|
|
|
|
|
"field": "sttpCode",
|
|
|
|
|
|
"operator": "eq",
|
|
|
|
|
|
"dataType": "string",
|
|
|
|
|
|
"value": "ZQ"
|
|
|
|
|
|
}
|
|
|
|
|
|
]
|
|
|
|
|
|
}
|
|
|
|
|
|
].filter(Boolean)
|
|
|
|
|
|
},
|
|
|
|
|
|
"select": [
|
|
|
|
|
|
"stcd",
|
|
|
|
|
|
"stnm",
|
|
|
|
|
|
"fhstcd",
|
|
|
|
|
|
"fhstnm",
|
|
|
|
|
|
"sttpCode"
|
|
|
|
|
|
]
|
|
|
|
|
|
}
|
|
|
|
|
|
let res = await msstbprptGetKendoList(params)
|
|
|
|
|
|
console.log(res)
|
2026-06-04 11:42:07 +08:00
|
|
|
|
if (!res?.data?.data || res.data.data.length == 0) {
|
2026-06-03 17:10:34 +08:00
|
|
|
|
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
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
// debugger
|
|
|
|
|
|
};
|
|
|
|
|
|
const convertDateRange = (dateRange: string[]) => {
|
|
|
|
|
|
if (!dateRange || dateRange.length !== 2) {
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
// debugger
|
|
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
};
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 加载图表数据
|
|
|
|
|
|
* @param startDate 开始日期 (格式: YYYY-MM-DD HH:mm:ss)
|
|
|
|
|
|
* @param endDate 结束日期 (格式: YYYY-MM-DD HH:mm:ss)
|
|
|
|
|
|
* @param stcd 测站代码
|
|
|
|
|
|
*/
|
|
|
|
|
|
const getChartData = async (startDate: string, endDate: string, stcd: string) => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
console.log('=== 开始加载图表数据 ===');
|
|
|
|
|
|
console.log('请求参数:', { startDate, endDate, stcd });
|
|
|
|
|
|
|
|
|
|
|
|
// 设置 loading 状态
|
|
|
|
|
|
loading.value = true;
|
|
|
|
|
|
hasData.value = false;
|
|
|
|
|
|
|
|
|
|
|
|
const params = {
|
|
|
|
|
|
"filter": {
|
|
|
|
|
|
"logic": "and",
|
|
|
|
|
|
"filters": [
|
|
|
|
|
|
{
|
|
|
|
|
|
"field": "DT",
|
|
|
|
|
|
"operator": "gte",
|
|
|
|
|
|
"value": startDate
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
"field": "DT",
|
|
|
|
|
|
"operator": "lte",
|
|
|
|
|
|
"value": endDate
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
"field": "stcd",
|
|
|
|
|
|
"operator": "eq",
|
|
|
|
|
|
"dataType": "string",
|
|
|
|
|
|
"value": stcd
|
|
|
|
|
|
}
|
|
|
|
|
|
]
|
|
|
|
|
|
},
|
|
|
|
|
|
"sort": [
|
|
|
|
|
|
{
|
|
|
|
|
|
"field": "dt",
|
|
|
|
|
|
"dir": "asc"
|
|
|
|
|
|
}
|
|
|
|
|
|
]
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 调用实际的API获取数据
|
|
|
|
|
|
const res = await sdriverdaysGetKendoList(params);
|
|
|
|
|
|
console.log('API返回结果:', res);
|
|
|
|
|
|
|
|
|
|
|
|
let data = res?.data?.data || [];
|
|
|
|
|
|
console.log('原始数据条数:', data.length);
|
|
|
|
|
|
|
|
|
|
|
|
// 处理空数据情况
|
|
|
|
|
|
if (!data || data.length === 0) {
|
|
|
|
|
|
echartsData.value = [];
|
|
|
|
|
|
hasData.value = false;
|
|
|
|
|
|
await nextTick();
|
|
|
|
|
|
updateChart();
|
|
|
|
|
|
console.warn('未查询到水文数据');
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ✅ 数据预处理:验证字段存在性并转换类型
|
|
|
|
|
|
const processedData = data
|
|
|
|
|
|
.map(item => {
|
|
|
|
|
|
// 验证必要字段
|
|
|
|
|
|
if (!item.dt || typeof item.dt !== 'string') {
|
|
|
|
|
|
console.warn('无效的日期数据:', item);
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
2026-06-03 18:25:57 +08:00
|
|
|
|
|
2026-06-03 17:10:34 +08:00
|
|
|
|
// 确保数值类型
|
|
|
|
|
|
const q = typeof item.q === 'number' ? item.q : parseFloat(item.q);
|
|
|
|
|
|
const z = typeof item.z === 'number' ? item.z : parseFloat(item.z);
|
|
|
|
|
|
const v = typeof item.v === 'number' ? item.v : parseFloat(item.v);
|
2026-06-03 18:25:57 +08:00
|
|
|
|
|
2026-06-03 17:10:34 +08:00
|
|
|
|
if (isNaN(q) || isNaN(z) || isNaN(v)) {
|
|
|
|
|
|
console.warn('无效的水文数据:', item);
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
2026-06-03 18:25:57 +08:00
|
|
|
|
|
2026-06-03 17:10:34 +08:00
|
|
|
|
return {
|
|
|
|
|
|
dt: item.dt.split(' ')[0], // 提取日期部分 "2026-05-03"
|
|
|
|
|
|
v: v, // 确保数值类型
|
|
|
|
|
|
q: q,
|
|
|
|
|
|
z: z
|
|
|
|
|
|
};
|
|
|
|
|
|
})
|
|
|
|
|
|
.filter(Boolean); // 过滤掉无效数据
|
|
|
|
|
|
|
|
|
|
|
|
console.log('处理后数据条数:', processedData.length);
|
|
|
|
|
|
console.log('处理后数据示例:', processedData[0]);
|
|
|
|
|
|
|
|
|
|
|
|
// ✅ 更新响应式数据
|
|
|
|
|
|
echartsData.value = processedData;
|
|
|
|
|
|
|
|
|
|
|
|
// 判断是否有数据
|
|
|
|
|
|
hasData.value = echartsData.value.length > 0;
|
|
|
|
|
|
console.log('hasData 设置为:', hasData.value);
|
|
|
|
|
|
|
|
|
|
|
|
// ✅ 刷新图表(确保DOM更新后执行)
|
|
|
|
|
|
await nextTick();
|
|
|
|
|
|
updateChart();
|
|
|
|
|
|
console.log('图表已更新');
|
|
|
|
|
|
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('获取图表数据失败:', error);
|
|
|
|
|
|
hasData.value = false;
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
// 关闭 loading 状态
|
|
|
|
|
|
await nextTick();
|
|
|
|
|
|
console.log('关闭 loading 状态');
|
|
|
|
|
|
loading.value = false;
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
console.error('日期转换失败');
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
loading.value = false;
|
|
|
|
|
|
hasData.value = false;
|
|
|
|
|
|
// 清空图表数据
|
|
|
|
|
|
echartsData.value = [];
|
|
|
|
|
|
// 更新图表显示空状态
|
|
|
|
|
|
await nextTick();
|
|
|
|
|
|
updateChart();
|
|
|
|
|
|
console.warn('参数不完整:', {
|
|
|
|
|
|
scopeDate: data.scopeDate,
|
|
|
|
|
|
select: data.select
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
watch(
|
|
|
|
|
|
() => JidiSelectEventStore.selectedItem,
|
|
|
|
|
|
(newVal) => {
|
|
|
|
|
|
baseid.value = newVal.wbsCode;
|
|
|
|
|
|
getselsectData()
|
|
|
|
|
|
},
|
|
|
|
|
|
{ deep: true, immediate: true }
|
|
|
|
|
|
);
|
2026-05-09 17:04:48 +08:00
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
<style lang="scss" scoped>
|
2026-06-03 17:10:34 +08:00
|
|
|
|
.chart-wrapper {
|
|
|
|
|
|
width: 100%;
|
2026-05-09 17:04:48 +08:00
|
|
|
|
height: 231px;
|
|
|
|
|
|
min-height: 231px;
|
|
|
|
|
|
position: relative;
|
2026-06-03 17:10:34 +08:00
|
|
|
|
/* 为empty-overlay提供定位上下文 */
|
2026-05-09 17:04:48 +08:00
|
|
|
|
|
|
|
|
|
|
.echarts-chart {
|
2026-06-03 17:10:34 +08:00
|
|
|
|
width: 100% !important;
|
|
|
|
|
|
height: 100% !important;
|
|
|
|
|
|
min-width: 100%;
|
|
|
|
|
|
min-height: 231px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.empty-overlay {
|
|
|
|
|
|
position: absolute;
|
|
|
|
|
|
top: 0;
|
|
|
|
|
|
left: 0;
|
2026-05-09 17:04:48 +08:00
|
|
|
|
width: 100%;
|
2026-06-03 17:10:34 +08:00
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
</style>
|