|
@@ -0,0 +1,346 @@
|
|
|
|
|
+# 故事 018.001 - 修复残疾人照片上传保存功能
|
|
|
|
|
+
|
|
|
|
|
+## Status
|
|
|
|
|
+Approved
|
|
|
|
|
+
|
|
|
|
|
+## Story
|
|
|
|
|
+**作为** 残疾人信息管理员
|
|
|
|
|
+**我希望** 上传的残疾人照片能够成功保存
|
|
|
|
|
+**以便** 下次查看时照片仍然存在,无需重复上传
|
|
|
|
|
+
|
|
|
|
|
+## Acceptance Criteria
|
|
|
|
|
+1. 首次上传残疾人照片后提交,照片能够成功保存到数据库
|
|
|
|
|
+2. 再次编辑残疾人信息时,已上传的照片能够正常显示
|
|
|
|
|
+3. 重新上传照片后能够成功保存,并正确更新数据库记录
|
|
|
|
|
+4. 照片上传失败时能够显示明确的错误提示信息
|
|
|
|
|
+5. 支持常见的图片格式(JPG、PNG、GIF、BMP、WebP等)
|
|
|
|
|
+6. 照片文件大小限制合理,不超过500MB
|
|
|
|
|
+7. 照片信息正确关联到残疾人记录(person_id)
|
|
|
|
|
+8. 照片文件信息正确保存到files表(file_id关联)
|
|
|
|
|
+
|
|
|
|
|
+## Tasks / Subtasks
|
|
|
|
|
+
|
|
|
|
|
+### Task 1: 排查照片上传保存问题根因 (AC: 1, 3, 7, 8)
|
|
|
|
|
+- [ ] 检查 `DisabilityPersonManagement.tsx` 中照片数据提交逻辑
|
|
|
|
|
+- [ ] 验证前端表单提交时 `photos` 字段是否正确传递给后端API
|
|
|
|
|
+- [ ] 检查 `createPhotos` 和 `updatePhotos` 状态在表单提交时的处理
|
|
|
|
|
+- [ ] 查看后端API接收和处理照片数据的代码
|
|
|
|
|
+- [ ] 检查 `disabled-person-crud.routes.ts` 中创建和更新残疾人时的照片处理逻辑
|
|
|
|
|
+- [ ] 验证数据库事务是否正确提交(包括残疾人信息和照片信息)
|
|
|
|
|
+
|
|
|
|
|
+### Task 2: 检查照片回显逻辑 (AC: 2)
|
|
|
|
|
+- [ ] 检查编辑残疾人信息时,后端API是否返回照片数据
|
|
|
|
|
+- [ ] 验证 `disabled-person-custom.routes.ts` 中获取残疾人详情时的照片关联查询
|
|
|
|
|
+- [ ] 检查前端 `updatePhotos` 状态是否正确填充后端返回的照片数据
|
|
|
|
|
+- [ ] 验证 `PhotoUploadField` 组件回显已上传照片的功能
|
|
|
|
|
+- [ ] 检查照片文件URL是否正确生成(通过 `file-module` API)
|
|
|
|
|
+
|
|
|
|
|
+### Task 3: 添加照片上传错误处理 (AC: 4)
|
|
|
|
|
+- [ ] 在前端添加照片上传失败时的错误捕获和提示
|
|
|
|
|
+- [ ] 在后端添加照片数据验证,确保必要字段(personId, fileId, photoType)存在
|
|
|
|
|
+- [ ] 添加文件ID有效性验证(检查file是否存在)
|
|
|
|
|
+- [ ] 添加重复照片检测(同一personId + photoType组合)
|
|
|
|
|
+- [ ] 在Schema中添加照片数据的验证规则
|
|
|
|
|
+
|
|
|
|
|
+### Task 4: 验证照片文件类型和大小限制 (AC: 5, 6)
|
|
|
|
|
+- [ ] 检查 `PhotoUploadField` 组件的 `accept` 属性配置
|
|
|
|
|
+- [ ] 验证 `FileSelector` 组件的文件类型验证
|
|
|
|
|
+- [ ] 确认文件大小限制配置(建议不超过500MB)
|
|
|
|
|
+- [ ] 添加超出限制时的友好错误提示
|
|
|
|
|
+
|
|
|
|
|
+### Task 5: 修复发现的问题 (AC: 1, 2, 3)
|
|
|
|
|
+- [ ] 根据排查结果,修复前端或后端代码中导致照片保存失败的问题
|
|
|
|
|
+- [ ] 确保照片数据正确传递到后端(检查API请求体结构)
|
|
|
|
|
+- [ ] 确保后端正确处理和保存照片数据(检查数据库操作)
|
|
|
|
|
+- [ ] 确保照片回显时正确加载和显示(检查数据获取和组件渲染)
|
|
|
|
|
+
|
|
|
|
|
+### Task 6: 单元测试 (AC: 所有)
|
|
|
|
|
+- [ ] 为照片上传保存逻辑添加单元测试
|
|
|
|
|
+- [ ] 测试照片数据正确提交到后端API
|
|
|
|
|
+- [ ] 测试照片回显正确加载已保存的照片
|
|
|
|
|
+- [ ] 测试错误处理和用户提示
|
|
|
|
|
+
|
|
|
|
|
+### Task 7: 集成测试 (AC: 1, 2, 3)
|
|
|
|
|
+- [ ] 编写端到端集成测试:创建残疾人并上传照片
|
|
|
|
|
+- [ ] 编写端到端集成测试:编辑残疾人并查看已上传照片
|
|
|
|
|
+- [ ] 编写端到端集成测试:重新上传照片并验证更新
|
|
|
|
|
+- [ ] 验证数据库中 `disabled_photo` 表记录正确
|
|
|
|
|
+- [ ] 验证照片与残疾人的关联关系正确
|
|
|
|
|
+
|
|
|
|
|
+### Task 8: 手动测试验证
|
|
|
|
|
+- [ ] 手动测试:创建残疾人,上传多张不同类型照片,提交后验证保存成功
|
|
|
|
|
+- [ ] 手动测试:重新进入编辑页面,验证照片正常显示
|
|
|
|
|
+- [ ] 手动测试:删除已有照片,重新上传,验证更新成功
|
|
|
|
|
+- [ ] 手动测试:上传超大文件,验证错误提示
|
|
|
|
|
+- [ ] 手动测试:上传不支持的文件类型,验证错误提示
|
|
|
|
|
+
|
|
|
|
|
+## Dev Notes
|
|
|
|
|
+
|
|
|
|
|
+### 数据模型信息
|
|
|
|
|
+[Source: allin-packages/disability-module/src/entities/disabled-photo.entity.ts]
|
|
|
|
|
+
|
|
|
|
|
+**DisabledPhoto Entity**:
|
|
|
|
|
+- `id` (photo_id): 主键,自增整数
|
|
|
|
|
+- `personId` (person_id): 残疾人ID,外键关联 `disabled_person.person_id`,`onDelete: CASCADE`
|
|
|
|
|
+- `photoType` (photo_type): 照片类型,varchar(50),如:身份证照片、残疾证照片、个人照片、其他照片
|
|
|
|
|
+- `fileId` (file_id): 文件ID,外键关联 `files.file_id`,`onDelete: CASCADE`
|
|
|
|
|
+- `uploadTime` (upload_time): 上传时间,timestamp,默认 `CURRENT_TIMESTAMP`
|
|
|
|
|
+- `canDownload` (can_download): 是否可下载,smallint,默认1(是)
|
|
|
|
|
+
|
|
|
|
|
+**关系**:
|
|
|
|
|
+- 多对一关联 `DisabledPerson` (person → person.photos)
|
|
|
|
|
+- 多对一关联 `File` (file → file模块的File实体)
|
|
|
|
|
+
|
|
|
|
|
+**DisabledPerson Entity** (相关字段):
|
|
|
|
|
+[Source: allin-packages/disability-module/src/entities/disabled-person.entity.ts]
|
|
|
|
|
+- 包含 `@OneToMany` 关系:`photos!: DisabledPhoto[]`
|
|
|
|
|
+
|
|
|
|
|
+### 前端组件结构
|
|
|
|
|
+[Source: allin-packages/disability-person-management-ui/src/components/]
|
|
|
|
|
+
|
|
|
|
|
+**PhotoUploadField 组件**:
|
|
|
|
|
+- 文件位置: `PhotoUploadField.tsx`
|
|
|
|
|
+- Props接口:
|
|
|
|
|
+ ```typescript
|
|
|
|
|
+ export interface PhotoItem {
|
|
|
|
|
+ photoType: string;
|
|
|
|
|
+ fileId: number | null;
|
|
|
|
|
+ canDownload: number;
|
|
|
|
|
+ tempId?: string; // 临时ID用于React key
|
|
|
|
|
+ }
|
|
|
|
|
+ export interface PhotoUploadFieldProps {
|
|
|
|
|
+ value?: PhotoItem[];
|
|
|
|
|
+ onChange?: (photos: PhotoItem[]) => void;
|
|
|
|
|
+ photoTypes?: string[];
|
|
|
|
|
+ maxPhotos?: number;
|
|
|
|
|
+ }
|
|
|
|
|
+ ```
|
|
|
|
|
+- 支持的功能: 添加照片、删除照片、选择照片类型、选择文件、设置可下载权限
|
|
|
|
|
+- 使用 `FileSelector` 组件选择文件(来自 `@d8d/file-management-ui`)
|
|
|
|
|
+
|
|
|
|
|
+**DisabilityPersonManagement 组件**:
|
|
|
|
|
+- 文件位置: `DisabilityPersonManagement.tsx`
|
|
|
|
|
+- 状态管理:
|
|
|
|
|
+ ```typescript
|
|
|
|
|
+ const [createPhotos, setCreatePhotos] = useState<PhotoItem[]>([]);
|
|
|
|
|
+ const [updatePhotos, setUpdatePhotos] = useState<PhotoItem[]>([]);
|
|
|
|
|
+ ```
|
|
|
|
|
+- 使用 `react-hook-form` 管理表单
|
|
|
|
|
+- 使用 `@tanstack/react-query` 进行数据获取和提交
|
|
|
|
|
+
|
|
|
|
|
+### API客户端
|
|
|
|
|
+[Source: allin-packages/disability-person-management-ui/src/api/disabilityClient.ts]
|
|
|
|
|
+
|
|
|
|
|
+**disabilityClientManager**:
|
|
|
|
|
+- 提供创建和更新残疾人的API方法
|
|
|
|
|
+- 需要验证请求体中是否正确包含 `photos` 字段
|
|
|
|
|
+
|
|
|
|
|
+### 后端路由
|
|
|
|
|
+[Source: allin-packages/disability-module/src/routes/]
|
|
|
|
|
+
|
|
|
|
|
+**disabled-person-crud.routes.ts**:
|
|
|
|
|
+- 通用CRUD路由,继承自 `GenericCrudService`
|
|
|
|
|
+- 创建和更新操作需要处理 `photos` 关联数据
|
|
|
|
|
+
|
|
|
|
|
+**disabled-person-custom.routes.ts**:
|
|
|
|
|
+- 自定义路由,可能包含获取残疾人详情的接口
|
|
|
|
|
+- 需要验证是否正确查询并返回照片关联数据
|
|
|
|
|
+
|
|
|
|
|
+### Schema验证
|
|
|
|
|
+[Source: allin-packages/disability-module/src/schemas/]
|
|
|
|
|
+
|
|
|
|
|
+**CreateDisabledPersonSchema / UpdateDisabledPersonSchema**:
|
|
|
|
|
+- 需要验证Schema中是否包含 `photos` 字段定义
|
|
|
|
|
+- 照片数据应该是一个数组,包含 `photoType`, `fileId`, `canDownload`
|
|
|
|
|
+
|
|
|
|
|
+### 文件模块集成
|
|
|
|
|
+[Source: packages/file-module/]
|
|
|
|
|
+
|
|
|
|
|
+**File Entity**:
|
|
|
|
|
+- `fileId`: 文件ID
|
|
|
|
|
+- `name`: 文件名
|
|
|
|
|
+- `path`: 存储路径
|
|
|
|
|
+- `size`: 文件大小
|
|
|
|
|
+- `type`: MIME类型
|
|
|
|
|
+
|
|
|
|
|
+**MinIO Service**:
|
|
|
|
|
+- 文件上传策略生成
|
|
|
|
|
+- 预签名URL获取
|
|
|
|
|
+- 文件下载
|
|
|
|
|
+
|
|
|
|
|
+**前端集成**:
|
|
|
|
|
+- `FileSelector` 组件来自 `@d8d/file-management-ui`
|
|
|
|
|
+- 支持文件预览、文件选择和上传
|
|
|
|
|
+
|
|
|
|
|
+### 地区选择组件
|
|
|
|
|
+[Source: packages/area-management-ui/]
|
|
|
|
|
+
|
|
|
|
|
+**ProvinceSelect / AreaSelectForm**:
|
|
|
|
|
+- 省市区级联选择器
|
|
|
|
|
+- 可能与地区选择性能问题相关(018-03故事)
|
|
|
|
|
+
|
|
|
|
|
+### 测试要求
|
|
|
|
|
+
|
|
|
|
|
+#### 单元测试
|
|
|
|
|
+- [Source: docs/architecture/testing-strategy.md]
|
|
|
|
|
+- 使用 Vitest 测试框架
|
|
|
|
|
+- 测试文件位置: `allin-packages/disability-person-management-ui/tests/unit/`
|
|
|
|
|
+- 已有测试: `PhotoUploadField.test.tsx`
|
|
|
|
|
+- 覆盖率目标: ≥ 80%
|
|
|
|
|
+
|
|
|
|
|
+#### 集成测试
|
|
|
|
|
+- [Source: docs/architecture/testing-strategy.md]
|
|
|
|
|
+- 使用 Vitest + Testing Library
|
|
|
|
|
+- 测试文件位置: `allin-packages/disability-person-management-ui/tests/integration/`
|
|
|
|
|
+- 已有测试: `disability-person.integration.test.tsx`
|
|
|
|
|
+- 需要测试照片上传、保存、回显的完整流程
|
|
|
|
|
+- 覆盖率目标: ≥ 60%
|
|
|
|
|
+
|
|
|
|
|
+#### E2E测试
|
|
|
|
|
+- [Source: docs/architecture/testing-strategy.md]
|
|
|
|
|
+- 使用 Playwright
|
|
|
|
|
+- 测试文件位置: `web/tests/e2e/`
|
|
|
|
|
+- 需要测试完整的用户操作流程
|
|
|
|
|
+
|
|
|
|
|
+### 调试技巧
|
|
|
|
|
+[Source: docs/architecture/testing-strategy.md]
|
|
|
|
|
+
|
|
|
|
|
+**查看测试详情**:
|
|
|
|
|
+```bash
|
|
|
|
|
+# 运行特定测试查看详细信息
|
|
|
|
|
+pnpm test --testNamePattern "照片上传"
|
|
|
|
|
+
|
|
|
|
|
+# 在disability-person-management-ui目录下
|
|
|
|
|
+cd allin-packages/disability-person-management-ui
|
|
|
|
|
+pnpm test --testNamePattern "照片"
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+**表单调试**:
|
|
|
|
|
+- 在表单 `form.handleSubmit` 的第二个参数中加 `console.debug` 查看表单验证错误:
|
|
|
|
|
+ ```typescript
|
|
|
|
|
+ form.handleSubmit(handleSubmit, (errors) => console.debug('表单验证错误:', errors))
|
|
|
|
|
+ ```
|
|
|
|
|
+
|
|
|
|
|
+**E2E测试失败调试**:
|
|
|
|
|
+- 查看页面结构: `cat test-results/**/error-context.md`
|
|
|
|
|
+- 使用调试模式: `pnpm test:e2e:chromium --debug`
|
|
|
|
|
+
|
|
|
|
|
+### 编码标准
|
|
|
|
|
+[Source: docs/architecture/coding-standards.md]
|
|
|
|
|
+
|
|
|
|
|
+**TypeScript严格模式**: 所有代码必须启用严格类型检查
|
|
|
|
|
+**命名约定**:
|
|
|
|
|
+- 文件名: kebab-case (如: `photo-upload-field.tsx`)
|
|
|
|
|
+- 类名: PascalCase (如: `PhotoUploadField`)
|
|
|
|
|
+- 函数/变量: camelCase (如: `handlePhotoUpload`)
|
|
|
|
|
+- 接口: PascalCase,无I前缀 (如: `PhotoItem`)
|
|
|
|
|
+
|
|
|
|
|
+**UI包开发规范**:
|
|
|
|
|
+- [Source: docs/architecture/ui-package-standards.md]
|
|
|
|
|
+- API路径映射验证: 确保故事中的API路径与实际后端路由一致
|
|
|
|
|
+- 类型推断最佳实践: 使用RPC推断类型,而不是直接导入schema类型
|
|
|
|
|
+- 测试选择器优化: 为关键交互元素添加 `data-testid` 属性
|
|
|
|
|
+- 表单组件模式: 使用条件渲染两个独立的Form组件(创建/编辑)
|
|
|
|
|
+
|
|
|
|
|
+**参考实现**:
|
|
|
|
|
+- 残疾人管理UI包: `allin-packages/disability-person-management-ui`
|
|
|
|
|
+- 文件管理UI包: `packages/file-management-ui`
|
|
|
|
|
+
|
|
|
|
|
+### 项目结构
|
|
|
|
|
+[Source: docs/architecture/source-tree.md]
|
|
|
|
|
+
|
|
|
|
|
+**残疾人相关包**:
|
|
|
|
|
+```
|
|
|
|
|
+allin-packages/
|
|
|
|
|
+├── disability-module/ # 残疾人后端模块
|
|
|
|
|
+│ ├── src/
|
|
|
|
|
+│ │ ├── entities/ # 数据实体
|
|
|
|
|
+│ │ │ ├── disabled-person.entity.ts
|
|
|
|
|
+│ │ │ ├── disabled-photo.entity.ts
|
|
|
|
|
+│ │ │ └── ...
|
|
|
|
|
+│ │ ├── routes/ # API路由
|
|
|
|
|
+│ │ │ ├── disabled-person-crud.routes.ts
|
|
|
|
|
+│ │ │ └── disabled-person-custom.routes.ts
|
|
|
|
|
+│ │ ├── schemas/ # 验证Schema
|
|
|
|
|
+│ │ └── services/ # 业务逻辑
|
|
|
|
|
+│ └── tests/
|
|
|
|
|
+└── disability-person-management-ui/ # 残疾人前端UI
|
|
|
|
|
+ ├── src/
|
|
|
|
|
+ │ ├── components/ # UI组件
|
|
|
|
|
+ │ │ ├── DisabilityPersonManagement.tsx
|
|
|
|
|
+ │ │ ├── PhotoUploadField.tsx
|
|
|
|
|
+ │ │ └── ...
|
|
|
|
|
+ │ └── api/ # API客户端
|
|
|
|
|
+ └── tests/ # 测试文件
|
|
|
|
|
+ ├── unit/
|
|
|
|
|
+ └── integration/
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+**共享包**:
|
|
|
|
|
+- `packages/file-module`: 文件管理模块
|
|
|
|
|
+- `packages/file-management-ui`: 文件管理UI
|
|
|
|
|
+- `packages/geo-areas`: 地理区域模块
|
|
|
|
|
+
|
|
|
|
|
+### 技术栈
|
|
|
|
|
+[Source: docs/architecture/tech-stack.md]
|
|
|
|
|
+
|
|
|
|
|
+- **运行时**: Node.js 20.18.3
|
|
|
|
|
+- **前端框架**: React 19.1.0
|
|
|
|
|
+- **构建工具**: Vite 7.0.0
|
|
|
|
|
+- **数据库**: PostgreSQL 17 + TypeORM 0.3.25
|
|
|
|
|
+- **状态管理**: React Query 5.83.0
|
|
|
|
|
+- **表单管理**: react-hook-form + zod
|
|
|
|
|
+- **测试框架**: Vitest 3.2.4
|
|
|
|
|
+- **文件存储**: MinIO
|
|
|
|
|
+
|
|
|
|
|
+### 可能的问题点
|
|
|
|
|
+
|
|
|
|
|
+#### 前端问题
|
|
|
|
|
+1. **表单提交时照片数据未正确传递**: 检查 `createForm.handleSubmit` 和 `updateForm.handleSubmit` 中的数据组装
|
|
|
|
|
+2. **状态管理问题**: `createPhotos` 和 `updatePhotos` 可能未正确同步到表单值
|
|
|
|
|
+3. **组件值绑定问题**: `PhotoUploadField` 的 `value` 和 `onChange` 可能未正确连接
|
|
|
|
|
+
|
|
|
|
|
+#### 后端问题
|
|
|
|
|
+1. **Schema验证问题**: Schema可能未定义 `photos` 字段,导致数据被过滤
|
|
|
|
|
+2. **Service层处理问题**: 创建/更新残疾人时可能未正确处理关联的照片数据
|
|
|
|
|
+3. **数据库事务问题**: 照片保存可能在独立事务中,导致残疾人保存失败时照片已保存
|
|
|
|
|
+4. **关联查询缺失**: 获取残疾人详情时可能未使用 `relations` 加载照片数据
|
|
|
|
|
+
|
|
|
|
|
+#### 数据库问题
|
|
|
|
|
+1. **外键约束**: `person_id` 或 `file_id` 可能在残疾人保存前不存在
|
|
|
|
|
+2. **级联删除配置**: `onDelete: CASCADE` 可能导致意外的数据删除
|
|
|
|
|
+
|
|
|
|
|
+### 修复检查清单
|
|
|
|
|
+
|
|
|
|
|
+在修复过程中,请按以下顺序检查:
|
|
|
|
|
+
|
|
|
|
|
+1. [ ] 前端表单提交时的请求体是否包含完整的 `photos` 数据
|
|
|
|
|
+2. [ ] 后端API是否正确接收和解析 `photos` 数据
|
|
|
|
|
+3. [ ] Schema是否包含 `photos` 字段的验证规则
|
|
|
|
|
+4. [ ] Service层是否正确处理照片数据的保存(使用 `DisabledPhoto` Entity)
|
|
|
|
|
+5. [ ] 数据库操作是否在事务中执行(确保原子性)
|
|
|
|
|
+6. [ ] 获取残疾人详情时是否使用 `relations: ['photos']` 加载照片
|
|
|
|
|
+7. [ ] 前端回显时是否正确使用后端返回的照片数据
|
|
|
|
|
+8. [ ] 照片文件URL是否正确生成(通过 `file-module` API)
|
|
|
|
|
+
|
|
|
|
|
+## Change Log
|
|
|
|
|
+| Date | Version | Description | Author |
|
|
|
|
|
+|------|---------|-------------|--------|
|
|
|
|
|
+| 2025-12-31 | 1.0 | 创建故事文档 | Bob (Scrum Master) |
|
|
|
|
|
+
|
|
|
|
|
+## Dev Agent Record
|
|
|
|
|
+
|
|
|
|
|
+### Agent Model Used
|
|
|
|
|
+待开发时填写
|
|
|
|
|
+
|
|
|
|
|
+### Debug Log References
|
|
|
|
|
+待开发时填写
|
|
|
|
|
+
|
|
|
|
|
+### Completion Notes List
|
|
|
|
|
+待开发时填写
|
|
|
|
|
+
|
|
|
|
|
+### File List
|
|
|
|
|
+待开发时填写
|
|
|
|
|
+
|
|
|
|
|
+## QA Results
|
|
|
|
|
+待QA测试时填写
|