2026-07-20 16:02:51 +08:00
|
|
|
|
<template>
|
2026-07-21 18:38:17 +08:00
|
|
|
|
<div class="w-full h-full flex flex-col pt-[20px] pl-[20px] body_one">
|
2026-07-20 16:02:51 +08:00
|
|
|
|
<!-- 搜索组件 -->
|
|
|
|
|
|
<VerticalTempSearch
|
|
|
|
|
|
@export-btn="exportBtn"
|
|
|
|
|
|
@search-finish="onSearchFinish"
|
|
|
|
|
|
@reset="onReset"
|
2026-08-20 09:03:31 +08:00
|
|
|
|
@seeEdit="handleSeeEdit"
|
|
|
|
|
|
@batch-delete="handleBatchDelete"
|
|
|
|
|
|
:selected-count="currentSearchParams.timeScale === 'tm' ? selectedRowsForChart.length : 0"
|
2026-07-20 16:02:51 +08:00
|
|
|
|
:exportLoading="exportLoading"
|
|
|
|
|
|
ref="searchRef"
|
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 垂向水温列勾选 -->
|
|
|
|
|
|
<div class="tab-checkbox-cxsw px-2">
|
|
|
|
|
|
<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>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 图表和表格区域 -->
|
|
|
|
|
|
<div class="w-full h-full flex-1 flex overflow-hidden px-2 pb-2">
|
|
|
|
|
|
<!-- 左侧图表 -->
|
|
|
|
|
|
<div class="w-[50%] h-full relative">
|
|
|
|
|
|
<div ref="chartRef" class="w-full h-full"></div>
|
|
|
|
|
|
<a-empty
|
|
|
|
|
|
v-if="!chartData || chartData.length === 0"
|
|
|
|
|
|
description="暂无数据"
|
|
|
|
|
|
class="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 右侧表格 -->
|
|
|
|
|
|
<div class="w-[50%] h-[100%] border-l border-[#dcdfe6] ml-2 pl-2 flex">
|
|
|
|
|
|
<BasicTable
|
|
|
|
|
|
ref="tableRef"
|
|
|
|
|
|
row-key="dt"
|
|
|
|
|
|
:scrollY="tableScrollY"
|
|
|
|
|
|
:scrollX="tableScrollX"
|
|
|
|
|
|
:columns="tableColumns"
|
|
|
|
|
|
:list-url="getVerticalList"
|
|
|
|
|
|
:transformData="transformData"
|
|
|
|
|
|
:searchParams="{
|
|
|
|
|
|
sort: sort
|
|
|
|
|
|
}"
|
|
|
|
|
|
:enable-row-selection="true"
|
|
|
|
|
|
:enable-row-highlight="false"
|
|
|
|
|
|
:min-selection-count="1"
|
|
|
|
|
|
@selection-change="handleCxswSelectionChange"
|
|
|
|
|
|
>
|
|
|
|
|
|
<template #action="{ record }">
|
|
|
|
|
|
<template v-if="currentSearchParams.timeScale === 'tm'">
|
|
|
|
|
|
<a-button
|
|
|
|
|
|
type="link"
|
|
|
|
|
|
class="text-[#2f6b98]"
|
|
|
|
|
|
size="small"
|
|
|
|
|
|
@click="handleEdit(record)"
|
|
|
|
|
|
>编辑</a-button
|
|
|
|
|
|
>
|
|
|
|
|
|
<a-button
|
|
|
|
|
|
type="link"
|
|
|
|
|
|
danger
|
|
|
|
|
|
size="small"
|
|
|
|
|
|
@click="handleDelete(record)"
|
|
|
|
|
|
>删除</a-button
|
|
|
|
|
|
>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</BasicTable>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<EditVerticalTempModal
|
|
|
|
|
|
v-model:open="editVisible"
|
|
|
|
|
|
:record="editRecord"
|
|
|
|
|
|
:time-scale="currentSearchParams.timeScale"
|
|
|
|
|
|
:visible-depths="selectedColumns"
|
|
|
|
|
|
@success="handleEditSuccess"
|
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
|
|
|
|
<DeleteConfirmModal
|
|
|
|
|
|
ref="deleteModalRef"
|
|
|
|
|
|
:delete-fn="handleDeleteFn"
|
|
|
|
|
|
title="删除垂向水温数据"
|
|
|
|
|
|
label="数据"
|
|
|
|
|
|
@success="handleEditSuccess"
|
|
|
|
|
|
/>
|
2026-08-20 09:03:31 +08:00
|
|
|
|
|
|
|
|
|
|
<!-- 操作日志弹框 -->
|
|
|
|
|
|
<OperationLogModal
|
|
|
|
|
|
v-model:open="operationLogVisible"
|
|
|
|
|
|
:table-name="operationTableName"
|
|
|
|
|
|
/>
|
2026-07-20 16:02:51 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<script setup lang="ts">
|
|
|
|
|
|
import {
|
|
|
|
|
|
ref,
|
|
|
|
|
|
computed,
|
|
|
|
|
|
onMounted,
|
|
|
|
|
|
nextTick,
|
|
|
|
|
|
watch,
|
|
|
|
|
|
onBeforeUnmount
|
|
|
|
|
|
} from 'vue';
|
|
|
|
|
|
import dayjs from 'dayjs';
|
|
|
|
|
|
import * as echarts from 'echarts';
|
|
|
|
|
|
import VerticalTempSearch from './VerticalTempSearch.vue';
|
|
|
|
|
|
import EditVerticalTempModal from './EditVerticalTempModal.vue';
|
|
|
|
|
|
import DeleteConfirmModal from '@/views/DataQueryMenuModule/components/conventionalHydropower/BasicData/DeleteConfirmModal.vue';
|
2026-08-20 09:03:31 +08:00
|
|
|
|
import OperationLogModal from '@/components/OperationLogModal/index.vue';
|
2026-07-20 16:02:51 +08:00
|
|
|
|
import BasicTable from '@/components/BasicTable/index.vue';
|
2026-08-20 09:03:31 +08:00
|
|
|
|
import { message } from 'ant-design-vue';
|
2026-07-20 16:02:51 +08:00
|
|
|
|
import { getVerticalList, deleteVerticalInfo } from '@/api/DataQueryMenuModule';
|
|
|
|
|
|
import { calcTableScrollY } from '@/utils/index';
|
|
|
|
|
|
|
|
|
|
|
|
const sort = ref<any>([
|
|
|
|
|
|
{
|
|
|
|
|
|
field: 'dt',
|
|
|
|
|
|
dir: 'asc'
|
|
|
|
|
|
}
|
|
|
|
|
|
]);
|
|
|
|
|
|
const tableRef = ref();
|
|
|
|
|
|
const searchRef = ref();
|
|
|
|
|
|
const tableScrollY = ref<string | number>(0);
|
|
|
|
|
|
const currentSearchParams = ref<any>({});
|
|
|
|
|
|
const exportLoading = ref(false);
|
|
|
|
|
|
const tableData = ref<any[]>([]);
|
|
|
|
|
|
const editVisible = ref(false);
|
|
|
|
|
|
const editRecord = ref<any>(null);
|
|
|
|
|
|
const deleteModalRef = ref();
|
2026-08-20 09:03:31 +08:00
|
|
|
|
const operationLogVisible = ref(false);
|
|
|
|
|
|
const operationTableName = 'SD_WTVT_R';
|
2026-07-20 16:02:51 +08:00
|
|
|
|
|
|
|
|
|
|
// 图表相关
|
|
|
|
|
|
const chartRef = ref<HTMLElement>();
|
|
|
|
|
|
let chartInstance: echarts.ECharts | null = null;
|
|
|
|
|
|
const chartData = ref<any[]>([]);
|
|
|
|
|
|
const selectedRowsForChart = ref<any[]>([]);
|
|
|
|
|
|
|
|
|
|
|
|
// 勾选相关
|
|
|
|
|
|
const columns = ref<any[]>([]);
|
|
|
|
|
|
const selectedColumns = ref<string[]>([]);
|
|
|
|
|
|
const checkboxOptions = ref<any[]>([]);
|
|
|
|
|
|
|
|
|
|
|
|
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 handleCheckboxChange = (values: string[]) => {
|
|
|
|
|
|
selectedColumns.value = values;
|
|
|
|
|
|
// 勾选变化时,根据当前选中的行重新渲染图表
|
|
|
|
|
|
if (selectedRowsForChart.value.length > 0) {
|
|
|
|
|
|
nextTick(() => {
|
|
|
|
|
|
updateChart(selectedRowsForChart.value);
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const handleSelectAllToggle = (e: any) => {
|
|
|
|
|
|
if (e.target.checked) {
|
|
|
|
|
|
selectedColumns.value = checkboxOptions.value.map((opt: any) => opt.value);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
selectedColumns.value = [];
|
|
|
|
|
|
}
|
|
|
|
|
|
// 全选/取消全选时,同步更新图表
|
|
|
|
|
|
if (selectedRowsForChart.value.length > 0) {
|
|
|
|
|
|
nextTick(() => {
|
|
|
|
|
|
updateChart(selectedRowsForChart.value);
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 表格列
|
|
|
|
|
|
const tableColumns = computed(() => {
|
|
|
|
|
|
const fixedCols = [
|
|
|
|
|
|
{
|
|
|
|
|
|
title: '测站名称',
|
|
|
|
|
|
dataIndex: 'stnm',
|
|
|
|
|
|
width: 150,
|
|
|
|
|
|
fixed: 'left'
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
title: '时间',
|
|
|
|
|
|
dataIndex: 'dt',
|
|
|
|
|
|
width: 160,
|
|
|
|
|
|
fixed: 'left',
|
|
|
|
|
|
customRender: ({ text }: any) =>
|
|
|
|
|
|
text
|
|
|
|
|
|
? dayjs(text).format(
|
|
|
|
|
|
currentSearchParams.value.timeScale === 'month'
|
|
|
|
|
|
? 'YYYY-MM'
|
|
|
|
|
|
: currentSearchParams.value.timeScale === 'dt'
|
|
|
|
|
|
? 'YYYY-MM-DD'
|
|
|
|
|
|
: 'YYYY-MM-DD HH:mm:ss'
|
|
|
|
|
|
)
|
|
|
|
|
|
: '-'
|
|
|
|
|
|
}
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
? parseFloat(val).toFixed(1)
|
|
|
|
|
|
: '-';
|
|
|
|
|
|
}
|
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
|
|
const actionCols =
|
|
|
|
|
|
currentSearchParams.value.timeScale === 'tm'
|
|
|
|
|
|
? [
|
|
|
|
|
|
{
|
|
|
|
|
|
title: '操作',
|
|
|
|
|
|
dataIndex: 'action',
|
|
|
|
|
|
fixed: 'right',
|
|
|
|
|
|
width: 120
|
|
|
|
|
|
}
|
|
|
|
|
|
]
|
|
|
|
|
|
: [];
|
|
|
|
|
|
|
|
|
|
|
|
return [...fixedCols, ...dataCols, ...actionCols];
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
const tableScrollX = computed(() => {
|
|
|
|
|
|
const cols = tableColumns.value;
|
|
|
|
|
|
const total = cols.reduce((s, c) => s + (c.width || 100), 0);
|
|
|
|
|
|
return total > 600 ? total : undefined;
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 默认选中行
|
|
|
|
|
|
const defaultSelectedRowKeys = computed(() => {
|
|
|
|
|
|
if (tableData.value && tableData.value.length > 0) {
|
|
|
|
|
|
return [tableData.value[0].dt];
|
|
|
|
|
|
}
|
|
|
|
|
|
return [];
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 图表颜色
|
|
|
|
|
|
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;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const series = selectedRows.map((row, index) => {
|
|
|
|
|
|
const data = Object.entries(row.dataList || {})
|
|
|
|
|
|
.filter(([depth]) => selectedColumns.value.includes(depth))
|
|
|
|
|
|
.map(([depth, temp]) => [parseFloat(temp as string), parseFloat(depth)])
|
|
|
|
|
|
.sort((a, b) => a[1] - b[1]);
|
|
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
name: dayjs(row.dt).format(
|
|
|
|
|
|
currentSearchParams.value.timeScale === 'month'
|
|
|
|
|
|
? 'YYYY-MM'
|
|
|
|
|
|
: currentSearchParams.value.timeScale === 'dt'
|
|
|
|
|
|
? 'YYYY-MM-DD'
|
|
|
|
|
|
: 'YYYY-MM-DD HH:mm'
|
|
|
|
|
|
),
|
|
|
|
|
|
type: 'line',
|
|
|
|
|
|
data,
|
|
|
|
|
|
smooth: false,
|
|
|
|
|
|
symbol: 'circle',
|
|
|
|
|
|
symbolSize: 4,
|
|
|
|
|
|
lineStyle: { width: 2, color: COLORS[index % COLORS.length] },
|
|
|
|
|
|
itemStyle: { color: COLORS[index % COLORS.length] }
|
|
|
|
|
|
};
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
const option = {
|
|
|
|
|
|
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.toFixed(1) + '℃';
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
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
|
|
|
|
|
|
},
|
|
|
|
|
|
dataZoom: {
|
|
|
|
|
|
type: 'inside',
|
|
|
|
|
|
xAxisIndex: [0],
|
|
|
|
|
|
throttle: 50,
|
|
|
|
|
|
start: 0,
|
|
|
|
|
|
end: 100
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
chartInstance.setOption(option, true);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 表格行勾选变化
|
|
|
|
|
|
const handleCxswSelectionChange = (
|
|
|
|
|
|
selectedRowKeys: string[],
|
|
|
|
|
|
selectedRows: any[]
|
|
|
|
|
|
) => {
|
|
|
|
|
|
selectedRowsForChart.value = selectedRows;
|
|
|
|
|
|
updateChart(selectedRows);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 搜索完成
|
|
|
|
|
|
const onSearchFinish = async (values: any) => {
|
|
|
|
|
|
currentSearchParams.value = values;
|
|
|
|
|
|
await nextTick();
|
|
|
|
|
|
initTable(values);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const onReset = async (values: any) => {
|
|
|
|
|
|
currentSearchParams.value = values;
|
|
|
|
|
|
await nextTick();
|
|
|
|
|
|
initTable(values);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const exportBtn = () => {
|
|
|
|
|
|
if (exportLoading.value) return;
|
|
|
|
|
|
|
|
|
|
|
|
exportLoading.value = true;
|
|
|
|
|
|
searchRef.value.btnLoading = true;
|
|
|
|
|
|
|
|
|
|
|
|
tableRef.value
|
|
|
|
|
|
.exportTable({
|
|
|
|
|
|
fileName: `垂向水温数据_${dayjs().format('YYYY-MM-DD HH-mm-ss')}`
|
|
|
|
|
|
})
|
|
|
|
|
|
.finally(() => {
|
|
|
|
|
|
exportLoading.value = false;
|
|
|
|
|
|
searchRef.value.btnLoading = false;
|
|
|
|
|
|
});
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const handleEdit = (record: any) => {
|
|
|
|
|
|
editRecord.value = { ...record };
|
|
|
|
|
|
editVisible.value = true;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2026-08-20 09:03:31 +08:00
|
|
|
|
// 删除(兼容单条:record 本身;批量:record.records 数组)
|
2026-07-20 16:02:51 +08:00
|
|
|
|
const handleDeleteFn = async (record: any, reason: string) => {
|
2026-08-20 09:03:31 +08:00
|
|
|
|
const list = Array.isArray(record.records) ? record.records : [record];
|
|
|
|
|
|
const dataList = list.map((r: any) => ({
|
|
|
|
|
|
id: r.stcd ?? r.id,
|
|
|
|
|
|
tm: r.tm ?? r.dt
|
|
|
|
|
|
}));
|
2026-07-20 16:02:51 +08:00
|
|
|
|
return deleteVerticalInfo({
|
2026-08-20 09:03:31 +08:00
|
|
|
|
dataList,
|
2026-07-20 16:02:51 +08:00
|
|
|
|
dataType: 'TIME',
|
|
|
|
|
|
source: reason
|
|
|
|
|
|
});
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2026-08-20 09:03:31 +08:00
|
|
|
|
// 删除后刷新:清空选中(让批量删除按钮回到禁用状态)并重新加载列表
|
|
|
|
|
|
const refreshAfterDelete = () => {
|
|
|
|
|
|
selectedRowsForChart.value = [];
|
|
|
|
|
|
tableRef.value?.clearSelection();
|
|
|
|
|
|
initTable(currentSearchParams.value);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2026-07-20 16:02:51 +08:00
|
|
|
|
const handleDelete = (record: any) => {
|
|
|
|
|
|
deleteModalRef.value?.open(record, () => {
|
2026-08-20 09:03:31 +08:00
|
|
|
|
refreshAfterDelete();
|
2026-07-20 16:02:51 +08:00
|
|
|
|
});
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2026-08-20 09:03:31 +08:00
|
|
|
|
// 批量删除(仅小时 tm 模式,与单条删除一致)
|
|
|
|
|
|
const handleBatchDelete = () => {
|
|
|
|
|
|
if (currentSearchParams.value.timeScale !== 'tm') {
|
|
|
|
|
|
message.info('仅小时数据支持删除');
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (selectedRowsForChart.value.length === 0) {
|
|
|
|
|
|
message.info('请先勾选要删除的数据');
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
deleteModalRef.value?.open(
|
|
|
|
|
|
{
|
|
|
|
|
|
records: [...selectedRowsForChart.value],
|
|
|
|
|
|
stnm: `选中的 ${selectedRowsForChart.value.length} 条数据`
|
|
|
|
|
|
},
|
|
|
|
|
|
() => {
|
|
|
|
|
|
refreshAfterDelete();
|
|
|
|
|
|
}
|
|
|
|
|
|
);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 查看修改(操作日志)
|
|
|
|
|
|
const handleSeeEdit = () => {
|
|
|
|
|
|
operationLogVisible.value = true;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2026-07-20 16:02:51 +08:00
|
|
|
|
const handleEditSuccess = () => {
|
|
|
|
|
|
initTable(currentSearchParams.value);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const initTable = (values: any) => {
|
|
|
|
|
|
const filters = [
|
|
|
|
|
|
values.rvcd && values.rvcd !== 'all'
|
|
|
|
|
|
? {
|
|
|
|
|
|
field: 'rvcd',
|
|
|
|
|
|
operator: 'contains',
|
|
|
|
|
|
dataType: 'string',
|
|
|
|
|
|
value: values.rvcd
|
|
|
|
|
|
}
|
|
|
|
|
|
: null,
|
|
|
|
|
|
values.rstcd
|
|
|
|
|
|
? {
|
|
|
|
|
|
field: 'rstcd',
|
|
|
|
|
|
operator: 'eq',
|
|
|
|
|
|
dataType: 'string',
|
|
|
|
|
|
value: values.rstcd
|
|
|
|
|
|
}
|
|
|
|
|
|
: null,
|
|
|
|
|
|
values.stcd
|
|
|
|
|
|
? {
|
|
|
|
|
|
field: 'stcd',
|
|
|
|
|
|
operator: 'eq',
|
|
|
|
|
|
dataType: 'string',
|
|
|
|
|
|
value: values.stcd
|
|
|
|
|
|
}
|
|
|
|
|
|
: null
|
|
|
|
|
|
].filter(Boolean);
|
|
|
|
|
|
|
|
|
|
|
|
if (values.timeScale == 'tm' || values.timeScale == 'dt') {
|
|
|
|
|
|
filters.push(
|
|
|
|
|
|
{
|
|
|
|
|
|
field: 'drtp',
|
|
|
|
|
|
operator: 'eq',
|
|
|
|
|
|
dataType: 'string',
|
|
|
|
|
|
value: values.timeScale == 'tm' ? 'HOUR' : 'DAY'
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
field: 'tm',
|
|
|
|
|
|
operator: 'gte',
|
|
|
|
|
|
dataType: 'date',
|
|
|
|
|
|
value: dayjs(values.jcdt.min).format('YYYY-MM-DD HH:mm:ss')
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
field: 'tm',
|
|
|
|
|
|
operator: 'lte',
|
|
|
|
|
|
dataType: 'date',
|
|
|
|
|
|
value: dayjs(values.jcdt.max).format('YYYY-MM-DD HH:mm:ss')
|
|
|
|
|
|
}
|
|
|
|
|
|
);
|
|
|
|
|
|
} else if (values.timeScale == 'month') {
|
|
|
|
|
|
filters.push(
|
|
|
|
|
|
{
|
|
|
|
|
|
field: 'drtp',
|
|
|
|
|
|
operator: 'eq',
|
|
|
|
|
|
dataType: 'string',
|
|
|
|
|
|
value: 'MON'
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
field: 'startYear',
|
|
|
|
|
|
operator: 'gte',
|
|
|
|
|
|
dataType: 'date',
|
|
|
|
|
|
value: dayjs(values.jcdt.min).format('YYYY')
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
field: 'startMonth',
|
|
|
|
|
|
operator: 'gte',
|
|
|
|
|
|
dataType: 'date',
|
|
|
|
|
|
value: dayjs(values.jcdt.min).format('MM')
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
field: 'endYear',
|
|
|
|
|
|
operator: 'lte',
|
|
|
|
|
|
dataType: 'date',
|
|
|
|
|
|
value: dayjs(values.jcdt.max).format('YYYY')
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
field: 'endMonth',
|
|
|
|
|
|
operator: 'lte',
|
|
|
|
|
|
dataType: 'date',
|
|
|
|
|
|
value: dayjs(values.jcdt.max).format('MM')
|
|
|
|
|
|
}
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const params = {
|
|
|
|
|
|
logic: 'and',
|
|
|
|
|
|
filters
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 重置勾选和图表状态
|
|
|
|
|
|
selectedColumns.value = [];
|
|
|
|
|
|
checkboxOptions.value = [];
|
|
|
|
|
|
columns.value = [];
|
|
|
|
|
|
selectedRowsForChart.value = [];
|
|
|
|
|
|
if (chartInstance) chartInstance.clear();
|
|
|
|
|
|
|
|
|
|
|
|
tableRef.value.getList(params);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const transformData = (data: any) => {
|
|
|
|
|
|
const apiColumns = data?.data?.data?.[0]?.columns || [];
|
|
|
|
|
|
const dataSource = data?.data?.data?.[0]?.dataSource || [];
|
|
|
|
|
|
|
|
|
|
|
|
// 过滤出非固定列
|
|
|
|
|
|
const dynamicColumns = apiColumns.filter(
|
|
|
|
|
|
(item: any) => item.key !== 'stnm' && item.key !== 'dt'
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
columns.value = dynamicColumns;
|
|
|
|
|
|
checkboxOptions.value = dynamicColumns.map((col: any) => ({
|
|
|
|
|
|
label: col.key + 'm',
|
|
|
|
|
|
value: col.dataIndex
|
|
|
|
|
|
}));
|
|
|
|
|
|
selectedColumns.value = checkboxOptions.value.map((opt: any) => opt.value);
|
|
|
|
|
|
|
|
|
|
|
|
// 转换数据格式为 dataList 结构
|
|
|
|
|
|
const formattedData = dataSource
|
|
|
|
|
|
.map((row: any) => {
|
|
|
|
|
|
const dataList: any = {};
|
|
|
|
|
|
dynamicColumns.forEach((col: any) => {
|
|
|
|
|
|
dataList[col.dataIndex] = row.dataList[col.dataIndex];
|
|
|
|
|
|
});
|
|
|
|
|
|
return {
|
|
|
|
|
|
...row,
|
|
|
|
|
|
stnm: row.stnm,
|
|
|
|
|
|
dt: row.dt,
|
|
|
|
|
|
dataList
|
|
|
|
|
|
};
|
|
|
|
|
|
})
|
|
|
|
|
|
.reverse();
|
|
|
|
|
|
|
2026-08-04 09:01:23 +08:00
|
|
|
|
const total = data?.data?.total || 0;
|
2026-07-20 16:02:51 +08:00
|
|
|
|
tableData.value = formattedData;
|
|
|
|
|
|
|
|
|
|
|
|
// 初始化图表:默认选中第一条
|
|
|
|
|
|
if (formattedData.length > 0) {
|
|
|
|
|
|
selectedRowsForChart.value = [formattedData[0]];
|
|
|
|
|
|
nextTick(() => {
|
|
|
|
|
|
chartData.value = JSON.parse(JSON.stringify(formattedData));
|
|
|
|
|
|
updateChart([formattedData[0]]);
|
|
|
|
|
|
// 手动设置表格默认选中第一行
|
|
|
|
|
|
tableRef.value?.setSelectedRowKeys?.([formattedData[0].dt]);
|
|
|
|
|
|
});
|
|
|
|
|
|
} else {
|
|
|
|
|
|
chartData.value = [];
|
|
|
|
|
|
if (chartInstance) chartInstance.clear();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
records: formattedData,
|
|
|
|
|
|
total
|
|
|
|
|
|
};
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 窗口大小变化时重绘图表
|
|
|
|
|
|
const handleResize = () => {
|
|
|
|
|
|
if (chartInstance) chartInstance.resize();
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// ResizeObserver
|
|
|
|
|
|
let resizeObserver: ResizeObserver | null = null;
|
|
|
|
|
|
|
|
|
|
|
|
onMounted(() => {
|
|
|
|
|
|
nextTick(() => {
|
|
|
|
|
|
tableScrollY.value = calcTableScrollY();
|
|
|
|
|
|
|
|
|
|
|
|
// 初始化图表
|
|
|
|
|
|
if (chartRef.value && !chartInstance) {
|
|
|
|
|
|
chartInstance = echarts.init(chartRef.value);
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 监听容器大小变化
|
|
|
|
|
|
if (chartRef.value) {
|
|
|
|
|
|
resizeObserver = new ResizeObserver(() => {
|
|
|
|
|
|
if (chartInstance) chartInstance.resize();
|
|
|
|
|
|
});
|
|
|
|
|
|
resizeObserver.observe(chartRef.value);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 窗口大小变化时重绘图表
|
|
|
|
|
|
window.addEventListener('resize', handleResize);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
onBeforeUnmount(() => {
|
|
|
|
|
|
if (chartInstance) {
|
|
|
|
|
|
chartInstance.dispose();
|
|
|
|
|
|
chartInstance = null;
|
|
|
|
|
|
}
|
|
|
|
|
|
window.removeEventListener('resize', handleResize);
|
|
|
|
|
|
if (resizeObserver) {
|
|
|
|
|
|
resizeObserver.disconnect();
|
|
|
|
|
|
resizeObserver = null;
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
<style scoped lang="scss">
|
|
|
|
|
|
.tab-checkbox-cxsw {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
gap: 8px;
|
|
|
|
|
|
padding-top: 8px;
|
|
|
|
|
|
padding-bottom: 8px;
|
|
|
|
|
|
|
|
|
|
|
|
.select-all-checkbox {
|
|
|
|
|
|
flex-shrink: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.checkbox-title {
|
|
|
|
|
|
width: 60px;
|
|
|
|
|
|
flex-shrink: 0;
|
|
|
|
|
|
padding-right: 10px;
|
|
|
|
|
|
font-weight: 500;
|
|
|
|
|
|
white-space: nowrap;
|
|
|
|
|
|
text-align: right;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.checkbox-group {
|
|
|
|
|
|
max-height: 68px;
|
|
|
|
|
|
overflow-y: auto;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 禁用表格行点击高亮样式
|
|
|
|
|
|
:deep(.ant-table-tbody .ant-table-row:hover > td) {
|
|
|
|
|
|
background-color: #ffffff !important;
|
|
|
|
|
|
}
|
|
|
|
|
|
:deep(.ant-table-tbody .ant-table-row-selected > td) {
|
|
|
|
|
|
background-color: #ffffff !important;
|
|
|
|
|
|
}
|
|
|
|
|
|
:deep(.ant-table-tbody .ant-table-row > td) {
|
|
|
|
|
|
cursor: default !important;
|
|
|
|
|
|
}
|
2026-07-21 18:38:17 +08:00
|
|
|
|
.body_one {
|
|
|
|
|
|
position: relative;
|
|
|
|
|
|
z-index: 900;
|
|
|
|
|
|
pointer-events: all;
|
|
|
|
|
|
}
|
2026-07-20 16:02:51 +08:00
|
|
|
|
</style>
|