# 车辆绑定与解绑功能说明 ## 功能概述 本系统实现了完整的用户车辆绑定与解绑功能,支持用户绑定车辆、强制重新绑定、查看当前绑定车辆以及解绑车辆。 ## 一、功能特点 ### 1. 绑定规则 - ✅ 一个用户同时只能绑定一辆车 - ✅ 一辆车可以被多个用户轮班绑定 - ✅ 绑定时自动解绑旧车辆 - ✅ 支持扫码绑定和下拉选择绑定 - ✅ 强制绑定时会提示当前绑定车辆信息 ### 2. 解绑规则 - ✅ 支持手动解绑当前车辆 - ✅ 解绑操作需二次确认 - ✅ 解绑记录保留在数据库中(status=1) - ✅ 解绑成功后自动刷新用户信息 ## 二、技术实现 ### 1. 数据库设计 #### 表名:sys_user_vehicle ```sql CREATE TABLE IF NOT EXISTS sys_user_vehicle ( id BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID', user_id BIGINT(20) NOT NULL COMMENT '用户ID', vehicle_id BIGINT(20) NOT NULL COMMENT '车辆ID', bind_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '绑定时间', bind_by VARCHAR(64) DEFAULT '' COMMENT '绑定操作人', status CHAR(1) DEFAULT '0' COMMENT '绑定状态(0正常 1解绑)', remark VARCHAR(500) DEFAULT NULL COMMENT '备注', create_by VARCHAR(64) DEFAULT '' COMMENT '创建者', create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', update_by VARCHAR(64) DEFAULT '' COMMENT '更新者', update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', PRIMARY KEY (id), UNIQUE KEY idx_user_vehicle (user_id, vehicle_id) COMMENT '用户车辆唯一索引', KEY idx_user_id (user_id) COMMENT '用户ID索引', KEY idx_vehicle_id (vehicle_id) COMMENT '车辆ID索引' ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户车辆绑定表'; ``` **字段说明:** - `status`: 0=正常绑定,1=已解绑 - `bind_time`: 绑定操作时间 - `update_time`: 解绑操作时间 ### 2. 后端接口 #### Controller层:VehicleInfoController.java **基础路径:** `/system/vehicle` ##### 2.1 绑定车辆 ```java @PostMapping("/bind") public AjaxResult bindVehicle(@RequestBody Map params) ``` **请求参数:** ```json { "userId": 1, "vehicleId": 100 } ``` **响应示例:** ```json { "code": 200, "msg": "车辆绑定成功" } ``` ##### 2.2 解绑车辆 ```java @PostMapping("/unbind") public AjaxResult unbindVehicle(@RequestBody Map params) ``` **请求参数:** ```json { "userId": 1, "vehicleId": 100 } ``` **响应示例:** ```json { "code": 200, "msg": "车辆解绑成功" } ``` ##### 2.3 获取用户绑定的车辆 ```java @GetMapping("/user/bound/{userId}") public AjaxResult getUserBoundVehicle(@PathVariable("userId") Long userId) ``` **响应示例:** ```json { "code": 200, "data": { "vehicleId": 100, "vehicleNumber": "粤A12345", "vehicleType": "救护车", "vehicleBrand": "福特", "vehicleModel": "全顺" } } ``` #### Service层:VehicleInfoServiceImpl.java ##### 2.4 绑定车辆业务逻辑 ```java @Transactional public int bindVehicleToUser(Long userId, Long vehicleId) { // 先解绑用户的所有车辆(确保一个用户只能绑定一辆车) vehicleInfoMapper.unbindAllVehiclesFromUser(userId); // 绑定新车辆 return vehicleInfoMapper.bindVehicleToUser(userId, vehicleId, bindBy); } ``` **事务特性:** - 使用 `@Transactional` 保证原子性 - 先解绑再绑定,失败自动回滚 ##### 2.5 解绑车辆业务逻辑 ```java public int unbindVehicleFromUser(Long userId, Long vehicleId) { return vehicleInfoMapper.unbindVehicleFromUser(userId, vehicleId); } ``` #### Mapper层:VehicleInfoMapper.xml ##### 2.6 SQL映射 **绑定车辆:** ```xml INSERT INTO sys_user_vehicle (user_id, vehicle_id, bind_time, bind_by, status, create_by, create_time) VALUES (#{userId}, #{vehicleId}, NOW(), #{bindBy}, '0', #{bindBy}, NOW()) ``` **解绑车辆:** ```xml UPDATE sys_user_vehicle SET status = '1', update_time = NOW() WHERE user_id = #{userId} AND vehicle_id = #{vehicleId} AND status = '0' ``` **解绑所有车辆:** ```xml UPDATE sys_user_vehicle SET status = '1', update_time = NOW() WHERE user_id = #{userId} AND status = '0' ``` **获取绑定车辆:** ```xml ``` ### 3. 前端实现 #### 3.1 API封装:vehicle.js ```javascript // 绑定车辆 export function bindVehicleToUser(userId, vehicleId) { return request({ url: '/system/vehicle/bind', method: 'post', data: { userId, vehicleId } }) } // 解绑车辆 export function unbindVehicleFromUser(userId, vehicleId) { return request({ url: '/system/vehicle/unbind', method: 'post', data: { userId, vehicleId } }) } // 获取用户绑定的车辆 export function getUserBoundVehicle(userId) { return request({ url: '/system/vehicle/user/bound/' + userId, method: 'get' }) } ``` #### 3.2 绑定车辆页面:pages/bind-vehicle.vue **页面路径:** `/pages/bind-vehicle` **主要功能:** 1. **加载当前绑定车辆** ```javascript loadCurrentBoundVehicle() { const userId = this.currentUser.userId getUserBoundVehicle(userId).then(response => { if (response.code === 200 && response.data) { this.currentBoundVehicle = response.data } }) } ``` 2. **绑定车辆逻辑** ```javascript bindVehicle() { // 检查是否选择的是当前已绑定的车辆 if (this.currentBoundVehicle && this.currentBoundVehicle.vehicleId === this.selectedVehicleId) { this.$modal.showToast('当前已绑定此车辆,无需重复绑定') return } // 如果已经绑定了其他车辆,提示是否强制绑定 if (this.currentBoundVehicle) { const confirmMsg = `您当前已绑定车辆:${currentVehicleNo}\n\n确认要解绑旧车辆并绑定新车辆:${this.selectedVehiclePlate} 吗?` this.$modal.confirm(confirmMsg).then(() => { this.performBind() }) } else { // 没有绑定车辆,直接绑定 this.$modal.confirm('确认绑定车辆 ' + this.selectedVehiclePlate + ' 吗?') .then(() => { this.performBind() }) } } ``` 3. **执行绑定操作** ```javascript performBind() { const userId = this.currentUser.userId bindVehicleToUser(userId, this.selectedVehicleId).then(response => { if (response.code === 200) { this.$modal.showToast('车辆绑定成功') this.$store.dispatch('GetInfo') // 刷新用户信息 setTimeout(() => { this.$tab.navigateBack() }, 1500) } }) } ``` #### 3.3 个人中心页面:pages/mine/index.vue **页面路径:** `/pages/mine/index` **主要功能:** 1. **显示绑定车辆信息** ```vue 绑定车辆: {{ boundVehicle || '未绑定' }} ``` 2. **解绑/绑定按钮** ```vue ``` 3. **获取绑定车辆信息** ```javascript getUserInfo() { const userId = this.$store.state.user.userId // 获取用户绑定的车辆信息 if (userId) { getUserBoundVehicle(userId).then(response => { if (response.code === 200 && response.data) { const vehicle = response.data this.boundVehicle = vehicle.vehicleNumber || '未知车牌' this.boundVehicleId = vehicle.vehicleId } else { this.boundVehicle = '未绑定' this.boundVehicleId = null } }) } } ``` 4. **解绑车辆** ```javascript unbindVehicle() { const userId = this.$store.state.user.userId const vehicleId = this.boundVehicleId this.$modal.confirm(`确认取消绑定车辆 ${this.boundVehicle} 吗?`).then(() => { unbindVehicleFromUser(userId, vehicleId).then(response => { if (response.code === 200) { this.boundVehicle = '未绑定' this.boundVehicleId = null this.$modal.showToast('取消绑定成功') this.$store.dispatch('GetInfo') // 刷新用户信息 } }) }) } ``` ## 三、业务流程 ### 绑定车辆流程 ```mermaid graph TD A[用户选择车辆] --> B{已绑定车辆?} B -->|否| C[直接绑定] B -->|是| D{选择相同车辆?} D -->|是| E[提示无需重复绑定] D -->|否| F[提示强制绑定确认] F --> G{用户确认?} G -->|是| H[解绑旧车辆] G -->|否| I[取消操作] H --> J[绑定新车辆] C --> J J --> K[刷新用户信息] K --> L[返回上一页] ``` ### 解绑车辆流程 ```mermaid graph TD A[用户点击解绑] --> B{已绑定车辆?} B -->|否| C[提示未绑定] B -->|是| D[显示确认对话框] D --> E{用户确认?} E -->|是| F[调用解绑API] E -->|否| G[取消操作] F --> H{解绑成功?} H -->|是| I[更新绑定状态] H -->|否| J[显示错误信息] I --> K[刷新用户信息] K --> L[显示成功提示] ``` ## 四、使用场景 ### 场景1:首次绑定车辆 1. 用户进入个人中心,点击"绑定车辆"按钮 2. 跳转到绑定车辆页面 3. 选择车牌号或扫码 4. 确认绑定 5. 绑定成功,返回个人中心 ### 场景2:更换绑定车辆 1. 用户进入绑定车辆页面 2. 系统自动加载当前绑定车辆 3. 选择新车辆 4. 系统提示:`您当前已绑定车辆:粤A12345,确认要解绑旧车辆并绑定新车辆:粤B67890 吗?` 5. 用户确认 6. 系统自动解绑旧车辆并绑定新车辆 7. 绑定成功 ### 场景3:解绑车辆 1. 用户进入个人中心 2. 看到当前绑定车辆信息 3. 点击"取消绑定车辆"按钮 4. 系统提示:`确认取消绑定车辆 粤A12345 吗?` 5. 用户确认 6. 解绑成功,显示"未绑定" ## 五、注意事项 ### 1. 数据一致性 - 使用 `@Transactional` 注解确保绑定/解绑操作的原子性 - 通过唯一索引防止重复绑定 - 解绑操作不删除记录,只修改状态(status=1) ### 2. 权限控制 - 接口使用 `@Anonymous` 注解,允许匿名访问 - 实际生产环境建议添加权限验证 - 建议增加只能解绑自己绑定的车辆的限制 ### 3. 异常处理 - 车辆不存在时返回错误 - 车辆状态异常时不允许绑定 - 网络请求失败时有友好的错误提示 ### 4. 用户体验 - 绑定/解绑操作都需要二次确认 - 提示信息清晰明确 - 操作成功后自动刷新用户信息 - 绑定成功后延迟1.5秒自动返回 ## 六、测试要点 ### 功能测试 - ✅ 首次绑定车辆 - ✅ 强制重新绑定(替换旧车辆) - ✅ 重复绑定相同车辆(应提示无需重复绑定) - ✅ 解绑当前车辆 - ✅ 解绑后重新绑定 ### 异常测试 - ✅ 绑定不存在的车辆 - ✅ 绑定状态异常的车辆 - ✅ 网络异常时的处理 - ✅ 并发绑定同一辆车的处理 ### 边界测试 - ✅ 用户未登录时的处理 - ✅ 车辆列表为空时的处理 - ✅ 数据库操作失败时的回滚 ## 七、文件清单 ### 后端文件 1. `sql/user_vehicle_bind.sql` - 数据库表结构 2. `ruoyi-system/mapper/VehicleInfoMapper.java` - Mapper接口 3. `ruoyi-system/mapper/VehicleInfoMapper.xml` - SQL映射 4. `ruoyi-system/service/IVehicleInfoService.java` - Service接口 5. `ruoyi-system/service/impl/VehicleInfoServiceImpl.java` - Service实现 6. `ruoyi-admin/controller/VehicleInfoController.java` - Controller ### 前端文件 1. `app/api/vehicle.js` - 车辆API封装 2. `app/pages/bind-vehicle.vue` - 绑定车辆页面 3. `app/pages/mine/index.vue` - 个人中心页面 ## 八、版本历史 | 版本 | 日期 | 修改内容 | 修改人 | |------|------|---------|--------| | 1.0 | 2025-10-15 | 初始版本,实现基础绑定解绑功能 | - | | 1.1 | 2025-10-15 | 修复API路径错误,完善解绑功能 | - | | 1.2 | 2025-10-15 | 完善个人中心解绑功能实现 | - |