组件化页面:封装el-tree

栏目: JavaScript · 发布时间: 4年前

内容简介:一直在开发平台类的项目。上一个项目开发的时候,依旧是每个页面上复制粘贴,尽管页面上的写法规范都定义了,但依旧让人难受,于是开始把那些常用的功能封装成为可复用的组件,偷懒和易于维护是我想要的。本文主要讲的是封装element-ui的tree组件,通过讲其内置的属性方法暴露出来,以及加上我们自定义的属性和方法,实现一个可以在不同场景下复用的组件,因为目前只用在了个人业务上,所以或许需求不一定能满足大家,但希望这个思路能对大家有所帮助很有必要在聊组件之前,先将设计的字段贴出来,在后面会根据使用依次说明设计字段的用

一直在开发平台类的项目。上一个项目开发的时候,依旧是每个页面上复制粘贴,尽管页面上的写法规范都定义了,但依旧让人难受,于是开始把那些常用的功能封装成为可复用的组件,偷懒和易于维护是我想要的。

写在前面

本文主要讲的是封装element-ui的tree组件,通过讲其内置的属性方法暴露出来,以及加上我们自定义的属性和方法,实现一个可以在不同场景下复用的组件,因为目前只用在了个人业务上,所以或许需求不一定能满足大家,但希望这个思路能对大家有所帮助

设计组件

自定义字段

很有必要在聊组件之前,先将设计的字段贴出来,在后面会根据使用依次说明设计字段的用处。

自定义属性

  • initTree // Boolean 树是否完成初始化加载,未完成为false
  • loadType // Number 加载方式 1: 正常通过接口加载 2: 懒加载 3: 传入数据
  • refresh // Number 用来刷新的字段
  • defaultClickedAsyc // Number | String 默认点击(对树删除编辑添加时的临时存储,在树刷新后赋值这些数据的)
  • defaultHighLightAsyc // Number | String 默认高亮(对树删除编辑添加时的临时存储,在树刷新后赋值这些数据的)
  • defaultExpandedAsyc // Array 默认展开(对树删除编辑添加时的临时存储,在树刷新后赋值这些数据的)
  • treeData // Array 树渲染数据(非懒加载时由外部渲染)
  • baseData // Array 树的基础数据,从组件中获取到
  • loadInfo // Object 正常加载相关配置
  • lazyInfo // Object 懒加载相关配置
  • leftClickData // Object 左键点击数据
  • rightClickData // Object 右键点击数据
  • rightMenuList // Array 右键菜单配置

展开 loadInfo 字段

loadType 为1时,需要配置 loadInfo 字段。

loadInfo: {
    key: 'id', // 节点唯一标识字段
    label: 'name', // 节点显示字段
    api: getAllApi, // 获取数据的接口
    params: { data: [{ key: 'type', value: 1 }], type: 'query' },
    resFieldList: ['content'] // 数据所在字段
}

api是要去请求的接口

params是接口获取数据的参数,类型分为url和query,url表示跟随在路径后面的参数,例如/:id,只有一个,query则表示key和value类型的参数,可以为多个

resFieldList是数据响应成功之后所在的字段,比如数据在res.content.data,则需要传入['content', 'data'], 在组件内部会通过拼接得到对应的数据

key是获取到的数据的唯一标识字段,如果是懒加载,组件内部会对这个key在做处理以保证key的唯一性

label是获取到的数据在树中要显示的字段名称

展开 lazyInfo 字段

loadType 为2时,需要配置 lazyInfo 字段。

lazyInfo: [
          {
            key: 'id', // 节点唯一标识字段
            label: 'name', // 节点显示字段
            type: 1, // 数据类型
            api: getAllApi, // 获取数据的接口
            params: { key: 'pid', value: 1, type: 'url' }, // 获取数据的参数
            resFieldList: ['content'] // 数据所在字段
          },
          {
            key: 'id',
            label: 'name',
            type: 2,
            api: getAllApi,
            params: { key: 'pid', value: '', type: 'url' },
            resFieldList: ['content'], // 数据所在字段
            leaf: true
          }
        ]

和正常通过接口加载不一样,懒加载因为节点的不确定性以及其自身的灵活,所以加载配置为一个数组,数组的每一项对应着加载节点的配置,这边单独拿出一项来进行分析。

api是要去请求的接口

params是接口获取数据的参数,类型分为url和query,url表示跟随在路径后面的参数,例如/:id,只有一个,query则表示key和value类型的参数,可以为多个,懒加载的key和value和普通加载是不同的,参数是从上一个接口返回数据中得到的

resFieldList是数据响应成功之后所在的字段,比如数据在res.content.data,则需要传入['content', 'data'], 在组件内部会通过拼接得到对应的数据

key是获取到的数据的唯一标识字段,如果是懒加载,组件内部会对这个key在做处理以保证key的唯一性

label是获取到的数据在树中要显示的字段名称

type是懒加载中需要设置的,我们对当前接口返回的数据设置为什么类型,标识,易于页面上去做一些逻辑操作

leaf是设置为当前加载的节点为叶子节点

el-tree自带属性

  • nodeKey // Number | String 树的node-key

    • defaultClicked // Object 默认点击 (设置为对象,保证数据能被监听到)
    • defaultHighLight // Number | String 高亮节点
    • defaultExpanded // Array 默认展开
    • clickNode // Boolean 是否手风琴点击
    • expandAll // Boolean 是否展开全部
    • checkBox // Boolean 是否有选中框
    • checkStrictly // Boolean 在有复选框的时候,是否·遵循父子不互关联
    • draggable // Boolean 是否可拖拽
    • ........

组件原来自带的属性这边不一一列举,因为是组件的二次封装,所以原来自带的属性首先我们是需要将它们暴露出去的,或者经过自己的加工后暴露出去。

设计加载方式

根据日常使用,树加载一般分为两种方式, 1. 传入数据 2. 懒加载 , 个人是又将方式又重新定义了一下,分为三种, 1. 正常通过接口加载 2. 懒加载 3. 传入数据 , 下面会通过参数设计和使用具体来分析这三种加载方式。

通过正常接口加载

当设置loadType为1时,则表示为正常接口加载,配置loadInfo。

加载代码完整如下:

// 正常通过接口加载
    initData () {
      // 非正常加载
      if (this.loadType !== 1) return
      // 加载loading
      this.treeLoading = true
      const treeProps = this.treeProps
      const loadInfo = this.loadInfo
      const params = loadInfo.params || {}
      let data
      if (params.type === 'url') {
        data = params.value
      } else if (params.type === 'query') {
        data = {}
        params.data.forEach(item => {
          data[item.key] = item.value
        })
      } else {
        // console.log('没有传参数类型')
      }
      loadInfo.api(data).then(res => {
        let arr = []
        if (res.success) {
          let resData = res
          const resFieldList = loadInfo.resFieldList
          // 得到定义的响应成功的数据字段
          for (let i = 0; i < resFieldList.length; i++) {
            resData = resData[resFieldList[i]]
          }
          // 数据处理
          arr = JSON.parse(JSON.stringify(resData))
          arr.forEach(item => {
            // 保证刷新之后key的唯一
            item.key = item[loadInfo.key]
            item[treeProps.label] = item[loadInfo.label]
          })
          // 得到数据后把数据给到父级,方便父级用到
          this.$emit('update:baseData', arr)
          // 设置默认高亮
          if (this.defaultHighLight || this.defaultHighLight === 0) {
            this.$nextTick(() => {
              this.$refs.TreeComponent.setCurrentKey(this.defaultHighLight)
            })
          }
          // 设置默认点击
          if ((this.defaultClicked && (this.defaultClicked.id || this.defaultClicked.id === 0))) {
            // 页面初始化,设置默认点击项, 并将点击事件派发到父级
            this.$emit('handleEvent', 'leftClick', { data: this.getSelectData(loadInfo.key, this.baseData, this.defaultClicked.id) || {}})
          }
        } else {
          this.$message({
            showClose: true,
            message: res.message,
            type: res.success ? 'success' : 'error',
            duration: 2000
          })
        }
        // 加载loading
        this.treeLoading = false
      }).catch(() => {
        // 加载loading
        this.treeLoading = false
      })
    }

懒加载

当设置loadType为2时,则表示为懒加载,配置lazyInfo。

组件内部方法获取到相关配置,然后调用接口以及配置得到对应的数据,从而可以实现第二个加载类型,通过懒加载加载树状数据。

加载完整代码如下:

// 懒加载数据
    handleLoadNode (node, resolve) {
      // 非懒加载
      if (this.loadType !== 2) return
      // 加载loading
      if (node.level === 0) {
        this.treeLoading = true
      }
      // 存下每个懒加载的数据
      this.$set(this.nodeInfoList, 'node' + node.level, { node, resolve })
      // 懒加载延迟时间
      const timeStamp = 100
      const treeProps = this.treeProps
      const levelInfo = this.lazyInfo[node.level]
      const params = levelInfo.params; let data
      if (params.type === 'url') {
        data = this.refreshLevel > 0 ? node.data[levelInfo.key] : params.value || params.value === 0 ? params.value : node.data[levelInfo.key]
      } else if (params.type === 'query') {
        params.data.forEach(item => {
          data = {}
          data[item.key] = item.default || node.data[item.value]
        })
      } else {
        console.log('没有传参数类型')
      }
      levelInfo.api(data).then(res => {
        let arr = []
        if (res.success) {
          let resData = res
          const resFieldList = levelInfo.resFieldList
          // 得到定义的响应成功的数据字段
          for (let i = 0; i < resFieldList.length; i++) {
            resData = resData[resFieldList[i]]
          }
          // 数据处理
          arr = JSON.parse(JSON.stringify(resData))
          arr.forEach(item => {
            // 保证key的唯一
            item.key = levelInfo.type + item[levelInfo.key]
            item['level' + node.level + 'data'] = node.data
            item[treeProps.label] = item[levelInfo.label]
            item.type = levelInfo.type
            item[treeProps.isLeaf] = levelInfo.leaf
          })
          // 得到数据后把数据给到父级,方便父级用到
          this.$emit('update:baseData', [...this.baseData, ...arr])
          // 设置默认高亮
          if (this.defaultHighLight || this.defaultHighLight === 0) {
            this.$nextTick(() => {
              this.$refs.TreeComponent.setCurrentKey(this.defaultHighLight)
            })
          }
          // 设置默认点击
          if ((this.defaultClicked && (this.defaultClicked.id || this.defaultClicked.id === 0))) {
            // 页面初始化,设置默认点击项, 并将点击事件派发到父级
            this.$emit('handleEvent', 'leftClick', { data: this.getSelectData(levelInfo.key, this.baseData, this.defaultClicked.id) || {}})
          }
        } else {
          this.$message({
            showClose: true,
            message: res.message,
            type: res.success ? 'success' : 'error',
            duration: 2000
          })
        }
        // 延迟加载,保证加载动画
        setTimeout(() => {
          resolve(arr)
        }, timeStamp)
        // 加载loading
        if (node.level === 0) {
          this.treeLoading = false
        }
      }).catch(() => {
        // 延迟加载,保证加载动画
        setTimeout(() => {
          resolve([])
        }, timeStamp)
        // 加载loading
        if (node.level === 0) {
          this.treeLoading = false
        }
      })
    }

传入数据

传入数据就是简单将树结构数据传入组件中,这个为第三种方式的加载。

设计右键菜单

树组件的右键菜单,可以减少页面上多余的按钮,通过右键交互确实省了很多事,所以这个功能是必须要封装的。

分析问题

  1. 右键点击,有两种类型,一种是点击在树上,一种是点击在树节点上,需要怎么处理?
  2. 右键点击菜单如何生成,当点击菜单在页面底部的时候看不见了怎么处理?
  3. 如何设置动态的右键菜单,根据当前不同的节点,不同的数据,生成自定义的菜单?

解决问题

  1. 分别为树和节点设置右键,分别处理
  2. 右键菜单生成,需要获取到当前点击的位置,然后在当前点击位置生成菜单即可,点击的时候根据菜单长度判断到浏览器底部的距离,来判断生成菜单是向下展开还是向上展开的即可。
  3. 设置动态菜单需要组件和外部页面配置,这个单独讲

动态的右键菜单

在不同的业务下,不同的接口下,不同的数据结构,点击节点如何生成对应的右键菜单,这里来分析一波。

从右键点击到菜单点击到事件完成,一共分成了五个步骤:

  1. 右键点击树或者树节点,将点击的信息派发到父级页面
  2. 父级页面根据当前点击的数据,生成对应的菜单数据结构,传入到组件
  3. 组件内部接收到菜单数据,循环出对应的dom显示出来
  4. 点击组件生成的对应的右键菜单,组件内部将当前点击的菜单选项派发给父级页面
  5. 父级页面根据右键菜单的类型完成对应要执行的逻辑

一个流程下来,才算是一个完整的右键菜单生成过程。

结论,我们只需要在树组件内部做右键点击的派发和右键菜单的点击派发事件,即可完成动态的右键菜单。

最后

呃呃,本来想完整的把一个二次封装el-tree详细分享以下的,但觉得完整的分析还是过于麻烦,因为设计的字段和功能还是有些多,要详细的把设计的字段和相关用意说清楚还是比较麻烦,这边就主要希望能把思路给到大家,帮助大家在日常开发中能够更加高效率的完成工作。

代码地址

示例地址


以上所述就是小编给大家介绍的《组件化页面:封装el-tree》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

伟大创意的诞生

伟大创意的诞生

史蒂文·约翰逊 (Steven Johnson) / 盛杨燕 / 浙江人民出版社 / 2014-8-1 / CNY 52.90

 大家都认得出好创意。印刷机、铅笔、抽水马桶、电池、互联网、GPS、Google——这些都是绝妙的创意。然而,它们是如何产生的?被喻为“科技界的达尔文”的作者,在《伟大创意的诞生》一书中,提供了深具启示意义以及有论证实据的答案。  作者史蒂文•约翰逊以富有感染力、包罗万象的风格游历于多重领域,从神经生物学、都市研究,到网络文化,找出了独特创新背后的7大关键模式,深入人类600年重要发明的......一起来看看 《伟大创意的诞生》 这本书的介绍吧!

随机密码生成器
随机密码生成器

多种字符组合密码

HTML 编码/解码
HTML 编码/解码

HTML 编码/解码

HEX CMYK 转换工具
HEX CMYK 转换工具

HEX CMYK 互转工具