Компоненты админки: vesp-modal
Следующий важный компонент админки - модальные окна для создания и редактирования моделей.
Vesp-modal расширяет компонент из BootstrapVue, принимает всего его параметры и добавляет отправку формы с данными.
Редактируемые данные передаются через v-model, и если в них указан первичный ключ (по умолчанию поле id, но можно настроить и другой), то форма отправится методом PATCH (редактирование), а если нет, то PUT (создание).
Содержимое формы вставляется в слот #form-fields, а сами формы я предлагаю хранить отдельно от модальных окон, для удобства. Давайте разберём форму работы с пользователями.
Создание модели
Модальные окна в админке находятся в отдельных маршрутах в директории pages, это даёт возможность переходить сразу по прямым ссылкам на создание и редактирование чего-либо.
Эти маршруты являются доченими по отношению к таблице с моделями, поэтому в шаблоне с таблицей обязательно должен быть тег <nuxt-child /> - именно в него и будет вставлена страница с модалкой.
Так и получается, что список пользователей находится по адресу admin/users, а создание нового пользоветеля admin/users/create.
<template>
<!--Окну обязательно нужено передать данные и адрес для отправки-->
<vesp-modal v-model="record" :url="url" :title="$t('models.user.title_one')">
<!--Вставляем поля формы в соответствующий слот компонента-->
<template #form-fields>
<!--И синхронизируем данные между формой и модалкой-->
<form-user v-model="record" />
</template>
</vesp-modal>
</template>
<script>
// Чтобы не дублировать адрес, импортируем его из страницы с таблицей
// Вдруг потом нужено будет его поменять? Проще это сделать в 1 месте
import {url} from '../users'
// Импортируем компонент с полями формы
import FormUser from '@/components/forms/user'
// Экспортируем url снова, для окна редактирования
export {url}
// Основной экспорт
export default {
components: {FormUser},
data() {
return {
// передаём url в template
url,
// и выставляем данные формы по умолчанию
record: {
fullname: '',
password: '',
active: true,
},
}
},
}
</script>
Вся логика отправки формы находится в модальном окне, поэтому компонент <form-user> содержит только поля формы, и ничего более. Ни тега form, ни индикации загрузки - ничего.
Иначе пришлось бы дублировать функционал отправки в каждой форме, а моей задачей было наоборот, всё упростить.
Смотрим <form-user> в сокращённом виде:
<template>
<div>
<!--Я использую компоненты BootstrapVue, но вообще можно псиать обычные-->
<!--div, input и прочие теги-->
<b-row>
<b-col md="6">
<!--Названия полей мультиязычные по умолчанию-->
<b-form-group :label="$t('models.user.username')">
<!--Поля ввода редактируют присланные данные-->
<b-form-input v-model.trim="record.username" required autofocus />
</b-form-group>
</b-col>
<b-col md="6">
...
</b-col>
</b-row>
<b-form-group :label="$t('models.user.fullname')">
<b-form-input v-model.trim="record.fullname" required />
</b-form-group>
...
</div>
</template>
<script>
export default {
name: 'FormUser',
// Компонент принимает только один параметр - value
props: {
value: {
type: Object,
required: true,
},
},
// И с помощью этого value мы создаём новый вычисляемый параметр
computed: {
// В эту логику сразу трудно въехать, смотрите ниже
record: {
get() {
return this.value
},
set(newValue) {
this.$emit('input', newValue)
},
},
},
...
}
</script>
Следите за руками:
- Данные в модальном окне передаются через v-model, внутри компонент такие данные доступны как value. Это у Vue так придумано, нужно просто запомнить
- v-model всегда означает, что передаваемые данные должны быть реактивными, то есть когда они меняются внутри компонента, мы это видим снаружи.
Как это делается в Vue? Мы получаем данные в this.value и с их помощью создаём новое вычисляемое свойство record, у которого есть 2 метода: get и set.
Get срабатывает при обращении к полям record, а set - при изменении. Так что, напрямую данные менять нельзя, и это связано с реактивностью работы.
Вместо изменения напрямую, функция set отправляет событие об изменении, которое принимает родительский компонент, меняет данные у себя передаёт уже изменённые данные обратно через v-model.
Честно скажу, я вникал в эту логику примерно неделю, пока не привык и не понял как с этим работает. Это реально сложная парадигма, и если пока не получается понять - то просто заучите, что при передаче данных через v-model в компонент, он не меняет их сам, он только оповещает родительский компонт об изменениях, чтоб тот их изменил у себя.
Именно эта логика гарантирует синхронность данных, когда в модалке v-model используется 2 раза: для самого окна, и для формы. Потому что при изменении чего-то в форме, событие об этом улетает в модалку, та обновляет данные и новые данные выставляются обратно в обоих местах.
Более подробно можно почитать в документации Vue, а мы можем пока отправить форму на сервер. Для этого прописан специальный метод в vesp-modal:
async submit() {
try {
// Передаём данные в хук beforeSubmit - он должен проверить их перед отправкой
// Перед этим убираем реактивность из данных, путём конвертации объекта в
// JSON и обратно
const record = this.beforeSubmit(JSON.parse(JSON.stringify(this.record)))
if (record) {
// Если в ответ вместо массива пришла строка - это ошибка проверки, выводим её
if (typeof record === 'string') {
this.$toast.error(record)
} else {
// Ошибки нет, выставляем флаг загрузки
this.loading = true
// Определяем метод отправки: PATCH или PUT
const method = record.id ? this.actionEdit : this.actionCreate
const {data} = await this.$axios[method](this.url, record)
// Даём событие об успешной отправке, на него можно подписаться
this.$emit('after-submit', data)
// И даём еще одно, глобальное событие - на него по умолчанию
// подписывается таблица с редактируемыми данными
// Это даёт нам обновление таблицы моделей при работе с ними в модалке
if (this.updateKey) {
this.$root.$emit(`app::${this.updateKey}::update`, data)
}
// Закрываем окно
this.hide()
}
}
} catch (err) {
} finally {
this.loading = false
}
},
При закрытии окна срабатывает метод, который возвращает нас на предыдущий адрес, с таблицей моделей. Как видите, всё максимально автоматизировано.
Редактирование модели
Редактирование пользователей находится по адресу admin/users/edit/idюзера и расширяет страницу создания модели.
У него вообще нет тега <template> (хотя его можно и добавить), потому что мы никак не меняем внешний вид компонента. Эта та же формы создания модели, с одним отличием - мы должны ей подсунуть уже созданную ранее модель.
<script>
// Загружаем страницу создания юзера и адрес для запросов из предыдущей модалки
import Create, {url} from '../create'
export default {
// Указываем, что наш компонент раширяет родительский
// То есть, он наследует всё, что прописано в UserCreate
extends: Create,
// Страница работает только если прислан id юзера
validate({params}) {
return /^\d+$/.test(params.id)
},
// Эта особенная функция загружает данные до создания компонента
// и является особенностью Nuxt, как и метод validate
async asyncData({app, params, error}) {
try {
// Получаем данные методом GET из контроллера
const {data: record} = await app.$axios.get(url + '/' + params.id)
// Возвращаем массив с данными, которые будут выставлены компоненту
// Родительский компонент выставлял переменную record, её мы и заменяем
return {record}
} catch (e) {
// Если данные не получаются - ошибка
error({statusCode: e.statusCode, message: e.data})
}
},
}
</script>
Вот так просто мы унаследовали родительский компонент и добавили в него получение начальных данных модели.
Функция asyncData обращается в API и заменяет родительский массив record, указанный в UserCreate по умолчанию:
record: {
fullname: '',
password: '',
active: true,
},
Таким образом форма видит начальные данные, в которых есть id модели и метод форма будет отправляться на этот id методом PATCH. Вместо создания новой модели будет отредактирована существующая.
Заключение
Эта логика выкристаллизовалась сама собой во время разработки многих админок на Vesp. С каждым разом я хотел писать всё меньше кода, поэтому определял общие моменты в работе и выносил их в родительский @vesp/frontend.
Именно таким образом и появились vesp-table и vesp-modal, которые закрывают 99% потребностей в управлении обычными моделями.
Думаю, на следующием уроке мы пробежимся по остальным компонентам админки, они гораздо проще и в основном добавляют какие-то типы ввода, которых нет в BootstrapVue, навроде выбора цвета или ввода телефонных номеров.
0
👍
👎
❤️
🔥
😮
😢
😀
😡
388
13.06.2022, 10:27:02
8 комментариев
NightRider
14.06.2022, 21:48:42
Пока что каша в голове, но думаю когда будем создавать другие формы, то в голове начнёт проясняться)
Василий Наумкин
14.06.2022, 22:09:23
Да, конечно, пока что это всё просто знакомство с системой - дальше будет реальная работа.
Александр Наумов
17.02.2023, 17:00:44
Василий, добрый день!
Дай, пожалуйста, наводку, как сделать копирование содержимого товара для заполнения полей при создании нового товара данными копируемого товара.
Если просто скопировать папку admin/pages/products/edit и переименовать ее в copy, что еще нужно подкрутить, или я не правильно мыслю?
Василий Наумкин
17.02.2023, 18:12:03
Создание маршрута и окошка - да, просто скопировать edit.
Только после получения данных товара в asyncData надо убрать из него id, чтобы при сохранении был создан новый, а не изменён старый.
Александр Наумов
18.02.2023, 16:53:23
Василий, спасибо большое, ты наше Все!!!
Разобрался, сделал.
Александр Наумов
18.02.2023, 17:06:35
И еще, в файле admin/pages/products/edit/_id.vue
в строке 8 .test(params.id)- лишнее затесалось?
Василий Наумкин
19.02.2023, 06:29:54
Нет, это проверка параметра маршрута на то, что в параметре id какое-то число.
Потому что, если на страницу передали не id, то запрос на сервер можно и не делать.
Александр Наумов
19.02.2023, 15:12:13
Спасибо!
bezumkin.ru
Личный сайт Василия Наумкина
Прямой эфир
Василий Наумкин
03.12.2024, 13:13:34
Генерация - это создание статичный файлов, для их работы потом pm2 не нужен, только правильная настр...
Василий Наумкин
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
Василий, спасибо!
Извини, тупанул.