Element Plus — готовый UI для Vue
Element Plus — библиотека готовых UI-компонентов для Vue 3: кнопки, формы, таблицы, диалоги, уведомления и датапикеры. Вместо того чтобы верстать каждый элемент вручную, ты берёшь готовый компонент и настраиваешь его через props.
1. Что такое UI-библиотека
UI-библиотека — набор стилизованных компонентов с единым дизайном и доступностью. Плюсы Element Plus:
- 80+ компонентов с документацией и примерами;
- поддержка Vue 3 + TypeScript из коробки;
- темы через CSS-переменные;
- i18n — встроенные переводы, включая русский.
2. Установка и подключение
npm install element-plus
npm install @element-plus/icons-vue// main.ts — полное подключение (проще всего)
import { createApp } from "vue";
import ElementPlus from "element-plus";
import "element-plus/dist/index.css";
import * as ElementPlusIconsVue from "@element-plus/icons-vue";
const app = createApp(App);
for (const [name, comp] of Object.entries(ElementPlusIconsVue)) {
app.component(name, comp);
}
app.use(ElementPlus).mount("#app");// Auto-import: компоненты подхватываются автоматически
npm install -D unplugin-vue-components unplugin-auto-import
// vite.config.ts
import AutoImport from "unplugin-auto-import/vite";
import Components from "unplugin-vue-components/vite";
import { ElementPlusResolver } from "unplugin-vue-components/resolvers";
plugins: [
AutoImport({ resolvers: [ElementPlusResolver()] }),
Components({ resolvers: [ElementPlusResolver()] })
]3. Кнопки: el-button
<el-button>По умолчанию</el-button>
<el-button type="primary">Основная</el-button>
<el-button type="success" round>Скруглённая</el-button>
<el-button type="danger" :disabled="loading">Удалить</el-button>
<el-button type="primary" :icon="Search" circle />Типы: primary, success, warning, danger, info. Атрибуты: round, circle, plain, text, link, disabled.
4. Формы: el-form и валидация
<script setup lang="ts">
import { reactive } from "vue";
import { FormInstance, FormRules } from "element-plus";
const formRef = ref<FormInstance>();
const form = reactive({ email: "", password: "" });
const rules: FormRules = {
email: [
{ required: true, message: "Email обязателен", trigger: "blur" },
{ type: "email", message: "Некорректный email", trigger: "blur" }
],
password: [
{ min: 6, message: "Минимум 6 символов", trigger: "blur" }
]
};
function submit() {
formRef.value?.validate((valid) => {
if (valid) console.log("OK", form);
});
}
</script>
<template>
<el-form ref="formRef" :model="form" :rules="rules" label-width="100px">
<el-form-item label="Email" prop="email">
<el-input v-model="form.email" placeholder="you@example.com" />
</el-form-item>
<el-form-item label="Пароль" prop="password">
<el-input v-model="form.password" type="password" show-password />
</el-form-item>
<el-button type="primary" @click="submit">Войти</el-button>
</el-form>
</template>rules с триггерами blur / change. Механика похожа на то, что ты видел в формах Vue 3.5. Таблицы: el-table
<el-table :data="products" stripe style="width: 100%">
<el-table-column prop="id" label="ID" width="60" />
<el-table-column prop="title" label="Название" />
<el-table-column prop="price" label="Цена" width="120">
<template #default="{ row }">
{{ row.price }} ₽
</template>
</el-table-column>
<el-table-column label="Действия" width="140">
<template #default="{ row }">
<el-button size="small" @click="onEdit(row)">Изменить</el-button>
</template>
</el-table-column>
</el-table>Полезные атрибуты: stripe (зебра), border, sortable, filterable, пагинация через pagination.
6. Диалоги: el-dialog
<el-button @click="dialogVisible = true">Открыть</el-button>
<el-dialog v-model="dialogVisible" title="Создать товар" width="480px">
<el-input v-model="form.title" placeholder="Название" />
<template #footer>
<el-button @click="dialogVisible = false">Отмена</el-button>
<el-button type="primary" @click="dialogVisible = false">Сохранить</el-button>
</template>
</el-dialog>v-model на visible. Не забудь закрыть диалог вопросом: данные формы теряются при закрытии, если не сохранить заранее.7. Уведомления: ElMessage и ElNotification
import { ElMessage, ElNotification } from "element-plus";
ElMessage.success("Товар добавлен");
ElMessage.warning("На складе мало товара");
ElMessage.error("Не удалось сохранить");
ElNotification({
title: "Готово",
message: "Отчёт сформирован",
type: "success",
position: "bottom-right",
duration: 3000
});duration (мс).8. Темизация через CSS-переменные
Все цвета библиотеки — CSS-переменные вида --el-color-primary. Переопредели их глобально:
// assets/element.css
:root {
--el-color-primary: #2563eb;
--el-color-primary-light-3: #4f83ef;
--el-color-success: #16a34a;
--el-color-danger: #dc2626;
--el-border-radius-base: 8px;
}// main.ts — после стилей библиотеки
import "element-plus/dist/index.css";
import "./assets/element.css";9. ConfigProvider и локализация
Датапикеры и пагинация по умолчанию по-английски. Подключи русскую локаль:
import ru from "element-plus/es/locale/lang/ru"; // русская локаль
<el-config-provider :locale="ru">
<App />
</el-config-provider>size и zIndex, заданные один раз на контейнере, действуют на все вложенные компоненты.10. Меню и навигация: el-menu, el-tabs
Навигация — сердце любой админки. Горизонтальное или вертикальное меню строится через el-menu:
<el-menu mode="vertical" :default-active="$route.path"
router @select="onSelect">
<el-menu-item index="/dashboard">Дашборд</el-menu-item>
<el-sub-menu index="/products">
<template #title>Товары</template>
<el-menu-item index="/products/list">Список</el-menu-item>
<el-menu-item index="/products/categories">Категории</el-menu-item>
</el-sub-menu>
</el-menu>Вкладки el-tabs организуют содержимое одной страницы:
<el-tabs v-model="activeTab">
<el-tab-pane label="Основное" name="main">...</el-tab-pane>
<el-tab-pane label="SEO" name="seo">...</el-tab-pane>
</el-tabs>router заставляет пункты меню работать как переходы по маршрутам — отдельные клики не нужны.11. Выбор дат: el-date-picker
Пикер дат умеет диапазоны, недели и кастомные форматы:
<el-date-picker
v-model="range"
type="daterange"
start-placeholder="Начало"
end-placeholder="Конец"
value-format="YYYY-MM-DD"
/><!-- месяц и год -->
<el-date-picker v-model="month" type="month" />
<!-- быстрые диапазоны -->
<el-date-picker type="datetimerange" :shortcuts="shortcuts" />const shortcuts = [
{ text: "Сегодня", value: () => [new Date(), new Date()] },
{ text: "7 дней", value: () => [dayjs().subtract(7, "day"), dayjs()] },
];value-format, чтобы не переводить даты руками.12. Выпадающие: el-select и el-cascader
Селект с поиском и множественным выбором — база форм:
<el-select v-model="cities" multiple filterable clearable
placeholder="Выберите города">
<el-option v-for="c in citiesAdd" :key="c"
:label="c" :value="c" />
</el-select>Для иерархии (страна → регион → город) — каскадный выпадающий список:
<el-cascader :options="regions" v-model="path"
:props="{ value: 'id', label: 'name', children: 'children' }" />const regions = [
{ id: 1, name: "Россия", children: [
{ id: 11, name: "Москва" },
{ id: 12, name: "Санкт-Петербург" },
]},
];remote-атрибут и событие search.13. Собираем админку: практика
Теперь всё готово для типовой админки — таблица, форма в диалоге, пагинация:
<el-table :data="rows" v-loading="loading">
<el-table-column prop="title" label="Название" />
<el-table-column prop="price" label="Цена" />
<el-table-column label="Действия" width="160">
<template #default="{ row }">
<el-button size="small" @click="edit(row)">Изменить</el-button>
<el-popconfirm title="Удалить?" @confirm="remove(row)">
<template #reference>
<el-button size="small" type="danger">Удалить</el-button>
</template>
</el-popconfirm>
</template>
</el-table-column>
</el-table>
<el-pagination v-model:current-page="page" :total="total"
layout="prev, pager, next" />:model у el-form.14. Загрузка файлов: el-upload
Компонент el-upload закрывает 90% задач загрузки: картинки, документы, прогресс и валидация на лету:
<el-upload
action="/api/upload"
:on-success="onUploaded"
:limit="1"
accept="image/*">
<el-button type="primary">Загрузить изображение</el-button>
</el-upload>action— адрес загрузки; для кастомного запроса —:http-request;:before-upload— проверка размера и типа до отправки;drag— drag-and-drop зона;multiple— батч-загрузка;- после успеха отдай файл назад в
:file-list, чтобы он остался видимым.
const beforeUpload = (file) => {
if (file.size > 2 * 1024 * 1024) {
ElMessage.error("Файл больше 2 МБ");
return false;
}
return true;
};15. Продвинутые таблицы: сортировка, фильтры
Таблицы для админок редко живут без сортировки и фильтров. Включение — декларативное:
<el-table :data="rows" @sort-change="onSort">
<el-table-column prop="price" label="Цена" sortable />
<el-table-column label="Статус">
<template #default v-if>
</template>
</el-table-column>
</el-table>sortableсортирует по колонке;sort-by— по вложенному полю;- фильтры:
:filters+filter-methodили серверные через@filter-change; - type="expand" — раскрывающиеся строки с деталями без перехода на страницу;
- для тысяч строк — виртуализация:
el-table-v2илиmax-heightсо скроллом.
16. Доступность и клавиатура
Element Plus заботится об a11y, но часть ответственности на тебе:
- focus-стили: не убирай
outline: на кнопках и полях он обязателен; - для форм —
label, связкаaria-, если визуальная подпись скрыта; - все диалоги закрываются по Esc, возвращай фокус после закрытия; кнопки управляются пробелом/Enter — проверь это на своём кастомном коде;
- контраст: не хардкодь приглушённые «хинты» — дай достаточный контраст для обязательных действий.
<el-dialog title="Редактировать" :close-on-click-modal="false"
aria-labelledby="dlg-title">
<h2 id="dlg-title">Редактирование записи</h2>
...
</el-dialog>17. Динамические формы и кастомная валидация
Формы с «полями по кнопке» (товары в заказе, вложения) собираются через массив полей и динамический rules:
<el-form :model="form" :rules="rules" ref="formRef">
<div v-for="(line, i) in form.lines" :key="i">
<el-form-item :prop="`lines.${i}.name`"
:rules="{ required: true, message: 'Укажите название' }">
<el-input v-model="line.name" />
</el-form-item>
</div>
<el-button type="primary" @click="addLine">+ Строка</el-button>
</el-form>- путь валидации —
lines.0.name: указывай через индекс элемента; - кастомная функция в
validatorпокрывает бизнес-правила (мнимальное количество); - после удаления строки пересчитывай
validateField, чтобы не осталось ложных ошибок.
const validators = {
minTotal: (rule, value, cb) =>
value < 1 ? cb(new Error("Сумма не может быть пустой")) : cb(),
};18. Кастомные колонки и экспорт данных
Таблицы редко показывают сырые поля. Слоты и форматтеры — базовый инструмент:
<el-table-column prop="price" label="Цена">
<template #default="{ row }">
{{ formatPrice(row.price) }}
</template>
</el-table-column>- форматтеры дат и валют выноси в утилиты — переиспользуются во всех таблицах;
- экспорт CSV/Excel: бери только видимые колонки, скрытые — исключай;
- фильтр в шапке — слот с кастомным содержимым и отдельным el-select.
function exportCsv(ref) {
const rows = ref.value.getSelectionRows().map(r => [r.title, r.price]);
downloadCsv(rows); // браузер сам скачает
}19. Skeleton, Empty и состояния загрузки
Пользователь прощает паузу, но не белый экран. Специальные состояния закрывают UX-дыры:
<el-skeleton :loading="loading" animated>
<template #template>
<el-skeleton-item variant="h3" />
<el-skeleton-item variant="rect" />
</template>
<template #default>
<p>Список товаров…</p>
</template>
</el-skeleton>
<el-empty v-if="!rows.length" description="Ничего не найдено">
<el-button @click="reload">Обновить</el-button>
</el-empty>- спиннер на кнопке (
:loadingна el-button) — мгновенный фидбек; - нет данных —
el-emptyс действием, а не голый текст; - ошибку сети показывай отдельно от пустого состояния.
animated и согласуй с темой.20. Большие списки: el-table-v2 и виртуализация
Десять тысяч строк на странице съедают память и рендер. Виртуализация рисует только видимое:
<el-table-v2 :columns="columns" :data="rows"
:width="800" :height="400" fixed />- el-table-v2 скроллит и переиспользует строки — для больших списков без сложных ячеек;
- серверная пагинация (limit/offset) — правильный выбор для 100К+ строк;
- для выпадающих списков — remote-вариант с поиском.
<el-select :remote-method="search" remote filterable
v-model="user" :loading="selectLoading"> ... </el-select>21. Чек-лист UI перед продакшеном
UI-финал решает восприятие, но косяки ловятся позже. Пройди по списку:
- компоненты одной строки — одной высоты (form item, кнопки);
- пустые и ошибочные состояния прикрыты (el-empty, el-message, el-dialog с текстом);
- локализация: ConfigProvider с
ruи кастомные тексты сообщений; - тема: CSS-переменные переопределены глобально, а не точечно в компоненте;
- доступность: tab-порядок и aria-подписи на иконочных кнопках.
const app = createApp(App)
app.use(ElementPlus, { locale: ru })
app.mount("#app")Что дальше
Собери админку на Vue 3: таблица товаров, форма с валидацией, корзина в Pinia. Запросы к API — курс REST API, а тесты компонентов — на Vitest c @vue/test-utils.