Знакомство с админкой

Сегодня мы начинаем знакомиться с админкой нашего приложения. Напоминаю, это точно такое же приложение, как и site, только не в минимальной комплектации, а со всеми нужными модулями по умолчанию.
Работает оно с помощью компонентов Booststrap-Vue, и и к его документации мы будем обращаться очень часто. Для оформления внешнего вида мы подключаем стили из Bootstrap.
Первом делом мы видим форму авторизации, которая работает при помощи модуля @nuxtjs/auth. Все модули подключаются в frontend/src/admin/nuxt.config.js, добавлением в Config.modules или Config.buildModules - это зависит от самого модуля и авторы пишут куда именно их нужно добавлять.
Этот модуль добавляет нам в приложение объект $auth, к которому можно обращаться из любого компонента. Если посмотреть в основной шаблон админки admin/layouts/default.vue, то вы увидите как именно выводится форма авторизации:

Логика авторизации

<template>
  <div class="min-vh-100 d-flex flex-column">
    <app-navbar />
    <b-container class="flex-grow-1">
      <-- вот наш компонент авторизации -->
      <app-login />
      <-- содержимое страницы выводится только авторизованным юзерам -->
      <nuxt v-if="$auth.loggedIn" class="pt-5" />
    </b-container>
    <app-footer />
  </div>
</template>
Шаблон проверяет статус пользователя через $auth.loggedIn и форма авторизации всегда наготове - как только статус пользователя изменится, она либо выведется, либо наоборот, спрячется.
Саму форму можно найти в файле frontend/src/admin/components/app/login.vue. Я не буду приводить её полностью, просто прокомментирую несколько моментов.
Во-первых, она выводится с помощью модального компонента Bootstrap-Vue:
<b-modal
    :visible="!$auth.loggedIn" // Показывать только гостям
    :title="$t('security.login')" // Заголовок берётся из лексиконов (про них позже)
    no-close-on-backdrop // Не закрывать при клике на фон
    no-close-on-esc    // Не закрывать при нажатии на Esc
    hide-header-close // Не выводить вообще кнопку закрытия
>
// ...
</b-modal>
Таким образом, форма всплывает в модалке, которую можно закрыть только одним способом - авторизоваться, после чего $auth.loggedIn станет true и окошко скроется.
Дальше смотрим метод submit:
// Отправка формы на сервер
async submit() {
  // при начале авторизации выставляем переменную, которую можно 
  // использовать для блокировки формы
  this.loading = true
  try {
    // Вызываем метод авторизации из $auth с данными формы
    await this.$auth.loginWith('local', {data: this.login})
    // Если всё хорошо - вывод сообщения об успехе
    await this.$toast.info(String(this.$t('security.greetings')))
    // Очистка формы
    this.onReset()
  } catch (err) {
    // Если ошибка - пишем в консоль
    console.error(err)
  } finally {
    // Но в любом случае после запроса убираем флаг загрузки
    this.loading = false
  }
},
// Обнудение полей формы авторизации
onReset() {
  this.login = {
    username: '',
    password: '',
  }
},
Собственно, вся авторизация заключается в магическом await this.$auth.loginWith('local', {data: this.login}), который отправит запрос на api/security/login, потому что так прописано в настройках Vesp. В ответ приходит токен, он сохраняется в локальное хранилище браузера и куки, а затем используется для всех запросов в API то тех пор, пока пользователь не разлогинится.
Первый же запрос после авторизации улетает в api/user/profile и возвращает данные пользователя, которые сохраняются в this.$auth.user.
Всё, что мы вернём в контроллере App\Controllers\User\Profile и будет сохранено в приложение, откуда потом можно будет получать, например, username:
if (this.$auth.loggedIn) {
    console.log(this.$auth.user.username)
} else {
    console.error('No user!')
}
Обычно в этом механизме ничего не нужно менять, кроме внешнего вида самой формы, и я его рассказываю вам просто для понимания принципа работы.
Выход из приложения можно делать в любом месте просто вызывая this.$auth.logout(). Это не только удалит токен из приложения, но и отправит запрос в api/security/logout, этот контроллер мы уже тоже рассматривали.

Разделы админки

Как вы помните, структура приложения формируется по директории pages, об этом можно почитать в документации Nuxt.
Адреса в нашей админке выглядят вот так:
user/
    profile.vue - изменение профиля текущего юзера, /admin/user/profile
users/ 
    edit/
        _id.vue - редактирование юзера, /admin/users/edit/id-юзера
    roles/ 
        edit/
            _id.vue - редактирование группы юзеров, /admin/users/roles/edit/id-роли
        create.vue - создание группы юзера, /admin/users/roles/create
    create.vue - создание нового юзера, /admin/users/create
    roles.vue - просмотр всех групп юзеров, /admin/users/roles
index.vue - корневая страница админки с главным меню, /admin/
users.vue  - просмотр всех пользователей, /admin/users
Таким образом, просто глядя на строку адреса в админке, вы уже знаете, какая страница за него отвечает.
Рекомендую вам сразу установить Vue DevTools для своего браузера, это очень облегчит понимание структуры приложения.

Минутка практики

Запускаем приложение для разработки: cd frontend && yarn dev:admin
Затем добавляем новую страницу test.vue в корне:
<template>
    <div>Hello, world!</div>
</template>

<script>
export default {
    name: 'TestPage',
}
</script>
И переходим по адресу /admin/test - новая страница уже на месте.
Vue DevTools услужливо показывает нам всю структуру приложения, включая TestPage внутри блока Nuxt.
Страница есть и работает, но она не появилась ни в меню, ни на главной странице. Для этого нам нужно добавить её в src/admin/plugins/menu.js - там перечислены все разделы админки в очень простом формате.
Приводим файл к такому виду:
export default [
   // наш новый раздел
  {
    name: 'test', // имя маршрута, его можно взять в DevTools
    title: 'Test', // название для вывода в меню
    scope: null, // Требуемое разрешение, мы ничего не требуем
  },
  {
    name: 'users',
    // ... оставляем что там есть по умолчанию
  },
]
И теперь всё в порядке: страница появилась и в меню, и на главной. Причём она правильно выделяется в меню как активная, когда мы переходим по адресу.
При этом в консоли можно увидеть предупреждения от системы лексиконов, но их мы будем проходить позже.

Заключение

Как вы видите, создание новых разделов админки заключается в простом создании новой страницы в директории pages.
Файл plugins/menu.js используется для перечисления разделов с указанием требуемых прав и названий - это даёт нам возможность располагать пункты меню в произвольном порядке.
На следующем уроке будем разбирать основные принципы работы страниц: проверку прав, загрузку данных из API, и передачу динамических параметров, типа id редактируемого пользователя.

13 комментариев

NightRider
Работает! Фантастика!
Василий Наумкин
Обалдеть!
Futuris
Не то слово!
Денис
Да, и правда фантастика :) В Windows столкнулся с удивительным моментом, перенос строки обозначается как CRLF, а на Mac & Unix LF и Prettier на это ругается, я использовал Sublime Text и пришлось настраивать конфиг (может пригодится)
"default_line_ending": "LF",
"default_line_ending": "unix",
  • в самой программе View → Line endings → Unix
Василий Наумкин
Работать на Windows - путь отважных =)
Вася
Сразу стало интересно как для футера сделать свое меню, а для шапки другое
Василий Наумкин
А можно же из 1 файла сделать 2 экспорта. По-умолчанию, и отдельно для футера:
export const Footer = {
    // ...
}

export default {
    //...
}
А дальше в app-footer.vue делаешь import {Footer} from '../../plugins/menu.js'.
Если нужна будет проверка прав, то придётся еще с Vuex похимичить, добавить новый getter по образу и подобию основного меню, но мы его пока не проходили.
Александр Наумов
Василий, а почему, что бы все заработало из этого урока (test.vue) нужно запускать cd frontend && yarn dev:admin , а сайт http://vesp-shop.test/ который под Nginx никаких изменений не видит?
Василий Наумкин
dev - это режим разработки, когда при каждом изменении исходных файлов код пересобирается и обновляется у тебя в браузере. Он никак не оптимизирован, не минифицирован, и вообще не предназначен для реальной работы на хостинге.
А вот на сайте vesp-shop.test уже готовый билд, который можно выгружать в продакшн. Он собирается после разработки отдельной командой - generate.
То есть, по сути, это 2 разных этапа одного процесса разработки сайта. Думаю, запуск проекта на хостинге у нас будет последнем уроком на курсе.
Александр Наумов
Василий, спасибо! Все понятно!
Александр Наумов
Василий, добрый день! Подскажи, пожалуйста, где запускается прелоадер в виде колеса перед появлением модульного окна. Что то не могу понять, это NuxtLoadingIndicator скрещенный с спиннером от BootstrapVue или ты свое, что-то сделал?
Василий Наумкин
Если я тебя правильно понял, то это встроенный индикатор.
Александр Наумов
Спасибо!!
bezumkin.ru
Личный сайт Василия Наумкина
Прямой эфир
Василий Наумкин
10.02.2025 05:39:25
Думаю да, можно. Но я не делал.
Василий Наумкин
04.02.2025 19:27:08
Я таким давно не занимаюсь и с MODX не работаю. Попробуйте обратиться к ребятам с modx.pro.
Василий Наумкин
23.12.2024 05:33:00
В MODX сначала создали проблему, автоматически генерируя адреса, а потом "решили" заморозкой. Так ч...
Дмитрий
14.12.2024 09:10:38
Василий, прошу прощения, тупанул, не разобрался сразу. Фреймворк отличный! "Чистый лист" на vue, рис...
Василий Наумкин
05.12.2024 20:01:14
В итоге основная ошибка была в неправильном общем root в Nginx, из-за чего запросы не улетали на фай...
inna
06.11.2024 15:47:13
Да. Все работает. Спасибо.
Василий Наумкин
01.07.2024 11:56:41
Да, верно, именно так. А в контроллере, скорее всего, ловить данные методом post.
Василий Наумкин
26.06.2024 09:38:15
О, точно, вылезает если не залогинен. Спасибо, исправил!
Василий Наумкин
20.03.2024 21:21:52
Volledig!
Андрей
14.03.2024 13:47:10
Василий! Как всегда очень круто! Моё почтение!
Уровни подписки
Спасибо!
500 ₽ в месяц
Эта подписка ничего не даёт, просто возможность сказать спасибо за мои заметки. Подписчики отмечаются зелёненьким цветом в комментариях.
Большое спасибо!
1 000 ₽ в месяц
И эта подписка не даёт ничего, кроме оранжевого цвета в комментариях и возможности сказать спасибо, но уже большое!