You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
13 KiB
13 KiB
Agents 开发规范
- 涉及到任何代码修改,记得更新此文档和设计文档
- 需求记录文档:
docs/REQUIREMENTS.md
前端自动化测试规范
重要: 前端进行功能更新和修改后,必须使用 Playwright 进行自动化测试验证。
详细文档: 参见 tests/README.md
测试目录结构
tests/
├── README.md # 测试使用文档
├── constitution.test.js # 体质分析功能测试
├── profile.test.js # "我的"页面功能测试
├── health-profile-complete.test.js # 健康档案完整功能测试(推荐)
└── screenshots/ # 测试截图目录
测试流程
- 修改代码后:编写或运行 Playwright 测试脚本验证功能
- 测试通过后:才能确认修复完成
- 不要让用户手动测试:自动化测试能发现的问题应自行解决
测试脚本示例
// 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();
})();
运行测试
# 安装 (首次)
npm install playwright
npx playwright install chromium
# 运行测试
node test-功能名.js
测试检查清单
- 功能正常工作
- 截图验证 UI 显示正确
- 错误场景处理正确
- 清理测试文件 (
rm test-*.js test-*.png)
前后端 API 响应格式规范
重要: 所有 API 必须遵循此格式约定,避免响应格式不匹配问题。
统一响应结构
{
"code": 0, // 0=成功,其他=错误码
"message": "success",
"data": <T> // 业务数据
}
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 检查清单
- 查看
server/docs/API.md确认后端响应格式 - 在
app/src/api/types.ts添加类型定义 - 在 API 模块中使用正确的泛型类型
- 在 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- 集成 AlertProviderapp/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 调用
使用方式:
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- 对接健康档案导航和真实病史数据
功能说明:
- 健康档案页面: 展示基础信息、生活习惯、病史记录、家族病史、过敏记录
- 用药记录: 改用后端病史数据,显示"治疗中"状态的记录
- 支持下拉刷新和长按删除
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: [...](直接返回数组)
解决方案:
- 创建统一类型定义文件
app/src/api/types.ts - 明确约定:列表接口 data 直接返回数组,不包装
- Store 层统一做 snake_case → camelCase 转换
新增文件:
app/src/api/types.ts- 统一 API 类型定义
修改文件:
app/src/api/conversation.ts- 使用新类型定义app/src/stores/chatStore.ts- 添加转换函数
关键代码:
// 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 格式不匹配导致运行时错误
错误信息:
Cannot read properties of undefined (reading 'length')- questions 字段currentQuestion.options.map is not a function- options 字段格式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- 添加防御性代码
关键修复代码:
// 问卷数据转换
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<string, number> = {};
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: "我的"页面功能测试
测试内容:
- 用户信息显示(昵称、手机号、体质标签)
- 编辑昵称功能
- 适老模式开关
- 健康管理菜单(健康档案、用药记录、体质报告、对话历史)
- 用药/治疗记录弹窗
- 关于我们弹窗
- 退出登录功能
- 健康档案导航
测试结果: 16/18 通过
通过项目:
- ✓ 用户信息显示完整
- ✓ 编辑昵称功能正常
- ✓ 适老模式开关正常
- ✓ 所有菜单项显示正常
- ✓ 用药记录弹窗正常
- ✓ 退出登录按钮显示
- ✓ 健康档案导航正常
测试文件:
- 测试脚本:
tests/profile.test.js - 测试截图:
tests/screenshots/profile-*.png,tests/screenshots/health-profile-*.png - 测试文档:
tests/README.md
2026-02-02: 健康档案编辑功能
新增功能:
- 基础信息编辑弹窗 - 编辑姓名、性别、出生日期、身高、体重、血型、职业、婚姻状况、地区
- 生活习惯编辑弹窗 - 编辑睡眠时间、睡眠质量、饮食、运动、吸烟、饮酒等
- 病史记录新增功能 - 添加疾病名称、类型、诊断日期、状态、备注
- 家族病史新增功能 - 添加亲属关系、疾病名称、备注
- 过敏记录新增功能 - 添加过敏类型、过敏原、严重程度、反应描述
修改文件:
app/src/api/user.ts- 添加 addMedicalHistory, addFamilyHistory, addAllergyRecord APIapp/src/stores/healthStore.ts- 添加创建记录的方法app/src/screens/profile/HealthProfileScreen.tsx- 添加编辑弹窗和新增功能
测试结果: 23/25 通过
通过项目:
- ✓ 健康档案页面打开
- ✓ 基础信息卡片显示
- ✓ 基础信息编辑弹窗打开
- ✓ 生活习惯卡片显示
- ✓ 病史记录卡片显示
- ✓ 病史记录新增弹窗打开
- ✓ 家族病史卡片显示
- ✓ 过敏记录卡片显示
2026-02-02: 健康档案保存功能修复
问题描述:
- 基础信息保存后页面不显示更新的数据
- 病史记录、家族病史、过敏记录添加失败
- 后端返回的数据格式与前端期望不匹配
根本原因:
fetchHealthProfile错误地将response.data直接赋给profile,但后端返回的是嵌套结构{ profile, lifestyle, medical_history, ... }- 后端字段名
medical_history(单数)与前端使用的medical_histories(复数)不匹配 - 添加记录时后端返回
data: null,但前端检查response.code === 0 && response.data,导致误判为失败
修复内容:
app/src/stores/healthStore.ts:fetchHealthProfile: 修复数据解析,支持嵌套结构data.profile || datafetchHealthProfile: 兼容两种字段名data.medical_history || data.medical_historiesupdateHealthProfile: 修改判断条件,只检查response.code === 0updateLifestyle: 同上addMedicalHistory: 修改判断条件,只检查response.code === 0,并在成功后调用fetchHealthProfile()刷新数据addFamilyHistory: 同上addAllergyRecord: 同上
测试验证:
- 测试脚本:
tests/health-profile-complete.test.js - 测试范围:
- 基础信息: 9 个字段(姓名、性别、出生日期、身高、体重、血型、职业、婚姻状况、地区)
- 生活习惯: 10 个字段(入睡时间、起床时间、睡眠质量、三餐规律、饮食偏好、日饮水量、运动频率、运动类型、吸烟、饮酒)
- 病史记录: 5 个字段(疾病名称、疾病类型、诊断日期、治疗状态、备注)
- 家族病史: 3 个字段(亲属关系、疾病名称、备注)
- 过敏记录: 4 个字段(过敏类型、过敏原、严重程度、过敏反应描述)
- 测试结果: 58/58 通过 ✓
- 测试截图:
tests/screenshots/hp-*.png