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

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值