WholeProcessPlatform/frontend/src/modules/rightSearchDrawer/monitoringTable/components/FlowDischarge.vue
2026-08-11 16:24:41 +08:00

418 lines
11 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<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> (/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>