最近在开发一个移动端表单提交的需求时,发现 Vant 的选择器对多选场景支持不够友好。而产品需求场景需要能够实现多选功能,于是决定手动封装一个多选组件,本文章将介绍如何创建一个类似 van-picker 的多选组件。
实现的效果图如下:
选中后展示效果图:
实现思路:
1、模板结构
该组件的模板结构主要由以下几个部分组成:
输入框 (van-field):使用 v-model 绑定到 resultLabel,显示当前选中的值。设置为只读 (readonly) 和可点击 (is-link),点击时会弹出选择器。
弹出层 (van-popup):使用 v-model=“show” 控制弹出层的显示与隐藏。弹出层包含一个工具栏 (van-picker__toolbar) 和内容区域 (multiplePicker-content)。工具栏中有取消和确认按钮,分别调用 cancel 和 onConfirm 方法。
多选框组 (van-checkbox-group):包含多个 van-cell,每个 van-cell 对应一个选项。每个 van-cell 内部有一个 van-checkbox,用于选择或取消选择。
<template>
<div class="multiplePicker">
<div class="van-hairline--bottom">
<van-field
v-model="resultLabel"
v-bind="$attrs"
readonly
:is-link="$attrs.disabled === undefined"
:rules="rules"
@click="showPopup($attrs.disabled)"
/>
<van-popup v-model="show" position="bottom" class="">
<div class="van-picker__toolbar">
<button type="button" class="van-picker__cancel" @click="cancel">
取消
</button>
<div class="van-ellipsis van-picker__title">{{ $attrs.label }}</div>
<button type="button" class="van-picker__confirm" @click="onConfirm">
确认
</button>
</div>
<div class="multiplePicker-content">
<van-checkbox-group
ref="checkboxGroup"
v-model="checkboxValue"
@change="change"
>
<van-cell-group>
<van-cell
v-for="(item, index) in columnsData"
:key="item[option.value]"
:title="item[option.label]"
clickable
@click="toggle(index)"
>
<template #right-icon>
<van-checkbox
ref="checkboxes"
shape="square"
:name="item[option.value]"
/>
</template>
</van-cell>
</van-cell-group>
</van-checkbox-group>
</div>
</van-popup>
</div>
</div>
</template>
2、方法说明:
getData(val):根据选中的值过滤出对应的选项。
onConfirm():确认选择,更新 resultValue 并关闭弹出层,同时触发 confirm 事件。
change(val):当选择状态变化时触发,更新选中值并触发 change 事件。
cancel():取消选择,关闭弹出层并触发 cancel 事件。
toggle(index):切换单个复选框的状态。
showPopup(disabled):控制弹出层的显示,如果组件被禁用则不显示弹出层。
<script>
export default {
name: 'MultiplePicker',
model: {
prop: 'selectValue'
},
props: {
columns: {
type: Array,
default: function() {
return [];
}
},
selectValue: {
type: Array,
default: function() {
return [];
}
},
option: {
type: Object,
default: function() {
return { label: 'label', value: 'value' };
}
},
rules: {
type: Array,
default: function() {
return [];
}
}
},
data() {
return {
show: false,
columnsData: JSON.parse(JSON.stringify(this.columns)),
checkboxValue: JSON.parse(JSON.stringify(this.selectValue)),
resultValue: JSON.parse(JSON.stringify(this.selectValue))
};
},
computed: {
resultLabel: {
get() {
const res = this.columns.filter(item => {
return this.resultValue.indexOf(item[this.option.value]) > -1;
});
const resLabel = res.map(item => {
return item[this.option.label];
});
return resLabel.join(',');
},
// eslint-disable-next-line no-empty-function
set() {}
}
},
watch: {
selectValue: function(newVal) {
this.resultValue = newVal;
},
resultValue(val) {
this.columnsData = JSON.parse(JSON.stringify(this.columns));
this.$emit('input', val);
}
},
methods: {
getData(val) {
const res = this.columnsData.filter(item => {
return val.indexOf(item[this.option.value]) > -1;
});
return res;
},
onConfirm() {
this.resultValue = this.checkboxValue;
this.show = !this.show;
this.$emit('confirm', this.resultValue, this.getData(this.resultValue));
},
change(val) {
this.$emit('change', val, this.getData(this.resultValue));
},
cancel() {
this.show = !this.show;
this.$emit('cancel', this.resultValue);
},
toggle(index) {
this.$refs.checkboxes[index].toggle();
},
showPopup(disabled) {
this.columnsData = JSON.parse(JSON.stringify(this.columns));
this.checkboxValue = JSON.parse(JSON.stringify(this.selectValue));
this.resultValue = JSON.parse(JSON.stringify(this.selectValue));
if (disabled !== undefined && disabled !== false) {
return false;
} else {
this.show = !this.show;
}
}
}
};
</script>
设置 .multiplePicker-content 的最大高度为视口高度的 50%,并允许垂直滚动
<style lang="less" scoped>
.multiplePicker {
&-content {
max-height: 50vh;
overflow-y: auto;
}
}
</style>