官方推荐picker,但是我们项目用picker还要搭配Popup和cell、field,维护太太麻烦,所以自己封装一个基于picker的select
目前是vue2版本
vue3版本
2023-02-10 再次更新,解决select默认值问题
select单选
//封装VanFieldSelectPicker组件
<template>
<div class="dh-field">
<div class=" van-hairline--bottom">
<van-field
v-model="resultLabel"
v-bind="$attrs"
readonly
:is-link="$attrs.disabled === undefined || $attrs.disabled === false"
input-align="right"
@click="showPopu($attrs.disabled)"
error-message-align='right'
class="dh-cell"
/>
<van-popup v-model="show" position="bottom">
<van-field v-model="searchVal" class="search" :placeholder="searchPh" @input="search" v-if="isSearch" input-align="left"/>
<van-picker
v-bind="$attrs"
:columns="columnsData"
:default-index="default_index"
show-toolbar
@cancel="cancel"
@confirm="onConfirm"
@change="change"
:value-key="this.option.label"
/>
</van-popup>
</div>
</div>
</template>
<script>
export default {
name: 'VanFieldSelectPicker',
model: {
prop: 'selectValue'
},
props: {
columns: {
type: Array,
default: function () {
return []
}
},
selectValue: {
type: [String, Number],
default: ''
},
option: {//这里一定要注意,我们项目的字典固定是name,code如果你的项目不是,要么改这里默认值,要么调用的时候要自定
type: Object,
default: function () {
return { label: 'name', value: 'code' }
}
},
isSearch: {
type: Boolean,
default: false
},
diy: { // 进针对特殊场景,自定义数据
type: Boolean,
default: false
},
offOption: { // 关闭option配置key-value;当数据是非集合的数组的时候,开启
type: Boolean,
default: false
},
searchPh: {
type: String,
default: '输入搜索信息'
},
},
computed: {
resultLabel: {
get () {
const res = this.columns.filter(item => {
const data = this.offOption ? item : item[this.option.value]
return data === this.resultValue
})
let label = ''
if (res.length) {
label = this.offOption ? res[0] : res[0][this.option.label]
}
if (this.diy && !res.length && this.selectValue) {
this.setSearch()
}
return label || this.selectValue
},
set () {
}
}
},
created () {
sessionStorage.removeItem("houseData");
},
data () {
return {
show: false,
searchVal: '',
resultValue: this.selectValue,
columnsData: [],
default_index: 0 // 默认选中项索引
}
},
methods: {
setSearch () {
this.searchVal = this.selectValue
},
search (val) {
if (val) {
this.columnsData = this.columns.filter(item => {
const data = this.offOption ? item : item[this.option.label]
return data.indexOf(val) > -1
})
} else {
this.columnsData = JSON.parse(JSON.stringify(this.columns))
}
},
onConfirm (value, index) {
let data = ''
if (value) {
data = this.offOption ? value : value[this.option.value]
}
if (this.diy) {
this.resultValue = data || this.searchVal
} else {
this.resultValue = data
}
this.show = !this.show
this.$emit('confirm', value, index, this.resultValue)
},
change (val, index) {
let data = ''
if (val) {
data = this.offOption ? val : val[this.option.value]
}
this.$emit('change', val, index,data)
},
cancel () {
this.show = !this.show
this.$emit('cancel')
},
showPopu (disabled) {
this.columnsData = JSON.parse(JSON.stringify(this.columns))
this.resultValue = this.selectValue
if (disabled !== undefined && disabled !== false) {
return false
} else {
this.show = !this.show
}
}
},
watch: {
selectValue: function (newVal) {
this.resultValue = newVal
},
resultValue (val) {
this.searchVal = ''
this.columnsData = JSON.parse(JSON.stringify(this.columns))
this.$emit('input', val)
},
show (val) {
if (val) {
if (this.diy && this.resultLabel && this.searchVal) {
this.search(this.searchVal)
}
}
},
resultLabel: { // 显示默认选中
handler (val) {
if (val) {
if (this.offOption) {
this.default_index = this.columns.indexOf(val)
} else {
const data = this.columns.map(item => item[this.option.label])
this.default_index = data.indexOf(val)
}
}
},
immediate: true
}
}
}
</script>
<style lang="scss" scoped>
.dh-field {
padding: 0 16px;background:#fff;
.dh-cell.van-cell{padding: 10px 0;}
.dh-cell.van-cell--required::before{left: -8px;}
.van-popup{border-radius: 20px 20px 0 0;}
.search{
/deep/.van-field__value{background: #f7f7f7;padding: 2px;}
}
}
</style>
使用(建议全局注册)
//这里一定要注意option,我们项目的字典固定是name,code格式 { name: '我是选中的label', code: '1', other: '额外数据' },如果你的项目不是,要么改主组件的option默认值,要么调用的时候要自定option
//以下option的设置是可以省略,因为默认就是name,code;为了方便大家理解加上,请注意自己项目的实际情况
<van-field-select-picker
label="单选select"
placeholder="请选择"
v-model="value1"
:columns="columns"
:option="{label:'name',value:'code'}"
@confirm="confirm2"
/>
data:
value1: '1', // select选中的value
columns: [// 如果可选数据不是label-value,需要配置下option,如果是就不需要配置
{ name: '我是选中的label', code: '1', other: '额外数据' },
{ name: '我也是选中的label33333', code: '2', other: '额外数据' },
{ name: '我是选中的label', code: '21', other: '额外数据' },
{ name: '我也是选中的label555555555', code: '22', other: '额外数据' },
{ name: '我是选中的label', code: '11', other: '额外数据' },
{ name: '我也是选中的label', code: '52', other: '额外数据' },
{ name: '我是选中的label', code: '71', other: '额外数据' },
{ name: '我也是选中的label', code: '72', other: '额外数据' }
]
methods:
confirm2 (data1, index, data2) { // checkbox确定,
// tips 正常获取值,用不到这个方法,用v-model获取值即可,这个方法是告诉你,可以获取任何你想要的数据
// data1 当前这一条的obj数据
// index 当前选择的索引
// data2 当前这一条数据的value
console.log(data1, data2, index)
this.value4 = data1
}
Events 同 vant-picker
|-confirm -- 点击完成按钮时触发 -- 单列:选中[整个数据]的值,选中值对应的索引,选中的value-|
|-cancel -- 点击取消按钮时触发 -- -|
|-change -- 选项改变时触发 -- 单列:选中[整个数据]的值,选中值对应的索引,选中的value-|
属性
label-width ---------------------------label的一个宽度设置
label="单选select"---------------------label文字
:columns="columns"---------------------可选择的数据,只接受key-value格式的对象集合,[1,2,3]不可以
:option="{label:'name',value:'code'}"--数据的配置格式,默认label(显示的文字),value(具体值)
checkbox多选
//封装VanFieldCheckbox组件
<template>
<div class="dh-field ">
<div class="van-hairline--bottom">
<van-field
v-model="resultLabel"
v-bind="$attrs"
readonly
:is-link="$attrs.disabled === undefined"
error-message-align='right'
input-align="right"
@click="showPopu($attrs.disabled)"
class="dh-cell"
/>
<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}}<span v-if="max">(最多选{{max}}个)</span></div>
<button type="button" class="van-picker__confirm" @click="onConfirm">确认</button>
</div>
<div class="checkbox-con" style="max-height:264px;overflow-y:auto">
<van-field v-model="searchVal" placeholder="搜索" @input="search" v-if="isSearch" input-align="left"/>
<van-cell title="全选" v-if="!max">
<template #right-icon>
<van-checkbox name="all" @click="toggleAll" v-model="checkedAll"/>
</template>
</van-cell>
<van-cell title="不包含" v-if="noIncludeOption">
<template #right-icon>
<van-checkbox name="00" @click="unToggleAll" v-model="unCheckedAll"/>
</template>
</van-cell>
<van-checkbox-group :max="max" v-model="checkboxValue" @change="change" ref="checkboxGroup">
<van-cell-group>
<van-cell
v-for="(item, index) in columnsData"
clickable
:key="item[option.value]"
:title="item[option.label]"
@click="toggle(index,item)"
>
<template #right-icon>
<van-checkbox :name="item[option.value]" ref="checkboxes" />
</template>
</van-cell>
</van-cell-group>
</van-checkbox-group>
</div>
</van-popup>
</div>
</div>
</template>
<script>
export default {
name: 'xyzFieldCheckbox',
model: {
prop: 'selectValue'
},
props: {
columns: {
type: Array,
default: function () {
return []
}
},
selectValue: {
type: Array,
default: function () {
return []
}
},
option: {
type: Object,
default: function () {
return { label: 'name', value: 'code' }
}
},
isSearch: {
type: Boolean,
default: false
},
max: { // 最大可选
type: [Number, String],
default: 0
},
noIncludeOption: { // 是否有不包含选项
type: Boolean,
default: false
}
},
computed: {
resultLabel: {
get () {
var columns = JSON.parse(JSON.stringify(this.columns))
if (this.noIncludeOption) { // 开启不包含
columns.unshift({ name: '不包含', code: '00' })
}
const res = 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(',')
},
set () {
}
}
},
data () {
return {
show: false,
searchVal: '',
columnsData: JSON.parse(JSON.stringify(this.columns)),
checkboxValue: JSON.parse(JSON.stringify(this.selectValue)),
checkedAll: false,
resultValue: JSON.parse(JSON.stringify(this.selectValue)),
unCheckedAll: false
}
},
methods: {
search (val) {
if (val) {
this.columnsData = this.columnsData.filter(item => {
return item[this.option.label].indexOf(val) > -1
})
} else {
this.columnsData = JSON.parse(JSON.stringify(this.columns))
}
},
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(val))
},
cancel () {
this.show = !this.show
this.$emit('cancel')
},
toggle (index, item) {
this.unCheckedAll = false
this.checkboxValue = this.checkboxValue.filter(item => item != '00')
this.$emit('clickItem', item)
this.$refs.checkboxes[index].toggle()
},
toggleAll (all) { // 全选
this.unCheckedAll = false
this.checkboxValue = this.checkboxValue.filter(item => item != '00')
this.$refs.checkboxGroup.toggleAll(this.checkedAll)
},
unToggleAll () { // 不包含
this.$refs.checkboxGroup.toggleAll(false)
this.checkboxValue = ['00']
},
showPopu (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
}
}
},
watch: {
selectValue: function (newVal) {
this.resultValue = newVal
},
resultValue (val) {
this.searchVal = ''
this.columnsData = JSON.parse(JSON.stringify(this.columns))
this.$emit('input', val)
},
columnsData: {
handler (val) {
if (val.length && val.length === this.checkboxValue.length) {
this.checkedAll = true
} else {
this.checkedAll = false
}
},
immediate: true
},
checkboxValue: {
handler (val) {
if (val.length && val.length === this.columnsData.length) {
this.checkedAll = true
} else {
if (val.includes('00')) { // 不包含
this.unCheckedAll = true
}
this.checkedAll = false
}
},
immediate: true
}
}
}
</script>
<style lang="scss" scoped>
.dh-field {
padding: 0 16px;background:#fff;
.dh-cell.van-cell{padding: 10px 0;}
.dh-cell.van-cell--required::before{left: -8px;}
.van-popup{border-radius: 20px 20px 0 0;}
}
</style>
使用
内置[不包含]选项,表示已有选项都不符合,和全选是互斥的,实际项目中为空只表示用户没有录入,但不表示全部不选,所以加上了,不需要不开启即可(默认不开启),需要的话可跟项目实际情况再自定义
//这里一定要注意option,我们项目的字典固定是name,code格式 { name: '我是选中的label', code: '1', other: '额外数据' },如果你的项目不是,要么改主组件的option默认值,要么调用的时候要自定option
//以下option的设置是可以省略,因为默认就是name,code;为了方便大家理解加上,请注意自己项目的实际情况
<van-field-checkbox
label="多选checkbox"
placeholder="请选择"
v-model="value2"
:columns="columns"
label-width="100"
:option="{label:'name',value:'code'}"
@confirm="confirm"
/>
data:
value2: ['1'], // checkbox选中的value
columns: [// 如果可选数据不是label-value,需要配置下option,如果是就不需要配置
{ name: '我是选中的label', code: '1', other: '额外数据' },
{ name: '我也是选中的label33333', code: '2', other: '额外数据' },
{ name: '我是选中的label', code: '21', other: '额外数据' },
{ name: '我也是选中的label555555555', code: '22', other: '额外数据' },
{ name: '我是选中的label', code: '11', other: '额外数据' },
{ name: '我也是选中的label', code: '52', other: '额外数据' },
{ name: '我是选中的label', code: '71', other: '额外数据' },
{ name: '我也是选中的label', code: '72', other: '额外数据' }
]
methods:
confirm (data1, data2) { // select确定,
// tips 正常获取值,用不到这个方法,用v-model获取值即可,这个方法是告诉你,可以获取任何你想要的数据
// data1 是当前选中数据的value的数组
// data2 是当前选中数据的整个obj集合
console.log(data1, data2)
this.value3 = data2
},
Events 同 vant-picker
|-confirm -- 点击完成按钮时触发 -- 单列:选中[整个数据]的值,选中值对应的索引,选中的value-|
|-cancel -- 点击取消按钮时触发 -- 单列:选中[整个数据]的值,选中值对应的索引,选中的value-|
|-change -- 选项改变时触发 -- 单列:选中[整个数据]的值,选中值对应的索引,选中的value-|
|-clickItem -- 点击单条数据的时候触发,-返回:选中的当前数据-|
属性
label-width ---------------------------label的一个宽度设置
label="单选select"---------------------label文字
:columns="columns"---------------------可选择的数据,只接受key-value格式的对象集合,[1,2,3]不可以
:option="{label:'name',value:'code'}"--数据的配置格式,默认label(显示的文字),value(具体值)
应该可以继续优化,但是这个能满足目前的项目,有做过类似封装的小伙伴求交流
目前项目中还有需求是非field的多选和单选弹出,这里也单独处理, 有需要后面再放git代码
image.png