子组件代码
<template>
<view class="uni-select-dc">
<view
ref="select"
class="uni-select-dc-select"
:class="{ active: active }"
@click.stop="handleSelect"
>
<!-- 禁用mask -->
<view class="uni-disabled" v-if="disabled"></view>
<!-- 清空 -->
<view
class="close-icon close-postion"
v-if="realValue.length && !active && !disabled && showClearIcon"
>
<text @click.stop="handleRemove(null)"></text>
</view>
<!-- 显示框 -->
<view class="uni-select-multiple" v-show="realValue.length">
<!-- 多选时展示内容 -->
<template v-if="multiple">
<view
class="uni-select-multiple-item"
v-for="(item, index) in changevalue"
:key="index"
>
{{ item.text }}
<view class="close-icon" v-if="showValueClear">
<text @click.stop="handleRemove(index)"></text>
</view>
</view>
</template>
<!-- 单选时展示内容 -->
<view v-else class="single-text">
{{ changevalue.length ? changevalue[0].text : '' }}
</view>
</view>
<!-- 为空时的显示文案 -->
<view v-if="realValue.length == 0 && showplaceholder">
{{ placeholder }}
</view>
<!-- 右边的下拉箭头 -->
<view
:class="{
disabled: disabled,
'uni-select-dc-icon': !downInner,
'uni-select-dc-inner': downInner,
}"
>
<text></text>
</view>
</view>
<!-- 下拉选项 -->
<scroll-view class="uni-select-dc-options" :scroll-y="true" v-show="active">
<template v-if="options.length">
<view
class="uni-select-dc-item"
:class="{ active: realValue.includes((item as any)[svalue]) }"
v-for="(item, index) in options"
:key="index"
@click.stop="handleChange(index, item)"
>
{{ (item as any)[slabel] }}
</view>
</template>
<template v-else>
<view class="uni-select-dc-item">无匹配项</view>
</template>
</scroll-view>
</view>
</template>
<script lang="ts" setup>
import { onMounted, onUnmounted, reactive, ref, watch } from 'vue'
const props = defineProps({
// 是否显示全部清空按钮
showClearIcon: {
type: Boolean,
default: false,
},
// 是否多选
multiple: {
type: Boolean,
default: false,
},
// 下拉箭头是否在框内
downInner: {
type: Boolean,
default: true,
},
// 是否显示单个删除
showValueClear: {
type: Boolean,
default: true,
},
// 禁用选择
disabled: {
type: Boolean,
default: false,
},
options: {
type: Array,
/**
* default
*/
default() {
return []
},
},
value: {
type: Array,
/**
* default
*/
default() {
return []
},
},
// 提示文案
placeholder: {
type: String,
default: '请选择',
},
// 是否显示提示文案
showplaceholder: {
type: Boolean,
default: true,
},
// 默认取text
slabel: {
type: String,
default: 'text',
},
// 默认取value
svalue: {
type: String,
default: 'value',
},
})
const emit = defineEmits(['change'])
const active = ref<boolean>(false) // 组件是否激活,
let changevalue = reactive<Record<any, any>>([])
let realValue = reactive<Record<string, any>>([])
const select = ref(null)
/**
* 初始化函数
*/
const init = () => {
if (props.value.length > 0) {
props.options.forEach((item) => {
props.value.forEach((i) => {
if ((item as any)[props.svalue] === i) {
changevalue.push(item)
}
})
})
realValue = props.value
} else {
changevalue = []
realValue = []
}
}
watch(
() => props.value,
() => {
init()
},
{ immediate: true },
)
/**
* 点击展示选项
*/
const handleSelect = () => {
if (props.disabled) return
active.value = !active.value
}
/**
* 关闭下拉框
*/
const closeDropdown = () => {
if (active.value) {
active.value = false
}
}
onMounted(() => {
document.addEventListener('click', closeDropdown)
})
onUnmounted(() => {
document.removeEventListener('click', closeDropdown)
})
/**
* 移除数据
* @param index index
*/
const handleRemove = (index: any) => {
if (index === null) {
realValue = []
changevalue = []
} else {
realValue.splice(index, 1)
changevalue.splice(index, 1)
}
emit('change', changevalue, realValue)
}
/**
* 点击组件某一项
* @param index index
* @param item item
*/
const handleChange = (index: any, item: any) => {
// 如果是单选框,选中一项后直接关闭
if (!props.multiple) {
changevalue.length = 0
realValue.length = 0
changevalue.push(item)
realValue.push(item[props.svalue])
active.value = !active.value
} else {
// 多选操作
const arrIndex = realValue.indexOf(item[props.svalue])
if (arrIndex > -1) {
// 如果该选项已经选中,当点击后就不选中
changevalue.splice(arrIndex, 1)
realValue.splice(arrIndex, 1)
} else {
// 否则选中该选项
changevalue.push(item)
realValue.push(item[props.svalue])
}
}
// 触发回调函数
emit('change', changevalue, realValue)
}
</script>
<style lang="scss" scoped>
.uni-select-dc {
position: relative;
width: 100%;
.uni-select-mask {
width: 100%;
height: 100%;
}
/* 删除按钮样式 */
.close-icon {
display: flex;
align-items: center;
justify-content: center;
width: 20px;
height: 100%;
cursor: pointer;
text {
position: relative;
width: 26rpx;
height: 26rpx;
background: #c0c4cc;
border: 1px solid #bbb;
border-radius: 50%;
&::before,
&::after {
position: absolute;
top: 45%;
left: 20%;
width: 60%;
height: 2rpx;
background-color: #fff;
transform: rotate(45deg);
content: '';
}
&::after {
transform: rotate(-45deg);
}
}
}
// 所有清空的定位
.close-postion {
position: absolute;
top: 0;
right: 70rpx;
width: 30rpx;
height: 100%;
}
/* 多选盒子 */
.uni-select-multiple {
display: flex;
flex-wrap: wrap;
.single-text {
color: #333;
}
.uni-select-multiple-item {
display: flex;
flex-shrink: 0;
margin: 6rpx 10rpx 6rpx 0;
padding: 4rpx 8rpx;
color: #3a3a3a;
background: #f4f4f5;
border-radius: 8rpx;
}
}
// select部分
.uni-select-dc-select {
position: relative;
z-index: 3;
display: flex;
align-items: center;
box-sizing: border-box;
width: 100%;
min-height: 70rpx;
padding: 0 60rpx 0 20rpx;
color: #999;
font-size: 24rpx;
border: 1px solid rgb(229, 229, 229);
border-radius: 8rpx;
user-select: none;
.uni-disabled {
position: absolute;
left: 0;
z-index: 1;
width: 100%;
height: 100%;
background-color: #ebf1f67a;
border-color: #e5e5e5;
cursor: no-drop;
}
.uni-select-dc-input {
display: block;
box-sizing: border-box;
width: 96%;
overflow: hidden;
color: #999;
font-size: 28rpx;
line-height: 60rpx;
white-space: nowrap;
text-overflow: ellipsis;
&.active {
color: #333;
}
}
.uni-select-dc-icon {
position: absolute;
top: 0;
right: 0;
display: flex;
align-items: center;
justify-content: center;
width: 36rpx;
height: 100%;
border-left: 1px solid rgb(229, 229, 229);
cursor: pointer;
text {
display: block;
width: 0;
height: 0;
border-color: #bbb transparent transparent;
border-style: solid;
border-width: 12rpx 12rpx 0;
transition: 0.3s;
}
&.disabled {
cursor: no-drop;
}
}
.uni-select-dc-inner {
position: absolute;
top: 15%;
right: 0;
display: flex;
align-items: center;
justify-content: center;
width: 40rpx;
height: 100%;
cursor: pointer;
text {
position: absolute;
top: 12rpx;
right: 20rpx;
display: block;
width: 12rpx;
height: 12rpx;
border: 1px solid #999999;
border-color: transparent transparent#bbb #bbb;
transform: rotate(-45deg);
transition: 0.3s;
}
&.disabled {
cursor: no-drop;
}
}
// 激活之后,图标旋转180度
&.active .uni-select-dc-icon {
text {
transform: rotate(180deg);
}
}
&.active .uni-select-dc-inner {
text {
position: absolute;
top: 24rpx;
right: 20rpx;
transform: rotate(-225deg);
}
}
}
// options部分
.uni-select-dc-options {
position: absolute;
top: calc(100% + 5px);
left: 0;
z-index: 9;
box-sizing: border-box;
width: 100%;
max-height: 400rpx;
padding: 10rpx 0;
background: #fff;
border: 1px solid rgb(229, 229, 229);
border-radius: 4px;
user-select: none;
.uni-select-dc-item {
box-sizing: border-box;
padding: 0 20rpx;
font-size: 28rpx;
line-height: 2.5;
cursor: pointer;
transition: 0.3s;
-webkit-user-select: none;
-moz-user-select: none;
// 取消长按的背景色
-webkit-tap-highlight-color: rgba(255, 255, 255, 0);
-moz-user-focus: none;
&.active {
color: #3774c6;
background-color: #f5f7fa;
&:hover {
color: #3774c6;
background-color: #f5f7fa;
}
}
&:hover {
background-color: #f5f5f5;
}
}
}
}
</style>
父组件代码
const monIndex = ref([0])
const changeValue = (item: any, value: any) => {
monIndex.value = value
}
<select
v-model:value="monIndex"
:options="[
{ id: 0, text: '测试1' },
{ id: 1, text: '测试2' },
{ id: 2, text: '测试3' },
{ id: 3, text: '测试4' },
{ id: 4, text: '测试5' },
]"
svalue="id"
@change="changeValue"
placeholder="请选择归属部门"
></select>
单选效果图
多选效果图