在商品详情页添加 SKU(规格)选择功能,让用户在加购或下单前选择商品规格。后端和购物车/结算链路已支持 SKU,主要工作在前端。
| 文件 | 改动 |
|---|---|
cfc-frontend/pages/discover-detail/product-detail/product-detail.vue |
新增 SKU 选择器 UI + 修改加购/购买逻辑 |
cfc-frontend/pages/shop/cart/cart.vue |
结算时传递 skuId + specDesc |
cfc-frontend/utils/api.js |
已有 productSpecMap,无需新增 |
loadDetail(id)
├── productDetail({ id }) → 商品基本信息
└── productSpecMap({ productIds: [id] }) → 规格分组
用户选择规格选项
→ 记录 selectedSpecs: { groupId → optionId }
→ 遍历 SKU 列表匹配当前选择组合
→ 找到匹配 SKU → 更新价格/库存/图片
→ 未完全选择 → 提示"请选择完整规格"
onAddToCart()
→ cartAdd({ userId, productId, skuId, quantity: 1 })
onBuy()
→ checkoutItems = [{ productId, productName, coverImage, skuId, specDesc, unitPrice, quantity }]
→ 存入 storage → 跳转 checkout
goCheckout() (cart.vue)
→ checkoutItems = [{ ..., skuId, specDesc }]
→ 存入 storage → 跳转 checkout
// /api/product/spec/map
// 入参: { productIds: [123] }
// 出参: { "123": [ ...groups ] }
specGroups: [
{
groupId: 1,
groupName: '颜色',
options: [
{ id: 101, name: '红色', skuId: 1, price: 10000, stock: 10, enabled: 1 },
{ id: 102, name: '蓝色', skuId: 2, price: 12000, stock: 5, enabled: 1 }
]
},
{
groupId: 2,
groupName: '尺码',
options: [
{ id: 201, name: 'M', skuId: 1, price: 10000, stock: 10, enabled: 1 },
{ id: 202, name: 'L', skuId: 2, price: 12000, stock: 5, enabled: 1 }
]
}
]
data() {
return {
product: {},
specGroups: [], // 从 specMap 获取
selectedSpecs: {}, // { groupId: optionId }
selectedSku: null, // 匹配到的完整 SKU
skuList: [], // 从 specMap 解析出的所有 SKU 映射
isMember: false // 会员状态
}
}
// 从 specMap 数据中构建 SKU 映射
// 每个 option 都带有 skuId,同一 skuId 跨 group 出现
// 当选完所有组后,任一选项的 skuId 即为匹配结果
matchSku: function() {
var groups = this.specGroups
var selected = this.selectedSpecs
// 检查是否所有组都已选
for (var i = 0; i < groups.length; i++) {
if (!selected[groups[i].groupId]) return null
}
// 取第一个选中选项的 skuId(同组所有选项的 skuId 指向同一 SKU)
var firstGroup = groups[0]
var firstOptionId = selected[firstGroup.groupId]
var skuId = null
for (var j = 0; j < firstGroup.options.length; j++) {
if (firstGroup.options[j].id === firstOptionId) {
skuId = firstGroup.options[j].skuId
break
}
}
return skuId
}
┌─────────────────────────────────┐
│ [轮播图] │
├─────────────────────────────────┤
│ 商品名称 │
│ ¥100.00 │ ← 主价格(大号橙色)
│ 会员价 ¥80.00 → 点击升级 │ ← 非会员: 小字灰色,可点击
│ ─ 或 ─ │
│ ¥80.00 │ ← 会员: 主价格显示会员价
│ ¥100.00 │ ← 会员: 原价带删除线
│ │
│ 库存 50 件 │
│ 供应商:XXX │
├─────────────────────────────────┤
│ 颜色 │ ← 规格组名
│ ┌──────┐ ┌──────┐ ┌──────┐ │
│ │ ■红色 │ │ 蓝色 │ │ 黑色 │ │ ← 选中态/未选态/不可选态
│ └──────┘ └──────┘ └──────┘ │
│ │
│ 尺码 │
│ ┌──────┐ ┌──────┐ ┌──────┐ │
│ │ S │ │ ■M │ │ L │ │
│ └──────┘ └──────┘ └──────┘ │
│ │
│ 已选: 红色 / M │
├─────────────────────────────────┤
│ 商品介绍... │
├─────────────────────────────────┤
│ 商品详情... │
├─────────────────────────────────┤
│ ¥100.00 [🖼][加入购物车][立即购买] │
└─────────────────────────────────┘
| 状态 | 样式 | 条件 |
|---|---|---|
| 未选 | 白底灰边框 #ddd | 默认 |
| 选中 | 橙底白字 #F97316 | 已点击选中 |
| 不可选 | 灰色+删除线 | 对应 SKU 库存为 0 或未启用 |
/pages/membership/upgrade)新增导入: productSpecMap, getMyMembership
新增 data:
specGroups: []selectedSpecs: {}selectedSku: nullisMember: falseloadDetail 中补充: 调用 productSpecMap 获取规格数据
新增模板: 在 info-section 和 intro-section 之间插入规格选择区域
修改 onAddToCart: 传 skuId
修改 onBuy: 传 skuId 和 specDesc
新增方法:
selectSpec(groupId, option) — 选择规格matchSku() — 匹配当前选择的 SKUbuildSpecDesc() — 构建规格描述文本checkMemberStatus() — 检查会员状态修改 goCheckout: 传 skuId 和 specDesc 到 checkoutItems
| 场景 | 行为 |
|---|---|
| 商品无 SKU(specMap 返回空) | 不显示 SKU 选择器,保持现有行为 |
| 部分规格选项库存为 0 | 该选项显示为灰色不可选 |
| 选择组合后无对应 SKU | 提示"该规格组合暂无库存" |
| 用户未选规格直接点加购 | 提示"请选择完整规格" |
| 非会员查看会员价 | 会员价小字显示,可点击跳转升级页 |
| 会员查看 | 主价格显示会员价,原价带删除线 |
skuId 处理逻辑)