|
@@ -0,0 +1,341 @@
|
|
|
|
|
+# Story 006.002: 父子商品管理UI体验优化
|
|
|
|
|
+
|
|
|
|
|
+## Status
|
|
|
|
|
+Draft
|
|
|
|
|
+
|
|
|
|
|
+## Story
|
|
|
|
|
+**As a** 系统管理员,
|
|
|
|
|
+**I want** 在创建和编辑商品时都能统一管理父子商品关系,
|
|
|
|
|
+**so that** 能够一次性完成商品和规格的配置,提高工作效率
|
|
|
|
|
+
|
|
|
|
|
+## Acceptance Criteria
|
|
|
|
|
+1. 在商品创建和编辑页面都添加统一的父子商品管理面板
|
|
|
|
|
+2. 面板智能支持创建模式和编辑模式的不同行为
|
|
|
|
|
+3. 创建模式:支持设为父商品、选择父商品、批量创建子商品规格模板
|
|
|
|
|
+4. 编辑模式:支持父子关系树展示、子商品管理、关系解除
|
|
|
|
|
+5. 将批量创建子商品功能整合到面板中,支持创建时批量创建
|
|
|
|
|
+6. 面板与表单数据实时同步,确保提交数据一致性
|
|
|
|
|
+7. 保持与现有功能的兼容性,平滑迁移用户体验
|
|
|
|
|
+
|
|
|
|
|
+## Tasks / Subtasks
|
|
|
|
|
+- [ ] **分析现有父子商品管理实现** (AC: 1, 2, 3, 4, 5, 6, 7)
|
|
|
|
|
+ - [ ] 分析GoodsManagement.tsx中的父子商品相关代码
|
|
|
|
|
+ - [ ] 分析GoodsChildSelector.tsx组件
|
|
|
|
|
+ - [ ] 分析BatchSpecCreator.tsx组件
|
|
|
|
|
+ - [ ] 识别创建模式和编辑模式的不同需求
|
|
|
|
|
+ - [ ] 分析表单数据同步机制
|
|
|
|
|
+
|
|
|
|
|
+- [ ] **设计父子商品管理面板组件架构** (AC: 1, 2, 3, 4)
|
|
|
|
|
+ - [ ] 设计GoodsParentChildPanel.tsx组件接口,支持创建/编辑模式
|
|
|
|
|
+ - [ ] 设计GoodsRelationshipTree.tsx组件(改进版)
|
|
|
|
|
+ - [ ] 设计ChildGoodsList.tsx组件
|
|
|
|
|
+ - [ ] 设计BatchSpecCreatorInline.tsx组件(支持创建模式模板)
|
|
|
|
|
+ - [ ] 设计表单数据同步机制
|
|
|
|
|
+
|
|
|
|
|
+- [ ] **创建父子商品管理面板组件** (AC: 1, 2, 3, 4)
|
|
|
|
|
+ - [ ] 创建GoodsParentChildPanel.tsx组件(支持双模式)
|
|
|
|
|
+ - [ ] 创建GoodsRelationshipTree.tsx组件(改进版)
|
|
|
|
|
+ - [ ] 创建ChildGoodsList.tsx组件
|
|
|
|
|
+ - [ ] 创建BatchSpecCreatorInline.tsx组件(支持模板保存)
|
|
|
|
|
+
|
|
|
|
|
+- [ ] **实现父子商品管理API** (AC: 4, 5)
|
|
|
|
|
+ - [ ] 创建`admin-goods-parent-child.mt.ts`自定义路由文件
|
|
|
|
|
+ - [ ] 实现GET /api/v1/goods/:id/children - 获取子商品列表
|
|
|
|
|
+ - [ ] 实现POST /api/v1/goods/:id/set-as-parent - 设为父商品
|
|
|
|
|
+ - [ ] 实现DELETE /api/v1/goods/:id/parent - 解除父子关系
|
|
|
|
|
+ - [ ] 实现POST /api/v1/goods/batch-create-children - 批量创建子商品(支持事务)
|
|
|
|
|
+ - [ ] 更新`admin-goods-routes.mt.ts`聚合基础CRUD和父子商品管理路由
|
|
|
|
|
+ - [ ] 确保API支持多租户隔离,保持`adminGoodsRoutesMt`名称不变
|
|
|
|
|
+
|
|
|
|
|
+- [ ] **集成父子商品管理面板到商品创建和编辑页面** (AC: 1, 5, 6, 7)
|
|
|
|
|
+ - [ ] 更新GoodsManagement.tsx集成新面板(创建和编辑模式)
|
|
|
|
|
+ - [ ] 移除原有的spuId/spuName表单字段和GoodsChildSelector
|
|
|
|
|
+ - [ ] 移除原有的批量创建按钮(整合到面板中)
|
|
|
|
|
+ - [ ] 实现面板与表单数据实时同步
|
|
|
|
|
+ - [ ] 更新创建和编辑提交逻辑,包含父子商品数据
|
|
|
|
|
+ - [ ] 确保向后兼容性
|
|
|
|
|
+
|
|
|
|
|
+- [ ] **编写单元测试和集成测试** (AC: 1, 2, 3, 4, 5, 6, 7)
|
|
|
|
|
+ - [ ] 测试创建模式的面板行为
|
|
|
|
|
+ - [ ] 测试编辑模式的面板行为
|
|
|
|
|
+ - [ ] 测试表单数据同步机制
|
|
|
|
|
+ - [ ] 测试批量创建子商品功能
|
|
|
|
|
+ - [ ] 测试完整的创建+配置流程
|
|
|
|
|
+ - [ ] 确保测试覆盖率 ≥ 80%
|
|
|
|
|
+ - [ ] 为GoodsParentChildPanel组件编写单元测试
|
|
|
|
|
+ - [ ] 为GoodsRelationshipTree组件编写单元测试
|
|
|
|
|
+ - [ ] 为ChildGoodsList组件编写单元测试
|
|
|
|
|
+ - [ ] 为BatchSpecCreatorInline组件编写单元测试
|
|
|
|
|
+ - [ ] 编写父子商品管理功能集成测试
|
|
|
|
|
+ - [ ] 确保测试覆盖率 ≥ 80%
|
|
|
|
|
+
|
|
|
|
|
+## 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 (服务端状态管理)
|
|
|
|
|
+- **测试框架**: Vitest 2.x (单元测试框架,更好的TypeORM支持)
|
|
|
|
|
+- **API测试**: hono/testing (内置,API端点测试,更好的类型安全)
|
|
|
|
|
+
|
|
|
|
|
+### 项目结构信息 [Source: architecture/source-tree.md]
|
|
|
|
|
+- **包管理**: 使用pnpm workspace管理多包依赖关系
|
|
|
|
|
+- **包架构层次**:
|
|
|
|
|
+ - **基础设施层**: shared-types → shared-utils → shared-crud
|
|
|
|
|
+ - **测试基础设施**: shared-test-util
|
|
|
|
|
+ - **业务模块层**: 多租户模块包(-mt后缀),支持租户数据隔离
|
|
|
|
|
+ - **应用层**: server (重构后)
|
|
|
|
|
+- **多租户架构**:
|
|
|
|
|
+ - **包复制策略**: 基于Epic-007方案,通过复制单租户包创建多租户版本
|
|
|
|
|
+ - **租户隔离**: 通过租户ID实现数据隔离,支持多租户部署
|
|
|
|
|
+ - **后端包**: 10个多租户模块包,支持租户数据隔离
|
|
|
|
|
+- **文件命名**: 保持现有kebab-case命名约定
|
|
|
|
|
+- **模块化架构**: 采用分层包结构,支持按需安装和独立开发
|
|
|
|
|
+
|
|
|
|
|
+### 编码标准 [Source: architecture/coding-standards.md]
|
|
|
|
|
+- **代码风格**: TypeScript严格模式,一致的缩进和命名
|
|
|
|
|
+- **测试位置**: `__tests__` 文件夹与源码并列(但实际使用`tests/`目录)
|
|
|
|
|
+- **覆盖率目标**: 核心业务逻辑 > 80%
|
|
|
|
|
+- **测试类型**: 单元测试、集成测试、E2E测试
|
|
|
|
|
+- **现有API兼容性**: 确保测试不破坏现有API契约
|
|
|
|
|
+- **数据库集成**: 使用测试数据库,避免污染生产数据
|
|
|
|
|
+
|
|
|
|
|
+### 现有代码分析
|
|
|
|
|
+**基于故事006.001的实现**:
|
|
|
|
|
+1. **GoodsManagement.tsx** (835行)
|
|
|
|
|
+ - 包含spuId/spuName字段表单控件
|
|
|
|
|
+ - 包含GoodsChildSelector组件
|
|
|
|
|
+ - 包含BatchSpecCreator弹窗
|
|
|
|
|
+ - 问题:父子商品管理功能分散
|
|
|
|
|
+
|
|
|
|
|
+2. **GoodsChildSelector.tsx** (214行)
|
|
|
|
|
+ - 子商品选择器组件
|
|
|
|
|
+ - 支持搜索、选择、租户过滤
|
|
|
|
|
+ - 问题:与批量创建功能分离
|
|
|
|
|
+
|
|
|
|
|
+3. **BatchSpecCreator.tsx** (424行)
|
|
|
|
|
+ - 批量创建子商品组件
|
|
|
|
|
+ - 支持多规格创建
|
|
|
|
|
+ - 问题:独立弹窗,与商品编辑流程分离
|
|
|
|
|
+
|
|
|
|
|
+### 组件设计
|
|
|
|
|
+**GoodsParentChildPanel.tsx** (支持创建和编辑模式):
|
|
|
|
|
+```tsx
|
|
|
|
|
+interface GoodsParentChildPanelProps {
|
|
|
|
|
+ // 基础属性
|
|
|
|
|
+ mode: 'create' | 'edit'; // 创建模式或编辑模式
|
|
|
|
|
+ goodsId?: number; // 当前商品ID(编辑模式)
|
|
|
|
|
+ goodsName?: string; // 当前商品名称
|
|
|
|
|
+
|
|
|
|
|
+ // 父子商品数据(双向绑定)
|
|
|
|
|
+ spuId?: number; // 父商品ID
|
|
|
|
|
+ spuName?: string; // 父商品名称
|
|
|
|
|
+ childGoodsIds?: number[]; // 子商品ID列表
|
|
|
|
|
+ batchSpecs?: BatchSpecTemplate[]; // 批量创建规格模板(创建模式)
|
|
|
|
|
+
|
|
|
|
|
+ // 回调函数
|
|
|
|
|
+ onDataChange?: (data: ParentChildData) => void; // 数据变化回调
|
|
|
|
|
+ onUpdate?: () => void; // 更新回调(编辑模式)
|
|
|
|
|
+
|
|
|
|
|
+ // 其他
|
|
|
|
|
+ tenantId?: number; // 租户ID
|
|
|
|
|
+ disabled?: boolean; // 是否禁用
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+interface ParentChildData {
|
|
|
|
|
+ spuId: number;
|
|
|
|
|
+ spuName: string | null;
|
|
|
|
|
+ childGoodsIds: number[];
|
|
|
|
|
+ batchSpecs?: BatchSpecTemplate[];
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+interface BatchSpecTemplate {
|
|
|
|
|
+ name: string;
|
|
|
|
|
+ price: number;
|
|
|
|
|
+ costPrice: number;
|
|
|
|
|
+ stock: number;
|
|
|
|
|
+ sort: number;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+enum PanelMode {
|
|
|
|
|
+ VIEW = 'view', // 查看模式
|
|
|
|
|
+ BATCH_CREATE = 'batch', // 批量创建模式
|
|
|
|
|
+ MANAGE_CHILDREN = 'manage' // 管理子商品模式
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+**GoodsRelationshipTree.tsx**:
|
|
|
|
|
+- 树状结构展示父子关系
|
|
|
|
|
+- 支持展开/收起子商品
|
|
|
|
|
+- 点击子商品跳转到编辑页面
|
|
|
|
|
+
|
|
|
|
|
+**ChildGoodsList.tsx**:
|
|
|
|
|
+- 子商品列表(分页)
|
|
|
|
|
+- 编辑/删除子商品操作
|
|
|
|
|
+- 解除父子关系功能
|
|
|
|
|
+
|
|
|
|
|
+**BatchSpecCreatorInline.tsx**:
|
|
|
|
|
+- 内联版本的批量创建组件
|
|
|
|
|
+- 实时预览创建的子商品
|
|
|
|
|
+- 创建后自动刷新关系树
|
|
|
|
|
+
|
|
|
|
|
+### API设计
|
|
|
|
|
+**在现有`adminGoodsRoutesMt`中聚合父子商品管理API**:
|
|
|
|
|
+
|
|
|
|
|
+**实现方案**:
|
|
|
|
|
+1. **创建`admin-goods-parent-child.mt.ts`**: 基于`public-goods-random.mt.ts`模式创建自定义路由
|
|
|
|
|
+2. **更新`admin-goods-routes.mt.ts`**: 聚合基础CRUD路由和父子商品管理路由
|
|
|
|
|
+3. **保持`adminGoodsRoutesMt`名称不变**: 前端代码无需修改
|
|
|
|
|
+
|
|
|
|
|
+**API端点** (聚合到现有`adminGoodsRoutesMt`):
|
|
|
|
|
+1. `GET /api/v1/goods/:id/children`
|
|
|
|
|
+ - 获取指定父商品的子商品列表
|
|
|
|
|
+ - 支持分页、搜索、多租户过滤
|
|
|
|
|
+
|
|
|
|
|
+2. `POST /api/v1/goods/:id/set-as-parent`
|
|
|
|
|
+ - 将普通商品设为父商品
|
|
|
|
|
+ - 验证:商品存在、非子商品、租户一致
|
|
|
|
|
+ - 操作:设置`spuId = 0`, `spuName = null`
|
|
|
|
|
+
|
|
|
|
|
+3. `DELETE /api/v1/goods/:id/parent`
|
|
|
|
|
+ - 解除子商品与父商品的关联
|
|
|
|
|
+ - 验证:商品存在且是子商品
|
|
|
|
|
+ - 操作:设置`spuId = 0`, `spuName = null`
|
|
|
|
|
+
|
|
|
|
|
+4. `POST /api/v1/goods/batch-create-children`
|
|
|
|
|
+ - 批量创建子商品(支持事务)
|
|
|
|
|
+ - 参数:`parentGoodsId`, `specs`数组
|
|
|
|
|
+ - 继承:子商品继承父商品的分类、供应商、商户等信息
|
|
|
|
|
+
|
|
|
|
|
+**前端使用** (无需修改):
|
|
|
|
|
+```typescript
|
|
|
|
|
+// 现有代码继续工作
|
|
|
|
|
+import { adminGoodsRoutesMt } from '@d8d/goods-module-mt';
|
|
|
|
|
+const goodsClient = hc<typeof adminGoodsRoutesMt>('/api/v1/goods');
|
|
|
|
|
+
|
|
|
|
|
+// 原有调用
|
|
|
|
|
+await goodsClient.index.$get({ query: { page: 1 } });
|
|
|
|
|
+
|
|
|
|
|
+// 新增调用
|
|
|
|
|
+await goodsClient[':id'].children.$get({ param: { id: 123 } });
|
|
|
|
|
+await goodsClient[':id'].setAsParent.$post({ param: { id: 123 } });
|
|
|
|
|
+await goodsClient[':id'].parent.$delete({ param: { id: 456 } });
|
|
|
|
|
+await goodsClient.batchCreateChildren.$post({ json: { ... } });
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+**优势**:
|
|
|
|
|
+- **零前端修改**: 保持`adminGoodsRoutesMt`名称和导入方式不变
|
|
|
|
|
+- **功能完整**: 单一客户端包含所有商品管理功能
|
|
|
|
|
+- **符合现有模式**: 基于`credit-balance-module-mt`聚合模式
|
|
|
|
|
+- **职责分离**: 父子商品管理逻辑独立,便于维护和测试
|
|
|
|
|
+
|
|
|
|
|
+### 集成方案
|
|
|
|
|
+**在GoodsManagement.tsx中的集成** (创建和编辑模式):
|
|
|
|
|
+```tsx
|
|
|
|
|
+// 在商品表单之后添加面板(创建和编辑都显示)
|
|
|
|
|
+<div className="mt-6 pt-6 border-t">
|
|
|
|
|
+ <GoodsParentChildPanel
|
|
|
|
|
+ mode={isCreateForm ? 'create' : 'edit'}
|
|
|
|
|
+ goodsId={isCreateForm ? undefined : editingGoods?.id}
|
|
|
|
|
+ goodsName={isCreateForm ? createForm.watch('name') : editingGoods?.name}
|
|
|
|
|
+ spuId={isCreateForm ? createForm.watch('spuId') : editingGoods?.spuId}
|
|
|
|
|
+ spuName={isCreateForm ? createForm.watch('spuName') : editingGoods?.spuName}
|
|
|
|
|
+ childGoodsIds={isCreateForm ? createForm.watch('childGoodsIds') : editingGoods?.childGoods?.map(c => c.id) || []}
|
|
|
|
|
+ onDataChange={(data) => {
|
|
|
|
|
+ // 实时同步数据到表单
|
|
|
|
|
+ if (isCreateForm) {
|
|
|
|
|
+ createForm.setValue('spuId', data.spuId);
|
|
|
|
|
+ createForm.setValue('spuName', data.spuName);
|
|
|
|
|
+ createForm.setValue('childGoodsIds', data.childGoodsIds);
|
|
|
|
|
+ // 保存批量创建模板
|
|
|
|
|
+ setBatchSpecs(data.batchSpecs || []);
|
|
|
|
|
+ }
|
|
|
|
|
+ }}
|
|
|
|
|
+ onUpdate={refetch}
|
|
|
|
|
+ />
|
|
|
|
|
+</div>
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+**提交逻辑调整**:
|
|
|
|
|
+```tsx
|
|
|
|
|
+const handleSubmit = (data: CreateRequest | UpdateRequest) => {
|
|
|
|
|
+ // 合并表单数据和面板数据
|
|
|
|
|
+ const submitData = {
|
|
|
|
|
+ ...data,
|
|
|
|
|
+ spuId: parentChildData?.spuId || 0,
|
|
|
|
|
+ spuName: parentChildData?.spuName || null,
|
|
|
|
|
+ childGoodsIds: parentChildData?.childGoodsIds || [],
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ if (isCreateForm) {
|
|
|
|
|
+ // 创建模式:可能需要批量创建子商品
|
|
|
|
|
+ createMutation.mutate(submitData, {
|
|
|
|
|
+ onSuccess: (result) => {
|
|
|
|
|
+ // 如果创建成功且有批量创建模板,创建子商品
|
|
|
|
|
+ if (batchSpecs.length > 0 && result.id) {
|
|
|
|
|
+ batchCreateChildren(result.id, batchSpecs);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+ } else if (editingGoods) {
|
|
|
|
|
+ updateMutation.mutate({ id: editingGoods.id, data: submitData as UpdateRequest });
|
|
|
|
|
+ }
|
|
|
|
|
+};
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+**移除原有功能**:
|
|
|
|
|
+- 移除编辑表单中的spuId/spuName字段
|
|
|
|
|
+- 移除编辑表单中的GoodsChildSelector组件
|
|
|
|
|
+- 移除商品列表中的批量创建按钮
|
|
|
|
|
+- 将功能全部整合到面板中
|
|
|
|
|
+
|
|
|
|
|
+### 技术约束
|
|
|
|
|
+- **多租户支持**: 所有操作必须包含tenantId过滤
|
|
|
|
|
+- **数据一致性**: 父子商品必须在同一租户下
|
|
|
|
|
+- **向后兼容**: 保持现有spuId/spuName字段的兼容性
|
|
|
|
|
+- **性能考虑**: 子商品列表查询使用分页,避免性能问题
|
|
|
|
|
+
|
|
|
|
|
+### 测试要求
|
|
|
|
|
+- **单元测试**: 测试面板组件、关系树组件、子商品列表组件
|
|
|
|
|
+- **集成测试**: 测试完整的父子商品管理流程
|
|
|
|
|
+- **兼容性测试**: 确保与现有功能兼容
|
|
|
|
|
+- **覆盖率**: 核心业务逻辑必须达到80%以上单元测试覆盖率
|
|
|
|
|
+
|
|
|
|
|
+## Testing
|
|
|
|
|
+### 测试标准 [Source: architecture/testing-strategy.md]
|
|
|
|
|
+- **测试文件位置**: `packages/goods-management-ui-mt/tests/` 目录下
|
|
|
|
|
+- **单元测试位置**: `tests/unit/**/*.test.{ts,tsx}`
|
|
|
|
|
+- **集成测试位置**: `tests/integration/**/*.test.{ts,tsx}`
|
|
|
|
|
+- **测试框架**: Vitest + Testing Library + hono/testing + shared-test-util
|
|
|
|
|
+- **覆盖率要求**: 单元测试 ≥ 80%,集成测试 ≥ 60%
|
|
|
|
|
+- **测试模式**: 使用测试数据工厂模式,避免硬编码测试数据
|
|
|
|
|
+- **数据库测试**: 使用专用测试数据库,事务回滚机制
|
|
|
|
|
+
|
|
|
|
|
+### 测试策略要求
|
|
|
|
|
+- **单元测试**: 验证面板组件渲染、模式切换、关系树展示
|
|
|
|
|
+- **集成测试**: 验证完整的父子商品管理流程、API调用、状态同步
|
|
|
|
|
+- **兼容性测试**: 验证与现有spuId/spuName字段的兼容性
|
|
|
|
|
+- **错误处理测试**: 测试各种错误场景和异常情况
|
|
|
|
|
+- **UI交互测试**: 测试用户交互、状态管理、数据更新
|
|
|
|
|
+
|
|
|
|
|
+## Change Log
|
|
|
|
|
+| Date | Version | Description | Author |
|
|
|
|
|
+|------|---------|-------------|--------|
|
|
|
|
|
+| 2025-12-09 | 1.0 | 初始故事创建 | John (Product Manager) |
|
|
|
|
|
+
|
|
|
|
|
+## Status
|
|
|
|
|
+⏳ Pending
|
|
|
|
|
+
|
|
|
|
|
+### 完成状态
|
|
|
|
|
+- [ ] 所有功能实现完成
|
|
|
|
|
+- [ ] 所有单元测试通过
|
|
|
|
|
+- [ ] 所有集成测试通过
|
|
|
|
|
+- [ ] 代码已提交并推送到远程仓库
|
|
|
|
|
+- [ ] 故事验收标准全部满足
|
|
|
|
|
+
|
|
|
|
|
+## QA Results
|
|
|
|
|
+*此部分由QA代理在审查完成后填写*
|