feat(mall): read the store directory from the API
Wave 5: the store directory, store home and product-page store card stop reading
MOCK_STORES, and the payment and order surfaces name their shop.
- a `shop_profiles` table beside `shops`, so the identity model both consoles
consume is untouched, with a public `GET /api/shops` and `GET /api/shops/{slug}`
and an admin `PUT /api/admin/shops/{id}/profile`
- a shop with no profile is still listed, with the fields absent rather than
invented; the pages guard every block, and a missing logo renders an
initial-letter placeholder
- `scripts/seed-demo.mjs` upserts a profile per demo shop, since profiles hang
off shops that script creates
- payment and order pages resolve shop ids to names from one cached shop read,
retiring the generic "Shop" label
- three things went rather than being faked, following the wave-1 precedent:
`distanceKm` and its sort (no geo model), the store home's sales/comments
sorts, and its "best sellers" rail (no sales model)
- `lowestSku` moved out of the fixed-data module into `apps/mall/utils/product.ts`
and re-exported, so live pages stop importing the mock module for a pure
helper
Verified: 28 backend tests green including five new shop tests; all three
frontends build; the directory, store home, store card and order cards all render
real data with no distance or sales claims; the fixed-data rollback still renders
the store surfaces with the backend stopped.
Note: `nuxt build` does not typecheck in this repo (no `typescript.typeCheck`,
no `vue-tsc`), which AGENTS.md implies it does. A re-export used here created no
local binding and broke internal callers at runtime while the build stayed green;
`docs/TBD-migrate-wave.md` records the gap.
OpenSpec change: openspec/changes/replace-mock-api-wave-5
This commit is contained in:
+60
-1
@@ -42,27 +42,79 @@ let r = await call("POST", "/auth/login", {
|
||||
if (r.status !== 200) fail("admin login", r);
|
||||
const admin = r.data.token;
|
||||
|
||||
// 2. shops with their own owner accounts
|
||||
// 2. shops with their own owner accounts and public profiles
|
||||
const SHOPS = [
|
||||
{
|
||||
slug: "demo-store",
|
||||
name: { en: "Demo Store", zh: "演示店铺" },
|
||||
owner: { email: "shop@vmall.local", password: "shop12345", displayName: "Demo Owner" },
|
||||
profile: {
|
||||
logo: "/mock/store-1.svg",
|
||||
banner: "/mock/banner-1.svg",
|
||||
company: "Demo Commerce Co., Ltd.",
|
||||
region: "California",
|
||||
address: { en: "1 Market Street, San Francisco", zh: "旧金山市场街 1 号" },
|
||||
notice: { en: "Free shipping on orders over $99.", zh: "满 99 美元免运费。" },
|
||||
after_sale: { en: "7-day returns, 1-year warranty.", zh: "支持 7 天退货,一年质保。" },
|
||||
score_rating: 4.9,
|
||||
score_agreement: 4.8,
|
||||
score_service: 4.9,
|
||||
score_speed: 4.7,
|
||||
},
|
||||
},
|
||||
{
|
||||
slug: "aurora-digital",
|
||||
name: { en: "Aurora Digital", zh: "极光数码" },
|
||||
owner: { email: "aurora@vmall.local", password: "shop12345", displayName: "Aurora Owner" },
|
||||
profile: {
|
||||
logo: "/mock/store-2.svg",
|
||||
banner: "/mock/banner-2.svg",
|
||||
company: "Aurora Technology Co., Ltd.",
|
||||
region: "New York",
|
||||
address: { en: "88 Hudson Yards, New York", zh: "纽约哈德逊城市广场 88 号" },
|
||||
notice: { en: "Same-day dispatch before 3 PM.", zh: "下午 3 点前下单当天发货。" },
|
||||
after_sale: { en: "Nationwide warranty, support 9:00-21:00.", zh: "全国联保,客服 9:00-21:00。" },
|
||||
score_rating: 4.7,
|
||||
score_agreement: 4.6,
|
||||
score_service: 4.8,
|
||||
score_speed: 4.6,
|
||||
},
|
||||
},
|
||||
{
|
||||
slug: "nordwind-home",
|
||||
name: { en: "Nordwind Home", zh: "北风家居" },
|
||||
owner: { email: "nordwind@vmall.local", password: "shop12345", displayName: "Nordwind Owner" },
|
||||
profile: {
|
||||
logo: "/mock/store-3.svg",
|
||||
banner: "/mock/banner-3.svg",
|
||||
company: "Nordwind Trading LLC",
|
||||
region: "Texas",
|
||||
address: { en: "1200 Congress Avenue, Austin", zh: "奥斯汀国会大道 1200 号" },
|
||||
notice: { en: "Free installation on large appliances.", zh: "大家电免费上门安装。" },
|
||||
after_sale: { en: "30-day returns on unopened goods.", zh: "未拆封商品 30 天可退。" },
|
||||
score_rating: 4.8,
|
||||
score_agreement: 4.7,
|
||||
score_service: 4.6,
|
||||
score_speed: 4.8,
|
||||
},
|
||||
},
|
||||
{
|
||||
slug: "terra-grocery",
|
||||
name: { en: "Terra Grocery", zh: "大地生鲜" },
|
||||
owner: { email: "terra@vmall.local", password: "shop12345", displayName: "Terra Owner" },
|
||||
profile: {
|
||||
logo: "/mock/store-4.svg",
|
||||
banner: "/mock/promo-1.svg",
|
||||
company: "Terra Foods Inc.",
|
||||
region: "Oregon",
|
||||
address: { en: "45 Willamette Loop, Portland", zh: "波特兰威拉米特环路 45 号" },
|
||||
notice: { en: "Cold-chain delivery, next-day slots.", zh: "全程冷链,次日达时段可选。" },
|
||||
after_sale: { en: "Fresh items refunded on delivery issues.", zh: "生鲜配送问题可直接退款。" },
|
||||
score_rating: 4.6,
|
||||
score_agreement: 4.5,
|
||||
score_service: 4.7,
|
||||
score_speed: 4.5,
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
@@ -79,6 +131,13 @@ async function ensureShop(def) {
|
||||
shopId = shops.data.find((s) => s.slug === def.slug).id;
|
||||
} else fail(`create shop ${def.slug}`, r);
|
||||
|
||||
// Upsert, so re-running the seed refreshes the profile rather than failing.
|
||||
r = await call("PUT", `/admin/shops/${shopId}/profile`, {
|
||||
token: admin,
|
||||
body: def.profile,
|
||||
});
|
||||
if (r.status !== 200) fail(`shop profile ${def.slug}`, r);
|
||||
|
||||
await call("POST", "/auth/register", {
|
||||
body: {
|
||||
email: def.owner.email,
|
||||
|
||||
Reference in New Issue
Block a user