|
@@ -0,0 +1,261 @@
|
|
|
|
|
+# Story 006.017: 小程序商品卡片多规格支持
|
|
|
|
|
+
|
|
|
|
|
+## Status
|
|
|
|
|
+Draft
|
|
|
|
|
+
|
|
|
|
|
+## Story
|
|
|
|
|
+**As a** 小程序用户,
|
|
|
|
|
+**I want** 在商品列表页面(首页、商品列表页、搜索结果页)点击商品卡片的"添加到购物车"图标时,如果商品有多规格选项,能够弹出规格选择器选择规格后再添加到购物车,
|
|
|
|
|
+**so that** 无需进入商品详情页就能快速完成多规格商品的购物车添加操作,提升购物体验
|
|
|
|
|
+
|
|
|
|
|
+## Acceptance Criteria
|
|
|
|
|
+1. 用户点击商品卡片的购物车图标时,如果商品有多规格选项(有子商品),弹出规格选择器(GoodsSpecSelector组件)
|
|
|
|
|
+2. 用户在规格选择器中选择规格和数量后,点击确定成功添加到购物车
|
|
|
|
|
+3. 如果商品没有规格选项(单规格商品),直接添加到购物车,现有功能不受影响
|
|
|
|
|
+4. 购物车正确记录父子商品关系,`parentGoodsId`字段正确设置
|
|
|
|
|
+5. 所有使用商品卡片的页面(首页、商品列表页、搜索结果页)都支持多规格商品
|
|
|
|
|
+6. 现有单规格商品功能不受影响,无回归问题
|
|
|
|
|
+7. 添加完整的单元测试,覆盖多规格和单规格场景
|
|
|
|
|
+
|
|
|
|
|
+## Tasks / Subtasks
|
|
|
|
|
+- [ ] **分析现有商品卡片组件和规格选择器组件** (AC: 1, 2, 3, 4)
|
|
|
|
|
+ - [ ] 分析`mini/src/components/goods-card/index.tsx`组件的当前实现,特别是`handleAddCart`函数
|
|
|
|
|
+ - [ ] 分析`mini/src/components/goods-spec-selector/index.tsx`组件的API和props接口
|
|
|
|
|
+ - [ ] 分析商品详情页(`mini/src/pages/goods-detail/index.tsx`)中的规格选择逻辑,作为参考实现
|
|
|
|
|
+ - [ ] 确认商品卡片需要传递哪些数据给规格选择器(goodsId, parentGoodsId, hasSpecOptions等)
|
|
|
|
|
+
|
|
|
|
|
+- [ ] **设计商品卡片组件扩展方案** (AC: 1, 2, 3, 4, 5)
|
|
|
|
|
+ - [ ] 设计商品卡片props扩展,添加多规格支持所需字段
|
|
|
|
|
+ - [ ] 设计状态管理方案:`showSpecModal`控制弹窗显示,`selectedSpec`记录选择的规格
|
|
|
|
|
+ - [ ] 设计规格选择器的集成方式,参考商品详情页的`handleAddToCart`逻辑
|
|
|
|
|
+ - [ ] 设计购物车添加逻辑,确保`parentGoodsId`正确传递
|
|
|
|
|
+
|
|
|
|
|
+- [ ] **实现商品卡片多规格支持** (AC: 1, 2, 3, 4)
|
|
|
|
|
+ - [ ] 修改`mini/src/components/goods-card/index.tsx`组件,添加规格选择判断逻辑
|
|
|
|
|
+ - [ ] 集成`GoodsSpecSelector`组件,支持`add-to-cart`操作类型
|
|
|
|
|
+ - [ ] 实现`handleAddCart`函数的多规格处理逻辑
|
|
|
|
|
+ - [ ] 添加状态管理:`showSpecModal`、`selectedSpec`、`pendingAction`等状态
|
|
|
|
|
+ - [ ] 确保规格选择器正确获取子商品列表数据
|
|
|
|
|
+
|
|
|
|
|
+- [ ] **更新商品卡片使用页面** (AC: 5)
|
|
|
|
|
+ - [ ] 更新`mini/src/pages/index/index.tsx`首页,确保传递正确的商品数据给商品卡片
|
|
|
|
|
+ - [ ] 更新`mini/src/pages/goods-list/index.tsx`商品列表页,确保传递正确的商品数据
|
|
|
|
|
+ - [ ] 更新`mini/src/pages/search-result/index.tsx`搜索结果页,确保传递正确的商品数据
|
|
|
|
|
+ - [ ] 更新`mini/src/components/goods-list/index.tsx`商品列表组件,确保数据传递正确
|
|
|
|
|
+
|
|
|
|
|
+- [ ] **编写单元测试** (AC: 7)
|
|
|
|
|
+ - [ ] 创建`mini/tests/unit/components/goods-card/goods-card.test.tsx`测试文件
|
|
|
|
|
+ - [ ] 测试单规格商品直接添加到购物车场景
|
|
|
|
|
+ - [ ] 测试多规格商品弹出规格选择器场景
|
|
|
|
|
+ - [ ] 测试规格选择后成功添加到购物车场景
|
|
|
|
|
+ - [ ] 测试父子商品关系正确记录场景
|
|
|
|
|
+ - [ ] 测试商品卡片在不同页面的数据传递正确性
|
|
|
|
|
+
|
|
|
|
|
+- [ ] **集成测试和验证** (AC: 1, 2, 3, 4, 5, 6)
|
|
|
|
|
+ - [ ] 运行现有测试套件,确保无回归问题
|
|
|
|
|
+ - [ ] 手动测试首页商品卡片的多规格支持
|
|
|
|
|
+ - [ ] 手动测试商品列表页的多规格支持
|
|
|
|
|
+ - [ ] 手动测试搜索结果页的多规格支持
|
|
|
|
|
+ - [ ] 验证购物车中父子商品关系正确性
|
|
|
|
|
+
|
|
|
|
|
+## Dev Notes
|
|
|
|
|
+
|
|
|
|
|
+### 技术栈信息 [Source: architecture/tech-stack.md]
|
|
|
|
|
+- **运行时**: Node.js 20.18.3
|
|
|
|
|
+- **框架**: Hono 4.8.5 (Web框架和API路由,RPC类型安全)
|
|
|
|
|
+- **前端框架**: React 19.1.0 (用户界面构建)
|
|
|
|
|
+- **数据库**: PostgreSQL 17 (通过TypeORM进行数据持久化存储)
|
|
|
|
|
+- **ORM**: TypeORM 0.3.25 (数据库操作抽象,实体管理)
|
|
|
|
|
+- **样式**: Tailwind CSS 4.1.11 (原子化CSS框架)
|
|
|
|
|
+- **状态管理**: React Query 5.83.0 (服务端状态管理)
|
|
|
|
|
+- **测试框架**: Jest 29.x (mini小程序专用测试框架)
|
|
|
|
|
+- **API测试**: hono/testing (内置,API端点测试,更好的类型安全)
|
|
|
|
|
+
|
|
|
|
|
+### 项目结构信息 [Source: architecture/source-tree.md]
|
|
|
|
|
+- **包管理**: 使用pnpm workspace管理多包依赖关系
|
|
|
|
|
+- **多租户架构**: 所有操作必须包含tenantId过滤,父子商品必须在同一租户下
|
|
|
|
|
+- **小程序包**: `mini/`目录包含Taro小程序前端代码
|
|
|
|
|
+- **主要组件位置**:
|
|
|
|
|
+ - 商品卡片: `mini/src/components/goods-card/index.tsx`
|
|
|
|
|
+ - 规格选择器: `mini/src/components/goods-spec-selector/index.tsx`
|
|
|
|
|
+ - 商品列表: `mini/src/components/goods-list/index.tsx`
|
|
|
|
|
+- **页面位置**:
|
|
|
|
|
+ - 首页: `mini/src/pages/index/index.tsx`
|
|
|
|
|
+ - 商品列表页: `mini/src/pages/goods-list/index.tsx`
|
|
|
|
|
+ - 搜索结果页: `mini/src/pages/search-result/index.tsx`
|
|
|
|
|
+ - 商品详情页: `mini/src/pages/goods-detail/index.tsx` (参考实现)
|
|
|
|
|
+- **API客户端**: 使用Hono RPC客户端调用商品API
|
|
|
|
|
+- **购物车上下文**: `mini/src/contexts/CartContext.tsx`,包含`addToCart`和`switchSpec`函数
|
|
|
|
|
+
|
|
|
|
|
+### 编码标准 [Source: architecture/coding-standards.md]
|
|
|
|
|
+- **代码风格**: TypeScript严格模式,一致的缩进和命名
|
|
|
|
|
+- **测试位置**: `tests/unit/`目录与源码对应结构
|
|
|
|
|
+- **覆盖率目标**: 核心业务逻辑 > 80%
|
|
|
|
|
+- **测试类型**: 单元测试、集成测试
|
|
|
|
|
+- **现有API兼容性**: 确保测试不破坏现有API契约
|
|
|
|
|
+
|
|
|
|
|
+### 参考实现分析
|
|
|
|
|
+**商品详情页规格选择逻辑** (`mini/src/pages/goods-detail/index.tsx`):
|
|
|
|
|
+- `handleAddToCart`函数(第355-417行)包含完整的规格选择逻辑
|
|
|
|
|
+- 检查是否有规格选项(`hasSpecOptions`)
|
|
|
|
|
+- 如果有规格选项,弹出规格选择器(`setShowSpecModal(true)`)
|
|
|
|
|
+- 规格选择器的`onConfirm`回调执行添加购物车操作
|
|
|
|
|
+- 使用`pendingAction`状态记录用户意图(加入购物车或立即购买)
|
|
|
|
|
+
|
|
|
|
|
+**商品卡片当前实现** (`mini/src/components/goods-card/index.tsx`):
|
|
|
|
|
+- `handleAddCart`函数(第41-44行)直接调用`onAddCart(data)`
|
|
|
|
|
+- 没有规格选择判断逻辑
|
|
|
|
|
+- 需要添加类似商品详情页的规格选择逻辑
|
|
|
|
|
+
|
|
|
|
|
+**规格选择器组件** (`mini/src/components/goods-spec-selector/index.tsx`):
|
|
|
|
|
+- 支持`actionType` prop(`add-to-cart`或`buy-now`)
|
|
|
|
|
+- 从API获取子商品列表作为规格选项
|
|
|
|
|
+- `onConfirm`回调返回选择的规格信息和数量
|
|
|
|
|
+- 支持`parentGoodsId`参数获取子商品列表
|
|
|
|
|
+
|
|
|
|
|
+### 技术实现要点
|
|
|
|
|
+1. **商品卡片props扩展**:
|
|
|
|
|
+ ```typescript
|
|
|
|
|
+ interface GoodsCardProps {
|
|
|
|
|
+ // 现有props...
|
|
|
|
|
+ hasSpecOptions?: boolean; // 是否有规格选项
|
|
|
|
|
+ parentGoodsId?: number; // 父商品ID(用于获取子商品列表)
|
|
|
|
|
+ goodsId?: number; // 当前商品ID(父商品或子商品ID)
|
|
|
|
|
+ // ...其他props
|
|
|
|
|
+ }
|
|
|
|
|
+ ```
|
|
|
|
|
+
|
|
|
|
|
+2. **状态管理**:
|
|
|
|
|
+ ```typescript
|
|
|
|
|
+ const [showSpecModal, setShowSpecModal] = useState(false);
|
|
|
|
|
+ const [selectedSpec, setSelectedSpec] = useState<GoodsSpecSelection | null>(null);
|
|
|
|
|
+ const [pendingAction, setPendingAction] = useState<'add-to-cart' | null>(null);
|
|
|
|
|
+ ```
|
|
|
|
|
+
|
|
|
|
|
+3. **修改handleAddCart函数**:
|
|
|
|
|
+ ```typescript
|
|
|
|
|
+ const handleAddCart = () => {
|
|
|
|
|
+ if (hasSpecOptions && parentGoodsId) {
|
|
|
|
|
+ // 有多规格选项,弹出规格选择器
|
|
|
|
|
+ setPendingAction('add-to-cart');
|
|
|
|
|
+ setShowSpecModal(true);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ // 单规格商品,直接添加到购物车
|
|
|
|
|
+ onAddCart(data);
|
|
|
|
|
+ }
|
|
|
|
|
+ };
|
|
|
|
|
+ ```
|
|
|
|
|
+
|
|
|
|
|
+4. **规格选择器集成**:
|
|
|
|
|
+ ```typescript
|
|
|
|
|
+ <GoodsSpecSelector
|
|
|
|
|
+ open={showSpecModal}
|
|
|
|
|
+ onOpenChange={setShowSpecModal}
|
|
|
|
|
+ parentGoodsId={parentGoodsId}
|
|
|
|
|
+ actionType="add-to-cart"
|
|
|
|
|
+ onConfirm={(selection) => {
|
|
|
|
|
+ // 执行添加购物车操作
|
|
|
|
|
+ onAddCart({
|
|
|
|
|
+ ...data,
|
|
|
|
|
+ id: selection.goodsId, // 子商品ID
|
|
|
|
|
+ parentGoodsId: parentGoodsId,
|
|
|
|
|
+ name: selection.goodsName, // 子商品名称(规格名称)
|
|
|
|
|
+ price: selection.price,
|
|
|
|
|
+ count: selection.quantity
|
|
|
|
|
+ });
|
|
|
|
|
+ setSelectedSpec(selection);
|
|
|
|
|
+ }}
|
|
|
|
|
+ />
|
|
|
|
|
+ ```
|
|
|
|
|
+
|
|
|
|
|
+5. **数据传递更新**:
|
|
|
|
|
+ - 首页、商品列表页、搜索结果页需要传递`hasSpecOptions`和`parentGoodsId`给商品卡片
|
|
|
|
|
+ - 需要通过商品API判断商品是否有子商品(规格选项)
|
|
|
|
|
+
|
|
|
|
|
+### 文件变更计划
|
|
|
|
|
+**主要修改文件**:
|
|
|
|
|
+1. `mini/src/components/goods-card/index.tsx` - 添加规格选择逻辑,集成GoodsSpecSelector组件
|
|
|
|
|
+2. `mini/src/components/goods-list/index.tsx` - 确保传递正确的商品数据
|
|
|
|
|
+
|
|
|
|
|
+**相关页面更新**:
|
|
|
|
|
+1. `mini/src/pages/index/index.tsx` - 首页商品卡片数据传递
|
|
|
|
|
+2. `mini/src/pages/goods-list/index.tsx` - 商品列表页数据传递
|
|
|
|
|
+3. `mini/src/pages/search-result/index.tsx` - 搜索结果页数据传递
|
|
|
|
|
+
|
|
|
|
|
+**测试文件**:
|
|
|
|
|
+1. `mini/tests/unit/components/goods-card/goods-card.test.tsx` - 商品卡片多规格支持单元测试(新建)
|
|
|
|
|
+2. 更新现有页面测试,验证多规格商品添加购物车功能
|
|
|
|
|
+
|
|
|
|
|
+### 技术约束
|
|
|
|
|
+- **多租户兼容性**: 父子商品必须在同一租户下,API调用包含租户过滤
|
|
|
|
|
+- **API调用**: 规格选择器需要调用`/api/v1/goods/:id/children`获取子商品列表
|
|
|
|
|
+- **购物车兼容性**: 确保`CartContext.addToCart`支持父子商品参数
|
|
|
|
|
+- **性能考虑**: 商品列表页可能显示大量商品,避免不必要的API调用
|
|
|
|
|
+
|
|
|
|
|
+### 测试策略
|
|
|
|
|
+- **单元测试**: 测试商品卡片的规格选择逻辑,覆盖单规格和多规格场景
|
|
|
|
|
+- **集成测试**: 测试商品卡片在不同页面的集成,验证数据传递和API调用
|
|
|
|
|
+- **手动测试**: 在实际页面测试多规格商品的购物车添加流程
|
|
|
|
|
+
|
|
|
|
|
+## Testing
|
|
|
|
|
+### 测试标准 [Source: architecture/testing-strategy.md]
|
|
|
|
|
+- **测试文件位置**: `mini/tests/`目录下
|
|
|
|
|
+- **单元测试位置**: `tests/unit/**/*.test.{ts,tsx}`
|
|
|
|
|
+- **测试框架**: Jest + Testing Library + Taro模拟
|
|
|
|
|
+- **覆盖率要求**: 单元测试 ≥ 80%
|
|
|
|
|
+- **测试模式**: 使用测试数据工厂模式,避免硬编码测试数据
|
|
|
|
|
+
|
|
|
|
|
+### 测试策略要求
|
|
|
|
|
+- **单元测试**: 验证商品卡片组件的规格选择逻辑,包括状态管理、事件处理、条件渲染
|
|
|
|
|
+- **集成测试**: 验证商品卡片在不同页面的数据传递和交互
|
|
|
|
|
+- **API模拟**: 模拟商品API返回子商品列表数据
|
|
|
|
|
+- **用户交互测试**: 测试点击购物车图标、规格选择、添加购物车等用户交互
|
|
|
|
|
+
|
|
|
|
|
+### 测试场景设计
|
|
|
|
|
+1. **单规格商品场景**:
|
|
|
|
|
+ - 点击购物车图标直接添加到购物车
|
|
|
|
|
+ - 验证`onAddCart`回调被调用,传递正确的商品数据
|
|
|
|
|
+
|
|
|
|
|
+2. **多规格商品场景**:
|
|
|
|
|
+ - 点击购物车图标弹出规格选择器
|
|
|
|
|
+ - 规格选择器显示子商品列表
|
|
|
|
|
+ - 选择规格和数量后点击确定
|
|
|
|
|
+ - 验证`onAddCart`回调被调用,传递子商品数据和`parentGoodsId`
|
|
|
|
|
+
|
|
|
|
|
+3. **规格选择取消场景**:
|
|
|
|
|
+ - 点击购物车图标弹出规格选择器
|
|
|
|
|
+ - 点击取消或关闭弹窗
|
|
|
|
|
+ - 验证购物车添加没有被执行
|
|
|
|
|
+
|
|
|
|
|
+4. **数据传递验证**:
|
|
|
|
|
+ - 验证首页、商品列表页、搜索结果页传递正确的`hasSpecOptions`和`parentGoodsId`
|
|
|
|
|
+ - 验证商品卡片正确使用传递的数据
|
|
|
|
|
+
|
|
|
|
|
+### 测试数据管理
|
|
|
|
|
+- 使用测试数据工厂创建商品数据
|
|
|
|
|
+- 模拟单规格商品数据(`spuId: 0`, 无子商品)
|
|
|
|
|
+- 模拟多规格商品数据(`spuId: 0`, 有子商品列表)
|
|
|
|
|
+- 模拟子商品数据(`spuId: <父商品ID>`)
|
|
|
|
|
+
|
|
|
|
|
+## Change Log
|
|
|
|
|
+| Date | Version | Description | Author |
|
|
|
|
|
+|------|---------|-------------|--------|
|
|
|
|
|
+| 2025-12-15 | 1.0 | 初始故事创建,添加小程序商品卡片多规格支持 | John (Product Manager) |
|
|
|
|
|
+
|
|
|
|
|
+## Dev Agent Record
|
|
|
|
|
+*此部分由开发代理在实现过程中填写*
|
|
|
|
|
+
|
|
|
|
|
+### Agent Model Used
|
|
|
|
|
+*待填写*
|
|
|
|
|
+
|
|
|
|
|
+### Debug Log References
|
|
|
|
|
+*待填写*
|
|
|
|
|
+
|
|
|
|
|
+### Completion Notes List
|
|
|
|
|
+*待填写*
|
|
|
|
|
+
|
|
|
|
|
+### File List
|
|
|
|
|
+*待填写*
|
|
|
|
|
+
|
|
|
|
|
+## QA Results
|
|
|
|
|
+*此部分由QA代理在审查完成后填写*
|