From f67945d53b20f6a45ae50b27d74c966eb1355bb4 Mon Sep 17 00:00:00 2001
From: wlzboy <66905212@qq.com>
Date: 星期日, 16 十一月 2025 22:53:54 +0800
Subject: [PATCH] feat: 增加分段GPS计算行程距离
---
ruoyi-ui/src/views/system/vehicle/index.vue | 31
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/GpsCollectServiceImpl.java | 4
sql/vehicle_mileage_stats_job.sql | 10
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/VehicleMileageStatsServiceImpl.java | 205 +++
ruoyi-quartz/src/main/java/com/ruoyi/quartz/task/VehicleGpsSegmentMileageTask.java | 121 +
ruoyi-system/src/main/java/com/ruoyi/system/mapper/VehicleGpsMapper.java | 12
ruoyi-quartz/src/main/java/com/ruoyi/quartz/task/GpsSyncTask.java | 6
sql/updates/update_vehicle_no_from_vehicle_info.sql | 25
ruoyi-ui/src/views/task/detail/index.vue | 112 +
sql/updates/add_segment_count_to_mileage_stats.sql | 26
ruoyi-ui/src/api/system/mileageStats.js | 67 +
sql/updates/add_task_id_to_segment_mileage.sql | 27
sql/updates/remove_dept_id_from_vehicle_info.sql | 26
ruoyi-system/src/main/java/com/ruoyi/system/mapper/VehicleInfoMapper.java | 16
ruoyi-ui/src/api/system/gpsSegment.js | 39
ruoyi-ui/src/components/TaskMileageDetail/README.md | 187 +++
ruoyi-ui/src/views/system/mileageStats/README.md | 172 ++
ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/VehicleGpsController.java | 7
ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/VehicleMileageStatsController.java | 25
ruoyi-system/src/main/java/com/ruoyi/system/domain/VehicleMileageStats.java | 46
ruoyi-system/src/main/resources/mapper/system/VehicleGpsMapper.xml | 15
sql/vehicle_gps_segment_mileage.sql | 49
ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/VehicleGpsSegmentMileageController.java | 132 ++
ruoyi-system/src/main/java/com/ruoyi/system/mapper/VehicleGpsSegmentMileageMapper.java | 78 +
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/VehicleInfoServiceImpl.java | 25
ruoyi-system/src/main/resources/mapper/system/VehicleMileageStatsMapper.xml | 50
ruoyi-ui/src/views/system/mileageStats/index.vue | 490 +++++++
sql/vehicle_mileage_stats.sql | 23
ruoyi-quartz/src/main/java/com/ruoyi/quartz/task/VehicleMileageStatsTask.java | 84 +
sql/vehicle_gps_segment_mileage_job.sql | 59
ruoyi-system/src/main/java/com/ruoyi/system/service/IVehicleMileageStatsService.java | 17
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/VehicleGpsSegmentMileageServiceImpl.java | 658 ++++++++++
ruoyi-framework/src/main/java/com/ruoyi/framework/config/JacksonConfig.java | 36
ruoyi-system/src/main/java/com/ruoyi/system/service/IVehicleInfoService.java | 8
ruoyi-system/src/main/java/com/ruoyi/system/domain/VehicleInfo.java | 17
ruoyi-system/src/main/java/com/ruoyi/system/domain/VehicleGpsSegmentMileage.java | 194 +++
ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/VehicleInfoController.java | 2
ruoyi-ui/src/components/TaskMileageDetail/index.vue | 258 ++++
ruoyi-system/src/main/java/com/ruoyi/system/service/IVehicleGpsSegmentMileageService.java | 69 +
ruoyi-system/src/main/resources/mapper/system/VehicleInfoMapper.xml | 87 +
ruoyi-system/src/main/resources/mapper/system/VehicleGpsSegmentMileageMapper.xml | 157 ++
41 files changed, 3,569 insertions(+), 103 deletions(-)
diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/VehicleGpsController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/VehicleGpsController.java
index b67fb67..48791f2 100644
--- a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/VehicleGpsController.java
+++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/VehicleGpsController.java
@@ -762,8 +762,7 @@
// 鏋勫缓澶╁湴鍥惧湴鐞嗙紪鐮丄PI URL
String url = "http://api.tianditu.gov.cn/geocoder";
- String params = "ds={\"keyWord\":\"" + address + \"}" +
- "&tk=" + tiandituMapConfig.getTk();
+ String params = "ds={\"keyWord\":\"" + address + "\"}&tk=" + tiandituMapConfig.getTk();
logger.info("澶╁湴鍥惧湴鐞嗙紪鐮佽姹�: address={}", address);
@@ -943,7 +942,7 @@
// 绗竴姝ワ細璧风偣鍦板潃杞潗鏍�
String geocodingUrl1 = "http://api.tianditu.gov.cn/geocoder";
- String geocodingParams1 = "ds={\"keyWord\":\"" + fromAddress + \"}" +
+ String geocodingParams1 = "ds={\"keyWord\":\"" + fromAddress + "\"}" +
"&tk=" + tiandituMapConfig.getTk();
String geocodingResponse1 = HttpUtils.sendGet(geocodingUrl1, geocodingParams1);
@@ -965,7 +964,7 @@
// 绗簩姝ワ細缁堢偣鍦板潃杞潗鏍�
String geocodingUrl2 = "http://api.tianditu.gov.cn/geocoder";
- String geocodingParams2 = "ds={\"keyWord\":\"" + toAddress + \"}" +
+ String geocodingParams2 = "ds={\"keyWord\":\"" + toAddress + "\"}" +
"&tk=" + tiandituMapConfig.getTk();
String geocodingResponse2 = HttpUtils.sendGet(geocodingUrl2, geocodingParams2);
diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/VehicleGpsSegmentMileageController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/VehicleGpsSegmentMileageController.java
new file mode 100644
index 0000000..b98f959
--- /dev/null
+++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/VehicleGpsSegmentMileageController.java
@@ -0,0 +1,132 @@
+package com.ruoyi.web.controller.system;
+
+import java.math.BigDecimal;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.List;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+import com.ruoyi.common.annotation.Log;
+import com.ruoyi.common.core.controller.BaseController;
+import com.ruoyi.common.core.domain.AjaxResult;
+import com.ruoyi.common.core.page.TableDataInfo;
+import com.ruoyi.common.enums.BusinessType;
+import com.ruoyi.system.domain.VehicleGpsSegmentMileage;
+import com.ruoyi.system.mapper.VehicleGpsSegmentMileageMapper;
+import com.ruoyi.system.service.IVehicleGpsSegmentMileageService;
+import com.ruoyi.system.service.ISysConfigService;
+
+/**
+ * GPS鍒嗘閲岀▼Controller
+ */
+@RestController
+@RequestMapping("/system/gpsSegment")
+public class VehicleGpsSegmentMileageController extends BaseController {
+
+ private static final Logger logger = LoggerFactory.getLogger(VehicleGpsSegmentMileageController.class);
+
+ @Autowired
+ private VehicleGpsSegmentMileageMapper segmentMileageMapper;
+
+ @Autowired
+ private IVehicleGpsSegmentMileageService segmentMileageService;
+
+ @Autowired
+ private ISysConfigService configService;
+
+ /**
+ * 鏌ヨGPS鍒嗘閲岀▼鍒楄〃
+ */
+ @PreAuthorize("@ss.hasPermi('system:gpsSegment:list')")
+ @GetMapping("/list")
+ public TableDataInfo list(VehicleGpsSegmentMileage segmentMileage) {
+ startPage();
+ List<VehicleGpsSegmentMileage> list = segmentMileageMapper.selectVehicleGpsSegmentMileageList(segmentMileage);
+ return getDataTable(list);
+ }
+
+ /**
+ * 鎸変换鍔D鏌ヨGPS鍒嗘閲岀▼鍒楄〃
+ */
+ @PreAuthorize("@ss.hasPermi('system:gpsSegment:query')")
+ @GetMapping("/task/{taskId}")
+ public AjaxResult getSegmentsByTask(@PathVariable("taskId") Long taskId) {
+ try {
+ List<VehicleGpsSegmentMileage> list = segmentMileageMapper.selectSegmentsByTaskId(taskId);
+ return success(list);
+ } catch (Exception e) {
+ logger.error("鏌ヨ浠诲姟GPS閲岀▼澶辫触 - taskId: {}", taskId, e);
+ return error("鏌ヨ澶辫触锛�" + e.getMessage());
+ }
+ }
+
+ /**
+ * 鏌ヨ浠诲姟鐨勬�婚噷绋�
+ */
+ @PreAuthorize("@ss.hasPermi('system:gpsSegment:query')")
+ @GetMapping("/task/{taskId}/total")
+ public AjaxResult getTaskTotalMileage(@PathVariable("taskId") Long taskId) {
+ try {
+ BigDecimal totalMileage = segmentMileageMapper.selectTotalMileageByTaskId(taskId);
+ return success(totalMileage);
+ } catch (Exception e) {
+ logger.error("鏌ヨ浠诲姟鎬婚噷绋嬪け璐� - taskId: {}", taskId, e);
+ return error("鏌ヨ澶辫触锛�" + e.getMessage());
+ }
+ }
+
+ /**
+ * 鏌ヨ杞﹁締鎸囧畾鏃ユ湡鑼冨洿鐨凣PS鍒嗘閲岀▼
+ */
+ @PreAuthorize("@ss.hasPermi('system:gpsSegment:query')")
+ @GetMapping("/range")
+ public AjaxResult getSegmentsByDateRange(
+ @RequestParam("vehicleId") Long vehicleId,
+ @RequestParam("startDate") String startDateStr,
+ @RequestParam("endDate") String endDateStr) {
+ try {
+ SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+ Date startDate = sdf.parse(startDateStr + " 00:00:00");
+ Date endDate = sdf.parse(endDateStr + " 23:59:59");
+
+ List<VehicleGpsSegmentMileage> list = segmentMileageMapper.selectSegmentsByDateRange(vehicleId, startDate, endDate);
+ return success(list);
+ } catch (Exception e) {
+ logger.error("鏌ヨGPS鍒嗘閲岀▼澶辫触 - vehicleId: {}, dateRange: {} - {}",
+ vehicleId, startDateStr, endDateStr, e);
+ return error("鏌ヨ澶辫触锛�" + e.getMessage());
+ }
+ }
+
+ /**
+ * 琛ュ伩璁$畻锛氭煡鎵惧苟璁$畻鏈澶勭悊鐨凣PS鏁版嵁
+ * 鐢ㄤ簬鏈嶅姟閲嶅惎鍚庣殑鏁版嵁淇
+ */
+ @PreAuthorize("@ss.hasPermi('system:gpsSegment:compensate')")
+ @Log(title = "GPS閲岀▼琛ュ伩璁$畻", businessType = BusinessType.OTHER)
+ @PostMapping("/compensate")
+ public AjaxResult compensateCalculation() {
+ try {
+ // 浠庨厤缃腑鑾峰彇鍥炴函澶╂暟锛岄粯璁�2澶�
+ String lookbackDaysConfig = configService.selectConfigByKey("gps.mileage.compensate.lookback.days");
+ int lookbackDays = lookbackDaysConfig != null ? Integer.parseInt(lookbackDaysConfig) : 2;
+
+ logger.info("寮�濮嬫墽琛孏PS閲岀▼琛ュ伩璁$畻 - 鍥炴函澶╂暟: {}", lookbackDays);
+
+ int successCount = segmentMileageService.compensateCalculation(lookbackDays);
+
+ return success("琛ュ伩璁$畻瀹屾垚锛屽鐞� " + successCount + " 杈嗚溅");
+ } catch (Exception e) {
+ logger.error("GPS閲岀▼琛ュ伩璁$畻澶辫触", e);
+ return error("琛ュ伩璁$畻澶辫触锛�" + e.getMessage());
+ }
+ }
+}
diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/VehicleInfoController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/VehicleInfoController.java
index fc51bfa..253d262 100644
--- a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/VehicleInfoController.java
+++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/VehicleInfoController.java
@@ -59,7 +59,7 @@
*/
@GetMapping(value = "/{vehicleId}")
public AjaxResult getInfo(@PathVariable("vehicleId") Long vehicleId) {
- return success(vehicleInfoService.selectVehicleInfoById(vehicleId));
+ return success(vehicleInfoService.selectVehicleInfoWithDeptsById(vehicleId));
}
/**
diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/VehicleMileageStatsController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/VehicleMileageStatsController.java
index 2383778..789d0cd 100644
--- a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/VehicleMileageStatsController.java
+++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/VehicleMileageStatsController.java
@@ -4,6 +4,8 @@
import java.util.Date;
import java.util.List;
import javax.servlet.http.HttpServletResponse;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.DeleteMapping;
@@ -29,6 +31,8 @@
@RestController
@RequestMapping("/system/mileageStats")
public class VehicleMileageStatsController extends BaseController {
+
+ private static final Logger logger = LoggerFactory.getLogger(VehicleMileageStatsController.class);
@Autowired
private IVehicleMileageStatsService vehicleMileageStatsService;
@@ -103,15 +107,23 @@
@PostMapping("/calculate")
public AjaxResult calculate(Long vehicleId, String statDate) {
try {
+ if (statDate == null || statDate.trim().isEmpty()) {
+ return error("缁熻鏃ユ湡涓嶈兘涓虹┖");
+ }
+
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
- Date date = sdf.parse(statDate);
+ sdf.setLenient(false); // 涓ユ牸瑙f瀽鏃ユ湡
+ Date date = sdf.parse(statDate.trim());
+
VehicleMileageStats stats = vehicleMileageStatsService.calculateAndSaveMileageStats(vehicleId, date);
+ // 淇String鍒癉ate杞崲闂锛屾坊鍔犳洿濂界殑閿欒澶勭悊
if (stats != null) {
- return success("閲岀▼缁熻璁$畻鎴愬姛", stats);
+ return AjaxResult.success("閲岀▼缁熻璁$畻鎴愬姛", stats);
} else {
return error("璇ヨ溅杈嗗湪鎸囧畾鏃ユ湡鏃燝PS鏁版嵁");
}
} catch (Exception e) {
+ logger.error("閲岀▼缁熻璁$畻澶辫触 - 杞﹁締ID: {}, 鏃ユ湡: {}", vehicleId, statDate, e);
return error("閲岀▼缁熻璁$畻澶辫触锛�" + e.getMessage());
}
}
@@ -124,11 +136,18 @@
@PostMapping("/batchCalculate")
public AjaxResult batchCalculate(String statDate) {
try {
+ if (statDate == null || statDate.trim().isEmpty()) {
+ return error("缁熻鏃ユ湡涓嶈兘涓虹┖");
+ }
+
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
- Date date = sdf.parse(statDate);
+ sdf.setLenient(false); // 涓ユ牸瑙f瀽鏃ユ湡
+ Date date = sdf.parse(statDate.trim());
+
int count = vehicleMileageStatsService.batchCalculateMileageStats(date);
return success("鎵归噺閲岀▼缁熻瀹屾垚锛屾垚鍔熺粺璁� " + count + " 杈嗚溅");
} catch (Exception e) {
+ logger.error("鎵归噺閲岀▼缁熻澶辫触 - 鏃ユ湡: {}", statDate, e);
return error("鎵归噺閲岀▼缁熻澶辫触锛�" + e.getMessage());
}
}
diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/config/JacksonConfig.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/JacksonConfig.java
new file mode 100644
index 0000000..5171d1d
--- /dev/null
+++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/JacksonConfig.java
@@ -0,0 +1,36 @@
+package com.ruoyi.framework.config;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.module.SimpleModule;
+import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
+import org.apache.ibatis.executor.loader.javassist.JavassistProxyFactory;
+import org.apache.ibatis.executor.loader.cglib.CglibProxyFactory;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
+
+import java.math.BigInteger;
+
+/**
+ * Jackson閰嶇疆绫�
+ * 瑙e喅MyBatis寤惰繜鍔犺浇浠g悊瀵硅薄搴忓垪鍖栭棶棰�
+ */
+@Configuration
+public class JacksonConfig {
+
+ @Bean
+ public ObjectMapper objectMapper(Jackson2ObjectMapperBuilder builder) {
+ ObjectMapper objectMapper = builder.createXmlMapper(false).build();
+
+ // 娉ㄥ唽鑷畾涔夋ā鍧�
+ SimpleModule module = new SimpleModule();
+ module.addSerializer(BigInteger.class, ToStringSerializer.instance);
+ module.addSerializer(Long.class, ToStringSerializer.instance);
+ objectMapper.registerModule(module);
+
+ // 绂佺敤FAIL_ON_EMPTY_BEANS鐗规�э紝閬垮厤浠g悊瀵硅薄搴忓垪鍖栭敊璇�
+ objectMapper.disable(com.fasterxml.jackson.databind.SerializationFeature.FAIL_ON_EMPTY_BEANS);
+
+ return objectMapper;
+ }
+}
\ No newline at end of file
diff --git a/ruoyi-quartz/src/main/java/com/ruoyi/quartz/task/GpsSyncTask.java b/ruoyi-quartz/src/main/java/com/ruoyi/quartz/task/GpsSyncTask.java
index bb1e178..ac0f9f2 100644
--- a/ruoyi-quartz/src/main/java/com/ruoyi/quartz/task/GpsSyncTask.java
+++ b/ruoyi-quartz/src/main/java/com/ruoyi/quartz/task/GpsSyncTask.java
@@ -3,6 +3,7 @@
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
+import java.util.stream.Collectors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -45,8 +46,11 @@
// 1. 鑾峰彇鎵�鏈夎溅杈嗕俊鎭�
List<VehicleInfo> vehicleList = vehicleInfoService.selectVehicleInfoList(new VehicleInfo());
+ List<String> deviceIds = vehicleList.stream().map(VehicleInfo::getDeviceId).collect(Collectors.toList());
// 2. 鑾峰彇鎵�鏈夎溅杈嗙殑GPS鏈�鍚庝綅缃�
- GpsLastPositionResponse gpsLastPositionResponse = gpsCollectService.getLastPosition(new GpsLastPositionRequest());
+ GpsLastPositionRequest request = new GpsLastPositionRequest();
+// request.setDeviceids(deviceIds);
+ GpsLastPositionResponse gpsLastPositionResponse = gpsCollectService.getLastPosition(request);
// 3. 閬嶅巻杞﹁締鍒楄〃锛岃幏鍙栨瘡涓溅杈嗙殑GPS浣嶇疆
for (VehicleInfo vehicle : vehicleList) {
diff --git a/ruoyi-quartz/src/main/java/com/ruoyi/quartz/task/VehicleGpsSegmentMileageTask.java b/ruoyi-quartz/src/main/java/com/ruoyi/quartz/task/VehicleGpsSegmentMileageTask.java
new file mode 100644
index 0000000..e1713ce
--- /dev/null
+++ b/ruoyi-quartz/src/main/java/com/ruoyi/quartz/task/VehicleGpsSegmentMileageTask.java
@@ -0,0 +1,121 @@
+package com.ruoyi.quartz.task;
+
+import java.util.Calendar;
+import java.util.Date;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+import com.ruoyi.system.service.IVehicleGpsSegmentMileageService;
+import com.ruoyi.system.service.ISysConfigService;
+
+/**
+ * 杞﹁締GPS鍒嗘閲岀▼璁$畻瀹氭椂浠诲姟
+ */
+@Component("vehicleGpsSegmentMileageTask")
+public class VehicleGpsSegmentMileageTask {
+
+ private static final Logger logger = LoggerFactory.getLogger(VehicleGpsSegmentMileageTask.class);
+
+ @Autowired
+ private IVehicleGpsSegmentMileageService segmentMileageService;
+
+ @Autowired
+ private ISysConfigService configService;
+
+ /**
+ * 璁$畻鏈�杩戜竴娈垫椂闂寸殑GPS鍒嗘閲岀▼
+ * 榛樿璁$畻鏈�杩�1灏忔椂鐨勬暟鎹�
+ */
+ public void calculateRecentSegmentMileage() {
+ calculateRecentSegmentMileage("60");
+ }
+
+ /**
+ * 璁$畻鏈�杩戞寚瀹氬垎閽熸暟鐨凣PS鍒嗘閲岀▼
+ *
+ * @param params 鍙傛暟瀛楃涓诧紝鏍煎紡锛氬垎閽熸暟锛堝锛�60琛ㄧず鏈�杩�60鍒嗛挓锛�
+ */
+ public void calculateRecentSegmentMileage(String params) {
+ try {
+ // 瑙f瀽鍙傛暟锛氳璁$畻鐨勬椂闂磋寖鍥达紙鍒嗛挓锛�
+ int minutes = 60; // 榛樿60鍒嗛挓
+ if (params != null && !params.trim().isEmpty()) {
+ try {
+ minutes = Integer.parseInt(params.trim());
+ } catch (NumberFormatException e) {
+ logger.warn("鍙傛暟鏍煎紡閿欒锛屼娇鐢ㄩ粯璁ゅ��60鍒嗛挓: {}", params);
+ }
+ }
+
+ // 鑾峰彇閰嶇疆鐨勬椂闂撮棿闅�
+ int segmentMinutes = 5; // 榛樿5鍒嗛挓
+ String segmentConfig = configService.selectConfigByKey("gps.mileage.segment.minutes");
+ if (segmentConfig != null && !segmentConfig.isEmpty()) {
+ try {
+ segmentMinutes = Integer.parseInt(segmentConfig);
+ } catch (NumberFormatException e) {
+ logger.warn("鍒嗘鏃堕棿闂撮殧閰嶇疆閿欒锛屼娇鐢ㄩ粯璁ゅ��5鍒嗛挓");
+ }
+ }
+
+ // 璁$畻鏃堕棿鑼冨洿
+ Calendar cal = Calendar.getInstance();
+ Date endTime = cal.getTime();
+ cal.add(Calendar.MINUTE, -minutes);
+ Date startTime = cal.getTime();
+
+ logger.info("寮�濮嬭绠桮PS鍒嗘閲岀▼ - 鏃堕棿鑼冨洿: {} 鍒� {}, 鏃堕棿娈甸棿闅�: {}鍒嗛挓",
+ startTime, endTime, segmentMinutes);
+
+ // 鎵归噺璁$畻
+ int successCount = segmentMileageService.batchCalculateSegmentMileage(startTime, endTime);
+
+ logger.info("GPS鍒嗘閲岀▼璁$畻瀹屾垚 - 鎴愬姛澶勭悊 {} 杈嗚溅", successCount);
+
+ } catch (Exception e) {
+ logger.error("GPS鍒嗘閲岀▼璁$畻浠诲姟鎵ц澶辫触", e);
+ }
+ }
+
+ /**
+ * 璁$畻鎸囧畾鏃ユ湡鐨凣PS鍒嗘閲岀▼
+ *
+ * @param params 鍙傛暟瀛楃涓诧紝鏍煎紡锛歽yyy-MM-dd锛堝锛�2025-01-15锛�
+ */
+ public void calculateDateSegmentMileage(String params) {
+ try {
+ if (params == null || params.trim().isEmpty()) {
+ logger.error("鏃ユ湡鍙傛暟涓嶈兘涓虹┖");
+ return;
+ }
+
+ // 瑙f瀽鏃ユ湡
+ java.text.SimpleDateFormat sdf = new java.text.SimpleDateFormat("yyyy-MM-dd");
+ Date date = sdf.parse(params.trim());
+
+ // 璁$畻褰撳ぉ鐨勮捣姝㈡椂闂�
+ Calendar cal = Calendar.getInstance();
+ cal.setTime(date);
+ cal.set(Calendar.HOUR_OF_DAY, 0);
+ cal.set(Calendar.MINUTE, 0);
+ cal.set(Calendar.SECOND, 0);
+ cal.set(Calendar.MILLISECOND, 0);
+ Date startTime = cal.getTime();
+
+ cal.add(Calendar.DAY_OF_MONTH, 1);
+ Date endTime = cal.getTime();
+
+ logger.info("寮�濮嬭绠楁寚瀹氭棩鏈烥PS鍒嗘閲岀▼ - 鏃ユ湡: {}, 鏃堕棿鑼冨洿: {} 鍒� {}",
+ params, startTime, endTime);
+
+ // 鎵归噺璁$畻
+ int successCount = segmentMileageService.batchCalculateSegmentMileage(startTime, endTime);
+
+ logger.info("鎸囧畾鏃ユ湡GPS鍒嗘閲岀▼璁$畻瀹屾垚 - 鎴愬姛澶勭悊 {} 杈嗚溅", successCount);
+
+ } catch (Exception e) {
+ logger.error("鎸囧畾鏃ユ湡GPS鍒嗘閲岀▼璁$畻浠诲姟鎵ц澶辫触 - 鍙傛暟: {}", params, e);
+ }
+ }
+}
diff --git a/ruoyi-quartz/src/main/java/com/ruoyi/quartz/task/VehicleMileageStatsTask.java b/ruoyi-quartz/src/main/java/com/ruoyi/quartz/task/VehicleMileageStatsTask.java
index 22cd0b8..8619a88 100644
--- a/ruoyi-quartz/src/main/java/com/ruoyi/quartz/task/VehicleMileageStatsTask.java
+++ b/ruoyi-quartz/src/main/java/com/ruoyi/quartz/task/VehicleMileageStatsTask.java
@@ -1,5 +1,7 @@
package com.ruoyi.quartz.task;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import org.slf4j.Logger;
@@ -55,30 +57,86 @@
logger.info("寮�濮嬫墽琛岃溅杈嗛噷绋嬬粺璁″畾鏃朵换鍔� - 缁熻鏃ユ湡: {}", dateStr);
try {
- // 瑙f瀽鏃ユ湡瀛楃涓�
- String[] parts = dateStr.split("-");
- if (parts.length != 3) {
- throw new IllegalArgumentException("鏃ユ湡鏍煎紡閿欒锛屽簲涓�: yyyy-MM-dd");
+ if (dateStr == null || dateStr.trim().isEmpty()) {
+ throw new IllegalArgumentException("鏃ユ湡涓嶈兘涓虹┖");
}
- Calendar calendar = Calendar.getInstance();
- calendar.set(Calendar.YEAR, Integer.parseInt(parts[0]));
- calendar.set(Calendar.MONTH, Integer.parseInt(parts[1]) - 1); // 鏈堜唤浠�0寮�濮�
- calendar.set(Calendar.DAY_OF_MONTH, Integer.parseInt(parts[2]));
- calendar.set(Calendar.HOUR_OF_DAY, 0);
- calendar.set(Calendar.MINUTE, 0);
- calendar.set(Calendar.SECOND, 0);
- calendar.set(Calendar.MILLISECOND, 0);
- Date targetDate = calendar.getTime();
+ // 浣跨敤SimpleDateFormat瑙f瀽鏃ユ湡瀛楃涓�
+ SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
+ sdf.setLenient(false); // 涓ユ牸瑙f瀽鏃ユ湡
+ Date targetDate = sdf.parse(dateStr.trim());
// 鎵归噺璁$畻閲岀▼缁熻
int successCount = vehicleMileageStatsService.batchCalculateMileageStats(targetDate);
logger.info("杞﹁締閲岀▼缁熻瀹氭椂浠诲姟鎵ц瀹屾垚 - 鏃ユ湡: {}, 鎴愬姛缁熻: {} 杈嗚溅", dateStr, successCount);
+ } catch (ParseException e) {
+ logger.error("杞﹁締閲岀▼缁熻瀹氭椂浠诲姟鎵ц澶辫触 - 鏃ユ湡鏍煎紡閿欒: {}", dateStr, e);
+ throw new RuntimeException("瀹氭椂浠诲姟鎵ц澶辫触: 鏃ユ湡鏍煎紡閿欒锛屽簲涓� yyyy-MM-dd");
} catch (Exception e) {
logger.error("杞﹁締閲岀▼缁熻瀹氭椂浠诲姟鎵ц澶辫触 - 鏃ユ湡: {}", dateStr, e);
throw new RuntimeException("瀹氭椂浠诲姟鎵ц澶辫触: " + e.getMessage());
}
}
+
+ /**
+ * 浠嶨PS鍒嗘閲岀▼姹囨�荤敓鎴愭槰鏃ョ粺璁℃暟鎹�
+ * 锛堟帹鑽愪娇鐢ㄦ鏂规硶锛屽熀浜庡凡璁$畻鐨勫垎娈甸噷绋嬫暟鎹眹鎬伙紝鎬ц兘鏇村ソ锛�
+ */
+ public void aggregateYesterdayFromSegments() {
+ logger.info("寮�濮嬫墽琛屼粠鍒嗘閲岀▼姹囨�讳换鍔� - 缁熻鏄ㄦ棩鏁版嵁");
+
+ try {
+ // 鑾峰彇鏄ㄥぉ鐨勬棩鏈�
+ Calendar calendar = Calendar.getInstance();
+ calendar.add(Calendar.DAY_OF_MONTH, -1);
+ calendar.set(Calendar.HOUR_OF_DAY, 0);
+ calendar.set(Calendar.MINUTE, 0);
+ calendar.set(Calendar.SECOND, 0);
+ calendar.set(Calendar.MILLISECOND, 0);
+ Date yesterday = calendar.getTime();
+
+ // 浠庡垎娈甸噷绋嬫眹鎬荤敓鎴愮粺璁�
+ int successCount = vehicleMileageStatsService.batchAggregateFromSegmentMileage(yesterday);
+
+ logger.info("浠庡垎娈甸噷绋嬫眹鎬讳换鍔℃墽琛屽畬鎴� - 鎴愬姛缁熻: {} 杈嗚溅", successCount);
+
+ } catch (Exception e) {
+ logger.error("浠庡垎娈甸噷绋嬫眹鎬讳换鍔℃墽琛屽け璐�", e);
+ throw new RuntimeException("姹囨�讳换鍔℃墽琛屽け璐�: " + e.getMessage());
+ }
+ }
+
+ /**
+ * 浠嶨PS鍒嗘閲岀▼姹囨�荤敓鎴愭寚瀹氭棩鏈熺殑缁熻鏁版嵁
+ *
+ * @param dateStr 鏃ユ湡瀛楃涓诧紝鏍煎紡锛歽yyy-MM-dd
+ */
+ public void aggregateFromSegmentsByDate(String dateStr) {
+ logger.info("寮�濮嬫墽琛屼粠鍒嗘閲岀▼姹囨�讳换鍔� - 缁熻鏃ユ湡: {}", dateStr);
+
+ try {
+ if (dateStr == null || dateStr.trim().isEmpty()) {
+ throw new IllegalArgumentException("鏃ユ湡涓嶈兘涓虹┖");
+ }
+
+ // 浣跨敤SimpleDateFormat瑙f瀽鏃ユ湡瀛楃涓�
+ SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
+ sdf.setLenient(false); // 涓ユ牸瑙f瀽鏃ユ湡
+ Date targetDate = sdf.parse(dateStr.trim());
+
+ // 浠庡垎娈甸噷绋嬫眹鎬荤敓鎴愮粺璁�
+ int successCount = vehicleMileageStatsService.batchAggregateFromSegmentMileage(targetDate);
+
+ logger.info("浠庡垎娈甸噷绋嬫眹鎬讳换鍔℃墽琛屽畬鎴� - 鏃ユ湡: {}, 鎴愬姛缁熻: {} 杈嗚溅", dateStr, successCount);
+
+ } catch (ParseException e) {
+ logger.error("浠庡垎娈甸噷绋嬫眹鎬讳换鍔℃墽琛屽け璐� - 鏃ユ湡鏍煎紡閿欒: {}", dateStr, e);
+ throw new RuntimeException("姹囨�讳换鍔℃墽琛屽け璐�: 鏃ユ湡鏍煎紡閿欒锛屽簲涓� yyyy-MM-dd");
+ } catch (Exception e) {
+ logger.error("浠庡垎娈甸噷绋嬫眹鎬讳换鍔℃墽琛屽け璐� - 鏃ユ湡: {}", dateStr, e);
+ throw new RuntimeException("姹囨�讳换鍔℃墽琛屽け璐�: " + e.getMessage());
+ }
+ }
}
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/domain/VehicleGpsSegmentMileage.java b/ruoyi-system/src/main/java/com/ruoyi/system/domain/VehicleGpsSegmentMileage.java
new file mode 100644
index 0000000..7a6128d
--- /dev/null
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/domain/VehicleGpsSegmentMileage.java
@@ -0,0 +1,194 @@
+package com.ruoyi.system.domain;
+
+import java.math.BigDecimal;
+import java.util.Date;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.ruoyi.common.annotation.Excel;
+import com.ruoyi.common.core.domain.BaseEntity;
+
+/**
+ * 杞﹁締GPS鍒嗘閲岀▼瀵硅薄 tb_vehicle_gps_segment_mileage
+ */
+public class VehicleGpsSegmentMileage extends BaseEntity {
+ private static final long serialVersionUID = 1L;
+
+ /** 鍒嗘ID */
+ private Long segmentId;
+
+ /** 杞﹁締ID */
+ @Excel(name = "杞﹁締ID")
+ private Long vehicleId;
+
+ /** 杞︾墝鍙� */
+ @Excel(name = "杞︾墝鍙�")
+ private String vehicleNo;
+
+ /** 鏃堕棿娈靛紑濮嬫椂闂� */
+ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+ @Excel(name = "鏃堕棿娈靛紑濮嬫椂闂�", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")
+ private Date segmentStartTime;
+
+ /** 鏃堕棿娈电粨鏉熸椂闂� */
+ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+ @Excel(name = "鏃堕棿娈电粨鏉熸椂闂�", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")
+ private Date segmentEndTime;
+
+ /** 璧风偣缁忓害 */
+ @Excel(name = "璧风偣缁忓害")
+ private BigDecimal startLongitude;
+
+ /** 璧风偣绾害 */
+ @Excel(name = "璧风偣绾害")
+ private BigDecimal startLatitude;
+
+ /** 缁堢偣缁忓害 */
+ @Excel(name = "缁堢偣缁忓害")
+ private BigDecimal endLongitude;
+
+ /** 缁堢偣绾害 */
+ @Excel(name = "缁堢偣绾害")
+ private BigDecimal endLatitude;
+
+ /** 娈佃窛绂�(鍏噷) */
+ @Excel(name = "娈佃窛绂�(鍏噷)")
+ private BigDecimal segmentDistance;
+
+ /** GPS鐐规暟閲� */
+ @Excel(name = "GPS鐐规暟閲�")
+ private Integer gpsPointCount;
+
+ /** 鍏宠仈鐨凣PS璁板綍ID鍒楄〃 */
+ private String gpsIds;
+
+ /** 鍏宠仈鐨勪换鍔D */
+ @Excel(name = "鍏宠仈浠诲姟ID")
+ private Long taskId;
+
+ /** 浠诲姟缂栧彿 */
+ @Excel(name = "浠诲姟缂栧彿")
+ private String taskCode;
+
+ /** 璁$畻鏂瑰紡 */
+ @Excel(name = "璁$畻鏂瑰紡")
+ private String calculateMethod;
+
+ public Long getSegmentId() {
+ return segmentId;
+ }
+
+ public void setSegmentId(Long segmentId) {
+ this.segmentId = segmentId;
+ }
+
+ public Long getVehicleId() {
+ return vehicleId;
+ }
+
+ public void setVehicleId(Long vehicleId) {
+ this.vehicleId = vehicleId;
+ }
+
+ public String getVehicleNo() {
+ return vehicleNo;
+ }
+
+ public void setVehicleNo(String vehicleNo) {
+ this.vehicleNo = vehicleNo;
+ }
+
+ public Date getSegmentStartTime() {
+ return segmentStartTime;
+ }
+
+ public void setSegmentStartTime(Date segmentStartTime) {
+ this.segmentStartTime = segmentStartTime;
+ }
+
+ public Date getSegmentEndTime() {
+ return segmentEndTime;
+ }
+
+ public void setSegmentEndTime(Date segmentEndTime) {
+ this.segmentEndTime = segmentEndTime;
+ }
+
+ public BigDecimal getStartLongitude() {
+ return startLongitude;
+ }
+
+ public void setStartLongitude(BigDecimal startLongitude) {
+ this.startLongitude = startLongitude;
+ }
+
+ public BigDecimal getStartLatitude() {
+ return startLatitude;
+ }
+
+ public void setStartLatitude(BigDecimal startLatitude) {
+ this.startLatitude = startLatitude;
+ }
+
+ public BigDecimal getEndLongitude() {
+ return endLongitude;
+ }
+
+ public void setEndLongitude(BigDecimal endLongitude) {
+ this.endLongitude = endLongitude;
+ }
+
+ public BigDecimal getEndLatitude() {
+ return endLatitude;
+ }
+
+ public void setEndLatitude(BigDecimal endLatitude) {
+ this.endLatitude = endLatitude;
+ }
+
+ public BigDecimal getSegmentDistance() {
+ return segmentDistance;
+ }
+
+ public void setSegmentDistance(BigDecimal segmentDistance) {
+ this.segmentDistance = segmentDistance;
+ }
+
+ public Integer getGpsPointCount() {
+ return gpsPointCount;
+ }
+
+ public void setGpsPointCount(Integer gpsPointCount) {
+ this.gpsPointCount = gpsPointCount;
+ }
+
+ public String getGpsIds() {
+ return gpsIds;
+ }
+
+ public void setGpsIds(String gpsIds) {
+ this.gpsIds = gpsIds;
+ }
+
+ public Long getTaskId() {
+ return taskId;
+ }
+
+ public void setTaskId(Long taskId) {
+ this.taskId = taskId;
+ }
+
+ public String getTaskCode() {
+ return taskCode;
+ }
+
+ public void setTaskCode(String taskCode) {
+ this.taskCode = taskCode;
+ }
+
+ public String getCalculateMethod() {
+ return calculateMethod;
+ }
+
+ public void setCalculateMethod(String calculateMethod) {
+ this.calculateMethod = calculateMethod;
+ }
+}
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/domain/VehicleInfo.java b/ruoyi-system/src/main/java/com/ruoyi/system/domain/VehicleInfo.java
index 5871080..66fa440 100644
--- a/ruoyi-system/src/main/java/com/ruoyi/system/domain/VehicleInfo.java
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/domain/VehicleInfo.java
@@ -6,11 +6,12 @@
import com.ruoyi.common.core.domain.BaseEntity;
import java.util.List;
+import java.io.Serializable;
/**
* 杞﹁締淇℃伅瀵硅薄 tb_vehicle_info
*/
-public class VehicleInfo extends BaseEntity {
+public class VehicleInfo extends BaseEntity implements Serializable {
private static final long serialVersionUID = 1L;
/** 杞﹁締ID */
@@ -187,4 +188,16 @@
.append("remark", getRemark())
.toString();
}
-}
\ No newline at end of file
+
+ /**
+ * 鍒濆鍖栧欢杩熷姞杞界殑灞炴�э紝閬垮厤搴忓垪鍖栭棶棰�
+ */
+ public void initializeLazyProperties() {
+ if (this.deptIds != null) {
+ this.deptIds.size(); // 瑙﹀彂鍔犺浇
+ }
+ if (this.deptNames != null) {
+ this.deptNames.size(); // 瑙﹀彂鍔犺浇
+ }
+ }
+}
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/domain/VehicleMileageStats.java b/ruoyi-system/src/main/java/com/ruoyi/system/domain/VehicleMileageStats.java
index a1fe49d..1c3bf1b 100644
--- a/ruoyi-system/src/main/java/com/ruoyi/system/domain/VehicleMileageStats.java
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/domain/VehicleMileageStats.java
@@ -23,6 +23,13 @@
@Excel(name = "杞︾墝鍙�")
private String vehicleNo;
+ /** 褰掑睘鍒嗗叕鍙� */
+ @Excel(name = "褰掑睘鍒嗗叕鍙�")
+ private String deptName;
+
+ /** 鍒嗗叕鍙窱D锛堢敤浜庢煡璇紝涓嶅鍑猴級 */
+ private Long deptId;
+
/** 缁熻鏃ユ湡 */
@JsonFormat(pattern = "yyyy-MM-dd")
@Excel(name = "缁熻鏃ユ湡", width = 30, dateFormat = "yyyy-MM-dd")
@@ -52,6 +59,13 @@
@Excel(name = "浠诲姟鏁伴噺")
private Integer taskCount;
+ /** 鍏宠仈鐨勫垎娈垫暟閲� */
+ @Excel(name = "鍒嗘鏁伴噺")
+ private Integer segmentCount;
+
+ /** 鏁版嵁鏉ユ簮锛坰egment-浠庡垎娈垫眹鎬伙紝gps-鐩存帴璁$畻锛� */
+ private String dataSource;
+
public Long getStatsId() {
return statsId;
}
@@ -74,6 +88,22 @@
public void setVehicleNo(String vehicleNo) {
this.vehicleNo = vehicleNo;
+ }
+
+ public String getDeptName() {
+ return deptName;
+ }
+
+ public void setDeptName(String deptName) {
+ this.deptName = deptName;
+ }
+
+ public Long getDeptId() {
+ return deptId;
+ }
+
+ public void setDeptId(Long deptId) {
+ this.deptId = deptId;
}
public Date getStatDate() {
@@ -131,4 +161,20 @@
public void setTaskCount(Integer taskCount) {
this.taskCount = taskCount;
}
+
+ public Integer getSegmentCount() {
+ return segmentCount;
+ }
+
+ public void setSegmentCount(Integer segmentCount) {
+ this.segmentCount = segmentCount;
+ }
+
+ public String getDataSource() {
+ return dataSource;
+ }
+
+ public void setDataSource(String dataSource) {
+ this.dataSource = dataSource;
+ }
}
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/mapper/VehicleGpsMapper.java b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/VehicleGpsMapper.java
index b1cae08..4f7ba6b 100644
--- a/ruoyi-system/src/main/java/com/ruoyi/system/mapper/VehicleGpsMapper.java
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/VehicleGpsMapper.java
@@ -69,4 +69,16 @@
* @return 杞﹁締ID鍒楄〃
*/
public List<Long> selectActiveVehicleIds();
+
+ /**
+ * 鏌ヨ鏈璁$畻鐨凣PS鍧愭爣锛堜笉鍦╰b_vehicle_gps_calculated琛ㄤ腑鐨勮褰曪級
+ *
+ * @param vehicleId 杞﹁締ID
+ * @param startTime 寮�濮嬫椂闂�
+ * @param endTime 缁撴潫鏃堕棿
+ * @return 鏈璁$畻鐨凣PS鍧愭爣鍒楄〃
+ */
+ public List<VehicleGps> selectUncalculatedGps(@Param("vehicleId") Long vehicleId,
+ @Param("startTime") Date startTime,
+ @Param("endTime") Date endTime);
}
\ No newline at end of file
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/mapper/VehicleGpsSegmentMileageMapper.java b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/VehicleGpsSegmentMileageMapper.java
new file mode 100644
index 0000000..57a4b75
--- /dev/null
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/VehicleGpsSegmentMileageMapper.java
@@ -0,0 +1,78 @@
+package com.ruoyi.system.mapper;
+
+import java.math.BigDecimal;
+import java.util.Date;
+import java.util.List;
+import org.apache.ibatis.annotations.Param;
+import com.ruoyi.system.domain.VehicleGpsSegmentMileage;
+
+/**
+ * 杞﹁締GPS鍒嗘閲岀▼Mapper鎺ュ彛
+ */
+public interface VehicleGpsSegmentMileageMapper {
+
+ /**
+ * 鏌ヨ杞﹁締GPS鍒嗘閲岀▼
+ */
+ public VehicleGpsSegmentMileage selectVehicleGpsSegmentMileageById(Long segmentId);
+
+ /**
+ * 鏌ヨ杞﹁締GPS鍒嗘閲岀▼鍒楄〃
+ */
+ public List<VehicleGpsSegmentMileage> selectVehicleGpsSegmentMileageList(VehicleGpsSegmentMileage vehicleGpsSegmentMileage);
+
+ /**
+ * 鏂板杞﹁締GPS鍒嗘閲岀▼
+ */
+ public int insertVehicleGpsSegmentMileage(VehicleGpsSegmentMileage vehicleGpsSegmentMileage);
+
+ /**
+ * 淇敼杞﹁締GPS鍒嗘閲岀▼
+ */
+ public int updateVehicleGpsSegmentMileage(VehicleGpsSegmentMileage vehicleGpsSegmentMileage);
+
+ /**
+ * 鍒犻櫎杞﹁締GPS鍒嗘閲岀▼
+ */
+ public int deleteVehicleGpsSegmentMileageById(Long segmentId);
+
+ /**
+ * 鎵归噺鍒犻櫎杞﹁締GPS鍒嗘閲岀▼
+ */
+ public int deleteVehicleGpsSegmentMileageByIds(Long[] segmentIds);
+
+ /**
+ * 鏌ヨ杞﹁締鍦ㄦ寚瀹氭椂闂存鏄惁宸插瓨鍦ㄥ垎娈甸噷绋嬭褰�
+ */
+ public VehicleGpsSegmentMileage selectByVehicleIdAndTime(@Param("vehicleId") Long vehicleId,
+ @Param("segmentStartTime") Date segmentStartTime);
+
+ /**
+ * 鏌ヨ杞﹁締鍦ㄦ寚瀹氭棩鏈熻寖鍥村唴鐨勫垎娈甸噷绋嬬粺璁�
+ */
+ public List<VehicleGpsSegmentMileage> selectSegmentsByDateRange(@Param("vehicleId") Long vehicleId,
+ @Param("startDate") Date startDate,
+ @Param("endDate") Date endDate);
+
+ /**
+ * 鎸変换鍔D鏌ヨ鍒嗘閲岀▼鍒楄〃
+ */
+ public List<VehicleGpsSegmentMileage> selectSegmentsByTaskId(@Param("taskId") Long taskId);
+
+ /**
+ * 鏌ヨ浠诲姟鐨勬�婚噷绋嬶紙鐩存帴姹傚拰锛�
+ */
+ public BigDecimal selectTotalMileageByTaskId(@Param("taskId") Long taskId);
+
+ /**
+ * 璁板綍GPS鐐瑰凡琚绠楋紙鎻掑叆鍒皌b_vehicle_gps_calculated琛級
+ */
+ public int insertGpsCalculated(@Param("gpsId") Long gpsId,
+ @Param("segmentId") Long segmentId,
+ @Param("vehicleId") Long vehicleId);
+
+ /**
+ * 妫�鏌PS鐐规槸鍚﹀凡琚绠�
+ */
+ public Long selectGpsCalculatedSegmentId(@Param("gpsId") Long gpsId);
+}
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/mapper/VehicleInfoMapper.java b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/VehicleInfoMapper.java
index 1a5f016..e1c37ff 100644
--- a/ruoyi-system/src/main/java/com/ruoyi/system/mapper/VehicleInfoMapper.java
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/VehicleInfoMapper.java
@@ -10,6 +10,22 @@
*/
public interface VehicleInfoMapper {
/**
+ * 鏌ヨ杞﹁締淇℃伅锛堝寘鍚鍒嗗叕鍙稿叧鑱旓級
+ *
+ * @param vehicleId 杞﹁締淇℃伅涓婚敭
+ * @return 杞﹁締淇℃伅锛堝寘鍚玠eptIds鍜宒eptNames锛�
+ */
+ public VehicleInfo selectVehicleInfoWithDeptsById(Long vehicleId);
+
+ /**
+ * 鏌ヨ杞﹁締淇℃伅鍒楄〃锛堝寘鍚鍒嗗叕鍙稿叧鑱旓級
+ *
+ * @param vehicleInfo 杞﹁締淇℃伅
+ * @return 杞﹁締淇℃伅闆嗗悎锛堝寘鍚玠eptIds鍜宒eptNames锛�
+ */
+ public List<VehicleInfo> selectVehicleInfoListWithDepts(VehicleInfo vehicleInfo);
+
+ /**
* 鏌ヨ杞﹁締淇℃伅
*
* @param vehicleId 杞﹁締淇℃伅涓婚敭
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/IVehicleGpsSegmentMileageService.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/IVehicleGpsSegmentMileageService.java
new file mode 100644
index 0000000..6251a4e
--- /dev/null
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/IVehicleGpsSegmentMileageService.java
@@ -0,0 +1,69 @@
+package com.ruoyi.system.service;
+
+import java.util.Date;
+import java.util.List;
+import com.ruoyi.system.domain.VehicleGpsSegmentMileage;
+
+/**
+ * 杞﹁締GPS鍒嗘閲岀▼Service鎺ュ彛
+ */
+public interface IVehicleGpsSegmentMileageService {
+
+ /**
+ * 鏌ヨ杞﹁締GPS鍒嗘閲岀▼
+ */
+ public VehicleGpsSegmentMileage selectVehicleGpsSegmentMileageById(Long segmentId);
+
+ /**
+ * 鏌ヨ杞﹁締GPS鍒嗘閲岀▼鍒楄〃
+ */
+ public List<VehicleGpsSegmentMileage> selectVehicleGpsSegmentMileageList(VehicleGpsSegmentMileage vehicleGpsSegmentMileage);
+
+ /**
+ * 鏂板杞﹁締GPS鍒嗘閲岀▼
+ */
+ public int insertVehicleGpsSegmentMileage(VehicleGpsSegmentMileage vehicleGpsSegmentMileage);
+
+ /**
+ * 淇敼杞﹁締GPS鍒嗘閲岀▼
+ */
+ public int updateVehicleGpsSegmentMileage(VehicleGpsSegmentMileage vehicleGpsSegmentMileage);
+
+ /**
+ * 鎵归噺鍒犻櫎杞﹁締GPS鍒嗘閲岀▼
+ */
+ public int deleteVehicleGpsSegmentMileageByIds(Long[] segmentIds);
+
+ /**
+ * 鍒犻櫎杞﹁締GPS鍒嗘閲岀▼淇℃伅
+ */
+ public int deleteVehicleGpsSegmentMileageById(Long segmentId);
+
+ /**
+ * 鎵归噺璁$畻鎵�鏈夎溅杈嗙殑GPS鍒嗘閲岀▼
+ *
+ * @param startTime 寮�濮嬫椂闂�
+ * @param endTime 缁撴潫鏃堕棿
+ * @return 鎴愬姛璁$畻鐨勮溅杈嗘暟閲�
+ */
+ public int batchCalculateSegmentMileage(Date startTime, Date endTime);
+
+ /**
+ * 璁$畻鍗曚釜杞﹁締鐨凣PS鍒嗘閲岀▼
+ *
+ * @param vehicleId 杞﹁締ID
+ * @param startTime 寮�濮嬫椂闂�
+ * @param endTime 缁撴潫鏃堕棿
+ * @return 鎴愬姛璁$畻鐨勫垎娈垫暟閲�
+ */
+ public int calculateVehicleSegmentMileage(Long vehicleId, Date startTime, Date endTime);
+
+ /**
+ * 琛ュ伩璁$畻锛氭煡鎵惧苟璁$畻鏈澶勭悊鐨凣PS鏁版嵁
+ * 鐢ㄤ簬鏈嶅姟閲嶅惎鍚庣殑鏁版嵁淇
+ *
+ * @param lookbackDays 鍥炴函澶╂暟锛堟煡璇㈠灏戝ぉ鍓嶇殑鏁版嵁锛�
+ * @return 鎴愬姛璁$畻鐨勮溅杈嗘暟閲�
+ */
+ public int compensateCalculation(int lookbackDays);
+}
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/IVehicleInfoService.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/IVehicleInfoService.java
index ca4db11..32be3be 100644
--- a/ruoyi-system/src/main/java/com/ruoyi/system/service/IVehicleInfoService.java
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/IVehicleInfoService.java
@@ -32,6 +32,14 @@
public List<VehicleInfo> selectVehicleInfoList(VehicleInfo vehicleInfo);
/**
+ * 鏌ヨ杞﹁締淇℃伅锛堝寘鍚鍒嗗叕鍙稿叧鑱旓級
+ *
+ * @param vehicleId 杞﹁締淇℃伅涓婚敭
+ * @return 杞﹁締淇℃伅锛堝寘鍚玠eptIds鍜宒eptNames锛�
+ */
+ public VehicleInfo selectVehicleInfoWithDeptsById(Long vehicleId);
+
+ /**
* 鏂板杞﹁締淇℃伅
*
* @param vehicleInfo 杞﹁締淇℃伅
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/IVehicleMileageStatsService.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/IVehicleMileageStatsService.java
index c030849..56a9243 100644
--- a/ruoyi-system/src/main/java/com/ruoyi/system/service/IVehicleMileageStatsService.java
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/IVehicleMileageStatsService.java
@@ -73,4 +73,21 @@
* @return 鎴愬姛缁熻鐨勮溅杈嗘暟閲�
*/
public int batchCalculateMileageStats(Date statDate);
+
+ /**
+ * 浠庡垎娈甸噷绋嬫暟鎹眹鎬荤敓鎴愭寜鏃ョ粺璁�
+ *
+ * @param vehicleId 杞﹁締ID
+ * @param statDate 缁熻鏃ユ湡
+ * @return 缁熻缁撴灉
+ */
+ public VehicleMileageStats aggregateFromSegmentMileage(Long vehicleId, Date statDate);
+
+ /**
+ * 鎵归噺浠庡垎娈甸噷绋嬫眹鎬荤敓鎴愭寜鏃ョ粺璁�
+ *
+ * @param statDate 缁熻鏃ユ湡
+ * @return 鎴愬姛缁熻鐨勮溅杈嗘暟閲�
+ */
+ public int batchAggregateFromSegmentMileage(Date statDate);
}
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/GpsCollectServiceImpl.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/GpsCollectServiceImpl.java
index 42e8390..a936fc2 100644
--- a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/GpsCollectServiceImpl.java
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/GpsCollectServiceImpl.java
@@ -5,6 +5,7 @@
import com.ruoyi.system.service.IGpsCollectService;
import com.ruoyi.system.config.GpsServiceConfig;
import com.ruoyi.common.utils.MD5Util;
+import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import org.springframework.http.HttpEntity;
@@ -30,6 +31,7 @@
/**
* GPS閲囬泦鏈嶅姟瀹炵幇
*/
+@Slf4j
@Service
public class GpsCollectServiceImpl implements IGpsCollectService {
@@ -430,7 +432,7 @@
// 瑙f瀽浣嶇疆璁板綍鍒楄〃
JSONArray recordsArray = jsonResult.getJSONArray("records");
List<GpsLastPosition> records = new ArrayList<>();
-
+ log.info("recordsArray length:{}",recordsArray.size());
for (int i = 0; i < recordsArray.size(); i++) {
JSONObject recordJson = recordsArray.getJSONObject(i);
GpsLastPosition record = new GpsLastPosition();
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/VehicleGpsSegmentMileageServiceImpl.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/VehicleGpsSegmentMileageServiceImpl.java
new file mode 100644
index 0000000..45fe76f
--- /dev/null
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/VehicleGpsSegmentMileageServiceImpl.java
@@ -0,0 +1,658 @@
+package com.ruoyi.system.service.impl;
+
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.util.*;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import com.alibaba.fastjson2.JSON;
+import com.alibaba.fastjson2.JSONArray;
+import com.alibaba.fastjson2.JSONObject;
+import com.ruoyi.common.config.TiandituMapConfig;
+import com.ruoyi.common.core.redis.RedisCache;
+import com.ruoyi.common.utils.http.HttpUtils;
+import com.ruoyi.system.domain.VehicleGps;
+import com.ruoyi.system.domain.VehicleGpsSegmentMileage;
+import com.ruoyi.system.domain.SysTask;
+import com.ruoyi.system.domain.VehicleInfo;
+import com.ruoyi.system.mapper.VehicleGpsMapper;
+import com.ruoyi.system.mapper.VehicleGpsSegmentMileageMapper;
+import com.ruoyi.system.mapper.SysTaskMapper;
+import com.ruoyi.system.mapper.VehicleInfoMapper;
+import com.ruoyi.system.service.IVehicleGpsSegmentMileageService;
+import com.ruoyi.system.service.IVehicleMileageStatsService;
+import com.ruoyi.system.service.ISysConfigService;
+
+/**
+ * 杞﹁締GPS鍒嗘閲岀▼Service涓氬姟灞傚鐞�
+ */
+@Service
+public class VehicleGpsSegmentMileageServiceImpl implements IVehicleGpsSegmentMileageService {
+
+ private static final Logger logger = LoggerFactory.getLogger(VehicleGpsSegmentMileageServiceImpl.class);
+
+ /** 鍦扮悆鍗婂緞锛堝叕閲岋級 */
+ private static final double EARTH_RADIUS_KM = 6371.0;
+
+ /** 澶╁湴鍥炬壒閲忚矾寰勮鍒扐PI */
+ private static final String TIANDITU_ROUTE_API = "http://api.tianditu.gov.cn/drive";
+
+ @Autowired
+ private VehicleGpsSegmentMileageMapper segmentMileageMapper;
+
+ @Autowired
+ private VehicleGpsMapper vehicleGpsMapper;
+
+ @Autowired
+ private SysTaskMapper sysTaskMapper;
+
+ @Autowired
+ private TiandituMapConfig tiandituMapConfig;
+
+ @Autowired
+ private ISysConfigService configService;
+
+ @Autowired
+ private RedisCache redisCache;
+
+ @Autowired
+ private IVehicleMileageStatsService mileageStatsService;
+
+ @Autowired
+ private VehicleInfoMapper vehicleInfoMapper;
+
+ @Override
+ public VehicleGpsSegmentMileage selectVehicleGpsSegmentMileageById(Long segmentId) {
+ return segmentMileageMapper.selectVehicleGpsSegmentMileageById(segmentId);
+ }
+
+ @Override
+ public List<VehicleGpsSegmentMileage> selectVehicleGpsSegmentMileageList(VehicleGpsSegmentMileage vehicleGpsSegmentMileage) {
+ return segmentMileageMapper.selectVehicleGpsSegmentMileageList(vehicleGpsSegmentMileage);
+ }
+
+ @Override
+ public int insertVehicleGpsSegmentMileage(VehicleGpsSegmentMileage vehicleGpsSegmentMileage) {
+ return segmentMileageMapper.insertVehicleGpsSegmentMileage(vehicleGpsSegmentMileage);
+ }
+
+ @Override
+ public int updateVehicleGpsSegmentMileage(VehicleGpsSegmentMileage vehicleGpsSegmentMileage) {
+ return segmentMileageMapper.updateVehicleGpsSegmentMileage(vehicleGpsSegmentMileage);
+ }
+
+ @Override
+ public int deleteVehicleGpsSegmentMileageByIds(Long[] segmentIds) {
+ return segmentMileageMapper.deleteVehicleGpsSegmentMileageByIds(segmentIds);
+ }
+
+ @Override
+ public int deleteVehicleGpsSegmentMileageById(Long segmentId) {
+ return segmentMileageMapper.deleteVehicleGpsSegmentMileageById(segmentId);
+ }
+
+ @Override
+ public int batchCalculateSegmentMileage(Date startTime, Date endTime) {
+ try {
+ // 鏌ヨ鎵�鏈夋椿璺冭溅杈�
+ List<Long> vehicleIds = vehicleGpsMapper.selectActiveVehicleIds();
+
+ if (vehicleIds == null || vehicleIds.isEmpty()) {
+ logger.info("娌℃湁鎵惧埌娲昏穬杞﹁締");
+ return 0;
+ }
+
+ int successCount = 0;
+ for (Long vehicleId : vehicleIds) {
+ try {
+ int segmentCount = calculateVehicleSegmentMileage(vehicleId, startTime, endTime);
+ if (segmentCount > 0) {
+ successCount++;
+ }
+ } catch (Exception e) {
+ logger.error("璁$畻杞﹁締 {} 鐨勫垎娈甸噷绋嬪け璐�", vehicleId, e);
+ }
+ }
+
+ logger.info("鎵归噺鍒嗘閲岀▼璁$畻瀹屾垚 - 鏃堕棿鑼冨洿: {} 鍒� {}, 鎬昏溅杈嗘暟: {}, 鎴愬姛: {}",
+ startTime, endTime, vehicleIds.size(), successCount);
+ return successCount;
+
+ } catch (Exception e) {
+ logger.error("鎵归噺璁$畻鍒嗘閲岀▼澶辫触", e);
+ throw new RuntimeException("鎵归噺璁$畻澶辫触: " + e.getMessage());
+ }
+ }
+
+ @Override
+ public int compensateCalculation(int lookbackDays) {
+ try {
+ // 璁$畻鏃堕棿鑼冨洿
+ Calendar cal = Calendar.getInstance();
+ Date endTime = cal.getTime();
+ cal.add(Calendar.DAY_OF_MONTH, -lookbackDays);
+ Date startTime = cal.getTime();
+
+ logger.info("寮�濮嬭ˉ鍋胯绠� - 鍥炴函澶╂暟: {}, 鏃堕棿鑼冨洿: {} 鍒� {}", lookbackDays, startTime, endTime);
+
+ // 鏌ヨ鎵�鏈夋椿璺冭溅杈�
+ List<Long> vehicleIds = vehicleGpsMapper.selectActiveVehicleIds();
+
+ if (vehicleIds == null || vehicleIds.isEmpty()) {
+ logger.info("娌℃湁鎵惧埌娲昏穬杞﹁締");
+ return 0;
+ }
+
+ int successCount = 0;
+ int totalUncalculated = 0;
+
+ for (Long vehicleId : vehicleIds) {
+ try {
+ // 鏌ヨ璇ヨ溅杈嗘湭琚绠楃殑GPS鏁版嵁
+ List<VehicleGps> uncalculatedGps = vehicleGpsMapper.selectUncalculatedGps(vehicleId, startTime, endTime);
+
+ if (uncalculatedGps == null || uncalculatedGps.isEmpty()) {
+ logger.debug("杞﹁締 {} 娌℃湁鏈绠楃殑GPS鏁版嵁", vehicleId);
+ continue;
+ }
+
+ totalUncalculated += uncalculatedGps.size();
+ logger.info("杞﹁締 {} 鍙戠幇 {} 涓湭璁$畻鐨凣PS鐐癸紝寮�濮嬭ˉ鍋胯绠�...",
+ vehicleId, uncalculatedGps.size());
+
+ // 閲嶆柊璁$畻璇ヨ溅杈嗗湪璇ユ椂闂磋寖鍥寸殑鍒嗘閲岀▼
+ // 娉ㄦ剰锛氳繖閲屼細閲嶆柊璁$畻鏁翠釜鏃堕棿鑼冨洿锛岀‘淇濊竟缂樿妭鐐硅姝g‘澶勭悊
+ int segmentCount = calculateVehicleSegmentMileage(vehicleId, startTime, endTime);
+
+ if (segmentCount > 0) {
+ successCount++;
+ logger.info("杞﹁締 {} 琛ュ伩璁$畻瀹屾垚锛岀敓鎴� {} 涓垎娈佃褰�", vehicleId, segmentCount);
+ }
+ } catch (Exception e) {
+ logger.error("杞﹁締 {} 琛ュ伩璁$畻澶辫触", vehicleId, e);
+ }
+ }
+
+ logger.info("琛ュ伩璁$畻瀹屾垚 - 鎬昏溅杈嗘暟: {}, 鏈绠桮PS鐐规暟: {}, 鎴愬姛杞﹁締鏁�: {}",
+ vehicleIds.size(), totalUncalculated, successCount);
+ return successCount;
+
+ } catch (Exception e) {
+ logger.error("琛ュ伩璁$畻澶辫触", e);
+ throw new RuntimeException("琛ュ伩璁$畻澶辫触: " + e.getMessage());
+ }
+ }
+
+ @Override
+ public int calculateVehicleSegmentMileage(Long vehicleId, Date startTime, Date endTime) {
+ try {
+ // 鑾峰彇閰嶇疆鐨勬椂闂撮棿闅旓紙鍒嗛挓锛�
+ int segmentMinutes = configService.selectConfigByKey("gps.mileage.segment.minutes") != null
+ ? Integer.parseInt(configService.selectConfigByKey("gps.mileage.segment.minutes"))
+ : 5;
+
+ // 鑾峰彇璁$畻鏂瑰紡閰嶇疆
+ String calculateMethod = configService.selectConfigByKey("gps.mileage.calculate.method");
+ if (calculateMethod == null || calculateMethod.isEmpty()) {
+ calculateMethod = "tianditu";
+ }
+
+ // 鑾峰彇鏄惁璺宠繃宸茶绠桮PS鐐圭殑閰嶇疆
+ String skipCalculatedConfig = configService.selectConfigByKey("gps.mileage.skip.calculated");
+ boolean skipCalculated = skipCalculatedConfig == null || "true".equalsIgnoreCase(skipCalculatedConfig);
+
+ // 鏌ヨ杞﹁締鍦ㄦ椂闂磋寖鍥村唴鐨凣PS鏁版嵁
+ List<VehicleGps> gpsList = vehicleGpsMapper.selectGpsDataByTimeRange(vehicleId, startTime, endTime);
+
+ if (gpsList == null || gpsList.isEmpty()) {
+ logger.debug("杞﹁締ID: {} 鍦ㄦ椂闂磋寖鍥� {} 鍒� {} 鍐呮棤GPS鏁版嵁", vehicleId, startTime, endTime);
+ return 0;
+ }
+
+ logger.info("杞﹁締ID: {} 鏌ヨ鍒� {} 鏉PS鏁版嵁", vehicleId, gpsList.size());
+
+ // 鎸夋椂闂存鍒嗙粍GPS鏁版嵁
+ Map<Date, List<VehicleGps>> segmentedData = segmentGpsDataByTime(gpsList, segmentMinutes);
+
+ int savedCount = 0;
+ VehicleGps previousSegmentLastPoint = null; // 璁板綍涓婁竴涓椂闂存鐨勬渶鍚庝竴涓偣
+
+ // 閬嶅巻姣忎釜鏃堕棿娈碉紝璁$畻閲岀▼
+ for (Map.Entry<Date, List<VehicleGps>> entry : segmentedData.entrySet()) {
+ Date segmentStartTime = entry.getKey();
+ List<VehicleGps> segmentGpsList = entry.getValue();
+
+ if (segmentGpsList.size() < 2) {
+ // 濡傛灉鏈鍙湁1涓偣锛屼絾鏈変笂涓�娈电殑鏈�鍚庝竴涓偣锛屼粛鍙绠楄法娈佃窛绂�
+ if (segmentGpsList.size() == 1 && previousSegmentLastPoint != null) {
+ // 淇濈暀褰撳墠鐐逛綔涓轰笅涓�娈电殑鍓嶇疆鐐癸紝浣嗕笉鍒涘缓璁板綍
+ previousSegmentLastPoint = segmentGpsList.get(0);
+ }
+ continue; // 鑷冲皯闇�瑕�2涓偣鎵嶈兘璁$畻璺濈
+ }
+
+ // 妫�鏌ユ槸鍚﹀凡瀛樺湪璇ユ椂闂存鐨勮褰�
+ VehicleGpsSegmentMileage existing = segmentMileageMapper.selectByVehicleIdAndTime(vehicleId, segmentStartTime);
+ if (existing != null) {
+ logger.debug("杞﹁締 {} 鏃堕棿娈� {} 鐨勫垎娈甸噷绋嬪凡瀛樺湪锛岃烦杩�", vehicleId, segmentStartTime);
+ // 鏇存柊涓婁竴娈垫渶鍚庝竴涓偣
+ previousSegmentLastPoint = segmentGpsList.get(segmentGpsList.size() - 1);
+ continue;
+ }
+
+ // 璁$畻鏃堕棿娈电殑缁撴潫鏃堕棿
+ Calendar cal = Calendar.getInstance();
+ cal.setTime(segmentStartTime);
+ cal.add(Calendar.MINUTE, segmentMinutes);
+ Date segmentEndTime = cal.getTime();
+
+ // 璁$畻璇ユ椂闂存鐨勯噷绋嬶紙鍖呮嫭璺ㄦ璺濈锛�
+ BigDecimal distance = calculateSegmentDistanceWithGap(segmentGpsList, calculateMethod, previousSegmentLastPoint);
+
+ // 鏀堕泦GPS ID鍒楄〃锛堝寘鎷笂涓�娈电殑鏈�鍚庝竴涓偣锛屽洜涓鸿法娈甸棿闅欒窛绂讳篃鐢ㄥ埌浜嗗畠锛�
+ List<Long> gpsIdList = new ArrayList<>();
+ // 濡傛灉鏈変笂涓�娈电殑鏈�鍚庝竴涓偣锛屽厛娣诲姞瀹冪殑ID
+ if (previousSegmentLastPoint != null && previousSegmentLastPoint.getGpsId() != null) {
+ gpsIdList.add(previousSegmentLastPoint.getGpsId());
+ }
+ // 鍐嶆坊鍔犲綋鍓嶆鐨勬墍鏈塆PS鐐笽D
+ for (VehicleGps gps : segmentGpsList) {
+ if (gps.getGpsId() != null) {
+ gpsIdList.add(gps.getGpsId());
+ }
+ }
+ String gpsIds = gpsIdList.stream()
+ .map(String::valueOf)
+ .collect(java.util.stream.Collectors.joining(","));
+
+ // 鍒涘缓鍒嗘閲岀▼璁板綍
+ VehicleGpsSegmentMileage segment = new VehicleGpsSegmentMileage();
+ segment.setVehicleId(vehicleId);
+
+ // 浠嶨PS鏁版嵁鎴栬溅杈嗚〃鑾峰彇杞︾墝鍙�
+ String vehicleNo = segmentGpsList.get(0).getVehicleNo();
+ if (vehicleNo == null || vehicleNo.trim().isEmpty()) {
+ // GPS鏁版嵁涓病鏈夎溅鐗屽彿锛屼粠杞﹁締琛ㄦ煡璇�
+ VehicleInfo vehicleInfo = vehicleInfoMapper.selectVehicleInfoById(vehicleId);
+ if (vehicleInfo != null) {
+ vehicleNo = vehicleInfo.getVehicleNo();
+ }
+ }
+ segment.setVehicleNo(vehicleNo);
+
+ segment.setSegmentStartTime(segmentStartTime);
+ segment.setSegmentEndTime(segmentEndTime);
+
+ // 璧风偣鍧愭爣
+ VehicleGps firstPoint = segmentGpsList.get(0);
+ segment.setStartLongitude(BigDecimal.valueOf(firstPoint.getLongitude()));
+ segment.setStartLatitude(BigDecimal.valueOf(firstPoint.getLatitude()));
+
+ // 缁堢偣鍧愭爣
+ VehicleGps lastPoint = segmentGpsList.get(segmentGpsList.size() - 1);
+ segment.setEndLongitude(BigDecimal.valueOf(lastPoint.getLongitude()));
+ segment.setEndLatitude(BigDecimal.valueOf(lastPoint.getLatitude()));
+
+ segment.setSegmentDistance(distance);
+ segment.setGpsPointCount(gpsIdList.size()); // GPS鐐规暟锛氬寘鎷竟缂樼偣 + 褰撳墠娈电殑鐐�
+ segment.setGpsIds(gpsIds); // 璁剧疆GPS ID鍒楄〃
+ segment.setCalculateMethod(calculateMethod);
+
+ // 鏌ヨ骞跺叧鑱旀鍦ㄦ墽琛岀殑浠诲姟
+ associateActiveTask(segment, vehicleId, segmentStartTime, segmentEndTime);
+
+ // 淇濆瓨鍒版暟鎹簱
+ segmentMileageMapper.insertVehicleGpsSegmentMileage(segment);
+
+ // 鏇存柊涓婁竴娈垫渶鍚庝竴涓偣锛屼緵涓嬩竴娈典娇鐢�
+ previousSegmentLastPoint = segmentGpsList.get(segmentGpsList.size() - 1);
+
+ // 璁板綍宸茶绠楃殑GPS鐐瑰埌鐘舵�佽〃锛堝鏋滃紑鍚簡閲嶅璁$畻鎺у埗锛�
+ if (skipCalculated && segment.getSegmentId() != null) {
+ for (Long gpsId : gpsIdList) {
+ try {
+ segmentMileageMapper.insertGpsCalculated(gpsId, segment.getSegmentId(), vehicleId);
+ } catch (Exception e) {
+ // 蹇界暐閲嶅閿紓甯革紝缁х画澶勭悊
+ logger.debug("璁板綍GPS璁$畻鐘舵�佸け璐ワ紝鍙兘宸插瓨鍦�: gpsId={}", gpsId);
+ }
+ }
+ }
+
+ savedCount++;
+
+ logger.debug("杞﹁締 {} 鏃堕棿娈� {} 鍒� {} 閲岀▼: {}km, GPS鐐规暟: {}, GPS IDs: {}",
+ vehicleId, segmentStartTime, segmentEndTime, distance, segmentGpsList.size(),
+ gpsIds.length() > 50 ? gpsIds.substring(0, 50) + "..." : gpsIds);
+ }
+
+ logger.info("杞﹁締 {} 璁$畻瀹屾垚锛屼繚瀛樹簡 {} 涓椂闂存鐨勯噷绋嬫暟鎹�", vehicleId, savedCount);
+
+ // 鑷姩瑙﹀彂姹囨�荤敓鎴愭瘡鏃ョ粺璁★紙濡傛灉鏈夋暟鎹淇濆瓨锛�
+ if (savedCount > 0) {
+ try {
+ // 鑾峰彇娑夊強鐨勬棩鏈熻寖鍥达紝瑙﹀彂姹囨��
+ Set<Date> affectedDates = new HashSet<>();
+ Calendar cal = Calendar.getInstance();
+
+ for (Map.Entry<Date, List<VehicleGps>> entry : segmentedData.entrySet()) {
+ cal.setTime(entry.getKey());
+ cal.set(Calendar.HOUR_OF_DAY, 0);
+ cal.set(Calendar.MINUTE, 0);
+ cal.set(Calendar.SECOND, 0);
+ cal.set(Calendar.MILLISECOND, 0);
+ affectedDates.add(cal.getTime());
+ }
+
+ // 瀵规瘡涓秹鍙婄殑鏃ユ湡锛岃Е鍙戞眹鎬�
+ for (Date statDate : affectedDates) {
+ try {
+ mileageStatsService.aggregateFromSegmentMileage(vehicleId, statDate);
+ logger.info("杞﹁締 {} 鏃ユ湡 {} 鐨勭粺璁℃暟鎹凡鑷姩姹囨�荤敓鎴�", vehicleId, statDate);
+ } catch (Exception e) {
+ logger.error("杞﹁締 {} 鏃ユ湡 {} 鑷姩姹囨�荤粺璁″け璐�", vehicleId, statDate, e);
+ }
+ }
+ } catch (Exception e) {
+ logger.error("瑙﹀彂鑷姩姹囨�诲け璐�", e);
+ }
+ }
+
+ return savedCount;
+
+ } catch (Exception e) {
+ logger.error("璁$畻杞﹁締 {} 鍒嗘閲岀▼澶辫触", vehicleId, e);
+ throw new RuntimeException("璁$畻鍒嗘閲岀▼澶辫触: " + e.getMessage());
+ }
+ }
+
+ /**
+ * 灏咷PS鏁版嵁鎸夋椂闂存鍒嗙粍
+ */
+ private Map<Date, List<VehicleGps>> segmentGpsDataByTime(List<VehicleGps> gpsList, int segmentMinutes) {
+ Map<Date, List<VehicleGps>> segmentedData = new LinkedHashMap<>();
+
+ for (VehicleGps gps : gpsList) {
+ // 瑙f瀽GPS閲囬泦鏃堕棿
+ Date collectTime = parseDateTime(gps.getCollectTime());
+
+ // 璁$畻璇PS鐐规墍灞炵殑鏃堕棿娈佃捣濮嬫椂闂达紙鍚戜笅鍙栨暣鍒版渶杩戠殑鏃堕棿娈碉級
+ Date segmentStart = getSegmentStartTime(collectTime, segmentMinutes);
+
+ // 娣诲姞鍒板搴旀椂闂存
+ segmentedData.computeIfAbsent(segmentStart, k -> new ArrayList<>()).add(gps);
+ }
+
+ return segmentedData;
+ }
+
+ /**
+ * 鑾峰彇鏃堕棿娈电殑璧峰鏃堕棿锛堝悜涓嬪彇鏁达級
+ */
+ private Date getSegmentStartTime(Date time, int segmentMinutes) {
+ Calendar cal = Calendar.getInstance();
+ cal.setTime(time);
+
+ // 灏嗗垎閽熸暟鍚戜笅鍙栨暣鍒版渶杩戠殑鍒嗘
+ int minute = cal.get(Calendar.MINUTE);
+ int segmentIndex = minute / segmentMinutes;
+ int alignedMinute = segmentIndex * segmentMinutes;
+
+ cal.set(Calendar.MINUTE, alignedMinute);
+ cal.set(Calendar.SECOND, 0);
+ cal.set(Calendar.MILLISECOND, 0);
+
+ return cal.getTime();
+ }
+
+ /**
+ * 璁$畻涓�涓椂闂存鍐呯殑鎬婚噷绋嬶紙鍖呮嫭涓庝笂涓�娈电殑闂撮殭璺濈锛�
+ * @param gpsList 褰撳墠鏃堕棿娈电殑GPS鐐瑰垪琛�
+ * @param calculateMethod 璁$畻鏂瑰紡
+ * @param previousLastPoint 涓婁竴涓椂闂存鐨勬渶鍚庝竴涓偣锛堝彲涓簄ull锛�
+ */
+ private BigDecimal calculateSegmentDistanceWithGap(List<VehicleGps> gpsList, String calculateMethod, VehicleGps previousLastPoint) {
+ if (gpsList == null || gpsList.size() < 2) {
+ return BigDecimal.ZERO;
+ }
+
+ BigDecimal totalDistance = BigDecimal.ZERO;
+
+ // 1. 鍏堣绠楄法娈甸棿闅欒窛绂伙紙涓婁竴娈垫渶鍚庝竴涓偣 -> 褰撳墠娈电涓�涓偣锛�
+ if (previousLastPoint != null) {
+ VehicleGps currentFirstPoint = gpsList.get(0);
+ double gapDistance = calculateHaversineDistance(
+ previousLastPoint.getLatitude().doubleValue(),
+ previousLastPoint.getLongitude().doubleValue(),
+ currentFirstPoint.getLatitude().doubleValue(),
+ currentFirstPoint.getLongitude().doubleValue()
+ );
+ totalDistance = totalDistance.add(BigDecimal.valueOf(gapDistance));
+
+ logger.debug("璺ㄦ闂撮殭璺濈: {}km (涓婁竴娈垫湯鐐� -> 褰撳墠娈甸鐐�)",
+ String.format("%.3f", gapDistance));
+ }
+
+ // 2. 鍐嶈绠楀綋鍓嶆鍐呴儴鐨勮窛绂�
+ BigDecimal segmentInternalDistance;
+ if ("tianditu".equalsIgnoreCase(calculateMethod)) {
+ segmentInternalDistance = calculateDistanceByTianditu(gpsList);
+ } else {
+ segmentInternalDistance = calculateDistanceByHaversine(gpsList);
+ }
+ totalDistance = totalDistance.add(segmentInternalDistance);
+
+ return totalDistance.setScale(3, RoundingMode.HALF_UP);
+ }
+
+ /**
+ * 璁$畻涓�涓椂闂存鍐呯殑鎬婚噷绋嬶紙浠呮鍐呰窛绂伙級
+ */
+ private BigDecimal calculateSegmentDistance(List<VehicleGps> gpsList, String calculateMethod) {
+ if (gpsList == null || gpsList.size() < 2) {
+ return BigDecimal.ZERO;
+ }
+
+ BigDecimal totalDistance = BigDecimal.ZERO;
+
+ if ("tianditu".equalsIgnoreCase(calculateMethod)) {
+ // 浣跨敤澶╁湴鍥続PI璁$畻锛堟壒閲忚绠楁洿绮剧‘锛�
+ totalDistance = calculateDistanceByTianditu(gpsList);
+ } else {
+ // 浣跨敤Haversine鍏紡璁$畻锛堢洿绾胯窛绂伙紝鏇村揩浣嗕笉澶熺簿纭級
+ totalDistance = calculateDistanceByHaversine(gpsList);
+ }
+
+ return totalDistance.setScale(3, RoundingMode.HALF_UP);
+ }
+
+ /**
+ * 浣跨敤澶╁湴鍥続PI璁$畻璺濈
+ */
+ private BigDecimal calculateDistanceByTianditu(List<VehicleGps> gpsList) {
+ try {
+ // 澶╁湴鍥捐矾寰勮鍒扐PI鏈夌偣鏁伴檺鍒讹紝濡傛灉鐐瑰お澶氶渶瑕佸垎鎵瑰鐞�
+ int maxPointsPerRequest = 50; // 澶╁湴鍥続PI寤鸿涓嶈秴杩�50涓偣
+ BigDecimal totalDistance = BigDecimal.ZERO;
+
+ // 濡傛灉GPS鐐规暟杈冨皯锛岀洿鎺ヤ娇鐢℉aversine鍏紡锛堥伩鍏嶉绻佽皟鐢ˋPI锛�
+ if (gpsList.size() <= 3) {
+ return calculateDistanceByHaversine(gpsList);
+ }
+
+ // 鍒嗘壒澶勭悊
+ for (int i = 0; i < gpsList.size() - 1; i += maxPointsPerRequest) {
+ int endIndex = Math.min(i + maxPointsPerRequest, gpsList.size());
+ List<VehicleGps> batchList = gpsList.subList(i, endIndex);
+
+ BigDecimal batchDistance = calculateBatchDistanceByTianditu(batchList);
+ totalDistance = totalDistance.add(batchDistance);
+ }
+
+ return totalDistance;
+
+ } catch (Exception e) {
+ logger.warn("澶╁湴鍥続PI璁$畻璺濈澶辫触锛岄檷绾т娇鐢℉aversine鍏紡: {}", e.getMessage());
+ return calculateDistanceByHaversine(gpsList);
+ }
+ }
+
+ /**
+ * 浣跨敤澶╁湴鍥続PI璁$畻涓�鎵笹PS鐐圭殑璺濈
+ */
+ private BigDecimal calculateBatchDistanceByTianditu(List<VehicleGps> gpsList) {
+ try {
+ // 绠�鍖栧鐞嗭細璁$畻鐩搁偦鐐逛箣闂寸殑鐩寸嚎璺濈鎬诲拰
+ // 娉細澶╁湴鍥剧殑璺緞瑙勫垝API涓昏鐢ㄤ簬瀵艰埅锛岃繖閲岀敤绠�鍖栫殑璺濈璁$畻
+ BigDecimal totalDistance = BigDecimal.ZERO;
+
+ for (int i = 0; i < gpsList.size() - 1; i++) {
+ VehicleGps p1 = gpsList.get(i);
+ VehicleGps p2 = gpsList.get(i + 1);
+
+ double distance = calculateHaversineDistance(
+ p1.getLatitude().doubleValue(),
+ p1.getLongitude().doubleValue(),
+ p2.getLatitude().doubleValue(),
+ p2.getLongitude().doubleValue()
+ );
+
+ totalDistance = totalDistance.add(BigDecimal.valueOf(distance));
+ }
+
+ return totalDistance;
+
+ } catch (Exception e) {
+ logger.error("澶╁湴鍥炬壒閲忚窛绂昏绠楀け璐�", e);
+ throw e;
+ }
+ }
+
+ /**
+ * 浣跨敤Haversine鍏紡璁$畻璺濈
+ */
+ private BigDecimal calculateDistanceByHaversine(List<VehicleGps> gpsList) {
+ BigDecimal totalDistance = BigDecimal.ZERO;
+
+ for (int i = 0; i < gpsList.size() - 1; i++) {
+ VehicleGps p1 = gpsList.get(i);
+ VehicleGps p2 = gpsList.get(i + 1);
+
+ double distance = calculateHaversineDistance(
+ p1.getLatitude().doubleValue(),
+ p1.getLongitude().doubleValue(),
+ p2.getLatitude().doubleValue(),
+ p2.getLongitude().doubleValue()
+ );
+
+ totalDistance = totalDistance.add(BigDecimal.valueOf(distance));
+ }
+
+ return totalDistance;
+ }
+
+ /**
+ * 浣跨敤Haversine鍏紡璁$畻涓ょ偣涔嬮棿鐨勮窛绂伙紙鍏噷锛�
+ */
+ private double calculateHaversineDistance(double lat1, double lon1, double lat2, double lon2) {
+ // 濡傛灉璧风偣鍜岀粓鐐圭粡绾害鐩稿悓锛岀洿鎺ヨ繑鍥�0锛岄伩鍏嶄笉蹇呰鐨勮绠�
+ if (lat1 == lat2 && lon1 == lon2) {
+ return 0.0;
+ }
+
+ // 灏嗚搴﹁浆鎹负寮у害
+ double dLat = Math.toRadians(lat2 - lat1);
+ double dLon = Math.toRadians(lon2 - lon1);
+ double rLat1 = Math.toRadians(lat1);
+ double rLat2 = Math.toRadians(lat2);
+
+ // Haversine鍏紡
+ double a = Math.sin(dLat / 2) * Math.sin(dLat / 2) +
+ Math.cos(rLat1) * Math.cos(rLat2) *
+ Math.sin(dLon / 2) * Math.sin(dLon / 2);
+
+ double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
+
+ return EARTH_RADIUS_KM * c;
+ }
+
+ /**
+ * 瑙f瀽鏃ユ湡鏃堕棿瀛楃涓�
+ */
+ private Date parseDateTime(String dateTimeStr) {
+ if (dateTimeStr == null || dateTimeStr.trim().isEmpty()) {
+ throw new RuntimeException("鏃ユ湡鏃堕棿瀛楃涓蹭笉鑳戒负绌�");
+ }
+
+ try {
+ java.text.SimpleDateFormat sdf = new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+ sdf.setLenient(false);
+ return sdf.parse(dateTimeStr.trim());
+ } catch (Exception e) {
+ throw new RuntimeException("鏃ユ湡鏃堕棿鏍煎紡閿欒: " + dateTimeStr + ", 搴斾负 yyyy-MM-dd HH:mm:ss", e);
+ }
+ }
+
+ /**
+ * 鏌ヨ骞跺叧鑱旇溅杈嗘鍦ㄦ墽琛岀殑浠诲姟
+ * @param segment 鍒嗘閲岀▼璁板綍
+ * @param vehicleId 杞﹁締ID
+ * @param segmentStartTime 鏃堕棿娈靛紑濮嬫椂闂�
+ * @param segmentEndTime 鏃堕棿娈电粨鏉熸椂闂�
+ */
+ private void associateActiveTask(VehicleGpsSegmentMileage segment, Long vehicleId,
+ Date segmentStartTime, Date segmentEndTime) {
+ try {
+ // 鏌ヨ璇ヨ溅杈嗘鍦ㄦ墽琛岀殑浠诲姟鍒楄〃
+ List<SysTask> activeTasks = sysTaskMapper.selectActiveTasksByVehicleId(vehicleId);
+
+ if (activeTasks == null || activeTasks.isEmpty()) {
+ logger.debug("杞﹁締 {} 鍦ㄦ椂闂存 {} - {} 娌℃湁姝e湪鎵ц鐨勪换鍔�", vehicleId, segmentStartTime, segmentEndTime);
+ return;
+ }
+
+ // 閬嶅巻浠诲姟锛屾煡鎵句笌褰撳墠鏃堕棿娈垫湁閲嶅彔鐨勪换鍔�
+ for (SysTask task : activeTasks) {
+ // 鑾峰彇浠诲姟鐨勫疄闄呮墽琛屾椂闂达紝濡傛灉娌℃湁瀹為檯鏃堕棿鍒欎娇鐢ㄨ鍒掓椂闂�
+ Date taskStart = task.getActualStartTime() != null ? task.getActualStartTime() : task.getPlannedStartTime();
+ Date taskEnd = task.getActualEndTime() != null ? task.getActualEndTime() : task.getPlannedEndTime();
+
+ // 鍒ゆ柇鏃堕棿娈垫槸鍚︽湁閲嶅彔
+ if (isTimeOverlap(segmentStartTime, segmentEndTime, taskStart, taskEnd)) {
+ // 鍏宠仈浠诲姟ID鍜屼换鍔$紪鍙�
+ segment.setTaskId(task.getTaskId());
+ segment.setTaskCode(task.getTaskCode());
+
+ logger.debug("杞﹁締 {} 鏃堕棿娈� {} - {} 鍏宠仈浠诲姟: taskId={}, taskCode={}",
+ vehicleId, segmentStartTime, segmentEndTime, task.getTaskId(), task.getTaskCode());
+ break; // 鎵惧埌涓�涓尮閰嶇殑浠诲姟鍗冲彲
+ }
+ }
+
+ } catch (Exception e) {
+ // 鍏宠仈浠诲姟澶辫触涓嶅奖鍝嶄富娴佺▼锛屽彧璁板綍鏃ュ織
+ logger.warn("鍏宠仈杞﹁締 {} 鐨勪换鍔′俊鎭け璐�", vehicleId, e);
+ }
+ }
+
+ /**
+ * 鍒ゆ柇涓や釜鏃堕棿娈垫槸鍚︽湁閲嶅彔
+ * @param start1 鏃堕棿娈�1寮�濮�
+ * @param end1 鏃堕棿娈�1缁撴潫
+ * @param start2 鏃堕棿娈�2寮�濮�
+ * @param end2 鏃堕棿娈�2缁撴潫
+ * @return true-鏈夐噸鍙�, false-鏃犻噸鍙�
+ */
+ private boolean isTimeOverlap(Date start1, Date end1, Date start2, Date end2) {
+ // 浠讳綍鏃堕棿涓簄ull锛岃繑鍥瀎alse
+ if (start1 == null || end1 == null || start2 == null || end2 == null) {
+ return false;
+ }
+
+ // 涓や釜鏃堕棿娈垫湁閲嶅彔鐨勬潯浠讹細
+ // start1 < end2 && end1 > start2
+ return start1.before(end2) && end1.after(start2);
+ }
+}
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/VehicleInfoServiceImpl.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/VehicleInfoServiceImpl.java
index 1b4a58d..9f6a373 100644
--- a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/VehicleInfoServiceImpl.java
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/VehicleInfoServiceImpl.java
@@ -44,7 +44,23 @@
*/
@Override
public VehicleInfo selectVehicleInfoById(Long vehicleId) {
- return vehicleInfoMapper.selectVehicleInfoById(vehicleId);
+ return vehicleInfoMapper.selectVehicleInfoWithDeptsById(vehicleId);
+ }
+
+ /**
+ * 鏌ヨ杞﹁締淇℃伅锛堝寘鍚鍒嗗叕鍙稿叧鑱旓級
+ *
+ * @param vehicleId 杞﹁締淇℃伅涓婚敭
+ * @return 杞﹁締淇℃伅锛堝寘鍚玠eptIds鍜宒eptNames锛�
+ */
+ @Override
+ public VehicleInfo selectVehicleInfoWithDeptsById(Long vehicleId) {
+ VehicleInfo vehicle = vehicleInfoMapper.selectVehicleInfoWithDeptsById(vehicleId);
+ // 鍒濆鍖栧欢杩熷姞杞界殑灞炴�э紝閬垮厤搴忓垪鍖栭棶棰�
+ if (vehicle != null) {
+ vehicle.initializeLazyProperties();
+ }
+ return vehicle;
}
/**
@@ -66,7 +82,12 @@
*/
@Override
public List<VehicleInfo> selectVehicleInfoList(VehicleInfo vehicleInfo) {
- return vehicleInfoMapper.selectVehicleInfoList(vehicleInfo);
+ List<VehicleInfo> list = vehicleInfoMapper.selectVehicleInfoListWithDepts(vehicleInfo);
+ // 鍒濆鍖栧欢杩熷姞杞界殑灞炴�э紝閬垮厤搴忓垪鍖栭棶棰�
+ for (VehicleInfo vehicle : list) {
+ vehicle.initializeLazyProperties();
+ }
+ return list;
}
/**
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/VehicleMileageStatsServiceImpl.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/VehicleMileageStatsServiceImpl.java
index 4bd7aa3..3cf88f0 100644
--- a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/VehicleMileageStatsServiceImpl.java
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/VehicleMileageStatsServiceImpl.java
@@ -2,6 +2,8 @@
import java.math.BigDecimal;
import java.math.RoundingMode;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
@@ -11,9 +13,13 @@
import org.springframework.stereotype.Service;
import com.ruoyi.system.domain.VehicleGps;
import com.ruoyi.system.domain.VehicleMileageStats;
+import com.ruoyi.system.domain.VehicleGpsSegmentMileage;
import com.ruoyi.system.domain.TaskTimeInterval;
+import com.ruoyi.system.domain.VehicleInfo;
import com.ruoyi.system.mapper.VehicleGpsMapper;
import com.ruoyi.system.mapper.VehicleMileageStatsMapper;
+import com.ruoyi.system.mapper.VehicleGpsSegmentMileageMapper;
+import com.ruoyi.system.mapper.VehicleInfoMapper;
import com.ruoyi.system.service.IVehicleMileageStatsService;
/**
@@ -32,6 +38,12 @@
@Autowired
private VehicleGpsMapper vehicleGpsMapper;
+
+ @Autowired
+ private VehicleGpsSegmentMileageMapper segmentMileageMapper;
+
+ @Autowired
+ private VehicleInfoMapper vehicleInfoMapper;
/**
* 鏌ヨ杞﹁締閲岀▼缁熻
@@ -122,10 +134,18 @@
stats.setVehicleId(vehicleId);
stats.setStatDate(statDate);
- // 鑾峰彇杞︾墝鍙�
+ // 鑾峰彇杞︾墝鍙凤細浼樺厛浠嶨PS鏁版嵁锛屽鏋滄病鏈夊垯浠庤溅杈嗚〃鏌ヨ
+ String vehicleNo = null;
if (!gpsList.isEmpty() && gpsList.get(0).getVehicleNo() != null) {
- stats.setVehicleNo(gpsList.get(0).getVehicleNo());
+ vehicleNo = gpsList.get(0).getVehicleNo();
}
+ if (vehicleNo == null || vehicleNo.trim().isEmpty()) {
+ VehicleInfo vehicleInfo = vehicleInfoMapper.selectVehicleInfoById(vehicleId);
+ if (vehicleInfo != null) {
+ vehicleNo = vehicleInfo.getVehicleNo();
+ }
+ }
+ stats.setVehicleNo(vehicleNo);
}
// 6. 璁剧疆缁熻鏁版嵁
@@ -208,8 +228,8 @@
);
// 鑾峰彇杩欐璺濈鐨勬椂闂村尯闂�
- Date segmentStart = p1.getCollectTime();
- Date segmentEnd = p2.getCollectTime();
+ Date segmentStart = parseDateTime(p1.getCollectTime());
+ Date segmentEnd = parseDateTime(p2.getCollectTime());
// 璁$畻杩欐璺濈鍦ㄤ换鍔℃椂娈电殑鍗犳瘮
double taskRatio = calculateTaskOverlapRatio(segmentStart, segmentEnd, taskIntervals);
@@ -240,6 +260,11 @@
* 浣跨敤Haversine鍏紡璁$畻涓や釜GPS鍧愭爣涔嬮棿鐨勮窛绂伙紙鍏噷锛�
*/
private double calculateDistance(double lat1, double lon1, double lat2, double lon2) {
+ // 濡傛灉璧风偣鍜岀粓鐐圭粡绾害鐩稿悓锛岀洿鎺ヨ繑鍥�0锛岄伩鍏嶄笉蹇呰鐨勮绠�
+ if (lat1 == lat2 && lon1 == lon2) {
+ return 0.0;
+ }
+
// 灏嗚搴﹁浆鎹负寮у害
double dLat = Math.toRadians(lat2 - lat1);
double dLon = Math.toRadians(lon2 - lon1);
@@ -293,4 +318,176 @@
BigDecimal nonTaskMileage = BigDecimal.ZERO;
BigDecimal taskRatio = BigDecimal.ZERO;
}
+
+ /**
+ * 瑙f瀽鏃ユ湡鏃堕棿瀛楃涓�
+ *
+ * @param dateTimeStr 鏃ユ湡鏃堕棿瀛楃涓诧紝鏍煎紡锛歽yyy-MM-dd HH:mm:ss
+ * @return Date瀵硅薄
+ * @throws RuntimeException 濡傛灉瑙f瀽澶辫触
+ */
+ private Date parseDateTime(String dateTimeStr) {
+ if (dateTimeStr == null || dateTimeStr.trim().isEmpty()) {
+ throw new RuntimeException("鏃ユ湡鏃堕棿瀛楃涓蹭笉鑳戒负绌�");
+ }
+
+ try {
+ SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+ sdf.setLenient(false);
+ return sdf.parse(dateTimeStr.trim());
+ } catch (ParseException e) {
+ throw new RuntimeException("鏃ユ湡鏃堕棿鏍煎紡閿欒: " + dateTimeStr + ", 搴斾负 yyyy-MM-dd HH:mm:ss", e);
+ }
+ }
+
+ /**
+ * 浠庡垎娈甸噷绋嬫暟鎹眹鎬荤敓鎴愭寜鏃ョ粺璁�
+ */
+ @Override
+ public VehicleMileageStats aggregateFromSegmentMileage(Long vehicleId, Date statDate) {
+ try {
+ // 1. 鑾峰彇缁熻鏃ユ湡鐨勫紑濮嬪拰缁撴潫鏃堕棿
+ Calendar calendar = Calendar.getInstance();
+ calendar.setTime(statDate);
+ calendar.set(Calendar.HOUR_OF_DAY, 0);
+ calendar.set(Calendar.MINUTE, 0);
+ calendar.set(Calendar.SECOND, 0);
+ calendar.set(Calendar.MILLISECOND, 0);
+ Date dayStart = calendar.getTime();
+
+ calendar.add(Calendar.DAY_OF_MONTH, 1);
+ Date dayEnd = calendar.getTime();
+
+ // 2. 鏌ヨ璇ユ棩鏈熻寖鍥村唴鐨勬墍鏈夊垎娈甸噷绋嬫暟鎹�
+ List<VehicleGpsSegmentMileage> segments = segmentMileageMapper.selectSegmentsByDateRange(vehicleId, dayStart, dayEnd);
+
+ if (segments == null || segments.isEmpty()) {
+ logger.info("杞﹁締ID: {} 鍦ㄦ棩鏈�: {} 鏃犲垎娈甸噷绋嬫暟鎹�", vehicleId, statDate);
+ return null;
+ }
+
+ // 3. 姹囨�婚噷绋嬫暟鎹�
+ BigDecimal totalMileage = BigDecimal.ZERO;
+ int totalGpsPoints = 0;
+
+ for (VehicleGpsSegmentMileage segment : segments) {
+ if (segment.getSegmentDistance() != null) {
+ totalMileage = totalMileage.add(segment.getSegmentDistance());
+ }
+ if (segment.getGpsPointCount() != null) {
+ totalGpsPoints += segment.getGpsPointCount();
+ }
+ }
+
+ // 4. 鏌ヨ璇ユ棩鏈熺殑浠诲姟鏃堕棿鍖洪棿锛岃绠椾换鍔¢噷绋嬪拰闈炰换鍔¢噷绋�
+ List<TaskTimeInterval> taskIntervals = vehicleMileageStatsMapper.selectTaskTimeIntervals(vehicleId, dayStart, dayEnd);
+
+ BigDecimal taskMileage = BigDecimal.ZERO;
+ BigDecimal nonTaskMileage = BigDecimal.ZERO;
+
+ for (VehicleGpsSegmentMileage segment : segments) {
+ Date segStart = segment.getSegmentStartTime();
+ Date segEnd = segment.getSegmentEndTime();
+ BigDecimal segDistance = segment.getSegmentDistance() != null ? segment.getSegmentDistance() : BigDecimal.ZERO;
+
+ // 璁$畻璇ュ垎娈典笌浠诲姟鏃舵鐨勯噸鍙犳瘮渚�
+ double taskRatio = calculateTaskOverlapRatio(segStart, segEnd, taskIntervals);
+
+ // 鍒嗘憞閲岀▼
+ BigDecimal taskDist = segDistance.multiply(BigDecimal.valueOf(taskRatio));
+ BigDecimal nonTaskDist = segDistance.multiply(BigDecimal.valueOf(1 - taskRatio));
+
+ taskMileage = taskMileage.add(taskDist);
+ nonTaskMileage = nonTaskMileage.add(nonTaskDist);
+ }
+
+ // 璁$畻浠诲姟閲岀▼鍗犳瘮
+ BigDecimal taskRatio = BigDecimal.ZERO;
+ if (totalMileage.compareTo(BigDecimal.ZERO) > 0) {
+ taskRatio = taskMileage.divide(totalMileage, 4, RoundingMode.HALF_UP);
+ }
+
+ // 5. 鏌ヨ鎴栧垱寤虹粺璁¤褰�
+ VehicleMileageStats stats = vehicleMileageStatsMapper.selectByVehicleIdAndDate(vehicleId, statDate);
+ boolean isNew = (stats == null);
+
+ if (isNew) {
+ stats = new VehicleMileageStats();
+ stats.setVehicleId(vehicleId);
+ stats.setStatDate(statDate);
+
+ // 鑾峰彇杞︾墝鍙凤細浼樺厛浠庡垎娈垫暟鎹紝濡傛灉娌℃湁鍒欎粠杞﹁締琛ㄦ煡璇�
+ String vehicleNo = null;
+ if (!segments.isEmpty() && segments.get(0).getVehicleNo() != null) {
+ vehicleNo = segments.get(0).getVehicleNo();
+ }
+ if (vehicleNo == null || vehicleNo.trim().isEmpty()) {
+ VehicleInfo vehicleInfo = vehicleInfoMapper.selectVehicleInfoById(vehicleId);
+ if (vehicleInfo != null) {
+ vehicleNo = vehicleInfo.getVehicleNo();
+ }
+ }
+ stats.setVehicleNo(vehicleNo);
+ }
+
+ // 6. 璁剧疆缁熻鏁版嵁
+ stats.setTotalMileage(totalMileage.setScale(2, RoundingMode.HALF_UP));
+ stats.setTaskMileage(taskMileage.setScale(2, RoundingMode.HALF_UP));
+ stats.setNonTaskMileage(nonTaskMileage.setScale(2, RoundingMode.HALF_UP));
+ stats.setTaskRatio(taskRatio);
+ stats.setGpsPointCount(totalGpsPoints);
+ stats.setTaskCount(taskIntervals == null ? 0 : taskIntervals.size());
+ stats.setSegmentCount(segments.size());
+ stats.setDataSource("segment"); // 鏍囪鏁版嵁鏉ユ簮涓哄垎娈垫眹鎬�
+
+ // 7. 淇濆瓨鍒版暟鎹簱
+ if (isNew) {
+ vehicleMileageStatsMapper.insertVehicleMileageStats(stats);
+ } else {
+ vehicleMileageStatsMapper.updateVehicleMileageStats(stats);
+ }
+
+ logger.info("杞﹁締ID: {} 鏃ユ湡: {} 浠庡垎娈垫眹鎬诲畬鎴� - 鎬婚噷绋�: {}km, 浠诲姟閲岀▼: {}km, 闈炰换鍔¢噷绋�: {}km, 鍒嗘鏁�: {}",
+ vehicleId, statDate, totalMileage, taskMileage, nonTaskMileage, segments.size());
+
+ return stats;
+
+ } catch (Exception e) {
+ logger.error("浠庡垎娈垫眹鎬婚噷绋嬬粺璁″け璐� - 杞﹁締ID: {}, 鏃ユ湡: {}", vehicleId, statDate, e);
+ throw new RuntimeException("姹囨�婚噷绋嬬粺璁″け璐�: " + e.getMessage());
+ }
+ }
+
+ /**
+ * 鎵归噺浠庡垎娈甸噷绋嬫眹鎬荤敓鎴愭寜鏃ョ粺璁�
+ */
+ @Override
+ public int batchAggregateFromSegmentMileage(Date statDate) {
+ try {
+ // 鏌ヨ鎵�鏈夋椿璺冭溅杈�
+ List<Long> vehicleIds = vehicleGpsMapper.selectActiveVehicleIds();
+
+ if (vehicleIds == null || vehicleIds.isEmpty()) {
+ logger.info("娌℃湁鎵惧埌娲昏穬杞﹁締");
+ return 0;
+ }
+
+ int successCount = 0;
+ for (Long vehicleId : vehicleIds) {
+ try {
+ aggregateFromSegmentMileage(vehicleId, statDate);
+ successCount++;
+ } catch (Exception e) {
+ logger.error("姹囨�昏溅杈� {} 鐨勯噷绋嬬粺璁″け璐�", vehicleId, e);
+ }
+ }
+
+ logger.info("鎵归噺閲岀▼姹囨�诲畬鎴� - 鏃ユ湡: {}, 鎬昏溅杈嗘暟: {}, 鎴愬姛: {}", statDate, vehicleIds.size(), successCount);
+ return successCount;
+
+ } catch (Exception e) {
+ logger.error("鎵归噺姹囨�婚噷绋嬬粺璁″け璐� - 鏃ユ湡: {}", statDate, e);
+ throw new RuntimeException("鎵归噺姹囨�诲け璐�: " + e.getMessage());
+ }
+ }
}
diff --git a/ruoyi-system/src/main/resources/mapper/system/VehicleGpsMapper.xml b/ruoyi-system/src/main/resources/mapper/system/VehicleGpsMapper.xml
index 5d8d093..3abe287 100644
--- a/ruoyi-system/src/main/resources/mapper/system/VehicleGpsMapper.xml
+++ b/ruoyi-system/src/main/resources/mapper/system/VehicleGpsMapper.xml
@@ -136,4 +136,19 @@
where collect_time >= DATE_SUB(NOW(), INTERVAL 7 DAY)
order by vehicle_id
</select>
+
+ <!-- 鏌ヨ鏈璁$畻鐨凣PS鍧愭爣锛堜笉鍦╰b_vehicle_gps_calculated琛ㄤ腑鐨勮褰曪級 -->
+ <select id="selectUncalculatedGps" resultMap="VehicleGpsResult">
+ SELECT g.gps_id, g.vehicle_id, g.device_id, g.longitude, g.latitude, g.altitude,
+ g.speed, g.direction, g.collect_time, g.device_report_time,
+ g.platform_process_time, g.create_time, v.vehicle_no
+ FROM tb_vehicle_gps g
+ LEFT JOIN tb_vehicle_info v ON g.vehicle_id = v.vehicle_id
+ LEFT JOIN tb_vehicle_gps_calculated c ON g.gps_id = c.gps_id
+ WHERE g.vehicle_id = #{vehicleId}
+ AND g.collect_time >= #{startTime}
+ AND g.collect_time <= #{endTime}
+ AND c.gps_id IS NULL -- 鏈璁$畻鐨凣PS鐐�
+ ORDER BY g.collect_time
+ </select>
</mapper>
\ No newline at end of file
diff --git a/ruoyi-system/src/main/resources/mapper/system/VehicleGpsSegmentMileageMapper.xml b/ruoyi-system/src/main/resources/mapper/system/VehicleGpsSegmentMileageMapper.xml
new file mode 100644
index 0000000..43b5fb3
--- /dev/null
+++ b/ruoyi-system/src/main/resources/mapper/system/VehicleGpsSegmentMileageMapper.xml
@@ -0,0 +1,157 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.ruoyi.system.mapper.VehicleGpsSegmentMileageMapper">
+
+ <resultMap type="VehicleGpsSegmentMileage" id="VehicleGpsSegmentMileageResult">
+ <result property="segmentId" column="segment_id" />
+ <result property="vehicleId" column="vehicle_id" />
+ <result property="vehicleNo" column="vehicle_no" />
+ <result property="segmentStartTime" column="segment_start_time" />
+ <result property="segmentEndTime" column="segment_end_time" />
+ <result property="startLongitude" column="start_longitude" />
+ <result property="startLatitude" column="start_latitude" />
+ <result property="endLongitude" column="end_longitude" />
+ <result property="endLatitude" column="end_latitude" />
+ <result property="segmentDistance" column="segment_distance" />
+ <result property="gpsPointCount" column="gps_point_count" />
+ <result property="gpsIds" column="gps_ids" />
+ <result property="taskId" column="task_id" />
+ <result property="taskCode" column="task_code" />
+ <result property="calculateMethod" column="calculate_method" />
+ <result property="createTime" column="create_time" />
+ <result property="updateTime" column="update_time" />
+ </resultMap>
+
+ <sql id="selectVehicleGpsSegmentMileageVo">
+ SELECT segment_id, vehicle_id, vehicle_no, segment_start_time, segment_end_time,
+ start_longitude, start_latitude, end_longitude, end_latitude,
+ segment_distance, gps_point_count, gps_ids, task_id, task_code,
+ calculate_method, create_time, update_time
+ FROM tb_vehicle_gps_segment_mileage
+ </sql>
+
+ <select id="selectVehicleGpsSegmentMileageList" parameterType="VehicleGpsSegmentMileage" resultMap="VehicleGpsSegmentMileageResult">
+ <include refid="selectVehicleGpsSegmentMileageVo"/>
+ <where>
+ <if test="vehicleId != null">
+ AND vehicle_id = #{vehicleId}
+ </if>
+ <if test="vehicleNo != null and vehicleNo != ''">
+ AND vehicle_no = #{vehicleNo}
+ </if>
+ <if test="params.beginTime != null and params.beginTime != ''">
+ AND segment_start_time >= #{params.beginTime}
+ </if>
+ <if test="params.endTime != null and params.endTime != ''">
+ AND segment_end_time <= #{params.endTime}
+ </if>
+ </where>
+ ORDER BY segment_start_time DESC
+ </select>
+
+ <select id="selectVehicleGpsSegmentMileageById" parameterType="Long" resultMap="VehicleGpsSegmentMileageResult">
+ <include refid="selectVehicleGpsSegmentMileageVo"/>
+ WHERE segment_id = #{segmentId}
+ </select>
+
+ <select id="selectByVehicleIdAndTime" resultMap="VehicleGpsSegmentMileageResult">
+ <include refid="selectVehicleGpsSegmentMileageVo"/>
+ WHERE vehicle_id = #{vehicleId} AND segment_start_time = #{segmentStartTime}
+ </select>
+
+ <select id="selectSegmentsByDateRange" resultMap="VehicleGpsSegmentMileageResult">
+ <include refid="selectVehicleGpsSegmentMileageVo"/>
+ WHERE vehicle_id = #{vehicleId}
+ AND segment_start_time >= #{startDate}
+ AND segment_end_time <= #{endDate}
+ ORDER BY segment_start_time
+ </select>
+
+ <!-- 鎸変换鍔D鏌ヨ鍒嗘閲岀▼鍒楄〃 -->
+ <select id="selectSegmentsByTaskId" resultMap="VehicleGpsSegmentMileageResult">
+ <include refid="selectVehicleGpsSegmentMileageVo"/>
+ WHERE task_id = #{taskId}
+ ORDER BY segment_start_time
+ </select>
+
+ <!-- 鏌ヨ浠诲姟鐨勬�婚噷绋嬶紙鐩存帴姹傚拰锛� -->
+ <select id="selectTotalMileageByTaskId" resultType="java.math.BigDecimal">
+ SELECT COALESCE(SUM(segment_distance), 0)
+ FROM tb_vehicle_gps_segment_mileage
+ WHERE task_id = #{taskId}
+ </select>
+
+ <insert id="insertVehicleGpsSegmentMileage" parameterType="VehicleGpsSegmentMileage">
+ INSERT INTO tb_vehicle_gps_segment_mileage
+ <trim prefix="(" suffix=")" suffixOverrides=",">
+ <if test="vehicleId != null">vehicle_id,</if>
+ <if test="vehicleNo != null">vehicle_no,</if>
+ <if test="segmentStartTime != null">segment_start_time,</if>
+ <if test="segmentEndTime != null">segment_end_time,</if>
+ <if test="startLongitude != null">start_longitude,</if>
+ <if test="startLatitude != null">start_latitude,</if>
+ <if test="endLongitude != null">end_longitude,</if>
+ <if test="endLatitude != null">end_latitude,</if>
+ <if test="segmentDistance != null">segment_distance,</if>
+ <if test="gpsPointCount != null">gps_point_count,</if>
+ <if test="gpsIds != null">gps_ids,</if>
+ <if test="taskId != null">task_id,</if>
+ <if test="taskCode != null">task_code,</if>
+ <if test="calculateMethod != null">calculate_method,</if>
+ </trim>
+ <trim prefix="values (" suffix=")" suffixOverrides=",">
+ <if test="vehicleId != null">#{vehicleId},</if>
+ <if test="vehicleNo != null">#{vehicleNo},</if>
+ <if test="segmentStartTime != null">#{segmentStartTime},</if>
+ <if test="segmentEndTime != null">#{segmentEndTime},</if>
+ <if test="startLongitude != null">#{startLongitude},</if>
+ <if test="startLatitude != null">#{startLatitude},</if>
+ <if test="endLongitude != null">#{endLongitude},</if>
+ <if test="endLatitude != null">#{endLatitude},</if>
+ <if test="segmentDistance != null">#{segmentDistance},</if>
+ <if test="gpsPointCount != null">#{gpsPointCount},</if>
+ <if test="gpsIds != null">#{gpsIds},</if>
+ <if test="taskId != null">#{taskId},</if>
+ <if test="taskCode != null">#{taskCode},</if>
+ <if test="calculateMethod != null">#{calculateMethod},</if>
+ </trim>
+ </insert>
+
+ <update id="updateVehicleGpsSegmentMileage" parameterType="VehicleGpsSegmentMileage">
+ UPDATE tb_vehicle_gps_segment_mileage
+ <trim prefix="SET" suffixOverrides=",">
+ <if test="vehicleNo != null">vehicle_no = #{vehicleNo},</if>
+ <if test="segmentEndTime != null">segment_end_time = #{segmentEndTime},</if>
+ <if test="endLongitude != null">end_longitude = #{endLongitude},</if>
+ <if test="endLatitude != null">end_latitude = #{endLatitude},</if>
+ <if test="segmentDistance != null">segment_distance = #{segmentDistance},</if>
+ <if test="gpsPointCount != null">gps_point_count = #{gpsPointCount},</if>
+ <if test="calculateMethod != null">calculate_method = #{calculateMethod},</if>
+ </trim>
+ WHERE segment_id = #{segmentId}
+ </update>
+
+ <delete id="deleteVehicleGpsSegmentMileageById" parameterType="Long">
+ DELETE FROM tb_vehicle_gps_segment_mileage WHERE segment_id = #{segmentId}
+ </delete>
+
+ <delete id="deleteVehicleGpsSegmentMileageByIds" parameterType="String">
+ DELETE FROM tb_vehicle_gps_segment_mileage WHERE segment_id IN
+ <foreach item="segmentId" collection="array" open="(" separator="," close=")">
+ #{segmentId}
+ </foreach>
+ </delete>
+
+ <!-- 璁板綍GPS鐐瑰凡琚绠� -->
+ <insert id="insertGpsCalculated">
+ INSERT INTO tb_vehicle_gps_calculated (gps_id, segment_id, vehicle_id, create_time)
+ VALUES (#{gpsId}, #{segmentId}, #{vehicleId}, NOW())
+ </insert>
+
+ <!-- 妫�鏌PS鐐规槸鍚﹀凡琚绠� -->
+ <select id="selectGpsCalculatedSegmentId" resultType="Long">
+ SELECT segment_id FROM tb_vehicle_gps_calculated WHERE gps_id = #{gpsId} LIMIT 1
+ </select>
+</mapper>
diff --git a/ruoyi-system/src/main/resources/mapper/system/VehicleInfoMapper.xml b/ruoyi-system/src/main/resources/mapper/system/VehicleInfoMapper.xml
index 123a769..ad36af4 100644
--- a/ruoyi-system/src/main/resources/mapper/system/VehicleInfoMapper.xml
+++ b/ruoyi-system/src/main/resources/mapper/system/VehicleInfoMapper.xml
@@ -14,26 +14,30 @@
<result property="vehicleModel" column="vehicle_model" />
<result property="status" column="status" />
<result property="platformCode" column="platform_code" />
- <result property="deptId" column="dept_id" />
<result property="deptName" column="dept_name" />
<result property="createBy" column="create_by" />
<result property="createTime" column="create_time" />
<result property="updateBy" column="update_by" />
<result property="updateTime" column="update_time" />
<result property="remark" column="remark" />
- <!-- 澶氫釜鍒嗗叕鍙稿叧鑱� -->
+ </resultMap>
+
+ <!-- 鍖呭惈澶氬垎鍏徃鍏宠仈鐨勫畬鏁寸粨鏋滄槧灏勶紙浠呭湪闇�瑕佹椂浣跨敤锛� -->
+ <resultMap type="com.ruoyi.system.domain.VehicleInfo" id="VehicleInfoWithDeptsResult" extends="VehicleInfoResult">
+ <!-- 澶氫釜鍒嗗叕鍙稿叧鑱旓紙绔嬪嵆鍔犺浇锛岄伩鍏嶅欢杩熷姞杞藉鑷寸殑搴忓垪鍖栭棶棰橈級 -->
<collection property="deptIds" ofType="Long"
select="selectVehicleDeptIds"
- column="vehicle_id"/>
+ column="vehicle_id"
+ fetchType="eager"/>
<collection property="deptNames" ofType="String"
select="selectVehicleDeptNames"
- column="vehicle_id"/>
+ column="vehicle_id"
+ fetchType="eager"/>
</resultMap>
<sql id="selectVehicleInfoVo">
- select v.vehicle_id, v.car_id, v.device_id, v.vehicle_no, v.vehicle_type, v.vehicle_brand, v.vehicle_model, v.status, v.platform_code, v.dept_id, d.dept_name, v.create_by, v.create_time, v.update_by, v.update_time, v.remark
+ select v.vehicle_id, v.car_id, v.device_id, v.vehicle_no, v.vehicle_type, v.vehicle_brand, v.vehicle_model, v.status, v.platform_code, v.create_by, v.create_time, v.update_by, v.update_time, v.remark
from tb_vehicle_info v
- left join sys_dept d on v.dept_id = d.dept_id
</sql>
<!-- 鏌ヨ杞﹁締鍏宠仈鐨勬墍鏈夊垎鍏徃ID -->
@@ -49,10 +53,13 @@
WHERE vd.vehicle_id = #{vehicle_id}
</select>
- <select id="selectVehicleInfoList" parameterType="VehicleInfo" resultMap="VehicleInfoResult">
- <include refid="selectVehicleInfoVo"/>
+ <select id="selectVehicleInfoListWithDepts" parameterType="VehicleInfo" resultMap="VehicleInfoWithDeptsResult">
+ select v.vehicle_id, v.car_id, v.device_id, v.vehicle_no, v.vehicle_type, v.vehicle_brand,
+ v.vehicle_model, v.status, v.platform_code, v.create_by, v.create_time,
+ v.update_by, v.update_time, v.remark
+ from tb_vehicle_info v
<where>
- <if test="vehicleNo != null and vehicleNo != ''"> and v.vehicle_no = #{vehicleNo}</if>
+ <if test="vehicleNo != null and vehicleNo != ''"> and v.vehicle_no LIKE concat('%', #{vehicleNo}, '%')</if>
<if test="deviceId != null and deviceId != ''"> and v.device_id = #{deviceId}</if>
<if test="vehicleType != null and vehicleType != ''"> and v.vehicle_type = #{vehicleType}</if>
<if test="vehicleBrand != null and vehicleBrand != ''"> and v.vehicle_brand = #{vehicleBrand}</if>
@@ -62,32 +69,73 @@
<!-- 閮ㄩ棬杩囨护锛氭牴鎹垎鍏徃ID鏌ヨ杞﹁締锛堥�氳繃鍏宠仈琛級 -->
<if test="deptId != null">
and EXISTS (
- SELECT 1 FROM tb_vehicle_dept vd
- WHERE vd.vehicle_id = v.vehicle_id
- AND vd.dept_id = #{deptId}
+ SELECT 1 FROM tb_vehicle_dept vd2
+ WHERE vd2.vehicle_id = v.vehicle_id
+ AND vd2.dept_id = #{deptId}
)
</if>
- <!-- 浠诲姟杞﹁締閫夋嫨蹇呴』杩囨护锛氬彧鏄剧ずcar_id鍜宒ept_id閮戒笉涓虹┖鐨勮溅杈� -->
- and v.car_id is not null and v.car_id != ''
- and v.dept_id is not null
+ and v.status=0
+ </where>
+ group by v.vehicle_id, v.car_id, v.device_id, v.vehicle_no, v.vehicle_type, v.vehicle_brand,
+ v.vehicle_model, v.status, v.platform_code, v.create_by, v.create_time,
+ v.update_by, v.update_time, v.remark
+ order by v.create_time desc
+ </select>
+
+ <select id="selectVehicleInfoList" parameterType="VehicleInfo" resultMap="VehicleInfoResult">
+ select v.vehicle_id, v.car_id, v.device_id, v.vehicle_no, v.vehicle_type, v.vehicle_brand,
+ v.vehicle_model, v.status, v.platform_code, v.create_by, v.create_time,
+ v.update_by, v.update_time, v.remark,
+ GROUP_CONCAT(DISTINCT d.dept_name ORDER BY d.dept_name SEPARATOR ',') as dept_name
+ from tb_vehicle_info v
+ left join tb_vehicle_dept vd on v.vehicle_id = vd.vehicle_id
+ left join sys_dept d on vd.dept_id = d.dept_id
+ <where>
+ <if test="vehicleNo != null and vehicleNo != ''"> and v.vehicle_no LIKE concat('%', #{vehicleNo}, '%')</if>
+ <if test="deviceId != null and deviceId != ''"> and v.device_id = #{deviceId}</if>
+ <if test="vehicleType != null and vehicleType != ''"> and v.vehicle_type = #{vehicleType}</if>
+ <if test="vehicleBrand != null and vehicleBrand != ''"> and v.vehicle_brand = #{vehicleBrand}</if>
+ <if test="vehicleModel != null and vehicleModel != ''"> and v.vehicle_model = #{vehicleModel}</if>
+ <if test="status != null and status != ''"> and v.status = #{status}</if>
+ <if test="platformCode != null and platformCode != ''"> and v.platform_code = #{platformCode}</if>
+ <!-- 閮ㄩ棬杩囨护锛氭牴鎹垎鍏徃ID鏌ヨ杞﹁締锛堥�氳繃鍏宠仈琛級 -->
+ <if test="deptId != null">
+ and EXISTS (
+ SELECT 1 FROM tb_vehicle_dept vd2
+ WHERE vd2.vehicle_id = v.vehicle_id
+ AND vd2.dept_id = #{deptId}
+ )
+ </if>
+ <!-- 浠诲姟杞﹁締閫夋嫨蹇呴』杩囨护锛氬彧鏄剧ずcar_id涓嶄负绌轰笖宸插叧鑱斿垎鍏徃鐨勮溅杈� -->
+<!-- and v.car_id is not null and v.car_id != ''-->
+<!-- and EXISTS (SELECT 1 FROM tb_vehicle_dept vd WHERE vd.vehicle_id = v.vehicle_id)-->
and v.status=0
</where>
+ group by v.vehicle_id, v.car_id, v.device_id, v.vehicle_no, v.vehicle_type, v.vehicle_brand,
+ v.vehicle_model, v.status, v.platform_code, v.create_by, v.create_time,
+ v.update_by, v.update_time, v.remark
</select>
- <select id="selectVehicleInfoById" parameterType="Long" resultMap="VehicleInfoResult">
+ <select id="selectVehicleInfoById" parameterType="Long" resultMap="VehicleInfoWithDeptsResult">
+ <include refid="selectVehicleInfoVo"/>
+ where v.vehicle_id = #{vehicleId}
+ </select>
+
+ <!-- 鏌ヨ杞﹁締淇℃伅锛堝寘鍚鍒嗗叕鍙稿叧鑱旓級 -->
+ <select id="selectVehicleInfoWithDeptsById" parameterType="Long" resultMap="VehicleInfoWithDeptsResult">
<include refid="selectVehicleInfoVo"/>
where v.vehicle_id = #{vehicleId}
</select>
<select id="selectVehicleInfoByPlateNumber" parameterType="String" resultMap="VehicleInfoResult">
<include refid="selectVehicleInfoVo"/>
- where v.vehicle_no = #{plateNumber}
+ where v.vehicle_no LIKE concat('%', #{plateNumber}, '%')
</select>
<select id="selectVehicleInfoByVehicleNo" parameterType="String" resultMap="VehicleInfoResult">
<include refid="selectVehicleInfoVo"/>
- where v.vehicle_no = #{vehicleNo}
+ where v.vehicle_no LIKE concat('%', #{vehicleNo}, '%')
</select>
<insert id="insertVehicleInfo" parameterType="VehicleInfo" useGeneratedKeys="true" keyProperty="vehicleId">
@@ -101,7 +149,6 @@
<if test="vehicleModel != null">vehicle_model,</if>
<if test="status != null">status,</if>
<if test="platformCode != null">platform_code,</if>
- <if test="deptId != null">dept_id,</if>
<if test="createBy != null">create_by,</if>
<if test="createTime != null">create_time,</if>
<if test="updateBy != null">update_by,</if>
@@ -117,7 +164,6 @@
<if test="vehicleModel != null">#{vehicleModel},</if>
<if test="status != null">#{status},</if>
<if test="platformCode != null">#{platformCode},</if>
- <if test="deptId != null">#{deptId},</if>
<if test="createBy != null">#{createBy},</if>
<if test="createTime != null">#{createTime},</if>
<if test="updateBy != null">#{updateBy},</if>
@@ -137,7 +183,6 @@
<if test="vehicleModel != null">vehicle_model = #{vehicleModel},</if>
<if test="status != null">status = #{status},</if>
<if test="platformCode != null">platform_code = #{platformCode},</if>
- <if test="deptId != null">dept_id = #{deptId},</if>
<if test="updateBy != null">update_by = #{updateBy},</if>
<if test="updateTime != null">update_time = #{updateTime},</if>
<if test="remark != null">remark = #{remark},</if>
diff --git a/ruoyi-system/src/main/resources/mapper/system/VehicleMileageStatsMapper.xml b/ruoyi-system/src/main/resources/mapper/system/VehicleMileageStatsMapper.xml
index 95439e9..69ddb7e 100644
--- a/ruoyi-system/src/main/resources/mapper/system/VehicleMileageStatsMapper.xml
+++ b/ruoyi-system/src/main/resources/mapper/system/VehicleMileageStatsMapper.xml
@@ -8,6 +8,8 @@
<id property="statsId" column="stats_id" />
<result property="vehicleId" column="vehicle_id" />
<result property="vehicleNo" column="vehicle_no" />
+ <result property="deptName" column="dept_name" />
+ <result property="deptId" column="dept_id" />
<result property="statDate" column="stat_date" />
<result property="totalMileage" column="total_mileage" />
<result property="taskMileage" column="task_mileage" />
@@ -15,6 +17,8 @@
<result property="taskRatio" column="task_ratio" />
<result property="gpsPointCount" column="gps_point_count" />
<result property="taskCount" column="task_count" />
+ <result property="segmentCount" column="segment_count" />
+ <result property="dataSource" column="data_source" />
<result property="createTime" column="create_time" />
<result property="updateTime" column="update_time" />
</resultMap>
@@ -26,41 +30,59 @@
</resultMap>
<sql id="selectVehicleMileageStatsVo">
- select stats_id, vehicle_id, vehicle_no, stat_date, total_mileage, task_mileage,
- non_task_mileage, task_ratio, gps_point_count, task_count, create_time, update_time
- from tb_vehicle_mileage_stats
+ select s.stats_id, s.vehicle_id, s.vehicle_no, s.stat_date, s.total_mileage, s.task_mileage,
+ s.non_task_mileage, s.task_ratio, s.gps_point_count, s.task_count, s.segment_count,
+ s.data_source, s.create_time, s.update_time,
+ GROUP_CONCAT(DISTINCT d.dept_name ORDER BY d.dept_name SEPARATOR ',') as dept_name,
+ vd.dept_id
+ from tb_vehicle_mileage_stats s
+ left join tb_vehicle_info v on s.vehicle_id = v.vehicle_id
+ left join tb_vehicle_dept vd on v.vehicle_id = vd.vehicle_id
+ left join sys_dept d on vd.dept_id = d.dept_id
</sql>
<select id="selectVehicleMileageStatsList" parameterType="VehicleMileageStats" resultMap="VehicleMileageStatsResult">
<include refid="selectVehicleMileageStatsVo"/>
<where>
<if test="vehicleId != null">
- and vehicle_id = #{vehicleId}
+ and s.vehicle_id = #{vehicleId}
</if>
<if test="vehicleNo != null and vehicleNo != ''">
- and vehicle_no = #{vehicleNo}
+ and s.vehicle_no like concat('%', #{vehicleNo}, '%')
+ </if>
+ <if test="deptId != null">
+ and vd.dept_id = #{deptId}
</if>
<if test="statDate != null">
- and stat_date = #{statDate}
+ and s.stat_date = #{statDate}
</if>
<if test="params.beginStatDate != null and params.beginStatDate != ''">
- and stat_date >= #{params.beginStatDate}
+ and s.stat_date >= #{params.beginStatDate}
</if>
<if test="params.endStatDate != null and params.endStatDate != ''">
- and stat_date <= #{params.endStatDate}
+ and s.stat_date <= #{params.endStatDate}
</if>
</where>
- order by stat_date desc, vehicle_id
+ group by s.stats_id, s.vehicle_id, s.vehicle_no, s.stat_date, s.total_mileage, s.task_mileage,
+ s.non_task_mileage, s.task_ratio, s.gps_point_count, s.task_count, s.segment_count,
+ s.data_source, s.create_time, s.update_time, vd.dept_id
+ order by s.stat_date desc, s.vehicle_id
</select>
<select id="selectVehicleMileageStatsById" parameterType="Long" resultMap="VehicleMileageStatsResult">
<include refid="selectVehicleMileageStatsVo"/>
- where stats_id = #{statsId}
+ where s.stats_id = #{statsId}
+ group by s.stats_id, s.vehicle_id, s.vehicle_no, s.stat_date, s.total_mileage, s.task_mileage,
+ s.non_task_mileage, s.task_ratio, s.gps_point_count, s.task_count, s.segment_count,
+ s.data_source, s.create_time, s.update_time, vd.dept_id
</select>
<select id="selectByVehicleIdAndDate" resultMap="VehicleMileageStatsResult">
<include refid="selectVehicleMileageStatsVo"/>
- where vehicle_id = #{vehicleId} and stat_date = #{statDate}
+ where s.vehicle_id = #{vehicleId} and s.stat_date = #{statDate}
+ group by s.stats_id, s.vehicle_id, s.vehicle_no, s.stat_date, s.total_mileage, s.task_mileage,
+ s.non_task_mileage, s.task_ratio, s.gps_point_count, s.task_count, s.segment_count,
+ s.data_source, s.create_time, s.update_time, vd.dept_id
</select>
<select id="selectTaskTimeIntervals" resultMap="TaskTimeIntervalResult">
@@ -88,6 +110,8 @@
<if test="taskRatio != null">task_ratio,</if>
<if test="gpsPointCount != null">gps_point_count,</if>
<if test="taskCount != null">task_count,</if>
+ <if test="segmentCount != null">segment_count,</if>
+ <if test="dataSource != null">data_source,</if>
create_time
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
@@ -100,6 +124,8 @@
<if test="taskRatio != null">#{taskRatio},</if>
<if test="gpsPointCount != null">#{gpsPointCount},</if>
<if test="taskCount != null">#{taskCount},</if>
+ <if test="segmentCount != null">#{segmentCount},</if>
+ <if test="dataSource != null">#{dataSource},</if>
NOW()
</trim>
</insert>
@@ -114,6 +140,8 @@
<if test="taskRatio != null">task_ratio = #{taskRatio},</if>
<if test="gpsPointCount != null">gps_point_count = #{gpsPointCount},</if>
<if test="taskCount != null">task_count = #{taskCount},</if>
+ <if test="segmentCount != null">segment_count = #{segmentCount},</if>
+ <if test="dataSource != null">data_source = #{dataSource},</if>
update_time = NOW()
</trim>
where stats_id = #{statsId}
diff --git a/ruoyi-ui/src/api/system/gpsSegment.js b/ruoyi-ui/src/api/system/gpsSegment.js
new file mode 100644
index 0000000..f529485
--- /dev/null
+++ b/ruoyi-ui/src/api/system/gpsSegment.js
@@ -0,0 +1,39 @@
+import request from '@/utils/request'
+
+// 鏌ヨGPS鍒嗘閲岀▼鍒楄〃
+export function listSegmentMileage(query) {
+ return request({
+ url: '/system/gpsSegment/list',
+ method: 'get',
+ params: query
+ })
+}
+
+// 鎸変换鍔D鏌ヨGPS鍒嗘閲岀▼鍒楄〃
+export function getSegmentsByTaskId(taskId) {
+ return request({
+ url: '/system/gpsSegment/task/' + taskId,
+ method: 'get'
+ })
+}
+
+// 鏌ヨ浠诲姟鎬婚噷绋�
+export function getTaskTotalMileage(taskId) {
+ return request({
+ url: '/system/gpsSegment/task/' + taskId + '/total',
+ method: 'get'
+ })
+}
+
+// 鏌ヨ杞﹁締鎸囧畾鏃ユ湡鑼冨洿鐨凣PS鍒嗘閲岀▼
+export function getSegmentsByDateRange(vehicleId, startDate, endDate) {
+ return request({
+ url: '/system/gpsSegment/range',
+ method: 'get',
+ params: {
+ vehicleId: vehicleId,
+ startDate: startDate,
+ endDate: endDate
+ }
+ })
+}
diff --git a/ruoyi-ui/src/api/system/mileageStats.js b/ruoyi-ui/src/api/system/mileageStats.js
new file mode 100644
index 0000000..62f0cb3
--- /dev/null
+++ b/ruoyi-ui/src/api/system/mileageStats.js
@@ -0,0 +1,67 @@
+import request from '@/utils/request'
+
+// 鏌ヨ杞﹁締閲岀▼缁熻鍒楄〃
+export function listMileageStats(query) {
+ return request({
+ url: '/system/mileageStats/list',
+ method: 'get',
+ params: query
+ })
+}
+
+// 鏌ヨ杞﹁締閲岀▼缁熻璇︾粏
+export function getMileageStats(statsId) {
+ return request({
+ url: '/system/mileageStats/' + statsId,
+ method: 'get'
+ })
+}
+
+// 鏂板杞﹁締閲岀▼缁熻
+export function addMileageStats(data) {
+ return request({
+ url: '/system/mileageStats',
+ method: 'post',
+ data: data
+ })
+}
+
+// 淇敼杞﹁締閲岀▼缁熻
+export function updateMileageStats(data) {
+ return request({
+ url: '/system/mileageStats',
+ method: 'put',
+ data: data
+ })
+}
+
+// 鍒犻櫎杞﹁締閲岀▼缁熻
+export function delMileageStats(statsId) {
+ return request({
+ url: '/system/mileageStats/' + statsId,
+ method: 'delete'
+ })
+}
+
+// 鎵嬪姩璁$畻鎸囧畾杞﹁締鎸囧畾鏃ユ湡鐨勯噷绋嬬粺璁�
+export function calculateMileageStats(vehicleId, statDate) {
+ return request({
+ url: '/system/mileageStats/calculate',
+ method: 'post',
+ params: {
+ vehicleId: vehicleId,
+ statDate: statDate
+ }
+ })
+}
+
+// 鎵归噺璁$畻鎸囧畾鏃ユ湡鎵�鏈夎溅杈嗙殑閲岀▼缁熻
+export function batchCalculateMileageStats(statDate) {
+ return request({
+ url: '/system/mileageStats/batchCalculate',
+ method: 'post',
+ params: {
+ statDate: statDate
+ }
+ })
+}
diff --git a/ruoyi-ui/src/components/TaskMileageDetail/README.md b/ruoyi-ui/src/components/TaskMileageDetail/README.md
new file mode 100644
index 0000000..1232453
--- /dev/null
+++ b/ruoyi-ui/src/components/TaskMileageDetail/README.md
@@ -0,0 +1,187 @@
+# 浠诲姟GPS閲岀▼缁熻鍔熻兘闆嗘垚鎸囧崡
+
+## 鍔熻兘姒傝堪
+
+璇ュ姛鑳藉疄鐜颁簡浠诲姟涓嶨PS閲岀▼鐨勮嚜鍔ㄥ叧鑱旓紝鍙互鍦ㄤ换鍔¤鎯呴〉闈㈢洿鎺ュ睍绀鸿浠诲姟鎵ц鏈熼棿鐨凣PS琛岄┒閲岀▼缁熻銆�
+
+## 鏍稿績鐗规��
+
+- 鉁� 鑷姩鍏宠仈锛欸PS閲岀▼璁$畻鏃惰嚜鍔ㄥ叧鑱旀鍦ㄦ墽琛岀殑浠诲姟
+- 鉁� 瀹炴椂鏌ヨ锛氭敮鎸佹寜浠诲姟ID蹇�熸煡璇㈠叧鑱旂殑GPS閲岀▼鏁版嵁
+- 鉁� 鍒嗘灞曠ず锛氭樉绀轰换鍔℃湡闂存瘡5鍒嗛挓鐨凣PS閲岀▼鍒嗘鏄庣粏
+- 鉁� 缁熻姹囨�伙細鑷姩璁$畻浠诲姟鎬婚噷绋嬨�佸垎娈垫暟銆丟PS鐐规暟
+- 鉁� 鍙鍖栫粍浠讹細鎻愪緵寮�绠卞嵆鐢ㄧ殑Vue缁勪欢
+
+## 蹇�熷紑濮�
+
+### 1. 鎵ц鏁版嵁搴撹縼绉�
+
+```bash
+mysql -u root -p < sql/updates/add_task_id_to_segment_mileage.sql
+```
+
+### 2. 鍦ㄤ换鍔¤鎯呴〉闈㈤泦鎴愮粍浠�
+
+```vue
+<template>
+ <div>
+ <!-- 浠诲姟鍩烘湰淇℃伅 -->
+ <el-card>
+ <!-- ... 浠诲姟璇︽儏鍐呭 ... -->
+ </el-card>
+
+ <!-- GPS閲岀▼缁熻缁勪欢 -->
+ <task-mileage-detail :task-id="taskId" />
+ </div>
+</template>
+
+<script>
+import TaskMileageDetail from '@/components/TaskMileageDetail'
+
+export default {
+ components: {
+ TaskMileageDetail
+ },
+ data() {
+ return {
+ taskId: 123 // 浠诲姟ID
+ }
+ }
+}
+</script>
+```
+
+### 3. API璋冪敤绀轰緥
+
+```javascript
+import { getTaskTotalMileage, getSegmentsByTaskId } from '@/api/system/gpsSegment'
+
+// 鏌ヨ浠诲姟鎬婚噷绋�
+getTaskTotalMileage(taskId).then(res => {
+ console.log('浠诲姟鎬婚噷绋�:', res.data, 'km')
+})
+
+// 鏌ヨ浠诲姟GPS鍒嗘鏄庣粏
+getSegmentsByTaskId(taskId).then(res => {
+ console.log('鍒嗘鏄庣粏:', res.data)
+})
+```
+
+## 缁勪欢灞炴��
+
+### TaskMileageDetail
+
+| 灞炴�� | 绫诲瀷 | 蹇呭~ | 璇存槑 |
+|-----|------|------|------|
+| taskId | Number/String | 鏄� | 浠诲姟ID |
+
+## API鎺ュ彛璇存槑
+
+### 1. 鎸変换鍔D鏌ヨGPS鍒嗘閲岀▼
+
+**鎺ュ彛鍦板潃**: `GET /system/gpsSegment/task/{taskId}`
+
+**鏉冮檺**: `system:gpsSegment:query`
+
+**杩斿洖绀轰緥**:
+```json
+{
+ "code": 200,
+ "data": [
+ {
+ "segmentId": 1,
+ "vehicleId": 10,
+ "vehicleNo": "绮12345",
+ "taskId": 100,
+ "taskCode": "T20250115001",
+ "segmentStartTime": "2025-01-15 10:00:00",
+ "segmentEndTime": "2025-01-15 10:05:00",
+ "segmentDistance": 2.5,
+ "gpsPointCount": 5,
+ "startLongitude": 113.264385,
+ "startLatitude": 23.129112,
+ "endLongitude": 113.280637,
+ "endLatitude": 23.125178
+ }
+ ]
+}
+```
+
+### 2. 鏌ヨ浠诲姟鎬婚噷绋�
+
+**鎺ュ彛鍦板潃**: `GET /system/gpsSegment/task/{taskId}/total`
+
+**鏉冮檺**: `system:gpsSegment:query`
+
+**杩斿洖绀轰緥**:
+```json
+{
+ "code": 200,
+ "data": 15.8
+}
+```
+
+### 3. 鎸夋棩鏈熻寖鍥存煡璇PS鍒嗘閲岀▼
+
+**鎺ュ彛鍦板潃**: `GET /system/gpsSegment/range`
+
+**鍙傛暟**:
+- `vehicleId`: 杞﹁締ID
+- `startDate`: 寮�濮嬫棩鏈� (鏍煎紡: yyyy-MM-dd)
+- `endDate`: 缁撴潫鏃ユ湡 (鏍煎紡: yyyy-MM-dd)
+
+## 鏁版嵁娴佺▼
+
+```mermaid
+graph LR
+ A[瀹氭椂浠诲姟瑙﹀彂] --> B[璁$畻GPS閲岀▼]
+ B --> C[鏌ヨ杞﹁締姝e湪鎵ц鐨勪换鍔
+ C --> D{鏃堕棿娈垫槸鍚﹂噸鍙�?}
+ D -->|鏄瘄 E[鍏宠仈task_id鍜宼ask_code]
+ D -->|鍚 F[task_id涓篘ULL]
+ E --> G[淇濆瓨鍒嗘閲岀▼璁板綍]
+ F --> G
+ G --> H[鍓嶇鏌ヨ灞曠ず]
+```
+
+## 缁熻閫昏緫
+
+1. **鏃堕棿閲嶅彔鍒ゆ柇**锛�
+ - GPS鍒嗘鏃堕棿娈碉細`[segmentStartTime, segmentEndTime]`
+ - 浠诲姟鎵ц鏃堕棿娈碉細`[actualStartTime, actualEndTime]`
+ - 閲嶅彔鏉′欢锛歚segmentStartTime < taskEndTime && segmentEndTime > taskStartTime`
+
+2. **浼樺厛绾�**锛�
+ - 浼樺厛浣跨敤浠诲姟鐨勫疄闄呭紑濮�/缁撴潫鏃堕棿
+ - 濡傛灉瀹為檯鏃堕棿涓虹┖锛屽垯浣跨敤璁″垝鏃堕棿
+
+3. **璺ㄦ閲岀▼**锛�
+ - 鑷姩璁$畻鐩搁偦5鍒嗛挓娈典箣闂寸殑闂撮殭璺濈
+ - 閬垮厤閲岀▼缁熻閬楁紡
+
+## 娉ㄦ剰浜嬮」
+
+1. **鏉冮檺閰嶇疆**锛氱‘淇濈敤鎴锋嫢鏈� `system:gpsSegment:query` 鏉冮檺
+2. **鏁版嵁鍑嗙‘鎬�**锛欸PS閲岀▼璁$畻鍚庢墠浼氭湁鍏宠仈鏁版嵁
+3. **瀹炴椂鎬�**锛氭暟鎹敱瀹氭椂浠诲姟鏇存柊锛岄潪瀹炴椂鏁版嵁
+4. **鍘嗗彶鏁版嵁**锛氬凡璁$畻鐨勫巻鍙睪PS鏁版嵁涓嶄細鑷姩鍏宠仈浠诲姟锛岄渶閲嶆柊璁$畻
+
+## 鎵╁睍寤鸿
+
+1. **浠诲姟鍒楄〃灞曠ず**锛氬湪浠诲姟鍒楄〃涓洿鎺ユ樉绀轰换鍔¢噷绋�
+2. **鎶ヨ〃缁熻**锛氬熀浜巘ask_id杩涜浠诲姟閲岀▼鍒嗘瀽
+3. **寮傚父妫�娴�**锛氬姣旈浼伴噷绋嬩笌瀹為檯GPS閲岀▼锛屽彂鐜板紓甯�
+4. **鎴愭湰鏍哥畻**锛氬熀浜庝换鍔¢噷绋嬭繘琛岃垂鐢ㄨ绠�
+
+## 鎬ц兘浼樺寲
+
+- 鉁� 浣跨敤绱㈠紩锛歚idx_task_id`, `idx_vehicle_task`
+- 鉁� 鑱氬悎鏌ヨ锛氱洿鎺UM璁$畻鎬婚噷绋�
+- 鉁� 鍒嗛〉鍔犺浇锛氬垎娈垫槑缁嗘敮鎸佸垎椤�
+- 鉁� 缂撳瓨绛栫暐锛氬彲瀵逛换鍔℃�婚噷绋嬭繘琛孯edis缂撳瓨
+
+## 鎶�鏈爤
+
+- **鍚庣**: Spring Boot + MyBatis
+- **鍓嶇**: Vue 2 + Element UI
+- **鏁版嵁搴�**: MySQL 5.7+
diff --git a/ruoyi-ui/src/components/TaskMileageDetail/index.vue b/ruoyi-ui/src/components/TaskMileageDetail/index.vue
new file mode 100644
index 0000000..cf7cc9c
--- /dev/null
+++ b/ruoyi-ui/src/components/TaskMileageDetail/index.vue
@@ -0,0 +1,258 @@
+<template>
+ <div class="task-mileage-detail">
+ <el-card class="box-card" shadow="hover">
+ <div slot="header" class="clearfix">
+ <span class="card-title">
+ <i class="el-icon-location-information"></i>
+ 浠诲姟GPS閲岀▼缁熻
+ </span>
+ <el-button
+ style="float: right; padding: 3px 10px"
+ type="text"
+ size="small"
+ @click="refreshData"
+ :loading="loading"
+ >
+ <i class="el-icon-refresh"></i> 鍒锋柊
+ </el-button>
+ </div>
+
+ <!-- 缁熻姒傝 -->
+ <div class="mileage-summary">
+ <el-row :gutter="20">
+ <el-col :span="8">
+ <div class="stat-item total-mileage">
+ <div class="stat-label">鎬婚噷绋�</div>
+ <div class="stat-value">{{ totalMileage }} <span class="unit">km</span></div>
+ </div>
+ </el-col>
+ <el-col :span="8">
+ <div class="stat-item segment-count">
+ <div class="stat-label">鍒嗘鏁�</div>
+ <div class="stat-value">{{ segmentCount }} <span class="unit">娈�</span></div>
+ </div>
+ </el-col>
+ <el-col :span="8">
+ <div class="stat-item gps-count">
+ <div class="stat-label">GPS鐐规暟</div>
+ <div class="stat-value">{{ totalGpsPoints }} <span class="unit">涓�</span></div>
+ </div>
+ </el-col>
+ </el-row>
+ </div>
+
+ <!-- 鍒嗘鏄庣粏 -->
+ <div class="segment-detail" v-if="segmentList.length > 0">
+ <el-divider content-position="left">
+ <i class="el-icon-tickets"></i> 閲岀▼鍒嗘鏄庣粏
+ </el-divider>
+
+ <el-table
+ :data="segmentList"
+ size="small"
+ :max-height="400"
+ stripe
+ border
+ >
+ <el-table-column type="index" label="搴忓彿" width="50" align="center" />
+ <el-table-column label="鏃堕棿娈靛紑濮�" prop="segmentStartTime" width="160" align="center">
+ <template slot-scope="scope">
+ {{ parseTime(scope.row.segmentStartTime, '{h}:{i}:{s}') }}
+ </template>
+ </el-table-column>
+ <el-table-column label="鏃堕棿娈电粨鏉�" prop="segmentEndTime" width="160" align="center">
+ <template slot-scope="scope">
+ {{ parseTime(scope.row.segmentEndTime, '{h}:{i}:{s}') }}
+ </template>
+ </el-table-column>
+ <el-table-column label="閲岀▼(km)" prop="segmentDistance" width="100" align="center">
+ <template slot-scope="scope">
+ <el-tag type="success" size="mini">{{ scope.row.segmentDistance }}</el-tag>
+ </template>
+ </el-table-column>
+ <el-table-column label="GPS鐐规暟" prop="gpsPointCount" width="90" align="center" />
+ <el-table-column label="璧风偣鍧愭爣" align="center" width="180">
+ <template slot-scope="scope">
+ <span class="coordinate">
+ {{ scope.row.startLongitude }}, {{ scope.row.startLatitude }}
+ </span>
+ </template>
+ </el-table-column>
+ <el-table-column label="缁堢偣鍧愭爣" align="center" width="180">
+ <template slot-scope="scope">
+ <span class="coordinate">
+ {{ scope.row.endLongitude }}, {{ scope.row.endLatitude }}
+ </span>
+ </template>
+ </el-table-column>
+ <el-table-column label="璁$畻鏂瑰紡" prop="calculateMethod" width="100" align="center">
+ <template slot-scope="scope">
+ <el-tag :type="scope.row.calculateMethod === 'tianditu' ? 'warning' : 'info'" size="mini">
+ {{ scope.row.calculateMethod === 'tianditu' ? '澶╁湴鍥�' : 'Haversine' }}
+ </el-tag>
+ </template>
+ </el-table-column>
+ </el-table>
+ </div>
+
+ <!-- 鏃犳暟鎹彁绀� -->
+ <el-empty
+ v-else-if="!loading"
+ description="鏆傛棤GPS閲岀▼鏁版嵁"
+ :image-size="80"
+ ></el-empty>
+
+ <!-- 鍔犺浇涓� -->
+ <div v-if="loading" class="loading-container">
+ <el-skeleton :rows="5" animated />
+ </div>
+ </el-card>
+ </div>
+</template>
+
+<script>
+import { getSegmentsByTaskId, getTaskTotalMileage } from '@/api/system/gpsSegment'
+
+export default {
+ name: 'TaskMileageDetail',
+ props: {
+ taskId: {
+ type: [Number, String],
+ required: true
+ }
+ },
+ data() {
+ return {
+ loading: false,
+ totalMileage: '0.00',
+ segmentList: [],
+ segmentCount: 0,
+ totalGpsPoints: 0
+ }
+ },
+ watch: {
+ taskId: {
+ handler(newVal) {
+ if (newVal) {
+ this.loadData()
+ }
+ },
+ immediate: true
+ }
+ },
+ methods: {
+ /** 鍔犺浇鏁版嵁 */
+ loadData() {
+ if (!this.taskId) {
+ return
+ }
+
+ this.loading = true
+
+ // 骞惰璇锋眰鎬婚噷绋嬪拰鍒嗘鏄庣粏
+ Promise.all([
+ getTaskTotalMileage(this.taskId),
+ getSegmentsByTaskId(this.taskId)
+ ]).then(([totalRes, segmentRes]) => {
+ // 澶勭悊鎬婚噷绋�
+ if (totalRes.code === 200) {
+ this.totalMileage = (totalRes.data || 0).toFixed(2)
+ }
+
+ // 澶勭悊鍒嗘鏄庣粏
+ if (segmentRes.code === 200 && segmentRes.data) {
+ this.segmentList = segmentRes.data
+ this.segmentCount = this.segmentList.length
+
+ // 璁$畻鎬籊PS鐐规暟
+ this.totalGpsPoints = this.segmentList.reduce((sum, item) => {
+ return sum + (item.gpsPointCount || 0)
+ }, 0)
+ }
+ }).catch(error => {
+ console.error('鍔犺浇浠诲姟GPS閲岀▼鏁版嵁澶辫触', error)
+ this.$message.error('鍔犺浇GPS閲岀▼鏁版嵁澶辫触')
+ }).finally(() => {
+ this.loading = false
+ })
+ },
+
+ /** 鍒锋柊鏁版嵁 */
+ refreshData() {
+ this.loadData()
+ }
+ }
+}
+</script>
+
+<style scoped lang="scss">
+.task-mileage-detail {
+ margin-top: 15px;
+
+ .card-title {
+ font-size: 16px;
+ font-weight: 500;
+ color: #303133;
+
+ i {
+ margin-right: 5px;
+ color: #409EFF;
+ }
+ }
+
+ .mileage-summary {
+ margin-bottom: 20px;
+
+ .stat-item {
+ padding: 20px;
+ text-align: center;
+ border-radius: 4px;
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+ color: white;
+
+ &.total-mileage {
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+ }
+
+ &.segment-count {
+ background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
+ }
+
+ &.gps-count {
+ background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
+ }
+
+ .stat-label {
+ font-size: 14px;
+ opacity: 0.9;
+ margin-bottom: 8px;
+ }
+
+ .stat-value {
+ font-size: 28px;
+ font-weight: bold;
+
+ .unit {
+ font-size: 14px;
+ font-weight: normal;
+ margin-left: 4px;
+ }
+ }
+ }
+ }
+
+ .segment-detail {
+ margin-top: 20px;
+
+ .coordinate {
+ font-family: 'Courier New', monospace;
+ font-size: 12px;
+ color: #606266;
+ }
+ }
+
+ .loading-container {
+ padding: 20px;
+ }
+}
+</style>
diff --git a/ruoyi-ui/src/views/system/mileageStats/README.md b/ruoyi-ui/src/views/system/mileageStats/README.md
new file mode 100644
index 0000000..a6c8dcf
--- /dev/null
+++ b/ruoyi-ui/src/views/system/mileageStats/README.md
@@ -0,0 +1,172 @@
+# 杞﹁締GPS琛岄┒缁熻绠$悊鐣岄潰
+
+## 鍔熻兘姒傝堪
+
+鍦� ruoyi-ui 鍚庡彴绠$悊绯荤粺涓柊澧炰簡杞﹁締GPS琛岄┒缁熻鍔熻兘锛岀敤浜庢煡鐪嬪拰绠$悊杞﹁締鐨凣PS閲岀▼缁熻鏁版嵁銆�
+
+## 鏂板鏂囦欢
+
+### 1. 鍓嶇椤甸潰
+- **鏂囦欢璺緞**: `ruoyi-ui/src/views/system/mileageStats/index.vue`
+- **鍔熻兘**: 杞﹁締閲岀▼缁熻绠$悊鐣岄潰
+
+### 2. API鎺ュ彛鏂囦欢
+- **鏂囦欢璺緞**: `ruoyi-ui/src/api/system/mileageStats.js`
+- **鍔熻兘**: 杞﹁締閲岀▼缁熻鐩稿叧鐨凙PI鎺ュ彛璋冪敤
+
+## 涓昏鍔熻兘
+
+### 1. 鏁版嵁鏌ヨ涓庡睍绀�
+- 鉁� 鏀寔鎸夎溅鐗屽彿銆佽溅杈咺D銆佺粺璁℃棩鏈熸煡璇�
+- 鉁� 鏀寔鏃ユ湡鑼冨洿鏌ヨ
+- 鉁� 鍒嗛〉鏄剧ず缁熻鏁版嵁
+- 鉁� 瀹炴椂灞曠ず浠ヤ笅鏁版嵁锛�
+ - 杞﹁締ID銆佽溅鐗屽彿
+ - 缁熻鏃ユ湡
+ - 鎬婚噷绋嬶紙km锛�
+ - 浠诲姟閲岀▼锛坘m锛�
+ - 闈炰换鍔¢噷绋嬶紙km锛�
+ - 浠诲姟鍗犳瘮锛堝甫棰滆壊鏍囩锛�
+ - GPS鐐规暟閲�
+ - 浠诲姟鏁伴噺
+ - 缁熻鏃堕棿
+
+### 2. 鎵嬪姩缁熻
+- 鉁� 鍙寚瀹氳溅杈咺D鍜岀粺璁℃棩鏈�
+- 鉁� 鎵嬪姩瑙﹀彂鍗曚釜杞﹁締鐨勯噷绋嬭绠�
+- 鉁� 瀹炴椂鏄剧ず缁熻杩涘害
+
+### 3. 鎵归噺缁熻
+- 鉁� 鍙寚瀹氱粺璁℃棩鏈�
+- 鉁� 鑷姩瀵规墍鏈夋椿璺冭溅杈嗚繘琛岄噷绋嬭绠�
+- 鉁� 鎻愮ず鐢ㄦ埛绛夊緟鏃堕棿鍙兘杈冮暱
+
+### 4. 鏁版嵁璇︽儏
+- 鉁� 鏌ョ湅璇︾粏鐨勯噷绋嬬粺璁′俊鎭�
+- 鉁� 浣跨敤鎻忚堪鍒楄〃缇庤灞曠ず
+
+### 5. 鏁版嵁瀵煎嚭
+- 鉁� 鏀寔灏嗙粺璁℃暟鎹鍑轰负Excel鏂囦欢
+- 鉁� 鍙寜鏌ヨ鏉′欢瀵煎嚭
+
+### 6. 鏁版嵁鍒犻櫎
+- 鉁� 鏀寔鍗曟潯鎴栨壒閲忓垹闄ょ粺璁¤褰�
+- 鉁� 鍒犻櫎鍓嶄簩娆$‘璁�
+
+## 鐣岄潰鐗硅壊
+
+### 1. 浠诲姟鍗犳瘮棰滆壊鏍囪瘑
+- 馃煝 **缁胯壊** (success): 鍗犳瘮 鈮� 80%
+- 馃數 **钃濊壊** (primary): 鍗犳瘮 60% - 79%
+- 馃煛 **姗欒壊** (warning): 鍗犳瘮 40% - 59%
+- 馃敶 **绾㈣壊** (danger): 鍗犳瘮 < 40%
+- 鈿� **鐏拌壊** (info): 鏃犳暟鎹�
+
+### 2. 鏁版嵁楂樹寒鏄剧ず
+- 鎬婚噷绋嬶細钃濊壊鍔犵矖
+- 浠诲姟閲岀▼锛氱豢鑹插姞绮�
+- 璇︽儏鏁版嵁锛氬ぇ瀛楀彿鍔犵矖
+
+## 鏉冮檺閰嶇疆
+
+闇�瑕佸湪绯荤粺鑿滃崟涓厤缃互涓嬫潈闄愭爣璇嗭細
+
+```
+system:mileageStats:list # 鏌ヨ鍒楄〃
+system:mileageStats:query # 鏌ョ湅璇︽儏
+system:mileageStats:export # 瀵煎嚭鏁版嵁
+system:mileageStats:remove # 鍒犻櫎鏁版嵁
+system:mileageStats:calculate # 鎵嬪姩缁熻
+system:mileageStats:batch # 鎵归噺缁熻
+```
+
+## 鑿滃崟閰嶇疆绀轰緥
+
+鍦ㄧ郴缁熺鐞� 鈫� 鑿滃崟绠$悊涓坊鍔狅細
+
+```
+鑿滃崟鍚嶇О: 杞﹁締閲岀▼缁熻
+鐖惰彍鍗�: 绯荤粺绠$悊
+鑿滃崟绫诲瀷: 鑿滃崟
+璺敱鍦板潃: mileageStats
+缁勪欢璺緞: system/mileageStats/index
+鏉冮檺鏍囪瘑: system:mileageStats:list
+鑿滃崟鍥炬爣: chart
+```
+
+## 鍚庣鎺ュ彛
+
+鍚庣鎺ュ彛宸插畬鎴愶紝璺緞涓猴細
+- Controller: `ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/VehicleMileageStatsController.java`
+- Service: `ruoyi-system/src/main/java/com/ruoyi/system/service/impl/VehicleMileageStatsServiceImpl.java`
+
+### 鎺ュ彛鍒楄〃
+
+| 鎺ュ彛璺緞 | 鏂规硶 | 璇存槑 |
+|---------|------|------|
+| /system/mileageStats/list | GET | 鏌ヨ缁熻鍒楄〃 |
+| /system/mileageStats/{statsId} | GET | 鑾峰彇缁熻璇︽儏 |
+| /system/mileageStats/{statsIds} | DELETE | 鍒犻櫎缁熻璁板綍 |
+| /system/mileageStats/export | POST | 瀵煎嚭缁熻鏁版嵁 |
+| /system/mileageStats/calculate | POST | 鎵嬪姩璁$畻缁熻 |
+| /system/mileageStats/batchCalculate | POST | 鎵归噺璁$畻缁熻 |
+
+## 浣跨敤璇存槑
+
+### 1. 鏌ョ湅缁熻鏁版嵁
+1. 鍦ㄥ悗鍙扮鐞嗙郴缁熶腑璁块棶"杞﹁締閲岀▼缁熻"鑿滃崟
+2. 鍙互閫氳繃杞︾墝鍙枫�佽溅杈咺D銆佹棩鏈熺瓑鏉′欢绛涢�夋暟鎹�
+3. 鐐瑰嚮"璇︽儏"鎸夐挳鏌ョ湅瀹屾暣鐨勭粺璁′俊鎭�
+
+### 2. 鎵嬪姩缁熻鍗曚釜杞﹁締
+1. 鐐瑰嚮"鎵嬪姩缁熻"鎸夐挳
+2. 杈撳叆杞﹁締ID鍜岀粺璁℃棩鏈�
+3. 鐐瑰嚮"寮�濮嬬粺璁�"锛岀郴缁熷皢璁$畻璇ヨ溅杈嗗湪鎸囧畾鏃ユ湡鐨勯噷绋嬫暟鎹�
+
+### 3. 鎵归噺缁熻鎵�鏈夎溅杈�
+1. 鐐瑰嚮"鎵归噺缁熻"鎸夐挳
+2. 閫夋嫨缁熻鏃ユ湡
+3. 鐐瑰嚮"寮�濮嬬粺璁�"锛岀郴缁熷皢璁$畻鎵�鏈夋椿璺冭溅杈嗗湪璇ユ棩鏈熺殑閲岀▼鏁版嵁
+4. 娉ㄦ剰锛氭鎿嶄綔鍙兘闇�瑕佽緝闀挎椂闂达紝璇疯�愬績绛夊緟
+
+### 4. 瀵煎嚭鏁版嵁
+1. 璁剧疆鏌ヨ鏉′欢锛堝彲閫夛級
+2. 鐐瑰嚮"瀵煎嚭"鎸夐挳
+3. 绯荤粺灏嗙敓鎴怑xcel鏂囦欢渚涗笅杞�
+
+## 鏁版嵁璇存槑
+
+- **鎬婚噷绋�**: 杞﹁締鍦ㄧ粺璁℃棩鏈熷唴鐨勬�昏椹堕噷绋�
+- **浠诲姟閲岀▼**: 杞﹁締鍦ㄦ墽琛屼换鍔℃湡闂寸殑琛岄┒閲岀▼
+- **闈炰换鍔¢噷绋�**: 杞﹁締鍦ㄩ潪浠诲姟鏃舵鐨勮椹堕噷绋�
+- **浠诲姟鍗犳瘮**: 浠诲姟閲岀▼ / 鎬婚噷绋嬬殑姣斾緥
+- **GPS鐐规暟**: 缁熻鏃ユ湡鍐呰褰曠殑GPS瀹氫綅鐐规暟閲�
+- **浠诲姟鏁�**: 缁熻鏃ユ湡鍐呰溅杈嗘墽琛岀殑浠诲姟鏁伴噺
+
+## 娉ㄦ剰浜嬮」
+
+1. 缁熻鏁版嵁鍩轰簬GPS瀹氫綅鐐硅绠楋紝闇�瑕佽溅杈嗗畨瑁匞PS璁惧骞舵甯镐笂浼犳暟鎹�
+2. 鎵归噺缁熻鍙兘娑堣�楄緝澶氱郴缁熻祫婧愶紝寤鸿鍦ㄤ笟鍔′綆宄版湡鎵ц
+3. 寤鸿瀹氭湡娓呯悊鍘嗗彶缁熻鏁版嵁锛屼繚鐣欏繀瑕佺殑鏃堕棿鑼冨洿鍗冲彲
+4. 濡傛灉缁熻缁撴灉涓�0锛屽彲鑳芥槸璇ユ棩鏈熷唴鏃燝PS鏁版嵁鎴栨棤浠诲姟鏁版嵁
+
+## 鎶�鏈疄鐜�
+
+### 閲岀▼璁$畻鏂规硶
+- 浣跨敤 **Haversine鍏紡** 璁$畻GPS鍧愭爣鐐逛箣闂寸殑璺濈
+- 鏍规嵁浠诲姟鏃堕棿娈佃嚜鍔ㄥ垎閰嶉噷绋嬪埌浠诲姟/闈炰换鍔$被鍒�
+- 绮剧‘鍒板皬鏁扮偣鍚�2浣�
+
+### 鏁版嵁鏉ユ簮
+- GPS鏁版嵁琛�: `tb_vehicle_gps`
+- 浠诲姟鏁版嵁琛�: `sys_task`
+- 缁熻缁撴灉琛�: `tb_vehicle_mileage_stats`
+
+## 鏈潵鎵╁睍
+
+鍙互鑰冭檻澧炲姞浠ヤ笅鍔熻兘锛�
+- 馃搳 缁熻鍥捐〃灞曠ず锛堟姌绾垮浘銆侀ゼ鍥剧瓑锛�
+- 馃搮 鏈堝害銆佸勾搴︽眹鎬荤粺璁�
+- 馃搱 杞﹁締閲岀▼瓒嬪娍鍒嗘瀽
+- 馃殫 杞﹁締鍒╃敤鐜囧垎鏋�
+- 馃挵 閲岀▼鎴愭湰鏍哥畻
diff --git a/ruoyi-ui/src/views/system/mileageStats/index.vue b/ruoyi-ui/src/views/system/mileageStats/index.vue
new file mode 100644
index 0000000..8a6738c
--- /dev/null
+++ b/ruoyi-ui/src/views/system/mileageStats/index.vue
@@ -0,0 +1,490 @@
+<template>
+ <div class="app-container">
+ <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="88px">
+ <el-form-item label="褰掑睘鍒嗗叕鍙�" prop="deptId">
+ <el-select
+ v-model="queryParams.deptId"
+ placeholder="璇烽�夋嫨鍒嗗叕鍙�"
+ clearable
+ filterable
+ size="small"
+ style="width: 200px"
+ >
+ <el-option
+ v-for="dept in deptOptions"
+ :key="dept.deptId"
+ :label="dept.deptName"
+ :value="dept.deptId"
+ />
+ </el-select>
+ </el-form-item>
+ <el-form-item label="杞︾墝鍙�" prop="vehicleNo">
+ <el-input
+ v-model="queryParams.vehicleNo"
+ placeholder="璇疯緭鍏ヨ溅鐗屽彿"
+ clearable
+ size="small"
+ @keyup.enter.native="handleQuery"
+ />
+ </el-form-item>
+ <el-form-item label="缁熻鏃ユ湡" prop="statDate">
+ <el-date-picker
+ v-model="queryParams.statDate"
+ type="date"
+ value-format="yyyy-MM-dd"
+ placeholder="閫夋嫨缁熻鏃ユ湡"
+ clearable
+ size="small"
+ />
+ </el-form-item>
+ <el-form-item label="鏃ユ湡鑼冨洿">
+ <el-date-picker
+ v-model="dateRange"
+ size="small"
+ style="width: 240px"
+ value-format="yyyy-MM-dd"
+ type="daterange"
+ range-separator="-"
+ start-placeholder="寮�濮嬫棩鏈�"
+ end-placeholder="缁撴潫鏃ユ湡"
+ ></el-date-picker>
+ </el-form-item>
+ <el-form-item>
+ <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">鎼滅储</el-button>
+ <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">閲嶇疆</el-button>
+ </el-form-item>
+ </el-form>
+
+ <el-row :gutter="10" class="mb8">
+ <el-col :span="1.5">
+ <el-button
+ type="success"
+ plain
+ icon="el-icon-s-operation"
+ size="mini"
+ @click="handleCalculate"
+ v-hasPermi="['system:mileageStats:calculate']"
+ >鎵嬪姩缁熻</el-button>
+ </el-col>
+ <el-col :span="1.5">
+ <el-button
+ type="info"
+ plain
+ icon="el-icon-s-grid"
+ size="mini"
+ @click="handleBatchCalculate"
+ v-hasPermi="['system:mileageStats:batch']"
+ >鎵归噺缁熻</el-button>
+ </el-col>
+ <el-col :span="1.5">
+ <el-button
+ type="warning"
+ plain
+ icon="el-icon-download"
+ size="mini"
+ @click="handleExport"
+ v-hasPermi="['system:mileageStats:export']"
+ >瀵煎嚭</el-button>
+ </el-col>
+ <el-col :span="1.5">
+ <el-button
+ type="danger"
+ plain
+ icon="el-icon-delete"
+ size="mini"
+ :disabled="multiple"
+ @click="handleDelete"
+ v-hasPermi="['system:mileageStats:remove']"
+ >鍒犻櫎</el-button>
+ </el-col>
+ <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
+ </el-row>
+
+ <el-table v-loading="loading" :data="statsList" @selection-change="handleSelectionChange">
+ <el-table-column type="selection" width="55" align="center" />
+ <el-table-column label="杞︾墝鍙�" align="center" prop="vehicleNo" width="120" fixed />
+ <el-table-column label="褰掑睘鍒嗗叕鍙�" align="center" prop="deptName" width="150" show-overflow-tooltip />
+ <el-table-column label="缁熻鏃ユ湡" align="center" prop="statDate" width="120">
+ <template slot-scope="scope">
+ <span>{{ parseTime(scope.row.statDate, '{y}-{m}-{d}') }}</span>
+ </template>
+ </el-table-column>
+ <el-table-column label="鎬婚噷绋�(km)" align="center" prop="totalMileage" width="110">
+ <template slot-scope="scope">
+ <span class="mileage-value">{{ scope.row.totalMileage || '0.00' }}</span>
+ </template>
+ </el-table-column>
+ <el-table-column label="浠诲姟閲岀▼(km)" align="center" prop="taskMileage" width="120">
+ <template slot-scope="scope">
+ <span class="mileage-value task-mileage">{{ scope.row.taskMileage || '0.00' }}</span>
+ </template>
+ </el-table-column>
+ <el-table-column label="闈炰换鍔¢噷绋�(km)" align="center" prop="nonTaskMileage" width="130">
+ <template slot-scope="scope">
+ <span class="mileage-value">{{ scope.row.nonTaskMileage || '0.00' }}</span>
+ </template>
+ </el-table-column>
+ <el-table-column label="浠诲姟鍗犳瘮" align="center" prop="taskRatio" width="100">
+ <template slot-scope="scope">
+ <el-tag :type="getRatioType(scope.row.taskRatio)">
+ {{ formatRatio(scope.row.taskRatio) }}
+ </el-tag>
+ </template>
+ </el-table-column>
+ <el-table-column label="GPS鐐规暟" align="center" prop="gpsPointCount" width="90" />
+ <el-table-column label="浠诲姟鏁�" align="center" prop="taskCount" width="80" />
+ <el-table-column label="鍒嗘鏁�" align="center" prop="segmentCount" width="80" />
+ <el-table-column label="缁熻鏃堕棿" align="center" prop="createTime" width="160">
+ <template slot-scope="scope">
+ <span>{{ parseTime(scope.row.createTime) }}</span>
+ </template>
+ </el-table-column>
+ <el-table-column label="鎿嶄綔" align="center" class-name="small-padding fixed-width" width="120">
+ <template slot-scope="scope">
+ <el-button
+ size="mini"
+ type="text"
+ icon="el-icon-view"
+ @click="handleView(scope.row)"
+ v-hasPermi="['system:mileageStats:query']"
+ >璇︽儏</el-button>
+ <el-button
+ size="mini"
+ type="text"
+ icon="el-icon-delete"
+ @click="handleDelete(scope.row)"
+ v-hasPermi="['system:mileageStats:remove']"
+ >鍒犻櫎</el-button>
+ </template>
+ </el-table-column>
+ </el-table>
+
+ <pagination
+ v-show="total>0"
+ :total="total"
+ :page.sync="queryParams.pageNum"
+ :limit.sync="queryParams.pageSize"
+ @pagination="getList"
+ />
+
+ <!-- 鎵嬪姩缁熻瀵硅瘽妗� -->
+ <el-dialog title="鎵嬪姩閲岀▼缁熻" :visible.sync="calculateOpen" width="500px" append-to-body>
+ <el-form ref="calculateForm" :model="calculateForm" :rules="calculateRules" label-width="100px">
+ <el-form-item label="杞﹁締ID" prop="vehicleId">
+ <el-input v-model="calculateForm.vehicleId" placeholder="璇疯緭鍏ヨ溅杈咺D" type="number" />
+ </el-form-item>
+ <el-form-item label="缁熻鏃ユ湡" prop="statDate">
+ <el-date-picker
+ v-model="calculateForm.statDate"
+ type="date"
+ value-format="yyyy-MM-dd"
+ placeholder="閫夋嫨缁熻鏃ユ湡"
+ style="width: 100%"
+ />
+ </el-form-item>
+ </el-form>
+ <div slot="footer" class="dialog-footer">
+ <el-button type="primary" @click="submitCalculate" :loading="calculateLoading">寮�濮嬬粺璁�</el-button>
+ <el-button @click="calculateOpen = false">鍙� 娑�</el-button>
+ </div>
+ </el-dialog>
+
+ <!-- 鎵归噺缁熻瀵硅瘽妗� -->
+ <el-dialog title="鎵归噺閲岀▼缁熻" :visible.sync="batchCalculateOpen" width="500px" append-to-body>
+ <el-form ref="batchCalculateForm" :model="batchCalculateForm" :rules="batchCalculateRules" label-width="100px">
+ <el-form-item label="缁熻鏃ユ湡" prop="statDate">
+ <el-date-picker
+ v-model="batchCalculateForm.statDate"
+ type="date"
+ value-format="yyyy-MM-dd"
+ placeholder="閫夋嫨缁熻鏃ユ湡"
+ style="width: 100%"
+ />
+ </el-form-item>
+ <el-alert
+ title="鎻愮ず"
+ type="warning"
+ description="鎵归噺缁熻灏嗗鎵�鏈夋椿璺冭溅杈嗚繘琛屾寚瀹氭棩鏈熺殑閲岀▼璁$畻锛屽彲鑳介渶瑕佽緝闀挎椂闂达紝璇疯�愬績绛夊緟銆�"
+ :closable="false"
+ show-icon
+ />
+ </el-form>
+ <div slot="footer" class="dialog-footer">
+ <el-button type="primary" @click="submitBatchCalculate" :loading="batchCalculateLoading">寮�濮嬬粺璁�</el-button>
+ <el-button @click="batchCalculateOpen = false">鍙� 娑�</el-button>
+ </div>
+ </el-dialog>
+
+ <!-- 璇︽儏瀵硅瘽妗� -->
+ <el-dialog title="閲岀▼缁熻璇︽儏" :visible.sync="detailOpen" width="600px" append-to-body>
+ <el-descriptions :column="2" border>
+ <el-descriptions-item label="杞︾墝鍙�">{{ detailData.vehicleNo }}</el-descriptions-item>
+ <el-descriptions-item label="褰掑睘鍒嗗叕鍙�">{{ detailData.deptName || '-' }}</el-descriptions-item>
+ <el-descriptions-item label="缁熻鏃ユ湡">
+ {{ parseTime(detailData.statDate, '{y}-{m}-{d}') }}
+ </el-descriptions-item>
+ <el-descriptions-item label="鎬婚噷绋�">
+ <span class="detail-value">{{ detailData.totalMileage }} km</span>
+ </el-descriptions-item>
+ <el-descriptions-item label="浠诲姟閲岀▼">
+ <span class="detail-value task-mileage">{{ detailData.taskMileage }} km</span>
+ </el-descriptions-item>
+ <el-descriptions-item label="闈炰换鍔¢噷绋�">
+ <span class="detail-value">{{ detailData.nonTaskMileage }} km</span>
+ </el-descriptions-item>
+ <el-descriptions-item label="浠诲姟鍗犳瘮">
+ <el-tag :type="getRatioType(detailData.taskRatio)">
+ {{ formatRatio(detailData.taskRatio) }}
+ </el-tag>
+ </el-descriptions-item>
+ <el-descriptions-item label="GPS鐐规暟">{{ detailData.gpsPointCount }}</el-descriptions-item>
+ <el-descriptions-item label="浠诲姟鏁�">{{ detailData.taskCount }}</el-descriptions-item>
+ <el-descriptions-item label="鍒嗘鏁�">{{ detailData.segmentCount }}</el-descriptions-item>
+ <el-descriptions-item label="鏁版嵁鏉ユ簮">
+ <el-tag :type="detailData.dataSource === 'segment' ? 'success' : 'info'" size="small">
+ {{ detailData.dataSource === 'segment' ? '浠庡垎娈垫眹鎬�' : '鐩存帴璁$畻' }}
+ </el-tag>
+ </el-descriptions-item>
+ <el-descriptions-item label="缁熻鏃堕棿" :span="2">
+ {{ parseTime(detailData.createTime) }}
+ </el-descriptions-item>
+ </el-descriptions>
+ <div slot="footer" class="dialog-footer">
+ <el-button @click="detailOpen = false">鍏� 闂�</el-button>
+ </div>
+ </el-dialog>
+ </div>
+</template>
+
+<script>
+import { listMileageStats, getMileageStats, delMileageStats, calculateMileageStats, batchCalculateMileageStats } from "@/api/system/mileageStats";
+import { listDept } from "@/api/system/dept";
+
+export default {
+ name: "MileageStats",
+ data() {
+ return {
+ // 閬僵灞�
+ loading: true,
+ // 閫変腑鏁扮粍
+ ids: [],
+ // 闈炲崟涓鐢�
+ single: true,
+ // 闈炲涓鐢�
+ multiple: true,
+ // 鏄剧ず鎼滅储鏉′欢
+ showSearch: true,
+ // 鎬绘潯鏁�
+ total: 0,
+ // 杞﹁締閲岀▼缁熻琛ㄦ牸鏁版嵁
+ statsList: [],
+ // 鍒嗗叕鍙搁�夐」
+ deptOptions: [],
+ // 鏃ユ湡鑼冨洿
+ dateRange: [],
+ // 寮瑰嚭灞傛爣棰�
+ title: "",
+ // 鏄惁鏄剧ず鎵嬪姩缁熻寮瑰嚭灞�
+ calculateOpen: false,
+ // 鏄惁鏄剧ず鎵归噺缁熻寮瑰嚭灞�
+ batchCalculateOpen: false,
+ // 鏄惁鏄剧ず璇︽儏寮瑰嚭灞�
+ detailOpen: false,
+ // 璇︽儏鏁版嵁
+ detailData: {},
+ // 鎵嬪姩缁熻鍔犺浇鐘舵��
+ calculateLoading: false,
+ // 鎵归噺缁熻鍔犺浇鐘舵��
+ batchCalculateLoading: false,
+ // 鏌ヨ鍙傛暟
+ queryParams: {
+ pageNum: 1,
+ pageSize: 10,
+ vehicleId: null,
+ vehicleNo: null,
+ deptId: null,
+ statDate: null
+ },
+ // 鎵嬪姩缁熻琛ㄥ崟
+ calculateForm: {
+ vehicleId: null,
+ statDate: null
+ },
+ // 鎵归噺缁熻琛ㄥ崟
+ batchCalculateForm: {
+ statDate: null
+ },
+ // 鎵嬪姩缁熻琛ㄥ崟鏍¢獙
+ calculateRules: {
+ vehicleId: [
+ { required: true, message: "杞﹁締ID涓嶈兘涓虹┖", trigger: "blur" }
+ ],
+ statDate: [
+ { required: true, message: "缁熻鏃ユ湡涓嶈兘涓虹┖", trigger: "change" }
+ ]
+ },
+ // 鎵归噺缁熻琛ㄥ崟鏍¢獙
+ batchCalculateRules: {
+ statDate: [
+ { required: true, message: "缁熻鏃ユ湡涓嶈兘涓虹┖", trigger: "change" }
+ ]
+ }
+ };
+ },
+ created() {
+ this.getList();
+ this.getDeptList();
+ },
+ methods: {
+ /** 鏌ヨ鍒嗗叕鍙稿垪琛� */
+ getDeptList() {
+ listDept().then(response => {
+ this.deptOptions = response.data || [];
+ });
+ },
+ /** 鏌ヨ杞﹁締閲岀▼缁熻鍒楄〃 */
+ getList() {
+ this.loading = true;
+ listMileageStats(this.addDateRange(this.queryParams, this.dateRange)).then(response => {
+ this.statsList = response.rows;
+ this.total = response.total;
+ this.loading = false;
+ });
+ },
+ /** 鎼滅储鎸夐挳鎿嶄綔 */
+ handleQuery() {
+ this.queryParams.pageNum = 1;
+ this.getList();
+ },
+ /** 閲嶇疆鎸夐挳鎿嶄綔 */
+ resetQuery() {
+ this.dateRange = [];
+ this.resetForm("queryForm");
+ this.handleQuery();
+ },
+ // 澶氶�夋閫変腑鏁版嵁
+ handleSelectionChange(selection) {
+ this.ids = selection.map(item => item.statsId)
+ this.single = selection.length !== 1
+ this.multiple = !selection.length
+ },
+ /** 鎵嬪姩缁熻鎸夐挳鎿嶄綔 */
+ handleCalculate() {
+ this.reset();
+ this.calculateOpen = true;
+ },
+ /** 鎵归噺缁熻鎸夐挳鎿嶄綔 */
+ handleBatchCalculate() {
+ this.batchCalculateForm = {
+ statDate: null
+ };
+ this.batchCalculateOpen = true;
+ },
+ /** 鎻愪氦鎵嬪姩缁熻 */
+ submitCalculate() {
+ this.$refs["calculateForm"].validate(valid => {
+ if (valid) {
+ this.calculateLoading = true;
+ calculateMileageStats(this.calculateForm.vehicleId, this.calculateForm.statDate).then(response => {
+ this.$modal.msgSuccess("閲岀▼缁熻瀹屾垚");
+ this.calculateOpen = false;
+ this.calculateLoading = false;
+ this.getList();
+ }).catch(() => {
+ this.calculateLoading = false;
+ });
+ }
+ });
+ },
+ /** 鎻愪氦鎵归噺缁熻 */
+ submitBatchCalculate() {
+ this.$refs["batchCalculateForm"].validate(valid => {
+ if (valid) {
+ this.$modal.confirm('纭瑕佸鎵�鏈夋椿璺冭溅杈嗚繘琛岄噷绋嬬粺璁″悧锛�').then(() => {
+ this.batchCalculateLoading = true;
+ batchCalculateMileageStats(this.batchCalculateForm.statDate).then(response => {
+ this.$modal.msgSuccess(response.msg || "鎵归噺閲岀▼缁熻瀹屾垚");
+ this.batchCalculateOpen = false;
+ this.batchCalculateLoading = false;
+ this.getList();
+ }).catch(() => {
+ this.batchCalculateLoading = false;
+ });
+ });
+ }
+ });
+ },
+ /** 鏌ョ湅璇︽儏鎸夐挳鎿嶄綔 */
+ handleView(row) {
+ const statsId = row.statsId;
+ getMileageStats(statsId).then(response => {
+ this.detailData = response.data;
+ this.detailOpen = true;
+ });
+ },
+ /** 鍒犻櫎鎸夐挳鎿嶄綔 */
+ handleDelete(row) {
+ const statsIds = row.statsId || this.ids;
+ this.$modal.confirm('鏄惁纭鍒犻櫎閫変腑鐨勮溅杈嗛噷绋嬬粺璁℃暟鎹紵').then(function() {
+ return delMileageStats(statsIds);
+ }).then(() => {
+ this.getList();
+ this.$modal.msgSuccess("鍒犻櫎鎴愬姛");
+ }).catch(() => {});
+ },
+ /** 瀵煎嚭鎸夐挳鎿嶄綔 */
+ handleExport() {
+ this.download('system/mileageStats/export', {
+ ...this.queryParams
+ }, `杞﹁締閲岀▼缁熻_${new Date().getTime()}.xlsx`)
+ },
+ /** 鏍煎紡鍖栦换鍔″崰姣旀樉绀� */
+ formatRatio(ratio) {
+ if (ratio === null || ratio === undefined) {
+ return '0%';
+ }
+ return (ratio * 100).toFixed(2) + '%';
+ },
+ /** 鏍规嵁鍗犳瘮鑾峰彇鏍囩绫诲瀷 */
+ getRatioType(ratio) {
+ if (ratio === null || ratio === undefined) {
+ return 'info';
+ }
+ const percent = ratio * 100;
+ if (percent >= 80) {
+ return 'success';
+ } else if (percent >= 60) {
+ return '';
+ } else if (percent >= 40) {
+ return 'warning';
+ } else {
+ return 'danger';
+ }
+ },
+ // 琛ㄥ崟閲嶇疆
+ reset() {
+ this.calculateForm = {
+ vehicleId: null,
+ statDate: null
+ };
+ this.resetForm("calculateForm");
+ }
+ }
+};
+</script>
+
+<style scoped>
+.mileage-value {
+ font-weight: bold;
+ color: #409EFF;
+}
+
+.task-mileage {
+ color: #67C23A;
+}
+
+.detail-value {
+ font-size: 16px;
+ font-weight: bold;
+}
+</style>
diff --git a/ruoyi-ui/src/views/system/vehicle/index.vue b/ruoyi-ui/src/views/system/vehicle/index.vue
index e1fdf89..40e9ca5 100644
--- a/ruoyi-ui/src/views/system/vehicle/index.vue
+++ b/ruoyi-ui/src/views/system/vehicle/index.vue
@@ -14,7 +14,7 @@
<el-select v-model="queryParams.vehicleType" placeholder="璇烽�夋嫨杞﹁締绫诲瀷" clearable size="small">
<el-option
v-for="dict in dict.type.sys_vehicle_type"
- :key="dict.value"
+ :key="'vtype-' + dict.value"
:label="dict.label"
:value="dict.value"
/>
@@ -24,7 +24,7 @@
<el-select v-model="queryParams.status" placeholder="璇烽�夋嫨鐘舵��" clearable size="small">
<el-option
v-for="dict in dict.type.sys_normal_disable"
- :key="dict.value"
+ :key="'status-' + dict.value"
:label="dict.label"
:value="dict.value"
/>
@@ -34,7 +34,7 @@
<el-select v-model="queryParams.platformCode" placeholder="璇烽�夋嫨骞冲彴" clearable size="small">
<el-option
v-for="dict in dict.type.sys_platform"
- :key="dict.value"
+ :key="'platform-' + dict.value"
:label="dict.label"
:value="dict.value"
/>
@@ -174,7 +174,7 @@
<el-select v-model="form.vehicleType" placeholder="璇烽�夋嫨杞﹁締绫诲瀷" clearable style="width: 100%">
<el-option
v-for="dict in dict.type.sys_vehicle_type"
- :key="dict.value"
+ :key="'form-vtype-' + dict.value"
:label="dict.label"
:value="dict.value"
/>
@@ -190,7 +190,7 @@
<el-select v-model="form.platformCode" placeholder="璇烽�夋嫨骞冲彴" clearable>
<el-option
v-for="dict in dict.type.sys_platform"
- :key="dict.value"
+ :key="'form-platform-' + dict.value"
:label="dict.label"
:value="dict.value"
/>
@@ -200,7 +200,7 @@
<el-radio-group v-model="form.status">
<el-radio
v-for="dict in dict.type.sys_normal_disable"
- :key="dict.value"
+ :key="'form-status-' + dict.value"
:label="dict.value"
>{{dict.label}}</el-radio>
</el-radio-group>
@@ -315,9 +315,12 @@
/** 鑾峰彇閮ㄩ棬鍒楄〃锛堝彧鏄剧ず鍒嗗叕鍙革細parent_id=100锛� */
getDeptList() {
listDept({ parentId: 100 }).then(response => {
+
// 杩囨护鍑哄垎鍏徃锛坧arent_id=100鐨勯儴闂級
if (response.data) {
- this.deptList = response.data.filter(dept => dept.parentId === 100);
+
+ this.deptList = response.data.filter(dept => dept.parentId === "100");
+ // console.log("deptList: ",this.deptList,response.data.filter(dept => dept.parentId === "100"));
} else {
this.deptList = [];
}
@@ -372,13 +375,13 @@
const vehicleId = row.vehicleId || this.ids
getVehicle(vehicleId).then(response => {
this.form = response.data;
- // 濡傛灉娌℃湁deptIds锛屽垯浠� deptId 鍜� deptName 涓濉�
- if (!this.form.deptIds || this.form.deptIds.length === 0) {
- if (this.form.deptId) {
- this.form.deptIds = [this.form.deptId];
- } else {
- this.form.deptIds = [];
- }
+ // 纭繚deptIds鏄竴涓暟缁�
+ if (!this.form.deptIds) {
+ this.form.deptIds = [];
+ }
+ // 濡傛灉deptIds涓虹┖鏁扮粍锛屼絾deptId鏈夊�硷紝鍒欐坊鍔燿eptId鍒癲eptIds涓�
+ if (this.form.deptIds.length === 0 && this.form.deptId) {
+ this.form.deptIds = [this.form.deptId];
}
this.open = true;
this.title = "淇敼杞﹁締淇℃伅";
diff --git a/ruoyi-ui/src/views/task/detail/index.vue b/ruoyi-ui/src/views/task/detail/index.vue
new file mode 100644
index 0000000..90d608e
--- /dev/null
+++ b/ruoyi-ui/src/views/task/detail/index.vue
@@ -0,0 +1,112 @@
+<template>
+ <div class="app-container">
+ <el-page-header @back="goBack" content="浠诲姟璇︽儏">
+ </el-page-header>
+
+ <el-card class="box-card" style="margin-top: 20px;" shadow="hover">
+ <div slot="header" class="clearfix">
+ <span><i class="el-icon-document"></i> 浠诲姟鍩烘湰淇℃伅</span>
+ </div>
+
+ <el-descriptions :column="2" border>
+ <el-descriptions-item label="浠诲姟缂栧彿">{{ taskInfo.taskCode }}</el-descriptions-item>
+ <el-descriptions-item label="浠诲姟鐘舵��">
+ <el-tag :type="getStatusType(taskInfo.taskStatus)">
+ {{ getStatusText(taskInfo.taskStatus) }}
+ </el-tag>
+ </el-descriptions-item>
+ <el-descriptions-item label="杞︾墝鍙�">{{ taskInfo.vehicleNo }}</el-descriptions-item>
+ <el-descriptions-item label="鎵ц浜�">{{ taskInfo.assigneeName }}</el-descriptions-item>
+ <el-descriptions-item label="璁″垝寮�濮嬫椂闂�">{{ taskInfo.plannedStartTime }}</el-descriptions-item>
+ <el-descriptions-item label="璁″垝缁撴潫鏃堕棿">{{ taskInfo.plannedEndTime }}</el-descriptions-item>
+ <el-descriptions-item label="瀹為檯寮�濮嬫椂闂�">{{ taskInfo.actualStartTime || '-' }}</el-descriptions-item>
+ <el-descriptions-item label="瀹為檯缁撴潫鏃堕棿">{{ taskInfo.actualEndTime || '-' }}</el-descriptions-item>
+ <el-descriptions-item label="鍑哄彂鍦板潃" :span="2">{{ taskInfo.departureAddress }}</el-descriptions-item>
+ <el-descriptions-item label="鐩殑鍦板潃" :span="2">{{ taskInfo.destinationAddress }}</el-descriptions-item>
+ </el-descriptions>
+ </el-card>
+
+ <!-- GPS閲岀▼缁熻缁勪欢 -->
+ <task-mileage-detail v-if="taskInfo.taskId" :task-id="taskInfo.taskId" />
+ </div>
+</template>
+
+<script>
+import TaskMileageDetail from '@/components/TaskMileageDetail'
+import { getTask } from '@/api/task'
+
+export default {
+ name: 'TaskDetail',
+ components: {
+ TaskMileageDetail
+ },
+ data() {
+ return {
+ taskInfo: {
+ taskId: null,
+ taskCode: '',
+ taskStatus: '',
+ vehicleNo: '',
+ assigneeName: '',
+ plannedStartTime: '',
+ plannedEndTime: '',
+ actualStartTime: '',
+ actualEndTime: '',
+ departureAddress: '',
+ destinationAddress: ''
+ }
+ }
+ },
+ created() {
+ const taskId = this.$route.params && this.$route.params.taskId
+ if (taskId) {
+ this.loadTaskInfo(taskId)
+ }
+ },
+ methods: {
+ /** 鍔犺浇浠诲姟淇℃伅 */
+ loadTaskInfo(taskId) {
+ getTask(taskId).then(response => {
+ this.taskInfo = response.data
+ })
+ },
+
+ /** 杩斿洖 */
+ goBack() {
+ this.$router.go(-1)
+ },
+
+ /** 鑾峰彇鐘舵�佺被鍨� */
+ getStatusType(status) {
+ const statusMap = {
+ 'PENDING': 'info',
+ 'DEPARTING': 'warning',
+ 'ARRIVED': 'primary',
+ 'RETURNING': 'warning',
+ 'COMPLETED': 'success',
+ 'CANCELLED': 'danger'
+ }
+ return statusMap[status] || 'info'
+ },
+
+ /** 鑾峰彇鐘舵�佹枃鏈� */
+ getStatusText(status) {
+ const statusMap = {
+ 'PENDING': '寰呭嚭鍙�',
+ 'DEPARTING': '鍑哄彂涓�',
+ 'ARRIVED': '宸插埌杈�',
+ 'RETURNING': '杩旂▼涓�',
+ 'COMPLETED': '宸插畬鎴�',
+ 'CANCELLED': '宸插彇娑�'
+ }
+ return statusMap[status] || status
+ }
+ }
+}
+</script>
+
+<style scoped>
+.app-container {
+ padding: 20px;
+}
+</style>
diff --git a/sql/updates/add_segment_count_to_mileage_stats.sql b/sql/updates/add_segment_count_to_mileage_stats.sql
new file mode 100644
index 0000000..9cad4f0
--- /dev/null
+++ b/sql/updates/add_segment_count_to_mileage_stats.sql
@@ -0,0 +1,26 @@
+-- 涓鸿溅杈嗛噷绋嬬粺璁¤〃娣诲姞鍒嗘鏁伴噺瀛楁
+-- 鐢ㄩ�旓細璁板綍褰撴棩鍏宠仈鐨凣PS鍒嗘鏁伴噺锛岀敤浜庢暟鎹畬鏁存�ф牎楠屽拰涓氬姟鐩戞帶
+
+USE `ry-vue`;
+
+-- 妫�鏌ュ瓧娈垫槸鍚﹀瓨鍦紝濡傛灉涓嶅瓨鍦ㄥ垯娣诲姞
+SET @dbname = DATABASE();
+SET @tablename = 'tb_vehicle_mileage_stats';
+SET @columnname = 'segment_count';
+SET @preparedStatement = (SELECT IF(
+ (
+ SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS
+ WHERE
+ (TABLE_SCHEMA = @dbname)
+ AND (TABLE_NAME = @tablename)
+ AND (COLUMN_NAME = @columnname)
+ ) > 0,
+ 'SELECT 1',
+ CONCAT('ALTER TABLE ', @tablename, ' ADD COLUMN `segment_count` int(11) DEFAULT 0 COMMENT ''鍏宠仈鐨勫垎娈垫暟閲�'' AFTER `task_count`;')
+));
+PREPARE alterIfNotExists FROM @preparedStatement;
+EXECUTE alterIfNotExists;
+DEALLOCATE PREPARE alterIfNotExists;
+
+-- 楠岃瘉瀛楁鏄惁娣诲姞鎴愬姛
+DESC tb_vehicle_mileage_stats;
diff --git a/sql/updates/add_task_id_to_segment_mileage.sql b/sql/updates/add_task_id_to_segment_mileage.sql
new file mode 100644
index 0000000..cfb8a8a
--- /dev/null
+++ b/sql/updates/add_task_id_to_segment_mileage.sql
@@ -0,0 +1,27 @@
+-- 涓鸿溅杈咷PS鍒嗘閲岀▼琛ㄦ坊鍔犱换鍔″叧鑱斿瓧娈�
+-- 鐢ㄩ�旓細鍦ㄨ绠桮PS閲岀▼鏃讹紝鑷姩鍏宠仈杞﹁締姝e湪鎵ц鐨勪换鍔★紝鏂逛究缁熻浠诲姟閲岀▼
+
+USE ry-vue;
+
+-- 娣诲姞浠诲姟ID瀛楁
+ALTER TABLE tb_vehicle_gps_segment_mileage
+ADD COLUMN task_id BIGINT(20) NULL COMMENT '鍏宠仈鐨勪换鍔D' AFTER gps_ids;
+
+-- 娣诲姞浠诲姟缂栧彿瀛楁
+ALTER TABLE tb_vehicle_gps_segment_mileage
+ADD COLUMN task_code VARCHAR(50) NULL COMMENT '鍏宠仈鐨勪换鍔$紪鍙�' AFTER task_id;
+
+-- 鍒涘缓绱㈠紩锛屾彁鍗囨寜浠诲姟ID鏌ヨ鐨勬�ц兘
+CREATE INDEX idx_task_id ON tb_vehicle_gps_segment_mileage(task_id);
+
+-- 鍒涘缓澶嶅悎绱㈠紩锛屾彁鍗囨寜杞﹁締ID鍜屼换鍔D缁勫悎鏌ヨ鐨勬�ц兘
+CREATE INDEX idx_vehicle_task ON tb_vehicle_gps_segment_mileage(vehicle_id, task_id);
+
+-- 鏌ョ湅琛ㄧ粨鏋勭‘璁�
+DESC tb_vehicle_gps_segment_mileage;
+
+-- 浣跨敤璇存槑锛�
+-- 1. 杩愯姝QL鑴氭湰娣诲姞瀛楁
+-- 2. 閲嶆柊璁$畻GPS閲岀▼鏃讹紝绯荤粺浼氳嚜鍔ㄥ叧鑱斾换鍔D
+-- 3. 缁熻浠诲姟閲岀▼鏃讹紝鍙洿鎺ラ�氳繃task_id鏌ヨ锛�
+-- SELECT SUM(segment_distance) FROM tb_vehicle_gps_segment_mileage WHERE task_id = ?
diff --git a/sql/updates/remove_dept_id_from_vehicle_info.sql b/sql/updates/remove_dept_id_from_vehicle_info.sql
new file mode 100644
index 0000000..2777111
--- /dev/null
+++ b/sql/updates/remove_dept_id_from_vehicle_info.sql
@@ -0,0 +1,26 @@
+-- 绉婚櫎tb_vehicle_info琛ㄧ殑dept_id瀛楁
+-- 璇存槑锛氳溅杈嗕笌閮ㄩ棬鐨勫叧鑱斿叧绯荤幇宸插畬鍏ㄨ縼绉诲埌tb_vehicle_dept澶氬澶氬叧绯昏〃
+
+-- 1. 鏁版嵁杩佺Щ妫�鏌ワ紙纭繚鎵�鏈夋暟鎹凡杩佺Щ鍒皌b_vehicle_dept琛級
+SELECT '鏁版嵁杩佺Щ妫�鏌�' AS 妫�鏌ラ」,
+ (SELECT COUNT(*) FROM tb_vehicle_info WHERE dept_id IS NOT NULL) AS 鍘熻〃鏈塪ept_id鐨勮褰曟暟,
+ (SELECT COUNT(DISTINCT vehicle_id) FROM tb_vehicle_dept) AS 鍏宠仈琛ㄤ腑鐨勮溅杈嗘暟;
+
+-- 2. 鏌ョ湅鏈縼绉荤殑鏁版嵁锛堝鏋滄湁锛�
+SELECT vehicle_id, vehicle_no, dept_id, '鏈縼绉诲埌tb_vehicle_dept' AS 鐘舵��
+FROM tb_vehicle_info
+WHERE dept_id IS NOT NULL
+ AND vehicle_id NOT IN (SELECT DISTINCT vehicle_id FROM tb_vehicle_dept);
+
+-- 3. 濡傛灉涓婇潰鏈夋湭杩佺Щ鐨勬暟鎹紝鍏堟墽琛岃縼绉伙紙鍙�夛級
+-- INSERT INTO tb_vehicle_dept (vehicle_id, dept_id, create_time)
+-- SELECT vehicle_id, dept_id, NOW()
+-- FROM tb_vehicle_info
+-- WHERE dept_id IS NOT NULL
+-- AND vehicle_id NOT IN (SELECT DISTINCT vehicle_id FROM tb_vehicle_dept);
+
+-- 4. 鍒犻櫎dept_id瀛楁
+ALTER TABLE tb_vehicle_info DROP COLUMN dept_id;
+
+-- 5. 楠岃瘉瀛楁宸插垹闄�
+SHOW COLUMNS FROM tb_vehicle_info;
diff --git a/sql/updates/update_vehicle_no_from_vehicle_info.sql b/sql/updates/update_vehicle_no_from_vehicle_info.sql
new file mode 100644
index 0000000..9b436af
--- /dev/null
+++ b/sql/updates/update_vehicle_no_from_vehicle_info.sql
@@ -0,0 +1,25 @@
+-- 鏇存柊鐜版湁鐨勭┖vehicle_no鏁版嵁
+-- 浠巘b_vehicle_info琛ㄥ悓姝ヨ溅鐗屽彿鍒皌b_vehicle_gps_segment_mileage鍜宼b_vehicle_mileage_stats
+
+-- 1. 鏇存柊GPS鍒嗘閲岀▼琛ㄧ殑vehicle_no
+UPDATE tb_vehicle_gps_segment_mileage seg
+INNER JOIN tb_vehicle_info v ON seg.vehicle_id = v.vehicle_id
+SET seg.vehicle_no = v.vehicle_no
+WHERE seg.vehicle_no IS NULL OR seg.vehicle_no = '';
+
+-- 2. 鏇存柊閲岀▼缁熻琛ㄧ殑vehicle_no
+UPDATE tb_vehicle_mileage_stats stats
+INNER JOIN tb_vehicle_info v ON stats.vehicle_id = v.vehicle_id
+SET stats.vehicle_no = v.vehicle_no
+WHERE stats.vehicle_no IS NULL OR stats.vehicle_no = '';
+
+-- 鏌ヨ鏇存柊缁撴灉
+SELECT '鏇存柊GPS鍒嗘閲岀▼琛�' AS 琛ㄥ悕, COUNT(*) AS 璁板綍鏁�,
+ SUM(CASE WHEN vehicle_no IS NOT NULL AND vehicle_no != '' THEN 1 ELSE 0 END) AS 宸叉湁杞︾墝鍙�,
+ SUM(CASE WHEN vehicle_no IS NULL OR vehicle_no = '' THEN 1 ELSE 0 END) AS 鏃犺溅鐗屽彿
+FROM tb_vehicle_gps_segment_mileage
+UNION ALL
+SELECT '鏇存柊閲岀▼缁熻琛�' AS 琛ㄥ悕, COUNT(*) AS 璁板綍鏁�,
+ SUM(CASE WHEN vehicle_no IS NOT NULL AND vehicle_no != '' THEN 1 ELSE 0 END) AS 宸叉湁杞︾墝鍙�,
+ SUM(CASE WHEN vehicle_no IS NULL OR vehicle_no = '' THEN 1 ELSE 0 END) AS 鏃犺溅鐗屽彿
+FROM tb_vehicle_mileage_stats;
diff --git a/sql/vehicle_gps_segment_mileage.sql b/sql/vehicle_gps_segment_mileage.sql
new file mode 100644
index 0000000..17e077a
--- /dev/null
+++ b/sql/vehicle_gps_segment_mileage.sql
@@ -0,0 +1,49 @@
+-- 杞﹁締GPS鍒嗘閲岀▼琛紙鎸�5鍒嗛挓鏃堕棿娈电粺璁★級
+CREATE TABLE `tb_vehicle_gps_segment_mileage` (
+ `segment_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '鍒嗘ID',
+ `vehicle_id` bigint(20) NOT NULL COMMENT '杞﹁締ID',
+ `vehicle_no` varchar(20) DEFAULT NULL COMMENT '杞︾墝鍙�',
+ `segment_start_time` datetime NOT NULL COMMENT '鏃堕棿娈靛紑濮嬫椂闂�',
+ `segment_end_time` datetime NOT NULL COMMENT '鏃堕棿娈电粨鏉熸椂闂�',
+ `start_longitude` decimal(10,7) DEFAULT NULL COMMENT '璧风偣缁忓害',
+ `start_latitude` decimal(10,7) DEFAULT NULL COMMENT '璧风偣绾害',
+ `end_longitude` decimal(10,7) DEFAULT NULL COMMENT '缁堢偣缁忓害',
+ `end_latitude` decimal(10,7) DEFAULT NULL COMMENT '缁堢偣绾害',
+ `segment_distance` decimal(10,3) DEFAULT 0.000 COMMENT '娈佃窛绂�(鍏噷)',
+ `gps_point_count` int(11) DEFAULT 0 COMMENT 'GPS鐐规暟閲�',
+ `gps_ids` text COMMENT '鍏宠仈鐨凣PS璁板綍ID鍒楄〃锛堥�楀彿鍒嗛殧锛�',
+ `calculate_method` varchar(20) DEFAULT 'tianditu' COMMENT '璁$畻鏂瑰紡(tianditu-澶╁湴鍥�/haversine-鐞冮潰璺濈)',
+ `create_time` datetime COMMENT '鍒涘缓鏃堕棿',
+ `update_time` datetime COMMENT '鏇存柊鏃堕棿',
+ PRIMARY KEY (`segment_id`),
+ UNIQUE KEY `uk_vehicle_time` (`vehicle_id`, `segment_start_time`),
+ KEY `idx_vehicle_id` (`vehicle_id`),
+ KEY `idx_start_time` (`segment_start_time`),
+ KEY `idx_vehicle_date` (`vehicle_id`, `segment_start_time`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='杞﹁締GPS鍒嗘閲岀▼琛�';
+
+-- GPS璁板綍璁$畻鐘舵�佸叧鑱旇〃锛堢敤浜庡揩閫熸煡璇PS鐐规槸鍚﹀凡琚绠楋級
+CREATE TABLE `tb_vehicle_gps_calculated` (
+ `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '涓婚敭ID',
+ `gps_id` bigint(20) NOT NULL COMMENT 'GPS璁板綍ID',
+ `segment_id` bigint(20) NOT NULL COMMENT '鍒嗘閲岀▼ID',
+ `vehicle_id` bigint(20) NOT NULL COMMENT '杞﹁締ID',
+ `create_time` datetime COMMENT '璁$畻鏃堕棿',
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `uk_gps_id` (`gps_id`),
+ KEY `idx_segment_id` (`segment_id`),
+ KEY `idx_vehicle_id` (`vehicle_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='GPS璁板綍璁$畻鐘舵�佽〃';
+
+-- 娣诲姞閰嶇疆鍙傛暟鍒扮郴缁熼厤缃〃
+INSERT INTO sys_config (config_name, config_key, config_value, config_type, remark, create_by, create_time)
+VALUES ('GPS閲岀▼璁$畻鏃堕棿闂撮殧', 'gps.mileage.segment.minutes', '5', 'Y', 'GPS閲岀▼璁$畻鐨勬椂闂撮棿闅旓紙鍒嗛挓锛夛紝鐢ㄤ簬灏咷PS鏁版嵁鍒嗘璁$畻閲岀▼', 'admin', NOW())
+ON DUPLICATE KEY UPDATE config_value = config_value;
+
+INSERT INTO sys_config (config_name, config_key, config_value, config_type, remark, create_by, create_time)
+VALUES ('GPS閲岀▼璁$畻鏂瑰紡', 'gps.mileage.calculate.method', 'tianditu', 'Y', 'GPS閲岀▼璁$畻鏂瑰紡锛歵ianditu-澶╁湴鍥続PI锛宧aversine-鐞冮潰璺濈鍏紡', 'admin', NOW())
+ON DUPLICATE KEY UPDATE config_value = config_value;
+
+INSERT INTO sys_config (config_name, config_key, config_value, config_type, remark, create_by, create_time)
+VALUES ('GPS閲岀▼閲嶅璁$畻鎺у埗', 'gps.mileage.skip.calculated', 'true', 'Y', '鏄惁璺宠繃宸茶绠楃殑GPS鐐癸細true-璺宠繃锛宖alse-鍏佽閲嶅璁$畻', 'admin', NOW())
+ON DUPLICATE KEY UPDATE config_value = config_value;
diff --git a/sql/vehicle_gps_segment_mileage_job.sql b/sql/vehicle_gps_segment_mileage_job.sql
new file mode 100644
index 0000000..829efff
--- /dev/null
+++ b/sql/vehicle_gps_segment_mileage_job.sql
@@ -0,0 +1,59 @@
+-- 杞﹁締GPS鍒嗘閲岀▼璁$畻瀹氭椂浠诲姟閰嶇疆
+
+-- 1. 瀹炴椂璁$畻浠诲姟锛堟瘡5鍒嗛挓鎵ц涓�娆★紝璁$畻鏈�杩�10鍒嗛挓鐨勬暟鎹級
+INSERT INTO sys_job (job_name, job_group, invoke_target, cron_expression, misfire_policy, concurrent, status, create_by, create_time, remark)
+VALUES (
+ 'GPS鍒嗘閲岀▼瀹炴椂璁$畻',
+ 'DEFAULT',
+ 'vehicleGpsSegmentMileageTask.calculateRecentSegmentMileage(''10'')',
+ '0 0/5 * * * ?',
+ '2',
+ '0',
+ '1',
+ 'admin',
+ NOW(),
+ '姣�5鍒嗛挓鎵ц涓�娆★紝璁$畻鏈�杩�10鍒嗛挓鍐呮墍鏈夎溅杈嗙殑GPS鍒嗘閲岀▼銆傚弬鏁�10琛ㄧず璁$畻鏈�杩�10鍒嗛挓鐨勬暟鎹紝鍙牴鎹渶瑕佽皟鏁�'
+);
+
+-- 2. 姣忓皬鏃舵眹鎬讳换鍔★紙姣忓皬鏃舵墽琛屼竴娆★紝璁$畻鏈�杩�1灏忔椂鐨勬暟鎹級
+INSERT INTO sys_job (job_name, job_group, invoke_target, cron_expression, misfire_policy, concurrent, status, create_by, create_time, remark)
+VALUES (
+ 'GPS鍒嗘閲岀▼灏忔椂姹囨��',
+ 'DEFAULT',
+ 'vehicleGpsSegmentMileageTask.calculateRecentSegmentMileage(''60'')',
+ '0 5 * * * ?',
+ '2',
+ '0',
+ '1',
+ 'admin',
+ NOW(),
+ '姣忓皬鏃剁5鍒嗛挓鎵ц锛岃绠楁渶杩�60鍒嗛挓鍐呮墍鏈夎溅杈嗙殑GPS鍒嗘閲岀▼锛岀敤浜庤ˉ鍏呴仐婕忕殑鏁版嵁'
+);
+
+-- 3. 姣忔棩鍏ㄥぉ璁$畻浠诲姟锛堟瘡澶╁噷鏅ㄦ墽琛岋紝璁$畻鍓嶄竴澶╃殑鏁版嵁锛�
+INSERT INTO sys_job (job_name, job_group, invoke_target, cron_expression, misfire_policy, concurrent, status, create_by, create_time, remark)
+VALUES (
+ 'GPS鍒嗘閲岀▼姣忔棩姹囨��',
+ 'DEFAULT',
+ 'vehicleGpsSegmentMileageTask.calculateDateSegmentMileage(DATE_FORMAT(DATE_SUB(NOW(), INTERVAL 1 DAY), ''%Y-%m-%d''))',
+ '0 10 1 * * ?',
+ '2',
+ '0',
+ '1',
+ 'admin',
+ NOW(),
+ '姣忓ぉ鍑屾櫒1鐐�10鍒嗘墽琛岋紝璁$畻鍓嶄竴澶╂墍鏈夎溅杈嗙殑GPS鍒嗘閲岀▼銆備娇鐢―ATE_FORMAT鍔ㄦ�佽绠楀墠涓�澶╃殑鏃ユ湡'
+);
+
+-- 娉ㄦ剰浜嬮」璇存槑
+-- 1. 榛樿鎵�鏈変换鍔$姸鎬佷负'1'锛堟殏鍋滐級锛岄渶瑕佸湪鍚庡彴绠$悊绯荤粺涓墜鍔ㄥ惎鍔�
+-- 2. 鏃堕棿闂撮殧閰嶇疆鍦� sys_config 琛ㄤ腑锛歡ps.mileage.segment.minutes锛堥粯璁�5鍒嗛挓锛�
+-- 3. 璁$畻鏂瑰紡閰嶇疆鍦� sys_config 琛ㄤ腑锛歡ps.mileage.calculate.method锛堥粯璁ianditu锛�
+-- 4. 寤鸿鍚姩椤哄簭锛�
+-- - 鍏堝惎鍔�"GPS鍒嗘閲岀▼瀹炴椂璁$畻"锛岃瀵熻繍琛屾儏鍐�
+-- - 纭鏃犺鍚庯紝鍐嶅惎鍔�"GPS鍒嗘閲岀▼灏忔椂姹囨��"浣滀负琛ュ厖
+-- - 鏈�鍚庡惎鍔�"GPS鍒嗘閲岀▼姣忔棩姹囨��"浣滀负鏁版嵁瀹屾暣鎬т繚闅�
+-- 5. cron琛ㄨ揪寮忚鏄庯細
+-- - '0 0/5 * * * ?' : 姣�5鍒嗛挓鎵ц涓�娆�
+-- - '0 5 * * * ?' : 姣忓皬鏃剁5鍒嗛挓鎵ц
+-- - '0 10 1 * * ?' : 姣忓ぉ鍑屾櫒1鐐�10鍒嗘墽琛�
diff --git a/sql/vehicle_mileage_stats.sql b/sql/vehicle_mileage_stats.sql
index 25d9667..eb5db0b 100644
--- a/sql/vehicle_mileage_stats.sql
+++ b/sql/vehicle_mileage_stats.sql
@@ -10,6 +10,8 @@
`task_ratio` decimal(5,4) DEFAULT 0.0000 COMMENT '浠诲姟閲岀▼鍗犳瘮(0-1)',
`gps_point_count` int(11) DEFAULT 0 COMMENT 'GPS鐐规暟閲�',
`task_count` int(11) DEFAULT 0 COMMENT '浠诲姟鏁伴噺',
+ `segment_count` int(11) DEFAULT 0 COMMENT '鍏宠仈鐨勫垎娈垫暟閲�',
+ `data_source` varchar(20) DEFAULT 'gps' COMMENT '鏁版嵁鏉ユ簮(segment-浠庡垎娈垫眹鎬�,gps-鐩存帴璁$畻)',
`create_time` datetime DEFAULT NULL COMMENT '鍒涘缓鏃堕棿',
`update_time` datetime DEFAULT NULL COMMENT '鏇存柊鏃堕棿',
PRIMARY KEY (`stats_id`),
@@ -17,24 +19,3 @@
KEY `idx_vehicle_id` (`vehicle_id`),
KEY `idx_stat_date` (`stat_date`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='杞﹁締閲岀▼缁熻琛�';
-
--- 杞﹁締閲岀▼缁熻鏄庣粏琛紙鍙�夛紝鐢ㄤ簬璋冭瘯鍜岃拷婧級
-CREATE TABLE `tb_vehicle_mileage_detail` (
- `detail_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '鏄庣粏ID',
- `stats_id` bigint(20) NOT NULL COMMENT '缁熻ID',
- `vehicle_id` bigint(20) NOT NULL COMMENT '杞﹁締ID',
- `segment_start_time` datetime NOT NULL COMMENT '娈佃捣濮嬫椂闂�',
- `segment_end_time` datetime NOT NULL COMMENT '娈电粨鏉熸椂闂�',
- `start_longitude` decimal(10,7) DEFAULT NULL COMMENT '璧风偣缁忓害',
- `start_latitude` decimal(10,7) DEFAULT NULL COMMENT '璧风偣绾害',
- `end_longitude` decimal(10,7) DEFAULT NULL COMMENT '缁堢偣缁忓害',
- `end_latitude` decimal(10,7) DEFAULT NULL COMMENT '缁堢偣绾害',
- `segment_distance` decimal(10,3) DEFAULT 0.000 COMMENT '娈佃窛绂�(鍏噷)',
- `task_distance` decimal(10,3) DEFAULT 0.000 COMMENT '浠诲姟鍐呰窛绂�(鍏噷)',
- `non_task_distance` decimal(10,3) DEFAULT 0.000 COMMENT '浠诲姟澶栬窛绂�(鍏噷)',
- `is_in_task` tinyint(1) DEFAULT 0 COMMENT '鏄惁瀹屽叏鍦ㄤ换鍔℃椂娈靛唴(0-鍚�,1-鏄�,2-閮ㄥ垎)',
- `create_time` datetime DEFAULT NULL COMMENT '鍒涘缓鏃堕棿',
- PRIMARY KEY (`detail_id`),
- KEY `idx_stats_id` (`stats_id`),
- KEY `idx_vehicle_id` (`vehicle_id`)
-) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='杞﹁締閲岀▼缁熻鏄庣粏琛�';
diff --git a/sql/vehicle_mileage_stats_job.sql b/sql/vehicle_mileage_stats_job.sql
index 2629623..a0ae74c 100644
--- a/sql/vehicle_mileage_stats_job.sql
+++ b/sql/vehicle_mileage_stats_job.sql
@@ -1,17 +1,17 @@
--- 娣诲姞杞﹁締閲岀▼缁熻瀹氭椂浠诲姟
+-- 娣诲姞杞﹁締閲岀▼缁熻瀹氭椂浠诲姟锛堜粠GPS鍒嗘姹囨�伙級
INSERT INTO sys_job (job_id, job_name, job_group, invoke_target, cron_expression, misfire_policy, concurrent, status, create_by, create_time, update_by, update_time, remark)
VALUES (
(SELECT IFNULL(MAX(job_id), 0) + 1 FROM sys_job t),
- '杞﹁締閲岀▼缁熻浠诲姟',
+ '杞﹁締閲岀▼缁熻浠诲姟锛堜粠鍒嗘姹囨�伙級',
'DEFAULT',
- 'vehicleMileageStatsTask.calculateYesterdayMileage',
+ 'vehicleMileageStatsTask.aggregateYesterdayFromSegments',
'0 30 1 * * ?',
'3',
'1',
- '0',
+ '1',
'admin',
NOW(),
'',
NULL,
- '姣忓ぉ鍑屾櫒1:30鎵ц锛岀粺璁℃槰鏃ユ墍鏈夎溅杈嗙殑琛岄┒閲岀▼'
+ '姣忓ぉ鍑屾櫒1:30鎵ц锛屼粠GPS鍒嗘閲岀▼姹囨�荤敓鎴愭槰鏃ョ粺璁℃暟鎹紙鎺ㄨ崘鏂瑰紡锛屾�ц兘鏇村ソ锛�'
);
--
Gitblit v1.9.1