当前位置:   article > 正文

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

vue-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>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54

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

先看后端返回的数据吧!

后端只返回了一个一维列表,列表中每个元素包含一个文件号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()
        }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

遍历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>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 204
  • 205
  • 206
  • 207
  • 208
  • 209
  • 210
  • 211
  • 212
  • 213
  • 214
  • 215
  • 216
  • 217
  • 218
  • 219
  • 220
  • 221
  • 222
  • 223
  • 224
  • 225
  • 226
  • 227
  • 228
  • 229
  • 230
  • 231
  • 232
  • 233
  • 234
  • 235
  • 236
  • 237
  • 238
  • 239
  • 240
  • 241
  • 242
  • 243
  • 244
  • 245
  • 246
  • 247
  • 248
  • 249
  • 250
  • 251
  • 252
  • 253
  • 254
  • 255
  • 256
  • 257
  • 258
  • 259
  • 260
  • 261
  • 262
  • 263
  • 264
  • 265
  • 266
  • 267
  • 268
  • 269
  • 270
  • 271
  • 272
  • 273
  • 274
  • 275
  • 276
  • 277
  • 278
  • 279
  • 280
  • 281
  • 282
  • 283
  • 284
  • 285
  • 286
  • 287
  • 288
  • 289
  • 290
  • 291
  • 292
  • 293
  • 294
  • 295
  • 296
  • 297
  • 298
  • 299
  • 300
  • 301
  • 302
  • 303
  • 304
  • 305
  • 306
  • 307
  • 308
  • 309
  • 310
  • 311
  • 312
  • 313
  • 314
  • 315
  • 316
  • 317
  • 318
  • 319
  • 320
  • 321
  • 322
  • 323
  • 324
  • 325
  • 326
  • 327
  • 328
  • 329
  • 330
  • 331
  • 332
  • 333
  • 334
  • 335
  • 336
  • 337
  • 338
  • 339
  • 340
  • 341
  • 342
  • 343
  • 344
  • 345
  • 346
  • 347
  • 348
  • 349
  • 350
  • 351
  • 352
  • 353
  • 354
  • 355
  • 356
  • 357
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小小林熬夜学编程/article/detail/119248
推荐阅读