
简介本资源为基于SSM与Vue3实现的设备维修管理系统毕设项目面向计算机相关专业毕业生及需要完整前后端分离案例的开发者。系统围绕设备维修全过程展开涵盖设备管理、维修申请、维修审批、维修过程记录、维修验收与统计分析等模块并为各节点加入审批功能实现从申请到交付的全流程回溯管理。后端采用Java语言与SSM框架前端使用Vue3配合ElementPlus整体为B/S架构适合作为毕设参考或课程设计模板。压缩包共约2000个文件包含1011个js脚本、892个md说明文档、87个json配置、5个txt文本、2个html页面、1个vue组件、1个docx文档及1个sql建表脚本整体约129.49MB目录结构清晰便于按模块查阅。目前已有284人学习下载可帮助读者快速理解系统分层设计、接口组织与前端组件拆分思路并对照文档完成环境搭建与功能复现。1. 设备维修管理系统从报修到验收SSMVue3 怎么把流程跑通工厂里一台注塑机半夜趴窝值班班长在微信群里吼了三遍没人应等第二天早班维修工到岗翻聊天记录才发现故障描述只有一句“机器不动了”。这种场景我见过太多次设备维修管理系统要解决的就是这件事把报修、派工、维修、验收、归档串成一条可追溯的线谁在什么时候接的单、换了什么件、停机多久全部落在数据库里。标题里的 SSM 是后端骨架Spring SpringMVC MyBatis 这套组合在毕设和中小型管理系统里依然是稳妥选择Vue3 负责前端交互Composition API 写起来比 Vue2 的 Options API 更顺手尤其是表单联动和列表刷新这类高频操作。这套系统适合谁做毕设的学生、刚接手设备管理模块的后端新手、想用一套完整项目练手 SSM 和 Vue3 联调的人。下面我按实际开发顺序把选型理由、建表、接口、前端页面和踩过的坑一条条拆开讲。2. 后端骨架怎么搭SSM 分层与 Vue3 工程初始化2.1 为什么还在用 SSM 而不是 SpringBoot先说清楚一件事SSM 不是过时技术它只是把 SpringBoot 自动配置的那层“黑匣子”拆开了。毕设场景下答辩老师往往会问你“IOC 容器怎么初始化的”“MyBatis 的 Mapper 代理是怎么生成的”用 SSM 你至少能指着applicationContext.xml说清楚 Bean 的加载顺序。SpringBoot 当然更省事但如果你连DispatcherServlet的职责都说不明白用 SpringBoot 反而容易被问住。SSM 的分层逻辑很直白Controller 接请求、Service 写业务、Mapper 做持久化。设备维修管理系统的业务不复杂但状态流转多——报修单有“待派工、维修中、待验收、已完成”四个状态每个状态对应不同的操作权限和字段变更。用 SSM 的好处是事务边界清晰Service 层加Transactional就能保证“派工时同时更新维修工状态和工单状态”这种操作要么全成功要么全回滚。Vue3 这边我一般用 Vite 初始化项目比 Vue CLI 快很多。npm create vitelatest repair-frontend -- --template vue这条命令跑完项目结构就出来了。Vue3 的 Composition API 在写维修工单列表时优势明显用ref定义响应式数据用reactive管理表单对象逻辑可以按功能抽成useRepairOrder这样的组合函数而不是像 Options API 那样把data、methods、computed拆得到处都是。2.2 数据库表设计与 MyBatis 映射设备维修管理系统最少需要五张核心表设备表、维修工单表、维修工表、备件表、工单备件关联表。我见过有人把维修工和用户混在一张表里结果权限控制写得一团糟。分开建表维修工表只存技能标签和在岗状态用户表走独立的登录认证。-- 设备表记录设备基本信息和当前状态 CREATE TABLE equipment ( id BIGINT PRIMARY KEY AUTO_INCREMENT, code VARCHAR(32) NOT NULL UNIQUE COMMENT 设备编号, name VARCHAR(64) NOT NULL COMMENT 设备名称, location VARCHAR(128) COMMENT 所在位置, status TINYINT DEFAULT 1 COMMENT 1正常 2故障 3维修中 4报废, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 维修工单表核心流转表 CREATE TABLE repair_order ( id BIGINT PRIMARY KEY AUTO_INCREMENT, order_no VARCHAR(32) NOT NULL UNIQUE COMMENT 工单号, equipment_id BIGINT NOT NULL, reporter_id BIGINT NOT NULL COMMENT 报修人, repairer_id BIGINT COMMENT 维修工派工后填入, fault_desc VARCHAR(512) COMMENT 故障描述, status TINYINT DEFAULT 0 COMMENT 0待派工 1维修中 2待验收 3已完成, report_time DATETIME DEFAULT CURRENT_TIMESTAMP, assign_time DATETIME, finish_time DATETIME, FOREIGN KEY (equipment_id) REFERENCES equipment(id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;建表时有两个细节容易翻车一是order_no的生成策略我一般用“RO日期四位序列”的格式在 Service 层用 Redis 自增或者数据库序列表实现不要用UUID因为工单号要给人看二是状态字段用TINYINT而不是VARCHAR查询和索引效率差很多。MyBatis 的映射文件里工单列表查询需要关联设备名和维修工姓名用resultMap做嵌套映射比写JOIN再手动拼装更清晰。下面这个查询是工单列表页的核心 SQLselect idselectOrderPage resultMapOrderVOMap SELECT o.*, e.name AS equipment_name, u.real_name AS repairer_name FROM repair_order o LEFT JOIN equipment e ON o.equipment_id e.id LEFT JOIN sys_user u ON o.repairer_id u.id where if teststatus ! nullAND o.status #{status}/if if testequipmentId ! nullAND o.equipment_id #{equipmentId}/if /where ORDER BY o.report_time DESC LIMIT #{offset}, #{limit} /selectwhere标签会自动处理第一个AND这个细节新手经常忽略导致 SQL 拼接出错。分页参数offset和limit在 Service 层根据页码计算不要在前端传否则容易被改参数拖库。2.3 Vue3 项目结构与 Axios 封装前端项目初始化后我一般按功能模块划分目录api放接口请求、views放页面、components放公共组件、composables放组合函数。Vue3 的setup语法糖写起来简洁但要注意ref和reactive的使用边界基本类型用ref对象用reactive别混着用导致响应式丢失。Axios 封装是联调的第一步请求拦截器里统一加 token响应拦截器里统一处理错误码。下面这段代码我用了很多次直接抄就行// api/request.js import axios from axios import { ElMessage } from element-plus const service axios.create({ baseURL: /api, timeout: 10000 }) // 请求拦截从 localStorage 取 token 塞进 header service.interceptors.request.use(config { const token localStorage.getItem(token) if (token) { config.headers.Authorization Bearer ${token} } return config }) // 响应拦截统一处理业务错误码 service.interceptors.response.use( response { const res response.data if (res.code ! 200) { ElMessage.error(res.message || 请求失败) return Promise.reject(new Error(res.message)) } return res.data }, error { ElMessage.error(error.message || 网络异常) return Promise.reject(error) } ) export default service这里有个坑Vite 开发环境下需要配置代理解决跨域在vite.config.js里加server.proxy把/api转发到后端端口。生产环境用 Nginx 做反向代理前端打包后的dist目录直接扔给 Nginx 托管。3. 核心业务怎么落地报修、派工、验收的接口与页面3.1 报修单创建与工单号生成报修单创建是整个流程的入口前端表单要填设备、故障描述、紧急程度后端要生成唯一工单号并写入数据库。工单号生成我试过三种方案数据库自增主键拼日期、Redis 原子递增、雪花算法。毕设场景下 Redis 方案最稳但如果你不想额外装 Redis用数据库序列表也能凑合。// Service 层创建报修单 Service public class RepairOrderServiceImpl implements RepairOrderService { Autowired private RepairOrderMapper orderMapper; Autowired private SequenceMapper sequenceMapper; Override Transactional(rollbackFor Exception.class) public String createOrder(RepairOrderDTO dto) { // 生成工单号RO yyyyMMdd 4位序列 String dateStr LocalDate.now().format(DateTimeFormatter.ofPattern(yyyyMMdd)); sequenceMapper.updateAndGet(repair_order, dateStr); Integer seq sequenceMapper.getCurrentSeq(repair_order, dateStr); String orderNo RO dateStr String.format(%04d, seq); RepairOrder order new RepairOrder(); order.setOrderNo(orderNo); order.setEquipmentId(dto.getEquipmentId()); order.setReporterId(dto.getReporterId()); order.setFaultDesc(dto.getFaultDesc()); order.setStatus(0); // 待派工 orderMapper.insert(order); // 同时更新设备状态为故障 equipmentMapper.updateStatus(dto.getEquipmentId(), 2); return orderNo; } }Transactional的rollbackFor Exception.class必须加否则遇到受检异常不回滚工单创建了但设备状态没改数据就对不上了。序列号生成用UPDATE ... SET seq seq 1配合SELECT两步操作在高并发下会有竞态问题但毕设场景并发量低加个synchronized或者数据库行锁就够用。前端报修页面用 Element Plus 的el-form做校验设备下拉框从/api/equipment/list拉数据故障描述限制 500 字。提交成功后跳转到工单详情页把生成的工单号展示出来。3.2 派工逻辑与维修工状态联动派工是管理员操作把待派工的工单分配给具体维修工。这里有个业务规则维修工同时最多接三单超过三单不允许再派。这个规则在 Service 层校验不要只靠前端限制。Override Transactional(rollbackFor Exception.class) public void assignOrder(Long orderId, Long repairerId) { // 校验维修工当前工单数 int count orderMapper.countByRepairerAndStatus(repairerId, 1); if (count 3) { throw new BusinessException(该维修工当前工单已满请选择其他人); } RepairOrder order orderMapper.selectById(orderId); if (order.getStatus() ! 0) { throw new BusinessException(该工单已被派工或已完成); } order.setRepairerId(repairerId); order.setStatus(1); // 维修中 order.setAssignTime(new Date()); orderMapper.updateById(order); // 更新维修工状态为忙碌 userMapper.updateWorkStatus(repairerId, 2); }countByRepairerAndStatus这个查询走repairer_id和status的联合索引不然工单多了之后派工页面会卡。前端派工弹窗里维修工下拉框只显示状态为“空闲”或“忙碌但未满三单”的人这个过滤逻辑放在后端接口里做前端只负责展示。3.3 验收流程与状态机收尾维修工修完设备后在系统里提交完工报告工单状态变成“待验收”。报修人或者设备管理员验收通过后工单状态变成“已完成”设备状态恢复为“正常”。验收不通过则退回“维修中”维修工需要重新处理。这个状态流转用枚举管理不要散落在各个 if-else 里public enum OrderStatus { PENDING_ASSIGN(0, 待派工), REPAIRING(1, 维修中), PENDING_ACCEPT(2, 待验收), COMPLETED(3, 已完成); private final int code; private final String desc; // 状态流转校验只允许相邻状态跳转 public static boolean canTransfer(int from, int to) { if (from 0 to 1) return true; if (from 1 to 2) return true; if (from 2 to 3) return true; if (from 2 to 1) return true; // 验收不通过退回 return false; } }验收接口里先调canTransfer校验不合法直接抛异常。前端验收页面展示维修工提交的完工报告和更换备件列表验收人点“通过”或“退回”按钮调对应接口。退回时要求填写退回原因这个原因会记录在工单日志表里方便后续追溯。4. 避坑与排查SSMVue3 联调中最容易翻车的五个点4.1 跨域问题预检请求 403 的排查现象前端调/api/repair/order/list报 403浏览器控制台显示Request Method: OPTIONS失败。原因后端 SpringMVC 没有配置 CORS或者配置了但拦截器把 OPTIONS 请求拦了。解决在springmvc.xml里加mvc:cors配置或者在 Controller 上加CrossOrigin。如果用了拦截器做登录校验记得在拦截器里放行 OPTIONS 请求否则预检过不去。!-- springmvc.xml 中配置全局 CORS -- mvc:cors mvc:mapping path/** allowed-originshttp://localhost:5173 allowed-methodsGET,POST,PUT,DELETE,OPTIONS allowed-headers* allow-credentialstrue/ /mvc:cors4.2 MyBatis 驼峰映射失效查出来字段全是 null现象数据库字段equipment_id查出来映射不到 Java 对象的equipmentId返回结果里这个字段是 null。原因MyBatis 默认不开启驼峰命名转换需要在mybatis-config.xml里加mapUnderscoreToCamelCasetrue。解决在配置文件中开启这个选项或者在resultMap里手动写result columnequipment_id propertyequipmentId/。我一般直接开全局配置省事。4.3 Vue3 响应式丢失改了数组页面不刷新现象在setup里用let orderList []定义数组调接口赋值后页面不更新。原因let定义的普通变量不是响应式的Vue3 追踪不到变化。解决用const orderList ref([])赋值时写orderList.value res.data。如果数组元素是对象且需要深层响应用reactive包裹或者ref配合.value操作。Vue3 的响应式基于 Proxy对ref数组的push、splice都能追踪但直接替换整个数组时必须走.value。4.4 事务不生效Service 内部方法调用现象在 Service 的 A 方法里调 B 方法B 方法加了Transactional但回滚没生效。原因Spring AOP 代理的是外部调用类内部方法调用不走代理事务注解失效。解决把 B 方法抽到另一个 Service 里或者用AopContext.currentProxy()获取代理对象再调。更简单的做法是调整代码结构别在类内部调事务方法。4.5 前端打包后接口 404Nginx 配置漏了现象开发环境正常npm run build后部署到 Nginx调接口全部 404。原因Vite 开发环境的proxy配置只在 dev server 生效生产环境需要 Nginx 做反向代理。解决在 Nginx 配置里加location /api/ { proxy_pass http://后端IP:端口/; }注意proxy_pass末尾的斜杠带斜杠会替换掉/api前缀不带则保留。这个细节坑过很多人血泪经验。5. 进阶技巧用组合函数抽离工单逻辑与验收自查5.1 把工单状态流转抽成 Vue3 组合函数工单列表、详情、派工弹窗都要用到状态判断和操作按钮的显隐逻辑如果每个页面都写一遍if (status 0) ...改起来会疯。我一般抽一个useOrderStatus组合函数把状态映射、按钮权限、颜色标签都收进去。// composables/useOrderStatus.js import { computed } from vue export function useOrderStatus(orderRef) { // 状态码到文本和颜色的映射 const statusMap { 0: { text: 待派工, color: #E6A23C, actions: [assign] }, 1: { text: 维修中, color: #409EFF, actions: [finish] }, 2: { text: 待验收, color: #67C23A, actions: [accept, reject] }, 3: { text: 已完成, color: #909399, actions: [] } } const currentStatus computed(() statusMap[orderRef.value?.status] || {}) // 判断当前用户是否有某个操作权限 const canAction (action) { return currentStatus.value.actions?.includes(action) } return { currentStatus, canAction } }在页面里这样用const { currentStatus, canAction } useOrderStatus(order)模板里直接v-ifcanAction(assign)控制按钮显隐。状态映射改一处所有页面生效。Vue3 的 Composition API 在抽离这类逻辑时比 Vue2 的 mixin 清晰得多mixin 的命名冲突和数据来源不明确问题在这里不存在。5.2 验收自查清单上线前跑一遍这七项毕设答辩前或者项目交付前我习惯按这个清单过一遍能挡掉大部分演示翻车检查项验证方式常见问题工单号唯一性连续创建 10 条工单序列号重复或格式错乱派工上限校验给同一维修工派第 4 单前端没拦、后端也没拦状态流转合法性直接改数据库状态后调接口跳过中间状态没报错分页查询性能造 1000 条工单后翻页没走索引查询超时事务回滚派工时故意传错维修工 ID工单状态改了但维修工没更新前端响应式删除列表某行后页面刷新数组没重新赋值视图不更新生产环境接口打包后部署 Nginx 访问代理配置漏了或路径不对这张表我每次交付前都会跑一遍尤其是事务回滚和分页性能这两项答辩时老师最喜欢挑这两个地方问。分页查询记得在repair_order表的status和report_time上建联合索引ORDER BY report_time DESC配合LIMIT才不会全表扫描。5.3 一个我踩过的坑别在验收环节省日志早期做这套系统时验收退回只改了工单状态没记录退回原因和操作人。结果维修工和报修人扯皮谁也说不清当时为什么退回。后来加了一张repair_order_log表每次状态变更都插一条记录字段包括工单 ID、操作人、操作类型、备注、时间。这张表平时没人看出问题的时候就是后悔药。写日志的代码放在 Service 层状态变更之后和业务操作在同一个事务里保证日志和状态一致。// 状态变更后记录日志 private void logOrderChange(Long orderId, Long operatorId, String action, String remark) { RepairOrderLog log new RepairOrderLog(); log.setOrderId(orderId); log.setOperatorId(operatorId); log.setAction(action); log.setRemark(remark); log.setCreateTime(new Date()); logMapper.insert(log); }这个习惯我保持到现在任何有状态流转的系统日志表都是最后兜底的那道防线。设备维修管理系统看起来简单但状态一多、人一多没有日志就是黑匣子。希望帮到你。本文还有配套的精品资源点击获取