做幼儿园管理系统这个项目内部代号 elx46前后断断续续花了三周时间。技术栈选的就是 Node.js 加 Vue后端用 Node.js 提供接口前端用 Vue 写管理后台给一家小型私立幼儿园搭了一套真正能跑起来的日常管理系统。最初的痛点很具体园长不想再用 Excel 登记幼儿信息接送孩子的时候家长在群里喊话、老师凭记忆认人收费记录也经常对不上账。系统做完之后档案、考勤、接送、收费这些琐碎事情才算收拢到了一起。这篇内容会把我在选型、表设计、接口开发、前端页面、部署上线过程中踩过的坑和能直接照搬的做法拆开讲一遍。适合正在做毕业设计、想给小型园所做信息化改造或者打算用 Node.js Vue 练手全栈的朋友参考。1. 整体设计与技术选型为什么是 Node.js Vue1.1 选型思路不用重框架用最顺手的组合做这套系统之前我也纠结过是不是直接上 Spring Boot后来衡量了一下实际场景还是决定用 Node.js。原因很实际这类管理系统的核心是增删改查、权限控制、文件上传和简单的数据统计没有特别复杂的计算逻辑Node.js 的 Express 框架写接口非常快前端同学也能很快上手维护。如果用 Spring Boot光环境配置和项目骨架就要折腾不少时间对一个两三周就要交付的项目来说性价比不高。前端选 Vue 而不是 React是因为 Vue 的中文资料多、学习曲线平缓配合 Element UI 做后台管理界面几乎是现成的。Vue 的单文件组件和组织方式也让页面拆得很清楚登录页、幼儿档案页、考勤页、收费页各管各的不会写成一团乱麻。整套组合可以理解成Vue 管页面展示和数据交互Node.js 管业务逻辑和数据库操作两边通过 JSON 格式的接口通信。开发时前后端可以各干各的不用等对方联调阶段再合并效率提升很明显。还有一点是部署成本。Node.js 应用在服务器上只需要装一个运行环境进程管理用 PM2反向代理用 Nginx整个链路比 Java 那套轻很多。对于小园所或者课程设计场景这种轻量部署方式更友好出了问题也好排查。1.2 模块拆解从晨检到离园的业务闭环幼儿园管理系统的模块划分我是按照幼儿园一天的实际流程来的。早上家长送孩子到园老师要做晨检记录体温和健康状况孩子进入班级后考勤状态变成已入园下午家长来接需要在门卫或者老师那里确认身份系统记录离园时间和接送人。这个流程听起来简单但涉及的角色和状态切换不少。系统里最终拆成了这几个核心模块幼儿档案管理、班级管理、教职工管理、每日考勤、接送记录、收费管理、公告通知、健康晨检记录。每个模块对应一个或几个数据表页面端就是对这些表的新增、修改、查询和删除。比如幼儿档案模块除了基本信息还要上传头像和体检报告收费模块要记录每月的托费、餐费、杂费并且区分已交和未交状态公告通知模块则是园方发布放假、活动、食谱等信息的地方家长登录后能看到自己孩子班级下的通知。模块之间不是孤立的。孩子从入园到离园考勤表里要记录状态接送记录表要关联幼儿 ID 和家长 ID收费表要关联班级信息用于生成统计报表。设计的时候把这些关系画成一张简单的脑图后面写接口和页面就不会反复改表结构。1.3 数据库设计与关系先定表再写代码我选的是 MySQL因为园方后续可能要跑报表MySQL 比 SQLite 更适合并发场景。核心表大概有这些admin 用户表同时存老师和管理员、class 班级表、student 幼儿表、attendance 考勤表、pickup_record 接送记录表、payment 收费表、notice 公告表、health_check 晨检表。用户表里的角色字段 role 用来区分 admin、teacher、parent不同角色登录后看到的功能菜单不一样。幼儿表里除了姓名、性别、出生日期还要存班级 ID、家长手机号、家庭住址、过敏史等字段。考勤表里每天每个孩子一条记录字段包括幼儿 ID、日期、入园时间、离园时间、状态。接送记录表更特殊一点一次接送可能涉及多个接送人所以我把接送人信息直接冗余在表里同时记录接送人和幼儿的关系比如爸爸、妈妈、爷爷等。表关系主要是外键关联student 表关联 class 表attendance 表关联 student 表payment 表关联 student 表。实际建表的时候我并没有开数据库外键约束而是在应用层控制逻辑关系这样后期做数据迁移或者批量导入的时候更灵活。这个取舍在小项目里很实用只要接口层把校验做够就不容易出现脏数据。2. 核心功能实现与难点拆解2.1 幼儿档案模块一张表如何撑起核心信息管理幼儿档案是整个系统使用频率最高的模块因为它牵涉到考勤、收费、接送等多个流程。字段设计上除了姓名、性别、出生日期、入园日期这些基本信息还必须包含班级 ID、家长姓名、联系电话、家庭住址、紧急联系人、过敏史和备注。过敏史这个字段千万别省实际使用中老师最关注的往往是孩子能不能吃某种食物、有没有药物过敏。照片上传我用的是 multer 中间件后端接收图片文件后存到服务器 uploads 目录数据库中只存访问路径。这里有个经验不要把图片直接转成 base64 塞进数据库否则数据表会膨胀得非常快接口响应也会变慢。统一用静态资源服务器或者 Nginx 映射 uploads 目录来处理图片访问页面加载速度会明显好很多。查询列表必须支持关键词搜索和分页搜索条件包括姓名、班级、入园日期范围。分页参数拿 page 和 pageSize后端用 limit 和 offset 实现。还应该加一个导出功能老师经常需要把班级名单导成 Excel后端可以用 node-xlsx 生成文件返回下载链接。这个功能虽然不起眼但实际用起来满意度很高。2.2 考勤与接送闭环状态流转怎么设计考勤和接送是幼儿园系统的灵魂功能。每天孩子到园后老师在前端点“入园”系统生成当天考勤记录状态为已入园离园时点“离园”同时记录离园时间。如果孩子当天请病假或者事假老师需要单独标记请假状态这样月底统计出勤率的时候可以直接按状态汇总。接送模块更有意思。国内幼儿园对安全要求高不是谁来都能接走孩子。系统里我做了接送人白名单机制每个孩子可以维护多个接送人包含姓名、电话、与孩子关系。家长来接时老师搜索孩子姓名或者手机号系统展示白名单内所有接送人老师核对身份后选择实际来接的人生成接送记录。如果是白名单以外的人接口直接拒绝并提示联系家长确认。这里最关键的一点是事务处理。考勤状态从已入园变成已离园时必须同时更新考勤表和写入接送记录如果两步操作中间出错误会出现孩子已离园但接送记录缺失的情况。Node.js 里我用数据库事务包裹这两步操作任何一步失败就整体回滚。这也是我后面运维时觉得最值得的一次设计。2.3 权限控制admin、teacher、parent 三级角色系统里有三类账号超级管理员、老师、家长。管理员可以管理所有班级和所有的数据老师只能管理自己负责的班级家长登录后只能看到自己孩子的信息。这个权限模型不复杂但容易做漏尤其是家长账号和幼儿的关联关系。后端我用 JWT 做登录态。用户登录成功后服务端签发一个包含用户 ID、角色、班级 ID、有效期等信息的 token前端每次请求在 Authorization 头里带上。后端写一个 auth 中间件解析 token、校验角色并在需要时把用户信息挂到 req 对象上。前端同样要做路由守卫。Vue Router 的 beforeEach 钩子里检查本地有没有 token没有就跳登录页有 token 再根据角色判断当前路由是否允许访问。管理员和老师的菜单不一样家长的菜单更简单这里我用了一个动态路由的方式根据角色过滤路由表后 addRoutes。前端守卫只是体验优化真正的安全边界必须放在后端这个原则一定不能省。2.4 Vue 里播放 m3u8一次历练出的经验园方有需求希望能在系统里看监控回放和教学视频视频文件大多是 m3u8 格式的分片流。一开始我用原生 video 标签直接播发现兼容性很差后来换成了 vue-video-player 配合 hls.js 来解决。具体做法是先安装 hls.js在 Vue 组件里监听视频地址变化如果是 m3u8 后缀就用 Hls 对象加载并绑定到 video 元素上如果浏览器原生支持就直接设置 video 的 src。播放器样式和进度条就交给 vue-video-player 处理。这里最坑的是跨域问题m3u8 列表文件和 ts 分片文件都必须在服务端配置跨域头否则播放器虽然能请求到索引文件但加载分片时会报错。如果文件在 Nginx 上需要在 location 里加上 Access-Control-Allow-Origin 头。还有一个细节有些 m3u8 地址带鉴权 token过期时间很短前端拿到的播放地址可能几分钟后就失效。这种情况不要在前端拼地址而是让后端接口生成短时有效的播放地址前端只管接收和播放。幼儿园的视频监控属于敏感信息这种临时地址能降低泄露风险。3. 从零到能跑的完整实操过程3.1 环境准备Node.js 安装和环境变量配置Node.js 的安装本身没什么难度但有一个极其常见的问题几乎每个新手都会遇到在 PowerShell 里执行 npm 命令时报错“无法加载文件 ...npm.ps1因为在此系统上禁止运行脚本”。这是 PowerShell 的执行策略默认禁止运行脚本导致的并不是 Node.js 装坏了。解决办法有两种。第一种是在当前项目目录下使用 cmd 终端执行 npm 命令绕过 PowerShell 的脚本策略这对于偶尔跑一下命令比较省事。第二种一劳永逸的做法是在 PowerShell 里执行Set-ExecutionPolicy RemoteSigned然后选 Yes这样本机创建的脚本和来自可信来源的脚本都可以运行。我这里更建议第二种因为在 VS Code 里经常要启动 dev server每次都切 cmd 很麻烦。Node.js 环境变量一般安装包会自动配好但如果你用了免安装版要手动把 Node.js 根目录加到 PATH。我自己习惯检查三件事node -v是否输出版本号npm -v是否正常以及 npm 的全局路径是否是预期目录。npm 默认下载慢我会设置国内镜像源npm config set registry https://registry.npmmirror.com。这一步能省下大量等待时间。3.2 后端工程初始化与核心接口后端我用的 Express项目结构按照模块来分routes放接口路由controllers放业务逻辑models放数据库操作middleware放鉴权和错误处理。先初始化项目mkdir server cd server npm init -y npm install express mysql2 cors jsonwebtoken multer入口文件不用写得很复杂核心是把中间件和路由挂载上去。举个例子登录接口的实现逻辑是接收用户名和密码查数据库比对匹配后生成 JWT 返回给前端。const express require(express); const jwt require(jsonwebtoken); const db require(./models/db); const app express(); app.use(express.json()); app.use(cors()); app.post(/api/login, async (req, res) { const { username, password } req.body; const [rows] await db.query( SELECT * FROM admin WHERE username ? AND password ?, [username, password] ); if (rows.length 0) { return res.status(401).json({ message: 用户名或密码错误 }); } const user rows[0]; const token jwt.sign( { id: user.id, role: user.role, classId: user.class_id }, your-secret-key, { expiresIn: 12h } ); res.json({ token, user: { id: user.id, username: user.username, role: user.role } }); }); app.listen(3000, () console.log(API server running on 3000));注意登录密码不能明文存生产环境至少要用 bcrypt 做哈希。上面代码只是演示流程真正写项目时要把查询用户和校验密码拆成独立函数方便后期加日志和限流。幼儿列表接口要支持分页和过滤写起来也不复杂。查询参数里带上 page、pageSize、keyword、classIdSQL 用 LIKE 模糊搜索返回结果同时包含总条数和当前页数据。分页数据格式统一成{ list, total }前端拿到后可以直接渲染表格和分页器。3.3 前端 Vue 工程初始化与页面落地前端我用的 Vue CLI 创建项目组件库选 Element UIHTTP 请求库选 axios状态管理用 Vuex。创建命令vue create web cd web npm install element-ui axios vuex vue-router入口文件需要注册 Element UI 和路由。我习惯把 axios 实例单独封装在utils/request.js里统一设置 baseURL、超时时间和请求拦截器。请求拦截器里从 localStorage 取 token加到 Authorization 头响应拦截器里判断状态码401 时清空登录信息并跳转登录页。后台页面布局用的是 Element UI 的el-container左侧菜单根据角色动态渲染。拿幼儿档案页举例就是一个搜索栏加表格加分页器。搜索栏放关键词输入框和班级选择器表格列展示孩子基本信息操作列放编辑和查看按钮。页面对应的方法就是调用接口获取数据再绑定到表格数据源上。刚开始用 Vue 的人容易把数据处理全堆在组件里页面一多就乱。我这边每个模块都会建对应的 API 文件比如api/student.js里统一放getStudentList、addStudent、updateStudent等方法页面组件只负责调用。这样接口路径改动时只需要改一个文件维护成本低很多。3.4 联调与部署从本地到服务器开发环境下前后端分离最常遇到跨域问题。解决办法不是在 axios 里写完整后端地址而是在 Vue CLI 的vue.config.js里配置 dev server 代理module.exports { devServer: { proxy: { /api: { target: http://localhost:3000, changeOrigin: true } } } }这样前端请求/api/login时会被代理到http://localhost:3000/api/login浏览器看起来是同源请求可以避免跨域。上线时我用 Nginx 托管前端打包产物并把/api反向代理到 Node.js 服务server { listen 80; server_name your-domain.com; location / { root /var/www/web/dist; try_files $uri $uri/ /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; } }这里有个关键配置是try_files ... /index.html否则 Vue Router 使用 history 模式时刷新非首页路径会直接 404。部署完用 PM2 启动 Node.js 进程设置开机自启整个系统就能长期跑了。4. 常见问题与排查技巧实录4.1 高频问题速查表下面这个表是我在开发和维护过程中遇到最多的问题基本覆盖了新手最容易卡住的几个点。问题现象根本原因解决办法npm 命令在 PowerShell 里报“禁止运行脚本”PowerShell 执行策略限制管理员身份运行Set-ExecutionPolicy RemoteSigned或用 cmd 执行 npmnpm install 速度极慢默认源在国外设置镜像源npm config set registry https://registry.npmmirror.com前端请求后端接口报跨域前后端端口不同开发环境用 devServer.proxy生产环境用 Nginx 反向代理本地路由正常部署后刷新页面 404Nginx 没配置 try_files在 location / 中加入try_files $uri $uri/ /index.html图片上传成功后访问不到静态资源路径没映射Nginx 添加 uploads 目录的 location或后端用 express.static 托管m3u8 播放只有声音没有画面ts 分片跨域或解码格式问题服务端配置跨域头并确保分片地址可公网访问这些问题里最容易被人忽视的是第一条。很多新人看到“禁止运行脚本”就以为 Node.js 安装出问题了重装好几遍还是一样的报错其实只是 PowerShell 策略在做拦截。如果你只是临时用直接在 cmd 里运行 npm 就行但长期开发还是改一下执行策略更省心。4.2 三个印象最深的调试经历第一个是 npm 脚本问题。当时我在 VS Code 里敲npm run serve结果终端直接报错npm : 无法加载文件 C:\Program Files\nodejs\npm.ps1。第一次遇到时我也挺懵后来查资料才知道是 PowerShell 的ExecutionPolicy惹的祸。我当前项目要求团队成员各自电脑都能跑所以统一让他们把策略改成 RemoteSigned同时提醒团队成员不要随意运行来历不明的脚本。第二个是 Vue Router 刷新 404。本地开发时路由很正常但打包部署到 Nginx 后只要用户停留在某个子页面刷新就会变成 404。原因是 Vue Router 的 history 模式会把路由交给前端处理但 Nginx 默认找不到对应的真实文件。最后在配置里加了一行try_files $uri $uri/ /index.html问题立即解决。这个知识点不复杂但没踩过坑的人确实想不到。第三个是 m3u8 播放的跨域问题。前端明明拿到了 m3u8 地址播放器也初始化了但一直卡在加载中。我用浏览器开发者工具看 Network发现 ts 分片请求全部被 CORS 拦截。后来我在视频文件所在的服务端统一加了跨域头播放立刻恢复正常。这个过程让我意识到处理视频流的时候不仅要看页面报错还要多关注网络面板里每个分片请求的状态。4.3 上线后还要注意的两件事系统跑起来之后真正的考验才开始。第一件事是数据备份。幼儿园的数据量不大但每天都有考勤和接送记录一旦数据库损坏恢复成本非常高。我写了一个简单的定时任务每天凌晨把 MySQL 数据库导出成 SQL 文件保留最近 30 天备份文件存到另一块磁盘或者云存储。这个习惯很笨但能救命。第二件事是账号安全。幼儿园系统里保存的是孩子的个人信息和接送记录属于比较敏感的数据。管理员密码不能太简单家长账号要绑定手机号后端接口要做简单的频率限制防止被爆破。生产环境的 JWT 密钥也不要写死在代码里最好通过环境变量注入。虽然小园所的访问量不大但这些安全习惯还是要养成后面接手任何项目都用得上。5. 一点个人体会这个项目做完之后我有几点感受特别深。第一次去园所部署的时候老师们其实不太习惯用系统他们更信任原来的纸质登记本能理解所以我特意把晨检和接送页面做得特别简单按钮能少就少大图标加大字号尽量让老师扫一眼就能知道下一步点哪里。技术上的坑可以慢慢填但如果一线用户觉得系统难用再完善的功能也白搭。如果让我重做一次我会从一开始就把“操作日志”加上。现在日志模块是后来补的虽然能用但审计粒度不够细比如某个收费记录被谁修改过、改之前是什么值这些信息目前查不到。对于涉及钱和幼儿安全的功能操作留痕特别重要建议所有做类似系统的朋友第一版就加上。最后再分享一个小技巧开发时不要把前端打包地址写死用一个环境变量区分开发、测试、生产。我吃过一次亏在本地测试完直接打包上传发现所有接口请求都指向了 localhost排查了半天才反应过来是环境变量没切。这个项目让我最大的收获不是某个框架写得多熟而是明白了系统最终要为人服务用户顺手才是真的好。