68 lines
3.0 KiB
Markdown
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**.
|