ARTICLE DETAIL

资讯详情

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

全栈实战:用Node.js+Vue打造新能源汽车4S店维修管理系统

全栈实战:用Node.js+Vue打造新能源汽车4S店维修管理系统 新能源汽车这波售后维保真不是老一套的“换个机油看看底盘”能糊弄得过去的电池健康检测、电机状态监控、高压系统绝缘检查每一项都牵涉到工单、配件、技师派工和结算纯靠纸质单子和 Excel 来回传迟早要出事。我这次用 Node.js Vue 做了一套新能源汽车养护 4S 店维修系统前端 Vue 3后端 Node.js数据库 MySQL标准的前后端分离 B/S 架构。做完之后最大的感受是这套系统不单单是“能跑”而是把前台接待、车间派工、配件出库、财务结算这一整条链路真正串成了一个闭环。这篇文章我会从需求拆解、技术选型、数据库设计、前后端核心代码、部署踩坑一路讲到最后的复盘心得适合正在做毕业设计的学生也适合想在门店里搞一套轻量管理系统的全栈工程师。下面开始正文。1. 整体架构与技术选型这套系统为什么能做下去1.1 从门店的真实痛点反推系统需求我刚开始接触这个需求时最先做的事情不是写代码而是先跑到一家新能源 4S 店售后车间待了两天观察他们日常怎么转。发现几个非常典型的问题前台接车时车辆信息靠手写登记保养提醒全靠售后专员脑子记维修工单填完之后就塞进文件夹配件出库和库存账本对不上财务结算又要翻半天单据。这个系统如果把“工单”作为核心物件来设计一切问题都能迎刃而解——所有业务环节都围绕着工单的创建、流转、完成去推进车辆信息、客户信息、配件出入库、结算记录全部关联到工单上数据自然就是通路的。基于这个思路系统需求被我拆成了几条主线第一门店角色分工清晰前台、技师、库管、财务、店长看到的界面和权限完全不同第二客户和车辆档案要电子化车牌、VIN、电池容量、保养周期这些关键字段一个不能少第三维修和保养流程要状态化比如“待接车、已派工、维修中、待结算、已完成”这样一环扣一环往下走第四配件库存要实时联动维修工单一旦生成相关配件库存就得同步预占和扣减第五也是客户体验最直接的一点保养到期提醒必须自动化不能让客户都跑到店门口了才发现自己车早该保养了。这些需求听起来多但本质就是一套“围绕工单状态机的数据流转系统”。技术难度并不高难的是把业务状态设计清楚把表关系规划明白。只要这两块地基打好了前端页面反而成了水到渠成的事。1.2 为什么选择 Vue Node.js而不是 Spring Boot 或者传统前后端不分离做这套系统的时候有不少人第一反应是“为什么不直接上 SpringBoot”。我理解这种惯性思维因为大家在校园或公司环境里接触的 ERP 类系统大多是基于 Java 那一套。但冷静算一下账这个项目的定位是“4S 店内部管理系统”属于典型的轻量级企业应用并发量顶多几十个账号同时在线数据规模也就是几万条工单、几万条库存记录它最需要的是快速迭代、灵活调整、前端体验好而不是微服务拆分那一套重型架构。Node.js 在这一层面的优势非常明显整个团队只要会 JavaScript前后端就能共用一套语言心智类型定义、工具函数甚至业务逻辑都能复用后端开发环境起来只需要一个node index.js没有打包、编译、部署这一串繁琐过程配合 Express 这种轻框架写一个 RESTful 接口就是三五行代码的事。Vue 这边就更不用说了Vue 3 的组合式 API 加上script setup组件逻辑的复用性大大提升配合 Element Plus 组件库后台管理系统的界面 taylor 起来效率极高。选这套技术栈还有一个很现实的原因——调试成本低。前后端分离之后前端只需要通过 axios 调后端接口后端接口用 Postman 一测就能知道结果。整套系统的开发链路非常短一个人从数据库建表做到前端页面发布基本两到三周能拿下第一版这一点对预算有限或者时间紧张的项目来说至关重要。1.3 前后端分离架构下的目录结构与交互链路这套系统的物理结构分成三个部分前端vue-4s-frontend、后端node-4s-backend、数据库 MySQL。前端跑在 5173 端口后端跑在 3000 端口开发环境通过 Vite 的代理转发解决跨域生产环境则用 Nginx 做静态资源托管和 API 反向代理。交互链路是这样的用户在浏览器里操作 Vue 页面 → 页面通过 axios 发起 HTTP 请求 → 请求被代理转发到 Node.js 后端 → 后端路由层接收请求调用业务逻辑层 → 业务逻辑通过 mysql2 驱动读写数据库 → 返回 JSON 数据给前端 → 前端用响应式数据更新页面。整个过程是无状态的后端通过 JWTJSON Web Token维护用户登录态接口鉴权统一在中间件里完成。如果对着目录看会更清晰vue-4s-frontend/ ├── src/ │ ├── api/ // axios 请求封装 │ ├── components/ // 公共组件 │ ├── router/ // 路由配置与守卫 │ ├── stores/ // Pinia 状态管理 │ ├── views/ // 页面级组件 │ └── utils/ // 工具函数 node-4s-backend/ ├── src/ │ ├── config/ // 数据库、JWT等配置 │ ├── controllers/ // 控制器层处理请求逻辑 │ ├── middlewares/ // 鉴权、错误处理中间件 │ ├── routes/ // 路由定义 │ ├── services/ // 业务逻辑层 │ └── db/ // 数据库连接与初始化这套分层结构看起来常规但它能保证一件事当你想给后端加一个“保养提醒推送”功能时只需要新增一个 route 文件、一个 controller 方法、一个 service 查询逻辑前端新增一个页面调用接口完全不必惊动其他模块。对于这种需要长期迭代的门店系统来说可维护性比性能优化更重要。2. 业务需求拆解与功能模块规划2.1 五种角色与权限控制设计4S 店维修场景里不同岗位的人虽然共享同一套数据但能做的事完全不一样。我把系统用户分为五种角色系统管理员、前台接待、车间技师、库管员、财务专员。系统管理员拥有全部权限可以创建账号、调整菜单、查看所有报表前台接待负责客户接待、创建预约、登记车辆信息、开维修工单车间技师只能看到分派给自己的工单可以填写维修记录、更新工单状态库管员管理配件库存、处理出入库单据财务专员负责结算工单费用、查看经营报表。这个权限模型在技术实现上并不复杂用的是“角色 → 菜单 → 接口权限”三级映射。前端根据用户角色动态生成路由菜单后端在每个接口的中间件里校验角色。比如新增配件的接口只允许库管和管理员调用普通技师即便拿着 URL 硬闯也会被后端 403 拦下来。这里我强烈建议一件事前端的权限控制只是增强用户体验真正的安全防线一定放在后端接口层否则别人一个请求就能绕过前端绕过所有限制。2.2 六大核心业务模块一个都不能少整个系统的主体功能我划分成了六个模块下面逐个说它们的职责和关键业务流程。预约管理模块负责保养预约和维修预约的登记、查询、改期与到店确认前台可以按日期、车牌号、手机号快速检索。车辆档案模块维护客户与车辆的基础信息包括车主姓名、联系电话、车牌号、VIN 码、品牌车型、购买日期、当前里程、电池容量这是整个系统的数据底座。维修工单模块是核心中枢从开单、派工、维修执行、完工质检到结算每一步都有状态记录和时间戳。配件库存模块维护零件字典、实时库存、入库出库记录和低库存预警出库操作必须关联到工单避免账目对不上。结算管理模块支持维修费、工时费、配件费三类费用合并计算支持折扣和多种支付方式。统计分析模块面向店长和财务展示今日接车量、营收、工单完成率、配件出库排行等经营指标。每一个模块内部都有状态流转。以维修工单为例状态机大致是“待接车 → 待派工 → 维修中 → 待质检 → 待结算 → 已完成”任何节点都有操作人、操作时间和备注这就是审计追踪。状态机在设计阶段就要考虑清楚如果等代码写完了再加状态牵一发动全身重构成本会非常高。2.3 非功能需求安全、并发与扩展性怎么平衡除了功能需求这套系统在设计时还重点考虑了三个非功能维度。安全性上密码不能明文存库我用 bcryptjs 做哈希加盐登录成功后签发 JWTToken 有效期设为 12 小时前端在请求拦截器里统一附带 Authorization 请求头。关键操作比如结算、作废、删除后端还会二次校验角色权限。并发层面4S 店单店并发撑死几十个用户MySQL 完全扛得住但要注意的一点是配件库存扣减不能出现超卖我在事务里用SELECT ... FOR UPDATE锁住配件记录再更新库存单并发场景下这个方案足够可靠。扩展性上后端接口全部走 RESTful 风格资源路径、请求方法、状态码都有统一约定后续如果要在其他门店部署只需要把前端 Nginx 配置和后端数据库连接串改一下其他代码零改动。营养模块规划的最终目标是让每一个业务动作都能“追得到源头”。谁开的单、谁派的工、谁修的、谁结算的所有链路都清晰留痕这才是门店管理系统与个人记账类小应用的本质区别。3. 环境准备与项目初始化从零到跑起来的第一步3.1 Node.js 与 npm 环境配置新手第一个拦路虎很多同学把这个系统代码拿到手之后卡住的第一关居然不是业务逻辑而是 Node.js 环境都起不来。这里我把我实际用的配置方案完整说一遍能帮你少走很多弯路。首先明确版本要求Node.js 建议 18 LTS 或 20 LTSnpm 6.9 以上即可。装 Node.js 的时候我只有一个建议——去官网下载 LTS 安装包不要用nvm装最新的尝鲜版更不要在 Windows 上手动改环境变量然后发现改错。安装完成后在终端里验证一下node -v npm -v如果出现npm : 无法加载文件 ...\npm.ps1因为在此系统上禁止运行脚本不用慌这是 PowerShell 的脚本执行策略问题不是 npm 坏了。解决方法是用管理员身份打开 PowerShell执行Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser重新打开终端再跑npm -v就能看到版本号了。macOS 用户如果不想折腾权限可以用 Homebrew 安装brew install node20装完配置一下 PATH 就行。3.2 使用 Vite 创建 Vue3 前端项目环境装好后创建 Vue 项目我推荐用 Vite而不是 Vue CLI。Vite 启动速度快得肉眼可见热更新体验比 webpack 时代好了一个量级。执行下面的命令npm create vitelatest vue-4s-frontend -- --template vue cd vue-4s-frontend npm install接下来安装项目运行需要的核心依赖路由、状态管理、UI 组件库、HTTP 请求库和图表库。我用的版本组合是 vue-router 4、pinia 2、element-plus 2、axios 1ECharts 用 5。npm install vue-router4 pinia2 element-plus axios echarts5安装完依赖后先把src/main.js改成下面这样的入口把 Element Plus 和 Pinia 都注册进去import { createApp } from vue import { createPinia } from pinia import ElementPlus from element-plus import element-plus/dist/index.css import App from ./App.vue import router from ./router const app createApp(App) app.use(createPinia()) app.use(router) app.use(ElementPlus) app.mount(#app)这一步做完前端骨架基本就立住了。后面所有页面的开发都是在views目录里新建页面、在router里挂路由、在api里封装接口请求三板斧。3.3 Express 后端骨架搭建后端我创建了一个独立的node-4s-backend目录初始化 Node 工程后安装依赖mkdir node-4s-backend cd node-4s-backend npm init -y npm install express mysql2 cors jsonwebtoken bcryptjs npm install nodemon --save-dev安装完成之后写一个最小的入口文件src/index.jsconst express require(express) const cors require(cors) const app express() app.use(cors()) app.use(express.json()) app.get(/api/health, (req, res) { res.json({ code: 200, message: server is running }) }) app.listen(3000, () { console.log(4S serve system backend running at http://localhost:3000) })然后在package.json里加上启动脚本scripts: { start: node src/index.js, dev: nodemon src/index.js }到这里前后端两个项目都能跑起来了。前后端代码能跑起来只是一个开始真正复杂的地方在于数据库设计——数据表之间的关系没设计好后面写每一个接口都会觉得别扭。4. 数据库设计与接口开发系统的“地基”工程4.1 核心表结构设计建表之前先把关系想明白数据库我用的 MySQL 8.0建库用CREATE DATABASE vehicle_4s_system DEFAULT CHARACTER SET utf8mb4;。下面这张表格展示的是核心表及其主要用途每一张都经过了一次“业务功能 → 数据字段 → 表关系”的推导过程。表名主要用途核心字段users系统用户表id, username, password, real_name, role, phone, statuscustomers客户档案表id, name, phone, id_card, address, created_atvehicles车辆档案表id, customer_id, plate_number, vin, brand, model, energy_type, battery_capacity, mileageappointments预约表id, vehicle_id, customer_id, service_type, appointment_time, status, remarkwork_orders维修工单表id, order_no, vehicle_id, customer_id, order_type, status, technician_id, total_amount, create_timeorder_items工单明细表id, order_id, item_name, item_type, quantity, unit_price, amountparts配件表id, part_code, part_name, spec, unit, stock_qty, warning_qty, purchase_price, sale_priceparts_stock_logs出入库记录表id, part_id, change_qty, type, related_order_no, operator_id, create_timesettlements结算表id, order_no, order_id, total_amount, discount, final_amount, pay_method, operator_id, pay_time表之间的关系可以这样理清一个客户可以拥有多辆车所以customers和vehicles是一对多一个预约固定对应一辆车和一个客户一个维修工单关联一辆车同时可以包含多个工单明细项配件出入库记录必须关联到具体的工单号否则库存追溯就断了结算表一对一关联维修工单一个工单只能结算一次。建表时有两个细节值得特别注意。第一所有涉及金额的字段一律用 DECIMAL(10,2)不要用 FLOAT不然算账的时候会出现 0.1 0.2 不等于 0.3 的诡异问题。第二order_no这类业务单号建议手动生成格式类似WO202501171001即 WO 加年月日加当日流水号人和机器都能快速看懂比自增 id 好用太多。4.2 RESTful API 接口规划前后端一起看这张表就够后端接口设计直接决定前端开发时的心情。我在规划的时候把接口按资源进行分组每个资源一组 CRUD 方法业务操作通过子路径来表达。模块方法与路径说明权限登录POST /api/auth/login账号密码登录返回 JWT公开用户GET /api/users获取用户列表管理员预约GET /api/appointments分页查询预约前台/管理员预约POST /api/appointments新增预约前台/管理员车辆GET /api/vehicles按车牌或车主电话查询所有登录用户车辆POST /api/vehicles新增车辆档案前台/管理员工单GET /api/work-orders按状态分页查询工单所有登录用户工单POST /api/work-orders创建维修工单前台/管理员工单PUT /api/work-orders/:id/status更新工单状态技师/管理员配件GET /api/parts查询配件列表及库存所有登录用户配件POST /api/parts/stock-out配件出库并扣减库存库管/管理员结算POST /api/settlements创建结算单财务/管理员统计GET /api/stats/overview首页仪表盘汇总数据店长/管理员接口路径的设计要点是“资源名用复数、动作交给 HTTP 方法、子操作放在冒号参数后面”。这种设计的好处是前端调用时不需要记住一堆杂乱无章的语义化路径比如/api/getOrderList、/api/deleteVehicleById这种散装的接口全部规避掉。4.3 后端核心代码实现登录鉴权、工单状态流转、库存扣减登录接口是整个系统鉴权的基础我用 bcryptjs 校验密码用 jsonwebtoken 签发令牌。代码实现如下注意密码比较用的是bcrypt.compare不能把库里存的哈希直接等于前端传过来的明文const jwt require(jsonwebtoken) const bcrypt require(bcryptjs) async function login(username, password) { const sql SELECT * FROM users WHERE username ? AND status 1 const [rows] await pool.query(sql, [username]) if (rows.length 0) throw new Error(用户不存在) const user rows[0] const valid await bcrypt.compare(password, user.password) if (!valid) throw new Error(密码错误) const token jwt.sign( { id: user.id, role: user.role, realName: user.real_name }, process.env.JWT_SECRET, { expiresIn: 12h } ) return { token, user: { id: user.id, realName: user.real_name, role: user.role } } }库存扣减是系统里最容易出并发问题的环节我用了事务配合SELECT ... FOR UPDATE做行级锁。下面这段代码表达了“配件出库并联动工单”的核心逻辑async function stockOut({ partId, quantity, orderNo, operatorId }) { const conn await pool.getConnection() try { await conn.beginTransaction() const [rows] await conn.query( SELECT stock_qty FROM parts WHERE id ? FOR UPDATE, [partId] ) if (rows.length 0) throw new Error(配件不存在) if (rows[0].stock_qty quantity) throw new Error(库存不足) await conn.query( UPDATE parts SET stock_qty stock_qty - ? WHERE id ?, [quantity, partId] ) await conn.query( INSERT INTO parts_stock_logs (part_id, change_qty, type, related_order_no, operator_id) VALUES (?, ?, ?, ?, ?), [partId, -quantity, out, orderNo, operatorId] ) await conn.commit() } catch (err) { await conn.rollback() throw err } finally { conn.release() } }这段代码的精髓在于“先锁记录再判断库存”。如果不加FOR UPDATE高并发下两个请求同时读到库存是 10然后都执行stock_qty - quantity最终库存就会变成错误的值。加上行级锁之后第二个请求必须等第一个事务提交后才能读取数据库层面就把超卖问题锁死了。工单状态流转接口相对直觉。前端传目标状态给后端后端先校验当前状态是否合法再写入新的状态和时间戳。状态机的校验规则集中在一个配置对象里比如const orderStatusFlow { pending: [assigned, cancelled], assigned: [repairing], repairing: [quality_check], quality_check: [pending_settlement], pending_settlement: [completed] }这样写的好处是状态流转逻辑集中、可读、易改后续如果要增加一个新的“返修”状态只需要在这个对象里加一条映射不需要去满仓的 service 文件里找零散的 if 判断。5. 前端页面与交互实现从裸接口到完整操作界面5.1 动态路由与登录权限控制前端第一关就是登录。用户输入账号密码后axios 发起 POST 请求拿到 JWT把 Token 存到 localStorage同时把用户角色信息放到 Pinia。这一步做完之后路由守卫就得干活了——没有 Token 一律重定向到/login有 Token 但访问了权限之外的页面则展示 403。Vue Router 守卫的实现方式是在路由配置里给每个路由加meta.roles然后在全局前置守卫里做判断router.beforeEach((to, from, next) { const token localStorage.getItem(token) if (to.path /login) { next() return } if (!token) { next(/login) return } const userRole useUserStore().role if (to.meta.roles !to.meta.roles.includes(userRole)) { next(/403) return } next() })另外一个很容易被忽略的细节是系统里不同角色的菜单应该是不同的而不是“全部显示但点击报错”。我在路由表里给每个页面配置了对应的角色数组侧边栏菜单遍历路由表时只渲染当前角色有权访问的项这样界面就不会出现“技师点库存管理结果弹个无权限”这种尴尬情况。5.2 工单管理页面的组件化实现工单管理页是这套系统里最复杂的界面但拆成组件后思路非常清晰。页面本身负责从接口拉取工单列表数据、定义筛选条件和分页状态表格组件负责展示工单的基础信息包括单号、车牌号、车型、客户、工单类型、状态、金额、开单时间操作按钮根据当前行状态动态渲染比如“待派工”状态显示“派工”按钮“维修中”状态显示“完工”按钮弹窗表单用于新建工单和派工分配表单里包含车辆选择、服务项目、维修技师下拉框。创建工单这个动作是最能体现组件通信价值的场景。车辆选择组件选完车牌后会把vehicleId通过emits(update:modelValue)抛给父组件父组件保存到表单响应式对象里提交时整个表单一起传给接口。Element Plus 的el-form和el-table封装了大部分交互细节我只需要专注业务逻辑不需要自己写下拉框和表格分页组件。工单列表的筛选状态我直接放在 URL 的 query 参数里这样刷新页面之后筛选条件不会丢失。比如/work-orders?statusrepairingpage2组件挂载时读一次路由参数筛选条件变化时router.replace同步更新地址。这个小习惯在做管理系统时特别实用既方便调试又方便把某个筛选状态的页面发给同事看问题。5.3 仪表盘可视化用 ECharts 把经营数据讲清楚系统登录后的首页我做成了一张数据仪表盘核心用 ECharts 来渲染图表。顶部的统计卡片展示今日接车量、今日营收、进行中工单、库存预警数量这些数据来自GET /api/stats/overview。下面的图表区域放两个重点图近 7 天营收趋势折线图以及工单类型分布环形图。ECharts 在 Vue 里的正确用法是先封装一个基础容器组件图表实例只创建一次配置变化时通过setOption更新而不是反复init销毁实例template div refchartRef stylewidth: 100%; height: 360px/div /template script setup import * as echarts from echarts import { ref, onMounted, onBeforeUnmount, watch } from vue const props defineProps({ option: { type: Object, required: true } }) const chartRef ref(null) let chart null onMounted(() { chart echarts.init(chartRef.value) chart.setOption(props.option) }) watch(() props.option, (val) { chart?.setOption(val) }, { deep: true }) onBeforeUnmount(() { chart?.dispose() }) /script封装成组件之后仪表盘页面就只需要关注“把后端返回的数据转换成 ECharts option 结构”这一件事。比如后端返回的营收数据格式是[{date: 2025-01-11, amount: 5600}, ...]前端就可以map成xAxis.data和series.data。数据格式转换逻辑我单独抽成utils/chartOption.js页面代码保持干净后续如果要调整图表样式也只改一个文件。5.4 Pinia 状态管理到底什么东西才需要放进 Store有人会问这套系统的状态管理能不能不用 Pinia直接在组件里ref一把梭对简单页面来说当然可以但系统里有两个全局数据必须放进 Store当前登录用户信息和系统侧的“基础字典数据”。登录信息放进 Store 的理由很简单——多个页面需要读取当前用户的角色和真实姓名来渲染界面如果每个页面都从 localStorage 里读代码会非常啰嗦而且一旦 Token 失效或被清理各页面的状态就可能不一致。字典数据的例子是“工单状态字典”和“预约状态字典”表格页需要把repairing这样的英文状态值映射成“维修中”这样的中文标签每个页面都写一遍映射对象就很容易写错。我统一放在useDictStore里选择器共享同一份映射表export const useDictStore defineStore(dict, () { const orderStatusMap { pending: 待接车, assigned: 已派工, repairing: 维修中, quality_check: 待质检, pending_settlement: 待结算, completed: 已完成 } return { orderStatusMap } })工单列表页里表格的 status 列需要显示中文标签时直接在模板里引用dictStore.orderStatusMap[row.status]即可。这样处理的好处是哪怕业务方临时要求把某个状态改名只需要改 Store 里的一行映射全系统相关的展示立刻同步不用去每个页面逐个查找替换。6. 联调、部署与高频 Bug 排查把坑提前给你踩一遍6.1 前后端联调与跨域代理配置前端和后台开发完第一版后第一步就是联调。开发环境里前端页面跑在 5173后端跑在 3000浏览器肯定会拦截跨域请求。虽然我后端已经通过cors包开放了跨域但更推荐在前端 Vite 配置代理把一定开头的请求转发给后端这样前端代码里所有请求路径都能保持相对路径// vite.config.js export default defineConfig({ server: { proxy: { /api: { target: http://localhost:3000, changeOrigin: true } } } })配置完成之后前端 axios 的baseURL直接写/api页面请求路径GET /api/work-orders会被 Vite 代理转发到http://localhost:3000/api/work-orders。生产环境部署时Nginx 里同样配置一个/api的proxy_pass到后端进程就完成了前后端在同域下的整合。6.2 高频问题实录照着这张表少加三天班我在开发过程中整理了六个最常遇到的问题每一个都在真实项目里踩过整理成速查表分享给你。问题表现根因分析解决方案后端启动直接崩溃报ER_ACCESS_DENIED_ERROR数据库账号密码不对检查.env文件里的DB_USER和DB_PASSWORD是否和 MySQL 实际账号一致前端请求接口报 404但接口用 Postman 测没问题Vite 代理没生效或代理路径不对检查vite.config.js的/api代理配置确保请求路径以/api开头登录接口密码一直报错数据库里存的密码没有用 bcrypt 加密重新用 bcryptjs 的hashSync生成密码不能用明文工单状态一直更新不成功状态机配置里当前状态没有到目标状态的映射检查orderStatusFlow映射关系配件库存出现负值扣减事务没有加行级锁并发导致超卖用SELECT ... FOR UPDATE锁行Token 过期后页面仍显示已登录前端拦截器没有统一处理 401 状态axios 响应拦截器检测到 401清除本地 Token 并跳转登录页第三行这个问题我要多说一句。很多同学在数据库里手动执行INSERT INTO users时直接就把123456这种明文密文塞进去了完全没有走后端注册逻辑。等到登录接口一比对bcrypt.compare(123456, 123456)永远返回 false怎么调都调不通。正确做法是写一个初始化脚本用 Node 的bcrypt.hashSync(123456, 10)生成哈希值再入库。6.3 生产环境部署Nginx 托管与进程守护部署环节我在这里分享一个最省心的方案。前端把npm run build出来的dist目录上传到服务器后端在服务器上安装 Node 环境后把代码传到某个目录执行npm install --production。然后配置 Nginx把静态资源和 API 请求拆到两个 locationserver { listen 80; server_name your-domain.com; root /var/www/vue-4s-frontend/dist; index 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; } location / { try_files $uri $uri/ /index.html; } }这里最关键的是try_files $uri $uri/ /index.html这一行。Vue Router 用的是 history 模式如果没有这一行用户直接在浏览器刷新/work-orders页面Nginx 会找不到这个文件返回 404加上这一行之后任何非 API 的路径都回退到index.html由前端路由接管页面渲染。后端进程的守护我建议用 PM2npm install -g pm2 pm2 start src/index.js --name 4s-backend pm2 save pm2 startuppm2 startup会生成开机自启脚本服务器重启后后端服务自动拉起来不用手动干预。部署到这个程度这套系统就算真正可以交付使用了。7. 踩坑复盘与进阶建议7.1 我在这套系统里最想推翻的三个设计决策整个项目做完之后我回看当初的设计决策有三个地方如果重做一定会改。第一个是报表模块不应该放在主系统里堆页面。我当时把统计分析直接做成“仪表盘页面一屏装下”但实际跑起来发现店长要看的数据颗粒度和维度远超预期——按车型分析、按月份对比、按技师绩效折算单个页面塞不下也不灵活。更好的做法是把报表数据做成通用查询接口前端用报表配置表动态渲染图表这样新增一张报表只是加一条配置不用动代码。第二个是预约模块和工单模块应该直接打通。目前的实现是预约确认后自动创建工单模板但还是需要前台手动点击确认。如果一开始就在数据库层面设计“预约状态驱动工单生成”动作更顺滑用户操作路径更短。第三是配件部分的供应商和采购单管理太简单。实际 4S 店配件数据量很大需要比价、退货、批次记录。早期只做了最基本的出入库后续要补采购模块时发现parts_stock_logs表的字段设计不够灵活改造起来有点伤筋动骨。如果你现在还在设计阶段建议预留supplier_id、batch_no字段。7.2 后面再扩展这几个方向最值得做这套系统后续的进化方向我觉得可以沿着“数据智能”和“服务延伸”两条线走。数据智能方面可以基于历史工单数据做保养到期预测、维修费用预估和常用配件备货建议。比如根据同一车型的保养间隔和里程数据在建档时自动算出下一个建议保养时间到期前三天通过短信或站内信提醒客户。这个功能难度不高但对门店客户体验的提升非常明显。服务延伸方面可以考虑把“客户自助端”拆出去做一个 C 端小程序让车主能自己上传车辆信息、查看维修进度、在线支付。做这一步时后端接口基本不需要重构因为客户端和后端之间还是走同一套 RESTful API只需要通过角色字段区分访问端即可。如果打开 C 端JWT 的签发逻辑和接口权限校验要重新过一遍避免客户调用到内部管理接口。还有一点是部署形态。如果后续门店数量多于两家建议把 MySQL 迁到云数据库后端加一层 Redis 做缓存和 Token 黑名单避免单机部署的运维风险。不过这些都是“业务跑起来之后”的事第一版把所有功能流程走通才是最重要的。最后分享一点我个人的真实感受做这套系统时我最大的收获不是学会了 Vue 或 Node而是建立了“以业务状态机为核心设计数据流”的思维方式。需求方告诉你的永远是一堆零散的功能点你作为系统设计者要做的第一件事就是把这些点梳理成一条线。线理清了哪怕代码有 bug 也容易修线理不清楚就算界面做得再漂亮系统用起来也是散的。如果你正在做类似的项目可以试着先把“工单状态流转”这张图画出来再动手写代码一定会少走很多弯路。
返回列表