☰
Node.js+Vue养老院管理系统:膳食管理与护工评价实战
2026/10/2 14:17:20 网站建设 项目流程

1. 项目概述与整体设计

1.1 项目背景与需求拆解

养老院这个场景,很多人第一反应是“不就是管吃管住嘛”,但真正深入进去你会发现,它的信息化管理远比想象中复杂。以老年人膳食管理为例:不同老人有不同慢性病,有人糖尿病不能吃含糖高的,有人高血压要控盐,有人牙口不好只能吃软食,食谱安排得一人一策。而护工评价这块就更典型了——护工服务质量直接影响老人生活质量,但评价数据散落在纸质表格里,月底统计一次要翻好几本记录本,既低效又容易遗漏。

这个项目的核心目标,就是把“膳食管理”和“护工评价”两个高频业务场景搬到线上,做一个轻量但完整的管理系统。管理员能维护老人档案、制定每周食谱、登记每日用餐情况;家属或管理人员能对护工进行多维度评分;系统自动生成统计报表,让管理者一眼看清“这个月护工整体表现如何”“哪类膳食满意度偏低”。

技术选型上,题目已经给定:后端Node.js,前端Vue。这个组合最适合这类中小型管理系统的原因是:前后端都用JavaScript,语言栈统一,一个人就能全栈搞定;Node.js的npm生态里现成的轮子多,Express框架写RESTful API非常快;Vue组件化开发做后台管理界面效率极高。整个项目能在一个月内从零到可用,对毕设、对小型团队内部系统来说都是性价比很高的路线。

这个系统适合谁参考?一类是正在做毕设的计算机专业学生,这套题目的完整度可以当全栈实训范本;另一类是想给自家非IT行业单位做信息化的开发者,比如医养机构的技术人员,照着这个思路搭一套内管系统完全可行。

1.2 技术选型:为什么是Node.js + Vue,而不是其他方案

很多人会问:这种管理系统,用Java Spring Boot + Vue不是更主流吗?为什么选Node.js?

坦白说,Java在后端领域确实老成,但Node.js在这个规模的项目上有它独特的优势。首先,内存占用低,一个养老院内部系统撑死几十个并发用户,Node.js单线程事件循环处理这种IO密集型请求绰绰有余,服务器成本可以压得很低。其次,是开发效率——Express框架中,创建一个CRUD接口只需要几行代码,配合Nodemon自动重启,改完代码立刻生效,开发节奏非常快。再一个很实际的原因:如果前端你已经选择了Vue,那么前后端统一用JavaScript/TypeScript,意味着数据模型定义、工具函数、甚至某些业务逻辑都能前后端复用,沟通成本几乎为零。

Vue这边选用Vue 3 + Element Plus是比较稳妥的选择。Vue 3的组合式API(Composition API)让代码组织更清晰,一套组件逻辑可以按功能聚合而不是按选项分散。Element Plus则直接提供了Table、Form、Dialog、Pagination这些后台管理开发中90%要用到的组件,写页面基本是在“拼积木”。

另外提一个容易被忽略的点:这套技术栈的人才供给。养老院这类单位后续要维护系统,招一个懂Vue的工程师远比招一个会Java微服务的人容易,用工成本也低一截。

1.3 系统功能模块与角色权限架构

这个系统的用户角色我最终拆成了四个:系统管理员、膳食管理员、护工、评价人员(可以是护士长或家属代表)。四类角色对应不同的功能边界,这是管理系统的地基,一定要在设计阶段就理清。

系统管理员管全局:用户管理、角色分配、数据看板。膳食管理员负责菜谱维护、食材计划、每日用餐登记。护工角色最轻,主要查看自己的排班、查看被评价的结果与改进建议。评价人员负责对护工评分、填写评价意见。

权限这块我采用RBAC(基于角色的访问控制)模型,数据库里建role表存角色信息,user表通过role_id关联角色。前端根据角色控制菜单渲染,后端在API层做二次校验——前端控制只能防君子,真正的安全防线在后端。

功能模块拆成六个:老人档案管理、食谱与膳食管理、护工信息管理、排班管理、评价中心、系统管理。其中评价中心是核心中的核心,后面我会单独展开讲。

2. 数据库设计与Node.js后端实现

2.1 数据表结构设计与关系梳理

数据库我用MySQL 8.0,设计工具用Navicat直接建库。表结构是重头戏,我列几张核心表的字段做个说明,照着抄基本不会出大问题。

用户表(sys_user)

字段类型说明
idbigint PK自增主键
usernamevarchar(50)登录名,唯一索引
passwordvarchar(100)BCrypt加密后的密码
real_namevarchar(50)真实姓名
role_idint关联角色表
phonevarchar(20)联系方式
statustinyint0禁用 1启用
create_timedatetime创建时间

老人信息表(elder):id、name、gender、age、bed_no(床号)、health_status(健康状态)、dietary_notes(饮食禁忌,如“糖尿病忌甜食”)、guardian_phone(家属电话)、create_time。

食谱表(recipe):id、meal_type(早餐/午餐/晚餐/加餐)、dish_name、ingredients(食材清单)、calories(卡路里)、nutrition_info(营养成分JSON)、适用人群标签(糖尿病适用/高血压适用/普通)、week_day(周几,1-7)、status(0停用 1启用)。

膳食记录表(meal_record):id、elder_id、recipe_id、actual_meal_time、portion(实际用餐量)、remark(备注,如“今天没胃口只吃了一半”)。

护工表(caregiver):id、user_id(关联登录账号)、name、gender、id_card、hire_date、level(星级)、status。

评价维度表(evaluation_dimension):id、dimension_name(如“服务态度”)、max_score(满分,默认10分)、sort_order。

评价记录表(evaluation_record):id、caregiver_id、elder_id(被谁评价)、evaluator_id(评价人用户ID)、score(总分)、attitude_score、skill_score、response_score、communication_score、content(文字评价)、evaluation_date。

表关系上,精髓在于评价记录表用一列存一个评分维度,而不是拆成多张表——这个项目最多四五个评价维度,拆表反而增加联表查询的复杂度。另外设计时别忘了加索引,常用查询条件如elder_id、caregiver_id、evaluation_date都要建索引,数据量过万后差别很大。

2.2 基于Express的API层设计与工程结构

后端工程结构我推荐分层清晰、不搞花活的写法:

server/ ├── app.js # 入口,创建Express实例 ├── routes/ # 路由定义,按模块分文件 │ ├── auth.js # 登录/注册 │ ├── elder.js # 老人档案 │ ├── recipe.js # 食谱管理 │ ├── mealRecord.js # 膳食记录 │ ├── caregiver.js # 护工管理 │ └── evaluation.js # 评价中心 ├── controllers/ # 控制器,处理请求参数、调用service ├── services/ # 业务逻辑层,写核心业务 ├── models/ # 数据模型,封装SQL查询 ├── middleware/ # 中间件(JWT鉴权、错误处理、日志) ├── config/ # 数据库连接配置 └── utils/ # 工具函数(jwt生成、密码加密等)

API设计遵循RESTful规范,用几个示例说明:

  • POST /api/auth/login—— 登录,返回JWT token
  • GET /api/elders?page=1&pageSize=10—— 分页查询老人列表
  • POST /api/recipes—— 新增食谱
  • GET /api/evaluations/caregiver/:id?month=2025-01—— 查询某护工某月的评价记录
  • GET /api/evaluations/stats?type=month&time=2025-01—— 评价统计数据聚合接口

JWT鉴权是这套系统的安全核心。用户登录后,后端用jsonwebtoken库生成token,载荷里包含userId和roleId,设置过期时间12小时。前端把token存在localStorage,每次请求在Axios拦截器里带上Authorization: Bearer <token>。后端写一个authMiddleware,在需要鉴权的路由上挂载,每次请求校验token合法性并解析出用户信息。

密码存储这块千万不要用MD5或者SHA1,直接用bcryptjs做哈希加盐处理,单条密码哈希耗时约100ms,对用户无感知,但对撞库攻击来说成本高到不值得。

2.3 膳食管理模块的核心逻辑实现

膳食管理这个模块,需求看起来是“维护菜谱”“记录用餐”,但真正难的是“记什么”。

对于每个老人,我设计了dietary_notes字段来标记饮食禁忌,这是一个非常关键的字段。健康老人记录“无禁忌”,糖尿病老人记录“低糖、无糖主食优先”,高血压老人记录“低盐”。在食谱表里,每条菜谱存applicable_tags字段,标注这道菜适合哪类人群。这样膳食管理员排菜谱时,系统能自动提示“这道红烧肉不适合糖尿病老人”,避免人为疏忽。

每周食谱生成是一个很讨好用的功能。我在route层提供一个GET /api/recipes/weekly接口,后端用SQL把当前周的七天食谱按meal_type分组查出来,组装成前端要好渲染的结构:

{ "monday": { "breakfast": ["小米粥", "煮鸡蛋", "馒头"], "lunch": ["红烧鱼块", "清炒时蔬", "米饭"], "dinner": ["紫菜蛋花汤", "蒸南瓜", "花卷"] }, "tuesday": { ... }, ... }

每日用餐登记的另一个可设亮点是“用餐量反馈”。老年人生病、心情不好、饭菜不合口味都会体现在用餐量上。我让护工在老人每餐后勾选用餐量(全部吃完/大部分/一半/基本没吃),后台把出餐量和实际用餐量对比,就能看出来伙食的满意度趋势。做得好的话,这个数据能直接反哺护工评价里的“膳食满意度”维度。

3. Vue前端工程与核心界面实现

3.1 工程初始化、UI组件库与项目目录规划

前端用Vite + Vue 3创建工程。有件事提醒一下——Vite相比Vue CLI最大的优势是冷启动极快,基于ES模块的按需编译,不用像webpack那样启动前全量打包,改代码浏览器即时刷新,开发体验完全不一样。创建命令很简单:

npm create vite@latest elder-care-web -- --template vue cd elder-care-web npm install npm install vue-router@4 pinia element-plus axios echarts

注意这里装的这几个依赖基本是这个项目的前端根基:vue-router负责路由,pinia负责状态管理,element-plus是UI库,axios是HTTP客户端,echarts是图表库(用于评价统计可视化)。

Element Plus这里有一个实用配置方法——按需引入。全量引入会把所有组件打进包里,即便没用到也占体积。按需引入需要装unplugin-vue-components和unplugin-auto-import插件,在vite.config.js里配置后,组件和API用到了才自动引入,打包体积能小一半左右。

目录结构按功能分模块而非按文件类型硬分:

src/ ├── api/ # 按模块封装的请求函数 │ ├── auth.js │ ├── elder.js │ ├── recipe.js │ └── evaluation.js ├── views/ # 页面级组件 │ ├── Login.vue │ ├── Dashboard.vue │ ├── elder/ │ ├── recipe/ │ ├── caregiver/ │ ├── evaluation/ │ └── system/ ├── components/ # 通用业务组件 │ ├── ElderSelect.vue │ ├── RecipeCard.vue │ └── ScoreInput.vue ├── router/index.js # 路由配置 ├── stores/ # Pinia状态管理 └── utils/request.js # Axios封装

3.2 路由设计、登录态管理与动态路由生成

路由是前端项目的命脉。由于系统有四种角色,不同角色看到的菜单不同,我采用动态路由方案——用户登录后,前端拿着后端返回的角色信息,动态注册该角色可访问的路由。

实现思路分三步:

第一步,路由表里只放所有角色都能访问的公共路由(登录页、404页)。

第二步,登录成功后,后端返回用户信息和角色标识,前端根据角色与路由的映射关系,用router.addRoute()动态添加该角色对应的路由。

第三步,在路由的beforeEach全局前置守卫里做三件事:检查token是否存在,不存在就跳登录页;检查当前访问的路由是否已注册,未注册则尝试根据用户角色动态添加;检查路由meta里的roles权限,不匹配就跳403页面。

router.beforeEach((to, from, next) => { const token = localStorage.getItem('token') if (!token && to.path !== '/login') { next('/login') return } if (token && to.path === '/login') { next('/dashboard') return } next() })

这里必须提醒一个新手常踩的坑:动态添加路由后,务必调用router.replace(to.fullPath)重新导航一次,否则页面会白屏——这是因为首次跳转时路由还没注册完,需要重进一次触发重新匹配。当初我排查这个问题花了大半个晚上。

3.3 护工评价模块的前端交互设计

评价中心的前端是整个项目最需要打磨交互的页面。我给评价人员提供的录入界面分三个区:选择被评护工、选择关联老人、评分与意见填写。

评分区我用Element Plus的el-rate组件做星星评分,五个维度各一行,每行有维度名称和对应的星星组件以及实时分值显示。用户点击星星后,前端即时汇总总分并展示在界面顶部,比如“当前总分:46/50”,让评价人可以直观感受到打分情况。

提交评价前做一个防重复提交的处理:每个评价人每天对同一护工最多提交一次评价,这是后端接口校验的规则,前端也同步判断——当评价人选中某护工后,前端调用GET /api/evaluations/check?caregiverId=xxx&evaluatorId=xxx&date=today,如果已存在记录,直接置灰提交按钮并提示“今日已评价该护工”。

评价记录列表页用Table组件展示,支持按月份、评价人、是否高分筛选。每条记录的评分用不同颜色标签区分——优秀(45分以上)绿色、合格(35-44分)蓝色、待改进(35分以下)红色。这样的视觉设计让管理者打开页面时,一眼就能锁定问题护工。

4. 关键功能实现与业务算法详解

4.1 营养配比与特殊饮食禁忌的自动提醒

这个功能我做得比较克制,没有引入营养学专业计算引擎(那会大大增加开发量),而是采用“规则配置 — 标签匹配 — 预警提示”三级方案。

在规则配置层,食谱表里每条菜谱存了calories字段和applicable_tags字段。tags用JSON数组存储,例如["糖尿病患者适用", "低盐"]。老人档案里同样存dietary_notes,例如“糖尿病、高血压”。在后端services层,写一个validateRecipeForElder函数:

function validateRecipeForElder(recipe, elder) { const elderNotes = elder.dietary_notes || '' const recipeTags = recipe.applicable_tags || [] // 规则1:老人有糖尿病,食谱必须包含"糖尿病适用" if (elderNotes.includes('糖尿病') && !recipeTags.includes('糖尿病适用')) { return { valid: false, reason: '该食谱不适合糖尿病老人' } } // 规则2:老人有高血压,食谱必须包含"低盐" if (elderNotes.includes('高血压') && !recipeTags.includes('低盐')) { return { valid: false, reason: '该食谱不适合高血压老人' } } return { valid: true } }

预警提示落在两个场景:膳食管理员在排周食谱时,系统对每一餐逐一校验所有老人的匹配情况,把不合格的菜品在界面上用红色感叹号标出来;护工在给老人登记用餐时,如果菜品与老人禁忌不匹配,同样弹窗提醒。这个功能做出来以后,食堂阿姨和管理员都反馈“再也不会给糖尿病人端甜粥了”。

4.2 评价维度设计:分数怎么算才公平

护工评价的评分算法,是这套系统里最需要用心的地方。网上很多模板系统就是“打个总星数”,但实际业务里这种评价毫无参考价值——说不上来差在哪,也就不知道改什么。

我的做法是把评价拆成五个维度,每个维度10分:服务态度、专业技能、响应速度、沟通能力、个人卫生。这五个维度覆盖了养老护理的核心素质要求。总分满分50分,45分以上为“优秀”,35分至44分为“合格”,35分以下为“待改进”。

但总分只是表面数据。月度汇总时,我还做了一个单护工维度薄弱项分析:

// 取某护工当月所有评价记录 const records = await getMonthlyRecords(caregiverId, month) // 按维度求平均分 const avgByDimension = { attitude: avg(records.map(r => r.attitude_score)), skill: avg(records.map(r => r.skill_score)), response: avg(records.map(r => r.response_score)), communication: avg(records.map(r => r.communication_score)), hygiene: avg(records.map(r => r.hygiene_score)) } // 找出最低维度,标记为薄弱项 const weakPoint = Object.entries(avgByDimension) .sort((a, b) => a[1] - b[1])[0]

这个薄弱项直接展示在护工个人月度报告里,并自动生成一句“下月重点提升项”。管理者不用再自己翻记录找问题,系统把改进方向都给出来了,这是整个系统里最“好用”的一个功能。

4.3 膳食满意度与护工评分的关联分析

模块开发到后期,我发现了一个很有价值的数据交叉点:老人“用餐量反馈”和“护工评分”之间是否存在关联?

这个洞察来源于实际业务观察——有些护工负责的老人在膳食记录里频繁出现“只吃了一半”“基本没吃”的反馈,而这位护工在“服务态度”“沟通能力”两个维度的评分也偏低。这是一致的,因为护工决定了餐食是否合胃口、是否主动询问老人对菜品的意见,也决定了老人就餐时的体验。

我写了一个关联分析接口:按月查询每位护工负责老人的平均剩饭率(1 - 实际份数/出餐份数),递归查找剩饭率超过30%且评价总分低于40分的护工名单。这个并不复杂,但做出来的“风险护工预警列表”让管理层眼前一亮——之前这两个数据从来是分开看的,现在被关联起来,可以主动发现服务问题。

5. 开发环境配置与联调阶段的坑

5.1 Node.js安装与npm脚本执行报错

这个项目的第一个拦路虎几乎都是环境问题。我遇到过很多次:在一个项目里npm start时,报错npm : 无法加载文件 npm.ps1,因为在此系统上禁止运行脚本。

这是Windows PowerShell的执行策略限制导致的,尤其是许多人在Administrator权限下装。解决办法是打开PowerShell,执行:

Set-ExecutionPolicy -ExecutionPolicy RemoteSigned

输A确认即可。这个命令把本机执行策略从Restricted改为RemoteSigned——意思是本地脚本可以运行,从网络下载的脚本需要有可信签名。装Node.js的时候我建议直接去官网下载LTS版本的安装包,不要用某些工具一键安装,那种容易装出一堆环境变量和权限问题。

Node.js装完后验证一下:

node -v npm -v

如果npm下载依赖非常慢,把默认源切换到国内镜像站,一劳永逸:

npm config set registry https://registry.npmmirror.com npm config get registry

5.2 Vue项目安装依赖与构建时的常见问题

Vue项目里依赖装到一半卡住,是很多新手会非常崩溃的问题。比较常见的是node_modules残留导致的冲突,解决办法是先删掉重来:

rm -rf node_modules package-lock.json npm install

Element Plus的按需自动导入插件偶尔会报auto-imports.d.ts找不到的错,解决方法是确保vite.config.js里配置的插件顺序正确,并且把生成的d.ts文件排除在eslint检查之外。

另外构建时容易遇到的坑是内存不足。Vue项目打包时JavaScript堆溢出,报错JavaScript heap out of memory,直接用命令加大堆内存:

"build": "node --max-old-space-size=4096 node_modules/vite/bin/vite.js build"

5.3 前后端跨域与联调的正确姿势

开发过程中前端跑在localhost:5173(Vite默认端口),后端跑在localhost:3000,浏览器直接请求后端接口必然跨域。两个解决方案都值得掌握,因为上线时通常还会遇到一次。

开发阶段用Vite自带的代理,在vite.config.js里配置:

server: { proxy: { '/api': { target: 'http://localhost:3000', changeOrigin: true } } }

前端代码里axios的baseURL直接写/api,匹配到/api开头的请求全部转发到后端3000端口。

上线阶段用Nginx做反向代理,这同时也是后端服务的兜底方案:

location /api/ { proxy_pass http://127.0.0.1:3000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; }

CORS在开发阶段有个更省事的兜底方案——后端加cors中间件,一句话允许所有跨域请求。但上线必须关掉,改成Nginx代理或者白名单方式,这是安全保障层面的事。

6. 测试重点与部署落地经验

6.1 功能测试的三个重点板块

这类管理系统测试不能只看页面通不通,要重点测三块逻辑:权限边界、数据关联完整性、统计准确性。

权限边界测试,我专门写了一个测试清单:用管理员账号能否修改普通用户密码?评价人员能否删除评价记录?护工登录后是否能看到他人评分?这类越权测试是管理系统最隐蔽的坑,前端菜单里没暴露入口不代表后端接口不存在,后端的鉴权中间件必须每一层都覆盖到。

数据关联完整性测试:删除一位老人之后,他名下的膳食记录怎么处理?我建议业务逻辑上不物理删除,而是用逻辑删除(is_deleted字段),历史评价记录也要保留。这个设计在测试时能省掉很多返工,否则真删了数据、统计报表出现负数,简直欲哭无泪。

统计准确性测试,就是要回到数据库里手工算一遍对比页面结果。比如我统计“1月所有评价记录的平均总分”,就写SQL查询再与前端图表对照,确保聚合逻辑没有偏移。

6.2 部署方案选择:一台服务器搞定前后端

这类中小型系统不需要复杂的容器编排,我最终采用的方案是一台2核4G的云服务器,系统为Ubuntu 22.04,前端用Nginx托管静态文件,后端用pm2守护Node进程。

后端部署步骤汇总:

# 1. 把server目录上传到服务器,或者用git拉取 git clone <仓库地址> cd server # 2. 安装生产依赖(跳过开发依赖,显著提速) npm install --production # 3. 用pm2启动,并设置开机自启 npm install -g pm2 pm2 start app.js --name elder-care-api pm2 save pm2 startup

前端部署步骤:

# 本地执行构建,产出dist目录 npm run build # 把dist/目录上传到服务器 /var/www/elder-care/ scp -r dist/* user@your-server:/var/www/elder-care/ # Nginx配置文件里,把根目录指向dist并配置代理

数据库这块别忘了定时备份。我写了一个简单的cron任务,每天凌晨3点用mysqldump全量备份,保留最近7天备份文件:

0 3 * * * mysqldump -u root -p*** elder_care_db > /backup/elder_$(date +\%Y\%m\%d).sql && find /backup -name "*.sql" -mtime +7 -delete

6.3 运维心得与体检式调优

系统上线运行一段时间后,一定要做一次“体检式”复盘。我当时发现的问题很典型:某个查询接口响应越来越慢。排查路径是先用后端日志看耗时,再Explain分析SQL,最终定位到evaluation_record表的查询没有走索引,手动补了caregiver_id和evaluation_date的联合索引后,响应时间从900ms降到50ms。

另外,评价中心的图表在首页Dashboard上初始化时,接口并发请求较多,我做了两个优化:一是把统计接口拆成按需加载,Dashboard页才请求;二是对月度汇总数据加了Redis缓存,缓存时间为10分钟。这个改动让首页加载速度肉眼可见地提升。

最后的实操心得

这个系统从零开始写到能交付使用,我前前后后大概用了三周多的时间。最想提醒后来者的一件事是:管理系统的核心竞争力从来不在技术多炫,而在你把业务细节吃得多透。你花两天时间研究“护工评价维度怎么设计才科学”,比花两天时间把组件库换成更新潮的框架,价值高得多。

另外一个切身的体会是,和真实的养老院管理人员沟通需求非常必要。我在项目开始之前专门去家附近的一家养老机构做了半天的访谈,问他们现在最痛的三个问题:排食谱靠手写、护工评价靠月底翻记录本、老人用餐情况靠口头转达。这三个问题直接变成了系统的三个核心功能模块。做出来的东西没有浪费一行代码。

最后分享一个可以继续扩展的方向:这个系统的老人档案、膳食记录、评价数据沉淀下来之后,可以做一套面向家属的小程序,让家属远程看到父母今天吃了什么、护工评价如何。技术上只需要把现有API对小程序端开放,再加一层家属角色权限,整个系统的价值就能再上一个台阶。这是一个比“堆技术栈”更有意义的迭代方向。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询