wip(freight): migration 0017 + model/column-list groundwork for add-freight-templates
This commit is contained in:
+489
-46
@@ -97,10 +97,42 @@ export interface IntegralProduct {
|
||||
// ---------- currencies ----------
|
||||
|
||||
export const MOCK_CURRENCIES = [
|
||||
{ code: "USD", name: L("US Dollar", "美元"), symbol: "$", exponent: 2, is_base: true, rate_to_base: "1", enabled: true },
|
||||
{ code: "CNY", name: L("Chinese Yuan", "人民币"), symbol: "¥", exponent: 2, is_base: false, rate_to_base: "0.14", enabled: true },
|
||||
{ code: "JPY", name: L("Japanese Yen", "日元"), symbol: "¥", exponent: 0, is_base: false, rate_to_base: "0.0067", enabled: true },
|
||||
{ code: "EUR", name: L("Euro", "欧元"), symbol: "€", exponent: 2, is_base: false, rate_to_base: "1.09", enabled: true },
|
||||
{
|
||||
code: "USD",
|
||||
name: L("US Dollar", "美元"),
|
||||
symbol: "$",
|
||||
exponent: 2,
|
||||
is_base: true,
|
||||
rate_to_base: "1",
|
||||
enabled: true,
|
||||
},
|
||||
{
|
||||
code: "CNY",
|
||||
name: L("Chinese Yuan", "人民币"),
|
||||
symbol: "¥",
|
||||
exponent: 2,
|
||||
is_base: false,
|
||||
rate_to_base: "0.14",
|
||||
enabled: true,
|
||||
},
|
||||
{
|
||||
code: "JPY",
|
||||
name: L("Japanese Yen", "日元"),
|
||||
symbol: "¥",
|
||||
exponent: 0,
|
||||
is_base: false,
|
||||
rate_to_base: "0.0067",
|
||||
enabled: true,
|
||||
},
|
||||
{
|
||||
code: "EUR",
|
||||
name: L("Euro", "欧元"),
|
||||
symbol: "€",
|
||||
exponent: 2,
|
||||
is_base: false,
|
||||
rate_to_base: "1.09",
|
||||
enabled: true,
|
||||
},
|
||||
];
|
||||
|
||||
// units of currency per 1 USD, scaled by 1e4 (integer math only)
|
||||
@@ -331,7 +363,10 @@ export const MOCK_STORES: MockStore[] = [
|
||||
distanceKm: 3.2,
|
||||
rate: { score: 4.9, agree: 4.8, service: 4.9, speed: 4.7 },
|
||||
notice: L("Free shipping on orders over $99.", "满 99 美元免运费。"),
|
||||
afterSale: L("7-day returns, 15-day exchanges, 1-year warranty.", "支持 7 天退货、15 天换货、一年质保。"),
|
||||
afterSale: L(
|
||||
"7-day returns, 15-day exchanges, 1-year warranty.",
|
||||
"支持 7 天退货、15 天换货、一年质保。",
|
||||
),
|
||||
},
|
||||
{
|
||||
id: "s2",
|
||||
@@ -345,7 +380,10 @@ export const MOCK_STORES: MockStore[] = [
|
||||
distanceKm: 12.5,
|
||||
rate: { score: 4.7, agree: 4.6, service: 4.8, speed: 4.6 },
|
||||
notice: L("Same-day dispatch before 3 PM.", "下午 3 点前下单当天发货。"),
|
||||
afterSale: L("Nationwide warranty, online support 9:00-21:00.", "全国联保,在线客服 9:00-21:00。"),
|
||||
afterSale: L(
|
||||
"Nationwide warranty, online support 9:00-21:00.",
|
||||
"全国联保,在线客服 9:00-21:00。",
|
||||
),
|
||||
},
|
||||
{
|
||||
id: "s3",
|
||||
@@ -394,30 +432,333 @@ interface ProductSpec {
|
||||
}
|
||||
|
||||
const PRODUCT_SPECS: ProductSpec[] = [
|
||||
{ n: 1, slug: "aurora-x90-pro", cat: "c1-1-1", store: "s1", brand: "b1", name: L("Aurora X90 Pro 5G", "Aurora X90 Pro 5G 手机"), sub: L("Flagship imaging, 120Hz OLED", "旗舰影像,120Hz OLED 屏"), price: 89900, market: 99900, stock: 128, attrs: [{ key: "Color", values: ["Black", "Silver"] }, { key: "Storage", values: ["256GB", "512GB"] }] },
|
||||
{ n: 2, slug: "aurora-lite-5g", cat: "c1-1-2", store: "s1", brand: "b1", name: L("Aurora Lite 5G", "Aurora Lite 5G 手机"), sub: L("All-day battery, great value", "长续航,高性价比"), price: 29900, market: 34900, stock: 356, attrs: [{ key: "Color", values: ["Blue", "Green"] }] },
|
||||
{ n: 3, slug: "hexon-buds-air", cat: "c1-2-1", store: "s2", brand: "b3", name: L("Hexon Buds Air", "Hexon Buds Air 真无线耳机"), sub: L("ANC, 36h total playback", "主动降噪,36 小时总续航"), price: 7900, market: 9900, stock: 842, attrs: [{ key: "Color", values: ["White", "Black"] }] },
|
||||
{ n: 4, slug: "hexon-boombox-mini", cat: "c1-2-2", store: "s2", brand: "b3", name: L("Hexon Boombox Mini", "Hexon Boombox Mini 蓝牙音箱"), sub: L("IPX7 waterproof, 20h playtime", "IPX7 防水,20 小时播放"), price: 5900, market: 7900, stock: 410, attrs: [{ key: "Color", values: ["Gray", "Red"] }] },
|
||||
{ n: 5, slug: "nordwind-air-14", cat: "c2-1-1", store: "s2", brand: "b2", name: L("Nordwind Air 14 Laptop", "Nordwind Air 14 轻薄本"), sub: L("1.19kg, 2.8K display, 16GB RAM", "1.19kg,2.8K 屏,16GB 内存"), price: 109900, market: 124900, stock: 96, attrs: [{ key: "Color", values: ["Silver", "Gray"] }, { key: "Memory", values: ["16GB", "32GB"] }] },
|
||||
{ n: 6, slug: "hexon-storm-16", cat: "c2-1-2", store: "s2", brand: "b3", name: L("Hexon Storm 16 Gaming Laptop", "Hexon Storm 16 游戏本"), sub: L("RTX graphics, 240Hz screen", "RTX 显卡,240Hz 电竞屏"), price: 189900, market: 209900, stock: 42, attrs: [{ key: "Color", values: ["Black"] }, { key: "Memory", values: ["16GB", "32GB"] }] },
|
||||
{ n: 7, slug: "nordwind-keys-pro", cat: "c2-2-1", store: "s2", brand: "b2", name: L("Nordwind Keys Pro", "Nordwind Keys Pro 机械键盘"), sub: L("Hot-swap, tri-mode wireless", "热插拔,三模无线"), price: 8900, market: 10900, stock: 520, attrs: [{ key: "Switch", values: ["Linear", "Tactile"] }] },
|
||||
{ n: 8, slug: "nordwind-view-27", cat: "c2-2-2", store: "s2", brand: "b2", name: L("Nordwind View 27 Monitor", "Nordwind View 27 显示器"), sub: L("4K IPS, 99% sRGB", "4K IPS,99% sRGB 色域"), price: 32900, market: 39900, stock: 150, attrs: [{ key: "Size", values: ["27in", "32in"] }] },
|
||||
{ n: 9, slug: "solace-rice-chef", cat: "c3-1-1", store: "s3", brand: "b5", name: L("Solace Rice Chef 4L", "Solace 智能电饭煲 4L"), sub: L("IH heating, 24h timer", "IH 电磁加热,24 小时预约"), price: 12900, market: 15900, stock: 230, attrs: [{ key: "Color", values: ["White"] }] },
|
||||
{ n: 10, slug: "solace-blend-max", cat: "c3-1-2", store: "s3", brand: "b5", name: L("Solace Blend Max", "Solace 破壁机"), sub: L("Quiet motor, self-cleaning", "静音电机,一键自清洗"), price: 14900, market: 18900, stock: 178, attrs: [{ key: "Color", values: ["Cream", "Green"] }] },
|
||||
{ n: 11, slug: "solace-vac-v12", cat: "c3-2-1", store: "s3", brand: "b5", name: L("Solace Vac V12", "Solace V12 无线吸尘器"), sub: L("60min runtime, laser dust detect", "60 分钟续航,激光显尘"), price: 39900, market: 45900, stock: 88, attrs: [{ key: "Color", values: ["Gray"] }] },
|
||||
{ n: 12, slug: "solace-pure-500", cat: "c3-2-2", store: "s3", brand: "b5", name: L("Solace Pure 500 Air Purifier", "Solace Pure 500 空气净化器"), sub: L("HEPA-13, 60m² coverage", "HEPA-13 滤芯,适用 60㎡"), price: 24900, market: 29900, stock: 140, attrs: [{ key: "Color", values: ["White"] }] },
|
||||
{ n: 13, slug: "mikado-field-jacket", cat: "c4-1-1", store: "s1", brand: "b4", name: L("Mikado Field Jacket", "Mikado 工装夹克"), sub: L("Water-repellent, 4 pockets", "防泼水面料,四袋设计"), price: 11900, market: 14900, stock: 310, attrs: [{ key: "Color", values: ["Olive", "Black"] }, { key: "Size", values: ["M", "L", "XL"] }] },
|
||||
{ n: 14, slug: "mikado-oxford-shirt", cat: "c4-1-2", store: "s1", brand: "b4", name: L("Mikado Oxford Shirt", "Mikado 牛津纺衬衫"), sub: L("Wrinkle-free cotton", "免烫纯棉"), price: 4900, market: 6900, stock: 460, attrs: [{ key: "Color", values: ["White", "Blue"] }, { key: "Size", values: ["M", "L", "XL"] }] },
|
||||
{ n: 15, slug: "mikado-commuter-pack", cat: "c4-2-1", store: "s1", brand: "b4", name: L("Mikado Commuter Pack 20L", "Mikado 通勤双肩包 20L"), sub: L("Fits 16in laptop, USB port", "可装 16 英寸电脑,外置 USB 口"), price: 6900, market: 8900, stock: 275, attrs: [{ key: "Color", values: ["Black", "Navy"] }] },
|
||||
{ n: 16, slug: "mikado-voyage-24", cat: "c4-2-2", store: "s1", brand: "b4", name: L("Mikado Voyage 24 Luggage", "Mikado Voyage 24 英寸旅行箱"), sub: L("PC shell, silent wheels", "PC 箱体,静音万向轮"), price: 13900, market: 16900, stock: 132, attrs: [{ key: "Color", values: ["Silver", "Black"] }] },
|
||||
{ n: 17, slug: "solace-glow-serum", cat: "c5-1-1", store: "s3", brand: "b5", name: L("Solace Glow Serum 30ml", "Solace 焕亮精华 30ml"), sub: L("Vitamin C + E, daily glow", "维 C + 维 E,日常焕亮"), price: 3900, market: 4900, stock: 640, attrs: [{ key: "Size", values: ["30ml", "50ml"] }] },
|
||||
{ n: 18, slug: "solace-hydra-mask", cat: "c5-1-2", store: "s3", brand: "b5", name: L("Solace Hydra Mask 10pcs", "Solace 补水面膜 10 片装"), sub: L("Hyaluronic acid boost", "玻尿酸深层补水"), price: 1900, market: 2900, stock: 900, attrs: [{ key: "Type", values: ["Hydrating", "Soothing"] }] },
|
||||
{ n: 19, slug: "hexon-shave-s9", cat: "c5-2-1", store: "s2", brand: "b3", name: L("Hexon Shave S9", "Hexon S9 电动剃须刀"), sub: L("3-head flex, wet & dry", "三刀头浮动,干湿两用"), price: 9900, market: 12900, stock: 205, attrs: [{ key: "Color", values: ["Black"] }] },
|
||||
{ n: 20, slug: "hexon-brush-t5", cat: "c5-2-2", store: "s2", brand: "b3", name: L("Hexon Brush T5", "Hexon T5 电动牙刷"), sub: L("Sonic, 90-day battery", "声波震动,90 天续航"), price: 4900, market: 6900, stock: 380, attrs: [{ key: "Color", values: ["White", "Pink"] }] },
|
||||
{ n: 21, slug: "terra-mixed-nuts", cat: "c6-1-1", store: "s4", brand: "b6", name: L("Terra Mixed Nuts 1kg", "Terra 混合坚果 1kg"), sub: L("6 kinds, no added salt", "6 种坚果,无添加盐"), price: 2900, market: 3900, stock: 720, attrs: [{ key: "Pack", values: ["1kg", "500g"] }] },
|
||||
{ n: 22, slug: "terra-butter-cookies", cat: "c6-1-2", store: "s4", brand: "b6", name: L("Terra Butter Cookies 908g", "Terra 黄油曲奇 908g"), sub: L("Danish recipe, gift tin", "丹麦配方,礼盒铁罐装"), price: 2400, market: 3200, stock: 540, attrs: [{ key: "Pack", values: ["908g"] }] },
|
||||
{ n: 23, slug: "terra-fuji-apples", cat: "c6-2-1", store: "s4", brand: "b6", name: L("Terra Fuji Apples 5kg", "Terra 红富士苹果 5kg"), sub: L("Crisp & sweet, farm direct", "脆甜多汁,产地直发"), price: 1900, market: 2600, stock: 460, attrs: [{ key: "Size", values: ["Medium", "Large"] }] },
|
||||
{ n: 24, slug: "terra-salmon-fillet", cat: "c6-2-2", store: "s4", brand: "b6", name: L("Terra Salmon Fillet 500g", "Terra 三文鱼中段 500g"), sub: L("Cold-chain, sashimi grade", "冷链直达,刺身级"), price: 3400, market: 4200, stock: 190, attrs: [{ key: "Cut", values: ["Fillet", "Steak"] }] },
|
||||
{
|
||||
n: 1,
|
||||
slug: "aurora-x90-pro",
|
||||
cat: "c1-1-1",
|
||||
store: "s1",
|
||||
brand: "b1",
|
||||
name: L("Aurora X90 Pro 5G", "Aurora X90 Pro 5G 手机"),
|
||||
sub: L("Flagship imaging, 120Hz OLED", "旗舰影像,120Hz OLED 屏"),
|
||||
price: 89900,
|
||||
market: 99900,
|
||||
stock: 128,
|
||||
attrs: [
|
||||
{ key: "Color", values: ["Black", "Silver"] },
|
||||
{ key: "Storage", values: ["256GB", "512GB"] },
|
||||
],
|
||||
},
|
||||
{
|
||||
n: 2,
|
||||
slug: "aurora-lite-5g",
|
||||
cat: "c1-1-2",
|
||||
store: "s1",
|
||||
brand: "b1",
|
||||
name: L("Aurora Lite 5G", "Aurora Lite 5G 手机"),
|
||||
sub: L("All-day battery, great value", "长续航,高性价比"),
|
||||
price: 29900,
|
||||
market: 34900,
|
||||
stock: 356,
|
||||
attrs: [{ key: "Color", values: ["Blue", "Green"] }],
|
||||
},
|
||||
{
|
||||
n: 3,
|
||||
slug: "hexon-buds-air",
|
||||
cat: "c1-2-1",
|
||||
store: "s2",
|
||||
brand: "b3",
|
||||
name: L("Hexon Buds Air", "Hexon Buds Air 真无线耳机"),
|
||||
sub: L("ANC, 36h total playback", "主动降噪,36 小时总续航"),
|
||||
price: 7900,
|
||||
market: 9900,
|
||||
stock: 842,
|
||||
attrs: [{ key: "Color", values: ["White", "Black"] }],
|
||||
},
|
||||
{
|
||||
n: 4,
|
||||
slug: "hexon-boombox-mini",
|
||||
cat: "c1-2-2",
|
||||
store: "s2",
|
||||
brand: "b3",
|
||||
name: L("Hexon Boombox Mini", "Hexon Boombox Mini 蓝牙音箱"),
|
||||
sub: L("IPX7 waterproof, 20h playtime", "IPX7 防水,20 小时播放"),
|
||||
price: 5900,
|
||||
market: 7900,
|
||||
stock: 410,
|
||||
attrs: [{ key: "Color", values: ["Gray", "Red"] }],
|
||||
},
|
||||
{
|
||||
n: 5,
|
||||
slug: "nordwind-air-14",
|
||||
cat: "c2-1-1",
|
||||
store: "s2",
|
||||
brand: "b2",
|
||||
name: L("Nordwind Air 14 Laptop", "Nordwind Air 14 轻薄本"),
|
||||
sub: L("1.19kg, 2.8K display, 16GB RAM", "1.19kg,2.8K 屏,16GB 内存"),
|
||||
price: 109900,
|
||||
market: 124900,
|
||||
stock: 96,
|
||||
attrs: [
|
||||
{ key: "Color", values: ["Silver", "Gray"] },
|
||||
{ key: "Memory", values: ["16GB", "32GB"] },
|
||||
],
|
||||
},
|
||||
{
|
||||
n: 6,
|
||||
slug: "hexon-storm-16",
|
||||
cat: "c2-1-2",
|
||||
store: "s2",
|
||||
brand: "b3",
|
||||
name: L("Hexon Storm 16 Gaming Laptop", "Hexon Storm 16 游戏本"),
|
||||
sub: L("RTX graphics, 240Hz screen", "RTX 显卡,240Hz 电竞屏"),
|
||||
price: 189900,
|
||||
market: 209900,
|
||||
stock: 42,
|
||||
attrs: [
|
||||
{ key: "Color", values: ["Black"] },
|
||||
{ key: "Memory", values: ["16GB", "32GB"] },
|
||||
],
|
||||
},
|
||||
{
|
||||
n: 7,
|
||||
slug: "nordwind-keys-pro",
|
||||
cat: "c2-2-1",
|
||||
store: "s2",
|
||||
brand: "b2",
|
||||
name: L("Nordwind Keys Pro", "Nordwind Keys Pro 机械键盘"),
|
||||
sub: L("Hot-swap, tri-mode wireless", "热插拔,三模无线"),
|
||||
price: 8900,
|
||||
market: 10900,
|
||||
stock: 520,
|
||||
attrs: [{ key: "Switch", values: ["Linear", "Tactile"] }],
|
||||
},
|
||||
{
|
||||
n: 8,
|
||||
slug: "nordwind-view-27",
|
||||
cat: "c2-2-2",
|
||||
store: "s2",
|
||||
brand: "b2",
|
||||
name: L("Nordwind View 27 Monitor", "Nordwind View 27 显示器"),
|
||||
sub: L("4K IPS, 99% sRGB", "4K IPS,99% sRGB 色域"),
|
||||
price: 32900,
|
||||
market: 39900,
|
||||
stock: 150,
|
||||
attrs: [{ key: "Size", values: ["27in", "32in"] }],
|
||||
},
|
||||
{
|
||||
n: 9,
|
||||
slug: "solace-rice-chef",
|
||||
cat: "c3-1-1",
|
||||
store: "s3",
|
||||
brand: "b5",
|
||||
name: L("Solace Rice Chef 4L", "Solace 智能电饭煲 4L"),
|
||||
sub: L("IH heating, 24h timer", "IH 电磁加热,24 小时预约"),
|
||||
price: 12900,
|
||||
market: 15900,
|
||||
stock: 230,
|
||||
attrs: [{ key: "Color", values: ["White"] }],
|
||||
},
|
||||
{
|
||||
n: 10,
|
||||
slug: "solace-blend-max",
|
||||
cat: "c3-1-2",
|
||||
store: "s3",
|
||||
brand: "b5",
|
||||
name: L("Solace Blend Max", "Solace 破壁机"),
|
||||
sub: L("Quiet motor, self-cleaning", "静音电机,一键自清洗"),
|
||||
price: 14900,
|
||||
market: 18900,
|
||||
stock: 178,
|
||||
attrs: [{ key: "Color", values: ["Cream", "Green"] }],
|
||||
},
|
||||
{
|
||||
n: 11,
|
||||
slug: "solace-vac-v12",
|
||||
cat: "c3-2-1",
|
||||
store: "s3",
|
||||
brand: "b5",
|
||||
name: L("Solace Vac V12", "Solace V12 无线吸尘器"),
|
||||
sub: L("60min runtime, laser dust detect", "60 分钟续航,激光显尘"),
|
||||
price: 39900,
|
||||
market: 45900,
|
||||
stock: 88,
|
||||
attrs: [{ key: "Color", values: ["Gray"] }],
|
||||
},
|
||||
{
|
||||
n: 12,
|
||||
slug: "solace-pure-500",
|
||||
cat: "c3-2-2",
|
||||
store: "s3",
|
||||
brand: "b5",
|
||||
name: L("Solace Pure 500 Air Purifier", "Solace Pure 500 空气净化器"),
|
||||
sub: L("HEPA-13, 60m² coverage", "HEPA-13 滤芯,适用 60㎡"),
|
||||
price: 24900,
|
||||
market: 29900,
|
||||
stock: 140,
|
||||
attrs: [{ key: "Color", values: ["White"] }],
|
||||
},
|
||||
{
|
||||
n: 13,
|
||||
slug: "mikado-field-jacket",
|
||||
cat: "c4-1-1",
|
||||
store: "s1",
|
||||
brand: "b4",
|
||||
name: L("Mikado Field Jacket", "Mikado 工装夹克"),
|
||||
sub: L("Water-repellent, 4 pockets", "防泼水面料,四袋设计"),
|
||||
price: 11900,
|
||||
market: 14900,
|
||||
stock: 310,
|
||||
attrs: [
|
||||
{ key: "Color", values: ["Olive", "Black"] },
|
||||
{ key: "Size", values: ["M", "L", "XL"] },
|
||||
],
|
||||
},
|
||||
{
|
||||
n: 14,
|
||||
slug: "mikado-oxford-shirt",
|
||||
cat: "c4-1-2",
|
||||
store: "s1",
|
||||
brand: "b4",
|
||||
name: L("Mikado Oxford Shirt", "Mikado 牛津纺衬衫"),
|
||||
sub: L("Wrinkle-free cotton", "免烫纯棉"),
|
||||
price: 4900,
|
||||
market: 6900,
|
||||
stock: 460,
|
||||
attrs: [
|
||||
{ key: "Color", values: ["White", "Blue"] },
|
||||
{ key: "Size", values: ["M", "L", "XL"] },
|
||||
],
|
||||
},
|
||||
{
|
||||
n: 15,
|
||||
slug: "mikado-commuter-pack",
|
||||
cat: "c4-2-1",
|
||||
store: "s1",
|
||||
brand: "b4",
|
||||
name: L("Mikado Commuter Pack 20L", "Mikado 通勤双肩包 20L"),
|
||||
sub: L("Fits 16in laptop, USB port", "可装 16 英寸电脑,外置 USB 口"),
|
||||
price: 6900,
|
||||
market: 8900,
|
||||
stock: 275,
|
||||
attrs: [{ key: "Color", values: ["Black", "Navy"] }],
|
||||
},
|
||||
{
|
||||
n: 16,
|
||||
slug: "mikado-voyage-24",
|
||||
cat: "c4-2-2",
|
||||
store: "s1",
|
||||
brand: "b4",
|
||||
name: L("Mikado Voyage 24 Luggage", "Mikado Voyage 24 英寸旅行箱"),
|
||||
sub: L("PC shell, silent wheels", "PC 箱体,静音万向轮"),
|
||||
price: 13900,
|
||||
market: 16900,
|
||||
stock: 132,
|
||||
attrs: [{ key: "Color", values: ["Silver", "Black"] }],
|
||||
},
|
||||
{
|
||||
n: 17,
|
||||
slug: "solace-glow-serum",
|
||||
cat: "c5-1-1",
|
||||
store: "s3",
|
||||
brand: "b5",
|
||||
name: L("Solace Glow Serum 30ml", "Solace 焕亮精华 30ml"),
|
||||
sub: L("Vitamin C + E, daily glow", "维 C + 维 E,日常焕亮"),
|
||||
price: 3900,
|
||||
market: 4900,
|
||||
stock: 640,
|
||||
attrs: [{ key: "Size", values: ["30ml", "50ml"] }],
|
||||
},
|
||||
{
|
||||
n: 18,
|
||||
slug: "solace-hydra-mask",
|
||||
cat: "c5-1-2",
|
||||
store: "s3",
|
||||
brand: "b5",
|
||||
name: L("Solace Hydra Mask 10pcs", "Solace 补水面膜 10 片装"),
|
||||
sub: L("Hyaluronic acid boost", "玻尿酸深层补水"),
|
||||
price: 1900,
|
||||
market: 2900,
|
||||
stock: 900,
|
||||
attrs: [{ key: "Type", values: ["Hydrating", "Soothing"] }],
|
||||
},
|
||||
{
|
||||
n: 19,
|
||||
slug: "hexon-shave-s9",
|
||||
cat: "c5-2-1",
|
||||
store: "s2",
|
||||
brand: "b3",
|
||||
name: L("Hexon Shave S9", "Hexon S9 电动剃须刀"),
|
||||
sub: L("3-head flex, wet & dry", "三刀头浮动,干湿两用"),
|
||||
price: 9900,
|
||||
market: 12900,
|
||||
stock: 205,
|
||||
attrs: [{ key: "Color", values: ["Black"] }],
|
||||
},
|
||||
{
|
||||
n: 20,
|
||||
slug: "hexon-brush-t5",
|
||||
cat: "c5-2-2",
|
||||
store: "s2",
|
||||
brand: "b3",
|
||||
name: L("Hexon Brush T5", "Hexon T5 电动牙刷"),
|
||||
sub: L("Sonic, 90-day battery", "声波震动,90 天续航"),
|
||||
price: 4900,
|
||||
market: 6900,
|
||||
stock: 380,
|
||||
attrs: [{ key: "Color", values: ["White", "Pink"] }],
|
||||
},
|
||||
{
|
||||
n: 21,
|
||||
slug: "terra-mixed-nuts",
|
||||
cat: "c6-1-1",
|
||||
store: "s4",
|
||||
brand: "b6",
|
||||
name: L("Terra Mixed Nuts 1kg", "Terra 混合坚果 1kg"),
|
||||
sub: L("6 kinds, no added salt", "6 种坚果,无添加盐"),
|
||||
price: 2900,
|
||||
market: 3900,
|
||||
stock: 720,
|
||||
attrs: [{ key: "Pack", values: ["1kg", "500g"] }],
|
||||
},
|
||||
{
|
||||
n: 22,
|
||||
slug: "terra-butter-cookies",
|
||||
cat: "c6-1-2",
|
||||
store: "s4",
|
||||
brand: "b6",
|
||||
name: L("Terra Butter Cookies 908g", "Terra 黄油曲奇 908g"),
|
||||
sub: L("Danish recipe, gift tin", "丹麦配方,礼盒铁罐装"),
|
||||
price: 2400,
|
||||
market: 3200,
|
||||
stock: 540,
|
||||
attrs: [{ key: "Pack", values: ["908g"] }],
|
||||
},
|
||||
{
|
||||
n: 23,
|
||||
slug: "terra-fuji-apples",
|
||||
cat: "c6-2-1",
|
||||
store: "s4",
|
||||
brand: "b6",
|
||||
name: L("Terra Fuji Apples 5kg", "Terra 红富士苹果 5kg"),
|
||||
sub: L("Crisp & sweet, farm direct", "脆甜多汁,产地直发"),
|
||||
price: 1900,
|
||||
market: 2600,
|
||||
stock: 460,
|
||||
attrs: [{ key: "Size", values: ["Medium", "Large"] }],
|
||||
},
|
||||
{
|
||||
n: 24,
|
||||
slug: "terra-salmon-fillet",
|
||||
cat: "c6-2-2",
|
||||
store: "s4",
|
||||
brand: "b6",
|
||||
name: L("Terra Salmon Fillet 500g", "Terra 三文鱼中段 500g"),
|
||||
sub: L("Cold-chain, sashimi grade", "冷链直达,刺身级"),
|
||||
price: 3400,
|
||||
market: 4200,
|
||||
stock: 190,
|
||||
attrs: [{ key: "Cut", values: ["Fillet", "Steak"] }],
|
||||
},
|
||||
];
|
||||
|
||||
function buildProducts(): Product[] {
|
||||
@@ -427,7 +768,8 @@ function buildProducts(): Product[] {
|
||||
const img = `/mock/product-${spec.n}.svg`;
|
||||
const skus: Sku[] = [];
|
||||
const combos = spec.attrs.reduce<string[][]>(
|
||||
(acc, attr) => acc.flatMap((prefix) => attr.values.map((v) => [...prefix, `${attr.key}:${v}`])),
|
||||
(acc, attr) =>
|
||||
acc.flatMap((prefix) => attr.values.map((v) => [...prefix, `${attr.key}:${v}`])),
|
||||
[[]],
|
||||
);
|
||||
combos.forEach((combo, k) => {
|
||||
@@ -499,7 +841,12 @@ function seedOf(id: string): number {
|
||||
return hash;
|
||||
}
|
||||
|
||||
export function searchMockProducts(query: MockSearchQuery): { items: Product[]; total: number; page: number; per_page: number } {
|
||||
export function searchMockProducts(query: MockSearchQuery): {
|
||||
items: Product[];
|
||||
total: number;
|
||||
page: number;
|
||||
per_page: number;
|
||||
} {
|
||||
const page = query.page && query.page > 0 ? query.page : 1;
|
||||
const perPage = query.perPage && query.perPage > 0 ? query.perPage : 20;
|
||||
let list = MOCK_PRODUCTS.filter((p) => p.status === "published");
|
||||
@@ -520,7 +867,9 @@ export function searchMockProducts(query: MockSearchQuery): { items: Product[];
|
||||
const dir = query.order === "asc" ? 1 : -1;
|
||||
switch (query.sort) {
|
||||
case "price":
|
||||
list = [...list].sort((a, b) => dir * ((lowestSku(a)?.price_minor ?? 0) - (lowestSku(b)?.price_minor ?? 0)));
|
||||
list = [...list].sort(
|
||||
(a, b) => dir * ((lowestSku(a)?.price_minor ?? 0) - (lowestSku(b)?.price_minor ?? 0)),
|
||||
);
|
||||
break;
|
||||
case "sales":
|
||||
// The fixed-data catalogue has no orders, so every product reports zero
|
||||
@@ -570,9 +919,30 @@ export const MOCK_PROMOS: MockPromo[] = [
|
||||
// ---------- product detail extras ----------
|
||||
|
||||
export const MOCK_COUPONS: MockCoupon[] = [
|
||||
{ id: "cp1", title: L("$5 off over $59", "满 59 减 5"), amountMinor: 500, thresholdMinor: 5900, currency: BASE_CURRENCY, expiresAt: "2026-12-31" },
|
||||
{ id: "cp2", title: L("$15 off over $199", "满 199 减 15"), amountMinor: 1500, thresholdMinor: 19900, currency: BASE_CURRENCY, expiresAt: "2026-11-30" },
|
||||
{ id: "cp3", title: L("$40 off over $499", "满 499 减 40"), amountMinor: 4000, thresholdMinor: 49900, currency: BASE_CURRENCY, expiresAt: "2026-10-31" },
|
||||
{
|
||||
id: "cp1",
|
||||
title: L("$5 off over $59", "满 59 减 5"),
|
||||
amountMinor: 500,
|
||||
thresholdMinor: 5900,
|
||||
currency: BASE_CURRENCY,
|
||||
expiresAt: "2026-12-31",
|
||||
},
|
||||
{
|
||||
id: "cp2",
|
||||
title: L("$15 off over $199", "满 199 减 15"),
|
||||
amountMinor: 1500,
|
||||
thresholdMinor: 19900,
|
||||
currency: BASE_CURRENCY,
|
||||
expiresAt: "2026-11-30",
|
||||
},
|
||||
{
|
||||
id: "cp3",
|
||||
title: L("$40 off over $499", "满 499 减 40"),
|
||||
amountMinor: 4000,
|
||||
thresholdMinor: 49900,
|
||||
currency: BASE_CURRENCY,
|
||||
expiresAt: "2026-10-31",
|
||||
},
|
||||
];
|
||||
|
||||
// ---------- marketing ----------
|
||||
@@ -584,7 +954,11 @@ export const SECKILL_SESSIONS: SeckillSession[] = [
|
||||
{ startHour: 18, endHour: 24, label: "18:00" },
|
||||
];
|
||||
|
||||
export function seckillProducts(): { product: Product; seckillPriceMinor: number; soldPct: number }[] {
|
||||
export function seckillProducts(): {
|
||||
product: Product;
|
||||
seckillPriceMinor: number;
|
||||
soldPct: number;
|
||||
}[] {
|
||||
return MOCK_PRODUCTS.slice(0, 8).map((p, i) => {
|
||||
const sku = lowestSku(p);
|
||||
const base = sku?.price_minor ?? 1000;
|
||||
@@ -605,12 +979,60 @@ export function collectiveProducts(): { product: Product; need: number; joined:
|
||||
}
|
||||
|
||||
export const INTEGRAL_PRODUCTS: IntegralProduct[] = [
|
||||
{ id: "ip1", name: L("Insulated Mug", "保温杯"), image: "/mock/product-9.svg", points: 1200, marketPriceMinor: 4900, currency: BASE_CURRENCY, stock: 300 },
|
||||
{ id: "ip2", name: L("Canvas Tote", "帆布手提袋"), image: "/mock/product-15.svg", points: 600, marketPriceMinor: 1900, currency: BASE_CURRENCY, stock: 500 },
|
||||
{ id: "ip3", name: L("Phone Stand", "手机支架"), image: "/mock/product-7.svg", points: 400, marketPriceMinor: 1200, currency: BASE_CURRENCY, stock: 800 },
|
||||
{ id: "ip4", name: L("Cable Organizer Set", "数据线收纳套装"), image: "/mock/product-3.svg", points: 300, marketPriceMinor: 900, currency: BASE_CURRENCY, stock: 1000 },
|
||||
{ id: "ip5", name: L("Notebook A5", "A5 笔记本"), image: "/mock/product-14.svg", points: 250, marketPriceMinor: 700, currency: BASE_CURRENCY, stock: 1200 },
|
||||
{ id: "ip6", name: L("Umbrella", "折叠雨伞"), image: "/mock/product-13.svg", points: 900, marketPriceMinor: 2900, currency: BASE_CURRENCY, stock: 260 },
|
||||
{
|
||||
id: "ip1",
|
||||
name: L("Insulated Mug", "保温杯"),
|
||||
image: "/mock/product-9.svg",
|
||||
points: 1200,
|
||||
marketPriceMinor: 4900,
|
||||
currency: BASE_CURRENCY,
|
||||
stock: 300,
|
||||
},
|
||||
{
|
||||
id: "ip2",
|
||||
name: L("Canvas Tote", "帆布手提袋"),
|
||||
image: "/mock/product-15.svg",
|
||||
points: 600,
|
||||
marketPriceMinor: 1900,
|
||||
currency: BASE_CURRENCY,
|
||||
stock: 500,
|
||||
},
|
||||
{
|
||||
id: "ip3",
|
||||
name: L("Phone Stand", "手机支架"),
|
||||
image: "/mock/product-7.svg",
|
||||
points: 400,
|
||||
marketPriceMinor: 1200,
|
||||
currency: BASE_CURRENCY,
|
||||
stock: 800,
|
||||
},
|
||||
{
|
||||
id: "ip4",
|
||||
name: L("Cable Organizer Set", "数据线收纳套装"),
|
||||
image: "/mock/product-3.svg",
|
||||
points: 300,
|
||||
marketPriceMinor: 900,
|
||||
currency: BASE_CURRENCY,
|
||||
stock: 1000,
|
||||
},
|
||||
{
|
||||
id: "ip5",
|
||||
name: L("Notebook A5", "A5 笔记本"),
|
||||
image: "/mock/product-14.svg",
|
||||
points: 250,
|
||||
marketPriceMinor: 700,
|
||||
currency: BASE_CURRENCY,
|
||||
stock: 1200,
|
||||
},
|
||||
{
|
||||
id: "ip6",
|
||||
name: L("Umbrella", "折叠雨伞"),
|
||||
image: "/mock/product-13.svg",
|
||||
points: 900,
|
||||
marketPriceMinor: 2900,
|
||||
currency: BASE_CURRENCY,
|
||||
stock: 260,
|
||||
},
|
||||
];
|
||||
|
||||
// ---------- user center ----------
|
||||
@@ -626,8 +1048,26 @@ export const MOCK_USER: User = {
|
||||
};
|
||||
|
||||
export const MOCK_ADDRESSES: MockAddress[] = [
|
||||
{ id: "a1", recipient: "Demo Shopper", phone: "+1 555 0100", region: "California", city: "Cupertino", line1: "1 Infinite Loop", postalCode: "95014", isDefault: true },
|
||||
{ id: "a2", recipient: "Demo Shopper", phone: "+1 555 0100", region: "New York", city: "New York", line1: "88 Fifth Ave", postalCode: "10011", isDefault: false },
|
||||
{
|
||||
id: "a1",
|
||||
recipient: "Demo Shopper",
|
||||
phone: "+1 555 0100",
|
||||
region: "California",
|
||||
city: "Cupertino",
|
||||
line1: "1 Infinite Loop",
|
||||
postalCode: "95014",
|
||||
isDefault: true,
|
||||
},
|
||||
{
|
||||
id: "a2",
|
||||
recipient: "Demo Shopper",
|
||||
phone: "+1 555 0100",
|
||||
region: "New York",
|
||||
city: "New York",
|
||||
line1: "88 Fifth Ave",
|
||||
postalCode: "10011",
|
||||
isDefault: false,
|
||||
},
|
||||
];
|
||||
|
||||
export function defaultAddress(): Address {
|
||||
@@ -661,7 +1101,10 @@ export interface DashboardCounts {
|
||||
|
||||
// ---------- seeded orders / shipments / invoices ----------
|
||||
|
||||
function orderItemFrom(productIndex: number, qty: number): { id: string; sku: Sku; product: Product; qty: number } {
|
||||
function orderItemFrom(
|
||||
productIndex: number,
|
||||
qty: number,
|
||||
): { id: string; sku: Sku; product: Product; qty: number } {
|
||||
const product = MOCK_PRODUCTS[productIndex];
|
||||
const sku = lowestSku(product) ?? product.skus?.[0];
|
||||
if (!sku) throw new Error("mock seed: product has no sku");
|
||||
|
||||
Reference in New Issue
Block a user