WholeProcessPlatform/frontend/src/modules/shuiWenNianNeiFenBu/index.vue
2026-06-01 08:37:38 +08:00

617 lines
17 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.

<template>
<SidePanelItem title="水温年内分布" :moreSelect="select" :datetimePicker="datetimePicker"
@update-values="handlePanelChange1">
<a-spin :spinning="loading" tip="加载中...">
<div v-show="!loading && showemit" class="water-temp-chart-container">
<div ref="chartRef" class="chart-wrapper"></div>
</div>
<div v-show="!loading && !showemit" class="water-temp-chart-container">
<a-empty description="暂无数据" />
</div>
</a-spin>
</SidePanelItem>
<!-- 数据点详情弹框 -->
<a-modal v-model:open="modalVisible" title="水温数据详情" :footer="null" width="1536px" @cancel="handleModalClose">
<YaerAverage :tm="`${datetimePicker.value}-${String(currentData.monthInt).padStart(2, '0')}-01 00:00:00`"
:dataDimensionVal="baseid" :stcd="select.value" />
</a-modal>
</template>
<script setup lang="ts">
import { ref, onMounted, onBeforeUnmount, watch } from 'vue'
import * as echarts from 'echarts'
import SidePanelItem from '@/components/SidePanelItem/index.vue';
import { getVmsstbprpt, yearListGetKendoListCust } from "@/api/sw";
import { useJidiSelectEventStore } from "@/store/modules/jidiSelectEvent";
import YaerAverage from "./TwoLayers/yaerAverage.vue"
const JidiSelectEventStore = useJidiSelectEventStore();
// 水温年内分布数据(响应式)
const waterTempData = ref<any[]>([])
const loading = ref(false)
const showemit = ref(true)
// 弹框相关状态
const modalVisible = ref(false)
const currentData = ref<any>({})
const jiDiList: any = ref([
{
"baseid": "all",
"basename": "当前全部",
"id": "9BFEC848-83EA-AD22-6DE2-10E969476693"
},
{
"baseid": "01",
"basename": "金沙江干流",
"id": "A33040B7-8977-D9F1-5E02-D7F0241AB8AA"
},
{
"baseid": "02",
"basename": "雅砻江干流",
"id": "C63D6020-995D-FE97-2F4A-F619A7142C79"
},
{
"baseid": "03",
"basename": "大渡河干流",
"id": "CCB0766B-F1D4-7DD6-650F-5C556B7231B3"
},
{
"baseid": "04",
"basename": "乌江干流",
"id": "E8A66641-B4F4-CC85-0815-38C5B2F93DBD"
},
{
"baseid": "05",
"basename": "长江上游干流",
"id": "E02C11E9-CEA5-A030-202F-3BFE84465D03"
},
{
"baseid": "10",
"basename": "湘西",
"id": "B1D1D52D-CEEF-6DC4-27DF-9990EB572F8D"
},
{
"baseid": "08",
"basename": "黄河上游干流",
"id": "BC6AF135-263D-09A9-D5CA-C99B2598FE6E"
},
{
"baseid": "09",
"basename": "黄河中游干流",
"id": "60EEEC28-128F-A2A8-44F3-6EAAC8FD8BB6"
},
{
"baseid": "06",
"basename": "南盘江-红水河",
"id": "7BB9A8F4-34B5-42B4-A7FC-CE910AD7F203"
},
{
"baseid": "12",
"basename": "东北",
"id": "47F8EF06-924E-E161-FCAF-62A66BBF252D"
},
{
"baseid": "07",
"basename": "澜沧江干流",
"id": "A966A4C9-278C-B0DA-2B97-2D10B7A6E96A"
},
{
"baseid": "13",
"basename": "怒江干流",
"id": "FA89E8CB-67A8-76DA-DC1E-23AE4C54F9E4"
},
{
"baseid": "11",
"basename": "闽浙赣",
"id": "CD98F995-EEB2-1021-D807-DA1B1AD9E49A"
},
{
"baseid": "other",
"basename": "其他",
"id": "AFBDFC67-B955-4EFD-959A-014CFB59EBFC"
}
]);
const chartRef = ref<HTMLElement | null>(null)
let chartInstance: echarts.ECharts | null = null
// 选择器配置
const select = ref({
show: true,
value: '',
options: [],
picker: undefined,
format: undefined,
});
// 日期选择器配置
const datetimePicker = ref({
show: true,
value: (() => {
const now = new Date();
const year = now.getFullYear();
return `${year}`;
})(),
format: "YYYY",
picker: "year" as const,
options: [],
});
const transUnit = (value: number | null) => {
if (value === null) return null
return value
}
const getUnitConfigByCode = (_code: string, _type: string) => {
return {
unit: '℃'
}
}
const getColorByCodeAndType = (_code: string[], _typeKey: string[]) => {
return ['#4b79ab', '#78c300']
}
// 处理图表数据点点击事件
const handleChartClick = (params: any) => {
if (!params || !String(params.dataIndex)) return
// 获取点击的数据点索引
const dataIndex = params.dataIndex
const dataItem = waterTempData.value[dataIndex]
if (dataItem) {
currentData.value = {
...dataItem,
stnm: select.value.options.find((opt: any) =>
opt.children?.some((child: any) => child.value === select.value.value)
)?.children?.find((child: any) => child.value === select.value.value)?.title || '未知站点'
}
modalVisible.value = true
}
}
// 关闭弹框
const handleModalClose = () => {
modalVisible.value = false
currentData.value = {}
}
//下拉框数据选择
const getselectData = async () => {
try {
const params = {
filter: {
logic: "and",
filters: [
{
field: "sttpFullPath",
operator: "contains",
value: "ENV,ENVM,WT,"
},
{
field: "sttpCode",
operator: "eq",
value: "WTRV"
},
baseid.value !== 'all' ? {
field: "baseId",
operator: "contains",
value: baseid.value
} : null
].filter(Boolean)
},
select: [
"stcd", "stnm", "lgtd", "lttd",
"baseId", "baseName", "siteStepSort"
]
}
const res = await getVmsstbprpt(params)
// if (!res?.data?.length) {
// select.value.options = []
// return
// }
let data = res?.data?.date || res?.data || []
// 设置默认选中值(第一个站点的 stcd
if (res.data) {
select.value.value = data?.data[0]?.stcd
} else {
select.value.value = ''
}
// 按 baseId 分组
const dataMapNameMap: Record<string, any[]> = {}
data.data.forEach((item: any) => {
const { baseId } = item
if (dataMapNameMap[baseId]) {
dataMapNameMap[baseId].push(item)
} else {
dataMapNameMap[baseId] = [item]
}
})
// 构建树形结构
const dataMapNameArr: any[] = []
const otherArr: any[] = []
Object.keys(dataMapNameMap).forEach((baseId: string) => {
const stations = dataMapNameMap[baseId]
const treeItem = {
title: stations[0]?.baseName,
value: baseId,
selectable: false,
children: stations
.sort((a, b) => a.siteStepSort - b.siteStepSort)
.map((station) => ({
title: station.stnm,
value: station.stcd,
lgtd: station.lgtd,
lttd: station.lttd
}))
}
// 区分普通基地和 other 基地
if (+baseId) {
dataMapNameArr.push(treeItem)
} else if (baseId === 'other') {
otherArr.push(treeItem)
}
})
// 按 jiDiList 顺序排序普通基地
dataMapNameArr.sort((a, b) => {
const indexA = jiDiList.value.findIndex((item: any) => item.baseid === a.value)
const indexB = jiDiList.value.findIndex((item: any) => item.baseid === b.value)
return indexA - indexB
})
// 合并结果并设置选项
select.value.options = [...dataMapNameArr, ...otherArr]
// if (baseid.value == 'all') {
// select.value.value = '008640202300001021'
// }
// else if (data.data.length > 0) {
// select.value.value = data.data[0].stcd
// }
getshuiwenList()
} catch (error) {
console.error('获取站点数据失败:', error)
select.value.options = []
}
}
//水温年内分布
const getshuiwenList = async () => {
if (loading.value) return; // 防止重复请求
if (!select.value.value) {
showemit.value = false
waterTempData.value = []
return;
}
loading.value = true;
const params = {
"filter": {
"logic": "and",
"filters": [
{
"field": "stcd",
"operator": "eq",
"dataType": "string",
"value": select.value.value
},
{
"field": "year",
"operator": "eq",
"dataType": "string",
"value": datetimePicker.value.value
}
]
},
"sort": [
{
"field": "month",
"dir": "asc"
}
]
}
try {
let res = await yearListGetKendoListCust(params)
let data = res?.data?.data || res?.data || []
// 处理空数据情况
if (data.length === 0) {
showemit.value = false
waterTempData.value = []
return
}
showemit.value = true
// 更新响应式数据
waterTempData.value = data
// 重新渲染图表
setTimeout(() => {
if (!chartInstance) {
initChart()
} else {
const option = getChartOption()
chartInstance.setOption(option, true)
}
// 强制重绘,确保尺寸正确
setTimeout(() => {
chartInstance?.resize()
}, 100)
}, 50)
} catch (error) {
console.error('获取水温数据失败:', error)
showemit.value = false
waterTempData.value = []
} finally {
loading.value = false;
}
}
const getChartOption = () => {
const { unit } = getUnitConfigByCode('Other', 'ACTUALTEMP')
const legendData = ['实测值', '天然']
const xData: string[] = []
const actualData: (number | null)[] = []
const naturalData: (number | null)[] = []
// 使用响应式数据waterTempData替代静态data
waterTempData.value.forEach((item: any) => {
xData.push(`${item.monthInt}月`)
actualData.push(item.actualTemp === null ? null : transUnit(item.actualTemp))
naturalData.push(item.naturalTemp === null ? null : transUnit(item.naturalTemp))
})
const code = ['Other']
const typeKey = ['ACTUALTEMP', 'NATURALTEMP']
const colors = getColorByCodeAndType(code, typeKey)
const options: any = {
color: colors,
tooltip: {
trigger: 'axis',
backgroundColor: 'rgba(51, 51, 51, 0.9)',
borderColor: 'transparent',
textStyle: {
color: '#ffffff',
fontSize: 14
},
formatter: function (params: any) {
if (!params || params.length === 0) return '';
let result = `<div style="font-weight: bold; margin-bottom: 8px;">${params[0].axisValue}</div>`;
params.forEach((item: any) => {
result += `<div style="display: flex; align-items: center; margin: 4px 0;">`;
result += `<span style="display: inline-block; width: 10px; height: 10px; border-radius: 50%; background-color: ${item.color}; margin-right: 8px;"></span>`;
const displayValue = item.value !== null && item.value !== undefined ? item.value: '-';
result += `<span>${item.seriesName} ${displayValue}${unit}</span>`;
result += `</div>`;
});
return result;
}
},
dataZoom: [
{
type: 'inside',
start: 0,
end: 100,
xAxisIndex: [0],
filterMode: 'filter',
zoomOnMouseWheel: true,
moveOnMouseMove: false,
moveOnMouseWheel: false
}
],
xAxis: {
type: 'category',
boundaryGap: true,
axisPointer: {
type: 'shadow',
shadowStyle: {
color: '#007aff1a'
}
},
axisLine: {
show: true,
lineStyle: {
color: '#8f8f8f'
}
},
axisTick: {
show: false
},
splitLine: {
show: true,
lineStyle: {
color: '#bfbfbf',
type: 'solid'
}
},
axisLabel: {
interval: 0,
color: '#000000',
fontSize: 12,
rotate: 0,
margin: 8,
align: 'center'
},
data: xData
},
yAxis: [
{
name: '水温(℃)',
type: 'value',
splitNumber: 5,
nameTextStyle: {
fontSize: 12,
color: '#000000'
},
lineStyle: {
color: '#6ca4f7',
width: 2
},
axisLine: {
show: true,
lineStyle: {
color: '#000000'
}
},
axisTick: {
show: true,
length: 3,
lineStyle: {
color: '#000000'
}
},
axisLabel: {
color: '#333',
fontSize: 12
},
splitLine: {
show: true,
lineStyle: {
color: '#bfbfbf',
type: 'solid'
}
}
}
],
legend: {
data: legendData
},
grid: {
top: 30,
bottom: 50,
right: 15,
left: 30
},
series: [
{
name: legendData[0],
data: actualData,
connectNulls: true,
smooth: true,
type: 'line',
itemStyle: {
color: colors[0]
}
},
{
name: legendData[1],
data: naturalData,
connectNulls: true,
smooth: true,
type: 'line',
itemStyle: {
color: colors[1]
}
}
]
}
return options
}
const initChart = async () => {
// 延迟初始化,确保容器已完成渲染
await new Promise(resolve => setTimeout(resolve, 100))
if (!chartRef.value) {
console.error('图表容器未找到')
return
}
try {
if (!chartInstance) {
chartInstance = echarts.init(chartRef.value)
}
const option = getChartOption()
chartInstance.setOption(option, false)
// 添加点击事件监听
chartInstance.off('click')
chartInstance.on('click', handleChartClick)
// 强制重绘,确保图表尺寸正确
setTimeout(() => {
chartInstance?.resize()
}, 100)
window.addEventListener('resize', handleResize)
} catch (error) {
console.error('图表初始化失败:', error)
}
}
const handleResize = () => {
if (chartInstance) {
chartInstance.resize()
}
}
onMounted(() => {
// 初始化图表
initChart()
// 加载初始数据
// if (select.value.value) {
// getshuiwenList()
// }
})
onBeforeUnmount(() => {
window.removeEventListener('resize', handleResize)
if (chartInstance) {
chartInstance.dispose()
chartInstance = null
}
})
//监听子组件的数据变化
const handlePanelChange1 = (data) => {
console.log('当前所有控件状态:', data);
// 当选择器或日期变化时,重新加载图表数据
if (data.moreSelect || data.datetime) {
select.value.value = data.moreSelect
datetimePicker.value.value = data.datetime
// debugger
getshuiwenList()
}
}
const baseid = ref('')
watch(
() => JidiSelectEventStore.selectedItem,
(newVal) => {
baseid.value = newVal.wbsCode;
getselectData()
},
{ deep: true, immediate: true }
);
</script>
<style scoped>
.water-temp-chart-container {
width: 100%;
height: 290px;
display: flex;
justify-content: center;
align-items: center;
}
.chart-wrapper {
width: 100%;
height: 100%;
}
:deep(.ant-spin-nested-loading) {
height: 290px !important;
}
</style>