healthapp
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.
 
 
 
 
 
 

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/                      # 测试截图目录

测试流程

  1. 修改代码后:编写或运行 Playwright 测试脚本验证功能
  2. 测试通过后:才能确认修复完成
  3. 不要让用户手动测试:自动化测试能发现的问题应自行解决

测试脚本示例

// 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

测试检查清单

  1. 功能正常工作
  2. 截图验证 UI 显示正确
  3. 错误场景处理正确
  4. 清理测试文件 (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 检查清单

  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 调用

使用方式:

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 - 添加转换函数

关键代码:

// 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 - 添加防御性代码

关键修复代码:

// 问卷数据转换
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: "我的"页面功能测试

测试内容:

  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

2026-02-02: AI 问答功能增强

修改内容:

  1. 等待动画: 添加 LoadingDots 组件,在等待 AI 回复和思考过程中显示动态加载动画
  2. 思考过程显示: 支持显示 AI 思考过程(需要模型支持 enable_thinking
  3. 放开思考过程过滤: 移除之前添加的关键词过滤逻辑,完整显示 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 - 商品SKU
  • PointsRecord - 积分变动记录
  • 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: 商城认证策略优化 — 公开浏览 + 按需登录

需求: 商城应允许用户自由浏览商品,仅在生成订单或支付时才需要登录

修改内容:

  1. 路由守卫改造 (mall/src/router/index.ts):

    • 取消全局登录拦截,改为按路由标记 meta.requiresAuth
    • 公开页面(首页/分类/商品详情/搜索)无需登录
    • 受保护页面(购物车/订单/结算/地址/会员)需要登录
  2. 操作级登录引导 (mall/src/utils/auth.ts):

    • 新增 useAuthCheck() composable
    • requireAuth(message) 方法:未登录时弹 ElMessageBox.confirm 引导登录
    • 用于商品详情页的"加入购物车"和"立即购买"按钮
  3. 商品详情页 (mall/src/views/mall/ProductDetailView.vue):

    • handleAddToCart()handleBuyNow() 执行前调用 requireAuth() 检查
  4. 测试更新 (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 - 测试更新