WholeProcessPlatform/frontend/src/modules/shengTaiLiuLiangXieFangSheShiMod/xieFangFenBu/index.vue

482 lines
11 KiB
Vue
Raw Normal View History

<template>
2026-06-16 17:39:51 +08:00
<SidePanelItem title="泄放方式分布情况">
<div ref="chartRef" class="bar-chart-container"></div>
<!-- 空状态提示 -->
<a-empty v-if="showEmpty" description="暂无数据" />
</SidePanelItem>
<!-- 详情弹框 -->
<a-modal
v-model:open="modalVisible"
title="泄放方式分布情况"
width="1536px"
:footer="null"
@cancel="handleModalCancel"
>
<FBQKTwolayer
v-if="modalVisible"
:selectData="selectData"
:jidiName="jidiName"
:jiDiList="JidiSelectEventStore.jidiData"
></FBQKTwolayer>
</a-modal>
</template>
<script lang="ts" setup>
2026-06-16 17:39:51 +08:00
import {
ref,
onMounted,
onBeforeUnmount,
watch,
computed,
nextTick
} from 'vue';
import SidePanelItem from '@/components/SidePanelItem/index.vue';
import * as echarts from 'echarts';
2026-06-16 17:39:51 +08:00
import type { ECharts } from 'echarts';
import { vmsstbprptGetKendoList } from '@/api/stll';
import { useJidiSelectEventStore } from '@/store/modules/jidiSelectEvent';
import FBQKTwolayer from './TwoLayer/FenBuQingKuangTwoLayer.vue';
import { useDraggable } from '@/utils/drag';
// 定义组件名
defineOptions({
2026-06-16 17:39:51 +08:00
name: 'XieFangFenBu'
});
2026-06-16 17:39:51 +08:00
const JidiSelectEventStore = useJidiSelectEventStore();
// DOM 引用
const chartRef = ref<HTMLElement | null>(null);
2026-06-16 17:39:51 +08:00
let chartInstance: ECharts | null = null;
// 响应式数据
const loading = ref(false);
const data = ref<any[]>([]);
const echartsXData = ref<string[]>([]);
const wbsCode = ref('');
// 弹框状态
const modalVisible = ref(false);
const selectData = ref(''); // 选中的设施类型名称
const jidiName = ref(''); // 选中的基地名称
useDraggable(modalVisible, { boundary: true, resetOnOpen: true });
2026-06-16 17:39:51 +08:00
// 设施类型配置
const typeName = [
{ label: '生态放流孔', eqtp: 'EQ_1' },
{ label: '生态放流闸', eqtp: 'EQ_2' },
{ label: '生态放流洞', eqtp: 'EQ_3' },
{ label: '生态放流管', eqtp: 'EQ_4' },
{ label: '基荷发电', eqtp: 'EQ_5' },
{ label: '生态机组', eqtp: 'EQ_6' },
{ label: '泄洪设施', eqtp: 'EQ_7' }
];
// 默认配色方案(当 getUnitConfigByCode 不可用时使用)
const defaultColors = [
'#9556a4', // EQ_1 - 紫色
'#30b7b9', // EQ_2 - 青绿色
'#4b79ab', // EQ_3 - 蓝色
'#dbb629', // EQ_4 - 黄色
'#df91ab', // EQ_5 - 粉色
'#78c300', // EQ_6 - 浅绿色
'#7399c6' // EQ_7 - 浅蓝色
];
2026-06-16 17:39:51 +08:00
// 计算是否显示空状态
const showEmpty = computed(() => {
return !loading.value && (!data.value || data.value.length === 0);
});
2026-06-16 17:39:51 +08:00
/**
* 手写 omit 函数替代 lodash.omit
*/
const omit = (obj: any, keys: string[]) => {
const result = { ...obj };
keys.forEach(key => {
delete result[key];
});
return result;
};
2026-06-16 17:39:51 +08:00
/**
* 获取颜色配置模拟 getUnitConfigByCode
* TODO: 如果项目中有等效实现请替换此处逻辑
*/
const getColorByType = (type: string): string => {
// 这里可以替换为实际的 getUnitConfigByCode 调用
// const config = getUnitConfigByCode('Other', type);
// return config?.lineColor || config?.pieColor;
// 暂时使用默认配色
const index = typeName.findIndex(item => item.eqtp === type);
return defaultColors[index] || '#999999';
};
2026-06-16 17:39:51 +08:00
/**
* 初始化图表
*/
const initChart = () => {
if (!chartRef.value) return;
// 确保容器有正确的高度
if (chartRef.value.clientHeight === 0) {
setTimeout(() => {
initChart();
}, 100);
return;
}
if (!chartInstance) {
chartInstance = echarts.init(chartRef.value);
}
const option = buildChartOption();
chartInstance.setOption(option, true);
// 绑定点击事件
chartInstance.off('click');
chartInstance.on('click', handleChartClick);
};
2026-06-16 17:39:51 +08:00
/**
* 构建图表配置
*/
const buildChartOption = () => {
// 计算每个基地的总数
const countSums: Record<string, number> = {};
data.value.forEach(item => {
const baseid = item.baseid;
const count = Number(item.count) || 0;
if (countSums[baseid]) {
countSums[baseid] += count;
} else {
countSums[baseid] = count;
}
2026-06-16 17:39:51 +08:00
});
2026-06-16 17:39:51 +08:00
const values = Object.values(countSums);
const maxValue = values.length > 0 ? Math.max(...values) : 0;
// 计算总计数据
const totalData: number[] = [];
const uniqueBaseNames = [...new Set(echartsXData.value)];
uniqueBaseNames.forEach((basename, index) => {
let total = 0;
data.value.forEach(item => {
if (item.basename === basename) {
total += Number(item.count) || 0;
}
});
totalData[index] = total;
});
// 构建 series 数据
const seriesData = typeName.map(type => {
return uniqueBaseNames.map(basename => {
const found = data.value.find(
item => item.eqtp === type.eqtp && item.basename === basename
);
return found ? Number(found.count) || 0 : 0;
});
});
// 获取颜色数组
const colors = typeName.map(type => getColorByType(type.eqtp));
2026-06-16 17:39:51 +08:00
const option: any = {
tooltip: {
trigger: 'axis',
confine: true,
formatter: (params: any) => {
const content = params
.map((item: any) => {
let value = item.value || 0;
if (item.seriesName === '总计') {
value = totalData[item.dataIndex];
}
2026-06-16 17:39:51 +08:00
return `<div>${item.marker}${item.seriesName}: &nbsp;&nbsp;&nbsp;&nbsp;<span style="float:right">${value} 个</span></div>`;
})
.join('');
return `<h3>${params[0].name}</h3>${content}`;
},
axisPointer: {
type: 'line'
}
},
grid: {
left: '8%',
right: '4%',
bottom: '20%',
top: '20%'
},
legend: {
type: 'scroll',
icon: 'rect',
orient: 'horizontal',
itemWidth: 12,
itemHeight: 12,
textStyle: {
fontSize: 12,
color: '#6A93B9',
height: 8
},
data: typeName.map(item => item.label)
},
xAxis: {
show: true,
type: 'category',
axisLine: { show: true },
axisLabel: {
fontSize: 12,
color: '#686868',
rotate: 37
},
axisTick: {
alignWithLabel: true
},
data: uniqueBaseNames
},
yAxis: {
max: maxValue,
type: 'value',
barWidth: 10,
name: '数量(个)',
nameTextStyle: {
fontSize: 12,
color: '#848484',
align: 'center'
},
axisTick: { show: false },
axisLabel: {
fontSize: 12,
color: '#343434',
fontFamily: 'Bebas'
}
},
series: [
...typeName.map((type, index) => ({
name: type.label,
type: 'bar' as const,
stack: 'total',
barCategoryGap: '50%',
data: seriesData[index]
})),
{
name: '总计',
type: 'bar' as const,
stack: 'total',
label: {
show: true,
position: 'top',
formatter: (params: any) => {
return String(totalData[params.dataIndex]);
}
},
2026-06-16 17:39:51 +08:00
itemStyle: {
color: 'transparent'
},
2026-06-16 17:39:51 +08:00
data: Array(totalData.length).fill(0)
}
],
color: colors
};
return option;
};
/**
* 图表点击事件
*/
const handleChartClick = (params: any) => {
// 根据系列名称找到对应的 sttpCode (eqtp)
const typeConfig = typeName.find(item => item.label === params.seriesName);
selectData.value = typeConfig ? typeConfig.eqtp : '';
// 将基地名称转换为 wbsCode
const jidiInfo = JidiSelectEventStore.jidiData.find(
(item: any) => item.wbsName === params.name
);
jidiName.value = jidiInfo ? jidiInfo.wbsCode : params.name;
modalVisible.value = true;
};
/**
* 弹框取消事件
*/
const handleModalCancel = () => {
modalVisible.value = false;
selectData.value = '';
jidiName.value = '';
};
/**
* 获取数据
*/
const fetchData = async () => {
if (!wbsCode.value) return;
loading.value = true;
try {
// 构建请求参数
let params: any = {
filter: {
logic: 'and',
filters: [
wbsCode.value !== 'all'
? {
field: 'baseId',
operator: 'eq',
dataType: 'string',
value: wbsCode.value
}
: null,
{
field: 'sttpFullPath',
operator: 'contains',
dataType: 'string',
value: 'ENV,ENVP,EQ,'
}
].filter(Boolean)
},
group: [
{
dir: 'asc',
field: 'baseStepSort'
},
2026-06-16 17:39:51 +08:00
{
dir: 'asc',
field: 'baseId'
},
2026-06-16 17:39:51 +08:00
{
dir: 'desc',
field: 'sttpCode'
}
],
groupResultFlat: 'true'
};
2026-06-16 17:39:51 +08:00
// 如果是全部基地,移除 baseId 过滤条件
if (wbsCode.value === 'all') {
params = omit(params, ['filter']);
params.filter = {
logic: 'and',
filters: [
{
field: 'sttpFullPath',
operator: 'contains',
dataType: 'string',
value: 'ENV,ENVP,EQ,'
}
]
};
}
2026-06-16 17:39:51 +08:00
const res = await vmsstbprptGetKendoList(params);
if (res.data && res.data.data) {
const apiData = res.data.data;
// 处理数据
const list: any[] = [];
apiData.forEach((it: any) => {
list.push({
key: it.baseId,
count: it.count_sttpCode,
eqtp: it.sttpCode
});
});
// 映射基地名称:使用 JidiSelectEventStore.jidiData
const processedData = list.map((item: any) => {
// 从 jidiData 中查找匹配的基地名称
const jidiInfo = JidiSelectEventStore.jidiData.find(
(jidi: any) => jidi.wbsCode === item.key
);
return {
baseid: item.key,
basename: jidiInfo ? jidiInfo.wbsName : item.key, // 如果找到则使用 wbsName否则使用 baseId
count: item.count,
eqtp: item.eqtp
};
});
data.value = processedData;
2026-06-18 09:29:15 +08:00
//
2026-06-16 17:39:51 +08:00
// 提取唯一的基地名称作为 X 轴数据
echartsXData.value = [
...new Set(processedData.map(item => item.basename))
];
// 数据更新后重新渲染图表
await nextTick();
initChart();
} else {
data.value = [];
echartsXData.value = [];
}
} catch (error) {
console.error('获取泄放方式分布数据失败:', error);
data.value = [];
echartsXData.value = [];
} finally {
loading.value = false;
}
};
2026-06-16 17:39:51 +08:00
/**
* 处理窗口大小变化
*/
const handleResize = () => {
2026-06-16 17:39:51 +08:00
chartInstance?.resize();
};
2026-06-16 17:39:51 +08:00
// 生命周期钩子
onMounted(() => {
2026-06-16 17:39:51 +08:00
// 延迟初始化图表,确保 DOM 已渲染
setTimeout(() => {
initChart();
2026-06-16 17:39:51 +08:00
setTimeout(() => {
chartInstance?.resize();
}, 200);
}, 50);
window.addEventListener('resize', handleResize);
// 组件挂载时获取数据
if (wbsCode.value) {
fetchData();
}
});
onBeforeUnmount(() => {
2026-06-16 17:39:51 +08:00
window.removeEventListener('resize', handleResize);
chartInstance?.dispose();
chartInstance = null;
});
2026-06-16 17:39:51 +08:00
// 监听基地选择变化
watch(
() => JidiSelectEventStore.selectedItem,
newVal => {
console.log('流域切换:', newVal);
if (newVal?.wbsCode) {
wbsCode.value = newVal.wbsCode;
fetchData();
}
},
{ deep: true, immediate: true }
);
</script>
<style lang="scss" scoped>
2026-06-16 17:39:51 +08:00
.bar-chart-container {
width: 100%;
height: 350px;
}
2026-06-16 17:39:51 +08:00
</style>