【组件开发】基于 Vue 3 和 Element Plus 的花名册表头设置组件(el-table动态设置表头组件开发)

本文将介绍一个基于 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 };
  });
};

使用说明

  1. 在父组件中引入并使用该组件:
ini 复制代码
import RosterHeaderSettings from './RosterHeaderSettings.vue';

// 在模板中使用
<roster-header-settings 
  v-model="visible"
  @close="handleClose"
  @onSubmit="handleSubmit"
/>
  1. 组件会发射两个事件:
    • close: 当对话框关闭时触发
    • onSubmit: 当用户点击保存时触发,携带当前选择的字段数组
  1. 默认会显示一些常用字段,用户可以根据需要调整

样式定制

组件提供了一些基础样式,可以通过修改以下类名来自定义外观:

arduino 复制代码
.draggable-item {
  // 可拖拽项的样式
}
.disable {
  // 禁止拖拽项的样式
}
.el-checkbox {
  width: 25%; // 每个复选框的宽度
}

总结

这个花名册表头设置组件通过组合 Vue.js 的响应式特性、Element Plus 的 UI 组件和 vuedraggable 的拖拽功能,实现了灵活的表头自定义功能。关键点包括:

  1. 使用响应式状态管理字段选择
  2. 实现分组选择和全选功能
  3. 提供拖拽排序交互
  4. 确保姓名字段始终显示且不可移除

该组件可以方便地集成到各种后台管理系统中,提升用户对表格显示的个性化控制能力。

完整代码

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>

图片展示

相关推荐
前端爆冲12 分钟前
基于vue和flex实现页面可配置组件顺序
前端·javascript·vue.js
正在脱发中21 分钟前
vue-cropper 遇到的坑 Failed to execute 'getComputedStyle' on 'Window': parameter
前端·vue.js
浪裡遊1 小时前
前端热门面试题day1
前端·javascript·vue.js·前端框架
hemoo2 小时前
Element UI 日期区间定制
vue.js·element
bilibilibiu灬2 小时前
Vue 3 `setupRenderEffect` 函数解析
vue.js
樊小肆2 小时前
Vue3 在线 PDF 编辑 2.0 撤回、反撤回
前端·vue.js·开源
bilibilibiu灬3 小时前
解析Vue 3中 `trigger` 函数是如何触发 DOM 更新的
vue.js
iceprosurface3 小时前
来试试用 react 的写法写 vue
vue.js
weixin_516875653 小时前
Vue 3 Watch 监听 Props 的踩坑记录
前端·javascript·vue.js