1. 程式人生 > 程式設計 >淺談Vue使用Cascader級聯選擇器資料回顯中的坑

淺談Vue使用Cascader級聯選擇器資料回顯中的坑

業務場景

由於專案需求,需要對相關類目進行多選,類目資料量又特別大,業務邏輯是使用懶載入方式載入各級類目資料,編輯時回顯使用者選擇的類目。

問題描述

使用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,}"
 />
 </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,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級聯選擇器資料回顯中的坑就是小編分享給大家的全部內容了,希望能給大家一個參考,也希望大家多多支援我們。