WholeProcessPlatform/frontend/src/views/DataQueryMenuModule/components/conventionalHydropower/BasicData/CustomColumnModal.vue

319 lines
7.8 KiB
Vue
Raw Normal View History

<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.defaultConfig !== true);
// 按 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>