Files
vmall/openspec/changes/archive/2026-09-17-mall-pc-storefront-replica/agent-contracts.md
T
Chengdong Zhang 21e99bb52b feat(mall): classic B2B2C PC storefront with fixed mock API layer
- 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
2026-09-17 19:13:29 +08:00

4.9 KiB
Raw Blame History

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, 1214px 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.