22 KiB
Agents 开发规范
- 涉及到任何代码修改,记得更新此文档和设计文档
- 需求记录文档:
docs/REQUIREMENTS.md
前端开发启动
使用统一启动脚本,自动检查端口占用并清理后启动:
# 同时启动 健康APP(5173) + 商城(5174)
npm run dev # 或 node scripts/dev.js
# 仅启动健康APP
npm run dev:web # 或 node scripts/dev.js web
# 仅启动商城
npm run dev:mall # 或 node scripts/dev.js mall
# 仅关闭占用端口的进程
npm run dev:kill # 或 node scripts/dev.js --kill
| 项目 | 目录 | 端口 | 说明 |
|---|---|---|---|
| 健康APP (web) | web/ |
5173 | 健康AI助手前端 |
| 健康商城 (mall) | mall/ |
5174 | 独立商城前端 |
| 后端 API | server/ |
8080 | Go-Zero 后端 |
前端自动化测试规范
重要: 前端进行功能更新和修改后,必须使用 Playwright 进行自动化测试验证。
详细文档: 参见 tests/README.md
测试目录结构
tests/
├── README.md # 测试使用文档
├── constitution.test.js # 体质分析功能测试
├── profile.test.js # "我的"页面功能测试
├── health-profile-complete.test.js # 健康档案完整功能测试(推荐)
├── chat.test.js # 问答页对话管理与流式输出测试
├── mall.test.js # 商城前端测试(53项,含公开浏览+登录守卫,API Mock)
├── mall-real.test.js # 商城前端真实数据测试(52项,需后端)
└── 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
2026-02-02: AI 问答功能增强
修改内容:
- 等待动画: 添加 LoadingDots 组件,在等待 AI 回复和思考过程中显示动态加载动画
- 思考过程显示: 支持显示 AI 思考过程(需要模型支持
enable_thinking) - 放开思考过程过滤: 移除之前添加的关键词过滤逻辑,完整显示 AI 思考内容
修改文件:
-
app/src/screens/chat/ChatDetailScreen.tsx:- 添加
LoadingDots组件,实现三个点的脉冲动画 - 在 AI 回复等待时显示 "AI 正在回复..."
- 在思考过程等待时显示 "💭 思考中..."
- 添加
-
server/internal/service/ai/aliyun.go:- 移除
thinkingFilterKeywords关键词列表 - 移除
shouldFilterThinking过滤函数 - 简化思考过程流式输出,直接转发不过滤
- 移除
-
server/config.yaml:- 注释掉
app_id配置(应用 API 格式与 OpenAI 兼容模式不同,暂不支持)
- 注释掉
LoadingDots 组件示例:
const LoadingDots = ({ text = "思考中", fontSize = 15 }) => {
const [dotCount, setDotCount] = useState(1);
useEffect(() => {
const interval = setInterval(() => {
setDotCount((prev) => (prev >= 3 ? 1 : prev + 1));
}, 400);
return () => clearInterval(interval);
}, []);
return (
<Text style={{ fontSize, color: colors.textSecondary }}>
{text}
{".".repeat(dotCount)}
</Text>
);
};
备注:
- 阿里云百炼应用 API 需要使用专用的
/api/v1/apps/{APP_ID}/completion端点,与当前 OpenAI 兼容模式实现不同 - 思考过程功能需要使用支持
enable_thinking参数的模型(如 qwen-plus)
2026-02-03: 商城后端 API 开发
内容: 在现有健康AI后端基础上扩展商城功能,共享用户体系和JWT认证
新增模型:
ProductCategory- 商品分类ProductSku- 商品SKUPointsRecord- 积分变动记录CartItem- 购物车Address- 收货地址Order/OrderItem- 订单
新增API路由 (/api/mall):
- 公开路由: 分类列表、商品列表/详情/搜索/推荐
- 认证路由: 会员信息、积分、购物车CRUD、地址CRUD、订单全流程、体质推荐
API定义: backend/healthapi/healthapi.api (商城部分在文件末尾)
测试: backend/healthapi/tests/mall_api_test.go - 30个Go集成测试全部通过
2026-02-07: 商城 Web H5 前端开发 → 独立项目拆分
内容: 基于 Vue 3 + TypeScript + Element Plus 开发商城 Web H5 前端,后拆分为独立项目
项目结构调整:
商城从 web/ 项目中拆分为独立的 mall/ 项目:
web/(端口 5173) - 健康AI助手前端mall/(端口 5174) - 健康商城前端- 两个项目共享同一后端(端口 8080)和 JWT Token(通过 localStorage 共享)
- 双向跳转通过
VITE_MALL_URL/VITE_HEALTH_AI_URL环境变量配置
mall/ 项目文件结构:
mall/
├── package.json # 独立依赖管理
├── vite.config.ts # Vite 配置(端口 5174)
├── tsconfig.json # TypeScript 配置
├── .env # 环境变量(API地址、健康AI地址)
├── index.html
└── src/
├── main.ts # 入口文件
├── App.vue
├── router/index.ts # 路由(根路径 / 开头)
├── api/
│ ├── request.ts # Axios 封装(共享Token拦截器)
│ ├── auth.ts # 认证 API
│ └── mall.ts # 商城 API(商品/购物车/订单/地址/会员)
├── stores/
│ ├── auth.ts # 认证状态(从 localStorage 恢复)
│ ├── constitution.ts # 体质数据(从 localStorage 读取)
│ ├── mall.ts # 购物车和分类
│ ├── order.ts # 订单
│ └── member.ts # 会员信息
├── types/index.ts # 商城类型定义 + 体质类型
├── utils/healthAI.ts # 健康AI跳转工具
├── styles/index.scss # 全局样式
└── views/
├── layout/MallLayout.vue # H5布局
└── mall/ # 10个页面
├── MallHomeView.vue # 首页
├── CategoryView.vue # 分类
├── ProductDetailView.vue # 商品详情
├── SearchView.vue # 搜索
├── CartView.vue # 购物车
├── CheckoutView.vue # 结算
├── OrderListView.vue # 订单列表
├── OrderDetailView.vue # 订单详情
├── AddressListView.vue # 地址管理
└── MemberView.vue # 会员中心
web/ 项目变更:
- 移除所有商城代码(views/mall/、stores/mall|order|member、api/mall、utils/healthAI等)
web/src/router/index.ts- 移除/mall路由组web/src/views/home/HomeView.vue- "健康商城"改为外部链接跳转web/src/views/profile/ProfileView.vue- "健康商城"改为外部链接跳转web/src/types/index.ts- 移除商城类型定义web/.env- 新增VITE_MALL_URL环境变量
认证策略(按需登录):
商城采用"公开浏览 + 按需登录"策略,用户可自由浏览商品,仅在涉及个人数据的操作时要求登录:
| 页面/操作 | 是否需要登录 | 说明 |
|---|---|---|
| 首页、分类、商品详情、搜索 | 否 | 自由浏览 |
| 加入购物车、立即购买 | 是 | 弹窗引导登录 |
| 购物车、结算、订单 | 是 | 路由守卫重定向 |
| 地址管理、会员中心 | 是 | 路由守卫重定向 |
- 路由守卫:仅
meta.requiresAuth的路由会重定向到/login - 操作拦截:
useAuthCheck().requireAuth()弹窗引导登录 - 401 处理:API 返回 401 时清除 token 并跳转到本地登录页
跨项目跳转机制:
健康AI (web/) ←→ 商城 (mall/)
├── 共享: localStorage 中的 token 和 user 信息
├── web → mall: window.open(VITE_MALL_URL)
├── mall → web: jumpToHealthAI() / goToHealthAI()
└── mall 独立登录: /login 页面(不再依赖 web 登录页)
启动方式:
# 健康AI前端
cd web && npm run dev # http://localhost:5173
# 商城前端
cd mall && npm run dev # http://localhost:5174
# 后端
cd backend && go run . # http://localhost:8080
设计规范:
- 主色调: #52C41A (绿色/健康主题)
- 价格色: #FF4D4F
- H5 布局 max-width: 750px
- 面向中老年用户:大字体、大按钮、简洁流程
- 与健康AI双向跳转集成(AI咨询悬浮按钮、体质推荐等)
2026-02-07: 商城认证策略优化 — 公开浏览 + 按需登录
需求: 商城应允许用户自由浏览商品,仅在生成订单或支付时才需要登录
修改内容:
-
路由守卫改造 (
mall/src/router/index.ts):- 取消全局登录拦截,改为按路由标记
meta.requiresAuth - 公开页面(首页/分类/商品详情/搜索)无需登录
- 受保护页面(购物车/订单/结算/地址/会员)需要登录
- 取消全局登录拦截,改为按路由标记
-
操作级登录引导 (
mall/src/utils/auth.ts):- 新增
useAuthCheck()composable requireAuth(message)方法:未登录时弹ElMessageBox.confirm引导登录- 用于商品详情页的"加入购物车"和"立即购买"按钮
- 新增
-
商品详情页 (
mall/src/views/mall/ProductDetailView.vue):handleAddToCart()和handleBuyNow()执行前调用requireAuth()检查
-
测试更新 (
tests/mall.test.js):- 新增 Phase 7A: 未登录公开浏览测试(首页/分类/商品/搜索可正常访问)
- 更新 Phase 7B: 受保护页面重定向测试(5个路由均重定向到 /login)
- 53项测试全部通过
新增文件:
mall/src/utils/auth.ts- 认证检查 composable
修改文件:
mall/src/router/index.ts- 路由守卫改造mall/src/views/mall/ProductDetailView.vue- 按钮登录检查tests/mall.test.js- 测试更新