vue用復(fù)選框?qū)崿F(xiàn)組件且支持單選和多選操作方式
前言
最近開發(fā)一個選擇電器的功能,電器分很多大類,而每一類又區(qū)分單選和多選。
我想只通過一個組件實現(xiàn)這個功能,于是使用了vant框架中的van-checkbox組件。
另外,每一種類的電器都支持可折疊,方便查看。
當(dāng)然其他框架的復(fù)選框組件實現(xiàn)也類似。
一、實現(xiàn)效果
二、實現(xiàn)步驟
注意:后臺給我的數(shù)據(jù)是沒有分類的,但是每一條數(shù)據(jù)都有type屬性,前端根據(jù)這個參數(shù)判斷類型。
1、代碼實現(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ù),把所有電器類型篩選出來,對應(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) { // 判斷是否是單選項 let singleFlag = 0 if (item.value === 'shuicao' || item.value === 'cooking' || item.value === 'yanji') { singleFlag = 1 } if (singleFlag === 1) { // 單選項中如果有其他項,取消其他項,改為當(dā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">
- 這段代碼是這個組件的核心,把復(fù)選框組作為循環(huán);
- 每個復(fù)選框組到底是單選,還是多選,這是根據(jù)max屬性來做判斷。
- max使用計算屬性來判斷,這里需要給計算屬性傳參數(shù),涉及到一個閉包的問題。
- v-model綁定的值是一個對象,對象包含多個屬性,每個屬性對應(yīng)每一個復(fù)選框組的值。注意:復(fù)選框組的值是一個數(shù)組,所以v-model是一個包含多個數(shù)組的對象。
- ElectricalRequireList是所有數(shù)據(jù)的集合,是一個數(shù)組,每一項的數(shù)據(jù)都是{name: '煙機(jī)', value: 'yanji', children: []}。
(2)、<van-collapse-item>
這個沒啥可說的,就是加個折疊的功能,像我這種展示圖片的,高度會占用很大空間,有必要加個折疊。
(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)單選功能會有一個問題:只有取消上一次選中的才能再選。
- 這個函數(shù)不難理解:判斷是否為單選的組,把選中的值改為最新值就可以了。
三、增加【更多】功能
客戶增加需求,每個種類后,根據(jù)后臺返回的數(shù)據(jù),判斷是否有更多的電器,如圖:
1、代碼實現(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ù),用來判讀是否顯示每個類型下的【更多】按鈕 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) { // 判斷是否是單選項 let singleFlag = 0 if (item.value === 'shuicao' || item.value === 'cooking' || item.value === 'yanji') { singleFlag = 1 } if (singleFlag === 1 && !mutexValue[item.value].includes(subItem.id)) { // 單選項中如果有其他項,取消其他項,改為當(dā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() { // 處理選擇電器時的互斥項 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ù)的方法,傳遞三個參數(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)入頁面,會調(diào)用兩個接口:一個是獲取主頁面的電器,另一個是獲取【更多】中的電器。
(2)、進(jìn)入頁面后,會自動勾選一些項,這是根據(jù)接口返回的數(shù)據(jù)勾選的。
this.$nextTick(()=>{ this.getCaseById(curCaseId); })
這里要在頁面渲染完畢后,再勾選。
(3)、在【更多】里勾選的電器,要同步更新到主頁面。這需要把【更多】里選中的電器的數(shù)據(jù)增加到主頁面數(shù)據(jù)上,還要把勾選的值添加到主頁面已選項中。
// 監(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)取消選中的電器時,如果取消的是當(dāng)時從【更多】選過來的電器,則把該電器從主頁面刪除,同時刪除【更多】里的選中狀態(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é)
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
Vue如何實現(xiàn)將后端返回二進(jìn)制文件在瀏覽器自動下載
Vue項目開發(fā)中,遇到界面下載功能時,前端如何實現(xiàn)將后端返回二進(jìn)制文件在瀏覽器自動下載呢,本文就來和大家聊聊具體的解決方法吧2024-11-11