基于el-tree实现一个较为完善的角色授权功能

65 阅读3分钟

onepice

问题背景

当谈到 element plus 的 Tree 树形控件 树形控件时,它提供了一种非常便捷的方式来展示层级关系记录,比如权限记录、组织架构等。尽管原生的Tree控件在大多数情况下表现良好,但仍然存在一些不足之处,特别是在关联操作方面。例如,原生的Tree控件可能在处理复杂的交互操作时显得有些力不从心,可能需要额外的定制和扩展来满足特定的需求。

需求目标

  1. 当节点被选中,上层与之有亲缘关系的节点都得选中,下层有子节点的子节点全选
  2. 当节点被取消选中,下层子节点全部取消选中
  3. 当某一层节点全被取消选中,但不影响上层父节点的选中状态

el-tree自带属性check-strictly="false"会启动联动,可以随意实现1、2的需求,但是无法实现3需求

let's show you the code

// 选中的id 后台返回的树状数据结构 每个节点有一个唯一id
const checkedKeys = ref<string[]>([]);
// 加载前获取
checkedKeys.value = getCheckedIds(list.data);

// 获取选中的id的函数
// 字段名字看后台返回自己对应修改 children是子节点,isSelect 0: 1代表是否选中 ts类型自己定义
const getCheckedIds = (auths: Permission.ResPermisionList[], initArr: string[] = []): string[] => {
  auths.forEach((item: Permission.ResPermisionList) => {
    if (item.children.length == 0) {
      // 如果不存在 children,且 isSelect 等于 1,则将 id 添加到 initArr 数组中
      if (item.isSelect == 1) {
        initArr.push(item.id as string);
      }
    } else if (item.isSelect == 1) {
      // 如果存在 children,则递归调用 getCheckedIds 函数
      initArr.push(item.id as string);
      getCheckedIds(item.children, initArr);
    }
  });
  return initArr;
};

const hanleCheck = (data: any, node: any) => {
  // 获取当前节点是否被选中
  const isChecked = treeRef.value!.getNode(data).checked;
  // 如果当前节点被选中,则遍历下级子节点并选中,如果当前节点取消选中,则遍历下级节点并取消
  if (isChecked) {
    // 判断该节点是否有下级节点,如果有那么遍历设置下级节点为选中
    data.menuEntitys && data.menuEntitys.length > 0 && setChildreChecked(data.menuEntitys, true);
  } else {
    // 如果节点取消选中,则取消该节点下的子节点选中
    data.menuEntitys && data.menuEntitys.length > 0 && setChildreChecked(data.menuEntitys, false);
  }
  function setChildreChecked(node: any, isChecked: boolean) {
    node.forEach((item: any) => {
      item.menuEntitys && item.menuEntitys.length > 0 && setChildreChecked(item.menuEntitys, isChecked);
      // // 修改勾选状态
      treeRef.value!.setChecked(item.menuId, isChecked, false);
    });
  }
};

const checkChange = data => {
  // console.log(data, checked, indeterminate);
  // 选中全部子节点,父节点也默认选中,但是子节点再次取消勾选或者全部子节点取消勾选也不会影响父节点勾选状态
  let checkNode = treeRef.value!.getNode(data); //获取当前节点
  // 勾选部分子节点,父节点变为半选状态
  if (checkNode.parent && checkNode.parent.childNodes.some(ele => ele.checked)) {
    checkNode.parent.indeterminate = true;
  }
  // 勾选全部子节点,父节点变为全选状态
  if (checkNode.parent && checkNode.parent.childNodes.every(ele => ele.checked)) {
    checkNode.parent.checked = true;
    checkNode.parent.indeterminate = false;
  }
  // 如果取消所有第二节点的勾选状态,则第一层父节点也取消勾选
  if (checkNode.level == 2 && checkNode.parent.childNodes.every(ele => !ele.checked)) {
    checkNode.parent.checked = false;
    checkNode.parent.indeterminate = false;
  }
};


const handleSubmit = () => {
  ruleFormRef.value!.validate(async valid => {
    if (!valid) return;
    try {
      // 获取selectdKeys
      const checkedKeys = treeRef.value?.getCheckedKeys() || [];
      const halfCheckedKeys = treeRef.value?.getHalfCheckedKeys() || [];
      const mergedKeys = checkedKeys.concat(halfCheckedKeys);
      const params = {
        roleId: drawerProps.value.rowData.roleId,
        roleName: drawerProps.value.rowData.roleName, // 解码 roleName
        roleDescription: drawerProps.value.rowData.roleDescription, // 解码 roleDescription
        menuList: mergedKeys?.join(",").split(",")
      };
      // const params = JSON.stringify(temParams);
      if (params.menuList[0] == "") {
        params.menuList = [];
      }
      loading.value = true;
      const res = await drawerProps.value.api!(params);
      if (res.status == 200) {
        ElMessage.success({ message: `${drawerProps.value.title}成功!` });
        drawerProps.value.getTableList!();
        drawerVisible.value = false;
        // 角色分配成功,刷新当前页面
        // window.location.reload();
        loading.value = false;
      } else {
        loading.value = false;
        ElMessage.error({ message: res.msg });
      }
    } catch (error) {
      loading.value = true;
      console.log(error);
    }
  });
};