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>
相关推荐
HEX9CF12 分钟前
【CTF Web】Pikachu xss之href输出 Writeup(GET请求+反射型XSS+javascript:伪协议绕过)
开发语言·前端·javascript·安全·网络安全·ecmascript·xss
凌云行者24 分钟前
使用rust写一个Web服务器——单线程版本
服务器·前端·rust
华农第一蒟蒻40 分钟前
Java中JWT(JSON Web Token)的运用
java·前端·spring boot·json·token
积水成江42 分钟前
关于Generator,async 和 await的介绍
前端·javascript·vue.js
___Dream43 分钟前
【黑马软件测试三】web功能测试、抓包
前端·功能测试
金灰43 分钟前
CSS3练习--电商web
前端·css·css3
人生の三重奏1 小时前
前端——js补充
开发语言·前端·javascript
Tandy12356_1 小时前
js逆向——webpack实战案例(一)
前端·javascript·安全·webpack
TonyH20021 小时前
webpack 4 的 30 个步骤构建 react 开发环境
前端·css·react.js·webpack·postcss·打包
你会发光哎u1 小时前
Webpack模式-Resolve-本地服务器
服务器·前端·webpack