3.0 KiB
3.0 KiB
Frontend Guidelines (mall / shop-admin / admin)
Three Nuxt 3 apps, one design system, one API contract. Ports are fixed: mall 3000, shop-admin 3001, admin 3002.
Hard rules
- Contract only. All API access goes through
$api(theApiClientfrom@vmall/shared). Never build URLs or fetch manually. Contract changes happen inpackages/shared/src/types.ts+api.tsand must keep all three apps building. - No
any/as any/@ts-ignore. Guard external data with types. - Money.
formatMoney(minor, code, exponent, locale)or the mall'sPriceTextcomponent. Never float math, never hardcode exponent 2. - i18n. All copy via
$t(). Missing keys go to the app's ownlocales-extra.ts(mall:locales/*.tsmodules), never to the shared packs from an app change. Keep en/zh key sets identical. - Layout. Tailwind v4 utilities with theme tokens (
bg-bg,text-text,border-border,bg-surface,text-primary,text-danger,text-success,text-muted). No<style scoped>. Prefer@vmall/uicomponents (VBtn,VCard,VField,VInput,VTable,VPage,VPanel,VBadge) over raw elements.
Page anatomy (follow it)
<script setup lang="ts">
definePageMeta({ middleware: "auth" }); // protected pages
const { $api } = useNuxtApp();
const loading = ref(true); // always handle loading
const errorMessage = ref(""); // visible failures, role="alert"
// load in onMounted (session lives in localStorage; SSR renders loading state)
</script>
<template>
<VPage :title="$t('nav.x')">…</VPage>
</template>
Patterns proven in the codebase: list page + inline row editing
(apps/admin/pages/currencies.vue), ordered whole-list replacement editors
(apps/admin/pages/content.vue, brands.vue), state-machine action pages with
409 surfacing (apps/shop-admin/pages/aftersales/).
The mall mock boundary
apps/mall/plugins/api.ts composes createMockApi() with per-domain live
picks. Adding a mall-facing domain means:
- implement the methods in
apps/mall/mock/api.ts(deterministic, mutable, persisted via theSTORAGE_KEYversioned localStorage state), - add the domain to
LiveDomain+LIVE_PICKSwith exact method picks (a missed method silently falls back to the mock while the domain looks live), - enable it in
nuxt.config.tsliveDomainsand in mock seeds (apps/mall/mock/data.ts) when new required model fields appear.
NUXT_PUBLIC_LIVE_DOMAINS is a JSON array env var (e.g. '["catalog"]'),
not a comma string.
Dev-server pitfalls (learned the hard way)
- After
pnpm build, dev servers needrm -rf apps/<app>/.nuxtor Vite fails with#app-manifestpre-transform errors. - Dev servers bind IPv6 localhost only; probe
localhost, not127.0.0.1. - Long-lived browser tabs across dev-server restarts lose hydration (stale Vite chunk hashes). When a page sticks on "Loading…" with no console errors, restart the dev server and open a fresh tab.