Компоненты админки: vesp-table
Продолжаем наше знакомство с админкой Vesp.
Опираясь на свой опыт работы в MODX я постарался повторить примерную логику работы и здесь, для чего были написаны основные компоненты админки. Под капотом они используют BootstrapVue, но в будущем могут быть переписаны и на другую систему, при этом основной код страниц менять не придётся.
Для примера давайте разберёмся как именно работает раздел с пользователями
Вывод списка моделей
На странице frontend/src/admin/pages/users.vue мы встречаем первый основной копонент - vesp-table, который расширяет таблицу из BootstrapVue, принимает её основные параметры и добавляет свои:
<template>
<div>
<vesp-table
:url="адрес контроллера, например admin/users"
:header-actions="массив с действиями в шапке таблицы"
:table-actions="массив с действиями в строках таблицы"
:fields="массив выводимых колонок"
:filters="массив для фильтрации запросов, обычно там просто поиск"
:limit=`количество результатов на странице`
:sort="сортировка"
:dir="направление сортировки"
:row-class="метод для определения CSS классов строки"
:on-load="метод для изменение полученных данных перед выводом"
/>
<!--
Если у страницы есть дочерние маршруты (создание/изменение моделей),
то не забываем указать этот тег, для вставки содержимого дочерней страницы
-->
<nuxt-child />
</div>
</template>
У копонента всего один обязательный параметр для работы - url. Если убрать все остальные параметры, то вы увидите вот такую таблицу:
Теперь смотрим на javascript методы страницы:
// Объявляем экспорт адреса контроллера, для дочерних страниц
export const url = 'admin/users'
export default {
name: 'UsersPage', // имя страницы
// Проверка прав доступа к странице
validate({app}) {
// Этот метод вызывается Nuxt перед отрисовкой страницы, и является статичным
// В нём еще нет никакого this, но в него передаётся объект со всем нужным
// Так что здесь мы используем app.$hasScope вместо обычного this.$hasScope
// Эта страница требует право доступа users
return app.$hasScope('users')
},
data() {
// Основные данные компонента
return {
// возвращаем ранее объявленный url, чтобы его можно было использовать в template
url,
// массив фильтров таблицы
filters: {
// таблица знает только этот вариант - и выводит для него строку поиска
query: '',
},
// сортировка
sort: 'id',
dir: 'asc',
}
},
head() {
// здесь возврщаются мета-теги страницы
return {
// в нашем случае только title
title: [this.$t('models.user.title_many'), this.$t('project')].join(' / '),
}
},
// следующие значения вычисляются автоматически и зависят от других значений
computed: {
// таблица с юзерами показывает если только мы не перешли на
// дочерний адрес с выводом групп пользователей
isActive() {
return !this.$route.name.includes('roles')
},
// Кнопки в заголовке таблицы
headerActions() {
return [
// маршрут кнопки, иконка из FontAwesome, название
// и вариант оформления из Bootstrap
{route: 'users-create', icon: 'plus', title: this.$t('actions.create')},
{route: 'users-roles', icon: 'users', title: this.$t('models.user_role.title_many'), variant: 'info'},
]
},
// Кнопки в строке таблицы: может быть адрес или фунция
// Функция onDelete является встроенной
tableActions() {
return [
{route: 'users-edit-id', icon: 'edit', title: this.$t('actions.edit')},
{function: 'onDelete', icon: 'times', title: this.$t('actions.delete'), variant: 'danger'},
]
},
// Колонки таблицы
fields() {
// Ключ в массиве полученных из контроллера данных, заголовок,
// возможность сортировки при клике и форматирование значения
return [
{key: 'id', label: this.$t('components.table.columns.id'), sortable: true},
{key: 'username', label: this.$t('models.user.username'), sortable: true},
{key: 'fullname', label: this.$t('models.user.fullname'), sortable: true},
{key: 'role.title', label: this.$t('models.user.role')},
{
key: 'created_at',
// запись из стандартного лексикона
label: this.$t('components.table.columns.created_at'),
// это встроенное форматирование даты
formatter: this.$options.filters.datetime,
sortable: true,
},
]
},
},
methods: {
// Неактивные юзеры выводятся серым цветом
rowClass(item) {
return item && !item.active ? 'text-muted' : ''
},
},
}
Я использую computed значения для настроек таблицы, потому что админка у нас мультиязычная и при переключении языка внизу страницы всё оформление мгновенно сменит язык - потому что computed значения изменятся в соответствии с новым языком и таблица перерисуется. Магия VueJS!
Сами лексиконы находятся в src/admin/lexicons и загружаются через файл index.js, который соединяет стандартные записи из Vesp с вашими, пользовательскими. Для работы используется @nuxtjs/i18n.
Принцип работы
Тут всё просто:
- Таблица рендерится на страницу: шапка с кнопками и поиском
- Сразу же делается запрос на сервер по указанному url
- В ответ ожидается массив с total и rows, ровно как и выдают контроллеры Vesp
- Если указан параметр on-load, то весь ответ передаётся в него, и там вы можете пройтись по rows и что-то поменять. В овет обязательно нужно вернуть массив в том же формате.
- Затем в таблице отрисовываются полученные данные, если они есть.
- К ним добавляются кнопочки, если указаны
Дальше при любом изменении параметров данные снова будут запрошены с сервера и таблица перерисуется - так и работает поиск, например. Путём ввода буквы в строку поиска вы меняете внутренний параметр filters.query и это ведёт к новому запросу на сервер.
А там, в контроллере, вы принимаете этот параметр и фильтруете записи:
protected function beforeCount(Builder $c): Builder
{
if ($query = $this->getProperty('query')) {
$c->where(
static function (Builder $c) use ($query) {
$c->where('username', 'LIKE', "%{$query}%");
$c->orWhere('fullname', 'LIKE', "%{$query}%");
}
);
}
return $c;
}
Оформление строк
Vesp-table позволяет изменить значения в таблице аж 3мя способами:
- Метод on-load который меняет сами данные
- Указание метода formatter при опреледении колонки в fields()
- Указание специального template в шаблоне таблицы
Таблица использует особое указание шаблонов с именем колонки в скобках, например #cell(username). Это не является стандартным в Vue, оно было придумано специально для таблицы.
Мы же можем использовать эти шаблоны для сложного индивидуального оформления, например вывести аватарку пользователя через свой компонент user-avatar:
<vesp-table :url="url" ...>
<template #cell(username)="{item}">
<user-avatar :user="item" />
{{ item.username }}
</template>
</vesp-table>
Так это сделано в админке на моём сайте:
Подробнее про шаблоны таблицы советую почитать в документации.
Заключение
Vesp-table является мощным и гибким средством вывода списка моделей из контроллеров Vesp, мы будем постоянного его использовать.
Более подробно устройство vesp-table можно посмотреть в исходниках компонента.
На следующем уроке будем создавать и менять модели во всплывающих модальных окнах.
0
👍
👎
❤️
🔥
😮
😢
😀
😡
407
11.06.2022, 10:18:10
2 комментария
Александр Наумов
02.07.2022, 22:13:57
Наверное, все-таки: frontend/src/admin/pages/users.vue ?
Василий Наумкин
03.07.2022, 02:28:21
Спасибо, поправил!
bezumkin.ru
Личный сайт Василия Наумкина
Прямой эфир
Василий Наумкин
01.07.2024, 11:56:41
Да, верно, именно так.
А в контроллере, скорее всего, ловить данные методом post.
Василий Наумкин
26.06.2024, 09:38:15
О, точно, вылезает если не залогинен.
Спасибо, исправил!
Василий Наумкин
09.04.2024, 04:45:01
> Ошибка 500
Это не похоже на ошибку Nginx, это скорее всего ошибка PHP - надо смотреть его логи.
...
russel gal
09.03.2024, 20:17:18
> А этот стоило написать хотя бы затем, чтобы получить комментарий от юзера, который ничего не писал...
Александр Наумов
27.01.2024, 03:06:18
Василий, спасибо!
Извини, тупанул.