ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

基于Vue+Node.js的个人健康档案管理系统全栈开发实践

基于Vue+Node.js的个人健康档案管理系统全栈开发实践 这段时间一直在折腾个人健康档案管理系统趁热把整个项目从选型到落地的过程整理出来。项目前端用的是Vue后端用Node.js标题里还提到了ThinkPHP这个在选型阶段确实认真对比过后面会专门说为什么最后没选它。这套系统的核心是让普通用户能录入体检数据、维护既往病史、生成健康趋势报表同时给医生端留出数据查看和异常提醒的入口。如果你正打算做类似的医疗健康类Web项目或者想看看Vue Node.js全栈开发从头到尾怎么组织这篇文章应该能帮你少走不少弯路。先说结果系统最终形态是前后端分离架构前端Vue 3 Vite Element Plus后端Express MySQL用JWT做身份鉴权部署时前端静态文件交给Nginx托管后端接口用PM2守护。整个开发周期大约三周核心功能全部跑通。整个过程里踩的坑不少尤其是Node.js环境配置、跨域、路由刷新404这几个点网上资料虽然多但碎片化严重这次一并整理成能直接照着做的完整记录。1. 项目盘点个人健康档案到底要做什么动手写代码之前先把需求彻底理清楚。这个环节看着不起眼实际决定了后面的开发速度和返工次数。1.1 从零拆解核心需求个人健康档案系统和普通的CRUD管理系统不太一样它最核心的难点不在功能多寡而在数据维度和时间跨度。一份完整的健康档案至少包含四类数据基础身份信息年龄、性别、身高体重、生理指标血压、血糖、心率、血脂、生活习惯运动频率、睡眠时长、烟酒情况、历史病历诊断记录、用药记录、过敏史。以我自己定的V1版本为例功能边界画得很清晰用户注册登录支持微信扫码绑定这个放到V2再做V1先用手机号加密码。体检报告拍照上传同时支持手动录入关键指标。指标趋势可视化血压、血糖、体重这三项一定要有折线图。医生端账号能查看授权患者的档案列表对异常指标打标。用药提醒基于用户设置的用药计划生成提醒记录这个虽然简单但是很体现健康管理的场景价值。这里有个容易被忽略的点数据的归属和授权。健康档案属于敏感数据系统里必须设计用户授权医生查看这个环节不能设计成医生能直接看到所有用户档案。我在V1就用了一张授权表搞定用户主动给医生开通查看权限随时可以关闭。1.2 技术选型为什么选了Node.js Vue没选ThinkPHP标题里提到的ThinkPHP我用过之前给单位做过几个管理后台都用的它。简单说ThinkPHP的优点是上手快、文档中文友好、单机部署方便适合PHP技术栈的团队快速交付传统服务端渲染项目。但这次做健康档案系统有几个需求点让我最终放弃了PHP方案。第一是实时性体验。健康档案涉及大量图表渲染和页面局部刷新前后端分离架构下Vue的交互体验比服务端模板渲染好得多。第二是团队技术栈延续性。我这边对JavaScript更熟Node.js后端和Vue前端共用一套语言体系类型定义、接口联调的成本低很多。第三是长连接场景。后面想给患者加在线问诊、实时提醒这类功能Node.js的事件驱动模型做WebSocket天然顺手PHP在这块要额外引入Workerman之类的方案复杂度上来了。如果你团队全是PHP背景、项目周期极短、没有复杂的实时交互需求ThinkPHP依然是好选择我这话不是贬低它。但健康档案数据可视化后续实时能力这个组合我最终站在了Node.js这边。选择本身没有绝对的对错关键是匹配场景。2. 地基工程Node.js环境配置与Vue项目初始化这块是很多人被劝退的地方。看起来只是装个软件、跑个命令实际上环境配置里藏着雷我一开始就在这栽了跟头。2.1 Node.js安装与环境变量配置含版本选择Node.js的安装本身不难去官网下载LTS版本一路Next就行。但有几个细节直接影响后续开发是否顺畅。第一个是版本选择。我吃过亏项目一开始用的Node 18后来引入了某个依赖要求Node 20升级Node之后老项目又跑不起来。现在我的建议是新项目直接上当前LTS版本比如现在Node 20或22避免装了一堆依赖后发现版本跑不了。老项目则务必使用NVMNode Version Manager来管理多个版本。Windows下装NVM稍微有点波折推荐用nvm-windows安装前先卸载已存在的Node否则会有环境变量冲突。装好之后三个命令就够用nvm install 20 nvm use 20 node -v环境变量这块正常情况下安装包会自动配置PATH不需要手动加。但有些绿色版Node或者手动解压的就需要自己配置。打开系统环境变量编辑新建NODE_HOME指向Node安装目录然后把%NODE_HOME%追加到Path里。改完之后记得重开终端别在旧终端里敲命令会读不到新配置。npm镜像也顺手配一下国内直接访问官方源下载依赖能慢到怀疑人生。命令行里执行npm config set registry https://registry.npmmirror.com配完之后npm config get registry确认下看到镜地址就说明生效了。这一步能省下大量等待时间属于必做项。2.2 用Vite快速创建Vue项目创建Vue 3项目现在官方推荐用Vite而不是Vue CLI。Vite的冷启动速度快到离谱热更新也快一个量级开发体验完全不同。npm create vuelatest执行后命令行会问你一堆问题包括是否用TypeScript、是否引入Vue Router、Pinia、ESLint等。我的选择是TypeScript选No。健康档案这类中小型项目用TS会多一层类型编写成本团队不强制的话没必要上。这个看团队习惯不做绝对推荐。Vue Router选Yes页面跳转必备。Pinia选Yes用户登录状态、健康档案的全局缓存都要用它。ESLint和Prettier选Yes多人协作时统一代码风格很重要一个人开发也可以从开始养成好习惯。项目创建后进入目录安装依赖cd personal-health npm install npm run dev浏览器访问http://localhost:5173看到Vue默认页面地基就成了。2.3 项目目录结构与基础依赖安装前端项目我习惯按下述方式组织目录。不是死规矩但一个清晰的结构能让你在三周后还记得某段代码在哪src/ api/ # 接口请求封装 assets/ # 静态资源 components/ # 公共组件 router/ # 路由配置 stores/ # Pinia数据管理 views/ # 页面组件 utils/ # 工具函数核心依赖我先装了这一批npm install element-plus axios echarts pinia vue-routerElement Plus用作UI组件库ECharts做健康趋势图表Axios统一发请求Pinia做状态管理。这套组合在Vue 3生态里最常用资料多、坑少适合当默认套件。后端部分我单独建了server目录同样初始化了一个Node项目mkdir server cd server npm init -y npm install express mysql2 cors jsonwebtokenExpress负责路由和中间件mysql2是MySQL驱动cors解决跨域jsonwebtoken签发和校验登录令牌。到这里环境和基础工程全部就绪可以开始写核心代码了。3. 后端服务搭建健康档案的数据模型与接口设计后端是整个系统的心脏虽然不那么直观出效果但接口设计的好坏直接决定前端开发是否顺畅。3.1 数据表设计与字段规划健康档案的数据表设计有个原则宁可多拆表不要全塞一张表。我设计了六张核心表各自职责单一用户表存账号信息密码用bcrypt加密存储明文存密码在真实项目里是大忌。健康档案主表存基础身体数据每次体检生成一条新记录而不是覆盖旧记录这样才有历史趋势可言。指标明细表存具体的血压、血糖、心率等字段把主表和明细表拆开的原因是指标项后续会扩展比如加一个尿酸不需要改主表结构。授权表管用户和医生的关联权限用状态字段区分有效、过期、已撤销。用药计划表和提醒记录表管提醒功能。以指标明细表为例我是这样设计字段的id INT 主键自增 record_id INT 档案记录ID indicator VARCHAR 指标编码如 blood_pressure value VARCHAR 指标值 unit VARCHAR 单位 measured_at DATETIME 测量时间indicator用编码而不是中文名是为了后续扩展和前端映射方便。前端拿到编码到配置表里查中文名、颜色、单位、正常范围展示时统一处理。3.2 RESTful接口设计与实现接口设计遵守RESTful风格资源用名词操作用HTTP方法。健康档案系统我主要设计了这些接口POST /api/auth/register注册POST /api/auth/login登录GET /api/profile获取当前用户档案POST /api/profile新建档案记录GET /api/profile/:id/trend获取指标趋势数据PUT /api/profile/:id更新档案记录DELETE /api/profile/:id删除记录GET /api/patients获取授权患者列表医生端Express里接口写起来很直接以趋势数据为例router.get(/profile/:id/trend, async (req, res) { const { id } req.params; const { indicator } req.query; const sql SELECT mi.value, mi.measured_at FROM medical_indicators mi WHERE mi.record_id ? AND mi.indicator ? ORDER BY mi.measured_at ASC ; const [rows] await db.query(sql, [id, indicator]); res.json({ code: 0, data: rows }); });这里有个细节所有接口的返回格式必须统一我固定用{ code: 0, message: , data: ... }前端封装Axios时只处理这一个结构省去各种特判。code非0时才进错误处理分支。3.3 JWT登录鉴权登录这块强烈建议直接用JWT比传统的Session方案更适合前后端分离架构。JWT的机制可以通俗理解成一张带签名的通行证用户登录成功后服务端签一张票给客户端里面存着用户ID和过期时间用服务端密钥签名保证无法伪造。之后客户端每次请求把这张票放在请求头里带过来服务端验签通过就放行。签发令牌的代码很简单const token jwt.sign( { userId: user.id, role: user.role }, process.env.JWT_SECRET, { expiresIn: 7d } );但有几个易踩坑的点。密钥千万别写死在代码里要放到.env文件加进.gitignore否则代码一上传就泄密了。过期时间设置要平衡安全性和体验我选了7天用户平常打开系统不用频繁重登。中间件校验时注意Bearer前缀的处理前端规范带Authorization: Bearer token后端解析时要先去掉Bearer 再验签忘了这一步会排查半天。4. 前端页面落地登录、档案列表与数据可视化后端接口就绪后前端的工作量主要集中在页面组织、接口对接和可视化呈现上。这一部分是用户真正看得见摸得着的体验好不好全在这里。4.1 路由搭建与动态路由Vue Router在这个项目里的配置我按照静态路由动态路由结合的方式来处理。静态路由放登录页、注册页这类不需要权限的页面动态路由则在用户登录后根据角色追加。登录页和注册页直接在路由表里写死const routes [ { path: /login, component: LoginView }, { path: /register, component: RegisterView }, { path: /, component: Layout, redirect: /dashboard, children: [...] } ];动态路由用在了角色权限上。普通用户和医生登录后的可见菜单是不一样的如果一开始就把所有路由挂上前端是能看到但没权限的接口体验混乱。用动态路由的思路是用户登录后调一次接口拿到自己的角色和权限码然后前端用router.addRoute()动态添加对应路由并渲染菜单。这里面有个隐患要注意刷新页面后Pinia里的用户状态会被清空动态路由也会丢失。解决办法是在路由守卫里加一层状态恢复逻辑每次刷新后判断Pinia里没有用户信息但本地存储有token时先重新拉取用户信息再重建动态路由。Vue Router的router.beforeEach里做这件事否则刷新到内页会直接白屏。4.2 档案管理页面表格、表单与校验档案管理页是整个系统的核心操作界面我用Element Plus的el-table展示档案记录列表用el-dialog嵌套表单实现新增和编辑用el-form自带的校验规则做前端数据校验。这里有一个比较关键的设计健康档案的指标项是动态的。用户今天可能只填血压和体重明天可能再加一个血糖。如果表单一上来把所有指标都铺开用户填写负担很大。我实现的思路是让用户自己勾选要录入的指标项然后动态渲染对应的表单项。体检日期、血压、心率这些字段的校验规则我统一放在了前端后端再做一次兜底校验。前端校验的是交互体验后端校验的是数据安全一个都不能少。比如血压的格式允许120/80这种收缩压/舒张压写法如果用户填了abc就要即时拦截。Element Plus的el-form校验规则配置很简单const rules { measuredAt: [{ required: true, message: 请选择体检日期, trigger: change }], bloodPressure: [ { pattern: /^\d{2,3}\/\d{2,3}$/, message: 格式应为120/80, trigger: blur } ] };列表页我加了分页、按日期范围筛选、关键词搜索三个基础功能。先做好这三个再谈别的花里胡哨的排序功能第一版没必要上。4.3 用ECharts做健康趋势可视化ECharts和Vue 3集成的方式我建议封装一个通用图表组件而不是在每个页面里都写一堆初始化代码。封装后页面里只需要传配置项即可。src/components/ChartCard.vue 接收 title图表标题和 optionECharts配置 内部负责初始化实例、响应式更新、窗口resize时自动重绘健康趋势图我按指标类型做了不同处理。血压用折线图同时展示收缩压和舒张压两条线让用户一眼看出来高压低压的走势。体重用面积图填充半透明背景色视觉上更柔和。血糖加一条正常范围参考线用户自己就能对照着看出有没有超标不用等医生解读。关于图表数据的前端处理有一个重要细节后端返回的数据结构和ECharts需要的数据结构不一定一致前端要写转换函数。比如后端返回的是[{ measuredAt: 2025-01-01, value: 120/80 }]画收缩压和舒张压两条线前要先把value按/拆分分别push到两个数组里。这个转换逻辑单独写成工具函数两个图重复用到的时候就不用复制粘贴了。5. 前后端联调与关键配置前后端分开写完之后联调阶段才是问题集中爆发的地方。跨域、请求封装、环境变量这些配置虽然不起眼但每一个都能卡你好几个小时。5.1 跨域问题本地开发与生产部署两套方案前后端分离开发时前端跑在5173端口后端跑在3000端口两个端口不同浏览器就会因为同源策略拦截跨域请求。本地开发时我用最简单的方式解决在后端加上cors中间件允许所有来源访问。const cors require(cors); app.use(cors());这个方案只适合作开发环境。生产环境如果把cors()这么开放着等于允许任意域名调用你的接口配合JWT一泄露就麻烦了。生产环境的正确做法是配置白名单只允许部署前端的那个域名访问const cors require(cors); app.use(cors({ origin: [https://health.example.com], credentials: true }));另一种方案是在Nginx层做反向代理前端请求/api时由Nginx转发到后端端口。这样浏览器看到的始终是同源请求后端也不需要开启cors。两种方案选一个即可我个人生产环境推荐Nginx代理配置更集中、更好排查。5.2 Axios封装与请求拦截器Axios如果不做封装每个页面都直接axios.get并手动处理token、错误码代码会迅速失控。我从项目第一天就统一封装了请求模块所有页面的请求都走同一套逻辑。import axios from axios; import { ElMessage } from element-plus; const service axios.create({ baseURL: import.meta.env.VITE_API_BASE_URL, timeout: 10000 }); service.interceptors.request.use(config { const token localStorage.getItem(token); if (token) { config.headers.Authorization Bearer ${token}; } return config; }); service.interceptors.response.use( response { const res response.data; if (res.code ! 0) { ElMessage.error(res.message || 请求失败); return Promise.reject(new Error(res.message)); } return res; }, error { if (error.response error.response.status 401) { localStorage.removeItem(token); window.location.href /login; } ElMessage.error(error.message || 网络异常); return Promise.reject(error); } );请求拦截器里做三件事自动携带token、统一处理业务错误码、401时跳转登录页。这三点做完前端页面里就可以很干净地直接调用接口错误处理全部收口到这一个地方。5.3 部署上线Nginx托管前端加PM2守护后端部署环节我遇到最多的问题是本地好好的服务器上就白屏。先说下我的标准部署流程先说前端。前端构建npm run build构建产物在dist目录把这个目录传到服务器Nginx里配置站点根目录指向它然后配置一个location规则把接口请求代理到后端端口。server { listen 80; server_name health.example.com; root /var/www/health/dist; index index.html; location /api/ { proxy_pass http://127.0.0.1:3000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } location / { try_files $uri $uri/ /index.html; } }这段配置有两个重点。第一/api的代理转发让前端请求同源规避跨域。第二try_files $uri $uri/ /index.html这段配置解决Vue Router的history模式刷新404问题。Vue Router默认的hash模式不会刷新404但URL上有#丑切到history模式后URL干净但如果Nginx不配置try_files用户刷新内页就会404。这段配置是必写项。后端部署用PM2。PM2是Node.js进程守护工具进程挂掉会自动拉起还能查看日志、设置开机自启npm install -g pm2 pm2 start server/index.js --name health-server pm2 save pm2 startuppm2 startup会提示你复制一条命令到root权限下执行这样服务器重启后PM2会自动带着后端进程起来不用人工登录。6. 实操中踩过的坑与解决方案这部分是全文最想让你看到的内容。很多问题我在网上搜了半天都是零散答案这里一次性整理成速查表照着处理就行。6.1 PowerShell禁止运行npm脚本Windows系统上第一次执行npm run dev时经常报这么一段错误npm : 无法加载文件 C:\Program Files\nodejs\npm.ps1因为在此系统上禁止运行脚本。这个问题的原因是PowerShell默认执行策略是Restricted不允许运行脚本文件npm.ps1正是一个脚本。解决方案是提升当前用户的执行策略Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser执行后选择Y确认然后再执行npm run dev就正常了。如果你用命令行时用的是Git Bash或者CMD通常不会遇到这个问题只有PowerShell会有。这个别硬背遇到就执行两三秒的事。6.2 刷新页面后动态路由全部丢失做角色权限的动态路由时我在开发环境一切正常一刷新直接白屏或404。原因是Pinia里的状态是内存级的刷新页面所有状态重置登录时注册的动态路由也跟着没了。我在路由守卫里做的恢复逻辑是这样的判断当前没有用户信息但本地存储里有token就调用GET /api/profile重新拉用户信息然后重新注册动态路由最后next({ ...to, replace: true })重新进入一次目标路由。这个写法比在网上乱找各种路由持久化插件可靠得多。6.3 ECharts图表在切换菜单后显示空白这个问题很隐蔽花了我大半天。原因是ECharts实例在容器隐藏或重新渲染后没有正确初始化。用v-if控制页面显隐时图表容器的宽度在初始化时为0图表就画不出来。我的解决方案是图表组件在nextTick之后再初始化ECharts实例并且监听容器尺寸变化时调用chart.resize()。ECharts官方支持ResizeObserver在组件挂载时监听const observer new ResizeObserver(() chart.resize()); observer.observe(chartDom.value);组件卸载时要记得observer.disconnect()和chart.dispose()否则会内存泄漏。图表空白这个问题九成出在时序上不是ECharts本身的问题。6.4 常见问题速查表问题症状推荐处理方式PowerShell禁止运行脚本npm命令执行失败Set-ExecutionPolicy -Scope CurrentUser前端请求跨域浏览器控制台CORS报错开发用cors中间件生产用Nginx代理刷新页面404history模式下路由丢失Nginx配置try_files $uri /index.html刷新白屏动态路由用户状态丢失路由守卫中做状态恢复图表空白容器隐藏后可见尺寸为0nextTick初始化 ResizeObserver重绘接口超时后端没启动或网络异常先curl后端地址确认联通性联调阶段我还有一个习惯每次前端报错先复制错误信息到控制台看Network面板确认是请求没发出、发出被拒、还是响应异常。后端配合在关键接口里加了日志输出console.log记录每次请求的路径、参数和耗时。这个习惯帮我迅速分清责任边界减少前后端互相扯皮的时间。7. 项目收尾感悟技术之外真正的价值在数据闭环写到这里系统的核心功能已经全部落地。对我来说比技术更深的体会在于个人健康档案系统真正要解决的命题。健康数据不是孤立的一串数字而是用户在数月甚至数年时间维度的生活习惯切片。单看一个血压值120/80没有意义但看到它在一个月内逐渐从140/90降下来数据就被赋予了指导价值。开发过程中我在自己的档案里连续记录了两周数据每天早上称体重、晚上量血压。系统跑起来的那天看着折线图上缓缓变化的曲线我忽然理解了为什么很多健康管理产品强调记录本身就具有干预效果。对绝大多数人来说就医频率很低平时根本不关注身体指标的日常波动。一个低门槛的记录工具能把这种隐性的变化显性化在指标连续走高时提前预警而不是等到体检报告出来才发现问题。这个项目的后续扩展方向我也想清楚了。短期先把V2的微信扫码登录做掉开放家庭共享模式让子女能查看父母的健康档案但只能只读不能修改。中期考虑接入主流体检机构的结构化报告解析用户体检完也不用手动录入。长期如果有条件可以探索基于指标趋势的智能健康建议比如连续三天血压偏高时推送低盐饮食提醒。技术上这些事情都不难难的是数据积累和用户信任。最后分享一个开发习惯层面的建议像这种带一定专业领域属性的管理系统最好在开工前花半天看看真实业务场景的流程哪怕只是去体检中心坐一会儿观察工作人员怎么录入数据、用户会问什么问题都强过自己闭门造车。我从原计划里的医生管理一切改到用户授权后医生才可见就是因为意识到健康数据的主权天然在用户手里。尊重业务场景本身往往比堆技术更值得投入。
返回列表