vue-tree-select树形组件,支持一次加载上万条树据无卡顿

在使用vue中tree和select组件,通常会调用后端接口从而去渲染数据。
若后端返回数据量过于庞大,则会导致渲染缓慢的问题和页面直接卡死的问题。
处理方案

1.先渲染100条数据

2.滚动时前端处理加载分页获取数据并添加

vue-tree树形结构递归调用组件

<template>
  <div class="ec-tree">
    <div v-for="(item,index) in treeData" :key="index">
      <div
        class="parent"
        @click.stop="itemClick(item)"
        :class="{on:item.checked}"
        :title="item[attrs.replaceFields.title]">
<!--        <input type="checkbox" @click.stop="onClick(item)" v-model="item.check">-->
        <a-icon v-if="item[attrs.replaceFields.children] && item[attrs.replaceFields.children].length" type="caret-right"></a-icon>
        <span>{{item[attrs.replaceFields.title]}}</span>
      </div>
      <div class="children" v-if="item[attrs.replaceFields.children] && item[attrs.replaceFields.children].length && item.expansion">
        <ec-tree
          :treeData="item[attrs.replaceFields.children]"
          :attrs="attrs"
          :defaultValue="defaultValue"
          @tree-node-click="treeNodeClick"
        >
        </ec-tree>
      </div>
    </div>
  </div>
</template>

<script>
  export default {
    name: 'ec-tree',
    props: {
      treeData: {
        type: Array,
        default: () => []
      },
      attrs: {
        type: Object,
        default: () => {
          return {
            replaceFields: {
              children: 'children',
              title: 'title',
              value: 'value',
              key: 'key'
            }
          }
        }
      },
      defaultValue: {
        type: String,
        default: ''
      }
    },
    data () {
      return {
        scopesDefault: [],
        node: [],
        check: ''
      }
    },
    methods: {
      itemClick (item) {
        this.$emit('tree-node-click', item) // 组件回调
        this.treeNodeClick(item)
      },
      treeNodeClick (item) {
        let name = this.attrs.replaceFields.title
        this.expansion(this.treeData, item)
        this.setSelect(this.treeData, item[name])
        this.$emit('change', item) // 返回结果
      },
      /***
       * 处理展开,递归处理
       * @param row
       */
      expansion (array, row) {
        let children = this.attrs.replaceFields.children
        array.map(item => {
          if (row === item) {
            this.$set(item, 'expansion', !item.expansion)
          }
          if (item[children] && item[children].length) {
            this.expansion(item[children], row)
          }
        })
      },
      /**
       * 设置择中数据,递归处理
       * @param array 数组
       * @param val
       */
      setSelect (array, val) {
        let children = this.attrs.replaceFields.children
        let name = this.attrs.replaceFields.title
        array.forEach(item => {
          this.$set(item, 'checked', false)
          if (item[name] === val) {
            this.$set(item, 'checked', true)
          }
          if (item[children] && item[children].length) {
            this.setSelect(item.children, val)
          }
        })
      }
    },
    created () {
      this.setSelect(this.treeData, this.defaultValue)
    }
  }
</script>

<style lang="scss">
  .ec-tree {
    position: relative;
    width: 100%;

    .parent {
      width: 100%;
      padding: 5px 0;
      overflow: hidden;
      color: #444;
      white-space: nowrap;
      text-overflow: ellipsis;
      cursor: pointer;

      &.on {
        color: #3f98e3;
      }
    }

    .children {
      width: 100%;
      padding: 5px 0 5px 20px;
      overflow: hidden;
      color: #444;
      white-space: nowrap;
      text-overflow: ellipsis;
      cursor: pointer;

      &.on {
        color: #3f98e3;
      }
    }
  }
</style>

父组件代码vue-tree-select

<template>
  <div>
    <div class="edit-tree-select-asyn">
     <a-input
       @click.stop="onFocus"
       @change="onChange"
       @blur="onBlur"
       @input="onInput"
       :placeholder="placeholder"
       :value="value"
       v-bind="attrs"
     >
       <a-icon type="down" :class="visible ? 'up':'down'" @click.stop="onDown" slot="suffix" style="color: rgba(0, 0, 0, 0.2); cursor: pointer;"></a-icon>
     </a-input>
      <div class="tree-body" @scroll="onScroll" v-show="visible">
         <ec-tree :treeData="filterList.length ? filterList : treeData" :attrs="attrs" :defaultValue="getValue" @change="getTreeData"></ec-tree>
         <a-empty v-if="treeData.length<=0"/>
      </div>
    </div>
  </div>
</template>

<script>
  import ecTree from '@/components/ec/ec-tree/ec-tree'
  export default {
    name: 'edit-tree-select-asyn',
    components: {
      ecTree
    },
     props: {
       parseDictData: {
        type: Array,
        default: () => []
        },
      component: {
        type: Object,
        default:{}
        },
    },
    data () {
      return {
        visible: false,
        treeData: [],
        filterList: [],
        currentState: false, // 当前状态
        pages: 0,
        flag: false,
        value:""
      }
    },
    computed: {
      attrs () {
        const attrs = {
          replaceFields: {
            children: 'children',
            title: this.labelProp,
            value: this.valueProp,
            key: this.valueProp,
            size: 50
          },
          allowClear: false,
          showSearch: false,
          inputBoxSetting: true,
          ...this.component.attrs
        }
        return attrs
      }
    },
    methods: {
      onChange (evnet) {
        let value = evnet.target.value
        this.filter(this.parseDictData, value)
        this.setInputBoxSetting(false) // 输入重置
        this.updateMode(value || '')
      },
      // 更新数据输入框的值
      updateMode (value) {
         this.value=value
      },
      /**
       * 滑动页面加载数据
       * */
      onScroll (e) {
        if (this.treeData.length >= this.parseDictData.length) { // 加载完成后不在重新处理拉
          return false
        }
        let event = e.target
        let scrollHeight = event.scrollHeight - event.clientHeight
        if (scrollHeight - event.scrollTop < 100 && !this.flag) {
          this.flag = true
          this.setData()
          this.flag = false
        }
      },
      // 获取结果数据
      getTreeData (item) {
        let name = this.attrs.replaceFields.title
        this.updateMode(item[name])
        this.$emit('change', item) // 返回结果
      },
      onFocus () {
        // this.treeData = this.parseDictData || []
        this.setData()
        this.currentState = true
        this.visible = true
      },
      // 设置数据
      setData () {
        for (let i = 0; i < this.attrs.replaceFields.size; i++) { // 选加载50条
          if (!this.parseDictData[this.pages]) {
            break
          }
          this.treeData.push(this.parseDictData[this.pages])
          this.pages++
        }
      },
      // 失去交点
      onBlur () {
        if (!this.currentState && this.attrs.inputBoxSetting) { // 不选择失去焦点清空内容
         this.value=''
        }
      },
      setInputBoxSetting (state) { // 设置不选择时清空内容true\false
        if (this.attrs.inputBoxSetting) {
          this.currentState = state
        }
      },
      // 点击下面
      onDown () {
        this.setData()
        this.visible = !this.visible
      },
      /**
       * 数据过滤
       * @param array //数组
       * @param value //输入值
       */
      filter (array, value) {
        if (value) {
          let reg = /^[A-Za-z]+$/
          let children = this.attrs.replaceFields.children
          let name = this.attrs.replaceFields.title
          if (reg.test(value)) { // 拼音
            array.forEach(item => {
              if (item.spellCode && (item.spellCode.toLowerCase().indexOf(value.toLowerCase()) > -1 || item.spellCode.indexOf(value) > -1)) {
                this.filterList.push(item)
              }
              if (item[children] && item[children].length) {
                this.filter(item[children], value)
              }
            })
          } else {
            array.forEach(item => {
              if (item[name].toLowerCase().indexOf(value.toLowerCase()) > -1 || item[name].indexOf(value) > -1) {
                this.filterList.push(item)
              }
              if (item[children] && item[children].length) {
                this.filter(item[children], value)
              }
            })
          }
        } else {
          this.filterList = []
        }
      },
      // 隐藏内容
      hide () {
        if (this.visible) {
          this.filterList = []
          this.visible = false
        }
      },
      // 输入内容
      onInput () {
        this.currentState = false
      }
    },
    mounted () {
      // 监听全局点击事件
      document.addEventListener('click', this.hide)
    },
    beforeDestroy () {
      // 在页面注销前,将点击事件给移除
      document.removeEventListener('click', this.hide)
    }
  }
</script>

<style lang="scss">
  .edit-tree-select-asyn {
    position: relative;

    .tree-body {
      position: absolute;
      top: 35px;
      left: 0;
      z-index: 999;
      box-sizing: border-box;
      width: 100%;
      min-height: 100px;
      max-height: 200px;
      padding: 5px;
      overflow: auto;
      overflow-y: scroll;
      background: #fff;
      border: 1px solid #eee;
      box-shadow: 0 5px 10px #eee;

      .parent {
        width: 100%;
        padding: 5px 0;
        overflow: hidden;
        white-space: nowrap;
        text-overflow: ellipsis;
        cursor: pointer;

        &.on {
          color: #3f98e3;
        }
      }

      .children {
        width: 100%;
        padding: 5px 0;
        padding-left: 20px;
        overflow: hidden;
        white-space: nowrap;
        text-overflow: ellipsis;
        cursor: pointer;

        &.on {
          color: #3f98e3;
        }
      }
    }

    .down {
      color: #ff2e31;
      transition-duration: 0.3s;
    }

    .up {
      color: #000c17;
      transform: rotate(180deg);
      transition-duration: 0.3s;
    }
  }
</style>

 调用组vue-tree-select

<vue-tree-select></vue-tree-select>

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

dogface07

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值