先上效果图
展开下拉
点击新增
在官方文档中发现,2.4.3版本对select新增了header和footer组件,可以实现一些新增操作功能。
但是有的小伙伴依赖的版本低于2.4.3,现有的插槽无法实现此功能,那么基于select组件手动封装一个组件实现此功能。
首先是模板部分。
首先是select组件,在option中添加一个自定义的option,我这里是个点击按钮的样式。
接下啦是个是一个点击之后产生的弹窗,没有什么特别的,弹窗里面维护一下自己想要的数据。form部分根据业务实际情况可以修改。
<template>
<el-select
v-model="selectedValue"
v-bind="$attrs"
:placeholder="placeholder"
@change="handleSelect"
ref="selectDropdown"
popper-class="custom-popper-class"
>
<el-option
v-for="option in computedOptions"
:key="option.value"
:label="option.label"
:value="option.value"
/>
<el-option key="__add__" value="__add__" class="add-option"
>找不到{{ tips }}?<span class="option-button">点击新增</span></el-option
>
</el-select>
<el-dialog v-model="showDialog" title="添加" @close="handleCancel">
<el-form :model="form" ref="formDialog" :rules="rules">
<el-form-item :label="tips" prop="inputValue">
<el-input v-model="form.inputValue" :placeholder="请输入" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="handleCancel">取消</el-button>
<el-button type="primary" @click="handleAdd">确定</el-button>
</template>
</el-dialog>
</template>
prop部分就不再多说了,根据实际情况可以自己添加。
emits
emits: ['update:modelValue', 'add'],
逻辑部分
首先就是watch监听,在某些异步情况下,传过来option部分未必有值,所以添加一个监听是有必要的。
rules部分因需求需要进行调整。
api,我这里的需求是需要和后端交互,如果成功的话,自己把刚刚添加的值push到options中。可以根据实际情况进行修改。
这里需要着重提的是props.options.push(newOption)这一行代码。有些人可能不理解,vue的props不是单向数据流吗?
没错!!!字面类型和引用类型就不多说了,不理解的小伙伴可以自己学习一下相关内容。我这里的重点是,options是外部传入的,可能在外部的options会被其他组件使用,这里的值修改,其他引用的地方也会跟着修改,就避免了值不同步,或者还得重新请求接口的麻烦了,小伙伴们可以根据实际业务场景,来决定这个值是不是要这么修改。
selectDropdown.value.toggleMenu(false)这行代码,是用来处理select设置了filterable: true属性,导致的弹窗关闭之后,通过冒泡误触发了select的聚焦事件,导致下拉选项出现的问题。
其他就是一些常规逻辑了。
setup(props, { emit }) {
const selectedValue = ref(props.modelValue)
const form = ref({
inputValue: '',
})
const tips = ref(props.tip)
const formDialog = ref(null)
const selectDropdown = ref(null)
const showDialog = ref(false)
const originalValue = toRef(props, 'modelValue')
const computedOptions = ref([...props.options])
watch(
() => props.options,
(newOptions) => {
computedOptions.value = [...newOptions]
}
)
watch(selectedValue, (newValue) => {
emit('update:modelValue', newValue)
})
const rules = {
inputValue: [
{ required: true, message: `请输入`, trigger: 'blur' },
{
validator: (rule, value, callback) => {
if (!value) {
callback()
} else {
const inputValue = value.toString()
const exists = computedOptions.value.some(
(option) => option.value.toString() === inputValue
)
if (exists) {
callback(new Error(`我这里是添加了一个重复校验`))
} else {
callback()
}
}
},
trigger: 'blur',
},
],
}
const handleAdd = () => {
formDialog.value.validate(async (valid) => {
if (valid) {
try {
const params = {
...
...
//一些参数
}
await props.api()(params)
const newOption = {
label: form.value.inputValue,
value: form.value.inputValue,
}
selectedValue.value = form.value.inputValue
form.value.inputValue = ''
/*
内部的option和外部的option同时修改。因为通过vue不推荐的方式修改外部option,
会导致vue监听数组的方法失效,无法触发watch
*/
computedOptions.value.push(newOption)
//此处强制修改props的值,因为外部可能有一些使用了相同下拉的值
// eslint-disable-next-line vue/no-mutating-props
props.options.push(newOption)
emit('update:modelValue', selectedValue.value)
emit('add', newOption)
handleCancel()
} catch (error) {
message.error(error.message)
}
} else {
return false
}
})
}
const handleSelect = (value) => {
if (value === '__add__') {
selectedValue.value = originalValue.value
showDialog.value = true
} else {
selectedValue.value = value
}
}
const handleCancel = () => {
formDialog.value.resetFields()
showDialog.value = false
selectDropdown.value.toggleMenu(false)
}
return {
selectedValue,
form,
showDialog,
computedOptions,
handleAdd,
handleSelect,
handleCancel,
rules,
formDialog,
selectDropdown,
tips,
}
},
最后附一下样式,css比较弱,还是比较难写的,哈哈哈。
.custom-popper-class .el-scrollbar__wrap {
overflow: visible !important;
position: relative;
}
.add-option {
position: sticky;
bottom: 6px;
background-color: #f5f7fa;
z-index: 1;
.option-button {
font-size: 12px;
color: #3183ff;
}
}