Motyw
Routing
Składanie tras
Plik src/router/routes.js importuje trasy każdego modułu i skleja je w jedną tablicę:
js
// src/router/routes.js
const routes = [
...authRoutes,
...userRoutes,
...mediaRoutes,
// ... pozostałe moduły
// --place where generator will inject new routes
// coreRoutes should be the last one
...coreRoutes,
];Tablica coreRoutes musi pozostać ostatnia
Plik modules/core/routes/public.js zawiera trasę /:catchAll(.*)* obsługującą stronę 404. Wszystko, co zostanie dopisane poniżej niej, nigdy się nie dopasuje.
Wewnątrz modułu trasy poszczególnych modeli agreguje plik routes/index.js:
js
// src/modules/blog/routes/index.js
import articleRoutes from "modules/blog/routes/articles";
import categoryRoutes from "modules/blog/routes/categories.js";
const routes = [
articleRoutes,
categoryRoutes,
// --place where generator will inject new routes
];
export default routes;Kształt trasy modelu
Każdy model deklaruje własny rekord najwyższego poziomu, zawierający layout i flagę secured. Właściwe strony są jego dziećmi:
js
// src/modules/blog/routes/articles.js
const articleRouteName = "blog.articles";
export default {
path: "/blog/articles",
component: () => import("layouts/MasterLayout.vue"),
meta: { secured: true },
children: [
{
path: "",
name: `${articleRouteName}.index`,
meta: { access: `${articleRouteName}.index` },
component: () => import("modules/blog/pages/articles/ArticleIndexPage.vue"),
},
{
path: "create",
name: `${articleRouteName}.create`,
meta: { access: `${articleRouteName}.store` },
component: () => import("modules/blog/pages/articles/ArticleCreatePage.vue"),
},
{
path: ":id",
name: `${articleRouteName}.edit`,
meta: { access: `${articleRouteName}.show` },
component: () => import("modules/blog/pages/articles/ArticleEditPage.vue"),
},
],
};Akcja w nazwie trasy nie musi odpowiadać nazwie uprawnienia:
| Nazwa trasy | meta.access |
|---|---|
blog.articles.index | blog.articles.index |
blog.articles.create | blog.articles.store |
blog.articles.edit | blog.articles.show |
Uprawnienia nazwane są tak jak metody kontrolera po stronie backendu, natomiast trasy — tak jak ekrany w panelu.
Konwencja <moduł>.<modele>.<akcja>
Z nazwy trasy korzysta pięć mechanizmów naraz — jej format nie jest więc kwestią estetyki. Zmiana nazwy psuje wszystkie pięć jednocześnie:
| Mechanizm | Wyliczana wartość | Plik |
|---|---|---|
| Guard nawigacji | uprawnienie (meta.access albo to.name) | modules/core/helpers/navigation-guards.js |
useCoreTable | trasa edycji ${alias}.edit | modules/core/composables/table.js |
useFormSubmit | trasa powrotu ${alias}.index i trasa po zapisie ${alias}.edit | modules/core/composables/form.js |
useCoreBreadcrumbs | klucze i18n powstałe z rozbicia nazwy po kropce | modules/core/composables/breadcrumbs.js |
useNestedRoutes | aktywna pozycja menu, na podstawie dwóch pierwszych segmentów | src/composables/useNestedRoutes.js |
W praktyce do komponentów przekazywany jest wyłącznie alias:
js
// src/modules/blog/components/BlogArticlesTable.vue
useCoreTable({ resource: "/blog/articles", alias: "blog.articles" });Pozostałe trasy wyliczane są automatycznie.
Odstępstwo od konwencji wymaga jawnej konfiguracji
Jeżeli trasa edycji nie nazywa się ${alias}.edit, należy podać ją bezpośrednio — w useCoreTable poprzez własną definicję akcji edit, a w useFormSubmit poprzez opcje onSaveRoute i redirectRoute.
Zabezpieczanie tras
Za kontrolę dostępu odpowiadają dwa pola w obiekcie meta:
| Pole | Umiejscowienie | Znaczenie |
|---|---|---|
secured: true | rekord nadrzędny, ten z layoutem | Trasa wymaga zalogowania. Guard sprawdza to.matched.some(r => r.meta.secured), wystarczy więc zadeklarować je raz, na rodzicu |
access: "..." | rekord dziecka | Klucz uprawnienia. Domyślnie przyjmowana jest wartość to.name |
Działanie samego guarda opisuje Autentykacja.
Dodawanie trasy niestandardowej
Dopisz kolejne dziecko do tablicy children. Poniżej ekran zmiany kolejności kategorii w module FAQ:
js
// src/modules/faq/routes/categories.js
{
path: "reorder",
name: `${categoriesRouteName}.reorder`,
meta: { access: `${categoriesRouteName}.update` },
component: () => import("modules/faq/pages/categories/CategoryReorderPage.vue"),
},Kolejność dzieci ma znaczenie
Trasy reorder i create muszą zostać zadeklarowane przed :id, w przeciwnym razie trasa parametryczna przechwyci je jako identyfikator.
Uzupełnij następnie klucz faq.categories.title.reorder w tłumaczeniach modułu — bez niego breadcrumbs wyświetlą surowy klucz.
Breadcrumbs
js
// src/modules/blog/pages/articles/ArticleEditPage.vue
const breadcrumbs = useCoreBreadcrumbs("blog.articles.edit");Composable useCoreBreadcrumbs(key, data?) rozbija klucz na <moduł>.<modele>.<akcja> i buduje ścieżkę zgodnie z konwencją i18n.
Dla akcji index powstają dwa elementy:
t("blog.name", 2) → t("blog.articles.title.index")Dla pozostałych akcji powstają trzy, przy czym element środkowy jest odnośnikiem:
t("blog.name", 2) → t("blog.articles.title.index") → t("blog.articles.title.edit")
{ name: "blog.articles.index" }Zwracana jest wartość typu computed, przekazywana bezpośrednio do strony:
vue
<!-- src/modules/blog/pages/articles/ArticleIndexPage.vue -->
<base-card-page :title="$t('blog.articles.title.index')" :breadcrumbs="breadcrumbs">Własne breadcrumbs
Konwencja nie wystarcza, gdy breadcrumbs zależą od danych rekordu — na przykład gdy ostatni element ma prezentować tytuł artykułu. Utwórz wtedy w module plik <moduł>.breadcrumbs.js; import.meta.glob wykryje go automatycznie.
Plik eksportuje funkcję przyjmującą t i zwracającą mapę kluczy "<modele>.<akcja>":
js
// src/modules/blog/blog.breadcrumbs.js
export default (t) => ({
"articles.edit": (data) => [
{ label: t("blog.name", 2) },
{ label: t("blog.articles.title.index"), to: { name: "blog.articles.index" } },
{ label: data?.title ?? t("blog.articles.title.edit") },
],
});Dane przekazywane są drugim argumentem composable:
js
// src/modules/blog/pages/articles/ArticleEditPage.vue
const breadcrumbs = useCoreBreadcrumbs("blog.articles.edit", () => query.data.value);Klucz nieodnaleziony w mapie oznacza powrót do zachowania konwencyjnego — nadpisywać można więc wyłącznie wybrane ekrany, bez opisywania pozostałych.
TIP
Pojedynczy element breadcrumbs jest obiektem { label, to? }. Pominięcie to sprawia, że element renderowany jest jako zwykły tekst.
Layouty w trasach
| Layout | Zastosowanie |
|---|---|
layouts/MasterLayout.vue | Wszystkie ekrany panelu, wraz z nagłówkiem i menu bocznym |
layouts/AuthLayout.vue | Logowanie i reset hasła |
layouts/BlankLayout.vue | Strona 404 oraz inne ekrany bez elementów nawigacyjnych |
Szczegóły opisują Layouty i strony.