树形选择变成表格树选择

vue 专栏收录该内容
2 篇文章 0 订阅

需求:在接口不改动的情况下由树形结构改成表格树结构,如下图

原图
在这里插入图片描述
调整后
在这里插入图片描述
1.这里使用的是antdesign框架,首先将a-tree改为a-table

<!-- <a-tree checkable v-model="checkedKeys" :treeData="menuTree" @check="checkItem"></a-tree> -->
     <a-table :columns="columns" :data-source="data" :pagination="false"></a-table>

2.定义table中的columns

// 这里除了第一列是菜单名称以外,其余都是checkbox,checkbox采用插槽的方式实现
/**
   * slots里面定义的是头部插槽名字,头部也有一个多选框
   * scopedSlots定义的是当前列插槽的名字
   * 
   */
columns: [
        {
          title: '授权菜单',
          dataIndex: 'title',
          key: 'title' 
        },
        {
          dataIndex: 'entry',
          key: 'entry',
          align:'center',
          slots: { title: 'customEntry' },
          scopedSlots: { customRender: 'checkBoxEntry' }
        },
        {
          dataIndex: 'edit',
          align:'center',
          key: 'edit',
          slots: { title: 'customEdit' },
          scopedSlots: { customRender: 'checkBoxEdit' }
        },
        {
          dataIndex: 'approval',
          align:'center',
          key: 'approval',
          slots: { title: 'customApproval' },
          scopedSlots: { customRender: 'checkBoxApproval' }
        },
        {
          dataIndex: 'query',
          align:'center',
          key: 'query',
          slots: { title: 'customQuery' },
          scopedSlots: { customRender: 'checkBoxQuery' }
        }
      ],

此时在table中加入checkbox插槽

// 例如头部
<a-checkbox slot="customEntry">录入</a-checkbox>
<a-checkbox slot="customEdit">编辑</a-checkbox>
<a-checkbox slot="customApproval">审核</a-checkbox>
<a-checkbox slot="customQuery">查询</a-checkbox>
// 每一列同理
<a-checkbox slot="checkBoxEntry" slot-scope="text,item"></a-checkbox>

3.修改后端返回数据data,由于之前是根据树形结构显示,后端在返回数据之前已经很友好的把数据封装成树形结构,示例如下:
(其中a10001、a10002、a10003、a10004分别表示录入、编辑、审核、查询权限)

[{                                                            //一级菜单
      "title": "计划管理",                                       
      "children": [{                                          // 授权模块
        "menuCode": "15",
        "title": "设备需求计划",                              
        "children": [{                                       // 模块可分配的权限
            "authority_button_name": "录入",
            "authority_button_code": "a10001",
            "menu_id": "15"
          },
          {
            "authority_button_name": "编辑",
            "authority_button_code": "a10002",
            "menu_id": "15"
          },
          {
            "authority_button_name": "审核",
            "authority_button_code": "a10003",
            "menu_id": "15"
          },
          {
            "authority_button_name": "查询",
            "authority_button_code": "a10004",
            "menu_id": "15"
          }
        ],
      }],
      "menuCode": "14"
    },
    {
      "title": "设备进场管理",
      "children": [{
          "title": "设备进场验收管理",
          "children": [{
            "authority_button_name": "录入",
            "authority_button_code": "a10001",
            "menu_id": "33"
          }],
          "menuCode": "33"
        },
        {
          "title": "水电管理",
          "children": [{                                              //  二级菜单
            "title": "水电抄表记录单",
            "children": [{
                "authority_button_name": "录入",
                "authority_button_code": "a10001",
                "menu_id": "40"
              },
              {
                "authority_button_name": "编辑",
                "authority_button_code": "a10002",
                "menu_id": "40"
              },
              {
                "authority_button_name": "审核",
                "authority_button_code": "a10003",
                "menu_id": "40"
              },
              {
                "authority_button_name": "查询",
                "authority_button_code": "a10004",
                "menu_id": "40"
              }
            ],
            "menuCode": "40"
          }],
          "menuCode": "112"
        }
      ],
      "menuCode": "38"
    },
  ],

由于树形结构的数据与表格树的数据基本类型,因此只需要把最里面一层children(模块可分配的权限)拿出来即可。

处理完以后的数据结构如下:

  1. authButtonGroup保存该模块下可选择的权限,即原始数据中的children中的数据,删除该children字段
  2. authButton保存该用户在该模块下已经存在的权限
[{                                                            //一级菜单
      "title": "计划管理",                                       
      "children": [{                                          // 授权模块
        "menuCode": "15",
        "title": "设备需求计划",   
        authButtonGroup: ['a10001','a10002','a10003','a10004'],     // 授权模块可选的权限
        authButton: ['a10001']                                      // 该用户目前已经拥有的权限
      }],
      "menuCode": "14"
    },
    {
      "title": "设备进场管理",
      "children": [{
          "title": "设备进场验收管理",
          authButtonGroup: ['a10001'],
          authButton: ['a10001'],
          "menuCode": "33"          
        },
        {
          "title": "水电管理",
          "children": [{                                              //  二级菜单
            "title": "水电抄表记录单",
            authButtonGroup: ['a10001','a10002','a10003','a10004'],
            authButton: ['a10001']                           
            "menuCode": "40"
          }],
          "menuCode": "112"
        }
      ],
      "menuCode": "38"
    },
  ],

处理数据的方法如下:

      let promises = [获取用户权限的接口,获取权限树的接口];
      Promise.all(promises).then(result=>{
        // 对用户权限做处理,用授权模块的menu_code作为key存入对象,以便下面使用
        let authButton = result[0].responseList;
        let authButtonMap = {};
        Array.isArray(authButton) && authButton.forEach(item=>{
          authButtonMap[item.menu_code] = item.authority_button_codes ? item.authority_button_codes.split(',') : [];
        })
        // 获取到权限树
        let data = [...result[1].responseList];
        // 定义一个方法对树进行处理
        function dealTree(item){
          let child = item.children;
         // 子节点不是菜单就是权限按钮,一般子节点下都只存在一种类型,非1即2
          child.forEach(inner=>{
            // 通过title判断,没有的就是模块可选的权限
            if(inner.title){
              dealTree(inner)
            }else{
              // 是模块可选的权限了,存起来
              !item.authButtonGroup && (item.authButtonGroup = []);
              item.authButtonGroup.push(inner.authority_button_code);
            }
          })
          if(item.authButtonGroup){
            item.authButton = authButtonMap[item.menuCode] || [];      //  存储用户在当前模块已有的权限
            delete item.children;  // 删除children
          }
          return item;
        }
        this.data = data.map(item=>{
          return dealTree(item);
        });

此时数据结构就变成了

菜单
	授权模块(包含模块可选的权限)
	菜单
		授权模块(包含模块可选的权限)

下面给checkbox绑定属性(点击事件、默认选中、菜单的半选状态)

先根据用户存在的权限默认选中:

// 如何当前授权模块中的authButton有a10001说明该用户存在这个权限,设置选中
<a-checkbox slot="checkBoxEntry" slot-scope="text,item" :checked="item.authButton.includes('a10001')"></a-checkbox>

分析:
1.当该节点是菜单时,不存在authButton这个数组
2.checkbox是否显示应该根据authButtonGroup这个数组中是否有值,即该模块可以被授什么权

进行如下改造:

<template slot="checkBoxEntry" slot-scope="text,item">
 //  该节点存在authButtonGroup,即授权模块
 <template v-if="item.authButtonGroup">
     //  判断该模块是否可以授权a10001,即authButtonGroup是否存在,有就显示checkbox,没有就不显示
    <a-checkbox v-if="item.authButtonGroup.includes('a10001')" :checked="item.authButton.includes('a10001')"></a-checkbox>
    <span v-else></span>
  </template>
  //  菜单是否显示checkbox,
  //  菜单显示checkbox的用途是用于给下面所有模块的全选或者全取消功能
  //  所有应该判断下面的模块是否存在checkbox,这里的判读条件不够全面,下面再说
  <a-checkbox v-else-if="item.children.some(inner => inner.authButtonGroup.includes('a10001'))" ></a-checkbox>
  <span v-else></span>
</template>

绑定点击事件
(由于每个checkbox设置的选中状态都是通过该模块的authButton数组是否有a10001、a10002、a10003、a10004。因此原理就是在该授权模块的authButton将指定的authCode加入或者删除)

<template slot="checkBoxEntry" slot-scope="text,item">
 <template v-if="item.authButtonGroup">
    <a-checkbox
     @change="onChangeRecord($event,item,'a10001')" 
     v-if="item.authButtonGroup.includes('a10001')" 
     :checked="item.authButton.includes('a10001')"></a-checkbox>
    <span v-else></span>
  </template>
  <a-checkbox
   @change="onChangeRecordMenu($event,item,'a10001')"
   v-else-if="item.children.some(inner => inner.authButtonGroup.includes('a10001'))"
   ></a-checkbox>
  <span v-else></span>
</template>

/**   授权模块的点击事件  **/
onChangeRecord(event,record,authCode){
  this.compareDleteOrAdd(record.authButton,authCode,true,true);  // 之前是选中点击即删除,之前未选中点击即添加
  this.$forceUpdate();  // 由于绑定的都是方法执行结果,手动触发视图更新
},
// 在数组中增加或者减少一项
compareDleteOrAdd(arr,item,isAdd,isDel){
  let index = arr.indexOf(item)
  if(index > -1){
    if(isDel){
      arr.splice(index,1);
    }
  }else{
    if(isAdd){
      arr.push(item);
    }
  }
  return index;
},
/**   菜单的点击事件  **/
onChangeRecordMenu(event,record,authCode){
  // 先获取当前点击按钮是选中还是取消,选中即菜单下所有模块全部选中,相反取消则全部取消
  let flag = event.target.checked;
  // 获取菜单下面的授权模块或者下级菜单
  let children = record.children;
  let stack = [];
  // 循环遍历,将所有的子模块都添加或者删除authCode
  while(children){
    children.forEach(item=>{
      // 是授权模块则直接删除或添加
      if(item.authButtonGroup){
        if(item.authButtonGroup.includes(authCode)){
          if(flag){
            this.compareDleteOrAdd(item.authButton,authCode,true,false);
          }else{
            this.compareDleteOrAdd(item.authButton,authCode,false,true);
          }
        }
      }else{
        // 如果是菜单,定义一个stack栈存入,下级菜单中的授权模块也需要处理
        stack.push(item);
      }
    })
    //  将stack栈中的菜单从第一个推出,再循环(主要是存在多级菜单的原因)
    let next = stack.shift();
    children = next ? next.children : '';
  }
  this.$forceUpdate();   // 手动触发视图更新
},

设置菜单的选中、半选状态

<template slot="checkBoxEntry" slot-scope="text,item">
 <template v-if="item.authButtonGroup">
    <a-checkbox
     @change="onChangeRecord($event,item,'a10001')" 
     v-if="item.authButtonGroup.includes('a10001')" 
     :checked="item.authButton.includes('a10001')"></a-checkbox>
    <span v-else></span>
  </template>
  <a-checkbox
   @change="onChangeRecordMenu($event,item,'a10001')"
   v-else-if="item.children.some(inner => inner.authButtonGroup.includes('a10001'))"
   :checked="checkAllSelect(item,'a10001')"
   :indeterminate="indeterminateCheck(item,'a10001') && !checkAllSelect(item,'a10001')" 
   ></a-checkbox>
  <span v-else></span>
</template>

/**   菜单的全选状态checkAllSelect返回true即全选  **/
// 即只需找到一个授权模块没有选中即返回false,找不到返回true
checkAllSelect(item,authCode){
  // 找到一个不满足
  let flag = true;
  let children = item.children;
  if(!item || !item.children){
    return false;
  }
  let stack = [];
  while(children){
    children.some(item=>{
      if(item.authButtonGroup){
        if(item.authButtonGroup.includes(authCode)){
          flag = item.authButton.includes(authCode);
          return !flag;
        }
      }else{
        stack.push(item);
        return false;
      }
    })
    // 找到了不向下执行
    if(!flag){
      break;
    }
    let next = stack.shift();
    children = next ? next.children : '';
  }
  return flag;
},

/**   菜单的半选状态设置checkout的indeterminate属性为true,即半选**/
//  半选其实就是至少有一个选中并且不是全部选中
// :indeterminate="indeterminateCheck(item,'a10001') && !checkAllSelect(item,'a10001')" 
// 找到至少一个选中
indeterminateCheck(item,authCode){
  // 至少有一个选中
  let flag = false;
  let children = item.children;
  let stack = [];
  while(children){
    children.some(item=>{
      if(item.authButtonGroup){
        if(item.authButtonGroup.includes(authCode)){
          flag = item.authButton.includes(authCode);
          return flag;
        }
      }else{
        stack.push(item);
        return false;
      }
    })
    // 找到了有一个
    if(flag){
      break;
    }
    let next = stack.shift();
    children = next ? next.children : '';
  }
  return flag;
},

完成以上操作基本上菜单的全选、取消、半选状态,授权模块的选择取消都已经处理完了

处理顶部的全选按钮(其实方法都是执行一样的,只是传参数的时候将这个表格数据data当初children传人即可)

<a-checkbox @change="onChangeAll($event,'a10001')" 
 :checked="checkAllSelect({children:data},'a10001')"
 :indeterminate="indeterminateCheck({children:data},'a10001') && !checkAllSelect({children:data},'a10001')"
 slot="customEntry">
   录入
 </a-checkbox>

/**  点击事件 **/
onChangeAll(event,authCode){
  this.onChangeRecordMenu(event,{children:this.data},authCode);
},

完整代码如下:

<template>
  <div class="permission-auth-model">
        <a-table :columns="columns" :data-source="data" :pagination="false">
          <a-checkbox @change="onChangeAll($event,'a10001')" 
          :checked="checkAllSelect({children:data},'a10001')"
          :indeterminate="indeterminateCheck({children:data},'a10001') && !checkAllSelect({children:data},'a10001')"
          slot="customEntry">
            录入
          </a-checkbox>
          <a-checkbox @change="onChangeAll($event,'a10002')" 
          :checked="checkAllSelect({children:data},'a10002')"
          :indeterminate="indeterminateCheck({children:data},'a10002') && !checkAllSelect({children:data},'a10002')"
          slot="customEdit">
            编辑
          </a-checkbox>
          <a-checkbox @change="onChangeAll($event,'a10003')"
          :checked="checkAllSelect({children:data},'a10003')"
          :indeterminate="indeterminateCheck({children:data},'a10003') && !checkAllSelect({children:data},'a10003')" 
          slot="customApproval">
            审核
          </a-checkbox>
          <a-checkbox @change="onChangeAll($event,'a10004')"
          :checked="checkAllSelect({children:data},'a10004')"
          :indeterminate="indeterminateCheck({children:data},'a10004') && !checkAllSelect({children:data},'a10004')"
          slot="customQuery">
            查询
          </a-checkbox>
          
          <template slot="checkBoxEntry" slot-scope="text,item">
            <template v-if="item.authButtonGroup">
              <a-checkbox @change="onChangeRecord($event,item,'a10001')" v-if="item.authButtonGroup.includes('a10001')" :checked="item.authButton.includes('a10001')"></a-checkbox>
              <span v-else></span>
            </template>
            <a-checkbox @change="onChangeRecordMenu($event,item,'a10001')" v-else-if="item.children.some(inner => inner.authButtonGroup.includes('a10001'))" 
            :checked="checkAllSelect(item,'a10001')"
            :indeterminate="indeterminateCheck(item,'a10001') && !checkAllSelect(item,'a10001')"
            ></a-checkbox>
            <span v-else></span>
          </template>
          
          <template slot="checkBoxEdit" slot-scope="text,item">
            <template v-if="item.authButtonGroup">
              <a-checkbox @change="onChangeRecord($event,item,'a10002')" v-if="item.authButtonGroup.includes('a10002')" :checked="item.authButton.includes('a10002')"></a-checkbox>
              <span v-else></span>
            </template>
            <a-checkbox @change="onChangeRecordMenu($event,item,'a10002')" v-else-if="item.children.some(inner => inner.authButtonGroup.includes('a10002'))" 
            :checked="checkAllSelect(item,'a10002')"
            :indeterminate="indeterminateCheck(item,'a10002') && !checkAllSelect(item,'a10002')"
            ></a-checkbox>
            <span v-else></span>
          </template>

          <template slot="checkBoxApproval" slot-scope="text,item">
            <template v-if="item.authButtonGroup">
              <a-checkbox @change="onChangeRecord($event,item,'a10003')" v-if="item.authButtonGroup.includes('a10003')" :checked="item.authButton.includes('a10003')"></a-checkbox>
              <span v-else></span>
            </template>
            <a-checkbox @change="onChangeRecordMenu($event,item,'a10003')" v-else-if="item.children.some(inner => inner.authButtonGroup.includes('a10003'))" 
            :checked="checkAllSelect(item,'a10003')"
            :indeterminate="indeterminateCheck(item,'a10003') && !checkAllSelect(item,'a10003')"
            ></a-checkbox>
            <span v-else></span>
          </template>

          <template slot="checkBoxQuery" slot-scope="text,item">
            <template v-if="item.authButtonGroup">
              <a-checkbox @change="onChangeRecord($event,item,'a10004')" v-if="item.authButtonGroup.includes('a10004')" :checked="item.authButton.includes('a10004')"></a-checkbox>
              <span v-else></span>
            </template>
            <a-checkbox @change="onChangeRecordMenu($event,item,'a10004')" v-else-if="item.children.some(inner => inner.authButtonGroup.includes('a10004'))" 
            :checked="checkAllSelect(item,'a10004')"
            :indeterminate="indeterminateCheck(item,'a10004') && !checkAllSelect(item,'a10004')"
            ></a-checkbox>
            <span v-else></span>
          </template>
        </a-table>
  </div>
</template>

<script>
export default {
  name: 'EnumModal',
  data() {
    return {
      columns: [
        {
          title: '授权菜单',
          dataIndex: 'title',
          key: 'title' 
        },
        {
          dataIndex: 'entry',
          key: 'entry',
          align:'center',
          slots: { title: 'customEntry' },
          scopedSlots: { customRender: 'checkBoxEntry' }
        },
        {
          dataIndex: 'edit',
          align:'center',
          key: 'edit',
          slots: { title: 'customEdit' },
          scopedSlots: { customRender: 'checkBoxEdit' }
        },
        {
          dataIndex: 'approval',
          align:'center',
          key: 'approval',
          slots: { title: 'customApproval' },
          scopedSlots: { customRender: 'checkBoxApproval' }
        },
        {
          dataIndex: 'query',
          align:'center',
          key: 'query',
          slots: { title: 'customQuery' },
          scopedSlots: { customRender: 'checkBoxQuery' }
        }
      ],
      data: []
    }
  },
  created(){
  	this.getTableData();
  },
  methods: {
    // 校验一个是否存在menucode或者子节点存在
    checkAllSelect(item,authCode){
      // 找到一个不满足
      let flag = true;
      let children = item.children;
      if(!item || !item.children){
        return false;
      }
      let stack = [];
      while(children){
        children.some(item=>{
          if(item.authButtonGroup){
            if(item.authButtonGroup.includes(authCode)){
              flag = item.authButton.includes(authCode);
              return !flag;
            }
          }else{
            stack.push(item);
            return false;
          }
        })
        // 找到了不向下执行
        if(!flag){
          break;
        }
        let next = stack.shift();
        children = next ? next.children : '';
      }
      return flag;
    },
    // 校验是否是半选状态
    indeterminateCheck(item,authCode){
      // 至少有一个选中
      let flag = false;
      let children = item.children;
      let stack = [];
      while(children){
        children.some(item=>{
          if(item.authButtonGroup){
            if(item.authButtonGroup.includes(authCode)){
              flag = item.authButton.includes(authCode);
              return flag;
            }
          }else{
            stack.push(item);
            return false;
          }
        })
        // 找到了有一个
        if(flag){
          break;
        }
        let next = stack.shift();
        children = next ? next.children : '';
      }
      return flag;

    },
    onChangeAll(event,authCode){
      let data = this.data.filter(item=>{
        return item.menuCode != 82;
      })
      let flag = this.checkAllSelect({children:this.data},authCode);
      event.target.checked = !flag;
      this.onChangeRecordMenu(event,{children:data},authCode);
    },
    onChangeRecordMenu(event,record,authCode){
      let flag = event.target.checked;
      let children = record.children;
      let stack = [];
      while(children){
        console.log(children);
        children.forEach(item=>{
          if(item.authButtonGroup){
            if(item.authButtonGroup.includes(authCode)){
              if(flag){
                this.compareDleteOrAdd(item.authButton,authCode,true,false);
              }else{
                this.compareDleteOrAdd(item.authButton,authCode,false,true);
              }
            }
          }else{
            stack.push(item);
          }
        })
        let next = stack.shift();
        children = next ? next.children : '';
      }
      this.$forceUpdate();
    },
    onChangeRecord(event,record,authCode){
      this.compareDleteOrAdd(record.authButton,authCode,true,true);
      this.$forceUpdate();
    },
    // 在数组中增加或者减少一项
    compareDleteOrAdd(arr,item,isAdd,isDel){
      let index = arr.indexOf(item)
      if(index > -1){
        if(isDel){
          arr.splice(index,1);
        }
      }else{
        if(isAdd){
          arr.push(item);
        }
      }
      return index;
    },
    getTableData() {
      let promises = [获取人员权限,获取表格树];
      Promise.all(promises).then(result=>{
        let authButton = result[0].responseList;
        let authButtonMap = {};
        Array.isArray(authButton) && authButton.forEach(item=>{
          authButtonMap[item.menu_code] = item.authority_button_codes ? item.authority_button_codes.split(',') : [];
        })
        let data = [...result[1].responseList];
        function dealTree(item){
          let child = item.children;
          if(Array.isArray(child)){
            if(child.length == 0){
              item.authButtonGroup = ['a10004'];
            }else {
              child.forEach(inner=>{
                if(inner.title){
                  dealTree(inner)
                }else{
                  !item.authButtonGroup && (item.authButtonGroup = []);
                  item.authButtonGroup.push(inner.authority_button_code);
                }
              })
            }
          }
          if(item.authButtonGroup){
            item.authButton = authButtonMap[item.menuCode] || [];
            delete item.children;
          }
          return item;
        }
        this.data = data.map(item=>{
          return dealTree(item);
        });
    },
}
</script>
  • 3
    点赞
  • 2
    评论
  • 3
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

©️2021 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值