修改水温垂向右侧点击,添加录像视频和建设状态,bug修改

This commit is contained in:
扈兆增 2026-06-16 11:36:17 +08:00
parent f1b8ffebf1
commit ce8855d3c2
27 changed files with 5926 additions and 4181 deletions

View File

@ -91,6 +91,24 @@ export function getVideoList(data: any) {
data data
}); });
} }
// 录像视频 - 时间查询 没接
export function getNoLiveVideoYear(data: any) {
return request({
url: '/wmp-env-server/env/vd/GetKendoListCust',
method: 'post',
data
});
}
// 录像视频
export function getNoLiveVideoList(data: any) {
return request({
url: '/wmp-env-server/env/vd/runData/GetKendoListCust',
method: 'post',
data
});
}
// 实时视频单个查询 // 实时视频单个查询
export function getVideoDetail(data: any) { export function getVideoDetail(data: any) {
return request({ return request({

View File

@ -74,11 +74,6 @@ const baseColumns = [
dataIndex: 'rowId', dataIndex: 'rowId',
width: 60 width: 60
}, },
{
title: '告警设施',
dataIndex: 'stnm',
width: 220
},
{ {
title: '告警时间', title: '告警时间',
dataIndex: 'tms', dataIndex: 'tms',
@ -91,12 +86,7 @@ const baseColumns = [
width: 120 width: 120
}, },
{ {
title: '监测要素', title: '限值(m³/s)',
dataIndex: 'ysName',
width: 120
},
{
title: '判断标准',
dataIndex: 'bzz', dataIndex: 'bzz',
width: 120 width: 120
}, },

View File

@ -21,6 +21,11 @@
</div> </div>
</div> </div>
<div class="filter-row"> <div class="filter-row">
<a-select
v-model:value="timeType"
style="margin-right: 12px"
:options="timeTypeOptions"
/>
<a-range-picker <a-range-picker
v-model:value="dateRange" v-model:value="dateRange"
:format="dateFormat" :format="dateFormat"

View File

@ -158,12 +158,12 @@ const columnsMap: Record<string, any[]> = {
}, },
{ {
title: '性腺发育期', title: '性腺发育期',
dataIndex: 'fwgh', dataIndex: 'st',
width: 140 width: 140
}, },
{ {
title: '早期资源量和种类', title: '早期资源量和种类',
dataIndex: 'fwgh', dataIndex: 'ps',
width: 220 width: 220
}, },
{ {
@ -203,7 +203,7 @@ const columnsMap: Record<string, any[]> = {
}, },
{ {
title: '电导率(μS/cm)', title: '电导率(μS/cm)',
dataIndex: 'tm', dataIndex: 'cond',
width: 180 width: 180
} }
], ],

View File

@ -0,0 +1,355 @@
<template>
<div class="fish-facility-build-state">
<a-spin :spinning="loading">
<!-- 步骤条 -->
<div class="steps-container">
<div class="steps-wrapper">
<div
v-for="(item, index) in bltList"
:key="item.code"
class="step-item"
:class="{
'step-active': currentStep > item.code,
'step-current': currentStep === item.code,
'step-last': index === bltList.length - 1
}"
>
<span class="step-label">{{ item.name }}</span>
<i
v-if="currentStep > item.code"
class="step-icon icon iconfont icon-check"
></i>
<div v-if="currentStep == 0" class="step-line"></div>
</div>
</div>
</div>
<!-- 建设进度 -->
<div class="progress-container">
<span class="progress-label">建设进度</span>
<div class="progress-bar-wrapper">
<div class="progress-bar">
<div
class="progress-fill"
:style="{ width: progressPercent + '%' }"
></div>
</div>
<span class="progress-text">{{ progressText }}</span>
</div>
</div>
<!-- 日期信息表格 -->
<div class="info-table">
<table>
<tbody>
<tr>
<td class="label-cell">计划开工日期</td>
<td class="value-cell">{{ planStartDate || '-' }}</td>
<td class="label-cell">计划完工日期</td>
<td class="value-cell">{{ planEndDate || '-' }}</td>
</tr>
<tr>
<td class="label-cell">实际开工日期</td>
<td class="value-cell">{{ startDate || '-' }}</td>
<td class="label-cell">建成日期</td>
<td class="value-cell">{{ endDate || '-' }}</td>
</tr>
</tbody>
</table>
</div>
</a-spin>
</div>
</template>
<script setup lang="ts">
import { ref, watch } from 'vue';
import { useModelStore } from '@/store/modules/model';
import dayjs from 'dayjs';
import { queryPostUrlList } from '@/api/mapModal';
const modelStore = useModelStore();
const loading = ref(false);
const hasLoaded = ref(false);
const props = defineProps({
url: {
type: String,
default: ''
},
isActive: {
type: Boolean,
default: false
}
});
const bltList = ref<any[]>([]);
const currentStep = ref(0);
const progressPercent = ref(0);
const progressText = ref('0%');
const planStartDate = ref('');
const planEndDate = ref('');
const startDate = ref('');
const endDate = ref('');
const formatDate = (date: any) => {
if (!date) return '';
return dayjs(date).format('YYYY-MM-DD');
};
const getData = async () => {
if (hasLoaded.value || !props.url || !modelStore.params.stcd) return;
loading.value = true;
try {
const params = {
filter: {
logic: 'and',
filters: [
{
field: 'stcd',
operator: 'eq',
dataType: 'string',
value: modelStore.params.stcd
}
]
}
};
const res: any = await queryPostUrlList(props.url, params);
if (res && res.data) {
const data = Array.isArray(res.data.data)
? res.data.data[0]
: res.data.data;
if (data) {
bltList.value = data.bltList || [];
currentStep.value = data.blprd || 0;
//
const progress = data.progress || '0%';
progressText.value = progress;
progressPercent.value = parseInt(progress) || 0;
planStartDate.value = formatDate(data.planStartDate);
planEndDate.value = formatDate(data.planEndDate);
startDate.value = formatDate(data.startDate);
endDate.value = formatDate(data.endDate);
}
}
} catch (error) {
console.error('获取建设状态数据失败:', error);
} finally {
loading.value = false;
hasLoaded.value = true;
}
};
watch(
() => props.isActive,
active => {
if (active && !hasLoaded.value) {
getData();
}
},
{ immediate: true }
);
watch(
() => modelStore.params.stcd,
(newStcd, oldStcd) => {
if (newStcd && newStcd !== oldStcd && props.url) {
hasLoaded.value = false;
getData();
}
}
);
</script>
<style scoped lang="scss">
.fish-facility-build-state {
width: 880px;
margin: 0 auto;
margin-top: 24px;
border: 1px solid #dfe6ec;
//
.steps-container {
background: #e4ecf2;
padding: 0;
margin-bottom: 24px;
.step-line {
&::after {
content: '';
position: absolute;
right: 0;
right: -6px; /* 负值让第二条线更靠右 */
top: 50%;
transform: translateY(-50%) rotate(45deg);
width: 26px;
height: 26px;
border-top: 2px solid #fff;
border-right: 2px solid #fff;
}
}
.steps-wrapper {
display: flex;
align-items: stretch;
.step-item {
display: flex;
align-items: center;
justify-content: center;
position: relative;
flex: 1;
min-height: 40px;
gap: 6px;
.step-label {
font-size: 14px;
color: #999;
white-space: nowrap;
}
.step-icon {
font-size: 16px;
color: #2f6b98;
}
// -
&::after {
content: '';
position: absolute;
right: 0;
top: 0;
height: 0;
width: 0;
}
//
&.step-active {
background: #f0f4f8;
.step-label {
color: #00000080;
}
&::after {
border-top: 20px solid transparent;
border-left: 15px solid #f0f4f8;
border-bottom: 20px solid transparent;
}
}
// div -
&.step-active:has(+ .step-current)::after {
border-top-color: #2f6b98;
border-bottom-color: #2f6b98;
}
// - +
&.step-current {
background: #2f6b98;
.step-label {
color: #fff;
}
//
&::after {
content: '';
position: absolute;
right: 0;
top: 0;
height: 0;
width: 0;
border-top: 20px solid #e4ecf2;
border-left: 15px solid #2f6b98;
border-bottom: 20px solid #e4ecf2;
}
}
//
&.step-last::after {
display: none;
}
}
}
}
//
.progress-container {
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 24px;
.progress-label {
font-size: 14px;
color: #333;
margin-right: 16px;
white-space: nowrap;
}
.progress-bar-wrapper {
display: flex;
align-items: center;
flex: 1;
max-width: 500px;
.progress-bar {
flex: 1;
height: 12px;
background: #e8e8e8;
border-radius: 6px;
overflow: hidden;
margin-right: 12px;
.progress-fill {
height: 100%;
background: linear-gradient(90deg, #2a7aaf 0%, #5cb85c 100%);
border-radius: 6px;
transition: width 0.3s ease;
}
}
.progress-text {
font-size: 14px;
color: #2a7aaf;
font-weight: 500;
white-space: nowrap;
}
}
}
//
.info-table {
padding: 16px;
table {
width: 100%;
border-collapse: collapse;
tr {
td {
padding: 12px 16px;
border: 1px solid #a8d8f0;
font-size: 14px;
&.label-cell {
background: #f4f7f9;
color: #333;
width: 18%;
text-align: right;
white-space: nowrap;
}
&.value-cell {
color: #333;
width: 32%;
}
}
}
}
}
}
</style>

View File

@ -472,6 +472,13 @@ const handleSearch = () => fetchData();
const initData = () => { const initData = () => {
if (hasLoaded.value) return; if (hasLoaded.value) return;
console.log(modelStore.filter.rangeTm);
if (modelStore.filter.rangeTm.length > 0) {
dateRange.value = [
dayjs(modelStore.filter.rangeTm[0]),
dayjs(modelStore.filter.rangeTm[1])
];
}
nextTick(() => initChart()); nextTick(() => initChart());
fetchData(); fetchData();
hasLoaded.value = true; hasLoaded.value = true;

View File

@ -0,0 +1,396 @@
<template>
<div class="no-live-video">
<!-- 视频模式 -->
<div class="video-mode">
<div class="video-main" v-loading="videoLoading">
<!-- 视频显示 -->
<div v-if="activeMedia.src" class="media-display">
<LiveVideoBox
ref="liveVideoRef"
:videoData="{ url: activeMedia.src }"
height="100%"
:showTitle="false"
/>
</div>
<div v-else class="media-empty">
<a-empty description="请选择右侧数据" />
</div>
</div>
<div class="video-sidebar" v-loading="videoLoading">
<!-- 时间范围查询 -->
<div class="sidebar-search">
<a-range-picker
v-model:value="dateRange"
format="YYYY-MM-DD"
value-format="YYYY-MM-DD"
style="width: 100%"
@change="handleDateChange"
/>
<a-button type="primary" class="ml-2" @click="handleSearch">
查询
</a-button>
</div>
<!-- 列表 -->
<div class="sidebar-list" v-if="videoList.length > 0">
<div
v-for="item in videoList"
:key="item.flpth"
class="sidebar-item"
:class="{ active: activeFlpth === item.flpth }"
@click="handleSelectMedia(item)"
>
<div class="item-thumb">
<img
v-if="item.imgPath"
:src="item.imgPath"
@error="handleImageError"
/>
<div v-else class="thumb-placeholder">
<img :src="videoCover" />
</div>
</div>
<div class="item-time">
<div>{{ item.titleName || item.stnm || '-' }}</div>
{{ item.tm }}
</div>
</div>
</div>
<a-empty
v-else
description="暂无数据"
class="empty-wrapper h-full flex items-center justify-center flex-col"
/>
<!-- 分页 -->
<div class="sidebar-pagination">
<a-pagination
v-model:current="videoPage"
v-model:pageSize="videoTake"
:page-size-options="videoPageSizeOptions"
:total="videoTotal"
show-size-changer
size="small"
@change="handleVideoPageChange"
/>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, watch } from 'vue';
import dayjs from 'dayjs';
import LiveVideoBox from '@/views/shiPinJianKong/components/LiveVideoBox.vue';
import { getNoLiveVideoYear, getNoLiveVideoList } from '@/api/mapModal';
import { useModelStore } from '@/store/modules/model';
import videoCover from '@/assets/images/videFm.jpg';
const modelStore = useModelStore();
const props = defineProps({
isActive: { type: Boolean, default: false }
});
const hasLoaded = ref(false);
//
const dateRange = ref<[string, string] | null>(null);
//
const videoPage = ref(1);
const videoTake = ref(10);
const videoPageSizeOptions = ['10', '20', '50'];
const videoTotal = ref(0);
const videoLoading = ref(false);
const videoList = ref<any[]>([]);
const activeFlpth = ref('');
const activeMedia = ref<{ src: string }>({ src: '' });
const liveVideoRef = ref();
// getNoLiveVideoYear
const initDefaultDateRange = async () => {
try {
const res: any = await getNoLiveVideoYear({
filter: {
logic: 'and',
filters: [
{
field: 'stcd',
operator: 'in',
value: [modelStore.params.stcd]
}
]
},
sort: [{ field: 'tm', dir: 'desc' }]
});
const data = res?.data?.data || res?.data || [];
if (data.length > 0) {
//
const latestTm = data[0].tm;
const endDate = dayjs(latestTm);
const startDate = endDate.subtract(7, 'day');
dateRange.value = [
startDate.format('YYYY-MM-DD'),
endDate.format('YYYY-MM-DD')
];
} else {
// 7
const endDate = dayjs();
const startDate = endDate.subtract(7, 'day');
dateRange.value = [
startDate.format('YYYY-MM-DD'),
endDate.format('YYYY-MM-DD')
];
}
} catch (error) {
console.error('获取时间范围失败:', error);
// 7
const endDate = dayjs();
const startDate = endDate.subtract(7, 'day');
dateRange.value = [
startDate.format('YYYY-MM-DD'),
endDate.format('YYYY-MM-DD')
];
}
};
//
const initData = async () => {
await initDefaultDateRange();
fetchVideoList();
};
//
const handleDateChange = () => {
videoPage.value = 1;
fetchVideoList();
};
//
const handleSearch = () => {
videoPage.value = 1;
fetchVideoList();
};
//
const fetchVideoList = async () => {
videoLoading.value = true;
try {
const skip = (videoPage.value - 1) * videoTake.value;
const timeFilters: any[] = [];
if (dateRange.value && dateRange.value[0] && dateRange.value[1]) {
timeFilters.push({
field: 'tm',
operator: 'gte',
dataType: 'date',
value: `${dateRange.value[0]} 00:00:00`
});
timeFilters.push({
field: 'tm',
operator: 'lte',
dataType: 'date',
value: `${dateRange.value[1]} 23:59:59`
});
}
const res: any = await getNoLiveVideoList({
take: videoTake.value,
skip,
filter: {
logic: 'and',
filters: [
{
field: 'stcd',
operator: 'in',
value: [modelStore.params.stcd]
},
...timeFilters
]
},
sort: [{ field: 'tm', dir: 'desc' }]
});
res?.data?.data.forEach(item => {
item.tm = dayjs(item.tm).format('YYYY-MM-DD HH') || '-';
});
videoList.value = res?.data?.data || [];
videoTotal.value = res?.data?.total || 0;
if (videoList.value.length > 0 && !activeFlpth.value) {
handleSelectMedia(videoList.value[0]);
}
} catch (error) {
console.error('获取视频列表失败:', error);
videoList.value = [];
} finally {
videoLoading.value = false;
}
};
//
const handleSelectMedia = (item: any) => {
activeFlpth.value = item.flpth;
activeMedia.value = { src: item.flpth || '' };
};
//
const handleVideoPageChange = (page: number) => {
videoPage.value = page;
activeFlpth.value = '';
activeMedia.value = { src: '' };
fetchVideoList();
};
//
const handleImageError = (e: Event) => {
const target = e.target as HTMLImageElement;
target.style.display = 'none';
};
// isActive
watch(
() => props.isActive,
active => {
if (active && !hasLoaded.value) {
initData();
}
},
{ immediate: true }
);
// stcd
watch(
() => modelStore.params.stcd,
async (newStcd, oldStcd) => {
if (newStcd && newStcd !== oldStcd) {
videoList.value = [];
activeFlpth.value = '';
activeMedia.value = { src: '' };
await initDefaultDateRange();
fetchVideoList();
}
}
);
</script>
<style lang="scss" scoped>
.no-live-video {
width: 100%;
.video-mode {
display: flex;
height: 600px;
gap: 12px;
.video-main {
flex: 0 0 60%;
background: #000;
border-radius: 4px;
overflow: hidden;
.media-display {
width: 100%;
height: 100%;
}
.media-empty {
display: flex;
align-items: center;
justify-content: center;
height: 100%;
}
}
.video-sidebar {
flex: 0 0 40%;
display: flex;
flex-direction: column;
border: 1px solid #e8e8e8;
border-radius: 4px;
.sidebar-search {
padding: 8px;
display: flex;
align-items: center;
border-bottom: 1px solid #e8e8e8;
}
.sidebar-list {
height: 600px;
overflow-y: auto;
display: grid;
grid-template-columns: repeat(2, 1fr);
grid-auto-rows: min-content;
gap: 8px;
padding: 8px;
.sidebar-item {
cursor: pointer;
border: 1px solid #f0f0f0;
border-radius: 4px;
overflow: hidden;
transition: all 0.2s;
&:hover {
border-color: #1890ff;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
&.active {
border-color: #1890ff;
background: #e6f7ff;
}
.item-thumb {
width: 100%;
height: 105px;
background: #1a1a1a;
overflow: hidden;
display: flex;
align-items: center;
justify-content: center;
img {
width: 100%;
height: 100%;
object-fit: cover;
}
.thumb-placeholder {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
img {
width: 100%;
height: 100%;
object-fit: cover;
}
}
}
.item-time {
padding: 4px;
font-size: 11px;
color: #666;
text-align: center;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
}
}
.sidebar-pagination {
padding: 8px;
text-align: center;
border-top: 1px solid #e8e8e8;
}
}
}
}
</style>

View File

@ -103,13 +103,13 @@ const tableColumns = ref([
String(record.tecnt).trim() !== '' String(record.tecnt).trim() !== ''
? record.tecnt ? record.tecnt
: '-'; : '-';
const transplant = const plantask =
record?.transplant !== undefined && record?.plantask !== undefined &&
record?.transplant !== null && record?.plantask !== null &&
String(record.transplant).trim() !== '' String(record.plplantask).trim() !== ''
? record.transplant ? record.plplantask
: '-'; : '-';
return `${tecnt}/${transplant}`; return `${tecnt}/${plantask}`;
} }
}, },
{ {
@ -123,13 +123,13 @@ const tableColumns = ref([
String(record.surnum).trim() !== '' String(record.surnum).trim() !== ''
? record.surnum ? record.surnum
: '-'; : '-';
const plantask = const transplant =
record?.plantask !== undefined && record?.transplant !== undefined &&
record?.plantask !== null && record?.transplant !== null &&
String(record.plantask).trim() !== '' String(record.transplant).trim() !== ''
? record.plantask ? record.transplant
: '-'; : '-';
return `${surnum}/${plantask}`; return `${surnum}/${transplant}`;
} }
}, },
{ {

View File

@ -479,6 +479,12 @@ const handleSearch = () => fetchData();
const initData = () => { const initData = () => {
if (hasLoaded.value) return; if (hasLoaded.value) return;
nextTick(() => { nextTick(() => {
if (modelStore.filter.rangeTm.length > 0) {
dateRange.value = [
dayjs(modelStore.filter.rangeTm[0]),
dayjs(modelStore.filter.rangeTm[1])
];
}
if (chartRef.value && !chartInstance) if (chartRef.value && !chartInstance)
chartInstance = echarts.init(chartRef.value); chartInstance = echarts.init(chartRef.value);
}); });

View File

@ -903,7 +903,15 @@ const handleExport = () => {
const initData = () => { const initData = () => {
if (hasLoaded.value) return; if (hasLoaded.value) return;
console.log('modelStore.filter.rangeTm:', modelStore.filter.rangeTm);
nextTick(() => { nextTick(() => {
if (modelStore.filter.rangeTm.length > 0) {
dateRange.value = [
dayjs(modelStore.filter.rangeTm[0]),
dayjs(modelStore.filter.rangeTm[1])
];
}
if (chartRef.value && !chartInstance) if (chartRef.value && !chartInstance)
chartInstance = echarts.init(chartRef.value); chartInstance = echarts.init(chartRef.value);
}); });

View File

@ -37,7 +37,12 @@
<!-- 同期对比列的自定义渲染 --> <!-- 同期对比列的自定义渲染 -->
<template #contrast="{ record }"> <template #contrast="{ record }">
<span <span
v-if="record.wt && record.beforeWt && !isNaN(Number(record.wt)) && !isNaN(Number(record.beforeWt))" v-if="
record.wt &&
record.beforeWt &&
!isNaN(Number(record.wt)) &&
!isNaN(Number(record.beforeWt))
"
:style="{ :style="{
color: color:
Number(record.wt) - Number(record.beforeWt) > 0 Number(record.wt) - Number(record.beforeWt) > 0
@ -664,6 +669,12 @@ const initData = () => {
setTimeout(() => { setTimeout(() => {
initChart(); initChart();
}, 200); }, 200);
if (modelStore.filter.rangeTm.length > 0) {
rangePicker.value = [
dayjs(modelStore.filter.rangeTm[0]),
dayjs(modelStore.filter.rangeTm[1])
];
}
hasLoaded.value = true; // hasLoaded.value = true; //
}; };

View File

@ -10,8 +10,8 @@
class="map-modal" class="map-modal"
> >
<!-- 问题 <!-- 问题
1.基础信息图片展示是什么逻辑 1.基础信息图片展示是什么逻辑
2. 电站专题展示逻辑 2. 电站专题展示逻辑
3. 实时视频回放 少接口 3. 实时视频回放 少接口
4. 预警提示 少接口 4. 预警提示 少接口
5. 生态流量 达标率查询不对() 5. 生态流量 达标率查询不对()
@ -20,13 +20,13 @@
8. 栖息地-流量监测 没有水位视频和流量视频 字段没数据 8. 栖息地-流量监测 没有水位视频和流量视频 字段没数据
9. 栖息地 水温水质流量没有数据没法测 9. 栖息地 水温水质流量没有数据没法测
10.鱼类增殖站 - 运行数据 要添加字典 10.鱼类增殖站 - 运行数据 要添加字典
11.珍惜植物园 - 种植要求字段不知道 11.珍惜植物园 - 种植要求字段不知道
12.水生调查断面 - 监测数据 不知道电导率 性腺发育期早期资源量和种类 12.水生调查断面 - 监测数据 不知道电导率 性腺发育期早期资源量和种类
13.水电告警情况 生态流量 不知道字段 接口传参有问题 13.水电告警情况 生态流量 不知道字段 接口传参有问题
14. 地图抽吸 碰撞检测 放大到具体层级锚点抽吸了但是popup弹框还在显示;
15.运行情况 计划开始运行时间 接口没接
没弄 环保设施
--> -->
<div v-if="modelStore.showStcdSelector" class="stcd-selector-wrapper"> <div v-if="modelStore.showStcdSelector" class="stcd-selector-wrapper">
<a-select <a-select
@ -88,18 +88,23 @@
v-show="currentActiveKey === 'videoInfo'" v-show="currentActiveKey === 'videoInfo'"
:is-active="currentActiveKey === 'videoInfo'" :is-active="currentActiveKey === 'videoInfo'"
/> />
<!-- 录像视频 -->
<NoLiveVideo
v-show="currentActiveKey === 'noLiveVideo'"
:is-active="currentActiveKey === 'noLiveVideo'"
/>
<!-- 全景影像 --> <!-- 全景影像 -->
<PanoramaInfo <PanoramaInfo
v-show="currentActiveKey === 'panoramaInfo'" v-show="currentActiveKey === 'panoramaInfo'"
:url="getTabUrl('panoramaInfo')" :url="getTabUrl('panoramaInfo')"
:is-active="currentActiveKey === 'panoramaInfo'" :is-active="currentActiveKey === 'panoramaInfo'"
/> />
<!-- 监测数据 - 电站运行过程线 --> <!-- 监测数据 - 电站运行过程线 -->
<MonitorInfo <MonitorInfo
v-show="currentActiveKey === 'monitorInfo'" v-show="currentActiveKey === 'monitorInfo'"
:is-active="currentActiveKey === 'monitorInfo'" :is-active="currentActiveKey === 'monitorInfo'"
/> />
<!-- 监测数据 - 水温 --> <!-- 监测数据 - 水温 -->
<WaterTemperature <WaterTemperature
v-show="currentActiveKey === 'WaterTemperature'" v-show="currentActiveKey === 'WaterTemperature'"
:is-active="currentActiveKey === 'WaterTemperature'" :is-active="currentActiveKey === 'WaterTemperature'"
@ -111,7 +116,7 @@
" "
:wt-config="modelStore.wtConfig" :wt-config="modelStore.wtConfig"
/> />
<!-- 监测数据 - 垂向水温 --> <!-- 监测数据 - 垂向水温 -->
<VerticalWaterTemperature <VerticalWaterTemperature
v-show="currentActiveKey === 'VerticalWaterTemperature'" v-show="currentActiveKey === 'VerticalWaterTemperature'"
:is-active="currentActiveKey === 'VerticalWaterTemperature'" :is-active="currentActiveKey === 'VerticalWaterTemperature'"
@ -167,6 +172,12 @@
v-show="currentActiveKey === 'FishZHFX'" v-show="currentActiveKey === 'FishZHFX'"
:is-active="currentActiveKey === 'FishZHFX'" :is-active="currentActiveKey === 'FishZHFX'"
/> />
<!-- 建设情况 -->
<FishFacilityBuildState
v-show="currentActiveKey === 'FishFacilityBuildState'"
:is-active="currentActiveKey === 'FishFacilityBuildState'"
:url="getTabUrl('FishFacilityBuildState')"
/>
<!-- AI运行识别 --> <!-- AI运行识别 -->
<AIYXSB <AIYXSB
v-show="currentActiveKey === 'AIYXSB'" v-show="currentActiveKey === 'AIYXSB'"
@ -178,12 +189,12 @@
:tabs-items="getTabChildren('tableTabs')" :tabs-items="getTabChildren('tableTabs')"
:is-active="currentActiveKey === 'tableTabs'" :is-active="currentActiveKey === 'tableTabs'"
/> />
<!-- 生态流量 --> <!-- 生态流量 -->
<EcologicalFlow <EcologicalFlow
v-show="currentActiveKey === 'EcologicalFlow'" v-show="currentActiveKey === 'EcologicalFlow'"
:is-active="currentActiveKey === 'EcologicalFlow'" :is-active="currentActiveKey === 'EcologicalFlow'"
/> />
<!-- 鱼类繁殖适宜性分析 --> <!-- 鱼类繁殖适宜性分析 -->
<WaterTemperatureRep <WaterTemperatureRep
v-show="currentActiveKey === 'WaterTemperatureRep'" v-show="currentActiveKey === 'WaterTemperatureRep'"
:is-active="currentActiveKey === 'WaterTemperatureRep'" :is-active="currentActiveKey === 'WaterTemperatureRep'"
@ -259,6 +270,7 @@ import { ref, watch, reactive, computed } 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 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'; //
@ -270,6 +282,7 @@ 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 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'; //
@ -999,6 +1012,12 @@ const handleClose = () => {
modelStore.verticalWaterStcd = ''; modelStore.verticalWaterStcd = '';
modelStore.aiSites = []; modelStore.aiSites = [];
modelStore.wtConfig = {}; modelStore.wtConfig = {};
modelStore.filter = {
stllgzlx: '', // -
stllTmType: '', // -
tm: '',
rangeTm: []
};
modelStore.wqShow = false; modelStore.wqShow = false;
modelStore.fishTabInfo = null; modelStore.fishTabInfo = null;
}; };

File diff suppressed because it is too large Load Diff

View File

@ -33,6 +33,7 @@ export class MapClass implements MapClassInterface {
this.service = new MapOl(); this.service = new MapOl();
} }
static getInstance(): MapClass { static getInstance(): MapClass {
console.log('getInstance');
if (!this.instance) { if (!this.instance) {
this.instance = new MapClass(); this.instance = new MapClass();
} }

View File

@ -72,6 +72,7 @@ export class MapOl implements MapInterface {
private hoveredFeatureId: string | number | null = null; private hoveredFeatureId: string | number | null = null;
private popupManager: PopupManager; private popupManager: PopupManager;
private regionMaskManager: RegionMaskManager; private regionMaskManager: RegionMaskManager;
private isBatchPopupMode = false;
constructor() { constructor() {
this.pointLayerManager = new PointLayerManager({ this.pointLayerManager = new PointLayerManager({
map: null, map: null,
@ -142,7 +143,11 @@ export class MapOl implements MapInterface {
this.popupManager.setMap(this.map); this.popupManager.setMap(this.map);
this.regionMaskManager.setContext(this.map, this.view); this.regionMaskManager.setContext(this.map, this.view);
this.view.on('change:resolution', () => {}); this.view.on('change:resolution', () => {
this.handleZoomChange();
const zoom = this.view.getZoom();
console.log('mouse zoom:', zoom);
});
this.map.on('click', evt => { this.map.on('click', evt => {
this.popupManager.showPopup(undefined, undefined); this.popupManager.showPopup(undefined, undefined);
this.popupManager.handleMapClick(evt.pixel, detectedFeature => { this.popupManager.handleMapClick(evt.pixel, detectedFeature => {
@ -169,9 +174,24 @@ export class MapOl implements MapInterface {
targetElement.style.cursor = payload.hoveredId ? 'pointer' : ''; targetElement.style.cursor = payload.hoveredId ? 'pointer' : '';
} }
this.showPopup(payload.detectedFeature, payload.coordinate); // 批量 popup 模式下(>= 14 级)不触发 hover popup
if (!this.isBatchPopupMode) {
this.showPopup(payload.detectedFeature, payload.coordinate);
}
}); });
}); });
this.map.on('pointerdrag', () => {
if (this.isBatchPopupMode) {
this.updateBatchPopups();
}
});
this.map.on('moveend', () => {
if (this.isBatchPopupMode) {
this.updateBatchPopups();
}
});
return Promise.resolve(this.map); return Promise.resolve(this.map);
} catch (e) { } catch (e) {
console.error('OL Init Error', e); console.error('OL Init Error', e);
@ -220,6 +240,52 @@ export class MapOl implements MapInterface {
showPopup(feature: Feature | undefined, coordinate: number[] | undefined) { showPopup(feature: Feature | undefined, coordinate: number[] | undefined) {
this.popupManager.showPopup(feature, coordinate); this.popupManager.showPopup(feature, coordinate);
} }
/**
* >= 15 popup
*/
private handleZoomChange() {
if (!this.view) return;
const zoom = this.view.getZoom();
if (zoom === undefined) return;
if (zoom >= 15) {
this.enableBatchPopupMode();
} else {
this.disableBatchPopupMode();
}
}
/**
* popup popup
*/
private enableBatchPopupMode() {
this.isBatchPopupMode = true;
this.popupManager.clearBatchPopups();
this.updateBatchPopups();
}
/**
* popup popup
*/
private disableBatchPopupMode() {
this.isBatchPopupMode = false;
this.popupManager.clearBatchPopups();
this.popupManager.showPopup(undefined, undefined);
}
/**
* popups
*/
private updateBatchPopups() {
const features = this.pointLayerManager.getFeaturesInViewport();
// 传入样式检查器,过滤掉 declutter/距离过滤等隐藏的锚点
this.popupManager.showPopupsForFeatures(features, feature => {
const style = this.createPointStyle(feature);
return style !== null;
});
}
/** /**
* ( Leaflet DivIcon ) * ( Leaflet DivIcon )
*/ */
@ -660,6 +726,10 @@ export class MapOl implements MapInterface {
const layerInstance = this.layerRegistry.get(registryKey as string); const layerInstance = this.layerRegistry.get(registryKey as string);
if (layerInstance) { if (layerInstance) {
layerInstance.setVisible(checked); layerInstance.setVisible(checked);
// 图层显隐变化时刷新 batch popup
if (this.isBatchPopupMode) {
this.updateBatchPopups();
}
} else { } else {
console.warn( console.warn(
`未找到标识为 [${registryKey}] 的图层实例,当前注册表 keys:`, `未找到标识为 [${registryKey}] 的图层实例,当前注册表 keys:`,
@ -669,6 +739,10 @@ export class MapOl implements MapInterface {
} }
mdLayerTreeShowOrHidden(layerType: string, checked?: boolean): void { mdLayerTreeShowOrHidden(layerType: string, checked?: boolean): void {
this.pointLayerManager.setLayerVisible(layerType, checked); this.pointLayerManager.setLayerVisible(layerType, checked);
// 图层显隐变化时刷新 batch popup
if (this.isBatchPopupMode) {
this.updateBatchPopups();
}
} }
hasLayer(layerType: string): void { hasLayer(layerType: string): void {
@ -1212,7 +1286,6 @@ export class MapOl implements MapInterface {
// 使用 Object.assign 批量设置样式,代码更整洁 // 使用 Object.assign 批量设置样式,代码更整洁
Object.assign(container.style, { Object.assign(container.style, {
position: 'absolute', position: 'absolute',
backgroundColor: 'rgba(255, 255, 255, 0.9)',
border: '1px solid #000', border: '1px solid #000',
padding: '4px 8px', padding: '4px 8px',
borderRadius: '4px', borderRadius: '4px',
@ -1223,7 +1296,6 @@ export class MapOl implements MapInterface {
display: 'flex', display: 'flex',
alignItems: 'center', alignItems: 'center',
zIndex: '1001', zIndex: '1001',
boxShadow: '0 2px 4px rgba(0,0,0,0.2)',
pointerEvents: 'auto' // 确保可以点击 pointerEvents: 'auto' // 确保可以点击
}); });
@ -1309,6 +1381,10 @@ export class MapOl implements MapInterface {
key || '', key || '',
!!checked !!checked
); );
// 图例变化时刷新 batch popup
if (this.isBatchPopupMode) {
this.updateBatchPopups();
}
} }
/** /**
@ -1327,6 +1403,10 @@ export class MapOl implements MapInterface {
anchoPointState, anchoPointState,
checked checked
); );
// 图例变化时刷新 batch popup
if (this.isBatchPopupMode) {
this.updateBatchPopups();
}
} }
switchView(_type): void {} switchView(_type): void {}

View File

@ -32,6 +32,34 @@ export class PointLayerManager {
this.map = map; this.map = map;
} }
// 备注:获取当前屏幕可视区域内的所有点要素(仅包含图层可见且图例可见的要素)。
getFeaturesInViewport(): Feature[] {
if (!this.map) return [];
const extent = this.map.getView().calculateExtent(this.map.getSize());
if (!extent) return [];
const [minX, minY, maxX, maxY] = extent;
const result: Feature[] = [];
this.forEachFeature((feature, layer) => {
// 跳过图层本身不可见的要素
if (!layer.getVisible()) return;
const geom = feature.getGeometry();
if (!geom || geom.getType() !== 'Point') return;
const coords = (geom as Point).getCoordinates();
const [x, y] = coords;
if (x >= minX && x <= maxX && y >= minY && y <= maxY) {
result.push(feature);
}
});
return result;
}
// 备注:统一返回当前点图层注册表,供外层做遍历或清理。 // 备注:统一返回当前点图层注册表,供外层做遍历或清理。
getRegistry() { getRegistry() {
return this.layerRegistry; return this.layerRegistry;
@ -128,6 +156,7 @@ export class PointLayerManager {
const vectorLayer = this.layerRegistry.get(layerKey); const vectorLayer = this.layerRegistry.get(layerKey);
if (vectorLayer && vectorLayer.getVisible() !== visible) { if (vectorLayer && vectorLayer.getVisible() !== visible) {
vectorLayer.setVisible(visible); vectorLayer.setVisible(visible);
vectorLayer.changed();
} }
} }

View File

@ -204,6 +204,213 @@ export class PopupManager {
this.popupElement.style.display = 'none'; this.popupElement.style.display = 'none';
} }
// 备注:批量显示多个要素的 Popup使用绝对定位的 div 而非 Overlay。
showPopupsForFeatures(
features: Feature[],
styleChecker?: (feature: Feature) => boolean
) {
if (!this.map || !this.popupElement) return;
// 清除之前批量显示的 popups
this.clearBatchPopups();
// 过滤:只显示图例可见的要素
const visibleFeatures = features.filter(
f => f.get('_legendVisible') !== false
);
if (visibleFeatures.length === 0) return;
const mapElement = this.map.getTargetElement();
const batchContainer = document.createElement('div');
batchContainer.className = 'batch-popup-container';
batchContainer.style.cssText = `
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
z-index: 1000;
`;
batchContainer.id = 'batch-popup-container';
// 先添加容器到 DOM以便后续元素能正确测量尺寸
mapElement.appendChild(batchContainer);
// 用于碰撞检测的已放置 popup 列表
const placedPopups: Array<{
left: number;
right: number;
top: number;
bottom: number;
}> = [];
let styleHiddenCount = 0;
let blockedCount = 0;
let renderedCount = 0;
const renderedIds: Array<string | number | null> = [];
const blockedIds: Array<string | number | null> = [];
// 用于 declutter 模拟检测的已放置要素边界框列表
const placedAnchors: Array<{
bbLeft: number;
bbRight: number;
bbTop: number;
bbBottom: number;
}> = [];
visibleFeatures.forEach(feature => {
const geom = feature.getGeometry();
if (!geom || geom.getType() !== 'Point') return;
const coords = (geom as Point).getCoordinates();
const pixel = this.map?.getPixelFromCoordinate(coords);
if (!pixel) return;
// 检查样式是否隐藏declutter、距离过滤等
if (styleChecker && !styleChecker(feature)) {
styleHiddenCount += 1;
return;
}
// 模拟 OpenLayers declutter基于图标+文字的边界框碰撞检测
const zoom = this.map.getView().getZoom() ?? 14;
let dynamicScale = 0.7 + (zoom - 4.5) * 0.08;
dynamicScale = Math.max(0.5, Math.min(3.0, dynamicScale));
// 计算要素的碰撞边界框
const labelText = (feature.get('_labelText') as string) || '';
const labelLineCount = labelText.split('\n').length;
const labelWidth = Math.min(
labelText
.split('\n')
.reduce((max, line) => Math.max(max, line.length), 0) *
7 *
dynamicScale,
200
);
const labelHeight = 12 * dynamicScale * labelLineCount;
const labelOffsetY =
labelLineCount > 1 ? -30 * dynamicScale : -22 * dynamicScale;
const iconSize = 32 * dynamicScale; // 假设图标原始大小 32x32
// 碰撞框 = 图标框 文字框
const bbLeft = pixel[0] - Math.max(iconSize / 2, labelWidth / 2);
const bbRight = pixel[0] + Math.max(iconSize / 2, labelWidth / 2);
const bbTop = pixel[1] + labelOffsetY - labelHeight;
const bbBottom = pixel[1] + iconSize / 2;
// 检查是否和已放置的要素碰撞
let blocked = false;
for (const placed of placedAnchors) {
if (
bbRight > placed.bbLeft &&
bbLeft < placed.bbRight &&
bbBottom > placed.bbTop &&
bbTop < placed.bbBottom
) {
blocked = true;
break;
}
}
if (blocked) {
blockedCount += 1;
blockedIds.push((feature.getId?.() as string | number | null) ?? null);
return;
}
const props = feature.getProperties();
const popupHtml = props.popupHtml || generatePopupHtml(props);
if (!popupHtml) return;
// 记录边界框用于 declutter 检测
placedAnchors.push({ bbLeft, bbRight, bbTop, bbBottom });
// 创建与原始 popupElement 完全相同的样式
const popupEl = document.createElement('div');
popupEl.className = this.popupElement.className;
popupEl.innerHTML = popupHtml;
// 不设置自定义 cssText只设置定位对应原始 popup 的 positioning: 'bottom-center', offset: [0, -10]
popupEl.style.position = 'absolute';
popupEl.style.display = 'block';
popupEl.style.transform = 'translate(-50%, -100%)'; // bottom-center 对齐
popupEl.style.pointerEvents = 'none';
// 临时设置 visibility: hidden 来测量尺寸
popupEl.style.visibility = 'hidden';
popupEl.style.left = `${pixel[0]}px`;
popupEl.style.top = `${pixel[1] - 10}px`;
batchContainer.appendChild(popupEl);
// 获取 popup 尺寸
const rect = popupEl.getBoundingClientRect();
const popupWidth = rect.width;
const popupHeight = rect.height;
// 计算 popup 的位置(对应原始 positioning: 'bottom-center', offset: [0, -10]
const popupX = pixel[0];
const popupY = pixel[1] - 10; // bottom-center 对齐 + 10px 偏移
const popupRect = {
left: popupX - popupWidth / 2,
right: popupX + popupWidth / 2,
top: popupY - popupHeight,
bottom: popupY
};
// 碰撞检测
let hasCollision = false;
for (const placed of placedPopups) {
if (this.checkCollision(popupRect, placed)) {
hasCollision = true;
break;
}
}
if (!hasCollision) {
// 无碰撞,显示 popup
popupEl.style.visibility = 'visible';
popupEl.style.left = `${popupX}px`;
popupEl.style.top = `${popupY}px`;
renderedCount += 1;
renderedIds.push((feature.getId?.() as string | number | null) ?? null);
placedPopups.push({
left: popupRect.left,
right: popupRect.right,
top: popupRect.top,
bottom: popupRect.bottom
});
} else {
// 有碰撞,移除该 popup
blockedCount += 1;
blockedIds.push((feature.getId?.() as string | number | null) ?? null);
batchContainer.removeChild(popupEl);
}
});
}
// 备注:清除批量显示的 popups。
clearBatchPopups() {
const existing = document.getElementById('batch-popup-container');
if (existing) {
existing.remove();
}
}
// 备注:检测两个 popup 矩形是否碰撞。
private checkCollision(
rect1: { left: number; right: number; top: number; bottom: number },
rect2: { left: number; right: number; top: number; bottom: number }
): boolean {
const padding = 4;
return !(
rect1.right + padding < rect2.left ||
rect1.left - padding > rect2.right ||
rect1.bottom + padding < rect2.top ||
rect1.top - padding > rect2.bottom
);
}
// 备注:统一重置 hover 和 Popup 状态,供地图销毁和切换时复用。 // 备注:统一重置 hover 和 Popup 状态,供地图销毁和切换时复用。
reset() { reset() {
if (this.animationFrameId) { if (this.animationFrameId) {
@ -264,6 +471,7 @@ export class PopupManager {
// 备注:销毁 Popup 管理器内部状态和 Overlay 引用。 // 备注:销毁 Popup 管理器内部状态和 Overlay 引用。
destroy() { destroy() {
this.clearBatchPopups();
this.reset(); this.reset();
if (this.popupElement && this.popupMouseEnterHandler) { if (this.popupElement && this.popupMouseEnterHandler) {
this.popupElement.removeEventListener( this.popupElement.removeEventListener(

View File

@ -1,11 +1,19 @@
<!-- 垂向水温变化图 --> <!-- 垂向水温变化图 -->
<template> <template>
<SidePanelItem title="垂向水温变化" :moreSelect="select" :datetimePicker="datetimePicker" <SidePanelItem
@update-values="handlePanelChange1"> title="垂向水温变化"
:moreSelect="select"
:datetimePicker="datetimePicker"
@update-values="handlePanelChange1"
>
<div class="chart-wrapper"> <div class="chart-wrapper">
<!-- 图表容器 --> <!-- 图表容器 -->
<a-spin :spinning="loading" tip="加载中..."> <a-spin :spinning="loading" tip="加载中...">
<div v-show="showemit && !loading" ref="chartRef" class="chart-container"></div> <div
v-show="showemit && !loading"
ref="chartRef"
class="chart-container"
></div>
<div v-show="!showemit && !loading" class="chart-container"> <div v-show="!showemit && !loading" class="chart-container">
<a-empty /> <a-empty />
</div> </div>
@ -15,18 +23,29 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ref, computed, onMounted, onBeforeUnmount, watch, nextTick } from 'vue'; import {
ref,
computed,
onMounted,
onBeforeUnmount,
watch,
nextTick
} 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';
import { useJidiSelectEventStore } from "@/store/modules/jidiSelectEvent"; import { useJidiSelectEventStore } from '@/store/modules/jidiSelectEvent';
import { getChuiXiangShuiWenTreeStcd, getCxswList } from "@/api/sw"; import { getChuiXiangShuiWenTreeStcd, getCxswList } from '@/api/sw';
import { useModelStore } from "@/store/modules/model"; import { useModelStore } from '@/store/modules/model';
import { useMapModalStore } from '@/store/modules/mapModal';
import { MapClass } from '@/components/gis/map.class';
const modelStore = useModelStore(); const modelStore = useModelStore();
const JidiSelectEventStore = useJidiSelectEventStore(); const JidiSelectEventStore = useJidiSelectEventStore();
const baseid = ref(''); const baseid = ref('');
//
const year = ref('');
// //
defineOptions({ defineOptions({
name: 'chuixiangshuiwenChangeMod' name: 'chuixiangshuiwenChangeMod'
@ -140,7 +159,7 @@ function transformAggregatesToChartData(
if (!monthData || monthData.length === 0) { if (!monthData || monthData.length === 0) {
result.push({ result.push({
name: `${month}`, name: `${month}`,
dataXy: [] // ECharts dataXy: [] // ECharts
}); });
continue; continue;
} }
@ -152,11 +171,11 @@ function transformAggregatesToChartData(
stnm: string; stnm: string;
}> = monthData.map((item: any) => ({ }> = monthData.map((item: any) => ({
value: [ value: [
parseFloat(item.vwt), // parseFloat(item.vwt), //
parseInt(item.wthg) // / parseInt(item.wthg) // /
] as [number, number], // ] as [number, number], //
stcd: stcd, // stcd: stcd, //
stnm: stnm // stnm: stnm //
})); }));
result.push({ result.push({
@ -172,16 +191,25 @@ function transformAggregatesToChartData(
* 根据当前年月生成颜色和图例显隐状态 * 根据当前年月生成颜色和图例显隐状态
*/ */
function generateColorsAndVisibility( function generateColorsAndVisibility(
dataLength: number, // 12 dataLength: number, // 12
sDate: string | number sDate: string | number
): { ): {
colors: string[]; colors: string[];
selected: Record<string, boolean>; selected: Record<string, boolean>;
} { } {
const baseColors = [ const baseColors = [
'#6ca4f7', '#78c300', '#9556a4', '#df91ab', '#6ca4f7',
'#7399c6', '#dbb629', '#56c2e3', '#f56a06', '#78c300',
'#cdba75', '#76523b', '#df75b6', '#00a050' '#9556a4',
'#df91ab',
'#7399c6',
'#dbb629',
'#56c2e3',
'#f56a06',
'#cdba75',
'#76523b',
'#df75b6',
'#00a050'
]; ];
const currentYear = new Date().getFullYear(); const currentYear = new Date().getFullYear();
@ -233,7 +261,7 @@ const getCurrentHourTimeTwo = () => {
const startTime = new Date(endTime); const startTime = new Date(endTime);
startTime.setMonth(startTime.getMonth() - 1); startTime.setMonth(startTime.getMonth() - 1);
const formatDateTime = (date) => { const formatDateTime = date => {
const year = date.getFullYear(); const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0'); const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0'); const day = String(date.getDate()).padStart(2, '0');
@ -253,15 +281,15 @@ const select = ref({
value: '', value: '',
options: [], options: [],
picker: undefined, picker: undefined,
format: undefined, format: undefined
}); });
const datetimePicker = ref({ const datetimePicker = ref({
show: true, show: true,
value: computed(() => getCurrentHourTime()), value: computed(() => getCurrentHourTime()),
format: "YYYY", format: 'YYYY',
picker: "year" as const, picker: 'year' as const,
options: [], options: []
}); });
/** /**
@ -273,58 +301,61 @@ const selectOptions = async () => {
try { try {
let filters = [ let filters = [
{ {
"field": "mway", field: 'mway',
"operator": "eq", operator: 'eq',
"dataType": "string", dataType: 'string',
"value": 2 value: 2
}, },
{ {
"field": "tm", field: 'tm',
"operator": "gte", operator: 'gte',
"dataType": "date", dataType: 'date',
"value": getCurrentHourTimeTwo()[0] value: getCurrentHourTimeTwo()[0]
}, },
{ {
"field": "tm", field: 'tm',
"operator": "lte", operator: 'lte',
"dataType": "date", dataType: 'date',
"value": getCurrentHourTimeTwo()[1] value: getCurrentHourTimeTwo()[1]
} }
]; ];
if (baseid.value && baseid.value !== 'all') { if (baseid.value && baseid.value !== 'all') {
filters.push({ filters.push({
"field": "baseId", field: 'baseId',
"operator": "eq", operator: 'eq',
"dataType": "string", dataType: 'string',
"value": baseid.value value: baseid.value
}); });
} }
let filter = { let filter = {
"logic": "and", logic: 'and',
"filters": filters filters: filters
}; };
let sort = baseid.value && baseid.value !== 'all' ? [ let sort =
{ baseid.value && baseid.value !== 'all'
"field": "rstcdStepSort", ? [
"dir": "asc" {
} field: 'rstcdStepSort',
] : [ dir: 'asc'
{ }
"field": "baseId", ]
"dir": "asc" : [
}, {
{ field: 'baseId',
"field": "rstcdStepSort", dir: 'asc'
"dir": "asc" },
} {
]; field: 'rstcdStepSort',
dir: 'asc'
}
];
const data = { filter, sort }; const data = { filter, sort };
const res = await getChuiXiangShuiWenTreeStcd(data); const res = await getChuiXiangShuiWenTreeStcd(data);
let dataOne = res?.data?.data || res.data let dataOne = res?.data?.data || res.data;
if (dataOne.length > 0) { if (dataOne.length > 0) {
const treeData = dataOne.map(item => { const treeData = dataOne.map(item => {
@ -334,27 +365,29 @@ const selectOptions = async () => {
selectable: false, selectable: false,
children: (item.stcdVo || []).map(child => ({ children: (item.stcdVo || []).map(child => ({
title: child.stnm, title: child.stnm,
value: child.stcd value: child.stcd,
lgtd: child.lgtd,
lttd: child.lttd
})) }))
}; };
}); });
if (baseid.value == 'all') { if (baseid.value == 'all') {
select.value.value = '008640203800001014' select.value.value = '008640203800001014';
select.value.options = treeData; select.value.options = treeData;
} else { } else {
select.value.value = treeData[0].children[0].value select.value.value = treeData[0].children[0].value;
select.value.options = treeData; select.value.options = treeData;
} }
} else { } else {
select.value.options = [] select.value.options = [];
select.value.value = '' select.value.value = '';
} }
} catch (error) { } catch (error) {
console.error('获取站点数据失败:', error); console.error('获取站点数据失败:', error);
} finally { } finally {
loading.value = false; // loading loading.value = false; // loading
} }
} };
// ==================== ==================== // ==================== ====================
@ -362,14 +395,16 @@ const chartRef = ref<HTMLElement | null>(null);
let chartInstance: ECharts | null = null; let chartInstance: ECharts | null = null;
// //
const chartData = ref<Array<{ const chartData = ref<
name: string; Array<{
dataXy: Array<{ name: string;
value: [number, number]; dataXy: Array<{
stcd: string; value: [number, number];
stnm: string; stcd: string;
}>; stnm: string;
}>>([]); }>;
}>
>([]);
// Y1=2= // Y1=2=
const type = ref(1); const type = ref(1);
@ -390,26 +425,26 @@ const fetchChartData = async (stcd: string, year: string | number) => {
try { try {
const params = { const params = {
filter: { filter: {
logic: "and", logic: 'and',
filters: [ filters: [
{ {
field: "year", field: 'year',
operator: "eq", operator: 'eq',
dataType: "string", dataType: 'string',
value: String(year) value: String(year)
}, },
{ {
field: "stcd", field: 'stcd',
operator: "eq", operator: 'eq',
dataType: "string", dataType: 'string',
value: stcd value: stcd
} }
] ]
}, },
sort: [ sort: [
{ {
field: "depth", field: 'depth',
dir: "asc" dir: 'asc'
} }
] ]
}; };
@ -429,18 +464,26 @@ const fetchChartData = async (stcd: string, year: string | number) => {
}); });
// stcd stnm // stcd stnm
chartData.value = transformAggregatesToChartData(aggregates, stcd, currentStnm); chartData.value = transformAggregatesToChartData(
aggregates,
stcd,
currentStnm
);
// 12 chartData.value.length // 12 chartData.value.length
const { colors: newColors, selected: newSelected } = generateColorsAndVisibility( const { colors: newColors, selected: newSelected } =
12, // 12 generateColorsAndVisibility(
year 12, // 12
); year
);
colors.value = newColors; colors.value = newColors;
selected.value = newSelected; selected.value = newSelected;
// showemit // showemit
if (chartData.value.length > 0 && chartData.value.some(item => item.dataXy.length > 0)) { if (
chartData.value.length > 0 &&
chartData.value.some(item => item.dataXy.length > 0)
) {
showemit.value = true; // showemit.value = true; //
} else { } else {
showemit.value = false; // showemit.value = false; //
@ -490,12 +533,26 @@ const initChart = () => {
console.log('站点名称:', stnm); console.log('站点名称:', stnm);
console.log('水温:', temperature); console.log('水温:', temperature);
console.log('深度:', depth); console.log('深度:', depth);
const matchMonth = params.seriesName.match(/\d+/);
const monthNum = matchMonth ? parseInt(matchMonth[0]) : 1;
const startTime = `${year.value}-${String(monthNum).padStart(
2,
'0'
)}-01 00:00:00`;
const lastDay = new Date(year.value, monthNum, 0).getDate();
const endTime = `${year.value}-${String(monthNum).padStart(
2,
'0'
)}-${lastDay} 23:59:59`;
console.log(endTime);
modelStore.modalVisible = true; modelStore.modalVisible = true;
modelStore.params.sttp = "WT"; modelStore.params.sttp = 'WT';
modelStore.params.enfc = '1'; modelStore.params.enfc = '1';
modelStore.title = stnm ; modelStore.title = stnm;
// modelStore.isBasicEdit = true; // modelStore.isBasicEdit = true;
modelStore.params.stcd = stcd; modelStore.params.stcd = stcd;
modelStore.filter.rangeTm = [startTime, endTime];
// TODO: // TODO:
}); });
@ -511,7 +568,7 @@ const updateChart = () => {
const yData: any[] = []; const yData: any[] = [];
const legend: string[] = []; const legend: string[] = [];
chartData.value.forEach((item) => { chartData.value.forEach(item => {
if (item.dataXy.length > 0) { if (item.dataXy.length > 0) {
// value[0] // value[0]
xData.push(item.dataXy[0].value[0]); xData.push(item.dataXy[0].value[0]);
@ -521,7 +578,7 @@ const updateChart = () => {
yData.push({ yData.push({
name: item.name, name: item.name,
data: item.dataXy, // 使 data: item.dataXy, // 使
type: 'line', type: 'line',
smooth: true, smooth: true,
connectNulls: true, connectNulls: true,
@ -593,7 +650,7 @@ const updateChart = () => {
type: 'value' as const, type: 'value' as const,
name: type.value === 2 ? '高程(m)' : '水深(m)', name: type.value === 2 ? '高程(m)' : '水深(m)',
min: 0, min: 0,
scale: false, // Y 0 scale: false, // Y 0
nameGap: type.value === 2 ? 22 : 10, nameGap: type.value === 2 ? 22 : 10,
inverse: type.value !== 2 inverse: type.value !== 2
}, },
@ -612,13 +669,17 @@ const handleChartClick = (params: any) => {
const matchMonth = params.seriesName.match(/\d+/); const matchMonth = params.seriesName.match(/\d+/);
const monthNum = matchMonth ? parseInt(matchMonth[0]) : 1; const monthNum = matchMonth ? parseInt(matchMonth[0]) : 1;
const year = typeof datetimePicker.value.value === 'string' const year =
? parseInt(datetimePicker.value.value) typeof datetimePicker.value.value === 'string'
: datetimePicker.value.value; ? parseInt(datetimePicker.value.value)
: datetimePicker.value.value;
const startTime = `${year}-${String(monthNum).padStart(2, '0')}-01 00:00:00`; const startTime = `${year}-${String(monthNum).padStart(2, '0')}-01 00:00:00`;
const lastDay = new Date(year, monthNum, 0).getDate(); const lastDay = new Date(year, monthNum, 0).getDate();
const endTime = `${year}-${String(monthNum).padStart(2, '0')}-${lastDay} 23:59:59`; const endTime = `${year}-${String(monthNum).padStart(
2,
'0'
)}-${lastDay} 23:59:59`;
const currentStcd = select.value.value; const currentStcd = select.value.value;
let currentStnm = ''; let currentStnm = '';
@ -669,7 +730,7 @@ onBeforeUnmount(() => {
watch( watch(
() => JidiSelectEventStore.selectedItem, () => JidiSelectEventStore.selectedItem,
async (newVal) => { async newVal => {
if (!newVal || !newVal.wbsCode) { if (!newVal || !newVal.wbsCode) {
return; return;
} }
@ -689,20 +750,28 @@ watch(
// }, // },
// { deep: true } // { deep: true }
// ); // );
const handlePanelChange1 = (data: any, type: string) => {
const handlePanelChange1 = (data: any) => {
console.log('当前所有控件状态:', data); console.log('当前所有控件状态:', data);
if (data.moreSelect && data.datetime) { if (data.moreSelect && data.datetime) {
fetchChartData(data.moreSelect, data.datetime) if (type == 'click') {
const mapClass = MapClass.getInstance();
mapClass.flyTopanto(
[
Number(data.selectedNodeExtra.lgtd),
Number(data.selectedNodeExtra.lttd)
],
15
);
}
year.value = data.datetime;
fetchChartData(data.moreSelect, data.datetime);
} else { } else {
showemit.value = true showemit.value = true;
chartData.value = []; chartData.value = [];
colors.value = []; colors.value = [];
selected.value = {}; selected.value = {};
showemit.value = false; showemit.value = false;
} }
}; };
</script> </script>

File diff suppressed because it is too large Load Diff

View File

@ -107,6 +107,8 @@ export const useMapOrchestrator = () => {
mapDataStore.setLoading(true); mapDataStore.setLoading(true);
try { try {
const previousPageKey = mapConfigStore.lastLoadOptions?.pageKey || '';
const previousCheckedKeys = mapViewStore.getCheckedLayerKeys();
const { layerConfig, legendOriginal, pageLegend } = const { layerConfig, legendOriginal, pageLegend } =
await mapConfigStore.loadPageMapConfig({ await mapConfigStore.loadPageMapConfig({
systemId: SYSTEM_ID, systemId: SYSTEM_ID,
@ -122,11 +124,26 @@ export const useMapOrchestrator = () => {
if (layerConfig.length > 0) { if (layerConfig.length > 0) {
mapStore.setLayerData(layerConfig); mapStore.setLayerData(layerConfig);
ensureBaseLayersInitialized(layerConfig); ensureBaseLayersInitialized(layerConfig);
const checkedKeys = mapViewStore.getCheckedLayerKeys(); let checkedKeys = mapViewStore.getCheckedLayerKeys();
if (previousPageKey === pageKey && previousCheckedKeys.length > 0) {
const currentLayerKeys = new Set(
layerConfig
.flatMap((item: any) => layerConfig2Flat([item]))
.map((item: any) => item?.key)
.filter(Boolean)
);
const runtimeCheckedKeys = previousCheckedKeys.filter(key =>
currentLayerKeys.has(key)
);
if (runtimeCheckedKeys.length > 0) {
mapViewStore.setCheckedLayerKeys(runtimeCheckedKeys);
checkedKeys = runtimeCheckedKeys;
}
}
await mapStore.loadAllLayerData(layerConfig, checkedKeys); await mapStore.loadAllLayerData(layerConfig, checkedKeys);
} }
} finally { } finally {
mapDataStore.setLoading(false); // mapDataStore.setLoading(false);
} }
}; };
@ -380,7 +397,7 @@ export const useMapOrchestrator = () => {
}; };
// 备注:统一处理搜索定位,按点位编码从缓存数据中查找并飞行到目标位置。 // 备注:统一处理搜索定位,按点位编码从缓存数据中查找并飞行到目标位置。
const focusPoint = (pointId: string, zoom: number = 14) => { const focusPoint = (pointId: string, zoom: number = 15) => {
if (!pointId) return; if (!pointId) return;
const targetPoint = mapDataStore.pointData.find((item: any) => { const targetPoint = mapDataStore.pointData.find((item: any) => {
return item.stcd === pointId || item._id === pointId; return item.stcd === pointId || item._id === pointId;

View File

@ -1,36 +1,73 @@
<template> <template>
<div class="monthly-average-container"> <div class="monthly-average-container">
<!-- 搜索表单 --> <!-- 搜索表单 -->
<a-form ref="formRef" :model="formValue" layout="inline" class="search-form"> <a-form
ref="formRef"
:model="formValue"
layout="inline"
class="search-form"
>
<a-form-item label="基地名称" name="dataDimensionVal"> <a-form-item label="基地名称" name="dataDimensionVal">
<a-select v-model:value="formValue.dataDimensionVal" placeholder="请选择" style="width: 200px" <a-select
@change="handleBaseChange"> v-model:value="formValue.dataDimensionVal"
<a-select-option v-for="item in jdList" :key="item.wbsCode" :value="item.wbsCode"> placeholder="请选择"
style="width: 200px"
@change="handleBaseChange"
>
<a-select-option
v-for="item in jdList"
:key="item.wbsCode"
:value="item.wbsCode"
>
{{ item.wbsName }} {{ item.wbsName }}
</a-select-option> </a-select-option>
</a-select> </a-select>
</a-form-item> </a-form-item>
<a-form-item label="所在流域" name="rvcd"> <a-form-item label="所在流域" name="rvcd">
<a-select v-model:value="formValue.rvcd" placeholder="请选择" style="width: 200px" <a-select
:disabled="!formValue.dataDimensionVal" @change="handleRvcdChange"> v-model:value="formValue.rvcd"
<a-select-option v-for="item in lyList" :key="item.value" :value="item.value"> placeholder="请选择"
style="width: 200px"
:disabled="!formValue.dataDimensionVal"
@change="handleRvcdChange"
>
<a-select-option
v-for="item in lyList"
:key="item.value"
:value="item.value"
>
{{ item.label }} {{ item.label }}
</a-select-option> </a-select-option>
</a-select> </a-select>
</a-form-item> </a-form-item>
<a-form-item label="断面名称" name="stcd"> <a-form-item label="断面名称" name="stcd">
<a-select v-model:value="formValue.stcd" placeholder="请选择" style="width: 200px" :disabled="!formValue.rvcd"> <a-select
<a-select-option v-for="item in dmList" :key="item.value" :value="item.value"> v-model:value="formValue.stcd"
placeholder="请选择"
style="width: 200px"
:disabled="!formValue.rvcd"
>
<a-select-option
v-for="item in dmList"
:key="item.value"
:value="item.value"
>
{{ item.label }} {{ item.label }}
</a-select-option> </a-select-option>
</a-select> </a-select>
</a-form-item> </a-form-item>
<a-form-item label="月份" name="tm"> <a-form-item label="月份" name="tm">
<a-date-picker v-model:value="formValue.tm" picker="month" :disabled-date="disabledDate" format="YYYY-MM" <a-date-picker
value-format="YYYY-MM-DD HH:mm:ss" :allow-clear="false" /> v-model:value="formValue.tm"
picker="month"
:disabled-date="disabledDate"
format="YYYY-MM"
value-format="YYYY-MM-DD HH:mm:ss"
:allow-clear="false"
/>
</a-form-item> </a-form-item>
<a-form-item> <a-form-item>
@ -45,8 +82,14 @@
<div style="width: 100%; display: flex"> <div style="width: 100%; display: flex">
<!-- 图表容器始终存在不再使用 v-if --> <!-- 图表容器始终存在不再使用 v-if -->
<div class="chart-wrapper" ref="chartRef"></div> <div class="chart-wrapper" ref="chartRef"></div>
<BasicTable ref="tableRef" :scrollY="460" :columns="columns" :list-url="DetGetKendoListCust" <BasicTable
:search-params="{ sort: sort }" :transform-data="customTransform"> ref="tableRef"
:scrollY="460"
:columns="columns"
:list-url="DetGetKendoListCust"
:search-params="{ sort: sort }"
:transform-data="customTransform"
>
<template #action="{ record }"> <template #action="{ record }">
<a @click="handleViewDetail(record)" class="text_hocer">查看详情</a> <a @click="handleViewDetail(record)" class="text_hocer">查看详情</a>
</template> </template>
@ -254,10 +297,10 @@ const fetchLyData = async () => {
}, },
formValue.dataDimensionVal != 'all' formValue.dataDimensionVal != 'all'
? { ? {
field: 'fullPath', field: 'fullPath',
operator: 'startswith', operator: 'startswith',
value: formValue.dataDimensionVal value: formValue.dataDimensionVal
} }
: null : null
].filter(Boolean) ].filter(Boolean)
}, },
@ -311,15 +354,15 @@ const fetchDmData = async () => {
{ field: 'mway', operator: 'eq', value: 2 }, { field: 'mway', operator: 'eq', value: 2 },
formValue.dataDimensionVal == 'all' formValue.dataDimensionVal == 'all'
? { ? {
field: 'rvcd', field: 'rvcd',
operator: 'contains', operator: 'contains',
value: formValue.rvcd value: formValue.rvcd
} }
: { : {
field: 'baseId', field: 'baseId',
operator: 'contains', operator: 'contains',
value: formValue.dataDimensionVal value: formValue.dataDimensionVal
} }
] ]
}, },
select: ['stcd', 'stnm', 'lgtd', 'lttd', 'rstcdStepSort'], select: ['stcd', 'stnm', 'lgtd', 'lttd', 'rstcdStepSort'],
@ -816,8 +859,12 @@ const handleViewDetail = (record: any) => {
}); });
modelStore.modalVisible = true; modelStore.modalVisible = true;
modelStore.params.sttp = 'WT'; modelStore.params.sttp = 'WT';
modelStore.title = stnm ; modelStore.title = stnm;
modelStore.params.stcd = formValue.stcd; modelStore.params.stcd = formValue.stcd;
modelStore.filter.rangeTm = [
dayjs(formValue.value.tm).startOf('month').format('YYYY-MM-DD HH:mm:ss'),
dayjs(formValue.value.tm).endOf('month').format('YYYY-MM-DD HH:mm:ss')
];
}; };
defineExpose({ defineExpose({

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -298,8 +298,12 @@ const handleExport = async () => {};
const handleViewDetail = (record: any) => { const handleViewDetail = (record: any) => {
modelStore.modalVisible = true; modelStore.modalVisible = true;
modelStore.params.sttp = 'WT'; modelStore.params.sttp = 'WT';
modelStore.title = record.stnm ; modelStore.title = record.stnm;
modelStore.params.stcd = record.stcd; modelStore.params.stcd = record.stcd;
modelStore.filter.rangeTm = [
dayjs(record.dt).startOf('month').format('YYYY-MM-DD HH:mm:ss'),
dayjs(record.dt).endOf('month').format('YYYY-MM-DD HH:mm:ss')
];
}; };
// //

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,34 +1,42 @@
<!-- SidePanelItem.vue --> <!-- SidePanelItem.vue -->
<template> <template>
<SidePanelItem title="沿程水温变化" :prompt="prompts" :moreSelect="select" :datetimePicker="datetimePicker" <SidePanelItem
@update-values="handlePanelChange1"> title="沿程水温变化"
:prompt="prompts"
:moreSelect="select"
:datetimePicker="datetimePicker"
@update-values="handlePanelChange1"
>
<a-spin :spinning="loading" tip="加载中..."> <a-spin :spinning="loading" tip="加载中...">
<div v-show="showemit && !loading" ref="chartRef" class="chart-container"></div> <div
<div v-show="!showemit && !loading" class="chart-container"> <a-empty /></div> v-show="showemit && !loading"
ref="chartRef"
class="chart-container"
></div>
<div v-show="!showemit && !loading" class="chart-container">
<a-empty />
</div>
</a-spin> </a-spin>
</SidePanelItem> </SidePanelItem>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ref, onMounted, onBeforeUnmount, watch, computed } from "vue"; import { ref, onMounted, onBeforeUnmount, watch, computed } 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';
import { getKendoListCust, wbsbGetKendoList } from "@/api/sw"; import { getKendoListCust, wbsbGetKendoList } from '@/api/sw';
import { useJidiSelectEventStore } from "@/store/modules/jidiSelectEvent"; import { useJidiSelectEventStore } from '@/store/modules/jidiSelectEvent';
import { da } from "element-plus/es/locale/index.mjs"; import { da } from 'element-plus/es/locale/index.mjs';
import { f } from "vue-router/dist/router-CWoNjPRp.mjs"; import { f } from 'vue-router/dist/router-CWoNjPRp.mjs';
import { useModelStore } from "@/store/modules/model"; import { useModelStore } from '@/store/modules/model';
const modelStore = useModelStore(); const modelStore = useModelStore();
// 便 // 便
defineOptions({ defineOptions({
name: "yanchengshuiwenChangeMod", name: 'yanchengshuiwenChangeMod'
}); });
// //
const getCurrentHourTime = () => { const getCurrentHourTime = () => {
@ -46,21 +54,21 @@ const baseid: any = ref(null);
const paramsOne = { const paramsOne = {
rvcd: '', rvcd: '',
tm: [] tm: []
} };
const dataOne = ref('') const dataOne = ref('');
// 使 computed 使 prompts dataOne.value // 使 computed 使 prompts dataOne.value
const prompts = computed(() => ({ const prompts = computed(() => ({
show: true, show: true,
value: `注:最新数据时间为${dataOne.value || getCurrentHourTime()}`, value: `注:最新数据时间为${dataOne.value || getCurrentHourTime()}`
})); }));
const showemit = ref(false) const showemit = ref(false);
const loading = ref(false) const loading = ref(false);
// const selectOptions: any = ref([]) // const selectOptions: any = ref([])
const optionsFmt = (options: any) => const optionsFmt = (options: any) =>
options.map((item: any) => ({ options.map((item: any) => ({
label: item.wbsName, label: item.wbsName,
value: item.wbsCode value: item.wbsCode
})) }));
const chartRef = ref<HTMLElement | null>(null); const chartRef = ref<HTMLElement | null>(null);
let chartInstance: ECharts | null = null; let chartInstance: ECharts | null = null;
@ -69,7 +77,9 @@ let chartInstance: ECharts | null = null;
const stationNames = ref([]); const stationNames = ref([]);
// - (°C) null使stcd // - (°C) null使stcd
const waterTemperatures = ref<{ value: number | null; stcd: string; stnm: string }[]>([]); const waterTemperatures = ref<
{ value: number | null; stcd: string; stnm: string }[]
>([]);
// 使 computed 使 currentTime dataOne.value // 使 computed 使 currentTime dataOne.value
const currentTime = computed(() => dataOne.value || getCurrentHourTime()); const currentTime = computed(() => dataOne.value || getCurrentHourTime());
@ -79,16 +89,16 @@ const select = ref({
value: '', value: '',
options: [], options: [],
picker: undefined, picker: undefined,
format: undefined, format: undefined
}); });
// //
const datetimePicker = ref({ const datetimePicker = ref({
show: true, show: true,
value: computed(() => getCurrentHourTime()), value: computed(() => getCurrentHourTime()),
format: "YYYY-MM-DD HH", format: 'YYYY-MM-DD HH',
picker: "date" as const, picker: 'date' as const,
options: [], options: []
}); });
// //
const initChart = () => { const initChart = () => {
@ -98,14 +108,14 @@ const initChart = () => {
const option = { const option = {
title: { title: {
text: "水温(°C)", text: '水温(°C)',
left: 5, left: 5,
top: 0, top: 0,
textStyle: { textStyle: {
color: "#000000", color: '#000000',
fontSize: 12, fontSize: 12,
fontWeight: "normal", fontWeight: 'normal'
}, }
}, },
dataZoom: [ dataZoom: [
{ {
@ -118,7 +128,7 @@ const initChart = () => {
} }
], ],
tooltip: { tooltip: {
trigger: "axis", trigger: 'axis',
formatter: (params: any) => { formatter: (params: any) => {
if (params && params.length > 0) { if (params && params.length > 0) {
const data = params[0]; const data = params[0];
@ -129,31 +139,31 @@ const initChart = () => {
return `${currentTime.value}<br/>${data.name}-`; return `${currentTime.value}<br/>${data.name}-`;
} }
} }
return ""; return '';
}, }
}, },
grid: { grid: {
left: "3%", left: '3%',
right: "4%", right: '4%',
bottom: "3%", bottom: '3%',
top: "15%", top: '15%',
containLabel: true, containLabel: true
}, },
xAxis: { xAxis: {
type: "category", type: 'category',
data: stationNames.value, data: stationNames.value,
boundaryGap: true, boundaryGap: true,
axisLine: { axisLine: {
show: true, show: true,
lineStyle: { lineStyle: {
color: "#8f8f8f", color: '#8f8f8f'
}, }
}, },
axisTick: { axisTick: {
show: false, show: false
}, },
axisLabel: { axisLabel: {
color: "#333", color: '#333',
fontSize: 12, fontSize: 12,
margin: 8, margin: 8,
interval: 0, interval: 0,
@ -166,76 +176,76 @@ const initChart = () => {
} else { } else {
return ` \n${value}`; // + + return ` \n${value}`; // + +
} }
}, }
}, },
splitLine: { splitLine: {
show: true, show: true,
lineStyle: { lineStyle: {
color: "#e0e0e0", color: '#e0e0e0',
type: "solid", type: 'solid'
}, }
}, }
}, },
yAxis: { yAxis: {
type: "value", type: 'value',
min: 0, min: 0,
// max: 10, // max: 10,
interval: 2, interval: 2,
axisLine: { axisLine: {
show: true, show: true,
lineStyle: { lineStyle: {
color: "#8f8f8f", color: '#8f8f8f'
}, }
}, },
axisTick: { axisTick: {
show: true, show: true,
length: 3, length: 3,
lineStyle: { lineStyle: {
color: "#8f8f8f", color: '#8f8f8f'
}, }
}, },
axisLabel: { axisLabel: {
color: "#333", color: '#333',
fontSize: 12, fontSize: 12
}, },
splitLine: { splitLine: {
show: true, show: true,
lineStyle: { lineStyle: {
color: "#e0e0e0", color: '#e0e0e0',
type: "solid", type: 'solid'
}, }
}, }
}, },
series: [ series: [
{ {
name: "水温", name: '水温',
type: "line", type: 'line',
smooth: true, smooth: true,
symbol: "circle", symbol: 'circle',
symbolSize: 6, symbolSize: 6,
connectNulls: true, // 线线 connectNulls: true, // 线线
lineStyle: { lineStyle: {
color: "#6ca4f7", color: '#6ca4f7',
width: 2, width: 2
}, },
itemStyle: { itemStyle: {
color: "#6ca4f7", color: '#6ca4f7'
}, },
areaStyle: { areaStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ {
offset: 0, offset: 0,
color: "rgba(108, 164, 247, 0.3)", color: 'rgba(108, 164, 247, 0.3)'
}, },
{ {
offset: 1, offset: 1,
color: "rgba(108, 164, 247, 0.05)", color: 'rgba(108, 164, 247, 0.05)'
}, }
]), ])
}, },
data: waterTemperatures.value, data: waterTemperatures.value
}, }
], ]
}; };
chartInstance.setOption(option); chartInstance.setOption(option);
@ -247,11 +257,12 @@ const initChart = () => {
console.log('stcd:', params.data.stcd); console.log('stcd:', params.data.stcd);
console.log('stnm:', params.data.stnm); console.log('stnm:', params.data.stnm);
console.log('temperature:', params.data.value); console.log('temperature:', params.data.value);
console.log('temperature:', paramsOne.tm);
modelStore.modalVisible = true; modelStore.modalVisible = true;
modelStore.params.sttp = "WT"; modelStore.params.sttp = 'WT';
modelStore.title = params.data.stnm; modelStore.title = params.data.stnm;
modelStore.params.stcd = params.data.stcd; modelStore.params.stcd = params.data.stcd;
modelStore.params.date = paramsOne.tm; modelStore.filter.rangeTm = [paramsOne.tm[1], paramsOne.tm[0]];
} }
}); });
}; };
@ -269,33 +280,48 @@ const init = async () => {
try { try {
const params = { const params = {
filter: { filter: {
logic: "and", logic: 'and',
filters: [ filters: [
{ field: "rvcd", operator: "eq", dataType: "string", value: paramsOne.rvcd }, {
{ field: "tm", operator: "gte", dataType: "date", value: paramsOne.tm[1] }, field: 'rvcd',
{ field: "tm", operator: "lte", dataType: "date", value: paramsOne.tm[0] }, operator: 'eq',
], dataType: 'string',
value: paramsOne.rvcd
},
{
field: 'tm',
operator: 'gte',
dataType: 'date',
value: paramsOne.tm[1]
},
{
field: 'tm',
operator: 'lte',
dataType: 'date',
value: paramsOne.tm[0]
}
]
}, },
sort: [{ field: "sort", dir: "asc" }], sort: [{ field: 'sort', dir: 'asc' }]
}; };
let res = await getKendoListCust(params); let res = await getKendoListCust(params);
let data = res.data.data || res.data let data = res.data.data || res.data;
if (data.length > 0) { if (data.length > 0) {
showemit.value = true showemit.value = true;
} else { } else {
showemit.value = false showemit.value = false;
} }
stationNames.value = data stationNames.value = data
.filter((item: any) => item.sttp == "1") .filter((item: any) => item.sttp == '1')
.map((item: any) => item.stnm); .map((item: any) => item.stnm);
console.log(stationNames.value); console.log(stationNames.value);
// stcdstnm // stcdstnm
waterTemperatures.value = data waterTemperatures.value = data
.filter((item: any) => item.sttp == "2") .filter((item: any) => item.sttp == '2')
.map((item: any) => ({ .map((item: any) => ({
value: item.temperature, value: item.temperature,
stcd: item.stcd, stcd: item.stcd,
@ -311,13 +337,13 @@ const init = async () => {
// //
chartInstance.setOption({ chartInstance.setOption({
xAxis: { xAxis: {
data: stationNames.value, data: stationNames.value
}, },
series: [ series: [
{ {
data: waterTemperatures.value, data: waterTemperatures.value
}, }
], ]
}); });
} }
// //
@ -336,11 +362,11 @@ const init = async () => {
const getSelectConfig = async () => { const getSelectConfig = async () => {
loading.value = true; loading.value = true;
try { try {
let obj: any = {} let obj: any = {};
if (baseid.value === 'all') { if (baseid.value === 'all') {
obj = { rstcdStepSort: 'asc', siteStepSort: 'asc' } obj = { rstcdStepSort: 'asc', siteStepSort: 'asc' };
} else { } else {
obj = { siteStepSort: 'asc' } obj = { siteStepSort: 'asc' };
} }
const filters = [ const filters = [
{ {
@ -348,13 +374,13 @@ const getSelectConfig = async () => {
operator: 'eq', operator: 'eq',
value: 'PSB_RVCD' value: 'PSB_RVCD'
} }
] ];
if (baseid.value && baseid.value !== 'all') { if (baseid.value && baseid.value !== 'all') {
filters.push({ filters.push({
field: 'objId', field: 'objId',
operator: 'eq', // contains operator: 'eq', // contains
value: baseid.value value: baseid.value
}) });
} }
let data: any = { let data: any = {
filter: { filter: {
@ -362,10 +388,10 @@ const getSelectConfig = async () => {
filters filters
} }
// select: ['WBS_CODE', 'WBS_NAME'] // select: ['WBS_CODE', 'WBS_NAME']
} };
if (obj) { if (obj) {
data.sort = [obj] data.sort = [obj];
} else { } else {
data = { data = {
...data, ...data,
@ -373,31 +399,42 @@ const getSelectConfig = async () => {
{ dir: 'asc', field: 'orderIndex' }, { dir: 'asc', field: 'orderIndex' },
{ dir: 'asc', field: 'wbsCode' }, { dir: 'asc', field: 'wbsCode' },
{ dir: 'asc', field: 'wbsName' }, { dir: 'asc', field: 'wbsName' },
{ dir: 'asc', field: 'objid' }, { dir: 'asc', field: 'objid' }
], ],
groupResultFlat: true groupResultFlat: true
} };
} }
let res = await wbsbGetKendoList(data) let res = await wbsbGetKendoList(data);
let dataOne = res?.data?.data || res?.data let dataOne = res?.data?.data || res?.data;
if (dataOne.length > 0) { if (dataOne.length > 0) {
let dataMapNameMap: any = {} let dataMapNameMap: any = {};
let jiDiListMap: any = {} let jiDiListMap: any = {};
JidiSelectEventStore.jidiData.forEach((item: any) => { JidiSelectEventStore.jidiData.forEach((item: any) => {
jiDiListMap[item.wbsCode] = item.wbsName jiDiListMap[item.wbsCode] = item.wbsName;
}) });
dataOne.map((item: any, index: number) => { dataOne.map((item: any, index: number) => {
const { stcd, stnm, objId } = item const { stcd, stnm, objId } = item;
if (dataMapNameMap[objId]) { if (dataMapNameMap[objId]) {
dataMapNameMap[objId].push({ ...item, baseName: jiDiListMap[objId], baseId: objId }) dataMapNameMap[objId].push({
...item,
baseName: jiDiListMap[objId],
baseId: objId
});
} else { } else {
dataMapNameMap[objId] = [{ ...item, baseName: jiDiListMap[objId], baseId: objId }] dataMapNameMap[objId] = [
{ ...item, baseName: jiDiListMap[objId], baseId: objId }
];
} }
return { label: item.wbsName, value: item.wbsCode, baseId: objId, baseName: jiDiListMap[objId] } return {
}) label: item.wbsName,
let dataMapNameArr: any = [] value: item.wbsCode,
baseId: objId,
baseName: jiDiListMap[objId]
};
});
let dataMapNameArr: any = [];
Object.keys(dataMapNameMap).forEach((item: any) => { Object.keys(dataMapNameMap).forEach((item: any) => {
dataMapNameArr.push({ dataMapNameArr.push({
@ -409,24 +446,29 @@ const getSelectConfig = async () => {
return { return {
title: item2.wbsName, title: item2.wbsName,
value: item2.wbsCode value: item2.wbsCode
} };
}) })
}) });
}) });
dataMapNameArr = dataMapNameArr.sort((a: any, b: any) => { dataMapNameArr = dataMapNameArr.sort((a: any, b: any) => {
const indexA = JidiSelectEventStore.jidiData.findIndex(item => item.wbsCode === a.value); const indexA = JidiSelectEventStore.jidiData.findIndex(
const indexB = JidiSelectEventStore.jidiData.findIndex(item => item.wbsCode === b.value); item => item.wbsCode === a.value
);
const indexB = JidiSelectEventStore.jidiData.findIndex(
item => item.wbsCode === b.value
);
return indexA - indexB; return indexA - indexB;
}) });
select.value.options = filterSelectOptions(dataMapNameArr) select.value.options = filterSelectOptions(dataMapNameArr);
if (baseid.value === 'all') { if (baseid.value === 'all') {
select.value.value = 'SJLY1U' select.value.value = 'SJLY1U';
} else if (filterSelectOptions(dataMapNameArr)[0]?.children) { } else if (filterSelectOptions(dataMapNameArr)[0]?.children) {
select.value.value = filterSelectOptions(dataMapNameArr)[0]?.children[0]?.value select.value.value =
filterSelectOptions(dataMapNameArr)[0]?.children[0]?.value;
} else if (filterSelectOptions(dataMapNameArr)[0]?.value) { } else if (filterSelectOptions(dataMapNameArr)[0]?.value) {
select.value.value = filterSelectOptions(dataMapNameArr)[0]?.value select.value.value = filterSelectOptions(dataMapNameArr)[0]?.value;
} else { } else {
select.value.value = '' select.value.value = '';
} }
} }
} catch (error) { } catch (error) {
@ -434,31 +476,39 @@ const getSelectConfig = async () => {
} finally { } finally {
loading.value = false; loading.value = false;
} }
} };
const filterSelectOptions = (selectOptions: any) => { const filterSelectOptions = (selectOptions: any) => {
if (selectOptions?.length === 1 && selectOptions?.[0]?.children?.length === 1) { if (
return [{ ...selectOptions?.[0]?.children?.[0], isLeaf: true }] selectOptions?.length === 1 &&
selectOptions?.[0]?.children?.length === 1
) {
return [{ ...selectOptions?.[0]?.children?.[0], isLeaf: true }];
} else if (selectOptions.length) { } else if (selectOptions.length) {
return selectOptions.map((e) => { return selectOptions.map(e => {
if (e?.children?.length > 1) { if (e?.children?.length > 1) {
return { ...e, isLeaf: false, selectable: true, children: e.children.map((item) => ({ ...item, isLeaf: true })) } return {
...e,
isLeaf: false,
selectable: true,
children: e.children.map(item => ({ ...item, isLeaf: true }))
};
} else { } else {
return { ...e.children[0], isLeaf: true } return { ...e.children[0], isLeaf: true };
} }
}) });
} }
return [] return [];
} };
// //
const handlePanelChange1 = (data) => { const handlePanelChange1 = data => {
console.log('当前所有控件状态:', data); console.log('当前所有控件状态:', data);
if (data.moreSelect) { if (data.moreSelect) {
paramsOne.rvcd = data.moreSelect paramsOne.rvcd = data.moreSelect;
} }
if (data.datetime) { if (data.datetime) {
dataOne.value = data.datetime dataOne.value = data.datetime;
const datetime = data.datetime; const datetime = data.datetime;
// //
@ -466,7 +516,14 @@ const handlePanelChange1 = (data) => {
const [year, month, day] = datePart.split('-').map(Number); const [year, month, day] = datePart.split('-').map(Number);
// Date // Date
const currentDate = new Date(year, month - 1, day, parseInt(hourPart), 0, 0); const currentDate = new Date(
year,
month - 1,
day,
parseInt(hourPart),
0,
0
);
// Date // Date
const previousDate = new Date(year, month - 2, day, 0, 0, 0); const previousDate = new Date(year, month - 2, day, 0, 0, 0);
@ -484,23 +541,22 @@ const handlePanelChange1 = (data) => {
// //
const result = [ const result = [
formatDate(currentDate), // "2026-05-15 15:00:00" formatDate(currentDate), // "2026-05-15 15:00:00"
formatDate(previousDate) // "2026-04-15 00:00:00" formatDate(previousDate) // "2026-04-15 00:00:00"
]; ];
paramsOne.tm = result paramsOne.tm = result;
console.log(paramsOne.tm, 'paramsOne.tm') console.log(paramsOne.tm, 'paramsOne.tm');
} }
if (paramsOne.rvcd || paramsOne.tm.length > 0) { if (paramsOne.rvcd || paramsOne.tm.length > 0) {
init(); init();
} }
};
}
watch( watch(
() => JidiSelectEventStore.selectedItem, () => JidiSelectEventStore.selectedItem,
(newVal) => { newVal => {
baseid.value = newVal.wbsCode; baseid.value = newVal.wbsCode;
getSelectConfig() getSelectConfig();
}, },
{ deep: true, immediate: true } { deep: true, immediate: true }
); );
@ -508,12 +564,12 @@ watch(
onMounted(() => { onMounted(() => {
// DOM // DOM
init(); init();
window.addEventListener("resize", handleResize); window.addEventListener('resize', handleResize);
}); });
// //
onBeforeUnmount(() => { onBeforeUnmount(() => {
window.removeEventListener("resize", handleResize); window.removeEventListener('resize', handleResize);
chartInstance?.dispose(); chartInstance?.dispose();
}); });
</script> </script>