Vue3+ThinkPHP+Node.js实战:搭建个人健康档案管理系统

发布时间:2026/10/9 22:42:29
Vue3+ThinkPHP+Node.js实战:搭建个人健康档案管理系统 我整理了家里三年的体检报告、门诊病历和用药记录数据散在纸质档案、手机截图和各个医院的公众号里每次复诊翻起来都很痛苦。干脆自己动手做了一套个人健康档案管理系统前端用 Vue 3 搭界面后端接口走 ThinkPHP 提供数据服务Node.js 则负责把开发工具链跑起来顺便承担了一个轻量的实时指标推送服务。这套系统解决的核心问题很直接把家庭成员的健康数据集中存起来随时能看趋势、查报告、设置用药提醒。这套组合初看有点混搭实际跑通之后我觉得它非常适合个人项目也适合刚接触前后端分离的开发者去完整走一遍流程。标题里的三个关键词——Nodejs、vue、thinkphp——并不是各写各的而是有明确分工。下面我把整个项目从选型、环境搭建、核心功能到部署交付的完整过程复盘一遍里面包含了不少热搜榜上出现过的坑比如 PowerShell 禁止运行 npm 脚本、Vue 里怎么播放 m3u8 视频、打包后如何放进后端工程等我尽量把排查思路和解决命令写清楚。1. 为什么是 Vue Node.js ThinkPHP一套系统的技术选型复盘1.1 先看需求再看框架健康档案到底要管理什么做系统之前我先把需求列了一张纸不急着选技术栈。个人健康档案管理系统要处理的无非是这几类数据家庭成员信息姓名、性别、生日、血型、身高体重等基础资料体检报告医院给的 PDF、图片、影像视频需要能上传、预览、归档日常指标血压、血糖、心率、体重这些是长期监测的核心数据用药记录药名、剂量、频次、起止时间最好还能有提醒就诊记录门诊时间、医院、科室、医生诊断和处方。这些需求对系统要求并不多高单机或者单服务器跑完全够用最大的压力反而是数据格式多样、展示形式复杂。PDF 要能预览体检中心的颈部血管超声录像经常是 m3u8 格式的视频流还需要把血压血糖画成趋势图。把这些需求翻译成技术语言后事情就清楚多了一个能做表格和表单的前端框架、一个能干文件上传和数据 CRUD 的后端框架、外加一个能处理视频流和实时推送的工具。1.2 三者的边界谁负责界面谁负责数据谁负责实时能力我最终确定的分工是这样的技术在本系统中的职责具体用途Vue 3前端界面用户登录、档案列表、表单录入、图表展示、视频播放ThinkPHP 6后端 API用户鉴权、健康数据 CRUD、文件上传、用药提醒接口Node.js工具链 实时服务Vite 构建、npm 依赖管理、Excel 导入脚本、SSE 指标推送Vue 部分用的是 Vue 3 组合式 API 写法加上 Element Plus 做后台界面Vite 做开发服务器和打包工具。这些工具本来就需要 Node.js 环境所以 Node.js 的第一身份是前端基础设施。但仅仅把 Node.js 当构建工具用它在这个项目里的价值就不够突出了。我在实际开发中发现ThinkPHP 做常规接口很顺手但要做类似健康监测设备实时推送这种长连接场景PHP 的阻塞模型写起来不痛快。于是我单独用 Node.js 写了一个非常轻量的 SSE 服务专门负责把血糖、心率这类变化频率高的数据推送到前端图表上。这样 Node.js 就从配套工具变成了系统的第二个后端服务标题里的三件套才算真正各司其职。1.3 为什么不换个更统一的栈这是朋友问得最多的问题既然都用 Node.js 了为什么不干脆用 NestJS 或者 Express 把后端一起写了既然用了 PHP为什么不直接用模板渲染页面省去前后端分离的麻烦我的真实想法是个人项目最重要的不是技术统一而是上手速度、维护成本、出活效率。ThinkPHP 的 ORM、验证器、中间件是我最熟悉的组合写健康档案这种带大量表单校验和文件管理的业务效率明显比用 Express 从零搭要高。Vue 和 ThinkPHP 做前后端分离好处是以后想加一个给老人用的简易版、或者做微信小程序端后端 API 可以直接复用不用重写。Node.js 在这里属于哪里需要哪里搬的角色这也是我建议初学者参考的地方不要被一个框架打天下的思路捆住项目里出现多个运行时很正常关键是每个组件解决什么问题、边界在哪要搞清楚。2. 开发环境从零到跑通Node 安装、npm 脚本权限、Vue 脚手架、ThinkPHP 运行2.1 Node.js 的安装与环境配置第一步别装错版本我在两台电脑上分别装了 Windows 和 macOS 两套环境踩过不少版本坑。Node.js 的安装本身不复杂但版本管理建议一开始就做好。Windows 上我用了 nvm-windowsmacOS 上用的 nvmLinux 同理。理由很简单项目 A 可能要求 Node 18项目 B 可能就要 Node 20没有版本切换工具就只能反复卸载安装太浪费生命。安装步骤简述如下去 Node.js 官网下载 LTS 版本安装包或者先装 nvm再用nvm install 20装指定版本Windows 安装包会根据系统架构自动配置 PATH装完务必重开一个终端窗口打开新终端执行验证命令node -v npm -vmacOS 上用 Homebrew 的话是brew install node或者brew install nvm后同步配置 shell 环境变量网上教程很多这里不多说。重点是装完后node -v能正常输出版本号才算环境配置完成。这个环节最常见的问题不是安装失败而是装完发现 npm 命令能用、node 命令也能用但项目启动脚本就是跑不起来——那就进入下面这个热搜级报错。2.2 最烦人的 PowerShell 脚本执行策略问题搜索热词里连续好几条都是这个报错我必须专门说一下npm : 无法加载文件 C:\Program Files\nodejs\npm.ps1因为在此系统上禁止运行脚本。 有关详细信息请参阅 https://go.microsoft.com/fwlink/?LinkID135170 中的 about_Execution_Policies。这个问题的本质不是 npm 坏了而是 Windows PowerShell 默认的脚本执行策略是受限的不允许加载 .ps1 脚本。npm 命令在 PowerShell 里本质上调用的就是 npm.ps1所以被拦截。排查思路应该是这样的先确认自己当前用的终端是 PowerShell 还是 CMDCMD 下通常没这个问题在 PowerShell 里执行Get-ExecutionPolicy看到返回Restricted说明确实是被策略拦了以管理员身份打开 PowerShell执行Set-ExecutionPolicy RemoteSigned按提示输入 Y 确认然后重新打开终端npm 就可以正常用了。RemoteSigned的含义值得多说一句它允许本地创建的脚本直接运行从互联网下载的脚本必须带有可信发布者签名。npm.ps1 是安装 Node.js 时本地生成的属于本地脚本所以可以执行这个策略兼顾了安全性和便利性是最常用的配置。如果你所在环境对安全要求高也可以只在当前用户下设置Set-ExecutionPolicy -Scope CurrentUser RemoteSigned。2.3 Vue 项目脚手架与依赖安装前端我选择用 Vite 作为构建工具创建项目的命令很简单npm create vuelatest health-frontend过程中可以选择 TypeScript、Vue Router、Pinia 等选项。如果之前没装过 create-vuenpm 会提示安装按 Y 继续即可。创建完成后cd health-frontend npm install npm run dev这里 npm install 时间取决于网络环境建议先把 npm 镜像源切到 npmmirror可以在用户目录下配.npmrcregistryhttps://registry.npmmirror.comElement Plus 的安装方式是按需引入我用的 Vite 插件方式npm install element-plus element-plus/icons-vue npm install -D unplugin-vue-components unplugin-auto-import然后修改vite.config.js加入两个插件组件就能自动按需导入了。这套配置做完Element Plus 的表格、表单、日期选择器用起来非常顺手做档案管理界面能省下一大半样式工作量。2.4 ThinkPHP 项目的初始化与本地运行后端我用 Composer 创建 ThinkPHP 6 项目composer create-project topthink/think health-api cd health-api php think runphp think run会启动一个 PHP 内置开发服务器默认监听 8000 端口本地调试完全够用。如果电脑没装 Composer先装 Composer 并确认 PHP 版本在 8.0 以上。ThinkPHP 项目跑起来之后建议先访问一下http://localhost:8000看到默认欢迎页说明环境正常。接下来要做的是关闭默认的multi_app设置或是按单应用模式配置路由规则我用的是单应用模式所有接口都写在route目录下的路由文件里。这里容易踩的坑是伪静态配置。ThinkPHP 的 URL 默认是index.php?s/api/xxx这种形式本地开发用php think run没感觉一旦部署到 Nginx 或者 Apache 上不配伪静态就会出现 404。线上环境我的 Nginx 配置会在第 5 节给出。2.5 Vite 开发代理让前端请求找到后端前后端分离后开发阶段会遇到跨域。最省事的方案不是在后端拼命配跨域头而是在 Vite 里做代理让浏览器感觉请求就是发给前端自己服务器的。我在vite.config.js里加了这样一段server: { port: 5173, proxy: { /api: { target: http://localhost:8000, changeOrigin: true } } }这样前端页面里所有/api/xxx的请求Vite 都会转发到http://localhost:8000也就是 ThinkPHP 的服务地址。浏览器地址栏看到的始终是localhost:5173不存在跨域问题。后端的路由只需要统一挂在/api前缀下即可。实际联调阶段我建议第一次跑通一个最简单的接口比如登录接口确认从 Vue 页面点击按钮到后端数据库返回结果的整条链路都通了再开始写复杂功能。链路不通就盲目堆代码出了问题很难定位。3. 健康档案系统的数据模型与核心功能落地3.1 数据库设计从用户到健康记录的建模思路个人健康档案的数据模型不需要太花哨但表结构要能支撑长期累积的数据。我设计了下面几张核心表这里贴出关键字段user用户表存登录账号、密码哈希、姓名、角色family_member家庭成员表关联 user存血型、身高、体重、身份证号脱敏后、关系health_record主要档案表存体检/就诊记录字段包括成员 ID、记录日期、类型、医院、科室、医生、诊断、报告文件路径vital_sign日常指标表存血压、血糖、心率、体重这是画趋势图和做提醒的主力表medication用药记录表存药名、剂量、频次、开始日期、结束日期、提醒时间。一个 user 对多个 family_member每个 family_member 对多个 health_record 和 vital_sign一对多关系足够不需要引入过复杂的设计。SQL 建表语句我就不全部贴了只提一个容易忽略的点health_record.report_file字段我存的是相对路径而非完整 URL这样换域名、迁服务器的时候不用改数据库内容。ThinkPHP 6 里我用了模型关联查询在 FamilyMember 模型里定义public function records() { return $this-hasMany(HealthRecord::class, member_id); }前端获取某个成员的全部档案时调一个接口就能把关联数据一次性取出来避免 N1 查询问题。3.2 登录鉴权与 Vue 路由守卫系统有看自己的数据和管理整个家庭档案两种角色权限核心逻辑是未登录用户只能看登录页普通用户只能看自己被授权的成员档案管理员可以管理全部成员。后端登录成功后返回一个 token前端存到 localStorage。Vue Router 里加一个全局前置守卫router.beforeEach((to, from, next) { const token localStorage.getItem(token) if (to.meta.requiresAuth !token) { next(/login) } else { next() } })注意一个细节不要把用户信息也全量塞进 localStorage只存 token用户姓名、角色、头像这些敏感字段应该用另一个接口按需获取。动态路由的做法是按角色从后端返回的菜单配置来注册路由比如管理员多一个成员管理路由模块普通用户不注册这个模块。这样前端跳转时天然就少了很多越权入口。后端鉴权我写在 ThinkPHP 的中间件里拦截所有/api/*请求解析 token 失败就返回 401。这个中间件注册在app/middleware.php中写起来非常快。3.3 体检报告上传、PDF 预览与 m3u8 视频播放文件上传是健康档案系统的硬需求。前后端分离下我的做法是前端用 Element Plus 的el-upload组件上传文件到 ThinkPHP 的公共上传接口后端把文件存到 public 目录下返回访问路径。PDF 预览这个需求经常有人问Vue 的 image 组件能不能显示 PDF。答案是不能Vue 不是浏览器不负责 PDF 渲染最终干活的是浏览器或者独立的前端库。我的两种实现方案简单场景直接用 iframe 嵌入浏览器预览iframe :srcpdfUrl stylewidth:100%;height:600px;/iframe需要批注、高亮、多页签等复杂场景用 pdf.js 或封装好的 pdfvuer 组件渲染到 canvas 上。个人项目我推荐先用 iframe够用且零依赖。体检中心的影像资料比如颈动脉超声的视频流经常是 m3u8 格式。m3u8 本质是一个索引文件浏览器自带 video 标签不能直接播放需要 JS 解析并分片加载。我的做法是引入 hls.jsimport Hls from hls.js function playM3u8(videoEl, src) { if (Hls.isSupported()) { const hls new Hls() hls.loadSource(src) hls.attachMedia(videoEl) } else if (videoEl.canPlayType(application/vnd.apple.mpegurl)) { videoEl.src src // Safari 原生支持 } }这样用户不需要电脑上装任何播放器插件打开网页就能看体检视频档案这也是搜索引擎里vue 播放 m3u8 免安装这个热词的正确答案。3.4 指标趋势图表与用药提醒健康档案如果只是堆报告价值不大真正有长期价值的是趋势分析。我用 ECharts 画血压、血糖、心率的折线图数据源就是vital_sign表。前端在某个成员详情页里按时间范围拉取指标数据渲染成折线图复诊时能直观看到三个月血糖走势医生也很认可这种形式。用药提醒功能我做得比较轻量后端只负责保存提醒配置前端在进入系统后启动一个定时器到点后用浏览器的 Notification API 弹通知。代码逻辑不复杂setInterval(() { const now new Date().getHours() : new Date().getMinutes() const reminders store.getters.reminders reminders.forEach(item { if (item.time now !item.doneTodayFlag) { new Notification(该吃 ${item.drugName} 了, { body: 剂量${item.dosage} }) } }) }, 30000)提醒功能一开始不必做得太重能准时弹出通知、能在页面里标记已吃这两个核心动作完成对家里老人来说就非常实用了。4. 联调阶段的典型报错与处理方法4.1 跨域问题开发和生产的解决思路完全不同开发阶段跨域靠 Vite 代理解决我在第 2.5 节已经写了配置。生产环境如果前端静态文件和后端接口不在同一个域名下还是逃不过跨域此时要在 ThinkPHP 里配跨域中间件。我实现了一个简单的跨域中间件核心逻辑就是在响应头里加上允许跨域的字段并在收到OPTIONS预检请求时直接返回 200return $response -header(Access-Control-Allow-Origin, https://health.example.com) -header(Access-Control-Allow-Methods, GET, POST, PUT, DELETE, OPTIONS) -header(Access-Control-Allow-Headers, Content-Type, Authorization);这里我给一个建议生产环境尽量让前端和后端保持同源也就是把打包出来的静态文件交给 Nginx 托管同时把/api路径反向代理到 ThinkPHP。同源之后就不需要开跨域暴露面更小遇到问题也更少。4.2 Vue 路由参数和插槽的两个高频疑问开发中 Vue Router 参数传递总是让人混淆。路由跳转时我用 query 方式传参格式如下router.push({ path: /member/detail, query: { id: memberId } })在详情页读取参数用route.query.id。如果希望参数不暴露在 URL 里可以改用 params 配合路由命名但刷新页面后 params 会丢失需要结合状态管理保存。做健康档案这种详情页场景我推荐 query 方式直观、刷新不掉。Vue 插槽slot是另一个高频话题。档案列表页我封装了一个通用卡片组件不同业务区块通过具名插槽扩展template div classrecord-card div classcard-header slot nameheader默认标题/slot /div div classcard-body slot/slot /div /div /template在使用时通过template #header传入自定义内容。插槽的本质是把组件内部留一个位置给父级填充内容理解了这个抽象再去看那些复杂的封装组件就不发怵了。另外还要提一个常见报错failed to load tsconfig vue/tsconfig/tsconfig.web.json。这个错多数是项目刚创建时依赖没有安装完整或者 tsconfig.json 里引用的vue/tsconfig包版本不对。我遇到时执行了npm install -D vue/tsconfig然后重启 Vite 开发服务器就恢复正常。如果依旧报错检查tsconfig.node.json和tsconfig.app.json里的 extends 路径是否和 node_modules 中实际安装路径一致。4.3 Node.js 在项目里真正干活的两个场景前面说了 Node.js 写了 SSE 推送服务这里贴出简化代码方便参考const http require(http) http.createServer((req, res) { res.writeHead(200, { Content-Type: text/event-stream, Cache-Control: no-cache, Connection: keep-alive }) const timer setInterval(() { res.write(data: ${JSON.stringify({ value: getLatestGlucose() })}\n\n) }, 5000) req.on(close, () clearInterval(timer)) }).listen(3001)前端用 EventSource 接收const source new EventSource(http://localhost:3001/glucose) source.onmessage (e) { const data JSON.parse(e.data) chart.appendData({ value: data.value }) }SSE 特别适合后端单向、持续推送的场景比如健康监测设备上报血糖数据前端图表实时刷新。相比 WebSocket 不用管双向通道和重连逻辑对个人项目的复杂度来说非常友好。第二个场景是 Excel 导入。家里老人原来手动记录了两年的血压台账在 Excel 里我用 Node.js 写了一个一次性脚本读 Excel 转成 JSON再调用后端导入接口批量写入vital_sign表node scripts/import-health-records.js写这类工具脚本用 Node.js 很顺手因为 npm 生态里有现成的xlsx库。这里也能体现 Node.js 在标题里的位置不是摆设——它是整个系统的外围工具层。5. 把项目交付出去源码分享、打包部署与环境配置5.1 如何把 Vue 项目源码干净地发给别人好几个人问过我vue 项目源码怎么发给别人后来我发现很多人发出去的是带着node_modules文件夹的压缩包又大又容易因路径差异跑不起来。标准的交付方式是删除项目里的node_modules和dist目录保留package.json和package-lock.json后者可以保证对方安装依赖时版本一致写一份 README注明 Node.js 版本要求以及执行顺序npm install-npm run dev环境配置里有数据库密码、密钥等内容的单独提供.env.example模板不要直接把真实配置发出去。对方拿到源码跑不起来90% 是 Node 工具链问题剩下的可能是镜像源问题。我在 README 里直接写了如果npm install很慢或者失败设置镜像源之后再装。ThinkPHP 端同理composer.json保留vendor目录不用发对方执行composer install即可。数据库迁移脚本和初始 SQL 文件一定记得放进仓库不然对方拿到手连表都没有系统根本跑不了。5.2 前端打包后如何与 ThinkPHP 一起部署经常有人搜vue 打包放进 springboot 中其实把打包后的前端放入任何后端工程托管思路都是一样的。我把步骤列一下这里以 ThinkPHP 为例执行前端构建命令npm run build生成结果在dist目录下。把dist里的文件复制到 ThinkPHP 的public目录中比如public_front/health子目录。配置 ThinkPHP 或 Nginx让静态文件请求直接命中这个目录API 请求继续转发给 index.php。生产环境我更推荐用 Nginx 来管理配置大概是这样server { listen 80; server_name health.example.com; root /var/www/health-frontend; index index.html; location /api/ { proxy_pass http://127.0.0.1:8000/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } location / { try_files $uri $uri/ /index.html; } }前端历史路由模式必须配置try_files ... /index.html否则用户直接访问health.example.com/member/detail这类带路径的 URL 会 404。5.3 上线后的日常维护与备份个人系统上线后最怕丢数据所以备份是第一优先级。我写了一个简单的定时任务每天凌晨把 MySQL 数据库导出成 SQL 文件保留最近 30 天同步到另一个磁盘目录。命令很简单mysqldump -u root -p health_db /backup/health_$(date %Y%m%d).sql文件上传的文件目录也要定期同步备份毕竟 PDF 报告和检查影像丢了很难找回。另外我建议给系统加一个简单的访问日志方便排查哪天被哪个接口大量调用我用的办法是 Nginx 的 access_log 配合 ThinkPHP 的日志。还有一个小体会个人健康档案属于长期使用的数据系统界面可以朴素但稳定性比花哨重要。我在后端对常见字段做了统一的输入校验文件上传做了类型和大小限制之前遇到过用户上传超大的体检视频导致磁盘变满的问题后来在php.ini里把post_max_size和upload_max_filesize都改为 100M并在业务层限制视频文件不大于 50M这个坑才算填上。最后再分享一点实际使用中的体会这套系统我已经用了不短的时间最满意的功能不是界面多好看而是复诊时打开网页就能调出过去半年的血糖曲线顺手把 PDF 报告投到医生屏幕上。健康档案管理这件事难的不是技术而是坚持记录所以系统里所有录入路径我都尽量设计得短手机上也能完成一次血压录入。如果你也想照着做一个我的建议是从最简单的版本开始先实现 Vue 首页 ThinkPHP 登录接口 一个健康记录表的增删改查跑通后再逐步加 PDF 预览、m3u8 播放、图表趋势和用药提醒。不要一开始就把所有依赖装齐Node 版本问题、PowerShell 权限问题、跨域问题每一个都会来一遍分阶段推进遇到报错逐个解决最后你得到的不仅是一个能用的系统还有一条完整的排错经验链。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询