Этот проект реализует компонент на Vue 3 для отображения предложений из API, включая задержку запросов (debounce), адаптивный интерфейс, обработку ошибок и загрузок. Компонент предоставляет переиспользуемую архитектуру для API-взаимодействия и поддерживает динамическую настройку для разных источников данных.
- Задержка запросов к API: Уменьшает количество запросов, ожидая завершения ввода.
- Использование composable: Легко интегрируется в другие компоненты для работы с API.
- Поддержка разных API: Позволяет настраивать трансформацию данных и проверку параметров запроса.
- Адаптивный интерфейс: UI корректно отображается на всех устройствах.
- Обработка ошибок: Удобные сообщения для пользователей.
- Состояние загрузки: Показывает индикатор загрузки.
- Переиспользуемость компонента: Возможность подстроить саджест под свои нужды
- Реализация без 3rd-party: Реализация только на Vue+TS
project
├── src
│ ├── components
│ │ ├── VSuggest.vue
│ │ ├── VSuggestItem.vue
│ │ ├── VTag.vue
│ │ ├── VLoader.vue
│ │ ├── VCompanyEntity.vue
│ │ ├── VUserEntity.vue
│ ├── composables
│ │ └── useGetFetchSuggestions.ts
│ ├── utils
│ │ └── debounce.ts
│ │ └── clickOutside.ts
│ ├── types
│ │ └── index.ts
│ ├── App.vue
│ └── main.ts
├── public
│ └── assets
│ └── images
│ └── noPhoto.png
│ └── styles
│ └── style.css
│ └── suggestItemStyle.css
├── package.json
└── README.md
Стартовый компонент
<template>
<VSuggest :inputLabel="'Пользователь или компания'"
:tagAmount="1"
:placeholderSuggest="'Введите логин'"
:apiUrl="'https://habr.com/kek/v2/publication/suggest-mention'"
/>
</template>- Возможность настройки количества сущностей допустимых для выбора
- Возможность настройки placeholder и label
- Возможность передачи других URl, но с идентичной структурой
Основной компонент, обрабатывающий ввод и отображающий список предложений.
- Поле ввода с задержкой запросов (debounced fetchSuggestions).
- Интеграция с компонентами
VLoaderиVSuggestItem. - Динамическая обработка ошибок и пустых состояний.
<template>
<main>
<section>
<div>
<label for="suggest" class="label_description">
<span>*</span>
Пользователь или компания
</label>
<br />
<div class="input_block">
<VTag @removeItem="removeItem" :listItem="listItem" />
<input
id="suggest"
type="text"
ref="inputUserValue"
v-model="query"
@input="debouncedFetchSuggestions"
:disabled="listItem.length >= 1"
:placeholder="listItem.length >= 1 ? '' : 'Введите логин'"
@keydown.enter.prevent="addItemToList(query)"
aria-autocomplete="list"
aria-expanded="true"
/>
<VLoader v-if="isLoading" />
</div>
<p v-if="error" class="error-message">{{ error }}</p>
</div>
<VSuggestItem :responseData="responseData" @selectedItem="addItemToList" v-if="flagActiveList" />
</section>
</main>
</template>Компонент для отображения списка предложений.
responseData: массив предложений из API.
@selectedItem: отправляет выбранное предложение обратно в родительский компонент.
Простой компонент, отображающий индикатор загрузки.
Компонент для отображения выбранных тегов с возможностью их удаления.
Компонент для отображения данных когда тип = компания
Компонент для отображения данных когда тип != компания
Composable для взаимодействия с API.
interface FetchSuggestionsOptions<T> {
apiUrl: string; // URL API
transformResponse?: (data: any) => T[]; // Логика трансформации данных
validateQueryParams?: (query: string) => boolean; // Логика проверки запроса
}{
isLoading: Ref<boolean>,
error: Ref<string | null>,
responseData: Ref<T[]>,
fetchSuggestions: (query: string) => void
}Пример использования:
const { isLoading, error, responseData, fetchSuggestions } = useGetFetchSuggestions({
apiUrl: "https://api.example.com/suggestions",
transformResponse: (data) => data.items,
validateQueryParams: (query) => query.length >= 3,
});Функция для задержки выполнения запросов.
Пример кода:
export function debounce<T extends (...args: any[]) => void>(fn: T, delay: number): T {
let timeout: ReturnType<typeof setTimeout>;
return ((...args: Parameters<T>) => {
clearTimeout(timeout);
timeout = setTimeout(() => fn(...args), delay);
}) as T;
}.suggest-container {
width: clamp(20%, 35%, 80%);
border-radius: 4px;
box-shadow: rgba(0, 0, 0, 0.25) 0px 0.0625em 0.0625em,
rgba(0, 0, 0, 0.25) 0px 0.125em 0.5em,
rgba(255, 255, 255, 0.1) 0px 0px 0px 1px inset;
overflow-y: auto;
}
.avatar {
width: 100%;
height: 100%;
object-fit: cover;
border-radius: 8px;
}
@media (max-width: 768px) {
.suggest-container {
width: 80%;
}
}const { isLoading, error, responseData, fetchSuggestions } = useGetFetchSuggestions({
apiUrl: "https://habr.com/kek/v2/publication/suggest-mention",
transformResponse: (data) => data.data,
validateQueryParams: (query) => query.trim().length >= 3,
});- 400 Error: Отображает сообщение "Некорректный запрос. Повторите попытку."
- 500 Error: Отображает сообщение "Ошибка сервера. Повторите позже."
git clone https://github.com/eldenhard/suggest.gitnpm installnpm run dev