添加右侧展开,bug修改,地图优化

This commit is contained in:
扈兆增 2026-09-02 16:19:28 +08:00
parent d4882f469c
commit 29aaeefc60
65 changed files with 13217 additions and 2592 deletions

View File

@ -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",

View File

@ -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());
// tabqxd // tabqxd
@ -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) => {
// tabstcd (v-model) // tabstcd (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 ,
}; };

View File

@ -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'

View 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>

View 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>

View 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>

View 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>

View File

@ -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;
} }
} }

View File

@ -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';

View File

@ -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: [

View File

@ -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);

View File

@ -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 != ''"

View File

@ -1,12 +0,0 @@
<template>
<div>
视频
</div>
</template>
<script lang="ts" setup>
import { ref, watch } from 'vue';
</script>
<style lang="scss" scoped>
</style>

View File

@ -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;
} }
// ==================== 文本像素级精确测量 ==================== // ==================== 文本像素级精确测量 ====================

View File

@ -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;
} }
/** /**

View File

@ -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(() => {

View File

@ -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>

View File

@ -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/wqRateCOUNT_DRYEAR // COUNT_DRMONTH/wqRateCOUNT_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

View File

@ -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>

View File

@ -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>

File diff suppressed because it is too large Load Diff

View File

@ -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>

View File

@ -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();
} }

View File

@ -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>

View 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>

View 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>

View File

@ -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 => {

View File

@ -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

View 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-81-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;
// 34
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'
}
},
// XaxisPointer
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
// 31228
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>

View 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-81-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;
// 34
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'
}
},
// XaxisPointer
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
// 31228
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>

View File

@ -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
}; };

View File

@ -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,

View File

@ -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>

View 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>

View 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>

View File

@ -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();
}, },

View File

@ -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();
// //
}, },

View 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>

View 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}: &nbsp;&nbsp;&nbsp;&nbsp;<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>

View File

@ -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 {

View File

@ -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">

View File

@ -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 }

View 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);
// stcdstnm
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>

View File

@ -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);

View File

@ -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
}; };
}); });

View File

@ -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 => {

View File

@ -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
)}`;
} }
}); });
// 获取图标路径 // 获取图标路径

View 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>

View 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: selectionBasicTableselection
//
};
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>

View 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);
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +0,0 @@
<template>
<div class="moduleContent" style="color: red; z-index: 1000">
目标检测模型管理
</div>
</template>
<script setup lang="ts"></script>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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';

View File

@ -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="放流总量" />

View File

@ -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 {

View File

@ -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>

View File

@ -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>

View File

@ -1,5 +0,0 @@
<template>
<div class="moduleContent" style="color: red; z-index: 1000">智能告警</div>
</template>
<script setup lang="ts"></script>