/
voltage
/
tcheck
Обзор
Документация
Войти
/
voltage
/
tcheck
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
frontend/src/components/AutocompleteInput.vue
165 строк
4 KB
Владимир
front users
17 июн 2026, 11:29
17 июн 2026, 11:29
11e284c
Код
Авторство
О чём код?
<template> <div class="dropdown" ref="dropdownRef"> <input type="text" class="form-control" :placeholder="placeholder" :value="displayText" @input="onInput" @focus="onFocus" @blur="onBlur" /> <ul class="dropdown-menu" :class="{ show: showDropdown }" style="width: 100%; max-height: 200px; overflow-y: auto;" > <li v-if="loading" class="dropdown-item text-muted">Загрузка...</li> <li v-else-if="items.length === 0 && !loading" class="dropdown-item text-muted"> {{ query ? 'Ничего не найдено' : 'Нет данных' }} </li> <li v-for="item in items" :key="item.id" class="dropdown-item" @mousedown.prevent="selectItem(item)" > {{ item.name }} </li> </ul> </div> </template> <script setup lang="ts"> import { ref, watch, onMounted, onUnmounted } from 'vue' interface Item { id: string name: string } const props = defineProps<{ modelValue: string | null placeholder?: string searchFunction: (query: string) => Promise<Item[]> selectedItem?: Item | null initialFetch?: () => Promise<Item[]> // ← новая опция }>() const emit = defineEmits<{ (e: 'update:modelValue', value: string | null): void (e: 'select', item: Item): void }>() const query = ref('') const items = ref<Item[]>([]) const loading = ref(false) const showDropdown = ref(false) const displayText = ref('') const dropdownRef = ref<HTMLElement | null>(null) let debounceTimer: ReturnType<typeof setTimeout> | null = null let initialLoaded = false let initialItemsCache: Item[] = [] // кешируем начальный список // ----- Обработчики ввода ----- const onInput = (event: Event) => { const target = event.target as HTMLInputElement query.value = target.value displayText.value = target.value emit('update:modelValue', null) if (query.value.trim()) { initialLoaded = false // переключаемся в режим поиска debounceSearch() } else { // очистили поле – показываем начальный список items.value = [...initialItemsCache] showDropdown.value = true } } const debounceSearch = () => { if (debounceTimer) clearTimeout(debounceTimer) debounceTimer = setTimeout(async () => { loading.value = true try { items.value = await props.searchFunction(query.value) showDropdown.value = true } catch { items.value = [] } finally { loading.value = false } }, 300) } // ----- Выбор элемента ----- const selectItem = (item: Item) => { displayText.value = item.name emit('update:modelValue', item.id) emit('select', item) showDropdown.value = false query.value = '' } // ----- Фокус: показываем начальные данные, если поле пустое ----- const onFocus = () => { if (!query.value.trim()) { loadInitial() } else if (items.value.length > 0) { showDropdown.value = true } } const loadInitial = async () => { if (!props.initialFetch) return if (!initialLoaded) { loading.value = true try { initialItemsCache = await props.initialFetch() items.value = [...initialItemsCache] initialLoaded = true showDropdown.value = true } catch { initialItemsCache = [] } finally { loading.value = false } } else { // уже загружены – просто показываем items.value = [...initialItemsCache] showDropdown.value = true } } // ----- Потеря фокуса ----- const onBlur = () => { setTimeout(() => { showDropdown.value = false }, 200) } // ----- Синхронизация с внешним selectedItem ----- watch(() => props.selectedItem, (newVal) => { if (newVal) { displayText.value = newVal.name } else if (!props.modelValue) { displayText.value = '' } }, { immediate: true }) // ----- Закрытие по клику вне ----- const handleClickOutside = (e: MouseEvent) => { if (dropdownRef.value && !dropdownRef.value.contains(e.target as Node)) { showDropdown.value = false } } onMounted(() => { document.addEventListener('click', handleClickOutside) }) onUnmounted(() => { document.removeEventListener('click', handleClickOutside) }) </script>