Files
vmall/docs/frontend-guidelines.md
T

68 lines
3.0 KiB
Markdown

# 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` (the `ApiClient` from
`@vmall/shared`). Never build URLs or fetch manually. Contract changes happen
in `packages/shared/src/types.ts` + `api.ts` and 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's
`PriceText` component. Never float math, never hardcode exponent 2.
- **i18n.** All copy via `$t()`. Missing keys go to the app's own
`locales-extra.ts` (mall: `locales/*.ts` modules), 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/ui`
components (`VBtn`, `VCard`, `VField`, `VInput`, `VTable`, `VPage`,
`VPanel`, `VBadge`) over raw elements.
## Page anatomy (follow it)
```vue
<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:
1. implement the methods in `apps/mall/mock/api.ts` (deterministic, mutable,
persisted via the `STORAGE_KEY` versioned localStorage state),
2. add the domain to `LiveDomain` + `LIVE_PICKS` with **exact method picks**
(a missed method silently falls back to the mock while the domain looks
live),
3. enable it in `nuxt.config.ts` `liveDomains` and 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 need `rm -rf apps/<app>/.nuxt` or Vite fails
with `#app-manifest` pre-transform errors.
- Dev servers bind IPv6 localhost only; probe `localhost`, not `127.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**.