主要包括以下组件

穿梭框组件

一、树形穿梭框组件

因为项目需求是: 穿梭框左右两边数据都是树形结构。所以ElementUI和Ant Design Vue 本身自带的穿梭框已经不再适配项目需求。所以需要使用第三方穿梭框插件el-tree-transfer

1、使用方法
  • 安装el-tree-transfer依赖
npm i -S el-tree-transfer
  • 具体使用
<template>
  <div>
    <!-- 
	openAll: 开启全部自动打开
	node_key="id" 唯一标志  没体会到有啥用
	title : 左右两边数据的名称 Array 
	:from_data : 保存左边树结构数据   必须含有pid 且如果parentId为null,则需要设置pid为0
	:to_data : 保存右边的树结构数据   必须含有pid 且如果parentId为null,则需要设置pid为0
	(pid一般是后端返回的parentId,需要前端处理,方法有:1、加属性(可参考其他博主);2、递归遍历,添加属性)
	:defaultProps="{ label: 'name' }" 设置默认属性  展示的名字的字段
	:mode="mode"  模式 mode指的是 穿梭框
	filter : 开启自带的过滤功能
	 @add-btn="add"   左边数据移向右侧
     @remove-btn="remove"   右侧数据移向左侧
     // 左右数据复选框触发事件(不包括全选框)
     // 解决过滤后全选导致的bug
     @left-check-change="onLeftChange"
     @right-check-change="onRightChange"
     --> 
    <tree-transfer 
    	node_key="id" 
    	:title="tit" 
    	ref="transfer" 
    	:from_data="fromData" 
    	:to_data="toData" 
    	:defaultProps="{ label: 'name' }"
         :mode="mode" 
         filter 
         height="487px" 
         @add-btn="add" 
         @remove-btn="remove" 
         @left-check-change="onLeftChange"
         @right-check-change="onRightChange"
         class="permission__tree-transfer">
    </tree-transfer>
  </div>
</template>
<script>
// 局部引入
import treeTransfer from 'el-tree-transfer' 
export default {
  // 注册组件
  components: {
    treeTransfer
  },
  props: {
    // 后端接口返回的左侧数据
    fromData: {
      type: Array
    },
    // 后端接口返回的右边数据
    toData: {
      type: Array
    },
    // 父组件传的穿梭框标题
    tit: {
      type: Array
    },
    // 已选用户
    selectedUsers: {
      type: Object
    },
    // 当前角色
    currentRoleType: {
      type: String
    }
  },
   watch: {
    fromData: {
      handler() {
        if (this.fromData && this.fromData.length > 0) {
          if (this.tit[0] === "可选用户") {
            this.dealUserRoleTypeTreeData(this.fromData);
          } else {
            this.dealSourceTreeData(this.fromData);
          }
        }
      },
      // immediate : true
    }
  },
  data() {
    return {
      // 树形穿梭框数据
      mode: 'transfer', // 现有树形穿梭框模式transfer 和通讯录模式addressList
      // title: ['可选资源', '已选资源'],
      selectedKeys: [],   // 选择的keys
      removedKeys: [],   // 移除的keys
      saveSelectedSourceIds: [],    // 保存已选资源的底层id

    }
  },
  methods: {
    // 当el-traee-transfer onLeftChange onRightChange 搜索过滤后选择全选进行穿梭发现把原数据都带过去了,而不是搜索出来的。 
    onLeftChange(nodeObj, treeObj) {
      this.filterNotDisabled(this.fromData);
      const treeTransfer = this.$refs.transfer.$refs['wl-transfer-component'];
      const fromTree = treeTransfer.$refs['from-tree'];
      const fromTreeCheckedKeys = fromTree.getCheckedKeys(true);
      const _fromTreeCheckedKeys = fromTreeCheckedKeys.filter(node => {
        return fromTree.getNode(node).visible;
      });
      fromTree.setCheckedKeys(_fromTreeCheckedKeys);

    },
    onRightChange(nodeObj, treeObj) {
      const treeTransfer = this.$refs.transfer.$refs['wl-transfer-component'];
      const toTree = treeTransfer.$refs['to-tree'];
      const toTreeCheckedKeys = toTree.getCheckedKeys(true);
      const _toTreeCheckedKeys = toTreeCheckedKeys.filter(node => {
        return toTree.getNode(node).visible;
      });
      toTree.setCheckedKeys(_toTreeCheckedKeys);
    },
    // 全选时  过滤出  禁止选中的节点被右移
    filterNotDisabled(nodeArr) {
      
      // 因为this.selectedKeys只会push  没有children的node   所以只需要过滤一级
      // return nodeArr.filter(item => item.disabled !== true);
      nodeArr.forEach(item=>{
        if(item.disabled && item.checked) {
          item.checked = false;
        }
      });
      console.log(nodeArr);
    },
    add(fromData, toData, obj) {
      if (this.tit[0] === "可选用户") {
        // let notDisabled = this.filterNotDisabled(obj.nodes);
        // console.log(notDisabled,'notDisabled');
        if (obj.nodes && obj.nodes.length > 0) {
          
          obj.nodes.forEach(item => {
            if (!(item.children && item.children.length > 0)) {
              this.selectedKeys.push(item.id);
            }
          })
        }
      } else {
        // 分配资源
        // harfKeys 存储所有父级
        // this.selectedKeys = [...obj.harfKeys,...obj.keys];
        this.selectedKeys.push(...obj.harfKeys, ...obj.keys);
        // this.selectedKeys = this.filterArr(this.selectedKeys);
      }

    },
    remove(fromData, toData, obj) {
      if (this.tit[0] === "可选用户") {
        if (obj.nodes && obj.nodes.length > 0) {
          obj.nodes.forEach(item => {
            if (!(item.children && item.children.length > 0)) {
              this.removedKeys.push(item.id);
            }
          })
        }
        this.dealUserTreeData(this.fromData);
      } else {
        // 分配资源
        this.removedKeys.push(...obj.keys);
        // this.removedKeys = this.filterArr(this.removedKeys);
        this.dealSourceTreeData(this.fromData);
      }
      this.$message.success("移除成功!");
      // 将之前已经选中的 移除到左边  左边禁用的 需要重新 可选
      console.log(this.removedKeys, '移除成功!', obj);
    },
    // 触发remove事件时,从右边移除到左边时,改变被操作的数据的禁用状态
    dealUserTreeData(treeData) {
      treeData && treeData.length > 0 && treeData.forEach(item => {
        if (item.children && item.children.length > 0) {
          this.dealUserTreeData(item.children);
        } else {
          // 这块逻辑必须和过滤系统、业务的  规则 区别开
          // 右边移除到左边的数据可以再次点击
          if (this.toData && this.toData.length > 0) {
            let ids = this.toData.map(toItem => toItem.id);
            // this.collectBottomIds(this.toData);
            if (ids.includes(item.id)) {
              item.disabled = true;
            } else {
              item.disabled = false;
            }
          } else {
            item.disabled = false;
          }
        }
      })
    },
    // 根据不同角色类型处理 已选角色的是否禁用状态
    dealUserRoleTypeTreeData(treeData) {
      treeData && treeData.length > 0 && treeData.forEach(item => {
        if (item.children && item.children.length > 0) {
          this.dealUserRoleTypeTreeData(item.children);
        } else {
          // 系统或者业务已选的禁用规则
          if (this.currentRoleType === "系统") {
            this.selectedUsers.SYSTEM && this.selectedUsers.SYSTEM.length > 0 && this.selectedUsers.SYSTEM.forEach(sysItem => {
              if (sysItem.sysUserId === item.id) {
                item.disabled = true;
              }
            });
            this.selectedUsers.BUSINESS && this.selectedUsers.BUSINESS.length > 0 && this.selectedUsers.BUSINESS.forEach(sysItem => {
              if (sysItem.sysUserId === item.id) {
                item.disabled = true;
              }
            });
          } else if (this.currentRoleType === "业务") {
            this.selectedUsers.SYSTEM && this.selectedUsers.SYSTEM.length > 0 && this.selectedUsers.SYSTEM.forEach(sysItem => {
              if (sysItem.sysUserId === item.id) {
                item.disabled = true;
              }
            });
          }
        }
      })
    },
    dealSourceTreeData(treeData) {
      treeData && treeData.length > 0 && treeData.forEach(item => {
        if (item.children && item.children.length > 0) {
          this.dealSourceTreeData(item.children);
        } else {
          if (this.toData && this.toData.length > 0) {
            this.saveSelectedSourceIds = [];  // 这块需要清空
            this.collectBottomIds(this.toData);
            if (this.saveSelectedSourceIds.includes(item.id)) {
              item.disabled = true;
            } else {
              item.disabled = false;
            }
          } else {
            item.disabled = false;
          }
        }
      })
    },
    // 收集已选资源底层id
    collectBottomIds(treeData) {
      treeData.forEach(item => {
        if (item.children && item.children.length > 0) {
          this.collectBottomIds(item.children);
        } else {
          this.saveSelectedSourceIds.push(item.id);
        }
      });
    }
  }
}
</script>
 
<style>
  /*处理自带的穿梭触发按钮样式  开始*/
.wl-transfer .transfer-center {
  left: 48% !important;
  width: auto !important;
}

.wl-transfer .transfer-left,
.wl-transfer .transfer-right {
  width: 44% !important;
}

.el-button.is-circle {
  border-radius: 0 !important;
  width: 25px !important;
  height: 32px !important;
  padding: 0px !important;
}

.transfer-center-item {
  width: 25px !important;
  height: 32px !important;
  text-align: center;
  line-height: 32px;
  padding: 0px !important;
}
  /*处理自带的穿梭触发按钮样式  结束*/


/* 取消穿梭框的 全选复选框  */
/* .permission__tree-transfer.wl-transfer .transfer-title .el-checkbox { 
  display: none;
} */
</style>
  • 注意事项

1、左右两侧的数据必须添加属性值和parentId属性值一样的pid属性,否则渲染失败;且如果parentId为null,则需要设置pid为0
2、过滤后,全选操作的bug处理(上面代码可见)
3、点击全选后,是可以选中前端处理禁用的数据的,所以这块再次禁用的,不可选中的数据需要后端接口过滤掉。(这块有过同样问题的兄弟可以一起探讨下。其实也可以改源码,我没找到~)

  • 递归处理接口数据,添加pid
dealTreeData(treeData) {
   treeData && treeData.length > 0 && treeData.forEach(item=>{
        if(item.children && item.children.length > 0) {
            if(item.parentId === null || item.parentId === "null") {
                item.pid = 0;
            } else {
                item.pid = item.parentId;
            }
            this.dealTreeData(item.children);
        } else {
            if(item.parentId === null || item.parentId === "null") {
                item.pid = 0;
            } else {
                item.pid = item.parentId;
            }
        }
    })
},

树组件

一、改变树组件自带的展开收缩图标

1、效果图如下

在这里插入图片描述

2、代码如下

/* // 树形列表  */
.el-tree .el-tree-node__expand-icon.expanded {
  -webkit-transform: rotate(0deg);
  transform: rotate(0deg);
}
/* //有子节点 且未展开  */
.el-tree .el-icon-caret-right:before {
  /* background: url("./assets/img/add-icon.png") no-repeat 0 3px; */
  content: "+";
  display: block;
  width: 18px;
  height: 18px;
  font-size: 16px;
  background-size: 16px;
  border-radius: 50%;
  border: 1px solid #999;
  display: flex;
  justify-content: center;
  align-items: center;
  font-weight: 700;
}

/* //有子节点 且已展开  */
.el-tree .el-tree-node__expand-icon.expanded.el-icon-caret-right:before {
  /* background: url("./assets/img/jian-icon.png") no-repeat 0 3px; */
  content: "-";
  display: block;
  width: 18px;
  height: 18px;
  font-size: 16px;
  background-size: 16px;
  border-radius: 50%;
  border: 1px solid #999;
  display: flex;
  justify-content: center;
  align-items: center;
  font-weight: 700;
  /* color: #1890FF; */
}

/* //没有子节点 */
.el-tree .el-tree-node__expand-icon.is-leaf::before {
  background: transparent no-repeat 0 3px;
  content: "";
  display: block;
  width: 0px;
  height: 0px;
  font-size: 0px;
  background-size: 0px;
  border: none;
  /* margin-left: 16px; */
}

二、树组件不同层级的不同样式展示

1、效果图如下

在这里插入图片描述

2、代码如下
<el-tree node-key="id" ref="geoFileTree" :data="fileTreeData" :props="defaultProps"
                        class="file_tree">
	<div class="file_tree-node" slot-scope="{ node, data }" style="width: 100%;">
	<div v-if="node.level === 1 || node.level === 2" style="display: flex;align-items: center;">
	   <img src="../../assets/img/geoProjectPreview/first_level.png" alt="">
	   <span style="color: #595959;font-weight: 700;margin-left:6px;">{{ node.label }}</span>
	</div>
	<div class="file-item" v-if="node.level !== 1 && node.level !== 2"
	   style="display: flex;align-items: center;justify-content: space-between;">
	   <div v-if="data.children">
	       <img src="../../assets/img/geoProjectPreview/folder.png" alt="">
	       <span style="margin-left:6px;">{{ node.label }}</span>
	   </div>
	   <div v-if="data.coverUrl" @click="newWindowOpen(data.coverUrl)" class="file-name">
	       <img v-if="!data.coverUrl.includes('.JPG')"
	           src="../../assets/img/geoProjectPreview/file_icon.png" alt="">
	       <img v-if="data.coverUrl.includes('.JPG')"
	           src="../../assets/img/geoProjectPreview/img_icon.png" alt="">
	       <span style="margin-left:6px;">{{ node.label }}</span>
	   </div>
	   <div v-if="node.level === 3 || data.coverUrl"
	       style="flex: 1;border-bottom: 1px dashed #C4C4C4;margin: 0px 32px;"></div>
	   <div v-if="node.level === 3">{{ data.createTime }}</div>
	   <div @click="checkFileInfo(data)" class="file-info" v-if="data.coverUrl">详情</div>
	</div>
	</div>
</el-tree>

三、通过点击外部按钮,实现树结构的不同层级或者所有层级的收缩和展开

1、效果图如下

在这里插入图片描述

2、实现代码如下
//遍历树的所有子节点,展开的时候给子节点展开状态赋值true,折叠时候赋值false
buildData(level = 0) {
    this.$nextTick(() => {
         // geoFileTree : 绑定到树组件上的ref值
        const nodeArr = this.$refs.geoFileTree.store._getAllNodes();
        for (let i = 0; i < nodeArr.length; i++) {
            if (level === 0) {
                // 全部折叠
                nodeArr[i].expanded = false;
            } else if (level === "all") {
                // 全部展开
                nodeArr[i].expanded = true;
            } else if (level === "1") {
                // 第一级展开
                nodeArr[i].expanded = nodeArr[i].level === 1;
            } else if (level === "2") {
                // 第二级展开
                nodeArr[i].expanded = nodeArr[i].level === 1 || nodeArr[i].level === 2;
            }
        }
    });
},
GitHub 加速计划 / eleme / element
54.06 K
14.63 K
下载
A Vue.js 2.0 UI Toolkit for Web
最近提交(Master分支:3 个月前 )
c345bb45 7 个月前
a07f3a59 * Update transition.md * Update table.md * Update transition.md * Update table.md * Update transition.md * Update table.md * Update table.md * Update transition.md * Update popover.md 7 个月前
Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐