news 2026/9/28 7:14:43

Node.js+Vue电子报销系统设计:全栈实现与部署实践

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Node.js+Vue电子报销系统设计:全栈实现与部署实践

基于 Node.js + Vue 的财务电子报销系统设计与实现

说实话,我最初接到"基于 nodejs_vvue 的企业财务电子报销系统设计与实现"这个选题时,第一反应是:报销系统这种活儿,技术含量看着不高,但真正做起来全是细节。传统报销流程里那些痛点——纸质单据满天飞、财务审核对账靠肉眼、员工垫资周期长——每一个都在逼着你把系统需求弄清楚。我这次用 Node.js 做后端、Vue 做前端,把一个完整的电子报销系统从零搭了起来,从环境配置、数据库设计、接口开发到前端联动,中间踩了不少坑,尤其是 Windows 下 Node.js 安装和 npm 权限问题,几乎每个新手都会撞上一次。这篇就把整个设计和实现过程掰开揉碎讲清楚,适合正在做毕业设计、企业内部小工具开发,或者想入门全栈实战的读者参考。

废话不多说,先交代项目背景和技术选型的思路,然后按"环境准备 → 后端模块 → 前端实现 → 部署上线"的顺序,把每个关键环节的设计原因和实操步骤都摊开讲。

1. 为什么选择 Node.js + Vue 来做电子报销:一个不折腾的选型过程

1.1 传统报销流程的痛点,决定了系统该有什么能力

在没做系统之前,企业里的报销流程是这样的:员工出差回来,整理一摞发票、行程单,贴到报销单上,手写金额、写事由,然后找部门领导签字,再跑到财务那边排队核验。财务拿到单据后,要人工核对发票真伪、计算总额、确认预算科目,最后还要手工录入财务软件。整个过程少则三五天,多则一两周,员工垫着钱,财务加班干活,中间任何一张发票贴错了都要打回去重来。

所以电子报销系统要解决的核心问题很明确:员工在线填写报销单、拍照或上传电子发票、系统自动计算金额、按组织架构流转审批、财务在线审核并导出数据。这意味着系统至少需要用户管理、报销单管理、审批流管理、附件管理、数据统计五个核心模块。技术选型的第一步,就是确认这些模块在 Node.js 生态里都有成熟方案,不需要我重复造轮子。

1.2 为什么是 Node.js,而不是 Spring Boot 或者 PHP

这几年 Spring Boot + Vue 的前后端分离方案在中小型系统里很流行,网上模板也一堆。但我在评估后还是选了 Node.js,主要基于三个理由。

第一,开发效率。报销系统的业务逻辑不算极端复杂,但涉及的状态流转和权限分支很多。Node.js 用 JavaScript 一把梭,前后端共用一套语言,写接口和写页面的心智负担小很多,尤其是像我这种需要一个人同时搞定前后端的场景,能省下不少上下文切换的时间。

第二,生态匹配。Node.js 的express(或koa)中间件机制非常灵活,做 JWT 鉴权、文件上传、Excel 导入导出都有非常成熟的库。配合multer、jsonwebtoken、mysql2、exceljs这些模块,基本可以满足报销系统百分之九十以上的能力需求。

第三,部署简单。企业内部小系统往往没有专业的运维环境,Node.js 应用一个node app.js就能跑起来,不像 Java 应用要装 Tomcat、配 JVM 参数。配合pm2做进程守护,一台普通 Windows 服务器或者 Linux 虚拟机就能稳定运行。

这里也顺便回应一个很多人纠结的问题:Node.js 底层是不是真的用 V8 引擎?是的,Node.js 的 JavaScript 解析和运行靠的是 Chrome 的 V8 引擎,所以它的异步 I/O 能力很强,特别适合处理报销系统这种"大量短请求、偶尔上传大文件"的 IO 密集型场景。如果项目是计算密集型(比如大量复杂的财务分摊算法),那 Node.js 不是最优解,但报销审批这个场景,完全够用且表现稳定。

1.3 技术栈全景图

最终我采用的技术栈清单如下:

层次选型说明
后端框架Express路由、中间件机制成熟,文档丰富
数据库MySQL 8.0事务支持完善,报销数据强调一致性
ORMSequelize模型定义清晰,迁移方便
鉴权JWT + bcrypt无状态会话,接口鉴权简单高效
文件上传Multer支持单文件、多文件,可配大小限制
Excel 处理ExcelJS导出报销明细报表用
前端框架Vue 3组合式 API 编写逻辑更清晰
UI 组件库Element Plus表单、表格、弹窗开箱即用
前端构建Vite冷启动快,打包配置简单
状态管理Pinia替代 Vuex,TS 友好
HTTP 请求Axios请求拦截器统一处理 token
部署工具PM2进程守护,崩溃自动重启

这套组合本质上是在"快速交付"和"工程规范"之间找一个平衡点。框架不追新,但也不老旧;用的人多,遇到问题搜得到答案。这比选一个看起来很酷但社区冷清的方案,稳妥得多。

2. 开工前的第一道坎:Node.js 环境配置与 npm 在 Windows 上的权限坑

我相信不少读者看到这个标题就笑了——npm : 无法加载文件 D:\Program Files\nodejs\npm.ps1,因为在此系统上禁止运行脚本,这应该是 Node.js 新手在 Windows 上遇到的第一个"玄学"报错。我做这个报销系统项目时,重装系统后第一天就撞上了。这里把完整排查过程写出来,你照着做就行。

2.1 Node.js 安装的版本选择和安装方式

首先说版本。Node.js 官网提供两个版本线:LTS(长期支持版)和 Current(最新尝鲜版)。我的建议是 LTS,而且尽量选 18 或 20 这样的较新 LTS,因为报销系统要用的mysql2、express这些库对新版本 Node 的兼容性已经非常成熟,没必要为了尝鲜陷进依赖兼容的泥潭。

安装方式有两种:直接下载.msi安装包,或者下载.zip免安装版。新手我强烈推荐.msi,一路 Next 就行,安装包会自动帮你配置好环境变量。如果你用的是免安装版,则需要手动添加环境变量:解压到某个目录(比如D:\nodejs),然后把该目录和D:\nodejs\node_global一起加到系统的Path变量里,否则命令行里敲node -v会提示"不是内部或外部命令"。

这里有个小细节:安装完成后不要急着关终端,先开一个新的命令提示符窗口,输入下面两行命令确认安装结果:

node -v npm -v

注意一点,如果你用的是 Windows PowerShell 或 VS Code 内置终端,此时大概率会直接报 npm 的.ps1权限错误,而node -v却正常。这就是下面要讲的坑。

2.2 npm.ps1 报错的根因与完整排查链路

我遇到的具体报错是这样:

npm : 无法加载文件 D:\Program Files\nodejs\npm.ps1,因为在此系统上禁止运行脚本。 有关详细信息,请参阅 https://go.microsoft.com/fwlink/?LinkID=135170 中的 about_Execution_Policies。 所在位置 行:1 字符: 1

很多人第一次看到这个报错,以为是 Node.js 装坏了,跑去卸载重装,结果浪费了两小时还是同样的问题。实际上原因非常简单:Windows 的 PowerShell 默认不允许执行.ps1脚本文件,而 npm 提供给 PowerShell 的入口恰恰是一个 PowerShell 脚本文件(npm.ps1),所以只要是 PowerShell 环境就会直接被安全策略拦下来。

排查链路如下:

第一步,先看当前 PowerShell 的执行策略。在 PowerShell 里运行:

Get-ExecutionPolicy

如果返回Restricted,就说明系统禁止运行任何脚本文件,这正是一切问题的根源。

第二步,确认 npm 本身没问题。直接在命令提示符(cmd)里输入npm -v,如果 cmd 环境能正常输出版本号,就进一步证明了问题只出在 PowerShell 的脚本执行策略上,而不是 Node.js 安装损坏。

第三步,解决。有两个思路,我建议两个都配置上:

方案一:以管理员身份打开 PowerShell,运行:

Set-ExecutionPolicy RemoteSigned

RemoteSigned的含义是:本地脚本可以运行,从互联网下载的脚本必须有数字签名才允许运行。这是一个相对安全的设置,也是很多开发者的标准配置。

方案二:在 VS Code 里把默认终端从 PowerShell 切换成 Command Prompt(cmd),或者 Git Bash。操作方法:VS Code 里按Ctrl + Shift + P打开命令面板,输入Terminal: Select Default Profile,选择Command Prompt即可。这样npm run dev这类命令不会走.ps1脚本,也绕过了执行策略限制。

这套排查思路值得记住,因为以后装 Vue 脚手架、跑 npm 脚本时,凡是看到"禁止运行脚本"字样的报错,基本都能用同一个方法解决。

2.3 Vue 项目创建与依赖安装:从 create-vue 到 npm run dev

整个系统前端的雏形,我用官方脚手架创建。旧的写法是vue create(基于 Vue CLI),现在 Vue 3 官方推荐的是create-vue,命令如下:

npm create vue@latest

执行后会出现一系列交互式询问,比如是否使用 TypeScript、是否使用 JSX、是否需要 Pinia、是否需要 Vue Router 等。我的选择是:TypeScript 先不启用,Router 启用,Pinia 启用,其余默认。报销系统这种中后台项目,不启用 TypeScript 能少处理一些类型定义上的麻烦,快速出活优先。

依赖安装过程中还会遇到网络问题。npm 默认源在国外,国内环境下安装依赖经常卡死或者报ETIMEDOUT。我的做法是把源切到国内镜像:

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

这里多说一句:淘宝的 npm 镜像源一直在维护,用npmmirror.com这个域名是目前的推荐配置。切换之后重新执行npm install,速度会明显提升,Vue 全家桶、Element Plus 这些依赖基本一两分钟内就能装完。

依赖装完,执行npm run dev,看到终端输出:

VITE v4.x ready in 500 ms ➜ Local: http://localhost:5173/

前端骨架就算跑通了。然后建议第一时间安装 Vue DevTools 浏览器插件。它是 Vue 调试的必需品,尤其是做报销单这种嵌套很深的表单组件时,组件层级、 props 传递、状态变更在 DevTools 里一目了然,能省下大量console.log的时间。插件直接在浏览器扩展商店搜索 "Vue.js devtools" 安装即可,注意选择对应 Vue 3 的版本。

2.4 开发环境的其他配置:编辑器与 Node.js 集成

用 VS Code 还是 WebStorm?我的体验是 VS Code 搭配Volar插件是目前 Vue 3 最顺手的组合,Volar 是 Vue 官方推荐的 VS Code 插件,负责模板语法高亮、类型检查和自动补全。安装完 Volar 后,记得把 VS Code 的默认格式化器设置为Volar,不然保存时格式化可能不生效或者格式风格不稳定。

有些读者可能习惯在 PyCharm 里配置 Node.js,因为同一套开发工具里既要写 Python 又要写 Node。PyCharm 专业版可以在Settings -> Languages & Frameworks -> Node.js里指定 Node 解释器路径,然后直接在 PyCharm 的终端里跑 npm 命令。这个能跑通,但我个人还是会单独开 VS Code 写前端,因为 PyCharm 对vue单文件组件的支持始终差一点意思,前端调试体验不如 VS Code + Volar 清爽。开发工具的选择,我的原则是:哪个顺手用哪个,但不要在一个工具里硬扭所有场景。

2.5 补充:Ubuntu 等 Linux 系统下的 Node.js 安装

如果你是部署在 Linux 服务器上(比如阿里云 ECS 的 Ubuntu 20.04),安装方式其实更简单,推荐用官方推荐的 NodeSource 方式,或者直接使用nvm管理 Node 版本。这里给一套最简单的命令:

curl -fsSL https://deb.nodesource.com/setup_18.x | sudo -E bash - sudo apt-get install -y nodejs

装完验证node -v和npm -v。Linux 下通常不会出现 PowerShell 那类权限问题,但要留意的是:生产环境尽量不要用 root 用户运行 Node 服务,创建一个普通用户来跑,安全性和稳定性都会更好。

3. 后端功能拆解:报销单、审批流与金额的边界控制

环境配好之后,真正的系统实现开始了。后端我按模块拆分思路来做:先建数据库模型,再定义接口路由,最后在路由的中间件层解决鉴权和权限问题。这一节讲清楚每个模块的设计原因和关键代码思路。

3.1 数据库设计:四张核心表撑起整个报销流程

报销系统的数据库设计不需要花哨,但每一张表都要想清楚"这条数据会在哪个流程阶段被谁用到"。我最终设计了四张核心表:

表名用途关键字段
users用户表id, username, password, real_name, department_id, role, created_at
departments部门表id, name, parent_id, leader_id
reimbursements报销单主表id, user_id, department_id, title, amount, status, apply_time, audit_time
reimbursement_items报销明细表id, reimbursement_id, expense_type, description, amount, invoice_no
audit_records审批记录表id, reimbursement_id, auditor_id, action, comment, audit_time

为什么报销单要拆成主表和明细表?这是很多新手纠结的点。原因很简单:一张报销单可能包含多笔明细,比如"交通费 200、住宿费 800、餐饮费 150",如果把明细直接塞在主表里,SQL 查询和 Excel 导出的灵活性都会受限。拆成两张表后,主表只存汇总金额和状态,明细表通过外键关联,统计"这个月哪个部门交通费超标"这类问题时,一条GROUP BY就能解决。

金额字段我建议用DECIMAL(10,2)而不是FLOAT,因为浮点数在计算机里天生存在精度问题,财务场景下"0.1 + 0.2 不等于 0.3"是不能接受的。DECIMAL是字符串存储,不会丢精度。

审批记录表很多人会忽略,但它其实特别重要。审计记录是财务合规的基础,出问题时要能追踪每一步是谁在什么时间做了什么决定。前端审批意见、退回原因这些都是往这张表里写。

3.2 报表单状态流转设计:一张图看懂六种状态

整个报销系统的业务核心,是报销单的状态机设计。我定义了六种状态:

待提交(DRAFT) → 待审批(PENDING) → 审批通过(APPROVED) → 已打款(PAID) ↓ 退回(REJECTED) → 已修改(UPDATED) → 重新提交

为什么要把"待提交"和"待审批"分开?因为我允许员工保存草稿,填到一半没填完的数据不应该直接进入审批流,否则会出现大量审批人打开一看啥也没有的情况。草稿状态让员工有时间准备发票、补充说明。

状态流转的约束条件我写在逻辑层:只有当前状态为PENDING的单据才能被审批人审批;只有当前状态为DRAFT或REJECTED的单据才能被员工修改后重新提交。如果这些校验散落在前端各个页面里,很容易出现绕过校验的非法操作,所以在后端接口层做统一校验更稳妥。

3.3 API 设计与关键接口实现

后端接口遵循 RESTful 风格,核心接口清单如下:

方法路径功能权限
POST/api/auth/login登录获取 token公开
GET/api/user/info获取当前用户信息登录用户
POST/api/reimbursement创建报销单登录用户
GET/api/reimbursement/list分页查询报销单登录用户
GET/api/reimbursement/detail/:id报销单详情登录用户
PUT/api/reimbursement/:id修改报销单本人/草稿状态
POST/api/reimbursement/submit/:id提交审批本人
POST/api/reimbursement/audit/:id审批通过/退回审批人
GET/api/reimbursement/stats部门报销统计财务/管理员

这几个接口里最有技术含量的是submit和audit,因为它们涉及状态变更的并发控制。比如员工点了"提交审批",前端同时发了两个请求,如果后端不做处理,单据可能被重复提交两次,产生两条审批记录。解决方案有两种:一是数据库层加乐观锁(在reimbursements表加version字段);二是在接口层加 Redis 分布式锁。因为内部系统并发量不高,我选了乐观锁,逻辑更简单:更新时带上version条件,如果更新行数为 0,说明数据已被其他人改过,返回提示"该单据状态已更新,请刷新页面"。

登录鉴权我用 JWT 实现。用户登录成功后,服务端生成一个有效期为 8 小时的 token,后续所有请求都在Authorization头里带上这个 token。后端写一个authMiddleware,统一解析、校验 token,并挂载到req.user上,这样每个路由里都能直接拿到当前用户的 id、角色,做权限判断非常方便。密码存储用bcryptjs哈希,加盐轮数设为 10,即使数据库泄露,明文密码也不会直接暴露。

3.4 文件上传与 Excel 导出:被很多人低估的两个功能

报销系统几乎离不开附件上传:发票照片、PDF 版的电子发票、行程单截图等。我用multer处理上传,配置了大小限制为单文件 5MB,存储路径按uploads/年月分目录。

做实操时发现一个容易被忽略的问题:财务做账时需要下载原始附件,而发票文件名往往是微信、支付宝自动生成的乱码,比如"wx_camera_20240812153000.jpg"。财务下载后根本分不清哪张是哪张。我的方案是:上传成功后,后端用uuid重命名文件存储,但在数据库里保留原始文件名,接口返回时带上原始文件名和下载地址。这样展示给用户的是"上海到北京高铁票.jpg",存储层则是a3f2c1b4.jpg,两全其美。

Excel 导出用exceljs实现。财务导出某月全公司报销明细时,接口会先查数据库,组装成数组,再用 ExcelJS 写成.xlsx文件返回。这里注意一个性能问题:如果一次性导出一万行数据,内存会飙升,我的处理是分批查询(每次查 1000 条)再逐批写入 Excel,实测一万行数据导出耗时在 5 秒以内。

3.5 权限控制的三层设计

报销系统的权限不是简单的"管理员/普通用户"二元划分。我拆成了三种角色:员工、审批人、财务。员工只能操作自己的单据;审批人可以审批本部门或下级部门的单据;财务可以查看全公司的单据、执行打款操作、导出报表。

权限校验我放在三个层面:

  • 接口层:authMiddleware之后,再加一个roleMiddleware,比如requireRole('finance'),角色不匹配直接返回 403。
  • 数据层:查询列表时,普通员工默认只能查user_id = 当前用户的数据,审批人可以看到状态为PENDING且部门归属为自己的数据。
  • 前端路由层:菜单根据不同角色动态渲染,财务看不到"待审批"菜单,员工看不到"报表导出"菜单。

前端的菜单权限放第 4 节细说,后端接口这层是最重要的——哪怕有人通过浏览器直接输入 API 地址,拿不到数据,权力边界也不会漏。

4. 前端交互细节:动态路由、表单校验与审批状态可视化

后端接口写完,接下来前端要真正做出员工能用的页面。这一节里说几个我反复调过、踩过坑的地方。

4.1 动态路由还是静态路由:权限菜单的正确打开方式

报销系统有登录页、首页、报销单列表、新建报销、待审批列表、财务统计、用户管理、部门管理一共八个页面。如果不管用户角色,全部路由静态注册,那么普通员工也能在浏览器里敲#/finance/stats看到财务统计页面。虽然后端接口会拦截数据请求,但页面白屏、报错弹窗这种体验非常糟糕。

我的做法:登录成功后,后端根据用户角色返回菜单权限数组,前端用router.addRoute动态注册路由。核心代码逻辑大致如下:

// 登录后动态添加路由 const asyncRoutes = { employee: [ { path: '/reimbursement/new', component: () => import('@/views/ReimbursementNew.vue') } ], approver: [ { path: '/audit/list', component: () => import('@/views/AuditList.vue') } ], finance: [ { path: '/finance/stats', component: () => import('@/views/FinanceStats.vue') } ] }; function setupRoutes(role) { const routes = asyncRoutes[role] || []; routes.forEach(route => router.addRoute(route)); }

动态路由的好处是菜单和权限天然同步,同一套代码在不同角色眼里长成不同的系统。坏处是刷新页面时,路由注册过程是异步的,如果用户直接刷新某个子页面,可能先匹配到404。解决办法是在router.beforeEach里加一个标记:如果用户已登录但动态路由尚未注册完成,则先await注册逻辑再放行。

4.2 报销单表单:金额计算与即时校验

新建报销单页面是员工使用频率最高的页面,体验好坏直接影响整个系统的口碑。我把表单拆成三个部分:基础信息(标题、报销事由、出差日期)、明细列表(类型、金额、发票号、说明)、附件上传。

金额这块我做了一个细节:明细列表中,用户输入每行金额后,自动累加实时显示总计,并且在底部显示人民币大写。比如合计 1234.56 元,自动展示"壹仟贰佰叁拾肆元伍角陆分"。这个功能其实底层就是把数字转大写,网上有现成 JS 函数,但千万注意分和整的处理:金额到分时不用写"整",没有角分时才写。别小看这个细节,财务看到大写金额少了个"整"字,会觉得系统不专业。

表单校验用 Element Plus 的表单验证规则,对应的规则包括:必填校验、金额必须是大于 0 的数字、发票号正则校验(允许 8 到 20 位字母数字)、附件必传。这里做的校验和后端校验保持一致——我在后端同样写了一份校验逻辑,防止绕过前端直接调接口传非法数据。前端校验是为了用户体验,后端校验才是真正的防线。

4.3 审批流的页面展现:状态流转要一眼看懂

待审批列表页,审批人看到的是所有PENDING的单据列表,每行显示申请人、部门、金额、申请时间,点击可以进入详情页。详情页上半部分是报销单内容(只读),下半部分是审批记录时间线,点击"通过"或"退回"按钮时弹窗要求填写审批意见。

这个页面的核心设计点在于:审批按钮的可点击状态完全由后端返回的状态字段驱动,前端不自行猜测。比如一张单已经是"已打款"状态,审批人无论如何都不该看到"通过/退回"按钮。这样做避免了多端状态不同步的混乱。

审批意见时间线用的是组件的timeline,每条记录显示审批人姓名、头像、动作(通过/退回)、意见内容和时间。员工提交后能清楚看到自己的单子卡在谁的环节,这个透明度对用户体验的提升非常明显。

4.4 axios 请求封装与拦截器的两个关键处理

前端所有请求统一封装在request.js里,核心是 axios 实例的拦截器配置。请求拦截器负责在发出请求前,从localStorage里取出 token,加到Authorization头里:

service.interceptors.request.use(config => { const token = localStorage.getItem('token'); if (token) { config.headers.Authorization = `Bearer ${token}`; } return config; });

响应拦截器负责统一处理两件事:业务状态码和 HTTP 错误。后端接口统一返回{ code: 0, data: ... },code为 0 才是成功;code非 0 时,拦截器弹出一个ElMessage提示错误信息。HTTP 401 时说明 token 过期,此时清除本地 token,并跳转登录页。这两个处理建议提前做好,不然每个接口都要自己写一遍错误处理逻辑,代码会冗余到没法看。

还有一个细节:文件下载类请求的响应内容不是 JSON,而是二进制流。我的做法是对responseType: 'blob'的请求单独处理,不经过统一的错误解析逻辑,而是根据Content-Disposition头里的文件名信息保存文件。

4.5 实用的自定义组件与常见小功能

整套前端做完,我沉淀了几个可以直接复用的组件:

  • MoneyInput.vue:金额输入框,自动过滤非数字字符,支持千分位展示。
  • DepartmentSelect.vue:部门树选择器,展开后可直接选择归属部门。
  • ExpenseTypeSelect.vue:报销类型下拉,配置了常用类型(交通费、住宿费、餐饮费、办公用品、差旅补助等)。
  • FileUploadList.vue:附件上传列表,支持预览图片和 PDF,支持删除、重新上传。
  • AmountToChinese.vue:金额大写展示组件。

组件封装的收益在项目后期特别明显:财务统计页需要选部门、选时间范围,直接复用DepartmentSelect.vue和日期范围组件,不用重复写模板。建议有同样开发任务的小伙伴,前两个页面写完后就把这些通用组件抽出来,之后每个页面的开发速度能快三分之一。

4.6 关于 Vue 3 自定义 v-model 与 vnodes 的实用场景

热搜词里提到vue的自定义v-model和vnodes的概念,这两个虽然敏感度不高,但在 Vue 项目里确实属于进阶知识。这里分享我的真实心得。

自定义v-model在封装表单类组件时很常用。比如封装一个只允许输入金额的MoneyInput.vue,它需要同时对外暴露"值"和"变更事件",这样父组件可以直接写:

<MoneyInput v-model="item.amount" />

自定义v-model的本质是modelValueprop 和update:modelValue事件的语法糖。我在封装组件时,会特别注意只接受modelValue作为输入,不直接修改它,而是通过 emit 让父组件更新数据,这样才能保证数据单向流动,避免复杂表单下数据状态混乱。用 Vue 3 的组合式 API 写的时候,defineProps和defineEmits的组合非常顺手,比 Options API 少了不少样板代码。

至于vnodes(虚拟节点),在报销系统里我遇到的实际场景是:根据费用类型动态渲染不同的输入控件。比如费用类型是"差旅补助"时,只需要填天数不需要填发票号;费用类型是"办公用品"时,则需要填发票号和供应商。用v-if也可以实现,但控件多了以后模板很臃肿。我后来改成用h()函数(也就是创建 vnode 的方式)动态构造表单项组件,代码更灵活,但可读性也相应地下降。如果你对vnodes还不熟,先用v-if完全没问题,不要为了炫技引入复杂性。

另外热搜词里提到的vue播放m3u8、m3u8播放器这类需求,我顺便提一句:如果企业里需要报销系统里嵌入视频或直播类的附件预览(比如某些培训报销涉及视频证据),可以考虑video.js搭配videojs-contrib-hls插件,这是目前最成熟的 m3u8 播放方案。不过常规报销系统里用到的不多,优先级不高,建议核心功能做完后再考虑。

5. 从"本地能跑"到"正式部署":联调、打包与运维的实际记录

系统开发完成只是第一步,真正考验人的是"别人也能用"。这一节讲我从本地联调、到服务器部署再到上线后稳定运行的完整操作记录。

5.1 前后端联调阶段的跨域处理

前端跑在http://localhost:5173,后端跑在http://localhost:3000,端口不同,浏览器默认会拦截跨域请求。解决办法有两个方向,我两个都试过。

方向一:后端开启 CORS。在 Express 里加一个中间件,设置允许的来源、允许的方法、允许的请求头。这种方式配置简单,生产环境也用得上,但要注意不要简单粗暴地设置Access-Control-Allow-Origin: *,最好在前端生产环境域名固定后,改为指定域名来源,否则等于把你的接口暴露给任何网站调用。

方向二:前端用 Vite 的代理功能。在vite.config.js里配置:

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

这个方案的好处是开发环境下前端请求路径保持/api,和后端接口路径一致,不用在 axios 里写完整的http://localhost:3000/api/...。部署时,同样的路径关系可以用 Nginx 反代处理。我个人的习惯是:开发环境用代理,生产环境用 Nginx,后端 Express 不额外开 CORS,这样权限边界最清晰。

5.2 前端打包构建的配置与优化

前端写完后,执行npm run build打包。Vite 默认输出到dist目录。这里有两个优化点,我是第三版部署时才补上的。

第一,代码分割。默认配置会把所有页面代码打包进一个 JS 文件,首屏加载很慢。Vite 支持按路由动态导入组件,C 端系统可能无所谓,但企业内部系统网速普遍一般,还是建议配置手动代码分包,把 Element Plus、ExcelJS 这类大体积库单独拆成 vendors 包。

第二,环境变量。用.env.production文件配置构建时的接口地址,比如VITE_API_BASE_URL=/api,这样打包出来的 JS 里不会出现localhost:3000这类开发地址。很多新手上线后页面白屏、接口 404,一半以上的原因都在这里——打包时没把接口地址切到生产环境。

5.3 部署流程与 PM2 进程守护

后端部署我用 PM2。PM2 是 Node.js 生态里最成熟的进程管理工具,它能做的事情包括:后台运行 Node 进程、崩溃自动重启、日志统一管理、多实例负载均衡。

生产环境部署步骤我整理成了脚本:

# 1. 拉取代码到服务器 git pull origin main # 2. 安装后端依赖并启动 cd server npm install --production pm2 start app.js --name reimburse-server # 3. 前端构建并将产物交给 Nginx cd ../web npm install npm run build sudo cp -r dist/* /var/www/reimburse/

PM2 有一个特别好用的命令,pm2 save配合pm2 startup可以设置开机自启,服务器重启后 Node 服务自动拉起,不需要人工干预。对没有专职运维的中小企业来说,这一个功能省了很多事。

Nginx 配置这里给一个最小可用的反代核心片段:

server { listen 80; server_name your-domain.com; location / { root /var/www/reimburse; 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 $uri $uri/ /index.html;这行。Vue 是单页应用,如果用户直接访问https://your-domain.com/reimbursement/123这种前端路由地址,Nginx 会先去找这个物理路径,找不到就会 404。加了try_files后,所有找不到的路径都会回退到index.html,由前端 Vue Router 接管处理,这是部署 Vue 单页应用必须写的一行配置。

5.4 上线后的性能与稳定性实测

系统上线后跑了一个月,我记录了这样一组数据:注册用户 120 人,月处理报销单约 600 张,平均每个审批环节耗时 4 小时。后端 Node 进程的内存占用稳定在 220MB 左右,CPU 使用率峰值不超过 25%,QPS 峰值大概 80 左右——这个负载对单机部署的 Node 应用来说,非常轻松。

唯一出现过的问题是:员工集中在下班前一小时提交报销单,导致有一段时间接口响应变慢。排查后发现瓶颈不在 Node 层,而在数据库——reimbursements表的status字段没加索引,按状态查询时全表扫描。加上索引后,查询耗时从 800ms 降到了 70ms。这个经验给到了我:数据库设计阶段,像status、user_id这类高频查询条件,一定要建索引,否则线上数据量一上来,性能问题立刻显现。

6. 总结一下我对这套系统的几点真实体会

整个项目从前端环境配置到后端接口实现再到部署上线,前后用了三周时间。真正的收获不在于代码量,而在于想清楚了几件事。

第一,报销系统的核心不是功能炫技,而是流程可控。数据一致性、状态流转、权限边界,这些"看不见"的设计比页面的美观度重要得多。财务系统出错是可以被追责的,所以每个环节都要留痕,每次状态变更都要有依据。

第二,Node.js + Vue 的组合非常适合这类企业内部工具。技术栈统一、开发效率高、部署成本低。一开始我也犹豫要不要用 Spring Boot 显得更"传统规范",但后来想明白了:工具没有高低之分,能把复杂流程稳定跑起来,能让使用者真正觉得省事,就是好工具。

第三,也是我反复强调的:环境配置的坑,每台电脑都可能不一样,一定要掌握排查思路而不是死记命令。npm.ps1权限报错、环境变量缺失、端口占用、依赖安装超时,这些问题以后大概率还会遇到,思路通了,这些都不是事。

最后分享一个小技巧:整个系统做完后,我专门用一天的测试数据把每一种异常路径都走了一遍——重复提交、附件超限、金额为负、审批人离职、跨部门审批…… 每发现一个异常就修一个。这套测试比写十个新功能都值钱,因为线上出问题时的代价远比开发时大得多。做系统的人,永远要给使用者留好后路。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/28 7:14:32

读透C网站开发书:从零搭建避坑指南,新手别再被域名服务器搞晕

读透C网站开发书:从零搭建避坑指南,新手别再被域名服务器搞晕 域名买好了吗?服务器配型选对了吗?如果你看到后台那些端口、IP、DNS记录,脑子还是一团浆糊,那这篇内容就是专门写给你的。很多刚转行做网站的朋友,手里捧着一本厚厚的《C网站开发书》,以为把代码敲完就能上线,结果卡在部署环节,看着…

作者头像 李华
网站建设 2026/9/28 7:14:23

找上海网页制作机构多少钱?3个细节避开坑,让流量自己来

找上海网页制作机构多少钱?3个细节避开坑,让流量自己来 网站做好了没人访问,比做不好更让人崩溃。你花了大几万甚至十几万,找了一家上海的网页制作机构,页面精美绝伦,结果上线三个月,后台流量数据像心电图一样平直。这时候你才想起来问一句: 到底多少钱才能做出一站能带来客户的网站?…

作者头像 李华
网站建设 2026/9/28 7:14:13

网站建设培训总结实战:3个免费工具搞定被黑痛点

网站建设培训总结实战:3个免费工具搞定被黑痛点 昨天凌晨两点,手机疯狂震动,客户发来截图,网站首页赫然挂着一堆赌博链接,后台日志一片血红。这种“网站被黑挂马不知道怎么办”的绝望感,做过运维的都知道有多窒息。别慌,深呼吸。在正式讲怎么救火之前,我得先泼盆冷水:如果你还在用那些不知名的第三方扫描器,或者…

作者头像 李华
网站建设 2026/9/28 7:14:05

独立站长必看的5类实用网站推荐与避坑指南

独立站长必看的5类实用网站推荐与避坑指南 备案流程一头雾水,域名解析半天没动静?别慌。 很多新手刚入行建站,盯着后台报错信息发呆,心里全是问号。这时候你需要一份硬核的避坑指南,而不是空喊口号。…

作者头像 李华
网站建设 2026/9/28 7:13:32

AI Engineering from Scratch:重建可验证、可审计的工业级AI流水线

1. 这不是“搭积木”&#xff0c;而是重建AI工程的地基“AI Engineering from Scratch”——看到这个标题&#xff0c;很多人第一反应是&#xff1a;“又要教人从零写Transformer&#xff1f;”或者“是不是又一个PyTorch手撕教程&#xff1f;”都不是。我带过6支AI产品交付团队…

作者头像 李华
网站建设 2026/9/28 7:13:22

告别建站拖延,用wordpress网站科学主题搞定性能优化

告别建站拖延,用wordpress网站科学主题搞定性能优化 改个按钮颜色,建站公司说下周才能排期?服务器一慢,客户流失率蹭蹭涨,你找他们要说法,得到的回复永远是“在优化了”。这种被动挨打的日子,真该结束了。很多站长把宝押在换服务器或加缓存上,却忽略了最底层的视觉逻辑。其实,一套符合…

作者头像 李华