761 lines
21 KiB
Vue
761 lines
21 KiB
Vue
|
|
<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>
|