WholeProcessPlatform/frontend/src/modules/qixidiliuliangbianhua/index.vue

503 lines
14 KiB
Vue
Raw Normal View History

2026-05-09 17:04:48 +08:00
<!-- SidePanelItem.vue -->
<template>
2026-05-12 08:47:27 +08:00
<SidePanelItem title="水文监测" :select="select" :scopeDate="datetimePicker" >
2026-05-09 17:04:48 +08:00
<div class="chart-container">
<div ref="chartRef" class="echarts-chart"></div>
</div>
</SidePanelItem>
</template>
<script lang="ts" setup>
import { ref, onMounted, onUnmounted, nextTick } from 'vue';
import * as echarts from 'echarts';
import type { EChartsOption } from 'echarts';
import SidePanelItem from '@/components/SidePanelItem/index.vue';
// 定义组件名
defineOptions({
name: 'qixidiliuliangbianhua'
});
// ==================== 选择器配置 ====================
const select = ref({
show: true,
value: undefined,
options: [],
picker: undefined,
format: undefined
});
2026-05-12 08:47:27 +08:00
// 日期选择器配置
const datetimePicker = ref({
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;
// 模拟图表数据(水温监测 - 12个月
const echartsData = ref<any[]>([
{ dt: '2024-01', wt: 8.5, q: 120.5, z: 45.2 },
{ dt: '2024-02', wt: 9.2, q: 135.8, z: 46.1 },
{ dt: '2024-03', wt: 12.8, q: 180.3, z: 47.5 },
{ dt: '2024-04', wt: 16.3, q: 220.6, z: 48.8 },
{ dt: '2024-05', wt: 20.5, q: 280.2, z: 50.3 },
{ dt: '2024-06', wt: 24.8, q: 350.5, z: 52.1 },
{ dt: '2024-07', wt: 28.3, q: 420.8, z: 53.6 },
{ dt: '2024-08', wt: 27.5, q: 390.3, z: 52.9 },
{ dt: '2024-09', wt: 23.2, q: 310.7, z: 51.2 },
{ dt: '2024-10', wt: 18.6, q: 240.5, z: 49.5 },
{ dt: '2024-11', wt: 13.4, q: 165.2, z: 47.8 },
{ dt: '2024-12', wt: 9.8, q: 130.6, z: 46.3 }
]);
// 图表配置引用(用于 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');
options.grid.left = '80px';
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');
options.grid.left = '80px';
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;
// 深拷贝当前配置
const options = JSON.parse(JSON.stringify(echartOptionRef.value));
// 同步图例选中状态
options.legend.selected = selected;
// 根据图例状态更新 Y 轴显隐
const newYAxis = options.yAxis.map((item: any) => {
let isShow = true;
for (const key in selected) {
if (key === item.name) {
isShow = selected[key];
}
}
return { ...item, show: isShow };
});
options.yAxis = newYAxis;
// 触发布局调整
yAxisShowDynamic(selected, options);
// 完全替换模式更新配置
chartInstance?.setOption(options, true);
echartOptionRef.value = options;
});
// 监听数据点点击事件
chartInstance.on('click', (params: any) => {
console.log('点击数据点:', {
date: params.name,
seriesName: params.seriesName,
value: params.value
});
// TODO: 可扩展为打开详情弹窗
});
};
// ==================== 图表配置生成 ====================
const updateChart = () => {
if (!chartInstance) return;
// 单位和颜色配置(可根据实际需求从配置系统获取)
const Color = ['#5470c6', '#91cc75', '#fac858'];
const _legendData = ['水温(℃)', '流量(m³/s)', '水位(m)'];
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);
const finalValue = item.value !== undefined && item.value !== null ? item.value : '-';
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;
}
},
// 颜色配置
color: Color,
// 图例配置
legend: {
data: _legendData,
selectedMode: 'multiple',
top: 0,
left: 'center',
itemGap: 15,
// 默认只选中"水温",其他系列隐藏
selected: {
'水温(℃)': true,
'流量(m³/s)': false,
'水位(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: {
show: true
},
axisLine: {
show: true
},
splitLine: {
show: true,
lineStyle: {
type: 'solid',
color: '#e0e0e0'
}
},
axisPointer: {
type: 'shadow',
shadowStyle: {
color: 'rgba(84, 112, 198, 0.2)'
}
}
},
// Y轴配置 - 三个独立Y轴
yAxis: [
{
type: 'value',
name: '水温(℃)',
position: 'left',
offset: 0,
show: true,
splitLine: {
show: true,
lineStyle: {
type: 'solid',
color: '#e0e0e0'
}
},
axisTick: {
show: true
},
axisLabel: {
show: true,
formatter: '{value}',
color: '#5470c6'
},
axisLine: {
show: true,
lineStyle: {
color: '#6e7079',
width: 2
}
},
nameTextStyle: {
color: '#5470c6',
fontSize: 12
}
},
{
type: 'value',
name: '流量(m³/s)',
position: 'left',
offset: 60,
show: false,
splitLine: { show: false },
axisTick: {
show: true
},
axisLabel: {
show: true,
formatter: '{value}',
color: '#91cc75'
},
axisLine: {
show: true,
lineStyle: {
color: '#6e7079',
width: 2
}
},
nameTextStyle: {
color: '#91cc75',
fontSize: 12
}
},
{
type: 'value',
name: '水位(m)',
position: 'right',
offset: 0,
show: false,
splitLine: { show: false },
axisTick: {
show: true
},
axisLabel: {
show: true,
formatter: '{value}',
color: '#fac858'
},
axisLine: {
show: true,
lineStyle: {
color: '#6e7079',
width: 2
}
},
nameTextStyle: {
color: '#fac858',
fontSize: 12
}
}
],
// 系列配置
series: [
{
name: '水温(℃)',
data: echartsData.value.map(item => item.wt),
type: 'line',
smooth: true,
connectNulls: true,
symbolSize: 4,
symbol: 'circle',
itemStyle: {
color: '#5470c6'
},
lineStyle: {
width: 2.5
},
yAxisIndex: 0
},
{
name: '流量(m³/s)',
data: echartsData.value.map(item => item.q),
type: 'line',
smooth: true,
connectNulls: true,
symbolSize: 4,
symbol: 'circle',
itemStyle: {
color: '#91cc75'
},
lineStyle: {
width: 2.5
},
yAxisIndex: 1
},
{
name: '水位(m)',
data: echartsData.value.map(item => item.z),
type: 'line',
smooth: true,
connectNulls: true,
symbolSize: 4,
symbol: 'circle',
itemStyle: {
color: '#fac858'
},
lineStyle: {
width: 2.5
},
yAxisIndex: 2
}
]
};
// 保存配置引用
echartOptionRef.value = setting;
// 完全替换模式更新图表
chartInstance.setOption(setting, true);
// ✅ 初始化时应用动态布局算法确保只有水温Y轴显示
yAxisShowDynamic({
'水温(℃)': true,
'流量(m³/s)': false,
'水位(m)': false
}, setting);
// 再次应用调整后的配置
chartInstance.setOption(setting, true);
};
// ==================== 生命周期钩子 ====================
onMounted(async () => {
// 双重等待机制nextTick + setTimeout
await nextTick();
setTimeout(() => {
initChart();
updateChart();
}, 50);
});
onUnmounted(() => {
// 销毁图表实例,防止内存泄漏
if (chartInstance) {
chartInstance.dispose();
chartInstance = null;
}
});
</script>
<style lang="scss" scoped>
.chart-container {
height: 231px;
min-height: 231px;
width: 100%;
position: relative;
.echarts-chart {
height: 100%;
width: 100%;
}
}
</style>