Skip to content

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",
],
PlikZadanie
boot/packages/axios.jsUdostępnia this.$axios i this.$api dla Options API. Nie tworzy instancji — ta powstaje przy imporcie modules/core/services/http.js
boot/packages/i18n.jsWywoł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.jsWywołuje setLocale() biblioteki yup, przez co komunikaty stają się obiektami { key, label } zamiast łańcuchów znaków
boot/packages/feather-icons.jsRejestruje kolekcję Feather w @iconify/vue i podpina IconSet.iconMapFn
boot/packages/vue-query.jsInstaluje VueQueryPlugin wraz z globalnymi ustawieniami zapytań
boot/app/ui.jsUstawia 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/utils

Peł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 ​

PrefiksZnaczeniePrzykład
Base*Prezentacyjny wrapper na komponent Quasara. Przyjmuje modelValue oraz error/errorMessage i działa również poza formularzemBaseInput, BaseTable, BaseConfirmDialog
V*Wersja powiązana z vee-validate. Przyjmuje name będące ścieżką pola i samodzielnie wywołuje useFieldVInput, VSelect, VContentBuilder
The*Singleton layoutu, występujący w jednej instancjiTheHeader, TheNavigationDrawer
<Moduł><Model>FormFormularz modeluBlogArticleForm
<Moduł><Modele>TableTabela modelu, w liczbie mnogiejBlogArticlesTable

Katalogi, trasy i klucze ​

ElementFormatPrzykład
Katalog modułucamelCasecontentBuilder, staticText
Ścieżka APIkebab-case/content-builder/shared-sections
Nazwa trasykebab-case, <moduł>.<modele>.<akcja>blog.articles.edit
Klucz i18n modułukebab-casestatic-text.static-texts.title.index
Plik komponentuPascalCaseBlogArticlesTable.vue
Użycie w szabloniekebab-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–900

Kolory marki zmienia się w quasar.variables.scss, a odstępy i breakpointy — w moon.variables.scss.

Made by Moonbite