WholeProcessPlatform/frontend/src/modules/rightSearchDrawer/monitoringTable/components/FlowDischarge.vue

418 lines
11 KiB
Vue
Raw Normal View History

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