319 lines
7.8 KiB
Vue
319 lines
7.8 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';
|
||
|
||
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>
|