修改水温垂向右侧点击,添加录像视频和建设状态,bug修改
This commit is contained in:
parent
f1b8ffebf1
commit
ce8855d3c2
@ -91,6 +91,24 @@ export function getVideoList(data: any) {
|
||||
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) {
|
||||
return request({
|
||||
|
||||
@ -74,11 +74,6 @@ const baseColumns = [
|
||||
dataIndex: 'rowId',
|
||||
width: 60
|
||||
},
|
||||
{
|
||||
title: '告警设施',
|
||||
dataIndex: 'stnm',
|
||||
width: 220
|
||||
},
|
||||
{
|
||||
title: '告警时间',
|
||||
dataIndex: 'tms',
|
||||
@ -91,12 +86,7 @@ const baseColumns = [
|
||||
width: 120
|
||||
},
|
||||
{
|
||||
title: '监测要素',
|
||||
dataIndex: 'ysName',
|
||||
width: 120
|
||||
},
|
||||
{
|
||||
title: '判断标准',
|
||||
title: '限值(m³/s)',
|
||||
dataIndex: 'bzz',
|
||||
width: 120
|
||||
},
|
||||
|
||||
@ -21,6 +21,11 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="filter-row">
|
||||
<a-select
|
||||
v-model:value="timeType"
|
||||
style="margin-right: 12px"
|
||||
:options="timeTypeOptions"
|
||||
/>
|
||||
<a-range-picker
|
||||
v-model:value="dateRange"
|
||||
:format="dateFormat"
|
||||
|
||||
@ -158,12 +158,12 @@ const columnsMap: Record<string, any[]> = {
|
||||
},
|
||||
{
|
||||
title: '性腺发育期',
|
||||
dataIndex: 'fwgh',
|
||||
dataIndex: 'st',
|
||||
width: 140
|
||||
},
|
||||
{
|
||||
title: '早期资源量和种类',
|
||||
dataIndex: 'fwgh',
|
||||
dataIndex: 'ps',
|
||||
width: 220
|
||||
},
|
||||
{
|
||||
@ -203,7 +203,7 @@ const columnsMap: Record<string, any[]> = {
|
||||
},
|
||||
{
|
||||
title: '电导率(μS/cm)',
|
||||
dataIndex: 'tm',
|
||||
dataIndex: 'cond',
|
||||
width: 180
|
||||
}
|
||||
],
|
||||
|
||||
@ -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>
|
||||
@ -472,6 +472,13 @@ const handleSearch = () => fetchData();
|
||||
|
||||
const initData = () => {
|
||||
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());
|
||||
fetchData();
|
||||
hasLoaded.value = true;
|
||||
|
||||
396
frontend/src/components/MapModal/components/NoLiveVideo.vue
Normal file
396
frontend/src/components/MapModal/components/NoLiveVideo.vue
Normal 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>
|
||||
@ -103,13 +103,13 @@ const tableColumns = ref([
|
||||
String(record.tecnt).trim() !== ''
|
||||
? record.tecnt
|
||||
: '-';
|
||||
const transplant =
|
||||
record?.transplant !== undefined &&
|
||||
record?.transplant !== null &&
|
||||
String(record.transplant).trim() !== ''
|
||||
? record.transplant
|
||||
const plantask =
|
||||
record?.plantask !== undefined &&
|
||||
record?.plantask !== null &&
|
||||
String(record.plplantask).trim() !== ''
|
||||
? record.plplantask
|
||||
: '-';
|
||||
return `${tecnt}/${transplant}`;
|
||||
return `${tecnt}/${plantask}`;
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -123,13 +123,13 @@ const tableColumns = ref([
|
||||
String(record.surnum).trim() !== ''
|
||||
? record.surnum
|
||||
: '-';
|
||||
const plantask =
|
||||
record?.plantask !== undefined &&
|
||||
record?.plantask !== null &&
|
||||
String(record.plantask).trim() !== ''
|
||||
? record.plantask
|
||||
const transplant =
|
||||
record?.transplant !== undefined &&
|
||||
record?.transplant !== null &&
|
||||
String(record.transplant).trim() !== ''
|
||||
? record.transplant
|
||||
: '-';
|
||||
return `${surnum}/${plantask}`;
|
||||
return `${surnum}/${transplant}`;
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@ -479,6 +479,12 @@ const handleSearch = () => fetchData();
|
||||
const initData = () => {
|
||||
if (hasLoaded.value) return;
|
||||
nextTick(() => {
|
||||
if (modelStore.filter.rangeTm.length > 0) {
|
||||
dateRange.value = [
|
||||
dayjs(modelStore.filter.rangeTm[0]),
|
||||
dayjs(modelStore.filter.rangeTm[1])
|
||||
];
|
||||
}
|
||||
if (chartRef.value && !chartInstance)
|
||||
chartInstance = echarts.init(chartRef.value);
|
||||
});
|
||||
|
||||
@ -903,7 +903,15 @@ const handleExport = () => {
|
||||
|
||||
const initData = () => {
|
||||
if (hasLoaded.value) return;
|
||||
console.log('modelStore.filter.rangeTm:', modelStore.filter.rangeTm);
|
||||
|
||||
nextTick(() => {
|
||||
if (modelStore.filter.rangeTm.length > 0) {
|
||||
dateRange.value = [
|
||||
dayjs(modelStore.filter.rangeTm[0]),
|
||||
dayjs(modelStore.filter.rangeTm[1])
|
||||
];
|
||||
}
|
||||
if (chartRef.value && !chartInstance)
|
||||
chartInstance = echarts.init(chartRef.value);
|
||||
});
|
||||
|
||||
@ -37,7 +37,12 @@
|
||||
<!-- 同期对比列的自定义渲染 -->
|
||||
<template #contrast="{ record }">
|
||||
<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="{
|
||||
color:
|
||||
Number(record.wt) - Number(record.beforeWt) > 0
|
||||
@ -664,6 +669,12 @@ const initData = () => {
|
||||
setTimeout(() => {
|
||||
initChart();
|
||||
}, 200);
|
||||
if (modelStore.filter.rangeTm.length > 0) {
|
||||
rangePicker.value = [
|
||||
dayjs(modelStore.filter.rangeTm[0]),
|
||||
dayjs(modelStore.filter.rangeTm[1])
|
||||
];
|
||||
}
|
||||
hasLoaded.value = true; // 标记为已请求过
|
||||
};
|
||||
|
||||
|
||||
@ -10,8 +10,8 @@
|
||||
class="map-modal"
|
||||
>
|
||||
<!-- 问题:
|
||||
1.基础信息图片展示是什么逻辑
|
||||
2. 电站专题展示逻辑
|
||||
1.基础信息图片展示是什么逻辑✅️
|
||||
2. 电站专题展示逻辑✅️
|
||||
3. 实时视频回放 少接口✅️
|
||||
4. 预警提示 少接口
|
||||
5. 生态流量 达标率查询不对(✅️)
|
||||
@ -20,13 +20,13 @@
|
||||
8. 栖息地-流量监测 没有水位视频和流量视频 字段没数据
|
||||
9. 栖息地 水温、水质、流量没有数据没法测
|
||||
10.鱼类增殖站 - 运行数据 要添加字典
|
||||
11.珍惜植物园 - 种植要求字段不知道
|
||||
12.水生调查断面 - 监测数据 不知道电导率 ,性腺发育期,早期资源量和种类
|
||||
13.水电告警情况 生态流量 不知道字段 接口传参有问题
|
||||
11.珍惜植物园 - 种植要求字段不知道✅️
|
||||
12.水生调查断面 - 监测数据 不知道电导率 ,性腺发育期,早期资源量和种类 ✅️
|
||||
13.水电告警情况 生态流量 不知道字段 ✅️ 接口传参有问题
|
||||
14. 地图抽吸 碰撞检测 放大到具体层级锚点抽吸了但是popup弹框还在显示;
|
||||
15.运行情况 计划开始运行时间 接口没接
|
||||
|
||||
|
||||
|
||||
没弄: 环保设施
|
||||
-->
|
||||
<div v-if="modelStore.showStcdSelector" class="stcd-selector-wrapper">
|
||||
<a-select
|
||||
@ -88,18 +88,23 @@
|
||||
v-show="currentActiveKey === 'videoInfo'"
|
||||
:is-active="currentActiveKey === 'videoInfo'"
|
||||
/>
|
||||
<!-- 录像视频 -->
|
||||
<NoLiveVideo
|
||||
v-show="currentActiveKey === 'noLiveVideo'"
|
||||
:is-active="currentActiveKey === 'noLiveVideo'"
|
||||
/>
|
||||
<!-- 全景影像 -->
|
||||
<PanoramaInfo
|
||||
v-show="currentActiveKey === 'panoramaInfo'"
|
||||
:url="getTabUrl('panoramaInfo')"
|
||||
:is-active="currentActiveKey === 'panoramaInfo'"
|
||||
/>
|
||||
<!-- 监测数据 - 电站运行过程线 -->
|
||||
<!-- 监测数据 - 电站运行过程线 ✅️-->
|
||||
<MonitorInfo
|
||||
v-show="currentActiveKey === 'monitorInfo'"
|
||||
:is-active="currentActiveKey === 'monitorInfo'"
|
||||
/>
|
||||
<!-- 监测数据 - 水温 -->
|
||||
<!-- 监测数据 - 水温 ✅️-->
|
||||
<WaterTemperature
|
||||
v-show="currentActiveKey === 'WaterTemperature'"
|
||||
:is-active="currentActiveKey === 'WaterTemperature'"
|
||||
@ -111,7 +116,7 @@
|
||||
"
|
||||
:wt-config="modelStore.wtConfig"
|
||||
/>
|
||||
<!-- 监测数据 - 垂向水温 -->
|
||||
<!-- 监测数据 - 垂向水温 ✅️ -->
|
||||
<VerticalWaterTemperature
|
||||
v-show="currentActiveKey === 'VerticalWaterTemperature'"
|
||||
:is-active="currentActiveKey === 'VerticalWaterTemperature'"
|
||||
@ -167,6 +172,12 @@
|
||||
v-show="currentActiveKey === 'FishZHFX'"
|
||||
:is-active="currentActiveKey === 'FishZHFX'"
|
||||
/>
|
||||
<!-- 建设情况 -->
|
||||
<FishFacilityBuildState
|
||||
v-show="currentActiveKey === 'FishFacilityBuildState'"
|
||||
:is-active="currentActiveKey === 'FishFacilityBuildState'"
|
||||
:url="getTabUrl('FishFacilityBuildState')"
|
||||
/>
|
||||
<!-- AI运行识别 -->
|
||||
<AIYXSB
|
||||
v-show="currentActiveKey === 'AIYXSB'"
|
||||
@ -178,12 +189,12 @@
|
||||
:tabs-items="getTabChildren('tableTabs')"
|
||||
:is-active="currentActiveKey === 'tableTabs'"
|
||||
/>
|
||||
<!-- 生态流量 -->
|
||||
<!-- 生态流量 ✅️-->
|
||||
<EcologicalFlow
|
||||
v-show="currentActiveKey === 'EcologicalFlow'"
|
||||
:is-active="currentActiveKey === 'EcologicalFlow'"
|
||||
/>
|
||||
<!-- 鱼类繁殖适宜性分析 -->
|
||||
<!-- 鱼类繁殖适宜性分析 ✅️-->
|
||||
<WaterTemperatureRep
|
||||
v-show="currentActiveKey === 'WaterTemperatureRep'"
|
||||
:is-active="currentActiveKey === 'WaterTemperatureRep'"
|
||||
@ -259,6 +270,7 @@ import { ref, watch, reactive, computed } from 'vue';
|
||||
// 导入预定义的 Tab 内容组件
|
||||
import BasicInfo from './components/BasicInfo.vue'; // 基本信息
|
||||
import VideoInfo from './components/videoInfo.vue'; // 实时视频
|
||||
import NoLiveVideo from './components/NoLiveVideo.vue'; // 录像视频
|
||||
import PanoramaInfo from './components/PanoramaInfo.vue'; // 全景影像
|
||||
import MonitorInfo from './components/MonitorInfo.vue'; // 电站运行过程线
|
||||
import WaterTemperature from './components/WaterTemperature.vue'; // 水温
|
||||
@ -270,6 +282,7 @@ import FlowMeasure from './components/FlowMeasure.vue'; // 流量监测
|
||||
import FishFacilityMonitorData from './components/FishFacilityMonitorData.vue'; // 在线监测数据
|
||||
import FishFacilityRunState from './components/FishFacilityRunState.vue'; // 运行情况
|
||||
import FlowDischarge from './components/FlowDischarge.vue'; // 生态流量泄放设施 - 监测数据
|
||||
import FishFacilityBuildState from './components/FishFacilityBuildState.vue'; // 建设情况
|
||||
// 鱼类增殖站
|
||||
import NormalOperationData from './components/NormalOperationData/index.vue'; // 运行数据
|
||||
import ProcessDiagram from './components/ProcessDiagram.vue'; // 过程图
|
||||
@ -999,6 +1012,12 @@ const handleClose = () => {
|
||||
modelStore.verticalWaterStcd = '';
|
||||
modelStore.aiSites = [];
|
||||
modelStore.wtConfig = {};
|
||||
modelStore.filter = {
|
||||
stllgzlx: '', // 生态流量 - 规则类型
|
||||
stllTmType: '', // 生态流量 - 时间类型
|
||||
tm: '',
|
||||
rangeTm: []
|
||||
};
|
||||
modelStore.wqShow = false;
|
||||
modelStore.fishTabInfo = null;
|
||||
};
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -33,6 +33,7 @@ export class MapClass implements MapClassInterface {
|
||||
this.service = new MapOl();
|
||||
}
|
||||
static getInstance(): MapClass {
|
||||
console.log('getInstance');
|
||||
if (!this.instance) {
|
||||
this.instance = new MapClass();
|
||||
}
|
||||
|
||||
@ -72,6 +72,7 @@ export class MapOl implements MapInterface {
|
||||
private hoveredFeatureId: string | number | null = null;
|
||||
private popupManager: PopupManager;
|
||||
private regionMaskManager: RegionMaskManager;
|
||||
private isBatchPopupMode = false;
|
||||
constructor() {
|
||||
this.pointLayerManager = new PointLayerManager({
|
||||
map: null,
|
||||
@ -142,7 +143,11 @@ export class MapOl implements MapInterface {
|
||||
this.popupManager.setMap(this.map);
|
||||
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.popupManager.showPopup(undefined, undefined);
|
||||
this.popupManager.handleMapClick(evt.pixel, detectedFeature => {
|
||||
@ -169,9 +174,24 @@ export class MapOl implements MapInterface {
|
||||
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);
|
||||
} catch (e) {
|
||||
console.error('OL Init Error', e);
|
||||
@ -220,6 +240,52 @@ export class MapOl implements MapInterface {
|
||||
showPopup(feature: Feature | undefined, coordinate: number[] | undefined) {
|
||||
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 效果,支持随缩放动态调整大小)
|
||||
*/
|
||||
@ -660,6 +726,10 @@ export class MapOl implements MapInterface {
|
||||
const layerInstance = this.layerRegistry.get(registryKey as string);
|
||||
if (layerInstance) {
|
||||
layerInstance.setVisible(checked);
|
||||
// 图层显隐变化时刷新 batch popup
|
||||
if (this.isBatchPopupMode) {
|
||||
this.updateBatchPopups();
|
||||
}
|
||||
} else {
|
||||
console.warn(
|
||||
`未找到标识为 [${registryKey}] 的图层实例,当前注册表 keys:`,
|
||||
@ -669,6 +739,10 @@ export class MapOl implements MapInterface {
|
||||
}
|
||||
mdLayerTreeShowOrHidden(layerType: string, checked?: boolean): void {
|
||||
this.pointLayerManager.setLayerVisible(layerType, checked);
|
||||
// 图层显隐变化时刷新 batch popup
|
||||
if (this.isBatchPopupMode) {
|
||||
this.updateBatchPopups();
|
||||
}
|
||||
}
|
||||
|
||||
hasLayer(layerType: string): void {
|
||||
@ -1212,7 +1286,6 @@ export class MapOl implements MapInterface {
|
||||
// 使用 Object.assign 批量设置样式,代码更整洁
|
||||
Object.assign(container.style, {
|
||||
position: 'absolute',
|
||||
backgroundColor: 'rgba(255, 255, 255, 0.9)',
|
||||
border: '1px solid #000',
|
||||
padding: '4px 8px',
|
||||
borderRadius: '4px',
|
||||
@ -1223,7 +1296,6 @@ export class MapOl implements MapInterface {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
zIndex: '1001',
|
||||
boxShadow: '0 2px 4px rgba(0,0,0,0.2)',
|
||||
pointerEvents: 'auto' // 确保可以点击
|
||||
});
|
||||
|
||||
@ -1309,6 +1381,10 @@ export class MapOl implements MapInterface {
|
||||
key || '',
|
||||
!!checked
|
||||
);
|
||||
// 图例变化时刷新 batch popup
|
||||
if (this.isBatchPopupMode) {
|
||||
this.updateBatchPopups();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1327,6 +1403,10 @@ export class MapOl implements MapInterface {
|
||||
anchoPointState,
|
||||
checked
|
||||
);
|
||||
// 图例变化时刷新 batch popup
|
||||
if (this.isBatchPopupMode) {
|
||||
this.updateBatchPopups();
|
||||
}
|
||||
}
|
||||
|
||||
switchView(_type): void {}
|
||||
|
||||
@ -32,6 +32,34 @@ export class PointLayerManager {
|
||||
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() {
|
||||
return this.layerRegistry;
|
||||
@ -128,6 +156,7 @@ export class PointLayerManager {
|
||||
const vectorLayer = this.layerRegistry.get(layerKey);
|
||||
if (vectorLayer && vectorLayer.getVisible() !== visible) {
|
||||
vectorLayer.setVisible(visible);
|
||||
vectorLayer.changed();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -204,6 +204,213 @@ export class PopupManager {
|
||||
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 状态,供地图销毁和切换时复用。
|
||||
reset() {
|
||||
if (this.animationFrameId) {
|
||||
@ -264,6 +471,7 @@ export class PopupManager {
|
||||
|
||||
// 备注:销毁 Popup 管理器内部状态和 Overlay 引用。
|
||||
destroy() {
|
||||
this.clearBatchPopups();
|
||||
this.reset();
|
||||
if (this.popupElement && this.popupMouseEnterHandler) {
|
||||
this.popupElement.removeEventListener(
|
||||
|
||||
@ -1,11 +1,19 @@
|
||||
<!-- 垂向水温变化图 -->
|
||||
<template>
|
||||
<SidePanelItem title="垂向水温变化" :moreSelect="select" :datetimePicker="datetimePicker"
|
||||
@update-values="handlePanelChange1">
|
||||
<SidePanelItem
|
||||
title="垂向水温变化"
|
||||
:moreSelect="select"
|
||||
:datetimePicker="datetimePicker"
|
||||
@update-values="handlePanelChange1"
|
||||
>
|
||||
<div class="chart-wrapper">
|
||||
<!-- 图表容器 -->
|
||||
<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">
|
||||
<a-empty />
|
||||
</div>
|
||||
@ -15,18 +23,29 @@
|
||||
</template>
|
||||
|
||||
<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 type { ECharts } from 'echarts';
|
||||
import SidePanelItem from '@/components/SidePanelItem/index.vue';
|
||||
import { useJidiSelectEventStore } from "@/store/modules/jidiSelectEvent";
|
||||
import { getChuiXiangShuiWenTreeStcd, getCxswList } from "@/api/sw";
|
||||
import { useModelStore } from "@/store/modules/model";
|
||||
import { useJidiSelectEventStore } from '@/store/modules/jidiSelectEvent';
|
||||
import { getChuiXiangShuiWenTreeStcd, getCxswList } from '@/api/sw';
|
||||
import { useModelStore } from '@/store/modules/model';
|
||||
import { useMapModalStore } from '@/store/modules/mapModal';
|
||||
import { MapClass } from '@/components/gis/map.class';
|
||||
|
||||
const modelStore = useModelStore();
|
||||
const JidiSelectEventStore = useJidiSelectEventStore();
|
||||
const baseid = ref('');
|
||||
|
||||
// 选择的时间
|
||||
const year = ref('');
|
||||
// 定义组件名
|
||||
defineOptions({
|
||||
name: 'chuixiangshuiwenChangeMod'
|
||||
@ -140,7 +159,7 @@ function transformAggregatesToChartData(
|
||||
if (!monthData || monthData.length === 0) {
|
||||
result.push({
|
||||
name: `${month}月`,
|
||||
dataXy: [] // 空数组,ECharts 会显示为断开
|
||||
dataXy: [] // 空数组,ECharts 会显示为断开
|
||||
});
|
||||
continue;
|
||||
}
|
||||
@ -152,11 +171,11 @@ function transformAggregatesToChartData(
|
||||
stnm: string;
|
||||
}> = monthData.map((item: any) => ({
|
||||
value: [
|
||||
parseFloat(item.vwt), // 温度
|
||||
parseInt(item.wthg) // 深度/高程
|
||||
] as [number, number], // ✅ 显式声明为元组类型
|
||||
stcd: stcd, // 站点编码
|
||||
stnm: stnm // 站点名称
|
||||
parseFloat(item.vwt), // 温度
|
||||
parseInt(item.wthg) // 深度/高程
|
||||
] as [number, number], // ✅ 显式声明为元组类型
|
||||
stcd: stcd, // 站点编码
|
||||
stnm: stnm // 站点名称
|
||||
}));
|
||||
|
||||
result.push({
|
||||
@ -172,16 +191,25 @@ function transformAggregatesToChartData(
|
||||
* 根据当前年月生成颜色和图例显隐状态
|
||||
*/
|
||||
function generateColorsAndVisibility(
|
||||
dataLength: number, // ⚠️ 现在固定传入 12
|
||||
dataLength: number, // ⚠️ 现在固定传入 12
|
||||
sDate: string | number
|
||||
): {
|
||||
colors: string[];
|
||||
selected: Record<string, boolean>;
|
||||
} {
|
||||
const baseColors = [
|
||||
'#6ca4f7', '#78c300', '#9556a4', '#df91ab',
|
||||
'#7399c6', '#dbb629', '#56c2e3', '#f56a06',
|
||||
'#cdba75', '#76523b', '#df75b6', '#00a050'
|
||||
'#6ca4f7',
|
||||
'#78c300',
|
||||
'#9556a4',
|
||||
'#df91ab',
|
||||
'#7399c6',
|
||||
'#dbb629',
|
||||
'#56c2e3',
|
||||
'#f56a06',
|
||||
'#cdba75',
|
||||
'#76523b',
|
||||
'#df75b6',
|
||||
'#00a050'
|
||||
];
|
||||
|
||||
const currentYear = new Date().getFullYear();
|
||||
@ -233,7 +261,7 @@ const getCurrentHourTimeTwo = () => {
|
||||
const startTime = new Date(endTime);
|
||||
startTime.setMonth(startTime.getMonth() - 1);
|
||||
|
||||
const formatDateTime = (date) => {
|
||||
const formatDateTime = date => {
|
||||
const year = date.getFullYear();
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0');
|
||||
const day = String(date.getDate()).padStart(2, '0');
|
||||
@ -253,15 +281,15 @@ const select = ref({
|
||||
value: '',
|
||||
options: [],
|
||||
picker: undefined,
|
||||
format: undefined,
|
||||
format: undefined
|
||||
});
|
||||
|
||||
const datetimePicker = ref({
|
||||
show: true,
|
||||
value: computed(() => getCurrentHourTime()),
|
||||
format: "YYYY",
|
||||
picker: "year" as const,
|
||||
options: [],
|
||||
format: 'YYYY',
|
||||
picker: 'year' as const,
|
||||
options: []
|
||||
});
|
||||
|
||||
/**
|
||||
@ -273,58 +301,61 @@ const selectOptions = async () => {
|
||||
try {
|
||||
let filters = [
|
||||
{
|
||||
"field": "mway",
|
||||
"operator": "eq",
|
||||
"dataType": "string",
|
||||
"value": 2
|
||||
field: 'mway',
|
||||
operator: 'eq',
|
||||
dataType: 'string',
|
||||
value: 2
|
||||
},
|
||||
{
|
||||
"field": "tm",
|
||||
"operator": "gte",
|
||||
"dataType": "date",
|
||||
"value": getCurrentHourTimeTwo()[0]
|
||||
field: 'tm',
|
||||
operator: 'gte',
|
||||
dataType: 'date',
|
||||
value: getCurrentHourTimeTwo()[0]
|
||||
},
|
||||
{
|
||||
"field": "tm",
|
||||
"operator": "lte",
|
||||
"dataType": "date",
|
||||
"value": getCurrentHourTimeTwo()[1]
|
||||
field: 'tm',
|
||||
operator: 'lte',
|
||||
dataType: 'date',
|
||||
value: getCurrentHourTimeTwo()[1]
|
||||
}
|
||||
];
|
||||
|
||||
if (baseid.value && baseid.value !== 'all') {
|
||||
filters.push({
|
||||
"field": "baseId",
|
||||
"operator": "eq",
|
||||
"dataType": "string",
|
||||
"value": baseid.value
|
||||
field: 'baseId',
|
||||
operator: 'eq',
|
||||
dataType: 'string',
|
||||
value: baseid.value
|
||||
});
|
||||
}
|
||||
|
||||
let filter = {
|
||||
"logic": "and",
|
||||
"filters": filters
|
||||
logic: 'and',
|
||||
filters: filters
|
||||
};
|
||||
|
||||
let sort = baseid.value && baseid.value !== 'all' ? [
|
||||
{
|
||||
"field": "rstcdStepSort",
|
||||
"dir": "asc"
|
||||
}
|
||||
] : [
|
||||
{
|
||||
"field": "baseId",
|
||||
"dir": "asc"
|
||||
},
|
||||
{
|
||||
"field": "rstcdStepSort",
|
||||
"dir": "asc"
|
||||
}
|
||||
];
|
||||
let sort =
|
||||
baseid.value && baseid.value !== 'all'
|
||||
? [
|
||||
{
|
||||
field: 'rstcdStepSort',
|
||||
dir: 'asc'
|
||||
}
|
||||
]
|
||||
: [
|
||||
{
|
||||
field: 'baseId',
|
||||
dir: 'asc'
|
||||
},
|
||||
{
|
||||
field: 'rstcdStepSort',
|
||||
dir: 'asc'
|
||||
}
|
||||
];
|
||||
|
||||
const data = { filter, sort };
|
||||
const res = await getChuiXiangShuiWenTreeStcd(data);
|
||||
let dataOne = res?.data?.data || res.data
|
||||
let dataOne = res?.data?.data || res.data;
|
||||
|
||||
if (dataOne.length > 0) {
|
||||
const treeData = dataOne.map(item => {
|
||||
@ -334,27 +365,29 @@ const selectOptions = async () => {
|
||||
selectable: false,
|
||||
children: (item.stcdVo || []).map(child => ({
|
||||
title: child.stnm,
|
||||
value: child.stcd
|
||||
value: child.stcd,
|
||||
lgtd: child.lgtd,
|
||||
lttd: child.lttd
|
||||
}))
|
||||
};
|
||||
});
|
||||
if (baseid.value == 'all') {
|
||||
select.value.value = '008640203800001014'
|
||||
select.value.value = '008640203800001014';
|
||||
select.value.options = treeData;
|
||||
} else {
|
||||
select.value.value = treeData[0].children[0].value
|
||||
select.value.value = treeData[0].children[0].value;
|
||||
select.value.options = treeData;
|
||||
}
|
||||
} else {
|
||||
select.value.options = []
|
||||
select.value.value = ''
|
||||
select.value.options = [];
|
||||
select.value.value = '';
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取站点数据失败:', error);
|
||||
} finally {
|
||||
loading.value = false; // 关闭loading
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// ==================== 新增图表相关状态 ====================
|
||||
|
||||
@ -362,14 +395,16 @@ const chartRef = ref<HTMLElement | null>(null);
|
||||
let chartInstance: ECharts | null = null;
|
||||
|
||||
// 图表数据(对象型格式,包含站点元信息)
|
||||
const chartData = ref<Array<{
|
||||
name: string;
|
||||
dataXy: Array<{
|
||||
value: [number, number];
|
||||
stcd: string;
|
||||
stnm: string;
|
||||
}>;
|
||||
}>>([]);
|
||||
const chartData = ref<
|
||||
Array<{
|
||||
name: string;
|
||||
dataXy: Array<{
|
||||
value: [number, number];
|
||||
stcd: string;
|
||||
stnm: string;
|
||||
}>;
|
||||
}>
|
||||
>([]);
|
||||
|
||||
// Y轴类型(1=水深,2=高程)
|
||||
const type = ref(1);
|
||||
@ -390,26 +425,26 @@ const fetchChartData = async (stcd: string, year: string | number) => {
|
||||
try {
|
||||
const params = {
|
||||
filter: {
|
||||
logic: "and",
|
||||
logic: 'and',
|
||||
filters: [
|
||||
{
|
||||
field: "year",
|
||||
operator: "eq",
|
||||
dataType: "string",
|
||||
field: 'year',
|
||||
operator: 'eq',
|
||||
dataType: 'string',
|
||||
value: String(year)
|
||||
},
|
||||
{
|
||||
field: "stcd",
|
||||
operator: "eq",
|
||||
dataType: "string",
|
||||
field: 'stcd',
|
||||
operator: 'eq',
|
||||
dataType: 'string',
|
||||
value: stcd
|
||||
}
|
||||
]
|
||||
},
|
||||
sort: [
|
||||
{
|
||||
field: "depth",
|
||||
dir: "asc"
|
||||
field: 'depth',
|
||||
dir: 'asc'
|
||||
}
|
||||
]
|
||||
};
|
||||
@ -429,18 +464,26 @@ const fetchChartData = async (stcd: string, year: string | number) => {
|
||||
});
|
||||
|
||||
// ✅ 转换数据,传递 stcd 和 stnm
|
||||
chartData.value = transformAggregatesToChartData(aggregates, stcd, currentStnm);
|
||||
chartData.value = transformAggregatesToChartData(
|
||||
aggregates,
|
||||
stcd,
|
||||
currentStnm
|
||||
);
|
||||
|
||||
// ✅ 修改:传入固定值 12,而非 chartData.value.length
|
||||
const { colors: newColors, selected: newSelected } = generateColorsAndVisibility(
|
||||
12, // ⚠️ 固定传入 12
|
||||
year
|
||||
);
|
||||
const { colors: newColors, selected: newSelected } =
|
||||
generateColorsAndVisibility(
|
||||
12, // ⚠️ 固定传入 12
|
||||
year
|
||||
);
|
||||
colors.value = newColors;
|
||||
selected.value = newSelected;
|
||||
|
||||
// 根据数据长度决定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; // 有数据则显示图表
|
||||
} else {
|
||||
showemit.value = false; // 无数据显示空状态
|
||||
@ -490,12 +533,26 @@ const initChart = () => {
|
||||
console.log('站点名称:', stnm);
|
||||
console.log('水温:', temperature);
|
||||
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.params.sttp = "WT";
|
||||
modelStore.params.sttp = 'WT';
|
||||
modelStore.params.enfc = '1';
|
||||
modelStore.title = stnm ;
|
||||
modelStore.title = stnm;
|
||||
// modelStore.isBasicEdit = true;
|
||||
modelStore.params.stcd = stcd;
|
||||
modelStore.filter.rangeTm = [startTime, endTime];
|
||||
|
||||
// TODO: 在此处添加您的业务逻辑
|
||||
});
|
||||
@ -511,7 +568,7 @@ const updateChart = () => {
|
||||
const yData: any[] = [];
|
||||
const legend: string[] = [];
|
||||
|
||||
chartData.value.forEach((item) => {
|
||||
chartData.value.forEach(item => {
|
||||
if (item.dataXy.length > 0) {
|
||||
// ✅ 从对象中提取 value[0](温度值)
|
||||
xData.push(item.dataXy[0].value[0]);
|
||||
@ -521,7 +578,7 @@ const updateChart = () => {
|
||||
|
||||
yData.push({
|
||||
name: item.name,
|
||||
data: item.dataXy, // ✅ 直接使用对象数组
|
||||
data: item.dataXy, // ✅ 直接使用对象数组
|
||||
type: 'line',
|
||||
smooth: true,
|
||||
connectNulls: true,
|
||||
@ -593,7 +650,7 @@ const updateChart = () => {
|
||||
type: 'value' as const,
|
||||
name: type.value === 2 ? '高程(m)' : '水深(m)',
|
||||
min: 0,
|
||||
scale: false, // ✅ 新增:确保 Y 轴从 0 开始,防止自动调整
|
||||
scale: false, // ✅ 新增:确保 Y 轴从 0 开始,防止自动调整
|
||||
nameGap: type.value === 2 ? 22 : 10,
|
||||
inverse: type.value !== 2
|
||||
},
|
||||
@ -612,13 +669,17 @@ const handleChartClick = (params: any) => {
|
||||
const matchMonth = params.seriesName.match(/\d+/);
|
||||
const monthNum = matchMonth ? parseInt(matchMonth[0]) : 1;
|
||||
|
||||
const year = typeof datetimePicker.value.value === 'string'
|
||||
? parseInt(datetimePicker.value.value)
|
||||
: datetimePicker.value.value;
|
||||
const year =
|
||||
typeof datetimePicker.value.value === 'string'
|
||||
? parseInt(datetimePicker.value.value)
|
||||
: datetimePicker.value.value;
|
||||
|
||||
const startTime = `${year}-${String(monthNum).padStart(2, '0')}-01 00:00:00`;
|
||||
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;
|
||||
let currentStnm = '';
|
||||
@ -669,7 +730,7 @@ onBeforeUnmount(() => {
|
||||
|
||||
watch(
|
||||
() => JidiSelectEventStore.selectedItem,
|
||||
async (newVal) => {
|
||||
async newVal => {
|
||||
if (!newVal || !newVal.wbsCode) {
|
||||
return;
|
||||
}
|
||||
@ -689,20 +750,28 @@ watch(
|
||||
// },
|
||||
// { deep: true }
|
||||
// );
|
||||
|
||||
const handlePanelChange1 = (data: any) => {
|
||||
const handlePanelChange1 = (data: any, type: string) => {
|
||||
console.log('当前所有控件状态:', data);
|
||||
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 {
|
||||
showemit.value = true
|
||||
showemit.value = true;
|
||||
chartData.value = [];
|
||||
colors.value = [];
|
||||
selected.value = {};
|
||||
showemit.value = false;
|
||||
}
|
||||
|
||||
|
||||
};
|
||||
</script>
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -107,6 +107,8 @@ export const useMapOrchestrator = () => {
|
||||
mapDataStore.setLoading(true);
|
||||
|
||||
try {
|
||||
const previousPageKey = mapConfigStore.lastLoadOptions?.pageKey || '';
|
||||
const previousCheckedKeys = mapViewStore.getCheckedLayerKeys();
|
||||
const { layerConfig, legendOriginal, pageLegend } =
|
||||
await mapConfigStore.loadPageMapConfig({
|
||||
systemId: SYSTEM_ID,
|
||||
@ -122,11 +124,26 @@ export const useMapOrchestrator = () => {
|
||||
if (layerConfig.length > 0) {
|
||||
mapStore.setLayerData(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);
|
||||
}
|
||||
} 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;
|
||||
const targetPoint = mapDataStore.pointData.find((item: any) => {
|
||||
return item.stcd === pointId || item._id === pointId;
|
||||
|
||||
@ -1,36 +1,73 @@
|
||||
<template>
|
||||
<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-select v-model:value="formValue.dataDimensionVal" placeholder="请选择" style="width: 200px"
|
||||
@change="handleBaseChange">
|
||||
<a-select-option v-for="item in jdList" :key="item.wbsCode" :value="item.wbsCode">
|
||||
<a-select
|
||||
v-model:value="formValue.dataDimensionVal"
|
||||
placeholder="请选择"
|
||||
style="width: 200px"
|
||||
@change="handleBaseChange"
|
||||
>
|
||||
<a-select-option
|
||||
v-for="item in jdList"
|
||||
:key="item.wbsCode"
|
||||
:value="item.wbsCode"
|
||||
>
|
||||
{{ item.wbsName }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="所在流域" name="rvcd">
|
||||
<a-select v-model:value="formValue.rvcd" placeholder="请选择" style="width: 200px"
|
||||
:disabled="!formValue.dataDimensionVal" @change="handleRvcdChange">
|
||||
<a-select-option v-for="item in lyList" :key="item.value" :value="item.value">
|
||||
<a-select
|
||||
v-model:value="formValue.rvcd"
|
||||
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 }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="断面名称" name="stcd">
|
||||
<a-select 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">
|
||||
<a-select
|
||||
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 }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="月份" name="tm">
|
||||
<a-date-picker 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-date-picker
|
||||
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>
|
||||
@ -45,8 +82,14 @@
|
||||
<div style="width: 100%; display: flex">
|
||||
<!-- 图表容器:始终存在,不再使用 v-if -->
|
||||
<div class="chart-wrapper" ref="chartRef"></div>
|
||||
<BasicTable ref="tableRef" :scrollY="460" :columns="columns" :list-url="DetGetKendoListCust"
|
||||
:search-params="{ sort: sort }" :transform-data="customTransform">
|
||||
<BasicTable
|
||||
ref="tableRef"
|
||||
:scrollY="460"
|
||||
:columns="columns"
|
||||
:list-url="DetGetKendoListCust"
|
||||
:search-params="{ sort: sort }"
|
||||
:transform-data="customTransform"
|
||||
>
|
||||
<template #action="{ record }">
|
||||
<a @click="handleViewDetail(record)" class="text_hocer">查看详情</a>
|
||||
</template>
|
||||
@ -254,10 +297,10 @@ const fetchLyData = async () => {
|
||||
},
|
||||
formValue.dataDimensionVal != 'all'
|
||||
? {
|
||||
field: 'fullPath',
|
||||
operator: 'startswith',
|
||||
value: formValue.dataDimensionVal
|
||||
}
|
||||
field: 'fullPath',
|
||||
operator: 'startswith',
|
||||
value: formValue.dataDimensionVal
|
||||
}
|
||||
: null
|
||||
].filter(Boolean)
|
||||
},
|
||||
@ -311,15 +354,15 @@ const fetchDmData = async () => {
|
||||
{ field: 'mway', operator: 'eq', value: 2 },
|
||||
formValue.dataDimensionVal == 'all'
|
||||
? {
|
||||
field: 'rvcd',
|
||||
operator: 'contains',
|
||||
value: formValue.rvcd
|
||||
}
|
||||
field: 'rvcd',
|
||||
operator: 'contains',
|
||||
value: formValue.rvcd
|
||||
}
|
||||
: {
|
||||
field: 'baseId',
|
||||
operator: 'contains',
|
||||
value: formValue.dataDimensionVal
|
||||
}
|
||||
field: 'baseId',
|
||||
operator: 'contains',
|
||||
value: formValue.dataDimensionVal
|
||||
}
|
||||
]
|
||||
},
|
||||
select: ['stcd', 'stnm', 'lgtd', 'lttd', 'rstcdStepSort'],
|
||||
@ -816,8 +859,12 @@ const handleViewDetail = (record: any) => {
|
||||
});
|
||||
modelStore.modalVisible = true;
|
||||
modelStore.params.sttp = 'WT';
|
||||
modelStore.title = stnm ;
|
||||
modelStore.title = stnm;
|
||||
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({
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -298,8 +298,12 @@ const handleExport = async () => {};
|
||||
const handleViewDetail = (record: any) => {
|
||||
modelStore.modalVisible = true;
|
||||
modelStore.params.sttp = 'WT';
|
||||
modelStore.title = record.stnm ;
|
||||
modelStore.title = record.stnm;
|
||||
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
@ -1,34 +1,42 @@
|
||||
<!-- SidePanelItem.vue -->
|
||||
<template>
|
||||
<SidePanelItem title="沿程水温变化" :prompt="prompts" :moreSelect="select" :datetimePicker="datetimePicker"
|
||||
@update-values="handlePanelChange1">
|
||||
<SidePanelItem
|
||||
title="沿程水温变化"
|
||||
:prompt="prompts"
|
||||
:moreSelect="select"
|
||||
: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>
|
||||
<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 } from "vue";
|
||||
import * as echarts from "echarts";
|
||||
import type { ECharts } from "echarts";
|
||||
import SidePanelItem from "@/components/SidePanelItem/index.vue";
|
||||
import { getKendoListCust, wbsbGetKendoList } from "@/api/sw";
|
||||
import { useJidiSelectEventStore } from "@/store/modules/jidiSelectEvent";
|
||||
import { ref, onMounted, onBeforeUnmount, watch, computed } from 'vue';
|
||||
import * as echarts from 'echarts';
|
||||
import type { ECharts } from 'echarts';
|
||||
import SidePanelItem from '@/components/SidePanelItem/index.vue';
|
||||
import { getKendoListCust, wbsbGetKendoList } from '@/api/sw';
|
||||
import { useJidiSelectEventStore } from '@/store/modules/jidiSelectEvent';
|
||||
|
||||
import { da } from "element-plus/es/locale/index.mjs";
|
||||
import { f } from "vue-router/dist/router-CWoNjPRp.mjs";
|
||||
import { useModelStore } from "@/store/modules/model";
|
||||
import { da } from 'element-plus/es/locale/index.mjs';
|
||||
import { f } from 'vue-router/dist/router-CWoNjPRp.mjs';
|
||||
import { useModelStore } from '@/store/modules/model';
|
||||
|
||||
const modelStore = useModelStore();
|
||||
|
||||
|
||||
|
||||
// 定义组件名(便于调试和递归)
|
||||
defineOptions({
|
||||
name: "yanchengshuiwenChangeMod",
|
||||
name: 'yanchengshuiwenChangeMod'
|
||||
});
|
||||
// 改为动态获取当前时间
|
||||
const getCurrentHourTime = () => {
|
||||
@ -46,21 +54,21 @@ const baseid: any = ref(null);
|
||||
const paramsOne = {
|
||||
rvcd: '',
|
||||
tm: []
|
||||
}
|
||||
const dataOne = ref('')
|
||||
};
|
||||
const dataOne = ref('');
|
||||
// 使用 computed 使 prompts 响应 dataOne.value 的变化
|
||||
const prompts = computed(() => ({
|
||||
show: true,
|
||||
value: `注:最新数据时间为${dataOne.value || getCurrentHourTime()}`,
|
||||
value: `注:最新数据时间为${dataOne.value || getCurrentHourTime()}`
|
||||
}));
|
||||
const showemit = ref(false)
|
||||
const loading = ref(false)
|
||||
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;
|
||||
@ -69,7 +77,9 @@ let chartInstance: ECharts | null = null;
|
||||
const stationNames = ref([]);
|
||||
|
||||
// 静态数据 - 水温值 (°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 的变化
|
||||
const currentTime = computed(() => dataOne.value || getCurrentHourTime());
|
||||
@ -79,16 +89,16 @@ const select = ref({
|
||||
value: '',
|
||||
options: [],
|
||||
picker: undefined,
|
||||
format: undefined,
|
||||
format: undefined
|
||||
});
|
||||
|
||||
// 日期选择器配置
|
||||
const datetimePicker = ref({
|
||||
show: true,
|
||||
value: computed(() => getCurrentHourTime()),
|
||||
format: "YYYY-MM-DD HH",
|
||||
picker: "date" as const,
|
||||
options: [],
|
||||
format: 'YYYY-MM-DD HH',
|
||||
picker: 'date' as const,
|
||||
options: []
|
||||
});
|
||||
// 初始化图表
|
||||
const initChart = () => {
|
||||
@ -98,14 +108,14 @@ const initChart = () => {
|
||||
|
||||
const option = {
|
||||
title: {
|
||||
text: "水温(°C)",
|
||||
text: '水温(°C)',
|
||||
left: 5,
|
||||
top: 0,
|
||||
textStyle: {
|
||||
color: "#000000",
|
||||
color: '#000000',
|
||||
fontSize: 12,
|
||||
fontWeight: "normal",
|
||||
},
|
||||
fontWeight: 'normal'
|
||||
}
|
||||
},
|
||||
dataZoom: [
|
||||
{
|
||||
@ -118,7 +128,7 @@ const initChart = () => {
|
||||
}
|
||||
],
|
||||
tooltip: {
|
||||
trigger: "axis",
|
||||
trigger: 'axis',
|
||||
formatter: (params: any) => {
|
||||
if (params && params.length > 0) {
|
||||
const data = params[0];
|
||||
@ -129,31 +139,31 @@ const initChart = () => {
|
||||
return `${currentTime.value}<br/>${data.name}:-`;
|
||||
}
|
||||
}
|
||||
return "";
|
||||
},
|
||||
return '';
|
||||
}
|
||||
},
|
||||
grid: {
|
||||
left: "3%",
|
||||
right: "4%",
|
||||
bottom: "3%",
|
||||
top: "15%",
|
||||
containLabel: true,
|
||||
left: '3%',
|
||||
right: '4%',
|
||||
bottom: '3%',
|
||||
top: '15%',
|
||||
containLabel: true
|
||||
},
|
||||
xAxis: {
|
||||
type: "category",
|
||||
type: 'category',
|
||||
data: stationNames.value,
|
||||
boundaryGap: true,
|
||||
axisLine: {
|
||||
show: true,
|
||||
lineStyle: {
|
||||
color: "#8f8f8f",
|
||||
},
|
||||
color: '#8f8f8f'
|
||||
}
|
||||
},
|
||||
axisTick: {
|
||||
show: false,
|
||||
show: false
|
||||
},
|
||||
axisLabel: {
|
||||
color: "#333",
|
||||
color: '#333',
|
||||
fontSize: 12,
|
||||
margin: 8,
|
||||
interval: 0,
|
||||
@ -166,76 +176,76 @@ const initChart = () => {
|
||||
} else {
|
||||
return ` \n${value}`; // 下方标签:空格占位 + 换行 + 文字
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
splitLine: {
|
||||
show: true,
|
||||
lineStyle: {
|
||||
color: "#e0e0e0",
|
||||
type: "solid",
|
||||
},
|
||||
},
|
||||
color: '#e0e0e0',
|
||||
type: 'solid'
|
||||
}
|
||||
}
|
||||
},
|
||||
yAxis: {
|
||||
type: "value",
|
||||
type: 'value',
|
||||
min: 0,
|
||||
// max: 10,
|
||||
interval: 2,
|
||||
axisLine: {
|
||||
show: true,
|
||||
lineStyle: {
|
||||
color: "#8f8f8f",
|
||||
},
|
||||
color: '#8f8f8f'
|
||||
}
|
||||
},
|
||||
axisTick: {
|
||||
show: true,
|
||||
length: 3,
|
||||
lineStyle: {
|
||||
color: "#8f8f8f",
|
||||
},
|
||||
color: '#8f8f8f'
|
||||
}
|
||||
},
|
||||
axisLabel: {
|
||||
color: "#333",
|
||||
fontSize: 12,
|
||||
color: '#333',
|
||||
fontSize: 12
|
||||
},
|
||||
splitLine: {
|
||||
show: true,
|
||||
lineStyle: {
|
||||
color: "#e0e0e0",
|
||||
type: "solid",
|
||||
},
|
||||
},
|
||||
color: '#e0e0e0',
|
||||
type: 'solid'
|
||||
}
|
||||
}
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: "水温",
|
||||
type: "line",
|
||||
name: '水温',
|
||||
type: 'line',
|
||||
smooth: true,
|
||||
symbol: "circle",
|
||||
symbol: 'circle',
|
||||
symbolSize: 6,
|
||||
connectNulls: true, // 连接空值两侧的线段,避免折线断开
|
||||
connectNulls: true, // 连接空值两侧的线段,避免折线断开
|
||||
lineStyle: {
|
||||
color: "#6ca4f7",
|
||||
width: 2,
|
||||
color: '#6ca4f7',
|
||||
width: 2
|
||||
},
|
||||
itemStyle: {
|
||||
color: "#6ca4f7",
|
||||
color: '#6ca4f7'
|
||||
},
|
||||
areaStyle: {
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||
{
|
||||
offset: 0,
|
||||
color: "rgba(108, 164, 247, 0.3)",
|
||||
color: 'rgba(108, 164, 247, 0.3)'
|
||||
},
|
||||
{
|
||||
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);
|
||||
@ -247,11 +257,12 @@ const initChart = () => {
|
||||
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.params.sttp = 'WT';
|
||||
modelStore.title = params.data.stnm;
|
||||
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 {
|
||||
const params = {
|
||||
filter: {
|
||||
logic: "and",
|
||||
logic: 'and',
|
||||
filters: [
|
||||
{ field: "rvcd", 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] },
|
||||
],
|
||||
{
|
||||
field: 'rvcd',
|
||||
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 data = res.data.data || res.data
|
||||
let data = res.data.data || res.data;
|
||||
|
||||
if (data.length > 0) {
|
||||
showemit.value = true
|
||||
showemit.value = true;
|
||||
} else {
|
||||
showemit.value = false
|
||||
showemit.value = false;
|
||||
}
|
||||
|
||||
stationNames.value = data
|
||||
.filter((item: any) => item.sttp == "1")
|
||||
.filter((item: any) => item.sttp == '1')
|
||||
.map((item: any) => item.stnm);
|
||||
console.log(stationNames.value);
|
||||
|
||||
// 修改为对象格式,携带stcd和stnm信息
|
||||
waterTemperatures.value = data
|
||||
.filter((item: any) => item.sttp == "2")
|
||||
.filter((item: any) => item.sttp == '2')
|
||||
.map((item: any) => ({
|
||||
value: item.temperature,
|
||||
stcd: item.stcd,
|
||||
@ -311,13 +337,13 @@ const init = async () => {
|
||||
// 如果图表已存在,只更新数据
|
||||
chartInstance.setOption({
|
||||
xAxis: {
|
||||
data: stationNames.value,
|
||||
data: stationNames.value
|
||||
},
|
||||
series: [
|
||||
{
|
||||
data: waterTemperatures.value,
|
||||
},
|
||||
],
|
||||
data: waterTemperatures.value
|
||||
}
|
||||
]
|
||||
});
|
||||
}
|
||||
// 强制重绘,确保尺寸正确
|
||||
@ -336,11 +362,11 @@ const init = async () => {
|
||||
const getSelectConfig = async () => {
|
||||
loading.value = true;
|
||||
try {
|
||||
let obj: any = {}
|
||||
let obj: any = {};
|
||||
if (baseid.value === 'all') {
|
||||
obj = { rstcdStepSort: 'asc', siteStepSort: 'asc' }
|
||||
obj = { rstcdStepSort: 'asc', siteStepSort: 'asc' };
|
||||
} else {
|
||||
obj = { siteStepSort: 'asc' }
|
||||
obj = { siteStepSort: 'asc' };
|
||||
}
|
||||
const filters = [
|
||||
{
|
||||
@ -348,13 +374,13 @@ const getSelectConfig = async () => {
|
||||
operator: 'eq',
|
||||
value: 'PSB_RVCD'
|
||||
}
|
||||
]
|
||||
];
|
||||
if (baseid.value && baseid.value !== 'all') {
|
||||
filters.push({
|
||||
field: 'objId',
|
||||
operator: 'eq', // contains
|
||||
value: baseid.value
|
||||
})
|
||||
});
|
||||
}
|
||||
let data: any = {
|
||||
filter: {
|
||||
@ -362,10 +388,10 @@ const getSelectConfig = async () => {
|
||||
filters
|
||||
}
|
||||
// select: ['WBS_CODE', 'WBS_NAME']
|
||||
}
|
||||
};
|
||||
|
||||
if (obj) {
|
||||
data.sort = [obj]
|
||||
data.sort = [obj];
|
||||
} else {
|
||||
data = {
|
||||
...data,
|
||||
@ -373,31 +399,42 @@ const getSelectConfig = async () => {
|
||||
{ dir: 'asc', field: 'orderIndex' },
|
||||
{ dir: 'asc', field: 'wbsCode' },
|
||||
{ dir: 'asc', field: 'wbsName' },
|
||||
{ dir: 'asc', field: 'objid' },
|
||||
{ dir: 'asc', field: 'objid' }
|
||||
],
|
||||
groupResultFlat: true
|
||||
}
|
||||
};
|
||||
}
|
||||
let res = await wbsbGetKendoList(data)
|
||||
let dataOne = res?.data?.data || res?.data
|
||||
let res = await wbsbGetKendoList(data);
|
||||
let dataOne = res?.data?.data || res?.data;
|
||||
|
||||
if (dataOne.length > 0) {
|
||||
let dataMapNameMap: any = {}
|
||||
let jiDiListMap: any = {}
|
||||
let dataMapNameMap: any = {};
|
||||
let jiDiListMap: any = {};
|
||||
JidiSelectEventStore.jidiData.forEach((item: any) => {
|
||||
jiDiListMap[item.wbsCode] = item.wbsName
|
||||
})
|
||||
jiDiListMap[item.wbsCode] = item.wbsName;
|
||||
});
|
||||
|
||||
dataOne.map((item: any, index: number) => {
|
||||
const { stcd, stnm, objId } = item
|
||||
const { stcd, stnm, objId } = item;
|
||||
if (dataMapNameMap[objId]) {
|
||||
dataMapNameMap[objId].push({ ...item, baseName: jiDiListMap[objId], baseId: objId })
|
||||
dataMapNameMap[objId].push({
|
||||
...item,
|
||||
baseName: jiDiListMap[objId],
|
||||
baseId: objId
|
||||
});
|
||||
} 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] }
|
||||
})
|
||||
let dataMapNameArr: any = []
|
||||
return {
|
||||
label: item.wbsName,
|
||||
value: item.wbsCode,
|
||||
baseId: objId,
|
||||
baseName: jiDiListMap[objId]
|
||||
};
|
||||
});
|
||||
let dataMapNameArr: any = [];
|
||||
|
||||
Object.keys(dataMapNameMap).forEach((item: any) => {
|
||||
dataMapNameArr.push({
|
||||
@ -409,24 +446,29 @@ const getSelectConfig = async () => {
|
||||
return {
|
||||
title: item2.wbsName,
|
||||
value: item2.wbsCode
|
||||
}
|
||||
};
|
||||
})
|
||||
})
|
||||
})
|
||||
});
|
||||
});
|
||||
dataMapNameArr = dataMapNameArr.sort((a: any, b: any) => {
|
||||
const indexA = JidiSelectEventStore.jidiData.findIndex(item => item.wbsCode === a.value);
|
||||
const indexB = JidiSelectEventStore.jidiData.findIndex(item => item.wbsCode === b.value);
|
||||
const indexA = JidiSelectEventStore.jidiData.findIndex(
|
||||
item => item.wbsCode === a.value
|
||||
);
|
||||
const indexB = JidiSelectEventStore.jidiData.findIndex(
|
||||
item => item.wbsCode === b.value
|
||||
);
|
||||
return indexA - indexB;
|
||||
})
|
||||
select.value.options = filterSelectOptions(dataMapNameArr)
|
||||
});
|
||||
select.value.options = filterSelectOptions(dataMapNameArr);
|
||||
if (baseid.value === 'all') {
|
||||
select.value.value = 'SJLY1U'
|
||||
select.value.value = 'SJLY1U';
|
||||
} 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) {
|
||||
select.value.value = filterSelectOptions(dataMapNameArr)[0]?.value
|
||||
select.value.value = filterSelectOptions(dataMapNameArr)[0]?.value;
|
||||
} else {
|
||||
select.value.value = ''
|
||||
select.value.value = '';
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
@ -434,31 +476,39 @@ const getSelectConfig = async () => {
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const filterSelectOptions = (selectOptions: any) => {
|
||||
if (selectOptions?.length === 1 && selectOptions?.[0]?.children?.length === 1) {
|
||||
return [{ ...selectOptions?.[0]?.children?.[0], isLeaf: true }]
|
||||
if (
|
||||
selectOptions?.length === 1 &&
|
||||
selectOptions?.[0]?.children?.length === 1
|
||||
) {
|
||||
return [{ ...selectOptions?.[0]?.children?.[0], isLeaf: true }];
|
||||
} else if (selectOptions.length) {
|
||||
return selectOptions.map((e) => {
|
||||
return selectOptions.map(e => {
|
||||
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 {
|
||||
return { ...e.children[0], isLeaf: true }
|
||||
return { ...e.children[0], isLeaf: true };
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
return []
|
||||
}
|
||||
return [];
|
||||
};
|
||||
//监听子组件的数据变化
|
||||
const handlePanelChange1 = (data) => {
|
||||
const handlePanelChange1 = data => {
|
||||
console.log('当前所有控件状态:', data);
|
||||
if (data.moreSelect) {
|
||||
paramsOne.rvcd = data.moreSelect
|
||||
paramsOne.rvcd = data.moreSelect;
|
||||
}
|
||||
|
||||
if (data.datetime) {
|
||||
dataOne.value = data.datetime
|
||||
dataOne.value = data.datetime;
|
||||
const datetime = data.datetime;
|
||||
|
||||
// 解析日期和时间
|
||||
@ -466,7 +516,14 @@ const handlePanelChange1 = (data) => {
|
||||
const [year, month, day] = datePart.split('-').map(Number);
|
||||
|
||||
// 创建当前时间的 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 对象
|
||||
const previousDate = new Date(year, month - 2, day, 0, 0, 0);
|
||||
@ -484,23 +541,22 @@ const handlePanelChange1 = (data) => {
|
||||
|
||||
// 生成结果数组
|
||||
const result = [
|
||||
formatDate(currentDate), // "2026-05-15 15:00:00"
|
||||
formatDate(previousDate) // "2026-04-15 00:00:00"
|
||||
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')
|
||||
paramsOne.tm = result;
|
||||
console.log(paramsOne.tm, 'paramsOne.tm');
|
||||
}
|
||||
if (paramsOne.rvcd || paramsOne.tm.length > 0) {
|
||||
init();
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
watch(
|
||||
() => JidiSelectEventStore.selectedItem,
|
||||
(newVal) => {
|
||||
newVal => {
|
||||
baseid.value = newVal.wbsCode;
|
||||
getSelectConfig()
|
||||
getSelectConfig();
|
||||
},
|
||||
{ deep: true, immediate: true }
|
||||
);
|
||||
@ -508,12 +564,12 @@ watch(
|
||||
onMounted(() => {
|
||||
// 延迟初始化,确保 DOM 渲染完成
|
||||
init();
|
||||
window.addEventListener("resize", handleResize);
|
||||
window.addEventListener('resize', handleResize);
|
||||
});
|
||||
|
||||
// 组件卸载前清理
|
||||
onBeforeUnmount(() => {
|
||||
window.removeEventListener("resize", handleResize);
|
||||
window.removeEventListener('resize', handleResize);
|
||||
chartInstance?.dispose();
|
||||
});
|
||||
</script>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user