Motyw
Architektura
Strona ta wyjaśnia trzy mechanizmy, które w kodzie działają niejawnie: pliki boot, auto-importy oraz konwencje nazewnicze.
Pliki boot
Pliki boot wykonują się przed startem aplikacji, w kolejności zadeklarowanej w quasar.config.js:
js
// quasar.config.js
boot: [
"packages/axios",
"packages/i18n",
"packages/yup",
"packages/feather-icons",
"packages/vue-query",
"app/ui",
],| Plik | Zadanie |
|---|---|
boot/packages/axios.js | Udostępnia this.$axios i this.$api dla Options API. Nie tworzy instancji — ta powstaje przy imporcie modules/core/services/http.js |
boot/packages/i18n.js | Wywołuje app.use(i18nInstance), a następnie przekazuje język ze store'a do trzech miejsc: api.setLocale(), api.setContentLocale() oraz i18n.locale.value; ustawia też pakiet językowy Quasara |
boot/packages/yup.js | Wywołuje setLocale() biblioteki yup, przez co komunikaty stają się obiektami { key, label } zamiast łańcuchów znaków |
boot/packages/feather-icons.js | Rejestruje kolekcję Feather w @iconify/vue i podpina IconSet.iconMapFn |
boot/packages/vue-query.js | Instaluje VueQueryPlugin wraz z globalnymi ustawieniami zapytań |
boot/app/ui.js | Ustawia domyślne parametry Notify, LoadingBar i Loading |
Domyślne ustawienia Vue Query
js
// src/boot/packages/vue-query.js
defaultOptions: {
queries: {
refetchOnWindowFocus: false,
retry: false,
staleTime: 1000 * 60 * 5, // 5 minut
},
}Wyłączenie ponawiania żądań i odświeżania przy powrocie do okna jest celowe: panel administracyjny ma prezentować przewidywalny stan, a o błędach informują toasty wystawiane przez interceptor.
Ikony Feather
Boot rejestruje funkcję IconSet.iconMapFn, która rozpoznaje prefiks feather_ i zamienia nazwę ikony na klasę CSS z maską SVG, wstrzykiwaną leniwie do współdzielonego arkusza stylów:
vue
<q-btn icon="feather_edit-3" />
<q-icon name="feather_trash-2" />Nieistniejąca ikona nie przerywa działania aplikacji
Zamiast niej wyświetlana jest ikona feather:help-circle, a w trybie deweloperskim w konsoli pojawia się komunikat Invalid Feather icon: "feather_xyz".
Odrębnym mechanizmem są własne ikony SVG umieszczone w src/icons/app/. Obsługuje je unplugin-icons poprzez kolekcję app, a wykorzystywane są jako komponenty:
vue
<i-app-avatar-mock />Ustawienia interfejsu
js
// src/boot/app/ui.js
Notify.setDefaults({ position: "top-right", timeout: 3500, progress: true, ... });
LoadingBar.setDefaults({ size: "2px", position: "top", color: "secondary" });
Loading.setDefaults({ spinner: QSpinnerTail, spinnerColor: "secondary" });Dzięki temu Notify.create({ message, type: "negative" }) nie wymaga w kodzie żadnej dodatkowej konfiguracji.
Auto-import composables i funkcji
Za auto-import odpowiada unplugin-auto-import, skonfigurowany w quasar.config.js:
js
// quasar.config.js
imports: [
"vue",
"vue-router",
{
"@tanstack/vue-query": [
"useMutation",
"useQuery",
"useInfiniteQuery",
"useQueryClient",
"keepPreviousData",
],
},
],
dirs: [
"src/modules/auth/composables",
"src/modules/core/composables",
"src/modules/media/composables",
"src/modules/media/services",
"src/modules/setting/composables",
"src/modules/menu/composables",
"src/modules/seo/services",
"src/modules/contentBuilder/composables",
"src/modules/contentBuilder/services",
"src/modules/page/composables",
"src/modules/page/services",
"src/modules/core/utils",
// --place where generator will inject new auto-import dirs
],Z tego powodu poniższy fragment działa bez ani jednej deklaracji importu:
js
const route = useRoute(); // vue-router
const id = computed(() => Number(route.params.id)); // vue
const { t } = useLocale(); // core/composables
const breadcrumbs = useCoreBreadcrumbs("blog.articles.edit"); // core/composables
const created = formatDate(row.created_at); // core/utilsPełna lista dostępnych nazw znajduje się w wygenerowanym pliku auto-imports.d.ts.
Composables modułu nie są auto-importowane domyślnie
Katalog src/modules/<moduł>/composables musi zostać jawnie wpisany do tablicy dirs. Generator plop module dopisuje go automatycznie, natomiast przy ręcznym tworzeniu modułu trzeba zrobić to samodzielnie. W przeciwnym razie composables wymagają importowania pełną ścieżką.
Co nie podlega auto-importowi
Poza mechanizmem pozostają pliki z modules/core/helpers/ (między innymi navigation-guards, response, object-to-formdata i validators) oraz cała warstwa api/* modułów. Importuje się je zawsze jawnie:
js
import { useArticleDetailQuery } from "modules/blog/api/queries/article.queries";Auto-rejestracja komponentów
Za rejestrację komponentów odpowiada unplugin-vue-components:
js
// quasar.config.js
dts: true,
extensions: ["vue", "js"],
resolvers: [QuasarResolver(), IconsResolver({ customCollections: ["app"] })],
dirs: [
"src/modules/auth/components",
"src/modules/core/components",
"src/modules/media/components",
// ... katalog components każdego modułu
// --place where generator will inject new components dirs
],Każdy komponent z katalogu components/ dowolnego modułu dostępny jest globalnie, a w szablonie odwołuje się do niego nazwą w konwencji kebab-case:
vue
<template>
<base-card-page :title="$t('blog.articles.title.index')" :breadcrumbs="breadcrumbs">
<blog-articles-table />
</base-card-page>
</template>Wygenerowany rejestr wszystkich komponentów znajduje się w pliku components.d.ts.
Nazwy komponentów obowiązują globalnie
Dwa moduły nie mogą zawierać komponentów o tej samej nazwie pliku. Z tego powodu obowiązuje prefiksowanie nazwą modułu, na przykład BlogArticleForm.vue oraz FaqCategoryForm.vue.
Konwencje nazewnicze
Komponenty
| Prefiks | Znaczenie | Przykład |
|---|---|---|
Base* | Prezentacyjny wrapper na komponent Quasara. Przyjmuje modelValue oraz error/errorMessage i działa również poza formularzem | BaseInput, BaseTable, BaseConfirmDialog |
V* | Wersja powiązana z vee-validate. Przyjmuje name będące ścieżką pola i samodzielnie wywołuje useField | VInput, VSelect, VContentBuilder |
The* | Singleton layoutu, występujący w jednej instancji | TheHeader, TheNavigationDrawer |
<Moduł><Model>Form | Formularz modelu | BlogArticleForm |
<Moduł><Modele>Table | Tabela modelu, w liczbie mnogiej | BlogArticlesTable |
Katalogi, trasy i klucze
| Element | Format | Przykład |
|---|---|---|
| Katalog modułu | camelCase | contentBuilder, staticText |
| Ścieżka API | kebab-case | /content-builder/shared-sections |
| Nazwa trasy | kebab-case, <moduł>.<modele>.<akcja> | blog.articles.edit |
| Klucz i18n modułu | kebab-case | static-text.static-texts.title.index |
| Plik komponentu | PascalCase | BlogArticlesTable.vue |
| Użycie w szablonie | kebab-case | <blog-articles-table /> |
Znaczenie nazwy trasy
Zapis <moduł>.<modele>.<akcja> pełni funkcję wykraczającą poza identyfikację ekranu. Na jego podstawie wyliczane są uprawnienia, breadcrumbs, trasa edycji w tabeli oraz trasa powrotu z formularza. Opisuje to Routing.
Idiom przekazywania slotów
Poniższa konstrukcja powtarza się w niemal każdym komponencie Base* oraz w generowanych tabelach:
vue
<template v-for="(_, slot) of $slots" #[slot]="scope">
<slot :name="slot" v-bind="scope || {}" />
</template>Przekazuje ona wszystkie sloty w dół, do opakowanego komponentu Quasara. W połączeniu z domyślnym przepuszczaniem atrybutów oznacza to, że BaseTable przyjmuje każdy prop i każdy slot komponentu QTable. Dlatego dokumentacja komponentów Base* opisuje wyłącznie właściwości dodatkowe — reszty szukaj w dokumentacji Quasara.
Style
Punktem wejścia jest src/css/app.scss, importujący wszystkie partiale:
src/css/
├── app.scss punkt wejścia — importuje partiale
├── _mixins.scss _layout.scss _table.scss
├── _form.scss _typography.scss _fonts.scss
├── _sortable.scss _feather-icons.scss _content-builder.scss
├── quasar.variables.scss zmienne marki Quasara (primary, secondary)
├── moon.variables.scss zmienne systemu designu (odstępy, breakpointy)
└── fonts/ krój Inter, woff2, wagi 100–900Kolory marki zmienia się w quasar.variables.scss, a odstępy i breakpointy — w moon.variables.scss.