ARTICLE DETAIL

资讯详情

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

基于Node.js+Vue的企业工资管理系统开发实战与踩坑记录

基于Node.js+Vue的企业工资管理系统开发实战与踩坑记录 去年给一家做机械加工的制造企业做了一套内部管理系统核心需求就一个员工工资管理。客户那边的 HR 每个月要用 Excel 算两百多号人的工资每次都要折腾好几天还经常出现算错、漏算、核对不上的情况财务和人事互相甩锅。客户提了几个硬性要求前后端分离、必须用 Node.js Vue 这套技术栈、部署在他们自己的 Windows 服务器上。当时我就明白这活儿的难点不在业务有多复杂而在于怎么把 Node.js 生态里那些零碎的环境问题、依赖问题、权限问题在真实环境里稳妥落地。这篇就把整个项目从环境配置、数据库设计、API 实现到前端页面、以及我实际踩过的一堆坑完整记录下来给准备做类似系统的朋友一份可以直接参考的实操笔记。这套系统说到底是给三类人用的人事算工资、财务管发放、员工查工资条。加上管理员做基础数据和权限维护角色划分很清晰。市面上现成的 SaaS 工资系统不少但客户数据敏感、要私有化部署还得跟他们已有的 OA 流程打通所以自研是更合理的路径。整个项目从需求确认到上线大概花了一个月开发本身不算慢真正耗时的是那些环境配置和联调问题。下面按项目的实际推进顺序来讲。1. 项目整体设计与技术选型思路1.1 核心需求拆解动工之前先花了一周把需求理清楚。企业工资管理系统看着简单真正列出来功能点还是相当多的组织架构管理部门增删改查、部门负责人设置员工档案管理工号、姓名、性别、部门、岗位、入职日期、银行卡号、联系方式薪资项目配置基本工资、岗位工资、绩效工资、加班费、餐补、交通补贴扣款项目有社保、公积金、个税、缺勤扣款等月度工资核算选择月份批量计算所有在职员工的应发工资、实发工资工资条查看员工只能看到自己的工资明细HR 能看到全公司的报表统计部门人力成本、月度薪资总额趋势、个税汇总等需求里最容易被忽略的是权限控制。员工和 HR 看到的工资数据完全不同公司领导层可能要看报表但不能看个人明细这些都要在数据库设计和接口设计阶段就留好扩展位不然后期改起来特别痛。1.2 为什么选 Node.js Vue 而不是 Java客户明确要 Node.js Vue这个选型本身也是合理的。对比传统的 SpringBoot Vue 前后端分离方案Node.js Express 在中小型内部系统上有几个明显优势开发效率高JavaScript 全栈同语言前端组和后端组沟通成本为零运行时内存占用比 Java 低不少在 4G 内存的 Windows 服务器上跑得很轻松生态丰富Express 中间件几乎能覆盖所有常见场景对前端开发团队友好转岗成本低Vue 这边选的是 Vue 3 Element Plus Vite。Vue 3 的组合式 API 在组织业务逻辑时比 Vue 2 的选项式 API 清晰很多Element Plus 的表单组件和表格组件对后台管理系统来说简直是量身定制的。Vite 的开发服务器启动速度比 Webpack 快了一个数量级热更新体验也好得多。1.3 总体架构设计系统采用经典的前后端分离三层层级前端Vue 3 Element Plus Vite ↓ HTTP JSON 后端Node.js Express ↓ Sequelize ORM 数据库MySQL 8.0为什么用 MySQL 而不是 MongoDB工资数据对事务性、一致性要求非常高两条记录之间要对得上账关系型数据库的强一致性和事务能力是刚需。ORM 选的 Sequelize虽然它有些 API 设计比较绕但胜在文档全、社区大、坑都被人踩过了适合项目周期紧张的情况。环境方面Node.js 版本锁定为 16 LTS这很重要。后面会专门讲版本踩坑。Redis 在这个项目里没有引入因为内部系统用户量不大JWT 无状态鉴权足够应付少一个中间件就少一个部署排障的环节。2. Node.js 环境配置最多人卡住的第一关这个项目第一个坑就出现在环境上。我在这台客户服务器上装 Node.js 的时候真的有一瞬间想摔键盘。项目本身没这么复杂但环境问题花了将就一天才彻底收拾干净。2.1 Node.js 下载安装与版本选择到官网下载 LTS 版本我用的 16.x。这里有个经验不要一看到新版本就手痒装 Current 版本框架和依赖的兼容性是滞后的。很多 npm 包还停留在支持 LTS 版本的阶段用太新的 Node.js 版本会遇到莫名其妙的编译错误。安装时注意两点第一安装路径不要带中文和空格否则后续有些原生模块编译会出问题第二不要勾选安装部分自带工具那个会调 PowerShell 去跑脚本在很多公司默认策略下会失败。安装完验证一下node -v npm -v能正确输出版本号就算成功一半了。2.2 npm 源切换国内直接连官方源下载依赖速度能让你怀疑人生。项目刚开始跑npm install卡在 node_modules 下载上进度条半天不动。果断换淘宝镜像源npm config set registry https://registry.npmmirror.com换完之后下载速度立竿见影。这个配置写在用户目录下的.npmrc文件里换一台电脑或者重装系统之后要记得重新设置。2.3 npm.ps1 无法加载文件的终极解法这是热搜里出现频率最高的一个问题也是 Windows 上跑 Node.js 的人都会碰到的npm : 无法加载文件 C:\Program Files\nodejs\npm.ps1因为在此系统上禁止运行脚本原因很明确Windows PowerShell 默认执行策略是 Restricted不允许运行任何 .ps1 脚本。npm 在 PowerShell 里是以 npm.ps1 方式调用的所以直接被拦截。解决办法不是去改文件权限而是放开 PowerShell 的执行策略在管理员权限的 PowerShell 里执行Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUserRemoteSigned的含义是本地创建的脚本可以运行从网络下载的脚本必须要有可信签名。这个策略相对安全只是放行了本地脚本。设置完执行Get-ExecutionPolicy -List确认生效。如果你不想动 PowerShell 策略也可以用 CMD 绕过打开命令提示符跑 npm 命令就没这个问题。但治标不治本建议还是把执行策略改好后面跑构建脚本、自动化部署都省事。2.4 多版本管理nvm-windows这个项目踩过环境多版本共存的坑之后我回过头在开发机上装了 nvm-windows。不同老项目用的 Node 版本不一样有的要求 12有的要求 16有的要 18手工切换环境变量简直是灾难。nvm 可以轻松切换nvm install 16.20.2 nvm use 16.20.2注意nvm-windows 和已安装的 Node.js 会有冲突装 nvm 之前要把系统中已有的 Node 卸载干净并手动清理环境变量里残留的 PATH 条目。3. 数据库设计工资系统的核心命脉一套工资管理系统是否能撑住复杂的业务逻辑数据库设计占了大头。这个部分设计得不好后面写代码的时候每一个查询都会很难受。3.1 核心表结构与关系我设计的主要表有七张用户表、部门表、员工表、薪资项目表、员工薪资标准表、月度工资单表、操作日志表。员工表和用户表是分开的。员工表存的是企业真实员工档案用户表存的是系统登录账号两者通过employee_id关联。这样设计的好处是不是所有员工都有系统账号但所有员工都可以被算薪。月度工资单表是整个系统的核心字段大致如下CREATE TABLE payroll_records ( id INT PRIMARY KEY AUTO_INCREMENT, employee_id INT NOT NULL, month VARCHAR(6) NOT NULL, -- 如 202402 base_salary DECIMAL(10,2), position_salary DECIMAL(10,2), performance_salary DECIMAL(10,2), overtime_pay DECIMAL(10,2), allowance DECIMAL(10,2), social_security DECIMAL(10,2), housing_fund DECIMAL(10,2), income_tax DECIMAL(10,2), other_deductions DECIMAL(10,2), gross_pay DECIMAL(10,2), net_pay DECIMAL(10,2), status TINYINT DEFAULT 1, -- 1草稿 2已确认 3已发放 created_at DATETIME, updated_at DATETIME );关键点来了工资单表里存的是每一项算好的金额快照而不是指向薪资标准和考勤记录的引用。这是为了让历史数据不可变——你五月份的工资单不会因为后来调整了薪资标准而发生变化。3.2 薪资计算逻辑别把公式写散工资计算的基本公式是应发工资 基本工资 岗位工资 绩效工资 加班费 补贴餐补/交通/全勤... 实发工资 应发工资 - 社保个人部分 - 公积金个人部分 - 个税 - 其他扣款个税是这里最容易写错的地方。目前政策是累计预扣法按照年度累计收入逐月计算税率专业工资软件都是这么算的。我的系统刚上线时为了图省事只做了按月单次计算后来财务反馈说量大月份个税对不上才改成正规的累计预扣算法。实现的时候不要把这些公式散落在各种业务代码里应该单独抽一个salary-calculator模块输入员工薪资标准和当月考勤扣款列表输出计算后的各项目金额。这样单元测试容易写调起来也方便。3.3 为什么工资数据要留快照刚接触这个业务的时候我想过工资单不就是薪资标准加考勤算出来的吗那每次要查历史工资重新算一遍不就行了后来被现实教育了。薪资标准会调整、个税起征点会变、社保基数每年都变、甚至公司福利政策都会改。如果依赖实时计算三年前的工资条永远说不清楚当初是怎么算出来的。所以工资单数据一旦确认就不允许修改只能作废重算。这个机制和发票处理逻辑很像——发票开错了不能改只能作废重开。这也是财务审计的要求。这个设计决策在需求沟通阶段就要和客户讲清楚不然产品经理会说工资条难道不能改吗。4. 后端 API 实现从登录鉴权到工资计算后端的整体结构是 Express 应用按模块划分目录controllers、routes、models、middleware、services。所有 API 统一返回{ code, msg, data }结构前端拿到之后根据 code 做统一处理。4.1 JWT 登录鉴权与密码加密密码存储直接用 bcryptjs密码在数据库里永远以哈希形式存在。加盐由库内部处理不需要自己造轮子。登录签发 JWTconst jwt require(jsonwebtoken); function signToken(user) { return jwt.sign( { id: user.id, employeeId: user.employeeId, role: user.role }, process.env.JWT_SECRET, { expiresIn: 8h } ); } function authMiddleware(req, res, next) { const token req.headers.authorization?.split( )[1]; if (!token) return res.status(401).json({ code: 401, msg: 未登录 }); try { req.user jwt.verify(token, process.env.JWT_SECRET); next(); } catch (e) { return res.status(401).json({ code: 401, msg: 登录已过期 }); } }这里有个实际项目经验JWT_SECRET 绝对不能写在代码里从环境变量读取。系统上线的时候部署脚本里自动生成一个随机字符串写入.env文件。客户服务器上如果只是自己用还好一旦有违规操作的风险密钥泄漏等于全系统裸奔。4.2 员工管理和批量导入员工模块是最标准的 CRUD但有一个点值得单独说批量导入。客户的 HR 手里有一份现成的员工 Excel 表几百个员工一条条录进去不现实。所以系统做了 Excel 批量导入功能用 exceljs 解析上传的 xlsx 文件逐行校验必填字段、工号唯一性、部门是否存在然后把校验通过的记录批量写入数据库。导入结果要给出清晰的统计反馈成功多少条、失败多少条、失败原因是什么。不然后台管理的人根本不知道导入没成功的原因。第一次做的时候我没有区分部分成功的情况导入一报错就全量回滚后来才改成逐条记录错误、部分成功的策略。4.3 月度工资核算与导出的实现细节工资核算接口是一次批处理操作选择一个月份系统找到当前所有在职员工读取他们的薪资标准和当月的考勤扣款记录逐人计算并写入 payroll_records。这里要注意避免重复生成。用户手一抖点了两次生成就会出现同一员工同一月份两条工资单。解决办法是在employee_id month上加唯一索引并在代码里先查再写处于并发状态时也能被数据库兜住。工资导出同样用 exceljs生成标准的工资条 Excel表头是员工姓名工号部门下面按项目列出各金额项。还有一个功能是给员工发工资条 PDF 或邮件这个需求当时排期不够后来以在系统内查看替代了。4.4 三级权限控制和数据范围隔离系统有三类角色管理员、HR、普通员工。权限控制分两级一级是接口级权限用中间件做角色校验function requireRole(...roles) { return (req, res, next) { if (!roles.includes(req.user.role)) { return res.status(403).json({ code: 403, msg: 无权限 }); } next(); }; }二级是数据级权限也就是行级隔离。普通员工只能查询自己的工资单这个不能只靠前端隐藏按钮来实现后端接口里必须强制带上employeeId req.user.employeeId条件。我当时特意做了个测试用一个普通员工的账号直接调用查看别人工资条的接口验证返回的是 403 还是数据泄漏。没有这套验证权限设计就是白做。5. Vue 前端实现页面不只是展示前端这个部分技术栈是 Vue 3 Element Plus Vite。整体页面的骨架是左侧菜单栏、右侧内容区顶部是用户信息和退出登录。这种后台管理系统的布局已经很标准化了直接基于 Element Plus 的布局组件搭。5.1 路由设计与导航守卫路由这块用 Vue Router 4页面按模块组织登录页、仪表盘、员工管理、部门管理、薪资设置、工资核算、工资单列表、工资条详情、系统设置。导航守卫用来处理登录状态和角色限制router.beforeEach((to, from, next) { const token localStorage.getItem(token); if (to.meta.requiresAuth !token) { next(/login); return; } const role localStorage.getItem(role); if (to.meta.roles !to.meta.roles.includes(role)) { next(/dashboard); return; } next(); });meta.roles写在路由定义里比如工资条详情页是{ roles: [admin, hr] }普通员工登录之后点这个入口直接被重定向。这个属于体验层的权限控制——真正安全底线还是在后端接口。5.2 Axios 封装与 token 管理整个项目所有接口请求都通过一个封装好的 axios 实例走不搞散装调用。请求拦截器自动从 localStorage 取 token 放进Authorization头这样每个接口都不用手动带 tokenconst request axios.create({ baseURL: /api, timeout: 10000 }); request.interceptors.request.use((config) { const token localStorage.getItem(token); if (token) config.headers.Authorization Bearer token; return config; }); request.interceptors.response.use( (response) response.data, (error) { if (error.response?.status 401) { localStorage.removeItem(token); router.push(/login); } return Promise.reject(error); } );响应拦截器统一处理 401用户登录过期后自动踢回登录页不用每个页面单独去判断。这里有个容易踩的坑后端返回的业务错误 code 可能是 500、400这些在 HTTP 层还是 200需要在前端业务层再判断一次code字段。两层判断容易漏要约定好规则。5.3 工资条查看与月度报表可视化普通员工的工资条页面我做了个类似请假条样式的卡片把应发项和扣款项分左右两列展示底部突出显示实发工资。每一条工资单后面有个展开按钮可以查看当月各项明细。现金额统一用toFixed(2)格式化避免 JS 浮点精度搞出 0.1 0.2 ≠ 0.3 这种低级问题。报表页面用 ECharts 做了三个核心图表近一年月度工资总额折线图、各部门人力成本柱状图、薪资构成饼图。数据接口返回的是聚合后的 JSON前端只管渲染不在浏览器端做复杂计算。做图表之前记得先和 UI 或者客户确认清楚看哪些维度我第一次全按自己的想法做了一堆图表结果客户最关心的是哪个部门人力成本涨得最多其他都在好看不实用。5.4 表单校验与细节体验工资系统里面表单免不了和数字打交道Element Plus 的表单校验规则用起来很方便。数字输入框要限制只能输数字和小数点金额输入框最好做成保留两位小数。我用了一个自定义指令在 input 的输入事件里做实时过滤防止用户手敲出不合法的字符。开发环境如果装了 Vue DevTools调组件状态会方便很多。这个工具在 Chrome 扩展商店直接下Vue 3 项目对应的是 Vue.js devtools 新版本。调试路由跳转参数、监听 Pinia 状态变化都靠它装好之后排查问题效率翻倍。6. 常见问题排查与避坑实录这个项目最大的收获其实全在踩坑里。下面这几个问题我基本都实测遇到过解决方案也是验证过的。6.1 npm install 慢到抓狂或者直接失败排查步骤按这个顺序走确认是否换了国内源npm config get registry如果目录结构被之前失败的安装弄乱了删掉node_modules和package-lock.json重新装清理缓存npm cache clean --force不要轻易尝试 cnpm。cnpm 虽然快但安装出来的依赖结构和官方 npm 不一致一些包会挂掉到时候排查更痛苦。优先用官方 npm 配国内镜像源速度和稳定性都能接受。6.2 node-sass 编译报错这是前端依赖里出现频率极高的一个问题。症状是到处报错Module build failed、Python not found、node-gyp各种编译错误。根因就是 node-sass 的原生绑定只针对特定的 Node.js 版本编译你升级了 Node.js 版本它就不匹配了。解决方案从根上做不要再碰 node-sass用 sass 也就是 dart-sass 替代。同样是写 SCSSsass 是纯 JS 实现没有原生编译环节和 Node 版本完美兼容。项目里如果已经在用 sass把node-sass从依赖里删干净全局搜一下有没有漏网引用。6.3 前后端跨域问题开发环境用 Vite 的 proxy 解决export default defineConfig({ server: { proxy: { /api: { target: http://localhost:3000, changeOrigin: true, }, }, }, });前端所有请求都写/api/xxxVite 代理到后端服务浏览器看到的请求是同源的绕开跨域。生产环境更简单Nginx 反代同时托管前端静态文件和/api接口转发根本不用后端开 CORS。如果后端一定要开跨域用cors这个 npm 包设置origin: true让任意来源可访问但这只适合内网内部系统。别在生产环境不加限制地开放跨域太危险。6.4 数据库中文乱码MySQL 连接上之后写入中文变???基本是字符集没配对。第一建库时指定 utf8mb4第二连接串带上 charset?charsetutf8mb4第三Sequelize 里define.charset也统一写 utf8mb4。三层都对齐基本不会乱码。6.5 金额精度与日期时区金额运算永远不要用 JS 的浮点数。后端计算工资时ORM 里的 DECIMAL 类型到 JS 里会变成字符串或数字加减乘除处理不好就有精度问题。我的做法是计算过程统一转为分为单位做整数运算最后再除以 100 转回元。这个方案虽然写起来稍微麻烦一点但绝对不出错。日期方面月份字段用202402这种字符串而不是 Date 类型避免了时区导致的月份错位。处理日期时间用 dayjs体积小、API 顺手比 moment 强多了。最后说点实际体会这套系统做完交付之后我最大的感受是企业内部的工资管理系统技术难度真的不大难点全在业务规则和环境适配。比如工资单一旦生成不能直接改这类需求数据库的表结构设计就要提前配合不能等开发到一半才临时加约束。再比如 Windows 服务器上跑 Node.js 应用那些 PowerShell 策略、Nginx 配置、端口占用问题会消耗比写业务代码更多的时间。如果现在让我重新做一遍类似的系统我会在动工前把环境问题一次性排查干净把 Node.js 版本用 nvm 管理起来从一开始就用严格的快照表结构设计工资数据。另外权限控制这种安全底线不要依赖任何人记得做而是先在文档里列出清晰的矩阵再按矩阵逐项实现和校验。整个过程下来真正值钱的不是那几段业务代码而是从能跑到稳定跑之间那些说不太清楚的细节经验。希望这篇记录能帮你绕开其中一部分把精力留给真正有价值的业务设计上。
返回列表