很多组件库里面的select都是沾满半个屏幕的那种,因为业务需求需要这种小巧的下拉选择,就需要自己封装一个,支持边框设置和多选,支持占位符和背景色设置等。所以这里就分享一下。
组件源代码:
<template>
<!-- <view class="uni-select-dc" :style="{ 'z-index': zindex }"> -->
<view class="uni-select-dc" @blur="onBlur" :style="{ 'z-index': zindex }">
<view
class="uni-select-dc-select"
:style="{
color: labelColor,
borderRadius: selRadius,
backgroundColor: bgColor,
}"
:class="{ active: active, showBorder: showBorder }"
@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">
<view
class="uni-select-multiple-item"
v-if="multiple"
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>
<!-- 单选时展示内容 -->
<view
v-else
class="single-text"
:style="{ color: labelColor ? labelColor : 'black' }"
>
{{ 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
:style="{
borderColor: `transparent transparent ${labelColor} ${labelColor}`,
}"
></text>
</view>
</view>
<!-- 下拉选项 -->
<scroll-view
class="uni-select-dc-options"
:scroll-y="true"
v-if="active"
>
<view
class="uni-select-dc-item"
v-for="(item, index) in options"
:key="index"
:class="{ activeOpt: realValue.includes((item as any)[svalue]) }"
@click.stop="handleChange(index, item)"
>
{{ (item as any)[slabel] }}
</view>
</scroll-view>
</view>
</template>
<script lang="ts" setup>
import { onShow } from '@dcloudio/uni-app'
import { reactive, ref } from 'vue'
// 选择框失去焦点后收回
const onBlur = () => {
console.log('收回折叠筐')
}
const props = defineProps({
// 是否显示全部清空按钮
showClearIcon: {
type: Boolean,
default: false,
},
// 是否多选
multiple: {
type: Boolean,
default: false,
},
// 下拉箭头是否在框内
downInner: {
type: Boolean,
default: true,
},
// 是否显示单个删除
showValueClear: {
type: Boolean,
default: true,
},
zindex: {
type: Number,
default: 99,
},
// 禁用选择
disabled: {
type: Boolean,
default: false,
},
options: {
type: Array,
default() {
return []
},
},
value: {
type: Array,
default() {
return []
},
},
placeholder: {
type: String,
default: '请选择',
},
showplaceholder: {
type: Boolean,
default: true,
},
// 默认取text
slabel: {
type: String,
default: 'text',
},
// 选中的label颜色
labelColor: {
type: String,
default: '#bbb',
},
// 圆角边框
selRadius: {
type: String,
default: '10rpx',
},
// 默认取value
svalue: {
type: String,
default: 'value',
},
// 背景色
bgColor: {
type: String,
default: '',
},
// 是否展示边框
showBorder: {
type: Boolean,
default: true,
},
})
const emit = defineEmits(['change'])
const active = ref<boolean>(false) // 组件是否激活,
let changevalue = reactive<Record<any, any>>([])
let realValue = reactive<Record<string, any>>([])
// 初始化函数
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
console.log('props---', changevalue, realValue)
} else {
changevalue = []
realValue = []
}
}
// 点击展示选项
const handleSelect = () => {
if (props.disabled) return
active.value = !active.value
}
// 移除数据
const handleRemove = (index: any) => {
if (index === null) {
realValue = []
changevalue = []
} else {
realValue.splice(index, 1)
changevalue.splice(index, 1)
}
emit('change', changevalue, realValue)
}
// 点击组件某一项
const handleChange = (index, item) => {
console.log('选中了某一项', index, item)
// 如果是单选框,选中一项后直接关闭
if (!props.multiple) {
console.log('关闭下拉框')
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)
}
onShow(() => {
init()
})
</script>
<style lang="scss" scoped>
.uni-select-dc {
position: relative;
z-index: 99;
.uni-select-mask {
width: 100%;
height: 100%;
}
/* 删除按钮样式*/
.close-icon {
height: 100%;
width: 20px;
display: flex;
align-items: center;
justify-content: center;
// z-index: 3;
cursor: pointer;
text {
position: relative;
background: #c0c4cc;
width: 13px;
height: 13px;
border-radius: 50%;
border: 1px solid #bbb;
&::before,
&::after {
content: '';
position: absolute;
left: 20%;
top: 50%;
height: 1px;
width: 60%;
transform: rotate(45deg);
background-color: #909399;
}
&::after {
transform: rotate(-45deg);
}
}
}
//所有情空的定位
.close-postion {
position: absolute;
right: 35px;
top: 0;
height: 100%;
width: 15px;
}
/* 多选盒子 */
.uni-select-multiple {
display: flex;
flex-wrap: nowrap;
overflow: scroll;
.single-text {
color: #333;
}
.uni-select-multiple-item {
background: #f4f4f5;
margin-right: 5px;
padding: 2px 4px;
border-radius: 4px;
color: #909399;
display: flex;
flex-shrink: 0;
}
}
.showBorder {
border: 1px solid rgb(229, 229, 229);
}
// select部分
.uni-select-dc-select {
user-select: none;
position: relative;
z-index: 3;
height: 30px;
padding: 0 30px 0 10px;
box-sizing: border-box;
border-radius: 4px;
display: flex;
flex-direction: row;
justify-content: flex-end;
align-items: center;
font-size: 12px;
color: #999;
min-width: 10px;
.uni-disabled {
position: absolute;
left: 0;
width: 100%;
height: 100%;
z-index: 19;
cursor: no-drop;
background: rgba(255, 255, 255, 0.5);
}
.uni-select-dc-input {
font-size: 14px;
color: #999;
display: block;
width: 96%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
line-height: 30px;
box-sizing: border-box;
&.active {
color: #333;
}
}
.uni-select-dc-icon {
cursor: pointer;
position: absolute;
right: 0;
top: 0;
height: 100%;
width: 20px;
display: flex;
align-items: center;
justify-content: center;
border-left: 1px solid rgb(229, 229, 229);
text {
display: block;
width: 0;
height: 0;
border-width: 12rpx 12rpx 0;
border-style: solid;
border-color: #bbb transparent transparent;
transition: 0.3s;
}
&.disabled {
cursor: no-drop;
text {
width: 20rpx;
height: 20rpx;
border: 2px solid #ff0000;
border-radius: 50%;
transition: 0.3s;
position: relative;
z-index: 999;
&::after {
content: '';
position: absolute;
top: 50%;
left: 0;
width: 100%;
height: 2px;
margin-top: -1px;
background-color: #ff0000;
transform: rotate(45deg);
}
}
}
}
.uni-select-dc-inner {
cursor: pointer;
position: absolute;
right: 0;
top: 0;
height: 100%;
width: 20px;
display: flex;
align-items: center;
justify-content: center;
text {
display: block;
width: 16rpx;
height: 16rpx;
position: absolute;
right: 20rpx;
top: 12rpx;
border: 2rpx solid #bbb;
transform: rotate(-45deg);
border-color: transparent transparent#bbb #bbb;
transition: 0.3s;
}
&.disabled {
cursor: no-drop;
text {
width: 20rpx;
height: 20rpx;
border: 2px solid #ff0000;
border-radius: 50%;
transition: 0.3s;
position: relative;
z-index: 999;
&::after {
content: '';
position: absolute;
top: 50%;
left: 0;
width: 100%;
height: 2px;
margin-top: -1px;
background-color: #ff0000;
transform: rotate(45deg);
}
}
}
}
// 激活之后,图标旋转180度
&.active .uni-select-dc-icon {
text {
transform: rotate(180deg);
}
}
&.active .uni-select-dc-inner {
text {
position: absolute;
right: 10px;
top: 12px;
transform: rotate(-225deg);
}
}
}
// options部分
.uni-select-dc-options {
user-select: none;
position: absolute;
top: calc(100% + 5px);
left: 0;
width: 100%;
// height: 400rpx;
max-height: 400rpx;
border-radius: 4px;
border: 1px solid rgb(229, 229, 229);
background: #fff;
padding: 5px 0;
box-sizing: border-box;
z-index: 9;
.uni-select-dc-item {
padding: 0 10px;
box-sizing: border-box;
cursor: pointer;
line-height: 2.5;
transition: 0.3s;
font-size: 14px;
color: #333;
// 取消长按的背景色
user-select: none;
-webkit-tap-highlight-color: rgba(255, 255, 255, 0);
-moz-user-focus: none;
-moz-user-select: none;
&.activeOpt {
color: #409eff;
background-color: #f5f7fa;
&:hover {
color: #409eff;
background-color: #f5f7fa;
}
}
&:hover {
background-color: #f5f5f5;
}
}
}
}
</style>
使用的时候,引入组件:
template内容:
<view class="langSelect">
<Select
:options="langOptions"
:value="[0]"
:showBorder="false"
labelColor="white"
selRadius="30rpx"
@change="langChange"
></Select>
</view>
ts内容:
import Select from '@/components/select/index.vue'
// 语言选择
const langOptions = [
{ value: 0, text: '简体中文' },
{ value: 1, text: '繁体中文' },
{ value: 2, text: '日语' },
{ value: 3, text: '英语' },
]
// lang change
const langChange = (e: any, value: any) => {
console.log('langChange', e, value)
}