2026-04-10 11:11:30 +08:00
|
|
|
|
<!-- SidePanelItem.vue -->
|
2026-05-20 08:44:31 +08:00
|
|
|
|
<!--
|
|
|
|
|
|
出入库水温图表组件
|
|
|
|
|
|
功能:展示水库入库水温和出库水温的时序变化曲线
|
|
|
|
|
|
数据来源:inOutOneGetKendoListCust接口
|
|
|
|
|
|
特性:支持月份选择、站点筛选、数据缩放、空值断开显示
|
|
|
|
|
|
-->
|
2026-04-10 11:11:30 +08:00
|
|
|
|
<template>
|
2026-05-20 08:44:31 +08:00
|
|
|
|
<SidePanelItem title="出入库水温" :moreSelect="select" :scopeDate="scopeDate" @update-values="handlePanelChange1">
|
|
|
|
|
|
<!-- ECharts图表容器 -->
|
|
|
|
|
|
<a-spin :spinning="loading" tip="加载中...">
|
|
|
|
|
|
<div v-show="showemit && !loading" ref="chartContainer" class="chart-container"></div>
|
|
|
|
|
|
<div v-show="!showemit && !loading" class="chart-container">
|
|
|
|
|
|
<a-empty />
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</a-spin>
|
2026-05-26 19:30:22 +08:00
|
|
|
|
|
|
|
|
|
|
<!-- 数据点详情弹框 -->
|
|
|
|
|
|
<a-modal v-model:open="modalVisible" :title="`${stationName}出入库水温`" :width="1536" :footer="null"
|
|
|
|
|
|
@cancel="handleModalClose">
|
|
|
|
|
|
<!-- 弹框内容区域 - 待完善 -->
|
|
|
|
|
|
<div class="modal-content">
|
|
|
|
|
|
<churukushuiwen v-if="clickDataInfo" :tm="[clickDataInfo.date, clickDataInfo.date]" :stcd="paramsOne.value" />
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</a-modal>
|
2026-04-10 11:11:30 +08:00
|
|
|
|
</SidePanelItem>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<script lang="ts" setup>
|
2026-05-20 08:44:31 +08:00
|
|
|
|
import { ref, onMounted, onUnmounted, watch } from 'vue';
|
2026-04-10 11:11:30 +08:00
|
|
|
|
import * as echarts from 'echarts';
|
2026-05-20 08:44:31 +08:00
|
|
|
|
import dayjs from 'dayjs';
|
2026-04-10 11:11:30 +08:00
|
|
|
|
import SidePanelItem from '@/components/SidePanelItem/index.vue';
|
2026-05-20 08:44:31 +08:00
|
|
|
|
import { getVmsstbprpt, inOutOneGetKendoListCust } from '@/api/sw';
|
|
|
|
|
|
import { useJidiSelectEventStore } from "@/store/modules/jidiSelectEvent";
|
2026-05-26 19:30:22 +08:00
|
|
|
|
import churukushuiwen from './churukushuiwen.vue'
|
2026-05-20 08:44:31 +08:00
|
|
|
|
// ==================== 组件基础配置 ====================
|
2026-04-10 11:11:30 +08:00
|
|
|
|
// 定义组件名(便于调试和递归)
|
|
|
|
|
|
defineOptions({
|
|
|
|
|
|
name: 'churukushuiwenMod'
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2026-05-20 08:44:31 +08:00
|
|
|
|
// ==================== 状态管理初始化 ====================
|
|
|
|
|
|
const JidiSelectEventStore = useJidiSelectEventStore(); // 基地选择事件Store
|
2026-04-10 11:11:30 +08:00
|
|
|
|
|
2026-05-20 08:44:31 +08:00
|
|
|
|
// ==================== DOM引用与图表实例 ====================
|
|
|
|
|
|
const chartContainer = ref<HTMLDivElement | null>(null); // 图表容器DOM引用
|
|
|
|
|
|
let chartInstance: echarts.ECharts | null = null; // ECharts实例
|
|
|
|
|
|
const currentMonth = new Date().toISOString().slice(0, 7); // 当前月份(格式: YYYY-MM)
|
|
|
|
|
|
const chartDataLoaded = ref(false); // 数据加载状态标志,防止重复请求
|
|
|
|
|
|
|
|
|
|
|
|
// ==================== 加载状态与显示控制 ====================
|
|
|
|
|
|
const showemit = ref(false); // 控制图表是否显示
|
|
|
|
|
|
const loading = ref(false); // 控制加载状态
|
|
|
|
|
|
|
|
|
|
|
|
// ==================== 下拉框配置(站点选择) ====================
|
|
|
|
|
|
const select = ref({
|
|
|
|
|
|
show: true, // 是否显示下拉框
|
|
|
|
|
|
value: '', // 当前选中值(站点编码)
|
|
|
|
|
|
options: [], // 下拉选项列表
|
|
|
|
|
|
picker: undefined, // 选择器类型(未使用)
|
|
|
|
|
|
format: undefined, // 格式化字符串(未使用)
|
2026-04-10 11:11:30 +08:00
|
|
|
|
});
|
|
|
|
|
|
|
2026-05-20 08:44:31 +08:00
|
|
|
|
// ==================== 日期范围选择器配置 ====================
|
|
|
|
|
|
const scopeDate = ref({
|
|
|
|
|
|
show: true, // 是否显示日期选择器
|
|
|
|
|
|
value: [currentMonth, currentMonth] as any, // 默认选中当前月份范围(使用 as any 绕过类型检查)
|
|
|
|
|
|
format: "YYYY-MM", // 显示格式
|
|
|
|
|
|
picker: "month" as const, // 选择器类型:月份选择
|
|
|
|
|
|
options: [], // 可选日期范围(未使用)
|
2026-04-10 11:11:30 +08:00
|
|
|
|
});
|
|
|
|
|
|
|
2026-05-20 08:44:31 +08:00
|
|
|
|
// ==================== 业务变量 ====================
|
|
|
|
|
|
const baseid = ref(''); // 当前选中的基地ID
|
2026-05-26 19:30:22 +08:00
|
|
|
|
const modalVisible = ref(false); // 弹框显示状态
|
|
|
|
|
|
const clickDataInfo = ref<any>(null); // 点击的数据点信息
|
|
|
|
|
|
const stationName = ref(''); // 站点名称
|
2026-05-20 08:44:31 +08:00
|
|
|
|
// ==================== 数据处理函数 ====================
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 转换图表数据格式
|
|
|
|
|
|
* 将后端返回的扁平数据结构转换为ECharts所需的双线数据格式
|
|
|
|
|
|
* @param rawData - 原始数据数组,包含tm(时间)、dwtp(类型:IWT/DWT)、wt(水温)字段
|
|
|
|
|
|
* @returns 包含日期数组、入库水温数组、出库水温数组的对象
|
|
|
|
|
|
*/
|
|
|
|
|
|
function transformChartData(rawData: any[]) {
|
|
|
|
|
|
// 1. 提取所有唯一日期并排序
|
|
|
|
|
|
const dateSet = new Set<string>()
|
|
|
|
|
|
rawData.forEach(item => {
|
|
|
|
|
|
const date = item.tm?.split(' ')[0] // "2026-04-01 00:00:00" -> "2026-04-01"
|
|
|
|
|
|
if (date) dateSet.add(date)
|
|
|
|
|
|
})
|
|
|
|
|
|
const dates = Array.from(dateSet).sort()
|
|
|
|
|
|
|
|
|
|
|
|
// 2. 构建两条线的数据(入库水温和出库水温)
|
|
|
|
|
|
const iwtData: (number | null)[] = [] // 入库水温(Inlet Water Temperature)
|
|
|
|
|
|
const dwtData: (number | null)[] = [] // 出库水温(Discharge Water Temperature)
|
|
|
|
|
|
|
|
|
|
|
|
dates.forEach(date => {
|
|
|
|
|
|
// 查找该日期的入库水温(dwtp='IWT')
|
|
|
|
|
|
const iwtItem = rawData.find(item =>
|
|
|
|
|
|
item.tm?.startsWith(date) && item.dwtp === 'IWT'
|
|
|
|
|
|
)
|
|
|
|
|
|
// 保留一位小数,缺失则填null(曲线断开)
|
|
|
|
|
|
iwtData.push(iwtItem ? Number(iwtItem.wt.toFixed(1)) : null)
|
|
|
|
|
|
|
|
|
|
|
|
// 查找该日期的出库水温(dwtp='DWT')
|
|
|
|
|
|
const dwtItem = rawData.find(item =>
|
|
|
|
|
|
item.tm?.startsWith(date) && item.dwtp === 'DWT'
|
|
|
|
|
|
)
|
|
|
|
|
|
// 保留一位小数,缺失则填null(曲线断开)
|
|
|
|
|
|
dwtData.push(dwtItem ? Number(dwtItem.wt.toFixed(1)) : null)
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
return { dates, iwtData, dwtData }
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 计算Y轴显示范围
|
|
|
|
|
|
* 基于数据最小值和最大值,上下各扩展0.2的缓冲空间
|
|
|
|
|
|
* @param data - 水温数据数组(可能包含null值)
|
|
|
|
|
|
* @returns Y轴的最小值和最大值对象
|
|
|
|
|
|
*/
|
|
|
|
|
|
function calculateYAxisRange(data: (number | null)[]) {
|
|
|
|
|
|
// 过滤出有效数值
|
|
|
|
|
|
const validValues = data.filter(v => v !== null) as number[]
|
|
|
|
|
|
if (validValues.length === 0) return { min: 0, max: 10 } // 无数据时返回默认范围
|
|
|
|
|
|
|
|
|
|
|
|
const min = Math.min(...validValues)
|
|
|
|
|
|
const max = Math.max(...validValues)
|
|
|
|
|
|
|
|
|
|
|
|
// 上下各扩展0.2,避免曲线贴边
|
|
|
|
|
|
return {
|
|
|
|
|
|
min: Number((min - 0.2).toFixed(1)), // 最小值减0.2
|
2026-05-26 19:30:22 +08:00
|
|
|
|
max: Number((max + 0.1).toFixed(1)) // 最大值加0.2
|
2026-05-20 08:44:31 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 计算X轴刻度间隔
|
|
|
|
|
|
* 确保最多显示4个刻度标签,避免标签重叠
|
|
|
|
|
|
* @param dateCount - 日期总数
|
|
|
|
|
|
* @returns X轴刻度间隔值(interval属性)
|
|
|
|
|
|
*/
|
|
|
|
|
|
function calculateXAxisInterval(dateCount: number): number {
|
|
|
|
|
|
if (dateCount <= 4) return 0 // 数据点≤4时,显示所有刻度
|
|
|
|
|
|
return Math.ceil(dateCount / 4) - 1 // 动态计算间隔,保证最多4个刻度
|
|
|
|
|
|
}
|
2026-05-26 19:30:22 +08:00
|
|
|
|
const paramsOne:any = ref({
|
2026-05-20 08:44:31 +08:00
|
|
|
|
value: '',
|
|
|
|
|
|
tm: [currentMonth, currentMonth]
|
|
|
|
|
|
})
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 获取图表数据
|
|
|
|
|
|
* 调用后端接口获取出入库水温数据,并触发图表更新
|
|
|
|
|
|
*/
|
|
|
|
|
|
async function fetchChartData() {
|
|
|
|
|
|
if (loading.value) return; // 防止重复请求
|
2026-05-26 19:30:22 +08:00
|
|
|
|
|
2026-05-20 08:44:31 +08:00
|
|
|
|
loading.value = true; // 开始请求前设置loading
|
2026-05-26 19:30:22 +08:00
|
|
|
|
|
2026-05-20 08:44:31 +08:00
|
|
|
|
try {
|
|
|
|
|
|
// 动态计算时间范围(考虑不同月份的实际天数)
|
|
|
|
|
|
const startDate = paramsOne.value.tm[0] + '-01 00:00:00';
|
|
|
|
|
|
const endDate = dayjs(paramsOne.value.tm[1]).endOf('month').format('YYYY-MM-DD') + ' 23:59:59';
|
2026-05-26 19:30:22 +08:00
|
|
|
|
|
2026-05-20 08:44:31 +08:00
|
|
|
|
// 构建查询参数(固定参数,后续可扩展为动态)
|
|
|
|
|
|
const params = {
|
|
|
|
|
|
filter: {
|
|
|
|
|
|
logic: "and",
|
|
|
|
|
|
filters: [
|
|
|
|
|
|
{
|
|
|
|
|
|
// OR逻辑:查询入库或出库水温
|
|
|
|
|
|
logic: "or",
|
|
|
|
|
|
filters: [
|
|
|
|
|
|
{ field: "engDwtCode", operator: "eq", value: paramsOne.value.value }, // 出库站点编码
|
|
|
|
|
|
{ field: "engIwtCode", operator: "eq", value: paramsOne.value.value } // 入库站点编码
|
|
|
|
|
|
]
|
|
|
|
|
|
},
|
|
|
|
|
|
// 时间范围过滤
|
|
|
|
|
|
{ field: "dt", operator: "gte", dataType: "date", value: startDate },
|
|
|
|
|
|
{ field: "dt", operator: "lte", dataType: "date", value: endDate }
|
|
|
|
|
|
]
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-04-10 11:11:30 +08:00
|
|
|
|
|
2026-05-20 08:44:31 +08:00
|
|
|
|
// 调用API获取数据
|
|
|
|
|
|
const res = await inOutOneGetKendoListCust(params)
|
|
|
|
|
|
const rawData = res?.data?.data || []
|
|
|
|
|
|
|
|
|
|
|
|
console.log('原始数据:', rawData)
|
|
|
|
|
|
|
|
|
|
|
|
// 根据数据长度决定showemit
|
|
|
|
|
|
if (rawData.length > 0) {
|
|
|
|
|
|
showemit.value = true; // 有数据则显示图表
|
2026-05-26 19:30:22 +08:00
|
|
|
|
|
2026-05-20 08:44:31 +08:00
|
|
|
|
// 转换数据格式
|
|
|
|
|
|
const { dates, iwtData, dwtData } = transformChartData(rawData)
|
|
|
|
|
|
|
|
|
|
|
|
console.log('转换后的数据:', { dates, iwtData, dwtData })
|
|
|
|
|
|
|
|
|
|
|
|
// 数据就绪后初始化或更新图表
|
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
|
if (!chartInstance) {
|
|
|
|
|
|
initChartWithData(dates, iwtData, dwtData); // 带数据初始化
|
|
|
|
|
|
} else {
|
|
|
|
|
|
updateChart(dates, iwtData, dwtData, 0); // 已有实例则更新
|
|
|
|
|
|
}
|
2026-05-26 19:30:22 +08:00
|
|
|
|
|
2026-05-20 08:44:31 +08:00
|
|
|
|
// 强制重绘,确保图表尺寸正确适配容器
|
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
|
if (chartInstance) {
|
|
|
|
|
|
chartInstance.resize();
|
|
|
|
|
|
}
|
|
|
|
|
|
}, 50);
|
|
|
|
|
|
}, 50);
|
|
|
|
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
showemit.value = false; // 无数据显示空状态
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
chartDataLoaded.value = true; // 标记数据已加载
|
|
|
|
|
|
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('获取图表数据失败:', error);
|
|
|
|
|
|
showemit.value = false; // 出错也显示空状态
|
|
|
|
|
|
chartDataLoaded.value = true; // 即使失败也标记为已加载,避免重复请求
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
loading.value = false; // 无论成功失败都关闭loading
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 带数据初始化图表
|
|
|
|
|
|
* @param dates - 日期数组
|
|
|
|
|
|
* @param iwtData - 入库水温数据
|
|
|
|
|
|
* @param dwtData - 出库水温数据
|
|
|
|
|
|
*/
|
|
|
|
|
|
const initChartWithData = (dates: string[], iwtData: (number | null)[], dwtData: (number | null)[]) => {
|
|
|
|
|
|
if (!chartContainer.value) return;
|
2026-05-26 19:30:22 +08:00
|
|
|
|
|
2026-04-10 11:11:30 +08:00
|
|
|
|
const containerHeight = chartContainer.value.offsetHeight;
|
|
|
|
|
|
if (containerHeight === 0) {
|
2026-05-20 08:44:31 +08:00
|
|
|
|
setTimeout(() => initChartWithData(dates, iwtData, dwtData), 100);
|
2026-04-10 11:11:30 +08:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
2026-05-26 19:30:22 +08:00
|
|
|
|
|
2026-04-10 11:11:30 +08:00
|
|
|
|
chartInstance = echarts.init(chartContainer.value);
|
2026-05-20 08:44:31 +08:00
|
|
|
|
updateChart(dates, iwtData, dwtData, 0);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 更新图表配置
|
|
|
|
|
|
* 根据处理后的数据动态生成ECharts配置项
|
|
|
|
|
|
* @param dates - 日期数组(X轴数据)
|
|
|
|
|
|
* @param iwtData - 入库水温数据数组
|
|
|
|
|
|
* @param dwtData - 出库水温数据数组
|
|
|
|
|
|
* @param yAxisMax - 废弃参数,保留以兼容旧代码
|
|
|
|
|
|
*/
|
|
|
|
|
|
function updateChart(dates: string[], iwtData: (number | null)[], dwtData: (number | null)[], yAxisMax: number) {
|
|
|
|
|
|
if (!chartInstance) {
|
|
|
|
|
|
console.warn('图表实例未初始化')
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
// ==================== 动态计算坐标轴范围 ====================
|
|
|
|
|
|
const xAxisInterval = calculateXAxisInterval(dates.length) // X轴刻度间隔
|
|
|
|
|
|
const { min: yAxisMin, max: yAxisMaxValue } = calculateYAxisRange([...iwtData, ...dwtData]) // Y轴范围
|
2026-04-10 11:11:30 +08:00
|
|
|
|
|
2026-05-20 08:44:31 +08:00
|
|
|
|
console.log('X轴间隔:', xAxisInterval, 'Y轴范围:', { min: yAxisMin, max: yAxisMaxValue })
|
|
|
|
|
|
|
|
|
|
|
|
// ==================== ECharts配置项 ====================
|
|
|
|
|
|
const option = {
|
|
|
|
|
|
// 提示框配置
|
|
|
|
|
|
tooltip: {
|
|
|
|
|
|
trigger: 'axis', // 触发类型:坐标轴触发
|
|
|
|
|
|
backgroundColor: 'rgba(50, 50, 50, 0.9)', // 背景色
|
|
|
|
|
|
borderColor: 'transparent', // 边框透明
|
|
|
|
|
|
textStyle: {
|
|
|
|
|
|
color: '#fff', // 文字颜色
|
|
|
|
|
|
fontSize: 14 // 字体大小
|
|
|
|
|
|
},
|
|
|
|
|
|
axisPointer: {
|
|
|
|
|
|
type: 'line', // 指示器类型:直线
|
|
|
|
|
|
lineStyle: {
|
|
|
|
|
|
color: 'rgba(91, 143, 249, 0.15)', // 指示线颜色
|
|
|
|
|
|
width: 30, // 指示线宽度
|
|
|
|
|
|
type: 'solid' // 线型:实线
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
// 自定义提示框内容
|
|
|
|
|
|
formatter: function (params: any) {
|
|
|
|
|
|
if (!params || params.length === 0) return '';
|
|
|
|
|
|
const date = params[0].name; // 获取日期
|
|
|
|
|
|
let result = `<div style="font-weight: bold; margin-bottom: 8px; font-size: 14px;">${date}</div>`;
|
|
|
|
|
|
params.forEach((item: any) => {
|
|
|
|
|
|
// 空值显示'-',符合项目规范
|
|
|
|
|
|
const value = item.value !== null && item.value !== undefined ? `${item.value}°C` : '-'
|
|
|
|
|
|
result += `<div style="display: flex; align-items: center; gap: 6px; margin: 4px 0; font-size: 14px;">
|
|
|
|
|
|
<span style="display: inline-block; width: 10px; height: 10px; border-radius: 50%; background-color: ${item.color};"></span>
|
|
|
|
|
|
<span>${item.seriesName}</span>
|
|
|
|
|
|
<span style="font-weight: bold; margin-left: auto;">${value}</span>
|
|
|
|
|
|
</div>`;
|
|
|
|
|
|
});
|
|
|
|
|
|
return result;
|
|
|
|
|
|
}
|
2026-04-10 11:11:30 +08:00
|
|
|
|
},
|
2026-05-20 08:44:31 +08:00
|
|
|
|
|
|
|
|
|
|
// 标题配置
|
|
|
|
|
|
title: {
|
|
|
|
|
|
text: '水温(°C)', // 标题文本
|
|
|
|
|
|
left: 17, // 左侧位置
|
|
|
|
|
|
top: 13, // 顶部位置
|
|
|
|
|
|
textStyle: {
|
|
|
|
|
|
fontSize: 13,
|
|
|
|
|
|
color: '#000000',
|
|
|
|
|
|
fontWeight: 'normal'
|
2026-04-10 11:11:30 +08:00
|
|
|
|
}
|
|
|
|
|
|
},
|
2026-05-20 08:44:31 +08:00
|
|
|
|
|
|
|
|
|
|
// 图例配置
|
|
|
|
|
|
legend: {
|
|
|
|
|
|
data: ['入库水温', '出库水温'], // 图例项
|
|
|
|
|
|
top: 13, // 顶部对齐
|
|
|
|
|
|
left: 'center', // 水平居中
|
|
|
|
|
|
itemWidth: 10, // 图例图标宽度
|
|
|
|
|
|
itemHeight: 10, // 图例图标高度
|
|
|
|
|
|
itemGap: 20, // 图例间距
|
|
|
|
|
|
textStyle: {
|
|
|
|
|
|
fontSize: 13,
|
2026-04-10 11:11:30 +08:00
|
|
|
|
color: '#000000'
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
2026-05-20 08:44:31 +08:00
|
|
|
|
|
|
|
|
|
|
// 网格配置(图表区域边距)
|
|
|
|
|
|
grid: {
|
|
|
|
|
|
left: '10%',
|
|
|
|
|
|
right: '5%',
|
|
|
|
|
|
top: '15%',
|
|
|
|
|
|
bottom: '10%'
|
2026-04-10 11:11:30 +08:00
|
|
|
|
},
|
2026-05-20 08:44:31 +08:00
|
|
|
|
|
|
|
|
|
|
// X轴配置
|
|
|
|
|
|
xAxis: {
|
|
|
|
|
|
type: 'category', // 类目轴
|
|
|
|
|
|
data: dates, // 日期数据
|
|
|
|
|
|
boundaryGap: true, // 两侧留白
|
|
|
|
|
|
interval: xAxisInterval, // 动态刻度间隔,最多显示4个标签
|
|
|
|
|
|
axisLine: {
|
|
|
|
|
|
show: true,
|
|
|
|
|
|
lineStyle: {
|
|
|
|
|
|
color: '#000000' // 轴线颜色
|
|
|
|
|
|
}
|
2026-04-10 11:11:30 +08:00
|
|
|
|
},
|
2026-05-20 08:44:31 +08:00
|
|
|
|
axisTick: {
|
|
|
|
|
|
show: false // 隐藏刻度线
|
|
|
|
|
|
},
|
|
|
|
|
|
axisLabel: {
|
|
|
|
|
|
color: '#000000', // 标签颜色
|
|
|
|
|
|
fontSize: 13, // 字体大小
|
|
|
|
|
|
margin: 10 // 标签与轴线距离
|
|
|
|
|
|
},
|
|
|
|
|
|
splitLine: {
|
|
|
|
|
|
show: true, // 显示分割线
|
|
|
|
|
|
lineStyle: {
|
|
|
|
|
|
color: '#bfbfbf', // 分割线颜色
|
|
|
|
|
|
type: 'solid' // 实线
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-04-10 11:11:30 +08:00
|
|
|
|
},
|
2026-05-20 08:44:31 +08:00
|
|
|
|
|
|
|
|
|
|
// 数据缩放配置(鼠标滚轮缩放)
|
|
|
|
|
|
dataZoom: [
|
|
|
|
|
|
{
|
|
|
|
|
|
type: 'inside', // 内置型数据区域缩放
|
|
|
|
|
|
start: 0, // 起始位置0%
|
|
|
|
|
|
end: 100, // 结束位置100%
|
|
|
|
|
|
minValueSpan: 2, // 最小显示2个数据点
|
|
|
|
|
|
zoomOnMouseWheel: true, // 允许滚轮缩放
|
|
|
|
|
|
moveOnMouseMove: true, // 允许鼠标拖拽平移
|
|
|
|
|
|
moveOnMouseWheel: false, // 滚轮不触发平移
|
|
|
|
|
|
zoomLock: false, // 不锁定缩放比例
|
|
|
|
|
|
throttle: 50 // 节流50ms
|
2026-04-10 11:11:30 +08:00
|
|
|
|
}
|
2026-05-20 08:44:31 +08:00
|
|
|
|
],
|
|
|
|
|
|
|
|
|
|
|
|
// Y轴配置
|
|
|
|
|
|
yAxis: {
|
|
|
|
|
|
type: 'value', // 数值轴
|
|
|
|
|
|
min: yAxisMin, // 动态最小值(数据最小值-0.2)
|
|
|
|
|
|
max: yAxisMaxValue, // 动态最大值(数据最大值+0.2)
|
|
|
|
|
|
scale: true, // 自适应缩放,不强制从0开始
|
|
|
|
|
|
axisLine: {
|
|
|
|
|
|
show: true,
|
|
|
|
|
|
lineStyle: {
|
|
|
|
|
|
color: '#000000',
|
|
|
|
|
|
width: 1
|
|
|
|
|
|
}
|
2026-04-10 11:11:30 +08:00
|
|
|
|
},
|
2026-05-20 08:44:31 +08:00
|
|
|
|
axisTick: {
|
|
|
|
|
|
show: true, // 显示刻度线
|
|
|
|
|
|
lineStyle: {
|
|
|
|
|
|
color: '#000000',
|
|
|
|
|
|
width: 1
|
|
|
|
|
|
},
|
|
|
|
|
|
length: 4 // 刻度线长度
|
2026-04-10 11:11:30 +08:00
|
|
|
|
},
|
2026-05-20 08:44:31 +08:00
|
|
|
|
axisLabel: {
|
|
|
|
|
|
color: '#000000',
|
|
|
|
|
|
fontSize: 12,
|
|
|
|
|
|
formatter: '{value}' // 标签格式
|
2026-04-10 11:11:30 +08:00
|
|
|
|
},
|
2026-05-20 08:44:31 +08:00
|
|
|
|
splitLine: {
|
|
|
|
|
|
show: true, // 显示网格线
|
|
|
|
|
|
lineStyle: {
|
|
|
|
|
|
color: '#bfbfbf',
|
|
|
|
|
|
type: 'solid'
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// 系列数据配置(两条折线)
|
|
|
|
|
|
series: [
|
|
|
|
|
|
{
|
|
|
|
|
|
name: '入库水温', // 系列名称
|
|
|
|
|
|
type: 'line', // 图表类型:折线图
|
|
|
|
|
|
smooth: true, // 平滑曲线
|
|
|
|
|
|
symbol: 'circle', // 数据点形状:圆形
|
|
|
|
|
|
symbolSize: 4, // 数据点大小
|
|
|
|
|
|
connectNulls: true, // 关键:不连接空值,实现曲线断开效果
|
|
|
|
|
|
lineStyle: {
|
|
|
|
|
|
width: 2, // 线宽
|
|
|
|
|
|
color: '#4b79ab' // 线条颜色:蓝色
|
|
|
|
|
|
},
|
|
|
|
|
|
itemStyle: {
|
|
|
|
|
|
color: '#4b79ab' // 数据点颜色
|
|
|
|
|
|
},
|
|
|
|
|
|
data: iwtData // 入库水温数据
|
2026-04-10 11:11:30 +08:00
|
|
|
|
},
|
2026-05-20 08:44:31 +08:00
|
|
|
|
{
|
|
|
|
|
|
name: '出库水温', // 系列名称
|
|
|
|
|
|
type: 'line', // 图表类型:折线图
|
|
|
|
|
|
smooth: true, // 平滑曲线
|
|
|
|
|
|
symbol: 'circle', // 数据点形状:圆形
|
|
|
|
|
|
symbolSize: 4, // 数据点大小
|
|
|
|
|
|
connectNulls: true, // 不连接空值,实现断开效果
|
|
|
|
|
|
lineStyle: {
|
|
|
|
|
|
width: 2, // 线宽
|
|
|
|
|
|
color: '#78c300' // 线条颜色:绿色
|
|
|
|
|
|
},
|
|
|
|
|
|
itemStyle: {
|
|
|
|
|
|
color: '#78c300' // 数据点颜色
|
|
|
|
|
|
},
|
|
|
|
|
|
data: dwtData // 出库水温数据
|
|
|
|
|
|
}
|
|
|
|
|
|
]
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 应用配置到图表实例
|
|
|
|
|
|
chartInstance.setOption(option, true); // true表示完全替换配置
|
2026-05-26 19:30:22 +08:00
|
|
|
|
|
|
|
|
|
|
// 绑定点击事件监听器
|
|
|
|
|
|
chartInstance.off('click'); // 先移除旧的事件监听
|
|
|
|
|
|
chartInstance.on('click', handleChartClick);
|
|
|
|
|
|
|
2026-05-20 08:44:31 +08:00
|
|
|
|
console.log('图表更新成功')
|
|
|
|
|
|
|
|
|
|
|
|
// 强制重绘,确保图表尺寸正确适配容器
|
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
|
if (chartInstance) {
|
|
|
|
|
|
chartInstance.resize(); // 重新计算图表尺寸
|
2026-04-10 11:11:30 +08:00
|
|
|
|
}
|
2026-05-20 08:44:31 +08:00
|
|
|
|
}, 50);
|
2026-04-10 11:11:30 +08:00
|
|
|
|
|
2026-05-20 08:44:31 +08:00
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('更新图表失败:', error)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ==================== 生命周期钩子 ====================
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 组件挂载时执行
|
|
|
|
|
|
* 初始化图表并获取数据
|
|
|
|
|
|
*/
|
|
|
|
|
|
onMounted(() => {
|
|
|
|
|
|
// 延迟100ms初始化,确保DOM渲染完成
|
2026-04-10 11:11:30 +08:00
|
|
|
|
setTimeout(() => {
|
2026-05-20 08:44:31 +08:00
|
|
|
|
if (chartContainer.value) {
|
|
|
|
|
|
// fetchChartData(); // 先获取真实数据
|
|
|
|
|
|
window.addEventListener('resize', handleResize); // 监听窗口大小变化
|
2026-04-10 11:11:30 +08:00
|
|
|
|
}
|
2026-05-20 08:44:31 +08:00
|
|
|
|
}, 100);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 组件卸载时执行
|
|
|
|
|
|
* 清理资源,防止内存泄漏
|
|
|
|
|
|
*/
|
|
|
|
|
|
onUnmounted(() => {
|
|
|
|
|
|
if (chartInstance) {
|
|
|
|
|
|
chartInstance.dispose(); // 销毁图表实例
|
|
|
|
|
|
chartInstance = null;
|
|
|
|
|
|
}
|
|
|
|
|
|
window.removeEventListener('resize', handleResize); // 移除事件监听
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 窗口大小变化处理函数
|
|
|
|
|
|
* 自适应调整图表尺寸
|
|
|
|
|
|
*/
|
|
|
|
|
|
const handleResize = () => {
|
|
|
|
|
|
if (chartInstance) {
|
|
|
|
|
|
chartInstance.resize(); // 重新计算图表尺寸
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// ==================== 事件处理函数 ====================
|
|
|
|
|
|
|
2026-05-26 19:30:22 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 图表数据点点击事件处理
|
|
|
|
|
|
* @param params - ECharts点击事件参数
|
|
|
|
|
|
*/
|
|
|
|
|
|
const handleChartClick = (params: any) => {
|
|
|
|
|
|
console.log('图表点击事件:', params);
|
|
|
|
|
|
|
|
|
|
|
|
// 只处理数据系列的点击
|
|
|
|
|
|
if (params.componentType === 'series') {
|
|
|
|
|
|
const { seriesName, name, value, dataIndex } = params;
|
|
|
|
|
|
|
|
|
|
|
|
// 保存点击数据信息
|
|
|
|
|
|
clickDataInfo.value = {
|
|
|
|
|
|
date: name, // 日期
|
|
|
|
|
|
temperature: value, // 水温值
|
|
|
|
|
|
type: seriesName, // 类型:入库水温/出库水温
|
|
|
|
|
|
dataIndex: dataIndex // 数据索引
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 获取站点名称(从下拉选项中查找)
|
|
|
|
|
|
// console.log('站点名称:', select.value.options, paramsOne.value.value);
|
|
|
|
|
|
const selectedStation = select.value.options
|
|
|
|
|
|
.flatMap((base: any) => base.children || [])
|
|
|
|
|
|
.find((station: any) => station.stcd === paramsOne.value.value);
|
|
|
|
|
|
stationName.value = selectedStation?.title || '未知站点';
|
|
|
|
|
|
|
|
|
|
|
|
// 打开弹框
|
|
|
|
|
|
modalVisible.value = true;
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 弹框关闭处理
|
|
|
|
|
|
*/
|
|
|
|
|
|
const handleModalClose = () => {
|
|
|
|
|
|
modalVisible.value = false;
|
|
|
|
|
|
clickDataInfo.value = null;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2026-05-20 08:44:31 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* SidePanelItem面板变化回调
|
|
|
|
|
|
* 处理下拉框和日期选择器的值变化
|
|
|
|
|
|
* @param data - 包含moreSelect(下拉框值)和datetime(日期范围)的对象
|
|
|
|
|
|
*/
|
|
|
|
|
|
const handlePanelChange1 = (data: any) => {
|
|
|
|
|
|
// TODO: 后续可扩展联动逻辑
|
|
|
|
|
|
if (data.moreSelect) {
|
|
|
|
|
|
paramsOne.value.value = data.moreSelect;
|
|
|
|
|
|
}
|
|
|
|
|
|
paramsOne.value.tm = data.scopeDate
|
|
|
|
|
|
fetchChartData()
|
2026-04-10 11:11:30 +08:00
|
|
|
|
};
|
2026-05-20 08:44:31 +08:00
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 获取站点下拉选项数据
|
|
|
|
|
|
* 根据选中的基地ID查询对应的站点列表
|
|
|
|
|
|
*/
|
|
|
|
|
|
const getselsectData = async () => {
|
|
|
|
|
|
loading.value = true; // 开始加载站点数据
|
2026-05-26 19:30:22 +08:00
|
|
|
|
|
2026-05-20 08:44:31 +08:00
|
|
|
|
try {
|
|
|
|
|
|
let params = {
|
|
|
|
|
|
"filter": {
|
|
|
|
|
|
"logic": "and",
|
|
|
|
|
|
"filters": [
|
|
|
|
|
|
// 如果baseid不是'all',则添加基地过滤条件
|
|
|
|
|
|
baseid.value != 'all' ? {
|
|
|
|
|
|
"field": "baseId",
|
|
|
|
|
|
"operator": "eq",
|
|
|
|
|
|
"dataType": "string",
|
|
|
|
|
|
"value": baseid.value
|
|
|
|
|
|
} : null,
|
|
|
|
|
|
// 站点类型过滤:只查询ENG类型站点
|
|
|
|
|
|
{
|
|
|
|
|
|
"field": "sttpCode",
|
|
|
|
|
|
"operator": "contains",
|
|
|
|
|
|
"dataType": "string",
|
|
|
|
|
|
"value": "ENG"
|
|
|
|
|
|
},
|
|
|
|
|
|
// 只查询有效站点(dtin=1)
|
|
|
|
|
|
{
|
|
|
|
|
|
"field": "dtin",
|
|
|
|
|
|
"operator": "eq",
|
|
|
|
|
|
"dataType": "string",
|
|
|
|
|
|
"value": 1
|
|
|
|
|
|
}
|
|
|
|
|
|
].filter(Boolean) // 过滤掉null值
|
|
|
|
|
|
},
|
|
|
|
|
|
"sort": [
|
|
|
|
|
|
// 如果是'all',按基地排序;否则不排序
|
|
|
|
|
|
baseid.value == 'all' ? {
|
|
|
|
|
|
"field": "baseStepSort",
|
|
|
|
|
|
"dir": "asc"
|
|
|
|
|
|
} : null,
|
|
|
|
|
|
{
|
|
|
|
|
|
"field": "rvcdStepSort",
|
|
|
|
|
|
"dir": "asc"
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
"field": "siteStepSort",
|
|
|
|
|
|
"dir": "asc"
|
|
|
|
|
|
}
|
|
|
|
|
|
].filter(Boolean), // 过滤掉null值
|
|
|
|
|
|
"select": [
|
|
|
|
|
|
"stcd", // 站点编码
|
|
|
|
|
|
"stnm", // 站点名称
|
|
|
|
|
|
"lgtd", // 经度
|
|
|
|
|
|
"lttd", // 纬度
|
|
|
|
|
|
"siteStepSort", // 站点排序
|
|
|
|
|
|
"baseId", // 基地ID
|
|
|
|
|
|
"baseName" // 基地名称
|
|
|
|
|
|
]
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 调用API获取站点数据
|
|
|
|
|
|
const res = await getVmsstbprpt(params)
|
|
|
|
|
|
console.log('res', res)
|
|
|
|
|
|
const data = res?.data?.data || res?.data
|
|
|
|
|
|
const obj: any = {}
|
|
|
|
|
|
|
|
|
|
|
|
if (data) {
|
|
|
|
|
|
// 将扁平数据转换为树形结构(按基地分组)
|
|
|
|
|
|
data.map((e) => {
|
|
|
|
|
|
if (obj[e.baseId]) {
|
|
|
|
|
|
// 如果基地已存在,添加子节点
|
|
|
|
|
|
obj[e.baseId] = { ...obj[e.baseId], children: [...obj[e.baseId].children, { ...e, value: e.stcd, title: e.stnm }] }
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 创建新的基地节点
|
|
|
|
|
|
obj[e.baseId] = { value: e.baseId, title: e.baseName, selectable: false, key: e.baseId, children: [{ ...e, value: e.stcd, title: e.stnm }] }
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
// 转换为数组并按jiDiList顺序排序
|
|
|
|
|
|
let treeData: any = []
|
|
|
|
|
|
const dataMapNameArr: any = Object.entries(obj).map(([, v]) => v)
|
|
|
|
|
|
treeData = dataMapNameArr?.sort((a: any, b: any) => {
|
2026-06-09 11:17:07 +08:00
|
|
|
|
const indexA = JidiSelectEventStore.jidiData.findIndex(item => item.wbsCode === a.value);
|
|
|
|
|
|
const indexB = JidiSelectEventStore.jidiData.findIndex(item => item.wbsCode === b.value);
|
2026-05-20 08:44:31 +08:00
|
|
|
|
return indexA - indexB;
|
|
|
|
|
|
})
|
|
|
|
|
|
console.log('treeData', treeData)
|
|
|
|
|
|
|
|
|
|
|
|
// 更新下拉框选项
|
|
|
|
|
|
select.value.options = treeData
|
|
|
|
|
|
|
|
|
|
|
|
// 设置默认选中值
|
|
|
|
|
|
if (baseid.value == 'all') {
|
|
|
|
|
|
select.value.value = '008640203800000001' // 默认选中第一个站点
|
|
|
|
|
|
} else {
|
|
|
|
|
|
select.value.value = treeData?.[0]?.children[0]?.value // 选中当前基地的第一个站点
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('获取站点数据失败:', error);
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
loading.value = false; // 关闭loading
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ==================== Watch监听器 ====================
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 监听基地选择变化
|
|
|
|
|
|
* 当用户切换基地时,重新加载站点下拉选项
|
|
|
|
|
|
*/
|
|
|
|
|
|
watch(
|
|
|
|
|
|
() => JidiSelectEventStore.selectedItem, // 监听Store中的选中项
|
|
|
|
|
|
(newVal) => {
|
|
|
|
|
|
baseid.value = newVal.wbsCode; // 更新基地ID
|
|
|
|
|
|
getselsectData() // 重新获取站点数据
|
|
|
|
|
|
},
|
|
|
|
|
|
{ deep: true, immediate: true } // 深度监听,立即执行
|
|
|
|
|
|
);
|
2026-04-10 11:11:30 +08:00
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
<style lang="scss" scoped>
|
2026-05-20 08:44:31 +08:00
|
|
|
|
/* 图表容器样式 */
|
2026-04-10 11:11:30 +08:00
|
|
|
|
.chart-container {
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
height: 252px;
|
2026-05-26 19:30:22 +08:00
|
|
|
|
display: flex;
|
|
|
|
|
|
justify-content: center;
|
|
|
|
|
|
align-items: center;
|
2026-05-20 08:44:31 +08:00
|
|
|
|
/* 固定高度252px */
|
|
|
|
|
|
}
|
2026-05-26 19:30:22 +08:00
|
|
|
|
|
|
|
|
|
|
:deep(.ant-spin-nested-loading) {
|
|
|
|
|
|
height: 252px !important;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* 弹框内容样式 */
|
|
|
|
|
|
.modal-content {
|
|
|
|
|
|
min-height: 100px;
|
|
|
|
|
|
padding: 16px 0;
|
2026-04-10 11:11:30 +08:00
|
|
|
|
}
|
|
|
|
|
|
</style>
|