Files
dcalc/frontend/src/components/calc/autocomplete/index.vue
T
2026-04-20 00:00:48 +08:00

251 lines
5.4 KiB
Vue

<script lang="tsx">
import { debouncedWatch, onClickOutside, useVModel } from '@vueuse/core'
import type { CSSProperties, PropType } from 'vue'
import {
Teleport,
Transition,
computed,
defineComponent,
onDeactivated,
ref,
renderList,
watch,
} from 'vue'
import { getElementZoom } from '@/utils/position'
type HandleChangeFunction = (value: string | number) => string[] | Promise<string[]>
export default defineComponent({
props: {
modelValue: {
type: [String, Number] as PropType<string | number>,
default: () => '',
},
placeholder: {
type: String,
default: '',
},
handleChange: {
type: Function as PropType<HandleChangeFunction>,
default: () => () => [],
},
delay: {
type: Number,
default: 300,
},
},
setup(props, { emit }) {
const modelValue = useVModel(props, 'modelValue')
const isOpen = ref(false)
const triggerRef = ref<HTMLElement>()
watch(isOpen, onResize)
// 下拉框位置
const dropdownPosition = ref({ x: 0, y: 0, w: 0 })
// 下拉框位置
const dropdownStyle = computed<CSSProperties>(() => {
return {
left: `${dropdownPosition.value.x}px`,
top: `${dropdownPosition.value.y}px`,
width: `${dropdownPosition.value.w}px`,
}
})
function onResize() {
if (triggerRef.value) {
const { width, height, left, top } = triggerRef.value.getBoundingClientRect()
const zoom = getElementZoom('body')
dropdownPosition.value = {
w: width / zoom,
x: left / zoom,
y: (top + height + 2) / zoom,
}
}
}
onClickOutside(triggerRef, () => (isOpen.value = false))
window.addEventListener('resize', onResize)
window.addEventListener('scroll', onResize)
onDeactivated(() => {
window.removeEventListener('resize', onResize)
window.removeEventListener('scroll', onResize)
})
function selectItem(item: string) {
return () => {
modelValue.value = item
}
}
const delay = computed(() => props.delay)
const droplist = ref<string[]>([])
debouncedWatch(
modelValue,
async (value) => (droplist.value = await props.handleChange(value)),
{
debounce: delay,
},
)
function handleInput() {
isOpen.value = droplist.value.length > 0
}
function handleKeyPress(event: KeyboardEvent) {
if (event.code == 'Enter') {
emit('enter', modelValue.value)
}
}
return () => {
return (
<div class="i-autocomplete">
<div class="i-autocomplete-trigger" ref={triggerRef}>
<input
onKeypress={handleKeyPress}
class="i-autocomplete-input"
onFocus={handleInput}
onInput={handleInput}
type="text"
v-model={modelValue.value}
placeholder={props.placeholder}
/>
</div>
<Teleport to="body">
<Transition name="dropdown" mode="out-in">
<div
class="i-autocomplete-dropdown"
style={dropdownStyle.value}
v-show={isOpen.value}
>
{renderList(droplist.value, (item) => (
<div class="i-autocomplete-dropdown-item" onClick={selectItem(item)}>
{item}
</div>
))}
</div>
</Transition>
</Teleport>
</div>
)
}
},
})
</script>
<style scoped lang="scss">
$text-color: #937639;
.i-autocomplete {
min-width: 80px;
width: 160px;
user-select: none;
outline: none;
height: 24px;
min-height: 16px;
line-height: 100%;
// font-size: 12px;
color: $text-color;
background-color: rgba(0, 0, 0, 1);
border: 1px solid #5b472a;
border-radius: 2px;
display: block;
.i-autocomplete-trigger {
display: flex;
height: 100%;
opacity: 0.8;
justify-content: space-between;
align-items: center;
.i-autocomplete-input {
padding-left: 8px;
width: 100%;
border: none;
outline: none;
color: inherit;
// font-size: 12px;
background-color: black;
font-family: SimSun;
}
&.disabled {
color: gray;
.i-select-down-icon {
background-image: url('./img/select_down_disabled.png');
}
}
}
.i-autocomplete-trigger:hover {
opacity: 1;
}
}
.i-autocomplete-dropdown {
position: fixed;
max-height: 160px;
overflow-y: auto;
background: black;
// font-size: 12px;
z-index: 888;
$hoverColor: #002947;
$activeColor: color.scale($hoverColor, $lightness: 5%);
color: $text-color;
.i-autocomplete-dropdown-item {
// font-size: 12px;
height: 20px;
line-height: 20px;
margin: 0;
padding: 0 8px;
border: none;
outline: none;
appearance: none;
display: block;
overflow: hidden;
&.active {
background-color: $activeColor;
}
&:hover:not(.active) {
// font-size: 12px;
background-color: $hoverColor;
}
}
}
.dropdown-active {
animation: dropdown-enter 0.2s ease-in;
}
.dropdown-active {
animation: dropdown-leave 0.2s ease-in;
}
.dropdown-enter-active {
animation: dropdown-enter 0.2s ease-in;
}
.dropdown-leave-active {
animation: dropdown-leave 0.2s ease-in;
}
.dropdown-enter-active {
animation: dropdown-enter 0.2s ease-in;
}
.dropdown-leave-active {
animation: dropdown-leave 0.2s ease-in;
}
</style>