过鱼设施的接口对接
This commit is contained in:
commit
ed29837df1
@ -7,7 +7,7 @@ VITE_APP_TITLE = '水电水利建设项目全过程环境管理信息平台'
|
|||||||
VITE_APP_PORT = 3000
|
VITE_APP_PORT = 3000
|
||||||
VITE_APP_BASE_API = '/dev-api'
|
VITE_APP_BASE_API = '/dev-api'
|
||||||
# 本地环境
|
# 本地环境
|
||||||
VITE_APP_BASE_URL = 'http://localhost:8093'
|
VITE_APP_BASE_URL = 'http://10.84.121.199:8093'
|
||||||
# 测试环境
|
# 测试环境
|
||||||
# VITE_APP_BASE_URL = 'http://172.16.21.142:8093'
|
# VITE_APP_BASE_URL = 'http://172.16.21.142:8093'
|
||||||
# VITE_APP_BASE_URL = 'http://172.16.21.142:8096'
|
# VITE_APP_BASE_URL = 'http://172.16.21.142:8096'
|
||||||
@ -15,7 +15,7 @@ VITE_APP_BASE_URL = 'http://localhost:8093'
|
|||||||
# VITE_APP_BASE_URL = 'http://10.84.121.21:8093'
|
# VITE_APP_BASE_URL = 'http://10.84.121.21:8093'
|
||||||
# VITE_APP_BASE_URL = 'http://192.168.1.162:8093'
|
# VITE_APP_BASE_URL = 'http://192.168.1.162:8093'
|
||||||
|
|
||||||
# 测试环境线上
|
# 测试环境线上10.84.121.122:
|
||||||
VITE_APP_TEST_ONLINE_URL = 'https://211.99.26.225:12122'
|
VITE_APP_TEST_ONLINE_URL = 'https://211.99.26.225:12122'
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -15,3 +15,11 @@ export function buildGetKendoListCust(data: any) {
|
|||||||
data
|
data
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
//过鱼总量
|
||||||
|
export function yearGetYearFpStatistics(data: any) {
|
||||||
|
return request({
|
||||||
|
url: '/api/wmp-env-server/env/fp/run/qgc/year/GetYearFpStatistics',
|
||||||
|
method: 'get',
|
||||||
|
params:data
|
||||||
|
});
|
||||||
|
}
|
||||||
@ -1,23 +1,23 @@
|
|||||||
<!-- SidePanelItem.vue -->
|
<!-- SidePanelItem.vue -->
|
||||||
<template>
|
<template>
|
||||||
<SidePanelItem :title="title" :datetimePicker="datetimePicker">
|
<SidePanelItem :title="title" :datetimePicker="datetimePicker" @update-values="handlePanelChange1">
|
||||||
<div
|
<!-- 加载状态 -->
|
||||||
class="container"
|
<div v-if="loading" class="loading-container">
|
||||||
@mouseenter="handleMouseEnter"
|
<a-spin tip="加载中..." />
|
||||||
@mouseleave="handleMouseLeave"
|
</div>
|
||||||
>
|
|
||||||
|
<!-- 无数据状态 -->
|
||||||
|
<div v-else-if="!hasData" class="empty-container">
|
||||||
|
<a-empty description="暂无数据" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 正常显示图表 -->
|
||||||
|
<div v-else class="container" @mouseenter="handleMouseEnter" @mouseleave="handleMouseLeave">
|
||||||
<!-- 跑马灯轨道容器 -->
|
<!-- 跑马灯轨道容器 -->
|
||||||
<div
|
<div class="carousel-track" :class="{ 'no-transition': isTransitioning }"
|
||||||
class="carousel-track"
|
:style="{ transform: `translateX(-${currentIndex * 100}%)` }">
|
||||||
:class="{ 'no-transition': isTransitioning }"
|
|
||||||
:style="{ transform: `translateX(-${currentIndex * 100}%)` }"
|
|
||||||
>
|
|
||||||
<!-- 遍历所有图表项(包含克隆项) -->
|
<!-- 遍历所有图表项(包含克隆项) -->
|
||||||
<div
|
<div v-for="(item, index) in renderChartData" :key="index" class="carousel-item">
|
||||||
v-for="(item, index) in renderChartData"
|
|
||||||
:key="index"
|
|
||||||
class="carousel-item"
|
|
||||||
>
|
|
||||||
<div class="pie-chart-container">
|
<div class="pie-chart-container">
|
||||||
<!-- 图表区域 -->
|
<!-- 图表区域 -->
|
||||||
<div :ref="el => setChartRef(el, index)" class="chart"></div>
|
<div :ref="el => setChartRef(el, index)" class="chart"></div>
|
||||||
@ -31,21 +31,44 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref, onMounted, onUnmounted, nextTick } from 'vue';
|
import { ref, onMounted, onUnmounted, nextTick, watch } from 'vue';
|
||||||
import * as echarts from 'echarts';
|
import * as echarts from 'echarts';
|
||||||
import type { ECharts } from 'echarts';
|
import type { ECharts } from 'echarts';
|
||||||
import SidePanelItem from '@/components/SidePanelItem/index.vue';
|
import SidePanelItem from '@/components/SidePanelItem/index.vue';
|
||||||
|
import { yearGetYearFpStatistics } from '@/api/gyss';
|
||||||
|
import { useJidiSelectEventStore } from "@/store/modules/jidiSelectEvent";
|
||||||
|
|
||||||
|
// API 数据类型定义
|
||||||
|
interface FpFtpStatisticsVo {
|
||||||
|
baseId: string;
|
||||||
|
baseName: string;
|
||||||
|
ftp: string;
|
||||||
|
fishName: string;
|
||||||
|
fcnt: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface BasinData {
|
||||||
|
fpCount: number;
|
||||||
|
baseId: string;
|
||||||
|
baseName: string;
|
||||||
|
fpFtpStatitcsVos: FpFtpStatisticsVo[];
|
||||||
|
}
|
||||||
|
|
||||||
// 定义组件名(便于调试和递归)
|
// 定义组件名(便于调试和递归)
|
||||||
defineOptions({
|
defineOptions({
|
||||||
name: 'GYZLLB'
|
name: 'GYZLLB'
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const JidiSelectEventStore = useJidiSelectEventStore();
|
||||||
|
const baseid = ref('');
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
title: { // 标题
|
title: { // 标题
|
||||||
type: String,
|
type: String,
|
||||||
default: '过鱼总量'
|
default: '过鱼总量'
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const datetimePicker = ref({
|
const datetimePicker = ref({
|
||||||
show: true,
|
show: true,
|
||||||
value: `${new Date().getFullYear() - 1}`, // 上一年
|
value: `${new Date().getFullYear() - 1}`, // 上一年
|
||||||
@ -54,6 +77,14 @@ const datetimePicker = ref({
|
|||||||
options: []
|
options: []
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 状态管理
|
||||||
|
const loading = ref<boolean>(false);
|
||||||
|
const hasData = ref<boolean>(true);
|
||||||
|
|
||||||
|
// 数据缓存
|
||||||
|
const cachedApiData = ref<BasinData[]>([]); // 缓存 API 返回的全量数据
|
||||||
|
const currentYear = ref<string>(''); // 当前选中的年份
|
||||||
|
|
||||||
// 图表相关
|
// 图表相关
|
||||||
interface ChartDataItem {
|
interface ChartDataItem {
|
||||||
title: string;
|
title: string;
|
||||||
@ -61,45 +92,7 @@ interface ChartDataItem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 原始图表数据(多组数据用于跑马灯)
|
// 原始图表数据(多组数据用于跑马灯)
|
||||||
const originalChartData = ref<ChartDataItem[]>(
|
const originalChartData = ref<ChartDataItem[]>([]);
|
||||||
[
|
|
||||||
{
|
|
||||||
title: '过鱼总量统计 - 2025年',
|
|
||||||
data: [
|
|
||||||
{ name: '草鱼', value: 1250 },
|
|
||||||
{ name: '鲢鱼', value: 980 },
|
|
||||||
{ name: '鳙鱼', value: 760 },
|
|
||||||
{ name: '青鱼', value: 540 },
|
|
||||||
{ name: '鲤鱼', value: 430 },
|
|
||||||
{ name: '鲫鱼', value: 320 },
|
|
||||||
{ name: '其他', value: 280 }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '过鱼总量统计 - 2024年',
|
|
||||||
data: [
|
|
||||||
{ name: '草鱼1', value: 1250 },
|
|
||||||
{ name: '鲢鱼1', value: 980 },
|
|
||||||
{ name: '鳙鱼1', value: 760 },
|
|
||||||
{ name: '青鱼1', value: 540 },
|
|
||||||
{ name: '鲤鱼1', value: 430 },
|
|
||||||
{ name: '鲫鱼1', value: 320 },
|
|
||||||
{ name: '其他1', value: 280 }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '过鱼总量统计 - 2023年',
|
|
||||||
data: [
|
|
||||||
{ name: '草鱼2', value: 1250 },
|
|
||||||
{ name: '鲢鱼2', value: 980 },
|
|
||||||
{ name: '鳙鱼2', value: 760 },
|
|
||||||
{ name: '青鱼2', value: 540 },
|
|
||||||
{ name: '鲤鱼2', value: 430 },
|
|
||||||
{ name: '鲫鱼2', value: 320 },
|
|
||||||
{ name: '其他2', value: 280 }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]);
|
|
||||||
|
|
||||||
// 克隆首尾项后的渲染数组(用于无缝循环)
|
// 克隆首尾项后的渲染数组(用于无缝循环)
|
||||||
const renderChartData = ref<ChartDataItem[]>([]);
|
const renderChartData = ref<ChartDataItem[]>([]);
|
||||||
@ -110,6 +103,16 @@ const currentIndex = ref(1); // 从1开始,跳过克隆的首项
|
|||||||
// 定时器引用
|
// 定时器引用
|
||||||
let timer: any = null;
|
let timer: any = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 停止自动轮播
|
||||||
|
*/
|
||||||
|
const stopAutoPlay = () => {
|
||||||
|
if (timer) {
|
||||||
|
clearInterval(timer);
|
||||||
|
timer = null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// 鼠标悬停状态
|
// 鼠标悬停状态
|
||||||
const isHovering = ref(false);
|
const isHovering = ref(false);
|
||||||
|
|
||||||
@ -140,7 +143,7 @@ const getUnitConfigByCode = (code: string, type: string): { unit: string } => {
|
|||||||
// 生成随机颜色(根据名称)- 生成适中亮度颜色
|
// 生成随机颜色(根据名称)- 生成适中亮度颜色
|
||||||
const generateRandomColor = (names: string[]): string[] => {
|
const generateRandomColor = (names: string[]): string[] => {
|
||||||
const colors: string[] = [];
|
const colors: string[] = [];
|
||||||
|
|
||||||
names.forEach(() => {
|
names.forEach(() => {
|
||||||
// 生成 HSL 颜色,控制饱和度和亮度在适中范围
|
// 生成 HSL 颜色,控制饱和度和亮度在适中范围
|
||||||
// H: 0-360 (色相)
|
// H: 0-360 (色相)
|
||||||
@ -149,42 +152,42 @@ const generateRandomColor = (names: string[]): string[] => {
|
|||||||
const hue = Math.floor(Math.random() * 360);
|
const hue = Math.floor(Math.random() * 360);
|
||||||
const saturation = 40 + Math.floor(Math.random() * 30); // 40-70%
|
const saturation = 40 + Math.floor(Math.random() * 30); // 40-70%
|
||||||
const lightness = 45 + Math.floor(Math.random() * 20); // 45-65%
|
const lightness = 45 + Math.floor(Math.random() * 20); // 45-65%
|
||||||
|
|
||||||
// 将 HSL 转换为 HEX
|
// 将 HSL 转换为 HEX
|
||||||
const h = hue / 360;
|
const h = hue / 360;
|
||||||
const s = saturation / 100;
|
const s = saturation / 100;
|
||||||
const l = lightness / 100;
|
const l = lightness / 100;
|
||||||
|
|
||||||
let r, g, b;
|
let r, g, b;
|
||||||
|
|
||||||
if (s === 0) {
|
if (s === 0) {
|
||||||
r = g = b = l;
|
r = g = b = l;
|
||||||
} else {
|
} else {
|
||||||
const hue2rgb = (p: number, q: number, t: number) => {
|
const hue2rgb = (p: number, q: number, t: number) => {
|
||||||
if (t < 0) t += 1;
|
if (t < 0) t += 1;
|
||||||
if (t > 1) t -= 1;
|
if (t > 1) t -= 1;
|
||||||
if (t < 1/6) return p + (q - p) * 6 * t;
|
if (t < 1 / 6) return p + (q - p) * 6 * t;
|
||||||
if (t < 1/2) return q;
|
if (t < 1 / 2) return q;
|
||||||
if (t < 2/3) return p + (q - p) * (2/3 - t) * 6;
|
if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;
|
||||||
return p;
|
return p;
|
||||||
};
|
};
|
||||||
|
|
||||||
const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
|
const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
|
||||||
const p = 2 * l - q;
|
const p = 2 * l - q;
|
||||||
|
|
||||||
r = hue2rgb(p, q, h + 1/3);
|
r = hue2rgb(p, q, h + 1 / 3);
|
||||||
g = hue2rgb(p, q, h);
|
g = hue2rgb(p, q, h);
|
||||||
b = hue2rgb(p, q, h - 1/3);
|
b = hue2rgb(p, q, h - 1 / 3);
|
||||||
}
|
}
|
||||||
|
|
||||||
const toHex = (x: number) => {
|
const toHex = (x: number) => {
|
||||||
const hex = Math.round(x * 255).toString(16);
|
const hex = Math.round(x * 255).toString(16);
|
||||||
return hex.length === 1 ? '0' + hex : hex;
|
return hex.length === 1 ? '0' + hex : hex;
|
||||||
};
|
};
|
||||||
|
|
||||||
colors.push(`#${toHex(r)}${toHex(g)}${toHex(b)}`);
|
colors.push(`#${toHex(r)}${toHex(g)}${toHex(b)}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
return colors;
|
return colors;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -220,7 +223,7 @@ const initChart = (index: number) => {
|
|||||||
const updateChart = (index: number) => {
|
const updateChart = (index: number) => {
|
||||||
const chartInstance = chartInstances.value[index];
|
const chartInstance = chartInstances.value[index];
|
||||||
const chartData = renderChartData.value[index];
|
const chartData = renderChartData.value[index];
|
||||||
|
|
||||||
if (!chartInstance || !chartData) return;
|
if (!chartInstance || !chartData) return;
|
||||||
|
|
||||||
const colorNames = chartData.data.map(item => item.name);
|
const colorNames = chartData.data.map(item => item.name);
|
||||||
@ -243,7 +246,7 @@ const updateChart = (index: number) => {
|
|||||||
{
|
{
|
||||||
text: `${totalAmount}`,
|
text: `${totalAmount}`,
|
||||||
subtext: `总量(${unit})`,
|
subtext: `总量(${unit})`,
|
||||||
left: '34%',
|
left: '29%',
|
||||||
top: '45%',
|
top: '45%',
|
||||||
textAlign: 'center',
|
textAlign: 'center',
|
||||||
textVerticalAlign: 'middle',
|
textVerticalAlign: 'middle',
|
||||||
@ -272,7 +275,7 @@ const updateChart = (index: number) => {
|
|||||||
pageIconInactiveColor: '#ccc',
|
pageIconInactiveColor: '#ccc',
|
||||||
pageIconSize: 12,
|
pageIconSize: 12,
|
||||||
pageFormatter: '{current}/{total}',
|
pageFormatter: '{current}/{total}',
|
||||||
formatter: function(name: string) {
|
formatter: function (name: string) {
|
||||||
const found = chartData.data.find(item => item.name === name);
|
const found = chartData.data.find(item => item.name === name);
|
||||||
const value = found ? found.value : '-';
|
const value = found ? found.value : '-';
|
||||||
const maxLength = 7;
|
const maxLength = 7;
|
||||||
@ -280,7 +283,7 @@ const updateChart = (index: number) => {
|
|||||||
return `${truncatedName} ${value}${unit}`;
|
return `${truncatedName} ${value}${unit}`;
|
||||||
},
|
},
|
||||||
textStyle: {
|
textStyle: {
|
||||||
fontSize: 13,
|
fontSize: 12,
|
||||||
color: '#666'
|
color: '#666'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -288,7 +291,7 @@ const updateChart = (index: number) => {
|
|||||||
{
|
{
|
||||||
type: 'pie',
|
type: 'pie',
|
||||||
radius: ['50%', '70%'],
|
radius: ['50%', '70%'],
|
||||||
center: ['35%', '50%'],
|
center: ['30%', '50%'],
|
||||||
avoidLabelOverlap: false,
|
avoidLabelOverlap: false,
|
||||||
itemStyle: {
|
itemStyle: {
|
||||||
borderRadius: 4,
|
borderRadius: 4,
|
||||||
@ -337,13 +340,13 @@ const handleResize = () => {
|
|||||||
const initRenderData = () => {
|
const initRenderData = () => {
|
||||||
const length = originalChartData.value.length;
|
const length = originalChartData.value.length;
|
||||||
if (length === 0) return;
|
if (length === 0) return;
|
||||||
|
|
||||||
renderChartData.value = [
|
renderChartData.value = [
|
||||||
originalChartData.value[length - 1], // 克隆最后一项
|
originalChartData.value[length - 1], // 克隆最后一项
|
||||||
...originalChartData.value, // 原始数据
|
...originalChartData.value, // 原始数据
|
||||||
originalChartData.value[0] // 克隆第一项
|
originalChartData.value[0] // 克隆第一项
|
||||||
];
|
];
|
||||||
|
|
||||||
// 初始化图表实例数组
|
// 初始化图表实例数组
|
||||||
chartInstances.value = new Array(renderChartData.value.length).fill(null);
|
chartInstances.value = new Array(renderChartData.value.length).fill(null);
|
||||||
chartRefs.value = new Array(renderChartData.value.length).fill(null);
|
chartRefs.value = new Array(renderChartData.value.length).fill(null);
|
||||||
@ -362,7 +365,7 @@ const startAutoPlay = () => {
|
|||||||
// 切换到下一张
|
// 切换到下一张
|
||||||
const nextSlide = () => {
|
const nextSlide = () => {
|
||||||
currentIndex.value++;
|
currentIndex.value++;
|
||||||
|
|
||||||
// 延迟检查是否需要无缝跳转
|
// 延迟检查是否需要无缝跳转
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
checkSeamlessJump();
|
checkSeamlessJump();
|
||||||
@ -372,15 +375,15 @@ const nextSlide = () => {
|
|||||||
// 检查是否需要无缝跳转(克隆项处理)
|
// 检查是否需要无缝跳转(克隆项处理)
|
||||||
const checkSeamlessJump = () => {
|
const checkSeamlessJump = () => {
|
||||||
const realLength = originalChartData.value.length;
|
const realLength = originalChartData.value.length;
|
||||||
|
|
||||||
// 如果到达克隆的最后一项(索引 = realLength + 1)
|
// 如果到达克隆的最后一项(索引 = realLength + 1)
|
||||||
if (currentIndex.value >= realLength + 1) {
|
if (currentIndex.value >= realLength + 1) {
|
||||||
// 1. 禁用过渡动画,实现瞬间跳转
|
// 1. 禁用过渡动画,实现瞬间跳转
|
||||||
isTransitioning.value = true;
|
isTransitioning.value = true;
|
||||||
|
|
||||||
// 2. 瞬间跳转到真实的第二项(索引1),用户看不到跳变
|
// 2. 瞬间跳转到真实的第二项(索引1),用户看不到跳变
|
||||||
currentIndex.value = 1;
|
currentIndex.value = 1;
|
||||||
|
|
||||||
// 3. 等待两帧后恢复过渡动画(确保DOM已更新)
|
// 3. 等待两帧后恢复过渡动画(确保DOM已更新)
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
@ -403,7 +406,7 @@ const handleMouseLeave = () => {
|
|||||||
// 页面加载时执行
|
// 页面加载时执行
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
initRenderData();
|
initRenderData();
|
||||||
|
|
||||||
// 等待DOM渲染完成
|
// 等待DOM渲染完成
|
||||||
await nextTick();
|
await nextTick();
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
@ -411,10 +414,10 @@ onMounted(async () => {
|
|||||||
renderChartData.value.forEach((_, index) => {
|
renderChartData.value.forEach((_, index) => {
|
||||||
initChart(index);
|
initChart(index);
|
||||||
});
|
});
|
||||||
|
|
||||||
// 监听窗口大小变化
|
// 监听窗口大小变化
|
||||||
window.addEventListener('resize', handleResize);
|
window.addEventListener('resize', handleResize);
|
||||||
|
|
||||||
// 启动自动轮播
|
// 启动自动轮播
|
||||||
startAutoPlay();
|
startAutoPlay();
|
||||||
}, 100);
|
}, 100);
|
||||||
@ -423,16 +426,241 @@ onMounted(async () => {
|
|||||||
// 组件卸载时清理
|
// 组件卸载时清理
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
if (timer) clearInterval(timer);
|
if (timer) clearInterval(timer);
|
||||||
|
|
||||||
// 销毁所有图表实例
|
// 销毁所有图表实例
|
||||||
chartInstances.value.forEach(instance => {
|
chartInstances.value.forEach(instance => {
|
||||||
if (instance) {
|
if (instance) {
|
||||||
instance.dispose();
|
instance.dispose();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
window.removeEventListener('resize', handleResize);
|
window.removeEventListener('resize', handleResize);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构建单一流域的图表数据
|
||||||
|
*/
|
||||||
|
const buildSingleBasinChartData = (basin: BasinData, year: string): ChartDataItem => {
|
||||||
|
return {
|
||||||
|
title: `${basin.baseName}`,
|
||||||
|
data: basin.fpFtpStatitcsVos.map((fish: FpFtpStatisticsVo) => ({
|
||||||
|
name: fish.fishName,
|
||||||
|
value: fish.fcnt
|
||||||
|
}))
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构建全部流域的图表数据(包含流域对比图和各流域鱼类分布图)
|
||||||
|
*/
|
||||||
|
const buildAllBasinsChartData = (apiData: BasinData[], year: string): ChartDataItem[] => {
|
||||||
|
const charts: ChartDataItem[] = [];
|
||||||
|
|
||||||
|
// 1️⃣ 第一张:各流域总量对比图(使用 fpCount)
|
||||||
|
const basinComparisonData = apiData
|
||||||
|
.filter(basin => basin.fpFtpStatitcsVos && basin.fpFtpStatitcsVos.length > 0)
|
||||||
|
.map(basin => ({
|
||||||
|
name: basin.baseName,
|
||||||
|
value: basin.fpCount
|
||||||
|
}));
|
||||||
|
|
||||||
|
if (basinComparisonData.length > 0) {
|
||||||
|
charts.push({
|
||||||
|
title: `总量`,
|
||||||
|
data: basinComparisonData
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2️⃣ 后续:每个流域的鱼类分布图(使用 fcnt)
|
||||||
|
apiData.forEach(basin => {
|
||||||
|
if (basin.fpFtpStatitcsVos && basin.fpFtpStatitcsVos.length > 0) {
|
||||||
|
charts.push(buildSingleBasinChartData(basin, year));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return charts;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 重新构建图表实例(用于完全重建)
|
||||||
|
*/
|
||||||
|
const rebuildCharts = async () => {
|
||||||
|
// 销毁旧图表实例
|
||||||
|
chartInstances.value.forEach(instance => {
|
||||||
|
if (instance) {
|
||||||
|
instance.dispose();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
chartInstances.value = [];
|
||||||
|
|
||||||
|
// 重新初始化渲染数据
|
||||||
|
initRenderData();
|
||||||
|
|
||||||
|
// 等待 DOM 更新
|
||||||
|
await nextTick();
|
||||||
|
|
||||||
|
// 重新初始化所有图表
|
||||||
|
setTimeout(() => {
|
||||||
|
renderChartData.value.forEach((_, index) => {
|
||||||
|
initChart(index);
|
||||||
|
});
|
||||||
|
}, 100);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 控制跑马灯的启停
|
||||||
|
*/
|
||||||
|
const controlCarousel = () => {
|
||||||
|
// 判断是否需要停止跑马灯
|
||||||
|
const shouldStop = baseid.value !== 'all' || originalChartData.value.length <= 1;
|
||||||
|
|
||||||
|
if (shouldStop) {
|
||||||
|
stopAutoPlay(); // 停止轮播,但不重置 currentIndex
|
||||||
|
} else {
|
||||||
|
startAutoPlay(); // 启动轮播
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据当前 baseid 更新图表显示(不请求 API)
|
||||||
|
*/
|
||||||
|
const updateChartByBaseid = () => {
|
||||||
|
if (!cachedApiData.value.length) {
|
||||||
|
hasData.value = false;
|
||||||
|
originalChartData.value = [];
|
||||||
|
renderChartData.value = []; // 清空渲染数据
|
||||||
|
stopAutoPlay(); // 停止轮播
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (baseid.value === 'all') {
|
||||||
|
// === 全部流域模式 ===
|
||||||
|
originalChartData.value = buildAllBasinsChartData(cachedApiData.value, currentYear.value);
|
||||||
|
} else {
|
||||||
|
// === 单一流域模式 ===
|
||||||
|
const targetBasin = cachedApiData.value.find(item => item.baseId === baseid.value);
|
||||||
|
|
||||||
|
if (!targetBasin || !targetBasin.fpFtpStatitcsVos || targetBasin.fpFtpStatitcsVos.length === 0) {
|
||||||
|
hasData.value = false;
|
||||||
|
originalChartData.value = [];
|
||||||
|
renderChartData.value = []; // 清空渲染数据
|
||||||
|
stopAutoPlay(); // 停止轮播
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
originalChartData.value = [buildSingleBasinChartData(targetBasin, currentYear.value)];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 先构建渲染数据
|
||||||
|
initRenderData();
|
||||||
|
|
||||||
|
// 再设置为有数据状态
|
||||||
|
hasData.value = true;
|
||||||
|
|
||||||
|
// 等待 DOM 更新后初始化图表
|
||||||
|
nextTick(() => {
|
||||||
|
// 销毁旧图表实例
|
||||||
|
chartInstances.value.forEach(instance => {
|
||||||
|
if (instance) {
|
||||||
|
instance.dispose();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
chartInstances.value = new Array(renderChartData.value.length).fill(null);
|
||||||
|
chartRefs.value = new Array(renderChartData.value.length).fill(null);
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
renderChartData.value.forEach((_, index) => {
|
||||||
|
initChart(index);
|
||||||
|
});
|
||||||
|
}, 100);
|
||||||
|
});
|
||||||
|
|
||||||
|
controlCarousel(); // 控制跑马灯启停
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取并处理图表数据
|
||||||
|
*/
|
||||||
|
const getechartsdata = async (yearOne: string) => {
|
||||||
|
if (!baseid.value) {
|
||||||
|
hasData.value = false;
|
||||||
|
originalChartData.value = [];
|
||||||
|
renderChartData.value = [];
|
||||||
|
stopAutoPlay();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果年份没变,且有缓存数据,直接使用缓存
|
||||||
|
if (yearOne === currentYear.value && cachedApiData.value.length > 0) {
|
||||||
|
updateChartByBaseid();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
loading.value = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const params = { year: yearOne };
|
||||||
|
const res = await yearGetYearFpStatistics(params);
|
||||||
|
// axios 返回的结构:res.data 才是真正的业务数据
|
||||||
|
const responseData = res.data;
|
||||||
|
if (!responseData || responseData.length === 0) {
|
||||||
|
console.warn('无有效数据');
|
||||||
|
hasData.value = false;
|
||||||
|
originalChartData.value = [];
|
||||||
|
renderChartData.value = [];
|
||||||
|
cachedApiData.value = [];
|
||||||
|
currentYear.value = '';
|
||||||
|
stopAutoPlay();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// debugger
|
||||||
|
// 缓存全量数据
|
||||||
|
cachedApiData.value = responseData;
|
||||||
|
currentYear.value = yearOne;
|
||||||
|
// 根据 baseid 更新显示
|
||||||
|
updateChartByBaseid();
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取过鱼统计数据失败:', error);
|
||||||
|
hasData.value = false;
|
||||||
|
originalChartData.value = [];
|
||||||
|
renderChartData.value = [];
|
||||||
|
cachedApiData.value = [];
|
||||||
|
currentYear.value = '';
|
||||||
|
stopAutoPlay();
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handlePanelChange1 = (data: any) => {
|
||||||
|
if (data.datetime) {
|
||||||
|
getechartsdata(data.datetime); // 年份变化,请求 API
|
||||||
|
} else {
|
||||||
|
// 置空图表数据,并显示暂无数据
|
||||||
|
hasData.value = false;
|
||||||
|
originalChartData.value = [];
|
||||||
|
renderChartData.value = []; // 清空渲染数据
|
||||||
|
cachedApiData.value = []; // 清空缓存
|
||||||
|
currentYear.value = '';
|
||||||
|
stopAutoPlay(); // 停止轮播
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => JidiSelectEventStore.selectedItem,
|
||||||
|
(newVal) => {
|
||||||
|
if (newVal && newVal.wbsCode) {
|
||||||
|
baseid.value = newVal.wbsCode;
|
||||||
|
|
||||||
|
// 切换流域时,不请求 API,只从缓存中筛选更新显示
|
||||||
|
if (cachedApiData.value.length > 0) {
|
||||||
|
updateChartByBaseid();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ deep: true, immediate: true }
|
||||||
|
);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@ -443,26 +671,26 @@ onUnmounted(() => {
|
|||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
position: relative;
|
position: relative;
|
||||||
overflow: hidden; // 隐藏超出容器的内容
|
overflow: hidden; // 隐藏超出容器的内容
|
||||||
|
|
||||||
// 跑马灯轨道
|
// 跑马灯轨道
|
||||||
.carousel-track {
|
.carousel-track {
|
||||||
display: flex; // 横向排列所有媒体项
|
display: flex; // 横向排列所有媒体项
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
transition: transform 0.5s ease-in-out; // 0.5秒平滑过渡
|
transition: transform 0.5s ease-in-out; // 0.5秒平滑过渡
|
||||||
|
|
||||||
// 禁用过渡动画(用于无缝跳转)
|
// 禁用过渡动画(用于无缝跳转)
|
||||||
&.no-transition {
|
&.no-transition {
|
||||||
transition: none;
|
transition: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 单个图表项
|
// 单个图表项
|
||||||
.carousel-item {
|
.carousel-item {
|
||||||
min-width: 100%; // 每个项目占满容器宽度
|
min-width: 100%; // 每个项目占满容器宽度
|
||||||
height: 100%;
|
height: 100%;
|
||||||
position: relative;
|
position: relative;
|
||||||
flex-shrink: 0; // 防止被压缩
|
flex-shrink: 0; // 防止被压缩
|
||||||
|
|
||||||
.pie-chart-container {
|
.pie-chart-container {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
@ -485,4 +713,22 @@ onUnmounted(() => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 加载状态样式
|
||||||
|
.loading-container {
|
||||||
|
width: 100%;
|
||||||
|
height: 290px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 空状态样式
|
||||||
|
.empty-container {
|
||||||
|
width: 100%;
|
||||||
|
height: 290px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@ -81,8 +81,8 @@ const guoyuStatus = ref<any>([]);
|
|||||||
|
|
||||||
const baseColumnsConfig: ColumnConfig[] = [
|
const baseColumnsConfig: ColumnConfig[] = [
|
||||||
{
|
{
|
||||||
dataIndex: 'hbrvnm',
|
dataIndex: 'rvnm',
|
||||||
key: 'hbrvnm',
|
key: 'rvnm',
|
||||||
title: '流域',
|
title: '流域',
|
||||||
width: 120,
|
width: 120,
|
||||||
fixed: 'left'
|
fixed: 'left'
|
||||||
|
|||||||
@ -256,8 +256,8 @@ const guoyuStatus = ref<any>([]);
|
|||||||
// --- 基础配置 ---
|
// --- 基础配置 ---
|
||||||
const baseColumnsConfig: ColumnConfig[] = [
|
const baseColumnsConfig: ColumnConfig[] = [
|
||||||
{
|
{
|
||||||
dataIndex: 'hbrvnm',
|
dataIndex: 'rvnm',
|
||||||
key: 'hbrvnm',
|
key: 'rvnm',
|
||||||
title: '流域',
|
title: '流域',
|
||||||
width: 120,
|
width: 120,
|
||||||
fixed: 'left'
|
fixed: 'left'
|
||||||
|
|||||||
@ -258,8 +258,8 @@ import PreviewMedia from '@/components/previewMedia/index.vue';
|
|||||||
const baseUrl = import.meta.env.VITE_APP_ATTACHMENT_URL;
|
const baseUrl = import.meta.env.VITE_APP_ATTACHMENT_URL;
|
||||||
let columns = ref([
|
let columns = ref([
|
||||||
{
|
{
|
||||||
dataIndex: 'hbrvnm',
|
dataIndex: 'rvnm',
|
||||||
key: 'hbrvnm',
|
key: 'rvnm',
|
||||||
title: '流域',
|
title: '流域',
|
||||||
width: 120,
|
width: 120,
|
||||||
fixed: 'left'
|
fixed: 'left'
|
||||||
@ -515,8 +515,8 @@ const previewListIndex = ref(0);
|
|||||||
// 详情表格列定义(删除状态列)
|
// 详情表格列定义(删除状态列)
|
||||||
const detailColumns = ref([
|
const detailColumns = ref([
|
||||||
{
|
{
|
||||||
dataIndex: 'hbrvnm',
|
dataIndex: 'rvnm',
|
||||||
key: 'hbrvnm',
|
key: 'rvnm',
|
||||||
title: '流域',
|
title: '流域',
|
||||||
width: 120,
|
width: 120,
|
||||||
fixed: 'left'
|
fixed: 'left'
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user