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

Сегодня мы начинаем знакомиться с админкой нашего приложения. Напоминаю, это точно такое же приложение, как и 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 комментариев

Работает! Фантастика!
Василий Наумкин
Обалдеть!
Не то слово!
Да, и правда фантастика :) В 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
Личный сайт Василия Наумкина
Прямой эфир
Александр Наумов
23.07.2024, 00:20:37
Василий, спасибо большое!!
Василий Наумкин
01.07.2024, 11:56:41
Да, верно, именно так. А в контроллере, скорее всего, ловить данные методом post.
Василий Наумкин
26.06.2024, 09:38:15
О, точно, вылезает если не залогинен. Спасибо, исправил!
Василий Наумкин
09.04.2024, 04:45:01
> Ошибка 500 Это не похоже на ошибку Nginx, это скорее всего ошибка PHP - надо смотреть его логи. ...
Василий Наумкин
20.03.2024, 21:21:52
Volledig!
Андрей
14.03.2024, 13:47:10
Василий! Как всегда очень круто! Моё почтение!
russel gal
09.03.2024, 20:17:18
> А этот стоило написать хотя бы затем, чтобы получить комментарий от юзера, который ничего не писал...
Александр Наумов
27.01.2024, 03:06:18
Василий, спасибо! Извини, тупанул.
Василий Наумкин
22.01.2024, 07:43:20
Давай-давай!
Василий Наумкин
24.12.2023, 14:26:13
Спасибо!