基于SpringBoot+Vue的车辆管理系统设计与实现:从业务状态机到权限审批流
2026/9/9 3:43:56 网站建设 项目流程

先说个真实感受。做这东西之前,我一直觉得“车辆管理系统”是个烂大街得不能再烂大街的课题,网上随便一搜,十个项目九个叫“XX管理系统”,技术栈清一色 SpringBoot + Vue,看着千篇一律。但真等到自己动手把一个带权限、带审批流、带统计报表、还带本地存储的完整系统落地时,才发现:车辆管理系统的难点从来不在“增删改查”本身,而在于业务状态机的设计、权限模型的取舍,以及一大堆你平时根本注意不到的“边界情况”。

这次我从零搭了一套基于 SpringBoot + Vue + MyBatis + MySQL 的车辆管理系统,不是简单糊一个页面,而是把车辆档案、派车审批、维修保养、保险年检提醒、驾驶员管理、数据统计全部揉在了一起。文章会把整个技术选型、表结构设计、核心模块实现思路、代码级的关键实现细节、以及我在实际开发中踩过的那些坑,全部摊开来讲。如果你正准备用这套技术栈做毕设、做课程设计,或者公司内部刚好需要一套轻量级的车辆管理工具,这篇内容可以直接当“参考答案”用。

1. 需求拆解与整体设计:车辆管理系统到底在管什么

先聊一个很多人容易忽略的点。拿到“车辆管理系统”这个需求时,大部分人的第一反应是“不就是一张车表加几个 CRUD 接口吗”。但如果你真的去业务现场待几天,会发现车辆管理的核心根本不是“管车”,而是“管事”——车是静态的,围绕车发生的派车申请、出车审批、维修记录、保险到期、保养提醒,这些才是真正有价值的数据流。

1.1 功能模块划分:从业务流反推系统边界

我在做需求分析时,把所有干系人拉在一起捋了一遍流程,最终把系统拆成了 6 个核心功能域:

模块核心功能关键数据状态变化
系统管理用户、角色、菜单、字典用户表、角色表、权限表无状态
车辆档案车辆信息登记、车牌查重、状态管理车辆信息表可用/出车中/维修中/已报废
派车管理用车申请、审批、出车归还派车单表待审批/已通过/已拒绝/出车中/已完成
维修保养维修记录登记、保养计划、费用统计维修保养表待维修/维修中/已完成
保险年检保险到期提醒、年检到期提醒保险信息表、年检记录表有效/即将到期/已过期
统计报表用车频次、费用汇总、里程统计所有业务表无状态

这里我特别想强调一个设计原则:车辆的状态不要存成单一字段,而是要让业务单据去“驱动”车辆状态。比如车辆在“可用”状态时,一旦派车单审批通过变成“出车中”,车辆的可用状态字段也应该同步变化。如果这两处状态没有联动,很容易出现“车已经被派出去了,系统里还显示可预约”的尴尬局面。

1.2 技术栈选型:为什么是 SpringBoot + Vue 这对组合

老实说,这套组合确实不算新颖,但胜在稳。SpringBoot 2.7.x 是目前教程生态最成熟的版本,MyBatis 作为持久层框架,SQL 可控性比 JPA 强太多,尤其适合这种需要大量多表联查、条件动态拼装的业务系统。前端用 Vue 2 + Element UI,虽然 Vue 3 已经普及,但 Element UI 的组件丰富度和文档成熟度,对管理系统这种“表单+表格+弹窗”三板斧的场景来说,开发效率确实高。

数据库我选了 MySQL 8.0,主要是考虑到 8.0 的窗口函数在统计报表场景确实好用,而且默认字符集 utf8mb4 对中文支持更友好。如果你本地装的是 5.7,这套表结构也能直接跑,就是有些统计 SQL 需要改写成子查询。

1.3 项目目录结构:拿到源码后先看哪里

很多朋友拿到一个 SpringBoot + Vue 的项目源码,第一反应是从 Controller 开始看,结果越看越乱。我的经验是:先看数据库脚本,再看配置文件的上下文,最后才轮到代码。数据库脚本告诉你系统有哪些数据、数据之间怎么关联;配置文件告诉你怎么启动、连接哪个库;代码只是把这两者翻译成接口罢了。

后端目录结构如下:

vehicle-management/ ├── src/main/java/com/example/vehicle │ ├── config // 跨域配置、MyBatis配置、拦截器注册 │ ├── controller // 接口层 │ ├── service // 业务层 │ │ └── impl │ ├── mapper // MyBatis的Mapper接口 │ ├── entity // 实体类 │ ├── dto // 请求/响应对象 │ ├── common // 统一返回结果、异常处理、工具类 │ ├── interceptor // JWT登录拦截器 │ └── VehicleApplication.java

前端目录结构:

vehicle-web/ ├── src/ │ ├── api/ // axios请求封装,按模块拆文件 │ ├── assets/ // 静态资源 │ ├── components/ // 公共组件(上传组件、分页组件等) │ ├── router/ // 路由配置,带动态路由 │ ├── store/ // Vuex状态管理(存用户信息、token) │ ├── views/ // 页面视图 │ │ ├── system/ // 用户管理、角色管理 │ │ ├── vehicle/ // 车辆档案 │ │ ├── dispatch/ // 派车管理 │ │ ├── maintain/ // 维修保养 │ │ ├── insurance/ // 保险年检 │ │ └── dashboard/ // 首页统计 │ ├── utils/ // 请求封装、日期处理等工具 │ ├── App.vue │ └── main.js

这种结构没什么花里胡哨的,但胜在清晰。每个模块的业务代码都在自己的包/目录下,后期维护或者给别人讲代码的时候,按图索骥就行。

2. 数据库设计详解:车辆管理系统的“地基”怎么打

数据库设计是最能看出一个开发者有没有经验的地方。很多新手的表设计,要不就是一张大宽表塞满所有字段,要不就是把所有业务塞进同一个表导致状态混乱。我这次的设计思路是:主表管静态数据,业务表管动态流程,字典表管枚举值,三表分离但通过外键/逻辑外键关联。

2.1 车辆信息表设计:静态档案字段怎么定

车辆信息表是整个系统的“主数据表”,它的字段设计决定了后续所有业务单据如何引用车辆。我在设计时重点考虑了三个点:唯一性约束、状态分离、冗余字段的取舍。

CREATE TABLE `vehicle_info` ( `id` int NOT NULL AUTO_INCREMENT COMMENT '主键ID', `plate_no` varchar(20) NOT NULL COMMENT '车牌号', `vehicle_type` varchar(10) DEFAULT NULL COMMENT '车辆类型:轿车/SUV/商务车/货车', `brand` varchar(50) DEFAULT NULL COMMENT '品牌', `model` varchar(50) DEFAULT NULL COMMENT '车型', `color` varchar(20) DEFAULT NULL COMMENT '颜色', `engine_no` varchar(50) DEFAULT NULL COMMENT '发动机号', `vin` varchar(50) DEFAULT NULL COMMENT '车架号', `buy_date` date DEFAULT NULL COMMENT '购买日期', `buy_price` decimal(10,2) DEFAULT NULL COMMENT '购买价格', `seat_count` int DEFAULT '5' COMMENT '核载人数', `mileage` decimal(10,2) DEFAULT '0.00' COMMENT '总里程(公里)', `owner_id` int DEFAULT NULL COMMENT '保管人ID,关联用户表', `status` tinyint DEFAULT '0' COMMENT '车辆状态:0可用/1出车中/2维修中/3已报废', `remark` varchar(500) DEFAULT NULL COMMENT '备注', `create_time` datetime DEFAULT CURRENT_TIMESTAMP, `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, `deleted` tinyint DEFAULT '0' COMMENT '逻辑删除:0正常/1已删除', PRIMARY KEY (`id`), UNIQUE KEY `uk_plate_no` (`plate_no`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='车辆信息表';

踩坑提醒:车牌号一定要建唯一索引,而且要在设计阶段就建立,不要等上线后才发现重复数据。另外,车辆类型、品牌这种“有限枚举值”不要直接用字符串写死在代码里,后面维护起来很痛苦,建议配合字典表使用。

另一个容易忽略的点是deleted字段。车辆管理系统里,车辆档案一旦创建就不建议物理删除,因为历史维修记录、派车单都外挂着它。逻辑删除才是正解——查询所有业务数据时,统一带上deleted = 0的过滤条件。

2.2 派车单表设计:状态机是业务的核心

派车单是这套系统里最复杂的业务表,它承载了整个用车审批流。我把状态变化设计成一个清晰的状态机:

待审批 -> 已通过 -> 出车中 -> 已完成 待审批 -> 已拒绝 已通过 -> 出车中 -> 已完成

对应 SQL:

CREATE TABLE `dispatch_order` ( `id` int NOT NULL AUTO_INCREMENT COMMENT '主键ID', `order_no` varchar(32) NOT NULL COMMENT '派车单编号', `vehicle_id` int NOT NULL COMMENT '车辆ID', `driver_id` int DEFAULT NULL COMMENT '驾驶员ID', `applicant_id` int NOT NULL COMMENT '申请人ID', `apply_reason` varchar(500) DEFAULT NULL COMMENT '用车事由', `start_time` datetime DEFAULT NULL COMMENT '预计出发时间', `end_time` datetime DEFAULT NULL COMMENT '预计归还时间', `destination` varchar(200) DEFAULT NULL COMMENT '目的地', `mileage_before` decimal(10,2) DEFAULT NULL COMMENT '出车里程', `mileage_after` decimal(10,2) DEFAULT NULL COMMENT '归还里程', `status` tinyint DEFAULT '0' COMMENT '状态:0待审批/1已通过/2已拒绝/3出车中/4已完成', `audit_user_id` int DEFAULT NULL COMMENT '审批人ID', `audit_time` datetime DEFAULT NULL COMMENT '审批时间', `audit_remark` varchar(500) DEFAULT NULL COMMENT '审批意见', `create_time` datetime DEFAULT CURRENT_TIMESTAMP, `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `uk_order_no` (`order_no`), KEY `idx_vehicle_id` (`vehicle_id`), KEY `idx_applicant_id` (`applicant_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='派车单表';

这张表有个设计巧思值得展开:我把mileage_beforemileage_after分开存,而不是只存一个“总里程”。原因是里程差是后续统计油耗、分析用车频次的重要数据源。出车时记录开始里程,归还时记录结束里程,两者之差就是本次用车的实际行驶里

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

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

立即咨询