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

Posted wangmj518

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了浅谈Vue使用Cascader级联选择器数据回显中的坑相关的知识,希望对你有一定的参考价值。

这篇文章主要介绍了浅谈Vue使用Cascader级联选择器数据回显中的坑,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧

业务场景

由于项目需求,需要对相关类目进行多选,类目数据量又特别大,业务逻辑是使用懒加载方式加载各级类目数据,编辑时回显用户选择的类目。

问题描述

使用Cascader级联选择器过程中主要存在的应用问题如下:

1、由于在未渲染节点数据的情况下编辑时无法找到对应的类目数据导致无法回显,如何自动全部加载已选择类目的相关节点数据;

2、提前加载数据后,点击相应父级节点出现数据重复等;

3、使用多个数据源相同的级联选择器,产生只能成功响应一个加载子级节点数据;

4、Vue中级联选择器相应数据完成加载,依然无法回显。

解决思路

Cascader级联选择器在需要回显的节点数据都存在的情况下,方可完成回显,首先想到的是把选中节点相关的数据全部获取到即可,遍历已选择的节点数据,遍历加载相对应的数据。(如果多个级联选择器使用同一个数据源,使用深拷贝将数据分开,避免产生影响)

由于是级联的数据懒加载,需要每一级相应的节点数据加载完进行下一步,故使用ES6中的Promise,将子级节点数据加载封装成一个Promise,待Promise执行完成,对列表数据遍历获取完成后返回即可。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

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中完成数据你回显。

相关代码

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

159

160

161

162

163

164

165

166

167

168

169

170

171

172

173

174

175

176

177

178

179

180

181

182

<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 级联选择的一种写法

简单记录类似省、市、区或品牌、车系、车型等多级结构,级联选择添加并展示的一种写法:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

159

160

161

162

163

164

165

166

167

168

169

170

171

172

173

174

175

176

177

178

179

180

181

182

183

184

185

186

187

188

189

190

191

192

193

194

195

196

197

198

199

200

201

202

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级联选择器数据回显中的坑就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持脚本之家。

开发者涨薪指南 48位大咖的思考法则、工作方式、逻辑体系

以上是关于浅谈Vue使用Cascader级联选择器数据回显中的坑的主要内容,如果未能解决你的问题,请参考以下文章

Vue+ElementUI Cascader级联选择器 省市区编辑回显(通过CodeToText,TextToCode辅助)

el-cascader级联选择懒加载数据回显解决办法

修改antd级联选择器(cascader)

基于layui的cascader级联选择器

elementUI的级联选择器el-cascader

怎么获取cascader级联选择器的值