CatPtain's picture
Upload 339 files
89ce340 verified
raw
history blame
4.67 kB
<template>
<div class="select-wrap" v-if="disabled">
<div class="select disabled" ref="selectRef">
<div class="selector">{{ value }}</div>
<div class="icon">
<slot name="icon">
<IconDown :size="14" />
</slot>
</div>
</div>
</div>
<Popover
class="select-wrap"
trigger="click"
v-model:value="popoverVisible"
placement="bottom"
:contentStyle="{
padding: 0,
boxShadow: '0 6px 16px 0 rgba(0, 0, 0, 0.08)',
}"
v-else
>
<template #content>
<template v-if="search">
<Input ref="searchInputRef" simple :placeholder="searchLabel" v-model:value="searchKey" :style="{ width: width + 2 + 'px' }" />
<Divider :margin="0" />
</template>
<div class="options" :style="{ width: width + 2 + 'px' }">
<div class="option"
:class="{
'disabled': option.disabled,
'selected': option.value === value,
}"
v-for="option in showOptions"
:key="option.value"
@click="handleSelect(option)"
>{{ option.label }}</div>
</div>
</template>
<div class="select" ref="selectRef">
<div class="selector">{{ showLabel }}</div>
<div class="icon">
<slot name="icon">
<IconDown :size="14" />
</slot>
</div>
</div>
</Popover>
</template>
<script lang="ts" setup>
import { computed, onMounted, onUnmounted, ref, watch, nextTick, onBeforeUnmount } from 'vue'
import Popover from './Popover.vue'
import Input from './Input.vue'
import Divider from './Divider.vue'
interface SelectOption {
label: string
value: string | number
disabled?: boolean
}
const props = withDefaults(defineProps<{
value: string | number
options: SelectOption[]
disabled?: boolean
search?: boolean
searchLabel?: string
}>(), {
disabled: false,
search: false,
searchLabel: '搜索',
})
const emit = defineEmits<{
(event: 'update:value', payload: string | number): void
}>()
const popoverVisible = ref(false)
const selectRef = ref<HTMLElement>()
const searchInputRef = ref<InstanceType<typeof Input>>()
const width = ref(0)
const searchKey = ref('')
const showLabel = computed(() => {
return props.options.find(item => item.value === props.value)?.label || props.value
})
const showOptions = computed(() => {
if (!props.search) return props.options
if (!searchKey.value.trim()) return props.options
const opts = props.options.filter(item => {
return item.label.toLowerCase().indexOf(searchKey.value.toLowerCase()) !== -1
})
return opts.length ? opts : props.options
})
watch(popoverVisible, () => {
if (popoverVisible.value) {
nextTick(() => {
if (searchInputRef.value) searchInputRef.value.focus()
})
}
else searchKey.value = ''
})
onBeforeUnmount(() => {
searchKey.value = ''
})
const updateWidth = () => {
if (!selectRef.value) return
width.value = selectRef.value.clientWidth
}
const resizeObserver = new ResizeObserver(updateWidth)
onMounted(() => {
if (!selectRef.value) return
resizeObserver.observe(selectRef.value)
})
onUnmounted(() => {
if (!selectRef.value) return
resizeObserver.unobserve(selectRef.value)
})
const handleSelect = (option: SelectOption) => {
if (option.disabled) return
emit('update:value', option.value)
popoverVisible.value = false
}
</script>
<style lang="scss" scoped>
.select {
width: 100%;
height: 32px;
padding-right: 32px;
border-radius: $borderRadius;
transition: border-color .25s;
font-size: 13px;
user-select: none;
background-color: #fff;
border: 1px solid #d9d9d9;
position: relative;
cursor: pointer;
&:not(.disabled):hover {
border-color: $themeColor;
}
&.disabled {
background-color: #f5f5f5;
border-color: #dcdcdc;
color: #b7b7b7;
cursor: default;
}
.selector {
min-width: 50px;
height: 30px;
line-height: 30px;
padding-left: 10px;
@include ellipsis-oneline();
}
}
.options {
max-height: 260px;
padding: 5px;
overflow: auto;
text-align: left;
font-size: 13px;
user-select: none;
}
.option {
height: 32px;
line-height: 32px;
padding: 0 5px;
border-radius: $borderRadius;
@include ellipsis-oneline();
&.disabled {
color: #b7b7b7;
}
&:not(.disabled, .selected):hover {
background-color: rgba($color: $themeColor, $alpha: .05);
cursor: pointer;
}
&.selected {
color: $themeColor;
font-weight: 700;
}
}
.icon {
width: 32px;
height: 30px;
color: #bfbfbf;
position: absolute;
top: 0;
right: 0;
display: flex;
justify-content: center;
align-items: center;
}
</style>