添加垂向水温
This commit is contained in:
parent
e2b77507fd
commit
fd16dd684c
@ -78,6 +78,33 @@ export function getMonitorDataWaterTempPowerStation(params: any) {
|
|||||||
params
|
params
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
// 监测数据 - 水温 - 、综合分析导出数据
|
||||||
|
export function exportMonitorDataWaterTempPowerStation(params: any) {
|
||||||
|
return request({
|
||||||
|
url: '/api/wmp-env-server/sw/alongDetail/qgc/GetKendoListCust',
|
||||||
|
method: 'post',
|
||||||
|
params,
|
||||||
|
responseType: 'blob'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 监测数据 - 水温 - 查询是否显示 等温水深
|
||||||
|
export function getMonitorDataWaterTempPowerStation2(params: any) {
|
||||||
|
return request({
|
||||||
|
url: '/api/wmp-env-server/sw/alongDetail/qgc/stcdCheck2',
|
||||||
|
method: 'get',
|
||||||
|
params
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// 监测数据 - 垂向水温
|
||||||
|
|
||||||
|
export function getMonitorDataWaterTempVertical(data: any) {
|
||||||
|
return request({
|
||||||
|
url: '/api/wmp-env-server/sw/cxDetail/GetKendoListCust',
|
||||||
|
method: 'post',
|
||||||
|
data
|
||||||
|
});
|
||||||
|
}
|
||||||
// 生态流量 - 限制范围查询 - 日
|
// 生态流量 - 限制范围查询 - 日
|
||||||
export function getEngLimitDay(data: any) {
|
export function getEngLimitDay(data: any) {
|
||||||
return request({
|
return request({
|
||||||
|
|||||||
@ -51,7 +51,9 @@ interface Props {
|
|||||||
searchParams?: Record<string, any>;
|
searchParams?: Record<string, any>;
|
||||||
defaultPageSize?: number;
|
defaultPageSize?: number;
|
||||||
isPage?: boolean;
|
isPage?: boolean;
|
||||||
|
defaultSelectedRowKeys?: string[];
|
||||||
getCheckboxProps?: (record: any) => any;
|
getCheckboxProps?: (record: any) => any;
|
||||||
|
minSelectionCount?: number; // 最小选中数量,0表示无限制
|
||||||
transformData?: (res: any) => { records: any[]; total: number };
|
transformData?: (res: any) => { records: any[]; total: number };
|
||||||
emptyPlaceholder?: string; // 新增:空值占位符
|
emptyPlaceholder?: string; // 新增:空值占位符
|
||||||
processEmptyValues?: boolean; // 新增:是否开启空值处理
|
processEmptyValues?: boolean; // 新增:是否开启空值处理
|
||||||
@ -66,7 +68,9 @@ const props = withDefaults(defineProps<Props>(), {
|
|||||||
data: () => [],
|
data: () => [],
|
||||||
searchParams: () => ({}),
|
searchParams: () => ({}),
|
||||||
defaultPageSize: 20,
|
defaultPageSize: 20,
|
||||||
|
defaultSelectedRowKeys: () => [],
|
||||||
getCheckboxProps: undefined,
|
getCheckboxProps: undefined,
|
||||||
|
minSelectionCount: 0,
|
||||||
transformData: undefined,
|
transformData: undefined,
|
||||||
scrollY: undefined,
|
scrollY: undefined,
|
||||||
emptyPlaceholder: '-',
|
emptyPlaceholder: '-',
|
||||||
@ -90,7 +94,7 @@ const tableData = ref<any[]>([]);
|
|||||||
const total = ref(0);
|
const total = ref(0);
|
||||||
const page = ref(1);
|
const page = ref(1);
|
||||||
const size = ref(props.defaultPageSize);
|
const size = ref(props.defaultPageSize);
|
||||||
const selectedRowKeys = ref<string[]>([]);
|
const selectedRowKeys = ref<string[]>([...props.defaultSelectedRowKeys]);
|
||||||
const selectedRows = ref<any[]>([]);
|
const selectedRows = ref<any[]>([]);
|
||||||
const lastFilter = ref<Record<string, any> | undefined>(undefined);
|
const lastFilter = ref<Record<string, any> | undefined>(undefined);
|
||||||
const tableContainerRef = ref<any>(null);
|
const tableContainerRef = ref<any>(null);
|
||||||
@ -124,6 +128,16 @@ const scrollConfig = computed(() => {
|
|||||||
const rowSelection = computed(() => ({
|
const rowSelection = computed(() => ({
|
||||||
selectedRowKeys: selectedRowKeys.value,
|
selectedRowKeys: selectedRowKeys.value,
|
||||||
onChange: (keys: string[], rows: any[]) => {
|
onChange: (keys: string[], rows: any[]) => {
|
||||||
|
// 至少保留 minSelectionCount 条勾选,如果取消后低于最小值则恢复默认第一条
|
||||||
|
if (
|
||||||
|
props.minSelectionCount > 0 &&
|
||||||
|
keys.length < props.minSelectionCount &&
|
||||||
|
props.data.length > 0
|
||||||
|
) {
|
||||||
|
const firstKey = props.data[0][props.rowKey];
|
||||||
|
keys = [firstKey];
|
||||||
|
rows = [props.data[0]];
|
||||||
|
}
|
||||||
selectedRowKeys.value = keys;
|
selectedRowKeys.value = keys;
|
||||||
selectedRows.value = rows;
|
selectedRows.value = rows;
|
||||||
emit('selection-change', keys, rows);
|
emit('selection-change', keys, rows);
|
||||||
@ -248,6 +262,32 @@ const observer = new ResizeObserver(() => {
|
|||||||
tableScrollY.value = calcTableScrollY(tableContainerRef.value);
|
tableScrollY.value = calcTableScrollY(tableContainerRef.value);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.data,
|
||||||
|
newData => {
|
||||||
|
if (newData && newData.length > 0) {
|
||||||
|
// If defaultSelectedRowKeys is provided, reset selection
|
||||||
|
if (
|
||||||
|
props.defaultSelectedRowKeys &&
|
||||||
|
props.defaultSelectedRowKeys.length > 0
|
||||||
|
) {
|
||||||
|
selectedRowKeys.value = [...props.defaultSelectedRowKeys];
|
||||||
|
selectedRows.value = newData.filter(row =>
|
||||||
|
props.defaultSelectedRowKeys.includes(row[props.rowKey])
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// Otherwise clear selection when data changes
|
||||||
|
selectedRowKeys.value = [];
|
||||||
|
selectedRows.value = [];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
selectedRowKeys.value = [];
|
||||||
|
selectedRows.value = [];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ deep: true }
|
||||||
|
);
|
||||||
|
|
||||||
// --- 空值处理工具函数 ---
|
// --- 空值处理工具函数 ---
|
||||||
const processData = (records: any[]) => {
|
const processData = (records: any[]) => {
|
||||||
if (!props.processEmptyValues || !Array.isArray(records)) return records;
|
if (!props.processEmptyValues || !Array.isArray(records)) return records;
|
||||||
|
|||||||
@ -1,98 +1,42 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="monitor-info">
|
<div class="monitor-info">
|
||||||
<!-- 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">
|
||||||
<a-range-picker
|
<a-range-picker
|
||||||
v-model:value="dateRange"
|
v-model:value="dateRange"
|
||||||
:format="dateFormat"
|
format="YYYY-MM-DD HH:mm"
|
||||||
:show-time="showTime ? showTimeConfig : false"
|
:show-time="showTimeConfig"
|
||||||
:allowClear="false"
|
:allowClear="false"
|
||||||
:presets="DateSetting.RangeButton.month1"
|
:presets="DateSetting.RangeButton.month1"
|
||||||
:disabled-date="disabledDate"
|
:disabled-date="disabledDate"
|
||||||
>
|
/>
|
||||||
</a-range-picker>
|
|
||||||
<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
|
<a-spin :spinning="isLoading" tip="加载中...">
|
||||||
v-model:value="selectedColumns"
|
<div class="tab-content">
|
||||||
name="checkboxgroup"
|
<div class="content-body">
|
||||||
:options="filteredCheckBoxOptions"
|
<div class="chart-wrapper">
|
||||||
class="checkbox-group"
|
<div ref="chartRef" class="chart-container"></div>
|
||||||
@change="handleCheckboxChange"
|
<a-empty
|
||||||
|
v-if="!chartData || chartData.length === 0"
|
||||||
|
description="暂无数据"
|
||||||
|
class="chart-empty"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 内容区域 -->
|
|
||||||
<a-spin :spinning="isLoading" tip="加载中...">
|
|
||||||
<div v-if="activeTabKey" class="tab-content">
|
|
||||||
<div class="content-body">
|
|
||||||
<!-- 电站运行过程线 -->
|
|
||||||
<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"
|
:scrollY="480"
|
||||||
:scrollY="activeTabKey === 'swjc.tabs.jcsj' ? 360 : 480"
|
|
||||||
:scrollX="tableScrollX"
|
:scrollX="tableScrollX"
|
||||||
:columns="currentColumns"
|
:columns="tableColumns"
|
||||||
:data="tableData"
|
:data="tableData"
|
||||||
>
|
/>
|
||||||
<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>
|
</div>
|
||||||
@ -101,128 +45,384 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref, watch, computed, onMounted } from 'vue';
|
import { ref, watch, onBeforeUnmount, onMounted, nextTick } from 'vue';
|
||||||
import dayjs, { Dayjs } from 'dayjs';
|
import dayjs, { Dayjs } from 'dayjs';
|
||||||
import { Tooltip } from 'ant-design-vue';
|
import * as echarts from 'echarts';
|
||||||
import {
|
import { getMonitorData } from '@/api/mapModal';
|
||||||
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();
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
isActive: {
|
isActive: { type: Boolean, default: false }
|
||||||
type: Boolean,
|
|
||||||
default: false
|
|
||||||
},
|
|
||||||
code: {
|
|
||||||
type: String,
|
|
||||||
default: ''
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const hasLoaded = ref(false);
|
const hasLoaded = ref(false);
|
||||||
const isLoading = ref(false);
|
const isLoading = ref(false);
|
||||||
const tableRef = ref<any>(null);
|
|
||||||
const tableData = ref<any[]>([]);
|
const tableData = ref<any[]>([]);
|
||||||
const chartData = ref<any[]>([]);
|
const chartData = ref<any[]>([]);
|
||||||
|
const chartRef = ref<HTMLElement>();
|
||||||
|
let chartInstance: echarts.ECharts | null = null;
|
||||||
|
|
||||||
// ==================== Tab 配置 ====================
|
const tabsList = [{ name: '电站运行过程线', key: 'dzxq.tabs.jcsj' }];
|
||||||
const tabsConfig: Record<string, any[]> = {
|
const activeTabKey = ref('dzxq.tabs.jcsj');
|
||||||
'dzxq.tabs.jcsj': [{ name: '电站运行过程线', key: 'dzxq.tabs.jcsj' }],
|
|
||||||
'swjc.tabs.jcsj': [{ name: '水温', key: 'swjc.tabs.jcsj' }]
|
|
||||||
};
|
|
||||||
|
|
||||||
const tabsList = computed(() => {
|
|
||||||
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 }> = {
|
|
||||||
'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(() => {
|
|
||||||
return timeConfig[activeTabKey.value] || timeConfig['dzxq.tabs.jcsj'];
|
|
||||||
});
|
|
||||||
|
|
||||||
const showTime = computed(() => currentTimeConfig.value.showTime);
|
|
||||||
const dateFormat = computed(() => currentTimeConfig.value.format);
|
|
||||||
|
|
||||||
|
// 时间选择器配置
|
||||||
const showTimeConfig = {
|
const showTimeConfig = {
|
||||||
format: 'HH:mm',
|
format: 'HH:mm',
|
||||||
hourStep: 1,
|
hourStep: 1,
|
||||||
minuteStep: 5,
|
minuteStep: 5,
|
||||||
secondStep: 60
|
secondStep: 60
|
||||||
};
|
};
|
||||||
|
const disabledDate = (current: Dayjs) =>
|
||||||
|
current && current.isAfter(dayjs(), 'day');
|
||||||
|
|
||||||
const disabledDate = (current: Dayjs) => {
|
const initDateRange = (): [Dayjs, Dayjs] => {
|
||||||
return current && current.isAfter(dayjs(), 'day');
|
return [dayjs().subtract(1, 'month'), 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: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 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);
|
||||||
};
|
};
|
||||||
|
|
||||||
// ==================== API 配置 ====================
|
// 更新图表
|
||||||
const apiConfig: Record<string, Function> = {
|
const updateChart = (data: any[]) => {
|
||||||
'dzxq.tabs.jcsj': getMonitorData,
|
if (!chartInstance) return;
|
||||||
'swjc.tabs.jcsj': getMonitorDataWaterTemp
|
if (!data || data.length === 0) {
|
||||||
|
chartInstance.clear();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const sorted = [...data].sort(
|
||||||
|
(a, b) => new Date(a.tm).getTime() - new Date(b.tm).getTime()
|
||||||
|
);
|
||||||
|
|
||||||
|
const xAxisData = sorted.map(item => {
|
||||||
|
const hm = dayjs(item.tm).format('HH:mm');
|
||||||
|
if (hm === '00:00') {
|
||||||
|
const date = dayjs(item.tm).format('MM-DD');
|
||||||
|
return `${hm}\n${date}`;
|
||||||
|
}
|
||||||
|
return hm;
|
||||||
|
});
|
||||||
|
|
||||||
|
const rzData = sorted.map(item => item.rz);
|
||||||
|
const dzData = sorted.map(item => item.dz);
|
||||||
|
const qiData = sorted.map(item => item.qi);
|
||||||
|
const qoData = sorted.map(item => item.qo);
|
||||||
|
const qecData = sorted.map(item => item.qec);
|
||||||
|
const qecLimitData = sorted.map(item => item.qecLimit);
|
||||||
|
|
||||||
|
const waterValues = [...rzData, ...dzData].filter(
|
||||||
|
v => v !== null && v !== undefined
|
||||||
|
);
|
||||||
|
const waterMin = waterValues.length > 0 ? Math.min(...waterValues) : 0;
|
||||||
|
const waterMax = waterValues.length > 0 ? Math.max(...waterValues) : 10;
|
||||||
|
|
||||||
|
const flowValues = [...qiData, ...qoData, ...qecData, ...qecLimitData].filter(
|
||||||
|
v => v !== null && v !== undefined
|
||||||
|
);
|
||||||
|
const flowMin = flowValues.length > 0 ? Math.min(...flowValues) : 0;
|
||||||
|
const flowMax = flowValues.length > 0 ? Math.max(...flowValues) : 10;
|
||||||
|
|
||||||
|
const formatRules: Record<
|
||||||
|
string,
|
||||||
|
{ format: (v: number) => string; unit: string }
|
||||||
|
> = {
|
||||||
|
坝上水位: { format: v => v.toFixed(2), unit: '(m)' },
|
||||||
|
坝下水位: { format: v => 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)' }
|
||||||
};
|
};
|
||||||
|
|
||||||
// 构建 filter 参数
|
const option = {
|
||||||
const buildFilterParams = () => {
|
tooltip: {
|
||||||
return {
|
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: '水位(m)',
|
||||||
|
type: 'value',
|
||||||
|
position: 'left',
|
||||||
|
axisLine: { lineStyle: { color: '#000000' } },
|
||||||
|
alignTicks: true,
|
||||||
|
scale: true,
|
||||||
|
splitNumber: 9
|
||||||
|
// boundaryGap: ['10%', 0]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '流量(m³/s)',
|
||||||
|
type: 'value',
|
||||||
|
position: 'right',
|
||||||
|
axisLine: { lineStyle: { color: '#000000' } },
|
||||||
|
splitNumber: 9
|
||||||
|
// boundaryGap: ['10%', 0]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
name: '坝上水位',
|
||||||
|
type: 'line',
|
||||||
|
yAxisIndex: 0,
|
||||||
|
data: rzData,
|
||||||
|
smooth: true,
|
||||||
|
symbol: 'circle',
|
||||||
|
symbolSize: 4,
|
||||||
|
lineStyle: { color: '#56C2E3', width: 2 },
|
||||||
|
itemStyle: { color: '#56C2E3' },
|
||||||
|
markPoint: {
|
||||||
|
data: [
|
||||||
|
{ type: 'max', name: 'Max' },
|
||||||
|
{ type: 'min', name: 'Min' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '坝下水位',
|
||||||
|
type: 'line',
|
||||||
|
yAxisIndex: 0,
|
||||||
|
data: dzData,
|
||||||
|
smooth: true,
|
||||||
|
symbol: 'circle',
|
||||||
|
symbolSize: 4,
|
||||||
|
lineStyle: { color: '#7399C6', width: 2 },
|
||||||
|
itemStyle: { color: '#7399C6' },
|
||||||
|
markPoint: {
|
||||||
|
data: [
|
||||||
|
{ type: 'max', name: 'Max' },
|
||||||
|
{ type: 'min', name: 'Min' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '入库流量',
|
||||||
|
type: 'line',
|
||||||
|
yAxisIndex: 1,
|
||||||
|
data: qiData,
|
||||||
|
smooth: true,
|
||||||
|
symbol: 'circle',
|
||||||
|
symbolSize: 4,
|
||||||
|
lineStyle: { color: '#4B79AB', width: 2 },
|
||||||
|
itemStyle: { color: '#4B79AB' },
|
||||||
|
markPoint: {
|
||||||
|
data: [
|
||||||
|
{ type: 'max', name: 'Max' },
|
||||||
|
{ type: 'min', name: 'Min' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '出库流量',
|
||||||
|
type: 'line',
|
||||||
|
yAxisIndex: 1,
|
||||||
|
data: qoData,
|
||||||
|
smooth: true,
|
||||||
|
symbol: 'circle',
|
||||||
|
symbolSize: 4,
|
||||||
|
lineStyle: { color: '#78C300', width: 2 },
|
||||||
|
itemStyle: { color: '#78C300' },
|
||||||
|
markPoint: {
|
||||||
|
data: [
|
||||||
|
{ type: 'max', name: 'Max' },
|
||||||
|
{ type: 'min', name: 'Min' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '生态流量',
|
||||||
|
type: 'line',
|
||||||
|
yAxisIndex: 1,
|
||||||
|
data: qecData,
|
||||||
|
smooth: true,
|
||||||
|
symbol: 'circle',
|
||||||
|
symbolSize: 4,
|
||||||
|
lineStyle: { color: '#00A050', width: 2 },
|
||||||
|
itemStyle: { color: '#00A050' },
|
||||||
|
markPoint: {
|
||||||
|
data: [
|
||||||
|
{ type: 'max', name: 'Max' },
|
||||||
|
{ type: 'min', name: 'Min' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '生态流量限值',
|
||||||
|
type: 'line',
|
||||||
|
yAxisIndex: 1,
|
||||||
|
data: qecLimitData,
|
||||||
|
smooth: true,
|
||||||
|
symbol: 'circle',
|
||||||
|
symbolSize: 4,
|
||||||
|
lineStyle: { color: '#F7A737', width: 2 },
|
||||||
|
itemStyle: { color: '#F7A737' },
|
||||||
|
markPoint: {
|
||||||
|
data: [
|
||||||
|
{ type: 'max', name: 'Max' },
|
||||||
|
{ type: 'min', name: 'Min' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
dataZoom: [
|
||||||
|
{
|
||||||
|
type: 'inside',
|
||||||
|
xAxisIndex: [0],
|
||||||
|
throttle: 50,
|
||||||
|
start: 0,
|
||||||
|
end: 100
|
||||||
|
}
|
||||||
|
],
|
||||||
|
toolbox: {
|
||||||
|
show: true,
|
||||||
|
feature: {
|
||||||
|
saveAsImage: { title: '保存为图片', type: 'png', pixelRatio: 2 }
|
||||||
|
},
|
||||||
|
right: 20,
|
||||||
|
top: 10
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
chartInstance.setOption(option, true);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 数据请求
|
||||||
|
const fetchData = async () => {
|
||||||
|
if (!dateRange.value) return;
|
||||||
|
isLoading.value = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const filterParams = {
|
||||||
filter: {
|
filter: {
|
||||||
logic: 'and',
|
logic: 'and',
|
||||||
filters: [
|
filters: [
|
||||||
@ -236,95 +436,40 @@ const buildFilterParams = () => {
|
|||||||
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')
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
sort: [
|
sort: [{ field: 'tm', dir: 'asc' }]
|
||||||
{
|
|
||||||
field: 'tm',
|
|
||||||
dir: 'asc'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// ==================== 数据请求 ====================
|
const res = await getMonitorData(filterParams);
|
||||||
const fetchData = async () => {
|
|
||||||
if (!dateRange.value) return;
|
|
||||||
|
|
||||||
isLoading.value = true;
|
|
||||||
|
|
||||||
try {
|
|
||||||
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 || [];
|
||||||
|
|
||||||
// 数据按时间倒序显示(最新的在前)
|
|
||||||
tableData.value = [...rawData].reverse();
|
tableData.value = [...rawData].reverse();
|
||||||
// 图表数据保持升序
|
|
||||||
chartData.value = rawData;
|
chartData.value = rawData;
|
||||||
}
|
nextTick(() => updateChart(rawData));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('获取数据失败:', error);
|
console.error('获取数据失败:', error);
|
||||||
tableData.value = [];
|
tableData.value = [];
|
||||||
chartData.value = [];
|
chartData.value = [];
|
||||||
|
if (chartInstance) chartInstance.clear();
|
||||||
} finally {
|
} finally {
|
||||||
isLoading.value = false;
|
isLoading.value = false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSearch = () => {
|
const handleSearch = () => fetchData();
|
||||||
fetchData();
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleExport = () => {
|
|
||||||
// 导出数据
|
|
||||||
};
|
|
||||||
|
|
||||||
// Tab 切换时重置日期范围
|
|
||||||
const handleTabChange = (key: string) => {
|
|
||||||
const days = defaultDateRangeConfig[key]?.days || 30;
|
|
||||||
const endDate = dayjs();
|
|
||||||
let startDate = dayjs().subtract(days, 'day');
|
|
||||||
|
|
||||||
if (key === 'swjc.tabs.jcsj') {
|
|
||||||
startDate = startDate.startOf('day');
|
|
||||||
}
|
|
||||||
|
|
||||||
dateRange.value = [startDate, endDate];
|
|
||||||
fetchData();
|
|
||||||
};
|
|
||||||
|
|
||||||
// ==================== 初始化 ====================
|
|
||||||
const initData = () => {
|
const initData = () => {
|
||||||
if (hasLoaded.value) return;
|
if (hasLoaded.value) return;
|
||||||
|
nextTick(() => initChart());
|
||||||
fetchData();
|
fetchData();
|
||||||
hasLoaded.value = true;
|
hasLoaded.value = true;
|
||||||
};
|
};
|
||||||
@ -332,9 +477,7 @@ const initData = () => {
|
|||||||
watch(
|
watch(
|
||||||
() => props.isActive,
|
() => props.isActive,
|
||||||
active => {
|
active => {
|
||||||
if (active && !hasLoaded.value) {
|
if (active && !hasLoaded.value) initData();
|
||||||
initData();
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{ immediate: true }
|
{ immediate: true }
|
||||||
);
|
);
|
||||||
@ -342,20 +485,48 @@ watch(
|
|||||||
watch(
|
watch(
|
||||||
() => modelStore.params.stcd,
|
() => modelStore.params.stcd,
|
||||||
(newStcd, oldStcd) => {
|
(newStcd, oldStcd) => {
|
||||||
if (newStcd && newStcd !== oldStcd) {
|
if (newStcd && newStcd !== oldStcd) fetchData();
|
||||||
console.log('【MonitorInfo】stcd 变化,重新加载数据:', newStcd);
|
}
|
||||||
fetchData();
|
);
|
||||||
|
|
||||||
|
let resizeObserver: ResizeObserver | null = null;
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.isActive,
|
||||||
|
active => {
|
||||||
|
if (active && chartRef.value) {
|
||||||
|
setTimeout(() => {
|
||||||
|
if (chartInstance) chartInstance.resize();
|
||||||
|
}, 200);
|
||||||
|
if (!resizeObserver) {
|
||||||
|
resizeObserver = new ResizeObserver(() => {
|
||||||
|
if (chartInstance) chartInstance.resize();
|
||||||
|
});
|
||||||
|
resizeObserver.observe(chartRef.value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const handleResize = () => {
|
||||||
|
if (chartInstance) chartInstance.resize();
|
||||||
|
};
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
window.addEventListener('resize', handleResize);
|
window.addEventListener('resize', handleResize);
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleResize = () => {
|
onBeforeUnmount(() => {
|
||||||
// 图表组件自己处理 resize
|
if (chartInstance) {
|
||||||
};
|
chartInstance.dispose();
|
||||||
|
chartInstance = null;
|
||||||
|
}
|
||||||
|
window.removeEventListener('resize', handleResize);
|
||||||
|
if (resizeObserver) {
|
||||||
|
resizeObserver.disconnect();
|
||||||
|
resizeObserver = null;
|
||||||
|
}
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@ -363,19 +534,11 @@ const handleResize = () => {
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
min-height: 600px;
|
min-height: 600px;
|
||||||
position: relative;
|
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;
|
||||||
@ -387,12 +550,6 @@ const handleResize = () => {
|
|||||||
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%;
|
||||||
@ -401,12 +558,26 @@ const handleResize = () => {
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
height: 600px;
|
height: 600px;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
:deep(.chart-container) {
|
.chart-wrapper {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
position: relative;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
padding-top: 10px;
|
padding-top: 10px;
|
||||||
|
|
||||||
|
.chart-container {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-empty {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
}
|
}
|
||||||
|
|
||||||
.table-wrapper {
|
.table-wrapper {
|
||||||
@ -423,7 +594,4 @@ const handleResize = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.summary-row {
|
|
||||||
background-color: #fafafa;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@ -0,0 +1,628 @@
|
|||||||
|
<template>
|
||||||
|
<div class="vertical-water-temperature">
|
||||||
|
<a-tabs v-model:activeKey="activeTabKey">
|
||||||
|
<a-tab-pane v-for="tab in tabsList" :key="tab.key" :tab="tab.name" />
|
||||||
|
<template #rightExtra>
|
||||||
|
<div class="search-bar">
|
||||||
|
<a-select
|
||||||
|
v-model:value="timeGranularity"
|
||||||
|
style="width: 100px; margin-right: 8px"
|
||||||
|
:options="granularityOptions"
|
||||||
|
@change="handleGranularityChange"
|
||||||
|
/>
|
||||||
|
<a-range-picker
|
||||||
|
v-model:value="dateRange"
|
||||||
|
:format="dateFormat"
|
||||||
|
:show-time="showTime ? showTimeConfig : false"
|
||||||
|
:allowClear="false"
|
||||||
|
:presets="DateSetting.RangeButton.month1"
|
||||||
|
:disabled-date="disabledDate"
|
||||||
|
/>
|
||||||
|
<a-button type="primary" class="search-btn" @click="handleSearch">
|
||||||
|
查询
|
||||||
|
</a-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</a-tabs>
|
||||||
|
|
||||||
|
<!-- 垂向水温列勾选 -->
|
||||||
|
<div class="tab-checkbox-cxsw">
|
||||||
|
<div class="checkbox-title">水深:</div>
|
||||||
|
<div class="checkbox-group">
|
||||||
|
<a-checkbox
|
||||||
|
:checked="isAllSelected"
|
||||||
|
:indeterminate="isIndeterminate"
|
||||||
|
@change="handleSelectAllToggle"
|
||||||
|
class="select-all-checkbox"
|
||||||
|
>
|
||||||
|
全选
|
||||||
|
</a-checkbox>
|
||||||
|
<a-checkbox-group
|
||||||
|
v-model:value="selectedColumns"
|
||||||
|
:options="checkboxOptions"
|
||||||
|
@change="handleCheckboxChange"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<a-spin :spinning="isLoading" tip="加载中...">
|
||||||
|
<div class="tab-content">
|
||||||
|
<div class="content-body">
|
||||||
|
<div class="chart-wrapper">
|
||||||
|
<div ref="chartRef" class="chart-container"></div>
|
||||||
|
<a-empty
|
||||||
|
v-if="!chartData || chartData.length === 0"
|
||||||
|
description="暂无数据"
|
||||||
|
class="chart-empty"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="table-wrapper">
|
||||||
|
<BasicTable
|
||||||
|
ref="tableRef"
|
||||||
|
row-key="dt"
|
||||||
|
:scrollY="480"
|
||||||
|
:scrollX="tableScrollX"
|
||||||
|
:columns="tableColumns"
|
||||||
|
:data="tableData"
|
||||||
|
:enable-row-selection="true"
|
||||||
|
:min-selection-count="1"
|
||||||
|
:default-selected-row-keys="defaultSelectedRowKeys"
|
||||||
|
@selection-change="handleCxswSelectionChange"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a-spin>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import {
|
||||||
|
ref,
|
||||||
|
watch,
|
||||||
|
computed,
|
||||||
|
onBeforeUnmount,
|
||||||
|
onMounted,
|
||||||
|
nextTick
|
||||||
|
} from 'vue';
|
||||||
|
import dayjs, { Dayjs } from 'dayjs';
|
||||||
|
import * as echarts from 'echarts';
|
||||||
|
import { getMonitorDataWaterTempVertical } from '@/api/mapModal';
|
||||||
|
import { useModelStore } from '@/store/modules/model';
|
||||||
|
import BasicTable from '@/components/BasicTable/index.vue';
|
||||||
|
import { DateSetting } from '@/utils/enumeration';
|
||||||
|
|
||||||
|
const modelStore = useModelStore();
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
isActive: { type: Boolean, default: false }
|
||||||
|
});
|
||||||
|
|
||||||
|
const hasLoaded = ref(false);
|
||||||
|
const isLoading = ref(false);
|
||||||
|
const tableData = ref<any[]>([]);
|
||||||
|
const chartData = ref<any[]>([]);
|
||||||
|
const chartRef = ref<HTMLElement>();
|
||||||
|
let chartInstance: echarts.ECharts | null = null;
|
||||||
|
|
||||||
|
const tabsList = [{ name: '垂向水温', key: 'cxsw.tabs.jcsj' }];
|
||||||
|
const activeTabKey = ref('cxsw.tabs.jcsj');
|
||||||
|
|
||||||
|
// 时间粒度
|
||||||
|
const timeGranularity = ref<'DAY' | 'HOUR'>('HOUR');
|
||||||
|
const granularityOptions = [
|
||||||
|
{ label: '日', value: 'DAY' },
|
||||||
|
{ label: '小时', value: 'HOUR' }
|
||||||
|
];
|
||||||
|
|
||||||
|
const showTime = computed(() => timeGranularity.value === 'HOUR');
|
||||||
|
const dateFormat = computed(() =>
|
||||||
|
timeGranularity.value === 'HOUR' ? 'YYYY-MM-DD HH' : 'YYYY-MM-DD'
|
||||||
|
);
|
||||||
|
|
||||||
|
const showTimeConfig = {
|
||||||
|
format: 'HH:mm',
|
||||||
|
hourStep: 1,
|
||||||
|
minuteStep: 5,
|
||||||
|
secondStep: 60
|
||||||
|
};
|
||||||
|
|
||||||
|
const disabledDate = (current: Dayjs) =>
|
||||||
|
current && current.isAfter(dayjs(), 'day');
|
||||||
|
|
||||||
|
const initDateRange = (): [Dayjs, Dayjs] => {
|
||||||
|
const days = 7;
|
||||||
|
const startDate = dayjs().subtract(days, 'day').startOf('day');
|
||||||
|
return [startDate, dayjs()];
|
||||||
|
};
|
||||||
|
const dateRange = ref<[Dayjs, Dayjs] | undefined>(initDateRange());
|
||||||
|
|
||||||
|
const handleGranularityChange = () => fetchData();
|
||||||
|
|
||||||
|
// ==================== 勾选配置 ====================
|
||||||
|
const columns = ref<any[]>([]);
|
||||||
|
const selectedColumns = ref<string[]>([]);
|
||||||
|
const checkboxOptions = ref<any[]>([]);
|
||||||
|
|
||||||
|
const handleCheckboxChange = (values: string[]) => {
|
||||||
|
selectedColumns.value = values;
|
||||||
|
};
|
||||||
|
|
||||||
|
const isAllSelected = computed(() => {
|
||||||
|
return (
|
||||||
|
checkboxOptions.value.length > 0 &&
|
||||||
|
selectedColumns.value.length === checkboxOptions.value.length
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
const isIndeterminate = computed(() => {
|
||||||
|
const total = checkboxOptions.value.length;
|
||||||
|
const sel = selectedColumns.value.length;
|
||||||
|
return sel > 0 && sel < total;
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleSelectAllToggle = (e: any) => {
|
||||||
|
if (e.target.checked) {
|
||||||
|
selectedColumns.value = checkboxOptions.value.map((opt: any) => opt.value);
|
||||||
|
} else {
|
||||||
|
selectedColumns.value = [];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// ==================== 表格列 ====================
|
||||||
|
const tableColumns = computed(() => {
|
||||||
|
const fixedCols = [
|
||||||
|
{
|
||||||
|
title: '测站名称',
|
||||||
|
dataIndex: 'stnm',
|
||||||
|
width: 150,
|
||||||
|
fixed: 'left'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '时间',
|
||||||
|
dataIndex: 'dt',
|
||||||
|
width: 140,
|
||||||
|
fixed: 'left',
|
||||||
|
customRender: ({ text }: any) =>
|
||||||
|
text
|
||||||
|
? dayjs(text).format(
|
||||||
|
timeGranularity.value === 'HOUR'
|
||||||
|
? 'YYYY-MM-DD HH:mm'
|
||||||
|
: 'YYYY-MM-DD'
|
||||||
|
)
|
||||||
|
: '-'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const dataCols = columns.value
|
||||||
|
.filter((col: any) => selectedColumns.value.includes(col.dataIndex))
|
||||||
|
.map((col: any) => ({
|
||||||
|
title: col.title,
|
||||||
|
dataIndex: col.dataIndex,
|
||||||
|
width: 120,
|
||||||
|
customRender: ({ record }: any) => {
|
||||||
|
const val = record.dataList?.[col.dataIndex];
|
||||||
|
return val !== undefined && val !== null ? val : '-';
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
return [...fixedCols, ...dataCols];
|
||||||
|
});
|
||||||
|
|
||||||
|
const tableScrollX = computed(() => {
|
||||||
|
const cols = tableColumns.value;
|
||||||
|
const total = cols.reduce((s, c) => s + (c.width || 100), 0);
|
||||||
|
return total > 600 ? total : undefined;
|
||||||
|
});
|
||||||
|
|
||||||
|
// ==================== 图表 ====================
|
||||||
|
const COLORS = [
|
||||||
|
'#5470c6',
|
||||||
|
'#91cc75',
|
||||||
|
'#fac858',
|
||||||
|
'#ee6666',
|
||||||
|
'#73c0de',
|
||||||
|
'#3ba272',
|
||||||
|
'#fc8452',
|
||||||
|
'#9a60b4',
|
||||||
|
'#ea7ccc',
|
||||||
|
'#5470c6',
|
||||||
|
'#73c0de',
|
||||||
|
'#fac858',
|
||||||
|
'#91cc75',
|
||||||
|
'#ee6666',
|
||||||
|
'#3ba272',
|
||||||
|
'#fc8452',
|
||||||
|
'#9a60b4',
|
||||||
|
'#ea7ccc',
|
||||||
|
'#5470c6',
|
||||||
|
'#73c0de'
|
||||||
|
];
|
||||||
|
|
||||||
|
const updateChart = (selectedRows: any[]) => {
|
||||||
|
if (!chartInstance) return;
|
||||||
|
if (!selectedRows || selectedRows.length === 0) {
|
||||||
|
chartInstance.clear();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(selectedRows);
|
||||||
|
// 为每个选中的行创建一个 series
|
||||||
|
const series = selectedRows.map((row, index) => {
|
||||||
|
const data = Object.entries(row.dataList || {})
|
||||||
|
.map(([depth, temp]) => [parseFloat(temp as string), parseFloat(depth)])
|
||||||
|
.sort((a, b) => a[1] - b[1]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
name: dayjs(row.dt).format(
|
||||||
|
timeGranularity.value === 'HOUR' ? 'YYYY-MM-DD HH:mm' : 'YYYY-MM-DD'
|
||||||
|
),
|
||||||
|
type: 'line',
|
||||||
|
data,
|
||||||
|
smooth: false,
|
||||||
|
symbol: 'circle',
|
||||||
|
symbolSize: 4,
|
||||||
|
lineStyle: { width: 2, color: COLORS[index % COLORS.length] },
|
||||||
|
itemStyle: { color: COLORS[index % COLORS.length] }
|
||||||
|
};
|
||||||
|
});
|
||||||
|
console.log(series);
|
||||||
|
|
||||||
|
const option = {
|
||||||
|
title: {
|
||||||
|
text: '垂向水温分布',
|
||||||
|
left: 'center'
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'axis',
|
||||||
|
axisPointer: {
|
||||||
|
type: 'cross',
|
||||||
|
label: {
|
||||||
|
formatter: (value: any) => {
|
||||||
|
if (value.axisDimension == 'x') {
|
||||||
|
return value.value.toFixed(1) + '℃';
|
||||||
|
} else if (value.axisDimension == 'y') {
|
||||||
|
return value.value.toFixed(2) + 'm';
|
||||||
|
}
|
||||||
|
|
||||||
|
return value.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
formatter: (params: any) => {
|
||||||
|
let html = params[0].seriesName + '\n';
|
||||||
|
params.forEach((item: any) => {
|
||||||
|
html += `<div style="display:flex;"><div style="width:40px;">${item.data[1]}m :</div> <div>${item.data[0]}℃</div></div>`;
|
||||||
|
});
|
||||||
|
return html;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
type: 'scroll',
|
||||||
|
top: 40,
|
||||||
|
data: series.map(s => s.name),
|
||||||
|
textStyle: { fontSize: 12 }
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
left: '8%',
|
||||||
|
right: 70,
|
||||||
|
top: '15%',
|
||||||
|
bottom: '8%',
|
||||||
|
containLabel: false
|
||||||
|
},
|
||||||
|
xAxis: {
|
||||||
|
position: 'top',
|
||||||
|
name: '水温 (℃)',
|
||||||
|
nameLocation: 'end',
|
||||||
|
type: 'value',
|
||||||
|
scale: true,
|
||||||
|
boundaryGap: ['10%', '10%'],
|
||||||
|
axisLabel: {
|
||||||
|
fontSize: 12,
|
||||||
|
color: '#000000',
|
||||||
|
formatter: function (value) {
|
||||||
|
return value + '℃';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
splitLine: { show: true }
|
||||||
|
},
|
||||||
|
yAxis: {
|
||||||
|
name: '水深 (m)',
|
||||||
|
type: 'value',
|
||||||
|
inverse: true,
|
||||||
|
nameLocation: 'end',
|
||||||
|
min: 0,
|
||||||
|
axisLabel: {
|
||||||
|
fontSize: 12,
|
||||||
|
color: '#000000',
|
||||||
|
formatter: function (value) {
|
||||||
|
return value + 'm';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
splitLine: { show: true }
|
||||||
|
},
|
||||||
|
series,
|
||||||
|
toolbox: {
|
||||||
|
show: true,
|
||||||
|
feature: {
|
||||||
|
saveAsImage: { title: '保存为图片', type: 'png', pixelRatio: 2 }
|
||||||
|
},
|
||||||
|
right: 20,
|
||||||
|
top: 10
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
chartInstance.setOption(option, true);
|
||||||
|
};
|
||||||
|
const handleCxswSelectionChange = (selectedRows: any[]) => {
|
||||||
|
selectedRowsForChart.value = selectedRows;
|
||||||
|
let arr = [];
|
||||||
|
tableData.value.map(row => {
|
||||||
|
if (selectedRows.includes(row.dt)) {
|
||||||
|
arr.push(row);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
updateChart(arr);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 图表选中的行数据
|
||||||
|
const selectedRowsForChart = ref<any[]>([]);
|
||||||
|
|
||||||
|
const defaultSelectedRowKeys = computed(() => {
|
||||||
|
if (tableData.value && tableData.value.length > 0) {
|
||||||
|
return [tableData.value[0].dt];
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
});
|
||||||
|
|
||||||
|
// 初始化时默认选中第一条
|
||||||
|
watch(
|
||||||
|
() => tableData.value,
|
||||||
|
newData => {
|
||||||
|
if (
|
||||||
|
newData &&
|
||||||
|
newData.length > 0 &&
|
||||||
|
selectedRowsForChart.value.length === 0
|
||||||
|
) {
|
||||||
|
selectedRowsForChart.value = [newData[0]];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ immediate: true }
|
||||||
|
);
|
||||||
|
// ==================== 数据请求 ====================
|
||||||
|
const fetchData = async () => {
|
||||||
|
if (!dateRange.value) return;
|
||||||
|
isLoading.value = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const timeFormat =
|
||||||
|
timeGranularity.value === 'DAY' ? 'YYYY-MM-DD' : 'YYYY-MM-DD HH:mm:ss';
|
||||||
|
|
||||||
|
const filterParams = {
|
||||||
|
filter: {
|
||||||
|
logic: 'and',
|
||||||
|
filters: [
|
||||||
|
{
|
||||||
|
field: 'stcd',
|
||||||
|
operator: 'eq',
|
||||||
|
dataType: 'string',
|
||||||
|
value: modelStore.params.stcd
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'drtp',
|
||||||
|
operator: 'eq',
|
||||||
|
dataType: 'string',
|
||||||
|
value: timeGranularity.value
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'tm',
|
||||||
|
operator: 'gte',
|
||||||
|
dataType: 'date',
|
||||||
|
value: dateRange.value[0].format(timeFormat)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'tm',
|
||||||
|
operator: 'lte',
|
||||||
|
dataType: 'date',
|
||||||
|
value: dateRange.value[1].format(timeFormat)
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
sort: [{ field: 'dt', dir: 'asc' }]
|
||||||
|
};
|
||||||
|
|
||||||
|
const res = await getMonitorDataWaterTempVertical(filterParams);
|
||||||
|
const responseData = res?.data?.data || res?.data || {};
|
||||||
|
const item = Array.isArray(responseData) ? responseData[0] : responseData;
|
||||||
|
|
||||||
|
const cols = item?.columns || [];
|
||||||
|
const dataSource = item?.dataSource || item?.data || [];
|
||||||
|
|
||||||
|
columns.value = cols;
|
||||||
|
checkboxOptions.value = cols
|
||||||
|
.filter((col: any) => col.dataIndex !== 'stnm' && col.dataIndex !== 'dt')
|
||||||
|
.map((col: any) => ({ label: col.key + 'm', value: col.dataIndex }));
|
||||||
|
selectedColumns.value = checkboxOptions.value.map((opt: any) => opt.value);
|
||||||
|
|
||||||
|
tableData.value = [...dataSource].reverse();
|
||||||
|
console.log(dataSource);
|
||||||
|
|
||||||
|
chartData.value = JSON.parse(JSON.stringify(tableData.value));
|
||||||
|
|
||||||
|
// 默认选中第一条,并初始化图表
|
||||||
|
if (tableData.value.length > 0) {
|
||||||
|
selectedRowsForChart.value = [tableData.value[0]];
|
||||||
|
nextTick(() => updateChart([tableData.value[0]]));
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取数据失败:', error);
|
||||||
|
tableData.value = [];
|
||||||
|
chartData.value = [];
|
||||||
|
columns.value = [];
|
||||||
|
checkboxOptions.value = [];
|
||||||
|
selectedColumns.value = [];
|
||||||
|
if (chartInstance) chartInstance.clear();
|
||||||
|
} finally {
|
||||||
|
isLoading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSearch = () => fetchData();
|
||||||
|
|
||||||
|
const initData = () => {
|
||||||
|
if (hasLoaded.value) return;
|
||||||
|
nextTick(() => {
|
||||||
|
if (chartRef.value && !chartInstance)
|
||||||
|
chartInstance = echarts.init(chartRef.value);
|
||||||
|
});
|
||||||
|
fetchData();
|
||||||
|
hasLoaded.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.isActive,
|
||||||
|
active => {
|
||||||
|
if (active && !hasLoaded.value) initData();
|
||||||
|
},
|
||||||
|
{ immediate: true }
|
||||||
|
);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => modelStore.params.stcd,
|
||||||
|
(newStcd, oldStcd) => {
|
||||||
|
if (newStcd && newStcd !== oldStcd) fetchData();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// ResizeObserver
|
||||||
|
let resizeObserver: ResizeObserver | null = null;
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.isActive,
|
||||||
|
active => {
|
||||||
|
if (active && chartRef.value) {
|
||||||
|
setTimeout(() => {
|
||||||
|
if (chartInstance) chartInstance.resize();
|
||||||
|
}, 200);
|
||||||
|
if (!resizeObserver) {
|
||||||
|
resizeObserver = new ResizeObserver(() => {
|
||||||
|
if (chartInstance) chartInstance.resize();
|
||||||
|
});
|
||||||
|
resizeObserver.observe(chartRef.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleResize = () => {
|
||||||
|
if (chartInstance) chartInstance.resize();
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
window.addEventListener('resize', handleResize);
|
||||||
|
});
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
if (chartInstance) {
|
||||||
|
chartInstance.dispose();
|
||||||
|
chartInstance = null;
|
||||||
|
}
|
||||||
|
window.removeEventListener('resize', handleResize);
|
||||||
|
if (resizeObserver) {
|
||||||
|
resizeObserver.disconnect();
|
||||||
|
resizeObserver = null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.vertical-water-temperature {
|
||||||
|
width: 100%;
|
||||||
|
min-height: 600px;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
:deep(.ant-tabs-nav) {
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-bar {
|
||||||
|
width: 100%;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-end;
|
||||||
|
|
||||||
|
.search-btn {
|
||||||
|
margin-left: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-checkbox-cxsw {
|
||||||
|
z-index: 100;
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
|
||||||
|
.select-all-checkbox {
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-title {
|
||||||
|
width: 60px;
|
||||||
|
height: 68px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
padding-right: 10px;
|
||||||
|
font-weight: 500;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-group {
|
||||||
|
height: 68px;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-content {
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
.content-body {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
height: 600px;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
.chart-wrapper {
|
||||||
|
flex: 1;
|
||||||
|
height: 100%;
|
||||||
|
position: relative;
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding-top: 10px;
|
||||||
|
|
||||||
|
.chart-container {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-empty {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-wrapper {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 500px;
|
||||||
|
height: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: 10px;
|
||||||
|
overflow: hidden;
|
||||||
|
overflow-x: auto;
|
||||||
|
border-left: 1px solid #dcdfe6;
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
1020
frontend/src/components/MapModal/components/WaterTemperature.vue
Normal file
1020
frontend/src/components/MapModal/components/WaterTemperature.vue
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,315 +0,0 @@
|
|||||||
<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>
|
|
||||||
@ -1,505 +0,0 @@
|
|||||||
<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>
|
|
||||||
@ -1,399 +0,0 @@
|
|||||||
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
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@ -16,6 +16,7 @@
|
|||||||
4. 预警提示 少接口
|
4. 预警提示 少接口
|
||||||
5. 生态流量 达标率查询不对
|
5. 生态流量 达标率查询不对
|
||||||
6. 鱼类适应性繁殖同期对比NAN (王)
|
6. 鱼类适应性繁殖同期对比NAN (王)
|
||||||
|
7. 出库水温 综合分析 导出没做
|
||||||
-->
|
-->
|
||||||
<div v-if="modelStore.showStcdSelector" class="stcd-selector-wrapper">
|
<div v-if="modelStore.showStcdSelector" class="stcd-selector-wrapper">
|
||||||
<a-select
|
<a-select
|
||||||
@ -61,11 +62,20 @@
|
|||||||
:url="getTabUrl('panoramaInfo')"
|
:url="getTabUrl('panoramaInfo')"
|
||||||
:is-active="currentActiveKey === 'panoramaInfo'"
|
:is-active="currentActiveKey === 'panoramaInfo'"
|
||||||
/>
|
/>
|
||||||
<!-- 监测数据 -->
|
<!-- 监测数据 - 电站运行过程线 -->
|
||||||
<MonitorInfo
|
<MonitorInfo
|
||||||
v-show="currentActiveKey === 'monitorInfo'"
|
v-show="currentActiveKey === 'monitorInfo'"
|
||||||
:is-active="currentActiveKey === 'monitorInfo'"
|
:is-active="currentActiveKey === 'monitorInfo'"
|
||||||
:code="getTabCode('monitorInfo')"
|
/>
|
||||||
|
<!-- 监测数据 - 水温 -->
|
||||||
|
<WaterTemperature
|
||||||
|
v-show="currentActiveKey === 'WaterTemperature'"
|
||||||
|
:is-active="currentActiveKey === 'WaterTemperature'"
|
||||||
|
/>
|
||||||
|
<!-- 监测数据 - 垂向水温 -->
|
||||||
|
<VerticalWaterTemperature
|
||||||
|
v-show="currentActiveKey === 'VerticalWaterTemperature'"
|
||||||
|
:is-active="currentActiveKey === 'VerticalWaterTemperature'"
|
||||||
/>
|
/>
|
||||||
<!-- 预警提示 -->
|
<!-- 预警提示 -->
|
||||||
<EarlyWarningAlert
|
<EarlyWarningAlert
|
||||||
@ -110,7 +120,9 @@ import { ref, watch, reactive, computed } from 'vue';
|
|||||||
import BasicInfo from './components/BasicInfo.vue'; // 基本信息
|
import BasicInfo from './components/BasicInfo.vue'; // 基本信息
|
||||||
import VideoInfo from './components/videoInfo.vue'; // 实时视频
|
import VideoInfo from './components/videoInfo.vue'; // 实时视频
|
||||||
import PanoramaInfo from './components/PanoramaInfo.vue'; // 全景影像
|
import PanoramaInfo from './components/PanoramaInfo.vue'; // 全景影像
|
||||||
import MonitorInfo from './components/MonitorInfo.vue'; // 监测数据
|
import MonitorInfo from './components/MonitorInfo.vue'; // 电站运行过程线
|
||||||
|
import WaterTemperature from './components/WaterTemperature.vue'; // 水温
|
||||||
|
import VerticalWaterTemperature from './components/VerticalWaterTemperature.vue'; // 垂向水温
|
||||||
import EarlyWarningAlert from './components/EarlyWarningAlert.vue'; // 预警提示
|
import EarlyWarningAlert from './components/EarlyWarningAlert.vue'; // 预警提示
|
||||||
import EcologicalFlow from './components/EcologicalFlow.vue'; // 生态流量
|
import EcologicalFlow from './components/EcologicalFlow.vue'; // 生态流量
|
||||||
import Attachment from './components/Attachment.vue'; // 查看报告/批复文件
|
import Attachment from './components/Attachment.vue'; // 查看报告/批复文件
|
||||||
|
|||||||
@ -743,6 +743,7 @@ const WTTabs: Array<any> = [
|
|||||||
name: '监测数据',
|
name: '监测数据',
|
||||||
key: 'WaterTemperature',
|
key: 'WaterTemperature',
|
||||||
type: 'WaterTemperature',
|
type: 'WaterTemperature',
|
||||||
|
code: 'swjc.tabs.jcsj',
|
||||||
default: true // 默认显示
|
default: true // 默认显示
|
||||||
},
|
},
|
||||||
// {
|
// {
|
||||||
@ -770,6 +771,7 @@ const WTTabs1: Array<any> = [
|
|||||||
name: '监测数据',
|
name: '监测数据',
|
||||||
key: 'VerticalWaterTemperature',
|
key: 'VerticalWaterTemperature',
|
||||||
type: 'VerticalWaterTemperature',
|
type: 'VerticalWaterTemperature',
|
||||||
|
code: 'cxsw.tabs.jcsj',
|
||||||
default: true // 默认显示
|
default: true // 默认显示
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user