業務場景
由於項目需求,需要對相關類目進行多選,類目數據量又特別大,業務邏輯是使用懶加載方式加載各級類目數據,編輯時回顯用戶選擇的類目。
問題描述
使用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>