vue用復(fù)選框?qū)崿F(xiàn)組件且支持單選和多選操作方式
前言
最近開發(fā)一個(gè)選擇電器的功能,電器分很多大類,而每一類又區(qū)分單選和多選。
我想只通過一個(gè)組件實(shí)現(xiàn)這個(gè)功能,于是使用了vant框架中的van-checkbox組件。
另外,每一種類的電器都支持可折疊,方便查看。
當(dāng)然其他框架的復(fù)選框組件實(shí)現(xiàn)也類似。
一、實(shí)現(xiàn)效果

二、實(shí)現(xiàn)步驟
注意:后臺(tái)給我的數(shù)據(jù)是沒有分類的,但是每一條數(shù)據(jù)都有type屬性,前端根據(jù)這個(gè)參數(shù)判斷類型。
1、代碼實(shí)現(xiàn)
<template>
<van-collapse class="layout-collapse" v-model="activeNames">
<van-checkbox-group class="layout-checkbox-group" :max="singleCheck(item.value)" v-model="ElectricalChecked[item.value]" v-for="item in ElectricalRequireList" :key="item.value" @change="changeUserCheckedElectrical(ElectricalChecked)">
<van-collapse-item :title="singleCheckTitle(item)" :name="item.value">
<van-checkbox
shape="square"
v-for="subItem in item.children"
:key="subItem.value"
:name="subItem.id"
@click="changeSingleCheck(item, subItem)"
>
<template>
<van-image fit="cover" :src="subItem.url" />
<span class="name">{{ subItem.name }}</span>
</template>
</van-checkbox>
</van-collapse-item>
</van-checkbox-group>
</van-collapse>
<template>data() {
return {
activeNames: [],
ElectricalRequireList: [],
}
},
computed: {
singleCheck: () => {
return (value) => ((value === 'shuicao' || value === 'cooking' || value === 'yanji') ? 1 : 0);
},
},
methods: {
getAllAppliances() {
request("getAllAppliances").then((res) => {
if (res && res.code === 0) {
// 獲取電器數(shù)據(jù)列表
const allArr = res.data
// 預(yù)定義電器種類
let typeList = []
// 預(yù)定義最終數(shù)據(jù)格式
let resultArray = []
allArr.map(item => {
// 定義處理后的數(shù)據(jù)格式:電器類型的key/value,和該類數(shù)據(jù)集合
const typeObj = {
name: item.typeName,
value: item.typeCode,
children: [],
};
// 遍歷數(shù)據(jù),把所有電器類型篩選出來,對(duì)應(yīng)類型的數(shù)據(jù)放進(jìn)children
if (!typeList.includes(item.typeCode)) {
typeList.push(item.typeCode)
// 提前定義好v-modle中的數(shù)據(jù)類型,存放選中的電器集合
this.$set(this.ElectricalChecked, item.typeCode, [])
typeObj.children.push(item)
return resultArray.push(typeObj)
} else {
resultArray.forEach((subItem) => {
if (subItem.value === item.typeCode) {
subItem.children.push(item);
}
});
return;
}
});
// 定義初始展開的折疊區(qū)域,這里存入所有類型,默認(rèn)全部展開
this.activeNames = this.activeNames.concat(typeList);
// 獲得最終的數(shù)據(jù),雙向綁定到組件中
this.ElectricalRequireList = resultArray;
}
});
},
changeSingleCheck (item, subItem) {
// 判斷是否是單選項(xiàng)
let singleFlag = 0
if (item.value === 'shuicao' || item.value === 'cooking' || item.value === 'yanji') {
singleFlag = 1
}
if (singleFlag === 1) {
// 單選項(xiàng)中如果有其他項(xiàng),取消其他項(xiàng),改為當(dāng)前項(xiàng)
if (this.ElectricalChecked[item.value].length && !this.ElectricalChecked[item.value].includes(subItem.id)) {
this.ElectricalChecked[item.value] = [subItem.id]
}
}
}
}
2、代碼解析
只能說這里沒有一條代碼是多余的,而且都是經(jīng)過踩坑之后,解決了所有bug之后的。
(1)、<van-checkbox-group>
<van-checkbox-group class="layout-checkbox-group" :max="singleCheck(item.value)" v-model="ElectricalChecked[item.value]" v-for="item in ElectricalRequireList" :key="item.value">
- 這段代碼是這個(gè)組件的核心,把復(fù)選框組作為循環(huán);
- 每個(gè)復(fù)選框組到底是單選,還是多選,這是根據(jù)max屬性來做判斷。
- max使用計(jì)算屬性來判斷,這里需要給計(jì)算屬性傳參數(shù),涉及到一個(gè)閉包的問題。
- v-model綁定的值是一個(gè)對(duì)象,對(duì)象包含多個(gè)屬性,每個(gè)屬性對(duì)應(yīng)每一個(gè)復(fù)選框組的值。注意:復(fù)選框組的值是一個(gè)數(shù)組,所以v-model是一個(gè)包含多個(gè)數(shù)組的對(duì)象。
- ElectricalRequireList是所有數(shù)據(jù)的集合,是一個(gè)數(shù)組,每一項(xiàng)的數(shù)據(jù)都是{name: '煙機(jī)', value: 'yanji', children: []}。
(2)、<van-collapse-item>
這個(gè)沒啥可說的,就是加個(gè)折疊的功能,像我這種展示圖片的,高度會(huì)占用很大空間,有必要加個(gè)折疊。
(3)、<van-checkbox>
<van-checkbox shape="square" v-for="subItem in item.children" :key="subItem.value" :name="subItem.id" @click="changeSingleCheck(item, subItem)">
這地方說一下click事件的意義吧。
- 如果不加click事件,用復(fù)選框?qū)崿F(xiàn)單選功能會(huì)有一個(gè)問題:只有取消上一次選中的才能再選。
- 這個(gè)函數(shù)不難理解:判斷是否為單選的組,把選中的值改為最新值就可以了。
三、增加【更多】功能
客戶增加需求,每個(gè)種類后,根據(jù)后臺(tái)返回的數(shù)據(jù),判斷是否有更多的電器,如圖:

1、代碼實(shí)現(xiàn)
<van-collapse class="layout-collapse" v-model="activeNames">
<van-checkbox-group class="layout-checkbox-group" :max="singleCheck(item.value)" v-model="ElectricalChecked[item.value]" v-for="item in ElectricalRequireList" :key="item.value" @change="handleUserCheckedElectrical(ElectricalChecked)">
<van-collapse-item :title="singleCheckTitle(item)" :name="item.value">
<div class="van-collapse-item">
<div class="van-collapse-item__title">
{{singleCheckTitle(item)}}
<div class="layout-button-more" v-if="item.showMore">
<span class="layout-button-more-text" @click="handleMore(item)">更多 > </span>
</div>
</div>
<div class="van-collapse-item__wrapper">
<van-checkbox
v-for="subItem in item.children"
:key="subItem.id"
:name="subItem.id"
@click="changeSingleCheck(item, subItem, mutexValue)"
:disabled="mutexValue[item.value].includes(subItem.id)"
>
<template>
<van-image fit="cover" :src="subItem.url" />
<span class="name">{{ subItem.name }}</span>
</template>
</van-checkbox>
</div>
</div>
</van-collapse-item>
</van-checkbox-group>
</van-collapse>
<!-- 更多需求 -->
<van-popup v-model="moreRequirementShow" position="bottom" :lazy-render="false" round style="height: 80%">
<div class="more-require-wrapper">
<div class="more-require-title">
<div class="more-require-title-line"></div>
</div>
<div class="more-require-content">
<div class="more-require-panel">
<van-collapse class="layout-collapse" v-model="activeMoreNames">
<van-checkbox-group class="layout-checkbox-group" :max="moreSingleCheck(item.value)" v-model="moreElectricalChecked[item.value]" v-for="item in caseList" :key="item.value" @change="handleUserCheckedMore(moreElectricalChecked)">
<van-collapse-item :title="moreSingleCheckTitle(item)" :name="item.value">
<div class="van-collapse-item">
<div class="van-collapse-item__title">
{{singleCheckTitle(item)}}
</div>
<div class="van-collapse-item__wrapper">
<van-checkbox v-for="subItem in item.children" :key="subItem.id" :name="subItem.id"
:disabled="mutexValueMore[item.value].includes(subItem.id)">
<template>
<van-image fit="cover" :src="subItem.url"/>
<span class="name">{{subItem.name}}</span>
</template>
</van-checkbox>
</div>
</div>
</van-collapse-item>
</van-checkbox-group>
</van-collapse>
</div>
</div>
<div class="more-require-button">
<van-button round type="primary" @click="confirmMore"
>確定</van-button
>
</div>
</div>
</van-popup>data() {
return {
activeNames: [],
ElectricalRequireList: [],
ElectricalChecked: {},
moreRequirementShow: false,
mutexValue: {},
}
},
computed: {
singleCheck: () => {
return (value) => ((value === 'shuicao' || value === 'cooking' || value === 'yanji') ? 1 : 0);
},
singleCheckTitle: () => {
return (item) => ((item.value === 'shuicao' || item.value === 'cooking' || item.value === 'yanji') ? item.name + "(單選)" : item.name);
},
},
watch: {
ElectricalChecked: {
handler (val) {
// 處理互斥操作
this.handleMutexValue()
},
deep: true
},
}
methods: {
getAllAppliances() {
request("getAllAppliances").then((res) => {
if (res && res.code === 0) {
const allArr = res.data.filter(
(col) => col.isMain === 0
);
this.ElectricalRequireBaseList = res.data
// 獲取【更多】中的數(shù)據(jù),用來判讀是否顯示每個(gè)類型下的【更多】按鈕
let allMoreArr = res.data.filter(
(col) => col.isMain === 1
);
this.moreElectricalRequireBaseList = allMoreArr
let typeMoreList = allMoreArr.map(item => {
return item.typeCode
})
let typeList = []
let resultArray = []
allArr.map(item => {
let typeObj = {
name: item.typeName,
value: item.typeCode,
showMore: false,
children: [],
};
// 如果【更多】中有該類型的數(shù)據(jù),則顯示【更多】按鈕
if(typeMoreList.includes(typeObj.value)) {
typeObj.showMore = true
}
if (!typeList.includes(item.typeCode)) {
typeList.push(item.typeCode)
this.$set(this.ElectricalChecked, item.typeCode, [])
this.$set(this.moreElectricalChecked, item.typeCode, [])
this.$set(this.mutexValue, item.typeCode, [])
this.$set(this.mutexValueMore, item.typeCode, [])
typeObj.children.push(item)
return resultArray.push(typeObj)
} else {
resultArray.forEach((subItem) => {
if (subItem.value === item.typeCode) {
subItem.children.push(item);
}
});
return;
}
});
this.activeNames = this.activeNames.concat(typeList);
this.ElectricalRequireList = resultArray;
}
});
},
changeSingleCheck (item, subItem, mutexValue) {
// 判斷是否是單選項(xiàng)
let singleFlag = 0
if (item.value === 'shuicao' || item.value === 'cooking' || item.value === 'yanji') {
singleFlag = 1
}
if (singleFlag === 1 && !mutexValue[item.value].includes(subItem.id)) {
// 單選項(xiàng)中如果有其他項(xiàng),取消其他項(xiàng),改為當(dāng)前項(xiàng)
if (this.ElectricalChecked[item.value].length && !this.ElectricalChecked[item.value].includes(subItem.id)) {
this.ElectricalChecked[item.value] = [subItem.id]
}
}
},
// 監(jiān)聽外部選中的物品,同步【更多】中的選中狀態(tài)
handleUserCheckedElectrical(ElectricalChecked) {
this.changeUserCheckedElectrical(ElectricalChecked)
Object.keys(ElectricalChecked).forEach((key) => {
Object.keys(this.moreElectricalChecked).forEach((allKey) => {
if (key == allKey) {
// 如果里邊存在,外部不存在,則刪除內(nèi)部的數(shù)據(jù)
this.moreElectricalChecked[allKey].forEach(newValItem => {
if(!ElectricalChecked[key].includes(newValItem)) {
let index = this.moreElectricalChecked[allKey].indexOf(newValItem)
this.moreElectricalChecked[allKey].splice(index, 1)
}
})
}
})
})
},
handleMore() {
this.moreRequirementShow = true;
},
// 處理互斥操作
handleMutexValue() {
// 處理選擇電器時(shí)的互斥項(xiàng)
const allSelectId = []
Object.keys(this.mutexValue).forEach(key => {
this.mutexValue[key] = []
this.mutexValueMore[key] = []
})
Object.keys(this.ElectricalChecked).forEach(key => {
allSelectId.push(...this.ElectricalChecked[key])
})
Object.keys(this.moreElectricalChecked).forEach(key => {
allSelectId.push(...this.moreElectricalChecked[key])
})
// 根據(jù)所有選中的電器,獲取互斥的所有電器
allSelectId.forEach(item => {
this.ElectricalRequireBaseList.forEach(subItem => {
if(item == subItem.id && subItem.mutualExclusion) {
const mutualExclusionList = subItem.mutualExclusion.split(",").map(item => { return Number(item)})
Object.keys(this.mutexValue).forEach(key => {
this.mutexValue[key].push(...mutualExclusionList)
this.mutexValueMore[key].push(...mutualExclusionList)
this.mutexValue[subItem.typeCode] = []
})
}
})
})
},
confirmMore() {
this.moreRequirementShow = false;
},
// 獲取【更多】頁面中顯示的數(shù)據(jù)
getCaseList() {
request("getAllAppliances").then((res) => {
if (res && res.code === 0) {
let allMoreArr = res.data.filter((col) => col.isMain === 1);
this.moreElectricalRequireBaseList = allMoreArr
let typeMoreList = []
let resultMoreArray = []
allMoreArr.map(item => {
const typeObj = {
name: item.typeName,
value: item.typeCode,
children: []
}
if (!typeMoreList.includes(item.typeCode)) {
typeMoreList.push(item.typeCode)
typeObj.children.push(item)
return resultMoreArray.push(typeObj)
} else {
resultMoreArray.forEach(subItem => {
if (subItem.value === item.typeCode) {
subItem.children.push(item)
}
})
return
}
})
this.activeMoreNames = this.activeMoreNames.concat(typeMoreList)
this.caseList = resultMoreArray
// 判斷模板案例中是否有【更多】中的電器
this.handleSetMoreCheck()
}
});
},
// 獲取案例詳細(xì)信息
getCaseById(caseId) {
request("getCaseById", { id: caseId }).then((res) => {
if (res && res.code === 0) {
this.caseLabelAppliances = res.data.label.appliances.data;
// 調(diào)用遍歷數(shù)據(jù)的方法,傳遞三個(gè)參數(shù):當(dāng)前案例中的需求,全部需求,需要選中的需求
this.handleCheck(
this.caseLabelAppliances,
this.ElectricalRequireBaseList,
this.ElectricalChecked
);
// 從vuex判斷有沒有用戶之前勾選的數(shù)據(jù)
Object.keys(this.userCheckedElectrical).forEach(key => {
this.ElectricalChecked[key] = this.userCheckedElectrical[key];
})
// 判斷模板案例中是否有【更多】中的電器
this.handleSetMoreCheck()
}
});
},
handleCheck(part, all, checked) {
// 遍歷電器列表
part.forEach((item) => {
all.forEach((allItem) => {
if (allItem.typeCode == item.code) {
// 具體類別下的已選電器
const caseElecs = item.data;
caseElecs.forEach((subItem) => {
if (subItem.id == allItem.id) {
checked[allItem.typeCode].push(subItem.id);
}
});
}
});
});
},
// 判斷模板案例中是否有【更多】中的電器
handleSetMoreCheck() {
this.handleCheck(
this.caseLabelAppliances,
this.moreElectricalRequireBaseList,
this.moreElectricalChecked
);
// 從vuex判斷有沒有用戶之前勾選的數(shù)據(jù)
Object.keys(this.userCheckedMore).forEach(key => {
this.moreElectricalChecked[key] = this.userCheckedMore[key];
})
// 初始狀態(tài)判斷【更多】中是否有選中的數(shù)據(jù),有則展示到外部
this.setMoreSelectToAll()
},
// 初始狀態(tài)判斷【更多】中是否有選中的數(shù)據(jù),有則展示到外部
setMoreSelectToAll() {
this.oldMoreElectricalChecked = JSON.parse(JSON.stringify(this.moreElectricalChecked))
Object.keys(this.moreElectricalChecked).forEach((key) => {
Object.keys(this.ElectricalChecked).forEach((allKey) => {
if (key == allKey && this.moreElectricalChecked[key].length) {
let selectKey = []
this.caseList.forEach(item => {
if (item.value == key) {
selectKey = item.children.filter(itemValue => this.moreElectricalChecked[key].includes(itemValue.id) )
}
})
this.ElectricalRequireList.forEach(item => {
if (item.value == key) {
item.children = item.children.concat(selectKey)
const map = new Map()
item.children = item.children.filter(itemKey => !map.has(itemKey.id) && map.set(itemKey.id, 1))
}
})
this.ElectricalChecked[allKey] = Object.assign(this.ElectricalChecked[allKey], this.moreElectricalChecked[key])
}
})
})
},
}
mounted() {
this.$nextTick(()=>{
this.getCaseById(curCaseId);
})
},
created() {
this.getAllAppliances();
this.getCaseList();
},
filters: {
ellipsis(value) {
if (!value) return "";
if (value.length > 8) {
return value.slice(0, 8) + "...";
}
return value;
},
},
2、代碼解析
新增的代碼中多了很多邏輯:
(1)、初始進(jìn)入頁面,會(huì)調(diào)用兩個(gè)接口:一個(gè)是獲取主頁面的電器,另一個(gè)是獲取【更多】中的電器。
(2)、進(jìn)入頁面后,會(huì)自動(dòng)勾選一些項(xiàng),這是根據(jù)接口返回的數(shù)據(jù)勾選的。
this.$nextTick(()=>{
this.getCaseById(curCaseId);
})
這里要在頁面渲染完畢后,再勾選。
(3)、在【更多】里勾選的電器,要同步更新到主頁面。這需要把【更多】里選中的電器的數(shù)據(jù)增加到主頁面數(shù)據(jù)上,還要把勾選的值添加到主頁面已選項(xiàng)中。
// 監(jiān)聽【更多】中選中的物品,同步到外部展示
handleUserCheckedMore (moreElectricalChecked) {
this.changeUserCheckedMore(moreElectricalChecked)
Object.keys(moreElectricalChecked).forEach((key) => {
Object.keys(this.oldMoreElectricalChecked).forEach((allKey) => {
if (key == allKey) {
// 如果newVal存在,oldVal不存在,則是新增的電器
moreElectricalChecked[key].forEach(newValItem => {
if(!this.oldMoreElectricalChecked[key].includes(newValItem)) {
let selectKey = []
this.caseList.forEach(item => {
if (item.value == key) {
selectKey = item.children.filter(itemValue => itemValue.id == newValItem )
}
})
this.ElectricalRequireList.forEach(item => {
if (item.value == key) {
// item.children = item.children ? item.children : []
item.children = item.children.concat(selectKey)
}
})
this.ElectricalChecked[allKey].push(newValItem)
}
})
// 如果newVal不存在,oldVal存在,則是減少的電器
this.oldMoreElectricalChecked[key].forEach(oldValItem => {
if(!moreElectricalChecked[key].includes(oldValItem)) {
this.ElectricalRequireList.forEach(item => {
if (item.value == key) {
item.children.forEach((ele, index) => {
if (ele.id == oldValItem) {
item.children.splice(index, 1)
}
})
}
})
}
})
}
})
})
this.oldMoreElectricalChecked = JSON.parse(JSON.stringify(this.moreElectricalChecked))
},
(4)、當(dāng)取消選中的電器時(shí),如果取消的是當(dāng)時(shí)從【更多】選過來的電器,則把該電器從主頁面刪除,同時(shí)刪除【更多】里的選中狀態(tài)
// 監(jiān)聽外部選中的物品,同步【更多】中的選中狀態(tài)
handleUserCheckedElectrical(ElectricalChecked) {
this.changeUserCheckedElectrical(ElectricalChecked)
Object.keys(ElectricalChecked).forEach((key) => {
Object.keys(this.moreElectricalChecked).forEach((allKey) => {
if (key == allKey) {
// 如果里邊存在,外部不存在,則刪除內(nèi)部的數(shù)據(jù)
this.moreElectricalChecked[allKey].forEach(newValItem => {
if(!ElectricalChecked[key].includes(newValItem)) {
let index = this.moreElectricalChecked[allKey].indexOf(newValItem)
this.moreElectricalChecked[allKey].splice(index, 1)
}
})
}
})
})
}
總結(jié)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
vue實(shí)現(xiàn)實(shí)時(shí)搜索顯示功能
這篇文章主要為大家詳細(xì)介紹了vue實(shí)現(xiàn)實(shí)時(shí)搜索顯示功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-04-04
vue計(jì)算屬性無法監(jiān)聽到數(shù)組內(nèi)部變化的解決方案
今天小編就為大家分享一篇vue計(jì)算屬性無法監(jiān)聽到數(shù)組內(nèi)部變化的解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2019-11-11
Vue實(shí)現(xiàn)移除數(shù)組中特定元素的方法小結(jié)
這篇文章主要介紹了Vue如何優(yōu)雅地移除數(shù)組中的特定元素,文中介紹了單個(gè)去除和批量去除的操作方法,并通過代碼示例講解的非常詳細(xì),具有一定的參考價(jià)值,需要的朋友可以參考下2024-03-03
Vue3格式化Volar報(bào)錯(cuò)的原因分析與解決
Volar 與vetur相同,volar是一個(gè)針對(duì)vue的vscode插件,下面這篇文章主要給大家介紹了關(guān)于Vue3格式化Volar報(bào)錯(cuò)的原因分析與解決方法,文中通過圖文介紹的非常詳細(xì),需要的朋友可以參考下2022-06-06
Vue3項(xiàng)目中優(yōu)雅實(shí)現(xiàn)微信授權(quán)登錄的方法
用戶在微信端中訪問第三方網(wǎng)頁,可以通過微信網(wǎng)頁授權(quán)機(jī)制獲取用戶的基本信息,進(jìn)而實(shí)現(xiàn)所需要的業(yè)務(wù)邏輯,這篇文章主要給大家介紹了關(guān)于Vue3項(xiàng)目中優(yōu)雅實(shí)現(xiàn)微信授權(quán)登錄的相關(guān)資料,需要的朋友可以參考下2021-09-09
Vue3使用md-editor-v3的實(shí)例解讀markdown文本
本文介紹了如何在Vue3項(xiàng)目中使用md-editor-v3實(shí)現(xiàn)Markdown編輯器,包括基礎(chǔ)實(shí)現(xiàn)、工具欄自定義、預(yù)覽功能以及擴(kuò)展功能2026-01-01
Vue計(jì)算屬性中reduce方法實(shí)現(xiàn)遍歷方式
這篇文章主要介紹了Vue計(jì)算屬性中reduce方法實(shí)現(xiàn)遍歷方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-03-03

