Продолжаем наше знакомство с админкой 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
, ровно как и выдают контроллеры Vespon-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
можно посмотреть в исходниках компонента.
На следующем уроке будем создавать и менять модели во всплывающих модальных окнах.
Наверное, все-таки:
frontend/src/admin/pages/users.vue
?Спасибо, поправил!