- Mock adapter implementing @vmall/shared ApiClient (localStorage-persisted cart/orders/invoices), runtime switch via mockApi flag (default on) - Fixed bilingual mock catalog: 3-level categories, 24 products w/ SKUs, stores, brands, banners, floors, seckill/collective/integral, comments, coupons, addresses, seeded orders/shipments/invoices - B2B2C mall shell: top bar, logo/search/cart header, dark nav + category mega-menu, value-prop footer, back-to-top; 1200px grid, #ca151e theme - UI primitives replacing element-plus: carousel, pagination, breadcrumb, qty stepper, rating stars, modal, tabs, step bar, product card - Pages: home floors, search (filters/sort/paging), goods detail (SKU picker, store rail, review tabs), cart -> checkout -> pay -> success, auth pages, user center (dashboard/orders/addresses/favorites/coupons/ invoices), stores, seckill, collective, integral - i18n split into per-domain locale modules (en+zh) - OpenSpec change mall-pc-storefront-replica archived; all specs green
40 lines
4.9 KiB
Markdown
40 lines
4.9 KiB
Markdown
# Agent contracts — mall-pc-storefront-replica
|
||
|
||
Read this whole file plus `proposal.md` and `design-reference.md` before editing.
|
||
|
||
## Hard rules
|
||
- `apps/mall` only. NEVER edit `packages/shared`, other apps, or backend.
|
||
- TS strict: no `any` / `as any` / `@ts-ignore`. Vue 3 `<script setup lang="ts">`.
|
||
- All UI copy via `$t()` / `t()`; keys live ONLY in your assigned `apps/mall/locales/<domain>.ts` file (shape: `export default { en: { <domain>: {...} }, zh: { <domain>: {...} } }`). Extend the existing structure; both en and zh always.
|
||
- Mock content fields are `LocalizedText` (`{en, zh}`); render with `t as pick` from `@vmall/shared`: `pick(text, locale)` where `const { locale } = useI18n()`.
|
||
- Money: integer minor units. Render prices ONLY via `<PriceText :amount-minor="n" currency="USD" />` (auto-imported component). No float math, no hardcoded exponent.
|
||
- Styling: scoped CSS per component using the global tokens/utilities in `apps/mall/assets/mall.css` (`--mall-red` etc., `.w1200`, `.mbtn`, `.mpanel`, `.mtable`, `.section-bg`, `.hover-lift`). No Tailwind, no element-plus, no new deps.
|
||
- Do NOT run builds/tests/dev servers; the integrator validates.
|
||
- Do NOT create/modify files outside your assigned list. If you believe a shared file must change, note it in your final report instead.
|
||
|
||
## Mock data layer (import from `~/mock/data`; read the file for full signatures)
|
||
- Shared-contract data: `MOCK_PRODUCTS: Product[]`, `MOCK_CATEGORIES: Category[]`, `MOCK_CURRENCIES`, `MOCK_USER`.
|
||
- Helpers: `searchMockProducts({q, categoryId, brandId, shopId, sort, order, page, perPage})` → `{items,total,page,per_page}`; `productById(idOrSlug)`; `productDetail(idOrSlug)` → `{product, store, comments, commentStats, coupons, salesRank}`; `lowestSku(product)`; `salesOf(p)`; `commentCountOf(p)`; `topCategories()`; `childCategories(id)`; `categorySubtreeIds(id)`; `brandOf(productId)`; `MOCK_BRANDS`.
|
||
- Stores: `MOCK_STORES: MockStore[]`, `storeById`, `storeDetail(idOrSlug)` → `{store, products, salesRank}`.
|
||
- Home: `MOCK_BANNERS`, `MOCK_PROMOS`, `MOCK_QUICK_LINKS`, `homeFloors()`.
|
||
- Marketing: `SECKILL_SESSIONS`, `seckillProducts()` → `{product, seckillPriceMinor, soldPct}[]`, `collectiveProducts()` → `{product, need, joined}[]`, `INTEGRAL_PRODUCTS`, banner images `/mock/integral-banner.svg`, `/mock/collective-banner.svg`.
|
||
- User center: `MOCK_ADDRESSES: MockAddress[]`, `MOCK_FAVORITES: MockFavorite[]`, `USER_STATS`, `MOCK_COUPONS`.
|
||
- Images: `/mock/*.svg` (product-1..24, banner-1..3, promo-1..3, floor-adv-1..6, store-1..4, avatar).
|
||
|
||
## API adapter (via `const { $api } = useNuxtApp()`)
|
||
Full `@vmall/shared` `ApiClient`, mock-backed with in-memory state: auth (login/register accept anything → demo user), `listProducts({page, per_page, category_id, q, shop_id})`, `getProduct`, `listCategories`, `listCurrencies`, `convert`, cart CRUD, `checkout(address, currency)` → `Order[]` (one per shop, clears cart), `listMyOrders`, `getOrder`, `cancelOrder`, `payOrder`, `listMyShipments`, `confirmDelivered`, `requestInvoice`, `listMyInvoices`. shop/* and admin/* throw — never call them.
|
||
Cart badge: `useCartStore()` → `.count`, `.refresh()` after cart mutations.
|
||
|
||
## Session
|
||
`useSessionStore()`: `isLoggedIn`, `user`, `setAuth(tokens)`, `logout()`, `hydrate()` (app.vue hydrates). Route guard: `definePageMeta({ middleware: "auth" })` on pages requiring login (user center, checkout).
|
||
|
||
## Components (auto-imported)
|
||
- Shell (already placed in app.vue — pages do NOT re-add): `ShellTopBar`, `ShellSiteHeader`, `ShellSiteFooter`.
|
||
- `UiCarousel` `{images: {image,url?}[], height?, interval?}`; `UiPagination` `{page,total,perPage}` emits `change`; `UiBreadcrumb` `{items: {label,to?}[]}` (already 1200px-wide); `UiQtyStepper` `v-model, max`; `UiRatingStars` `{value,size?}`; `UiEmptyState` `{text?}`; `UiModal` `{open,title?}` emits `close`, slots default+footer; `UiTabs` `{tabs:[{key,label}], modelValue}` emits update, default slot receives `{active}`; `UiStepBar` `{steps: string[], active: number}`; `UiProductCard` `{product: Product}`; `PriceText` `{amountMinor, currency}`; `StatusBadge` `{status, kind}` (uses shared `order.status.*` etc. keys).
|
||
|
||
## Routes (fixed)
|
||
`/` home (done); `/search` (query: q, category, brand, sort, order, page); `/goods/[id]`; `/cart`; `/checkout`, `/checkout/pay`, `/checkout/success`; `/login`, `/register`, `/forgot-password`; `/stores`, `/stores/[id]`; `/seckill`, `/collective`, `/integral`; `/user` shell + `/user` (dashboard), `/user/orders`, `/user/orders/[id]`, `/user/addresses`, `/user/favorites`, `/user/coupons`, `/user/invoices`.
|
||
|
||
## Visual reference
|
||
Follow `design-reference.md` (reference digest): 1200px grid, `#ca151e` red, `#333` dark nav, hairline `#efefef` borders, 12–14px text, card hover lift, red price + struck market price, 4-step bar on transaction pages, right-floating 400px auth panel over `/mock/login-bg.svg`, user center 234px left menu + white `.mpanel` content.
|