中老年智能健康管理项目,从答辩版做到了商用雏形
作者:燐妤
原文链接:点击此处
这段时间我一直在做一个叫 HealthGuardAI 的项目。最开始,它只是一个能上台答辩、能写进简历的课程项目;后来我又把它往真实商用方向推了一步,慢慢补出了机构管理员、健康管理师、普通用户这套完整角色链路。
这篇文章不讲空话,尽量按一次真实项目推进的顺序来写:用了什么技术栈、为什么做、界面怎么设计、怎么一步步开发、怎么测试和部署。如果你也在做类似的健康管理、SaaS 平台、机构后台项目,这篇可以直接参考。

一、这个项目到底解决什么问题
我一开始做这个项目,目标很明确:
不是做一个"看起来很炫"的 demo,而是做一个能贴近真实健康管理场景的系统。
现实里很多中老年健康管理产品都会遇到同样的问题:
- 用户只做一次体检,后面就没人跟进了。
- 体检报告看得懂的人少,很多指标看完还是不知道该怎么做。
- 健康顾问、体检中心、社区服务人员没有统一系统来管理客户。
- 用户和机构之间的数据授权、随访、提醒、运营统计都比较零散。
所以我把项目设计成了一个 B2B2C 模式:
- B 端机构:体检中心、康养机构、健康管理公司、社区服务机构
- B 端人员:机构管理员、健康管理师
- C 端用户:普通用户、中老年用户、体检客户
简单说就是:
机构买系统,机构管理员管后台,健康管理师做服务,普通用户授权数据并接受随访。
这个结构比单纯的个人健康记录工具更接近真实市场,也更适合做成一个能展示、能答辩、能继续商业化的项目。
二、完整技术栈
下面这部分是项目里真正用到的前后端技术栈,版本都来自仓库配置文件。
1. 前端技术栈
| 层级 | 技术 | 版本 / 说明 |
|---|---|---|
| 前端框架 | Vue | 3.5.40 |
| 构建工具 | Vite | 8.1.5 |
| 语言 | TypeScript | ~6.0.0 |
| 状态管理 | Pinia | 4.0.2 |
| 持久化插件 | pinia-plugin-persistedstate | 4.7.1 |
| 路由 | Vue Router | 5.2.0 |
| 样式方案 | Tailwind CSS | 4.3.3 |
| Tailwind 集成 | @tailwindcss/vite | 4.3.3 |
| 请求库 | axios | 1.19.0 |
| 请求模拟 | axios-mock-adapter | 2.1.0 |
| 图表库 | ECharts | 5.6.0 |
| Vue 图表封装 | vue-echarts | 7.0.3 |
| 类型检查 | vue-tsc | 3.3.7 |
| 构建脚本工具 | npm-run-all2 | 9.0.2 |
前端运行环境要求:
- Node.js:
^22.18.0 || >=24.12.0
2. 后端技术栈
| 层级 | 技术 | 版本 / 说明 |
|---|---|---|
| 后端框架 | FastAPI | >=0.115,<1.0 |
| ASGI 服务 | Uvicorn | >=0.30,<1.0 |
| 数据校验 | Pydantic | >=2.8,<3.0 |
| ORM | SQLAlchemy | >=2.0,<3.0 |
| 数据库驱动 | PyMySQL | >=1.1,<2.0 |
| HTTP 客户端 | httpx | >=0.27,<1.0 |
| 环境变量 | python-dotenv | >=1.0,<2.0 |
| 上传处理 | python-multipart | >=0.0.9,<1.0 |
| 号码认证 | alibabacloud-dypnsapi20170525 | >=1.0,<2.0 |
| 阿里云 SDK | alibabacloud-tea-openapi / tea-util | >=0.3,<1.0 |
| PDF / 报告处理 | PyMuPDF | >=1.24,<2.0 |
3. 部署与运行环境
| 层级 | 技术 | 说明 |
|---|---|---|
| 操作系统 | Ubuntu Server 24.04 LTS | 腾讯云服务器环境 |
| 容器 | Docker | 27.5.1 |
| 编排 | Docker Compose | v2.32.4 |
| 公网访问 | IP 直连 | 当前未购买域名 |
| 代码托管 | Gitee | 项目同步和备份 |
| 文档输出 | Markdown / CSDN | 便于直接整理和发布 |
三、项目背景与价值
这个项目的核心价值,不是"我做了一个页面",而是"我把一个健康管理场景拆成了能落地的流程"。
1. 用户侧价值
- 体检报告可以上传、识别、查看。
- 健康数据可以持续录入。
- 智能问答可以给出参考建议。
- 用户可以授权健康管理师查看指定数据。
- 随访任务可以在前后端形成闭环。
2. 机构侧价值
- 机构管理员可以管理成员和套餐权益。
- 健康管理师可以查看授权用户。
- 机构可以追踪服务用户、授权状态、随访进度和运营数据。
- 机构可以把一次体检变成长期服务。
3. 商业价值
这个项目后面不是卖给单个普通用户,而是卖给有真实服务对象的机构:
- 体检中心
- 康养机构
- 健康管理公司
- 社区服务机构
- 养老机构
也就是说,它更像一套 面向机构的健康管理 SaaS,而不是单独的个人工具。
四、UI / UX 设计流程与理念
这个项目在界面上没有走"花里胡哨"的路线,而是尽量做成 清晰、稳定、能快速扫一眼就懂 的工具型界面。
1. 视觉风格
整体采用偏健康感的绿色体系:
- 主色:绿色
- 背景:白色 / 极浅绿色
- 成功状态:绿色
- 警告状态:橙色
- 危险状态:红色
这样做的原因很简单:
- 健康类产品不适合太花。
- 中老年用户看界面时,第一眼要知道"哪里能点、哪个是主按钮、哪里有风险"。
- 绿色系更适合"稳定、可信、服务感"。
2. 版式思路
我把页面拆成了卡片式分区:
- 登录注册页:左右分栏,左侧品牌与说明,右侧表单。
- 首页仪表盘:顶部总览 + 中部指标卡 + 右侧建议栏。
- 设置页:账户信息、偏好设置、数据与隐私、账户与安全分区清晰。
- 机构端页面:服务用户、随访任务、套餐权益、运营统计各自独立。
这种做法的好处是:
- 信息密度高,但不乱。
- 用户不会被一长串表单压住。
- 关键操作按钮更容易被看到。
3. 交互逻辑
我特别重视这些交互细节:
- 所有可见文字统一中文
- 按钮文字居中
- 主按钮和次按钮层级清楚
- 危险操作必须二次确认
- 加载、空状态、错误状态要分开
- 刷新、提交、保存都要有明确反馈
- 滚动到聊天底部、返回底部按钮、弹窗关闭等操作要自然
比如:
- 登录/注册页在刷新前后保持同样的视觉边界,不让线框忽隐忽现。
- AI 聊天页面问题多的时候,输入框不能被挤到很下面。
- OCR 确认页的底部操作区不能跟着页面一起乱飘。
- 设置页的"服务授权"入口必须让普通用户能看见,而不是藏在别的地方。
4. 设计上的一个原则
我给这个项目定过一个很硬的规则:
所有界面不能有英文/缩写。
这条规则听起来很狠,但很适合中老年健康管理场景。
因为这类系统不是给开发者自己用的,而是给普通用户、机构管理员、健康管理师一起用的。界面必须让人一眼读懂。
五、代表性界面截图
下面这些图基本能把整个项目的主流程讲清楚。
1. 登录页

左侧是品牌和产品说明,右侧是登录表单。这个页面重点解决的是:首屏就把"健康卫士"这个产品感觉立住。
2. 健康看板

这是普通用户进入系统后最重要的首页,集中展示最近一次健康状态、趋势摘要、行动建议和快速记录入口。
3. 设置页

设置页主要展示账户信息、偏好设置、数据与隐私和账户安全。后面我还把普通用户的"服务授权"入口补进了这里。
4. 服务用户页

这是健康管理师端的核心页面之一。它展示被授权的普通用户、授权范围、基础档案、体检报告、趋势数据和随访闭环。
5. 随访任务页

健康管理师在这里创建、跟进和完成随访任务,形成完整服务链路。
6. 套餐权益页

这是机构管理员侧的经营工具,用来管理套餐名额、权益配置和机构资源。
六、架构设计与开发流程
1. 系统架构
#mermaid-svg-qVwdfsINnFOwWuxC{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-qVwdfsINnFOwWuxC .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-qVwdfsINnFOwWuxC .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-qVwdfsINnFOwWuxC .error-icon{fill:#552222;}#mermaid-svg-qVwdfsINnFOwWuxC .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-qVwdfsINnFOwWuxC .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-qVwdfsINnFOwWuxC .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-qVwdfsINnFOwWuxC .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-qVwdfsINnFOwWuxC .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-qVwdfsINnFOwWuxC .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-qVwdfsINnFOwWuxC .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-qVwdfsINnFOwWuxC .marker{fill:#333333;stroke:#333333;}#mermaid-svg-qVwdfsINnFOwWuxC .marker.cross{stroke:#333333;}#mermaid-svg-qVwdfsINnFOwWuxC svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-qVwdfsINnFOwWuxC p{margin:0;}#mermaid-svg-qVwdfsINnFOwWuxC .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-qVwdfsINnFOwWuxC .cluster-label text{fill:#333;}#mermaid-svg-qVwdfsINnFOwWuxC .cluster-label span{color:#333;}#mermaid-svg-qVwdfsINnFOwWuxC .cluster-label span p{background-color:transparent;}#mermaid-svg-qVwdfsINnFOwWuxC .label text,#mermaid-svg-qVwdfsINnFOwWuxC span{fill:#333;color:#333;}#mermaid-svg-qVwdfsINnFOwWuxC .node rect,#mermaid-svg-qVwdfsINnFOwWuxC .node circle,#mermaid-svg-qVwdfsINnFOwWuxC .node ellipse,#mermaid-svg-qVwdfsINnFOwWuxC .node polygon,#mermaid-svg-qVwdfsINnFOwWuxC .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-qVwdfsINnFOwWuxC .rough-node .label text,#mermaid-svg-qVwdfsINnFOwWuxC .node .label text,#mermaid-svg-qVwdfsINnFOwWuxC .image-shape .label,#mermaid-svg-qVwdfsINnFOwWuxC .icon-shape .label{text-anchor:middle;}#mermaid-svg-qVwdfsINnFOwWuxC .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-qVwdfsINnFOwWuxC .rough-node .label,#mermaid-svg-qVwdfsINnFOwWuxC .node .label,#mermaid-svg-qVwdfsINnFOwWuxC .image-shape .label,#mermaid-svg-qVwdfsINnFOwWuxC .icon-shape .label{text-align:center;}#mermaid-svg-qVwdfsINnFOwWuxC .node.clickable{cursor:pointer;}#mermaid-svg-qVwdfsINnFOwWuxC .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-qVwdfsINnFOwWuxC .arrowheadPath{fill:#333333;}#mermaid-svg-qVwdfsINnFOwWuxC .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-qVwdfsINnFOwWuxC .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-qVwdfsINnFOwWuxC .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-qVwdfsINnFOwWuxC .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-qVwdfsINnFOwWuxC .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-qVwdfsINnFOwWuxC .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-qVwdfsINnFOwWuxC .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-qVwdfsINnFOwWuxC .cluster text{fill:#333;}#mermaid-svg-qVwdfsINnFOwWuxC .cluster span{color:#333;}#mermaid-svg-qVwdfsINnFOwWuxC div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-qVwdfsINnFOwWuxC .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-qVwdfsINnFOwWuxC rect.text{fill:none;stroke-width:0;}#mermaid-svg-qVwdfsINnFOwWuxC .icon-shape,#mermaid-svg-qVwdfsINnFOwWuxC .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-qVwdfsINnFOwWuxC .icon-shape p,#mermaid-svg-qVwdfsINnFOwWuxC .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-qVwdfsINnFOwWuxC .icon-shape .label rect,#mermaid-svg-qVwdfsINnFOwWuxC .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-qVwdfsINnFOwWuxC .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-qVwdfsINnFOwWuxC .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-qVwdfsINnFOwWuxC :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} B端机构
C端用户
后端 FastAPI
认证与授权
数据服务
机构与成员
随访与运营
前端 Vue 3
页面路由
Pinia 状态管理
API 封装
登录 / 注册 / 体检报告 / 智能问答 / 服务授权
机构管理员
健康管理师
套餐权益 / 服务用户 / 随访任务 / 运营统计
SQLite / MySQL
大模型服务
号码认证 / OCR / 第三方服务
2. 开发流程
整个项目不是一下子做完的,而是按这个顺序推进的:
第一步:需求分析
先把场景拆开:
- 普通用户要什么?
- 健康管理师要什么?
- 机构管理员要什么?
- 答辩时最重要的主流程是什么?
最开始先保证:
- 登录注册能用
- 健康数据能看
- 报告能识别
- AI 问答能跑
- 设置页能改
第二步:架构设计
前端和后端分层很清楚:
- 前端负责界面、路由、交互、状态管理。
- 后端负责认证、数据库、机构角色、业务规则。
- 容器负责打包和部署。
第三步:前端编码
前端用了 Vue 3 + Vite + TypeScript,做了这些主页面:
- 登录 / 注册
- 健康看板
- 手动录入
- 历史记录
- 智能问答
- 设置与隐私
- 机构服务页面
在界面上,我特别反复打磨了:
- 按钮对齐
- 图标显示
- 字体层级
- 加载态
- 空状态
- 错误状态
- 切页动画
第四步:后端编码
后端用 FastAPI 搭接口,再用 SQLAlchemy 做数据持久化。
除了基础登录注册,还补了:
- 体检报告导入
- 号码认证
- 大模型配置
- 数据授权
- 机构成员
- 服务用户
- 随访任务
- 运营统计
第五步:测试与修复
我在这个阶段花了很多时间修"看起来小,但现场很影响体验"的问题,比如:
- 页面刷新后视觉变样
- 某些按钮看起来不明显
- 图标不显示
- 某些接口会超时
- 页面加载后卡在骨架屏
- 登录注册错误信息不提示
第六步:部署
后面把项目放到腾讯云 Ubuntu Server 上,用 Docker Compose 跑前后端。
部署时踩过的坑也不少,最典型的就是:
.env.production和.env混用- 环境变量缺失
- 后端健康检查不通过
- 端口映射搞错
- 浏览器缓存导致页面看不到新功能
七、关键代码片段
1. 后端健康检查
python
@app.on_event("startup")
def startup_event() -> None:
init_database()
@app.get("/api/health")
async def health_check():
return ok({"ok": True, "service": settings.app_name, "version": settings.version})
这段代码的作用是:
- 启动时初始化数据库
- 提供健康检查接口给 Docker Compose 探活
2. Docker Compose 里的核心配置
yaml
backend:
healthcheck:
test:
- CMD
- python
- -c
- import urllib.request; urllib.request.urlopen("http://127.0.0.1:8000/api/health", timeout=5)
frontend:
depends_on:
backend:
condition: service_healthy
ports:
- "${PUBLIC_PORT:-80}:8080"
这段配置很关键:
- 后端必须先健康,前端才启动。
- 外部端口默认走
PUBLIC_PORT,没有配置时默认是80。
3. 普通用户服务授权接口封装
ts
export function getAvailableHealthManagers(): Promise<AvailableHealthManager[]> {
return unwrap<AvailableHealthManager[]>(http.get('/data-access/available-health-managers'))
}
export function createOrUpdateDataAccessGrant(
payload: CreateDataAccessGrantPayload,
): Promise<DataAccessGrantSummary> {
return unwrap<DataAccessGrantSummary>(http.post('/data-access/grants', payload))
}
这也是最近补上的一个关键链路:
普通用户在设置页发起授权,健康管理师端才能看到对应客户。
八、测试与验证
项目做完不是靠感觉,而是靠几轮实际验证。
前端验证命令:
bash
npm run type-check
npm run build
后端验证命令:
bash
python -m compileall backend\app
python -m unittest backend.tests.test_auth_api -v
部署验证命令:
bash
docker compose build --pull frontend backend
docker compose up -d
docker compose ps
在这过程中遇到过的典型问题包括:
- 后端容器
unhealthy - 前端页面请求超时
- 服务授权页一直刷新
- 线上资源还是旧包
- 浏览器缓存看不到更新
最后基本都靠日志、健康检查、资源包对比和重新构建定位出来了。
九、这个项目现在处在什么阶段
如果按实际状态来说,这个项目已经不只是"答辩项目"了。
它现在更接近下面这个形态:
- 能展示
- 能讲流程
- 能跑主线
- 能解释商业模式
- 能继续向正式落地推进
更准确一点说,它已经是一个 商用雏形。
你可以把它对外包装成:
面向体检中心、康养机构和健康管理团队的中老年智能健康管理 SaaS。
十、最后的总结
这个项目最有价值的地方,不是某一个页面,而是它把一整条健康管理链路串起来了:
text
普通用户录入与授权
健康管理师查看与随访
机构管理员管理成员与运营
后端负责数据、权限、认证和业务规则
如果只做答辩,它已经够用了。
如果继续往商用走,它还有很大的延展空间,比如:
- 真实支付
- 机构套餐订阅
- 更完整的审计日志
- 更细的权限体系
- 更稳定的号码认证
- 更完整的多机构运营后台
但不管往哪走,这个项目的主骨架已经比较清楚了。