Vue -- Tree 树形控件<el-tree>讲解及应用实例

一、效果展示

实验室横向课题中的一个需求,做的是一个文件上传和下载的树形控件文件。要求按照阶段和任务段展示,即第一层是阶段数,第二层是任务段数,第三层是具体的文件。在文件后面有文件上传和下载的按钮。直接上图说明。

二、树形控件

基础的树形结构:

实现代码:

el-tree :data="data" :props="defaultProps" @node-click="handleNodeClick"></el-tree>

<script>
  export default {
    data() {
      return {
        data: [{
          label: '一级 1',
          children: [{
            label: '二级 1-1',
            children: [{
              label: '三级 1-1-1'
            }]
          }]
        }, {
          label: '一级 2',
          children: [{
            label: '二级 2-1',
            children: [{
              label: '三级 2-1-1'
            }]
          }, {
            label: '二级 2-2',
            children: [{
              label: '三级 2-2-1'
            }]
          }]
        }, {
          label: '一级 3',
          children: [{
            label: '二级 3-1',
            children: [{
              label: '三级 3-1-1'
            }]
          }, {
            label: '二级 3-2',
            children: [{
              label: '三级 3-2-1'
            }]
          }]
        }],
        defaultProps: {
          children: 'children',
          label: 'label'
        }
      };
    },
    methods: {
      handleNodeClick(data) {
        console.log(data);
      }
    }
  };
</script>

我们不难发现组件比较简单,重要的对数据的处理。按照官网给出的数据格式,数据应该也是分层给出。若后端返回的数据不是处理好的树形数据,那么前端需要按照设计自己处理数据。这次我的后端也没有给我处理好分层数据,我也是自己熬夜处理好的数据,具体处理过程都在下面的代码中,必要的已经加了注释!

先看后端返回的数据吧!

后端只返回了一个一维列表,列表中每个元素包含一个文件号apqpNo、一个文件名fileName和一个状态值state…需要把这个一维列表处理好分成三层数据,那么开始处理数据!

处理过程:

(1)新建 projectFileDate = [], 存后端返回的项目的数据信息,数据信息包含据后端返回的文件编号、文件名、状态等。

新建realMap=[] ,新建realMap存取处理的数据。realMap中有6个子map,初始化为空,分别为0-5阶段的文件数据。

取到projectFileDate 中 每个元素中的文件号apqpNo,对文件号进行处理。例子:apqp值为1.2.5时,则可以取出文件号中的三个数(1 2 5),第一个数是文件树的第一层,根据第二个数和第三个数可以确定阶段数dest(根据已经建好fileMap映射)。

        const firstDigst = projectFileDate[i].apqpNo
        const fileId = firstDigst.split('.') // id: 9.2.1
        // 分割字符串 fileId = ["9", "2", "1"]
        var matrix_i = fileId[0]
        const matrix_j = fileId[1]
        const matrix_k = fileId[2]
        // 因第0阶段数据库中的主id为9,故改为0
        if (matrix_i == = '9') {
            matrix_i = '0'
        }
        // 每一个叶子文件属于一个 串联节点(映射关系定义在 .mapMatrix.js)
        const dest = matrix[matrix_i][matrix_j][matrix_k]
        if (realMap[matrix_i][dest] == = undefined) {
            // eslint-disable-next-line no-array-constructor
            realMap[matrix_i][dest] = new Array()
        }

遍历projectFileDate,把projectFileDate中的每个元素push进realMap[matrix_i][dest]中,得到的real即为处理好的文件数据。

(2)对realMap处理成分层的树的数据。

遍历realmap,在一层循环中加上两个属性值:label和chiledren[].其中label为确定的文件数据第几阶段数。

curLabel保存realMap[i]值,对curLabel进行添加两个元素,同样是label和chiledren[].其中label为确定的文件数据第几任务段数。

finalChild保存realMap[i][j]值,对finalChild进行添加三个元素,label、status和id.其中label为确定的文件名称。

各层保存完依次push进父层,最中得到已建立好的树的数据finalFileTree。

下面是具体的实现代码

<template>
  <div>
    <h2 style="margin:15px 10px 10px 16%">{{ titleContext }}</h2>
    <hr>
    <div class="treea">
      <div ref="treeDiv" class="tree-container tree-src">
        <el-tree
          id="tree"
          ref="tree"
          class="tree tree-dis"
          :indent="0"
          :data="data"
          :props="defaultProps"
          :render-content="renderContent"
          @node-click="treeNodeClick"
        >
          />
          <!--
data绑的值用来展示数据,
props用来配置选项(树的子节点,树节点文本展示,叶子节点),
@node-click节点被点击时的事件
render-content:指定渲染函数,文件上传下载在此函数操作
           -->
        </el-tree></div>
    </div>
  </div>
</template>
<script>
export default {
  props: ['currentProNo'],
  data() {
    return {
      // 当前打开的文件气泡
      preOpenCus: 'none',
      // 项目号
      projectNo: '',
      // 项目名称
      projectName: '',
      // 文件树标题
      titleContext: '',
      // 文件树数据
      direction: 'btt',
      treeFileData: [],
      changeVisible: false,
      fillFileVisible: false,
      apqpNoForTriple: '',
      infoBoardTitle: '相关信息',
      visible_popover: false,
      dialogUploadFile: false,
      peoMap: {},
      apqpNo: '',
      apqpMarkArr: '',
      data: [],
      defaultProps: {  
        // 树形控件的树形绑定对象
        children: 'children', // 设置通过chiledren树形展示节点信息
        label: 'label', // 通过label设置树形节点文本展示
        disabled: function(data, node) {
          if (data.type === 9) {
            return true
          }
        }
      },
      // 记录每层的打开节点
      preOpenNode: {
        // 打开节点的最大层数
        max: 0
      }
    }
  },
  watch: {
    
  },
  created() {
    this.getProjectDate()
  },
  methods: {
    async getProjectDate() {
      // 获取项目的项目号  后面根据项目号查询项目的数据构建项目树
      var project_no1 = this.$route.query.projectNo
      if (typeof project_no1 !== 'undefined' && project_no1 !== null && project_no1.length !== 0) {
        this.projectNo = this.$route.query.projectNo // this.$route.query.projectNo是项目列表点击继续上传按钮传的项目编号
      }
      var project_no2 = this.$store.getters.curProjectNo
      if (typeof project_no2 !== 'undefined' && project_no2 !== null && project_no2.length !== 0) {
        this.projectNo = this.$store.getters.curProjectNo // this.$store.getters.curProjectNo是从步骤二获取的项目编号
      }
      // 调用接口获取该项目需要上传的文件的数据
      // 数据包括以下部分
      /**
       *  项目名称
       *  项目文件列表[文件号fileNumber、文件名称filename、状态state]
       */
      // 项目名称为构建的文件树的标题
      // 文件号1.1.1  此文件为1阶段,判断是属于那个文件段
      // 文件树是三层结构:
      /**
       *  第1阶段          -------  1层
       *     0.1-0.2       -------  2层
       *       市场调研意见书  -------3层
       */
      if (typeof this.projectNo === 'undefined' && this.projectNo === null && this.projectNo.length === 0) {
        this.$message('没有项目编号!')
        return
      }
      await this.$http.get('/dare/document/selectFileTree?projectNo=' + this.projectNo).then(res => {
        const projectFileDate = [] // projectFileDate存项目的数据信息,数据信息包含据信息后端返回的文件编号、文件名等
        // 渲染文件树标题
        this.titleContext = '项目名:' + res.data.result.projectName + '???????' + '项目编号:' + this.projectNo

        // 项目文件的数据映射到构建的树中
        const realMap = [] // 新建数组projectFileNoList接收文件编号数据
        for (let i = 0; i < 6; i++) {
          realMap[i] = {} // realMap中有6个子map,分别为0-5阶段的文件数据
        }
        realMap[matrix_i] = {}
        // spacial case
        if (res.data.result.fileTreeDTOList.length === 0) {
          this.$message('没有需要上传的文件')
          return
        }
        for (let i = 0; i < res.data.result.fileTreeDTOList.length; i++) {
        // 为每个文件号后面添加一个文件名
          projectFileDate.push(res.data.result.fileTreeDTOList[i])
          // 按照阶段分为0-5个阶段,按照文件号的第一个字符分组
          const firstDigst = projectFileDate[i].apqpNo
          const fileId = firstDigst.split('.') // id: 9.2.1
          // 分割字符串 fileId = ["9", "2", "1"]
          var matrix_i = fileId[0]
          const matrix_j = fileId[1]
          const matrix_k = fileId[2]
          // 因第0阶段数据库中的主id为9,故改为0
          if (matrix_i === '9') {
            matrix_i = '0'
          }
          // 每一个叶子文件属于一个 串联节点(映射关系定义在 .mapMatrix.js)
          const dest = matrix[matrix_i][matrix_j][matrix_k]
          if (realMap[matrix_i][dest] === undefined) {
          // eslint-disable-next-line no-array-constructor
            realMap[matrix_i][dest] = new Array()
          }
          realMap[matrix_i][dest].push(projectFileDate[i])
        }
        // console.log(realMap) // 已经处理好的文件数据
        // 处理第0阶段
        const finalFileTree = []
        const mapStr = ['第0阶段', '第1阶段', '第2阶段', '第3阶段', '第4阶段', '第5阶段']
        for (const i in realMap) { // 遍历循环realMap 空数据直接跳过
          const curMap = realMap[i] // cur保存每个阶段的数据
          var curLabel = mapStr[i]
          const curPar = { // 阶段层的数据
            label: curLabel,
            type: '0',
            children: []
          }
          for (const j in curMap) {
            const curObj = { // 阶段下的任务段的数据
              label: stepMap[j],
              type: '0',
              children: []
            }
            for (const k in realMap[i][j]) {
              const cur = realMap[i][j][k] // cur保存阶段下的任务段的子文件数据
              const finalChild = { // cur保存阶段下的任务段的子文件数据
                label: cur.fileName,
                status: cur.state,
                id: cur.apqpNo
              }
              curObj.children.push(finalChild)
            }
            curPar.children.push(curObj)
          }
          // 一个阶段构造完成, 例如, 第一阶段, 第二阶段
          if (curPar.children.length !== 0) {
            finalFileTree.push(curPar)
          }
        }
        this.data = finalFileTree // 已建立好的树的数据
      })
    },

    // 文件树的相关操作   对文件上传和下载的操作在此函数中操作
    renderContent(h, { node, data, store }) {
        // 。。。。。。
    },

    // 树节点点击函数,一层只展开一个节点,关闭同层节点时,使其展开的子节点也关闭,若无此需求可以在标签内添加accordion手风琴属性
    async treeNodeClick(data, node, el) {
    // console.log(node.level,'层打开的是',node)
      let preExpended = true; let maxLevel = node.level
      // 只有非叶节点出发展开收起控制
      for (let lel = node.level; lel <= this.preOpenNode.max && !node.isLeaf; lel++) {
        this.preOpenNode[lel].expanded = false
        // 若有文件气泡开启,关闭此气泡,若未开启不会开启
        this.popVisiableController(this.preOpenCus)
        if (this.preOpenNode[lel].id === node.id) {
          preExpended = false
          maxLevel = node.level - 1
        }
      }
      // level层打开的是这个node,只记录非叶节点
      if (!node.isLeaf) {
        this.preOpenNode[node.level] = node
        this.preOpenNode.max = maxLevel
        node.expanded = preExpended
      }
    },

    cusVisiable() {
    },

    popVisiableController(id) {
      if (id === this.preOpenCus) {
      // console.log("这是再次点击关闭,或者来自节点点击")
        this.preOpenCus = 'none'
        this.lastOpenCus = 'none'
        return
      }
      this.lastOpenCus = this.preOpenCus
      this.preOpenCus = id
    }
  }
}
</script>
<style scoped>

    .butUpload + div {
        display: inline-block;
    }

    .clearfix:before,
    .clearfix:after{
        content: "";
        display: table;
        clear: both;
    }

    .grid-content {
        font-size: 23px;
        border-radius: 4px;
        min-height: 36px;
        font-weight: bold;
        padding-left: 20px;
        line-height: 36px;
    }

    .bg-purple {
        background: #d3dce6;
    }

   .tree /deep/ .el-tree-node {
        position: relative;
        padding-left: 16px;
    }

    .tree /deep/ .el-tree-node__children {
        padding-left: 16px;
    }

    .tree > .el-tree-node:not(:first-child) .el-tree-node__content {
        height: 90px;
    }

    .tree-src /deep/ .tree .el-tree-node .el-tree-node__content {
        height: 90px;
    }

    .tree-src /deep/ .tree-dis >.el-tree-node .el-tree-node__content {
        height: 30px;
    }

    .tree-src /deep/ .tree-dis >.el-tree-node .el-tree-node__children .el-tree-node__content {
        height: 30px;
        margin-bottom: 0px;
    }

    .tree-src /deep/ .tree-dis >.el-tree-node .el-tree-node__children .el-tree-node:not(:first-child)
    .el-tree-node__content {
        height: 55px;
    }

    .tree-src /deep/ .tree-dis >.el-tree-node .el-tree-node__children .el-tree-node__children
    .el-tree-node .el-tree-node__content {
        height: 30px;
        margin-bottom: 0px;
    }

    .tree-container {
        font-size: 16px;
    }

    .el-tree /deep/ .el-tree-node__expand-icon.expanded {
        -webkit-transform: rotate(0deg);
        transform: rotate(0deg);
    }

    /*有子节点,但是未展开*/
    .el-tree /deep/ .el-icon-caret-right:before {
        background: url("./folder.png") no-repeat 0 3px;
        content: '';
        display: block;
        width: 16px;
        height: 16px;
        font-size: 16px;
        background-size: 16px;
    }

    /*有子节点,并且已经展开*/
    .el-tree /deep/ .el-tree-node__expand-icon.expanded.el-icon-caret-right:before {
        background: url("./folder_open.png") no-repeat 0 3px;
        content: '';
        display: block;
        width: 16px;
        height: 16px;
        font-size: 16px;
        background-size: 16px;
    }

    /*没有子节点,就是一个文件*/
    .el-tree /deep/.el-tree-node__expand-icon.is-leaf::before {
        background: url("./file.png") no-repeat 0 3px;
        content: '';
        display: block;
        width: 16px;
        height: 21px;
        font-size: 16px;
        background-size: 16px;
    }

    .fillForm {
        margin-left: 99px;
        font-weight: bold;
        font-size: 14px;
    }

    .fillFormHidden {
        margin-left: 99px;
        font-weight: bold;
        font-size: 14px;
        visibility: hidden;
    }

    .menu-title {
        margin-top: 14px;
        margin-left: 5px;
        margin-bottom: 20px;
    }

    /* 右侧信息栏 */
    .right-wrapper {
        border-radius: 8px;
        box-shadow: 0 0 8px rgba(0,0,0,0.2);
        padding: 1px 10px 9px;
        margin-top: 15px;
    }

</style>

原文连接:https://blog.csdn.net/m0_67265919/article/details/123367904

相关推荐

Webpack学习系列 - Webpack5 怎么集成Babel ?

我在淘宝做弹窗,2022 年初的回顾与展望

看完这篇,你也可以搞定有趣的动态曲线绘制

低代码平台的属性面板该如何设计?

34个图片压缩工具集合,包含在线压缩和CLI工具

冴羽答读者问:过程比结果重要吗?如果是,怎么理解?如果不是,又怎么解?

中杯超大杯中间的新选择——vue2.7+vite+ts实践

LiveData源码分析

亚马逊Prime:流媒大战杀手锏

Vue详解知识概括

基于 Docker 来部署 Vue 或 React 前端项目及 Node 后端服务

完美解决自定义字体垂直方向上下偏移的问题

使用vuepress从零开始搭建我的技术文档|已部署到线上

【Vue.js 3.0源码】AST 转换之节点内部转换

小程序+电商,该如何寻找营销增长点

前端如何开始深度学习,那不妨试试JAX

你可能不知道的 前端浏览器(window) 对本地文件操作(File System Access API)

爱奇艺向抖音开启授权,打开内容价值的新大门

使用ComposeDesktop开发一款桌面端多功能APK工具

一个简洁、强大、可扩展的前端项目架构是什么样的?