el-tree 加载一万个节点临时方案

304 阅读1分钟

在我们的项目中,有一个目录树,这个目录树组件用的是element-ui中的el-tree。但是当有上万个节点时,渲染会有点卡。由于时间紧,任务大,我想了一个临时方案(为什么说是临时方案呢?因为针对这种场景可能会有更好的方案):树结构第一级不做懒加载,直接请求,第二级懒加载,因为第一级数据不多,第二级数据多,在第二级懒加载时分页请求,即第一次请求50条,点击更多,请求第二页,更多按钮总跟在第二级节点最后。

实现

<template>
  <div>
    <el-input v-model="query" @input="handleInput"></el-input>
    <el-tree
      ref="myTree"
      :data="treeData"
      v-loading="loading"
      node-key="id"
      :indent="10"
      @node-click="handleNodeClick"
      :highlight-current="true"
      :expand-on-click-node="true"
      lazy
      :load="loadNode"
    >
      <div class="custom-tree-node" slot-scope="{ node, data }">
        <div v-if="node.label==='加载更多'" class="left-tree__more" @click.stop="handleMore(data)">
          {{ node.label }}
        </div>
        <span v-else>
          {{ node.label }}
        </span>
      </div>
    </el-tree>
  </div>
</template>

<script>
class ReqObj {
  constructor (options) {
    for (const name in options) {
      if (Object.prototype.hasOwnProperty.call(options, name)) {
        this[name] = options[name]
      }
    }
    this.pageSize = 50
    this.pageNum = 1
    this.total = 0
    this.list = []

    this.start()
  }

  async reqMethod () {
    // 请求数据
    const res = await getData({
      pageNum: this.pageNum,
      pageSize: this.pageSize
    })
    const data = res.data || {}
    this.total = data.total || 0
    const list = data.list || []

    list.forEach(item => {
      item.isLeaf = true
    })

    if (this.total === 0) {
      list.push({ id: Symbol(''), label: '暂无数据', isLeaf: true })
    }

    this.list = this.list.concat(list)

    let result = []
    if (this.pageNum * this.pageSize < this.total) {
      // 如果还有数据,最后一项是“加载更多”
      result = this.list.concat([{
        id: Symbol(this.node.id),
        label: '加载更多',
        isLeaf: true,
        $nodePid: this.node.id // 携带父节点的id,在reqManages中通过这个id查找ReqObj实例
      }])
    } else {
      result = this.list
    }

    this.resolve(result)

    return result
  }

  loadMore () {
    this.pageNum += 1
    return this.reqMethod()
  }

  start () {
    this.reqMethod()
  }
}

export default {
  name: '',
  data () {
    return {
      loading: false,
      query: '',
      treeData: [],
      defaultProps: {
        children: 'children',
        label: 'label',
        isLeaf: 'isLeaf'
      },
      reqManages: {}
    }
  },
  created () {
  },
  methods: {
    handleInput () {
      this.handleSearch()
    },

    // 点击加载更多
    async handleMore (item) {
      if (this.reqManages[item.$nodePid]) {
        this.loading = true
        await this.reqManages[item.$nodePid].loadMore()
        this.loading = false
      }
    },

    async loadNode (node, resolve) {
      if (node.level === 1) {
        if (this.reqManages[node.id]) return
        // new一个ReqObj实例保存到reqManages中,key是node.id
        this.reqManages[node.id] = new ReqObj({
          resolve, // 传入当前这个resolve
          node, // 传入点开的这个node
          vm: this // 当前vue实例
        })
      }
    },

    clearReqManages () {
      this.reqManages = {}
    },

    handleSearch () {
      this.treeData = []
      this.clearReqManages()
      // 模拟请求
      // 目录树第一级不用懒加载,展开时再触发懒加载
      setTimeout(() => {
        this.expandFirst()
      }, 1000)
    },

    // 展开第一个节点
    expandFirst () {
      this.$nextTick(() => {
        const firstItem = this.treeData[0]
        const node = this.$refs.myTree.getNode(firstItem.id)
        // 调用expand方法触发懒加载
        node.expand()
      })
    }
  }
}
</script>

<style lang="scss" scoped>
.left-tree__more {
  width: 100%;
  text-align: center;
  cursor: pointer;
  color: $--color-primary
}
</style>

效果