Vue使用Cascader级联选择器数据回显中的坑

业务场景

由于项目需求,需要对相关类目进行多选,类目数据量又特别大,业务逻辑是使用懒加载方式加载各级类目数据,编辑时回显用户选择的类目。

问题描述

使用Cascader级联选择器过程中主要存在的应用问题如下:

1、由于在未渲染节点数据的情况下编辑时无法找到对应的类目数据导致无法回显,如何自动全部加载已选择类目的相关节点数据;

2、提前加载数据后,点击相应父级节点出现数据重复等;

3、使用多个数据源相同的级联选择器,产生只能成功响应一个加载子级节点数据;

4、Vue中级联选择器相应数据完成加载,依然无法回显。

解决思路

Cascader级联选择器在需要回显的节点数据都存在的情况下,方可完成回显,首先想到的是把选中节点相关的数据全部获取到即可,遍历已选择的节点数据,遍历加载相对应的数据。(如果多个级联选择器使用同一个数据源,使用深拷贝将数据分开,避免产生影响)

由于是级联的数据懒加载,需要每一级相应的节点数据加载完进行下一步,故使用ES6中的Promise,将子级节点数据加载封装成一个Promise,待Promise执行完成,对列表数据遍历获取完成后返回即可。

getChildrenList (fid, level = 0) {
      return new Promise((resolve, reject) => {
        API.getCategory({ fid: fid, level: level }).then(
          res => {
            if (res) {
              if (res.code === 0 && res.result) {
                resolve(res.result)
              }
            }
          }
        )
      })
    },
let twolist = this.getChildrenList(codeArr[0], 1)
let thirdlist = this.getChildrenList(codeArr[1], 2)
Promise.all([twolist, thirdlist]).then((data) => {
    ...
})

Vue2的双向数据绑定使用ES2015中的Object.defineProperty(),该方法无法检测到Array中的深层数据变化,需要使用$set来触发列表数据的更新。

一个三级级联选择器,首先获取全部一级类目,二级类目和三级类目采用懒加载,获取数据的步骤如下:

1、获取全部一级类目;

2、由于使用异步数据加载,使用Promise进行数据请求;

3、根据已选择的类目获取相关联的二级类目和三级类目;

4、数据请求完成,使用$set触发列表数据更新,在$nextTick中完成数据你回显。

 

相关代码

<template>
  <div>
    <el-cascader
      placeholder="请选择所属类目"
      :options="categoryList"
      :show-all-levels="false"
      v-model="category"
      collapse-tags
      :props="{
        multiple: true,
        value: 'code',
        label: 'name',
        children: 'children',
        ...props,
      }"
    />
    <el-cascader
      placeholder="请选择所属类目"
      :options="secondCategoryList"
      :show-all-levels="false"
      v-model="secondCategory"
      collapse-tags
      :props="{
        multiple: true,
        value: 'code',
        label: 'name',
        children: 'children',
        ...props,
      }"
    />
  </div>
</template>

<script>
export default {
  data () {
    return {
      categoryList: [],
      category: [],
      secondCategoryList: [],
      secondCategory: [],
      props: {
        lazy: true,
        // checkStrictly: true,  // 父子级节点关联
        async lazyLoad (node, reso) {
          const { level, data } = node
          if (data && data.children && data.children.length !== 0) {
            return reso(node)
          }
          if (data && data.leaf) {
            return reso([])
          }
          const lv3Code = data ? data.code : null
          setTimeout(() => {
            lv3Code && API.getCategory({ fid: lv3Code, level: level }).then(
              res => {
                if (res) {
                  if (res.code === 0 && res.result) {
                    const nodes = res.result.map(item => ({ leaf: level === 2, ...item, children: [] }))
                    data.children = nodes
                    reso(nodes)
                  } else {
                    reso([])
                  }
                }
              }
            )
          }, 500)
        }
      }
    }
  },
  mounted () {
    this.getCategory()
    this.initData()
  },
  methods: {
    initData () {
      let _that = this
      异步获取编辑数据。。。
      .then(result => {
        // 此处仅处理result中firstCategory和secondCategory均不为空的情况
        let firstTemp = _that.getCategoryListFormat(result.firstCategory, _that.categoryList)
        let secondTemp = _that.getCategoryListFormat(result.secondCategory, _that.secondCategoryList)
        let promiseArr = [firstTemp, secondTemp].filter(_ => _)
        Promise.all(promiseArr).then((formatRes) => {
          // 触发列表数据响应
          this.$set(_that.categoryList, formatRes[0].tragetCategoryList)
          this.$set(_that.secondCategoryList, formatRes[1].tragetCategoryList)
          _that.$nextTick(() => {
            // 数据加载完成后,在下一次循环中回显
            _that.category = formatRes[0].category
            _that.secondCategory = formatRes[1].category
          })
        })
      })
    },
    getCategoryListFormat (categorySelectList, tragetCategoryList) {
      return new Promise((resolve, reject) => {
        const category = []
        let flag = 0
        let counter = categorySelectList.length

        categorySelectList.forEach(v => { // 遍历已选择节点数据
          const oneNode = v
          const twoNode = v.children
          const threeNode = v.children.children
          const codeArr = [oneNode.code, twoNode.code, threeNode.code]
          category.push(codeArr)
          twoNode.children = twoNode.children ? twoNode.children : []
          let twolist = this.getChildrenList(codeArr[0], 1)
          let thirdlist = this.getChildrenList(codeArr[1], 2)
          Promise.all([twolist, thirdlist]).then((data) => {
            let twochildren = data[0]
            let threechildren = data[1]
            threechildren = threechildren.map(item => ({ leaf: true, ...item })) // 三级节点设置成叶子节点
            twoNode.children = threechildren
            tragetCategoryList.forEach(w => { // 遍历列表添加相应节点数据
              if (w.code === oneNode.code) {
                if (!w.children) {
                  w.children = twochildren
                }
                w.children.forEach(item => {
                  if (item.code === twoNode.code) {
                    item.children = twoNode.children
                  }
                })
              }
            })
            flag++
            if (flag === counter) {
              resolve({ tragetCategoryList, category })
            }
          })
        })
      })
    },
    getChildrenList (fid, level = 0) {
      return new Promise((resolve, reject) => {
        API.getCategory({ fid: fid, level: level }).then(
          res => {
            if (res) {
              if (res.code === 0 && res.result) {
                resolve(res.result)
              }
            }
          }
        )
      })
    },
    getCategory(fid = 0, level = 0) {
      API.getCategory({ fid: fid, level: level })
        .then(
          res => {
            if (res) {
              if (res.code == 0 && res.result) {
                this.categoryList = this.deepClone(res.result);
              }
            }
          }
        )
    },
    deepClone (source) { // 深拷贝
      if (!source && typeof source !== 'object') {
        throw new Error('error arguments', 'shallowClone')
      }
      const targetObj = source.constructor === Array ? [] : {}
      Object.keys(source).forEach(keys => {
        if (source[keys] && typeof source[keys] === 'object') {
          targetObj[keys] = source[keys].constructor === Array ? [] : {}
          targetObj[keys] = deepClone(source[keys])
        } else {
          targetObj[keys] = source[keys]
        }
      })
      return targetObj
    }
  }
}
</script>

<style lang="less" scoped>
  
</style>

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章