vue複選框實現組件支持單選和多選

最近開發一個選擇電器的功能,電器分很多大類,而每一類又區分單選和多選。

我想只通過一個組件實現這個功能,於是使用了vant框架中的van-checkbox組件。

另外,每一種類的電器都支持可摺疊,方便查看。

當然其他框架的複選框組件實現也類似。

一、實現效果

vue複選框實現組件支持單選和多選

二、實現步驟

注意:後台給我的數據是沒有分類的,但是每一條數據都有type屬性,前端根據這個參數判斷類型。

1、代碼實現

<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) {
		  	// 獲取電器數據列表
		    const allArr = res.data
		    // 預定義電器種類
		    let typeList = []
		    // 預定義最終數據格式
		    let resultArray = []
		    allArr.map(item => {
		      // 定義處理後的數據格式:電器類型的key/value,和該類數據集合
		      const typeObj = {
		        name: item.typeName,
		        value: item.typeCode,
		        children: [],
		      };
		      // 遍曆數據,把所有電器類型篩選出來,對應類型的數據放進children
		      if (!typeList.includes(item.typeCode)) {
		        typeList.push(item.typeCode)
		        // 提前定義好v-modle中的數據類型,存放選中的電器集合
		        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;
		      }
		    });
		    // 定義初始展開的摺疊區域,這裡存入所有類型,默認全部展開
		    this.activeNames = this.activeNames.concat(typeList);
		    // 獲得最終的數據,雙向綁定到組件中
		    this.ElectricalRequireList = resultArray;
		  }
		});
	},
	changeSingleCheck (item, subItem) {
      // 判斷是否是單選項
      let singleFlag = 0
      if (item.value === 'shuicao' || item.value === 'cooking' || item.value === 'yanji') {
        singleFlag = 1
      }
      if (singleFlag === 1) {
        // 單選項中如果有其他項,取消其他項,改為當前項
        if (this.ElectricalChecked[item.value].length && !this.ElectricalChecked[item.value].includes(subItem.id)) {
          this.ElectricalChecked[item.value] = [subItem.id]
        }
      }
    }
}

2、代碼解析

只能說這裡沒有一條代碼是多餘的,而且都是經過踩坑之後,解決了所有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">
  • 這段代碼是這個組件的核心,把複選框組作為循環;
  • 每個複選框組到底是單選,還是多選,這是根據max屬性來做判斷。
  • max使用計算屬性來判斷,這裡需要給計算屬性傳參數,涉及到一個閉包的問題。
  • v-model綁定的值是一個對象,對象包含多個屬性,每個屬性對應每一個複選框組的值。注意:複選框組的值是一個數組,所以v-model是一個包含多個數組的對象。
  • ElectricalRequireList是所有數據的集合,是一個數組,每一項的數據都是{name: ‘煙機’, 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事件,用複選框實現單選功能會有一個問題:只有取消上一次選中的才能再選。
  • 這個函數不難理解:判斷是否為單選的組,把選中的值改為最新值就可以了。

三、增加【更多】功能

客戶增加需求,每個種類後,根據後台返回的數據,判斷是否有更多的電器,如圖:

請添加圖片描述

1、代碼實現

<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
          // 獲取【更多】中的數據,用來判讀是否顯示每個類型下的【更多】按鈕
          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: [],
            };
            // 如果【更多】中有該類型的數據,則顯示【更多】按鈕
            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)) {
        // 單選項中如果有其他項,取消其他項,改為當前項
        if (this.ElectricalChecked[item.value].length && !this.ElectricalChecked[item.value].includes(subItem.id)) {
          this.ElectricalChecked[item.value] = [subItem.id]
        }
      }
    },
   // 監聽外部選中的物品,同步【更多】中的選中狀態
   handleUserCheckedElectrical(ElectricalChecked) {
     this.changeUserCheckedElectrical(ElectricalChecked)
     Object.keys(ElectricalChecked).forEach((key) => {
       Object.keys(this.moreElectricalChecked).forEach((allKey) => {
         if (key == allKey) {
           // 如果裡邊存在,外部不存在,則刪除內部的數據
           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])
     })
     // 根據所有選中的電器,獲取互斥的所有電器
     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;
   },
   // 獲取【更多】頁面中顯示的數據
   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()
       }
     });
   },
   // 獲取案例詳細信息
   getCaseById(caseId) {
     request("getCaseById", { id: caseId }).then((res) => {
       if (res && res.code === 0) {
         this.caseLabelAppliances = res.data.label.appliances.data;
         // 調用遍曆數據的方法,傳遞三個參數:當前案例中的需求,全部需求,需要選中的需求
         this.handleCheck(
           this.caseLabelAppliances,
           this.ElectricalRequireBaseList,
           this.ElectricalChecked
         );
         // 從vuex判斷有沒有用戶之前勾選的數據
         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判斷有沒有用戶之前勾選的數據
     Object.keys(this.userCheckedMore).forEach(key => {
       this.moreElectricalChecked[key] = this.userCheckedMore[key];
     })
     // 初始狀態判斷【更多】中是否有選中的數據,有則展示到外部
     this.setMoreSelectToAll()
   },
   // 初始狀態判斷【更多】中是否有選中的數據,有則展示到外部
   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)、初始進入頁面,會調用兩個接口:一個是獲取主頁面的電器,另一個是獲取【更多】中的電器。

(2)、進入頁面後,會自動勾選一些項,這是根據接口返回的數據勾選的。

this.$nextTick(()=>{
  this.getCaseById(curCaseId);
})

這裡要在頁面渲染完畢後,再勾選。

(3)、在【更多】里勾選的電器,要同步更新到主頁面。這需要把【更多】里選中的電器的數據增加到主頁面數據上,還要把勾選的值添加到主頁面已選項中。

// 監聽【更多】中選中的物品,同步到外部展示
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)、當取消選中的電器時,如果取消的是當時從【更多】選過來的電器,則把該電器從主頁面刪除,同時刪除【更多】里的選中狀態

// 監聽外部選中的物品,同步【更多】中的選中狀態
handleUserCheckedElectrical(ElectricalChecked) {
  this.changeUserCheckedElectrical(ElectricalChecked)
  Object.keys(ElectricalChecked).forEach((key) => {
    Object.keys(this.moreElectricalChecked).forEach((allKey) => {
      if (key == allKey) {
        // 如果裡邊存在,外部不存在,則刪除內部的數據
        this.moreElectricalChecked[allKey].forEach(newValItem => {
          if(!ElectricalChecked[key].includes(newValItem)) {
            let index = this.moreElectricalChecked[allKey].indexOf(newValItem)
            this.moreElectricalChecked[allKey].splice(index, 1)
          }
        })
      }
    })
  })
}

總結

以上為個人經驗,希望能給大家一個參考,也希望大家多多支持。

原創文章,作者:簡單一點,如若轉載,請註明出處:https://www.506064.com/zh-hk/n/7010.html

(1)
打賞 微信掃一掃 微信掃一掃 支付寶掃一掃 支付寶掃一掃
簡單一點的頭像簡單一點
上一篇 2024-04-26 11:06
下一篇 2024-04-26 11:17

發表回復

登錄後才能評論