WholeProcessPlatform/frontend/src/modules/fishSurvey/FishChangeTu/index.vue

339 lines
7.9 KiB
Vue
Raw Normal View History

2026-06-18 09:29:15 +08:00
<template>
<SidePanelItem
title="鱼类变化情况"
:datetimePicker="datetimePicker"
@update-values="handlePanelChange"
>
<a-spin :spinning="loading">
<div v-show="!sourceData.length && !loading" class="empty-wrapper">
<a-empty description="暂无数据" />
</div>
<div v-show="sourceData.length" class="chart-wrapper">
<div ref="chartContainer" class="chart-container"></div>
</div>
</a-spin>
</SidePanelItem>
</template>
<script lang="ts" setup>
import { ref, computed, watch, onMounted, onUnmounted, nextTick } from 'vue';
import * as echarts from 'echarts';
import type { EChartsOption } from 'echarts';
import SidePanelItem from '@/components/SidePanelItem/index.vue';
import { sdFprdRGetFtpChangeInfo } from '@/api/yldc';
import { useJidiSelectEventStore } from '@/store/modules/jidiSelectEvent';
defineOptions({
name: 'FishChangeTu'
});
const JidiSelectEventStore = useJidiSelectEventStore();
// const BASE_ID = '07';
// 日期选择器配置
const datetimePicker = ref({
show: true,
value: `${new Date().getFullYear()}`,
format: 'YYYY',
picker: 'year' as const,
options: []
});
// 数据状态
const sourceData = ref<
Array<{
year: number;
month: number;
ftpCount: number;
fishCount: number;
}>
>([]);
const loading = ref(false);
// 图表实例
const chartContainer = ref<HTMLDivElement | null>(null);
let chartInstance: echarts.ECharts | null = null;
let resizeObserver: ResizeObserver | null = null;
// 生成从1月到当前月份的月份列表
function getMonthsUntilNow(year: number) {
const now = new Date();
const currentYear = now.getFullYear();
const currentMonth = now.getMonth() + 1;
const months: Array<{ name: string; value: number }> = [];
const maxMonth = year >= currentYear ? currentMonth : 12;
for (let i = 1; i <= maxMonth; i++) {
months.push({
name: i + '月',
value: i
});
}
return months;
}
// 处理图表数据
const fishData = computed(() => {
const dates = getMonthsUntilNow(parseInt(datetimePicker.value.value));
if (sourceData.value.length > 0) {
const speciesCount = dates.map(item => {
const info = sourceData.value.find(v => v.month === item.value);
return info ? Number(info.ftpCount) : 0;
});
const fishCount = dates.map(item => {
const info = sourceData.value.find(v => v.month === item.value);
return info ? Number(info.fishCount) : 0;
});
return {
dates: dates.map(item => item.name),
speciesCount,
fishCount
};
}
return {
dates: dates.map(item => item.name),
speciesCount: [],
fishCount: []
};
});
// 图表配置
const option = computed<EChartsOption>(() => {
const data = fishData.value;
const baseFontSize = 12;
// 动态计算Y轴范围
const speciesMax = Math.max(...data.speciesCount, 1);
const fishMax = Math.max(...data.fishCount, 1);
return {
yAxisSplit: 5,
tooltip: {
trigger: 'axis',
axisPointer: { type: 'cross' },
backgroundColor: 'rgba(50,50,50,0.9)',
textStyle: { fontSize: baseFontSize, color: '#fff' }
},
legend: {
data: ['种类', '总数'],
top: 0,
textStyle: { fontSize: baseFontSize }
},
grid: {
left: '50px',
right: '60px',
top: '45px',
bottom: '40px'
},
xAxis: {
type: 'category',
data: data.dates,
axisLine: { lineStyle: { color: '#ccc' } },
splitLine: { show: true },
axisLabel: {
color: '#666',
fontSize: baseFontSize,
rotate: 0,
interval: 0
}
},
yAxis: [
{
type: 'value',
name: '种类',
nameTextStyle: { fontSize: baseFontSize },
min: 0,
max: Math.ceil(speciesMax * 1.2),
axisLine: { lineStyle: { color: '#165DFF' } },
axisLabel: {
color: '#666',
fontSize: baseFontSize,
formatter: (value: number) => Math.floor(value).toString()
},
splitLine: { lineStyle: { color: '#eee' } }
},
{
type: 'value',
name: '总数',
nameTextStyle: { fontSize: baseFontSize },
min: 0,
max: Math.ceil(fishMax * 1.2),
axisLine: { lineStyle: { color: '#36CFC9' } },
axisLabel: {
color: '#666',
fontSize: baseFontSize,
formatter: (value: number) => Math.floor(value).toString()
}
}
],
series: [
{
name: '种类',
type: 'line',
data: data.speciesCount,
yAxisIndex: 0,
color: '#165DFF',
smooth: true,
symbol: 'circle',
symbolSize: 8,
label: {
show: true,
position: 'top',
fontSize: baseFontSize - 2
}
},
{
name: '总数',
type: 'line',
data: data.fishCount,
yAxisIndex: 1,
color: '#36CFC9',
smooth: true,
symbol: 'circle',
symbolSize: 8,
label: {
show: true,
position: 'top',
fontSize: baseFontSize - 2
}
}
]
};
});
// 获取数据
const getData = async () => {
try {
loading.value = true;
const year = datetimePicker.value.value;
const res = await sdFprdRGetFtpChangeInfo({
baseId: baseid.value,
year
});
if (res && (res as any).data) {
sourceData.value = (res as any).data;
} else {
sourceData.value = [];
}
} catch (error) {
console.error('获取鱼类变化数据失败:', error);
sourceData.value = [];
} finally {
loading.value = false;
}
};
// 初始化图表
const initChart = () => {
if (!chartContainer.value) return;
if (chartInstance) {
chartInstance.dispose();
}
chartInstance = echarts.init(chartContainer.value);
chartInstance.setOption(option.value, true);
};
// 更新图表
const updateChart = () => {
if (chartInstance) {
chartInstance.setOption(option.value, true);
chartInstance.resize();
}
};
// 处理面板变化
const handlePanelChange = (data: any) => {
if (data.datetime) {
datetimePicker.value.value = data.datetime;
getData();
}
};
// 窗口大小变化时重新调整图表
const handleResize = () => {
if (chartInstance) {
chartInstance.resize();
}
};
let baseid = ref('');
watch(
() => JidiSelectEventStore.selectedItem,
newVal => {
if (newVal && newVal.wbsCode) {
baseid.value = newVal.wbsCode;
}
},
{ deep: true, immediate: true }
);
onMounted(() => {
getData();
window.addEventListener('resize', handleResize);
});
onUnmounted(() => {
if (chartInstance) {
chartInstance.dispose();
chartInstance = null;
}
if (resizeObserver) {
resizeObserver.disconnect();
}
window.removeEventListener('resize', handleResize);
});
// 监听数据变化,数据到达后初始化图表
watch(sourceData, newData => {
if (newData && newData.length > 0) {
// 先销毁旧的 observer
if (resizeObserver) {
resizeObserver.disconnect();
}
// 使用 ResizeObserver 等待容器有实际尺寸后再初始化图表
resizeObserver = new ResizeObserver(entries => {
for (const entry of entries) {
const { width, height } = entry.contentRect;
if (width > 0 && height > 0) {
nextTick(() => {
if (!chartInstance && chartContainer.value) {
initChart();
} else {
updateChart();
}
});
resizeObserver?.disconnect();
resizeObserver = null;
}
}
});
nextTick(() => {
if (chartContainer.value) {
resizeObserver.observe(chartContainer.value);
}
});
}
});
</script>
<style lang="scss" scoped>
.empty-wrapper {
display: flex;
align-items: center;
justify-content: center;
height: 200px;
}
.chart-wrapper {
margin: 0;
width: 100%;
position: relative;
}
.chart-container {
width: 100%;
height: 200px;
}
</style>