浅谈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,
 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);

以上这篇浅谈Vue使用Cascader级联选择器数据回显中的坑就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持我们。

(0)

相关推荐

  • 解决ant-design-vue中menu菜单无法默认展开的问题

    在ant-design-vue的文档中, menu有两个可以传的参数,分别为defaultOpenKeys和openKeys,但在接手做一个项目时,openKeys这个参数前人已经写了,可能是从文档中直接复制demo没有删除掉,我也没去管. 等到需要用到这个defaultOpenKeys的时候,想做每次刷新还会再次打开上次打开的菜单的效果,但一直无效,网上也没有查到有类似的情况,后面只能自己去看源码,查了一下,发现这个代码片段 这段代码不就说明了有openKeys就不会有defaultOpenK

  • Vue使用CDN引用项目组件,减少项目体积的步骤

    Vue项目打包后有的文件动辄几百KB或几M,这对一个前端项目的加载无疑是致命的.当你的服务器部署在阿里云或亚马逊,每秒只有100kb的加载速度时,页面的载入速度绝对让你崩溃.那么有什么办法可以在依旧是100kb/s的加载速度下让我们的页面快起来呢?CDN算是其中之一的解决办法. 首先,我们要明白为什么我的Vue项目在打包后产生的文件会那么大.我们在最开始使用Vue的时候几乎所有组件.插件的引用都会放到项目主文件中进行即 main.js文件,我们的引用方式可能是这样的: import Vue fr

  • Ant design vue中的联动选择取消操作

    项目中会遇到需求就是table表格中选中在侧边展示,侧边删除,table中选中取消的联动选中 ui框架:Ant design vue 组件:table 和 tag html中 <template v-for="tag in dataType"> <!-- key不能使用index --> <a-tag :key="tag.id" closable :afterClose="() => deleteDataType(tag

  • vue+swiper实现左右滑动的测试题功能

    最近在做一个测试题,前后可以切换题目,点击按钮选择答案,选择答案的同时改变按钮的背景色,如下图所示: 初始代码 我用了vue和swiper.所有的题目是一个对象数组,通过v-for渲染: <swiper ref="mySwiper" :options="swiperOptions"> <swiper-slide v-for="(item, index) in listData" :key="index">

  • Ant Design Vue table中列超长显示...并加提示语的实例

    我就废话不多说了,大家还是直接看代码吧~ <template> <a-row class="a-left"> <a-row> <p class="a-title">今日考勤状况</p> <a-row type="flex" justify="space-around"> <a-col :span="4" class="b

  • 利用vue3+ts实现管理后台(增删改查)

    简单的管理后台基本上就是数据的增删改查.主要就是 列表 + form 表单.每个页面的逻辑基本上都相同.不同的地方就是每个页面需要调用的具体 API 及参数. 以前 vue2 的时候最简单的做法是写出来一个页面的逻辑,然后直接 copy 到各个页面中,修改 API 及参数即可.高级一点的是利用 mixin 函数,将可复用逻辑抽离,每个页面引入 mixin. vue3 之后新增了composition API.本文就是利用composition API,将可复用的逻辑抽离到composition

  • vue中可编辑树状表格的实现代码

    vue中可编辑树状表格的代码如下所示: html代码 <template> <el-table :data="datatree" row-key="id" :tree-props="{children: 'children'}" > <el-table-column label="姓名" border> <template slot-scope="scope">

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

    业务场景 由于项目需求,需要对相关类目进行多选,类目数据量又特别大,业务逻辑是使用懒加载方式加载各级类目数据,编辑时回显用户选择的类目. 问题描述 使用Cascader级联选择器过程中主要存在的应用问题如下: 1.由于在未渲染节点数据的情况下编辑时无法找到对应的类目数据导致无法回显,如何自动全部加载已选择类目的相关节点数据: 2.提前加载数据后,点击相应父级节点出现数据重复等: 3.使用多个数据源相同的级联选择器,产生只能成功响应一个加载子级节点数据: 4.Vue中级联选择器相应数据完成加载,依

  • 详解关于element级联选择器数据回显问题

    element级联选择器数据回显问题 对于前端小菜鸡来说,被这个问题也是困扰了好久.也是百度的方法. 表单部分代码: <el-form-item label="部门名称:" prop="deptId"> <el-cascader placeholder="请选择部门" :props="depShowType" :options="deptData" filterable change-on

  • 浅谈vue项目4rs vue-router上线后history模式遇到的坑

    此次项目开发完后准备打包,因为hash模式的路径带#看着实在是有点丑,所以采用history模式.因为本次项目属于以前网站重构,但是seo问题,所以需要以前的一些地址路径写,所以vue-router的路径全部重改了.打包后查了网上一大堆,都说要把config里的index.js 里的build里的 assetsPublicPath: '/'改成'./',打包上线发现在hash模式下是没问题的, 但一旦改成history模式,有些动态js文件的路径都是错的.无奈之下死马当活马医,把'./'改回了'

  • 浅谈vue单一组件下动态修改数据时的全部重渲染

    今天在学习vue的过程中,发现一个有趣的现象. 在某一组件下的某一数据通过点击事件被动态修改的时候,对应view中的数据同步的进行了修改,没错,这不是废话吗,vue的一大特色就是数据的双向绑定.可有趣的是,该组件下我写的另一个用Math.random()的data值对应的值和视图也发生了变化 这就让我这个刚入门的小白有点奇怪了,我修改一个,怎么变了两个????脑洞放开一想,会不会数据在双向同步的时候,发生了什么,比如.是不是只要有一个节点变了,node都重新进行了加载??? 我想这其中的缘由必定

  • 浅谈Vue.js之初始化el以及数据的绑定说明

    1.初始化el 2.数据绑定说明 3.监听值的变化 以上这篇浅谈Vue.js之初始化el以及数据的绑定说明就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持我们.

  • 浅谈vue异步数据影响页面渲染

    今天遇到一个问题,要保证页面渲染前请求的数据已经得到了 由于user是在异步请求之后保存在session中,而在页面渲染时session中还没有user,页面直接报错. 因此我希望能在所有请求都得到后再去做页面的渲染. 1.先把id为app的div用v-if="appShow",定义appShow为false进行隐藏,避免渲染 2.写计数器,每1ms进行一次查询,如果session中已经有user,删除过滤器,移除滤布,appShow为true,开始渲染页面,这样可以保证页面的正常渲染

  • 浅谈vue获得后台数据无法显示到table上面的坑

    因为刚学vue然后自己自习了一下axios,然后想写一个简单的查询后台数据 <tr v-for=" user in uList"> <td>{{user.id}}</td> <td>{{user.name}}</td> <td>{{user.gender}}</td> </td> </tr> 然后先是写了这样一个代码 created: function () { axios.ge

  • 浅谈Vue Element中Select下拉框选取值的问题

    之前写了.一个原生的select的,因为展示效果原因,给删除掉了,忘记保存代码了,现在大家展示使用elementUI的下拉框封装一个组件,供咱们项目中经常调用,减少代码量. html: <el-select v-model="ite" placeholder="请选择" value-key="mateGroup"> <el-option style="width: auto" :disabled="

  • 浅谈vue首次渲染全过程

    目录 1.vue初始化 vue入口文件 完整版和运行时版本的区别 1.1.src/core/instace/index.js 1.2.src/core/index.js 1.3.src/platforms/web/runtime/index.js 1.4.src/platforms/web/entry-runtime-with-compiler.js 1.5.vue初始化总结 2.vue构造函数执行 2.1.beforeCreate钩子 2.2.created钩子 2.3.$mount函数 2.

  • 浅谈Vue使用Elementui修改默认的最快方法

    相信大家都需要过,在Vue中使用Elementui的时候,遇到最多也最蛋疼的问题就是修改默认样式,接下来直奔主题: // template <el-progress :text-inside="true" :stroke-width="26" :percentage="70" ></el-progress> 默认样式 方法1 1.找默认添加的类名 2.去掉scoped,scoped是Vue是限制独立组件中的CSS样式不被溢

随机推荐