<template>
<div class="container">
<div class="select-wrapper" ref="selectRef">
<div class="select-input-box" @click="showSelectList" :class="{
focus: visible, 'show-close':
!props.hideCloseIcon, multiple: props.multiple, disabled: props.disabled
}">
<div class="select-value"> {{ selectValue }}</div>
<!-- <common-input v-if="!props.multiple" :disabled="props.disabled" :type="'text'" v-model:input-value="selectValue"
:readonly="true" class="select-input" :placeholder="placeholder"></common-input> -->
<!-- <common-input v-if="!props.multiple" :disabled="props.disabled" :type="'text'" v-model:input-value="selectValue"
:readonly="true" class="select-input" :placeholder="placeholder"></common-input> -->
<!-- <div v-else class="multiple-select-box">
<div class="multiple-select-item" v-for="(item, index) in multipleSelectValue" :key="item">
<div>{{ labelValueMap[item] }}</div>
<div class="close-icon-box">
<common-svg class="multiple-close-icon" @click="multipleClear(index, $event)" name="close">
</common-svg>
</div>
</div>
</div> -->
<common-svg v-if="!props.disabled" :class="{ 'list-show': visible, 'has-value': selectValue }" class="arrow-icon"
name="arrow-icon">
</common-svg>
<common-svg v-if="!props.hideCloseIcon" class="close-icon" @click="clear($event)"
:class="{ 'has-value': selectValue }" name="close">
</common-svg>
</div>
<common-error-tip :error-msg="props.errorTip"></common-error-tip>
<Transition v-if="!props.disabled" :name="placement === 'top' ? 'select-top-transition' : 'select-transition'">
<div class="select-list" :class="{ 'placement-top': placement === 'top' }" v-show="visible">
<div class="select-option" v-for="item in props.selectList" :key="item[props.valueAttr]"
@click="selectChange(item)" @mouseenter="selectMouseenter(item)"
:class="{ disabled: isDisabled(item), 'height-light': heightLightItem === item[props.valueAttr], 'multiple-selected': props.multiple && multipleSelectValue.indexOf(item[props.valueAttr]) > -1 }">
<span>{{ item[props.labelAttr] }}</span>
<common-svg class="checkbox-icon" name="checkbox"></common-svg>
</div>
</div>
</Transition>
</div>
</div>
</template>
<script lang="ts" setup>
import { PropType } from 'vue'
import { onClickOutside } from '@vueuse/core'
type selectOption = Record<string, any>
const props = defineProps({
placeholder: {
type: String,
default: 'Please select',
},
errorTip: {
type: String,
default: '',
},
modelValue: {
type: [String, Number, Boolean, Array],
},
selectList: {
type: Array as PropType<selectOption[]>,
default: () => []
},
inputStyle: {
type: Object,
default: () => { }
},
optionalList: {
type: Array as PropType<(string | number)[]>,
default: () => []
},
labelAttr: {
type: String,
default: 'label',
},
valueAttr: {
type: String,
default: 'value',
},
hideCloseIcon: {
type: Boolean,
default: false,
},
multiple: {
type: Boolean,
default: false,
},
disabled: {
type: Boolean,
default: false,
}
})
const emits = defineEmits(['update:modelValue', 'change'])
const visible = ref(false)
const selectRef = ref()
onClickOutside(selectRef, () => {
visible.value = false
})
const heightLightItem = ref<string | number | boolean | undefined>('')
const placement = ref('')
function showSelectList() {
visible.value = !visible.value
if (visible.value) {
let wHeight = document.documentElement.clientHeight || document.body.clientHeight
if (wHeight - selectRef.value.getBoundingClientRect().bottom < 308) {
placement.value = "top"
} else {
placement.value = ""
}
if (!props.multiple) {
heightLightItem.value = props.modelValue as string | number | boolean
}
}
}
const selectValue = ref('')
const multipleSelectValue = ref<string[]>([])
watch(() => props.modelValue, (val: any) => {
if (props.multiple) {
if (val && val.length) {
multipleSelectValue.value = val.map((e: any) => e + '')
} else {
multipleSelectValue.value = []
}
} else {
if (val !== undefined) {
let target = props.selectList.find(e => e[props.valueAttr] === val)
if (target) {
selectValue.value = target[props.labelAttr]
}
} else {
selectValue.value = ''
}
}
}, { immediate: true })
const labelValueMap = computed(() => {
let result: Record<string, any> = {}
if (props.selectList && props.selectList.length) {
props.selectList.forEach(e => {
result[e[props.valueAttr]] = e[props.labelAttr]
})
}
return result
})
function clear(event: Event) {
event.stopPropagation()
selectValue.value = ''
heightLightItem.value = ''
emits('update:modelValue', undefined)
emits('change', undefined)
}
function multipleClear(index: number, event: Event) {
event.stopPropagation()
multipleSelectValue.value.splice(index, 1)
emits('update:modelValue', multipleSelectValue.value)
emits('change', multipleSelectValue.value)
}
function isDisabled(item: selectOption) {
return props.optionalList && props.optionalList.length && props.optionalList.indexOf(item[props.valueAttr]) < 0
}
function selectChange(item: selectOption) {
if (isDisabled(item)) {
return
}
if (props.multiple) {
let targetIndex = multipleSelectValue.value.indexOf(item[props.valueAttr])
if (targetIndex > -1) {
multipleSelectValue.value.splice(targetIndex, 1)
} else {
multipleSelectValue.value.push(item[props.valueAttr])
}
emits('update:modelValue', multipleSelectValue.value)
emits('change', multipleSelectValue.value)
} else {
visible.value = false
heightLightItem.value = item[props.valueAttr]
emits('update:modelValue', item[props.valueAttr])
emits('change', item[props.valueAttr], item)
}
}
function selectMouseenter(item: selectOption) {
heightLightItem.value = item[props.valueAttr]
}
</script>
<style scoped lang="scss">
.container {
width: max-content;
.select-wrapper {
min-width: 120px;
max-width: 600px;
display: flex;
font-size: 14px;
height: 32px;
border-radius: 20px;
border: 1px solid var(--main-theme-color) !important;
.select-input-box {
border: none !important;
}
.select-input-box {
display: flex;
align-items: center;
width: 100%;
height: 100%;
padding: 8px;
border-radius: 4px;
border: 1px solid #cccccc;
box-sizing: border-box;
cursor: pointer;
&.focus {
border-color: #333333;
}
&.multiple {
height: auto;
padding: 6px 12px;
}
&.disabled {
background: #E6E6E6 !important;
}
.select-input {
outline: none;
background: none;
border: none;
min-width: 0;
color: inherit;
}
.multiple-select-box {
display: flex;
flex-wrap: wrap;
gap: 4px;
flex: 1;
min-width: 0;
.multiple-select-item {
display: flex;
align-items: center;
padding: 4px 7px;
background: #f0f2f5;
border-radius: 4px;
.close-icon-box {
display: flex;
align-items: center;
justify-content: center;
width: 18px;
height: 18px;
margin-left: 8px;
border-radius: 50%;
cursor: pointer;
&:hover {
background: #cccccc;
}
.close-icon {
width: 14px;
height: 14px;
color: #333333;
}
}
}
}
.arrow-icon {
flex-shrink: 0;
width: 20px;
height: 20px;
color: #999;
transform: rotateZ(0deg);
transition: transform 0.3s;
&.list-show {
transform: rotateZ(-180deg);
}
}
.close-icon {
display: none;
cursor: pointer;
flex-shrink: 0;
width: 20px;
height: 20px;
color: #999;
}
&.show-close:hover {
.arrow-icon.has-value {
display: none;
}
.close-icon.has-value {
display: block;
}
}
}
.select-list {
position: absolute;
left: 0;
top: calc(100%);
width: 100%;
background: #fff;
box-shadow: 0px 4px 16px 0px rgba(0, 0, 0, 0.16);
border-radius: 4px;
z-index: $z-index-height-fifth;
color: #333333;
max-height: 300px;
overflow-y: auto;
@include scrollBar;
&.placement-top {
top: auto;
bottom: calc(100% + 8px);
}
.select-option {
width: 100%;
height: 42px;
padding: 12px;
cursor: pointer;
&:nth-child(1) {
border-radius: 4px 4px 0 0;
}
&:nth-last-child(1) {
border-radius: 0 0 4px 4px;
}
&.height-light {
background: #f6f6f6;
}
&.disabled {
color: #c0c4cc;
cursor: not-allowed;
&:hover {
background: #fff;
}
}
.checkbox-icon {
display: none;
}
&.multiple-selected {
display: flex;
justify-content: space-between;
color: var(--main-theme-color);
.checkbox-icon {
display: block;
width: 18px;
height: 18px;
color: var(--main-theme-color);
}
}
}
}
}
}
.select-transition-enter-active,
.select-transition-leave-active {
opacity: 1;
transform: scaleY(1);
transition: transform 0.3s cubic-bezier(0.23, 1, 0.32, 1), opacity 0.3s cubic-bezier(0.23, 1, 0.32, 1);
transform-origin: center top;
}
.select-transition-enter-from,
.select-transition-leave-active {
opacity: 0;
transform: scaleY(0);
}
.select-top-transition-enter-active,
.select-top-transition-leave-active {
opacity: 1;
transform: scaleY(1);
transition: transform 0.3s cubic-bezier(0.23, 1, 0.32, 1), opacity 0.3s cubic-bezier(0.23, 1, 0.32, 1);
transform-origin: center bottom;
}
.select-top-transition-enter-from,
.select-top-transition-leave-active {
opacity: 0;
transform: scaleY(0);
}
</style>