3.2 KiB
3.2 KiB
VMall frontend conventions (READ FIRST — applies to every app agent)
Repo: /Users/chengdzhang/github/jamyun/vmall (pnpm workspace). The Rust API is COMPLETE and tested; do not touch apps/api, packages/shared, openspec, or any app other than yours.
Your app
- Nuxt 3 + pinia + @nuxtjs/i18n. Deps installed. Verify with
pnpm --filter @vmall/<your-app> buildat the end (MUST pass). Do not run dev servers, do not run other apps' builds, do not run cargo. - The API may not be running while you work — code against the contract, prove with
nuxt build(type safety + compile). NEVER mock the API.
Contract (packages/shared/src)
useNuxtApp().$apiis a typedApiClient(see packages/shared/src/api.ts for every method + request body types). Provided by plugins/api.ts (already wired, reads baseUrl from runtimeConfig public.apiBase, token from localStoragevmall.token).- Types in packages/shared/src/types.ts (User, Product, Sku, Cart, Order, Shipment, Invoice, Currency, Paged, etc.). Money = integer minor units + ISO currency code.
t(localizedText, locale)picks the display string from a JSONB {"en","zh"} map.formatMoney(amountMinor, currency, exponent, locale)formats minor units.- All UI strings via $t with keys from @vmall/shared/locales (en + zh exist). If you need a key that doesn't exist, add it to YOUR app's locales-extra.ts (enExtra/zhExtra, same nested shape) — NEVER edit packages/shared.
- Shared stylesheet
@vmall/shared/ui.cssis loaded: use its classes (.card, .btn .primary .sm .danger, .table, .badge .green/.blue/.orange/.red, .field, .grid.products, .product-card, .page-title, .page-head, .form-narrow, .muted, .row, .between, .mt, .mb, .error-text). Add app-scoped CSS only in a <style> block when needed.
Session & guards
- stores/session.ts:
useSessionStore()— hydrate() on mounted (app.vue already does), setAuth({token,user}), logout(), getters.isLoggedIn, state.user (role, display_name). - Route guard convention: create
middleware/auth.tswith defineNuxtRouteMiddleware that (client-side) reads localStoragevmall.token+vmall.user(JSON) and redirects to /login when absent or when user.role is not in the app's allowed roles. Apply withdefinePageMeta({ middleware: "auth" })on every protected page. The /login page itself is public. - Login page pattern: form →
$api.login(email, password)→ on success checkuser.roleagainst the app's allowed roles (wrong role → show error, do not setAuth) →session.setAuth(...)→ navigateTo("/"). Show API error message on failure.
Behavior rules
- All pages render in BOTH en and zh — switcher already in app.vue; never hardcode user-facing strings.
- All list pages handle empty state ($t('common.empty')) and API errors (show err.message in .error-text).
- After every mutation, re-fetch the affected list/detail from the API (no local-only state faking).
- Dates:
new Date(x).toLocaleString(locale === 'zh' ? 'zh-CN' : 'en-US'). - Money inputs: enter major units (e.g. 12.99), convert to minor via exponent (SKU currencies are exp-2 except JPY exp-0; SKU editor may restrict currency to USD/CNY/EUR which are all exp-2).
- Status badges: map statuses to .badge colors (green=positive terminal, blue=active/in-progress, orange=pending, red=cancelled/rejected).