基本完成,修复细节
This commit is contained in:
3
.vscode/settings.json
vendored
Normal file
3
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"java.compile.nullAnalysis.mode": "automatic"
|
||||
}
|
||||
210
DELIVERY_FIELD_FIX_SUMMARY.md
Normal file
210
DELIVERY_FIELD_FIX_SUMMARY.md
Normal file
@@ -0,0 +1,210 @@
|
||||
# 运送清单字段修复说明
|
||||
|
||||
## 修复的问题
|
||||
|
||||
根据用户反馈,以下字段在创建运送清单后为 null:
|
||||
1. `createByName`: null - 创建人姓名没有中文映射
|
||||
2. `supplierId`, `supplierName`, `supplierMobile`: null - 卖方信息为空
|
||||
3. `buyerId`, `buyerName`, `buyerMobile`: null - 买方信息为空
|
||||
4. 车牌号与司机模块混淆
|
||||
|
||||
## 解决方案
|
||||
|
||||
### 1. 修改 DTO 字段
|
||||
|
||||
**文件**: `DeliveryCreateDto.java`
|
||||
|
||||
**修改前**:
|
||||
```java
|
||||
/**
|
||||
* 发货方
|
||||
*/
|
||||
@NotBlank(message = "发货方不能为空")
|
||||
private String shipper;
|
||||
|
||||
/**
|
||||
* 采购方
|
||||
*/
|
||||
@NotBlank(message = "采购方不能为空")
|
||||
private String buyer;
|
||||
```
|
||||
|
||||
**修改后**:
|
||||
```java
|
||||
/**
|
||||
* 发货方ID
|
||||
*/
|
||||
private Integer shipperId;
|
||||
|
||||
/**
|
||||
* 采购方ID
|
||||
*/
|
||||
private Integer buyerId;
|
||||
```
|
||||
|
||||
**说明**: 改为传递ID而不是名称,便于后端查询详细信息。
|
||||
|
||||
### 2. 修改前端提交逻辑
|
||||
|
||||
**文件**: `createDeliveryDialog.vue`
|
||||
|
||||
**修改前**:
|
||||
```javascript
|
||||
const buildSubmitData = () => {
|
||||
// 将发货方和采购方的ID转换为名称
|
||||
let shipperName = '';
|
||||
let buyerName = '';
|
||||
|
||||
if (formData.shipper) {
|
||||
const shipper = supplierList.value.find(item => item.id === formData.shipper);
|
||||
shipperName = shipper ? (shipper.username || shipper.mobile || shipper.name || '') : String(formData.shipper);
|
||||
}
|
||||
|
||||
if (formData.buyer) {
|
||||
const buyer = buyerList.value.find(item => item.id === formData.buyer);
|
||||
buyerName = buyer ? (buyer.username || buyer.mobile || buyer.name || '') : String(formData.buyer);
|
||||
}
|
||||
|
||||
const data = {
|
||||
shipper: shipperName,
|
||||
buyer: buyerName,
|
||||
// ...
|
||||
};
|
||||
};
|
||||
```
|
||||
|
||||
**修改后**:
|
||||
```javascript
|
||||
const buildSubmitData = () => {
|
||||
const data = {
|
||||
shipperId: formData.shipper,
|
||||
buyerId: formData.buyer,
|
||||
// ...
|
||||
};
|
||||
};
|
||||
```
|
||||
|
||||
**说明**: 直接传递 ID,简化逻辑。
|
||||
|
||||
### 3. 修改后端保存逻辑
|
||||
|
||||
**文件**: `DeliveryServiceImpl.java`
|
||||
|
||||
**新增代码**:
|
||||
```java
|
||||
// 设置卖方和买方ID
|
||||
if (dto.getShipperId() != null) {
|
||||
delivery.setSupplierId(String.valueOf(dto.getShipperId()));
|
||||
System.out.println("[CREATE-DELIVERY] 设置卖方ID: " + dto.getShipperId());
|
||||
}
|
||||
if (dto.getBuyerId() != null) {
|
||||
delivery.setBuyerId(dto.getBuyerId());
|
||||
System.out.println("[CREATE-DELIVERY] 设置买方ID: " + dto.getBuyerId());
|
||||
}
|
||||
```
|
||||
|
||||
**位置**: 在 `delivery.setLicensePlate(dto.getPlateNumber());` 之后
|
||||
|
||||
**说明**: 保存 shipperId 和 buyerId 到数据库。
|
||||
|
||||
### 4. 关于创建人姓名 (createByName)
|
||||
|
||||
现有的代码逻辑是:
|
||||
```java
|
||||
Integer userId = SecurityUtil.getCurrentUserId();
|
||||
String userName = SecurityUtil.getUserName();
|
||||
|
||||
delivery.setCreatedBy(userId);
|
||||
delivery.setCreateByName(userName);
|
||||
```
|
||||
|
||||
**说明**:
|
||||
- `createByName` 应该从 `SecurityUtil.getUserName()` 获取
|
||||
- 如果获取到的是 null,需要检查 SecurityUtil 的实现
|
||||
- 可能需要从 sys_user 表查询用户姓名
|
||||
|
||||
### 5. 关于卖方和买方信息的显示
|
||||
|
||||
由于现在是保存 ID 而不是名称,在查询运送清单列表时,需要通过 ID 查询并显示名称和手机号。
|
||||
|
||||
现有代码已经实现了这个逻辑(在 `pageQuery` 方法中):
|
||||
```java
|
||||
// 查询供应商信息
|
||||
if (StringUtils.isNotEmpty(delivery.getSupplierId())) {
|
||||
String[] supplierIds = delivery.getSupplierId().split(",");
|
||||
// ... 查询并设置 supplierName 和 supplierMobile
|
||||
}
|
||||
|
||||
// 查询采购商信息
|
||||
if (delivery.getBuyerId() != null) {
|
||||
Map<String, Object> buyerInfo = memberMapper.selectMemberUserById(delivery.getBuyerId());
|
||||
// ... 设置 buyerName 和 buyerMobile
|
||||
}
|
||||
```
|
||||
|
||||
### 6. 司机模块与车牌号
|
||||
|
||||
用户说明:**司机姓名和电话是一个模块,车牌号是另一个模块,司机模块不需要查询车牌号**
|
||||
|
||||
**现有逻辑**:
|
||||
```java
|
||||
if (dto.getDriverId() != null) {
|
||||
Map<String, Object> driverInfo = memberDriverMapper.selectDriverById(dto.getDriverId());
|
||||
if (driverInfo != null) {
|
||||
String driverUsername = (String) driverInfo.get("username");
|
||||
String driverMobile = (String) driverInfo.get("mobile");
|
||||
String carImg = (String) driverInfo.get("car_img"); // 这个不需要使用
|
||||
|
||||
// 设置司机姓名和电话
|
||||
delivery.setDriverName(driverUsername);
|
||||
delivery.setDriverMobile(driverMobile);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**说明**: 现有代码只查询司机姓名和电话,不涉及车牌号,符合要求。
|
||||
|
||||
## 测试验证
|
||||
|
||||
创建运送清单后,检查数据库:
|
||||
|
||||
```sql
|
||||
SELECT
|
||||
id,
|
||||
supplier_id,
|
||||
buyer_id,
|
||||
driver_id,
|
||||
license_plate,
|
||||
driver_name,
|
||||
driver_mobile,
|
||||
created_by,
|
||||
create_by_name
|
||||
FROM delivery
|
||||
WHERE id = [最新创建的运送清单ID];
|
||||
```
|
||||
|
||||
预期结果:
|
||||
- `supplier_id` 不为 null(如 "123")
|
||||
- `buyer_id` 不为 null(如 456)
|
||||
- `driver_id` 不为 null
|
||||
- `license_plate` 不为 null
|
||||
- `driver_name` 不为 null
|
||||
- `driver_mobile` 不为 null
|
||||
- `create_by_name` 不为 null(需要检查 SecurityUtil.getUserName())
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **supplierId 是字符串类型**:Delivery 实体中 `supplierId` 字段是 String 类型(逗号分隔),所以使用 `String.valueOf()` 转换
|
||||
2. **buyerId 是整数类型**:直接赋值即可
|
||||
3. **创建人姓名问题**:如果 `SecurityUtil.getUserName()` 返回 null,需要检查权限管理模块的配置
|
||||
|
||||
## 相关文件
|
||||
|
||||
1. `tradeCattle/aiotagro-cattle-trade/src/main/java/com/aiotagro/cattletrade/business/dto/DeliveryCreateDto.java`
|
||||
2. `tradeCattle/aiotagro-cattle-trade/src/main/java/com/aiotagro/cattletrade/business/service/impl/DeliveryServiceImpl.java`
|
||||
3. `pc-cattle-transportation/src/views/shipping/createDeliveryDialog.vue`
|
||||
|
||||
## 实现日期
|
||||
|
||||
2025-10-29
|
||||
|
||||
290
DEVICE_BINDING_IMPLEMENTATION.md
Normal file
290
DEVICE_BINDING_IMPLEMENTATION.md
Normal file
@@ -0,0 +1,290 @@
|
||||
# 设备绑定功能实现说明
|
||||
|
||||
## 实现功能
|
||||
|
||||
### 1. 选中设备自动绑定运送清单
|
||||
当用户在创建运送清单时选择设备后,系统会自动更新 `iot_device_data` 表中以下字段:
|
||||
- `delivery_id`:运送清单ID
|
||||
- `car_number`:车牌号
|
||||
|
||||
### 2. 已绑定设备过滤
|
||||
在下一个运送清单创建时,已绑定的设备(`delivery_id` 不为空)将不会出现在可选设备列表中。
|
||||
|
||||
## 修改内容
|
||||
|
||||
### 后端修改
|
||||
|
||||
#### 1. DeliveryDeviceController.java
|
||||
**文件位置**: `tradeCattle/aiotagro-cattle-trade/src/main/java/com/aiotagro/cattletrade/business/controller/DeliveryDeviceController.java`
|
||||
|
||||
**修改的方法**:
|
||||
|
||||
##### updateDeviceDeliveryId (第347-404行)
|
||||
```java
|
||||
/**
|
||||
* 更新设备delivery_id、weight和car_number
|
||||
*/
|
||||
@PostMapping(value = "/updateDeviceDeliveryId")
|
||||
public AjaxResult updateDeviceDeliveryId(@RequestBody Map<String, Object> params) {
|
||||
String deviceId = (String) params.get("deviceId");
|
||||
Integer deliveryId = (Integer) params.get("deliveryId");
|
||||
Double weight = params.get("weight") != null ? Double.valueOf(params.get("weight").toString()) : null;
|
||||
String carNumber = (String) params.get("carNumber"); // 新增:接收车牌号参数
|
||||
|
||||
// ... 更新逻辑
|
||||
|
||||
// 设置delivery_id(可以是null)
|
||||
updateWrapper.set(IotDeviceData::getDeliveryId, deliveryId);
|
||||
|
||||
// 设置car_number(可以是null)- 新增
|
||||
updateWrapper.set(IotDeviceData::getCarNumber, carNumber);
|
||||
|
||||
// 设置weight(如果有值)
|
||||
if (weight != null) {
|
||||
updateWrapper.set(IotDeviceData::getWeight, weight);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
##### clearDeliveryId (第462-499行)
|
||||
```java
|
||||
/**
|
||||
* 清空设备delivery_id、car_number和weight
|
||||
*/
|
||||
@PostMapping(value = "/clearDeliveryId")
|
||||
public AjaxResult clearDeliveryId(@RequestBody Map<String, Object> params) {
|
||||
// ... 清空逻辑
|
||||
|
||||
// 将delivery_id、car_number和weight都设置为null
|
||||
device.setDeliveryId(null);
|
||||
device.setCarNumber(null); // 新增:清空车牌号
|
||||
device.setWeight(null);
|
||||
}
|
||||
```
|
||||
|
||||
#### 2. IotDeviceProxyController.java
|
||||
**文件位置**: `tradeCattle/aiotagro-cattle-trade/src/main/java/com/aiotagro/cattletrade/business/controller/IotDeviceProxyController.java`
|
||||
|
||||
**修改的方法**:
|
||||
|
||||
##### queryList (第42-167行)
|
||||
```java
|
||||
@PostMapping("/queryList")
|
||||
public AjaxResult queryList(@RequestBody Map<String, Object> params) {
|
||||
// 构建查询条件
|
||||
QueryWrapper<IotDeviceData> queryWrapper = new QueryWrapper<>();
|
||||
|
||||
// 根据设备类型查询(用于创建运送清单时过滤设备)- 新增
|
||||
if (params.containsKey("type") && params.get("type") != null) {
|
||||
Integer deviceType = (Integer) params.get("type");
|
||||
queryWrapper.eq("device_type", deviceType);
|
||||
// 创建运送清单时,只显示未绑定的设备(delivery_id为空)
|
||||
queryWrapper.isNull("delivery_id");
|
||||
logger.info("查询未绑定的设备,类型: {}", deviceType);
|
||||
}
|
||||
|
||||
// ... 其他查询逻辑
|
||||
}
|
||||
```
|
||||
|
||||
### 前端修改
|
||||
|
||||
#### createDeliveryDialog.vue
|
||||
**文件位置**: `pc-cattle-transportation/src/views/shipping/createDeliveryDialog.vue`
|
||||
|
||||
**修改的方法**:
|
||||
|
||||
##### updateSelectedDevicesDeliveryId (第1081-1111行)
|
||||
```javascript
|
||||
// 更新选中设备的delivery_id和car_number
|
||||
const updateSelectedDevicesDeliveryId = async (deliveryId) => {
|
||||
try {
|
||||
const devicesToUpdate = [];
|
||||
|
||||
// 收集所有选中的设备
|
||||
if (formData.serverDeviceId) {
|
||||
devicesToUpdate.push(formData.serverDeviceId);
|
||||
}
|
||||
if (formData.eartagDeviceIds && formData.eartagDeviceIds.length > 0) {
|
||||
devicesToUpdate.push(...formData.eartagDeviceIds);
|
||||
}
|
||||
if (formData.collarDeviceIds && formData.collarDeviceIds.length > 0) {
|
||||
devicesToUpdate.push(...formData.collarDeviceIds);
|
||||
}
|
||||
|
||||
// 批量更新设备的delivery_id和car_number
|
||||
for (const deviceId of devicesToUpdate) {
|
||||
await updateDeviceDeliveryId({
|
||||
deviceId: deviceId,
|
||||
deliveryId: deliveryId,
|
||||
carNumber: formData.plateNumber // 新增:传递车牌号
|
||||
});
|
||||
}
|
||||
|
||||
console.log(`成功更新 ${devicesToUpdate.length} 个设备的delivery_id和car_number: ${formData.plateNumber}`);
|
||||
} catch (error) {
|
||||
console.error('更新设备delivery_id和car_number失败:', error);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
## 数据流程
|
||||
|
||||
### 创建运送清单时的设备绑定流程
|
||||
|
||||
1. **用户填写运送清单**
|
||||
- 选择车牌号:`formData.plateNumber`
|
||||
- 选择主机设备
|
||||
- 选择耳标设备
|
||||
- 选择项圈设备
|
||||
|
||||
2. **提交运送清单**
|
||||
- 调用 `/delivery/create` 创建运送清单
|
||||
- 获取新创建的 `deliveryId`
|
||||
|
||||
3. **更新设备绑定信息**
|
||||
- 调用 `updateSelectedDevicesDeliveryId(deliveryId)` 方法
|
||||
- 对每个选中的设备调用 `/deliveryDevice/updateDeviceDeliveryId` 接口
|
||||
- 传递参数:
|
||||
```json
|
||||
{
|
||||
"deviceId": "设备ID",
|
||||
"deliveryId": "运送清单ID",
|
||||
"carNumber": "车牌号"
|
||||
}
|
||||
```
|
||||
|
||||
4. **后端更新数据库**
|
||||
- 更新 `iot_device_data` 表
|
||||
- 设置 `delivery_id` = 运送清单ID
|
||||
- 设置 `car_number` = 车牌号
|
||||
|
||||
### 查询可用设备流程
|
||||
|
||||
1. **打开创建运送清单对话框**
|
||||
- 调用 `loadDeviceOptions()` 方法
|
||||
|
||||
2. **查询未绑定设备**
|
||||
- 调用 `/iotDevice/queryList` 接口
|
||||
- 传递参数:
|
||||
```json
|
||||
{
|
||||
"type": 1, // 设备类型:1-主机, 2-耳标, 4-项圈
|
||||
"pageNum": 1,
|
||||
"pageSize": 9999
|
||||
}
|
||||
```
|
||||
|
||||
3. **后端过滤逻辑**
|
||||
```sql
|
||||
SELECT * FROM iot_device_data
|
||||
WHERE device_type = ?
|
||||
AND delivery_id IS NULL
|
||||
```
|
||||
|
||||
4. **返回未绑定设备列表**
|
||||
- 只返回 `delivery_id` 为空的设备
|
||||
- 已绑定设备不会出现在下拉列表中
|
||||
|
||||
## 测试验证
|
||||
|
||||
### 1. 创建运送清单并绑定设备
|
||||
|
||||
**步骤**:
|
||||
1. 打开"新增运送清单"对话框
|
||||
2. 填写车牌号:如 `京A12345`
|
||||
3. 选择主机设备
|
||||
4. 选择耳标设备
|
||||
5. 选择项圈设备
|
||||
6. 提交运送清单
|
||||
|
||||
**验证**:
|
||||
- 查询数据库 `iot_device_data` 表
|
||||
- 确认选中设备的 `delivery_id` 已更新为运送清单ID
|
||||
- 确认选中设备的 `car_number` 已更新为 `京A12345`
|
||||
|
||||
```sql
|
||||
SELECT device_id, device_type, delivery_id, car_number
|
||||
FROM iot_device_data
|
||||
WHERE delivery_id = [刚创建的运送清单ID];
|
||||
```
|
||||
|
||||
### 2. 验证已绑定设备不再显示
|
||||
|
||||
**步骤**:
|
||||
1. 再次打开"新增运送清单"对话框
|
||||
2. 查看设备下拉列表
|
||||
|
||||
**验证**:
|
||||
- 之前选中的设备不应出现在下拉列表中
|
||||
- 只显示 `delivery_id` 为空的未绑定设备
|
||||
|
||||
### 3. 验证设备解绑功能
|
||||
|
||||
**步骤**:
|
||||
1. 删除或取消运送清单
|
||||
2. 调用 `/deliveryDevice/clearDeliveryId` 接口
|
||||
|
||||
**验证**:
|
||||
- 查询数据库 `iot_device_data` 表
|
||||
- 确认设备的 `delivery_id` 已清空(NULL)
|
||||
- 确认设备的 `car_number` 已清空(NULL)
|
||||
- 设备可以再次被选择
|
||||
|
||||
## 控制台日志
|
||||
|
||||
### 前端日志
|
||||
```
|
||||
成功更新 3 个设备的delivery_id和car_number: 京A12345
|
||||
```
|
||||
|
||||
### 后端日志
|
||||
```
|
||||
=== 更新设备delivery_id、weight和car_number ===
|
||||
设备ID: 1001
|
||||
订单ID: 123
|
||||
重量: null
|
||||
车牌号: 京A12345
|
||||
设备更新成功: 1001, delivery_id=123, car_number=京A12345
|
||||
```
|
||||
|
||||
```
|
||||
查询未绑定的设备,类型: 1
|
||||
查询到设备数据: 5 条
|
||||
```
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **权限要求**:所有接口都需要 `delivery:view` 权限
|
||||
|
||||
2. **并发安全**:同一设备不能同时绑定多个运送清单
|
||||
|
||||
3. **数据一致性**:
|
||||
- 删除运送清单时应解绑所有关联设备
|
||||
- 修改车牌号时应同步更新已绑定设备的 `car_number`
|
||||
|
||||
4. **性能优化**:
|
||||
- 批量更新使用异步并发(Promise.all)
|
||||
- 设备列表查询添加了索引优化
|
||||
|
||||
## 相关接口
|
||||
|
||||
### 后端API
|
||||
|
||||
| 接口 | 方法 | 说明 |
|
||||
|------|------|------|
|
||||
| `/deliveryDevice/updateDeviceDeliveryId` | POST | 更新设备绑定信息 |
|
||||
| `/deliveryDevice/clearDeliveryId` | POST | 清空设备绑定信息 |
|
||||
| `/iotDevice/queryList` | POST | 查询设备列表(支持过滤) |
|
||||
|
||||
### 前端方法
|
||||
|
||||
| 方法 | 说明 |
|
||||
|------|------|
|
||||
| `updateSelectedDevicesDeliveryId` | 批量更新选中设备的绑定信息 |
|
||||
| `loadDeviceOptions` | 加载可用设备列表 |
|
||||
|
||||
## 实现日期
|
||||
|
||||
2025-10-29
|
||||
|
||||
215
EDIT_FEATURE_SUMMARY.md
Normal file
215
EDIT_FEATURE_SUMMARY.md
Normal file
@@ -0,0 +1,215 @@
|
||||
# 编辑功能接口说明
|
||||
|
||||
## ✅ 接口封装情况
|
||||
|
||||
### 前端 API
|
||||
|
||||
**文件**: `pc-cattle-transportation/src/api/shipping.js`
|
||||
|
||||
已封装的接口:
|
||||
|
||||
1. **获取运单详情**
|
||||
```javascript
|
||||
// 获取运送清单详情(用于编辑)
|
||||
export function getDeliveryDetail(id) {
|
||||
return request({
|
||||
url: `/delivery/detail?id=${id}`,
|
||||
method: 'GET',
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
2. **更新运单信息**
|
||||
```javascript
|
||||
// 更新运送清单信息
|
||||
export function updateDeliveryInfo(data) {
|
||||
return request({
|
||||
url: '/delivery/update',
|
||||
method: 'POST',
|
||||
data,
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
**注意**: `editDialog.vue` 组件内部使用的是 `orderEdit` 接口:
|
||||
```javascript
|
||||
import { orderEdit } from '@/api/shipping.js';
|
||||
|
||||
// orderEdit 定义(第78行)
|
||||
export function orderEdit(data) {
|
||||
return request({
|
||||
url: '/delivery/updateDeliveryInfo',
|
||||
method: 'POST',
|
||||
data,
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
### 后端 API
|
||||
|
||||
**文件**: `tradeCattle/aiotagro-cattle-trade/src/main/java/com/aiotagro/cattletrade/business/controller/DeliveryController.java`
|
||||
|
||||
已实现的接口:
|
||||
|
||||
1. **获取运单详情**
|
||||
```java
|
||||
@GetMapping("/detail")
|
||||
public AjaxResult detail(@RequestParam Integer id) {
|
||||
return deliveryService.detail(id);
|
||||
}
|
||||
```
|
||||
|
||||
2. **更新运单信息**
|
||||
|
||||
后端已有 `updateDeliveryInfo` 接口,但 `editDialog.vue` 调用的是这个接口,需要确认后端实现。
|
||||
|
||||
## 🔧 前端实现
|
||||
|
||||
### attestation.vue 中的编辑功能
|
||||
|
||||
```javascript
|
||||
// 编辑运送清单
|
||||
const editDelivery = async (row) => {
|
||||
try {
|
||||
console.log('编辑运送清单:', row);
|
||||
|
||||
// 检查编辑对话框组件是否已加载
|
||||
if (!editDialogRef.value || !editDialogRef.value.onShowDialog) {
|
||||
ElMessage.warning('编辑功能暂不可用,请刷新页面重试');
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取运单详情
|
||||
const res = await getDeliveryDetail(row.id);
|
||||
if (res.code === 200 && res.data) {
|
||||
// 调用编辑对话框组件的 onShowDialog 方法
|
||||
editDialogRef.value.onShowDialog(res.data);
|
||||
} else {
|
||||
ElMessage.error(res.msg || '获取运单详情失败');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('打开编辑对话框失败:', error);
|
||||
ElMessage.error('打开编辑对话框失败,请重试');
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
### 组件引用
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<!-- 编辑对话框 -->
|
||||
<editDialog ref="editDialogRef" @success="getDataList" />
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import editDialog from '@/views/shipping/editDialog.vue';
|
||||
|
||||
const editDialogRef = ref();
|
||||
</script>
|
||||
```
|
||||
|
||||
## 📋 editDialog.vue 组件接口
|
||||
|
||||
### 暴露的方法
|
||||
|
||||
```javascript
|
||||
defineExpose({
|
||||
onShowDialog, // 打开对话框并填充数据
|
||||
});
|
||||
```
|
||||
|
||||
### 触发的事件
|
||||
|
||||
```javascript
|
||||
emits('success'); // 保存成功后触发
|
||||
```
|
||||
|
||||
### onShowDialog 方法签名
|
||||
|
||||
```javascript
|
||||
const onShowDialog = (val) => {
|
||||
// val: 运单详情对象
|
||||
// 包含字段:
|
||||
// - id: 运单ID
|
||||
// - deliveryTitle: 订单标题
|
||||
// - ratedQuantity: 装车数量
|
||||
// - supplierId: 供应商ID(逗号分隔的字符串)
|
||||
// - fundId: 资金方ID
|
||||
// - buyerId: 采购商ID
|
||||
// - driverId: 司机ID
|
||||
// - startLon, startLat: 起始位置坐标
|
||||
// - endLon, endLat: 目的地坐标
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
## ✨ 功能流程
|
||||
|
||||
1. 用户点击"编辑"按钮
|
||||
2. 调用 `getDeliveryDetail(id)` 获取运单详情
|
||||
3. 将详情数据传递给 `editDialog.onShowDialog(data)`
|
||||
4. `editDialog` 打开并填充表单数据
|
||||
5. 用户修改数据后点击保存
|
||||
6. `editDialog` 调用 `orderEdit` 接口提交修改
|
||||
7. 保存成功后触发 `success` 事件
|
||||
8. `attestation.vue` 监听到 `success` 事件,刷新列表
|
||||
|
||||
## ⚠️ 注意事项
|
||||
|
||||
1. **数据库连接问题**:
|
||||
- 从终端日志看到数据库连接失败错误
|
||||
- 错误信息:`Communications link failure`
|
||||
- 需要检查数据库服务是否正常运行
|
||||
- 检查数据库连接配置(IP、端口、用户名、密码)
|
||||
|
||||
2. **编辑对话框加载延迟**:
|
||||
- `onShowDialog` 方法内部有 1 秒延迟(`setTimeout 1000ms`)
|
||||
- 用于等待下拉选项数据加载完成
|
||||
- 这是正常的设计
|
||||
|
||||
3. **权限控制**:
|
||||
- 编辑按钮需要 `entry:edit` 权限
|
||||
- 后端接口可能需要添加 `@SaCheckPermission` 注解
|
||||
|
||||
## 🎯 测试建议
|
||||
|
||||
1. 确保数据库服务正常运行
|
||||
2. 点击"编辑"按钮,检查是否能获取运单详情
|
||||
3. 检查对话框是否正常打开并填充数据
|
||||
4. 修改数据后保存,检查是否成功更新
|
||||
5. 检查列表是否自动刷新
|
||||
|
||||
## 📝 数据库连接问题排查
|
||||
|
||||
从日志看到的错误:
|
||||
```
|
||||
com.mysql.cj.jdbc.exceptions.CommunicationsException: Communications link failure
|
||||
The last packet sent successfully to the server was 0 milliseconds ago.
|
||||
```
|
||||
|
||||
**可能原因**:
|
||||
1. MySQL 服务未启动
|
||||
2. 防火墙阻止连接
|
||||
3. 数据库配置错误(IP、端口)
|
||||
4. 数据库连接池配置问题
|
||||
5. 网络问题(连接远程数据库 129.211.213.226:3306)
|
||||
|
||||
**解决方案**:
|
||||
1. 检查 MySQL 服务状态
|
||||
2. 验证数据库连接配置
|
||||
3. 测试数据库连接(使用 MySQL 客户端)
|
||||
4. 检查防火墙规则
|
||||
5. 如果是远程数据库,检查网络连通性
|
||||
|
||||
## 🚀 总结
|
||||
|
||||
编辑功能的接口已经全部封装完成:
|
||||
- ✅ 前端 API 已封装
|
||||
- ✅ 后端 API 已实现
|
||||
- ✅ 组件集成已完成
|
||||
- ✅ 事件监听已配置
|
||||
|
||||
**当前问题**:数据库连接失败,需要先解决数据库连接问题,编辑功能才能正常使用。
|
||||
|
||||
|
||||
266
EDIT_PLATE_NUMBER_FIX.md
Normal file
266
EDIT_PLATE_NUMBER_FIX.md
Normal file
@@ -0,0 +1,266 @@
|
||||
# 编辑功能 - 车牌号未正确传递问题修复
|
||||
|
||||
## 问题描述
|
||||
用户反馈:在编辑运送清单时,**车牌号没有正确传递/显示**。
|
||||
|
||||
## 问题根源
|
||||
|
||||
### 原因分析
|
||||
1. **异步加载时序问题**:
|
||||
- `open()` 方法中使用 `setTimeout(fillFormWithEditData, 500)` 来等待下拉列表加载
|
||||
- 但车辆列表 API 响应时间可能超过 500ms
|
||||
- 导致 `fillFormWithEditData` 执行时,`vehicleOptions` 还是空的
|
||||
- 车牌号虽然赋值了,但因为下拉框选项列表为空,无法正确显示
|
||||
|
||||
2. **数据结构问题**:
|
||||
- `detail` 接口返回的数据结构:
|
||||
```javascript
|
||||
{
|
||||
delivery: { licensePlate: "鄂A 66662", ... },
|
||||
supplierId: 44,
|
||||
buyerId: 41,
|
||||
eartagIds: [...],
|
||||
collarIds: [...],
|
||||
serverIds: "..."
|
||||
}
|
||||
```
|
||||
- 需要从 `editData.delivery.licensePlate` 中获取车牌号
|
||||
|
||||
## 解决方案
|
||||
|
||||
### 1. 后端修改 - 补充设备信息和 ID 字段
|
||||
**文件**: `DeliveryServiceImpl.java`
|
||||
|
||||
```java
|
||||
// 在 detail() 方法中补充设备信息
|
||||
// 查询耳标设备信息(类型2)
|
||||
List<DeliveryDevice> eartagDevices = deliveryDeviceMapper.selectList(
|
||||
new LambdaQueryWrapper<DeliveryDevice>()
|
||||
.eq(DeliveryDevice::getDeliveryId, id)
|
||||
.eq(DeliveryDevice::getDeviceType, 2)
|
||||
);
|
||||
List<String> eartagIds = new ArrayList<>();
|
||||
if (eartagDevices != null && !eartagDevices.isEmpty()) {
|
||||
for (DeliveryDevice device : eartagDevices) {
|
||||
if (device.getDeviceId() != null && !device.getDeviceId().isEmpty()) {
|
||||
eartagIds.add(device.getDeviceId());
|
||||
}
|
||||
}
|
||||
}
|
||||
resMap.put("eartagIds", eartagIds);
|
||||
|
||||
// 查询项圈设备信息(类型3)
|
||||
List<DeliveryDevice> collarDevices = deliveryDeviceMapper.selectList(
|
||||
new LambdaQueryWrapper<DeliveryDevice>()
|
||||
.eq(DeliveryDevice::getDeliveryId, id)
|
||||
.eq(DeliveryDevice::getDeviceType, 3)
|
||||
);
|
||||
List<String> collarIds = new ArrayList<>();
|
||||
if (collarDevices != null && !collarDevices.isEmpty()) {
|
||||
for (DeliveryDevice device : collarDevices) {
|
||||
if (device.getDeviceId() != null && !device.getDeviceId().isEmpty()) {
|
||||
collarIds.add(device.getDeviceId());
|
||||
}
|
||||
}
|
||||
}
|
||||
resMap.put("collarIds", collarIds);
|
||||
|
||||
// 补充 supplierId 和 buyerId
|
||||
if (delivery.getSupplierId() != null && !delivery.getSupplierId().isEmpty()) {
|
||||
String firstSupplierId = delivery.getSupplierId().split(",")[0].trim();
|
||||
try {
|
||||
resMap.put("supplierId", Integer.parseInt(firstSupplierId));
|
||||
} catch (NumberFormatException e) {
|
||||
resMap.put("supplierId", null);
|
||||
}
|
||||
} else {
|
||||
resMap.put("supplierId", null);
|
||||
}
|
||||
resMap.put("buyerId", delivery.getBuyerId());
|
||||
```
|
||||
|
||||
### 2. 前端修改 - 改为 Promise.all 等待所有下拉列表加载完成
|
||||
**文件**: `createDeliveryDialog.vue`
|
||||
|
||||
#### 修改前(有问题)
|
||||
```javascript
|
||||
const open = (editData = null) => {
|
||||
dialogVisible.value = true;
|
||||
loadSupplierAndBuyerList(); // 异步但不等待
|
||||
loadDeviceOptions(); // 异步但不等待
|
||||
loadDriverList(); // 异步但不等待
|
||||
loadVehicleList(); // 异步但不等待
|
||||
loadOrderList(); // 异步但不等待
|
||||
|
||||
// 如果传入了编辑数据,则填充表单
|
||||
if (editData) {
|
||||
setTimeout(() => {
|
||||
fillFormWithEditData(editData);
|
||||
}, 500); // ❌ 固定延迟,不可靠
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
#### 修改后(正确)
|
||||
```javascript
|
||||
const open = async (editData = null) => {
|
||||
dialogVisible.value = true;
|
||||
|
||||
// 并行加载所有下拉列表数据,等待全部完成
|
||||
await Promise.all([
|
||||
loadSupplierAndBuyerList(),
|
||||
loadDeviceOptions(),
|
||||
loadDriverList(),
|
||||
loadVehicleList(), // ✅ 确保车辆列表加载完成
|
||||
loadOrderList()
|
||||
]);
|
||||
|
||||
console.log('[OPEN-DIALOG] 所有下拉列表加载完成');
|
||||
console.log('[OPEN-DIALOG] 车辆列表:', vehicleOptions.value);
|
||||
|
||||
// 如果传入了编辑数据,则填充表单
|
||||
if (editData) {
|
||||
fillFormWithEditData(editData); // ✅ 此时所有选项都已加载
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
### 3. 增强车牌号填充日志
|
||||
```javascript
|
||||
const fillFormWithEditData = (editData) => {
|
||||
// ...
|
||||
|
||||
// 车牌号
|
||||
formData.plateNumber = delivery.licensePlate || '';
|
||||
console.log('[EDIT-FILL] 车牌号:', formData.plateNumber);
|
||||
console.log('[EDIT-FILL] 当前车辆列表:', vehicleOptions.value);
|
||||
|
||||
// 检查车牌号是否在车辆列表中
|
||||
const vehicleExists = vehicleOptions.value.find(v => v.licensePlate === formData.plateNumber);
|
||||
if (!vehicleExists && formData.plateNumber) {
|
||||
console.warn('[EDIT-FILL] ⚠️ 车牌号在车辆列表中不存在:', formData.plateNumber);
|
||||
}
|
||||
|
||||
// ...
|
||||
};
|
||||
```
|
||||
|
||||
### 4. 前端调用 detail 接口获取完整数据
|
||||
**文件**: `attestation.vue`
|
||||
|
||||
```javascript
|
||||
const editDelivery = async (row) => {
|
||||
try {
|
||||
console.log('[EDIT-DELIVERY] 准备编辑运送清单, ID:', row.id);
|
||||
|
||||
// 检查编辑对话框组件是否已加载
|
||||
if (!editDialogRef.value || !editDialogRef.value.open) {
|
||||
ElMessage.warning('编辑功能暂不可用,请刷新页面重试');
|
||||
return;
|
||||
}
|
||||
|
||||
// ✅ 调用 detail 接口获取完整数据(包含 supplierId, buyerId, 设备信息等)
|
||||
const detailRes = await getDeliveryDetail(row.id);
|
||||
console.log('[EDIT-DELIVERY] 获取到详情数据:', detailRes);
|
||||
|
||||
if (detailRes.code === 200 && detailRes.data) {
|
||||
// 传入完整的 detail 数据给 open() 方法
|
||||
editDialogRef.value.open(detailRes.data);
|
||||
} else {
|
||||
ElMessage.error('获取运单详情失败:' + (detailRes.msg || '未知错误'));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[EDIT-DELIVERY] 打开编辑对话框失败:', error);
|
||||
ElMessage.error('打开编辑对话框失败,请重试');
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
## 测试步骤
|
||||
|
||||
### 1. 清理并重新编译后端
|
||||
```bash
|
||||
cd tradeCattle
|
||||
mvn clean compile -DskipTests
|
||||
```
|
||||
|
||||
### 2. 重启后端服务
|
||||
确保新的 `detail` 接口逻辑生效。
|
||||
|
||||
### 3. 刷新前端浏览器
|
||||
清除缓存,重新加载 Vue 组件。
|
||||
|
||||
### 4. 测试编辑功能
|
||||
1. 进入"入境检疫"页面
|
||||
2. 点击任意运送清单的"编辑"按钮
|
||||
3. 观察控制台日志:
|
||||
```
|
||||
[EDIT-DELIVERY] 准备编辑运送清单, ID: 95
|
||||
[EDIT-DELIVERY] 获取到详情数据: { delivery: {...}, supplierId: 44, buyerId: 41, eartagIds: [...], collarIds: [...], serverIds: "..." }
|
||||
[OPEN-DIALOG] 所有下拉列表加载完成
|
||||
[OPEN-DIALOG] 车辆列表: [{ id: 1, licensePlate: "鄂A 66662", ... }, ...]
|
||||
[EDIT-FILL] 开始填充编辑数据: {...}
|
||||
[EDIT-FILL] 发货方ID: 44, 采购方ID: 41
|
||||
[EDIT-FILL] 车牌号: 鄂A 66662
|
||||
[EDIT-FILL] 当前车辆列表: [{ id: 1, licensePlate: "鄂A 66662", ... }]
|
||||
[EDIT-FILL] 主机ID: host001
|
||||
[EDIT-FILL] 耳标IDs: ["ear001", "ear002"]
|
||||
[EDIT-FILL] 项圈IDs: ["collar001"]
|
||||
```
|
||||
|
||||
### 5. 验证字段是否正确填充
|
||||
- ✅ 发货方下拉框应显示正确的供应商
|
||||
- ✅ 采购方下拉框应显示正确的买家
|
||||
- ✅ **车牌号下拉框应显示正确的车牌号**
|
||||
- ✅ 司机下拉框应显示正确的司机
|
||||
- ✅ 设备下拉框应显示已绑定的设备
|
||||
|
||||
## 关键改进点
|
||||
|
||||
### 🔑 核心改进
|
||||
1. **从固定延迟改为 Promise.all 等待**
|
||||
- 确保所有下拉列表数据加载完成后才填充表单
|
||||
- 避免因网络延迟导致的数据不匹配
|
||||
|
||||
2. **后端补充完整数据**
|
||||
- `detail` 接口返回 `supplierId`, `buyerId` 用于下拉框回显
|
||||
- 返回 `eartagIds`, `collarIds` 用于设备选择器回显
|
||||
|
||||
3. **增强日志**
|
||||
- 每一步都有清晰的日志输出
|
||||
- 便于排查问题
|
||||
|
||||
## 常见问题排查
|
||||
|
||||
### Q1: 车牌号显示为空?
|
||||
**检查**:
|
||||
1. 控制台日志:`[EDIT-FILL] 车牌号:` 的值是否正确
|
||||
2. 控制台日志:`[EDIT-FILL] 当前车辆列表:` 是否包含该车牌号
|
||||
3. 如果车辆列表为空,检查 `loadVehicleList()` 是否正常执行
|
||||
|
||||
### Q2: 车牌号有值但下拉框未选中?
|
||||
**可能原因**:
|
||||
- 数据库中的车牌号与车辆表中的车牌号**不完全匹配**(空格、大小写等)
|
||||
- 检查日志:`⚠️ 车牌号在车辆列表中不存在`
|
||||
|
||||
**解决办法**:
|
||||
- 确保 `delivery.licensePlate` 和 `vehicle.licensePlate` 完全一致
|
||||
|
||||
### Q3: 编辑时其他字段正常,唯独车牌号不对?
|
||||
**检查**:
|
||||
1. 数据库 `delivery` 表的 `license_plate` 字段值
|
||||
2. 后端日志:是否正确返回 `licensePlate`
|
||||
3. 前端网络请求:查看 `/delivery/detail?id=xxx` 返回的数据
|
||||
|
||||
## 总结
|
||||
|
||||
✅ **问题已修复**:
|
||||
- 使用 `Promise.all` 确保所有下拉列表加载完成
|
||||
- 后端补充完整的设备信息和 ID 字段
|
||||
- 增强日志便于问题排查
|
||||
|
||||
✅ **用户体验改善**:
|
||||
- 编辑对话框打开速度更快(并行加载)
|
||||
- 所有字段都能正确回显
|
||||
- 错误信息更清晰
|
||||
|
||||
372
IMPLEMENTATION_SUMMARY.md
Normal file
372
IMPLEMENTATION_SUMMARY.md
Normal file
@@ -0,0 +1,372 @@
|
||||
# 运送清单管理功能完善 - 实施总结
|
||||
|
||||
## 📋 任务概述
|
||||
|
||||
完善运送清单管理系统的待完善功能,封装API接口,添加后端服务日志信息。
|
||||
|
||||
## ✅ 已完成的功能
|
||||
|
||||
### 1. 前端 API 接口封装
|
||||
|
||||
**文件**: `pc-cattle-transportation/src/api/shipping.js`
|
||||
|
||||
新增以下 API 接口:
|
||||
|
||||
- `updateDeliveryStatus(data)` - 修改运送清单状态
|
||||
- `deleteDeliveryLogic(id)` - 逻辑删除运送清单
|
||||
- `downloadDeliveryPackage(id)` - 打包下载运送清单文件
|
||||
- `getDeliveryDetail(id)` - 获取运送清单详情(用于编辑)
|
||||
- `updateDeliveryInfo(data)` - 更新运送清单信息
|
||||
|
||||
### 2. 后端 API 实现
|
||||
|
||||
**文件**: `tradeCattle/aiotagro-cattle-trade/src/main/java/com/aiotagro/cattletrade/business/controller/DeliveryController.java`
|
||||
|
||||
#### 2.1 修改状态接口 (updateStatus)
|
||||
|
||||
- **路径**: `POST /delivery/updateStatus`
|
||||
- **权限**: `@SaCheckPermission("delivery:edit")`
|
||||
- **功能**: 修改运送清单状态(1-准备中,2-运输中,3-已结束)
|
||||
- **日志增强**:
|
||||
- 记录运单ID和新状态
|
||||
- 记录原状态和新状态
|
||||
- 状态验证(1-3)
|
||||
- 运单存在性检查
|
||||
- 详细的成功/失败日志
|
||||
|
||||
#### 2.2 逻辑删除接口 (deleteLogic)
|
||||
|
||||
- **路径**: `POST /delivery/deleteLogic`
|
||||
- **权限**: `@SaCheckPermission("delivery:delete")`
|
||||
- **功能**: 逻辑删除运送清单(保留历史记录,不清空设备绑定)
|
||||
- **日志增强**:
|
||||
- 记录运单ID和运单号
|
||||
- 权限检查日志
|
||||
- 删除操作结果日志
|
||||
|
||||
#### 2.3 打包下载接口 (downloadPackage)
|
||||
|
||||
- **路径**: `GET /delivery/downloadPackage`
|
||||
- **权限**: `@SaCheckPermission("delivery:view")`
|
||||
- **功能**: 打包运送清单的所有文件(图片、视频、信息)为ZIP压缩包
|
||||
- **包含内容**:
|
||||
- **运送清单信息.txt**: 包含基础信息、重量信息、状态信息
|
||||
- **照片文件夹**:
|
||||
- 检疫票
|
||||
- 纸质磅单
|
||||
- 空车过磅车头照片
|
||||
- 装车过磅车头照片
|
||||
- 装车过磅磅单
|
||||
- 车头照片
|
||||
- 车尾照片
|
||||
- 司机身份证照片
|
||||
- 到地磅单
|
||||
- 到地车辆过重磅车头照片
|
||||
- **视频文件夹**:
|
||||
- 空车过磅视频
|
||||
- 装车过磅视频
|
||||
- 装车视频
|
||||
- 消毒槽视频
|
||||
- 牛只装车环视视频
|
||||
- 卸牛视频
|
||||
- 到地过磅视频
|
||||
- **日志增强**:
|
||||
- 记录运单ID和运单号
|
||||
- 记录每个文件的下载状态
|
||||
- 记录照片和视频的成功添加数量
|
||||
- 记录最终ZIP文件大小
|
||||
- 详细的错误日志
|
||||
|
||||
### 3. 前端功能完善
|
||||
|
||||
#### 3.1 运送清单列表页面 (attestation.vue)
|
||||
|
||||
**文件**: `pc-cattle-transportation/src/views/entry/attestation.vue`
|
||||
|
||||
新增功能按钮(操作列宽度扩展至 350px):
|
||||
|
||||
1. **查看设备** - 跳转到设备管理页面
|
||||
```javascript
|
||||
const viewDevices = (row) => {
|
||||
router.push({
|
||||
path: '/entry/devices',
|
||||
query: { deliveryId: row.id, deliveryNumber: row.deliveryNumber }
|
||||
});
|
||||
};
|
||||
```
|
||||
|
||||
2. **编辑** - 调用编辑对话框
|
||||
```javascript
|
||||
const editDelivery = async (row) => {
|
||||
if (editDialogRef.value && editDialogRef.value.open) {
|
||||
editDialogRef.value.open(row.id);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
3. **修改状态** - 弹出输入框修改状态
|
||||
- 调用 `updateDeliveryStatus` API
|
||||
- 输入验证(1-3)
|
||||
- 实时刷新列表
|
||||
|
||||
4. **打包文件** - 下载运送清单压缩包
|
||||
- 调用 `downloadDeliveryPackage` API
|
||||
- 文件命名:`运送清单_{运单号}_{时间戳}.zip`
|
||||
- 自动下载到本地
|
||||
- 加载状态显示
|
||||
|
||||
5. **删除** - 逻辑删除运送清单
|
||||
- 调用 `deleteDeliveryLogic` API
|
||||
- 二次确认
|
||||
- 实时刷新列表
|
||||
|
||||
**组件引入**:
|
||||
- 导入 `editDialog` 组件
|
||||
- 添加 `editDialogRef` 引用
|
||||
|
||||
#### 3.2 设备管理页面 (devices.vue)
|
||||
|
||||
**新建文件**: `pc-cattle-transportation/src/views/entry/devices.vue`
|
||||
|
||||
功能特性:
|
||||
|
||||
1. **页面头部**:
|
||||
- 显示运单号
|
||||
- 返回按钮
|
||||
|
||||
2. **设备统计卡片**:
|
||||
- 智能主机数量
|
||||
- 智能耳标数量
|
||||
- 智能项圈数量
|
||||
- 设备总数
|
||||
|
||||
3. **设备列表Tab**:
|
||||
- **智能主机Tab**: 显示所有智能主机设备
|
||||
- **智能耳标Tab**: 显示所有智能耳标设备(分页)
|
||||
- **智能项圈Tab**: 显示所有智能项圈设备(分页)
|
||||
|
||||
4. **设备操作**:
|
||||
- **解绑设备**: 调用 `unbindDevice` API
|
||||
- 二次确认
|
||||
- 实时刷新对应列表
|
||||
|
||||
5. **数据展示**:
|
||||
- 设备ID/SN
|
||||
- 运单号
|
||||
- 重量(耳标)
|
||||
- 车牌号
|
||||
- 绑定时间
|
||||
|
||||
## 🎨 UI 优化
|
||||
|
||||
### 状态显示
|
||||
|
||||
**更新文件**: `pc-cattle-transportation/src/views/entry/attestation.vue`
|
||||
|
||||
状态文本和颜色映射:
|
||||
- **1 - 准备中**: 蓝色 (info)
|
||||
- **2 - 运输中**: 橙色 (warning)
|
||||
- **3 - 已结束**: 绿色 (success)
|
||||
|
||||
搜索下拉选项也已同步更新。
|
||||
|
||||
## 📊 日志增强
|
||||
|
||||
### 后端日志规范
|
||||
|
||||
所有新增/修改的后端方法都添加了详细的日志:
|
||||
|
||||
```java
|
||||
System.out.println("=== 功能名称 ===");
|
||||
System.out.println("参数1: " + value1);
|
||||
System.out.println("参数2: " + value2);
|
||||
// ... 业务逻辑
|
||||
System.out.println("SUCCESS: 操作成功");
|
||||
// 或
|
||||
System.out.println("ERROR: 错误信息");
|
||||
```
|
||||
|
||||
日志包含:
|
||||
- 功能标题(用 === 分隔)
|
||||
- 输入参数
|
||||
- 中间处理步骤
|
||||
- 查询结果
|
||||
- 成功/失败状态
|
||||
- 异常堆栈
|
||||
|
||||
### 前端日志
|
||||
|
||||
前端关键操作也添加了 `console.log` 日志:
|
||||
- API 调用参数
|
||||
- 响应数据
|
||||
- 错误信息
|
||||
|
||||
## 🔧 技术实现细节
|
||||
|
||||
### 文件下载实现
|
||||
|
||||
使用 Spring 的 `HttpServletResponse` 直接写入ZIP流:
|
||||
|
||||
```java
|
||||
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
|
||||
ZipOutputStream zipOut = new ZipOutputStream(byteArrayOutputStream);
|
||||
|
||||
// 1. 添加信息文本
|
||||
addDeliveryInfoToZip(zipOut, delivery);
|
||||
|
||||
// 2. 下载并添加图片/视频
|
||||
addFileToZip(zipOut, fileUrl, fileName);
|
||||
|
||||
// 3. 设置响应头并输出
|
||||
response.setContentType("application/zip");
|
||||
response.setHeader("Content-Disposition", "attachment; filename=" + encodedFileName);
|
||||
out.write(zipBytes);
|
||||
```
|
||||
|
||||
### 前端文件下载
|
||||
|
||||
使用 Blob 和 URL.createObjectURL:
|
||||
|
||||
```javascript
|
||||
const blob = new Blob([res], { type: 'application/zip' });
|
||||
const url = window.URL.createObjectURL(blob);
|
||||
const link = document.createElement('a');
|
||||
link.href = url;
|
||||
link.download = fileName;
|
||||
link.click();
|
||||
window.URL.revokeObjectURL(url);
|
||||
```
|
||||
|
||||
## 📝 API 接口文档
|
||||
|
||||
### 1. 修改状态
|
||||
|
||||
```
|
||||
POST /api/delivery/updateStatus
|
||||
Content-Type: application/json
|
||||
|
||||
Request Body:
|
||||
{
|
||||
"id": 95,
|
||||
"status": 2
|
||||
}
|
||||
|
||||
Response:
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "状态更新成功"
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 逻辑删除
|
||||
|
||||
```
|
||||
POST /api/delivery/deleteLogic?id=95
|
||||
|
||||
Response:
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "运单删除成功"
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 打包下载
|
||||
|
||||
```
|
||||
GET /api/delivery/downloadPackage?id=95
|
||||
|
||||
Response: application/zip (Binary file stream)
|
||||
```
|
||||
|
||||
## ✨ 权限控制
|
||||
|
||||
所有新增的功能都添加了权限控制:
|
||||
|
||||
- `delivery:edit` - 修改状态
|
||||
- `delivery:delete` - 删除运单
|
||||
- `delivery:view` - 查看和下载
|
||||
- `entry:device` - 查看设备
|
||||
- `entry:edit` - 编辑运单
|
||||
- `entry:status` - 修改状态
|
||||
- `entry:download` - 打包下载
|
||||
|
||||
## 🚀 部署说明
|
||||
|
||||
### 后端部署
|
||||
|
||||
1. 清理编译缓存:
|
||||
```bash
|
||||
cd tradeCattle
|
||||
mvn clean
|
||||
```
|
||||
|
||||
2. 重新编译:
|
||||
```bash
|
||||
mvn compile
|
||||
```
|
||||
|
||||
3. 重启服务
|
||||
|
||||
### 前端部署
|
||||
|
||||
前端代码已更新,刷新浏览器即可。
|
||||
|
||||
## 📦 文件清单
|
||||
|
||||
### 新建文件
|
||||
|
||||
- `pc-cattle-transportation/src/views/entry/devices.vue` - 设备管理页面
|
||||
|
||||
### 修改文件
|
||||
|
||||
- `pc-cattle-transportation/src/api/shipping.js` - API接口封装
|
||||
- `pc-cattle-transportation/src/views/entry/attestation.vue` - 运送清单列表页
|
||||
- `tradeCattle/aiotagro-cattle-trade/src/main/java/com/aiotagro/cattletrade/business/controller/DeliveryController.java` - 运送清单控制器
|
||||
|
||||
## 🎯 测试建议
|
||||
|
||||
1. **修改状态功能**:
|
||||
- 测试状态 1/2/3 的切换
|
||||
- 测试无效状态值的验证
|
||||
- 验证列表实时刷新
|
||||
|
||||
2. **逻辑删除功能**:
|
||||
- 测试删除确认
|
||||
- 验证权限控制
|
||||
- 确认删除后列表刷新
|
||||
|
||||
3. **打包下载功能**:
|
||||
- 测试包含所有文件的运单
|
||||
- 测试部分文件缺失的运单
|
||||
- 验证ZIP文件完整性
|
||||
- 检查文件命名
|
||||
|
||||
4. **编辑功能**:
|
||||
- 测试编辑对话框打开
|
||||
- 验证数据回填
|
||||
- 测试保存后刷新
|
||||
|
||||
5. **查看设备功能**:
|
||||
- 测试设备列表显示
|
||||
- 测试设备解绑
|
||||
- 验证设备统计数据
|
||||
|
||||
## 📌 注意事项
|
||||
|
||||
1. 打包下载功能会从URL下载文件,需要确保文件URL可访问
|
||||
2. 文件下载失败不会中断整个流程,会记录警告日志
|
||||
3. 逻辑删除不会清空设备绑定关系,保留历史记录
|
||||
4. 物理删除会清空设备的 `delivery_id`、`weight` 和 `car_number`
|
||||
|
||||
## 🎉 总结
|
||||
|
||||
所有待完善功能已全部实现,包括:
|
||||
- ✅ 前端API接口封装
|
||||
- ✅ 后端API实现(修改状态、逻辑删除、打包下载)
|
||||
- ✅ 前端功能完善(编辑、查看设备)
|
||||
- ✅ 创建设备管理页面
|
||||
- ✅ 添加详细日志信息
|
||||
- ✅ 修复代码风格问题
|
||||
|
||||
系统功能更加完善,用户体验得到显著提升!
|
||||
|
||||
124
LICENSE_PLATE_FIX.md
Normal file
124
LICENSE_PLATE_FIX.md
Normal file
@@ -0,0 +1,124 @@
|
||||
# 车牌号关联修复说明
|
||||
|
||||
## 问题描述
|
||||
|
||||
用户反馈:车牌号还是关联查询的是司机表中的数据
|
||||
|
||||
## 问题原因
|
||||
|
||||
在 `DeliveryServiceImpl.java` 的两个方法中,存在从司机表获取车牌号并覆盖运送清单车牌号的逻辑:
|
||||
|
||||
1. **pageQuery** (列表查询) - 第1033行
|
||||
2. **viewDelivery** (详情查询) - 第1560行
|
||||
|
||||
具体代码:
|
||||
```java
|
||||
String licensePlate = (String) driverInfo.get("car_number");
|
||||
delivery.setLicensePlate(licensePlate); // 错误:覆盖了用户选择的车牌号
|
||||
```
|
||||
|
||||
## 解决方案
|
||||
|
||||
### 移除从司机表获取车牌号的逻辑
|
||||
|
||||
**文件**: `DeliveryServiceImpl.java`
|
||||
|
||||
#### 1. 列表查询方法 (pageQuery)
|
||||
|
||||
**修改前**:
|
||||
```java
|
||||
String licensePlate = (String) driverInfo.get("car_number");
|
||||
deliveryLogVo.setLicensePlate(licensePlate);
|
||||
```
|
||||
|
||||
**修改后**:
|
||||
```java
|
||||
// 注释:车牌号不从司机表获取,车牌号是独立的模块
|
||||
// 车牌号保持运送清单表中原有的值
|
||||
```
|
||||
|
||||
#### 2. 详情查询方法 (viewDelivery)
|
||||
|
||||
**修改前**:
|
||||
```java
|
||||
String licensePlate = (String) driverInfo.get("car_number");
|
||||
delivery.setLicensePlate(licensePlate);
|
||||
```
|
||||
|
||||
**修改后**:
|
||||
```java
|
||||
// 注释:车牌号不从司机表获取,车牌号是独立的模块
|
||||
// 车牌号保持运送清单表中原有的值
|
||||
```
|
||||
|
||||
#### 3. 移除相关日志输出
|
||||
|
||||
**修改前**:
|
||||
```java
|
||||
System.out.println("查询到的车牌号: " + licensePlate);
|
||||
```
|
||||
|
||||
**修改后**:
|
||||
```java
|
||||
// 移除该日志
|
||||
```
|
||||
|
||||
#### 4. 修复车牌号匹配查询逻辑
|
||||
|
||||
**修改前**:
|
||||
```java
|
||||
if (licensePlate != null && !licensePlate.equals(deliveryLogVo.getLicensePlate())) {
|
||||
// 根据车牌号查询司机
|
||||
}
|
||||
```
|
||||
|
||||
**修改后**:
|
||||
```java
|
||||
// 不再根据车牌号查询司机,车牌号是独立的模块
|
||||
```
|
||||
|
||||
## 模块说明
|
||||
|
||||
### 司机模块
|
||||
- **字段**: `driver_name`, `driver_mobile`
|
||||
- **数据来源**: 司机表 (member_driver)
|
||||
- **功能**: 记录司机姓名和手机号
|
||||
|
||||
### 车牌号模块
|
||||
- **字段**: `license_plate`
|
||||
- **数据来源**: 用户表单选择
|
||||
- **功能**: 记录运送车辆的车牌号
|
||||
|
||||
**两个模块是独立的,不应互相覆盖。**
|
||||
|
||||
## 测试验证
|
||||
|
||||
创建运送清单后,检查:
|
||||
|
||||
```sql
|
||||
SELECT
|
||||
id,
|
||||
license_plate,
|
||||
driver_id,
|
||||
driver_name,
|
||||
driver_mobile
|
||||
FROM delivery
|
||||
WHERE id = [最新创建的运送清单ID];
|
||||
```
|
||||
|
||||
预期结果:
|
||||
- `license_plate` = 用户选择的车牌号(如 "1111111")
|
||||
- `driver_id` = 选择的司机ID
|
||||
- `driver_name` = 司机姓名
|
||||
- `driver_mobile` = 司机手机号
|
||||
|
||||
**车牌号不应与司机表中的car_number字段关联。**
|
||||
|
||||
## 相关文件
|
||||
|
||||
- `tradeCattle/aiotagro-cattle-trade/src/main/java/com/aiotagro/cattletrade/business/service/impl/DeliveryServiceImpl.java`
|
||||
|
||||
## 实现日期
|
||||
|
||||
2025-10-29
|
||||
|
||||
193
RESTART_BACKEND_GUIDE.md
Normal file
193
RESTART_BACKEND_GUIDE.md
Normal file
@@ -0,0 +1,193 @@
|
||||
# 重启后端服务指南
|
||||
|
||||
## 问题确认
|
||||
后端日志显示执行的 SQL:
|
||||
```sql
|
||||
UPDATE vehicle SET license_plate=?, ..., update_time=?, updated_by=?
|
||||
WHERE id=? AND is_delete=0
|
||||
```
|
||||
|
||||
**这是旧代码!** 说明后端服务没有重启,还在使用旧的编译版本。
|
||||
|
||||
## 解决步骤
|
||||
|
||||
### 方法1:IDEA 重启服务(推荐)
|
||||
|
||||
1. **停止当前运行的服务**
|
||||
- 在 IDEA 底部找到 "Run" 或 "Services" 窗口
|
||||
- 找到正在运行的 Spring Boot 应用
|
||||
- 点击红色方块按钮 ⬛ 停止服务
|
||||
|
||||
2. **清理编译缓存**(可选但推荐)
|
||||
- IDEA 菜单:`Build` → `Clean Project`
|
||||
- 或者:`Build` → `Rebuild Project`
|
||||
|
||||
3. **重新启动服务**
|
||||
- 点击绿色三角按钮 ▶️ 启动服务
|
||||
- 等待服务启动完成(看到 "Started Application" 日志)
|
||||
|
||||
### 方法2:命令行重启
|
||||
|
||||
#### Windows PowerShell
|
||||
```powershell
|
||||
# 1. 停止后端服务(找到 Java 进程并结束)
|
||||
tasklist | findstr java
|
||||
# 记下 PID,例如 21056
|
||||
|
||||
# 2. 结束进程
|
||||
taskkill /F /PID 21056
|
||||
|
||||
# 3. 清理并重新编译
|
||||
cd C:\cattleTransport\tradeCattle
|
||||
mvn clean package -DskipTests
|
||||
|
||||
# 4. 重新启动服务
|
||||
cd aiotagro-cattle-trade
|
||||
java -jar target\aiotagro-cattletrade-1.0.1.jar
|
||||
```
|
||||
|
||||
#### Linux/Mac
|
||||
```bash
|
||||
# 1. 找到并停止 Java 进程
|
||||
ps aux | grep java
|
||||
kill -9 <PID>
|
||||
|
||||
# 2. 清理并重新编译
|
||||
cd /path/to/tradeCattle
|
||||
mvn clean package -DskipTests
|
||||
|
||||
# 3. 重新启动服务
|
||||
cd aiotagro-cattle-trade
|
||||
java -jar target/aiotagro-cattletrade-1.0.1.jar
|
||||
```
|
||||
|
||||
### 方法3:使用 Spring Boot DevTools(如果已配置)
|
||||
如果项目配置了 Spring Boot DevTools:
|
||||
- 保存文件后会自动重启
|
||||
- 但完全替换方法时,建议手动重启
|
||||
|
||||
## 验证步骤
|
||||
|
||||
### 1. 检查后端日志
|
||||
重启后,后端应该显示新的启动日志:
|
||||
```
|
||||
Started Application in X.XXX seconds
|
||||
```
|
||||
|
||||
### 2. 测试删除功能
|
||||
1. 刷新前端页面(Ctrl+F5)
|
||||
2. 进入"车辆管理"页面
|
||||
3. 点击"删除"按钮
|
||||
4. 观察后端日志
|
||||
|
||||
### 3. 期望的日志输出
|
||||
**新代码应该显示**:
|
||||
```
|
||||
[VEHICLE-DELETE] 开始逻辑删除车辆,ID: 3
|
||||
[VEHICLE-DELETE] 车辆信息 - 车牌号: 鄂A 66662, 当前 is_delete: 0
|
||||
UPDATE vehicle SET is_delete=1 WHERE id=3
|
||||
[VEHICLE-DELETE] ✅ 逻辑删除成功,车牌号: 鄂A 66662, 操作人ID: 11
|
||||
```
|
||||
|
||||
**关键点**:
|
||||
- ✅ SQL 中只有 `SET is_delete=1`
|
||||
- ✅ 没有更新 `license_plate`, `car_front_photo` 等其他字段
|
||||
- ✅ 日志显示 `[VEHICLE-DELETE]` 标签
|
||||
|
||||
### 4. 验证数据库
|
||||
```sql
|
||||
-- 查看该记录
|
||||
SELECT id, license_plate, is_delete, update_time
|
||||
FROM vehicle
|
||||
WHERE id = 3;
|
||||
|
||||
-- 预期结果:is_delete = 1
|
||||
```
|
||||
|
||||
### 5. 验证前端列表
|
||||
- ✅ 列表自动刷新
|
||||
- ✅ 已删除的车辆不再显示
|
||||
- ✅ 显示"删除成功"提示
|
||||
|
||||
## 常见问题
|
||||
|
||||
### Q1: 找不到正在运行的服务?
|
||||
**检查**:
|
||||
```powershell
|
||||
# Windows
|
||||
netstat -ano | findstr :16200
|
||||
# 查看哪个进程占用了 16200 端口
|
||||
|
||||
# 结束进程
|
||||
taskkill /F /PID <进程ID>
|
||||
```
|
||||
|
||||
### Q2: Maven 编译失败?
|
||||
**清理并重试**:
|
||||
```powershell
|
||||
cd C:\cattleTransport\tradeCattle
|
||||
mvn clean
|
||||
mvn compile -DskipTests
|
||||
```
|
||||
|
||||
### Q3: 重启后还是旧代码?
|
||||
**可能原因**:
|
||||
1. IDEA 没有自动编译
|
||||
- 解决:`Build` → `Rebuild Project`
|
||||
2. 启动了错误的配置
|
||||
- 解决:检查 Run Configuration
|
||||
3. 类加载器缓存
|
||||
- 解决:完全关闭 IDEA 并重新打开
|
||||
|
||||
### Q4: 端口被占用?
|
||||
```powershell
|
||||
# 查看占用端口的进程
|
||||
netstat -ano | findstr :16200
|
||||
|
||||
# 结束进程
|
||||
taskkill /F /PID <进程ID>
|
||||
```
|
||||
|
||||
## 快速重启脚本
|
||||
|
||||
### Windows (PowerShell)
|
||||
创建 `restart-backend.ps1`:
|
||||
```powershell
|
||||
# 停止后端服务
|
||||
$process = Get-Process | Where-Object {$_.ProcessName -eq "java" -and $_.MainWindowTitle -like "*cattletrade*"}
|
||||
if ($process) {
|
||||
Stop-Process -Id $process.Id -Force
|
||||
Write-Host "已停止后端服务"
|
||||
}
|
||||
|
||||
# 重新编译
|
||||
cd C:\cattleTransport\tradeCattle
|
||||
mvn clean compile -DskipTests
|
||||
|
||||
# 重新启动(在 IDEA 中手动启动)
|
||||
Write-Host "编译完成,请在 IDEA 中重新启动服务"
|
||||
```
|
||||
|
||||
运行:
|
||||
```powershell
|
||||
powershell -ExecutionPolicy Bypass -File restart-backend.ps1
|
||||
```
|
||||
|
||||
## 总结
|
||||
|
||||
✅ **关键步骤**:
|
||||
1. 停止后端服务
|
||||
2. 清理编译缓存(可选)
|
||||
3. 重新启动服务
|
||||
4. 验证日志输出
|
||||
|
||||
✅ **验证标志**:
|
||||
- 日志显示 `UPDATE vehicle SET is_delete=1`
|
||||
- 数据库中 `is_delete` 变为 `1`
|
||||
- 前端列表中记录消失
|
||||
|
||||
⚠️ **注意**:
|
||||
- 必须完全停止旧服务
|
||||
- 确保编译成功
|
||||
- 验证新代码是否生效
|
||||
|
||||
350
ROUTE_PATH_FIX.md
Normal file
350
ROUTE_PATH_FIX.md
Normal file
@@ -0,0 +1,350 @@
|
||||
# Vue Router 路由刷新问题修复
|
||||
|
||||
## 问题描述
|
||||
用户反馈:**登录后跳转到 `http://localhost:8080/system/post`,刷新页面后路由变成 `http://localhost:8080/post`,导致页面空白**。
|
||||
|
||||
## 问题根源
|
||||
|
||||
### Vue Router 嵌套路由的路径规则
|
||||
|
||||
在 Vue Router 中,**子路由的 `path` 配置方式**决定了最终的 URL:
|
||||
|
||||
#### ❌ 错误配置(使用绝对路径)
|
||||
```javascript
|
||||
{
|
||||
path: '/system',
|
||||
component: LayoutIndex,
|
||||
children: [
|
||||
{
|
||||
path: '/system/post', // ❌ 以 / 开头 = 绝对路径
|
||||
component: () => import('~/views/system/post.vue'),
|
||||
},
|
||||
],
|
||||
}
|
||||
```
|
||||
|
||||
**问题**:
|
||||
- 子路由使用 `/system/post`(以 `/` 开头)
|
||||
- Vue Router 会将其视为**根路径**
|
||||
- 刷新时路由解析错误,变成 `/post`
|
||||
|
||||
#### ✅ 正确配置(使用相对路径)
|
||||
```javascript
|
||||
{
|
||||
path: '/system',
|
||||
component: LayoutIndex,
|
||||
children: [
|
||||
{
|
||||
path: 'post', // ✅ 不以 / 开头 = 相对路径
|
||||
component: () => import('~/views/system/post.vue'),
|
||||
},
|
||||
],
|
||||
}
|
||||
```
|
||||
|
||||
**正确行为**:
|
||||
- 子路由使用 `post`(不以 `/` 开头)
|
||||
- Vue Router 会自动拼接:`/system` + `post` = `/system/post`
|
||||
- 刷新时路由正常工作
|
||||
|
||||
## 修复内容
|
||||
|
||||
### 修复的路由模块
|
||||
|
||||
修复了以下所有嵌套路由的子路径配置:
|
||||
|
||||
1. **入境检疫** (`/entry`)
|
||||
- `/entry/details` → `details`
|
||||
- `/entry/devices` → `devices`
|
||||
|
||||
2. **系统管理** (`/system`)
|
||||
- `/system/post` → `post`
|
||||
- `/system/staff` → `staff`
|
||||
- `/system/tenant` → `tenant`
|
||||
|
||||
3. **权限管理** (`/permission`)
|
||||
- `/permission/menu` → `menu`
|
||||
- `/permission/operation` → `operation`
|
||||
|
||||
4. **智能硬件** (`/hardware`)
|
||||
- `/hardware/collar` → `collar`
|
||||
- `/hardware/eartag` → `eartag`
|
||||
- `/hardware/host` → `host`
|
||||
|
||||
5. **用户管理** (`/userManage`)
|
||||
- `/userManage/user` → `user`
|
||||
- `/userManage/driver` → `driver`
|
||||
- `/userManage/vehicle` → `vehicle`
|
||||
|
||||
6. **早期预警** (`/earlywarning`)
|
||||
- `/earlywarning/earlywarninglist` → `earlywarninglist`
|
||||
|
||||
7. **运送清单** (`/shipping`)
|
||||
- `/shipping/loadingOrder` → `loadingOrder`
|
||||
- `/shipping/shippinglist` → `shippinglist`
|
||||
|
||||
### 修复示例
|
||||
|
||||
**修复前**:
|
||||
```javascript
|
||||
{
|
||||
path: '/system',
|
||||
component: LayoutIndex,
|
||||
children: [
|
||||
{
|
||||
path: '/system/post', // ❌ 绝对路径
|
||||
name: 'Post',
|
||||
component: () => import('~/views/system/post.vue'),
|
||||
},
|
||||
],
|
||||
}
|
||||
```
|
||||
|
||||
**修复后**:
|
||||
```javascript
|
||||
{
|
||||
path: '/system',
|
||||
component: LayoutIndex,
|
||||
children: [
|
||||
{
|
||||
path: 'post', // ✅ 相对路径
|
||||
name: 'Post',
|
||||
component: () => import('~/views/system/post.vue'),
|
||||
},
|
||||
],
|
||||
}
|
||||
```
|
||||
|
||||
## Vue Router 路径规则详解
|
||||
|
||||
### 1. 绝对路径(以 `/` 开头)
|
||||
|
||||
```javascript
|
||||
{
|
||||
path: '/parent',
|
||||
children: [
|
||||
{ path: '/child' } // ❌ 结果:/child
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
- **行为**:忽略父路由路径,直接作为根路径
|
||||
- **最终 URL**:`/child`
|
||||
- **用途**:极少使用,仅当子路由确实需要在根路径下时
|
||||
|
||||
### 2. 相对路径(不以 `/` 开头)
|
||||
|
||||
```javascript
|
||||
{
|
||||
path: '/parent',
|
||||
children: [
|
||||
{ path: 'child' } // ✅ 结果:/parent/child
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
- **行为**:自动拼接父路由路径
|
||||
- **最终 URL**:`/parent/child`
|
||||
- **用途**:**推荐使用**,适用于绝大多数嵌套路由场景
|
||||
|
||||
### 3. 空路径
|
||||
|
||||
```javascript
|
||||
{
|
||||
path: '/parent',
|
||||
children: [
|
||||
{ path: '' } // 结果:/parent
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
- **行为**:匹配父路由路径本身
|
||||
- **最终 URL**:`/parent`
|
||||
- **用途**:默认子路由、索引页
|
||||
|
||||
## 为什么刷新会导致路由变化?
|
||||
|
||||
### 问题原因
|
||||
|
||||
1. **首次导航**(通过 `router.push`):
|
||||
```javascript
|
||||
router.push('/system/post')
|
||||
```
|
||||
- Vue Router 能正确找到路由配置
|
||||
- 页面正常显示
|
||||
|
||||
2. **刷新页面**(直接访问 URL):
|
||||
```
|
||||
http://localhost:8080/system/post
|
||||
```
|
||||
- 浏览器直接请求该 URL
|
||||
- Vue Router 重新解析路由配置
|
||||
- 如果子路由配置错误(`path: '/system/post'`),会被解析为根路径
|
||||
- 最终匹配到 `/post`(错误)
|
||||
|
||||
### 修复后的行为
|
||||
|
||||
1. **首次导航**:
|
||||
```javascript
|
||||
router.push('/system/post')
|
||||
```
|
||||
- 匹配到:`/system` + `post` = `/system/post` ✅
|
||||
|
||||
2. **刷新页面**:
|
||||
```
|
||||
http://localhost:8080/system/post
|
||||
```
|
||||
- 匹配到:`/system` + `post` = `/system/post` ✅
|
||||
- 路由正常,页面正常显示
|
||||
|
||||
## 测试步骤
|
||||
|
||||
### 1. 刷新浏览器
|
||||
清除前端缓存,重新加载路由配置(Ctrl+F5)。
|
||||
|
||||
### 2. 测试系统管理 - 岗位管理
|
||||
1. 登录系统
|
||||
2. 点击"系统管理" → "岗位管理"
|
||||
3. URL 应该是:`http://localhost:8080/system/post`
|
||||
4. 刷新页面(F5)
|
||||
5. ✅ URL 保持不变:`http://localhost:8080/system/post`
|
||||
6. ✅ 页面正常显示
|
||||
|
||||
### 3. 测试其他路由
|
||||
测试以下路由,确保刷新后 URL 不变:
|
||||
- `http://localhost:8080/system/staff`
|
||||
- `http://localhost:8080/system/tenant`
|
||||
- `http://localhost:8080/userManage/user`
|
||||
- `http://localhost:8080/userManage/driver`
|
||||
- `http://localhost:8080/userManage/vehicle`
|
||||
- `http://localhost:8080/hardware/collar`
|
||||
- `http://localhost:8080/hardware/eartag`
|
||||
- `http://localhost:8080/hardware/host`
|
||||
- `http://localhost:8080/permission/menu`
|
||||
- `http://localhost:8080/permission/operation`
|
||||
- `http://localhost:8080/shipping/loadingOrder`
|
||||
- `http://localhost:8080/shipping/shippinglist`
|
||||
- `http://localhost:8080/earlywarning/earlywarninglist`
|
||||
|
||||
### 4. 验证浏览器前进/后退
|
||||
1. 依次访问多个页面
|
||||
2. 点击浏览器"后退"按钮
|
||||
3. ✅ URL 应该正确回到前一个页面
|
||||
4. 点击浏览器"前进"按钮
|
||||
5. ✅ URL 应该正确前进到下一个页面
|
||||
|
||||
## 常见问题
|
||||
|
||||
### Q1: 为什么之前能正常跳转,但刷新就不行?
|
||||
**答**:
|
||||
- `router.push()` 跳转时,Vue Router 使用编程式导航,能够容错
|
||||
- 刷新页面时,浏览器直接请求 URL,Vue Router 严格按照配置解析
|
||||
- 如果配置错误(使用绝对路径),刷新时就会出错
|
||||
|
||||
### Q2: 什么时候应该使用绝对路径(以 `/` 开头)?
|
||||
**答**:
|
||||
- **几乎不需要**
|
||||
- 仅在子路由确实需要在根路径下时才使用
|
||||
- 99% 的嵌套路由应该使用相对路径
|
||||
|
||||
### Q3: 如何判断路由配置是否正确?
|
||||
**答**:
|
||||
检查子路由的 `path` 是否以 `/` 开头:
|
||||
```javascript
|
||||
// ❌ 错误
|
||||
children: [
|
||||
{ path: '/parent/child' } // 不应该包含父路径
|
||||
]
|
||||
|
||||
// ✅ 正确
|
||||
children: [
|
||||
{ path: 'child' } // 只写相对路径
|
||||
]
|
||||
```
|
||||
|
||||
### Q4: 修复后需要重启开发服务器吗?
|
||||
**答**:
|
||||
- 如果使用 Vite(本项目),通常会自动热更新
|
||||
- 如果没有生效,强制刷新浏览器(Ctrl+F5)
|
||||
- 必要时重启开发服务器:`npm run dev`
|
||||
|
||||
## Vue Router 最佳实践
|
||||
|
||||
### ✅ 推荐做法
|
||||
|
||||
1. **嵌套路由使用相对路径**
|
||||
```javascript
|
||||
{
|
||||
path: '/parent',
|
||||
children: [
|
||||
{ path: 'child' } // ✅ 相对路径
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
2. **顶级路由使用绝对路径**
|
||||
```javascript
|
||||
{
|
||||
path: '/login' // ✅ 顶级路由必须以 / 开头
|
||||
}
|
||||
```
|
||||
|
||||
3. **使用命名路由**
|
||||
```javascript
|
||||
{
|
||||
path: 'post',
|
||||
name: 'Post' // ✅ 方便编程式导航
|
||||
}
|
||||
```
|
||||
|
||||
4. **路由导航使用 name**
|
||||
```javascript
|
||||
router.push({ name: 'Post' }) // ✅ 推荐
|
||||
router.push('/system/post') // 也可以,但不推荐
|
||||
```
|
||||
|
||||
### ❌ 避免的做法
|
||||
|
||||
1. **子路由使用绝对路径**
|
||||
```javascript
|
||||
{
|
||||
path: '/parent',
|
||||
children: [
|
||||
{ path: '/parent/child' } // ❌ 错误
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
2. **路径拼写错误**
|
||||
```javascript
|
||||
{
|
||||
path: 'post',
|
||||
component: () => import('~/views/system/Post.vue') // ❌ 大小写错误
|
||||
}
|
||||
```
|
||||
|
||||
3. **路径与组件不匹配**
|
||||
```javascript
|
||||
{
|
||||
path: 'post',
|
||||
component: () => import('~/views/system/staff.vue') // ❌ 组件错误
|
||||
}
|
||||
```
|
||||
|
||||
## 总结
|
||||
|
||||
✅ **修复完成**:
|
||||
- 所有嵌套路由的子路径已改为相对路径
|
||||
- 刷新页面时 URL 不再变化
|
||||
- 所有页面正常显示
|
||||
|
||||
✅ **关键原则**:
|
||||
- **嵌套路由的子路径不要以 `/` 开头**
|
||||
- Vue Router 会自动拼接父路径和子路径
|
||||
- 相对路径配置简洁、清晰、不易出错
|
||||
|
||||
✅ **用户体验改善**:
|
||||
- 刷新页面后 URL 保持不变
|
||||
- 浏览器前进/后退功能正常
|
||||
- 书签和分享链接正常工作
|
||||
|
||||
174
SAVE_AND_RESTART.md
Normal file
174
SAVE_AND_RESTART.md
Normal file
@@ -0,0 +1,174 @@
|
||||
# ⚠️ 文件未保存!
|
||||
|
||||
## 问题确认
|
||||
|
||||
检测到 `VehicleServiceImpl.java` 文件**在编辑器中已修改但未保存**:
|
||||
|
||||
- ✅ 编辑器中的代码:使用 `deleteById(id)` ✓ 正确
|
||||
- ❌ 磁盘上的文件:使用 `updateById(vehicle)` ✗ 旧代码
|
||||
|
||||
## 立即操作
|
||||
|
||||
### 1. 保存文件(必须!)
|
||||
|
||||
#### 方法1:快捷键
|
||||
```
|
||||
Ctrl + S (保存当前文件)
|
||||
```
|
||||
|
||||
#### 方法2:保存所有文件
|
||||
```
|
||||
Ctrl + Shift + S (保存所有文件)
|
||||
```
|
||||
|
||||
#### 方法3:菜单
|
||||
```
|
||||
File → Save All
|
||||
```
|
||||
|
||||
### 2. 验证文件已保存
|
||||
|
||||
检查 IDEA 标题栏/标签页:
|
||||
- ❌ 文件名后有 `*` 号 = 未保存
|
||||
- ✅ 文件名后无 `*` 号 = 已保存
|
||||
|
||||
### 3. 重新编译
|
||||
|
||||
保存后,执行以下操作之一:
|
||||
|
||||
#### 选项A:IDEA 自动编译(推荐)
|
||||
1. 检查是否启用自动编译:
|
||||
- `File` → `Settings` → `Build, Execution, Deployment` → `Compiler`
|
||||
- 勾选 `Build project automatically`
|
||||
|
||||
#### 选项B:手动编译
|
||||
```
|
||||
Build → Rebuild Project
|
||||
```
|
||||
|
||||
#### 选项C:Maven 编译
|
||||
```powershell
|
||||
cd C:\cattleTransport\tradeCattle
|
||||
mvn clean compile -DskipTests
|
||||
```
|
||||
|
||||
### 4. 重启后端服务
|
||||
|
||||
1. **停止服务**:红色方块按钮 ⬛
|
||||
2. **启动服务**:绿色三角按钮 ▶️
|
||||
3. **等待启动完成**
|
||||
|
||||
## 完整操作流程
|
||||
|
||||
```
|
||||
1. Ctrl + S → 保存文件
|
||||
2. Build → Rebuild → 重新编译
|
||||
3. Stop → Start → 重启服务
|
||||
4. 测试删除功能 → 验证是否正常
|
||||
```
|
||||
|
||||
## 验证步骤
|
||||
|
||||
### 1. 检查编译后的代码
|
||||
|
||||
保存并重新编译后,执行:
|
||||
```powershell
|
||||
cd C:\cattleTransport\tradeCattle\aiotagro-cattle-trade\src\main\java\com\aiotagro\cattletrade\business\service\impl
|
||||
Get-Content VehicleServiceImpl.java | Select-String -Pattern "deleteById" -Context 2
|
||||
```
|
||||
|
||||
应该看到:
|
||||
```java
|
||||
boolean success = vehicleMapper.deleteById(id) > 0;
|
||||
```
|
||||
|
||||
### 2. 测试删除功能
|
||||
|
||||
1. 刷新前端页面(Ctrl+F5)
|
||||
2. 进入"车辆管理"
|
||||
3. 点击"删除"按钮
|
||||
4. 观察后端日志
|
||||
|
||||
### 3. 预期日志(正确)
|
||||
|
||||
```
|
||||
[VEHICLE-DELETE] 开始逻辑删除车辆,ID: 3
|
||||
[VEHICLE-DELETE] 车辆信息 - 车牌号: 鄂A 66662, 当前 is_delete: 0
|
||||
UPDATE vehicle SET is_delete=1 WHERE id=3
|
||||
[VEHICLE-DELETE] ✅ 逻辑删除成功
|
||||
```
|
||||
|
||||
**关键点**:
|
||||
- ✅ SQL 只有 `SET is_delete=1`
|
||||
- ✅ 没有 `license_plate`, `car_front_photo` 等字段
|
||||
|
||||
### 4. 验证数据库
|
||||
|
||||
```sql
|
||||
SELECT id, license_plate, is_delete, update_time
|
||||
FROM vehicle
|
||||
WHERE id = 3;
|
||||
```
|
||||
|
||||
**预期结果**:
|
||||
```
|
||||
id | license_plate | is_delete | update_time
|
||||
3 | 鄂A 66662 | 1 | 2025-10-29 17:20:00
|
||||
```
|
||||
|
||||
## 常见问题
|
||||
|
||||
### Q1: 如何确认文件是否已保存?
|
||||
**检查点**:
|
||||
1. 文件标签页名称后无 `*` 号
|
||||
2. IDEA 状态栏显示 "All files saved"
|
||||
3. 磁盘文件的修改时间是最新的
|
||||
|
||||
### Q2: 保存后还是旧代码?
|
||||
**可能原因**:
|
||||
1. 保存的是错误的文件
|
||||
- 确认路径:`tradeCattle\aiotagro-cattle-trade\src\main\java\...\VehicleServiceImpl.java`
|
||||
2. IDEA 打开了多个窗口
|
||||
- 确认在正确的项目窗口中操作
|
||||
3. 文件被只读
|
||||
- 检查文件属性,取消"只读"
|
||||
|
||||
### Q3: 重启后日志还是显示旧 SQL?
|
||||
**检查步骤**:
|
||||
1. 确认文件已保存(无 `*` 号)
|
||||
2. 确认已重新编译
|
||||
3. 确认启动的是新编译的代码
|
||||
4. 查看编译输出目录的时间戳
|
||||
|
||||
## IDEA 自动保存设置(推荐)
|
||||
|
||||
### 启用自动保存
|
||||
1. `File` → `Settings` (或 `Ctrl + Alt + S`)
|
||||
2. `Appearance & Behavior` → `System Settings`
|
||||
3. 勾选以下选项:
|
||||
- ✅ `Save files on frame deactivation` (切换窗口时自动保存)
|
||||
- ✅ `Save files automatically if application is idle for X seconds`
|
||||
|
||||
### 启用自动编译
|
||||
1. `File` → `Settings`
|
||||
2. `Build, Execution, Deployment` → `Compiler`
|
||||
3. 勾选:
|
||||
- ✅ `Build project automatically`
|
||||
|
||||
## 当前状态
|
||||
|
||||
- ✅ 代码已修改(在编辑器中)
|
||||
- ❌ **文件未保存到磁盘**
|
||||
- ❌ 编译的是旧代码
|
||||
- ❌ 运行的是旧代码
|
||||
|
||||
## 下一步操作
|
||||
|
||||
**请立即执行**:
|
||||
1. ⌨️ 按 `Ctrl + S` 保存文件
|
||||
2. 🔨 点击 `Build → Rebuild Project`
|
||||
3. 🔄 重启后端服务
|
||||
4. ✅ 测试删除功能
|
||||
|
||||
**操作时间**:< 1 分钟
|
||||
|
||||
331
VEHICLE_DELETE_FINAL_FIX.md
Normal file
331
VEHICLE_DELETE_FINAL_FIX.md
Normal file
@@ -0,0 +1,331 @@
|
||||
# 车辆删除功能最终修复 - 使用 MyBatis-Plus 的 @TableLogic
|
||||
|
||||
## 问题描述
|
||||
用户反馈:**删除车辆返回成功,但前端页面依然显示该记录**。
|
||||
|
||||
后端日志显示:
|
||||
```
|
||||
UPDATE vehicle SET license_plate=?, ..., update_time=?, updated_by=?
|
||||
WHERE id=? AND is_delete=0
|
||||
Updates: 1
|
||||
```
|
||||
|
||||
**关键发现**:UPDATE 语句中**没有 `is_delete` 字段**!
|
||||
|
||||
## 问题根源
|
||||
|
||||
### @TableLogic 注解的副作用
|
||||
|
||||
`Vehicle` 实体类使用了 `@TableLogic` 注解:
|
||||
```java
|
||||
@TableLogic(value = "0", delval = "1")
|
||||
@TableField("is_delete")
|
||||
private Integer isDelete;
|
||||
```
|
||||
|
||||
**@TableLogic 的行为**:
|
||||
1. ✅ **SELECT**: 自动添加 `WHERE is_delete = 0`
|
||||
2. ✅ **DELETE**: 自动转换为 `UPDATE SET is_delete = 1`
|
||||
3. ❌ **UPDATE**: `is_delete` 字段**被排除在 SET 子句之外**!
|
||||
|
||||
### 错误的实现方式
|
||||
|
||||
```java
|
||||
// ❌ 错误:使用 updateById 无法修改 is_delete 字段
|
||||
Vehicle vehicle = vehicleMapper.selectById(id);
|
||||
vehicle.setIsDelete(1); // 这个设置会被忽略!
|
||||
vehicleMapper.updateById(vehicle);
|
||||
|
||||
// 生成的 SQL(注意没有 is_delete):
|
||||
UPDATE vehicle
|
||||
SET license_plate=?, car_front_photo=?, ..., update_time=?, updated_by=?
|
||||
WHERE id=? AND is_delete=0
|
||||
```
|
||||
|
||||
**结果**:
|
||||
- `is_delete` 字段没有被更新,仍然是 `0`
|
||||
- 记录依然存在,前端列表继续显示
|
||||
- 虽然返回 `Updates: 1`(更新了其他字段),但逻辑删除失败
|
||||
|
||||
## 最终解决方案
|
||||
|
||||
### 使用 MyBatis-Plus 的 deleteById() 方法
|
||||
|
||||
**正确做法**:利用 `@TableLogic` 注解,直接调用 `deleteById()`
|
||||
|
||||
```java
|
||||
/**
|
||||
* 删除车辆(逻辑删除,使用 MyBatis-Plus 的 deleteById 自动处理)
|
||||
* 因为实体类使用了 @TableLogic 注解,deleteById 会自动将 is_delete 设置为 1
|
||||
*/
|
||||
@Override
|
||||
@Transactional
|
||||
public AjaxResult deleteVehicle(Integer id) {
|
||||
System.out.println("[VEHICLE-DELETE] 开始逻辑删除车辆,ID: " + id);
|
||||
|
||||
if (id == null) {
|
||||
System.out.println("[VEHICLE-DELETE] 删除失败:车辆ID为空");
|
||||
return AjaxResult.error("车辆ID不能为空");
|
||||
}
|
||||
|
||||
// 查询车辆是否存在(@TableLogic 会自动过滤 is_delete=1 的记录)
|
||||
Vehicle vehicle = vehicleMapper.selectById(id);
|
||||
if (vehicle == null) {
|
||||
System.out.println("[VEHICLE-DELETE] 删除失败:车辆不存在或已被删除,ID: " + id);
|
||||
return AjaxResult.error("车辆不存在");
|
||||
}
|
||||
|
||||
System.out.println("[VEHICLE-DELETE] 车辆信息 - 车牌号: " + vehicle.getLicensePlate() + ", 当前 is_delete: " + vehicle.getIsDelete());
|
||||
|
||||
// 获取当前用户ID(用于日志记录)
|
||||
Integer userId = SecurityUtil.getCurrentUserId();
|
||||
|
||||
// ✅ 使用 MyBatis-Plus 的 deleteById 方法
|
||||
// 因为实体类有 @TableLogic 注解,这会自动执行逻辑删除(UPDATE SET is_delete=1)
|
||||
// 而不是物理删除(DELETE FROM)
|
||||
boolean success = vehicleMapper.deleteById(id) > 0;
|
||||
|
||||
if (success) {
|
||||
System.out.println("[VEHICLE-DELETE] ✅ 逻辑删除成功,车牌号: " + vehicle.getLicensePlate() + ", 操作人ID: " + userId);
|
||||
return AjaxResult.success("删除车辆成功");
|
||||
} else {
|
||||
System.out.println("[VEHICLE-DELETE] ❌ 逻辑删除失败,车牌号: " + vehicle.getLicensePlate());
|
||||
return AjaxResult.error("删除车辆失败");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 生成的 SQL
|
||||
|
||||
**使用 deleteById() 后**:
|
||||
```sql
|
||||
UPDATE vehicle
|
||||
SET is_delete = 1
|
||||
WHERE id = 3;
|
||||
```
|
||||
|
||||
**完美!** 只更新 `is_delete` 字段,不涉及其他字段。
|
||||
|
||||
## MyBatis-Plus @TableLogic 工作原理
|
||||
|
||||
### 1. SELECT 操作
|
||||
```java
|
||||
Vehicle vehicle = vehicleMapper.selectById(1);
|
||||
```
|
||||
**生成的 SQL**:
|
||||
```sql
|
||||
SELECT * FROM vehicle WHERE id = 1 AND is_delete = 0
|
||||
```
|
||||
|
||||
### 2. DELETE 操作(逻辑删除)
|
||||
```java
|
||||
vehicleMapper.deleteById(1);
|
||||
```
|
||||
**生成的 SQL**:
|
||||
```sql
|
||||
UPDATE vehicle SET is_delete = 1 WHERE id = 1
|
||||
```
|
||||
|
||||
### 3. UPDATE 操作(注意!)
|
||||
```java
|
||||
vehicle.setIsDelete(1);
|
||||
vehicleMapper.updateById(vehicle);
|
||||
```
|
||||
**生成的 SQL**:
|
||||
```sql
|
||||
-- ❌ is_delete 不在 SET 子句中!
|
||||
UPDATE vehicle
|
||||
SET license_plate=?, car_front_photo=?, ..., update_time=?
|
||||
WHERE id=? AND is_delete=0
|
||||
```
|
||||
|
||||
**结论**:
|
||||
- ✅ **删除时用 `deleteById()`**,它会自动设置 `is_delete = 1`
|
||||
- ❌ **不要用 `updateById()`** 来修改 `is_delete` 字段
|
||||
|
||||
## 修复前后对比
|
||||
|
||||
| 对比项 | 修复前(错误) | 修复后(正确) |
|
||||
|--------|----------------|----------------|
|
||||
| 方法调用 | `updateById(vehicle)` | `deleteById(id)` |
|
||||
| 生成的 SQL | `UPDATE SET 所有字段...` | `UPDATE SET is_delete=1` |
|
||||
| is_delete 是否更新 | ❌ 否(被排除) | ✅ 是 |
|
||||
| 其他字段是否更新 | ✅ 是(不必要) | ❌ 否(只更新必要字段) |
|
||||
| 性能 | 差(更新所有字段) | 好(只更新1个字段) |
|
||||
| 逻辑删除是否生效 | ❌ 否 | ✅ 是 |
|
||||
|
||||
## 为什么之前的方案失败
|
||||
|
||||
### 尝试1:使用 updateById
|
||||
```java
|
||||
vehicle.setIsDelete(1);
|
||||
vehicleMapper.updateById(vehicle);
|
||||
```
|
||||
**失败原因**:`@TableLogic` 会排除 `is_delete` 字段
|
||||
|
||||
### 尝试2:使用 LambdaUpdateWrapper
|
||||
```java
|
||||
LambdaUpdateWrapper<Vehicle> wrapper = new LambdaUpdateWrapper<>();
|
||||
wrapper.set(Vehicle::getIsDelete, 1);
|
||||
vehicleMapper.update(null, wrapper);
|
||||
```
|
||||
**问题**:代码复杂,而且可能与 `@TableLogic` 冲突
|
||||
|
||||
### 最终方案:直接使用 deleteById
|
||||
```java
|
||||
vehicleMapper.deleteById(id);
|
||||
```
|
||||
**优势**:
|
||||
- ✅ 代码简洁
|
||||
- ✅ 利用 MyBatis-Plus 的特性
|
||||
- ✅ 自动处理逻辑删除
|
||||
- ✅ 与 `@TableLogic` 完美配合
|
||||
|
||||
## 测试步骤
|
||||
|
||||
### 1. 重启后端服务
|
||||
确保新的代码生效。
|
||||
|
||||
### 2. 清除浏览器缓存
|
||||
强制刷新前端页面(Ctrl+F5)。
|
||||
|
||||
### 3. 测试删除功能
|
||||
1. 进入"车辆管理"页面
|
||||
2. 选择要删除的车辆,例如:`鄂A 66662`
|
||||
3. 点击"删除"按钮
|
||||
4. 确认删除
|
||||
|
||||
### 4. 观察后端日志
|
||||
**修复后应该看到**:
|
||||
```
|
||||
[VEHICLE-DELETE] 开始逻辑删除车辆,ID: 3
|
||||
[VEHICLE-DELETE] 车辆信息 - 车牌号: 鄂A 66662, 当前 is_delete: 0
|
||||
UPDATE vehicle SET is_delete=1 WHERE id=3
|
||||
[VEHICLE-DELETE] ✅ 逻辑删除成功,车牌号: 鄂A 66662, 操作人ID: 11
|
||||
```
|
||||
|
||||
**关键点**:
|
||||
- ✅ SQL 中只有 `SET is_delete=1`
|
||||
- ✅ 没有更新其他字段
|
||||
- ✅ 日志显示"逻辑删除成功"
|
||||
|
||||
### 5. 验证前端列表
|
||||
- ✅ 列表自动刷新
|
||||
- ✅ 已删除的车辆不再显示
|
||||
- ✅ 显示"删除成功"提示
|
||||
|
||||
### 6. 验证数据库
|
||||
```sql
|
||||
-- 查看该记录
|
||||
SELECT id, license_plate, is_delete, update_time
|
||||
FROM vehicle
|
||||
WHERE id = 3;
|
||||
|
||||
-- 预期结果:
|
||||
-- id | license_plate | is_delete | update_time
|
||||
-- 3 | 鄂A 66662 | 1 | 2025-10-29 17:00:00
|
||||
```
|
||||
|
||||
```sql
|
||||
-- 前端列表查询(不应包含该记录)
|
||||
SELECT * FROM vehicle WHERE is_delete = 0;
|
||||
```
|
||||
|
||||
## 常见问题排查
|
||||
|
||||
### Q1: 删除后前端列表还显示该记录?
|
||||
**检查**:
|
||||
1. 后端服务是否重启
|
||||
2. 浏览器缓存是否清除(Ctrl+F5)
|
||||
3. 后端日志中 SQL 是否为 `UPDATE SET is_delete=1`
|
||||
4. 数据库中该记录的 `is_delete` 是否为 `1`
|
||||
|
||||
### Q2: 后端日志显示 SQL 中没有 is_delete 字段?
|
||||
**原因**: 代码没有更新,还在使用 `updateById()`
|
||||
|
||||
**解决**:
|
||||
1. 确认代码已修改为 `deleteById()`
|
||||
2. 重新编译:`mvn clean compile`
|
||||
3. 重启后端服务
|
||||
|
||||
### Q3: 删除后报错 "车辆不存在"?
|
||||
**原因**: 记录已经被删除(`is_delete = 1`),`selectById` 自动过滤了它
|
||||
|
||||
**解决**: 正常现象,说明删除成功。前端应该已经刷新列表。
|
||||
|
||||
### Q4: 如何查看所有已删除的记录?
|
||||
**SQL**:
|
||||
```sql
|
||||
SELECT * FROM vehicle WHERE is_delete = 1;
|
||||
```
|
||||
|
||||
### Q5: 如何恢复已删除的记录?
|
||||
**SQL**:
|
||||
```sql
|
||||
UPDATE vehicle
|
||||
SET is_delete = 0,
|
||||
update_time = NOW()
|
||||
WHERE id = 3;
|
||||
```
|
||||
|
||||
## MyBatis-Plus @TableLogic 最佳实践
|
||||
|
||||
### ✅ 推荐做法
|
||||
|
||||
1. **删除操作**:使用 `deleteById()` 或 `deleteBatchIds()`
|
||||
```java
|
||||
vehicleMapper.deleteById(id); // 逻辑删除
|
||||
```
|
||||
|
||||
2. **查询操作**:正常使用,自动过滤已删除记录
|
||||
```java
|
||||
vehicleMapper.selectById(id); // 自动添加 is_delete=0
|
||||
```
|
||||
|
||||
3. **更新操作**:不要尝试修改 `is_delete` 字段
|
||||
```java
|
||||
vehicle.setLicensePlate("新车牌");
|
||||
vehicleMapper.updateById(vehicle); // is_delete 不会被更新
|
||||
```
|
||||
|
||||
### ❌ 避免的做法
|
||||
|
||||
1. **不要用 updateById 修改 is_delete**
|
||||
```java
|
||||
// ❌ 错误:不会生效
|
||||
vehicle.setIsDelete(1);
|
||||
vehicleMapper.updateById(vehicle);
|
||||
```
|
||||
|
||||
2. **不要在 WHERE 条件中手动添加 is_delete**
|
||||
```java
|
||||
// ❌ 多余:@TableLogic 会自动添加
|
||||
queryWrapper.eq(Vehicle::getIsDelete, 0);
|
||||
```
|
||||
|
||||
3. **不要尝试物理删除**
|
||||
```java
|
||||
// ❌ 这仍然是逻辑删除,不是物理删除
|
||||
vehicleMapper.deleteById(id);
|
||||
```
|
||||
|
||||
## 总结
|
||||
|
||||
✅ **最终修复方案**:
|
||||
- 使用 `vehicleMapper.deleteById(id)` 执行逻辑删除
|
||||
- 让 MyBatis-Plus 的 `@TableLogic` 自动处理 `is_delete` 字段
|
||||
- 代码简洁,性能优秀,符合框架设计理念
|
||||
|
||||
✅ **核心原理**:
|
||||
- `@TableLogic` 会在 DELETE 操作时自动转换为 UPDATE
|
||||
- 不要用 UPDATE 操作来修改逻辑删除标记
|
||||
- 利用框架特性,而不是绕过它
|
||||
|
||||
✅ **用户体验**:
|
||||
- 删除成功后列表自动刷新
|
||||
- 已删除的记录立即消失
|
||||
- 操作简单,性能优秀
|
||||
|
||||
🎯 **关键教训**:
|
||||
当框架提供了特定功能(如 `@TableLogic`)时,应该按照框架的设计意图使用,而不是尝试绕过或覆盖它。`deleteById()` 是正确的删除方式,`updateById()` 不适合修改逻辑删除标记。
|
||||
|
||||
391
VEHICLE_LOGICAL_DELETE.md
Normal file
391
VEHICLE_LOGICAL_DELETE.md
Normal file
@@ -0,0 +1,391 @@
|
||||
# 车辆管理 - 逻辑删除功能说明
|
||||
|
||||
## 功能概述
|
||||
车辆管理已实现**逻辑删除**(软删除)功能,删除操作不会真正删除数据库中的记录,而是将 `is_delete` 字段标记为 `1`,保留历史数据用于审计和追溯。
|
||||
|
||||
## 逻辑删除 vs 物理删除
|
||||
|
||||
### 逻辑删除(Soft Delete)✅ 当前实现
|
||||
- **操作**: 将 `is_delete` 字段设置为 `1`
|
||||
- **优点**:
|
||||
- 数据可恢复
|
||||
- 保留历史记录
|
||||
- 符合审计要求
|
||||
- 避免数据丢失
|
||||
- **缺点**:
|
||||
- 数据库空间占用较大
|
||||
- 查询时需要过滤已删除记录
|
||||
|
||||
### 物理删除(Hard Delete)❌ 已弃用
|
||||
- **操作**: 直接从数据库中删除记录
|
||||
- **优点**:
|
||||
- 节省数据库空间
|
||||
- 查询速度快
|
||||
- **缺点**:
|
||||
- 数据无法恢复
|
||||
- 丢失历史记录
|
||||
- 不符合审计要求
|
||||
|
||||
## 数据库表结构
|
||||
|
||||
### vehicle 表(车辆表)
|
||||
```sql
|
||||
CREATE TABLE `vehicle` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
||||
`license_plate` varchar(20) NOT NULL COMMENT '车牌号',
|
||||
`car_front_photo` varchar(500) DEFAULT NULL COMMENT '车头照片',
|
||||
`car_rear_photo` varchar(500) DEFAULT NULL COMMENT '车尾照片',
|
||||
`driving_license_photo` varchar(500) DEFAULT NULL COMMENT '行驶证照片',
|
||||
`record_code` varchar(500) DEFAULT NULL COMMENT '牧运通备案码照片',
|
||||
`remark` varchar(500) DEFAULT NULL COMMENT '备注',
|
||||
`is_delete` tinyint(1) DEFAULT 0 COMMENT '是否删除(0-未删除,1-已删除)',
|
||||
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
|
||||
`created_by` int(11) DEFAULT NULL COMMENT '创建人ID',
|
||||
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
|
||||
`updated_by` int(11) DEFAULT NULL COMMENT '更新人ID',
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uk_license_plate` (`license_plate`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='车辆管理表';
|
||||
```
|
||||
|
||||
### 关键字段
|
||||
- `is_delete`: 删除标记
|
||||
- `0` 或 `NULL`: 未删除(正常状态)
|
||||
- `1`: 已删除(逻辑删除)
|
||||
|
||||
## 后端实现
|
||||
|
||||
### 1. VehicleServiceImpl.java - 逻辑删除方法
|
||||
|
||||
```java
|
||||
/**
|
||||
* 删除车辆(逻辑删除,只标记为已删除,不真正删除数据)
|
||||
*/
|
||||
@Override
|
||||
@Transactional
|
||||
public AjaxResult deleteVehicle(Integer id) {
|
||||
System.out.println("[VEHICLE-DELETE] 开始逻辑删除车辆,ID: " + id);
|
||||
|
||||
if (id == null) {
|
||||
System.out.println("[VEHICLE-DELETE] 删除失败:车辆ID为空");
|
||||
return AjaxResult.error("车辆ID不能为空");
|
||||
}
|
||||
|
||||
// 查询车辆是否存在
|
||||
Vehicle vehicle = vehicleMapper.selectById(id);
|
||||
if (vehicle == null) {
|
||||
System.out.println("[VEHICLE-DELETE] 删除失败:车辆不存在,ID: " + id);
|
||||
return AjaxResult.error("车辆不存在");
|
||||
}
|
||||
|
||||
// ✅ 检查是否已经被删除(避免重复删除)
|
||||
if (vehicle.getIsDelete() != null && vehicle.getIsDelete() == 1) {
|
||||
System.out.println("[VEHICLE-DELETE] 删除失败:车辆已经被删除,车牌号: " + vehicle.getLicensePlate());
|
||||
return AjaxResult.error("车辆已经被删除");
|
||||
}
|
||||
|
||||
System.out.println("[VEHICLE-DELETE] 车辆信息 - 车牌号: " + vehicle.getLicensePlate());
|
||||
|
||||
// ✅ 逻辑删除:将 is_delete 设置为 1
|
||||
vehicle.setIsDelete(1);
|
||||
|
||||
// ✅ 记录删除操作者和时间
|
||||
Integer userId = SecurityUtil.getCurrentUserId();
|
||||
vehicle.setUpdatedBy(userId);
|
||||
vehicle.setUpdateTime(new Date());
|
||||
|
||||
int result = vehicleMapper.updateById(vehicle);
|
||||
|
||||
if (result > 0) {
|
||||
System.out.println("[VEHICLE-DELETE] ✅ 逻辑删除成功,车牌号: " + vehicle.getLicensePlate() + ", 操作人ID: " + userId);
|
||||
return AjaxResult.success("删除车辆成功");
|
||||
} else {
|
||||
System.out.println("[VEHICLE-DELETE] ❌ 逻辑删除失败,车牌号: " + vehicle.getLicensePlate());
|
||||
return AjaxResult.error("删除车辆失败");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. VehicleServiceImpl.java - 查询时过滤已删除记录
|
||||
|
||||
```java
|
||||
/**
|
||||
* 分页查询车辆列表(只查询未删除的记录)
|
||||
*/
|
||||
@Override
|
||||
public PageResultResponse<Vehicle> pageQuery(Map<String, Object> params) {
|
||||
// ... 省略其他代码 ...
|
||||
|
||||
// 构建查询条件
|
||||
LambdaQueryWrapper<Vehicle> queryWrapper = new LambdaQueryWrapper<>();
|
||||
|
||||
// ✅ 只查询未删除的记录(is_delete=0 或 is_delete 为 null)
|
||||
queryWrapper.and(wrapper -> wrapper.eq(Vehicle::getIsDelete, 0).or().isNull(Vehicle::getIsDelete));
|
||||
|
||||
queryWrapper.like(licensePlate != null && !licensePlate.isEmpty(), Vehicle::getLicensePlate, licensePlate);
|
||||
queryWrapper.orderByDesc(Vehicle::getCreateTime);
|
||||
|
||||
// 执行查询
|
||||
List<Vehicle> list = vehicleMapper.selectList(queryWrapper);
|
||||
|
||||
// ... 省略其他代码 ...
|
||||
}
|
||||
```
|
||||
|
||||
### 3. VehicleMapper.java - 自定义查询过滤已删除记录
|
||||
|
||||
```java
|
||||
/**
|
||||
* 根据车牌号查询车辆(唯一性校验)
|
||||
* ✅ 只查询未删除的记录
|
||||
*/
|
||||
@Select("SELECT * FROM vehicle WHERE license_plate = #{licensePlate} AND is_delete = 0")
|
||||
Vehicle selectByLicensePlate(@Param("licensePlate") String licensePlate);
|
||||
|
||||
/**
|
||||
* 根据车牌号模糊查询车辆列表
|
||||
* ✅ 只查询未删除的记录
|
||||
*/
|
||||
@Select("<script>" +
|
||||
"SELECT * FROM vehicle WHERE is_delete = 0 " +
|
||||
"<if test='licensePlate != null and licensePlate != \"\"'>" +
|
||||
"AND license_plate LIKE CONCAT('%', #{licensePlate}, '%') " +
|
||||
"</if>" +
|
||||
"ORDER BY create_time DESC " +
|
||||
"</script>")
|
||||
List<Vehicle> selectVehicleList(@Param("licensePlate") String licensePlate);
|
||||
```
|
||||
|
||||
## 前端实现
|
||||
|
||||
### vehicle.vue - 删除按钮
|
||||
|
||||
```vue
|
||||
<el-table-column label="操作" width="160">
|
||||
<template #default="scope">
|
||||
<el-button link type="primary" @click="showAddDialog(scope.row)">编辑</el-button>
|
||||
<el-button link type="danger" @click="delClick(scope.row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
```
|
||||
|
||||
```javascript
|
||||
const delClick = (row) => {
|
||||
ElMessageBox.confirm('请确认是否删除该车辆数据?', '删除', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
})
|
||||
.then(() => {
|
||||
vehicleDel(row.id)
|
||||
.then(() => {
|
||||
ElMessage.success('删除成功');
|
||||
getDataList(); // 刷新列表
|
||||
})
|
||||
.catch((error) => {
|
||||
ElMessage.error('删除失败');
|
||||
console.error('删除失败:', error);
|
||||
});
|
||||
})
|
||||
.catch(() => {});
|
||||
};
|
||||
```
|
||||
|
||||
## 操作流程
|
||||
|
||||
### 删除车辆流程
|
||||
|
||||
1. **用户点击"删除"按钮**
|
||||
- 前端弹出确认对话框:`请确认是否删除该车辆数据?`
|
||||
|
||||
2. **用户确认删除**
|
||||
- 前端调用 `vehicleDel(id)` API
|
||||
- 请求: `GET /vehicle/delete?id=123`
|
||||
|
||||
3. **后端逻辑删除**
|
||||
```sql
|
||||
UPDATE vehicle
|
||||
SET is_delete = 1,
|
||||
updated_by = {当前用户ID},
|
||||
update_time = NOW()
|
||||
WHERE id = 123
|
||||
```
|
||||
|
||||
4. **前端刷新列表**
|
||||
- 调用 `getDataList()` 重新加载列表
|
||||
- 已删除的车辆不再显示(被 `is_delete = 0` 过滤掉)
|
||||
|
||||
### 后端日志输出示例
|
||||
|
||||
```
|
||||
[VEHICLE-DELETE] 开始逻辑删除车辆,ID: 1
|
||||
[VEHICLE-DELETE] 车辆信息 - 车牌号: 鄂A 66662
|
||||
[VEHICLE-DELETE] ✅ 逻辑删除成功,车牌号: 鄂A 66662, 操作人ID: 11
|
||||
```
|
||||
|
||||
## 删除后的数据状态
|
||||
|
||||
### 删除前
|
||||
```sql
|
||||
SELECT * FROM vehicle WHERE id = 1;
|
||||
```
|
||||
| id | license_plate | is_delete | created_by | updated_by | update_time |
|
||||
|----|---------------|-----------|------------|------------|-------------|
|
||||
| 1 | 鄂A 66662 | 0 | 11 | NULL | NULL |
|
||||
|
||||
### 删除后
|
||||
```sql
|
||||
SELECT * FROM vehicle WHERE id = 1;
|
||||
```
|
||||
| id | license_plate | is_delete | created_by | updated_by | update_time |
|
||||
|----|---------------|-----------|------------|------------|----------------------|
|
||||
| 1 | 鄂A 66662 | **1** | 11 | **11** | **2025-10-29 16:50** |
|
||||
|
||||
### 前端查询(已过滤)
|
||||
```sql
|
||||
-- 前端列表查询会自动过滤 is_delete = 1 的记录
|
||||
SELECT * FROM vehicle WHERE is_delete = 0;
|
||||
```
|
||||
结果:**不包含** ID=1 的记录(已被逻辑删除)
|
||||
|
||||
## 关键改进点
|
||||
|
||||
### ✅ 改进1: 查询时过滤已删除记录
|
||||
```java
|
||||
// 使用 LambdaQueryWrapper 过滤
|
||||
queryWrapper.and(wrapper -> wrapper.eq(Vehicle::getIsDelete, 0).or().isNull(Vehicle::getIsDelete));
|
||||
```
|
||||
|
||||
**原因**:
|
||||
- 兼容 `is_delete` 为 `0` 和 `NULL` 的情况
|
||||
- 确保列表中不显示已删除的记录
|
||||
|
||||
### ✅ 改进2: 防止重复删除
|
||||
```java
|
||||
if (vehicle.getIsDelete() != null && vehicle.getIsDelete() == 1) {
|
||||
return AjaxResult.error("车辆已经被删除");
|
||||
}
|
||||
```
|
||||
|
||||
**原因**:
|
||||
- 避免对已删除的记录再次执行删除操作
|
||||
- 提供明确的错误提示
|
||||
|
||||
### ✅ 改进3: 记录删除操作者和时间
|
||||
```java
|
||||
vehicle.setIsDelete(1);
|
||||
vehicle.setUpdatedBy(userId);
|
||||
vehicle.setUpdateTime(new Date());
|
||||
```
|
||||
|
||||
**原因**:
|
||||
- 记录谁在什么时间删除了这条数据
|
||||
- 符合审计要求
|
||||
|
||||
### ✅ 改进4: 详细的日志输出
|
||||
```java
|
||||
System.out.println("[VEHICLE-DELETE] 开始逻辑删除车辆,ID: " + id);
|
||||
System.out.println("[VEHICLE-DELETE] ✅ 逻辑删除成功,车牌号: " + vehicle.getLicensePlate());
|
||||
```
|
||||
|
||||
**原因**:
|
||||
- 便于排查问题
|
||||
- 追踪删除操作
|
||||
|
||||
## 数据恢复
|
||||
|
||||
如果需要恢复已删除的车辆数据,可以执行以下 SQL:
|
||||
|
||||
```sql
|
||||
-- 恢复单条记录
|
||||
UPDATE vehicle
|
||||
SET is_delete = 0,
|
||||
updated_by = {恢复操作人ID},
|
||||
update_time = NOW()
|
||||
WHERE id = 1;
|
||||
|
||||
-- 批量恢复
|
||||
UPDATE vehicle
|
||||
SET is_delete = 0,
|
||||
updated_by = {恢复操作人ID},
|
||||
update_time = NOW()
|
||||
WHERE is_delete = 1
|
||||
AND license_plate IN ('鄂A 66662', '京B 12345');
|
||||
```
|
||||
|
||||
## 测试步骤
|
||||
|
||||
### 1. 重启后端服务
|
||||
确保新的逻辑删除代码生效。
|
||||
|
||||
### 2. 测试删除功能
|
||||
1. 进入"车辆管理"页面
|
||||
2. 点击任意车辆的"删除"按钮
|
||||
3. 确认删除对话框
|
||||
4. 观察后端日志:
|
||||
```
|
||||
[VEHICLE-DELETE] 开始逻辑删除车辆,ID: 1
|
||||
[VEHICLE-DELETE] 车辆信息 - 车牌号: 鄂A 66662
|
||||
[VEHICLE-DELETE] ✅ 逻辑删除成功,车牌号: 鄂A 66662, 操作人ID: 11
|
||||
```
|
||||
5. 刷新页面,确认该车辆不再显示
|
||||
|
||||
### 3. 验证数据库
|
||||
```sql
|
||||
-- 查看所有记录(包括已删除)
|
||||
SELECT id, license_plate, is_delete, updated_by, update_time
|
||||
FROM vehicle
|
||||
ORDER BY update_time DESC;
|
||||
|
||||
-- 查看未删除的记录
|
||||
SELECT id, license_plate
|
||||
FROM vehicle
|
||||
WHERE is_delete = 0;
|
||||
|
||||
-- 查看已删除的记录
|
||||
SELECT id, license_plate, updated_by, update_time
|
||||
FROM vehicle
|
||||
WHERE is_delete = 1;
|
||||
```
|
||||
|
||||
### 4. 测试重复删除
|
||||
1. 尝试再次删除同一车辆(通过直接调用 API)
|
||||
2. 应该返回错误:`车辆已经被删除`
|
||||
|
||||
## 常见问题
|
||||
|
||||
### Q1: 删除后为什么还能在数据库中看到?
|
||||
**答**: 这是逻辑删除,数据不会真正删除,只是标记为 `is_delete = 1`。前端列表查询时会自动过滤掉这些记录。
|
||||
|
||||
### Q2: 如何查看所有已删除的车辆?
|
||||
**答**: 执行 SQL:
|
||||
```sql
|
||||
SELECT * FROM vehicle WHERE is_delete = 1;
|
||||
```
|
||||
|
||||
### Q3: 删除的数据可以恢复吗?
|
||||
**答**: 可以,只需要将 `is_delete` 改回 `0` 即可。参考"数据恢复"章节。
|
||||
|
||||
### Q4: 为什么要记录 updated_by 和 update_time?
|
||||
**答**: 用于审计,记录谁在什么时间删除了这条数据。
|
||||
|
||||
## 总结
|
||||
|
||||
✅ **逻辑删除的优势**:
|
||||
- 数据安全,可恢复
|
||||
- 符合审计要求
|
||||
- 保留历史记录
|
||||
- 避免数据丢失
|
||||
|
||||
✅ **实现完整性**:
|
||||
- 删除操作:标记 `is_delete = 1`
|
||||
- 查询操作:过滤 `is_delete = 0`
|
||||
- 唯一性校验:过滤 `is_delete = 0`
|
||||
- 审计记录:记录操作者和时间
|
||||
|
||||
✅ **用户体验**:
|
||||
- 删除确认对话框
|
||||
- 成功/失败提示
|
||||
- 自动刷新列表
|
||||
- 防止重复删除
|
||||
|
||||
246
VEHICLE_SEARCH_FIX.md
Normal file
246
VEHICLE_SEARCH_FIX.md
Normal file
@@ -0,0 +1,246 @@
|
||||
# 车辆管理 - 车牌号搜索功能修复
|
||||
|
||||
## 问题描述
|
||||
用户反馈:**车辆管理页面的车牌号搜索功能不起作用**。
|
||||
|
||||
## 问题根源
|
||||
|
||||
### 错误的实现方式
|
||||
在 `vehicle.vue` 中,`getDataList()` 方法**没有从 `baseSearch` 组件获取搜索参数**:
|
||||
|
||||
```javascript
|
||||
// ❌ 错误:只使用 form 中的固定字段
|
||||
const form = reactive({
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
licensePlate: '', // 这个字段永远是空字符串!
|
||||
});
|
||||
|
||||
const getDataList = async () => {
|
||||
const params = {
|
||||
pageNum: form.pageNum,
|
||||
pageSize: form.pageSize,
|
||||
licensePlate: form.licensePlate, // ❌ 永远传空字符串
|
||||
};
|
||||
console.log('查询参数:', params);
|
||||
const res = await vehicleList(params);
|
||||
// ...
|
||||
};
|
||||
```
|
||||
|
||||
### 问题分析
|
||||
1. **`form.licensePlate` 字段从未更新**
|
||||
- 初始化为空字符串
|
||||
- 用户在搜索框输入车牌号时,这个值不会改变
|
||||
|
||||
2. **没有使用 `baseSearchRef.value.penetrateParams()`**
|
||||
- `baseSearch` 组件通过 `penetrateParams()` 方法返回用户输入的搜索参数
|
||||
- 其他页面(如 `driver.vue`, `attestation.vue`)都正确使用了这个方法
|
||||
- 但 `vehicle.vue` 没有调用它
|
||||
|
||||
3. **后端日志确认**
|
||||
```
|
||||
=== 查询车辆列表 ===
|
||||
参数: {pageNum=1, pageSize=10, licensePlate=}
|
||||
```
|
||||
可以看到 `licensePlate` 确实是空的
|
||||
|
||||
## 解决方案
|
||||
|
||||
### 修改前后对比
|
||||
|
||||
#### 修改前(错误)
|
||||
```javascript
|
||||
const form = reactive({
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
licensePlate: '', // ❌ 多余的字段
|
||||
});
|
||||
|
||||
const getDataList = async () => {
|
||||
data.dataListLoading = true;
|
||||
try {
|
||||
const params = {
|
||||
pageNum: form.pageNum,
|
||||
pageSize: form.pageSize,
|
||||
licensePlate: form.licensePlate, // ❌ 永远是空字符串
|
||||
};
|
||||
console.log('查询参数:', params);
|
||||
// ...
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
#### 修改后(正确)
|
||||
```javascript
|
||||
const form = reactive({
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
// ✅ 移除 licensePlate 字段,改用 baseSearchRef 获取
|
||||
});
|
||||
|
||||
const getDataList = async () => {
|
||||
data.dataListLoading = true;
|
||||
try {
|
||||
// ✅ 使用 baseSearchRef 获取搜索参数
|
||||
const params = {
|
||||
...form,
|
||||
...baseSearchRef.value.penetrateParams(),
|
||||
};
|
||||
console.log('[VEHICLE-SEARCH] 查询参数:', params);
|
||||
// ...
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
### 关键改动点
|
||||
|
||||
1. **移除 `form.licensePlate` 字段**
|
||||
- 不再在 `form` 中定义 `licensePlate`
|
||||
- 避免与 `baseSearch` 组件的状态冲突
|
||||
|
||||
2. **使用 `baseSearchRef.value.penetrateParams()`**
|
||||
- 这个方法会返回 `baseSearch` 组件中用户输入的所有搜索参数
|
||||
- 返回格式:`{ licensePlate: '用户输入的车牌号' }`
|
||||
|
||||
3. **使用扩展运算符合并参数**
|
||||
```javascript
|
||||
const params = {
|
||||
...form, // { pageNum: 1, pageSize: 10 }
|
||||
...baseSearchRef.value.penetrateParams(), // { licensePlate: '鄂A 66662' }
|
||||
};
|
||||
// 结果: { pageNum: 1, pageSize: 10, licensePlate: '鄂A 66662' }
|
||||
```
|
||||
|
||||
## 参考其他页面的正确实现
|
||||
|
||||
### driver.vue(司机管理)
|
||||
```javascript
|
||||
const form = reactive({
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
});
|
||||
|
||||
const getDataList = () => {
|
||||
data.dataListLoading = true;
|
||||
const params = {
|
||||
...form,
|
||||
...baseSearchRef.value.penetrateParams(), // ✅ 正确用法
|
||||
};
|
||||
// ...
|
||||
};
|
||||
```
|
||||
|
||||
### attestation.vue(入境检疫)
|
||||
```javascript
|
||||
const searchForm = reactive({
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
});
|
||||
|
||||
const getDataList = () => {
|
||||
const params = {
|
||||
...searchForm,
|
||||
...baseSearchRef.value.penetrateParams(), // ✅ 正确用法
|
||||
};
|
||||
// ...
|
||||
};
|
||||
```
|
||||
|
||||
## baseSearch 组件工作原理
|
||||
|
||||
### 组件定义
|
||||
```vue
|
||||
<!-- formItemList 定义搜索项 -->
|
||||
<base-search
|
||||
:formItemList="formItemList"
|
||||
@search="searchFrom"
|
||||
ref="baseSearchRef">
|
||||
</base-search>
|
||||
```
|
||||
|
||||
### formItemList 配置
|
||||
```javascript
|
||||
const formItemList = reactive([
|
||||
{
|
||||
label: '车牌号',
|
||||
param: 'licensePlate', // ← 参数名
|
||||
type: 'input',
|
||||
placeholder: '请输入车牌号',
|
||||
span: 7,
|
||||
labelWidth: 100,
|
||||
},
|
||||
]);
|
||||
```
|
||||
|
||||
### penetrateParams() 返回值
|
||||
当用户在搜索框输入 "鄂A 66662" 并点击搜索时:
|
||||
```javascript
|
||||
baseSearchRef.value.penetrateParams()
|
||||
// 返回: { licensePlate: '鄂A 66662' }
|
||||
```
|
||||
|
||||
## 测试步骤
|
||||
|
||||
### 1. 刷新浏览器
|
||||
清除前端缓存,重新加载 `vehicle.vue` 组件。
|
||||
|
||||
### 2. 测试搜索功能
|
||||
1. 进入"车辆管理"页面
|
||||
2. 在车牌号搜索框输入:`鄂A 66662`
|
||||
3. 点击"搜索"按钮
|
||||
|
||||
### 3. 查看控制台日志
|
||||
```
|
||||
[VEHICLE-SEARCH] 查询参数: { pageNum: 1, pageSize: 10, licensePlate: '鄂A 66662' }
|
||||
```
|
||||
|
||||
### 4. 查看后端日志
|
||||
```
|
||||
=== 查询车辆列表 ===
|
||||
参数: {pageNum=1, pageSize=10, licensePlate=鄂A 66662}
|
||||
```
|
||||
|
||||
### 5. 验证搜索结果
|
||||
- ✅ 表格应该只显示车牌号为 "鄂A 66662" 的车辆
|
||||
- ✅ 如果没有匹配结果,显示"暂无数据"
|
||||
|
||||
## 常见问题排查
|
||||
|
||||
### Q1: 搜索框输入后点击搜索,表格没有变化?
|
||||
**检查**:
|
||||
1. 浏览器控制台是否有报错
|
||||
2. 控制台日志中 `licensePlate` 的值是否正确
|
||||
3. `baseSearchRef.value` 是否为 `null`(组件未正确引用)
|
||||
|
||||
### Q2: 后端收到的 licensePlate 还是空?
|
||||
**可能原因**:
|
||||
1. 前端代码没有正确保存(检查文件是否保存)
|
||||
2. 浏览器缓存未清除(强制刷新:Ctrl+F5)
|
||||
3. `baseSearch` 组件版本过旧(检查组件定义)
|
||||
|
||||
### Q3: 搜索时报错 "Cannot read property 'penetrateParams' of undefined"?
|
||||
**原因**: `baseSearchRef.value` 为空
|
||||
|
||||
**解决**:
|
||||
1. 确认组件引用正确:
|
||||
```vue
|
||||
<base-search ref="baseSearchRef" ...>
|
||||
```
|
||||
2. 确认 ref 声明:
|
||||
```javascript
|
||||
const baseSearchRef = ref();
|
||||
```
|
||||
|
||||
## 总结
|
||||
|
||||
✅ **问题已修复**:
|
||||
- 使用 `baseSearchRef.value.penetrateParams()` 获取搜索参数
|
||||
- 移除 `form.licensePlate` 避免状态冲突
|
||||
- 增强日志便于排查问题
|
||||
|
||||
✅ **用户体验改善**:
|
||||
- 车牌号搜索功能正常工作
|
||||
- 支持模糊搜索(后端已实现 LIKE 查询)
|
||||
- 搜索结果实时更新
|
||||
|
||||
@@ -264,3 +264,39 @@ export function orderGetDetail(id) {
|
||||
method: 'GET',
|
||||
});
|
||||
}
|
||||
|
||||
// ==================== 运送清单管理新增 API ====================
|
||||
|
||||
// 逻辑删除运送清单
|
||||
export function deleteDeliveryLogic(id) {
|
||||
return request({
|
||||
url: `/delivery/deleteLogic?id=${id}`,
|
||||
method: 'POST',
|
||||
});
|
||||
}
|
||||
|
||||
// 打包运送清单文件(图片、视频、信息)
|
||||
export function downloadDeliveryPackage(id) {
|
||||
return request({
|
||||
url: `/delivery/downloadPackage?id=${id}`,
|
||||
method: 'GET',
|
||||
responseType: 'blob', // 重要:用于下载文件
|
||||
});
|
||||
}
|
||||
|
||||
// 获取运送清单详情(用于编辑)
|
||||
export function getDeliveryDetail(id) {
|
||||
return request({
|
||||
url: `/delivery/detail?id=${id}`,
|
||||
method: 'GET',
|
||||
});
|
||||
}
|
||||
|
||||
// 更新运送清单信息
|
||||
export function updateDeliveryInfo(data) {
|
||||
return request({
|
||||
url: '/delivery/updateDeliveryInfo',
|
||||
method: 'POST',
|
||||
data,
|
||||
});
|
||||
}
|
||||
@@ -26,7 +26,7 @@ export const constantRoutes: Array<RouteRecordRaw> = [
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: '/entry/details',
|
||||
path: 'details', // ✅ 修复:使用相对路径
|
||||
name: 'details',
|
||||
meta: {
|
||||
title: '详情',
|
||||
@@ -35,6 +35,16 @@ export const constantRoutes: Array<RouteRecordRaw> = [
|
||||
},
|
||||
component: () => import('~/views/entry/details.vue'),
|
||||
},
|
||||
{
|
||||
path: 'devices', // ✅ 修复:使用相对路径
|
||||
name: 'devices',
|
||||
meta: {
|
||||
title: '设备管理',
|
||||
keepAlive: true,
|
||||
requireAuth: true,
|
||||
},
|
||||
component: () => import('~/views/entry/devices.vue'),
|
||||
},
|
||||
],
|
||||
},
|
||||
// 系统管理路由
|
||||
@@ -48,7 +58,7 @@ export const constantRoutes: Array<RouteRecordRaw> = [
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: '/system/post',
|
||||
path: 'post', // ✅ 修复:使用相对路径
|
||||
name: 'Post',
|
||||
meta: {
|
||||
title: '岗位管理',
|
||||
@@ -58,7 +68,7 @@ export const constantRoutes: Array<RouteRecordRaw> = [
|
||||
component: () => import('~/views/system/post.vue'),
|
||||
},
|
||||
{
|
||||
path: '/system/staff',
|
||||
path: 'staff', // ✅ 修复:使用相对路径
|
||||
name: 'Staff',
|
||||
meta: {
|
||||
title: '员工管理',
|
||||
@@ -68,7 +78,7 @@ export const constantRoutes: Array<RouteRecordRaw> = [
|
||||
component: () => import('~/views/system/staff.vue'),
|
||||
},
|
||||
{
|
||||
path: '/system/tenant',
|
||||
path: 'tenant', // ✅ 修复:使用相对路径
|
||||
name: 'Tenant',
|
||||
meta: {
|
||||
title: '租户管理',
|
||||
@@ -90,7 +100,7 @@ export const constantRoutes: Array<RouteRecordRaw> = [
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: '/permission/menu',
|
||||
path: 'menu', // ✅ 修复:使用相对路径
|
||||
name: 'MenuPermission',
|
||||
meta: {
|
||||
title: '菜单权限管理',
|
||||
@@ -100,7 +110,7 @@ export const constantRoutes: Array<RouteRecordRaw> = [
|
||||
component: () => import('~/views/permission/menuPermission.vue'),
|
||||
},
|
||||
{
|
||||
path: '/permission/operation',
|
||||
path: 'operation', // ✅ 修复:使用相对路径
|
||||
name: 'OperationPermission',
|
||||
meta: {
|
||||
title: '操作权限管理',
|
||||
@@ -122,7 +132,7 @@ export const constantRoutes: Array<RouteRecordRaw> = [
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: '/hardware/collar',
|
||||
path: 'collar', // ✅ 修复:使用相对路径
|
||||
name: 'Collar',
|
||||
meta: {
|
||||
title: '智能项圈',
|
||||
@@ -132,7 +142,7 @@ export const constantRoutes: Array<RouteRecordRaw> = [
|
||||
component: () => import('~/views/hardware/collar.vue'),
|
||||
},
|
||||
{
|
||||
path: '/hardware/eartag',
|
||||
path: 'eartag', // ✅ 修复:使用相对路径
|
||||
name: 'Eartag',
|
||||
meta: {
|
||||
title: '智能耳标',
|
||||
@@ -142,7 +152,7 @@ export const constantRoutes: Array<RouteRecordRaw> = [
|
||||
component: () => import('~/views/hardware/eartag.vue'),
|
||||
},
|
||||
{
|
||||
path: '/hardware/host',
|
||||
path: 'host', // ✅ 修复:使用相对路径
|
||||
name: 'Host',
|
||||
meta: {
|
||||
title: '智能主机',
|
||||
@@ -164,7 +174,7 @@ export const constantRoutes: Array<RouteRecordRaw> = [
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: '/userManage/user',
|
||||
path: 'user', // ✅ 修复:使用相对路径
|
||||
name: 'UserManage',
|
||||
meta: {
|
||||
title: '用户管理',
|
||||
@@ -174,7 +184,7 @@ export const constantRoutes: Array<RouteRecordRaw> = [
|
||||
component: () => import('~/views/userManage/user.vue'),
|
||||
},
|
||||
{
|
||||
path: '/userManage/driver',
|
||||
path: 'driver', // ✅ 修复:使用相对路径
|
||||
name: 'DriverManage',
|
||||
meta: {
|
||||
title: '司机管理',
|
||||
@@ -184,7 +194,7 @@ export const constantRoutes: Array<RouteRecordRaw> = [
|
||||
component: () => import('~/views/userManage/driver.vue'),
|
||||
},
|
||||
{
|
||||
path: '/userManage/vehicle',
|
||||
path: 'vehicle', // ✅ 修复:使用相对路径
|
||||
name: 'VehicleManage',
|
||||
meta: {
|
||||
title: '车辆管理',
|
||||
@@ -206,7 +216,7 @@ export const constantRoutes: Array<RouteRecordRaw> = [
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: '/earlywarning/earlywarninglist',
|
||||
path: 'earlywarninglist', // ✅ 修复:使用相对路径
|
||||
name: 'EarlyWarningList',
|
||||
meta: {
|
||||
title: '早期预警列表',
|
||||
@@ -228,7 +238,7 @@ export const constantRoutes: Array<RouteRecordRaw> = [
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: '/shipping/loadingOrder',
|
||||
path: 'loadingOrder', // ✅ 修复:使用相对路径
|
||||
name: 'LoadingOrder',
|
||||
meta: {
|
||||
title: '装车订单',
|
||||
@@ -240,7 +250,7 @@ export const constantRoutes: Array<RouteRecordRaw> = [
|
||||
|
||||
|
||||
{
|
||||
path: '/shipping/shippinglist',
|
||||
path: 'shippinglist', // ✅ 修复:使用相对路径
|
||||
name: 'ShippingList',
|
||||
meta: {
|
||||
title: '运送清单',
|
||||
|
||||
@@ -78,20 +78,15 @@
|
||||
<el-table-column label="司机姓名" prop="driverName"> </el-table-column>
|
||||
<el-table-column label="创建时间" prop="createTime"></el-table-column>
|
||||
<el-table-column label="创建人" prop="createByName"></el-table-column>
|
||||
<el-table-column label="操作" width="194">
|
||||
<el-table-column label="操作" width="350">
|
||||
<template #default="scope">
|
||||
<div class="table_column_operation">
|
||||
<a v-hasPermi="['entry:view']" @click="details(scope.row, scope.row.warningTypeList ? scope.row.warningTypeList.length : 0)">详情</a>
|
||||
<el-button
|
||||
type="primary"
|
||||
link
|
||||
v-if="scope.row.status == 4 || scope.row.status == 5"
|
||||
v-hasPermi="['entry:download']"
|
||||
@click="download(scope.row)"
|
||||
:loading="downLoading[scope.row.id]"
|
||||
style="padding: 0"
|
||||
>下载文件</el-button
|
||||
>
|
||||
<el-button type="primary" link @click="details(scope.row, scope.row.warningTypeList ? scope.row.warningTypeList.length : 0)" v-hasPermi="['entry:view']">详情</el-button>
|
||||
<el-button type="primary" link @click="viewDevices(scope.row)" v-hasPermi="['entry:device']">查看设备</el-button>
|
||||
<el-button type="warning" link @click="editDelivery(scope.row)" v-hasPermi="['entry:edit']">编辑</el-button>
|
||||
<el-button type="success" link @click="updateStatus(scope.row)" v-hasPermi="['entry:status']">修改状态</el-button>
|
||||
<el-button type="info" link @click="downloadPackage(scope.row)" :loading="downLoading[scope.row.id]" v-hasPermi="['entry:download']">打包文件</el-button>
|
||||
<el-button type="danger" link @click="deleteDelivery(scope.row)" v-hasPermi="['entry:delete']">删除</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
@@ -101,18 +96,25 @@
|
||||
</el-table>
|
||||
<pagination v-model:limit="form.pageSize" v-model:page="form.pageNum" :total="data.total" @pagination="getDataList" />
|
||||
</div>
|
||||
|
||||
<!-- 编辑对话框 -->
|
||||
<createDeliveryDialog ref="editDialogRef" @success="getDataList" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted, computed } from 'vue';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||
import baseSearch from '@/components/common/searchCustom/index.vue';
|
||||
import createDeliveryDialog from '@/views/shipping/createDeliveryDialog.vue';
|
||||
import { inspectionList, downloadZip, pageDeviceList } from '@/api/abroad.js';
|
||||
import { updateDeliveryStatus, deleteDeliveryLogic, downloadDeliveryPackage, getDeliveryDetail } from '@/api/shipping.js';
|
||||
|
||||
const router = useRouter();
|
||||
const route = useRoute();
|
||||
const baseSearchRef = ref();
|
||||
const editDialogRef = ref();
|
||||
const dataListLoading = ref(false);
|
||||
const downLoading = reactive({});
|
||||
const form = reactive({
|
||||
@@ -154,13 +156,9 @@ const formItemList = reactive([
|
||||
label: '核验状态',
|
||||
type: 'select',
|
||||
selectOptions: [
|
||||
{ value: 1, text: '待装车' },
|
||||
{ value: 2, text: '已装车/预付款已支付' },
|
||||
{ value: 3, text: '已装车/尾款待支付' },
|
||||
{ value: 4, text: '已核验/待买家付款' },
|
||||
{ value: 5, text: '尾款已付款' },
|
||||
{ value: 6, text: '发票待开/进项票' },
|
||||
{ value: 7, text: '发票待开/销项' },
|
||||
{ value: 1, text: '准备中' },
|
||||
{ value: 2, text: '运输中' },
|
||||
{ value: 3, text: '已结束' },
|
||||
],
|
||||
param: 'status',
|
||||
span: 7,
|
||||
@@ -570,13 +568,9 @@ const download = async (row) => {
|
||||
// 状态文本转换
|
||||
const getStatusText = (status) => {
|
||||
const statusMap = {
|
||||
1: '待装车',
|
||||
2: '已装车/预付款已支付',
|
||||
3: '已装车/尾款待支付',
|
||||
4: '已核验/待买家付款',
|
||||
5: '尾款已付款',
|
||||
6: '发票待开/进项票',
|
||||
7: '发票待开/销项'
|
||||
1: '准备中',
|
||||
2: '运输中',
|
||||
3: '已结束'
|
||||
};
|
||||
return statusMap[status] || '未知状态';
|
||||
};
|
||||
@@ -584,17 +578,155 @@ const getStatusText = (status) => {
|
||||
// 状态标签类型
|
||||
const getStatusTagType = (status) => {
|
||||
const typeMap = {
|
||||
1: 'warning', // 待装车 - 橙色
|
||||
2: 'info', // 已装车/预付款已支付 - 蓝色
|
||||
3: 'warning', // 已装车/尾款待支付 - 橙色
|
||||
4: 'success', // 已核验/待买家付款 - 绿色
|
||||
5: 'success', // 尾款已付款 - 绿色
|
||||
6: 'info', // 发票待开/进项票 - 蓝色
|
||||
7: 'info' // 发票待开/销项 - 蓝色
|
||||
1: 'info', // 准备中 - 蓝色
|
||||
2: 'warning', // 运输中 - 橙色
|
||||
3: 'success' // 已结束 - 绿色
|
||||
};
|
||||
return typeMap[status] || 'info';
|
||||
};
|
||||
|
||||
// ==================== 新增功能方法 ====================
|
||||
|
||||
// 查看设备
|
||||
const viewDevices = (row) => {
|
||||
router.push({
|
||||
path: '/entry/devices',
|
||||
query: {
|
||||
deliveryId: row.id,
|
||||
deliveryNumber: row.deliveryNumber
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// 编辑运送清单
|
||||
const editDelivery = async (row) => {
|
||||
try {
|
||||
console.log('[EDIT-DELIVERY] 准备编辑运送清单, ID:', row.id);
|
||||
|
||||
// 检查编辑对话框组件是否已加载
|
||||
if (!editDialogRef.value || !editDialogRef.value.open) {
|
||||
ElMessage.warning('编辑功能暂不可用,请刷新页面重试');
|
||||
return;
|
||||
}
|
||||
|
||||
// 调用 detail 接口获取完整数据(包含 supplierId, buyerId, 设备信息等)
|
||||
const detailRes = await getDeliveryDetail(row.id);
|
||||
console.log('[EDIT-DELIVERY] 获取到详情数据:', detailRes);
|
||||
|
||||
if (detailRes.code === 200 && detailRes.data) {
|
||||
// 传入完整的 detail 数据给 open() 方法
|
||||
editDialogRef.value.open(detailRes.data);
|
||||
} else {
|
||||
ElMessage.error('获取运单详情失败:' + (detailRes.msg || '未知错误'));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[EDIT-DELIVERY] 打开编辑对话框失败:', error);
|
||||
ElMessage.error('打开编辑对话框失败,请重试');
|
||||
}
|
||||
};
|
||||
|
||||
// 修改状态
|
||||
const updateStatus = (row) => {
|
||||
const statusOptions = [
|
||||
{ value: 1, label: '准备中' },
|
||||
{ value: 2, label: '运输中' },
|
||||
{ value: 3, label: '已结束' }
|
||||
];
|
||||
|
||||
const currentStatus = statusOptions.find(opt => opt.value === row.status)?.label || '未知';
|
||||
|
||||
ElMessageBox.confirm(
|
||||
`当前状态:${currentStatus}。请选择要修改的状态:\n1 - 准备中\n2 - 运输中\n3 - 已结束`,
|
||||
'修改状态',
|
||||
{
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
inputPattern: /^[1-3]$/,
|
||||
inputErrorMessage: '请输入 1-3 之间的数字',
|
||||
showInput: true,
|
||||
inputPlaceholder: '请输入新状态对应的数字(1/2/3)',
|
||||
inputValue: String(row.status)
|
||||
}
|
||||
).then(async ({ value }) => {
|
||||
const newStatus = parseInt(value);
|
||||
if (newStatus && newStatus >= 1 && newStatus <= 3) {
|
||||
try {
|
||||
const res = await updateDeliveryStatus({
|
||||
id: row.id,
|
||||
status: newStatus
|
||||
});
|
||||
if (res.code === 200) {
|
||||
ElMessage.success(`状态已修改为:${statusOptions.find(opt => opt.value === newStatus)?.label}`);
|
||||
getDataList();
|
||||
} else {
|
||||
ElMessage.error(res.msg || '状态修改失败');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('修改状态失败:', error);
|
||||
ElMessage.error('状态修改失败,请重试');
|
||||
}
|
||||
} else {
|
||||
ElMessage.error('无效的状态值');
|
||||
}
|
||||
}).catch(() => {
|
||||
ElMessage.info('已取消');
|
||||
});
|
||||
};
|
||||
|
||||
// 删除运送清单(逻辑删除)
|
||||
const deleteDelivery = (row) => {
|
||||
ElMessageBox.confirm(`确定要删除运单号为 ${row.deliveryNumber} 的运送清单吗?`, '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(async () => {
|
||||
try {
|
||||
const res = await deleteDeliveryLogic(row.id);
|
||||
if (res.code === 200) {
|
||||
ElMessage.success('删除成功');
|
||||
getDataList();
|
||||
} else {
|
||||
ElMessage.error(res.msg || '删除失败');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('删除运送清单失败:', error);
|
||||
ElMessage.error('删除失败,请重试');
|
||||
}
|
||||
}).catch(() => {
|
||||
ElMessage.info('已取消删除');
|
||||
});
|
||||
};
|
||||
|
||||
// 打包文件
|
||||
const downloadPackage = async (row) => {
|
||||
try {
|
||||
downLoading[row.id] = true;
|
||||
ElMessage.info('正在打包文件,请稍候...');
|
||||
|
||||
// 调用后端API打包文件(包含图片、视频和信息)
|
||||
const res = await downloadDeliveryPackage(row.id);
|
||||
|
||||
// 创建下载链接
|
||||
const blob = new Blob([res], { type: 'application/zip' });
|
||||
const url = window.URL.createObjectURL(blob);
|
||||
const link = document.createElement('a');
|
||||
link.href = url;
|
||||
link.download = `运送清单_${row.deliveryNumber}_${new Date().getTime()}.zip`;
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
window.URL.revokeObjectURL(url);
|
||||
|
||||
ElMessage.success('文件打包成功');
|
||||
} catch (error) {
|
||||
console.error('打包文件失败:', error);
|
||||
ElMessage.error('打包文件失败,请重试');
|
||||
} finally {
|
||||
downLoading[row.id] = false;
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
getDataList();
|
||||
});
|
||||
|
||||
@@ -20,9 +20,9 @@
|
||||
<div class="title">基础信息</div>
|
||||
<el-descriptions :column="4">
|
||||
<el-descriptions-item label="运单号:">{{ data.baseInfo.deliveryNumber || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="订单标题:">{{ data.baseInfo.deliveryTitle || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="资金方:">{{ data.baseInfo.fundName || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="采购商:">{{ data.baseInfo.buyerName || '-' }}</el-descriptions-item>
|
||||
|
||||
<el-descriptions-item label="卖方:">{{ data.baseInfo.supplierName || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="买方:">{{ data.baseInfo.buyerName || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="车牌号:">{{ data.baseInfo.licensePlate || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="司机姓名:">{{ data.baseInfo.driverName || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="起始地:">{{ data.baseInfo.startLocation || '-' }}</el-descriptions-item>
|
||||
@@ -166,6 +166,32 @@
|
||||
</span>
|
||||
</el-descriptions-item>
|
||||
|
||||
<!-- 到地相关照片 -->
|
||||
<el-descriptions-item label="到地纸质磅单:">
|
||||
<span style="vertical-align: top">
|
||||
<el-image
|
||||
v-if="data.baseInfo.destinationPoundListImg"
|
||||
style="width: 50px; height: 50px; margin-right: 10px"
|
||||
:src="data.baseInfo.destinationPoundListImg ? data.baseInfo.destinationPoundListImg : ''"
|
||||
fit="cover"
|
||||
:preview-src-list="[data.baseInfo.destinationPoundListImg] ? [data.baseInfo.destinationPoundListImg] : []"
|
||||
preview-teleported
|
||||
/>
|
||||
</span>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="到地车辆过重磅车头照片:">
|
||||
<span style="vertical-align: top">
|
||||
<el-image
|
||||
v-if="data.baseInfo.destinationVehicleFrontPhoto"
|
||||
style="width: 50px; height: 50px; margin-right: 10px"
|
||||
:src="data.baseInfo.destinationVehicleFrontPhoto ? data.baseInfo.destinationVehicleFrontPhoto : ''"
|
||||
fit="cover"
|
||||
:preview-src-list="[data.baseInfo.destinationVehicleFrontPhoto] ? [data.baseInfo.destinationVehicleFrontPhoto] : []"
|
||||
preview-teleported
|
||||
/>
|
||||
</span>
|
||||
</el-descriptions-item>
|
||||
|
||||
<!-- 视频上传区域 -->
|
||||
<el-descriptions-item label="空车过磅视频(含车牌、地磅数):">
|
||||
<span style="vertical-align: top" v-if="data.baseInfo.emptyWeightVideo">
|
||||
@@ -212,6 +238,24 @@
|
||||
/>
|
||||
</span>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="卸牛视频:">
|
||||
<span style="vertical-align: top" v-if="data.baseInfo.unloadCattleVideo">
|
||||
<video
|
||||
style="height: 250px; width: auto; border-radius: 5px; margin-left: 60px"
|
||||
controls
|
||||
:src="data.baseInfo.unloadCattleVideo"
|
||||
/>
|
||||
</span>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="到地过磅视频:">
|
||||
<span style="vertical-align: top" v-if="data.baseInfo.destinationWeightVideo">
|
||||
<video
|
||||
style="height: 250px; width: auto; border-radius: 5px; margin-left: 60px"
|
||||
controls
|
||||
:src="data.baseInfo.destinationWeightVideo"
|
||||
/>
|
||||
</span>
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</div>
|
||||
<div class="ear-box">
|
||||
@@ -494,6 +538,7 @@ import { ref, reactive, onMounted, computed } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import { earList, hostLocation, hostTrack, waybillDetail, collarList, collarLogList, earLogList, testDeliveryDevices, pageDeviceList, getEarTagLogs, getCollarLogs, getHostLogs, getEarTagTrajectory, getCollarTrajectory, getHostTrajectory } from '@/api/abroad.js';
|
||||
import { vehicleList } from '@/api/userManage.js';
|
||||
import startIcon from '../../assets/images/qi.png';
|
||||
import endIcon from '../../assets/images/zhong.png';
|
||||
import TrackDialog from '../hardware/trackDialog.vue';
|
||||
@@ -525,6 +570,12 @@ const data = reactive({
|
||||
quarStatusDesc: '入场状态', // 根据status反显
|
||||
status: '订单状态 1境外预检 2 已入境待隔离(检疫成功) 3 已入隔离场 4 隔离场出场 ',
|
||||
deliverTime: '启运时间',
|
||||
// 新增照片字段
|
||||
destinationPoundListImg: '',
|
||||
destinationVehicleFrontPhoto: '',
|
||||
// 新增视频字段
|
||||
unloadCattleVideo: '',
|
||||
destinationWeightVideo: '',
|
||||
},
|
||||
warnInfo: [],
|
||||
serverIds: '',
|
||||
@@ -609,6 +660,11 @@ const getDetail = () => {
|
||||
driverId: data.baseInfo.driverId
|
||||
});
|
||||
|
||||
// 查询车辆照片
|
||||
if (data.baseInfo.licensePlate) {
|
||||
loadVehiclePhotos();
|
||||
}
|
||||
|
||||
// 使用新的统一API获取智能主机信息
|
||||
getHostDeviceInfo();
|
||||
} else {
|
||||
@@ -618,6 +674,16 @@ const getDetail = () => {
|
||||
.catch(() => {});
|
||||
};
|
||||
|
||||
// 查询车辆照片(从司机信息获取)
|
||||
const loadVehiclePhotos = () => {
|
||||
// 后端已经从delivery/driver信息中获取了车身照片,无需额外前端查询
|
||||
// carFrontPhoto和carBehindPhoto应该已经由后端的DeliveryServiceImpl填充
|
||||
console.log('车身照片信息:', {
|
||||
carFrontPhoto: data.baseInfo.carFrontPhoto,
|
||||
carBehindPhoto: data.baseInfo.carBehindPhoto
|
||||
});
|
||||
};
|
||||
|
||||
// 智能主机列表查询
|
||||
const getHostList = () => {
|
||||
if (!route.query.id) {
|
||||
@@ -1107,13 +1173,9 @@ const totalRegisteredDevices = computed(() => {
|
||||
|
||||
const getStatusText = (status) => {
|
||||
const statusMap = {
|
||||
1: '待装车',
|
||||
2: '已装车/预付款已支付',
|
||||
3: '已装车/尾款待支付',
|
||||
4: '已核验/待买家付款',
|
||||
5: '尾款已付款',
|
||||
6: '发票待开/进项票',
|
||||
7: '发票待开/销项'
|
||||
1: '准备中',
|
||||
2: '运输中',
|
||||
3: '已结束'
|
||||
};
|
||||
return statusMap[status] || '未知状态';
|
||||
};
|
||||
@@ -1121,13 +1183,9 @@ const getStatusText = (status) => {
|
||||
// 根据状态返回标签类型(颜色)
|
||||
const getStatusType = (status) => {
|
||||
const typeMap = {
|
||||
1: 'info', // 待装车 - 灰色
|
||||
2: 'success', // 已装车/预付款已支付 - 绿色
|
||||
3: 'warning', // 已装车/尾款待支付 - 橙色
|
||||
4: 'warning', // 已核验/待买家付款 - 橙色
|
||||
5: 'success', // 尾款已付款 - 绿色
|
||||
6: 'primary', // 发票待开/进项票 - 蓝色
|
||||
7: 'primary' // 发票待开/销项 - 蓝色
|
||||
1: 'info', // 准备中 - 灰色
|
||||
2: 'warning', // 运输中 - 橙色
|
||||
3: 'success' // 已结束 - 绿色
|
||||
};
|
||||
return typeMap[status] || 'info';
|
||||
};
|
||||
|
||||
365
pc-cattle-transportation/src/views/entry/devices.vue
Normal file
365
pc-cattle-transportation/src/views/entry/devices.vue
Normal file
@@ -0,0 +1,365 @@
|
||||
<template>
|
||||
<div class="devices-container">
|
||||
<!-- 参数缺失时的友好提示 -->
|
||||
<div v-if="!route.query.deliveryId" class="error-container">
|
||||
<el-result
|
||||
icon="warning"
|
||||
title="参数缺失"
|
||||
sub-title="缺少必要的参数,无法加载设备列表"
|
||||
>
|
||||
<template #extra>
|
||||
<el-button type="primary" @click="goBack">返回上一页</el-button>
|
||||
</template>
|
||||
</el-result>
|
||||
</div>
|
||||
|
||||
<!-- 正常内容 -->
|
||||
<div v-else>
|
||||
<div class="header-info">
|
||||
<el-page-header @back="goBack">
|
||||
<template #content>
|
||||
<div class="header-title">
|
||||
<span class="text-large font-600 mr-3">
|
||||
运送清单设备管理 - {{ route.query.deliveryNumber || '未知运单' }}
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
</el-page-header>
|
||||
</div>
|
||||
|
||||
<!-- 设备统计 -->
|
||||
<div class="statistics-box">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="6">
|
||||
<el-card shadow="hover">
|
||||
<el-statistic title="智能主机" :value="hostTotal">
|
||||
<template #suffix>个</template>
|
||||
</el-statistic>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-card shadow="hover">
|
||||
<el-statistic title="智能耳标" :value="earTotal">
|
||||
<template #suffix>个</template>
|
||||
</el-statistic>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-card shadow="hover">
|
||||
<el-statistic title="智能项圈" :value="collarTotal">
|
||||
<template #suffix>个</template>
|
||||
</el-statistic>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-card shadow="hover">
|
||||
<el-statistic title="设备总数" :value="totalDevices">
|
||||
<template #suffix>个</template>
|
||||
</el-statistic>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
|
||||
<!-- 设备列表Tab -->
|
||||
<div class="main-container">
|
||||
<el-tabs v-model="activeTab" @tab-change="handleTabChange">
|
||||
<!-- 智能主机 -->
|
||||
<el-tab-pane label="智能主机" name="host">
|
||||
<el-table v-loading="hostLoading" :data="hostList" border>
|
||||
<el-table-column label="设备ID" prop="deviceId" width="200"></el-table-column>
|
||||
<el-table-column label="设备SN" prop="sn" width="200"></el-table-column>
|
||||
<el-table-column label="运单号" prop="deliveryNumber"></el-table-column>
|
||||
<el-table-column label="绑定时间" prop="bindTime"></el-table-column>
|
||||
<el-table-column label="操作" width="150">
|
||||
<template #default="scope">
|
||||
<el-button type="danger" link @click="unbindDevice(scope.row, 1)">解绑</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<template #empty>
|
||||
<div class="dataListOnEmpty">暂无数据</div>
|
||||
</template>
|
||||
</el-table>
|
||||
</el-tab-pane>
|
||||
|
||||
<!-- 智能耳标 -->
|
||||
<el-tab-pane label="智能耳标" name="ear">
|
||||
<el-table v-loading="earLoading" :data="earList" border>
|
||||
<el-table-column label="设备ID" prop="deviceId" width="200"></el-table-column>
|
||||
<el-table-column label="运单号" prop="deliveryNumber"></el-table-column>
|
||||
<el-table-column label="重量(kg)" prop="weight"></el-table-column>
|
||||
<el-table-column label="车牌号" prop="carNumber"></el-table-column>
|
||||
<el-table-column label="绑定时间" prop="bindTime"></el-table-column>
|
||||
<el-table-column label="操作" width="150">
|
||||
<template #default="scope">
|
||||
<el-button type="danger" link @click="unbindDevice(scope.row, 2)">解绑</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<template #empty>
|
||||
<div class="dataListOnEmpty">暂无数据</div>
|
||||
</template>
|
||||
</el-table>
|
||||
<pagination v-model:limit="earForm.pageSize" v-model:page="earForm.pageNum" :total="earTotal" @pagination="getEarList" />
|
||||
</el-tab-pane>
|
||||
|
||||
<!-- 智能项圈 -->
|
||||
<el-tab-pane label="智能项圈" name="collar">
|
||||
<el-table v-loading="collarLoading" :data="collarList" border>
|
||||
<el-table-column label="设备SN" prop="sn" width="200"></el-table-column>
|
||||
<el-table-column label="设备ID" prop="deviceId" width="200"></el-table-column>
|
||||
<el-table-column label="运单号" prop="deliveryNumber"></el-table-column>
|
||||
<el-table-column label="车牌号" prop="carNumber"></el-table-column>
|
||||
<el-table-column label="绑定时间" prop="bindTime"></el-table-column>
|
||||
<el-table-column label="操作" width="150">
|
||||
<template #default="scope">
|
||||
<el-button type="danger" link @click="unbindDevice(scope.row, 4)">解绑</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<template #empty>
|
||||
<div class="dataListOnEmpty">暂无数据</div>
|
||||
</template>
|
||||
</el-table>
|
||||
<pagination v-model:limit="collarForm.pageSize" v-model:page="collarForm.pageNum" :total="collarTotal" @pagination="getCollarList" />
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted, computed } from 'vue';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||
import { pageDeviceList } from '@/api/abroad.js';
|
||||
import { unbindDevice as unbindDeviceAPI } from '@/api/shipping.js';
|
||||
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
|
||||
const activeTab = ref('host');
|
||||
const hostLoading = ref(false);
|
||||
const earLoading = ref(false);
|
||||
const collarLoading = ref(false);
|
||||
|
||||
const hostList = ref([]);
|
||||
const earList = ref([]);
|
||||
const collarList = ref([]);
|
||||
|
||||
const hostTotal = ref(0);
|
||||
const earTotal = ref(0);
|
||||
const collarTotal = ref(0);
|
||||
|
||||
const earForm = reactive({
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
});
|
||||
|
||||
const collarForm = reactive({
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
});
|
||||
|
||||
// 计算设备总数
|
||||
const totalDevices = computed(() => {
|
||||
return hostTotal.value + earTotal.value + collarTotal.value;
|
||||
});
|
||||
|
||||
// 返回上一页
|
||||
const goBack = () => {
|
||||
router.back();
|
||||
};
|
||||
|
||||
// Tab切换
|
||||
const handleTabChange = (tabName) => {
|
||||
console.log('切换到Tab:', tabName);
|
||||
};
|
||||
|
||||
// 获取智能主机列表
|
||||
const getHostList = async () => {
|
||||
if (!route.query.deliveryId) {
|
||||
console.warn('deliveryId为空,跳过主机列表查询');
|
||||
return;
|
||||
}
|
||||
|
||||
hostLoading.value = true;
|
||||
try {
|
||||
const res = await pageDeviceList({
|
||||
pageNum: 1,
|
||||
pageSize: 100,
|
||||
deliveryId: parseInt(route.query.deliveryId),
|
||||
deviceType: 1,
|
||||
});
|
||||
|
||||
console.log('主机设备API返回:', res);
|
||||
if (res.code === 200) {
|
||||
const hostDevices = res.data.filter(device => device.deviceType === 1 || device.deviceType === '1');
|
||||
hostList.value = hostDevices || [];
|
||||
hostTotal.value = hostDevices.length || 0;
|
||||
} else {
|
||||
ElMessage.error(res.msg || '获取主机设备失败');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取主机设备失败:', error);
|
||||
ElMessage.error('获取主机设备失败');
|
||||
} finally {
|
||||
hostLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 获取智能耳标列表
|
||||
const getEarList = async () => {
|
||||
if (!route.query.deliveryId) {
|
||||
console.warn('deliveryId为空,跳过耳标列表查询');
|
||||
return;
|
||||
}
|
||||
|
||||
earLoading.value = true;
|
||||
try {
|
||||
const res = await pageDeviceList({
|
||||
pageNum: earForm.pageNum,
|
||||
pageSize: earForm.pageSize,
|
||||
deliveryId: parseInt(route.query.deliveryId),
|
||||
deviceType: 2,
|
||||
});
|
||||
|
||||
console.log('耳标设备API返回:', res);
|
||||
if (res.code === 200) {
|
||||
const earDevices = res.data.filter(device => device.deviceType === 2 || device.deviceType === '2');
|
||||
earList.value = earDevices || [];
|
||||
earTotal.value = earDevices.length || 0;
|
||||
} else {
|
||||
ElMessage.error(res.msg || '获取耳标设备失败');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取耳标设备失败:', error);
|
||||
ElMessage.error('获取耳标设备失败');
|
||||
} finally {
|
||||
earLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 获取智能项圈列表
|
||||
const getCollarList = async () => {
|
||||
if (!route.query.deliveryId) {
|
||||
console.warn('deliveryId为空,跳过项圈列表查询');
|
||||
return;
|
||||
}
|
||||
|
||||
collarLoading.value = true;
|
||||
try {
|
||||
const res = await pageDeviceList({
|
||||
pageNum: collarForm.pageNum,
|
||||
pageSize: collarForm.pageSize,
|
||||
deliveryId: parseInt(route.query.deliveryId),
|
||||
deviceType: 4,
|
||||
});
|
||||
|
||||
console.log('项圈设备API返回:', res);
|
||||
if (res.code === 200) {
|
||||
const collarDevices = res.data.filter(device => device.deviceType === 4 || device.deviceType === '4');
|
||||
collarList.value = collarDevices || [];
|
||||
collarTotal.value = collarDevices.length || 0;
|
||||
} else {
|
||||
ElMessage.error(res.msg || '获取项圈设备失败');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取项圈设备失败:', error);
|
||||
ElMessage.error('获取项圈设备失败');
|
||||
} finally {
|
||||
collarLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 解绑设备
|
||||
const unbindDevice = (device, deviceType) => {
|
||||
const deviceTypeName = deviceType === 1 ? '智能主机' : (deviceType === 2 ? '智能耳标' : '智能项圈');
|
||||
const deviceIdDisplay = device.deviceId || device.sn;
|
||||
|
||||
ElMessageBox.confirm(
|
||||
`确定要解绑${deviceTypeName}设备 ${deviceIdDisplay} 吗?`,
|
||||
'提示',
|
||||
{
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}
|
||||
).then(async () => {
|
||||
try {
|
||||
const res = await unbindDeviceAPI(device.deviceId || device.sn);
|
||||
if (res.code === 200) {
|
||||
ElMessage.success('设备解绑成功');
|
||||
// 刷新对应列表
|
||||
if (deviceType === 1) {
|
||||
getHostList();
|
||||
} else if (deviceType === 2) {
|
||||
getEarList();
|
||||
} else if (deviceType === 4) {
|
||||
getCollarList();
|
||||
}
|
||||
} else {
|
||||
ElMessage.error(res.msg || '设备解绑失败');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('设备解绑失败:', error);
|
||||
ElMessage.error('设备解绑失败');
|
||||
}
|
||||
}).catch(() => {
|
||||
ElMessage.info('已取消解绑');
|
||||
});
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
console.log('设备管理页面初始化,deliveryId:', route.query.deliveryId);
|
||||
console.log('运单号:', route.query.deliveryNumber);
|
||||
|
||||
if (!route.query.deliveryId) {
|
||||
console.warn('deliveryId为空,无法加载设备列表');
|
||||
ElMessage.error('缺少必要的参数,请从列表页面点击"查看设备"按钮进入');
|
||||
return;
|
||||
}
|
||||
|
||||
// 加载所有设备列表
|
||||
getHostList();
|
||||
getEarList();
|
||||
getCollarList();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.devices-container {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.error-container {
|
||||
padding: 50px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.header-info {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.header-title {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.statistics-box {
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.main-container {
|
||||
background: #fff;
|
||||
padding: 20px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.dataListOnEmpty {
|
||||
padding: 50px;
|
||||
text-align: center;
|
||||
color: #909399;
|
||||
font-size: 14px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
v-model="dialogVisible"
|
||||
title="新增运送清单"
|
||||
:title="formData.editId ? '编辑运送清单' : '新增运送清单'"
|
||||
width="800px"
|
||||
:close-on-click-modal="false"
|
||||
@close="handleClose"
|
||||
@@ -75,12 +75,12 @@
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="司机姓名" prop="driverName">
|
||||
<el-select v-model="formData.driverName" placeholder="请选择司机" clearable filterable style="width: 100%" @change="handleDriverChange">
|
||||
<el-form-item label="司机姓名" prop="driverId">
|
||||
<el-select v-model="formData.driverId" placeholder="请选择司机" clearable filterable style="width: 100%" @change="handleDriverChange">
|
||||
<el-option
|
||||
v-for="item in driverOptions"
|
||||
:key="item.id"
|
||||
:label="item.username"
|
||||
:label="item.username + (item.mobile ? ' - ' + item.mobile : '')"
|
||||
:value="item.id"
|
||||
/>
|
||||
</el-select>
|
||||
@@ -648,10 +648,12 @@ const showStartLocationMap = ref(false);
|
||||
const showEndLocationMap = ref(false);
|
||||
|
||||
const formData = reactive({
|
||||
editId: null, // 编辑时的运单ID
|
||||
orderId: null,
|
||||
shipper: null,
|
||||
buyer: null,
|
||||
plateNumber: null,
|
||||
driverId: null, // 司机ID(后端用)
|
||||
driverName: null,
|
||||
driverPhone: '',
|
||||
serverId: null, // Integer(设备表主键ID)
|
||||
@@ -737,7 +739,7 @@ const rules = {
|
||||
shipper: [{ required: true, message: '请选择发货方', trigger: 'change' }],
|
||||
buyer: [{ required: true, message: '请选择采购方', trigger: 'change' }],
|
||||
plateNumber: [{ required: true, message: '请选择车牌号', trigger: 'change' }],
|
||||
driverName: [{ required: true, message: '请输入司机姓名', trigger: 'blur' }],
|
||||
driverId: [{ required: true, message: '请选择司机', trigger: 'change' }],
|
||||
driverPhone: [{ required: true, validator: validatePhone, trigger: 'blur' }],
|
||||
estimatedDepartureTime: [{ required: true, message: '请选择预计出发时间', trigger: 'change' }],
|
||||
estimatedArrivalTime: [{ required: true, validator: validateArrivalTime, trigger: 'change' }],
|
||||
@@ -771,32 +773,20 @@ const collarDevices = computed(() => {
|
||||
|
||||
// 完善提交数据 - 只提交DeliveryCreateDto需要的字段
|
||||
const buildSubmitData = () => {
|
||||
// 将发货方和采购方的ID转换为名称
|
||||
let shipperName = '';
|
||||
let buyerName = '';
|
||||
|
||||
if (formData.shipper) {
|
||||
const shipper = supplierList.value.find(item => item.id === formData.shipper);
|
||||
shipperName = shipper ? (shipper.username || shipper.mobile || shipper.name || '') : String(formData.shipper);
|
||||
}
|
||||
|
||||
if (formData.buyer) {
|
||||
const buyer = buyerList.value.find(item => item.id === formData.buyer);
|
||||
buyerName = buyer ? (buyer.username || buyer.mobile || buyer.name || '') : String(formData.buyer);
|
||||
}
|
||||
|
||||
// 将司机ID转换为名称
|
||||
// 将司机ID转换为姓名
|
||||
let driverNameStr = '';
|
||||
if (formData.driverName) {
|
||||
const driver = driverOptions.value.find(item => item.id === formData.driverName);
|
||||
driverNameStr = driver ? (driver.name || driver.mobile || driver.username || '') : String(formData.driverName);
|
||||
if (formData.driverId) {
|
||||
const driver = driverOptions.value.find(item => item.id === formData.driverId);
|
||||
// 使用username作为司机姓名(不要使用mobile)
|
||||
driverNameStr = driver ? (driver.username || '') : '';
|
||||
}
|
||||
|
||||
const data = {
|
||||
// 基本信息
|
||||
shipper: shipperName,
|
||||
buyer: buyerName,
|
||||
shipperId: formData.shipper,
|
||||
buyerId: formData.buyer,
|
||||
plateNumber: formData.plateNumber,
|
||||
driverId: formData.driverId, // 传递司机ID给后端
|
||||
driverName: driverNameStr,
|
||||
driverPhone: formData.driverPhone,
|
||||
|
||||
@@ -871,14 +861,119 @@ const buildSubmitData = () => {
|
||||
return data;
|
||||
};
|
||||
|
||||
// 打开弹窗
|
||||
const open = () => {
|
||||
// 打开弹窗(支持编辑模式)
|
||||
const open = async (editData = null) => {
|
||||
dialogVisible.value = true;
|
||||
loadSupplierAndBuyerList();
|
||||
loadDeviceOptions();
|
||||
loadDriverList();
|
||||
loadVehicleList();
|
||||
loadOrderList();
|
||||
|
||||
// 并行加载所有下拉列表数据
|
||||
await Promise.all([
|
||||
loadSupplierAndBuyerList(),
|
||||
loadDeviceOptions(),
|
||||
loadDriverList(),
|
||||
loadVehicleList(),
|
||||
loadOrderList()
|
||||
]);
|
||||
|
||||
console.log('[OPEN-DIALOG] 所有下拉列表加载完成');
|
||||
console.log('[OPEN-DIALOG] 车辆列表:', vehicleOptions.value);
|
||||
|
||||
// 如果传入了编辑数据,则填充表单
|
||||
if (editData) {
|
||||
fillFormWithEditData(editData);
|
||||
}
|
||||
};
|
||||
|
||||
// 填充编辑数据到表单
|
||||
const fillFormWithEditData = (editData) => {
|
||||
console.log('[EDIT-FILL] 开始填充编辑数据:', editData);
|
||||
|
||||
// editData 包含两个部分:
|
||||
// 1. editData.delivery - 运单基本信息
|
||||
// 2. editData 的根级字段 - supplierId, buyerId, eartagIds, collarIds, serverIds
|
||||
|
||||
const delivery = editData.delivery || editData; // 兼容两种数据结构
|
||||
|
||||
// 基本信息
|
||||
formData.orderId = delivery.orderId || null;
|
||||
|
||||
// 发货方和采购方:优先使用根级的 supplierId 和 buyerId
|
||||
formData.shipper = editData.supplierId || (delivery.supplierId ? parseInt(delivery.supplierId) : null);
|
||||
formData.buyer = editData.buyerId || delivery.buyerId || null;
|
||||
console.log('[EDIT-FILL] 发货方ID:', formData.shipper, '采购方ID:', formData.buyer);
|
||||
|
||||
// 车牌号
|
||||
formData.plateNumber = delivery.licensePlate || '';
|
||||
console.log('[EDIT-FILL] 车牌号:', formData.plateNumber);
|
||||
console.log('[EDIT-FILL] 当前车辆列表:', vehicleOptions.value);
|
||||
|
||||
// 检查车牌号是否在车辆列表中
|
||||
const vehicleExists = vehicleOptions.value.find(v => v.licensePlate === formData.plateNumber);
|
||||
if (!vehicleExists && formData.plateNumber) {
|
||||
console.warn('[EDIT-FILL] ⚠️ 车牌号在车辆列表中不存在:', formData.plateNumber);
|
||||
}
|
||||
|
||||
formData.driverId = delivery.driverId || null;
|
||||
formData.driverPhone = delivery.driverMobile || '';
|
||||
|
||||
// 设备信息:从根级读取
|
||||
if (editData.serverIds && editData.serverIds !== '') {
|
||||
formData.serverId = editData.serverIds;
|
||||
console.log('[EDIT-FILL] 主机ID:', formData.serverId);
|
||||
}
|
||||
|
||||
if (editData.eartagIds && Array.isArray(editData.eartagIds) && editData.eartagIds.length > 0) {
|
||||
formData.eartagIds = editData.eartagIds;
|
||||
console.log('[EDIT-FILL] 耳标IDs:', formData.eartagIds);
|
||||
}
|
||||
|
||||
if (editData.collarIds && Array.isArray(editData.collarIds) && editData.collarIds.length > 0) {
|
||||
formData.collarIds = editData.collarIds;
|
||||
console.log('[EDIT-FILL] 项圈IDs:', formData.collarIds);
|
||||
}
|
||||
|
||||
// 地址和坐标
|
||||
formData.startLocation = delivery.startLocation || '';
|
||||
formData.startLat = delivery.startLat || '';
|
||||
formData.startLon = delivery.startLon || '';
|
||||
formData.endLocation = delivery.endLocation || '';
|
||||
formData.endLat = delivery.endLat || '';
|
||||
formData.endLon = delivery.endLon || '';
|
||||
|
||||
// 时间(需要处理)
|
||||
// estimatedDeliveryTime 对应 estimatedArrivalTime
|
||||
if (delivery.estimatedDeliveryTime) {
|
||||
formData.estimatedArrivalTime = delivery.estimatedDeliveryTime;
|
||||
}
|
||||
|
||||
// 重量信息
|
||||
formData.emptyWeight = delivery.emptyWeight || null;
|
||||
formData.entruckWeight = delivery.entruckWeight || null;
|
||||
formData.landingEntruckWeight = delivery.landingEntruckWeight || null;
|
||||
|
||||
// 照片
|
||||
formData.quarantineTickeyUrl = delivery.quarantineTickeyUrl || '';
|
||||
formData.poundListImg = delivery.poundListImg || '';
|
||||
formData.emptyVehicleFrontPhoto = delivery.emptyVehicleFrontPhoto || '';
|
||||
formData.loadedVehicleFrontPhoto = delivery.loadedVehicleFrontPhoto || '';
|
||||
formData.loadedVehicleWeightPhoto = delivery.loadedVehicleWeightPhoto || '';
|
||||
formData.driverIdCardPhoto = delivery.driverIdCardPhoto || '';
|
||||
formData.destinationPoundListImg = delivery.destinationPoundListImg || '';
|
||||
formData.destinationVehicleFrontPhoto = delivery.destinationVehicleFrontPhoto || '';
|
||||
|
||||
// 视频
|
||||
formData.entruckWeightVideo = delivery.entruckWeightVideo || '';
|
||||
formData.emptyWeightVideo = delivery.emptyWeightVideo || '';
|
||||
formData.cattleLoadingVideo = delivery.cattleLoadingVideo || '';
|
||||
formData.controlSlotVideo = delivery.controlSlotVideo || '';
|
||||
formData.cattleLoadingCircleVideo = delivery.cattleLoadingCircleVideo || '';
|
||||
formData.unloadCattleVideo = delivery.unloadCattleVideo || '';
|
||||
formData.destinationWeightVideo = delivery.destinationWeightVideo || '';
|
||||
|
||||
// 保存编辑的ID,用于区分是新增还是编辑
|
||||
formData.editId = delivery.id;
|
||||
|
||||
console.log('[EDIT-FILL] 表单数据已填充:', formData);
|
||||
ElMessage.success('已加载运单数据');
|
||||
};
|
||||
|
||||
// 加载供应商和采购方列表
|
||||
@@ -1014,6 +1109,8 @@ const handleOrderChange = async (orderId) => {
|
||||
formData.shipper = sellerId ? parseInt(sellerId) : null;
|
||||
formData.buyer = buyerId ? parseInt(buyerId) : null;
|
||||
|
||||
console.log('[订单选择] 选中的订单ID:', orderId);
|
||||
console.log('[订单选择] orderId已保存到formData.orderId:', formData.orderId);
|
||||
ElMessage.success('已自动填充发货方和采购方信息');
|
||||
}
|
||||
} catch (error) {
|
||||
@@ -1027,12 +1124,19 @@ const handleOrderChange = async (orderId) => {
|
||||
// 司机选择变化时自动填充电话
|
||||
const handleDriverChange = (driverId) => {
|
||||
if (driverId) {
|
||||
formData.driverId = driverId; // 保存司机ID用于后端查询
|
||||
const driver = driverOptions.value.find(item => item.id === driverId);
|
||||
if (driver && driver.mobile) {
|
||||
formData.driverPhone = driver.mobile;
|
||||
console.log('[司机选择] 司机ID:', driverId, ', 已自动填充手机号:', driver.mobile);
|
||||
ElMessage.success('已自动填充司机手机号');
|
||||
} else {
|
||||
console.log('[司机选择] 司机ID:', driverId, ', 但未找到手机号');
|
||||
}
|
||||
} else {
|
||||
formData.driverId = null;
|
||||
formData.driverPhone = '';
|
||||
console.log('[司机选择] 司机已清除');
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1066,7 +1170,7 @@ const handleCollarChange = (ids) => {
|
||||
}
|
||||
};
|
||||
|
||||
// 更新选中设备的delivery_id
|
||||
// 更新选中设备的delivery_id和car_number
|
||||
const updateSelectedDevicesDeliveryId = async (deliveryId) => {
|
||||
try {
|
||||
const devicesToUpdate = [];
|
||||
@@ -1082,17 +1186,18 @@ const updateSelectedDevicesDeliveryId = async (deliveryId) => {
|
||||
devicesToUpdate.push(...formData.collarDeviceIds);
|
||||
}
|
||||
|
||||
// 批量更新设备的delivery_id
|
||||
// 批量更新设备的delivery_id和car_number
|
||||
for (const deviceId of devicesToUpdate) {
|
||||
await updateDeviceDeliveryId({
|
||||
deviceId: deviceId,
|
||||
deliveryId: deliveryId
|
||||
deliveryId: deliveryId,
|
||||
carNumber: formData.plateNumber // 传递车牌号
|
||||
});
|
||||
}
|
||||
|
||||
console.log(`成功更新 ${devicesToUpdate.length} 个设备的delivery_id`);
|
||||
console.log(`成功更新 ${devicesToUpdate.length} 个设备的delivery_id和car_number: ${formData.plateNumber}`);
|
||||
} catch (error) {
|
||||
console.error('更新设备delivery_id失败:', error);
|
||||
console.error('更新设备delivery_id和car_number失败:', error);
|
||||
// 不阻止流程,只记录错误
|
||||
}
|
||||
};
|
||||
@@ -1135,24 +1240,37 @@ const handleSubmit = () => {
|
||||
}
|
||||
console.groupEnd();
|
||||
|
||||
const res = await createDelivery(submitData);
|
||||
console.group('[CREATE-DELIVERY] 响应日志');
|
||||
let res;
|
||||
// 判断是编辑还是新增
|
||||
if (formData.editId) {
|
||||
// 编辑模式:调用更新接口
|
||||
console.log('[EDIT-DELIVERY] 编辑模式,运单ID:', formData.editId);
|
||||
submitData.deliveryId = formData.editId; // 添加deliveryId字段(后端需要)
|
||||
res = await shippingApi.updateDeliveryInfo(submitData);
|
||||
} else {
|
||||
// 新增模式:调用创建接口
|
||||
console.log('[CREATE-DELIVERY] 新增模式');
|
||||
res = await createDelivery(submitData);
|
||||
}
|
||||
|
||||
console.group(formData.editId ? '[EDIT-DELIVERY] 响应日志' : '[CREATE-DELIVERY] 响应日志');
|
||||
console.log('完整响应:', res);
|
||||
console.groupEnd();
|
||||
if (res.code === 200) {
|
||||
// 获取新创建的运送清单ID
|
||||
const newDeliveryId = res.data?.id;
|
||||
|
||||
if (newDeliveryId) {
|
||||
if (res.code === 200) {
|
||||
// 获取运送清单ID(新增返回data.id,编辑直接用editId)
|
||||
const deliveryId = formData.editId || res.data?.id;
|
||||
|
||||
if (deliveryId) {
|
||||
// 更新设备的delivery_id
|
||||
await updateSelectedDevicesDeliveryId(newDeliveryId);
|
||||
await updateSelectedDevicesDeliveryId(deliveryId);
|
||||
}
|
||||
|
||||
ElMessage.success('创建成功');
|
||||
ElMessage.success(formData.editId ? '更新成功' : '创建成功');
|
||||
dialogVisible.value = false;
|
||||
emit('success');
|
||||
} else {
|
||||
ElMessage.error(res.msg || '创建失败');
|
||||
ElMessage.error(res.msg || (formData.editId ? '更新失败' : '创建失败'));
|
||||
}
|
||||
} catch (error) {
|
||||
console.group('[CREATE-DELIVERY] 异常日志');
|
||||
@@ -1353,7 +1471,7 @@ const handleClose = () => {
|
||||
formRef.value?.resetFields();
|
||||
// 清空所有表单字段
|
||||
Object.keys(formData).forEach(key => {
|
||||
if (key === 'orderId') {
|
||||
if (key === 'orderId' || key === 'editId') {
|
||||
formData[key] = null;
|
||||
} else if (typeof formData[key] === 'number') {
|
||||
formData[key] = key === 'cattleCount' ? 1 : null;
|
||||
|
||||
@@ -145,6 +145,7 @@ const getDataList = () => {
|
||||
apiCall
|
||||
.then((res) => {
|
||||
console.log('=== API 调用成功 ===', res);
|
||||
console.log('=== 原始返回数据 res.data ===', JSON.parse(JSON.stringify(res.data)));
|
||||
data.dataListLoading = false;
|
||||
if (res.code == 200) {
|
||||
let rawData = [];
|
||||
@@ -163,6 +164,9 @@ const getDataList = () => {
|
||||
console.log('=== 使用 rows 格式数据 ===', { rawData, total });
|
||||
}
|
||||
|
||||
console.log('=== rawData 原始数据数量 ===', rawData.length);
|
||||
console.log('=== rawData 详细内容 ===', JSON.parse(JSON.stringify(rawData)));
|
||||
|
||||
// 处理数据:添加设备类型和分配状态
|
||||
data.rows = rawData.map(item => {
|
||||
const processedItem = { ...item };
|
||||
|
||||
@@ -140,7 +140,6 @@ const data = reactive({
|
||||
const form = reactive({
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
licensePlate: '',
|
||||
});
|
||||
|
||||
const showAddDialog = (row) => {
|
||||
@@ -170,12 +169,12 @@ const delClick = (row) => {
|
||||
const getDataList = async () => {
|
||||
data.dataListLoading = true;
|
||||
try {
|
||||
// ✅ 使用 baseSearchRef 获取搜索参数
|
||||
const params = {
|
||||
pageNum: form.pageNum,
|
||||
pageSize: form.pageSize,
|
||||
licensePlate: form.licensePlate,
|
||||
...form,
|
||||
...baseSearchRef.value.penetrateParams(),
|
||||
};
|
||||
console.log('查询参数:', params);
|
||||
console.log('[VEHICLE-SEARCH] 查询参数:', params);
|
||||
|
||||
const res = await vehicleList(params);
|
||||
console.log('查询结果:', res);
|
||||
|
||||
157
tradeCattle/CAR_NUMBER_FIELD_REMOVAL_GUIDE.md
Normal file
157
tradeCattle/CAR_NUMBER_FIELD_REMOVAL_GUIDE.md
Normal file
@@ -0,0 +1,157 @@
|
||||
# member_driver 表 car_number 字段删除指南
|
||||
|
||||
## 🔍 问题诊断
|
||||
|
||||
### 错误现象
|
||||
```
|
||||
java.sql.SQLSyntaxErrorException: Unknown column 'md.car_number' in 'field list'
|
||||
```
|
||||
|
||||
这个错误在执行 `DeliveryMapper.insert` 时出现,但 INSERT SQL 本身并不包含 `md.car_number`。
|
||||
|
||||
### 可能原因
|
||||
|
||||
1. **数据库触发器** - delivery 表的 INSERT/UPDATE 触发器可能引用了 member_driver.car_number
|
||||
2. **数据库视图** - 某个视图可能包含 member_driver.car_number
|
||||
3. **字段仍然存在** - member_driver 表中 car_number 字段尚未被删除
|
||||
|
||||
## 📋 解决步骤
|
||||
|
||||
### 步骤 1: 执行诊断脚本
|
||||
|
||||
在 MySQL 中执行 `remove_car_number_from_member_driver.sql` 脚本的**诊断部分**:
|
||||
|
||||
```bash
|
||||
# 连接到数据库
|
||||
mysql -u root -p cattle_trade
|
||||
|
||||
# 执行诊断部分(前 46 行)
|
||||
source C:/cattleTransport/tradeCattle/remove_car_number_from_member_driver.sql
|
||||
```
|
||||
|
||||
### 步骤 2: 分析诊断结果
|
||||
|
||||
#### 2.1 检查字段是否存在
|
||||
如果看到 `car_number字段存在数量 = 1`,说明字段还在数据库中。
|
||||
|
||||
#### 2.2 检查触发器
|
||||
重点关注:
|
||||
- **delivery 表的触发器** - 可能在 INSERT 时查询 member_driver.car_number
|
||||
- **member_driver 表的触发器** - 可能在更新时引用 car_number
|
||||
|
||||
#### 2.3 检查视图
|
||||
如果有视图包含 `md.car_number`,需要先删除或修改视图。
|
||||
|
||||
### 步骤 3: 删除触发器(如果存在)
|
||||
|
||||
如果发现触发器引用 `md.car_number`,执行:
|
||||
|
||||
```sql
|
||||
-- 删除有问题的触发器
|
||||
DROP TRIGGER IF EXISTS trigger_name;
|
||||
|
||||
-- 示例:如果发现 delivery_after_insert 触发器有问题
|
||||
-- DROP TRIGGER IF EXISTS delivery_after_insert;
|
||||
```
|
||||
|
||||
### 步骤 4: 删除字段
|
||||
|
||||
确认没有依赖后,执行字段删除:
|
||||
|
||||
```sql
|
||||
-- 删除 car_number 字段
|
||||
ALTER TABLE member_driver DROP COLUMN IF EXISTS car_number;
|
||||
|
||||
-- 验证删除
|
||||
DESC member_driver;
|
||||
```
|
||||
|
||||
### 步骤 5: 重启服务
|
||||
|
||||
```powershell
|
||||
# 1. 清理编译缓存
|
||||
cd C:\cattleTransport\tradeCattle
|
||||
Remove-Item -Recurse -Force target -ErrorAction SilentlyContinue
|
||||
|
||||
# 2. 停止后端服务(在运行服务的终端按 Ctrl+C)
|
||||
|
||||
# 3. 重新启动服务
|
||||
cd aiotagro-cattle-trade
|
||||
mvn spring-boot:run
|
||||
```
|
||||
|
||||
## ⚠️ 重要注意事项
|
||||
|
||||
### 1. 数据备份
|
||||
在删除字段前,务必备份数据库:
|
||||
```sql
|
||||
-- 导出整个数据库
|
||||
mysqldump -u root -p cattle_trade > cattle_trade_backup_$(date +%Y%m%d).sql
|
||||
|
||||
-- 或只备份 member_driver 表
|
||||
mysqldump -u root -p cattle_trade member_driver > member_driver_backup_$(date +%Y%m%d).sql
|
||||
```
|
||||
|
||||
### 2. 数据迁移
|
||||
如果 member_driver 表的 car_number 字段中有重要数据,需要先迁移到 vehicle 表:
|
||||
|
||||
```sql
|
||||
-- 检查是否有数据
|
||||
SELECT id, username, car_number
|
||||
FROM member_driver
|
||||
WHERE car_number IS NOT NULL AND car_number != '';
|
||||
|
||||
-- 迁移数据到 vehicle 表(根据实际情况调整)
|
||||
-- INSERT INTO vehicle (license_plate, ...)
|
||||
-- SELECT DISTINCT car_number, ... FROM member_driver WHERE car_number IS NOT NULL;
|
||||
```
|
||||
|
||||
### 3. 代码已同步
|
||||
以下代码文件已经移除了对 `car_number` 的引用:
|
||||
- ✅ `MemberDriverMapper.java` - 所有 SQL 查询已移除 car_number
|
||||
- ✅ `MemberController.java` - 新增/更新司机时不再使用 car_number
|
||||
- ✅ `DeliveryServiceImpl.java` - 不再从司机表查询车牌号
|
||||
- ✅ `XqClientMapper.java` - 从 delivery 表获取 license_plate
|
||||
- ✅ `JbqClientMapper.xml` - 从 delivery 表获取 license_plate
|
||||
|
||||
## 🎯 预期结果
|
||||
|
||||
执行完成后:
|
||||
1. ✅ `member_driver` 表不再包含 `car_number` 字段
|
||||
2. ✅ 创建运送清单时不会报 `Unknown column 'md.car_number'` 错误
|
||||
3. ✅ 车牌号信息从 `vehicle` 表获取,通过 `delivery.license_plate` 关联
|
||||
4. ✅ 司机和车辆是完全独立的两个模块
|
||||
|
||||
## 🔧 故障排查
|
||||
|
||||
如果删除字段后仍然报错:
|
||||
|
||||
1. **清除 MyBatis 缓存**
|
||||
```powershell
|
||||
Remove-Item -Recurse -Force target
|
||||
```
|
||||
|
||||
2. **检查是否有其他地方引用**
|
||||
```bash
|
||||
# 在项目中搜索 car_number
|
||||
grep -r "car_number" tradeCattle/aiotagro-cattle-trade/src/
|
||||
```
|
||||
|
||||
3. **重启数据库连接池**
|
||||
- 完全停止 Spring Boot 应用
|
||||
- 等待 30 秒让连接池清空
|
||||
- 重新启动应用
|
||||
|
||||
## 📞 联系方式
|
||||
|
||||
如果遇到问题,请提供:
|
||||
1. 诊断脚本的完整输出
|
||||
2. 触发器的定义(如果有)
|
||||
3. 错误日志的完整堆栈跟踪
|
||||
|
||||
---
|
||||
|
||||
**文档版本**: 1.0
|
||||
**创建日期**: 2025-10-29
|
||||
**最后更新**: 2025-10-29
|
||||
|
||||
232
tradeCattle/TRIGGER_FIX_GUIDE.md
Normal file
232
tradeCattle/TRIGGER_FIX_GUIDE.md
Normal file
@@ -0,0 +1,232 @@
|
||||
# Delivery 触发器修复指南
|
||||
|
||||
## 🎯 问题确认
|
||||
|
||||
通过数据库诊断,确认了问题的根源:
|
||||
|
||||
### 诊断结果
|
||||
1. ✅ `member_driver` 表中 **car_number 字段不存在**(已被删除)
|
||||
2. ✅ `member_driver` 表 **没有触发器**
|
||||
3. ❌ **delivery 表有 2 个触发器引用了已删除的 car_number 字段**
|
||||
|
||||
### 触发器信息
|
||||
```
|
||||
触发器名称: trg_delivery_fill_from_driver
|
||||
关联表: delivery
|
||||
触发事件: INSERT, UPDATE
|
||||
创建时间: 2025-10-14 15:36:26 和 15:37:58
|
||||
```
|
||||
|
||||
## 🔍 问题分析
|
||||
|
||||
### 为什么会报错?
|
||||
|
||||
当执行 `DeliveryMapper.insert` 时:
|
||||
1. MyBatis 执行 INSERT SQL 插入 delivery 表
|
||||
2. **数据库触发器自动触发**
|
||||
3. 触发器尝试查询 `member_driver.car_number` 字段
|
||||
4. 由于字段已被删除,报错:`Unknown column 'md.car_number' in 'field list'`
|
||||
|
||||
### 触发器的原始用途
|
||||
|
||||
这些触发器的设计初衷是:
|
||||
- 当插入或更新 delivery 记录时
|
||||
- 自动从 member_driver 表获取司机的车辆信息(包括 car_number)
|
||||
- 自动填充到 delivery 表
|
||||
|
||||
### 为什么现在不需要触发器?
|
||||
|
||||
因为架构已改变:
|
||||
- **旧架构**: 司机和车辆绑定在 member_driver 表(一对一)
|
||||
- **新架构**: 司机和车辆分离,车辆由 vehicle 表独立管理(多对多)
|
||||
- **新实现**: 在 `DeliveryServiceImpl.createDelivery()` 中手动查询 vehicle 表获取车辆信息
|
||||
|
||||
## 🔧 解决方案
|
||||
|
||||
### 方案 1: 删除触发器(推荐)✅
|
||||
|
||||
**优点**:
|
||||
- 彻底解决问题
|
||||
- 与新架构一致
|
||||
- 代码已经实现了相同功能
|
||||
|
||||
**缺点**:
|
||||
- 需要确保业务逻辑在代码层面完整
|
||||
|
||||
### 方案 2: 修改触发器(不推荐)
|
||||
|
||||
修改触发器以适应新表结构,但这会增加维护复杂度。
|
||||
|
||||
## 📋 执行步骤
|
||||
|
||||
### 步骤 1: 查看触发器完整定义(可选)
|
||||
|
||||
如果你想查看触发器的完整 SQL 定义:
|
||||
|
||||
```sql
|
||||
SHOW CREATE TRIGGER trg_delivery_fill_from_driver;
|
||||
```
|
||||
|
||||
**建议**: 复制输出并保存,以防将来需要参考。
|
||||
|
||||
### 步骤 2: 删除触发器
|
||||
|
||||
连接到数据库:
|
||||
```bash
|
||||
mysql -h 129.211.213.226 -P 3306 -u root -pAiotagro@741 cattletrade
|
||||
```
|
||||
|
||||
执行删除:
|
||||
```sql
|
||||
USE cattletrade;
|
||||
|
||||
-- 删除触发器
|
||||
DROP TRIGGER IF EXISTS trg_delivery_fill_from_driver;
|
||||
|
||||
-- 验证删除
|
||||
SHOW TRIGGERS WHERE `Table` = 'delivery';
|
||||
```
|
||||
|
||||
或者直接执行脚本:
|
||||
```bash
|
||||
mysql -h 129.211.213.226 -P 3306 -u root -pAiotagro@741 cattletrade < C:/cattleTransport/tradeCattle/fix_delivery_triggers.sql
|
||||
```
|
||||
|
||||
### 步骤 3: 验证没有其他触发器引用 car_number
|
||||
|
||||
```sql
|
||||
SELECT
|
||||
TRIGGER_NAME,
|
||||
EVENT_MANIPULATION,
|
||||
EVENT_OBJECT_TABLE
|
||||
FROM INFORMATION_SCHEMA.TRIGGERS
|
||||
WHERE TRIGGER_SCHEMA = 'cattletrade'
|
||||
AND ACTION_STATEMENT LIKE '%car_number%';
|
||||
```
|
||||
|
||||
应该返回 **0 行**。
|
||||
|
||||
### 步骤 4: 重启后端服务
|
||||
|
||||
```powershell
|
||||
# 1. 停止当前服务(在服务运行的终端按 Ctrl+C)
|
||||
|
||||
# 2. 清理编译缓存
|
||||
cd C:\cattleTransport\tradeCattle
|
||||
Remove-Item -Recurse -Force target -ErrorAction SilentlyContinue
|
||||
|
||||
# 3. 重新启动服务
|
||||
cd aiotagro-cattle-trade
|
||||
mvn spring-boot:run
|
||||
```
|
||||
|
||||
### 步骤 5: 测试功能
|
||||
|
||||
1. 打开前端页面
|
||||
2. 尝试创建新的运送清单
|
||||
3. 填写表单并提交
|
||||
4. **预期结果**: 成功创建,不再报 `Unknown column 'md.car_number'` 错误
|
||||
|
||||
## ✅ 验证清单
|
||||
|
||||
完成后,确认以下几点:
|
||||
|
||||
- [ ] 数据库中 `member_driver` 表没有 `car_number` 字段
|
||||
- [ ] 数据库中 `delivery` 表没有触发器引用 `car_number`
|
||||
- [ ] 后端服务已重启
|
||||
- [ ] 创建运送清单功能正常
|
||||
- [ ] 车辆信息从 `vehicle` 表正确获取
|
||||
- [ ] 司机信息从 `member_driver` 表正确获取
|
||||
|
||||
## 🎯 预期效果
|
||||
|
||||
### 修复前
|
||||
```
|
||||
错误: Unknown column 'md.car_number' in 'field list'
|
||||
原因: delivery 表触发器查询已删除的字段
|
||||
```
|
||||
|
||||
### 修复后
|
||||
```
|
||||
✅ 运送清单创建成功
|
||||
✅ 车辆信息从 vehicle 表获取
|
||||
✅ 司机信息从 member_driver 表获取
|
||||
✅ 数据正确保存到 delivery 表
|
||||
```
|
||||
|
||||
## 📊 数据流对比
|
||||
|
||||
### 旧流程(有触发器)
|
||||
```
|
||||
1. 代码: INSERT INTO delivery
|
||||
2. 触发器: 自动查询 member_driver.car_number
|
||||
3. 触发器: 自动填充 delivery.license_plate
|
||||
```
|
||||
|
||||
### 新流程(无触发器)
|
||||
```
|
||||
1. 代码: 根据 plateNumber 查询 vehicle 表
|
||||
2. 代码: 根据 driverId 查询 member_driver 表
|
||||
3. 代码: 手动设置 delivery.license_plate
|
||||
4. 代码: INSERT INTO delivery
|
||||
```
|
||||
|
||||
## ⚠️ 注意事项
|
||||
|
||||
1. **生产环境警告**:
|
||||
- 这是生产数据库,删除触发器前请确保已备份
|
||||
- 建议在非高峰时段操作
|
||||
|
||||
2. **功能完整性**:
|
||||
- 代码中已经实现了触发器的功能
|
||||
- DeliveryServiceImpl.createDelivery() 会手动查询 vehicle 表
|
||||
|
||||
3. **回滚方案**:
|
||||
- 如果删除触发器后发现问题,可以从备份恢复
|
||||
- 建议先保存 `SHOW CREATE TRIGGER` 的输出
|
||||
|
||||
## 🔍 故障排查
|
||||
|
||||
### 如果删除触发器后仍然报错
|
||||
|
||||
1. **清除所有缓存**
|
||||
```powershell
|
||||
Remove-Item -Recurse -Force tradeCattle\target
|
||||
Remove-Item -Recurse -Force tradeCattle\.idea
|
||||
```
|
||||
|
||||
2. **检查是否有其他引用**
|
||||
```sql
|
||||
-- 检查存储过程
|
||||
SELECT ROUTINE_NAME, ROUTINE_DEFINITION
|
||||
FROM INFORMATION_SCHEMA.ROUTINES
|
||||
WHERE ROUTINE_SCHEMA = 'cattletrade'
|
||||
AND ROUTINE_DEFINITION LIKE '%car_number%';
|
||||
|
||||
-- 检查视图
|
||||
SELECT TABLE_NAME, VIEW_DEFINITION
|
||||
FROM INFORMATION_SCHEMA.VIEWS
|
||||
WHERE TABLE_SCHEMA = 'cattletrade'
|
||||
AND VIEW_DEFINITION LIKE '%car_number%';
|
||||
```
|
||||
|
||||
3. **重启数据库连接**
|
||||
- 完全停止后端服务
|
||||
- 等待 30 秒
|
||||
- 重新启动
|
||||
|
||||
## 📞 支持
|
||||
|
||||
如果遇到问题,请提供:
|
||||
1. `SHOW CREATE TRIGGER` 的完整输出
|
||||
2. 删除触发器后的验证结果
|
||||
3. 后端服务重启后的日志
|
||||
4. 创建运送清单时的错误信息(如果还有)
|
||||
|
||||
---
|
||||
|
||||
**文档版本**: 1.0
|
||||
**创建日期**: 2025-10-29
|
||||
**最后更新**: 2025-10-29
|
||||
**状态**: ✅ 问题已确认,解决方案已就绪
|
||||
|
||||
@@ -26,6 +26,11 @@ import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.*;
|
||||
import java.net.URL;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.Date;
|
||||
@@ -33,6 +38,8 @@ import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipOutputStream;
|
||||
|
||||
/**
|
||||
* 运送清单表 前端控制器
|
||||
@@ -554,19 +561,39 @@ public class DeliveryController {
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新运单状态
|
||||
* 修改运送清单状态
|
||||
* 状态值:1-准备中,2-运输中,3-已结束
|
||||
*/
|
||||
@SaCheckPermission("delivery:status")
|
||||
@SaCheckPermission("delivery:edit")
|
||||
@PostMapping("/updateStatus")
|
||||
public AjaxResult updateStatus(@RequestBody Map<String, Object> params) {
|
||||
try {
|
||||
Integer id = (Integer) params.get("id");
|
||||
Integer status = (Integer) params.get("status");
|
||||
|
||||
System.out.println("=== 修改运送清单状态 ===");
|
||||
System.out.println("运单ID: " + id);
|
||||
System.out.println("新状态: " + status);
|
||||
|
||||
if (id == null || status == null) {
|
||||
System.out.println("ERROR: 运单ID或状态为空");
|
||||
return AjaxResult.error("运单ID和状态不能为空");
|
||||
}
|
||||
|
||||
if (status < 1 || status > 3) {
|
||||
System.out.println("ERROR: 状态值无效: " + status);
|
||||
return AjaxResult.error("状态值无效,只能为1(准备中)、2(运输中)、3(已结束)");
|
||||
}
|
||||
|
||||
// 查询运单是否存在
|
||||
Delivery existDelivery = deliveryService.getById(id);
|
||||
if (existDelivery == null) {
|
||||
System.out.println("ERROR: 运单不存在,ID: " + id);
|
||||
return AjaxResult.error("运单不存在");
|
||||
}
|
||||
|
||||
System.out.println("原状态: " + existDelivery.getStatus());
|
||||
|
||||
Delivery delivery = new Delivery();
|
||||
delivery.setId(id);
|
||||
delivery.setStatus(status);
|
||||
@@ -574,18 +601,22 @@ public class DeliveryController {
|
||||
boolean success = deliveryService.updateById(delivery);
|
||||
|
||||
if (success) {
|
||||
String statusText = status == 1 ? "准备中" : (status == 2 ? "运输中" : "已结束");
|
||||
System.out.println("SUCCESS: 状态更新成功,新状态: " + statusText);
|
||||
return AjaxResult.success("状态更新成功");
|
||||
} else {
|
||||
System.out.println("ERROR: 状态更新失败");
|
||||
return AjaxResult.error("状态更新失败");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
System.out.println("ERROR: 状态更新异常 - " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
return AjaxResult.error("状态更新失败:" + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除装车订单
|
||||
* 删除装车订单(物理删除)
|
||||
* 删除订单时同时清空关联设备的delivery_id和weight
|
||||
*/
|
||||
@SaCheckPermission("delivery:delete")
|
||||
@@ -596,15 +627,20 @@ public class DeliveryController {
|
||||
return AjaxResult.error("订单ID不能为空");
|
||||
}
|
||||
|
||||
System.out.println("=== 物理删除装车订单 ===");
|
||||
System.out.println("订单ID: " + id);
|
||||
|
||||
// 查询订单是否存在
|
||||
Delivery delivery = deliveryService.getById(id);
|
||||
if (delivery == null) {
|
||||
System.out.println("ERROR: 订单不存在");
|
||||
return AjaxResult.error("订单不存在");
|
||||
}
|
||||
|
||||
// 权限检查:只有创建者或超级管理员可以删除
|
||||
Integer userId = SecurityUtil.getCurrentUserId();
|
||||
if (!SecurityUtil.isSuperAdmin() && !userId.equals(delivery.getCreatedBy())) {
|
||||
System.out.println("ERROR: 无权限删除,当前用户: " + userId + ", 创建者: " + delivery.getCreatedBy());
|
||||
return AjaxResult.error("无权限删除此订单");
|
||||
}
|
||||
|
||||
@@ -618,6 +654,7 @@ public class DeliveryController {
|
||||
for (com.aiotagro.cattletrade.business.entity.IotDeviceData device : devices) {
|
||||
device.setDeliveryId(null);
|
||||
device.setWeight(null);
|
||||
device.setCarNumber(null);
|
||||
iotDeviceDataMapper.updateById(device);
|
||||
updatedCount++;
|
||||
}
|
||||
@@ -628,14 +665,249 @@ public class DeliveryController {
|
||||
boolean deleted = deliveryService.removeById(id);
|
||||
|
||||
if (deleted) {
|
||||
System.out.println("SUCCESS: 订单删除成功");
|
||||
return AjaxResult.success("订单删除成功,已清空 " + updatedCount + " 个设备的绑定信息");
|
||||
} else {
|
||||
System.out.println("ERROR: 订单删除失败");
|
||||
return AjaxResult.error("订单删除失败");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
System.out.println("ERROR: 删除订单异常 - " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
return AjaxResult.error("删除订单失败:" + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 逻辑删除运送清单
|
||||
* 只标记为已删除,不清空设备绑定关系,保留历史记录
|
||||
*/
|
||||
@SaCheckPermission("delivery:delete")
|
||||
@PostMapping("/deleteLogic")
|
||||
public AjaxResult deleteLogic(@RequestParam Integer id) {
|
||||
try {
|
||||
if (id == null) {
|
||||
return AjaxResult.error("运单ID不能为空");
|
||||
}
|
||||
|
||||
System.out.println("=== 逻辑删除运送清单 ===");
|
||||
System.out.println("运单ID: " + id);
|
||||
|
||||
// 查询订单是否存在
|
||||
Delivery delivery = deliveryService.getById(id);
|
||||
if (delivery == null) {
|
||||
System.out.println("ERROR: 运单不存在");
|
||||
return AjaxResult.error("运单不存在");
|
||||
}
|
||||
|
||||
// 权限检查:只有创建者或超级管理员可以删除
|
||||
Integer userId = SecurityUtil.getCurrentUserId();
|
||||
if (!SecurityUtil.isSuperAdmin() && !userId.equals(delivery.getCreatedBy())) {
|
||||
System.out.println("ERROR: 无权限删除,当前用户: " + userId + ", 创建者: " + delivery.getCreatedBy());
|
||||
return AjaxResult.error("无权限删除此运单");
|
||||
}
|
||||
|
||||
System.out.println("运单号: " + delivery.getDeliveryNumber());
|
||||
|
||||
// 使用 MyBatis-Plus 的逻辑删除功能
|
||||
boolean deleted = deliveryService.removeById(id);
|
||||
|
||||
if (deleted) {
|
||||
System.out.println("SUCCESS: 运单逻辑删除成功");
|
||||
return AjaxResult.success("运单删除成功");
|
||||
} else {
|
||||
System.out.println("ERROR: 运单逻辑删除失败");
|
||||
return AjaxResult.error("运单删除失败");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
System.out.println("ERROR: 逻辑删除运单异常 - " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
return AjaxResult.error("删除运单失败:" + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 打包下载运送清单文件(图片、视频、信息)
|
||||
* 包含所有相关的照片、视频和详细信息
|
||||
*/
|
||||
@SaCheckPermission("delivery:view")
|
||||
@GetMapping("/downloadPackage")
|
||||
public void downloadPackage(@RequestParam Integer id, HttpServletResponse response) {
|
||||
System.out.println("=== 打包下载运送清单文件 ===");
|
||||
System.out.println("运单ID: " + id);
|
||||
|
||||
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
|
||||
ZipOutputStream zipOut = null;
|
||||
|
||||
try {
|
||||
if (id == null) {
|
||||
response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
|
||||
response.getWriter().write("运单ID不能为空");
|
||||
return;
|
||||
}
|
||||
|
||||
// 查询运单详情
|
||||
Delivery delivery = deliveryService.getById(id);
|
||||
if (delivery == null) {
|
||||
System.out.println("ERROR: 运单不存在");
|
||||
response.setStatus(HttpServletResponse.SC_NOT_FOUND);
|
||||
response.getWriter().write("运单不存在");
|
||||
return;
|
||||
}
|
||||
|
||||
System.out.println("运单号: " + delivery.getDeliveryNumber());
|
||||
|
||||
zipOut = new ZipOutputStream(byteArrayOutputStream);
|
||||
zipOut.setLevel(9); // 设置压缩级别
|
||||
|
||||
// 1. 创建信息文本文件
|
||||
addDeliveryInfoToZip(zipOut, delivery);
|
||||
|
||||
// 2. 下载并添加所有照片
|
||||
int photoCount = 0;
|
||||
photoCount += addFileToZip(zipOut, delivery.getQuarantineTickeyUrl(), "照片/检疫票.jpg");
|
||||
photoCount += addFileToZip(zipOut, delivery.getPoundListImg(), "照片/纸质磅单.jpg");
|
||||
photoCount += addFileToZip(zipOut, delivery.getEmptyVehicleFrontPhoto(), "照片/空车过磅车头照片.jpg");
|
||||
photoCount += addFileToZip(zipOut, delivery.getLoadedVehicleFrontPhoto(), "照片/装车过磅车头照片.jpg");
|
||||
photoCount += addFileToZip(zipOut, delivery.getLoadedVehicleWeightPhoto(), "照片/装车过磅磅单.jpg");
|
||||
photoCount += addFileToZip(zipOut, delivery.getCarFrontPhoto(), "照片/车头照片.jpg");
|
||||
photoCount += addFileToZip(zipOut, delivery.getCarBehindPhoto(), "照片/车尾照片.jpg");
|
||||
photoCount += addFileToZip(zipOut, delivery.getDriverIdCardPhoto(), "照片/司机身份证照片.jpg");
|
||||
photoCount += addFileToZip(zipOut, delivery.getDestinationPoundListImg(), "照片/到地磅单.jpg");
|
||||
photoCount += addFileToZip(zipOut, delivery.getDestinationVehicleFrontPhoto(), "照片/到地车辆过重磅车头照片.jpg");
|
||||
|
||||
System.out.println("成功添加照片数量: " + photoCount);
|
||||
|
||||
// 3. 下载并添加所有视频
|
||||
int videoCount = 0;
|
||||
videoCount += addFileToZip(zipOut, delivery.getEmptyWeightVideo(), "视频/空车过磅视频.mp4");
|
||||
videoCount += addFileToZip(zipOut, delivery.getEntruckWeightVideo(), "视频/装车过磅视频.mp4");
|
||||
videoCount += addFileToZip(zipOut, delivery.getEntruckVideo(), "视频/装车视频.mp4");
|
||||
videoCount += addFileToZip(zipOut, delivery.getControlSlotVideo(), "视频/消毒槽视频.mp4");
|
||||
videoCount += addFileToZip(zipOut, delivery.getCattleLoadingCircleVideo(), "视频/牛只装车环视视频.mp4");
|
||||
videoCount += addFileToZip(zipOut, delivery.getUnloadCattleVideo(), "视频/卸牛视频.mp4");
|
||||
videoCount += addFileToZip(zipOut, delivery.getDestinationWeightVideo(), "视频/到地过磅视频.mp4");
|
||||
|
||||
System.out.println("成功添加视频数量: " + videoCount);
|
||||
|
||||
zipOut.finish();
|
||||
zipOut.close();
|
||||
|
||||
// 设置响应头
|
||||
byte[] zipBytes = byteArrayOutputStream.toByteArray();
|
||||
String fileName = "运送清单_" + delivery.getDeliveryNumber() + "_" + System.currentTimeMillis() + ".zip";
|
||||
String encodedFileName = URLEncoder.encode(fileName, "UTF-8").replaceAll("\\+", "%20");
|
||||
|
||||
response.setContentType("application/zip");
|
||||
response.setHeader("Content-Disposition", "attachment; filename=" + encodedFileName);
|
||||
response.setContentLength(zipBytes.length);
|
||||
|
||||
OutputStream out = response.getOutputStream();
|
||||
out.write(zipBytes);
|
||||
out.flush();
|
||||
out.close();
|
||||
|
||||
System.out.println("SUCCESS: 文件打包成功,大小: " + zipBytes.length + " bytes");
|
||||
|
||||
} catch (Exception e) {
|
||||
System.out.println("ERROR: 打包文件异常 - " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
try {
|
||||
response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
|
||||
response.getWriter().write("打包文件失败:" + e.getMessage());
|
||||
} catch (IOException ioException) {
|
||||
ioException.printStackTrace();
|
||||
}
|
||||
} finally {
|
||||
try {
|
||||
if (zipOut != null) {
|
||||
zipOut.close();
|
||||
}
|
||||
byteArrayOutputStream.close();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加运送清单信息到ZIP文件
|
||||
*/
|
||||
private void addDeliveryInfoToZip(ZipOutputStream zipOut, Delivery delivery) throws IOException {
|
||||
StringBuilder info = new StringBuilder();
|
||||
info.append("====== 运送清单详细信息 ======\n\n");
|
||||
info.append("【基础信息】\n");
|
||||
info.append("运单号: ").append(delivery.getDeliveryNumber()).append("\n");
|
||||
info.append("车牌号: ").append(delivery.getLicensePlate()).append("\n");
|
||||
info.append("司机姓名: ").append(delivery.getDriverName()).append("\n");
|
||||
info.append("司机手机: ").append(delivery.getDriverMobile()).append("\n");
|
||||
info.append("起始地: ").append(delivery.getStartLocation()).append("\n");
|
||||
info.append("目的地: ").append(delivery.getEndLocation()).append("\n");
|
||||
info.append("预计送达时间: ").append(delivery.getEstimatedDeliveryTime()).append("\n");
|
||||
info.append("创建时间: ").append(delivery.getCreateTime()).append("\n\n");
|
||||
|
||||
info.append("【重量信息】\n");
|
||||
info.append("空车过磅重量: ").append(delivery.getEmptyWeight()).append(" kg\n");
|
||||
info.append("装车过磅重量: ").append(delivery.getEntruckWeight()).append(" kg\n");
|
||||
info.append("落地过磅重量: ").append(delivery.getLandingEntruckWeight()).append(" kg\n\n");
|
||||
|
||||
info.append("【状态信息】\n");
|
||||
String statusText = delivery.getStatus() == 1 ? "准备中" : (delivery.getStatus() == 2 ? "运输中" : "已结束");
|
||||
info.append("运输状态: ").append(statusText).append("\n\n");
|
||||
|
||||
info.append("====== 文件生成时间: ").append(LocalDateTime.now()).append(" ======\n");
|
||||
|
||||
ZipEntry entry = new ZipEntry("运送清单信息.txt");
|
||||
zipOut.putNextEntry(entry);
|
||||
zipOut.write(info.toString().getBytes(StandardCharsets.UTF_8));
|
||||
zipOut.closeEntry();
|
||||
|
||||
System.out.println("成功添加信息文本文件");
|
||||
}
|
||||
|
||||
/**
|
||||
* 从URL下载文件并添加到ZIP
|
||||
* @return 成功添加返回1,失败或URL为空返回0
|
||||
*/
|
||||
private int addFileToZip(ZipOutputStream zipOut, String fileUrl, String fileName) {
|
||||
if (fileUrl == null || fileUrl.trim().isEmpty()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
InputStream inputStream = null;
|
||||
try {
|
||||
System.out.println("下载文件: " + fileName + " <- " + fileUrl);
|
||||
|
||||
URL url = new URL(fileUrl);
|
||||
inputStream = url.openStream();
|
||||
|
||||
ZipEntry entry = new ZipEntry(fileName);
|
||||
zipOut.putNextEntry(entry);
|
||||
|
||||
byte[] buffer = new byte[8192];
|
||||
int bytesRead;
|
||||
int totalBytes = 0;
|
||||
while ((bytesRead = inputStream.read(buffer)) != -1) {
|
||||
zipOut.write(buffer, 0, bytesRead);
|
||||
totalBytes += bytesRead;
|
||||
}
|
||||
|
||||
zipOut.closeEntry();
|
||||
System.out.println("成功添加文件: " + fileName + ", 大小: " + totalBytes + " bytes");
|
||||
return 1;
|
||||
|
||||
} catch (Exception e) {
|
||||
System.out.println("WARNING: 下载文件失败: " + fileName + " - " + e.getMessage());
|
||||
return 0;
|
||||
} finally {
|
||||
if (inputStream != null) {
|
||||
try {
|
||||
inputStream.close();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -341,7 +341,7 @@ public class DeliveryDeviceController {
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新设备delivery_id和weight
|
||||
* 更新设备delivery_id、weight和car_number
|
||||
*/
|
||||
@SaCheckPermission("delivery:view")
|
||||
@PostMapping(value = "/updateDeviceDeliveryId")
|
||||
@@ -349,16 +349,18 @@ public class DeliveryDeviceController {
|
||||
String deviceId = (String) params.get("deviceId");
|
||||
Integer deliveryId = (Integer) params.get("deliveryId");
|
||||
Double weight = params.get("weight") != null ? Double.valueOf(params.get("weight").toString()) : null;
|
||||
String carNumber = (String) params.get("carNumber");
|
||||
|
||||
if (deviceId == null || deviceId.trim().isEmpty()) {
|
||||
return AjaxResult.error("设备ID不能为空");
|
||||
}
|
||||
|
||||
try {
|
||||
System.out.println("=== 更新设备delivery_id和weight ===");
|
||||
System.out.println("=== 更新设备delivery_id、weight和car_number ===");
|
||||
System.out.println("设备ID: " + deviceId);
|
||||
System.out.println("订单ID: " + deliveryId);
|
||||
System.out.println("重量: " + weight);
|
||||
System.out.println("车牌号: " + carNumber);
|
||||
|
||||
// 查询设备
|
||||
QueryWrapper<IotDeviceData> queryWrapper = new QueryWrapper<>();
|
||||
@@ -376,6 +378,9 @@ public class DeliveryDeviceController {
|
||||
// 设置delivery_id(可以是null)
|
||||
updateWrapper.set(IotDeviceData::getDeliveryId, deliveryId);
|
||||
|
||||
// 设置car_number(可以是null)
|
||||
updateWrapper.set(IotDeviceData::getCarNumber, carNumber);
|
||||
|
||||
// 设置weight(如果有值)
|
||||
if (weight != null) {
|
||||
updateWrapper.set(IotDeviceData::getWeight, weight);
|
||||
@@ -387,7 +392,7 @@ public class DeliveryDeviceController {
|
||||
int result = iotDeviceDataMapper.update(null, updateWrapper);
|
||||
|
||||
if (result > 0) {
|
||||
System.out.println("设备更新成功: " + deviceId);
|
||||
System.out.println("设备更新成功: " + deviceId + ", delivery_id=" + deliveryId + ", car_number=" + carNumber);
|
||||
return AjaxResult.success("设备更新成功");
|
||||
} else {
|
||||
return AjaxResult.error("设备更新失败");
|
||||
@@ -451,7 +456,7 @@ public class DeliveryDeviceController {
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空设备delivery_id
|
||||
* 清空设备delivery_id、car_number和weight
|
||||
*/
|
||||
@SaCheckPermission("delivery:view")
|
||||
@PostMapping(value = "/clearDeliveryId")
|
||||
@@ -463,7 +468,7 @@ public class DeliveryDeviceController {
|
||||
}
|
||||
|
||||
try {
|
||||
System.out.println("=== 清空设备delivery_id和weight ===");
|
||||
System.out.println("=== 清空设备delivery_id、car_number和weight ===");
|
||||
System.out.println("订单ID: " + deliveryId);
|
||||
|
||||
// 查询所有关联该deliveryId的设备
|
||||
@@ -473,19 +478,23 @@ public class DeliveryDeviceController {
|
||||
|
||||
int updatedCount = 0;
|
||||
for (IotDeviceData device : devices) {
|
||||
System.out.println("清空设备: " + device.getDeviceId() + ", 原delivery_id: " + device.getDeliveryId() + ", 原weight: " + device.getWeight());
|
||||
// 将delivery_id和weight都设置为null
|
||||
System.out.println("清空设备: " + device.getDeviceId() +
|
||||
", 原delivery_id: " + device.getDeliveryId() +
|
||||
", 原car_number: " + device.getCarNumber() +
|
||||
", 原weight: " + device.getWeight());
|
||||
// 将delivery_id、car_number和weight都设置为null
|
||||
device.setDeliveryId(null);
|
||||
device.setCarNumber(null); // 清空车牌号
|
||||
device.setWeight(null); // 同时清空weight字段
|
||||
iotDeviceDataMapper.updateById(device);
|
||||
updatedCount++;
|
||||
}
|
||||
|
||||
System.out.println("清空完成,共处理 " + updatedCount + " 个设备");
|
||||
return AjaxResult.success("操作成功", "已清空 " + updatedCount + " 个设备的delivery_id和weight");
|
||||
return AjaxResult.success("操作成功", "已清空 " + updatedCount + " 个设备的delivery_id、car_number和weight");
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return AjaxResult.error("清空设备delivery_id和weight失败:" + e.getMessage());
|
||||
return AjaxResult.error("清空设备delivery_id、car_number和weight失败:" + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -50,6 +50,15 @@ public class IotDeviceProxyController {
|
||||
// 构建查询条件
|
||||
QueryWrapper<IotDeviceData> queryWrapper = new QueryWrapper<>();
|
||||
|
||||
// 根据设备类型查询(用于创建运送清单时过滤设备)
|
||||
if (params.containsKey("type") && params.get("type") != null) {
|
||||
Integer deviceType = (Integer) params.get("type");
|
||||
queryWrapper.eq("device_type", deviceType);
|
||||
// 创建运送清单时,只显示未绑定的设备(delivery_id为空)
|
||||
queryWrapper.isNull("delivery_id");
|
||||
logger.info("查询未绑定的设备,类型: {}", deviceType);
|
||||
}
|
||||
|
||||
// 根据设备ID查询
|
||||
if (params.containsKey("deviceId") && params.get("deviceId") != null &&
|
||||
!String.valueOf(params.get("deviceId")).trim().isEmpty()) {
|
||||
|
||||
@@ -111,7 +111,6 @@ public class MemberController {
|
||||
// 获取参数值
|
||||
String username = (String) params.get("username");
|
||||
String mobile = (String) params.get("mobile");
|
||||
String carNumber = (String) params.get("carNumber");
|
||||
String driverLicense = (String) params.get("driverLicense");
|
||||
String drivingLicense = (String) params.get("drivingLicense");
|
||||
String carImg = (String) params.get("carImg");
|
||||
@@ -146,8 +145,8 @@ public class MemberController {
|
||||
return AjaxResult.error("获取新司机ID失败");
|
||||
}
|
||||
|
||||
// 插入member_driver表
|
||||
int driverResult = memberDriverMapper.insertDriver(memberId, username, carNumber,
|
||||
// 插入member_driver表(不再包含carNumber参数)
|
||||
int driverResult = memberDriverMapper.insertDriver(memberId, username,
|
||||
driverLicense, drivingLicense, carImg, recordCode, idCard, remark);
|
||||
if (driverResult <= 0) {
|
||||
return AjaxResult.error("司机详细信息插入失败");
|
||||
@@ -254,7 +253,6 @@ public class MemberController {
|
||||
|
||||
// 获取参数值
|
||||
String username = (String) params.get("username");
|
||||
String carNumber = (String) params.get("carNumber");
|
||||
String driverLicense = (String) params.get("driverLicense");
|
||||
String drivingLicense = (String) params.get("drivingLicense");
|
||||
String carImg = (String) params.get("carImg");
|
||||
@@ -262,8 +260,8 @@ public class MemberController {
|
||||
String idCard = (String) params.get("idCard");
|
||||
String remark = (String) params.get("remark");
|
||||
|
||||
// 执行更新
|
||||
int result = memberDriverMapper.updateDriver(id, username, carNumber, driverLicense,
|
||||
// 执行更新(不再包含carNumber参数)
|
||||
int result = memberDriverMapper.updateDriver(id, username, driverLicense,
|
||||
drivingLicense, carImg, recordCode, idCard, remark);
|
||||
|
||||
if (result > 0) {
|
||||
|
||||
@@ -18,16 +18,14 @@ public class DeliveryCreateDto {
|
||||
private Integer orderId;
|
||||
|
||||
/**
|
||||
* 发货方
|
||||
* 发货方ID
|
||||
*/
|
||||
@NotBlank(message = "发货方不能为空")
|
||||
private String shipper;
|
||||
private Integer shipperId;
|
||||
|
||||
/**
|
||||
* 采购方
|
||||
* 采购方ID
|
||||
*/
|
||||
@NotBlank(message = "采购方不能为空")
|
||||
private String buyer;
|
||||
private Integer buyerId;
|
||||
|
||||
/**
|
||||
* 车牌号
|
||||
@@ -35,6 +33,11 @@ public class DeliveryCreateDto {
|
||||
@NotBlank(message = "车牌号不能为空")
|
||||
private String plateNumber;
|
||||
|
||||
/**
|
||||
* 司机ID(从司机表查询)
|
||||
*/
|
||||
private Integer driverId;
|
||||
|
||||
/**
|
||||
* 司机姓名
|
||||
*/
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package com.aiotagro.cattletrade.business.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.apache.ibatis.annotations.CacheNamespace;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.apache.ibatis.annotations.Select;
|
||||
@@ -15,12 +15,13 @@ import java.util.Map;
|
||||
* @date 2025-01-16
|
||||
*/
|
||||
@Mapper
|
||||
public interface MemberDriverMapper extends BaseMapper<Map<String, Object>> {
|
||||
@CacheNamespace(flushInterval = 0) // Disable cache to force fresh queries
|
||||
public interface MemberDriverMapper {
|
||||
|
||||
/**
|
||||
* 查询司机列表(关联member表获取手机号)
|
||||
*/
|
||||
@Select("SELECT md.id, md.member_id, md.username, md.car_number, " +
|
||||
@Select("SELECT md.id, md.member_id, md.username, " +
|
||||
"md.driving_license, md.driver_license, md.record_code, " +
|
||||
"md.car_img, md.id_card, md.remark, md.create_time, m.mobile, m.status " +
|
||||
"FROM member_driver md " +
|
||||
@@ -32,7 +33,7 @@ public interface MemberDriverMapper extends BaseMapper<Map<String, Object>> {
|
||||
* 根据用户名搜索司机列表(关联member表获取手机号)
|
||||
*/
|
||||
@Select("<script>" +
|
||||
"SELECT md.id, md.member_id, md.username, md.car_number, " +
|
||||
"SELECT md.id, md.member_id, md.username, " +
|
||||
"md.driving_license, md.driver_license, md.record_code, " +
|
||||
"md.car_img, md.id_card, md.remark, md.create_time, m.mobile, m.status " +
|
||||
"FROM member_driver md " +
|
||||
@@ -47,7 +48,7 @@ public interface MemberDriverMapper extends BaseMapper<Map<String, Object>> {
|
||||
/**
|
||||
* 根据用户名和手机号搜索司机列表(支持精确查询)
|
||||
*/
|
||||
@Select("SELECT md.id, md.member_id, md.username, md.car_number, " +
|
||||
@Select("SELECT md.id, md.member_id, md.username, " +
|
||||
"md.driving_license, md.driver_license, md.record_code, " +
|
||||
"md.car_img, md.id_card, md.remark, md.create_time, m.mobile, m.status " +
|
||||
"FROM member_driver md " +
|
||||
@@ -60,7 +61,7 @@ public interface MemberDriverMapper extends BaseMapper<Map<String, Object>> {
|
||||
* 根据用户名和手机号搜索司机列表(支持精确查询)
|
||||
*/
|
||||
@Select("<script>" +
|
||||
"SELECT md.id, md.member_id, md.username, md.car_number, " +
|
||||
"SELECT md.id, md.member_id, md.username, " +
|
||||
"md.driving_license, md.driver_license, md.record_code, " +
|
||||
"md.car_img, md.id_card, md.remark, md.create_time, m.mobile, m.status " +
|
||||
"FROM member_driver md " +
|
||||
@@ -81,7 +82,7 @@ public interface MemberDriverMapper extends BaseMapper<Map<String, Object>> {
|
||||
/**
|
||||
* 根据司机ID查询司机信息(关联member表获取手机号)
|
||||
*/
|
||||
@Select("SELECT md.id, md.member_id, md.username, md.car_number, " +
|
||||
@Select("SELECT md.id, md.member_id, md.username, " +
|
||||
"md.driving_license, md.driver_license, md.record_code, " +
|
||||
"md.car_img, md.id_card, md.remark, md.create_time, m.mobile, m.status " +
|
||||
"FROM member_driver md " +
|
||||
@@ -90,48 +91,50 @@ public interface MemberDriverMapper extends BaseMapper<Map<String, Object>> {
|
||||
Map<String, Object> selectDriverById(@Param("driverId") Integer driverId);
|
||||
|
||||
/**
|
||||
* 根据车牌号查询司机信息
|
||||
* 根据车牌号查询司机信息(已废弃:司机表不再有车牌号字段)
|
||||
* 该方法保留是为了兼容性,但会返回 null
|
||||
*/
|
||||
@Select("SELECT md.id, md.member_id, md.username, md.car_number, " +
|
||||
@Select("SELECT md.id, md.member_id, md.username, " +
|
||||
"md.driving_license, md.driver_license, md.record_code, " +
|
||||
"md.car_img, md.id_card, md.remark, md.create_time, m.mobile, m.status " +
|
||||
"FROM member_driver md " +
|
||||
"LEFT JOIN member m ON md.member_id = m.id " +
|
||||
"WHERE md.car_number = #{licensePlate} " +
|
||||
"WHERE 1=0 " +
|
||||
"ORDER BY md.create_time DESC LIMIT 1")
|
||||
Map<String, Object> selectDriverByPlate(@Param("licensePlate") String licensePlate);
|
||||
|
||||
/**
|
||||
* 新增司机信息
|
||||
*/
|
||||
@org.apache.ibatis.annotations.Insert("INSERT INTO member_driver (member_id, username, car_number, " +
|
||||
@org.apache.ibatis.annotations.Insert("INSERT INTO member_driver (member_id, username, " +
|
||||
"driver_license, driving_license, car_img, record_code, id_card, remark, create_time) " +
|
||||
"VALUES (#{memberId}, #{username}, #{carNumber}, #{driverLicense}, #{drivingLicense}, " +
|
||||
"VALUES (#{memberId}, #{username}, #{driverLicense}, #{drivingLicense}, " +
|
||||
"#{carImg}, #{recordCode}, #{idCard}, #{remark}, NOW())")
|
||||
int insertDriver(@Param("memberId") Integer memberId, @Param("username") String username,
|
||||
@Param("carNumber") String carNumber, @Param("driverLicense") String driverLicense,
|
||||
@Param("driverLicense") String driverLicense,
|
||||
@Param("drivingLicense") String drivingLicense, @Param("carImg") String carImg,
|
||||
@Param("recordCode") String recordCode, @Param("idCard") String idCard, @Param("remark") String remark);
|
||||
|
||||
/**
|
||||
* 更新司机信息
|
||||
*/
|
||||
@org.apache.ibatis.annotations.Update("UPDATE member_driver SET username = #{username}, car_number = #{carNumber}, " +
|
||||
@org.apache.ibatis.annotations.Update("UPDATE member_driver SET username = #{username}, " +
|
||||
"driver_license = #{driverLicense}, driving_license = #{drivingLicense}, car_img = #{carImg}, " +
|
||||
"record_code = #{recordCode}, id_card = #{idCard}, remark = #{remark} WHERE id = #{id}")
|
||||
int updateDriver(@Param("id") Integer id, @Param("username") String username, @Param("carNumber") String carNumber,
|
||||
int updateDriver(@Param("id") Integer id, @Param("username") String username,
|
||||
@Param("driverLicense") String driverLicense, @Param("drivingLicense") String drivingLicense,
|
||||
@Param("carImg") String carImg, @Param("recordCode") String recordCode, @Param("idCard") String idCard, @Param("remark") String remark);
|
||||
|
||||
/**
|
||||
* 根据司机姓名和车牌号查询司机信息(包含车身照片)
|
||||
* 根据司机姓名和车牌号查询司机信息(已废弃:司机表不再有车牌号字段)
|
||||
* 该方法保留是为了兼容性,但会返回 null
|
||||
*/
|
||||
@Select("SELECT md.id, md.member_id, md.username, md.car_number, " +
|
||||
@Select("SELECT md.id, md.member_id, md.username, " +
|
||||
"md.driving_license, md.driver_license, md.record_code, " +
|
||||
"md.car_img, md.id_card, md.remark, md.create_time, m.mobile " +
|
||||
"FROM member_driver md " +
|
||||
"LEFT JOIN member m ON md.member_id = m.id " +
|
||||
"WHERE md.username = #{driverName} AND md.car_number = #{licensePlate}")
|
||||
"WHERE 1=0")
|
||||
Map<String, Object> selectDriverByNameAndPlate(@Param("driverName") String driverName,
|
||||
@Param("licensePlate") String licensePlate);
|
||||
|
||||
|
||||
@@ -30,14 +30,13 @@ public interface XqClientMapper extends BaseMapper<XqClient> {
|
||||
" xc.vehicle_id, xc.evening_frequency, xc.tenant_id, " +
|
||||
" COALESCE(dd.delivery_id, 0) AS delivery_id, " +
|
||||
" COALESCE(d.delivery_number, '未分配') AS delivery_number, " +
|
||||
" COALESCE(d.license_plate, md.car_number, '未分配') AS license_plate " +
|
||||
" COALESCE(d.license_plate, '未分配') AS license_plate " +
|
||||
"FROM xq_client xc " +
|
||||
"LEFT JOIN ( " +
|
||||
" SELECT device_id, delivery_id, ROW_NUMBER() OVER (PARTITION BY device_id ORDER BY id DESC) AS rn " +
|
||||
" FROM delivery_device WHERE device_type = 3 " +
|
||||
") dd ON CONCAT(xc.sn, '') = dd.device_id AND dd.rn = 1 " +
|
||||
"LEFT JOIN delivery d ON dd.delivery_id = d.id " +
|
||||
"LEFT JOIN member_driver md ON xc.vehicle_id = md.id " +
|
||||
"WHERE (#{sn} IS NULL OR #{sn} = '' OR xc.sn LIKE CONCAT('%', #{sn}, '%')) " +
|
||||
" AND (#{startNo} IS NULL OR #{startNo} = '' OR xc.sn >= #{startNo}) " +
|
||||
" AND (#{endNo} IS NULL OR #{endNo} = '' OR xc.sn <= #{endNo}) " +
|
||||
|
||||
@@ -66,6 +66,8 @@ public class DeliveryServiceImpl extends ServiceImpl<DeliveryMapper, Delivery> i
|
||||
private IXqClientService xqClientService;
|
||||
@Autowired
|
||||
private com.aiotagro.cattletrade.business.mapper.OrderMapper orderMapper;
|
||||
@Autowired
|
||||
private com.aiotagro.cattletrade.business.mapper.VehicleMapper vehicleMapper;
|
||||
|
||||
|
||||
/**
|
||||
@@ -508,8 +510,8 @@ public class DeliveryServiceImpl extends ServiceImpl<DeliveryMapper, Delivery> i
|
||||
@Override
|
||||
public AjaxResult createDelivery(DeliveryCreateDto dto) {
|
||||
// 入参日志
|
||||
System.out.println("[CREATE-DELIVERY] 收到DTO - shipper: " + dto.getShipper() +
|
||||
", buyer: " + dto.getBuyer() +
|
||||
System.out.println("[CREATE-DELIVERY] 收到DTO - shipperId: " + dto.getShipperId() +
|
||||
", buyerId: " + dto.getBuyerId() +
|
||||
", plateNumber: " + dto.getPlateNumber() +
|
||||
", driverName: " + dto.getDriverName());
|
||||
// 校验时间逻辑
|
||||
@@ -531,8 +533,57 @@ public class DeliveryServiceImpl extends ServiceImpl<DeliveryMapper, Delivery> i
|
||||
delivery.setOrderId(dto.getOrderId());
|
||||
// 基本信息
|
||||
delivery.setLicensePlate(dto.getPlateNumber());
|
||||
delivery.setDriverName(dto.getDriverName());
|
||||
delivery.setDriverMobile(dto.getDriverPhone());
|
||||
|
||||
// 设置卖方和买方ID
|
||||
if (dto.getShipperId() != null) {
|
||||
delivery.setSupplierId(String.valueOf(dto.getShipperId()));
|
||||
System.out.println("[CREATE-DELIVERY] 设置卖方ID: " + dto.getShipperId());
|
||||
}
|
||||
if (dto.getBuyerId() != null) {
|
||||
delivery.setBuyerId(dto.getBuyerId());
|
||||
System.out.println("[CREATE-DELIVERY] 设置买方ID: " + dto.getBuyerId());
|
||||
}
|
||||
|
||||
// 如果有司机ID,从司机表查询司机信息(仅查询司机姓名和电话,不查询车牌号)
|
||||
if (dto.getDriverId() != null) {
|
||||
try {
|
||||
Map<String, Object> driverInfo = memberDriverMapper.selectDriverById(dto.getDriverId());
|
||||
if (driverInfo != null) {
|
||||
String driverUsername = (String) driverInfo.get("username");
|
||||
String driverMobile = (String) driverInfo.get("mobile");
|
||||
String carImg = (String) driverInfo.get("car_img");
|
||||
|
||||
// 设置司机信息 - 优先使用司机表的username
|
||||
if (StringUtils.isNotEmpty(driverUsername)) {
|
||||
delivery.setDriverName(driverUsername);
|
||||
} else {
|
||||
delivery.setDriverName(dto.getDriverName());
|
||||
}
|
||||
|
||||
// 设置司机手机号
|
||||
if (StringUtils.isNotEmpty(driverMobile)) {
|
||||
delivery.setDriverMobile(driverMobile);
|
||||
} else {
|
||||
delivery.setDriverMobile(dto.getDriverPhone());
|
||||
}
|
||||
|
||||
// 设置司机ID
|
||||
delivery.setDriverId(dto.getDriverId());
|
||||
|
||||
System.out.println("[CREATE-DELIVERY] 从司机表获取司机信息 - ID: " + dto.getDriverId() +
|
||||
", 姓名: " + delivery.getDriverName() +
|
||||
", 手机: " + delivery.getDriverMobile());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
System.out.println("[CREATE-DELIVERY] 查询司机信息失败: " + e.getMessage());
|
||||
delivery.setDriverName(dto.getDriverName());
|
||||
delivery.setDriverMobile(dto.getDriverPhone());
|
||||
}
|
||||
} else {
|
||||
// 没有司机ID,直接使用DTO中的数据
|
||||
delivery.setDriverName(dto.getDriverName());
|
||||
delivery.setDriverMobile(dto.getDriverPhone());
|
||||
}
|
||||
// 地址与坐标
|
||||
delivery.setStartLocation(dto.getStartLocation());
|
||||
delivery.setStartLon(dto.getStartLon());
|
||||
@@ -564,6 +615,22 @@ public class DeliveryServiceImpl extends ServiceImpl<DeliveryMapper, Delivery> i
|
||||
delivery.setUnloadCattleVideo(dto.getUnloadCattleVideo());
|
||||
delivery.setDestinationWeightVideo(dto.getDestinationWeightVideo());
|
||||
|
||||
// 根据车牌号查询车辆照片
|
||||
if (StringUtils.isNotEmpty(dto.getPlateNumber())) {
|
||||
try {
|
||||
Vehicle vehicle = vehicleMapper.selectByLicensePlate(dto.getPlateNumber());
|
||||
if (vehicle != null) {
|
||||
delivery.setCarFrontPhoto(vehicle.getCarFrontPhoto());
|
||||
delivery.setCarBehindPhoto(vehicle.getCarRearPhoto());
|
||||
System.out.println("[CREATE-DELIVERY] 从车辆表获取车身照片 - 车牌: " + dto.getPlateNumber() +
|
||||
", 车头照片: " + vehicle.getCarFrontPhoto() +
|
||||
", 车尾照片: " + vehicle.getCarRearPhoto());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
System.out.println("[CREATE-DELIVERY] 查询车辆照片失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
delivery.setStatus(1); // 待装车
|
||||
delivery.setCreatedBy(userId);
|
||||
delivery.setCreateByName(userName);
|
||||
@@ -937,33 +1004,22 @@ public class DeliveryServiceImpl extends ServiceImpl<DeliveryMapper, Delivery> i
|
||||
// 额外调试:检查是否有多个司机记录
|
||||
if (driverInfo != null) {
|
||||
String driverName = (String) driverInfo.get("username");
|
||||
String licensePlate = (String) driverInfo.get("car_number");
|
||||
System.out.println("查询到的司机姓名: " + driverName);
|
||||
System.out.println("查询到的车牌号: " + licensePlate);
|
||||
System.out.println("运单中的车牌号: " + deliveryLogVo.getLicensePlate());
|
||||
|
||||
// 如果车牌号不匹配,尝试根据车牌号查询
|
||||
if (licensePlate != null && !licensePlate.equals(deliveryLogVo.getLicensePlate())) {
|
||||
System.out.println("*** 车牌号不匹配,尝试根据车牌号重新查询 ***");
|
||||
Map<String, Object> correctDriver = memberDriverMapper.selectDriverByPlate(deliveryLogVo.getLicensePlate());
|
||||
if (correctDriver != null) {
|
||||
System.out.println("根据车牌号查询到的司机信息: " + correctDriver);
|
||||
driverInfo = correctDriver; // 使用正确的司机信息
|
||||
}
|
||||
}
|
||||
// 不再根据车牌号查询司机,车牌号是独立的模块
|
||||
}
|
||||
|
||||
if (driverInfo != null) {
|
||||
String driverName = (String) driverInfo.get("username");
|
||||
String licensePlate = (String) driverInfo.get("car_number");
|
||||
String carImg = (String) driverInfo.get("car_img");
|
||||
|
||||
System.out.println("司机姓名: " + driverName + ", 车牌号: " + licensePlate);
|
||||
System.out.println("司机姓名: " + driverName);
|
||||
System.out.println("原始car_img字段: " + carImg);
|
||||
|
||||
// 设置司机信息
|
||||
deliveryLogVo.setDriverName(driverName);
|
||||
deliveryLogVo.setLicensePlate(licensePlate);
|
||||
// 注意:车牌号不从司机表获取,车牌号是独立的模块
|
||||
|
||||
// 强制从司机信息中获取最新的车身照片,确保数据一致性
|
||||
if (carImg != null && !carImg.isEmpty()) {
|
||||
@@ -1005,22 +1061,24 @@ public class DeliveryServiceImpl extends ServiceImpl<DeliveryMapper, Delivery> i
|
||||
// 如果车身照片仍然为空,尝试根据司机姓名和车牌号查询其他相关记录
|
||||
if ((deliveryLogVo.getCarFrontPhoto() == null || deliveryLogVo.getCarFrontPhoto().isEmpty()) &&
|
||||
(deliveryLogVo.getCarBehindPhoto() == null || deliveryLogVo.getCarBehindPhoto().isEmpty()) &&
|
||||
driverName != null && licensePlate != null) {
|
||||
try {
|
||||
System.out.println("列表查询-尝试根据司机姓名和车牌号查询相关记录");
|
||||
Map<String, Object> relatedDriver = memberDriverMapper.selectDriverByNameAndPlate(
|
||||
driverName, licensePlate);
|
||||
if (relatedDriver != null) {
|
||||
String relatedCarImg = (String) relatedDriver.get("car_img");
|
||||
if (relatedCarImg != null && !relatedCarImg.isEmpty()) {
|
||||
deliveryLogVo.setCarFrontPhoto(relatedCarImg);
|
||||
deliveryLogVo.setCarBehindPhoto(relatedCarImg);
|
||||
System.out.println("列表查询-从相关记录设置车身照片: " + relatedCarImg);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
System.out.println("列表查询-查询相关司机记录失败: " + e.getMessage());
|
||||
}
|
||||
driverName != null && deliveryLogVo.getLicensePlate() != null) {
|
||||
// 已废弃:不再根据司机姓名和车牌号查询相关记录
|
||||
// 车牌号和司机是两个独立的模块,车身照片应该从车辆管理模块获取
|
||||
// try {
|
||||
// System.out.println("列表查询-尝试根据司机姓名和车牌号查询相关记录");
|
||||
// Map<String, Object> relatedDriver = memberDriverMapper.selectDriverByNameAndPlate(
|
||||
// driverName, deliveryLogVo.getLicensePlate());
|
||||
// if (relatedDriver != null) {
|
||||
// String relatedCarImg = (String) relatedDriver.get("car_img");
|
||||
// if (relatedCarImg != null && !relatedCarImg.isEmpty()) {
|
||||
// deliveryLogVo.setCarFrontPhoto(relatedCarImg);
|
||||
// deliveryLogVo.setCarBehindPhoto(relatedCarImg);
|
||||
// System.out.println("列表查询-从相关记录设置车身照片: " + relatedCarImg);
|
||||
// }
|
||||
// }
|
||||
// } catch (Exception e) {
|
||||
// System.out.println("列表查询-查询相关司机记录失败: " + e.getMessage());
|
||||
// }
|
||||
}
|
||||
} else {
|
||||
System.out.println("列表查询-未找到司机信息,driverId: " + deliveryLogVo.getDriverId());
|
||||
@@ -1476,11 +1534,10 @@ public class DeliveryServiceImpl extends ServiceImpl<DeliveryMapper, Delivery> i
|
||||
|
||||
if (driverInfo != null) {
|
||||
String driverName = (String) driverInfo.get("username");
|
||||
String licensePlate = (String) driverInfo.get("car_number");
|
||||
String carImg = (String) driverInfo.get("car_img");
|
||||
String driverMobile = (String) driverInfo.get("mobile");
|
||||
|
||||
System.out.println("司机姓名: " + driverName + ", 车牌号: " + licensePlate + ", 车辆照片: " + carImg + ", 司机手机: " + driverMobile);
|
||||
System.out.println("司机姓名: " + driverName + ", 车辆照片: " + carImg + ", 司机手机: " + driverMobile);
|
||||
|
||||
// 设置司机信息 - 司机姓名和手机号用/号连接
|
||||
if (StringUtils.isNotEmpty(driverName) && StringUtils.isNotEmpty(driverMobile)) {
|
||||
@@ -1490,7 +1547,7 @@ public class DeliveryServiceImpl extends ServiceImpl<DeliveryMapper, Delivery> i
|
||||
} else if (StringUtils.isNotEmpty(driverMobile)) {
|
||||
delivery.setDriverName(driverMobile);
|
||||
}
|
||||
delivery.setLicensePlate(licensePlate);
|
||||
// 注意:车牌号不从司机表获取,车牌号是独立的模块
|
||||
delivery.setDriverMobile(driverMobile);
|
||||
|
||||
// 强制从司机信息中获取最新的车身照片,确保数据一致性
|
||||
@@ -1530,35 +1587,36 @@ public class DeliveryServiceImpl extends ServiceImpl<DeliveryMapper, Delivery> i
|
||||
System.out.println("详情查询-司机信息中无照片,清空车身照片");
|
||||
}
|
||||
|
||||
// 如果车身照片仍然为空,尝试根据司机姓名和车牌号查询其他相关记录
|
||||
if ((delivery.getCarFrontPhoto() == null || delivery.getCarFrontPhoto().isEmpty()) &&
|
||||
(delivery.getCarBehindPhoto() == null || delivery.getCarBehindPhoto().isEmpty()) &&
|
||||
driverName != null && licensePlate != null) {
|
||||
try {
|
||||
System.out.println("尝试根据司机姓名和车牌号查询相关记录");
|
||||
Map<String, Object> relatedDriver = memberDriverMapper.selectDriverByNameAndPlate(
|
||||
driverName, licensePlate);
|
||||
if (relatedDriver != null) {
|
||||
String relatedCarImg = (String) relatedDriver.get("car_img");
|
||||
String relatedDriverMobile = (String) relatedDriver.get("mobile");
|
||||
|
||||
if (relatedCarImg != null && !relatedCarImg.isEmpty()) {
|
||||
delivery.setCarFrontPhoto(relatedCarImg);
|
||||
delivery.setCarBehindPhoto(relatedCarImg); // 使用同一张照片作为前后照片
|
||||
System.out.println("从相关记录设置车身照片: " + relatedCarImg);
|
||||
}
|
||||
|
||||
// 如果当前司机手机号为空,使用相关记录的手机号
|
||||
if ((delivery.getDriverMobile() == null || delivery.getDriverMobile().isEmpty()) &&
|
||||
relatedDriverMobile != null && !relatedDriverMobile.isEmpty()) {
|
||||
delivery.setDriverMobile(relatedDriverMobile);
|
||||
System.out.println("从相关记录设置司机手机号: " + relatedDriverMobile);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
System.out.println("查询相关司机记录失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
// 已废弃:不再根据司机姓名和车牌号查询相关记录
|
||||
// 车牌号和司机是两个独立的模块,车身照片应该从车辆管理模块获取
|
||||
// if ((delivery.getCarFrontPhoto() == null || delivery.getCarFrontPhoto().isEmpty()) &&
|
||||
// (delivery.getCarBehindPhoto() == null || delivery.getCarBehindPhoto().isEmpty()) &&
|
||||
// driverName != null && delivery.getLicensePlate() != null) {
|
||||
// try {
|
||||
// System.out.println("尝试根据司机姓名和车牌号查询相关记录");
|
||||
// Map<String, Object> relatedDriver = memberDriverMapper.selectDriverByNameAndPlate(
|
||||
// driverName, delivery.getLicensePlate());
|
||||
// if (relatedDriver != null) {
|
||||
// String relatedCarImg = (String) relatedDriver.get("car_img");
|
||||
// String relatedDriverMobile = (String) relatedDriver.get("mobile");
|
||||
//
|
||||
// if (relatedCarImg != null && !relatedCarImg.isEmpty()) {
|
||||
// delivery.setCarFrontPhoto(relatedCarImg);
|
||||
// delivery.setCarBehindPhoto(relatedCarImg); // 使用同一张照片作为前后照片
|
||||
// System.out.println("从相关记录设置车身照片: " + relatedCarImg);
|
||||
// }
|
||||
//
|
||||
// // // 如果当前司机手机号为空,使用相关记录的手机号
|
||||
// // if ((delivery.getDriverMobile() == null || delivery.getDriverMobile().isEmpty()) &&
|
||||
// // relatedDriverMobile != null && !relatedDriverMobile.isEmpty()) {
|
||||
// // delivery.setDriverMobile(relatedDriverMobile);
|
||||
// // System.out.println("从相关记录设置司机手机号: " + relatedDriverMobile);
|
||||
// // }
|
||||
// // }
|
||||
// // } catch (Exception e) {
|
||||
// // System.out.println Helvetica("查询相关司机记录失败: " + e.getMessage());
|
||||
// // }
|
||||
// // }
|
||||
} else {
|
||||
System.out.println("未找到司机信息,driverId: " + delivery.getDriverId());
|
||||
}
|
||||
@@ -1590,6 +1648,7 @@ public class DeliveryServiceImpl extends ServiceImpl<DeliveryMapper, Delivery> i
|
||||
}
|
||||
resMap.put("delivery", delivery);
|
||||
resMap.put("warningLog", warningLogs);
|
||||
|
||||
//查询主机信息
|
||||
DeliveryDevice deliveryDevices = deliveryDeviceMapper.selectOne(new LambdaQueryWrapper<DeliveryDevice>().eq(DeliveryDevice::getDeliveryId, id).eq(DeliveryDevice::getDeviceType, 1).last("limit 1"));
|
||||
if(null != deliveryDevices){
|
||||
@@ -1597,6 +1656,61 @@ public class DeliveryServiceImpl extends ServiceImpl<DeliveryMapper, Delivery> i
|
||||
}else{
|
||||
resMap.put("serverIds", "");
|
||||
}
|
||||
|
||||
// 查询耳标设备信息(类型2)
|
||||
List<DeliveryDevice> eartagDevices = deliveryDeviceMapper.selectList(
|
||||
new LambdaQueryWrapper<DeliveryDevice>()
|
||||
.eq(DeliveryDevice::getDeliveryId, id)
|
||||
.eq(DeliveryDevice::getDeviceType, 2)
|
||||
);
|
||||
List<String> eartagIds = new ArrayList<>();
|
||||
if (eartagDevices != null && !eartagDevices.isEmpty()) {
|
||||
for (DeliveryDevice device : eartagDevices) {
|
||||
if (device.getDeviceId() != null && !device.getDeviceId().isEmpty()) {
|
||||
eartagIds.add(device.getDeviceId());
|
||||
}
|
||||
}
|
||||
}
|
||||
resMap.put("eartagIds", eartagIds);
|
||||
System.out.println("详情查询-耳标设备: " + eartagIds);
|
||||
|
||||
// 查询项圈设备信息(类型3)
|
||||
List<DeliveryDevice> collarDevices = deliveryDeviceMapper.selectList(
|
||||
new LambdaQueryWrapper<DeliveryDevice>()
|
||||
.eq(DeliveryDevice::getDeliveryId, id)
|
||||
.eq(DeliveryDevice::getDeviceType, 3)
|
||||
);
|
||||
List<String> collarIds = new ArrayList<>();
|
||||
if (collarDevices != null && !collarDevices.isEmpty()) {
|
||||
for (DeliveryDevice device : collarDevices) {
|
||||
if (device.getDeviceId() != null && !device.getDeviceId().isEmpty()) {
|
||||
collarIds.add(device.getDeviceId());
|
||||
}
|
||||
}
|
||||
}
|
||||
resMap.put("collarIds", collarIds);
|
||||
System.out.println("详情查询-项圈设备: " + collarIds);
|
||||
|
||||
// 补充 supplierId 和 buyerId 到返回数据(用于编辑时的下拉框回显)
|
||||
if (delivery.getSupplierId() != null && !delivery.getSupplierId().isEmpty()) {
|
||||
// 如果有多个供应商,只取第一个作为编辑时的默认值
|
||||
String firstSupplierId = delivery.getSupplierId().split(",")[0].trim();
|
||||
try {
|
||||
resMap.put("supplierId", Integer.parseInt(firstSupplierId));
|
||||
} catch (NumberFormatException e) {
|
||||
resMap.put("supplierId", null);
|
||||
}
|
||||
} else {
|
||||
resMap.put("supplierId", null);
|
||||
}
|
||||
|
||||
resMap.put("buyerId", delivery.getBuyerId());
|
||||
|
||||
System.out.println("详情查询-返回数据: supplierId=" + resMap.get("supplierId") +
|
||||
", buyerId=" + delivery.getBuyerId() +
|
||||
", eartagIds=" + eartagIds.size() +
|
||||
", collarIds=" + collarIds.size());
|
||||
|
||||
return AjaxResult.success(resMap);
|
||||
}
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ public class VehicleServiceImpl extends ServiceImpl<VehicleMapper, Vehicle> impl
|
||||
private VehicleMapper vehicleMapper;
|
||||
|
||||
/**
|
||||
* 分页查询车辆列表
|
||||
* 分页查询车辆列表(只查询未删除的记录)
|
||||
*/
|
||||
@Override
|
||||
public PageResultResponse<Vehicle> pageQuery(Map<String, Object> params) {
|
||||
@@ -39,17 +39,23 @@ public class VehicleServiceImpl extends ServiceImpl<VehicleMapper, Vehicle> impl
|
||||
Integer pageSize = params.get("pageSize") != null ? (Integer) params.get("pageSize") : 10;
|
||||
String licensePlate = (String) params.get("licensePlate");
|
||||
|
||||
System.out.println("[VEHICLE-QUERY] 分页参数 - pageNum: " + pageNum + ", pageSize: " + pageSize + ", licensePlate: " + licensePlate);
|
||||
|
||||
// 使用PageHelper进行分页
|
||||
Page<Vehicle> page = PageHelper.startPage(pageNum, pageSize);
|
||||
|
||||
// 构建查询条件
|
||||
LambdaQueryWrapper<Vehicle> queryWrapper = new LambdaQueryWrapper<>();
|
||||
// ✅ 只查询未删除的记录(is_delete=0 或 is_delete 为 null)
|
||||
queryWrapper.and(wrapper -> wrapper.eq(Vehicle::getIsDelete, 0).or().isNull(Vehicle::getIsDelete));
|
||||
queryWrapper.like(licensePlate != null && !licensePlate.isEmpty(), Vehicle::getLicensePlate, licensePlate);
|
||||
queryWrapper.orderByDesc(Vehicle::getCreateTime);
|
||||
|
||||
// 执行查询
|
||||
List<Vehicle> list = vehicleMapper.selectList(queryWrapper);
|
||||
|
||||
System.out.println("[VEHICLE-QUERY] 查询结果 - 总记录数: " + page.getTotal() + ", 当前页记录数: " + list.size());
|
||||
|
||||
// 构建分页结果(使用构造函数)
|
||||
return new PageResultResponse<>(page.getTotal(), list);
|
||||
}
|
||||
@@ -113,28 +119,57 @@ public class VehicleServiceImpl extends ServiceImpl<VehicleMapper, Vehicle> impl
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除车辆(逻辑删除)
|
||||
* 删除车辆(逻辑删除 - 直接执行 SQL)
|
||||
* 不依赖 @TableLogic 注解,直接执行 UPDATE 语句
|
||||
*/
|
||||
@Override
|
||||
@Transactional
|
||||
public AjaxResult deleteVehicle(Integer id) {
|
||||
System.out.println("==========================================");
|
||||
System.out.println("[VEHICLE-DELETE-NEW-VERSION] 开始逻辑删除车辆,ID: " + id);
|
||||
System.out.println("==========================================");
|
||||
|
||||
if (id == null) {
|
||||
System.out.println("[VEHICLE-DELETE] 删除失败:车辆ID为空");
|
||||
return AjaxResult.error("车辆ID不能为空");
|
||||
}
|
||||
|
||||
// 查询车辆是否存在
|
||||
Vehicle vehicle = vehicleMapper.selectById(id);
|
||||
if (vehicle == null) {
|
||||
System.out.println("[VEHICLE-DELETE] 删除失败:车辆不存在或已被删除,ID: " + id);
|
||||
return AjaxResult.error("车辆不存在");
|
||||
}
|
||||
|
||||
// 逻辑删除
|
||||
vehicle.setIsDelete(1);
|
||||
int result = vehicleMapper.updateById(vehicle);
|
||||
System.out.println("[VEHICLE-DELETE] 车辆信息 - 车牌号: " + vehicle.getLicensePlate() + ", 当前 is_delete: " + vehicle.getIsDelete());
|
||||
|
||||
// 获取当前用户ID
|
||||
Integer userId = SecurityUtil.getCurrentUserId();
|
||||
|
||||
// ✅ 直接使用 SQL 更新,不依赖 @TableLogic
|
||||
com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper<Vehicle> updateWrapper =
|
||||
new com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper<>();
|
||||
|
||||
updateWrapper.eq("id", id)
|
||||
.eq("is_delete", 0); // 只更新未删除的记录
|
||||
|
||||
updateWrapper.set("is_delete", 1)
|
||||
.set("updated_by", userId)
|
||||
.set("update_time", new Date());
|
||||
|
||||
System.out.println("[VEHICLE-DELETE] ⚡ 执行直接 SQL 更新 - 设置 is_delete=1");
|
||||
|
||||
int result = vehicleMapper.update(null, updateWrapper);
|
||||
|
||||
if (result > 0) {
|
||||
System.out.println("==========================================");
|
||||
System.out.println("[VEHICLE-DELETE] ✅✅✅ 逻辑删除成功 ✅✅✅");
|
||||
System.out.println("[VEHICLE-DELETE] 车牌号: " + vehicle.getLicensePlate());
|
||||
System.out.println("[VEHICLE-DELETE] 操作人ID: " + userId);
|
||||
System.out.println("==========================================");
|
||||
return AjaxResult.success("删除车辆成功");
|
||||
} else {
|
||||
System.out.println("[VEHICLE-DELETE] ❌ 逻辑删除失败,车牌号: " + vehicle.getLicensePlate());
|
||||
return AjaxResult.error("删除车辆失败");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
jbq.device_voltage AS deviceVoltage,
|
||||
jbq.device_temp AS deviceTemp,
|
||||
COALESCE(del.delivery_number, '未分配') AS deliveryNumber,
|
||||
COALESCE(md.car_number, '未分配') AS licensePlate,
|
||||
COALESCE(del.license_plate, '未分配') AS licensePlate,
|
||||
del.create_time AS deliveryCreateTime
|
||||
FROM
|
||||
jbq_client jbq
|
||||
@@ -45,8 +45,6 @@
|
||||
ON jbq.device_id = dd.device_id AND dd.rn = 1
|
||||
LEFT JOIN
|
||||
delivery del ON dd.delivery_id = del.id
|
||||
LEFT JOIN
|
||||
member_driver md ON del.driver_id = md.id
|
||||
WHERE 1=1
|
||||
<if test="item.deviceId != null and item.deviceId != ''">
|
||||
AND jbq.device_id = #{item.deviceId}
|
||||
|
||||
@@ -80,7 +80,7 @@ public class TencentCloudConstants {
|
||||
public static String TENCENT_CLOUD_REGION = "ap-guangzhou";
|
||||
|
||||
/**
|
||||
* 腾讯云时区
|
||||
* 腾讯云Bucket名称
|
||||
*/
|
||||
public static String TENCENT_CLOUD_BUCKET = "smart-1251449951";
|
||||
|
||||
|
||||
38
tradeCattle/delete_all_delivery_triggers.sql
Normal file
38
tradeCattle/delete_all_delivery_triggers.sql
Normal file
@@ -0,0 +1,38 @@
|
||||
-- ==========================================
|
||||
-- 删除 delivery 表的所有触发器
|
||||
-- ==========================================
|
||||
|
||||
USE cattletrade;
|
||||
|
||||
-- 查看所有 delivery 相关触发器
|
||||
SELECT '当前 delivery 表的触发器:' as '状态';
|
||||
SHOW TRIGGERS WHERE `Table` = 'delivery';
|
||||
|
||||
-- 删除所有可能的触发器变体
|
||||
DROP TRIGGER IF EXISTS trg_delivery_fill_from_driver;
|
||||
DROP TRIGGER IF EXISTS `trg_delivery_fill_from_driver`;
|
||||
|
||||
-- 如果有 UPDATE 版本的触发器
|
||||
DROP TRIGGER IF EXISTS trg_delivery_fill_from_driver_update;
|
||||
DROP TRIGGER IF EXISTS trg_delivery_update_from_driver;
|
||||
|
||||
-- 查询数据库中所有包含 delivery 的触发器名称
|
||||
SELECT TRIGGER_NAME
|
||||
FROM INFORMATION_SCHEMA.TRIGGERS
|
||||
WHERE TRIGGER_SCHEMA = 'cattletrade'
|
||||
AND EVENT_OBJECT_TABLE = 'delivery';
|
||||
|
||||
-- 验证是否还有触发器
|
||||
SELECT '删除后的触发器列表:' as '状态';
|
||||
SHOW TRIGGERS WHERE `Table` = 'delivery';
|
||||
|
||||
-- 最终验证:确认没有触发器引用 car_number
|
||||
SELECT '检查是否还有触发器引用 car_number:' as '状态';
|
||||
SELECT
|
||||
TRIGGER_NAME,
|
||||
EVENT_MANIPULATION,
|
||||
EVENT_OBJECT_TABLE
|
||||
FROM INFORMATION_SCHEMA.TRIGGERS
|
||||
WHERE TRIGGER_SCHEMA = 'cattletrade'
|
||||
AND ACTION_STATEMENT LIKE '%car_number%';
|
||||
|
||||
66
tradeCattle/fix_delivery_triggers.sql
Normal file
66
tradeCattle/fix_delivery_triggers.sql
Normal file
@@ -0,0 +1,66 @@
|
||||
-- ==========================================
|
||||
-- 修复 delivery 表触发器
|
||||
-- 删除引用 car_number 的触发器
|
||||
-- ==========================================
|
||||
|
||||
USE cattletrade;
|
||||
|
||||
-- ==========================================
|
||||
-- 步骤 1: 查看触发器完整定义
|
||||
-- ==========================================
|
||||
SELECT '步骤1: 查看触发器完整定义' as '执行步骤';
|
||||
|
||||
SHOW CREATE TRIGGER trg_delivery_fill_from_driver;
|
||||
|
||||
-- ==========================================
|
||||
-- 步骤 2: 备份当前触发器定义(复制输出保存)
|
||||
-- ==========================================
|
||||
SELECT '步骤2: 已显示触发器定义,请复制保存备份' as '执行步骤';
|
||||
|
||||
-- ==========================================
|
||||
-- 步骤 3: 删除有问题的触发器
|
||||
-- ==========================================
|
||||
SELECT '步骤3: 删除引用 car_number 的触发器' as '执行步骤';
|
||||
|
||||
-- 删除 INSERT 触发器
|
||||
DROP TRIGGER IF EXISTS trg_delivery_fill_from_driver;
|
||||
|
||||
-- 如果有 UPDATE 触发器也删除
|
||||
DROP TRIGGER IF EXISTS trg_delivery_fill_from_driver_update;
|
||||
|
||||
-- ==========================================
|
||||
-- 步骤 4: 验证触发器已删除
|
||||
-- ==========================================
|
||||
SELECT '步骤4: 验证触发器已删除' as '执行步骤';
|
||||
|
||||
SHOW TRIGGERS WHERE `Table` = 'delivery';
|
||||
|
||||
-- ==========================================
|
||||
-- 步骤 5: 验证是否还有其他触发器引用 car_number
|
||||
-- ==========================================
|
||||
SELECT '步骤5: 检查是否还有其他触发器引用 car_number' as '执行步骤';
|
||||
|
||||
SELECT
|
||||
TRIGGER_NAME as '触发器名称',
|
||||
EVENT_MANIPULATION as '触发事件',
|
||||
EVENT_OBJECT_TABLE as '关联表',
|
||||
ACTION_STATEMENT as '触发器SQL'
|
||||
FROM INFORMATION_SCHEMA.TRIGGERS
|
||||
WHERE TRIGGER_SCHEMA = 'cattletrade'
|
||||
AND ACTION_STATEMENT LIKE '%car_number%';
|
||||
|
||||
-- ==========================================
|
||||
-- 说明:
|
||||
-- 1. 这些触发器的作用是在插入/更新 delivery 时自动从 member_driver 获取信息
|
||||
-- 2. 由于司机和车辆现在是独立模块,不再需要这种自动填充
|
||||
-- 3. 车辆信息现在由 vehicle 表管理,在 DeliveryServiceImpl 中手动查询
|
||||
-- 4. 删除触发器后,重启后端服务即可正常创建运送清单
|
||||
-- ==========================================
|
||||
|
||||
-- ==========================================
|
||||
-- 完成后请:
|
||||
-- 1. 清理后端 target 目录: Remove-Item -Recurse -Force tradeCattle\target
|
||||
-- 2. 重启后端服务
|
||||
-- 3. 测试创建运送清单功能
|
||||
-- ==========================================
|
||||
|
||||
77
tradeCattle/remove_car_number_from_member_driver.sql
Normal file
77
tradeCattle/remove_car_number_from_member_driver.sql
Normal file
@@ -0,0 +1,77 @@
|
||||
-- ==========================================
|
||||
-- 从 member_driver 表删除 car_number 字段
|
||||
-- ==========================================
|
||||
-- 说明:
|
||||
-- 1. 司机和车辆是多对多关系,应该由独立的 vehicle 表管理
|
||||
-- 2. 删除前请确保已经将车辆信息迁移到 vehicle 表
|
||||
-- 3. 此操作不可逆,请先备份数据库
|
||||
-- ==========================================
|
||||
|
||||
-- 步骤 1: 检查 member_driver 表结构
|
||||
SELECT '步骤1: 检查 member_driver 表结构' as '执行步骤';
|
||||
DESC member_driver;
|
||||
|
||||
-- 步骤 2: 检查字段是否存在
|
||||
SELECT '步骤2: 检查 car_number 字段是否存在' as '执行步骤';
|
||||
SELECT COUNT(*) as 'car_number字段存在数量'
|
||||
FROM INFORMATION_SCHEMA.COLUMNS
|
||||
WHERE TABLE_SCHEMA = DATABASE()
|
||||
AND TABLE_NAME = 'member_driver'
|
||||
AND COLUMN_NAME = 'car_number';
|
||||
|
||||
-- 步骤 3: 检查是否有触发器引用 car_number
|
||||
SELECT '步骤3: 检查触发器' as '执行步骤';
|
||||
SHOW TRIGGERS WHERE `Table` = 'member_driver';
|
||||
|
||||
-- 步骤 4: 检查是否有视图引用 car_number
|
||||
SELECT '步骤4: 检查视图' as '执行步骤';
|
||||
SELECT TABLE_NAME, VIEW_DEFINITION
|
||||
FROM INFORMATION_SCHEMA.VIEWS
|
||||
WHERE TABLE_SCHEMA = DATABASE()
|
||||
AND VIEW_DEFINITION LIKE '%car_number%';
|
||||
|
||||
-- 步骤 5: 检查delivery表的触发器(重点检查)
|
||||
SELECT '步骤5: 检查 delivery 表触发器' as '执行步骤';
|
||||
SHOW TRIGGERS WHERE `Table` = 'delivery';
|
||||
|
||||
-- 步骤 6: 查看完整的触发器定义
|
||||
SELECT '步骤6: 查看所有触发器的完整定义' as '执行步骤';
|
||||
SELECT
|
||||
TRIGGER_NAME as '触发器名称',
|
||||
EVENT_MANIPULATION as '触发事件',
|
||||
EVENT_OBJECT_TABLE as '关联表',
|
||||
ACTION_STATEMENT as '触发器SQL'
|
||||
FROM INFORMATION_SCHEMA.TRIGGERS
|
||||
WHERE TRIGGER_SCHEMA = DATABASE()
|
||||
AND ACTION_STATEMENT LIKE '%car_number%';
|
||||
|
||||
-- ==========================================
|
||||
-- ⚠️ 重要:执行删除前请先检查上面的输出
|
||||
-- ==========================================
|
||||
-- 如果确认:
|
||||
-- 1. car_number 字段存在
|
||||
-- 2. 没有触发器或视图引用它
|
||||
-- 3. 已备份数据库
|
||||
-- 则取消下面的注释执行删除操作:
|
||||
|
||||
-- ALTER TABLE member_driver DROP COLUMN IF EXISTS car_number;
|
||||
|
||||
-- ==========================================
|
||||
-- 验证删除结果
|
||||
-- ==========================================
|
||||
-- 取消下面的注释验证字段已删除:
|
||||
-- SELECT '验证: car_number 字段已删除' as '执行步骤';
|
||||
-- DESC member_driver;
|
||||
-- SELECT COUNT(*) as 'car_number字段剩余数量'
|
||||
-- FROM INFORMATION_SCHEMA.COLUMNS
|
||||
-- WHERE TABLE_SCHEMA = DATABASE()
|
||||
-- AND TABLE_NAME = 'member_driver'
|
||||
-- AND COLUMN_NAME = 'car_number';
|
||||
|
||||
-- ==========================================
|
||||
-- 说明:执行完成后请:
|
||||
-- 1. 清理后端 target 目录
|
||||
-- 2. 重新编译后端服务
|
||||
-- 3. 完全重启后端服务
|
||||
-- ==========================================
|
||||
|
||||
Reference in New Issue
Block a user