先部署后测试

This commit is contained in:
xuqiuyun
2025-10-17 15:25:33 +08:00
parent 7c7d2487ea
commit 9979e00b47
6 changed files with 0 additions and 760 deletions

View File

@@ -1,332 +0,0 @@
# 基于手机号的数据权限过滤功能实现说明
## 功能概述
实现了基于登录用户手机号的数据权限过滤功能,保护用户隐私,确保普通用户只能查看与自己相关的装车订单和运送清单。
## 核心需求
1. **角色识别**:系统识别四个角色的手机号
- 司机Driver
- 供应商Supplier可能有多个
- 资金方Fund
- 采购商Buyer
2. **权限规则**
- **超级管理员roleId=1**:可以查看所有订单和运送清单
- **普通用户**:只能查看与自己手机号相关的数据(四个角色中任意一个匹配即可)
## 技术实现
### 1. 后端修改
#### 1.1 实体类修改
**文件**: `tradeCattle/aiotagro-cattle-trade/src/main/java/com/aiotagro/cattletrade/business/entity/Delivery.java`
添加了三个新的手机号字段(司机手机号已存在):
```java
/**
* 供应商手机号(逗号分隔)
*/
@TableField(exist = false)
private String supplierMobile;
/**
* 资金方手机号
*/
@TableField(exist = false)
private String fundMobile;
/**
* 采购商手机号
*/
@TableField(exist = false)
private String buyerMobile;
```
#### 1.2 服务层修改
**文件**: `tradeCattle/aiotagro-cattle-trade/src/main/java/com/aiotagro/cattletrade/business/service/impl/DeliveryServiceImpl.java`
修改了两个关键方法:
##### 1装车订单列表查询 - `pageQueryListLog`
```java
public PageResultResponse<Delivery> pageQueryListLog(DeliverListDto dto) {
// 1. 获取当前登录用户手机号
String currentUserMobile = SecurityUtil.getUserMobile();
// 2. 查询所有数据
List<Delivery> resList = list(deliveryLambdaQueryWrapper);
// 3. 关联查询四个角色的手机号
resList.forEach(deliveryLogVo -> {
// 查询供应商手机号supplierId是逗号分隔的
// 查询资金方手机号
// 查询采购商手机号
// 查询司机手机号
});
// 4. 数据权限过滤(非超级管理员)
if (!SecurityUtil.isSuperAdmin()) {
resList = resList.stream().filter(delivery -> {
// 匹配司机手机号
// 匹配供应商手机号
// 匹配资金方手机号
// 匹配采购商手机号
return hasPermission;
}).collect(Collectors.toList());
}
return new PageResultResponse(filteredTotal, resList);
}
```
##### 2运送清单列表查询 - `pageQuery`
实现逻辑与 `pageQueryListLog` 相同,确保两个页面的权限过滤一致。
### 2. 权限判断逻辑
#### 2.1 超级管理员判断
```java
// 位置SecurityUtil.java
public static boolean isSuperAdmin() {
Integer roleId = getRoleId();
return roleId != null && roleId.equals(RoleConstants.SUPER_ADMIN_ROLE_ID);
}
// RoleConstants.SUPER_ADMIN_ROLE_ID = 1
```
#### 2.2 手机号匹配逻辑
普通用户的数据访问权限判断:
```java
boolean hasPermission = false;
// 1. 检查是否是司机
if (currentUserMobile.equals(delivery.getDriverMobile())) {
hasPermission = true;
}
// 2. 检查是否是供应商(可能有多个)
if (!hasPermission && delivery.getSupplierMobile() != null) {
String[] supplierMobiles = delivery.getSupplierMobile().split(",");
for (String mobile : supplierMobiles) {
if (currentUserMobile.equals(mobile.trim())) {
hasPermission = true;
break;
}
}
}
// 3. 检查是否是资金方
if (!hasPermission && currentUserMobile.equals(delivery.getFundMobile())) {
hasPermission = true;
}
// 4. 检查是否是采购商
if (!hasPermission && currentUserMobile.equals(delivery.getBuyerMobile())) {
hasPermission = true;
}
return hasPermission;
```
### 3. 角色手机号关联查询
系统在返回列表数据前会根据角色ID查询对应的手机号
```java
// 1. 供应商手机号查询supplierId可能是逗号分隔的多个ID
if (delivery.getSupplierId() != null) {
String[] supplierIds = delivery.getSupplierId().split(",");
List<String> supplierMobiles = new ArrayList<>();
for (String supplierId : supplierIds) {
Member supplier = memberMapper.selectById(Integer.parseInt(supplierId));
if (supplier != null) {
supplierMobiles.add(supplier.getMobile());
}
}
delivery.setSupplierMobile(String.join(",", supplierMobiles));
}
// 2. 资金方手机号查询
if (delivery.getFundId() != null) {
Member fund = memberMapper.selectById(delivery.getFundId());
if (fund != null) {
delivery.setFundMobile(fund.getMobile());
}
}
// 3. 采购商手机号查询
if (delivery.getBuyerId() != null) {
Member buyer = memberMapper.selectById(delivery.getBuyerId());
if (buyer != null) {
delivery.setBuyerMobile(buyer.getMobile());
}
}
// 4. 司机手机号查询
if (delivery.getDriverId() != null) {
Map<String, Object> driverInfo = memberDriverMapper.selectDriverById(delivery.getDriverId());
if (driverInfo != null) {
delivery.setDriverMobile((String) driverInfo.get("mobile"));
}
}
```
## 影响范围
### 1. 受影响的页面
- **装车订单列表页** (`/shipping/loadingOrder`)
- 接口:`/delivery/pageDeliveryOrderList`
- **运送清单列表页** (`/entry/attestation`)
- 接口:`/delivery/pageQueryList`
### 2. 不受影响的功能
- 订单详情查询
- 订单创建/编辑
- 设备分配
- 其他管理功能
## 测试建议
### 测试场景1超级管理员登录
**步骤**
1. 使用 roleId=1 的超级管理员账号登录
2. 访问装车订单列表页
3. 访问运送清单列表页
**预期结果**
- 可以看到系统中所有的订单和运送清单
- 控制台输出:`=== 超级管理员,不执行数据权限过滤 ===`
### 测试场景2司机登录
**步骤**
1. 使用司机账号登录手机号13800138001
2. 访问装车订单列表页
3. 访问运送清单列表页
**预期结果**
- 只能看到 driverMobile = '13800138001' 的订单
- 其他订单不可见
- 控制台输出匹配日志
### 测试场景3供应商登录
**步骤**
1. 使用供应商账号登录手机号13900139001
2. 访问装车订单列表页
3. 访问运送清单列表页
**预期结果**
- 只能看到 supplierMobile 包含 '13900139001' 的订单
- 其他订单不可见
### 测试场景4资金方/采购商登录
**步骤**
1. 使用资金方或采购商账号登录
2. 访问装车订单列表页
3. 访问运送清单列表页
**预期结果**
- 只能看到与自己手机号匹配的订单
- 其他订单不可见
### 测试场景5多角色用户
**步骤**
1. 创建一个订单,将同一个用户设置为多个角色(如既是司机又是供应商)
2. 使用该用户登录
3. 访问列表页
**预期结果**
- 该用户可以看到此订单(任意角色匹配即可)
## 调试信息
系统在控制台输出详细的调试日志,方便问题排查:
```
=== 装车订单列表查询 - 当前登录用户手机号: 13800138001 ===
=== 非超级管理员,执行数据权限过滤 ===
当前用户手机号: 13800138001
订单 ZC20251016142501 - 匹配司机手机号
订单 ZC20251016142502 - 无权限,过滤掉
过滤后的订单数量: 1
```
## 数据库表结构
### delivery 表
相关字段:
- `supplier_id` VARCHAR - 供应商ID逗号分隔
- `fund_id` INT - 资金方ID
- `driver_id` INT - 司机ID
- `buyer_id` INT - 采购商ID
### member 表
相关字段:
- `id` INT - 会员ID
- `mobile` VARCHAR - 手机号
- `type` INT - 会员类型
### member_driver 表
相关字段:
- `id` INT - 司机ID
- `member_id` INT - 关联 member.id
- `username` VARCHAR - 司机姓名
- `mobile` VARCHAR - 司机手机号
## 性能优化建议
1. **批量查询优化**:当前实现对每个订单单独查询角色手机号,如果订单数量较多,建议改为批量查询:
```java
// 收集所有角色ID
List<Integer> allMemberIds = new ArrayList<>();
// ... 添加所有 fundId, buyerId 等
// 批量查询
List<Member> members = memberMapper.selectBatchIds(allMemberIds);
Map<Integer, String> idToMobileMap = ...;
```
2. **缓存优化**:对于频繁访问的会员信息,可以考虑添加 Redis 缓存。
3. **分页问题**:当前实现在内存中过滤数据后更新分页信息,这可能导致分页不准确。建议:
- 方案A在数据库层面进行过滤通过 SQL JOIN 查询)
- 方案B在应用层正确处理分页逻辑
## 注意事项
1. **手机号格式**确保系统中所有手机号格式一致都是11位数字无空格
2. **空值处理**:代码中已处理 null 值情况,避免空指针异常
3. **供应商多值处理**supplierId 可能是逗号分隔的多个值,需要分别处理
4. **调试日志**:生产环境建议关闭或降低日志级别
## 版本信息
- 实现日期2025-10-16
- 修改文件:
- `Delivery.java`
- `DeliveryServiceImpl.java`
- 影响接口:
- `/delivery/pageDeliveryOrderList` (装车订单列表)
- `/delivery/pageQueryList` (运送清单列表)

View File

@@ -1,109 +0,0 @@
# 新增用户自动创建员工功能说明
## 功能概述
在新增用户功能中,当用户创建成功后,系统会自动在系统管理的员工管理中添加对应的员工记录,并根据用户类型设置相应的岗位。
## 实现细节
### 1. 用户类型映射
系统支持以下用户类型:
- **1**: 司机
- **2**: 供应商(农户)
- **3**: 资金方
- **4**: 采购商
### 2. 自动创建流程
当新增用户成功后,系统会执行以下步骤:
1. **检查重复性**: 检查手机号是否已存在于员工表中,如果存在则跳过创建
2. **获取或创建岗位**: 根据用户类型获取对应的岗位,如果岗位不存在则自动创建
3. **创建员工记录**: 在`sys_user`表中创建员工记录,包含以下信息:
- 员工姓名(来自用户姓名)
- 手机号(来自用户手机号)
- 岗位ID根据用户类型自动分配
- 默认密码:`123456`(已加密)
- 账号状态(继承自用户状态,默认为启用)
- 创建时间
- 创建人
### 3. 岗位自动创建
系统会根据用户类型自动创建对应的岗位:
| 用户类型 | 岗位名称 | 岗位描述 |
|---------|---------|---------|
| 1 | 司机 | 司机用户岗位 |
| 2 | 供应商 | 供应商用户岗位 |
| 3 | 资金方 | 资金方用户岗位 |
| 4 | 采购商 | 采购商用户岗位 |
| 其他 | 普通用户 | 普通用户岗位 |
### 4. 密码处理
- 默认密码设置为:`123456`
- 使用系统统一的密码加密方式:`MD5(aiot + 密码)`
- 与现有员工管理系统的密码加密方式保持一致
## 技术实现
### 修改的文件
- `tradeCattle/aiotagro-cattle-trade/src/main/java/com/aiotagro/cattletrade/business/controller/MemberController.java`
### 新增的方法
1. **`createEmployeeRecord()`**: 创建员工记录的核心方法
2. **`getOrCreateRoleByUserType()`**: 根据用户类型获取或创建岗位
### 依赖注入
新增了`SysRoleMapper`的依赖注入,用于岗位管理操作。
## 使用说明
### 前端调用
前端无需修改继续使用现有的新增用户API
```javascript
// 调用新增用户API
userAdd({
username: '张三',
mobile: '13800138000',
type: 2, // 供应商
status: 0, // 启用
cbkAccount: 'account123',
remark: '备注信息'
})
```
### 后端处理
后端会自动:
1. 创建用户记录(原有功能)
2. 自动创建对应的员工记录(新增功能)
3. 如果岗位不存在,自动创建岗位
## 错误处理
- 员工记录创建失败不会影响用户创建的成功
- 错误信息会记录到系统日志中
- 如果手机号已存在于员工表中,会跳过创建并记录日志
## 注意事项
1. **数据一致性**: 确保用户表和员工表中的手机号保持一致
2. **权限管理**: 新创建的员工记录需要管理员手动分配菜单权限
3. **密码安全**: 建议用户首次登录后修改默认密码
4. **岗位管理**: 自动创建的岗位可以在系统管理的岗位管理中进行编辑和权限分配
## 测试建议
1. 测试不同用户类型的用户创建
2. 验证员工记录是否正确创建
3. 检查岗位是否按预期创建
4. 确认密码加密是否正确
5. 测试重复手机号的处理逻辑

View File

@@ -1,132 +0,0 @@
# 智能项圈运单分配功能实现总结
## 已完成的功能
### 1. 数据库修改 ✅
- 创建了 `添加项圈运单字段.sql` 文件
-`xq_client` 表添加了三个字段:
- `delivery_id` INT - 运单ID
- `delivery_number` VARCHAR(64) - 运单号
- `license_plate` VARCHAR(64) - 车牌号
### 2. 后端Java代码修改 ✅
#### 2.1 XqClient实体类更新
- 文件:`tradeCattle/aiotagro-cattle-trade/src/main/java/com/aiotagro/cattletrade/business/entity/XqClient.java`
- 添加了 `deliveryId` 字段映射
#### 2.2 XqClientMapper查询SQL修改
- 文件:`tradeCattle/aiotagro-cattle-trade/src/main/java/com/aiotagro/cattletrade/business/mapper/XqClientMapper.java`
- 修改了 `selectXqClientListWithRelations` 方法,将 `xc.sn AS deviceId` 返回统一使用sn作为设备编号
- 修改了 `selectUnassignedXqClientList` 方法同样使用sn作为deviceId
#### 2.3 设备分配逻辑更新
- 文件:`tradeCattle/aiotagro-cattle-trade/src/main/java/com/aiotagro/cattletrade/business/controller/DeliveryController.java`
-`updateDeviceDeliveryInfo` 方法中,为项圈设备添加了 `deliveryId` 字段的更新
- 现在分配项圈时会同时更新:`delivery_id``delivery_number``license_plate`
#### 2.4 新增查看所有设备API
- 文件:`tradeCattle/aiotagro-cattle-trade/src/main/java/com/aiotagro/cattletrade/business/controller/DeliveryDeviceController.java`
- 新增 `/deliveryDevice/pageDeviceList` 接口
- 同时查询耳标(type=2)和项圈(type=3)设备
- 返回统一的设备列表,包含 `deviceType` 字段区分设备类型
#### 2.5 新增运单状态更新API
- 文件:`tradeCattle/aiotagro-cattle-trade/src/main/java/com/aiotagro/cattletrade/business/controller/DeliveryController.java`
- 新增 `/delivery/updateStatus` 接口
- 支持更新运单的核验状态(1-待核验 2-已核验 3-已完成)
### 3. 前端代码修改 ✅
#### 3.1 分配设备对话框优化
- 文件:`pc-cattle-transportation/src/views/shipping/assignDialog.vue`
- 修改项圈设备编号映射,优先使用 `sn` 字段:`deviceId: item.sn || item.deviceId`
- 确保项圈设备状态判断基于 `delivery_number` 字段
#### 3.2 查看设备对话框增强
- 文件:`pc-cattle-transportation/src/views/shipping/lookDialog.vue`
- 改用新的 `deviceAllList` API同时显示耳标和项圈设备
- 更新设备编号显示逻辑:`scope.row.deviceId || scope.row.sn`
- 更新佩戴状态判断:支持 `isWare``is_wear` 两种字段
#### 3.3 新增API接口定义
- 文件:`pc-cattle-transportation/src/api/shipping.js`
- 新增 `deviceAllList` 函数:查看运单所有设备
- 新增 `updateDeliveryStatus` 函数:更新运单状态
#### 3.4 运送清单列表功能增强
- 文件:`pc-cattle-transportation/src/views/shipping/loadingOrder.vue`
- 添加"编辑状态"按钮到操作列
- 实现 `editStatus` 方法,弹出输入框修改运单状态
- 操作列宽度调整为420px以容纳新按钮
## 功能特性
### 核心功能
1. **智能项圈分配到运单**
- 项圈设备可以通过"分配设备"功能分配到运单
- 分配时自动更新项圈的运单ID、运单号和车牌号
2. **设备编号统一**
- 项圈设备统一使用 `sn` 字段作为设备编号
- 前后端保持一致,确保数据正确映射
3. **设备状态判断**
- 基于 `delivery_number` 字段判断设备是否已分配
- 未分配显示"未分配",已分配显示实际运单号
4. **查看设备功能**
- 支持同时查看运单下的耳标和项圈设备
- 自动区分设备类型并显示相应标签
5. **运单状态管理**
- 新增"编辑状态"按钮,可直接修改运单核验状态
- 支持三种状态1-待核验、2-已核验、3-已完成
### 数据同步
- 分配设备时,`xq_client` 表的以下字段会自动更新:
- `delivery_id`运单ID
- `delivery_number`:运单号
- `license_plate`:车牌号(从运单的 `license_plate` 字段获取)
## 使用说明
### 1. 执行数据库脚本
```bash
# 在MySQL中执行
source 添加项圈运单字段.sql
```
### 2. 分配项圈到运单
1. 进入"运送清单"页面
2. 点击某个运单的"分配设备"按钮
3. 在设备类型下拉框选择"智能项圈"或"全部设备"
4. 勾选要分配的项圈设备
5. 点击"保存"完成分配
### 3. 查看运单设备
1. 在"运送清单"页面点击"查看设备"按钮
2. 弹窗中会显示该运单的所有设备(耳标+项圈)
3. 设备类型列会显示"智能耳标"或"智能项圈"
### 4. 修改运单状态
1. 在"运送清单"页面点击"编辑状态"按钮
2. 在弹出的输入框中输入状态值1/2/3
3. 点击确定完成状态更新
## 技术要点
1. **后端SQL优化**:使用 `ROW_NUMBER()` 窗口函数获取设备的最新运单关联
2. **前端兼容性**支持多种字段名映射deviceId/sn, isWare/is_wear
3. **权限控制**所有API都添加了 `@SaCheckPermission` 权限验证
4. **错误处理**:完善的异常捕获和用户提示
## 测试建议
- [ ] 测试项圈设备分配功能
- [ ] 验证分配后 `xq_client` 表字段是否正确更新
- [ ] 测试查看设备功能是否同时显示耳标和项圈
- [ ] 验证设备状态判断逻辑(未分配/已分配)
- [ ] 测试运单状态编辑功能
- [ ] 验证项圈设备编号显示是否使用sn字段

View File

@@ -1,8 +0,0 @@
-- 为智能耳标表添加运单号和车牌号字段
ALTER TABLE jbq_client ADD COLUMN IF NOT EXISTS delivery_number VARCHAR(255) COMMENT '运单号';
ALTER TABLE jbq_client ADD COLUMN IF NOT EXISTS license_plate VARCHAR(255) COMMENT '车牌号';
-- 为智能项圈表添加运单号和车牌号字段
ALTER TABLE xq_client ADD COLUMN IF NOT EXISTS delivery_number VARCHAR(255) COMMENT '运单号';
ALTER TABLE xq_client ADD COLUMN IF NOT EXISTS license_plate VARCHAR(255) COMMENT '车牌号';

View File

@@ -1,9 +0,0 @@
-- 1. 在xq_client表添加运单相关字段
ALTER TABLE xq_client
ADD COLUMN delivery_id INT COMMENT '运单ID',
ADD COLUMN delivery_number VARCHAR(64) COMMENT '运单号',
ADD COLUMN license_plate VARCHAR(64) COMMENT '车牌号';
-- 2. 可选在delivery表添加项圈设备字段
-- ALTER TABLE delivery
-- ADD COLUMN xq_device_ids TEXT COMMENT '智能项圈设备ID列表(JSON)';

View File

@@ -1,170 +0,0 @@
# 车身照片关联查询功能实现说明
## 🎯 功能需求
在运送清单详情页面的"车身照片"字段中,需要显示与司机姓名和车牌号相关联的车身前后照片,确保车身照片能够正确映射到运送清单信息表单中。
## 🔧 技术实现
### 1. **问题分析**
- **现状**`delivery` 表中的 `carFrontPhoto``carBehindPhoto` 字段为空
- **需求**:根据司机姓名和车牌号关联查询车身照片
- **数据源**`member_driver` 表中的 `car_img` 字段包含司机车辆照片
### 2. **后端实现**
#### 2.1 新增查询方法
`MemberDriverMapper.java` 中添加了根据司机姓名和车牌号查询的方法:
```java
/**
* 根据司机姓名和车牌号查询司机信息(包含车身照片)
*/
@Select("SELECT md.id, md.member_id, md.username, md.car_number, " +
"md.driving_license, md.driver_license, md.record_code, " +
"md.car_img, 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}")
Map<String, Object> selectDriverByNameAndPlate(@Param("driverName") String driverName,
@Param("licensePlate") String licensePlate);
```
#### 2.2 修改详情查询逻辑
`DeliveryServiceImpl.java``detail` 方法中添加了车身照片关联查询逻辑:
```java
// 查询司机信息
if (delivery.getDriverId() != null) {
try {
// 根据司机ID直接查询司机信息
Map<String, Object> driverInfo = memberDriverMapper.selectDriverById(delivery.getDriverId());
if (driverInfo != null) {
delivery.setDriverName((String) driverInfo.get("username"));
delivery.setLicensePlate((String) driverInfo.get("car_number"));
// 如果delivery表中的车身照片为空尝试从司机信息中获取
if (delivery.getCarFrontPhoto() == null || delivery.getCarFrontPhoto().isEmpty()) {
String carImg = (String) driverInfo.get("car_img");
if (carImg != null && !carImg.isEmpty()) {
delivery.setCarFrontPhoto(carImg);
}
}
// 如果车身照片仍然为空,尝试根据司机姓名和车牌号查询其他相关记录
if ((delivery.getCarFrontPhoto() == null || delivery.getCarFrontPhoto().isEmpty()) &&
(delivery.getCarBehindPhoto() == null || delivery.getCarBehindPhoto().isEmpty())) {
try {
Map<String, Object> relatedDriver = memberDriverMapper.selectDriverByNameAndPlate(
delivery.getDriverName(), delivery.getLicensePlate());
if (relatedDriver != null) {
String relatedCarImg = (String) relatedDriver.get("car_img");
if (relatedCarImg != null && !relatedCarImg.isEmpty()) {
delivery.setCarFrontPhoto(relatedCarImg);
delivery.setCarBehindPhoto(relatedCarImg); // 使用同一张照片作为前后照片
}
}
} catch (Exception e) {
// 忽略查询错误
}
}
}
} catch (Exception e) {
// 忽略查询错误,继续处理
e.printStackTrace();
}
}
```
### 3. **查询逻辑流程**
#### 3.1 优先级查询顺序
1. **第一优先级**:直接从 `delivery` 表获取 `carFrontPhoto``carBehindPhoto`
2. **第二优先级**:从当前司机的 `member_driver` 记录中获取 `car_img`
3. **第三优先级**:根据司机姓名和车牌号查询其他相关司机记录
#### 3.2 数据映射规则
- **车头照片**:优先使用 `delivery.carFrontPhoto`,其次使用 `member_driver.car_img`
- **车尾照片**:优先使用 `delivery.carBehindPhoto`,其次使用 `member_driver.car_img`
- **备用方案**:如果前后照片都为空,使用同一张 `car_img` 作为前后照片
### 4. **前端显示**
前端详情页面中的车身照片显示组件已经正确配置:
```vue
<el-descriptions-item label="车身照片:">
<span style="vertical-align: top">
<el-image
v-if="data.baseInfo.carFrontPhoto"
style="width: 50px; height: 50px; margin-right: 10px"
:src="data.baseInfo.carFrontPhoto ? data.baseInfo.carFrontPhoto : ''"
fit="cover"
:preview-src-list="[data.baseInfo.carFrontPhoto] ? [data.baseInfo.carFrontPhoto] : []"
preview-teleported
/>
<el-image
v-if="data.baseInfo.carBehindPhoto"
style="width: 50px; height: 50px; margin-right: 10px"
:src="data.baseInfo.carBehindPhoto ? data.baseInfo.carBehindPhoto : ''"
fit="cover"
:preview-src-list="[data.baseInfo.carBehindPhoto] ? [data.baseInfo.carBehindPhoto] : []"
preview-teleported
/>
</span>
</el-descriptions-item>
```
## 📊 数据流程
### 1. **API调用流程**
```
前端详情页面 → waybillDetail API → DeliveryServiceImpl.detail() →
MemberDriverMapper.selectDriverById() → MemberDriverMapper.selectDriverByNameAndPlate() →
返回包含车身照片的delivery对象
```
### 2. **数据查询流程**
```
1. 查询delivery表基本信息
2. 根据driverId查询司机信息
3. 如果车身照片为空从司机信息中获取car_img
4. 如果仍然为空,根据司机姓名和车牌号查询相关记录
5. 返回完整的delivery对象给前端
```
## 🎯 功能特点
### 1. **智能关联查询**
- ✅ 支持多层级查询策略
- ✅ 自动匹配司机姓名和车牌号
- ✅ 容错处理,避免查询失败影响整体功能
### 2. **数据完整性**
- ✅ 确保车身照片能够正确显示
- ✅ 支持前后照片分别显示
- ✅ 提供备用显示方案
### 3. **性能优化**
- ✅ 优先使用已有数据,减少数据库查询
- ✅ 异常处理,避免查询错误影响用户体验
- ✅ 缓存友好的查询策略
## 📁 相关文件
- **后端Mapper**`tradeCattle/.../MemberDriverMapper.java`
- **后端Service**`tradeCattle/.../DeliveryServiceImpl.java`
- **前端页面**`pc-cattle-transportation/src/views/entry/details.vue`
## 🎉 总结
车身照片关联查询功能已经完成!现在:
-**智能查询**:根据司机姓名和车牌号自动关联查询车身照片
-**多层级策略**:支持多种查询策略,确保数据完整性
-**容错处理**:异常情况下不影响整体功能
-**前端显示**:车身照片能够正确显示在详情页面中
运送清单详情页面现在可以正确显示与司机姓名和车牌号相关联的车身前后照片!