添加出库水温监测数据

This commit is contained in:
扈兆增 2026-06-08 17:01:10 +08:00
parent ed29837df1
commit 68e7dc11be
8 changed files with 1859 additions and 808 deletions

View File

@ -62,14 +62,22 @@ export function getMonitorData(data: any) {
data data
}); });
} }
// 生态流量 -限制范围查询 // 监测数据 - 水温
export function getEngLimit(data: any) { export function getMonitorDataWaterTemp(data: any) {
return request({ return request({
url: '/eq/data/getEngLimit', url: '/api/wmp-env-server/sw/alongDetail/qgc/GetKendoListCust',
method: 'post', method: 'post',
data data
}); });
} }
// 监测数据 - 水温 - 查询显示 综合/等温
export function getMonitorDataWaterTempPowerStation(params: any) {
return request({
url: '/api/wmp-env-server/sw/wtrv/getIoWtrvFlag',
method: 'get',
params
});
}
// 生态流量 - 限制范围查询 - 日 // 生态流量 - 限制范围查询 - 日
export function getEngLimitDay(data: any) { export function getEngLimitDay(data: any) {
return request({ return request({
@ -78,3 +86,11 @@ export function getEngLimitDay(data: any) {
data data
}); });
} }
// 生态流量 - 限制范围查询 - 月
export function getEngLimit(data: any) {
return request({
url: '/eq/interval/qgc/month/GetKendoListCust',
method: 'post',
data
});
}

View File

@ -11,6 +11,10 @@
:row-key="rowKey" :row-key="rowKey"
@change="handleTableChange" @change="handleTableChange"
> >
<!-- 合计行插槽 -->
<template #summary>
<slot name="summary"></slot>
</template>
<!-- 使用 bodyCell 插槽透传自定义列内容 --> <!-- 使用 bodyCell 插槽透传自定义列内容 -->
<template #bodyCell="{ column, text, record, index }"> <template #bodyCell="{ column, text, record, index }">
<template v-for="slotName in customSlotNames" :key="slotName"> <template v-for="slotName in customSlotNames" :key="slotName">

View File

@ -152,7 +152,7 @@ const columnsConfig = ref([
columns: BasicColumns columns: BasicColumns
}, },
{ {
type: 'wt_point', type: 'WT',
columns: wtPointColumns columns: wtPointColumns
}, },
{ {

View File

@ -1,7 +1,7 @@
<template> <template>
<div class="monitor-info"> <div class="monitor-info">
<!-- Tabs 区域 --> <!-- Tabs 区域 -->
<a-tabs v-model:activeKey="activeTabKey"> <a-tabs v-model:activeKey="activeTabKey" @change="handleTabChange">
<a-tab-pane v-for="tab in tabsList" :key="tab.key" :tab="tab.name" /> <a-tab-pane v-for="tab in tabsList" :key="tab.key" :tab="tab.name" />
<template #rightExtra> <template #rightExtra>
<div class="search-bar"> <div class="search-bar">
@ -17,40 +17,105 @@
<a-button type="primary" class="search-btn" @click="handleSearch"> <a-button type="primary" class="search-btn" @click="handleSearch">
查询 查询
</a-button> </a-button>
<a-button
v-if="activeTabKey === 'swjc.tabs.jcsj'"
type="primary"
class="search-btn"
@click="handleExport"
>
导出
</a-button>
</div> </div>
</template> </template>
</a-tabs> </a-tabs>
<div class="tab-checkbox" v-if="activeTabKey === 'swjc.tabs.jcsj'">
<a-checkbox-group
v-model:value="selectedColumns"
name="checkboxgroup"
:options="filteredCheckBoxOptions"
class="checkbox-group"
@change="handleCheckboxChange"
/>
</div>
<!-- 内容区域 --> <!-- 内容区域 -->
<div v-if="activeTabKey === 'dzxq.tabs.jcsj'" class="tab-content">
<a-spin :spinning="isLoading" tip="加载中..."> <a-spin :spinning="isLoading" tip="加载中...">
<!-- 左侧图表 + 右侧表格 --> <div v-if="activeTabKey" class="tab-content">
<div class="content-body"> <div class="content-body">
<div class="chart-wrapper" ref="chartRef"></div> <!-- 电站运行过程线 -->
<PowerStationChart
v-if="activeTabKey === 'dzxq.tabs.jcsj'"
:data="chartData"
/>
<!-- 水温 -->
<WaterTempChart
v-if="activeTabKey === 'swjc.tabs.jcsj'"
:data="chartData"
:mode="currentChartMode"
/>
<!-- 右侧表格 -->
<div class="table-wrapper"> <div class="table-wrapper">
<BasicTable <BasicTable
ref="tableRef" ref="tableRef"
:scrollY="480" :scrollY="activeTabKey === 'swjc.tabs.jcsj' ? 360 : 480"
:scrollX="980" :scrollX="tableScrollX"
:columns="currentColumns" :columns="currentColumns"
:data="tableData" :data="tableData"
:isPage="true" >
/> <template #summary>
<a-table-summary fixed v-if="activeTabKey === 'swjc.tabs.jcsj'">
<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>
</div> </div>
</div> </div>
</a-spin> </a-spin>
</div> </div>
</div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ref, watch, computed, onMounted, onUnmounted } from 'vue'; import { ref, watch, computed, onMounted } from 'vue';
import * as echarts from 'echarts';
import dayjs, { Dayjs } from 'dayjs'; import dayjs, { Dayjs } from 'dayjs';
import { getMonitorData } from '@/api/mapModal'; import { Tooltip } from 'ant-design-vue';
import {
getMonitorData,
getMonitorDataWaterTemp,
getMonitorDataWaterTempPowerStation
} 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 { DateSetting } from '@/utils/enumeration'; import { DateSetting } from '@/utils/enumeration';
import { InfoCircleOutlined } from '@ant-design/icons-vue';
import PowerStationChart from './charts/PowerStationChart.vue';
import WaterTempChart from './charts/WaterTempChart.vue';
import { useWaterTempTable } from './composables/useWaterTempTable';
const modelStore = useModelStore(); const modelStore = useModelStore();
@ -67,34 +132,77 @@ const props = defineProps({
const hasLoaded = ref(false); const hasLoaded = ref(false);
const isLoading = ref(false); const isLoading = ref(false);
const activeTabKey = ref('dzxq.tabs.jcsj');
const chartRef = ref<HTMLDivElement | null>(null);
let chartInstance: echarts.ECharts | null = null;
const tableRef = ref<any>(null); const tableRef = ref<any>(null);
const tableData = ref<any[]>([]); const tableData = ref<any[]>([]);
const chartData = ref<any[]>([]);
// ==================== Tab ==================== // ==================== Tab ====================
const tabsConfig: Record<string, any[]> = { const tabsConfig: Record<string, any[]> = {
'dzxq.tabs.jcsj': [{ name: '电站运行过程线', key: 'dzxq.tabs.jcsj' }] 'dzxq.tabs.jcsj': [{ name: '电站运行过程线', key: 'dzxq.tabs.jcsj' }],
'swjc.tabs.jcsj': [{ name: '水温', key: 'swjc.tabs.jcsj' }]
}; };
const tabsList = computed(() => { const tabsList = computed(() => {
return tabsConfig[props.code] || tabsConfig['dzxq.tabs.jcsj']; return tabsConfig[props.code] || tabsConfig['dzxq.tabs.jcsj'];
}); });
// activeTabKey code
const getDefaultTabKey = () => {
const tabs = tabsConfig[props.code];
return tabs && tabs.length > 0 ? tabs[0].key : 'dzxq.tabs.jcsj';
};
const activeTabKey = ref(getDefaultTabKey());
// ==================== ====================
const {
selectedColumns,
showSummaryCheckbox,
showDeepCheckbox,
filteredCheckBoxOptions,
currentChartMode,
currentColumns,
tableScrollX,
summaryColumns,
summaryRows,
handleCheckboxChange: handleCheckboxChangeBase
} = useWaterTempTable(chartData, activeTabKey);
const handleCheckboxChange = (values: any[]) => {
handleCheckboxChangeBase(values, fetchData);
};
// ==================== ==================== // ==================== ====================
const defaultDateRangeConfig: Record<string, { days: number }> = {
'dzxq.tabs.jcsj': { days: 30 },
'swjc.tabs.jcsj': { days: 7 }
};
const initDateRange = (): [Dayjs, Dayjs] => {
const days = defaultDateRangeConfig[activeTabKey.value]?.days || 30;
const endDate = dayjs();
let startDate = dayjs().subtract(days, 'day');
if (activeTabKey.value === 'swjc.tabs.jcsj') {
startDate = startDate.startOf('day');
}
return [startDate, endDate];
};
const dateRange = ref<[Dayjs, Dayjs] | undefined>(initDateRange());
const timeConfig: Record<string, { showTime: boolean; format: string }> = { const timeConfig: Record<string, { showTime: boolean; format: string }> = {
'dzxq.tabs.jcsj': { showTime: true, format: 'YYYY-MM-DD HH:mm' } 'dzxq.tabs.jcsj': { showTime: true, format: 'YYYY-MM-DD HH:mm' },
'swjc.tabs.jcsj': { showTime: true, format: 'YYYY-MM-DD HH:mm' }
}; };
const currentTimeConfig = computed(() => { const currentTimeConfig = computed(() => {
return timeConfig[props.code] || timeConfig['dzxq.tabs.jcsj']; return timeConfig[activeTabKey.value] || timeConfig['dzxq.tabs.jcsj'];
}); });
const showTime = computed(() => currentTimeConfig.value.showTime); const showTime = computed(() => currentTimeConfig.value.showTime);
const dateFormat = computed(() => currentTimeConfig.value.format); const dateFormat = computed(() => currentTimeConfig.value.format);
//
const showTimeConfig = { const showTimeConfig = {
format: 'HH:mm', format: 'HH:mm',
hourStep: 1, hourStep: 1,
@ -102,103 +210,19 @@ const showTimeConfig = {
secondStep: 60 secondStep: 60
}; };
//
const initDateRange = (): [Dayjs, Dayjs] => {
const endDate = dayjs();
const startDate = dayjs().subtract(1, 'month');
return [startDate, endDate];
};
const dateRange = ref<[Dayjs, Dayjs] | undefined>(initDateRange());
//
const disabledDate = (current: Dayjs) => { const disabledDate = (current: Dayjs) => {
return current && current.isAfter(dayjs(), 'day'); return current && current.isAfter(dayjs(), 'day');
}; };
// ==================== ==================== // ==================== API ====================
const tableColumnsConfig: Record<string, any[]> = { const apiConfig: Record<string, Function> = {
'dzxq.tabs.jcsj': [ 'dzxq.tabs.jcsj': getMonitorData,
{ 'swjc.tabs.jcsj': getMonitorDataWaterTemp
title: '时间',
dataIndex: 'tm',
width: 180,
fixed: 'left',
customRender: ({ text }: any) =>
text ? dayjs(text).format('YYYY-MM-DD HH:mm:ss') : '-'
},
{
title: '坝上水位(m)',
dataIndex: 'rz',
width: 130,
customRender: ({ text }: any) =>
text !== undefined && text !== null ? Number(text).toFixed(2) : '-'
},
{
title: '坝下水位(m)',
dataIndex: 'dz',
width: 130,
customRender: ({ text }: any) =>
text !== undefined && text !== null ? Number(text).toFixed(2) : '-'
},
{
title: '入库流量(m³/s)',
dataIndex: 'qi',
width: 130,
customRender: ({ text }: any) =>
text !== undefined && text !== null ? Math.round(Number(text)) : '-'
},
{
title: '出库流量(m³/s)',
dataIndex: 'qo',
width: 130,
customRender: ({ text }: any) =>
text !== undefined && text !== null ? Number(text) : '-'
},
{
title: '生态流量(m³/s)',
dataIndex: 'qec',
width: 130,
customRender: ({ text }: any) =>
text !== undefined && text !== null ? Number(text).toFixed(1) : '-'
},
{
title: '生态流量限值(m³/s)',
dataIndex: 'qecLimit',
width: 150,
customRender: ({ text }: any) =>
text !== undefined && text !== null ? Number(text).toFixed(1) : '-'
}
]
}; };
const currentColumns = computed(() => { // filter
return tableColumnsConfig[props.code] || tableColumnsConfig['dzxq.tabs.jcsj']; const buildFilterParams = () => {
}); return {
// ==================== ====================
const chartLegendConfig: Record<string, string[]> = {
'dzxq.tabs.jcsj': [
'坝上水位',
'坝下水位',
'入库流量',
'出库流量',
'生态流量',
'生态流量限值'
]
};
const chartLegendData = computed(() => {
return chartLegendConfig[props.code] || chartLegendConfig['dzxq.tabs.jcsj'];
});
// ==================== ====================
const fetchData = async () => {
if (!dateRange.value) return;
isLoading.value = true;
const params = {
filter: { filter: {
logic: 'and', logic: 'and',
filters: [ filters: [
@ -212,13 +236,13 @@ const fetchData = async () => {
field: 'tm', field: 'tm',
operator: 'gte', operator: 'gte',
dataType: 'date', dataType: 'date',
value: dateRange.value[0].format('YYYY-MM-DD HH:mm:ss') value: dateRange.value![0].format('YYYY-MM-DD HH:mm:ss')
}, },
{ {
field: 'tm', field: 'tm',
operator: 'lte', operator: 'lte',
dataType: 'date', dataType: 'date',
value: dateRange.value[1].format('YYYY-MM-DD HH:mm:ss') value: dateRange.value![1].format('YYYY-MM-DD HH:mm:ss')
} }
] ]
}, },
@ -229,324 +253,79 @@ const fetchData = async () => {
} }
] ]
}; };
};
// ==================== ====================
const fetchData = async () => {
if (!dateRange.value) return;
isLoading.value = true;
try { try {
const res = await getMonitorData(params); const mainApi = apiConfig[activeTabKey.value] || getMonitorData;
if (activeTabKey.value === 'swjc.tabs.jcsj') {
const [mainRes, powerStationRes] = await Promise.all([
mainApi(buildFilterParams()),
getMonitorDataWaterTempPowerStation({
stcd: modelStore.params.stcd
})
]);
const mainData = mainRes?.data?.data || mainRes?.data?.records || [];
// powerStationRes.data checkbox /
showSummaryCheckbox.value = !!powerStationRes?.data?.ioWtrv;
showDeepCheckbox.value = !!powerStationRes?.data?.hasRstcdWtvt;
//
tableData.value = [...mainData].reverse();
//
chartData.value = mainData;
} else {
const res = await mainApi(buildFilterParams());
const rawData = res?.data?.data || res?.data?.records || []; const rawData = res?.data?.data || res?.data?.records || [];
// 11 //
const reversedData = [...rawData].reverse(); tableData.value = [...rawData].reverse();
tableData.value = reversedData; //
updateChart(rawData); // chartData.value = rawData;
}
} catch (error) { } catch (error) {
console.error('获取数据失败:', error); console.error('获取数据失败:', error);
tableData.value = []; tableData.value = [];
chartData.value = [];
} finally { } finally {
isLoading.value = false; isLoading.value = false;
} }
}; };
//
const handleSearch = () => { const handleSearch = () => {
fetchData(); fetchData();
}; };
// ==================== ==================== const handleExport = () => {
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('HH:mm'));
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);
// Y
const waterValues = [...rzData, ...dzData].filter(
v => v !== null && v !== undefined
);
const waterMax = waterValues.length > 0 ? Math.max(...waterValues) : 10;
const waterYMax = Math.ceil(waterMax * 1.1);
const waterYMin = Math.floor(Math.min(...waterValues) * 0.95);
// Y
const flowValues = [...qiData, ...qoData, ...qecData, ...qecLimitData].filter(
v => v !== null && v !== undefined
);
const flowMax = flowValues.length > 0 ? Math.max(...flowValues) : 10;
const flowYMax = Math.ceil(flowMax * 1.1);
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>`;
//
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)' }
};
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,
data: chartLegendData.value,
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: [
// Y
{
name: '水位',
type: 'value',
position: 'left',
axisLine: { lineStyle: { color: '#000000' } },
alignTicks: true,
scale: true,
splitNumber: 9,
boundaryGap: ['10%', 0]
},
// Y
{
name: '流量',
type: 'value',
position: 'right',
axisLine: { lineStyle: { color: '#000000' } },
alignTicks: true,
scale: true,
splitNumber: 9,
boundaryGap: ['10%', 0]
}
],
series: [
// 使 Y
{
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' }
]
}
},
// 使 Y
{
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);
}; };
const initChart = () => { // Tab
if (!chartRef.value) return; const handleTabChange = (key: string) => {
if (chartInstance) { const days = defaultDateRangeConfig[key]?.days || 30;
chartInstance.dispose(); const endDate = dayjs();
} let startDate = dayjs().subtract(days, 'day');
chartInstance = echarts.init(chartRef.value);
};
const handleResize = () => { if (key === 'swjc.tabs.jcsj') {
if (chartInstance) { startDate = startDate.startOf('day');
chartInstance.resize();
} }
};
const destroyChart = () => { dateRange.value = [startDate, endDate];
if (chartInstance) { fetchData();
chartInstance.dispose();
chartInstance = null;
}
window.removeEventListener('resize', handleResize);
}; };
// ==================== ==================== // ==================== ====================
const initData = () => { const initData = () => {
if (hasLoaded.value) return; if (hasLoaded.value) return;
fetchData(); fetchData();
setTimeout(() => {
initChart();
}, 200);
hasLoaded.value = true; hasLoaded.value = true;
}; };
@ -574,20 +353,29 @@ onMounted(() => {
window.addEventListener('resize', handleResize); window.addEventListener('resize', handleResize);
}); });
onUnmounted(() => { const handleResize = () => {
destroyChart(); // resize
window.removeEventListener('resize', handleResize); };
});
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.monitor-info { .monitor-info {
width: 100%; width: 100%;
min-height: 600px; min-height: 600px;
position: relative;
:deep(.ant-tabs-nav) { :deep(.ant-tabs-nav) {
margin-bottom: 4px; margin-bottom: 4px;
} }
.checkbox-group {
margin-bottom: 12px;
}
.summary-tip-icon {
color: rgb(53, 169, 255);
margin-left: 4px;
}
.search-bar { .search-bar {
width: 100%; width: 100%;
margin-bottom: 12px; margin-bottom: 12px;
@ -599,6 +387,12 @@ onUnmounted(() => {
margin-left: 8px; margin-left: 8px;
} }
} }
.tab-checkbox {
position: absolute;
top: 12px;
left: 30%;
z-index: 100;
}
.tab-content { .tab-content {
width: 100%; width: 100%;
@ -608,7 +402,7 @@ onUnmounted(() => {
display: flex; display: flex;
height: 600px; height: 600px;
.chart-wrapper { :deep(.chart-container) {
flex: 1; flex: 1;
height: 100%; height: 100%;
box-sizing: border-box; box-sizing: border-box;
@ -621,6 +415,7 @@ onUnmounted(() => {
height: 100%; height: 100%;
box-sizing: border-box; box-sizing: border-box;
padding: 10px; padding: 10px;
overflow: hidden;
overflow-x: auto; overflow-x: auto;
border-left: 1px solid #dcdfe6; border-left: 1px solid #dcdfe6;
margin-left: 10px; margin-left: 10px;
@ -628,4 +423,7 @@ onUnmounted(() => {
} }
} }
} }
.summary-row {
background-color: #fafafa;
}
</style> </style>

View File

@ -0,0 +1,315 @@
<template>
<div class="chart-container" ref="chartRef"></div>
</template>
<script lang="ts" setup>
import { ref, watch, onMounted, onUnmounted } from 'vue';
import * as echarts from 'echarts';
import dayjs from 'dayjs';
const props = defineProps({
data: {
type: Array as () => any[],
default: () => []
}
});
const chartRef = ref<HTMLDivElement | null>(null);
let chartInstance: echarts.ECharts | null = null;
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 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('HH:mm'));
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 waterValues = [...rzData, ...dzData].filter(
v => v !== null && v !== undefined
);
const waterMax = waterValues.length > 0 ? Math.max(...waterValues) : 10;
const waterYMax = Math.ceil(waterMax * 1.1);
const waterYMin = Math.floor(Math.min(...waterValues) * 0.95);
const flowValues = [...qiData, ...qoData, ...qecData, ...qecLimitData].filter(
v => v !== null && v !== undefined
);
const flowMax = flowValues.length > 0 ? Math.max(...flowValues) : 10;
const flowYMax = Math.ceil(flowMax * 1.1);
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,
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: '水位',
type: 'value',
position: 'left',
axisLine: { lineStyle: { color: '#000000' } },
alignTicks: true,
scale: true,
splitNumber: 9,
boundaryGap: ['10%', 0]
},
{
name: '流量',
type: 'value',
position: 'right',
axisLine: { lineStyle: { color: '#000000' } },
alignTicks: true,
scale: true,
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);
};
const initChart = () => {
if (!chartRef.value) return;
if (chartInstance) {
chartInstance.dispose();
}
chartInstance = echarts.init(chartRef.value);
};
const handleResize = () => {
if (chartInstance) {
chartInstance.resize();
}
};
watch(
() => props.data,
newData => {
updateChart(newData);
},
{ deep: true }
);
onMounted(() => {
initChart();
window.addEventListener('resize', handleResize);
});
onUnmounted(() => {
if (chartInstance) {
chartInstance.dispose();
chartInstance = null;
}
window.removeEventListener('resize', handleResize);
});
</script>
<style lang="scss" scoped>
.chart-container {
width: 100%;
height: 100%;
}
</style>

View File

@ -0,0 +1,505 @@
<template>
<div class="chart-container" ref="chartRef"></div>
</template>
<script lang="ts" setup>
import { ref, watch, onMounted, onUnmounted } from 'vue';
import * as echarts from 'echarts';
import dayjs from 'dayjs';
const props = defineProps({
data: {
type: Array as () => any[],
default: () => []
},
mode: {
type: String,
default: 'base' // 'base' | 'deep' | 'summary'
}
});
const chartRef = ref<HTMLDivElement | null>(null);
let chartInstance: echarts.ECharts | null = null;
//
function shouldStagger(width: number, dataLen: number) {
const labelWidth = 100;
const maxLabels = Math.floor(width / labelWidth);
return dataLen > maxLabels;
}
function getAxisLabelConfig(chartWidth: number, xAxisData: string[]) {
const dataLen = xAxisData.length;
const needStagger = shouldStagger(chartWidth, dataLen);
return {
fontSize: 12,
interval: 'auto',
formatter: (value: string, index: number) => {
const [date, time] = value.split(' ');
if (needStagger) {
if (index % 2 === 0) {
return `${date}\n${time}\n `;
} else {
return ` \n${date}\n${time}`;
}
} else {
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);
// ==================== ====================
if (props.mode === 'base') {
const wtData = sorted.map(item => item.wt);
const option = {
tooltip: {
show: true,
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')
: '';
let html = `<div style="font-size:16px;margin-bottom:8px;">${fullTime}</div>`;
params.forEach((param: any) => {
const value =
param.value != null ? Number(param.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:${param.color};margin-right:8px;"></span>
<span style="flex:1;font-size:14px;text-align:left;margin-right:6px;">水温: </span>
<span style="font-size:14px;min-width:60px;text-align:right;"><strong>${value}</strong> °C</span>
</div>
`;
});
return html;
}
},
legend: {
type: 'scroll',
top: 10,
data: ['水温'],
textStyle: { fontSize: 12 }
},
grid: { left: 60, right: 40, top: 80, bottom: 80 },
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: wtData,
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);
return;
}
// ==================== ====================
if (props.mode === 'deep') {
const wtData = sorted.map(item => item.wt);
const depthData = sorted.map(item => item?.wtvtDataVo?.wthg);
const option = {
tooltip: {
show: true,
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')
: '';
let html = `<div style="font-size:16px;margin-bottom:8px;">${fullTime}</div>`;
params.forEach((param: any) => {
const value =
param.value != null
? param.seriesName.includes('水深')
? Number(param.value).toFixed(2)
: Number(param.value).toFixed(1)
: '-';
const unit = param.seriesName.includes('水深') ? 'm' : '°C';
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>${value}</strong> ${unit}</span>
</div>
`;
});
return html;
}
},
legend: {
type: 'scroll',
top: 10,
data: ['水温', '等温水深'],
textStyle: { fontSize: 12 }
},
grid: { left: 60, right: 60, top: 80, bottom: 80 },
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',
position: 'left',
axisLine: { lineStyle: { color: '#000000' } },
scale: true,
splitLine: {
show: true,
lineStyle: { color: '#bfbfbf', type: 'solid' }
}
},
//
{
name: '水深(m)',
type: 'value',
position: 'right',
axisLine: { lineStyle: { color: '#000000' } },
scale: true,
splitLine: { show: false }
}
],
series: [
{
name: '水温',
type: 'line',
yAxisIndex: 0,
data: wtData,
smooth: true,
showSymbol: false,
lineStyle: { color: '#4B79AB', width: 2 },
itemStyle: { color: '#4B79AB' }
},
{
name: '等温水深',
type: 'line',
yAxisIndex: 1,
data: depthData,
smooth: true,
showSymbol: false,
lineStyle: { color: '#AB7AE5', width: 2 },
itemStyle: { color: '#AB7AE5' }
}
],
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);
return;
}
// ==================== ====================
if (props.mode === 'summary') {
const outWtData = sorted.map(item => item.wt);
const inWtData = sorted.map(item => item?.iwtDataVo?.wt);
const natureTmpData = sorted.map(item => item.natureTmp);
const airTempData = sorted.map(item => item?.tmpDataVo?.at);
const outFlowData = sorted.map(item => item?.hydropwDataVo?.qo);
const inFlowData = sorted.map(item => item?.hydropwDataVo?.qi);
const option = {
tooltip: {
show: true,
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')
: '';
let html = `<div style="font-size:16px;margin-bottom:8px;">${fullTime}</div>`;
params.forEach((param: any) => {
const isFlow = param.seriesName.includes('流量');
const value =
param.value != null
? isFlow
? Number(param.value).toFixed(3)
: Number(param.value).toFixed(1)
: '-';
const unit = isFlow ? 'm³/s' : '°C';
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>${value}</strong> ${unit}</span>
</div>
`;
});
return html;
}
},
legend: {
type: 'scroll',
top: 10,
data: [
'出库水温',
'入库水温',
'天然水温',
'气温',
'出库流量',
'入库流量'
],
textStyle: { fontSize: 12 }
},
grid: { left: 60, right: 60, top: 80, bottom: 80 },
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',
position: 'left',
axisLine: { lineStyle: { color: '#000000' } },
scale: true,
splitLine: {
show: true,
lineStyle: { color: '#bfbfbf', type: 'solid' }
}
},
//
{
name: '流量(m³/s)',
type: 'value',
position: 'right',
axisLine: { lineStyle: { color: '#000000' } },
scale: true,
splitLine: { show: false }
}
],
series: [
//
{
name: '出库水温',
type: 'line',
yAxisIndex: 0,
data: outWtData,
smooth: true,
showSymbol: false,
lineStyle: { color: '#78C300', width: 2 },
itemStyle: { color: '#78C300' }
},
{
name: '入库水温',
type: 'line',
yAxisIndex: 0,
data: inWtData,
smooth: true,
showSymbol: false,
lineStyle: { color: '#9557A4', width: 2 },
itemStyle: { color: '#9557A4' }
},
{
name: '天然水温',
type: 'line',
yAxisIndex: 0,
data: natureTmpData,
smooth: true,
showSymbol: false,
lineStyle: { color: '#DF91AB', width: 2 },
itemStyle: { color: '#DF91AB' }
},
{
name: '气温',
type: 'line',
yAxisIndex: 0,
data: airTempData,
smooth: true,
showSymbol: false,
lineStyle: { color: '#7399C6', width: 2 },
itemStyle: { color: '#7399C6' }
},
//
{
name: '出库流量',
type: 'line',
yAxisIndex: 1,
data: outFlowData,
smooth: true,
showSymbol: false,
lineStyle: { color: '#56C2E3', width: 2 },
itemStyle: { color: '#56C2E3' }
},
{
name: '入库流量',
type: 'line',
yAxisIndex: 1,
data: inFlowData,
smooth: true,
showSymbol: false,
lineStyle: { color: '#DBB629', width: 2 },
itemStyle: { color: '#DBB629' }
}
],
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);
return;
}
};
const initChart = () => {
if (!chartRef.value) return;
if (chartInstance) {
chartInstance.dispose();
}
chartInstance = echarts.init(chartRef.value);
};
const handleResize = () => {
if (chartInstance) {
chartInstance.resize();
}
};
watch(
() => [props.data, props.mode],
() => {
updateChart(props.data);
},
{ deep: true }
);
onMounted(() => {
initChart();
window.addEventListener('resize', handleResize);
});
onUnmounted(() => {
if (chartInstance) {
chartInstance.dispose();
chartInstance = null;
}
window.removeEventListener('resize', handleResize);
});
</script>
<style lang="scss" scoped>
.chart-container {
width: 100%;
height: 100%;
}
</style>

View File

@ -0,0 +1,399 @@
import { ref, computed } from 'vue';
import dayjs from 'dayjs';
/**
* Checkbox
*/
export function useWaterTempTable(chartData: any[], activeTabKey: any) {
const selectedColumns = ref<any[]>([]);
// ==================== Checkbox 配置 ====================
const checkBoxOptions = [
{ label: '综合分析', value: 'summary' },
{ label: '等深水温', value: 'deep' }
];
const showSummaryCheckbox = ref(false); // ioWtrv
const showDeepCheckbox = ref(false); // hasRstcdWtvt
const isDataEmpty = computed(
() => !Array.isArray(chartData.value) || chartData.value.length === 0
);
// 根据 powerStationRes 数据 + 互斥逻辑 动态过滤 checkbox 选项
const filteredCheckBoxOptions = computed(() => {
if (activeTabKey.value !== 'swjc.tabs.jcsj') return [];
if (isDataEmpty.value) return [];
const selected = selectedColumns.value;
const hasSelected = selected.length > 0;
const selectedValue = selected[0];
return checkBoxOptions.filter(opt => {
if (opt.value === 'summary' && !showSummaryCheckbox.value) return false;
if (opt.value === 'deep' && !showDeepCheckbox.value) return false;
if (hasSelected) {
return opt.value === selectedValue;
}
return true;
});
});
const isDeepMode = computed(() => selectedColumns.value.includes('deep'));
const isSummaryMode = computed(() =>
selectedColumns.value.includes('summary')
);
const currentChartMode = computed(() => {
if (isSummaryMode.value) return 'summary';
if (isDeepMode.value) return 'deep';
return 'base';
});
// Checkbox 变化处理
const handleCheckboxChange = (values: any[], fetchData: () => void) => {
const hasSummary = values.includes('summary');
const hasDeep = values.includes('deep');
if (hasSummary && hasDeep) {
selectedColumns.value = [values[values.length - 1]];
} else {
selectedColumns.value = values;
}
fetchData();
};
// ==================== 表格列配置 ====================
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 deepModeExtraColumns = [
{
title: '等温水深(m)',
dataIndex: 'deep_wt',
summary: true,
customRender: ({ record }: any) => {
const val = record?.wtvtDataVo?.wthg;
return val !== undefined && val !== null ? Number(val).toFixed(2) : '-';
}
},
{
title: '垂向水温(℃)',
dataIndex: 'vert_wt',
summary: true,
customRender: ({ record }: any) => {
const val = record?.wtvtDataVo?.vwt;
return val !== undefined && val !== null ? Number(val).toFixed(1) : '-';
}
}
];
const summaryModeColumns = [
{
title: '时间',
dataIndex: 'tm',
width: 130,
fixed: 'left',
customRender: ({ text }: any) =>
text ? dayjs(text).format('YYYY-MM-DD HH:mm') : '-'
},
{
title: '出库水温(℃)',
dataIndex: 'wt',
summary: true,
customRender: ({ text }: any) =>
text !== undefined && text !== null ? Number(text).toFixed(1) : '-'
},
{
title: '入库水温(℃)',
dataIndex: 'iwt_wt',
summary: true,
customRender: ({ record }: any) =>
record?.iwtDataVo?.wt !== undefined && record?.iwtDataVo?.wt !== null
? Number(record.iwtDataVo.wt).toFixed(1)
: '-'
},
{
title: '天然水温(°C)',
dataIndex: 'natureTmp',
summary: true,
customRender: ({ text }: any) =>
text !== undefined && text !== null ? Number(text).toFixed(1) : '-'
},
{
title: '气温(℃)',
dataIndex: 'tmp_at',
summary: true,
width: 80,
customRender: ({ record }: any) =>
record?.tmpDataVo?.at !== undefined && record?.tmpDataVo?.at !== null
? Number(record.tmpDataVo.at).toFixed(1)
: '-'
},
{
title: '出库流量(m³/s)',
dataIndex: 'hydropw_qo',
summary: true,
width: 120,
customRender: ({ record }: any) =>
record?.hydropwDataVo?.qo !== undefined &&
record?.hydropwDataVo?.qo !== null
? Number(record.hydropwDataVo.qo).toFixed(3)
: '-'
},
{
title: '入库流量(m³/s)',
dataIndex: 'hydropw_qi',
summary: true,
width: 120,
customRender: ({ record }: any) =>
record?.hydropwDataVo?.qi !== undefined &&
record?.hydropwDataVo?.qi !== null
? Number(record.hydropwDataVo.qi).toFixed(3)
: '-'
}
];
const tableColumnsConfig: Record<string, any[]> = {
'dzxq.tabs.jcsj': [
{
title: '时间',
dataIndex: 'tm',
width: 180,
fixed: 'left',
customRender: ({ text }: any) =>
text ? dayjs(text).format('YYYY-MM-DD HH:mm:ss') : '-'
},
{
title: '坝上水位(m)',
dataIndex: 'rz',
width: 130,
customRender: ({ text }: any) =>
text !== undefined && text !== null ? Number(text).toFixed(2) : '-'
},
{
title: '坝下水位(m)',
dataIndex: 'dz',
width: 130,
customRender: ({ text }: any) =>
text !== undefined && text !== null ? Number(text).toFixed(2) : '-'
},
{
title: '入库流量(m³/s)',
dataIndex: 'qi',
width: 130,
customRender: ({ text }: any) =>
text !== undefined && text !== null ? Math.round(Number(text)) : '-'
},
{
title: '出库流量(m³/s)',
dataIndex: 'qo',
width: 130,
customRender: ({ text }: any) =>
text !== undefined && text !== null ? Number(text) : '-'
},
{
title: '生态流量(m³/s)',
dataIndex: 'qec',
width: 130,
customRender: ({ text }: any) =>
text !== undefined && text !== null ? Number(text).toFixed(1) : '-'
},
{
title: '生态流量限值(m³/s)',
dataIndex: 'qecLimit',
width: 150,
customRender: ({ text }: any) =>
text !== undefined && text !== null ? Number(text).toFixed(1) : '-'
}
],
'swjc.tabs.jcsj': []
};
const swjcColumns = computed(() => {
if (isSummaryMode.value) {
return summaryModeColumns;
}
if (isDeepMode.value) {
return [...baseWaterTempColumns, ...deepModeExtraColumns];
}
return baseWaterTempColumns;
});
const currentColumns = computed(() => {
if (activeTabKey.value === 'swjc.tabs.jcsj') {
return swjcColumns.value;
}
return (
tableColumnsConfig[activeTabKey.value] ||
tableColumnsConfig['dzxq.tabs.jcsj']
);
});
const tableScrollX = computed(() => {
const columns = currentColumns.value;
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',
iwt_wt: 'iwtDataVo.wt',
natureTmp: 'natureTmp',
tmp_at: 'tmpDataVo.at',
hydropw_qo: 'hydropwDataVo.qo',
hydropw_qi: 'hydropwDataVo.qi',
deep_wt: 'wtvtDataVo.wthg',
vert_wt: 'wtvtDataVo.vwt'
};
const summaryColumns = computed(() => {
if (activeTabKey.value !== 'swjc.tabs.jcsj') return [];
const cols = currentColumns.value
.filter((col: any) => col.summary === true)
.map((col: any, idx: number) => ({
...col,
summaryIndex: idx + 1
}));
return cols;
});
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(() => {
if (activeTabKey.value !== 'swjc.tabs.jcsj') return [];
if (isDataEmpty.value) 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 }>
}
];
summaryColumns.value.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;
});
return {
// state
selectedColumns,
showSummaryCheckbox,
showDeepCheckbox,
isDataEmpty,
// computed
filteredCheckBoxOptions,
isDeepMode,
isSummaryMode,
currentChartMode,
currentColumns,
tableScrollX,
summaryColumns,
summaryRows,
// methods
handleCheckboxChange
};
}

View File

@ -8,12 +8,30 @@
<div class="search-form"> <div class="search-form">
<a-space size="middle"> <a-space size="middle">
<div>所属基地:</div> <div>所属基地:</div>
<a-select v-model:value="searchData.baseId" placeholder="请选择所属基地" style="width: 200px" show-search <a-select
:filter-option="filterOption" :options="jiDiList" @change="handleHydropowerStationChange" /> v-model:value="searchData.baseId"
placeholder="请选择所属基地"
style="width: 200px"
show-search
:filter-option="filterOption"
:options="jiDiList"
@change="handleHydropowerStationChange"
/>
<div>所属栖息地:</div> <div>所属栖息地:</div>
<a-select v-model:value="searchData.fhstcd" placeholder=" " style="width: 200px" show-search <a-select
:filter-option="filterOption" :options="stationOptions" /> v-model:value="searchData.fhstcd"
<a-input v-model:value="searchData.stnm" placeholder="请输入电站或设施名称" style="width: 200px" allow-clear /> placeholder=" "
style="width: 200px"
show-search
:filter-option="filterOption"
:options="stationOptions"
/>
<a-input
v-model:value="searchData.stnm"
placeholder="请输入电站或设施名称"
style="width: 200px"
allow-clear
/>
<a-button type="primary" @click="handleSearch">查询</a-button> <a-button type="primary" @click="handleSearch">查询</a-button>
<a-tooltip title="重置"> <a-tooltip title="重置">
@ -22,8 +40,16 @@
</a-space> </a-space>
</div> </div>
<!-- 列表 --> <!-- 列表 -->
<BasicTable ref="tableRef" :scrollY="360" :columns="columns" :list-url="fhGetKendoListCust" :enable-sort="true" @sort-change="handleSortChange" <BasicTable
:search-params="searchParams" :transform-data="customTransform"> ref="tableRef"
:scrollY="360"
:columns="columns"
:list-url="fhGetKendoListCust"
:enable-sort="true"
@sort-change="handleSortChange"
:search-params="searchParams"
:transform-data="customTransform"
>
<template #bodyCell="{ column, record }"> <template #bodyCell="{ column, record }">
<template v-if="column.key === 'ennm'"> <template v-if="column.key === 'ennm'">
<a @click="handleViewDetail(record, 'dz')" class="text-link"> <a @click="handleViewDetail(record, 'dz')" class="text-link">
@ -37,115 +63,96 @@
</template> </template>
</template> </template>
</BasicTable> </BasicTable>
</div> </div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ref, computed, watch, onMounted,nextTick } from 'vue' import { ref, computed, watch, onMounted, nextTick } from 'vue';
import { msstbprptGetKendoList, fhGetKendoListCust } from '@/api/qxd' import { msstbprptGetKendoList, fhGetKendoListCust } from '@/api/qxd';
import BasicTable from '@/components/BasicTable/index.vue' import BasicTable from '@/components/BasicTable/index.vue';
import { useModelStore } from "@/store/modules/model"; import { useModelStore } from '@/store/modules/model';
// ==================== Props ==================== // ==================== Props ====================
const props = defineProps<{ const props = defineProps<{
baseId?: string baseId?: string;
tabs?: any[] tabs?: any[];
activeKey?: string activeKey?: string;
}>() }>();
// ==================== ==================== // ==================== ====================
const tabIndex = ref(props.activeKey || 'FH') const tabIndex = ref(props.activeKey || 'FH');
const searchData = ref<any>({ const searchData = ref<any>({
baseId: props.baseId || 'all', baseId: props.baseId || 'all',
fhstcd: 'all', fhstcd: 'all',
stnm: null stnm: null
}) });
const modelStore = useModelStore(); const modelStore = useModelStore();
const tableRef = ref() const tableRef = ref();
const stationOptions: any = ref([]) const stationOptions: any = ref([]);
// ==================== () ==================== // ==================== () ====================
const jiDiList: any = ref([ const jiDiList: any = ref([
{ {
"value": "all", value: 'all',
"label": "当前全部", label: '当前全部'
}, },
{ {
"value": "01", value: '01',
"label": "金沙江干流", label: '金沙江干流'
}, },
{ {
"value": "02", value: '02',
"label": "雅砻江干流", label: '雅砻江干流'
}, },
{ {
"value": "03", value: '03',
"label": "大渡河干流", label: '大渡河干流'
}, },
{ {
"value": "04", value: '04',
"label": "乌江干流", label: '乌江干流'
}, },
{ {
"value": "05", value: '05',
"label": "长江上游干流", label: '长江上游干流'
}, },
{ {
"value": "10", value: '10',
"label": "湘西", label: '湘西'
}, },
{ {
"value": "08", value: '08',
"label": "黄河上游干流", label: '黄河上游干流'
}, },
{ {
"value": "09", value: '09',
"label": "黄河中游干流", label: '黄河中游干流'
}, },
{ {
"value": "06", value: '06',
"label": "南盘江-红水河", label: '南盘江-红水河'
}, },
{ {
"value": "12", value: '12',
"label": "东北", label: '东北'
}, },
{ {
"value": "07", value: '07',
"label": "澜沧江干流", label: '澜沧江干流'
}, },
{ {
"value": "13", value: '13',
"label": "怒江干流", label: '怒江干流'
}, },
{ {
"value": "11", value: '11',
"label": "闽浙赣", label: '闽浙赣'
}, },
{ {
"value": "other", value: 'other',
"label": "其他", label: '其他'
} }
]) ]);
// //
const columns = [ const columns = [
{ {
@ -153,77 +160,77 @@ const columns = [
title: '设施名称', title: '设施名称',
dataIndex: 'stnm', dataIndex: 'stnm',
width: '176px', width: '176px',
sorter: true, sorter: true
}, },
{ {
key: 'baseName', key: 'baseName',
title: '所在基地', title: '所在基地',
dataIndex: 'baseName', dataIndex: 'baseName',
width: '150px', width: '150px',
sorter: true, sorter: true
}, },
{ {
key: 'ennm', key: 'ennm',
title: '所属电站', title: '所属电站',
dataIndex: 'ennm', dataIndex: 'ennm',
width: '150px', width: '150px',
sorter: true, sorter: true
}, },
{ {
key: 'fhstnm', key: 'fhstnm',
title: '所属栖息地', title: '所属栖息地',
dataIndex: 'fhstnm', dataIndex: 'fhstnm',
width: '176px', width: '176px',
sorter: true, sorter: true
}, },
{ {
key: 'ststdt', key: 'ststdt',
title: '开工日期', title: '开工日期',
dataIndex: 'ststdt', dataIndex: 'ststdt',
width: '150px', width: '150px',
sorter: true, sorter: true
}, },
{ {
key: 'esstdt', key: 'esstdt',
title: '建成日期', title: '建成日期',
dataIndex: 'esstdt', dataIndex: 'esstdt',
width: '150px', width: '150px',
sorter: true, sorter: true
}, },
{ {
key: 'mway', key: 'mway',
title: '监测方式', title: '监测方式',
dataIndex: 'mway', dataIndex: 'mway',
width: '150px', width: '150px',
sorter: true, sorter: true
}, },
{ {
key: 'vlsr', key: 'vlsr',
title: '数据接入来源', title: '数据接入来源',
dataIndex: 'vlsr', dataIndex: 'vlsr',
width: '150px', width: '150px',
sorter: true, sorter: true
}, },
{ {
key: 'hydrodtin_type', key: 'hydrodtin_type',
title: '数据接入类型', title: '数据接入类型',
dataIndex: 'hydrodtinType', dataIndex: 'hydrodtinType',
width: '150px', width: '150px',
sorter: true, sorter: true
}, },
{ {
key: 'enable', key: 'enable',
title: '是否启用', title: '是否启用',
dataIndex: 'enable', dataIndex: 'enable',
width: '150px', width: '150px',
sorter: true, sorter: true
}, },
{ {
key: 'dtin', key: 'dtin',
title: '是否接入', title: '是否接入',
dataIndex: 'dtin', dataIndex: 'dtin',
width: '150px', width: '150px',
sorter: true, sorter: true
}, },
{ {
title: '操作', title: '操作',
@ -243,189 +250,196 @@ const handleSortChange = (info: { field: string; order: string | null }) => {
sortState.value = null; // sortState.value = null; //
} }
nextTick(() => { nextTick(() => {
handleSearch() handleSearch();
}); });
} };
const searchParams = computed(() => { const searchParams = computed(() => {
const params: any = { group: [],groupResultFlat: false,select:[] }; const params: any = { group: [], groupResultFlat: false, select: [] };
if (sortState.value) { if (sortState.value) {
// [{ field: 'name', dir: 'asc' }] // [{ field: 'name', dir: 'asc' }]
params.sort = [{ params.sort = [
{
field: sortState.value.field, field: sortState.value.field,
dir: sortState.value.order === 'ascend' ? 'asc' : 'desc' dir: sortState.value.order === 'ascend' ? 'asc' : 'desc'
}]; }
}else{ ];
params.sort = [{ } else {
"field": "baseStepSort", params.sort = [
"dir": "asc" {
field: 'baseStepSort',
dir: 'asc'
}, },
{ {
"field": "rvcdStepSort", field: 'rvcdStepSort',
"dir": "asc" dir: 'asc'
}, },
{ {
"field": "rstcdStepSort", field: 'rstcdStepSort',
"dir": "asc" dir: 'asc'
}] }
];
} }
return params; return params;
}); });
const handleTabChange = (key: string) => { const handleTabChange = (key: string) => {
console.log('【Tab切换】', key) console.log('【Tab切换】', key);
tabIndex.value = key tabIndex.value = key;
handleSearch() handleSearch();
} };
const filterOption = (input: string, option: any) => { const filterOption = (input: string, option: any) => {
// 使 options option { label, value } // 使 options option { label, value }
return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0 return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0;
} };
// //
const handleHydropowerStationChange = (value: string) => { const handleHydropowerStationChange = (value: string) => {
console.log('【水电站选择变化】', value) console.log('【水电站选择变化】', value);
// //
searchData.value.fhstcd = 'all' searchData.value.fhstcd = 'all';
getselectTwo() getselectTwo();
} };
// //
const getselectTwo = async () => { const getselectTwo = async () => {
let params = { let params = {
"filter": { filter: {
"logic": "and", logic: 'and',
"filters": [ filters: [
{ {
"field": "sttpCode", field: 'sttpCode',
"operator": "eq", operator: 'eq',
"dataType": "string", dataType: 'string',
"value": tabIndex.value value: tabIndex.value
}, },
searchData.value.baseId != 'all' ? { searchData.value.baseId != 'all'
"field": "baseId", ? {
"operator": "eq", field: 'baseId',
"dataType": "string", operator: 'eq',
"value": searchData.value.baseId dataType: 'string',
} : null value: searchData.value.baseId
}
: null
].filter(Boolean) ].filter(Boolean)
}, },
"select": [ select: ['stcd', 'stnm']
"stcd", };
"stnm" let res = await msstbprptGetKendoList(params);
] console.log('【获取栖息地】', res);
} stationOptions.value = [];
let res = await msstbprptGetKendoList(params)
console.log('【获取栖息地】', res)
stationOptions.value = []
if (res?.data?.data.length > 0) { if (res?.data?.data.length > 0) {
stationOptions.value = res.data.data.map((item: any) => { stationOptions.value = res.data.data.map((item: any) => {
return { return {
label: item.stnm, label: item.stnm,
value: item.stcd value: item.stcd
} };
}) });
} }
stationOptions.value.unshift({ stationOptions.value.unshift({
label: '当前全部', label: '当前全部',
value: 'all' value: 'all'
}) });
} };
const handleSearch = () => { const handleSearch = () => {
console.log('【搜索】', searchData.value) console.log('【搜索】', searchData.value);
const filters: any[] = [ const filters: any[] = [
searchData.value.baseId != 'all' ? { searchData.value.baseId != 'all'
"field": "baseId", ? {
"operator": "eq", field: 'baseId',
"value": searchData.value.baseId operator: 'eq',
} : null, value: searchData.value.baseId
searchData.value.fhstcd != 'all' ? { }
"field": "fhstcd", : null,
"operator": "eq", searchData.value.fhstcd != 'all'
"value": searchData.value.fhstcd ? {
} : null, field: 'fhstcd',
operator: 'eq',
value: searchData.value.fhstcd
}
: null,
{ {
"field": "sttpCode", field: 'sttpCode',
"operator": "eq", operator: 'eq',
"value": tabIndex.value value: tabIndex.value
}, },
searchData.value.stnm ? { searchData.value.stnm
"logic": "or", ? {
"filters": [ logic: 'or',
filters: [
{ {
"field": "stnm", field: 'stnm',
"operator": "contains", operator: 'contains',
"value": searchData.value.stnm value: searchData.value.stnm
}, },
{ {
"field": "ennm", field: 'ennm',
"operator": "contains", operator: 'contains',
"value": searchData.value.stnm value: searchData.value.stnm
} }
] ]
} : null }
].filter(Boolean) : null
].filter(Boolean);
const filter = { const filter = {
logic: 'and', logic: 'and',
filters filters
} };
// //
tableRef.value?.getList(filter) tableRef.value?.getList(filter);
} };
const handleReset = () => { const handleReset = () => {
console.log('【重置】') console.log('【重置】');
searchData.value = { searchData.value = {
baseId: 'all', baseId: 'all',
fhstcd: 'all', fhstcd: 'all',
stnm: null stnm: null
} };
handleSearch() handleSearch();
} };
// //
const customTransform = (res: any) => { const customTransform = (res: any) => {
return { return {
records: res?.data?.data || [], records: res?.data?.data || [],
total: res?.data?.total || 0 total: res?.data?.total || 0
} };
} };
// //
const handleViewDetail = (record: any, type: any) => { const handleViewDetail = (record: any, type: any) => {
if (type == 'dz') { if (type == 'dz') {
modelStore.modalVisible = true; modelStore.modalVisible = true;
modelStore.params.sttp = "ENG"; modelStore.params.sttp = 'ENG';
modelStore.title = record.ennm + " 详情信息"; modelStore.title = record.ennm + ' 详情信息';
modelStore.params.stcd = record.rstcd; modelStore.params.stcd = record.rstcd;
} else { } else {
if (tabIndex.value == 'FH') { if (tabIndex.value == 'FH') {
modelStore.modalVisible = true; modelStore.modalVisible = true;
modelStore.params.sttp = "fh_point"; modelStore.params.sttp = 'fh_point';
modelStore.title = record.stnm + " 详情信息"; modelStore.title = record.stnm + ' 详情信息';
modelStore.params.stcd = record.stcd; modelStore.params.stcd = record.stcd;
modelStore.params.show = false modelStore.params.show = false;
} else if (tabIndex.value == 'WTRV') { } else if (tabIndex.value == 'WTRV') {
modelStore.modalVisible = true; modelStore.modalVisible = true;
modelStore.params.sttp = "wt_point"; modelStore.params.sttp = 'wt_point';
modelStore.title = record.stnm + " 详情信息"; modelStore.title = record.stnm + ' 详情信息';
modelStore.params.stcd = record.stcd; modelStore.params.stcd = record.stcd;
} else if (tabIndex.value == 'ZQ') { } else if (tabIndex.value == 'ZQ') {
modelStore.modalVisible = true; modelStore.modalVisible = true;
modelStore.params.sttp = "fh_zq_point"; modelStore.params.sttp = 'fh_zq_point';
modelStore.title = record.stnm + " 详情信息"; modelStore.title = record.stnm + ' 详情信息';
modelStore.params.stcd = record.stcd; modelStore.params.stcd = record.stcd;
} else if (tabIndex.value == 'VD') { } else if (tabIndex.value == 'VD') {
modelStore.modalVisible = true; modelStore.modalVisible = true;
modelStore.params.sttp = "stinfo_ai_video_point"; modelStore.params.sttp = 'stinfo_ai_video_point';
modelStore.title = "视频监控站详情信息"; modelStore.title = '视频监控站详情信息';
modelStore.isBasicEdit = true; modelStore.isBasicEdit = true;
modelStore.params.stcd = record.stcd; modelStore.params.stcd = record.stcd;
} }
} }
} };
// ==================== ==================== // ==================== ====================
onMounted(() => { onMounted(() => {
getselectTwo() getselectTwo();
handleSearch() handleSearch();
}) });
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">