本文将介绍一个基于 Vue.js 和 Element Plus 实现的花名册表头设置组件,该组件允许用户自定义表格显示的字段及其顺序。
组件概述
这个组件主要提供以下功能:
- 自定义显示哪些表格字段
- 通过拖拽调整字段显示顺序
- 按字段分组进行批量选择
- 保存个人化的表头设置
核心功能实现
1. 对话框基础结构
使用 Element Plus 的 el-dialog
组件作为容器:
xml
<el-dialog
v-model="dialogVisible"
title="花名册表头设置"
width="500"
destroy-on-close
@close="onClose(false)"
>
<!-- 内容区域 -->
</el-dialog>
2. 拖拽排序功能
使用 vuedraggable
库实现已选字段的拖拽排序:
ini
<draggable
v-model="tableTag"
group="bossAccount"
:animation="300"
@change="tableDrag"
item-key="value"
:move="onMove"
filter=".disable"
>
<template #item="{ element }">
<el-tag
effect="dark"
:class="element.value !== 'name' ? 'draggable-item' : 'disable'"
:type="element.value === 'name' ? 'info' : 'primary'"
:closable="element.value !== 'name'"
@close="handleClose(element)"
>
{{ element.label }}
</el-tag>
</template>
</draggable>
3. 分组字段选择
实现分组选择功能,包括全选和部分选择:
ini
<div v-for="(group, key) in fieldGroups" :key="key">
<div style="margin-top: 8px; background-color: #eceff4; padding: 0 4px">
<el-checkbox
v-model="group.content"
:indeterminate="group.isIndeterminate"
@change="checkAllChange(group)"
>
{{ group.title }}
</el-checkbox>
</div>
<div style="padding: 0 4px">
<el-checkbox-group
v-model="group.checked"
@change="handleCheckedChange(group)"
>
<el-checkbox
v-for="item in group.list"
:key="item.value"
:label="item.label"
:value="item.value"
:disabled="item.value === 'name'"
>
{{ item.label }}
</el-checkbox>
</el-checkbox-group>
</div>
</div>
关键逻辑实现
1. 字段分组管理
php
const fieldGroups = reactive({
system: {
title: "系统信息",
content: false,
isIndeterminate: true,
checked: [],
list: [{ label: "实人认证", value: "authentication" }],
},
base: {
title: "基本信息",
content: false,
isIndeterminate: true,
checked: ["name"],
list: [
{ label: "姓名", value: "name" },
// 其他字段...
],
},
// 其他分组...
});
2. 全选/部分选逻辑
ini
const checkAllChange = (group) => {
group.checked = group.content
? group.list.map((item) => item.value)
: group.title === "基本信息"
? ["name"]
: [];
group.isIndeterminate = false;
updateTableTag();
};
const handleCheckedChange = (group) => {
updateTableTag();
group.content = group.checked.length === group.list.length;
group.isIndeterminate =
group.checked.length > 0 && group.checked.length < group.list.length;
};
3. 已选字段同步
ini
const updateTableTag = () => {
const allSelected = Object.values(fieldGroups).flatMap(
(group) => group.checked,
);
const uniqueSelected = [...new Set(allSelected)];
const nameIndex = uniqueSelected.indexOf("name");
if (nameIndex !== -1) {
uniqueSelected.splice(nameIndex, 1);
uniqueSelected.unshift("name");
}
tableTag.value = uniqueSelected.map((value) => {
for (const group of Object.values(fieldGroups)) {
const item = group.list.find((item) => item.value === value);
if (item) return item;
}
return { label: value, value };
});
};
使用说明
- 在父组件中引入并使用该组件:
ini
import RosterHeaderSettings from './RosterHeaderSettings.vue';
// 在模板中使用
<roster-header-settings
v-model="visible"
@close="handleClose"
@onSubmit="handleSubmit"
/>
- 组件会发射两个事件:
-
close
: 当对话框关闭时触发onSubmit
: 当用户点击保存时触发,携带当前选择的字段数组
- 默认会显示一些常用字段,用户可以根据需要调整
样式定制
组件提供了一些基础样式,可以通过修改以下类名来自定义外观:
arduino
.draggable-item {
// 可拖拽项的样式
}
.disable {
// 禁止拖拽项的样式
}
.el-checkbox {
width: 25%; // 每个复选框的宽度
}
总结
这个花名册表头设置组件通过组合 Vue.js 的响应式特性、Element Plus 的 UI 组件和 vuedraggable 的拖拽功能,实现了灵活的表头自定义功能。关键点包括:
- 使用响应式状态管理字段选择
- 实现分组选择和全选功能
- 提供拖拽排序交互
- 确保姓名字段始终显示且不可移除
该组件可以方便地集成到各种后台管理系统中,提升用户对表格显示的个性化控制能力。
完整代码
xml
<template>
<!-- 使用 Element Plus 的对话框组件,用于显示花名册表头设置 -->
<el-dialog
v-model="dialogVisible"
title="花名册表头设置"
width="500"
destroy-on-close
@close="onClose(false)"
>
<!-- 对话框内容区域 -->
<div style="max-height: 700px; overflow-y: auto">
<!-- 提示信息,说明设置仅对当前用户生效 -->
<div style="padding-bottom: 12px; font-size: 0.9em">
本设置仅对本人生效,不影响其他用户的花名册列表
</div>
<div>
<!-- 已选择字段的标题 -->
<div style="font-size: 1.1em; font-weight: bold">
已选择字段
<span style="font-size: 0.8em; font-weight: normal"
>可拖拽标签调整顺序</span
>
</div>
<!-- 使用 vuedraggable 组件实现拖拽排序 -->
<draggable
v-model="tableTag"
group="bossAccount"
:animation="300"
@change="tableDrag"
item-key="value"
:move="onMove"
filter=".disable"
>
<!-- 拖拽项的模板 -->
<template #item="{ element }">
<el-tag
effect="dark"
style="margin: 8px; width: 28%; justify-content: space-between"
:class="element.value !== 'name' ? 'draggable-item' : 'disable'"
:type="element.value === 'name' ? 'info' : 'primary'"
:closable="element.value !== 'name'"
@close="handleClose(element)"
>
{{ element.label }}
<!-- 显示标签的文本 -->
</el-tag>
</template>
</draggable>
<!-- 档案字段的标题 -->
<div style="font-size: 1.1em; font-weight: bold">档案字段</div>
<!-- 遍历 fieldGroups 对象,显示每个分组的字段 -->
<div v-for="(group, key) in fieldGroups" :key="key">
<!-- 分组标题 -->
<div
style="margin-top: 8px; background-color: #eceff4; padding: 0 4px"
>
<el-checkbox
v-model="group.content"
:indeterminate="group.isIndeterminate"
@change="checkAllChange(group)"
>
{{ group.title }}
</el-checkbox>
</div>
<!-- 分组内的字段列表 -->
<div style="padding: 0 4px">
<el-checkbox-group
v-model="group.checked"
@change="handleCheckedChange(group)"
>
<el-checkbox
v-for="item in group.list"
:key="item.value"
:label="item.label"
:value="item.value"
:disabled="item.value === 'name'"
>
{{ item.label }}
<!-- 显示字段的标签 -->
</el-checkbox>
</el-checkbox-group>
</div>
</div>
</div>
</div>
<!-- 对话框的底部操作区域 -->
<template #footer>
<div class="dialog-footer">
<el-button @click="onClose()">取消</el-button>
<!-- 取消按钮,点击时调用 onClose 方法 -->
<el-button type="primary" @click="submit"> 保存 </el-button>
<!-- 保存按钮,点击时调用 submit 方法 -->
</div>
</template>
</el-dialog>
</template>
<script setup>
import { ref, reactive, onMounted } from "vue"; // 引入 Vue 的响应式 API
import draggable from "vuedraggable"; // 引入拖拽组件
// 控制对话框的显示与隐藏
const dialogVisible = ref(true);
// 定义事件发射器,用于与父组件通信
const emit = defineEmits(["close", "onSubmit"]);
// 关闭对话框的回调函数
const onClose = (t = false) => {
emit("close", t); // 发射 close 事件,传递参数 t
};
// 保存设置的回调函数
const submit = () => {
emit("onSubmit", tableTag.value);
onClose();
};
// 拖拽事件的回调函数
const tableDrag = (event) => {};
// 拖拽时的回调函数,用于判断是否允许拖拽
const onMove = (e) => {
if (e.relatedContext.element.value === "name") return false; // 如果目标元素是 "name",则禁止拖拽
return true; // 否则允许拖拽
};
// 定义字段分组对象,包含多个分组,每个分组有标题、全选状态、半选状态、已选字段列表和字段列表
const fieldGroups = reactive({
system: {
title: "系统信息",
content: false,
isIndeterminate: true,
checked: [],
list: [{ label: "实人认证", value: "authentication" }],
},
base: {
title: "基本信息",
content: false,
isIndeterminate: true,
checked: ["name"],
list: [
{ label: "姓名", value: "name" },
{ label: "邮箱", value: "email" },
{ label: "部门", value: "deptId" },
{ label: "直属主管", value: "supervisor" },
{ label: "职位", value: "positions" },
{ label: "手机号", value: "mobile" },
{ label: "工号", value: "workNum" },
{ label: "分机号", value: "extension" },
{ label: "办公地点", value: "officeLocations" },
{ label: "备注", value: "reMark" },
{ label: "入职时间", value: "entryTime" },
{ label: "司龄(系统计算)", value: "secretaryAge" },
],
},
work: {
title: "工作信息",
content: false,
isIndeterminate: true,
checked: [],
list: [
{ label: "员工类型", value: "employeeType" },
{ label: "员工状态", value: "status" },
{ label: "试用期", value: "probationPeriod" },
{ label: "实际转正日期", value: "actualPromotionDate" },
{ label: "计划转正日期", value: "plannedPromotionDate" },
{ label: "岗位职级", value: "jobLevel" },
],
},
personal: {
title: "个人信息",
content: false,
isIndeterminate: true,
checked: [],
list: [
{ label: "身份证姓名", value: "cardName" },
{ label: "证件号码", value: "idNumber" },
{ label: "出生日期", value: "birthDate" },
{ label: "年龄(系统计算)", value: "age" },
{ label: "性别", value: "gender" },
{ label: "民族", value: "nationality" },
{ label: "身份证地址", value: "address" },
{ label: "证件有效期", value: "idExpiryDate" },
{ label: "婚姻状况", value: "maritalStatus" },
],
},
});
// 定义已选择的字段列表
const tableTag = ref([]);
// 更新已选择的字段列表
const updateTableTag = () => {
const allSelected = Object.values(fieldGroups).flatMap(
(group) => group.checked,
);
const uniqueSelected = [...new Set(allSelected)];
const nameIndex = uniqueSelected.indexOf("name");
if (nameIndex !== -1) {
uniqueSelected.splice(nameIndex, 1);
uniqueSelected.unshift("name");
}
tableTag.value = uniqueSelected.map((value) => {
for (const group of Object.values(fieldGroups)) {
const item = group.list.find((item) => item.value === value);
if (item) return item;
}
return { label: value, value };
});
};
// 处理标签关闭事件
const handleClose = (tag) => {
tableTag.value = tableTag.value.filter((item) => item.value !== tag.value);
for (const group of Object.values(fieldGroups)) {
const index = group.checked.indexOf(tag.value);
if (index !== -1) {
group.checked.splice(index, 1); // 移除对应的选中状态
}
}
updateCheckAllStatus();
};
// 更新全选状态
const updateCheckAllStatus = () => {
Object.values(fieldGroups).forEach((group) => {
group.content = group.checked.length === group.list.length;
group.isIndeterminate =
group.checked.length > 0 && group.checked.length < group.list.length;
});
};
// 处理全选状态变化
const checkAllChange = (group) => {
group.checked = group.content
? group.list.map((item) => item.value)
: group.title === "基本信息"
? ["name"]
: [];
group.isIndeterminate = false;
updateTableTag();
};
// 处理字段选中状态变化
const handleCheckedChange = (group) => {
updateTableTag();
group.content = group.checked.length === group.list.length;
group.isIndeterminate =
group.checked.length > 0 && group.checked.length < group.list.length;
};
// 根据 tableTag 的初始值,更新 fieldGroups 的 checked 状态
const syncFieldGroupsWithTableTag = () => {
tableTag.value.forEach((tag) => {
for (const group of Object.values(fieldGroups)) {
const item = group.list.find((item) => item.value === tag.value);
if (item && !group.checked.includes(item.value)) {
group.checked.push(item.value);
}
}
});
updateCheckAllStatus();
};
// 组件挂载时调用 updateTableTag 方法
onMounted(() => {
tableTag.value = [
{ label: "姓名", value: "name" },
{ label: "部门", value: "deptId" },
{ label: "职位", value: "positions" },
{ label: "入职时间", value: "entryTime" },
{ label: "员工类型", value: "employeeType" },
{ label: "手机号", value: "mobile" },
{ label: "邮箱", value: "email" },
{ label: "工号", value: "workNum" },
{ label: "性别", value: "gender" },
{ label: "年龄(系统计算)", value: "age" },
{ label: "民族", value: "nationality" },
];
syncFieldGroupsWithTableTag(); // 同步更新 fieldGroups 的 checked 状态
updateTableTag();
});
</script>
<style scoped lang="scss">
.draggable-item {
// 可拖拽项的样式
}
.disable {
// 禁止拖拽项的样式
}
.el-checkbox {
width: 25%; // 每个复选框的宽度
}
</style>
图片展示
