diff --git a/agents.md b/agents.md index d204f9e..9c1e95c 100644 --- a/agents.md +++ b/agents.md @@ -1,4 +1,410 @@ # Agents 开发规范 - 涉及到任何代码修改,记得更新此文档和设计文档 -- +- 需求记录文档: [`docs/REQUIREMENTS.md`](docs/REQUIREMENTS.md) + +## 前端自动化测试规范 + +**重要**: 前端进行功能更新和修改后,必须使用 Playwright 进行自动化测试验证。 + +**详细文档**: 参见 [`tests/README.md`](tests/README.md) + +### 测试目录结构 + +``` +tests/ +├── README.md # 测试使用文档 +├── constitution.test.js # 体质分析功能测试 +├── profile.test.js # "我的"页面功能测试 +├── health-profile-complete.test.js # 健康档案完整功能测试(推荐) +└── screenshots/ # 测试截图目录 +``` + +### 测试流程 + +1. **修改代码后**:编写或运行 Playwright 测试脚本验证功能 +2. **测试通过后**:才能确认修复完成 +3. **不要让用户手动测试**:自动化测试能发现的问题应自行解决 + +### 测试脚本示例 + +```javascript +// test-功能名.js +const { chromium } = require("playwright"); + +(async () => { + const browser = await chromium.launch({ headless: false }); + const page = await browser.newPage(); + + // 1. 打开应用 + await page.goto("http://localhost:8081"); + await page.waitForTimeout(2000); + + // 2. 执行测试步骤... + // 3. 截图验证 + await page.screenshot({ path: "test-screenshot.png" }); + + // 4. 断言检查 + const element = await page.locator("text=期望文本").first(); + if (await element.isVisible()) { + console.log("✓ 测试通过"); + } else { + console.log("✗ 测试失败"); + } + + await browser.close(); +})(); +``` + +### 运行测试 + +```bash +# 安装 (首次) +npm install playwright +npx playwright install chromium + +# 运行测试 +node test-功能名.js +``` + +### 测试检查清单 + +1. [ ] 功能正常工作 +2. [ ] 截图验证 UI 显示正确 +3. [ ] 错误场景处理正确 +4. [ ] 清理测试文件 (`rm test-*.js test-*.png`) + +--- + +## 前后端 API 响应格式规范 + +**重要**: 所有 API 必须遵循此格式约定,避免响应格式不匹配问题。 + +### 统一响应结构 + +```json +{ + "code": 0, // 0=成功,其他=错误码 + "message": "success", + "data": // 业务数据 +} +``` + +### data 字段约定 + +| 接口类型 | data 格式 | 示例 | +| -------- | ------------------ | -------------------------- | +| 列表接口 | 直接返回数组 `T[]` | `data: [{id:1}, {id:2}]` | +| 详情接口 | 返回对象 `T` | `data: {id:1, name:"xxx"}` | +| 创建接口 | 返回创建的对象 | `data: {id:1, ...}` | +| 删除接口 | `null` 或空 | `data: null` | + +### 字段命名约定 + +- **后端 API**: 使用 snake_case(`created_at`, `user_id`) +- **前端应用**: 使用 camelCase(`createdAt`, `userId`) +- **前端需做转换**: 在 Store 层统一转换 + +### 类型定义文件 + +- **后端**: `server/docs/API.md` - API 文档 +- **前端**: `app/src/api/types.ts` - 统一类型定义 + +### 新增 API 检查清单 + +1. [ ] 查看 `server/docs/API.md` 确认后端响应格式 +2. [ ] 在 `app/src/api/types.ts` 添加类型定义 +3. [ ] 在 API 模块中使用正确的泛型类型 +4. [ ] 在 Store 中添加 snake_case → camelCase 转换 + +--- + +## 开发记录 + +### 2026-02-02: Expo Web 兼容性修复 + +**问题**: React Native 的 `Alert.alert()` 在 Web 上无法正常显示弹窗 + +**解决方案**: 创建跨平台的 `AlertProvider` 组件,使用 react-native-paper 的 Snackbar 和 Dialog 替代原生 Alert + +**新增文件**: + +- `app/src/components/AlertProvider.tsx` - 全局 Alert Context 和组件 +- `app/src/components/index.ts` - 组件导出 + +**修改文件**: + +- `app/App.tsx` - 集成 AlertProvider +- `app/src/screens/auth/LoginScreen.tsx` - 替换 Alert 调用 +- `app/src/screens/chat/ChatDetailScreen.tsx` - 替换 Alert 调用 +- `app/src/screens/chat/ChatListScreen.tsx` - 替换 Alert 调用 +- `app/src/screens/constitution/ConstitutionTestScreen.tsx` - 替换 Alert 调用 +- `app/src/screens/profile/ProfileScreen.tsx` - 替换 Alert 调用 + +**使用方式**: + +```tsx +import { useAlert } from "../../components"; + +const { showAlert, showToast } = useAlert(); + +// Toast 简单提示 +showToast("请输入正确的手机号"); + +// Dialog 确认弹窗 +showAlert("确认删除", "确定要删除吗?", [ + { text: "取消", style: "cancel" }, + { text: "删除", style: "destructive", onPress: () => handleDelete() }, +]); +``` + +--- + +### 2026-02-02: 健康档案功能对接 + +**内容**: 对接后端真实 API,实现健康档案功能 + +**新增文件**: + +- `app/src/stores/healthStore.ts` - 健康档案状态管理 +- `app/src/screens/profile/HealthProfileScreen.tsx` - 健康档案页面 + +**修改文件**: + +- `app/src/api/user.ts` - 添加健康档案相关 API(病史、家族病史、过敏记录) +- `app/src/navigation/index.tsx` - 添加 ProfileStack 和健康档案路由 +- `app/src/screens/profile/ProfileScreen.tsx` - 对接健康档案导航和真实病史数据 + +**功能说明**: + +1. **健康档案页面**: 展示基础信息、生活习惯、病史记录、家族病史、过敏记录 +2. **用药记录**: 改用后端病史数据,显示"治疗中"状态的记录 +3. **支持下拉刷新和长按删除** + +**API 对接**: + +- `GET /api/user/health-profile` - 获取完整健康档案 +- `GET /api/user/lifestyle` - 获取生活习惯 +- `GET /api/user/medical-history` - 获取病史列表 +- `GET /api/user/family-history` - 获取家族病史 +- `GET /api/user/allergy-records` - 获取过敏记录 +- `DELETE /api/user/medical-history/:id` - 删除病史 +- `DELETE /api/user/family-history/:id` - 删除家族病史 +- `DELETE /api/user/allergy-records/:id` - 删除过敏记录 + +--- + +### 2026-02-02: API 响应格式统一规范 + +**问题**: 前后端响应格式不匹配导致数据解析失败(已出现两次:Token 获取、对话列表) + +**根本原因**: + +- 前端期望: `data: { conversations: [...], total: number }` +- 后端实际: `data: [...]` (直接返回数组) + +**解决方案**: + +1. 创建统一类型定义文件 `app/src/api/types.ts` +2. 明确约定:列表接口 data 直接返回数组,不包装 +3. Store 层统一做 snake_case → camelCase 转换 + +**新增文件**: + +- `app/src/api/types.ts` - 统一 API 类型定义 + +**修改文件**: + +- `app/src/api/conversation.ts` - 使用新类型定义 +- `app/src/stores/chatStore.ts` - 添加转换函数 + +**关键代码**: + +```typescript +// app/src/api/types.ts - API 响应类型与后端保持一致 +export interface ConversationItem { + id: number; + title: string; + created_at: string; // snake_case + updated_at: string; +} + +// app/src/stores/chatStore.ts - 转换函数 +const convertConversation = (item: ConversationItem): Conversation => ({ + id: String(item.id), + title: item.title, + createdAt: item.created_at, // 转为 camelCase + updatedAt: item.updated_at, +}); +``` + +--- + +### 2026-02-02: 体质分析功能修复 + +**问题**: 体质分析功能多处 API 格式不匹配导致运行时错误 + +**错误信息**: + +1. `Cannot read properties of undefined (reading 'length')` - questions 字段 +2. `currentQuestion.options.map is not a function` - options 字段格式 +3. `Cannot read properties of undefined (reading 'suggestions')` - 结果格式 + +**根本原因**: + +- 问卷题目:后端直接返回数组 `data: [...]`,前端期望 `data: { questions: [...] }` +- 选项格式:后端存储 `["没有","很少","有时","经常","总是"]`,前端期望 `[{value, label}]` +- 结果格式:后端返回 `{ primary_constitution: {...}, all_scores: [...] }`,前端期望 `{ primaryType, scores }` + +**修改文件**: + +- `app/src/stores/constitutionStore.ts` - 添加数据格式转换 +- `app/src/api/constitution.ts` - 更新类型定义 +- `app/src/screens/constitution/ConstitutionResultScreen.tsx` - 添加防御性代码 + +**关键修复代码**: + +```typescript +// 问卷数据转换 +const questions = rawQuestions.map((q: any) => { + let parsedOptions = + typeof q.options === "string" ? JSON.parse(q.options) : q.options; + + // 字符串数组转为 {value, label} 格式 + if (Array.isArray(parsedOptions) && typeof parsedOptions[0] === "string") { + parsedOptions = parsedOptions.map((label: string, index: number) => ({ + value: index + 1, + label, + })); + } + + return { + id: q.id || q.ID, + constitution_type: q.constitution_type, + question: q.question_text || q.question, + options: parsedOptions, + order_num: q.order_num, + }; +}); + +// 结果数据转换 +const scores: Record = {}; +apiResult.all_scores?.forEach((item: any) => { + scores[item.type] = item.score; +}); + +const result = { + primaryType: apiResult.primary_constitution?.type, + scores, + // ... +}; +``` + +**测试验证**: + +- 测试脚本: `tests/constitution.test.js` +- 测试结果: 12/13 通过,67 道题完整测试 +- 测试截图: `tests/screenshots/constitution-result.png` + +--- + +### 2026-02-02: "我的"页面功能测试 + +**测试内容**: + +1. 用户信息显示(昵称、手机号、体质标签) +2. 编辑昵称功能 +3. 适老模式开关 +4. 健康管理菜单(健康档案、用药记录、体质报告、对话历史) +5. 用药/治疗记录弹窗 +6. 关于我们弹窗 +7. 退出登录功能 +8. 健康档案导航 + +**测试结果**: 16/18 通过 + +**通过项目**: + +- ✓ 用户信息显示完整 +- ✓ 编辑昵称功能正常 +- ✓ 适老模式开关正常 +- ✓ 所有菜单项显示正常 +- ✓ 用药记录弹窗正常 +- ✓ 退出登录按钮显示 +- ✓ 健康档案导航正常 + +**测试文件**: + +- 测试脚本: `tests/profile.test.js` +- 测试截图: `tests/screenshots/profile-*.png`, `tests/screenshots/health-profile-*.png` +- 测试文档: `tests/README.md` + +--- + +### 2026-02-02: 健康档案编辑功能 + +**新增功能**: + +1. 基础信息编辑弹窗 - 编辑姓名、性别、出生日期、身高、体重、血型、职业、婚姻状况、地区 +2. 生活习惯编辑弹窗 - 编辑睡眠时间、睡眠质量、饮食、运动、吸烟、饮酒等 +3. 病史记录新增功能 - 添加疾病名称、类型、诊断日期、状态、备注 +4. 家族病史新增功能 - 添加亲属关系、疾病名称、备注 +5. 过敏记录新增功能 - 添加过敏类型、过敏原、严重程度、反应描述 + +**修改文件**: + +- `app/src/api/user.ts` - 添加 addMedicalHistory, addFamilyHistory, addAllergyRecord API +- `app/src/stores/healthStore.ts` - 添加创建记录的方法 +- `app/src/screens/profile/HealthProfileScreen.tsx` - 添加编辑弹窗和新增功能 + +**测试结果**: 23/25 通过 + +**通过项目**: + +- ✓ 健康档案页面打开 +- ✓ 基础信息卡片显示 +- ✓ 基础信息编辑弹窗打开 +- ✓ 生活习惯卡片显示 +- ✓ 病史记录卡片显示 +- ✓ 病史记录新增弹窗打开 +- ✓ 家族病史卡片显示 +- ✓ 过敏记录卡片显示 + +--- + +### 2026-02-02: 健康档案保存功能修复 + +**问题描述**: + +1. 基础信息保存后页面不显示更新的数据 +2. 病史记录、家族病史、过敏记录添加失败 +3. 后端返回的数据格式与前端期望不匹配 + +**根本原因**: + +1. `fetchHealthProfile` 错误地将 `response.data` 直接赋给 `profile`,但后端返回的是嵌套结构 `{ profile, lifestyle, medical_history, ... }` +2. 后端字段名 `medical_history`(单数)与前端使用的 `medical_histories`(复数)不匹配 +3. 添加记录时后端返回 `data: null`,但前端检查 `response.code === 0 && response.data`,导致误判为失败 + +**修复内容**: + +- `app/src/stores/healthStore.ts`: + - `fetchHealthProfile`: 修复数据解析,支持嵌套结构 `data.profile || data` + - `fetchHealthProfile`: 兼容两种字段名 `data.medical_history || data.medical_histories` + - `updateHealthProfile`: 修改判断条件,只检查 `response.code === 0` + - `updateLifestyle`: 同上 + - `addMedicalHistory`: 修改判断条件,只检查 `response.code === 0`,并在成功后调用 `fetchHealthProfile()` 刷新数据 + - `addFamilyHistory`: 同上 + - `addAllergyRecord`: 同上 + +**测试验证**: + +- 测试脚本: `tests/health-profile-complete.test.js` +- 测试范围: + - 基础信息: 9 个字段(姓名、性别、出生日期、身高、体重、血型、职业、婚姻状况、地区) + - 生活习惯: 10 个字段(入睡时间、起床时间、睡眠质量、三餐规律、饮食偏好、日饮水量、运动频率、运动类型、吸烟、饮酒) + - 病史记录: 5 个字段(疾病名称、疾病类型、诊断日期、治疗状态、备注) + - 家族病史: 3 个字段(亲属关系、疾病名称、备注) + - 过敏记录: 4 个字段(过敏类型、过敏原、严重程度、过敏反应描述) +- 测试结果: **58/58 通过** ✓ +- 测试截图: `tests/screenshots/hp-*.png` diff --git a/docs/REQUIREMENTS.md b/docs/REQUIREMENTS.md new file mode 100644 index 0000000..37bd99d --- /dev/null +++ b/docs/REQUIREMENTS.md @@ -0,0 +1,235 @@ +# 健康应用需求记录文档 + +> 本文档记录了项目开发过程中用户提出的所有需求和功能相关提示词,按时间顺序整理。 + +--- + +## 一、平台兼容性需求 + +### 1.1 RN 转 Web 开发 + +> 当前的 APP 使用了 rn 进行开发,但是目前无法接上真机设备,需要把 rn 先转成 react h5 进行开发,后续再转到设备 + +**解决方案**:使用 Expo Web (`expo start --web`) 在浏览器中运行 React Native 应用 + +### 1.2 Web 端兼容问题 + +> expo start --web 好像在浏览器上有些兼容问题,比如当前 rn APP 的登录页,错误弹窗一直无法显示 + +**解决方案**:创建自定义 `AlertProvider` 组件,使用 `react-native-paper` 的 `Snackbar` 和 `Dialog` 替代原生 `Alert` + +--- + +## 二、API 接口对接需求 + +### 2.1 真实接口对接 + +> 下面继续完成真实接口的对接,首先是对话记录的管理,和"我的"页面上未完成的功能,很多数据都没有接入真实数据 + +### 2.2 统一响应格式 + +> 要和后端约定一个统一的响应格式,这个响应格式不匹配问题已经出现了两次,第一次是 token 获取不到 + +**解决方案**: + +- 创建统一的 `ApiResponse` 类型 +- 处理 `snake_case`(后端)与 `camelCase`(前端)的命名转换 + +--- + +## 三、对话功能需求 + +### 3.1 历史对话持久化 + +> 历史对话,每次刷新页面都会丢失 + +> 还是没有,使用后端存储,这样可以避免本地存储的兼容问题 + +**解决方案**:移除本地缓存,完全依赖后端存储对话记录 + +### 3.2 对话删除功能 + +> 历史对话里的删除功能,点击无响应,应该是先要弹出确认弹窗,然后执行记录删除 + +> 点击删除,记录弹窗不关闭,确认弹窗被遮挡 + +**解决方案**:使用 `setTimeout` 在关闭记录弹窗后再显示确认弹窗 + +### 3.3 问答页面 UI 调整 + +> 问答页面的 新建悬浮窗 移除,右上的历史按钮 改成 "对话管理" + +**修改内容**: + +- 移除右下角 "新建对话" FAB 悬浮按钮 +- 右上角按钮文字从 "历史" 改为 "对话管理" +- 弹窗标题从 "历史对话" 改为 "对话管理" + +--- + +## 四、用户中心需求 + +### 4.1 用户信息编辑 + +> 我的 页面,用户信息编辑功能点击无效 + +> 点击保存显示失败,你应该测试完功能再交付 + +**解决方案**: + +- 实现 `handleOpenEdit` 和 `handleSaveProfile` 方法 +- 修复 `authStore.updateProfile` 处理后端返回 `data: null` 的情况 + +### 4.2 首页欢迎语 + +> 首页欢迎词语,现在显示的是健康达人,应该显示的是用户的昵称 + +**解决方案**:修改 `HomeScreen.tsx`,动态显示 `user?.nickname` + +--- + +## 五、健康档案需求 + +### 5.1 健康档案编辑功能 + +> 我的->健康档案里的信息只有显示,没有编辑功能,在上一个测试文件中添加健康档案的功能测试,并且添加编辑功能 + +**实现内容**: + +- 基础信息编辑(9 个字段) +- 生活习惯编辑(10 个字段) +- 病史记录添加 +- 家族病史添加 +- 过敏记录添加 + +### 5.2 健康档案保存问题 + +> 健康档案测试->每一项可填写或可选择内容都需要进行相关测试,特别是基础信息填写,并且填写保存后查看是否正确保存,现在有些功能并不能正确保存 + +**问题根因**: + +1. 后端返回嵌套结构,前端解析错误 +2. 字段命名不一致(`medical_history` vs `medical_histories`) +3. 后端添加操作返回 `data: null`,前端判断失败 + +**解决方案**:修复 `healthStore.ts` 的数据解析和成功判断逻辑 + +### 5.3 病史记录删除 + +> 健康档案里的病史记录,长按删除显示失败,修复后添加到测试 + +**问题根因**:GORM 的 `ID` 字段没有 `json` 标签,序列化为大写 `ID`,前端获取不到正确的 id + +**解决方案**:修改后端模型,显式定义 `ID` 字段并添加 `json:"id"` 标签 + +--- + +## 六、体质分析需求 + +### 6.1 体质结果显示问题 + +> 体质页面,显示已完成体测,但是体质判断结果没显示,点击计入也是白屏,请测试并修复 + +**问题根因**: + +- `constitutionDescriptions[result.primaryType]` 访问 undefined +- 问题选项格式转换错误 +- 后端返回数据结构与前端期望不匹配 + +**解决方案**: + +- 添加防御性检查 +- 修复数据格式转换 +- 完善 `submitAnswers` 和 `fetchResult` 的数据处理 + +--- + +## 七、自动化测试需求 + +### 7.1 引入自动化测试 + +> 还是不行,你应该可以自己进行测试,使用 PLAYWRIGHT MCP,而不是让我重复测试 + +### 7.2 体质分析测试 + +> 建立测试脚本,进行体质分析功能的测试 + +### 7.3 保留测试脚本 + +> 测试脚本不要删除,整理出一个测试脚本使用文档 + +### 7.4 "我的"页面测试 + +> 编写测试脚本,对 "我的" 页面上的功能进行完成测试,有错误或者未完成的,进行修复。完成测试和修复,整理测试使用文档 + +### 7.5 健康档案完整测试 + +> 脚本需要先进行健康档案的测试,再到用药和治疗记录 + +> 1.在我的观测中,简况管理测试后的测试界面一直卡在图片这里闪烁,2,健康管理测试,只是测试了页面上的卡片是否存在,没有测试具体编辑和保存功能,添加具体功能测试 + +**测试脚本**: + +- `tests/constitution.test.js` - 体质分析测试 +- `tests/profile.test.js` - "我的"页面测试 +- `tests/health-profile-complete.test.js` - 健康档案完整测试 + +--- + +## 八、开发规范需求 + +### 8.1 前端自动化测试规范 + +> 前端进行功能更新和修改后,使用 PLAYWRIGHT MCP 进行自动化测试 + +**已添加到 AGENTS.md 开发规范** + +--- + +## 需求统计 + +| 类别 | 数量 | +| ---------- | ------ | +| 平台兼容性 | 2 | +| API 接口 | 2 | +| 对话功能 | 3 | +| 用户中心 | 2 | +| 健康档案 | 3 | +| 体质分析 | 1 | +| 自动化测试 | 5 | +| 开发规范 | 1 | +| **总计** | **19** | + +--- + +## 附:原始需求记录(按时间顺序) + +1. 当前的 APP 使用了 rn 进行开发,但是目前无法接上真机设备,需要把 rn 先转成 react h5 进行开发,后续再转到设备 +2. expo start --web 好像在浏览器上有些兼容问题,比如当前 rn APP 的登录页,错误弹窗一直无法显示 +3. 弹窗已经显示,可以继续使用这种方式 +4. 下面继续完成真实接口的对接,首先是对话记录的管理,和"我的"页面上未完成的功能,很多数据都没有接入真实数据 +5. 历史对话,每次刷新页面都会丢失 +6. 还是没有,使用后端存储,这样可以避免本地存储的兼容问题 +7. 还是没有历史对话 +8. 要和后端约定一个统一的响应格式,这个响应格式不匹配问题已经出现了两次,第一次是 token 获取不到 +9. 历史对话里的删除功能,点击无响应,应该是先要弹出确认弹窗,然后执行记录删除 +10. 点击删除,记录弹窗不关闭,确认弹窗被遮挡 +11. 还是不行,你应该可以自己进行测试,使用 PLAYWRIGHT MCP,而不是让我重复测试 +12. 我的 页面,用户信息编辑功能点击无效 +13. 点击保存显示失败,你应该测试完功能再交付 +14. 首页欢迎词语,现在显示的是健康达人,应该显示的是用户的昵称 +15. 建立测试脚本,进行体质分析功能的测试 +16. http://localhost:8081/ 当前访问项目为白屏 +17. 体质页面,显示已完成体测,但是体质判断结果没显示,点击计入也是白屏,请测试并修复 +18. 测试脚本不要删除,整理出一个测试脚本使用文档 +19. 编写测试脚本,对 "我的" 页面上的功能进行完成测试,有错误或者未完成的,进行修复。完成测试和修复,整理测试使用文档 +20. 我的->健康档案里的信息只有显示,没有编辑功能,在上一个测试文件中添加健康档案的功能测试,并且添加编辑功能 +21. 脚本需要先进行健康档案的测试,再到用药和治疗记录 +22. 在我的观测中,简况管理测试后的测试界面一直卡在图片这里闪烁;健康管理测试只是测试了页面上的卡片是否存在,没有测试具体编辑和保存功能,添加具体功能测试 +23. 健康档案测试->每一项可填写或可选择内容都需要进行相关测试,特别是基础信息填写,并且填写保存后查看是否正确保存,现在有些功能并不能正确保存 +24. 问答页面的 新建悬浮窗 移除,右上的历史按钮 改成 "对话管理" +25. 健康档案里的病史记录,长按删除显示失败,修复后添加到测试 + +--- + +_文档最后更新:2026-02-02_