浅谈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


猜你喜欢
- 这两天一直在看tensorflow中的读取数据的队列,说实话,真的是很难懂。也可能我之前没这方面的经验吧,最早我都使用的theano,什么都
- 本文实例为大家分享了JS实现倒计时图文效果的具体代码,供大家参考,具体内容如下<body><img src="i
- 张量范数:torch.norm(input, p=2) → float返回输入张量 input 的 p 范数举个例子:>>>
- 在写论文时,如果是菜鸟级别,可能不会花太多时间去学latex,直接用word去写,但是这有一个问题,当我们用其他工具画完实验彩色图时,放到w
- 一: 基本使用:1:环境的安装:pip install flask-sqlalchemypip install pymysql2:组件初始化
- 本节内容0、列表常用功能汇总1、定义列表2、访问列表中的元素3、切片4、追加5、插入6、修改7、拷贝8、删除9、扩展10、统计11、翻转12
- 一、Pyecharts简介和安装1、简介Echarts 是一个由百度开源的数据可视化,凭借着良好的交互性,精巧的图表设计,得到了众多开发者的
- 目录函数什么是函数/方法2.为什么需要函数1、载体2、组织3、复用4、封装5、清晰6、按需3.如何声明/调用一个函数4.函数/方法的参数1、
- 本文实例为大家分享了python判断设备是否联网的具体代码,供大家参考,具体内容如下直接上代码,就是用判断socket能不连上的方法来判断。
- 系列文章:PyQt5使用mimeData实现拖拽事件教程示例解析上实现思路1、简要介绍QMimeData2、QMimeData的用例1:在Q
- onmouseout 发现它的触发太敏感,当经过层内文字链时,即触发onmousetout事件,功能不能正常显示,经过一番搜索,整理出来,供
- 一、前言1.1.环境python版本:3.6Django版本:1.11.61.2.预览效果最终搭建的blog的样子,基本上满足需求了。框架搭
- 将PHP的执行页面预先转换成HTML,是所谓的PHP静态化方法之一。其他还有模板替换法,opcache等方法。静态化的作用:提高网站的响应速
- (一)关于体验约瑟夫.派恩和詹姆士.吉尔摩在《体验经济》一书中提出其观点:所谓“体验”就是企业以商品为道具,以服务为舞台,以顾客为中心,创造
- 【原文地址】 Recipe: Deploying a SQL Database to a Remote Hosting Environmen
- 直接将 视频的HTML网址存入models ,以字符串的形式#关于我们 CharFieldclass About(models.Model)
- asp连接sql server代码如下:dim connset conn = Serve
- python中的lambda通常是用来在python中创建匿名函数的,而用def创建的方法是有名称的,除了从表面上的方法名不一样外,pyth
- python读取Excel表格文件,例如获取这个文件的数据python读取Excel表格文件,需要如下步骤:1、安装Excel读取数据的库-
- 1、Git Bash默认路径在windows系统上操作Git的客户端是Git Bash。安装完Git Bash之后,双击打开,如下图:使用p