2026-04-24 17:46:41 +08:00
|
|
|
|
<!-- SidePanelItem.vue -->
|
|
|
|
|
|
<template>
|
2026-09-02 16:19:28 +08:00
|
|
|
|
<SidePanelItem
|
|
|
|
|
|
title="地表水水质达标率"
|
|
|
|
|
|
:select="select"
|
|
|
|
|
|
:datetimePicker="datetimePicker"
|
|
|
|
|
|
@update-values="handlePanelChange1"
|
|
|
|
|
|
:style="
|
|
|
|
|
|
chartData.length === 0 && !loading && baseid != 'all'
|
|
|
|
|
|
? 'height: 300px'
|
|
|
|
|
|
: 'height: 650px'
|
|
|
|
|
|
"
|
|
|
|
|
|
>
|
|
|
|
|
|
<div class="body_item">
|
|
|
|
|
|
<div v-if="baseid == 'all'" class="tabs_all">
|
|
|
|
|
|
<div
|
|
|
|
|
|
:class="tabs == '0' ? 'zhong_tabs' : 'no_tabs'"
|
|
|
|
|
|
@click="handleTabChange(0)"
|
|
|
|
|
|
>
|
|
|
|
|
|
自建水质站
|
2026-04-24 17:46:41 +08:00
|
|
|
|
</div>
|
2026-09-02 16:19:28 +08:00
|
|
|
|
<div
|
|
|
|
|
|
:class="tabs == '1' ? 'zhong_tabs' : 'no_tabs'"
|
|
|
|
|
|
@click="handleTabChange(1)"
|
|
|
|
|
|
>
|
|
|
|
|
|
国家水质站
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div v-show="tabs == '0' || tabs == '1'" class="tabs_body">
|
|
|
|
|
|
<a-spin :spinning="loading">
|
|
|
|
|
|
<!-- 始终渲染图表容器,确保有固定宽高 -->
|
|
|
|
|
|
<div ref="chartRef" class="chart-container"></div>
|
|
|
|
|
|
<!-- 无数据时显示Empty,但不影响容器尺寸 -->
|
|
|
|
|
|
<div
|
|
|
|
|
|
v-if="chartData.length === 0 && !loading"
|
|
|
|
|
|
class="empty-overlay"
|
|
|
|
|
|
:style="
|
|
|
|
|
|
chartData.length === 0 && !loading && baseid != 'all'
|
|
|
|
|
|
? 'height: 300px'
|
|
|
|
|
|
: 'height: 650px'
|
|
|
|
|
|
"
|
|
|
|
|
|
>
|
|
|
|
|
|
<a-empty description="暂无数据" />
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</a-spin>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</SidePanelItem>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 环境质量满足度弹框 -->
|
|
|
|
|
|
<a-modal
|
|
|
|
|
|
v-model:open="modalVisible"
|
|
|
|
|
|
title="环境质量满足度"
|
|
|
|
|
|
width="1536px"
|
|
|
|
|
|
:footer="null"
|
|
|
|
|
|
centered
|
|
|
|
|
|
>
|
|
|
|
|
|
<EnvironmentalQualityTwoLayers
|
|
|
|
|
|
:datas="
|
|
|
|
|
|
baseid == 'all' ? JidiSelectEventStore.jidiData : stationListForModal
|
|
|
|
|
|
"
|
|
|
|
|
|
:type="select.value"
|
|
|
|
|
|
:dateValue="datetimePicker.value"
|
|
|
|
|
|
:dataDimensionVal="clickedBarData?.selectedId || baseid"
|
|
|
|
|
|
:buildType="tabs"
|
|
|
|
|
|
:typeOne="fieldOne"
|
|
|
|
|
|
:baseid="baseid"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</a-modal>
|
2026-04-24 17:46:41 +08:00
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<script lang="ts" setup>
|
2026-09-02 16:19:28 +08:00
|
|
|
|
import {
|
|
|
|
|
|
ref,
|
|
|
|
|
|
onMounted,
|
|
|
|
|
|
onUnmounted,
|
|
|
|
|
|
watch,
|
|
|
|
|
|
nextTick,
|
|
|
|
|
|
computed,
|
|
|
|
|
|
inject,
|
|
|
|
|
|
defineOptions
|
|
|
|
|
|
} from 'vue';
|
2026-04-24 17:46:41 +08:00
|
|
|
|
import * as echarts from 'echarts';
|
|
|
|
|
|
import SidePanelItem from '@/components/SidePanelItem/index.vue';
|
2026-09-02 16:19:28 +08:00
|
|
|
|
import { wqGetKendoListCust } from '@/api/sz';
|
|
|
|
|
|
import { useJidiSelectEventStore } from '@/store/modules/jidiSelectEvent';
|
|
|
|
|
|
import EnvironmentalQualityTwoLayers from './TwoLayers/EnvironmentalQualityTwoLayers.vue';
|
2026-07-07 10:33:29 +08:00
|
|
|
|
import { useDraggable } from '@/utils/drag';
|
|
|
|
|
|
|
2026-04-24 17:46:41 +08:00
|
|
|
|
// 定义组件名(便于调试和递归)
|
|
|
|
|
|
defineOptions({
|
2026-09-02 16:19:28 +08:00
|
|
|
|
name: 'ShuiZhiDaBiaoQingKuang'
|
2026-04-24 17:46:41 +08:00
|
|
|
|
});
|
2026-06-01 08:37:38 +08:00
|
|
|
|
const JidiSelectEventStore = useJidiSelectEventStore();
|
2026-06-02 14:52:44 +08:00
|
|
|
|
const tabs: any = ref('0');
|
2026-04-24 17:46:41 +08:00
|
|
|
|
const chartRef = ref<HTMLElement | null>(null);
|
|
|
|
|
|
let chartInstance: echarts.ECharts | null = null;
|
2026-06-01 08:37:38 +08:00
|
|
|
|
const loading = ref(false);
|
|
|
|
|
|
|
2026-06-02 14:52:44 +08:00
|
|
|
|
// 响式图表数据
|
2026-09-02 16:19:28 +08:00
|
|
|
|
const chartData = ref<
|
|
|
|
|
|
Array<{ name: string; current: number | null; lastYear: number | null }>
|
|
|
|
|
|
>([]);
|
2026-06-01 08:37:38 +08:00
|
|
|
|
|
2026-06-02 14:52:44 +08:00
|
|
|
|
// 新增:存储当前基地下的电站列表(用于弹框传递)
|
2026-09-02 16:19:28 +08:00
|
|
|
|
const stationListForModal = ref<Array<{ baseid: string; basename: string }>>(
|
|
|
|
|
|
[]
|
|
|
|
|
|
);
|
2026-06-02 14:52:44 +08:00
|
|
|
|
|
|
|
|
|
|
// 新增:存储图表数据索引与 baseid/rstcd 的映射关系
|
|
|
|
|
|
const chartDataIdMap = ref<Array<string>>([]);
|
|
|
|
|
|
|
2026-06-01 08:37:38 +08:00
|
|
|
|
const select = ref({
|
2026-09-02 16:19:28 +08:00
|
|
|
|
show: true,
|
|
|
|
|
|
value: 'month',
|
|
|
|
|
|
options: [
|
|
|
|
|
|
{ value: 'month', label: '月度' },
|
|
|
|
|
|
{ value: 'year', label: '年度' }
|
|
|
|
|
|
],
|
|
|
|
|
|
picker: undefined,
|
|
|
|
|
|
format: undefined
|
2026-06-01 08:37:38 +08:00
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 日期选择器配置
|
|
|
|
|
|
const datetimePicker = ref({
|
2026-09-02 16:19:28 +08:00
|
|
|
|
show: true,
|
|
|
|
|
|
value: (() => {
|
|
|
|
|
|
const now = new Date();
|
|
|
|
|
|
const year = now.getFullYear();
|
|
|
|
|
|
const month = String(now.getMonth() + 1).padStart(2, '0');
|
|
|
|
|
|
return `${year}-${month}`;
|
|
|
|
|
|
})(),
|
|
|
|
|
|
format: 'YYYY-MM',
|
|
|
|
|
|
picker: 'month' as const,
|
|
|
|
|
|
options: []
|
2026-06-01 08:37:38 +08:00
|
|
|
|
});
|
2026-09-02 16:19:28 +08:00
|
|
|
|
const baseid = ref('');
|
|
|
|
|
|
const fieldOne = ref('');
|
2026-06-01 08:37:38 +08:00
|
|
|
|
// 解析API返回的嵌套数据(支持两种结构)
|
2026-09-02 16:19:28 +08:00
|
|
|
|
const parseApiResponse = (
|
|
|
|
|
|
items: any[],
|
|
|
|
|
|
currentValueForApi: string,
|
|
|
|
|
|
lastYearValue: string
|
|
|
|
|
|
) => {
|
|
|
|
|
|
const result: Array<{
|
|
|
|
|
|
name: string;
|
|
|
|
|
|
current: number | null;
|
|
|
|
|
|
lastYear: number | null;
|
|
|
|
|
|
}> = [];
|
|
|
|
|
|
fieldOne.value = '';
|
|
|
|
|
|
// 新增:清空 ID 映射数组
|
|
|
|
|
|
chartDataIdMap.value = [];
|
|
|
|
|
|
|
|
|
|
|
|
// 新增:提取电站列表(仅当 baseid != 'all' 时)
|
|
|
|
|
|
const stations: Array<{ baseid: string; basename: string }> = [];
|
|
|
|
|
|
|
|
|
|
|
|
console.log('=== parseApiResponse 开始解析 ===');
|
|
|
|
|
|
console.log('currentValueForApi:', currentValueForApi);
|
|
|
|
|
|
console.log('lastYearValue:', lastYearValue);
|
|
|
|
|
|
console.log('items数量:', items?.length);
|
|
|
|
|
|
|
|
|
|
|
|
items.forEach((group, groupIndex) => {
|
|
|
|
|
|
console.log(`\n--- 处理第${groupIndex + 1}个group ---`);
|
|
|
|
|
|
console.log('group.key:', group.key);
|
|
|
|
|
|
console.log('group.items数量:', group.items?.length);
|
|
|
|
|
|
|
|
|
|
|
|
group.items?.forEach((mainGroup, mainIndex) => {
|
|
|
|
|
|
console.log(` → 第${mainIndex + 1}个mainGroup:`);
|
|
|
|
|
|
console.log(' field:', mainGroup.field);
|
|
|
|
|
|
console.log(' key:', mainGroup.key);
|
|
|
|
|
|
|
|
|
|
|
|
// 判断是哪种结构:检查第二层的field字段
|
|
|
|
|
|
const field = mainGroup.field;
|
|
|
|
|
|
|
|
|
|
|
|
if (field === 'ennm' || field === 'baseId') {
|
|
|
|
|
|
// 提取中文名称:从mainGroup.key获取(站点名或基地ID)
|
|
|
|
|
|
let displayName = '';
|
|
|
|
|
|
|
|
|
|
|
|
// mainGroup.key 可能是站点名(如"两河口")或基地ID(如"02")
|
|
|
|
|
|
if (mainGroup.key && typeof mainGroup.key === 'string') {
|
|
|
|
|
|
displayName = mainGroup.key;
|
|
|
|
|
|
console.log(' 从mainGroup.key获取名称:', displayName);
|
|
|
|
|
|
}
|
2026-06-02 14:52:44 +08:00
|
|
|
|
|
2026-09-02 16:19:28 +08:00
|
|
|
|
// 如果mainGroup.key不是有效的中文名称,尝试从subGroup.key获取
|
|
|
|
|
|
if (!displayName || !/[\u4e00-\u9fa5]/.test(displayName)) {
|
|
|
|
|
|
const subGroup = mainGroup.items?.[0];
|
|
|
|
|
|
console.log(' subGroup.key:', subGroup?.key);
|
|
|
|
|
|
if (subGroup && subGroup.key && typeof subGroup.key === 'string') {
|
|
|
|
|
|
displayName = subGroup.key; // 这里应该是中文名(如"雅砻江干流")
|
|
|
|
|
|
console.log(' 从subGroup.key获取名称:', displayName);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-06-01 08:37:38 +08:00
|
|
|
|
|
2026-09-02 16:19:28 +08:00
|
|
|
|
// 如果还是没有,尝试从最内层数据获取
|
|
|
|
|
|
if (!displayName) {
|
|
|
|
|
|
const subGroup = mainGroup.items?.[0];
|
|
|
|
|
|
const firstMonthGroup = subGroup?.items?.[0];
|
|
|
|
|
|
const dataItem = firstMonthGroup?.items?.[0];
|
|
|
|
|
|
if (dataItem) {
|
|
|
|
|
|
if (field === 'ennm') {
|
|
|
|
|
|
displayName = dataItem.ENNM || '';
|
|
|
|
|
|
} else if (field === 'baseId') {
|
|
|
|
|
|
displayName = dataItem.BASENAME || '';
|
2026-06-01 08:37:38 +08:00
|
|
|
|
}
|
2026-09-02 16:19:28 +08:00
|
|
|
|
console.log(' 从最内层数据获取名称:', displayName);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-06-01 08:37:38 +08:00
|
|
|
|
|
2026-09-02 16:19:28 +08:00
|
|
|
|
if (!displayName) {
|
|
|
|
|
|
console.log(' ⚠️ 跳过:没有名称');
|
|
|
|
|
|
return; // 跳过没有名称的数据
|
|
|
|
|
|
}
|
2026-06-02 14:52:44 +08:00
|
|
|
|
|
2026-09-02 16:19:28 +08:00
|
|
|
|
let currentPeriodValue: number | null = null;
|
|
|
|
|
|
let lastYearPeriodValue: number | null = null;
|
|
|
|
|
|
|
|
|
|
|
|
// 遍历时间分组:需要深入到第3层
|
|
|
|
|
|
const subGroup = mainGroup.items?.[0];
|
|
|
|
|
|
console.log(' subGroup.items数量:', subGroup?.items?.length);
|
|
|
|
|
|
|
|
|
|
|
|
subGroup?.items?.forEach(monthGroup => {
|
|
|
|
|
|
const periodKey = monthGroup.key; // "2026-05" 或 "2025-05" 或 "2026" 或 "2025"
|
|
|
|
|
|
const dataItem = monthGroup.items?.[0];
|
|
|
|
|
|
|
|
|
|
|
|
if (dataItem) {
|
|
|
|
|
|
// 兼容月度数据(COUNT_DRMONTH/wqRate)和年度数据(COUNT_DRYEAR)
|
|
|
|
|
|
const value =
|
|
|
|
|
|
dataItem.COUNT_DRMONTH ??
|
|
|
|
|
|
dataItem.COUNT_DRYEAR ??
|
|
|
|
|
|
dataItem.wqRate;
|
|
|
|
|
|
console.log(` 时间:${periodKey}, 值:${value}`);
|
|
|
|
|
|
|
|
|
|
|
|
// 判断是当前期还是去年同期
|
|
|
|
|
|
if (periodKey === currentValueForApi) {
|
|
|
|
|
|
currentPeriodValue = value != null ? Number(value) : null;
|
|
|
|
|
|
console.log(` ✓ 当前期值: ${currentPeriodValue}`);
|
|
|
|
|
|
} else if (periodKey === lastYearValue) {
|
|
|
|
|
|
lastYearPeriodValue = value != null ? Number(value) : null;
|
|
|
|
|
|
console.log(` ✓ 去年同期值: ${lastYearPeriodValue}`);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
2026-06-01 08:37:38 +08:00
|
|
|
|
|
2026-09-02 16:19:28 +08:00
|
|
|
|
// 添加换行符:在"干流"前添加\n
|
|
|
|
|
|
const finalDisplayName = displayName.replace(/干流$/, '\n干流');
|
|
|
|
|
|
|
|
|
|
|
|
console.log(
|
|
|
|
|
|
` → 最终数据: {name: "${finalDisplayName}", current: ${currentPeriodValue}, lastYear: ${lastYearPeriodValue}}`
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
// 新增:记录 ID 映射关系
|
|
|
|
|
|
if (field === 'baseId') {
|
|
|
|
|
|
fieldOne.value = 'all';
|
|
|
|
|
|
// baseid == 'all' 时,记录基地 ID
|
|
|
|
|
|
chartDataIdMap.value.push(mainGroup.key); // "02", "03" 等
|
|
|
|
|
|
console.log(' 记录基地ID:', mainGroup.key);
|
|
|
|
|
|
} else if (field === 'ennm') {
|
|
|
|
|
|
fieldOne.value = 'other';
|
|
|
|
|
|
// baseid != 'all' 时,记录电站编码
|
|
|
|
|
|
const stationCode = subGroup?.key; // "008660306300000001"
|
|
|
|
|
|
chartDataIdMap.value.push(stationCode || '');
|
|
|
|
|
|
console.log(' 记录电站编码:', stationCode);
|
|
|
|
|
|
|
|
|
|
|
|
// 同时提取电站列表
|
|
|
|
|
|
if (displayName && stationCode) {
|
|
|
|
|
|
stations.push({
|
|
|
|
|
|
baseid: stationCode,
|
|
|
|
|
|
basename: displayName
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
result.push({
|
|
|
|
|
|
name: finalDisplayName,
|
|
|
|
|
|
current: currentPeriodValue,
|
|
|
|
|
|
lastYear: lastYearPeriodValue
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 新增:如果提取到了电站列表,更新响应式变量
|
|
|
|
|
|
if (stations.length > 0) {
|
|
|
|
|
|
stationListForModal.value = stations;
|
|
|
|
|
|
console.log('提取到的电站列表:', stations);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
stationListForModal.value = [];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
console.log('ID 映射关系:', chartDataIdMap.value);
|
|
|
|
|
|
console.log('\n=== parseApiResponse 解析完成 ===');
|
|
|
|
|
|
console.log('result数量:', result.length);
|
|
|
|
|
|
console.log('result:', result);
|
|
|
|
|
|
|
|
|
|
|
|
return result;
|
2026-04-24 17:46:41 +08:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 处理tab切换
|
|
|
|
|
|
const handleTabChange = (tab: number) => {
|
2026-09-02 16:19:28 +08:00
|
|
|
|
tabs.value = tab;
|
|
|
|
|
|
getEchartsData();
|
2026-04-24 17:46:41 +08:00
|
|
|
|
};
|
|
|
|
|
|
|
2026-06-01 08:37:38 +08:00
|
|
|
|
//监听tabs变化,处理图表刷新
|
2026-09-02 16:19:28 +08:00
|
|
|
|
watch(tabs, newVal => {
|
|
|
|
|
|
if (newVal === '0' || newVal === '1') {
|
|
|
|
|
|
nextTick(() => {
|
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
|
getEchartsData();
|
|
|
|
|
|
}, 50);
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
2026-04-24 17:46:41 +08:00
|
|
|
|
});
|
2026-07-31 11:13:49 +08:00
|
|
|
|
// 监听电站变化(优先使用父组件 provide 的电站上下文,兜底 jidiStore)
|
|
|
|
|
|
const injectedStation = inject<any>('dianZhanStation', null);
|
2026-09-02 16:19:28 +08:00
|
|
|
|
const stationSource = computed(
|
|
|
|
|
|
() => injectedStation?.value || JidiSelectEventStore.selectedItem
|
|
|
|
|
|
);
|
2026-07-31 11:13:49 +08:00
|
|
|
|
|
2026-06-01 08:37:38 +08:00
|
|
|
|
watch(
|
2026-09-02 16:19:28 +08:00
|
|
|
|
() => stationSource.value,
|
|
|
|
|
|
newVal => {
|
|
|
|
|
|
if (newVal && newVal.wbsCode) {
|
|
|
|
|
|
baseid.value = newVal.wbsCode;
|
|
|
|
|
|
getEchartsData();
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
{ deep: true }
|
2026-06-01 08:37:38 +08:00
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
// 初始化图表实例(仅创建实例,不设置option)
|
|
|
|
|
|
const initChart = (): Promise<void> => {
|
2026-09-02 16:19:28 +08:00
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
|
|
if (!chartRef.value) {
|
|
|
|
|
|
console.error('图表容器ref不存在');
|
|
|
|
|
|
reject(new Error('图表容器不存在'));
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
2026-06-01 08:37:38 +08:00
|
|
|
|
|
2026-09-02 16:19:28 +08:00
|
|
|
|
if (chartInstance) {
|
|
|
|
|
|
resolve();
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
2026-06-01 08:37:38 +08:00
|
|
|
|
|
2026-09-02 16:19:28 +08:00
|
|
|
|
const domElement = chartRef.value;
|
|
|
|
|
|
|
|
|
|
|
|
// 检查 DOM 尺寸
|
|
|
|
|
|
if (domElement.clientWidth === 0 || domElement.clientHeight === 0) {
|
|
|
|
|
|
console.warn('图表容器尺寸为0,等待DOM布局完成...', {
|
|
|
|
|
|
clientWidth: domElement.clientWidth,
|
|
|
|
|
|
clientHeight: domElement.clientHeight,
|
|
|
|
|
|
offsetWidth: domElement.offsetWidth,
|
|
|
|
|
|
offsetHeight: domElement.offsetHeight
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 使用多次重试机制
|
|
|
|
|
|
let retryCount = 0;
|
|
|
|
|
|
const maxRetries = 10;
|
|
|
|
|
|
|
|
|
|
|
|
const tryInit = () => {
|
|
|
|
|
|
retryCount++;
|
|
|
|
|
|
|
|
|
|
|
|
if (domElement.clientWidth > 0 && domElement.clientHeight > 0) {
|
|
|
|
|
|
chartInstance = echarts.init(domElement);
|
|
|
|
|
|
console.log('图表实例已创建(第' + retryCount + '次尝试)');
|
|
|
|
|
|
resolve();
|
|
|
|
|
|
} else if (retryCount < maxRetries) {
|
|
|
|
|
|
console.log(`第${retryCount}次尝试,容器尺寸仍为0,继续等待...`);
|
|
|
|
|
|
requestAnimationFrame(tryInit);
|
2026-06-01 08:37:38 +08:00
|
|
|
|
} else {
|
2026-09-02 16:19:28 +08:00
|
|
|
|
console.error('图表容器尺寸仍为0,已达到最大重试次数', {
|
|
|
|
|
|
clientWidth: domElement.clientWidth,
|
|
|
|
|
|
clientHeight: domElement.clientHeight
|
|
|
|
|
|
});
|
|
|
|
|
|
reject(new Error('图表容器尺寸为0'));
|
2026-06-01 08:37:38 +08:00
|
|
|
|
}
|
2026-09-02 16:19:28 +08:00
|
|
|
|
};
|
2026-06-01 08:37:38 +08:00
|
|
|
|
|
2026-09-02 16:19:28 +08:00
|
|
|
|
// 开始重试
|
|
|
|
|
|
requestAnimationFrame(tryInit);
|
2026-06-01 08:37:38 +08:00
|
|
|
|
} else {
|
2026-09-02 16:19:28 +08:00
|
|
|
|
// 尺寸正常,直接初始化
|
|
|
|
|
|
chartInstance = echarts.init(domElement);
|
|
|
|
|
|
console.log('图表实例已创建');
|
|
|
|
|
|
resolve();
|
2026-06-01 08:37:38 +08:00
|
|
|
|
}
|
2026-09-02 16:19:28 +08:00
|
|
|
|
});
|
|
|
|
|
|
};
|
2026-06-01 08:37:38 +08:00
|
|
|
|
|
2026-09-02 16:19:28 +08:00
|
|
|
|
//获取图表数据
|
|
|
|
|
|
const getEchartsData = async () => {
|
|
|
|
|
|
// 1. 格式验证
|
|
|
|
|
|
const currentValue = datetimePicker.value.value;
|
|
|
|
|
|
//
|
|
|
|
|
|
if (select.value.value === 'year') {
|
|
|
|
|
|
// 年度模式:验证是否为 YYYY 格式(4位数字)
|
|
|
|
|
|
if (!/^\d{4}$/.test(currentValue)) {
|
|
|
|
|
|
console.warn('年度模式下日期格式不正确,应为 YYYY 格式');
|
|
|
|
|
|
return;
|
2026-06-01 08:37:38 +08:00
|
|
|
|
}
|
2026-09-02 16:19:28 +08:00
|
|
|
|
} else if (select.value.value === 'month') {
|
|
|
|
|
|
// 月度模式:验证是否为 YYYY-MM 格式
|
|
|
|
|
|
if (!/^\d{4}-\d{2}$/.test(currentValue)) {
|
|
|
|
|
|
console.warn('月度模式下日期格式不正确,应为 YYYY-MM 格式');
|
|
|
|
|
|
return;
|
2026-06-02 14:52:44 +08:00
|
|
|
|
}
|
2026-09-02 16:19:28 +08:00
|
|
|
|
} else {
|
|
|
|
|
|
console.warn('未知的选择器类型');
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 2. 计算当前值和上一年的值
|
|
|
|
|
|
let currentValueForApi: string; // 第一个值:当前选择
|
|
|
|
|
|
let lastYearValue: string; // 第二个值:上一年同期
|
|
|
|
|
|
|
|
|
|
|
|
if (select.value.value === 'year') {
|
|
|
|
|
|
// 年模式
|
|
|
|
|
|
currentValueForApi = currentValue; // "2025"
|
|
|
|
|
|
lastYearValue = String(Number(currentValue) - 1); // "2024"
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 月模式
|
|
|
|
|
|
currentValueForApi = currentValue; // "2025-03"
|
|
|
|
|
|
|
|
|
|
|
|
// 提取年份和月份
|
|
|
|
|
|
const [year, month] = currentValue.split('-');
|
|
|
|
|
|
// 年份减1,月份保持不变
|
|
|
|
|
|
const lastYear = String(Number(year) - 1);
|
|
|
|
|
|
lastYearValue = `${lastYear}-${month}`; // "2024-03"
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 3. 构建请求参数
|
|
|
|
|
|
console.log(baseid.value);
|
|
|
|
|
|
if (!baseid.value) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
//
|
|
|
|
|
|
let params = {
|
|
|
|
|
|
filter: {
|
|
|
|
|
|
logic: 'and',
|
|
|
|
|
|
filters: [
|
|
|
|
|
|
baseid.value != 'all'
|
|
|
|
|
|
? {
|
|
|
|
|
|
field: 'baseId',
|
|
|
|
|
|
operator: 'eq',
|
|
|
|
|
|
value: baseid.value
|
|
|
|
|
|
}
|
|
|
|
|
|
: null,
|
|
|
|
|
|
{
|
|
|
|
|
|
field: 'type',
|
|
|
|
|
|
operator: 'eq',
|
|
|
|
|
|
value: select.value.value == 'year' ? 'YEAR' : 'MON'
|
2026-06-01 08:37:38 +08:00
|
|
|
|
},
|
2026-09-02 16:19:28 +08:00
|
|
|
|
baseid.value == 'all'
|
|
|
|
|
|
? {
|
|
|
|
|
|
field: 'dtinType',
|
|
|
|
|
|
operator: 'eq',
|
|
|
|
|
|
value: tabs.value
|
|
|
|
|
|
}
|
|
|
|
|
|
: {
|
|
|
|
|
|
field: 'rstcd',
|
|
|
|
|
|
operator: 'isnotnull'
|
2026-06-01 08:37:38 +08:00
|
|
|
|
},
|
2026-09-02 16:19:28 +08:00
|
|
|
|
,
|
|
|
|
|
|
{
|
|
|
|
|
|
logic: 'or',
|
|
|
|
|
|
filters: [
|
2026-06-01 08:37:38 +08:00
|
|
|
|
{
|
2026-09-02 16:19:28 +08:00
|
|
|
|
field: select.value.value == 'year' ? 'drYear' : 'drMonth',
|
|
|
|
|
|
operator: 'eq',
|
|
|
|
|
|
value: currentValueForApi, // 当前值(年或年月)
|
|
|
|
|
|
dataType: 'string'
|
|
|
|
|
|
},
|
2026-06-01 08:37:38 +08:00
|
|
|
|
{
|
2026-09-02 16:19:28 +08:00
|
|
|
|
field: select.value.value == 'year' ? 'drYear' : 'drMonth',
|
|
|
|
|
|
operator: 'eq',
|
|
|
|
|
|
value: lastYearValue, // 上一年同期
|
|
|
|
|
|
dataType: 'string'
|
2026-06-01 08:37:38 +08:00
|
|
|
|
}
|
2026-09-02 16:19:28 +08:00
|
|
|
|
]
|
2026-06-01 08:37:38 +08:00
|
|
|
|
}
|
2026-09-02 16:19:28 +08:00
|
|
|
|
].filter(Boolean)
|
|
|
|
|
|
},
|
|
|
|
|
|
group: [
|
|
|
|
|
|
baseid.value == 'all'
|
|
|
|
|
|
? {
|
|
|
|
|
|
dir: 'des',
|
|
|
|
|
|
field: 'baseStepSort'
|
|
|
|
|
|
}
|
|
|
|
|
|
: {
|
|
|
|
|
|
dir: 'asc',
|
|
|
|
|
|
field: 'rstcdStepSort'
|
|
|
|
|
|
},
|
|
|
|
|
|
,
|
|
|
|
|
|
baseid.value == 'all'
|
|
|
|
|
|
? {
|
|
|
|
|
|
dir: 'des',
|
|
|
|
|
|
field: 'baseId'
|
|
|
|
|
|
}
|
|
|
|
|
|
: {
|
|
|
|
|
|
dir: 'asc',
|
|
|
|
|
|
field: 'ennm'
|
|
|
|
|
|
},
|
|
|
|
|
|
baseid.value == 'all'
|
|
|
|
|
|
? {
|
|
|
|
|
|
dir: 'des',
|
|
|
|
|
|
field: 'baseName'
|
|
|
|
|
|
}
|
|
|
|
|
|
: {
|
|
|
|
|
|
dir: 'des',
|
|
|
|
|
|
field: 'rstcd'
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
dir: 'desc',
|
|
|
|
|
|
field: select.value.value == 'year' ? 'drYear' : 'drMonth'
|
|
|
|
|
|
}
|
|
|
|
|
|
].filter(Boolean),
|
|
|
|
|
|
groupResultFlat: false,
|
|
|
|
|
|
aggregate: [
|
|
|
|
|
|
{
|
|
|
|
|
|
aggregate: 'sum',
|
|
|
|
|
|
field: 'wqRate'
|
|
|
|
|
|
}
|
|
|
|
|
|
]
|
|
|
|
|
|
};
|
|
|
|
|
|
console.log('请求参数:', params);
|
|
|
|
|
|
//
|
|
|
|
|
|
// 4. 调用API并处理数据
|
|
|
|
|
|
loading.value = true;
|
|
|
|
|
|
try {
|
|
|
|
|
|
const res = await wqGetKendoListCust(params);
|
|
|
|
|
|
|
|
|
|
|
|
console.log('=== API返回数据调试 ===');
|
|
|
|
|
|
console.log('res:', res);
|
|
|
|
|
|
console.log('res.data:', res?.data);
|
|
|
|
|
|
console.log('res.data.data:', res?.data?.data);
|
|
|
|
|
|
console.log('currentValueForApi:', currentValueForApi);
|
|
|
|
|
|
console.log('lastYearValue:', lastYearValue);
|
2026-06-01 08:37:38 +08:00
|
|
|
|
|
2026-09-02 16:19:28 +08:00
|
|
|
|
if (!res?.data?.data) {
|
|
|
|
|
|
console.warn('数据获取失败或数据为空');
|
|
|
|
|
|
chartData.value = [];
|
|
|
|
|
|
// 清空图表中原有的数据
|
|
|
|
|
|
if (chartInstance) {
|
|
|
|
|
|
chartInstance.clear();
|
|
|
|
|
|
console.log('图表已清空(数据获取失败)');
|
|
|
|
|
|
}
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
2026-06-01 08:37:38 +08:00
|
|
|
|
|
2026-09-02 16:19:28 +08:00
|
|
|
|
// 解析数据
|
|
|
|
|
|
const parsedData = parseApiResponse(
|
|
|
|
|
|
res.data.data,
|
|
|
|
|
|
currentValueForApi,
|
|
|
|
|
|
lastYearValue
|
|
|
|
|
|
);
|
2026-06-01 08:37:38 +08:00
|
|
|
|
|
2026-09-02 16:19:28 +08:00
|
|
|
|
console.log('解析后的数据 parsedData:', parsedData);
|
2026-06-01 08:37:38 +08:00
|
|
|
|
|
2026-09-02 16:19:28 +08:00
|
|
|
|
// 只有当有数据时才更新
|
|
|
|
|
|
if (parsedData.length > 0) {
|
|
|
|
|
|
chartData.value = parsedData;
|
2026-06-01 08:37:38 +08:00
|
|
|
|
|
2026-09-02 16:19:28 +08:00
|
|
|
|
console.log('chartData.value:', chartData.value);
|
2026-06-01 08:37:38 +08:00
|
|
|
|
|
2026-09-02 16:19:28 +08:00
|
|
|
|
// 等待 DOM 更新后,再初始化和更新图表
|
|
|
|
|
|
await nextTick();
|
2026-06-01 08:37:38 +08:00
|
|
|
|
|
2026-09-02 16:19:28 +08:00
|
|
|
|
try {
|
|
|
|
|
|
// 如果图表实例不存在,先初始化(等待完成)
|
|
|
|
|
|
if (!chartInstance) {
|
|
|
|
|
|
await initChart();
|
2026-06-01 08:37:38 +08:00
|
|
|
|
}
|
2026-09-02 16:19:28 +08:00
|
|
|
|
|
|
|
|
|
|
// 更新图表
|
2026-06-01 08:37:38 +08:00
|
|
|
|
if (chartInstance) {
|
2026-09-02 16:19:28 +08:00
|
|
|
|
updateChartData(currentValueForApi, lastYearValue);
|
|
|
|
|
|
console.log('图表已更新');
|
|
|
|
|
|
} else {
|
|
|
|
|
|
console.error('图表实例创建失败');
|
2026-06-01 08:37:38 +08:00
|
|
|
|
}
|
2026-09-02 16:19:28 +08:00
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('图表初始化失败:', error);
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
chartData.value = [];
|
|
|
|
|
|
console.warn('解析后数据为空');
|
|
|
|
|
|
// 清空图表中原有的数据
|
|
|
|
|
|
if (chartInstance) {
|
|
|
|
|
|
chartInstance.clear();
|
|
|
|
|
|
console.log('图表已清空(解析后数据为空)');
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('获取图表数据失败:', error);
|
|
|
|
|
|
chartData.value = [];
|
|
|
|
|
|
// 清空图表中原有的数据
|
|
|
|
|
|
if (chartInstance) {
|
|
|
|
|
|
chartInstance.clear();
|
|
|
|
|
|
console.log('图表已清空(发生错误)');
|
2026-06-01 08:37:38 +08:00
|
|
|
|
}
|
2026-09-02 16:19:28 +08:00
|
|
|
|
} finally {
|
|
|
|
|
|
loading.value = false;
|
|
|
|
|
|
}
|
2026-06-01 08:37:38 +08:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 更新图表数据
|
|
|
|
|
|
const updateChartData = (currentLabel: string, lastYearLabel: string) => {
|
2026-09-02 16:19:28 +08:00
|
|
|
|
if (!chartInstance) return;
|
|
|
|
|
|
|
|
|
|
|
|
// 动态生成图例标签
|
|
|
|
|
|
const modeText = select.value.value === 'year' ? '' : ' 月度';
|
|
|
|
|
|
const legendCurrentLabel = currentLabel + modeText;
|
|
|
|
|
|
const legendLastYearLabel = ' 去年同期';
|
|
|
|
|
|
|
|
|
|
|
|
const option = {
|
|
|
|
|
|
// 图例配置
|
|
|
|
|
|
legend: {
|
|
|
|
|
|
top: 10,
|
|
|
|
|
|
itemWidth: 18,
|
|
|
|
|
|
itemHeight: 12,
|
|
|
|
|
|
itemGap: 20,
|
|
|
|
|
|
icon: 'roundRect',
|
|
|
|
|
|
data: [
|
|
|
|
|
|
{ name: legendCurrentLabel, itemStyle: { color: '#4A8BC2' } },
|
|
|
|
|
|
{ name: legendLastYearLabel, itemStyle: { color: '#9B59B6' } }
|
|
|
|
|
|
],
|
|
|
|
|
|
textStyle: {
|
|
|
|
|
|
fontSize: 12,
|
|
|
|
|
|
color: '#333'
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
2026-06-01 08:37:38 +08:00
|
|
|
|
|
2026-09-02 16:19:28 +08:00
|
|
|
|
// 提示框配置
|
|
|
|
|
|
tooltip: {
|
|
|
|
|
|
trigger: 'axis',
|
|
|
|
|
|
axisPointer: {
|
|
|
|
|
|
type: 'shadow',
|
|
|
|
|
|
shadowStyle: {
|
|
|
|
|
|
color: 'rgba(200, 200, 200, 0.2)'
|
|
|
|
|
|
},
|
|
|
|
|
|
label: {
|
|
|
|
|
|
show: false
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
backgroundColor: '#fff',
|
|
|
|
|
|
borderColor: '#e8e8e8',
|
|
|
|
|
|
borderWidth: 1,
|
|
|
|
|
|
padding: 12,
|
|
|
|
|
|
textStyle: {
|
|
|
|
|
|
color: '#333',
|
|
|
|
|
|
fontSize: 12
|
|
|
|
|
|
},
|
|
|
|
|
|
formatter: (params: any) => {
|
|
|
|
|
|
const name = params[0].name.replace('\n', '');
|
|
|
|
|
|
let result = `<div style="font-weight: bold; margin-bottom: 8px;">${name}</div>`;
|
|
|
|
|
|
params.forEach((item: any) => {
|
|
|
|
|
|
const value = item.value != null ? Number(item.value) : '-';
|
|
|
|
|
|
result += `<div style="display: flex; align-items: center; margin-bottom: 4px;">
|
2026-04-24 17:46:41 +08:00
|
|
|
|
<span style="display: inline-block; width: 10px; height: 10px; border-radius: 50%; background: ${item.color}; margin-right: 8px;"></span>
|
|
|
|
|
|
<span>${item.seriesName}:</span>
|
2026-06-01 08:37:38 +08:00
|
|
|
|
<span style="margin-left: auto; font-weight: bold;">${value}%</span>
|
2026-04-24 17:46:41 +08:00
|
|
|
|
</div>`;
|
2026-09-02 16:19:28 +08:00
|
|
|
|
});
|
|
|
|
|
|
return result;
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
2026-06-01 08:37:38 +08:00
|
|
|
|
|
2026-09-02 16:19:28 +08:00
|
|
|
|
// 网格配置
|
|
|
|
|
|
grid: {
|
|
|
|
|
|
left: 60,
|
|
|
|
|
|
right: 10,
|
|
|
|
|
|
top: 60,
|
|
|
|
|
|
bottom: 60
|
|
|
|
|
|
},
|
2026-06-01 08:37:38 +08:00
|
|
|
|
|
2026-09-02 16:19:28 +08:00
|
|
|
|
// X轴配置
|
|
|
|
|
|
xAxis: {
|
|
|
|
|
|
type: 'value',
|
|
|
|
|
|
min: 0,
|
|
|
|
|
|
max: 100,
|
|
|
|
|
|
axisLine: {
|
|
|
|
|
|
show: false
|
|
|
|
|
|
},
|
|
|
|
|
|
axisTick: {
|
|
|
|
|
|
show: false
|
|
|
|
|
|
},
|
|
|
|
|
|
splitLine: {
|
|
|
|
|
|
show: true,
|
|
|
|
|
|
lineStyle: {
|
|
|
|
|
|
color: '#e8e8e8',
|
|
|
|
|
|
type: 'solid'
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
axisLabel: {
|
|
|
|
|
|
fontSize: 12,
|
|
|
|
|
|
color: '#666'
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
2026-06-01 08:37:38 +08:00
|
|
|
|
|
2026-09-02 16:19:28 +08:00
|
|
|
|
// Y轴配置
|
|
|
|
|
|
yAxis: {
|
|
|
|
|
|
type: 'category',
|
|
|
|
|
|
data: chartData.value.map(item => item.name),
|
|
|
|
|
|
inverse: true,
|
|
|
|
|
|
axisLine: {
|
|
|
|
|
|
show: true,
|
|
|
|
|
|
lineStyle: {
|
|
|
|
|
|
color: '#333',
|
|
|
|
|
|
width: 1
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
axisTick: {
|
|
|
|
|
|
show: true,
|
|
|
|
|
|
length: 4,
|
|
|
|
|
|
lineStyle: {
|
|
|
|
|
|
color: '#333',
|
|
|
|
|
|
width: 1
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
axisLabel: {
|
|
|
|
|
|
fontSize: 12,
|
|
|
|
|
|
color: '#333',
|
|
|
|
|
|
margin: 12
|
|
|
|
|
|
},
|
|
|
|
|
|
// 在Y轴底部添加"达标率(%)"标签
|
|
|
|
|
|
name: '达标率(%)',
|
|
|
|
|
|
nameLocation: 'end',
|
|
|
|
|
|
nameGap: 20,
|
|
|
|
|
|
nameTextStyle: {
|
|
|
|
|
|
fontSize: 12,
|
|
|
|
|
|
color: '#666',
|
|
|
|
|
|
align: 'left'
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
2026-06-01 08:37:38 +08:00
|
|
|
|
|
2026-09-02 16:19:28 +08:00
|
|
|
|
// 系列配置
|
|
|
|
|
|
series: [
|
|
|
|
|
|
{
|
|
|
|
|
|
name: legendCurrentLabel,
|
|
|
|
|
|
type: 'bar',
|
|
|
|
|
|
data: chartData.value.map(item =>
|
|
|
|
|
|
item.current != null ? Math.floor(Number(item.current)) : null
|
|
|
|
|
|
),
|
|
|
|
|
|
barWidth: 10,
|
|
|
|
|
|
barGap: '20%',
|
|
|
|
|
|
itemStyle: {
|
|
|
|
|
|
color: '#4A8BC2',
|
|
|
|
|
|
borderRadius: [0, 3, 3, 0]
|
2026-06-01 08:37:38 +08:00
|
|
|
|
}
|
2026-09-02 16:19:28 +08:00
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
name: legendLastYearLabel,
|
|
|
|
|
|
type: 'bar',
|
|
|
|
|
|
data: chartData.value.map(item =>
|
|
|
|
|
|
item.lastYear != null ? Math.floor(Number(item.lastYear)) : null
|
|
|
|
|
|
),
|
|
|
|
|
|
barWidth: 10,
|
|
|
|
|
|
barGap: '20%',
|
|
|
|
|
|
itemStyle: {
|
|
|
|
|
|
color: '#9B59B6',
|
|
|
|
|
|
borderRadius: [0, 3, 3, 0]
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
]
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
chartInstance.setOption(option, { notMerge: true });
|
|
|
|
|
|
|
|
|
|
|
|
// 移除旧的点击事件监听
|
|
|
|
|
|
chartInstance.off('click');
|
|
|
|
|
|
|
|
|
|
|
|
// 绑定点击事件
|
|
|
|
|
|
chartInstance.on('click', (params: any) => {
|
|
|
|
|
|
if (params.componentType === 'series' && params.seriesType === 'bar') {
|
|
|
|
|
|
// 获取点击的柱子对应的数据
|
|
|
|
|
|
const dataIndex = params.dataIndex;
|
|
|
|
|
|
const barData = chartData.value[dataIndex];
|
|
|
|
|
|
|
|
|
|
|
|
if (barData) {
|
|
|
|
|
|
// 新增:获取对应的 baseid 或 rstcd
|
|
|
|
|
|
const selectedId = chartDataIdMap.value[dataIndex] || '';
|
|
|
|
|
|
//
|
|
|
|
|
|
clickedBarData.value = {
|
|
|
|
|
|
name: barData.name.replace('\n', ''), // 去除换行符
|
|
|
|
|
|
current: barData.current,
|
|
|
|
|
|
lastYear: barData.lastYear,
|
|
|
|
|
|
selectedId: selectedId // 新增:存储选中的 ID
|
|
|
|
|
|
};
|
|
|
|
|
|
modalVisible.value = true;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
2026-04-24 17:46:41 +08:00
|
|
|
|
};
|
|
|
|
|
|
|
2026-06-01 08:37:38 +08:00
|
|
|
|
// 监听 datetimePicker.value.value 的变化
|
|
|
|
|
|
watch(
|
2026-09-02 16:19:28 +08:00
|
|
|
|
() => datetimePicker.value.value,
|
|
|
|
|
|
async (newValue, oldValue) => {
|
|
|
|
|
|
// 确保新值和旧值不同,且图表已初始化
|
|
|
|
|
|
if (newValue !== oldValue && chartInstance) {
|
|
|
|
|
|
await nextTick();
|
|
|
|
|
|
getEchartsData();
|
2026-06-01 08:37:38 +08:00
|
|
|
|
}
|
2026-09-02 16:19:28 +08:00
|
|
|
|
}
|
2026-06-01 08:37:38 +08:00
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
//监听子组件的数据变化
|
2026-09-02 16:19:28 +08:00
|
|
|
|
const handlePanelChange1 = async data => {
|
|
|
|
|
|
console.log('当前所有控件状态:', data);
|
|
|
|
|
|
// 当选择器或日期变化时,重新加载图表数据
|
|
|
|
|
|
if (data.select || data.datetime) {
|
|
|
|
|
|
await (select.value.value = data.select);
|
|
|
|
|
|
|
|
|
|
|
|
// 根据 select 的值决定 datetime 的格式
|
|
|
|
|
|
let formattedDatetime = data.datetime;
|
|
|
|
|
|
if (data.select === 'year') {
|
|
|
|
|
|
// 如果是年度模式,将 datetime 转换为 YYYY 格式
|
|
|
|
|
|
if (data.datetime && typeof data.datetime === 'string') {
|
|
|
|
|
|
// 提取年份部分(假设原格式为 YYYY-MM)
|
|
|
|
|
|
const yearMatch = data.datetime.match(/^(\d{4})/);
|
|
|
|
|
|
if (yearMatch) {
|
|
|
|
|
|
formattedDatetime = yearMatch[1];
|
2026-06-02 14:52:44 +08:00
|
|
|
|
}
|
2026-09-02 16:19:28 +08:00
|
|
|
|
}
|
2026-06-01 08:37:38 +08:00
|
|
|
|
}
|
2026-09-02 16:19:28 +08:00
|
|
|
|
// 如果是 month 模式,保持 datetime 原值不变
|
|
|
|
|
|
|
|
|
|
|
|
await (datetimePicker.value.value = formattedDatetime);
|
|
|
|
|
|
await (datetimePicker.value.picker = data.select);
|
|
|
|
|
|
await (datetimePicker.value.format =
|
|
|
|
|
|
data.select == 'month' ? 'YYYY-MM' : 'YYYY');
|
|
|
|
|
|
getEchartsData();
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
2026-06-01 08:37:38 +08:00
|
|
|
|
//获取图表数据
|
|
|
|
|
|
|
2026-04-24 17:46:41 +08:00
|
|
|
|
// 页面加载时执行
|
|
|
|
|
|
onMounted(() => {
|
2026-09-02 16:19:28 +08:00
|
|
|
|
// 延迟初始化,确保容器已渲染
|
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
|
initChart();
|
|
|
|
|
|
// 如果已有 selectedItem,触发数据加载
|
|
|
|
|
|
const currentStation =
|
|
|
|
|
|
injectedStation?.value || JidiSelectEventStore.selectedItem;
|
|
|
|
|
|
if (currentStation?.wbsCode) {
|
|
|
|
|
|
baseid.value = currentStation.wbsCode;
|
|
|
|
|
|
getEchartsData();
|
|
|
|
|
|
}
|
|
|
|
|
|
}, 100);
|
2026-06-01 08:37:38 +08:00
|
|
|
|
|
2026-09-02 16:19:28 +08:00
|
|
|
|
// 监听窗口resize
|
|
|
|
|
|
window.addEventListener('resize', () => {
|
|
|
|
|
|
chartInstance?.resize();
|
|
|
|
|
|
});
|
2026-04-24 17:46:41 +08:00
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 组件卸载时清理
|
|
|
|
|
|
onUnmounted(() => {
|
2026-09-02 16:19:28 +08:00
|
|
|
|
chartInstance?.dispose();
|
|
|
|
|
|
chartInstance = null;
|
|
|
|
|
|
chartData.value = [];
|
|
|
|
|
|
window.removeEventListener('resize', () => {
|
|
|
|
|
|
chartInstance?.resize();
|
|
|
|
|
|
});
|
2026-04-24 17:46:41 +08:00
|
|
|
|
});
|
2026-06-01 08:37:38 +08:00
|
|
|
|
|
|
|
|
|
|
// 弹框相关状态
|
|
|
|
|
|
const modalVisible = ref(false);
|
2026-09-02 16:19:28 +08:00
|
|
|
|
const clickedBarData = ref<{
|
|
|
|
|
|
name: string;
|
|
|
|
|
|
current: number | null;
|
|
|
|
|
|
lastYear: number | null;
|
|
|
|
|
|
selectedId?: string;
|
|
|
|
|
|
} | null>(null);
|
2026-07-07 10:33:29 +08:00
|
|
|
|
useDraggable(modalVisible, { boundary: true, resetOnOpen: true });
|
2026-04-24 17:46:41 +08:00
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
<style lang="scss" scoped>
|
|
|
|
|
|
.body_item {
|
2026-09-02 16:19:28 +08:00
|
|
|
|
width: 100%;
|
|
|
|
|
|
height: 600px;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
|
|
|
|
|
|
|
.tabs_all {
|
|
|
|
|
|
width: 28px;
|
|
|
|
|
|
height: 500px;
|
|
|
|
|
|
box-sizing: border-box;
|
|
|
|
|
|
border: 2px solid #2f6b98;
|
|
|
|
|
|
border-radius: 5px;
|
|
|
|
|
|
|
|
|
|
|
|
.zhong_tabs {
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
height: 50%;
|
|
|
|
|
|
background: #2f6b98;
|
|
|
|
|
|
color: #fff;
|
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
|
text-align: center;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: center;
|
|
|
|
|
|
box-sizing: border-box;
|
|
|
|
|
|
text-shadow: 0 0 0.25px currentcolor;
|
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.no_tabs {
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
height: 50%;
|
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
|
text-align: center;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: center;
|
|
|
|
|
|
box-sizing: border-box;
|
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.no_tabs:hover {
|
|
|
|
|
|
color: #40a9ff;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.tabs_body {
|
|
|
|
|
|
width: 368px;
|
2026-04-24 17:46:41 +08:00
|
|
|
|
height: 600px;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
2026-09-02 16:19:28 +08:00
|
|
|
|
justify-content: center;
|
|
|
|
|
|
position: relative;
|
|
|
|
|
|
/* 为empty-overlay提供定位上下文 */
|
|
|
|
|
|
|
|
|
|
|
|
.chart-container {
|
|
|
|
|
|
width: 100% !important;
|
|
|
|
|
|
height: 100% !important;
|
|
|
|
|
|
min-width: 368px;
|
|
|
|
|
|
/* 强制最小宽度 */
|
|
|
|
|
|
min-height: 600px;
|
|
|
|
|
|
/* 强制最小高度 */
|
|
|
|
|
|
}
|
2026-04-24 17:46:41 +08:00
|
|
|
|
|
2026-09-02 16:19:28 +08:00
|
|
|
|
.empty-overlay {
|
|
|
|
|
|
position: absolute;
|
|
|
|
|
|
top: 0;
|
|
|
|
|
|
left: 0;
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
height: 300px;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: center;
|
|
|
|
|
|
background: rgba(255, 255, 255, 0.9);
|
|
|
|
|
|
z-index: 10;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-06-01 08:37:38 +08:00
|
|
|
|
|
2026-09-02 16:19:28 +08:00
|
|
|
|
.modal-content {
|
|
|
|
|
|
padding: 20px;
|
|
|
|
|
|
|
|
|
|
|
|
.detail-info {
|
|
|
|
|
|
.info-header {
|
|
|
|
|
|
margin-bottom: 24px;
|
|
|
|
|
|
padding-bottom: 16px;
|
|
|
|
|
|
border-bottom: 2px solid #e8e8e8;
|
|
|
|
|
|
|
|
|
|
|
|
h3 {
|
|
|
|
|
|
margin: 0 0 8px 0;
|
|
|
|
|
|
font-size: 20px;
|
|
|
|
|
|
color: #333;
|
|
|
|
|
|
font-weight: bold;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.time-info {
|
|
|
|
|
|
margin: 0;
|
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
|
color: #666;
|
|
|
|
|
|
}
|
2026-04-24 17:46:41 +08:00
|
|
|
|
}
|
2026-06-01 08:37:38 +08:00
|
|
|
|
|
2026-09-02 16:19:28 +08:00
|
|
|
|
.info-body {
|
|
|
|
|
|
.info-item {
|
2026-04-24 17:46:41 +08:00
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
2026-09-02 16:19:28 +08:00
|
|
|
|
margin-bottom: 16px;
|
|
|
|
|
|
padding: 12px 16px;
|
|
|
|
|
|
background: #f5f7fa;
|
|
|
|
|
|
border-radius: 4px;
|
|
|
|
|
|
|
|
|
|
|
|
.label {
|
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
|
color: #666;
|
|
|
|
|
|
min-width: 140px;
|
2026-06-01 08:37:38 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-02 16:19:28 +08:00
|
|
|
|
.value {
|
|
|
|
|
|
font-size: 16px;
|
|
|
|
|
|
font-weight: bold;
|
2026-06-01 08:37:38 +08:00
|
|
|
|
|
2026-09-02 16:19:28 +08:00
|
|
|
|
&.current {
|
|
|
|
|
|
color: #4a8bc2;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
&.last-year {
|
|
|
|
|
|
color: #9b59b6;
|
|
|
|
|
|
}
|
2026-06-02 14:52:44 +08:00
|
|
|
|
|
2026-09-02 16:19:28 +08:00
|
|
|
|
&.change {
|
|
|
|
|
|
&.positive {
|
|
|
|
|
|
color: #52c41a;
|
2026-06-01 08:37:38 +08:00
|
|
|
|
}
|
2026-06-02 14:52:44 +08:00
|
|
|
|
|
2026-09-02 16:19:28 +08:00
|
|
|
|
&.negative {
|
|
|
|
|
|
color: #ff4d4f;
|
2026-06-01 08:37:38 +08:00
|
|
|
|
}
|
2026-06-02 14:52:44 +08:00
|
|
|
|
|
2026-09-02 16:19:28 +08:00
|
|
|
|
&.neutral {
|
|
|
|
|
|
color: #666;
|
2026-06-01 08:37:38 +08:00
|
|
|
|
}
|
2026-09-02 16:19:28 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.custom-section {
|
|
|
|
|
|
margin-top: 24px;
|
|
|
|
|
|
padding: 16px;
|
|
|
|
|
|
background: #fafafa;
|
|
|
|
|
|
border-radius: 4px;
|
|
|
|
|
|
border: 1px solid #e8e8e8;
|
|
|
|
|
|
|
|
|
|
|
|
h4 {
|
|
|
|
|
|
margin: 0 0 12px 0;
|
|
|
|
|
|
font-size: 16px;
|
|
|
|
|
|
color: #333;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
p {
|
|
|
|
|
|
margin: 0;
|
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
|
color: #666;
|
|
|
|
|
|
line-height: 1.6;
|
2026-04-24 17:46:41 +08:00
|
|
|
|
}
|
2026-09-02 16:19:28 +08:00
|
|
|
|
}
|
2026-04-24 17:46:41 +08:00
|
|
|
|
}
|
2026-09-02 16:19:28 +08:00
|
|
|
|
}
|
2026-04-24 17:46:41 +08:00
|
|
|
|
}
|
2026-09-02 16:19:28 +08:00
|
|
|
|
</style>
|