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


Posted in Javascript onOctober 31, 2020

业务场景

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

问题描述

使用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>

补充知识:Ant Design 级联选择的一种写法

简单记录类似省、市、区或品牌、车系、车型等多级结构,级联选择添加并展示的一种写法:

import React from 'react';
import {Button, Form, message, Row, Tag,Select,Col} from 'antd';
import request from "../../../../utils/request";
const FormItem = Form.Item;
const Option = Select.Option;
 
class CarSeriesCascader extends React.Component {
 
 constructor(props) {
  super(props);
  this.state = {
   defaultBrandList:[],
   selectedCarModelList: props.carModelList ? props.carModelList : [],
   brandCode:null,
   carModelList:[],
   carId:null,
   modelCode:null,
   modelName:null
  }
 }
 
 componentDidMount() {
  let promise = request(`/car/getBrandList`);
  promise.then(result =>{
  if(result != null){
   this.setState({
   defaultBrandList:result
   });
  }else{
   message.error("获取品牌数据失败");
  }
  }).catch(err => {
   message.error("获取品牌数据失败");
  });
  // this.setState({
  // selectedCarModelList:(this.props.carModelList ? this.props.carModelList : [])
  // });
  this.handleChange(this.state.selectedCarModelList);
 }
 
 getLimitList = (selectedCarModelList) => {
  let limitList = selectedCarModelList.map((carModel,index) => {
   let limitItem = {};
   limitItem.modelName = carModel.modelName;
   limitItem.modelCode = carModel.modelCode;
   limitItem.carId = carModel.carId;
   return limitItem;
  });
  return limitList;
 } 
 
 addCarModel = () => {
  let addCarModel = {};
  let selectedCarModelList = this.state.selectedCarModelList;
  // 选中车型号
  if (this.state.carId !== null) {
   // 检查车型是否已选中
   for (let index = this.state.selectedCarModelList.length - 1; index >= 0; index--) {
    let carModel = this.state.selectedCarModelList[index];
    if (carModel.carId == this.state.carId) {
     message.error("车型已在已选车型中");
     return;
    }
   }
   addCarModel.carId = this.state.carId;
   addCarModel.modelCode = this.state.modelCode;
   addCarModel.modelName = this.state.modelName;
   selectedCarModelList.push(addCarModel);
  } else {
   return;
  }
  this.handleChange(selectedCarModelList);
  this.setState({
   selectedCarModelList
  });
 }
 
 handleChange = (selectedCarModelList) => {
  if (this.props.onChange) {
   let limitList = this.getLimitList(selectedCarModelList);
   this.props.onChange(limitList);
  }
 }
 
 deleteTag = (limitCode) => {
  debugger
  let selectedCarModelList = this.state.selectedCarModelList;
  selectedCarModelList = selectedCarModelList.filter(carModel => !(carModel.modelCode === limitCode));
  this.handleChange(selectedCarModelList);
  this.setState({selectedCarModelList});
 }
 
 //品牌变化
 brandChange = (brandName) => {
 this.state.defaultBrandList.map((item, index) => {
  if (item.brandName == brandName) {
  let promise = request(`/car/getModelList?brandCode=` + item.brandCode);
  promise.then(result =>{
   if(result != null){
   this.setState({
    brandCode:item.brandCode,
    carModelList:result
   });
   }else{
   message.error("获取车型数据失败");
   }
  }).catch(err => {
   message.error("获取车型数据失败:");
  });
  }
 });
 }
 
 //车型变化
 modelChange = (modelName) => {
 this.props.form.setFieldsValue({modelName: null});
 let _this = this;
 this.state.carModelList.map((item, index) => {
  if (item.modelName == modelName) {
  console.log(item);
  this.setState({
  modelCode : item.modelCode,
  carId : item.carId,
  modelName : item.modelName
  });
  }
 });
 }
 
 render() {
  const {getFieldDecorator} = this.props.form;
  //品牌名称列表
  let allBrandListOption = this.state.defaultBrandList != null ? this.state.defaultBrandList.map((item, index) => {
  return <Option value={item.brandName} key={index}>{item.brandName}</Option>;
  }) : null;
 
  //车型名称列表
  let allModelListOption = this.state.carModelList != null ? this.state.carModelList.map((item, index) => {
  return <Option value={item.modelName} key={index}>{item.modelName}</Option>;
  }) : null;
 
  const {
   closable=true,
  } = this.props;
 
  const existCarModel = [];
  const limitList = this.getLimitList(this.state.selectedCarModelList);
  for (let index = limitList.length - 1; index >= 0; index--) {
   let limitItem = limitList[index];
   existCarModel.push(<Tag
    key={limitItem.modelCode}
    closable={closable}
    onClose={(e) => {
     e.preventDefault();
     this.deleteTag(limitItem.modelCode);
    }}
   >{limitItem.modelName}</Tag>);
  }
 
  return (
   <div>
    <Row>
     <FormItem >
      {getFieldDecorator('brandName', {
      rules: [{
       message: '请选择品牌'
      }],
      })(
      <Select
       placeholder="品牌"
       dropdownMatchSelectWidth={false}
       onChange={this.brandChange}
       style={{ marginRight: 10, width: 100 }}>
       <Option value={null}>选择品牌</Option>
       {allBrandListOption}
      </Select>
      )}
      {getFieldDecorator('modelName', {
      rules: [{
       message: '请选择车型'
      }],
      })(
      <Select
       placeholder="车型"
       dropdownMatchSelectWidth={false}
       onChange={this.modelChange}
       style={{ marginRight: 10, width: 260 }}>
       <Option value={null}>选择车型</Option>
       {allModelListOption}
      </Select>
      )}
      <Button type={"primary"} icon={"plus"} onClick={this.addCarModel}>添加车型</Button>
     </FormItem>
    </Row>
    <Row>
     {existCarModel}
    </Row>
   </div>
  )
 }
} 
export default Form.create()(CarSeriesCascader);

以上这篇浅谈Vue使用Cascader级联选择器数据回显中的坑就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
分享一个我自己写的ToolTip提示插件(附源码)
Jan 20 Javascript
JavaScript禁止页面操作的示例代码
Dec 17 Javascript
jQuery超酷平面式时钟效果代码分享
Mar 30 Javascript
ES6通过babel转码使用webpack使用import关键字
Dec 13 Javascript
js获取当前周、上一周、下一周日期
Mar 19 Javascript
JavaScript代码判断输入的字符串是否含有特殊字符和表情代码实例
Aug 17 Javascript
jQuery实现注册会员时密码强度提示信息功能示例
Sep 05 jQuery
Vue通过URL传参如何控制全局console.log的开关详解
Dec 07 Javascript
vue用递归组件写树形控件的实例代码
Jul 19 Javascript
vue 实现微信浮标效果
Sep 01 Javascript
详谈Object.defineProperty 及实现数据双向绑定
Jul 18 Javascript
vue 内联样式style中的background用法说明
Aug 05 Javascript
Ant design vue中的联动选择取消操作
Oct 31 #Javascript
Ant Design Vue table中列超长显示...并加提示语的实例
Oct 31 #Javascript
vue中可编辑树状表格的实现代码
Oct 31 #Javascript
Ant Design Pro 之 ProTable使用操作
Oct 31 #Javascript
react ant Design手动设置表单的值操作
Oct 31 #Javascript
解决pycharm双击但是无法打开的情况
Oct 31 #Javascript
antd的select下拉框因为数据量太大造成卡顿的解决方式
Oct 31 #Javascript
You might like
人族 Terran 基本策略
2020/03/14 星际争霸
Laravel 5.4向IoC容器中添加自定义类的方法示例
2017/08/15 PHP
javaScript 判断字符串是否为数字的简单方法
2009/07/25 Javascript
javascript定义函数的方法
2010/12/06 Javascript
js克隆对象、数组的常用方法介绍
2013/09/26 Javascript
Javascript变量作用域详解
2013/12/06 Javascript
jquery checkbox 勾选的bug问题解决方案与分析
2014/11/13 Javascript
JS长整型精度问题实例分析
2015/01/13 Javascript
前端面试题及答案整理(二)
2016/08/26 Javascript
详解XMLHttpRequest(一)同步请求和异步请求
2016/09/14 Javascript
JS正则替换去空格的方法
2017/03/24 Javascript
基于jQuery实现的单行公告活动轮播效果
2017/08/23 jQuery
JavaScript控制浏览器全屏显示简单示例
2018/07/05 Javascript
Element PageHeader页头的使用方法
2020/07/26 Javascript
Python 元组(Tuple)操作详解
2014/03/11 Python
Python中列表(list)操作方法汇总
2014/08/18 Python
python数据结构之图深度优先和广度优先实例详解
2015/07/08 Python
安装ElasticSearch搜索工具并配置Python驱动的方法
2015/12/22 Python
Scrapy框架CrawlSpiders的介绍以及使用详解
2017/11/29 Python
Python多重继承的方法解析执行顺序实例分析
2018/05/26 Python
利用Python如何批量修改数据库执行Sql文件
2018/07/29 Python
Ubuntu18.04中Python2.7与Python3.6环境切换
2019/06/14 Python
python tkinter实现屏保程序
2019/07/30 Python
python中for循环变量作用域及用法详解
2019/11/05 Python
python日志通过不同的等级打印不同的颜色(示例代码)
2021/01/13 Python
matplotlib之pyplot模块之标题(title()和suptitle())
2021/02/22 Python
上海期货面试题
2014/01/31 面试题
计算机专业毕业生求职信分享
2013/12/24 职场文书
爽歪歪广告词
2014/03/20 职场文书
班级学习雷锋活动总结
2014/07/04 职场文书
2015关爱留守儿童工作总结
2014/12/12 职场文书
入党自荐书范文
2015/03/05 职场文书
药房管理制度范本
2015/08/06 职场文书
2019年圣诞节祝福语集锦
2019/12/25 职场文书
Python基本数据类型之字符串str
2021/07/21 Python
在HTML中引入CSS的几种方式介绍
2021/12/06 HTML / CSS