При разработке приложения часто возникает необходимость предоставить пользователям обратную связь о произошедших событиях или действиях. Например, вы можете сообщить им, что запись в базе данных была создана или обновлена. Однако отображение этой информации не должно происходить за счет нарушения содержимого на экране. Вот тут и появляются тосты.

Назван в честь сходства с хлебом, выскакивающим из тостера:

Всплывающее уведомление предоставляет простой отзыв об операции в небольшом всплывающем окне. Он заполняет только пространство, необходимое для сообщения, а текущая активность остается видимой и интерактивной. Тосты автоматически исчезают по истечении времени ожидания.

Это краткое руководство создано для приложений, использующих Laravel и Vue, а Инерция заполняет пробел между ними.

Настраивать

Если у вас еще нет приложения, настроенного выше, создайте новое, запустив laravel new NEW_APP, войдите в каталог только что созданного приложения и установите Laravel Breeze с помощью Composer, запустив composer require laravel/breeze --dev.

После добавления установите breeze с помощью Vue с помощью Artisan-команды php artisan breeze:install vue.

Пакет тостов

Есть хороший маленький пакет, который очень легко настроить, называется Vue Toastification. Добавьте пакет Vue Toastification, запустив

# yarn
yarn add vue-toastification@next

# npm
npm install --save vue-toastification@next

а затем обновите app.js, чтобы использовать только что добавленный пакет:

import './bootstrap';
import '../css/app.css';

import { createApp, h } from 'vue';
import { createInertiaApp } from '@inertiajs/vue3';
import { resolvePageComponent } from 'laravel-vite-plugin/inertia-helpers';
import { ZiggyVue } from '../../vendor/tightenco/ziggy/dist/vue.m';
+import Toast from "vue-toastification";
+import "vue-toastification/dist/index.css";

const appName = window.document.getElementsByTagName('title')[0]?.innerText || 'Laravel';

createInertiaApp({
    title: (title) => `${title} - ${appName}`,
    resolve: (name) => resolvePageComponent(`./Pages/${name}.vue`, import.meta.glob('./Pages/**/*.vue')),
    setup({ el, App, props, plugin }) {
        return createApp({ render: () => h(App, props) })
            .use(plugin)
            .use(ZiggyVue, Ziggy)
+           .use(Toast)
            .mount(el);
    },
    progress: {
        color: '#4B5563',
    },
});

Внутри HandleInertiaRequests.php есть метод share(), который определяет реквизиты, которые по умолчанию являются общими внутри приложения. Обновите это, чтобы поделиться ключом toast, который получает значение всплывающего уведомления из сеанса, если оно существует.

/**
    * Define the props that are shared by default.
    *
    * @return array<string, mixed>
    */
public function share(Request $request): array
{
    return array_merge(parent::share($request), [
        'auth' => [
            'user' => $request->user(),
            'role' => $request->user()?->roles()->first() ?? null,
            'permissions' => $request->user()?->roles()->first()?->permissions()->pluck('permission'),
        ],
        'ziggy' => function () use ($request) {
            return array_merge((new Ziggy)->toArray(), [
                'location' => $request->url(),
            ]);
        },
+       'toast' => $request->session()->get('toast', null),
    ]);
}

И, наконец, добавьте приведенное ниже в раздел скрипта используемого файла макета, в котором вы хотите отображать всплывающие уведомления.

import { useToast } from "vue-toastification";

const toast = useToast();

const toastMessage = computed(() => {
    return usePage().props.toast
})

watch(toastMessage, () => {
    toast(toastMessage.value.message, {
        type: toastMessage.value.type
    });
});

Теперь, если вы обновите модель в своем контроллере и вернетесь на ту же страницу с всплывающим сообщением, отображаемым для сеанса, как показано ниже, хорошее всплывающее уведомление должно появиться в верхней правой части экрана и автоматически скрыться после несколько секунд.

public function store(ModelRequest $modelRequest)
{
    Model::create($modelRequest->validated());

    return back()->with('toast', [
        'title' => 'Created',
        'message' => 'Model successfully created',
        'type' => 'success',
    ]);
}

Однако, если бы вы сохранили новую модель и перенаправили ее на другую страницу, уведомление еще не отображалось бы, поскольку Inertia уничтожает и воссоздает дочерние экземпляры макета между посещениями. Использование постоянного макета решает эту проблему.

Еще раз внутри app.js обновите свойство разрешения, чтобы установить файл макета по умолчанию.

+ import Layout from './Layout'

createInertiaApp({
- resolve: (name) => resolvePageComponent(`./Pages/${name}.vue`, import.meta.glob('./Pages/**/*.vue')),
+ resolve: name => {
+   const pages = import.meta.glob('./Pages/**/*.vue', { eager: true })
+   let page = pages[`./Pages/${name}.vue`]
+   page.default.layout = page.default.layout || Layout
+   return page
+ },
  // ...
})

Теперь все файлы Vue, соответствующие приведенным выше настройкам, будут использовать определенный вами макет и могут просто иметь корень <div> вместо импорта макета внутри файла Vue, как показано ниже:

<script setup></script>

<template>
    <div></div>
</template>

И метод, который перенаправляет на новую страницу, будет отображать всплывающее уведомление.

public function store(ModelRequest $modelRequest)
{
    Model::create($modelRequest->validated());

    return redirect()->route('models.index')->with('toast', [
        'title' => 'Created',
        'message' => 'Model successfully created',
        'type' => 'success',
    ]);
}

Однако это означает, что такие страницы, как страница входа в систему, будут иметь новый макет по умолчанию, что, вероятно, нежелательно. Исправьте это, установив макет по умолчанию внутри этого файла.

+ <script>
+ import GuestLayout from './GuestLayout'

+ export default {
+   // Using a render function...
+   layout: (h, page) => h(GuestLayout, [page]),
+ }
+ </script>

<script setup>
...
</script>

<template>
    <div>
        <Head title="Log in" />

        <LoginForm />
    </div>
</template>

Спасибо за чтение, и я надеюсь, что это помогло!

Майкл

Full Stack Developer / Директор / michaelconnelly.dev / madebyfabric.uk