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

作者:Raytheon107 时间:2024-04-10 13:47:27 

业务场景

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

问题描述

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

来源:https://blog.csdn.net/Raytheon107/article/details/105752108

标签:Vue,Cascader,级联,选择器,回显
0
投稿

猜你喜欢

  • Python 使用os.remove删除文件夹时报错的解决方法

    2021-06-21 00:34:58
  • win2003安装sqlserver 2000提示无法验证产品密钥的解决方法

    2024-01-27 00:18:39
  • Python中的通函数numpy.ufunc详解

    2023-09-03 22:52:08
  • JavaScript 页面编码与浏览器类型判断代码

    2024-04-08 10:54:03
  • python 字符串和整数的转换方法

    2023-10-11 02:31:42
  • Scrapy-redis爬虫分布式爬取的分析和实现

    2023-01-04 10:21:19
  • 基于layer.js实现收货地址弹框选择然后返回相应的地址信息

    2024-05-08 09:32:22
  • Python装饰器decorator用法实例

    2023-02-06 23:26:43
  • Python写一个简单的api接口的实现

    2023-07-23 20:20:53
  • web前端页面性能优化

    2009-08-15 12:31:00
  • linecache模块加载和缓存文件内容详解

    2022-09-23 20:00:45
  • JavaScript实现QQ聊天室功能

    2024-04-19 09:47:53
  • 谈谈网页设计中的字体应用 (3) 实战应用篇·上

    2009-11-24 13:09:00
  • Python日期的加减等操作的示例

    2021-10-06 14:28:14
  • SQL Server 2005 Management Studio Express企业管理器将英文变成简体中文版的实现方法

    2024-01-27 23:35:38
  • Git如何合并多次提交

    2023-03-27 13:05:03
  • 通过Py2exe将自己的python程序打包成.exe/.app的方法

    2021-07-05 11:05:55
  • MySql分组后随机获取每组一条数据的操作

    2024-01-26 21:12:11
  • python中的class_static的@classmethod的巧妙用法

    2022-07-18 15:54:43
  • Golang正整数指定规则排序算法问题分析

    2023-07-12 09:12:03
  • asp之家 网络编程 m.aspxhome.com