添加下拉框选择,右侧切换功能
This commit is contained in:
parent
69e46e9914
commit
800a2665a0
@ -2,7 +2,7 @@
|
|||||||
<div
|
<div
|
||||||
id="map-baselayer"
|
id="map-baselayer"
|
||||||
class="baselayer-switcher"
|
class="baselayer-switcher"
|
||||||
:style="{ right: drawerOpen ? '480px' : '12px' }"
|
:style="{ right: drawerOpen ? '480px' : '26px' }"
|
||||||
v-if="uiStore.mapType == '2D'"
|
v-if="uiStore.mapType == '2D'"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
@ -21,7 +21,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, watch } from 'vue';
|
import { computed, ref, watch } from 'vue';
|
||||||
import { useUiStore } from '@/store/modules/ui';
|
import { useUiStore } from '@/store/modules/ui';
|
||||||
import { useMapConfigStore } from '@/modules/map/stores/map-config.store';
|
import { useMapConfigStore } from '@/modules/map/stores/map-config.store';
|
||||||
import { useMapViewStore } from '@/modules/map/stores/map-view.store';
|
import { useMapViewStore } from '@/modules/map/stores/map-view.store';
|
||||||
@ -41,14 +41,9 @@ const props = defineProps({
|
|||||||
const uiStore = useUiStore();
|
const uiStore = useUiStore();
|
||||||
const mapConfigStore = useMapConfigStore();
|
const mapConfigStore = useMapConfigStore();
|
||||||
const mapViewStore = useMapViewStore();
|
const mapViewStore = useMapViewStore();
|
||||||
const drawerOpen = ref(uiStore.drawerOpen);
|
// 抽屉展开(页面自带抽屉或全局抽屉任一打开)时,底图切换整体左移
|
||||||
|
const drawerOpen = computed(
|
||||||
// 监听 store 中的 drawerOpen 变化
|
() => uiStore.drawerOpen || uiStore.globalDrawerOpen
|
||||||
watch(
|
|
||||||
() => uiStore.drawerOpen,
|
|
||||||
newVal => {
|
|
||||||
drawerOpen.value = newVal;
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
const layers = [
|
const layers = [
|
||||||
{ key: 's_province_boundaries', name: '矢量', img: shiliangImg },
|
{ key: 's_province_boundaries', name: '矢量', img: shiliangImg },
|
||||||
@ -66,7 +61,7 @@ const activeKey = ref(layers[0].key);
|
|||||||
// 监听外部通过 store 切换底图(如电站专题页)
|
// 监听外部通过 store 切换底图(如电站专题页)
|
||||||
watch(
|
watch(
|
||||||
() => mapViewStore.activeBaseLayerKey,
|
() => mapViewStore.activeBaseLayerKey,
|
||||||
(newKey) => {
|
newKey => {
|
||||||
if (newKey && layers.some(l => l.key === newKey)) {
|
if (newKey && layers.some(l => l.key === newKey)) {
|
||||||
activeKey.value = newKey;
|
activeKey.value = newKey;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -66,6 +66,7 @@ interface Props {
|
|||||||
processEmptyValues?: boolean; // 新增:是否开启空值处理
|
processEmptyValues?: boolean; // 新增:是否开启空值处理
|
||||||
enableEllipsis?: boolean; // 新增:是否开启超出隐藏
|
enableEllipsis?: boolean; // 新增:是否开启超出隐藏
|
||||||
enableSort?: boolean; // 新增:是否启用排序功能
|
enableSort?: boolean; // 新增:是否启用排序功能
|
||||||
|
paginationConfig?: Record<string, any>; // 自定义分页配置,传入则覆盖默认分页配置
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = withDefaults(defineProps<Props>(), {
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
@ -165,7 +166,8 @@ const rowSelection = computed(() => ({
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
// --- Pagination Config ---
|
// --- Pagination Config ---
|
||||||
const paginationConfig = computed(() => ({
|
const paginationConfig = computed(() => {
|
||||||
|
const defaultConfig: Record<string, any> = {
|
||||||
total: total.value,
|
total: total.value,
|
||||||
current: page.value,
|
current: page.value,
|
||||||
pageSize: size.value,
|
pageSize: size.value,
|
||||||
@ -173,7 +175,10 @@ const paginationConfig = computed(() => ({
|
|||||||
showQuickJumper: true,
|
showQuickJumper: true,
|
||||||
showTotal: (total: number) => `共 ${total} 条`,
|
showTotal: (total: number) => `共 ${total} 条`,
|
||||||
pageSizeOptions: ['20', '50', '100']
|
pageSizeOptions: ['20', '50', '100']
|
||||||
}));
|
};
|
||||||
|
// 传入的自定义分页配置与默认配置合并,未传的字段使用默认值
|
||||||
|
return { ...defaultConfig, ...props.paginationConfig };
|
||||||
|
});
|
||||||
|
|
||||||
const $slots = useSlots();
|
const $slots = useSlots();
|
||||||
|
|
||||||
|
|||||||
115
frontend/src/components/GlobalRightDrawer/index.vue
Normal file
115
frontend/src/components/GlobalRightDrawer/index.vue
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
<!-- 全局右侧抽屉:由 AppMain 承载,展示锚点搜索内容(基本信息 + 监测表格),受 uiStore.globalDrawerOpen 控制 -->
|
||||||
|
<template>
|
||||||
|
<div class="global-right-drawer">
|
||||||
|
<div
|
||||||
|
@click="handleToggle"
|
||||||
|
class="drawerController1"
|
||||||
|
v-if="!uiStore.globalDrawerOpen"
|
||||||
|
>
|
||||||
|
<img src="../../assets/components/arrow-left.png" alt="" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<a-drawer
|
||||||
|
:get-container="false"
|
||||||
|
:style="{ position: 'relative' }"
|
||||||
|
v-model:open="uiStore.globalDrawerOpen"
|
||||||
|
:mask="false"
|
||||||
|
placement="right"
|
||||||
|
width="450"
|
||||||
|
:closable="false"
|
||||||
|
:headerStyle="{ color: '#FAFCFE' }"
|
||||||
|
>
|
||||||
|
<div @click="handleToggle" class="drawerController">
|
||||||
|
<img src="../../assets/components/arrow-right.png" alt="" />
|
||||||
|
</div>
|
||||||
|
<div style="padding: 16px 16px 0" class="text_she">
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
</a-drawer>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { defineOptions } from 'vue';
|
||||||
|
import { useUiStore } from '@/store/modules/ui';
|
||||||
|
|
||||||
|
// 定义组件名 (便于调试)
|
||||||
|
defineOptions({
|
||||||
|
name: 'globalRightDrawer'
|
||||||
|
});
|
||||||
|
|
||||||
|
const uiStore = useUiStore();
|
||||||
|
|
||||||
|
const handleToggle = () => {
|
||||||
|
uiStore.setGlobalDrawerOpen(!uiStore.globalDrawerOpen);
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.global-right-drawer {
|
||||||
|
width: 450px;
|
||||||
|
height: 100%;
|
||||||
|
position: relative;
|
||||||
|
box-sizing: border-box;
|
||||||
|
pointer-events: all;
|
||||||
|
|
||||||
|
.drawerController1 {
|
||||||
|
width: 18px;
|
||||||
|
position: absolute;
|
||||||
|
right: -2px;
|
||||||
|
cursor: pointer;
|
||||||
|
z-index: 10;
|
||||||
|
height: 88px;
|
||||||
|
line-height: 88px;
|
||||||
|
top: 45%;
|
||||||
|
vertical-align: middle;
|
||||||
|
background-image: url(../../assets/components/bg-toggle.e1dabcf3.svg);
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.drawerController {
|
||||||
|
width: 18px;
|
||||||
|
position: absolute;
|
||||||
|
right: 10px;
|
||||||
|
cursor: pointer;
|
||||||
|
z-index: 10;
|
||||||
|
height: 88px;
|
||||||
|
line-height: 88px;
|
||||||
|
top: 45%;
|
||||||
|
left: -18px;
|
||||||
|
vertical-align: middle;
|
||||||
|
background-image: url(../../assets/components/bg-toggle.e1dabcf3.svg);
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-drawer {
|
||||||
|
// margin: 3px 0px;
|
||||||
|
.ant-drawer-content {
|
||||||
|
overflow: visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-drawer-content-wrapper {
|
||||||
|
border: 2px solid #c5d6e2 !important;
|
||||||
|
box-shadow: 3px 3px 3px 9px #e5edf3 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-drawer-body {
|
||||||
|
padding: 0px !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.text_she {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #262626;
|
||||||
|
font-variant: tabular-nums;
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto,
|
||||||
|
Helvetica Neue, Arial, Noto Sans, sans-serif, Apple Color Emoji,
|
||||||
|
Segoe UI Emoji, Segoe UI Symbol, Noto Color Emoji;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -2349,7 +2349,7 @@ const FBPointColumns: Array<any> = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: '生产工艺',
|
name: '生产工艺',
|
||||||
filed: 'zzfkgy',
|
filed: 'zzflgy',
|
||||||
visible: true,
|
visible: true,
|
||||||
type: 'select',
|
type: 'select',
|
||||||
url: ''
|
url: ''
|
||||||
@ -2478,7 +2478,7 @@ const VaPointColumns: Array<any> = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: '保护方式',
|
name: '保护方式',
|
||||||
filed: 'bhfs',
|
filed: 'protmthd',
|
||||||
visible: true,
|
visible: true,
|
||||||
type: 'input',
|
type: 'input',
|
||||||
url: ''
|
url: ''
|
||||||
@ -2586,7 +2586,7 @@ const VPPointColumns: Array<any> = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: '保护方式',
|
name: '保护方式',
|
||||||
filed: 'bhfs',
|
filed: 'protmthd',
|
||||||
visible: true,
|
visible: true,
|
||||||
type: 'input',
|
type: 'input',
|
||||||
url: ''
|
url: ''
|
||||||
|
|||||||
@ -23,7 +23,7 @@
|
|||||||
<div class="tab-content">
|
<div class="tab-content">
|
||||||
<div class="content-body">
|
<div class="content-body">
|
||||||
<div class="chart-wrapper">
|
<div class="chart-wrapper">
|
||||||
<MonitorInfoEcharts :data="chartData" :is-active="isActive" />
|
<div ref="chartRef" class="chart-container"></div>
|
||||||
<a-empty
|
<a-empty
|
||||||
v-if="!chartData || chartData.length === 0"
|
v-if="!chartData || chartData.length === 0"
|
||||||
description="暂无数据"
|
description="暂无数据"
|
||||||
@ -45,12 +45,12 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref, watch } from 'vue';
|
import { ref, watch, onMounted, onBeforeUnmount, nextTick } from 'vue';
|
||||||
import dayjs, { Dayjs } from 'dayjs';
|
import dayjs, { Dayjs } from 'dayjs';
|
||||||
|
import * as echarts from 'echarts';
|
||||||
import { getMonitorData } from '@/api/mapModal';
|
import { getMonitorData } from '@/api/mapModal';
|
||||||
import { useModelStore } from '@/store/modules/model';
|
import { useModelStore } from '@/store/modules/model';
|
||||||
import BasicTable from '@/components/BasicTable/index.vue';
|
import BasicTable from '@/components/BasicTable/index.vue';
|
||||||
import MonitorInfoEcharts from '../componentsEcharts/MonitorInfoEcharts.vue';
|
|
||||||
import { DateSetting } from '@/utils/enumeration';
|
import { DateSetting } from '@/utils/enumeration';
|
||||||
|
|
||||||
const modelStore = useModelStore();
|
const modelStore = useModelStore();
|
||||||
@ -64,6 +64,323 @@ const isLoading = ref(false);
|
|||||||
const tableData = ref<any[]>([]);
|
const tableData = ref<any[]>([]);
|
||||||
const chartData = ref<any[]>([]);
|
const chartData = ref<any[]>([]);
|
||||||
|
|
||||||
|
// ==================== 图表 ====================
|
||||||
|
const chartRef = ref<HTMLElement>();
|
||||||
|
let chartInstance: echarts.ECharts | null = null;
|
||||||
|
let resizeObserver: ResizeObserver | null = null;
|
||||||
|
|
||||||
|
// 初始化图表
|
||||||
|
const initChart = () => {
|
||||||
|
if (!chartRef.value) return;
|
||||||
|
if (chartInstance) chartInstance.dispose();
|
||||||
|
chartInstance = echarts.init(chartRef.value);
|
||||||
|
updateChart(chartData.value);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 更新图表
|
||||||
|
const updateChart = (data: any[]) => {
|
||||||
|
if (!chartInstance) return;
|
||||||
|
if (!data || data.length === 0) {
|
||||||
|
chartInstance.clear();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const sorted = [...data].sort(
|
||||||
|
(a, b) => new Date(a.tm).getTime() - new Date(b.tm).getTime()
|
||||||
|
);
|
||||||
|
|
||||||
|
const xAxisData = sorted.map(item => {
|
||||||
|
const hm = dayjs(item.tm).format('HH:mm');
|
||||||
|
if (hm === '00:00') {
|
||||||
|
const date = dayjs(item.tm).format('MM-DD');
|
||||||
|
return `${hm}\n${date}`;
|
||||||
|
}
|
||||||
|
return hm;
|
||||||
|
});
|
||||||
|
|
||||||
|
const rzData = sorted.map(item => item.rz);
|
||||||
|
const dzData = sorted.map(item => item.dz);
|
||||||
|
const qiData = sorted.map(item => item.qi);
|
||||||
|
const qoData = sorted.map(item => item.qo);
|
||||||
|
const qecData = sorted.map(item => item.qec);
|
||||||
|
const qecLimitData = sorted.map(item => item.qecLimit);
|
||||||
|
|
||||||
|
const formatRules: Record<
|
||||||
|
string,
|
||||||
|
{ format: (v: number) => string; unit: string }
|
||||||
|
> = {
|
||||||
|
坝上水位: { format: v => v.toFixed(2), unit: '(m)' },
|
||||||
|
坝下水位: { format: v => v.toFixed(2), unit: '(m)' },
|
||||||
|
入库流量: { format: v => String(Math.round(v)), unit: '(m³/s)' },
|
||||||
|
出库流量: { format: v => String(Number(v)), unit: '(m³/s)' },
|
||||||
|
生态流量: { format: v => v.toFixed(1), unit: '(m³/s)' },
|
||||||
|
生态流量限值: { format: v => v.toFixed(1), unit: '(m³/s)' }
|
||||||
|
};
|
||||||
|
|
||||||
|
const option = {
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'axis',
|
||||||
|
backgroundColor: 'rgba(50, 50, 50, 0.9)',
|
||||||
|
textStyle: { color: '#fff', fontSize: 12 },
|
||||||
|
axisPointer: { type: 'cross' },
|
||||||
|
formatter: (params: any) => {
|
||||||
|
if (!params || params.length === 0) return '';
|
||||||
|
const dataIndex = params[0].dataIndex;
|
||||||
|
const fullTime = sorted[dataIndex]?.tm
|
||||||
|
? dayjs(sorted[dataIndex].tm).format('YYYY-MM-DD HH:mm:ss')
|
||||||
|
: '';
|
||||||
|
let html = `<div style="font-size:16px;margin-bottom:8px;">${fullTime}</div>`;
|
||||||
|
params.forEach((param: any) => {
|
||||||
|
if (param.value == null) return;
|
||||||
|
const rule = Object.entries(formatRules).find(([key]) =>
|
||||||
|
param.seriesName.includes(key)
|
||||||
|
);
|
||||||
|
const displayValue = rule
|
||||||
|
? rule[1].format(Number(param.value))
|
||||||
|
: param.value;
|
||||||
|
const unit = rule ? rule[1].unit : '';
|
||||||
|
html += `
|
||||||
|
<div style="display:flex;align-items:center;justify-content:space-between;margin:4px 0;">
|
||||||
|
<span style="display:inline-block;width:10px;height:10px;border-radius:50%;background:${param.color};margin-right:8px;"></span>
|
||||||
|
<span style="flex:1;font-size:14px;text-align:left;margin-right:6px;">${param.seriesName}: </span>
|
||||||
|
<span style="font-size:14px;min-width:60px;text-align:right;"><strong>${displayValue}</strong> ${unit}</span>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
});
|
||||||
|
return html;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
type: 'scroll',
|
||||||
|
top: 10,
|
||||||
|
right: 60,
|
||||||
|
width: '80%',
|
||||||
|
data: [
|
||||||
|
'坝上水位',
|
||||||
|
'坝下水位',
|
||||||
|
'入库流量',
|
||||||
|
'出库流量',
|
||||||
|
'生态流量',
|
||||||
|
'生态流量限值'
|
||||||
|
],
|
||||||
|
textStyle: { fontSize: 12 },
|
||||||
|
selected: {
|
||||||
|
坝上水位: false,
|
||||||
|
坝下水位: false,
|
||||||
|
入库流量: true,
|
||||||
|
出库流量: true,
|
||||||
|
生态流量: false,
|
||||||
|
生态流量限值: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
left: 60,
|
||||||
|
right: 60,
|
||||||
|
top: 80,
|
||||||
|
bottom: 60
|
||||||
|
},
|
||||||
|
xAxis: {
|
||||||
|
type: 'category',
|
||||||
|
data: xAxisData,
|
||||||
|
axisLine: { lineStyle: { color: '#000000' } },
|
||||||
|
axisTick: { show: false },
|
||||||
|
axisLabel: { fontSize: 12 },
|
||||||
|
splitLine: {
|
||||||
|
show: true,
|
||||||
|
lineStyle: { color: '#bfbfbf', type: 'solid' }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
yAxis: [
|
||||||
|
{
|
||||||
|
name: '水位(m)',
|
||||||
|
type: 'value',
|
||||||
|
position: 'left',
|
||||||
|
axisLine: { lineStyle: { color: '#000000' } },
|
||||||
|
alignTicks: true,
|
||||||
|
scale: true,
|
||||||
|
splitNumber: 9
|
||||||
|
// boundaryGap: ['10%', 0]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '流量(m³/s)',
|
||||||
|
type: 'value',
|
||||||
|
position: 'right',
|
||||||
|
axisLine: { lineStyle: { color: '#000000' } },
|
||||||
|
splitNumber: 9
|
||||||
|
// boundaryGap: ['10%', 0]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
name: '坝上水位',
|
||||||
|
type: 'line',
|
||||||
|
yAxisIndex: 0,
|
||||||
|
data: rzData,
|
||||||
|
smooth: true,
|
||||||
|
symbol: 'circle',
|
||||||
|
symbolSize: 4,
|
||||||
|
lineStyle: { color: '#56C2E3', width: 2 },
|
||||||
|
itemStyle: { color: '#56C2E3' },
|
||||||
|
markPoint: {
|
||||||
|
data: [
|
||||||
|
{ type: 'max', name: 'Max' },
|
||||||
|
{ type: 'min', name: 'Min' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '坝下水位',
|
||||||
|
type: 'line',
|
||||||
|
yAxisIndex: 0,
|
||||||
|
data: dzData,
|
||||||
|
smooth: true,
|
||||||
|
symbol: 'circle',
|
||||||
|
symbolSize: 4,
|
||||||
|
lineStyle: { color: '#7399C6', width: 2 },
|
||||||
|
itemStyle: { color: '#7399C6' },
|
||||||
|
markPoint: {
|
||||||
|
data: [
|
||||||
|
{ type: 'max', name: 'Max' },
|
||||||
|
{ type: 'min', name: 'Min' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '入库流量',
|
||||||
|
type: 'line',
|
||||||
|
yAxisIndex: 1,
|
||||||
|
data: qiData,
|
||||||
|
smooth: true,
|
||||||
|
symbol: 'circle',
|
||||||
|
symbolSize: 4,
|
||||||
|
lineStyle: { color: '#4B79AB', width: 2 },
|
||||||
|
itemStyle: { color: '#4B79AB' },
|
||||||
|
markPoint: {
|
||||||
|
data: [
|
||||||
|
{ type: 'max', name: 'Max' },
|
||||||
|
{ type: 'min', name: 'Min' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '出库流量',
|
||||||
|
type: 'line',
|
||||||
|
yAxisIndex: 1,
|
||||||
|
data: qoData,
|
||||||
|
smooth: true,
|
||||||
|
symbol: 'circle',
|
||||||
|
symbolSize: 4,
|
||||||
|
lineStyle: { color: '#78C300', width: 2 },
|
||||||
|
itemStyle: { color: '#78C300' },
|
||||||
|
markPoint: {
|
||||||
|
data: [
|
||||||
|
{ type: 'max', name: 'Max' },
|
||||||
|
{ type: 'min', name: 'Min' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '生态流量',
|
||||||
|
type: 'line',
|
||||||
|
yAxisIndex: 1,
|
||||||
|
data: qecData,
|
||||||
|
smooth: true,
|
||||||
|
symbol: 'circle',
|
||||||
|
symbolSize: 4,
|
||||||
|
lineStyle: { color: '#00A050', width: 2 },
|
||||||
|
itemStyle: { color: '#00A050' },
|
||||||
|
markPoint: {
|
||||||
|
data: [
|
||||||
|
{ type: 'max', name: 'Max' },
|
||||||
|
{ type: 'min', name: 'Min' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '生态流量限值',
|
||||||
|
type: 'line',
|
||||||
|
yAxisIndex: 1,
|
||||||
|
data: qecLimitData,
|
||||||
|
smooth: true,
|
||||||
|
symbol: 'circle',
|
||||||
|
symbolSize: 4,
|
||||||
|
lineStyle: { color: '#F7A737', width: 2 },
|
||||||
|
itemStyle: { color: '#F7A737' },
|
||||||
|
markPoint: {
|
||||||
|
data: [
|
||||||
|
{ type: 'max', name: 'Max' },
|
||||||
|
{ type: 'min', name: 'Min' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
dataZoom: [
|
||||||
|
{
|
||||||
|
type: 'inside',
|
||||||
|
xAxisIndex: [0],
|
||||||
|
throttle: 50,
|
||||||
|
start: 0,
|
||||||
|
end: 100
|
||||||
|
}
|
||||||
|
],
|
||||||
|
toolbox: {
|
||||||
|
show: true,
|
||||||
|
feature: {
|
||||||
|
saveAsImage: { title: '保存为图片', type: 'png', pixelRatio: 2 }
|
||||||
|
},
|
||||||
|
right: 20,
|
||||||
|
top: 10
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
chartInstance.setOption(option, true);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 激活时初始化图表并处理自适应,数据变化时动态刷新
|
||||||
|
watch(
|
||||||
|
() => props.isActive,
|
||||||
|
active => {
|
||||||
|
if (!active) return;
|
||||||
|
nextTick(() => {
|
||||||
|
initChart();
|
||||||
|
setTimeout(() => {
|
||||||
|
if (chartInstance) chartInstance.resize();
|
||||||
|
}, 200);
|
||||||
|
if (!resizeObserver && chartRef.value) {
|
||||||
|
resizeObserver = new ResizeObserver(() => {
|
||||||
|
if (chartInstance) chartInstance.resize();
|
||||||
|
});
|
||||||
|
resizeObserver.observe(chartRef.value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
{ immediate: true }
|
||||||
|
);
|
||||||
|
|
||||||
|
watch(chartData, newData => {
|
||||||
|
updateChart(newData);
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleResize = () => {
|
||||||
|
if (chartInstance) chartInstance.resize();
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
window.addEventListener('resize', handleResize);
|
||||||
|
});
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
if (chartInstance) {
|
||||||
|
chartInstance.dispose();
|
||||||
|
chartInstance = null;
|
||||||
|
}
|
||||||
|
window.removeEventListener('resize', handleResize);
|
||||||
|
if (resizeObserver) {
|
||||||
|
resizeObserver.disconnect();
|
||||||
|
resizeObserver = null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
const tabsList = [{ name: '电站运行过程线', key: 'dzxq.tabs.jcsj' }];
|
const tabsList = [{ name: '电站运行过程线', key: 'dzxq.tabs.jcsj' }];
|
||||||
const activeTabKey = ref('dzxq.tabs.jcsj');
|
const activeTabKey = ref('dzxq.tabs.jcsj');
|
||||||
|
|
||||||
@ -261,6 +578,11 @@ watch(
|
|||||||
position: relative;
|
position: relative;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
padding-top: 10px;
|
padding-top: 10px;
|
||||||
|
|
||||||
|
.chart-container {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.chart-empty {
|
.chart-empty {
|
||||||
|
|||||||
@ -32,6 +32,7 @@ import {
|
|||||||
getNormalAddedSituationYear,
|
getNormalAddedSituationYear,
|
||||||
getNormalResearchSituation
|
getNormalResearchSituation
|
||||||
} from '@/api/mapModal';
|
} from '@/api/mapModal';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
import { useModelStore } from '@/store/modules/model';
|
import { useModelStore } from '@/store/modules/model';
|
||||||
import BasicTable from '@/components/BasicTable/index.vue';
|
import BasicTable from '@/components/BasicTable/index.vue';
|
||||||
|
|
||||||
@ -45,8 +46,9 @@ const hasLoaded = ref(false);
|
|||||||
const tableRef = ref();
|
const tableRef = ref();
|
||||||
|
|
||||||
// 搜索参数
|
// 搜索参数
|
||||||
|
|
||||||
const searchParams = ref({
|
const searchParams = ref({
|
||||||
tm: null as string | null
|
tm: dayjs().format('YYYY')
|
||||||
});
|
});
|
||||||
|
|
||||||
// 表格列配置
|
// 表格列配置
|
||||||
@ -109,7 +111,10 @@ const getYearList = async () => {
|
|||||||
|
|
||||||
// 刷新表格
|
// 刷新表格
|
||||||
const refreshTable = () => {
|
const refreshTable = () => {
|
||||||
const year = searchParams.value.tm;
|
let year = searchParams.value.tm;
|
||||||
|
if (year == null) {
|
||||||
|
year = dayjs().format('YYYY');
|
||||||
|
}
|
||||||
const startDate = `${year}-01-01 00:00:00`;
|
const startDate = `${year}-01-01 00:00:00`;
|
||||||
const endDate = `${year}-12-31 23:59:59`;
|
const endDate = `${year}-12-31 23:59:59`;
|
||||||
|
|
||||||
|
|||||||
@ -612,8 +612,6 @@ const applyTabFilters = async (params: any) => {
|
|||||||
try {
|
try {
|
||||||
const sttpCode = params?.sttp;
|
const sttpCode = params?.sttp;
|
||||||
const stcd = params?.stcd;
|
const stcd = params?.stcd;
|
||||||
const eqtp = params?.eqtp;
|
|
||||||
const baseId = modelStore.baseId;
|
|
||||||
|
|
||||||
if (!stcd) return;
|
if (!stcd) return;
|
||||||
|
|
||||||
@ -690,7 +688,6 @@ const qxdStationOptions = ref<{ label: string; value: string }[]>([]);
|
|||||||
const qxdSelectedStcd = ref<string>('');
|
const qxdSelectedStcd = ref<string>('');
|
||||||
const qxdSelectedStcdWq = ref<string>('');
|
const qxdSelectedStcdWq = ref<string>('');
|
||||||
const qxdSelectedStcdZq = ref<string>('');
|
const qxdSelectedStcdZq = ref<string>('');
|
||||||
const qxdLoadedCodes = ref<Set<string>>(new Set());
|
|
||||||
|
|
||||||
// 获取当前tab对应的qxd配置
|
// 获取当前tab对应的qxd配置
|
||||||
const getCurrentQxdConfig = (tabKey: string) => {
|
const getCurrentQxdConfig = (tabKey: string) => {
|
||||||
@ -731,13 +728,18 @@ const setQxdSelectedStcd = (tabKey: string, value: string) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 加载栖息地站点列表
|
// 加载栖息地站点列表(每次切换都会重新加载,保留 qxd 模式判断)
|
||||||
|
let qxdRequestSeq = 0; // 请求序号,用于竞态守卫(仅最后一次请求生效)
|
||||||
const loadQxdStationList = async (tabKey: string) => {
|
const loadQxdStationList = async (tabKey: string) => {
|
||||||
const code = getTabCode(tabKey);
|
const code = getTabCode(tabKey);
|
||||||
|
console.log(code);
|
||||||
|
|
||||||
if (!code || !qxdConfigMap[code]) return;
|
if (!code || !qxdConfigMap[code]) return;
|
||||||
if (qxdLoadedCodes.value.has(code)) return;
|
console.log(modelStore.params.stcd);
|
||||||
|
|
||||||
const config = qxdConfigMap[code];
|
const config = qxdConfigMap[code];
|
||||||
|
const requestSeq = ++qxdRequestSeq;
|
||||||
|
qxdStationOptions.value = []; // 先清空,等待加载
|
||||||
try {
|
try {
|
||||||
const params = {
|
const params = {
|
||||||
filter: {
|
filter: {
|
||||||
@ -760,7 +762,9 @@ const loadQxdStationList = async (tabKey: string) => {
|
|||||||
select: ['stcd', 'stnm']
|
select: ['stcd', 'stnm']
|
||||||
};
|
};
|
||||||
const res = await config.apiFn(params);
|
const res = await config.apiFn(params);
|
||||||
|
if (requestSeq !== qxdRequestSeq) return; // 非最新请求,丢弃结果
|
||||||
const stations = res?.data?.data || res?.data?.records || [];
|
const stations = res?.data?.data || res?.data?.records || [];
|
||||||
|
console.log(res);
|
||||||
if (stations.length > 0) {
|
if (stations.length > 0) {
|
||||||
qxdStationOptions.value = stations.map((item: any) => ({
|
qxdStationOptions.value = stations.map((item: any) => ({
|
||||||
label: item.stnm,
|
label: item.stnm,
|
||||||
@ -768,8 +772,8 @@ const loadQxdStationList = async (tabKey: string) => {
|
|||||||
}));
|
}));
|
||||||
setQxdSelectedStcd(tabKey, stations[0].stcd);
|
setQxdSelectedStcd(tabKey, stations[0].stcd);
|
||||||
}
|
}
|
||||||
qxdLoadedCodes.value.add(code);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
if (requestSeq !== qxdRequestSeq) return; // 非最新请求,忽略错误
|
||||||
console.error(`获取站点列表失败(${code}):`, error);
|
console.error(`获取站点列表失败(${code}):`, error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -793,12 +797,6 @@ const handleQxdStationChange = () => {
|
|||||||
// import MapView from './components/MapView.vue';
|
// import MapView from './components/MapView.vue';
|
||||||
// import SurroundingInfo from './components/SurroundingInfo.vue';
|
// import SurroundingInfo from './components/SurroundingInfo.vue';
|
||||||
|
|
||||||
// 定义 Tab 配置项接口
|
|
||||||
interface TabItem {
|
|
||||||
key: string;
|
|
||||||
title: string;
|
|
||||||
url: string;
|
|
||||||
}
|
|
||||||
// 获取 Tab 配置项的 URL
|
// 获取 Tab 配置项的 URL
|
||||||
const getTabUrl = (key: string) => {
|
const getTabUrl = (key: string) => {
|
||||||
const tab = tabsConfig.value.find((item: any) => item.key === key);
|
const tab = tabsConfig.value.find((item: any) => item.key === key);
|
||||||
@ -914,7 +912,6 @@ const handleClose = () => {
|
|||||||
qxdSelectedStcd.value = '';
|
qxdSelectedStcd.value = '';
|
||||||
qxdSelectedStcdWq.value = '';
|
qxdSelectedStcdWq.value = '';
|
||||||
qxdSelectedStcdZq.value = '';
|
qxdSelectedStcdZq.value = '';
|
||||||
qxdLoadedCodes.value = new Set();
|
|
||||||
// 清理电站专题状态
|
// 清理电站专题状态
|
||||||
hasPowerStatData.value = false;
|
hasPowerStatData.value = false;
|
||||||
modelStore.verticalWaterStcd = '';
|
modelStore.verticalWaterStcd = '';
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
<div
|
<div
|
||||||
id="map-controller"
|
id="map-controller"
|
||||||
class="map-controller"
|
class="map-controller"
|
||||||
:style="{ right: drawerOpen ? '480px' : '12px' }"
|
:style="{ right: drawerOpen ? '480px' : '26px' }"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="map-controller-group"
|
class="map-controller-group"
|
||||||
@ -74,16 +74,11 @@ const map = toRef(props, 'map');
|
|||||||
|
|
||||||
// 使用 Pinia store
|
// 使用 Pinia store
|
||||||
const uiStore = useUiStore();
|
const uiStore = useUiStore();
|
||||||
const drawerOpen = ref(uiStore.drawerOpen);
|
// 抽屉展开(页面自带抽屉或全局抽屉任一打开)时,控制器整体左移
|
||||||
const tjVisible = ref(false);
|
const drawerOpen = computed(
|
||||||
|
() => uiStore.drawerOpen || uiStore.globalDrawerOpen
|
||||||
// 监听 store 中的 drawerOpen 变化
|
|
||||||
watch(
|
|
||||||
() => uiStore.drawerOpen,
|
|
||||||
newVal => {
|
|
||||||
drawerOpen.value = newVal;
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
|
const tjVisible = ref(false);
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => uiStore.mapType,
|
() => uiStore.mapType,
|
||||||
|
|||||||
@ -140,6 +140,19 @@ const modelStore = useModelStore();
|
|||||||
const mapClass = MapClass.getInstance();
|
const mapClass = MapClass.getInstance();
|
||||||
const ENG_POINT_LAYER_KEY = 'eng_point';
|
const ENG_POINT_LAYER_KEY = 'eng_point';
|
||||||
const YLFB_POINT_LAYER_KEY = 'ylfb_point';
|
const YLFB_POINT_LAYER_KEY = 'ylfb_point';
|
||||||
|
// 需要弹右侧搜索抽屉的 sttpCode(按图层类型全局配置)
|
||||||
|
const DRAWER_STTP_CODES = [
|
||||||
|
'ENG',
|
||||||
|
'WTRV',
|
||||||
|
'WTVT',
|
||||||
|
'WQ',
|
||||||
|
'FP',
|
||||||
|
'FH',
|
||||||
|
'ZQ',
|
||||||
|
'VP',
|
||||||
|
'VA',
|
||||||
|
'EQ'
|
||||||
|
];
|
||||||
const siteRangePicker = [
|
const siteRangePicker = [
|
||||||
{ label: '全部', value: 'all' },
|
{ label: '全部', value: 'all' },
|
||||||
{ label: '大型电站', value: 'large_eng_built' },
|
{ label: '大型电站', value: 'large_eng_built' },
|
||||||
@ -231,7 +244,10 @@ watch(
|
|||||||
anchorPointSelect: null,
|
anchorPointSelect: null,
|
||||||
fishSurveyZhuanZhi: false
|
fishSurveyZhuanZhi: false
|
||||||
};
|
};
|
||||||
handleSearch(false);
|
uiStore.setGlobalDrawerOpen(false);
|
||||||
|
setTimeout(() => {
|
||||||
|
uiStore.setDrawerOpen(true);
|
||||||
|
}, 300);
|
||||||
resetFishSurveyState();
|
resetFishSurveyState();
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@ -599,7 +615,10 @@ watch(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
formModel.value.anchorPointSelect = null;
|
formModel.value.anchorPointSelect = null;
|
||||||
handleSearch(false);
|
uiStore.setGlobalDrawerOpen(false);
|
||||||
|
setTimeout(() => {
|
||||||
|
uiStore.setDrawerOpen(true);
|
||||||
|
}, 300);
|
||||||
},
|
},
|
||||||
{ immediate: true }
|
{ immediate: true }
|
||||||
);
|
);
|
||||||
@ -641,7 +660,10 @@ watch(
|
|||||||
|
|
||||||
const handleCapacityChange = (value: string) => {
|
const handleCapacityChange = (value: string) => {
|
||||||
formModel.value.anchorPointSelect = null;
|
formModel.value.anchorPointSelect = null;
|
||||||
handleSearch(false);
|
uiStore.setGlobalDrawerOpen(false);
|
||||||
|
setTimeout(() => {
|
||||||
|
uiStore.setDrawerOpen(true);
|
||||||
|
}, 300);
|
||||||
mapOrchestrator.changeEngPointCapacity(value);
|
mapOrchestrator.changeEngPointCapacity(value);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -665,26 +687,38 @@ const focusSelectedAnchorPoint = () => {
|
|||||||
// isFishSurveyMode.value ? anchorPointOptions.value : undefined
|
// isFishSurveyMode.value ? anchorPointOptions.value : undefined
|
||||||
// );
|
// );
|
||||||
};
|
};
|
||||||
|
|
||||||
// 备注:搜索定位只分发定位命令,不在筛选组件里直接操作地图实例。
|
// 备注:搜索定位只分发定位命令,不在筛选组件里直接操作地图实例。
|
||||||
|
// 抽屉是否弹出由选中点位的 sttpCode 决定(命中 DRAWER_STTP_CODES 才弹)。
|
||||||
const handleAnchorPointChange = (value: string) => {
|
const handleAnchorPointChange = (value: string) => {
|
||||||
if (value == null) handleSearch(false);
|
if (value == null) {
|
||||||
if (!value) return;
|
// 清空:先收起全局抽屉,等完全关闭后再恢复页面自带抽屉
|
||||||
focusSelectedAnchorPoint();
|
uiStore.setGlobalDrawerOpen(false);
|
||||||
handleSearch(true);
|
setTimeout(() => {
|
||||||
};
|
uiStore.setDrawerOpen(true);
|
||||||
|
}, 300);
|
||||||
// 备注:用 function 声明以提升到作用域顶部,供上方 immediate watch 安全调用。
|
|
||||||
function handleSearch(open: boolean) {
|
|
||||||
if (uiStore.searchDrawerOpen === open) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (!value) return;
|
||||||
|
focusSelectedAnchorPoint();
|
||||||
|
|
||||||
|
const matched = anchorPointOptions.value.find(item => item.stcd === value);
|
||||||
|
const sttpCode = String(
|
||||||
|
matched?.sttpCode || matched?.sttp || ''
|
||||||
|
).toUpperCase();
|
||||||
|
// 非抽屉图层:不弹抽屉,收起全局抽屉后恢复页面自带抽屉
|
||||||
|
if (!DRAWER_STTP_CODES.includes(sttpCode)) {
|
||||||
|
uiStore.setGlobalDrawerOpen(false);
|
||||||
|
setTimeout(() => {
|
||||||
|
uiStore.setDrawerOpen(true);
|
||||||
|
}, 300);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 需要抽屉:先收起页面自带抽屉,等完全关闭后再展示全局抽屉
|
||||||
uiStore.setDrawerOpen(false);
|
uiStore.setDrawerOpen(false);
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
uiStore.setSearchDrawerOpen(open);
|
uiStore.setGlobalDrawerOpen(true);
|
||||||
uiStore.setDrawerOpen(true);
|
}, 300);
|
||||||
}, 500);
|
};
|
||||||
}
|
|
||||||
watch(
|
watch(
|
||||||
() => uiStore.mapSwitchCompletedTick,
|
() => uiStore.mapSwitchCompletedTick,
|
||||||
async (newVal, oldVal) => {
|
async (newVal, oldVal) => {
|
||||||
|
|||||||
@ -4,17 +4,20 @@ import { useTagsViewStore } from '@/store/modules/tagsView';
|
|||||||
import { useRoute } from 'vue-router';
|
import { useRoute } from 'vue-router';
|
||||||
import MapModal from '@/components/MapModal/index.vue';
|
import MapModal from '@/components/MapModal/index.vue';
|
||||||
import ylfbModal from '@/components/ylfbModal/index.vue';
|
import ylfbModal from '@/components/ylfbModal/index.vue';
|
||||||
|
import GlobalRightDrawer from '@/components/GlobalRightDrawer/index.vue';
|
||||||
|
import basicInfoMod from '@/modules/rightSearchDrawer/basicInfo/index.vue';
|
||||||
|
import monitoringTableMod from '@/modules/rightSearchDrawer/monitoringTable/index.vue';
|
||||||
import { useModelStore } from '@/store/modules/model';
|
import { useModelStore } from '@/store/modules/model';
|
||||||
import GisView from '@/components/gis/GisView.vue';
|
import GisView from '@/components/gis/GisView.vue';
|
||||||
|
|
||||||
const modelStore = useModelStore();
|
const modelStore = useModelStore();
|
||||||
const tagsViewStore = useTagsViewStore();
|
const tagsViewStore = useTagsViewStore();
|
||||||
const router = useRoute();
|
const route = useRoute();
|
||||||
const routeKey = computed(() => router.path + Math.random());
|
const routeKey = computed(() => route.path + Math.random());
|
||||||
|
|
||||||
// 智能配图页面隐藏底层 GisView(避免地图叠放冲突)
|
// 智能配图页面隐藏底层 GisView(避免地图叠放冲突)
|
||||||
const isZhiNengPeiTu = computed(() => {
|
const isZhiNengPeiTu = computed(() => {
|
||||||
return router.path === '/zhiNengFenXi/zhiNengPeiTu/index';
|
return route.path === '/zhiNengFenXi/zhiNengPeiTu/index';
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@ -31,6 +34,14 @@ const isZhiNengPeiTu = computed(() => {
|
|||||||
</router-view>
|
</router-view>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- 全局抽屉:承载锚点搜索内容(基本信息 + 监测表格),由锚点下拉 sttpCode 驱动打开 -->
|
||||||
|
<div class="global-drawer-wrap">
|
||||||
|
<GlobalRightDrawer>
|
||||||
|
<basicInfoMod />
|
||||||
|
<monitoringTableMod />
|
||||||
|
</GlobalRightDrawer>
|
||||||
|
</div>
|
||||||
|
|
||||||
<MapModal
|
<MapModal
|
||||||
v-model:visible="modelStore.modalVisible"
|
v-model:visible="modelStore.modalVisible"
|
||||||
:title="modelStore.title"
|
:title="modelStore.title"
|
||||||
@ -58,4 +69,13 @@ const isZhiNengPeiTu = computed(() => {
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
.global-drawer-wrap {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
width: 450px;
|
||||||
|
height: 100%;
|
||||||
|
z-index: 100;
|
||||||
|
pointer-events: all;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@ -52,13 +52,20 @@
|
|||||||
:footer="null"
|
:footer="null"
|
||||||
@cancel="handleModalClose"
|
@cancel="handleModalClose"
|
||||||
>
|
>
|
||||||
<FishResource v-if="modalVisible" :modalData = "modalData" />
|
<FishResource v-if="modalVisible" :modalData="modalData" />
|
||||||
</a-modal>
|
</a-modal>
|
||||||
</SidePanelItem>
|
</SidePanelItem>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref, onMounted, onUnmounted, nextTick, watch, computed } from 'vue';
|
import {
|
||||||
|
ref,
|
||||||
|
onMounted,
|
||||||
|
onUnmounted,
|
||||||
|
nextTick,
|
||||||
|
watch,
|
||||||
|
defineOptions
|
||||||
|
} from 'vue';
|
||||||
import * as echarts from 'echarts';
|
import * as echarts from 'echarts';
|
||||||
import type { ECharts } from 'echarts';
|
import type { ECharts } from 'echarts';
|
||||||
import SidePanelItem from '@/components/SidePanelItem/index.vue';
|
import SidePanelItem from '@/components/SidePanelItem/index.vue';
|
||||||
@ -832,7 +839,7 @@ watch(
|
|||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.container {
|
.container {
|
||||||
width: 100%;
|
width: 400px;
|
||||||
height: 290px;
|
height: 290px;
|
||||||
// border: 1px solid #7fd6ff;
|
// border: 1px solid #7fd6ff;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
|
|||||||
@ -3,39 +3,115 @@
|
|||||||
<SidePanelItem title="设施类型及接入情况">
|
<SidePanelItem title="设施类型及接入情况">
|
||||||
<a-spin :spinning="loading">
|
<a-spin :spinning="loading">
|
||||||
<div v-if="dataJson.length > 0" class="card-container">
|
<div v-if="dataJson.length > 0" class="card-container">
|
||||||
<div v-for="(item) in dataJson" class="facility-card" @click="imgclick(item)">
|
<template v-for="(item, index) in dataJson" :key="index">
|
||||||
|
<div
|
||||||
|
v-if="index < 2"
|
||||||
|
:key="index"
|
||||||
|
:class="'facility-card' + index"
|
||||||
|
@click="imgclick(item)"
|
||||||
|
>
|
||||||
|
<div class="img_title">
|
||||||
<div class="img_icon">
|
<div class="img_icon">
|
||||||
<i class="icon iconfont" :class="item?.icon" />
|
<i class="icon iconfont" :class="item?.icon" />
|
||||||
</div>
|
</div>
|
||||||
<div class="img_text">
|
<div class="img_text">
|
||||||
<div>{{ item.name }}</div>
|
<div>{{ item.name }}</div>
|
||||||
<div class="text_num">{{ item.totalNum }}</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="img_num">
|
||||||
|
<div>
|
||||||
|
已接入
|
||||||
|
<span
|
||||||
|
:class="{
|
||||||
|
img_num_span1: index == 0,
|
||||||
|
img_num_span3: index == 1
|
||||||
|
}"
|
||||||
|
>{{ item.accessNum }}</span
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
未接入
|
||||||
|
<span class="img_num_span2">{{
|
||||||
|
Number(item.totalNum) - Number(item.accessNum)
|
||||||
|
}}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="img_sider">
|
||||||
|
<div
|
||||||
|
class="img_sider_inner"
|
||||||
|
:style="{
|
||||||
|
width: getPercent(item) + '%',
|
||||||
|
backgroundColor: getBarColor(index)
|
||||||
|
}"
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template v-for="(item, index) in dataJson" :key="index">
|
||||||
|
<div
|
||||||
|
v-if="index >= 2 && index < 4"
|
||||||
|
:class="'facility-card' + index"
|
||||||
|
@click="imgclick(item)"
|
||||||
|
>
|
||||||
|
<div class="img_num">
|
||||||
|
<div class="img_text1">
|
||||||
|
<div>{{ item.name }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="img_text2">
|
||||||
|
{{ item.totalNum }}
|
||||||
|
</div>
|
||||||
|
<div class="img_text3">设备总数</div>
|
||||||
|
</div>
|
||||||
|
<div class="img_title">
|
||||||
|
<div class="img_icon">
|
||||||
|
<i class="icon iconfont" :class="item?.icon" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-else style="width: 100%;height: 158px;display: flex; align-items: center; justify-content: center;">
|
</template>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-else
|
||||||
|
style="
|
||||||
|
width: 100%;
|
||||||
|
height: 158px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
"
|
||||||
|
>
|
||||||
<a-empty description="暂无数据" />
|
<a-empty description="暂无数据" />
|
||||||
</div>
|
</div>
|
||||||
</a-spin>
|
</a-spin>
|
||||||
</SidePanelItem>
|
</SidePanelItem>
|
||||||
|
|
||||||
<!-- 自定义弹框 -->
|
<!-- 自定义弹框 -->
|
||||||
<a-modal v-model:open="modalVisible" title="设施类型及接入情况" width="1536px" :footer="null" @cancel="handleModalClose">
|
<a-modal
|
||||||
|
v-model:open="modalVisible"
|
||||||
|
title="设施类型及接入情况"
|
||||||
|
width="1536px"
|
||||||
|
:footer="null"
|
||||||
|
@cancel="handleModalClose"
|
||||||
|
>
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
|
<DiwenshuijianhuansheshileixingzuchengjijieruqingkuangTwoLayers
|
||||||
<DiwenshuijianhuansheshileixingzuchengjijieruqingkuangTwoLayers ref="twoLayersRef" :datas="JidiSelectEventStore.jidiData" :BID="baseid"
|
ref="twoLayersRef"
|
||||||
:data="selectedItem" :res="res">
|
:datas="JidiSelectEventStore.jidiData"
|
||||||
|
:BID="baseid"
|
||||||
|
:data="selectedItem"
|
||||||
|
:res="res"
|
||||||
|
>
|
||||||
</DiwenshuijianhuansheshileixingzuchengjijieruqingkuangTwoLayers>
|
</DiwenshuijianhuansheshileixingzuchengjijieruqingkuangTwoLayers>
|
||||||
</div>
|
</div>
|
||||||
</a-modal>
|
</a-modal>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref, onMounted, watch } from 'vue';
|
import { ref, onMounted, watch, defineOptions } from 'vue';
|
||||||
import SidePanelItem from '@/components/SidePanelItem/index.vue';
|
import SidePanelItem from '@/components/SidePanelItem/index.vue';
|
||||||
import { dwInfoGetKendoListCust } from "@/api/sw";
|
import { dwInfoGetKendoListCust } from '@/api/sw';
|
||||||
import { useJidiSelectEventStore } from "@/store/modules/jidiSelectEvent";
|
import { useJidiSelectEventStore } from '@/store/modules/jidiSelectEvent';
|
||||||
import DiwenshuijianhuansheshileixingzuchengjijieruqingkuangTwoLayers from "./TwoLayers/diwenshuijianhuansheshileixingzuchengjijieruqingkuangTwoLayers.vue"
|
import DiwenshuijianhuansheshileixingzuchengjijieruqingkuangTwoLayers from './TwoLayers/diwenshuijianhuansheshileixingzuchengjijieruqingkuangTwoLayers.vue';
|
||||||
import { useDraggable } from '@/utils/drag';
|
import { useDraggable } from '@/utils/drag';
|
||||||
|
|
||||||
const JidiSelectEventStore = useJidiSelectEventStore();
|
const JidiSelectEventStore = useJidiSelectEventStore();
|
||||||
@ -63,13 +139,13 @@ const iconMap: Record<string, string> = {
|
|||||||
const res = ref({
|
const res = ref({
|
||||||
hydrodtin: '',
|
hydrodtin: '',
|
||||||
bldstt: ''
|
bldstt: ''
|
||||||
})
|
});
|
||||||
// 响应式数据
|
// 响应式数据
|
||||||
const dataJson = ref<DataString[]>([]);
|
const dataJson = ref<DataString[]>([]);
|
||||||
|
|
||||||
// 加载状态
|
// 加载状态
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
const baseid = ref('')
|
const baseid = ref('');
|
||||||
|
|
||||||
// 弹框相关
|
// 弹框相关
|
||||||
const modalVisible = ref(false);
|
const modalVisible = ref(false);
|
||||||
@ -81,28 +157,30 @@ const getListData = async () => {
|
|||||||
loading.value = true;
|
loading.value = true;
|
||||||
try {
|
try {
|
||||||
const params = {
|
const params = {
|
||||||
"filter": {
|
filter: {
|
||||||
"logic": "and",
|
logic: 'and',
|
||||||
"filters": [
|
filters: [
|
||||||
baseid.value == 'all' ? null : {
|
baseid.value == 'all'
|
||||||
"field": "baseId",
|
? null
|
||||||
"operator": "contains",
|
: {
|
||||||
"dataType": "string",
|
field: 'baseId',
|
||||||
"value": baseid.value
|
operator: 'contains',
|
||||||
}
|
dataType: 'string',
|
||||||
].filter(Boolean),
|
value: baseid.value
|
||||||
}
|
}
|
||||||
|
].filter(Boolean)
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
let res = await dwInfoGetKendoListCust(params)
|
let res = await dwInfoGetKendoListCust(params);
|
||||||
let data = res?.data?.data || res?.data || []
|
let data = res?.data?.data || res?.data || [];
|
||||||
|
|
||||||
// 合并接口数据和icon配置
|
// 合并接口数据和icon配置
|
||||||
if (Array.isArray(data) && data.length > 0) {
|
if (Array.isArray(data) && data.length > 0) {
|
||||||
dataJson.value = data.map((item: any) => ({
|
dataJson.value = data.map((item: any) => ({
|
||||||
...item,
|
...item,
|
||||||
icon: iconMap[item.dwtp] || 'icon-dwsjhQita' // 根据dwtp匹配icon,默认使用"其他"图标
|
icon: iconMap[item.dwtp] || 'icon-dwsjhQita' // 根据dwtp匹配icon,默认使用"其他"图标
|
||||||
}))
|
}));
|
||||||
} else {
|
} else {
|
||||||
dataJson.value = [];
|
dataJson.value = [];
|
||||||
}
|
}
|
||||||
@ -115,9 +193,9 @@ const getListData = async () => {
|
|||||||
};
|
};
|
||||||
watch(
|
watch(
|
||||||
() => JidiSelectEventStore.selectedItem,
|
() => JidiSelectEventStore.selectedItem,
|
||||||
(newVal) => {
|
newVal => {
|
||||||
baseid.value = newVal.wbsCode;
|
baseid.value = newVal.wbsCode;
|
||||||
getListData()
|
getListData();
|
||||||
},
|
},
|
||||||
{ deep: true, immediate: true }
|
{ deep: true, immediate: true }
|
||||||
);
|
);
|
||||||
@ -132,18 +210,28 @@ watch(
|
|||||||
// };
|
// };
|
||||||
const imgclick = (item: DataString) => {
|
const imgclick = (item: DataString) => {
|
||||||
// 点击图片处理逻辑
|
// 点击图片处理逻辑
|
||||||
console.log(item)
|
console.log(item);
|
||||||
selectedItem.value = item;
|
selectedItem.value = item;
|
||||||
modalVisible.value = true;
|
modalVisible.value = true;
|
||||||
}
|
};
|
||||||
|
|
||||||
// 模态框关闭处理
|
// 模态框关闭处理
|
||||||
const handleModalClose = () => {
|
const handleModalClose = () => {
|
||||||
// 重置子组件的搜索状态
|
// 重置子组件的搜索状态
|
||||||
if (twoLayersRef.value && twoLayersRef.value.resetSearchState) {
|
if (twoLayersRef.value && twoLayersRef.value.resetSearchState) {
|
||||||
twoLayersRef.value.resetSearchState()
|
twoLayersRef.value.resetSearchState();
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
|
// 进度条百分比:已接入 / 总数
|
||||||
|
const getPercent = (item: DataString) => {
|
||||||
|
const total = Number(item.totalNum);
|
||||||
|
const access = Number(item.accessNum);
|
||||||
|
if (!total || total <= 0) return 0;
|
||||||
|
return Math.min(Math.round((access / total) * 100), 100);
|
||||||
|
};
|
||||||
|
// 进度条颜色:与已接入数字颜色保持一致
|
||||||
|
const getBarColor = (index: number) => (index === 0 ? '#2d6ac8' : '#ff9900');
|
||||||
|
|
||||||
// 页面加载时执行的逻辑
|
// 页面加载时执行的逻辑
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
@ -157,51 +245,99 @@ onMounted(() => {
|
|||||||
width: 406px;
|
width: 406px;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
overflow-x: auto;
|
gap: 10px;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
.facility-card0,
|
||||||
.facility-card {
|
.facility-card1 {
|
||||||
width: 95.5px;
|
position: relative;
|
||||||
height: 158px;
|
width: 100%;
|
||||||
border: 1px solid rgb(229, 236, 245);
|
background-color: #f3f5fa;
|
||||||
border-radius: 2px;
|
padding: 14px;
|
||||||
|
border-radius: 4px;
|
||||||
|
.img_title {
|
||||||
|
width: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
.img_num {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-top: 10px;
|
||||||
|
color: #949494;
|
||||||
|
font-size: 10px;
|
||||||
|
span {
|
||||||
|
margin-left: 5px;
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
.img_num_span1 {
|
||||||
|
color: #2d6ac8;
|
||||||
|
}
|
||||||
|
.img_num_span2 {
|
||||||
|
color: #000000;
|
||||||
|
}
|
||||||
|
.img_num_span3 {
|
||||||
|
color: #ff9900;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.img_sider {
|
||||||
|
height: 7px;
|
||||||
|
width: 100%;
|
||||||
|
border-radius: 5px;
|
||||||
|
background-color: #e5ecf5;
|
||||||
|
overflow: hidden;
|
||||||
|
cursor: default;
|
||||||
|
margin-top: 10px;
|
||||||
|
.img_sider_inner {
|
||||||
|
height: 100%;
|
||||||
|
border-radius: 3px;
|
||||||
|
transition: width 0.3s ease;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.img_icon {
|
.img_icon {
|
||||||
margin-top: 25px;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
width: 40px;
|
width: 40px;
|
||||||
height: 40px;
|
height: 40px;
|
||||||
border-radius: 40px;
|
border-radius: 14px;
|
||||||
background-color: rgb(83, 137, 181);
|
background-color: #e2e9f8;
|
||||||
line-height: 40px;
|
line-height: 40px;
|
||||||
color: rgb(255, 255, 255);
|
color: #2652a0;
|
||||||
}
|
i {
|
||||||
|
font-size: 22px;
|
||||||
.img_text {
|
|
||||||
margin-top: 20px;
|
|
||||||
font-size: 14px;
|
|
||||||
color: #333;
|
|
||||||
|
|
||||||
.text_num {
|
|
||||||
width: 100%;
|
|
||||||
margin-top: 3px;
|
|
||||||
text-align: center;
|
|
||||||
// font-size: 16px;
|
|
||||||
color: #2f6b98;
|
|
||||||
font-size: 18px;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.facility-card2,
|
||||||
|
.facility-card3 {
|
||||||
|
position: relative;
|
||||||
|
width: 198px;
|
||||||
|
background-color: #f3f5fa;
|
||||||
|
padding: 14px;
|
||||||
|
border-radius: 4px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
.img_text1 {
|
||||||
|
font-size: 16px;
|
||||||
|
color: #363636;
|
||||||
|
}
|
||||||
|
.img_text2 {
|
||||||
|
color: #000000;
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: bold;
|
||||||
|
margin: 4px 0;
|
||||||
|
}
|
||||||
|
.img_text3 {
|
||||||
|
color: #949494;
|
||||||
|
font-size: 10px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.ant-spin-nested-loading) {
|
|
||||||
height: 158px !important;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal-content {
|
.modal-content {
|
||||||
|
|||||||
@ -116,27 +116,11 @@ const VPInfo = [
|
|||||||
value: 'stnm',
|
value: 'stnm',
|
||||||
col: 24
|
col: 24
|
||||||
},
|
},
|
||||||
{
|
|
||||||
label: '站址',
|
|
||||||
value: 'stlc',
|
|
||||||
col: 24
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
label: '所属流域',
|
label: '所属流域',
|
||||||
value: 'hbrvcdName',
|
value: 'hbrvcdName',
|
||||||
col: 24
|
col: 24
|
||||||
},
|
},
|
||||||
{
|
|
||||||
label: '建成日期',
|
|
||||||
value: 'jcdt',
|
|
||||||
type: 'date',
|
|
||||||
col: 12
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: '面积(km²)',
|
|
||||||
value: 'area',
|
|
||||||
col: 12
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
label: '保护对象',
|
label: '保护对象',
|
||||||
value: 'protObj',
|
value: 'protObj',
|
||||||
@ -154,22 +138,11 @@ const VAInfo = [
|
|||||||
value: 'stnm',
|
value: 'stnm',
|
||||||
col: 24
|
col: 24
|
||||||
},
|
},
|
||||||
{
|
|
||||||
label: '站址',
|
|
||||||
value: 'stlc',
|
|
||||||
col: 24
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
label: '所属流域',
|
label: '所属流域',
|
||||||
value: 'hbrvcdName',
|
value: 'hbrvcdName',
|
||||||
col: 24
|
col: 24
|
||||||
},
|
},
|
||||||
{
|
|
||||||
label: '建成日期',
|
|
||||||
value: 'jcdt',
|
|
||||||
type: 'date',
|
|
||||||
col: 12
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
label: '保护对象',
|
label: '保护对象',
|
||||||
value: 'protobj',
|
value: 'protobj',
|
||||||
|
|||||||
@ -79,7 +79,6 @@ watch(
|
|||||||
async newVal => {
|
async newVal => {
|
||||||
if (!newVal?.stcd) return;
|
if (!newVal?.stcd) return;
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
console.log(newVal);
|
|
||||||
try {
|
try {
|
||||||
logo.value = '';
|
logo.value = '';
|
||||||
const res = await getStcdDetail(
|
const res = await getStcdDetail(
|
||||||
@ -87,7 +86,6 @@ watch(
|
|||||||
newVal.stcd
|
newVal.stcd
|
||||||
);
|
);
|
||||||
const data = res.data.msStbprpT || {};
|
const data = res.data.msStbprpT || {};
|
||||||
console.log(data);
|
|
||||||
if (data.logo) {
|
if (data.logo) {
|
||||||
logo.value = baseUrl + '/?' + data.logo;
|
logo.value = baseUrl + '/?' + data.logo;
|
||||||
} else {
|
} else {
|
||||||
@ -96,13 +94,17 @@ watch(
|
|||||||
if (data.sttpCode == 'ENG') {
|
if (data.sttpCode == 'ENG') {
|
||||||
data.sttp = data.sttpCode;
|
data.sttp = data.sttpCode;
|
||||||
}
|
}
|
||||||
|
if (data.sttp && data.sttp.includes('_')) {
|
||||||
const code = data.sttp.split('_')[0];
|
const code = data.sttp.split('_')[0];
|
||||||
formDiv.value = moduleMap[code + 'Info'];
|
formDiv.value = moduleMap[code + 'Info'] || null;
|
||||||
|
} else {
|
||||||
|
// 处理没有下划线的情况
|
||||||
|
formDiv.value = moduleMap[data.sttp + 'Info'];
|
||||||
|
}
|
||||||
|
|
||||||
if (data.ttpwr) {
|
if (data.ttpwr) {
|
||||||
data.ttpwr = data.ttpwr / 1000;
|
data.ttpwr = data.ttpwr / 1000;
|
||||||
}
|
}
|
||||||
console.log(data);
|
|
||||||
info.value = data;
|
info.value = data;
|
||||||
} finally {
|
} finally {
|
||||||
loading.value = false;
|
loading.value = false;
|
||||||
@ -110,13 +112,14 @@ watch(
|
|||||||
},
|
},
|
||||||
{ immediate: true, deep: true }
|
{ immediate: true, deep: true }
|
||||||
);
|
);
|
||||||
|
|
||||||
const iconClick = () => {
|
const iconClick = () => {
|
||||||
if (info.value.sttpMap == 'ylfb') {
|
if (info.value.sttpMap == 'ylfb') {
|
||||||
modelStore.ylfbModalVisible = true;
|
modelStore.ylfbModalVisible = true;
|
||||||
modelStore.params = info.value;
|
modelStore.params = info.value;
|
||||||
} else {
|
} else {
|
||||||
modelStore.modalVisible = true;
|
modelStore.modalVisible = true;
|
||||||
modelStore.params = info.value;
|
modelStore.params = modelStore.selectedAnchorPoint;
|
||||||
modelStore.title = info.value.titleName || info.value.stnm;
|
modelStore.title = info.value.titleName || info.value.stnm;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,151 +0,0 @@
|
|||||||
<template>
|
|
||||||
<SidePanelItem title="电站运行过程线" :shrink="false">
|
|
||||||
<template #title-right-content>
|
|
||||||
<a-range-picker
|
|
||||||
class="w-[220px]"
|
|
||||||
v-model:value="dateRange"
|
|
||||||
format="YYYY-MM-DD"
|
|
||||||
:show-time="showTimeConfig"
|
|
||||||
:allowClear="false"
|
|
||||||
:presets="DateSetting.RangeButton.month1"
|
|
||||||
:disabled-date="disabledDate"
|
|
||||||
size="small"
|
|
||||||
@change="handleSearch"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
<a-spin :spinning="isLoading" tip="加载中...">
|
|
||||||
<div class="monitor-chart">
|
|
||||||
<MonitorInfoEcharts :data="chartData" :is-active="true" />
|
|
||||||
<a-empty
|
|
||||||
v-if="!chartData || chartData.length === 0"
|
|
||||||
description="暂无数据"
|
|
||||||
class="chart-empty"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</a-spin>
|
|
||||||
</SidePanelItem>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { ref, watch, onUpdated } from 'vue';
|
|
||||||
import dayjs, { Dayjs } from 'dayjs';
|
|
||||||
import { useUiStore } from '@/store/modules/ui';
|
|
||||||
import { getMonitorData } from '@/api/mapModal';
|
|
||||||
import { useModelStore } from '@/store/modules/model';
|
|
||||||
import SidePanelItem from '@/components/SidePanelItem/index.vue';
|
|
||||||
import MonitorInfoEcharts from '@/components/MapModal/componentsEcharts/MonitorInfoEcharts.vue';
|
|
||||||
import { DateSetting } from '@/utils/enumeration';
|
|
||||||
|
|
||||||
const uiStore = useUiStore();
|
|
||||||
const modelStore = useModelStore();
|
|
||||||
|
|
||||||
const isLoading = ref(false);
|
|
||||||
const chartData = ref<any[]>([]);
|
|
||||||
|
|
||||||
// 时间选择器配置(默认参数与 MonitorInfo 保持一致)
|
|
||||||
const showTimeConfig = {
|
|
||||||
format: 'HH:mm',
|
|
||||||
hourStep: 1,
|
|
||||||
minuteStep: 5,
|
|
||||||
secondStep: 60
|
|
||||||
};
|
|
||||||
const disabledDate = (current: Dayjs) =>
|
|
||||||
current && current.isAfter(dayjs(), 'day');
|
|
||||||
|
|
||||||
const initDateRange = (): [Dayjs, Dayjs] => {
|
|
||||||
return [dayjs().subtract(1, 'month'), dayjs()];
|
|
||||||
};
|
|
||||||
const dateRange = ref<[Dayjs, Dayjs] | undefined>(initDateRange());
|
|
||||||
|
|
||||||
// 数据请求(查询参数取 modelStore.selectedAnchorPoint)
|
|
||||||
const fetchData = async () => {
|
|
||||||
const stcd = modelStore.selectedAnchorPoint?.stcd;
|
|
||||||
if (!dateRange.value || !stcd) return;
|
|
||||||
isLoading.value = true;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const filterParams = {
|
|
||||||
filter: {
|
|
||||||
logic: 'and',
|
|
||||||
filters: [
|
|
||||||
{
|
|
||||||
field: 'stcd',
|
|
||||||
operator: 'eq',
|
|
||||||
dataType: 'string',
|
|
||||||
value: stcd
|
|
||||||
},
|
|
||||||
{
|
|
||||||
field: 'tm',
|
|
||||||
operator: 'gte',
|
|
||||||
dataType: 'date',
|
|
||||||
value: dateRange.value[0].format('YYYY-MM-DD HH:mm:ss')
|
|
||||||
},
|
|
||||||
{
|
|
||||||
field: 'tm',
|
|
||||||
operator: 'lte',
|
|
||||||
dataType: 'date',
|
|
||||||
value: dateRange.value[1].format('YYYY-MM-DD HH:mm:ss')
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
sort: [{ field: 'tm', dir: 'asc' }]
|
|
||||||
};
|
|
||||||
|
|
||||||
const res = await getMonitorData(filterParams);
|
|
||||||
const rawData = res?.data?.data || res?.data?.records || [];
|
|
||||||
|
|
||||||
chartData.value = rawData;
|
|
||||||
} catch (error) {
|
|
||||||
console.error('获取数据失败:', error);
|
|
||||||
chartData.value = [];
|
|
||||||
} finally {
|
|
||||||
isLoading.value = false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 时间选择器 change 触发查询
|
|
||||||
const handleSearch = () => fetchData();
|
|
||||||
watch(
|
|
||||||
() => uiStore.searchDrawerOpen,
|
|
||||||
newVal => {
|
|
||||||
if (!newVal) {
|
|
||||||
dateRange.value = initDateRange();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
// 选中点位变化时刷新;初始已有点位时立即加载
|
|
||||||
watch(
|
|
||||||
() => modelStore.selectedAnchorPoint?.stcd,
|
|
||||||
(newStcd, oldStcd) => {
|
|
||||||
if (!newStcd) return;
|
|
||||||
if (newStcd !== oldStcd) fetchData();
|
|
||||||
},
|
|
||||||
{ immediate: true }
|
|
||||||
);
|
|
||||||
|
|
||||||
// 全局时间范围变化时同步本地日期选择器(默认参数联动)
|
|
||||||
watch(
|
|
||||||
() => modelStore.filter.rangeTm,
|
|
||||||
newRange => {
|
|
||||||
if (Array.isArray(newRange) && newRange.length > 0) {
|
|
||||||
dateRange.value = [dayjs(newRange[0]), dayjs(newRange[1])];
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{ deep: true }
|
|
||||||
);
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.monitor-chart {
|
|
||||||
position: relative;
|
|
||||||
width: 100%;
|
|
||||||
height: 400px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.chart-empty {
|
|
||||||
position: absolute;
|
|
||||||
top: 50%;
|
|
||||||
left: 50%;
|
|
||||||
transform: translate(-50%, -50%);
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@ -0,0 +1,321 @@
|
|||||||
|
<template>
|
||||||
|
<SidePanelItem title="监测数据" :shrink="false">
|
||||||
|
<template #title-right-content>
|
||||||
|
<a-date-picker
|
||||||
|
class="w-[110px]"
|
||||||
|
v-model:value="year"
|
||||||
|
picker="year"
|
||||||
|
format="YYYY"
|
||||||
|
value-format="YYYY"
|
||||||
|
:allowClear="false"
|
||||||
|
size="small"
|
||||||
|
@change="handleSearch"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<!-- 选中行的放生照片走马灯,展示在表格上方 10px 外边距 -->
|
||||||
|
<a-spin :spinning="imageLoading" tip="加载中...">
|
||||||
|
<a-carousel
|
||||||
|
v-if="imageUrls.length > 0"
|
||||||
|
class="animal-carousel"
|
||||||
|
dots-class="carousel-dots"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
v-for="(url, index) in imageUrls"
|
||||||
|
:key="index"
|
||||||
|
class="carousel-item"
|
||||||
|
>
|
||||||
|
<a-image :src="url" alt="放生照片" class="carousel-img" />
|
||||||
|
</div>
|
||||||
|
</a-carousel>
|
||||||
|
</a-spin>
|
||||||
|
<div class="monitor-table">
|
||||||
|
<BasicTable
|
||||||
|
ref="tableRef"
|
||||||
|
:scrollY="300"
|
||||||
|
:scrollX="tableScrollX"
|
||||||
|
:columns="tableColumns"
|
||||||
|
:list-url="getTableList"
|
||||||
|
:transform-data="transformTableData"
|
||||||
|
:enableRowHighlight="true"
|
||||||
|
row-key="_rowKey"
|
||||||
|
:paginationConfig="{
|
||||||
|
showSizeChanger: false,
|
||||||
|
showQuickJumper: false
|
||||||
|
}"
|
||||||
|
@row-click="handleRowClick"
|
||||||
|
@data-loaded="handleDataLoaded"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</SidePanelItem>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { ref, watch } from 'vue';
|
||||||
|
import { useUiStore } from '@/store/modules/ui';
|
||||||
|
import { getNormal2Year, getNormal2List, getIdUrl } from '@/api/mapModal';
|
||||||
|
import { useModelStore } from '@/store/modules/model';
|
||||||
|
import SidePanelItem from '@/components/SidePanelItem/index.vue';
|
||||||
|
import BasicTable from '@/components/BasicTable/index.vue';
|
||||||
|
|
||||||
|
const uiStore = useUiStore();
|
||||||
|
const modelStore = useModelStore();
|
||||||
|
|
||||||
|
const DEFAULT_PAGE_SIZE = 20;
|
||||||
|
|
||||||
|
const tableRef = ref();
|
||||||
|
const year = ref<string>('');
|
||||||
|
const tableColumns = ref([
|
||||||
|
{
|
||||||
|
title: '种类',
|
||||||
|
dataIndex: 'tetp',
|
||||||
|
width: 90
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '放生日期',
|
||||||
|
dataIndex: 'tm',
|
||||||
|
width: 88
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '救助数量(只)',
|
||||||
|
dataIndex: 'tecnt',
|
||||||
|
width: 98
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '放生地点',
|
||||||
|
dataIndex: 'stlc'
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
const tableScrollX = ref(320);
|
||||||
|
|
||||||
|
// ==================== 放生照片走马灯 ====================
|
||||||
|
const imageLoading = ref(false);
|
||||||
|
const imageUrls = ref<string[]>([]);
|
||||||
|
|
||||||
|
const IMAGE_EXTENSIONS = ['.jpg', '.jpeg', '.png', '.gif', '.webp', '.bmp'];
|
||||||
|
|
||||||
|
const loadImages = async (record: any) => {
|
||||||
|
imageUrls.value = [];
|
||||||
|
if (!record) return;
|
||||||
|
const fids = String(record?.fid || '')
|
||||||
|
.split(',')
|
||||||
|
.map((item: string) => item.trim())
|
||||||
|
.filter(Boolean);
|
||||||
|
if (fids.length === 0) return;
|
||||||
|
imageLoading.value = true;
|
||||||
|
try {
|
||||||
|
const res: any = await getIdUrl({ id: fids.join(',') });
|
||||||
|
const data = res?.data || {};
|
||||||
|
imageUrls.value = fids
|
||||||
|
.map((fid: string) => data?.[fid])
|
||||||
|
.filter(
|
||||||
|
(item: any) =>
|
||||||
|
item &&
|
||||||
|
IMAGE_EXTENSIONS.includes(String(item.ext || '').toLowerCase())
|
||||||
|
)
|
||||||
|
.map((item: any) => item.fullpath)
|
||||||
|
.filter(Boolean);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取放生照片失败:', error);
|
||||||
|
imageUrls.value = [];
|
||||||
|
} finally {
|
||||||
|
imageLoading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleRowClick = (record: any) => {
|
||||||
|
loadImages(record);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 数据加载完成后默认展示第一条的照片;无数据时清空图片
|
||||||
|
const handleDataLoaded = (params: any, data: any) => {
|
||||||
|
const records = data?.records || [];
|
||||||
|
if (records.length > 0) {
|
||||||
|
loadImages(records[0]);
|
||||||
|
} else {
|
||||||
|
imageUrls.value = [];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// ==================== 年份与数据查询 ====================
|
||||||
|
const getDefaultYear = (yearList: any[]) => {
|
||||||
|
const firstItem = yearList?.[0];
|
||||||
|
if (typeof firstItem === 'string' || typeof firstItem === 'number') {
|
||||||
|
return String(firstItem);
|
||||||
|
}
|
||||||
|
return firstItem?.yr || firstItem?.tm || firstItem?.year || null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getYearRange = (yearValue: string) => ({
|
||||||
|
startDate: `${yearValue}-01-01 00:00:00`,
|
||||||
|
endDate: `${yearValue}-12-31 23:59:59`
|
||||||
|
});
|
||||||
|
|
||||||
|
const transformTableData = (res: any) => {
|
||||||
|
const records = res?.data?.records || res?.data?.data || res?.data || [];
|
||||||
|
const total = res?.data?.data?.total || res?.data?.total || res?.total || 0;
|
||||||
|
|
||||||
|
return {
|
||||||
|
records: records.map((item: any, index: number) => ({
|
||||||
|
...item,
|
||||||
|
_rowKey:
|
||||||
|
item?.id ||
|
||||||
|
item?.fid ||
|
||||||
|
`${item?.stcd || 'stcd'}-${item?.tm || year.value || 'tm'}-${index}`
|
||||||
|
})),
|
||||||
|
total
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const buildFilter = () => ({
|
||||||
|
logic: 'and',
|
||||||
|
filters: [
|
||||||
|
{
|
||||||
|
field: 'stcd',
|
||||||
|
operator: 'eq',
|
||||||
|
dataType: 'string',
|
||||||
|
value: modelStore.selectedAnchorPoint?.stcd
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'tm',
|
||||||
|
operator: 'gte',
|
||||||
|
dataType: 'date',
|
||||||
|
value: getYearRange(year.value).startDate
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'tm',
|
||||||
|
operator: 'lte',
|
||||||
|
dataType: 'date',
|
||||||
|
value: getYearRange(year.value).endDate
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
const getTableList = (params: any) => {
|
||||||
|
const take = Number(params?.take || DEFAULT_PAGE_SIZE);
|
||||||
|
const currentPage = Number(params?.skip || 1);
|
||||||
|
const skip = Math.max(currentPage - 1, 0) * take;
|
||||||
|
|
||||||
|
return getNormal2List({
|
||||||
|
...params,
|
||||||
|
take,
|
||||||
|
skip,
|
||||||
|
filter: params?.filter || buildFilter()
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const refreshTable = () => {
|
||||||
|
if (!modelStore.selectedAnchorPoint?.stcd || !year.value) return;
|
||||||
|
tableRef.value?.getList(buildFilter());
|
||||||
|
};
|
||||||
|
|
||||||
|
// 获取年份列表并赋值默认年份(年份列表第一个),然后自动查询
|
||||||
|
const getYearList = async () => {
|
||||||
|
const stcd = modelStore.selectedAnchorPoint?.stcd;
|
||||||
|
if (!stcd) return;
|
||||||
|
try {
|
||||||
|
const res: any = await getNormal2Year({
|
||||||
|
filter: {
|
||||||
|
logic: 'and',
|
||||||
|
filters: [
|
||||||
|
{
|
||||||
|
field: 'stcd',
|
||||||
|
operator: 'eq',
|
||||||
|
dataType: 'string',
|
||||||
|
value: stcd
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const yearData = res?.data?.data || res?.data?.records || res?.data || [];
|
||||||
|
const defaultYear =
|
||||||
|
getDefaultYear(yearData) || String(new Date().getFullYear());
|
||||||
|
|
||||||
|
year.value = String(defaultYear);
|
||||||
|
refreshTable();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取年份列表失败:', error);
|
||||||
|
if (tableRef.value) {
|
||||||
|
tableRef.value.loading = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSearch = () => {
|
||||||
|
if (!year.value) return;
|
||||||
|
imageUrls.value = []; // 切换年份时先清空图片,避免加载期间显示旧数据图片
|
||||||
|
refreshTable();
|
||||||
|
};
|
||||||
|
|
||||||
|
// ==================== 联动 ====================
|
||||||
|
watch(
|
||||||
|
() => uiStore.searchDrawerOpen,
|
||||||
|
newVal => {
|
||||||
|
if (!newVal) {
|
||||||
|
year.value = '';
|
||||||
|
imageUrls.value = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// 选中点位变化时重新加载;初始已有点位时立即加载
|
||||||
|
watch(
|
||||||
|
() => modelStore.selectedAnchorPoint?.stcd,
|
||||||
|
(newStcd, oldStcd) => {
|
||||||
|
if (!newStcd) return;
|
||||||
|
if (newStcd !== oldStcd) {
|
||||||
|
year.value = '';
|
||||||
|
imageUrls.value = [];
|
||||||
|
getYearList();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ immediate: true }
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.animal-carousel {
|
||||||
|
margin-top: 10px;
|
||||||
|
// height: 300px;
|
||||||
|
background: #f5f5f5;
|
||||||
|
|
||||||
|
// a-carousel 内部滑块为库生成元素,需穿透样式撑满高度并居中
|
||||||
|
::v-deep(.slick-list),
|
||||||
|
::v-deep(.slick-track),
|
||||||
|
::v-deep(.slick-slide) {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
::v-deep(.slick-slide > div) {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.carousel-item {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
:deep(.ant-image) {
|
||||||
|
max-width: 100%;
|
||||||
|
max-height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.ant-image-img) {
|
||||||
|
max-width: 100%;
|
||||||
|
max-height: 100%;
|
||||||
|
object-fit: contain;
|
||||||
|
cursor: zoom-in;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.monitor-table {
|
||||||
|
margin-top: 10px;
|
||||||
|
width: 410px;
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -0,0 +1,417 @@
|
|||||||
|
<template>
|
||||||
|
<SidePanelItem title="生态流量泄放设施" :shrink="false">
|
||||||
|
<template #title-right-content>
|
||||||
|
<a-range-picker
|
||||||
|
class="w-[220px]"
|
||||||
|
v-model:value="dateRange"
|
||||||
|
format="YYYY-MM-DD HH:mm"
|
||||||
|
:show-time="showTimeConfig"
|
||||||
|
:allowClear="false"
|
||||||
|
:presets="DateSetting.RangeButton.month1"
|
||||||
|
:disabled-date="disabledDate"
|
||||||
|
size="small"
|
||||||
|
@change="handleSearch"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<a-spin :spinning="isLoading" tip="加载中...">
|
||||||
|
<div class="monitor-chart">
|
||||||
|
<div ref="chartRef" class="chart-container"></div>
|
||||||
|
<a-empty
|
||||||
|
v-if="!chartData || chartData.length === 0"
|
||||||
|
description="暂无数据"
|
||||||
|
class="chart-empty"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="monitor-table">
|
||||||
|
<BasicTable
|
||||||
|
:scrollY="300"
|
||||||
|
:scrollX="tableScrollX"
|
||||||
|
:columns="tableColumns"
|
||||||
|
:data="tableData"
|
||||||
|
:paginationConfig="{
|
||||||
|
showSizeChanger: false,
|
||||||
|
showQuickJumper: false
|
||||||
|
}"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</a-spin>
|
||||||
|
</SidePanelItem>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { ref, watch, onMounted, onBeforeUnmount, nextTick } from 'vue';
|
||||||
|
import dayjs, { Dayjs } from 'dayjs';
|
||||||
|
import * as echarts from 'echarts';
|
||||||
|
import { useUiStore } from '@/store/modules/ui';
|
||||||
|
import { getFlowDischargeYear, getFlowDischargeList } from '@/api/mapModal';
|
||||||
|
import { useModelStore } from '@/store/modules/model';
|
||||||
|
import SidePanelItem from '@/components/SidePanelItem/index.vue';
|
||||||
|
import BasicTable from '@/components/BasicTable/index.vue';
|
||||||
|
import { DateSetting } from '@/utils/enumeration';
|
||||||
|
|
||||||
|
const uiStore = useUiStore();
|
||||||
|
const modelStore = useModelStore();
|
||||||
|
|
||||||
|
const isLoading = ref(false);
|
||||||
|
const tableData = ref<any[]>([]);
|
||||||
|
const chartData = ref<any[]>([]);
|
||||||
|
const chartRef = ref<HTMLElement>();
|
||||||
|
let chartInstance: echarts.ECharts | null = null;
|
||||||
|
let resizeObserver: ResizeObserver | null = null;
|
||||||
|
|
||||||
|
// ==================== 时间选择器配置 ====================
|
||||||
|
const showTimeConfig = {
|
||||||
|
format: 'HH:mm',
|
||||||
|
hourStep: 1,
|
||||||
|
minuteStep: 5,
|
||||||
|
secondStep: 60
|
||||||
|
};
|
||||||
|
const disabledDate = (current: Dayjs) =>
|
||||||
|
current && current.isAfter(dayjs(), 'day');
|
||||||
|
|
||||||
|
const initDateRange = (): [Dayjs, Dayjs] => {
|
||||||
|
return [dayjs().subtract(7, 'day'), dayjs()];
|
||||||
|
};
|
||||||
|
const dateRange = ref<[Dayjs, Dayjs] | undefined>(initDateRange());
|
||||||
|
|
||||||
|
// ==================== 表格 ====================
|
||||||
|
const tableColumns = [
|
||||||
|
{
|
||||||
|
title: '时间',
|
||||||
|
dataIndex: 'tm',
|
||||||
|
width: 180,
|
||||||
|
fixed: 'left',
|
||||||
|
customRender: ({ text }: any) =>
|
||||||
|
text ? dayjs(text).format('YYYY-MM-DD HH:mm') : '-'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '流量(m³/s)',
|
||||||
|
dataIndex: 'q',
|
||||||
|
width: 130,
|
||||||
|
customRender: ({ text }: any) =>
|
||||||
|
text !== undefined && text !== null ? Number(text) : '-'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
const tableScrollX = tableColumns.reduce((s, c) => s + (c.width || 100), 0);
|
||||||
|
|
||||||
|
// ==================== 图表(流量单线) ====================
|
||||||
|
const initChart = () => {
|
||||||
|
if (!chartRef.value) return;
|
||||||
|
if (chartInstance) chartInstance.dispose();
|
||||||
|
chartInstance = echarts.init(chartRef.value);
|
||||||
|
updateChart(chartData.value);
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateChart = (data: any[]) => {
|
||||||
|
if (!chartInstance) return;
|
||||||
|
if (!data || data.length === 0) {
|
||||||
|
chartInstance.clear();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const sorted = [...data].sort(
|
||||||
|
(a, b) => new Date(a.tm).getTime() - new Date(b.tm).getTime()
|
||||||
|
);
|
||||||
|
|
||||||
|
const xAxisData = sorted.map(item => {
|
||||||
|
const hm = dayjs(item.tm).format('HH:mm');
|
||||||
|
if (hm === '00:00') {
|
||||||
|
const date = dayjs(item.tm).format('MM-DD');
|
||||||
|
return `${hm}\n${date}`;
|
||||||
|
}
|
||||||
|
return hm;
|
||||||
|
});
|
||||||
|
|
||||||
|
const qData = sorted.map(item => item.q);
|
||||||
|
|
||||||
|
const option = {
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'axis',
|
||||||
|
backgroundColor: 'rgba(50, 50, 50, 0.9)',
|
||||||
|
textStyle: { color: '#fff', fontSize: 12 },
|
||||||
|
axisPointer: { type: 'cross' },
|
||||||
|
formatter: (params: any) => {
|
||||||
|
if (!params || params.length === 0) return '';
|
||||||
|
const dataIndex = params[0].dataIndex;
|
||||||
|
const fullTime = sorted[dataIndex]?.tm
|
||||||
|
? dayjs(sorted[dataIndex].tm).format('YYYY-MM-DD HH:mm:ss')
|
||||||
|
: '';
|
||||||
|
let html = `<div style="font-size:16px;margin-bottom:8px;">${fullTime}</div>`;
|
||||||
|
params.forEach((param: any) => {
|
||||||
|
if (param.value == null) return;
|
||||||
|
const displayValue = Number(param.value).toFixed(2);
|
||||||
|
html += `
|
||||||
|
<div style="display:flex;align-items:center;justify-content:space-between;margin:4px 0;">
|
||||||
|
<span style="display:inline-block;width:10px;height:10px;border-radius:50%;background:${param.color};margin-right:8px;"></span>
|
||||||
|
<span style="flex:1;font-size:14px;text-align:left;margin-right:6px;">${param.seriesName}: </span>
|
||||||
|
<span style="font-size:14px;min-width:60px;text-align:right;"><strong>${displayValue}</strong> (m³/s)</span>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
});
|
||||||
|
return html;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
type: 'scroll',
|
||||||
|
top: 10,
|
||||||
|
data: ['流量'],
|
||||||
|
textStyle: { fontSize: 12 }
|
||||||
|
},
|
||||||
|
grid: { left: 50, right: 20, top: 44, bottom: 50 },
|
||||||
|
xAxis: {
|
||||||
|
type: 'category',
|
||||||
|
data: xAxisData,
|
||||||
|
axisLine: { lineStyle: { color: '#000000' } },
|
||||||
|
axisTick: { show: false },
|
||||||
|
axisLabel: { fontSize: 12 },
|
||||||
|
splitLine: {
|
||||||
|
show: true,
|
||||||
|
lineStyle: { color: '#bfbfbf', type: 'solid' }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
yAxis: {
|
||||||
|
name: '流量(m³/s)',
|
||||||
|
type: 'value',
|
||||||
|
position: 'left',
|
||||||
|
axisLine: { lineStyle: { color: '#000000' } },
|
||||||
|
scale: true,
|
||||||
|
splitNumber: 9
|
||||||
|
},
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
name: '流量',
|
||||||
|
type: 'line',
|
||||||
|
data: qData,
|
||||||
|
smooth: true,
|
||||||
|
symbol: 'circle',
|
||||||
|
symbolSize: 4,
|
||||||
|
lineStyle: { color: '#12C1EA', width: 2 },
|
||||||
|
itemStyle: { color: '#12C1EA' }
|
||||||
|
}
|
||||||
|
],
|
||||||
|
dataZoom: [
|
||||||
|
{
|
||||||
|
type: 'inside',
|
||||||
|
xAxisIndex: [0],
|
||||||
|
throttle: 50,
|
||||||
|
start: 0,
|
||||||
|
end: 100
|
||||||
|
}
|
||||||
|
],
|
||||||
|
toolbox: {
|
||||||
|
show: true,
|
||||||
|
feature: {
|
||||||
|
saveAsImage: { title: '保存为图片', type: 'png', pixelRatio: 2 }
|
||||||
|
},
|
||||||
|
right: 20,
|
||||||
|
top: 10
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
chartInstance.setOption(option, true);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 数据变化时动态刷新图表
|
||||||
|
watch(chartData, newData => {
|
||||||
|
updateChart(newData);
|
||||||
|
});
|
||||||
|
|
||||||
|
const safeResize = (
|
||||||
|
chart: echarts.ECharts | null,
|
||||||
|
el: HTMLElement | undefined
|
||||||
|
) => {
|
||||||
|
if (!chart) return;
|
||||||
|
// 容器不可见(display:none / 宽度为 0)时跳过,避免收起/切换时做昂贵的同步重排
|
||||||
|
if (!el || el.clientWidth === 0) return;
|
||||||
|
chart.resize();
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
nextTick(() => {
|
||||||
|
initChart();
|
||||||
|
setTimeout(() => {
|
||||||
|
safeResize(chartInstance, chartRef.value);
|
||||||
|
}, 200);
|
||||||
|
if (!resizeObserver && chartRef.value) {
|
||||||
|
resizeObserver = new ResizeObserver(() =>
|
||||||
|
safeResize(chartInstance, chartRef.value)
|
||||||
|
);
|
||||||
|
resizeObserver.observe(chartRef.value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
window.addEventListener('resize', () =>
|
||||||
|
safeResize(chartInstance, chartRef.value)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
if (chartInstance) {
|
||||||
|
chartInstance.dispose();
|
||||||
|
chartInstance = null;
|
||||||
|
}
|
||||||
|
window.removeEventListener('resize', () =>
|
||||||
|
safeResize(chartInstance, chartRef.value)
|
||||||
|
);
|
||||||
|
if (resizeObserver) {
|
||||||
|
resizeObserver.disconnect();
|
||||||
|
resizeObserver = null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// ==================== 默认日期 ====================
|
||||||
|
// 用年份接口返回的最后一个日期作为结束日期,往前 7 天为开始日期
|
||||||
|
const fetchDefaultDate = async () => {
|
||||||
|
const stcd = modelStore.selectedAnchorPoint?.stcd;
|
||||||
|
if (!stcd) return;
|
||||||
|
try {
|
||||||
|
const filterParams = {
|
||||||
|
filter: {
|
||||||
|
logic: 'and',
|
||||||
|
filters: [
|
||||||
|
{
|
||||||
|
field: 'stcd',
|
||||||
|
operator: 'eq',
|
||||||
|
dataType: 'string',
|
||||||
|
value: stcd
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'sttpCode',
|
||||||
|
operator: 'eq',
|
||||||
|
dataType: 'string',
|
||||||
|
value: 'EQ'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const res = await getFlowDischargeYear(filterParams);
|
||||||
|
const dates = res?.data?.data || [];
|
||||||
|
|
||||||
|
if (dates && dates.length > 0) {
|
||||||
|
// 使用最后一个日期作为结束日期
|
||||||
|
const endDate = dayjs(dates[dates.length - 1]);
|
||||||
|
const startDate = endDate.subtract(7, 'day');
|
||||||
|
dateRange.value = [startDate, endDate];
|
||||||
|
} else {
|
||||||
|
// 没有数据默认当前天
|
||||||
|
dateRange.value = [dayjs().subtract(7, 'day'), dayjs()];
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取默认日期失败:', error);
|
||||||
|
// 失败时使用默认前7天
|
||||||
|
dateRange.value = [dayjs().subtract(7, 'day'), dayjs()];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// ==================== 数据请求 ====================
|
||||||
|
const fetchData = async () => {
|
||||||
|
const stcd = modelStore.selectedAnchorPoint?.stcd;
|
||||||
|
if (!dateRange.value || !stcd) return;
|
||||||
|
isLoading.value = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const filterParams = {
|
||||||
|
filter: {
|
||||||
|
logic: 'and',
|
||||||
|
filters: [
|
||||||
|
{
|
||||||
|
field: 'stcd',
|
||||||
|
operator: 'eq',
|
||||||
|
dataType: 'string',
|
||||||
|
value: stcd
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'tm',
|
||||||
|
operator: 'gte',
|
||||||
|
dataType: 'date',
|
||||||
|
value: dateRange.value[0].format('YYYY-MM-DD HH:mm:ss')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'tm',
|
||||||
|
operator: 'lte',
|
||||||
|
dataType: 'date',
|
||||||
|
value: dateRange.value[1].format('YYYY-MM-DD HH:mm:ss')
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
sort: [{ field: 'tm', dir: 'asc' }]
|
||||||
|
};
|
||||||
|
|
||||||
|
const res = await getFlowDischargeList('EQ_R', filterParams);
|
||||||
|
const rawData = res?.data?.data || res?.data?.records || [];
|
||||||
|
|
||||||
|
tableData.value = [...rawData].reverse();
|
||||||
|
chartData.value = rawData;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取数据失败:', error);
|
||||||
|
tableData.value = [];
|
||||||
|
chartData.value = [];
|
||||||
|
} finally {
|
||||||
|
isLoading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 时间选择器 change 触发查询
|
||||||
|
const handleSearch = () => fetchData();
|
||||||
|
|
||||||
|
// ==================== 联动 ====================
|
||||||
|
watch(
|
||||||
|
() => uiStore.searchDrawerOpen,
|
||||||
|
newVal => {
|
||||||
|
if (!newVal) {
|
||||||
|
dateRange.value = initDateRange();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// 选中点位变化时重新加载;初始已有点位时立即加载
|
||||||
|
watch(
|
||||||
|
() => modelStore.selectedAnchorPoint?.stcd,
|
||||||
|
async (newStcd, oldStcd) => {
|
||||||
|
if (!newStcd) return;
|
||||||
|
if (newStcd !== oldStcd) {
|
||||||
|
dateRange.value = initDateRange();
|
||||||
|
await fetchDefaultDate();
|
||||||
|
fetchData();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ immediate: true }
|
||||||
|
);
|
||||||
|
|
||||||
|
// 全局时间范围变化时同步本地日期选择器(默认参数联动)
|
||||||
|
watch(
|
||||||
|
() => modelStore.filter.rangeTm,
|
||||||
|
newRange => {
|
||||||
|
if (Array.isArray(newRange) && newRange.length > 0) {
|
||||||
|
dateRange.value = [dayjs(newRange[0]), dayjs(newRange[1])];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ deep: true }
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.monitor-chart {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
height: 300px;
|
||||||
|
|
||||||
|
.chart-container {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-empty {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.monitor-table {
|
||||||
|
margin-top: 10px;
|
||||||
|
width: 410px;
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -0,0 +1,493 @@
|
|||||||
|
<template>
|
||||||
|
<SidePanelItem title="流量监测数据" :shrink="false">
|
||||||
|
<template #title-right-content>
|
||||||
|
<a-range-picker
|
||||||
|
class="w-[220px]"
|
||||||
|
v-model:value="dateRange"
|
||||||
|
format="YYYY-MM-DD"
|
||||||
|
:show-time="showTimeConfig"
|
||||||
|
:allowClear="false"
|
||||||
|
:presets="DateSetting.RangeButton.month1"
|
||||||
|
:disabled-date="disabledDate"
|
||||||
|
size="small"
|
||||||
|
@change="handleSearch"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<a-spin :spinning="isLoading" tip="加载中...">
|
||||||
|
<div class="monitor-chart">
|
||||||
|
<div ref="chartRef" class="chart-container"></div>
|
||||||
|
<a-empty
|
||||||
|
v-if="isDataEmpty"
|
||||||
|
description="暂无数据"
|
||||||
|
class="chart-empty"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="monitor-table">
|
||||||
|
<BasicTable
|
||||||
|
ref="tableRef"
|
||||||
|
:scrollY="240"
|
||||||
|
:scrollX="tableScrollX"
|
||||||
|
:columns="tableColumns"
|
||||||
|
:data="tableData"
|
||||||
|
:paginationConfig="{
|
||||||
|
showSizeChanger: false,
|
||||||
|
showQuickJumper: false
|
||||||
|
}"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</a-spin>
|
||||||
|
</SidePanelItem>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import {
|
||||||
|
ref,
|
||||||
|
watch,
|
||||||
|
computed,
|
||||||
|
onMounted,
|
||||||
|
onBeforeUnmount,
|
||||||
|
nextTick
|
||||||
|
} from 'vue';
|
||||||
|
import dayjs, { Dayjs } from 'dayjs';
|
||||||
|
import * as echarts from 'echarts';
|
||||||
|
import { useUiStore } from '@/store/modules/ui';
|
||||||
|
import { getFlowStationList } from '@/api/DataQueryMenuModule';
|
||||||
|
import { useModelStore } from '@/store/modules/model';
|
||||||
|
import SidePanelItem from '@/components/SidePanelItem/index.vue';
|
||||||
|
import BasicTable from '@/components/BasicTable/index.vue';
|
||||||
|
import { DateSetting } from '@/utils/enumeration';
|
||||||
|
|
||||||
|
const uiStore = useUiStore();
|
||||||
|
const modelStore = useModelStore();
|
||||||
|
|
||||||
|
const isLoading = ref(false);
|
||||||
|
const chartData = ref<any[]>([]);
|
||||||
|
const tableData = ref<any[]>([]);
|
||||||
|
const tableRef = ref<any>();
|
||||||
|
|
||||||
|
const isDataEmpty = computed(() => {
|
||||||
|
const data = Array.isArray(chartData.value) ? chartData.value : [];
|
||||||
|
return data.length === 0;
|
||||||
|
});
|
||||||
|
|
||||||
|
// ==================== 图表 ====================
|
||||||
|
const chartRef = ref<HTMLElement>();
|
||||||
|
let chartInstance: echarts.ECharts | null = null;
|
||||||
|
let resizeObserver: ResizeObserver | null = null;
|
||||||
|
|
||||||
|
const initChart = () => {
|
||||||
|
if (!chartRef.value) return;
|
||||||
|
if (chartInstance) chartInstance.dispose();
|
||||||
|
chartInstance = echarts.init(chartRef.value);
|
||||||
|
updateChart(chartData.value);
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateChart = (data: any[]) => {
|
||||||
|
if (!chartInstance) return;
|
||||||
|
if (!data || data.length === 0) {
|
||||||
|
chartInstance.clear();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const sorted = [...data].sort(
|
||||||
|
(a, b) => new Date(a.tm).getTime() - new Date(b.tm).getTime()
|
||||||
|
);
|
||||||
|
|
||||||
|
const xAxisData = sorted.map(item => {
|
||||||
|
const hm = dayjs(item.tm).format('HH:mm');
|
||||||
|
if (hm === '00:00') {
|
||||||
|
const date = dayjs(item.tm).format('MM-DD');
|
||||||
|
return `${hm}\n${date}`;
|
||||||
|
}
|
||||||
|
return hm;
|
||||||
|
});
|
||||||
|
|
||||||
|
const zData = sorted.map(item => item.z);
|
||||||
|
const qData = sorted.map(item => item.q);
|
||||||
|
const vData = sorted.map(item => item.v);
|
||||||
|
|
||||||
|
const waterValues = zData.filter(v => v !== null && v !== undefined);
|
||||||
|
const waterMin = waterValues.length > 0 ? Math.min(...waterValues) : 0;
|
||||||
|
const waterMax = waterValues.length > 0 ? Math.max(...waterValues) : 10;
|
||||||
|
|
||||||
|
const flowValues = [...qData, ...vData].filter(
|
||||||
|
v => v !== null && v !== undefined
|
||||||
|
);
|
||||||
|
const flowMin = flowValues.length > 0 ? Math.min(...flowValues) : 0;
|
||||||
|
const flowMax = flowValues.length > 0 ? Math.max(...flowValues) : 10;
|
||||||
|
|
||||||
|
const formatRules: Record<
|
||||||
|
string,
|
||||||
|
{ format: (v: number) => string; unit: string }
|
||||||
|
> = {
|
||||||
|
水位: { format: v => v.toFixed(2), unit: '(m)' },
|
||||||
|
流量: { format: v => String(Math.round(v)), unit: '(m³/s)' },
|
||||||
|
流速: { format: v => v.toFixed(2), unit: '(m³/s)' }
|
||||||
|
};
|
||||||
|
|
||||||
|
const option = {
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'axis',
|
||||||
|
backgroundColor: 'rgba(50, 50, 50, 0.9)',
|
||||||
|
textStyle: { color: '#fff', fontSize: 12 },
|
||||||
|
axisPointer: { type: 'cross' },
|
||||||
|
formatter: (params: any) => {
|
||||||
|
if (!params || params.length === 0) return '';
|
||||||
|
const dataIndex = params[0].dataIndex;
|
||||||
|
const fullTime = sorted[dataIndex]?.tm
|
||||||
|
? dayjs(sorted[dataIndex].tm).format('YYYY-MM-DD HH:mm:ss')
|
||||||
|
: '';
|
||||||
|
let html = `<div style="font-size:16px;margin-bottom:8px;">${fullTime}</div>`;
|
||||||
|
params.forEach((param: any) => {
|
||||||
|
if (param.value == null) return;
|
||||||
|
const rule = Object.entries(formatRules).find(([key]) =>
|
||||||
|
param.seriesName.includes(key)
|
||||||
|
);
|
||||||
|
const displayValue = rule
|
||||||
|
? rule[1].format(Number(param.value))
|
||||||
|
: param.value;
|
||||||
|
const unit = rule ? rule[1].unit : '';
|
||||||
|
html += `
|
||||||
|
<div style="display:flex;align-items:center;justify-content:space-between;margin:4px 0;">
|
||||||
|
<span style="display:inline-block;width:10px;height:10px;border-radius:50%;background:${param.color};margin-right:8px;"></span>
|
||||||
|
<span style="flex:1;font-size:14px;text-align:left;margin-right:6px;">${param.seriesName}: </span>
|
||||||
|
<span style="font-size:14px;min-width:60px;text-align:right;"><strong>${displayValue}</strong> ${unit}</span>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
});
|
||||||
|
return html;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
top: 0,
|
||||||
|
data: ['水位', '流量', '流速'],
|
||||||
|
textStyle: { fontSize: 12 },
|
||||||
|
selected: {
|
||||||
|
水位: true,
|
||||||
|
流量: true,
|
||||||
|
流速: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
left: 60,
|
||||||
|
right: 100,
|
||||||
|
top: 60,
|
||||||
|
bottom: 50
|
||||||
|
},
|
||||||
|
xAxis: {
|
||||||
|
type: 'category',
|
||||||
|
data: xAxisData,
|
||||||
|
axisLine: { lineStyle: { color: '#000000' } },
|
||||||
|
axisTick: { show: false },
|
||||||
|
axisLabel: { fontSize: 12 },
|
||||||
|
splitLine: {
|
||||||
|
show: true,
|
||||||
|
lineStyle: { color: '#bfbfbf', type: 'solid' }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
yAxis: [
|
||||||
|
{
|
||||||
|
name: '水位(m)',
|
||||||
|
type: 'value',
|
||||||
|
position: 'left',
|
||||||
|
axisLine: { show: true, lineStyle: { color: '#56C2E3' } },
|
||||||
|
axisLabel: { color: '#56C2E3' },
|
||||||
|
nameTextStyle: { color: '#56C2E3' },
|
||||||
|
alignTicks: true,
|
||||||
|
scale: true,
|
||||||
|
splitNumber: 9
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '流量(m³/s)',
|
||||||
|
type: 'value',
|
||||||
|
position: 'right',
|
||||||
|
axisLine: { show: true, lineStyle: { color: '#9556A4' } },
|
||||||
|
axisLabel: { color: '#9556A4' },
|
||||||
|
nameTextStyle: { color: '#9556A4' },
|
||||||
|
alignTicks: true,
|
||||||
|
scale: true,
|
||||||
|
splitNumber: 9
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '流速(m³/s)',
|
||||||
|
type: 'value',
|
||||||
|
position: 'right',
|
||||||
|
axisLine: { show: true, lineStyle: { color: '#78C300' } },
|
||||||
|
axisLabel: { color: '#78C300' },
|
||||||
|
nameTextStyle: { color: '#78C300' },
|
||||||
|
alignTicks: true,
|
||||||
|
scale: true,
|
||||||
|
splitNumber: 9,
|
||||||
|
offset: 60
|
||||||
|
}
|
||||||
|
],
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
name: '水位',
|
||||||
|
type: 'line',
|
||||||
|
yAxisIndex: 0,
|
||||||
|
data: zData,
|
||||||
|
smooth: true,
|
||||||
|
symbol: 'circle',
|
||||||
|
symbolSize: 4,
|
||||||
|
lineStyle: { color: '#56C2E3', width: 2 },
|
||||||
|
itemStyle: { color: '#56C2E3' }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '流量',
|
||||||
|
type: 'line',
|
||||||
|
yAxisIndex: 1,
|
||||||
|
data: qData,
|
||||||
|
smooth: true,
|
||||||
|
symbol: 'circle',
|
||||||
|
symbolSize: 4,
|
||||||
|
lineStyle: { color: '#9556A4', width: 2 },
|
||||||
|
itemStyle: { color: '#9556A4' }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '流速',
|
||||||
|
type: 'line',
|
||||||
|
yAxisIndex: 2,
|
||||||
|
data: vData,
|
||||||
|
smooth: true,
|
||||||
|
symbol: 'circle',
|
||||||
|
symbolSize: 4,
|
||||||
|
lineStyle: { color: '#78C300', width: 2 },
|
||||||
|
itemStyle: { color: '#78C300' }
|
||||||
|
}
|
||||||
|
],
|
||||||
|
dataZoom: [
|
||||||
|
{
|
||||||
|
type: 'inside',
|
||||||
|
xAxisIndex: [0],
|
||||||
|
throttle: 50,
|
||||||
|
start: 0,
|
||||||
|
end: 100
|
||||||
|
}
|
||||||
|
],
|
||||||
|
toolbox: {
|
||||||
|
show: true,
|
||||||
|
feature: {
|
||||||
|
saveAsImage: { title: '保存为图片', type: 'png', pixelRatio: 2 }
|
||||||
|
},
|
||||||
|
right: 20,
|
||||||
|
top: -6
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
chartInstance.setOption(option, true);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 数据变化时动态刷新图表
|
||||||
|
watch(chartData, newData => {
|
||||||
|
updateChart(newData);
|
||||||
|
});
|
||||||
|
|
||||||
|
const safeResize = () => {
|
||||||
|
if (!chartInstance) return;
|
||||||
|
// 容器不可见(display:none / 宽度为 0)时跳过,避免收起/切换时做昂贵的同步重排
|
||||||
|
if (!chartRef.value || chartRef.value.clientWidth === 0) return;
|
||||||
|
chartInstance.resize();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleResize = () => {
|
||||||
|
safeResize();
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
nextTick(() => {
|
||||||
|
initChart();
|
||||||
|
setTimeout(() => {
|
||||||
|
safeResize();
|
||||||
|
}, 200);
|
||||||
|
if (!resizeObserver && chartRef.value) {
|
||||||
|
resizeObserver = new ResizeObserver(() => {
|
||||||
|
safeResize();
|
||||||
|
});
|
||||||
|
resizeObserver.observe(chartRef.value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
window.addEventListener('resize', handleResize);
|
||||||
|
});
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
if (chartInstance) {
|
||||||
|
chartInstance.dispose();
|
||||||
|
chartInstance = null;
|
||||||
|
}
|
||||||
|
window.removeEventListener('resize', handleResize);
|
||||||
|
if (resizeObserver) {
|
||||||
|
resizeObserver.disconnect();
|
||||||
|
resizeObserver = null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 时间选择器配置
|
||||||
|
const showTimeConfig = {
|
||||||
|
format: 'HH:mm',
|
||||||
|
hourStep: 1,
|
||||||
|
minuteStep: 5,
|
||||||
|
secondStep: 60
|
||||||
|
};
|
||||||
|
const disabledDate = (current: Dayjs) =>
|
||||||
|
current && current.isAfter(dayjs(), 'day');
|
||||||
|
|
||||||
|
const initDateRange = (): [Dayjs, Dayjs] => {
|
||||||
|
return [dayjs().subtract(1, 'month'), dayjs()];
|
||||||
|
};
|
||||||
|
const dateRange = ref<[Dayjs, Dayjs] | undefined>(initDateRange());
|
||||||
|
|
||||||
|
// ==================== 表格列配置 ====================
|
||||||
|
const tableColumns = [
|
||||||
|
{
|
||||||
|
title: '时间',
|
||||||
|
dataIndex: 'tm',
|
||||||
|
width: 160,
|
||||||
|
fixed: 'left',
|
||||||
|
customRender: ({ text }: any) =>
|
||||||
|
text ? dayjs(text).format('YYYY-MM-DD HH:mm:ss') : '-'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '水位(m)',
|
||||||
|
dataIndex: 'z',
|
||||||
|
width: 120,
|
||||||
|
customRender: ({ text }: any) =>
|
||||||
|
text !== undefined && text !== null ? Number(text).toFixed(2) : '-'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '流量(m³/s)',
|
||||||
|
dataIndex: 'q',
|
||||||
|
width: 130,
|
||||||
|
customRender: ({ text }: any) =>
|
||||||
|
text !== undefined && text !== null ? Number(text).toFixed(1) : '-'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '流速(m³/s)',
|
||||||
|
dataIndex: 'v',
|
||||||
|
width: 130,
|
||||||
|
customRender: ({ text }: any) =>
|
||||||
|
text !== undefined && text !== null ? Number(text).toFixed(2) : '-'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const tableScrollX = computed(() => {
|
||||||
|
const totalWidth = tableColumns.reduce(
|
||||||
|
(sum: number, col: any) => sum + (col.width || 100),
|
||||||
|
0
|
||||||
|
);
|
||||||
|
return totalWidth > 600 ? totalWidth : undefined;
|
||||||
|
});
|
||||||
|
|
||||||
|
// ==================== 数据请求 ====================
|
||||||
|
// 数据请求(查询参数取 modelStore.selectedAnchorPoint)
|
||||||
|
const fetchData = async () => {
|
||||||
|
const stcd = modelStore.selectedAnchorPoint?.stcd;
|
||||||
|
if (!dateRange.value || !stcd) return;
|
||||||
|
isLoading.value = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const filterParams = {
|
||||||
|
filter: {
|
||||||
|
logic: 'and',
|
||||||
|
filters: [
|
||||||
|
{
|
||||||
|
field: 'stcd',
|
||||||
|
operator: 'eq',
|
||||||
|
dataType: 'string',
|
||||||
|
value: stcd
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'tm',
|
||||||
|
operator: 'gte',
|
||||||
|
dataType: 'date',
|
||||||
|
value: dateRange.value[0].format('YYYY-MM-DD HH:mm:ss')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'tm',
|
||||||
|
operator: 'lte',
|
||||||
|
dataType: 'date',
|
||||||
|
value: dateRange.value[1].format('YYYY-MM-DD HH:mm:ss')
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
sort: [{ field: 'tm', dir: 'asc' }]
|
||||||
|
};
|
||||||
|
|
||||||
|
const res = await getFlowStationList(filterParams);
|
||||||
|
const rawData = res?.data?.data || res?.data?.records || [];
|
||||||
|
|
||||||
|
tableData.value = [...rawData].reverse();
|
||||||
|
chartData.value = rawData;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取数据失败:', error);
|
||||||
|
tableData.value = [];
|
||||||
|
chartData.value = [];
|
||||||
|
} finally {
|
||||||
|
isLoading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 时间选择器 change 触发查询
|
||||||
|
const handleSearch = () => fetchData();
|
||||||
|
watch(
|
||||||
|
() => uiStore.searchDrawerOpen,
|
||||||
|
newVal => {
|
||||||
|
if (!newVal) {
|
||||||
|
dateRange.value = initDateRange();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
// 选中点位变化时刷新;初始已有点位时立即加载
|
||||||
|
watch(
|
||||||
|
() => modelStore.selectedAnchorPoint?.stcd,
|
||||||
|
(newStcd, oldStcd) => {
|
||||||
|
if (!newStcd) return;
|
||||||
|
if (newStcd !== oldStcd) fetchData();
|
||||||
|
},
|
||||||
|
{ immediate: true }
|
||||||
|
);
|
||||||
|
|
||||||
|
// 全局时间范围变化时同步本地日期选择器(默认参数联动)
|
||||||
|
watch(
|
||||||
|
() => modelStore.filter.rangeTm,
|
||||||
|
newRange => {
|
||||||
|
if (Array.isArray(newRange) && newRange.length > 0) {
|
||||||
|
dateRange.value = [dayjs(newRange[0]), dayjs(newRange[1])];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ deep: true }
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.monitor-chart {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
height: 300px;
|
||||||
|
|
||||||
|
.chart-container {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-empty {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.monitor-table {
|
||||||
|
margin-top: 10px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
// 表格列数多、tableScrollX 较宽时必须限制在抽屉宽度内横向滚动,
|
||||||
|
// 否则在 RightDrawer 的 .ant-drawer-content { overflow: visible } 下会溢出抽屉
|
||||||
|
width: 410px;
|
||||||
|
overflow-x: auto;
|
||||||
|
|
||||||
|
.table-container {
|
||||||
|
border: 1px solid #dcdfe6;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -0,0 +1,303 @@
|
|||||||
|
<template>
|
||||||
|
<SidePanelItem title="过鱼总量" :shrink="false">
|
||||||
|
<template #title-right-content>
|
||||||
|
<a-date-picker
|
||||||
|
class="w-[110px]"
|
||||||
|
v-model:value="year"
|
||||||
|
picker="year"
|
||||||
|
format="YYYY"
|
||||||
|
:allowClear="false"
|
||||||
|
:disabled-date="disabledDate"
|
||||||
|
size="small"
|
||||||
|
@change="handleSearch"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<a-spin :spinning="isLoading" tip="加载中...">
|
||||||
|
<div class="monitor-chart" :style="{ height: chartHeight + 'px' }">
|
||||||
|
<div ref="chartRef" class="chart-container"></div>
|
||||||
|
<a-empty
|
||||||
|
v-if="isDataEmpty"
|
||||||
|
description="暂无数据"
|
||||||
|
class="chart-empty"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</a-spin>
|
||||||
|
</SidePanelItem>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import {
|
||||||
|
ref,
|
||||||
|
watch,
|
||||||
|
computed,
|
||||||
|
onMounted,
|
||||||
|
onBeforeUnmount,
|
||||||
|
nextTick
|
||||||
|
} from 'vue';
|
||||||
|
import dayjs, { Dayjs } from 'dayjs';
|
||||||
|
import * as echarts from 'echarts';
|
||||||
|
import { useUiStore } from '@/store/modules/ui';
|
||||||
|
import { useModelStore } from '@/store/modules/model';
|
||||||
|
import { yearGetYearFpStatistics } from '@/api/gyss';
|
||||||
|
import SidePanelItem from '@/components/SidePanelItem/index.vue';
|
||||||
|
|
||||||
|
const uiStore = useUiStore();
|
||||||
|
const modelStore = useModelStore();
|
||||||
|
|
||||||
|
const isLoading = ref(false);
|
||||||
|
const chartData = ref<any[]>([]);
|
||||||
|
|
||||||
|
const isDataEmpty = computed(() => {
|
||||||
|
const data = Array.isArray(chartData.value) ? chartData.value : [];
|
||||||
|
return data.length === 0;
|
||||||
|
});
|
||||||
|
|
||||||
|
// ==================== 图表高度自适应 legend 行数 ====================
|
||||||
|
// 基础高度(1 行 legend 时的布局,饼图/标题均按此基准固定)
|
||||||
|
const BASE_CHART_HEIGHT = 300;
|
||||||
|
const LEGEND_ROW_HEIGHT = 20; // 每多一行 legend 增加的高度
|
||||||
|
const ITEMS_PER_ROW = 3; // 一行约 3-4 个,按 3 保守估算避免遮挡
|
||||||
|
|
||||||
|
const legendRows = computed(() => {
|
||||||
|
const len = chartData.value.length;
|
||||||
|
return len === 0 ? 1 : Math.ceil(len / ITEMS_PER_ROW);
|
||||||
|
});
|
||||||
|
|
||||||
|
const chartHeight = computed(() => {
|
||||||
|
return BASE_CHART_HEIGHT + (legendRows.value - 1) * LEGEND_ROW_HEIGHT;
|
||||||
|
});
|
||||||
|
|
||||||
|
// ==================== 图表 ====================
|
||||||
|
const chartRef = ref<HTMLElement>();
|
||||||
|
let chartInstance: echarts.ECharts | null = null;
|
||||||
|
let resizeObserver: ResizeObserver | null = null;
|
||||||
|
|
||||||
|
// 固定色板(避免每次渲染随机变色)
|
||||||
|
const COLORS = [
|
||||||
|
'#5470c6',
|
||||||
|
'#91cc75',
|
||||||
|
'#fac858',
|
||||||
|
'#ee6666',
|
||||||
|
'#73c0de',
|
||||||
|
'#3ba272',
|
||||||
|
'#fc8452',
|
||||||
|
'#9a60b4',
|
||||||
|
'#ea7ccc',
|
||||||
|
'#56C2E3',
|
||||||
|
'#7399C6',
|
||||||
|
'#4B79AB',
|
||||||
|
'#78C300',
|
||||||
|
'#00A050',
|
||||||
|
'#F7A737'
|
||||||
|
];
|
||||||
|
|
||||||
|
const initChart = () => {
|
||||||
|
if (!chartRef.value) return;
|
||||||
|
if (chartInstance) chartInstance.dispose();
|
||||||
|
chartInstance = echarts.init(chartRef.value);
|
||||||
|
updateChart(chartData.value);
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateChart = (data: any[]) => {
|
||||||
|
if (!chartInstance) return;
|
||||||
|
if (!data || data.length === 0) {
|
||||||
|
chartInstance.clear();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const unit = '尾';
|
||||||
|
const totalAmount = data.reduce(
|
||||||
|
(sum, item) => sum + Number(item.value || 0),
|
||||||
|
0
|
||||||
|
);
|
||||||
|
|
||||||
|
const option: any = {
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'item',
|
||||||
|
formatter: `{b}: {c} ${unit} ({d}%)`,
|
||||||
|
backgroundColor: 'rgba(50, 50, 50, 0.9)',
|
||||||
|
borderColor: 'transparent',
|
||||||
|
textStyle: { color: '#fff', fontSize: 12 }
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
text: `${totalAmount}`,
|
||||||
|
subtext: `总量(${unit})`,
|
||||||
|
left: '49%',
|
||||||
|
top: 117,
|
||||||
|
textAlign: 'center',
|
||||||
|
textVerticalAlign: 'middle',
|
||||||
|
textStyle: { fontSize: 24, fontWeight: 'bold', color: '#333' },
|
||||||
|
subtextStyle: { fontSize: 12, color: '#999', align: 'center' }
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
type: 'plain',
|
||||||
|
orient: 'horizontal',
|
||||||
|
left: 'center',
|
||||||
|
bottom: 5,
|
||||||
|
width: '90%',
|
||||||
|
itemWidth: 25,
|
||||||
|
itemHeight: 13,
|
||||||
|
itemGap: 8,
|
||||||
|
formatter: (name: string) => {
|
||||||
|
const found = data.find(item => item.name === name);
|
||||||
|
const value = found ? found.value : '-';
|
||||||
|
const maxLength = 7;
|
||||||
|
const truncatedName =
|
||||||
|
name.length > maxLength ? name.substring(0, maxLength) + '...' : name;
|
||||||
|
return `${truncatedName} ${value}${unit}`;
|
||||||
|
},
|
||||||
|
textStyle: { fontSize: 12, color: '#666' }
|
||||||
|
},
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
type: 'pie',
|
||||||
|
radius: [75, 105],
|
||||||
|
center: ['50%', 132],
|
||||||
|
avoidLabelOverlap: false,
|
||||||
|
itemStyle: { borderRadius: 4, borderColor: '#fff', borderWidth: 2 },
|
||||||
|
label: { show: false },
|
||||||
|
emphasis: {
|
||||||
|
label: { show: false },
|
||||||
|
itemStyle: {
|
||||||
|
shadowBlur: 10,
|
||||||
|
shadowOffsetX: 0,
|
||||||
|
shadowColor: 'rgba(0, 0, 0, 0.3)'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
labelLine: { show: false },
|
||||||
|
data: data.map((item, idx) => ({
|
||||||
|
...item,
|
||||||
|
itemStyle: { color: COLORS[idx % COLORS.length] }
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
chartInstance.setOption(option, true);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 数据变化时动态刷新图表
|
||||||
|
watch(chartData, newData => {
|
||||||
|
updateChart(newData);
|
||||||
|
});
|
||||||
|
|
||||||
|
const safeResize = () => {
|
||||||
|
if (!chartInstance) return;
|
||||||
|
// 容器不可见(display:none / 宽度为 0)时跳过,避免收起/切换时做昂贵的同步重排
|
||||||
|
if (!chartRef.value || chartRef.value.clientWidth === 0) return;
|
||||||
|
chartInstance.resize();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleResize = () => {
|
||||||
|
safeResize();
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
nextTick(() => {
|
||||||
|
initChart();
|
||||||
|
setTimeout(() => {
|
||||||
|
safeResize();
|
||||||
|
}, 200);
|
||||||
|
if (!resizeObserver && chartRef.value) {
|
||||||
|
resizeObserver = new ResizeObserver(() => {
|
||||||
|
safeResize();
|
||||||
|
});
|
||||||
|
resizeObserver.observe(chartRef.value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
window.addEventListener('resize', handleResize);
|
||||||
|
});
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
if (chartInstance) {
|
||||||
|
chartInstance.dispose();
|
||||||
|
chartInstance = null;
|
||||||
|
}
|
||||||
|
window.removeEventListener('resize', handleResize);
|
||||||
|
if (resizeObserver) {
|
||||||
|
resizeObserver.disconnect();
|
||||||
|
resizeObserver = null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 年份选择器配置(默认上一年)
|
||||||
|
const disabledDate = (current: Dayjs) =>
|
||||||
|
current && current.isAfter(dayjs(), 'year');
|
||||||
|
|
||||||
|
const initYear = (): Dayjs => dayjs().subtract(1, 'year');
|
||||||
|
const year = ref<Dayjs>(initYear());
|
||||||
|
|
||||||
|
// ==================== 数据请求 ====================
|
||||||
|
// 数据请求(查询参数取 modelStore.selectedAnchorPoint)
|
||||||
|
const fetchData = async () => {
|
||||||
|
const stcd = modelStore.selectedAnchorPoint?.stcd;
|
||||||
|
if (!year.value || !stcd) return;
|
||||||
|
isLoading.value = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await yearGetYearFpStatistics({
|
||||||
|
year: year.value.format('YYYY'),
|
||||||
|
stcd
|
||||||
|
});
|
||||||
|
|
||||||
|
const responseData = res?.data || [];
|
||||||
|
const firstBasin = Array.isArray(responseData) ? responseData[0] : null;
|
||||||
|
|
||||||
|
if (!firstBasin || !firstBasin.fpFtpStatitcsVos?.length) {
|
||||||
|
chartData.value = [];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 取第一个流域的鱼类组成分布
|
||||||
|
chartData.value = firstBasin.fpFtpStatitcsVos.map((fish: any) => ({
|
||||||
|
name: fish.fishName,
|
||||||
|
value: fish.fcnt
|
||||||
|
}));
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取过鱼统计数据失败:', error);
|
||||||
|
chartData.value = [];
|
||||||
|
} finally {
|
||||||
|
isLoading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 年份选择 change 触发查询
|
||||||
|
const handleSearch = () => fetchData();
|
||||||
|
watch(
|
||||||
|
() => uiStore.searchDrawerOpen,
|
||||||
|
newVal => {
|
||||||
|
if (!newVal) {
|
||||||
|
year.value = initYear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
// 选中点位变化时刷新;初始已有点位时立即加载
|
||||||
|
watch(
|
||||||
|
() => modelStore.selectedAnchorPoint?.stcd,
|
||||||
|
(newStcd, oldStcd) => {
|
||||||
|
if (!newStcd) return;
|
||||||
|
if (newStcd !== oldStcd) fetchData();
|
||||||
|
},
|
||||||
|
{ immediate: true }
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.monitor-chart {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
// 高度由 chartHeight 内联样式动态控制(随 legend 行数增长)
|
||||||
|
|
||||||
|
.chart-container {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-empty {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -0,0 +1,444 @@
|
|||||||
|
<template>
|
||||||
|
<SidePanelItem title="监测数据" :shrink="false">
|
||||||
|
<template #title-right-content>
|
||||||
|
<a-date-picker
|
||||||
|
class="w-[110px]"
|
||||||
|
v-model:value="year"
|
||||||
|
picker="year"
|
||||||
|
format="YYYY"
|
||||||
|
value-format="YYYY"
|
||||||
|
:allowClear="false"
|
||||||
|
size="small"
|
||||||
|
@change="handleSearch"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<a-spin :spinning="chartLoading" tip="加载中...">
|
||||||
|
<div class="monitor-chart">
|
||||||
|
<div ref="chartRef" class="chart-container"></div>
|
||||||
|
<a-empty
|
||||||
|
v-if="isChartEmpty"
|
||||||
|
description="暂无数据"
|
||||||
|
class="chart-empty"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</a-spin>
|
||||||
|
<div class="monitor-table">
|
||||||
|
<BasicTable
|
||||||
|
ref="tableRef"
|
||||||
|
:scrollY="300"
|
||||||
|
:scrollX="tableScrollX"
|
||||||
|
:columns="tableColumns"
|
||||||
|
:list-url="getTableList"
|
||||||
|
:transform-data="transformTableData"
|
||||||
|
row-key="_rowKey"
|
||||||
|
:paginationConfig="{
|
||||||
|
showSizeChanger: false,
|
||||||
|
showQuickJumper: false
|
||||||
|
}"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</SidePanelItem>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import {
|
||||||
|
ref,
|
||||||
|
watch,
|
||||||
|
computed,
|
||||||
|
onMounted,
|
||||||
|
onBeforeUnmount,
|
||||||
|
nextTick
|
||||||
|
} from 'vue';
|
||||||
|
import * as echarts from 'echarts';
|
||||||
|
import { useUiStore } from '@/store/modules/ui';
|
||||||
|
import {
|
||||||
|
getNormalDataMonitoring2Year,
|
||||||
|
getNormalDataMonitoring2List
|
||||||
|
} from '@/api/mapModal';
|
||||||
|
import { useModelStore } from '@/store/modules/model';
|
||||||
|
import SidePanelItem from '@/components/SidePanelItem/index.vue';
|
||||||
|
import BasicTable from '@/components/BasicTable/index.vue';
|
||||||
|
|
||||||
|
const uiStore = useUiStore();
|
||||||
|
const modelStore = useModelStore();
|
||||||
|
|
||||||
|
const DEFAULT_PAGE_SIZE = 20;
|
||||||
|
|
||||||
|
const tableRef = ref();
|
||||||
|
const year = ref<string>('');
|
||||||
|
|
||||||
|
// ==================== 图表(柱状图:保护对象 x 轴,两两堆叠) ====================
|
||||||
|
const chartLoading = ref(false);
|
||||||
|
const chartData = ref<any[]>([]);
|
||||||
|
const chartRef = ref<HTMLElement>();
|
||||||
|
let chartInstance: echarts.ECharts | null = null;
|
||||||
|
let resizeObserver: ResizeObserver | null = null;
|
||||||
|
|
||||||
|
const toNum = (val: any) => {
|
||||||
|
if (val === undefined || val === null || val === '' || isNaN(Number(val)))
|
||||||
|
return 0;
|
||||||
|
return Number(val);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 相同保护对象聚合求和
|
||||||
|
const chartAggData = computed(() => {
|
||||||
|
const map = new Map<
|
||||||
|
string,
|
||||||
|
{ tecnt: number; plantask: number; surnum: number; transplant: number }
|
||||||
|
>();
|
||||||
|
(Array.isArray(chartData.value) ? chartData.value : []).forEach(item => {
|
||||||
|
const name = item?.tetp ?? '未知';
|
||||||
|
if (!map.has(name)) {
|
||||||
|
map.set(name, { tecnt: 0, plantask: 0, surnum: 0, transplant: 0 });
|
||||||
|
}
|
||||||
|
const agg = map.get(name)!;
|
||||||
|
agg.tecnt += toNum(item.tecnt);
|
||||||
|
agg.plantask += toNum(item.plantask);
|
||||||
|
agg.surnum += toNum(item.surnum);
|
||||||
|
agg.transplant += toNum(item.transplant);
|
||||||
|
});
|
||||||
|
return [...map.entries()].map(([name, agg]) => ({ name, ...agg }));
|
||||||
|
});
|
||||||
|
|
||||||
|
const isChartEmpty = computed(() => chartAggData.value.length === 0);
|
||||||
|
|
||||||
|
const initChart = () => {
|
||||||
|
if (!chartRef.value) return;
|
||||||
|
if (chartInstance) chartInstance.dispose();
|
||||||
|
chartInstance = echarts.init(chartRef.value);
|
||||||
|
updateChart();
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateChart = () => {
|
||||||
|
if (!chartInstance) return;
|
||||||
|
if (isChartEmpty.value) {
|
||||||
|
chartInstance.clear();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = chartAggData.value;
|
||||||
|
const xAxisData = data.map(item => item.name);
|
||||||
|
|
||||||
|
const option = {
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'axis',
|
||||||
|
axisPointer: { type: 'shadow' },
|
||||||
|
backgroundColor: 'rgba(50,50,50,0.9)',
|
||||||
|
textStyle: { color: '#fff', fontSize: 12 }
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
top: 0,
|
||||||
|
data: ['实际种植', '种植要求', '存活数量', '移栽规模'],
|
||||||
|
textStyle: { fontSize: 12 }
|
||||||
|
},
|
||||||
|
grid: { left: 50, right: 20, top: 50, bottom: 30 },
|
||||||
|
xAxis: {
|
||||||
|
type: 'category',
|
||||||
|
data: xAxisData,
|
||||||
|
axisLine: { lineStyle: { color: '#000000' } },
|
||||||
|
axisTick: { show: false },
|
||||||
|
axisLabel: { fontSize: 12, interval: 0 }
|
||||||
|
},
|
||||||
|
yAxis: {
|
||||||
|
type: 'value',
|
||||||
|
axisLine: { lineStyle: { color: '#000000' } },
|
||||||
|
splitLine: {
|
||||||
|
show: true,
|
||||||
|
lineStyle: { color: '#bfbfbf', type: 'solid' }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
name: '实际种植',
|
||||||
|
type: 'bar',
|
||||||
|
stack: 'plant',
|
||||||
|
barMaxWidth: 40,
|
||||||
|
data: data.map(item => item.tecnt),
|
||||||
|
itemStyle: { color: '#5B8FF9' }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '种植要求',
|
||||||
|
type: 'bar',
|
||||||
|
stack: 'plant',
|
||||||
|
barMaxWidth: 40,
|
||||||
|
data: data.map(item => item.plantask),
|
||||||
|
itemStyle: { color: '#63DAAB' }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '存活数量',
|
||||||
|
type: 'bar',
|
||||||
|
stack: 'transplant',
|
||||||
|
barMaxWidth: 40,
|
||||||
|
data: data.map(item => item.surnum),
|
||||||
|
itemStyle: { color: '#657798' }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '移栽规模',
|
||||||
|
type: 'bar',
|
||||||
|
stack: 'transplant',
|
||||||
|
barMaxWidth: 40,
|
||||||
|
data: data.map(item => item.transplant),
|
||||||
|
itemStyle: { color: '#F6C022' }
|
||||||
|
}
|
||||||
|
],
|
||||||
|
toolbox: {
|
||||||
|
show: true,
|
||||||
|
feature: {
|
||||||
|
saveAsImage: { title: '保存为图片', type: 'png', pixelRatio: 2 }
|
||||||
|
},
|
||||||
|
right: 0,
|
||||||
|
top: 10
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
chartInstance.setOption(option, true);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 图表数据变化时动态刷新
|
||||||
|
watch(chartAggData, () => {
|
||||||
|
updateChart();
|
||||||
|
});
|
||||||
|
|
||||||
|
const safeResize = (
|
||||||
|
chart: echarts.ECharts | null,
|
||||||
|
el: HTMLElement | undefined
|
||||||
|
) => {
|
||||||
|
if (!chart) return;
|
||||||
|
// 容器不可见(display:none / 宽度为 0)时跳过,避免收起/切换时做昂贵的同步重排
|
||||||
|
if (!el || el.clientWidth === 0) return;
|
||||||
|
chart.resize();
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
nextTick(() => {
|
||||||
|
initChart();
|
||||||
|
setTimeout(() => {
|
||||||
|
safeResize(chartInstance, chartRef.value);
|
||||||
|
}, 200);
|
||||||
|
if (!resizeObserver && chartRef.value) {
|
||||||
|
resizeObserver = new ResizeObserver(() =>
|
||||||
|
safeResize(chartInstance, chartRef.value)
|
||||||
|
);
|
||||||
|
resizeObserver.observe(chartRef.value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
window.addEventListener('resize', () =>
|
||||||
|
safeResize(chartInstance, chartRef.value)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
if (chartInstance) {
|
||||||
|
chartInstance.dispose();
|
||||||
|
chartInstance = null;
|
||||||
|
}
|
||||||
|
window.removeEventListener('resize', () =>
|
||||||
|
safeResize(chartInstance, chartRef.value)
|
||||||
|
);
|
||||||
|
if (resizeObserver) {
|
||||||
|
resizeObserver.disconnect();
|
||||||
|
resizeObserver = null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// ==================== 表格 ====================
|
||||||
|
const formatValue = (val: any) =>
|
||||||
|
val !== undefined && val !== null && String(val).trim() !== '' ? val : '-';
|
||||||
|
|
||||||
|
const tableColumns = ref([
|
||||||
|
{
|
||||||
|
title: '保护对象',
|
||||||
|
dataIndex: 'tetp',
|
||||||
|
width: 120
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '实际种植/种植要求',
|
||||||
|
dataIndex: 'surnum',
|
||||||
|
width: 140,
|
||||||
|
customRender: ({ record }: any) =>
|
||||||
|
`${formatValue(record?.tecnt)}/${formatValue(record?.plantask)}`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '存活数量/移栽规模',
|
||||||
|
dataIndex: 'treeNum',
|
||||||
|
width: 140,
|
||||||
|
customRender: ({ record }: any) =>
|
||||||
|
`${formatValue(record?.surnum)}/${formatValue(record?.transplant)}`
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
const tableScrollX = ref(320);
|
||||||
|
|
||||||
|
const transformTableData = (res: any) => {
|
||||||
|
const records = res?.data?.records || res?.data?.data || res?.data || [];
|
||||||
|
const total = res?.data?.data?.total || res?.data?.total || res?.total || 0;
|
||||||
|
|
||||||
|
return {
|
||||||
|
records: records.map((item: any, index: number) => ({
|
||||||
|
...item,
|
||||||
|
_rowKey:
|
||||||
|
item?.id ||
|
||||||
|
item?.fid ||
|
||||||
|
`${item?.stcd || 'stcd'}-${item?.tm || year.value || 'tm'}-${index}`
|
||||||
|
})),
|
||||||
|
total
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// ==================== 年份与数据查询 ====================
|
||||||
|
const getDefaultYear = (yearList: any[]) => {
|
||||||
|
const firstItem = yearList?.[0];
|
||||||
|
if (typeof firstItem === 'string' || typeof firstItem === 'number') {
|
||||||
|
return String(firstItem);
|
||||||
|
}
|
||||||
|
return firstItem?.yr || firstItem?.tm || firstItem?.year || null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const buildFilter = () => ({
|
||||||
|
logic: 'and',
|
||||||
|
filters: [
|
||||||
|
{
|
||||||
|
field: 'stcd',
|
||||||
|
operator: 'in',
|
||||||
|
dataType: 'string',
|
||||||
|
value: [modelStore.selectedAnchorPoint?.stcd]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'tm',
|
||||||
|
operator: 'eq',
|
||||||
|
dataType: 'string',
|
||||||
|
value: year.value
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
const getTableList = (params: any) => {
|
||||||
|
const take = Number(params?.take || DEFAULT_PAGE_SIZE);
|
||||||
|
const currentPage = Number(params?.skip || 1);
|
||||||
|
const skip = Math.max(currentPage - 1, 0) * take;
|
||||||
|
|
||||||
|
return getNormalDataMonitoring2List({
|
||||||
|
...params,
|
||||||
|
take,
|
||||||
|
skip,
|
||||||
|
filter: params?.filter || buildFilter()
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const refreshTable = () => {
|
||||||
|
if (!modelStore.selectedAnchorPoint?.stcd || !year.value) return;
|
||||||
|
tableRef.value?.getList(buildFilter());
|
||||||
|
};
|
||||||
|
|
||||||
|
// 图表使用全量数据(不分页)按保护对象聚合
|
||||||
|
const fetchChartData = async () => {
|
||||||
|
if (!modelStore.selectedAnchorPoint?.stcd || !year.value) return;
|
||||||
|
chartLoading.value = true;
|
||||||
|
try {
|
||||||
|
const res = await getNormalDataMonitoring2List({
|
||||||
|
filter: buildFilter(),
|
||||||
|
take: 10000,
|
||||||
|
skip: 0
|
||||||
|
});
|
||||||
|
chartData.value = res?.data?.records || res?.data?.data || res?.data || [];
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取植物园图表数据失败:', error);
|
||||||
|
chartData.value = [];
|
||||||
|
} finally {
|
||||||
|
chartLoading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const refreshAll = () => {
|
||||||
|
refreshTable();
|
||||||
|
fetchChartData();
|
||||||
|
};
|
||||||
|
|
||||||
|
// 获取年份列表并赋值默认年份(年份列表第一个),然后自动查询
|
||||||
|
const getYearList = async () => {
|
||||||
|
const stcd = modelStore.selectedAnchorPoint?.stcd;
|
||||||
|
if (!stcd) return;
|
||||||
|
try {
|
||||||
|
const res: any = await getNormalDataMonitoring2Year({
|
||||||
|
filter: {
|
||||||
|
logic: 'and',
|
||||||
|
filters: [
|
||||||
|
{
|
||||||
|
field: 'stcd',
|
||||||
|
operator: 'eq',
|
||||||
|
dataType: 'string',
|
||||||
|
value: stcd
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const yearData = res?.data?.data || res?.data?.records || res?.data || [];
|
||||||
|
const defaultYear =
|
||||||
|
getDefaultYear(yearData) || String(new Date().getFullYear());
|
||||||
|
|
||||||
|
year.value = String(defaultYear);
|
||||||
|
refreshAll();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取年份列表失败:', error);
|
||||||
|
if (tableRef.value) {
|
||||||
|
tableRef.value.loading = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSearch = () => {
|
||||||
|
if (!year.value) return;
|
||||||
|
chartData.value = []; // 切换年份时先清空图表,避免加载期间显示旧数据
|
||||||
|
refreshAll();
|
||||||
|
};
|
||||||
|
|
||||||
|
// ==================== 联动 ====================
|
||||||
|
watch(
|
||||||
|
() => uiStore.searchDrawerOpen,
|
||||||
|
newVal => {
|
||||||
|
if (!newVal) {
|
||||||
|
year.value = '';
|
||||||
|
chartData.value = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// 选中点位变化时重新加载;初始已有点位时立即加载
|
||||||
|
watch(
|
||||||
|
() => modelStore.selectedAnchorPoint?.stcd,
|
||||||
|
(newStcd, oldStcd) => {
|
||||||
|
if (!newStcd) return;
|
||||||
|
if (newStcd !== oldStcd) {
|
||||||
|
year.value = '';
|
||||||
|
chartData.value = [];
|
||||||
|
getYearList();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ immediate: true }
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.monitor-chart {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
height: 300px;
|
||||||
|
|
||||||
|
.chart-container {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-empty {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.monitor-table {
|
||||||
|
margin-top: 10px;
|
||||||
|
width: 410px;
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -0,0 +1,760 @@
|
|||||||
|
<template>
|
||||||
|
<SidePanelItem title="水温监测" :shrink="false">
|
||||||
|
<template #title-right-content>
|
||||||
|
<a-range-picker
|
||||||
|
class="w-[220px]"
|
||||||
|
v-model:value="wtDateRange"
|
||||||
|
format="YYYY-MM-DD"
|
||||||
|
:show-time="showTimeConfig"
|
||||||
|
:allowClear="false"
|
||||||
|
:presets="DateSetting.RangeButton.month1"
|
||||||
|
:disabled-date="disabledDate"
|
||||||
|
size="small"
|
||||||
|
@change="fetchWtData"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<div class="station-select-row">
|
||||||
|
测站:
|
||||||
|
<a-select
|
||||||
|
v-model:value="wtSelectedStcd"
|
||||||
|
:options="wtStationOptions"
|
||||||
|
class="station-select"
|
||||||
|
placeholder="请选择站点"
|
||||||
|
@change="fetchWtData"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<a-spin :spinning="wtLoading" tip="加载中...">
|
||||||
|
<div class="monitor-chart">
|
||||||
|
<div ref="wtChartRef" class="chart-container"></div>
|
||||||
|
<a-empty v-if="wtIsEmpty" description="暂无数据" class="chart-empty" />
|
||||||
|
</div>
|
||||||
|
</a-spin>
|
||||||
|
</SidePanelItem>
|
||||||
|
|
||||||
|
<SidePanelItem title="流量监测" :shrink="false">
|
||||||
|
<template #title-right-content>
|
||||||
|
<a-range-picker
|
||||||
|
class="w-[220px]"
|
||||||
|
v-model:value="llDateRange"
|
||||||
|
format="YYYY-MM-DD"
|
||||||
|
:show-time="showTimeConfig"
|
||||||
|
:allowClear="false"
|
||||||
|
:presets="DateSetting.RangeButton.month1"
|
||||||
|
:disabled-date="disabledDate"
|
||||||
|
size="small"
|
||||||
|
@change="fetchLlData"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<div class="station-select-row">
|
||||||
|
测站:
|
||||||
|
<a-select
|
||||||
|
v-model:value="llSelectedStcd"
|
||||||
|
:options="llStationOptions"
|
||||||
|
class="station-select"
|
||||||
|
placeholder="请选择站点"
|
||||||
|
@change="fetchLlData"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<a-spin :spinning="llLoading" tip="加载中...">
|
||||||
|
<div class="monitor-chart">
|
||||||
|
<div ref="llChartRef" class="chart-container"></div>
|
||||||
|
<a-empty v-if="llIsEmpty" description="暂无数据" class="chart-empty" />
|
||||||
|
</div>
|
||||||
|
</a-spin>
|
||||||
|
</SidePanelItem>
|
||||||
|
|
||||||
|
<SidePanelItem title="实时视频" :shrink="false">
|
||||||
|
<template #title-right-content> </template>
|
||||||
|
</SidePanelItem>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import {
|
||||||
|
ref,
|
||||||
|
watch,
|
||||||
|
computed,
|
||||||
|
onMounted,
|
||||||
|
onBeforeUnmount,
|
||||||
|
nextTick
|
||||||
|
} from 'vue';
|
||||||
|
import dayjs, { Dayjs } from 'dayjs';
|
||||||
|
import * as echarts from 'echarts';
|
||||||
|
import { useUiStore } from '@/store/modules/ui';
|
||||||
|
import {
|
||||||
|
getMonitorDataWaterTemp,
|
||||||
|
getMonitorDataWt,
|
||||||
|
getMonitorDataZq
|
||||||
|
} from '@/api/mapModal';
|
||||||
|
import { getFlowStationList } from '@/api/DataQueryMenuModule';
|
||||||
|
import { useModelStore } from '@/store/modules/model';
|
||||||
|
import SidePanelItem from '@/components/SidePanelItem/index.vue';
|
||||||
|
import { DateSetting } from '@/utils/enumeration';
|
||||||
|
|
||||||
|
const uiStore = useUiStore();
|
||||||
|
const modelStore = useModelStore();
|
||||||
|
|
||||||
|
// ==================== 时间选择器配置 ====================
|
||||||
|
const showTimeConfig = {
|
||||||
|
format: 'HH:mm',
|
||||||
|
hourStep: 1,
|
||||||
|
minuteStep: 5,
|
||||||
|
secondStep: 60
|
||||||
|
};
|
||||||
|
const disabledDate = (current: Dayjs) =>
|
||||||
|
current && current.isAfter(dayjs(), 'day');
|
||||||
|
|
||||||
|
const initDateRange = (): [Dayjs, Dayjs] => {
|
||||||
|
return [dayjs().subtract(1, 'month'), dayjs()];
|
||||||
|
};
|
||||||
|
|
||||||
|
// ==================== 水温图表 ====================
|
||||||
|
const wtLoading = ref(false);
|
||||||
|
const wtChartData = ref<any[]>([]);
|
||||||
|
const wtDateRange = ref<[Dayjs, Dayjs] | undefined>(initDateRange());
|
||||||
|
const wtChartRef = ref<HTMLElement>();
|
||||||
|
let wtChartInstance: echarts.ECharts | null = null;
|
||||||
|
let wtResizeObserver: ResizeObserver | null = null;
|
||||||
|
|
||||||
|
const wtIsEmpty = computed(() => {
|
||||||
|
const data = Array.isArray(wtChartData.value) ? wtChartData.value : [];
|
||||||
|
return data.length === 0;
|
||||||
|
});
|
||||||
|
|
||||||
|
const initWtChart = () => {
|
||||||
|
if (!wtChartRef.value) return;
|
||||||
|
if (wtChartInstance) wtChartInstance.dispose();
|
||||||
|
wtChartInstance = echarts.init(wtChartRef.value);
|
||||||
|
updateWtChart(wtChartData.value);
|
||||||
|
};
|
||||||
|
|
||||||
|
function wtShouldStagger(width: number, dataLen: number) {
|
||||||
|
return dataLen > Math.floor(width / 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getWtAxisLabelConfig(chartWidth: number, xAxisData: string[]) {
|
||||||
|
const needStagger = wtShouldStagger(chartWidth, xAxisData.length);
|
||||||
|
return {
|
||||||
|
fontSize: 12,
|
||||||
|
interval: 'auto',
|
||||||
|
formatter: (value: string, index: number) => {
|
||||||
|
const [date, time] = value.split(' ');
|
||||||
|
if (needStagger) {
|
||||||
|
return index % 2 === 0 ? `${date}\n${time}\n ` : ` \n${date}\n${time}`;
|
||||||
|
}
|
||||||
|
return `${date}\n${time}`;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateWtChart = (data: any[]) => {
|
||||||
|
if (!wtChartInstance) return;
|
||||||
|
if (!data || data.length === 0) {
|
||||||
|
wtChartInstance.clear();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const sorted = [...data].sort(
|
||||||
|
(a, b) => new Date(a.tm).getTime() - new Date(b.tm).getTime()
|
||||||
|
);
|
||||||
|
const xAxisData = sorted.map(item =>
|
||||||
|
dayjs(item.tm).format('YYYY-MM-DD HH:mm')
|
||||||
|
);
|
||||||
|
const width = wtChartRef.value?.clientWidth || 0;
|
||||||
|
const axisLabelConfig = getWtAxisLabelConfig(width, xAxisData);
|
||||||
|
|
||||||
|
const option = {
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'axis',
|
||||||
|
backgroundColor: 'rgba(50,50,50,0.9)',
|
||||||
|
textStyle: { color: '#fff', fontSize: 12 },
|
||||||
|
axisPointer: { type: 'cross' },
|
||||||
|
formatter: (params: any) => {
|
||||||
|
if (!params?.length) return '';
|
||||||
|
const idx = params[0].dataIndex;
|
||||||
|
const fullTime = sorted[idx]?.tm
|
||||||
|
? dayjs(sorted[idx].tm).format('YYYY-MM-DD HH:mm')
|
||||||
|
: '';
|
||||||
|
let html = `<div style="font-size:16px;margin-bottom:8px;">${fullTime}</div>`;
|
||||||
|
params.forEach((p: any) => {
|
||||||
|
const v = p.value != null ? Number(p.value).toFixed(1) : '-';
|
||||||
|
html += `<div style="display:flex;align-items:center;justify-content:space-between;margin:4px 0;"><span style="display:inline-block;width:10px;height:10px;border-radius:50%;background:${p.color};margin-right:8px;"></span><span style="flex:1;font-size:14px;">水温: </span><span style="font-size:14px;min-width:60px;text-align:right;"><strong>${v}</strong> °C</span></div>`;
|
||||||
|
});
|
||||||
|
return html;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
width: '80%',
|
||||||
|
type: 'scroll',
|
||||||
|
top: 10,
|
||||||
|
data: ['水温'],
|
||||||
|
textStyle: { fontSize: 12 }
|
||||||
|
},
|
||||||
|
grid: { left: 40, right: 20, top: 44, bottom: 50 },
|
||||||
|
xAxis: {
|
||||||
|
type: 'category',
|
||||||
|
data: xAxisData,
|
||||||
|
axisLine: { lineStyle: { color: '#000000' } },
|
||||||
|
axisTick: { show: false },
|
||||||
|
axisLabel: axisLabelConfig,
|
||||||
|
splitLine: {
|
||||||
|
show: true,
|
||||||
|
lineStyle: { color: '#bfbfbf', type: 'solid' }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
yAxis: {
|
||||||
|
name: '水温(°C)',
|
||||||
|
type: 'value',
|
||||||
|
axisLine: { lineStyle: { color: '#000000' } },
|
||||||
|
scale: true,
|
||||||
|
splitLine: {
|
||||||
|
show: true,
|
||||||
|
lineStyle: { color: '#bfbfbf', type: 'solid' }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
name: '水温',
|
||||||
|
type: 'line',
|
||||||
|
data: sorted.map(item => item.wt),
|
||||||
|
smooth: true,
|
||||||
|
symbol: 'none',
|
||||||
|
lineStyle: { color: '#4B79AB', width: 2 },
|
||||||
|
itemStyle: { color: '#4B79AB' },
|
||||||
|
areaStyle: { color: '#85A9D0' }
|
||||||
|
}
|
||||||
|
],
|
||||||
|
dataZoom: [
|
||||||
|
{ type: 'inside', xAxisIndex: [0], throttle: 50, start: 0, end: 100 }
|
||||||
|
],
|
||||||
|
toolbox: {
|
||||||
|
show: true,
|
||||||
|
feature: {
|
||||||
|
saveAsImage: { title: '保存为图片', type: 'png', pixelRatio: 2 }
|
||||||
|
},
|
||||||
|
right: 20,
|
||||||
|
top: 10
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
wtChartInstance.setOption(option, true);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 数据变化时动态刷新水温图表
|
||||||
|
watch(wtChartData, newData => {
|
||||||
|
updateWtChart(newData);
|
||||||
|
});
|
||||||
|
|
||||||
|
const fetchWtData = async () => {
|
||||||
|
const stcd = wtSelectedStcd.value;
|
||||||
|
if (!wtDateRange.value || !stcd) return;
|
||||||
|
wtLoading.value = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const filterParams = {
|
||||||
|
filter: {
|
||||||
|
logic: 'and',
|
||||||
|
filters: [
|
||||||
|
{
|
||||||
|
field: 'stcd',
|
||||||
|
operator: 'eq',
|
||||||
|
dataType: 'string',
|
||||||
|
value: stcd
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'tm',
|
||||||
|
operator: 'gte',
|
||||||
|
dataType: 'date',
|
||||||
|
value: wtDateRange.value[0].format('YYYY-MM-DD HH:mm:ss')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'tm',
|
||||||
|
operator: 'lte',
|
||||||
|
dataType: 'date',
|
||||||
|
value: wtDateRange.value[1].format('YYYY-MM-DD HH:mm:ss')
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
sort: [{ field: 'tm', dir: 'asc' }]
|
||||||
|
};
|
||||||
|
|
||||||
|
const res = await getMonitorDataWaterTemp(filterParams);
|
||||||
|
wtChartData.value = res?.data?.data || res?.data?.records || [];
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取水温数据失败:', error);
|
||||||
|
wtChartData.value = [];
|
||||||
|
} finally {
|
||||||
|
wtLoading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// ==================== 流量图表 ====================
|
||||||
|
const llLoading = ref(false);
|
||||||
|
const llChartData = ref<any[]>([]);
|
||||||
|
const llDateRange = ref<[Dayjs, Dayjs] | undefined>(initDateRange());
|
||||||
|
const llChartRef = ref<HTMLElement>();
|
||||||
|
let llChartInstance: echarts.ECharts | null = null;
|
||||||
|
let llResizeObserver: ResizeObserver | null = null;
|
||||||
|
|
||||||
|
const llIsEmpty = computed(() => {
|
||||||
|
const data = Array.isArray(llChartData.value) ? llChartData.value : [];
|
||||||
|
return data.length === 0;
|
||||||
|
});
|
||||||
|
|
||||||
|
const initLlChart = () => {
|
||||||
|
if (!llChartRef.value) return;
|
||||||
|
if (llChartInstance) llChartInstance.dispose();
|
||||||
|
llChartInstance = echarts.init(llChartRef.value);
|
||||||
|
updateLlChart(llChartData.value);
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateLlChart = (data: any[]) => {
|
||||||
|
if (!llChartInstance) return;
|
||||||
|
if (!data || data.length === 0) {
|
||||||
|
llChartInstance.clear();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const sorted = [...data].sort(
|
||||||
|
(a, b) => new Date(a.tm).getTime() - new Date(b.tm).getTime()
|
||||||
|
);
|
||||||
|
|
||||||
|
const xAxisData = sorted.map(item => {
|
||||||
|
const hm = dayjs(item.tm).format('HH:mm');
|
||||||
|
if (hm === '00:00') {
|
||||||
|
const date = dayjs(item.tm).format('MM-DD');
|
||||||
|
return `${hm}\n${date}`;
|
||||||
|
}
|
||||||
|
return hm;
|
||||||
|
});
|
||||||
|
|
||||||
|
const zData = sorted.map(item => item.z);
|
||||||
|
const qData = sorted.map(item => item.q);
|
||||||
|
const vData = sorted.map(item => item.v);
|
||||||
|
|
||||||
|
const formatRules: Record<
|
||||||
|
string,
|
||||||
|
{ format: (v: number) => string; unit: string }
|
||||||
|
> = {
|
||||||
|
水位: { format: v => v.toFixed(2), unit: '(m)' },
|
||||||
|
流量: { format: v => String(Math.round(v)), unit: '(m³/s)' },
|
||||||
|
流速: { format: v => v.toFixed(2), unit: '(m³/s)' }
|
||||||
|
};
|
||||||
|
|
||||||
|
const option = {
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'axis',
|
||||||
|
backgroundColor: 'rgba(50, 50, 50, 0.9)',
|
||||||
|
textStyle: { color: '#fff', fontSize: 12 },
|
||||||
|
axisPointer: { type: 'cross' },
|
||||||
|
formatter: (params: any) => {
|
||||||
|
if (!params || params.length === 0) return '';
|
||||||
|
const dataIndex = params[0].dataIndex;
|
||||||
|
const fullTime = sorted[dataIndex]?.tm
|
||||||
|
? dayjs(sorted[dataIndex].tm).format('YYYY-MM-DD HH:mm:ss')
|
||||||
|
: '';
|
||||||
|
let html = `<div style="font-size:16px;margin-bottom:8px;">${fullTime}</div>`;
|
||||||
|
params.forEach((param: any) => {
|
||||||
|
if (param.value == null) return;
|
||||||
|
const rule = Object.entries(formatRules).find(([key]) =>
|
||||||
|
param.seriesName.includes(key)
|
||||||
|
);
|
||||||
|
const displayValue = rule
|
||||||
|
? rule[1].format(Number(param.value))
|
||||||
|
: param.value;
|
||||||
|
const unit = rule ? rule[1].unit : '';
|
||||||
|
html += `
|
||||||
|
<div style="display:flex;align-items:center;justify-content:space-between;margin:4px 0;">
|
||||||
|
<span style="display:inline-block;width:10px;height:10px;border-radius:50%;background:${param.color};margin-right:8px;"></span>
|
||||||
|
<span style="flex:1;font-size:14px;text-align:left;margin-right:6px;">${param.seriesName}: </span>
|
||||||
|
<span style="font-size:14px;min-width:60px;text-align:right;"><strong>${displayValue}</strong> ${unit}</span>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
});
|
||||||
|
return html;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
top: 0,
|
||||||
|
data: ['水位', '流量', '流速'],
|
||||||
|
textStyle: { fontSize: 12 },
|
||||||
|
selected: {
|
||||||
|
水位: true,
|
||||||
|
流量: true,
|
||||||
|
流速: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
left: 60,
|
||||||
|
right: 100,
|
||||||
|
top: 60,
|
||||||
|
bottom: 50
|
||||||
|
},
|
||||||
|
xAxis: {
|
||||||
|
type: 'category',
|
||||||
|
data: xAxisData,
|
||||||
|
axisLine: { lineStyle: { color: '#000000' } },
|
||||||
|
axisTick: { show: false },
|
||||||
|
axisLabel: { fontSize: 12 },
|
||||||
|
splitLine: {
|
||||||
|
show: true,
|
||||||
|
lineStyle: { color: '#bfbfbf', type: 'solid' }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
yAxis: [
|
||||||
|
{
|
||||||
|
name: '水位(m)',
|
||||||
|
type: 'value',
|
||||||
|
position: 'left',
|
||||||
|
axisLine: { show: true, lineStyle: { color: '#56C2E3' } },
|
||||||
|
axisLabel: { color: '#56C2E3' },
|
||||||
|
nameTextStyle: { color: '#56C2E3' },
|
||||||
|
alignTicks: true,
|
||||||
|
scale: true,
|
||||||
|
splitNumber: 9
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '流量(m³/s)',
|
||||||
|
type: 'value',
|
||||||
|
position: 'right',
|
||||||
|
axisLine: { show: true, lineStyle: { color: '#9556A4' } },
|
||||||
|
axisLabel: { color: '#9556A4' },
|
||||||
|
nameTextStyle: { color: '#9556A4' },
|
||||||
|
alignTicks: true,
|
||||||
|
scale: true,
|
||||||
|
splitNumber: 9
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '流速(m³/s)',
|
||||||
|
type: 'value',
|
||||||
|
position: 'right',
|
||||||
|
axisLine: { show: true, lineStyle: { color: '#78C300' } },
|
||||||
|
axisLabel: { color: '#78C300' },
|
||||||
|
nameTextStyle: { color: '#78C300' },
|
||||||
|
alignTicks: true,
|
||||||
|
scale: true,
|
||||||
|
splitNumber: 9,
|
||||||
|
offset: 60
|
||||||
|
}
|
||||||
|
],
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
name: '水位',
|
||||||
|
type: 'line',
|
||||||
|
yAxisIndex: 0,
|
||||||
|
data: zData,
|
||||||
|
smooth: true,
|
||||||
|
symbol: 'circle',
|
||||||
|
symbolSize: 4,
|
||||||
|
lineStyle: { color: '#56C2E3', width: 2 },
|
||||||
|
itemStyle: { color: '#56C2E3' }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '流量',
|
||||||
|
type: 'line',
|
||||||
|
yAxisIndex: 1,
|
||||||
|
data: qData,
|
||||||
|
smooth: true,
|
||||||
|
symbol: 'circle',
|
||||||
|
symbolSize: 4,
|
||||||
|
lineStyle: { color: '#9556A4', width: 2 },
|
||||||
|
itemStyle: { color: '#9556A4' }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '流速',
|
||||||
|
type: 'line',
|
||||||
|
yAxisIndex: 2,
|
||||||
|
data: vData,
|
||||||
|
smooth: true,
|
||||||
|
symbol: 'circle',
|
||||||
|
symbolSize: 4,
|
||||||
|
lineStyle: { color: '#78C300', width: 2 },
|
||||||
|
itemStyle: { color: '#78C300' }
|
||||||
|
}
|
||||||
|
],
|
||||||
|
dataZoom: [
|
||||||
|
{
|
||||||
|
type: 'inside',
|
||||||
|
xAxisIndex: [0],
|
||||||
|
throttle: 50,
|
||||||
|
start: 0,
|
||||||
|
end: 100
|
||||||
|
}
|
||||||
|
],
|
||||||
|
toolbox: {
|
||||||
|
show: true,
|
||||||
|
feature: {
|
||||||
|
saveAsImage: { title: '保存为图片', type: 'png', pixelRatio: 2 }
|
||||||
|
},
|
||||||
|
right: 20,
|
||||||
|
top: -6
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
llChartInstance.setOption(option, true);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 数据变化时动态刷新流量图表
|
||||||
|
watch(llChartData, newData => {
|
||||||
|
updateLlChart(newData);
|
||||||
|
});
|
||||||
|
|
||||||
|
const fetchLlData = async () => {
|
||||||
|
const stcd = llSelectedStcd.value;
|
||||||
|
if (!llDateRange.value || !stcd) return;
|
||||||
|
llLoading.value = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const filterParams = {
|
||||||
|
filter: {
|
||||||
|
logic: 'and',
|
||||||
|
filters: [
|
||||||
|
{
|
||||||
|
field: 'stcd',
|
||||||
|
operator: 'eq',
|
||||||
|
dataType: 'string',
|
||||||
|
value: stcd
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'tm',
|
||||||
|
operator: 'gte',
|
||||||
|
dataType: 'date',
|
||||||
|
value: llDateRange.value[0].format('YYYY-MM-DD HH:mm:ss')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'tm',
|
||||||
|
operator: 'lte',
|
||||||
|
dataType: 'date',
|
||||||
|
value: llDateRange.value[1].format('YYYY-MM-DD HH:mm:ss')
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
sort: [{ field: 'tm', dir: 'asc' }]
|
||||||
|
};
|
||||||
|
|
||||||
|
const res = await getFlowStationList(filterParams);
|
||||||
|
llChartData.value = res?.data?.data || res?.data?.records || [];
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取流量数据失败:', error);
|
||||||
|
llChartData.value = [];
|
||||||
|
} finally {
|
||||||
|
llLoading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// ==================== 测站选择器(水温) ====================
|
||||||
|
const wtStationOptions = ref<{ label: string; value: string }[]>([]);
|
||||||
|
const wtSelectedStcd = ref<string>('');
|
||||||
|
let wtStationReqSeq = 0; // 请求序号,用于竞态守卫(仅最后一次请求生效)
|
||||||
|
const loadWtStationList = async () => {
|
||||||
|
const fhstcd = modelStore.selectedAnchorPoint?.stcd;
|
||||||
|
if (!fhstcd) return;
|
||||||
|
const requestSeq = ++wtStationReqSeq;
|
||||||
|
wtStationOptions.value = []; // 先清空,等待加载
|
||||||
|
wtSelectedStcd.value = ''; // 同步清空选中值,避免上一父站选中残留
|
||||||
|
try {
|
||||||
|
const res = await getMonitorDataWt({
|
||||||
|
filter: {
|
||||||
|
logic: 'and',
|
||||||
|
filters: [
|
||||||
|
{
|
||||||
|
field: 'fhstcd',
|
||||||
|
operator: 'eq',
|
||||||
|
dataType: 'string',
|
||||||
|
value: fhstcd
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'sttpCode',
|
||||||
|
operator: 'eq',
|
||||||
|
dataType: 'string',
|
||||||
|
value: 'WTRV'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
select: ['stcd', 'stnm']
|
||||||
|
});
|
||||||
|
if (requestSeq !== wtStationReqSeq) return; // 非最新请求,丢弃结果
|
||||||
|
const stations = res?.data?.data || res?.data?.records || [];
|
||||||
|
if (stations.length > 0) {
|
||||||
|
wtStationOptions.value = stations.map((item: any) => ({
|
||||||
|
label: item.stnm,
|
||||||
|
value: item.stcd
|
||||||
|
}));
|
||||||
|
wtSelectedStcd.value = stations[0].stcd;
|
||||||
|
fetchWtData();
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
if (requestSeq !== wtStationReqSeq) return; // 非最新请求,忽略错误
|
||||||
|
console.error('获取水温测站列表失败:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// ==================== 测站选择器(流量) ====================
|
||||||
|
const llStationOptions = ref<{ label: string; value: string }[]>([]);
|
||||||
|
const llSelectedStcd = ref<string>('');
|
||||||
|
let llStationReqSeq = 0; // 请求序号,用于竞态守卫(仅最后一次请求生效)
|
||||||
|
const loadLlStationList = async () => {
|
||||||
|
const fhstcd = modelStore.selectedAnchorPoint?.stcd;
|
||||||
|
if (!fhstcd) return;
|
||||||
|
const requestSeq = ++llStationReqSeq;
|
||||||
|
llStationOptions.value = []; // 先清空,等待加载
|
||||||
|
llSelectedStcd.value = ''; // 同步清空选中值,避免上一父站选中残留
|
||||||
|
try {
|
||||||
|
const res = await getMonitorDataZq({
|
||||||
|
filter: {
|
||||||
|
logic: 'and',
|
||||||
|
filters: [
|
||||||
|
{
|
||||||
|
field: 'fhstcd',
|
||||||
|
operator: 'eq',
|
||||||
|
dataType: 'string',
|
||||||
|
value: fhstcd
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'sttpCode',
|
||||||
|
operator: 'eq',
|
||||||
|
dataType: 'string',
|
||||||
|
value: 'ZQ'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
select: ['stcd', 'stnm']
|
||||||
|
});
|
||||||
|
if (requestSeq !== llStationReqSeq) return; // 非最新请求,丢弃结果
|
||||||
|
const stations = res?.data?.data || res?.data?.records || [];
|
||||||
|
if (stations.length > 0) {
|
||||||
|
llStationOptions.value = stations.map((item: any) => ({
|
||||||
|
label: item.stnm,
|
||||||
|
value: item.stcd
|
||||||
|
}));
|
||||||
|
llSelectedStcd.value = stations[0].stcd;
|
||||||
|
fetchLlData();
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
if (requestSeq !== llStationReqSeq) return; // 非最新请求,忽略错误
|
||||||
|
console.error('获取流量测站列表失败:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// ==================== 生命周期 ====================
|
||||||
|
const safeResize = (
|
||||||
|
chart: echarts.ECharts | null,
|
||||||
|
el: HTMLElement | undefined
|
||||||
|
) => {
|
||||||
|
if (!chart) return;
|
||||||
|
// 容器不可见(display:none / 宽度为 0)时跳过,避免收起/切换时做昂贵的同步重排
|
||||||
|
if (!el || el.clientWidth === 0) return;
|
||||||
|
chart.resize();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleResize = () => {
|
||||||
|
safeResize(wtChartInstance, wtChartRef.value);
|
||||||
|
safeResize(llChartInstance, llChartRef.value);
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
nextTick(() => {
|
||||||
|
initWtChart();
|
||||||
|
initLlChart();
|
||||||
|
setTimeout(() => {
|
||||||
|
handleResize();
|
||||||
|
}, 200);
|
||||||
|
if (!wtResizeObserver && wtChartRef.value) {
|
||||||
|
wtResizeObserver = new ResizeObserver(() =>
|
||||||
|
safeResize(wtChartInstance, wtChartRef.value)
|
||||||
|
);
|
||||||
|
wtResizeObserver.observe(wtChartRef.value);
|
||||||
|
}
|
||||||
|
if (!llResizeObserver && llChartRef.value) {
|
||||||
|
llResizeObserver = new ResizeObserver(() =>
|
||||||
|
safeResize(llChartInstance, llChartRef.value)
|
||||||
|
);
|
||||||
|
llResizeObserver.observe(llChartRef.value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
window.addEventListener('resize', handleResize);
|
||||||
|
});
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
if (wtChartInstance) {
|
||||||
|
wtChartInstance.dispose();
|
||||||
|
wtChartInstance = null;
|
||||||
|
}
|
||||||
|
if (llChartInstance) {
|
||||||
|
llChartInstance.dispose();
|
||||||
|
llChartInstance = null;
|
||||||
|
}
|
||||||
|
window.removeEventListener('resize', handleResize);
|
||||||
|
if (wtResizeObserver) {
|
||||||
|
wtResizeObserver.disconnect();
|
||||||
|
wtResizeObserver = null;
|
||||||
|
}
|
||||||
|
if (llResizeObserver) {
|
||||||
|
llResizeObserver.disconnect();
|
||||||
|
llResizeObserver = null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// ==================== 联动 ====================
|
||||||
|
watch(
|
||||||
|
() => uiStore.searchDrawerOpen,
|
||||||
|
newVal => {
|
||||||
|
if (!newVal) {
|
||||||
|
wtDateRange.value = initDateRange();
|
||||||
|
llDateRange.value = initDateRange();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
// 选中点位变化时重新加载测站列表并刷新图表;初始已有点位时立即加载
|
||||||
|
watch(
|
||||||
|
() => modelStore.selectedAnchorPoint?.stcd,
|
||||||
|
(newStcd, oldStcd) => {
|
||||||
|
if (!newStcd) return;
|
||||||
|
if (newStcd !== oldStcd) {
|
||||||
|
loadWtStationList();
|
||||||
|
loadLlStationList();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ immediate: true }
|
||||||
|
);
|
||||||
|
|
||||||
|
// 全局时间范围变化时同步本地日期选择器(默认参数联动)
|
||||||
|
watch(
|
||||||
|
() => modelStore.filter.rangeTm,
|
||||||
|
newRange => {
|
||||||
|
if (Array.isArray(newRange) && newRange.length > 0) {
|
||||||
|
const next = [dayjs(newRange[0]), dayjs(newRange[1])];
|
||||||
|
wtDateRange.value = next;
|
||||||
|
llDateRange.value = next;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ deep: true }
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.station-select-row {
|
||||||
|
margin-bottom: 12px;
|
||||||
|
|
||||||
|
.station-select {
|
||||||
|
width: 200px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.monitor-chart {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
height: 300px;
|
||||||
|
|
||||||
|
.chart-container {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-empty {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -0,0 +1,454 @@
|
|||||||
|
<template>
|
||||||
|
<SidePanelItem title="垂向水温监测数据" :shrink="false">
|
||||||
|
<template #title-right-content>
|
||||||
|
<a-range-picker
|
||||||
|
class="w-[220px]"
|
||||||
|
v-model:value="dateRange"
|
||||||
|
format="YYYY-MM-DD"
|
||||||
|
:show-time="showTimeConfig"
|
||||||
|
:allowClear="false"
|
||||||
|
:presets="DateSetting.RangeButton.month1"
|
||||||
|
:disabled-date="disabledDate"
|
||||||
|
size="small"
|
||||||
|
@change="handleSearch"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<a-spin :spinning="isLoading" tip="加载中...">
|
||||||
|
<div class="monitor-chart">
|
||||||
|
<div ref="chartRef" class="chart-container"></div>
|
||||||
|
<a-empty
|
||||||
|
v-if="!chartData || chartData.length === 0"
|
||||||
|
description="暂无数据"
|
||||||
|
class="chart-empty"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="monitor-table">
|
||||||
|
<BasicTable
|
||||||
|
ref="tableRef"
|
||||||
|
row-key="dt"
|
||||||
|
:scrollY="240"
|
||||||
|
:scrollX="tableScrollX"
|
||||||
|
:columns="tableColumns"
|
||||||
|
:data="tableData"
|
||||||
|
:paginationConfig="{
|
||||||
|
showSizeChanger: false,
|
||||||
|
showQuickJumper: false
|
||||||
|
}"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</a-spin>
|
||||||
|
</SidePanelItem>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import {
|
||||||
|
ref,
|
||||||
|
watch,
|
||||||
|
computed,
|
||||||
|
onMounted,
|
||||||
|
onBeforeUnmount,
|
||||||
|
nextTick
|
||||||
|
} from 'vue';
|
||||||
|
import dayjs, { Dayjs } from 'dayjs';
|
||||||
|
import * as echarts from 'echarts';
|
||||||
|
import { useUiStore } from '@/store/modules/ui';
|
||||||
|
import { getMonitorDataWaterTempVertical } from '@/api/mapModal';
|
||||||
|
import { useModelStore } from '@/store/modules/model';
|
||||||
|
import SidePanelItem from '@/components/SidePanelItem/index.vue';
|
||||||
|
import BasicTable from '@/components/BasicTable/index.vue';
|
||||||
|
import { DateSetting } from '@/utils/enumeration';
|
||||||
|
|
||||||
|
const uiStore = useUiStore();
|
||||||
|
const modelStore = useModelStore();
|
||||||
|
|
||||||
|
const isLoading = ref(false);
|
||||||
|
const tableData = ref<any[]>([]);
|
||||||
|
const chartData = ref<any[]>([]);
|
||||||
|
const tableRef = ref<any>();
|
||||||
|
|
||||||
|
// ==================== 图表 ====================
|
||||||
|
const chartRef = ref<HTMLElement>();
|
||||||
|
let chartInstance: echarts.ECharts | null = null;
|
||||||
|
let resizeObserver: ResizeObserver | null = null;
|
||||||
|
|
||||||
|
const initChart = () => {
|
||||||
|
if (!chartRef.value) return;
|
||||||
|
if (chartInstance) chartInstance.dispose();
|
||||||
|
chartInstance = echarts.init(chartRef.value);
|
||||||
|
updateChart(chartData.value);
|
||||||
|
};
|
||||||
|
|
||||||
|
const COLORS = [
|
||||||
|
'#5470c6',
|
||||||
|
'#91cc75',
|
||||||
|
'#fac858',
|
||||||
|
'#ee6666',
|
||||||
|
'#73c0de',
|
||||||
|
'#3ba272',
|
||||||
|
'#fc8452',
|
||||||
|
'#9a60b4',
|
||||||
|
'#ea7ccc',
|
||||||
|
'#5470c6',
|
||||||
|
'#73c0de',
|
||||||
|
'#fac858',
|
||||||
|
'#91cc75',
|
||||||
|
'#ee6666',
|
||||||
|
'#3ba272',
|
||||||
|
'#fc8452',
|
||||||
|
'#9a60b4',
|
||||||
|
'#ea7ccc',
|
||||||
|
'#5470c6',
|
||||||
|
'#73c0de'
|
||||||
|
];
|
||||||
|
|
||||||
|
const updateChart = (rows: any[]) => {
|
||||||
|
if (!chartInstance) return;
|
||||||
|
if (!rows || rows.length === 0) {
|
||||||
|
chartInstance.clear();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 为每个数据行生成一条垂向水温分布曲线
|
||||||
|
const series = rows.map((row, index) => {
|
||||||
|
const data = Object.entries(row.dataList || {})
|
||||||
|
.map(([depth, temp]) => [parseFloat(temp as string), parseFloat(depth)])
|
||||||
|
.sort((a, b) => a[1] - b[1]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
name: dayjs(row.dt).format('YYYY-MM-DD HH:mm'),
|
||||||
|
type: 'line',
|
||||||
|
data,
|
||||||
|
smooth: false,
|
||||||
|
symbol: 'circle',
|
||||||
|
symbolSize: 4,
|
||||||
|
lineStyle: { width: 2, color: COLORS[index % COLORS.length] },
|
||||||
|
itemStyle: { color: COLORS[index % COLORS.length] }
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const option = {
|
||||||
|
title: {
|
||||||
|
text: '垂向水温分布',
|
||||||
|
top: 0,
|
||||||
|
left: 'center'
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'axis',
|
||||||
|
axisPointer: {
|
||||||
|
type: 'cross',
|
||||||
|
label: {
|
||||||
|
formatter: (value: any) => {
|
||||||
|
if (value.axisDimension == 'x') {
|
||||||
|
return value.value.toFixed(1) + '℃';
|
||||||
|
} else if (value.axisDimension == 'y') {
|
||||||
|
return value.value.toFixed(2) + 'm';
|
||||||
|
}
|
||||||
|
|
||||||
|
return value.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
formatter: (params: any) => {
|
||||||
|
let html = params[0].seriesName + '\n';
|
||||||
|
params.forEach((item: any) => {
|
||||||
|
html += `<div style="display:flex;"><div style="width:40px;">${item.data[1]}m :</div> <div>${item.data[0]}℃</div></div>`;
|
||||||
|
});
|
||||||
|
return html;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
type: 'scroll',
|
||||||
|
top: 30,
|
||||||
|
data: series.map(s => s.name),
|
||||||
|
textStyle: { fontSize: 12 }
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
left: 40,
|
||||||
|
right: 70,
|
||||||
|
top: 80,
|
||||||
|
bottom: 40,
|
||||||
|
containLabel: false
|
||||||
|
},
|
||||||
|
xAxis: {
|
||||||
|
position: 'top',
|
||||||
|
name: '水温 (℃)',
|
||||||
|
nameLocation: 'end',
|
||||||
|
type: 'value',
|
||||||
|
scale: true,
|
||||||
|
boundaryGap: ['10%', '10%'],
|
||||||
|
axisLabel: {
|
||||||
|
fontSize: 12,
|
||||||
|
color: '#000000',
|
||||||
|
formatter: function (value: number) {
|
||||||
|
return value.toFixed(1) + '℃';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
splitLine: { show: true }
|
||||||
|
},
|
||||||
|
yAxis: {
|
||||||
|
name: '水深 (m)',
|
||||||
|
type: 'value',
|
||||||
|
inverse: true,
|
||||||
|
nameLocation: 'end',
|
||||||
|
min: 0,
|
||||||
|
axisLabel: {
|
||||||
|
fontSize: 12,
|
||||||
|
color: '#000000',
|
||||||
|
formatter: function (value: number) {
|
||||||
|
return value + 'm';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
splitLine: { show: true }
|
||||||
|
},
|
||||||
|
series,
|
||||||
|
toolbox: {
|
||||||
|
show: true,
|
||||||
|
feature: {
|
||||||
|
saveAsImage: { title: '保存为图片', type: 'png', pixelRatio: 2 }
|
||||||
|
},
|
||||||
|
right: 20,
|
||||||
|
top: 10
|
||||||
|
},
|
||||||
|
dataZoom: {
|
||||||
|
type: 'inside',
|
||||||
|
xAxisIndex: [0],
|
||||||
|
throttle: 50,
|
||||||
|
start: 0,
|
||||||
|
end: 100
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
chartInstance.setOption(option, true);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 数据变化时动态刷新图表
|
||||||
|
watch(chartData, newData => {
|
||||||
|
updateChart(newData);
|
||||||
|
});
|
||||||
|
|
||||||
|
const safeResize = () => {
|
||||||
|
if (!chartInstance) return;
|
||||||
|
// 容器不可见(display:none / 宽度为 0)时跳过,避免收起/切换时对大量 series 做昂贵的同步重排
|
||||||
|
if (!chartRef.value || chartRef.value.clientWidth === 0) return;
|
||||||
|
chartInstance.resize();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleResize = () => {
|
||||||
|
safeResize();
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
nextTick(() => {
|
||||||
|
initChart();
|
||||||
|
setTimeout(() => {
|
||||||
|
safeResize();
|
||||||
|
}, 200);
|
||||||
|
if (!resizeObserver && chartRef.value) {
|
||||||
|
resizeObserver = new ResizeObserver(() => {
|
||||||
|
safeResize();
|
||||||
|
});
|
||||||
|
resizeObserver.observe(chartRef.value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
window.addEventListener('resize', handleResize);
|
||||||
|
});
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
if (chartInstance) {
|
||||||
|
chartInstance.dispose();
|
||||||
|
chartInstance = null;
|
||||||
|
}
|
||||||
|
window.removeEventListener('resize', handleResize);
|
||||||
|
if (resizeObserver) {
|
||||||
|
resizeObserver.disconnect();
|
||||||
|
resizeObserver = null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 固定按小时粒度查询
|
||||||
|
const showTimeConfig = {
|
||||||
|
format: 'HH:mm',
|
||||||
|
hourStep: 1,
|
||||||
|
minuteStep: 5,
|
||||||
|
secondStep: 60
|
||||||
|
};
|
||||||
|
const disabledDate = (current: Dayjs) =>
|
||||||
|
current && current.isAfter(dayjs(), 'day');
|
||||||
|
|
||||||
|
const initDateRange = (): [Dayjs, Dayjs] => {
|
||||||
|
const startDate = dayjs().subtract(7, 'day').startOf('day');
|
||||||
|
return [startDate, dayjs()];
|
||||||
|
};
|
||||||
|
const dateRange = ref<[Dayjs, Dayjs] | undefined>(initDateRange());
|
||||||
|
|
||||||
|
// ==================== 表格列 ====================
|
||||||
|
const columns = ref<any[]>([]);
|
||||||
|
|
||||||
|
const tableColumns = computed(() => {
|
||||||
|
const fixedCols = [
|
||||||
|
{
|
||||||
|
title: '测站名称',
|
||||||
|
dataIndex: 'stnm',
|
||||||
|
width: 150
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '时间',
|
||||||
|
dataIndex: 'dt',
|
||||||
|
width: 140,
|
||||||
|
customRender: ({ text }: any) =>
|
||||||
|
text ? dayjs(text).format('YYYY-MM-DD HH:mm') : '-'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
console.log(columns.value);
|
||||||
|
|
||||||
|
const excludeDataIndexes = fixedCols.map(col => col.dataIndex);
|
||||||
|
// 展示全部水深列
|
||||||
|
const dataCols = columns.value
|
||||||
|
|
||||||
|
.filter((col: any) => !excludeDataIndexes.includes(col.dataIndex))
|
||||||
|
.map((col: any) => ({
|
||||||
|
title: col.title,
|
||||||
|
dataIndex: col.dataIndex,
|
||||||
|
width: 120,
|
||||||
|
customRender: ({ record }: any) => {
|
||||||
|
const val = record.dataList?.[col.dataIndex];
|
||||||
|
return val !== undefined && val !== null ? val : '-';
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
return [...fixedCols, ...dataCols];
|
||||||
|
});
|
||||||
|
|
||||||
|
const tableScrollX = computed(() => {
|
||||||
|
const cols = tableColumns.value;
|
||||||
|
const total = cols.reduce((s: number, c: any) => s + (c.width || 100), 0);
|
||||||
|
console.log(total);
|
||||||
|
return total > 600 ? total : undefined;
|
||||||
|
});
|
||||||
|
|
||||||
|
// ==================== 数据请求 ====================
|
||||||
|
// 数据请求(查询参数取 modelStore.selectedAnchorPoint)
|
||||||
|
const fetchData = async () => {
|
||||||
|
const stcd = modelStore.selectedAnchorPoint?.stcd;
|
||||||
|
if (!dateRange.value || !stcd) return;
|
||||||
|
isLoading.value = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const filterParams = {
|
||||||
|
filter: {
|
||||||
|
logic: 'and',
|
||||||
|
filters: [
|
||||||
|
{
|
||||||
|
field: 'stcd',
|
||||||
|
operator: 'eq',
|
||||||
|
dataType: 'string',
|
||||||
|
value: stcd
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'drtp',
|
||||||
|
operator: 'eq',
|
||||||
|
dataType: 'string',
|
||||||
|
value: 'HOUR'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'tm',
|
||||||
|
operator: 'gte',
|
||||||
|
dataType: 'date',
|
||||||
|
value: dateRange.value[0].format('YYYY-MM-DD HH:mm:ss')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'tm',
|
||||||
|
operator: 'lte',
|
||||||
|
dataType: 'date',
|
||||||
|
value: dateRange.value[1].format('YYYY-MM-DD HH:mm:ss')
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
sort: [{ field: 'dt', dir: 'asc' }]
|
||||||
|
};
|
||||||
|
|
||||||
|
const res = await getMonitorDataWaterTempVertical(filterParams);
|
||||||
|
const responseData = res?.data?.data || res?.data || {};
|
||||||
|
const item = Array.isArray(responseData) ? responseData[0] : responseData;
|
||||||
|
|
||||||
|
const cols = item?.columns || [];
|
||||||
|
const dataSource = item?.dataSource || item?.data || [];
|
||||||
|
|
||||||
|
columns.value = cols;
|
||||||
|
tableData.value = [...dataSource].reverse();
|
||||||
|
chartData.value = JSON.parse(JSON.stringify(tableData.value));
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取数据失败:', error);
|
||||||
|
tableData.value = [];
|
||||||
|
chartData.value = [];
|
||||||
|
columns.value = [];
|
||||||
|
} finally {
|
||||||
|
isLoading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 时间选择器 change 触发查询
|
||||||
|
const handleSearch = () => fetchData();
|
||||||
|
watch(
|
||||||
|
() => uiStore.searchDrawerOpen,
|
||||||
|
newVal => {
|
||||||
|
if (!newVal) {
|
||||||
|
dateRange.value = initDateRange();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
// 选中点位变化时刷新;初始已有点位时立即加载
|
||||||
|
watch(
|
||||||
|
() => modelStore.selectedAnchorPoint?.stcd,
|
||||||
|
(newStcd, oldStcd) => {
|
||||||
|
if (!newStcd) return;
|
||||||
|
if (newStcd !== oldStcd) fetchData();
|
||||||
|
},
|
||||||
|
{ immediate: true }
|
||||||
|
);
|
||||||
|
|
||||||
|
// 全局时间范围变化时同步本地日期选择器(默认参数联动)
|
||||||
|
watch(
|
||||||
|
() => modelStore.filter.rangeTm,
|
||||||
|
newRange => {
|
||||||
|
if (Array.isArray(newRange) && newRange.length > 0) {
|
||||||
|
dateRange.value = [dayjs(newRange[0]), dayjs(newRange[1])];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ deep: true }
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.monitor-chart {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
height: 300px;
|
||||||
|
|
||||||
|
.chart-container {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-empty {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.monitor-table {
|
||||||
|
margin-top: 10px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
// 表格列数多、tableScrollX 较宽(可达数千像素),必须限制在抽屉宽度内横向滚动,
|
||||||
|
// 否则在 RightDrawer 的 .ant-drawer-content { overflow: visible } 下会溢出抽屉,
|
||||||
|
// 抽屉滑出动画时看起来像"抽屉变 100% 宽"
|
||||||
|
width: 410px;
|
||||||
|
overflow-x: auto;
|
||||||
|
|
||||||
|
.table-container {
|
||||||
|
border: 1px solid #dcdfe6;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -0,0 +1,696 @@
|
|||||||
|
<template>
|
||||||
|
<SidePanelItem title="水质监测数据" :shrink="false">
|
||||||
|
<template #title-right-content>
|
||||||
|
<a-range-picker
|
||||||
|
class="w-[220px]"
|
||||||
|
v-model:value="dateRange"
|
||||||
|
format="YYYY-MM-DD HH"
|
||||||
|
:show-time="showTimeConfig"
|
||||||
|
:allowClear="false"
|
||||||
|
:presets="DateSetting.RangeButton.hour"
|
||||||
|
:disabled-date="disabledDate"
|
||||||
|
size="small"
|
||||||
|
@change="handleSearch"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<div class="tab-checkbox" v-if="filteredCheckBoxOptions.length > 0">
|
||||||
|
<a-checkbox-group
|
||||||
|
v-model:value="selectedColumns"
|
||||||
|
name="checkboxgroup"
|
||||||
|
:options="filteredCheckBoxOptions"
|
||||||
|
class="checkbox-group"
|
||||||
|
@change="handleCheckboxChange"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<a-spin :spinning="isLoading" tip="加载中...">
|
||||||
|
<div class="monitor-chart">
|
||||||
|
<div ref="chartRef" class="chart-container"></div>
|
||||||
|
<a-empty
|
||||||
|
v-if="isDataEmpty"
|
||||||
|
description="暂无数据"
|
||||||
|
class="chart-empty"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="monitor-table">
|
||||||
|
<BasicTable
|
||||||
|
ref="tableRef"
|
||||||
|
:scrollY="240"
|
||||||
|
:scrollX="tableScrollX"
|
||||||
|
:columns="currentColumns"
|
||||||
|
:data="tableData"
|
||||||
|
:paginationConfig="{
|
||||||
|
showSizeChanger: false,
|
||||||
|
showQuickJumper: false
|
||||||
|
}"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</a-spin>
|
||||||
|
</SidePanelItem>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import {
|
||||||
|
ref,
|
||||||
|
watch,
|
||||||
|
computed,
|
||||||
|
onMounted,
|
||||||
|
onBeforeUnmount,
|
||||||
|
nextTick
|
||||||
|
} from 'vue';
|
||||||
|
import dayjs, { Dayjs } from 'dayjs';
|
||||||
|
import * as echarts from 'echarts';
|
||||||
|
import {
|
||||||
|
getMonitorDataWaterQuality,
|
||||||
|
getMonitorDataWaterQualityDetail,
|
||||||
|
getMonitorDataWaterQualityList
|
||||||
|
} from '@/api/mapModal';
|
||||||
|
import { useModelStore } from '@/store/modules/model';
|
||||||
|
import { useUiStore } from '@/store/modules/ui';
|
||||||
|
import SidePanelItem from '@/components/SidePanelItem/index.vue';
|
||||||
|
import BasicTable from '@/components/BasicTable/index.vue';
|
||||||
|
import { DateSetting } from '@/utils/enumeration';
|
||||||
|
|
||||||
|
const uiStore = useUiStore();
|
||||||
|
const modelStore = useModelStore();
|
||||||
|
|
||||||
|
const isLoading = ref(false);
|
||||||
|
const tableData = ref<any[]>([]);
|
||||||
|
const chartData = ref<any[]>([]);
|
||||||
|
const tableRef = ref<any>();
|
||||||
|
|
||||||
|
// ==================== 图表 ====================
|
||||||
|
const chartRef = ref<HTMLElement>();
|
||||||
|
let chartInstance: echarts.ECharts | null = null;
|
||||||
|
let resizeObserver: ResizeObserver | null = null;
|
||||||
|
|
||||||
|
const initChart = () => {
|
||||||
|
if (!chartRef.value) return;
|
||||||
|
if (chartInstance) chartInstance.dispose();
|
||||||
|
chartInstance = echarts.init(chartRef.value);
|
||||||
|
updateChart(chartData.value, allDetailParams.value);
|
||||||
|
bindLegendEvent();
|
||||||
|
};
|
||||||
|
|
||||||
|
// 时间选择器配置
|
||||||
|
const showTimeConfig = {
|
||||||
|
format: 'HH',
|
||||||
|
hourStep: 1,
|
||||||
|
minuteStep: 5,
|
||||||
|
secondStep: 60
|
||||||
|
};
|
||||||
|
const disabledDate = (current: Dayjs) =>
|
||||||
|
current && current.isAfter(dayjs(), 'day');
|
||||||
|
|
||||||
|
const initDateRange = (): [Dayjs, Dayjs] => {
|
||||||
|
const startDate = dayjs().subtract(7, 'day').startOf('day');
|
||||||
|
return [startDate, dayjs()];
|
||||||
|
};
|
||||||
|
const dateRange = ref<[Dayjs, Dayjs] | undefined>(initDateRange());
|
||||||
|
|
||||||
|
// ==================== Checkbox 配置 ====================
|
||||||
|
const selectedColumns = ref<any[]>([]);
|
||||||
|
const checkBoxOptions = [{ label: '综合分析', value: 'summary' }];
|
||||||
|
const showSummaryCheckbox = ref(false);
|
||||||
|
|
||||||
|
const isDataEmpty = computed(() => {
|
||||||
|
const data = Array.isArray(chartData.value) ? chartData.value : [];
|
||||||
|
return data.length === 0;
|
||||||
|
});
|
||||||
|
|
||||||
|
const filteredCheckBoxOptions = computed(() => {
|
||||||
|
if (isDataEmpty.value) return [];
|
||||||
|
|
||||||
|
const available = checkBoxOptions.filter(opt => {
|
||||||
|
if (opt.value === 'summary' && !showSummaryCheckbox.value) return false;
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
return available;
|
||||||
|
});
|
||||||
|
|
||||||
|
const isSummaryMode = computed(() => selectedColumns.value.includes('summary'));
|
||||||
|
|
||||||
|
const handleCheckboxChange = (values: any[]) => {
|
||||||
|
selectedColumns.value = values;
|
||||||
|
if (values.length === 0) {
|
||||||
|
selectedColumns.value = [];
|
||||||
|
}
|
||||||
|
fetchData();
|
||||||
|
};
|
||||||
|
|
||||||
|
// ==================== 动态参数(用于图表和表格) ====================
|
||||||
|
const allDetailParams = ref<any[]>([]); // 所有参数(用于表格列判断和图表series)
|
||||||
|
const visibleSeriesNames = ref<string[]>([]); // 当前图表显示的series名称(最多2个)
|
||||||
|
|
||||||
|
// 解析showControl获取unit
|
||||||
|
const parseUnit = (showControl: string) => {
|
||||||
|
if (!showControl) return '';
|
||||||
|
try {
|
||||||
|
const config = JSON.parse(showControl);
|
||||||
|
return config.unit || '';
|
||||||
|
} catch {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// ==================== 表格列配置 ====================
|
||||||
|
// 固定左侧列
|
||||||
|
const fixedColumns = [
|
||||||
|
{
|
||||||
|
title: '时间',
|
||||||
|
dataIndex: 'tm',
|
||||||
|
width: 160,
|
||||||
|
customRender: ({ text }: any) =>
|
||||||
|
text ? dayjs(text).format('YYYY-MM-DD HH:mm') : '-'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '水质要求',
|
||||||
|
dataIndex: 'wwqtgName',
|
||||||
|
width: 100
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '水质等级',
|
||||||
|
dataIndex: 'wqgrdName',
|
||||||
|
width: 100
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '是否达标',
|
||||||
|
dataIndex: 'sfdb',
|
||||||
|
width: 100,
|
||||||
|
customRender: ({ text }: any) => {
|
||||||
|
const isUnqualified = text == 0; // 0是不达标,1是达标
|
||||||
|
return {
|
||||||
|
props: {
|
||||||
|
style: { color: isUnqualified ? 'red' : 'inherit' }
|
||||||
|
},
|
||||||
|
children: text == 0 ? '不达标' : text == 1 ? '达标' : '-'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
// 动态水质参数列,根据接口返回生成
|
||||||
|
const fixedQualityColumns = computed(() => {
|
||||||
|
const columns = allDetailParams.value.map((p: any) => {
|
||||||
|
const unit = parseUnit(p.showControl);
|
||||||
|
const title = unit ? `${p.ysShowName}(${unit})` : p.ysShowName;
|
||||||
|
const dataIndex = p.ys.toLowerCase();
|
||||||
|
const isEnabled = p.enable == 1;
|
||||||
|
|
||||||
|
// 宽度判断:默认100,超过5个字140,超过6个字160
|
||||||
|
const titleLen = title.length;
|
||||||
|
let width = 100;
|
||||||
|
if (titleLen > 6) {
|
||||||
|
width = 160;
|
||||||
|
} else if (titleLen > 5) {
|
||||||
|
width = 140;
|
||||||
|
}
|
||||||
|
|
||||||
|
const fieldUpper = p.ys.toUpperCase(); // max/min 中的字段是大写
|
||||||
|
|
||||||
|
return {
|
||||||
|
title,
|
||||||
|
dataIndex,
|
||||||
|
enabled: isEnabled,
|
||||||
|
width,
|
||||||
|
customRender: ({ text, record }: any) => {
|
||||||
|
if (!isEnabled) return '-'; // enable=0 显示-
|
||||||
|
if (text === undefined || text === null || text === '') return '-';
|
||||||
|
const num = Number(text);
|
||||||
|
if (isNaN(num)) return '-';
|
||||||
|
|
||||||
|
// 判断是否超出 max/min(max/min 从 record 中取)
|
||||||
|
const maxList = record?.max;
|
||||||
|
const minList = record?.min;
|
||||||
|
let isOutOfRange = false;
|
||||||
|
|
||||||
|
if (Array.isArray(maxList) && maxList.length > 0) {
|
||||||
|
const maxItem = maxList.find((m: any) => m[fieldUpper] !== undefined);
|
||||||
|
if (maxItem && num > maxItem[fieldUpper]) {
|
||||||
|
isOutOfRange = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (Array.isArray(minList) && minList.length > 0) {
|
||||||
|
const minItem = minList.find((m: any) => m[fieldUpper] !== undefined);
|
||||||
|
if (minItem && num < minItem[fieldUpper]) {
|
||||||
|
isOutOfRange = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
props: {
|
||||||
|
style: { color: isOutOfRange ? 'red' : 'inherit' }
|
||||||
|
},
|
||||||
|
children: num
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
// 综合分析模式下添加出库流量列
|
||||||
|
if (isSummaryMode.value) {
|
||||||
|
columns.push({
|
||||||
|
title: '出库流量(m³/s)',
|
||||||
|
dataIndex: 'qo',
|
||||||
|
width: 140,
|
||||||
|
customRender: ({ text }: any) => {
|
||||||
|
if (text === undefined || text === null || text === '') return '-';
|
||||||
|
const num = Number(text);
|
||||||
|
return isNaN(num) ? '-' : num;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return columns;
|
||||||
|
});
|
||||||
|
|
||||||
|
const currentColumns = computed(() => {
|
||||||
|
return [...fixedColumns, ...fixedQualityColumns.value];
|
||||||
|
});
|
||||||
|
|
||||||
|
const tableScrollX = computed(() => {
|
||||||
|
const columns = currentColumns.value;
|
||||||
|
const totalWidth = columns.reduce(
|
||||||
|
(sum: number, col: any) => sum + (col.width || 120),
|
||||||
|
0
|
||||||
|
);
|
||||||
|
return totalWidth > 600 ? totalWidth : undefined;
|
||||||
|
});
|
||||||
|
|
||||||
|
// ==================== 图表 ====================
|
||||||
|
function shouldStagger(width: number, dataLen: number) {
|
||||||
|
return dataLen > Math.floor(width / 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getAxisLabelConfig(chartWidth: number, xAxisData: string[]) {
|
||||||
|
const needStagger = shouldStagger(chartWidth, xAxisData.length);
|
||||||
|
return {
|
||||||
|
fontSize: 12,
|
||||||
|
interval: 'auto',
|
||||||
|
formatter: (value: string, index: number) => {
|
||||||
|
const [date, time] = value.split(' ');
|
||||||
|
if (needStagger) {
|
||||||
|
return index % 2 === 0 ? `${date}\n${time}\n ` : ` \n${date}\n${time}`;
|
||||||
|
}
|
||||||
|
return `${date}\n${time}`;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateChart = (data: any[], enabledParams: any[]) => {
|
||||||
|
if (!chartInstance) return;
|
||||||
|
if (!data || data.length === 0) {
|
||||||
|
chartInstance.clear();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const sorted = [...data].sort(
|
||||||
|
(a, b) => new Date(a.tm).getTime() - new Date(b.tm).getTime()
|
||||||
|
);
|
||||||
|
const xAxisData = sorted.map(item =>
|
||||||
|
dayjs(item.tm).format('YYYY-MM-DD HH:mm')
|
||||||
|
);
|
||||||
|
const width = chartRef.value?.clientWidth || 0;
|
||||||
|
const axisLabelConfig = getAxisLabelConfig(width, xAxisData);
|
||||||
|
|
||||||
|
// 生成所有series(从接口获取所有参数)
|
||||||
|
const allSeries = enabledParams.map((param: any) => {
|
||||||
|
const showControl = param.showControl || '{}';
|
||||||
|
let lineColor = '';
|
||||||
|
let unit = '';
|
||||||
|
try {
|
||||||
|
const config = JSON.parse(showControl);
|
||||||
|
lineColor = config.lineColor || '';
|
||||||
|
unit = config.unit || '';
|
||||||
|
} catch {
|
||||||
|
// 解析失败使用默认颜色
|
||||||
|
}
|
||||||
|
const dataIndex = param.ys.toLowerCase();
|
||||||
|
const legendName = param.ysShowName; // legend不显示unit
|
||||||
|
const yAxisName = unit ? `${param.ysShowName}\n${unit}` : param.ysShowName; // Y轴显示名称+unit
|
||||||
|
const color = lineColor;
|
||||||
|
|
||||||
|
return {
|
||||||
|
name: legendName,
|
||||||
|
ysShowName: param.ysShowName,
|
||||||
|
unit,
|
||||||
|
yAxisName,
|
||||||
|
type: 'line',
|
||||||
|
data: sorted.map(item => item[dataIndex]),
|
||||||
|
smooth: true,
|
||||||
|
symbol: 'none',
|
||||||
|
lineStyle: { color: color, width: 2 },
|
||||||
|
itemStyle: { color: color }
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
// 综合分析模式下添加出库流量series(默认不选中)
|
||||||
|
if (isSummaryMode.value) {
|
||||||
|
allSeries.push({
|
||||||
|
name: '出库流量',
|
||||||
|
ysShowName: '出库流量',
|
||||||
|
unit: 'm³/s',
|
||||||
|
yAxisName: '出库流量\nm³/s',
|
||||||
|
type: 'line',
|
||||||
|
data: sorted.map(item => item.qo),
|
||||||
|
smooth: true,
|
||||||
|
symbol: 'none',
|
||||||
|
lineStyle: { color: '#4B79AB', width: 2 },
|
||||||
|
itemStyle: { color: '#4B79AB' }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const legendData = allSeries.map((s: any) => s.name);
|
||||||
|
|
||||||
|
// 初始化可见series:默认只选中溶解氧
|
||||||
|
if (visibleSeriesNames.value.length === 0) {
|
||||||
|
const defaultName = '溶解氧';
|
||||||
|
if (legendData.includes(defaultName)) {
|
||||||
|
visibleSeriesNames.value = [defaultName];
|
||||||
|
} else if (legendData.length > 0) {
|
||||||
|
visibleSeriesNames.value = [legendData[0]];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// series 包含所有数据,通过 legend selected 控制显示
|
||||||
|
const series = allSeries;
|
||||||
|
|
||||||
|
// 构建Y轴配置(最多两个,基于 visibleSeriesNames)
|
||||||
|
const yAxisConfig: any[] = [];
|
||||||
|
const visibleSeriesList = series.filter((s: any) =>
|
||||||
|
visibleSeriesNames.value.includes(s.name)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (visibleSeriesList.length >= 1) {
|
||||||
|
yAxisConfig.push({
|
||||||
|
name: visibleSeriesList[0].yAxisName,
|
||||||
|
type: 'value',
|
||||||
|
position: 'left',
|
||||||
|
scale: true,
|
||||||
|
splitLine: {
|
||||||
|
show: true,
|
||||||
|
lineStyle: { color: '#bfbfbf', type: 'solid' }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// 给所有series设置yAxisIndex
|
||||||
|
series.forEach((s: any) => {
|
||||||
|
s.yAxisIndex = visibleSeriesNames.value.includes(s.name) ? 0 : 0;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (visibleSeriesList.length >= 2) {
|
||||||
|
yAxisConfig.push({
|
||||||
|
name: visibleSeriesList[1].yAxisName,
|
||||||
|
type: 'value',
|
||||||
|
position: 'right',
|
||||||
|
scale: true,
|
||||||
|
splitLine: { show: false }
|
||||||
|
});
|
||||||
|
visibleSeriesList[0].yAxisIndex = 0;
|
||||||
|
visibleSeriesList[1].yAxisIndex = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构建legend selected状态
|
||||||
|
const legendSelected = legendData.reduce((acc: any, name) => {
|
||||||
|
acc[name] = visibleSeriesNames.value.includes(name);
|
||||||
|
return acc;
|
||||||
|
}, {});
|
||||||
|
|
||||||
|
const option: any = {
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'axis',
|
||||||
|
backgroundColor: 'rgba(50,50,50,0.9)',
|
||||||
|
textStyle: { color: '#fff', fontSize: 12 },
|
||||||
|
axisPointer: { type: 'cross' },
|
||||||
|
formatter: (params: any) => {
|
||||||
|
if (!params?.length) return '';
|
||||||
|
const idx = params[0].dataIndex;
|
||||||
|
const fullTime = sorted[idx]?.tm
|
||||||
|
? dayjs(sorted[idx].tm).format('YYYY-MM-DD HH:mm')
|
||||||
|
: '';
|
||||||
|
let html = `<div style="font-size:16px;margin-bottom:8px;">${fullTime}</div>`;
|
||||||
|
params.forEach((p: any) => {
|
||||||
|
const v = p.value != null ? Number(p.value).toFixed(2) : '-';
|
||||||
|
html += `<div style="display:flex;align-items:center;justify-content:space-between;margin:4px 0;"><span style="display:inline-block;width:10px;height:10px;border-radius:50%;background:${p.color};margin-right:8px;"></span><span style="flex:1;font-size:14px;">${p.seriesName}: </span><span style="font-size:14px;min-width:60px;text-align:right;"><strong>${v}</strong></span></div>`;
|
||||||
|
});
|
||||||
|
return html;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
type: 'scroll',
|
||||||
|
width: '80%',
|
||||||
|
right: 60,
|
||||||
|
top: 10,
|
||||||
|
data: legendData,
|
||||||
|
textStyle: { fontSize: 12 },
|
||||||
|
selected: legendSelected
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
left: 60,
|
||||||
|
right: series.length >= 2 ? 60 : 40,
|
||||||
|
top: 80,
|
||||||
|
bottom: 60
|
||||||
|
},
|
||||||
|
xAxis: {
|
||||||
|
type: 'category',
|
||||||
|
data: xAxisData,
|
||||||
|
axisLine: { lineStyle: { color: '#000000' } },
|
||||||
|
axisTick: { show: false },
|
||||||
|
axisLabel: axisLabelConfig,
|
||||||
|
splitLine: {
|
||||||
|
show: true,
|
||||||
|
lineStyle: { color: '#bfbfbf', type: 'solid' }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
yAxis: yAxisConfig.length === 1 ? yAxisConfig[0] : yAxisConfig,
|
||||||
|
series,
|
||||||
|
dataZoom: [
|
||||||
|
{ type: 'inside', xAxisIndex: [0], throttle: 50, start: 0, end: 100 }
|
||||||
|
],
|
||||||
|
toolbox: {
|
||||||
|
show: true,
|
||||||
|
feature: {
|
||||||
|
saveAsImage: { title: '保存为图片', type: 'png', pixelRatio: 2 }
|
||||||
|
},
|
||||||
|
right: 20,
|
||||||
|
top: 10
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
chartInstance.setOption(option, true);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 绑定legend切换事件(只绑定一次)
|
||||||
|
const bindLegendEvent = () => {
|
||||||
|
if (!chartInstance) return;
|
||||||
|
chartInstance.off('legendselectchanged');
|
||||||
|
chartInstance.on('legendselectchanged', (params: any) => {
|
||||||
|
const selected = params.selected;
|
||||||
|
const name = params.name;
|
||||||
|
const wasSelected = selected[name]; // echarts already updated internal state
|
||||||
|
|
||||||
|
if (wasSelected) {
|
||||||
|
// 用户点击了选中(从取消变为选中)
|
||||||
|
const prevVisible = [...visibleSeriesNames.value];
|
||||||
|
if (prevVisible.length >= 2) {
|
||||||
|
// 已有2个选中,取消第一个,保留第二个和新点击的
|
||||||
|
visibleSeriesNames.value = [prevVisible[1], name];
|
||||||
|
} else {
|
||||||
|
visibleSeriesNames.value = [...new Set([...prevVisible, name])];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 用户点击了取消选中
|
||||||
|
visibleSeriesNames.value = visibleSeriesNames.value.filter(
|
||||||
|
n => n !== name
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重新渲染图表(使用allDetailParams包含所有参数)
|
||||||
|
updateChart(chartData.value, allDetailParams.value);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// ==================== 数据请求 ====================
|
||||||
|
const fetchData = async () => {
|
||||||
|
const stcd = modelStore.selectedAnchorPoint?.stcd;
|
||||||
|
if (!dateRange.value || !stcd) return;
|
||||||
|
|
||||||
|
isLoading.value = true;
|
||||||
|
try {
|
||||||
|
const filterParams = {
|
||||||
|
filter: {
|
||||||
|
logic: 'and',
|
||||||
|
filters: [
|
||||||
|
{
|
||||||
|
field: 'stcd',
|
||||||
|
operator: 'eq',
|
||||||
|
dataType: 'string',
|
||||||
|
value: stcd
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'tm',
|
||||||
|
operator: 'gte',
|
||||||
|
dataType: 'date',
|
||||||
|
value: dateRange.value[0].format('YYYY-MM-DD HH:mm:ss')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'tm',
|
||||||
|
operator: 'lte',
|
||||||
|
dataType: 'date',
|
||||||
|
value: dateRange.value[1].format('YYYY-MM-DD HH:mm:ss')
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
sort: [{ field: 'tm', dir: 'asc' }]
|
||||||
|
};
|
||||||
|
|
||||||
|
// 并发请求三个接口
|
||||||
|
const [qualityRes, detailRes, listRes] = await Promise.all([
|
||||||
|
getMonitorDataWaterQuality(filterParams),
|
||||||
|
getMonitorDataWaterQualityDetail({
|
||||||
|
stcd,
|
||||||
|
tbCode: 'WQ_R',
|
||||||
|
startTime: dateRange.value[0].format('YYYY-MM-DD HH:mm:ss'),
|
||||||
|
endTime: dateRange.value[1].format('YYYY-MM-DD HH:mm:ss')
|
||||||
|
}),
|
||||||
|
getMonitorDataWaterQualityList(filterParams)
|
||||||
|
]);
|
||||||
|
|
||||||
|
// 判断是否显示综合分析(只有当data不为null且不为空数组时才显示)
|
||||||
|
const qualityData = qualityRes?.data?.data || qualityRes?.data?.records;
|
||||||
|
showSummaryCheckbox.value = !!qualityData && qualityData.length > 0;
|
||||||
|
|
||||||
|
// 处理detail数据
|
||||||
|
const detailData = detailRes?.data?.data || detailRes?.data || [];
|
||||||
|
allDetailParams.value = detailData; // 所有参数用于表格列判断
|
||||||
|
|
||||||
|
// 重置可见series(因为参数可能变化)
|
||||||
|
visibleSeriesNames.value = [];
|
||||||
|
|
||||||
|
// 获取列表数据
|
||||||
|
const listData = listRes?.data?.data || listRes?.data?.records || [];
|
||||||
|
|
||||||
|
tableData.value = [...listData].reverse();
|
||||||
|
chartData.value = listData;
|
||||||
|
nextTick(() => updateChart(listData, allDetailParams.value));
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取水质数据失败:', error);
|
||||||
|
tableData.value = [];
|
||||||
|
chartData.value = [];
|
||||||
|
allDetailParams.value = [];
|
||||||
|
if (chartInstance) chartInstance.clear();
|
||||||
|
} finally {
|
||||||
|
isLoading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 时间选择器 change 触发查询
|
||||||
|
const handleSearch = () => fetchData();
|
||||||
|
|
||||||
|
const safeResize = () => {
|
||||||
|
if (!chartInstance) return;
|
||||||
|
// 容器不可见(display:none / 宽度为 0)时跳过,避免收起/切换时做昂贵的同步重排
|
||||||
|
if (!chartRef.value || chartRef.value.clientWidth === 0) return;
|
||||||
|
chartInstance.resize();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleResize = () => {
|
||||||
|
safeResize();
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
nextTick(() => {
|
||||||
|
initChart();
|
||||||
|
setTimeout(() => {
|
||||||
|
safeResize();
|
||||||
|
}, 200);
|
||||||
|
if (!resizeObserver && chartRef.value) {
|
||||||
|
resizeObserver = new ResizeObserver(() => {
|
||||||
|
safeResize();
|
||||||
|
});
|
||||||
|
resizeObserver.observe(chartRef.value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
window.addEventListener('resize', handleResize);
|
||||||
|
});
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
if (chartInstance) {
|
||||||
|
chartInstance.dispose();
|
||||||
|
chartInstance = null;
|
||||||
|
}
|
||||||
|
window.removeEventListener('resize', handleResize);
|
||||||
|
if (resizeObserver) {
|
||||||
|
resizeObserver.disconnect();
|
||||||
|
resizeObserver = null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => uiStore.searchDrawerOpen,
|
||||||
|
newVal => {
|
||||||
|
if (!newVal) {
|
||||||
|
dateRange.value = initDateRange();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
// 选中点位变化时刷新;初始已有点位时立即加载
|
||||||
|
watch(
|
||||||
|
() => modelStore.selectedAnchorPoint?.stcd,
|
||||||
|
(newStcd, oldStcd) => {
|
||||||
|
if (!newStcd) return;
|
||||||
|
if (newStcd !== oldStcd) fetchData();
|
||||||
|
},
|
||||||
|
{ immediate: true }
|
||||||
|
);
|
||||||
|
|
||||||
|
// 全局时间范围变化时同步本地日期选择器(默认参数联动)
|
||||||
|
watch(
|
||||||
|
() => modelStore.filter.rangeTm,
|
||||||
|
newRange => {
|
||||||
|
if (Array.isArray(newRange) && newRange.length > 0) {
|
||||||
|
dateRange.value = [dayjs(newRange[0]), dayjs(newRange[1])];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ deep: true }
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.tab-checkbox {
|
||||||
|
margin: 4px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.monitor-chart {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
height: 300px;
|
||||||
|
|
||||||
|
.chart-container {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-empty {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.monitor-table {
|
||||||
|
margin-top: 10px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
// 表格列数多、tableScrollX 较宽(可达数千像素),必须限制在抽屉宽度内横向滚动,
|
||||||
|
// 否则在 RightDrawer 的 .ant-drawer-content { overflow: visible } 下会溢出抽屉,
|
||||||
|
// 抽屉滑出动画时看起来像"抽屉变 100% 宽"
|
||||||
|
width: 410px;
|
||||||
|
overflow-x: auto;
|
||||||
|
|
||||||
|
.table-container {
|
||||||
|
border: 1px solid #dcdfe6;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -0,0 +1,531 @@
|
|||||||
|
<template>
|
||||||
|
<SidePanelItem title="水温监测数据" :shrink="false">
|
||||||
|
<template #title-right-content>
|
||||||
|
<a-range-picker
|
||||||
|
class="w-[220px]"
|
||||||
|
v-model:value="dateRange"
|
||||||
|
format="YYYY-MM-DD"
|
||||||
|
:show-time="showTimeConfig"
|
||||||
|
:allowClear="false"
|
||||||
|
:presets="DateSetting.RangeButton.month1"
|
||||||
|
:disabled-date="disabledDate"
|
||||||
|
size="small"
|
||||||
|
@change="handleSearch"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<a-spin :spinning="isLoading" tip="加载中...">
|
||||||
|
<div class="monitor-chart">
|
||||||
|
<div ref="chartRef" class="chart-container"></div>
|
||||||
|
<a-empty
|
||||||
|
v-if="!chartData || chartData.length === 0"
|
||||||
|
description="暂无数据"
|
||||||
|
class="chart-empty"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="monitor-table">
|
||||||
|
<BasicTable
|
||||||
|
ref="tableRef"
|
||||||
|
:scrollY="240"
|
||||||
|
:scrollX="tableScrollX"
|
||||||
|
:columns="currentColumns"
|
||||||
|
:data="tableData"
|
||||||
|
:paginationConfig="{
|
||||||
|
showSizeChanger: false,
|
||||||
|
showQuickJumper: false
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<template #summary>
|
||||||
|
<a-table-summary fixed>
|
||||||
|
<template
|
||||||
|
v-for="summaryRow in summaryRows"
|
||||||
|
:key="summaryRow.label"
|
||||||
|
>
|
||||||
|
<a-table-summary-row class="summary-row">
|
||||||
|
<a-table-summary-cell :index="0">
|
||||||
|
{{ summaryRow.label }}
|
||||||
|
</a-table-summary-cell>
|
||||||
|
<a-table-summary-cell
|
||||||
|
v-for="col in summaryColumns"
|
||||||
|
:key="col.dataIndex"
|
||||||
|
:index="col.summaryIndex"
|
||||||
|
>
|
||||||
|
{{ summaryRow.cells[col.dataIndex]?.value ?? '-' }}
|
||||||
|
<Tooltip
|
||||||
|
v-if="summaryRow.cells[col.dataIndex]?.time"
|
||||||
|
:title="`时间:${summaryRow.cells[col.dataIndex].time}`"
|
||||||
|
placement="top"
|
||||||
|
>
|
||||||
|
<InfoCircleOutlined class="summary-tip-icon" />
|
||||||
|
</Tooltip>
|
||||||
|
</a-table-summary-cell>
|
||||||
|
</a-table-summary-row>
|
||||||
|
</template>
|
||||||
|
</a-table-summary>
|
||||||
|
</template>
|
||||||
|
</BasicTable>
|
||||||
|
</div>
|
||||||
|
</a-spin>
|
||||||
|
</SidePanelItem>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import {
|
||||||
|
ref,
|
||||||
|
watch,
|
||||||
|
computed,
|
||||||
|
onMounted,
|
||||||
|
onBeforeUnmount,
|
||||||
|
nextTick
|
||||||
|
} from 'vue';
|
||||||
|
import dayjs, { Dayjs } from 'dayjs';
|
||||||
|
import * as echarts from 'echarts';
|
||||||
|
import { Tooltip } from 'ant-design-vue';
|
||||||
|
import { useUiStore } from '@/store/modules/ui';
|
||||||
|
import { getMonitorDataWaterTemp } from '@/api/mapModal';
|
||||||
|
import { useModelStore } from '@/store/modules/model';
|
||||||
|
import SidePanelItem from '@/components/SidePanelItem/index.vue';
|
||||||
|
import BasicTable from '@/components/BasicTable/index.vue';
|
||||||
|
import { DateSetting } from '@/utils/enumeration';
|
||||||
|
import { InfoCircleOutlined } from '@ant-design/icons-vue';
|
||||||
|
|
||||||
|
const uiStore = useUiStore();
|
||||||
|
const modelStore = useModelStore();
|
||||||
|
|
||||||
|
const isLoading = ref(false);
|
||||||
|
const chartData = ref<any[]>([]);
|
||||||
|
const tableData = ref<any[]>([]);
|
||||||
|
const tableRef = ref<any>();
|
||||||
|
|
||||||
|
// ==================== 图表 ====================
|
||||||
|
const chartRef = ref<HTMLElement>();
|
||||||
|
let chartInstance: echarts.ECharts | null = null;
|
||||||
|
let resizeObserver: ResizeObserver | null = null;
|
||||||
|
|
||||||
|
const initChart = () => {
|
||||||
|
if (!chartRef.value) return;
|
||||||
|
if (chartInstance) chartInstance.dispose();
|
||||||
|
chartInstance = echarts.init(chartRef.value);
|
||||||
|
updateChart(chartData.value);
|
||||||
|
};
|
||||||
|
|
||||||
|
function shouldStagger(width: number, dataLen: number) {
|
||||||
|
return dataLen > Math.floor(width / 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getAxisLabelConfig(chartWidth: number, xAxisData: string[]) {
|
||||||
|
const needStagger = shouldStagger(chartWidth, xAxisData.length);
|
||||||
|
return {
|
||||||
|
fontSize: 12,
|
||||||
|
interval: 'auto',
|
||||||
|
formatter: (value: string, index: number) => {
|
||||||
|
const [date, time] = value.split(' ');
|
||||||
|
if (needStagger) {
|
||||||
|
return index % 2 === 0 ? `${date}\n${time}\n ` : ` \n${date}\n${time}`;
|
||||||
|
}
|
||||||
|
return `${date}\n${time}`;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateChart = (data: any[]) => {
|
||||||
|
if (!chartInstance) return;
|
||||||
|
if (!data || data.length === 0) {
|
||||||
|
chartInstance.clear();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const sorted = [...data].sort(
|
||||||
|
(a, b) => new Date(a.tm).getTime() - new Date(b.tm).getTime()
|
||||||
|
);
|
||||||
|
const xAxisData = sorted.map(item =>
|
||||||
|
dayjs(item.tm).format('YYYY-MM-DD HH:mm')
|
||||||
|
);
|
||||||
|
const width = chartRef.value?.clientWidth || 0;
|
||||||
|
const axisLabelConfig = getAxisLabelConfig(width, xAxisData);
|
||||||
|
|
||||||
|
const option = {
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'axis',
|
||||||
|
backgroundColor: 'rgba(50,50,50,0.9)',
|
||||||
|
textStyle: { color: '#fff', fontSize: 12 },
|
||||||
|
axisPointer: { type: 'cross' },
|
||||||
|
formatter: (params: any) => {
|
||||||
|
if (!params?.length) return '';
|
||||||
|
const idx = params[0].dataIndex;
|
||||||
|
const fullTime = sorted[idx]?.tm
|
||||||
|
? dayjs(sorted[idx].tm).format('YYYY-MM-DD HH:mm')
|
||||||
|
: '';
|
||||||
|
let html = `<div style="font-size:16px;margin-bottom:8px;">${fullTime}</div>`;
|
||||||
|
params.forEach((p: any) => {
|
||||||
|
const v = p.value != null ? Number(p.value).toFixed(1) : '-';
|
||||||
|
html += `<div style="display:flex;align-items:center;justify-content:space-between;margin:4px 0;"><span style="display:inline-block;width:10px;height:10px;border-radius:50%;background:${p.color};margin-right:8px;"></span><span style="flex:1;font-size:14px;">水温: </span><span style="font-size:14px;min-width:60px;text-align:right;"><strong>${v}</strong> °C</span></div>`;
|
||||||
|
});
|
||||||
|
return html;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
width: '80%',
|
||||||
|
type: 'scroll',
|
||||||
|
top: 10,
|
||||||
|
data: ['水温'],
|
||||||
|
textStyle: { fontSize: 12 }
|
||||||
|
},
|
||||||
|
grid: { left: 40, right: 20, top: 44, bottom: 50 },
|
||||||
|
xAxis: {
|
||||||
|
type: 'category',
|
||||||
|
data: xAxisData,
|
||||||
|
axisLine: { lineStyle: { color: '#000000' } },
|
||||||
|
axisTick: { show: false },
|
||||||
|
axisLabel: axisLabelConfig,
|
||||||
|
splitLine: {
|
||||||
|
show: true,
|
||||||
|
lineStyle: { color: '#bfbfbf', type: 'solid' }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
yAxis: {
|
||||||
|
name: '水温(°C)',
|
||||||
|
type: 'value',
|
||||||
|
axisLine: { lineStyle: { color: '#000000' } },
|
||||||
|
scale: true,
|
||||||
|
splitLine: {
|
||||||
|
show: true,
|
||||||
|
lineStyle: { color: '#bfbfbf', type: 'solid' }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
name: '水温',
|
||||||
|
type: 'line',
|
||||||
|
data: sorted.map(item => item.wt),
|
||||||
|
smooth: true,
|
||||||
|
symbol: 'none',
|
||||||
|
lineStyle: { color: '#4B79AB', width: 2 },
|
||||||
|
itemStyle: { color: '#4B79AB' },
|
||||||
|
areaStyle: { color: '#85A9D0' }
|
||||||
|
}
|
||||||
|
],
|
||||||
|
dataZoom: [
|
||||||
|
{ type: 'inside', xAxisIndex: [0], throttle: 50, start: 0, end: 100 }
|
||||||
|
],
|
||||||
|
toolbox: {
|
||||||
|
show: true,
|
||||||
|
feature: {
|
||||||
|
saveAsImage: { title: '保存为图片', type: 'png', pixelRatio: 2 }
|
||||||
|
},
|
||||||
|
right: 20,
|
||||||
|
top: 10
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
chartInstance.setOption(option, true);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 数据变化时动态刷新图表
|
||||||
|
watch(chartData, newData => {
|
||||||
|
updateChart(newData);
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleResize = () => {
|
||||||
|
if (chartInstance) chartInstance.resize();
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
nextTick(() => {
|
||||||
|
initChart();
|
||||||
|
setTimeout(() => {
|
||||||
|
if (chartInstance) chartInstance.resize();
|
||||||
|
}, 200);
|
||||||
|
if (!resizeObserver && chartRef.value) {
|
||||||
|
resizeObserver = new ResizeObserver(() => {
|
||||||
|
if (chartInstance) chartInstance.resize();
|
||||||
|
});
|
||||||
|
resizeObserver.observe(chartRef.value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
window.addEventListener('resize', handleResize);
|
||||||
|
});
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
if (chartInstance) {
|
||||||
|
chartInstance.dispose();
|
||||||
|
chartInstance = null;
|
||||||
|
}
|
||||||
|
window.removeEventListener('resize', handleResize);
|
||||||
|
if (resizeObserver) {
|
||||||
|
resizeObserver.disconnect();
|
||||||
|
resizeObserver = null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 时间选择器配置
|
||||||
|
const showTimeConfig = {
|
||||||
|
format: 'HH:mm',
|
||||||
|
hourStep: 1,
|
||||||
|
minuteStep: 5,
|
||||||
|
secondStep: 60
|
||||||
|
};
|
||||||
|
const disabledDate = (current: Dayjs) =>
|
||||||
|
current && current.isAfter(dayjs(), 'day');
|
||||||
|
|
||||||
|
const initDateRange = (): [Dayjs, Dayjs] => {
|
||||||
|
return [dayjs().subtract(1, 'month'), dayjs()];
|
||||||
|
};
|
||||||
|
const dateRange = ref<[Dayjs, Dayjs] | undefined>(initDateRange());
|
||||||
|
|
||||||
|
// ==================== 表格列配置 ====================
|
||||||
|
const baseWaterTempColumns = [
|
||||||
|
{
|
||||||
|
title: '时间',
|
||||||
|
dataIndex: 'tm',
|
||||||
|
fixed: 'left',
|
||||||
|
customRender: ({ text }: any) =>
|
||||||
|
text ? dayjs(text).format('YYYY-MM-DD HH:mm') : '-'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '水温(°C)',
|
||||||
|
dataIndex: 'wt',
|
||||||
|
summary: true,
|
||||||
|
customRender: ({ text }: any) =>
|
||||||
|
text !== undefined && text !== null ? Number(text).toFixed(1) : '-'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const currentColumns = baseWaterTempColumns;
|
||||||
|
|
||||||
|
const tableScrollX = computed(() => {
|
||||||
|
const columns = currentColumns;
|
||||||
|
const totalWidth = columns.reduce(
|
||||||
|
(sum: number, col: any) => sum + (col.width || 100),
|
||||||
|
0
|
||||||
|
);
|
||||||
|
return totalWidth > 600 ? totalWidth : undefined;
|
||||||
|
});
|
||||||
|
|
||||||
|
// ==================== 合计行 ====================
|
||||||
|
const dataPathMap: Record<string, string> = {
|
||||||
|
wt: 'wt'
|
||||||
|
};
|
||||||
|
|
||||||
|
const summaryColumns = computed(() => {
|
||||||
|
return currentColumns
|
||||||
|
.filter((col: any) => col.summary === true)
|
||||||
|
.map((col: any, idx: number) => ({ ...col, summaryIndex: idx + 1 }));
|
||||||
|
});
|
||||||
|
|
||||||
|
const getNestedValue = (obj: any, path: string) => {
|
||||||
|
if (!path || !obj) return undefined;
|
||||||
|
return path.split('.').reduce((acc, key) => acc?.[key], obj);
|
||||||
|
};
|
||||||
|
|
||||||
|
const calcSummary = (dataIndex: string, type: 'max' | 'min' | 'avg') => {
|
||||||
|
const data = Array.isArray(chartData.value) ? chartData.value : [];
|
||||||
|
if (!data || data.length === 0) return null;
|
||||||
|
const dataPath = dataPathMap[dataIndex] || dataIndex;
|
||||||
|
const values = data
|
||||||
|
.map((r: any) => {
|
||||||
|
const val = getNestedValue(r, dataPath);
|
||||||
|
return val !== null && val !== undefined && val !== '-'
|
||||||
|
? Number(val)
|
||||||
|
: NaN;
|
||||||
|
})
|
||||||
|
.filter((v: number) => !isNaN(v));
|
||||||
|
if (values.length === 0) return null;
|
||||||
|
if (type === 'max') return Math.max(...values);
|
||||||
|
if (type === 'min') return Math.min(...values);
|
||||||
|
return values.reduce((s, v) => s + v, 0) / values.length;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getSummaryTime = (dataIndex: string, type: 'max' | 'min') => {
|
||||||
|
const data = Array.isArray(chartData.value) ? chartData.value : [];
|
||||||
|
if (!data || data.length === 0) return '';
|
||||||
|
const dataPath = dataPathMap[dataIndex] || dataIndex;
|
||||||
|
const records = data.filter((r: any) => {
|
||||||
|
const val = getNestedValue(r, dataPath);
|
||||||
|
return val !== null && val !== undefined && val !== '-';
|
||||||
|
});
|
||||||
|
if (records.length === 0) return '';
|
||||||
|
const target = Math[type](
|
||||||
|
...records.map((r: any) => Number(getNestedValue(r, dataPath)))
|
||||||
|
);
|
||||||
|
const record = records.find(
|
||||||
|
(r: any) => Number(getNestedValue(r, dataPath)) === target
|
||||||
|
);
|
||||||
|
return record?.tm ? dayjs(record.tm).format('YYYY-MM-DD HH:mm:ss') : '';
|
||||||
|
};
|
||||||
|
|
||||||
|
const formatSummaryValue = (dataIndex: string, value: number | null) => {
|
||||||
|
if (value === null) return '-';
|
||||||
|
if (dataIndex.includes('qo') || dataIndex.includes('qi'))
|
||||||
|
return Number(value).toFixed(3);
|
||||||
|
if (dataIndex === 'deep_wt') return Number(value).toFixed(2);
|
||||||
|
return Number(value).toFixed(1);
|
||||||
|
};
|
||||||
|
|
||||||
|
const summaryRows = computed(() => {
|
||||||
|
// Force dependency tracking
|
||||||
|
void chartData.value;
|
||||||
|
void summaryColumns.value;
|
||||||
|
|
||||||
|
if (!Array.isArray(chartData.value) || chartData.value.length === 0)
|
||||||
|
return [];
|
||||||
|
|
||||||
|
const cols = summaryColumns.value;
|
||||||
|
if (!cols || cols.length === 0) return [];
|
||||||
|
|
||||||
|
const rows = [
|
||||||
|
{
|
||||||
|
label: '最大值',
|
||||||
|
showTime: true,
|
||||||
|
cells: {} as Record<string, { value: string; time: string }>
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '最小值',
|
||||||
|
showTime: true,
|
||||||
|
cells: {} as Record<string, { value: string; time: string }>
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '平均值',
|
||||||
|
showTime: false,
|
||||||
|
cells: {} as Record<string, { value: string; time: string }>
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
cols.forEach((col: any) => {
|
||||||
|
const dataIndex = col.dataIndex;
|
||||||
|
const maxVal = calcSummary(dataIndex, 'max');
|
||||||
|
const minVal = calcSummary(dataIndex, 'min');
|
||||||
|
const avgVal = calcSummary(dataIndex, 'avg');
|
||||||
|
rows[0].cells[dataIndex] = {
|
||||||
|
value: formatSummaryValue(dataIndex, maxVal),
|
||||||
|
time: maxVal !== null ? getSummaryTime(dataIndex, 'max') : ''
|
||||||
|
};
|
||||||
|
rows[1].cells[dataIndex] = {
|
||||||
|
value: formatSummaryValue(dataIndex, minVal),
|
||||||
|
time: minVal !== null ? getSummaryTime(dataIndex, 'min') : ''
|
||||||
|
};
|
||||||
|
rows[2].cells[dataIndex] = {
|
||||||
|
value: formatSummaryValue(dataIndex, avgVal),
|
||||||
|
time: ''
|
||||||
|
};
|
||||||
|
});
|
||||||
|
return rows;
|
||||||
|
});
|
||||||
|
|
||||||
|
// ==================== 数据请求 ====================
|
||||||
|
// 数据请求(查询参数取 modelStore.selectedAnchorPoint)
|
||||||
|
const fetchData = async () => {
|
||||||
|
const stcd = modelStore.selectedAnchorPoint?.stcd;
|
||||||
|
if (!dateRange.value || !stcd) return;
|
||||||
|
isLoading.value = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const filterParams = {
|
||||||
|
filter: {
|
||||||
|
logic: 'and',
|
||||||
|
filters: [
|
||||||
|
{
|
||||||
|
field: 'stcd',
|
||||||
|
operator: 'eq',
|
||||||
|
dataType: 'string',
|
||||||
|
value: stcd
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'tm',
|
||||||
|
operator: 'gte',
|
||||||
|
dataType: 'date',
|
||||||
|
value: dateRange.value[0].format('YYYY-MM-DD HH:mm:ss')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'tm',
|
||||||
|
operator: 'lte',
|
||||||
|
dataType: 'date',
|
||||||
|
value: dateRange.value[1].format('YYYY-MM-DD HH:mm:ss')
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
sort: [{ field: 'tm', dir: 'asc' }]
|
||||||
|
};
|
||||||
|
|
||||||
|
const res = await getMonitorDataWaterTemp(filterParams);
|
||||||
|
const rawData = res?.data?.data || res?.data?.records || [];
|
||||||
|
|
||||||
|
chartData.value = rawData;
|
||||||
|
tableData.value = [...rawData].reverse();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取数据失败:', error);
|
||||||
|
chartData.value = [];
|
||||||
|
tableData.value = [];
|
||||||
|
} finally {
|
||||||
|
isLoading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 时间选择器 change 触发查询
|
||||||
|
const handleSearch = () => fetchData();
|
||||||
|
watch(
|
||||||
|
() => uiStore.searchDrawerOpen,
|
||||||
|
newVal => {
|
||||||
|
if (!newVal) {
|
||||||
|
dateRange.value = initDateRange();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
// 选中点位变化时刷新;初始已有点位时立即加载
|
||||||
|
watch(
|
||||||
|
() => modelStore.selectedAnchorPoint?.stcd,
|
||||||
|
(newStcd, oldStcd) => {
|
||||||
|
if (!newStcd) return;
|
||||||
|
if (newStcd !== oldStcd) fetchData();
|
||||||
|
},
|
||||||
|
{ immediate: true }
|
||||||
|
);
|
||||||
|
|
||||||
|
// 全局时间范围变化时同步本地日期选择器(默认参数联动)
|
||||||
|
watch(
|
||||||
|
() => modelStore.filter.rangeTm,
|
||||||
|
newRange => {
|
||||||
|
if (Array.isArray(newRange) && newRange.length > 0) {
|
||||||
|
dateRange.value = [dayjs(newRange[0]), dayjs(newRange[1])];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ deep: true }
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.monitor-chart {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
height: 300px;
|
||||||
|
|
||||||
|
.chart-container {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-empty {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.monitor-table {
|
||||||
|
margin-top: 12px;
|
||||||
|
border-radius: 2px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
.table-container {
|
||||||
|
border: 1px solid #dcdfe6;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.summary-tip-icon) {
|
||||||
|
color: rgb(53, 169, 255);
|
||||||
|
margin-left: 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.summary-row {
|
||||||
|
background-color: #fafafa;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -1,17 +1,48 @@
|
|||||||
<template>
|
<template>
|
||||||
|
<SidePanelItem title="电站运行过程线" :shrink="false">
|
||||||
|
<template #title-right-content>
|
||||||
|
<a-range-picker
|
||||||
|
class="w-[220px]"
|
||||||
|
v-model:value="dateRange"
|
||||||
|
format="YYYY-MM-DD"
|
||||||
|
:show-time="showTimeConfig"
|
||||||
|
:allowClear="false"
|
||||||
|
:presets="DateSetting.RangeButton.month1"
|
||||||
|
:disabled-date="disabledDate"
|
||||||
|
size="small"
|
||||||
|
@change="handleSearch"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<a-spin :spinning="isLoading" tip="加载中...">
|
||||||
|
<div class="monitor-chart">
|
||||||
<div ref="chartRef" class="chart-container"></div>
|
<div ref="chartRef" class="chart-container"></div>
|
||||||
|
<a-empty
|
||||||
|
v-if="!chartData || chartData.length === 0"
|
||||||
|
description="暂无数据"
|
||||||
|
class="chart-empty"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</a-spin>
|
||||||
|
</SidePanelItem>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref, watch, onBeforeUnmount, onMounted, nextTick } from 'vue';
|
import { ref, watch, onMounted, onBeforeUnmount, nextTick } from 'vue';
|
||||||
import dayjs from 'dayjs';
|
import dayjs, { Dayjs } from 'dayjs';
|
||||||
import * as echarts from 'echarts';
|
import * as echarts from 'echarts';
|
||||||
|
import { useUiStore } from '@/store/modules/ui';
|
||||||
|
import { getMonitorData } from '@/api/mapModal';
|
||||||
|
import { useModelStore } from '@/store/modules/model';
|
||||||
|
import SidePanelItem from '@/components/SidePanelItem/index.vue';
|
||||||
|
import { DateSetting } from '@/utils/enumeration';
|
||||||
|
|
||||||
const props = defineProps({
|
const uiStore = useUiStore();
|
||||||
data: { type: Array as () => any[], default: () => [] },
|
const modelStore = useModelStore();
|
||||||
isActive: { type: Boolean, default: false }
|
|
||||||
});
|
|
||||||
|
|
||||||
|
const isLoading = ref(false);
|
||||||
|
const chartData = ref<any[]>([]);
|
||||||
|
|
||||||
|
// ==================== 图表 ====================
|
||||||
const chartRef = ref<HTMLElement>();
|
const chartRef = ref<HTMLElement>();
|
||||||
let chartInstance: echarts.ECharts | null = null;
|
let chartInstance: echarts.ECharts | null = null;
|
||||||
let resizeObserver: ResizeObserver | null = null;
|
let resizeObserver: ResizeObserver | null = null;
|
||||||
@ -21,7 +52,7 @@ const initChart = () => {
|
|||||||
if (!chartRef.value) return;
|
if (!chartRef.value) return;
|
||||||
if (chartInstance) chartInstance.dispose();
|
if (chartInstance) chartInstance.dispose();
|
||||||
chartInstance = echarts.init(chartRef.value);
|
chartInstance = echarts.init(chartRef.value);
|
||||||
updateChart(props.data);
|
updateChart(chartData.value);
|
||||||
};
|
};
|
||||||
|
|
||||||
// 更新图表
|
// 更新图表
|
||||||
@ -283,11 +314,16 @@ const updateChart = (data: any[]) => {
|
|||||||
chartInstance.setOption(option, true);
|
chartInstance.setOption(option, true);
|
||||||
};
|
};
|
||||||
|
|
||||||
// 激活时初始化图表并处理自适应,数据变化时动态刷新
|
// 数据变化时动态刷新图表
|
||||||
watch(
|
watch(chartData, newData => {
|
||||||
() => props.isActive,
|
updateChart(newData);
|
||||||
active => {
|
});
|
||||||
if (!active) return;
|
|
||||||
|
const handleResize = () => {
|
||||||
|
if (chartInstance) chartInstance.resize();
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
initChart();
|
initChart();
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
@ -300,22 +336,6 @@ watch(
|
|||||||
resizeObserver.observe(chartRef.value);
|
resizeObserver.observe(chartRef.value);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
|
||||||
{ immediate: true }
|
|
||||||
);
|
|
||||||
|
|
||||||
watch(
|
|
||||||
() => props.data,
|
|
||||||
newData => {
|
|
||||||
updateChart(newData);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleResize = () => {
|
|
||||||
if (chartInstance) chartInstance.resize();
|
|
||||||
};
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
window.addEventListener('resize', handleResize);
|
window.addEventListener('resize', handleResize);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -330,11 +350,116 @@ onBeforeUnmount(() => {
|
|||||||
resizeObserver = null;
|
resizeObserver = null;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 时间选择器配置(默认参数与 MonitorInfo 保持一致)
|
||||||
|
const showTimeConfig = {
|
||||||
|
format: 'HH:mm',
|
||||||
|
hourStep: 1,
|
||||||
|
minuteStep: 5,
|
||||||
|
secondStep: 60
|
||||||
|
};
|
||||||
|
const disabledDate = (current: Dayjs) =>
|
||||||
|
current && current.isAfter(dayjs(), 'day');
|
||||||
|
|
||||||
|
const initDateRange = (): [Dayjs, Dayjs] => {
|
||||||
|
return [dayjs().subtract(1, 'month'), dayjs()];
|
||||||
|
};
|
||||||
|
const dateRange = ref<[Dayjs, Dayjs] | undefined>(initDateRange());
|
||||||
|
|
||||||
|
// 数据请求(查询参数取 modelStore.selectedAnchorPoint)
|
||||||
|
const fetchData = async () => {
|
||||||
|
const stcd = modelStore.selectedAnchorPoint?.stcd;
|
||||||
|
if (!dateRange.value || !stcd) return;
|
||||||
|
isLoading.value = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const filterParams = {
|
||||||
|
filter: {
|
||||||
|
logic: 'and',
|
||||||
|
filters: [
|
||||||
|
{
|
||||||
|
field: 'stcd',
|
||||||
|
operator: 'eq',
|
||||||
|
dataType: 'string',
|
||||||
|
value: stcd
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'tm',
|
||||||
|
operator: 'gte',
|
||||||
|
dataType: 'date',
|
||||||
|
value: dateRange.value[0].format('YYYY-MM-DD HH:mm:ss')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'tm',
|
||||||
|
operator: 'lte',
|
||||||
|
dataType: 'date',
|
||||||
|
value: dateRange.value[1].format('YYYY-MM-DD HH:mm:ss')
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
sort: [{ field: 'tm', dir: 'asc' }]
|
||||||
|
};
|
||||||
|
|
||||||
|
const res = await getMonitorData(filterParams);
|
||||||
|
const rawData = res?.data?.data || res?.data?.records || [];
|
||||||
|
|
||||||
|
chartData.value = rawData;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取数据失败:', error);
|
||||||
|
chartData.value = [];
|
||||||
|
} finally {
|
||||||
|
isLoading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 时间选择器 change 触发查询
|
||||||
|
const handleSearch = () => fetchData();
|
||||||
|
watch(
|
||||||
|
() => uiStore.searchDrawerOpen,
|
||||||
|
newVal => {
|
||||||
|
if (!newVal) {
|
||||||
|
dateRange.value = initDateRange();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
// 选中点位变化时刷新;初始已有点位时立即加载
|
||||||
|
watch(
|
||||||
|
() => modelStore.selectedAnchorPoint?.stcd,
|
||||||
|
(newStcd, oldStcd) => {
|
||||||
|
if (!newStcd) return;
|
||||||
|
if (newStcd !== oldStcd) fetchData();
|
||||||
|
},
|
||||||
|
{ immediate: true }
|
||||||
|
);
|
||||||
|
|
||||||
|
// 全局时间范围变化时同步本地日期选择器(默认参数联动)
|
||||||
|
watch(
|
||||||
|
() => modelStore.filter.rangeTm,
|
||||||
|
newRange => {
|
||||||
|
if (Array.isArray(newRange) && newRange.length > 0) {
|
||||||
|
dateRange.value = [dayjs(newRange[0]), dayjs(newRange[1])];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ deep: true }
|
||||||
|
);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.chart-container {
|
.monitor-chart {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
height: 400px;
|
||||||
|
|
||||||
|
.chart-container {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-empty {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
@ -0,0 +1,56 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<!-- 电站运行过程线 -->
|
||||||
|
<monitorInfoMod v-if="sttpValue == 'ENG'" />
|
||||||
|
<!-- 水温监控 -->
|
||||||
|
<WaterTemperature v-if="sttpValue == 'WTRV'" />
|
||||||
|
<!-- 垂向水温监控 -->
|
||||||
|
<VerticalWaterTemperature v-if="sttpValue == 'WTVT'" />
|
||||||
|
<!-- 水质监控 -->
|
||||||
|
<WaterQuality v-if="sttpValue == 'WQ'" />
|
||||||
|
<!-- 过鱼总量监控 -->
|
||||||
|
<GYZLLB v-if="sttpValue == 'FP'" />
|
||||||
|
<!-- 栖息地 FH -->
|
||||||
|
<QXDInfo v-if="sttpValue == 'FH'" />
|
||||||
|
<!-- 流量站 -->
|
||||||
|
<FlowMeasure v-if="sttpValue == 'ZQ'" />
|
||||||
|
<!-- 植物园 VP -->
|
||||||
|
<Plant v-if="sttpValue == 'VP'" />
|
||||||
|
<!-- 动物园 VA -->
|
||||||
|
<Animal v-if="sttpValue == 'VA'" />
|
||||||
|
<!-- 生态流量泄放设施 EQ -->
|
||||||
|
<FlowDischarge v-if="sttpValue == 'EQ'" />
|
||||||
|
<!-- 鱼类增殖站 -->
|
||||||
|
<!-- 生态调查 -->
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { ref, watch } from 'vue';
|
||||||
|
import { useModelStore } from '@/store/modules/model';
|
||||||
|
import WaterTemperature from '@/modules/rightSearchDrawer/monitoringTable/components/WaterTemperature.vue';
|
||||||
|
import VerticalWaterTemperature from '@/modules/rightSearchDrawer/monitoringTable/components/VerticalWaterTemperature.vue';
|
||||||
|
import WaterQuality from '@/modules/rightSearchDrawer/monitoringTable/components/WaterQuality.vue';
|
||||||
|
import monitorInfoMod from '@/modules/rightSearchDrawer/monitoringTable/components/monitorInfo.vue';
|
||||||
|
import GYZLLB from '@/modules/rightSearchDrawer/monitoringTable/components/GYZLLB.vue';
|
||||||
|
import QXDInfo from '@/modules/rightSearchDrawer/monitoringTable/components/QXDInfo.vue';
|
||||||
|
import FlowMeasure from '@/modules/rightSearchDrawer/monitoringTable/components/FlowMeasure.vue';
|
||||||
|
import Plant from '@/modules/rightSearchDrawer/monitoringTable/components/Plant.vue';
|
||||||
|
import Animal from '@/modules/rightSearchDrawer/monitoringTable/components/Animal.vue';
|
||||||
|
import FlowDischarge from '@/modules/rightSearchDrawer/monitoringTable/components/FlowDischarge.vue';
|
||||||
|
|
||||||
|
const modelStore = useModelStore();
|
||||||
|
const sttpValue = ref('');
|
||||||
|
|
||||||
|
// 选中点位变化时刷新;初始已有点位时立即加载
|
||||||
|
watch(
|
||||||
|
() => modelStore.selectedAnchorPoint,
|
||||||
|
newVal => {
|
||||||
|
console.log(newVal);
|
||||||
|
sttpValue.value = newVal?.sttpCode || '';
|
||||||
|
},
|
||||||
|
{ immediate: true }
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped></style>
|
||||||
@ -816,6 +816,10 @@ export const useMapStore = defineStore('map', () => {
|
|||||||
|
|
||||||
mapViewStore.setCheckedLayerKeys(checkKeys);
|
mapViewStore.setCheckedLayerKeys(checkKeys);
|
||||||
|
|
||||||
|
// 备注:勾选态变化后按最新勾选集合重建合并锚点数据,保证搜索下拉框等消费方
|
||||||
|
// 始终拿到当前勾选图层的数据,避免旧重建结果只包含部分图层导致下拉数据缺失。
|
||||||
|
syncPointDataForFilter(checkKeys);
|
||||||
|
|
||||||
// 收集变化的图层 key(新增选中 / 取消选中)
|
// 收集变化的图层 key(新增选中 / 取消选中)
|
||||||
const newlyChecked: string[] = [];
|
const newlyChecked: string[] = [];
|
||||||
const newlyUnchecked: string[] = [];
|
const newlyUnchecked: string[] = [];
|
||||||
|
|||||||
@ -12,6 +12,8 @@ export const useUiStore = defineStore('ui', () => {
|
|||||||
const skipRoamCameraRestore = ref(false);
|
const skipRoamCameraRestore = ref(false);
|
||||||
// 搜索抽屉状态
|
// 搜索抽屉状态
|
||||||
const searchDrawerOpen = ref(false);
|
const searchDrawerOpen = ref(false);
|
||||||
|
// 全局兜底抽屉状态(AppMain 通用抽屉,给未自带抽屉的页面用)
|
||||||
|
const globalDrawerOpen = ref(false);
|
||||||
|
|
||||||
// 切换抽屉状态
|
// 切换抽屉状态
|
||||||
const toggleDrawer = () => {
|
const toggleDrawer = () => {
|
||||||
@ -23,10 +25,12 @@ export const useUiStore = defineStore('ui', () => {
|
|||||||
};
|
};
|
||||||
// 设置搜索抽屉状态
|
// 设置搜索抽屉状态
|
||||||
const setSearchDrawerOpen = (open: boolean) => {
|
const setSearchDrawerOpen = (open: boolean) => {
|
||||||
console.log(open);
|
|
||||||
|
|
||||||
searchDrawerOpen.value = open;
|
searchDrawerOpen.value = open;
|
||||||
};
|
};
|
||||||
|
// 设置全局兜底抽屉状态
|
||||||
|
const setGlobalDrawerOpen = (open: boolean) => {
|
||||||
|
globalDrawerOpen.value = open;
|
||||||
|
};
|
||||||
|
|
||||||
const markMapSwitchCompleted = () => {
|
const markMapSwitchCompleted = () => {
|
||||||
mapSwitchCompletedTick.value += 1;
|
mapSwitchCompletedTick.value += 1;
|
||||||
@ -40,6 +44,8 @@ export const useUiStore = defineStore('ui', () => {
|
|||||||
setDrawerOpen,
|
setDrawerOpen,
|
||||||
setSearchDrawerOpen,
|
setSearchDrawerOpen,
|
||||||
searchDrawerOpen,
|
searchDrawerOpen,
|
||||||
|
globalDrawerOpen,
|
||||||
|
setGlobalDrawerOpen,
|
||||||
mapType,
|
mapType,
|
||||||
mapSwitchCompletedTick,
|
mapSwitchCompletedTick,
|
||||||
markMapSwitchCompleted
|
markMapSwitchCompleted
|
||||||
|
|||||||
@ -126,3 +126,6 @@ svg {
|
|||||||
.ant-dropdown {
|
.ant-dropdown {
|
||||||
z-index: 9999 !important;
|
z-index: 9999 !important;
|
||||||
}
|
}
|
||||||
|
.ant-table-pagination.ant-pagination {
|
||||||
|
padding: 0 4px !important;
|
||||||
|
}
|
||||||
|
|||||||
@ -1,6 +1,4 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useUiStore } from '@/store/modules/ui';
|
|
||||||
import basicInfoMod from '@/modules/rightSearchDrawer/basicInfo/index.vue';
|
|
||||||
import JidiSelectorMod from '@/modules/jidiSelectorMod.vue';
|
import JidiSelectorMod from '@/modules/jidiSelectorMod.vue';
|
||||||
import RightDrawer from '@/components/RightDrawer/index.vue';
|
import RightDrawer from '@/components/RightDrawer/index.vue';
|
||||||
import GuoYuSheShiJieShao from '@/modules/guoyusheshijieshao/index.vue';
|
import GuoYuSheShiJieShao from '@/modules/guoyusheshijieshao/index.vue';
|
||||||
@ -10,7 +8,6 @@ import GYZLLB from '@/modules/GYZLLB/index.vue';
|
|||||||
import { useJidiSelectEventStore } from '@/store/modules/jidiSelectEvent';
|
import { useJidiSelectEventStore } from '@/store/modules/jidiSelectEvent';
|
||||||
import { ref, watch } from 'vue';
|
import { ref, watch } from 'vue';
|
||||||
const JidiSelectEventStore = useJidiSelectEventStore();
|
const JidiSelectEventStore = useJidiSelectEventStore();
|
||||||
const uiStore = useUiStore();
|
|
||||||
const baseid = ref('');
|
const baseid = ref('');
|
||||||
watch(
|
watch(
|
||||||
() => JidiSelectEventStore.selectedItem,
|
() => JidiSelectEventStore.selectedItem,
|
||||||
@ -28,15 +25,10 @@ watch(
|
|||||||
</div>
|
</div>
|
||||||
<div class="rightContent">
|
<div class="rightContent">
|
||||||
<RightDrawer>
|
<RightDrawer>
|
||||||
<div v-show="uiStore.searchDrawerOpen">
|
|
||||||
<basicInfoMod />
|
|
||||||
</div>
|
|
||||||
<div v-show="!uiStore.searchDrawerOpen">
|
|
||||||
<GuoYuSheShiJieShao v-if="baseid != 'all'" />
|
<GuoYuSheShiJieShao v-if="baseid != 'all'" />
|
||||||
<GuoYuSheShiJianSheQingKuang v-if="baseid == 'all'" />
|
<GuoYuSheShiJianSheQingKuang v-if="baseid == 'all'" />
|
||||||
<GuoYuJianCeTJ />
|
<GuoYuJianCeTJ />
|
||||||
<GYZLLB title="过鱼总量" />
|
<GYZLLB title="过鱼总量" />
|
||||||
</div>
|
|
||||||
</RightDrawer>
|
</RightDrawer>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,12 +1,8 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useUiStore } from '@/store/modules/ui';
|
|
||||||
import JidiSelectorMod from '@/modules/jidiSelectorMod.vue';
|
import JidiSelectorMod from '@/modules/jidiSelectorMod.vue';
|
||||||
import RightDrawer from '@/components/RightDrawer/index.vue';
|
import RightDrawer from '@/components/RightDrawer/index.vue';
|
||||||
import HuanbaoMod from '@/modules/huanbaoMod/index.vue'; // 环保设施建设情况
|
import HuanbaoMod from '@/modules/huanbaoMod/index.vue'; // 环保设施建设情况
|
||||||
import Dianxingcuoshijieshao from '@/modules/dianxingcuoshijieshao/index.vue'; // 环保设施建设情况
|
import Dianxingcuoshijieshao from '@/modules/dianxingcuoshijieshao/index.vue'; // 环保设施建设情况
|
||||||
import basicInfoMod from '@/modules/rightSearchDrawer/basicInfo/index.vue';
|
|
||||||
import monitorInfoMod from '@/modules/rightSearchDrawer/monitorInfo/index.vue';
|
|
||||||
const uiStore = useUiStore();
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@ -16,14 +12,8 @@ const uiStore = useUiStore();
|
|||||||
</div>
|
</div>
|
||||||
<div class="rightContent">
|
<div class="rightContent">
|
||||||
<RightDrawer>
|
<RightDrawer>
|
||||||
<div v-show="uiStore.searchDrawerOpen">
|
|
||||||
<basicInfoMod />
|
|
||||||
<!-- <monitorInfoMod /> -->
|
|
||||||
</div>
|
|
||||||
<div v-show="!uiStore.searchDrawerOpen">
|
|
||||||
<HuanbaoMod />
|
<HuanbaoMod />
|
||||||
<Dianxingcuoshijieshao />
|
<Dianxingcuoshijieshao />
|
||||||
</div>
|
|
||||||
</RightDrawer>
|
</RightDrawer>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,7 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, watch } from 'vue';
|
import { ref, watch } from 'vue';
|
||||||
import { useUiStore } from '@/store/modules/ui';
|
|
||||||
import basicInfoMod from '@/modules/rightSearchDrawer/basicInfo/index.vue';
|
|
||||||
import JidiSelectorMod from '@/modules/jidiSelectorMod.vue';
|
import JidiSelectorMod from '@/modules/jidiSelectorMod.vue';
|
||||||
import RightDrawer from '@/components/RightDrawer/index.vue';
|
import RightDrawer from '@/components/RightDrawer/index.vue';
|
||||||
import HuanbaozdjcgzkzQK from '@/modules/huanbaozdjcgzkzQK/index.vue';
|
import HuanbaozdjcgzkzQK from '@/modules/huanbaozdjcgzkzQK/index.vue';
|
||||||
@ -9,7 +7,6 @@ import SZYCBH from '@/modules/waterQuality/index.vue';
|
|||||||
import { useJidiSelectEventStore } from '@/store/modules/jidiSelectEvent';
|
import { useJidiSelectEventStore } from '@/store/modules/jidiSelectEvent';
|
||||||
const JidiSelectEventStore = useJidiSelectEventStore();
|
const JidiSelectEventStore = useJidiSelectEventStore();
|
||||||
const wbsCode = ref('');
|
const wbsCode = ref('');
|
||||||
const uiStore = useUiStore();
|
|
||||||
watch(
|
watch(
|
||||||
() => JidiSelectEventStore.selectedItem,
|
() => JidiSelectEventStore.selectedItem,
|
||||||
newVal => {
|
newVal => {
|
||||||
@ -26,13 +23,8 @@ watch(
|
|||||||
</div>
|
</div>
|
||||||
<div class="rightContent">
|
<div class="rightContent">
|
||||||
<RightDrawer>
|
<RightDrawer>
|
||||||
<div v-show="uiStore.searchDrawerOpen">
|
|
||||||
<basicInfoMod />
|
|
||||||
</div>
|
|
||||||
<div v-show="!uiStore.searchDrawerOpen">
|
|
||||||
<HuanbaozdjcgzkzQK />
|
<HuanbaozdjcgzkzQK />
|
||||||
<SZYCBH v-if="wbsCode != 'all'" />
|
<SZYCBH v-if="wbsCode != 'all'" />
|
||||||
</div>
|
|
||||||
</RightDrawer>
|
</RightDrawer>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,12 +1,8 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useUiStore } from '@/store/modules/ui';
|
|
||||||
import JidiSelectorMod from '@/modules/jidiSelectorMod.vue';
|
import JidiSelectorMod from '@/modules/jidiSelectorMod.vue';
|
||||||
import RightDrawer from '@/components/RightDrawer/index.vue';
|
import RightDrawer from '@/components/RightDrawer/index.vue';
|
||||||
import jidiInfoMod from '@/modules/jidiInfoMod/index.vue';
|
import jidiInfoMod from '@/modules/jidiInfoMod/index.vue';
|
||||||
import shuidianhuangjingjieruMod from '@/modules/shuidianhuangjingjieruMod/index.vue';
|
import shuidianhuangjingjieruMod from '@/modules/shuidianhuangjingjieruMod/index.vue';
|
||||||
import basicInfoMod from '@/modules/rightSearchDrawer/basicInfo/index.vue';
|
|
||||||
import monitorInfoMod from '@/modules/rightSearchDrawer/monitorInfo/index.vue';
|
|
||||||
const uiStore = useUiStore();
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@ -16,14 +12,8 @@ const uiStore = useUiStore();
|
|||||||
</div>
|
</div>
|
||||||
<div class="rightContent">
|
<div class="rightContent">
|
||||||
<RightDrawer>
|
<RightDrawer>
|
||||||
<div v-show="uiStore.searchDrawerOpen">
|
|
||||||
<basicInfoMod />
|
|
||||||
<monitorInfoMod />
|
|
||||||
</div>
|
|
||||||
<div v-show="!uiStore.searchDrawerOpen">
|
|
||||||
<jidiInfoMod />
|
<jidiInfoMod />
|
||||||
<shuidianhuangjingjieruMod />
|
<shuidianhuangjingjieruMod />
|
||||||
</div>
|
|
||||||
</RightDrawer>
|
</RightDrawer>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,13 +1,10 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useUiStore } from '@/store/modules/ui';
|
|
||||||
import basicInfoMod from '@/modules/rightSearchDrawer/basicInfo/index.vue';
|
|
||||||
import JidiSelectorMod from '@/modules/jidiSelectorMod.vue';
|
import JidiSelectorMod from '@/modules/jidiSelectorMod.vue';
|
||||||
import RightDrawer from '@/components/RightDrawer/index.vue';
|
import RightDrawer from '@/components/RightDrawer/index.vue';
|
||||||
import QiXiDiBaoHuGongZuoKaiZhan from '@/modules/qixidibaohugongzuokaizhanQK/index.vue';
|
import QiXiDiBaoHuGongZuoKaiZhan from '@/modules/qixidibaohugongzuokaizhanQK/index.vue';
|
||||||
import QixidijchuXx from '@/modules/qixidijchuXx/index.vue';
|
import QixidijchuXx from '@/modules/qixidijchuXx/index.vue';
|
||||||
import QiXiDiShuiWenBianHua from '@/modules/qixidishuiwenbianhua/index.vue';
|
import QiXiDiShuiWenBianHua from '@/modules/qixidishuiwenbianhua/index.vue';
|
||||||
import QiXiDiLiuLiangBianHua from '@/modules/qixidiliuliangbianhua/index.vue';
|
import QiXiDiLiuLiangBianHua from '@/modules/qixidiliuliangbianhua/index.vue';
|
||||||
const uiStore = useUiStore();
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@ -17,15 +14,10 @@ const uiStore = useUiStore();
|
|||||||
</div>
|
</div>
|
||||||
<div class="rightContent">
|
<div class="rightContent">
|
||||||
<RightDrawer>
|
<RightDrawer>
|
||||||
<div v-show="uiStore.searchDrawerOpen">
|
|
||||||
<basicInfoMod />
|
|
||||||
</div>
|
|
||||||
<div v-show="!uiStore.searchDrawerOpen">
|
|
||||||
<QiXiDiBaoHuGongZuoKaiZhan />
|
<QiXiDiBaoHuGongZuoKaiZhan />
|
||||||
<QiXiDiShuiWenBianHua />
|
<QiXiDiShuiWenBianHua />
|
||||||
<QiXiDiLiuLiangBianHua />
|
<QiXiDiLiuLiangBianHua />
|
||||||
<QixidijchuXx />
|
<QixidijchuXx />
|
||||||
</div>
|
|
||||||
</RightDrawer>
|
</RightDrawer>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useUiStore } from '@/store/modules/ui';
|
import { useUiStore } from '@/store/modules/ui';
|
||||||
import basicInfoMod from '@/modules/rightSearchDrawer/basicInfo/index.vue';
|
import basicInfoMod from '@/modules/rightSearchDrawer/basicInfo/index.vue';
|
||||||
|
import monitoringTableMod from '@/modules/rightSearchDrawer/monitoringTable/index.vue';
|
||||||
import JidiSelectorMod from '@/modules/jidiSelectorMod.vue';
|
import JidiSelectorMod from '@/modules/jidiSelectorMod.vue';
|
||||||
import RightDrawer from '@/components/RightDrawer/index.vue';
|
import RightDrawer from '@/components/RightDrawer/index.vue';
|
||||||
import DiWenSJHSSLXZCJJRQK from '@/modules/diwenshuijianhuansheshileixingzuchengjijieruqingkuang/index.vue';
|
import DiWenSJHSSLXZCJJRQK from '@/modules/diwenshuijianhuansheshileixingzuchengjijieruqingkuang/index.vue';
|
||||||
@ -17,6 +18,7 @@ const uiStore = useUiStore();
|
|||||||
<RightDrawer>
|
<RightDrawer>
|
||||||
<div v-show="uiStore.searchDrawerOpen">
|
<div v-show="uiStore.searchDrawerOpen">
|
||||||
<basicInfoMod />
|
<basicInfoMod />
|
||||||
|
<monitoringTableMod />
|
||||||
</div>
|
</div>
|
||||||
<div v-show="!uiStore.searchDrawerOpen">
|
<div v-show="!uiStore.searchDrawerOpen">
|
||||||
<SheShiLeiXingJieShao />
|
<SheShiLeiXingJieShao />
|
||||||
|
|||||||
@ -1,11 +1,8 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useUiStore } from '@/store/modules/ui';
|
|
||||||
import basicInfoMod from '@/modules/rightSearchDrawer/basicInfo/index.vue';
|
|
||||||
import JidiSelectorMod from '@/modules/jidiSelectorMod.vue';
|
import JidiSelectorMod from '@/modules/jidiSelectorMod.vue';
|
||||||
import RightDrawer from '@/components/RightDrawer/index.vue';
|
import RightDrawer from '@/components/RightDrawer/index.vue';
|
||||||
import MonthlyAvgWaterTemCompareHistory from '@/modules/monthlyAvgWaterTemCompareHistory/index.vue'; // 月平均水温历史对比
|
import MonthlyAvgWaterTemCompareHistory from '@/modules/monthlyAvgWaterTemCompareHistory/index.vue'; // 月平均水温历史对比
|
||||||
import ShuiWenNianNeiFenBu from '@/modules/shuiWenNianNeiFenBu/index.vue'; // 水温年内分布
|
import ShuiWenNianNeiFenBu from '@/modules/shuiWenNianNeiFenBu/index.vue'; // 水温年内分布
|
||||||
const uiStore = useUiStore();
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@ -15,13 +12,8 @@ const uiStore = useUiStore();
|
|||||||
</div>
|
</div>
|
||||||
<div class="rightContent">
|
<div class="rightContent">
|
||||||
<RightDrawer>
|
<RightDrawer>
|
||||||
<div v-show="uiStore.searchDrawerOpen">
|
|
||||||
<basicInfoMod />
|
|
||||||
</div>
|
|
||||||
<div v-show="!uiStore.searchDrawerOpen">
|
|
||||||
<MonthlyAvgWaterTemCompareHistory />
|
<MonthlyAvgWaterTemCompareHistory />
|
||||||
<ShuiWenNianNeiFenBu />
|
<ShuiWenNianNeiFenBu />
|
||||||
</div>
|
|
||||||
</RightDrawer>
|
</RightDrawer>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,13 +1,10 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useUiStore } from '@/store/modules/ui';
|
|
||||||
import basicInfoMod from '@/modules/rightSearchDrawer/basicInfo/index.vue';
|
|
||||||
import JidiSelectorMod from '@/modules/jidiSelectorMod.vue';
|
import JidiSelectorMod from '@/modules/jidiSelectorMod.vue';
|
||||||
import RightDrawer from '@/components/RightDrawer/index.vue';
|
import RightDrawer from '@/components/RightDrawer/index.vue';
|
||||||
import ChuixiangshuiwenChangeMod from '@/modules/chuixiangshuiwenChangeMod/index.vue'; // 垂向水温变化
|
import ChuixiangshuiwenChangeMod from '@/modules/chuixiangshuiwenChangeMod/index.vue'; // 垂向水温变化
|
||||||
import ChurukushuiwenMod from '@/modules/churukushuiwenMod/index.vue'; // 出入库水温变化
|
import ChurukushuiwenMod from '@/modules/churukushuiwenMod/index.vue'; // 出入库水温变化
|
||||||
import YanchengshuiwenChangeMod from '@/modules/yanchengshuiwenChangeMod/index.vue'; // 沿程水温变化
|
import YanchengshuiwenChangeMod from '@/modules/yanchengshuiwenChangeMod/index.vue'; // 沿程水温变化
|
||||||
import ShuiwenJCGZKZQK from '@/modules/shuiwenjiancegongzuokaizhangqingkuang/index.vue';
|
import ShuiwenJCGZKZQK from '@/modules/shuiwenjiancegongzuokaizhangqingkuang/index.vue';
|
||||||
const uiStore = useUiStore();
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@ -17,15 +14,10 @@ const uiStore = useUiStore();
|
|||||||
</div>
|
</div>
|
||||||
<div class="rightContent">
|
<div class="rightContent">
|
||||||
<RightDrawer>
|
<RightDrawer>
|
||||||
<div v-show="uiStore.searchDrawerOpen">
|
|
||||||
<basicInfoMod />
|
|
||||||
</div>
|
|
||||||
<div v-show="!uiStore.searchDrawerOpen">
|
|
||||||
<ShuiwenJCGZKZQK />
|
<ShuiwenJCGZKZQK />
|
||||||
<YanchengshuiwenChangeMod />
|
<YanchengshuiwenChangeMod />
|
||||||
<ChuixiangshuiwenChangeMod />
|
<ChuixiangshuiwenChangeMod />
|
||||||
<ChurukushuiwenMod />
|
<ChurukushuiwenMod />
|
||||||
</div>
|
|
||||||
</RightDrawer>
|
</RightDrawer>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,7 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, watch } from 'vue';
|
import { ref, watch } from 'vue';
|
||||||
import { useUiStore } from '@/store/modules/ui';
|
|
||||||
import basicInfoMod from '@/modules/rightSearchDrawer/basicInfo/index.vue';
|
|
||||||
import JidiSelectorMod from '@/modules/jidiSelectorMod.vue';
|
import JidiSelectorMod from '@/modules/jidiSelectorMod.vue';
|
||||||
import RightDrawer from '@/components/RightDrawer/index.vue';
|
import RightDrawer from '@/components/RightDrawer/index.vue';
|
||||||
import ShuiZhiJianCeGongZuoQingKuang from '@/modules/shuizhijiancegongzuoQK/index.vue';
|
import ShuiZhiJianCeGongZuoQingKuang from '@/modules/shuizhijiancegongzuoQK/index.vue';
|
||||||
@ -9,7 +7,6 @@ import EnvironmentalQuality from '@/modules/EnvironmentalQuality/index.vue'; //
|
|||||||
import { useJidiSelectEventStore } from '@/store/modules/jidiSelectEvent';
|
import { useJidiSelectEventStore } from '@/store/modules/jidiSelectEvent';
|
||||||
import SZYCBH from '@/modules/waterQuality/index.vue';
|
import SZYCBH from '@/modules/waterQuality/index.vue';
|
||||||
const JidiSelectEventStore = useJidiSelectEventStore();
|
const JidiSelectEventStore = useJidiSelectEventStore();
|
||||||
const uiStore = useUiStore();
|
|
||||||
const wbsCode = ref('');
|
const wbsCode = ref('');
|
||||||
watch(
|
watch(
|
||||||
() => JidiSelectEventStore.selectedItem,
|
() => JidiSelectEventStore.selectedItem,
|
||||||
@ -29,14 +26,9 @@ watch(
|
|||||||
</div>
|
</div>
|
||||||
<div class="rightContent">
|
<div class="rightContent">
|
||||||
<RightDrawer>
|
<RightDrawer>
|
||||||
<div v-show="uiStore.searchDrawerOpen">
|
|
||||||
<basicInfoMod />
|
|
||||||
</div>
|
|
||||||
<div v-show="!uiStore.searchDrawerOpen">
|
|
||||||
<ShuiZhiJianCeGongZuoQingKuang />
|
<ShuiZhiJianCeGongZuoQingKuang />
|
||||||
<EnvironmentalQuality />
|
<EnvironmentalQuality />
|
||||||
<SZYCBH v-if="wbsCode != 'all'"></SZYCBH>
|
<SZYCBH v-if="wbsCode != 'all'"></SZYCBH>
|
||||||
</div>
|
|
||||||
</RightDrawer>
|
</RightDrawer>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -2,8 +2,6 @@
|
|||||||
import { ref, watch } from 'vue';
|
import { ref, watch } from 'vue';
|
||||||
import JidiSelectorMod from '@/modules/jidiSelectorMod.vue';
|
import JidiSelectorMod from '@/modules/jidiSelectorMod.vue';
|
||||||
import RightDrawer from '@/components/RightDrawer/index.vue';
|
import RightDrawer from '@/components/RightDrawer/index.vue';
|
||||||
import { useUiStore } from '@/store/modules/ui';
|
|
||||||
import basicInfoMod from '@/modules/rightSearchDrawer/basicInfo/index.vue';
|
|
||||||
|
|
||||||
import ZengZhiJiHuaWanChengQingKuang from '@/modules/zengZhiJiHuaWanChengQingKuang/index.vue';
|
import ZengZhiJiHuaWanChengQingKuang from '@/modules/zengZhiJiHuaWanChengQingKuang/index.vue';
|
||||||
import ZengZhiZhanJieShaoMod from '@/modules/zengZhiZhanJieShaoMod/index.vue';
|
import ZengZhiZhanJieShaoMod from '@/modules/zengZhiZhanJieShaoMod/index.vue';
|
||||||
@ -12,7 +10,6 @@ import ZZZYXSJTJ from '@/modules/zengzhizhanyunxingsjtj/index.vue';
|
|||||||
import FLZLLB from '@/modules/GYZLLB/index.vue';
|
import FLZLLB from '@/modules/GYZLLB/index.vue';
|
||||||
import { useJidiSelectEventStore } from '@/store/modules/jidiSelectEvent';
|
import { useJidiSelectEventStore } from '@/store/modules/jidiSelectEvent';
|
||||||
const JidiSelectEventStore = useJidiSelectEventStore();
|
const JidiSelectEventStore = useJidiSelectEventStore();
|
||||||
const uiStore = useUiStore();
|
|
||||||
const wbsCode = ref('all');
|
const wbsCode = ref('all');
|
||||||
watch(
|
watch(
|
||||||
() => JidiSelectEventStore.selectedItem,
|
() => JidiSelectEventStore.selectedItem,
|
||||||
@ -35,16 +32,11 @@ watch(
|
|||||||
</div>
|
</div>
|
||||||
<div class="rightContent">
|
<div class="rightContent">
|
||||||
<RightDrawer>
|
<RightDrawer>
|
||||||
<div v-show="uiStore.searchDrawerOpen">
|
|
||||||
<basicInfoMod />
|
|
||||||
</div>
|
|
||||||
<div v-show="!uiStore.searchDrawerOpen">
|
|
||||||
<ZZZJSYXQKTT />
|
<ZZZJSYXQKTT />
|
||||||
<ZengZhiZhanJieShaoMod v-if="wbsCode != 'all'" />
|
<ZengZhiZhanJieShaoMod v-if="wbsCode != 'all'" />
|
||||||
<ZengZhiJiHuaWanChengQingKuang v-if="wbsCode != 'all'" />
|
<ZengZhiJiHuaWanChengQingKuang v-if="wbsCode != 'all'" />
|
||||||
<FLZLLB title="放流总量" />
|
<FLZLLB title="放流总量" />
|
||||||
<ZZZYXSJTJ />
|
<ZZZYXSJTJ />
|
||||||
</div>
|
|
||||||
</RightDrawer>
|
</RightDrawer>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,7 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, watch } from 'vue';
|
import { ref, watch } from 'vue';
|
||||||
import { useUiStore } from '@/store/modules/ui';
|
|
||||||
import basicInfoMod from '@/modules/rightSearchDrawer/basicInfo/index.vue';
|
|
||||||
import JidiSelectorMod from '@/modules/jidiSelectorMod.vue';
|
import JidiSelectorMod from '@/modules/jidiSelectorMod.vue';
|
||||||
import RightDrawer from '@/components/RightDrawer/index.vue';
|
import RightDrawer from '@/components/RightDrawer/index.vue';
|
||||||
import ZhiWuYuanJianSheJiJieRuQingKuangBar from '@/modules/ZhenXiZhiWuYuanMod/ZhiWuYuanJianSheJiJieRuQingKuangBar/index.vue'; // 植物园建设及接入情况
|
import ZhiWuYuanJianSheJiJieRuQingKuangBar from '@/modules/ZhenXiZhiWuYuanMod/ZhiWuYuanJianSheJiJieRuQingKuangBar/index.vue'; // 植物园建设及接入情况
|
||||||
@ -9,7 +7,6 @@ import Dwjzqk from '@/modules/ZhenXiZhiWuYuanMod/Dwjzqk/index.vue';
|
|||||||
import JZZQKJS from '@/modules/DongWuJiuZhuZhan/JiuZhuZhanQingKuangJieShao.vue'; // 救助站情况介绍
|
import JZZQKJS from '@/modules/DongWuJiuZhuZhan/JiuZhuZhanQingKuangJieShao.vue'; // 救助站情况介绍
|
||||||
import { useJidiSelectEventStore } from '@/store/modules/jidiSelectEvent';
|
import { useJidiSelectEventStore } from '@/store/modules/jidiSelectEvent';
|
||||||
const JidiSelectEventStore = useJidiSelectEventStore();
|
const JidiSelectEventStore = useJidiSelectEventStore();
|
||||||
const uiStore = useUiStore();
|
|
||||||
const wbsCode = ref('all');
|
const wbsCode = ref('all');
|
||||||
watch(
|
watch(
|
||||||
() => JidiSelectEventStore.selectedItem,
|
() => JidiSelectEventStore.selectedItem,
|
||||||
@ -32,14 +29,9 @@ watch(
|
|||||||
</div>
|
</div>
|
||||||
<div class="rightContent">
|
<div class="rightContent">
|
||||||
<RightDrawer>
|
<RightDrawer>
|
||||||
<div v-show="uiStore.searchDrawerOpen">
|
|
||||||
<basicInfoMod />
|
|
||||||
</div>
|
|
||||||
<div v-show="!uiStore.searchDrawerOpen">
|
|
||||||
<JZZQKJS v-if="wbsCode != 'all'" />
|
<JZZQKJS v-if="wbsCode != 'all'" />
|
||||||
<ZhiWuYuanJianSheJiJieRuQingKuangBar v-if="wbsCode == 'all'" />
|
<ZhiWuYuanJianSheJiJieRuQingKuangBar v-if="wbsCode == 'all'" />
|
||||||
<Dwjzqk />
|
<Dwjzqk />
|
||||||
</div>
|
|
||||||
</RightDrawer>
|
</RightDrawer>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user