项目中,原始使用 checkbox 的话,一般绑定 v-model 就行,但是,如果想实现类似微信从通讯录选中好友来建群的效果,如下图,加上可以组里面再选择,就需要将 v-model 拆解成 checked 和 change 事件,然后封装单项组件。

v-model 可以绑定两种类型的值,Boolean和Array。所以组件封装之后可以这样使用:
//- item是内容区的相关信息,比如头像 昵称 uid
//- 1 boolean类型的时候,通常用于全选 组选的情况
checkboxEnhanced(v-model='bool') 同意协议
//- 2 array类型的时候,这时候可以循环
checkboxEnhanced(v-for="(item,index) in list" v-model='arr' :value='item.code') {{item.name}}

v-model 是 boolean 的时候
组件逻辑
- 将原始的input隐藏,通过点击内容区触发input的click事件,从而达到控制其change事件
- change事件会改变元素的checked属性,再向父组件派发事件,父组件根据传递的值更新自己的checked,而更新后的值又会再影响这个组件的checked,形成闭环。
- 这里为了方便父组件使用v-model,将model设置为change事件和checked vue官网文档
<!-- checkboxBoolean.vue -->
<template lang="pug">
div.checkbox-box
div.checkbox-content-box(@click='$refs.input.click()')
//- 自定义选中图标
div.icon-box
img.icon(alt='' :src='checked?"https://blog-huahua.oss-cn-beijing.aliyuncs.com/blog/code/icon_selected.png":"https://blog-huahua.oss-cn-beijing.aliyuncs.com/blog/code/icon_not_selected.png"')
div.content
//- slot可以自定义后面的内容
slot
//- 这里可以根据自己的项目灵活改变
div.avatar-box
img.img-avatar(alt='' :src='item.avatar || " https://blog-huahua.oss-cn-beijing.aliyuncs.com/blog/code/default_avatar.png"')
.name {{item.name}}
//- 原始input隐藏,这里的value看情况使用,可以不传,change是将选择事件抛出去,让父组件知晓
input(ref='input' hidden type='checkbox' :checked='checked' :value='value' @change='changeInput($event)')
</template>
<script>
/**
使用方法
checkbox-boolean(v-model='checked') 嘿哈
加上value和change事件也是没问题的
checkbox-boolean(v-model='checked' :item='item' :value='item.code' @change='handleChange')
*/
export default {
name: "checkbox-boolean",
model: {
prop: "checked",
event: "change",
},
props: {
item: {
type: Object,
default() {
return {};
},
},
checked: {
type: Boolean,
required: true,
default() {
return false;
},
},
value: {
// type: [Boolean, String, Number],
default() {
return this.item;
},
},
},
methods: {
changeInput($event) {
// 第一项就将是否选中扔出去,这里注意,扔出去之后,父组件用v-model的话,父组件的值会自动变化
// v-model是个语法糖,本质上相当于父组件 checkbox-item(:checked='checked' @change='checked=$event')
this.$emit("change", $event.target.checked, $event);
},
},
};
</script>
<style scoped>
.checkbox-box {
display: flex;
margin-left: 30px;
margin-right: 30px;
}
.checkbox-content-box {
display: flex;
height: 80px;
line-height: 80px;
margin-top: 12px;
margin-bottom: 12px;
width: 100%;
}
.icon {
width: 34px;
height: 34px;
display: inline-block;
vertical-align: middle;
}
.content {
display: flex;
margin-left: 20px;
}
.img-avatar {
display: block;
width: 80px;
height: 80px;
border-radius: 50%;
}
.name {
margin-left: 15px;
color: #333;
font-size: 30px;
}
</style>
v-model 是 Array 的时候
组件实现逻辑:
- 当父组件传过来的checked是数组类型的时候,
- 当前组件的初始状态curChecked是,看数组里有没有当前value,有就是选中,没有就不选中
- 数组是引用类型,为了不改变父组件的值,这里使用selectedList复制一份checked
- 当checkbox有change事件的时候,选中就将value值push到selectedList,否则删掉,然后将selectedList抛给父组件
- !!! 注意这里使用computed,以为别的复选框选择的时候,selectedList也会跟着变化
<!-- checkboxArray.vue -->
<template lang="pug">
div.checkbox-box
div.checkbox-content-box(@click='$refs.input.click()')
//- 自定义选中图标
div.icon-box
img.icon(alt='' :src='curChecked?"https://blog-huahua.oss-cn-beijing.aliyuncs.com/blog/code/icon_selected.png":"https://blog-huahua.oss-cn-beijing.aliyuncs.com/blog/code/icon_not_selected.png"')
div.content
//- slot可以自定义后面的内容
slot
//- 这里可以灵活改变
div.avatar-box
img.img-avatar(alt='' :src='item.avatar || " https://blog-huahua.oss-cn-beijing.aliyuncs.com/blog/code/default_avatar.png"')
.name {{item.name}}
//- 原始input隐藏,这里的value看情况使用,可以不传,change是将选择事件抛出去,让父组件知晓
input(ref='input' hidden type='checkbox' :checked='curChecked' :value='value' @change='changeInput($event)')
</template>
<script>
export default {
name: "checkbox-array",
model: {
prop: "checked",
event: "change",
},
props: {
item: {
type: Object,
default() {
return {};
},
},
checked: {
required: true,
default() {
return [];
},
},
value: {
default() {
return this.item;
},
},
},
computed: {
selectedList: {
get() {
return [...this.checked];
},
set() {
console.log();
},
},
curChecked() {
return this.checked.includes(this.value);
},
},
methods: {
changeInput($event) {
let { checked } = $event.target;
// 看下选中的值在数组中的索引,这里不用原生的value,因为$event.target.value始终是字符串类型
let index = this.selectedList.indexOf(this.value);
// 选择的时候,selectedList跟着变化
checked
? this.selectedList.push(this.value)
: index !== -1 && this.selectedList.splice(index, 1);
this.$emit("change", this.selectedList, $event);
},
},
};
</script>
<style scoped>
.checkbox-box {
display: flex;
margin-left: 30px;
margin-right: 30px;
}
.checkbox-content-box {
display: flex;
height: 80px;
line-height: 80px;
margin-top: 12px;
margin-bottom: 12px;
width: 100%;
}
.icon {
width: 34px;
height: 34px;
display: inline-block;
vertical-align: middle;
}
.content {
display: flex;
margin-left: 20px;
}
.img-avatar {
display: block;
width: 80px;
height: 80px;
border-radius: 50%;
}
.name {
margin-left: 15px;
color: #333;
font-size: 30px;
}
</style>
将两种情况合并
这里简单使用 component 组件,别忘了slot。 vue 官网介绍component
<!--CheckboxEnhanced.vue-->
<template lang="pug">
div
component(v-if="!isArray" is='CheckboxBoolean' v-bind='$attrs' v-on='$listeners')
//- 自定义后面的内容
slot
component(v-else is='CheckboxArray' v-bind='$attrs' v-on='$listeners')
slot
</template>
<script>
import CheckboxBoolean from "@/components/CheckboxBoolean";
import CheckboxArray from "@/components/CheckboxArray";
export default {
name: "checkbox-enhanced",
components: {
CheckboxArray,
CheckboxBoolean,
},
model: {
prop: "checked",
event: "change",
},
created() {
this.isArray = Array.isArray(this.$attrs.checked);
},
data() {
return {
isArray: false,
};
},
};
</script>
综合使用
//- Home.vue
<template lang="pug">
div
h2 boolean
div
div {{checked}}
checkbox-enhanced(v-model='checked' @change='change') 同意协议
h2 array
div
div {{selectedCodes}}
checkbox-enhanced(v-for='(item,index) in items' :key='index' v-model='selectedCodes' :item='item' :value='item.code' @change='changeItem')
h2 array 自定义传入的内容
div
div {{selectedCodes}}
checkbox-enhanced(v-for='(item,index) in items' :key='index' v-model='selectedCodes' :value='item.code' @change='changeItem') {{item.name}}
h3 group
section(v-for='(group,index) in groups' :key='index')
br
div {{group.name}}
checkbox-enhanced(v-for='(item,idx) in group.children' :key='idx' v-model='selectedCodes' :item='item' :value='item.code' @change='changeItem')
</template>
<script>
// @ is an alias to /src
import CheckboxEnhanced from "@/components/CheckboxEnhanced.vue";
export default {
name: "Home",
components: {
CheckboxEnhanced
},
data() {
return {
item: { code: 6, name: "组" },
items: [
{ code: 1, name: "huahua" },
{ code: 2, name: "huahua2" }
],
selectedCodes: [],
checked: true,
groups: [
{
name: "a组的成员",
children: [{ code: 3, name: "huahua3" }]
},
{
name: "b组的成员",
children: [{ code: 4, name: "huahua4" }]
}
]
};
},
watch: {
groups(newVal) {
console.log(newVal);
}
},
methods: {
changeItem() {
console.log(arguments);
},
change(group) {
console.log(group);
}
}
};
</script>