298 lines
7.2 KiB
Vue
298 lines
7.2 KiB
Vue
|
|
<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';
|
|||
|
|
import { BasicColumns } from '@/components/MapModal/column.config';
|
|||
|
|
|
|||
|
|
interface ColumnItem {
|
|||
|
|
name: string;
|
|||
|
|
filed: string;
|
|||
|
|
disabled?: boolean;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
interface ColumnGroup {
|
|||
|
|
name: string;
|
|||
|
|
children: ColumnItem[];
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
interface Props {
|
|||
|
|
visible: boolean;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
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[]>([]);
|
|||
|
|
|
|||
|
|
// 根据 BasicColumns 构建分组结构
|
|||
|
|
const buildGroupedColumns = () => {
|
|||
|
|
const groups: ColumnGroup[] = [];
|
|||
|
|
let currentGroup: ColumnGroup | null = null;
|
|||
|
|
|
|||
|
|
for (const col of BasicColumns) {
|
|||
|
|
if (!col.name && !col.filed) continue;
|
|||
|
|
|
|||
|
|
if (col.visible === false && col.name) {
|
|||
|
|
currentGroup = {
|
|||
|
|
name: col.name,
|
|||
|
|
children: []
|
|||
|
|
};
|
|||
|
|
groups.push(currentGroup);
|
|||
|
|
} else if (col.name && col.filed) {
|
|||
|
|
if (!currentGroup) {
|
|||
|
|
currentGroup = {
|
|||
|
|
name: '基础信息',
|
|||
|
|
children: []
|
|||
|
|
};
|
|||
|
|
groups.push(currentGroup);
|
|||
|
|
}
|
|||
|
|
const isStationName = col.filed === 'ennm';
|
|||
|
|
currentGroup.children.push({
|
|||
|
|
name: col.name,
|
|||
|
|
filed: col.filed,
|
|||
|
|
disabled: isStationName
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return groups;
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 始终勾选的 filed(电站名称)
|
|||
|
|
const STATION_NAME_FILED = 'ennm';
|
|||
|
|
|
|||
|
|
// 初始化选中状态(所有非禁用的都默认选中 + 电站名称始终选中)
|
|||
|
|
const initSelectedKeys = () => {
|
|||
|
|
const allKeys: string[] = [STATION_NAME_FILED];
|
|||
|
|
for (const group of groupedColumns.value) {
|
|||
|
|
for (const child of group.children) {
|
|||
|
|
if (!child.disabled && child.filed !== STATION_NAME_FILED) {
|
|||
|
|
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();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
);
|
|||
|
|
</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>
|