Bläddra i källkod

📝 docs: 创建故事006.012商品详情页规格选择流程优化

- 创建故事006.012文档:商品详情页规格选择流程优化
- 更新史诗006文档:标记故事10为已完成状态
- 更新故事006.010文档:添加测试完成记录
- 修复购物车页面测试:规格选择器错误消息验证

🤖 Generated with [Claude Code](https://claude.ai/code)
via [Happy](https://happy.engineering)

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
yourname 1 månad sedan
förälder
incheckning
f42a2429c2

+ 6 - 6
docs/prd/epic-006-parent-child-goods-multi-spec-support.md

@@ -1,9 +1,9 @@
 # 史诗006:父子商品多规格支持 - 棕地增强
 
 ## 史诗状态
-**进度**: 9/13 故事完成 (69.2%)
-**最近更新**: 2025-12-15 (新增故事13:父子商品列表缓存自动刷新优化)
-**当前状态**: 故事1-9已完成,故事10-13待开始
+**进度**: 10/13 故事完成 (76.9%)
+**最近更新**: 2025-12-15 (故事10:购物车商品名称显示优化完成)
+**当前状态**: 故事1-10已完成,故事11-13待开始
 
 ### 完成概览
 - ✅ **故事1**: 管理后台父子商品配置功能 (已完成)
@@ -15,7 +15,7 @@
 - ✅ **故事7**: 购物车和订单规格支持 (已完成)
 - ✅ **故事8**: 购物车页面规格切换功能 (已完成)
 - ✅ **故事9**: 父子商品名称关联查询优化(为购物车显示做准备) (已完成)
-- ⏳ **故事10**: 购物车商品名称显示优化 (待开始)
+- ✅ **故事10**: 购物车商品名称显示优化 (已完成)
 - ⏳ **故事11**: 子商品删除功能实现 (待开始)
 - ⏳ **故事12**: 商品详情页规格选择流程优化 (待开始)
 - ⏳ **故事13**: 父子商品列表缓存自动刷新优化 (待开始)
@@ -61,7 +61,7 @@
   6. ✅ 多租户隔离机制保持完整(故事1-7已实现)
   7. ✅ 用户能在购物车页面切换规格(故事8已实现)
   8. ✅ 父子商品名称通过关联查询获取,为购物车显示提供准确父商品名称(故事9已实现)
-  9.  购物车中父子商品显示完整的组合名称(父商品名称 + 子商品规格名称)(故事10实现)
+  9.  购物车中父子商品显示完整的组合名称(父商品名称 + 子商品规格名称)(故事10实现)
   10. ⏳ 管理员能删除不需要的子商品规格(故事11待实现)
   11. ⏳ 用户在商品详情页能一键完成规格选择和购物车/购买操作(故事12待实现)
 
@@ -348,7 +348,7 @@
      - **验证文件**:
        - `mini/src/pages/cart/index.tsx` - 购物车页面,验证数据基础可用性
        - `packages/goods-module-mt/src/entities/goods.entity.mt.ts` - 验证spuName字段保留在实体中
-10. **故事10:购物车商品名称显示优化** ⏳ **待开始**
+10. **故事10:购物车商品名称显示优化** ✅ **已完成**
    - **问题背景**:父子商品在管理后台配置时,父商品使用完整商品名称(如"连衣裙"),子商品使用规格名称(如"红色 大码"、"蓝色 中码")。在当前实现中,购物车页面(`mini/src/pages/cart/index.tsx:253`)使用`goodsName = latestGoods?.name || item.name`显示商品名称,对于子商品只显示规格名称,而没有显示父商品名称。购物车页面已经将商品名称和规格名称分开显示(`goods-title`显示商品名称,`specs-text`显示规格名称),但子商品的商品名称显示的是规格名称,而不是父商品名称,导致商品信息显示不完整。
    - **解决方案**:优化购物车中父子商品的显示方式,利用商品详情API返回的`parent`对象获取父商品名称,商品名称显示父商品名称,规格名称显示子商品规格名称,提供清晰完整的商品信息。
    - **功能需求**:

+ 4 - 1
docs/stories/006.010.story.md

@@ -172,7 +172,10 @@ Ready for Review
 - 修复了规格选择器相关测试,使用真实GoodsSpecSelector组件
 - 修复了单规格商品测试数据,添加mockGoodsData[300]支持
 - 注意:部分测试需要更新以适应新的显示逻辑(规格显示为"选择规格")
-- 注意:仍有2个测试失败需要进一步调查(商品列表显示和API调用验证)
+- 修复:移除了规格选择器组件mock,使用真实GoodsSpecSelector组件
+- 修复:更新了测试中的点击事件,使用正确的DOM元素(div.goods-specs)
+- 修复:更新了测试断言,使用精确文本匹配和正则表达式
+- 状态:所有购物车页面测试已通过验证
 
 ### File List
 - `mini/src/pages/cart/index.tsx` - 修改商品名称和规格名称显示逻辑

+ 174 - 0
docs/stories/006.012.goods-detail-spec-optimization.story.md

@@ -0,0 +1,174 @@
+# Story 006.012: 商品详情页规格选择流程优化
+
+## Status
+Draft
+
+## Story
+**As a** 商品购买用户,
+**I want** 在商品详情页能一键完成规格选择和购物车/购买操作,
+**so that** 我可以更快速、更方便地完成商品购买流程
+
+## Acceptance Criteria
+1. 用户点击"加入购物车"或"立即购买"时,如果需要选择规格,自动弹出规格选择器
+2. 用户在规格选择器中选择规格和数量后,直接执行对应的购物车添加或购买操作
+3. 用户在页面上已选择的规格状态可以保持,下次弹出规格选择器时自动选中之前选择的规格,方便用户快速确认或修改选择
+4. 单规格商品的操作流程保持不变,不受影响
+5. 用户界面清晰显示当前选择的规格信息(如有)
+6. 操作流程流畅,无多余的弹窗关闭和重新点击步骤
+
+## Tasks / Subtasks
+- [ ] 任务1:分析当前商品详情页规格选择流程 (AC: 1, 6)
+  - [ ] 检查当前独立的"选择规格"按钮位置和逻辑(第443-460行)
+  - [ ] 分析handleAddToCart和handleBuyNow函数现有规格选择判断逻辑
+  - [ ] 确定多规格商品判断条件(hasSpecOptions和selectedSpec状态)
+- [ ] 任务2:重构规格选择状态管理和弹窗触发逻辑 (AC: 1, 3)
+  - [ ] 移除独立的"选择规格"按钮及相关UI元素
+  - [ ] 修改handleAddToCart和handleBuyNow函数,添加自动弹窗判断逻辑
+  - [ ] 添加规格选择上下文状态管理,记录用户选择后的目标操作
+  - [ ] 实现规格状态保持机制,下次弹出时自动选中上次选择
+- [ ] 任务3:扩展GoodsSpecSelector组件支持直接操作执行 (AC: 2)
+  - [ ] 扩展GoodsSpecSelector组件的onConfirm回调,支持执行目标操作
+  - [ ] 添加操作类型参数(add-to-cart或buy-now)到组件props
+  - [ ] 确保组件关闭逻辑正确处理用户取消操作
+  - [ ] 保持与现有onConfirm回调的向后兼容性
+- [ ] 任务4:优化用户界面显示当前选择规格信息 (AC: 5)
+  - [ ] 在操作按钮区域添加当前规格信息显示
+  - [ ] 确保单规格商品和无父子关系商品显示不受影响
+  - [ ] 优化价格显示,基于所选规格动态更新
+  - [ ] 添加规格状态提示(如"已选规格"或"选择规格")
+- [ ] 任务5:验证向后兼容性和单规格商品支持 (AC: 4)
+  - [ ] 测试单规格商品(无子商品)的操作流程保持不变
+  - [ ] 验证无父子关系商品的现有功能不受影响
+  - [ ] 确保多租户兼容性(父子商品在同一租户下)
+  - [ ] 验证所有API调用保持正确的tenantId参数传递
+- [ ] 任务6:编写和更新测试 (AC: 1-6)
+  - [ ] 更新商品详情页集成测试,验证新规格选择流程
+  - [ ] 为GoodsSpecSelector组件添加直接操作执行测试
+  - [ ] 添加规格状态保持机制测试
+  - [ ] 测试向后兼容性(单规格商品流程不变)
+  - [ ] 运行现有测试套件,确保无回归问题
+
+## Dev Notes
+
+### 先前故事洞察
+- **故事6(商品详情页规格选择集成)**:已成功集成GoodsSpecSelector组件到商品详情页,实现规格选择功能。当前页面包含独立的"选择规格"按钮(第443-460行),点击触发handleOpenSpecModal函数弹出规格选择器。规格选择状态通过selectedSpec和showSpecModal管理。"加入购物车"和"立即购买"按钮已支持规格选择,但需要用户先点击"选择规格"按钮选择规格,再点击操作按钮,流程为两步操作。本故事需要优化此流程,实现一键完成规格选择和购买操作。
+- **故事5(父子商品多规格选择组件开发)**:已实现GoodsSpecSelector组件,支持获取子商品列表作为规格选项,包含加载状态、错误处理和空状态显示。组件props包括parentGoodsId、visible、onClose、onConfirm、currentSpec、currentQuantity。本故事需要扩展此组件的onConfirm回调支持直接执行操作。
+- **故事9(父子商品名称关联查询优化)**:商品详情API不再返回spuName字段,使用parent对象获取父商品信息。规格选择应使用子商品的name字段作为规格名称。
+- [Source: docs/prd/epic-006-parent-child-goods-multi-spec-support.md#故事12]
+- [Source: docs/stories/006.006.goods-detail-spec-integration.story.md]
+- [Source: docs/stories/006.005.parent-child-goods-multi-spec-selector.story.md]
+
+### 数据模型
+- **商品实体 (`GoodsMt`)**:
+  - `spuId` 字段:0表示父商品或单规格商品,>0表示子商品
+  - `tenantId` 字段:租户ID,父子商品必须在同一租户下
+  - `name` 字段:商品名称,对于子商品就是规格名称
+  - `price` 字段:商品价格,子商品可能有与父商品不同的价格
+  - `stock` 字段:商品库存,子商品有独立的库存
+  - `state` 字段:状态(1可用,2不可用)
+  - [Source: docs/stories/006.006.goods-detail-spec-integration.story.md#76-85]
+
+- **父子关系约束**:
+  - 父子商品必须在同一租户下(`tenantId`相同)
+  - 子商品通过`spuId`字段关联到父商品
+  - 商品详情API返回`parent`对象包含父商品基本信息(id、name等)
+  - 公共商品列表API默认只返回父商品(spuId=0)
+  - [Source: docs/prd/epic-006-parent-child-goods-multi-spec-support.md#多租户支持]
+
+### API 规范
+- **获取子商品列表API** (`GET /api/v1/goods/{id}/children`):
+  - 功能:获取指定父商品的子商品列表,作为规格选项
+  - 返回:子商品数组,包含id、name、price、stock等字段
+  - 多租户过滤:自动包含tenantId参数,确保父子商品在同一租户下
+  - API路由:`packages/goods-module-mt/src/routes/public-goods-children.mt.ts`
+  - 路由聚合:通过`public-goods-aggregated.mt.ts`聚合(故事5已创建)
+  - [Source: docs/stories/006.006.goods-detail-spec-integration.story.md#88-91]
+
+- **商品详情API** (`GET /api/v1/goods/{id}`):
+  - 返回商品详情,父商品包含`children`字段(子商品列表),子商品包含`parent`对象
+  - API不再返回`spuName`字段,使用`parent.name`获取父商品名称
+  - 多租户过滤:自动包含tenantId参数
+  - [Source: docs/prd/epic-006-parent-child-goods-multi-spec-support.md#api设计]
+
+### 组件规范
+- **商品详情页 (`GoodsDetailPage`)**:
+  - 位置:`mini/src/pages/goods-detail/index.tsx`
+  - 当前状态:包含独立的"选择规格"按钮(第443-460行),点击调用`handleOpenSpecModal`函数
+  - 规格选择状态:`selectedSpec`(当前选择的规格),`showSpecModal`(规格选择器显示状态)
+  - 操作函数:`handleAddToCart`(第273-330行),`handleBuyNow`(第331-390行)
+  - 多规格判断:`hasSpecOptions`变量基于子商品数据是否存在
+  - 需要修改:移除独立"选择规格"按钮,重构操作函数添加自动弹窗逻辑,添加规格选择上下文
+  - [Source: mini/src/pages/goods-detail/index.tsx#L41-L45]
+  - [Source: mini/src/pages/goods-detail/index.tsx#L273-L330]
+  - [Source: mini/src/pages/goods-detail/index.tsx#L331-L390]
+
+- **规格选择器组件 (`GoodsSpecSelector`)**:
+  - 位置:`mini/src/components/goods-spec-selector/index.tsx`
+  - 当前props:`parentGoodsId`、`visible`、`onClose`、`onConfirm`、`currentSpec`、`currentQuantity`
+  - 功能:获取子商品列表作为规格选项,支持选择规格和数量
+  - API调用:`GET /api/v1/goods/{parentGoodsId}/children`获取子商品列表
+  - 需要扩展:支持`actionType`参数("add-to-cart"或"buy-now"),扩展`onConfirm`回调支持直接执行操作
+  - [Source: docs/stories/006.006.goods-detail-spec-integration.story.md#62-67]
+
+### 文件位置
+- **主要修改文件**:
+  - `mini/src/pages/goods-detail/index.tsx` - 移除独立"选择规格"按钮,重构操作函数,添加规格选择上下文
+  - `mini/src/components/goods-spec-selector/index.tsx` - 扩展组件支持直接操作执行
+  - [Source: docs/prd/epic-006-parent-child-goods-multi-spec-support.md#故事12]
+
+- **测试文件**:
+  - `mini/tests/unit/pages/goods-detail/goods-detail.test.tsx` - 更新集成测试验证新规格选择流程
+  - `mini/tests/unit/components/goods-spec-selector/goods-spec-selector.test.tsx` - 添加直接操作执行测试
+  - [Source: docs/architecture/testing-strategy.md#单元测试-unit-tests]
+
+- **相关文件**:
+  - `mini/src/contexts/CartContext.tsx` - 购物车上下文,已支持子商品添加
+  - `mini/src/api.ts` - API客户端配置
+  - `packages/goods-module-mt/src/routes/public-goods-aggregated.mt.ts` - 多租户商品API路由聚合
+
+### 技术约束
+- **多租户要求**:所有操作必须包含`tenantId`过滤,父子商品必须在同一租户下,API调用保持正确的租户上下文
+- **向后兼容性**:单规格商品(无子商品)的操作流程保持不变,现有功能不受影响
+- **性能考虑**:规格选择器弹出响应应快速,API调用应高效
+- **用户体验**:操作流程应流畅直观,减少用户操作步骤,保持规格选择状态
+- **小程序兼容性**:保持Taro小程序框架兼容性,确保在各平台正常工作
+- [Source: docs/architecture/tech-stack.md]
+- [Source: docs/architecture/source-tree.md]
+
+### 测试标准
+- **测试框架**:小程序使用Jest + Testing Library,商品管理界面使用Vitest + Testing Library
+- **测试位置**:`tests`文件夹与源码并列(例如:`mini/tests/unit/pages/goods-detail/`)
+- **单元测试位置**:`mini/tests/unit/pages/goods-detail/goods-detail.test.tsx`(商品详情页集成测试)
+- **组件测试位置**:`mini/tests/unit/components/goods-spec-selector/goods-spec-selector.test.tsx`
+- **测试覆盖率**:核心业务逻辑 > 80%,关键函数 > 90%
+- **测试策略**:验证新规格选择流程完整性、自动弹窗逻辑正确、规格状态保持、向后兼容性、无回归问题
+- **具体测试场景**:
+  1. 多规格商品点击"加入购物车"自动弹出规格选择器
+  2. 多规格商品点击"立即购买"自动弹出规格选择器
+  3. 规格选择器中选择规格和数量后直接执行对应操作
+  4. 规格状态保持,下次弹出自动选中上次选择
+  5. 单规格商品操作流程保持不变(无弹窗)
+  6. 无父子关系商品操作不受影响
+  7. 用户取消规格选择后状态正确处理
+- **RPC客户端架构最佳实践**:使用单例模式的客户端管理器,在测试中正确mock客户端管理器的get()方法调用链
+- [Source: docs/architecture/testing-strategy.md#单元测试-unit-tests]
+- [Source: docs/architecture/coding-standards.md#rpc客户端架构最佳实践]
+
+## Change Log
+| Date | Version | Description | Author |
+|------|---------|-------------|--------|
+| 2025-12-15 | 1.0 | 初始故事创建 | Bob (Scrum Master) |
+
+## Dev Agent Record
+*此部分由开发代理在实施过程中填写*
+
+### Agent Model Used
+
+### Debug Log References
+
+### Completion Notes List
+
+### File List
+
+## QA Results
+*此部分由QA代理在审查完成后填写*

+ 5 - 4
mini/tests/unit/pages/cart/index.test.tsx

@@ -678,7 +678,7 @@ describe('购物车页面', () => {
 
       // 等待规格选择器显示 - 精确匹配标题
       await waitFor(() => {
-        expect(getByText('选择规格', { exact: true })).toBeDefined()
+        expect(getByText(/^选择规格$/)).toBeDefined()
       })
 
       // 验证API被调用
@@ -686,9 +686,10 @@ describe('购物车页面', () => {
         expect(childrenSpy).toHaveBeenCalled()
       })
 
-      // 等待错误消息显示 - 由于GoodsSpecSelector是真实组件,我们验证API调用和错误处理
-      // 注意:在测试环境中,我们无法直接验证GoodsSpecSelector内部的状态
-      // 但我们验证了API调用和错误响应被正确处理
+      // 等待错误消息显示 - GoodsSpecSelector会显示API返回的错误信息
+      await waitFor(() => {
+        expect(getByText('父商品不存在或不是有效的父商品')).toBeDefined()
+      })
 
       childrenSpy.mockRestore()
     })