Răsfoiți Sursa

提交全图监控

chenxiaofei 6 luni în urmă
părinte
comite
7c9e2246f0
14 a modificat fișierele cu 854 adăugiri și 11 ștergeri
  1. 17 4
      sckw-modules/sckw-transport/src/main/java/com/sckw/transport/controller/KwfTruckTraceController.java
  2. 14 0
      sckw-modules/sckw-transport/src/main/java/com/sckw/transport/dao/KwtVehicleExceptionMapper.java
  3. 168 0
      sckw-modules/sckw-transport/src/main/java/com/sckw/transport/model/KwtVehicleException.java
  4. 4 3
      sckw-modules/sckw-transport/src/main/java/com/sckw/transport/model/dto/MapVehicleQueryReq.java
  5. 32 0
      sckw-modules/sckw-transport/src/main/java/com/sckw/transport/model/dto/VehicleExceptionQueryReq.java
  6. 65 0
      sckw-modules/sckw-transport/src/main/java/com/sckw/transport/model/enuma/ExceptionSortTypeEnum.java
  7. 53 0
      sckw-modules/sckw-transport/src/main/java/com/sckw/transport/model/enuma/MapVehicleSortTypeEnum.java
  8. 58 0
      sckw-modules/sckw-transport/src/main/java/com/sckw/transport/model/enuma/VehicleExceptionTypeEnum.java
  9. 11 0
      sckw-modules/sckw-transport/src/main/java/com/sckw/transport/model/vo/MapVehicleVo.java
  10. 56 0
      sckw-modules/sckw-transport/src/main/java/com/sckw/transport/model/vo/VehicleExceptionVo.java
  11. 71 0
      sckw-modules/sckw-transport/src/main/java/com/sckw/transport/repository/KwtVehicleExceptionRepository.java
  12. 165 0
      sckw-modules/sckw-transport/src/main/java/com/sckw/transport/service/VehicleExceptionService.java
  13. 105 4
      sckw-modules/sckw-transport/src/main/java/com/sckw/transport/service/kwfTruckTraceService.java
  14. 35 0
      sql/2025/12/01/2025_12_01_vehicle_exception.sql

+ 17 - 4
sckw-modules/sckw-transport/src/main/java/com/sckw/transport/controller/KwfTruckTraceController.java

@@ -12,7 +12,10 @@ import com.sckw.transport.model.vo.TruckSelectVo;
 import com.sckw.transport.model.dto.TruckSelectReq;
 import com.sckw.transport.model.dto.MapVehicleQueryReq;
 import com.sckw.transport.model.vo.MapVehicleVo;
+import com.sckw.transport.model.dto.VehicleExceptionQueryReq;
+import com.sckw.transport.model.vo.VehicleExceptionVo;
 import com.sckw.transport.service.kwfTruckTraceService;
+import com.sckw.transport.service.VehicleExceptionService;
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.tags.Tag;
 import jakarta.validation.Valid;
@@ -32,6 +35,7 @@ import java.util.List;
 @Tag(name = "车辆轨迹相关接口")
 public class KwfTruckTraceController {
     private final kwfTruckTraceService kwfTruckTraceService;
+    private final VehicleExceptionService vehicleExceptionService;
 
     /**
      * 获取车辆实时位置信息
@@ -80,8 +84,7 @@ public class KwfTruckTraceController {
     @PostMapping("/truckSelect")
     @Operation(summary = "车辆下拉列表", description = "支持根据车牌号输入匹配,返回车牌号和定位状态")
     public BaseResult<List<TruckSelectVo>> getTruckSelectList(@RequestBody TruckSelectReq req) {
-        List<TruckSelectVo> list = kwfTruckTraceService.getTruckSelectList(req);
-        return BaseResult.success(list);
+        return BaseResult.success(kwfTruckTraceService.getTruckSelectList(req));
     }
 
     /**
@@ -90,8 +93,18 @@ public class KwfTruckTraceController {
     @PostMapping("/mapVehicleList")
     @Operation(summary = "地图车辆列表", description = "分页查询地图中展示的进行中任务车辆信息")
     public BaseResult<PageDataResult<MapVehicleVo>> queryMapVehicleList(@RequestBody MapVehicleQueryReq req) {
-        PageDataResult<MapVehicleVo> result = kwfTruckTraceService.queryMapVehicleList(req);
-        return BaseResult.success(result);
+        return BaseResult.success(kwfTruckTraceService.queryMapVehicleList(req));
     }
 
+    /**
+     * 分页查询车辆异常图片信息
+     */
+    @PostMapping("/exceptionList")
+    @Operation(summary = "车辆异常列表", description = "根据运输企业ID、异常类型、车牌号、定位状态分页查询车辆异常图片信息")
+    public BaseResult<PageDataResult<VehicleExceptionVo>> queryExceptionList(@RequestBody VehicleExceptionQueryReq req) {
+        return BaseResult.success(vehicleExceptionService.queryExceptionList(req));
+    }
+
+
+
 }

+ 14 - 0
sckw-modules/sckw-transport/src/main/java/com/sckw/transport/dao/KwtVehicleExceptionMapper.java

@@ -0,0 +1,14 @@
+package com.sckw.transport.dao;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.sckw.transport.model.KwtVehicleException;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * @author cxf
+ * @desc 车辆异常图片Mapper
+ * @date 2025-12-01
+ */
+@Mapper
+public interface KwtVehicleExceptionMapper extends BaseMapper<KwtVehicleException> {
+}

+ 168 - 0
sckw-modules/sckw-transport/src/main/java/com/sckw/transport/model/KwtVehicleException.java

@@ -0,0 +1,168 @@
+package com.sckw.transport.model;
+
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableLogic;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * @author cxf
+ * @desc 车辆异常图片表
+ * @date 2025-12-01
+ */
+@Data
+@TableName("kwt_vehicle_exception")
+public class KwtVehicleException implements Serializable {
+    
+    @Serial
+    private static final long serialVersionUID = 1L;
+    
+    /**
+     * 主键
+     */
+    @TableField("id")
+    private Long id;
+    
+    /**
+     * 企业ID
+     */
+    @TableField("ent_id")
+    private Long entId;
+    
+    /**
+     * 车辆ID
+     */
+    @TableField("truck_id")
+    private Long truckId;
+    
+    /**
+     * 车牌号
+     */
+    @TableField("truck_no")
+    private String truckNo;
+    
+    /**
+     * 运单号
+     */
+    @TableField("w_order_no")
+    private String wOrderNo;
+    
+    /**
+     * 司机ID
+     */
+    @TableField("driver_id")
+    private Long driverId;
+    
+    /**
+     * 司机姓名
+     */
+    @TableField("driver_name")
+    private String driverName;
+    
+    /**
+     * 司机电话
+     */
+    @TableField("driver_phone")
+    private String driverPhone;
+    
+    /**
+     * 异常类型(1-车辆偏航,2-急刹车,3-超速,4-异常停车)
+     */
+    @TableField("exception_type")
+    private Integer exceptionType;
+    
+    /**
+     * 图片URL
+     */
+    @TableField("image_url")
+    private String imageUrl;
+    
+    /**
+     * 异常时间
+     */
+    @TableField("exception_time")
+    private Date exceptionTime;
+    
+    /**
+     * 任务开始时间
+     */
+    @TableField("task_start_time")
+    private Date taskStartTime;
+    
+    /**
+     * 任务结束时间
+     */
+    @TableField("task_end_time")
+    private Date taskEndTime;
+    
+    /**
+     * 任务耗时(分钟)
+     */
+    @TableField("task_duration")
+    private Integer taskDuration;
+    
+    /**
+     * 异常数量
+     */
+    @TableField("exception_count")
+    private Integer exceptionCount;
+    
+    /**
+     * 经度
+     */
+    @TableField("longitude")
+    private String longitude;
+    
+    /**
+     * 纬度
+     */
+    @TableField("latitude")
+    private String latitude;
+    
+    /**
+     * 位置描述
+     */
+    @TableField("location")
+    private String location;
+    
+    /**
+     * 异常详情描述
+     */
+    @TableField("description")
+    private String description;
+    
+    /**
+     * 创建人
+     */
+    @TableField("create_by")
+    private Long createBy;
+    
+    /**
+     * 创建时间
+     */
+    @TableField("create_time")
+    private Date createTime;
+    
+    /**
+     * 更新人
+     */
+    @TableField("update_by")
+    private Long updateBy;
+    
+    /**
+     * 更新时间
+     */
+    @TableField("update_time")
+    private Date updateTime;
+    
+    /**
+     * 是否删除(0未删除,1删除)
+     */
+    @TableLogic
+    @TableField("del_flag")
+    private Integer delFlag;
+}

+ 4 - 3
sckw-modules/sckw-transport/src/main/java/com/sckw/transport/model/dto/MapVehicleQueryReq.java

@@ -1,9 +1,7 @@
 package com.sckw.transport.model.dto;
 
-import com.sckw.core.web.request.PageReq;
 import io.swagger.v3.oas.annotations.media.Schema;
-import lombok.Data;
-import lombok.EqualsAndHashCode;
+import lombok.Data;;
 
 import java.io.Serial;
 import java.io.Serializable;
@@ -33,6 +31,9 @@ public class MapVehicleQueryReq  implements Serializable {
     @Schema(description = "任务状态")
     private Integer status;
 
+    @Schema(description = "排序类型(1-按时间排序,2-耗时排序,3-异常排序)", example = "1")
+    private Integer sortType = 1;
+
     @Schema(description = "开始日期(yyyy-MM-dd)默认当天")
     private String startDate;
 

+ 32 - 0
sckw-modules/sckw-transport/src/main/java/com/sckw/transport/model/dto/VehicleExceptionQueryReq.java

@@ -0,0 +1,32 @@
+package com.sckw.transport.model.dto;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+/**
+ * @author cxf
+ * @desc 车辆异常查询请求
+ * @date 2025-12-01
+ */
+@Data
+@Schema(description = "车辆异常查询请求")
+public class VehicleExceptionQueryReq {
+    
+    @Schema(description = "运输企业ID", requiredMode = Schema.RequiredMode.REQUIRED)
+    private Long entId;
+    
+    @Schema(description = "异常类型(1-车辆偏航,2-急刹车,3-超速,4-异常停车)")
+    private Integer exceptionType;
+    
+    @Schema(description = "车牌号")
+    private String truckNo;
+    
+    @Schema(description = "定位状态(1-在线,0-离线)")
+    private Integer locationStatus;
+    
+    @Schema(description = "页码", example = "1")
+    private Integer pageNum = 1;
+    
+    @Schema(description = "每页数量", example = "20")
+    private Integer pageSize = 20;
+}

+ 65 - 0
sckw-modules/sckw-transport/src/main/java/com/sckw/transport/model/enuma/ExceptionSortTypeEnum.java

@@ -0,0 +1,65 @@
+package com.sckw.transport.model.enuma;
+
+import lombok.Getter;
+
+/**
+ * @author cxf
+ * @desc 异常排序类型枚举
+ * @date 2025-12-01
+ */
+@Getter
+public enum ExceptionSortTypeEnum {
+    
+    /**
+     * 按时间排序(任务开始时间倒序)
+     */
+    TIME_DESC(1, "按时间排序", "exception_time DESC"),
+    
+    /**
+     * 耗时排序(按任务完成耗时倒序)
+     */
+    DURATION_DESC(2, "耗时排序", "task_duration DESC"),
+    
+    /**
+     * 异常排序(任务过程产生的异常数倒序)
+     */
+    EXCEPTION_COUNT_DESC(3, "异常排序", "exception_count DESC");
+    
+    private final Integer code;
+    private final String name;
+    private final String orderBySql;
+    
+    ExceptionSortTypeEnum(Integer code, String name, String orderBySql) {
+        this.code = code;
+        this.name = name;
+        this.orderBySql = orderBySql;
+    }
+    
+    public static String getName(Integer code) {
+        for (ExceptionSortTypeEnum entity : ExceptionSortTypeEnum.values()) {
+            if (entity.getCode().equals(code)) {
+                return entity.getName();
+            }
+        }
+        return null;
+    }
+    
+    public static String getOrderBySql(Integer code) {
+        for (ExceptionSortTypeEnum entity : ExceptionSortTypeEnum.values()) {
+            if (entity.getCode().equals(code)) {
+                return entity.getOrderBySql();
+            }
+        }
+        // 默认按时间倒序
+        return TIME_DESC.getOrderBySql();
+    }
+    
+    public static ExceptionSortTypeEnum getByCode(Integer code) {
+        for (ExceptionSortTypeEnum entity : ExceptionSortTypeEnum.values()) {
+            if (entity.getCode().equals(code)) {
+                return entity;
+            }
+        }
+        return TIME_DESC;
+    }
+}

+ 53 - 0
sckw-modules/sckw-transport/src/main/java/com/sckw/transport/model/enuma/MapVehicleSortTypeEnum.java

@@ -0,0 +1,53 @@
+package com.sckw.transport.model.enuma;
+
+import lombok.Getter;
+
+/**
+ * @author cxf
+ * @desc 地图车辆排序类型枚举
+ * @date 2025-12-01
+ */
+@Getter
+public enum MapVehicleSortTypeEnum {
+    
+    /**
+     * 按时间排序(任务开始时间倒序)
+     */
+    TIME_DESC(1, "按时间排序"),
+    
+    /**
+     * 耗时排序(按任务完成耗时倒序)
+     */
+    DURATION_DESC(2, "耗时排序"),
+    
+    /**
+     * 异常排序(任务过程产生的异常数倒序)
+     */
+    EXCEPTION_COUNT_DESC(3, "异常排序");
+    
+    private final Integer code;
+    private final String name;
+    
+    MapVehicleSortTypeEnum(Integer code, String name) {
+        this.code = code;
+        this.name = name;
+    }
+    
+    public static String getName(Integer code) {
+        for (MapVehicleSortTypeEnum entity : MapVehicleSortTypeEnum.values()) {
+            if (entity.getCode().equals(code)) {
+                return entity.getName();
+            }
+        }
+        return null;
+    }
+    
+    public static MapVehicleSortTypeEnum getByCode(Integer code) {
+        for (MapVehicleSortTypeEnum entity : MapVehicleSortTypeEnum.values()) {
+            if (entity.getCode().equals(code)) {
+                return entity;
+            }
+        }
+        return TIME_DESC;
+    }
+}

+ 58 - 0
sckw-modules/sckw-transport/src/main/java/com/sckw/transport/model/enuma/VehicleExceptionTypeEnum.java

@@ -0,0 +1,58 @@
+package com.sckw.transport.model.enuma;
+
+import lombok.Getter;
+
+/**
+ * @author AI Assistant
+ * @desc 车辆异常类型枚举
+ * @date 2025-12-01
+ */
+@Getter
+public enum VehicleExceptionTypeEnum {
+    
+    /**
+     * 车辆偏航
+     */
+    DEVIATION(1, "车辆偏航"),
+    
+    /**
+     * 急刹车
+     */
+    SUDDEN_BRAKE(2, "急刹车"),
+    
+    /**
+     * 超速
+     */
+    OVERSPEED(3, "超速"),
+    
+    /**
+     * 异常停车
+     */
+    ABNORMAL_PARKING(4, "异常停车");
+    
+    private final Integer code;
+    private final String name;
+    
+    VehicleExceptionTypeEnum(Integer code, String name) {
+        this.code = code;
+        this.name = name;
+    }
+    
+    public static String getName(Integer code) {
+        for (VehicleExceptionTypeEnum entity : VehicleExceptionTypeEnum.values()) {
+            if (entity.getCode().equals(code)) {
+                return entity.getName();
+            }
+        }
+        return null;
+    }
+    
+    public static VehicleExceptionTypeEnum getByCode(Integer code) {
+        for (VehicleExceptionTypeEnum entity : VehicleExceptionTypeEnum.values()) {
+            if (entity.getCode().equals(code)) {
+                return entity;
+            }
+        }
+        return null;
+    }
+}

+ 11 - 0
sckw-modules/sckw-transport/src/main/java/com/sckw/transport/model/vo/MapVehicleVo.java

@@ -3,6 +3,7 @@ package com.sckw.transport.model.vo;
 import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.Data;
 
+import java.io.Serial;
 import java.io.Serializable;
 import java.math.BigDecimal;
 
@@ -14,6 +15,7 @@ import java.math.BigDecimal;
 @Schema(description = "地图车辆展示信息")
 public class MapVehicleVo implements Serializable {
 
+    @Serial
     private static final long serialVersionUID = 1L;
 
     @Schema(description = "商品信息")
@@ -78,4 +80,13 @@ public class MapVehicleVo implements Serializable {
 
     @Schema(description = "单位")
     private String unit;
+
+    @Schema(description = "任务开始时间(yyyy-MM-dd HH:mm:ss)")
+    private String taskStartTime;
+
+    @Schema(description = "任务耗时(分钟)")
+    private Long taskDuration;
+
+    @Schema(description = "异常数量")
+    private Integer exceptionCount;
 }

+ 56 - 0
sckw-modules/sckw-transport/src/main/java/com/sckw/transport/model/vo/VehicleExceptionVo.java

@@ -0,0 +1,56 @@
+package com.sckw.transport.model.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+/**
+ * @author cxf
+ * @desc 车辆异常图片信息VO
+ * @date 2025-12-01
+ */
+@Data
+@Schema(description = "车辆异常图片信息")
+public class VehicleExceptionVo {
+    
+    @Schema(description = "图片ID")
+    private Long id;
+    
+    @Schema(description = "车牌号")
+    private String truckNo;
+    
+    @Schema(description = "异常类型代码(1-车辆偏航,2-急刹车,3-超速,4-异常停车)")
+    private Integer exceptionType;
+    
+    @Schema(description = "异常类型名称")
+    private String exceptionTypeName;
+    
+    @Schema(description = "图片URL")
+    private String imageUrl;
+    
+    @Schema(description = "异常时间(yyyy-MM-dd HH:mm:ss)")
+    private String exceptionTime;
+    
+    @Schema(description = "经度")
+    private String longitude;
+    
+    @Schema(description = "纬度")
+    private String latitude;
+    
+    @Schema(description = "位置描述")
+    private String location;
+    
+    @Schema(description = "定位状态(1-在线,0-离线)")
+    private Integer locationStatus;
+    
+    @Schema(description = "定位状态描述")
+    private String locationStatusDesc;
+    
+    @Schema(description = "异常详情描述")
+    private String description;
+    
+    @Schema(description = "司机姓名")
+    private String driverName;
+    
+    @Schema(description = "司机电话")
+    private String driverPhone;
+}

+ 71 - 0
sckw-modules/sckw-transport/src/main/java/com/sckw/transport/repository/KwtVehicleExceptionRepository.java

@@ -0,0 +1,71 @@
+package com.sckw.transport.repository;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.core.toolkit.StringUtils;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.sckw.transport.dao.KwtVehicleExceptionMapper;
+import com.sckw.transport.model.KwtVehicleException;
+import org.springframework.stereotype.Repository;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.stream.Collectors;
+
+/**
+ * @author cxf
+ * @desc 车辆异常图片Repository
+ * @date 2025-12-01
+ */
+@Repository
+public class KwtVehicleExceptionRepository extends ServiceImpl<KwtVehicleExceptionMapper, KwtVehicleException> {
+    
+    /**
+     * 分页查询车辆异常图片
+     *
+     * @param entId          企业ID
+     * @param exceptionType  异常类型
+     * @param truckNo        车牌号
+     * @param pageNum        页码
+     * @param pageSize       每页数量
+     * @return 分页结果
+     */
+    public IPage<KwtVehicleException> queryExceptionImagePage(Long entId, Integer exceptionType,
+                                                              String truckNo, int pageNum, int pageSize) {
+        return page(new Page<>(pageNum, pageSize),
+                Wrappers.<KwtVehicleException>lambdaQuery()
+                        .eq(KwtVehicleException::getDelFlag, 0)
+                        .eq(Objects.nonNull(entId), KwtVehicleException::getEntId, entId)
+                        .eq(Objects.nonNull(exceptionType), KwtVehicleException::getExceptionType, exceptionType)
+                        .like(StringUtils.isNotBlank(truckNo), KwtVehicleException::getTruckNo, truckNo)
+                        .orderByDesc(KwtVehicleException::getExceptionTime));
+    }
+    
+    /**
+     * 批量查询运单的异常数量
+     *
+     * @param wOrderNos 运单号列表
+     * @return 运单号对应的异常数量 Map
+     */
+    public Map<String, Integer> countExceptionsByWOrderNos(List<String> wOrderNos) {
+        if (wOrderNos == null || wOrderNos.isEmpty()) {
+            return Map.of();
+        }
+        
+        List<KwtVehicleException> exceptions = list(
+                Wrappers.<KwtVehicleException>lambdaQuery()
+                        .eq(KwtVehicleException::getDelFlag, 0)
+                        .in(KwtVehicleException::getWOrderNo, wOrderNos)
+        );
+        
+        // 按运单号分组统计数量
+        return exceptions.stream()
+                .filter(e -> e.getWOrderNo() != null)
+                .collect(Collectors.groupingBy(
+                        KwtVehicleException::getWOrderNo,
+                        Collectors.collectingAndThen(Collectors.counting(), Long::intValue)
+                ));
+    }
+}

+ 165 - 0
sckw-modules/sckw-transport/src/main/java/com/sckw/transport/service/VehicleExceptionService.java

@@ -0,0 +1,165 @@
+package com.sckw.transport.service;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.sckw.core.web.response.result.PageDataResult;
+import com.sckw.transport.api.feign.VehicleTraceClient;
+import com.sckw.transport.api.model.dto.VehicleDataDTO;
+import com.sckw.transport.api.model.dto.VehicleReturnData;
+import com.sckw.core.web.response.BaseResult;
+import com.sckw.transport.model.KwtVehicleException;
+import com.sckw.transport.model.dto.VehicleExceptionQueryReq;
+import com.sckw.transport.model.enuma.VehicleExceptionTypeEnum;
+import com.sckw.transport.model.vo.VehicleExceptionVo;
+import com.sckw.transport.repository.KwtVehicleExceptionRepository;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.collections4.CollectionUtils;
+import org.springframework.stereotype.Service;
+
+import java.time.LocalDateTime;
+import java.util.*;
+import java.util.stream.Collectors;
+
+/**
+ * @author AI Assistant
+ * @desc 车辆异常图片Service
+ * @date 2025-12-01
+ */
+@Service
+@Slf4j
+@RequiredArgsConstructor
+public class VehicleExceptionService {
+    
+    private final KwtVehicleExceptionRepository exceptionImageRepository;
+    private final VehicleTraceClient vehicleTraceClient;
+    
+    /**
+     * 分页查询车辆异常图片信息
+     *
+     * @param req 查询请求
+     * @return 分页结果
+     */
+    public PageDataResult<VehicleExceptionVo> queryExceptionList(VehicleExceptionQueryReq req) {
+        // 分页查询异常图片数据
+        IPage<KwtVehicleException> page = exceptionImageRepository.queryExceptionImagePage(
+                req.getEntId(),
+                req.getExceptionType(),
+                req.getTruckNo(),
+                req.getPageNum(),
+                req.getPageSize()
+        );
+        
+        List<KwtVehicleException> records = page.getRecords();
+        if (CollectionUtils.isEmpty(records)) {
+            return PageDataResult.empty(req.getPageNum(), req.getPageSize());
+        }
+        
+        // 如果有定位状态筛选,需要查询车辆实时定位
+        Map<String, Integer> truckLocationStatusMap = new HashMap<>();
+        if (req.getLocationStatus() != null) {
+            List<String> truckNos = records.stream()
+                    .map(KwtVehicleException::getTruckNo)
+                    .distinct()
+                    .collect(Collectors.toList());
+            
+            truckLocationStatusMap = queryVehicleLocationStatus(truckNos);
+        }
+        
+        // 转换为VO
+        final Map<String, Integer> finalLocationStatusMap = truckLocationStatusMap;
+        List<VehicleExceptionVo> voList = records.stream()
+                .map(image -> buildVehicleExceptionImageVo(image, finalLocationStatusMap))
+                .filter(Objects::nonNull)
+                .collect(Collectors.toList());
+        
+        // 如果有定位状态筛选,过滤结果
+        if (req.getLocationStatus() != null) {
+            voList = voList.stream()
+                    .filter(vo -> req.getLocationStatus().equals(vo.getLocationStatus()))
+                    .collect(Collectors.toList());
+        }
+        
+        return PageDataResult.of(page, voList);
+    }
+    
+    /**
+     * 构建车辆异常图片VO
+     *
+     * @param image                异常图片实体
+     * @param locationStatusMap    定位状态Map
+     * @return VO对象
+     */
+    private VehicleExceptionVo buildVehicleExceptionImageVo(KwtVehicleException image,
+                                                            Map<String, Integer> locationStatusMap) {
+        VehicleExceptionVo vo = new VehicleExceptionVo();
+        
+        vo.setId(image.getId());
+        vo.setTruckNo(image.getTruckNo());
+        vo.setExceptionType(image.getExceptionType());
+        vo.setExceptionTypeName(VehicleExceptionTypeEnum.getName(image.getExceptionType()));
+        vo.setImageUrl(image.getImageUrl());
+        
+        // 格式化异常时间
+        if (image.getExceptionTime() != null) {
+            vo.setExceptionTime(new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
+                    .format(image.getExceptionTime()));
+        }
+        
+        vo.setLongitude(image.getLongitude());
+        vo.setLatitude(image.getLatitude());
+        vo.setLocation(image.getLocation());
+        vo.setDescription(image.getDescription());
+        vo.setDriverName(image.getDriverName());
+        vo.setDriverPhone(image.getDriverPhone());
+        
+        // 设置定位状态
+        if (locationStatusMap.containsKey(image.getTruckNo())) {
+            Integer locationStatus = locationStatusMap.get(image.getTruckNo());
+            vo.setLocationStatus(locationStatus);
+            vo.setLocationStatusDesc(locationStatus == 1 ? "在线" : "离线");
+        } else {
+            vo.setLocationStatus(0);
+            vo.setLocationStatusDesc("离线");
+        }
+        
+        return vo;
+    }
+    
+    /**
+     * 批量查询车辆定位状态
+     *
+     * @param truckNos 车牌号列表
+     * @return 车牌号与定位状态的映射
+     */
+    private Map<String, Integer> queryVehicleLocationStatus(List<String> truckNos) {
+        Map<String, Integer> locationStatusMap = new HashMap<>();
+        LocalDateTime now = LocalDateTime.now();
+        LocalDateTime thirtyMinutesAgo = now.minusMinutes(30);
+        
+        for (String truckNo : truckNos) {
+            try {
+                VehicleDataDTO vehicleDataDTO = new VehicleDataDTO();
+                vehicleDataDTO.setCarNo(truckNo);
+                BaseResult<VehicleReturnData> result = vehicleTraceClient.queryRealTimeLocation(vehicleDataDTO);
+                // 默认离线
+                Integer locationStatus = 0;
+                
+                if (result != null && result.getCode() == 200 && result.getData() != null) {
+                    VehicleReturnData vehicleData = result.getData();
+                    
+                    // 判断30分钟内有定位数据为在线
+                    if (vehicleData.getTs() != null && vehicleData.getTs().isAfter(thirtyMinutesAgo)) {
+                        locationStatus = 1;
+                    }
+                }
+                
+                locationStatusMap.put(truckNo, locationStatus);
+            } catch (Exception e) {
+                log.warn("查询车辆定位状态异常, 车牌号: {}", truckNo, e);
+                locationStatusMap.put(truckNo, 0);
+            }
+        }
+        
+        return locationStatusMap;
+    }
+}

+ 105 - 4
sckw-modules/sckw-transport/src/main/java/com/sckw/transport/service/kwfTruckTraceService.java

@@ -48,6 +48,7 @@ import com.sckw.transport.model.vo.TruckSelectVo;
 import com.sckw.transport.model.dto.TruckSelectReq;
 import com.sckw.transport.model.dto.MapVehicleQueryReq;
 import com.sckw.transport.model.vo.MapVehicleVo;
+import com.sckw.transport.model.enuma.MapVehicleSortTypeEnum;
 import com.sckw.transport.repository.*;
 import com.sckw.transport.response.CollectZjxlResponse;
 import com.sckw.transport.service.zj.VehicleCollectService;
@@ -866,6 +867,14 @@ public class kwfTruckTraceService {
         // 批量查询定位状态和位置信息
         Map<String, VehicleLocationInfo> locationInfoMap = queryVehicleLocationBatch(truckNos);
         
+        // 批量查询运单轨迹数据,统计异常数量(通过alarmCode判断)
+        List<String> wOrderNos = waybillOrders.stream()
+                .map(KwtWaybillOrder::getWOrderNo)
+                .filter(Objects::nonNull)
+                .distinct()
+                .collect(Collectors.toList());
+        Map<String, Integer> exceptionCountMap = countExceptionsByTraceData(wOrderNos);
+        
         // 根据定位状态筛选(这个筛选保留在内存中,因为涉及外部服务调用)
         List<KwtWaybillOrder> filteredOrders = waybillOrders;
         if (req.getLocationStatus() != null) {
@@ -881,10 +890,32 @@ public class kwfTruckTraceService {
         Map<String, KwtLogisticsOrderUnit> finalUnitMap = unitMap;
         Map<Long, KwtLogisticsOrderGoods> finalGoodsMap = goodsMap;
         List<MapVehicleVo> result = filteredOrders.stream()
-                .map(order -> buildMapVehicleVo(order, wOrderIdSubtaskMap, finalUnitMap, finalGoodsMap, locationInfoMap))
-                .sorted(Comparator.comparing(MapVehicleVo::getLocationTime, Comparator.reverseOrder()))
+                .map(order -> buildMapVehicleVo(order, wOrderIdSubtaskMap, finalUnitMap, finalGoodsMap, locationInfoMap, exceptionCountMap))
                 .collect(Collectors.toList());
         
+        // 根据排序类型进行排序
+        MapVehicleSortTypeEnum sortType = MapVehicleSortTypeEnum.getByCode(req.getSortType());
+        result = switch (sortType) {
+            case TIME_DESC ->
+                // 按任务开始时间倒序
+                    result.stream()
+                            .sorted(Comparator.comparing(MapVehicleVo::getTaskStartTime,
+                                    Comparator.nullsLast(Comparator.reverseOrder())))
+                            .collect(Collectors.toList());
+            case DURATION_DESC ->
+                // 按任务耗时倒序
+                    result.stream()
+                            .sorted(Comparator.comparing(MapVehicleVo::getTaskDuration,
+                                    Comparator.nullsLast(Comparator.reverseOrder())))
+                            .collect(Collectors.toList());
+            case EXCEPTION_COUNT_DESC ->
+                // 按异常数量倒序
+                    result.stream()
+                            .sorted(Comparator.comparing(MapVehicleVo::getExceptionCount,
+                                    Comparator.nullsLast(Comparator.reverseOrder())))
+                            .collect(Collectors.toList());
+        };
+        
         // 返回分页结果(使用数据库分页的总数)
         return PageDataResult.of(page, result);
     }
@@ -993,10 +1024,11 @@ public class kwfTruckTraceService {
                     if (vehicleData.getTs() != null) {
                         locationInfo.setLocationTime(vehicleData.getTs().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
                     }
+                    locationInfo.setStatus(vehicleData.getStatus());
                 } else {
                     locationInfo.setLocationStatus(0);
                 }
-                
+
                 locationInfoMap.put(truckNo, locationInfo);
             } catch (Exception e) {
                 log.warn("查询车辆定位信息异常, 车牌号: {}", truckNo, e);
@@ -1009,6 +1041,53 @@ public class kwfTruckTraceService {
         return locationInfoMap;
     }
     
+    /**
+     * 批量查询运单轨迹数据并统计异常数量
+     * 通过调用VehicleTraceClient#queryVehicleDataList查询轨迹,统计alarmCode不为空的数据为异常
+     *
+     * @param wOrderNos 运单号列表
+     * @return 运单号对应的异常数量 Map
+     */
+    private Map<String, Integer> countExceptionsByTraceData(List<String> wOrderNos) {
+        Map<String, Integer> exceptionCountMap = new HashMap<>();
+        
+        if (CollectionUtils.isEmpty(wOrderNos)) {
+            return exceptionCountMap;
+        }
+        
+        // 批量查询每个运单的轨迹数据
+        for (String wOrderNo : wOrderNos) {
+            try {
+                VehicleDataDTO vehicleDataDTO = new VehicleDataDTO();
+                vehicleDataDTO.setWOrderNo(wOrderNo);
+                
+                // 调用Feign接口查询轨迹列表
+                BaseResult<List<com.sckw.transport.api.model.dto.VehicleReturnData>> result = 
+                        vehicleTraceClient.queryVehicleDataList(vehicleDataDTO);
+                
+                if (result != null && result.getCode() == HttpStatus.SUCCESS_CODE && result.getData() != null) {
+                    List<com.sckw.transport.api.model.dto.VehicleReturnData> traceDataList = result.getData();
+                    
+                    // 统计alarmCode不为null且不为0的记录数量(表示有异常报警)
+                    int exceptionCount = (int) traceDataList.stream()
+                            .filter(data -> StringUtils.equals(data.getStatus(),"0"))
+                            .count();
+                    
+                    exceptionCountMap.put(wOrderNo, exceptionCount);
+                    log.debug("运单号: {}, 异常数量: {}", wOrderNo, exceptionCount);
+                } else {
+                    log.warn("查询运单轨迹数据失败或返回空数据, 运单号: {}", wOrderNo);
+                    exceptionCountMap.put(wOrderNo, 0);
+                }
+            } catch (Exception e) {
+                log.error("查询运单轨迹数据异常, 运单号: {}", wOrderNo, e);
+                exceptionCountMap.put(wOrderNo, 0);
+            }
+        }
+        
+        return exceptionCountMap;
+    }
+    
     /**
      * 构建地图车辆VO
      */
@@ -1016,7 +1095,8 @@ public class kwfTruckTraceService {
                                            Map<Long, KwtWaybillOrderSubtask> subtaskMap,
                                            Map<String, KwtLogisticsOrderUnit> unitMap,
                                            Map<Long, KwtLogisticsOrderGoods> goodsMap,
-                                           Map<String, VehicleLocationInfo> locationInfoMap) {
+                                           Map<String, VehicleLocationInfo> locationInfoMap,
+                                           Map<String, Integer> exceptionCountMap) {
         MapVehicleVo vo = new MapVehicleVo();
         
         // 车辆和司机信息
@@ -1077,6 +1157,26 @@ public class kwfTruckTraceService {
             vo.setLocationStatusDesc("离线");
         }
         
+        // 任务开始时间
+        if (order.getTaskStartTime() != null) {
+            vo.setTaskStartTime(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(order.getTaskStartTime()));
+        } else if (order.getCreateTime() != null) {
+            // 如果没有任务开始时间,使用创建时间
+            vo.setTaskStartTime(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(order.getCreateTime()));
+        }
+        
+        // 计算任务耗时(分钟)
+        Date startTime = order.getTaskStartTime() != null ? order.getTaskStartTime() : order.getCreateTime();
+        if (startTime != null) {
+            Date endTime = order.getTaskEndTime() != null ? order.getTaskEndTime() : new Date();
+            long duration = (endTime.getTime() - startTime.getTime()) / (1000 * 60);
+            vo.setTaskDuration(duration);
+        }
+        
+        // 从异常表查询该运单的异常数量
+        Integer exceptionCount = exceptionCountMap.getOrDefault(order.getWOrderNo(), 0);
+        vo.setExceptionCount(exceptionCount);
+        
         return vo;
     }
     
@@ -1090,6 +1190,7 @@ public class kwfTruckTraceService {
         private String latitude;
         private String location;
         private String locationTime;
+        private String status;
     }
 
     @NotNull

+ 35 - 0
sql/2025/12/01/2025_12_01_vehicle_exception.sql

@@ -0,0 +1,35 @@
+-- 车辆异常表
+CREATE TABLE IF NOT EXISTS `kwt_vehicle_exception` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键',
+  `ent_id` bigint(20) DEFAULT '0' COMMENT '企业ID',
+  `truck_id` bigint(20) DEFAULT '0' COMMENT '车辆ID',
+  `truck_no` varchar(50) DEFAULT '' COMMENT '车牌号',
+  `w_order_no` varchar(50) DEFAULT '' COMMENT '运单号',
+  `driver_id` bigint(20) DEFAULT '0' COMMENT '司机ID',
+  `driver_name` varchar(50) DEFAULT '' COMMENT '司机姓名',
+  `driver_phone` varchar(20) DEFAULT '' COMMENT '司机电话',
+  `exception_type` int(2) DEFAULT '0' COMMENT '异常类型(1-车辆偏航,2-急刹车,3-超速,4-异常停车)',
+  `image_url` varchar(500) DEFAULT '' COMMENT '图片URL',
+  `exception_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '异常时间',
+  `task_start_time` datetime DEFAULT NULL COMMENT '任务开始时间',
+  `task_end_time` datetime DEFAULT NULL COMMENT '任务结束时间',
+  `task_duration` int(10) DEFAULT '0' COMMENT '任务耗时(分钟)',
+  `exception_count` int(10) DEFAULT '0' COMMENT '异常数量',
+  `longitude` varchar(50) DEFAULT '' COMMENT '经度',
+  `latitude` varchar(50) DEFAULT '' COMMENT '纬度',
+  `location` varchar(200) DEFAULT '' COMMENT '位置描述',
+  `description` varchar(500) DEFAULT '' COMMENT '异常详情描述',
+  `create_by` bigint(20) DEFAULT '0' COMMENT '创建人',
+  `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+  `update_by` bigint(20) DEFAULT '0' COMMENT '更新人',
+  `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+  `del_flag` int(1) DEFAULT '0' COMMENT '是否删除(0未删除,1删除)',
+  PRIMARY KEY (`id`),
+  KEY `idx_ent_id` (`ent_id`),
+  KEY `idx_truck_no` (`truck_no`),
+  KEY `idx_w_order_no` (`w_order_no`),
+  KEY `idx_exception_type` (`exception_type`),
+  KEY `idx_exception_time` (`exception_time`),
+  KEY `idx_task_duration` (`task_duration`),
+  KEY `idx_exception_count` (`exception_count`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='车辆异常表';