WholeProcessPlatform/frontend/src/views/DataQueryMenuModule/components/conventionalHydropower/BasicData/CustomColumnModal.vue
2026-07-31 11:13:49 +08:00

319 lines
7.8 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

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

<template>
<a-modal
:open="visible"
title="自定义数据列"
:width="1280"
:style="{ maxWidth: '80vw' }"
:footer="null"
@cancel="handleClose"
:destroyOnClose="true"
class="custom-column-modal"
>
<div class="custom-column-content">
<div
v-for="(group, idx) in groupedColumns"
:key="idx"
class="column-group"
>
<!-- 分组标题行 -->
<div class="group-header">
<span class="group-title">{{ group.name }}</span>
<div class="group-actions">
<a-checkbox
:checked="isGroupAllSelected(group)"
:indeterminate="isGroupIndeterminate(group)"
:disabled="isGroupAllDisabled(group)"
@change="e => handleGroupSelectAll(group, e.target.checked)"
>
全选
</a-checkbox>
<a-checkbox
:disabled="isGroupAllDisabled(group)"
@change="() => handleGroupSelectInverse(group)"
>
反选
</a-checkbox>
</div>
</div>
<!-- 子项列表 -->
<div class="group-items">
<a-checkbox
v-for="item in group.children"
:key="item.filed"
:checked="tempSelectedKeys.includes(item.filed)"
:disabled="item.disabled"
@change="e => handleItemChange(item, e.target.checked)"
>
{{ item.name }}
</a-checkbox>
</div>
</div>
</div>
<div class="modal-footer">
<a-button @click="handleClose">取消</a-button>
<a-button type="primary" @click="handleConfirm">确定</a-button>
</div>
</a-modal>
</template>
<script lang="ts" setup>
import { ref, watch } from 'vue';
interface ColumnItem {
name: string;
filed: string;
disabled?: boolean;
}
interface ColumnGroup {
name: string;
children: ColumnItem[];
}
export interface UserColumnItem {
columnEn: string;
columnName: string;
checked: number;
groupType: string;
orderIndex: number;
defaultConfig: boolean;
}
interface Props {
visible: boolean;
userColumnList: UserColumnItem[];
}
const props = defineProps<Props>();
const emit = defineEmits<{
(e: 'update:visible', value: boolean): void;
(e: 'confirm', keys: string[]): void;
}>();
const groupedColumns = ref<ColumnGroup[]>([]);
const tempSelectedKeys = ref<string[]>([]);
// 始终勾选的 filed电站名称
const STATION_NAME_FILED = 'stnm';
// 根据 API 返回的 userColumnList 构建分组结构
const buildGroupedColumns = () => {
const groups: ColumnGroup[] = [];
const groupMap = new Map<string, ColumnItem[]>();
// 过滤掉 defaultConfig: true 的项(兼容字符串类型)
const filteredList = props.userColumnList.filter(item => item.enable == 1);
// 按 groupType 分组
for (const item of filteredList) {
if (!item.groupType) continue;
if (!groupMap.has(item.groupType)) {
groupMap.set(item.groupType, []);
}
const isStationName = item.columnEn === STATION_NAME_FILED;
groupMap.get(item.groupType)!.push({
name: item.columnName,
filed: item.columnEn,
disabled: isStationName
});
}
// 转换为分组数组
for (const [groupName, children] of groupMap.entries()) {
groups.push({ name: groupName, children });
}
return groups;
};
// 初始化选中状态(根据 API 的 checked 字段)
const initSelectedKeys = () => {
const allKeys: string[] = [STATION_NAME_FILED];
for (const group of groupedColumns.value) {
for (const child of group.children) {
if (child.disabled) continue;
// 从 userColumnList 中查找对应的 checked 状态
const apiItem = props.userColumnList.find(
item => item.columnEn === child.filed
);
if (apiItem && apiItem.checked === 1) {
allKeys.push(child.filed);
}
}
}
tempSelectedKeys.value = [...allKeys];
};
// 判断分组是否全选
const isGroupAllSelected = (group: ColumnGroup) => {
const selectable = group.children.filter(child => !child.disabled);
if (selectable.length === 0) return false;
return selectable.every(child =>
tempSelectedKeys.value.includes(child.filed)
);
};
// 判断分组是否半选
const isGroupIndeterminate = (group: ColumnGroup) => {
const selectable = group.children.filter(child => !child.disabled);
if (selectable.length === 0) return false;
const selectedCount = selectable.filter(child =>
tempSelectedKeys.value.includes(child.filed)
).length;
return selectedCount > 0 && selectedCount < selectable.length;
};
// 判断分组是否所有项都被禁用
const isGroupAllDisabled = (group: ColumnGroup) => {
return group.children.every(child => child.disabled);
};
// 全选 / 取消全选
const handleGroupSelectAll = (group: ColumnGroup, checked: boolean) => {
const selectableFiled = group.children
.filter(child => !child.disabled)
.map(child => child.filed);
if (checked) {
for (const filed of selectableFiled) {
if (!tempSelectedKeys.value.includes(filed)) {
tempSelectedKeys.value.push(filed);
}
}
} else {
const filedSet = new Set(selectableFiled);
tempSelectedKeys.value = tempSelectedKeys.value.filter(
filed => !filedSet.has(filed)
);
}
};
// 反选
const handleGroupSelectInverse = (group: ColumnGroup) => {
for (const child of group.children) {
if (child.disabled) continue;
const index = tempSelectedKeys.value.indexOf(child.filed);
if (index > -1) {
tempSelectedKeys.value.splice(index, 1);
} else {
tempSelectedKeys.value.push(child.filed);
}
}
};
// 单个项变化
const handleItemChange = (item: ColumnItem, checked: boolean) => {
if (checked) {
if (!tempSelectedKeys.value.includes(item.filed)) {
tempSelectedKeys.value.push(item.filed);
}
} else {
tempSelectedKeys.value = tempSelectedKeys.value.filter(
filed => filed !== item.filed
);
}
};
const handleClose = () => {
emit('update:visible', false);
};
const handleConfirm = () => {
// 确保电站名称始终在结果中
const keys = [...new Set([STATION_NAME_FILED, ...tempSelectedKeys.value])];
emit('confirm', keys);
emit('update:visible', false);
};
watch(
() => props.visible,
newVal => {
if (newVal) {
groupedColumns.value = buildGroupedColumns();
initSelectedKeys();
}
}
);
// 当弹窗打开时,如果 userColumnList 更新则同步刷新
watch(
() => props.userColumnList,
() => {
if (props.visible) {
groupedColumns.value = buildGroupedColumns();
initSelectedKeys();
}
}
);
</script>
<style lang="scss" scoped>
.custom-column-modal {
:deep(.ant-modal-header) {
background-color: #005294;
.ant-modal-title {
color: #fff;
font-weight: 600;
}
}
.custom-column-content {
max-height: 500px;
overflow-y: auto;
padding: 8px 0;
.column-group {
margin-bottom: 16px;
border: 1px solid #e8e8e8;
border-radius: 4px;
padding: 12px;
.group-header {
display: flex;
justify-content: space-between;
align-items: center;
padding-bottom: 8px;
border-bottom: 1px solid #f0f0f0;
margin-bottom: 8px;
.group-title {
font-weight: 600;
font-size: 14px;
color: #333;
}
.group-actions {
display: flex;
gap: 12px;
:deep(.ant-checkbox-wrapper) {
margin-right: 0;
}
}
}
.group-items {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 8px;
:deep(.ant-checkbox-wrapper) {
margin: 0;
font-size: 13px;
}
}
}
}
.modal-footer {
display: flex;
justify-content: flex-end;
gap: 8px;
margin-top: 16px;
padding-top: 16px;
border-top: 1px solid #f0f0f0;
}
}
</style>