淺談Vue使用Cascader級(jí)聯(lián)選擇器數(shù)據(jù)回顯中的坑
業(yè)務(wù)場(chǎng)景
由于項(xiàng)目需求,需要對(duì)相關(guān)類(lèi)目進(jìn)行多選,類(lèi)目數(shù)據(jù)量又特別大,業(yè)務(wù)邏輯是使用懶加載方式加載各級(jí)類(lèi)目數(shù)據(jù),編輯時(shí)回顯用戶選擇的類(lèi)目。
問(wèn)題描述
使用Cascader級(jí)聯(lián)選擇器過(guò)程中主要存在的應(yīng)用問(wèn)題如下:
1、由于在未渲染節(jié)點(diǎn)數(shù)據(jù)的情況下編輯時(shí)無(wú)法找到對(duì)應(yīng)的類(lèi)目數(shù)據(jù)導(dǎo)致無(wú)法回顯,如何自動(dòng)全部加載已選擇類(lèi)目的相關(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ù)完成加載,依然無(wú)法回顯。
解決思路
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ù)分開(kāi),避免產(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(),該方法無(wú)法檢測(cè)到Array中的深層數(shù)據(jù)變化,需要使用$set來(lái)觸發(fā)列表數(shù)據(jù)的更新。
一個(gè)三級(jí)級(jí)聯(lián)選擇器,首先獲取全部一級(jí)類(lèi)目,二級(jí)類(lèi)目和三級(jí)類(lèi)目采用懶加載,獲取數(shù)據(jù)的步驟如下:
1、獲取全部一級(jí)類(lèi)目;
2、由于使用異步數(shù)據(jù)加載,使用Promise進(jìn)行數(shù)據(jù)請(qǐng)求;
3、根據(jù)已選擇的類(lèi)目獲取相關(guān)聯(lián)的二級(jí)類(lèi)目和三級(jí)類(lèi)目;
4、數(shù)據(jù)請(qǐng)求完成,使用$set觸發(fā)列表數(shù)據(jù)更新,在$nextTick中完成數(shù)據(jù)你回顯。
相關(guān)代碼
<template> <div> <el-cascader placeholder="請(qǐng)選擇所屬類(lèi)目" :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)選擇所屬類(lèi)目" :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)選擇的一種寫(xiě)法
簡(jiǎn)單記錄類(lèi)似省、市、區(qū)或品牌、車(chē)系、車(chē)型等多級(jí)結(jié)構(gòu),級(jí)聯(lián)選擇添加并展示的一種寫(xiě)法:
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; // 選中車(chē)型號(hào) if (this.state.carId !== null) { // 檢查車(chē)型是否已選中 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("車(chē)型已在已選車(chē)型中"); 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("獲取車(chē)型數(shù)據(jù)失敗"); } }).catch(err => { message.error("獲取車(chē)型數(shù)據(jù)失敗:"); }); } }); } //車(chē)型變化 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; //品牌名稱(chēng)列表 let allBrandListOption = this.state.defaultBrandList != null ? this.state.defaultBrandList.map((item, index) => { return <Option value={item.brandName} key={index}>{item.brandName}</Option>; }) : null; //車(chē)型名稱(chēng)列表 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)選擇車(chē)型' }], })( <Select placeholder="車(chē)型" dropdownMatchSelectWidth={false} onChange={this.modelChange} style={{ marginRight: 10, width: 260 }}> <Option value={null}>選擇車(chē)型</Option> {allModelListOption} </Select> )} <Button type={"primary"} icon={"plus"} onClick={this.addCarModel}>添加車(chē)型</Button> </FormItem> </Row> <Row> {existCarModel} </Row> </div> ) } } export default Form.create()(CarSeriesCascader);
以上這篇淺談Vue使用Cascader級(jí)聯(lián)選擇器數(shù)據(jù)回顯中的坑就是小編分享給大家的全部?jī)?nèi)容了,希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
vue-cli項(xiàng)目無(wú)法用本機(jī)IP訪問(wèn)的解決方法
今天小編就為大家分享一篇vue-cli項(xiàng)目無(wú)法用本機(jī)IP訪問(wèn)的解決方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2018-09-09vue通過(guò)elementUI組件實(shí)現(xiàn)圖片預(yù)覽效果
我們?cè)陂_(kāi)發(fā)中經(jīng)常會(huì)遇到通過(guò)點(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),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-06-06