news 2026/11/6 12:34:00

SSM+Vue3设备维修管理系统毕设实战:从选型到联调避坑

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
SSM+Vue3设备维修管理系统毕设实战:从选型到联调避坑

简介:本资源为基于SSM与Vue3实现的设备维修管理系统毕业设计项目,面向计算机相关专业需要完成毕设的学生及Java全栈初学者。系统采用B/S架构与前后端分离模式,后端以Java语言开发,前端使用Vue3配合ElementPlus构建界面,覆盖设备管理、维修申请、维修审批、维修过程记录、维修验收与统计分析等模块,并为各节点加入审批功能,实现从申请到交付的全流程信息化管理与回溯。压缩包共约2000个文件,以1011个js脚本、892个md说明文档、87个json配置为主,另含sql建库脚本、html页面与docx设计文档,整体约129.49MB,目录结构清晰便于按模块查阅。目前已有284人学习下载,适合作为毕设选题参考、课程设计模板或SSM与Vue3前后端分离项目的实战学习素材。

1. 毕设选 SSM+Vue3 做设备维修管理系统:这套组合到底值不值得投入

设备维修这件事,很多中小工厂和物业还在用微信群喊人、Excel 记台账。设备坏了谁报修、派给谁、修了多久、换了什么件、下次保养什么时候,全靠人脑记。一旦设备数量上百、维修工超过五个,信息就开始乱:报修单丢了、责任分不清、备件库存对不上。设备维修管理系统要解决的就是这条链路——从报修、派单、维修、验收、备件消耗到统计报表,全部线上留痕。

标题里的 SSM 是 Spring + SpringMVC + MyBatis 这套经典 Java 后端组合,Vue3 是前端框架。为什么毕设场景下这套组合依然能打?因为 SSM 资料多、结构清晰、答辩时老师听得懂,Vue3 的 Composition API 写后台管理页面比 Vue2 更顺手,配合 Element Plus 能快速搭出表格、表单、弹窗。这套方案适合计算机相关专业的本科毕设,也适合想练手前后端分离的中级开发者。它不追求高并发,追求的是业务闭环完整、代码可读、能讲清楚每一层在干什么。下面我按实际落地的顺序,把选型、建表、接口、前端页面和踩坑一条条拆开讲。

2. 环境搭建与 SSM 后端骨架:从零跑通第一个接口

2.1 版本选型:为什么是 JDK8 + Spring 5 + MyBatis 3

毕设最怕的是环境装三天跑不起来。SSM 这套东西版本兼容性经过多年验证,我一般锁定 JDK8、Spring 5.3.x、MyBatis 3.5.x、MySQL 8.0、Maven 3.8。JDK8 是因为大量 SSM 教程和依赖都基于它,用 JDK17 会遇到反射和模块化报错,徒增麻烦。Spring 5.3 是 5.x 最后一个稳定分支,和 MyBatis 整合成熟。MySQL 8.0 注意驱动类改成com.mysql.cj.jdbc.Driver,连接串要带时区和allowPublicKeyRetrieval。

Vue3 这边用 Vite 构建,Node 建议 18 LTS。Element Plus 选 2.x,和 Vue3 配套。这里有个热词里常被问到的点:vue3 和 vue2 的区别,落到项目里最直接的就是 Vue3 用createApp挂载、用 Composition API 组织逻辑、用ref和reactive管理响应式数据,而 Vue2 是new Vue加 Options API。后台管理系统这种表单表格密集的场景,Composition API 把同一功能的逻辑聚在一起,比 Vue2 的 data/methods/computed 分散写法好维护得多。

2.2 Maven 依赖与目录结构

后端用 Maven 管理依赖,核心依赖如下。注意 SSM 是三个框架整合,不是单个 starter,需要手动配 Spring 和 MyBatis 的整合包。

<!-- pom.xml 核心依赖,版本按需微调 --> <dependencies> <!-- Spring 核心与 Web MVC --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.3.30</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>5.3.30</version> </dependency> <!-- MyBatis 与 Spring 整合 --> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.13</version> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>2.1.1</version> </dependency> <!-- MySQL 驱动,8.0 用 cj 驱动 --> <dependency> <groupId>com.mysql</groupId> <artifactId>mysql-connector-j</artifactId> <version>8.0.33</version> </dependency> <!-- 连接池、JSON、日志 --> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.2.20</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.15.2</version> </dependency> </dependencies>

目录结构按经典分层走:controller接请求、service写业务、mapper做持久化、entity放实体、config放 Spring 配置。这个分层不是形式主义,答辩时老师问「你的业务逻辑写在哪」,你能指着 service 层说清楚,比全塞 controller 里强太多。

2.3 Spring 与 MyBatis 整合配置

整合的关键是把SqlSessionFactory交给 Spring 管理,再用MapperScannerConfigurer扫描 mapper 接口。下面是一段 Java Config 写法,比 XML 更直观。

@Configuration @MapperScan("com.repair.mapper") // 扫描 mapper 接口,省去逐个注册 public class MyBatisConfig { @Bean public DataSource dataSource() { DruidDataSource ds = new DruidDataSource(); ds.setUrl("jdbc:mysql://localhost:3306/repair_db?serverTimezone=Asia/Shanghai&useSSL=false&allowPublicKeyRetrieval=true"); ds.setUsername("root"); ds.setPassword("your_password"); ds.setDriverClassName("com.mysql.cj.jdbc.Driver"); return ds; } @Bean public SqlSessionFactory sqlSessionFactory(DataSource ds) throws Exception { SqlSessionFactoryBean bean = new SqlSessionFactoryBean(); bean.setDataSource(ds); // 指定 mapper xml 位置,接口和 xml 分离是 SSM 常见做法 bean.setMapperLocations(new PathMatchingResourcePatternResolver() .getResources("classpath:mapper/*.xml")); return bean.getObject(); } }

@MapperScan是 SSM 里最常用的注解之一,热词里 ssm 常用注解 经常被搜,它解决的就是「每个 mapper 都要手动注册」的痛点。dataSource里连接串的三个参数别漏:serverTimezone不设会报时区错误,useSSL=false避免本地连接警告,allowPublicKeyRetrieval=true是 MySQL8 用 caching_sha2 认证时需要的。跑通标准是:启动 Tomcat 后访问一个测试接口能返回 JSON,且日志里没有No qualifying bean或CannotGetJdbcConnection报错。

3. 设备维修业务建模:数据表设计与核心接口实现

3.1 五张核心表撑起维修闭环

设备维修管理系统的业务闭环是:设备档案 → 报修单 → 派单 → 维修记录 → 备件消耗。对应五张核心表,字段设计直接决定后面接口好不好写。

表名作用关键字段
device设备档案id, device_no, device_name, location, status, buy_date
repair_order报修单id, device_id, fault_desc, reporter, report_time, status
dispatch派单记录id, order_id, repairer_id, dispatch_time, dispatch_status
repair_record维修记录id, order_id, repairer_id, repair_desc, cost, finish_time
spare_part备件库存id, part_name, stock, unit, warn_stock

repair_order.status用数字或枚举表示流转状态:0 待派单、1 已派单、2 维修中、3 待验收、4 已完成。这个状态机是整个系统的骨架,前端所有按钮的显示隐藏都依赖它。spare_part.warn_stock是预警阈值,库存低于它就标红,这是答辩时容易被问到的「亮点功能」。

3.2 报修单分页查询接口

后台列表页几乎都是分页查询,这里用 MyBatis 的limit配合 PageHelper 或者手写分页。手写分页更可控,毕设推荐手写,逻辑清楚。

// RepairOrderController.java @RestController @RequestMapping("/api/order") public class RepairOrderController { @Autowired private RepairOrderService orderService; // 分页查询报修单,支持按状态和设备名筛选 @GetMapping("/page") public Result page(@RequestParam(defaultValue = "1") Integer pageNum, @RequestParam(defaultValue = "10") Integer pageSize, @RequestParam(required = false) Integer status, @RequestParam(required = false) String deviceName) { // 计算偏移量,pageNum 从 1 开始 int offset = (pageNum - 1) * pageSize; List<RepairOrderVO> list = orderService.pageQuery(offset, pageSize, status, deviceName); int total = orderService.countQuery(status, deviceName); return Result.success(new PageResult<>(list, total)); } }

pageNum和pageSize用@RequestParam接收,前端传参时注意 pageNum 从 1 开始,后端算 offset 要减 1。status和deviceName是可选筛选条件,用required = false,service 层用动态 SQL 拼接。返回统一用Result包装,包含 code、msg、data,前端拦截器统一处理,这是前后端分离的常见约定。

3.3 MyBatis 动态 SQL 写筛选条件

mapper XML 里用<if>标签拼动态条件,这是 MyBatis 最实用的特性之一。

<!-- RepairOrderMapper.xml --> <select id="pageQuery" resultType="com.repair.vo.RepairOrderVO"> SELECT o.id, o.fault_desc, o.reporter, o.report_time, o.status, d.device_name, d.location FROM repair_order o LEFT JOIN device d ON o.device_id = d.id <where> <if test="status != null"> AND o.status = #{status} </if> <if test="deviceName != null and deviceName != ''"> AND d.device_name LIKE CONCAT('%', #{deviceName}, '%') </if> </where> ORDER BY o.report_time DESC LIMIT #{offset}, #{pageSize} </select>

<where>标签会自动处理第一个AND,避免WHERE AND的语法错误,这是新手最容易翻车的地方。LIKE CONCAT('%', #{deviceName}, '%')用#{}预编译防注入,别用${}拼接字符串。LEFT JOIN保证即使设备被删了报修单也能查出来,实际项目里设备一般做逻辑删除,不会真删。

3.4 派单与状态流转接口

派单是业务核心动作,涉及更新报修单状态和插入派单记录,必须放在同一个事务里。

@Service public class DispatchServiceImpl implements DispatchService { @Autowired private RepairOrderMapper orderMapper; @Autowired private DispatchMapper dispatchMapper; @Override @Transactional(rollbackFor = Exception.class) // 任一失败整体回滚 public void dispatch(Integer orderId, Integer repairerId) { // 先校验当前状态必须是待派单,防止重复派单 RepairOrder order = orderMapper.selectById(orderId); if (order == null || order.getStatus() != 0) { throw new BizException("该工单状态不允许派单"); } // 更新工单状态为已派单 orderMapper.updateStatus(orderId, 1); // 插入派单记录 Dispatch dispatch = new Dispatch(); dispatch.setOrderId(orderId); dispatch.setRepairerId(repairerId); dispatch.setDispatchTime(new Date()); dispatchMapper.insert(dispatch); } }

@Transactional的rollbackFor = Exception.class很关键,默认只回滚运行时异常,业务里抛的自定义异常如果是受检异常就不会回滚,这是血泪经验。状态校验放在更新前,用乐观锁思路防止并发重复派单。BizException是自定义业务异常,配合全局异常处理器返回友好提示。

4. Vue3 前端页面:从登录到维修工单列表

4.1 Vite 初始化与 Element Plus 接入

前端用 Vite 创建 Vue3 项目,命令很直接。

# 创建 Vue3 项目,选 Vue 模板 npm create vite@latest repair-frontend -- --template vue cd repair-frontend npm install # 安装路由、状态管理、UI 库、请求库 npm install vue-router@4 pinia element-plus axios

装完后在main.js里注册 Element Plus 和路由。Vue3 的挂载方式和 Vue2 不同,用createApp。

// main.js import { createApp } from 'vue' 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(ElementPlus) // 全局注册组件,省去逐个引入 app.use(router) app.mount('#app')

createApp是 Vue3 的入口,和 Vue2 的new Vue({ render: h => h(App) })相比更简洁。Element Plus 全局注册后,模板里直接用<el-table>、<el-form>等标签。热词里 vue3 使用 elementui 其实指的是 Element Plus,因为原版 Element UI 只支持 Vue2,Vue3 必须用 Plus 版本,这点别搞混。

4.2 用 Composition API 写工单列表页

工单列表页是后台最典型的页面:搜索栏 + 表格 + 分页 + 操作按钮。用<script setup>语法糖写,逻辑最紧凑。

<template> <div class="order-page"> <!-- 搜索栏 --> <el-form :inline="true" :model="query"> <el-form-item label="设备名称"> <el-input v-model="query.deviceName" placeholder="请输入" /> </el-form-item> <el-form-item label="状态"> <el-select v-model="query.status" clearable> <el-option label="待派单" :value="0" /> <el-option label="已派单" :value="1" /> <el-option label="维修中" :value="2" /> </el-select> </el-form-item> <el-button type="primary" @click="loadData">查询</el-button> </el-form> <!-- 表格 --> <el-table :data="tableData" border> <el-table-column prop="deviceName" label="设备名称" /> <el-table-column prop="faultDesc" label="故障描述" /> <el-table-column prop="reporter" label="报修人" /> <el-table-column label="状态"> <template #default="{ row }"> <el-tag :type="statusType(row.status)">{{ statusText(row.status) }}</el-tag> </template> </el-table-column> <el-table-column label="操作"> <template #default="{ row }"> <el-button v-if="row.status === 0" @click="handleDispatch(row)">派单</el-button> </template> </el-table-column> </el-table> <!-- 分页 --> <el-pagination v-model:current-page="query.pageNum" :page-size="query.pageSize" :total="total" @current-change="loadData" /> </div> </template> <script setup> import { ref, reactive, onMounted } from 'vue' import axios from 'axios' const query = reactive({ pageNum: 1, pageSize: 10, status: null, deviceName: '' }) const tableData = ref([]) const total = ref(0) // 加载列表数据 const loadData = async () => { const res = await axios.get('/api/order/page', { params: query }) tableData.value = res.data.data.list total.value = res.data.data.total } onMounted(loadData) // 页面挂载后自动加载 </script>

ref用于基本类型和数组,reactive用于对象,这是 Composition API 里最容易混淆的点。热词里 vue3 reactive 和 vue3 ref 万能对象 被反复搜,实际规则是:ref返回带.value的响应式引用,模板里自动解包;reactive返回 Proxy 对象,直接访问属性。表格数据用ref([]),查询条件用reactive({}),这是我在项目里固定的习惯。v-model:current-page是 Vue3 里v-model带参数的写法,替代 Vue2 的.sync。

4.3 axios 封装与跨域处理

前后端分离必然遇到跨域。开发阶段在vite.config.js里配代理最省事。

// vite.config.js import { defineConfig } from 'vite' import vue from '@vitejs/plugin-vue' export default defineConfig({ plugins: [vue()], server: { proxy: { '/api': { target: 'http://localhost:8080', // 后端地址 changeOrigin: true } } } })

配了代理后,前端请求/api/order/page会被转发到后端 8080 端口,浏览器不认为是跨域。生产环境则在后端配 CORS 或 Nginx 反向代理。changeOrigin: true会把请求头里的 host 改成目标地址,某些后端校验 host 时必须开。这里提醒一句,代理只在开发服务器生效,打包后的静态文件部署到 Nginx 时要另配。

5. 联调避坑:那些让毕设卡三天的常见问题

5.1 后端接口返回 404 或 500

现象:前端请求报 404,或者 500 但控制台没详细信息。原因通常是 SpringMVC 没扫描到 controller,或者@RequestMapping路径拼错。解决:检查spring-mvc.xml或 Java Config 里的component-scan包路径是否覆盖 controller,检查类上和方法上的路径拼接结果。500 的话看后端日志堆栈,多半是 mapper 没找到或 SQL 语法错。

5.2 前端表格数据不显示

现象:接口返回了数据,但表格空白。原因常见两种:一是tableData.value赋值时忘了.value,直接写tableData = res.data,响应式丢失;二是后端返回字段名和prop不一致,比如后端是device_name下划线,前端写deviceName。解决:统一用驼峰,在 MyBatis 里开mapUnderscoreToCamelCase=true自动转换,或者 SQL 里用别名。

5.3 时间字段显示成一串数字或乱码

现象:report_time显示成1699999999000或带 T 的 ISO 格式。原因:Jackson 默认把 Date 序列化成时间戳。解决:在实体字段上加@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8"),或者在配置文件里全局设置。时区不写GMT+8会差 8 小时,这个坑几乎人人踩过。

5.4 派单后状态没变或重复派单

现象:点了派单按钮,工单状态还是待派单,或者同一条被派了两次。原因:事务没生效,或者状态校验缺失。解决:确认@Transactional所在方法是被 Spring 代理调用的(同类内部调用不生效),确认派单前查了状态。并发场景下可以在 update 语句里加AND status = 0做条件更新,根据影响行数判断是否成功。

5.5 Vue3 打包后页面空白

现象:npm run build后部署,页面白屏,控制台报资源 404。原因:Vite 默认打包路径是绝对路径/,部署到子目录就找不到。解决:在vite.config.js里设base: './'改成相对路径。另外路由模式如果用 history,Nginx 要配try_files回退到 index.html,否则刷新 404。

6. 让毕设加分:状态机可视化与备件预警的进阶做法

基础功能跑通后,答辩想拿高分得有点亮点。我一般会加两个东西:工单状态流转可视化和备件库存预警。状态机可视化不用引重型库,用 Element Plus 的<el-steps>就能画出「报修 → 派单 → 维修 → 验收 → 完成」的进度条,把当前工单的 status 映射到 active 索引即可。

<el-steps :active="currentStep" finish-status="success"> <el-step title="报修" /> <el-step title="派单" /> <el-step title="维修" /> <el-step title="验收" /> <el-step title="完成" /> </el-steps>

currentStep由工单状态计算:status 为 0 时 active 是 1,为 4 时是 5。这样老师一眼就能看懂业务流转,比干巴巴的表格有说服力。

备件预警则是在维修完成时扣减库存,并检查是否低于warn_stock。扣减库存要用UPDATE spare_part SET stock = stock - #{num} WHERE id = #{id} AND stock >= #{num},把库存判断放进 SQL 的 where 条件里,根据影响行数判断是否扣减成功,避免并发超卖。低于预警值的备件在首页用红色标签列出,这就是一个完整的「发现—处理—预警」闭环。

验证方法上,我习惯用 Postman 先把所有接口跑一遍,确认状态码和数据正确,再联调前端。数据库层面用几条 SQL 造测试数据,覆盖各种状态。最后压测不用做,毕设规模没必要,但可以手动模拟并发派单,验证条件更新是否挡住了重复操作。

说个我自己的教训:当年做类似系统时,图省事把状态判断全写在前端,后端接口裸奔,结果答辩演示时老师直接调接口把已完成的工单又派了一次,当场翻车。从那以后我坚持一条习惯——所有状态校验和权限判断必须落在后端,前端只做展示。这个习惯后来帮我省了无数麻烦。希望帮到你。

本文还有配套的精品资源,点击获取

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

台湾道路矢量数据分级处理:QGIS拆分shp并导出WKT全攻略

简介&#xff1a;台湾省道路数据最新分级精确到乡道矢量数据包&#xff0c;面向地理信息系统开发、交通规划与地图制图等专业人员&#xff0c;适合需要道路路网底图进行空间分析、可视化或研究台湾地区道路分级体系的场景。数据分为三个维度&#xff1a;城市一级至四级道路线要…

作者头像 李华
网站建设 2026/11/6 12:33:55

Python打卡第32天

浙大疏锦行 类的装饰器&#xff1a; 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 def class_logger(cls): """ A class decorator that adds a lo…

作者头像 李华
网站建设 2026/10/28 3:29:24

RFID抗金属标签在智能制造中的选型与部署实战指南

1. 从一张“贴不上”的标签说起&#xff1a;RFID抗金属标签到底解决了什么问题做过智能制造产线改造的人大概率都遇到过这种尴尬&#xff1a;花大价钱上了RFID读写器和系统&#xff0c;结果在金属工装板、金属托盘、金属货架上贴标签时&#xff0c;读取率直接崩盘。普通不干胶R…

作者头像 李华
网站建设 2026/10/28 6:12:37

番茄识别数据集构建与训练避坑实战指南

简介&#xff1a;面向目标检测任务的番茄识别数据集&#xff0c;共含4786张番茄图片的标注信息&#xff0c;类别覆盖未成熟番茄、成熟番茄和患病番茄&#xff0c;适用于YOLO系列、Faster Rcnn、SSD等主流目标检测模型的训练与评估。压缩包共2000个文件&#xff0c;以txt标签文件…

作者头像 李华
网站建设 2026/11/5 8:52:28

Python开心消消乐源码实战:游戏循环、匹配判定与改造技巧

简介&#xff1a;Python项目实例源码&#xff0c;实现经典三消游戏开心消消乐&#xff0c;面向Python初学者与游戏开发爱好者&#xff0c;完整呈现休闲消除类游戏从界面搭建到核心逻辑的实现过程。资源共138个文件&#xff0c;涵盖92张png游戏素材、20组mp3/ogg背景音乐与音效、…

作者头像 李华
网站建设 2026/10/29 14:37:21

2026 企业 AI 办公工具选型指南:从评估框架到产品适配

一、企业选 AI 办公工具&#xff0c;为什么不能只看功能列表 不少企业在调研AI办公工具的初期&#xff0c;很容易陷入几个典型的选型误区&#xff1a;有的团队把不同产品的功能列表拉出来逐行比对&#xff0c;谁的功能条目多就优先纳入采购清单&#xff0c;最后上线之后才发现大…

作者头像 李华