el-tree 的各项骚操作

要求!!!

1. 区分两组数据,展示不同颜色

2. 给其中一组数据新增按钮 并且这个按钮可以控制下级菜单全选/反选 点击按钮时需要展开当前节点

3. 其中一组数据不采用竖型,用横行展示

实现逻辑!!

  1. 后端给到我的本身就是两组不同的数据,我需要把这两组数据合并成一组,才能在一个el-tree 中展示,一组数据是菜单一组数据是组件,组件属于菜单的子集,通过组件里面的menuId和当前的菜单id做比较,相同则合并到对应菜单的children中

  2. 同时在这两组数据合并之前,我需要遍历递归,给每组数据新增一个属性type ,菜单的新增type=menu,组件新增type为comp,方便在合并之后区分谁是谁

  3. 这里展示不同的颜色,我则只需要判断当前这个type为什么,

  4. 新增按钮,这里的按钮是只要是菜单就都有按钮,那么同样的,我在渲染的时候,button中做一个判断,v-if 是否包含type为menu

  5. 按钮控制下级菜单全选和反选就复杂很多this.$refs.tree.setChecked(id, this.checked, true) ,我用的它自身包含的属性,设置当前节点是否选中,this.checked 是布尔值,如果为true表示选中,false 表示不选中,但是这里会遇到问题?? 没有及时更新

  6. 针对没有及时更新的问题 ,我再次查找了文档,getCheckedNodes,getHalfCheckedNodes 找到了这两个api,是获取到当前选中的id的,在点击全选反选按钮之后,再次调这个函数,就可以解决问题了

  7. 其中组件这组数据,它不要按照原版的方式处理,要改成横行展示 ,这里需要获取到节点的dom ,然后找到父元素去修改样式,会遇到一个问题,无法及时更新获取到dom ,这个问题我是采用的api 展开当前节点,handleNodeClick 然后再this.$nextTick 之后再调用方法,这样就解决这个问题了

  8. 点击按钮的时候需要展开当前节点 ,这个我找了api,没有可以直接用的,于是百度, this.$refs.tree.store.nodesMap[id].expanded = true,这个方法可行

初版

完善之后

具体代码如下

分类介绍:

1. 这个是具体的树结构

data="processedMenuOptions" 是整体的数据

:props="defaultMenuProps" 展示对应的字段

@check="handleCheck" 选中的事件

@node-expand="handleNodeClick" 点击点击的事件,展开收起节点

js 复制代码
<el-form-item label="菜单组件权限" prop="components">
  <el-tree class="tree-border" :data="processedMenuOptions" show-checkbox ref="tree" node-key="id"
    empty-text="加载中,请稍候" :props="defaultMenuProps" @check="handleCheck" @node-expand="handleNodeClick">
    <template v-slot="{ node, data }">
      <span :style="getNodeStyle(node, data)" :class="data.type == 'comp' ? 'levelname' : ''">
        {{ data.menuTitle }}
        <el-button type="primary" size="mini"
          style="position: absolute; right:12px;margin-top: 8px;height: 26px;"
          v-if="data.type == 'menu'" @click.stop="toggleSubMenuSelection(data)"
          :disabled="!data.isButton">
          全选/反选</el-button>
      </span>
    </template>
  </el-tree>
</el-form-item>

2.事件实现具体代码

js 复制代码
<script>
import { deepClone } from '@/util/validate'

export default {
  name: "Role",
  data() {
    return {
      // 表单参数
      form: {},
      defaultMenuProps: {
        children: "children",
        label: "menuTitle"
      },
      //是否选中
      checked: false
    };
  },
  created() {
    this.getList();
    this.getMenuTreeselect()
    this.getDeptTreeselect()

  },
  mounted() {
    var levelName = document.getElementsByClassName('levelname');
    if (levelName.length > 0) {
      this.changeCss(); // 初次加载时调用一次
    }
  },
  methods: {
    // 默认选中情况
    handleCheck(data, { checkedKeys }) {
      this.$refs.tree.getCheckedKeys()
      this.getMenuAllCheckedKeys()
    },
    // 展开当前节点
    handleNodeClick() {
      this.$nextTick(() => {
        this.changeCss()
      })
    },
    // 全选反选
    toggleSubMenuSelection(data) {
      this.handleNodeClick()
      let id = data.id
      let node = this.$refs.tree.getNode(id);
      if (node) {
        this.checked = !this.hasCheckedDescendants(node);
        // 展开节点
        // 设置当前节点
        this.$refs.tree.setCurrentKey(id);
        // 获取当前节点属性
        this.$refs.tree.store.nodesMap[id]
        this.$refs.tree.store.nodesMap[id].expanded = true
        this.handleNodeClick()
      }
      this.$refs.tree.setChecked(id, this.checked, true);
      this.$refs.tree.setChecked(id, true);
      // 反转选中状态并设置节点的选中状态(仅需调用一次)
      this.checked = !this.checked
      this.getMenuAllCheckedKeys();
      
    },

    // 检查节点及其子孙节点是否有选中的菜单或组件
    hasCheckedDescendants(node) {
      if (node.childNodes && node.childNodes.length > 0) {
        for (let childNode of node.childNodes) {
          if (childNode.checked) {
            return true;
          }
          if (this.hasCheckedDescendants(childNode)) {
            return true;
          }
        }
      }
      return false;
    },

    // 区分菜单和组件颜色
    getNodeStyle(node, data) {
      const nodeStyle = {
        color: data.type === 'comp' ? '#666' : '#333'
      };
      return nodeStyle;
    },
    changeCss() {
      // levelname是上面的最底层节点的名字
      var levelName = document.getElementsByClassName('levelname');
      // console.log(levelName, 'levelName');
      if (levelName.length > 0) {
        for (var i = 0; i < levelName.length; i++) {
          levelName[i].parentNode.style.cssFloat = 'left';
          levelName[i].parentNode.style.styleFloat = 'left';
          levelName[i].parentNode.style.paddingLeft = '0px'
          levelName[i].parentNode.style.paddingRight = '10px'
          levelName[i].parentNode.style.marginLeft = '54px'
        }
      }

    },
    /** 查询菜单树和组件树结构 */
    getMenuTreeselect() {
      listComponent().then(res => {
        this.menuOptions = res.data.menus.map(menu => ({
          ...menu,
          type: 'menu'
        }));;
        this.componentsOptions = res.data.components.map(component => ({
          ...component,
          menuTitle: component.componentName,
          type: 'comp',
          id: 'comp_' + component.id, // 添加前缀
        }))

        const menu = { id: 'root', menuTitle: '主类目', children: [...deepClone(this.menuOptions)], type: 'menu' };
        // 创建新的数组来存储处理后的菜单数据,不修改原始数据
        this.processedMenuOptions = deepClone([menu])
        // 递归遍历菜单树,将匹配的组件数据作为子集添加到菜单中
        for (let menu of this.processedMenuOptions) {
          this.updateComponents(menu);
        }
        console.log(this.processedMenuOptions, '处理后的菜单数据');
      })
    },
    // 递归函数,用于处理多层嵌套的菜单树
    updateComponents(menu) {
      const children = menu.children;
      menu.type = 'menu'; // 设置当前节点的类型为菜单
      if (children && children.length > 0) {
        for (let child of children) {
          this.updateComponents(child);
        }
      } else {
        const menuId = menu.id;
        const matchedComponents = this.componentsOptions.filter(component => component.menuId === menuId);
        menu.children = matchedComponents;
      }
    },
    // 所有节点数据
    getMenuAllCheckedKeys() {
      let checkedMenuKeys = [];
      let checkedCompKeys = [];
      for (let node of this.$refs.tree.getCheckedNodes()) {
        if (node.type === 'menu') {
          checkedMenuKeys.push(node.id);
        } else if (node.type === 'comp') {
          checkedCompKeys.push(node.id);
        }
      }
      for (let node of this.$refs.tree.getHalfCheckedNodes()) {
        if (node.type === 'menu') {
          checkedMenuKeys.push(node.id);
        }
      }
      // 递归更新 isButton 属性
      const updateIsButton = (node) => {
        if (checkedMenuKeys.includes(node.id)) {
          this.$set(node, 'isButton', true);
        } else {
          this.$delete(node, 'isButton');
        }
        if (node.children) {
          node.children.forEach(child => {
            updateIsButton(child);
          });
        }
      };
      this.processedMenuOptions.forEach(node => {
        updateIsButton(node);
      });
      return {
        menuKeys: checkedMenuKeys,
        compKeys: checkedCompKeys
      };
    },
    // 取消按钮
    cancel() {
      this.open = false;
      this.reset();
    },
    // 取消按钮(数据权限)
    cancelDataScope() {
      this.reset();
    },
    // 表单重置
    reset() {
      if (this.$refs.tree != undefined) {
        this.$refs.tree.setCheckedKeys([]);
      }
      this.form = {
        id: undefined,
        roleName: undefined,
        roleType: undefined,
        deptId: undefined,
        allowEdit: 0,
        menus: [],
        components: [],
        remark: undefined
      };
      this.resetForm("form");
    },
    /** 搜索按钮操作 */
    handleQuery() {
      this.queryParams.pageNum = 1;
      this.getList();
    },
    /** 重置按钮操作 */
    resetQuery() {
      this.dateRange = [];
      this.resetForm("queryForm");
      this.handleQuery();
    },
    /** 新增按钮操作 */
    handleAdd() {
      this.reset();
      this.getMenuTreeselect();
      this.open = true;
      this.title = "添加角色";
    },
    /** 编辑按钮操作 */
    handleUpdate(row) {
      this.reset();
      this.getMenuTreeselect();
      const roleId = row.id;
      getRole({ id: roleId }).then(res => {
        this.open = true;
        this.form = res.data;
        this.title = "修改角色";
        // 在数据加载完成后再设置选中节点
        this.$nextTick(() => {
          if (this.form.menus && this.form.components) {
            this.setCheckedNodes(this.$refs.tree, this.form.menus, 'menu');
            // 对 components 数据中的 id 添加 'comp_' 前缀
            const processedComponents = this.form.components.map(componentId => 'comp_' + componentId);
            this.setCheckedNodes(this.$refs.tree, processedComponents, 'comp');
          }
        });
      });
    },
    setCheckedNodes(ref, nodeIds, nodeType) {
      if (ref && nodeIds && nodeIds.length > 0) {
        this.$nextTick(() => {
          nodeIds.forEach(nodeId => {
            this.setCheckedNode(ref, nodeId, nodeType);
          });
        });
      }
    },
    setCheckedNode(ref, nodeId, nodeType) {
      this.$nextTick(() => {
        const node = ref.getNode(nodeId, nodeType);
        if (node) {
          // 回显
          // 修改节点数据对象中的 isButton 字段
          ref.setChecked(nodeId, true, false, nodeType);
          this.getMenuAllCheckedKeys()  //要放在最后,否则容易出现数据未加载就执行问题
        }
      });
    },
    /** 提交按钮 */
    submitForm: function () {
      this.$refs["form"].validate(valid => {
        if (valid) {
          let menuData = this.getMenuAllCheckedKeys();
          // 过滤掉名为 "root" 的菜单项
          this.form.menus = menuData.menuKeys.filter(menuKey => menuKey !== 'root');
          this.form.components = menuData.compKeys.map(item => {
            return parseInt(item.split('_')[1]);
          });
          if (this.form.id != undefined) {
            updateRole(this.form).then(response => {
              this.$modal.msgSuccess("修改成功");
              this.open = false;
              this.getList();
            });
          } else {
            addRole(this.form).then(response => {
              this.$modal.msgSuccess("新增成功");
              this.open = false;
              this.getList();
            });
          }
        }
      });
    },
    /** 删除按钮操作 */
    handleDelete(row) {
      const roleIds = row.id
      this.$modal.confirm('是否确认删除角色编号为"' + roleIds + '"的数据项?').then(function () {
        return delRole({ id: roleIds });
      }).then(() => {
        this.getList();
        this.$modal.msgSuccess("删除成功");
      }).catch(() => { });
    },
  }
};
</script>
相关推荐
崔庆才丨静觅5 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60616 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了6 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅6 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅7 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅7 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment7 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅7 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊7 小时前
jwt介绍
前端
爱敲代码的小鱼8 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax