vue tree封装一个可选的树组件方式

目录
  • 组件实现的基本功能
  • 先看效果图

组件实现的基本功能

1,根据后端返回的数据格式,传入组件动态的渲染出当前角色有哪些权限(新建,修改)

2,适配有2级和只有一级多选的数据

3,有全选(√) ,全不选 ,部分已选(-)的3装状态,每一级都支持(用的iview2次封装)

4,改变之后返回当前选中的所有权限的id,用于提交

5,手风琴效果,小屏适配

先看效果图

有部分权限没打开

打开

小屏

权限数据结构,select_status=1表示选中

默认添加没有权限的初始数据结构

有些数据只有一级子菜单,有些有两级

核心代码.vue

<template>
  <div class="powerList">
    <div v-for="(item,index) in powers" :key="item.id" :class="{item:true,active:item.active}">
      <Checkbox :indeterminate="item.indeterminate" :value="item.val1" @click.prevent.native="handleCheckAll(index)">
        <span style="color:#1C213A;font-weight:600;">&nbsp;{{item.name}}</span>
      </Checkbox>
      <div style="font-size:30px;float:right;width:220px;cursor: pointer;color:#BEC1C8;text-align:right;" @click="open(item.active,index)">
        <Icon :type="item.active? 'md-arrow-dropup': 'md-arrow-dropdown'" />
      </div>
      <!-- 全部都是只有一级子菜单的 -->
      <CheckboxGroup v-if="oneChild(item.child)" v-model="item.checked" @on-change="checkGroupChange($event,index)">
        <Checkbox v-for="item2 in item.child" :key='item2.id' :label="item2.id">{{item2.name}}</Checkbox>
      </CheckboxGroup>
      <template v-else>
        <div v-for='(item3,index3) in item.child' :key="item3.id" class="item3">
          <!-- 有二级子菜单的 -->
          <template v-if="item3.child">
            <Checkbox :indeterminate="item3.indeterminate" :value="item3.val1" @click.prevent.native="level2CheckAll(index,index3)">&nbsp;{{item3.name}}</Checkbox>
            <CheckboxGroup v-model="item3.checked" @on-change='checkGroupChange($event,index,index3)'>
              <Checkbox v-for="item4 in item3.child" :key='item4.id' :label="item4.id">{{item4.name}}</Checkbox>
            </CheckboxGroup>
          </template>
        </div>
      </template>
    </div>
  </div>
</template>
<script>
/**
 *公共的权限选择组件,接受后端返回的权限列表powerData
 *返回选中的权限的id的arr @change接受
  */
export default {
  props: {
    powerData: Array
  },
  data () {
    return {
      powers: []
    }
  },
  watch: {
    powerData: {
      handler (val, old) {
        if (val && val.length && JSON.stringify(val) !== JSON.stringify(old)) {
          this.powers = this.formatData(val)
          this.save([...this.powers])
        }
      },
      immediate: true,
      deep: true
    }
  },
  methods: {
    // 保存返回选中的项的数组
    save (arr) {
      let auth_id = []
      arr.forEach(item => {
        if (item.indeterminate || item.val1) {
          auth_id.push(item.id)
          if (this.oneChild(item.child)) {
            auth_id = auth_id.concat([...item.checked])
          } else {
            item.child.forEach(item2 => {
              if (item2.indeterminate || item2.val1) {
                auth_id.push(item2.id)
                auth_id = auth_id.concat([...item2.checked])
              }
            })
          }
        }
      })
      console.log(auth_id)
      this.$emit('change', auth_id)
    },
    //把获取到的权限格式化成需要的格式
    /**
     * active :dom开关
     * indeterminate: 勾选状态为-的样式
     * val1 全选的状态
     * total 有多级时子菜单的总数
     * checked 二级多选当前选中的值
      */
    formatData (arr) {
      return arr.map(item => {
        item.active = false //用于开关下拉
        if (!this.oneChild(item.child)) { // 有多级子菜单
          let total = 0 //计算当前项下的底级子菜单的总数 用于给主一级全选相应的状态
          item.child.map(item => item.child.length).forEach(item => {
            total += item
          })
          item.total = total
          item.child = item.child.map(item2 => {
            item2.checked = item2.child.filter(item3 => item3.select_status === 1).map(item4 => item4.id)
            if (item2.checked.length === item2.child.length) { //选中的子选项数量等于子选项数量 就是全选
              item2.indeterminate = false
              item2.val1 = true
            } else if (item2.checked.length === 0) { // 没有选中的
              item2.indeterminate = false
              item2.val1 = false
            } else { //<
              item2.indeterminate = true
              item2.val1 = false
            }
            return item2
          })
          this.changeLevel1Status(item) // 设置一级全选的状态
        } else { // 只有一级子菜单的
          item.checked = item.child.filter(item => item.select_status === 1).map(item => item.id) //子菜单当前选中的值
          let childNum = item.child.length
          let checkedNum = item.child.filter(item5 => item5.select_status === 1).length
          if (checkedNum === 0) {
            item.indeterminate = false
            item.val1 = false
          } else if (checkedNum < childNum) {
            item.indeterminate = true
            item.val1 = false
          } else {
            item.indeterminate = false
            item.val1 = true
          }
        }
        return item
      })
    },
    // 权限展开,手风琴
    open (val, index) {
      // 关闭所有的
      this.powers = this.powers.map(item => {
        item.active = false
        return item
      })
      let obj = this.powers[index]
      obj.active = !val
      // 打开点击的
      this.powers.splice(index, 1, obj)
    },
    // 只有一级子菜单的
    oneChild (arr) {
      return arr.every(item => item.child === undefined)
    },
    /**
    * 主菜单的全选
    * @param index (索引)
    */
    handleCheckAll (index) {
      if (this.oneChild(this.powers[index].child)) { //只有一级子菜单的
        let powers = [...this.powers]
        let obj = powers[index]
        if (obj.indeterminate) { //当前项有子菜单选中的
          obj.val1 = false;
        } else {
          obj.val1 = !obj.val1
        }
        obj.indeterminate = false;
        if (obj.val1) { // 如果是全选
          obj.checked = obj.child.map(item => item.id);
        } else {
          obj.checked = [];
        }
        this.powers = [...powers]
        this.save([...this.powers])
      } else {
        this.checkAllOther(index)
      }
    },
    // 有多级子菜单的全选
    checkAllOther (index) {
      let powers = [...this.powers]
      if (powers[index].indeterminate) { //当前项有子菜单选中的
        powers[index].val1 = false;
      } else {
        powers[index].val1 = !powers[index].val1
      }
      powers[index].indeterminate = false;
      if (powers[index].val1) { // 如果是全选
        powers[index].child.forEach(item => {
          item.val1 = true // 所有2级全选选中
          item.indeterminate = false //状态去掉
          item.checked = item.child.map(item2 => item2.id) //2级全选的子菜单都选中
        })
      } else {
        powers[index].child.forEach(item => {
          item.val1 = false
          item.checked = [];
          item.indeterminate = false //状态去掉
        })
      }
      this.powers = [...powers]
      this.save([...this.powers])
    },
    // 子菜单的checkGrounp改变,data选中的项的id构成的arr
    checkGroupChange (data, index, index3) {
      let powers = [...this.powers]
      let obj = index3 === undefined ? powers[index] : powers[index].child[index3]// 有index3就是二级子菜单的checkGroup
      if (data.length === obj.child.length) { //全部勾选
        obj.indeterminate = false;
        obj.val1 = true;
      } else if (data.length > 0) { //有选中的
        obj.indeterminate = true;
        obj.val1 = false;
      } else { //没选中的
        obj.indeterminate = false;
        obj.val1 = false;
      }
      // 有二级子菜单的项改变的时候
      if (index3 !== undefined) {
        this.changeLevel1Status(powers[index])
      }
      this.powers = [...powers]
      this.save([...this.powers])
    },
    // 2级的全选
    level2CheckAll (index, index3) {
      let powers = [...this.powers]
      let obj = powers[index].child[index3] //当前操作的2级全选
      if (obj.indeterminate) { //当前项有子菜单选中的
        obj.val1 = false;
      } else {
        obj.val1 = !obj.val1
      }
      obj.indeterminate = false;
      if (obj.val1) { // 如果是全选
        obj.checked = obj.child.map(item => item.id);
      } else {
        obj.checked = [];
      }
      this.changeLevel1Status(powers[index])
      this.powers = [...powers]
      this.save([...this.powers])
    },
    // 二级子菜单或2级全选时更改一级全选的状态
    changeLevel1Status (obj) {
      let checkedNum = 0
      obj.child.map(item => item.checked.length).forEach(item => {
        checkedNum += item
      })
      if (checkedNum === 0) {
        obj.indeterminate = false
        obj.val1 = false
      } else if (checkedNum < obj.total) {
        obj.indeterminate = true
        obj.val1 = false
      } else {
        obj.indeterminate = false
        obj.val1 = true
      }
    }
  },
}
</script>
<style lang="scss" scoped>
.powerList {
  display: flex;
  justify-content: space-between;
  flex-wrap: wrap;
  .item {
    width: 370px;
    height: 58px;
    background: #f9f9f9;
    line-height: 58px;
    margin-bottom: 20px;
    padding: 0 10px 0 20px;
    overflow: hidden;
    &.active {
      height: auto;
    }
    /deep/ .ivu-checkbox-group label {
      display: block;
    }
    .item3 {
      /deep/ .ivu-checkbox-group {
        padding-left: 20px;
        label {
          display: inline-block;
        }
      }
    }
  }
}
</style>

核心代码主要就是数据的处理,底级的更改都触发上一级的状态和值得改变

用的时候主要就是一个props传入数据, 一个方法返回给父组件选中的值

<power :powerData='powers' @change="powerChange" />

以上为个人经验,希望能给大家一个参考,也希望大家多多支持我们。

(0)

相关推荐

  • Vue组件tree实现树形菜单

    vue 编写的树形菜单,小巧实用,支持vue1.0,vue2.0 v1.0 功能: 1.支持多级树目录 2.支持高亮点击的节点 3.支持展开点击节点 4.支持点击收缩节点时收缩所有子目录 5.支持自定义回调函数,点击节点时回调,参数为节点信息 用法:<launch-tree :list='list' :options='options'></launch-tree> list = [ { name: '一级目录', // 目录名字 isOpen: true, // 是否初始展开目录

  • 如何实现vue的tree组件

    前言 Tree一直是大家熟知的组件,做一些大型的后台管理系统都会用到.使用树组件可以完整的展现其中的层级关系,并具有展开收起选择等交互功能. 效果 节点可以无限的递归延伸 可以展开和收起子节点 如果子节点全部选择对应的父节点也应该选中,反之父节点取消选中对应子节点也需要取消选中 API prop传递data属性,来描述所有的节点的信息 每个节点的配置描述如下 title: 展示的标题 expand 是否展开节点 checked 是否选中节点 children 子节点 以及还有两个event on

  • Vue使用zTree插件封装树组件操作示例

    本文实例讲述了Vue使用zTree插件封装树组件操作.分享给大家供大家参考,具体如下: 1.通过npm安装jquery npm install jquery --save-dev 2.在build/webpack.base.conf文件当中引入jquery module.exports = { ... resolve: { extensions: ['.js', '.vue', '.json'], alias: { 'vue$': 'vue/dist/vue.esm.js', '@': reso

  • 使用Vue实现一个树组件的示例

    HTML代码: <!DOCTYPE html> <html> <head> <title>Vue Demo</title> <meta charset="utf-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content

  • vue tree封装一个可选的树组件方式

    目录 组件实现的基本功能 先看效果图 组件实现的基本功能 1,根据后端返回的数据格式,传入组件动态的渲染出当前角色有哪些权限(新建,修改) 2,适配有2级和只有一级多选的数据 3,有全选(√) ,全不选 ,部分已选(-)的3装状态,每一级都支持(用的iview2次封装) 4,改变之后返回当前选中的所有权限的id,用于提交 5,手风琴效果,小屏适配 先看效果图 有部分权限没打开 打开 小屏 权限数据结构,select_status=1表示选中 默认添加没有权限的初始数据结构 有些数据只有一级子菜单

  • Vue实现封装一个切片上传组件

    目录 组件效果 使用文档 封装过程 1. 文件切片 2. 构造切片请求参数 3. 控制分片请求的并发 完整代码 待完善 组件效果 单文件切片上传 多文件切片上传 组件使用案例 <template> <div id="app"> <div class="upload-wrap"> <UploadSlice :action="uploadInfoSlice.actionChunk" :headers=&quo

  • vue封装一个图案手势锁组件

    目录 说在前面 效果展示 预览地址 实现步骤 1.组件设计 2.组件分析 3.组件实现 4.组件使用 组件库引用 源码地址 组件文档 说在前面 现在很多人都喜欢使用图案手势锁,这里我使用vue来封装了一个可以直接使用的组件,在这里记录一下这个组件的开发步骤. 效果展示 组件实现效果如下图: 预览地址 http://jyeontu.xyz/jvuewheel/#/JAppsLock 实现步骤 完成一个组件需要几步? 1.组件设计 首先我们应该要知道我们要做怎样的组件,具备怎样的功能,这样才可以开始

  • vue+elementUI封装一个根据后端变化的动态table(完整代码)

    实现了自动生成和插槽两个方式,主要把 el-table 和el-pagination封装在一起 效果图: 使用组件,启用自动生成 :auto="true" 自动生成-编辑 (包括请求已经实现了)新增和删除也是一样 ps:如有额外的按钮可以用插槽实现 查询的时候,只需要多返回下面数据,就可以自动生成列,和对应操作按钮 目录 table.vue <template> <div> <el-row v-if="auto"> <el-

  • 封装一个最简单ErrorBoundary组件处理react异常

    前言 从 React 16 开始,引入了 Error Boundaries 概念,它可以捕获它的子组件中产生的错误,记录错误日志,并展示降级内容,具体 官网地址 错误边界避免一个组件错误导致整个页面白屏不能使用等情况,使用优雅降级的方式呈现备用的 UI,错误边界可以在渲染期间.生命周期和整个组件树的构造函数中捕获错误.自 React 16 起,任何未被错误边界捕获的错误将会导致整个 React 组件树被卸载 ErrorBoundary 意义 某些 UI 崩溃,不至于整个 webapp 崩溃 在浏

  • vue关于this.$refs.tabs.refreshs()刷新组件方式

    目录 this.$refs.tabs.refreshs()刷新组件 第一种:当前组件刷新 第二种:刷新父组件时 第三种:非关系组件 vue组件重新加载(刷新) 如何刷新当前组件 this.$refs.tabs.refreshs()刷新组件 当更改了用户信息后,需要刷新页面或者组件. 第一种:当前组件刷新 定义一个请求用户信息的方法,在需要时调用: sessionStorage.setItem('userInfo',JSON.stringify(this.userInfo)); //从sessio

  • React教程之封装一个Portal可复用组件的方法

    Portal简介 所以我们需要的一个通用组件,它做如下的事情: 可以声明式的写在一个组件中 并不真正render在被声明的地方 支持过渡动画 那么,像modal.tooltip.notification等组件都是可以基于这个组件的.我们叫这个组件为Portal. 使用了React16+的你,对Portal至少有所了解或者熟练使用. Portal可以创建一个在你的root元素之外的DOM. 1.通常你的网站只有一个root <body> <div id="root"&g

  • 基于vue-upload-component封装一个图片上传组件的示例

    需求分析 业务要求,需要一个图片上传控件,需满足 多图上传 点击预览 图片前端压缩 支持初始化数据 相关功能及资源分析 基本功能 先到https://www.npmjs.com/search?q=vue+upload上搜索有关上传的控件,没有完全满足需求的组件,过滤后找到 vue-upload-component 组件,功能基本都有,自定义也比较灵活,就以以此进行二次开发. 预览 因为项目是基于 vant 做的,本身就提供了 ImagePreview 的预览组件,使用起来也简单,如果业务需求需要

  • vue中使用elementui实现树组件tree右键增删改功能

    功能描述: 1.右击节点可进行增删改 2.可对节点数据进行模糊查询 3.右击第一级节点可以进行同级节点增加 4.双击节点或点击修改节点 都可以对节点获取焦点并进行修改,回车修改成功 5.可对节点进行拖拽,实现节点移动功能 效果图: 完整代码: <template> <div class="lalala tree-container"> <el-input placeholder="输入关键字进行过滤" v-model="fil

  • vue element-ui之怎么封装一个自己的组件的详解

    为什么要进行组件封装? 封装的目的就是为了能够更加便捷.快速的进行业务功能的开发.组件(component)是vue的最强大功能之一,组件可以实现一些类似功能的复用及与其它业务逻辑的解耦.在开发中,我们难免会写很多类似的.重复的代码,有时候两个业务模块有相似的功能,采用复制粘贴已经很省事,但如果涉及的字段或有一些小差别,你也会觉得很烦,毕竟你要从头到尾瞅着去改动.这时候如果把那些相同的功能,抽象出来抽离成组件,通过组件引用方式就会显得格外省事了. Vue中怎么封装一个自己的组件 想要封装好一个组

随机推荐