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

386 lines
12 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!-- SidePanelItem.vue -->
<template>
<div>
<SidePanelItem title="泄放方式分布情况">
<div class="chart-container">
<!-- 自定义图例区域 -->
<div class="legend-container">
<div class="legend-items">
<div
v-for="(item, index) in currentLegendItems"
:key="item.name"
class="legend-item"
:class="{ 'inactive': legendInactiveSet.has(item.name) }"
@click="toggleLegend(item.name)"
>
<span class="legend-color" :style="{ backgroundColor: item.color }"></span>
<span class="legend-text">{{ item.name }}</span>
</div>
</div>
<!-- 分页控制 -->
<div class="legend-pagination" v-if="totalPages > 1">
<span
class="pagination-btn"
:class="{ disabled: currentPage === 1 }"
@click="prevPage"
>
</span>
<span class="pagination-text">{{ currentPage }}/{{ totalPages }}</span>
<span
class="pagination-btn"
:class="{ disabled: currentPage === totalPages }"
@click="nextPage"
>
</span>
</div>
</div>
<!-- ECharts 图表容器 -->
<div ref="chartRef" class="chart"></div>
</div>
</SidePanelItem>
</div>
</template>
<script lang="ts" setup>
import { ref, onMounted, computed, watch, onBeforeUnmount } from 'vue';
import SidePanelItem from '@/components/SidePanelItem/index.vue';
import * as echarts from 'echarts';
import type { EChartsOption } from 'echarts';
// 定义组件名
defineOptions({
name: 'xieFangFenBu'
});
// 图表实例引用
const chartRef = ref<HTMLElement | null>(null);
let chartInstance: echarts.ECharts | null = null;
// 图例配置
const ITEMS_PER_PAGE = 4; // 每页显示 4 个图例
const currentPage = ref(1); // 当前页码
// 完整的图例数据8 个图例)
const allLegendItems = ref([
{ name: '生态放流孔', color: '#7cb342' },
{ name: '生态放流闸', color: '#ce93d8' },
{ name: '生态放流洞', color: '#64b5f6' },
{ name: '生态放流管', color: '#ffd54f' },
{ name: '生态放流口', color: '#ff8a65' },
{ name: '生态放流道', color: '#4db6ac' },
{ name: '生态放流渠', color: '#ba68c8' },
{ name: '生态放流站', color: '#a1887f' }
]);
// 计算总页数
const totalPages = computed(() => {
return Math.ceil(allLegendItems.value.length / ITEMS_PER_PAGE);
});
// 当前页的图例项
const currentLegendItems = computed(() => {
const start = (currentPage.value - 1) * ITEMS_PER_PAGE;
const end = start + ITEMS_PER_PAGE;
return allLegendItems.value.slice(start, end);
});
// 图例 inactive 状态集合
const legendInactiveSet = ref<Set<string>>(new Set());
// 切换图例显示/隐藏
const toggleLegend = (name: string) => {
if (legendInactiveSet.value.has(name)) {
legendInactiveSet.value.delete(name);
} else {
legendInactiveSet.value.add(name);
}
updateChart();
};
// 上一页
const prevPage = () => {
if (currentPage.value > 1) {
currentPage.value--;
}
};
// 下一页
const nextPage = () => {
if (currentPage.value < totalPages.value) {
currentPage.value++;
}
};
// 示例数据 - 可以替换为实际 API 数据
const xData = ref([
'金沙江干流',
'雅砻江干流',
'大渡河干流',
'乌江干流',
'湘西',
'黄河上游干流',
'东北',
'闽浙赣',
'其他'
]);
// 系列数据与图例顺序对应8 个系列)
const seriesData = ref([
{ name: '生态放流孔', data: [2, 1, 3, 1, 2, 1, 0, 1, 5] },
{ name: '生态放流闸', data: [3, 2, 4, 2, 3, 1, 1, 2, 8] },
{ name: '生态放流洞', data: [5, 2, 6, 3, 4, 2, 1, 2, 9] },
{ name: '生态放流管', data: [15, 0, 12, 18, 4, 0, 0, 0, 21] },
{ name: '生态放流口', data: [0, 0, 0, 0, 0, 0, 0, 0, 0] },
{ name: '生态放流道', data: [0, 0, 0, 0, 0, 0, 0, 0, 0] },
{ name: '生态放流渠', data: [0, 0, 0, 0, 0, 0, 0, 0, 0] },
{ name: '生态放流站', data: [0, 0, 0, 0, 0, 0, 0, 0, 0] }
]);
// 计算每个柱子的总和(只计算显示的系列)
const calculateTotal = (index: number) => {
return seriesData.value
.filter(series => !legendInactiveSet.value.has(series.name)) // 只计算未隐藏的系列
.reduce((sum, series) => {
return sum + (series.data[index] || 0);
}, 0);
};
// 更新图表
const updateChart = () => {
if (!chartInstance) return;
const option: EChartsOption = {
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'shadow'
},
formatter: (params: any) => {
let result = `${params[0].name}<br/>`;
params.forEach((param: any) => {
// 只显示 active 的图例
if (!legendInactiveSet.value.has(param.seriesName)) {
result += `${param.marker} ${param.seriesName}: ${param.value}<br/>`;
}
});
return result;
}
},
grid: {
left: '3%',
right: '4%',
bottom: '15%',
top: '10%',
containLabel: true
},
xAxis: {
type: 'category',
data: xData.value,
axisLabel: {
rotate: 45, // X 轴标签倾斜 45 度
interval: 0 // 显示所有标签
},
axisTick: {
alignWithLabel: true
}
},
yAxis: {
type: 'value',
min: 0,
// Y 轴刻度根据数据动态计算
axisLabel: {
formatter: '{value}'
}
},
series: seriesData.value.map((series, index) => {
// 从图例数据中获取颜色
const legendItem = allLegendItems.value.find(item => item.name === series.name);
const color = legendItem?.color;
// 判断当前系列是否被隐藏
const isHidden = legendInactiveSet.value.has(series.name);
return {
name: series.name,
type: 'bar',
stack: 'total',
barWidth: '60%',
// 如果系列被隐藏,将数据设置为 null否则使用原始数据
data: isHidden ? series.data.map(() => null) : series.data,
// 设置系列颜色
itemStyle: {
color: color
},
// 柱顶数值标签 - 只在最后一个系列显示总和
label: {
show: index === seriesData.value.length - 1, // 只在最后一个系列显示
position: 'top',
fontSize: 12,
color: '#333',
formatter: (params: any) => {
// 计算总和
const total = calculateTotal(params.dataIndex);
return total > 0 ? total.toString() : '';
}
},
// 鼠标悬停效果
emphasis: {
itemStyle: {
shadowBlur: 0,
shadowColor: 'rgba(0, 0, 0, 0)'
}
}
};
})
};
chartInstance.setOption(option, true);
};
// 初始化图表
const initChart = () => {
if (!chartRef.value) return;
// 延迟初始化确保容器已渲染
setTimeout(() => {
chartInstance = echarts.init(chartRef.value);
updateChart();
// 强制重绘确保尺寸正确
setTimeout(() => {
chartInstance?.resize();
}, 100);
}, 50);
};
// 监听窗口大小变化
const handleResize = () => {
chartInstance?.resize();
};
// 监听图例分页变化
watch([currentPage, legendInactiveSet], () => {
updateChart();
}, { deep: true });
// 页面加载时执行
onMounted(() => {
initChart();
window.addEventListener('resize', handleResize);
});
// 页面卸载时清理
onBeforeUnmount(() => {
window.removeEventListener('resize', handleResize);
chartInstance?.dispose();
chartInstance = null;
});
// 暴露刷新数据的方法
const refreshData = (newXData: string[], newSeriesData: any[]) => {
xData.value = newXData;
seriesData.value = newSeriesData;
updateChart();
};
// 暴露设置图例数据的方法
const setLegendData = (legendData: Array<{ name: string; color: string }>) => {
allLegendItems.value = legendData;
currentPage.value = 1; // 重置为第一页
legendInactiveSet.value.clear();
};
// 暴露给父组件使用
defineExpose({
refreshData,
setLegendData
});
</script>
<style lang="scss" scoped>
.chart-container {
width: 100%;
.legend-container {
display: flex;
justify-content: space-between;
align-items: center;
.legend-items {
display: flex;
gap: 10px; // 减小间距
flex-wrap: nowrap; // 不换行
flex: 1; // 占据剩余空间
min-width: 0; // 允许收缩
.legend-item {
display: flex;
align-items: center;
cursor: pointer;
transition: opacity 0.3s;
flex-shrink: 0; // 不允许收缩
&:hover {
opacity: 0.8;
}
&.inactive {
opacity: 0.5;
}
.legend-color {
width: 12px;
height: 12px;
border-radius: 2px;
margin-right: 4px;
flex-shrink: 0;
}
.legend-text {
font-size: 12px; // 稍微减小字体
color: #333;
white-space: nowrap;
}
}
}
.legend-pagination {
display: flex;
align-items: center;
flex-shrink: 0;
margin-left: 5px; // 增加左边距
.pagination-btn {
cursor: pointer;
font-size: 14px;
color: #666;
user-select: none;
transition: color 0.3s;
&:hover:not(.disabled) {
color: #409eff;
}
&.disabled {
cursor: not-allowed;
color: #ccc;
}
}
.pagination-text {
font-size: 13px;
color: #666;
min-width: 30px;
text-align: center;
}
}
}
.chart {
width: 100%;
height: 350px;
}
}
</style>