# 商品 SKU 选择器 实现计划 > **面向 AI 代理的工作者:** 必需子技能:使用 superpowers:subagent-driven-development(推荐)或 superpowers:executing-plans 逐任务实现此计划。步骤使用复选框(`- [ ]`)语法来跟踪进度。 **目标:** 在商品详情页添加 SKU 规格选择功能,让用户在加购/下单前选择规格,会员价联动展示 **架构:** 使用现有 `/api/product/spec/map` 接口获取规格分组数据,在详情页 `info-section` 和 `intro-section` 之间插入规格选择器,选择后同步更新价格/库存,加购/下单时传递 `skuId` 和 `specDesc` **技术栈:** uni-app Vue 2 Options API,微信小程序 **设计文档:** `docs/superpowers/specs/2026-08-04-product-sku-selector-design.md` --- ### 任务 1:product-detail.vue — 数据层改造 **文件:** - 修改:`cfc-frontend/pages/discover-detail/product-detail/product-detail.vue` - [ ] **步骤 1:添加 imports 和 data 属性** 在 `import` 中添加 `productSpecMap` 和 `getMyMembership`: ```js import { productDetail, productOrderCreate, productOrderPay, cartAdd, getShareQrCode, productSpecMap, getMyMembership } from '@/utils/api.js' ``` 在 `data()` 中添加: ```js data() { return { product: {}, loading: false, showPoster: false, posterQrCode: '', posterDimension: '', posterInviteText: '', // SKU 选择器状态 specGroups: [], // 规格分组数据 selectedSpecs: {}, // { groupId: optionId } selectedSku: null, // 匹配到的完整 SKU isMember: false // 会员状态 } } ``` - [ ] **步骤 2:添加计算属性** 在 `computed` 中添加: ```js computed: { // ... 现有计算属性 ... hasSku: function() { return this.specGroups && this.specGroups.length > 0 }, allSpecsSelected: function() { if (!this.hasSku) return true for (var i = 0; i < this.specGroups.length; i++) { if (!this.selectedSpecs[this.specGroups[i].groupId]) return false } return true }, displayPrice: function() { // 有 SKU 且已选 → 显示 SKU 价格 if (this.selectedSku) return this.selectedSku.price // 有 SKU 但未选 → 显示产品默认价格 if (this.hasSku) return this.product.price || 0 // 无 SKU → 显示产品价格 return this.product.price || 0 }, displayMemberPrice: function() { if (this.selectedSku) { // 如果 SKU 有 memberPrice,用;否则用产品的 memberPrice return this.product.memberPrice || 0 } return this.product.memberPrice || 0 }, displayStock: function() { if (this.selectedSku) return this.selectedSku.stock || 0 if (this.hasSku) return 0 return this.product.stock || 0 }, buyButtonDisabled: function() { return this.hasSku && !this.allSpecsSelected }, buyButtonText: function() { if (this.hasSku && !this.allSpecsSelected) return '请选择完整规格' return '立即购买' } } ``` - [ ] **步骤 3:修改 loadDetail 方法** 修改 `loadDetail` 在加载商品详情后获取规格数据: ```js loadDetail: function(id) { this.loading = true var that = this productDetail({ id: parseInt(id) }).then(function(res) { that.loading = false if (res.code === 200 && res.data) { that.product = res.data that.setShareInfo( that.product.name, '/pages/discover-detail/product-detail/product-detail?id=' + that.product.id ) // 加载 SKU 规格数据 that.loadSkuData(id) } }).catch(function() { that.loading = false }) } ``` - [ ] **步骤 4:添加 loadSkuData 和 checkMemberStatus 方法** 在 `methods` 中添加: ```js loadSkuData: function(productId) { var that = this productSpecMap({ productIds: [parseInt(productId)] }).then(function(res) { if (res.code === 200 && res.data) { var map = res.data var groups = map[productId] if (groups && groups.length > 0) { that.specGroups = groups } } }).catch(function() { // 规格加载失败不阻塞页面 }) }, checkMemberStatus: function() { var that = this getMyMembership().then(function(res) { if (res.data && res.data.memberLevel) { that.isMember = res.data.memberLevel !== 'FREE' } }).catch(function() {}) } ``` 在 `onLoad` 末尾调用 `checkMemberStatus`: ```js onLoad: function(options) { // ... 现有代码 ... if (pid) { this.loadDetail(pid) this.checkMemberStatus() } } ``` - [ ] **步骤 5:添加规格选择方法** 在 `methods` 中添加: ```js selectSpec: function(groupId, option) { // 切换选中状态 if (this.selectedSpecs[groupId] === option.id) { // 点击已选中的 → 取消选中 delete this.selectedSpecs[groupId] this.selectedSku = null } else { this.selectedSpecs[groupId] = option.id this.matchSku() } // 触发视图更新 this.$forceUpdate() }, matchSku: function() { // 检查是否所有组都已选 for (var i = 0; i < this.specGroups.length; i++) { if (!this.selectedSpecs[this.specGroups[i].groupId]) { this.selectedSku = null return } } // 取所有选中选项的 skuId 的交集 var commonSkuIds = null for (var j = 0; j < this.specGroups.length; j++) { var group = this.specGroups[j] var selectedOptionId = this.selectedSpecs[group.groupId] var skuIds = [] for (var k = 0; k < group.options.length; k++) { if (group.options[k].id === selectedOptionId) { skuIds.push(group.options[k].skuId) } } if (commonSkuIds === null) { commonSkuIds = skuIds } else { // 取交集 var intersection = [] for (var m = 0; m < commonSkuIds.length; m++) { for (var n = 0; n < skuIds.length; n++) { if (commonSkuIds[m] === skuIds[n]) { intersection.push(commonSkuIds[m]) break } } } commonSkuIds = intersection } } if (commonSkuIds && commonSkuIds.length === 1) { // 找到匹配的 SKU,从 specMap 数据中取价格和库存 var matchedSkuId = commonSkuIds[0] // 从任一组的选项中获取完整的 SKU 信息 for (var p = 0; p < this.specGroups.length; p++) { var opts = this.specGroups[p].options for (var q = 0; q < opts.length; q++) { if (opts[q].skuId === matchedSkuId) { this.selectedSku = { id: matchedSkuId, price: opts[q].price, stock: opts[q].stock } return } } } } this.selectedSku = null }, buildSpecDesc: function() { if (!this.selectedSku) return '' var desc = '' for (var i = 0; i < this.specGroups.length; i++) { var group = this.specGroups[i] var optionId = this.selectedSpecs[group.groupId] var optionName = '' for (var j = 0; j < group.options.length; j++) { if (group.options[j].id === optionId) { optionName = group.options[j].name break } } if (desc) desc += '; ' desc += group.groupName + ':' + optionName } return desc } ``` ### 任务 2:product-detail.vue — 模板层改造 **文件:** - 修改:`cfc-frontend/pages/discover-detail/product-detail/product-detail.vue` - [ ] **步骤 1:修改价格展示区** 将 `info-section` 中的 price-row 替换为: ```html {{ formatPriceWithSymbol(displayPrice) }} {{ formatPriceWithSymbol(displayPrice) }} 会员价 {{ formatPriceWithSymbol(displayMemberPrice) }} → 点击升级 会员价 {{ formatPriceWithSymbol(displayMemberPrice) }} ``` - [ ] **步骤 2:添加 SKU 选择器模板** 在 `info-section` 结束标签 `` 之后、`intro-section` 之前插入: ```html {{ group.groupName }} {{ option.name }} 已选: {{ buildSpecDesc() }} ``` - [ ] **步骤 3:修改底部栏联动** 将 `bottom-bar` 中的价格和按钮替换为: ```html {{ formatPriceWithSymbol(displayPrice) }} 会员{{ formatPriceWithSymbol(displayMemberPrice) }} 会员{{ formatPriceWithSymbol(displayMemberPrice) }} ``` - [ ] **步骤 4:添加样式** 在 `