修改内容
This commit is contained in:
198
ORDER_TABLE_IMPLEMENTATION_REPORT.md
Normal file
198
ORDER_TABLE_IMPLEMENTATION_REPORT.md
Normal file
@@ -0,0 +1,198 @@
|
||||
# 订单表功能实现报告
|
||||
|
||||
## 概述
|
||||
根据需求,重新设计并实现了一个新的订单管理系统,包含订单表的数据库设计、后端接口以及前端页面。
|
||||
|
||||
## 实现内容
|
||||
|
||||
### 1. 数据库设计
|
||||
|
||||
**表结构:** `order`
|
||||
|
||||
| 字段名 | 类型 | 说明 |
|
||||
|--------|------|------|
|
||||
| id | int(11) | 主键ID,自增 |
|
||||
| buyer_id | varchar(500) | 买方ID(多个买家用逗号分隔) |
|
||||
| seller_id | varchar(500) | 卖方ID(多个卖家用逗号分隔) |
|
||||
| settlement_type | int(11) | 结算方式:1-上车重量,2-下车重量,3-按肉价结算 |
|
||||
| is_delete | tinyint(1) | 逻辑删除标记(0-正常,1-已删除) |
|
||||
| create_time | datetime | 创建时间 |
|
||||
| created_by | int(11) | 创建人ID |
|
||||
| update_time | datetime | 更新时间 |
|
||||
| updated_by | int(11) | 更新人ID |
|
||||
|
||||
**SQL文件位置:** `tradeCattle/create_order_table.sql`
|
||||
|
||||
### 2. 后端实现
|
||||
|
||||
#### 2.1 实体类(Entity)
|
||||
- **文件:** `Order.java`
|
||||
- **位置:** `aiotagro-cattle-trade/src/main/java/com/aiotagro/cattletrade/business/entity/Order.java`
|
||||
- **特性:**
|
||||
- 使用MyBatis-Plus注解
|
||||
- 包含@TableLogic注解实现逻辑删除
|
||||
- 包含显示字段:buyerName、sellerName、createdByName、settlementTypeDesc
|
||||
|
||||
#### 2.2 Mapper接口
|
||||
- **文件:** `OrderMapper.java`
|
||||
- **位置:** `aiotagro-cattle-trade/src/main/java/com/aiotagro/cattletrade/business/mapper/OrderMapper.java`
|
||||
- **说明:** 继承BaseMapper,提供基础的CRUD操作
|
||||
|
||||
#### 2.3 Service层
|
||||
- **接口:** `IOrderService.java`
|
||||
- **实现类:** `OrderServiceImpl.java`
|
||||
- **位置:** `aiotagro-cattle-trade/src/main/java/com/aiotagro/cattletrade/business/service/`
|
||||
- **方法:**
|
||||
- `pageQuery` - 分页查询订单列表
|
||||
- `addOrder` - 新增订单
|
||||
- `updateOrder` - 更新订单
|
||||
- `deleteOrder` - 逻辑删除订单
|
||||
- `getOrderDetail` - 查询订单详情
|
||||
- `fillOrderInfo` - 填充订单关联信息(买方名称、卖方名称等)
|
||||
|
||||
**日志记录:** 所有操作都通过Logger记录详细日志
|
||||
|
||||
#### 2.4 Controller层
|
||||
- **文件:** `OrderController.java`
|
||||
- **位置:** `aiotagro-cattle-trade/src/main/java/com/aiotagro/cattletrade/business/controller/OrderController.java`
|
||||
- **接口列表:**
|
||||
- `POST /order/list` - 查询订单列表(分页)
|
||||
- `POST /order/add` - 新增订单
|
||||
- `POST /order/edit` - 更新订单
|
||||
- `GET /order/delete` - 删除订单(逻辑删除)
|
||||
- `GET /order/detail` - 查询订单详情
|
||||
|
||||
**权限控制:** 使用SaCheckPermission注解进行权限验证
|
||||
- `order:list` - 列表查询权限
|
||||
- `order:add` - 新增权限
|
||||
- `order:edit` - 编辑权限
|
||||
- `order:delete` - 删除权限
|
||||
- `order:view` - 查看权限
|
||||
|
||||
### 3. 前端实现
|
||||
|
||||
#### 3.1 API接口
|
||||
**文件:** `pc-cattle-transportation/src/api/shipping.js`
|
||||
|
||||
新增接口方法:
|
||||
- `orderPageQuery(data)` - 订单列表查询
|
||||
- `orderAddNew(data)` - 新增订单
|
||||
- `orderUpdate(data)` - 更新订单
|
||||
- `orderDelete(id)` - 删除订单
|
||||
- `orderGetDetail(id)` - 查询订单详情
|
||||
|
||||
#### 3.2 前端页面
|
||||
**文件:** `pc-cattle-transportation/src/views/shipping/orderDialog.vue`
|
||||
|
||||
**功能特性:**
|
||||
- 简化的表单设计,只包含必要字段
|
||||
- 支持多选买方和卖方(使用multiple属性)
|
||||
- 下拉选择结算方式
|
||||
- 远程搜索支持
|
||||
- 分页功能
|
||||
- 支持新增和编辑两种模式
|
||||
|
||||
**表单字段:**
|
||||
- 卖方(sellerId):多选下拉框,支持搜索和分页
|
||||
- 买方(buyerId):多选下拉框,支持搜索和分页
|
||||
- 结算方式(settlementType):单选下拉框,3个选项
|
||||
|
||||
## 核心特性
|
||||
|
||||
### 1. 逻辑删除
|
||||
- 使用MyBatis-Plus的@TableLogic注解
|
||||
- 删除操作不会真正删除数据,只是将is_delete字段设置为1
|
||||
- 查询时会自动过滤已删除的记录
|
||||
|
||||
### 2. 操作日志
|
||||
所有操作都记录详细日志:
|
||||
- 操作类型(新增/修改/删除/查询)
|
||||
- 操作参数
|
||||
- 操作结果
|
||||
- 异常信息
|
||||
|
||||
### 3. 关联查询
|
||||
自动填充关联信息:
|
||||
- 买方名称:从member_user表查询username
|
||||
- 卖方名称:从member_user表查询username
|
||||
- 创建人姓名:从sys_user表查询user_name
|
||||
- 结算方式描述:自动转换(1→上车重量,2→下车重量,3→按肉价结算)
|
||||
|
||||
### 4. 数据校验
|
||||
- 后端参数校验
|
||||
- 前端表单验证
|
||||
- 结算方式枚举值校验
|
||||
|
||||
## 使用方法
|
||||
|
||||
### 后端部署
|
||||
1. 执行SQL脚本创建订单表
|
||||
```sql
|
||||
-- 执行 tradeCattle/create_order_table.sql
|
||||
```
|
||||
|
||||
2. 重启后端服务
|
||||
|
||||
### 前端使用
|
||||
```javascript
|
||||
// 在需要使用的组件中引入
|
||||
import orderDialog from '@/views/shipping/orderDialog.vue'
|
||||
|
||||
// 调用方法
|
||||
const dialogRef = ref(null)
|
||||
dialogRef.value.onShowDialog() // 新增模式
|
||||
dialogRef.value.onShowDialog(orderData) // 编辑模式
|
||||
```
|
||||
|
||||
## 权限配置
|
||||
|
||||
需要在菜单权限系统中配置以下权限:
|
||||
- `order:list` - 订单列表
|
||||
- `order:add` - 新增订单
|
||||
- `order:edit` - 编辑订单
|
||||
- `order:delete` - 删除订单
|
||||
- `order:view` - 查看订单详情
|
||||
|
||||
## 技术栈
|
||||
|
||||
### 后端
|
||||
- Java 8+
|
||||
- Spring Boot
|
||||
- MyBatis-Plus
|
||||
- Sa-Token(权限管理)
|
||||
- SLF4J(日志)
|
||||
|
||||
### 前端
|
||||
- Vue 3
|
||||
- Element Plus
|
||||
- Axios
|
||||
|
||||
## 后续优化建议
|
||||
|
||||
1. 添加订单状态字段
|
||||
2. 添加更多业务字段(如金额、数量等)
|
||||
3. 实现订单导出功能
|
||||
4. 添加订单统计功能
|
||||
5. 实现订单审核流程
|
||||
|
||||
## 文件清单
|
||||
|
||||
### 后端文件
|
||||
- `create_order_table.sql` - 数据库表结构
|
||||
- `Order.java` - 实体类
|
||||
- `OrderMapper.java` - Mapper接口
|
||||
- `IOrderService.java` - Service接口
|
||||
- `OrderServiceImpl.java` - Service实现
|
||||
- `OrderController.java` - Controller
|
||||
|
||||
### 前端文件
|
||||
- `shipping.js` - API接口(新增部分)
|
||||
- `orderDialog.vue` - 订单弹窗组件
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. order是MySQL的保留关键字,创建表时需要用反引号包裹:`` `order` ``
|
||||
2. 买方ID和卖方ID存储为逗号分隔的字符串,使用时需要拆分
|
||||
3. 逻辑删除功能需要确保相关查询都使用正确的字段过滤
|
||||
4. 关联查询可能会影响性能,大量数据时建议添加缓存
|
||||
|
||||
186
pc-cattle-transportation/DRIVER_DELETE_IMPLEMENTATION.md
Normal file
186
pc-cattle-transportation/DRIVER_DELETE_IMPLEMENTATION.md
Normal file
@@ -0,0 +1,186 @@
|
||||
# 司机管理删除功能实现报告
|
||||
|
||||
## 概述
|
||||
|
||||
实现司机管理页面中的删除按钮功能,可以删除数据库中的司机数据。
|
||||
|
||||
## 实现内容
|
||||
|
||||
### 1. 后端实现
|
||||
|
||||
#### 控制器 (`MemberController.java`)
|
||||
|
||||
新增删除司机接口:
|
||||
|
||||
```402:447:tradeCattle/aiotagro-cattle-trade/src/main/java/com/aiotagro/cattletrade/business/controller/MemberController.java
|
||||
/**
|
||||
* 删除司机
|
||||
*/
|
||||
@SaCheckPermission("member:delete")
|
||||
@PostMapping("/deleteDriver")
|
||||
@Transactional
|
||||
public AjaxResult deleteDriver(@RequestBody Map<String, Object> params) {
|
||||
try {
|
||||
Integer id = (Integer) params.get("id");
|
||||
if (id == null) {
|
||||
return AjaxResult.error("司机ID不能为空");
|
||||
}
|
||||
|
||||
// 查询司机信息,获取member_id
|
||||
Map<String, Object> driverInfo = memberDriverMapper.selectDriverById(id);
|
||||
if (driverInfo == null) {
|
||||
return AjaxResult.error("司机信息不存在");
|
||||
}
|
||||
|
||||
Integer memberId = (Integer) driverInfo.get("member_id");
|
||||
|
||||
// 删除司机详情(member_driver表)
|
||||
int driverResult = memberDriverMapper.deleteById(id);
|
||||
|
||||
// 如果member_id存在,也删除member表记录
|
||||
if (memberId != null) {
|
||||
int memberResult = memberMapper.deleteById(memberId);
|
||||
if (memberResult > 0 && driverResult > 0) {
|
||||
return AjaxResult.success("删除成功");
|
||||
} else if (driverResult > 0) {
|
||||
return AjaxResult.success("删除司机信息成功");
|
||||
} else {
|
||||
return AjaxResult.error("删除失败");
|
||||
}
|
||||
} else {
|
||||
if (driverResult > 0) {
|
||||
return AjaxResult.success("删除成功");
|
||||
} else {
|
||||
return AjaxResult.error("删除失败");
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return AjaxResult.error("删除司机失败:" + e.getMessage());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 删除逻辑说明
|
||||
|
||||
1. **删除两个表的数据**:
|
||||
- 删除 `member_driver` 表中的司机详细信息
|
||||
- 删除 `member` 表中的基础会员信息
|
||||
|
||||
2. **事务保证**:使用 `@Transactional` 注解确保数据一致性
|
||||
|
||||
3. **权限控制**:使用 `@SaCheckPermission("member:delete")` 进行权限验证
|
||||
|
||||
### 2. 前端实现
|
||||
|
||||
#### API接口 (`userManage.js`)
|
||||
|
||||
添加删除司机API方法:
|
||||
|
||||
```70:77:pc-cattle-transportation/src/api/userManage.js
|
||||
// 司机 - 删除
|
||||
export function driverDel(id) {
|
||||
return request({
|
||||
url: '/member/deleteDriver',
|
||||
method: 'POST',
|
||||
data: { id },
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
#### 页面实现 (`driver.vue`)
|
||||
|
||||
导入必要的依赖:
|
||||
```60:63:pc-cattle-transportation/src/views/userManage/driver.vue
|
||||
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||
import { Picture } from '@element-plus/icons-vue';
|
||||
import baseSearch from '@/components/common/searchCustom/index.vue';
|
||||
import { driverList, driverDel } from '@/api/userManage.js';
|
||||
```
|
||||
|
||||
实现删除方法:
|
||||
```160:181:pc-cattle-transportation/src/views/userManage/driver.vue
|
||||
// 删除
|
||||
const delClick = (row) => {
|
||||
ElMessageBox.confirm('请确认是否删除该司机数据?', '删除', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
})
|
||||
.then(() => {
|
||||
driverDel(row.id)
|
||||
.then(() => {
|
||||
ElMessage.success('删除成功');
|
||||
getDataList();
|
||||
})
|
||||
.catch((error) => {
|
||||
ElMessage.error('删除失败');
|
||||
console.error('删除失败:', error);
|
||||
});
|
||||
})
|
||||
.catch(() => {
|
||||
// 用户取消删除
|
||||
});
|
||||
};
|
||||
```
|
||||
|
||||
## 功能特点
|
||||
|
||||
1. **二次确认**:点击删除按钮时,会弹出确认对话框
|
||||
2. **直接删除**:确认后直接调用后端接口删除数据库记录
|
||||
3. **级联删除**:删除司机记录时,同时删除关联的会员基础信息
|
||||
4. **自动刷新**:删除成功后自动刷新列表
|
||||
5. **错误处理**:删除失败时显示错误提示
|
||||
6. **事务保护**:使用数据库事务确保数据一致性
|
||||
7. **权限控制**:需要 `member:delete` 权限才能执行删除操作
|
||||
|
||||
## 工作流程
|
||||
|
||||
1. 用户点击"删除"按钮
|
||||
2. 弹出确认对话框:"请确认是否删除该司机数据?"
|
||||
3. 用户点击"确定"
|
||||
4. 前端调用 `/member/deleteDriver` 接口,传递司机ID
|
||||
5. 后端查询司机信息,获取关联的 member_id
|
||||
6. 后端删除 `member_driver` 表中的司机信息
|
||||
7. 后端删除 `member` 表中的会员基础信息
|
||||
8. 返回成功响应
|
||||
9. 前端显示"删除成功"提示
|
||||
10. 自动刷新司机列表
|
||||
|
||||
## 数据库影响
|
||||
|
||||
删除操作会影响以下表:
|
||||
|
||||
1. **member_driver表**:删除司机的详细信息(姓名、车牌、照片等)
|
||||
2. **member表**:删除会员的基础信息(手机号、状态等)
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **不可恢复**:删除操作是物理删除,不可恢复(除非有备份)
|
||||
2. **关联数据**:删除司机前需要检查该司机是否有关联的运单或其他业务数据
|
||||
3. **权限要求**:需要具有 `member:delete` 权限的用户才能删除
|
||||
4. **事务保护**:使用事务确保即使部分删除失败也不会导致数据不一致
|
||||
|
||||
## 后续改进建议
|
||||
|
||||
1. **软删除**:考虑实现软删除(添加 `is_delete` 标记)
|
||||
2. **关联检查**:删除前检查司机是否有关联的运单
|
||||
3. **级联处理**:如果有关联数据,提供选项:
|
||||
- 阻止删除
|
||||
- 解绑后删除
|
||||
- 级联删除所有关联数据
|
||||
4. **操作日志**:记录删除操作的日志
|
||||
|
||||
## 修改的文件
|
||||
|
||||
### 后端
|
||||
- ✅ `tradeCattle/aiotagro-cattle-trade/src/main/java/com/aiotagro/cattletrade/business/controller/MemberController.java` - 新增删除接口
|
||||
|
||||
### 前端
|
||||
- ✅ `pc-cattle-transportation/src/api/userManage.js` - 添加删除API方法
|
||||
- ✅ `pc-cattle-transportation/src/views/userManage/driver.vue` - 实现删除功能
|
||||
|
||||
## 创建时间
|
||||
|
||||
2025-01-16
|
||||
|
||||
114
pc-cattle-transportation/SUPER_ADMIN_PERMISSION_EXPLANATION.md
Normal file
114
pc-cattle-transportation/SUPER_ADMIN_PERMISSION_EXPLANATION.md
Normal file
@@ -0,0 +1,114 @@
|
||||
# 超级管理员权限说明
|
||||
|
||||
## 问题原因
|
||||
|
||||
超级管理员(15900000000)的操作权限没有全部打开的原因是:
|
||||
|
||||
### 1. **权限管理基于角色,而非超级管理员特权**
|
||||
|
||||
当前系统使用**基于角色的权限管理(RBAC)**:
|
||||
- 权限存储在 `sys_role_menu` 表中
|
||||
- 所有使用相同 `roleId` 的用户共享相同的权限
|
||||
- 即使 `roleId=1`(超级管理员角色),也要遵循数据库中的权限配置
|
||||
|
||||
### 2. **超级管理员权限标识**
|
||||
|
||||
系统在代码层面为超级管理员提供了特殊处理:
|
||||
|
||||
```java
|
||||
// StpInterfaceImpl.java 第38-42行
|
||||
if (roleId.equals(RoleConstants.SUPER_ADMIN_ROLE_ID)) {
|
||||
log.info("用户 {} 是超级管理员,拥有所有权限", loginId);
|
||||
// 超级管理员返回通配符权限
|
||||
return Collections.singletonList(RoleConstants.ALL_PERMISSION);
|
||||
}
|
||||
```
|
||||
|
||||
这意味着:
|
||||
- **后端验证**:超级管理员拥有 `*:*:*` 权限,后端不会拦截任何操作
|
||||
- **前端显示**:但前端界面的复选框状态取决于数据库中的 `sys_role_menu` 表
|
||||
|
||||
### 3. **权限界面的作用**
|
||||
|
||||
"操作权限管理"界面中的复选框状态:
|
||||
- ✅ **不影响功能权限**:只是用于展示和编辑数据库中的权限配置
|
||||
- ✅ **超级管理员仍然可以操作**:即使复选框未选中,后端也会允许访问
|
||||
- ⚠️ **前端按钮显示受影响**:如果权限未勾选,前端 `v-hasPermi` 指令会隐藏按钮
|
||||
|
||||
## 解决方案
|
||||
|
||||
### 方案1:为超级管理员角色分配所有权限(推荐)
|
||||
|
||||
在数据库中为 `roleId=1` 分配所有菜单权限:
|
||||
|
||||
```sql
|
||||
-- 查询所有菜单ID
|
||||
SELECT id FROM sys_menu WHERE is_delete = 0;
|
||||
|
||||
-- 为超级管理员角色分配所有菜单权限
|
||||
-- 假设有 100 个菜单,IDs 为 1-100
|
||||
INSERT INTO sys_role_menu (role_id, menu_id)
|
||||
SELECT 1, id FROM sys_menu WHERE is_delete = 0
|
||||
ON DUPLICATE KEY UPDATE role_id = role_id;
|
||||
```
|
||||
|
||||
### 方案2:前端特殊处理超级管理员
|
||||
|
||||
修改前端的权限检查逻辑,让超级管理员始终显示所有按钮:
|
||||
|
||||
```javascript
|
||||
// src/utils/permission.js 或类似的权限检查文件
|
||||
const hasPermission = (permission) => {
|
||||
const userStore = useUserStore();
|
||||
const isSuperAdmin = userStore.roleId === 1; // 超级管理员 roleId=1
|
||||
|
||||
if (isSuperAdmin) {
|
||||
return true; // 超级管理员直接返回 true
|
||||
}
|
||||
|
||||
// 普通用户的权限检查逻辑
|
||||
// ...
|
||||
};
|
||||
```
|
||||
|
||||
### 方案3:完全忽略前端权限检查(不推荐)
|
||||
|
||||
对于超级管理员,可以跳过所有前端权限检查,但这可能带来安全隐患。
|
||||
|
||||
## 建议
|
||||
|
||||
**最佳实践**:
|
||||
1. 在数据库中为超级管理员角色(roleId=1)分配所有菜单权限
|
||||
2. 前端保留权限检查逻辑(安全考虑)
|
||||
3. 后端继续使用 `*:*:*` 特殊处理
|
||||
|
||||
这样既保证了超级管理员的功能完整性,又保持了权限管理的规范性。
|
||||
|
||||
## 如何判断超级管理员是否有权限
|
||||
|
||||
### 前端权限检查(影响按钮显示)
|
||||
|
||||
```vue
|
||||
<!-- 如果权限未勾选,按钮会被隐藏 -->
|
||||
<el-button v-hasPermi="['loading:edit']">编辑</el-button>
|
||||
```
|
||||
|
||||
### 后端权限验证(实际控制)
|
||||
|
||||
```java
|
||||
// 即使前端按钮显示,后端也会验证
|
||||
@SaCheckPermission("loading:edit")
|
||||
public AjaxResult editOrder(...) {
|
||||
// 超级管理员 roleId=1 会被自动放行
|
||||
}
|
||||
```
|
||||
|
||||
## 总结
|
||||
|
||||
**超级管理员的操作权限没有全部打开**是因为:
|
||||
1. 数据库中的 `sys_role_menu` 表没有为超级管理员角色分配所有菜单
|
||||
2. 前端的复选框显示基于数据库配置
|
||||
3. 但这**不影响**后端功能权限:超级管理员仍然可以访问所有接口
|
||||
|
||||
**解决方案**:为超级管理员角色在数据库中分配所有菜单权限即可。
|
||||
|
||||
144
pc-cattle-transportation/USER_DELETE_IMPLEMENTATION.md
Normal file
144
pc-cattle-transportation/USER_DELETE_IMPLEMENTATION.md
Normal file
@@ -0,0 +1,144 @@
|
||||
# 用户管理删除功能实现报告
|
||||
|
||||
## 概述
|
||||
|
||||
实现用户管理页面中的删除按钮功能,可以删除数据库中的用户数据。
|
||||
|
||||
## 实现内容
|
||||
|
||||
### 1. 前端实现 (`pc-cattle-transportation/src/views/system/user.vue`)
|
||||
|
||||
#### 导入必要的依赖
|
||||
```javascript
|
||||
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||
import { sysUserList, sysUserDel, sysUserSave } from '@/api/sys.js';
|
||||
```
|
||||
|
||||
#### 删除用户方法
|
||||
```92:117:pc-cattle-transportation/src/views/system/user.vue
|
||||
// 删除用户
|
||||
const delClick = (row) => {
|
||||
ElMessageBox.confirm('请确认是否删除该用户?', '提示', {
|
||||
cancelButtonText: '取消',
|
||||
confirmButtonText: '确定',
|
||||
type: 'warning',
|
||||
})
|
||||
.then(() => {
|
||||
sysUserDel(row.id)
|
||||
.then(() => {
|
||||
ElMessage.success('删除成功');
|
||||
getDataList();
|
||||
})
|
||||
.catch((error) => {
|
||||
ElMessage.error('删除失败');
|
||||
console.error('删除失败:', error);
|
||||
});
|
||||
})
|
||||
.catch(() => {
|
||||
// 用户取消删除
|
||||
});
|
||||
};
|
||||
```
|
||||
|
||||
#### 表格操作列
|
||||
```20:25:pc-cattle-transportation/src/views/system/user.vue
|
||||
<el-table-column label="操作" width="150">
|
||||
<template #default="scope">
|
||||
<el-button link type="primary" @click="showAddDialog(scope.row)">编辑</el-button>
|
||||
<el-button link type="primary" @click="delClick(scope.row)" style="color: #f56c6c;">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
```
|
||||
|
||||
### 2. API接口 (`pc-cattle-transportation/src/api/sys.js`)
|
||||
|
||||
```89:95:pc-cattle-transportation/src/api/sys.js
|
||||
// 子账号管理-删除
|
||||
export function sysUserDel(id) {
|
||||
return request({
|
||||
url: `/sysUser/delete?id=${id}`,
|
||||
method: 'GET',
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 后端接口实现
|
||||
|
||||
#### Controller (`SysUserController.java`)
|
||||
```46:49:tradeCattle/aiotagro-cattle-trade/src/main/java/com/aiotagro/cattletrade/business/controller/SysUserController.java
|
||||
@GetMapping("/delete")
|
||||
public AjaxResult delete(@RequestParam Integer id) {
|
||||
return userService.delete(id);
|
||||
}
|
||||
```
|
||||
|
||||
#### Service (`SysUserServiceImpl.java`)
|
||||
```80:84:tradeCattle/aiotagro-cattle-trade/src/main/java/com/aiotagro/cattletrade/business/service/impl/SysUserServiceImpl.java
|
||||
@Override
|
||||
public AjaxResult delete(Integer id) {
|
||||
removeById(id);
|
||||
return AjaxResult.success();
|
||||
}
|
||||
```
|
||||
|
||||
## 功能特点
|
||||
|
||||
1. **二次确认**:点击删除按钮时,会弹出确认对话框
|
||||
2. **直接删除**:确认后直接调用后端接口删除数据库记录
|
||||
3. **自动刷新**:删除成功后自动刷新列表
|
||||
4. **错误处理**:删除失败时显示错误提示
|
||||
5. **用户友好**:删除按钮使用红色样式,清晰地表示危险操作
|
||||
|
||||
## 工作流程
|
||||
|
||||
1. 用户点击"删除"按钮
|
||||
2. 弹出确认对话框:"请确认是否删除该用户?"
|
||||
3. 用户点击"确定"
|
||||
4. 前端调用 `/sysUser/delete?id={userId}` 接口
|
||||
5. 后端执行 `removeById(id)` 删除数据库记录
|
||||
6. 返回成功响应
|
||||
7. 前端显示"删除成功"提示
|
||||
8. 自动刷新用户列表
|
||||
|
||||
## 测试建议
|
||||
|
||||
1. **功能测试**
|
||||
- 点击删除按钮,确认对话框正常弹出
|
||||
- 点击取消,不执行删除
|
||||
- 点击确定,用户被删除
|
||||
- 删除后列表自动刷新
|
||||
|
||||
2. **边界测试**
|
||||
- 测试删除不存在的用户ID
|
||||
- 测试网络异常情况
|
||||
|
||||
3. **权限测试**
|
||||
- 确认用户是否有删除权限
|
||||
|
||||
## 修改的文件
|
||||
|
||||
### 前端
|
||||
- ✅ `pc-cattle-transportation/src/views/system/user.vue` - 实现删除功能和完整的数据列表
|
||||
- ✅ `pc-cattle-transportation/src/api/sys.js` - API接口(已存在)
|
||||
|
||||
### 后端
|
||||
- ✅ `tradeCattle/aiotagro-cattle-trade/src/main/java/com/aiotagro/cattletrade/business/controller/SysUserController.java` - Controller接口(已存在)
|
||||
- ✅ `tradeCattle/aiotagro-cattle-trade/src/main/java/com/aiotagro/cattletrade/business/service/impl/SysUserServiceImpl.java` - Service实现(已存在)
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **数据安全**:删除操作是永久性的,不可恢复(除非有备份)
|
||||
2. **权限控制**:建议在后端添加权限校验,只允许特定角色删除用户
|
||||
3. **关联数据**:如果用户有关联数据(如订单、设备等),需要检查是否应该级联删除或阻止删除
|
||||
|
||||
## 后续改进建议
|
||||
|
||||
1. 添加权限校验
|
||||
2. 添加关联数据检查(如用户是否有关联的订单或设备)
|
||||
3. 实现软删除(添加 `isDelete` 标记,而不是物理删除)
|
||||
4. 添加操作日志记录
|
||||
|
||||
## 创建时间
|
||||
|
||||
2025-01-16
|
||||
|
||||
@@ -49,6 +49,15 @@ export function updateDeviceDeliveryId(data) {
|
||||
});
|
||||
}
|
||||
|
||||
// 解绑单个设备(将delivery_id设置为null)
|
||||
export function unbindDevice(deviceId) {
|
||||
return request({
|
||||
url: '/deliveryDevice/updateDeviceDeliveryId',
|
||||
method: 'POST',
|
||||
data: { deviceId, deliveryId: null },
|
||||
});
|
||||
}
|
||||
|
||||
// 批量更新设备weight
|
||||
export function updateDeviceWeights(data) {
|
||||
return request({
|
||||
@@ -210,4 +219,48 @@ export function shippingList(data) {
|
||||
method: 'POST',
|
||||
data,
|
||||
});
|
||||
}
|
||||
|
||||
// ==================== 订单管理接口 ====================
|
||||
// 订单列表查询
|
||||
export function orderPageQuery(data) {
|
||||
return request({
|
||||
url: '/order/list',
|
||||
method: 'POST',
|
||||
data,
|
||||
});
|
||||
}
|
||||
|
||||
// 新增订单
|
||||
export function orderAddNew(data) {
|
||||
return request({
|
||||
url: '/order/add',
|
||||
method: 'POST',
|
||||
data,
|
||||
});
|
||||
}
|
||||
|
||||
// 更新订单
|
||||
export function orderUpdate(data) {
|
||||
return request({
|
||||
url: '/order/edit',
|
||||
method: 'POST',
|
||||
data,
|
||||
});
|
||||
}
|
||||
|
||||
// 删除订单
|
||||
export function orderDelete(id) {
|
||||
return request({
|
||||
url: `/order/delete?id=${id}`,
|
||||
method: 'GET',
|
||||
});
|
||||
}
|
||||
|
||||
// 查询订单详情
|
||||
export function orderGetDetail(id) {
|
||||
return request({
|
||||
url: `/order/detail?id=${id}`,
|
||||
method: 'GET',
|
||||
});
|
||||
}
|
||||
@@ -67,6 +67,15 @@ export function driverDetail(id) {
|
||||
});
|
||||
}
|
||||
|
||||
// 司机 - 删除
|
||||
export function driverDel(id) {
|
||||
return request({
|
||||
url: '/member/deleteDriver',
|
||||
method: 'POST',
|
||||
data: { id },
|
||||
});
|
||||
}
|
||||
|
||||
// --------- 会员管理 -----------
|
||||
// 根据类型获取会员列表(用于装车订单下拉框)
|
||||
export function memberListByType(data) {
|
||||
@@ -75,4 +84,40 @@ export function memberListByType(data) {
|
||||
method: 'POST',
|
||||
data,
|
||||
});
|
||||
}
|
||||
|
||||
// --------- 车辆管理 -----------
|
||||
// 车辆 - 列表
|
||||
export function vehicleList(data) {
|
||||
return request({
|
||||
url: '/vehicle/list',
|
||||
method: 'POST',
|
||||
data,
|
||||
});
|
||||
}
|
||||
|
||||
// 车辆 - 新增
|
||||
export function vehicleAdd(data) {
|
||||
return request({
|
||||
url: '/vehicle/add',
|
||||
method: 'POST',
|
||||
data,
|
||||
});
|
||||
}
|
||||
|
||||
// 车辆 - 编辑
|
||||
export function vehicleEdit(data) {
|
||||
return request({
|
||||
url: '/vehicle/edit',
|
||||
method: 'POST',
|
||||
data,
|
||||
});
|
||||
}
|
||||
|
||||
// 车辆 - 删除
|
||||
export function vehicleDel(id) {
|
||||
return request({
|
||||
url: `/vehicle/delete?id=${id}`,
|
||||
method: 'GET',
|
||||
});
|
||||
}
|
||||
@@ -183,6 +183,16 @@ export const constantRoutes: Array<RouteRecordRaw> = [
|
||||
},
|
||||
component: () => import('~/views/userManage/driver.vue'),
|
||||
},
|
||||
{
|
||||
path: '/userManage/vehicle',
|
||||
name: 'VehicleManage',
|
||||
meta: {
|
||||
title: '车辆管理',
|
||||
keepAlive: true,
|
||||
requireAuth: true,
|
||||
},
|
||||
component: () => import('~/views/userManage/vehicle.vue'),
|
||||
},
|
||||
],
|
||||
},
|
||||
// 早期预警路由
|
||||
|
||||
@@ -52,6 +52,12 @@
|
||||
<template #default="scope">
|
||||
<el-tag type="warning" v-if="scope.row.warningType == 3">运输距离预警</el-tag>
|
||||
<el-tag type="danger" v-if="scope.row.warningType == 2">数量盘单预警</el-tag>
|
||||
<el-tag type="info" v-if="scope.row.warningType == 4">设备停留预警</el-tag>
|
||||
<el-tag type="danger" v-if="scope.row.warningType == 5">高温预警</el-tag>
|
||||
<el-tag type="info" v-if="scope.row.warningType == 6">低温预警</el-tag>
|
||||
<el-tag type="warning" v-if="scope.row.warningType == 7">位置偏离预警</el-tag>
|
||||
<el-tag type="danger" v-if="scope.row.warningType == 8">延误预警</el-tag>
|
||||
<el-tag type="success" v-if="scope.row.warningType == 9">超前到达预警</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="warningTime" label="预警时间" />
|
||||
@@ -121,6 +127,12 @@ const formItemList = reactive([
|
||||
selectOptions: [
|
||||
{ value: 2, text: '数量盘单预警' },
|
||||
{ value: 3, text: '运输距离预警' },
|
||||
{ value: 4, text: '设备停留预警' },
|
||||
{ value: 5, text: '高温预警' },
|
||||
{ value: 6, text: '低温预警' },
|
||||
{ value: 7, text: '位置偏离预警' },
|
||||
{ value: 8, text: '延误预警' },
|
||||
{ value: 9, text: '超前到达预警' },
|
||||
],
|
||||
param: 'warningType',
|
||||
span: 7,
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
<el-descriptions-item label="创建时间:">{{ data.baseInfo.createTime || '' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="登记设备数量:">{{ totalRegisteredDevices }} 个</el-descriptions-item>
|
||||
<el-descriptions-item label="状态:">
|
||||
<el-tag :type="data.baseInfo.status === 2 ? 'success' : 'warning'">{{ getStatusText(data.baseInfo.status) }}</el-tag>
|
||||
<el-tag :type="getStatusType(data.baseInfo.status)">{{ getStatusText(data.baseInfo.status) }}</el-tag>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="车身照片:">
|
||||
<span style="vertical-align: top">
|
||||
@@ -1108,14 +1108,30 @@ const totalRegisteredDevices = computed(() => {
|
||||
const getStatusText = (status) => {
|
||||
const statusMap = {
|
||||
1: '待装车',
|
||||
2: '装车中',
|
||||
3: '运输中',
|
||||
4: '已送达',
|
||||
5: '已完成'
|
||||
2: '已装车/预付款已支付',
|
||||
3: '已装车/尾款待支付',
|
||||
4: '已核验/待买家付款',
|
||||
5: '尾款已付款',
|
||||
6: '发票待开/进项票',
|
||||
7: '发票待开/销项'
|
||||
};
|
||||
return statusMap[status] || '未知状态';
|
||||
};
|
||||
|
||||
// 根据状态返回标签类型(颜色)
|
||||
const getStatusType = (status) => {
|
||||
const typeMap = {
|
||||
1: 'info', // 待装车 - 灰色
|
||||
2: 'success', // 已装车/预付款已支付 - 绿色
|
||||
3: 'warning', // 已装车/尾款待支付 - 橙色
|
||||
4: 'warning', // 已核验/待买家付款 - 橙色
|
||||
5: 'success', // 尾款已付款 - 绿色
|
||||
6: 'primary', // 发票待开/进项票 - 蓝色
|
||||
7: 'primary' // 发票待开/销项 - 蓝色
|
||||
};
|
||||
return typeMap[status] || 'info';
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
data.id = route.query.id;
|
||||
data.status = route.query.status;
|
||||
|
||||
@@ -70,8 +70,10 @@
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted } from 'vue';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import { BmlLushu } from 'vue-baidu-map-3x';
|
||||
import { collarTrack, collarTrackOrder } from '~/api/hardware.js';
|
||||
import { getCollarTrajectory as getCollarTrajectoryFromAbroad } from '~/api/abroad.js';
|
||||
import startIcon from '../../assets/images/qi.png';
|
||||
import endIcon from '../../assets/images/zhong.png';
|
||||
import biaoIcon from '../../assets/images/biaozhu.png';
|
||||
@@ -172,6 +174,60 @@ const reset = () => {
|
||||
// 查询定位
|
||||
const getTrack = () => {
|
||||
data.trackLoading = true;
|
||||
|
||||
// 使用新的API从xq_client_log表查询轨迹数据
|
||||
if (data.deliveryId && data.xqDeviceId) {
|
||||
getCollarTrajectoryFromAbroad({
|
||||
deviceId: data.xqDeviceId,
|
||||
deliveryId: parseInt(data.deliveryId), // 明确转换为整数
|
||||
startTime: formData.time[0] ? formData.time[0] : '',
|
||||
endTime: formData.time[1] ? formData.time[1] : '',
|
||||
})
|
||||
.then((res) => {
|
||||
data.trackLoading = false;
|
||||
console.log('=== 查询轨迹API返回结果:', res);
|
||||
|
||||
if (res.code === 200 && res.data && res.data.length > 0) {
|
||||
data.mapShow = true;
|
||||
data.path = [];
|
||||
|
||||
res.data.forEach((item, index) => {
|
||||
const lng = parseFloat(item.longitude || item.lng || 0);
|
||||
const lat = parseFloat(item.latitude || item.lat || 0);
|
||||
|
||||
if (lng !== 0 && lat !== 0 && !isNaN(lng) && !isNaN(lat)) {
|
||||
data.path.push({
|
||||
lng: lng,
|
||||
lat: lat,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
if (data.path.length > 0) {
|
||||
data.startMark = data.path[0];
|
||||
data.endMark = data.path[data.path.length - 1];
|
||||
console.log('轨迹查询成功,共', data.path.length, '个轨迹点');
|
||||
} else {
|
||||
console.log('没有有效的轨迹点');
|
||||
data.noTrack = true;
|
||||
ElMessage.warning('该时间范围内暂无有效轨迹点');
|
||||
}
|
||||
} else {
|
||||
console.log('没有轨迹数据');
|
||||
ElMessage.warning('该时间范围内暂无轨迹数据');
|
||||
data.noTrack = true;
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('查询轨迹失败:', error);
|
||||
data.trackLoading = false;
|
||||
data.noTrack = true;
|
||||
ElMessage.error('查询轨迹失败');
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// 兼容旧的查询方式
|
||||
if (data.type == 'order') {
|
||||
collarTrack({
|
||||
deliveryId: data.deliveryId,
|
||||
@@ -240,13 +296,54 @@ const onShowTrackDialog = (row) => {
|
||||
data.dialogVisible = true;
|
||||
getNowDate();
|
||||
if (row) {
|
||||
data.deliveryId = row.deliveryId;
|
||||
data.xqDeviceId = row.deviceId;
|
||||
// 确保deliveryId是数字类型
|
||||
data.deliveryId = typeof row.deliveryId === 'string' ? parseInt(row.deliveryId) : row.deliveryId;
|
||||
data.xqDeviceId = row.deviceId || row.sn;
|
||||
data.type = row.type ? row.type : '';
|
||||
data.mapShow = false;
|
||||
data.noTrack = false;
|
||||
data.formData = '';
|
||||
getTrack();
|
||||
|
||||
// 如果传入了trajectoryPoints,直接使用这些轨迹点
|
||||
if (row.trajectoryPoints && row.trajectoryPoints.length > 0) {
|
||||
console.log('=== trackDialog: 直接使用传入的轨迹点 ===');
|
||||
console.log('轨迹点数量:', row.trajectoryPoints.length);
|
||||
console.log('轨迹点数据:', row.trajectoryPoints);
|
||||
|
||||
data.mapShow = true;
|
||||
data.path = [];
|
||||
row.trajectoryPoints.forEach((item, index) => {
|
||||
const lng = parseFloat(item.longitude || item.lng || 0);
|
||||
const lat = parseFloat(item.latitude || item.lat || 0);
|
||||
|
||||
console.log(`轨迹点${index}: latitude=${item.latitude}, longitude=${item.longitude}, lng=${lng}, lat=${lat}`);
|
||||
|
||||
// 检查经纬度是否有效
|
||||
if (lng !== 0 && lat !== 0 && !isNaN(lng) && !isNaN(lat)) {
|
||||
data.path.push({
|
||||
lng: lng,
|
||||
lat: lat,
|
||||
});
|
||||
} else {
|
||||
console.warn(`轨迹点${index}经纬度无效,跳过`);
|
||||
}
|
||||
});
|
||||
|
||||
console.log('最终path数据:', data.path);
|
||||
|
||||
if (data.path.length > 0) {
|
||||
data.startMark = data.path[0]; // 起点
|
||||
data.endMark = data.path[data.path.length - 1]; // 终点
|
||||
console.log('起点:', data.startMark);
|
||||
console.log('终点:', data.endMark);
|
||||
} else {
|
||||
console.error('没有有效的轨迹点,显示空状态');
|
||||
data.noTrack = true;
|
||||
}
|
||||
} else {
|
||||
// 没有传入轨迹点,使用原来的查询逻辑
|
||||
data.formData = '';
|
||||
getTrack();
|
||||
}
|
||||
}
|
||||
};
|
||||
defineExpose({
|
||||
|
||||
@@ -255,6 +255,11 @@ const generateRoutes = async () => {
|
||||
await permissionStore.generateRoutes();
|
||||
}
|
||||
|
||||
// 确保路径以斜杠开头
|
||||
if (!targetPath.startsWith('/')) {
|
||||
targetPath = '/' + targetPath;
|
||||
}
|
||||
|
||||
// 使用replace而不是push,避免路由警告
|
||||
try {
|
||||
await router.replace({ path: targetPath });
|
||||
|
||||
@@ -55,7 +55,6 @@
|
||||
<el-button
|
||||
type="primary"
|
||||
size="small"
|
||||
v-hasPermi="['permission:operation:assign']"
|
||||
@click="handleSaveUserPermissions"
|
||||
:disabled="!currentUser"
|
||||
:loading="saveLoading"
|
||||
@@ -66,7 +65,6 @@
|
||||
<el-button
|
||||
type="danger"
|
||||
size="small"
|
||||
v-hasPermi="['permission:operation:assign']"
|
||||
@click="handleClearUserPermissions"
|
||||
:disabled="!currentUser"
|
||||
:loading="clearLoading"
|
||||
|
||||
@@ -15,12 +15,26 @@
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="发货方" prop="shipper">
|
||||
<el-input v-model="formData.shipper" placeholder="请输入发货方" />
|
||||
<el-select v-model="formData.shipper" placeholder="请选择发货方" clearable filterable style="width: 100%">
|
||||
<el-option
|
||||
v-for="item in supplierList"
|
||||
:key="item.id"
|
||||
:label="item.username || item.mobile"
|
||||
:value="item.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="采购方" prop="buyer">
|
||||
<el-input v-model="formData.buyer" placeholder="请输入采购方" />
|
||||
<el-select v-model="formData.buyer" placeholder="请选择采购方" clearable filterable style="width: 100%">
|
||||
<el-option
|
||||
v-for="item in buyerList"
|
||||
:key="item.id"
|
||||
:label="item.username || item.mobile"
|
||||
:value="item.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
@@ -28,12 +42,26 @@
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="车牌号" prop="plateNumber">
|
||||
<el-input v-model="formData.plateNumber" placeholder="如:京A12345" />
|
||||
<el-select v-model="formData.plateNumber" placeholder="请选择车牌号" clearable filterable style="width: 100%">
|
||||
<el-option
|
||||
v-for="item in vehicleOptions"
|
||||
:key="item.id"
|
||||
:label="item.licensePlate"
|
||||
:value="item.licensePlate"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="司机姓名" prop="driverName">
|
||||
<el-input v-model="formData.driverName" placeholder="请输入司机姓名" />
|
||||
<el-select v-model="formData.driverName" placeholder="请选择司机" clearable filterable style="width: 100%" @change="handleDriverChange">
|
||||
<el-option
|
||||
v-for="item in driverOptions"
|
||||
:key="item.id"
|
||||
:label="item.username"
|
||||
:value="item.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
@@ -55,9 +83,9 @@
|
||||
>
|
||||
<el-option
|
||||
v-for="item in serverList"
|
||||
:key="item.id"
|
||||
:label="item.deviceNo || item.deviceId"
|
||||
:value="item.id"
|
||||
:key="item.deviceId"
|
||||
:label="item.deviceId + (item.name ? ' - ' + item.name : '')"
|
||||
:value="item.deviceId"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
@@ -77,9 +105,9 @@
|
||||
>
|
||||
<el-option
|
||||
v-for="item in eartagList"
|
||||
:key="item.id"
|
||||
:label="item.deviceNo || item.deviceId"
|
||||
:value="item.id"
|
||||
:key="item.deviceId"
|
||||
:label="item.deviceId + (item.name ? ' - ' + item.name : '')"
|
||||
:value="item.deviceId"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
@@ -96,9 +124,9 @@
|
||||
>
|
||||
<el-option
|
||||
v-for="item in collarList"
|
||||
:key="item.id"
|
||||
:label="item.deviceNo || item.deviceId"
|
||||
:value="item.id"
|
||||
:key="item.deviceId"
|
||||
:label="item.deviceId + (item.name ? ' - ' + item.name : '')"
|
||||
:value="item.deviceId"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
@@ -131,14 +159,36 @@
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-col :span="24">
|
||||
<el-form-item label="起点地址" prop="startLocation">
|
||||
<el-input v-model="formData.startLocation" placeholder="请输入起点地址" />
|
||||
<el-input
|
||||
v-model="formData.startLocation"
|
||||
placeholder="请输入或在地图上选择起点地址"
|
||||
@click="showStartLocationMap = true"
|
||||
readonly
|
||||
style="cursor: pointer;"
|
||||
>
|
||||
<template #append>
|
||||
<el-button @click="showStartLocationMap = true">在地图上选择</el-button>
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
</el-row>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="24">
|
||||
<el-form-item label="目的地地址" prop="endLocation">
|
||||
<el-input v-model="formData.endLocation" placeholder="请输入目的地地址" />
|
||||
<el-input
|
||||
v-model="formData.endLocation"
|
||||
placeholder="请输入或在地图上选择目的地地址"
|
||||
@click="showEndLocationMap = true"
|
||||
readonly
|
||||
style="cursor: pointer;"
|
||||
>
|
||||
<template #append>
|
||||
<el-button @click="showEndLocationMap = true">在地图上选择</el-button>
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
@@ -197,12 +247,57 @@
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 起点地址选择地图 -->
|
||||
<el-dialog v-model="showStartLocationMap" title="选择起点地址" width="900px">
|
||||
<baidu-map
|
||||
class="map"
|
||||
:center="{lng: 116.404, lat: 39.915}"
|
||||
:zoom="15"
|
||||
:scroll-wheel-zoom="true"
|
||||
@click="handleStartLocationClick"
|
||||
style="height: 500px"
|
||||
>
|
||||
<bm-marker v-if="formData.startLon && formData.startLat" :position="{lng: parseFloat(formData.startLon), lat: parseFloat(formData.startLat)}" :dragging="true" @dragging="handleStartMarkerDrag" />
|
||||
<bm-map-type :map-types="['BMAP_NORMAL_MAP', 'BMAP_HYBRID_MAP']"></bm-map-type>
|
||||
</baidu-map>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="showStartLocationMap = false">取消</el-button>
|
||||
<el-button type="primary" @click="showStartLocationMap = false">确定</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 目的地地址选择地图 -->
|
||||
<el-dialog v-model="showEndLocationMap" title="选择目的地地址" width="900px">
|
||||
<baidu-map
|
||||
class="map"
|
||||
:center="{lng: 116.404, lat: 39.915}"
|
||||
:zoom="15"
|
||||
:scroll-wheel-zoom="true"
|
||||
@click="handleEndLocationClick"
|
||||
style="height: 500px"
|
||||
>
|
||||
<bm-marker v-if="formData.endLon && formData.endLat" :position="{lng: parseFloat(formData.endLon), lat: parseFloat(formData.endLat)}" :dragging="true" @dragging="handleEndMarkerDrag" />
|
||||
<bm-map-type :map-types="['BMAP_NORMAL_MAP', 'BMAP_HYBRID_MAP']"></bm-map-type>
|
||||
</baidu-map>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="showEndLocationMap = false">取消</el-button>
|
||||
<el-button type="primary" @click="showEndLocationMap = false">确定</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive } from 'vue';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import { createDelivery, getAvailableServers, getAvailableEartags, getAvailableCollars } from '@/api/shipping.js';
|
||||
import { createDelivery, updateDeviceDeliveryId } from '@/api/shipping.js';
|
||||
import { memberListByType, driverList as fetchDriverList, vehicleList as fetchVehicleList } from '@/api/userManage.js';
|
||||
import { iotDeviceQueryList } from '@/api/hardware.js';
|
||||
import { BaiduMap, BmMapType, BmMarker } from 'vue-baidu-map-3x';
|
||||
|
||||
const dialogVisible = ref(false);
|
||||
const formRef = ref(null);
|
||||
@@ -210,12 +305,18 @@ const submitLoading = ref(false);
|
||||
const serverList = ref([]);
|
||||
const eartagList = ref([]);
|
||||
const collarList = ref([]);
|
||||
const supplierList = ref([]);
|
||||
const buyerList = ref([]);
|
||||
const driverOptions = ref([]);
|
||||
const vehicleOptions = ref([]);
|
||||
const showStartLocationMap = ref(false);
|
||||
const showEndLocationMap = ref(false);
|
||||
|
||||
const formData = reactive({
|
||||
shipper: '',
|
||||
buyer: '',
|
||||
plateNumber: '',
|
||||
driverName: '',
|
||||
shipper: null,
|
||||
buyer: null,
|
||||
plateNumber: null,
|
||||
driverName: null,
|
||||
driverPhone: '',
|
||||
serverId: null,
|
||||
eartagIds: [],
|
||||
@@ -224,6 +325,10 @@ const formData = reactive({
|
||||
estimatedArrivalTime: '',
|
||||
startLocation: '',
|
||||
endLocation: '',
|
||||
startLat: '',
|
||||
startLon: '',
|
||||
endLat: '',
|
||||
endLon: '',
|
||||
cattleCount: 1,
|
||||
estimatedWeight: null,
|
||||
quarantineCertNo: '',
|
||||
@@ -266,8 +371,8 @@ const validateArrivalTime = (rule, value, callback) => {
|
||||
};
|
||||
|
||||
const rules = {
|
||||
shipper: [{ required: true, message: '请输入发货方', trigger: 'blur' }],
|
||||
buyer: [{ required: true, message: '请输入采购方', trigger: 'blur' }],
|
||||
shipper: [{ required: true, message: '请选择发货方', trigger: 'change' }],
|
||||
buyer: [{ required: true, message: '请选择采购方', trigger: 'change' }],
|
||||
plateNumber: [{ required: true, validator: validatePlateNumber, trigger: 'blur' }],
|
||||
driverName: [{ required: true, message: '请输入司机姓名', trigger: 'blur' }],
|
||||
driverPhone: [{ required: true, validator: validatePhone, trigger: 'blur' }],
|
||||
@@ -279,45 +384,173 @@ const rules = {
|
||||
estimatedWeight: [{ required: true, message: '请输入预估重量', trigger: 'blur' }],
|
||||
};
|
||||
|
||||
// 完善提交数据
|
||||
const buildSubmitData = () => {
|
||||
const data = { ...formData };
|
||||
// 确保经纬度是字符串格式
|
||||
if (data.startLat) data.startLat = String(data.startLat);
|
||||
if (data.startLon) data.startLon = String(data.startLon);
|
||||
if (data.endLat) data.endLat = String(data.endLat);
|
||||
if (data.endLon) data.endLon = String(data.endLon);
|
||||
return data;
|
||||
};
|
||||
|
||||
// 打开弹窗
|
||||
const open = () => {
|
||||
dialogVisible.value = true;
|
||||
loadSupplierAndBuyerList();
|
||||
loadDeviceOptions();
|
||||
loadDriverList();
|
||||
loadVehicleList();
|
||||
};
|
||||
|
||||
// 加载供应商和采购方列表
|
||||
const loadSupplierAndBuyerList = async () => {
|
||||
try {
|
||||
// 加载供应商列表 (type=2)
|
||||
const supplierRes = await memberListByType({ type: 2, pageNum: 1, pageSize: 9999 });
|
||||
if (supplierRes.code === 200) {
|
||||
supplierList.value = supplierRes.data?.rows || supplierRes.data || [];
|
||||
}
|
||||
|
||||
// 加载采购方列表 (type=4)
|
||||
const buyerRes = await memberListByType({ type: 4, pageNum: 1, pageSize: 9999 });
|
||||
if (buyerRes.code === 200) {
|
||||
buyerList.value = buyerRes.data?.rows || buyerRes.data || [];
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载供应商/采购方列表失败', error);
|
||||
}
|
||||
};
|
||||
|
||||
// 加载设备选项
|
||||
const loadDeviceOptions = async () => {
|
||||
try {
|
||||
// 加载主机设备
|
||||
const serverRes = await getAvailableServers({ pageNum: 1, pageSize: 9999 });
|
||||
// 统一使用 iotDeviceQueryList 接口,通过 type 参数区分设备类型
|
||||
// type: 1-主机, 2-耳标, 4-项圈
|
||||
const [serverRes, eartagRes, collarRes] = await Promise.all([
|
||||
iotDeviceQueryList({ pageNum: 1, pageSize: 9999, type: 1 }), // 主机
|
||||
iotDeviceQueryList({ pageNum: 1, pageSize: 9999, type: 2 }), // 耳标
|
||||
iotDeviceQueryList({ pageNum: 1, pageSize: 9999, type: 4 }) // 项圈
|
||||
]);
|
||||
|
||||
if (serverRes.code === 200) {
|
||||
serverList.value = serverRes.data?.rows || serverRes.data || [];
|
||||
// 过滤出主机类型设备 (type === 1)
|
||||
const allServers = serverRes.data?.rows || serverRes.data || [];
|
||||
serverList.value = allServers.filter(item => item.type === 1 || item.type === '1');
|
||||
}
|
||||
|
||||
// 加载耳标设备
|
||||
const eartagRes = await getAvailableEartags({ pageNum: 1, pageSize: 9999 });
|
||||
|
||||
if (eartagRes.code === 200) {
|
||||
eartagList.value = eartagRes.data?.rows || eartagRes.data || [];
|
||||
// 过滤出耳标类型设备 (type === 2)
|
||||
const allEartags = eartagRes.data?.rows || eartagRes.data || [];
|
||||
eartagList.value = allEartags.filter(item => item.type === 2 || item.type === '2');
|
||||
}
|
||||
|
||||
// 加载项圈设备
|
||||
const collarRes = await getAvailableCollars({ pageNum: 1, pageSize: 9999 });
|
||||
|
||||
if (collarRes.code === 200) {
|
||||
collarList.value = collarRes.data?.rows || collarRes.data || [];
|
||||
// 过滤出项圈类型设备 (type === 4)
|
||||
const allCollars = collarRes.data?.rows || collarRes.data || [];
|
||||
collarList.value = allCollars.filter(item => item.type === 4 || item.type === '4');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载设备列表失败', error);
|
||||
}
|
||||
};
|
||||
|
||||
// 加载司机列表
|
||||
const loadDriverList = async () => {
|
||||
try {
|
||||
const res = await fetchDriverList({ pageNum: 1, pageSize: 9999 });
|
||||
if (res.code === 200) {
|
||||
driverOptions.value = res.data?.rows || res.data || [];
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载司机列表失败', error);
|
||||
}
|
||||
};
|
||||
|
||||
// 加载车辆列表
|
||||
const loadVehicleList = async () => {
|
||||
try {
|
||||
const res = await fetchVehicleList({ pageNum: 1, pageSize: 9999 });
|
||||
if (res.code === 200) {
|
||||
vehicleOptions.value = res.data?.data?.rows || res.data?.rows || res.data || [];
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载车辆列表失败', error);
|
||||
}
|
||||
};
|
||||
|
||||
// 司机选择变化时自动填充电话
|
||||
const handleDriverChange = (driverId) => {
|
||||
if (driverId) {
|
||||
const driver = driverOptions.value.find(item => item.id === driverId);
|
||||
if (driver && driver.mobile) {
|
||||
formData.driverPhone = driver.mobile;
|
||||
}
|
||||
} else {
|
||||
formData.driverPhone = '';
|
||||
}
|
||||
};
|
||||
|
||||
// 更新选中设备的delivery_id
|
||||
const updateSelectedDevicesDeliveryId = async (deliveryId) => {
|
||||
try {
|
||||
const devicesToUpdate = [];
|
||||
|
||||
// 收集所有选中的设备
|
||||
if (formData.serverId) {
|
||||
devicesToUpdate.push(formData.serverId);
|
||||
}
|
||||
if (formData.eartagIds && formData.eartagIds.length > 0) {
|
||||
devicesToUpdate.push(...formData.eartagIds);
|
||||
}
|
||||
if (formData.collarIds && formData.collarIds.length > 0) {
|
||||
devicesToUpdate.push(...formData.collarIds);
|
||||
}
|
||||
|
||||
// 批量更新设备的delivery_id
|
||||
for (const deviceId of devicesToUpdate) {
|
||||
await updateDeviceDeliveryId({
|
||||
deviceId: deviceId,
|
||||
deliveryId: deliveryId
|
||||
});
|
||||
}
|
||||
|
||||
console.log(`成功更新 ${devicesToUpdate.length} 个设备的delivery_id`);
|
||||
} catch (error) {
|
||||
console.error('更新设备delivery_id失败:', error);
|
||||
// 不阻止流程,只记录错误
|
||||
}
|
||||
};
|
||||
|
||||
// 提交表单
|
||||
const handleSubmit = () => {
|
||||
formRef.value.validate(async (valid) => {
|
||||
if (valid) {
|
||||
// 验证地址经纬度
|
||||
if (!formData.startLat || !formData.startLon) {
|
||||
ElMessage.warning('请在地图上选择起点位置');
|
||||
return;
|
||||
}
|
||||
if (!formData.endLat || !formData.endLon) {
|
||||
ElMessage.warning('请在地图上选择目的地位置');
|
||||
return;
|
||||
}
|
||||
|
||||
submitLoading.value = true;
|
||||
try {
|
||||
const res = await createDelivery(formData);
|
||||
const submitData = buildSubmitData();
|
||||
console.log('提交的数据:', submitData);
|
||||
const res = await createDelivery(submitData);
|
||||
if (res.code === 200) {
|
||||
// 获取新创建的运送清单ID
|
||||
const newDeliveryId = res.data?.id;
|
||||
|
||||
if (newDeliveryId) {
|
||||
// 更新设备的delivery_id
|
||||
await updateSelectedDevicesDeliveryId(newDeliveryId);
|
||||
}
|
||||
|
||||
ElMessage.success('创建成功');
|
||||
dialogVisible.value = false;
|
||||
emit('success');
|
||||
@@ -325,6 +558,7 @@ const handleSubmit = () => {
|
||||
ElMessage.error(res.msg || '创建失败');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('创建失败:', error);
|
||||
ElMessage.error('创建失败,请稍后重试');
|
||||
} finally {
|
||||
submitLoading.value = false;
|
||||
@@ -333,10 +567,64 @@ const handleSubmit = () => {
|
||||
});
|
||||
};
|
||||
|
||||
// 地图点击事件 - 起点
|
||||
const handleStartLocationClick = (e) => {
|
||||
formData.startLon = e.point.lng;
|
||||
formData.startLat = e.point.lat;
|
||||
// 反向地理编码获取地址
|
||||
const geocoder = new window.BMap.Geocoder();
|
||||
geocoder.getLocation(e.point, (res) => {
|
||||
if (res) {
|
||||
formData.startLocation = res.address;
|
||||
ElMessage.success('已设置起点地址');
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// 起点标记拖拽事件
|
||||
const handleStartMarkerDrag = (e) => {
|
||||
formData.startLon = e.point.lng;
|
||||
formData.startLat = e.point.lat;
|
||||
const geocoder = new window.BMap.Geocoder();
|
||||
geocoder.getLocation(e.point, (res) => {
|
||||
if (res) {
|
||||
formData.startLocation = res.address;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// 地图点击事件 - 目的地
|
||||
const handleEndLocationClick = (e) => {
|
||||
formData.endLon = e.point.lng;
|
||||
formData.endLat = e.point.lat;
|
||||
// 反向地理编码获取地址
|
||||
const geocoder = new window.BMap.Geocoder();
|
||||
geocoder.getLocation(e.point, (res) => {
|
||||
if (res) {
|
||||
formData.endLocation = res.address;
|
||||
ElMessage.success('已设置目的地地址');
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// 目的地标记拖拽事件
|
||||
const handleEndMarkerDrag = (e) => {
|
||||
formData.endLon = e.point.lng;
|
||||
formData.endLat = e.point.lat;
|
||||
const geocoder = new window.BMap.Geocoder();
|
||||
geocoder.getLocation(e.point, (res) => {
|
||||
if (res) {
|
||||
formData.endLocation = res.address;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// 关闭弹窗
|
||||
const handleClose = () => {
|
||||
formRef.value?.resetFields();
|
||||
dialogVisible.value = false;
|
||||
showStartLocationMap.value = false;
|
||||
showEndLocationMap.value = false;
|
||||
};
|
||||
|
||||
// 暴露方法给父组件
|
||||
@@ -348,6 +636,11 @@ const emit = defineEmits(['success']);
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.map {
|
||||
width: 100%;
|
||||
height: 500px;
|
||||
}
|
||||
|
||||
.dialog-footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
|
||||
@@ -842,20 +842,24 @@ const updateDeviceWeightsLocal = (customDevices = null) => {
|
||||
// 否则收集当前表单中的设备重量
|
||||
// 添加智能耳标的重量
|
||||
data.deliveryDevices.forEach(device => {
|
||||
if (device.bindWeight && device.bindWeight.trim() !== '') {
|
||||
// 安全地检查bindWeight值
|
||||
const weightStr = String(device.bindWeight || '').trim();
|
||||
if (weightStr !== '' && !isNaN(parseFloat(weightStr))) {
|
||||
devices.push({
|
||||
deviceId: device.deviceId,
|
||||
weight: parseFloat(device.bindWeight)
|
||||
weight: parseFloat(weightStr)
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 添加智能项圈的重量
|
||||
data.xqDevices.forEach(device => {
|
||||
if (device.bindWeight && device.bindWeight.trim() !== '') {
|
||||
// 安全地检查bindWeight值
|
||||
const weightStr = String(device.bindWeight || '').trim();
|
||||
if (weightStr !== '' && !isNaN(parseFloat(weightStr))) {
|
||||
devices.push({
|
||||
deviceId: device.deviceId,
|
||||
weight: parseFloat(device.bindWeight)
|
||||
weight: parseFloat(weightStr)
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -4,143 +4,50 @@
|
||||
<!-- 横向滚动操作栏 -->
|
||||
<div class="operation-scroll-bar">
|
||||
<div class="operation-scroll-container">
|
||||
<el-button type="primary" v-hasPermi="['loading:create']" @click="showAddDialog(null)">创建装车订单</el-button>
|
||||
<!-- <el-button
|
||||
<el-button type="primary" v-hasPermi="['loading:create']" @click="showAddDialog(null)">创建订单</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
v-hasPermi="['loading:add']"
|
||||
@click="showCreateDeliveryDialog"
|
||||
style="margin-left: 10px"
|
||||
>
|
||||
新增运送清单
|
||||
</el-button> -->
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="main-container">
|
||||
<el-table :data="rows" :key="data.tableKey" border v-loading="data.dataListLoading" element-loading-text="数据加载中..." style="width: 100%">
|
||||
<el-table-column label="装车订单编号" prop="deliveryNumber">
|
||||
<el-table-column label="订单编号" prop="deliveryNumber" width="180">
|
||||
<template #default="scope">
|
||||
{{ scope.row.deliveryNumber || '--' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="订单标题" prop="deliveryTitle">
|
||||
<el-table-column label="供应商" prop="supplierName" width="150">
|
||||
<template #default="scope">
|
||||
{{ scope.row.deliveryTitle || '--' }}
|
||||
{{ scope.row.supplierName || '--' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="起始地" prop="startLocation">
|
||||
<el-table-column label="采购商" prop="buyerName" width="150">
|
||||
<template #default="scope">
|
||||
{{ scope.row.startLocation || '--' }}
|
||||
{{ scope.row.buyerName || '--' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="目的地" prop="endLocation">
|
||||
<el-table-column label="结算方式" prop="settlementMethod" width="150">
|
||||
<template #default="scope">
|
||||
{{ scope.row.endLocation || '--' }}
|
||||
{{ getSettlementMethod(scope.row) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="采购单价(元/公斤)" prop="buyerPrice">
|
||||
<template #default="scope">
|
||||
{{ scope.row.buyerPrice || '0' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="销售单价(元/公斤)" prop="salePrice">
|
||||
<template #default="scope">
|
||||
{{ scope.row.salePrice || '0' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="约定单价(元/公斤)" prop="firmPrice">
|
||||
<template #default="scope">
|
||||
{{ scope.row.firmPrice || '0' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="装车数量" prop="ratedQuantity">
|
||||
<template #default="scope">
|
||||
{{ scope.row.ratedQuantity || '0' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="已分配设备数量" prop="bindJbqCount">
|
||||
<template #default="scope">
|
||||
{{ getTotalDeviceCount(scope.row.id) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="已佩戴设备数量" prop="wareCount">
|
||||
<template #default="scope">
|
||||
<span :style="{ color: getTotalDeviceCount(scope.row.id) == scope.row.wareCount ? '' : 'red' }">
|
||||
{{ scope.row.wareCount || '0' }}
|
||||
</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="创建人" prop="createByName">
|
||||
<el-table-column label="创建人" prop="createByName" width="120">
|
||||
<template #default="scope">
|
||||
{{ scope.row.createByName || '--' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="创建时间" prop="createTime">
|
||||
<el-table-column label="创建时间" prop="createTime" width="180">
|
||||
<template #default="scope">
|
||||
{{ scope.row.createTime || '--' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="核验状态" prop="statusDesc" width="100" :key="`status-${data.forceUpdate}`">
|
||||
<template #default="scope">
|
||||
<!-- 调试信息 -->
|
||||
<div style="display: none;">{{ console.log('核验状态调试:', { status: scope.row.status, statusDesc: scope.row.statusDesc, rowId: scope.row.id }) }}</div>
|
||||
<el-tag :type="getStatusTagType(scope.row.status)" :key="`tag-${scope.row.id}-${data.forceUpdate}`">
|
||||
{{ scope.row.statusDesc || getStatusText(scope.row.status) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="登记设备数量" prop="registeredJbqCount" width="120" :key="`count-${data.forceUpdate}`">
|
||||
<template #default="scope">
|
||||
<!-- 调试信息 -->
|
||||
<div style="display: none;">{{ console.log('设备数量调试:', { registeredJbqCount: scope.row.registeredJbqCount, totalDeviceCount: getTotalDeviceCount(scope.row.id) }) }}</div>
|
||||
<span :key="`count-span-${scope.row.id}-${data.forceUpdate}`">{{ getTotalDeviceCount(scope.row.id) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="车内盘点耳标数量" prop="earTagCount" width="140" :key="`ear-tag-${data.forceUpdate}`">
|
||||
<template #default="scope">
|
||||
<!-- 调试信息 -->
|
||||
<div style="display: none;">{{ console.log('耳标数量调试:', { earTagCount: scope.row.earTagCount, actualEarTagCount: getEarTagCount(scope.row.id) }) }}</div>
|
||||
<span :key="`ear-tag-span-${scope.row.id}-${data.forceUpdate}`">{{ getEarTagCount(scope.row.id) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="车牌号" prop="licensePlate" width="120">
|
||||
<template #default="scope">
|
||||
{{ scope.row.licensePlate || '--' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="司机姓名" prop="driverName" width="120">
|
||||
<template #default="scope">
|
||||
{{ scope.row.driverName || '--' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="车身照片" width="200">
|
||||
<template #default="scope">
|
||||
<div style="display: flex; flex-wrap: wrap; gap: 5px;">
|
||||
<!-- 使用前端分割逻辑处理车身照片 -->
|
||||
<template v-if="getProcessedCarPhotos(scope.row).length > 0">
|
||||
<el-image
|
||||
v-for="(img, index) in getProcessedCarPhotos(scope.row)"
|
||||
:key="'car-' + index"
|
||||
style="width: 60px; height: 60px; border-radius: 4px; border: 1px solid #dcdfe6;"
|
||||
:src="img"
|
||||
fit="cover"
|
||||
:lazy="true"
|
||||
:preview-src-list="getProcessedCarPhotos(scope.row)"
|
||||
preview-teleported
|
||||
@error="handleImageError(img, index)"
|
||||
>
|
||||
<template #error>
|
||||
<div style="width: 60px; height: 60px; display: flex; justify-content: center; align-items: center; background: #f5f7fa;">
|
||||
<el-icon style="font-size: 24px; color: #c0c4cc;"><Picture /></el-icon>
|
||||
</div>
|
||||
</template>
|
||||
</el-image>
|
||||
</template>
|
||||
<!-- 无照片时显示占位 -->
|
||||
<span v-else style="color: #999; font-size: 12px; display: flex; align-items: center;">暂无照片</span>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="420">
|
||||
<template #default="scope">
|
||||
<el-button link type="primary" :disabled="scope.row.status != 1" v-hasPermi="['loading:edit']" @click="showEditDialog(scope.row)">编辑</el-button>
|
||||
@@ -201,41 +108,18 @@ const formItemList = reactive([
|
||||
placeholder: '请输入运单号',
|
||||
},
|
||||
{
|
||||
label: '订单标题',
|
||||
label: '供应商',
|
||||
type: 'input',
|
||||
param: 'deliveryTitle',
|
||||
param: 'supplierName',
|
||||
span: 6,
|
||||
placeholder: '请输入订单标题',
|
||||
placeholder: '请输入供应商',
|
||||
},
|
||||
{
|
||||
label: '目的地',
|
||||
label: '采购商',
|
||||
type: 'input',
|
||||
param: 'endLocation',
|
||||
param: 'buyerName',
|
||||
span: 6,
|
||||
placeholder: '请输入目的地',
|
||||
},
|
||||
{
|
||||
label: '车牌号',
|
||||
type: 'input',
|
||||
param: 'licensePlate',
|
||||
span: 6,
|
||||
placeholder: '请输入车牌号',
|
||||
},
|
||||
{
|
||||
label: '核验状态',
|
||||
type: 'select',
|
||||
param: 'status',
|
||||
span: 6,
|
||||
placeholder: '请选择核验状态',
|
||||
selectOptions: [
|
||||
{ text: '待装车', value: 1 },
|
||||
{ text: '已装车/待资金方付款', value: 2 },
|
||||
{ text: '待核验/资金方已付款', value: 3 },
|
||||
{ text: '已核验/待买家付款', value: 4 },
|
||||
{ text: '买家已付款', value: 5 }
|
||||
],
|
||||
labelKey: 'text',
|
||||
valueKey: 'value',
|
||||
placeholder: '请输入采购商',
|
||||
},
|
||||
{
|
||||
label: '创建时间',
|
||||
@@ -359,25 +243,25 @@ const getDataList = () => {
|
||||
console.log('API响应:', res);
|
||||
console.log('数据行数:', res.data.rows ? res.data.rows.length : 0);
|
||||
|
||||
// 前端精确搜索:在API返回的数据中根据车牌号精确搜索
|
||||
if (searchParams.licensePlate && res.data.rows && res.data.rows.length > 0) {
|
||||
console.log('=== 前端精确搜索车牌号 ===');
|
||||
console.log('搜索车牌号:', searchParams.licensePlate);
|
||||
console.log('API返回的所有车牌号:');
|
||||
res.data.rows.forEach((row, index) => {
|
||||
console.log(`第${index + 1}行车牌号:`, row.licensePlate);
|
||||
});
|
||||
|
||||
// 精确匹配车牌号
|
||||
const filteredRows = res.data.rows.filter(row => row.licensePlate === searchParams.licensePlate);
|
||||
console.log('精确匹配结果:', filteredRows.length, '条');
|
||||
|
||||
// 前端精确搜索:根据条件精确搜索
|
||||
if (searchParams.buyerName && res.data.rows && res.data.rows.length > 0) {
|
||||
// 精确匹配采购商名称
|
||||
const filteredRows = res.data.rows.filter(row => row.buyerName === searchParams.buyerName);
|
||||
if (filteredRows.length > 0) {
|
||||
res.data.rows = filteredRows;
|
||||
res.data.total = filteredRows.length;
|
||||
} else {
|
||||
res.data.rows = [];
|
||||
res.data.total = 0;
|
||||
}
|
||||
}
|
||||
if (searchParams.supplierName && res.data.rows && res.data.rows.length > 0) {
|
||||
// 精确匹配供应商名称
|
||||
const filteredRows = res.data.rows.filter(row => row.supplierName === searchParams.supplierName);
|
||||
if (filteredRows.length > 0) {
|
||||
console.log('找到匹配的车牌号数据:', filteredRows);
|
||||
res.data.rows = filteredRows;
|
||||
res.data.total = filteredRows.length;
|
||||
} else {
|
||||
console.log('未找到匹配的车牌号,显示空结果');
|
||||
res.data.rows = [];
|
||||
res.data.total = 0;
|
||||
}
|
||||
@@ -638,6 +522,22 @@ const getProcessedCarPhotos = (row) => {
|
||||
return carImgUrls;
|
||||
};
|
||||
|
||||
// 获取结算方式
|
||||
const getSettlementMethod = (row) => {
|
||||
// 根据现有字段判断结算方式
|
||||
// 如果有空车磅重和装车磅重,使用上车重量结算
|
||||
// 如果有落地磅重,使用下车重量结算
|
||||
// 如果有约定单价,使用按照肉价结算
|
||||
|
||||
if (row.emptyWeight && row.entruckWeight) {
|
||||
return '上车重量';
|
||||
} else if (row.landingEntruckWeight) {
|
||||
return '下车重量';
|
||||
} else if (row.firmPrice) {
|
||||
return '按照肉价';
|
||||
}
|
||||
return '--';
|
||||
};
|
||||
|
||||
// 监听rows变化,强制更新表格
|
||||
watch(rows, (newRows) => {
|
||||
|
||||
@@ -31,77 +31,9 @@
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="牛只图片" prop="gpsState" width="350">
|
||||
<el-table-column label="操作" width="100">
|
||||
<template #default="scope">
|
||||
<div style="display: flex" v-if="scope.row">
|
||||
<!-- 正面图片 -->
|
||||
<div v-if="scope.row.frontImg">
|
||||
<el-image
|
||||
v-for="(img, index) in getImageList(scope.row.frontImg)"
|
||||
:key="'front-' + index"
|
||||
:src="img"
|
||||
style="width: 80px; height: 80px; margin-right: 10px"
|
||||
fit="cover"
|
||||
:preview-src-list="getImageList(scope.row.frontImg)"
|
||||
preview-teleported
|
||||
>
|
||||
<template #error>
|
||||
<div
|
||||
style="width: 80px; height: 80px; display: flex; justify-content: center; align-items: center"
|
||||
class="image-slot"
|
||||
>
|
||||
<el-icon style="font-size: 50px"><Picture /></el-icon>
|
||||
</div>
|
||||
</template>
|
||||
</el-image>
|
||||
</div>
|
||||
<!-- 侧面图片 -->
|
||||
<div v-if="scope.row.sideImg">
|
||||
<el-image
|
||||
v-for="(img, index) in getImageList(scope.row.sideImg)"
|
||||
:key="'side-' + index"
|
||||
:src="img"
|
||||
style="width: 80px; height: 80px; margin-right: 10px"
|
||||
fit="cover"
|
||||
:preview-src-list="getImageList(scope.row.sideImg)"
|
||||
preview-teleported
|
||||
>
|
||||
<template #error>
|
||||
<div
|
||||
style="width: 80px; height: 80px; display: flex; justify-content: center; align-items: center"
|
||||
class="image-slot"
|
||||
>
|
||||
<el-icon style="font-size: 50px"><Picture /></el-icon>
|
||||
</div>
|
||||
</template>
|
||||
</el-image>
|
||||
</div>
|
||||
<!-- 臀部图片 -->
|
||||
<div v-if="scope.row.hipImg">
|
||||
<el-image
|
||||
v-for="(img, index) in getImageList(scope.row.hipImg)"
|
||||
:key="'hip-' + index"
|
||||
:src="img"
|
||||
style="width: 80px; height: 80px; margin-right: 10px"
|
||||
fit="cover"
|
||||
:preview-src-list="getImageList(scope.row.hipImg)"
|
||||
preview-teleported
|
||||
>
|
||||
<template #error>
|
||||
<div
|
||||
style="width: 80px; height: 80px; display: flex; justify-content: center; align-items: center"
|
||||
class="image-slot"
|
||||
>
|
||||
<el-icon style="font-size: 50px"><Picture /></el-icon>
|
||||
</div>
|
||||
</template>
|
||||
</el-image>
|
||||
</div>
|
||||
<!-- 无图片时显示占位 -->
|
||||
<div v-if="!scope.row.frontImg && !scope.row.sideImg && !scope.row.hipImg" style="color: #999; display: flex; align-items: center;">
|
||||
暂无图片
|
||||
</div>
|
||||
</div>
|
||||
<el-button link type="danger" @click="handleUnbind(scope.row)">解绑</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
@@ -116,7 +48,8 @@
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted } from 'vue';
|
||||
import { deviceAllList } from '@/api/shipping.js';
|
||||
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||
import { deviceAllList, unbindDevice } from '@/api/shipping.js';
|
||||
|
||||
const data = reactive({
|
||||
dialogVisible: false,
|
||||
@@ -170,6 +103,34 @@ const onShowLookDialog = (row) => {
|
||||
getDataList();
|
||||
}
|
||||
};
|
||||
|
||||
// 解绑设备
|
||||
const handleUnbind = (row) => {
|
||||
ElMessageBox.confirm('确认解绑该设备吗?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
})
|
||||
.then(() => {
|
||||
unbindDevice(row.deviceId || row.sn)
|
||||
.then((res) => {
|
||||
if (res.code === 200) {
|
||||
ElMessage.success('解绑成功');
|
||||
getDataList();
|
||||
} else {
|
||||
ElMessage.error(res.msg || '解绑失败');
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
ElMessage.error('解绑失败');
|
||||
console.error('解绑失败:', error);
|
||||
});
|
||||
})
|
||||
.catch(() => {
|
||||
// 用户取消操作
|
||||
});
|
||||
};
|
||||
|
||||
defineExpose({
|
||||
onShowLookDialog,
|
||||
});
|
||||
|
||||
@@ -1,238 +1,83 @@
|
||||
<template>
|
||||
<el-dialog v-model="data.dialogVisible" title="创建装车订单" :before-close="handleClose" style="width: 1100px; padding-bottom: 20px">
|
||||
<el-form ref="formDataRef" :model="ruleForm" :rules="rules" label-width="auto">
|
||||
<el-row :gutter="40">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="订单标题" prop="deliveryTitle">
|
||||
<el-input v-model="ruleForm.deliveryTitle" placeholder="请输入订单标题" clearable></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="装车数量" prop="ratedQuantity">
|
||||
<el-input v-model="ruleForm.ratedQuantity" placeholder="请输入装车数量" clearable> <template #append>头</template></el-input>
|
||||
</el-form-item></el-col
|
||||
<el-dialog v-model="data.dialogVisible" title="创建订单" :before-close="handleClose" width="600px">
|
||||
<el-form ref="formDataRef" :model="ruleForm" :rules="rules" label-width="120px">
|
||||
<el-form-item label="卖方" prop="sellerId">
|
||||
<el-select
|
||||
v-model="ruleForm.sellerId"
|
||||
clearable
|
||||
filterable
|
||||
remote
|
||||
:remote-method="supplierRemoteMethod"
|
||||
:loading="data.supplierLoading"
|
||||
placeholder="请选择卖方"
|
||||
style="width: 100%"
|
||||
multiple
|
||||
collapse-tags
|
||||
collapse-tags-tooltip
|
||||
:max-collapse-tags="2"
|
||||
>
|
||||
</el-row>
|
||||
<el-row :gutter="40">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="选择供应商" prop="supplierName">
|
||||
<el-select
|
||||
v-model="ruleForm.supplierName"
|
||||
clearable
|
||||
filterable
|
||||
remote
|
||||
:remote-method="supplierRemoteMethod"
|
||||
:loading="data.supplierLoading"
|
||||
@change="supplierChange"
|
||||
placeholder="请选择供应商"
|
||||
style="width: 100%"
|
||||
multiple
|
||||
collapse-tags
|
||||
collapse-tags-tooltip
|
||||
:max-collapse-tags="2"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in data.supplierOptions"
|
||||
:key="item.id"
|
||||
:label="item.username + ' / ' + item.mobile + ' (' + item.tenantName + ')'"
|
||||
:value="item.mobile"
|
||||
>
|
||||
</el-option>
|
||||
<el-pagination
|
||||
style="padding: 0px 20px"
|
||||
@current-change="supplierHandleCurrentChange"
|
||||
:page-size="10"
|
||||
:current-page="data.supplierPageNum"
|
||||
layout="total, prev, pager, next"
|
||||
:total="data.supplierTotal"
|
||||
>
|
||||
</el-pagination>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="选择资金方" prop="financeName">
|
||||
<el-select
|
||||
v-model="ruleForm.financeName"
|
||||
clearable
|
||||
filterable
|
||||
remote
|
||||
:remote-method="financeRemoteMethod"
|
||||
:loading="data.financeLoading"
|
||||
@change="financeChange"
|
||||
placeholder="请选择资金方"
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in data.financeOptions"
|
||||
:key="item.id"
|
||||
:label="item.username + ' / ' + item.mobile + ' (' + item.tenantName + ')'"
|
||||
:value="item.mobile"
|
||||
>
|
||||
</el-option>
|
||||
<el-pagination
|
||||
style="padding: 0px 20px"
|
||||
@current-change="financeHandleCurrentChange"
|
||||
:page-size="10"
|
||||
:current-page="data.financePageNum"
|
||||
layout="total, prev, pager, next"
|
||||
:total="data.financeTotal"
|
||||
>
|
||||
</el-pagination>
|
||||
</el-select> </el-form-item
|
||||
></el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="40">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="选择司机" prop="driverMobile">
|
||||
<el-select
|
||||
v-model="ruleForm.driverMobile"
|
||||
clearable
|
||||
filterable
|
||||
remote
|
||||
:remote-method="driverRemoteMethod"
|
||||
:loading="data.driverLoading"
|
||||
@change="driverChange"
|
||||
placeholder="请选择司机"
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in data.driverOptions"
|
||||
:key="item.id"
|
||||
:label="item.username + ' / ' + item.mobile"
|
||||
:value="item.mobile"
|
||||
>
|
||||
</el-option>
|
||||
<el-pagination
|
||||
style="padding: 0px 20px"
|
||||
@current-change="driverHandleCurrentChange"
|
||||
:page-size="10"
|
||||
:current-page="data.driverPageNum"
|
||||
layout="total, prev, pager, next"
|
||||
:total="data.driverTotal"
|
||||
>
|
||||
</el-pagination>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="选择采购商" prop="purchaserMobile">
|
||||
<el-select
|
||||
v-model="ruleForm.purchaserMobile"
|
||||
clearable
|
||||
filterable
|
||||
remote
|
||||
:remote-method="purchaserRemoteMethod"
|
||||
:loading="data.purchaserLoading"
|
||||
@change="purchaserChange"
|
||||
placeholder="请选择采购商"
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in data.purchaserOptions"
|
||||
:key="item.id"
|
||||
:label="item.username + ' / ' + item.mobile + ' (' + item.tenantName + ')'"
|
||||
:value="item.mobile"
|
||||
>
|
||||
</el-option>
|
||||
<el-pagination
|
||||
style="padding: 0px 20px"
|
||||
@current-change="purchaserHandleCurrentChange"
|
||||
:page-size="10"
|
||||
:current-page="data.purchaserPageNum"
|
||||
layout="total, prev, pager, next"
|
||||
:total="data.purchaserTotal"
|
||||
>
|
||||
</el-pagination>
|
||||
</el-select> </el-form-item
|
||||
></el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="40">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="采购单价" prop="buyerPrice">
|
||||
<el-input v-model="ruleForm.buyerPrice" placeholder="请输入采购单价" clearable>
|
||||
<template #append>元/公斤</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="销售单价" prop="salePrice">
|
||||
<el-input v-model="ruleForm.salePrice" placeholder="请输入销售单价" clearable> <template #append>元/公斤</template></el-input>
|
||||
</el-form-item></el-col
|
||||
<el-option
|
||||
v-for="item in data.supplierOptions"
|
||||
:key="item.id"
|
||||
:label="item.username + ' / ' + item.mobile + ' (' + item.tenantName + ')'"
|
||||
:value="item.id"
|
||||
>
|
||||
</el-option>
|
||||
<el-pagination
|
||||
style="padding: 0px 20px"
|
||||
@current-change="supplierHandleCurrentChange"
|
||||
:page-size="10"
|
||||
:current-page="data.supplierPageNum"
|
||||
layout="total, prev, pager, next"
|
||||
:total="data.supplierTotal"
|
||||
>
|
||||
</el-pagination>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="买方" prop="buyerId">
|
||||
<el-select
|
||||
v-model="ruleForm.buyerId"
|
||||
clearable
|
||||
filterable
|
||||
remote
|
||||
:remote-method="purchaserRemoteMethod"
|
||||
:loading="data.purchaserLoading"
|
||||
placeholder="请选择买方"
|
||||
style="width: 100%"
|
||||
multiple
|
||||
collapse-tags
|
||||
collapse-tags-tooltip
|
||||
:max-collapse-tags="2"
|
||||
>
|
||||
</el-row>
|
||||
<el-row :gutter="40">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="约定单价" prop="firmPrice">
|
||||
<el-input v-model="ruleForm.firmPrice" placeholder="请输入约定单价" clearable>
|
||||
<template #append>元/公斤</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="40">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="起始地" prop="startLocation">
|
||||
<el-autocomplete
|
||||
v-model="ruleForm.startLocation"
|
||||
:fetch-suggestions="startSearchLocation"
|
||||
placeholder="请输入起始地"
|
||||
style="width: 100%"
|
||||
:trigger-on-focus="false"
|
||||
@select="startHandleSelects"
|
||||
/>
|
||||
<div class="maps" style="width: 100%">
|
||||
<baidu-map
|
||||
class="bm-view"
|
||||
:center="data.startCenter"
|
||||
:zoom="14"
|
||||
style="height: 300px; width: 100%; border: 1px solid #ddd"
|
||||
@ready="handler"
|
||||
:scroll-wheel-zoom="true"
|
||||
:extensions_road="true"
|
||||
:extensions_town="true"
|
||||
v-if="data.dialogVisible"
|
||||
@click="startClickInfo"
|
||||
:map-type="'BMAP_NORMAL_MAP'"
|
||||
:enable-map-click="true"
|
||||
>
|
||||
<bm-marker :position="data.startCenter" :dragging="true"></bm-marker>
|
||||
</baidu-map>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="目的地" prop="endLocation">
|
||||
<el-autocomplete
|
||||
v-model="ruleForm.endLocation"
|
||||
:fetch-suggestions="endSearchLocation"
|
||||
placeholder="请输入目的地"
|
||||
style="width: 100%"
|
||||
:trigger-on-focus="false"
|
||||
@select="endHandleSelects"
|
||||
/>
|
||||
<div class="maps" style="width: 100%">
|
||||
<baidu-map
|
||||
class="bm-view"
|
||||
:center="data.endCenter"
|
||||
:zoom="14"
|
||||
style="height: 300px; width: 100%; border: 1px solid #ddd"
|
||||
@ready="handler"
|
||||
:scroll-wheel-zoom="true"
|
||||
:extensions_road="true"
|
||||
:extensions_town="true"
|
||||
v-if="data.dialogVisible"
|
||||
@click="endClickInfo"
|
||||
:map-type="'BMAP_NORMAL_MAP'"
|
||||
:enable-map-click="true"
|
||||
>
|
||||
<bm-marker :position="data.endCenter" :dragging="true"></bm-marker>
|
||||
</baidu-map>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-option
|
||||
v-for="item in data.purchaserOptions"
|
||||
:key="item.id"
|
||||
:label="item.username + ' / ' + item.mobile + ' (' + item.tenantName + ')'"
|
||||
:value="item.id"
|
||||
>
|
||||
</el-option>
|
||||
<el-pagination
|
||||
style="padding: 0px 20px"
|
||||
@current-change="purchaserHandleCurrentChange"
|
||||
:page-size="10"
|
||||
:current-page="data.purchaserPageNum"
|
||||
layout="total, prev, pager, next"
|
||||
:total="data.purchaserTotal"
|
||||
>
|
||||
</el-pagination>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="结算方式" prop="settlementType">
|
||||
<el-select v-model="ruleForm.settlementType" placeholder="请选择结算方式" style="width: 100%">
|
||||
<el-option label="上车重量" :value="1"></el-option>
|
||||
<el-option label="下车重量" :value="2"></el-option>
|
||||
<el-option label="按肉价结算" :value="3"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button :loading="data.saveLoading" type="primary" @click="onClickSave">保存</el-button>
|
||||
@@ -243,195 +88,56 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted } from 'vue';
|
||||
import { orderAdd } from '@/api/shipping.js';
|
||||
import { driverList, userList, memberListByType } from '@/api/userManage.js';
|
||||
import { ref, reactive } from 'vue';
|
||||
import { orderAddNew, orderUpdate } from '@/api/shipping.js';
|
||||
import { memberListByType } from '@/api/userManage.js';
|
||||
|
||||
const emits = defineEmits();
|
||||
const formDataRef = ref(null);
|
||||
const maps = ref();
|
||||
const BMap = reactive({});
|
||||
|
||||
const data = reactive({
|
||||
dialogVisible: false,
|
||||
saveLoading: false,
|
||||
supplierOptions: [],
|
||||
financeOptions: [],
|
||||
driverOptions: [],
|
||||
purchaserOptions: [],
|
||||
startCenter: { lng: 0, lat: 0 },
|
||||
endCenter: { lng: 0, lat: 0 },
|
||||
driverLoading: false, // 司机loading
|
||||
driverLoading: false,
|
||||
driverName: '',
|
||||
driverPageNum: 1,
|
||||
driverTotal: 0,
|
||||
purchaserLoading: false, // 采购商loading
|
||||
purchaserLoading: false,
|
||||
purchaserPageNum: 1,
|
||||
purchaserTotal: 0,
|
||||
purchaserName: '',
|
||||
financeLoading: false, // 资金方loading
|
||||
financePageNum: 1,
|
||||
financeTotal: 0,
|
||||
financeName: '',
|
||||
supplierLoading: false, // 供应商loading
|
||||
supplierLoading: false,
|
||||
supplierPageNum: 1,
|
||||
supplierTotal: 0,
|
||||
supplierName: '',
|
||||
});
|
||||
|
||||
const ruleForm = reactive({
|
||||
deliveryTitle: '', // 订单标题
|
||||
ratedQuantity: '', // 装车数量
|
||||
supplier: [], // 供应商
|
||||
fundId: '', // 资金方
|
||||
driverId: '', // 司机
|
||||
buyerId: '', // 采购商
|
||||
buyerPrice: '', // 采购单价
|
||||
salePrice: '', // 销售单价
|
||||
firmPrice: '', // 约定单价
|
||||
startLocation: '', // 起始地
|
||||
startLat: '',
|
||||
startLon: '',
|
||||
endLocation: '', // 目的地
|
||||
endLat: '',
|
||||
endLon: '',
|
||||
driverMobile: '',
|
||||
purchaserMobile: '',
|
||||
supplierMobile: '',
|
||||
id: null, // 订单ID(编辑时使用)
|
||||
buyerId: [], // 买方ID数组
|
||||
sellerId: [], // 卖方ID数组
|
||||
settlementType: 1, // 结算方式:1-上车重量,2-下车重量,3-按肉价结算
|
||||
});
|
||||
|
||||
const rules = reactive({
|
||||
deliveryTitle: [{ required: true, message: '请输入订单标题', trigger: 'blur' }],
|
||||
// ratedQuantity: [{ required: true, message: '请输入装车数量', trigger: 'blur' }],
|
||||
// supplierName: [{ required: true, message: '请选择供应商', trigger: 'change' }],
|
||||
// financeName: [{ required: true, message: '请选择资金方', trigger: 'change' }],
|
||||
// driverMobile: [{ required: true, message: '请选择司机', trigger: 'change' }],
|
||||
// purchaserMobile: [{ required: true, message: '请选择采购商', trigger: 'change' }],
|
||||
// buyerPrice: [{ required: true, message: '请输入采购单价', trigger: 'blur' }],
|
||||
// salePrice: [{ required: true, message: '请输入销售单价', trigger: 'blur' }],
|
||||
// startLocation: [{ required: true, message: '请输入起始地', trigger: 'blur' }],
|
||||
// endLocation: [{ required: true, message: '请输入目的地', trigger: 'blur' }],
|
||||
buyerId: [{ required: true, message: '请选择买方', trigger: 'change' }],
|
||||
sellerId: [{ required: true, message: '请选择卖方', trigger: 'change' }],
|
||||
settlementType: [{ required: true, message: '请选择结算方式', trigger: 'change' }],
|
||||
});
|
||||
|
||||
const handleClose = () => {
|
||||
if (formDataRef.value) {
|
||||
formDataRef.value.resetFields();
|
||||
}
|
||||
// 重置表单数据
|
||||
ruleForm.id = null;
|
||||
ruleForm.buyerId = [];
|
||||
ruleForm.sellerId = [];
|
||||
ruleForm.settlementType = 1;
|
||||
data.dialogVisible = false;
|
||||
};
|
||||
// ----------------
|
||||
// 初始化
|
||||
const handler = ({ BMap, map }) => {
|
||||
BMap = BMap;
|
||||
maps.value = map;
|
||||
if (data.startCenter.lng == 0 && data.startCenter.lat == 0) {
|
||||
const localcity = new BMap.LocalCity();
|
||||
localcity.get((e) => {
|
||||
data.startCenter.lng = e.center.lng;
|
||||
data.startCenter.lat = e.center.lat;
|
||||
data.endCenter.lng = e.center.lng;
|
||||
data.endCenter.lat = e.center.lat;
|
||||
});
|
||||
} else {
|
||||
// 如果有坐标点则,展示坐标点
|
||||
}
|
||||
};
|
||||
const startSearchLocation = async (str, cb) => {
|
||||
// 使用百度地图的地点搜索服务
|
||||
const local = new window.BMap.LocalSearch(maps.value, {
|
||||
onSearchComplete(res) {
|
||||
const arr = [];
|
||||
if (local.getStatus() == BMAP_STATUS_SUCCESS) {
|
||||
for (let i = 0; i < res.getCurrentNumPois(); i++) {
|
||||
const x = res.getPoi(i);
|
||||
const item = { value: x.address + x.title, point: x.point };
|
||||
arr.push(item);
|
||||
}
|
||||
cb(arr);
|
||||
} else {
|
||||
// ElMessage.error('未找到相关地点,请尝试其他关键字。');
|
||||
}
|
||||
},
|
||||
});
|
||||
local.search(str);
|
||||
};
|
||||
|
||||
const endSearchLocation = async (str, cb) => {
|
||||
// 使用百度地图的地点搜索服务
|
||||
const local = new window.BMap.LocalSearch(maps.value, {
|
||||
onSearchComplete(res) {
|
||||
const arr = [];
|
||||
if (local.getStatus() == BMAP_STATUS_SUCCESS) {
|
||||
for (let i = 0; i < res.getCurrentNumPois(); i++) {
|
||||
const x = res.getPoi(i);
|
||||
const item = { value: x.address + x.title, point: x.point };
|
||||
arr.push(item);
|
||||
}
|
||||
cb(arr);
|
||||
} else {
|
||||
// ElMessage.error('未找到相关地点,请尝试其他关键字。');
|
||||
}
|
||||
},
|
||||
});
|
||||
local.search(str);
|
||||
};
|
||||
const startHandleSelects = (item) => {
|
||||
// 点击搜索的点位并地图跳转到该坐标
|
||||
const { point } = item;
|
||||
ruleForm.startLocation = item.value;
|
||||
getStartClickInfo({ point });
|
||||
};
|
||||
const getStartClickInfo = ({ point }) => {
|
||||
const geoc = new window.BMap.Geocoder(); // 创建地址解析器的实例
|
||||
data.startCenter.lng = point.lng;
|
||||
data.startCenter.lat = point.lat;
|
||||
geoc.getLocation(point, function (result) {
|
||||
if (result.surroundingPois.length > 0) {
|
||||
const fcaArr = [result.addressComponents.province, result.addressComponents.city, result.addressComponents.district];
|
||||
ruleForm.startLon = result.point.lng;
|
||||
ruleForm.startLat = result.point.lat;
|
||||
}
|
||||
});
|
||||
};
|
||||
const startClickInfo = (e) => {
|
||||
data.startCenter.lng = e.point.lng;
|
||||
data.startCenter.lat = e.point.lat;
|
||||
const geocoder = new window.BMap.Geocoder();
|
||||
geocoder.getLocation(e.point, (res) => {
|
||||
if (res) {
|
||||
ruleForm.startLocation = res.address;
|
||||
ruleForm.startLon = res.point.lng;
|
||||
ruleForm.startLat = res.point.lat;
|
||||
}
|
||||
});
|
||||
};
|
||||
// 到达点
|
||||
const endHandleSelects = (item) => {
|
||||
// 点击搜索的点位并地图跳转到该坐标
|
||||
const { point } = item;
|
||||
ruleForm.endLocation = item.value;
|
||||
getEndClickInfo({ point });
|
||||
};
|
||||
const getEndClickInfo = ({ point }) => {
|
||||
const geoc = new window.BMap.Geocoder(); // 创建地址解析器的实例
|
||||
data.endCenter.lng = point.lng;
|
||||
data.endCenter.lat = point.lat;
|
||||
geoc.getLocation(point, function (result) {
|
||||
if (result.surroundingPois.length > 0) {
|
||||
const fcaArr = [result.addressComponents.province, result.addressComponents.city, result.addressComponents.district];
|
||||
ruleForm.endLon = result.point.lng;
|
||||
ruleForm.endLat = result.point.lat;
|
||||
}
|
||||
});
|
||||
};
|
||||
const endClickInfo = (e) => {
|
||||
data.endCenter.lng = e.point.lng;
|
||||
data.endCenter.lat = e.point.lat;
|
||||
const geocoder = new window.BMap.Geocoder();
|
||||
geocoder.getLocation(e.point, (res) => {
|
||||
if (res) {
|
||||
ruleForm.endLocation = res.address;
|
||||
ruleForm.endLon = res.point.lng;
|
||||
ruleForm.endLat = res.point.lat;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// ----------------
|
||||
// 供应商远程搜索
|
||||
@@ -440,6 +146,7 @@ const supplierRemoteMethod = (e) => {
|
||||
data.supplierPageNum = 1;
|
||||
getSupplierList();
|
||||
};
|
||||
|
||||
// 供应商 列表
|
||||
const getSupplierList = () => {
|
||||
data.supplierLoading = true;
|
||||
@@ -459,101 +166,21 @@ const getSupplierList = () => {
|
||||
data.supplierLoading = false;
|
||||
});
|
||||
};
|
||||
|
||||
// 选择供应商分页
|
||||
const supplierHandleCurrentChange = (val) => {
|
||||
data.supplierPageNum = val;
|
||||
getSupplierList();
|
||||
};
|
||||
// 选择供应商
|
||||
const supplierChange = (e) => {
|
||||
if (e) {
|
||||
// ruleForm.supplier = data.supplierOptions.find((item) => item.mobile == e).id;
|
||||
ruleForm.supplier = data.supplierOptions.filter((user) => e.includes(user.mobile)).map((user) => user.id);
|
||||
} else {
|
||||
ruleForm.supplier = [];
|
||||
}
|
||||
};
|
||||
// 供应商远程搜索
|
||||
const financeRemoteMethod = (e) => {
|
||||
data.financeName = e;
|
||||
data.financePageNum = 1;
|
||||
getFinanceList();
|
||||
};
|
||||
// 资金方 列表
|
||||
const getFinanceList = () => {
|
||||
data.financeLoading = true;
|
||||
const params = {
|
||||
pageNum: data.financePageNum,
|
||||
pageSize: 10,
|
||||
type: 3, // 资金方类型
|
||||
username: data.financeName,
|
||||
};
|
||||
memberListByType(params)
|
||||
.then((res) => {
|
||||
data.financeLoading = false;
|
||||
data.financeOptions = res.data.rows;
|
||||
data.financeTotal = res.data.total;
|
||||
})
|
||||
.catch(() => {
|
||||
data.financeLoading = false;
|
||||
});
|
||||
};
|
||||
// 选择资金方分页
|
||||
const financeHandleCurrentChange = (val) => {
|
||||
data.financePageNum = val;
|
||||
getFinanceList();
|
||||
};
|
||||
// 选择资金方
|
||||
const financeChange = (e) => {
|
||||
if (e) {
|
||||
ruleForm.fundId = data.financeOptions.find((item) => item.mobile == e).id;
|
||||
} else {
|
||||
ruleForm.fundId = '';
|
||||
}
|
||||
};
|
||||
// 司机远程搜索
|
||||
const driverRemoteMethod = (e) => {
|
||||
data.driverName = e;
|
||||
data.driverPageNum = 1;
|
||||
getDriverList();
|
||||
};
|
||||
// 列表
|
||||
const getDriverList = () => {
|
||||
data.driverLoading = true;
|
||||
const params = {
|
||||
pageNum: data.driverPageNum,
|
||||
pageSize: 10,
|
||||
username: data.driverName,
|
||||
};
|
||||
driverList(params)
|
||||
.then((res) => {
|
||||
data.driverLoading = false;
|
||||
data.driverOptions = res.data.rows;
|
||||
data.driverTotal = res.data.total;
|
||||
})
|
||||
.catch(() => {
|
||||
data.driverLoading = false;
|
||||
});
|
||||
};
|
||||
// 选择司机分页
|
||||
const driverHandleCurrentChange = (val) => {
|
||||
data.driverPageNum = val;
|
||||
getDriverList();
|
||||
};
|
||||
// 选择司机
|
||||
const driverChange = (e) => {
|
||||
if (e) {
|
||||
ruleForm.driverId = data.driverOptions.find((item) => item.mobile == e).id;
|
||||
} else {
|
||||
ruleForm.driverId = '';
|
||||
}
|
||||
};
|
||||
|
||||
// ----------------
|
||||
// 采购商远程搜索
|
||||
const purchaserRemoteMethod = (e) => {
|
||||
data.purchaserName = e;
|
||||
data.purchaserPageNum = 1;
|
||||
getPurchaserList();
|
||||
};
|
||||
|
||||
// 采购商 列表
|
||||
const getPurchaserList = () => {
|
||||
data.purchaserLoading = true;
|
||||
@@ -573,99 +200,76 @@ const getPurchaserList = () => {
|
||||
data.purchaserLoading = false;
|
||||
});
|
||||
};
|
||||
|
||||
// 采购商分页
|
||||
const purchaserHandleCurrentChange = (val) => {
|
||||
data.purchaserPageNum = val;
|
||||
getPurchaserList();
|
||||
};
|
||||
// 选择采购商
|
||||
const purchaserChange = (e) => {
|
||||
if (e) {
|
||||
ruleForm.buyerId = data.purchaserOptions.find((item) => item.mobile == e).id;
|
||||
} else {
|
||||
ruleForm.buyerId = '';
|
||||
}
|
||||
};
|
||||
|
||||
// 保存
|
||||
const onClickSave = () => {
|
||||
if (formDataRef.value) {
|
||||
formDataRef.value.validate((valid) => {
|
||||
if (valid) {
|
||||
const params = {
|
||||
deliveryTitle: ruleForm.deliveryTitle,
|
||||
ratedQuantity: ruleForm.ratedQuantity,
|
||||
supplierId: ruleForm.supplier.join(','),
|
||||
fundId: ruleForm.fundId,
|
||||
driverId: ruleForm.driverId,
|
||||
buyerId: ruleForm.buyerId,
|
||||
buyerPrice: ruleForm.buyerPrice,
|
||||
salePrice: ruleForm.salePrice,
|
||||
firmPrice: ruleForm.firmPrice,
|
||||
startLocation: ruleForm.startLocation,
|
||||
startLat: ruleForm.startLat,
|
||||
startLon: ruleForm.startLon,
|
||||
endLocation: ruleForm.endLocation,
|
||||
endLat: ruleForm.endLat,
|
||||
endLon: ruleForm.endLon,
|
||||
id: ruleForm.id,
|
||||
buyerId: ruleForm.buyerId.join(','), // 将数组转为逗号分隔的字符串
|
||||
sellerId: ruleForm.sellerId.join(','), // 将数组转为逗号分隔的字符串
|
||||
settlementType: ruleForm.settlementType,
|
||||
};
|
||||
|
||||
data.saveLoading = true;
|
||||
orderAdd(params)
|
||||
.then((res) => {
|
||||
data.saveLoading = false;
|
||||
if (res.code === 200) {
|
||||
ElMessage({
|
||||
message: res.msg,
|
||||
type: 'success',
|
||||
});
|
||||
emits('success');
|
||||
if (formDataRef.value) {
|
||||
formDataRef.value.resetFields();
|
||||
data.dialogVisible = false;
|
||||
}
|
||||
} else {
|
||||
ElMessage.error(res.msg);
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
data.saveLoading = false;
|
||||
});
|
||||
|
||||
// 根据是否有ID判断是新增还是编辑
|
||||
const savePromise = ruleForm.id ? orderUpdate(params) : orderAddNew(params);
|
||||
|
||||
savePromise.then((res) => {
|
||||
data.saveLoading = false;
|
||||
if (res.code === 200) {
|
||||
ElMessage({
|
||||
message: res.msg,
|
||||
type: 'success',
|
||||
});
|
||||
emits('success');
|
||||
handleClose();
|
||||
} else {
|
||||
ElMessage.error(res.msg);
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
data.saveLoading = false;
|
||||
});
|
||||
} else {
|
||||
console.log('error submit!');
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
const onShowDialog = () => {
|
||||
|
||||
const onShowDialog = (orderData) => {
|
||||
if (formDataRef.value) {
|
||||
formDataRef.value.resetFields();
|
||||
}
|
||||
|
||||
// 重置表单数据
|
||||
ruleForm.id = orderData?.id || null;
|
||||
ruleForm.buyerId = orderData?.buyerId ? orderData.buyerId.split(',') : [];
|
||||
ruleForm.sellerId = orderData?.sellerId ? orderData.sellerId.split(',') : [];
|
||||
ruleForm.settlementType = orderData?.settlementType || 1;
|
||||
|
||||
data.dialogVisible = true;
|
||||
getDriverList();
|
||||
getPurchaserList();
|
||||
getFinanceList();
|
||||
getSupplierList();
|
||||
getPurchaserList();
|
||||
};
|
||||
|
||||
defineExpose({
|
||||
onShowDialog,
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
::v-deep .anchorBL {
|
||||
display: none;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.bm-view {
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
/* 优化WebGL渲染 */
|
||||
transform: translateZ(0);
|
||||
-webkit-transform: translateZ(0);
|
||||
will-change: transform;
|
||||
}
|
||||
|
||||
.maps {
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
::v-deep .el-select-dropdown__list {
|
||||
padding-bottom: 40px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -6,17 +6,21 @@
|
||||
</div>
|
||||
<div class="main-container">
|
||||
<el-table :data="data.rows" border v-loading="data.dataListLoading" element-loading-text="数据加载中..." style="width: 100%">
|
||||
<el-table-column label="用户姓名" prop="id"></el-table-column>
|
||||
<el-table-column label="用户手机号" prop="roleName"> </el-table-column>
|
||||
<el-table-column label="用户类型" prop="mobile"></el-table-column>
|
||||
<el-table-column label="账号状态" prop="name"></el-table-column>
|
||||
<el-table-column label="备注" prop="roleName"> </el-table-column>
|
||||
<el-table-column label="创建人" prop="mobile"></el-table-column>
|
||||
<el-table-column label="创建时间" prop="name"></el-table-column>
|
||||
<el-table-column label="操作" width="120">
|
||||
<el-table-column label="用户姓名" prop="name"></el-table-column>
|
||||
<el-table-column label="用户手机号" prop="mobile"> </el-table-column>
|
||||
<el-table-column label="用户类型" prop="roleName"></el-table-column>
|
||||
<el-table-column label="账号状态" prop="status">
|
||||
<template #default="scope">
|
||||
<el-button link type="primary" @click="showAddDialog(scope.row)">编辑111</el-button>
|
||||
<el-button link type="primary" @click="delClick(scope.row)">删除111</el-button>
|
||||
{{ scope.row.status === 1 ? '启用' : '禁用' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="备注" prop="remark"> </el-table-column>
|
||||
<el-table-column label="创建人" prop="createBy"></el-table-column>
|
||||
<el-table-column label="创建时间" prop="createTime"></el-table-column>
|
||||
<el-table-column label="操作" width="150">
|
||||
<template #default="scope">
|
||||
<el-button link type="primary" @click="showAddDialog(scope.row)">编辑</el-button>
|
||||
<el-button link type="primary" @click="delClick(scope.row)" style="color: #f56c6c;">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<template #empty>
|
||||
@@ -30,58 +34,97 @@
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted } from 'vue';
|
||||
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||
import baseSearch from '@/components/common/searchCustom/index.vue';
|
||||
import { sysUserList, sysUserDel, sysUserSave } from '@/api/sys.js';
|
||||
|
||||
const baseSearchRef = ref();
|
||||
|
||||
const formItemList = reactive([
|
||||
{
|
||||
label: '用户姓名',
|
||||
prop: 'username',
|
||||
type: 'input',
|
||||
placeholder: '请输入用户名',
|
||||
param: 'name',
|
||||
placeholder: '请输入用户姓名',
|
||||
span: 7,
|
||||
labelWidth: 100,
|
||||
},
|
||||
{
|
||||
label: '用户手机号',
|
||||
prop: 'phone',
|
||||
type: 'input',
|
||||
param: 'mobile',
|
||||
placeholder: '请输入用户手机号',
|
||||
span: 7,
|
||||
labelWidth: 100,
|
||||
},
|
||||
{
|
||||
label: '用户类型',
|
||||
type: 'select',
|
||||
selectOptions: [
|
||||
{ value: 1, text: '供应商' },
|
||||
{ value: 2, text: '资金方' },
|
||||
{ value: 3, text: '采购商' },
|
||||
],
|
||||
param: 'type',
|
||||
span: 7,
|
||||
labelWidth: 100,
|
||||
},
|
||||
]);
|
||||
|
||||
const searchFrom = () => {
|
||||
form.pageNum = 1;
|
||||
getDataList();
|
||||
};
|
||||
|
||||
const data = reactive({
|
||||
rows: [
|
||||
{
|
||||
id: '1',
|
||||
},
|
||||
],
|
||||
rows: [],
|
||||
total: 0,
|
||||
dataListLoading: false,
|
||||
});
|
||||
|
||||
const form = reactive({
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
});
|
||||
|
||||
// 列表
|
||||
const getDataList = () => {};
|
||||
const getDataList = () => {
|
||||
data.dataListLoading = true;
|
||||
const params = {
|
||||
...form,
|
||||
...baseSearchRef.value?.penetrateParams(),
|
||||
};
|
||||
sysUserList(params)
|
||||
.then((res) => {
|
||||
data.dataListLoading = false;
|
||||
data.rows = res.data.rows || [];
|
||||
data.total = res.data.total || 0;
|
||||
})
|
||||
.catch(() => {
|
||||
data.dataListLoading = false;
|
||||
});
|
||||
};
|
||||
|
||||
// 删除用户
|
||||
const delClick = (row) => {
|
||||
ElMessageBox.confirm('请确认是否删除该用户?', '提示', {
|
||||
cancelButtonText: '取消',
|
||||
confirmButtonText: '确定',
|
||||
type: 'warning',
|
||||
})
|
||||
.then(() => {
|
||||
sysUserDel(row.id)
|
||||
.then(() => {
|
||||
ElMessage.success('删除成功');
|
||||
getDataList();
|
||||
})
|
||||
.catch((error) => {
|
||||
ElMessage.error('删除失败');
|
||||
console.error('删除失败:', error);
|
||||
});
|
||||
})
|
||||
.catch(() => {
|
||||
// 用户取消删除
|
||||
});
|
||||
};
|
||||
|
||||
// 编辑用户
|
||||
const showAddDialog = (row) => {
|
||||
// TODO: 实现编辑对话框
|
||||
console.log('编辑用户:', row);
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
getDataList();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
|
||||
@@ -6,23 +6,19 @@
|
||||
</div>
|
||||
<div class="main-container">
|
||||
<el-table :data="data.rows" border v-loading="data.dataListLoading" element-loading-text="数据加载中..." style="width: 100%">
|
||||
<el-table-column label="司机姓名" prop="username"></el-table-column>
|
||||
<el-table-column label="司机手机号" prop="mobile"> </el-table-column>
|
||||
<el-table-column label="车牌号" prop="car_number"></el-table-column>
|
||||
<el-table-column label="车辆照片" prop="car_img" min-width="120">
|
||||
<el-table-column label="司机姓名" prop="username" width="120"></el-table-column>
|
||||
<el-table-column label="司机手机号" prop="mobile" width="130"></el-table-column>
|
||||
<el-table-column label="驾驶证照片" prop="driver_license" min-width="120">
|
||||
<template #default="scope">
|
||||
<div style="display: flex; flex-wrap: wrap; gap: 5px">
|
||||
<template v-if="scope.row.car_img && getImageListSync(scope.row.car_img).length > 0">
|
||||
<template v-if="scope.row.driver_license">
|
||||
<el-image
|
||||
v-for="(item, index) in getImageListSync(scope.row.car_img)"
|
||||
:key="index"
|
||||
:src="item"
|
||||
:src="scope.row.driver_license"
|
||||
style="width: 60px; height: 60px; border-radius: 4px; border: 1px solid #dcdfe6"
|
||||
fit="cover"
|
||||
:preview-src-list="getImageListSync(scope.row.car_img)"
|
||||
:preview-src-list="[scope.row.driver_license]"
|
||||
preview-teleported
|
||||
:lazy="true"
|
||||
@error="handleImageErrorLocal(item, index)"
|
||||
>
|
||||
<template #error>
|
||||
<div style="width: 60px; height: 60px; display: flex; align-items: center; justify-content: center; background: #f5f7fa; border-radius: 4px; color: #909399">
|
||||
@@ -35,13 +31,36 @@
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="备注" prop="remark"> </el-table-column>
|
||||
<el-table-column label="创建时间" prop="create_time"></el-table-column>
|
||||
<el-table-column label="身份证照片" prop="id_card" min-width="120">
|
||||
<template #default="scope">
|
||||
<div style="display: flex; flex-wrap: wrap; gap: 5px">
|
||||
<template v-if="scope.row.id_card">
|
||||
<el-image
|
||||
v-for="(url, index) in scope.row.id_card.split(',')"
|
||||
:key="index"
|
||||
:src="url.trim()"
|
||||
style="width: 60px; height: 60px; border-radius: 4px; border: 1px solid #dcdfe6"
|
||||
fit="cover"
|
||||
:preview-src-list="scope.row.id_card.split(',')"
|
||||
preview-teleported
|
||||
:lazy="true"
|
||||
>
|
||||
<template #error>
|
||||
<div style="width: 60px; height: 60px; display: flex; align-items: center; justify-content: center; background: #f5f7fa; border-radius: 4px; color: #909399">
|
||||
<el-icon size="20"><Picture /></el-icon>
|
||||
</div>
|
||||
</template>
|
||||
</el-image>
|
||||
</template>
|
||||
<span v-else style="color: #999; font-size: 12px">暂无图片</span>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="创建时间" prop="create_time" width="180"></el-table-column>
|
||||
<el-table-column label="操作" width="160">
|
||||
<template #default="scope">
|
||||
<el-button link type="primary" @click="showAddDialog(scope.row)">编辑</el-button>
|
||||
<!-- <el-button link type="primary" @click="delClick(scope.row)">删除</el-button> -->
|
||||
<el-button link type="primary" @click="showDetailDialog(scope.row)">详情</el-button>
|
||||
<el-button link type="danger" @click="delClick(scope.row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<template #empty>
|
||||
@@ -50,23 +69,20 @@
|
||||
</el-table>
|
||||
<pagination v-model:limit="form.pageSize" v-model:page="form.pageNum" :total="data.total" @pagination="getDataList" />
|
||||
<DriverDialog ref="DriverDialogRef" @success="getDataList" />
|
||||
<DetailDialog ref="DetailDialogRef" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted } from 'vue';
|
||||
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||
import { Picture } from '@element-plus/icons-vue';
|
||||
import baseSearch from '@/components/common/searchCustom/index.vue';
|
||||
import { driverList } from '@/api/userManage.js';
|
||||
import { driverList, driverDel } from '@/api/userManage.js';
|
||||
import DriverDialog from './driverDialog.vue';
|
||||
import DetailDialog from './driverDetailDialog.vue';
|
||||
import { getImageList, getImageListWithProxy, getImageListSmart, testImageUrl, handleImageError } from '@/utils/imageUtils.js';
|
||||
|
||||
const baseSearchRef = ref();
|
||||
const DriverDialogRef = ref();
|
||||
const DetailDialogRef = ref();
|
||||
const formItemList = reactive([
|
||||
{
|
||||
label: '司机姓名',
|
||||
@@ -106,48 +122,9 @@ const getDataList = () => {
|
||||
...baseSearchRef.value.penetrateParams(),
|
||||
};
|
||||
|
||||
// 添加调试信息
|
||||
console.log('发送查询参数:', params);
|
||||
|
||||
driverList(params)
|
||||
.then((res) => {
|
||||
data.dataListLoading = false;
|
||||
console.log('查询结果:', res);
|
||||
console.log('司机数据详情:', res.data.rows);
|
||||
// 特别检查car_img字段 - 分析能显示和不能显示的图片差异
|
||||
if (res.data.rows && res.data.rows.length > 0) {
|
||||
res.data.rows.forEach(async (driver, index) => {
|
||||
console.log(`=== 司机${index + 1}: ${driver.username} (${driver.mobile}) ===`);
|
||||
console.log(`car_img字段:`, driver.car_img);
|
||||
console.log(`car_img类型:`, typeof driver.car_img);
|
||||
|
||||
if (driver.car_img) {
|
||||
const urls = getImageList(driver.car_img);
|
||||
console.log(`解析后的图片URLs:`, urls);
|
||||
|
||||
// 分析URL特征
|
||||
urls.forEach((url, urlIndex) => {
|
||||
console.log(`URL ${urlIndex + 1} 分析:`);
|
||||
console.log(` - 完整URL: ${url}`);
|
||||
console.log(` - 长度: ${url.length}`);
|
||||
console.log(` - 是否HTTPS: ${url.startsWith('https://')}`);
|
||||
console.log(` - 域名: ${url.includes('cos.ap-guangzhou.myqcloud.com') ? '腾讯云COS' : '其他'}`);
|
||||
console.log(` - 文件名: ${url.split('/').pop()}`);
|
||||
|
||||
// 检查是否是车辆照片还是其他类型图片
|
||||
const fileName = url.split('/').pop().toLowerCase();
|
||||
if (fileName.includes('cow') || fileName.includes('4c4e')) {
|
||||
console.log(` - 图片类型: 车辆照片 (可能CORS受限)`);
|
||||
} else {
|
||||
console.log(` - 图片类型: 其他图片 (可能不受CORS限制)`);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
console.log(`car_img字段为空`);
|
||||
}
|
||||
console.log(`---`);
|
||||
});
|
||||
}
|
||||
data.rows = res.data.rows;
|
||||
data.total = res.data.total;
|
||||
})
|
||||
@@ -158,68 +135,33 @@ const getDataList = () => {
|
||||
};
|
||||
// 删除
|
||||
const delClick = (row) => {
|
||||
ElMessageBox.confirm('请确认是否删除该条数据?', '删除', {
|
||||
ElMessageBox.confirm('请确认是否删除该司机数据?', '删除', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'error',
|
||||
type: 'warning',
|
||||
})
|
||||
.then(() => {
|
||||
ElMessage({
|
||||
type: 'success',
|
||||
message: '删除成功',
|
||||
});
|
||||
driverDel(row.id)
|
||||
.then(() => {
|
||||
ElMessage.success('删除成功');
|
||||
getDataList();
|
||||
})
|
||||
.catch((error) => {
|
||||
ElMessage.error('删除失败');
|
||||
console.error('删除失败:', error);
|
||||
});
|
||||
})
|
||||
.catch(() => {
|
||||
ElMessage({
|
||||
type: 'info',
|
||||
message: '取消删除',
|
||||
});
|
||||
// 用户取消删除
|
||||
});
|
||||
};
|
||||
// 新增司机
|
||||
// 新增/编辑司机
|
||||
const showAddDialog = (row) => {
|
||||
if (DriverDialogRef.value) {
|
||||
DriverDialogRef.value.onShowDialog(row);
|
||||
}
|
||||
};
|
||||
// 详情
|
||||
const showDetailDialog = (row) => {
|
||||
if (DetailDialogRef.value) {
|
||||
DetailDialogRef.value.onShowDetailDialog(row);
|
||||
}
|
||||
};
|
||||
|
||||
// 使用智能处理方案 - 根据图片域名决定是否使用代理
|
||||
const getImageListSync = (imageUrl) => {
|
||||
return getImageListSmart(imageUrl);
|
||||
};
|
||||
|
||||
// 异步版本用于调试
|
||||
const getImageListAsync = async (imageUrl) => {
|
||||
const urls = getImageList(imageUrl);
|
||||
console.log('getImageList 输入参数:', imageUrl);
|
||||
console.log('getImageList 参数类型:', typeof imageUrl);
|
||||
console.log('getImageList 解析结果:', urls);
|
||||
|
||||
// 测试每个URL的可访问性
|
||||
for (let i = 0; i < urls.length; i++) {
|
||||
const url = urls[i];
|
||||
console.log(`URL ${i + 1}:`, url);
|
||||
console.log(`URL ${i + 1} 是否以http开头:`, url.startsWith('http'));
|
||||
console.log(`URL ${i + 1} 长度:`, url.length);
|
||||
|
||||
// 测试URL可访问性
|
||||
const isAccessible = await testImageUrl(url);
|
||||
console.log(`URL ${i + 1} 可访问性:`, isAccessible);
|
||||
}
|
||||
|
||||
return urls;
|
||||
};
|
||||
|
||||
// 使用工具函数处理图片错误
|
||||
const handleImageErrorLocal = (imageUrl, index) => {
|
||||
handleImageError(imageUrl, index);
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
getDataList();
|
||||
|
||||
@@ -7,158 +7,38 @@
|
||||
<el-form-item label="司机手机号" prop="mobile">
|
||||
<el-input v-model="ruleForm.mobile" placeholder="请输入司机手机号" clearable></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="账号状态" prop="status">
|
||||
<el-radio-group v-model="ruleForm.status">
|
||||
<el-radio :value="0">启用</el-radio>
|
||||
<el-radio :value="1">禁用</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="车牌号" prop="carNumber">
|
||||
<el-input v-model="ruleForm.carNumber" placeholder="请输入车牌号" clearable></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="驾驶证" prop="driverImg">
|
||||
<el-form-item label="驾驶证照片" prop="driver_license">
|
||||
<el-upload
|
||||
:limit="2"
|
||||
list-type="picture-card"
|
||||
action="/api/common/upload"
|
||||
:on-success="
|
||||
(response, file, fileList) => {
|
||||
return handleAvatarSuccess(response, file, fileList, 'driverImg');
|
||||
}
|
||||
"
|
||||
:on-success="(response, file, fileList) => handleAvatarSuccess(response, file, fileList, 'driver_license')"
|
||||
:on-preview="handlePreview"
|
||||
:on-remove="
|
||||
(file, fileList) => {
|
||||
return handleRemove(file, fileList, 'driverImg');
|
||||
}
|
||||
"
|
||||
:on-remove="(file, fileList) => handleRemove(file, fileList, 'driver_license')"
|
||||
:before-upload="beforeAvatarUpload"
|
||||
:file-list="ruleForm.driverImg"
|
||||
:file-list="ruleForm.driver_license"
|
||||
:headers="importHeaders"
|
||||
:on-exceed="
|
||||
(files, uploadFiles) => {
|
||||
return handleExceed(files, uploadFiles, 2);
|
||||
}
|
||||
"
|
||||
:on-exceed="() => handleExceed(2)"
|
||||
>
|
||||
<el-icon><Plus /></el-icon>
|
||||
</el-upload>
|
||||
</el-form-item>
|
||||
<el-form-item label="行驶证" prop="licenseImg">
|
||||
<el-form-item label="身份证照片" prop="id_card">
|
||||
<el-upload
|
||||
:limit="2"
|
||||
list-type="picture-card"
|
||||
action="/api/common/upload"
|
||||
:on-success="
|
||||
(response, file, fileList) => {
|
||||
return handleAvatarSuccess(response, file, fileList, 'licenseImg');
|
||||
}
|
||||
"
|
||||
:on-success="(response, file, fileList) => handleAvatarSuccess(response, file, fileList, 'id_card')"
|
||||
:on-preview="handlePreview"
|
||||
:on-remove="
|
||||
(file, fileList) => {
|
||||
return handleRemove(file, fileList, 'licenseImg');
|
||||
}
|
||||
"
|
||||
:on-remove="(file, fileList) => handleRemove(file, fileList, 'id_card')"
|
||||
:before-upload="beforeAvatarUpload"
|
||||
:file-list="ruleForm.licenseImg"
|
||||
:file-list="ruleForm.id_card"
|
||||
:headers="importHeaders"
|
||||
:on-exceed="
|
||||
(files, uploadFiles) => {
|
||||
return handleExceed(files, uploadFiles, 2);
|
||||
}
|
||||
"
|
||||
:on-exceed="() => handleExceed(2)"
|
||||
>
|
||||
<el-icon><Plus /></el-icon>
|
||||
</el-upload>
|
||||
</el-form-item>
|
||||
<el-form-item label="牧运通备案码" prop="codeImg">
|
||||
<el-upload
|
||||
:limit="2"
|
||||
list-type="picture-card"
|
||||
action="/api/common/upload"
|
||||
:on-success="
|
||||
(response, file, fileList) => {
|
||||
return handleAvatarSuccess(response, file, fileList, 'codeImg');
|
||||
}
|
||||
"
|
||||
:on-preview="handlePreview"
|
||||
:on-remove="
|
||||
(file, fileList) => {
|
||||
return handleRemove(file, fileList, 'codeImg');
|
||||
}
|
||||
"
|
||||
:before-upload="beforeAvatarUpload"
|
||||
:file-list="ruleForm.codeImg"
|
||||
:headers="importHeaders"
|
||||
:on-exceed="
|
||||
(files, uploadFiles) => {
|
||||
return handleExceed(files, uploadFiles, 2);
|
||||
}
|
||||
"
|
||||
>
|
||||
<el-icon><Plus /></el-icon>
|
||||
</el-upload>
|
||||
</el-form-item>
|
||||
<el-form-item label="车头&车身照片" prop="carImg">
|
||||
<el-upload
|
||||
:limit="2"
|
||||
list-type="picture-card"
|
||||
action="/api/common/upload"
|
||||
:on-success="
|
||||
(response, file, fileList) => {
|
||||
return handleAvatarSuccess(response, file, fileList, 'carImg');
|
||||
}
|
||||
"
|
||||
:on-preview="handlePreview"
|
||||
:on-remove="
|
||||
(file, fileList) => {
|
||||
return handleRemove(file, fileList, 'carImg');
|
||||
}
|
||||
"
|
||||
:before-upload="beforeAvatarUpload"
|
||||
:file-list="ruleForm.carImg"
|
||||
:headers="importHeaders"
|
||||
:on-exceed="
|
||||
(files, uploadFiles) => {
|
||||
return handleExceed(files, uploadFiles, 2);
|
||||
}
|
||||
"
|
||||
>
|
||||
<el-icon><Plus /></el-icon>
|
||||
</el-upload>
|
||||
</el-form-item>
|
||||
<el-form-item label="身份证前后面" prop="idCardImg">
|
||||
<el-upload
|
||||
:limit="2"
|
||||
list-type="picture-card"
|
||||
action="/api/common/upload"
|
||||
:on-success="
|
||||
(response, file, fileList) => {
|
||||
return handleAvatarSuccess(response, file, fileList, 'idCardImg');
|
||||
}
|
||||
"
|
||||
:on-preview="handlePreview"
|
||||
:on-remove="
|
||||
(file, fileList) => {
|
||||
return handleRemove(file, fileList, 'idCardImg');
|
||||
}
|
||||
"
|
||||
:before-upload="beforeAvatarUpload"
|
||||
:file-list="ruleForm.idCardImg"
|
||||
:headers="importHeaders"
|
||||
:on-exceed="
|
||||
(files, uploadFiles) => {
|
||||
return handleExceed(files, uploadFiles, 2);
|
||||
}
|
||||
"
|
||||
>
|
||||
<el-icon><Plus /></el-icon>
|
||||
</el-upload>
|
||||
</el-form-item>
|
||||
<el-form-item label="备注" prop="remark">
|
||||
<el-input v-model="ruleForm.remark" placeholder="请输入备注" clearable></el-input>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
@@ -174,13 +54,15 @@
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted, nextTick } from 'vue';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import { Plus } from '@element-plus/icons-vue';
|
||||
import { driverAdd, driverEdit } from '@/api/userManage.js';
|
||||
import { checkMobile } from '~/utils/validateFuns.js';
|
||||
import { useUserStore } from '../../store/user';
|
||||
|
||||
const userStore = useUserStore();
|
||||
const importHeaders = reactive({ Authorization: userStore.$state.token });
|
||||
const emits = defineEmits();
|
||||
|
||||
const formDataRef = ref();
|
||||
const data = reactive({
|
||||
dialogVisible: false,
|
||||
@@ -188,18 +70,15 @@ const data = reactive({
|
||||
saveLoading: false,
|
||||
dialogVisibleImg: false,
|
||||
dialogImageUrl: '',
|
||||
isEdit: false,
|
||||
});
|
||||
|
||||
const ruleForm = reactive({
|
||||
username: '', // 司机姓名
|
||||
mobile: '', // 司机手机号
|
||||
status: '', // 账号状态
|
||||
carNumber: '', // 车牌号
|
||||
driverImg: [], // 驾驶证
|
||||
licenseImg: [], // 行驶证
|
||||
codeImg: [], // 牧运通备案码
|
||||
carImg: [], // 车头&车身照片
|
||||
idCardImg: [], // 身份证前后面
|
||||
remark: '', // 备注
|
||||
id: null,
|
||||
username: '',
|
||||
mobile: '',
|
||||
driver_license: [], // 驾驶证照片
|
||||
id_card: [], // 身份证照片
|
||||
});
|
||||
|
||||
const rules = reactive({
|
||||
@@ -210,228 +89,141 @@ const rules = reactive({
|
||||
validator(rule, value, callback) {
|
||||
if (!value) {
|
||||
callback(new Error('请输入司机手机号'));
|
||||
}
|
||||
if (!checkMobile(value)) {
|
||||
} else if (!/^1[3-9]\d{9}$/.test(value)) {
|
||||
callback(new Error('请输入正确的手机号'));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
callback();
|
||||
},
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
status: [{ required: true, message: '请选择账户状态', trigger: 'change' }],
|
||||
carNumber: [{ required: true, message: '请输入车牌号', trigger: 'blur' }],
|
||||
driverImg: [{ required: true, message: '请上传驾驶证', trigger: 'change' }],
|
||||
licenseImg: [{ required: true, message: '请上传行驶证', trigger: 'change' }],
|
||||
codeImg: [{ required: true, message: '请上传牧运通备案码', trigger: 'change' }],
|
||||
carImg: [{ required: true, message: '请上传车头&车身照片', trigger: 'change' }],
|
||||
idCardImg: [{ required: true, message: '请上传身份证前后面', trigger: 'change' }],
|
||||
driver_license: [{ required: true, message: '请上传驾驶证照片', trigger: 'change' }],
|
||||
id_card: [{ required: true, message: '请上传身份证照片', trigger: 'change' }],
|
||||
});
|
||||
|
||||
const handleAvatarSuccess = (res, file, fileList, type) => {
|
||||
console.log('上传成功响应:', res);
|
||||
console.log('文件信息:', file);
|
||||
console.log('类型:', type);
|
||||
|
||||
if (ruleForm.hasOwnProperty(type)) {
|
||||
// 检查响应格式,支持多种可能的响应结构
|
||||
let imageUrl = null;
|
||||
|
||||
if (res && res.data && res.data.src) {
|
||||
// 格式1: res.data.src
|
||||
imageUrl = res.data.src;
|
||||
} else if (res && res.data && typeof res.data === 'string') {
|
||||
// 格式2: res.data 直接是URL字符串
|
||||
imageUrl = res.data;
|
||||
} else if (res && res.url) {
|
||||
// 格式3: res.url
|
||||
imageUrl = res.url;
|
||||
} else if (res && typeof res === 'string') {
|
||||
// 格式4: res 直接是URL字符串
|
||||
imageUrl = res;
|
||||
} else {
|
||||
console.error('无法解析上传响应:', res);
|
||||
ElMessage.error('图片上传失败:响应格式不正确');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('解析到的图片URL:', imageUrl);
|
||||
ruleForm[type].push({ url: imageUrl });
|
||||
if (imageUrl) {
|
||||
// 直接更新 fileList
|
||||
file.url = imageUrl;
|
||||
ruleForm[type] = fileList;
|
||||
console.log(`${type} 上传成功:`, imageUrl, 'fileList:', fileList);
|
||||
} else {
|
||||
console.error('无法解析图片URL:', res);
|
||||
ElMessage.error('上传失败:无法获取图片URL');
|
||||
}
|
||||
}
|
||||
};
|
||||
// 移除
|
||||
|
||||
const handleRemove = (file, fileList, type) => {
|
||||
if (ruleForm.hasOwnProperty(type)) {
|
||||
ruleForm[type] = fileList;
|
||||
}
|
||||
};
|
||||
// 上传时 - 判断
|
||||
const beforeAvatarUpload = (file) => {
|
||||
const isJPG = file.type === 'image/jpeg' || file.type === 'image/png';
|
||||
const isLt1M = file.size / 1024 / 1024 < 2;
|
||||
|
||||
if (!isJPG) {
|
||||
ElMessage.error('上传头像图片只能是 JPG 格式!');
|
||||
const beforeAvatarUpload = (file) => {
|
||||
const isImage = file.type.startsWith('image/');
|
||||
const isLt2M = file.size / 1024 / 1024 < 2;
|
||||
|
||||
if (!isImage) {
|
||||
ElMessage.error('上传文件只能是图片格式!');
|
||||
}
|
||||
if (!isLt1M) {
|
||||
ElMessage.error('上传头像图片大小不能超过 2MB!');
|
||||
if (!isLt2M) {
|
||||
ElMessage.error('上传图片大小不能超过 2MB!');
|
||||
}
|
||||
return isJPG && isLt1M;
|
||||
return isImage && isLt2M;
|
||||
};
|
||||
// 超出限制
|
||||
const handleExceed = (files, uploadFiles, number) => {
|
||||
ElMessage({
|
||||
message: `最多上传${number}张照片`,
|
||||
type: 'warning',
|
||||
});
|
||||
|
||||
const handleExceed = (number) => {
|
||||
ElMessage.warning(`最多只能上传${number}张图片!`);
|
||||
};
|
||||
// 预览
|
||||
|
||||
const handlePreview = (file) => {
|
||||
data.dialogImageUrl = file.url;
|
||||
data.dialogVisibleImg = true;
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
if (formDataRef.value) {
|
||||
formDataRef.value.resetFields();
|
||||
}
|
||||
formDataRef.value?.resetFields();
|
||||
ruleForm.id = null;
|
||||
ruleForm.username = '';
|
||||
ruleForm.mobile = '';
|
||||
ruleForm.driver_license = [];
|
||||
ruleForm.id_card = [];
|
||||
data.dialogVisible = false;
|
||||
data.isEdit = false;
|
||||
};
|
||||
|
||||
// 保存按钮
|
||||
const onClickSave = () => {
|
||||
if (formDataRef.value) {
|
||||
formDataRef.value.validate((valid) => {
|
||||
if (valid) {
|
||||
const params = {
|
||||
username: ruleForm.username,
|
||||
mobile: ruleForm.mobile,
|
||||
status: ruleForm.status,
|
||||
carNumber: ruleForm.carNumber,
|
||||
remark: ruleForm.remark,
|
||||
};
|
||||
params.driverLicense = ruleForm.driverImg.length > 0 ? ruleForm.driverImg.map((item) => item.url).join(',') : '';
|
||||
params.drivingLicense = ruleForm.licenseImg.length > 0 ? ruleForm.licenseImg.map((item) => item.url).join(',') : '';
|
||||
params.carImg = ruleForm.carImg.length > 0 ? ruleForm.carImg.map((item) => item.url).join(',') : '';
|
||||
params.recordCode = ruleForm.codeImg.length > 0 ? ruleForm.codeImg.map((item) => item.url).join(',') : '';
|
||||
params.idCard = ruleForm.idCardImg.length > 0 ? ruleForm.idCardImg.map((item) => item.url).join(',') : '';
|
||||
// params.recordCode = ruleForm.codeImg.length > 0 ? ruleForm.codeImg[0].url : '';
|
||||
data.saveLoading = true;
|
||||
if (data.title === '新增司机') {
|
||||
driverAdd(params)
|
||||
.then((res) => {
|
||||
data.saveLoading = false;
|
||||
if (res.code === 200) {
|
||||
ElMessage({
|
||||
message: res.msg,
|
||||
type: 'success',
|
||||
});
|
||||
emits('success');
|
||||
if (formDataRef.value) {
|
||||
formDataRef.value.resetFields();
|
||||
data.dialogVisible = false;
|
||||
} else {
|
||||
data.saveLoading = false;
|
||||
ElMessage.error(res.msg);
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
data.saveLoading = false;
|
||||
});
|
||||
formDataRef.value.validate((valid) => {
|
||||
if (valid) {
|
||||
data.saveLoading = true;
|
||||
|
||||
const params = {
|
||||
id: ruleForm.id,
|
||||
username: ruleForm.username,
|
||||
mobile: ruleForm.mobile,
|
||||
driverLicense: ruleForm.driver_license.length > 0 ? ruleForm.driver_license.map((item) => item.url).join(',') : '',
|
||||
idCard: ruleForm.id_card.length > 0 ? ruleForm.id_card.map((item) => item.url).join(',') : '',
|
||||
};
|
||||
|
||||
console.log('提交数据:', params);
|
||||
|
||||
const apiCall = data.isEdit ? driverEdit(params) : driverAdd(params);
|
||||
|
||||
apiCall.then((res) => {
|
||||
data.saveLoading = false;
|
||||
if (res.code === 200) {
|
||||
ElMessage.success(data.isEdit ? '编辑成功' : '新增成功');
|
||||
handleClose();
|
||||
emits('success');
|
||||
} else {
|
||||
params.id = ruleForm.id;
|
||||
driverEdit(params)
|
||||
.then((res) => {
|
||||
data.saveLoading = false;
|
||||
if (res.code === 200) {
|
||||
ElMessage({
|
||||
message: res.msg,
|
||||
type: 'success',
|
||||
});
|
||||
emits('success');
|
||||
if (formDataRef.value) {
|
||||
formDataRef.value.resetFields();
|
||||
data.dialogVisible = false;
|
||||
} else {
|
||||
data.saveLoading = false;
|
||||
ElMessage.error(res.msg);
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
data.saveLoading = false;
|
||||
});
|
||||
ElMessage.error(res.msg || '操作失败');
|
||||
}
|
||||
} else {
|
||||
console.log('error submit!');
|
||||
}
|
||||
});
|
||||
}
|
||||
}).catch((error) => {
|
||||
data.saveLoading = false;
|
||||
console.error('保存失败:', error);
|
||||
ElMessage.error('保存失败,请稍后重试');
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
const onShowDialog = (row) => {
|
||||
data.title = row ? '编辑司机' : '新增司机';
|
||||
|
||||
const onShowDialog = (row = null) => {
|
||||
data.dialogVisible = true;
|
||||
if (formDataRef.value) {
|
||||
formDataRef.value.resetFields();
|
||||
}
|
||||
data.isEdit = !!row;
|
||||
data.title = row ? '编辑司机' : '新增司机';
|
||||
|
||||
if (row) {
|
||||
nextTick(() => {
|
||||
ruleForm.id = row.id;
|
||||
ruleForm.username = row.username; // 司机姓名
|
||||
ruleForm.mobile = row.mobile; // 司机手机号
|
||||
ruleForm.carNumber = row.car_number; // 车牌号 - 修复字段名
|
||||
ruleForm.status = row.status || '1'; // 账号状态 - 添加默认值
|
||||
ruleForm.remark = row.remark; // 备注
|
||||
ruleForm.driverImg = row.driver_license
|
||||
? getImageList(row.driver_license).map((item) => {
|
||||
return {
|
||||
url: item,
|
||||
};
|
||||
})
|
||||
: [];
|
||||
ruleForm.licenseImg = row.driving_license
|
||||
? getImageList(row.driving_license).map((item) => {
|
||||
return {
|
||||
url: item,
|
||||
};
|
||||
})
|
||||
: [];
|
||||
ruleForm.carImg = row.car_img
|
||||
? getImageList(row.car_img).map((item) => {
|
||||
return {
|
||||
url: item,
|
||||
};
|
||||
})
|
||||
: [];
|
||||
ruleForm.codeImg = row.record_code
|
||||
? getImageList(row.record_code).map((item) => {
|
||||
return {
|
||||
url: item,
|
||||
};
|
||||
})
|
||||
: [];
|
||||
ruleForm.idCardImg = row.id_card
|
||||
? getImageList(row.id_card).map((item) => {
|
||||
return {
|
||||
url: item,
|
||||
};
|
||||
})
|
||||
: [];
|
||||
});
|
||||
ruleForm.id = row.id;
|
||||
ruleForm.username = row.username || '';
|
||||
ruleForm.mobile = row.mobile || '';
|
||||
ruleForm.driver_license = row.driver_license ? row.driver_license.split(',').map(url => ({ url: url.trim() })) : [];
|
||||
ruleForm.id_card = row.id_card ? row.id_card.split(',').map(url => ({ url: url.trim() })) : [];
|
||||
}
|
||||
};
|
||||
|
||||
// 处理逗号分隔的图片URL
|
||||
const getImageList = (imageUrl) => {
|
||||
if (!imageUrl || imageUrl.trim() === '') {
|
||||
return [];
|
||||
}
|
||||
// 按逗号分割并过滤空字符串
|
||||
return imageUrl.split(',').map(url => url.trim()).filter(url => url !== '');
|
||||
};
|
||||
|
||||
defineExpose({
|
||||
onShowDialog,
|
||||
});
|
||||
defineExpose({ onShowDialog });
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
<style scoped>
|
||||
.dialog-footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
</style>
|
||||
|
||||
218
pc-cattle-transportation/src/views/userManage/vehicle.vue
Normal file
218
pc-cattle-transportation/src/views/userManage/vehicle.vue
Normal file
@@ -0,0 +1,218 @@
|
||||
<template>
|
||||
<div>
|
||||
<base-search :formItemList="formItemList" @search="searchFrom" ref="baseSearchRef"></base-search>
|
||||
<div style="display: flex; padding: 10px; background: #fff; margin-bottom: 10px">
|
||||
<el-button type="primary" @click="showAddDialog(null)">新增车辆</el-button>
|
||||
</div>
|
||||
<div class="main-container">
|
||||
<el-table :data="data.rows" border v-loading="data.dataListLoading" element-loading-text="数据加载中..." style="width: 100%">
|
||||
<el-table-column label="车牌号" prop="licensePlate" width="120"></el-table-column>
|
||||
<el-table-column label="车头照片" prop="carFrontPhoto" min-width="120">
|
||||
<template #default="scope">
|
||||
<el-image
|
||||
v-if="scope.row.carFrontPhoto"
|
||||
:src="scope.row.carFrontPhoto"
|
||||
style="width: 60px; height: 60px; border-radius: 4px; border: 1px solid #dcdfe6"
|
||||
fit="cover"
|
||||
:preview-src-list="[scope.row.carFrontPhoto]"
|
||||
preview-teleported
|
||||
:lazy="true"
|
||||
>
|
||||
<template #error>
|
||||
<div style="width: 60px; height: 60px; display: flex; align-items: center; justify-content: center; background: #f5f7fa; border-radius: 4px; color: #909399">
|
||||
<el-icon size="20"><Picture /></el-icon>
|
||||
</div>
|
||||
</template>
|
||||
</el-image>
|
||||
<span v-else style="color: #999; font-size: 12px">暂无图片</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="车尾照片" prop="carRearPhoto" min-width="120">
|
||||
<template #default="scope">
|
||||
<el-image
|
||||
v-if="scope.row.carRearPhoto"
|
||||
:src="scope.row.carRearPhoto"
|
||||
style="width: 60px; height: 60px; border-radius: 4px; border: 1px solid #dcdfe6"
|
||||
fit="cover"
|
||||
:preview-src-list="[scope.row.carRearPhoto]"
|
||||
preview-teleported
|
||||
:lazy="true"
|
||||
>
|
||||
<template #error>
|
||||
<div style="width: 60px; height: 60px; display: flex; align-items: center; justify-content: center; background: #f5f7fa; border-radius: 4px; color: #909399">
|
||||
<el-icon size="20"><Picture /></el-icon>
|
||||
</div>
|
||||
</template>
|
||||
</el-image>
|
||||
<span v-else style="color: #999; font-size: 12px">暂无图片</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="行驶证" prop="drivingLicensePhoto" min-width="120">
|
||||
<template #default="scope">
|
||||
<el-image
|
||||
v-if="scope.row.drivingLicensePhoto"
|
||||
:src="scope.row.drivingLicensePhoto"
|
||||
style="width: 60px; height: 60px; border-radius: 4px; border: 1px solid #dcdfe6"
|
||||
fit="cover"
|
||||
:preview-src-list="[scope.row.drivingLicensePhoto]"
|
||||
preview-teleported
|
||||
:lazy="true"
|
||||
>
|
||||
<template #error>
|
||||
<div style="width: 60px; height: 60px; display: flex; align-items: center; justify-content: center; background: #f5f7fa; border-radius: 4px; color: #909399">
|
||||
<el-icon size="20"><Picture /></el-icon>
|
||||
</div>
|
||||
</template>
|
||||
</el-image>
|
||||
<span v-else style="color: #999; font-size: 12px">暂无图片</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="牧运通备案码" prop="recordCode" min-width="120">
|
||||
<template #default="scope">
|
||||
<el-image
|
||||
v-if="scope.row.recordCode"
|
||||
:src="scope.row.recordCode"
|
||||
style="width: 60px; height: 60px; border-radius: 4px; border: 1px solid #dcdfe6"
|
||||
fit="cover"
|
||||
:preview-src-list="[scope.row.recordCode]"
|
||||
preview-teleported
|
||||
:lazy="true"
|
||||
>
|
||||
<template #error>
|
||||
<div style="width: 60px; height: 60px; display: flex; align-items: center; justify-content: center; background: #f5f7fa; border-radius: 4px; color: #909399">
|
||||
<el-icon size="20"><Picture /></el-icon>
|
||||
</div>
|
||||
</template>
|
||||
</el-image>
|
||||
<span v-else style="color: #999; font-size: 12px">暂无图片</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="备注" prop="remark" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column label="创建时间" prop="createTime" width="180"></el-table-column>
|
||||
<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>
|
||||
<template #empty>
|
||||
<div class="dataListOnEmpty">暂无数据</div>
|
||||
</template>
|
||||
</el-table>
|
||||
<pagination v-model:limit="form.pageSize" v-model:page="form.pageNum" :total="data.total" @pagination="getDataList" />
|
||||
<VehicleDialog ref="VehicleDialogRef" @success="getDataList" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted } from 'vue';
|
||||
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||
import { Picture } from '@element-plus/icons-vue';
|
||||
import baseSearch from '@/components/common/searchCustom/index.vue';
|
||||
import { vehicleList, vehicleDel } from '@/api/userManage.js';
|
||||
import VehicleDialog from './vehicleDialog.vue';
|
||||
|
||||
const baseSearchRef = ref();
|
||||
const VehicleDialogRef = ref();
|
||||
const formItemList = reactive([
|
||||
{
|
||||
label: '车牌号',
|
||||
param: 'licensePlate',
|
||||
type: 'input',
|
||||
placeholder: '请输入车牌号',
|
||||
span: 7,
|
||||
labelWidth: 100,
|
||||
},
|
||||
]);
|
||||
|
||||
const searchFrom = () => {
|
||||
form.pageNum = 1;
|
||||
getDataList();
|
||||
};
|
||||
|
||||
const data = reactive({
|
||||
rows: [],
|
||||
total: 0,
|
||||
dataListLoading: false,
|
||||
});
|
||||
|
||||
const form = reactive({
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
licensePlate: '',
|
||||
});
|
||||
|
||||
const showAddDialog = (row) => {
|
||||
VehicleDialogRef.value.open(row);
|
||||
};
|
||||
|
||||
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(() => {});
|
||||
};
|
||||
|
||||
const getDataList = async () => {
|
||||
data.dataListLoading = true;
|
||||
try {
|
||||
const params = {
|
||||
pageNum: form.pageNum,
|
||||
pageSize: form.pageSize,
|
||||
licensePlate: form.licensePlate,
|
||||
};
|
||||
console.log('查询参数:', params);
|
||||
|
||||
const res = await vehicleList(params);
|
||||
console.log('查询结果:', res);
|
||||
|
||||
if (res.code === 200) {
|
||||
// 数据嵌套在 res.data.data 中
|
||||
const dataInfo = res.data?.data || res.data;
|
||||
data.rows = dataInfo?.rows || [];
|
||||
data.total = dataInfo?.total || 0;
|
||||
console.log('提取的数据:', dataInfo);
|
||||
console.log('列表数据:', data.rows);
|
||||
} else {
|
||||
ElMessage.error(res.msg || '查询失败');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('查询车辆列表失败:', error);
|
||||
ElMessage.error('查询失败,请稍后重试');
|
||||
} finally {
|
||||
data.dataListLoading = false;
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
getDataList();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.main-container {
|
||||
padding: 10px;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.dataListOnEmpty {
|
||||
text-align: center;
|
||||
padding: 20px;
|
||||
color: #999;
|
||||
}
|
||||
</style>
|
||||
|
||||
249
pc-cattle-transportation/src/views/userManage/vehicleDialog.vue
Normal file
249
pc-cattle-transportation/src/views/userManage/vehicleDialog.vue
Normal file
@@ -0,0 +1,249 @@
|
||||
<template>
|
||||
<el-dialog v-model="data.dialogVisible" :title="data.title" :before-close="handleClose" style="width: 650px; padding-bottom: 20px">
|
||||
<el-form ref="formDataRef" :model="ruleForm" :rules="rules" label-width="auto">
|
||||
<el-form-item label="车牌号" prop="licensePlate">
|
||||
<el-input v-model="ruleForm.licensePlate" placeholder="请输入车牌号" clearable></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="车头照片" prop="carFrontPhoto">
|
||||
<el-upload
|
||||
:limit="1"
|
||||
list-type="picture-card"
|
||||
action="/api/common/upload"
|
||||
:on-success="(response, file, fileList) => handleAvatarSuccess(response, file, fileList, 'carFrontPhoto')"
|
||||
:on-preview="handlePreview"
|
||||
:on-remove="(file, fileList) => handleRemove(file, fileList, 'carFrontPhoto')"
|
||||
:before-upload="beforeAvatarUpload"
|
||||
:file-list="ruleForm.carFrontPhoto"
|
||||
:headers="importHeaders"
|
||||
:on-exceed="handleExceed"
|
||||
>
|
||||
<el-icon><Plus /></el-icon>
|
||||
</el-upload>
|
||||
</el-form-item>
|
||||
<el-form-item label="车尾照片" prop="carRearPhoto">
|
||||
<el-upload
|
||||
:limit="1"
|
||||
list-type="picture-card"
|
||||
action="/api/common/upload"
|
||||
:on-success="(response, file, fileList) => handleAvatarSuccess(response, file, fileList, 'carRearPhoto')"
|
||||
:on-preview="handlePreview"
|
||||
:on-remove="(file, fileList) => handleRemove(file, fileList, 'carRearPhoto')"
|
||||
:before-upload="beforeAvatarUpload"
|
||||
:file-list="ruleForm.carRearPhoto"
|
||||
:headers="importHeaders"
|
||||
:on-exceed="handleExceed"
|
||||
>
|
||||
<el-icon><Plus /></el-icon>
|
||||
</el-upload>
|
||||
</el-form-item>
|
||||
<el-form-item label="行驶证照片" prop="drivingLicensePhoto">
|
||||
<el-upload
|
||||
:limit="1"
|
||||
list-type="picture-card"
|
||||
action="/api/common/upload"
|
||||
:on-success="(response, file, fileList) => handleAvatarSuccess(response, file, fileList, 'drivingLicensePhoto')"
|
||||
:on-preview="handlePreview"
|
||||
:on-remove="(file, fileList) => handleRemove(file, fileList, 'drivingLicensePhoto')"
|
||||
:before-upload="beforeAvatarUpload"
|
||||
:file-list="ruleForm.drivingLicensePhoto"
|
||||
:headers="importHeaders"
|
||||
:on-exceed="handleExceed"
|
||||
>
|
||||
<el-icon><Plus /></el-icon>
|
||||
</el-upload>
|
||||
</el-form-item>
|
||||
<el-form-item label="牧运通备案码" prop="recordCode">
|
||||
<el-upload
|
||||
:limit="1"
|
||||
list-type="picture-card"
|
||||
action="/api/common/upload"
|
||||
:on-success="(response, file, fileList) => handleAvatarSuccess(response, file, fileList, 'recordCode')"
|
||||
:on-preview="handlePreview"
|
||||
:on-remove="(file, fileList) => handleRemove(file, fileList, 'recordCode')"
|
||||
:before-upload="beforeAvatarUpload"
|
||||
:file-list="ruleForm.recordCode"
|
||||
:headers="importHeaders"
|
||||
:on-exceed="handleExceed"
|
||||
>
|
||||
<el-icon><Plus /></el-icon>
|
||||
</el-upload>
|
||||
</el-form-item>
|
||||
<el-form-item label="备注" prop="remark">
|
||||
<el-input v-model="ruleForm.remark" placeholder="请输入备注" clearable></el-input>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button :loading="data.saveLoading" type="primary" @click="onClickSave">保存</el-button>
|
||||
<el-button @click="handleClose">取消</el-button>
|
||||
</span>
|
||||
</template>
|
||||
<el-dialog v-model="data.dialogVisibleImg">
|
||||
<img w-full :src="data.dialogImageUrl" alt="Preview Image" />
|
||||
</el-dialog>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive } from 'vue';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import { Plus } from '@element-plus/icons-vue';
|
||||
import { vehicleAdd, vehicleEdit } from '@/api/userManage.js';
|
||||
import { useUserStore } from '../../store/user';
|
||||
|
||||
const userStore = useUserStore();
|
||||
const importHeaders = reactive({ Authorization: userStore.$state.token });
|
||||
const emits = defineEmits(['success']);
|
||||
const formDataRef = ref();
|
||||
const data = reactive({
|
||||
dialogVisible: false,
|
||||
title: '',
|
||||
saveLoading: false,
|
||||
dialogVisibleImg: false,
|
||||
dialogImageUrl: '',
|
||||
isEdit: false,
|
||||
});
|
||||
const ruleForm = reactive({
|
||||
id: null,
|
||||
licensePlate: '',
|
||||
carFrontPhoto: [],
|
||||
carRearPhoto: [],
|
||||
drivingLicensePhoto: [],
|
||||
recordCode: [],
|
||||
remark: '',
|
||||
});
|
||||
|
||||
const rules = reactive({
|
||||
licensePlate: [{ required: true, message: '请输入车牌号', trigger: 'blur' }],
|
||||
});
|
||||
|
||||
const handleAvatarSuccess = (res, file, fileList, type) => {
|
||||
console.log('上传成功响应:', res);
|
||||
|
||||
if (ruleForm.hasOwnProperty(type)) {
|
||||
let imageUrl = null;
|
||||
|
||||
if (res && res.data && res.data.src) {
|
||||
imageUrl = res.data.src;
|
||||
} else if (res && res.data && typeof res.data === 'string') {
|
||||
imageUrl = res.data;
|
||||
} else if (res && res.url) {
|
||||
imageUrl = res.url;
|
||||
} else if (res && typeof res === 'string') {
|
||||
imageUrl = res;
|
||||
}
|
||||
|
||||
if (imageUrl) {
|
||||
ruleForm[type] = [{ url: imageUrl, uid: file.uid, name: file.name }];
|
||||
console.log(`${type} 上传成功:`, imageUrl);
|
||||
} else {
|
||||
console.error('无法解析图片URL:', res);
|
||||
ElMessage.error('上传失败:无法获取图片URL');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleRemove = (file, fileList, type) => {
|
||||
ruleForm[type] = fileList;
|
||||
};
|
||||
|
||||
const handlePreview = (file) => {
|
||||
data.dialogImageUrl = file.url;
|
||||
data.dialogVisibleImg = true;
|
||||
};
|
||||
|
||||
const beforeAvatarUpload = (file) => {
|
||||
const isImage = file.type.startsWith('image/');
|
||||
const isLt2M = file.size / 1024 / 1024 < 2;
|
||||
|
||||
if (!isImage) {
|
||||
ElMessage.error('上传文件只能是图片格式!');
|
||||
return false;
|
||||
}
|
||||
if (!isLt2M) {
|
||||
ElMessage.error('上传图片大小不能超过 2MB!');
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
const handleExceed = () => {
|
||||
ElMessage.warning('最多只能上传一张图片!');
|
||||
};
|
||||
|
||||
const onClickSave = async () => {
|
||||
await formDataRef.value.validate(async (valid) => {
|
||||
if (valid) {
|
||||
data.saveLoading = true;
|
||||
|
||||
try {
|
||||
const formData = {
|
||||
id: data.isEdit ? ruleForm.id : null,
|
||||
licensePlate: ruleForm.licensePlate,
|
||||
carFrontPhoto: ruleForm.carFrontPhoto.length > 0 ? ruleForm.carFrontPhoto[0].url : null,
|
||||
carRearPhoto: ruleForm.carRearPhoto.length > 0 ? ruleForm.carRearPhoto[0].url : null,
|
||||
drivingLicensePhoto: ruleForm.drivingLicensePhoto.length > 0 ? ruleForm.drivingLicensePhoto[0].url : null,
|
||||
recordCode: ruleForm.recordCode.length > 0 ? ruleForm.recordCode[0].url : null,
|
||||
remark: ruleForm.remark,
|
||||
};
|
||||
|
||||
console.log('提交数据:', formData);
|
||||
|
||||
const res = data.isEdit ? await vehicleEdit(formData) : await vehicleAdd(formData);
|
||||
|
||||
if (res.code === 200) {
|
||||
ElMessage.success(data.isEdit ? '编辑成功' : '新增成功');
|
||||
handleClose();
|
||||
emits('success');
|
||||
} else {
|
||||
ElMessage.error(res.msg || '操作失败');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('保存失败:', error);
|
||||
ElMessage.error('保存失败,请稍后重试');
|
||||
} finally {
|
||||
data.saveLoading = false;
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
formDataRef.value?.resetFields();
|
||||
ruleForm.id = null;
|
||||
ruleForm.licensePlate = '';
|
||||
ruleForm.carFrontPhoto = [];
|
||||
ruleForm.carRearPhoto = [];
|
||||
ruleForm.drivingLicensePhoto = [];
|
||||
ruleForm.recordCode = [];
|
||||
ruleForm.remark = '';
|
||||
data.dialogVisible = false;
|
||||
data.isEdit = false;
|
||||
};
|
||||
|
||||
const open = (row = null) => {
|
||||
data.dialogVisible = true;
|
||||
data.isEdit = !!row;
|
||||
data.title = row ? '编辑车辆' : '新增车辆';
|
||||
|
||||
if (row) {
|
||||
ruleForm.id = row.id;
|
||||
ruleForm.licensePlate = row.licensePlate || '';
|
||||
ruleForm.carFrontPhoto = row.carFrontPhoto ? [{ url: row.carFrontPhoto }] : [];
|
||||
ruleForm.carRearPhoto = row.carRearPhoto ? [{ url: row.carRearPhoto }] : [];
|
||||
ruleForm.drivingLicensePhoto = row.drivingLicensePhoto ? [{ url: row.drivingLicensePhoto }] : [];
|
||||
ruleForm.recordCode = row.recordCode ? [{ url: row.recordCode }] : [];
|
||||
ruleForm.remark = row.remark || '';
|
||||
}
|
||||
};
|
||||
|
||||
defineExpose({ open });
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.dialog-footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
</style>
|
||||
|
||||
141
tradeCattle/IOT_DEVICE_CLEANUP_FIX_REPORT.md
Normal file
141
tradeCattle/IOT_DEVICE_CLEANUP_FIX_REPORT.md
Normal file
@@ -0,0 +1,141 @@
|
||||
# IoT设备清理功能实现报告
|
||||
|
||||
## 问题描述
|
||||
|
||||
外部物联网系统可以将设备重新分配到其他项目,但该项目中的设备数据仍然保留在数据库中,导致以下问题:
|
||||
- 数据库中存在不属于本项目的设备记录
|
||||
- 这些设备可能会在设备列表中显示,造成混淆
|
||||
- 设备数量统计不准确
|
||||
|
||||
## 解决方案
|
||||
|
||||
在设备数据同步过程中添加清理逻辑,自动删除已被重新分配到其他项目的设备。
|
||||
|
||||
### 实现内容
|
||||
|
||||
#### 1. 修改同步逻辑 (`IotDeviceSyncService.java`)
|
||||
|
||||
##### 添加必要的导入
|
||||
```java
|
||||
import java.util.Set;
|
||||
import java.util.HashSet;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
```
|
||||
|
||||
##### 在 `syncIotDeviceData()` 方法中添加设备ID集合提取
|
||||
```59:65:tradeCattle/aiotagro-cattle-trade/src/main/java/com/aiotagro/cattletrade/business/service/IotDeviceSyncService.java
|
||||
// 提取API返回的设备ID集合
|
||||
Set<String> apiDeviceIds = new HashSet<>();
|
||||
for (Map<String, Object> deviceData figuring deviceDataList) {
|
||||
if (deviceData.get("deviceId") != null) {
|
||||
apiDeviceIds.add(String.valueOf(deviceData.get("deviceId")));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
##### 添加清理调用
|
||||
```111:116:tradeCattle/aiotagro-cattle-trade/src/main/java/com/aiotagro/cattletrade/business/service/IotDeviceSyncService.java
|
||||
// 清理已被重新分配到其他项目的设备
|
||||
try {
|
||||
cleanupReassignedDevices(apiDeviceIds);
|
||||
} catch (Exception e) {
|
||||
logger.error("清理重新分配设备失败", e);
|
||||
}
|
||||
```
|
||||
|
||||
#### 2. 新增清理方法 `cleanupReassignedDevices()`
|
||||
|
||||
```368:396:tradeCattle/aiotagro-cattle-trade/src/main/java/com/aiotagro/cattletrade/business/service/IotDeviceSyncService.java
|
||||
private void cleanupReassignedDevices(Set<String> apiDeviceIds) {
|
||||
try {
|
||||
logger.info("开始清理已重新分配的设备");
|
||||
|
||||
// 查询数据库中所有设备
|
||||
List<IotDeviceData> allDevices = iotDeviceDataMapper.selectList(null);
|
||||
|
||||
int deletedCount = 0;
|
||||
for (IotDeviceData device : allDevices) {
|
||||
// 如果设备不在API返回列表中
|
||||
if (!apiDeviceIds.contains(device.getDeviceId())) {
|
||||
// 只删除未被分配的设备(tenantId和deliveryId都为null)
|
||||
if (device.getTenantId() == null && device.getDeliveryId() == null) {
|
||||
iotDeviceDataMapper.deleteById(device.getId());
|
||||
logger.info("删除已重新分配的设备: deviceId={}, id={}", device.getDeviceId(), device.getId());
|
||||
deletedCount++;
|
||||
} else {
|
||||
logger.warn("设备不在API返回列表中但已被分配,保留: deviceId={}, tenantId={}, deliveryId={}",
|
||||
device.getDeviceId(), device.getTenantId(), device.getDeliveryId());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
logger.info("清理完成,共删除 {} 个已重新分配的设备", deletedCount);
|
||||
} catch (Exception e) {
|
||||
logger.error("清理已重新分配设备时发生错误", e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 清理逻辑说明
|
||||
|
||||
1. **设备对比**:将数据库中所有设备与API返回的设备列表对比
|
||||
2. **安全删除**:只删除满足以下条件的设备:
|
||||
- 设备不在API返回列表中(已被重新分配)
|
||||
- `tenantId` 为 null(未分配给租户)
|
||||
- `deliveryId` 为 null(未分配给装车订单)
|
||||
3. **保护机制**:如果设备已被分配(有 `tenantId` 或 `deliveryId`),则保留该设备,防止误删重要数据
|
||||
4. **日志记录**:记录清理过程和结果,便于问题追踪
|
||||
|
||||
### 工作流程
|
||||
|
||||
1. 从外部API获取设备数据列表
|
||||
2. 提取API返回的设备ID集合
|
||||
3. 同步/更新API中的设备数据
|
||||
4. **新增**:清理数据库中不在API列表且未被分配的设备
|
||||
5. 记录同步日志
|
||||
|
||||
### 优势
|
||||
|
||||
- ✅ 自动清理:在同步过程中自动清理,无需手动操作
|
||||
- ✅ 安全可靠:只删除未分配的设备,已分配的设备不会被误删
|
||||
- ✅ 实时更新:每次同步都会检查并清理
|
||||
- ✅ 日志完整:详细的日志记录,便于问题追踪
|
||||
- ✅ 错误处理:异常捕获,不影响主同步流程
|
||||
|
||||
### 注意事项
|
||||
|
||||
1. **已分配设备保护**:如果设备已被分配给租户或装车订单,即使不在API列表中也不会被删除
|
||||
2. **数据保留**:历史数据(如设备日志)不会受影响,只删除 `iot_device_data` 表中的记录
|
||||
3. **性能考虑**:每次同步都会查询所有设备,如果设备数量很大,可能需要优化查询策略
|
||||
|
||||
### 测试建议
|
||||
|
||||
1. **模拟场景**:在外部物联网系统将某个设备重新分配到其他项目
|
||||
2. **执行同步**:手动触发设备同步(调用 `/api/iotSync/sync` 接口)
|
||||
3. **验证结果**:
|
||||
- 检查数据库中该设备是否被删除
|
||||
- 查看同步日志,确认清理操作记录
|
||||
- 如果设备已被分配,应保留在数据库中
|
||||
|
||||
### 部署步骤
|
||||
|
||||
1. 备份当前代码和数据库
|
||||
2. 更新 `IotDeviceSyncService.java` 文件
|
||||
3. 重新编译并部署后端服务
|
||||
4. 测试同步功能
|
||||
5. 检查日志确保清理功能正常工作
|
||||
|
||||
## 修改文件
|
||||
|
||||
- `tradeCattle/aiotagro-cattle-trade/src/main/java/com/aiotagro/cattletrade/business/service/IotDeviceSyncService.java`
|
||||
|
||||
## 相关接口
|
||||
|
||||
- 设备同步接口:`POST /api/iotSync/sync`
|
||||
- 设备查询接口:`POST /api/iotDevice/queryList`
|
||||
|
||||
## 创建时间
|
||||
|
||||
2025-01-16
|
||||
|
||||
7
tradeCattle/add_order_id_to_delivery.sql
Normal file
7
tradeCattle/add_order_id_to_delivery.sql
Normal file
@@ -0,0 +1,7 @@
|
||||
-- 为 delivery 表添加 order_id 字段,用于关联 order 订单表
|
||||
-- 一个订单可以对应多个装车运单
|
||||
|
||||
ALTER TABLE `delivery`
|
||||
ADD COLUMN `order_id` int(11) DEFAULT NULL COMMENT '订单ID(关联order表)' AFTER `id`,
|
||||
ADD INDEX `idx_order_id` (`order_id`);
|
||||
|
||||
@@ -55,6 +55,9 @@ public class DeliveryController {
|
||||
|
||||
@Autowired
|
||||
private IXqClientService xqClientService;
|
||||
|
||||
@Autowired
|
||||
private com.aiotagro.cattletrade.business.mapper.IotDeviceDataMapper iotDeviceDataMapper;
|
||||
|
||||
|
||||
|
||||
@@ -581,4 +584,58 @@ public class DeliveryController {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除装车订单
|
||||
* 删除订单时同时清空关联设备的delivery_id和weight
|
||||
*/
|
||||
@SaCheckPermission("delivery:delete")
|
||||
@GetMapping("/deleteDelivery")
|
||||
public AjaxResult deleteDelivery(@RequestParam Integer id) {
|
||||
try {
|
||||
if (id == null) {
|
||||
return AjaxResult.error("订单ID不能为空");
|
||||
}
|
||||
|
||||
// 查询订单是否存在
|
||||
Delivery delivery = deliveryService.getById(id);
|
||||
if (delivery == null) {
|
||||
return AjaxResult.error("订单不存在");
|
||||
}
|
||||
|
||||
// 权限检查:只有创建者或超级管理员可以删除
|
||||
Integer userId = SecurityUtil.getCurrentUserId();
|
||||
if (!SecurityUtil.isSuperAdmin() && !userId.equals(delivery.getCreatedBy())) {
|
||||
return AjaxResult.error("无权限删除此订单");
|
||||
}
|
||||
|
||||
// 1. 先清空关联设备的delivery_id和weight
|
||||
com.baomidou.mybatisplus.core.conditions.query.QueryWrapper<com.aiotagro.cattletrade.business.entity.IotDeviceData> queryWrapper =
|
||||
new com.baomidou.mybatisplus.core.conditions.query.QueryWrapper<>();
|
||||
queryWrapper.eq("delivery_id", id);
|
||||
List<com.aiotagro.cattletrade.business.entity.IotDeviceData> devices = iotDeviceDataMapper.selectList(queryWrapper);
|
||||
|
||||
int updatedCount = 0;
|
||||
for (com.aiotagro.cattletrade.business.entity.IotDeviceData device : devices) {
|
||||
device.setDeliveryId(null);
|
||||
device.setWeight(null);
|
||||
iotDeviceDataMapper.updateById(device);
|
||||
updatedCount++;
|
||||
}
|
||||
|
||||
System.out.println("清空设备数量: " + updatedCount);
|
||||
|
||||
// 2. 删除订单记录
|
||||
boolean deleted = deliveryService.removeById(id);
|
||||
|
||||
if (deleted) {
|
||||
return AjaxResult.success("订单删除成功,已清空 " + updatedCount + " 个设备的绑定信息");
|
||||
} else {
|
||||
return AjaxResult.error("订单删除失败");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return AjaxResult.error("删除订单失败:" + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ import com.aiotagro.cattletrade.business.service.IDeliveryDeviceService;
|
||||
import com.aiotagro.cattletrade.business.service.IotDeviceLogSyncService;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
||||
import com.aiotagro.common.core.web.domain.AjaxResult;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
@@ -368,13 +369,22 @@ public class DeliveryDeviceController {
|
||||
return AjaxResult.error("设备不存在");
|
||||
}
|
||||
|
||||
// 更新delivery_id和weight
|
||||
device.setDeliveryId(deliveryId);
|
||||
// 使用LambdaUpdateWrapper来更新,支持null值
|
||||
LambdaUpdateWrapper<IotDeviceData> updateWrapper = new LambdaUpdateWrapper<>();
|
||||
updateWrapper.eq(IotDeviceData::getDeviceId, deviceId);
|
||||
|
||||
// 设置delivery_id(可以是null)
|
||||
updateWrapper.set(IotDeviceData::getDeliveryId, deliveryId);
|
||||
|
||||
// 设置weight(如果有值)
|
||||
if (weight != null) {
|
||||
device.setWeight(weight);
|
||||
updateWrapper.set(IotDeviceData::getWeight, weight);
|
||||
}
|
||||
|
||||
int result = iotDeviceDataMapper.updateById(device);
|
||||
// 设置更新时间
|
||||
updateWrapper.set(IotDeviceData::getUpdateTime, new Date());
|
||||
|
||||
int result = iotDeviceDataMapper.update(null, updateWrapper);
|
||||
|
||||
if (result > 0) {
|
||||
System.out.println("设备更新成功: " + deviceId);
|
||||
@@ -785,7 +795,21 @@ public class DeliveryDeviceController {
|
||||
@PostMapping(value = "/getCollarTrajectory")
|
||||
public AjaxResult getCollarTrajectory(@RequestBody Map<String, Object> params) {
|
||||
String deviceId = (String) params.get("deviceId");
|
||||
Integer deliveryId = (Integer) params.get("deliveryId");
|
||||
|
||||
// 安全地转换deliveryId为Integer,支持String和Integer两种类型
|
||||
Integer deliveryId = null;
|
||||
Object deliveryIdObj = params.get("deliveryId");
|
||||
if (deliveryIdObj != null) {
|
||||
if (deliveryIdObj instanceof Integer) {
|
||||
deliveryId = (Integer) deliveryIdObj;
|
||||
} else if (deliveryIdObj instanceof String) {
|
||||
try {
|
||||
deliveryId = Integer.parseInt((String) deliveryIdObj);
|
||||
} catch (NumberFormatException e) {
|
||||
return AjaxResult.error("运送清单ID格式错误");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (deviceId == null || deviceId.trim().isEmpty()) {
|
||||
return AjaxResult.error("设备ID不能为空");
|
||||
@@ -818,6 +842,22 @@ public class DeliveryDeviceController {
|
||||
logQuery.isNotNull("longitude");
|
||||
logQuery.ne("latitude", "0");
|
||||
logQuery.ne("longitude", "0");
|
||||
|
||||
// 添加日期范围过滤(如果前端传了日期参数)
|
||||
if (params.get("startTime") != null && params.get("endTime") != null) {
|
||||
String startTime = params.get("startTime").toString();
|
||||
String endTime = params.get("endTime").toString();
|
||||
try {
|
||||
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
||||
Date startDate = sdf.parse(startTime);
|
||||
Date endDate = sdf.parse(endTime);
|
||||
logQuery.between("update_time", startDate, endDate);
|
||||
System.out.println("日期范围: " + startTime + " 到 " + endTime);
|
||||
} catch (Exception e) {
|
||||
System.out.println("日期解析失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
logQuery.orderByAsc("update_time");
|
||||
List<XqClientLog> logs = xqClientLogMapper.selectList(logQuery);
|
||||
|
||||
|
||||
@@ -399,6 +399,62 @@ public class MemberController {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除司机
|
||||
*/
|
||||
@SaCheckPermission("member:delete")
|
||||
@PostMapping("/deleteDriver")
|
||||
@Transactional
|
||||
public AjaxResult deleteDriver(@RequestBody Map<String, Object> params) {
|
||||
try {
|
||||
Integer id = (Integer) params.get("id");
|
||||
if (id == null) {
|
||||
return AjaxResult.error("司机ID不能为空");
|
||||
}
|
||||
|
||||
// 查询司机信息,获取member_id
|
||||
Map<String, Object> driverInfo = memberDriverMapper.selectDriverById(id);
|
||||
if (driverInfo == null) {
|
||||
return AjaxResult.error("司机信息不存在");
|
||||
}
|
||||
|
||||
// member_id 从数据库返回时可能是Long类型,需要转换为Integer
|
||||
Object memberIdObj = driverInfo.get("member_id");
|
||||
Integer memberId = null;
|
||||
if (memberIdObj != null) {
|
||||
if (memberIdObj instanceof Long) {
|
||||
memberId = ((Long) memberIdObj).intValue();
|
||||
} else if (memberIdObj instanceof Integer) {
|
||||
memberId = (Integer) memberIdObj;
|
||||
}
|
||||
}
|
||||
|
||||
// 删除司机详情(member_driver表)
|
||||
int driverResult = memberDriverMapper.deleteDriver(id);
|
||||
|
||||
// 如果member_id存在,也删除member表记录
|
||||
if (memberId != null) {
|
||||
int memberResult = memberMapper.deleteById(memberId);
|
||||
if (memberResult > 0 && driverResult > 0) {
|
||||
return AjaxResult.success("删除成功");
|
||||
} else if (driverResult > 0) {
|
||||
return AjaxResult.success("删除司机信息成功");
|
||||
} else {
|
||||
return AjaxResult.error("删除失败");
|
||||
}
|
||||
} else {
|
||||
if (driverResult > 0) {
|
||||
return AjaxResult.success("删除成功");
|
||||
} else {
|
||||
return AjaxResult.error("删除失败");
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return AjaxResult.error("删除司机失败:" + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据用户类型自动创建员工记录
|
||||
*
|
||||
|
||||
@@ -0,0 +1,133 @@
|
||||
package com.aiotagro.cattletrade.business.controller;
|
||||
|
||||
import cn.dev33.satoken.annotation.SaCheckPermission;
|
||||
import com.aiotagro.cattletrade.business.entity.Order;
|
||||
import com.aiotagro.cattletrade.business.service.IOrderService;
|
||||
import com.aiotagro.common.core.web.domain.AjaxResult;
|
||||
import com.aiotagro.common.core.web.domain.PageResultResponse;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 订单管理控制器
|
||||
*
|
||||
* @author System
|
||||
* @date 2025-01-20
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/order")
|
||||
public class OrderController {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(OrderController.class);
|
||||
|
||||
@Autowired
|
||||
private IOrderService orderService;
|
||||
|
||||
/**
|
||||
* 查询订单列表(分页+搜索)
|
||||
*/
|
||||
@SaCheckPermission("order:list")
|
||||
@PostMapping("/list")
|
||||
public AjaxResult list(@RequestBody Map<String, Object> params) {
|
||||
try {
|
||||
logger.info("查询订单列表,参数:{}", params);
|
||||
PageResultResponse<Order> result = orderService.pageQuery(params);
|
||||
logger.info("查询成功,共{}条记录", result.getData().getTotal());
|
||||
return AjaxResult.success(result);
|
||||
} catch (Exception e) {
|
||||
logger.error("查询订单列表失败:{}", e.getMessage(), e);
|
||||
return AjaxResult.error("查询订单列表失败:" + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增订单
|
||||
*/
|
||||
@SaCheckPermission("order:add")
|
||||
@PostMapping("/add")
|
||||
public AjaxResult add(@RequestBody Order order) {
|
||||
try {
|
||||
logger.info("新增订单,买方:{},卖方:{},结算方式:{}", order.getBuyerId(), order.getSellerId(), order.getSettlementType());
|
||||
|
||||
// 参数验证
|
||||
if (order.getSettlementType() == null) {
|
||||
logger.error("新增失败:结算方式不能为空");
|
||||
return AjaxResult.error("结算方式不能为空");
|
||||
}
|
||||
|
||||
return orderService.addOrder(order);
|
||||
} catch (Exception e) {
|
||||
logger.error("新增订单失败:{}", e.getMessage(), e);
|
||||
return AjaxResult.error("新增订单失败:" + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 编辑订单
|
||||
*/
|
||||
@SaCheckPermission("order:edit")
|
||||
@PostMapping("/edit")
|
||||
public AjaxResult edit(@RequestBody Order order) {
|
||||
try {
|
||||
logger.info("编辑订单,订单ID:{}", order.getId());
|
||||
|
||||
// 参数验证
|
||||
if (order.getId() == null) {
|
||||
logger.error("编辑失败:订单ID不能为空");
|
||||
return AjaxResult.error("订单ID不能为空");
|
||||
}
|
||||
|
||||
return orderService.updateOrder(order);
|
||||
} catch (Exception e) {
|
||||
logger.error("编辑订单失败:{}", e.getMessage(), e);
|
||||
return AjaxResult.error("编辑订单失败:" + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除订单(逻辑删除)
|
||||
*/
|
||||
@SaCheckPermission("order:delete")
|
||||
@GetMapping("/delete")
|
||||
public AjaxResult delete(@RequestParam Integer id) {
|
||||
try {
|
||||
logger.info("删除订单,订单ID:{}", id);
|
||||
|
||||
if (id == null) {
|
||||
logger.error("删除失败:订单ID不能为空");
|
||||
return AjaxResult.error("订单ID不能为空");
|
||||
}
|
||||
|
||||
return orderService.deleteOrder(id);
|
||||
} catch (Exception e) {
|
||||
logger.error("删除订单失败:{}", e.getMessage(), e);
|
||||
return AjaxResult.error("删除订单失败:" + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询订单详情
|
||||
*/
|
||||
@SaCheckPermission("order:view")
|
||||
@GetMapping("/detail")
|
||||
public AjaxResult detail(@RequestParam Integer id) {
|
||||
try {
|
||||
logger.info("查询订单详情,订单ID:{}", id);
|
||||
|
||||
if (id == null) {
|
||||
logger.error("查询失败:订单ID不能为空");
|
||||
return AjaxResult.error("订单ID不能为空");
|
||||
}
|
||||
|
||||
return orderService.getOrderDetail(id);
|
||||
} catch (Exception e) {
|
||||
logger.error("查询订单详情失败:{}", e.getMessage(), e);
|
||||
return AjaxResult.error("查询订单详情失败:" + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,141 @@
|
||||
package com.aiotagro.cattletrade.business.controller;
|
||||
|
||||
import cn.dev33.satoken.annotation.SaCheckPermission;
|
||||
import com.aiotagro.cattletrade.business.entity.Vehicle;
|
||||
import com.aiotagro.cattletrade.business.service.IVehicleService;
|
||||
import com.aiotagro.common.core.web.domain.AjaxResult;
|
||||
import com.aiotagro.common.core.web.domain.PageResultResponse;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 车辆管理控制器
|
||||
*
|
||||
* @author System
|
||||
* @date 2025-01-20
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/vehicle")
|
||||
public class VehicleController {
|
||||
|
||||
@Autowired
|
||||
private IVehicleService vehicleService;
|
||||
|
||||
/**
|
||||
* 查询车辆列表(分页+搜索)
|
||||
*/
|
||||
@SaCheckPermission("vehicle:query")
|
||||
@PostMapping("/list")
|
||||
public AjaxResult list(@RequestBody Map<String, Object> params) {
|
||||
try {
|
||||
System.out.println("=== 查询车辆列表 ===");
|
||||
System.out.println("参数: " + params);
|
||||
|
||||
PageResultResponse<Vehicle> result = vehicleService.pageQuery(params);
|
||||
|
||||
System.out.println("查询成功");
|
||||
|
||||
return AjaxResult.success(result);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
System.out.println("查询车辆列表失败: " + e.getMessage());
|
||||
return AjaxResult.error("查询车辆列表失败:" + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增车辆
|
||||
*/
|
||||
@SaCheckPermission("vehicle:add")
|
||||
@PostMapping("/add")
|
||||
public AjaxResult add(@RequestBody Vehicle vehicle) {
|
||||
try {
|
||||
System.out.println("=== 新增车辆 ===");
|
||||
System.out.println("车牌号: " + vehicle.getLicensePlate());
|
||||
System.out.println("车头照片: " + vehicle.getCarFrontPhoto());
|
||||
System.out.println("车尾照片: " + vehicle.getCarRearPhoto());
|
||||
System.out.println("行驶证照片: " + vehicle.getDrivingLicensePhoto());
|
||||
System.out.println("牧运通备案码: " + vehicle.getRecordCode());
|
||||
System.out.println("备注: " + vehicle.getRemark());
|
||||
|
||||
// 参数验证
|
||||
if (vehicle.getLicensePlate() == null || vehicle.getLicensePlate().trim().isEmpty()) {
|
||||
System.out.println("新增失败: 车牌号不能为空");
|
||||
return AjaxResult.error("车牌号不能为空");
|
||||
}
|
||||
|
||||
AjaxResult result = vehicleService.addVehicle(vehicle);
|
||||
System.out.println("新增操作执行完成");
|
||||
return result;
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
System.out.println("新增车辆失败: " + e.getMessage());
|
||||
return AjaxResult.error("新增车辆失败:" + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 编辑车辆
|
||||
*/
|
||||
@SaCheckPermission("vehicle:edit")
|
||||
@PostMapping("/edit")
|
||||
public AjaxResult edit(@RequestBody Vehicle vehicle) {
|
||||
try {
|
||||
System.out.println("=== 编辑车辆 ===");
|
||||
System.out.println("车辆ID: " + vehicle.getId());
|
||||
System.out.println("车牌号: " + vehicle.getLicensePlate());
|
||||
System.out.println("车头照片: " + vehicle.getCarFrontPhoto());
|
||||
System.out.println("车尾照片: " + vehicle.getCarRearPhoto());
|
||||
System.out.println("行驶证照片: " + vehicle.getDrivingLicensePhoto());
|
||||
System.out.println("牧运通备案码: " + vehicle.getRecordCode());
|
||||
System.out.println("备注: " + vehicle.getRemark());
|
||||
|
||||
// 参数验证
|
||||
if (vehicle.getId() == null) {
|
||||
System.out.println("编辑失败: 车辆ID不能为空");
|
||||
return AjaxResult.error("车辆ID不能为空");
|
||||
}
|
||||
|
||||
if (vehicle.getLicensePlate() == null || vehicle.getLicensePlate().trim().isEmpty()) {
|
||||
System.out.println("编辑失败: 车牌号不能为空");
|
||||
return AjaxResult.error("车牌号不能为空");
|
||||
}
|
||||
|
||||
AjaxResult result = vehicleService.updateVehicle(vehicle);
|
||||
System.out.println("编辑操作执行完成");
|
||||
return result;
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
System.out.println("编辑车辆失败: " + e.getMessage());
|
||||
return AjaxResult.error("编辑车辆失败:" + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除车辆(逻辑删除)
|
||||
*/
|
||||
@SaCheckPermission("vehicle:delete")
|
||||
@GetMapping("/delete")
|
||||
public AjaxResult delete(@RequestParam Integer id) {
|
||||
try {
|
||||
System.out.println("=== 删除车辆 ===");
|
||||
System.out.println("车辆ID: " + id);
|
||||
|
||||
if (id == null) {
|
||||
System.out.println("删除失败: 车辆ID不能为空");
|
||||
return AjaxResult.error("车辆ID不能为空");
|
||||
}
|
||||
|
||||
AjaxResult result = vehicleService.deleteVehicle(id);
|
||||
System.out.println("删除操作执行完成");
|
||||
return result;
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
System.out.println("删除车辆失败: " + e.getMessage());
|
||||
return AjaxResult.error("删除车辆失败:" + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,6 +33,12 @@ public class Delivery implements Serializable {
|
||||
@TableId(value = "id", type = IdType.AUTO)
|
||||
private Integer id;
|
||||
|
||||
/**
|
||||
* 订单ID(关联order表)
|
||||
*/
|
||||
@TableField("order_id")
|
||||
private Integer orderId;
|
||||
|
||||
/**
|
||||
* 运单号
|
||||
*/
|
||||
@@ -415,4 +421,10 @@ public class Delivery implements Serializable {
|
||||
*/
|
||||
@TableField(exist = false)
|
||||
private String buyerMobile;
|
||||
|
||||
/**
|
||||
* 订单信息(关联查询,不存储在数据库中)
|
||||
*/
|
||||
@TableField(exist = false)
|
||||
private Order orderInfo;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
package com.aiotagro.cattletrade.business.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableLogic;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 订单管理实体类
|
||||
*
|
||||
* @author System
|
||||
* @date 2025-01-20
|
||||
*/
|
||||
@Data
|
||||
@TableName("`order`")
|
||||
public class Order implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 主键ID
|
||||
*/
|
||||
@TableId(value = "id", type = IdType.AUTO)
|
||||
private Integer id;
|
||||
|
||||
/**
|
||||
* 买方ID(多个买家用逗号分隔)
|
||||
*/
|
||||
@TableField("buyer_id")
|
||||
private String buyerId;
|
||||
|
||||
/**
|
||||
* 卖方ID(多个卖家用逗号分隔)
|
||||
*/
|
||||
@TableField("seller_id")
|
||||
private String sellerId;
|
||||
|
||||
/**
|
||||
* 结算方式:1-上车重量,2-下车重量,3-按肉价结算
|
||||
*/
|
||||
@TableField("settlement_type")
|
||||
private Integer settlementType;
|
||||
|
||||
/**
|
||||
* 逻辑删除标记(0-正常,1-已删除)
|
||||
*/
|
||||
@TableLogic(value = "0", delval = "1")
|
||||
@TableField("is_delete")
|
||||
private Integer isDelete;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
@TableField("create_time")
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
|
||||
private Date createTime;
|
||||
|
||||
/**
|
||||
* 创建人ID
|
||||
*/
|
||||
@TableField("created_by")
|
||||
private Integer createdBy;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
@TableField("update_time")
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
|
||||
private Date updateTime;
|
||||
|
||||
/**
|
||||
* 更新人ID
|
||||
*/
|
||||
@TableField("updated_by")
|
||||
private Integer updatedBy;
|
||||
|
||||
/**
|
||||
* 买方名称(不存储在数据库中,用于显示)
|
||||
*/
|
||||
@TableField(exist = false)
|
||||
private String buyerName;
|
||||
|
||||
/**
|
||||
* 卖方名称(不存储在数据库中,用于显示)
|
||||
*/
|
||||
@TableField(exist = false)
|
||||
private String sellerName;
|
||||
|
||||
/**
|
||||
* 创建人姓名(不存储在数据库中,用于显示)
|
||||
*/
|
||||
@TableField(exist = false)
|
||||
private String createdByName;
|
||||
|
||||
/**
|
||||
* 结算方式描述(不存储在数据库中,用于显示)
|
||||
*/
|
||||
@TableField(exist = false)
|
||||
private String settlementTypeDesc;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
package com.aiotagro.cattletrade.business.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableLogic;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 车辆管理实体类
|
||||
*
|
||||
* @author System
|
||||
* @date 2025-01-20
|
||||
*/
|
||||
@Data
|
||||
@TableName("vehicle")
|
||||
public class Vehicle implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 主键ID
|
||||
*/
|
||||
@TableId(value = "id", type = IdType.AUTO)
|
||||
private Integer id;
|
||||
|
||||
/**
|
||||
* 车牌号
|
||||
*/
|
||||
@TableField("license_plate")
|
||||
private String licensePlate;
|
||||
|
||||
/**
|
||||
* 车头照片URL
|
||||
*/
|
||||
@TableField("car_front_photo")
|
||||
private String carFrontPhoto;
|
||||
|
||||
/**
|
||||
* 车尾照片URL
|
||||
*/
|
||||
@TableField("car_rear_photo")
|
||||
private String carRearPhoto;
|
||||
|
||||
/**
|
||||
* 行驶证照片URL
|
||||
*/
|
||||
@TableField("driving_license_photo")
|
||||
private String drivingLicensePhoto;
|
||||
|
||||
/**
|
||||
* 牧运通备案码
|
||||
*/
|
||||
@TableField("record_code")
|
||||
private String recordCode;
|
||||
|
||||
/**
|
||||
* 备注
|
||||
*/
|
||||
@TableField("remark")
|
||||
private String remark;
|
||||
|
||||
/**
|
||||
* 逻辑删除标记(0-正常,1-已删除)
|
||||
*/
|
||||
@TableLogic(value = "0", delval = "1")
|
||||
@TableField("is_delete")
|
||||
private Integer isDelete;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
@TableField("create_time")
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
|
||||
private Date createTime;
|
||||
|
||||
/**
|
||||
* 创建人ID
|
||||
*/
|
||||
@TableField("created_by")
|
||||
private Integer createdBy;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
@TableField("update_time")
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
|
||||
private Date updateTime;
|
||||
|
||||
/**
|
||||
* 更新人ID
|
||||
*/
|
||||
@TableField("updated_by")
|
||||
private Integer updatedBy;
|
||||
}
|
||||
|
||||
@@ -134,5 +134,11 @@ public interface MemberDriverMapper extends BaseMapper<Map<String, Object>> {
|
||||
"WHERE md.username = #{driverName} AND md.car_number = #{licensePlate}")
|
||||
Map<String, Object> selectDriverByNameAndPlate(@Param("driverName") String driverName,
|
||||
@Param("licensePlate") String licensePlate);
|
||||
|
||||
/**
|
||||
* 删除司机信息
|
||||
*/
|
||||
@org.apache.ibatis.annotations.Delete("DELETE FROM member_driver WHERE id = #{id}")
|
||||
int deleteDriver(@Param("id") Integer id);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.aiotagro.cattletrade.business.mapper;
|
||||
|
||||
import com.aiotagro.cattletrade.business.entity.Order;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
/**
|
||||
* 订单管理Mapper接口
|
||||
*
|
||||
* @author System
|
||||
* @date 2025-01-20
|
||||
*/
|
||||
@Mapper
|
||||
public interface OrderMapper extends BaseMapper<Order> {
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
package com.aiotagro.cattletrade.business.mapper;
|
||||
|
||||
import com.aiotagro.cattletrade.business.entity.Vehicle;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.apache.ibatis.annotations.Select;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 车辆管理 Mapper 接口
|
||||
*
|
||||
* @author System
|
||||
* @date 2025-01-20
|
||||
*/
|
||||
@Mapper
|
||||
public interface VehicleMapper extends BaseMapper<Vehicle> {
|
||||
|
||||
/**
|
||||
* 根据车牌号查询车辆(唯一性校验)
|
||||
* @param licensePlate 车牌号
|
||||
* @return 车辆信息
|
||||
*/
|
||||
@Select("SELECT * FROM vehicle WHERE license_plate = #{licensePlate} AND is_delete = 0")
|
||||
Vehicle selectByLicensePlate(@Param("licensePlate") String licensePlate);
|
||||
|
||||
/**
|
||||
* 根据车牌号模糊查询车辆列表
|
||||
* @param licensePlate 车牌号(可为空)
|
||||
* @return 车辆列表
|
||||
*/
|
||||
@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);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,391 @@
|
||||
package com.aiotagro.cattletrade.business.service;
|
||||
|
||||
import com.aiotagro.cattletrade.business.entity.*;
|
||||
import com.aiotagro.cattletrade.business.mapper.*;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* 设备预警检测服务
|
||||
* 在设备日志同步时进行预警检测
|
||||
*/
|
||||
@Service
|
||||
public class DeviceWarningService {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(DeviceWarningService.class);
|
||||
|
||||
@Autowired
|
||||
private DeliveryMapper deliveryMapper;
|
||||
|
||||
@Autowired
|
||||
private WarningLogMapper warningLogMapper;
|
||||
|
||||
@Autowired
|
||||
private XqClientLogMapper xqClientLogMapper;
|
||||
|
||||
@Autowired
|
||||
private IotDeviceDataMapper iotDeviceDataMapper;
|
||||
|
||||
/**
|
||||
* 检测设备预警
|
||||
* 每5分钟执行一次
|
||||
*/
|
||||
public void checkDeviceWarnings() {
|
||||
try {
|
||||
logger.info("开始执行设备预警检测任务");
|
||||
|
||||
// 查询所有未完成的运单 (status < 5)
|
||||
List<Delivery> activeDeliveries = deliveryMapper.selectList(
|
||||
new QueryWrapper<Delivery>()
|
||||
.lt("status", 5)
|
||||
);
|
||||
|
||||
if (activeDeliveries.isEmpty()) {
|
||||
logger.info("没有待检测的运单");
|
||||
return;
|
||||
}
|
||||
|
||||
logger.info("找到 {} 个待检测运单", activeDeliveries.size());
|
||||
|
||||
for (Delivery delivery : activeDeliveries) {
|
||||
try {
|
||||
// 获取运单关联的设备
|
||||
List<String> deviceIds = getDeliveryDevices(delivery.getId());
|
||||
|
||||
// 如果运单没有绑定设备,跳过预警检测
|
||||
if (deviceIds.isEmpty()) {
|
||||
logger.debug("运单 {} 未绑定设备,跳过预警检测", delivery.getDeliveryNumber());
|
||||
continue;
|
||||
}
|
||||
|
||||
logger.debug("运单 {} 有 {} 个设备,开始预警检测", delivery.getDeliveryNumber(), deviceIds.size());
|
||||
|
||||
// 检测停留预警
|
||||
checkStagnationWarning(delivery, deviceIds);
|
||||
|
||||
// 检测温度预警
|
||||
checkTemperatureWarning(delivery, deviceIds);
|
||||
|
||||
// 检测距离偏离预警
|
||||
checkDistanceDeviation(delivery, deviceIds);
|
||||
|
||||
// 检测到达时间预警
|
||||
checkArrivalTimeWarning(delivery, deviceIds);
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("检测运单 {} 预警失败", delivery.getDeliveryNumber(), e);
|
||||
}
|
||||
}
|
||||
|
||||
logger.info("设备预警检测任务执行完成");
|
||||
} catch (Exception e) {
|
||||
logger.error("执行设备预警检测任务失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检测停留预警 - 15分钟位置不动
|
||||
*/
|
||||
private void checkStagnationWarning(Delivery delivery, List<String> deviceIds) {
|
||||
try {
|
||||
for (String deviceId : deviceIds) {
|
||||
// 查询最近15分钟的日志记录(每5分钟一条,共3条)
|
||||
List<XqClientLog> recentLogs = xqClientLogMapper.selectList(
|
||||
new QueryWrapper<XqClientLog>()
|
||||
.eq("device_id", deviceId)
|
||||
.orderByDesc("update_time")
|
||||
.last("LIMIT 3")
|
||||
);
|
||||
|
||||
if (recentLogs.size() >= 3) {
|
||||
// 检查最近3条记录的位置是否相同
|
||||
String firstLat = recentLogs.get(0).getLatitude();
|
||||
String firstLon = recentLogs.get(0).getLongitude();
|
||||
|
||||
boolean isStagnant = true;
|
||||
for (int i = 1; i < recentLogs.size(); i++) {
|
||||
String currentLat = recentLogs.get(i).getLatitude();
|
||||
String currentLon = recentLogs.get(i).getLongitude();
|
||||
|
||||
if (currentLat == null || currentLon == null ||
|
||||
firstLat == null || firstLon == null ||
|
||||
!firstLat.equals(currentLat) || !firstLon.equals(currentLon)) {
|
||||
isStagnant = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (isStagnant) {
|
||||
// 保存预警记录
|
||||
saveWarningLog(delivery.getId(), "4", "设备停留预警:15分钟内位置未发生变化");
|
||||
logger.info("运单 {} 设备 {} 触发停留预警", delivery.getDeliveryNumber(), deviceId);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("检测停留预警失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检测温度预警
|
||||
*/
|
||||
private void checkTemperatureWarning(Delivery delivery, List<String> deviceIds) {
|
||||
try {
|
||||
for (String deviceId : deviceIds) {
|
||||
// 查询最新的设备日志
|
||||
XqClientLog latestLog = xqClientLogMapper.selectOne(
|
||||
new QueryWrapper<XqClientLog>()
|
||||
.eq("device_id", deviceId)
|
||||
.orderByDesc("update_time")
|
||||
.last("LIMIT 1")
|
||||
);
|
||||
|
||||
if (latestLog != null && latestLog.getDeviceTemp() != null) {
|
||||
try {
|
||||
double temperature = Double.parseDouble(latestLog.getDeviceTemp());
|
||||
|
||||
if (temperature > 40) {
|
||||
saveWarningLog(delivery.getId(), "5", "高温预警:设备温度 " + temperature + "°C,超过40°C");
|
||||
logger.info("运单 {} 设备 {} 触发高温预警", delivery.getDeliveryNumber(), deviceId);
|
||||
} else if (temperature < 30) {
|
||||
saveWarningLog(delivery.getId(), "6", "低温预警:设备温度 " + temperature + "°C,低于30°C");
|
||||
logger.info("运单 {} 设备 {} 触发低温预警", delivery.getDeliveryNumber(), deviceId);
|
||||
}
|
||||
} catch (NumberFormatException e) {
|
||||
logger.warn("设备温度格式错误: {}", latestLog.getDeviceTemp());
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("检测温度预警失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检测距离偏离预警 - 当前位置偏离路线的十分之一
|
||||
*/
|
||||
private void checkDistanceDeviation(Delivery delivery, List<String> deviceIds) {
|
||||
try {
|
||||
if (delivery.getStartLat() == null || delivery.getStartLon() == null ||
|
||||
delivery.getEndLat() == null || delivery.getEndLon() == null) {
|
||||
logger.debug("运单 {} 的起终点经纬度不完整,跳过距离检测", delivery.getDeliveryNumber());
|
||||
return;
|
||||
}
|
||||
|
||||
// 计算起始地和目的地的距离
|
||||
double totalDistance = calculateDistance(
|
||||
Double.parseDouble(delivery.getStartLat()),
|
||||
Double.parseDouble(delivery.getStartLon()),
|
||||
Double.parseDouble(delivery.getEndLat()),
|
||||
Double.parseDouble(delivery.getEndLon())
|
||||
);
|
||||
|
||||
double deviationThreshold = totalDistance / 10; // 十分之一的距离
|
||||
|
||||
for (String deviceId : deviceIds) {
|
||||
// 查询最新的设备日志
|
||||
XqClientLog latestLog = xqClientLogMapper.selectOne(
|
||||
new QueryWrapper<XqClientLog>()
|
||||
.eq("device_id", deviceId)
|
||||
.orderByDesc("update_time")
|
||||
.last("LIMIT 1")
|
||||
);
|
||||
|
||||
if (latestLog != null && latestLog.getLatitude() != null && latestLog.getLongitude() != null) {
|
||||
try {
|
||||
double currentLat = Double.parseDouble(latestLog.getLatitude());
|
||||
double currentLon = Double.parseDouble(latestLog.getLongitude());
|
||||
|
||||
// 计算当前位置到起点的距离
|
||||
double distanceFromStart = calculateDistance(
|
||||
Double.parseDouble(delivery.getStartLat()),
|
||||
Double.parseDouble(delivery.getStartLon()),
|
||||
currentLat,
|
||||
currentLon
|
||||
);
|
||||
|
||||
// 计算当前位置到终点的距离
|
||||
double distanceFromEnd = calculateDistance(
|
||||
currentLat,
|
||||
currentLon,
|
||||
Double.parseDouble(delivery.getEndLat()),
|
||||
Double.parseDouble(delivery.getEndLon())
|
||||
);
|
||||
|
||||
// 计算实际路线距离(当前位置到起点的距离 + 当前位置到终点的距离)
|
||||
double actualRouteDistance = distanceFromStart + distanceFromEnd;
|
||||
|
||||
// 如果实际路线距离超出总距离 + 偏离阈值,触发预警
|
||||
if (actualRouteDistance > totalDistance + deviationThreshold) {
|
||||
saveWarningLog(delivery.getId(), "7",
|
||||
String.format("位置偏离预警:当前位置偏离计划路线超过 %.2f 公里", deviationThreshold));
|
||||
logger.info("运单 {} 设备 {} 触发位置偏离预警", delivery.getDeliveryNumber(), deviceId);
|
||||
}
|
||||
} catch (NumberFormatException e) {
|
||||
logger.warn("经纬度格式错误");
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("检测距离偏离预警失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检测到达时间预警
|
||||
*/
|
||||
private void checkArrivalTimeWarning(Delivery delivery, List<String> deviceIds) {
|
||||
try {
|
||||
if (delivery.getEstimatedDeliveryTime() == null) {
|
||||
logger.debug("运单 {} 没有预计送达时间,跳过到达时间检测", delivery.getDeliveryNumber());
|
||||
return;
|
||||
}
|
||||
|
||||
Date estimatedTime = delivery.getEstimatedDeliveryTime();
|
||||
Date currentTime = new Date();
|
||||
|
||||
// 计算剩余时间(小时)
|
||||
long diffInMillis = estimatedTime.getTime() - currentTime.getTime();
|
||||
double remainingHours = diffInMillis / (1000.0 * 60 * 60);
|
||||
|
||||
if (deviceIds.isEmpty()) {
|
||||
logger.debug("运单 {} 没有设备,跳过到达时间检测", delivery.getDeliveryNumber());
|
||||
return;
|
||||
}
|
||||
|
||||
// 查询最新位置
|
||||
XqClientLog latestLog = xqClientLogMapper.selectOne(
|
||||
new QueryWrapper<XqClientLog>()
|
||||
.in("device_id", deviceIds)
|
||||
.orderByDesc("update_time")
|
||||
.last("LIMIT 1")
|
||||
);
|
||||
|
||||
if (latestLog != null && latestLog.getLatitude() != null && latestLog.getLongitude() != null) {
|
||||
try {
|
||||
double currentLat = Double.parseDouble(latestLog.getLatitude());
|
||||
double currentLon = Double.parseDouble(latestLog.getLongitude());
|
||||
|
||||
if (delivery.getEndLat() == null || delivery.getEndLon() == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
double endLat = Double.parseDouble(delivery.getEndLat());
|
||||
double endLon = Double.parseDouble(delivery.getEndLon());
|
||||
|
||||
// 计算剩余距离(公里)
|
||||
double remainingDistance = calculateDistance(currentLat, currentLon, endLat, endLon);
|
||||
|
||||
// 假设平均速度80公里/小时
|
||||
double avgSpeed = 80;
|
||||
double requiredTime = remainingDistance / avgSpeed;
|
||||
|
||||
// 判断是否能在预计时间内到达
|
||||
if (remainingHours > 0 && requiredTime > remainingHours) {
|
||||
// 延误预警
|
||||
saveWarningLog(delivery.getId(), "8",
|
||||
String.format("延误预警:预计无法在约定时间到达,预计延误 %.2f 小时", requiredTime - remainingHours));
|
||||
logger.info("运单 {} 触发延误预警", delivery.getDeliveryNumber());
|
||||
} else if (remainingHours < 0 && Math.abs(remainingHours) > 2) {
|
||||
// 超前到达预警(超过2小时)
|
||||
saveWarningLog(delivery.getId(), "9",
|
||||
String.format("超前到达预警:已提前 %.2f 小时到达目的地", Math.abs(remainingHours)));
|
||||
logger.info("运单 {} 触发超前到达预警", delivery.getDeliveryNumber());
|
||||
}
|
||||
} catch (NumberFormatException e) {
|
||||
logger.warn("经纬度格式错误");
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("检测到达时间预警失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取运单关联的设备ID列表(从iot_device_data表查询)
|
||||
*/
|
||||
private List<String> getDeliveryDevices(Integer deliveryId) {
|
||||
try {
|
||||
List<IotDeviceData> devices = iotDeviceDataMapper.selectList(
|
||||
new QueryWrapper<IotDeviceData>()
|
||||
.eq("delivery_id", deliveryId)
|
||||
);
|
||||
|
||||
List<String> deviceIds = new ArrayList<>();
|
||||
for (IotDeviceData device : devices) {
|
||||
if (device.getDeviceId() != null && !device.getDeviceId().isEmpty()) {
|
||||
deviceIds.add(device.getDeviceId());
|
||||
}
|
||||
}
|
||||
|
||||
return deviceIds;
|
||||
} catch (Exception e) {
|
||||
logger.error("查询运单{}的设备失败", deliveryId, e);
|
||||
return new ArrayList<>();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存预警记录
|
||||
*/
|
||||
private void saveWarningLog(Integer deliveryId, String warningType, String warningDesc) {
|
||||
WarningLog warningLog = new WarningLog();
|
||||
warningLog.setDeliveryId(deliveryId);
|
||||
warningLog.setWarningType(warningType);
|
||||
warningLog.setWarningTime(new Date());
|
||||
|
||||
// 验证是否已有相同类型的最新预警(5分钟内不重复保存)
|
||||
WarningLog latestWarning = warningLogMapper.selectOne(
|
||||
new QueryWrapper<WarningLog>()
|
||||
.eq("delivery_id", deliveryId)
|
||||
.eq("warning_type", warningType)
|
||||
.orderByDesc("warning_time")
|
||||
.last("LIMIT 1")
|
||||
);
|
||||
|
||||
if (latestWarning != null) {
|
||||
long diffMinutes = (new Date().getTime() - latestWarning.getWarningTime().getTime()) / (1000 * 60);
|
||||
if (diffMinutes < 5) {
|
||||
logger.debug("5分钟内已有相同预警,跳过保存");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
warningLogMapper.insert(warningLog);
|
||||
logger.info("保存预警记录:运单ID={}, 预警类型={}", deliveryId, warningType);
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算两点间距离(Haversine公式)
|
||||
*/
|
||||
private double calculateDistance(double lat1, double lon1, double lat2, double lon2) {
|
||||
final int R = 6371; // 地球半径(公里)
|
||||
|
||||
double lat1Rad = Math.toRadians(lat1);
|
||||
double lat2Rad = Math.toRadians(lat2);
|
||||
double deltaLat = Math.toRadians(lat2 - lat1);
|
||||
double deltaLon = Math.toRadians(lon2 - lon1);
|
||||
|
||||
double a = Math.sin(deltaLat / 2) * Math.sin(deltaLat / 2) +
|
||||
Math.cos(lat1Rad) * Math.cos(lat2Rad) *
|
||||
Math.sin(deltaLon / 2) * Math.sin(deltaLon / 2);
|
||||
|
||||
double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
|
||||
|
||||
return R * c;
|
||||
}
|
||||
|
||||
/**
|
||||
* 输出统计信息
|
||||
*/
|
||||
public void logStatistics() {
|
||||
logger.info("设备预警检测统计信息已更新");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
package com.aiotagro.cattletrade.business.service;
|
||||
|
||||
import com.aiotagro.cattletrade.business.entity.Order;
|
||||
import com.aiotagro.common.core.web.domain.AjaxResult;
|
||||
import com.aiotagro.common.core.web.domain.PageResultResponse;
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 订单管理服务接口
|
||||
*
|
||||
* @author System
|
||||
* @date 2025-01-20
|
||||
*/
|
||||
public interface IOrderService extends IService<Order> {
|
||||
|
||||
/**
|
||||
* 分页查询订单列表
|
||||
*
|
||||
* @param params 查询参数
|
||||
* @return 分页结果
|
||||
*/
|
||||
PageResultResponse<Order> pageQuery(Map<String, Object> params);
|
||||
|
||||
/**
|
||||
* 新增订单
|
||||
*
|
||||
* @param order 订单信息
|
||||
* @return AjaxResult
|
||||
*/
|
||||
AjaxResult addOrder(Order order);
|
||||
|
||||
/**
|
||||
* 更新订单信息
|
||||
*
|
||||
* @param order 订单信息
|
||||
* @return AjaxResult
|
||||
*/
|
||||
AjaxResult updateOrder(Order order);
|
||||
|
||||
/**
|
||||
* 删除订单(逻辑删除)
|
||||
*
|
||||
* @param id 订单ID
|
||||
* @return AjaxResult
|
||||
*/
|
||||
AjaxResult deleteOrder(Integer id);
|
||||
|
||||
/**
|
||||
* 查询订单详情
|
||||
*
|
||||
* @param id 订单ID
|
||||
* @return AjaxResult
|
||||
*/
|
||||
AjaxResult getOrderDetail(Integer id);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
package com.aiotagro.cattletrade.business.service;
|
||||
|
||||
import com.aiotagro.cattletrade.business.entity.Vehicle;
|
||||
import com.aiotagro.common.core.web.domain.AjaxResult;
|
||||
import com.aiotagro.common.core.web.domain.PageResultResponse;
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 车辆管理服务接口
|
||||
*
|
||||
* @author System
|
||||
* @date 2025-01-20
|
||||
*/
|
||||
public interface IVehicleService extends IService<Vehicle> {
|
||||
|
||||
/**
|
||||
* 分页查询车辆列表
|
||||
*
|
||||
* @param params 查询参数
|
||||
* @return 分页结果
|
||||
*/
|
||||
PageResultResponse<Vehicle> pageQuery(Map<String, Object> params);
|
||||
|
||||
/**
|
||||
* 新增车辆
|
||||
*
|
||||
* @param vehicle 车辆信息
|
||||
* @return AjaxResult
|
||||
*/
|
||||
AjaxResult addVehicle(Vehicle vehicle);
|
||||
|
||||
/**
|
||||
* 更新车辆信息
|
||||
*
|
||||
* @param vehicle 车辆信息
|
||||
* @return AjaxResult
|
||||
*/
|
||||
AjaxResult updateVehicle(Vehicle vehicle);
|
||||
|
||||
/**
|
||||
* 删除车辆(逻辑删除)
|
||||
*
|
||||
* @param id 车辆ID
|
||||
* @return AjaxResult
|
||||
*/
|
||||
AjaxResult deleteVehicle(Integer id);
|
||||
}
|
||||
|
||||
@@ -16,6 +16,9 @@ import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.*;
|
||||
import java.util.Set;
|
||||
import java.util.HashSet;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
|
||||
/**
|
||||
* IoT设备数据同步服务
|
||||
@@ -53,7 +56,15 @@ public class IotDeviceSyncService {
|
||||
// 调用外部API获取数据
|
||||
List<Map<String, Object>> deviceDataList = fetchDeviceDataFromApi();
|
||||
|
||||
if (deviceDataList.isEmpty()) {
|
||||
// 提取API返回的设备ID集合
|
||||
Set<String> apiDeviceIds = new HashSet<>();
|
||||
for (Map<String, Object> deviceData : deviceDataList) {
|
||||
if (deviceData.get("deviceId") != null) {
|
||||
apiDeviceIds.add(String.valueOf(deviceData.get("deviceId")));
|
||||
}
|
||||
}
|
||||
|
||||
if (apiDeviceIds.isEmpty()) {
|
||||
logger.warn("未获取到设备数据");
|
||||
syncLog.setSyncStatus("SUCCESS");
|
||||
syncLog.setTotalCount(0);
|
||||
@@ -74,7 +85,7 @@ public class IotDeviceSyncService {
|
||||
|
||||
// 检查设备是否已存在
|
||||
IotDeviceData existingDevice = iotDeviceDataMapper.selectOne(
|
||||
new com.baomidou.mybatisplus.core.conditions.query.QueryWrapper<IotDeviceData>()
|
||||
new QueryWrapper<IotDeviceData>()
|
||||
.eq("device_id", iotDevice.getDeviceId())
|
||||
);
|
||||
|
||||
@@ -97,6 +108,13 @@ public class IotDeviceSyncService {
|
||||
}
|
||||
}
|
||||
|
||||
// 清理已被重新分配到其他项目的设备
|
||||
try {
|
||||
cleanupReassignedDevices(apiDeviceIds);
|
||||
} catch (Exception e) {
|
||||
logger.error("清理重新分配设备失败", e);
|
||||
}
|
||||
|
||||
syncLog.setSyncStatus(failedCount > 0 ? "FAILED" : "SUCCESS");
|
||||
syncLog.setSuccessCount(successCount);
|
||||
syncLog.setFailedCount(failedCount);
|
||||
@@ -342,4 +360,38 @@ public class IotDeviceSyncService {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理已被重新分配到其他项目的设备
|
||||
* 只删除未被分配给租户或装车订单的设备
|
||||
*/
|
||||
private void cleanupReassignedDevices(Set<String> apiDeviceIds) {
|
||||
try {
|
||||
logger.info("开始清理已重新分配的设备");
|
||||
|
||||
// 查询数据库中所有设备
|
||||
List<IotDeviceData> allDevices = iotDeviceDataMapper.selectList(null);
|
||||
|
||||
int deletedCount = 0;
|
||||
for (IotDeviceData device : allDevices) {
|
||||
// 如果设备不在API返回列表中
|
||||
if (!apiDeviceIds.contains(device.getDeviceId())) {
|
||||
// 只删除未被分配的设备(tenantId和deliveryId都为null)
|
||||
if (device.getTenantId() == null && device.getDeliveryId() == null) {
|
||||
iotDeviceDataMapper.deleteById(device.getId());
|
||||
logger.info("删除已重新分配的设备: deviceId={}, id={}", device.getDeviceId(), device.getId());
|
||||
deletedCount++;
|
||||
} else {
|
||||
logger.warn("设备不在API返回列表中但已被分配,保留: deviceId={}, tenantId={}, deliveryId={}",
|
||||
device.getDeviceId(), device.getTenantId(), device.getDeliveryId());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
logger.info("清理完成,共删除 {} 个已重新分配的设备", deletedCount);
|
||||
} catch (Exception e) {
|
||||
logger.error("清理已重新分配设备时发生错误", e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,6 +64,8 @@ public class DeliveryServiceImpl extends ServiceImpl<DeliveryMapper, Delivery> i
|
||||
private IDeliveryDeviceService deliveryDeviceService;
|
||||
@Autowired
|
||||
private IXqClientService xqClientService;
|
||||
@Autowired
|
||||
private com.aiotagro.cattletrade.business.mapper.OrderMapper orderMapper;
|
||||
|
||||
|
||||
/**
|
||||
@@ -1183,6 +1185,16 @@ public class DeliveryServiceImpl extends ServiceImpl<DeliveryMapper, Delivery> i
|
||||
}
|
||||
}
|
||||
|
||||
// 填充装车订单的关联订单信息
|
||||
for适合 (Delivery delivery : resList) {
|
||||
if (delivery.getOrderId() != null) {
|
||||
com.aiotagro.cattletrade.business.entity.Order order = orderMapper.selectById(delivery.getOrderId());
|
||||
if (order != null) {
|
||||
delivery.setOrderInfo(order);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 更新分页信息
|
||||
long filteredTotal = resList.size();
|
||||
return new PageResultResponse(filteredTotal, resList);
|
||||
|
||||
@@ -0,0 +1,293 @@
|
||||
package com.aiotagro.cattletrade.business.service.impl;
|
||||
|
||||
import com.aiotagro.cattletrade.business.entity.Order;
|
||||
import com.aiotagro.cattletrade.business.entity.Member;
|
||||
import com.aiotagro.cattletrade.business.entity.SysUser;
|
||||
import com.aiotagro.cattletrade.business.mapper.OrderMapper;
|
||||
import com.aiotagro.cattletrade.business.mapper.MemberMapper;
|
||||
import com.aiotagro.cattletrade.business.mapper.SysUserMapper;
|
||||
import com.aiotagro.cattletrade.business.service.IOrderService;
|
||||
import com.aiotagro.common.core.utils.SecurityUtil;
|
||||
import com.aiotagro.common.core.web.domain.AjaxResult;
|
||||
import com.aiotagro.common.core.web.domain.PageResultResponse;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.github.pagehelper.Page;
|
||||
import com.github.pagehelper.PageHelper;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 订单管理服务实现类
|
||||
*
|
||||
* @author System
|
||||
* @date 2025-01-20
|
||||
*/
|
||||
@Service
|
||||
public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements IOrderService {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(OrderServiceImpl.class);
|
||||
|
||||
@Autowired
|
||||
private OrderMapper orderMapper;
|
||||
|
||||
@Autowired
|
||||
private MemberMapper memberMapper;
|
||||
|
||||
@Autowired
|
||||
private SysUserMapper sysUserMapper;
|
||||
|
||||
/**
|
||||
* 分页查询订单列表
|
||||
*/
|
||||
@Override
|
||||
public PageResultResponse<Order> pageQuery(Map<String, Object> params) {
|
||||
Integer pageNum = params.get("pageNum") != null ? (Integer) params.get("pageNum") : 1;
|
||||
Integer pageSize = params.get("pageSize") != null ? (Integer) params.get("pageSize") : 10;
|
||||
Integer settlementType = params.get("settlementType") != null ? (Integer) params.get("settlementType") : null;
|
||||
|
||||
logger.info("分页查询订单列表,页码:{},每页数量:{}", pageNum, pageSize);
|
||||
|
||||
// 使用PageHelper进行分页
|
||||
Page<Order> page = PageHelper.startPage(pageNum, pageSize);
|
||||
|
||||
// 构建查询条件
|
||||
LambdaQueryWrapper<Order> queryWrapper = new LambdaQueryWrapper<>();
|
||||
queryWrapper.eq(settlementType != null, Order::getSettlementType, settlementType);
|
||||
queryWrapper.orderByDesc(Order::getCreateTime);
|
||||
|
||||
// 执行查询
|
||||
List<Order> list = orderMapper.selectList(queryWrapper);
|
||||
|
||||
// 填充关联信息
|
||||
list.forEach(this::fillOrderInfo);
|
||||
|
||||
// 构建分页结果
|
||||
logger.info("查询到{}条订单记录", list.size());
|
||||
return new PageResultResponse<>(page.getTotal(), list);
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增订单
|
||||
*/
|
||||
@Override
|
||||
@Transactional
|
||||
public AjaxResult addOrder(Order order) {
|
||||
logger.info("开始新增订单,买方:{},卖方:{},结算方式:{}", order.getBuyerId(), order.getSellerId(), order.getSettlementType());
|
||||
|
||||
try {
|
||||
// 验证结算方式
|
||||
if (order.getSettlementType() == null || order.getSettlementType() < 1 || order.getSettlementType() > 3) {
|
||||
logger.error("结算方式无效:{}", order.getSettlementType());
|
||||
return AjaxResult.error("结算方式无效");
|
||||
}
|
||||
|
||||
// 设置创建人和创建时间
|
||||
Integer userId = SecurityUtil.getCurrentUserId();
|
||||
order.setCreatedBy(userId);
|
||||
order.setCreateTime(new Date());
|
||||
|
||||
// 插入数据库
|
||||
int result = orderMapper.insert(order);
|
||||
|
||||
if (result > 0) {
|
||||
logger.info("新增订单成功,订单ID:{}", order.getId());
|
||||
return AjaxResult.success("新增订单成功");
|
||||
} else {
|
||||
logger.error("新增订单失败");
|
||||
return AjaxResult.error("新增订单失败");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("新增订单异常:{}", e.getMessage(), e);
|
||||
return AjaxResult.error("新增订单失败:" + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新订单信息
|
||||
*/
|
||||
@Override
|
||||
@Transactional
|
||||
public AjaxResult updateOrder(Order order) {
|
||||
if (order.getId() == null) {
|
||||
logger.error("订单ID不能为空");
|
||||
return AjaxResult.error("订单ID不能为空");
|
||||
}
|
||||
|
||||
logger.info("开始更新订单,订单ID:{}", order.getId());
|
||||
|
||||
try {
|
||||
// 验证订单是否存在
|
||||
Order existingOrder = orderMapper.selectById(order.getId());
|
||||
if (existingOrder == null) {
|
||||
logger.error("订单不存在,ID:{}", order.getId());
|
||||
return AjaxResult.error("订单不存在");
|
||||
}
|
||||
|
||||
// 验证结算方式
|
||||
if (order.getSettlementType() != null && (order.getSettlementType() < 1 || order.getSettlementType() > 3)) {
|
||||
logger.error("结算方式无效:{}", order.getSettlementType());
|
||||
return AjaxResult.error("结算方式无效");
|
||||
}
|
||||
|
||||
// 设置更新人和更新时间
|
||||
Integer userId = SecurityUtil.getCurrentUserId();
|
||||
order.setUpdatedBy(userId);
|
||||
order.setUpdateTime(new Date());
|
||||
|
||||
// 更新数据库
|
||||
int result = orderMapper.updateById(order);
|
||||
|
||||
if (result > 0) {
|
||||
logger.info("更新订单成功,订单ID:{}", order.getId());
|
||||
return AjaxResult.success("更新订单成功");
|
||||
} else {
|
||||
logger.error("更新订单失败");
|
||||
return AjaxResult.error("更新订单失败");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("更新订单异常:{}", e.getMessage(), e);
|
||||
return AjaxResult.error("更新订单失败:" + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除订单(逻辑删除)
|
||||
*/
|
||||
@Override
|
||||
@Transactional
|
||||
public AjaxResult deleteOrder(Integer id) {
|
||||
if (id == null) {
|
||||
logger.error("订单ID不能为空");
|
||||
return AjaxResult.error("订单ID不能为空");
|
||||
}
|
||||
|
||||
logger.info("开始删除订单,订单ID:{}", id);
|
||||
|
||||
try {
|
||||
// 查询订单是否存在
|
||||
Order order = orderMapper.selectById(id);
|
||||
if (order == null) {
|
||||
logger.error("订单不存在,ID:{}", id);
|
||||
return AjaxResult.error("订单不存在");
|
||||
}
|
||||
|
||||
// 逻辑删除(通过update方法设置is_delete=1)
|
||||
// MyBatis-Plus的@TableLogic会自动处理
|
||||
int result = orderMapper.deleteById(id);
|
||||
|
||||
if (result > 0) {
|
||||
logger.info("删除订单成功,订单ID:{}", id);
|
||||
return AjaxResult.success("删除订单成功");
|
||||
} else {
|
||||
logger.error("删除订单失败");
|
||||
return AjaxResult.error("删除订单失败");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("删除订单异常:{}", e.getMessage(), e);
|
||||
return AjaxResult.error("删除订单失败:" + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询订单详情
|
||||
*/
|
||||
@Override
|
||||
public AjaxResult getOrderDetail(Integer id) {
|
||||
if (id == null) {
|
||||
return AjaxResult.error("订单ID不能为空");
|
||||
}
|
||||
|
||||
logger.info("查询订单详情,订单ID:{}", id);
|
||||
|
||||
Order order = orderMapper.selectById(id);
|
||||
if (order == null) {
|
||||
logger.error("订单不存在,ID:{}", id);
|
||||
return AjaxResult.error("订单不存在");
|
||||
}
|
||||
|
||||
// 填充关联信息
|
||||
fillOrderInfo(order);
|
||||
|
||||
return AjaxResult.success(order);
|
||||
}
|
||||
|
||||
/**
|
||||
* 填充订单关联信息(买方名称、卖方名称、创建人名称、结算方式描述)
|
||||
*/
|
||||
private void fillOrderInfo(Order order) {
|
||||
// 填充买方名称
|
||||
if (order.getBuyerId() != null && !order.getBuyerId().isEmpty()) {
|
||||
String buyerNames = Arrays.stream(order.getBuyerId().split(","))
|
||||
.map(buyerId -> {
|
||||
try {
|
||||
Map<String, Object> memberUser = memberMapper.selectMemberUserById(Integer.parseInt(buyerId.trim()));
|
||||
if (memberUser != null && memberUser.get("username") != null) {
|
||||
return (String) memberUser.get("username");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.warn("查询买方信息失败,买方ID:{}", buyerId);
|
||||
}
|
||||
return "";
|
||||
})
|
||||
.filter(name -> !name.isEmpty())
|
||||
.collect(Collectors.joining(", "));
|
||||
order.setBuyerName(buyerNames);
|
||||
}
|
||||
|
||||
// 填充卖方名称
|
||||
if (order.getSellerId() != null && !order.getSellerId().isEmpty()) {
|
||||
String sellerNames = Arrays.stream(order.getSellerId().split(","))
|
||||
.map(sellerId -> {
|
||||
try {
|
||||
Map<String, Object> memberUser = memberMapper.selectMemberUserById(Integer.parseInt(sellerId.trim()));
|
||||
if (memberUser != null && memberUser.get("username") != null) {
|
||||
return (String) memberUser.get("username");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.warn("查询卖方信息失败,卖方ID:{}", sellerId);
|
||||
}
|
||||
return "";
|
||||
})
|
||||
.filter(name -> !name.isEmpty())
|
||||
.collect(Collectors.joining(", "));
|
||||
order.setSellerName(sellerNames);
|
||||
}
|
||||
|
||||
// 填充创建人名称
|
||||
if (order.getCreatedBy() != null) {
|
||||
SysUser user = sysUserMapper.selectById(order.getCreatedBy());
|
||||
if (user != null) {
|
||||
order.setCreatedByName(user.getName());
|
||||
}
|
||||
}
|
||||
|
||||
// 填充结算方式描述
|
||||
if (order.getSettlementType() != null) {
|
||||
switch (order.getSettlementType()) {
|
||||
case 1:
|
||||
order.setSettlementTypeDesc("上车重量");
|
||||
break;
|
||||
case 2:
|
||||
order.setSettlementTypeDesc("下车重量");
|
||||
break;
|
||||
case 3:
|
||||
order.setSettlementTypeDesc("按肉价结算");
|
||||
break;
|
||||
default:
|
||||
order.setSettlementTypeDesc("未知");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,142 @@
|
||||
package com.aiotagro.cattletrade.business.service.impl;
|
||||
|
||||
import com.aiotagro.cattletrade.business.entity.Vehicle;
|
||||
import com.aiotagro.cattletrade.business.mapper.VehicleMapper;
|
||||
import com.aiotagro.cattletrade.business.service.IVehicleService;
|
||||
import com.aiotagro.common.core.utils.SecurityUtil;
|
||||
import com.aiotagro.common.core.web.domain.AjaxResult;
|
||||
import com.aiotagro.common.core.web.domain.PageResultResponse;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.github.pagehelper.Page;
|
||||
import com.github.pagehelper.PageHelper;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 车辆管理服务实现类
|
||||
*
|
||||
* @author System
|
||||
* @date 2025-01-20
|
||||
*/
|
||||
@Service
|
||||
public class VehicleServiceImpl extends ServiceImpl<VehicleMapper, Vehicle> implements IVehicleService {
|
||||
|
||||
@Autowired
|
||||
private VehicleMapper vehicleMapper;
|
||||
|
||||
/**
|
||||
* 分页查询车辆列表
|
||||
*/
|
||||
@Override
|
||||
public PageResultResponse<Vehicle> pageQuery(Map<String, Object> params) {
|
||||
Integer pageNum = params.get("pageNum") != null ? (Integer) params.get("pageNum") : 1;
|
||||
Integer pageSize = params.get("pageSize") != null ? (Integer) params.get("pageSize") : 10;
|
||||
String licensePlate = (String) params.get("licensePlate");
|
||||
|
||||
// 使用PageHelper进行分页
|
||||
Page<Vehicle> page = PageHelper.startPage(pageNum, pageSize);
|
||||
|
||||
// 构建查询条件
|
||||
LambdaQueryWrapper<Vehicle> queryWrapper = new LambdaQueryWrapper<>();
|
||||
queryWrapper.like(licensePlate != null && !licensePlate.isEmpty(), Vehicle::getLicensePlate, licensePlate);
|
||||
queryWrapper.orderByDesc(Vehicle::getCreateTime);
|
||||
|
||||
// 执行查询
|
||||
List<Vehicle> list = vehicleMapper.selectList(queryWrapper);
|
||||
|
||||
// 构建分页结果(使用构造函数)
|
||||
return new PageResultResponse<>(page.getTotal(), list);
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增车辆
|
||||
*/
|
||||
@Override
|
||||
@Transactional
|
||||
public AjaxResult addVehicle(Vehicle vehicle) {
|
||||
// 验证车牌号是否已存在
|
||||
Vehicle existingVehicle = vehicleMapper.selectByLicensePlate(vehicle.getLicensePlate());
|
||||
if (existingVehicle != null) {
|
||||
return AjaxResult.error("车牌号已存在");
|
||||
}
|
||||
|
||||
// 设置创建人和创建时间
|
||||
Integer userId = SecurityUtil.getCurrentUserId();
|
||||
vehicle.setCreatedBy(userId);
|
||||
vehicle.setCreateTime(new Date());
|
||||
|
||||
// 插入数据库
|
||||
int result = vehicleMapper.insert(vehicle);
|
||||
|
||||
if (result > 0) {
|
||||
return AjaxResult.success("新增车辆成功");
|
||||
} else {
|
||||
return AjaxResult.error("新增车辆失败");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新车辆信息
|
||||
*/
|
||||
@Override
|
||||
@Transactional
|
||||
public AjaxResult updateVehicle(Vehicle vehicle) {
|
||||
if (vehicle.getId() == null) {
|
||||
return AjaxResult.error("车辆ID不能为空");
|
||||
}
|
||||
|
||||
// 验证车牌号唯一性(排除当前记录)
|
||||
Vehicle existingVehicle = vehicleMapper.selectByLicensePlate(vehicle.getLicensePlate());
|
||||
if (existingVehicle != null && !existingVehicle.getId().equals(vehicle.getId())) {
|
||||
return AjaxResult.error("车牌号已存在");
|
||||
}
|
||||
|
||||
// 设置更新人和更新时间
|
||||
Integer userId = SecurityUtil.getCurrentUserId();
|
||||
vehicle.setUpdatedBy(userId);
|
||||
vehicle.setUpdateTime(new Date());
|
||||
|
||||
// 更新数据库
|
||||
int result = vehicleMapper.updateById(vehicle);
|
||||
|
||||
if (result > 0) {
|
||||
return AjaxResult.success("更新车辆成功");
|
||||
} else {
|
||||
return AjaxResult.error("更新车辆失败");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除车辆(逻辑删除)
|
||||
*/
|
||||
@Override
|
||||
@Transactional
|
||||
public AjaxResult deleteVehicle(Integer id) {
|
||||
if (id == null) {
|
||||
return AjaxResult.error("车辆ID不能为空");
|
||||
}
|
||||
|
||||
// 查询车辆是否存在
|
||||
Vehicle vehicle = vehicleMapper.selectById(id);
|
||||
if (vehicle == null) {
|
||||
return AjaxResult.error("车辆不存在");
|
||||
}
|
||||
|
||||
// 逻辑删除
|
||||
vehicle.setIsDelete(1);
|
||||
int result = vehicleMapper.updateById(vehicle);
|
||||
|
||||
if (result > 0) {
|
||||
return AjaxResult.success("删除车辆成功");
|
||||
} else {
|
||||
return AjaxResult.error("删除车辆失败");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,12 +5,18 @@ import lombok.Getter;
|
||||
@Getter
|
||||
public enum WarningStatusAdminEnum {
|
||||
/**
|
||||
* 牲畜类型
|
||||
* 预警类型枚举
|
||||
*/
|
||||
CATTLE(0, "盘点正常"),
|
||||
SHEEP(1, "距离正常"),
|
||||
PIG(2, "数量盘点预警"),
|
||||
DEER(3, "运输距离预警");
|
||||
NORMAL_COUNT(0, "盘点正常"),
|
||||
NORMAL_DISTANCE(1, "距离正常"),
|
||||
COUNT_WARNING(2, "数量盘点预警"),
|
||||
DISTANCE_WARNING(3, "运输距离预警"),
|
||||
STAGNATION_WARNING(4, "设备停留预警"),
|
||||
HIGH_TEMP_WARNING(5, "高温预警"),
|
||||
LOW_TEMP_WARNING(6, "低温预警"),
|
||||
POSITION_DEVIATION_WARNING(7, "位置偏离预警"),
|
||||
DELAY_WARNING(8, "延误预警"),
|
||||
EARLY_ARRIVAL_WARNING(9, "超前到达预警");
|
||||
|
||||
private final int value ;
|
||||
private final String description ;
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.aiotagro.cattletrade.job;
|
||||
|
||||
import com.aiotagro.cattletrade.business.service.IotDeviceSyncService;
|
||||
import com.aiotagro.cattletrade.business.service.IotDeviceLogSyncService;
|
||||
import com.aiotagro.cattletrade.business.service.DeviceWarningService;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
@@ -24,6 +25,9 @@ public class IotDeviceSyncJob {
|
||||
|
||||
@Autowired
|
||||
private IotDeviceLogSyncService iotDeviceLogSyncService;
|
||||
|
||||
@Autowired
|
||||
private DeviceWarningService deviceWarningService;
|
||||
|
||||
/**
|
||||
* 每5分钟同步一次IoT设备数据
|
||||
@@ -40,9 +44,9 @@ public class IotDeviceSyncJob {
|
||||
}
|
||||
|
||||
/**
|
||||
* 每60分钟同步一次设备数据到日志表
|
||||
* 每5分钟同步一次设备数据到日志表
|
||||
*/
|
||||
@Scheduled(fixedRate = 60 * 60 * 1000) // 60分钟
|
||||
@Scheduled(fixedRate = 5 * 60 * 1000) // 5分钟
|
||||
public void syncDeviceDataToLogs() {
|
||||
try {
|
||||
logger.info("开始执行设备日志同步定时任务");
|
||||
@@ -51,6 +55,12 @@ public class IotDeviceSyncJob {
|
||||
|
||||
// 输出统计信息
|
||||
iotDeviceLogSyncService.logSyncStatistics();
|
||||
|
||||
// 执行预警检测
|
||||
logger.info("开始执行设备预警检测任务");
|
||||
deviceWarningService.checkDeviceWarnings();
|
||||
logger.info("设备预警检测任务执行完成");
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("设备日志同步定时任务执行失败", e);
|
||||
}
|
||||
|
||||
@@ -64,10 +64,11 @@
|
||||
wl.warning_time,
|
||||
wl.inventory_jbq_count,
|
||||
su.name as createByName
|
||||
FROM delivery d left join warning_log wl on d.id = wl.delivery_id
|
||||
FROM delivery d inner join warning_log wl on d.id = wl.delivery_id
|
||||
left join sys_user su on d.created_by = su.id
|
||||
<where>
|
||||
wl.warning_type in (2,3)
|
||||
wl.warning_type in (2,3,4,5,6,7,8,9)
|
||||
and wl.id in (select max(id) from warning_log where delivery_id = d.id group by warning_type)
|
||||
<if test="dto.deliveryNumber != null and '' != dto.deliveryNumber">
|
||||
and d.delivery_number like concat('%', #{dto.deliveryNumber}, '%')
|
||||
</if>
|
||||
|
||||
20
tradeCattle/create_order_table.sql
Normal file
20
tradeCattle/create_order_table.sql
Normal file
@@ -0,0 +1,20 @@
|
||||
-- 创建订单管理表
|
||||
-- 用于管理订单信息,包括买方、卖方、结算方式等
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `order` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
||||
`buyer_id` varchar(500) DEFAULT NULL COMMENT '买方ID(多个买家用逗号分隔)',
|
||||
`seller_id` varchar(500) DEFAULT NULL COMMENT '卖方ID(多个卖家用逗号分隔)',
|
||||
`settlement_type` int(11) NOT NULL COMMENT '结算方式:1-上车重量,2-下车重量,3-按肉价结算',
|
||||
`is_delete` tinyint(1) DEFAULT 0 COMMENT '逻辑删除标记(0-正常,1-已删除)',
|
||||
`create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
`created_by` int(11) DEFAULT NULL COMMENT '创建人ID',
|
||||
`update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
`updated_by` int(11) DEFAULT NULL COMMENT '更新人ID',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_is_delete` (`is_delete`),
|
||||
KEY `idx_created_by` (`created_by`),
|
||||
KEY `idx_create_time` (`create_time`),
|
||||
KEY `idx_settlement_type` (`settlement_type`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订单管理表';
|
||||
|
||||
23
tradeCattle/create_vehicle_table.sql
Normal file
23
tradeCattle/create_vehicle_table.sql
Normal file
@@ -0,0 +1,23 @@
|
||||
-- 创建车辆管理表
|
||||
-- 用于管理车辆的详细信息,包括车牌号、照片、行驶证和牧运通备案码等
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `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 '车头照片URL',
|
||||
`car_rear_photo` varchar(500) DEFAULT NULL COMMENT '车尾照片URL',
|
||||
`driving_license_photo` varchar(500) DEFAULT NULL COMMENT '行驶证照片URL',
|
||||
`record_code` varchar(100) DEFAULT NULL COMMENT '牧运通备案码',
|
||||
`remark` varchar(500) DEFAULT NULL COMMENT '备注',
|
||||
`is_delete` tinyint(1) DEFAULT 0 COMMENT '逻辑删除(0-正常,1-已删除)',
|
||||
`create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
`created_by` int(11) DEFAULT NULL COMMENT '创建人ID',
|
||||
`update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
`updated_by` int(11) DEFAULT NULL COMMENT '更新人ID',
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uk_license_plate` (`license_plate`),
|
||||
KEY `idx_is_delete` (`is_delete`),
|
||||
KEY `idx_created_by` (`created_by`),
|
||||
KEY `idx_create_time` (`create_time`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='车辆管理表';
|
||||
|
||||
45
tradeCattle/grant_all_permissions_to_super_admin.sql
Normal file
45
tradeCattle/grant_all_permissions_to_super_admin.sql
Normal file
@@ -0,0 +1,45 @@
|
||||
-- 为超级管理员(roleId=1)分配所有菜单权限
|
||||
-- 这将解决操作权限管理界面中超级管理员权限未全部打开的问题
|
||||
|
||||
-- 1. 查询当前超级管理员已有的权限
|
||||
SELECT rm.role_id, r.name as role_name, rm.menu_id, m.name as menu_name, m.authority
|
||||
FROM sys_role_menu rm
|
||||
LEFT JOIN sys_role r ON rm.role_id = r.id
|
||||
LEFT JOIN sys_menu m ON rm.menu_id = m.id
|
||||
WHERE rm.role_id = 1 AND m.is_delete = 0
|
||||
ORDER BY m.sort;
|
||||
|
||||
-- 2. 为超级管理员角色分配所有菜单权限
|
||||
-- 使用 INSERT ... ON DUPLICATE KEY UPDATE 避免重复插入
|
||||
INSERT INTO sys_role_menu (role_id, menu_id)
|
||||
SELECT 1, id FROM sys_menu WHERE is_delete = 0
|
||||
ON DUPLICATE KEY UPDATE role_id = role_id;
|
||||
|
||||
-- 3. 验证分配结果
|
||||
SELECT
|
||||
rm.role_id,
|
||||
r.name as role_name,
|
||||
COUNT(rm.menu_id) as menu_count
|
||||
FROM sys_role_menu rm
|
||||
LEFT JOIN sys_role r ON rm.role_id = r.id
|
||||
WHERE rm.role_id = 1
|
||||
GROUP BY rm.role_id, r.name;
|
||||
|
||||
-- 4. 查询总菜单数
|
||||
SELECT COUNT(*) as total_menus FROM sys_menu WHERE is_delete = 0;
|
||||
|
||||
-- 5. 查询超级管理员所有权限详情
|
||||
SELECT
|
||||
rm.role_id,
|
||||
r.name as role_name,
|
||||
m.id as menu_id,
|
||||
m.name as menu_name,
|
||||
m.type,
|
||||
m.authority,
|
||||
m.page_url
|
||||
FROM sys_role_menu rm
|
||||
LEFT JOIN sys_role r ON rm.role_id = r.id
|
||||
LEFT JOIN sys_menu m ON rm.menu_id = m.id
|
||||
WHERE rm.role_id = 1 AND m.is_delete = 0
|
||||
ORDER BY m.parent_id, m.sort;
|
||||
|
||||
18
tradeCattle/grant_super_admin_to_user.sql
Normal file
18
tradeCattle/grant_super_admin_to_user.sql
Normal file
@@ -0,0 +1,18 @@
|
||||
-- 授予手机号15900000000的用户超级管理员权限
|
||||
-- 修改sys_user表中的role_id为1(超级管理员角色ID)
|
||||
|
||||
UPDATE sys_user
|
||||
SET role_id = 1
|
||||
WHERE mobile = '15900000000' AND is_delete = 0;
|
||||
|
||||
-- 查询修改结果
|
||||
SELECT id, name, mobile, role_id, status, create_time
|
||||
FROM sys_user
|
||||
WHERE mobile = '15900000000' AND is_delete = 0;
|
||||
|
||||
-- 验证角色是否正确
|
||||
SELECT u.id, u.name, u.mobile, u.role_id, r.name as role_name, r.description
|
||||
FROM sys_user u
|
||||
LEFT JOIN sys_role r ON u.role_id = r.id
|
||||
WHERE u.mobile = '15900000000' AND u.is_delete = 0;
|
||||
|
||||
99
tradeCattle/insert_vehicle_menu_permissions.sql
Normal file
99
tradeCattle/insert_vehicle_menu_permissions.sql
Normal file
@@ -0,0 +1,99 @@
|
||||
-- 车辆管理模块权限配置SQL
|
||||
-- 在sys_menu表中添加车辆管理的菜单权限
|
||||
|
||||
-- 1. 插入车辆管理主菜单
|
||||
INSERT INTO `sys_menu` (
|
||||
`name`,
|
||||
`parent_id`,
|
||||
`sort`,
|
||||
`route_url`,
|
||||
`page_url`,
|
||||
`type`,
|
||||
`authority`,
|
||||
`create_time`,
|
||||
`update_time`
|
||||
) VALUES (
|
||||
'车辆管理',
|
||||
(SELECT id FROM (SELECT id FROM sys_menu WHERE name = '用户管理' AND parent_id = 0 LIMIT 1) AS t),
|
||||
3,
|
||||
'/vehicle',
|
||||
'/userManage/vehicle',
|
||||
1,
|
||||
'vehicle:query',
|
||||
NOW(),
|
||||
NOW()
|
||||
) ON DUPLICATE KEY UPDATE
|
||||
`name` = VALUES(`name`),
|
||||
`page_url` = VALUES(`page_url`);
|
||||
|
||||
-- 2. 获取刚插入的车辆管理菜单ID
|
||||
SET @vehicle_menu_id = LAST_INSERT_ID();
|
||||
SELECT @vehicle_menu_id := id FROM sys_menu WHERE page_url = '/userManage/vehicle' LIMIT 1;
|
||||
|
||||
-- 3. 插入新增车辆权限按钮
|
||||
INSERT INTO `sys_menu` (
|
||||
`name`,
|
||||
`parent_id`,
|
||||
`sort`,
|
||||
`type`,
|
||||
`authority`,
|
||||
`create_time`,
|
||||
`update_time`
|
||||
) VALUES (
|
||||
'新增车辆',
|
||||
@vehicle_menu_id,
|
||||
1,
|
||||
2,
|
||||
'vehicle:add',
|
||||
NOW(),
|
||||
NOW()
|
||||
) ON DUPLICATE KEY UPDATE
|
||||
`name` = VALUES(`name`),
|
||||
`authority` = VALUES(`authority`);
|
||||
|
||||
-- 4. 插入编辑车辆权限按钮
|
||||
INSERT INTO `sys_menu` (
|
||||
`name`,
|
||||
`parent_id`,
|
||||
`sort`,
|
||||
`type`,
|
||||
`authority`,
|
||||
`create_time`,
|
||||
`update_time`
|
||||
) VALUES (
|
||||
'编辑车辆',
|
||||
@vehicle_menu_id,
|
||||
2,
|
||||
2,
|
||||
'vehicle:edit',
|
||||
NOW(),
|
||||
NOW()
|
||||
) ON DUPLICATE KEY UPDATE
|
||||
`name` = VALUES(`name`),
|
||||
`authority` = VALUES(`authority`);
|
||||
|
||||
-- 5. 插入删除车辆权限按钮
|
||||
INSERT INTO `sys_menu` (
|
||||
`name`,
|
||||
`parent_id`,
|
||||
`sort`,
|
||||
`type`,
|
||||
`authority`,
|
||||
`create_time`,
|
||||
`update_time`
|
||||
) VALUES (
|
||||
'删除车辆',
|
||||
@vehicle_menu_id,
|
||||
3,
|
||||
2,
|
||||
'vehicle:delete',
|
||||
NOW(),
|
||||
NOW()
|
||||
) ON DUPLICATE KEY UPDATE
|
||||
`name` = VALUES(`name`),
|
||||
`authority` = VALUES(`authority`);
|
||||
|
||||
-- 6. 将车辆管理相关权限分配给超级管理员角色(role_id = 1)
|
||||
INSERT INTO `sys_role_menu` (`role_id`, `menu_id`)
|
||||
SELECT 1, `id` FROM `sys_menu` WHERE `authority` IN ('vehicle:query', 'vehicle:add', 'vehicle:edit', 'vehicle:delete')
|
||||
ON DUPLICATE KEY UPDATE `role_id` = `role_id`;
|
||||
Reference in New Issue
Block a user