- 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
4.9 KiB
Agent contracts — mall-pc-storefront-replica
Read this whole file plus proposal.md and design-reference.md before editing.
Hard rules
apps/mallonly. NEVER editpackages/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 assignedapps/mall/locales/<domain>.tsfile (shape:export default { en: { <domain>: {...} }, zh: { <domain>: {...} } }). Extend the existing structure; both en and zh always. - Mock content fields are
LocalizedText({en, zh}); render witht as pickfrom@vmall/shared:pick(text, locale)whereconst { 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-redetc.,.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}emitschange;UiBreadcrumb{items: {label,to?}[]}(already 1200px-wide);UiQtyStepperv-model, max;UiRatingStars{value,size?};UiEmptyState{text?};UiModal{open,title?}emitsclose, 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 sharedorder.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.