WholeProcessPlatform/frontend/src/modules/EnvironmentalQuality/index.vue

1028 lines
33 KiB
Vue
Raw Normal View History

2026-04-24 17:46:41 +08:00
<!-- SidePanelItem.vue -->
<template>
2026-06-01 08:37:38 +08:00
<SidePanelItem title="地表水水质达标率" :select="select" :datetimePicker="datetimePicker"
2026-06-02 14:52:44 +08:00
@update-values="handlePanelChange1" :style="chartData.length === 0 && !loading && baseid != 'all' ? 'height: 300px' : 'height: 650px'">
2026-04-24 17:46:41 +08:00
<div class="body_item">
2026-06-01 08:37:38 +08:00
<div v-if="baseid == 'all'" class="tabs_all">
2026-06-02 14:52:44 +08:00
<div :class="tabs == '0' ? 'zhong_tabs' : 'no_tabs'" @click="handleTabChange(0)">自建水质站</div>
<div :class="tabs == '1' ? 'zhong_tabs' : 'no_tabs'" @click="handleTabChange(1)">国家水质站</div>
2026-04-24 17:46:41 +08:00
</div>
2026-06-02 14:52:44 +08:00
<div v-show="tabs == '0' || tabs == '1'" class="tabs_body">
2026-06-01 08:37:38 +08:00
<a-spin :spinning="loading">
<!-- 始终渲染图表容器确保有固定宽高 -->
<div ref="chartRef" class="chart-container"></div>
<!-- 无数据时显示Empty但不影响容器尺寸 -->
2026-06-02 14:52:44 +08:00
<div v-if="chartData.length === 0 && !loading" class="empty-overlay" :style="chartData.length === 0 && !loading && baseid != 'all' ? 'height: 300px' : 'height: 650px'">
2026-06-01 08:37:38 +08:00
<a-empty description="暂无数据" />
</div>
</a-spin>
2026-04-24 17:46:41 +08:00
</div>
</div>
</SidePanelItem>
2026-06-01 08:37:38 +08:00
<!-- 环境质量满足度弹框 -->
2026-06-02 14:52:44 +08:00
<a-modal v-model:open="modalVisible" title="环境质量满足度" width="1536px" :footer="null" centered>
<EnvironmentalQualityTwoLayers :datas="(baseid == 'all' ? jiDiList : stationListForModal)" :type="select.value"
:dateValue="datetimePicker.value" :dataDimensionVal="(clickedBarData?.selectedId || baseid)"
:buildType="tabs" :typeOne="fieldOne" :baseid="baseid" />
2026-06-01 08:37:38 +08:00
</a-modal>
2026-04-24 17:46:41 +08:00
</template>
<script lang="ts" setup>
2026-06-01 08:37:38 +08:00
import { ref, onMounted, onUnmounted, watch, nextTick, computed } from 'vue';
2026-04-24 17:46:41 +08:00
import * as echarts from 'echarts';
import SidePanelItem from '@/components/SidePanelItem/index.vue';
2026-06-01 08:37:38 +08:00
import { wqGetKendoListCust } from '@/api/sz'
import { useJidiSelectEventStore } from "@/store/modules/jidiSelectEvent";
import EnvironmentalQualityTwoLayers from "./TwoLayers/EnvironmentalQualityTwoLayers.vue"
2026-04-24 17:46:41 +08:00
// 定义组件名(便于调试和递归)
defineOptions({
name: 'EnvironmentalQuality'
});
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-06-01 08:37:38 +08:00
const chartData = ref<Array<{ name: string; current: number | null; lastYear: number | null }>>([]);
2026-06-02 14:52:44 +08:00
// 新增:存储当前基地下的电站列表(用于弹框传递)
const stationListForModal = ref<Array<{ baseid: string; basename: string }>>([]);
// 新增:存储图表数据索引与 baseid/rstcd 的映射关系
const chartDataIdMap = ref<Array<string>>([]);
2026-06-01 08:37:38 +08:00
const select = ref({
show: true,
value: 'month',
options: [
{ value: 'month', label: '月度' },
{ value: 'year', label: '年度' }
],
picker: undefined,
format: undefined,
});
// 日期选择器配置
const datetimePicker = ref({
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: [],
});
const baseid = ref("")
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"
}
])
2026-06-02 14:52:44 +08:00
const fieldOne = ref('')
2026-06-01 08:37:38 +08:00
// 解析API返回的嵌套数据支持两种结构
const parseApiResponse = (items: any[], currentValueForApi: string, lastYearValue: string) => {
const result: Array<{ name: string; current: number | null; lastYear: number | null }> = [];
2026-06-02 14:52:44 +08:00
fieldOne.value = ''
// 新增:清空 ID 映射数组
chartDataIdMap.value = [];
// 新增:提取电站列表(仅当 baseid != 'all' 时)
const stations: Array<{ baseid: string; basename: string }> = [];
2026-06-01 08:37:38 +08:00
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);
2026-06-02 14:52:44 +08:00
2026-06-01 08:37:38 +08:00
group.items?.forEach((mainGroup, mainIndex) => {
console.log(` → 第${mainIndex + 1}个mainGroup:`);
console.log(' field:', mainGroup.field);
console.log(' key:', mainGroup.key);
2026-06-02 14:52:44 +08:00
2026-06-01 08:37:38 +08:00
// 判断是哪种结构检查第二层的field字段
const field = mainGroup.field;
2026-06-02 14:52:44 +08:00
2026-06-01 08:37:38 +08:00
if (field === 'ennm' || field === 'baseId') {
// 提取中文名称从mainGroup.key获取站点名或基地ID
let displayName = '';
2026-06-02 14:52:44 +08:00
2026-06-01 08:37:38 +08:00
// 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-06-01 08:37:38 +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-02 14:52:44 +08:00
2026-06-01 08:37:38 +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 || '';
}
console.log(' 从最内层数据获取名称:', displayName);
}
}
2026-06-02 14:52:44 +08:00
2026-06-01 08:37:38 +08:00
if (!displayName) {
console.log(' ⚠️ 跳过:没有名称');
return; // 跳过没有名称的数据
}
2026-06-02 14:52:44 +08:00
2026-06-01 08:37:38 +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);
2026-06-02 14:52:44 +08:00
2026-06-01 08:37:38 +08:00
subGroup?.items?.forEach(monthGroup => {
2026-06-02 14:52:44 +08:00
const periodKey = monthGroup.key; // "2026-05" 或 "2025-05" 或 "2026" 或 "2025"
2026-06-01 08:37:38 +08:00
const dataItem = monthGroup.items?.[0];
if (dataItem) {
2026-06-02 14:52:44 +08:00
// 兼容月度数据COUNT_DRMONTH/wqRate和年度数据COUNT_DRYEAR
const value = dataItem.COUNT_DRMONTH ?? dataItem.COUNT_DRYEAR ?? dataItem.wqRate;
2026-06-01 08:37:38 +08:00
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}`);
}
}
});
// 添加换行符:在"干流"前添加\n
const finalDisplayName = displayName.replace(/干流$/, '\n干流');
2026-04-24 17:46:41 +08:00
2026-06-01 08:37:38 +08:00
console.log(` → 最终数据: {name: "${finalDisplayName}", current: ${currentPeriodValue}, lastYear: ${lastYearPeriodValue}}`);
2026-06-02 14:52:44 +08:00
// 新增:记录 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
});
}
}
2026-06-01 08:37:38 +08:00
result.push({
name: finalDisplayName,
current: currentPeriodValue,
lastYear: lastYearPeriodValue
});
}
});
});
2026-06-02 14:52:44 +08:00
// 新增:如果提取到了电站列表,更新响应式变量
if (stations.length > 0) {
stationListForModal.value = stations;
console.log('提取到的电站列表:', stations);
} else {
stationListForModal.value = [];
}
console.log('ID 映射关系:', chartDataIdMap.value);
2026-06-01 08:37:38 +08:00
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) => {
tabs.value = tab;
2026-06-01 08:37:38 +08:00
getEchartsData()
2026-04-24 17:46:41 +08:00
};
2026-06-01 08:37:38 +08:00
//监听tabs变化处理图表刷新
2026-04-24 17:46:41 +08:00
watch(tabs, (newVal) => {
2026-06-02 14:52:44 +08:00
if (newVal === '0' || newVal === '1') {
2026-04-24 17:46:41 +08:00
nextTick(() => {
setTimeout(() => {
2026-06-01 08:37:38 +08:00
getEchartsData();
2026-04-24 17:46:41 +08:00
}, 50);
});
}
});
2026-06-01 08:37:38 +08:00
watch(
() => JidiSelectEventStore.selectedItem,
(newVal) => {
if (newVal && newVal.wbsCode) {
baseid.value = newVal.wbsCode;
getEchartsData();
}
},
{ deep: true }
);
// 初始化图表实例仅创建实例不设置option
const initChart = (): Promise<void> => {
return new Promise((resolve, reject) => {
if (!chartRef.value) {
console.error('图表容器ref不存在');
reject(new Error('图表容器不存在'));
return;
}
if (chartInstance) {
resolve();
return;
}
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);
} else {
console.error('图表容器尺寸仍为0已达到最大重试次数', {
clientWidth: domElement.clientWidth,
clientHeight: domElement.clientHeight
});
reject(new Error('图表容器尺寸为0'));
}
};
// 开始重试
requestAnimationFrame(tryInit);
} else {
// 尺寸正常,直接初始化
chartInstance = echarts.init(domElement);
console.log('图表实例已创建');
resolve();
}
});
};
//获取图表数据
const getEchartsData = async () => {
// 1. 格式验证
const currentValue = datetimePicker.value.value;
// debugger
if (select.value.value === 'year') {
// 年度模式:验证是否为 YYYY 格式4位数字
if (!/^\d{4}$/.test(currentValue)) {
console.warn('年度模式下日期格式不正确,应为 YYYY 格式');
return;
}
} else if (select.value.value === 'month') {
// 月度模式:验证是否为 YYYY-MM 格式
if (!/^\d{4}-\d{2}$/.test(currentValue)) {
console.warn('月度模式下日期格式不正确,应为 YYYY-MM 格式');
return;
}
} 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. 构建请求参数
2026-06-02 14:52:44 +08:00
console.log(baseid.value)
if( ! baseid.value){
return
}
// debugger
2026-06-01 08:37:38 +08:00
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-02 14:52:44 +08:00
baseid.value == 'all' ? {
2026-06-01 08:37:38 +08:00
"field": "dtinType",
"operator": "eq",
2026-06-02 14:52:44 +08:00
"value": 0
} : {
"field": "rstcd",
"operator": "isnotnull"
}, ,
2026-06-01 08:37:38 +08:00
{
"logic": "or",
"filters": [
{
"field": select.value.value == "year" ? "drYear" : "drMonth",
"operator": "eq",
"value": currentValueForApi, // 当前值(年或年月)
"dataType": "string"
},
{
"field": select.value.value == "year" ? "drYear" : "drMonth",
"operator": "eq",
"value": lastYearValue, // 上一年同期
"dataType": "string"
}
]
}
].filter(Boolean)
},
"group": [
2026-06-02 14:52:44 +08:00
baseid.value == 'all' ? {
2026-06-01 08:37:38 +08:00
"dir": "des",
"field": "baseStepSort"
2026-06-02 14:52:44 +08:00
} : {
"dir": "asc",
"field": "rstcdStepSort"
}, ,
2026-06-01 08:37:38 +08:00
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"
}
2026-06-02 14:52:44 +08:00
].filter(Boolean),
2026-06-01 08:37:38 +08:00
"groupResultFlat": false,
"aggregate": [
{
"aggregate": "sum",
"field": "wqRate"
}
]
};
2026-06-02 14:52:44 +08:00
console.log('请求参数:', params);
// debugger
2026-06-01 08:37:38 +08:00
// 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);
if (!res?.data?.data) {
console.warn('数据获取失败或数据为空');
chartData.value = [];
// 清空图表中原有的数据
if (chartInstance) {
chartInstance.clear();
console.log('图表已清空(数据获取失败)');
}
return;
}
// 解析数据
const parsedData = parseApiResponse(res.data.data, currentValueForApi, lastYearValue);
console.log('解析后的数据 parsedData:', parsedData);
// 只有当有数据时才更新
if (parsedData.length > 0) {
chartData.value = parsedData;
console.log('chartData.value:', chartData.value);
// 等待 DOM 更新后,再初始化和更新图表
await nextTick();
try {
// 如果图表实例不存在,先初始化(等待完成)
if (!chartInstance) {
await initChart();
}
// 更新图表
if (chartInstance) {
updateChartData(currentValueForApi, lastYearValue);
console.log('图表已更新');
} else {
console.error('图表实例创建失败');
}
} 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('图表已清空(发生错误)');
}
} finally {
loading.value = false;
}
};
// 更新图表数据
const updateChartData = (currentLabel: string, lastYearLabel: string) => {
if (!chartInstance) return;
// 动态生成图例标签
const modeText = select.value.value === 'year' ? '' : ' 月度';
const legendCurrentLabel = currentLabel + modeText;
const legendLastYearLabel = ' 去年同期';
2026-04-24 17:46:41 +08:00
const option = {
// 图例配置
legend: {
top: 10,
itemWidth: 18,
itemHeight: 12,
itemGap: 20,
icon: 'roundRect',
data: [
2026-06-01 08:37:38 +08:00
{ name: legendCurrentLabel, itemStyle: { color: '#4A8BC2' } },
{ name: legendLastYearLabel, itemStyle: { color: '#9B59B6' } }
2026-04-24 17:46:41 +08:00
],
textStyle: {
fontSize: 12,
color: '#333'
}
},
2026-06-01 08:37:38 +08:00
2026-04-24 17:46:41 +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) => {
2026-06-02 14:52:44 +08:00
const value = item.value != null ? Number(item.value) : '-';
2026-04-24 17:46:41 +08:00
result += `<div style="display: flex; align-items: center; margin-bottom: 4px;">
<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>`;
});
return result;
}
},
2026-06-01 08:37:38 +08:00
2026-04-24 17:46:41 +08:00
// 网格配置
grid: {
left: 60,
right: 10,
top: 60,
2026-06-01 08:37:38 +08:00
bottom: 60
2026-04-24 17:46:41 +08:00
},
2026-06-01 08:37:38 +08:00
2026-04-24 17:46:41 +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-04-24 17:46:41 +08:00
// Y轴配置
yAxis: {
type: 'category',
2026-06-01 08:37:38 +08:00
data: chartData.value.map(item => item.name),
2026-04-24 17:46:41 +08:00
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-04-24 17:46:41 +08:00
// 系列配置
series: [
{
2026-06-01 08:37:38 +08:00
name: legendCurrentLabel,
2026-04-24 17:46:41 +08:00
type: 'bar',
2026-06-02 14:52:44 +08:00
data: chartData.value.map(item => item.current != null ? Math.floor(Number(item.current)) : null),
2026-04-24 17:46:41 +08:00
barWidth: 10,
barGap: '20%',
itemStyle: {
color: '#4A8BC2',
borderRadius: [0, 3, 3, 0]
}
},
{
2026-06-01 08:37:38 +08:00
name: legendLastYearLabel,
2026-04-24 17:46:41 +08:00
type: 'bar',
2026-06-02 14:52:44 +08:00
data: chartData.value.map(item => item.lastYear != null ? Math.floor(Number(item.lastYear)) : null),
2026-04-24 17:46:41 +08:00
barWidth: 10,
barGap: '20%',
itemStyle: {
color: '#9B59B6',
borderRadius: [0, 3, 3, 0]
}
}
]
};
2026-06-01 08:37:38 +08:00
chartInstance.setOption(option, { notMerge: true });
2026-06-02 14:52:44 +08:00
2026-06-01 08:37:38 +08:00
// 移除旧的点击事件监听
chartInstance.off('click');
2026-06-02 14:52:44 +08:00
2026-06-01 08:37:38 +08:00
// 绑定点击事件
chartInstance.on('click', (params: any) => {
if (params.componentType === 'series' && params.seriesType === 'bar') {
// 获取点击的柱子对应的数据
const dataIndex = params.dataIndex;
const barData = chartData.value[dataIndex];
2026-06-02 14:52:44 +08:00
2026-06-01 08:37:38 +08:00
if (barData) {
2026-06-02 14:52:44 +08:00
// 新增:获取对应的 baseid 或 rstcd
const selectedId = chartDataIdMap.value[dataIndex] || '';
// debugger
2026-06-01 08:37:38 +08:00
clickedBarData.value = {
name: barData.name.replace('\n', ''), // 去除换行符
current: barData.current,
2026-06-02 14:52:44 +08:00
lastYear: barData.lastYear,
selectedId: selectedId // 新增:存储选中的 ID
2026-06-01 08:37:38 +08:00
};
modalVisible.value = true;
2026-04-24 17:46:41 +08:00
}
2026-06-01 08:37:38 +08:00
}
2026-04-24 17:46:41 +08:00
});
};
2026-06-01 08:37:38 +08:00
// 监听 datetimePicker.value.value 的变化
watch(
() => datetimePicker.value.value,
async (newValue, oldValue) => {
// 确保新值和旧值不同,且图表已初始化
if (newValue !== oldValue && chartInstance) {
await nextTick();
getEchartsData();
}
}
);
//监听子组件的数据变化
const handlePanelChange1 = async (data) => {
console.log('当前所有控件状态:', data);
2026-06-02 14:52:44 +08:00
// 当选择器或日期变化时,重新加载图表数据
2026-06-01 08:37:38 +08:00
if (data.select || data.datetime) {
await (select.value.value = data.select)
2026-06-02 14:52:44 +08:00
// 根据 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];
}
}
}
// 如果是 month 模式,保持 datetime 原值不变
await (datetimePicker.value.value = formattedDatetime)
2026-06-01 08:37:38 +08:00
await (datetimePicker.value.picker = data.select)
await (datetimePicker.value.format = data.select == 'month' ? 'YYYY-MM' : 'YYYY')
getEchartsData()
}
}
//获取图表数据
2026-04-24 17:46:41 +08:00
// 页面加载时执行
onMounted(() => {
// 延迟初始化,确保容器已渲染
setTimeout(() => {
initChart();
2026-06-01 08:37:38 +08:00
// 如果已有 selectedItem触发数据加载
if (JidiSelectEventStore.selectedItem?.wbsCode) {
baseid.value = JidiSelectEventStore.selectedItem.wbsCode;
getEchartsData();
}
2026-04-24 17:46:41 +08:00
}, 100);
2026-06-01 08:37:38 +08:00
2026-04-24 17:46:41 +08:00
// 监听窗口resize
window.addEventListener('resize', () => {
chartInstance?.resize();
});
});
// 组件卸载时清理
onUnmounted(() => {
chartInstance?.dispose();
2026-06-01 08:37:38 +08:00
chartInstance = null;
chartData.value = [];
2026-04-24 17:46:41 +08:00
window.removeEventListener('resize', () => {
chartInstance?.resize();
});
});
2026-06-01 08:37:38 +08:00
// 弹框相关状态
const modalVisible = ref(false);
2026-06-02 14:52:44 +08:00
const clickedBarData = ref<{ name: string; current: number | null; lastYear: number | null; selectedId?: string } | null>(null);
2026-06-01 08:37:38 +08:00
2026-04-24 17:46:41 +08:00
</script>
<style lang="scss" scoped>
.body_item {
width: 100%;
height: 600px;
display: flex;
align-items: center;
justify-content: space-between;
.tabs_all {
width: 28px;
2026-06-01 08:37:38 +08:00
height: 500px;
2026-04-24 17:46:41 +08:00
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 .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;
}
2026-06-01 08:37:38 +08:00
.no_tabs:hover {
color: #40a9ff;
2026-04-24 17:46:41 +08:00
}
}
2026-06-01 08:37:38 +08:00
2026-04-24 17:46:41 +08:00
.tabs_body {
width: 368px;
height: 600px;
display: flex;
align-items: center;
justify-content: center;
2026-06-01 08:37:38 +08:00
position: relative;
/* 为empty-overlay提供定位上下文 */
2026-04-24 17:46:41 +08:00
.chart-container {
2026-06-01 08:37:38 +08:00
width: 100% !important;
height: 100% !important;
min-width: 368px;
/* 强制最小宽度 */
min-height: 600px;
/* 强制最小高度 */
}
.empty-overlay {
position: absolute;
top: 0;
left: 0;
2026-04-24 17:46:41 +08:00
width: 100%;
2026-06-02 14:52:44 +08:00
height: 300px;
2026-06-01 08:37:38 +08:00
display: flex;
align-items: center;
justify-content: center;
background: rgba(255, 255, 255, 0.9);
z-index: 10;
}
}
}
.modal-content {
padding: 20px;
2026-06-02 14:52:44 +08:00
2026-06-01 08:37:38 +08:00
.detail-info {
.info-header {
margin-bottom: 24px;
padding-bottom: 16px;
border-bottom: 2px solid #e8e8e8;
2026-06-02 14:52:44 +08:00
2026-06-01 08:37:38 +08:00
h3 {
margin: 0 0 8px 0;
font-size: 20px;
color: #333;
font-weight: bold;
}
2026-06-02 14:52:44 +08:00
2026-06-01 08:37:38 +08:00
.time-info {
margin: 0;
font-size: 14px;
color: #666;
}
}
2026-06-02 14:52:44 +08:00
2026-06-01 08:37:38 +08:00
.info-body {
.info-item {
display: flex;
align-items: center;
margin-bottom: 16px;
padding: 12px 16px;
background: #f5f7fa;
border-radius: 4px;
2026-06-02 14:52:44 +08:00
2026-06-01 08:37:38 +08:00
.label {
font-size: 14px;
color: #666;
min-width: 140px;
}
2026-06-02 14:52:44 +08:00
2026-06-01 08:37:38 +08:00
.value {
font-size: 16px;
font-weight: bold;
2026-06-02 14:52:44 +08:00
2026-06-01 08:37:38 +08:00
&.current {
color: #4A8BC2;
}
2026-06-02 14:52:44 +08:00
2026-06-01 08:37:38 +08:00
&.last-year {
color: #9B59B6;
}
2026-06-02 14:52:44 +08:00
2026-06-01 08:37:38 +08:00
&.change {
&.positive {
color: #52c41a;
}
2026-06-02 14:52:44 +08:00
2026-06-01 08:37:38 +08:00
&.negative {
color: #ff4d4f;
}
2026-06-02 14:52:44 +08:00
2026-06-01 08:37:38 +08:00
&.neutral {
color: #666;
}
}
}
}
2026-06-02 14:52:44 +08:00
2026-06-01 08:37:38 +08:00
.custom-section {
margin-top: 24px;
padding: 16px;
background: #fafafa;
border-radius: 4px;
border: 1px solid #e8e8e8;
2026-06-02 14:52:44 +08:00
2026-06-01 08:37:38 +08:00
h4 {
margin: 0 0 12px 0;
font-size: 16px;
color: #333;
}
2026-06-02 14:52:44 +08:00
2026-06-01 08:37:38 +08:00
p {
margin: 0;
font-size: 14px;
color: #666;
line-height: 1.6;
}
}
2026-04-24 17:46:41 +08:00
}
}
}
</style>