添加右侧展开,bug修改,地图优化
This commit is contained in:
parent
d4882f469c
commit
29aaeefc60
@ -3,7 +3,7 @@
|
|||||||
"version": "1.2.0",
|
"version": "1.2.0",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite serve --mode development",
|
"dev": "vite serve --mode development",
|
||||||
"build": "cross-env NODE_OPTIONS=--max-old-space-size=8192 vite build --mode production",
|
"build": "vite build --mode production",
|
||||||
"build:mvn": "cross-env NODE_OPTIONS=--max-old-space-size=8192 vite build --mode production",
|
"build:mvn": "cross-env NODE_OPTIONS=--max-old-space-size=8192 vite build --mode production",
|
||||||
"serve": "vite preview",
|
"serve": "vite preview",
|
||||||
"lint": "eslint src/**/*.{ts,js,vue} --fix",
|
"lint": "eslint src/**/*.{ts,js,vue} --fix",
|
||||||
|
|||||||
@ -29,15 +29,10 @@
|
|||||||
|
|
||||||
<!-- Tabs 区域 - 等待加载完成后显示 -->
|
<!-- Tabs 区域 - 等待加载完成后显示 -->
|
||||||
<a-tabs v-else :active-key="currentActiveKey" @change="onTabChange">
|
<a-tabs v-else :active-key="currentActiveKey" @change="onTabChange">
|
||||||
<a-tab-pane v-for="tab in tabsConfig" :key="tab.key" :tab="tab.name">
|
<a-tab-pane v-for="tab in tabsConfig" :key="tab.key" :tab="tab.name"> </a-tab-pane>
|
||||||
</a-tab-pane>
|
|
||||||
<template #rightExtra v-if="anchor.sttp == 'ENG' || anchor.sttp == 'eng'">
|
<template #rightExtra v-if="anchor.sttp == 'ENG' || anchor.sttp == 'eng'">
|
||||||
<a-tooltip :title="!hasPowerStatData ? '该电站无专题配置' : ''">
|
<a-tooltip :title="!hasPowerStatData ? '该电站无专题配置' : ''">
|
||||||
<a-button
|
<a-button type="primary" :disabled="!hasPowerStatData" @click="gotoEngTopic">
|
||||||
type="primary"
|
|
||||||
:disabled="!hasPowerStatData"
|
|
||||||
@click="gotoEngTopic"
|
|
||||||
>
|
|
||||||
<i class="icon iconfont icon-topic mr-[5px]"></i>
|
<i class="icon iconfont icon-topic mr-[5px]"></i>
|
||||||
电站专题
|
电站专题
|
||||||
</a-button></a-tooltip
|
</a-button></a-tooltip
|
||||||
@ -90,11 +85,7 @@
|
|||||||
v-show="currentActiveKey === 'WaterTemperature'"
|
v-show="currentActiveKey === 'WaterTemperature'"
|
||||||
:is-active="currentActiveKey === 'WaterTemperature'"
|
:is-active="currentActiveKey === 'WaterTemperature'"
|
||||||
:code="getTabCode('WaterTemperature')"
|
:code="getTabCode('WaterTemperature')"
|
||||||
:stcd="
|
:stcd="getTabCode('WaterTemperature') == 'qxd.tabs.jcsj' ? qxdSelectedStcd : ''"
|
||||||
getTabCode('WaterTemperature') == 'qxd.tabs.jcsj'
|
|
||||||
? qxdSelectedStcd
|
|
||||||
: ''
|
|
||||||
"
|
|
||||||
:wt-config="modelStore.wtConfig"
|
:wt-config="modelStore.wtConfig"
|
||||||
/>
|
/>
|
||||||
<!-- 监测数据 - 垂向水温 ✅️ -->
|
<!-- 监测数据 - 垂向水温 ✅️ -->
|
||||||
@ -108,11 +99,7 @@
|
|||||||
v-show="currentActiveKey === 'WaterQuality'"
|
v-show="currentActiveKey === 'WaterQuality'"
|
||||||
:is-active="currentActiveKey === 'WaterQuality'"
|
:is-active="currentActiveKey === 'WaterQuality'"
|
||||||
:code="getTabCode('WaterQuality')"
|
:code="getTabCode('WaterQuality')"
|
||||||
:stcd="
|
:stcd="getTabCode('WaterQuality') == 'qxd.tabs.szjc' ? qxdSelectedStcdWq : ''"
|
||||||
getTabCode('WaterQuality') == 'qxd.tabs.szjc'
|
|
||||||
? qxdSelectedStcdWq
|
|
||||||
: ''
|
|
||||||
"
|
|
||||||
:wq-show="modelStore.wqShow"
|
:wq-show="modelStore.wqShow"
|
||||||
/>
|
/>
|
||||||
<!-- 监测数据 - 水电站告警情况 -->
|
<!-- 监测数据 - 水电站告警情况 -->
|
||||||
@ -132,11 +119,7 @@
|
|||||||
:is-active="currentActiveKey === 'FlowMeasure'"
|
:is-active="currentActiveKey === 'FlowMeasure'"
|
||||||
:url="getTabUrl('FlowMeasure')"
|
:url="getTabUrl('FlowMeasure')"
|
||||||
:code="getTabCode('FlowMeasure')"
|
:code="getTabCode('FlowMeasure')"
|
||||||
:stcd="
|
:stcd="getTabCode('FlowMeasure') == 'qxd.tabs.lcjs' ? qxdSelectedStcdZq : ''"
|
||||||
getTabCode('FlowMeasure') == 'qxd.tabs.lcjs'
|
|
||||||
? qxdSelectedStcdZq
|
|
||||||
: ''
|
|
||||||
"
|
|
||||||
/>
|
/>
|
||||||
<!-- 在线监测数据 -->
|
<!-- 在线监测数据 -->
|
||||||
<FishFacilityMonitorData
|
<FishFacilityMonitorData
|
||||||
@ -247,46 +230,46 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref, watch, computed, toRef } from 'vue';
|
import { ref, watch, computed, toRef } from "vue";
|
||||||
// 导入预定义的 Tab 内容组件
|
// 导入预定义的 Tab 内容组件
|
||||||
import BasicInfo from './components/BasicInfo.vue'; // 基本信息
|
import BasicInfo from "./components/BasicInfo.vue"; // 基本信息
|
||||||
import VideoInfo from './components/videoInfo.vue'; // 实时视频
|
import VideoInfo from "./components/videoInfo.vue"; // 实时视频
|
||||||
import NoLiveVideo from './components/NoLiveVideo.vue'; // 录像视频
|
import NoLiveVideo from "./components/NoLiveVideo.vue"; // 录像视频
|
||||||
import PanoramaInfo from './components/PanoramaInfo.vue'; // 全景影像
|
import PanoramaInfo from "./components/PanoramaInfo.vue"; // 全景影像
|
||||||
import MonitorInfo from './components/MonitorInfo.vue'; // 电站运行过程线
|
import MonitorInfo from "./components/MonitorInfo.vue"; // 电站运行过程线
|
||||||
import WaterTemperature from './components/WaterTemperature.vue'; // 水温
|
import WaterTemperature from "./components/WaterTemperature.vue"; // 水温
|
||||||
import VerticalWaterTemperature from './components/VerticalWaterTemperature.vue'; // 垂向水温
|
import VerticalWaterTemperature from "./components/VerticalWaterTemperature.vue"; // 垂向水温
|
||||||
import WaterQuality from './components/WaterQuality.vue'; // 水质
|
import WaterQuality from "./components/WaterQuality.vue"; // 水质
|
||||||
import AIYXSB from './components/AIYXSB/index.vue'; // AI运行识别
|
import AIYXSB from "./components/AIYXSB/index.vue"; // AI运行识别
|
||||||
import EarlyWarningAlert from './components/EarlyWarningAlert.vue'; // 预警提示
|
import EarlyWarningAlert from "./components/EarlyWarningAlert.vue"; // 预警提示
|
||||||
import FlowMeasure from './components/FlowMeasure.vue'; // 流量监测
|
import FlowMeasure from "./components/FlowMeasure.vue"; // 流量监测
|
||||||
import FishFacilityMonitorData from './components/FishFacilityMonitorData.vue'; // 在线监测数据
|
import FishFacilityMonitorData from "./components/FishFacilityMonitorData.vue"; // 在线监测数据
|
||||||
import FishFacilityRunState from './components/FishFacilityRunState.vue'; // 运行情况
|
import FishFacilityRunState from "./components/FishFacilityRunState.vue"; // 运行情况
|
||||||
import FlowDischarge from './components/FlowDischarge.vue'; // 生态流量泄放设施 - 监测数据
|
import FlowDischarge from "./components/FlowDischarge.vue"; // 生态流量泄放设施 - 监测数据
|
||||||
import FishFacilityBuildState from './components/FishFacilityBuildState.vue'; // 建设情况
|
import FishFacilityBuildState from "./components/FishFacilityBuildState.vue"; // 建设情况
|
||||||
// 鱼类增殖站
|
// 鱼类增殖站
|
||||||
import NormalOperationData from './components/NormalOperationData/index.vue'; // 运行数据
|
import NormalOperationData from "./components/NormalOperationData/index.vue"; // 运行数据
|
||||||
import ProcessDiagram from './components/ProcessDiagram.vue'; // 过程图
|
import ProcessDiagram from "./components/ProcessDiagram.vue"; // 过程图
|
||||||
import NormalAddedSituation from './components/NormalAddedSituation.vue'; // 增值放流情况
|
import NormalAddedSituation from "./components/NormalAddedSituation.vue"; // 增值放流情况
|
||||||
import NormalResearchSituation from './components/NormalResearchSituation.vue'; // 科研情况
|
import NormalResearchSituation from "./components/NormalResearchSituation.vue"; // 科研情况
|
||||||
// 珍惜植物园
|
// 珍惜植物园
|
||||||
import NormalDataMonitoring2 from './components/NormalDataMonitoring2.vue'; // 运行数据
|
import NormalDataMonitoring2 from "./components/NormalDataMonitoring2.vue"; // 运行数据
|
||||||
// 野生动物检测 -现场录像
|
// 野生动物检测 -现场录像
|
||||||
import liveFootage from './components/liveFootage.vue'; // 现场录像
|
import liveFootage from "./components/liveFootage.vue"; // 现场录像
|
||||||
import onSitePhoto from './components/onSitePhoto.vue'; // 现场照片
|
import onSitePhoto from "./components/onSitePhoto.vue"; // 现场照片
|
||||||
/// 动物救助站
|
/// 动物救助站
|
||||||
import Normal from './components/Normal.vue'; // 监测数据
|
import Normal from "./components/Normal.vue"; // 监测数据
|
||||||
// 水生生态调查断面
|
// 水生生态调查断面
|
||||||
import Ecology from './components/Ecology.vue'; // 监测数据
|
import Ecology from "./components/Ecology.vue"; // 监测数据
|
||||||
// 水电站告警情况
|
// 水电站告警情况
|
||||||
import DZGJ from './components/DZGJ.vue'; // 水电站告警情况
|
import DZGJ from "./components/DZGJ.vue"; // 水电站告警情况
|
||||||
|
|
||||||
import FishZHFX from './components/FishZHFX.vue'; // 鱼类繁殖适宜性分析
|
import FishZHFX from "./components/FishZHFX.vue"; // 鱼类繁殖适宜性分析
|
||||||
import EcologicalFlow from './components/EcologicalFlow.vue'; // 生态流量
|
import EcologicalFlow from "./components/EcologicalFlow.vue"; // 生态流量
|
||||||
import Attachment from './components/Attachment.vue'; // 查看报告/批复文件
|
import Attachment from "./components/Attachment.vue"; // 查看报告/批复文件
|
||||||
import WaterTemperatureRep from './components/WaterTemperatureRep.vue'; // 水温对比数据
|
import WaterTemperatureRep from "./components/WaterTemperatureRep.vue"; // 水温对比数据
|
||||||
import { useModelStore } from '@/store/modules/model';
|
import { useModelStore } from "@/store/modules/model";
|
||||||
import { handleTabs } from './setting.config';
|
import { handleTabs } from "./setting.config";
|
||||||
import {
|
import {
|
||||||
getMonitorDataWt,
|
getMonitorDataWt,
|
||||||
getMonitorDataWq,
|
getMonitorDataWq,
|
||||||
@ -298,9 +281,9 @@ import {
|
|||||||
getVideoList,
|
getVideoList,
|
||||||
getMonitorDataWaterTempPowerStation,
|
getMonitorDataWaterTempPowerStation,
|
||||||
getMonitorDataWaterTempPowerStation2,
|
getMonitorDataWaterTempPowerStation2,
|
||||||
checkPowerStationTopic
|
checkPowerStationTopic,
|
||||||
} from '@/api/mapModal';
|
} from "@/api/mapModal";
|
||||||
import { useDraggable } from '@/utils/drag';
|
import { useDraggable } from "@/utils/drag";
|
||||||
// import { useRouter } from 'vue-router';
|
// import { useRouter } from 'vue-router';
|
||||||
|
|
||||||
const modelStore = useModelStore();
|
const modelStore = useModelStore();
|
||||||
@ -312,7 +295,7 @@ const isLoading = ref(true);
|
|||||||
// 电站专题配置数据状态
|
// 电站专题配置数据状态
|
||||||
const hasPowerStatData = ref(false);
|
const hasPowerStatData = ref(false);
|
||||||
// 电站专题 URL 参数(仅 hbb)
|
// 电站专题 URL 参数(仅 hbb)
|
||||||
const hbbSearchParams = '?menu=dianZhanZhuanTi&page=dianZhanZhuanTi';
|
const hbbSearchParams = "?menu=dianZhanZhuanTi&page=dianZhanZhuanTi";
|
||||||
// 判断当前是否在电站专题页面
|
// 判断当前是否在电站专题页面
|
||||||
const isPowerStatPage = computed(() => {
|
const isPowerStatPage = computed(() => {
|
||||||
return window.location.search === hbbSearchParams;
|
return window.location.search === hbbSearchParams;
|
||||||
@ -331,26 +314,26 @@ const checkPowerStationTopicData = async (stcd: string) => {
|
|||||||
try {
|
try {
|
||||||
const res = await checkPowerStationTopic({
|
const res = await checkPowerStationTopic({
|
||||||
filter: {
|
filter: {
|
||||||
logic: 'and',
|
logic: "and",
|
||||||
filters: [
|
filters: [
|
||||||
{
|
{
|
||||||
field: 'stcd',
|
field: "stcd",
|
||||||
operator: 'eq',
|
operator: "eq",
|
||||||
dataType: 'string',
|
dataType: "string",
|
||||||
value: stcd
|
value: stcd,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: 'bclData',
|
field: "bclData",
|
||||||
operator: 'contains',
|
operator: "contains",
|
||||||
dataType: 'string',
|
dataType: "string",
|
||||||
value: 'image/png'
|
value: "image/png",
|
||||||
}
|
},
|
||||||
]
|
],
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
hasPowerStatData.value = res?.data?.data && res?.data?.data?.length > 0;
|
hasPowerStatData.value = res?.data?.data && res?.data?.data?.length > 0;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('查询电站专题配置失败:', error);
|
console.error("查询电站专题配置失败:", error);
|
||||||
hasPowerStatData.value = false;
|
hasPowerStatData.value = false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -377,14 +360,14 @@ const gotoEngTopic = () => {
|
|||||||
// 使用 Vue Router 跳转
|
// 使用 Vue Router 跳转
|
||||||
// router.push(url);
|
// router.push(url);
|
||||||
*/
|
*/
|
||||||
console.log('电站专题跳转(暂未实现):', modelStore.params.stcd);
|
console.log("电站专题跳转(暂未实现):", modelStore.params.stcd);
|
||||||
};
|
};
|
||||||
|
|
||||||
// 环境判断变量(与 React 版本对齐)
|
// 环境判断变量(与 React 版本对齐)
|
||||||
const app = 'hbb'; // 模拟 React 中的 Session.getAppCode()
|
const app = "hbb"; // 模拟 React 中的 Session.getAppCode()
|
||||||
const env = 'hbb'; // 模拟 React 中的 window.__env__
|
const env = "hbb"; // 模拟 React 中的 window.__env__
|
||||||
const isLCJ = env?.startsWith('lcj');
|
const isLCJ = env?.startsWith("lcj");
|
||||||
const sttpList = ['ENG', 'WT', 'WQ', 'FP', 'FB', 'VP', 'DW', 'EQ', 'VA'];
|
const sttpList = ["ENG", "WT", "WQ", "FP", "FB", "VP", "DW", "EQ", "VA"];
|
||||||
|
|
||||||
// ==================== 异步 Tab 过滤逻辑 ====================
|
// ==================== 异步 Tab 过滤逻辑 ====================
|
||||||
// 功能8: 鱼类栖息地数据类型检查 (React: isLCJ && sttpCode === 'WE')
|
// 功能8: 鱼类栖息地数据类型检查 (React: isLCJ && sttpCode === 'WE')
|
||||||
@ -395,20 +378,18 @@ const checkFishTab = async (stcd: string) => {
|
|||||||
if (data && data.length > 0) {
|
if (data && data.length > 0) {
|
||||||
modelStore.fishTabInfo = data[0];
|
modelStore.fishTabInfo = data[0];
|
||||||
// 根据 hasWt/hasWq/hasFv 过滤 FlowMeasure 的子 Tab
|
// 根据 hasWt/hasWq/hasFv 过滤 FlowMeasure 的子 Tab
|
||||||
const flowTab = tabsConfig.value.find(
|
const flowTab = tabsConfig.value.find((item: any) => item.key === "FlowMeasure");
|
||||||
(item: any) => item.key === 'FlowMeasure'
|
|
||||||
);
|
|
||||||
if (flowTab && flowTab.tabs) {
|
if (flowTab && flowTab.tabs) {
|
||||||
flowTab.tabs = flowTab.tabs.filter((tab: any) => {
|
flowTab.tabs = flowTab.tabs.filter((tab: any) => {
|
||||||
if (tab.name === '水温(℃)' && !data[0].hasWt) return false;
|
if (tab.name === "水温(℃)" && !data[0].hasWt) return false;
|
||||||
if (tab.name === '水质' && !data[0].hasWq) return false;
|
if (tab.name === "水质" && !data[0].hasWq) return false;
|
||||||
if (tab.name === '流速(m/s)' && !data[0].hasFv) return false;
|
if (tab.name === "流速(m/s)" && !data[0].hasFv) return false;
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('鱼类数据检查失败:', error);
|
console.error("鱼类数据检查失败:", error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -417,25 +398,25 @@ const checkAnalysisAvailability = async (stcd: string) => {
|
|||||||
try {
|
try {
|
||||||
const filter = {
|
const filter = {
|
||||||
filter: {
|
filter: {
|
||||||
logic: 'and',
|
logic: "and",
|
||||||
filters: [
|
filters: [
|
||||||
{ field: 'stcd', operator: 'eq', value: stcd },
|
{ field: "stcd", operator: "eq", value: stcd },
|
||||||
{ field: 'mway', operator: 'eq', value: '2' },
|
{ field: "mway", operator: "eq", value: "2" },
|
||||||
{ field: 'dtinType', operator: 'eq', value: '0' },
|
{ field: "dtinType", operator: "eq", value: "0" },
|
||||||
{
|
{
|
||||||
logic: 'or',
|
logic: "or",
|
||||||
filters: [
|
filters: [
|
||||||
{ field: 'engLctn3Code', operator: 'isnotnull' },
|
{ field: "engLctn3Code", operator: "isnotnull" },
|
||||||
{ field: 'engLctn1Code', operator: 'isnotnull' }
|
{ field: "engLctn1Code", operator: "isnotnull" },
|
||||||
]
|
],
|
||||||
}
|
},
|
||||||
]
|
],
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
const res = await checkAnalysisShow(filter);
|
const res = await checkAnalysisShow(filter);
|
||||||
modelStore.wqShow = !!res?.data;
|
modelStore.wqShow = !!res?.data;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('水质 AI 预测检查失败:', error);
|
console.error("水质 AI 预测检查失败:", error);
|
||||||
modelStore.wqShow = false;
|
modelStore.wqShow = false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -448,26 +429,26 @@ const checkWaterTempConfig = async (stcd: string) => {
|
|||||||
getMonitorDataWaterTempPowerStation2({ stcd }),
|
getMonitorDataWaterTempPowerStation2({ stcd }),
|
||||||
getBuildState({
|
getBuildState({
|
||||||
filter: {
|
filter: {
|
||||||
logic: 'and',
|
logic: "and",
|
||||||
filters: [{ field: 'stcd', operator: 'eq', value: stcd }]
|
filters: [{ field: "stcd", operator: "eq", value: stcd }],
|
||||||
}
|
},
|
||||||
})
|
}),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
modelStore.wtConfig = {
|
modelStore.wtConfig = {
|
||||||
ioWtrv: configRes?.data?.ioWtrv,
|
ioWtrv: configRes?.data?.ioWtrv,
|
||||||
hasRstcdWtvt: dwRes?.data
|
hasRstcdWtvt: dwRes?.data,
|
||||||
};
|
};
|
||||||
|
|
||||||
// 检查出入库水温对比
|
// 检查出入库水温对比
|
||||||
const buildData = buildRes?.data?.[0];
|
const buildData = buildRes?.data?.[0];
|
||||||
if (!buildData?.engIwtCode && !buildData?.engDwtCode) {
|
if (!buildData?.engIwtCode && !buildData?.engDwtCode) {
|
||||||
tabsConfig.value = tabsConfig.value.filter(
|
tabsConfig.value = tabsConfig.value.filter(
|
||||||
(item: any) => item.name !== '出入库水温对比'
|
(item: any) => item.name !== "出入库水温对比"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('水温配置检查失败:', error);
|
console.error("水温配置检查失败:", error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -476,28 +457,23 @@ const checkBuildState = async (stcd: string) => {
|
|||||||
try {
|
try {
|
||||||
const res = await getBuildState({
|
const res = await getBuildState({
|
||||||
filter: {
|
filter: {
|
||||||
logic: 'and',
|
logic: "and",
|
||||||
filters: [{ field: 'stcd', operator: 'eq', value: stcd }]
|
filters: [{ field: "stcd", operator: "eq", value: stcd }],
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const buid = res?.data?.[0]?.bldsttCcode;
|
const buid = res?.data?.[0]?.bldsttCcode;
|
||||||
// 0 未建 1 在建 2 已建
|
// 0 未建 1 在建 2 已建
|
||||||
const zj = ['基础信息', '实时视频', '全景影像', '查看报告'];
|
const zj = ["基础信息", "实时视频", "全景影像", "查看报告"];
|
||||||
const wj = ['基础信息', '查看报告'];
|
const wj = ["基础信息", "查看报告"];
|
||||||
|
|
||||||
if (res?.data?.length > 0) {
|
if (res?.data?.length > 0) {
|
||||||
const zJTabData = tabsConfig.value?.filter((item: any) =>
|
const zJTabData = tabsConfig.value?.filter((item: any) => zj.includes(item.name));
|
||||||
zj.includes(item.name)
|
const wJTabData = tabsConfig.value?.filter((item: any) => wj.includes(item.name));
|
||||||
);
|
tabsConfig.value = buid == 1 ? zJTabData : buid == 0 ? wJTabData : tabsConfig.value;
|
||||||
const wJTabData = tabsConfig.value?.filter((item: any) =>
|
|
||||||
wj.includes(item.name)
|
|
||||||
);
|
|
||||||
tabsConfig.value =
|
|
||||||
buid == 1 ? zJTabData : buid == 0 ? wJTabData : tabsConfig.value;
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('建设状态检查失败:', error);
|
console.error("建设状态检查失败:", error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -507,17 +483,17 @@ const setQGCTabList = async (_tabs: any, stcd: string) => {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
// 1. 电站建设状态过滤
|
// 1. 电站建设状态过滤
|
||||||
if (modelStore.params.sttp == 'ENG' && modelStore.params.eqtp != 'QEC') {
|
if (modelStore.params.sttp == "ENG" && modelStore.params.eqtp != "QEC") {
|
||||||
const res = await getBuildState({
|
const res = await getBuildState({
|
||||||
filter: {
|
filter: {
|
||||||
logic: 'and',
|
logic: "and",
|
||||||
filters: [{ field: 'stcd', operator: 'eq', value: stcd }]
|
filters: [{ field: "stcd", operator: "eq", value: stcd }],
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const buid = res?.data?.[0]?.bldsttCcode;
|
const buid = res?.data?.[0]?.bldsttCcode;
|
||||||
const zj = ['基础信息', '实时视频', '全景影像', '查看报告'];
|
const zj = ["基础信息", "实时视频", "全景影像", "查看报告"];
|
||||||
const wj = ['基础信息', '查看报告'];
|
const wj = ["基础信息", "查看报告"];
|
||||||
|
|
||||||
if (res?.data?.length > 0) {
|
if (res?.data?.length > 0) {
|
||||||
const zJTabData = tabs?.filter((item: any) => zj.includes(item.name));
|
const zJTabData = tabs?.filter((item: any) => zj.includes(item.name));
|
||||||
@ -531,20 +507,16 @@ const setQGCTabList = async (_tabs: any, stcd: string) => {
|
|||||||
let newTabs = [...tabs];
|
let newTabs = [...tabs];
|
||||||
if (res4 && res4.data && res4.data.relList) {
|
if (res4 && res4.data && res4.data.relList) {
|
||||||
const relList = res4.data.relList;
|
const relList = res4.data.relList;
|
||||||
const jianCei = relList.find((item: any) => item.sttpCode === 'WTVT');
|
const jianCei = relList.find((item: any) => item.sttpCode === "WTVT");
|
||||||
const aiShiBie = relList.filter((item: any) =>
|
const aiShiBie = relList.filter((item: any) => item?.sttpCode?.startsWith("AI"));
|
||||||
item?.sttpCode?.startsWith('AI')
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!jianCei) {
|
if (!jianCei) {
|
||||||
newTabs = newTabs.filter(
|
newTabs = newTabs.filter((item) => item.type !== "VerticalWaterTemperature");
|
||||||
item => item.type !== 'VerticalWaterTemperature'
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
modelStore.verticalWaterStcd = jianCei.stcd;
|
modelStore.verticalWaterStcd = jianCei.stcd;
|
||||||
}
|
}
|
||||||
if (!aiShiBie.length) {
|
if (!aiShiBie.length) {
|
||||||
newTabs = newTabs.filter(item => item.type !== 'AIYXSB');
|
newTabs = newTabs.filter((item) => item.type !== "AIYXSB");
|
||||||
} else {
|
} else {
|
||||||
modelStore.aiSites = aiShiBie;
|
modelStore.aiSites = aiShiBie;
|
||||||
}
|
}
|
||||||
@ -553,65 +525,65 @@ const setQGCTabList = async (_tabs: any, stcd: string) => {
|
|||||||
// 3. 视频存在性检查
|
// 3. 视频存在性检查
|
||||||
let params = {
|
let params = {
|
||||||
filter: {
|
filter: {
|
||||||
logic: 'and',
|
logic: "and",
|
||||||
filters: [
|
filters: [
|
||||||
{
|
{
|
||||||
field: 'sttpCode',
|
field: "sttpCode",
|
||||||
operator: 'startswith',
|
operator: "startswith",
|
||||||
value: 'VD_'
|
value: "VD_",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
logic: 'or',
|
logic: "or",
|
||||||
filters: [
|
filters: [
|
||||||
{
|
{
|
||||||
field: 'rstcd',
|
field: "rstcd",
|
||||||
operator: 'eq',
|
operator: "eq",
|
||||||
dataType: 'string',
|
dataType: "string",
|
||||||
value: stcd
|
value: stcd,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: 'stcd',
|
field: "stcd",
|
||||||
operator: 'eq',
|
operator: "eq",
|
||||||
dataType: 'string',
|
dataType: "string",
|
||||||
value: stcd
|
value: stcd,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: 'fhstcd',
|
field: "fhstcd",
|
||||||
operator: 'eq',
|
operator: "eq",
|
||||||
dataType: 'string',
|
dataType: "string",
|
||||||
value: stcd
|
value: stcd,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: 'stCode',
|
field: "stCode",
|
||||||
operator: 'eq',
|
operator: "eq",
|
||||||
dataType: 'string',
|
dataType: "string",
|
||||||
value: stcd
|
value: stcd,
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
select: ['stcd', 'stnm', 'sttpCode', 'sttpName', 'playWay', 'url'],
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
select: ["stcd", "stnm", "sttpCode", "sttpName", "playWay", "url"],
|
||||||
sort: [
|
sort: [
|
||||||
{
|
{
|
||||||
field: 'stnm',
|
field: "stnm",
|
||||||
dir: 'asc'
|
dir: "asc",
|
||||||
}
|
},
|
||||||
]
|
],
|
||||||
};
|
};
|
||||||
const res2 = await getVideoList(params);
|
const res2 = await getVideoList(params);
|
||||||
console.log('', modelStore.params.sttp);
|
console.log("", modelStore.params.sttp);
|
||||||
if (
|
if (
|
||||||
res2?.data?.data?.length > 0 ||
|
res2?.data?.data?.length > 0 ||
|
||||||
res2?.data?.records?.length > 0 ||
|
res2?.data?.records?.length > 0 ||
|
||||||
modelStore.params.sttp == 'WQ'
|
modelStore.params.sttp == "WQ"
|
||||||
) {
|
) {
|
||||||
tabsConfig.value = newTabs;
|
tabsConfig.value = newTabs;
|
||||||
} else {
|
} else {
|
||||||
tabsConfig.value = newTabs.filter((item: any) => item.type != 'video');
|
tabsConfig.value = newTabs.filter((item: any) => item.type != "video");
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('QGC Tab 过滤失败:', error);
|
console.error("QGC Tab 过滤失败:", error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -628,38 +600,36 @@ const applyTabFilters = async (params: any) => {
|
|||||||
if (!stcd) return;
|
if (!stcd) return;
|
||||||
|
|
||||||
// 按照 React 的分支逻辑执行(注意运算符优先级)
|
// 按照 React 的分支逻辑执行(注意运算符优先级)
|
||||||
if (isLCJ && sttpCode === 'VP') {
|
if (isLCJ && sttpCode === "VP") {
|
||||||
// LCJ 珍稀植物园视频检查
|
// LCJ 珍稀植物园视频检查
|
||||||
const res = await getVideoList({
|
const res = await getVideoList({
|
||||||
filter: {
|
filter: {
|
||||||
logic: 'and',
|
logic: "and",
|
||||||
filters: [{ field: 'stcd', operator: 'eq', value: stcd }]
|
filters: [{ field: "stcd", operator: "eq", value: stcd }],
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
if (!res?.data?.data?.length && !res?.data?.records?.length) {
|
if (!res?.data?.data?.length && !res?.data?.records?.length) {
|
||||||
tabsConfig.value = tabsConfig.value.filter(
|
tabsConfig.value = tabsConfig.value.filter((item) => item.name != "实时视频");
|
||||||
item => item.name != '实时视频'
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
} else if (isLCJ && sttpCode === 'WE') {
|
} else if (isLCJ && sttpCode === "WE") {
|
||||||
// LCJ 鱼类栖息地数据类型检查
|
// LCJ 鱼类栖息地数据类型检查
|
||||||
await checkFishTab(stcd);
|
await checkFishTab(stcd);
|
||||||
} else if (sttpCode === 'WQ' || sttpCode === 'WQFB' || sttpCode === 'wq') {
|
} else if (sttpCode === "WQ" || sttpCode === "WQFB" || sttpCode === "wq") {
|
||||||
// 水质 AI 预测检查(React 逻辑:WQ 需要条件,WQFB/wq 无条件)
|
// 水质 AI 预测检查(React 逻辑:WQ 需要条件,WQFB/wq 无条件)
|
||||||
await checkAnalysisAvailability(stcd);
|
await checkAnalysisAvailability(stcd);
|
||||||
} else if (sttpCode === 'WT' || sttpCode === 'wt') {
|
} else if (sttpCode === "WT" || sttpCode === "wt") {
|
||||||
// 水温站特殊处理(React 逻辑:WT 需要条件,wt 无条件)
|
// 水温站特殊处理(React 逻辑:WT 需要条件,wt 无条件)
|
||||||
await checkWaterTempConfig(stcd);
|
await checkWaterTempConfig(stcd);
|
||||||
} else if (
|
} else if (
|
||||||
sttpList.includes(sttpCode?.toUpperCase()) ||
|
sttpList.includes(sttpCode?.toUpperCase()) ||
|
||||||
sttpCode?.startsWith('FP_') ||
|
sttpCode?.startsWith("FP_") ||
|
||||||
sttpCode?.includes('DW')
|
sttpCode?.includes("DW")
|
||||||
) {
|
) {
|
||||||
// QGC 完整 Tab 过滤(包含建设状态、垂向水温、AI识别、视频检查)
|
// QGC 完整 Tab 过滤(包含建设状态、垂向水温、AI识别、视频检查)
|
||||||
await setQGCTabList(tabsConfig.value, stcd);
|
await setQGCTabList(tabsConfig.value, stcd);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Tab 过滤逻辑执行失败:', error);
|
console.error("Tab 过滤逻辑执行失败:", error);
|
||||||
} finally {
|
} finally {
|
||||||
isLoading.value = false; // 无论成功失败都停止加载
|
isLoading.value = false; // 无论成功失败都停止加载
|
||||||
}
|
}
|
||||||
@ -675,31 +645,31 @@ const qxdConfigMap: Record<
|
|||||||
selectProp: string;
|
selectProp: string;
|
||||||
}
|
}
|
||||||
> = {
|
> = {
|
||||||
'qxd.tabs.jcsj': {
|
"qxd.tabs.jcsj": {
|
||||||
// 水温
|
// 水温
|
||||||
sttpCode: 'WTRV',
|
sttpCode: "WTRV",
|
||||||
apiFn: getMonitorDataWt,
|
apiFn: getMonitorDataWt,
|
||||||
selectProp: 'qxdSelectedStcd'
|
selectProp: "qxdSelectedStcd",
|
||||||
},
|
},
|
||||||
'qxd.tabs.szjc': {
|
"qxd.tabs.szjc": {
|
||||||
// 水质
|
// 水质
|
||||||
sttpCode: 'WQ',
|
sttpCode: "WQ",
|
||||||
apiFn: getMonitorDataWq,
|
apiFn: getMonitorDataWq,
|
||||||
selectProp: 'qxdSelectedStcdWq'
|
selectProp: "qxdSelectedStcdWq",
|
||||||
},
|
},
|
||||||
'qxd.tabs.lcjs': {
|
"qxd.tabs.lcjs": {
|
||||||
// 流量监测
|
// 流量监测
|
||||||
sttpCode: 'ZQ',
|
sttpCode: "ZQ",
|
||||||
apiFn: getMonitorDataZq,
|
apiFn: getMonitorDataZq,
|
||||||
selectProp: 'qxdSelectedStcdZq'
|
selectProp: "qxdSelectedStcdZq",
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
// 各模式的站点选项和选中值
|
// 各模式的站点选项和选中值
|
||||||
const qxdStationOptions = ref<{ label: string; value: string }[]>([]);
|
const qxdStationOptions = ref<{ label: string; value: string }[]>([]);
|
||||||
const qxdSelectedStcd = ref<string>('');
|
const qxdSelectedStcd = ref<string>("");
|
||||||
const qxdSelectedStcdWq = ref<string>('');
|
const qxdSelectedStcdWq = ref<string>("");
|
||||||
const qxdSelectedStcdZq = ref<string>('');
|
const qxdSelectedStcdZq = ref<string>("");
|
||||||
const qxdLoadedCodes = ref<Set<string>>(new Set());
|
const qxdLoadedCodes = ref<Set<string>>(new Set());
|
||||||
|
|
||||||
// 获取当前tab对应的qxd配置
|
// 获取当前tab对应的qxd配置
|
||||||
@ -711,16 +681,16 @@ const getCurrentQxdConfig = (tabKey: string) => {
|
|||||||
// 获取选中的stcd
|
// 获取选中的stcd
|
||||||
const getQxdSelectedStcd = (tabKey: string) => {
|
const getQxdSelectedStcd = (tabKey: string) => {
|
||||||
const config = getCurrentQxdConfig(tabKey);
|
const config = getCurrentQxdConfig(tabKey);
|
||||||
if (!config) return '';
|
if (!config) return "";
|
||||||
switch (config.selectProp) {
|
switch (config.selectProp) {
|
||||||
case 'qxdSelectedStcd':
|
case "qxdSelectedStcd":
|
||||||
return qxdSelectedStcd.value;
|
return qxdSelectedStcd.value;
|
||||||
case 'qxdSelectedStcdWq':
|
case "qxdSelectedStcdWq":
|
||||||
return qxdSelectedStcdWq.value;
|
return qxdSelectedStcdWq.value;
|
||||||
case 'qxdSelectedStcdZq':
|
case "qxdSelectedStcdZq":
|
||||||
return qxdSelectedStcdZq.value;
|
return qxdSelectedStcdZq.value;
|
||||||
default:
|
default:
|
||||||
return '';
|
return "";
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -729,13 +699,13 @@ const setQxdSelectedStcd = (tabKey: string, value: string) => {
|
|||||||
const config = getCurrentQxdConfig(tabKey);
|
const config = getCurrentQxdConfig(tabKey);
|
||||||
if (!config) return;
|
if (!config) return;
|
||||||
switch (config.selectProp) {
|
switch (config.selectProp) {
|
||||||
case 'qxdSelectedStcd':
|
case "qxdSelectedStcd":
|
||||||
qxdSelectedStcd.value = value;
|
qxdSelectedStcd.value = value;
|
||||||
break;
|
break;
|
||||||
case 'qxdSelectedStcdWq':
|
case "qxdSelectedStcdWq":
|
||||||
qxdSelectedStcdWq.value = value;
|
qxdSelectedStcdWq.value = value;
|
||||||
break;
|
break;
|
||||||
case 'qxdSelectedStcdZq':
|
case "qxdSelectedStcdZq":
|
||||||
qxdSelectedStcdZq.value = value;
|
qxdSelectedStcdZq.value = value;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -751,30 +721,30 @@ const loadQxdStationList = async (tabKey: string) => {
|
|||||||
try {
|
try {
|
||||||
const params = {
|
const params = {
|
||||||
filter: {
|
filter: {
|
||||||
logic: 'and',
|
logic: "and",
|
||||||
filters: [
|
filters: [
|
||||||
{
|
{
|
||||||
field: 'fhstcd',
|
field: "fhstcd",
|
||||||
operator: 'eq',
|
operator: "eq",
|
||||||
dataType: 'string',
|
dataType: "string",
|
||||||
value: modelStore.params.stcd
|
value: modelStore.params.stcd,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: 'sttpCode',
|
field: "sttpCode",
|
||||||
operator: 'eq',
|
operator: "eq",
|
||||||
dataType: 'string',
|
dataType: "string",
|
||||||
value: config.sttpCode
|
value: config.sttpCode,
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
select: ['stcd', 'stnm']
|
],
|
||||||
|
},
|
||||||
|
select: ["stcd", "stnm"],
|
||||||
};
|
};
|
||||||
const res = await config.apiFn(params);
|
const res = await config.apiFn(params);
|
||||||
const stations = res?.data?.data || res?.data?.records || [];
|
const stations = res?.data?.data || res?.data?.records || [];
|
||||||
if (stations.length > 0) {
|
if (stations.length > 0) {
|
||||||
qxdStationOptions.value = stations.map((item: any) => ({
|
qxdStationOptions.value = stations.map((item: any) => ({
|
||||||
label: item.stnm,
|
label: item.stnm,
|
||||||
value: item.stcd
|
value: item.stcd,
|
||||||
}));
|
}));
|
||||||
setQxdSelectedStcd(tabKey, stations[0].stcd);
|
setQxdSelectedStcd(tabKey, stations[0].stcd);
|
||||||
}
|
}
|
||||||
@ -787,7 +757,7 @@ const loadQxdStationList = async (tabKey: string) => {
|
|||||||
// 获取当前激活tab对应的选中stcd (支持v-model双向绑定)
|
// 获取当前激活tab对应的选中stcd (支持v-model双向绑定)
|
||||||
const currentQxdSelectedStcd = computed({
|
const currentQxdSelectedStcd = computed({
|
||||||
get: () => getQxdSelectedStcd(currentActiveKey.value),
|
get: () => getQxdSelectedStcd(currentActiveKey.value),
|
||||||
set: (val: string) => setQxdSelectedStcd(currentActiveKey.value, val)
|
set: (val: string) => setQxdSelectedStcd(currentActiveKey.value, val),
|
||||||
});
|
});
|
||||||
|
|
||||||
// 判断是否是qxd模式
|
// 判断是否是qxd模式
|
||||||
@ -812,7 +782,7 @@ interface TabItem {
|
|||||||
// 获取 Tab 配置项的 URL
|
// 获取 Tab 配置项的 URL
|
||||||
const getTabUrl = (key: string) => {
|
const getTabUrl = (key: string) => {
|
||||||
const tab = tabsConfig.value.find((item: any) => item.key === key);
|
const tab = tabsConfig.value.find((item: any) => item.key === key);
|
||||||
return tab?.url || '';
|
return tab?.url || "";
|
||||||
};
|
};
|
||||||
// 获取 Tab 配置项的子 Tab
|
// 获取 Tab 配置项的子 Tab
|
||||||
const getTabChildren = (key: string) => {
|
const getTabChildren = (key: string) => {
|
||||||
@ -823,7 +793,7 @@ const getTabChildren = (key: string) => {
|
|||||||
// 获取 Tab 配置项的 code
|
// 获取 Tab 配置项的 code
|
||||||
const getTabCode = (key: string) => {
|
const getTabCode = (key: string) => {
|
||||||
const tab = tabsConfig.value.find((item: any) => item.key === key);
|
const tab = tabsConfig.value.find((item: any) => item.key === key);
|
||||||
return tab?.code || '';
|
return tab?.code || "";
|
||||||
};
|
};
|
||||||
// 定义 Props
|
// 定义 Props
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
@ -834,22 +804,22 @@ const props = defineProps<{
|
|||||||
}>();
|
}>();
|
||||||
|
|
||||||
// 启用弹窗拖拽(限制在可视区域内)
|
// 启用弹窗拖拽(限制在可视区域内)
|
||||||
useDraggable(toRef(props, 'visible'), { boundary: true, resetOnOpen: true });
|
useDraggable(toRef(props, "visible"), { boundary: true, resetOnOpen: true });
|
||||||
// 定义 Emits
|
// 定义 Emits
|
||||||
// 添加 'update:activeKey' 以支持 v-model:active-key
|
// 添加 'update:activeKey' 以支持 v-model:active-key
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(e: 'update:visible', value: boolean): void;
|
(e: "update:visible", value: boolean): void;
|
||||||
(e: 'change', key: string): void;
|
(e: "change", key: string): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
// 内部维护的 activeKey
|
// 内部维护的 activeKey
|
||||||
const currentActiveKey = ref<string>('');
|
const currentActiveKey = ref<string>("");
|
||||||
const anchor = ref<any>('');
|
const anchor = ref<any>("");
|
||||||
|
|
||||||
// 监听外部传入的 activeKey 变化,同步到内部状态
|
// 监听外部传入的 activeKey 变化,同步到内部状态
|
||||||
watch(
|
watch(
|
||||||
() => props.activeKey,
|
() => props.activeKey,
|
||||||
newVal => {
|
(newVal) => {
|
||||||
if (newVal && newVal !== currentActiveKey.value) {
|
if (newVal && newVal !== currentActiveKey.value) {
|
||||||
currentActiveKey.value = newVal;
|
currentActiveKey.value = newVal;
|
||||||
}
|
}
|
||||||
@ -858,14 +828,14 @@ watch(
|
|||||||
);
|
);
|
||||||
watch(
|
watch(
|
||||||
() => modelStore.params,
|
() => modelStore.params,
|
||||||
async newVal => {
|
async (newVal) => {
|
||||||
if (!newVal || Object.keys(newVal).length === 0) return;
|
if (!newVal || Object.keys(newVal).length === 0) return;
|
||||||
console.log(newVal);
|
console.log(newVal);
|
||||||
anchor.value = newVal;
|
anchor.value = newVal;
|
||||||
let params = newVal;
|
let params = newVal;
|
||||||
if (['VD', 'WTRV', 'ZQ', 'WQ'].includes(params?.sttpCode)) {
|
if (["VD", "WTRV", "ZQ", "WQ"].includes(params?.sttpCode)) {
|
||||||
params.sttp = params.sttpCode;
|
params.sttp = params.sttpCode;
|
||||||
} else if (['ENG_ALARM'].includes(params?.sttpMap)) {
|
} else if (["ENG_ALARM"].includes(params?.sttpMap)) {
|
||||||
params.sttp = params.sttpMap;
|
params.sttp = params.sttpMap;
|
||||||
}
|
}
|
||||||
tabsConfig.value = handleTabs({ ...params });
|
tabsConfig.value = handleTabs({ ...params });
|
||||||
@ -874,8 +844,8 @@ watch(
|
|||||||
currentActiveKey.value = value?.key;
|
currentActiveKey.value = value?.key;
|
||||||
|
|
||||||
// 电站类型时,查询电站专题配置数据
|
// 电站类型时,查询电站专题配置数据
|
||||||
if (params?.sttp?.toUpperCase() === 'ENG') {
|
if (params?.sttp?.toUpperCase() === "ENG") {
|
||||||
checkPowerStationTopicData(params.stcd);
|
// checkPowerStationTopicData(params.stcd);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 应用异步 Tab 过滤(非阻塞式)
|
// 应用异步 Tab 过滤(非阻塞式)
|
||||||
@ -887,7 +857,7 @@ watch(
|
|||||||
// 监听 tab 切换,如果是 qxd 模式,加载站点列表
|
// 监听 tab 切换,如果是 qxd 模式,加载站点列表
|
||||||
watch(
|
watch(
|
||||||
() => currentActiveKey.value,
|
() => currentActiveKey.value,
|
||||||
activeKey => {
|
(activeKey) => {
|
||||||
const code = getTabCode(activeKey);
|
const code = getTabCode(activeKey);
|
||||||
if (code && qxdConfigMap[code]) {
|
if (code && qxdConfigMap[code]) {
|
||||||
loadQxdStationList(activeKey);
|
loadQxdStationList(activeKey);
|
||||||
@ -900,42 +870,42 @@ const onTabChange = (key: string) => {
|
|||||||
currentActiveKey.value = key;
|
currentActiveKey.value = key;
|
||||||
// 通知父组件更新 activeKey (支持 v-model)
|
// 通知父组件更新 activeKey (支持 v-model)
|
||||||
// 通知父组件发生了切换事件 (用于业务逻辑,如加载数据)
|
// 通知父组件发生了切换事件 (用于业务逻辑,如加载数据)
|
||||||
emit('change', key);
|
emit("change", key);
|
||||||
};
|
};
|
||||||
|
|
||||||
// 关闭弹窗
|
// 关闭弹窗
|
||||||
const handleClose = () => {
|
const handleClose = () => {
|
||||||
emit('update:visible', false);
|
emit("update:visible", false);
|
||||||
modelStore.params = {
|
modelStore.params = {
|
||||||
sttp: 'eng',
|
sttp: "eng",
|
||||||
stcd: '',
|
stcd: "",
|
||||||
date: '',
|
date: "",
|
||||||
show: true,
|
show: true,
|
||||||
enfc: '0',
|
enfc: "0",
|
||||||
eqtp: '',
|
eqtp: "",
|
||||||
baseId: '',
|
baseId: "",
|
||||||
sttpCode: '',
|
sttpCode: "",
|
||||||
sttpMap: ''
|
sttpMap: "",
|
||||||
};
|
};
|
||||||
// 清理状态,避免影响下次打开
|
// 清理状态,避免影响下次打开
|
||||||
modelStore.showStcdSelector = false;
|
modelStore.showStcdSelector = false;
|
||||||
modelStore.stcdOptions = [];
|
modelStore.stcdOptions = [];
|
||||||
// 清理qxd模式状态
|
// 清理qxd模式状态
|
||||||
qxdStationOptions.value = [];
|
qxdStationOptions.value = [];
|
||||||
qxdSelectedStcd.value = '';
|
qxdSelectedStcd.value = "";
|
||||||
qxdSelectedStcdWq.value = '';
|
qxdSelectedStcdWq.value = "";
|
||||||
qxdSelectedStcdZq.value = '';
|
qxdSelectedStcdZq.value = "";
|
||||||
qxdLoadedCodes.value = new Set();
|
qxdLoadedCodes.value = new Set();
|
||||||
// 清理电站专题状态
|
// 清理电站专题状态
|
||||||
hasPowerStatData.value = false;
|
hasPowerStatData.value = false;
|
||||||
modelStore.verticalWaterStcd = '';
|
modelStore.verticalWaterStcd = "";
|
||||||
modelStore.aiSites = [];
|
modelStore.aiSites = [];
|
||||||
modelStore.wtConfig = {};
|
modelStore.wtConfig = {};
|
||||||
modelStore.filter = {
|
modelStore.filter = {
|
||||||
stllgzlx: '', // 生态流量 - 规则类型
|
stllgzlx: "", // 生态流量 - 规则类型
|
||||||
stllTmType: '', // 生态流量 - 时间类型
|
stllTmType: "", // 生态流量 - 时间类型
|
||||||
tm: '',
|
tm: "",
|
||||||
rangeTm: []
|
rangeTm: [],
|
||||||
};
|
};
|
||||||
modelStore.wqShow = false;
|
modelStore.wqShow = false;
|
||||||
modelStore.fishTabInfo = null;
|
modelStore.fishTabInfo = null;
|
||||||
@ -945,7 +915,7 @@ const handleClose = () => {
|
|||||||
const modalData = ref(props.data);
|
const modalData = ref(props.data);
|
||||||
watch(
|
watch(
|
||||||
() => props.data,
|
() => props.data,
|
||||||
newVal => {
|
(newVal) => {
|
||||||
modalData.value = newVal;
|
modalData.value = newVal;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@ -956,7 +926,7 @@ const filterOption = (input: string, option: any) => {
|
|||||||
|
|
||||||
// 处理测站切换
|
// 处理测站切换
|
||||||
const handleStcdChange = (newStcd: string) => {
|
const handleStcdChange = (newStcd: string) => {
|
||||||
console.log('【测站切换】', newStcd);
|
console.log("【测站切换】", newStcd);
|
||||||
// 由于 BasicInfo.vue 监听了 stcd 变化,这里只需更新 store
|
// 由于 BasicInfo.vue 监听了 stcd 变化,这里只需更新 store
|
||||||
// 如果需要刷新所有 tab 的数据,可以在这里触发事件
|
// 如果需要刷新所有 tab 的数据,可以在这里触发事件
|
||||||
};
|
};
|
||||||
|
|||||||
@ -11,7 +11,7 @@ VITE_APP_BASE_URL = 'http://localhost:8093'
|
|||||||
# 测试环境
|
# 测试环境
|
||||||
# VITE_APP_BASE_URL = 'http://172.16.21.142:8093'
|
# VITE_APP_BASE_URL = 'http://172.16.21.142:8093'
|
||||||
# 汤伟
|
# 汤伟
|
||||||
VITE_APP_BASE_URL = 'http://10.84.111.235:8093'
|
VITE_APP_BASE_URL = 'http://10.84.216.252:8093'
|
||||||
# 李林
|
# 李林
|
||||||
# VITE_APP_BASE_URL = 'http://10.84.111.25:8093'
|
# VITE_APP_BASE_URL = 'http://10.84.111.25:8093'
|
||||||
# VITE_APP_BASE_URL = 'http://10.84.111.211:8093'
|
# VITE_APP_BASE_URL = 'http://10.84.111.211:8093'
|
||||||
|
|||||||
6
frontend/src/assets/svg/Application.svg
Normal file
6
frontend/src/assets/svg/Application.svg
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<svg version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" width="16px" height="16px" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g transform="matrix(1 0 0 1 -1786 -829 )">
|
||||||
|
<path d="M 11.9999999556426 0 L 4.00000004435742 0 C 3.053226196683 0 2.28571434669061 0.818679320547503 2.28571434669061 1.82857143452381 L 2.28571434669061 3.1512380952381 C 1.60057497972548 3.40981403899944 1.14264753458247 4.10097919714247 1.14285720933208 4.87619047023809 L 1.14285720933208 6.19885714880952 C 0.457717842367174 6.45743309257087 -0.000209602775839812 7.1485982507139 0 7.92380952380952 L 0 12.8000000059524 C 0 14.48315352556 1.27918649766411 15.8476190416667 2.85714290699912 15.8476190416667 L 13.1428570930009 15.8476190416667 C 14.7208135023359 15.8476190416667 15.9999999187803 14.48315352556 15.9999999280262 12.8000000059524 L 15.9999999280262 7.92380952380952 C 16.0002096027758 7.1485982507139 15.5422821576328 6.45743309257087 14.8571427906679 6.19885714880952 L 14.8571427906679 4.87619047023809 C 14.8573524654175 4.10097919714247 14.3994250202745 3.40981403899944 13.7142856533094 3.1512380952381 L 13.7142856533094 1.82857143452381 C 13.7142856533094 0.818679320547503 12.946773803317 0 11.9999999556426 0 Z M 13.7142856533094 4.87619047023809 L 13.7142856533094 6.0952380952381 L 10.2857142579755 6.0952380952381 C 9.97012297849938 6.0952380952381 9.71428569766704 6.36813119743816 9.71428569766704 6.70476189880952 C 9.71428569766704 7.71465401278583 8.94677384767442 8.53333332738094 8 8.53333332738094 C 7.05322615232558 8.53333332738094 6.28571430233296 7.71465401278583 6.28571430233296 6.70476189880952 C 6.28571430233296 6.36813119743816 6.02987702150062 6.0952380952381 5.71428574202446 6.0952380952381 L 2.28571434669061 6.0952380952381 L 2.28571434669061 4.8761904702381 C 2.28571434669061 4.53955976886673 2.54155162752295 4.26666666666667 2.85714290699912 4.26666666666667 L 13.1428570930009 4.26666666666667 C 13.4584483724771 4.26666666666667 13.7142856533094 4.53955976886673 13.7142856533094 4.87619047023809 Z M 1.14285720933208 7.92380952380952 C 1.14285720933208 7.58717882243816 1.39869449016464 7.3142857202381 1.71428576964081 7.3142857202381 L 5.20000003272435 7.31428572023809 C 5.47269110222078 8.73247492680181 6.64277123544298 9.75055874974881 8 9.75055874974881 C 9.35722876455702 9.75055874974881 10.5273088977792 8.73247492680181 10.7999999672757 7.3142857202381 L 14.2857142303592 7.3142857202381 C 14.6013055098354 7.3142857202381 14.8571427906679 7.58717882243816 14.8571427906679 7.92380952380952 L 14.8571427906679 12.8000000059524 C 14.8571427906679 13.8098921199287 14.0896309406753 14.6285714345238 13.1428570930009 14.6285714345238 L 2.85714290699912 14.6285714345238 C 1.91036905932469 14.6285714345238 1.14285720933208 13.8098921199287 1.14285720933208 12.8000000059524 L 1.14285720933208 7.92380952380952 Z M 12.5714285326922 1.82857143452381 L 12.5714285326922 3.04761904166666 L 3.42857146730785 3.04761904166666 L 3.42857146730785 1.82857143452381 C 3.42857146730785 1.49194071529531 3.68440874814019 1.21904761309524 4.00000002761635 1.21904761309524 L 11.9999999556426 1.21904761309524 C 12.3155912518598 1.21904761309524 12.5714285326922 1.49194071529531 12.5714285326922 1.82857141666667 Z M 5.71428574202446 12.1904761845238 C 5.71428574202446 11.8538454831524 5.9701230228568 11.5809523809524 6.28571430233296 11.5809523809524 L 9.71428569766704 11.5809523809524 C 10.0298769817662 11.5809523809524 10.2857142663461 11.8538454871499 10.2857142663461 12.1904761934524 C 10.2857142663461 12.5271068997549 10.0298769817662 12.8000000059524 9.71428569766704 12.8000000059524 L 6.28571430233296 12.8000000059524 C 5.9701230228568 12.8000000059524 5.71428574202446 12.5271069037523 5.71428574202446 12.1904762023809 Z " fill-rule="nonzero" fill="#255aa7" stroke="none" transform="matrix(1 0 0 1 1786 829 )" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
6
frontend/src/assets/svg/edit.svg
Normal file
6
frontend/src/assets/svg/edit.svg
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<svg version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" width="16px" height="16px" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g transform="matrix(1 0 0 1 -1754 -829 )">
|
||||||
|
<path d="M 0.89038789844335 0 L 10.9513354812796 0 C 11.1287252776408 0 11.2174193247947 0.0886947998772105 11.3061150796195 0.177389650310463 L 11.3948099806087 0.266084455805011 L 14.4104339965652 3.45909741428739 C 14.5878237929264 3.6364869971898 14.6765186939158 3.81387679916839 14.6765186939158 4.07996106960125 L 14.6765186939158 8.69209156199027 C 14.6765186939158 9.0468711603301 14.4104339965652 9.31295500384528 14.0556543982254 9.31295500384528 C 13.7008747998855 9.31295500384528 13.4347909563703 9.0468711603301 13.4347909563703 8.69209156199027 L 13.4347909563703 5.23299390615734 L 10.5965567423925 5.23299390615734 C 10.1530822430634 5.23299390615734 9.70960946263972 4.87821473473519 9.62091456165035 4.34604576695179 L 9.62091456165035 1.33042209927024 L 1.24516733949781 1.33042209927024 L 1.24516733949781 14.6346450355716 L 9.00004994577171 14.6346450355716 C 9.35482954411145 14.6346450355716 9.62091338762662 14.9007280196341 9.62091338762662 15.2555076179739 L 9.62091338762662 15.3442025189632 C 9.62091338762662 15.698982117303 9.35482954411145 15.9650668146536 9.08874399292563 15.9650668146536 L 0.979082855606066 15.9650668146536 C 0.535608732638593 15.9650668146536 0.092134716400551 15.6102872163137 0 15.0781195349007 L 0 0.975642933465427 C 0.00343992214061473 0.443474044324756 0.35821911603216 0 0.89038789844335 0 Z M 13.1687062590199 3.99126662361633 L 10.9513354868971 1.59650647643252 L 10.9513354868971 3.99126662361633 L 13.1687054051845 3.99126662361633 Z M 12.7252317596908 10.2885980608921 C 13.6121798988963 10.2885980608921 14.4991288919373 10.7320725602213 14.9426033912664 11.5303257984375 C 15.3860778905955 12.3285790366538 15.3860778905955 13.3042220768486 14.9426033912664 14.1024753150649 L 15.7408557756471 14.9007276994458 C 16.0069404729977 15.1668123967963 16.0069404729977 15.5215919951362 15.7408557756471 15.7876766924867 C 15.4747727915847 16.0537596765492 15.1199914799568 16.0537596765492 14.8539084958943 15.8763698801879 L 14.7652135949052 15.7876766924867 L 13.9669603566888 14.9894225948178 C 13.080013076936 15.5215919895188 11.9269802403799 15.4328970941469 11.0400312417216 14.8120327984565 C 10.2417780035053 14.1024753150649 9.88699840516551 13.0381373794981 10.2417780035053 11.9738002977667 C 10.6852525028344 10.9981581170245 11.6608946835765 10.2885980608921 12.725233478596 10.2885980608921 Z M 13.9669594972361 12.7720535416003 C 13.9669594972361 12.0624952043733 13.3460960553812 11.5303257984375 12.7252317596908 11.5303257984375 C 12.2817581198142 11.5303257984375 11.8382844799378 11.7964096419527 11.6608938241238 12.1511892402925 C 11.3948099806087 12.5059688386324 11.3948099806087 13.0381373794981 11.6608938241238 13.3929169778379 C 11.9269785214744 13.7476965761778 12.2817581198142 14.0137804196929 12.7252317596908 14.0137804196929 C 13.4347909563703 14.0137804196929 13.9669594972361 13.4816118788272 13.9669594972361 12.7720535416003 Z M 3.12549730248907 5.36519114038518 C 3.21419198440231 4.92030965519137 3.39158199983967 4.69786826379193 3.83505563971607 4.69786826379193 L 7.95345164763125 4.69786826379193 C 8.39692528750766 4.69786826379193 8.66300998485826 4.92030964957403 8.66300998485826 5.36519114038518 C 8.66300998485826 5.81007263119633 8.39692528750766 6.03251444389611 7.95345164763125 6.03251444389611 L 3.83505563971607 6.03251444389611 C 3.39158199983967 6.03251444389611 3.12549730248907 5.81007305811402 3.12549730248907 5.36519114038518 Z M 3.21419199001957 8.14570312899906 C 3.21419198440231 7.81204254734647 3.39158199983967 7.47838025240581 3.74636117126192 7.47838025240581 L 10.3482113678974 7.47838025240581 C 10.7029909662372 7.47838025240581 10.8803807625984 7.70082035181753 10.8803807625984 8.14570312899906 C 10.8803807625984 8.5905859061806 10.7029909662372 8.81302600559231 10.3482113678974 8.81302600559231 L 3.74636117126192 8.81302600559231 C 3.39158199983967 8.81302600559231 3.21419199001957 8.5905859061806 3.21419199001957 8.14570312899906 Z M 3.12549730248907 11.0374355942365 C 3.21419198440231 10.592553682125 3.39158199983967 10.3701127176432 3.83505563971607 10.3701127176432 L 7.95345164763125 10.3701127176432 C 8.39692528750766 10.3701127176432 8.66300998485826 10.5925536765076 8.66300998485826 11.0374355942365 C 8.66300998485826 11.482318371418 8.39692528750766 11.7047593302824 7.95345164763125 11.7047593302824 L 3.83505563971607 11.7047593302824 C 3.39158199983967 11.7047593302824 3.12549730248907 11.482318371418 3.12549730248907 11.0374355942365 Z " fill-rule="nonzero" fill="#255aa7" stroke="none" transform="matrix(1 0 0 1 1754 829 )" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
6
frontend/src/assets/svg/station.svg
Normal file
6
frontend/src/assets/svg/station.svg
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<svg version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" width="16px" height="16px" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g transform="matrix(1 0 0 1 -672 -170 )">
|
||||||
|
<path d="M 14.3232142857144 16 C 15.2428571428571 15.9232142857143 15.9875 15.1785714285714 16 14.2589285714286 L 16 1.66249999999999 C 15.9875 0.74464285714285 15.2428571428571 0 14.325 0 L 1.72857142857151 0 C 0.80892857142851 0 0.0642857142856883 0.74464285714285 0 1.66428571428571 L 0 14.2589285714286 C 0.0642857142856883 15.1785714285714 0.80892857142851 15.9232142857143 1.72857142857151 16 L 14.3232142857144 16 Z M 12.2446428571428 7.625 C 12.3125 7.53214285714286 11.7571428571429 7.0125 11.6875 6.9375 C 10.6964285714286 5.86785714285715 9.48214285714289 4.68214285714285 9.48214285714289 3.11607142857142 C 9.4803571428572 1.6125 10.7 0.394642857142856 12.2035714285714 0.394642857142856 C 13.7071428571428 0.394642857142856 14.925 1.6125 14.925 3.11607142857142 C 14.925 4.96250000000001 13.3589285714286 6.18571428571428 12.325 7.51964285714286 C 12.2982142857143 7.55535714285715 12.2714285714285 7.58928571428571 12.2446428571428 7.625 Z M 11.2375 11.1071428571428 L 14.8464285714285 7.28035714285714 C 14.9857142857143 7.13214285714285 15.2196428571428 7.125 15.3696428571428 7.26428571428571 C 15.5178571428571 7.40535714285714 15.525 7.63928571428571 15.3839285714286 7.78749999999999 L 11.7660714285714 11.625 L 14.3732142857143 14.1803571428571 C 14.5196428571428 14.3232142857143 14.5214285714285 14.5571428571428 14.3785714285715 14.7035714285714 C 14.3053571428571 14.7767857142857 14.2107142857143 14.8142857142857 14.1142857142856 14.8142857142857 C 14.0214285714285 14.8142857142857 13.9267857142856 14.7785714285714 13.8553571428572 14.7089285714286 L 8.46785714285716 9.42857142857142 L 3.93035714285713 14.2392857142857 C 3.85714285714289 14.3160714285714 3.75892857142856 14.3553571428571 3.66071428571433 14.3553571428571 C 3.56964285714287 14.3553571428571 3.47857142857151 14.3214285714286 3.40714285714284 14.2553571428571 C 3.25892857142856 14.1160714285714 3.25178571428569 13.8821428571428 3.39107142857142 13.7321428571428 L 7.93928571428569 8.91071428571429 L 5.68571428571431 6.70178571428571 L 2.18928571428569 10.4089285714286 C 2.11607142857144 10.4857142857143 2.01785714285711 10.525 1.91964285714289 10.525 C 1.82857142857142 10.525 1.73749999999995 10.4910714285714 1.66607142857151 10.425 C 1.51785714285711 10.2857142857143 1.51071428571436 10.0517857142857 1.64999999999998 9.90178571428571 L 5.15535714285716 6.18392857142857 L 1.68035714285713 2.77857142857142 C 1.53392857142853 2.63571428571429 1.53214285714284 2.40178571428571 1.67499999999995 2.25535714285715 C 1.81785714285718 2.10892857142856 2.05178571428564 2.10714285714286 2.19821428571436 2.25 L 8.44821428571436 8.37321428571428 L 9.57321428571436 7.18035714285713 C 9.71249999999998 7.03214285714286 9.94642857142856 7.02500000000001 10.0964285714285 7.16428571428571 C 10.2446428571428 7.30357142857142 10.2517857142857 7.53749999999999 10.1125 7.6875 L 8.97857142857151 8.88928571428571 L 11.2375 11.1071428571428 Z M 13.625 2.87142857142857 C 13.625 2.08639524845481 12.9886047515453 1.45 12.2035714285714 1.45 C 11.4185381055977 1.45 10.7821428571428 2.08639524845481 10.7821428571428 2.87142857142857 C 10.7821428571428 3.65646189440234 11.4185381055977 4.29285714285714 12.2035714285714 4.29285714285714 C 12.9886047515453 4.29285714285714 13.625 3.65646189440234 13.625 2.87142857142857 Z " fill-rule="nonzero" fill="#005293" stroke="none" transform="matrix(1 0 0 1 672 170 )" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
6
frontend/src/assets/svg/version.svg
Normal file
6
frontend/src/assets/svg/version.svg
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<svg version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" width="16px" height="16px" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g transform="matrix(1 0 0 1 -565 -875 )">
|
||||||
|
<path d="M 0 8 C 0 3.58181818749995 3.58181818749995 0 8 0 C 12.4181818125001 0 16 3.58181818749995 16 8 C 16 12.4181818125001 12.4181818125001 16 8 16 C 3.58181818749995 16 0 12.4181818125001 0 8 Z M 7.23490909374993 4.18436364062495 L 7.27999999999997 4.18200000000002 L 7.23509090624998 4.18436364062495 L 5.10563635937501 4.34854545312498 C 4.71454545312497 4.378727265625 4.37836362500002 4.71400000000006 4.3481818125 5.10545454687497 L 4.184545453125 7.23490909374993 C 4.16616282204484 7.47433213284717 4.25329832724606 7.70983708702352 4.42309090625008 7.87963635937501 L 8.12090909375002 11.5770909062501 C 8.27478417521331 11.7320563374394 8.48434384064558 11.8188926242638 8.70272726562496 11.8181818125 C 8.9210035982984 11.8186207899432 9.13040619707181 11.731822466318 9.28436364062497 11.5770909062501 L 11.5774545468751 9.28363635937501 C 11.8982525597308 8.96207485258412 11.898415190837 8.44158010127046 11.5778181875 8.11981818749996 L 7.88 4.42254545312494 C 7.7100917776836 4.25272220670638 7.47440966938086 4.16570316615639 7.23490909374993 4.18436364062495 Z M 5.66798476241524 6.28836102379478 C 5.66798476241524 5.94604586717719 5.94495902002711 5.66826642423223 6.28727273437494 5.66727273437505 C 6.62979956582933 5.66796697062637 6.90710481095891 5.94583482992755 6.90710481095891 6.28836236492305 C 6.90710481095891 6.45242147490467 6.84219401306905 6.6098168600837 6.72654545312503 6.72618181250004 C 6.48382821774419 6.96799606812897 6.09126268850582 6.96799606812897 5.84854545312498 6.72618181250004 C 5.7328960771963 6.60981674757102 5.66798476241524 6.45242078878493 5.66798476241524 6.28836102379478 Z " fill-rule="nonzero" fill="#ff424f" stroke="none" transform="matrix(1 0 0 1 565 875 )" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
@ -162,7 +162,7 @@
|
|||||||
value="specify"
|
value="specify"
|
||||||
class="zhding"
|
class="zhding"
|
||||||
>
|
>
|
||||||
<span class="option-label" style="min-width: 30px;">指定</span>
|
<span class="option-label" style="min-width: 30px">指定</span>
|
||||||
<div v-if="field.key === 'hour'" class="hour-grid">
|
<div v-if="field.key === 'hour'" class="hour-grid">
|
||||||
<a-checkbox-group
|
<a-checkbox-group
|
||||||
v-model:value="fieldData[field.key].specifyValues"
|
v-model:value="fieldData[field.key].specifyValues"
|
||||||
@ -368,22 +368,10 @@ const formatHour = (val: number) => {
|
|||||||
return val.toString().padStart(2, '0');
|
return val.toString().padStart(2, '0');
|
||||||
};
|
};
|
||||||
|
|
||||||
const isDaySpecific = () => {
|
|
||||||
const day = fieldData.day;
|
|
||||||
return day.type !== 'all' && day.type !== 'lastDay';
|
|
||||||
};
|
|
||||||
|
|
||||||
const isWeekSpecific = () => {
|
|
||||||
const week = fieldData.week;
|
|
||||||
return week.type !== 'all' && week.type !== 'none';
|
|
||||||
};
|
|
||||||
|
|
||||||
const generateFieldValue = (key: string) => {
|
const generateFieldValue = (key: string) => {
|
||||||
const data = fieldData[key];
|
const data = fieldData[key];
|
||||||
switch (data.type) {
|
switch (data.type) {
|
||||||
case 'all':
|
case 'all':
|
||||||
if (key === 'day' && isWeekSpecific()) return '?';
|
|
||||||
if (key === 'week' && isDaySpecific()) return '?';
|
|
||||||
if (key === 'year') return '';
|
if (key === 'year') return '';
|
||||||
return '*';
|
return '*';
|
||||||
case 'none':
|
case 'none':
|
||||||
@ -404,8 +392,6 @@ const generateFieldValue = (key: string) => {
|
|||||||
return 'L';
|
return 'L';
|
||||||
case 'specify':
|
case 'specify':
|
||||||
if (data.specifyValues.length === 0) {
|
if (data.specifyValues.length === 0) {
|
||||||
if (key === 'day' && isWeekSpecific()) return '?';
|
|
||||||
if (key === 'week' && isDaySpecific()) return '?';
|
|
||||||
return '*';
|
return '*';
|
||||||
}
|
}
|
||||||
return data.specifyValues
|
return data.specifyValues
|
||||||
@ -527,6 +513,12 @@ const validateCron = (
|
|||||||
errorMessage: '日和周不能同时指定,请将其中一个设为"不指定"'
|
errorMessage: '日和周不能同时指定,请将其中一个设为"不指定"'
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
if (day === '?' && week === '?') {
|
||||||
|
return {
|
||||||
|
valid: false,
|
||||||
|
errorMessage: '日和周不能同时为不指定,请至少指定其中一个'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
return { valid: true, errorMessage: '' };
|
return { valid: true, errorMessage: '' };
|
||||||
};
|
};
|
||||||
@ -699,8 +691,8 @@ watch(
|
|||||||
align-items: flex-start !important;
|
align-items: flex-start !important;
|
||||||
// margin-top: px;
|
// margin-top: px;
|
||||||
}
|
}
|
||||||
:deep(.ant-radio){
|
:deep(.ant-radio) {
|
||||||
align-self:auto !important;
|
align-self: auto !important;
|
||||||
// margin-top: 0px !important;
|
// margin-top: 0px !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -35,7 +35,6 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, nextTick, ref, watch } from 'vue';
|
import { computed, nextTick, ref, watch } from 'vue';
|
||||||
import dayjs from 'dayjs';
|
|
||||||
import { message } from 'ant-design-vue';
|
import { message } from 'ant-design-vue';
|
||||||
import BasicTable from '@/components/BasicTable/index.vue';
|
import BasicTable from '@/components/BasicTable/index.vue';
|
||||||
import { queryPostUrlList } from '@/api/mapModal';
|
import { queryPostUrlList } from '@/api/mapModal';
|
||||||
|
|||||||
@ -49,7 +49,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, onMounted, watch, computed, h } from 'vue';
|
import { ref, watch, computed, h } from 'vue';
|
||||||
import { Button } from 'ant-design-vue';
|
import { Button } from 'ant-design-vue';
|
||||||
import BasicTable from '@/components/BasicTable/index.vue';
|
import BasicTable from '@/components/BasicTable/index.vue';
|
||||||
import { getMonitorDataFpq, getMonitorDataOnline } from '@/api/mapModal';
|
import { getMonitorDataFpq, getMonitorDataOnline } from '@/api/mapModal';
|
||||||
@ -60,7 +60,6 @@ import { useDraggable } from '@/utils/drag';
|
|||||||
|
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
const modelStore = useModelStore();
|
const modelStore = useModelStore();
|
||||||
const hasLoaded = ref(false);
|
|
||||||
const tableRef = ref();
|
const tableRef = ref();
|
||||||
const searchParams = ref([
|
const searchParams = ref([
|
||||||
dayjs().subtract(5, 'year').format('YYYY-MM-DD HH:mm:ss'),
|
dayjs().subtract(5, 'year').format('YYYY-MM-DD HH:mm:ss'),
|
||||||
@ -276,7 +275,6 @@ const initData = async () => {
|
|||||||
const res1 = await getMonitorDataFpq(params1);
|
const res1 = await getMonitorDataFpq(params1);
|
||||||
const res = await getMonitorDataFpq(params);
|
const res = await getMonitorDataFpq(params);
|
||||||
let stcd = '';
|
let stcd = '';
|
||||||
console.log(res1);
|
|
||||||
if (res) {
|
if (res) {
|
||||||
if (res?.data?.data?.length > 0) {
|
if (res?.data?.data?.length > 0) {
|
||||||
stcd = res.data.data.map(item => item.stcd);
|
stcd = res.data.data.map(item => item.stcd);
|
||||||
@ -291,7 +289,6 @@ const initData = async () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
console.log(stcd);
|
|
||||||
const filter = {
|
const filter = {
|
||||||
logic: 'and',
|
logic: 'and',
|
||||||
filters: [
|
filters: [
|
||||||
|
|||||||
@ -85,9 +85,12 @@ const updateChart = (data: any[]) => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const sorted = [...data].sort(
|
const toTime = (tm: any) => {
|
||||||
(a, b) => new Date(a.tm).getTime() - new Date(b.tm).getTime()
|
if (!tm) return 0;
|
||||||
);
|
const t = new Date(tm).getTime();
|
||||||
|
return Number.isNaN(t) ? 0 : t;
|
||||||
|
};
|
||||||
|
const sorted = [...data].sort((a, b) => toTime(a.tm) - toTime(b.tm));
|
||||||
|
|
||||||
const xAxisData = sorted.map(item => {
|
const xAxisData = sorted.map(item => {
|
||||||
const hm = dayjs(item.tm).format('HH:mm');
|
const hm = dayjs(item.tm).format('HH:mm');
|
||||||
@ -399,57 +402,65 @@ const initDateRange = (): [Dayjs, Dayjs] => {
|
|||||||
};
|
};
|
||||||
const dateRange = ref<[Dayjs, Dayjs] | undefined>(initDateRange());
|
const dateRange = ref<[Dayjs, Dayjs] | undefined>(initDateRange());
|
||||||
|
|
||||||
|
// 空值兜底:null/undefined/空串/'-'占位符/NaN 均显示为 '-'
|
||||||
|
const formatCell = (
|
||||||
|
text: any,
|
||||||
|
formatter: (num: number) => string = num => String(num)
|
||||||
|
) => {
|
||||||
|
if (text === null || text === undefined || text === '' || text === '-') {
|
||||||
|
return '-';
|
||||||
|
}
|
||||||
|
const num = Number(text);
|
||||||
|
if (Number.isNaN(num)) return '-';
|
||||||
|
return formatter(num);
|
||||||
|
};
|
||||||
|
|
||||||
// 表格列配置
|
// 表格列配置
|
||||||
const tableColumns = [
|
const tableColumns = [
|
||||||
{
|
{
|
||||||
title: '时间',
|
title: '时间',
|
||||||
dataIndex: 'tm',
|
dataIndex: 'tm',
|
||||||
width: 180,
|
width: 150,
|
||||||
fixed: 'left',
|
fixed: 'left',
|
||||||
customRender: ({ text }: any) =>
|
customRender: ({ text }: any) =>
|
||||||
text ? dayjs(text).format('YYYY-MM-DD HH:mm:ss') : '-'
|
text && text !== '-' ? dayjs(text).format('YYYY-MM-DD HH:mm:ss') : '-'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '坝上水位(m)',
|
title: '坝上水位(m)',
|
||||||
dataIndex: 'rz',
|
dataIndex: 'rz',
|
||||||
width: 130,
|
width: 100,
|
||||||
customRender: ({ text }: any) =>
|
customRender: ({ text }: any) => formatCell(text, v => v.toFixed(2))
|
||||||
text !== undefined && text !== null ? Number(text).toFixed(2) : '-'
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '坝下水位(m)',
|
title: '坝下水位(m)',
|
||||||
dataIndex: 'dz',
|
dataIndex: 'dz',
|
||||||
width: 130,
|
width: 100,
|
||||||
customRender: ({ text }: any) =>
|
customRender: ({ text }: any) => formatCell(text, v => v.toFixed(2))
|
||||||
text !== undefined && text !== null ? Number(text).toFixed(2) : '-'
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '入库流量(m³/s)',
|
title: '入库流量(m³/s)',
|
||||||
dataIndex: 'qi',
|
dataIndex: 'qi',
|
||||||
width: 130,
|
width: 120,
|
||||||
customRender: ({ text }: any) =>
|
customRender: ({ text }: any) =>
|
||||||
text !== undefined && text !== null ? Math.round(Number(text)) : '-'
|
formatCell(text, v => String(Math.round(v)))
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '出库流量(m³/s)',
|
title: '出库流量(m³/s)',
|
||||||
dataIndex: 'qo',
|
dataIndex: 'qo',
|
||||||
width: 130,
|
width: 120,
|
||||||
customRender: ({ text }: any) =>
|
customRender: ({ text }: any) => formatCell(text, v => String(v))
|
||||||
text !== undefined && text !== null ? Number(text) : '-'
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '生态流量(m³/s)',
|
title: '生态流量(m³/s)',
|
||||||
dataIndex: 'qec',
|
dataIndex: 'qec',
|
||||||
width: 130,
|
width: 120,
|
||||||
customRender: ({ text }: any) =>
|
customRender: ({ text }: any) => formatCell(text, v => v.toFixed(1))
|
||||||
text !== undefined && text !== null ? Number(text).toFixed(1) : '-'
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '生态流量限值(m³/s)',
|
title: '生态流量限值(m³/s)',
|
||||||
dataIndex: 'qecLimit',
|
dataIndex: 'qecLimit',
|
||||||
width: 150,
|
width: 140,
|
||||||
customRender: ({ text }: any) =>
|
customRender: ({ text }: any) => formatCell(text, v => v.toFixed(1))
|
||||||
text !== undefined && text !== null ? Number(text).toFixed(1) : '-'
|
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
const tableScrollX = tableColumns.reduce((s, c) => s + (c.width || 100), 0);
|
const tableScrollX = tableColumns.reduce((s, c) => s + (c.width || 100), 0);
|
||||||
|
|||||||
@ -6,6 +6,7 @@
|
|||||||
:src="iframeUrl"
|
:src="iframeUrl"
|
||||||
class="panorama-info-iframe"
|
class="panorama-info-iframe"
|
||||||
frameborder="0"
|
frameborder="0"
|
||||||
|
allowfullscreen
|
||||||
></iframe>
|
></iframe>
|
||||||
<a-select
|
<a-select
|
||||||
v-show="iframeUrl != ''"
|
v-show="iframeUrl != ''"
|
||||||
|
|||||||
@ -1,12 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div>
|
|
||||||
视频
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { ref, watch } from 'vue';
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
</style>
|
|
||||||
@ -3916,10 +3916,41 @@ export class MapCesium implements MapInterface {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 获取 eng 渲染优先级:告警=300, eng=200, 普通=100(与 2D 完全一致) */
|
/** 获取 eng 渲染优先级:大型=300/中型=200,同规模已建>在建>未建,其他 eng=150,普通=100(与 2D 完全一致,报警点同规格内 +10) */
|
||||||
private getEntityEngRenderPriority(entity: Cesium.Entity): number {
|
private getEntityEngRenderPriority(entity: Cesium.Entity): number {
|
||||||
if (this.isEntityEngAlarm(entity)) return 300;
|
const e = entity as any;
|
||||||
return this.isEntityEng(entity) ? 200 : 100;
|
const rawData: Record<string, any> = e._rawData || {};
|
||||||
|
const legendState = String(rawData.anchoPointState || '')
|
||||||
|
.trim()
|
||||||
|
.toLowerCase();
|
||||||
|
const isAlarm = this.isEntityEngAlarm(entity);
|
||||||
|
|
||||||
|
// 建设状态权重:已建 > 在建 > 未建
|
||||||
|
let buildRank = 0;
|
||||||
|
if (legendState.includes('_ubuilt')) {
|
||||||
|
buildRank = 2;
|
||||||
|
} else if (legendState.includes('_nbuilt')) {
|
||||||
|
buildRank = 1;
|
||||||
|
} else if (legendState.includes('_built')) {
|
||||||
|
buildRank = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
let base = 100;
|
||||||
|
|
||||||
|
// 大型电站优先级高于中型电站,同规模内已建优先
|
||||||
|
if (legendState.startsWith('large_eng_')) {
|
||||||
|
base = 300 + buildRank * 30;
|
||||||
|
} else if (legendState.startsWith('mid_eng_')) {
|
||||||
|
base = 200 + buildRank * 30;
|
||||||
|
} else if (this.isEntityEng(entity)) {
|
||||||
|
base = 150;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 报警范围点仅在同规格内略微提升,不跨越大型/中型与建设状态层级
|
||||||
|
if (isAlarm) {
|
||||||
|
base += 10;
|
||||||
|
}
|
||||||
|
return base;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==================== 文本像素级精确测量 ====================
|
// ==================== 文本像素级精确测量 ====================
|
||||||
|
|||||||
@ -47,7 +47,7 @@ const VITE_APP_MAP_URL = import.meta.env.VITE_APP_MAP_URL;
|
|||||||
const CENTER_positionCN = [114.17112499999996, 38]; // OpenLayers 使用 [lon, lat]
|
const CENTER_positionCN = [114.17112499999996, 38]; // OpenLayers 使用 [lon, lat]
|
||||||
const MIN_ZOOM = 4.23;
|
const MIN_ZOOM = 4.23;
|
||||||
const MAX_ZOOM = 22;
|
const MAX_ZOOM = 22;
|
||||||
const INITIAL_ZOOM = 4.5;
|
const INITIAL_ZOOM = 4.4;
|
||||||
const BATCH_POPUP_MODE_ZOOM = 15;
|
const BATCH_POPUP_MODE_ZOOM = 15;
|
||||||
const FULL_DISPLAY_NO_COLLISION_ZOOM = Number.POSITIVE_INFINITY;
|
const FULL_DISPLAY_NO_COLLISION_ZOOM = Number.POSITIVE_INFINITY;
|
||||||
const POINT_FADE_DURATION = 300; // 锚点/文字碰撞显隐的淡入淡出时长(ms)
|
const POINT_FADE_DURATION = 300; // 锚点/文字碰撞显隐的淡入淡出时长(ms)
|
||||||
@ -153,9 +153,9 @@ export class MapOl implements MapInterface {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const mouseWheelInteraction = new MouseWheelZoom({
|
const mouseWheelInteraction = new MouseWheelZoom({
|
||||||
duration: 100, // 缩放动画持续时间 (ms),Leaflet 默认也有动画
|
duration: 100, // 缩放动画持续时间 (ms)
|
||||||
maxDelta: 2,
|
maxDelta: 1, // 每滚一格最大缩放步长,对齐 Mapbox 的 ~0.5 缩放幅度
|
||||||
constrainResolution: false // ✅ 关键:false 允许缩放到非整数级别 (如 4.5, 4.6),实现平滑缩放
|
constrainResolution: false // 允许缩放到非整数级别,实现平滑缩放
|
||||||
});
|
});
|
||||||
|
|
||||||
this.map = new OlMap({
|
this.map = new OlMap({
|
||||||
@ -496,15 +496,11 @@ export class MapOl implements MapInterface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const currentZoom: any = this.view ? this.view.getZoom() : 4.5;
|
const currentZoom: any = this.view ? this.view.getZoom() : 4.5;
|
||||||
const cachedDensityVisible = feature.get('_densityVisible');
|
const iconCollisionVisible = feature.get('_iconCollisionVisible') !== false;
|
||||||
const densityVisible =
|
|
||||||
typeof cachedDensityVisible === 'boolean'
|
|
||||||
? cachedDensityVisible
|
|
||||||
: currentZoom >= this.getFeatureDensityMinZoom(feature);
|
|
||||||
const iconTargetVisible =
|
const iconTargetVisible =
|
||||||
legendVisible !== false &&
|
legendVisible !== false &&
|
||||||
regionVisible !== false &&
|
regionVisible !== false &&
|
||||||
densityVisible &&
|
iconCollisionVisible &&
|
||||||
this.shouldRenderNearbyFeature();
|
this.shouldRenderNearbyFeature();
|
||||||
|
|
||||||
const dynamicScale = this.getDynamicPointScale(currentZoom);
|
const dynamicScale = this.getDynamicPointScale(currentZoom);
|
||||||
@ -518,7 +514,6 @@ export class MapOl implements MapInterface {
|
|||||||
dynamicScale,
|
dynamicScale,
|
||||||
labelLineCount
|
labelLineCount
|
||||||
);
|
);
|
||||||
const iconCollisionVisible = feature.get('_iconCollisionVisible') !== false;
|
|
||||||
const labelCollisionVisible =
|
const labelCollisionVisible =
|
||||||
feature.get('_labelCollisionVisible') === true;
|
feature.get('_labelCollisionVisible') === true;
|
||||||
|
|
||||||
@ -1029,35 +1024,10 @@ export class MapOl implements MapInterface {
|
|||||||
|
|
||||||
if (currentZoom >= FULL_DISPLAY_NO_COLLISION_ZOOM) {
|
if (currentZoom >= FULL_DISPLAY_NO_COLLISION_ZOOM) {
|
||||||
viewportEntries.forEach(entry => {
|
viewportEntries.forEach(entry => {
|
||||||
const minZoom = this.getFeatureDensityMinZoom(entry.feature);
|
|
||||||
const densityVisible =
|
|
||||||
currentZoom >= minZoom ||
|
|
||||||
(entry.legendVisible &&
|
|
||||||
entry.regionVisible &&
|
|
||||||
!!entry.layerKey &&
|
|
||||||
entry.pixelX !== undefined &&
|
|
||||||
entry.pixelY !== undefined &&
|
|
||||||
!this.hasNearbyViewportFeature(
|
|
||||||
{
|
|
||||||
feature: entry.feature,
|
|
||||||
layerKey: entry.layerKey,
|
|
||||||
pixelX: entry.pixelX,
|
|
||||||
pixelY: entry.pixelY
|
|
||||||
},
|
|
||||||
densityPixelGrid,
|
|
||||||
densityIsolationThreshold
|
|
||||||
));
|
|
||||||
this.syncFeatureDensityVisible(
|
|
||||||
entry.feature,
|
|
||||||
densityVisible,
|
|
||||||
dirtyLayerKeys
|
|
||||||
);
|
|
||||||
const shouldRenderIcon =
|
const shouldRenderIcon =
|
||||||
!!entry.iconUrl &&
|
!!entry.iconUrl &&
|
||||||
this.isIconReady(entry.iconUrl) &&
|
|
||||||
entry.legendVisible &&
|
entry.legendVisible &&
|
||||||
entry.regionVisible &&
|
entry.regionVisible &&
|
||||||
densityVisible &&
|
|
||||||
entry.nearbyVisible;
|
entry.nearbyVisible;
|
||||||
const hasLabel = !!entry.labelText;
|
const hasLabel = !!entry.labelText;
|
||||||
|
|
||||||
@ -1103,23 +1073,11 @@ export class MapOl implements MapInterface {
|
|||||||
|
|
||||||
const shouldRenderIcon =
|
const shouldRenderIcon =
|
||||||
!!entry.iconUrl &&
|
!!entry.iconUrl &&
|
||||||
this.isIconReady(entry.iconUrl) &&
|
|
||||||
entry.legendVisible &&
|
entry.legendVisible &&
|
||||||
entry.regionVisible &&
|
entry.regionVisible &&
|
||||||
densityVisible &&
|
|
||||||
entry.nearbyVisible;
|
entry.nearbyVisible;
|
||||||
|
|
||||||
if (!shouldRenderIcon) {
|
if (!shouldRenderIcon) {
|
||||||
this.syncFeatureIconCollisionVisible(
|
|
||||||
entry.feature,
|
|
||||||
false,
|
|
||||||
dirtyLayerKeys
|
|
||||||
);
|
|
||||||
this.syncFeatureLabelCollisionVisible(
|
|
||||||
entry.feature,
|
|
||||||
false,
|
|
||||||
dirtyLayerKeys
|
|
||||||
);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1138,9 +1096,18 @@ export class MapOl implements MapInterface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const dynamicScale = this.getDynamicPointScale(currentZoom);
|
const dynamicScale = this.getDynamicPointScale(currentZoom);
|
||||||
// 备注:图标碰撞盒适当小于视觉图标尺寸,允许相邻站点轻微贴近显示,
|
// 备注:图标碰撞盒使用实际图标尺寸(从 iconSizeCache 获取),
|
||||||
// 避免像“小浪底/三门峡”这类近点被过早裁掉成只剩一个点。
|
// 再乘 0.9 略小于视觉尺寸,避免碰撞盒大于视觉图标导致误杀。
|
||||||
const iconCollisionSize = Math.max(14, 24 * dynamicScale);
|
// 当缓存不可用时(图标尚未加载完),回退到固定 24px。
|
||||||
|
const cachedIconSize = this.iconSizeCache.get(entry.iconUrl);
|
||||||
|
const iconCollisionSize = cachedIconSize
|
||||||
|
? Math.max(
|
||||||
|
14,
|
||||||
|
Math.max(cachedIconSize.width, cachedIconSize.height) *
|
||||||
|
dynamicScale *
|
||||||
|
0.9
|
||||||
|
)
|
||||||
|
: Math.max(14, 24 * dynamicScale);
|
||||||
const iconPadding = 0;
|
const iconPadding = 0;
|
||||||
const iconLeft = entry.pixelX - iconCollisionSize / 2 - iconPadding;
|
const iconLeft = entry.pixelX - iconCollisionSize / 2 - iconPadding;
|
||||||
const iconRight = entry.pixelX + iconCollisionSize / 2 + iconPadding;
|
const iconRight = entry.pixelX + iconCollisionSize / 2 + iconPadding;
|
||||||
@ -1611,16 +1578,6 @@ export class MapOl implements MapInterface {
|
|||||||
iconRect: { left: number; right: number; top: number; bottom: number },
|
iconRect: { left: number; right: number; top: number; bottom: number },
|
||||||
labelRect: { left: number; right: number; top: number; bottom: number }
|
labelRect: { left: number; right: number; top: number; bottom: number }
|
||||||
) {
|
) {
|
||||||
// 标签避让图标时仅保留图标中心更小的保护区,并允许边缘接触不算碰撞,
|
|
||||||
// 避免像“三峡/黄龙滩”这类仅在边界轻微擦到时把文字过度压掉。
|
|
||||||
const inset = 4;
|
|
||||||
const padding = 0;
|
|
||||||
const narrowedIconRect = {
|
|
||||||
left: iconRect.left + inset,
|
|
||||||
right: iconRect.right - inset,
|
|
||||||
top: iconRect.top + inset,
|
|
||||||
bottom: iconRect.bottom - inset
|
|
||||||
};
|
|
||||||
// 标签块向上扩展避让边距(等价于给文字加"上边距"),
|
// 标签块向上扩展避让边距(等价于给文字加"上边距"),
|
||||||
// 让下方锚点文字不再紧贴上方锚点图标底部(含文字描边/图标视觉边缘)看着像重叠
|
// 让下方锚点文字不再紧贴上方锚点图标底部(含文字描边/图标视觉边缘)看着像重叠
|
||||||
const extendedLabelRect = {
|
const extendedLabelRect = {
|
||||||
@ -1631,10 +1588,10 @@ export class MapOl implements MapInterface {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return !(
|
return !(
|
||||||
narrowedIconRect.right + padding <= extendedLabelRect.left ||
|
iconRect.right <= extendedLabelRect.left ||
|
||||||
narrowedIconRect.left - padding >= extendedLabelRect.right ||
|
iconRect.left >= extendedLabelRect.right ||
|
||||||
narrowedIconRect.bottom + padding <= extendedLabelRect.top ||
|
iconRect.bottom <= extendedLabelRect.top ||
|
||||||
narrowedIconRect.top - padding >= extendedLabelRect.bottom
|
iconRect.top >= extendedLabelRect.bottom
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1776,10 +1733,37 @@ export class MapOl implements MapInterface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private getFeatureEngRenderPriority(feature: Feature): number {
|
private getFeatureEngRenderPriority(feature: Feature): number {
|
||||||
if (this.isFeatureEngAlarm(feature)) {
|
const legendState = String(feature.get('anchoPointState') || '')
|
||||||
return 300;
|
.trim()
|
||||||
|
.toLowerCase();
|
||||||
|
const isAlarm = this.isFeatureEngAlarm(feature);
|
||||||
|
|
||||||
|
// 建设状态权重:已建 > 在建 > 未建
|
||||||
|
let buildRank = 0;
|
||||||
|
if (legendState.includes('_ubuilt')) {
|
||||||
|
buildRank = 2;
|
||||||
|
} else if (legendState.includes('_nbuilt')) {
|
||||||
|
buildRank = 1;
|
||||||
|
} else if (legendState.includes('_built')) {
|
||||||
|
buildRank = 3;
|
||||||
}
|
}
|
||||||
return this.isFeatureEng(feature) ? 200 : 100;
|
|
||||||
|
let base = 100;
|
||||||
|
|
||||||
|
// 大型电站优先级高于中型电站,同规模内已建优先
|
||||||
|
if (legendState.startsWith('large_eng_')) {
|
||||||
|
base = 300 + buildRank * 30;
|
||||||
|
} else if (legendState.startsWith('mid_eng_')) {
|
||||||
|
base = 200 + buildRank * 30;
|
||||||
|
} else if (this.isFeatureEng(feature)) {
|
||||||
|
base = 150;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 报警范围点仅在同规格内略微提升,不跨越大型/中型与建设状态层级
|
||||||
|
if (isAlarm) {
|
||||||
|
base += 10;
|
||||||
|
}
|
||||||
|
return base;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -45,6 +45,13 @@
|
|||||||
</a-checkbox>
|
</a-checkbox>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-col>
|
</a-col>
|
||||||
|
<a-col v-if="isMenu('shengTaiDiaoCha/luShengShengTaiDiaoCha')">
|
||||||
|
<a-form-item label="" name="">
|
||||||
|
<a-checkbox v-model:checked="formModel.fishSurveyZhuanZhi">
|
||||||
|
动物分布
|
||||||
|
</a-checkbox>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
<a-col v-if="formModel.fishSurveyZhuanZhi">
|
<a-col v-if="formModel.fishSurveyZhuanZhi">
|
||||||
<a-form-item label="" name="">
|
<a-form-item label="" name="">
|
||||||
<a-date-picker
|
<a-date-picker
|
||||||
@ -71,6 +78,7 @@
|
|||||||
:loading="fishSpeciesLoading"
|
:loading="fishSpeciesLoading"
|
||||||
:max-tag-count="1"
|
:max-tag-count="1"
|
||||||
option-filter-prop="label"
|
option-filter-prop="label"
|
||||||
|
@change="fishSurveyZhuanZhiParamsChange"
|
||||||
>
|
>
|
||||||
<a-select-option
|
<a-select-option
|
||||||
v-for="item in fishSpeciesOptions"
|
v-for="item in fishSpeciesOptions"
|
||||||
@ -160,6 +168,8 @@ const DRAWER_STTP_CODES = [
|
|||||||
'AIVD',
|
'AIVD',
|
||||||
'DW',
|
'DW',
|
||||||
'WE',
|
'WE',
|
||||||
|
'WVA',
|
||||||
|
'FPRD'
|
||||||
];
|
];
|
||||||
const siteRangePicker = [
|
const siteRangePicker = [
|
||||||
{ label: '全部', value: 'all' },
|
{ label: '全部', value: 'all' },
|
||||||
@ -354,9 +364,9 @@ const fetchFishSurveyOptions = async (
|
|||||||
|
|
||||||
fishPointOptions.value = nextFishPointOptions;
|
fishPointOptions.value = nextFishPointOptions;
|
||||||
fishSpeciesOptions.value = nextFishSpeciesOptions;
|
fishSpeciesOptions.value = nextFishSpeciesOptions;
|
||||||
fishSurveyZhuanZhiParams.value.value = nextFishSpeciesOptions.map(
|
// fishSurveyZhuanZhiParams.value.value = nextFishSpeciesOptions.map(
|
||||||
item => item.value
|
// item => item.value
|
||||||
);
|
// );
|
||||||
syncFishAnchorPointSelection(nextFishPointOptions);
|
syncFishAnchorPointSelection(nextFishPointOptions);
|
||||||
} finally {
|
} finally {
|
||||||
fishPointLoading.value = false;
|
fishPointLoading.value = false;
|
||||||
@ -552,6 +562,9 @@ const resetFishSurveyState = () => {
|
|||||||
fishPointLoading.value = false;
|
fishPointLoading.value = false;
|
||||||
fishSpeciesLoading.value = false;
|
fishSpeciesLoading.value = false;
|
||||||
syncFishPointLayerData([]);
|
syncFishPointLayerData([]);
|
||||||
|
// 退出鱼种筛选时,重置抽屉内容模式与选中物种
|
||||||
|
modelStore.selectedFishSpecies = [];
|
||||||
|
modelStore.globalDrawerMode = 'basicInfo';
|
||||||
};
|
};
|
||||||
|
|
||||||
// 备注:搜索下拉只从当前缓存点位和运行态筛选条件派生,不直接驱动地图行为。
|
// 备注:搜索下拉只从当前缓存点位和运行态筛选条件派生,不直接驱动地图行为。
|
||||||
@ -702,6 +715,14 @@ const focusSelectedAnchorPoint = () => {
|
|||||||
// 抽屉是否弹出由选中点位的 sttpCode 决定(命中 DRAWER_STTP_CODES 才弹)。
|
// 抽屉是否弹出由选中点位的 sttpCode 决定(命中 DRAWER_STTP_CODES 才弹)。
|
||||||
const handleAnchorPointChange = (value: string) => {
|
const handleAnchorPointChange = (value: string) => {
|
||||||
if (value == null) {
|
if (value == null) {
|
||||||
|
// 鱼种筛选仍选中时:清空锚点后切回“物种介绍”展示,不收起抽屉
|
||||||
|
if (
|
||||||
|
formModel.value.fishSurveyZhuanZhi &&
|
||||||
|
fishSurveyZhuanZhiParams.value.value.length > 0
|
||||||
|
) {
|
||||||
|
modelStore.globalDrawerMode = 'speciesIntro';
|
||||||
|
return;
|
||||||
|
}
|
||||||
// 清空:退出搜索状态,先收起全局抽屉,等完全关闭后再恢复页面自带抽屉
|
// 清空:退出搜索状态,先收起全局抽屉,等完全关闭后再恢复页面自带抽屉
|
||||||
uiStore.setIsSearchActive(false);
|
uiStore.setIsSearchActive(false);
|
||||||
uiStore.setGlobalDrawerOpen(false);
|
uiStore.setGlobalDrawerOpen(false);
|
||||||
@ -727,6 +748,34 @@ const handleAnchorPointChange = (value: string) => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// 需要抽屉:进入搜索状态,先收起页面自带抽屉,等完全关闭后再展示全局抽屉
|
// 需要抽屉:进入搜索状态,先收起页面自带抽屉,等完全关闭后再展示全局抽屉
|
||||||
|
// 切换为“基础信息”模式(与鱼种下拉的“物种介绍”模式互斥,后选者覆盖前者,且不关闭抽屉)
|
||||||
|
modelStore.globalDrawerMode = 'basicInfo';
|
||||||
|
uiStore.setIsSearchActive(true);
|
||||||
|
uiStore.setDrawerOpen(false);
|
||||||
|
setTimeout(() => {
|
||||||
|
uiStore.setGlobalDrawerOpen(true);
|
||||||
|
}, 300);
|
||||||
|
};
|
||||||
|
const fishSurveyZhuanZhiParamsChange = (value: string[]) => {
|
||||||
|
const selectedValues = Array.isArray(value) ? value : [];
|
||||||
|
// 鱼种清空:同步清空锚点检索框,并关闭全局抽屉、恢复页面自带抽屉
|
||||||
|
if (!selectedValues.length) {
|
||||||
|
fishSurveyZhuanZhiParams.value.anchorPointSelect = null;
|
||||||
|
modelStore.selectedFishSpecies = [];
|
||||||
|
modelStore.globalDrawerMode = 'basicInfo';
|
||||||
|
uiStore.setIsSearchActive(false);
|
||||||
|
uiStore.setGlobalDrawerOpen(false);
|
||||||
|
setTimeout(() => {
|
||||||
|
uiStore.setDrawerOpen(true);
|
||||||
|
}, 300);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const selectedSpecies = selectedValues
|
||||||
|
.map(v => fishSpeciesOptions.value.find(item => item.value === v))
|
||||||
|
.filter(Boolean) as { label: string; value: string }[];
|
||||||
|
// 记录选中物种并切换全局抽屉为“物种介绍”模式;抽屉已打开时仅替换内容
|
||||||
|
modelStore.selectedFishSpecies = selectedSpecies;
|
||||||
|
modelStore.globalDrawerMode = 'speciesIntro';
|
||||||
uiStore.setIsSearchActive(true);
|
uiStore.setIsSearchActive(true);
|
||||||
uiStore.setDrawerOpen(false);
|
uiStore.setDrawerOpen(false);
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import ylfbModal from '@/components/ylfbModal/index.vue';
|
|||||||
import GlobalRightDrawer from '@/components/GlobalRightDrawer/index.vue';
|
import GlobalRightDrawer from '@/components/GlobalRightDrawer/index.vue';
|
||||||
import basicInfoMod from '@/modules/rightSearchDrawer/basicInfo/index.vue';
|
import basicInfoMod from '@/modules/rightSearchDrawer/basicInfo/index.vue';
|
||||||
import monitoringTableMod from '@/modules/rightSearchDrawer/monitoringTable/index.vue';
|
import monitoringTableMod from '@/modules/rightSearchDrawer/monitoringTable/index.vue';
|
||||||
|
import speciesIntroMod from '@/modules/rightSearchDrawer/speciesIntro/index.vue';
|
||||||
import { useModelStore } from '@/store/modules/model';
|
import { useModelStore } from '@/store/modules/model';
|
||||||
import GisView from '@/components/gis/GisView.vue';
|
import GisView from '@/components/gis/GisView.vue';
|
||||||
|
|
||||||
@ -36,8 +37,15 @@ const isZhiNengPeiTu = computed(() => {
|
|||||||
|
|
||||||
<div class="global-drawer-wrap">
|
<div class="global-drawer-wrap">
|
||||||
<GlobalRightDrawer>
|
<GlobalRightDrawer>
|
||||||
|
<!-- 物种介绍模式:鱼种下拉选择时展示(动物/植物介绍 + 监测表格);否则展示基础信息 + 监测表格 -->
|
||||||
|
<template v-if="modelStore.globalDrawerMode === 'speciesIntro'">
|
||||||
|
<speciesIntroMod />
|
||||||
|
<monitoringTableMod />
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
<basicInfoMod />
|
<basicInfoMod />
|
||||||
<monitoringTableMod />
|
<monitoringTableMod />
|
||||||
|
</template>
|
||||||
</GlobalRightDrawer>
|
</GlobalRightDrawer>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@ -1,46 +1,95 @@
|
|||||||
<!-- SidePanelItem.vue -->
|
<!-- SidePanelItem.vue -->
|
||||||
<template>
|
<template>
|
||||||
<SidePanelItem title="地表水水质达标率" :select="select" :datetimePicker="datetimePicker"
|
<SidePanelItem
|
||||||
@update-values="handlePanelChange1" :style="chartData.length === 0 && !loading && baseid != 'all' ? 'height: 300px' : 'height: 650px'">
|
title="地表水水质达标率"
|
||||||
|
:select="select"
|
||||||
|
:datetimePicker="datetimePicker"
|
||||||
|
@update-values="handlePanelChange1"
|
||||||
|
:style="
|
||||||
|
chartData.length === 0 && !loading && baseid != 'all'
|
||||||
|
? 'height: 300px'
|
||||||
|
: 'height: 650px'
|
||||||
|
"
|
||||||
|
>
|
||||||
<div class="body_item">
|
<div class="body_item">
|
||||||
<div v-if="baseid == 'all'" class="tabs_all">
|
<div v-if="baseid == 'all'" class="tabs_all">
|
||||||
<div :class="tabs == '0' ? 'zhong_tabs' : 'no_tabs'" @click="handleTabChange(0)">自建水质站</div>
|
<div
|
||||||
<div :class="tabs == '1' ? 'zhong_tabs' : 'no_tabs'" @click="handleTabChange(1)">国家水质站</div>
|
:class="tabs == '0' ? 'zhong_tabs' : 'no_tabs'"
|
||||||
|
@click="handleTabChange(0)"
|
||||||
|
>
|
||||||
|
自建水质站
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
:class="tabs == '1' ? 'zhong_tabs' : 'no_tabs'"
|
||||||
|
@click="handleTabChange(1)"
|
||||||
|
>
|
||||||
|
国家水质站
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-show="tabs == '0' || tabs == '1'" class="tabs_body">
|
<div v-show="tabs == '0' || tabs == '1'" class="tabs_body">
|
||||||
<a-spin :spinning="loading">
|
<a-spin :spinning="loading">
|
||||||
<!-- 始终渲染图表容器,确保有固定宽高 -->
|
<!-- 始终渲染图表容器,确保有固定宽高 -->
|
||||||
<div ref="chartRef" class="chart-container"></div>
|
<div ref="chartRef" class="chart-container"></div>
|
||||||
<!-- 无数据时显示Empty,但不影响容器尺寸 -->
|
<!-- 无数据时显示Empty,但不影响容器尺寸 -->
|
||||||
<div v-if="chartData.length === 0 && !loading" class="empty-overlay" :style="chartData.length === 0 && !loading && baseid != 'all' ? 'height: 300px' : 'height: 650px'">
|
<div
|
||||||
|
v-if="chartData.length === 0 && !loading"
|
||||||
|
class="empty-overlay"
|
||||||
|
:style="
|
||||||
|
chartData.length === 0 && !loading && baseid != 'all'
|
||||||
|
? 'height: 300px'
|
||||||
|
: 'height: 650px'
|
||||||
|
"
|
||||||
|
>
|
||||||
<a-empty description="暂无数据" />
|
<a-empty description="暂无数据" />
|
||||||
</div>
|
</div>
|
||||||
</a-spin>
|
</a-spin>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</SidePanelItem>
|
</SidePanelItem>
|
||||||
|
|
||||||
<!-- 环境质量满足度弹框 -->
|
<!-- 环境质量满足度弹框 -->
|
||||||
<a-modal v-model:open="modalVisible" title="环境质量满足度" width="1536px" :footer="null" centered>
|
<a-modal
|
||||||
<EnvironmentalQualityTwoLayers :datas="(baseid == 'all' ? JidiSelectEventStore.jidiData : stationListForModal)" :type="select.value"
|
v-model:open="modalVisible"
|
||||||
:dateValue="datetimePicker.value" :dataDimensionVal="(clickedBarData?.selectedId || baseid)"
|
title="环境质量满足度"
|
||||||
:buildType="tabs" :typeOne="fieldOne" :baseid="baseid" />
|
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>
|
</a-modal>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref, onMounted, onUnmounted, watch, nextTick, computed, inject } from 'vue';
|
import {
|
||||||
|
ref,
|
||||||
|
onMounted,
|
||||||
|
onUnmounted,
|
||||||
|
watch,
|
||||||
|
nextTick,
|
||||||
|
computed,
|
||||||
|
inject,
|
||||||
|
defineOptions
|
||||||
|
} from 'vue';
|
||||||
import * as echarts from 'echarts';
|
import * as echarts from 'echarts';
|
||||||
import SidePanelItem from '@/components/SidePanelItem/index.vue';
|
import SidePanelItem from '@/components/SidePanelItem/index.vue';
|
||||||
import { wqGetKendoListCust } from '@/api/sz'
|
import { wqGetKendoListCust } from '@/api/sz';
|
||||||
import { useJidiSelectEventStore } from "@/store/modules/jidiSelectEvent";
|
import { useJidiSelectEventStore } from '@/store/modules/jidiSelectEvent';
|
||||||
import EnvironmentalQualityTwoLayers from "./TwoLayers/EnvironmentalQualityTwoLayers.vue"
|
import EnvironmentalQualityTwoLayers from './TwoLayers/EnvironmentalQualityTwoLayers.vue';
|
||||||
import { useDraggable } from '@/utils/drag';
|
import { useDraggable } from '@/utils/drag';
|
||||||
|
|
||||||
// 定义组件名(便于调试和递归)
|
// 定义组件名(便于调试和递归)
|
||||||
defineOptions({
|
defineOptions({
|
||||||
name: 'EnvironmentalQuality'
|
name: 'ShuiZhiDaBiaoQingKuang'
|
||||||
});
|
});
|
||||||
const JidiSelectEventStore = useJidiSelectEventStore();
|
const JidiSelectEventStore = useJidiSelectEventStore();
|
||||||
const tabs: any = ref('0');
|
const tabs: any = ref('0');
|
||||||
@ -49,10 +98,14 @@ let chartInstance: echarts.ECharts | null = null;
|
|||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
|
|
||||||
// 响式图表数据
|
// 响式图表数据
|
||||||
const chartData = ref<Array<{ name: string; current: number | null; lastYear: number | null }>>([]);
|
const chartData = ref<
|
||||||
|
Array<{ name: string; current: number | null; lastYear: number | null }>
|
||||||
|
>([]);
|
||||||
|
|
||||||
// 新增:存储当前基地下的电站列表(用于弹框传递)
|
// 新增:存储当前基地下的电站列表(用于弹框传递)
|
||||||
const stationListForModal = ref<Array<{ baseid: string; basename: string }>>([]);
|
const stationListForModal = ref<Array<{ baseid: string; basename: string }>>(
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
// 新增:存储图表数据索引与 baseid/rstcd 的映射关系
|
// 新增:存储图表数据索引与 baseid/rstcd 的映射关系
|
||||||
const chartDataIdMap = ref<Array<string>>([]);
|
const chartDataIdMap = ref<Array<string>>([]);
|
||||||
@ -65,7 +118,7 @@ const select = ref({
|
|||||||
{ value: 'year', label: '年度' }
|
{ value: 'year', label: '年度' }
|
||||||
],
|
],
|
||||||
picker: undefined,
|
picker: undefined,
|
||||||
format: undefined,
|
format: undefined
|
||||||
});
|
});
|
||||||
|
|
||||||
// 日期选择器配置
|
// 日期选择器配置
|
||||||
@ -77,16 +130,24 @@ const datetimePicker = ref({
|
|||||||
const month = String(now.getMonth() + 1).padStart(2, '0');
|
const month = String(now.getMonth() + 1).padStart(2, '0');
|
||||||
return `${year}-${month}`;
|
return `${year}-${month}`;
|
||||||
})(),
|
})(),
|
||||||
format: "YYYY-MM",
|
format: 'YYYY-MM',
|
||||||
picker: "month" as const,
|
picker: 'month' as const,
|
||||||
options: [],
|
options: []
|
||||||
});
|
});
|
||||||
const baseid = ref("")
|
const baseid = ref('');
|
||||||
const fieldOne = ref('')
|
const fieldOne = ref('');
|
||||||
// 解析API返回的嵌套数据(支持两种结构)
|
// 解析API返回的嵌套数据(支持两种结构)
|
||||||
const parseApiResponse = (items: any[], currentValueForApi: string, lastYearValue: string) => {
|
const parseApiResponse = (
|
||||||
const result: Array<{ name: string; current: number | null; lastYear: number | null }> = [];
|
items: any[],
|
||||||
fieldOne.value = ''
|
currentValueForApi: string,
|
||||||
|
lastYearValue: string
|
||||||
|
) => {
|
||||||
|
const result: Array<{
|
||||||
|
name: string;
|
||||||
|
current: number | null;
|
||||||
|
lastYear: number | null;
|
||||||
|
}> = [];
|
||||||
|
fieldOne.value = '';
|
||||||
// 新增:清空 ID 映射数组
|
// 新增:清空 ID 映射数组
|
||||||
chartDataIdMap.value = [];
|
chartDataIdMap.value = [];
|
||||||
|
|
||||||
@ -164,7 +225,10 @@ const parseApiResponse = (items: any[], currentValueForApi: string, lastYearValu
|
|||||||
|
|
||||||
if (dataItem) {
|
if (dataItem) {
|
||||||
// 兼容月度数据(COUNT_DRMONTH/wqRate)和年度数据(COUNT_DRYEAR)
|
// 兼容月度数据(COUNT_DRMONTH/wqRate)和年度数据(COUNT_DRYEAR)
|
||||||
const value = dataItem.COUNT_DRMONTH ?? dataItem.COUNT_DRYEAR ?? dataItem.wqRate;
|
const value =
|
||||||
|
dataItem.COUNT_DRMONTH ??
|
||||||
|
dataItem.COUNT_DRYEAR ??
|
||||||
|
dataItem.wqRate;
|
||||||
console.log(` 时间:${periodKey}, 值:${value}`);
|
console.log(` 时间:${periodKey}, 值:${value}`);
|
||||||
|
|
||||||
// 判断是当前期还是去年同期
|
// 判断是当前期还是去年同期
|
||||||
@ -181,16 +245,18 @@ const parseApiResponse = (items: any[], currentValueForApi: string, lastYearValu
|
|||||||
// 添加换行符:在"干流"前添加\n
|
// 添加换行符:在"干流"前添加\n
|
||||||
const finalDisplayName = displayName.replace(/干流$/, '\n干流');
|
const finalDisplayName = displayName.replace(/干流$/, '\n干流');
|
||||||
|
|
||||||
console.log(` → 最终数据: {name: "${finalDisplayName}", current: ${currentPeriodValue}, lastYear: ${lastYearPeriodValue}}`);
|
console.log(
|
||||||
|
` → 最终数据: {name: "${finalDisplayName}", current: ${currentPeriodValue}, lastYear: ${lastYearPeriodValue}}`
|
||||||
|
);
|
||||||
|
|
||||||
// 新增:记录 ID 映射关系
|
// 新增:记录 ID 映射关系
|
||||||
if (field === 'baseId') {
|
if (field === 'baseId') {
|
||||||
fieldOne.value = 'all'
|
fieldOne.value = 'all';
|
||||||
// baseid == 'all' 时,记录基地 ID
|
// baseid == 'all' 时,记录基地 ID
|
||||||
chartDataIdMap.value.push(mainGroup.key); // "02", "03" 等
|
chartDataIdMap.value.push(mainGroup.key); // "02", "03" 等
|
||||||
console.log(' 记录基地ID:', mainGroup.key);
|
console.log(' 记录基地ID:', mainGroup.key);
|
||||||
} else if (field === 'ennm') {
|
} else if (field === 'ennm') {
|
||||||
fieldOne.value = 'other'
|
fieldOne.value = 'other';
|
||||||
// baseid != 'all' 时,记录电站编码
|
// baseid != 'all' 时,记录电站编码
|
||||||
const stationCode = subGroup?.key; // "008660306300000001"
|
const stationCode = subGroup?.key; // "008660306300000001"
|
||||||
chartDataIdMap.value.push(stationCode || '');
|
chartDataIdMap.value.push(stationCode || '');
|
||||||
@ -233,27 +299,28 @@ const parseApiResponse = (items: any[], currentValueForApi: string, lastYearValu
|
|||||||
// 处理tab切换
|
// 处理tab切换
|
||||||
const handleTabChange = (tab: number) => {
|
const handleTabChange = (tab: number) => {
|
||||||
tabs.value = tab;
|
tabs.value = tab;
|
||||||
getEchartsData()
|
getEchartsData();
|
||||||
};
|
};
|
||||||
|
|
||||||
//监听tabs变化,处理图表刷新
|
//监听tabs变化,处理图表刷新
|
||||||
watch(tabs, (newVal) => {
|
watch(tabs, newVal => {
|
||||||
if (newVal === '0' || newVal === '1') {
|
if (newVal === '0' || newVal === '1') {
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
getEchartsData();
|
getEchartsData();
|
||||||
|
|
||||||
}, 50);
|
}, 50);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
// 监听电站变化(优先使用父组件 provide 的电站上下文,兜底 jidiStore)
|
// 监听电站变化(优先使用父组件 provide 的电站上下文,兜底 jidiStore)
|
||||||
const injectedStation = inject<any>('dianZhanStation', null);
|
const injectedStation = inject<any>('dianZhanStation', null);
|
||||||
const stationSource = computed(() => injectedStation?.value || JidiSelectEventStore.selectedItem);
|
const stationSource = computed(
|
||||||
|
() => injectedStation?.value || JidiSelectEventStore.selectedItem
|
||||||
|
);
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => stationSource.value,
|
() => stationSource.value,
|
||||||
(newVal) => {
|
newVal => {
|
||||||
if (newVal && newVal.wbsCode) {
|
if (newVal && newVal.wbsCode) {
|
||||||
baseid.value = newVal.wbsCode;
|
baseid.value = newVal.wbsCode;
|
||||||
getEchartsData();
|
getEchartsData();
|
||||||
@ -363,84 +430,96 @@ const getEchartsData = async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 3. 构建请求参数
|
// 3. 构建请求参数
|
||||||
console.log(baseid.value)
|
console.log(baseid.value);
|
||||||
if( ! baseid.value){
|
if (!baseid.value) {
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
//
|
//
|
||||||
let params = {
|
let params = {
|
||||||
"filter": {
|
filter: {
|
||||||
"logic": "and",
|
logic: 'and',
|
||||||
"filters": [
|
filters: [
|
||||||
baseid.value != 'all' ? {
|
baseid.value != 'all'
|
||||||
"field": "baseId",
|
? {
|
||||||
"operator": "eq",
|
field: 'baseId',
|
||||||
"value": baseid.value
|
operator: 'eq',
|
||||||
} : null,
|
value: baseid.value
|
||||||
|
}
|
||||||
|
: null,
|
||||||
{
|
{
|
||||||
"field": "type",
|
field: 'type',
|
||||||
"operator": "eq",
|
operator: 'eq',
|
||||||
"value": select.value.value == "year" ? "YEAR" : "MON"
|
value: select.value.value == 'year' ? 'YEAR' : 'MON'
|
||||||
},
|
},
|
||||||
baseid.value == 'all' ? {
|
baseid.value == 'all'
|
||||||
"field": "dtinType",
|
? {
|
||||||
"operator": "eq",
|
field: 'dtinType',
|
||||||
"value":tabs.value
|
operator: 'eq',
|
||||||
} : {
|
value: tabs.value
|
||||||
"field": "rstcd",
|
}
|
||||||
"operator": "isnotnull"
|
: {
|
||||||
}, ,
|
field: 'rstcd',
|
||||||
|
operator: 'isnotnull'
|
||||||
|
},
|
||||||
|
,
|
||||||
{
|
{
|
||||||
"logic": "or",
|
logic: 'or',
|
||||||
"filters": [
|
filters: [
|
||||||
{
|
{
|
||||||
"field": select.value.value == "year" ? "drYear" : "drMonth",
|
field: select.value.value == 'year' ? 'drYear' : 'drMonth',
|
||||||
"operator": "eq",
|
operator: 'eq',
|
||||||
"value": currentValueForApi, // 当前值(年或年月)
|
value: currentValueForApi, // 当前值(年或年月)
|
||||||
"dataType": "string"
|
dataType: 'string'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"field": select.value.value == "year" ? "drYear" : "drMonth",
|
field: select.value.value == 'year' ? 'drYear' : 'drMonth',
|
||||||
"operator": "eq",
|
operator: 'eq',
|
||||||
"value": lastYearValue, // 上一年同期
|
value: lastYearValue, // 上一年同期
|
||||||
"dataType": "string"
|
dataType: 'string'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
].filter(Boolean)
|
].filter(Boolean)
|
||||||
},
|
},
|
||||||
"group": [
|
group: [
|
||||||
baseid.value == 'all' ? {
|
baseid.value == 'all'
|
||||||
"dir": "des",
|
? {
|
||||||
"field": "baseStepSort"
|
dir: 'des',
|
||||||
} : {
|
field: 'baseStepSort'
|
||||||
"dir": "asc",
|
}
|
||||||
"field": "rstcdStepSort"
|
: {
|
||||||
}, ,
|
dir: 'asc',
|
||||||
baseid.value == 'all' ? {
|
field: 'rstcdStepSort'
|
||||||
"dir": "des",
|
|
||||||
"field": "baseId"
|
|
||||||
} : {
|
|
||||||
"dir": "asc",
|
|
||||||
"field": "ennm"
|
|
||||||
},
|
},
|
||||||
baseid.value == 'all' ? {
|
,
|
||||||
"dir": "des",
|
baseid.value == 'all'
|
||||||
"field": "baseName"
|
? {
|
||||||
} : {
|
dir: 'des',
|
||||||
"dir": "des",
|
field: 'baseId'
|
||||||
"field": "rstcd"
|
}
|
||||||
|
: {
|
||||||
|
dir: 'asc',
|
||||||
|
field: 'ennm'
|
||||||
|
},
|
||||||
|
baseid.value == 'all'
|
||||||
|
? {
|
||||||
|
dir: 'des',
|
||||||
|
field: 'baseName'
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
dir: 'des',
|
||||||
|
field: 'rstcd'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"dir": "desc",
|
dir: 'desc',
|
||||||
"field": select.value.value == "year" ? "drYear" : "drMonth"
|
field: select.value.value == 'year' ? 'drYear' : 'drMonth'
|
||||||
}
|
}
|
||||||
].filter(Boolean),
|
].filter(Boolean),
|
||||||
"groupResultFlat": false,
|
groupResultFlat: false,
|
||||||
"aggregate": [
|
aggregate: [
|
||||||
{
|
{
|
||||||
"aggregate": "sum",
|
aggregate: 'sum',
|
||||||
"field": "wqRate"
|
field: 'wqRate'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
@ -470,7 +549,11 @@ const getEchartsData = async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 解析数据
|
// 解析数据
|
||||||
const parsedData = parseApiResponse(res.data.data, currentValueForApi, lastYearValue);
|
const parsedData = parseApiResponse(
|
||||||
|
res.data.data,
|
||||||
|
currentValueForApi,
|
||||||
|
lastYearValue
|
||||||
|
);
|
||||||
|
|
||||||
console.log('解析后的数据 parsedData:', parsedData);
|
console.log('解析后的数据 parsedData:', parsedData);
|
||||||
|
|
||||||
@ -656,7 +739,9 @@ const updateChartData = (currentLabel: string, lastYearLabel: string) => {
|
|||||||
{
|
{
|
||||||
name: legendCurrentLabel,
|
name: legendCurrentLabel,
|
||||||
type: 'bar',
|
type: 'bar',
|
||||||
data: chartData.value.map(item => item.current != null ? Math.floor(Number(item.current)) : null),
|
data: chartData.value.map(item =>
|
||||||
|
item.current != null ? Math.floor(Number(item.current)) : null
|
||||||
|
),
|
||||||
barWidth: 10,
|
barWidth: 10,
|
||||||
barGap: '20%',
|
barGap: '20%',
|
||||||
itemStyle: {
|
itemStyle: {
|
||||||
@ -667,7 +752,9 @@ const updateChartData = (currentLabel: string, lastYearLabel: string) => {
|
|||||||
{
|
{
|
||||||
name: legendLastYearLabel,
|
name: legendLastYearLabel,
|
||||||
type: 'bar',
|
type: 'bar',
|
||||||
data: chartData.value.map(item => item.lastYear != null ? Math.floor(Number(item.lastYear)) : null),
|
data: chartData.value.map(item =>
|
||||||
|
item.lastYear != null ? Math.floor(Number(item.lastYear)) : null
|
||||||
|
),
|
||||||
barWidth: 10,
|
barWidth: 10,
|
||||||
barGap: '20%',
|
barGap: '20%',
|
||||||
itemStyle: {
|
itemStyle: {
|
||||||
@ -719,11 +806,11 @@ watch(
|
|||||||
);
|
);
|
||||||
|
|
||||||
//监听子组件的数据变化
|
//监听子组件的数据变化
|
||||||
const handlePanelChange1 = async (data) => {
|
const handlePanelChange1 = async data => {
|
||||||
console.log('当前所有控件状态:', data);
|
console.log('当前所有控件状态:', data);
|
||||||
// 当选择器或日期变化时,重新加载图表数据
|
// 当选择器或日期变化时,重新加载图表数据
|
||||||
if (data.select || data.datetime) {
|
if (data.select || data.datetime) {
|
||||||
await (select.value.value = data.select)
|
await (select.value.value = data.select);
|
||||||
|
|
||||||
// 根据 select 的值决定 datetime 的格式
|
// 根据 select 的值决定 datetime 的格式
|
||||||
let formattedDatetime = data.datetime;
|
let formattedDatetime = data.datetime;
|
||||||
@ -739,23 +826,23 @@ const handlePanelChange1 = async (data) => {
|
|||||||
}
|
}
|
||||||
// 如果是 month 模式,保持 datetime 原值不变
|
// 如果是 month 模式,保持 datetime 原值不变
|
||||||
|
|
||||||
await (datetimePicker.value.value = formattedDatetime)
|
await (datetimePicker.value.value = formattedDatetime);
|
||||||
await (datetimePicker.value.picker = data.select)
|
await (datetimePicker.value.picker = data.select);
|
||||||
await (datetimePicker.value.format = data.select == 'month' ? 'YYYY-MM' : 'YYYY')
|
await (datetimePicker.value.format =
|
||||||
getEchartsData()
|
data.select == 'month' ? 'YYYY-MM' : 'YYYY');
|
||||||
|
getEchartsData();
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
//获取图表数据
|
//获取图表数据
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// 页面加载时执行
|
// 页面加载时执行
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
// 延迟初始化,确保容器已渲染
|
// 延迟初始化,确保容器已渲染
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
initChart();
|
initChart();
|
||||||
// 如果已有 selectedItem,触发数据加载
|
// 如果已有 selectedItem,触发数据加载
|
||||||
const currentStation = injectedStation?.value || JidiSelectEventStore.selectedItem;
|
const currentStation =
|
||||||
|
injectedStation?.value || JidiSelectEventStore.selectedItem;
|
||||||
if (currentStation?.wbsCode) {
|
if (currentStation?.wbsCode) {
|
||||||
baseid.value = currentStation.wbsCode;
|
baseid.value = currentStation.wbsCode;
|
||||||
getEchartsData();
|
getEchartsData();
|
||||||
@ -780,10 +867,13 @@ onUnmounted(() => {
|
|||||||
|
|
||||||
// 弹框相关状态
|
// 弹框相关状态
|
||||||
const modalVisible = ref(false);
|
const modalVisible = ref(false);
|
||||||
const clickedBarData = ref<{ name: string; current: number | null; lastYear: number | null; selectedId?: string } | null>(null);
|
const clickedBarData = ref<{
|
||||||
|
name: string;
|
||||||
|
current: number | null;
|
||||||
|
lastYear: number | null;
|
||||||
|
selectedId?: string;
|
||||||
|
} | null>(null);
|
||||||
useDraggable(modalVisible, { boundary: true, resetOnOpen: true });
|
useDraggable(modalVisible, { boundary: true, resetOnOpen: true });
|
||||||
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@ -812,7 +902,7 @@ useDraggable(modalVisible, { boundary: true, resetOnOpen: true });
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
text-shadow: 0 0 .25px currentcolor;
|
text-shadow: 0 0 0.25px currentcolor;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -909,11 +999,11 @@ useDraggable(modalVisible, { boundary: true, resetOnOpen: true });
|
|||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
|
|
||||||
&.current {
|
&.current {
|
||||||
color: #4A8BC2;
|
color: #4a8bc2;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.last-year {
|
&.last-year {
|
||||||
color: #9B59B6;
|
color: #9b59b6;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.change {
|
&.change {
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,967 @@
|
|||||||
|
<template>
|
||||||
|
<div class="environmental-quality-container">
|
||||||
|
<!-- 搜索区域 -->
|
||||||
|
<a-form ref="formRef" :model="formState" layout="inline" class="search-form">
|
||||||
|
<a-space wrap>
|
||||||
|
<a-form-item label="所属基地" name="dataDimensionVal">
|
||||||
|
<a-select v-model:value="formState.dataDimensionVal" placeholder="请选择" style="width: 140px"
|
||||||
|
show-search :filter-option="filterBaseOption" option-filter-prop="label"
|
||||||
|
@change="handleBaseChange">
|
||||||
|
<a-select-option v-for="item in baseList" :key="item.wbsCode" :value="item.wbsCode"
|
||||||
|
:label="item.wbsName">
|
||||||
|
{{ item.wbsName }}
|
||||||
|
</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
|
||||||
|
<a-form-item v-if="typeOne == 'all'" label="测站类型" name="dtinType">
|
||||||
|
<a-select v-model:value="formState.dtinType" placeholder="请选择" style="width: 120px" show-search
|
||||||
|
:filter-option="filterOption" option-filter-prop="label">
|
||||||
|
<a-select-option :value="''" label="全部">全部</a-select-option>
|
||||||
|
<a-select-option :value="'0'" label="自建水质站">自建水质站</a-select-option>
|
||||||
|
<a-select-option :value="'1'" label="国家水质站">国家水质站</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
|
||||||
|
<a-form-item label="时间维度" name="types">
|
||||||
|
<a-select v-model:value="formState.types" placeholder="请选择" style="width: 80px" show-search
|
||||||
|
:filter-option="filterOption" option-filter-prop="label">
|
||||||
|
<a-select-option value="year" label="年度">年度</a-select-option>
|
||||||
|
<a-select-option value="month" label="月度">月度</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
|
||||||
|
<a-form-item label="达标状态" name="sfdb">
|
||||||
|
<a-select v-model:value="formState.sfdb" placeholder="请选择" style="width: 160px" show-search
|
||||||
|
:filter-option="filterOption" option-filter-prop="label">
|
||||||
|
<a-select-option value="" label="全部">全部</a-select-option>
|
||||||
|
<a-select-option value="0" label="不达标">不达标</a-select-option>
|
||||||
|
<a-select-option value="1" label="达标">达标</a-select-option>
|
||||||
|
<a-select-option value="2" label="无目标等级/无数据">无目标等级/无数据</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
|
||||||
|
<a-form-item label="时间" name="dateValues">
|
||||||
|
<a-date-picker v-model:value="formState.dateValues"
|
||||||
|
:format="formState.types === 'month' ? 'YYYY-MM' : 'YYYY'" :picker="formState.types"
|
||||||
|
placeholder="请选择月份" style="width: 100px" :disabled-date="disabledMonthDate" />
|
||||||
|
|
||||||
|
</a-form-item>
|
||||||
|
|
||||||
|
<a-form-item label="断面名称" name="stnm">
|
||||||
|
<a-input v-model:value="formState.stnm" placeholder="请输入断面名称" style="width: 180px" allow-clear />
|
||||||
|
</a-form-item>
|
||||||
|
|
||||||
|
<a-form-item>
|
||||||
|
<a-space>
|
||||||
|
<a-button type="primary" @click="handleSearch">查询</a-button>
|
||||||
|
<a-button @click="handleReset">重置</a-button>
|
||||||
|
</a-space>
|
||||||
|
</a-form-item>
|
||||||
|
</a-space>
|
||||||
|
</a-form>
|
||||||
|
|
||||||
|
<!-- 图表和表格布局区域 -->
|
||||||
|
<div class="content-layout">
|
||||||
|
<!-- 左侧饼图区域 -->
|
||||||
|
<div class="chart-section">
|
||||||
|
<a-spin :spinning="pieLoading" tip="加载中...">
|
||||||
|
<div ref="pieChartRef" class="pie-chart-container"></div>
|
||||||
|
<div v-if="!showPieChart && !pieLoading" class="empty-overlay">
|
||||||
|
<a-empty description="暂无数据" />
|
||||||
|
</div>
|
||||||
|
</a-spin>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 右侧表格区域 -->
|
||||||
|
<div class="table-section">
|
||||||
|
<BasicTable ref="tableRef" :scrollY="360" :columns="columns" :list-url="wqGetKendoList"
|
||||||
|
:search-params="{ sort: sortConfig }" :transform-data="customTransform">
|
||||||
|
<template #action="{ record }">
|
||||||
|
<!-- 操作列 -->
|
||||||
|
<a @click="handleViewDetail(record)" class="text-link">
|
||||||
|
查看详情
|
||||||
|
</a>
|
||||||
|
</template>
|
||||||
|
</BasicTable>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 详情弹窗 -->
|
||||||
|
<a-modal v-model:open="detailModalVisible" :title="`${detailData.stnm || ''} 详细信息`" width="1536px" :footer="null"
|
||||||
|
@cancel="handleDetailModalClose">
|
||||||
|
<HJMZDTwoLays v-if="detailModalVisible" :tc="{
|
||||||
|
stcd: detailData.stcd || '',
|
||||||
|
startTime: detailData.tm,
|
||||||
|
endTime: (typeof detailData.tm === 'string' ? detailData.tm.split(' ')[0] : dayjs(detailData.tm).format('YYYY-MM-DD')) + ' 23:59:59',
|
||||||
|
sfdb: formState.sfdb || ''
|
||||||
|
}" :calculateFlag="{ calculateFlag: 0 }" />
|
||||||
|
</a-modal>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, reactive, computed, onMounted, onBeforeUnmount, watch, nextTick, h } from 'vue'
|
||||||
|
import { message } from 'ant-design-vue'
|
||||||
|
import * as echarts from 'echarts'
|
||||||
|
import dayjs, { Dayjs } from 'dayjs'
|
||||||
|
import isSameOrAfter from 'dayjs/plugin/isSameOrAfter'
|
||||||
|
import BasicTable from '@/components/BasicTable/index.vue'
|
||||||
|
import { wqGetKendoList } from '@/api/sz'
|
||||||
|
import HJMZDTwoLays from "./HJMZDTwoLays.vue"
|
||||||
|
import { useDraggable } from '@/utils/drag';
|
||||||
|
|
||||||
|
// 注册 dayjs 插件
|
||||||
|
dayjs.extend(isSameOrAfter)
|
||||||
|
|
||||||
|
// Props 定义
|
||||||
|
const props = defineProps<{
|
||||||
|
datas?: Array<{ wbsCode: string; wbsName: string }> // 基地列表
|
||||||
|
type?: string // 时间维度
|
||||||
|
modalData?: string // 模态框数据
|
||||||
|
dateValue?: string | Dayjs // 日期值
|
||||||
|
dataDimensionVal?: string // 数据维度值
|
||||||
|
buildType?: string | number // 建设类型
|
||||||
|
typeOne: string //区分基地和站点
|
||||||
|
baseid: string
|
||||||
|
}>()
|
||||||
|
|
||||||
|
// Refs
|
||||||
|
const formRef = ref()
|
||||||
|
const tableRef = ref()
|
||||||
|
const pieChartRef = ref<HTMLElement | null>(null)
|
||||||
|
let pieChartInstance: echarts.ECharts | null = null
|
||||||
|
|
||||||
|
// State
|
||||||
|
const baseid = ref(props.baseid || '')
|
||||||
|
const typeOne = ref(props.typeOne || '')
|
||||||
|
const originalMonth = ref<string>('') // 保存原始月份,用于年度切回月度时恢复
|
||||||
|
const formState = reactive({
|
||||||
|
dataDimensionVal: props.dataDimensionVal || '',
|
||||||
|
dtinType: props.buildType.toString() || '',
|
||||||
|
types: props.type || 'month',
|
||||||
|
sfdb: '',
|
||||||
|
dateValues: props.dateValue ? (typeof props.dateValue === 'string' ? dayjs(props.dateValue, props.type === 'month' ? 'YYYY-MM' : 'YYYY') : props.dateValue) : dayjs(),
|
||||||
|
stnm: ''
|
||||||
|
})
|
||||||
|
const kong = ref(true)
|
||||||
|
const baseList = computed(() => props.datas || [])
|
||||||
|
const pieLoading = ref(false)
|
||||||
|
const showPieChart = ref(true)
|
||||||
|
const pieCode = ref<string[]>([]) // 饼图选中的代码,用于过滤表格
|
||||||
|
const displayTotal = ref<number>(0) // 饼图中心显示的总数(动态更新)
|
||||||
|
|
||||||
|
// 弹窗相关
|
||||||
|
const detailModalVisible = ref(false)
|
||||||
|
const detailData = ref<any>({})
|
||||||
|
const mapModalVisible = ref(false)
|
||||||
|
const mapModalData = ref<any>({})
|
||||||
|
useDraggable(detailModalVisible, { boundary: true, resetOnOpen: true });
|
||||||
|
// 排序配置
|
||||||
|
const sortConfig = computed(() => [
|
||||||
|
{ field: 'rstcdStepSort', dir: 'asc' },
|
||||||
|
{ field: 'stnm', dir: 'asc' },
|
||||||
|
{ field: 'tm', dir: 'desc' }
|
||||||
|
])
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// 自定义数据转换
|
||||||
|
const customTransform = (res: any) => {
|
||||||
|
return {
|
||||||
|
records: res?.data?.data || [],
|
||||||
|
total: res?.data?.total || 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 计算断面名称合并信息
|
||||||
|
const getStnmMergeInfo = (records: any[]) => {
|
||||||
|
if (!records || records.length === 0) return {}
|
||||||
|
|
||||||
|
const mergeInfo: Record<number, { rowSpan: number }> = {}
|
||||||
|
let i = 0
|
||||||
|
|
||||||
|
while (i < records.length) {
|
||||||
|
const currentStnm = records[i].stnm
|
||||||
|
let rowSpan = 1
|
||||||
|
|
||||||
|
// 查找后续相同断面名称的行
|
||||||
|
let j = i + 1
|
||||||
|
while (j < records.length && records[j].stnm === currentStnm) {
|
||||||
|
rowSpan++
|
||||||
|
j++
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置第一行的 rowSpan
|
||||||
|
mergeInfo[i] = { rowSpan }
|
||||||
|
|
||||||
|
// 设置后续行的 rowSpan 为 0(隐藏)
|
||||||
|
for (let k = i + 1; k < j; k++) {
|
||||||
|
mergeInfo[k] = { rowSpan: 0 }
|
||||||
|
}
|
||||||
|
|
||||||
|
i = j
|
||||||
|
}
|
||||||
|
|
||||||
|
return mergeInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// 表格列定义
|
||||||
|
const columns = [
|
||||||
|
{
|
||||||
|
title: '日期',
|
||||||
|
dataIndex: 'tm',
|
||||||
|
key: 'tm',
|
||||||
|
width: 120,
|
||||||
|
customRender: ({ record }: any) => {
|
||||||
|
const date = record.tm
|
||||||
|
if (!date) return '-'
|
||||||
|
if (typeof date === 'string' && /^\d{4}-\d{2}-\d{2}$/.test(date)) {
|
||||||
|
return date
|
||||||
|
}
|
||||||
|
if (typeof date === 'string' && date.includes(' ')) {
|
||||||
|
return date.split(' ')[0]
|
||||||
|
}
|
||||||
|
return date
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '断面名称',
|
||||||
|
dataIndex: 'stnm',
|
||||||
|
key: 'stnm',
|
||||||
|
width: 200,
|
||||||
|
ellipsis: true,
|
||||||
|
customCell: (record: any, rowIndex: number) => {
|
||||||
|
const tableData = tableRef.value?.tableData || []
|
||||||
|
const mergeInfo = getStnmMergeInfo(tableData)
|
||||||
|
return mergeInfo[rowIndex] || { rowSpan: 1 }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '测站类型',
|
||||||
|
dataIndex: 'sttypeName',
|
||||||
|
key: 'sttypeName',
|
||||||
|
width: 80
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '达标状态',
|
||||||
|
dataIndex: 'sfdbName',
|
||||||
|
key: 'sfdbName',
|
||||||
|
width: 80,
|
||||||
|
customRender: ({ record }: any) => {
|
||||||
|
const sfdb = record.sfdbName
|
||||||
|
let color = '#000000'
|
||||||
|
|
||||||
|
if (sfdb === '达标') {
|
||||||
|
color = '#008000' // 达标 - 绿色
|
||||||
|
} else if (sfdb === '不达标') {
|
||||||
|
color = '#FF0000' // 不达标 - 红色
|
||||||
|
}
|
||||||
|
|
||||||
|
return h('span', { style: { color } }, record.sfdbName || '-')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '水质要求',
|
||||||
|
dataIndex: 'wwqtgName',
|
||||||
|
key: 'wwqtgName',
|
||||||
|
width: 80
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '当前水质',
|
||||||
|
dataIndex: 'wqGrdName',
|
||||||
|
key: 'wqGrdName',
|
||||||
|
width: 80
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '去年同期水质',
|
||||||
|
dataIndex: 'beforeWqGrdName',
|
||||||
|
key: 'beforeWqGrdName',
|
||||||
|
width: 140
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '操作',
|
||||||
|
key: 'action',
|
||||||
|
width: 100,
|
||||||
|
fixed: 'right',
|
||||||
|
slots: { customRender: 'action' }
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
// 禁用未来月份
|
||||||
|
const disabledMonthDate = (current: Dayjs) => {
|
||||||
|
if (!current) return false
|
||||||
|
const now = dayjs()
|
||||||
|
return current.isAfter(now, 'month')
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// 基地选择器搜索过滤函数
|
||||||
|
const filterBaseOption = (input: string, option: any) => {
|
||||||
|
return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// 通用下拉框搜索过滤函数
|
||||||
|
const filterOption = (input: string, option: any) => {
|
||||||
|
return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取饼图数据
|
||||||
|
const fetchPieData = async () => {
|
||||||
|
pieLoading.value = true
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 计算时间参数
|
||||||
|
const timeFormat = formState.types === 'month' ? 'YYYY-MM' : 'YYYY'
|
||||||
|
const startTime = formState.dateValues ? formState.dateValues.format(timeFormat) : ''
|
||||||
|
|
||||||
|
if (!startTime || !formState.dataDimensionVal) {
|
||||||
|
showPieChart.value = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构建请求参数
|
||||||
|
const filters: any[] = [
|
||||||
|
formState.dataDimensionVal != 'all' ? {
|
||||||
|
field: 'baseId',
|
||||||
|
operator: 'eq',
|
||||||
|
dataType: 'string',
|
||||||
|
value: typeOne.value == 'all' ? formState.dataDimensionVal : baseid.value
|
||||||
|
} : null,
|
||||||
|
formState.types === 'month' ? {
|
||||||
|
field: 'drMonth',
|
||||||
|
operator: 'eq',
|
||||||
|
dataType: 'string',
|
||||||
|
value: startTime
|
||||||
|
} : {
|
||||||
|
field: 'drYear',
|
||||||
|
operator: 'eq',
|
||||||
|
dataType: 'string',
|
||||||
|
value: startTime
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'TYPE',
|
||||||
|
operator: 'eq',
|
||||||
|
dataType: 'string',
|
||||||
|
value: 'DAY'
|
||||||
|
},
|
||||||
|
typeOne.value != 'all' ? {
|
||||||
|
field: 'rstcd',
|
||||||
|
operator: 'eq',
|
||||||
|
dataType: 'string',
|
||||||
|
value: formState.dataDimensionVal
|
||||||
|
} : null,
|
||||||
|
pieCode.value.length > 0 ? {
|
||||||
|
field: 'sfdb',
|
||||||
|
operator: 'in',
|
||||||
|
dataType: 'string',
|
||||||
|
value: pieCode.value
|
||||||
|
} : (formState.sfdb ? {
|
||||||
|
field: 'sfdb',
|
||||||
|
operator: 'eq',
|
||||||
|
dataType: 'string',
|
||||||
|
value: formState.sfdb
|
||||||
|
} : null),
|
||||||
|
formState.stnm ? {
|
||||||
|
field: 'stnm',
|
||||||
|
operator: 'contains',
|
||||||
|
dataType: 'string',
|
||||||
|
value: formState.stnm
|
||||||
|
} : null,
|
||||||
|
typeOne.value == 'all' && formState.dtinType != '' ? {
|
||||||
|
field: 'dtinType',
|
||||||
|
operator: 'eq',
|
||||||
|
dataType: 'string',
|
||||||
|
value: formState.dtinType
|
||||||
|
} : null
|
||||||
|
].filter(Boolean)
|
||||||
|
|
||||||
|
const params = {
|
||||||
|
filter: {
|
||||||
|
logic: 'and',
|
||||||
|
filters
|
||||||
|
},
|
||||||
|
group: [
|
||||||
|
{
|
||||||
|
"dir": "des",
|
||||||
|
"field": "sfdb"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
// 调用API获取饼图数据
|
||||||
|
const res = await wqGetKendoList(params)
|
||||||
|
|
||||||
|
let data = res?.data?.data || res?.data || []
|
||||||
|
|
||||||
|
if (data.length === 0) {
|
||||||
|
// 销毁图表实例
|
||||||
|
if (pieChartInstance) {
|
||||||
|
pieChartInstance.dispose()
|
||||||
|
pieChartInstance = null
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重置相关状态
|
||||||
|
displayTotal.value = 0
|
||||||
|
pieCode.value = []
|
||||||
|
showPieChart.value = false
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 转换数据格式 - 为每个数据项设置对应颜色
|
||||||
|
const dataSource = data.map((item: any) => {
|
||||||
|
const name = item.keyName || 'null'
|
||||||
|
// 根据名称设置颜色:达标-绿色,不达标-红色,其他-灰色
|
||||||
|
let color = '#d9d9d9'
|
||||||
|
if (name === '达标') {
|
||||||
|
color = '#52c41a' // 绿色
|
||||||
|
} else if (name === '不达标') {
|
||||||
|
color = '#ff4d4f' // 红色
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
name: name,
|
||||||
|
value: item.count,
|
||||||
|
key: item.keyName === '达标' ? 'DB_STATUS_1' : item.keyName === '不达标' ? 'DB_STATUS_0' : 'CURNODATA',
|
||||||
|
keyData: item?.key,
|
||||||
|
itemStyle: {
|
||||||
|
color: color
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 使用所有数据项,包括"无数据"
|
||||||
|
const filteredDataSource = dataSource
|
||||||
|
|
||||||
|
if (filteredDataSource.length === 0) {
|
||||||
|
showPieChart.value = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
showPieChart.value = true
|
||||||
|
|
||||||
|
// 初始化或更新饼图
|
||||||
|
await nextTick()
|
||||||
|
initPieChart(filteredDataSource)
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取饼图数据失败:', error)
|
||||||
|
message.error('获取饼图数据失败')
|
||||||
|
showPieChart.value = false
|
||||||
|
} finally {
|
||||||
|
pieLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化饼图
|
||||||
|
const initPieChart = (data: any[]) => {
|
||||||
|
if (!pieChartRef.value) return
|
||||||
|
|
||||||
|
// 如果实例已存在,先销毁
|
||||||
|
if (pieChartInstance) {
|
||||||
|
pieChartInstance.dispose()
|
||||||
|
pieChartInstance = null
|
||||||
|
}
|
||||||
|
|
||||||
|
pieChartInstance = echarts.init(pieChartRef.value)
|
||||||
|
|
||||||
|
// 计算总数并初始化 displayTotal
|
||||||
|
const total = data.reduce((sum, item) => sum + item.value, 0)
|
||||||
|
displayTotal.value = total
|
||||||
|
|
||||||
|
const option = {
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'item',
|
||||||
|
formatter: '{b}: {c} 次 ({d}%)'
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
orient: 'vertical',
|
||||||
|
right: '10%',
|
||||||
|
top: '10%',
|
||||||
|
data: data.map(item => item.name)
|
||||||
|
},
|
||||||
|
title: [
|
||||||
|
{
|
||||||
|
text: displayTotal.value.toString(),
|
||||||
|
left: '39%',
|
||||||
|
top: '38%',
|
||||||
|
textAlign: 'center',
|
||||||
|
textStyle: {
|
||||||
|
fontSize: 20,
|
||||||
|
fontWeight: 'bold',
|
||||||
|
color: '#2f6b98'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: '监测总次数',
|
||||||
|
left: '39%',
|
||||||
|
top: '44%',
|
||||||
|
textAlign: 'center',
|
||||||
|
textStyle: {
|
||||||
|
fontSize: 13,
|
||||||
|
color: '#333333',
|
||||||
|
fontWeight: '500'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: '(次)',
|
||||||
|
left: '39%',
|
||||||
|
top: '49%',
|
||||||
|
textAlign: 'center',
|
||||||
|
textStyle: {
|
||||||
|
fontSize: 13,
|
||||||
|
color: '#333333',
|
||||||
|
fontWeight: '500'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
name: '断面总数量',
|
||||||
|
type: 'pie',
|
||||||
|
radius: ['25%', '35%'],
|
||||||
|
center: ['40%', '45%'],
|
||||||
|
avoidLabelOverlap: false,
|
||||||
|
itemStyle: {
|
||||||
|
borderRadius: 10,
|
||||||
|
borderColor: '#fff',
|
||||||
|
borderWidth: 2
|
||||||
|
},
|
||||||
|
label: {
|
||||||
|
show: true,
|
||||||
|
formatter: '{b}'
|
||||||
|
},
|
||||||
|
emphasis: {
|
||||||
|
label: {
|
||||||
|
show: true,
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: 'bold'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data: data
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
pieChartInstance.setOption(option)
|
||||||
|
|
||||||
|
// 绑定点击事件 - 过滤表格数据
|
||||||
|
pieChartInstance.on('legendselectchanged', (params: any) => {
|
||||||
|
kong.value = false
|
||||||
|
const selectedKeys = Object.keys(params.selected).filter(key => params.selected[key])
|
||||||
|
const allFalse = Object.values(params.selected).every(value => value === false)
|
||||||
|
|
||||||
|
// 动态计算当前显示的总数
|
||||||
|
if (allFalse) {
|
||||||
|
// 所有图例都取消选中,显示 0
|
||||||
|
displayTotal.value = 0
|
||||||
|
} else {
|
||||||
|
// 只统计被选中图例的数量总和
|
||||||
|
displayTotal.value = data
|
||||||
|
.filter(item => selectedKeys.includes(item.name))
|
||||||
|
.reduce((sum, item) => sum + item.value, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 局部更新 title 文本
|
||||||
|
pieChartInstance.setOption({
|
||||||
|
title: [
|
||||||
|
{
|
||||||
|
text: displayTotal.value.toString()
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
const selectedCodes = data
|
||||||
|
.filter(item => selectedKeys.includes(item.name))
|
||||||
|
.map(item => item.keyData)
|
||||||
|
|
||||||
|
pieCode.value = allFalse ? ['5'] : selectedCodes
|
||||||
|
// 刷新表格
|
||||||
|
handleSearch()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 基地变化处理
|
||||||
|
const handleBaseChange = () => {
|
||||||
|
formState.stnm = ''
|
||||||
|
handleSearch()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询处理
|
||||||
|
const handleSearch = () => {
|
||||||
|
const timeFormat = formState.types === 'month' ? 'YYYY-MM' : 'YYYY'
|
||||||
|
const startTime = formState.dateValues ? formState.dateValues.format(timeFormat) : ''
|
||||||
|
// console.log( typeOne.value )
|
||||||
|
// console.log( formState.dtinType )
|
||||||
|
// console.log(typeOne.value == 'all' && formState.dtinType !='')
|
||||||
|
//
|
||||||
|
const filters: any[] = [
|
||||||
|
formState.dataDimensionVal != 'all' ? {
|
||||||
|
field: 'baseId',
|
||||||
|
operator: 'eq',
|
||||||
|
dataType: 'string',
|
||||||
|
value: typeOne.value == 'all' ? formState.dataDimensionVal : baseid.value
|
||||||
|
} : null,
|
||||||
|
formState.types === 'month' ? {
|
||||||
|
field: 'drMonth',
|
||||||
|
operator: 'eq',
|
||||||
|
dataType: 'string',
|
||||||
|
value: startTime
|
||||||
|
} : {
|
||||||
|
field: 'drYear',
|
||||||
|
operator: 'eq',
|
||||||
|
dataType: 'string',
|
||||||
|
value: startTime
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'TYPE',
|
||||||
|
operator: 'eq',
|
||||||
|
dataType: 'string',
|
||||||
|
value: 'DAY'
|
||||||
|
},
|
||||||
|
typeOne.value != 'all' ? {
|
||||||
|
field: 'rstcd',
|
||||||
|
operator: 'eq',
|
||||||
|
dataType: 'string',
|
||||||
|
value: formState.dataDimensionVal
|
||||||
|
} : null,
|
||||||
|
pieCode.value.length > 0 ? {
|
||||||
|
field: 'sfdb',
|
||||||
|
operator: 'in',
|
||||||
|
dataType: 'string',
|
||||||
|
value: pieCode.value
|
||||||
|
} : (formState.sfdb ? {
|
||||||
|
field: 'sfdb',
|
||||||
|
operator: 'eq',
|
||||||
|
dataType: 'string',
|
||||||
|
value: formState.sfdb
|
||||||
|
} : null),
|
||||||
|
formState.stnm ? {
|
||||||
|
field: 'stnm',
|
||||||
|
operator: 'contains',
|
||||||
|
dataType: 'string',
|
||||||
|
value: formState.stnm
|
||||||
|
} : null,
|
||||||
|
typeOne.value == 'all' && formState.dtinType != '' ? {
|
||||||
|
field: 'dtinType',
|
||||||
|
operator: 'eq',
|
||||||
|
dataType: 'string',
|
||||||
|
value: formState.dtinType
|
||||||
|
} : null
|
||||||
|
].filter(Boolean)
|
||||||
|
|
||||||
|
const filter = {
|
||||||
|
logic: 'and',
|
||||||
|
filters
|
||||||
|
}
|
||||||
|
|
||||||
|
// 刷新表格
|
||||||
|
tableRef.value?.getList(filter)
|
||||||
|
if (kong.value) {
|
||||||
|
//
|
||||||
|
fetchPieData()
|
||||||
|
}
|
||||||
|
kong.value = true
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重置处理
|
||||||
|
const handleReset = () => {
|
||||||
|
formState.dataDimensionVal = props.dataDimensionVal || ''
|
||||||
|
formState.dtinType = ''
|
||||||
|
formState.types = props.type || 'month'
|
||||||
|
formState.sfdb = ''
|
||||||
|
formState.dateValues =props.dateValue ? (typeof props.dateValue === 'string' ? dayjs(props.dateValue, props.type === 'month' ? 'YYYY-MM' : 'YYYY') : props.dateValue) : dayjs(),
|
||||||
|
formState.stnm = ''
|
||||||
|
pieCode.value = []
|
||||||
|
|
||||||
|
handleSearch()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查看详情
|
||||||
|
const handleViewDetail = (record: any) => {
|
||||||
|
detailData.value = { ...record }
|
||||||
|
detailModalVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 详情弹窗关闭
|
||||||
|
const handleDetailModalClose = () => {
|
||||||
|
detailModalVisible.value = false
|
||||||
|
detailData.value = {}
|
||||||
|
}
|
||||||
|
// 地图弹窗关闭
|
||||||
|
const handleMapModalClose = () => {
|
||||||
|
mapModalVisible.value = false
|
||||||
|
mapModalData.value = {}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 监听日期变化
|
||||||
|
watch(
|
||||||
|
() => formState.dateValues,
|
||||||
|
() => {
|
||||||
|
handleSearch()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// 监听 props.dataDimensionVal 变化,重置表单状态并重新加载数据
|
||||||
|
watch(
|
||||||
|
() => props.dataDimensionVal,
|
||||||
|
(newVal) => {
|
||||||
|
// 重置表单状态
|
||||||
|
formState.dataDimensionVal = newVal || ''
|
||||||
|
formState.dtinType = props.buildType.toString() || ''
|
||||||
|
formState.sfdb = ''
|
||||||
|
formState.stnm = ''
|
||||||
|
pieCode.value = []
|
||||||
|
// 重新加载数据
|
||||||
|
nextTick(() => {
|
||||||
|
handleSearch()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// 监听 props.type 变化,同步更新时间维度和日期格式
|
||||||
|
watch(
|
||||||
|
() => props.type,
|
||||||
|
(newType) => {
|
||||||
|
if (!newType || newType === formState.types) return
|
||||||
|
|
||||||
|
// 转换日期格式以匹配新的时间维度
|
||||||
|
const currentValue: any = formState.dateValues
|
||||||
|
if (currentValue) {
|
||||||
|
if (newType === 'year') {
|
||||||
|
// 切换到年度模式:保存当前月份
|
||||||
|
if (typeof currentValue.year === 'function') {
|
||||||
|
// dayjs 对象,保存月份
|
||||||
|
originalMonth.value = String(currentValue.month() + 1).padStart(2, '0')
|
||||||
|
const year = currentValue.year()
|
||||||
|
formState.dateValues = dayjs(String(year), 'YYYY')
|
||||||
|
} else if (typeof currentValue === 'string') {
|
||||||
|
// 字符串,提取并保存月份
|
||||||
|
const monthMatch = currentValue.match(/^(\d{4})-(\d{2})/)
|
||||||
|
if (monthMatch) {
|
||||||
|
originalMonth.value = monthMatch[2]
|
||||||
|
formState.dateValues = dayjs(monthMatch[1], 'YYYY')
|
||||||
|
} else {
|
||||||
|
const yearMatch = currentValue.match(/^(\d{4})/)
|
||||||
|
if (yearMatch) {
|
||||||
|
formState.dateValues = dayjs(yearMatch[1], 'YYYY')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (newType === 'month') {
|
||||||
|
// 切换到月度模式:恢复原来保存的月份
|
||||||
|
const monthToUse = originalMonth.value || '01'
|
||||||
|
if (typeof currentValue.year === 'function') {
|
||||||
|
const year = currentValue.year()
|
||||||
|
formState.dateValues = dayjs(`${year}-${monthToUse}`, 'YYYY-MM')
|
||||||
|
} else if (typeof currentValue === 'string') {
|
||||||
|
const yearMatch = currentValue.match(/^(\d{4})/)
|
||||||
|
if (yearMatch) {
|
||||||
|
formState.dateValues = dayjs(`${yearMatch[1]}-${monthToUse}`, 'YYYY-MM')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新时间维度
|
||||||
|
formState.types = newType
|
||||||
|
|
||||||
|
// 重新加载数据
|
||||||
|
nextTick(() => {
|
||||||
|
handleSearch()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// 监听 props.typeOne 变化,同步更新本地 state
|
||||||
|
watch(
|
||||||
|
() => props.typeOne,
|
||||||
|
(newVal) => {
|
||||||
|
typeOne.value = newVal
|
||||||
|
//
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// 监听 props.baseid 变化,同步更新本地 state
|
||||||
|
watch(
|
||||||
|
() => props.baseid,
|
||||||
|
(newVal) => {
|
||||||
|
baseid.value = newVal || ''
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// 监听时间维度变化,自动转换日期格式
|
||||||
|
watch(
|
||||||
|
() => formState.types,
|
||||||
|
(newType, oldType) => {
|
||||||
|
if (!formState.dateValues || newType === oldType) return
|
||||||
|
//
|
||||||
|
const currentValue: any = formState.dateValues
|
||||||
|
|
||||||
|
if (newType === 'year') {
|
||||||
|
// 从月度切换到年度:保存当前月份
|
||||||
|
if (currentValue && typeof currentValue.year === 'function') {
|
||||||
|
// dayjs 对象,保存月份
|
||||||
|
originalMonth.value = String(currentValue.month() + 1).padStart(2, '0')
|
||||||
|
const year = currentValue.year()
|
||||||
|
formState.dateValues = dayjs(String(year), 'YYYY')
|
||||||
|
} else if (typeof currentValue === 'string') {
|
||||||
|
// 字符串,提取并保存月份
|
||||||
|
const monthMatch = currentValue.match(/^(\d{4})-(\d{2})/)
|
||||||
|
if (monthMatch) {
|
||||||
|
originalMonth.value = monthMatch[2]
|
||||||
|
formState.dateValues = dayjs(monthMatch[1], 'YYYY')
|
||||||
|
} else {
|
||||||
|
const yearMatch = currentValue.match(/^(\d{4})/)
|
||||||
|
if (yearMatch) {
|
||||||
|
formState.dateValues = dayjs(yearMatch[1], 'YYYY')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (newType === 'month') {
|
||||||
|
// 从年度切换到月度:恢复原来保存的月份
|
||||||
|
const monthToUse = originalMonth.value || '01'
|
||||||
|
if (currentValue && typeof currentValue.year === 'function') {
|
||||||
|
const year = currentValue.year()
|
||||||
|
formState.dateValues = dayjs(`${year}-${monthToUse}`, 'YYYY-MM')
|
||||||
|
} else if (typeof currentValue === 'string') {
|
||||||
|
const yearMatch = currentValue.match(/^(\d{4})/)
|
||||||
|
if (yearMatch) {
|
||||||
|
formState.dateValues = dayjs(`${yearMatch[1]}-${monthToUse}`, 'YYYY-MM')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 日期格式转换后,重新获取数据
|
||||||
|
nextTick(() => {
|
||||||
|
handleSearch()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// 初始化
|
||||||
|
onMounted(async () => {
|
||||||
|
await nextTick()
|
||||||
|
handleSearch()
|
||||||
|
|
||||||
|
// 监听窗口resize
|
||||||
|
window.addEventListener('resize', () => {
|
||||||
|
pieChartInstance?.resize()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
// 组件卸载时清理
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
pieChartInstance?.dispose()
|
||||||
|
pieChartInstance = null
|
||||||
|
window.removeEventListener('resize', () => {
|
||||||
|
pieChartInstance?.resize()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
// 暴露方法给父组件
|
||||||
|
defineExpose({
|
||||||
|
handleSearch
|
||||||
|
})
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.environmental-quality-container {
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
.search-form {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 0px 0px 16px 0px;
|
||||||
|
|
||||||
|
:deep(.ant-space) {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.ant-form-item) {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-layout {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
gap: 16px;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
.chart-section {
|
||||||
|
width: 35%;
|
||||||
|
min-width: 300px;
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 16px;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
.pie-chart-container {
|
||||||
|
width: 100%;
|
||||||
|
height: 482px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-overlay {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background: rgba(255, 255, 255, 0.9);
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-section {
|
||||||
|
flex: 1;
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 16px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-link {
|
||||||
|
color: #2f6b98;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: #40a9ff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-content {
|
||||||
|
padding: 20px;
|
||||||
|
max-height: 600px;
|
||||||
|
overflow-y: auto;
|
||||||
|
|
||||||
|
pre {
|
||||||
|
background: #f5f5f5;
|
||||||
|
padding: 12px;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 12px;
|
||||||
|
margin-top: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.ant-spin-nested-loading) {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.ant-spin-container) {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -0,0 +1,595 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<!-- 搜索表单 - 使用 Ant Design Vue 原生组件 -->
|
||||||
|
<a-form
|
||||||
|
ref="formRef"
|
||||||
|
:model="formState"
|
||||||
|
layout="inline"
|
||||||
|
@finish="onFinish"
|
||||||
|
style="margin-bottom: 16px"
|
||||||
|
>
|
||||||
|
<a-form-item label="达标状态" name="sfdb">
|
||||||
|
<a-select
|
||||||
|
v-model:value="formState.sfdb"
|
||||||
|
placeholder="请选择"
|
||||||
|
allow-clear
|
||||||
|
style="width: 160px"
|
||||||
|
>
|
||||||
|
<a-select-option value="">全部</a-select-option>
|
||||||
|
<a-select-option :value="'0'">不达标</a-select-option>
|
||||||
|
<a-select-option :value="'1'">达标</a-select-option>
|
||||||
|
<a-select-option :value="'2'">无目标等级/无数据</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
|
||||||
|
<a-form-item>
|
||||||
|
<a-space>
|
||||||
|
<a-button type="primary" html-type="submit">查询</a-button>
|
||||||
|
<Tooltip title="重置">
|
||||||
|
<a-button @click="handleReset">重置</a-button>
|
||||||
|
</Tooltip>
|
||||||
|
</a-space>
|
||||||
|
</a-form-item>
|
||||||
|
</a-form>
|
||||||
|
|
||||||
|
<!-- 数据表格 -->
|
||||||
|
<BasicTable
|
||||||
|
ref="tableRef"
|
||||||
|
:columns="columns"
|
||||||
|
:list-url="
|
||||||
|
props.calculateFlag?.calculateFlag == 1
|
||||||
|
? dataGetKendoListCust
|
||||||
|
: NodataGetKendoListCust
|
||||||
|
"
|
||||||
|
:scrollY="500"
|
||||||
|
:search-params="{ sort: [{ field: 'tm', dir: 'desc' }] }"
|
||||||
|
:transform-data="customTransform"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, reactive, computed, watch, onMounted, h, nextTick } from 'vue';
|
||||||
|
import { omit } from 'lodash';
|
||||||
|
import { Button, Tooltip, Popover } from 'ant-design-vue';
|
||||||
|
import BasicTable from '@/components/BasicTable/index.vue';
|
||||||
|
import {
|
||||||
|
ruleGetKendoListCust,
|
||||||
|
noAuthGetKendoListCust,
|
||||||
|
stbprpGetStbprpYsByStcd,
|
||||||
|
dataGetKendoListCust,
|
||||||
|
NodataGetKendoListCust
|
||||||
|
} from '@/api/sz';
|
||||||
|
|
||||||
|
// ==================== Props 定义 ====================
|
||||||
|
interface Props {
|
||||||
|
tc: {
|
||||||
|
stcd: string;
|
||||||
|
startTime: string;
|
||||||
|
endTime: string;
|
||||||
|
sfdb: string;
|
||||||
|
};
|
||||||
|
calculateFlag?: {
|
||||||
|
calculateFlag: number;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps<Props>();
|
||||||
|
|
||||||
|
// ==================== 响应式状态 ====================
|
||||||
|
const formRef = ref();
|
||||||
|
const tableRef = ref();
|
||||||
|
const col = ref<any[]>([]);
|
||||||
|
|
||||||
|
// ✅ 表单状态(替代之前的 sfdb ref)
|
||||||
|
const formState = reactive({
|
||||||
|
sfdb: '1'
|
||||||
|
});
|
||||||
|
|
||||||
|
// ==================== 固定列配置 ====================
|
||||||
|
const columnList = [
|
||||||
|
{
|
||||||
|
dataIndexRaw: '',
|
||||||
|
key: 'tm',
|
||||||
|
title: '时间',
|
||||||
|
dataIndex: 'tm',
|
||||||
|
children: [],
|
||||||
|
visible: true,
|
||||||
|
fixed: '',
|
||||||
|
ellipsis: true,
|
||||||
|
wrap: false,
|
||||||
|
dictType: '',
|
||||||
|
dictCode: '',
|
||||||
|
dictDatas: {},
|
||||||
|
fromType: '',
|
||||||
|
dataType: 'date',
|
||||||
|
dataFormat: '',
|
||||||
|
selectMode: '',
|
||||||
|
unit: '',
|
||||||
|
remark: '',
|
||||||
|
rules: '',
|
||||||
|
ruleTips: '',
|
||||||
|
setting: {},
|
||||||
|
chartColor: '',
|
||||||
|
elementCode: '',
|
||||||
|
disabledDate: true,
|
||||||
|
sorter: true,
|
||||||
|
multiLink: false,
|
||||||
|
width: 160
|
||||||
|
},
|
||||||
|
{
|
||||||
|
dataIndexRaw: '',
|
||||||
|
key: 'wwqtg',
|
||||||
|
title: '水质要求',
|
||||||
|
dataIndex: 'wwqtgName',
|
||||||
|
children: [],
|
||||||
|
visible: true,
|
||||||
|
fixed: '',
|
||||||
|
ellipsis: true,
|
||||||
|
wrap: false,
|
||||||
|
dictType: '',
|
||||||
|
dictCode: '',
|
||||||
|
dictDatas: {},
|
||||||
|
fromType: '',
|
||||||
|
dataFormat: '',
|
||||||
|
selectMode: '',
|
||||||
|
unit: '',
|
||||||
|
remark: '',
|
||||||
|
rules: '',
|
||||||
|
ruleTips: '',
|
||||||
|
setting: {},
|
||||||
|
chartColor: '',
|
||||||
|
elementCode: '',
|
||||||
|
sorter: true,
|
||||||
|
disabledDate: true,
|
||||||
|
multiLink: false,
|
||||||
|
width: 100
|
||||||
|
},
|
||||||
|
{
|
||||||
|
dataIndexRaw: '',
|
||||||
|
key: 'wqGrd',
|
||||||
|
title: '水质等级',
|
||||||
|
dataIndex: 'wqgrdName',
|
||||||
|
children: [],
|
||||||
|
visible: true,
|
||||||
|
fixed: '',
|
||||||
|
ellipsis: true,
|
||||||
|
wrap: false,
|
||||||
|
dictType: '',
|
||||||
|
dictCode: '',
|
||||||
|
dictDatas: {},
|
||||||
|
fromType: '',
|
||||||
|
dataFormat: '',
|
||||||
|
selectMode: '',
|
||||||
|
unit: '',
|
||||||
|
remark: '',
|
||||||
|
rules: '',
|
||||||
|
ruleTips: '',
|
||||||
|
setting: {},
|
||||||
|
chartColor: '',
|
||||||
|
elementCode: '',
|
||||||
|
sorter: true,
|
||||||
|
disabledDate: true,
|
||||||
|
multiLink: false,
|
||||||
|
width: 100
|
||||||
|
},
|
||||||
|
{
|
||||||
|
dataIndexRaw: 'sfdb',
|
||||||
|
key: 'sfdb',
|
||||||
|
title: '是否达标',
|
||||||
|
dataIndex: 'sfdbName',
|
||||||
|
children: [],
|
||||||
|
visible: true,
|
||||||
|
fixed: '',
|
||||||
|
ellipsis: true,
|
||||||
|
wrap: false,
|
||||||
|
// TODO: 【您需要实现】字典转换逻辑 - BasicTable 不支持 dictType/dictCode
|
||||||
|
// 需要在 customRender 中手动处理
|
||||||
|
dictType: 'dataDict',
|
||||||
|
dictCode: 'sd_wq_r.sfdb',
|
||||||
|
dictDatas: {},
|
||||||
|
fromType: 'select',
|
||||||
|
dataFormat: '',
|
||||||
|
selectMode: '',
|
||||||
|
unit: '',
|
||||||
|
remark: '',
|
||||||
|
rules: '',
|
||||||
|
ruleTips: '',
|
||||||
|
setting: {},
|
||||||
|
chartColor: '',
|
||||||
|
elementCode: '',
|
||||||
|
sorter: true,
|
||||||
|
disabledDate: true,
|
||||||
|
multiLink: false,
|
||||||
|
width: 100,
|
||||||
|
// ✅ Vue3 版本需要添加 customRender
|
||||||
|
customRender: ({ record }: any) => {
|
||||||
|
// TODO: 【您需要实现】字典转换逻辑
|
||||||
|
// 示例:return getDictLabel('sd_wq_r.sfdb', record.sfdbName)
|
||||||
|
return record.sfdbName; // 暂时直接显示原始值
|
||||||
|
}
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
// ==================== 动态列计算 ====================
|
||||||
|
const columns = computed(() => {
|
||||||
|
if (!col.value.length) return columnList;
|
||||||
|
return [...columnList, ...col.value];
|
||||||
|
});
|
||||||
|
|
||||||
|
// ==================== 筛选条件计算 ====================
|
||||||
|
const tableFilter = computed(() => {
|
||||||
|
let res: any = {
|
||||||
|
stcd: props.tc.stcd,
|
||||||
|
sfdb: formState.sfdb === '2' ? '' : formState.sfdb,
|
||||||
|
startTime: props.tc.startTime,
|
||||||
|
endTime: props.tc.endTime
|
||||||
|
};
|
||||||
|
if (res.sfdb === '') res = omit(res, 'sfdb');
|
||||||
|
return res;
|
||||||
|
});
|
||||||
|
|
||||||
|
// ==================== 方法定义 ====================
|
||||||
|
|
||||||
|
// 搜索表单提交
|
||||||
|
const onFinish = () => {
|
||||||
|
const filters: any[] = [
|
||||||
|
{
|
||||||
|
field: 'stcd',
|
||||||
|
operator: 'eq',
|
||||||
|
dataType: 'string',
|
||||||
|
value: tableFilter.value.stcd
|
||||||
|
},
|
||||||
|
formState.sfdb
|
||||||
|
? {
|
||||||
|
field: 'sfdb',
|
||||||
|
operator: 'eq',
|
||||||
|
dataType: 'string',
|
||||||
|
value: formState.sfdb
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
{
|
||||||
|
field: 'tm',
|
||||||
|
operator: 'gte',
|
||||||
|
dataType: 'date',
|
||||||
|
value: tableFilter.value.startTime
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'tm',
|
||||||
|
operator: 'lte',
|
||||||
|
dataType: 'date',
|
||||||
|
value: tableFilter.value.endTime
|
||||||
|
}
|
||||||
|
].filter(Boolean);
|
||||||
|
const filter = {
|
||||||
|
logic: 'and',
|
||||||
|
filters
|
||||||
|
};
|
||||||
|
// 触发表格刷新
|
||||||
|
tableRef.value?.getList(filter);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 重置表单
|
||||||
|
const handleReset = () => {
|
||||||
|
formState.sfdb = '1';
|
||||||
|
onFinish();
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO: 【预留】获取动态列配置 - 您需要实现的API调用逻辑
|
||||||
|
const getCol = async (params: any) => {
|
||||||
|
try {
|
||||||
|
const lo = await columYaoshu(params);
|
||||||
|
col.value = lo || [];
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取列配置失败:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO: 【预留】处理列配置的核心逻辑 - 您需要实现
|
||||||
|
const columYaoshu = async (params: any) => {
|
||||||
|
// API 1: 获取预警规则限值
|
||||||
|
let params3 = {
|
||||||
|
filter: {
|
||||||
|
logic: 'and',
|
||||||
|
filters: [
|
||||||
|
{
|
||||||
|
field: 'stcd',
|
||||||
|
operator: 'eq',
|
||||||
|
dataType: 'string',
|
||||||
|
value: params?.stcd
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const MaxData = await ruleGetKendoListCust(params3);
|
||||||
|
|
||||||
|
// API 2: 获取数据范围
|
||||||
|
let params2 = {
|
||||||
|
filter: {
|
||||||
|
logic: 'and',
|
||||||
|
filters: [
|
||||||
|
{
|
||||||
|
field: 'stcd',
|
||||||
|
operator: 'eq',
|
||||||
|
dataType: 'string',
|
||||||
|
value: params?.stcd
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'tm',
|
||||||
|
operator: 'lte',
|
||||||
|
dataType: 'date',
|
||||||
|
value: params?.endTime
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'tm',
|
||||||
|
operator: 'gte',
|
||||||
|
dataType: 'date',
|
||||||
|
value: params?.startTime
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const noAuthRes = await noAuthGetKendoListCust(params2);
|
||||||
|
const noAuthResData = noAuthRes?.data?.data || [];
|
||||||
|
|
||||||
|
// API 3: 获取要素配置
|
||||||
|
let params1 = {
|
||||||
|
stcd: params?.stcd,
|
||||||
|
tbCode: 'WQ_R',
|
||||||
|
endTime: params.endTime,
|
||||||
|
startTime: params.startTime
|
||||||
|
};
|
||||||
|
const res = await stbprpGetStbprpYsByStcd(params1);
|
||||||
|
if (res?.data?.data) {
|
||||||
|
const processedColumns = res.data.data
|
||||||
|
.filter((item: any) => item.enable === '1')
|
||||||
|
.map((item: any) => {
|
||||||
|
const _t = item.ysShowName;
|
||||||
|
const reg = /[((][^(())]+[))]/;
|
||||||
|
|
||||||
|
// TODO: 【您需要实现】单位转换工具函数
|
||||||
|
const _u = getUnitConfigByCode('WQ_R', item.ys)?.unit;
|
||||||
|
if (_u) {
|
||||||
|
item.ysShowName = reg.test(_t)
|
||||||
|
? _t.replace(reg, `(${_u})`)
|
||||||
|
: `${_t}(${_u})`;
|
||||||
|
}
|
||||||
|
|
||||||
|
let showValue = '';
|
||||||
|
if (noAuthResData.length) {
|
||||||
|
const { max, min } = noAuthResData[0];
|
||||||
|
const ys = item.ys;
|
||||||
|
const maxValue = max.filter((e: any) => e[ys]);
|
||||||
|
const minValue = min.filter((e: any) => e[ys]);
|
||||||
|
|
||||||
|
if (maxValue.length && minValue.length) {
|
||||||
|
showValue = `(${minValue[0]?.[ys]}-${maxValue[0]?.[ys]})`;
|
||||||
|
} else {
|
||||||
|
if (minValue.length) {
|
||||||
|
showValue = `(>${minValue[0]?.[ys]})`;
|
||||||
|
}
|
||||||
|
if (maxValue.length) {
|
||||||
|
showValue = `(<${maxValue[0]?.[ys]})`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
dataIndexRaw: '',
|
||||||
|
key: item.ys.toLowerCase(),
|
||||||
|
// ✅ Vue3 版本:title 支持函数返回 VNode
|
||||||
|
title: () =>
|
||||||
|
h('div', [
|
||||||
|
h(
|
||||||
|
'p',
|
||||||
|
{
|
||||||
|
style: {
|
||||||
|
overflow: 'hidden',
|
||||||
|
textOverflow: 'ellipsis',
|
||||||
|
wordBreak: 'keep-all'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
item.ysShowName
|
||||||
|
),
|
||||||
|
h('p', showValue)
|
||||||
|
]),
|
||||||
|
dataIndex: item.ys.toLowerCase(),
|
||||||
|
children: [],
|
||||||
|
visible: true,
|
||||||
|
fixed: '',
|
||||||
|
sorter: true,
|
||||||
|
ellipsis: true,
|
||||||
|
wrap: false,
|
||||||
|
dictType: '',
|
||||||
|
dictCode: '',
|
||||||
|
dictDatas: {},
|
||||||
|
// ✅ Vue3 版本:使用 customRender 替代 render
|
||||||
|
customRender: ({ record }: any) => {
|
||||||
|
// TODO: 【您需要传入】MaxData 数据
|
||||||
|
const datalist = MaxData?.data?.data;
|
||||||
|
const dynamicDataIndex = item.ys;
|
||||||
|
const dynamicDataName = item.ysShowName;
|
||||||
|
const matchedItem = datalist.find(
|
||||||
|
(m: any) => m.ys === dynamicDataIndex
|
||||||
|
);
|
||||||
|
|
||||||
|
// 判断是否超标
|
||||||
|
if (
|
||||||
|
matchedItem &&
|
||||||
|
(matchedItem.isAsc == 1
|
||||||
|
? matchedItem?.maxVal
|
||||||
|
: matchedItem.minVal) &&
|
||||||
|
record[dynamicDataIndex?.toLowerCase()] &&
|
||||||
|
((matchedItem.isAsc == 1 &&
|
||||||
|
record[dynamicDataIndex?.toLowerCase()] >
|
||||||
|
matchedItem?.maxVal) ||
|
||||||
|
(matchedItem.isAsc != 1 &&
|
||||||
|
record[dynamicDataIndex?.toLowerCase()] < matchedItem.minVal))
|
||||||
|
) {
|
||||||
|
// ✅ Vue3 版本:使用 h() 函数创建 VNode
|
||||||
|
return h(
|
||||||
|
Popover,
|
||||||
|
{
|
||||||
|
content: h(
|
||||||
|
'div',
|
||||||
|
`预警标准: ${
|
||||||
|
matchedItem.isAsc === 1
|
||||||
|
? `>${matchedItem.maxVal}`
|
||||||
|
: `<${matchedItem.minVal}`
|
||||||
|
}`
|
||||||
|
),
|
||||||
|
trigger: 'hover'
|
||||||
|
},
|
||||||
|
() =>
|
||||||
|
h(
|
||||||
|
'span',
|
||||||
|
{
|
||||||
|
style: { color: 'red', cursor: 'pointer' }
|
||||||
|
// TODO: 【您需要实现】单位转换渲染
|
||||||
|
},
|
||||||
|
transUnitRender(
|
||||||
|
record[dynamicDataIndex?.toLowerCase()],
|
||||||
|
'WQ_R',
|
||||||
|
dynamicDataIndex
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 正常显示
|
||||||
|
// TODO: 【您需要实现】单位转换渲染
|
||||||
|
return transUnitRender(
|
||||||
|
record[dynamicDataIndex?.toLowerCase()],
|
||||||
|
'WQ_R',
|
||||||
|
dynamicDataIndex
|
||||||
|
);
|
||||||
|
},
|
||||||
|
fromType: '',
|
||||||
|
dataType: '',
|
||||||
|
dataFormat: '',
|
||||||
|
selectMode: '',
|
||||||
|
unit: '',
|
||||||
|
remark: '',
|
||||||
|
rules: '',
|
||||||
|
ruleTips: '',
|
||||||
|
setting: {},
|
||||||
|
chartColor: '',
|
||||||
|
elementCode: '',
|
||||||
|
disabledDate: true,
|
||||||
|
multiLink: false
|
||||||
|
};
|
||||||
|
});
|
||||||
|
console.log(processedColumns);
|
||||||
|
onFinish();
|
||||||
|
return processedColumns || [];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO: 【预留】单位转换工具函数 - 您需要实现
|
||||||
|
// ==================== 单位转换工具函数区域 ====================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据表代码和要素代码获取单位配置
|
||||||
|
* @param tbCode 表代码 (如 'WQ_R')
|
||||||
|
* @param ys 要素代码 (如 'DOX', 'CODCR')
|
||||||
|
* @returns 单位配置对象 { unit: string }
|
||||||
|
*/
|
||||||
|
const getUnitConfigByCode = (tbCode: string, ys: string) => {
|
||||||
|
// TODO: 【您需要实现】单位配置映射
|
||||||
|
const unitConfig: Record<string, Record<string, { unit: string }>> = {
|
||||||
|
WQ_R: {
|
||||||
|
PH: { unit: '' },
|
||||||
|
DOX: { unit: 'mg/L' },
|
||||||
|
CODMN: { unit: 'mg/L' },
|
||||||
|
CODCR: { unit: 'mg/L' },
|
||||||
|
NH3N: { unit: 'mg/L' },
|
||||||
|
TP: { unit: 'mg/L' },
|
||||||
|
TN: { unit: 'mg/L' },
|
||||||
|
WTMP: { unit: '℃' },
|
||||||
|
COND: { unit: 'μS/cm' },
|
||||||
|
CLARITY: { unit: 'm' }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return unitConfig[tbCode]?.[ys] || { unit: '' };
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 带单位的值渲染(根据要素类型设置不同小数位数,不带单位)
|
||||||
|
* @param value 数值
|
||||||
|
* @param tbCode 表代码
|
||||||
|
* @param ys 要素代码
|
||||||
|
* @returns 格式化后的字符串(不带单位)
|
||||||
|
*/
|
||||||
|
const transUnitRender = (value: any, tbCode: string, ys: string) => {
|
||||||
|
if (value === null || value === undefined || value === '') return '-';
|
||||||
|
|
||||||
|
const numValue = Number(value);
|
||||||
|
if (isNaN(numValue)) return String(value);
|
||||||
|
|
||||||
|
// 根据不同要素类型设置小数位数
|
||||||
|
let formattedValue: string;
|
||||||
|
|
||||||
|
if (ys === 'WTMP' || ys === 'PH') {
|
||||||
|
// 水温和PH保留一位小数
|
||||||
|
formattedValue = numValue.toFixed(1);
|
||||||
|
} else if (ys === 'COND') {
|
||||||
|
// 电导率不要小数
|
||||||
|
formattedValue = Math.round(numValue).toString();
|
||||||
|
} else {
|
||||||
|
// 其他要素保留两位小数
|
||||||
|
formattedValue = numValue.toFixed(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
return formattedValue;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 纯单位转换(不带渲染)
|
||||||
|
* @param value 数值
|
||||||
|
* @param tbCode 表代码
|
||||||
|
* @param ys 要素代码
|
||||||
|
* @returns 转换后的数值
|
||||||
|
*/
|
||||||
|
const transUnit = (value: any, tbCode: string, ys: string) => {
|
||||||
|
if (value === null || value === undefined) return null;
|
||||||
|
return Number(value);
|
||||||
|
};
|
||||||
|
|
||||||
|
// ==================== 监听器 ====================
|
||||||
|
|
||||||
|
// 监听 tc 和 formState.sfdb 变化,重新获取列配置
|
||||||
|
// watch(
|
||||||
|
// () => [props.tc.stcd, props.tc.startTime, props.tc.endTime, formState.sfdb],
|
||||||
|
// ([stcd, startTime, endTime]) => {
|
||||||
|
// if (!stcd) {
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
// //
|
||||||
|
// tableRef.value.tableData = [];
|
||||||
|
// getCol({ stcd, startTime, endTime });
|
||||||
|
// },
|
||||||
|
// { immediate: false } // 不在初始化时立即执行,由 onMounted 处理
|
||||||
|
// );
|
||||||
|
|
||||||
|
// 初始化
|
||||||
|
onMounted(() => {
|
||||||
|
nextTick(() => {});
|
||||||
|
getCol({
|
||||||
|
stcd: props.tc.stcd,
|
||||||
|
startTime: props.tc.startTime,
|
||||||
|
endTime: props.tc.endTime
|
||||||
|
});
|
||||||
|
});
|
||||||
|
// 自定义数据转换
|
||||||
|
const customTransform = (res: any) => {
|
||||||
|
return {
|
||||||
|
records: res?.data?.data || [],
|
||||||
|
total: res?.data?.total || 0
|
||||||
|
};
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
/* 如需添加样式,请在此处编写 */
|
||||||
|
</style>
|
||||||
1049
frontend/src/modules/ShuiZhiDaBiaoQingKuang/index.vue
Normal file
1049
frontend/src/modules/ShuiZhiDaBiaoQingKuang/index.vue
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,6 +1,6 @@
|
|||||||
<!-- SidePanelItem.vue -->
|
<!-- SidePanelItem.vue -->
|
||||||
<template>
|
<template>
|
||||||
<SidePanelItem title="动物救助情况">
|
<SidePanelItem title="动物救助站情况">
|
||||||
<!-- Loading 状态 -->
|
<!-- Loading 状态 -->
|
||||||
<div v-if="loading" class="loading-container">
|
<div v-if="loading" class="loading-container">
|
||||||
<a-spin />
|
<a-spin />
|
||||||
@ -164,7 +164,7 @@ const getData = async () => {
|
|||||||
const res = await wvaGetKendoListCust(params);
|
const res = await wvaGetKendoListCust(params);
|
||||||
if (res?.data?.data?.length > 0) {
|
if (res?.data?.data?.length > 0) {
|
||||||
// 过滤掉 imgPath 为 null 的对象
|
// 过滤掉 imgPath 为 null 的对象
|
||||||
const filteredData = res.data.data
|
const filteredData = res.data.data;
|
||||||
originalMediaData.value = filteredData.map((item: any) => {
|
originalMediaData.value = filteredData.map((item: any) => {
|
||||||
return {
|
return {
|
||||||
url: `${baseUrl}?${item.fid}&view=jpg`,
|
url: `${baseUrl}?${item.fid}&view=jpg`,
|
||||||
@ -205,23 +205,23 @@ onUnmounted(() => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
// Loading 容器
|
// Loading 容器
|
||||||
.loading-container {
|
.loading-container {
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
width: 100%;
|
|
||||||
height:290px;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 暂无数据容器
|
|
||||||
.empty-container {
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 290px;
|
height: 290px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 暂无数据容器
|
||||||
|
.empty-container {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 100%;
|
||||||
|
height: 290px;
|
||||||
|
}
|
||||||
.container {
|
.container {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 290px;
|
height: 290px;
|
||||||
@ -230,8 +230,6 @@ onUnmounted(() => {
|
|||||||
position: relative;
|
position: relative;
|
||||||
overflow: hidden; // 隐藏超出容器的内容
|
overflow: hidden; // 隐藏超出容器的内容
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// 跑马灯轨道
|
// 跑马灯轨道
|
||||||
.carousel-track {
|
.carousel-track {
|
||||||
display: flex; // 横向排列所有媒体项
|
display: flex; // 横向排列所有媒体项
|
||||||
@ -285,7 +283,7 @@ onUnmounted(() => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
:deep(.ant-empty-description){
|
:deep(.ant-empty-description) {
|
||||||
text-indent:0em !important;
|
text-indent: 0em !important;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@ -8,7 +8,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<SidePanelItem
|
<SidePanelItem
|
||||||
title="出入库水温"
|
title="出入库水温"
|
||||||
:moreSelect="select"
|
|
||||||
:scopeDate="scopeDate"
|
:scopeDate="scopeDate"
|
||||||
@update-values="handlePanelChange1"
|
@update-values="handlePanelChange1"
|
||||||
>
|
>
|
||||||
@ -573,7 +572,8 @@ const handleChartClick = (params: any) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// 获取站点名称
|
// 获取站点名称
|
||||||
stationName.value = select.value.options?.find(
|
stationName.value =
|
||||||
|
select.value.options?.find(
|
||||||
(item: any) => item.value === select.value.value
|
(item: any) => item.value === select.value.value
|
||||||
)?.title || '未知站点';
|
)?.title || '未知站点';
|
||||||
|
|
||||||
@ -597,8 +597,7 @@ const handleModalClose = () => {
|
|||||||
*/
|
*/
|
||||||
const handlePanelChange1 = (data: any, type: string) => {
|
const handlePanelChange1 = (data: any, type: string) => {
|
||||||
// TODO: 后续可扩展联动逻辑
|
// TODO: 后续可扩展联动逻辑
|
||||||
if (data.moreSelect && data.scopeDate) {
|
if (data.scopeDate) {
|
||||||
select.value.value = data.moreSelect;
|
|
||||||
paramsOne.value.tm = data.scopeDate;
|
paramsOne.value.tm = data.scopeDate;
|
||||||
fetchChartData();
|
fetchChartData();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -52,18 +52,18 @@
|
|||||||
:search-params="searchParams"
|
:search-params="searchParams"
|
||||||
:transform-data="customTransform"
|
:transform-data="customTransform"
|
||||||
>
|
>
|
||||||
<template #coenvwState="{ column, record }">
|
<template #coenvwState="{ record }">
|
||||||
<a-tag :color="record.coenvwState > 0 ? 'green' : '#999'">
|
<a-tag :color="record.coenvwState > 0 ? 'green' : '#999'">
|
||||||
{{ record.coenvwState > 0 ? '在线' : '离线' }}
|
{{ record.coenvwState > 0 ? '在线' : '离线' }}
|
||||||
</a-tag>
|
</a-tag>
|
||||||
</template>
|
</template>
|
||||||
<template #dvtp="{ column, record }">
|
<template #dvtp="{ record }">
|
||||||
{{ dvtpText(record.dvtp) }}
|
{{ dvtpText(record.dvtp) }}
|
||||||
</template>
|
</template>
|
||||||
<template #stindxForEng="{ column, record }">
|
<template #stindxForEng="{ record }">
|
||||||
{{ record.dvtp == 2 ? '生态流量' : '出库流量' }}
|
{{ record.dvtp == 2 ? '生态流量' : '出库流量' }}
|
||||||
</template>
|
</template>
|
||||||
<template #action="{ column, record }">
|
<template #action="{ record }">
|
||||||
<a @click="handleViewDetail(record, 'detail')" class="text-link">
|
<a @click="handleViewDetail(record, 'detail')" class="text-link">
|
||||||
查看详情
|
查看详情
|
||||||
</a>
|
</a>
|
||||||
@ -73,10 +73,14 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import dayjs from 'dayjs';
|
||||||
import { ref, computed, onMounted, h } from 'vue';
|
import { ref, computed, onMounted, h } from 'vue';
|
||||||
import { Tooltip } from 'ant-design-vue';
|
import { Tooltip } from 'ant-design-vue';
|
||||||
import { QuestionCircleOutlined } from '@ant-design/icons-vue';
|
import { QuestionCircleOutlined } from '@ant-design/icons-vue';
|
||||||
import { vmsstbprptGetKendoList,overviewvmsstbprptGetKendoList } from '@/api/home';
|
import {
|
||||||
|
vmsstbprptGetKendoList,
|
||||||
|
overviewvmsstbprptGetKendoList
|
||||||
|
} from '@/api/home';
|
||||||
import BasicTable from '@/components/BasicTable/index.vue';
|
import BasicTable from '@/components/BasicTable/index.vue';
|
||||||
import { useModelStore } from '@/store/modules/model';
|
import { useModelStore } from '@/store/modules/model';
|
||||||
import { useJidiSelectEventStore } from '@/store/modules/jidiSelectEvent';
|
import { useJidiSelectEventStore } from '@/store/modules/jidiSelectEvent';
|
||||||
@ -142,11 +146,25 @@ const columns = computed(() => {
|
|||||||
title: '建成时间',
|
title: '建成时间',
|
||||||
dataIndex: 'jcdt',
|
dataIndex: 'jcdt',
|
||||||
width: '150px',
|
width: '150px',
|
||||||
sort: true
|
sort: true,
|
||||||
|
customRender: ({ text }) =>
|
||||||
|
text ? dayjs(text).format('YYYY-MM-DD') : '-'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'coenvwState',
|
key: 'coenvwState',
|
||||||
title: h(Tooltip, { title: '最近1天有监测数据为在线,否则为离线' }, { default: () => h('span', null, ['监测状态', h(QuestionCircleOutlined, { style: { marginLeft: '4px', color: '#000' } })]) }),
|
title: h(
|
||||||
|
Tooltip,
|
||||||
|
{ title: '最近1天有监测数据为在线,否则为离线' },
|
||||||
|
{
|
||||||
|
default: () =>
|
||||||
|
h('span', null, [
|
||||||
|
'监测状态',
|
||||||
|
h(QuestionCircleOutlined, {
|
||||||
|
style: { marginLeft: '4px', color: '#000' }
|
||||||
|
})
|
||||||
|
])
|
||||||
|
}
|
||||||
|
),
|
||||||
dataIndex: 'coenvwState',
|
dataIndex: 'coenvwState',
|
||||||
width: '120px',
|
width: '120px',
|
||||||
slots: { customRender: 'coenvwState' }
|
slots: { customRender: 'coenvwState' }
|
||||||
@ -190,7 +208,9 @@ const columns = computed(() => {
|
|||||||
title: '建成时间',
|
title: '建成时间',
|
||||||
dataIndex: 'jcdt',
|
dataIndex: 'jcdt',
|
||||||
width: '150px',
|
width: '150px',
|
||||||
sort: true
|
sort: true,
|
||||||
|
customRender: ({ text }) =>
|
||||||
|
text ? dayjs(text).format('YYYY-MM-DD') : '-'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'stindx',
|
key: 'stindx',
|
||||||
@ -204,7 +224,19 @@ const columns = computed(() => {
|
|||||||
if (tabIndex.value !== '4') {
|
if (tabIndex.value !== '4') {
|
||||||
baseColumns.push({
|
baseColumns.push({
|
||||||
key: 'coenvwState',
|
key: 'coenvwState',
|
||||||
title: h(Tooltip, { title: '最近1天有监测数据为在线,否则为离线' }, { default: () => h('span', null, ['监测状态', h(QuestionCircleOutlined, { style: { marginLeft: '4px', color: '#999' } })]) }),
|
title: h(
|
||||||
|
Tooltip,
|
||||||
|
{ title: '最近1天有监测数据为在线,否则为离线' },
|
||||||
|
{
|
||||||
|
default: () =>
|
||||||
|
h('span', null, [
|
||||||
|
'监测状态',
|
||||||
|
h(QuestionCircleOutlined, {
|
||||||
|
style: { marginLeft: '4px', color: '#999' }
|
||||||
|
})
|
||||||
|
])
|
||||||
|
}
|
||||||
|
),
|
||||||
dataIndex: 'coenvwState',
|
dataIndex: 'coenvwState',
|
||||||
width: '120px',
|
width: '120px',
|
||||||
slots: { customRender: 'coenvwState' }
|
slots: { customRender: 'coenvwState' }
|
||||||
@ -315,7 +347,7 @@ const getRstcdData = async () => {
|
|||||||
select: ['stcd', 'stnm']
|
select: ['stcd', 'stnm']
|
||||||
};
|
};
|
||||||
const res = await overviewvmsstbprptGetKendoList(parmas);
|
const res = await overviewvmsstbprptGetKendoList(parmas);
|
||||||
rstcdOptions.value = res.data.data.map((item) => {
|
rstcdOptions.value = res.data.data.map(item => {
|
||||||
return {
|
return {
|
||||||
value: item.stcd,
|
value: item.stcd,
|
||||||
label: item.stnm
|
label: item.stnm
|
||||||
@ -323,7 +355,7 @@ const getRstcdData = async () => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
const handleBaseChange = () => {
|
const handleBaseChange = () => {
|
||||||
searchData.value.stcd = null
|
searchData.value.stcd = null;
|
||||||
getRstcdData();
|
getRstcdData();
|
||||||
// handleSearch();
|
// handleSearch();
|
||||||
};
|
};
|
||||||
@ -344,7 +376,7 @@ const handleSearch = () => {
|
|||||||
value: searchData.value.baseId
|
value: searchData.value.baseId
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if(searchData.value.stcd){
|
if (searchData.value.stcd) {
|
||||||
filters.push({
|
filters.push({
|
||||||
field: 'stcd',
|
field: 'stcd',
|
||||||
operator: 'eq',
|
operator: 'eq',
|
||||||
@ -507,7 +539,7 @@ const handleReset = () => {
|
|||||||
stnm: null,
|
stnm: null,
|
||||||
stcd: null
|
stcd: null
|
||||||
};
|
};
|
||||||
getRstcdData()
|
getRstcdData();
|
||||||
handleSearch();
|
handleSearch();
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -622,7 +654,7 @@ onMounted(() => {
|
|||||||
value: element.wbsCode
|
value: element.wbsCode
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
getRstcdData()
|
getRstcdData();
|
||||||
handleSearch();
|
handleSearch();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
234
frontend/src/modules/lushengshebeitongji/LsstjkTk.vue
Normal file
234
frontend/src/modules/lushengshebeitongji/LsstjkTk.vue
Normal file
@ -0,0 +1,234 @@
|
|||||||
|
<template>
|
||||||
|
<div class="lsstjk-table">
|
||||||
|
<!-- 表格 -->
|
||||||
|
<div class="table-wrapper">
|
||||||
|
<BasicTable
|
||||||
|
ref="tableRef"
|
||||||
|
:columns="columns"
|
||||||
|
:scroll-y="500"
|
||||||
|
:scroll-x="800"
|
||||||
|
:list-url="listUrl"
|
||||||
|
:search-params="searchParams"
|
||||||
|
:transform-data="customTransform"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { ref, computed, watch, onMounted } from 'vue';
|
||||||
|
import BasicTable from '@/components/BasicTable/index.vue';
|
||||||
|
import { getVmsstbprptList, getTetSpecList } from '@/api/stdc';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
|
||||||
|
// ==================== Props ====================
|
||||||
|
const props = defineProps<{
|
||||||
|
selectDateTime?: any;
|
||||||
|
orderIndex?: string;
|
||||||
|
hbrvcd?: string;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
// ==================== 搜索状态 ====================
|
||||||
|
const tableRef = ref();
|
||||||
|
const yearValue = ref(
|
||||||
|
props.selectDateTime ? dayjs(props.selectDateTime) : dayjs()
|
||||||
|
);
|
||||||
|
|
||||||
|
// ==================== 接口URL ====================
|
||||||
|
const listUrl = computed(() => {
|
||||||
|
return props.orderIndex === '20' ? getTetSpecList : getVmsstbprptList;
|
||||||
|
});
|
||||||
|
|
||||||
|
// ==================== 表格列配置 ====================
|
||||||
|
const columns = computed(() => {
|
||||||
|
if (props.orderIndex === '20') {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
key: 'name',
|
||||||
|
title: '名称',
|
||||||
|
dataIndex: 'name',
|
||||||
|
fixed: 'left' as const,
|
||||||
|
width: 200,
|
||||||
|
ellipsis: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'nameEn',
|
||||||
|
title: '英文名称',
|
||||||
|
dataIndex: 'nameEn',
|
||||||
|
width: 200,
|
||||||
|
ellipsis: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'protectlvlName',
|
||||||
|
title: '保护级别',
|
||||||
|
dataIndex: 'protectlvlName',
|
||||||
|
width: 200,
|
||||||
|
ellipsis: true
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}else{
|
||||||
|
// orderIndex === '16' 红外相机
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
key: 'box',
|
||||||
|
title: '监测区域',
|
||||||
|
dataIndex: 'box',
|
||||||
|
fixed: 'left' as const,
|
||||||
|
width: 200,
|
||||||
|
ellipsis: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'stnm',
|
||||||
|
title: '站点名称',
|
||||||
|
dataIndex: 'stnm',
|
||||||
|
width: 200,
|
||||||
|
ellipsis: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'baseName',
|
||||||
|
title: '所属基地',
|
||||||
|
dataIndex: 'baseName',
|
||||||
|
width: 200,
|
||||||
|
ellipsis: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'stlc',
|
||||||
|
title: '所在地址',
|
||||||
|
dataIndex: 'stlc',
|
||||||
|
width: 200,
|
||||||
|
ellipsis: true
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
// ==================== 搜索参数 ====================
|
||||||
|
const searchParams = computed(() => {
|
||||||
|
const filters: any[] = [];
|
||||||
|
|
||||||
|
if (props.hbrvcd) {
|
||||||
|
filters.push({
|
||||||
|
field: 'hbrvcd',
|
||||||
|
operator: 'in',
|
||||||
|
dataType: 'string',
|
||||||
|
value: props.hbrvcd
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (props.orderIndex === '20') {
|
||||||
|
filters.push({
|
||||||
|
field: 'sttpCode',
|
||||||
|
operator: 'eq',
|
||||||
|
dataType: 'string',
|
||||||
|
value: 'TE_ANIMALS'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const startTime = `${dayjs(yearValue.value).format('YYYY')}-01-01 00:00:00`;
|
||||||
|
const endTime = `${dayjs(yearValue.value).format('YYYY')}-12-31 23:59:59`;
|
||||||
|
filters.push({
|
||||||
|
field: 'startTime',
|
||||||
|
operator: 'gte',
|
||||||
|
dataType: 'date',
|
||||||
|
value: startTime
|
||||||
|
});
|
||||||
|
filters.push({
|
||||||
|
field: 'endTime',
|
||||||
|
operator: 'lte',
|
||||||
|
dataType: 'date',
|
||||||
|
value: endTime
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
filter: {
|
||||||
|
logic: 'and',
|
||||||
|
filters
|
||||||
|
},
|
||||||
|
sort: [],
|
||||||
|
group: [],
|
||||||
|
select: [],
|
||||||
|
groupResultFlat: false
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
// ==================== 数据转换 ====================
|
||||||
|
const customTransform = (res: any) => {
|
||||||
|
return {
|
||||||
|
records: res?.data?.data || [],
|
||||||
|
total: res?.data?.total || 0
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// ==================== 搜索与重置 ====================
|
||||||
|
const handleSearch = () => {
|
||||||
|
const filter = getFilter();
|
||||||
|
tableRef.value?.getList(filter);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getFilter = () => {
|
||||||
|
const filters: any[] = [
|
||||||
|
{
|
||||||
|
field: 'sttpCode',
|
||||||
|
operator: 'eq',
|
||||||
|
dataType: 'string',
|
||||||
|
value: 'WVA'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
if (props.hbrvcd) {
|
||||||
|
filters.push({
|
||||||
|
field: 'hbrvcd',
|
||||||
|
operator: 'in',
|
||||||
|
dataType: 'string',
|
||||||
|
value: props.hbrvcd
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
logic: 'and',
|
||||||
|
filters
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// ==================== 监听 selectDateTime 变化 ====================
|
||||||
|
watch(
|
||||||
|
() => props.selectDateTime,
|
||||||
|
newVal => {
|
||||||
|
if (newVal) {
|
||||||
|
yearValue.value = dayjs(newVal);
|
||||||
|
handleSearch();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ immediate: false }
|
||||||
|
);
|
||||||
|
|
||||||
|
// ==================== 监听 orderIndex 变化 ====================
|
||||||
|
watch(
|
||||||
|
() => props.orderIndex,
|
||||||
|
() => {
|
||||||
|
handleSearch();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// ==================== 挂载时执行 ====================
|
||||||
|
onMounted(() => {
|
||||||
|
handleSearch();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.lsstjk-table {
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
.search-form {
|
||||||
|
padding: 16px 0;
|
||||||
|
border-bottom: 1px solid #f0f0f0;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-wrapper {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
378
frontend/src/modules/lushengshebeitongji/index.vue
Normal file
378
frontend/src/modules/lushengshebeitongji/index.vue
Normal file
@ -0,0 +1,378 @@
|
|||||||
|
<!-- SidePanelItem.vue -->
|
||||||
|
<template>
|
||||||
|
<SidePanelItem title="监测设备统计">
|
||||||
|
<a-spin :spinning="dataLoading" tip="加载中...">
|
||||||
|
<div class="facility-grid">
|
||||||
|
<div
|
||||||
|
v-for="facility in facilities"
|
||||||
|
:key="facility.name"
|
||||||
|
class="facility-card"
|
||||||
|
@click="handleCardClick(facility)"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style="
|
||||||
|
width: 60px;
|
||||||
|
height: 62px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<div class="facility-icon">
|
||||||
|
<i
|
||||||
|
style="color: #fff"
|
||||||
|
:class="facility.icon"
|
||||||
|
type="icon-shengtailiuliang2"
|
||||||
|
></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="facility-info">
|
||||||
|
<div class="facility-name">{{ facility.name }}</div>
|
||||||
|
<div style="font-size: 16px">
|
||||||
|
<span class="facility-count">{{ facility.count }}</span>
|
||||||
|
<span>{{ facility.unit }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a-spin>
|
||||||
|
</SidePanelItem>
|
||||||
|
|
||||||
|
<!-- 陆生生态调查概况弹框 -->
|
||||||
|
<a-modal
|
||||||
|
v-model:open="modalVisible"
|
||||||
|
:title="currentOrderIndex === '16' ? '红外相机信息' : '重点陆生动物'"
|
||||||
|
width="1536px"
|
||||||
|
:footer="null"
|
||||||
|
@cancel="handleModalClose"
|
||||||
|
>
|
||||||
|
<LsstjkTk
|
||||||
|
v-if="modalVisible"
|
||||||
|
:selectDateTime="datetimePicker.value"
|
||||||
|
:orderIndex="currentOrderIndex"
|
||||||
|
:hbrvcd="select.value"
|
||||||
|
/>
|
||||||
|
</a-modal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import {
|
||||||
|
ref,
|
||||||
|
onMounted,
|
||||||
|
onUnmounted,
|
||||||
|
nextTick,
|
||||||
|
watch,
|
||||||
|
inject,
|
||||||
|
computed,
|
||||||
|
defineOptions
|
||||||
|
} from 'vue';
|
||||||
|
import { useJidiSelectEventStore } from '@/store/modules/jidiSelectEvent';
|
||||||
|
import SidePanelItem from '@/components/SidePanelItem/index.vue';
|
||||||
|
import LsstjkTk from './LsstjkTk.vue';
|
||||||
|
import { wbsbGetKendoList, getEvnmAutoMonitor } from '@/api/stdc';
|
||||||
|
import moment from 'moment';
|
||||||
|
import { useDraggable } from '@/utils/drag';
|
||||||
|
|
||||||
|
// 定义组件名
|
||||||
|
defineOptions({
|
||||||
|
name: 'LSJCSBTJ'
|
||||||
|
});
|
||||||
|
|
||||||
|
const JidiSelectEventStore = useJidiSelectEventStore();
|
||||||
|
const baseid = ref('');
|
||||||
|
const dataLoading = ref(false);
|
||||||
|
|
||||||
|
// ==================== 弹框状态 ====================
|
||||||
|
const modalVisible = ref(false);
|
||||||
|
const currentOrderIndex = ref('16');
|
||||||
|
useDraggable(modalVisible, { boundary: true, resetOnOpen: true });
|
||||||
|
// ==================== 响应式变量定义 ====================
|
||||||
|
// 选择器配置
|
||||||
|
const select = ref({
|
||||||
|
show: true,
|
||||||
|
value: undefined,
|
||||||
|
options: [],
|
||||||
|
picker: undefined,
|
||||||
|
format: undefined,
|
||||||
|
width: '120px'
|
||||||
|
});
|
||||||
|
|
||||||
|
// 日期选择器配置
|
||||||
|
const datetimePicker = ref({
|
||||||
|
show: true,
|
||||||
|
value: `${new Date().getFullYear()}`,
|
||||||
|
format: 'YYYY',
|
||||||
|
picker: 'year' as const,
|
||||||
|
options: []
|
||||||
|
});
|
||||||
|
|
||||||
|
const facilities: any = ref([
|
||||||
|
{
|
||||||
|
name: '红外摄像机',
|
||||||
|
count: 0,
|
||||||
|
unit: '个',
|
||||||
|
icon: 'icon iconfont icon-dongwujiuzhuzhan',
|
||||||
|
orderInx: '16'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '鸟类智能识别装置',
|
||||||
|
count: 0,
|
||||||
|
unit: '种',
|
||||||
|
icon: 'icon iconfont icon-shipinjiankongshebei',
|
||||||
|
orderInx: '20'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '动物智能识别装置',
|
||||||
|
count: 0,
|
||||||
|
unit: '个',
|
||||||
|
icon: 'icon iconfont icon-dongwujiuzhuzhan',
|
||||||
|
orderInx: '21'
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
|
||||||
|
// ==================== 事件处理 ====================
|
||||||
|
const handlePanelChange = (data: any) => {
|
||||||
|
console.log('当前所有控件状态:', data);
|
||||||
|
if (data.select) {
|
||||||
|
select.value.value = data.select;
|
||||||
|
}
|
||||||
|
if (data.datetime) {
|
||||||
|
datetimePicker.value.value = data.datetime;
|
||||||
|
}
|
||||||
|
// 选择器或日期变化时,重新获取统计数据
|
||||||
|
getCardData();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCardClick = (facility: any) => {
|
||||||
|
console.log('卡片被点击,准备打开弹框');
|
||||||
|
currentOrderIndex.value = facility.orderInx;
|
||||||
|
modalVisible.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleModalClose = () => {
|
||||||
|
modalVisible.value = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
// ==================== 获取卡片统计数据 ====================
|
||||||
|
const getCardData = async () => {
|
||||||
|
if (!datetimePicker.value.value) return;
|
||||||
|
|
||||||
|
dataLoading.value = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const year = moment(datetimePicker.value.value).format('YYYY');
|
||||||
|
const startTime = `${year}-01-01 00:00:00`;
|
||||||
|
const endTime = `${year}-12-31 23:59:59`;
|
||||||
|
|
||||||
|
const filters: any[] = [
|
||||||
|
{
|
||||||
|
field: 'showIds',
|
||||||
|
operator: 'in',
|
||||||
|
value: ['16', '20']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'startTime',
|
||||||
|
operator: 'gte',
|
||||||
|
dataType: 'date',
|
||||||
|
value: startTime
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'endTime',
|
||||||
|
operator: 'lte',
|
||||||
|
dataType: 'date',
|
||||||
|
value: endTime
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
if (baseid.value && baseid.value !== 'all') {
|
||||||
|
filters.unshift({
|
||||||
|
field: 'hbrvcd',
|
||||||
|
operator: 'in',
|
||||||
|
value: [baseid.value]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const params = {
|
||||||
|
filter: {
|
||||||
|
logic: 'and',
|
||||||
|
filters
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const res = await getEvnmAutoMonitor(params);
|
||||||
|
const data = res?.data?.data;
|
||||||
|
|
||||||
|
if (data && Array.isArray(data)) {
|
||||||
|
const wildAnimalItem = data.find((item: any) => item.orderInx === 16);
|
||||||
|
const keyAnimalItem = data.find((item: any) => item.orderInx === 20);
|
||||||
|
|
||||||
|
// facilities.value = [
|
||||||
|
// {
|
||||||
|
// name: '野生动物监测',
|
||||||
|
// count: wildAnimalItem?.cnt ?? 0,
|
||||||
|
// unit: '个',
|
||||||
|
// icon: 'icon iconfont icon-dongwujiuzhuzhan',
|
||||||
|
// orderInx: '16'
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// name: '重点陆生动物',
|
||||||
|
// count: keyAnimalItem?.cnt ?? 0,
|
||||||
|
// unit: '种',
|
||||||
|
// icon: 'icon iconfont icon-shipinjiankongshebei',
|
||||||
|
// orderInx: '20'
|
||||||
|
// }
|
||||||
|
// ];
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取统计数据失败:', error);
|
||||||
|
facilities.value = [
|
||||||
|
{
|
||||||
|
name: '野生动物监测',
|
||||||
|
count: 0,
|
||||||
|
unit: '个',
|
||||||
|
icon: 'icon iconfont icon-dongwujiuzhuzhan',
|
||||||
|
orderInx: '16'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '重点陆生动物',
|
||||||
|
count: 0,
|
||||||
|
unit: '种',
|
||||||
|
icon: 'icon iconfont icon-shipinjiankongshebei',
|
||||||
|
orderInx: '20'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
} finally {
|
||||||
|
dataLoading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// ==================== 获取选择器数据 ====================
|
||||||
|
const getSelectData = async () => {
|
||||||
|
let params = {
|
||||||
|
filter: {
|
||||||
|
logic: 'and',
|
||||||
|
filters: [
|
||||||
|
{
|
||||||
|
field: 'wbsType',
|
||||||
|
operator: 'eq',
|
||||||
|
value: 'PSB_RVCD'
|
||||||
|
},
|
||||||
|
baseid.value != 'all'
|
||||||
|
? {
|
||||||
|
field: 'objId',
|
||||||
|
operator: 'eq',
|
||||||
|
value: baseid.value
|
||||||
|
}
|
||||||
|
: null
|
||||||
|
].filter(Boolean)
|
||||||
|
},
|
||||||
|
group: [
|
||||||
|
{
|
||||||
|
dir: 'asc',
|
||||||
|
field: 'orderIndex'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
dir: 'asc',
|
||||||
|
field: 'wbsCode'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
dir: 'asc',
|
||||||
|
field: 'wbsName'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
dir: 'asc',
|
||||||
|
field: 'objid'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
groupResultFlat: true
|
||||||
|
};
|
||||||
|
const res = await wbsbGetKendoList(params);
|
||||||
|
let data = res?.data?.data;
|
||||||
|
select.value.options = data.map((item: any) => {
|
||||||
|
return {
|
||||||
|
value: item.wbsCode,
|
||||||
|
label: item.wbsName
|
||||||
|
};
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// 监听电站变化(优先使用父组件 provide 的电站上下文,兜底 jidiStore)
|
||||||
|
const injectedStation = inject<any>('dianZhanStation', null);
|
||||||
|
const stationSource = computed(
|
||||||
|
() => injectedStation?.value || JidiSelectEventStore.selectedItem
|
||||||
|
);
|
||||||
|
|
||||||
|
// ==================== 监听基地变化 ====================
|
||||||
|
watch(
|
||||||
|
() => stationSource.value,
|
||||||
|
async newVal => {
|
||||||
|
if (newVal && newVal.wbsCode) {
|
||||||
|
baseid.value = newVal.wbsCode;
|
||||||
|
getSelectData();
|
||||||
|
getCardData();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ deep: true, immediate: true }
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.facility-grid {
|
||||||
|
width: 406px;
|
||||||
|
flex-flow: wrap;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto,
|
||||||
|
Helvetica Neue, Arial, Noto Sans, sans-serif, Apple Color Emoji,
|
||||||
|
Segoe UI Emoji, Segoe UI Symbol, Noto Color Emoji;
|
||||||
|
}
|
||||||
|
|
||||||
|
.facility-card {
|
||||||
|
width: 200px;
|
||||||
|
height: 64px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin: 4px 0px;
|
||||||
|
background: #fff;
|
||||||
|
border: 1px solid rgb(229, 236, 245);
|
||||||
|
border-radius: 2px;
|
||||||
|
transition: all 0.3s;
|
||||||
|
cursor: pointer;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.facility-icon {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
background: #2f6b98;
|
||||||
|
border-radius: 50%;
|
||||||
|
|
||||||
|
.anticon {
|
||||||
|
font-size: 24px;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.facility-info {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.facility-name {
|
||||||
|
font-size: 16px;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.facility-count {
|
||||||
|
font-size: 18px;
|
||||||
|
color: #2f6b98;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.ant-spin-nested-loading) {
|
||||||
|
min-height: 72px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -131,25 +131,45 @@ const isAlarmRangeLegendState = (legendState: string): boolean => {
|
|||||||
|
|
||||||
const getPointRenderPriority = (point: Record<string, any>): number => {
|
const getPointRenderPriority = (point: Record<string, any>): number => {
|
||||||
const layerKey = String(point.layerKey || point.type || '').toLowerCase();
|
const layerKey = String(point.layerKey || point.type || '').toLowerCase();
|
||||||
const legendState = String(point.anchoPointState || '').trim().toLowerCase();
|
const legendState = String(point.anchoPointState || '')
|
||||||
|
.trim()
|
||||||
|
.toLowerCase();
|
||||||
|
|
||||||
if (
|
const isAlarm =
|
||||||
layerKey.includes('eng_alarm_point') ||
|
layerKey.includes('eng_alarm_point') ||
|
||||||
layerKey.includes('alarm_range') ||
|
layerKey.includes('alarm_range') ||
|
||||||
isAlarmRangeLegendState(legendState)
|
isAlarmRangeLegendState(legendState);
|
||||||
) {
|
const isLarge = legendState.startsWith('large_eng_');
|
||||||
return 300;
|
const isMid = legendState.startsWith('mid_eng_');
|
||||||
|
const isEng = layerKey.includes('eng_point') || isLarge || isMid;
|
||||||
|
|
||||||
|
// 建设状态权重:已建 > 在建 > 未建
|
||||||
|
let buildRank = 0;
|
||||||
|
if (legendState.includes('_ubuilt')) {
|
||||||
|
buildRank = 2;
|
||||||
|
} else if (legendState.includes('_nbuilt')) {
|
||||||
|
buildRank = 1;
|
||||||
|
} else if (legendState.includes('_built')) {
|
||||||
|
buildRank = 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
let base = 100;
|
||||||
layerKey.includes('eng_point') ||
|
|
||||||
legendState.startsWith('large_eng_') ||
|
// 大型电站优先级最高,中型电站次之;同规模内已建优先
|
||||||
legendState.startsWith('mid_eng_')
|
if (isLarge) {
|
||||||
) {
|
base = 300 + buildRank * 30;
|
||||||
return 200;
|
} else if (isMid) {
|
||||||
|
base = 200 + buildRank * 30;
|
||||||
|
} else if (isEng) {
|
||||||
|
base = 150;
|
||||||
}
|
}
|
||||||
|
|
||||||
return 100;
|
// 报警范围点仅在同规格内略微提升,不跨越大型/中型与建设状态层级
|
||||||
|
if (isAlarm) {
|
||||||
|
base += 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
return base;
|
||||||
};
|
};
|
||||||
|
|
||||||
const normalizeText = (value: unknown): string => {
|
const normalizeText = (value: unknown): string => {
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
<!-- SidePanelItem.vue -->
|
<!-- SidePanelItem.vue -->
|
||||||
<template>
|
<template>
|
||||||
<SidePanelItem title="栖息地保护工作开展情况">
|
<SidePanelItem title="栖息地监测设备统计">
|
||||||
<a-spin :spinning="loading" tip="加载中...">
|
<a-spin :spinning="loading" tip="加载中...">
|
||||||
<div class="facility-grid">
|
<div class="facility-grid">
|
||||||
<div
|
<div
|
||||||
|
|||||||
817
frontend/src/modules/qixidifenbutongji/index.vue
Normal file
817
frontend/src/modules/qixidifenbutongji/index.vue
Normal file
@ -0,0 +1,817 @@
|
|||||||
|
<!-- SidePanelItem.vue -->
|
||||||
|
<template>
|
||||||
|
<SidePanelItem
|
||||||
|
title="栖息地分布统计"
|
||||||
|
:select="select"
|
||||||
|
:scopeDate="datetimePicker"
|
||||||
|
@update-values="handlePanelChange1"
|
||||||
|
>
|
||||||
|
<div class="chart-wrapper">
|
||||||
|
<a-spin :spinning="loading">
|
||||||
|
<!-- 始终渲染图表容器,确保有固定宽高 -->
|
||||||
|
<div ref="chartRef" class="water-temp-chart"></div>
|
||||||
|
<!-- 无数据时显示Empty,但不影响容器尺寸 -->
|
||||||
|
<div v-if="!loading && !hasData" class="empty-overlay">
|
||||||
|
<a-empty description="暂无数据" />
|
||||||
|
</div>
|
||||||
|
</a-spin>
|
||||||
|
</div>
|
||||||
|
</SidePanelItem>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import {
|
||||||
|
ref,
|
||||||
|
onMounted,
|
||||||
|
onUnmounted,
|
||||||
|
nextTick,
|
||||||
|
watch,
|
||||||
|
inject,
|
||||||
|
computed,
|
||||||
|
type Ref
|
||||||
|
} from 'vue';
|
||||||
|
import * as echarts from 'echarts';
|
||||||
|
import type { EChartsOption } from 'echarts';
|
||||||
|
import SidePanelItem from '@/components/SidePanelItem/index.vue';
|
||||||
|
import { useJidiSelectEventStore } from '@/store/modules/jidiSelectEvent';
|
||||||
|
import { msstbprptGetKendoList, sdrvwtsGetKendoList } from '@/api/qxd';
|
||||||
|
import { useModelStore } from '@/store/modules/model';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
// 定义组件名
|
||||||
|
defineOptions({
|
||||||
|
name: 'qixidishuiwenbianhua'
|
||||||
|
});
|
||||||
|
|
||||||
|
// ==================== 响应式变量定义 ====================
|
||||||
|
const JidiSelectEventStore = useJidiSelectEventStore();
|
||||||
|
// 图表容器引用
|
||||||
|
const chartRef = ref<HTMLDivElement>();
|
||||||
|
let chartInstance: echarts.ECharts | null = null;
|
||||||
|
const baseid = ref('');
|
||||||
|
|
||||||
|
// Loading 状态和数据状态
|
||||||
|
const loading = ref(true);
|
||||||
|
const hasData = ref(true); // 初始化为 true,因为 initChart 时会渲染
|
||||||
|
|
||||||
|
// 选择器配置
|
||||||
|
const select = ref({
|
||||||
|
show: true,
|
||||||
|
value: undefined,
|
||||||
|
options: [],
|
||||||
|
picker: undefined,
|
||||||
|
format: undefined
|
||||||
|
});
|
||||||
|
|
||||||
|
// 日期选择器配置
|
||||||
|
const datetimePicker: any = ref({
|
||||||
|
show: true,
|
||||||
|
value: (() => {
|
||||||
|
const now = new Date();
|
||||||
|
const currentMonth = `${now.getFullYear()}-${String(
|
||||||
|
now.getMonth() + 1
|
||||||
|
).padStart(2, '0')}`;
|
||||||
|
const lastMonth = new Date(now.getFullYear(), now.getMonth() - 1, 1);
|
||||||
|
const lastMonthStr = `${lastMonth.getFullYear()}-${String(
|
||||||
|
lastMonth.getMonth() + 1
|
||||||
|
).padStart(2, '0')}`;
|
||||||
|
return [lastMonthStr, currentMonth];
|
||||||
|
})(),
|
||||||
|
format: 'YY-MM',
|
||||||
|
picker: 'month' as const,
|
||||||
|
options: []
|
||||||
|
});
|
||||||
|
|
||||||
|
// 图表数据
|
||||||
|
const echartsData = ref<any[]>([]);
|
||||||
|
const modelStore = useModelStore();
|
||||||
|
// 单位配置
|
||||||
|
const unita = ref('℃');
|
||||||
|
|
||||||
|
// 颜色配置 - 使用HSL色彩空间生成舒适的颜色
|
||||||
|
const Color = [
|
||||||
|
'#5470c6',
|
||||||
|
'#91cc75',
|
||||||
|
'#fac858',
|
||||||
|
'#ee6666',
|
||||||
|
'#73c0de',
|
||||||
|
'#3ba272',
|
||||||
|
'#fc8452',
|
||||||
|
'#9a60b4'
|
||||||
|
];
|
||||||
|
|
||||||
|
// ==================== 工具函数 ====================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成模拟数据 - 季节性水温变化
|
||||||
|
*/
|
||||||
|
const generateMockData = () => {
|
||||||
|
const data = [];
|
||||||
|
const baseTemp = 15; // 基础温度
|
||||||
|
|
||||||
|
for (let i = 0; i < 12; i++) {
|
||||||
|
const month = String(i + 1).padStart(2, '0');
|
||||||
|
// 模拟季节性变化:夏季高,冬季低
|
||||||
|
// 正弦波模拟:峰值在7-8月,谷值在1-2月
|
||||||
|
const temp =
|
||||||
|
baseTemp +
|
||||||
|
Math.sin((i / 12) * Math.PI * 2 - Math.PI / 2) * 10 +
|
||||||
|
Math.random() * 2;
|
||||||
|
|
||||||
|
data.push({
|
||||||
|
dt: `2024-${month}`,
|
||||||
|
wt: parseFloat(temp.toFixed(1))
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return data;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化图表
|
||||||
|
*/
|
||||||
|
const initChart = () => {
|
||||||
|
if (!chartRef.value) {
|
||||||
|
console.warn('图表容器未找到');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 销毁已存在的实例
|
||||||
|
if (chartInstance) {
|
||||||
|
chartInstance.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建新实例
|
||||||
|
chartInstance = echarts.init(chartRef.value);
|
||||||
|
|
||||||
|
// 绑定数据点点击事件
|
||||||
|
chartInstance.on('click', handleDataPointClick);
|
||||||
|
|
||||||
|
// 更新图表
|
||||||
|
updateChart();
|
||||||
|
|
||||||
|
// 监听窗口大小变化
|
||||||
|
window.addEventListener('resize', handleResize);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新图表配置
|
||||||
|
*/
|
||||||
|
const updateChart = () => {
|
||||||
|
if (!chartInstance) return;
|
||||||
|
|
||||||
|
const _legendData = [`水温(${unita.value})`];
|
||||||
|
|
||||||
|
// 处理空数据情况
|
||||||
|
const hasData = echartsData.value && echartsData.value.length > 0;
|
||||||
|
|
||||||
|
const option: EChartsOption = {
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'axis',
|
||||||
|
backgroundColor: 'rgba(0, 0, 0, 0.5)',
|
||||||
|
borderColor: 'transparent',
|
||||||
|
textStyle: {
|
||||||
|
color: '#ffffff'
|
||||||
|
},
|
||||||
|
formatter: function (params: any) {
|
||||||
|
if (!params || params.length === 0) return '';
|
||||||
|
|
||||||
|
let res = `${params[0].name} <br/>`;
|
||||||
|
|
||||||
|
for (const item of params) {
|
||||||
|
const seriesName = item.seriesName ?? '';
|
||||||
|
let regx = /\(([^()]+?)\)/;
|
||||||
|
let unit = seriesName.match(regx);
|
||||||
|
// 数据保留一位小数
|
||||||
|
const finalValue =
|
||||||
|
item.value !== undefined && item.value !== null
|
||||||
|
? Number(item.value).toFixed(1)
|
||||||
|
: '-';
|
||||||
|
|
||||||
|
if (item.value !== undefined && item.value !== null) {
|
||||||
|
res += `<span style="background: ${
|
||||||
|
item.color
|
||||||
|
}; height:10px; width: 10px; border-radius: 50%;display: inline-block;margin-right:10px;"></span> ${
|
||||||
|
item.seriesName
|
||||||
|
} ${finalValue}${unit?.[1] || ''} <br/>`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// axisPointer 配置:鼠标经过时显示阴影块
|
||||||
|
axisPointer: {
|
||||||
|
type: 'shadow',
|
||||||
|
label: {
|
||||||
|
show: false
|
||||||
|
},
|
||||||
|
shadowStyle: {
|
||||||
|
color: 'rgba(84, 112, 198, 0.2)'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
color: Color,
|
||||||
|
legend: {
|
||||||
|
data: _legendData,
|
||||||
|
top: 0,
|
||||||
|
left: 'center',
|
||||||
|
selectedMode: 'multiple'
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
top: 25,
|
||||||
|
left: '3%',
|
||||||
|
right: '3%',
|
||||||
|
bottom: '3%',
|
||||||
|
containLabel: true
|
||||||
|
},
|
||||||
|
dataZoom: {
|
||||||
|
type: 'inside',
|
||||||
|
start: 0,
|
||||||
|
end: 100
|
||||||
|
},
|
||||||
|
xAxis: [
|
||||||
|
{
|
||||||
|
type: 'category',
|
||||||
|
data: echartsData.value?.map((item: any) => item.dt) || [],
|
||||||
|
boundaryGap: true,
|
||||||
|
// X轴标签配置 - 最多显示4个标签,强制显示第一个
|
||||||
|
axisLabel: {
|
||||||
|
show: true,
|
||||||
|
rotate: 0, // 不倾斜
|
||||||
|
interval: (index: number, value: string): boolean => {
|
||||||
|
const total = echartsData.value?.length || 0;
|
||||||
|
if (total <= 4) return true; // 数据少于4条,全部显示
|
||||||
|
|
||||||
|
// 强制显示第一个
|
||||||
|
if (index === 0) return true;
|
||||||
|
|
||||||
|
// 其余均匀分布3个标签(总共4个)
|
||||||
|
const step = Math.floor((total - 1) / 3);
|
||||||
|
return index % step === 0 && index < total;
|
||||||
|
},
|
||||||
|
formatter: function (value: string) {
|
||||||
|
// 显示完整日期 "YYYY-MM-DD" 格式
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// X轴网格线(垂直方向)
|
||||||
|
splitLine: {
|
||||||
|
show: true,
|
||||||
|
lineStyle: {
|
||||||
|
type: 'solid',
|
||||||
|
color: '#e0e0e0'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// X轴的axisPointer配置:鼠标经过时显示阴影块
|
||||||
|
axisPointer: {
|
||||||
|
type: 'shadow',
|
||||||
|
shadowStyle: {
|
||||||
|
color: 'rgba(84, 112, 198, 0.2)'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
yAxis: [
|
||||||
|
{
|
||||||
|
type: 'value',
|
||||||
|
// 移除 name,避免与 legend 重复,在刻度上显示单位
|
||||||
|
name: _legendData[0], // 显示 "水温(℃)"
|
||||||
|
nameLocation: 'end', // 显示在 Y 轴顶部
|
||||||
|
nameGap: 8, // 与轴线距离 20px
|
||||||
|
nameTextStyle: {
|
||||||
|
color: '#333333',
|
||||||
|
fontSize: 12
|
||||||
|
},
|
||||||
|
// 计算 Y 轴范围:最小值向下取整,最大值向上取整
|
||||||
|
min:
|
||||||
|
hasData && echartsData.value.length > 0
|
||||||
|
? Math.floor(Math.min(...echartsData.value.map(item => item.wt)))
|
||||||
|
: undefined,
|
||||||
|
max:
|
||||||
|
hasData && echartsData.value.length > 0
|
||||||
|
? Math.ceil(Math.max(...echartsData.value.map(item => item.wt)))
|
||||||
|
: undefined,
|
||||||
|
// 确保显示刻度和标签,保持整数
|
||||||
|
axisLabel: {
|
||||||
|
show: true,
|
||||||
|
color: '#333',
|
||||||
|
formatter: (value: number) => `${Math.round(value)} ℃` // 取整并显示单位
|
||||||
|
},
|
||||||
|
axisTick: {
|
||||||
|
show: true
|
||||||
|
},
|
||||||
|
axisLine: {
|
||||||
|
show: true,
|
||||||
|
lineStyle: {
|
||||||
|
color: '#333'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
splitLine: {
|
||||||
|
show: true,
|
||||||
|
lineStyle: {
|
||||||
|
type: 'solid'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// 确保 Y 轴刻度为整数
|
||||||
|
minInterval: 1,
|
||||||
|
maxInterval: 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
name: _legendData[0],
|
||||||
|
data: hasData
|
||||||
|
? echartsData.value?.map((item: any) => item.wt) || []
|
||||||
|
: [],
|
||||||
|
type: 'line',
|
||||||
|
smooth: true,
|
||||||
|
symbol: 'circle',
|
||||||
|
symbolSize: 4,
|
||||||
|
lineStyle: {
|
||||||
|
width: 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
// 使用完全替换模式更新配置
|
||||||
|
chartInstance.setOption(option, true);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理窗口大小变化
|
||||||
|
*/
|
||||||
|
const handleResize = () => {
|
||||||
|
if (chartInstance) {
|
||||||
|
chartInstance.resize();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理数据点点击事件
|
||||||
|
*/
|
||||||
|
const handleDataPointClick = (params: any) => {
|
||||||
|
console.log('=== 数据点点击事件 ===');
|
||||||
|
console.log('点击参数:', params);
|
||||||
|
|
||||||
|
if (!params || !params.dataIndex) {
|
||||||
|
console.warn('无效的数据点点击');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const dataIndex = params.dataIndex;
|
||||||
|
const dataItem = echartsData.value[dataIndex];
|
||||||
|
|
||||||
|
if (dataItem) {
|
||||||
|
console.log('点击的数据:', {
|
||||||
|
日期: dataItem.dt,
|
||||||
|
水温: dataItem.wt,
|
||||||
|
测站代码: dataItem.stcd,
|
||||||
|
索引: dataIndex
|
||||||
|
});
|
||||||
|
modelStore.modalVisible = true;
|
||||||
|
modelStore.params.sttp = 'WT';
|
||||||
|
modelStore.title = '水温监测';
|
||||||
|
modelStore.params.stcd = dataItem.stcd;
|
||||||
|
modelStore.showStcdSelector = true;
|
||||||
|
modelStore.stcdOptions = select.value.options;
|
||||||
|
modelStore.filter.rangeTm = [
|
||||||
|
dayjs(dataItem.dt).startOf('day').format('YYYY-MM-DD HH:mm:ss'),
|
||||||
|
dayjs(dataItem.dt).endOf('day').format('YYYY-MM-DD HH:mm:ss')
|
||||||
|
];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 加载模拟数据
|
||||||
|
*/
|
||||||
|
const loadData = async () => {
|
||||||
|
try {
|
||||||
|
// 生成模拟数据
|
||||||
|
echartsData.value = generateMockData();
|
||||||
|
|
||||||
|
// 数据加载完成后更新图表
|
||||||
|
await nextTick();
|
||||||
|
updateChart();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('加载水温数据失败:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 加载图表数据
|
||||||
|
* @param startDate 开始日期 (格式: YYYY-MM-DD HH:mm:ss)
|
||||||
|
* @param endDate 结束日期 (格式: YYYY-MM-DD HH:mm:ss)
|
||||||
|
* @param stcd 测站代码
|
||||||
|
*/
|
||||||
|
const getChartData = async (
|
||||||
|
startDate: string,
|
||||||
|
endDate: string,
|
||||||
|
stcd: string
|
||||||
|
) => {
|
||||||
|
try {
|
||||||
|
console.log('=== 开始加载图表数据 ===');
|
||||||
|
console.log('请求参数:', { startDate, endDate, stcd });
|
||||||
|
|
||||||
|
// 设置 loading 状态
|
||||||
|
loading.value = true;
|
||||||
|
hasData.value = false;
|
||||||
|
|
||||||
|
// TODO: 实现API调用逻辑
|
||||||
|
// 参考选中的代码结构,构建请求参数
|
||||||
|
const params = {
|
||||||
|
filter: {
|
||||||
|
logic: 'and',
|
||||||
|
filters: [
|
||||||
|
{
|
||||||
|
field: 'DT',
|
||||||
|
operator: 'gte',
|
||||||
|
value: startDate // 例如: "2026-05-01 00:00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'DT',
|
||||||
|
operator: 'lte',
|
||||||
|
value: endDate // 例如: "2026-06-30 23:59:59"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'stcd',
|
||||||
|
operator: 'eq',
|
||||||
|
dataType: 'string',
|
||||||
|
value: stcd
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
sort: [
|
||||||
|
{
|
||||||
|
field: 'dt',
|
||||||
|
dir: 'asc'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
// 调用实际的API获取数据
|
||||||
|
const res = await sdrvwtsGetKendoList(params);
|
||||||
|
console.log('API返回结果:', res);
|
||||||
|
|
||||||
|
let data = res?.data?.data || [];
|
||||||
|
console.log('原始数据条数:', data.length);
|
||||||
|
|
||||||
|
// 处理空数据情况
|
||||||
|
if (!data || data.length === 0) {
|
||||||
|
echartsData.value = [];
|
||||||
|
hasData.value = false;
|
||||||
|
await nextTick();
|
||||||
|
updateChart();
|
||||||
|
console.warn('未查询到水温数据');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 转换数据格式:提取日期部分用于X轴显示,并进行数据验证和类型转换
|
||||||
|
echartsData.value = data
|
||||||
|
.map(item => {
|
||||||
|
// 验证必要字段
|
||||||
|
if (!item.dt || typeof item.dt !== 'string') {
|
||||||
|
console.warn('无效的日期数据:', item);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 确保 wt 是数值类型
|
||||||
|
const wt = typeof item.wt === 'number' ? item.wt : parseFloat(item.wt);
|
||||||
|
if (isNaN(wt)) {
|
||||||
|
console.warn('无效的水温数据:', item);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
dt: item.dt.substring(0, 10), // 提取 "2026-05-02" 格式
|
||||||
|
wt: wt, // 确保是 number 类型
|
||||||
|
stcd: item.stcd || stcd // 保留 stcd 字段,如果 API 返回中没有则使用传入的 stcd
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.filter(Boolean); // 过滤掉无效数据
|
||||||
|
|
||||||
|
console.log('转换后的图表数据:', echartsData.value);
|
||||||
|
console.log('数据类型检查:', {
|
||||||
|
第一条数据: echartsData.value[0],
|
||||||
|
dt类型: typeof echartsData.value[0]?.dt,
|
||||||
|
wt类型: typeof echartsData.value[0]?.wt,
|
||||||
|
wt값: echartsData.value[0]?.wt
|
||||||
|
});
|
||||||
|
|
||||||
|
// 判断是否有数据
|
||||||
|
hasData.value = echartsData.value.length > 0;
|
||||||
|
console.log('hasData 设置为:', hasData.value);
|
||||||
|
|
||||||
|
// 数据加载完成后更新图表
|
||||||
|
await nextTick();
|
||||||
|
updateChart();
|
||||||
|
console.log('✅ 图表数据加载完成');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取图表数据失败:', error);
|
||||||
|
hasData.value = false;
|
||||||
|
} finally {
|
||||||
|
// 关闭 loading 状态
|
||||||
|
await nextTick();
|
||||||
|
console.log('关闭 loading 状态');
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将 YY-MM 格式转换为完整的日期时间范围
|
||||||
|
* @param dateRange ['26-05', '26-06'] 格式的数组
|
||||||
|
* @returns { startDate: string, endDate: string } 格式化后的日期范围
|
||||||
|
*/
|
||||||
|
const convertDateRange = (dateRange: string[]) => {
|
||||||
|
if (!dateRange || dateRange.length !== 2) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
//
|
||||||
|
|
||||||
|
const [startStr, endStr] = dateRange;
|
||||||
|
|
||||||
|
// 获取今天的日期(日部分)
|
||||||
|
const today = new Date();
|
||||||
|
const currentDay = today.getDate();
|
||||||
|
|
||||||
|
// 解析开始日期 YY-MM
|
||||||
|
const [startYearStr, startMonthStr] = startStr.split('-');
|
||||||
|
const startYear = parseInt(startYearStr);
|
||||||
|
const startMonth = parseInt(startMonthStr);
|
||||||
|
const fullStartYear = startYear < 100 ? 2000 + startYear : startYear;
|
||||||
|
|
||||||
|
// 解析结束日期 YY-MM
|
||||||
|
const [endYearStr, endMonthStr] = endStr.split('-');
|
||||||
|
const endYear = parseInt(endYearStr);
|
||||||
|
const endMonth = parseInt(endMonthStr);
|
||||||
|
const fullEndYear = endYear < 100 ? 2000 + endYear : endYear;
|
||||||
|
|
||||||
|
// 开始日期:基于第一个值的年月 + 今天的日期 + 00:00:00
|
||||||
|
// 需要处理月份天数边界问题(如今天是31号,但2月只有28天)
|
||||||
|
const startMonthLastDay = new Date(fullStartYear, startMonth, 0).getDate();
|
||||||
|
const actualStartDay = Math.min(currentDay, startMonthLastDay);
|
||||||
|
const startDate = `${fullStartYear}-${String(startMonth).padStart(
|
||||||
|
2,
|
||||||
|
'0'
|
||||||
|
)}-${String(actualStartDay).padStart(2, '0')} 00:00:00`;
|
||||||
|
|
||||||
|
// 结束日期:基于第二个值的年月 + 今天的日期 + 23:59:59
|
||||||
|
// 同样需要处理月份天数边界问题
|
||||||
|
const endMonthLastDay = new Date(fullEndYear, endMonth, 0).getDate();
|
||||||
|
const actualEndDay = Math.min(currentDay, endMonthLastDay);
|
||||||
|
const endDate = `${fullEndYear}-${String(endMonth).padStart(2, '0')}-${String(
|
||||||
|
actualEndDay
|
||||||
|
).padStart(2, '0')} 23:59:59`;
|
||||||
|
|
||||||
|
return {
|
||||||
|
startDate,
|
||||||
|
endDate
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const handlePanelChange1 = async data => {
|
||||||
|
console.log('=== 用户交互触发 ===');
|
||||||
|
console.log('当前所有控件状态:', data);
|
||||||
|
// scopeDate: ['26-05', '26-06']
|
||||||
|
// select: "0836000011"
|
||||||
|
if (data.scopeDate && data.scopeDate.length > 0 && data.select) {
|
||||||
|
console.log('参数验证通过,开始加载数据');
|
||||||
|
// 转换日期格式并加载数据
|
||||||
|
const dateRange = convertDateRange(data.scopeDate);
|
||||||
|
if (dateRange) {
|
||||||
|
console.log('日期转换结果:', dateRange);
|
||||||
|
await getChartData(dateRange.startDate, dateRange.endDate, data.select);
|
||||||
|
} else {
|
||||||
|
console.error('日期转换失败');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
loading.value = false;
|
||||||
|
hasData.value = false;
|
||||||
|
// 清空图表数据
|
||||||
|
echartsData.value = [];
|
||||||
|
// 更新图表显示空状态
|
||||||
|
await nextTick();
|
||||||
|
updateChart();
|
||||||
|
console.warn('参数不完整:', {
|
||||||
|
scopeDate: data.scopeDate,
|
||||||
|
select: data.select
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
//获取水温监测下拉框
|
||||||
|
const getselsectData = async () => {
|
||||||
|
let params = {
|
||||||
|
filter: {
|
||||||
|
logic: 'and',
|
||||||
|
filters: [
|
||||||
|
baseid.value != 'all'
|
||||||
|
? {
|
||||||
|
field: 'baseId',
|
||||||
|
operator: 'eq',
|
||||||
|
dataType: 'string',
|
||||||
|
value: baseid.value
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
{
|
||||||
|
field: 'fhstcd',
|
||||||
|
operator: 'isnotnull'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
logic: 'or',
|
||||||
|
filters: [
|
||||||
|
{
|
||||||
|
field: 'sttpCode',
|
||||||
|
operator: 'eq',
|
||||||
|
dataType: 'string',
|
||||||
|
value: 'WTRV'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
].filter(Boolean)
|
||||||
|
},
|
||||||
|
select: ['stcd', 'stnm', 'fhstcd', 'fhstnm', 'sttpCode']
|
||||||
|
};
|
||||||
|
let res = await msstbprptGetKendoList(params);
|
||||||
|
console.log(res);
|
||||||
|
if (!res?.data?.data || res.data.data.length == 0) {
|
||||||
|
select.value.value = '';
|
||||||
|
select.value.options = [];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
select.value.value = res.data.data[0].stcd;
|
||||||
|
select.value.options = res.data.data.map((item: any) => {
|
||||||
|
return {
|
||||||
|
label: item.stnm,
|
||||||
|
value: item.stcd
|
||||||
|
};
|
||||||
|
});
|
||||||
|
//
|
||||||
|
};
|
||||||
|
////
|
||||||
|
// 监听电站变化(优先使用父组件 provide 的电站上下文,兜底 jidiStore)
|
||||||
|
const injectedStation = inject<any>('dianZhanStation', null);
|
||||||
|
const stationSource = computed(
|
||||||
|
() => injectedStation?.value || JidiSelectEventStore.selectedItem
|
||||||
|
);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => stationSource.value,
|
||||||
|
newVal => {
|
||||||
|
baseid.value = newVal.wbsCode;
|
||||||
|
getselsectData();
|
||||||
|
},
|
||||||
|
{ deep: true, immediate: true }
|
||||||
|
);
|
||||||
|
|
||||||
|
// ==================== ppbclTagList 配置 ====================
|
||||||
|
const MODULE_PATH = '@/modules/qixidishuiwenbianhua/index.vue';
|
||||||
|
|
||||||
|
const ppbclConfigMap = inject<
|
||||||
|
Ref<Record<string, { isShow: number; tagValue: string }>>
|
||||||
|
>('ppbclConfigMap', ref({}));
|
||||||
|
|
||||||
|
/** 根据 tagValue 计算默认日期范围(YY-MM 格式) */
|
||||||
|
function getDateRangeFromTagValue(tagValue: string): [string, string] | null {
|
||||||
|
const now = dayjs();
|
||||||
|
let start: dayjs.Dayjs;
|
||||||
|
|
||||||
|
switch (tagValue) {
|
||||||
|
case '7d':
|
||||||
|
start = now.subtract(7, 'day');
|
||||||
|
break;
|
||||||
|
case '1M':
|
||||||
|
start = now.subtract(1, 'month');
|
||||||
|
break;
|
||||||
|
case 'a1M':
|
||||||
|
start = now.subtract(1, 'month').startOf('month');
|
||||||
|
break;
|
||||||
|
case '3M':
|
||||||
|
start = now.subtract(3, 'month');
|
||||||
|
break;
|
||||||
|
case 'a3M':
|
||||||
|
start = now.subtract(3, 'month').startOf('month');
|
||||||
|
break;
|
||||||
|
case '1y':
|
||||||
|
start = now.subtract(1, 'year');
|
||||||
|
break;
|
||||||
|
case '3y':
|
||||||
|
start = now.subtract(3, 'year');
|
||||||
|
break;
|
||||||
|
case '5y':
|
||||||
|
start = now.subtract(5, 'year');
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return [start.format('YYYY-MM'), now.format('YYYY-MM')];
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => ppbclConfigMap?.value?.[MODULE_PATH],
|
||||||
|
config => {
|
||||||
|
if (config) {
|
||||||
|
// isShow 控制时间选择器显隐
|
||||||
|
datetimePicker.value.show = config.isShow !== 0;
|
||||||
|
|
||||||
|
// tagValue 设置默认时间范围
|
||||||
|
if (config.tagValue) {
|
||||||
|
const dateRange = getDateRangeFromTagValue(config.tagValue);
|
||||||
|
if (dateRange) {
|
||||||
|
datetimePicker.value.value = dateRange;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ immediate: true }
|
||||||
|
);
|
||||||
|
|
||||||
|
// ==================== 生命周期钩子 ====================
|
||||||
|
|
||||||
|
// 页面加载时执行
|
||||||
|
onMounted(() => {
|
||||||
|
// 双重等待确保DOM和样式计算完成
|
||||||
|
nextTick(() => {
|
||||||
|
setTimeout(() => {
|
||||||
|
// 校验容器尺寸
|
||||||
|
if (chartRef.value) {
|
||||||
|
const rect = chartRef.value.getBoundingClientRect();
|
||||||
|
if (rect.width > 0 && rect.height > 0) {
|
||||||
|
initChart();
|
||||||
|
// 注释掉模拟数据加载,等待用户交互后加载真实数据
|
||||||
|
// loadData();
|
||||||
|
console.log('✅ 图表初始化完成,等待用户交互加载数据');
|
||||||
|
} else {
|
||||||
|
console.warn('图表容器尺寸为0,启动重试机制');
|
||||||
|
// 指数退避重试
|
||||||
|
let retryCount = 0;
|
||||||
|
const maxRetries = 5;
|
||||||
|
const retryInit = () => {
|
||||||
|
if (retryCount < maxRetries) {
|
||||||
|
retryCount++;
|
||||||
|
setTimeout(() => {
|
||||||
|
const newRect = chartRef.value?.getBoundingClientRect();
|
||||||
|
if (newRect && newRect.width > 0 && newRect.height > 0) {
|
||||||
|
initChart();
|
||||||
|
// loadData();
|
||||||
|
console.log('✅ 图表初始化完成(重试成功)');
|
||||||
|
} else {
|
||||||
|
retryInit();
|
||||||
|
}
|
||||||
|
}, 50 * Math.pow(2, retryCount));
|
||||||
|
} else {
|
||||||
|
console.error('图表容器初始化失败,已达到最大重试次数');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
retryInit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, 50);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// 组件卸载时清理
|
||||||
|
onUnmounted(() => {
|
||||||
|
// 移除事件监听
|
||||||
|
window.removeEventListener('resize', handleResize);
|
||||||
|
|
||||||
|
// 销毁图表实例
|
||||||
|
if (chartInstance) {
|
||||||
|
// 解绑数据点点击事件
|
||||||
|
chartInstance.off('click', handleDataPointClick);
|
||||||
|
chartInstance.dispose();
|
||||||
|
chartInstance = null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.chart-wrapper {
|
||||||
|
width: 100%;
|
||||||
|
height: 231px;
|
||||||
|
min-height: 231px;
|
||||||
|
position: relative;
|
||||||
|
/* 为empty-overlay提供定位上下文 */
|
||||||
|
|
||||||
|
.water-temp-chart {
|
||||||
|
width: 100% !important;
|
||||||
|
height: 100% !important;
|
||||||
|
min-width: 100%;
|
||||||
|
min-height: 231px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-overlay {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background: rgba(255, 255, 255);
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
:deep(.ant-spin-nested-loading > div > .ant-spin) {
|
||||||
|
background: rgba(255, 255, 255) !important;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
817
frontend/src/modules/qixidijiancejieguotongji/index.vue
Normal file
817
frontend/src/modules/qixidijiancejieguotongji/index.vue
Normal file
@ -0,0 +1,817 @@
|
|||||||
|
<!-- SidePanelItem.vue -->
|
||||||
|
<template>
|
||||||
|
<SidePanelItem
|
||||||
|
title="监测结果统计"
|
||||||
|
:select="select"
|
||||||
|
:scopeDate="datetimePicker"
|
||||||
|
@update-values="handlePanelChange1"
|
||||||
|
>
|
||||||
|
<div class="chart-wrapper">
|
||||||
|
<a-spin :spinning="loading">
|
||||||
|
<!-- 始终渲染图表容器,确保有固定宽高 -->
|
||||||
|
<div ref="chartRef" class="water-temp-chart"></div>
|
||||||
|
<!-- 无数据时显示Empty,但不影响容器尺寸 -->
|
||||||
|
<div v-if="!loading && !hasData" class="empty-overlay">
|
||||||
|
<a-empty description="暂无数据" />
|
||||||
|
</div>
|
||||||
|
</a-spin>
|
||||||
|
</div>
|
||||||
|
</SidePanelItem>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import {
|
||||||
|
ref,
|
||||||
|
onMounted,
|
||||||
|
onUnmounted,
|
||||||
|
nextTick,
|
||||||
|
watch,
|
||||||
|
inject,
|
||||||
|
computed,
|
||||||
|
type Ref
|
||||||
|
} from 'vue';
|
||||||
|
import * as echarts from 'echarts';
|
||||||
|
import type { EChartsOption } from 'echarts';
|
||||||
|
import SidePanelItem from '@/components/SidePanelItem/index.vue';
|
||||||
|
import { useJidiSelectEventStore } from '@/store/modules/jidiSelectEvent';
|
||||||
|
import { msstbprptGetKendoList, sdrvwtsGetKendoList } from '@/api/qxd';
|
||||||
|
import { useModelStore } from '@/store/modules/model';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
// 定义组件名
|
||||||
|
defineOptions({
|
||||||
|
name: 'qixidishuiwenbianhua'
|
||||||
|
});
|
||||||
|
|
||||||
|
// ==================== 响应式变量定义 ====================
|
||||||
|
const JidiSelectEventStore = useJidiSelectEventStore();
|
||||||
|
// 图表容器引用
|
||||||
|
const chartRef = ref<HTMLDivElement>();
|
||||||
|
let chartInstance: echarts.ECharts | null = null;
|
||||||
|
const baseid = ref('');
|
||||||
|
|
||||||
|
// Loading 状态和数据状态
|
||||||
|
const loading = ref(true);
|
||||||
|
const hasData = ref(true); // 初始化为 true,因为 initChart 时会渲染
|
||||||
|
|
||||||
|
// 选择器配置
|
||||||
|
const select = ref({
|
||||||
|
show: true,
|
||||||
|
value: undefined,
|
||||||
|
options: [],
|
||||||
|
picker: undefined,
|
||||||
|
format: undefined
|
||||||
|
});
|
||||||
|
|
||||||
|
// 日期选择器配置
|
||||||
|
const datetimePicker: any = ref({
|
||||||
|
show: true,
|
||||||
|
value: (() => {
|
||||||
|
const now = new Date();
|
||||||
|
const currentMonth = `${now.getFullYear()}-${String(
|
||||||
|
now.getMonth() + 1
|
||||||
|
).padStart(2, '0')}`;
|
||||||
|
const lastMonth = new Date(now.getFullYear(), now.getMonth() - 1, 1);
|
||||||
|
const lastMonthStr = `${lastMonth.getFullYear()}-${String(
|
||||||
|
lastMonth.getMonth() + 1
|
||||||
|
).padStart(2, '0')}`;
|
||||||
|
return [lastMonthStr, currentMonth];
|
||||||
|
})(),
|
||||||
|
format: 'YY-MM',
|
||||||
|
picker: 'month' as const,
|
||||||
|
options: []
|
||||||
|
});
|
||||||
|
|
||||||
|
// 图表数据
|
||||||
|
const echartsData = ref<any[]>([]);
|
||||||
|
const modelStore = useModelStore();
|
||||||
|
// 单位配置
|
||||||
|
const unita = ref('℃');
|
||||||
|
|
||||||
|
// 颜色配置 - 使用HSL色彩空间生成舒适的颜色
|
||||||
|
const Color = [
|
||||||
|
'#5470c6',
|
||||||
|
'#91cc75',
|
||||||
|
'#fac858',
|
||||||
|
'#ee6666',
|
||||||
|
'#73c0de',
|
||||||
|
'#3ba272',
|
||||||
|
'#fc8452',
|
||||||
|
'#9a60b4'
|
||||||
|
];
|
||||||
|
|
||||||
|
// ==================== 工具函数 ====================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成模拟数据 - 季节性水温变化
|
||||||
|
*/
|
||||||
|
const generateMockData = () => {
|
||||||
|
const data = [];
|
||||||
|
const baseTemp = 15; // 基础温度
|
||||||
|
|
||||||
|
for (let i = 0; i < 12; i++) {
|
||||||
|
const month = String(i + 1).padStart(2, '0');
|
||||||
|
// 模拟季节性变化:夏季高,冬季低
|
||||||
|
// 正弦波模拟:峰值在7-8月,谷值在1-2月
|
||||||
|
const temp =
|
||||||
|
baseTemp +
|
||||||
|
Math.sin((i / 12) * Math.PI * 2 - Math.PI / 2) * 10 +
|
||||||
|
Math.random() * 2;
|
||||||
|
|
||||||
|
data.push({
|
||||||
|
dt: `2024-${month}`,
|
||||||
|
wt: parseFloat(temp.toFixed(1))
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return data;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化图表
|
||||||
|
*/
|
||||||
|
const initChart = () => {
|
||||||
|
if (!chartRef.value) {
|
||||||
|
console.warn('图表容器未找到');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 销毁已存在的实例
|
||||||
|
if (chartInstance) {
|
||||||
|
chartInstance.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建新实例
|
||||||
|
chartInstance = echarts.init(chartRef.value);
|
||||||
|
|
||||||
|
// 绑定数据点点击事件
|
||||||
|
chartInstance.on('click', handleDataPointClick);
|
||||||
|
|
||||||
|
// 更新图表
|
||||||
|
updateChart();
|
||||||
|
|
||||||
|
// 监听窗口大小变化
|
||||||
|
window.addEventListener('resize', handleResize);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新图表配置
|
||||||
|
*/
|
||||||
|
const updateChart = () => {
|
||||||
|
if (!chartInstance) return;
|
||||||
|
|
||||||
|
const _legendData = [`水温(${unita.value})`];
|
||||||
|
|
||||||
|
// 处理空数据情况
|
||||||
|
const hasData = echartsData.value && echartsData.value.length > 0;
|
||||||
|
|
||||||
|
const option: EChartsOption = {
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'axis',
|
||||||
|
backgroundColor: 'rgba(0, 0, 0, 0.5)',
|
||||||
|
borderColor: 'transparent',
|
||||||
|
textStyle: {
|
||||||
|
color: '#ffffff'
|
||||||
|
},
|
||||||
|
formatter: function (params: any) {
|
||||||
|
if (!params || params.length === 0) return '';
|
||||||
|
|
||||||
|
let res = `${params[0].name} <br/>`;
|
||||||
|
|
||||||
|
for (const item of params) {
|
||||||
|
const seriesName = item.seriesName ?? '';
|
||||||
|
let regx = /\(([^()]+?)\)/;
|
||||||
|
let unit = seriesName.match(regx);
|
||||||
|
// 数据保留一位小数
|
||||||
|
const finalValue =
|
||||||
|
item.value !== undefined && item.value !== null
|
||||||
|
? Number(item.value).toFixed(1)
|
||||||
|
: '-';
|
||||||
|
|
||||||
|
if (item.value !== undefined && item.value !== null) {
|
||||||
|
res += `<span style="background: ${
|
||||||
|
item.color
|
||||||
|
}; height:10px; width: 10px; border-radius: 50%;display: inline-block;margin-right:10px;"></span> ${
|
||||||
|
item.seriesName
|
||||||
|
} ${finalValue}${unit?.[1] || ''} <br/>`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// axisPointer 配置:鼠标经过时显示阴影块
|
||||||
|
axisPointer: {
|
||||||
|
type: 'shadow',
|
||||||
|
label: {
|
||||||
|
show: false
|
||||||
|
},
|
||||||
|
shadowStyle: {
|
||||||
|
color: 'rgba(84, 112, 198, 0.2)'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
color: Color,
|
||||||
|
legend: {
|
||||||
|
data: _legendData,
|
||||||
|
top: 0,
|
||||||
|
left: 'center',
|
||||||
|
selectedMode: 'multiple'
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
top: 25,
|
||||||
|
left: '3%',
|
||||||
|
right: '3%',
|
||||||
|
bottom: '3%',
|
||||||
|
containLabel: true
|
||||||
|
},
|
||||||
|
dataZoom: {
|
||||||
|
type: 'inside',
|
||||||
|
start: 0,
|
||||||
|
end: 100
|
||||||
|
},
|
||||||
|
xAxis: [
|
||||||
|
{
|
||||||
|
type: 'category',
|
||||||
|
data: echartsData.value?.map((item: any) => item.dt) || [],
|
||||||
|
boundaryGap: true,
|
||||||
|
// X轴标签配置 - 最多显示4个标签,强制显示第一个
|
||||||
|
axisLabel: {
|
||||||
|
show: true,
|
||||||
|
rotate: 0, // 不倾斜
|
||||||
|
interval: (index: number, value: string): boolean => {
|
||||||
|
const total = echartsData.value?.length || 0;
|
||||||
|
if (total <= 4) return true; // 数据少于4条,全部显示
|
||||||
|
|
||||||
|
// 强制显示第一个
|
||||||
|
if (index === 0) return true;
|
||||||
|
|
||||||
|
// 其余均匀分布3个标签(总共4个)
|
||||||
|
const step = Math.floor((total - 1) / 3);
|
||||||
|
return index % step === 0 && index < total;
|
||||||
|
},
|
||||||
|
formatter: function (value: string) {
|
||||||
|
// 显示完整日期 "YYYY-MM-DD" 格式
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// X轴网格线(垂直方向)
|
||||||
|
splitLine: {
|
||||||
|
show: true,
|
||||||
|
lineStyle: {
|
||||||
|
type: 'solid',
|
||||||
|
color: '#e0e0e0'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// X轴的axisPointer配置:鼠标经过时显示阴影块
|
||||||
|
axisPointer: {
|
||||||
|
type: 'shadow',
|
||||||
|
shadowStyle: {
|
||||||
|
color: 'rgba(84, 112, 198, 0.2)'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
yAxis: [
|
||||||
|
{
|
||||||
|
type: 'value',
|
||||||
|
// 移除 name,避免与 legend 重复,在刻度上显示单位
|
||||||
|
name: _legendData[0], // 显示 "水温(℃)"
|
||||||
|
nameLocation: 'end', // 显示在 Y 轴顶部
|
||||||
|
nameGap: 8, // 与轴线距离 20px
|
||||||
|
nameTextStyle: {
|
||||||
|
color: '#333333',
|
||||||
|
fontSize: 12
|
||||||
|
},
|
||||||
|
// 计算 Y 轴范围:最小值向下取整,最大值向上取整
|
||||||
|
min:
|
||||||
|
hasData && echartsData.value.length > 0
|
||||||
|
? Math.floor(Math.min(...echartsData.value.map(item => item.wt)))
|
||||||
|
: undefined,
|
||||||
|
max:
|
||||||
|
hasData && echartsData.value.length > 0
|
||||||
|
? Math.ceil(Math.max(...echartsData.value.map(item => item.wt)))
|
||||||
|
: undefined,
|
||||||
|
// 确保显示刻度和标签,保持整数
|
||||||
|
axisLabel: {
|
||||||
|
show: true,
|
||||||
|
color: '#333',
|
||||||
|
formatter: (value: number) => `${Math.round(value)} ℃` // 取整并显示单位
|
||||||
|
},
|
||||||
|
axisTick: {
|
||||||
|
show: true
|
||||||
|
},
|
||||||
|
axisLine: {
|
||||||
|
show: true,
|
||||||
|
lineStyle: {
|
||||||
|
color: '#333'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
splitLine: {
|
||||||
|
show: true,
|
||||||
|
lineStyle: {
|
||||||
|
type: 'solid'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// 确保 Y 轴刻度为整数
|
||||||
|
minInterval: 1,
|
||||||
|
maxInterval: 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
name: _legendData[0],
|
||||||
|
data: hasData
|
||||||
|
? echartsData.value?.map((item: any) => item.wt) || []
|
||||||
|
: [],
|
||||||
|
type: 'line',
|
||||||
|
smooth: true,
|
||||||
|
symbol: 'circle',
|
||||||
|
symbolSize: 4,
|
||||||
|
lineStyle: {
|
||||||
|
width: 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
// 使用完全替换模式更新配置
|
||||||
|
chartInstance.setOption(option, true);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理窗口大小变化
|
||||||
|
*/
|
||||||
|
const handleResize = () => {
|
||||||
|
if (chartInstance) {
|
||||||
|
chartInstance.resize();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理数据点点击事件
|
||||||
|
*/
|
||||||
|
const handleDataPointClick = (params: any) => {
|
||||||
|
console.log('=== 数据点点击事件 ===');
|
||||||
|
console.log('点击参数:', params);
|
||||||
|
|
||||||
|
if (!params || !params.dataIndex) {
|
||||||
|
console.warn('无效的数据点点击');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const dataIndex = params.dataIndex;
|
||||||
|
const dataItem = echartsData.value[dataIndex];
|
||||||
|
|
||||||
|
if (dataItem) {
|
||||||
|
console.log('点击的数据:', {
|
||||||
|
日期: dataItem.dt,
|
||||||
|
水温: dataItem.wt,
|
||||||
|
测站代码: dataItem.stcd,
|
||||||
|
索引: dataIndex
|
||||||
|
});
|
||||||
|
modelStore.modalVisible = true;
|
||||||
|
modelStore.params.sttp = 'WT';
|
||||||
|
modelStore.title = '水温监测';
|
||||||
|
modelStore.params.stcd = dataItem.stcd;
|
||||||
|
modelStore.showStcdSelector = true;
|
||||||
|
modelStore.stcdOptions = select.value.options;
|
||||||
|
modelStore.filter.rangeTm = [
|
||||||
|
dayjs(dataItem.dt).startOf('day').format('YYYY-MM-DD HH:mm:ss'),
|
||||||
|
dayjs(dataItem.dt).endOf('day').format('YYYY-MM-DD HH:mm:ss')
|
||||||
|
];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 加载模拟数据
|
||||||
|
*/
|
||||||
|
const loadData = async () => {
|
||||||
|
try {
|
||||||
|
// 生成模拟数据
|
||||||
|
echartsData.value = generateMockData();
|
||||||
|
|
||||||
|
// 数据加载完成后更新图表
|
||||||
|
await nextTick();
|
||||||
|
updateChart();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('加载水温数据失败:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 加载图表数据
|
||||||
|
* @param startDate 开始日期 (格式: YYYY-MM-DD HH:mm:ss)
|
||||||
|
* @param endDate 结束日期 (格式: YYYY-MM-DD HH:mm:ss)
|
||||||
|
* @param stcd 测站代码
|
||||||
|
*/
|
||||||
|
const getChartData = async (
|
||||||
|
startDate: string,
|
||||||
|
endDate: string,
|
||||||
|
stcd: string
|
||||||
|
) => {
|
||||||
|
try {
|
||||||
|
console.log('=== 开始加载图表数据 ===');
|
||||||
|
console.log('请求参数:', { startDate, endDate, stcd });
|
||||||
|
|
||||||
|
// 设置 loading 状态
|
||||||
|
loading.value = true;
|
||||||
|
hasData.value = false;
|
||||||
|
|
||||||
|
// TODO: 实现API调用逻辑
|
||||||
|
// 参考选中的代码结构,构建请求参数
|
||||||
|
const params = {
|
||||||
|
filter: {
|
||||||
|
logic: 'and',
|
||||||
|
filters: [
|
||||||
|
{
|
||||||
|
field: 'DT',
|
||||||
|
operator: 'gte',
|
||||||
|
value: startDate // 例如: "2026-05-01 00:00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'DT',
|
||||||
|
operator: 'lte',
|
||||||
|
value: endDate // 例如: "2026-06-30 23:59:59"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'stcd',
|
||||||
|
operator: 'eq',
|
||||||
|
dataType: 'string',
|
||||||
|
value: stcd
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
sort: [
|
||||||
|
{
|
||||||
|
field: 'dt',
|
||||||
|
dir: 'asc'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
// 调用实际的API获取数据
|
||||||
|
const res = await sdrvwtsGetKendoList(params);
|
||||||
|
console.log('API返回结果:', res);
|
||||||
|
|
||||||
|
let data = res?.data?.data || [];
|
||||||
|
console.log('原始数据条数:', data.length);
|
||||||
|
|
||||||
|
// 处理空数据情况
|
||||||
|
if (!data || data.length === 0) {
|
||||||
|
echartsData.value = [];
|
||||||
|
hasData.value = false;
|
||||||
|
await nextTick();
|
||||||
|
updateChart();
|
||||||
|
console.warn('未查询到水温数据');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 转换数据格式:提取日期部分用于X轴显示,并进行数据验证和类型转换
|
||||||
|
echartsData.value = data
|
||||||
|
.map(item => {
|
||||||
|
// 验证必要字段
|
||||||
|
if (!item.dt || typeof item.dt !== 'string') {
|
||||||
|
console.warn('无效的日期数据:', item);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 确保 wt 是数值类型
|
||||||
|
const wt = typeof item.wt === 'number' ? item.wt : parseFloat(item.wt);
|
||||||
|
if (isNaN(wt)) {
|
||||||
|
console.warn('无效的水温数据:', item);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
dt: item.dt.substring(0, 10), // 提取 "2026-05-02" 格式
|
||||||
|
wt: wt, // 确保是 number 类型
|
||||||
|
stcd: item.stcd || stcd // 保留 stcd 字段,如果 API 返回中没有则使用传入的 stcd
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.filter(Boolean); // 过滤掉无效数据
|
||||||
|
|
||||||
|
console.log('转换后的图表数据:', echartsData.value);
|
||||||
|
console.log('数据类型检查:', {
|
||||||
|
第一条数据: echartsData.value[0],
|
||||||
|
dt类型: typeof echartsData.value[0]?.dt,
|
||||||
|
wt类型: typeof echartsData.value[0]?.wt,
|
||||||
|
wt값: echartsData.value[0]?.wt
|
||||||
|
});
|
||||||
|
|
||||||
|
// 判断是否有数据
|
||||||
|
hasData.value = echartsData.value.length > 0;
|
||||||
|
console.log('hasData 设置为:', hasData.value);
|
||||||
|
|
||||||
|
// 数据加载完成后更新图表
|
||||||
|
await nextTick();
|
||||||
|
updateChart();
|
||||||
|
console.log('✅ 图表数据加载完成');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取图表数据失败:', error);
|
||||||
|
hasData.value = false;
|
||||||
|
} finally {
|
||||||
|
// 关闭 loading 状态
|
||||||
|
await nextTick();
|
||||||
|
console.log('关闭 loading 状态');
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将 YY-MM 格式转换为完整的日期时间范围
|
||||||
|
* @param dateRange ['26-05', '26-06'] 格式的数组
|
||||||
|
* @returns { startDate: string, endDate: string } 格式化后的日期范围
|
||||||
|
*/
|
||||||
|
const convertDateRange = (dateRange: string[]) => {
|
||||||
|
if (!dateRange || dateRange.length !== 2) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
//
|
||||||
|
|
||||||
|
const [startStr, endStr] = dateRange;
|
||||||
|
|
||||||
|
// 获取今天的日期(日部分)
|
||||||
|
const today = new Date();
|
||||||
|
const currentDay = today.getDate();
|
||||||
|
|
||||||
|
// 解析开始日期 YY-MM
|
||||||
|
const [startYearStr, startMonthStr] = startStr.split('-');
|
||||||
|
const startYear = parseInt(startYearStr);
|
||||||
|
const startMonth = parseInt(startMonthStr);
|
||||||
|
const fullStartYear = startYear < 100 ? 2000 + startYear : startYear;
|
||||||
|
|
||||||
|
// 解析结束日期 YY-MM
|
||||||
|
const [endYearStr, endMonthStr] = endStr.split('-');
|
||||||
|
const endYear = parseInt(endYearStr);
|
||||||
|
const endMonth = parseInt(endMonthStr);
|
||||||
|
const fullEndYear = endYear < 100 ? 2000 + endYear : endYear;
|
||||||
|
|
||||||
|
// 开始日期:基于第一个值的年月 + 今天的日期 + 00:00:00
|
||||||
|
// 需要处理月份天数边界问题(如今天是31号,但2月只有28天)
|
||||||
|
const startMonthLastDay = new Date(fullStartYear, startMonth, 0).getDate();
|
||||||
|
const actualStartDay = Math.min(currentDay, startMonthLastDay);
|
||||||
|
const startDate = `${fullStartYear}-${String(startMonth).padStart(
|
||||||
|
2,
|
||||||
|
'0'
|
||||||
|
)}-${String(actualStartDay).padStart(2, '0')} 00:00:00`;
|
||||||
|
|
||||||
|
// 结束日期:基于第二个值的年月 + 今天的日期 + 23:59:59
|
||||||
|
// 同样需要处理月份天数边界问题
|
||||||
|
const endMonthLastDay = new Date(fullEndYear, endMonth, 0).getDate();
|
||||||
|
const actualEndDay = Math.min(currentDay, endMonthLastDay);
|
||||||
|
const endDate = `${fullEndYear}-${String(endMonth).padStart(2, '0')}-${String(
|
||||||
|
actualEndDay
|
||||||
|
).padStart(2, '0')} 23:59:59`;
|
||||||
|
|
||||||
|
return {
|
||||||
|
startDate,
|
||||||
|
endDate
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const handlePanelChange1 = async data => {
|
||||||
|
console.log('=== 用户交互触发 ===');
|
||||||
|
console.log('当前所有控件状态:', data);
|
||||||
|
// scopeDate: ['26-05', '26-06']
|
||||||
|
// select: "0836000011"
|
||||||
|
if (data.scopeDate && data.scopeDate.length > 0 && data.select) {
|
||||||
|
console.log('参数验证通过,开始加载数据');
|
||||||
|
// 转换日期格式并加载数据
|
||||||
|
const dateRange = convertDateRange(data.scopeDate);
|
||||||
|
if (dateRange) {
|
||||||
|
console.log('日期转换结果:', dateRange);
|
||||||
|
await getChartData(dateRange.startDate, dateRange.endDate, data.select);
|
||||||
|
} else {
|
||||||
|
console.error('日期转换失败');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
loading.value = false;
|
||||||
|
hasData.value = false;
|
||||||
|
// 清空图表数据
|
||||||
|
echartsData.value = [];
|
||||||
|
// 更新图表显示空状态
|
||||||
|
await nextTick();
|
||||||
|
updateChart();
|
||||||
|
console.warn('参数不完整:', {
|
||||||
|
scopeDate: data.scopeDate,
|
||||||
|
select: data.select
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
//获取水温监测下拉框
|
||||||
|
const getselsectData = async () => {
|
||||||
|
let params = {
|
||||||
|
filter: {
|
||||||
|
logic: 'and',
|
||||||
|
filters: [
|
||||||
|
baseid.value != 'all'
|
||||||
|
? {
|
||||||
|
field: 'baseId',
|
||||||
|
operator: 'eq',
|
||||||
|
dataType: 'string',
|
||||||
|
value: baseid.value
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
{
|
||||||
|
field: 'fhstcd',
|
||||||
|
operator: 'isnotnull'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
logic: 'or',
|
||||||
|
filters: [
|
||||||
|
{
|
||||||
|
field: 'sttpCode',
|
||||||
|
operator: 'eq',
|
||||||
|
dataType: 'string',
|
||||||
|
value: 'WTRV'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
].filter(Boolean)
|
||||||
|
},
|
||||||
|
select: ['stcd', 'stnm', 'fhstcd', 'fhstnm', 'sttpCode']
|
||||||
|
};
|
||||||
|
let res = await msstbprptGetKendoList(params);
|
||||||
|
console.log(res);
|
||||||
|
if (!res?.data?.data || res.data.data.length == 0) {
|
||||||
|
select.value.value = '';
|
||||||
|
select.value.options = [];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
select.value.value = res.data.data[0].stcd;
|
||||||
|
select.value.options = res.data.data.map((item: any) => {
|
||||||
|
return {
|
||||||
|
label: item.stnm,
|
||||||
|
value: item.stcd
|
||||||
|
};
|
||||||
|
});
|
||||||
|
//
|
||||||
|
};
|
||||||
|
////
|
||||||
|
// 监听电站变化(优先使用父组件 provide 的电站上下文,兜底 jidiStore)
|
||||||
|
const injectedStation = inject<any>('dianZhanStation', null);
|
||||||
|
const stationSource = computed(
|
||||||
|
() => injectedStation?.value || JidiSelectEventStore.selectedItem
|
||||||
|
);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => stationSource.value,
|
||||||
|
newVal => {
|
||||||
|
baseid.value = newVal.wbsCode;
|
||||||
|
getselsectData();
|
||||||
|
},
|
||||||
|
{ deep: true, immediate: true }
|
||||||
|
);
|
||||||
|
|
||||||
|
// ==================== ppbclTagList 配置 ====================
|
||||||
|
const MODULE_PATH = '@/modules/qixidishuiwenbianhua/index.vue';
|
||||||
|
|
||||||
|
const ppbclConfigMap = inject<
|
||||||
|
Ref<Record<string, { isShow: number; tagValue: string }>>
|
||||||
|
>('ppbclConfigMap', ref({}));
|
||||||
|
|
||||||
|
/** 根据 tagValue 计算默认日期范围(YY-MM 格式) */
|
||||||
|
function getDateRangeFromTagValue(tagValue: string): [string, string] | null {
|
||||||
|
const now = dayjs();
|
||||||
|
let start: dayjs.Dayjs;
|
||||||
|
|
||||||
|
switch (tagValue) {
|
||||||
|
case '7d':
|
||||||
|
start = now.subtract(7, 'day');
|
||||||
|
break;
|
||||||
|
case '1M':
|
||||||
|
start = now.subtract(1, 'month');
|
||||||
|
break;
|
||||||
|
case 'a1M':
|
||||||
|
start = now.subtract(1, 'month').startOf('month');
|
||||||
|
break;
|
||||||
|
case '3M':
|
||||||
|
start = now.subtract(3, 'month');
|
||||||
|
break;
|
||||||
|
case 'a3M':
|
||||||
|
start = now.subtract(3, 'month').startOf('month');
|
||||||
|
break;
|
||||||
|
case '1y':
|
||||||
|
start = now.subtract(1, 'year');
|
||||||
|
break;
|
||||||
|
case '3y':
|
||||||
|
start = now.subtract(3, 'year');
|
||||||
|
break;
|
||||||
|
case '5y':
|
||||||
|
start = now.subtract(5, 'year');
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return [start.format('YYYY-MM'), now.format('YYYY-MM')];
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => ppbclConfigMap?.value?.[MODULE_PATH],
|
||||||
|
config => {
|
||||||
|
if (config) {
|
||||||
|
// isShow 控制时间选择器显隐
|
||||||
|
datetimePicker.value.show = config.isShow !== 0;
|
||||||
|
|
||||||
|
// tagValue 设置默认时间范围
|
||||||
|
if (config.tagValue) {
|
||||||
|
const dateRange = getDateRangeFromTagValue(config.tagValue);
|
||||||
|
if (dateRange) {
|
||||||
|
datetimePicker.value.value = dateRange;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ immediate: true }
|
||||||
|
);
|
||||||
|
|
||||||
|
// ==================== 生命周期钩子 ====================
|
||||||
|
|
||||||
|
// 页面加载时执行
|
||||||
|
onMounted(() => {
|
||||||
|
// 双重等待确保DOM和样式计算完成
|
||||||
|
nextTick(() => {
|
||||||
|
setTimeout(() => {
|
||||||
|
// 校验容器尺寸
|
||||||
|
if (chartRef.value) {
|
||||||
|
const rect = chartRef.value.getBoundingClientRect();
|
||||||
|
if (rect.width > 0 && rect.height > 0) {
|
||||||
|
initChart();
|
||||||
|
// 注释掉模拟数据加载,等待用户交互后加载真实数据
|
||||||
|
// loadData();
|
||||||
|
console.log('✅ 图表初始化完成,等待用户交互加载数据');
|
||||||
|
} else {
|
||||||
|
console.warn('图表容器尺寸为0,启动重试机制');
|
||||||
|
// 指数退避重试
|
||||||
|
let retryCount = 0;
|
||||||
|
const maxRetries = 5;
|
||||||
|
const retryInit = () => {
|
||||||
|
if (retryCount < maxRetries) {
|
||||||
|
retryCount++;
|
||||||
|
setTimeout(() => {
|
||||||
|
const newRect = chartRef.value?.getBoundingClientRect();
|
||||||
|
if (newRect && newRect.width > 0 && newRect.height > 0) {
|
||||||
|
initChart();
|
||||||
|
// loadData();
|
||||||
|
console.log('✅ 图表初始化完成(重试成功)');
|
||||||
|
} else {
|
||||||
|
retryInit();
|
||||||
|
}
|
||||||
|
}, 50 * Math.pow(2, retryCount));
|
||||||
|
} else {
|
||||||
|
console.error('图表容器初始化失败,已达到最大重试次数');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
retryInit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, 50);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// 组件卸载时清理
|
||||||
|
onUnmounted(() => {
|
||||||
|
// 移除事件监听
|
||||||
|
window.removeEventListener('resize', handleResize);
|
||||||
|
|
||||||
|
// 销毁图表实例
|
||||||
|
if (chartInstance) {
|
||||||
|
// 解绑数据点点击事件
|
||||||
|
chartInstance.off('click', handleDataPointClick);
|
||||||
|
chartInstance.dispose();
|
||||||
|
chartInstance = null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.chart-wrapper {
|
||||||
|
width: 100%;
|
||||||
|
height: 231px;
|
||||||
|
min-height: 231px;
|
||||||
|
position: relative;
|
||||||
|
/* 为empty-overlay提供定位上下文 */
|
||||||
|
|
||||||
|
.water-temp-chart {
|
||||||
|
width: 100% !important;
|
||||||
|
height: 100% !important;
|
||||||
|
min-width: 100%;
|
||||||
|
min-height: 231px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-overlay {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background: rgba(255, 255, 255);
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
:deep(.ant-spin-nested-loading > div > .ant-spin) {
|
||||||
|
background: rgba(255, 255, 255) !important;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -123,12 +123,17 @@ const VPInfo = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: '保护对象',
|
label: '保护对象',
|
||||||
value: 'protObj',
|
value: 'protobj',
|
||||||
col: 24
|
col: 24
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: '保护方式',
|
label: '保护方式',
|
||||||
value: 'bhfs',
|
value: 'protmthd',
|
||||||
|
col: 24
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '站址',
|
||||||
|
value: 'stlc',
|
||||||
col: 24
|
col: 24
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
@ -150,7 +155,7 @@ const VAInfo = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: '保护方式',
|
label: '保护方式',
|
||||||
value: 'bhfs',
|
value: 'protmthd',
|
||||||
col: 24
|
col: 24
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
@ -279,7 +284,7 @@ const VDInfo = [
|
|||||||
{
|
{
|
||||||
label: '监控内容类型',
|
label: '监控内容类型',
|
||||||
value: 'sttpName',
|
value: 'sttpName',
|
||||||
col: 12
|
col: 24
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: '所属行政区',
|
label: '所属行政区',
|
||||||
@ -373,8 +378,8 @@ const AIVDInfo = [
|
|||||||
value: 'stlc',
|
value: 'stlc',
|
||||||
col: 24
|
col: 24
|
||||||
}
|
}
|
||||||
]
|
];
|
||||||
////调查断面
|
//调查断面
|
||||||
const WEInfo = [
|
const WEInfo = [
|
||||||
{
|
{
|
||||||
label: '断面名称',
|
label: '断面名称',
|
||||||
@ -390,8 +395,48 @@ const WEInfo = [
|
|||||||
label: '所在河段',
|
label: '所在河段',
|
||||||
value: 'hbrvcdName',
|
value: 'hbrvcdName',
|
||||||
col: 24
|
col: 24
|
||||||
|
}
|
||||||
|
];
|
||||||
|
//野生动物监测
|
||||||
|
const WVAInfo = [
|
||||||
|
{
|
||||||
|
label: '测站名称',
|
||||||
|
value: 'stnm',
|
||||||
|
col: 24
|
||||||
},
|
},
|
||||||
]
|
{
|
||||||
|
label: '测站类型',
|
||||||
|
value: 'sttpName',
|
||||||
|
col: 24
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '监测方式',
|
||||||
|
value: 'mwayName',
|
||||||
|
col: 24
|
||||||
|
}
|
||||||
|
];
|
||||||
|
const FPRDInfo = [
|
||||||
|
{
|
||||||
|
label: '监控点名',
|
||||||
|
value: 'stnm',
|
||||||
|
col: 24
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '监控点类型',
|
||||||
|
value: 'stindx',
|
||||||
|
col: 24
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '监控点地址',
|
||||||
|
value: 'stlc',
|
||||||
|
col: 24
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '所属流域',
|
||||||
|
value: 'hbrvcdName',
|
||||||
|
col: 24
|
||||||
|
}
|
||||||
|
];
|
||||||
export {
|
export {
|
||||||
ENGInfo,
|
ENGInfo,
|
||||||
DWInfo,
|
DWInfo,
|
||||||
@ -405,5 +450,7 @@ export {
|
|||||||
VDInfo,
|
VDInfo,
|
||||||
FHInfo,
|
FHInfo,
|
||||||
AIVDInfo,
|
AIVDInfo,
|
||||||
WEInfo
|
WEInfo,
|
||||||
|
WVAInfo,
|
||||||
|
FPRDInfo
|
||||||
};
|
};
|
||||||
|
|||||||
@ -47,10 +47,12 @@ import {
|
|||||||
WTInfo,
|
WTInfo,
|
||||||
WQInfo,
|
WQInfo,
|
||||||
ZQInfo,
|
ZQInfo,
|
||||||
VDInfo,// 视频
|
VDInfo, // 视频
|
||||||
FHInfo, //鱼类栖息地
|
FHInfo, //鱼类栖息地
|
||||||
AIVDInfo, // AI视频
|
AIVDInfo, // AI视频
|
||||||
WEInfo//调查断面
|
WEInfo, //调查断面
|
||||||
|
WVAInfo, //野生动物监测
|
||||||
|
FPRDInfo //鱼类调查装置
|
||||||
} from './config.js';
|
} from './config.js';
|
||||||
import { getStcdDetail } from '@/api/mapModal';
|
import { getStcdDetail } from '@/api/mapModal';
|
||||||
|
|
||||||
@ -77,8 +79,10 @@ const moduleMap = {
|
|||||||
WQFBInfo: WQInfo,
|
WQFBInfo: WQInfo,
|
||||||
WQFPInfo: WQInfo,
|
WQFPInfo: WQInfo,
|
||||||
FHInfo: FHInfo,
|
FHInfo: FHInfo,
|
||||||
AIVDInfo:AIVDInfo,
|
AIVDInfo: AIVDInfo,
|
||||||
WEInfo:WEInfo,
|
WEInfo: WEInfo,
|
||||||
|
WVAInfo: WVAInfo,
|
||||||
|
FPRDInfo: FPRDInfo
|
||||||
} as const;
|
} as const;
|
||||||
watch(
|
watch(
|
||||||
() => modelStore.selectedAnchorPoint,
|
() => modelStore.selectedAnchorPoint,
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<SidePanelItem title="电站运行过程线" :shrink="false">
|
<SidePanelItem title="电站监测数据" :shrink="false">
|
||||||
<template #title-right-content>
|
<template #title-right-content>
|
||||||
<a-range-picker
|
<a-range-picker
|
||||||
class="w-[220px]"
|
class="w-[220px]"
|
||||||
@ -22,6 +22,19 @@
|
|||||||
class="chart-empty"
|
class="chart-empty"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="monitor-table">
|
||||||
|
<BasicTable
|
||||||
|
ref="tableRef"
|
||||||
|
:scrollY="240"
|
||||||
|
:scrollX="tableScrollX"
|
||||||
|
:columns="tableColumns"
|
||||||
|
:data="tableData"
|
||||||
|
:paginationConfig="{
|
||||||
|
showSizeChanger: false,
|
||||||
|
showQuickJumper: false
|
||||||
|
}"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</a-spin>
|
</a-spin>
|
||||||
</SidePanelItem>
|
</SidePanelItem>
|
||||||
</template>
|
</template>
|
||||||
@ -34,6 +47,7 @@ import { useUiStore } from '@/store/modules/ui';
|
|||||||
import { getMonitorData } from '@/api/mapModal';
|
import { getMonitorData } from '@/api/mapModal';
|
||||||
import { useModelStore } from '@/store/modules/model';
|
import { useModelStore } from '@/store/modules/model';
|
||||||
import SidePanelItem from '@/components/SidePanelItem/index.vue';
|
import SidePanelItem from '@/components/SidePanelItem/index.vue';
|
||||||
|
import BasicTable from '@/components/BasicTable/index.vue';
|
||||||
import { DateSetting } from '@/utils/enumeration';
|
import { DateSetting } from '@/utils/enumeration';
|
||||||
|
|
||||||
const uiStore = useUiStore();
|
const uiStore = useUiStore();
|
||||||
@ -41,6 +55,8 @@ const modelStore = useModelStore();
|
|||||||
|
|
||||||
const isLoading = ref(false);
|
const isLoading = ref(false);
|
||||||
const chartData = ref<any[]>([]);
|
const chartData = ref<any[]>([]);
|
||||||
|
const tableData = ref<any[]>([]);
|
||||||
|
const tableRef = ref<any>();
|
||||||
|
|
||||||
// ==================== 图表 ====================
|
// ==================== 图表 ====================
|
||||||
const chartRef = ref<HTMLElement>();
|
const chartRef = ref<HTMLElement>();
|
||||||
@ -366,6 +382,70 @@ const initDateRange = (): [Dayjs, Dayjs] => {
|
|||||||
};
|
};
|
||||||
const dateRange = ref<[Dayjs, Dayjs] | undefined>(initDateRange());
|
const dateRange = ref<[Dayjs, Dayjs] | undefined>(initDateRange());
|
||||||
|
|
||||||
|
// ==================== 表格 ====================
|
||||||
|
// 空值兜底:null/undefined/空串/'-'占位符/NaN 均显示为 '-'
|
||||||
|
const formatCell = (
|
||||||
|
text: any,
|
||||||
|
formatter: (num: number) => string = num => String(num)
|
||||||
|
) => {
|
||||||
|
if (text === null || text === undefined || text === '' || text === '-') {
|
||||||
|
return '-';
|
||||||
|
}
|
||||||
|
const num = Number(text);
|
||||||
|
if (Number.isNaN(num)) return '-';
|
||||||
|
return formatter(num);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 表格列配置(与 MapModal/MonitorInfo 保持一致)
|
||||||
|
const tableColumns = [
|
||||||
|
{
|
||||||
|
title: '时间',
|
||||||
|
dataIndex: 'tm',
|
||||||
|
width: 150,
|
||||||
|
fixed: 'left',
|
||||||
|
customRender: ({ text }: any) =>
|
||||||
|
text && text !== '-' ? dayjs(text).format('YYYY-MM-DD HH:mm:ss') : '-'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '坝上水位(m)',
|
||||||
|
dataIndex: 'rz',
|
||||||
|
width: 100,
|
||||||
|
customRender: ({ text }: any) => formatCell(text, v => v.toFixed(2))
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '坝下水位(m)',
|
||||||
|
dataIndex: 'dz',
|
||||||
|
width: 100,
|
||||||
|
customRender: ({ text }: any) => formatCell(text, v => v.toFixed(2))
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '入库流量(m³/s)',
|
||||||
|
dataIndex: 'qi',
|
||||||
|
width: 120,
|
||||||
|
customRender: ({ text }: any) =>
|
||||||
|
formatCell(text, v => String(Math.round(v)))
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '出库流量(m³/s)',
|
||||||
|
dataIndex: 'qo',
|
||||||
|
width: 120,
|
||||||
|
customRender: ({ text }: any) => formatCell(text, v => String(v))
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '生态流量(m³/s)',
|
||||||
|
dataIndex: 'qec',
|
||||||
|
width: 120,
|
||||||
|
customRender: ({ text }: any) => formatCell(text, v => v.toFixed(1))
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '生态流量限值(m³/s)',
|
||||||
|
dataIndex: 'qecLimit',
|
||||||
|
width: 140,
|
||||||
|
customRender: ({ text }: any) => formatCell(text, v => v.toFixed(1))
|
||||||
|
}
|
||||||
|
];
|
||||||
|
const tableScrollX = tableColumns.reduce((s, c) => s + (c.width || 100), 0);
|
||||||
|
|
||||||
// 数据请求(查询参数取 modelStore.selectedAnchorPoint)
|
// 数据请求(查询参数取 modelStore.selectedAnchorPoint)
|
||||||
const fetchData = async () => {
|
const fetchData = async () => {
|
||||||
const stcd = modelStore.selectedAnchorPoint?.stcd;
|
const stcd = modelStore.selectedAnchorPoint?.stcd;
|
||||||
@ -404,9 +484,11 @@ const fetchData = async () => {
|
|||||||
const rawData = res?.data?.data || res?.data?.records || [];
|
const rawData = res?.data?.data || res?.data?.records || [];
|
||||||
|
|
||||||
chartData.value = rawData;
|
chartData.value = rawData;
|
||||||
|
tableData.value = [...rawData].reverse();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('获取数据失败:', error);
|
console.error('获取数据失败:', error);
|
||||||
chartData.value = [];
|
chartData.value = [];
|
||||||
|
tableData.value = [];
|
||||||
} finally {
|
} finally {
|
||||||
isLoading.value = false;
|
isLoading.value = false;
|
||||||
}
|
}
|
||||||
@ -462,4 +544,13 @@ watch(
|
|||||||
left: 50%;
|
left: 50%;
|
||||||
transform: translate(-50%, -50%);
|
transform: translate(-50%, -50%);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.monitor-table {
|
||||||
|
margin-top: 12px;
|
||||||
|
border-radius: 2px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
.table-container {
|
||||||
|
border: 1px solid #dcdfe6;
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
128
frontend/src/modules/rightSearchDrawer/speciesIntro/index.vue
Normal file
128
frontend/src/modules/rightSearchDrawer/speciesIntro/index.vue
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
<!--
|
||||||
|
物种介绍(动物/植物介绍)面板:
|
||||||
|
- 面板标题根据当前生态调查菜单动态展示(水生生态调查→鱼类介绍,陆生生态调查→动物介绍)
|
||||||
|
- 右上角下拉框(title-right-content 插槽)选项为上方鱼种/动物种多选框已选中的数据,默认展示第一条
|
||||||
|
- 主体区域只展示当前选中物种:上方图片、下方文字描述
|
||||||
|
- 数据来自 modelStore.selectedFishSpecies(预留 image / intro 字段,后续对接真实数据)
|
||||||
|
-->
|
||||||
|
<template>
|
||||||
|
<SidePanelItem :title="panelTitle" :shrink="false">
|
||||||
|
<template #title-right-content>
|
||||||
|
<a-select
|
||||||
|
v-model:value="currentValue"
|
||||||
|
size="small"
|
||||||
|
style="width: 110px"
|
||||||
|
placeholder="请选择"
|
||||||
|
:options="speciesOptions"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<a-empty v-if="!currentSpecies" description="请先在上方选择物种" />
|
||||||
|
<div v-else class="species-card">
|
||||||
|
<!-- 上方:图片 -->
|
||||||
|
<a-image
|
||||||
|
v-if="currentSpecies.image"
|
||||||
|
class="species-image"
|
||||||
|
:src="currentSpecies.image"
|
||||||
|
:preview="false"
|
||||||
|
/>
|
||||||
|
<div v-else class="species-image species-image--empty">
|
||||||
|
<span>暂无图片</span>
|
||||||
|
</div>
|
||||||
|
<!-- 下方:文字描述 -->
|
||||||
|
<div class="species-info">
|
||||||
|
<div class="species-name">{{ currentSpecies.label }}</div>
|
||||||
|
<div class="species-desc">
|
||||||
|
{{ currentSpecies.intro || '暂无描述' }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</SidePanelItem>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { computed, ref, watch } from 'vue';
|
||||||
|
import { useRoute } from 'vue-router';
|
||||||
|
import { useModelStore } from '@/store/modules/model';
|
||||||
|
import SidePanelItem from '@/components/SidePanelItem/index.vue';
|
||||||
|
|
||||||
|
const modelStore = useModelStore();
|
||||||
|
const route = useRoute();
|
||||||
|
|
||||||
|
// 面板标题:根据当前菜单展示对应介绍(水生→鱼类,陆生→动物)
|
||||||
|
const panelTitle = computed(() => {
|
||||||
|
if (route.path.includes('shengTaiDiaoCha/shuiShengShengTaiDiaoCha')) {
|
||||||
|
return '鱼类介绍';
|
||||||
|
}
|
||||||
|
if (route.path.includes('shengTaiDiaoCha/luShengShengTaiDiaoCha')) {
|
||||||
|
return '动物介绍';
|
||||||
|
}
|
||||||
|
return '物种介绍';
|
||||||
|
});
|
||||||
|
|
||||||
|
// 右上角下拉框选项 = 上方多选框已选中的物种
|
||||||
|
const speciesOptions = computed(() =>
|
||||||
|
(modelStore.selectedFishSpecies || []).map(item => ({
|
||||||
|
label: item.label,
|
||||||
|
value: item.value
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
|
||||||
|
const currentValue = ref<string | undefined>();
|
||||||
|
|
||||||
|
// 当前展示的物种(默认第一条)
|
||||||
|
const currentSpecies = computed(() =>
|
||||||
|
speciesOptions.value.find(item => item.value === currentValue.value)
|
||||||
|
);
|
||||||
|
|
||||||
|
// 已选物种变化时,默认选中第一条
|
||||||
|
watch(
|
||||||
|
() => modelStore.selectedFishSpecies,
|
||||||
|
list => {
|
||||||
|
currentValue.value = (list || [])[0]?.value;
|
||||||
|
},
|
||||||
|
{ immediate: true, deep: true }
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.species-card {
|
||||||
|
border: 1px solid #e5edf3;
|
||||||
|
border-radius: 4px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.species-image {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
height: 200px;
|
||||||
|
object-fit: cover;
|
||||||
|
background: #f5f5f5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.species-image--empty {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
color: #bfbfbf;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.species-info {
|
||||||
|
padding: 10px 12px 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.species-name {
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #2f6b98;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.species-desc {
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 22px;
|
||||||
|
color: #595959;
|
||||||
|
word-break: break-all;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
494
frontend/src/modules/shengTaiHuangJingBuMod/index.vue
Normal file
494
frontend/src/modules/shengTaiHuangJingBuMod/index.vue
Normal file
@ -0,0 +1,494 @@
|
|||||||
|
<!-- SidePanelItem.vue -->
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<SidePanelItem title="生态环境部门要求生态流量达标情况">
|
||||||
|
<!-- <div class="body_topOne">
|
||||||
|
<a-radio-group v-model:value="mode" @change="dataChage">
|
||||||
|
<a-radio-button value="hour">逐时</a-radio-button>
|
||||||
|
<a-radio-button value="day">日</a-radio-button>
|
||||||
|
</a-radio-group>
|
||||||
|
</div> -->
|
||||||
|
<div class="body_title">逐日</div>
|
||||||
|
<div class="body_main">
|
||||||
|
<div class="body_div" v-for="(i, index) in dataArr" :key="index">
|
||||||
|
<div class="body_div_icon">
|
||||||
|
<img :src="i.icon" alt="" />
|
||||||
|
</div>
|
||||||
|
<div class="body_div_text">{{ i.qecCnt }}</div>
|
||||||
|
<div class="body_div_text_sub">
|
||||||
|
{{ i.text }}
|
||||||
|
<span> (座) </span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="body_title">逐时</div>
|
||||||
|
<div class="body_main">
|
||||||
|
<div class="body_div" v-for="(i, index) in dataArr" :key="index">
|
||||||
|
<div class="body_div_icon">
|
||||||
|
<img :src="i.icon" alt="" />
|
||||||
|
</div>
|
||||||
|
<div class="body_div_text">{{ i.qecCnt }}</div>
|
||||||
|
<div class="body_div_text_sub">
|
||||||
|
{{ i.text }}
|
||||||
|
<span> (座) </span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- <a-spin :spinning="spinning">
|
||||||
|
<div>
|
||||||
|
<div v-for="el in datalist" @click="handleBarClick(el)">
|
||||||
|
<div :key="el.key">
|
||||||
|
<div class="boy_one">
|
||||||
|
<div style="flex: 1; white-space: nowrap; margin-right: 5px">
|
||||||
|
{{ el.name }}
|
||||||
|
</div>
|
||||||
|
<img style="flex: 1" src="@/assets/components/fgx.svg" alt="" />
|
||||||
|
</div>
|
||||||
|
<div class="body_zhu">
|
||||||
|
<div class="body_biao">
|
||||||
|
<div v-for="value in allArr">
|
||||||
|
<div :key="value" class="nei_body">
|
||||||
|
<div
|
||||||
|
v-if="
|
||||||
|
value <
|
||||||
|
(el?.key == null
|
||||||
|
? 1
|
||||||
|
: el.qecTCnt == 0
|
||||||
|
? 0
|
||||||
|
: el.qecCnt / el.qecTCnt) *
|
||||||
|
20
|
||||||
|
"
|
||||||
|
class="little_body"
|
||||||
|
:style="{ backgroundColor: el.color }"
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="body_text">{{ el.qecCnt }}/{{ el.qecTCnt }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a-spin> -->
|
||||||
|
</SidePanelItem>
|
||||||
|
|
||||||
|
<!-- 自定义弹框 -->
|
||||||
|
<a-modal
|
||||||
|
v-model:open="modalVisible"
|
||||||
|
:title="'生态流量达标情况'"
|
||||||
|
width="1536px"
|
||||||
|
:footer="null"
|
||||||
|
>
|
||||||
|
<STLLXFFS
|
||||||
|
v-if="modalVisible"
|
||||||
|
:options="list"
|
||||||
|
:typeKey="selectedItem.key"
|
||||||
|
:time="mode"
|
||||||
|
:basicId="baseid"
|
||||||
|
:dateArr="dateArr"
|
||||||
|
/>
|
||||||
|
</a-modal>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { ref, onMounted, watch, inject, computed, defineOptions } from 'vue';
|
||||||
|
import SidePanelItem from '@/components/SidePanelItem/index.vue';
|
||||||
|
import {
|
||||||
|
qgcetQgcStaticData,
|
||||||
|
evnmAutoMonitorGetKendoListCust
|
||||||
|
} from '@/api/stll';
|
||||||
|
import { useJidiSelectEventStore } from '@/store/modules/jidiSelectEvent';
|
||||||
|
import { useMapViewStore } from '@/modules/map/stores/map-view.store';
|
||||||
|
import STLLXFFS from '@/modules/shengtaidabiaoMod/TwoLayer/ShengTaiLiuLiangDaBQKTwoLayer.vue';
|
||||||
|
import { useDraggable } from '@/utils/drag';
|
||||||
|
import icon from '@/assets/icons/client.svg';
|
||||||
|
// 定义组件名(便于调试和递归)
|
||||||
|
defineOptions({
|
||||||
|
name: 'shengtaidabiaoMod'
|
||||||
|
});
|
||||||
|
const JidiSelectEventStore = useJidiSelectEventStore();
|
||||||
|
const mapViewStore = useMapViewStore();
|
||||||
|
const baseid = ref('');
|
||||||
|
const dateArr = ref<any[]>([]);
|
||||||
|
|
||||||
|
const mode = ref('hour');
|
||||||
|
const dataArr: any = ref([
|
||||||
|
{
|
||||||
|
qecCnt: '0', // 座数
|
||||||
|
qecTCnt: '0', //总座数
|
||||||
|
icon: icon,
|
||||||
|
text: '达标',
|
||||||
|
type: 1,
|
||||||
|
key: 'qecInterval'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: icon,
|
||||||
|
qecCnt: '0', // 座数
|
||||||
|
qecTCnt: '0', //总座数
|
||||||
|
text: '不达标',
|
||||||
|
type: 1,
|
||||||
|
key: 'qecInterval'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: icon,
|
||||||
|
qecCnt: '0', // 座数
|
||||||
|
qecTCnt: '0', //总座数
|
||||||
|
text: '无数据',
|
||||||
|
type: 1,
|
||||||
|
key: 'qecInterval'
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
// const dataArr: any = ref([
|
||||||
|
// {
|
||||||
|
// name: '生态环境部门要求',
|
||||||
|
// qecCnt: '0', // 座数
|
||||||
|
// qecTCnt: '0', //总座数
|
||||||
|
// type: 1,
|
||||||
|
// key: 'qecInterval'
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// name: '水利部门要求',
|
||||||
|
// qecCnt: '0', // 座数
|
||||||
|
// qecTCnt: '0', //总座数
|
||||||
|
// type: 2,
|
||||||
|
// key: 'mwrInterval'
|
||||||
|
// }
|
||||||
|
// ]);
|
||||||
|
const list: any = ref([
|
||||||
|
{ name: '生态环境部门要求', type: 1, color: '#77C300', key: 'qecInterval' },
|
||||||
|
{ name: '水利部门要求', type: 2, color: '#56C3E3', key: 'mwrInterval' },
|
||||||
|
{ name: '多年平均流量 10%', type: 3, color: '#F76B01', key: 'avqInterval' },
|
||||||
|
{ name: '无生态流量要求', type: 4, color: '#B4B4B4', key: null }
|
||||||
|
]);
|
||||||
|
const datalist: any = ref([]);
|
||||||
|
const allArr: any = ref(Array.from({ length: 20 }, (_, i) => i));
|
||||||
|
const spinning = ref(false);
|
||||||
|
const modalVisible = ref(false);
|
||||||
|
const selectedItem = ref<any>(null);
|
||||||
|
|
||||||
|
// 启用弹窗拖拽(限制在可视区域内)
|
||||||
|
useDraggable(modalVisible, { boundary: true, resetOnOpen: true });
|
||||||
|
|
||||||
|
// 处理进度条点击事件
|
||||||
|
const handleBarClick = (item: any) => {
|
||||||
|
selectedItem.value = item;
|
||||||
|
modalVisible.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 根据 type 将 list 中的数据合并到 dataArr 中
|
||||||
|
const setStyle = () => {
|
||||||
|
if (dataArr.value.length == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
dataArr.value.forEach((item: any) => {
|
||||||
|
// 在 list 中找到 type 匹配的数据
|
||||||
|
const matched = list.value.find(
|
||||||
|
(listItem: any) => listItem.type === item.type
|
||||||
|
);
|
||||||
|
if (matched) {
|
||||||
|
// 合并数据,dataArr 的属性优先
|
||||||
|
datalist.value.push({
|
||||||
|
...matched,
|
||||||
|
...item
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
console.log(datalist.value);
|
||||||
|
};
|
||||||
|
const dataChage = () => {
|
||||||
|
getData();
|
||||||
|
};
|
||||||
|
const getdate = (offsetYears = 0) => {
|
||||||
|
const formatDate = (date: Date) => {
|
||||||
|
const year = date.getFullYear();
|
||||||
|
const month = String(date.getMonth() + 1).padStart(2, '0');
|
||||||
|
const day = String(date.getDate()).padStart(2, '0');
|
||||||
|
return `${year}-${month}-${day}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
let startDate: string;
|
||||||
|
let endDate: string;
|
||||||
|
|
||||||
|
if (dateArr.value && dateArr.value.length === 2) {
|
||||||
|
const start = new Date(dateArr.value[0]);
|
||||||
|
const end = new Date(dateArr.value[1]);
|
||||||
|
if (offsetYears !== 0) {
|
||||||
|
start.setFullYear(start.getFullYear() + offsetYears);
|
||||||
|
end.setFullYear(end.getFullYear() + offsetYears);
|
||||||
|
}
|
||||||
|
startDate = `${formatDate(start)} 00:00:00`;
|
||||||
|
endDate = `${formatDate(end)} 23:59:59`;
|
||||||
|
} else {
|
||||||
|
const now = new Date();
|
||||||
|
const oneMonthAgo = new Date(now);
|
||||||
|
oneMonthAgo.setMonth(oneMonthAgo.getMonth() - 1);
|
||||||
|
if (offsetYears !== 0) {
|
||||||
|
now.setFullYear(now.getFullYear() + offsetYears);
|
||||||
|
oneMonthAgo.setFullYear(oneMonthAgo.getFullYear() + offsetYears);
|
||||||
|
}
|
||||||
|
startDate = `${formatDate(oneMonthAgo)} 00:00:00`;
|
||||||
|
endDate = `${formatDate(now)} 23:59:59`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return { startDate, endDate };
|
||||||
|
};
|
||||||
|
|
||||||
|
const getData = async () => {
|
||||||
|
const { startDate, endDate } = getdate(0);
|
||||||
|
|
||||||
|
let params = {
|
||||||
|
filter: {
|
||||||
|
logic: 'and',
|
||||||
|
filters: [
|
||||||
|
baseid.value != 'all'
|
||||||
|
? {
|
||||||
|
field: 'baseId',
|
||||||
|
operator: 'eq',
|
||||||
|
dataType: 'string',
|
||||||
|
value: baseid.value
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
{
|
||||||
|
field: 'dtin',
|
||||||
|
operator: 'eq',
|
||||||
|
dataType: 'string',
|
||||||
|
value: '1'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'type',
|
||||||
|
operator: 'eq',
|
||||||
|
dataType: 'string',
|
||||||
|
value: mode.value
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'tm',
|
||||||
|
operator: 'gte',
|
||||||
|
dataType: 'date',
|
||||||
|
value: startDate
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'tm',
|
||||||
|
operator: 'lte',
|
||||||
|
dataType: 'date',
|
||||||
|
value: endDate
|
||||||
|
}
|
||||||
|
].filter(Boolean)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
spinning.value = true;
|
||||||
|
try {
|
||||||
|
let res = await qgcetQgcStaticData(params);
|
||||||
|
// 根据API响应结构规范处理数据
|
||||||
|
if (res.data && res.data.list) {
|
||||||
|
const apiList = res.data.list;
|
||||||
|
//
|
||||||
|
// 只使用前两条数据(type: 1 和 type: 2)
|
||||||
|
// if (apiList && apiList.length > 0) {
|
||||||
|
// dataArr.value = apiList.slice(0, 2).map((item: any) => ({
|
||||||
|
// // name: item.typeName || item.desc,
|
||||||
|
// qecCnt: item.qecCnt,
|
||||||
|
// qecTCnt: item.qecTCnt,
|
||||||
|
// type: item.type
|
||||||
|
// }));
|
||||||
|
|
||||||
|
// // 清空旧数据并重新合并样式
|
||||||
|
// datalist.value = [];
|
||||||
|
// setStyle();
|
||||||
|
// } else {
|
||||||
|
// // 无数据场景处理
|
||||||
|
// dataArr.value = [];
|
||||||
|
// datalist.value = [];
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取生态流量达标数据失败:', error);
|
||||||
|
} finally {
|
||||||
|
spinning.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// 监听电站变化(优先使用父组件 provide 的电站上下文,兜底 jidiStore)
|
||||||
|
const injectedStation = inject<any>('dianZhanStation', null);
|
||||||
|
const stationSource = computed(
|
||||||
|
() => injectedStation?.value || JidiSelectEventStore.selectedItem
|
||||||
|
);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => stationSource.value,
|
||||||
|
newVal => {
|
||||||
|
if (newVal && newVal.wbsCode) {
|
||||||
|
baseid.value = newVal.wbsCode;
|
||||||
|
getData();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ deep: true, immediate: true }
|
||||||
|
);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => mapViewStore.searchTimeRange,
|
||||||
|
newVal => {
|
||||||
|
dateArr.value = newVal;
|
||||||
|
getData();
|
||||||
|
},
|
||||||
|
{ deep: true }
|
||||||
|
);
|
||||||
|
|
||||||
|
//统计电站范围:接入过生态流量数据的电站
|
||||||
|
const titleData: any = ref({ cnt: 0 });
|
||||||
|
const getcont = async () => {
|
||||||
|
let params = {
|
||||||
|
filter: {
|
||||||
|
logic: 'and',
|
||||||
|
filters: [
|
||||||
|
{
|
||||||
|
field: 'showIds',
|
||||||
|
operator: 'in',
|
||||||
|
value: ['7']
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let res = await evnmAutoMonitorGetKendoListCust(params);
|
||||||
|
let list = res?.data?.data[0];
|
||||||
|
if (list) {
|
||||||
|
titleData.value = list;
|
||||||
|
}
|
||||||
|
//
|
||||||
|
};
|
||||||
|
// 页面加载时执行的逻辑
|
||||||
|
onMounted(() => {
|
||||||
|
setStyle();
|
||||||
|
getcont();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.body_topOne {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.boy_one {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin: 5px 0px;
|
||||||
|
}
|
||||||
|
.body_title {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: bold;
|
||||||
|
padding-left: 10px;
|
||||||
|
margin-left: 4px;
|
||||||
|
position: relative;
|
||||||
|
&::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 4px;
|
||||||
|
left: 0;
|
||||||
|
width: 4px;
|
||||||
|
height: 14px;
|
||||||
|
background-color: #2f6b98;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.body_main {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
padding: 6px 10px;
|
||||||
|
justify-content: space-between;
|
||||||
|
.body_div {
|
||||||
|
width: 33%;
|
||||||
|
height: 100%;
|
||||||
|
padding: 10px;
|
||||||
|
margin: 10px 0;
|
||||||
|
background-color: #f3f5fa;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
cursor: pointer;
|
||||||
|
border-radius: 4px;
|
||||||
|
.body_div_icon {
|
||||||
|
width: 30px;
|
||||||
|
height: 30px;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
.body_div_text {
|
||||||
|
font-size: 28px;
|
||||||
|
font-weight: 500;
|
||||||
|
margin: 10px 0;
|
||||||
|
}
|
||||||
|
.body_div_text_sub {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 400;
|
||||||
|
span {
|
||||||
|
color: #949494;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.body_zhu {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
|
||||||
|
.body_biao {
|
||||||
|
width: 100%;
|
||||||
|
height: 52px;
|
||||||
|
border: 1px solid #ccdae7;
|
||||||
|
border-radius: 2px;
|
||||||
|
display: flex;
|
||||||
|
padding: 5px 1px;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
.nei_body {
|
||||||
|
width: 12px;
|
||||||
|
height: 100%;
|
||||||
|
border: 1px solid #ccdae7;
|
||||||
|
padding: 2px;
|
||||||
|
margin-left: 4px;
|
||||||
|
|
||||||
|
.little_body {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.body_text {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
width: 90px;
|
||||||
|
height: 100%;
|
||||||
|
justify-content: space-around;
|
||||||
|
margin-left: 6px;
|
||||||
|
align-items: center;
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-radio-group {
|
||||||
|
// border: 3px solid #2f6b98 !important;
|
||||||
|
// border-radius: 10px !important;
|
||||||
|
|
||||||
|
.ant-radio-button-wrapper-checked {
|
||||||
|
border: 1px solid #2f6b98 !important;
|
||||||
|
background-color: #2f6b98 !important;
|
||||||
|
color: #fff !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-radio-button-wrapper {
|
||||||
|
border: 2px solid #2f6b98 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-radio-button-wrapper-checked :before {
|
||||||
|
background-color: #2f6b98 !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -303,7 +303,9 @@ const getData = async () => {
|
|||||||
};
|
};
|
||||||
// 监听电站变化(优先使用父组件 provide 的电站上下文,兜底 jidiStore)
|
// 监听电站变化(优先使用父组件 provide 的电站上下文,兜底 jidiStore)
|
||||||
const injectedStation = inject<any>('dianZhanStation', null);
|
const injectedStation = inject<any>('dianZhanStation', null);
|
||||||
const stationSource = computed(() => injectedStation?.value || JidiSelectEventStore.selectedItem);
|
const stationSource = computed(
|
||||||
|
() => injectedStation?.value || JidiSelectEventStore.selectedItem
|
||||||
|
);
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => stationSource.value,
|
() => stationSource.value,
|
||||||
@ -318,8 +320,7 @@ watch(
|
|||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => mapViewStore.searchTimeRange,
|
() => mapViewStore.searchTimeRange,
|
||||||
(newVal) => {
|
newVal => {
|
||||||
|
|
||||||
dateArr.value = newVal;
|
dateArr.value = newVal;
|
||||||
getData();
|
getData();
|
||||||
},
|
},
|
||||||
|
|||||||
@ -65,7 +65,7 @@ import SidePanelItem from '@/components/SidePanelItem/index.vue';
|
|||||||
import * as echarts from 'echarts';
|
import * as echarts from 'echarts';
|
||||||
import { eqqecRateCount, evnmAutoMonitorGetKendoListCust } from '@/api/stll';
|
import { eqqecRateCount, evnmAutoMonitorGetKendoListCust } from '@/api/stll';
|
||||||
import ModalYkzhbzdjcgz from '../shengtaidabiaoMod/TwoLayer/ModalYkzhbzdjcgz.vue';
|
import ModalYkzhbzdjcgz from '../shengtaidabiaoMod/TwoLayer/ModalYkzhbzdjcgz.vue';
|
||||||
import STLLXFFS from './TwoLayer/STLLXFFS.vue'
|
import STLLXFFS from './TwoLayer/STLLXFFS.vue';
|
||||||
import { useMapViewStore } from '@/modules/map/stores/map-view.store';
|
import { useMapViewStore } from '@/modules/map/stores/map-view.store';
|
||||||
import { useDraggable } from '@/utils/drag';
|
import { useDraggable } from '@/utils/drag';
|
||||||
|
|
||||||
@ -321,13 +321,17 @@ const handleChartClick = (params: any) => {
|
|||||||
: `生态流量达标率(按调节性能统计)`;
|
: `生态流量达标率(按调节性能统计)`;
|
||||||
|
|
||||||
// 获取当前点击项的详细数据
|
// 获取当前点击项的详细数据
|
||||||
const baseId = mode.value === 'top' ? chartData.value.baseIds[currentIndex] : undefined;
|
const baseId =
|
||||||
const qecPerformance = mode.value === 'left' ? chartData.value.qecPerformances[currentIndex] : undefined;
|
mode.value === 'top' ? chartData.value.baseIds[currentIndex] : undefined;
|
||||||
|
const qecPerformance =
|
||||||
|
mode.value === 'left'
|
||||||
|
? chartData.value.qecPerformances[currentIndex]
|
||||||
|
: undefined;
|
||||||
//
|
//
|
||||||
|
|
||||||
const currentItem = {
|
const currentItem = {
|
||||||
baseId: baseId,
|
baseId: baseId,
|
||||||
baseName: mode.value === 'top' ? categoryName :'',
|
baseName: mode.value === 'top' ? categoryName : '',
|
||||||
qecPerformance: qecPerformance,
|
qecPerformance: qecPerformance,
|
||||||
qecPerformanceName: mode.value === 'left' ? categoryName : '',
|
qecPerformanceName: mode.value === 'left' ? categoryName : '',
|
||||||
qecRate: chartData.value.currentData[currentIndex],
|
qecRate: chartData.value.currentData[currentIndex],
|
||||||
@ -353,7 +357,6 @@ const getdate = (offsetYears = 0) => {
|
|||||||
|
|
||||||
// 如果 dateArr.value 有数据,使用数组中的日期
|
// 如果 dateArr.value 有数据,使用数组中的日期
|
||||||
if (dateArr.value && dateArr.value.length === 2) {
|
if (dateArr.value && dateArr.value.length === 2) {
|
||||||
|
|
||||||
const start = new Date(dateArr.value[0]);
|
const start = new Date(dateArr.value[0]);
|
||||||
const end = new Date(dateArr.value[1]);
|
const end = new Date(dateArr.value[1]);
|
||||||
|
|
||||||
@ -475,9 +478,15 @@ const getEcharts = async () => {
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
// 处理当前数据
|
// 处理当前数据
|
||||||
const currentApiData = (currentRes && currentRes.data && currentRes.data.data) ? currentRes.data.data : [];
|
const currentApiData =
|
||||||
|
currentRes && currentRes.data && currentRes.data.data
|
||||||
|
? currentRes.data.data
|
||||||
|
: [];
|
||||||
// 处理去年同期数据
|
// 处理去年同期数据
|
||||||
const lastYearApiData = (lastYearRes && lastYearRes.data && lastYearRes.data) ? lastYearRes.data.data : [];
|
const lastYearApiData =
|
||||||
|
lastYearRes && lastYearRes.data && lastYearRes.data
|
||||||
|
? lastYearRes.data.data
|
||||||
|
: [];
|
||||||
|
|
||||||
// 根据 mode 提取不同的数据
|
// 根据 mode 提取不同的数据
|
||||||
if (mode.value === 'top') {
|
if (mode.value === 'top') {
|
||||||
@ -523,7 +532,9 @@ const getEcharts = async () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
chartData.value = {
|
chartData.value = {
|
||||||
categories: currentApiData.map((item: any) => item.qecPerformanceName || ''),
|
categories: currentApiData.map(
|
||||||
|
(item: any) => item.qecPerformanceName || ''
|
||||||
|
),
|
||||||
currentData: currentApiData.map((item: any) =>
|
currentData: currentApiData.map((item: any) =>
|
||||||
item.qecRate != null ? Number(item.qecRate) : 0
|
item.qecRate != null ? Number(item.qecRate) : 0
|
||||||
),
|
),
|
||||||
@ -532,7 +543,9 @@ const getEcharts = async () => {
|
|||||||
return lastYearRate != null ? Number(lastYearRate) : 0;
|
return lastYearRate != null ? Number(lastYearRate) : 0;
|
||||||
}),
|
}),
|
||||||
baseIds: [],
|
baseIds: [],
|
||||||
qecPerformances: currentApiData.map((item: any) => item.qecPerformance || '')
|
qecPerformances: currentApiData.map(
|
||||||
|
(item: any) => item.qecPerformance || ''
|
||||||
|
)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -593,7 +606,7 @@ watch(
|
|||||||
() => mapViewStore.searchTimeRange,
|
() => mapViewStore.searchTimeRange,
|
||||||
(newVal, oldVal) => {
|
(newVal, oldVal) => {
|
||||||
console.log('时间范围变化:', oldVal, '->', newVal);
|
console.log('时间范围变化:', oldVal, '->', newVal);
|
||||||
dateArr.value = newVal
|
dateArr.value = newVal;
|
||||||
loadData();
|
loadData();
|
||||||
// 处理你的逻辑
|
// 处理你的逻辑
|
||||||
},
|
},
|
||||||
|
|||||||
493
frontend/src/modules/shuiLiBuMod/index.vue
Normal file
493
frontend/src/modules/shuiLiBuMod/index.vue
Normal file
@ -0,0 +1,493 @@
|
|||||||
|
<!-- SidePanelItem.vue -->
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<SidePanelItem title="水利部门要求生态流量达标情况">
|
||||||
|
<!-- <div class="body_topOne">
|
||||||
|
<a-radio-group v-model:value="mode" @change="dataChage">
|
||||||
|
<a-radio-button value="hour">逐时</a-radio-button>
|
||||||
|
<a-radio-button value="day">日</a-radio-button>
|
||||||
|
</a-radio-group>
|
||||||
|
</div> -->
|
||||||
|
<div class="body_title">逐日</div>
|
||||||
|
<div class="body_main">
|
||||||
|
<div class="body_div" v-for="(i, index) in dataArr" :key="index">
|
||||||
|
<div class="body_div_icon">
|
||||||
|
<img :src="i.icon" alt="" />
|
||||||
|
</div>
|
||||||
|
<div class="body_div_text">{{ i.qecCnt }}</div>
|
||||||
|
<div class="body_div_text_sub">
|
||||||
|
{{ i.text }}
|
||||||
|
<span> (座) </span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="body_title">逐时</div>
|
||||||
|
<div class="body_main">
|
||||||
|
<div class="body_div" v-for="(i, index) in dataArr" :key="index">
|
||||||
|
<div class="body_div_icon">
|
||||||
|
<img :src="i.icon" alt="" />
|
||||||
|
</div>
|
||||||
|
<div class="body_div_text">{{ i.qecCnt }}</div>
|
||||||
|
<div class="body_div_text_sub">
|
||||||
|
{{ i.text }}
|
||||||
|
<span> (座) </span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- <a-spin :spinning="spinning">
|
||||||
|
<div>
|
||||||
|
<div v-for="el in datalist" @click="handleBarClick(el)">
|
||||||
|
<div :key="el.key">
|
||||||
|
<div class="boy_one">
|
||||||
|
<div style="flex: 1; white-space: nowrap; margin-right: 5px">
|
||||||
|
{{ el.name }}
|
||||||
|
</div>
|
||||||
|
<img style="flex: 1" src="@/assets/components/fgx.svg" alt="" />
|
||||||
|
</div>
|
||||||
|
<div class="body_zhu">
|
||||||
|
<div class="body_biao">
|
||||||
|
<div v-for="value in allArr">
|
||||||
|
<div :key="value" class="nei_body">
|
||||||
|
<div
|
||||||
|
v-if="
|
||||||
|
value <
|
||||||
|
(el?.key == null
|
||||||
|
? 1
|
||||||
|
: el.qecTCnt == 0
|
||||||
|
? 0
|
||||||
|
: el.qecCnt / el.qecTCnt) *
|
||||||
|
20
|
||||||
|
"
|
||||||
|
class="little_body"
|
||||||
|
:style="{ backgroundColor: el.color }"
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="body_text">{{ el.qecCnt }}/{{ el.qecTCnt }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a-spin> -->
|
||||||
|
</SidePanelItem>
|
||||||
|
|
||||||
|
<!-- 自定义弹框 -->
|
||||||
|
<a-modal
|
||||||
|
v-model:open="modalVisible"
|
||||||
|
:title="'生态流量达标情况'"
|
||||||
|
width="1536px"
|
||||||
|
:footer="null"
|
||||||
|
>
|
||||||
|
<STLLXFFS
|
||||||
|
v-if="modalVisible"
|
||||||
|
:options="list"
|
||||||
|
:typeKey="selectedItem.key"
|
||||||
|
:time="mode"
|
||||||
|
:basicId="baseid"
|
||||||
|
:dateArr="dateArr"
|
||||||
|
/>
|
||||||
|
</a-modal>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { ref, onMounted, watch, inject, computed, defineOptions } from 'vue';
|
||||||
|
import SidePanelItem from '@/components/SidePanelItem/index.vue';
|
||||||
|
import {
|
||||||
|
qgcetQgcStaticData,
|
||||||
|
evnmAutoMonitorGetKendoListCust
|
||||||
|
} from '@/api/stll';
|
||||||
|
import { useJidiSelectEventStore } from '@/store/modules/jidiSelectEvent';
|
||||||
|
import { useMapViewStore } from '@/modules/map/stores/map-view.store';
|
||||||
|
import STLLXFFS from '@/modules/shengtaidabiaoMod/TwoLayer/ShengTaiLiuLiangDaBQKTwoLayer.vue';
|
||||||
|
import { useDraggable } from '@/utils/drag';
|
||||||
|
import icon from '@/assets/icons/client.svg';
|
||||||
|
// 定义组件名(便于调试和递归)
|
||||||
|
defineOptions({
|
||||||
|
name: 'shengtaidabiaoMod'
|
||||||
|
});
|
||||||
|
const JidiSelectEventStore = useJidiSelectEventStore();
|
||||||
|
const mapViewStore = useMapViewStore();
|
||||||
|
const baseid = ref('');
|
||||||
|
const dateArr = ref<any[]>([]);
|
||||||
|
|
||||||
|
const mode = ref('hour');
|
||||||
|
const dataArr: any = ref([
|
||||||
|
{
|
||||||
|
qecCnt: '0', // 座数
|
||||||
|
qecTCnt: '0', //总座数
|
||||||
|
icon: icon,
|
||||||
|
text: '达标',
|
||||||
|
type: 1,
|
||||||
|
key: 'qecInterval'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: icon,
|
||||||
|
qecCnt: '0', // 座数
|
||||||
|
qecTCnt: '0', //总座数
|
||||||
|
text: '不达标',
|
||||||
|
type: 1,
|
||||||
|
key: 'qecInterval'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: icon,
|
||||||
|
qecCnt: '0', // 座数
|
||||||
|
qecTCnt: '0', //总座数
|
||||||
|
text: '无数据',
|
||||||
|
type: 1,
|
||||||
|
key: 'qecInterval'
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
// const dataArr: any = ref([
|
||||||
|
// {
|
||||||
|
// name: '生态环境部门要求',
|
||||||
|
// qecCnt: '0', // 座数
|
||||||
|
// qecTCnt: '0', //总座数
|
||||||
|
// type: 1,
|
||||||
|
// key: 'qecInterval'
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// name: '水利部门要求',
|
||||||
|
// qecCnt: '0', // 座数
|
||||||
|
// qecTCnt: '0', //总座数
|
||||||
|
// type: 2,
|
||||||
|
// key: 'mwrInterval'
|
||||||
|
// }
|
||||||
|
// ]);
|
||||||
|
const list: any = ref([
|
||||||
|
{ name: '生态环境部门要求', type: 1, color: '#77C300', key: 'qecInterval' },
|
||||||
|
{ name: '水利部门要求', type: 2, color: '#56C3E3', key: 'mwrInterval' },
|
||||||
|
{ name: '多年平均流量 10%', type: 3, color: '#F76B01', key: 'avqInterval' },
|
||||||
|
{ name: '无生态流量要求', type: 4, color: '#B4B4B4', key: null }
|
||||||
|
]);
|
||||||
|
const datalist: any = ref([]);
|
||||||
|
const allArr: any = ref(Array.from({ length: 20 }, (_, i) => i));
|
||||||
|
const spinning = ref(false);
|
||||||
|
const modalVisible = ref(false);
|
||||||
|
const selectedItem = ref<any>(null);
|
||||||
|
|
||||||
|
// 启用弹窗拖拽(限制在可视区域内)
|
||||||
|
useDraggable(modalVisible, { boundary: true, resetOnOpen: true });
|
||||||
|
|
||||||
|
// 处理进度条点击事件
|
||||||
|
const handleBarClick = (item: any) => {
|
||||||
|
selectedItem.value = item;
|
||||||
|
modalVisible.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 根据 type 将 list 中的数据合并到 dataArr 中
|
||||||
|
const setStyle = () => {
|
||||||
|
if (dataArr.value.length == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
dataArr.value.forEach((item: any) => {
|
||||||
|
// 在 list 中找到 type 匹配的数据
|
||||||
|
const matched = list.value.find(
|
||||||
|
(listItem: any) => listItem.type === item.type
|
||||||
|
);
|
||||||
|
if (matched) {
|
||||||
|
// 合并数据,dataArr 的属性优先
|
||||||
|
datalist.value.push({
|
||||||
|
...matched,
|
||||||
|
...item
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
console.log(datalist.value);
|
||||||
|
};
|
||||||
|
const dataChage = () => {
|
||||||
|
getData();
|
||||||
|
};
|
||||||
|
const getdate = (offsetYears = 0) => {
|
||||||
|
const formatDate = (date: Date) => {
|
||||||
|
const year = date.getFullYear();
|
||||||
|
const month = String(date.getMonth() + 1).padStart(2, '0');
|
||||||
|
const day = String(date.getDate()).padStart(2, '0');
|
||||||
|
return `${year}-${month}-${day}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
let startDate: string;
|
||||||
|
let endDate: string;
|
||||||
|
|
||||||
|
if (dateArr.value && dateArr.value.length === 2) {
|
||||||
|
const start = new Date(dateArr.value[0]);
|
||||||
|
const end = new Date(dateArr.value[1]);
|
||||||
|
if (offsetYears !== 0) {
|
||||||
|
start.setFullYear(start.getFullYear() + offsetYears);
|
||||||
|
end.setFullYear(end.getFullYear() + offsetYears);
|
||||||
|
}
|
||||||
|
startDate = `${formatDate(start)} 00:00:00`;
|
||||||
|
endDate = `${formatDate(end)} 23:59:59`;
|
||||||
|
} else {
|
||||||
|
const now = new Date();
|
||||||
|
const oneMonthAgo = new Date(now);
|
||||||
|
oneMonthAgo.setMonth(oneMonthAgo.getMonth() - 1);
|
||||||
|
if (offsetYears !== 0) {
|
||||||
|
now.setFullYear(now.getFullYear() + offsetYears);
|
||||||
|
oneMonthAgo.setFullYear(oneMonthAgo.getFullYear() + offsetYears);
|
||||||
|
}
|
||||||
|
startDate = `${formatDate(oneMonthAgo)} 00:00:00`;
|
||||||
|
endDate = `${formatDate(now)} 23:59:59`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return { startDate, endDate };
|
||||||
|
};
|
||||||
|
|
||||||
|
const getData = async () => {
|
||||||
|
const { startDate, endDate } = getdate(0);
|
||||||
|
|
||||||
|
let params = {
|
||||||
|
filter: {
|
||||||
|
logic: 'and',
|
||||||
|
filters: [
|
||||||
|
baseid.value != 'all'
|
||||||
|
? {
|
||||||
|
field: 'baseId',
|
||||||
|
operator: 'eq',
|
||||||
|
dataType: 'string',
|
||||||
|
value: baseid.value
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
{
|
||||||
|
field: 'dtin',
|
||||||
|
operator: 'eq',
|
||||||
|
dataType: 'string',
|
||||||
|
value: '1'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'type',
|
||||||
|
operator: 'eq',
|
||||||
|
dataType: 'string',
|
||||||
|
value: mode.value
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'tm',
|
||||||
|
operator: 'gte',
|
||||||
|
dataType: 'date',
|
||||||
|
value: startDate
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'tm',
|
||||||
|
operator: 'lte',
|
||||||
|
dataType: 'date',
|
||||||
|
value: endDate
|
||||||
|
}
|
||||||
|
].filter(Boolean)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
spinning.value = true;
|
||||||
|
try {
|
||||||
|
let res = await qgcetQgcStaticData(params);
|
||||||
|
// 根据API响应结构规范处理数据
|
||||||
|
if (res.data && res.data.list) {
|
||||||
|
const apiList = res.data.list;
|
||||||
|
//
|
||||||
|
// 只使用前两条数据(type: 1 和 type: 2)
|
||||||
|
// if (apiList && apiList.length > 0) {
|
||||||
|
// dataArr.value = apiList.slice(0, 2).map((item: any) => ({
|
||||||
|
// // name: item.typeName || item.desc,
|
||||||
|
// qecCnt: item.qecCnt,
|
||||||
|
// qecTCnt: item.qecTCnt,
|
||||||
|
// type: item.type
|
||||||
|
// }));
|
||||||
|
|
||||||
|
// // 清空旧数据并重新合并样式
|
||||||
|
// datalist.value = [];
|
||||||
|
// setStyle();
|
||||||
|
// } else {
|
||||||
|
// // 无数据场景处理
|
||||||
|
// dataArr.value = [];
|
||||||
|
// datalist.value = [];
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取生态流量达标数据失败:', error);
|
||||||
|
} finally {
|
||||||
|
spinning.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// 监听电站变化(优先使用父组件 provide 的电站上下文,兜底 jidiStore)
|
||||||
|
const injectedStation = inject<any>('dianZhanStation', null);
|
||||||
|
const stationSource = computed(
|
||||||
|
() => injectedStation?.value || JidiSelectEventStore.selectedItem
|
||||||
|
);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => stationSource.value,
|
||||||
|
newVal => {
|
||||||
|
if (newVal && newVal.wbsCode) {
|
||||||
|
baseid.value = newVal.wbsCode;
|
||||||
|
getData();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ deep: true, immediate: true }
|
||||||
|
);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => mapViewStore.searchTimeRange,
|
||||||
|
newVal => {
|
||||||
|
dateArr.value = newVal;
|
||||||
|
getData();
|
||||||
|
},
|
||||||
|
{ deep: true }
|
||||||
|
);
|
||||||
|
|
||||||
|
//统计电站范围:接入过生态流量数据的电站
|
||||||
|
const titleData: any = ref({ cnt: 0 });
|
||||||
|
const getcont = async () => {
|
||||||
|
let params = {
|
||||||
|
filter: {
|
||||||
|
logic: 'and',
|
||||||
|
filters: [
|
||||||
|
{
|
||||||
|
field: 'showIds',
|
||||||
|
operator: 'in',
|
||||||
|
value: ['7']
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let res = await evnmAutoMonitorGetKendoListCust(params);
|
||||||
|
let list = res?.data?.data[0];
|
||||||
|
if (list) {
|
||||||
|
titleData.value = list;
|
||||||
|
}
|
||||||
|
//
|
||||||
|
};
|
||||||
|
// 页面加载时执行的逻辑
|
||||||
|
onMounted(() => {
|
||||||
|
setStyle();
|
||||||
|
getcont();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.body_topOne {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.boy_one {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin: 5px 0px;
|
||||||
|
}
|
||||||
|
.body_title {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: bold;
|
||||||
|
padding-left: 10px;
|
||||||
|
margin-left: 4px;
|
||||||
|
position: relative;
|
||||||
|
&::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 4px;
|
||||||
|
left: 0;
|
||||||
|
width: 4px;
|
||||||
|
height: 14px;
|
||||||
|
background-color: #2f6b98;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.body_main {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
padding: 6px 10px;
|
||||||
|
justify-content: space-between;
|
||||||
|
.body_div {
|
||||||
|
width: 33%;
|
||||||
|
height: 100%;
|
||||||
|
padding: 10px;
|
||||||
|
margin: 10px 0;
|
||||||
|
background-color: #f3f5fa;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
border-radius: 4px;
|
||||||
|
.body_div_icon {
|
||||||
|
width: 30px;
|
||||||
|
height: 30px;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
.body_div_text {
|
||||||
|
font-size: 28px;
|
||||||
|
font-weight: 500;
|
||||||
|
margin: 10px 0;
|
||||||
|
}
|
||||||
|
.body_div_text_sub {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 400;
|
||||||
|
span {
|
||||||
|
color: #949494;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.body_zhu {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
|
||||||
|
.body_biao {
|
||||||
|
width: 100%;
|
||||||
|
height: 52px;
|
||||||
|
border: 1px solid #ccdae7;
|
||||||
|
border-radius: 2px;
|
||||||
|
display: flex;
|
||||||
|
padding: 5px 1px;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
.nei_body {
|
||||||
|
width: 12px;
|
||||||
|
height: 100%;
|
||||||
|
border: 1px solid #ccdae7;
|
||||||
|
padding: 2px;
|
||||||
|
margin-left: 4px;
|
||||||
|
|
||||||
|
.little_body {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.body_text {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
width: 90px;
|
||||||
|
height: 100%;
|
||||||
|
justify-content: space-around;
|
||||||
|
margin-left: 6px;
|
||||||
|
align-items: center;
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-radio-group {
|
||||||
|
// border: 3px solid #2f6b98 !important;
|
||||||
|
// border-radius: 10px !important;
|
||||||
|
|
||||||
|
.ant-radio-button-wrapper-checked {
|
||||||
|
border: 1px solid #2f6b98 !important;
|
||||||
|
background-color: #2f6b98 !important;
|
||||||
|
color: #fff !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-radio-button-wrapper {
|
||||||
|
border: 2px solid #2f6b98 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-radio-button-wrapper-checked :before {
|
||||||
|
background-color: #2f6b98 !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
592
frontend/src/modules/shuiWenJianCeKaiZhanQingKuang/index.vue
Normal file
592
frontend/src/modules/shuiWenJianCeKaiZhanQingKuang/index.vue
Normal file
@ -0,0 +1,592 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<SidePanelItem
|
||||||
|
title="各基地水温监测开展情况"
|
||||||
|
@update-values="handlePanelChange"
|
||||||
|
>
|
||||||
|
<!-- 加载状态 -->
|
||||||
|
<div v-if="dataLoading" class="chart-loading">
|
||||||
|
<a-spin tip="加载中..." />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 无数据提示 -->
|
||||||
|
<a-empty
|
||||||
|
v-if="!chartData.length && !dataLoading"
|
||||||
|
description="暂无数据"
|
||||||
|
class="chart-empty"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- 图表容器(使用 display 控制,避免 v-if 销毁 DOM)-->
|
||||||
|
<div
|
||||||
|
ref="chartRef"
|
||||||
|
class="chart-container"
|
||||||
|
:style="{
|
||||||
|
display: chartData.length && !dataLoading ? 'block' : 'none'
|
||||||
|
}"
|
||||||
|
></div>
|
||||||
|
</SidePanelItem>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { ref, watch, nextTick, onBeforeUnmount, defineOptions } from 'vue';
|
||||||
|
import SidePanelItem from '@/components/SidePanelItem/index.vue';
|
||||||
|
import { useJidiSelectEventStore } from '@/store/modules/jidiSelectEvent';
|
||||||
|
// import { wbsbGetKendoList } from '@/api/sz';
|
||||||
|
import * as echarts from 'echarts';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
// TODO: 导入生态流量 API
|
||||||
|
import { qgcHourVGetKendoListCust, wbsbGetKendoList } from '@/api/stll';
|
||||||
|
import { useModelStore } from '@/store/modules/model';
|
||||||
|
// 定义组件名
|
||||||
|
defineOptions({
|
||||||
|
name: 'ShuiWenJianCeKaiZhanQingKuang'
|
||||||
|
});
|
||||||
|
|
||||||
|
// ==================== 响应式状态 ====================
|
||||||
|
|
||||||
|
// 日期时间选择器配置
|
||||||
|
const datetimePicker = ref({
|
||||||
|
show: true,
|
||||||
|
value: dayjs().format('YYYY-MM-DD HH:mm'), // 默认当前时间
|
||||||
|
format: 'YYYY-MM-DD HH:mm',
|
||||||
|
picker: 'date',
|
||||||
|
timeFormat: 'HH:mm',
|
||||||
|
options: []
|
||||||
|
});
|
||||||
|
|
||||||
|
// 河段选择器配置
|
||||||
|
const select = ref({
|
||||||
|
show: true,
|
||||||
|
value: '',
|
||||||
|
options: [],
|
||||||
|
picker: undefined,
|
||||||
|
format: undefined
|
||||||
|
});
|
||||||
|
const modelStore = useModelStore();
|
||||||
|
// 业务数据状态
|
||||||
|
const wbsCode = ref(''); // 流域编码
|
||||||
|
const selectData = ref(''); // 选中的河段编码
|
||||||
|
const chartData = ref<any[]>([]); // 图表数据
|
||||||
|
const dataLoading = ref(false); // 加载状态
|
||||||
|
const latestTime = ref(''); // 最新数据时间
|
||||||
|
const hasTimeData = ref(false); // 是否有时间数据
|
||||||
|
|
||||||
|
// 图表相关
|
||||||
|
const chartRef = ref<HTMLElement | null>(null);
|
||||||
|
let chartInstance: echarts.ECharts | null = null;
|
||||||
|
|
||||||
|
// 弹窗状态
|
||||||
|
const modalVisible = ref(false);
|
||||||
|
const modalData = ref<any>({
|
||||||
|
stcd: '',
|
||||||
|
stnm: '',
|
||||||
|
titleName: ''
|
||||||
|
});
|
||||||
|
|
||||||
|
// Store
|
||||||
|
const JidiSelectEventStore = useJidiSelectEventStore();
|
||||||
|
|
||||||
|
// ==================== 核心方法 ====================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取河段列表
|
||||||
|
*/
|
||||||
|
const getSelect = async () => {
|
||||||
|
if (!wbsCode.value) {
|
||||||
|
console.warn('wbsCode 为空,无法获取河段数据');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let params = {
|
||||||
|
filter: {
|
||||||
|
logic: 'and',
|
||||||
|
filters: [
|
||||||
|
{
|
||||||
|
field: 'objId',
|
||||||
|
operator: 'eq',
|
||||||
|
value: wbsCode.value
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'wbsType',
|
||||||
|
operator: 'eq',
|
||||||
|
value: 'PSB_RVCD'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
group: [
|
||||||
|
{
|
||||||
|
dir: 'asc',
|
||||||
|
field: 'orderIndex'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
dir: 'asc',
|
||||||
|
field: 'wbsCode'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
dir: 'asc',
|
||||||
|
field: 'wbsName'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
dir: 'asc',
|
||||||
|
field: 'objid'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
groupResultFlat: 'true'
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
let res = await wbsbGetKendoList(params);
|
||||||
|
|
||||||
|
// 处理返回的数据
|
||||||
|
let data;
|
||||||
|
if (res && res.data && res.data.data) {
|
||||||
|
if (Array.isArray(res.data.data)) {
|
||||||
|
data = res.data.data;
|
||||||
|
} else if (res.data.data.data && Array.isArray(res.data.data.data)) {
|
||||||
|
data = res.data.data.data;
|
||||||
|
} else {
|
||||||
|
console.warn('无法解析河段数据:', res);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.warn('未获取到河段数据');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data && data.length > 0) {
|
||||||
|
// ;
|
||||||
|
select.value.value = data[0].wbsCode;
|
||||||
|
selectData.value = data[0].wbsCode; // 同步选中第一个
|
||||||
|
select.value.options = data.map(item => ({
|
||||||
|
value: item.wbsCode,
|
||||||
|
label: item.wbsName
|
||||||
|
}));
|
||||||
|
|
||||||
|
// 获取完河段后立即加载图表数据
|
||||||
|
await loadChartData();
|
||||||
|
} else {
|
||||||
|
console.warn('河段数据为空');
|
||||||
|
select.value.value = '';
|
||||||
|
select.value.options = [];
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取河段数据失败:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取生态流量数据(预留 API 位置)
|
||||||
|
*/
|
||||||
|
const getList = async (rvcd: string, sDate: string) => {
|
||||||
|
// TODO: 调用实际的 API
|
||||||
|
// 参数结构参考 React 版本:
|
||||||
|
// - baseId: 从 JidiSelectEventStore.selectedItem 获取
|
||||||
|
// - rvcd: 河段编码
|
||||||
|
// - tm: 时间范围(当前时间往前推1个月)
|
||||||
|
|
||||||
|
const currentTime = dayjs(sDate);
|
||||||
|
const startTime = currentTime.subtract(1, 'month');
|
||||||
|
let params = {
|
||||||
|
filter: {
|
||||||
|
logic: 'and',
|
||||||
|
filters: [
|
||||||
|
{
|
||||||
|
field: 'baseId',
|
||||||
|
operator: 'eq',
|
||||||
|
dataType: 'string',
|
||||||
|
value: wbsCode.value
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'rvcd',
|
||||||
|
operator: 'eq',
|
||||||
|
dataType: 'string',
|
||||||
|
value: rvcd
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'tm',
|
||||||
|
operator: 'gte',
|
||||||
|
dataType: 'date',
|
||||||
|
value: startTime.format('YYYY-MM-DD HH:mm:ss')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'tm',
|
||||||
|
operator: 'lte',
|
||||||
|
dataType: 'date',
|
||||||
|
value: currentTime.format('YYYY-MM-DD HH:mm:ss')
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
sort: [
|
||||||
|
{
|
||||||
|
field: 'sort',
|
||||||
|
dir: 'asc'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
// TODO: 替换为实际 API 调用FAA
|
||||||
|
const res = await qgcHourVGetKendoListCust(params);
|
||||||
|
|
||||||
|
// 处理返回的数据结构
|
||||||
|
if (!res || !res.data || !res.data.data || !Array.isArray(res.data.data)) {
|
||||||
|
console.warn('未获取到有效的生态流量数据:', res);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 映射数据结构,适配图表展示需求
|
||||||
|
return res.data.data.map((item: any) => ({
|
||||||
|
TM: dayjs(item.tm).format('YYYY-MM-DD H时'),
|
||||||
|
stnm: item.stnm,
|
||||||
|
stcd: item.stcd,
|
||||||
|
qec: item.qec, // 生态流量
|
||||||
|
qecLimit: item.qecLimit, // 环评限值
|
||||||
|
qi: item.qi // 入库流量
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 加载图表数据
|
||||||
|
*/
|
||||||
|
const loadChartData = async () => {
|
||||||
|
if (!selectData.value || !datetimePicker.value.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
dataLoading.value = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const data = await getList(selectData.value, datetimePicker.value.value);
|
||||||
|
|
||||||
|
if (data && data.length > 0) {
|
||||||
|
chartData.value = data;
|
||||||
|
hasTimeData.value = true;
|
||||||
|
// 提取最新时间
|
||||||
|
if (data[0]?.TM) {
|
||||||
|
latestTime.value = data[0].TM;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 数据加载完成后渲染图表
|
||||||
|
await nextTick();
|
||||||
|
initChart();
|
||||||
|
} else {
|
||||||
|
chartData.value = [];
|
||||||
|
hasTimeData.value = false;
|
||||||
|
latestTime.value = '';
|
||||||
|
// 清空图表
|
||||||
|
if (chartInstance) {
|
||||||
|
chartInstance.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('加载图表数据失败:', error);
|
||||||
|
chartData.value = [];
|
||||||
|
} finally {
|
||||||
|
dataLoading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化 ECharts 图表
|
||||||
|
*/
|
||||||
|
const initChart = () => {
|
||||||
|
if (!chartRef.value) {
|
||||||
|
console.warn('图表容器未准备好');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果实例已存在,先销毁
|
||||||
|
if (chartInstance) {
|
||||||
|
chartInstance.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建新实例
|
||||||
|
chartInstance = echarts.init(chartRef.value);
|
||||||
|
|
||||||
|
// 准备数据
|
||||||
|
const xData: string[] = [];
|
||||||
|
const inData: (number | string)[] = []; // 入库流量
|
||||||
|
const outData: (number | string)[] = []; // 生态流量
|
||||||
|
let hasValidData = false; // 标记是否有有效数据
|
||||||
|
|
||||||
|
chartData.value.forEach(item => {
|
||||||
|
xData.push(item.stnm);
|
||||||
|
const qecValue = item.qec ?? '-';
|
||||||
|
const qiValue = item.qi ?? '-';
|
||||||
|
|
||||||
|
outData.push(qecValue);
|
||||||
|
inData.push(qiValue);
|
||||||
|
|
||||||
|
// 检查是否有任意一个字段有有效数据
|
||||||
|
if (qecValue !== '-' || qiValue !== '-') {
|
||||||
|
hasValidData = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 如果所有数据都为空,显示暂无数据
|
||||||
|
if (!hasValidData && chartData.value.length > 0) {
|
||||||
|
chartData.value = [];
|
||||||
|
if (chartInstance) {
|
||||||
|
chartInstance.clear();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 图表配置
|
||||||
|
const option: echarts.EChartsOption = {
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'axis',
|
||||||
|
confine: true,
|
||||||
|
formatter: function (params: any) {
|
||||||
|
const datum = chartData.value.find(
|
||||||
|
(el: any) => el.stnm === params[0].name
|
||||||
|
);
|
||||||
|
const unit = 'm³/s'; // TODO: 根据单位配置动态获取
|
||||||
|
|
||||||
|
const content = params
|
||||||
|
.map((item: any) => {
|
||||||
|
const value = item.value ?? '-';
|
||||||
|
return `<div>${item.marker}${item.seriesName}: <span style="float:right">${value} ${unit}</span></div>`;
|
||||||
|
})
|
||||||
|
.join('');
|
||||||
|
|
||||||
|
return `<h3 style="color:white">${params[0].name}<br/>${
|
||||||
|
datum?.TM || ''
|
||||||
|
}</h3>${content}`;
|
||||||
|
},
|
||||||
|
axisPointer: {
|
||||||
|
type: 'line'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
data: ['表层水温', '垂向水温'],
|
||||||
|
icon: 'roundRect',
|
||||||
|
top: 10
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
left: 10,
|
||||||
|
right: 10,
|
||||||
|
bottom: 10,
|
||||||
|
top: 70,
|
||||||
|
containLabel: true
|
||||||
|
},
|
||||||
|
xAxis: {
|
||||||
|
type: 'category',
|
||||||
|
data: xData,
|
||||||
|
axisLine: {
|
||||||
|
lineStyle: {
|
||||||
|
color: '#505050'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
splitLine: {
|
||||||
|
show: true,
|
||||||
|
lineStyle: {
|
||||||
|
color: '#8f8f8f',
|
||||||
|
type: 'solid'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
axisLabel: {
|
||||||
|
interval: xData.length > 12 ? 2 : 0,
|
||||||
|
rotate: 0,
|
||||||
|
formatter: (value: string, index: number) => {
|
||||||
|
const shortValue =
|
||||||
|
value.substring(0, 6) + (value.length > 6 ? '...' : '');
|
||||||
|
if (index % 2 !== 0) {
|
||||||
|
return `{a|${shortValue}}`;
|
||||||
|
} else {
|
||||||
|
return `\n{b|}${shortValue}`;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
rich: {
|
||||||
|
a: {
|
||||||
|
height: 20,
|
||||||
|
align: 'center'
|
||||||
|
},
|
||||||
|
b: {
|
||||||
|
height: 30,
|
||||||
|
align: 'center'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
yAxis: {
|
||||||
|
type: 'value',
|
||||||
|
minInterval: 1,
|
||||||
|
axisLine: {
|
||||||
|
lineStyle: {
|
||||||
|
color: '#505050'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
splitLine: {
|
||||||
|
show: true,
|
||||||
|
lineStyle: {
|
||||||
|
color: '#8f8f8f',
|
||||||
|
type: 'solid'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
dataZoom: [
|
||||||
|
{
|
||||||
|
type: 'inside',
|
||||||
|
xAxisIndex: 0,
|
||||||
|
start: 0,
|
||||||
|
end: 100,
|
||||||
|
zoomOnMouseWheel: true,
|
||||||
|
moveOnMouseMove: true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
name: '表层水温',
|
||||||
|
data: inData,
|
||||||
|
type: 'line',
|
||||||
|
smooth: true,
|
||||||
|
symbol: 'circle',
|
||||||
|
symbolSize: 6,
|
||||||
|
connectNulls: true,
|
||||||
|
color: '#5470c6' // TODO: 根据配置动态获取
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '垂向水温',
|
||||||
|
data: outData,
|
||||||
|
type: 'line',
|
||||||
|
smooth: true,
|
||||||
|
symbol: 'circle',
|
||||||
|
symbolSize: 6,
|
||||||
|
connectNulls: true,
|
||||||
|
color: '#91cc75' // TODO: 根据配置动态获取
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
chartInstance.setOption(option);
|
||||||
|
|
||||||
|
// 绑定点击事件
|
||||||
|
chartInstance.on('click', handleChartClick);
|
||||||
|
|
||||||
|
// 自适应窗口大小
|
||||||
|
window.addEventListener('resize', handleResize);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 图表点击事件
|
||||||
|
*/
|
||||||
|
const handleChartClick = (params: any) => {
|
||||||
|
const datum = chartData.value.find((item: any) => item.stnm === params.name);
|
||||||
|
if (datum) {
|
||||||
|
const currentTime = dayjs(datetimePicker.value.value);
|
||||||
|
const startTime = currentTime.subtract(1, 'month');
|
||||||
|
modalVisible.value = true;
|
||||||
|
modelStore.modalVisible = true;
|
||||||
|
modelStore.params.sttp = 'ENG';
|
||||||
|
modelStore.title = datum.stnm;
|
||||||
|
modelStore.params.stcd = datum.stcd;
|
||||||
|
modelStore.params.eqtp = 'QEC';
|
||||||
|
modelStore.filter.rangeTm = [startTime, currentTime];
|
||||||
|
modelStore.filter.stllgzlx = 'qecInterval';
|
||||||
|
modelStore.filter.stllTmType = 'day';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 窗口大小改变时重新调整图表
|
||||||
|
*/
|
||||||
|
const handleResize = () => {
|
||||||
|
if (chartInstance) {
|
||||||
|
chartInstance.resize();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 弹窗取消事件
|
||||||
|
*/
|
||||||
|
const handleModalCancel = () => {
|
||||||
|
modalVisible.value = false;
|
||||||
|
modalData.value = {
|
||||||
|
stcd: '',
|
||||||
|
stnm: '',
|
||||||
|
titleName: ''
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 面板数据变化处理
|
||||||
|
*/
|
||||||
|
const handlePanelChange = async (data: any) => {
|
||||||
|
console.log('当前所有控件状态:', data);
|
||||||
|
|
||||||
|
// 当选择器或日期变化时,重新加载图表数据
|
||||||
|
if (data.select) {
|
||||||
|
select.value.value = data.select;
|
||||||
|
selectData.value = data.select;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.datetime) {
|
||||||
|
datetimePicker.value.value = data.datetime;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重新加载数据
|
||||||
|
if (data.select || data.datetime) {
|
||||||
|
await loadChartData();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// ==================== 生命周期与监听 ====================
|
||||||
|
|
||||||
|
// 监听流域切换
|
||||||
|
watch(
|
||||||
|
() => JidiSelectEventStore.selectedItem,
|
||||||
|
newVal => {
|
||||||
|
console.log('流域切换:', newVal);
|
||||||
|
if (newVal?.wbsCode) {
|
||||||
|
wbsCode.value = newVal.wbsCode;
|
||||||
|
getSelect();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ deep: true, immediate: true }
|
||||||
|
);
|
||||||
|
// 组件卸载时清理
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
if (chartInstance) {
|
||||||
|
chartInstance.dispose();
|
||||||
|
chartInstance = null;
|
||||||
|
}
|
||||||
|
window.removeEventListener('resize', handleResize);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.chart-loading {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
height: 424px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-empty {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
height: 424px;
|
||||||
|
flex-direction: column;
|
||||||
|
/* align-content: center; */
|
||||||
|
justify-content: center;
|
||||||
|
text-align: center !important;
|
||||||
|
:deep(.ant-empty-description) {
|
||||||
|
text-align: center !important;
|
||||||
|
text-indent: 0;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-container {
|
||||||
|
width: 406px;
|
||||||
|
height: 424px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.time-tip {
|
||||||
|
text-align: right;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #8e8e8e;
|
||||||
|
padding: 8px 0;
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -1,26 +1,37 @@
|
|||||||
<!-- SidePanelItem.vue -->
|
<!-- SidePanelItem.vue -->
|
||||||
<template>
|
<template>
|
||||||
<SidePanelItem title="水温监测工作开展情况">
|
<SidePanelItem title="水温监测统计">
|
||||||
<a-spin :spinning="loading" tip="加载中...">
|
<a-spin :spinning="loading" tip="加载中...">
|
||||||
<div class="facility-grid">
|
<div class="facility-grid">
|
||||||
<div v-for="facility in facilities" :key="facility.name" class="facility-card"
|
<div
|
||||||
@click="handleFacilityClick(facility)">
|
v-for="facility in facilities"
|
||||||
<div style="
|
:key="facility.name"
|
||||||
|
class="facility-card"
|
||||||
|
@click="handleFacilityClick(facility)"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style="
|
||||||
width: 60px;
|
width: 60px;
|
||||||
height: 62px;
|
height: 62px;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
">
|
"
|
||||||
|
>
|
||||||
<div class="facility-icon">
|
<div class="facility-icon">
|
||||||
<i style="color: #fff" :class="facility.icon" type="icon-shengtailiuliang2"></i>
|
<i
|
||||||
|
style="color: #fff"
|
||||||
|
:class="facility.icon"
|
||||||
|
type="icon-shengtailiuliang2"
|
||||||
|
></i>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="facility-info">
|
<div class="facility-info">
|
||||||
<div class="facility-name">{{ facility.name }}</div>
|
<div class="facility-name">{{ facility.name }}</div>
|
||||||
<div style="font-size: 16px">
|
<div style="font-size: 16px">
|
||||||
<span class="facility-count">{{ facility.count }}</span><span>个</span>
|
<span class="facility-count">{{ facility.count }}</span
|
||||||
|
><span>个</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -28,40 +39,54 @@
|
|||||||
</a-spin>
|
</a-spin>
|
||||||
|
|
||||||
<!-- 弹框 -->
|
<!-- 弹框 -->
|
||||||
<a-modal v-model:open="modalVisible" :title="'水温监测工作开展情况'" :footer="null" width="1536px" @cancel="handleCloseModal">
|
<a-modal
|
||||||
<ShuiwenjiancegongzuoEJ v-if="currentFacility" :activeKey="currentFacility.key" :tabs="facilities" :baseId="baseid" />
|
v-model:open="modalVisible"
|
||||||
|
:title="'水温监测工作开展情况'"
|
||||||
|
:footer="null"
|
||||||
|
width="1536px"
|
||||||
|
@cancel="handleCloseModal"
|
||||||
|
>
|
||||||
|
<ShuiwenjiancegongzuoEJ
|
||||||
|
v-if="currentFacility"
|
||||||
|
:activeKey="currentFacility.key"
|
||||||
|
:tabs="facilities"
|
||||||
|
:baseId="baseid"
|
||||||
|
/>
|
||||||
</a-modal>
|
</a-modal>
|
||||||
</SidePanelItem>
|
</SidePanelItem>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref, onMounted, watch } from "vue";
|
import { ref, onMounted, watch } from 'vue';
|
||||||
import SidePanelItem from "@/components/SidePanelItem/index.vue";
|
import SidePanelItem from '@/components/SidePanelItem/index.vue';
|
||||||
import { getKendoListCust, baseEvnmAutoMonitorGetKendoListCust } from "@/api/sw";
|
import {
|
||||||
import { useJidiSelectEventStore } from "@/store/modules/jidiSelectEvent";
|
getKendoListCust,
|
||||||
import ShuiwenjiancegongzuoEJ from "./shuiwenjiancegongzuoEJ.vue"
|
baseEvnmAutoMonitorGetKendoListCust
|
||||||
|
} from '@/api/sw';
|
||||||
|
import { useJidiSelectEventStore } from '@/store/modules/jidiSelectEvent';
|
||||||
|
import ShuiwenjiancegongzuoEJ from './shuiwenjiancegongzuoEJ.vue';
|
||||||
import { useDraggable } from '@/utils/drag';
|
import { useDraggable } from '@/utils/drag';
|
||||||
|
|
||||||
const JidiSelectEventStore = useJidiSelectEventStore();
|
const JidiSelectEventStore = useJidiSelectEventStore();
|
||||||
// 定义组件名(便于调试和递归)
|
// 定义组件名(便于调试和递归)
|
||||||
defineOptions({
|
defineOptions({
|
||||||
name: "shuiwenjiancegongzuokaizhangqingkuang",
|
name: 'shuiwenjiancegongzuokaizhangqingkuang'
|
||||||
});
|
});
|
||||||
|
|
||||||
// 设施数据
|
// 设施数据
|
||||||
const facilities = ref([
|
const facilities = ref([
|
||||||
{
|
{
|
||||||
name: "表层水温",
|
name: '表层水温',
|
||||||
count: 0,
|
count: 0,
|
||||||
icon: "icon iconfont icon-shuizhijiancezhan",
|
icon: 'icon iconfont icon-shuizhijiancezhan',
|
||||||
key:'26'
|
key: '26'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "垂向水温",
|
name: '垂向水温',
|
||||||
count: 0,
|
count: 0,
|
||||||
icon: "icon iconfont icon-diwenshuijianhuan",
|
icon: 'icon iconfont icon-diwenshuijianhuan',
|
||||||
key:'27'
|
key: '27'
|
||||||
},
|
}
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Loading 状态
|
// Loading 状态
|
||||||
@ -76,8 +101,6 @@ const handleFacilityClick = (facility: any) => {
|
|||||||
// console.log(facility);
|
// console.log(facility);
|
||||||
currentFacility.value = facility;
|
currentFacility.value = facility;
|
||||||
modalVisible.value = true;
|
modalVisible.value = true;
|
||||||
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// 关闭弹框
|
// 关闭弹框
|
||||||
@ -93,30 +116,29 @@ const init = async () => {
|
|||||||
try {
|
try {
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
const params = {
|
const params = {
|
||||||
"filter": {
|
filter: {
|
||||||
"logic": "and",
|
logic: 'and',
|
||||||
"filters": [
|
filters: [
|
||||||
{
|
{
|
||||||
"field": "showIds",
|
field: 'showIds',
|
||||||
"operator": "in",
|
operator: 'in',
|
||||||
"value": [
|
value: ['26', '27']
|
||||||
"26",
|
|
||||||
"27"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
baseid.value != 'all' ? {
|
baseid.value != 'all'
|
||||||
"field": "baseId",
|
? {
|
||||||
"operator": "eq",
|
field: 'baseId',
|
||||||
"value": baseid.value
|
operator: 'eq',
|
||||||
} : null
|
value: baseid.value
|
||||||
|
}
|
||||||
|
: null
|
||||||
].filter(Boolean)
|
].filter(Boolean)
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
let res = await baseEvnmAutoMonitorGetKendoListCust(params);
|
let res = await baseEvnmAutoMonitorGetKendoListCust(params);
|
||||||
console.log(res);
|
console.log(res);
|
||||||
let data = res.data.data || res.data
|
let data = res.data.data || res.data;
|
||||||
data.forEach((item: any, index: any) => {
|
data.forEach((item: any, index: any) => {
|
||||||
facilities.value[index].count = item.cnt
|
facilities.value[index].count = item.cnt;
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('获取水温监测数据失败:', error);
|
console.error('获取水温监测数据失败:', error);
|
||||||
@ -130,10 +152,10 @@ onMounted(() => {
|
|||||||
init();
|
init();
|
||||||
});
|
});
|
||||||
|
|
||||||
const baseid = ref("")
|
const baseid = ref('');
|
||||||
watch(
|
watch(
|
||||||
() => JidiSelectEventStore.selectedItem,
|
() => JidiSelectEventStore.selectedItem,
|
||||||
(newVal) => {
|
newVal => {
|
||||||
baseid.value = newVal.wbsCode;
|
baseid.value = newVal.wbsCode;
|
||||||
init();
|
init();
|
||||||
},
|
},
|
||||||
@ -147,9 +169,9 @@ watch(
|
|||||||
flex-flow: wrap;
|
flex-flow: wrap;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Helvetica Neue, Arial,
|
font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto,
|
||||||
Noto Sans, sans-serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol,
|
Helvetica Neue, Arial, Noto Sans, sans-serif, Apple Color Emoji,
|
||||||
Noto Color Emoji;
|
Segoe UI Emoji, Segoe UI Symbol, Noto Color Emoji;
|
||||||
}
|
}
|
||||||
|
|
||||||
.facility-card {
|
.facility-card {
|
||||||
|
|||||||
@ -9,11 +9,25 @@
|
|||||||
<div class="search-form">
|
<div class="search-form">
|
||||||
<a-space size="middle">
|
<a-space size="middle">
|
||||||
<div>选择水电站:</div>
|
<div>选择水电站:</div>
|
||||||
<a-select v-model:value="searchData.stcd.dataDimensionData" placeholder="请选择水电站" style="width: 200px"
|
<a-select
|
||||||
show-search :filter-option="filterOption" :options="hydropowerStationOptions"
|
v-model:value="searchData.stcd.dataDimensionData"
|
||||||
@change="handleHydropowerStationChange" />
|
placeholder="请选择水电站"
|
||||||
<a-select v-model:value="searchData.stnm" placeholder=" " style="width: 200px" allow-clear show-search
|
style="width: 200px"
|
||||||
:filter-option="filterOption" :options="stationOptions" @change="handleStationChange" />
|
show-search
|
||||||
|
:filter-option="filterOption"
|
||||||
|
:options="hydropowerStationOptions"
|
||||||
|
@change="handleHydropowerStationChange"
|
||||||
|
/>
|
||||||
|
<a-select
|
||||||
|
v-model:value="searchData.stnm"
|
||||||
|
placeholder="请选择电站"
|
||||||
|
style="width: 200px"
|
||||||
|
allow-clear
|
||||||
|
show-search
|
||||||
|
:filter-option="filterOption"
|
||||||
|
:options="stationOptions"
|
||||||
|
@change="handleStationChange"
|
||||||
|
/>
|
||||||
|
|
||||||
<a-button type="primary" @click="handleSearch">查询</a-button>
|
<a-button type="primary" @click="handleSearch">查询</a-button>
|
||||||
<a-tooltip title="重置">
|
<a-tooltip title="重置">
|
||||||
@ -23,8 +37,15 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 数据表格 -->
|
<!-- 数据表格 -->
|
||||||
<a-table :columns="columns" :data-source="tableData" :loading="loading" :pagination="pagination"
|
<a-table
|
||||||
:scroll="{ y: tableHeight }" row-key="stcd" @change="handleTableChange">
|
:columns="columns"
|
||||||
|
:data-source="tableData"
|
||||||
|
:loading="loading"
|
||||||
|
:pagination="pagination"
|
||||||
|
:scroll="{ y: tableHeight }"
|
||||||
|
row-key="stcd"
|
||||||
|
@change="handleTableChange"
|
||||||
|
>
|
||||||
<template #bodyCell="{ column, record }">
|
<template #bodyCell="{ column, record }">
|
||||||
<!-- 监测状态 -->
|
<!-- 监测状态 -->
|
||||||
<template v-if="column.key === 'coenvwState'">
|
<template v-if="column.key === 'coenvwState'">
|
||||||
@ -50,22 +71,27 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref, computed, watch, onMounted } from 'vue'
|
import dayjs from 'dayjs';
|
||||||
import { vmsstbprptGetKendoList, wbsbGetKendoList, getVmsstbprpt } from '@/api/sw'
|
import { ref, computed, watch, onMounted } from 'vue';
|
||||||
import { useModelStore } from "@/store/modules/model";
|
import {
|
||||||
|
vmsstbprptGetKendoList,
|
||||||
|
wbsbGetKendoList,
|
||||||
|
getVmsstbprpt
|
||||||
|
} from '@/api/sw';
|
||||||
|
import { useModelStore } from '@/store/modules/model';
|
||||||
|
|
||||||
const modelStore = useModelStore();
|
const modelStore = useModelStore();
|
||||||
// ==================== Props 定义 ====================
|
// ==================== Props 定义 ====================
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
baseId?: string
|
baseId?: string;
|
||||||
tabs?: any[]
|
tabs?: any[];
|
||||||
activeKey?: string
|
activeKey?: string;
|
||||||
}>()
|
}>();
|
||||||
|
|
||||||
// ==================== 状态管理 ====================
|
// ==================== 状态管理 ====================
|
||||||
const tabIndex = ref(props.activeKey || '26')
|
const tabIndex = ref(props.activeKey || '26');
|
||||||
const loading = ref(false)
|
const loading = ref(false);
|
||||||
const tableData = ref<any[]>([])
|
const tableData = ref<any[]>([]);
|
||||||
const pagination = ref({
|
const pagination = ref({
|
||||||
current: 1,
|
current: 1,
|
||||||
pageSize: 20,
|
pageSize: 20,
|
||||||
@ -75,27 +101,27 @@ const pagination = ref({
|
|||||||
showQuickJumper: true,
|
showQuickJumper: true,
|
||||||
showTotal: (total: number) => `共 ${total} 条`,
|
showTotal: (total: number) => `共 ${total} 条`,
|
||||||
pageSizeOptions: ['10', '20', '50', '100']
|
pageSizeOptions: ['10', '20', '50', '100']
|
||||||
})
|
});
|
||||||
const tableHeight = 'calc(48vh)'
|
const tableHeight = 'calc(48vh)';
|
||||||
|
|
||||||
// 水电站列表数据
|
// 水电站列表数据
|
||||||
const hydropowerStationList = ref<any[]>([])
|
const hydropowerStationList = ref<any[]>([]);
|
||||||
const stationDataList: any = ref([])
|
const stationDataList: any = ref([]);
|
||||||
|
|
||||||
// 转换为 a-select options 格式
|
// 转换为 a-select options 格式
|
||||||
const hydropowerStationOptions = computed(() => {
|
const hydropowerStationOptions = computed(() => {
|
||||||
return hydropowerStationList.value.map(station => ({
|
return hydropowerStationList.value.map(station => ({
|
||||||
label: station.wbsName,
|
label: station.wbsName,
|
||||||
value: station.wbsCode
|
value: station.wbsCode
|
||||||
}))
|
}));
|
||||||
})
|
});
|
||||||
|
|
||||||
const stationOptions = computed(() => {
|
const stationOptions = computed(() => {
|
||||||
return stationDataList.value.map(station => ({
|
return stationDataList.value.map(station => ({
|
||||||
label: station.stnm,
|
label: station.stnm,
|
||||||
value: station.stcd
|
value: station.stcd
|
||||||
}))
|
}));
|
||||||
})
|
});
|
||||||
|
|
||||||
const searchData = ref<any>({
|
const searchData = ref<any>({
|
||||||
stcd: {
|
stcd: {
|
||||||
@ -104,58 +130,57 @@ const searchData = ref<any>({
|
|||||||
dataDimensionData: props.baseId || 'all'
|
dataDimensionData: props.baseId || 'all'
|
||||||
},
|
},
|
||||||
stnm: null
|
stnm: null
|
||||||
})
|
});
|
||||||
|
|
||||||
// ==================== 计算合并信息 ====================
|
// ==================== 计算合并信息 ====================
|
||||||
const getRowSpanInfo = (dataIndex: string) => {
|
const getRowSpanInfo = (dataIndex: string) => {
|
||||||
const spanMap = new Map()
|
const spanMap = new Map();
|
||||||
|
|
||||||
if (!tableData.value || tableData.value.length === 0) {
|
if (!tableData.value || tableData.value.length === 0) {
|
||||||
return spanMap
|
return spanMap;
|
||||||
}
|
}
|
||||||
|
|
||||||
let currentGroupStart = 0
|
let currentGroupStart = 0;
|
||||||
let currentValue = tableData.value[0][dataIndex]
|
let currentValue = tableData.value[0][dataIndex];
|
||||||
|
|
||||||
for (let i = 0; i < tableData.value.length; i++) {
|
for (let i = 0; i < tableData.value.length; i++) {
|
||||||
const value = tableData.value[i][dataIndex]
|
const value = tableData.value[i][dataIndex];
|
||||||
|
|
||||||
if (value !== currentValue) {
|
if (value !== currentValue) {
|
||||||
// 记录当前组的起始位置和行数
|
// 记录当前组的起始位置和行数
|
||||||
spanMap.set(currentGroupStart, i - currentGroupStart)
|
spanMap.set(currentGroupStart, i - currentGroupStart);
|
||||||
currentGroupStart = i
|
currentGroupStart = i;
|
||||||
currentValue = value
|
currentValue = value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 处理最后一组
|
// 处理最后一组
|
||||||
spanMap.set(currentGroupStart, tableData.value.length - currentGroupStart)
|
spanMap.set(currentGroupStart, tableData.value.length - currentGroupStart);
|
||||||
|
|
||||||
return spanMap
|
return spanMap;
|
||||||
}
|
};
|
||||||
|
|
||||||
// ==================== 监听 Props 变化 ====================
|
// ==================== 监听 Props 变化 ====================
|
||||||
// const baseId = ref('')
|
// const baseId = ref('')
|
||||||
watch(
|
watch(
|
||||||
() => props.baseId,
|
() => props.baseId,
|
||||||
(newVal) => {
|
newVal => {
|
||||||
if (newVal && newVal !== searchData.value.stcd.dataDimensionData) {
|
if (newVal && newVal !== searchData.value.stcd.dataDimensionData) {
|
||||||
searchData.value.stcd.dataDimensionData = newVal
|
searchData.value.stcd.dataDimensionData = newVal;
|
||||||
fetchData()
|
fetchData();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
);
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => props.activeKey,
|
() => props.activeKey,
|
||||||
(newVal) => {
|
newVal => {
|
||||||
if (newVal && newVal !== tabIndex.value) {
|
if (newVal && newVal !== tabIndex.value) {
|
||||||
tabIndex.value = newVal
|
tabIndex.value = newVal;
|
||||||
fetchData()
|
fetchData();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
);
|
||||||
|
|
||||||
|
|
||||||
// ==================== 列配置 ====================
|
// ==================== 列配置 ====================
|
||||||
const columns = computed(() => {
|
const columns = computed(() => {
|
||||||
@ -166,16 +191,16 @@ const columns = computed(() => {
|
|||||||
key: 'baseName',
|
key: 'baseName',
|
||||||
width: 150,
|
width: 150,
|
||||||
customCell: (record: any, index: number) => {
|
customCell: (record: any, index: number) => {
|
||||||
const spanMap = getRowSpanInfo('baseName')
|
const spanMap = getRowSpanInfo('baseName');
|
||||||
const rowSpan = spanMap.get(index)
|
const rowSpan = spanMap.get(index);
|
||||||
|
|
||||||
if (rowSpan === undefined) {
|
if (rowSpan === undefined) {
|
||||||
// 被合并的行,不显示
|
// 被合并的行,不显示
|
||||||
return { rowSpan: 0 }
|
return { rowSpan: 0 };
|
||||||
}
|
}
|
||||||
|
|
||||||
// 第一行,设置合并行数
|
// 第一行,设置合并行数
|
||||||
return { rowSpan }
|
return { rowSpan };
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -194,7 +219,9 @@ const columns = computed(() => {
|
|||||||
title: '建成时间',
|
title: '建成时间',
|
||||||
dataIndex: 'jcdt',
|
dataIndex: 'jcdt',
|
||||||
key: 'jcdt',
|
key: 'jcdt',
|
||||||
width: 120
|
width: 120,
|
||||||
|
customRender: ({ text }) =>
|
||||||
|
text ? dayjs(text).format('YYYY-MM-DD') : '-'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '监测指标',
|
title: '监测指标',
|
||||||
@ -216,22 +243,25 @@ const columns = computed(() => {
|
|||||||
align: 'center' as const,
|
align: 'center' as const,
|
||||||
fixed: 'right' as const
|
fixed: 'right' as const
|
||||||
}
|
}
|
||||||
]
|
];
|
||||||
|
|
||||||
return baseColumns.filter(Boolean)
|
return baseColumns.filter(Boolean);
|
||||||
})
|
});
|
||||||
|
|
||||||
// ==================== 过滤条件构建 ====================
|
// ==================== 过滤条件构建 ====================
|
||||||
const buildSearchFilter = () => {
|
const buildSearchFilter = () => {
|
||||||
const filters: any[] = []
|
const filters: any[] = [];
|
||||||
|
|
||||||
// 基地ID过滤
|
// 基地ID过滤
|
||||||
if (searchData.value?.stcd?.dataDimensionData && searchData.value.stcd.dataDimensionData !== 'all') {
|
if (
|
||||||
|
searchData.value?.stcd?.dataDimensionData &&
|
||||||
|
searchData.value.stcd.dataDimensionData !== 'all'
|
||||||
|
) {
|
||||||
filters.push({
|
filters.push({
|
||||||
field: 'baseId',
|
field: 'baseId',
|
||||||
operator: 'eq',
|
operator: 'eq',
|
||||||
value: searchData.value.stcd.dataDimensionData
|
value: searchData.value.stcd.dataDimensionData
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// // 测站ID过滤
|
// // 测站ID过滤
|
||||||
@ -249,7 +279,7 @@ const buildSearchFilter = () => {
|
|||||||
field: 'rstcd',
|
field: 'rstcd',
|
||||||
operator: 'eq',
|
operator: 'eq',
|
||||||
value: searchData.value.stnm
|
value: searchData.value.stnm
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tab特定过滤
|
// Tab特定过滤
|
||||||
@ -268,8 +298,8 @@ const buildSearchFilter = () => {
|
|||||||
dataType: 'string',
|
dataType: 'string',
|
||||||
value: 'WTRV'
|
value: 'WTRV'
|
||||||
}
|
}
|
||||||
)
|
);
|
||||||
break
|
break;
|
||||||
case '27':
|
case '27':
|
||||||
filters.push(
|
filters.push(
|
||||||
{
|
{
|
||||||
@ -284,21 +314,23 @@ const buildSearchFilter = () => {
|
|||||||
dataType: 'string',
|
dataType: 'string',
|
||||||
value: 'WTVT'
|
value: 'WTVT'
|
||||||
}
|
}
|
||||||
)
|
);
|
||||||
break
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
logic: 'and',
|
logic: 'and',
|
||||||
filters: filters.filter((f) => f.value !== undefined && f.value !== null && f.value !== '')
|
filters: filters.filter(
|
||||||
}
|
f => f.value !== undefined && f.value !== null && f.value !== ''
|
||||||
}
|
)
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
// ==================== API调用 ====================
|
// ==================== API调用 ====================
|
||||||
const fetchData = async () => {
|
const fetchData = async () => {
|
||||||
loading.value = true
|
loading.value = true;
|
||||||
try {
|
try {
|
||||||
const filter = buildSearchFilter()
|
const filter = buildSearchFilter();
|
||||||
const params = {
|
const params = {
|
||||||
take: pagination.value.pageSize,
|
take: pagination.value.pageSize,
|
||||||
skip: pagination.value.current,
|
skip: pagination.value.current,
|
||||||
@ -323,172 +355,170 @@ const fetchData = async () => {
|
|||||||
{ field: 'rstcdStepSort', dir: 'asc' },
|
{ field: 'rstcdStepSort', dir: 'asc' },
|
||||||
{ field: 'siteStepSort', dir: 'asc' }
|
{ field: 'siteStepSort', dir: 'asc' }
|
||||||
]
|
]
|
||||||
}
|
};
|
||||||
|
|
||||||
console.log('【请求参数】', params)
|
console.log('【请求参数】', params);
|
||||||
const res = await vmsstbprptGetKendoList(params)
|
const res = await vmsstbprptGetKendoList(params);
|
||||||
console.log('【响应数据】', res)
|
console.log('【响应数据】', res);
|
||||||
|
|
||||||
// 数据处理
|
// 数据处理
|
||||||
let total = res.data.total
|
let total = res.data.total;
|
||||||
let records = res?.data?.data || res?.data
|
let records = res?.data?.data || res?.data;
|
||||||
// if (Array.isArray(res?.data?.data)) {
|
// if (Array.isArray(res?.data?.data)) {
|
||||||
// records = res.data.data
|
// records = res.data.data
|
||||||
// total = res.data.data.length
|
// total = res.data.data.length
|
||||||
// }
|
// }
|
||||||
|
|
||||||
tableData.value = records || []
|
tableData.value = records || [];
|
||||||
pagination.value.total = total
|
pagination.value.total = total;
|
||||||
loading.value = false
|
loading.value = false;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('获取水文监测数据失败:', error)
|
console.error('获取水文监测数据失败:', error);
|
||||||
tableData.value = []
|
tableData.value = [];
|
||||||
pagination.value.total = 0
|
pagination.value.total = 0;
|
||||||
} finally {
|
} finally {
|
||||||
loading.value = false
|
loading.value = false;
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
const getselect = async () => {
|
const getselect = async () => {
|
||||||
try {
|
try {
|
||||||
const params = {
|
const params = {
|
||||||
"filter": {
|
filter: {
|
||||||
"logic": "and",
|
logic: 'and',
|
||||||
"filters": [
|
filters: [
|
||||||
{
|
{
|
||||||
"field": "wbsType",
|
field: 'wbsType',
|
||||||
"operator": "eq",
|
operator: 'eq',
|
||||||
"dataType": "string",
|
dataType: 'string',
|
||||||
"value": "PSB"
|
value: 'PSB'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"field": "treeLevel",
|
field: 'treeLevel',
|
||||||
"operator": "eq",
|
operator: 'eq',
|
||||||
"dataType": "string",
|
dataType: 'string',
|
||||||
"value": "1"
|
value: '1'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"sort": [
|
sort: [
|
||||||
{
|
{
|
||||||
"field": "orderIndex",
|
field: 'orderIndex',
|
||||||
"dir": "asc"
|
dir: 'asc'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
};
|
||||||
|
|
||||||
const res = await wbsbGetKendoList(params)
|
const res = await wbsbGetKendoList(params);
|
||||||
console.log('【水电站列表】', res)
|
console.log('【水电站列表】', res);
|
||||||
|
|
||||||
// 数据处理:提取水电站列表
|
// 数据处理:提取水电站列表
|
||||||
if (res?.data?.data && Array.isArray(res.data.data)) {
|
if (res?.data?.data && Array.isArray(res.data.data)) {
|
||||||
hydropowerStationList.value = res.data.data
|
hydropowerStationList.value = res.data.data;
|
||||||
console.log('站点数据列表', stationDataList.value)
|
console.log('站点数据列表', stationDataList.value);
|
||||||
getselectTwo(searchData.value.stcd.dataDimensionData)
|
getselectTwo(searchData.value.stcd.dataDimensionData);
|
||||||
console.log('【水电站数量】', hydropowerStationList.value.length)
|
console.log('【水电站数量】', hydropowerStationList.value.length);
|
||||||
} else {
|
} else {
|
||||||
console.warn('【警告】未获取到有效的水电站数据')
|
console.warn('【警告】未获取到有效的水电站数据');
|
||||||
hydropowerStationList.value = []
|
hydropowerStationList.value = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('【错误】获取水电站列表失败:', error)
|
console.error('【错误】获取水电站列表失败:', error);
|
||||||
hydropowerStationList.value = []
|
hydropowerStationList.value = [];
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
const getselectTwo = async (code: any) => {
|
const getselectTwo = async (code: any) => {
|
||||||
console.log('站点数据列表')
|
console.log('站点数据列表');
|
||||||
const params = {
|
const params = {
|
||||||
"filter": {
|
filter: {
|
||||||
"logic": "and",
|
logic: 'and',
|
||||||
"filters": [
|
filters: [
|
||||||
code != 'all' ? {
|
code != 'all'
|
||||||
"field": "baseId",
|
? {
|
||||||
"operator": "eq",
|
field: 'baseId',
|
||||||
"dataType": "string",
|
operator: 'eq',
|
||||||
"value": code
|
dataType: 'string',
|
||||||
} : null,
|
value: code
|
||||||
|
}
|
||||||
|
: null,
|
||||||
{
|
{
|
||||||
"field": "sttpCode",
|
field: 'sttpCode',
|
||||||
"operator": "eq",
|
operator: 'eq',
|
||||||
"dataType": "string",
|
dataType: 'string',
|
||||||
"value": "ENG"
|
value: 'ENG'
|
||||||
}
|
}
|
||||||
].filter(Boolean)
|
].filter(Boolean)
|
||||||
},
|
},
|
||||||
"sort": code !== 'all'
|
sort:
|
||||||
|
code !== 'all'
|
||||||
? [
|
? [
|
||||||
{
|
{
|
||||||
"field": "baseId",
|
field: 'baseId',
|
||||||
"dir": "asc"
|
dir: 'asc'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"field": "rvcdStepSort",
|
field: 'rvcdStepSort',
|
||||||
"dir": "asc"
|
dir: 'asc'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"field": "rstcdStepSort",
|
field: 'rstcdStepSort',
|
||||||
"dir": "asc"
|
dir: 'asc'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"field": "siteStepSort",
|
field: 'siteStepSort',
|
||||||
"dir": "asc"
|
dir: 'asc'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
: [
|
: [
|
||||||
{
|
{
|
||||||
"field": "ttpwr",
|
field: 'ttpwr',
|
||||||
"dir": "desc"
|
dir: 'desc'
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"select": [
|
select: ['stcd', 'stnm']
|
||||||
"stcd",
|
};
|
||||||
"stnm"
|
const res = await getVmsstbprpt(params);
|
||||||
]
|
|
||||||
}
|
|
||||||
const res = await getVmsstbprpt(params)
|
|
||||||
// console.log('结果', res)
|
// console.log('结果', res)
|
||||||
console.log('站点数据列表', res?.data?.data)
|
console.log('站点数据列表', res?.data?.data);
|
||||||
stationDataList.value = res?.data?.data || res?.data
|
stationDataList.value = res?.data?.data || res?.data;
|
||||||
|
};
|
||||||
}
|
|
||||||
|
|
||||||
// ==================== 事件处理 ====================
|
// ==================== 事件处理 ====================
|
||||||
const filterOption = (input: string, option: any) => {
|
const filterOption = (input: string, option: any) => {
|
||||||
// 使用 options 属性时,option 是标准对象 { label, value }
|
// 使用 options 属性时,option 是标准对象 { label, value }
|
||||||
return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
|
return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0;
|
||||||
}
|
};
|
||||||
|
|
||||||
const handleHydropowerStationChange = (value: string) => {
|
const handleHydropowerStationChange = (value: string) => {
|
||||||
console.log('【水电站选择变化】', value)
|
console.log('【水电站选择变化】', value);
|
||||||
// 清空测站选择
|
// 清空测站选择
|
||||||
searchData.value.stnm = null
|
searchData.value.stnm = null;
|
||||||
searchData.value.stcd.stcdId = ''
|
searchData.value.stcd.stcdId = '';
|
||||||
// 获取该水电站下的测站列表
|
// 获取该水电站下的测站列表
|
||||||
getselectTwo(value)
|
getselectTwo(value);
|
||||||
// 重新查询数据
|
// 重新查询数据
|
||||||
// pagination.value.current = 1
|
// pagination.value.current = 1
|
||||||
// fetchData()
|
// fetchData()
|
||||||
}
|
};
|
||||||
|
|
||||||
const handleStationChange = (value: string | undefined) => {
|
const handleStationChange = (value: string | undefined) => {
|
||||||
console.log('【测站选择变化】', value)
|
console.log('【测站选择变化】', value);
|
||||||
// 更新测站ID
|
// 更新测站ID
|
||||||
if (value) {
|
if (value) {
|
||||||
searchData.value.stcd.stcdId = value
|
searchData.value.stcd.stcdId = value;
|
||||||
} else {
|
} else {
|
||||||
searchData.value.stcd.stcdId = ''
|
searchData.value.stcd.stcdId = '';
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
const handleSearch = () => {
|
const handleSearch = () => {
|
||||||
console.log('【搜索】', searchData.value)
|
console.log('【搜索】', searchData.value);
|
||||||
pagination.value.current = 1
|
pagination.value.current = 1;
|
||||||
fetchData()
|
fetchData();
|
||||||
}
|
};
|
||||||
|
|
||||||
const handleReset = () => {
|
const handleReset = () => {
|
||||||
console.log('【重置】')
|
console.log('【重置】');
|
||||||
searchData.value = {
|
searchData.value = {
|
||||||
stcd: {
|
stcd: {
|
||||||
dataDimensionType: 'hyBase',
|
dataDimensionType: 'hyBase',
|
||||||
@ -496,44 +526,44 @@ const handleReset = () => {
|
|||||||
dataDimensionData: props.baseId || 'all'
|
dataDimensionData: props.baseId || 'all'
|
||||||
},
|
},
|
||||||
stnm: null
|
stnm: null
|
||||||
}
|
};
|
||||||
pagination.value.current = 1
|
pagination.value.current = 1;
|
||||||
fetchData()
|
fetchData();
|
||||||
}
|
};
|
||||||
|
|
||||||
const handleTabChange = (key: string) => {
|
const handleTabChange = (key: string) => {
|
||||||
console.log('【Tab切换】', key)
|
console.log('【Tab切换】', key);
|
||||||
tabIndex.value = key
|
tabIndex.value = key;
|
||||||
pagination.value.current = 1
|
pagination.value.current = 1;
|
||||||
fetchData()
|
fetchData();
|
||||||
}
|
};
|
||||||
|
|
||||||
const handleTableChange = (pag: any) => {
|
const handleTableChange = (pag: any) => {
|
||||||
pagination.value.current = pag.current
|
pagination.value.current = pag.current;
|
||||||
pagination.value.pageSize = pag.pageSize
|
pagination.value.pageSize = pag.pageSize;
|
||||||
fetchData()
|
fetchData();
|
||||||
}
|
};
|
||||||
|
|
||||||
const handleViewDetail = (record: any) => {
|
const handleViewDetail = (record: any) => {
|
||||||
if (record) {
|
if (record) {
|
||||||
modelStore.modalVisible = true;
|
modelStore.modalVisible = true;
|
||||||
modelStore.params.sttp = "WT";
|
modelStore.params.sttp = 'WT';
|
||||||
modelStore.title = record.stnm ;
|
modelStore.title = record.stnm;
|
||||||
if (tabIndex.value == "27") {
|
if (tabIndex.value == '27') {
|
||||||
modelStore.params.enfc = "1";
|
modelStore.params.enfc = '1';
|
||||||
}
|
}
|
||||||
modelStore.params.stcd = record.stcd;
|
modelStore.params.stcd = record.stcd;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: 后续实现详情查看功能
|
// TODO: 后续实现详情查看功能
|
||||||
}
|
};
|
||||||
|
|
||||||
// ==================== 生命周期 ====================
|
// ==================== 生命周期 ====================
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
console.log('【组件挂载】水文监测工作二级页面已加载')
|
console.log('【组件挂载】水文监测工作二级页面已加载');
|
||||||
fetchData()
|
fetchData();
|
||||||
getselect()
|
getselect();
|
||||||
})
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
|
|||||||
@ -3,7 +3,6 @@
|
|||||||
<SidePanelItem
|
<SidePanelItem
|
||||||
title="沿程水温变化"
|
title="沿程水温变化"
|
||||||
:prompt="prompts"
|
:prompt="prompts"
|
||||||
:moreSelect="select"
|
|
||||||
:datetimePicker="datetimePicker"
|
:datetimePicker="datetimePicker"
|
||||||
@update-values="handlePanelChange1"
|
@update-values="handlePanelChange1"
|
||||||
>
|
>
|
||||||
@ -21,7 +20,14 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref, onMounted, onBeforeUnmount, watch, computed } from 'vue';
|
import {
|
||||||
|
ref,
|
||||||
|
onMounted,
|
||||||
|
onBeforeUnmount,
|
||||||
|
watch,
|
||||||
|
computed,
|
||||||
|
defineOptions
|
||||||
|
} from 'vue';
|
||||||
import * as echarts from 'echarts';
|
import * as echarts from 'echarts';
|
||||||
import type { ECharts } from 'echarts';
|
import type { ECharts } from 'echarts';
|
||||||
import SidePanelItem from '@/components/SidePanelItem/index.vue';
|
import SidePanelItem from '@/components/SidePanelItem/index.vue';
|
||||||
@ -293,12 +299,14 @@ const init = async () => {
|
|||||||
filter: {
|
filter: {
|
||||||
logic: 'and',
|
logic: 'and',
|
||||||
filters: [
|
filters: [
|
||||||
select.value.value != 'all' ? {
|
select.value.value != 'all'
|
||||||
|
? {
|
||||||
field: 'baseId',
|
field: 'baseId',
|
||||||
operator: 'eq',
|
operator: 'eq',
|
||||||
dataType: 'string',
|
dataType: 'string',
|
||||||
value: select.value.value
|
value: select.value.value
|
||||||
}:null,
|
}
|
||||||
|
: null,
|
||||||
{
|
{
|
||||||
field: 'tm',
|
field: 'tm',
|
||||||
operator: 'gte',
|
operator: 'gte',
|
||||||
@ -374,7 +382,6 @@ const init = async () => {
|
|||||||
};
|
};
|
||||||
//监听子组件的数据变化
|
//监听子组件的数据变化
|
||||||
const handlePanelChange1 = data => {
|
const handlePanelChange1 = data => {
|
||||||
|
|
||||||
console.log('当前所有控件状态:', data);
|
console.log('当前所有控件状态:', data);
|
||||||
if (data.moreSelect) {
|
if (data.moreSelect) {
|
||||||
select.value.value = data.moreSelect;
|
select.value.value = data.moreSelect;
|
||||||
@ -402,15 +409,6 @@ const handlePanelChange1 = data => {
|
|||||||
const previousDate = new Date(year, month - 2, day, 0, 0, 0);
|
const previousDate = new Date(year, month - 2, day, 0, 0, 0);
|
||||||
|
|
||||||
// 格式化函数:将 Date 对象转为 "YYYY-MM-DD HH:mm:ss" 格式
|
// 格式化函数:将 Date 对象转为 "YYYY-MM-DD HH:mm:ss" 格式
|
||||||
function formatDate(date) {
|
|
||||||
const y = date.getFullYear();
|
|
||||||
const m = String(date.getMonth() + 1).padStart(2, '0');
|
|
||||||
const d = String(date.getDate()).padStart(2, '0');
|
|
||||||
const h = String(date.getHours()).padStart(2, '0');
|
|
||||||
const min = String(date.getMinutes()).padStart(2, '0');
|
|
||||||
const s = String(date.getSeconds()).padStart(2, '0');
|
|
||||||
return `${y}-${m}-${d} ${h}:${min}:${s}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 生成结果数组
|
// 生成结果数组
|
||||||
const result = [
|
const result = [
|
||||||
@ -425,12 +423,21 @@ const handlePanelChange1 = data => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function formatDate(date) {
|
||||||
|
const y = date.getFullYear();
|
||||||
|
const m = String(date.getMonth() + 1).padStart(2, '0');
|
||||||
|
const d = String(date.getDate()).padStart(2, '0');
|
||||||
|
const h = String(date.getHours()).padStart(2, '0');
|
||||||
|
const min = String(date.getMinutes()).padStart(2, '0');
|
||||||
|
const s = String(date.getSeconds()).padStart(2, '0');
|
||||||
|
return `${y}-${m}-${d} ${h}:${min}:${s}`;
|
||||||
|
}
|
||||||
watch(
|
watch(
|
||||||
() => JidiSelectEventStore.selectedItem,
|
() => JidiSelectEventStore.selectedItem,
|
||||||
newVal => {
|
newVal => {
|
||||||
// if (newVal.baseId) {
|
// if (newVal.baseId) {
|
||||||
select.value.value = newVal.wbsCode;
|
select.value.value = newVal.wbsCode;
|
||||||
// init();
|
init();
|
||||||
// }
|
// }
|
||||||
},
|
},
|
||||||
{ deep: true, immediate: true }
|
{ deep: true, immediate: true }
|
||||||
|
|||||||
467
frontend/src/modules/zengzhizhanfbutongji/index.vue
Normal file
467
frontend/src/modules/zengzhizhanfbutongji/index.vue
Normal file
@ -0,0 +1,467 @@
|
|||||||
|
<!-- SidePanelItem.vue -->
|
||||||
|
<template>
|
||||||
|
<SidePanelItem
|
||||||
|
title="各水电基地增殖站分布统计"
|
||||||
|
:datetimePicker="datetimePicker"
|
||||||
|
@update-values="handlePanelChange1"
|
||||||
|
>
|
||||||
|
<a-spin :spinning="loading" tip="加载中...">
|
||||||
|
<div
|
||||||
|
v-show="showemit && !loading"
|
||||||
|
ref="chartRef"
|
||||||
|
class="chart-container"
|
||||||
|
></div>
|
||||||
|
<div v-show="!showemit && !loading" class="chart-container">
|
||||||
|
<a-empty />
|
||||||
|
</div>
|
||||||
|
</a-spin>
|
||||||
|
</SidePanelItem>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import {
|
||||||
|
ref,
|
||||||
|
onMounted,
|
||||||
|
onBeforeUnmount,
|
||||||
|
watch,
|
||||||
|
computed,
|
||||||
|
defineOptions
|
||||||
|
} from 'vue';
|
||||||
|
import * as echarts from 'echarts';
|
||||||
|
import type { ECharts } from 'echarts';
|
||||||
|
import SidePanelItem from '@/components/SidePanelItem/index.vue';
|
||||||
|
import { getKendoListCust } from '@/api/sw';
|
||||||
|
import { useJidiSelectEventStore } from '@/store/modules/jidiSelectEvent';
|
||||||
|
|
||||||
|
import { useModelStore } from '@/store/modules/model';
|
||||||
|
|
||||||
|
const modelStore = useModelStore();
|
||||||
|
|
||||||
|
// 定义组件名(便于调试和递归)
|
||||||
|
defineOptions({
|
||||||
|
name: 'yanchengshuiwenChangeMod'
|
||||||
|
});
|
||||||
|
// 改为动态获取当前时间
|
||||||
|
const getCurrentHourTime = () => {
|
||||||
|
const now = new Date();
|
||||||
|
// 减去一小时
|
||||||
|
now.setHours(now.getHours() - 1);
|
||||||
|
const year = now.getFullYear();
|
||||||
|
const month = String(now.getMonth() + 1).padStart(2, '0');
|
||||||
|
const day = String(now.getDate()).padStart(2, '0');
|
||||||
|
const hour = String(now.getHours()).padStart(2, '0');
|
||||||
|
return `${year}-${month}-${day} ${hour}`;
|
||||||
|
};
|
||||||
|
const JidiSelectEventStore = useJidiSelectEventStore();
|
||||||
|
const paramsOne = {
|
||||||
|
tm: []
|
||||||
|
};
|
||||||
|
const dataOne = ref('');
|
||||||
|
// 保存原始表格数据,用于提取 minTm
|
||||||
|
const rawTableData = ref<any[]>([]);
|
||||||
|
// 使用 computed 使 prompts 基于原始数据的 minTm 字段
|
||||||
|
const showemit = ref(false);
|
||||||
|
const loading = ref(false);
|
||||||
|
// const selectOptions: any = ref([])
|
||||||
|
const optionsFmt = (options: any) =>
|
||||||
|
options.map((item: any) => ({
|
||||||
|
label: item.wbsName,
|
||||||
|
value: item.wbsCode
|
||||||
|
}));
|
||||||
|
|
||||||
|
const chartRef = ref<HTMLElement | null>(null);
|
||||||
|
let chartInstance: ECharts | null = null;
|
||||||
|
|
||||||
|
// 静态数据 - 站点名称
|
||||||
|
const stationNames = ref([]);
|
||||||
|
|
||||||
|
// 静态数据 - 水温值 (°C),班多无数据设为 null,使用对象格式携带stcd信息
|
||||||
|
const waterTemperatures = ref<
|
||||||
|
{ value: number | null; stcd: string; stnm: string }[]
|
||||||
|
>([]);
|
||||||
|
|
||||||
|
// 使用 computed 使 currentTime 响应 dataOne.value 的变化
|
||||||
|
const currentTime = computed(() => dataOne.value || getCurrentHourTime());
|
||||||
|
// 选择器配置
|
||||||
|
const select = ref({
|
||||||
|
show: true,
|
||||||
|
value: '',
|
||||||
|
options: [],
|
||||||
|
picker: undefined,
|
||||||
|
format: undefined
|
||||||
|
});
|
||||||
|
|
||||||
|
// 日期选择器配置
|
||||||
|
const datetimePicker = ref({
|
||||||
|
show: true,
|
||||||
|
value: computed(() => getCurrentHourTime()),
|
||||||
|
format: 'YYYY-MM-DD HH',
|
||||||
|
picker: 'date' as const,
|
||||||
|
options: []
|
||||||
|
});
|
||||||
|
// 初始化图表
|
||||||
|
const initChart = () => {
|
||||||
|
if (!chartRef.value) return;
|
||||||
|
|
||||||
|
chartInstance = echarts.init(chartRef.value);
|
||||||
|
|
||||||
|
const option = {
|
||||||
|
title: {
|
||||||
|
text: '水温(°C)',
|
||||||
|
left: 5,
|
||||||
|
top: 0,
|
||||||
|
textStyle: {
|
||||||
|
color: '#000000',
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: 'normal'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
dataZoom: [
|
||||||
|
{
|
||||||
|
type: 'inside',
|
||||||
|
start: 0,
|
||||||
|
end: 100,
|
||||||
|
zoomOnMouseWheel: true,
|
||||||
|
moveOnMouseMove: true,
|
||||||
|
filterMode: 'filter'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'axis',
|
||||||
|
formatter: (params: any) => {
|
||||||
|
if (params && params.length > 0) {
|
||||||
|
const data = params[0];
|
||||||
|
// 根据项目规范:空值统一显示为短横线 `-`
|
||||||
|
if (data.value != null) {
|
||||||
|
return `${currentTime.value}<br/>${data.name}:${data.value}°C`;
|
||||||
|
} else {
|
||||||
|
return `${currentTime.value}<br/>${data.name}:-`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
left: '3%',
|
||||||
|
right: '4%',
|
||||||
|
bottom: '3%',
|
||||||
|
top: '15%',
|
||||||
|
containLabel: true
|
||||||
|
},
|
||||||
|
xAxis: {
|
||||||
|
type: 'category',
|
||||||
|
data: stationNames.value,
|
||||||
|
boundaryGap: true,
|
||||||
|
axisLine: {
|
||||||
|
show: true,
|
||||||
|
lineStyle: {
|
||||||
|
color: '#8f8f8f'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
axisTick: {
|
||||||
|
show: false
|
||||||
|
},
|
||||||
|
axisLabel: {
|
||||||
|
color: '#333',
|
||||||
|
fontSize: 12,
|
||||||
|
margin: 8,
|
||||||
|
interval: 0,
|
||||||
|
formatter: (value: string, index: number) => {
|
||||||
|
// 偶数索引(0, 2, 4, 6)的标签在上方
|
||||||
|
// 奇数索引(1, 3, 5)的标签在下方
|
||||||
|
// 通过添加空行实现上下错位
|
||||||
|
if (index % 2 === 0) {
|
||||||
|
return `${value}\n `; // 上方标签:文字 + 换行 + 空格占位
|
||||||
|
} else {
|
||||||
|
return ` \n${value}`; // 下方标签:空格占位 + 换行 + 文字
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
splitLine: {
|
||||||
|
show: true,
|
||||||
|
lineStyle: {
|
||||||
|
color: '#e0e0e0',
|
||||||
|
type: 'solid'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
yAxis: {
|
||||||
|
type: 'value',
|
||||||
|
min: 0,
|
||||||
|
// max: 10,
|
||||||
|
interval: 2,
|
||||||
|
axisLine: {
|
||||||
|
show: true,
|
||||||
|
lineStyle: {
|
||||||
|
color: '#8f8f8f'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
axisTick: {
|
||||||
|
show: true,
|
||||||
|
length: 3,
|
||||||
|
lineStyle: {
|
||||||
|
color: '#8f8f8f'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
axisLabel: {
|
||||||
|
color: '#333',
|
||||||
|
fontSize: 12
|
||||||
|
},
|
||||||
|
splitLine: {
|
||||||
|
show: true,
|
||||||
|
lineStyle: {
|
||||||
|
color: '#e0e0e0',
|
||||||
|
type: 'solid'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
name: '水温',
|
||||||
|
type: 'line',
|
||||||
|
smooth: true,
|
||||||
|
symbol: 'circle',
|
||||||
|
symbolSize: 6,
|
||||||
|
connectNulls: true, // 连接空值两侧的线段,避免折线断开
|
||||||
|
lineStyle: {
|
||||||
|
color: '#6ca4f7',
|
||||||
|
width: 2
|
||||||
|
},
|
||||||
|
itemStyle: {
|
||||||
|
color: '#6ca4f7'
|
||||||
|
},
|
||||||
|
areaStyle: {
|
||||||
|
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||||
|
{
|
||||||
|
offset: 0,
|
||||||
|
color: 'rgba(108, 164, 247, 0.3)'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
offset: 1,
|
||||||
|
color: 'rgba(108, 164, 247, 0.05)'
|
||||||
|
}
|
||||||
|
])
|
||||||
|
},
|
||||||
|
data: waterTemperatures.value
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
chartInstance.setOption(option);
|
||||||
|
|
||||||
|
// 添加点击事件监听器
|
||||||
|
chartInstance.on('click', (params: any) => {
|
||||||
|
if (params.componentType === 'series') {
|
||||||
|
console.log('点击数据点:', params);
|
||||||
|
console.log('stcd:', params.data.stcd);
|
||||||
|
console.log('stnm:', params.data.stnm);
|
||||||
|
console.log('temperature:', params.data.value);
|
||||||
|
console.log('temperature:', paramsOne.tm);
|
||||||
|
modelStore.modalVisible = true;
|
||||||
|
modelStore.params.sttp = 'WT';
|
||||||
|
modelStore.title = params.data.stnm;
|
||||||
|
modelStore.params.stcd = params.data.stcd;
|
||||||
|
modelStore.filter.rangeTm = [paramsOne.tm[1], paramsOne.tm[0]];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// 处理窗口大小变化
|
||||||
|
const handleResize = () => {
|
||||||
|
chartInstance?.resize();
|
||||||
|
};
|
||||||
|
|
||||||
|
const init = async () => {
|
||||||
|
if (loading.value) return; // 防止重复请求
|
||||||
|
loading.value = true;
|
||||||
|
try {
|
||||||
|
const params = {
|
||||||
|
filter: {
|
||||||
|
logic: 'and',
|
||||||
|
filters: [
|
||||||
|
select.value.value != 'all'
|
||||||
|
? {
|
||||||
|
field: 'baseId',
|
||||||
|
operator: 'eq',
|
||||||
|
dataType: 'string',
|
||||||
|
value: select.value.value
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
{
|
||||||
|
field: 'tm',
|
||||||
|
operator: 'gte',
|
||||||
|
dataType: 'date',
|
||||||
|
value: paramsOne.tm[1]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'tm',
|
||||||
|
operator: 'lte',
|
||||||
|
dataType: 'date',
|
||||||
|
value: paramsOne.tm[0]
|
||||||
|
}
|
||||||
|
].filter(Boolean)
|
||||||
|
},
|
||||||
|
sort: [{ field: 'sort', dir: 'asc' }]
|
||||||
|
};
|
||||||
|
|
||||||
|
let res = await getKendoListCust(params);
|
||||||
|
let data = res.data.data || res.data;
|
||||||
|
|
||||||
|
// 保存原始数据,用于提取 minTm
|
||||||
|
rawTableData.value = data;
|
||||||
|
|
||||||
|
if (data.length > 0) {
|
||||||
|
showemit.value = true;
|
||||||
|
} else {
|
||||||
|
showemit.value = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
stationNames.value = data
|
||||||
|
.filter((item: any) => item.sttp == '1')
|
||||||
|
.map((item: any) => item.stnm);
|
||||||
|
console.log(stationNames.value);
|
||||||
|
|
||||||
|
// 修改为对象格式,携带stcd和stnm信息
|
||||||
|
waterTemperatures.value = data
|
||||||
|
.filter((item: any) => item.sttp == '2')
|
||||||
|
.map((item: any) => ({
|
||||||
|
value: item.temperature,
|
||||||
|
stcd: item.stcd,
|
||||||
|
stnm: item.stnm
|
||||||
|
}));
|
||||||
|
console.log(waterTemperatures.value);
|
||||||
|
|
||||||
|
// 数据获取完成后,初始化或更新图表
|
||||||
|
setTimeout(() => {
|
||||||
|
if (!chartInstance) {
|
||||||
|
initChart();
|
||||||
|
} else {
|
||||||
|
// 如果图表已存在,只更新数据
|
||||||
|
chartInstance.setOption({
|
||||||
|
xAxis: {
|
||||||
|
data: stationNames.value
|
||||||
|
},
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
data: waterTemperatures.value
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// 强制重绘,确保尺寸正确
|
||||||
|
setTimeout(() => {
|
||||||
|
chartInstance?.resize();
|
||||||
|
}, 100);
|
||||||
|
}, 50);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('数据加载失败:', error);
|
||||||
|
showemit.value = false;
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
//监听子组件的数据变化
|
||||||
|
const handlePanelChange1 = data => {
|
||||||
|
console.log('当前所有控件状态:', data);
|
||||||
|
if (data.moreSelect) {
|
||||||
|
select.value.value = data.moreSelect;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.datetime) {
|
||||||
|
dataOne.value = data.datetime;
|
||||||
|
const datetime = data.datetime;
|
||||||
|
|
||||||
|
// 解析日期和时间
|
||||||
|
const [datePart, hourPart] = datetime.split(' ');
|
||||||
|
const [year, month, day] = datePart.split('-').map(Number);
|
||||||
|
|
||||||
|
// 创建当前时间的 Date 对象
|
||||||
|
const currentDate = new Date(
|
||||||
|
year,
|
||||||
|
month - 1,
|
||||||
|
day,
|
||||||
|
parseInt(hourPart),
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
);
|
||||||
|
|
||||||
|
// 创建一个月前的 Date 对象
|
||||||
|
const previousDate = new Date(year, month - 2, day, 0, 0, 0);
|
||||||
|
|
||||||
|
// 格式化函数:将 Date 对象转为 "YYYY-MM-DD HH:mm:ss" 格式
|
||||||
|
|
||||||
|
// 生成结果数组
|
||||||
|
const result = [
|
||||||
|
formatDate(currentDate), // "2026-05-15 15:00:00"
|
||||||
|
formatDate(previousDate) // "2026-04-15 00:00:00"
|
||||||
|
];
|
||||||
|
paramsOne.tm = result;
|
||||||
|
console.log(paramsOne.tm, 'paramsOne.tm');
|
||||||
|
}
|
||||||
|
if (select.value.value || paramsOne.tm.length > 0) {
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
function formatDate(date) {
|
||||||
|
const y = date.getFullYear();
|
||||||
|
const m = String(date.getMonth() + 1).padStart(2, '0');
|
||||||
|
const d = String(date.getDate()).padStart(2, '0');
|
||||||
|
const h = String(date.getHours()).padStart(2, '0');
|
||||||
|
const min = String(date.getMinutes()).padStart(2, '0');
|
||||||
|
const s = String(date.getSeconds()).padStart(2, '0');
|
||||||
|
return `${y}-${m}-${d} ${h}:${min}:${s}`;
|
||||||
|
}
|
||||||
|
watch(
|
||||||
|
() => JidiSelectEventStore.selectedItem,
|
||||||
|
newVal => {
|
||||||
|
// if (newVal.baseId) {
|
||||||
|
select.value.value = newVal.wbsCode;
|
||||||
|
init();
|
||||||
|
// }
|
||||||
|
},
|
||||||
|
{ deep: true, immediate: true }
|
||||||
|
);
|
||||||
|
|
||||||
|
// 监听 JidiSelectEventStore.jidiData 变化,同步下拉框选项(不过滤"当前全部"项)
|
||||||
|
watch(
|
||||||
|
() => JidiSelectEventStore.jidiData,
|
||||||
|
newVal => {
|
||||||
|
select.value.options = newVal.map(item => ({
|
||||||
|
value: item.wbsCode,
|
||||||
|
title: item.wbsName
|
||||||
|
}));
|
||||||
|
// 如果当前没有选中值,默认选中第一个有效项
|
||||||
|
if (!select.value.value && newVal.length > 0) {
|
||||||
|
select.value.value = newVal[0].baseId || newVal[0].wbsCode;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ deep: true, immediate: true }
|
||||||
|
);
|
||||||
|
// 页面加载时执行的逻辑
|
||||||
|
onMounted(() => {
|
||||||
|
// 延迟初始化,确保 DOM 渲染完成
|
||||||
|
// init();
|
||||||
|
window.addEventListener('resize', handleResize);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 组件卸载前清理
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
window.removeEventListener('resize', handleResize);
|
||||||
|
chartInstance?.dispose();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.chart-container {
|
||||||
|
width: 100%;
|
||||||
|
height: 252px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.ant-spin-nested-loading) {
|
||||||
|
height: 252px !important;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -800,7 +800,10 @@ export const useMapStore = defineStore('map', () => {
|
|||||||
mapDataStore.setPointLayerCacheChecked(key, true);
|
mapDataStore.setPointLayerCacheChecked(key, true);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
console.warn(`图层 ${key} 没有缓存数据`);
|
// 无缓存数据:勾选态下触发加载,否则图层树勾选无缓存图层时锚点永远不出现。
|
||||||
|
// 不阻塞调用方(updateLayerData 会 await 本函数),交给 loadLayerData 的
|
||||||
|
// 请求去重 / abort / 图例暂存机制统一处理。
|
||||||
|
void loadLayerData(layer);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -1360,6 +1363,7 @@ export const useMapStore = defineStore('map', () => {
|
|||||||
requestData
|
requestData
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
if (hasEffectivePointLayerCache(key, requestIdentifier)) {
|
if (hasEffectivePointLayerCache(key, requestIdentifier)) {
|
||||||
syncBackgroundCacheToReactiveStore(key);
|
syncBackgroundCacheToReactiveStore(key);
|
||||||
const cachedList = getEffectivePointLayerData(key);
|
const cachedList = getEffectivePointLayerData(key);
|
||||||
|
|||||||
@ -22,6 +22,12 @@ export const useModelStore = defineStore('model', () => {
|
|||||||
const wqShow = ref<boolean>(false); // 水质 AI 预测显示
|
const wqShow = ref<boolean>(false); // 水质 AI 预测显示
|
||||||
const fishTabInfo = ref<any>(null); // 鱼类监测数据信息
|
const fishTabInfo = ref<any>(null); // 鱼类监测数据信息
|
||||||
const selectedAnchorPoint = ref<any>(null); // 地图筛选下拉选中的点位对象(供基本信息等面板消费)
|
const selectedAnchorPoint = ref<any>(null); // 地图筛选下拉选中的点位对象(供基本信息等面板消费)
|
||||||
|
// 全局抽屉内容模式:'basicInfo' 展示基础信息,'speciesIntro' 展示物种介绍(动物/植物介绍)
|
||||||
|
const globalDrawerMode = ref<'basicInfo' | 'speciesIntro'>('basicInfo');
|
||||||
|
// 鱼种下拉选中的物种列表(供物种介绍面板展示;预留 image/intro 等字段,后续对接真实数据源)
|
||||||
|
const selectedFishSpecies = ref<
|
||||||
|
Array<{ label: string; value: string; image?: string; intro?: string }>
|
||||||
|
>([]);
|
||||||
|
|
||||||
const params = ref<{
|
const params = ref<{
|
||||||
sttp: string;
|
sttp: string;
|
||||||
@ -77,6 +83,8 @@ export const useModelStore = defineStore('model', () => {
|
|||||||
wtConfig,
|
wtConfig,
|
||||||
wqShow,
|
wqShow,
|
||||||
fishTabInfo,
|
fishTabInfo,
|
||||||
selectedAnchorPoint
|
selectedAnchorPoint,
|
||||||
|
globalDrawerMode,
|
||||||
|
selectedFishSpecies
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|||||||
@ -8,6 +8,17 @@ import { ref } from 'vue';
|
|||||||
const modules = import.meta.glob('../../views/**/**.vue');
|
const modules = import.meta.glob('../../views/**/**.vue');
|
||||||
export const Layout = () => import('@/layout/index.vue');
|
export const Layout = () => import('@/layout/index.vue');
|
||||||
|
|
||||||
|
// 路由生成后后台预取所有页面 chunk:首次导航若依赖懒加载 chunk 下载,
|
||||||
|
// 会被首页地图加载的十几个接口挤占连接导致页面迟迟不切换,预取后切页即时生效。
|
||||||
|
let viewPrefetchStarted = false;
|
||||||
|
const prefetchViewChunks = () => {
|
||||||
|
if (viewPrefetchStarted) return;
|
||||||
|
viewPrefetchStarted = true;
|
||||||
|
Object.values(modules).forEach(load => {
|
||||||
|
load().catch(() => {});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const filterAsyncRoutes = (routes: RouteRecordRaw[], roles: string[]) => {
|
const filterAsyncRoutes = (routes: RouteRecordRaw[], roles: string[]) => {
|
||||||
const res: RouteRecordRaw[] = [];
|
const res: RouteRecordRaw[] = [];
|
||||||
routes.forEach(route => {
|
routes.forEach(route => {
|
||||||
@ -61,6 +72,8 @@ export const usePermissionStore = defineStore('permission', () => {
|
|||||||
const asyncRoutes: any = response;
|
const asyncRoutes: any = response;
|
||||||
const accessedRoutes = filterAsyncRoutes(asyncRoutes, roles);
|
const accessedRoutes = filterAsyncRoutes(asyncRoutes, roles);
|
||||||
setRoutes(accessedRoutes);
|
setRoutes(accessedRoutes);
|
||||||
|
// 后台预取页面 chunk,不等结果,不阻塞导航
|
||||||
|
prefetchViewChunks();
|
||||||
resolve(accessedRoutes);
|
resolve(accessedRoutes);
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
|
|||||||
@ -173,15 +173,21 @@ export function downloadFileByUrl(url: string, fileName?: string) {
|
|||||||
document.body.removeChild(link);
|
document.body.removeChild(link);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
const modules = import.meta.glob('@/assets/legend/*.{svg,png}', {
|
// 图例图标以 data URL 形式内联进打包产物(?raw + encodeURIComponent),
|
||||||
eager: true
|
// 图标零网络请求、无跨域/连接池争用问题,所有图层(无论是否勾选)的图标均即时可用。
|
||||||
|
const modules = import.meta.glob('@/assets/legend/*.svg', {
|
||||||
|
eager: true,
|
||||||
|
query: '?raw',
|
||||||
|
import: 'default'
|
||||||
});
|
});
|
||||||
// 图例图标映射
|
// 图例图标映射
|
||||||
export const iconMap: Record<string, string> = {};
|
export const iconMap: Record<string, string> = {};
|
||||||
Object.entries(modules).forEach(([path, module]) => {
|
Object.entries(modules).forEach(([path, module]) => {
|
||||||
const fileName = path.match(/\/([^/]+)\.(svg|png)$/)?.[1];
|
const fileName = path.match(/\/([^/]+)\.svg$/)?.[1];
|
||||||
if (fileName) {
|
if (fileName) {
|
||||||
iconMap[fileName] = (module as any).default;
|
iconMap[fileName] = `data:image/svg+xml,${encodeURIComponent(
|
||||||
|
module as string
|
||||||
|
)}`;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
// 获取图标路径
|
// 获取图标路径
|
||||||
|
|||||||
296
frontend/src/views/moXingGuanLi/MapStationPicker.vue
Normal file
296
frontend/src/views/moXingGuanLi/MapStationPicker.vue
Normal file
@ -0,0 +1,296 @@
|
|||||||
|
<template>
|
||||||
|
<a-modal
|
||||||
|
:visible="visible"
|
||||||
|
:title="title"
|
||||||
|
:footer="null"
|
||||||
|
width="1200px"
|
||||||
|
:body-style="{ padding: 0, height: '620px' }"
|
||||||
|
:z-index="1050"
|
||||||
|
@cancel="handleCancel"
|
||||||
|
destroy-on-close
|
||||||
|
>
|
||||||
|
<div class="msp-toolbar">
|
||||||
|
<span class="msp-toolbar-label">搜索范围:</span>
|
||||||
|
<a-input-number
|
||||||
|
v-model:value="searchRadius"
|
||||||
|
:min="1"
|
||||||
|
:max="500"
|
||||||
|
style="width: 80px"
|
||||||
|
@blur="onRadiusConfirm"
|
||||||
|
@keyup.enter="onRadiusConfirm"
|
||||||
|
/>
|
||||||
|
<span class="msp-unit">km</span>
|
||||||
|
<a-button
|
||||||
|
type="primary"
|
||||||
|
size="small"
|
||||||
|
style="margin-left: 8px"
|
||||||
|
@click="handleSave"
|
||||||
|
>保存设置</a-button
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="msp-map-area">
|
||||||
|
<div ref="mapContainer" class="msp-map-container" />
|
||||||
|
|
||||||
|
<!-- 地图工具按钮(悬浮在地图底部) -->
|
||||||
|
<div class="msp-map-tools">
|
||||||
|
<a-button size="small" class="msp-tool-btn" @click="handleZoomIn"
|
||||||
|
>+</a-button
|
||||||
|
>
|
||||||
|
<a-button size="small" class="msp-tool-btn" @click="handleZoomOut"
|
||||||
|
>-</a-button
|
||||||
|
>
|
||||||
|
<a-button
|
||||||
|
size="small"
|
||||||
|
class="msp-tool-btn"
|
||||||
|
:type="isMeasuring ? 'primary' : 'default'"
|
||||||
|
@click="handleToggleMeasure"
|
||||||
|
>测距</a-button
|
||||||
|
>
|
||||||
|
<a-button size="small" class="msp-tool-btn" @click="handleClearMeasure"
|
||||||
|
>清除</a-button
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 右侧悬浮面板 -->
|
||||||
|
<div class="msp-right-panel">
|
||||||
|
<div class="msp-panel-title">站点列表</div>
|
||||||
|
<div class="msp-station-list">
|
||||||
|
<div
|
||||||
|
v-for="station in filteredStations"
|
||||||
|
:key="station.name"
|
||||||
|
class="msp-station-item"
|
||||||
|
:class="{ 'msp-station-selected': selectedNames.has(station.name) }"
|
||||||
|
@click="toggleStation(station.name)"
|
||||||
|
>
|
||||||
|
<span class="msp-station-name">{{ station.name }}</span>
|
||||||
|
<span class="msp-station-type">{{ station.type }}</span>
|
||||||
|
<span
|
||||||
|
class="msp-station-check"
|
||||||
|
:class="{ 'msp-check-visible': selectedNames.has(station.name) }"
|
||||||
|
>
|
||||||
|
<CheckOutlined />
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a-modal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, computed, watch, nextTick } from 'vue';
|
||||||
|
import { CheckOutlined } from '@ant-design/icons-vue';
|
||||||
|
import { StationMap } from './map.station';
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
visible: boolean;
|
||||||
|
title: string;
|
||||||
|
type: 'weather' | 'upstream';
|
||||||
|
centerLon?: number;
|
||||||
|
centerLat?: number;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(e: 'update:visible', val: boolean): void;
|
||||||
|
(e: 'save', selected: string[]): void;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const stationMap = new StationMap();
|
||||||
|
const mapContainer = ref<HTMLElement | null>(null);
|
||||||
|
const isMeasuring = ref(false);
|
||||||
|
|
||||||
|
const searchRadius = ref(10);
|
||||||
|
|
||||||
|
const allStations = [
|
||||||
|
{ name: '气象站A', type: '气象站' },
|
||||||
|
{ name: '气象站B', type: '气象站' },
|
||||||
|
{ name: '气象站C', type: '气象站' },
|
||||||
|
{ name: '上游站1', type: '上游站' },
|
||||||
|
{ name: '上游站2', type: '上游站' },
|
||||||
|
{ name: '上游站3', type: '上游站' }
|
||||||
|
];
|
||||||
|
|
||||||
|
const filteredStations = computed(() => {
|
||||||
|
const targetTypeLabel = props.type === 'weather' ? '气象站' : '上游站';
|
||||||
|
return allStations.filter(s => s.type === targetTypeLabel);
|
||||||
|
});
|
||||||
|
|
||||||
|
const selectedNames = ref<Set<string>>(new Set());
|
||||||
|
|
||||||
|
const toggleStation = (name: string) => {
|
||||||
|
const s = new Set(selectedNames.value);
|
||||||
|
if (s.has(name)) s.delete(name);
|
||||||
|
else s.add(name);
|
||||||
|
selectedNames.value = s;
|
||||||
|
};
|
||||||
|
|
||||||
|
const initMap = async () => {
|
||||||
|
await nextTick();
|
||||||
|
setTimeout(() => {
|
||||||
|
if (!mapContainer.value) return;
|
||||||
|
const lon = props.centerLon ?? 114.171;
|
||||||
|
const lat = props.centerLat ?? 38;
|
||||||
|
stationMap.init(mapContainer.value, lon, lat, searchRadius.value);
|
||||||
|
}, 100);
|
||||||
|
};
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.visible,
|
||||||
|
async val => {
|
||||||
|
if (val) {
|
||||||
|
// 重置状态
|
||||||
|
searchRadius.value = 10;
|
||||||
|
isMeasuring.value = false;
|
||||||
|
selectedNames.value = new Set();
|
||||||
|
await initMap();
|
||||||
|
} else {
|
||||||
|
stationMap.destroy();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// 搜索范围确认后更新地图(失焦或回车)
|
||||||
|
const onRadiusConfirm = () => {
|
||||||
|
const val = searchRadius.value;
|
||||||
|
if (val && val >= 1) {
|
||||||
|
stationMap.updateRange(val);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleZoomIn = () => stationMap.zoomIn();
|
||||||
|
const handleZoomOut = () => stationMap.zoomOut();
|
||||||
|
const handleToggleMeasure = () => {
|
||||||
|
stationMap.toggleMeasure();
|
||||||
|
isMeasuring.value = stationMap.isMeasureActive;
|
||||||
|
};
|
||||||
|
const handleClearMeasure = () => {
|
||||||
|
stationMap.clearMeasure();
|
||||||
|
isMeasuring.value = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSave = () => {
|
||||||
|
emit('save', Array.from(selectedNames.value));
|
||||||
|
emit('update:visible', false);
|
||||||
|
};
|
||||||
|
const handleCancel = () => emit('update:visible', false);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.msp-toolbar {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 8px 16px;
|
||||||
|
background: #fafafa;
|
||||||
|
border-bottom: 1px solid #e8e8e8;
|
||||||
|
}
|
||||||
|
.msp-toolbar-label {
|
||||||
|
font-size: 13px;
|
||||||
|
color: #303133;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
.msp-unit {
|
||||||
|
font-size: 13px;
|
||||||
|
color: #606266;
|
||||||
|
margin-left: 4px;
|
||||||
|
margin-right: 8px;
|
||||||
|
}
|
||||||
|
.msp-map-area {
|
||||||
|
position: relative;
|
||||||
|
height: calc(100% - 42px);
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.msp-map-container {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
.msp-map-tools {
|
||||||
|
position: absolute;
|
||||||
|
left: 50%;
|
||||||
|
bottom: 16px;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
display: flex;
|
||||||
|
gap: 6px;
|
||||||
|
background: rgba(255, 255, 255, 0.9);
|
||||||
|
padding: 6px 10px;
|
||||||
|
border-radius: 6px;
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
.msp-tool-btn {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 13px;
|
||||||
|
min-width: 30px;
|
||||||
|
height: 30px;
|
||||||
|
}
|
||||||
|
.msp-right-panel {
|
||||||
|
position: absolute;
|
||||||
|
right: 12px;
|
||||||
|
top: 12px;
|
||||||
|
bottom: 12px;
|
||||||
|
width: 200px;
|
||||||
|
background: rgba(255, 255, 255, 0.95);
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 10px;
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.12);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
.msp-panel-title {
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #005293;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
padding-bottom: 6px;
|
||||||
|
border-bottom: 1px solid #e8ecf1;
|
||||||
|
}
|
||||||
|
.msp-station-list {
|
||||||
|
flex: 1;
|
||||||
|
overflow-y: auto;
|
||||||
|
margin: 0 -10px;
|
||||||
|
padding: 0 10px;
|
||||||
|
}
|
||||||
|
.msp-station-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 8px 10px;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.15s;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
.msp-station-item:hover {
|
||||||
|
background: #f0f5ff;
|
||||||
|
border-color: #d6e4ff;
|
||||||
|
}
|
||||||
|
.msp-station-selected {
|
||||||
|
background: #f0f5ff;
|
||||||
|
border-color: #005293;
|
||||||
|
}
|
||||||
|
.msp-station-name {
|
||||||
|
flex: 1;
|
||||||
|
font-size: 13px;
|
||||||
|
color: #303133;
|
||||||
|
}
|
||||||
|
.msp-station-type {
|
||||||
|
font-size: 11px;
|
||||||
|
color: #909399;
|
||||||
|
margin-right: 8px;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
.msp-station-check {
|
||||||
|
width: 16px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
color: #005293;
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.15s;
|
||||||
|
}
|
||||||
|
.msp-check-visible {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
217
frontend/src/views/moXingGuanLi/SampleDataModal.vue
Normal file
217
frontend/src/views/moXingGuanLi/SampleDataModal.vue
Normal file
@ -0,0 +1,217 @@
|
|||||||
|
<template>
|
||||||
|
<a-modal
|
||||||
|
:visible="visible"
|
||||||
|
:title="title"
|
||||||
|
width="80%"
|
||||||
|
style="min-width: 1200px"
|
||||||
|
:z-index="1050"
|
||||||
|
@cancel="handleCancel"
|
||||||
|
:footer="false"
|
||||||
|
destroy-on-close
|
||||||
|
>
|
||||||
|
<!-- 顶部:站点tabs -->
|
||||||
|
<div class="sdm-top-tabs">
|
||||||
|
<div
|
||||||
|
v-for="(station, idx) in stations"
|
||||||
|
:key="station"
|
||||||
|
class="sdm-tab-item"
|
||||||
|
:class="{ 'sdm-tab-active': activeTabIndex === idx }"
|
||||||
|
@click="activeTabIndex = idx"
|
||||||
|
>
|
||||||
|
{{ station }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 查询栏 -->
|
||||||
|
<div class="sdm-search-bar">
|
||||||
|
<div class="sdm-search-left">
|
||||||
|
<a-radio-group v-model:value="currentElement" button-style="solid">
|
||||||
|
<a-radio-button
|
||||||
|
v-for="el in elements"
|
||||||
|
:key="el.value"
|
||||||
|
:value="el.value"
|
||||||
|
>
|
||||||
|
{{ el.label }}
|
||||||
|
</a-radio-button>
|
||||||
|
</a-radio-group>
|
||||||
|
<a-range-picker
|
||||||
|
v-model:value="searchDateRange"
|
||||||
|
style="width: 260px; margin-left: 12px"
|
||||||
|
/>
|
||||||
|
<a-button type="primary" style="margin-left: 12px" @click="handleQuery">
|
||||||
|
查询
|
||||||
|
</a-button>
|
||||||
|
</div>
|
||||||
|
<div class="sdm-search-right">
|
||||||
|
<a-button type="primary" style="margin-left: 12px" @click="handleQuery">
|
||||||
|
自动识别
|
||||||
|
</a-button>
|
||||||
|
<a-button danger style="margin-left: 8px" @click="handleBatchDelete">
|
||||||
|
批量删除
|
||||||
|
</a-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 表格区域 -->
|
||||||
|
<div class="sdm-table-area">
|
||||||
|
<BasicTable
|
||||||
|
:columns="tableColumns"
|
||||||
|
:table-data="tableData"
|
||||||
|
:loading="loading"
|
||||||
|
:enable-row-selection="true"
|
||||||
|
row-key="id"
|
||||||
|
:pagination="{ pageSize: 10 }"
|
||||||
|
>
|
||||||
|
<template #operation="{ record }">
|
||||||
|
<a-button danger size="small" @click="handleDelete(record)">
|
||||||
|
删除
|
||||||
|
</a-button>
|
||||||
|
</template>
|
||||||
|
</BasicTable>
|
||||||
|
</div>
|
||||||
|
</a-modal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, computed, watch } from 'vue';
|
||||||
|
import BasicTable from '@/components/BasicTable/index.vue';
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
visible: boolean;
|
||||||
|
title: string;
|
||||||
|
stations: string[]; // 选择的站点列表
|
||||||
|
defaultDateRange: any; // 从父页带过来的时间范围
|
||||||
|
stationType: 'weather' | 'upstream';
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(e: 'update:visible', val: boolean): void;
|
||||||
|
(e: 'delete', records: any[]): void;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
// 当前激活的tab(站点)
|
||||||
|
const activeTabIndex = ref(0);
|
||||||
|
|
||||||
|
// 要素列表
|
||||||
|
const elements = ref([
|
||||||
|
{ label: '降雨', value: 'rain' },
|
||||||
|
{ label: '气温', value: 'temp' },
|
||||||
|
{ label: '气压', value: 'press' },
|
||||||
|
{ label: '湿度', value: 'humidity' },
|
||||||
|
{ label: '风速', value: 'windSpeed' },
|
||||||
|
{ label: '风向', value: 'windDir' }
|
||||||
|
]);
|
||||||
|
const currentElement = ref('rain');
|
||||||
|
|
||||||
|
// 搜索时间范围
|
||||||
|
const searchDateRange = ref<any>(props.defaultDateRange);
|
||||||
|
|
||||||
|
// 表格列
|
||||||
|
const tableColumns = computed(() => [
|
||||||
|
{
|
||||||
|
title: '监测时间',
|
||||||
|
dataIndex: 'monitorTime',
|
||||||
|
width: 180
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '数值',
|
||||||
|
dataIndex: 'value',
|
||||||
|
width: 120
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '单位',
|
||||||
|
dataIndex: 'unit',
|
||||||
|
width: 80
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '操作',
|
||||||
|
dataIndex: 'operation',
|
||||||
|
width: 80,
|
||||||
|
fixed: 'right'
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
|
||||||
|
// 表格数据(模拟固定数据)
|
||||||
|
const mockData = [
|
||||||
|
{ id: 1, monitorTime: '2024-01-01 00:00', value: '12.5', unit: 'mm' },
|
||||||
|
{ id: 2, monitorTime: '2024-01-01 01:00', value: '0.0', unit: 'mm' },
|
||||||
|
{ id: 3, monitorTime: '2024-01-01 02:00', value: '0.0', unit: 'mm' },
|
||||||
|
{ id: 4, monitorTime: '2024-01-01 03:00', value: '5.2', unit: 'mm' },
|
||||||
|
{ id: 5, monitorTime: '2024-01-01 04:00', value: '8.7', unit: 'mm' },
|
||||||
|
{ id: 6, monitorTime: '2024-01-01 05:00', value: '0.0', unit: 'mm' },
|
||||||
|
{ id: 7, monitorTime: '2024-01-01 06:00', value: '0.0', unit: 'mm' },
|
||||||
|
{ id: 8, monitorTime: '2024-01-01 07:00', value: '0.0', unit: 'mm' }
|
||||||
|
];
|
||||||
|
|
||||||
|
const loading = ref(false);
|
||||||
|
const tableData = ref(mockData);
|
||||||
|
|
||||||
|
// 操作
|
||||||
|
const handleQuery = () => {
|
||||||
|
// TODO: 接口查询,预留
|
||||||
|
loading.value = true;
|
||||||
|
setTimeout(() => {
|
||||||
|
loading.value = false;
|
||||||
|
}, 500);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDelete = (record: any) => {
|
||||||
|
tableData.value = tableData.value.filter(r => r.id !== record.id);
|
||||||
|
emit('delete', [record]);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleBatchDelete = () => {
|
||||||
|
// TODO: 批量删除需要从表格selection获取选中行,BasicTable透传selection后处理
|
||||||
|
// 先预留位置,接口后续接入
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCancel = () => {
|
||||||
|
emit('update:visible', false);
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.sdm-top-tabs {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
padding-bottom: 8px;
|
||||||
|
border-bottom: 1px solid #e8e8e8;
|
||||||
|
}
|
||||||
|
.sdm-tab-item {
|
||||||
|
padding: 6px 16px;
|
||||||
|
border: 1px solid #d9d9d9;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #606266;
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
.sdm-tab-item:hover {
|
||||||
|
color: #1890ff;
|
||||||
|
border-color: #1890ff;
|
||||||
|
}
|
||||||
|
.sdm-tab-active {
|
||||||
|
background: #1890ff;
|
||||||
|
color: #fff;
|
||||||
|
border-color: #1890ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sdm-search-bar {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
padding: 12px 16px;
|
||||||
|
background: #fafafa;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
.sdm-search-left,
|
||||||
|
.sdm-search-right {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.sdm-table-area {
|
||||||
|
min-height: 360px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
308
frontend/src/views/moXingGuanLi/map.station.ts
Normal file
308
frontend/src/views/moXingGuanLi/map.station.ts
Normal file
@ -0,0 +1,308 @@
|
|||||||
|
import OlMap from 'ol/Map';
|
||||||
|
import View from 'ol/View';
|
||||||
|
import TileLayer from 'ol/layer/Tile';
|
||||||
|
import VectorLayer from 'ol/layer/Vector';
|
||||||
|
import VectorSource from 'ol/source/Vector';
|
||||||
|
import WMTS from 'ol/source/WMTS';
|
||||||
|
import WMTSTileGrid from 'ol/tilegrid/WMTS';
|
||||||
|
import { defaults as defaultInteractions, Draw } from 'ol/interaction';
|
||||||
|
import { getTopLeft, getWidth } from 'ol/extent';
|
||||||
|
import { get as getProjection, fromLonLat } from 'ol/proj';
|
||||||
|
import { getLength } from 'ol/sphere';
|
||||||
|
import Feature from 'ol/Feature';
|
||||||
|
import LineString from 'ol/geom/LineString';
|
||||||
|
import Point from 'ol/geom/Point';
|
||||||
|
import Polygon from 'ol/geom/Polygon';
|
||||||
|
import Style from 'ol/style/Style';
|
||||||
|
import Stroke from 'ol/style/Stroke';
|
||||||
|
import CircleStyle from 'ol/style/Circle';
|
||||||
|
import Fill from 'ol/style/Fill';
|
||||||
|
import Text from 'ol/style/Text';
|
||||||
|
|
||||||
|
// WMTS 底图配置
|
||||||
|
const VITE_APP_MAP_URL = import.meta.env.VITE_APP_MAP_URL || '';
|
||||||
|
const BASE_LAYER_CONFIG = {
|
||||||
|
id: 'customBaseLayer',
|
||||||
|
key: 'customBaseLayer',
|
||||||
|
type: 'wmts',
|
||||||
|
name: 'qgc_sx_gjjdx_arcgistiles_l13',
|
||||||
|
url:
|
||||||
|
VITE_APP_MAP_URL +
|
||||||
|
'/geoserver/gwc/service/wmts?REQUEST=GetTile&SERVICE=WMTS&VERSION=1.0.0&LAYER=qgc_qsj_arcgistiles_l13&STYLE=&TILEMATRIX=EPSG:3857_qgc_qsj_arcgistiles_l13:{z}&TILEMATRIXSET=EPSG:3857_qgc_qsj_arcgistiles_l13&FORMAT=image/png&TILECOL={x}&TILEROW={y}',
|
||||||
|
matrixIds_index: [
|
||||||
|
'0',
|
||||||
|
'1',
|
||||||
|
'2',
|
||||||
|
'3',
|
||||||
|
'4',
|
||||||
|
'5',
|
||||||
|
'6',
|
||||||
|
'7',
|
||||||
|
'8',
|
||||||
|
'9',
|
||||||
|
'10',
|
||||||
|
'11',
|
||||||
|
'12'
|
||||||
|
],
|
||||||
|
tileMatrixSetID: 'EPSG:3857_qgc_qsj_arcgistiles_l13'
|
||||||
|
};
|
||||||
|
|
||||||
|
export class StationMap {
|
||||||
|
map: OlMap | null = null;
|
||||||
|
view: View | null = null;
|
||||||
|
private measureSource: VectorSource | null = null;
|
||||||
|
private measureLayer: VectorLayer | null = null;
|
||||||
|
private drawInteraction: Draw | null = null;
|
||||||
|
private isMeasuring = false;
|
||||||
|
private anchorLayer: VectorLayer | null = null;
|
||||||
|
private anchorSource: VectorSource | null = null;
|
||||||
|
private rangeSource: VectorSource | null = null;
|
||||||
|
private rangeLayer: VectorLayer | null = null;
|
||||||
|
private centerLon = 114.171;
|
||||||
|
private centerLat = 38;
|
||||||
|
|
||||||
|
init(
|
||||||
|
container: HTMLElement,
|
||||||
|
centerLon: number,
|
||||||
|
centerLat: number,
|
||||||
|
radiusKm: number
|
||||||
|
) {
|
||||||
|
this.centerLon = centerLon;
|
||||||
|
this.centerLat = centerLat;
|
||||||
|
const zoom = this.calcZoomByRadius(radiusKm);
|
||||||
|
|
||||||
|
const projection = getProjection('EPSG:3857');
|
||||||
|
if (!projection) return;
|
||||||
|
const projectionExtent = projection.getExtent();
|
||||||
|
const size = getWidth(projectionExtent) / 256;
|
||||||
|
|
||||||
|
const resolutions: number[] = [];
|
||||||
|
const matrixIds: string[] = [];
|
||||||
|
const maxZoom = 13;
|
||||||
|
for (let z = 0; z <= maxZoom; ++z) {
|
||||||
|
resolutions[z] = size / Math.pow(2, z);
|
||||||
|
matrixIds[z] = `${BASE_LAYER_CONFIG.tileMatrixSetID}:${z}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const wmtsLayer = new TileLayer({
|
||||||
|
source: new WMTS({
|
||||||
|
url: BASE_LAYER_CONFIG.url.split('?')[0],
|
||||||
|
layer: 'qgc_qsj_arcgistiles_l13',
|
||||||
|
matrixSet: BASE_LAYER_CONFIG.tileMatrixSetID,
|
||||||
|
format: 'image/png',
|
||||||
|
projection: projection,
|
||||||
|
tileGrid: new WMTSTileGrid({
|
||||||
|
origin: getTopLeft(projectionExtent),
|
||||||
|
resolutions: resolutions,
|
||||||
|
matrixIds: matrixIds
|
||||||
|
}),
|
||||||
|
style: 'default',
|
||||||
|
wrapX: true,
|
||||||
|
crossOrigin: 'anonymous'
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
// 测量图层
|
||||||
|
this.measureSource = new VectorSource();
|
||||||
|
this.measureLayer = new VectorLayer({
|
||||||
|
source: this.measureSource,
|
||||||
|
style: new Style({
|
||||||
|
fill: new Fill({ color: 'rgba(255, 77, 79, 0.15)' }),
|
||||||
|
stroke: new Stroke({ color: '#ff4d4f', width: 2 })
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
// 锚点图层(预留)
|
||||||
|
this.anchorSource = new VectorSource();
|
||||||
|
this.anchorLayer = new VectorLayer({
|
||||||
|
source: this.anchorSource,
|
||||||
|
style: new Style({
|
||||||
|
image: new CircleStyle({
|
||||||
|
radius: 6,
|
||||||
|
fill: new Fill({ color: '#1890ff' }),
|
||||||
|
stroke: new Stroke({ color: '#fff', width: 2 })
|
||||||
|
})
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
// 范围指示圆
|
||||||
|
this.rangeSource = new VectorSource();
|
||||||
|
this.rangeLayer = new VectorLayer({
|
||||||
|
source: this.rangeSource,
|
||||||
|
style: new Style({
|
||||||
|
fill: new Fill({ color: 'rgba(255, 77, 79, 0.2)' }),
|
||||||
|
stroke: new Stroke({ color: '#ff4d4f', width: 2, lineDash: [6, 4] })
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
this.view = new View({
|
||||||
|
center: fromLonLat([centerLon, centerLat]),
|
||||||
|
zoom: zoom,
|
||||||
|
minZoom: 4,
|
||||||
|
maxZoom: 16,
|
||||||
|
projection: 'EPSG:3857'
|
||||||
|
});
|
||||||
|
|
||||||
|
this.map = new OlMap({
|
||||||
|
target: container,
|
||||||
|
layers: [wmtsLayer, this.measureLayer, this.anchorLayer, this.rangeLayer],
|
||||||
|
view: this.view,
|
||||||
|
controls: [],
|
||||||
|
interactions: defaultInteractions()
|
||||||
|
});
|
||||||
|
|
||||||
|
// 绘制初始范围圆
|
||||||
|
this.drawRangeCircle(radiusKm);
|
||||||
|
}
|
||||||
|
|
||||||
|
private calcZoomByRadius(radiusKm: number): number {
|
||||||
|
return Math.max(4, Math.min(14, Math.round(14 - Math.log2(radiusKm))));
|
||||||
|
}
|
||||||
|
|
||||||
|
destroy() {
|
||||||
|
if (this.map) {
|
||||||
|
this.map.setTarget(undefined);
|
||||||
|
this.map = null;
|
||||||
|
}
|
||||||
|
this.view = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 测距
|
||||||
|
*/
|
||||||
|
toggleMeasure() {
|
||||||
|
if (!this.map || !this.measureSource) return;
|
||||||
|
this.isMeasuring ? this.stopMeasure() : this.startMeasure();
|
||||||
|
}
|
||||||
|
|
||||||
|
get isMeasureActive() {
|
||||||
|
return this.isMeasuring;
|
||||||
|
}
|
||||||
|
|
||||||
|
private startMeasure() {
|
||||||
|
if (!this.map || !this.measureSource) return;
|
||||||
|
this.isMeasuring = true;
|
||||||
|
|
||||||
|
this.drawInteraction = new Draw({
|
||||||
|
source: this.measureSource,
|
||||||
|
type: 'LineString',
|
||||||
|
style: new Style({
|
||||||
|
stroke: new Stroke({ color: '#ff4d4f', width: 2, lineDash: [4, 4] }),
|
||||||
|
image: new CircleStyle({
|
||||||
|
radius: 4,
|
||||||
|
fill: new Fill({ color: '#ff4d4f' })
|
||||||
|
})
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
this.drawInteraction.on('drawstart', () => {
|
||||||
|
this.measureSource?.clear();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.drawInteraction.on('drawend', evt => {
|
||||||
|
const line = evt.feature.getGeometry() as LineString;
|
||||||
|
if (!line) return;
|
||||||
|
const length = getLength(line);
|
||||||
|
const label =
|
||||||
|
length > 1000
|
||||||
|
? `${(length / 1000).toFixed(2)} km`
|
||||||
|
: `${length.toFixed(1)} m`;
|
||||||
|
|
||||||
|
const coords = line.getLastCoordinate();
|
||||||
|
const labelFeature = new Feature(new Point(coords));
|
||||||
|
labelFeature.setStyle(
|
||||||
|
new Style({
|
||||||
|
text: new Text({
|
||||||
|
text: label,
|
||||||
|
font: '12px sans-serif',
|
||||||
|
fill: new Fill({ color: '#ff4d4f' }),
|
||||||
|
stroke: new Stroke({ color: '#fff', width: 3 }),
|
||||||
|
offsetY: -10
|
||||||
|
})
|
||||||
|
})
|
||||||
|
);
|
||||||
|
this.measureSource?.addFeature(labelFeature);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.map.addInteraction(this.drawInteraction);
|
||||||
|
}
|
||||||
|
|
||||||
|
private stopMeasure() {
|
||||||
|
if (!this.map || !this.drawInteraction) return;
|
||||||
|
this.map.removeInteraction(this.drawInteraction);
|
||||||
|
this.drawInteraction = null;
|
||||||
|
this.isMeasuring = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
clearMeasure() {
|
||||||
|
this.measureSource?.clear();
|
||||||
|
if (this.isMeasuring) this.stopMeasure();
|
||||||
|
}
|
||||||
|
|
||||||
|
zoomIn() {
|
||||||
|
if (!this.view) return;
|
||||||
|
const z = this.view.getZoom() || 6;
|
||||||
|
this.view.animate({ zoom: z + 1, duration: 250 });
|
||||||
|
}
|
||||||
|
|
||||||
|
zoomOut() {
|
||||||
|
if (!this.view) return;
|
||||||
|
const z = this.view.getZoom() || 6;
|
||||||
|
this.view.animate({ zoom: z - 1, duration: 250 });
|
||||||
|
}
|
||||||
|
|
||||||
|
flyTo(lon: number, lat: number, radiusKm: number) {
|
||||||
|
if (!this.view) return;
|
||||||
|
this.view.animate({
|
||||||
|
center: fromLonLat([lon, lat]),
|
||||||
|
zoom: this.calcZoomByRadius(radiusKm),
|
||||||
|
duration: 500
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新搜索范围:重新定位并绘制范围圆
|
||||||
|
*/
|
||||||
|
updateRange(radiusKm: number) {
|
||||||
|
if (!this.view) return;
|
||||||
|
this.view.animate({
|
||||||
|
zoom: this.calcZoomByRadius(radiusKm),
|
||||||
|
duration: 300
|
||||||
|
});
|
||||||
|
this.drawRangeCircle(radiusKm);
|
||||||
|
}
|
||||||
|
|
||||||
|
private drawRangeCircle(radiusKm: number) {
|
||||||
|
if (!this.rangeSource) return;
|
||||||
|
this.rangeSource.clear();
|
||||||
|
|
||||||
|
const radiusM = radiusKm * 1000;
|
||||||
|
const numPoints = 64;
|
||||||
|
const coords: number[][] = [];
|
||||||
|
const R = 6371000; // 地球平均半径(米)
|
||||||
|
|
||||||
|
for (let i = 0; i <= numPoints; i++) {
|
||||||
|
const bearing = (i * 2 * Math.PI) / numPoints; // 弧度,顺时针从北
|
||||||
|
const lat1 = (this.centerLat * Math.PI) / 180;
|
||||||
|
const lon1 = (this.centerLon * Math.PI) / 180;
|
||||||
|
|
||||||
|
// 球面 destination 计算(haversine 公式逆运算)
|
||||||
|
const lat2 = Math.asin(
|
||||||
|
Math.sin(lat1) * Math.cos(radiusM / R) +
|
||||||
|
Math.cos(lat1) * Math.sin(radiusM / R) * Math.cos(bearing)
|
||||||
|
);
|
||||||
|
const lon2 =
|
||||||
|
lon1 +
|
||||||
|
Math.atan2(
|
||||||
|
Math.sin(bearing) * Math.sin(radiusM / R) * Math.cos(lat1),
|
||||||
|
Math.cos(radiusM / R) - Math.sin(lat1) * Math.sin(lat2)
|
||||||
|
);
|
||||||
|
|
||||||
|
coords.push(fromLonLat([(lon2 * 180) / Math.PI, (lat2 * 180) / Math.PI]));
|
||||||
|
}
|
||||||
|
|
||||||
|
const polygon = new Polygon([coords]);
|
||||||
|
const feature = new Feature(polygon);
|
||||||
|
this.rangeSource.addFeature(feature);
|
||||||
|
}
|
||||||
|
}
|
||||||
2171
frontend/src/views/moXingGuanLi/moXingGuanLi.vue
Normal file
2171
frontend/src/views/moXingGuanLi/moXingGuanLi.vue
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,7 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="moduleContent" style="color: red; z-index: 1000">
|
|
||||||
目标检测模型管理
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts"></script>
|
|
||||||
@ -1,15 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="shiXuMoXingGuanLi-page">时序模型管理</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts"></script>
|
|
||||||
<style scoped lang="scss">
|
|
||||||
.shiXuMoXingGuanLi-page {
|
|
||||||
position: relative;
|
|
||||||
pointer-events: all;
|
|
||||||
z-index: 2000;
|
|
||||||
background-color: #fff;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@ -2,9 +2,11 @@
|
|||||||
import JidiSelectorMod from '@/modules/jidiSelectorMod.vue';
|
import JidiSelectorMod from '@/modules/jidiSelectorMod.vue';
|
||||||
import RightDrawer from '@/components/RightDrawer/index.vue';
|
import RightDrawer from '@/components/RightDrawer/index.vue';
|
||||||
import QiXiDiBaoHuGongZuoKaiZhan from '@/modules/qixidibaohugongzuokaizhanQK/index.vue';
|
import QiXiDiBaoHuGongZuoKaiZhan from '@/modules/qixidibaohugongzuokaizhanQK/index.vue';
|
||||||
import QixidijchuXx from '@/modules/qixidijchuXx/index.vue';
|
import QiXiDiFenBuTongJi from '@/modules/qixidifenbutongji/index.vue';
|
||||||
import QiXiDiShuiWenBianHua from '@/modules/qixidishuiwenbianhua/index.vue';
|
import QiXiDJianCeJieGuoTongJi from '@/modules/qixidijiancejieguotongji/index.vue';
|
||||||
import QiXiDiLiuLiangBianHua from '@/modules/qixidiliuliangbianhua/index.vue';
|
// import QixidijchuXx from '@/modules/qixidijchuXx/index.vue';
|
||||||
|
// import QiXiDiShuiWenBianHua from '@/modules/qixidishuiwenbianhua/index.vue';
|
||||||
|
// import QiXiDiLiuLiangBianHua from '@/modules/qixidiliuliangbianhua/index.vue';
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@ -14,10 +16,9 @@ import QiXiDiLiuLiangBianHua from '@/modules/qixidiliuliangbianhua/index.vue';
|
|||||||
</div>
|
</div>
|
||||||
<div class="rightContent">
|
<div class="rightContent">
|
||||||
<RightDrawer>
|
<RightDrawer>
|
||||||
|
<QiXiDiFenBuTongJi />
|
||||||
<QiXiDiBaoHuGongZuoKaiZhan />
|
<QiXiDiBaoHuGongZuoKaiZhan />
|
||||||
<QiXiDiShuiWenBianHua />
|
<QiXiDJianCeJieGuoTongJi />
|
||||||
<QiXiDiLiuLiangBianHua />
|
|
||||||
<QixidijchuXx />
|
|
||||||
</RightDrawer>
|
</RightDrawer>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import JidiSelectorMod from "@/modules/jidiSelectorMod.vue";
|
import JidiSelectorMod from '@/modules/jidiSelectorMod.vue';
|
||||||
import RightDrawer from "@/components/RightDrawer/index.vue";
|
import RightDrawer from '@/components/RightDrawer/index.vue';
|
||||||
import LSSTJCQKTT from "@/modules/lushengshengtaijiance/index.vue"
|
import LSJCSBTJ from '@/modules/lushengshebeitongji/index.vue';
|
||||||
import Ysdwjc from "@/modules/Ysdwjc/index.vue"
|
import Ysdwjc from '@/modules/Ysdwjc/index.vue';
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@ -12,7 +12,7 @@ import Ysdwjc from "@/modules/Ysdwjc/index.vue"
|
|||||||
</div>
|
</div>
|
||||||
<div class="rightContent">
|
<div class="rightContent">
|
||||||
<RightDrawer>
|
<RightDrawer>
|
||||||
<LSSTJCQKTT />
|
<LSJCSBTJ />
|
||||||
<Ysdwjc />
|
<Ysdwjc />
|
||||||
</RightDrawer>
|
</RightDrawer>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,11 +1,13 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import JidiSelectorMod from '@/modules/jidiSelectorMod.vue';
|
import JidiSelectorMod from '@/modules/jidiSelectorMod.vue';
|
||||||
import RightDrawer from '@/components/RightDrawer/index.vue';
|
import RightDrawer from '@/components/RightDrawer/index.vue';
|
||||||
import ShengtaidabiaoMod from '@/modules/shengtaidabiaoMod/index.vue'; // 模块-生态流量达标情况 生态流量达标情况
|
// import ShengtaidabiaoMod from '@/modules/shengtaidabiaoMod/index.vue'; // 模块-生态流量达标情况 生态流量达标情况
|
||||||
import ShengtaidabiaoTwoMod from '@/modules/shengtaidabiaoTwoMod/index.vue';
|
// import ShengtaidabiaoTwoMod from '@/modules/shengtaidabiaoTwoMod/index.vue';
|
||||||
import StllyanchengChangeMod from '@/modules/stllyanchengChangeMod/index.vue' // 模块-生态流量沿程变化
|
import ShengTaiHuangJingBuMod from '@/modules/shengTaiHuangJingBuMod/index.vue'; // 模块-生态流量达标情况 生态流量达标情况
|
||||||
|
import ShuiLiBuMod from '@/modules/shuiLiBuMod/index.vue'; // 模块-生态流量达标情况 生态流量达标情况
|
||||||
|
import StllyanchengChangeMod from '@/modules/stllyanchengChangeMod/index.vue'; // 模块-生态流量沿程变化
|
||||||
import { useJidiSelectEventStore } from '@/store/modules/jidiSelectEvent';
|
import { useJidiSelectEventStore } from '@/store/modules/jidiSelectEvent';
|
||||||
import { ref, onMounted, watch } from 'vue';
|
import { ref, watch } from 'vue';
|
||||||
const JidiSelectEventStore = useJidiSelectEventStore();
|
const JidiSelectEventStore = useJidiSelectEventStore();
|
||||||
const baseid = ref('');
|
const baseid = ref('');
|
||||||
watch(
|
watch(
|
||||||
@ -26,8 +28,8 @@ watch(
|
|||||||
</div>
|
</div>
|
||||||
<div class="rightContent">
|
<div class="rightContent">
|
||||||
<RightDrawer>
|
<RightDrawer>
|
||||||
<ShengtaidabiaoMod />
|
<ShengTaiHuangJingBuMod />
|
||||||
<ShengtaidabiaoTwoMod v-if="baseid == 'all'" />
|
<ShuiLiBuMod />
|
||||||
<StllyanchengChangeMod v-if="baseid != 'all'" />
|
<StllyanchengChangeMod v-if="baseid != 'all'" />
|
||||||
</RightDrawer>
|
</RightDrawer>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,10 +1,24 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { ref, watch } from 'vue';
|
||||||
import JidiSelectorMod from '@/modules/jidiSelectorMod.vue';
|
import JidiSelectorMod from '@/modules/jidiSelectorMod.vue';
|
||||||
import RightDrawer from '@/components/RightDrawer/index.vue';
|
import RightDrawer from '@/components/RightDrawer/index.vue';
|
||||||
import ChuixiangshuiwenChangeMod from '@/modules/chuixiangshuiwenChangeMod/index.vue'; // 垂向水温变化
|
import ChuixiangshuiwenChangeMod from '@/modules/chuixiangshuiwenChangeMod/index.vue'; // 垂向水温变化
|
||||||
import ChurukushuiwenMod from '@/modules/churukushuiwenMod/index.vue'; // 出入库水温变化
|
import ChurukushuiwenMod from '@/modules/churukushuiwenMod/index.vue'; // 出入库水温变化
|
||||||
import YanchengshuiwenChangeMod from '@/modules/yanchengshuiwenChangeMod/index.vue'; // 沿程水温变化
|
import YanchengshuiwenChangeMod from '@/modules/yanchengshuiwenChangeMod/index.vue'; // 沿程水温变化
|
||||||
import ShuiwenJCGZKZQK from '@/modules/shuiwenjiancegongzuokaizhangqingkuang/index.vue';
|
import ShuiwenJCGZKZQK from '@/modules/shuiwenjiancegongzuokaizhangqingkuang/index.vue';
|
||||||
|
import { useJidiSelectEventStore } from '@/store/modules/jidiSelectEvent';
|
||||||
|
import ShuiWenJianCeKaiZhanQingKuang from '@/modules/shuiWenJianCeKaiZhanQingKuang/index.vue'; // 模块-水温监测工作开展情况
|
||||||
|
const JidiSelectEventStore = useJidiSelectEventStore();
|
||||||
|
const baseid = ref('');
|
||||||
|
watch(
|
||||||
|
() => JidiSelectEventStore.selectedItem,
|
||||||
|
newVal => {
|
||||||
|
if (newVal && newVal.wbsCode) {
|
||||||
|
baseid.value = newVal.wbsCode;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ deep: true, immediate: true }
|
||||||
|
);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@ -15,9 +29,10 @@ import ShuiwenJCGZKZQK from '@/modules/shuiwenjiancegongzuokaizhangqingkuang/ind
|
|||||||
<div class="rightContent">
|
<div class="rightContent">
|
||||||
<RightDrawer>
|
<RightDrawer>
|
||||||
<ShuiwenJCGZKZQK />
|
<ShuiwenJCGZKZQK />
|
||||||
<YanchengshuiwenChangeMod />
|
<YanchengshuiwenChangeMod v-if="baseid != 'all'" />
|
||||||
<ChuixiangshuiwenChangeMod />
|
<ChuixiangshuiwenChangeMod v-if="baseid != 'all'" />
|
||||||
<ChurukushuiwenMod />
|
<ChurukushuiwenMod v-if="baseid != 'all'" />
|
||||||
|
<ShuiWenJianCeKaiZhanQingKuang v-if="baseid == 'all'" />
|
||||||
</RightDrawer>
|
</RightDrawer>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -5,15 +5,16 @@ import RightDrawer from '@/components/RightDrawer/index.vue';
|
|||||||
import ShuiZhiJianCeGongZuoQingKuang from '@/modules/shuizhijiancegongzuoQK/index.vue';
|
import ShuiZhiJianCeGongZuoQingKuang from '@/modules/shuizhijiancegongzuoQK/index.vue';
|
||||||
import EnvironmentalQuality from '@/modules/EnvironmentalQuality/index.vue'; // 环境质量满足度
|
import EnvironmentalQuality from '@/modules/EnvironmentalQuality/index.vue'; // 环境质量满足度
|
||||||
import { useJidiSelectEventStore } from '@/store/modules/jidiSelectEvent';
|
import { useJidiSelectEventStore } from '@/store/modules/jidiSelectEvent';
|
||||||
|
import ShuiZhiDaBiaoQingKuang from '@/modules/shuiZhiDaBiaoQingKuang/index.vue'; // 模块-水位监测工作开展情况
|
||||||
import SZYCBH from '@/modules/waterQuality/index.vue';
|
import SZYCBH from '@/modules/waterQuality/index.vue';
|
||||||
const JidiSelectEventStore = useJidiSelectEventStore();
|
const JidiSelectEventStore = useJidiSelectEventStore();
|
||||||
const wbsCode = ref('');
|
const baseid = ref('');
|
||||||
watch(
|
watch(
|
||||||
() => JidiSelectEventStore.selectedItem,
|
() => JidiSelectEventStore.selectedItem,
|
||||||
newVal => {
|
newVal => {
|
||||||
console.log(newVal);
|
console.log(newVal);
|
||||||
//
|
//
|
||||||
wbsCode.value = newVal.wbsCode;
|
baseid.value = newVal.wbsCode;
|
||||||
},
|
},
|
||||||
{ deep: true, immediate: true }
|
{ deep: true, immediate: true }
|
||||||
);
|
);
|
||||||
@ -27,8 +28,9 @@ watch(
|
|||||||
<div class="rightContent">
|
<div class="rightContent">
|
||||||
<RightDrawer>
|
<RightDrawer>
|
||||||
<ShuiZhiJianCeGongZuoQingKuang />
|
<ShuiZhiJianCeGongZuoQingKuang />
|
||||||
<EnvironmentalQuality />
|
<ShuiZhiDaBiaoQingKuang v-if="baseid == 'all'" />
|
||||||
<SZYCBH v-if="wbsCode != 'all'"></SZYCBH>
|
<EnvironmentalQuality v-if="baseid != 'all'" />
|
||||||
|
<SZYCBH v-if="baseid != 'all'"></SZYCBH>
|
||||||
</RightDrawer>
|
</RightDrawer>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -103,7 +103,7 @@ import { ref, reactive, computed, watch,onMounted } from 'vue';
|
|||||||
import { message, Modal } from 'ant-design-vue';
|
import { message, Modal } from 'ant-design-vue';
|
||||||
import type { Rule } from 'ant-design-vue/es/form';
|
import type { Rule } from 'ant-design-vue/es/form';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import CronPicker from './CronPicker.vue';
|
import CronPicker from '@/components/CronPicker/index.vue';
|
||||||
import { useJidiSelectEventStore } from '@/store/modules/jidiSelectEvent';
|
import { useJidiSelectEventStore } from '@/store/modules/jidiSelectEvent';
|
||||||
import { getDictItemsByCode } from '@/api/dict';
|
import { getDictItemsByCode } from '@/api/dict';
|
||||||
import { useDraggable } from '@/utils/drag';
|
import { useDraggable } from '@/utils/drag';
|
||||||
|
|||||||
@ -5,7 +5,8 @@ import RightDrawer from '@/components/RightDrawer/index.vue';
|
|||||||
|
|
||||||
import ZengZhiJiHuaWanChengQingKuang from '@/modules/zengZhiJiHuaWanChengQingKuang/index.vue';
|
import ZengZhiJiHuaWanChengQingKuang from '@/modules/zengZhiJiHuaWanChengQingKuang/index.vue';
|
||||||
import ZengZhiZhanJieShaoMod from '@/modules/zengZhiZhanJieShaoMod/index.vue';
|
import ZengZhiZhanJieShaoMod from '@/modules/zengZhiZhanJieShaoMod/index.vue';
|
||||||
import ZZZJSYXQKTT from '@/modules/zengzhizhanjiansheyunxing/index.vue';
|
// import ZZZJSYXQKTT from '@/modules/zengzhizhanjiansheyunxing/index.vue';
|
||||||
|
import ZZZFBTJ from '@/modules/zengzhizhanfbutongji/index.vue';
|
||||||
import ZZZYXSJTJ from '@/modules/zengzhizhanyunxingsjtj/index.vue';
|
import ZZZYXSJTJ from '@/modules/zengzhizhanyunxingsjtj/index.vue';
|
||||||
import FLZLLB from '@/modules/GYZLLB/index.vue';
|
import FLZLLB from '@/modules/GYZLLB/index.vue';
|
||||||
import { useJidiSelectEventStore } from '@/store/modules/jidiSelectEvent';
|
import { useJidiSelectEventStore } from '@/store/modules/jidiSelectEvent';
|
||||||
@ -32,7 +33,7 @@ watch(
|
|||||||
</div>
|
</div>
|
||||||
<div class="rightContent">
|
<div class="rightContent">
|
||||||
<RightDrawer>
|
<RightDrawer>
|
||||||
<ZZZJSYXQKTT />
|
<ZZZFBTJ />
|
||||||
<ZengZhiZhanJieShaoMod v-if="wbsCode != 'all'" />
|
<ZengZhiZhanJieShaoMod v-if="wbsCode != 'all'" />
|
||||||
<ZengZhiJiHuaWanChengQingKuang v-if="wbsCode != 'all'" />
|
<ZengZhiJiHuaWanChengQingKuang v-if="wbsCode != 'all'" />
|
||||||
<FLZLLB title="放流总量" />
|
<FLZLLB title="放流总量" />
|
||||||
|
|||||||
@ -1,7 +1,8 @@
|
|||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import { UniverSheetsCorePreset } from '@univerjs/preset-sheets-core';
|
|
||||||
import UniverPresetSheetsCoreZhCN from '@univerjs/preset-sheets-core/locales/zh-CN';
|
// Univer 单例缓存,避免重复初始化导致 Identifier already exists 警告
|
||||||
import { createUniver, LocaleType, mergeLocales } from '@univerjs/presets';
|
let cachedUniverInstance: { univer: any; univerAPI: any } | null = null;
|
||||||
|
|
||||||
interface dmFilter {
|
interface dmFilter {
|
||||||
baseId?: string;
|
baseId?: string;
|
||||||
rstcd?: string;
|
rstcd?: string;
|
||||||
@ -222,8 +223,22 @@ const initializeExcelWorksheet = (univerAPI: any) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 创建Excel实例
|
// 创建Excel实例(单例模式,仅首次调用时创建,后续复用缓存实例)
|
||||||
export const createExcelInstance = container => {
|
export const createExcelInstance = async container => {
|
||||||
|
if (cachedUniverInstance) {
|
||||||
|
return cachedUniverInstance;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 动态导入 Univer 模块,避免预加载时触发模块初始化导致 Identifier 警告
|
||||||
|
const [
|
||||||
|
{ UniverSheetsCorePreset },
|
||||||
|
UniverPresetSheetsCoreZhCN,
|
||||||
|
{ createUniver, LocaleType, mergeLocales }
|
||||||
|
] = await Promise.all([
|
||||||
|
import('@univerjs/preset-sheets-core'),
|
||||||
|
import('@univerjs/preset-sheets-core/locales/zh-CN'),
|
||||||
|
import('@univerjs/presets')
|
||||||
|
]);
|
||||||
const { univer, univerAPI } = createUniver({
|
const { univer, univerAPI } = createUniver({
|
||||||
locale: LocaleType.ZH_CN,
|
locale: LocaleType.ZH_CN,
|
||||||
locales: {
|
locales: {
|
||||||
@ -300,8 +315,14 @@ export const createExcelInstance = container => {
|
|||||||
// 初始化Excel工作表
|
// 初始化Excel工作表
|
||||||
initializeExcelWorksheet(univerAPI);
|
initializeExcelWorksheet(univerAPI);
|
||||||
|
|
||||||
|
cachedUniverInstance = { univer, univerAPI };
|
||||||
return { univer, univerAPI };
|
return { univer, univerAPI };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 重置单例缓存(页面卸载时调用)
|
||||||
|
export const resetUniverInstance = () => {
|
||||||
|
cachedUniverInstance = null;
|
||||||
|
};
|
||||||
// 检查单元格编辑事件,如果row是奇数,根据column判断是否需要添加平均数公式
|
// 检查单元格编辑事件,如果row是奇数,根据column判断是否需要添加平均数公式
|
||||||
export const handleCellEdit = (params: any, fWorksheet: any, year: string) => {
|
export const handleCellEdit = (params: any, fWorksheet: any, year: string) => {
|
||||||
const {
|
const {
|
||||||
|
|||||||
@ -36,7 +36,11 @@ import {
|
|||||||
import BasicSearch from '@/components/BasicSearch/index.vue';
|
import BasicSearch from '@/components/BasicSearch/index.vue';
|
||||||
import '@univerjs/preset-sheets-core/lib/index.css';
|
import '@univerjs/preset-sheets-core/lib/index.css';
|
||||||
import { exportExcel } from './components/export';
|
import { exportExcel } from './components/export';
|
||||||
import { createExcelInstance, renderDataToExcel } from './components/utils';
|
import {
|
||||||
|
createExcelInstance,
|
||||||
|
renderDataToExcel,
|
||||||
|
resetUniverInstance
|
||||||
|
} from './components/utils';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
|
|
||||||
const basicSearchRef = ref();
|
const basicSearchRef = ref();
|
||||||
@ -351,18 +355,18 @@ const initData = async (values: any) => {
|
|||||||
loading.value = false;
|
loading.value = false;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
onMounted(() => {
|
onMounted(async () => {
|
||||||
// 创建Excel实例
|
// 创建Excel实例(动态导入,避免预加载触发 Identifier 警告)
|
||||||
const { univer, univerAPI } = createExcelInstance(containerRef.value);
|
const { univer, univerAPI } = await createExcelInstance(containerRef.value);
|
||||||
// 保存 univer 实例到 ref
|
// 保存 univer 实例到 ref
|
||||||
univerInstanceRef.value = { univer, univerAPI };
|
univerInstanceRef.value = { univer, univerAPI };
|
||||||
initStcd(initSearchData);
|
initStcd(initSearchData);
|
||||||
initData(initSearchData);
|
initData(initSearchData);
|
||||||
});
|
});
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
// 销毁Excel实例
|
// 单例模式:不销毁实例,避免重复初始化 Identifier 警告
|
||||||
const { univer, univerAPI } = univerInstanceRef.value;
|
// 仅清除 ref 引用,Univer 实例由模块级缓存持有
|
||||||
univerAPI.dispose();
|
resetUniverInstance();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@ -97,7 +97,9 @@
|
|||||||
</a-tabs>
|
</a-tabs>
|
||||||
<a-button type="primary">开始预测</a-button>
|
<a-button type="primary">开始预测</a-button>
|
||||||
</div>
|
</div>
|
||||||
<a-button type="primary">历史预测</a-button>
|
<a-button class="chart-btn-primary" type="primary"
|
||||||
|
>历史预测</a-button
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
<!-- echarts容器 -->
|
<!-- echarts容器 -->
|
||||||
<div
|
<div
|
||||||
@ -406,7 +408,7 @@ onBeforeUnmount(() => {
|
|||||||
.chart-container {
|
.chart-container {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 20%;
|
left: 20%;
|
||||||
top: 10px;
|
top: 6px;
|
||||||
width: 80%;
|
width: 80%;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
@ -414,4 +416,8 @@ onBeforeUnmount(() => {
|
|||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
padding-right: 20px;
|
padding-right: 20px;
|
||||||
}
|
}
|
||||||
|
.chart-btn-primary {
|
||||||
|
position: absolute;
|
||||||
|
right: 10px;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@ -1,5 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="moduleContent" style="color: red; z-index: 1000">智能告警</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts"></script>
|
|
||||||
Loading…
Reference in New Issue
Block a user