浅谈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 相关文章推荐
javascript 冒号 使用说明
Jun 06 Javascript
JS取文本框中最小值的简单实例
Nov 29 Javascript
利用JS来控制键盘的上下左右键(示例代码)
Dec 14 Javascript
js查找节点的方法小结
Jan 13 Javascript
JavaScript判断表单中多选框checkbox选中个数的方法
Aug 17 Javascript
JavaScript判断数字是否为质数的方法汇总
Jun 02 Javascript
Node.js包管理器Yarn的入门介绍与安装
Oct 17 Javascript
微信小程序中使元素占满整个屏幕高度实现方法
Dec 14 Javascript
原生js实现简单的链式操作
Jul 04 Javascript
jQuery dateRangePicker插件使用方法详解
Jul 28 jQuery
Vue路由切换页面不更新问题解决方案
Jul 10 Javascript
js实现圆形菜单选择器
Dec 03 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
PHP静态新闻列表自动生成代码
2007/06/14 PHP
yii2中LinkPager增加总页数和总记录数的实例
2017/08/28 PHP
php 使用mpdf实现指定字段配置字体样式的方法
2019/07/29 PHP
php的RSA加密解密算法原理与用法分析
2020/01/23 PHP
Aster vs KG BO3 第三场2.18
2021/03/10 DOTA
JavaScript 对话框和状态栏使用说明
2009/10/25 Javascript
js字符串的各种格式的转换 ToString,Format
2011/08/08 Javascript
uploadify在Firefox下丢失session问题的解决方法
2013/08/07 Javascript
JavaScript制作淘宝星级评分效果的思路
2020/06/23 Javascript
jQuery实现的导航动画效果(附demo源码)
2016/04/01 Javascript
js实现导航栏中英文切换效果
2017/01/16 Javascript
vuejs开发组件分享之H5图片上传、压缩及拍照旋转的问题处理
2017/03/06 Javascript
node.js程序作为服务并在windows下开机自启动(用forever)
2017/03/29 Javascript
jQuery实现的手风琴侧边菜单效果
2017/03/29 jQuery
原生JS实现移动端web轮播图详解(结合Tween算法造轮子)
2017/09/10 Javascript
element 结合vue 在表单验证时有值却提示错误的解决办法
2018/01/22 Javascript
Vue render渲染时间戳转时间,时间转时间戳及渲染进度条效果
2018/07/27 Javascript
Vue项目History模式404问题解决方法
2018/10/31 Javascript
使用weixin-java-miniapp配置进行单个小程序的配置详解
2019/03/29 Javascript
详解无限滚动插件vue-infinite-scroll源码解析
2019/05/12 Javascript
vue实现修改图片后实时更新
2019/11/14 Javascript
js实现点击按钮随机生成背景颜色
2020/09/05 Javascript
微信小程序实现页面监听自定义组件的触发事件
2020/11/01 Javascript
python使用正则表达式分析网页中的图片并进行替换的方法
2015/03/26 Python
pow在python中的含义及用法
2019/07/11 Python
Python3之乱码\xe6\x97\xa0\xe6\xb3\x95处理方式
2020/05/11 Python
浅谈html5与APP混合开发遇到的问题总结
2018/03/20 HTML / CSS
花园仓库建筑:Garden Buildings Direct
2018/02/16 全球购物
《一个小村庄的故事》教学反思
2014/04/13 职场文书
活动总结模板
2014/05/09 职场文书
物流管理专业推荐信
2014/09/06 职场文书
运动会400米加油稿(8篇)
2014/09/22 职场文书
解除劳动合同协议书(样本)
2014/10/02 职场文书
工作失误检讨书(3篇)
2014/10/11 职场文书
2014党的群众路线教育实践活动总结材料
2014/10/31 职场文书
vue中this.$http.post()跨域和请求参数丢失的解决
2022/04/08 Vue.js