项目介绍
本项目是一个基于 Spring Boot + Vue.js 的实验设备管理系统,主要用于高校实验室设备的智能化管理。系统实现了设备信息管理、预约使用、维修记录、数据统计等核心功能,提高了实验室设备管理效率。
技术栈
后端
- Spring Boot 2.x
- Spring Security
- MyBatis Plus
- MySQL 8.0
- Redis
- JWT
前端
- Vue.js 2.x
- Element UI
- Axios
- Echarts
- Vue Router
- Vuex
核心功能
1. 设备管理
- 设备信息的增删改查
- 设备分类管理
- 设备状态实时监控
- 设备二维码生成
2. 预约管理
- 在线预约实验设备
- 预约时间冲突检测
- 预约审批流程
- 使用提醒
3. 维修管理
- 设备故障报修
- 维修进度跟踪
- 维修记录管理
- 维修统计分析
4. 用户管理
- 用户角色权限
- 实验室管理员
- 普通用户
- 系统管理员
5. 数据统计
- 设备使用率统计
- 故障率分析
- 预约情况统计
- 数据可视化展示
系统架构
├── lab-admin // 后台管理系统
├── lab-api // 后端接口服务
├── lab-common // 公共模块
├── lab-framework // 框架核心
├── lab-system // 系统功能
└── lab-web // 前端页面
数据库设计
主要数据表:
-- 设备信息表
CREATE TABLE device_info (id bigint NOT NULL AUTO_INCREMENT,name varchar(100) NOT NULL COMMENT '设备名称',code varchar(50) NOT NULL COMMENT '设备编号',type_id bigint COMMENT '设备类型ID',status tinyint DEFAULT 0 COMMENT '设备状态',location varchar(200) COMMENT '存放位置',create_time datetime DEFAULT CURRENT_TIMESTAMP,PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;-- 预约记录表
CREATE TABLE appointment (id bigint NOT NULL AUTO_INCREMENT, device_id bigint NOT NULL COMMENT '设备ID',user_id bigint NOT NULL COMMENT '预约用户ID',start_time datetime NOT NULL COMMENT '开始时间',end_time datetime NOT NULL COMMENT '结束时间',status tinyint DEFAULT 0 COMMENT '预约状态',create_time datetime DEFAULT CURRENT_TIMESTAMP,PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
项目亮点
-
采用前后端分离架构,提高开发效率
-
使用 Redis 缓存提升系统性能
-
集成微信小程序实现移动端访问
-
引入 WebSocket 实现设备状态实时监控
-
支持设备二维码扫描快速查看信息
-
数据可视化展示,直观反映使用情况
项目部署
- 环境准备
# 安装 JDK、Maven、MySQL、Redis、Node.js# 克隆项目
git clone https://github.com/xxx/lab-manage.git# 导入数据库
mysql -u root -p < lab.sql
- 后端部署
# 修改配置
vim lab-admin/src/main/resources/application.yml# 打包
mvn clean package# 启动
java -jar lab-admin.jar
- 前端部署
# 安装依赖
npm install# 打包
npm run build# 部署
cp -r dist/* /usr/share/nginx/html/
总结
本项目采用主流的 Java 全栈技术栈,实现了实验设备管理的智能化、信息化。系统功能完善,性能稳定,具有良好的可扩展性。在实际使用中取得了良好的效果,为高校实验室管理提供了有力支持。
设备管理与预约系统详细设计
一、设备管理模块
1. 设备信息管理
1.1 数据库设计
-- 设备信息表
CREATE TABLE device_info (id bigint NOT NULL AUTO_INCREMENT,name varchar(100) NOT NULL COMMENT '设备名称',code varchar(50) NOT NULL COMMENT '设备编号',type_id bigint COMMENT '设备类型ID',model varchar(100) COMMENT '设备型号',brand varchar(100) COMMENT '品牌',price decimal(10,2) COMMENT '采购价格',purchase_date date COMMENT '采购日期',warranty_period int COMMENT '保修期(月)',status tinyint DEFAULT 0 COMMENT '设备状态:0-闲置 1-使用中 2-维修中 3-报废',location varchar(200) COMMENT '存放位置',description text COMMENT '设备描述',qrcode varchar(255) COMMENT '二维码URL',create_time datetime DEFAULT CURRENT_TIMESTAMP,update_time datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;-- 设备分类表
CREATE TABLE device_type (id bigint NOT NULL AUTO_INCREMENT,name varchar(50) NOT NULL COMMENT '分类名称',parent_id bigint COMMENT '父分类ID',sort int DEFAULT 0 COMMENT '排序号',create_time datetime DEFAULT CURRENT_TIMESTAMP,PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
1.2 后端实现
@RestController
@RequestMapping("/device")
public class DeviceController {@Autowiredprivate DeviceService deviceService;// 新增设备@PostMappingpublic Result add(@RequestBody DeviceDTO device) {return deviceService.add(device);}// 修改设备@PutMappingpublic Result update(@RequestBody DeviceDTO device) {return deviceService.update(device);}// 删除设备@DeleteMapping("/{id}")public Result delete(@PathVariable Long id) {return deviceService.delete(id);}// 查询设备详情@GetMapping("/{id}")public Result getById(@PathVariable Long id) {return deviceService.getById(id);}// 分页查询设备列表@GetMapping("/list")public Result list(DeviceQuery query) {return deviceService.list(query);}
}
1.3 前端实现
<!-- 设备列表 -->
<template><div class="device-list"><!-- 搜索栏 --><el-form :inline="true" :model="queryParams" class="search-form"><el-form-item label="设备名称"><el-input v-model="queryParams.name" placeholder="请输入设备名称"/></el-form-item><el-form-item label="设备类型"><el-select v-model="queryParams.typeId" placeholder="请选择"><el-option v-for="item in typeOptions" :key="item.id" :label="item.name" :value="item.id"/></el-select></el-form-item><el-form-item label="设备状态"><el-select v-model="queryParams.status" placeholder="请选择"><el-option label="闲置" value="0"/><el-option label="使用中" value="1"/><el-option label="维修中" value="2"/><el-option label="报废" value="3"/></el-select></el-form-item><el-form-item><el-button type="primary" @click="handleQuery">查询</el-button></el-form-item></el-form><!-- 工具栏 --><el-row class="toolbar"><el-button type="primary" @click="handleAdd">新增设备</el-button><el-button type="danger" @click="handleBatchDelete">批量删除</el-button></el-row><!-- 数据表格 --><el-table :data="deviceList" @selection-change="handleSelectionChange"><el-table-column type="selection" width="55"/><el-table-column label="设备名称" prop="name"/><el-table-column label="设备编号" prop="code"/><el-table-column label="设备类型" prop="typeName"/><el-table-column label="状态"><template slot-scope="scope"><el-tag :type="getStatusType(scope.row.status)">{{getStatusName(scope.row.status)}}</el-tag></template></el-table-column><el-table-column label="操作"><template slot-scope="scope"><el-button type="text" @click="handleEdit(scope.row)">编辑</el-button><el-button type="text" @click="handleDelete(scope.row)">删除</el-button><el-button type="text" @click="handleQRCode(scope.row)">查看二维码</el-button></template></el-table-column></el-table></div>
</template>
2. 设备状态监控
2.1 WebSocket实现实时监控
@ServerEndpoint("/websocket/device")
@Component
public class DeviceWebSocket {// 存储设备连接会话private static Map<String, Session> deviceSessions = new ConcurrentHashMap<>();@OnOpenpublic void onOpen(Session session) {String deviceId = session.getQueryString();deviceSessions.put(deviceId, session);}@OnClosepublic void onClose(Session session) {String deviceId = session.getQueryString();deviceSessions.remove(deviceId);}// 发送设备状态更新public void sendMessage(String deviceId, DeviceStatus status) {Session session = deviceSessions.get(deviceId);if(session != null) {session.getAsyncRemote().sendText(JSON.toJSONString(status));}}
}
2.2 前端状态监听
// 设备状态监听
export default {data() {return {websocket: null}},created() {this.initWebSocket()},methods: {initWebSocket() {this.websocket = new WebSocket(WS_URL + '/websocket/device')this.websocket.onmessage = this.onMessage},onMessage(e) {const data = JSON.parse(e.data)// 更新设备状态this.updateDeviceStatus(data)}}
}
3. 设备二维码生成
@Service
public class QRCodeService {// 生成设备二维码public String generateDeviceQRCode(Long deviceId) {// 生成二维码内容(设备详情页URL)String content = DEVICE_URL_PREFIX + deviceId;// 生成二维码图片BitMatrix bitMatrix = new MultiFormatWriter().encode(content, BarcodeFormat.QR_CODE,200, 200 // 二维码尺寸);// 上传二维码图片String qrcodeUrl = uploadQRCode(bitMatrix);return qrcodeUrl;}
}
二、预约管理模块
1. 数据库设计
-- 预约记录表
CREATE TABLE appointment (id bigint NOT NULL AUTO_INCREMENT,device_id bigint NOT NULL COMMENT '设备ID',user_id bigint NOT NULL COMMENT '预约用户ID',start_time datetime NOT NULL COMMENT '开始时间',end_time datetime NOT NULL COMMENT '结束时间', purpose varchar(500) COMMENT '使用目的',status tinyint DEFAULT 0 COMMENT '预约状态:0-待审核 1-已通过 2-已拒绝 3-已取消',approve_by bigint COMMENT '审批人ID',approve_time datetime COMMENT '审批时间',approve_remark varchar(500) COMMENT '审批备注',create_time datetime DEFAULT CURRENT_TIMESTAMP,PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;-- 预约提醒表
CREATE TABLE appointment_remind (id bigint NOT NULL AUTO_INCREMENT,appointment_id bigint NOT NULL COMMENT '预约ID',remind_time datetime NOT NULL COMMENT '提醒时间',remind_type tinyint COMMENT '提醒方式:0-系统消息 1-邮件 2-短信',status tinyint DEFAULT 0 COMMENT '提醒状态',create_time datetime DEFAULT CURRENT_TIMESTAMP,PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
2. 预约冲突检测
@Service
public class AppointmentService {// 检查时间冲突private boolean checkTimeConflict(AppointmentDTO dto) {// 查询设备在该时间段内的所有预约List<Appointment> list = appointmentMapper.selectList(new QueryWrapper<Appointment>().eq("device_id", dto.getDeviceId()).eq("status", 1) // 已通过的预约.and(w -> w// 开始时间在已预约时间段内.between("start_time", dto.getStartTime(), dto.getEndTime())// 或结束时间在已预约时间段内.or().between("end_time", dto.getStartTime(), dto.getEndTime())));return !list.isEmpty();}// 提交预约public Result submit(AppointmentDTO dto) {// 检查时间冲突if(checkTimeConflict(dto)) {return Result.error("该时间段内设备已被预约");}// 保存预约记录Appointment appointment = new Appointment();BeanUtils.copyProperties(dto, appointment);appointmentMapper.insert(appointment);// 创建预约提醒createRemind(appointment);return Result.ok();}
}
3. 预约审批流程
@Service
public class ApprovalService {// 审批预约@Transactionalpublic Result approve(ApprovalDTO dto) {// 获取预约记录Appointment appointment = appointmentMapper.selectById(dto.getAppointmentId());if(appointment == null) {return Result.error("预约记录不存在");}// 更新预约状态appointment.setStatus(dto.getApproveResult());appointment.setApproveBy(SecurityUtils.getUserId());appointment.setApproveTime(new Date());appointment.setApproveRemark(dto.getRemark());appointmentMapper.updateById(appointment);// 发送审批结果通知MessageDTO message = new MessageDTO();message.setUserId(appointment.getUserId());message.setTitle("预约审批结果通知");message.setContent("您预约的设备" + appointment.getDeviceName() + "已" + (dto.getApproveResult() == 1 ? "通过" : "被拒绝"));messageService.send(message);return Result.ok();}
}
4. 使用提醒
@Component
public class AppointmentRemindTask {@Autowiredprivate AppointmentRemindService remindService;// 定时检查预约提醒@Scheduled(cron = "0 */5 * * * ?") // 每5分钟执行一次public void checkRemind() {// 查询待提醒的预约List<AppointmentRemind> list = remindService.getUnreminded();for(AppointmentRemind remind : list) {// 发送提醒switch(remind.getRemindType()) {case 0: // 系统消息sendSystemMessage(remind);break;case 1: // 邮件sendEmail(remind);break;case 2: // 短信sendSMS(remind);break;}// 更新提醒状态remind.setStatus(1);remindService.updateById(remind);}}
}
5. 前端预约界面
<!-- 预约表单 -->
<template><el-dialog title="预约设备" :visible.sync="visible"><el-form :model="form" :rules="rules" ref="form" label-width="100px"><el-form-item label="设备名称"><el-input v-model="deviceName" disabled/></el-form-item><el-form-item label="使用时间" required><el-col :span="11"><el-date-pickerv-model="form.startTime"type="datetime"placeholder="开始时间":picker-options="startTimeOptions"/></el-col><el-col :span="2" class="text-center">至</el-col><el-col :span="11"><el-date-pickerv-model="form.endTime"type="datetime"placeholder="结束时间":picker-options="endTimeOptions"/></el-col></el-form-item><el-form-item label="使用目的" prop="purpose"><el-input type="textarea" v-model="form.purpose"/></el-form-item><el-form-item label="提醒方式"><el-checkbox-group v-model="form.remindType"><el-checkbox label="0">系统消息</el-checkbox><el-checkbox label="1">邮件</el-checkbox><el-checkbox label="2">短信</el-checkbox></el-checkbox-group></el-form-item></el-form><div slot="footer"><el-button @click="visible = false">取 消</el-button><el-button type="primary" @click="submitForm">提 交</el-button></div></el-dialog>
</template><script>
export default {data() {return {visible: false,form: {deviceId: null,startTime: null,endTime: null,purpose: '',remindType: ['0']},rules: {purpose: [{ required: true, message: '请输入使用目的', trigger: 'blur' }]},startTimeOptions: {disabledDate: (time) => {return time.getTime() < Date.now() - 8.64e7}},endTimeOptions: {disabledDate: (time) => {return time.getTime() < this.form.startTime}}}},methods: {submitForm() {this.$refs.form.validate(valid => {if(valid) {appointmentApi.submit(this.form).then(res => {if(res.code === 200) {this.$message.success('预约成功')this.visible = false}})}})}}
}
</script>
以上是设备管理和预约管理两个核心模块的详细设计和实现。主要特点:
- 设备管理:
- 完整的CRUD功能
- 基于WebSocket的实时状态监控
- 集成二维码生成功能
- 支持设备分类管理
- 预约管理:
- 严格的时间冲突检测
- 完整的审批流程
- 灵活的提醒方式(系统消息/邮件/短信)
- 友好的预约表单界面
这两个模块通过合理的数据库设计、后端接口实现和前端界面展示,实现了实验设备的高效管理和使用。
维修管理与用户权限系统详细设计
一、维修管理模块
1. 数据库设计
-- 维修记录表
CREATE TABLE repair_record (id bigint NOT NULL AUTO_INCREMENT,device_id bigint NOT NULL COMMENT '设备ID',report_user_id bigint NOT NULL COMMENT '报修人ID',repair_user_id bigint COMMENT '维修人ID',fault_type varchar(50) COMMENT '故障类型',fault_desc text COMMENT '故障描述',repair_cost decimal(10,2) COMMENT '维修费用',status tinyint DEFAULT 0 COMMENT '维修状态:0-待处理 1-维修中 2-已完成 3-无法维修',report_time datetime COMMENT '报修时间',start_time datetime COMMENT '维修开始时间',end_time datetime COMMENT '维修完成时间',result varchar(500) COMMENT '维修结果',remark varchar(500) COMMENT '备注',create_time datetime DEFAULT CURRENT_TIMESTAMP,update_time datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;-- 维修进度表
CREATE TABLE repair_progress (id bigint NOT NULL AUTO_INCREMENT,repair_id bigint NOT NULL COMMENT '维修记录ID',status tinyint COMMENT '进度状态',content varchar(500) COMMENT '进度说明',operator bigint COMMENT '操作人ID',create_time datetime DEFAULT CURRENT_TIMESTAMP,PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;-- 故障类型表
CREATE TABLE fault_type (id bigint NOT NULL AUTO_INCREMENT,name varchar(50) NOT NULL COMMENT '类型名称',sort int DEFAULT 0 COMMENT '排序号',create_time datetime DEFAULT CURRENT_TIMESTAMP,PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
2. 设备故障报修
@RestController
@RequestMapping("/repair")
public class RepairController {@Autowiredprivate RepairService repairService;// 提交报修@PostMapping("/report")public Result report(@RequestBody RepairDTO repair) {// 校验设备状态DeviceInfo device = deviceService.getById(repair.getDeviceId());if(device.getStatus() == 2) {return Result.error("该设备已在维修中");}// 创建维修记录RepairRecord record = new RepairRecord();record.setDeviceId(repair.getDeviceId());record.setReportUserId(SecurityUtils.getUserId());record.setFaultType(repair.getFaultType());record.setFaultDesc(repair.getFaultDesc());record.setReportTime(new Date());repairService.save(record);// 更新设备状态为维修中deviceService.updateStatus(repair.getDeviceId(), 2);// 通知维修人员notifyRepairUsers(record);return Result.ok();}
}
3. 维修进度跟踪
@Service
public class RepairProgressService {// 更新维修进度@Transactionalpublic void updateProgress(ProgressDTO dto) {// 记录进度RepairProgress progress = new RepairProgress();progress.setRepairId(dto.getRepairId());progress.setStatus(dto.getStatus());progress.setContent(dto.getContent());progress.setOperator(SecurityUtils.getUserId());progressMapper.insert(progress);// 更新维修记录状态RepairRecord record = repairMapper.selectById(dto.getRepairId());record.setStatus(dto.getStatus());if(dto.getStatus() == 2) { // 维修完成record.setEndTime(new Date());}repairMapper.updateById(record);// 发送进度通知sendProgressNotification(dto);}// 获取维修进度列表public List<ProgressVO> getProgressList(Long repairId) {return progressMapper.selectProgressList(repairId);}
}
4. 维修统计分析
@Service
public class RepairStatisticsService {// 统计故障类型分布public Map<String, Integer> getFaultTypeStats(StatisticsQuery query) {return repairMapper.selectFaultTypeStats(query);}// 统计维修时长分布public List<Map<String, Object>> getRepairDurationStats(StatisticsQuery query) {return repairMapper.selectRepairDurationStats(query);}// 统计设备故障率public List<DeviceFaultRateVO> getDeviceFaultRate(StatisticsQuery query) {return repairMapper.selectDeviceFaultRate(query);}// 导出维修统计报告public void exportStatisticsReport(StatisticsQuery query, HttpServletResponse response) {// 获取统计数据Map<String, Object> data = new HashMap<>();data.put("faultTypeStats", getFaultTypeStats(query));data.put("repairDurationStats", getRepairDurationStats(query));data.put("deviceFaultRate", getDeviceFaultRate(query));// 生成Excel报告ExcelUtils.exportExcel(response, "维修统计报告", data);}
}
5. 维修管理前端界面
<!-- 维修进度跟踪 -->
<template><div class="repair-progress"><!-- 维修基本信息 --><el-card class="box-card"><div slot="header"><span>维修信息</span></div><el-descriptions :column="3" border><el-descriptions-item label="设备名称">{{repairInfo.deviceName}}</el-descriptions-item><el-descriptions-item label="故障类型">{{repairInfo.faultType}}</el-descriptions-item><el-descriptions-item label="报修时间">{{repairInfo.reportTime}}</el-descriptions-item><el-descriptions-item label="故障描述">{{repairInfo.faultDesc}}</el-descriptions-item></el-descriptions></el-card><!-- 进度时间线 --><el-card class="progress-card"><div slot="header"><span>维修进度</span><el-button v-if="hasPermission('repair:update')"type="primary" size="small"@click="showUpdateProgress">更新进度</el-button></div><el-timeline><el-timeline-itemv-for="(progress, index) in progressList":key="index":timestamp="progress.createTime"><el-card><h4>{{getStatusName(progress.status)}}</h4><p>{{progress.content}}</p><p>操作人: {{progress.operatorName}}</p></el-card></el-timeline-item></el-timeline></el-card><!-- 更新进度弹窗 --><el-dialog title="更新维修进度" :visible.sync="dialogVisible"><el-form :model="form" :rules="rules" ref="form"><el-form-item label="进度状态" prop="status"><el-select v-model="form.status"><el-option label="维修中" :value="1"/><el-option label="已完成" :value="2"/><el-option label="无法维修" :value="3"/></el-select></el-form-item><el-form-item label="进度说明" prop="content"><el-input type="textarea" v-model="form.content"/></el-form-item></el-form><div slot="footer"><el-button @click="dialogVisible = false">取 消</el-button><el-button type="primary" @click="submitProgress">确 定</el-button></div></el-dialog></div>
</template>
二、用户权限管理
1. 数据库设计
-- 用户表
CREATE TABLE sys_user (id bigint NOT NULL AUTO_INCREMENT,username varchar(50) NOT NULL COMMENT '用户名',password varchar(100) NOT NULL COMMENT '密码',name varchar(50) COMMENT '姓名',phone varchar(11) COMMENT '手机号',email varchar(50) COMMENT '邮箱',avatar varchar(100) COMMENT '头像',status tinyint DEFAULT 0 COMMENT '状态:0-正常 1-禁用',create_time datetime DEFAULT CURRENT_TIMESTAMP,PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;-- 角色表
CREATE TABLE sys_role (id bigint NOT NULL AUTO_INCREMENT,name varchar(50) NOT NULL COMMENT '角色名称',code varchar(50) NOT NULL COMMENT '角色编码',sort int DEFAULT 0 COMMENT '排序号',status tinyint DEFAULT 0 COMMENT '状态',create_time datetime DEFAULT CURRENT_TIMESTAMP,PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;-- 菜单表
CREATE TABLE sys_menu (id bigint NOT NULL AUTO_INCREMENT,parent_id bigint COMMENT '父菜单ID',name varchar(50) COMMENT '菜单名称',path varchar(200) COMMENT '路由地址',component varchar(255) COMMENT '组件路径',perms varchar(100) COMMENT '权限标识',type tinyint COMMENT '类型:0-目录 1-菜单 2-按钮',icon varchar(100) COMMENT '图标',sort int DEFAULT 0 COMMENT '排序号',status tinyint DEFAULT 0 COMMENT '状态',create_time datetime DEFAULT CURRENT_TIMESTAMP,PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;-- 用户角色关联表
CREATE TABLE sys_user_role (user_id bigint NOT NULL COMMENT '用户ID',role_id bigint NOT NULL COMMENT '角色ID',PRIMARY KEY (user_id, role_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;-- 角色菜单关联表
CREATE TABLE sys_role_menu (role_id bigint NOT NULL COMMENT '角色ID',menu_id bigint NOT NULL COMMENT '菜单ID',PRIMARY KEY (role_id, menu_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
2. 权限控制实现
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {@Overrideprotected void configure(HttpSecurity http) throws Exception {http.authorizeRequests()// 公开接口.antMatchers("/auth/login").permitAll()// 需要管理员权限.antMatchers("/system/**").hasRole("ADMIN")// 需要实验室管理员权限.antMatchers("/device/add", "/device/update", "/device/delete").hasRole("LAB_ADMIN")// 其他接口需要登录.anyRequest().authenticated().and().addFilter(new JwtAuthenticationFilter(authenticationManager())).addFilter(new JwtAuthorizationFilter(authenticationManager()));}
}// 自定义权限注解
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface RequiresPermissions {String[] value();Logical logical() default Logical.AND;
}// 权限注解切面
@Aspect
@Component
public class PermissionAspect {@Around("@annotation(requiresPermissions)")public Object around(ProceedingJoinPoint point, RequiresPermissions requiresPermissions) throws Throwable {// 校验用户权限if(!hasPermission(requiresPermissions.value(), requiresPermissions.logical())) {throw new NoPermissionException();}return point.proceed();}
}
3. 角色权限管理
@Service
public class RoleService {// 分配角色权限@Transactionalpublic void assignPerms(RolePermsDTO dto) {// 删除原有权限roleMenuMapper.deleteByRoleId(dto.getRoleId());// 保存新权限if(CollUtil.isNotEmpty(dto.getMenuIds())) {List<SysRoleMenu> list = dto.getMenuIds().stream().map(menuId -> {SysRoleMenu rm = new SysRoleMenu();rm.setRoleId(dto.getRoleId());rm.setMenuId(menuId);return rm;}).collect(Collectors.toList());roleMenuMapper.batchInsert(list);}// 清除权限缓存clearPermissionCache();}// 获取角色权限树public List<MenuTreeVO> getRoleMenuTree(Long roleId) {// 获取所有菜单List<SysMenu> menuList = menuMapper.selectList(null);// 获取角色已有权限List<Long> roleMenuIds = roleMenuMapper.selectMenuIdsByRoleId(roleId);// 构建树形结构return buildMenuTree(menuList, roleMenuIds);}
}
4. 用户管理前端实现
<!-- 用户管理 -->
<template><div class="user-manage"><!-- 搜索栏 --><el-form :inline="true" :model="queryParams"><el-form-item label="用户名"><el-input v-model="queryParams.username"/></el-form-item><el-form-item label="角色"><el-select v-model="queryParams.roleId"><el-option v-for="role in roleOptions":key="role.id":label="role.name":value="role.id"/></el-select></el-form-item><el-form-item><el-button type="primary" @click="handleQuery">查询</el-button></el-form-item></el-form><!-- 工具栏 --><el-row class="toolbar"><el-button type="primary" @click="handleAdd">新增用户</el-button><el-button type="danger" @click="handleBatchDelete">批量删除</el-button></el-row><!-- 用户列表 --><el-table :data="userList" @selection-change="handleSelectionChange"><el-table-column type="selection" width="55"/><el-table-column label="用户名" prop="username"/><el-table-column label="姓名" prop="name"/><el-table-column label="角色"><template slot-scope="scope"><el-tag v-for="role in scope.row.roles" :key="role.id"style="margin-right: 5px">{{role.name}}</el-tag></template></el-table-column><el-table-column label="状态"><template slot-scope="scope"><el-switchv-model="scope.row.status":active-value="0":inactive-value="1"@change="handleStatusChange(scope.row)"/></template></el-table-column><el-table-column label="操作" width="200"><template slot-scope="scope"><el-button type="text" @click="handleEdit(scope.row)">编辑</el-button><el-button type="text" @click="handleDelete(scope.row)">删除</el-button><el-button type="text" @click="handleAssignRole(scope.row)">分配角色</el-button></template></el-table-column></el-table><!-- 分配角色弹窗 --><el-dialog title="分配角色" :visible.sync="roleDialogVisible"><el-checkbox-group v-model="selectedRoleIds"><el-checkbox v-for="role in roleOptions":key="role.id":label="role.id">{{role.name}}</el-checkbox></el-checkbox-group><div slot="footer"><el-button @click="roleDialogVisible = false">取 消</el-button><el-button type="primary" @click="submitAssignRole">确 定</el-button></div></el-dialog></div>
</template>
5. 三种角色权限说明
- 系统管理员(ADMIN)
- 用户管理
- 角色权限管理
- 系统参数配置
- 日志查看
- 实验室管理员(LAB_ADMIN)
- 设备管理
- 预约审批
- 维修管理
- 统计分析
- 普通用户(USER)
- 设备查看
- 预约申请
- 故障报修
- 个人中心
主要特点:
- 维修管理:
- 完整的维修工作流
- 实时进度跟踪
- 多维度统计分析
- 维修记录导出
- 用户权限:
- 基于RBAC的权限模型
- 细粒度的权限控制
- 灵活的角色分配
- 友好的权限管理界面
通过以上设计,实现了设备维修的全流程管理和系统的多级权限控制,保证了系统的安全性和可用性。
数据统计与可视化模块详细设计
一、数据库设计
-- 设备使用记录表
CREATE TABLE device_usage_record (id bigint NOT NULL AUTO_INCREMENT,device_id bigint NOT NULL COMMENT '设备ID',user_id bigint NOT NULL COMMENT '使用人ID',start_time datetime NOT NULL COMMENT '开始时间',end_time datetime NOT NULL COMMENT '结束时间',duration int COMMENT '使用时长(分钟)',usage_type tinyint COMMENT '使用类型:0-教学 1-科研 2-其他',create_time datetime DEFAULT CURRENT_TIMESTAMP,PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;-- 设备故障统计表
CREATE TABLE device_fault_stats (id bigint NOT NULL AUTO_INCREMENT,device_id bigint NOT NULL COMMENT '设备ID',fault_count int DEFAULT 0 COMMENT '故障次数',repair_cost decimal(10,2) DEFAULT 0 COMMENT '维修费用',avg_repair_time int COMMENT '平均维修时长(小时)',stats_month varchar(7) COMMENT '统计月份',create_time datetime DEFAULT CURRENT_TIMESTAMP,PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;-- 预约统计表
CREATE TABLE appointment_stats (id bigint NOT NULL AUTO_INCREMENT,device_id bigint NOT NULL COMMENT '设备ID',total_count int DEFAULT 0 COMMENT '预约总数',success_count int DEFAULT 0 COMMENT '成功预约数',cancel_count int DEFAULT 0 COMMENT '取消预约数',stats_month varchar(7) COMMENT '统计月份',create_time datetime DEFAULT CURRENT_TIMESTAMP,PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
二、后端实现
1. 设备使用率统计
@Service
public class DeviceUsageStatService {// 计算设备使用率public List<DeviceUsageVO> calculateUsageRate(StatQuery query) {List<DeviceUsageVO> result = new ArrayList<>();// 获取时间范围内的工作时间(分钟)long totalMinutes = getTotalWorkMinutes(query.getStartDate(), query.getEndDate());// 查询设备使用记录List<DeviceUsageRecord> records = usageMapper.selectByTimeRange(query);// 按设备分组统计Map<Long, List<DeviceUsageRecord>> deviceMap = records.stream().collect(Collectors.groupingBy(DeviceUsageRecord::getDeviceId));deviceMap.forEach((deviceId, deviceRecords) -> {DeviceUsageVO vo = new DeviceUsageVO();vo.setDeviceId(deviceId);// 计算使用时长long usedMinutes = deviceRecords.stream().mapToLong(DeviceUsageRecord::getDuration).sum();// 计算使用率double usageRate = (double) usedMinutes / totalMinutes * 100;vo.setUsageRate(NumberUtil.round(usageRate, 2));// 统计使用类型分布Map<Integer, Long> typeStats = deviceRecords.stream().collect(Collectors.groupingBy(DeviceUsageRecord::getUsageType,Collectors.counting()));vo.setTypeStats(typeStats);result.add(vo);});return result;}// 导出使用率报表public void exportUsageReport(StatQuery query, HttpServletResponse response) {List<DeviceUsageVO> statsList = calculateUsageRate(query);// 创建Excel工作簿XSSFWorkbook workbook = new XSSFWorkbook();XSSFSheet sheet = workbook.createSheet("设备使用率统计");// 创建标题行XSSFRow titleRow = sheet.createRow(0);titleRow.createCell(0).setCellValue("设备名称");titleRow.createCell(1).setCellValue("使用率(%)");titleRow.createCell(2).setCellValue("教学使用(次)");titleRow.createCell(3).setCellValue("科研使用(次)");titleRow.createCell(4).setCellValue("其他用途(次)");// 填充数据int rowNum = 1;for(DeviceUsageVO vo : statsList) {XSSFRow dataRow = sheet.createRow(rowNum++);dataRow.createCell(0).setCellValue(vo.getDeviceName());dataRow.createCell(1).setCellValue(vo.getUsageRate());dataRow.createCell(2).setCellValue(vo.getTypeStats().getOrDefault(0, 0L));dataRow.createCell(3).setCellValue(vo.getTypeStats().getOrDefault(1, 0L));dataRow.createCell(4).setCellValue(vo.getTypeStats().getOrDefault(2, 0L));}// 导出ExcelExcelUtils.export(response, workbook, "设备使用率统计.xlsx");}
}
2. 故障率分析
@Service
public class DeviceFaultAnalysisService {// 分析设备故障情况public List<FaultAnalysisVO> analyzeFaultRate(StatQuery query) {List<FaultAnalysisVO> result = new ArrayList<>();// 查询故障统计数据List<DeviceFaultStats> statsList = faultStatsMapper.selectByMonth(query);// 计算故障率和维修指标statsList.forEach(stats -> {FaultAnalysisVO vo = new FaultAnalysisVO();vo.setDeviceId(stats.getDeviceId());// 计算月平均故障次数double avgFaultCount = calculateAvgFaultCount(stats);vo.setAvgFaultCount(avgFaultCount);// 计算平均维修时长vo.setAvgRepairTime(stats.getAvgRepairTime());// 计算维修费用vo.setTotalRepairCost(stats.getRepairCost());// 分析故障趋势vo.setFaultTrend(analyzeFaultTrend(stats.getDeviceId(), query));result.add(vo);});return result;}// 分析故障趋势private List<TrendPointVO> analyzeFaultTrend(Long deviceId, StatQuery query) {// 按月统计故障次数List<Map<String, Object>> monthlyStats = faultStatsMapper.selectMonthlyStats(deviceId, query);// 计算环比变化List<TrendPointVO> trendList = new ArrayList<>();for(int i = 0; i < monthlyStats.size(); i++) {TrendPointVO point = new TrendPointVO();point.setMonth(monthlyStats.get(i).get("month").toString());point.setValue(monthlyStats.get(i).get("faultCount"));if(i > 0) {// 计算环比增长率Integer lastCount = (Integer)monthlyStats.get(i-1).get("faultCount");Integer currentCount = (Integer)monthlyStats.get(i).get("faultCount");double rate = (currentCount - lastCount) / (double)lastCount * 100;point.setGrowthRate(NumberUtil.round(rate, 2));}trendList.add(point);}return trendList;}
}
3. 预约情况统计
@Service
public class AppointmentStatService {// 统计预约情况public List<AppointmentStatVO> statAppointment(StatQuery query) {List<AppointmentStatVO> result = new ArrayList<>();// 查询预约统计数据List<AppointmentStats> statsList = appointmentStatsMapper.selectByMonth(query);statsList.forEach(stats -> {AppointmentStatVO vo = new AppointmentStatVO();vo.setDeviceId(stats.getDeviceId());// 计算预约成功率double successRate = (double)stats.getSuccessCount() / stats.getTotalCount() * 100;vo.setSuccessRate(NumberUtil.round(successRate, 2));// 计算预约取消率double cancelRate = (double)stats.getCancelCount() / stats.getTotalCount() * 100;vo.setCancelRate(NumberUtil.round(cancelRate, 2));// 统计时段分布vo.setTimeDistribution(statTimeDistribution(stats.getDeviceId(), query));result.add(vo);});return result;}// 统计预约时段分布private Map<String, Integer> statTimeDistribution(Long deviceId, StatQuery query) {return appointmentMapper.selectTimeDistribution(deviceId, query);}
}
三、前端实现
1. 数据可视化组件
<!-- 设备使用率图表 -->
<template><div class="usage-chart"><div class="chart-header"><h3>设备使用率统计</h3><div class="chart-toolbar"><el-date-pickerv-model="dateRange"type="daterange"range-separator="至"start-placeholder="开始日期"end-placeholder="结束日期"@change="handleDateChange"/><el-button type="primary" @click="exportData">导出数据</el-button></div></div><!-- 使用率柱状图 --><div class="chart-container"><div ref="usageChart" style="height: 400px"/></div><!-- 使用类型饼图 --><div class="chart-container"><div ref="typeChart" style="height: 300px"/></div></div>
</template><script>
import * as echarts from 'echarts'export default {data() {return {dateRange: [],usageChart: null,typeChart: null}},mounted() {this.initCharts()this.loadData()},methods: {initCharts() {// 初始化使用率图表this.usageChart = echarts.init(this.$refs.usageChart)this.usageChart.setOption({title: {text: '设备使用率'},tooltip: {trigger: 'axis',axisPointer: {type: 'shadow'}},xAxis: {type: 'category',data: []},yAxis: {type: 'value',name: '使用率(%)'},series: [{type: 'bar',data: []}]})// 初始化类型分布图表this.typeChart = echarts.init(this.$refs.typeChart)this.typeChart.setOption({title: {text: '使用类型分布'},tooltip: {trigger: 'item'},legend: {orient: 'vertical',left: 'left'},series: [{type: 'pie',radius: '50%',data: []}]})},loadData() {const query = {startDate: this.dateRange[0],endDate: this.dateRange[1]}// 加载使用率数据statisticsApi.getDeviceUsage(query).then(res => {const data = res.data// 更新使用率图表this.usageChart.setOption({xAxis: {data: data.map(item => item.deviceName)},series: [{data: data.map(item => item.usageRate)}]})// 更新类型分布图表this.typeChart.setOption({series: [{data: [{name: '教学', value: data.reduce((sum, item) => sum + item.typeStats[0] || 0, 0)},{name: '科研', value: data.reduce((sum, item) => sum + item.typeStats[1] || 0, 0)},{name: '其他', value: data.reduce((sum, item) => sum + item.typeStats[2] || 0, 0)}]}]})})}}
}
</script>
2. 故障分析图表
<!-- 故障分析图表 -->
<template><div class="fault-analysis"><!-- 故障率趋势图 --><div class="chart-container"><div ref="trendChart" style="height: 400px"/></div><!-- 维修指标卡片 --><el-row :gutter="20" class="stat-cards"><el-col :span="8"><el-card shadow="hover"><div slot="header"><span>月平均故障次数</span></div><div class="card-content"><count-to :startVal="0":endVal="avgFaultCount":duration="2000"/><div class="trend"><i :class="faultTrend >= 0 ? 'el-icon-top' : 'el-icon-bottom'"/><span>环比{{Math.abs(faultTrend)}}%</span></div></div></el-card></el-col><el-col :span="8"><el-card shadow="hover"><div slot="header"><span>平均维修时长(小时)</span></div><div class="card-content"><count-to :startVal="0":endVal="avgRepairTime":duration="2000"/></div></el-card></el-col><el-col :span="8"><el-card shadow="hover"><div slot="header"><span>维修总费用(元)</span></div><div class="card-content"><count-to :startVal="0":endVal="totalRepairCost":duration="2000"/></div></el-card></el-col></el-row></div>
</template><script>
import CountTo from 'vue-count-to'export default {components: {CountTo},data() {return {trendChart: null,avgFaultCount: 0,faultTrend: 0,avgRepairTime: 0,totalRepairCost: 0}},mounted() {this.initTrendChart()this.loadData()},methods: {initTrendChart() {this.trendChart = echarts.init(this.$refs.trendChart)this.trendChart.setOption({title: {text: '故障率趋势'},tooltip: {trigger: 'axis'},xAxis: {type: 'category',data: []},yAxis: {type: 'value',name: '故障次数'},series: [{type: 'line',smooth: true,data: []}]})}}
}
</script>
3. 预约统计图表
<!-- 预约统计图表 -->
<template><div class="appointment-stats"><!-- 预约成功率图表 --><div class="chart-container"><div ref="successRateChart" style="height: 350px"/></div><!-- 时段分布热力图 --><div class="chart-container"><div ref="heatmapChart" style="height: 350px"/></div></div>
</template><script>
export default {data() {return {successRateChart: null,heatmapChart: null}},methods: {initHeatmap() {const hours = ['8:00', '9:00', '10:00', '11:00', '14:00', '15:00', '16:00', '17:00']const days = ['周一', '周二', '周三', '周四', '周五']this.heatmapChart = echarts.init(this.$refs.heatmapChart)this.heatmapChart.setOption({title: {text: '预约时段分布'},tooltip: {position: 'top'},xAxis: {type: 'category',data: hours,splitArea: {show: true}},yAxis: {type: 'category',data: days,splitArea: {show: true}},visualMap: {min: 0,max: 10,calculable: true,orient: 'horizontal',left: 'center',bottom: '15%'},series: [{type: 'heatmap',data: [],label: {show: true},emphasis: {itemStyle: {shadowBlur: 10,shadowColor: 'rgba(0, 0, 0, 0.5)'}}}]})}}
}
</script>
四、主要特点
- 多维度统计分析
- 设备使用率统计
- 故障率分析
- 预约情况统计
- 时段分布分析
- 丰富的图表类型
- 柱状图
- 折线图
- 饼图
- 热力图
- 数据卡片
- 数据导出功能
- Excel报表导出
- 图表图片导出
- 数据筛选导出
- 实时数据更新
- 定时刷新
- 手动刷新
- 条件筛选
- 交互式图表
- 图表联动
- 数据钻取
- 图表缩放
通过以上设计,实现了系统数据的可视化展示和统计分析功能,为管理决策提供数据支持。