From caf56217dc2bf898b63b0e1f31a7098202c32825 Mon Sep 17 00:00:00 2001
From: wlzboy <66905212@qq.com>
Date: 星期六, 15 十一月 2025 16:50:17 +0800
Subject: [PATCH] Merge branch 'feature_gps'

---
 ruoyi-system/src/main/resources/mapper/system/VehicleGpsMapper.xml                           |   17 
 sql/车辆里程统计实现总结.md                                                                            |  339 ++++++++
 sql/vehicle_mileage_stats_job.sql                                                            |   17 
 ruoyi-system/src/main/resources/mapper/system/VehicleMileageStatsMapper.xml                  |  117 ++-
 sql/vehicle_mileage_stats.sql                                                                |   47 
 ruoyi-quartz/src/main/java/com/ruoyi/quartz/task/VehicleMileageStatsTask.java                |   84 ++
 ruoyi-system/src/main/java/com/ruoyi/system/service/impl/VehicleMileageStatsServiceImpl.java |  296 +++++++
 ruoyi-system/src/main/java/com/ruoyi/system/mapper/VehicleGpsMapper.java                     |   21 
 ruoyi-admin/src/main/resources/application.yml                                               |    5 
 ruoyi-system/src/main/java/com/ruoyi/system/service/IVehicleMileageStatsService.java         |   76 ++
 sql/车辆里程统计使用说明.md                                                                            |  202 +++++
 sql/vehicle_mileage_stats_menu.sql                                                           |   56 +
 app/api/mileageStats.js                                                                      |   67 +
 ruoyi-system/src/main/java/com/ruoyi/system/mapper/VehicleMileageStatsMapper.java            |   41 
 ruoyi-common/src/main/java/com/ruoyi/common/config/TiandituMapConfig.java                    |   25 
 ruoyi-system/src/main/java/com/ruoyi/system/domain/TaskTimeInterval.java                     |   51 +
 ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/VehicleGpsController.java          |  394 ++++++++++
 ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/VehicleMileageStatsController.java |  135 +++
 app/api/map.js                                                                               |  123 +++
 ruoyi-system/src/main/java/com/ruoyi/system/domain/VehicleMileageStats.java                  |  151 +--
 20 files changed, 2,107 insertions(+), 157 deletions(-)

diff --git a/app/api/map.js b/app/api/map.js
index 05acf2e..bf9cc31 100644
--- a/app/api/map.js
+++ b/app/api/map.js
@@ -143,4 +143,127 @@
       region: region || '骞垮窞'
     }
   })
+}
+
+// ==================== 澶╁湴鍥炬帴鍙� ====================
+
+// 澶╁湴鍥惧湴鐞嗙紪鐮丄PI锛堝湴鍧�杞潗鏍囷級
+export function tiandituGeocoding(address) {
+  // 鍙傛暟楠岃瘉
+  if (!address) {
+    return Promise.reject(new Error('鍙傛暟涓嶅畬鏁�,缂哄皯鍦板潃淇℃伅'))
+  }
+  
+  return request({
+    url: '/system/gps/tianditu/geocoding',
+    method: 'get',
+    params: {
+      address: address
+    }
+  })
+}
+
+// 澶╁湴鍥鹃�嗗湴鐞嗙紪鐮丄PI锛堝潗鏍囪浆鍦板潃锛�
+export function tiandituReverseGeocoding(lon, lat) {
+  // 鍙傛暟楠岃瘉
+  if (lat === undefined || lat === null || lon === undefined || lon === null) {
+    return Promise.reject(new Error('鍙傛暟涓嶅畬鏁�,缂哄皯缁忕含搴﹀潗鏍�'))
+  }
+  
+  // 妫�鏌ュ弬鏁版湁鏁堟��
+  if (isNaN(lat) || isNaN(lon)) {
+    return Promise.reject(new Error('鍙傛暟鏃犳晥,缁忕含搴﹀潗鏍囨牸寮忛敊璇�'))
+  }
+  
+  return request({
+    url: '/system/gps/tianditu/reverseGeocoding',
+    method: 'get',
+    params: {
+      lon: lon,
+      lat: lat
+    }
+  })
+}
+
+// 澶╁湴鍥綪OI鎼滅储API
+export function tiandituPlaceSearch(keyWord, queryType, level, mapBound, start, count) {
+  // 鍙傛暟楠岃瘉
+  if (!keyWord) {
+    return Promise.reject(new Error('鍙傛暟涓嶅畬鏁�,缂哄皯鎼滅储鍏抽敭璇�'))
+  }
+  
+  return request({
+    url: '/system/gps/tianditu/place/search',
+    method: 'get',
+    params: {
+      keyWord: keyWord,
+      queryType: queryType || '1',
+      level: level,
+      mapBound: mapBound,
+      start: start || 0,
+      count: count || 10
+    }
+  })
+}
+
+// 澶╁湴鍥鹃┚杞﹁矾寰勮鍒扐PI
+export function tiandituRouteDriving(orig, dest, mid, style) {
+  // 鍙傛暟楠岃瘉
+  if (!orig || !dest) {
+    return Promise.reject(new Error('鍙傛暟涓嶅畬鏁�,缂哄皯璧风偣鎴栫粓鐐瑰潗鏍�'))
+  }
+  
+  // 楠岃瘉鍧愭爣鏍煎紡锛堢粡搴�,绾害锛�
+  const origParts = orig.split(',')
+  const destParts = dest.split(',')
+  if (origParts.length !== 2 || destParts.length !== 2) {
+    return Promise.reject(new Error('鍧愭爣鏍煎紡閿欒,搴斾负:缁忓害,绾害'))
+  }
+  
+  return request({
+    url: '/system/gps/tianditu/route/driving',
+    method: 'get',
+    params: {
+      orig: orig,
+      dest: dest,
+      mid: mid,
+      style: style || '0'
+    }
+  })
+}
+
+// 澶╁湴鍥捐绠椾袱涓湴鍧�涔嬮棿鐨勮窛绂伙紙缁勫悎鎺ュ彛锛氬湴鍧�杞潗鏍� + 璺緞瑙勫垝锛�
+export function tiandituDistanceByAddress(fromAddress, toAddress) {
+  // 鍙傛暟楠岃瘉
+  if (!fromAddress || !toAddress) {
+    return Promise.reject(new Error('鍙傛暟涓嶅畬鏁�,缂哄皯璧风偣鎴栫粓鐐瑰湴鍧�'))
+  }
+  
+  return request({
+    url: '/system/gps/tianditu/distance/byAddress',
+    method: 'get',
+    params: {
+      fromAddress: fromAddress,
+      toAddress: toAddress
+    }
+  })
+}
+
+// 澶╁湴鍥惧湴鍧�鎼滅储鎻愮ずAPI锛堣緭鍏ヨ仈鎯筹級
+export function tiandituPlaceSuggestion(keyWord, region, city, count) {
+  // 鍙傛暟楠岃瘉
+  if (!keyWord) {
+    return Promise.reject(new Error('鍙傛暟涓嶅畬鏁�,缂哄皯鎼滅储鍏抽敭璇�'))
+  }
+  
+  return request({
+    url: '/system/gps/tianditu/place/suggestion',
+    method: 'get',
+    params: {
+      keyWord: keyWord,
+      region: region,
+      city: city,
+      count: count || 10
+    }
+  })
 }
\ No newline at end of file
diff --git a/app/api/mileageStats.js b/app/api/mileageStats.js
index e69de29..3388dbc 100644
--- a/app/api/mileageStats.js
+++ b/app/api/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(statsIds) {
+  return request({
+    url: '/system/mileageStats/' + statsIds,
+    method: 'delete'
+  })
+}
+
+// 鎵嬪姩璁$畻鎸囧畾杞﹁締鎸囧畾鏃ユ湡鐨勯噷绋嬬粺璁�
+export function calculateMileage(vehicleId, statDate) {
+  return request({
+    url: '/system/mileageStats/calculate',
+    method: 'post',
+    params: {
+      vehicleId: vehicleId,
+      statDate: statDate
+    }
+  })
+}
+
+// 鎵归噺璁$畻鎸囧畾鏃ユ湡鎵�鏈夎溅杈嗙殑閲岀▼缁熻
+export function batchCalculateMileage(statDate) {
+  return request({
+    url: '/system/mileageStats/batchCalculate',
+    method: 'post',
+    params: {
+      statDate: statDate
+    }
+  })
+}
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 1a770d2..b67fb67 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
@@ -11,6 +11,7 @@
 import com.ruoyi.system.service.*;
 import com.ruoyi.common.config.TencentMapConfig;
 import com.ruoyi.common.config.BaiduMapConfig;
+import com.ruoyi.common.config.TiandituMapConfig;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.GetMapping;
@@ -63,6 +64,9 @@
     
     @Autowired
     private BaiduMapConfig baiduMapConfig;
+    
+    @Autowired
+    private TiandituMapConfig tiandituMapConfig;
 
    /**
      * 鏌ヨ杞﹁締GPS鍧愭爣鍒楄〃
@@ -740,4 +744,394 @@
             return AjaxResult.error("鍦板潃鎼滅储澶辫触锛�" + e.getMessage());
         }
     }
+    
+    // ==================== 澶╁湴鍥炬帴鍙� ====================
+    
+    /**
+     * 澶╁湴鍥惧湴鐞嗙紪鐮佹帴鍙d唬鐞嗭紙鍦板潃杞潗鏍囷級
+     * 鏂囨。锛歨ttps://lbs.tianditu.gov.cn/server/geocoding.html
+     */
+    @Anonymous()
+    @GetMapping("/tianditu/geocoding")
+    public AjaxResult tiandituGeocoding(String address) {
+        try {
+            // 妫�鏌ュ弬鏁�
+            if (address == null || address.trim().isEmpty()) {
+                return AjaxResult.error("鍙傛暟涓嶅畬鏁达紝缂哄皯鍦板潃淇℃伅");
+            }
+            
+            // 鏋勫缓澶╁湴鍥惧湴鐞嗙紪鐮丄PI URL
+            String url = "http://api.tianditu.gov.cn/geocoder";
+            String params = "ds={\"keyWord\":\"" + address + \"}" +
+                           "&tk=" + tiandituMapConfig.getTk();
+            
+            logger.info("澶╁湴鍥惧湴鐞嗙紪鐮佽姹�: address={}", address);
+            
+            // 鍙戦�丠TTP璇锋眰
+            String response = HttpUtils.sendGet(url, params);
+            
+            // 杩斿洖缁撴灉
+            return AjaxResult.success("鏌ヨ鎴愬姛", response);
+        } catch (Exception e) {
+            logger.error("澶╁湴鍥惧湴鐞嗙紪鐮佸け璐�", e);
+            return AjaxResult.error("鍦扮悊缂栫爜澶辫触锛�" + e.getMessage());
+        }
+    }
+    
+    /**
+     * 澶╁湴鍥鹃�嗗湴鐞嗙紪鐮佹帴鍙d唬鐞嗭紙鍧愭爣杞湴鍧�锛�
+     * 鏂囨。锛歨ttps://lbs.tianditu.gov.cn/server/geocoding.html
+     */
+    @Anonymous()
+    @GetMapping("/tianditu/reverseGeocoding")
+    public AjaxResult tiandituReverseGeocoding(Double lon, Double lat) {
+        try {
+            // 妫�鏌ュ弬鏁�
+            if (lat == null || lon == null) {
+                return AjaxResult.error("鍙傛暟涓嶅畬鏁达紝缂哄皯缁忕含搴﹀潗鏍�");
+            }
+            
+            // 妫�鏌ュ弬鏁版湁鏁堟��
+            if (Double.isNaN(lat) || Double.isNaN(lon) || 
+                Double.isInfinite(lat) || Double.isInfinite(lon)) {
+                return AjaxResult.error("鍙傛暟鏃犳晥锛岀粡绾害鍧愭爣鏍煎紡閿欒");
+            }
+            
+            // 鏋勫缓澶╁湴鍥鹃�嗗湴鐞嗙紪鐮丄PI URL
+            String url = "http://api.tianditu.gov.cn/geocoder";
+            String params = "postStr={\"lon\":" + lon + ",\"lat\":" + lat + ",\"ver\":1}" +
+                           "&type=geocode" +
+                           "&tk=" + tiandituMapConfig.getTk();
+            
+            logger.info("澶╁湴鍥鹃�嗗湴鐞嗙紪鐮佽姹�: lon={}, lat={}", lon, lat);
+            
+            // 鍙戦�丠TTP璇锋眰
+            String response = HttpUtils.sendGet(url, params);
+            
+            // 杩斿洖缁撴灉
+            return AjaxResult.success("鏌ヨ鎴愬姛", response);
+        } catch (Exception e) {
+            logger.error("澶╁湴鍥鹃�嗗湴鐞嗙紪鐮佸け璐�: lon={}, lat={}", lon, lat, e);
+            return AjaxResult.error("閫嗗湴鐞嗙紪鐮佸け璐ワ細" + e.getMessage());
+        }
+    }
+    
+    /**
+     * 澶╁湴鍥惧湴鐐规悳绱㈡帴鍙d唬鐞嗭紙POI鎼滅储锛�
+     * 鏂囨。锛歨ttps://lbs.tianditu.gov.cn/server/search.html
+     */
+    @Anonymous()
+    @GetMapping("/tianditu/place/search")
+    public AjaxResult tiandituPlaceSearch(String keyWord, String queryType, String level, 
+                                          String mapBound, Integer start, Integer count) {
+        try {
+            // 妫�鏌ュ弬鏁�
+            if (keyWord == null || keyWord.trim().isEmpty()) {
+                return AjaxResult.error("鍙傛暟涓嶅畬鏁达紝缂哄皯鎼滅储鍏抽敭璇�");
+            }
+            
+            // 璁剧疆榛樿鍊�
+            if (queryType == null || queryType.trim().isEmpty()) {
+                queryType = "1"; // 1-鏅�氭悳绱紝7-鍛ㄨ竟鎼滅储
+            }
+            if (start == null) {
+                start = 0;
+            }
+            if (count == null) {
+                count = 10;
+            }
+            
+            // 鏋勫缓澶╁湴鍥綪OI鎼滅储API URL
+            String url = "http://api.tianditu.gov.cn/search";
+            StringBuilder paramsBuilder = new StringBuilder();
+            paramsBuilder.append("postStr={\"keyWord\":\"").append(keyWord).append("\"");
+            paramsBuilder.append(",\"queryType\":\"").append(queryType).append("\"");
+            if (level != null && !level.trim().isEmpty()) {
+                paramsBuilder.append(",\"level\":\"").append(level).append("\"");
+            }
+            if (mapBound != null && !mapBound.trim().isEmpty()) {
+                paramsBuilder.append(",\"mapBound\":\"").append(mapBound).append("\"");
+            }
+            paramsBuilder.append(",\"start\":\"").append(start).append("\"");
+            paramsBuilder.append(",\"count\":\"").append(count).append("\"");
+            paramsBuilder.append("}" );
+            paramsBuilder.append("&type=query");
+            paramsBuilder.append("&tk=").append(tiandituMapConfig.getTk());
+            
+            String params = paramsBuilder.toString();
+            
+            logger.info("澶╁湴鍥綪OI鎼滅储璇锋眰: keyWord={}, queryType={}", keyWord, queryType);
+            
+            // 鍙戦�丠TTP璇锋眰
+            String response = HttpUtils.sendGet(url, params);
+            
+            // 杩斿洖缁撴灉
+            return AjaxResult.success("鏌ヨ鎴愬姛", response);
+        } catch (Exception e) {
+            logger.error("澶╁湴鍥綪OI鎼滅储澶辫触", e);
+            return AjaxResult.error("POI鎼滅储澶辫触锛�" + e.getMessage());
+        }
+    }
+    
+    /**
+     * 澶╁湴鍥捐矾绾胯鍒掓帴鍙d唬鐞嗭紙椹捐溅璺緞瑙勫垝锛�
+     * 鏂囨。锛歨ttps://lbs.tianditu.gov.cn/server/drive.html
+     */
+    @Anonymous()
+    @GetMapping("/tianditu/route/driving")
+    public AjaxResult tiandituRouteDriving(String orig, String dest, String mid, String style) {
+        try {
+            // 妫�鏌ュ弬鏁�
+            if (orig == null || orig.trim().isEmpty() || 
+                dest == null || dest.trim().isEmpty()) {
+                return AjaxResult.error("鍙傛暟涓嶅畬鏁达紝缂哄皯璧风偣鎴栫粓鐐瑰潗鏍�");
+            }
+            
+            // 楠岃瘉鍧愭爣鏍煎紡锛堢粡搴�,绾害锛�
+            String[] origParts = orig.split(",");
+            String[] destParts = dest.split(",");
+            if (origParts.length != 2 || destParts.length != 2) {
+                return AjaxResult.error("鍧愭爣鏍煎紡閿欒锛屽簲涓猴細缁忓害,绾害");
+            }
+            
+            // 璁剧疆榛樿鍊�
+            if (style == null || style.trim().isEmpty()) {
+                style = "0"; // 0-鎺ㄨ崘锛�1-閬垮紑楂橀��
+            }
+            
+            // 鏋勫缓澶╁湴鍥鹃┚杞﹁矾寰勮鍒扐PI URL
+            String url = "http://api.tianditu.gov.cn/drive";
+            StringBuilder paramsBuilder = new StringBuilder();
+            paramsBuilder.append("postStr={\"orig\":\"").append(orig).append("\"");
+            paramsBuilder.append(",\"dest\":\"").append(dest).append("\"");
+            if (mid != null && !mid.trim().isEmpty()) {
+                paramsBuilder.append(",\"mid\":\"").append(mid).append("\"");
+            }
+            paramsBuilder.append(",\"style\":\"").append(style).append("\"");
+            paramsBuilder.append("}" );
+            paramsBuilder.append("&tk=").append(tiandituMapConfig.getTk());
+            
+            String params = paramsBuilder.toString();
+            
+            logger.info("澶╁湴鍥鹃┚杞﹁矾寰勮鍒掕姹�: orig={}, dest={}", orig, dest);
+            
+            // 鍙戦�丠TTP璇锋眰
+            String response = HttpUtils.sendGet(url, params);
+            
+            // 杩斿洖缁撴灉
+            return AjaxResult.success("璁$畻鎴愬姛", response);
+        } catch (Exception e) {
+            logger.error("澶╁湴鍥鹃┚杞﹁矾寰勮鍒掑け璐�", e);
+            return AjaxResult.error("璺緞瑙勫垝澶辫触锛�" + e.getMessage());
+        }
+    }
+    
+    /**
+     * 澶╁湴鍥捐绠椾袱涓湴鍧�涔嬮棿鐨勮窛绂伙紙缁勫悎鎺ュ彛锛氬湴鍧�杞潗鏍� + 璺緞瑙勫垝锛�
+     */
+    @Anonymous()
+    @GetMapping("/tianditu/distance/byAddress")
+    public AjaxResult tiandituDistanceByAddress(String fromAddress, String toAddress) {
+        try {
+            // 妫�鏌ュ弬鏁�
+            if (fromAddress == null || fromAddress.trim().isEmpty() || 
+                toAddress == null || toAddress.trim().isEmpty()) {
+                return AjaxResult.error("鍙傛暟涓嶅畬鏁达紝缂哄皯璧风偣鎴栫粓鐐瑰湴鍧�");
+            }
+            
+            logger.info("寮�濮嬭绠楀湴鍧�璺濈: fromAddress={}, toAddress={}", fromAddress, toAddress);
+            
+            // 绗竴姝ワ細璧风偣鍦板潃杞潗鏍�
+            String geocodingUrl1 = "http://api.tianditu.gov.cn/geocoder";
+            String geocodingParams1 = "ds={\"keyWord\":\"" + fromAddress + \"}" +
+                                     "&tk=" + tiandituMapConfig.getTk();
+            
+            String geocodingResponse1 = HttpUtils.sendGet(geocodingUrl1, geocodingParams1);
+            logger.info("璧风偣鍦扮悊缂栫爜鍝嶅簲: {}", geocodingResponse1);
+            
+            // 瑙f瀽璧风偣鍧愭爣
+            com.alibaba.fastjson2.JSONObject geocodingJson1 = com.alibaba.fastjson2.JSONObject.parseObject(geocodingResponse1);
+            if (!"0".equals(geocodingJson1.getString("status"))) {
+                logger.error("璧风偣鍦扮悊缂栫爜澶辫触: {}", geocodingResponse1);
+                return AjaxResult.error("璧风偣鍦板潃瑙f瀽澶辫触");
+            }
+            com.alibaba.fastjson2.JSONObject location1 = geocodingJson1.getJSONObject("location");
+            if (location1 == null) {
+                return AjaxResult.error("璧风偣鍦板潃鏈壘鍒板搴斿潗鏍�");
+            }
+            double fromLon = location1.getDouble("lon");
+            double fromLat = location1.getDouble("lat");
+            logger.info("璧风偣鍧愭爣: lon={}, lat={}", fromLon, fromLat);
+            
+            // 绗簩姝ワ細缁堢偣鍦板潃杞潗鏍�
+            String geocodingUrl2 = "http://api.tianditu.gov.cn/geocoder";
+            String geocodingParams2 = "ds={\"keyWord\":\"" + toAddress + \"}" +
+                                     "&tk=" + tiandituMapConfig.getTk();
+            
+            String geocodingResponse2 = HttpUtils.sendGet(geocodingUrl2, geocodingParams2);
+            logger.info("缁堢偣鍦扮悊缂栫爜鍝嶅簲: {}", geocodingResponse2);
+            
+            // 瑙f瀽缁堢偣鍧愭爣
+            com.alibaba.fastjson2.JSONObject geocodingJson2 = com.alibaba.fastjson2.JSONObject.parseObject(geocodingResponse2);
+            if (!"0".equals(geocodingJson2.getString("status"))) {
+                logger.error("缁堢偣鍦扮悊缂栫爜澶辫触: {}", geocodingResponse2);
+                return AjaxResult.error("缁堢偣鍦板潃瑙f瀽澶辫触");
+            }
+            com.alibaba.fastjson2.JSONObject location2 = geocodingJson2.getJSONObject("location");
+            if (location2 == null) {
+                return AjaxResult.error("缁堢偣鍦板潃鏈壘鍒板搴斿潗鏍�");
+            }
+            double toLon = location2.getDouble("lon");
+            double toLat = location2.getDouble("lat");
+            logger.info("缁堢偣鍧愭爣: lon={}, lat={}", toLon, toLat);
+            
+            // 绗笁姝ワ細璋冪敤璺緞瑙勫垝鎺ュ彛璁$畻璺濈
+            String routeUrl = "http://api.tianditu.gov.cn/drive";
+            String orig = fromLon + "," + fromLat;
+            String dest = toLon + "," + toLat;
+            String routeParams = "postStr={\"orig\":\"" + orig + "\",\"dest\":\"" + dest + "\",\"style\":\"0\"}" +
+                                "&tk=" + tiandituMapConfig.getTk();
+            
+            logger.info("璺緞瑙勫垝璇锋眰: orig={}, dest={}", orig, dest);
+            String routeResponse = HttpUtils.sendGet(routeUrl, routeParams);
+            logger.info("璺緞瑙勫垝鍝嶅簲: {}", routeResponse);
+            
+            // 瑙f瀽璺濈缁撴灉
+            com.alibaba.fastjson2.JSONObject routeJson = com.alibaba.fastjson2.JSONObject.parseObject(routeResponse);
+            if (!"0".equals(routeJson.getString("status"))) {
+                logger.error("璺緞瑙勫垝澶辫触: {}", routeResponse);
+                return AjaxResult.error("璺緞瑙勫垝澶辫触");
+            }
+            
+            // 鎻愬彇璺濈淇℃伅
+            com.alibaba.fastjson2.JSONObject result = routeJson.getJSONObject("result");
+            if (result == null) {
+                logger.error("璺緞瑙勫垝缁撴灉涓虹┖");
+                return AjaxResult.error("璺緞瑙勫垝澶辫触");
+            }
+            
+            com.alibaba.fastjson2.JSONArray routes = result.getJSONArray("routes");
+            if (routes == null || routes.isEmpty()) {
+                logger.error("鏈壘鍒拌矾绾夸俊鎭�");
+                return AjaxResult.error("鏈壘鍒拌矾绾夸俊鎭�");
+            }
+            
+            com.alibaba.fastjson2.JSONObject route = routes.getJSONObject(0);
+            double distance = route.getDoubleValue("distance"); // 璺濈锛屽崟浣嶏細绫�
+            double duration = route.getDoubleValue("duration"); // 鏃堕暱锛屽崟浣嶏細绉�
+            
+            logger.info("璁$畻鎴愬姛: 璺濈={}绫�, 鏃堕暱={}绉�", distance, duration);
+            
+            // 鏋勫缓杩斿洖缁撴灉
+            Map<String, Object> resultMap = new HashMap<>();
+            resultMap.put("distance", (int)distance); // 璺濈锛堢背锛�
+            resultMap.put("duration", (int)duration); // 鏃堕暱锛堢锛�
+            resultMap.put("distanceKm", String.format("%.1f", distance / 1000.0)); // 璺濈锛堝叕閲岋級
+            resultMap.put("durationMin", (int)(duration / 60)); // 鏃堕暱锛堝垎閽燂級
+            
+            // 璧风偣鍧愭爣
+            Map<String, Object> fromLocation = new HashMap<>();
+            fromLocation.put("lon", fromLon);
+            fromLocation.put("lat", fromLat);
+            resultMap.put("fromLocation", fromLocation);
+            
+            // 缁堢偣鍧愭爣
+            Map<String, Object> toLocation = new HashMap<>();
+            toLocation.put("lon", toLon);
+            toLocation.put("lat", toLat);
+            resultMap.put("toLocation", toLocation);
+            
+            return AjaxResult.success("璁$畻鎴愬姛", resultMap);
+        } catch (Exception e) {
+            logger.error("璁$畻鍦板潃璺濈澶辫触", e);
+            return AjaxResult.error("璁$畻璺濈澶辫触锛�" + e.getMessage());
+        }
+    }
+    
+    /**
+     * 澶╁湴鍥捐緭鍏ユ彁绀烘帴鍙d唬鐞嗭紙鍦板潃鑱旀兂锛�
+     * 鏂囨。锛歨ttps://lbs.tianditu.gov.cn/server/suggestion.html
+     */
+    @Anonymous()
+    @GetMapping("/tianditu/place/suggestion")
+    public AjaxResult tiandituPlaceSuggestion(String keyWord, String region, String city, Integer count) {
+        try {
+            // 妫�鏌ュ弬鏁�
+            if (keyWord == null || keyWord.trim().isEmpty()) {
+                return AjaxResult.error("鍙傛暟涓嶅畬鏁达紝缂哄皯鎼滅储鍏抽敭璇�");
+            }
+            
+            // 璁剧疆榛樿鍊�
+            if (count == null) {
+                count = 10;
+            }
+            
+            // 鏋勫缓澶╁湴鍥捐緭鍏ユ彁绀篈PI URL
+            String url = "http://api.tianditu.gov.cn/search";
+            StringBuilder paramsBuilder = new StringBuilder();
+            paramsBuilder.append("postStr={\"keyWord\":\"").append(keyWord).append("\"");
+            if (region != null && !region.trim().isEmpty()) {
+                paramsBuilder.append(",\"region\":\"").append(region).append("\"");
+            }
+            if (city != null && !city.trim().isEmpty()) {
+                paramsBuilder.append(",\"city\":\"").append(city).append("\"");
+            }
+            paramsBuilder.append(",\"count\":\"").append(count).append("\"");
+            paramsBuilder.append("}" );
+            paramsBuilder.append("&type=suggest");
+            paramsBuilder.append("&tk=").append(tiandituMapConfig.getTk());
+            
+            String params = paramsBuilder.toString();
+            
+            logger.info("澶╁湴鍥捐緭鍏ユ彁绀鸿姹�: keyWord={}, region={}", keyWord, region);
+            
+            // 鍙戦�丠TTP璇锋眰
+            String response = HttpUtils.sendGet(url, params);
+            logger.debug("澶╁湴鍥捐緭鍏ユ彁绀哄搷搴�: {}", response);
+            
+            // 瑙f瀽鍝嶅簲
+            com.alibaba.fastjson2.JSONObject jsonResponse = com.alibaba.fastjson2.JSONObject.parseObject(response);
+            if (!"0".equals(jsonResponse.getString("status"))) {
+                logger.error("杈撳叆鎻愮ず澶辫触: {}", response);
+                return AjaxResult.error("鍦板潃鎼滅储澶辫触");
+            }
+            
+            // 鎻愬彇鎻愮ず鍒楄〃
+            com.alibaba.fastjson2.JSONArray results = jsonResponse.getJSONArray("suggests");
+            if (results == null || results.isEmpty()) {
+                logger.info("鏈壘鍒板尮閰嶇殑鍦板潃");
+                return AjaxResult.success("鏌ヨ鎴愬姛", new ArrayList<>());
+            }
+            
+            // 鏋勫缓杩斿洖缁撴灉
+            List<Map<String, Object>> suggestions = new ArrayList<>();
+            for (int i = 0; i < results.size(); i++) {
+                com.alibaba.fastjson2.JSONObject item = results.getJSONObject(i);
+                
+                Map<String, Object> suggestion = new HashMap<>();
+                suggestion.put("name", item.getString("name")); // 鍚嶇О
+                suggestion.put("address", item.getString("address")); // 鍦板潃
+                suggestion.put("province", item.getString("province")); // 鐪�
+                suggestion.put("city", item.getString("city")); // 甯�
+                suggestion.put("district", item.getString("district")); // 鍖�
+                
+                // 缁忕含搴︿俊鎭�
+                com.alibaba.fastjson2.JSONObject location = item.getJSONObject("location");
+                if (location != null) {
+                    Map<String, Object> locationMap = new HashMap<>();
+                    locationMap.put("lon", location.getDouble("lon"));
+                    locationMap.put("lat", location.getDouble("lat"));
+                    suggestion.put("location", locationMap);
+                }
+                
+                suggestions.add(suggestion);
+            }
+            
+            logger.info("鍦板潃鎼滅储鎻愮ず鎴愬姛: 鎵惧埌{}鏉$粨鏋�", suggestions.size());
+            return AjaxResult.success("鏌ヨ鎴愬姛", suggestions);
+        } catch (Exception e) {
+            logger.error("鍦板潃鎼滅储鎻愮ず澶辫触", e);
+            return AjaxResult.error("鍦板潃鎼滅储澶辫触锛�" + e.getMessage());
+        }
+    }
 }
\ No newline at end of file
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 e69de29..2383778 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
@@ -0,0 +1,135 @@
+package com.ruoyi.web.controller.system;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.List;
+import javax.servlet.http.HttpServletResponse;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.DeleteMapping;
+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.PutMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+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.common.utils.poi.ExcelUtil;
+import com.ruoyi.system.domain.VehicleMileageStats;
+import com.ruoyi.system.service.IVehicleMileageStatsService;
+
+/**
+ * 杞﹁締閲岀▼缁熻Controller
+ */
+@RestController
+@RequestMapping("/system/mileageStats")
+public class VehicleMileageStatsController extends BaseController {
+    
+    @Autowired
+    private IVehicleMileageStatsService vehicleMileageStatsService;
+
+    /**
+     * 鏌ヨ杞﹁締閲岀▼缁熻鍒楄〃
+     */
+    @PreAuthorize("@ss.hasPermi('system:mileageStats:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(VehicleMileageStats vehicleMileageStats) {
+        startPage();
+        List<VehicleMileageStats> list = vehicleMileageStatsService.selectVehicleMileageStatsList(vehicleMileageStats);
+        return getDataTable(list);
+    }
+
+    /**
+     * 瀵煎嚭杞﹁締閲岀▼缁熻鍒楄〃
+     */
+    @PreAuthorize("@ss.hasPermi('system:mileageStats:export')")
+    @Log(title = "杞﹁締閲岀▼缁熻", businessType = BusinessType.EXPORT)
+    @PostMapping("/export")
+    public void export(HttpServletResponse response, VehicleMileageStats vehicleMileageStats) {
+        List<VehicleMileageStats> list = vehicleMileageStatsService.selectVehicleMileageStatsList(vehicleMileageStats);
+        ExcelUtil<VehicleMileageStats> util = new ExcelUtil<VehicleMileageStats>(VehicleMileageStats.class);
+        util.exportExcel(response, list, "杞﹁締閲岀▼缁熻鏁版嵁");
+    }
+
+    /**
+     * 鑾峰彇杞﹁締閲岀▼缁熻璇︾粏淇℃伅
+     */
+    @PreAuthorize("@ss.hasPermi('system:mileageStats:query')")
+    @GetMapping(value = "/{statsId}")
+    public AjaxResult getInfo(@PathVariable("statsId") Long statsId) {
+        return success(vehicleMileageStatsService.selectVehicleMileageStatsById(statsId));
+    }
+
+    /**
+     * 鏂板杞﹁締閲岀▼缁熻
+     */
+    @PreAuthorize("@ss.hasPermi('system:mileageStats:add')")
+    @Log(title = "杞﹁締閲岀▼缁熻", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody VehicleMileageStats vehicleMileageStats) {
+        return toAjax(vehicleMileageStatsService.insertVehicleMileageStats(vehicleMileageStats));
+    }
+
+    /**
+     * 淇敼杞﹁締閲岀▼缁熻
+     */
+    @PreAuthorize("@ss.hasPermi('system:mileageStats:edit')")
+    @Log(title = "杞﹁締閲岀▼缁熻", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody VehicleMileageStats vehicleMileageStats) {
+        return toAjax(vehicleMileageStatsService.updateVehicleMileageStats(vehicleMileageStats));
+    }
+
+    /**
+     * 鍒犻櫎杞﹁締閲岀▼缁熻
+     */
+    @PreAuthorize("@ss.hasPermi('system:mileageStats:remove')")
+    @Log(title = "杞﹁締閲岀▼缁熻", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{statsIds}")
+    public AjaxResult remove(@PathVariable Long[] statsIds) {
+        return toAjax(vehicleMileageStatsService.deleteVehicleMileageStatsByIds(statsIds));
+    }
+
+    /**
+     * 鎵嬪姩瑙﹀彂璁$畻鎸囧畾杞﹁締鎸囧畾鏃ユ湡鐨勯噷绋嬬粺璁�
+     */
+    @PreAuthorize("@ss.hasPermi('system:mileageStats:calculate')")
+    @Log(title = "杞﹁締閲岀▼缁熻", businessType = BusinessType.OTHER)
+    @PostMapping("/calculate")
+    public AjaxResult calculate(Long vehicleId, String statDate) {
+        try {
+            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
+            Date date = sdf.parse(statDate);
+            VehicleMileageStats stats = vehicleMileageStatsService.calculateAndSaveMileageStats(vehicleId, date);
+            if (stats != null) {
+                return success("閲岀▼缁熻璁$畻鎴愬姛", stats);
+            } else {
+                return error("璇ヨ溅杈嗗湪鎸囧畾鏃ユ湡鏃燝PS鏁版嵁");
+            }
+        } catch (Exception e) {
+            return error("閲岀▼缁熻璁$畻澶辫触锛�" + e.getMessage());
+        }
+    }
+
+    /**
+     * 鎵归噺璁$畻鎸囧畾鏃ユ湡鎵�鏈夎溅杈嗙殑閲岀▼缁熻
+     */
+    @PreAuthorize("@ss.hasPermi('system:mileageStats:batch')")
+    @Log(title = "杞﹁締閲岀▼缁熻", businessType = BusinessType.OTHER)
+    @PostMapping("/batchCalculate")
+    public AjaxResult batchCalculate(String statDate) {
+        try {
+            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
+            Date date = sdf.parse(statDate);
+            int count = vehicleMileageStatsService.batchCalculateMileageStats(date);
+            return success("鎵归噺閲岀▼缁熻瀹屾垚锛屾垚鍔熺粺璁� " + count + " 杈嗚溅");
+        } catch (Exception e) {
+            return error("鎵归噺閲岀▼缁熻澶辫触锛�" + e.getMessage());
+        }
+    }
+}
diff --git a/ruoyi-admin/src/main/resources/application.yml b/ruoyi-admin/src/main/resources/application.yml
index a29e877..9db576a 100644
--- a/ruoyi-admin/src/main/resources/application.yml
+++ b/ruoyi-admin/src/main/resources/application.yml
@@ -165,3 +165,8 @@
 baidu:
   map:
     ak: GX7G1RmAbTEQHor9NKpzRiB2jerqaY1E  # 璇锋浛鎹负鎮ㄧ殑鐧惧害鍦板浘API Key
+
+# 澶╁湴鍥鹃厤缃�
+tianditu:
+  map:
+    tk: 4d1d0b3a4a03b9c5099c0e25ab1b23f3  # 璇锋浛鎹负鎮ㄧ殑澶╁湴鍥続PI Key
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/config/TiandituMapConfig.java b/ruoyi-common/src/main/java/com/ruoyi/common/config/TiandituMapConfig.java
index e69de29..edc8fae 100644
--- a/ruoyi-common/src/main/java/com/ruoyi/common/config/TiandituMapConfig.java
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/config/TiandituMapConfig.java
@@ -0,0 +1,25 @@
+package com.ruoyi.common.config;
+
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * 澶╁湴鍥鹃厤缃被
+ */
+@Configuration
+@ConfigurationProperties(prefix = "tianditu.map")
+public class TiandituMapConfig {
+    
+    /**
+     * 澶╁湴鍥続PI Key (tk鍙傛暟)
+     */
+    private String tk;
+
+    public String getTk() {
+        return tk;
+    }
+
+    public void setTk(String tk) {
+        this.tk = tk;
+    }
+}
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 e69de29..22cd0b8 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
@@ -0,0 +1,84 @@
+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.IVehicleMileageStatsService;
+
+/**
+ * 杞﹁締閲岀▼缁熻瀹氭椂浠诲姟
+ */
+@Component("vehicleMileageStatsTask")
+public class VehicleMileageStatsTask {
+    
+    private static final Logger logger = LoggerFactory.getLogger(VehicleMileageStatsTask.class);
+    
+    @Autowired
+    private IVehicleMileageStatsService vehicleMileageStatsService;
+
+    /**
+     * 璁$畻鏄ㄦ棩鎵�鏈夎溅杈嗙殑閲岀▼缁熻
+     */
+    public void calculateYesterdayMileage() {
+        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.batchCalculateMileageStats(yesterday);
+            
+            logger.info("杞﹁締閲岀▼缁熻瀹氭椂浠诲姟鎵ц瀹屾垚 - 鎴愬姛缁熻: {} 杈嗚溅", successCount);
+            
+        } catch (Exception e) {
+            logger.error("杞﹁締閲岀▼缁熻瀹氭椂浠诲姟鎵ц澶辫触", e);
+            throw new RuntimeException("瀹氭椂浠诲姟鎵ц澶辫触: " + e.getMessage());
+        }
+    }
+
+    /**
+     * 璁$畻鎸囧畾鏃ユ湡鐨勯噷绋嬬粺璁★紙鐢ㄤ簬鎵嬪姩瑙﹀彂鎴栬ˉ绠楀巻鍙叉暟鎹級
+     * 
+     * @param dateStr 鏃ユ湡瀛楃涓诧紝鏍煎紡锛歽yyy-MM-dd
+     */
+    public void calculateMileageByDate(String dateStr) {
+        logger.info("寮�濮嬫墽琛岃溅杈嗛噷绋嬬粺璁″畾鏃朵换鍔� - 缁熻鏃ユ湡: {}", dateStr);
+        
+        try {
+            // 瑙f瀽鏃ユ湡瀛楃涓�
+            String[] parts = dateStr.split("-");
+            if (parts.length != 3) {
+                throw new IllegalArgumentException("鏃ユ湡鏍煎紡閿欒锛屽簲涓�: yyyy-MM-dd");
+            }
+            
+            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();
+            
+            // 鎵归噺璁$畻閲岀▼缁熻
+            int successCount = vehicleMileageStatsService.batchCalculateMileageStats(targetDate);
+            
+            logger.info("杞﹁締閲岀▼缁熻瀹氭椂浠诲姟鎵ц瀹屾垚 - 鏃ユ湡: {}, 鎴愬姛缁熻: {} 杈嗚溅", dateStr, successCount);
+            
+        } catch (Exception e) {
+            logger.error("杞﹁締閲岀▼缁熻瀹氭椂浠诲姟鎵ц澶辫触 - 鏃ユ湡: {}", dateStr, e);
+            throw new RuntimeException("瀹氭椂浠诲姟鎵ц澶辫触: " + e.getMessage());
+        }
+    }
+}
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/domain/TaskTimeInterval.java b/ruoyi-system/src/main/java/com/ruoyi/system/domain/TaskTimeInterval.java
index e69de29..16f60eb 100644
--- a/ruoyi-system/src/main/java/com/ruoyi/system/domain/TaskTimeInterval.java
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/domain/TaskTimeInterval.java
@@ -0,0 +1,51 @@
+package com.ruoyi.system.domain;
+
+import java.util.Date;
+
+/**
+ * 浠诲姟鏃堕棿鍖洪棿
+ */
+public class TaskTimeInterval {
+    
+    /** 浠诲姟ID */
+    private Long taskId;
+    
+    /** 寮�濮嬫椂闂� */
+    private Date startTime;
+    
+    /** 缁撴潫鏃堕棿 */
+    private Date endTime;
+
+    public TaskTimeInterval() {
+    }
+
+    public TaskTimeInterval(Long taskId, Date startTime, Date endTime) {
+        this.taskId = taskId;
+        this.startTime = startTime;
+        this.endTime = endTime;
+    }
+
+    public Long getTaskId() {
+        return taskId;
+    }
+
+    public void setTaskId(Long taskId) {
+        this.taskId = taskId;
+    }
+
+    public Date getStartTime() {
+        return startTime;
+    }
+
+    public void setStartTime(Date startTime) {
+        this.startTime = startTime;
+    }
+
+    public Date getEndTime() {
+        return endTime;
+    }
+
+    public void setEndTime(Date endTime) {
+        this.endTime = endTime;
+    }
+}
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 ab5cb3b..a1fe49d 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
@@ -3,19 +3,13 @@
 import java.math.BigDecimal;
 import java.util.Date;
 import com.fasterxml.jackson.annotation.JsonFormat;
-import org.apache.commons.lang3.builder.ToStringBuilder;
-import org.apache.commons.lang3.builder.ToStringStyle;
 import com.ruoyi.common.annotation.Excel;
 import com.ruoyi.common.core.domain.BaseEntity;
 
 /**
- * 杞﹁締閲岀▼缁熻瀵硅薄 vehicle_mileage_stats
- * 
- * @author ruoyi
- * @date 2025-01-15
+ * 杞﹁締閲岀▼缁熻瀵硅薄 tb_vehicle_mileage_stats
  */
-public class VehicleMileageStats extends BaseEntity
-{
+public class VehicleMileageStats extends BaseEntity {
     private static final long serialVersionUID = 1L;
 
     /** 缁熻ID */
@@ -25,117 +19,116 @@
     @Excel(name = "杞﹁締ID")
     private Long vehicleId;
 
+    /** 杞︾墝鍙� */
+    @Excel(name = "杞︾墝鍙�")
+    private String vehicleNo;
+
     /** 缁熻鏃ユ湡 */
     @JsonFormat(pattern = "yyyy-MM-dd")
     @Excel(name = "缁熻鏃ユ湡", width = 30, dateFormat = "yyyy-MM-dd")
-    private Date statsDate;
+    private Date statDate;
 
     /** 鎬婚噷绋�(鍏噷) */
     @Excel(name = "鎬婚噷绋�(鍏噷)")
     private BigDecimal totalMileage;
 
-    /** 浠诲姟閲岀▼(鍏噷) */
-    @Excel(name = "浠诲姟閲岀▼(鍏噷)")
+    /** 浠诲姟鏃舵閲岀▼(鍏噷) */
+    @Excel(name = "浠诲姟鏃舵閲岀▼(鍏噷)")
     private BigDecimal taskMileage;
 
-    /** 闈炰换鍔¢噷绋�(鍏噷) */
-    @Excel(name = "闈炰换鍔¢噷绋�(鍏噷)")
+    /** 闈炰换鍔℃椂娈甸噷绋�(鍏噷) */
+    @Excel(name = "闈炰换鍔℃椂娈甸噷绋�(鍏噷)")
     private BigDecimal nonTaskMileage;
 
-    /** 浠诲姟閲岀▼鍗犳瘮(%) */
-    @Excel(name = "浠诲姟閲岀▼鍗犳瘮(%)")
-    private BigDecimal taskMileageRatio;
+    /** 浠诲姟閲岀▼鍗犳瘮(0-1) */
+    @Excel(name = "浠诲姟閲岀▼鍗犳瘮")
+    private BigDecimal taskRatio;
 
-    /** GPS鐐规暟 */
-    @Excel(name = "GPS鐐规暟")
-    private Integer gpsCount;
+    /** GPS鐐规暟閲� */
+    @Excel(name = "GPS鐐规暟閲�")
+    private Integer gpsPointCount;
 
-    public void setStatsId(Long statsId) 
-    {
+    /** 浠诲姟鏁伴噺 */
+    @Excel(name = "浠诲姟鏁伴噺")
+    private Integer taskCount;
+
+    public Long getStatsId() {
+        return statsId;
+    }
+
+    public void setStatsId(Long statsId) {
         this.statsId = statsId;
     }
 
-    public Long getStatsId() 
-    {
-        return statsId;
+    public Long getVehicleId() {
+        return vehicleId;
     }
-    public void setVehicleId(Long vehicleId) 
-    {
+
+    public void setVehicleId(Long vehicleId) {
         this.vehicleId = vehicleId;
     }
 
-    public Long getVehicleId() 
-    {
-        return vehicleId;
-    }
-    public void setStatsDate(Date statsDate) 
-    {
-        this.statsDate = statsDate;
+    public String getVehicleNo() {
+        return vehicleNo;
     }
 
-    public Date getStatsDate() 
-    {
-        return statsDate;
+    public void setVehicleNo(String vehicleNo) {
+        this.vehicleNo = vehicleNo;
     }
-    public void setTotalMileage(BigDecimal totalMileage) 
-    {
+
+    public Date getStatDate() {
+        return statDate;
+    }
+
+    public void setStatDate(Date statDate) {
+        this.statDate = statDate;
+    }
+
+    public BigDecimal getTotalMileage() {
+        return totalMileage;
+    }
+
+    public void setTotalMileage(BigDecimal totalMileage) {
         this.totalMileage = totalMileage;
     }
 
-    public BigDecimal getTotalMileage() 
-    {
-        return totalMileage;
+    public BigDecimal getTaskMileage() {
+        return taskMileage;
     }
-    public void setTaskMileage(BigDecimal taskMileage) 
-    {
+
+    public void setTaskMileage(BigDecimal taskMileage) {
         this.taskMileage = taskMileage;
     }
 
-    public BigDecimal getTaskMileage() 
-    {
-        return taskMileage;
+    public BigDecimal getNonTaskMileage() {
+        return nonTaskMileage;
     }
-    public void setNonTaskMileage(BigDecimal nonTaskMileage) 
-    {
+
+    public void setNonTaskMileage(BigDecimal nonTaskMileage) {
         this.nonTaskMileage = nonTaskMileage;
     }
 
-    public BigDecimal getNonTaskMileage() 
-    {
-        return nonTaskMileage;
-    }
-    public void setTaskMileageRatio(BigDecimal taskMileageRatio) 
-    {
-        this.taskMileageRatio = taskMileageRatio;
+    public BigDecimal getTaskRatio() {
+        return taskRatio;
     }
 
-    public BigDecimal getTaskMileageRatio() 
-    {
-        return taskMileageRatio;
-    }
-    public void setGpsCount(Integer gpsCount) 
-    {
-        this.gpsCount = gpsCount;
+    public void setTaskRatio(BigDecimal taskRatio) {
+        this.taskRatio = taskRatio;
     }
 
-    public Integer getGpsCount() 
-    {
-        return gpsCount;
+    public Integer getGpsPointCount() {
+        return gpsPointCount;
     }
 
-    @Override
-    public String toString() {
-        return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)
-            .append("statsId", getStatsId())
-            .append("vehicleId", getVehicleId())
-            .append("statsDate", getStatsDate())
-            .append("totalMileage", getTotalMileage())
-            .append("taskMileage", getTaskMileage())
-            .append("nonTaskMileage", getNonTaskMileage())
-            .append("taskMileageRatio", getTaskMileageRatio())
-            .append("gpsCount", getGpsCount())
-            .append("createTime", getCreateTime())
-            .append("updateTime", getUpdateTime())
-            .toString();
+    public void setGpsPointCount(Integer gpsPointCount) {
+        this.gpsPointCount = gpsPointCount;
+    }
+
+    public Integer getTaskCount() {
+        return taskCount;
+    }
+
+    public void setTaskCount(Integer taskCount) {
+        this.taskCount = taskCount;
     }
 }
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 52f64bd..b1cae08 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
@@ -1,6 +1,8 @@
 package com.ruoyi.system.mapper;
 
+import java.util.Date;
 import java.util.List;
+import org.apache.ibatis.annotations.Param;
 import com.ruoyi.system.domain.VehicleGps;
 
 /**
@@ -48,4 +50,23 @@
      * @return 鍒犻櫎鐨勮褰曟暟
      */
     public int deleteVehicleGpsBeforeDate();
+
+    /**
+     * 鏌ヨ杞﹁締鍦ㄦ寚瀹氭椂闂磋寖鍥村唴鐨凣PS鏁版嵁锛堟寜閲囬泦鏃堕棿鎺掑簭锛�
+     * 
+     * @param vehicleId 杞﹁締ID
+     * @param startTime 寮�濮嬫椂闂�
+     * @param endTime 缁撴潫鏃堕棿
+     * @return GPS鍧愭爣鍒楄〃
+     */
+    public List<VehicleGps> selectGpsDataByTimeRange(@Param("vehicleId") Long vehicleId,
+                                                       @Param("startTime") Date startTime,
+                                                       @Param("endTime") Date endTime);
+
+    /**
+     * 鏌ヨ鎵�鏈夋椿璺冭溅杈咺D鍒楄〃
+     * 
+     * @return 杞﹁締ID鍒楄〃
+     */
+    public List<Long> selectActiveVehicleIds();
 } 
\ No newline at end of file
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/mapper/VehicleMileageStatsMapper.java b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/VehicleMileageStatsMapper.java
index ad784f7..328dbe8 100644
--- a/ruoyi-system/src/main/java/com/ruoyi/system/mapper/VehicleMileageStatsMapper.java
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/VehicleMileageStatsMapper.java
@@ -2,24 +2,22 @@
 
 import java.util.Date;
 import java.util.List;
-import com.ruoyi.system.domain.VehicleMileageStats;
 import org.apache.ibatis.annotations.Param;
+import com.ruoyi.system.domain.VehicleMileageStats;
+import com.ruoyi.system.domain.TaskTimeInterval;
 
 /**
  * 杞﹁締閲岀▼缁熻Mapper鎺ュ彛
- * 
- * @author ruoyi
- * @date 2025-01-15
  */
-public interface VehicleMileageStatsMapper 
-{
+public interface VehicleMileageStatsMapper {
+    
     /**
      * 鏌ヨ杞﹁締閲岀▼缁熻
      * 
-     * @param statsId 杞﹁締閲岀▼缁熻涓婚敭
+     * @param statsId 缁熻ID
      * @return 杞﹁締閲岀▼缁熻
      */
-    public VehicleMileageStats selectVehicleMileageStatsByStatsId(Long statsId);
+    public VehicleMileageStats selectVehicleMileageStatsById(Long statsId);
 
     /**
      * 鏌ヨ杞﹁締閲岀▼缁熻鍒楄〃
@@ -48,25 +46,38 @@
     /**
      * 鍒犻櫎杞﹁締閲岀▼缁熻
      * 
-     * @param statsId 杞﹁締閲岀▼缁熻涓婚敭
+     * @param statsId 缁熻ID
      * @return 缁撴灉
      */
-    public int deleteVehicleMileageStatsByStatsId(Long statsId);
+    public int deleteVehicleMileageStatsById(Long statsId);
 
     /**
      * 鎵归噺鍒犻櫎杞﹁締閲岀▼缁熻
      * 
-     * @param statsIds 闇�瑕佸垹闄ょ殑鏁版嵁涓婚敭闆嗗悎
+     * @param statsIds 闇�瑕佸垹闄ょ殑鏁版嵁ID
      * @return 缁撴灉
      */
-    public int deleteVehicleMileageStatsByStatsIds(Long[] statsIds);
+    public int deleteVehicleMileageStatsByIds(Long[] statsIds);
 
     /**
-     * 鏌ヨ鎸囧畾杞﹁締鍜屾棩鏈熺殑缁熻璁板綍
+     * 鏌ヨ杞﹁締鍦ㄦ寚瀹氭棩鏈熺殑缁熻璁板綍
      * 
      * @param vehicleId 杞﹁締ID
-     * @param statsDate 缁熻鏃ユ湡
+     * @param statDate 缁熻鏃ユ湡
      * @return 缁熻璁板綍
      */
-    public VehicleMileageStats selectByVehicleIdAndDate(@Param("vehicleId") Long vehicleId, @Param("statsDate") Date statsDate);
+    public VehicleMileageStats selectByVehicleIdAndDate(@Param("vehicleId") Long vehicleId, 
+                                                         @Param("statDate") Date statDate);
+
+    /**
+     * 鏌ヨ杞﹁締鍦ㄦ寚瀹氭椂闂磋寖鍥村唴鐨勪换鍔℃椂闂村尯闂�
+     * 
+     * @param vehicleId 杞﹁締ID
+     * @param startTime 寮�濮嬫椂闂�
+     * @param endTime 缁撴潫鏃堕棿
+     * @return 浠诲姟鏃堕棿鍖洪棿鍒楄〃
+     */
+    public List<TaskTimeInterval> selectTaskTimeIntervals(@Param("vehicleId") Long vehicleId,
+                                                           @Param("startTime") Date startTime,
+                                                           @Param("endTime") Date endTime);
 }
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 e69de29..c030849 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
@@ -0,0 +1,76 @@
+package com.ruoyi.system.service;
+
+import java.util.Date;
+import java.util.List;
+import com.ruoyi.system.domain.VehicleMileageStats;
+
+/**
+ * 杞﹁締閲岀▼缁熻Service鎺ュ彛
+ */
+public interface IVehicleMileageStatsService {
+    
+    /**
+     * 鏌ヨ杞﹁締閲岀▼缁熻
+     * 
+     * @param statsId 缁熻ID
+     * @return 杞﹁締閲岀▼缁熻
+     */
+    public VehicleMileageStats selectVehicleMileageStatsById(Long statsId);
+
+    /**
+     * 鏌ヨ杞﹁締閲岀▼缁熻鍒楄〃
+     * 
+     * @param vehicleMileageStats 杞﹁締閲岀▼缁熻
+     * @return 杞﹁締閲岀▼缁熻闆嗗悎
+     */
+    public List<VehicleMileageStats> selectVehicleMileageStatsList(VehicleMileageStats vehicleMileageStats);
+
+    /**
+     * 鏂板杞﹁締閲岀▼缁熻
+     * 
+     * @param vehicleMileageStats 杞﹁締閲岀▼缁熻
+     * @return 缁撴灉
+     */
+    public int insertVehicleMileageStats(VehicleMileageStats vehicleMileageStats);
+
+    /**
+     * 淇敼杞﹁締閲岀▼缁熻
+     * 
+     * @param vehicleMileageStats 杞﹁締閲岀▼缁熻
+     * @return 缁撴灉
+     */
+    public int updateVehicleMileageStats(VehicleMileageStats vehicleMileageStats);
+
+    /**
+     * 鎵归噺鍒犻櫎杞﹁締閲岀▼缁熻
+     * 
+     * @param statsIds 闇�瑕佸垹闄ょ殑鏁版嵁ID
+     * @return 缁撴灉
+     */
+    public int deleteVehicleMileageStatsByIds(Long[] statsIds);
+
+    /**
+     * 鍒犻櫎杞﹁締閲岀▼缁熻淇℃伅
+     * 
+     * @param statsId 缁熻ID
+     * @return 缁撴灉
+     */
+    public int deleteVehicleMileageStatsById(Long statsId);
+
+    /**
+     * 璁$畻骞朵繚瀛樻寚瀹氳溅杈嗘寚瀹氭棩鏈熺殑閲岀▼缁熻
+     * 
+     * @param vehicleId 杞﹁締ID
+     * @param statDate 缁熻鏃ユ湡
+     * @return 缁熻缁撴灉
+     */
+    public VehicleMileageStats calculateAndSaveMileageStats(Long vehicleId, Date statDate);
+
+    /**
+     * 鎵归噺璁$畻鎵�鏈夎溅杈嗘寚瀹氭棩鏈熺殑閲岀▼缁熻
+     * 
+     * @param statDate 缁熻鏃ユ湡
+     * @return 鎴愬姛缁熻鐨勮溅杈嗘暟閲�
+     */
+    public int batchCalculateMileageStats(Date statDate);
+}
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 e69de29..4bd7aa3 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
@@ -0,0 +1,296 @@
+package com.ruoyi.system.service.impl;
+
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.util.Calendar;
+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.stereotype.Service;
+import com.ruoyi.system.domain.VehicleGps;
+import com.ruoyi.system.domain.VehicleMileageStats;
+import com.ruoyi.system.domain.TaskTimeInterval;
+import com.ruoyi.system.mapper.VehicleGpsMapper;
+import com.ruoyi.system.mapper.VehicleMileageStatsMapper;
+import com.ruoyi.system.service.IVehicleMileageStatsService;
+
+/**
+ * 杞﹁締閲岀▼缁熻Service涓氬姟灞傚鐞�
+ */
+@Service
+public class VehicleMileageStatsServiceImpl implements IVehicleMileageStatsService {
+    
+    private static final Logger logger = LoggerFactory.getLogger(VehicleMileageStatsServiceImpl.class);
+    
+    /** 鍦扮悆鍗婂緞锛堝叕閲岋級 */
+    private static final double EARTH_RADIUS_KM = 6371.0;
+    
+    @Autowired
+    private VehicleMileageStatsMapper vehicleMileageStatsMapper;
+    
+    @Autowired
+    private VehicleGpsMapper vehicleGpsMapper;
+
+    /**
+     * 鏌ヨ杞﹁締閲岀▼缁熻
+     */
+    @Override
+    public VehicleMileageStats selectVehicleMileageStatsById(Long statsId) {
+        return vehicleMileageStatsMapper.selectVehicleMileageStatsById(statsId);
+    }
+
+    /**
+     * 鏌ヨ杞﹁締閲岀▼缁熻鍒楄〃
+     */
+    @Override
+    public List<VehicleMileageStats> selectVehicleMileageStatsList(VehicleMileageStats vehicleMileageStats) {
+        return vehicleMileageStatsMapper.selectVehicleMileageStatsList(vehicleMileageStats);
+    }
+
+    /**
+     * 鏂板杞﹁締閲岀▼缁熻
+     */
+    @Override
+    public int insertVehicleMileageStats(VehicleMileageStats vehicleMileageStats) {
+        return vehicleMileageStatsMapper.insertVehicleMileageStats(vehicleMileageStats);
+    }
+
+    /**
+     * 淇敼杞﹁締閲岀▼缁熻
+     */
+    @Override
+    public int updateVehicleMileageStats(VehicleMileageStats vehicleMileageStats) {
+        return vehicleMileageStatsMapper.updateVehicleMileageStats(vehicleMileageStats);
+    }
+
+    /**
+     * 鎵归噺鍒犻櫎杞﹁締閲岀▼缁熻
+     */
+    @Override
+    public int deleteVehicleMileageStatsByIds(Long[] statsIds) {
+        return vehicleMileageStatsMapper.deleteVehicleMileageStatsByIds(statsIds);
+    }
+
+    /**
+     * 鍒犻櫎杞﹁締閲岀▼缁熻淇℃伅
+     */
+    @Override
+    public int deleteVehicleMileageStatsById(Long statsId) {
+        return vehicleMileageStatsMapper.deleteVehicleMileageStatsById(statsId);
+    }
+
+    /**
+     * 璁$畻骞朵繚瀛樻寚瀹氳溅杈嗘寚瀹氭棩鏈熺殑閲岀▼缁熻
+     */
+    @Override
+    public VehicleMileageStats calculateAndSaveMileageStats(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. 鏌ヨ杞﹁締鍦ㄨ鏃ユ湡鐨凣PS鏁版嵁锛堟寜鏃堕棿鎺掑簭锛�
+            List<VehicleGps> gpsList = vehicleGpsMapper.selectGpsDataByTimeRange(vehicleId, dayStart, dayEnd);
+            
+            if (gpsList == null || gpsList.isEmpty()) {
+                logger.info("杞﹁締ID: {} 鍦ㄦ棩鏈�: {} 鏃燝PS鏁版嵁", vehicleId, statDate);
+                return null;
+            }
+            
+            // 3. 鏌ヨ杞﹁締鍦ㄨ鏃ユ湡鐨勪换鍔℃椂闂村尯闂�
+            List<TaskTimeInterval> taskIntervals = vehicleMileageStatsMapper.selectTaskTimeIntervals(vehicleId, dayStart, dayEnd);
+            
+            // 4. 璁$畻閲岀▼
+            MileageCalculation calculation = calculateMileage(gpsList, taskIntervals);
+            
+            // 5. 鏌ヨ鎴栧垱寤虹粺璁¤褰�
+            VehicleMileageStats stats = vehicleMileageStatsMapper.selectByVehicleIdAndDate(vehicleId, statDate);
+            boolean isNew = (stats == null);
+            
+            if (isNew) {
+                stats = new VehicleMileageStats();
+                stats.setVehicleId(vehicleId);
+                stats.setStatDate(statDate);
+                
+                // 鑾峰彇杞︾墝鍙�
+                if (!gpsList.isEmpty() && gpsList.get(0).getVehicleNo() != null) {
+                    stats.setVehicleNo(gpsList.get(0).getVehicleNo());
+                }
+            }
+            
+            // 6. 璁剧疆缁熻鏁版嵁
+            stats.setTotalMileage(calculation.totalMileage);
+            stats.setTaskMileage(calculation.taskMileage);
+            stats.setNonTaskMileage(calculation.nonTaskMileage);
+            stats.setTaskRatio(calculation.taskRatio);
+            stats.setGpsPointCount(gpsList.size());
+            stats.setTaskCount(taskIntervals == null ? 0 : taskIntervals.size());
+            
+            // 7. 淇濆瓨鍒版暟鎹簱
+            if (isNew) {
+                vehicleMileageStatsMapper.insertVehicleMileageStats(stats);
+            } else {
+                vehicleMileageStatsMapper.updateVehicleMileageStats(stats);
+            }
+            
+            logger.info("杞﹁締ID: {} 鏃ユ湡: {} 閲岀▼缁熻瀹屾垚 - 鎬婚噷绋�: {}km, 浠诲姟閲岀▼: {}km, 闈炰换鍔¢噷绋�: {}km, 鍗犳瘮: {}", 
+                       vehicleId, statDate, calculation.totalMileage, calculation.taskMileage, 
+                       calculation.nonTaskMileage, calculation.taskRatio);
+            
+            return stats;
+            
+        } catch (Exception e) {
+            logger.error("璁$畻杞﹁締閲岀▼缁熻澶辫触 - 杞﹁締ID: {}, 鏃ユ湡: {}", vehicleId, statDate, e);
+            throw new RuntimeException("璁$畻閲岀▼缁熻澶辫触: " + e.getMessage());
+        }
+    }
+
+    /**
+     * 鎵归噺璁$畻鎵�鏈夎溅杈嗘寚瀹氭棩鏈熺殑閲岀▼缁熻
+     */
+    @Override
+    public int batchCalculateMileageStats(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 {
+                    calculateAndSaveMileageStats(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());
+        }
+    }
+
+    /**
+     * 璁$畻閲岀▼鐨勫唴閮ㄦ柟娉�
+     */
+    private MileageCalculation calculateMileage(List<VehicleGps> gpsList, List<TaskTimeInterval> taskIntervals) {
+        MileageCalculation result = new MileageCalculation();
+        
+        // 閬嶅巻GPS鐐癸紝璁$畻鐩搁偦鐐逛箣闂寸殑璺濈
+        for (int i = 0; i < gpsList.size() - 1; i++) {
+            VehicleGps p1 = gpsList.get(i);
+            VehicleGps p2 = gpsList.get(i + 1);
+            
+            // 璁$畻涓ょ偣闂磋窛绂伙紙浣跨敤Haversine鍏紡锛�
+            double distance = calculateDistance(
+                p1.getLatitude().doubleValue(), 
+                p1.getLongitude().doubleValue(),
+                p2.getLatitude().doubleValue(), 
+                p2.getLongitude().doubleValue()
+            );
+            
+            // 鑾峰彇杩欐璺濈鐨勬椂闂村尯闂�
+            Date segmentStart = p1.getCollectTime();
+            Date segmentEnd = p2.getCollectTime();
+            
+            // 璁$畻杩欐璺濈鍦ㄤ换鍔℃椂娈电殑鍗犳瘮
+            double taskRatio = calculateTaskOverlapRatio(segmentStart, segmentEnd, taskIntervals);
+            
+            // 鍒嗘憡閲岀▼
+            double taskDistance = distance * taskRatio;
+            double nonTaskDistance = distance * (1 - taskRatio);
+            
+            result.totalMileage = result.totalMileage.add(BigDecimal.valueOf(distance));
+            result.taskMileage = result.taskMileage.add(BigDecimal.valueOf(taskDistance));
+            result.nonTaskMileage = result.nonTaskMileage.add(BigDecimal.valueOf(nonTaskDistance));
+        }
+        
+        // 璁$畻浠诲姟閲岀▼鍗犳瘮
+        if (result.totalMileage.compareTo(BigDecimal.ZERO) > 0) {
+            result.taskRatio = result.taskMileage.divide(result.totalMileage, 4, RoundingMode.HALF_UP);
+        }
+        
+        // 淇濈暀涓や綅灏忔暟
+        result.totalMileage = result.totalMileage.setScale(2, RoundingMode.HALF_UP);
+        result.taskMileage = result.taskMileage.setScale(2, RoundingMode.HALF_UP);
+        result.nonTaskMileage = result.nonTaskMileage.setScale(2, RoundingMode.HALF_UP);
+        
+        return result;
+    }
+
+    /**
+     * 浣跨敤Haversine鍏紡璁$畻涓や釜GPS鍧愭爣涔嬮棿鐨勮窛绂伙紙鍏噷锛�
+     */
+    private double calculateDistance(double lat1, double lon1, double lat2, double lon2) {
+        // 灏嗚搴﹁浆鎹负寮у害
+        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;
+    }
+
+    /**
+     * 璁$畻鏃堕棿娈典笌浠诲姟鏃舵鐨勯噸鍙犳瘮渚�
+     */
+    private double calculateTaskOverlapRatio(Date segmentStart, Date segmentEnd, List<TaskTimeInterval> taskIntervals) {
+        if (taskIntervals == null || taskIntervals.isEmpty()) {
+            return 0.0;
+        }
+        
+        long segmentDuration = segmentEnd.getTime() - segmentStart.getTime();
+        if (segmentDuration <= 0) {
+            return 0.0;
+        }
+        
+        long totalOverlap = 0;
+        
+        for (TaskTimeInterval task : taskIntervals) {
+            // 璁$畻閲嶅彔鏃堕棿
+            long overlapStart = Math.max(segmentStart.getTime(), task.getStartTime().getTime());
+            long overlapEnd = Math.min(segmentEnd.getTime(), task.getEndTime().getTime());
+            
+            if (overlapEnd > overlapStart) {
+                totalOverlap += (overlapEnd - overlapStart);
+            }
+        }
+        
+        return (double) totalOverlap / segmentDuration;
+    }
+
+    /**
+     * 閲岀▼璁$畻缁撴灉鍐呴儴绫�
+     */
+    private static class MileageCalculation {
+        BigDecimal totalMileage = BigDecimal.ZERO;
+        BigDecimal taskMileage = BigDecimal.ZERO;
+        BigDecimal nonTaskMileage = BigDecimal.ZERO;
+        BigDecimal taskRatio = BigDecimal.ZERO;
+    }
+}
diff --git a/ruoyi-system/src/main/resources/mapper/system/VehicleGpsMapper.xml b/ruoyi-system/src/main/resources/mapper/system/VehicleGpsMapper.xml
index 9e01aae..5d8d093 100644
--- a/ruoyi-system/src/main/resources/mapper/system/VehicleGpsMapper.xml
+++ b/ruoyi-system/src/main/resources/mapper/system/VehicleGpsMapper.xml
@@ -119,4 +119,21 @@
             where g2.vehicle_id = g.vehicle_id
         )
     </delete>
+
+    <select id="selectGpsDataByTimeRange" resultMap="VehicleGpsResult">
+        select gps_id, vehicle_id, device_id, longitude, latitude, altitude, speed, direction, 
+               collect_time, device_report_time, platform_process_time, create_time
+        from tb_vehicle_gps
+        where vehicle_id = #{vehicleId}
+          and collect_time &gt;= #{startTime}
+          and collect_time &lt;= #{endTime}
+        order by collect_time
+    </select>
+
+    <select id="selectActiveVehicleIds" resultType="Long">
+        select distinct vehicle_id
+        from tb_vehicle_gps
+        where collect_time &gt;= DATE_SUB(NOW(), INTERVAL 7 DAY)
+        order by vehicle_id
+    </select>
 </mapper> 
\ No newline at end of file
diff --git a/ruoyi-system/src/main/resources/mapper/system/VehicleMileageStatsMapper.xml b/ruoyi-system/src/main/resources/mapper/system/VehicleMileageStatsMapper.xml
index 1dd8199..95439e9 100644
--- a/ruoyi-system/src/main/resources/mapper/system/VehicleMileageStatsMapper.xml
+++ b/ruoyi-system/src/main/resources/mapper/system/VehicleMileageStatsMapper.xml
@@ -5,91 +5,126 @@
 <mapper namespace="com.ruoyi.system.mapper.VehicleMileageStatsMapper">
     
     <resultMap type="VehicleMileageStats" id="VehicleMileageStatsResult">
-        <result property="statsId"    column="stats_id"    />
-        <result property="vehicleId"    column="vehicle_id"    />
-        <result property="statsDate"    column="stats_date"    />
-        <result property="totalMileage"    column="total_mileage"    />
-        <result property="taskMileage"    column="task_mileage"    />
-        <result property="nonTaskMileage"    column="non_task_mileage"    />
-        <result property="taskMileageRatio"    column="task_mileage_ratio"    />
-        <result property="gpsCount"    column="gps_count"    />
-        <result property="createTime"    column="create_time"    />
-        <result property="updateTime"    column="update_time"    />
+        <id     property="statsId"         column="stats_id"           />
+        <result property="vehicleId"       column="vehicle_id"         />
+        <result property="vehicleNo"       column="vehicle_no"         />
+        <result property="statDate"        column="stat_date"          />
+        <result property="totalMileage"    column="total_mileage"      />
+        <result property="taskMileage"     column="task_mileage"       />
+        <result property="nonTaskMileage"  column="non_task_mileage"   />
+        <result property="taskRatio"       column="task_ratio"         />
+        <result property="gpsPointCount"   column="gps_point_count"    />
+        <result property="taskCount"       column="task_count"         />
+        <result property="createTime"      column="create_time"        />
+        <result property="updateTime"      column="update_time"        />
+    </resultMap>
+
+    <resultMap type="TaskTimeInterval" id="TaskTimeIntervalResult">
+        <result property="taskId"          column="task_id"            />
+        <result property="startTime"       column="start_time"         />
+        <result property="endTime"         column="end_time"           />
     </resultMap>
 
     <sql id="selectVehicleMileageStatsVo">
-        select stats_id, vehicle_id, stats_date, total_mileage, task_mileage, non_task_mileage, 
-               task_mileage_ratio, gps_count, create_time, update_time
-        from vehicle_mileage_stats
+        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
     </sql>
 
     <select id="selectVehicleMileageStatsList" parameterType="VehicleMileageStats" resultMap="VehicleMileageStatsResult">
         <include refid="selectVehicleMileageStatsVo"/>
         <where>  
-            <if test="vehicleId != null "> and vehicle_id = #{vehicleId}</if>
-            <if test="statsDate != null "> and stats_date = #{statsDate}</if>
+            <if test="vehicleId != null">
+                and vehicle_id = #{vehicleId}
+            </if>
+            <if test="vehicleNo != null and vehicleNo != ''">
+                and vehicle_no = #{vehicleNo}
+            </if>
+            <if test="statDate != null">
+                and stat_date = #{statDate}
+            </if>
+            <if test="params.beginStatDate != null and params.beginStatDate != ''">
+                and stat_date &gt;= #{params.beginStatDate}
+            </if>
+            <if test="params.endStatDate != null and params.endStatDate != ''">
+                and stat_date &lt;= #{params.endStatDate}
+            </if>
         </where>
-        order by stats_date desc, vehicle_id
+        order by stat_date desc, vehicle_id
     </select>
     
-    <select id="selectVehicleMileageStatsByStatsId" parameterType="Long" resultMap="VehicleMileageStatsResult">
+    <select id="selectVehicleMileageStatsById" parameterType="Long" resultMap="VehicleMileageStatsResult">
         <include refid="selectVehicleMileageStatsVo"/>
         where stats_id = #{statsId}
     </select>
-    
+
     <select id="selectByVehicleIdAndDate" resultMap="VehicleMileageStatsResult">
         <include refid="selectVehicleMileageStatsVo"/>
-        where vehicle_id = #{vehicleId} and stats_date = #{statsDate}
+        where vehicle_id = #{vehicleId} and stat_date = #{statDate}
+    </select>
+
+    <select id="selectTaskTimeIntervals" resultMap="TaskTimeIntervalResult">
+        select tv.task_id, t.create_time as start_time, 
+               IFNULL(t.actual_end_time, NOW()) as end_time
+        from sys_task_vehicle tv
+        inner join sys_task t on tv.task_id = t.task_id
+        where tv.vehicle_id = #{vehicleId}
+          and t.del_flag = '0'
+          and t.actual_end_time is not null
+          and t.create_time &lt; #{endTime}
+          and t.actual_end_time &gt; #{startTime}
+        order by t.create_time
     </select>
         
     <insert id="insertVehicleMileageStats" parameterType="VehicleMileageStats" useGeneratedKeys="true" keyProperty="statsId">
-        insert into vehicle_mileage_stats
+        insert into tb_vehicle_mileage_stats
         <trim prefix="(" suffix=")" suffixOverrides=",">
             <if test="vehicleId != null">vehicle_id,</if>
-            <if test="statsDate != null">stats_date,</if>
+            <if test="vehicleNo != null">vehicle_no,</if>
+            <if test="statDate != null">stat_date,</if>
             <if test="totalMileage != null">total_mileage,</if>
             <if test="taskMileage != null">task_mileage,</if>
             <if test="nonTaskMileage != null">non_task_mileage,</if>
-            <if test="taskMileageRatio != null">task_mileage_ratio,</if>
-            <if test="gpsCount != null">gps_count,</if>
-            <if test="createTime != null">create_time,</if>
-            <if test="updateTime != null">update_time,</if>
+            <if test="taskRatio != null">task_ratio,</if>
+            <if test="gpsPointCount != null">gps_point_count,</if>
+            <if test="taskCount != null">task_count,</if>
+            create_time
          </trim>
         <trim prefix="values (" suffix=")" suffixOverrides=",">
             <if test="vehicleId != null">#{vehicleId},</if>
-            <if test="statsDate != null">#{statsDate},</if>
+            <if test="vehicleNo != null">#{vehicleNo},</if>
+            <if test="statDate != null">#{statDate},</if>
             <if test="totalMileage != null">#{totalMileage},</if>
             <if test="taskMileage != null">#{taskMileage},</if>
             <if test="nonTaskMileage != null">#{nonTaskMileage},</if>
-            <if test="taskMileageRatio != null">#{taskMileageRatio},</if>
-            <if test="gpsCount != null">#{gpsCount},</if>
-            <if test="createTime != null">#{createTime},</if>
-            <if test="updateTime != null">#{updateTime},</if>
+            <if test="taskRatio != null">#{taskRatio},</if>
+            <if test="gpsPointCount != null">#{gpsPointCount},</if>
+            <if test="taskCount != null">#{taskCount},</if>
+            NOW()
          </trim>
     </insert>
 
     <update id="updateVehicleMileageStats" parameterType="VehicleMileageStats">
-        update vehicle_mileage_stats
+        update tb_vehicle_mileage_stats
         <trim prefix="SET" suffixOverrides=",">
-            <if test="vehicleId != null">vehicle_id = #{vehicleId},</if>
-            <if test="statsDate != null">stats_date = #{statsDate},</if>
+            <if test="vehicleNo != null">vehicle_no = #{vehicleNo},</if>
             <if test="totalMileage != null">total_mileage = #{totalMileage},</if>
             <if test="taskMileage != null">task_mileage = #{taskMileage},</if>
             <if test="nonTaskMileage != null">non_task_mileage = #{nonTaskMileage},</if>
-            <if test="taskMileageRatio != null">task_mileage_ratio = #{taskMileageRatio},</if>
-            <if test="gpsCount != null">gps_count = #{gpsCount},</if>
-            <if test="createTime != null">create_time = #{createTime},</if>
-            <if test="updateTime != null">update_time = #{updateTime},</if>
+            <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>
+            update_time = NOW()
         </trim>
         where stats_id = #{statsId}
     </update>
 
-    <delete id="deleteVehicleMileageStatsByStatsId" parameterType="Long">
-        delete from vehicle_mileage_stats where stats_id = #{statsId}
+    <delete id="deleteVehicleMileageStatsById" parameterType="Long">
+        delete from tb_vehicle_mileage_stats where stats_id = #{statsId}
     </delete>
 
-    <delete id="deleteVehicleMileageStatsByStatsIds" parameterType="String">
-        delete from vehicle_mileage_stats where stats_id in 
+    <delete id="deleteVehicleMileageStatsByIds" parameterType="Long">
+        delete from tb_vehicle_mileage_stats where stats_id in 
         <foreach item="statsId" collection="array" open="(" separator="," close=")">
             #{statsId}
         </foreach>
diff --git a/sql/vehicle_mileage_stats.sql b/sql/vehicle_mileage_stats.sql
index b059dec..25d9667 100644
--- a/sql/vehicle_mileage_stats.sql
+++ b/sql/vehicle_mileage_stats.sql
@@ -1,37 +1,40 @@
 -- 杞﹁締閲岀▼缁熻琛�
-DROP TABLE IF EXISTS `vehicle_mileage_stats`;
-CREATE TABLE `vehicle_mileage_stats` (
+CREATE TABLE `tb_vehicle_mileage_stats` (
   `stats_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '缁熻ID',
   `vehicle_id` bigint(20) NOT NULL COMMENT '杞﹁締ID',
-  `stats_date` date NOT NULL COMMENT '缁熻鏃ユ湡',
-  `total_mileage` decimal(10,2) DEFAULT '0.00' COMMENT '鎬婚噷绋�(鍏噷)',
-  `task_mileage` decimal(10,2) DEFAULT '0.00' COMMENT '浠诲姟閲岀▼(鍏噷)',
-  `non_task_mileage` decimal(10,2) DEFAULT '0.00' COMMENT '闈炰换鍔¢噷绋�(鍏噷)',
-  `task_mileage_ratio` decimal(5,2) DEFAULT '0.00' COMMENT '浠诲姟閲岀▼鍗犳瘮(%)',
-  `gps_count` int(11) DEFAULT '0' COMMENT 'GPS鐐规暟',
+  `vehicle_no` varchar(20) DEFAULT NULL COMMENT '杞︾墝鍙�',
+  `stat_date` date NOT NULL COMMENT '缁熻鏃ユ湡',
+  `total_mileage` decimal(10,2) DEFAULT 0.00 COMMENT '鎬婚噷绋�(鍏噷)',
+  `task_mileage` decimal(10,2) DEFAULT 0.00 COMMENT '浠诲姟鏃舵閲岀▼(鍏噷)',
+  `non_task_mileage` decimal(10,2) DEFAULT 0.00 COMMENT '闈炰换鍔℃椂娈甸噷绋�(鍏噷)',
+  `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 '浠诲姟鏁伴噺',
   `create_time` datetime DEFAULT NULL COMMENT '鍒涘缓鏃堕棿',
   `update_time` datetime DEFAULT NULL COMMENT '鏇存柊鏃堕棿',
   PRIMARY KEY (`stats_id`),
-  UNIQUE KEY `uk_vehicle_date` (`vehicle_id`,`stats_date`),
-  KEY `idx_stats_date` (`stats_date`),
-  KEY `idx_vehicle_id` (`vehicle_id`)
+  UNIQUE KEY `uk_vehicle_date` (`vehicle_id`, `stat_date`),
+  KEY `idx_vehicle_id` (`vehicle_id`),
+  KEY `idx_stat_date` (`stat_date`)
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='杞﹁締閲岀▼缁熻琛�';
 
--- 杞﹁締閲岀▼缁熻鏄庣粏琛�
-DROP TABLE IF EXISTS `vehicle_mileage_stats_detail`;
-CREATE TABLE `vehicle_mileage_stats_detail` (
+-- 杞﹁締閲岀▼缁熻鏄庣粏琛紙鍙�夛紝鐢ㄤ簬璋冭瘯鍜岃拷婧級
+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',
-  `task_id` bigint(20) DEFAULT NULL COMMENT '浠诲姟ID(浠诲姟鏃舵)',
-  `start_time` datetime NOT NULL COMMENT '寮�濮嬫椂闂�',
-  `end_time` datetime NOT NULL COMMENT '缁撴潫鏃堕棿',
-  `mileage` decimal(10,2) DEFAULT '0.00' COMMENT '閲岀▼(鍏噷)',
-  `is_task_period` char(1) DEFAULT '0' COMMENT '鏄惁浠诲姟鏃舵(0鍚� 1鏄�)',
-  `gps_count` int(11) DEFAULT '0' COMMENT 'GPS鐐规暟',
+  `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`),
-  KEY `idx_task_id` (`task_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 e69de29..2629623 100644
--- a/sql/vehicle_mileage_stats_job.sql
+++ b/sql/vehicle_mileage_stats_job.sql
@@ -0,0 +1,17 @@
+-- 娣诲姞杞﹁締閲岀▼缁熻瀹氭椂浠诲姟
+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', 
+  '0 30 1 * * ?', 
+  '3', 
+  '1', 
+  '0', 
+  'admin', 
+  NOW(), 
+  '', 
+  NULL, 
+  '姣忓ぉ鍑屾櫒1:30鎵ц锛岀粺璁℃槰鏃ユ墍鏈夎溅杈嗙殑琛岄┒閲岀▼'
+);
diff --git a/sql/vehicle_mileage_stats_menu.sql b/sql/vehicle_mileage_stats_menu.sql
index e69de29..d2dd644 100644
--- a/sql/vehicle_mileage_stats_menu.sql
+++ b/sql/vehicle_mileage_stats_menu.sql
@@ -0,0 +1,56 @@
+-- 杞﹁締閲岀▼缁熻鑿滃崟鏉冮檺
+-- 鐖惰彍鍗旾D闇�瑕佹牴鎹疄闄呯郴缁熶腑鐨�"绯荤粺绠$悊"鎴�"杞﹁締绠$悊"鑿滃崟ID杩涜璋冩暣
+
+-- 1. 娣诲姞杞﹁締閲岀▼缁熻鑿滃崟
+INSERT INTO sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
+VALUES (
+  (SELECT IFNULL(MAX(menu_id), 0) + 1 FROM sys_menu t),
+  '杞﹁締閲岀▼缁熻', 
+  3, -- 绯荤粺宸ュ叿锛岃鏍规嵁瀹為檯鎯呭喌淇敼parent_id
+  6, 
+  'mileageStats', 
+  'system/mileageStats/index', 
+  1, 
+  0, 
+  'C', 
+  '0', 
+  '0', 
+  'system:mileageStats:list', 
+  'chart', 
+  'admin', 
+  NOW(), 
+  '', 
+  NULL, 
+  '杞﹁締閲岀▼缁熻鑿滃崟'
+);
+
+-- 鑾峰彇鍒氭彃鍏ョ殑鑿滃崟ID锛堢敤浜庡悗缁寜閽潈闄愶級
+SET @menuId = (SELECT MAX(menu_id) FROM sys_menu WHERE menu_name = '杞﹁締閲岀▼缁熻');
+
+-- 2. 娣诲姞鏌ヨ鎸夐挳
+INSERT INTO sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
+VALUES ((SELECT IFNULL(MAX(menu_id), 0) + 1 FROM sys_menu t), '杞﹁締閲岀▼缁熻鏌ヨ', @menuId, 1, '#', '', 1, 0, 'F', '0', '0', 'system:mileageStats:query', '#', 'admin', NOW(), '', NULL, '');
+
+-- 3. 娣诲姞鏂板鎸夐挳
+INSERT INTO sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
+VALUES ((SELECT IFNULL(MAX(menu_id), 0) + 1 FROM sys_menu t), '杞﹁締閲岀▼缁熻鏂板', @menuId, 2, '#', '', 1, 0, 'F', '0', '0', 'system:mileageStats:add', '#', 'admin', NOW(), '', NULL, '');
+
+-- 4. 娣诲姞淇敼鎸夐挳
+INSERT INTO sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
+VALUES ((SELECT IFNULL(MAX(menu_id), 0) + 1 FROM sys_menu t), '杞﹁締閲岀▼缁熻淇敼', @menuId, 3, '#', '', 1, 0, 'F', '0', '0', 'system:mileageStats:edit', '#', 'admin', NOW(), '', NULL, '');
+
+-- 5. 娣诲姞鍒犻櫎鎸夐挳
+INSERT INTO sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
+VALUES ((SELECT IFNULL(MAX(menu_id), 0) + 1 FROM sys_menu t), '杞﹁締閲岀▼缁熻鍒犻櫎', @menuId, 4, '#', '', 1, 0, 'F', '0', '0', 'system:mileageStats:remove', '#', 'admin', NOW(), '', NULL, '');
+
+-- 6. 娣诲姞瀵煎嚭鎸夐挳
+INSERT INTO sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
+VALUES ((SELECT IFNULL(MAX(menu_id), 0) + 1 FROM sys_menu t), '杞﹁締閲岀▼缁熻瀵煎嚭', @menuId, 5, '#', '', 1, 0, 'F', '0', '0', 'system:mileageStats:export', '#', 'admin', NOW(), '', NULL, '');
+
+-- 7. 娣诲姞璁$畻鎸夐挳
+INSERT INTO sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
+VALUES ((SELECT IFNULL(MAX(menu_id), 0) + 1 FROM sys_menu t), '杞﹁締閲岀▼缁熻璁$畻', @menuId, 6, '#', '', 1, 0, 'F', '0', '0', 'system:mileageStats:calculate', '#', 'admin', NOW(), '', NULL, '');
+
+-- 8. 娣诲姞鎵归噺璁$畻鎸夐挳
+INSERT INTO sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
+VALUES ((SELECT IFNULL(MAX(menu_id), 0) + 1 FROM sys_menu t), '杞﹁締閲岀▼缁熻鎵归噺璁$畻', @menuId, 7, '#', '', 1, 0, 'F', '0', '0', 'system:mileageStats:batch', '#', 'admin', NOW(), '', NULL, '');
diff --git "a/sql/\350\275\246\350\276\206\351\207\214\347\250\213\347\273\237\350\256\241\344\275\277\347\224\250\350\257\264\346\230\216.md" "b/sql/\350\275\246\350\276\206\351\207\214\347\250\213\347\273\237\350\256\241\344\275\277\347\224\250\350\257\264\346\230\216.md"
index e69de29..31a6ee0 100644
--- "a/sql/\350\275\246\350\276\206\351\207\214\347\250\213\347\273\237\350\256\241\344\275\277\347\224\250\350\257\264\346\230\216.md"
+++ "b/sql/\350\275\246\350\276\206\351\207\214\347\250\213\347\273\237\350\256\241\344\275\277\347\224\250\350\257\264\346\230\216.md"
@@ -0,0 +1,202 @@
+# 杞﹁締GPS閲岀▼缁熻鍔熻兘浣跨敤璇存槑
+
+## 鍔熻兘姒傝堪
+
+鏈姛鑳藉疄鐜颁簡杞﹁締GPS琛岄┒閲岀▼鐨勮嚜鍔ㄧ粺璁★紝鍖呮嫭锛�
+1. **鎬婚噷绋嬬粺璁�**锛氬熀浜嶨PS鐐硅绠楄溅杈嗘瘡鏃ユ�昏椹堕噷绋�
+2. **浠诲姟鏃舵閲岀▼**锛氳绠楄溅杈嗗湪鎵ц浠诲姟鏈熼棿鐨勮椹堕噷绋�
+3. **闈炰换鍔℃椂娈甸噷绋�**锛氳绠楄溅杈嗛潪鎵ц浠诲姟鏈熼棿鐨勮椹堕噷绋�
+4. **浠诲姟閲岀▼鍗犳瘮**锛氳绠椾换鍔¢噷绋嬪崰鎬婚噷绋嬬殑姣斾緥
+
+## 鏍稿績绠楁硶
+
+### 1. 璺濈璁$畻
+浣跨敤 **Haversine鍏紡** 璁$畻鐩搁偦GPS鐐逛箣闂寸殑瀹為檯璺濈锛堣�冭檻鍦扮悆鏇茬巼锛�
+
+### 2. 閲岀▼鍒嗘憡绠楁硶
+鏍规嵁GPS璁板綍鐨勬椂闂村尯闂翠笌浠诲姟鏃堕棿鍖洪棿鐨�**閲嶅彔姣斾緥**锛屽皢姣忔璺濈鍒嗘憡鍒颁换鍔¢噷绋嬪拰闈炰换鍔¢噷绋嬶細
+
+```
+鏃堕棿閲嶅彔姣斾緥 = 閲嶅彔鏃堕暱 / 鎬绘椂闀�
+浠诲姟閲岀▼ = 娈佃窛绂� 脳 鏃堕棿閲嶅彔姣斾緥
+闈炰换鍔¢噷绋� = 娈佃窛绂� 脳 (1 - 鏃堕棿閲嶅彔姣斾緥)
+```
+
+## 閮ㄧ讲姝ラ
+
+### 1. 鎵ц鏁版嵁搴撹剼鏈�
+
+鎸夐『搴忔墽琛屼互涓婼QL鏂囦欢锛�
+
+```bash
+# 1. 鍒涘缓缁熻琛�
+sql/vehicle_mileage_stats.sql
+
+# 2. 娣诲姞瀹氭椂浠诲姟
+sql/vehicle_mileage_stats_job.sql
+
+# 3. 娣诲姞鑿滃崟鏉冮檺
+sql/vehicle_mileage_stats_menu.sql
+```
+
+### 2. 浠g爜宸茶嚜鍔ㄩ儴缃�
+
+浠ヤ笅浠g爜鏂囦欢宸插垱寤猴細
+
+**瀹炰綋绫伙細**
+- `VehicleMileageStats.java` - 閲岀▼缁熻瀹炰綋
+- `TaskTimeInterval.java` - 浠诲姟鏃堕棿鍖洪棿
+
+**鏁版嵁璁块棶灞傦細**
+- `VehicleMileageStatsMapper.java` - 閲岀▼缁熻Mapper鎺ュ彛
+- `VehicleMileageStatsMapper.xml` - MyBatis鏄犲皠鏂囦欢
+- `VehicleGpsMapper.java` - 鎵╁睍GPS鏌ヨ鏂规硶
+
+**涓氬姟灞傦細**
+- `IVehicleMileageStatsService.java` - Service鎺ュ彛
+- `VehicleMileageStatsServiceImpl.java` - Service瀹炵幇锛堟牳蹇冪畻娉曪級
+
+**鎺у埗灞傦細**
+- `VehicleMileageStatsController.java` - REST API鎺ュ彛
+
+**瀹氭椂浠诲姟锛�**
+- `VehicleMileageStatsTask.java` - 瀹氭椂缁熻浠诲姟
+
+### 3. 鍚姩瀹氭椂浠诲姟
+
+绯荤粺浼氳嚜鍔ㄦ坊鍔犲畾鏃朵换鍔★紝榛樿閰嶇疆锛�
+- **鎵ц鏃堕棿**锛氭瘡澶╁噷鏅� 1:30
+- **缁熻鑼冨洿**锛氬墠涓�澶╃殑鎵�鏈夎溅杈咷PS鏁版嵁
+- **Cron琛ㄨ揪寮�**锛歚0 30 1 * * ?`
+
+鍦ㄧ郴缁熺鐞� -> 瀹氭椂浠诲姟涓彲浠ユ煡鐪嬪拰绠$悊璇ヤ换鍔°��
+
+## 浣跨敤鏂瑰紡
+
+### 1. 鑷姩缁熻锛堟帹鑽愶級
+
+瀹氭椂浠诲姟姣忓ぉ鍑屾櫒鑷姩鎵ц锛屾棤闇�浜哄伐骞查銆�
+
+### 2. 鎵嬪姩瑙﹀彂缁熻
+
+#### 2.1 鍗曡溅杈嗙粺璁�
+
+璋冪敤鎺ュ彛锛�
+```
+POST /system/mileageStats/calculate
+鍙傛暟锛�
+  - vehicleId: 杞﹁締ID
+  - statDate: 缁熻鏃ユ湡锛堟牸寮忥細yyyy-MM-dd锛�
+```
+
+#### 2.2 鎵归噺缁熻
+
+璋冪敤鎺ュ彛锛�
+```
+POST /system/mileageStats/batchCalculate
+鍙傛暟锛�
+  - statDate: 缁熻鏃ユ湡锛堟牸寮忥細yyyy-MM-dd锛�
+```
+
+#### 2.3 閫氳繃瀹氭椂浠诲姟琛ョ畻鍘嗗彶鏁版嵁
+
+鍦ㄥ畾鏃朵换鍔$鐞嗕腑锛屾墽琛岋細
+```
+vehicleMileageStatsTask.calculateMileageByDate('2025-11-09')
+```
+
+### 3. 鏌ヨ缁熻缁撴灉
+
+```
+GET /system/mileageStats/list
+鍙傛暟锛堝彲閫夛級锛�
+  - vehicleId: 杞﹁締ID
+  - vehicleNo: 杞︾墝鍙�
+  - statDate: 缁熻鏃ユ湡
+  - beginStatDate: 寮�濮嬫棩鏈�
+  - endStatDate: 缁撴潫鏃ユ湡
+```
+
+### 4. 瀵煎嚭缁熻鏁版嵁
+
+```
+POST /system/mileageStats/export
+鍙傛暟锛氬悓鏌ヨ鎺ュ彛
+```
+
+## 鏁版嵁琛ㄧ粨鏋�
+
+### tb_vehicle_mileage_stats锛堥噷绋嬬粺璁¤〃锛�
+
+| 瀛楁 | 绫诲瀷 | 璇存槑 |
+|------|------|------|
+| stats_id | bigint | 缁熻ID锛堜富閿級 |
+| vehicle_id | bigint | 杞﹁締ID |
+| vehicle_no | varchar(20) | 杞︾墝鍙� |
+| stat_date | date | 缁熻鏃ユ湡 |
+| total_mileage | decimal(10,2) | 鎬婚噷绋嬶紙鍏噷锛� |
+| task_mileage | decimal(10,2) | 浠诲姟鏃舵閲岀▼锛堝叕閲岋級 |
+| non_task_mileage | decimal(10,2) | 闈炰换鍔℃椂娈甸噷绋嬶紙鍏噷锛� |
+| task_ratio | decimal(5,4) | 浠诲姟閲岀▼鍗犳瘮锛�0-1锛� |
+| gps_point_count | int | GPS鐐规暟閲� |
+| task_count | int | 浠诲姟鏁伴噺 |
+
+### tb_vehicle_mileage_detail锛堥噷绋嬫槑缁嗚〃锛�
+
+鐢ㄤ簬璋冭瘯鍜岃拷婧紝璁板綍姣忔GPS杞ㄨ抗鐨勯噷绋嬪垎鎽婃槑缁嗐��
+
+## 娉ㄦ剰浜嬮」
+
+### 1. GPS鏁版嵁璐ㄩ噺
+
+- GPS閲囬泦闂撮殧寤鸿鍦� 30-60绉�
+- 杩囩煭锛氳绠楅噺澶э紝鎬ц兘褰卞搷
+- 杩囬暱锛氶噷绋嬬簿搴﹂檷浣�
+
+### 2. 浠诲姟鏃堕棿瀹氫箟
+
+浠诲姟鏃舵 = 浠庝换鍔″垱寤烘椂闂达紙`create_time`锛夊埌浠诲姟瀹屾垚鏃堕棿锛坄actual_end_time`锛�
+
+纭繚浠诲姟琛ㄤ腑杩欎袱涓瓧娈靛噯纭褰曘��
+
+### 3. 鎬ц兘浼樺寲
+
+- 缁熻鏁版嵁鎸夋棩鏈熸眹鎬伙紝閬垮厤瀹炴椂璁$畻
+- 寤鸿淇濈暀3-6涓湀鐨勭粺璁℃暟鎹紝瀹氭湡褰掓。鍘嗗彶鏁版嵁
+- GPS鍘熷鏁版嵁寤鸿淇濈暀7-30澶╋紙鐢辨竻鐞嗕换鍔℃帶鍒讹級
+
+### 4. 鏁版嵁琛ョ畻
+
+濡傞渶琛ョ畻鍘嗗彶鏁版嵁锛屽彲浠ラ�氳繃瀹氭椂浠诲姟鎴朅PI鎺ュ彛鎵归噺鎵ц锛�
+
+```java
+// 琛ョ畻鏈�杩�7澶╃殑鏁版嵁绀轰緥
+for (int i = 1; i <= 7; i++) {
+    String date = "2025-11-" + String.format("%02d", i);
+    vehicleMileageStatsTask.calculateMileageByDate(date);
+}
+```
+
+## 鎵╁睍璇存槑
+
+### 澶╁湴鍥炬帴鍙i泦鎴�
+
+铏界劧鏍稿績绠楁硶浣跨敤Haversine鍏紡璁$畻璺濈锛屼絾绯荤粺宸查泦鎴愬ぉ鍦板浘鎺ュ彛锛屽彲鐢ㄤ簬锛�
+
+1. **鍦板潃瑙f瀽**锛氬皢GPS鍧愭爣杞崲涓哄湴鍧�淇℃伅
+2. **璺緞瑙勫垝**锛氳绠楀疄闄呴亾璺窛绂伙紙姣旂洿绾胯窛绂绘洿鍑嗙‘锛�
+3. **POI鏌ヨ**锛氭煡璇㈡部閫斿叴瓒g偣
+
+濡傞渶浣跨敤澶╁湴鍥続PI杩涜璺緞璺濈璁$畻锛屽彲鍙傝�� `VehicleGpsController` 涓殑澶╁湴鍥炬帴鍙c��
+
+## 鎶�鏈敮鎸�
+
+濡傛湁闂锛岃妫�鏌ワ細
+1. 鏁版嵁搴撹〃鏄惁姝g‘鍒涘缓
+2. 瀹氭椂浠诲姟鏄惁姝e父鍚姩
+3. GPS鏁版嵁鏄惁姝e父閲囬泦
+4. 浠诲姟琛ㄧ殑鏃堕棿瀛楁鏄惁鍑嗙‘
+
+鏃ュ織浣嶇疆锛�
+- Service灞傛棩蹇楋細鎼滅储 `VehicleMileageStatsServiceImpl`
+- 瀹氭椂浠诲姟鏃ュ織锛氭悳绱� `VehicleMileageStatsTask`
diff --git "a/sql/\350\275\246\350\276\206\351\207\214\347\250\213\347\273\237\350\256\241\345\256\236\347\216\260\346\200\273\347\273\223.md" "b/sql/\350\275\246\350\276\206\351\207\214\347\250\213\347\273\237\350\256\241\345\256\236\347\216\260\346\200\273\347\273\223.md"
index e69de29..1c173f1 100644
--- "a/sql/\350\275\246\350\276\206\351\207\214\347\250\213\347\273\237\350\256\241\345\256\236\347\216\260\346\200\273\347\273\223.md"
+++ "b/sql/\350\275\246\350\276\206\351\207\214\347\250\213\347\273\237\350\256\241\345\256\236\347\216\260\346\200\273\347\273\223.md"
@@ -0,0 +1,339 @@
+# 杞﹁締GPS閲岀▼缁熻鍔熻兘瀹炵幇鎬荤粨
+
+## 涓�銆佸姛鑳藉疄鐜版竻鍗�
+
+### 鉁� 1. 鏁版嵁搴撳眰
+- [x] `vehicle_mileage_stats.sql` - 鍒涘缓閲岀▼缁熻琛ㄥ拰鏄庣粏琛�
+- [x] `vehicle_mileage_stats_job.sql` - 鍒涘缓瀹氭椂浠诲姟閰嶇疆
+- [x] `vehicle_mileage_stats_menu.sql` - 鍒涘缓鑿滃崟鏉冮檺閰嶇疆
+
+### 鉁� 2. 瀹炰綋绫伙紙Domain锛�
+- [x] `VehicleMileageStats.java` - 閲岀▼缁熻瀹炰綋
+- [x] `TaskTimeInterval.java` - 浠诲姟鏃堕棿鍖洪棿杈呭姪绫�
+
+### 鉁� 3. 鏁版嵁璁块棶灞傦紙Mapper锛�
+- [x] `VehicleMileageStatsMapper.java` - 閲岀▼缁熻Mapper鎺ュ彛
+- [x] `VehicleMileageStatsMapper.xml` - MyBatis鏄犲皠閰嶇疆
+- [x] `VehicleGpsMapper.java` - 鎵╁睍GPS鏌ヨ鏂规硶锛堟柊澧�2涓柟娉曪級
+- [x] `VehicleGpsMapper.xml` - 鎵╁睍GPS鏌ヨSQL
+
+### 鉁� 4. 涓氬姟閫昏緫灞傦紙Service锛�
+- [x] `IVehicleMileageStatsService.java` - Service鎺ュ彛
+- [x] `VehicleMileageStatsServiceImpl.java` - Service瀹炵幇锛堟牳蹇冪畻娉曪級
+
+### 鉁� 5. 鎺у埗灞傦紙Controller锛�
+- [x] `VehicleMileageStatsController.java` - REST API鎺ュ彛
+
+### 鉁� 6. 瀹氭椂浠诲姟锛圱ask锛�
+- [x] `VehicleMileageStatsTask.java` - 鑷姩缁熻瀹氭椂浠诲姟
+
+### 鉁� 7. 鍓嶇API
+- [x] `mileageStats.js` - 鍓嶇鎺ュ彛灏佽
+
+### 鉁� 8. 鏂囨。
+- [x] `杞﹁締閲岀▼缁熻浣跨敤璇存槑.md` - 璇︾粏浣跨敤鏂囨。
+
+## 浜屻�佹牳蹇冩妧鏈疄鐜�
+
+### 1. 閲岀▼璁$畻绠楁硶
+
+#### Haversine鍏紡锛堣绠桮PS鐐归棿璺濈锛�
+```java
+private double calculateDistance(double lat1, double lon1, double lat2, double lon2) {
+    double dLat = Math.toRadians(lat2 - lat1);
+    double dLon = Math.toRadians(lon2 - lon1);
+    double rLat1 = Math.toRadians(lat1);
+    double rLat2 = Math.toRadians(lat2);
+
+    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;
+}
+```
+
+#### 鏃堕棿閲嶅彔姣斾緥璁$畻锛堜换鍔¢噷绋嬪垎鎽婏級
+```java
+private double calculateTaskOverlapRatio(Date segmentStart, Date segmentEnd, 
+                                          List<TaskTimeInterval> taskIntervals) {
+    long segmentDuration = segmentEnd.getTime() - segmentStart.getTime();
+    long totalOverlap = 0;
+    
+    for (TaskTimeInterval task : taskIntervals) {
+        long overlapStart = Math.max(segmentStart.getTime(), task.getStartTime().getTime());
+        long overlapEnd = Math.min(segmentEnd.getTime(), task.getEndTime().getTime());
+        
+        if (overlapEnd > overlapStart) {
+            totalOverlap += (overlapEnd - overlapStart);
+        }
+    }
+    
+    return (double) totalOverlap / segmentDuration;
+}
+```
+
+### 2. 浠诲姟鏃舵瀹氫箟
+
+浠诲姟鏃舵 = 浠庝换鍔″垱寤烘椂闂达紙`sys_task.create_time`锛夊埌浠诲姟瀹屾垚鏃堕棿锛坄sys_task.actual_end_time`锛�
+
+SQL鏌ヨ锛�
+```sql
+select tv.task_id, t.create_time as start_time, 
+       IFNULL(t.actual_end_time, NOW()) as end_time
+from sys_task_vehicle tv
+inner join sys_task t on tv.task_id = t.task_id
+where tv.vehicle_id = #{vehicleId}
+  and t.del_flag = '0'
+  and t.actual_end_time is not null
+  and t.create_time < #{endTime}
+  and t.actual_end_time > #{startTime}
+```
+
+### 3. 缁熻鏁版嵁缂撳瓨
+
+- 姣忔棩瀹氭椂浠诲姟鑷姩缁熻鍓嶄竴澶╃殑鏁版嵁
+- 缁熻缁撴灉瀛樺偍鍦� `tb_vehicle_mileage_stats` 琛ㄤ腑
+- 鏀寔閲嶅璁$畻锛堟洿鏂板凡鏈夎褰曪級
+- 鍞竴绱㈠紩锛歚uk_vehicle_date (vehicle_id, stat_date)`
+
+## 涓夈�丄PI鎺ュ彛璇存槑
+
+### 1. 鏌ヨ缁熻鍒楄〃
+```
+GET /system/mileageStats/list
+鍙傛暟锛�
+  - vehicleId: 杞﹁締ID锛堝彲閫夛級
+  - vehicleNo: 杞︾墝鍙凤紙鍙�夛級
+  - statDate: 缁熻鏃ユ湡锛堝彲閫夛級
+  - beginStatDate: 寮�濮嬫棩鏈燂紙鍙�夛級
+  - endStatDate: 缁撴潫鏃ユ湡锛堝彲閫夛級
+  - pageNum: 椤电爜
+  - pageSize: 姣忛〉鏁伴噺
+```
+
+### 2. 鎵嬪姩璁$畻鍗曡溅杈嗛噷绋�
+```
+POST /system/mileageStats/calculate
+鍙傛暟锛�
+  - vehicleId: 杞﹁締ID锛堝繀濉級
+  - statDate: 缁熻鏃ユ湡锛屾牸寮� yyyy-MM-dd锛堝繀濉級
+杩斿洖锛歏ehicleMileageStats瀵硅薄
+```
+
+### 3. 鎵归噺璁$畻鎵�鏈夎溅杈嗛噷绋�
+```
+POST /system/mileageStats/batchCalculate
+鍙傛暟锛�
+  - statDate: 缁熻鏃ユ湡锛屾牸寮� yyyy-MM-dd锛堝繀濉級
+杩斿洖锛氭垚鍔熺粺璁$殑杞﹁締鏁伴噺
+```
+
+### 4. 瀵煎嚭缁熻鏁版嵁
+```
+POST /system/mileageStats/export
+鍙傛暟锛氬悓鏌ヨ鍒楄〃鎺ュ彛
+杩斿洖锛欵xcel鏂囦欢
+```
+
+## 鍥涖�佸畾鏃朵换鍔¢厤缃�
+
+### 榛樿閰嶇疆
+- **浠诲姟鍚嶇О**锛氳溅杈嗛噷绋嬬粺璁′换鍔�
+- **Bean鍚嶇О**锛歷ehicleMileageStatsTask
+- **鏂规硶璋冪敤**锛歝alculateYesterdayMileage
+- **Cron琛ㄨ揪寮�**锛歚0 30 1 * * ?`锛堟瘡澶╁噷鏅�1:30鎵ц锛�
+- **鎵ц绛栫暐**锛氱珛鍗虫墽琛�
+- **骞跺彂鎵ц**锛氱姝�
+- **鐘舵��**锛氬惎鐢�
+
+### 鎵嬪姩瑙﹀彂鏂瑰紡
+鍦ㄧ郴缁熺鐞� -> 瀹氭椂浠诲姟涓紝鍙互鎵嬪姩鎵ц锛�
+
+1. **缁熻鏄ㄦ棩鏁版嵁**锛�
+   ```
+   vehicleMileageStatsTask.calculateYesterdayMileage
+   ```
+
+2. **缁熻鎸囧畾鏃ユ湡**锛�
+   ```
+   vehicleMileageStatsTask.calculateMileageByDate('2025-11-09')
+   ```
+
+## 浜斻�佹暟鎹〃缁撴瀯
+
+### tb_vehicle_mileage_stats
+| 瀛楁鍚� | 绫诲瀷 | 璇存槑 |
+|--------|------|------|
+| stats_id | bigint(20) | 缁熻ID锛屼富閿� |
+| vehicle_id | bigint(20) | 杞﹁締ID |
+| vehicle_no | varchar(20) | 杞︾墝鍙� |
+| stat_date | date | 缁熻鏃ユ湡 |
+| total_mileage | decimal(10,2) | 鎬婚噷绋嬶紙鍏噷锛� |
+| task_mileage | decimal(10,2) | 浠诲姟鏃舵閲岀▼锛堝叕閲岋級 |
+| non_task_mileage | decimal(10,2) | 闈炰换鍔℃椂娈甸噷绋嬶紙鍏噷锛� |
+| task_ratio | decimal(5,4) | 浠诲姟閲岀▼鍗犳瘮锛�0-1锛� |
+| gps_point_count | int(11) | GPS鐐规暟閲� |
+| task_count | int(11) | 浠诲姟鏁伴噺 |
+| create_time | datetime | 鍒涘缓鏃堕棿 |
+| update_time | datetime | 鏇存柊鏃堕棿 |
+
+**绱㈠紩锛�**
+- PRIMARY KEY: stats_id
+- UNIQUE KEY: uk_vehicle_date (vehicle_id, stat_date)
+- KEY: idx_vehicle_id (vehicle_id)
+- KEY: idx_stat_date (stat_date)
+
+### tb_vehicle_mileage_detail锛堝彲閫夛級
+鐢ㄤ簬瀛樺偍閲岀▼璁$畻鏄庣粏锛屾柟渚胯皟璇曞拰杩芥函銆�
+
+## 鍏�侀儴缃叉楠�
+
+### 1. 鎵ц鏁版嵁搴撹剼鏈紙鎸夐『搴忥級
+```bash
+1. sql/vehicle_mileage_stats.sql
+2. sql/vehicle_mileage_stats_job.sql
+3. sql/vehicle_mileage_stats_menu.sql
+```
+
+### 2. 閲嶅惎搴旂敤
+浠g爜鏂囦欢宸茶嚜鍔ㄥ垱寤猴紝閲嶅惎搴旂敤鍗冲彲鐢熸晥銆�
+
+### 3. 楠岃瘉閮ㄧ讲
+1. 鐧诲綍绯荤粺锛屾鏌ヨ彍鍗曟槸鍚︽樉绀�"杞﹁締閲岀▼缁熻"
+2. 杩涘叆绯荤粺绠$悊 -> 瀹氭椂浠诲姟锛屾鏌ユ槸鍚︽湁"杞﹁締閲岀▼缁熻浠诲姟"
+3. 鎵嬪姩鎵ц瀹氭椂浠诲姟鎴栬皟鐢ˋPI鎺ュ彛娴嬭瘯鍔熻兘
+
+## 涓冦�佷娇鐢ㄧず渚�
+
+### 绀轰緥1锛氭墜鍔ㄨ绠楁煇杞﹁締鏄ㄦ棩閲岀▼
+```javascript
+import { calculateMileage } from '@/api/mileageStats'
+
+calculateMileage(1001, '2025-11-09').then(response => {
+  console.log('缁熻缁撴灉锛�', response.data)
+  // 杈撳嚭绀轰緥锛�
+  // {
+  //   vehicleNo: '绮12345',
+  //   statDate: '2025-11-09',
+  //   totalMileage: 285.67,
+  //   taskMileage: 198.43,
+  //   nonTaskMileage: 87.24,
+  //   taskRatio: 0.6948,
+  //   gpsPointCount: 1205,
+  //   taskCount: 8
+  // }
+})
+```
+
+### 绀轰緥2锛氭壒閲忚绠楁墍鏈夎溅杈嗘寚瀹氭棩鏈熼噷绋�
+```javascript
+import { batchCalculateMileage } from '@/api/mileageStats'
+
+batchCalculateMileage('2025-11-09').then(response => {
+  console.log(response.msg) // 杈撳嚭锛氭壒閲忛噷绋嬬粺璁″畬鎴愶紝鎴愬姛缁熻 45 杈嗚溅
+})
+```
+
+### 绀轰緥3锛氭煡璇㈣溅杈嗛噷绋嬬粺璁℃姤琛�
+```javascript
+import { listMileageStats } from '@/api/mileageStats'
+
+const query = {
+  vehicleNo: '绮12345',
+  beginStatDate: '2025-11-01',
+  endStatDate: '2025-11-09',
+  pageNum: 1,
+  pageSize: 10
+}
+
+listMileageStats(query).then(response => {
+  console.log('缁熻鍒楄〃锛�', response.rows)
+})
+```
+
+## 鍏�佹敞鎰忎簨椤�
+
+### 1. GPS鏁版嵁瑕佹眰
+- GPS閲囬泦闂撮殧锛氬缓璁�30-60绉�
+- 鏁版嵁瀛楁蹇呭~锛歷ehicle_id, longitude, latitude, collect_time
+- 鍧愭爣绯荤粺锛氭敮鎸乄GS84銆丟CJ02绛夊父鐢ㄥ潗鏍囩郴
+
+### 2. 浠诲姟鏁版嵁瑕佹眰
+- 浠诲姟琛細sys_task
+- 杞﹁締浠诲姟鍏宠仈琛細sys_task_vehicle
+- 蹇呭~瀛楁锛歵ask_id, vehicle_id, create_time, actual_end_time
+
+### 3. 鎬ц兘浼樺寲寤鸿
+- 瀹氭椂浠诲姟閬垮紑涓氬姟楂樺嘲鏈燂紙寤鸿鍑屾櫒鎵ц锛�
+- GPS鍘熷鏁版嵁瀹氭湡娓呯悊锛堝缓璁繚鐣�7-30澶╋級
+- 缁熻鏁版嵁瀹氭湡褰掓。锛堝缓璁繚鐣�3-6涓湀锛�
+
+### 4. 鏁版嵁鍑嗙‘鎬�
+- 閲岀▼璁$畻鍩轰簬GPS杞ㄨ抗锛岀簿搴﹀彈GPS淇″彿璐ㄩ噺褰卞搷
+- Haversine鍏紡璁$畻鐨勬槸鐩寸嚎璺濈锛屽疄闄呴亾璺窛绂诲彲鑳芥洿闀�
+- 鍙粨鍚堝ぉ鍦板浘璺緞瑙勫垝API鑾峰彇鏇村噯纭殑閬撹矾璺濈
+
+## 涔濄�佹墿灞曞姛鑳藉缓璁�
+
+### 1. 闆嗘垚澶╁湴鍥捐矾寰勮窛绂伙紙鏇村噯纭級
+褰撳墠浣跨敤Haversine鍏紡璁$畻鐩寸嚎璺濈锛屽彲鍗囩骇涓猴細
+- 灏咷PS杞ㄨ抗鐐瑰彂閫佸埌澶╁湴鍥捐矾寰勮鍒扐PI
+- 鑾峰彇瀹為檯閬撹矾璺濈
+- 鎻愰珮閲岀▼缁熻绮惧害
+
+### 2. 瀹炴椂閲岀▼缁熻
+- 鍦℅PS鏁版嵁鍏ュ簱鏃跺疄鏃惰绠�
+- 浣跨敤Redis缂撳瓨褰撴棩绱閲岀▼
+- 鍑屾櫒瀹氭椂浠诲姟浠呭仛鏁版嵁鍥哄寲
+
+### 3. 閲岀▼寮傚父鍛婅
+- 鍗曟棩閲岀▼瓒呰繃闃堝�煎憡璀�
+- 闀挎椂闂存棤GPS鏁版嵁鍛婅
+- 閲岀▼绐佸彉寮傚父鍛婅
+
+### 4. 鏁版嵁鍙鍖�
+- 姣忔棩閲岀▼瓒嬪娍鍥�
+- 浠诲姟閲岀▼鍗犳瘮楗煎浘
+- 杞﹁締閲岀▼鎺掑悕姒�
+
+## 鍗併�佹晠闅滄帓鏌�
+
+### 闂1锛氬畾鏃朵换鍔℃湭鎵ц
+- 妫�鏌ュ畾鏃朵换鍔$姸鎬佹槸鍚︿负"鍚敤"
+- 妫�鏌ron琛ㄨ揪寮忔槸鍚︽纭�
+- 鏌ョ湅瀹氭椂浠诲姟鏃ュ織
+
+### 闂2锛氱粺璁$粨鏋滀负0
+- 妫�鏌PS鏁版嵁鏄惁瀛樺湪
+- 妫�鏌PS鏁版嵁鐨刢ollect_time瀛楁鏄惁姝g‘
+- 妫�鏌ヤ换鍔℃暟鎹槸鍚﹀瓨鍦�
+
+### 闂3锛氶噷绋嬫暟鎹紓甯�
+- 妫�鏌PS鍧愭爣鏄惁鍚堟硶锛堢粡绾害鑼冨洿锛�
+- 妫�鏌ユ槸鍚﹀瓨鍦℅PS婕傜Щ鐐�
+- 鍚敤鏄庣粏琛ㄥ垎鏋愭瘡娈佃窛绂�
+
+### 鏌ョ湅鏃ュ織
+```bash
+# Service灞傛棩蹇�
+grep "VehicleMileageStatsServiceImpl" logs/ruoyi-*.log
+
+# 瀹氭椂浠诲姟鏃ュ織
+grep "VehicleMileageStatsTask" logs/ruoyi-*.log
+```
+
+## 鍗佷竴銆佹�荤粨
+
+鉁� 鏈姛鑳藉凡瀹屾暣瀹炵幇杞﹁締GPS閲岀▼缁熻鐨勬墍鏈夐渶姹傦細
+- 鉁� 姣忔棩鑷姩缁熻杞﹁締琛岄┒閲岀▼
+- 鉁� 鍖哄垎浠诲姟鏃舵鍜岄潪浠诲姟鏃舵閲岀▼
+- 鉁� 璁$畻浠诲姟閲岀▼鍗犳瘮
+- 鉁� 缁熻鏁版嵁缂撳瓨鍒版暟鎹簱琛�
+- 鉁� 鏀寔鎵嬪姩瑙﹀彂鍜屾壒閲忚绠�
+- 鉁� 鎻愪緵瀹屾暣鐨勬煡璇㈠拰瀵煎嚭鍔熻兘
+- 鉁� 闆嗘垚瀹氭椂浠诲姟鑷姩鍖栨墽琛�
+
+鏍稿績绠楁硶閲囩敤Haversine鍏紡璁$畻GPS鐐归棿璺濈锛屾寜鏃堕棿閲嶅彔姣斾緥鍒嗘憡閲岀▼鍒颁换鍔″拰闈炰换鍔℃椂娈碉紝纭繚缁熻鍑嗙‘鎬с�傛墍鏈夋暟鎹紦瀛樺湪涓撶敤缁熻琛ㄤ腑锛屾敮鎸侀珮鏁堟煡璇㈠拰鍒嗘瀽銆�

--
Gitblit v1.9.1