淺談Vue使用Cascader級(jí)聯(lián)選擇器數(shù)據(jù)回顯中的坑
業(yè)務(wù)場(chǎng)景
由于項(xiàng)目需求,需要對(duì)相關(guān)類目進(jìn)行多選,類目數(shù)據(jù)量又特別大,業(yè)務(wù)邏輯是使用懶加載方式加載各級(jí)類目數(shù)據(jù),編輯時(shí)回顯用戶選擇的類目。
問題描述
使用Cascader級(jí)聯(lián)選擇器過程中主要存在的應(yīng)用問題如下:
1、由于在未渲染節(jié)點(diǎn)數(shù)據(jù)的情況下編輯時(shí)無法找到對(duì)應(yīng)的類目數(shù)據(jù)導(dǎo)致無法回顯,如何自動(dòng)全部加載已選擇類目的相關(guān)節(jié)點(diǎn)數(shù)據(jù);
2、提前加載數(shù)據(jù)后,點(diǎn)擊相應(yīng)父級(jí)節(jié)點(diǎn)出現(xiàn)數(shù)據(jù)重復(fù)等;
3、使用多個(gè)數(shù)據(jù)源相同的級(jí)聯(lián)選擇器,產(chǎn)生只能成功響應(yīng)一個(gè)加載子級(jí)節(jié)點(diǎn)數(shù)據(jù);
4、Vue中級(jí)聯(lián)選擇器相應(yīng)數(shù)據(jù)完成加載,依然無法回顯。
解決思路
Cascader級(jí)聯(lián)選擇器在需要回顯的節(jié)點(diǎn)數(shù)據(jù)都存在的情況下,方可完成回顯,首先想到的是把選中節(jié)點(diǎn)相關(guān)的數(shù)據(jù)全部獲取到即可,遍歷已選擇的節(jié)點(diǎn)數(shù)據(jù),遍歷加載相對(duì)應(yīng)的數(shù)據(jù)。(如果多個(gè)級(jí)聯(lián)選擇器使用同一個(gè)數(shù)據(jù)源,使用深拷貝將數(shù)據(jù)分開,避免產(chǎn)生影響)
由于是級(jí)聯(lián)的數(shù)據(jù)懶加載,需要每一級(jí)相應(yīng)的節(jié)點(diǎn)數(shù)據(jù)加載完進(jìn)行下一步,故使用ES6中的Promise,將子級(jí)節(jié)點(diǎn)數(shù)據(jù)加載封裝成一個(gè)Promise,待Promise執(zhí)行完成,對(duì)列表數(shù)據(jù)遍歷獲取完成后返回即可。
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的雙向數(shù)據(jù)綁定使用ES2015中的Object.defineProperty(),該方法無法檢測(cè)到Array中的深層數(shù)據(jù)變化,需要使用$set來觸發(fā)列表數(shù)據(jù)的更新。
一個(gè)三級(jí)級(jí)聯(lián)選擇器,首先獲取全部一級(jí)類目,二級(jí)類目和三級(jí)類目采用懶加載,獲取數(shù)據(jù)的步驟如下:
1、獲取全部一級(jí)類目;
2、由于使用異步數(shù)據(jù)加載,使用Promise進(jìn)行數(shù)據(jù)請(qǐng)求;
3、根據(jù)已選擇的類目獲取相關(guān)聯(lián)的二級(jí)類目和三級(jí)類目;
4、數(shù)據(jù)請(qǐng)求完成,使用$set觸發(fā)列表數(shù)據(jù)更新,在$nextTick中完成數(shù)據(jù)你回顯。
相關(guān)代碼
<template>
<div>
<el-cascader
placeholder="請(qǐng)選擇所屬類目"
:options="categoryList"
:show-all-levels="false"
v-model="category"
collapse-tags
:props="{
multiple: true,
value: 'code',
label: 'name',
children: 'children',
...props,
}"
/>
<el-cascader
placeholder="請(qǐng)選擇所屬類目"
: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, // 父子級(jí)節(jié)點(diǎn)關(guān)聯(lián)
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
異步獲取編輯數(shù)據(jù)。。。
.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) => {
// 觸發(fā)列表數(shù)據(jù)響應(yīng)
this.$set(_that.categoryList, formatRes[0].tragetCategoryList)
this.$set(_that.secondCategoryList, formatRes[1].tragetCategoryList)
_that.$nextTick(() => {
// 數(shù)據(jù)加載完成后,在下一次循環(huán)中回顯
_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 => { // 遍歷已選擇節(jié)點(diǎn)數(shù)據(jù)
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 })) // 三級(jí)節(jié)點(diǎn)設(shè)置成葉子節(jié)點(diǎn)
twoNode.children = threechildren
tragetCategoryList.forEach(w => { // 遍歷列表添加相應(yīng)節(jié)點(diǎn)數(shù)據(jù)
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>
補(bǔ)充知識(shí):Ant Design 級(jí)聯(lián)選擇的一種寫法
簡(jiǎn)單記錄類似省、市、區(qū)或品牌、車系、車型等多級(jí)結(jié)構(gòu),級(jí)聯(lián)選擇添加并展示的一種寫法:
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("獲取品牌數(shù)據(jù)失敗");
}
}).catch(err => {
message.error("獲取品牌數(shù)據(jù)失敗");
});
// 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;
// 選中車型號(hào)
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("獲取車型數(shù)據(jù)失敗");
}
}).catch(err => {
message.error("獲取車型數(shù)據(jù)失敗:");
});
}
});
}
//車型變化
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: '請(qǐng)選擇品牌'
}],
})(
<Select
placeholder="品牌"
dropdownMatchSelectWidth={false}
onChange={this.brandChange}
style={{ marginRight: 10, width: 100 }}>
<Option value={null}>選擇品牌</Option>
{allBrandListOption}
</Select>
)}
{getFieldDecorator('modelName', {
rules: [{
message: '請(qǐng)選擇車型'
}],
})(
<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級(jí)聯(lián)選擇器數(shù)據(jù)回顯中的坑就是小編分享給大家的全部內(nèi)容了,希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
vue-cli項(xiàng)目無法用本機(jī)IP訪問的解決方法
今天小編就為大家分享一篇vue-cli項(xiàng)目無法用本機(jī)IP訪問的解決方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2018-09-09
vue通過elementUI組件實(shí)現(xiàn)圖片預(yù)覽效果
我們?cè)陂_發(fā)中經(jīng)常會(huì)遇到通過點(diǎn)擊某個(gè)按鈕或者文字實(shí)現(xiàn)圖片的預(yù)覽功能,這里我們分別介紹vue2和vue3里面如何實(shí)現(xiàn)圖片預(yù)覽方法,需要的朋友可以參考下2023-09-09
淺談element中InfiniteScroll按需引入的一點(diǎn)注意事項(xiàng)
這篇文章主要介紹了淺談element中InfiniteScroll按需引入的一點(diǎn)注意事項(xiàng),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-06-06

