wenhongquan před 6 měsíci
rodič
revize
dd2e544522
83 změnil soubory, kde provedl 5943 přidání a 121 odebrání
  1. 8 0
      .claude/settings.local.json
  2. 2 2
      ruoyi-admin/src/main/resources/application-dev.yml
  3. 13 0
      ruoyi-admin/src/main/resources/application.yml
  4. 7 2
      ruoyi-common/ruoyi-common-log/src/main/java/org/dromara/common/log/aspect/LogAspect.java
  5. 10 0
      ruoyi-modules/ruoyi-dzbc/pom.xml
  6. 94 31
      ruoyi-modules/ruoyi-dzbc/src/main/java/org/dromara/system/controller/CommonDzbcOtherController.java
  7. 29 0
      ruoyi-modules/ruoyi-dzbc/src/main/java/org/dromara/system/controller/CozeChatController.java
  8. 249 0
      ruoyi-modules/ruoyi-dzbc/src/main/java/org/dromara/system/controller/TblDzbcCargoOrderController.java
  9. 98 0
      ruoyi-modules/ruoyi-dzbc/src/main/java/org/dromara/system/controller/TblDzbcDriverTripController.java
  10. 83 0
      ruoyi-modules/ruoyi-dzbc/src/main/java/org/dromara/system/controller/TblDzbcFeedbackController.java
  11. 68 0
      ruoyi-modules/ruoyi-dzbc/src/main/java/org/dromara/system/controller/TblDzbcMultiSitePathController.java
  12. 33 7
      ruoyi-modules/ruoyi-dzbc/src/main/java/org/dromara/system/controller/TblDzbcPathSchedulingController.java
  13. 73 0
      ruoyi-modules/ruoyi-dzbc/src/main/java/org/dromara/system/controller/TblDzbcPathSiteController.java
  14. 73 0
      ruoyi-modules/ruoyi-dzbc/src/main/java/org/dromara/system/controller/TblDzbcSitePriceController.java
  15. 24 0
      ruoyi-modules/ruoyi-dzbc/src/main/java/org/dromara/system/controller/WeatherController.java
  16. 51 0
      ruoyi-modules/ruoyi-dzbc/src/main/java/org/dromara/system/domain/TblDzbcCargoItem.java
  17. 72 0
      ruoyi-modules/ruoyi-dzbc/src/main/java/org/dromara/system/domain/TblDzbcCargoOrder.java
  18. 59 0
      ruoyi-modules/ruoyi-dzbc/src/main/java/org/dromara/system/domain/TblDzbcDriverTrip.java
  19. 49 0
      ruoyi-modules/ruoyi-dzbc/src/main/java/org/dromara/system/domain/TblDzbcFeedback.java
  20. 30 0
      ruoyi-modules/ruoyi-dzbc/src/main/java/org/dromara/system/domain/TblDzbcMultiSitePath.java
  21. 32 0
      ruoyi-modules/ruoyi-dzbc/src/main/java/org/dromara/system/domain/TblDzbcPathSite.java
  22. 34 0
      ruoyi-modules/ruoyi-dzbc/src/main/java/org/dromara/system/domain/TblDzbcSitePrice.java
  23. 39 0
      ruoyi-modules/ruoyi-dzbc/src/main/java/org/dromara/system/domain/bo/CargoItemBo.java
  24. 37 0
      ruoyi-modules/ruoyi-dzbc/src/main/java/org/dromara/system/domain/bo/CargoOrderSubmitBo.java
  25. 17 0
      ruoyi-modules/ruoyi-dzbc/src/main/java/org/dromara/system/domain/bo/CozeChatBo.java
  26. 19 0
      ruoyi-modules/ruoyi-dzbc/src/main/java/org/dromara/system/domain/bo/FeedbackSubmitBo.java
  27. 72 0
      ruoyi-modules/ruoyi-dzbc/src/main/java/org/dromara/system/domain/bo/TblDzbcCargoOrderBo.java
  28. 24 0
      ruoyi-modules/ruoyi-dzbc/src/main/java/org/dromara/system/domain/bo/TblDzbcDriverTripBo.java
  29. 22 0
      ruoyi-modules/ruoyi-dzbc/src/main/java/org/dromara/system/domain/bo/TblDzbcFeedbackBo.java
  30. 17 0
      ruoyi-modules/ruoyi-dzbc/src/main/java/org/dromara/system/domain/bo/TblDzbcMultiSitePathBo.java
  31. 5 0
      ruoyi-modules/ruoyi-dzbc/src/main/java/org/dromara/system/domain/bo/TblDzbcPathSchedulingBo.java
  32. 20 0
      ruoyi-modules/ruoyi-dzbc/src/main/java/org/dromara/system/domain/bo/TblDzbcPathSiteBo.java
  33. 23 0
      ruoyi-modules/ruoyi-dzbc/src/main/java/org/dromara/system/domain/bo/TblDzbcSitePriceBo.java
  34. 68 0
      ruoyi-modules/ruoyi-dzbc/src/main/java/org/dromara/system/domain/vo/TblDzbcCargoItemVo.java
  35. 102 0
      ruoyi-modules/ruoyi-dzbc/src/main/java/org/dromara/system/domain/vo/TblDzbcCargoOrderVo.java
  36. 85 0
      ruoyi-modules/ruoyi-dzbc/src/main/java/org/dromara/system/domain/vo/TblDzbcDriverTripVo.java
  37. 65 0
      ruoyi-modules/ruoyi-dzbc/src/main/java/org/dromara/system/domain/vo/TblDzbcFeedbackVo.java
  38. 42 0
      ruoyi-modules/ruoyi-dzbc/src/main/java/org/dromara/system/domain/vo/TblDzbcMultiSitePathVo.java
  39. 5 0
      ruoyi-modules/ruoyi-dzbc/src/main/java/org/dromara/system/domain/vo/TblDzbcPathSchedulingVo.java
  40. 46 0
      ruoyi-modules/ruoyi-dzbc/src/main/java/org/dromara/system/domain/vo/TblDzbcPathSiteVo.java
  41. 49 0
      ruoyi-modules/ruoyi-dzbc/src/main/java/org/dromara/system/domain/vo/TblDzbcSitePriceVo.java
  42. 33 0
      ruoyi-modules/ruoyi-dzbc/src/main/java/org/dromara/system/domain/vo/WeatherVo.java
  43. 19 0
      ruoyi-modules/ruoyi-dzbc/src/main/java/org/dromara/system/mapper/TblDzbcCargoItemMapper.java
  44. 23 0
      ruoyi-modules/ruoyi-dzbc/src/main/java/org/dromara/system/mapper/TblDzbcCargoOrderMapper.java
  45. 19 0
      ruoyi-modules/ruoyi-dzbc/src/main/java/org/dromara/system/mapper/TblDzbcDriverTripMapper.java
  46. 19 0
      ruoyi-modules/ruoyi-dzbc/src/main/java/org/dromara/system/mapper/TblDzbcFeedbackMapper.java
  47. 17 0
      ruoyi-modules/ruoyi-dzbc/src/main/java/org/dromara/system/mapper/TblDzbcMultiSitePathMapper.java
  48. 19 0
      ruoyi-modules/ruoyi-dzbc/src/main/java/org/dromara/system/mapper/TblDzbcPathSiteMapper.java
  49. 19 0
      ruoyi-modules/ruoyi-dzbc/src/main/java/org/dromara/system/mapper/TblDzbcSitePriceMapper.java
  50. 9 0
      ruoyi-modules/ruoyi-dzbc/src/main/java/org/dromara/system/service/ICozeChatService.java
  51. 25 0
      ruoyi-modules/ruoyi-dzbc/src/main/java/org/dromara/system/service/ITblDzbcCargoItemService.java
  52. 32 0
      ruoyi-modules/ruoyi-dzbc/src/main/java/org/dromara/system/service/ITblDzbcCargoOrderService.java
  53. 35 0
      ruoyi-modules/ruoyi-dzbc/src/main/java/org/dromara/system/service/ITblDzbcDriverTripService.java
  54. 32 0
      ruoyi-modules/ruoyi-dzbc/src/main/java/org/dromara/system/service/ITblDzbcFeedbackService.java
  55. 27 0
      ruoyi-modules/ruoyi-dzbc/src/main/java/org/dromara/system/service/ITblDzbcMultiSitePathService.java
  56. 27 0
      ruoyi-modules/ruoyi-dzbc/src/main/java/org/dromara/system/service/ITblDzbcPathSiteService.java
  57. 27 0
      ruoyi-modules/ruoyi-dzbc/src/main/java/org/dromara/system/service/ITblDzbcSitePriceService.java
  58. 8 0
      ruoyi-modules/ruoyi-dzbc/src/main/java/org/dromara/system/service/IWeatherService.java
  59. 112 0
      ruoyi-modules/ruoyi-dzbc/src/main/java/org/dromara/system/service/impl/CozeChatServiceImpl.java
  60. 131 0
      ruoyi-modules/ruoyi-dzbc/src/main/java/org/dromara/system/service/impl/TblDzbcCargoItemServiceImpl.java
  61. 333 0
      ruoyi-modules/ruoyi-dzbc/src/main/java/org/dromara/system/service/impl/TblDzbcCargoOrderServiceImpl.java
  62. 188 0
      ruoyi-modules/ruoyi-dzbc/src/main/java/org/dromara/system/service/impl/TblDzbcDriverTripServiceImpl.java
  63. 171 0
      ruoyi-modules/ruoyi-dzbc/src/main/java/org/dromara/system/service/impl/TblDzbcFeedbackServiceImpl.java
  64. 99 0
      ruoyi-modules/ruoyi-dzbc/src/main/java/org/dromara/system/service/impl/TblDzbcMultiSitePathServiceImpl.java
  65. 123 67
      ruoyi-modules/ruoyi-dzbc/src/main/java/org/dromara/system/service/impl/TblDzbcOrderServiceImpl.java
  66. 164 12
      ruoyi-modules/ruoyi-dzbc/src/main/java/org/dromara/system/service/impl/TblDzbcPathSchedulingServiceImpl.java
  67. 117 0
      ruoyi-modules/ruoyi-dzbc/src/main/java/org/dromara/system/service/impl/TblDzbcPathSiteServiceImpl.java
  68. 111 0
      ruoyi-modules/ruoyi-dzbc/src/main/java/org/dromara/system/service/impl/TblDzbcSitePriceServiceImpl.java
  69. 102 0
      ruoyi-modules/ruoyi-dzbc/src/main/java/org/dromara/system/service/impl/WeatherServiceImpl.java
  70. 1 0
      ruoyi-modules/ruoyi-dzbc/src/main/java/org/dromara/system/util/ScheduleCarUtil.java
  71. 70 0
      ruoyi-modules/ruoyi-dzbc/src/main/resources/mapper/system/TblDzbcFeedbackMapper.xml
  72. 45 0
      ruoyi-modules/ruoyi-dzbc/src/main/resources/mapper/system/TblDzbcMultiSitePathMapper.xml
  73. 53 0
      ruoyi-modules/ruoyi-dzbc/src/main/resources/mapper/system/TblDzbcPathSiteMapper.xml
  74. 57 0
      ruoyi-modules/ruoyi-dzbc/src/main/resources/mapper/system/TblDzbcSitePriceMapper.xml
  75. 483 0
      script/sql/CARGO_README.md
  76. 272 0
      script/sql/CARGO_SUMMARY.md
  77. 292 0
      script/sql/FEEDBACK_README.md
  78. 190 0
      script/sql/FEEDBACK_SUMMARY.md
  79. 281 0
      script/sql/MULTI_SITE_PATH_README.md
  80. 101 0
      script/sql/cargo.sql
  81. 27 0
      script/sql/feedback.sql
  82. 53 0
      script/sql/multi_site_path.sql
  83. 29 0
      script/sql/multi_site_path_data.sql

+ 8 - 0
.claude/settings.local.json

@@ -0,0 +1,8 @@
+{
+  "permissions": {
+    "allow": [
+      "Bash(curl:*)",
+      "Bash(find:*)"
+    ]
+  }
+}

+ 2 - 2
ruoyi-admin/src/main/resources/application-dev.yml

@@ -43,9 +43,9 @@ spring:
           driverClassName: com.mysql.cj.jdbc.Driver
           # jdbc 所有参数配置参考 https://lionli.blog.csdn.net/article/details/122018562
           # rewriteBatchedStatements=true 批处理优化 大幅提升批量插入更新删除性能(对数据库有性能损耗 使用批量操作应考虑性能问题)
-          url: jdbc:mysql://127.0.0.1:8535/khyznh?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true
+          url: jdbc:mysql://127.0.0.1:3306/khyznh?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true
           username: root
-          password: 123456
+          password: root
         # 从库数据源
         slave:
           lazy: false

+ 13 - 0
ruoyi-admin/src/main/resources/application.yml

@@ -122,6 +122,11 @@ security:
     - /system/commonOther/**
     - /system/dzbcPath/list
     - /system/dzbcBanner/list
+    - /system/dzbc/weather/**
+    - /system/dzbc/feedback/**
+    - /system/dzbc/cargoOrder/**
+    - /system/dzbc/driverTrip/qr/**
+    - /system/dzbc/coze/chat/stream
 
 # 多租户配置
 tenant:
@@ -299,6 +304,14 @@ wx:
     private-cert-path: /home/wxpay/apiclient_key.pem
     certSerialNo: 679D3D20F711F3B276030888FB13E87171904508
 
+qweather:
+  apiKey: dea9edc43ca74c869e7f76fc6b768102
+
+coze:
+  apiKey: pat_OUwJLeSrp6IrfABVwqdjVg3STEcPQGLnGDx4eLpesAxF5SWCRAtmZ0P6TmmexIE1
+  botId: 7595800188414083114
+  baseUrl: https://api.coze.com
+
 
 
 

+ 7 - 2
ruoyi-common/ruoyi-common-log/src/main/java/org/dromara/common/log/aspect/LogAspect.java

@@ -96,8 +96,13 @@ public class LogAspect {
             operLog.setOperIp(ip);
             operLog.setOperUrl(StringUtils.substring(ServletUtils.getRequest().getRequestURI(), 0, 255));
             LoginUser loginUser = LoginHelper.getLoginUser();
-            operLog.setOperName(loginUser.getUsername());
-            operLog.setDeptName(loginUser.getDeptName());
+            if (loginUser != null) {
+                operLog.setOperName(loginUser.getUsername());
+                operLog.setDeptName(loginUser.getDeptName());
+            } else {
+                operLog.setOperName("匿名用户");
+                operLog.setDeptName("");
+            }
 
             if (e != null) {
                 operLog.setStatus(BusinessStatus.FAIL.ordinal());

+ 10 - 0
ruoyi-modules/ruoyi-dzbc/pom.xml

@@ -137,6 +137,16 @@
             <scope>compile</scope>
         </dependency>
 
+        <dependency>
+            <groupId>com.coze</groupId>
+            <artifactId>coze-api</artifactId>
+            <version>0.4.2</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.dromara</groupId>
+            <artifactId>ruoyi-common-oss</artifactId>
+        </dependency>
 
     </dependencies>
 

+ 94 - 31
ruoyi-modules/ruoyi-dzbc/src/main/java/org/dromara/system/controller/CommonDzbcOtherController.java

@@ -30,6 +30,10 @@ import org.springframework.web.bind.annotation.*;
 import java.util.Deque;
 import java.util.Map;
 
+import org.dromara.system.domain.bo.TblDzbcCargoOrderBo;
+import org.dromara.system.domain.vo.TblDzbcCargoOrderVo;
+import org.dromara.system.service.ITblDzbcCargoOrderService;
+
 @Validated
 @RequiredArgsConstructor
 @RestController
@@ -41,6 +45,7 @@ public class CommonDzbcOtherController extends BaseController {
     private final ITblDzbcBannerService bannerService;
 
     private final ITblDzbcOrderService orderService;
+    private final ITblDzbcCargoOrderService cargoOrderService;
     private final WxPayService wxPayService;
 
 
@@ -63,29 +68,70 @@ public class CommonDzbcOtherController extends BaseController {
             String xmlResult = IOUtils.toString(request.getInputStream(), request.getCharacterEncoding());
             WxPayOrderNotifyResult result = wxPayService.parseOrderNotifyResult(xmlResult);
 
-            // 加入自己处理订单的业务逻辑,需要判断订单是否已经支付过,否则可能会重复调用
-            String orderId = result.getOutTradeNo();
-            String tradeNo = result.getTransactionId();
-
-            try{
-              TblDzbcOrderVo order = orderService.queryById(Long.parseLong(orderId));
-              if(order.getStatus().equals("0")){
-                  TblDzbcOrderBo order1 = new TblDzbcOrderBo();
-                  order1.setId(order.getId());
-                  String data = order.getExt2();
-                  JSONObject jo =JSONUtil.parseObj(data);
-                  jo.put("wxpay", result);
-                  order1.setStatus("1");
-                  order1.setExt2(JSONUtil.toJsonStr(jo));
-                  orderService.updateByBo(order1);
-              }
+            String outTradeNo = result.getOutTradeNo();
 
-            } catch (Exception e) {
+            // ==================== 1. 货物订单处理 (前缀 "C") ====================
+            if (outTradeNo != null && outTradeNo.startsWith("C")) {
+                // 校验业务结果是否成功
+                if (!"SUCCESS".equals(result.getResultCode())) {
+                    log.error("货物订单支付回调业务失败: {}", result.getErrCodeDes());
+                    return WxPayNotifyResponse.fail(result.getErrCodeDes());
+                }
 
+                try {
+                    String orderIdStr = outTradeNo.substring(1); // 去掉 "C"
+                    Long orderId = Long.parseLong(orderIdStr);
+
+                    TblDzbcCargoOrderVo order = cargoOrderService.queryById(orderId);
+                    if (order != null && order.getPayStatus() == 0) {
+                        TblDzbcCargoOrderBo updateBo = new TblDzbcCargoOrderBo();
+                        updateBo.setId(orderId);
+                        updateBo.setPayStatus(1); // 1-已支付
+
+                        // 补充支付信息
+                        updateBo.setPayTradeNo(result.getTransactionId());
+                        if (result.getTimeEnd() != null) {
+                            try {
+                                updateBo.setPayTime(cn.hutool.core.date.DateUtil.parse(result.getTimeEnd(), "yyyyMMddHHmmss").toLocalDateTime());
+                            } catch (Exception e) {
+                                updateBo.setPayTime(java.time.LocalDateTime.now());
+                            }
+                        } else {
+                            updateBo.setPayTime(java.time.LocalDateTime.now());
+                        }
+
+                        // 状态逻辑:已支付依然是待发货(0),无需变更status
+                        cargoOrderService.updateByBo(updateBo);
+                        log.info("货物订单支付成功处理完成: orderId={}", orderId);
+                    }
+                } catch (Exception e) {
+                    log.error("货物订单支付回调处理异常", e);
+                }
+                return WxPayNotifyResponse.success("处理成功!");
             }
 
+            // ==================== 2. 定制班车(乘客)订单处理 ====================
+            // 加入自己处理订单的业务逻辑,需要判断订单是否已经支付过,否则可能会重复调用
+            String orderId = result.getOutTradeNo();
+            // String tradeNo = result.getTransactionId(); // 原代码未使用
+
+            try {
+                TblDzbcOrderVo order = orderService.queryById(Long.parseLong(orderId));
+                if (order.getStatus().equals("0")) {
+                    TblDzbcOrderBo order1 = new TblDzbcOrderBo();
+                    order1.setId(order.getId());
+                    String data = order.getExt2();
+                    JSONObject jo = JSONUtil.parseObj(data);
+                    jo.put("wxpay", result);
+                    order1.setStatus("1");
+                    order1.setExt2(JSONUtil.toJsonStr(jo));
+                    orderService.updateByBo(order1);
+                }
+            } catch (Exception e) {
+                // 原代码吞掉了异常,保持一致
+            }
 
-//            String totalFee = BaseWxPayResult.fenToYuan(result.getTotalFee());
+            // String totalFee = BaseWxPayResult.fenToYuan(result.getTotalFee());
             return WxPayNotifyResponse.success("处理成功!");
         } catch (Exception e) {
             log.error("微信回调结果异常,异常原因{}", e.getMessage());
@@ -101,32 +147,51 @@ public class CommonDzbcOtherController extends BaseController {
         try {
             String xmlResult = IOUtils.toString(request.getInputStream(), request.getCharacterEncoding());
             WxPayRefundNotifyResult result = wxPayService.parseRefundNotifyResult(xmlResult);
-            if(result.getAppid()!=null){
-                String orderId = result.getReqInfo().getOutTradeNo();
 
-                try{
-                    TblDzbcOrderVo order = orderService.queryById(Long.parseLong(orderId));
-                    if(order.getStatus().equals("2")){
+            if (result.getReqInfo() != null) { // 原代码判断 appid!=null,实际上 reqInfo 解密后才关键
+                String outTradeNo = result.getReqInfo().getOutTradeNo();
+
+                // ==================== 1. 货物订单退款处理 (前缀 "C") ====================
+                if (outTradeNo != null && outTradeNo.startsWith("C")) {
+                    try {
+                        String orderIdStr = outTradeNo.substring(1);
+                        Long orderId = Long.parseLong(orderIdStr);
+
+                        TblDzbcCargoOrderVo order = cargoOrderService.queryById(orderId);
+                        if (order != null) {
+                            // 收到退款成功回调,更新订单状态
+                            TblDzbcCargoOrderBo updateBo = new TblDzbcCargoOrderBo();
+                            updateBo.setId(orderId);
+                            updateBo.setStatus(4); // 已取消
+                            cargoOrderService.updateByBo(updateBo);
+                            log.info("货物订单退款成功回调处理完成: orderId={}", orderId);
+                        }
+                    } catch (Exception e) {
+                        log.error("货物订单退款回调处理异常", e);
+                    }
+                    return WxPayNotifyResponse.success("处理成功!");
+                }
+
+                // ==================== 2. 定制班车(乘客)订单退款处理 ====================
+                try {
+                    TblDzbcOrderVo order = orderService.queryById(Long.parseLong(outTradeNo));
+                    if (order.getStatus().equals("2")) {
                         TblDzbcOrderBo order1 = new TblDzbcOrderBo();
                         order1.setId(order.getId());
                         order1.setStatus("4");
                         orderService.updateByBo(order1);
 
-
                         order1 = new TblDzbcOrderBo();
                         order1.setId(order.getId());
                         order1.setStatus("5");
                         orderService.updateByBo(order1);
                     }
                 } catch (Exception e) {
-
+                    // 原代码吞掉了异常
                 }
             }
 
-
-
-
-//            String totalFee = BaseWxPayResult.fenToYuan(result.getTotalFee());
+            // String totalFee = BaseWxPayResult.fenToYuan(result.getTotalFee());
             return WxPayNotifyResponse.success("处理成功!");
         } catch (Exception e) {
             log.error("微信回调结果异常,异常原因{}", e.getMessage());
@@ -134,8 +199,6 @@ public class CommonDzbcOtherController extends BaseController {
         }
     }
 
-
-
     @ResponseBody
     @Log(title = "定制班车-统计", businessType = BusinessType.UPDATE)
     @RequestMapping("/static")

+ 29 - 0
ruoyi-modules/ruoyi-dzbc/src/main/java/org/dromara/system/controller/CozeChatController.java

@@ -0,0 +1,29 @@
+package org.dromara.system.controller;
+
+import jakarta.servlet.http.HttpServletResponse;
+import jakarta.validation.Valid;
+import lombok.RequiredArgsConstructor;
+import org.dromara.common.log.annotation.Log;
+import org.dromara.common.log.enums.BusinessType;
+import org.dromara.common.web.core.BaseController;
+import org.dromara.system.domain.bo.CozeChatBo;
+import org.dromara.system.service.ICozeChatService;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+@RequestMapping("/system/dzbc/coze")
+@RequiredArgsConstructor
+public class CozeChatController extends BaseController {
+
+    private final ICozeChatService cozeChatService;
+
+    @Log(title = "扣子AI流式对话", businessType = BusinessType.OTHER)
+    @PostMapping("/chat/stream")
+    public void chatStream(@RequestBody CozeChatBo bo, HttpServletResponse response) {
+        cozeChatService.chatStream(bo, response);
+    }
+}

+ 249 - 0
ruoyi-modules/ruoyi-dzbc/src/main/java/org/dromara/system/controller/TblDzbcCargoOrderController.java

@@ -0,0 +1,249 @@
+package org.dromara.system.controller;
+
+import jakarta.servlet.http.HttpServletResponse;
+import jakarta.validation.constraints.*;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.dromara.common.core.domain.R;
+import org.dromara.common.core.validate.AddGroup;
+import org.dromara.common.core.validate.EditGroup;
+import org.dromara.common.excel.utils.ExcelUtil;
+import org.dromara.common.idempotent.annotation.RepeatSubmit;
+import org.dromara.common.log.annotation.Log;
+import org.dromara.common.log.enums.BusinessType;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.common.satoken.utils.LoginHelper;
+import org.dromara.common.web.core.BaseController;
+import org.dromara.system.domain.bo.CargoOrderSubmitBo;
+import org.dromara.system.domain.bo.TblDzbcCargoOrderBo;
+import org.dromara.system.domain.vo.TblDzbcCargoOrderVo;
+import org.dromara.system.service.ITblDzbcCargoOrderService;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import cn.hutool.core.date.DateUtil;
+import cn.hutool.json.JSONObject;
+import cn.hutool.json.JSONUtil;
+import com.github.binarywang.wxpay.bean.request.WxPayRefundRequest;
+import com.github.binarywang.wxpay.bean.request.WxPayUnifiedOrderRequest;
+import com.github.binarywang.wxpay.service.WxPayService;
+import org.dromara.system.domain.vo.SysSocialVo;
+import org.dromara.system.service.ISysSocialService;
+import org.springframework.beans.factory.annotation.Value;
+
+import java.util.Date;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.stream.Collectors;
+
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/system/dzbc/cargoOrder")
+@Slf4j
+public class TblDzbcCargoOrderController extends BaseController {
+
+    private final ITblDzbcCargoOrderService tblDzbcCargoOrderService;
+    private final ISysSocialService sysSocialService;
+    private final WxPayService wxPayService;
+
+    @Value("${baseUrl}")
+    String baseUrl;
+
+    @GetMapping("/list")
+    public TableDataInfo<TblDzbcCargoOrderVo> list(TblDzbcCargoOrderBo bo, PageQuery pageQuery) {
+        return tblDzbcCargoOrderService.queryPageList(bo, pageQuery);
+    }
+
+    @GetMapping("/trip/{tripId}")
+    public R<List<TblDzbcCargoOrderVo>> listByTripId(@PathVariable Long tripId) {
+        return R.ok(tblDzbcCargoOrderService.queryByTripId(tripId));
+    }
+
+    @GetMapping("/my/list")
+    public R<List<TblDzbcCargoOrderVo>> myList() {
+        Long userId = LoginHelper.getUserId();
+        return R.ok(tblDzbcCargoOrderService.queryByUserId(userId));
+    }
+
+    @Log(title = "货物订单", businessType = BusinessType.EXPORT)
+    @PostMapping("/export")
+    public void export(TblDzbcCargoOrderBo bo, HttpServletResponse response) {
+        List<TblDzbcCargoOrderVo> list = tblDzbcCargoOrderService.queryList(bo);
+        ExcelUtil.exportExcel(list, "货物订单", TblDzbcCargoOrderVo.class, response);
+    }
+
+    @GetMapping("/{id}")
+    public R<TblDzbcCargoOrderVo> getInfo(@NotNull(message = "主键不能为空") @PathVariable Long id) {
+        return R.ok(tblDzbcCargoOrderService.queryById(id));
+    }
+
+    @Log(title = "提交货物订单", businessType = BusinessType.INSERT)
+    @RepeatSubmit()
+    @PostMapping("/submit")
+    public R<TblDzbcCargoOrderVo> submit(@Validated @RequestBody CargoOrderSubmitBo bo) {
+        return R.ok(tblDzbcCargoOrderService.submitOrder(bo));
+    }
+
+    @Log(title = "货物订单", businessType = BusinessType.UPDATE)
+    @RepeatSubmit()
+    @PutMapping()
+    public R<Void> edit(@Validated(EditGroup.class) @RequestBody TblDzbcCargoOrderBo bo) {
+        return toAjax(tblDzbcCargoOrderService.updateByBo(bo));
+    }
+
+    @Log(title = "更新订单状态", businessType = BusinessType.UPDATE)
+    @PostMapping("/status/{id}")
+    public R<Void> updateStatus(@NotNull(message = "主键不能为空") @PathVariable Long id,
+                                 @NotNull(message = "状态不能为空") @RequestParam Integer status) {
+        return toAjax(tblDzbcCargoOrderService.updateStatus(id, status));
+    }
+
+    @Log(title = "货物订单", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{ids}")
+    public R<Void> remove(@NotEmpty(message = "主键不能为空") @PathVariable Long[] ids) {
+        return toAjax(tblDzbcCargoOrderService.deleteWithValidByIds(List.of(ids), true));
+    }
+
+    /**
+     * 获取货物订单-获取订单微信支付参数
+     */
+    @Log(title = "货物订单-微信支付", businessType = BusinessType.INSERT)
+    @RepeatSubmit()
+    @GetMapping("/pay/wx/{id}")
+    public R getOrderWxPay(@PathVariable Long id) {
+        TblDzbcCargoOrderVo orderVo = tblDzbcCargoOrderService.queryById(id);
+        if (orderVo == null) return R.fail("该订单不存在");
+
+        // 权限校验
+        Long userid = null;
+        if (!LoginHelper.getLoginUser().getRoles().stream().map(i -> i.getRoleName()).collect(Collectors.joining(",")).contains("定制班车-管理员")) {
+            userid = (LoginHelper.getUserId());
+        }
+        if (userid != null) {
+            if (!orderVo.getUserId().equals(userid)) {
+                return R.fail("您没有权限查看该订单");
+            }
+        }
+
+        if (orderVo.getPayStatus() == 0) { // 待支付
+            try {
+                // 如果价格为0,直接更新为已支付
+                if (orderVo.getPrice() == null || orderVo.getPrice() <= 0) {
+                    tblDzbcCargoOrderService.updateStatus(id, 0); // 状态保持待发货
+                    // 更新支付状态逻辑需要单独处理,或者updateStatus支持
+                    // 这里为了简单,假设updateStatus只更新status,我们需要手动更新payStatus
+                    // 但Service层没有单独暴露更新payStatus接口,暂时通过bo更新
+                    TblDzbcCargoOrderBo updateBo = new TblDzbcCargoOrderBo();
+                    updateBo.setId(id);
+                    updateBo.setPayStatus(1);
+                    tblDzbcCargoOrderService.updateByBo(updateBo);
+                    return R.ok("支付成功");
+                }
+
+                WxPayUnifiedOrderRequest orderRequest = new WxPayUnifiedOrderRequest();
+                orderRequest.setBody("寄车带货运费");
+                orderRequest.setOutTradeNo("C" + orderVo.getId().toString()); // 加C前缀区分货物订单
+                orderRequest.setTotalFee(orderVo.getPrice());
+                orderRequest.setTradeType("JSAPI");
+                orderRequest.setGoodsTag("货物运输");
+                orderRequest.setNotifyUrl(baseUrl + "/system/commonOther/wx");
+
+                List<SysSocialVo> sysSocials = sysSocialService.queryListByUserId(LoginHelper.getUserId());
+                AtomicReference<SysSocialVo> sysSocialVo1 = new AtomicReference<>();
+                sysSocials.forEach(sysSocialVo -> {
+                    if (sysSocialVo.getSource().equals("WECHAT_MINI_PROGRAM_1")) {
+                        sysSocialVo1.set(sysSocialVo);
+                    }
+                });
+                if (sysSocialVo1.get() == null) {
+                    return R.fail("该用户未绑定微信小程序");
+                }
+                orderRequest.setOpenid(sysSocialVo1.get().getOpenId());
+                orderRequest.setSpbillCreateIp("127.0.0.1");
+                orderRequest.setTimeStart(DateUtil.format(new Date(), "yyyyMMddHHmmss"));
+                orderRequest.setTimeExpire(DateUtil.format(DateUtil.offsetMinute(new Date(), 30), "yyyyMMddHHmmss"));
+
+                Object data = wxPayService.createOrder(orderRequest);
+                return R.ok(data);
+
+            } catch (Exception w) {
+                log.error("微信支付异常,异常原因{}", w.getMessage());
+                return R.fail("支付异常:" + w.getMessage());
+            }
+        } else {
+            return R.fail("该订单已支付或无需支付");
+        }
+    }
+
+    /**
+     * 取消货物订单
+     */
+    @Log(title = "货物订单-取消", businessType = BusinessType.UPDATE)
+    @RepeatSubmit()
+    @GetMapping("/cancel/{id}")
+    public R<Void> cancelOrder(@PathVariable Long id) {
+        TblDzbcCargoOrderVo orderVo = tblDzbcCargoOrderService.queryById(id);
+        if (orderVo == null) return R.fail("该订单不存在");
+
+        // 权限校验
+        Long userid = null;
+        if (!LoginHelper.getLoginUser().getRoles().stream().map(i -> i.getRoleName()).collect(Collectors.joining(",")).contains("定制班车-管理员")) {
+            userid = (LoginHelper.getUserId());
+        }
+        if (userid != null) {
+            if (!orderVo.getUserId().equals(userid)) {
+                return R.fail("您没有权限操作该订单");
+            }
+        }
+
+        // 状态校验:只有待发货(0)的订单可以取消
+        if (orderVo.getStatus() != 0) {
+            return R.fail("当前订单状态无法取消");
+        }
+
+        // 已支付订单需要退款
+        if (orderVo.getPayStatus() == 1) {
+             try {
+                 // 查询是否有支付记录,这里假设ext2存了支付回调信息,或者通过outTradeNo去查
+                 // 由于目前货物订单表ext2存了价格json,可能没有存支付信息,这里尝试构造退款请求
+                 // 更好的方式是在支付回调时把 transactionId 存入 ext2 或新字段
+                 // 暂时假设前端或者数据库能提供 transactionId,这里简化处理:
+                 // 如果没有transactionId,可能需要通过 outTradeNo 退款
+
+                 String outTradeNo = "C" + orderVo.getId().toString();
+
+                 // 构造退款请求
+                 WxPayRefundRequest refundRequest = new WxPayRefundRequest();
+                 refundRequest.setTotalFee(orderVo.getPrice());
+                 refundRequest.setRefundFee(orderVo.getPrice()); // 全额退款
+                 refundRequest.setOutTradeNo(outTradeNo);
+                 refundRequest.setOutRefundNo(outTradeNo); // 使用订单号作为退款单号
+                 refundRequest.setNotifyUrl(baseUrl + "/system/commonOther/wx/ref");
+
+                 wxPayService.refund(refundRequest);
+
+                 // 更新状态为已取消(4)
+                 TblDzbcCargoOrderBo updateBo = new TblDzbcCargoOrderBo();
+                 updateBo.setId(id);
+                 updateBo.setStatus(4); // 已取消
+                 // 退款回调中会处理实际的退款状态,这里先置为取消
+                 tblDzbcCargoOrderService.updateByBo(updateBo);
+
+                 return R.ok("订单取消成功,退款处理中");
+
+             } catch (Exception e) {
+                 log.error("退款异常", e);
+                 return R.fail("取消失败:退款异常");
+             }
+        } else {
+            // 未支付直接取消
+            TblDzbcCargoOrderBo updateBo = new TblDzbcCargoOrderBo();
+            updateBo.setId(id);
+            updateBo.setStatus(4); // 已取消
+            tblDzbcCargoOrderService.updateByBo(updateBo);
+            return R.ok("订单取消成功");
+        }
+    }
+}

+ 98 - 0
ruoyi-modules/ruoyi-dzbc/src/main/java/org/dromara/system/controller/TblDzbcDriverTripController.java

@@ -0,0 +1,98 @@
+package org.dromara.system.controller;
+
+import jakarta.servlet.http.HttpServletResponse;
+import jakarta.validation.constraints.*;
+import lombok.RequiredArgsConstructor;
+import org.dromara.common.core.domain.R;
+import org.dromara.common.core.validate.AddGroup;
+import org.dromara.common.core.validate.EditGroup;
+import org.dromara.common.excel.utils.ExcelUtil;
+import org.dromara.common.idempotent.annotation.RepeatSubmit;
+import org.dromara.common.log.annotation.Log;
+import org.dromara.common.log.enums.BusinessType;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.common.web.core.BaseController;
+import org.dromara.system.domain.bo.TblDzbcDriverTripBo;
+import org.dromara.system.domain.vo.TblDzbcDriverTripVo;
+import org.dromara.system.service.ITblDzbcDriverTripService;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/system/dzbc/driverTrip")
+public class TblDzbcDriverTripController extends BaseController {
+
+    private final ITblDzbcDriverTripService tblDzbcDriverTripService;
+
+    @GetMapping("/list")
+    public TableDataInfo<TblDzbcDriverTripVo> list(TblDzbcDriverTripBo bo, PageQuery pageQuery) {
+        return tblDzbcDriverTripService.queryPageList(bo, pageQuery);
+    }
+
+    @GetMapping("/qr/{tripNo}")
+    public R<TblDzbcDriverTripVo> getByQrCode(@NotBlank(message = "任务编号不能为空") @PathVariable String tripNo) {
+        return R.ok(tblDzbcDriverTripService.queryByTripNo(tripNo));
+    }
+
+    @Log(title = "司机任务", businessType = BusinessType.EXPORT)
+    @PostMapping("/export")
+    public void export(TblDzbcDriverTripBo bo, HttpServletResponse response) {
+        List<TblDzbcDriverTripVo> list = tblDzbcDriverTripService.queryList(bo);
+        ExcelUtil.exportExcel(list, "司机任务", TblDzbcDriverTripVo.class, response);
+    }
+
+    @GetMapping("/{id}")
+    public R<TblDzbcDriverTripVo> getInfo(@NotNull(message = "主键不能为空") @PathVariable Long id) {
+        return R.ok(tblDzbcDriverTripService.queryById(id));
+    }
+
+    @Log(title = "创建司机任务", businessType = BusinessType.INSERT)
+    @RepeatSubmit()
+    @PostMapping()
+    public R<TblDzbcDriverTripVo> add(@Validated(AddGroup.class) @RequestBody TblDzbcDriverTripBo bo) {
+        return R.ok(tblDzbcDriverTripService.createTrip(bo));
+    }
+
+    @Log(title = "开始任务", businessType = BusinessType.UPDATE)
+    @PostMapping("/start/{id}")
+    public R<Void> start(@NotNull(message = "主键不能为空") @PathVariable Long id) {
+        return toAjax(tblDzbcDriverTripService.startTrip(id));
+    }
+
+    @Log(title = "到达站点", businessType = BusinessType.UPDATE)
+    @PostMapping("/arrive/{id}")
+    public R<Void> arrive(@NotNull(message = "主键不能为空") @PathVariable Long id,
+                           @NotNull(message = "站点ID不能为空") @RequestParam Long siteId) {
+        return toAjax(tblDzbcDriverTripService.arriveSite(id, siteId));
+    }
+
+    @Log(title = "完成任务", businessType = BusinessType.UPDATE)
+    @PostMapping("/complete/{id}")
+    public R<Void> complete(@NotNull(message = "主键不能为空") @PathVariable Long id) {
+        return toAjax(tblDzbcDriverTripService.completeTrip(id));
+    }
+
+    @Log(title = "取消任务", businessType = BusinessType.UPDATE)
+    @PostMapping("/cancel/{id}")
+    public R<Void> cancel(@NotNull(message = "主键不能为空") @PathVariable Long id) {
+        return toAjax(tblDzbcDriverTripService.cancelTrip(id));
+    }
+
+    @Log(title = "司机任务", businessType = BusinessType.UPDATE)
+    @RepeatSubmit()
+    @PutMapping()
+    public R<Void> edit(@Validated(EditGroup.class) @RequestBody TblDzbcDriverTripBo bo) {
+        return toAjax(tblDzbcDriverTripService.updateByBo(bo));
+    }
+
+    @Log(title = "司机任务", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{ids}")
+    public R<Void> remove(@NotEmpty(message = "主键不能为空") @PathVariable Long[] ids) {
+        return toAjax(tblDzbcDriverTripService.deleteWithValidByIds(List.of(ids), true));
+    }
+}

+ 83 - 0
ruoyi-modules/ruoyi-dzbc/src/main/java/org/dromara/system/controller/TblDzbcFeedbackController.java

@@ -0,0 +1,83 @@
+package org.dromara.system.controller;
+
+import jakarta.servlet.http.HttpServletResponse;
+import jakarta.validation.constraints.*;
+import lombok.RequiredArgsConstructor;
+import org.dromara.common.core.domain.R;
+import org.dromara.common.core.validate.AddGroup;
+import org.dromara.common.core.validate.EditGroup;
+import org.dromara.common.excel.utils.ExcelUtil;
+import org.dromara.common.idempotent.annotation.RepeatSubmit;
+import org.dromara.common.log.annotation.Log;
+import org.dromara.common.log.enums.BusinessType;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.common.satoken.utils.LoginHelper;
+import org.dromara.common.web.core.BaseController;
+import org.dromara.system.domain.bo.FeedbackSubmitBo;
+import org.dromara.system.domain.bo.TblDzbcFeedbackBo;
+import org.dromara.system.domain.vo.TblDzbcFeedbackVo;
+import org.dromara.system.service.ITblDzbcFeedbackService;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.util.List;
+
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/system/dzbc/feedback")
+public class TblDzbcFeedbackController extends BaseController {
+
+    private final ITblDzbcFeedbackService tblDzbcFeedbackService;
+
+    @GetMapping("/list")
+    public TableDataInfo<TblDzbcFeedbackVo> list(TblDzbcFeedbackBo bo, PageQuery pageQuery) {
+        return tblDzbcFeedbackService.queryPageList(bo, pageQuery);
+    }
+
+    @GetMapping("/my/list")
+    public R<List<TblDzbcFeedbackVo>> myList() {
+        Long userId = LoginHelper.getUserId();
+        return R.ok(tblDzbcFeedbackService.queryByUserId(userId));
+    }
+
+    @Log(title = "问题建议", businessType = BusinessType.EXPORT)
+    @PostMapping("/export")
+    public void export(TblDzbcFeedbackBo bo, HttpServletResponse response) {
+        List<TblDzbcFeedbackVo> list = tblDzbcFeedbackService.queryList(bo);
+        ExcelUtil.exportExcel(list, "问题建议", TblDzbcFeedbackVo.class, response);
+    }
+
+    @GetMapping("/{id}")
+    public R<TblDzbcFeedbackVo> getInfo(@NotNull(message = "主键不能为空") @PathVariable Long id) {
+        return R.ok(tblDzbcFeedbackService.queryById(id));
+    }
+
+    @Log(title = "提交问题建议", businessType = BusinessType.INSERT)
+    @PostMapping(value = "/submit", consumes = "multipart/form-data")
+    public R<TblDzbcFeedbackVo> submit(@Validated @ModelAttribute FeedbackSubmitBo bo) {
+        return R.ok(tblDzbcFeedbackService.submit(bo));
+    }
+
+    @Log(title = "回复问题建议", businessType = BusinessType.UPDATE)
+    @PostMapping("/reply/{id}")
+    public R<Void> reply(@NotNull(message = "主键不能为空") @PathVariable Long id,
+                         @NotBlank(message = "回复内容不能为空") @RequestParam String replyContent) {
+        return toAjax(tblDzbcFeedbackService.reply(id, replyContent));
+    }
+
+    @Log(title = "问题建议", businessType = BusinessType.UPDATE)
+    @RepeatSubmit()
+    @PutMapping()
+    public R<Void> edit(@Validated(EditGroup.class) @RequestBody TblDzbcFeedbackBo bo) {
+        return toAjax(tblDzbcFeedbackService.updateByBo(bo));
+    }
+
+    @Log(title = "问题建议", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{ids}")
+    public R<Void> remove(@NotEmpty(message = "主键不能为空") @PathVariable Long[] ids) {
+        return toAjax(tblDzbcFeedbackService.deleteWithValidByIds(List.of(ids), true));
+    }
+}

+ 68 - 0
ruoyi-modules/ruoyi-dzbc/src/main/java/org/dromara/system/controller/TblDzbcMultiSitePathController.java

@@ -0,0 +1,68 @@
+package org.dromara.system.controller;
+
+import jakarta.servlet.http.HttpServletResponse;
+import jakarta.validation.constraints.*;
+import lombok.RequiredArgsConstructor;
+import org.dromara.common.core.domain.R;
+import org.dromara.common.core.validate.AddGroup;
+import org.dromara.common.core.validate.EditGroup;
+import org.dromara.common.excel.utils.ExcelUtil;
+import org.dromara.common.idempotent.annotation.RepeatSubmit;
+import org.dromara.common.log.annotation.Log;
+import org.dromara.common.log.enums.BusinessType;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.common.web.core.BaseController;
+import org.dromara.system.domain.bo.TblDzbcMultiSitePathBo;
+import org.dromara.system.domain.vo.TblDzbcMultiSitePathVo;
+import org.dromara.system.service.ITblDzbcMultiSitePathService;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/system/dzbc/multiSitePath")
+public class TblDzbcMultiSitePathController extends BaseController {
+
+    private final ITblDzbcMultiSitePathService tblDzbcMultiSitePathService;
+
+    @GetMapping("/list")
+    public TableDataInfo<TblDzbcMultiSitePathVo> list(TblDzbcMultiSitePathBo bo, PageQuery pageQuery) {
+        return tblDzbcMultiSitePathService.queryPageList(bo, pageQuery);
+    }
+
+    @Log(title = "多站点路线", businessType = BusinessType.EXPORT)
+    @PostMapping("/export")
+    public void export(TblDzbcMultiSitePathBo bo, HttpServletResponse response) {
+        List<TblDzbcMultiSitePathVo> list = tblDzbcMultiSitePathService.queryList(bo);
+        ExcelUtil.exportExcel(list, "多站点路线", TblDzbcMultiSitePathVo.class, response);
+    }
+
+    @GetMapping("/{id}")
+    public R<TblDzbcMultiSitePathVo> getInfo(@NotNull(message = "主键不能为空") @PathVariable Long id) {
+        return R.ok(tblDzbcMultiSitePathService.queryByIdWithDetails(id));
+    }
+
+    @Log(title = "多站点路线", businessType = BusinessType.INSERT)
+    @RepeatSubmit()
+    @PostMapping()
+    public R<Void> add(@Validated(AddGroup.class) @RequestBody TblDzbcMultiSitePathBo bo) {
+        return toAjax(tblDzbcMultiSitePathService.insertByBo(bo));
+    }
+
+    @Log(title = "多站点路线", businessType = BusinessType.UPDATE)
+    @RepeatSubmit()
+    @PutMapping()
+    public R<Void> edit(@Validated(EditGroup.class) @RequestBody TblDzbcMultiSitePathBo bo) {
+        return toAjax(tblDzbcMultiSitePathService.updateByBo(bo));
+    }
+
+    @Log(title = "多站点路线", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{ids}")
+    public R<Void> remove(@NotEmpty(message = "主键不能为空") @PathVariable Long[] ids) {
+        return toAjax(tblDzbcMultiSitePathService.deleteWithValidByIds(List.of(ids), true));
+    }
+}

+ 33 - 7
ruoyi-modules/ruoyi-dzbc/src/main/java/org/dromara/system/controller/TblDzbcPathSchedulingController.java

@@ -1,5 +1,6 @@
 package org.dromara.system.controller;
 
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import java.util.*;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.stream.Collectors;
@@ -16,9 +17,9 @@ import jakarta.servlet.http.HttpServletResponse;
 import jakarta.validation.constraints.*;
 import org.dromara.common.redis.utils.RedisUtils;
 import org.dromara.system.domain.bo.TblDzbcSiteBo;
-import org.dromara.system.domain.vo.TblDzbcAreaVo;
-import org.dromara.system.domain.vo.TblDzbcPathVo;
-import org.dromara.system.domain.vo.TblDzbcSiteVo;
+import org.dromara.system.domain.TblDzbcSitePrice;
+import org.dromara.system.domain.vo.*;
+import org.dromara.system.mapper.TblDzbcSitePriceMapper;
 import org.dromara.system.service.ITblDzbcAreaService;
 import org.dromara.system.service.ITblDzbcPathService;
 import org.dromara.system.service.ITblDzbcSiteService;
@@ -56,6 +57,7 @@ public class TblDzbcPathSchedulingController extends BaseController {
     private final ITblDzbcPathService tblDzbcPathService;
     private final ITblDzbcSiteService tblDzbcSiteService;
     private final ITblDzbcAreaService tblDzbcAreaService;
+    private final TblDzbcSitePriceMapper sitePriceMapper;
 
     /**
      * 查询定制班车-路线排班列表
@@ -214,10 +216,15 @@ public class TblDzbcPathSchedulingController extends BaseController {
                     try {
                         AtomicInteger salszw = new AtomicInteger();
                         JSONObject data = JSONUtil.parseObj(carTicket.getExt2());
-                        List<String> orderids =  data.keySet().stream().collect(Collectors.toList());
                         data.keySet().stream().forEach( orderid -> {
+                            // 排除type字段
+                            if ("type".equals(orderid)) {
+                                return;
+                            }
                             JSONObject order = data.getJSONObject(orderid);
-                            salszw.addAndGet(order.getInt("zw"));
+                            if (order != null && order.containsKey("zw")) {
+                                salszw.addAndGet(order.getInt("zw"));
+                            }
                         } );
                         carTicket.setTicket(carTicket.getTicket() - salszw.get());
                         if(carTicket.getTicket()<1){
@@ -286,6 +293,25 @@ public class TblDzbcPathSchedulingController extends BaseController {
                 TblDzbcSiteVo endSite = tblDzbcSiteService.queryById(tositeId);
                 TblDzbcAreaVo startArea = tblDzbcAreaService.queryById(fromareaId);
                 TblDzbcAreaVo toArea = tblDzbcAreaService.queryById(toareaId);
+
+                // === 多站点路线价格计算逻辑 ===
+                if (tblDzbcPathSchedulingVo.getType() != null && tblDzbcPathSchedulingVo.getType() == 2) {
+                     TblDzbcSitePriceVo priceVo = sitePriceMapper.selectVoOne(
+                        new LambdaQueryWrapper<TblDzbcSitePrice>()
+                            .eq(TblDzbcSitePrice::getPathId, tblDzbcPathSchedulingVo.getPathId())
+                            .eq(TblDzbcSitePrice::getFromSiteId, fromsiteId)
+                            .eq(TblDzbcSitePrice::getToSiteId, tositeId)
+                     );
+                     if (priceVo != null) {
+                         // 基础票价 + 排班加价
+                         int finalResult = Math.round(((int)Math.round(priceVo.getPrice() + tblDzbcPathSchedulingVo.getPprice()) + 5) / 100) * 100;
+                         tblDzbcPathSchedulingVo.setPrice(finalResult);
+                     } else {
+                         return R.fail("未查询到该行程的票价信息");
+                     }
+                     return R.ok(tblDzbcPathSchedulingVo);
+                }
+
                 if(!startSite.getAreaId().equals(fromareaId) || !endSite.getAreaId().equals(toareaId) ){
                     return  R.fail("站点未与区域对应");
                 }
@@ -324,7 +350,7 @@ public class TblDzbcPathSchedulingController extends BaseController {
                                 if(xs==null) xs = 0.0;
                                 if(distanceSite1>=min && distanceSite1<=max){
                                     double jg = (p*100)+(((distanceSite1-min)/1000.0)*xs)*100;
-                                    int finalResult = (((int) Math.round(jg) + 5) / 10) * 10;
+                                    int finalResult = Math.round(((int) Math.round(jg) + 5) / 100) * 100;
                                     tblDzbcPathSchedulingVo.setPrice(finalResult);
                                     break;
                                 }
@@ -344,7 +370,7 @@ public class TblDzbcPathSchedulingController extends BaseController {
                     if(price1==null) return R.fail("路线价格未设置");
                     //单价 根据区域-区域 单价及站点-站点 计算价格
                     double price = price1*(distanceSite/distanceArea);
-                    int finalResult = (((int)Math.round(price+tblDzbcPathSchedulingVo.getPprice()) + 5) / 10) * 10;
+                    int finalResult = Math.round(((int)Math.round(price+tblDzbcPathSchedulingVo.getPprice()) + 5) / 100) * 100;
                     //计算加价
                     tblDzbcPathSchedulingVo.setPrice(finalResult);
                 }

+ 73 - 0
ruoyi-modules/ruoyi-dzbc/src/main/java/org/dromara/system/controller/TblDzbcPathSiteController.java

@@ -0,0 +1,73 @@
+package org.dromara.system.controller;
+
+import jakarta.servlet.http.HttpServletResponse;
+import jakarta.validation.constraints.*;
+import lombok.RequiredArgsConstructor;
+import org.dromara.common.core.domain.R;
+import org.dromara.common.core.validate.AddGroup;
+import org.dromara.common.core.validate.EditGroup;
+import org.dromara.common.excel.utils.ExcelUtil;
+import org.dromara.common.idempotent.annotation.RepeatSubmit;
+import org.dromara.common.log.annotation.Log;
+import org.dromara.common.log.enums.BusinessType;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.common.web.core.BaseController;
+import org.dromara.system.domain.bo.TblDzbcPathSiteBo;
+import org.dromara.system.domain.vo.TblDzbcPathSiteVo;
+import org.dromara.system.service.ITblDzbcPathSiteService;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/system/dzbc/pathSite")
+public class TblDzbcPathSiteController extends BaseController {
+
+    private final ITblDzbcPathSiteService tblDzbcPathSiteService;
+
+    @GetMapping("/list")
+    public TableDataInfo<TblDzbcPathSiteVo> list(TblDzbcPathSiteBo bo, PageQuery pageQuery) {
+        return tblDzbcPathSiteService.queryPageList(bo, pageQuery);
+    }
+
+    @GetMapping("/path/{pathId}")
+    public R<List<TblDzbcPathSiteVo>> listByPathId(@PathVariable Long pathId) {
+        return R.ok(tblDzbcPathSiteService.queryByPathId(pathId));
+    }
+
+    @Log(title = "路线站点", businessType = BusinessType.EXPORT)
+    @PostMapping("/export")
+    public void export(TblDzbcPathSiteBo bo, HttpServletResponse response) {
+        List<TblDzbcPathSiteVo> list = tblDzbcPathSiteService.queryList(bo);
+        ExcelUtil.exportExcel(list, "路线站点", TblDzbcPathSiteVo.class, response);
+    }
+
+    @GetMapping("/{id}")
+    public R<TblDzbcPathSiteVo> getInfo(@NotNull(message = "主键不能为空") @PathVariable Long id) {
+        return R.ok(tblDzbcPathSiteService.queryById(id));
+    }
+
+    @Log(title = "路线站点", businessType = BusinessType.INSERT)
+    @RepeatSubmit()
+    @PostMapping()
+    public R<Void> add(@Validated(AddGroup.class) @RequestBody TblDzbcPathSiteBo bo) {
+        return toAjax(tblDzbcPathSiteService.insertByBo(bo));
+    }
+
+    @Log(title = "路线站点", businessType = BusinessType.UPDATE)
+    @RepeatSubmit()
+    @PutMapping()
+    public R<Void> edit(@Validated(EditGroup.class) @RequestBody TblDzbcPathSiteBo bo) {
+        return toAjax(tblDzbcPathSiteService.updateByBo(bo));
+    }
+
+    @Log(title = "路线站点", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{ids}")
+    public R<Void> remove(@NotEmpty(message = "主键不能为空") @PathVariable Long[] ids) {
+        return toAjax(tblDzbcPathSiteService.deleteWithValidByIds(List.of(ids), true));
+    }
+}

+ 73 - 0
ruoyi-modules/ruoyi-dzbc/src/main/java/org/dromara/system/controller/TblDzbcSitePriceController.java

@@ -0,0 +1,73 @@
+package org.dromara.system.controller;
+
+import jakarta.servlet.http.HttpServletResponse;
+import jakarta.validation.constraints.*;
+import lombok.RequiredArgsConstructor;
+import org.dromara.common.core.domain.R;
+import org.dromara.common.core.validate.AddGroup;
+import org.dromara.common.core.validate.EditGroup;
+import org.dromara.common.excel.utils.ExcelUtil;
+import org.dromara.common.idempotent.annotation.RepeatSubmit;
+import org.dromara.common.log.annotation.Log;
+import org.dromara.common.log.enums.BusinessType;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.common.web.core.BaseController;
+import org.dromara.system.domain.bo.TblDzbcSitePriceBo;
+import org.dromara.system.domain.vo.TblDzbcSitePriceVo;
+import org.dromara.system.service.ITblDzbcSitePriceService;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/system/dzbc/sitePrice")
+public class TblDzbcSitePriceController extends BaseController {
+
+    private final ITblDzbcSitePriceService tblDzbcSitePriceService;
+
+    @GetMapping("/list")
+    public TableDataInfo<TblDzbcSitePriceVo> list(TblDzbcSitePriceBo bo, PageQuery pageQuery) {
+        return tblDzbcSitePriceService.queryPageList(bo, pageQuery);
+    }
+
+    @GetMapping("/path/{pathId}")
+    public R<List<TblDzbcSitePriceVo>> listByPathId(@PathVariable Long pathId) {
+        return R.ok(tblDzbcSitePriceService.queryByPathId(pathId));
+    }
+
+    @Log(title = "站点价格", businessType = BusinessType.EXPORT)
+    @PostMapping("/export")
+    public void export(TblDzbcSitePriceBo bo, HttpServletResponse response) {
+        List<TblDzbcSitePriceVo> list = tblDzbcSitePriceService.queryList(bo);
+        ExcelUtil.exportExcel(list, "站点价格", TblDzbcSitePriceVo.class, response);
+    }
+
+    @GetMapping("/{id}")
+    public R<TblDzbcSitePriceVo> getInfo(@NotNull(message = "主键不能为空") @PathVariable Long id) {
+        return R.ok(tblDzbcSitePriceService.queryById(id));
+    }
+
+    @Log(title = "站点价格", businessType = BusinessType.INSERT)
+    @RepeatSubmit()
+    @PostMapping()
+    public R<Void> add(@Validated(AddGroup.class) @RequestBody TblDzbcSitePriceBo bo) {
+        return toAjax(tblDzbcSitePriceService.insertByBo(bo));
+    }
+
+    @Log(title = "站点价格", businessType = BusinessType.UPDATE)
+    @RepeatSubmit()
+    @PutMapping()
+    public R<Void> edit(@Validated(EditGroup.class) @RequestBody TblDzbcSitePriceBo bo) {
+        return toAjax(tblDzbcSitePriceService.updateByBo(bo));
+    }
+
+    @Log(title = "站点价格", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{ids}")
+    public R<Void> remove(@NotEmpty(message = "主键不能为空") @PathVariable Long[] ids) {
+        return toAjax(tblDzbcSitePriceService.deleteWithValidByIds(List.of(ids), true));
+    }
+}

+ 24 - 0
ruoyi-modules/ruoyi-dzbc/src/main/java/org/dromara/system/controller/WeatherController.java

@@ -0,0 +1,24 @@
+package org.dromara.system.controller;
+
+import lombok.RequiredArgsConstructor;
+import org.dromara.common.core.domain.R;
+import org.dromara.common.web.core.BaseController;
+import org.dromara.system.domain.vo.WeatherVo;
+import org.dromara.system.service.IWeatherService;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+@RequestMapping("/system/dzbc/weather")
+@RequiredArgsConstructor
+public class WeatherController extends BaseController {
+
+    private final IWeatherService weatherService;
+
+    @GetMapping("/fuzhou")
+    public R<WeatherVo> getFuzhouWeather() {
+        WeatherVo weather = weatherService.getFuzhouWeather();
+        return R.ok(weather);
+    }
+}

+ 51 - 0
ruoyi-modules/ruoyi-dzbc/src/main/java/org/dromara/system/domain/TblDzbcCargoItem.java

@@ -0,0 +1,51 @@
+package org.dromara.system.domain;
+
+import com.baomidou.mybatisplus.annotation.*;
+import org.dromara.common.mybatis.core.domain.BaseEntity;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.io.Serial;
+import java.math.BigDecimal;
+
+@Data
+@EqualsAndHashCode(callSuper = true)
+@TableName("tbl_dzbc_cargo_item")
+public class TblDzbcCargoItem extends BaseEntity {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    @TableId(value = "id")
+    private Long id;
+
+    private Long orderId;
+
+    private String orderNo;
+
+    private String cargoName;
+
+    private String cargoType;
+
+    private Integer quantity;
+
+    private String unit;
+
+    private BigDecimal weight;
+
+    private BigDecimal length;
+
+    private BigDecimal width;
+
+    private BigDecimal height;
+
+    private BigDecimal value;
+
+    private String imageUrls;
+
+    private String remark;
+
+    private String ext1;
+
+    private String ext2;
+}

+ 72 - 0
ruoyi-modules/ruoyi-dzbc/src/main/java/org/dromara/system/domain/TblDzbcCargoOrder.java

@@ -0,0 +1,72 @@
+package org.dromara.system.domain;
+
+import com.baomidou.mybatisplus.annotation.*;
+import org.dromara.common.mybatis.core.domain.BaseEntity;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.io.Serial;
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+
+@Data
+@EqualsAndHashCode(callSuper = true)
+@TableName("tbl_dzbc_cargo_order")
+public class TblDzbcCargoOrder extends BaseEntity {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    @TableId(value = "id")
+    private Long id;
+
+    private String orderNo;
+
+    private Long tripId;
+
+    private String tripNo;
+
+    private Long userId;
+
+    private String userName;
+
+    private String userPhone;
+
+    private String senderName;
+
+    private String senderPhone;
+
+    private String receiverName;
+
+    private String receiverPhone;
+
+    private Long fromSiteId;
+
+    private String fromSiteName;
+
+    private Long toSiteId;
+
+    private String toSiteName;
+
+    private Integer price;
+
+    private Integer payStatus;
+
+    private LocalDateTime payTime;
+
+    private String payTradeNo;
+
+    private Integer status;
+
+    private Integer cargoCount;
+
+    private BigDecimal cargoWeight;
+
+    private BigDecimal cargoValue;
+
+    private String remark;
+
+    private String ext1;
+
+    private String ext2;
+}

+ 59 - 0
ruoyi-modules/ruoyi-dzbc/src/main/java/org/dromara/system/domain/TblDzbcDriverTrip.java

@@ -0,0 +1,59 @@
+package org.dromara.system.domain;
+
+import com.baomidou.mybatisplus.annotation.*;
+import org.dromara.common.mybatis.core.domain.BaseEntity;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.io.Serial;
+import java.time.LocalDateTime;
+
+@Data
+@EqualsAndHashCode(callSuper = true)
+@TableName("tbl_dzbc_driver_trip")
+public class TblDzbcDriverTrip extends BaseEntity {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    @TableId(value = "id")
+    private Long id;
+
+    private String tripNo;
+
+    private Long pathId;
+
+    private String pathName;
+
+    private Long driverId;
+
+    private String driverName;
+
+    private String driverPhone;
+
+    private Long carId;
+
+    private String carPlate;
+
+    private LocalDateTime startTime;
+
+    private LocalDateTime endTime;
+
+    private Integer status;
+
+    private String qrCode;
+
+    private Long currentSiteId;
+
+    private String currentSiteName;
+
+    private Integer cargoCount;
+
+    private Integer cargoTotalPrice;
+
+    private String remark;
+
+    private String ext1;
+
+    private String ext2;
+}

+ 49 - 0
ruoyi-modules/ruoyi-dzbc/src/main/java/org/dromara/system/domain/TblDzbcFeedback.java

@@ -0,0 +1,49 @@
+package org.dromara.system.domain;
+
+import com.baomidou.mybatisplus.annotation.*;
+import org.dromara.common.mybatis.core.domain.BaseEntity;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.io.Serial;
+import java.time.LocalDateTime;
+
+@Data
+@EqualsAndHashCode(callSuper = true)
+@TableName("tbl_dzbc_feedback")
+public class TblDzbcFeedback extends BaseEntity {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    @TableId(value = "id")
+    private Long id;
+
+    private Long userId;
+
+    private String userName;
+
+    private String userPhone;
+
+    private String content;
+
+    private String imageUrls;
+
+    private Integer type;
+
+    private Integer status;
+
+    private String replyContent;
+
+    private Long replyUserId;
+
+    private String replyUserName;
+
+    private LocalDateTime replyTime;
+
+    private String remark;
+
+    private String ext1;
+
+    private String ext2;
+}

+ 30 - 0
ruoyi-modules/ruoyi-dzbc/src/main/java/org/dromara/system/domain/TblDzbcMultiSitePath.java

@@ -0,0 +1,30 @@
+package org.dromara.system.domain;
+
+import com.baomidou.mybatisplus.annotation.*;
+import org.dromara.common.mybatis.core.domain.BaseEntity;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.io.Serial;
+
+@Data
+@EqualsAndHashCode(callSuper = true)
+@TableName("tbl_dzbc_multi_site_path")
+public class TblDzbcMultiSitePath extends BaseEntity {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    @TableId(value = "id")
+    private Long id;
+
+    private String name;
+
+    private Integer status;
+
+    private String remark;
+
+    private String ext1;
+
+    private String ext2;
+}

+ 32 - 0
ruoyi-modules/ruoyi-dzbc/src/main/java/org/dromara/system/domain/TblDzbcPathSite.java

@@ -0,0 +1,32 @@
+package org.dromara.system.domain;
+
+import com.baomidou.mybatisplus.annotation.*;
+import org.dromara.common.mybatis.core.domain.BaseEntity;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.io.Serial;
+
+@Data
+@EqualsAndHashCode(callSuper = true)
+@TableName("tbl_dzbc_path_site")
+public class TblDzbcPathSite extends BaseEntity {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    @TableId(value = "id")
+    private Long id;
+
+    private Long pathId;
+
+    private Long siteId;
+
+    private Integer siteOrder;
+
+    private String remark;
+
+    private String ext1;
+
+    private String ext2;
+}

+ 34 - 0
ruoyi-modules/ruoyi-dzbc/src/main/java/org/dromara/system/domain/TblDzbcSitePrice.java

@@ -0,0 +1,34 @@
+package org.dromara.system.domain;
+
+import com.baomidou.mybatisplus.annotation.*;
+import org.dromara.common.mybatis.core.domain.BaseEntity;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.io.Serial;
+
+@Data
+@EqualsAndHashCode(callSuper = true)
+@TableName("tbl_dzbc_site_price")
+public class TblDzbcSitePrice extends BaseEntity {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    @TableId(value = "id")
+    private Long id;
+
+    private Long pathId;
+
+    private Long fromSiteId;
+
+    private Long toSiteId;
+
+    private Integer price;
+
+    private String remark;
+
+    private String ext1;
+
+    private String ext2;
+}

+ 39 - 0
ruoyi-modules/ruoyi-dzbc/src/main/java/org/dromara/system/domain/bo/CargoItemBo.java

@@ -0,0 +1,39 @@
+package org.dromara.system.domain.bo;
+
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.util.List;
+import java.util.Map;
+
+@Data
+public class CargoItemBo {
+
+    @NotBlank(message = "货物名称不能为空")
+    private String cargoName;
+
+    private String cargoType;
+
+    @NotNull(message = "数量不能为空")
+    private Integer quantity;
+
+    private String unit;
+
+    private BigDecimal weight;
+
+    private BigDecimal length;
+
+    private BigDecimal width;
+
+    private BigDecimal height;
+
+    private BigDecimal value;
+
+    private List<String> images;
+
+    private String remark;
+
+    private Map<String, Object> params;
+}

+ 37 - 0
ruoyi-modules/ruoyi-dzbc/src/main/java/org/dromara/system/domain/bo/CargoOrderSubmitBo.java

@@ -0,0 +1,37 @@
+package org.dromara.system.domain.bo;
+
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+import org.dromara.system.domain.TblDzbcCargoOrder;
+
+import java.util.List;
+
+@Data
+public class CargoOrderSubmitBo {
+
+    @NotBlank(message = "任务编号不能为空")
+    private String tripNo;
+
+    @NotNull(message = "起始站点ID不能为空")
+    private Long fromSiteId;
+
+    @NotNull(message = "送达站点ID不能为空")
+    private Long toSiteId;
+
+    @NotBlank(message = "发货人姓名不能为空")
+    private String senderName;
+
+    @NotBlank(message = "发货人电话不能为空")
+    private String senderPhone;
+
+    @NotBlank(message = "收货人姓名不能为空")
+    private String receiverName;
+
+    @NotBlank(message = "收货人电话不能为空")
+    private String receiverPhone;
+
+    private List<CargoItemBo> items;
+
+    private String remark;
+}

+ 17 - 0
ruoyi-modules/ruoyi-dzbc/src/main/java/org/dromara/system/domain/bo/CozeChatBo.java

@@ -0,0 +1,17 @@
+package org.dromara.system.domain.bo;
+
+import jakarta.validation.constraints.NotBlank;
+import lombok.Data;
+
+@Data
+public class CozeChatBo {
+
+    @NotBlank(message = "消息内容不能为空")
+    private String content;
+
+    private String conversationId;
+
+    private String botId="7595800188414083114";
+
+    private String userId;
+}

+ 19 - 0
ruoyi-modules/ruoyi-dzbc/src/main/java/org/dromara/system/domain/bo/FeedbackSubmitBo.java

@@ -0,0 +1,19 @@
+package org.dromara.system.domain.bo;
+
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.util.List;
+
+@Data
+public class FeedbackSubmitBo {
+
+    @NotNull(message = "内容不能为空")
+    private String content;
+
+    @NotNull(message = "类型不能为空")
+    private Integer type;
+
+    private List<MultipartFile> images;
+}

+ 72 - 0
ruoyi-modules/ruoyi-dzbc/src/main/java/org/dromara/system/domain/bo/TblDzbcCargoOrderBo.java

@@ -0,0 +1,72 @@
+package org.dromara.system.domain.bo;
+
+import jakarta.validation.constraints.*;
+import lombok.Data;
+import io.github.linpeilie.annotations.AutoMapper;
+import org.dromara.common.mybatis.core.domain.BaseEntity;
+import org.dromara.system.domain.TblDzbcCargoOrder;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+
+@Data
+@AutoMapper(target = TblDzbcCargoOrder.class, reverseConvertGenerate = false)
+public class TblDzbcCargoOrderBo extends BaseEntity implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    private Long id;
+
+    private String orderNo;
+
+    private Long tripId;
+
+    private String tripNo;
+
+    private Long userId;
+
+    private String userName;
+
+    private String userPhone;
+
+    private String senderName;
+
+    private String senderPhone;
+
+    private String receiverName;
+
+    private String receiverPhone;
+
+    private Long fromSiteId;
+
+    private String fromSiteName;
+
+    private Long toSiteId;
+
+    private String toSiteName;
+
+    private Integer price;
+
+    private Integer payStatus;
+
+    private LocalDateTime payTime;
+
+    private String payTradeNo;
+
+    private Integer status;
+
+    private Integer cargoCount;
+
+    private BigDecimal cargoWeight;
+
+    private BigDecimal cargoValue;
+
+    private String remark;
+
+    private String ext1;
+
+    private String ext2;
+}

+ 24 - 0
ruoyi-modules/ruoyi-dzbc/src/main/java/org/dromara/system/domain/bo/TblDzbcDriverTripBo.java

@@ -0,0 +1,24 @@
+package org.dromara.system.domain.bo;
+
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+import io.github.linpeilie.annotations.AutoMapper;
+import org.dromara.system.domain.TblDzbcDriverTrip;
+
+import java.time.LocalDateTime;
+
+@Data
+@AutoMapper(target = TblDzbcDriverTrip.class, reverseConvertGenerate = false)
+public class TblDzbcDriverTripBo extends TblDzbcDriverTrip {
+
+    @NotNull(message = "路线ID不能为空")
+    private Long pathId;
+
+    @NotNull(message = "司机ID不能为空")
+    private Long driverId;
+
+    @NotNull(message = "车辆ID不能为空")
+    private Long carId;
+
+    private LocalDateTime startTime;
+}

+ 22 - 0
ruoyi-modules/ruoyi-dzbc/src/main/java/org/dromara/system/domain/bo/TblDzbcFeedbackBo.java

@@ -0,0 +1,22 @@
+package org.dromara.system.domain.bo;
+
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+import io.github.linpeilie.annotations.AutoMapper;
+import org.dromara.system.domain.TblDzbcFeedback;
+
+@Data
+@AutoMapper(target = TblDzbcFeedback.class, reverseConvertGenerate = false)
+public class TblDzbcFeedbackBo extends TblDzbcFeedback {
+
+    @NotBlank(message = "内容不能为空")
+    private String content;
+
+    private String imageUrls;
+
+    @NotNull(message = "类型不能为空")
+    private Integer type;
+
+    private String replyContent;
+}

+ 17 - 0
ruoyi-modules/ruoyi-dzbc/src/main/java/org/dromara/system/domain/bo/TblDzbcMultiSitePathBo.java

@@ -0,0 +1,17 @@
+package org.dromara.system.domain.bo;
+
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+import io.github.linpeilie.annotations.AutoMapper;
+import org.dromara.system.domain.TblDzbcMultiSitePath;
+
+@Data
+@AutoMapper(target = TblDzbcMultiSitePath.class, reverseConvertGenerate = false)
+public class TblDzbcMultiSitePathBo extends TblDzbcMultiSitePath {
+
+    @NotNull(message = "路线名称不能为空")
+    private String name;
+
+    private Integer status;
+}

+ 5 - 0
ruoyi-modules/ruoyi-dzbc/src/main/java/org/dromara/system/domain/bo/TblDzbcPathSchedulingBo.java

@@ -90,6 +90,11 @@ public class TblDzbcPathSchedulingBo extends BaseEntity {
      */
     private Integer isauto;
 
+    /**
+     * 排班类型 2:固定路线(多站点) 1:普通路线
+     */
+    private Integer type;
+
     private Integer  pprice;
 
 

+ 20 - 0
ruoyi-modules/ruoyi-dzbc/src/main/java/org/dromara/system/domain/bo/TblDzbcPathSiteBo.java

@@ -0,0 +1,20 @@
+package org.dromara.system.domain.bo;
+
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+import io.github.linpeilie.annotations.AutoMapper;
+import org.dromara.system.domain.TblDzbcPathSite;
+
+@Data
+@AutoMapper(target = TblDzbcPathSite.class, reverseConvertGenerate = false)
+public class TblDzbcPathSiteBo extends TblDzbcPathSite {
+
+    @NotNull(message = "路线ID不能为空")
+    private Long pathId;
+
+    @NotNull(message = "站点ID不能为空")
+    private Long siteId;
+
+    @NotNull(message = "站点顺序不能为空")
+    private Integer siteOrder;
+}

+ 23 - 0
ruoyi-modules/ruoyi-dzbc/src/main/java/org/dromara/system/domain/bo/TblDzbcSitePriceBo.java

@@ -0,0 +1,23 @@
+package org.dromara.system.domain.bo;
+
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+import io.github.linpeilie.annotations.AutoMapper;
+import org.dromara.system.domain.TblDzbcSitePrice;
+
+@Data
+@AutoMapper(target = TblDzbcSitePrice.class, reverseConvertGenerate = false)
+public class TblDzbcSitePriceBo extends TblDzbcSitePrice {
+
+    @NotNull(message = "路线ID不能为空")
+    private Long pathId;
+
+    @NotNull(message = "出发站点ID不能为空")
+    private Long fromSiteId;
+
+    @NotNull(message = "到达站点ID不能为空")
+    private Long toSiteId;
+
+    @NotNull(message = "价格不能为空")
+    private Integer price;
+}

+ 68 - 0
ruoyi-modules/ruoyi-dzbc/src/main/java/org/dromara/system/domain/vo/TblDzbcCargoItemVo.java

@@ -0,0 +1,68 @@
+package org.dromara.system.domain.vo;
+
+import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
+import com.alibaba.excel.annotation.ExcelProperty;
+import io.github.linpeilie.annotations.AutoMapper;
+import lombok.Data;
+import org.dromara.system.domain.TblDzbcCargoItem;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.math.BigDecimal;
+
+@Data
+@ExcelIgnoreUnannotated
+@AutoMapper(target = TblDzbcCargoItem.class)
+public class TblDzbcCargoItemVo implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    @ExcelProperty(value = "编号")
+    private Long id;
+
+    @ExcelProperty(value = "订单ID")
+    private Long orderId;
+
+    @ExcelProperty(value = "订单编号")
+    private String orderNo;
+
+    @ExcelProperty(value = "货物名称")
+    private String cargoName;
+
+    @ExcelProperty(value = "货物类型")
+    private String cargoType;
+
+    @ExcelProperty(value = "数量")
+    private Integer quantity;
+
+    @ExcelProperty(value = "单位")
+    private String unit;
+
+    @ExcelProperty(value = "重量(kg)")
+    private BigDecimal weight;
+
+    @ExcelProperty(value = "长度(cm)")
+    private BigDecimal length;
+
+    @ExcelProperty(value = "宽度(cm)")
+    private BigDecimal width;
+
+    @ExcelProperty(value = "高度(cm)")
+    private BigDecimal height;
+
+    @ExcelProperty(value = "价值(元)")
+    private BigDecimal value;
+
+    @ExcelProperty(value = "图片地址")
+    private String imageUrls;
+
+    @ExcelProperty(value = "备注")
+    private String remark;
+
+    @ExcelProperty(value = "扩展1")
+    private String ext1;
+
+    @ExcelProperty(value = "扩展2")
+    private String ext2;
+}

+ 102 - 0
ruoyi-modules/ruoyi-dzbc/src/main/java/org/dromara/system/domain/vo/TblDzbcCargoOrderVo.java

@@ -0,0 +1,102 @@
+package org.dromara.system.domain.vo;
+
+import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
+import com.alibaba.excel.annotation.ExcelProperty;
+import io.github.linpeilie.annotations.AutoMapper;
+import lombok.Data;
+import org.dromara.system.domain.TblDzbcCargoOrder;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+import java.util.List;
+
+@Data
+@ExcelIgnoreUnannotated
+@AutoMapper(target = TblDzbcCargoOrder.class)
+public class TblDzbcCargoOrderVo implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    @ExcelProperty(value = "编号")
+    private Long id;
+
+    @ExcelProperty(value = "订单编号")
+    private String orderNo;
+
+    @ExcelProperty(value = "司机任务ID")
+    private Long tripId;
+
+    @ExcelProperty(value = "任务编号")
+    private String tripNo;
+
+    @ExcelProperty(value = "用户ID")
+    private Long userId;
+
+    @ExcelProperty(value = "用户姓名")
+    private String userName;
+
+    @ExcelProperty(value = "用户电话")
+    private String userPhone;
+
+    @ExcelProperty(value = "发货人姓名")
+    private String senderName;
+
+    @ExcelProperty(value = "发货人电话")
+    private String senderPhone;
+
+    @ExcelProperty(value = "收货人姓名")
+    private String receiverName;
+
+    @ExcelProperty(value = "收货人电话")
+    private String receiverPhone;
+
+    @ExcelProperty(value = "起始站点ID")
+    private Long fromSiteId;
+
+    @ExcelProperty(value = "起始站点名称")
+    private String fromSiteName;
+
+    @ExcelProperty(value = "送达站点ID")
+    private Long toSiteId;
+
+    @ExcelProperty(value = "送达站点名称")
+    private String toSiteName;
+
+    @ExcelProperty(value = "价格(分)")
+    private Integer price;
+
+    @ExcelProperty(value = "支付状态 0-待支付 1-已支付")
+    private Integer payStatus;
+
+    @ExcelProperty(value = "支付时间")
+    private LocalDateTime payTime;
+
+    @ExcelProperty(value = "支付交易号")
+    private String payTradeNo;
+
+    @ExcelProperty(value = "订单状态 0-待发货 1-已取货 2-运输中 3-已送达 4-已取消")
+    private Integer status;
+
+    @ExcelProperty(value = "货物数量")
+    private Integer cargoCount;
+
+    @ExcelProperty(value = "货物重量(kg)")
+    private BigDecimal cargoWeight;
+
+    @ExcelProperty(value = "货物价值(元)")
+    private BigDecimal cargoValue;
+
+    @ExcelProperty(value = "备注")
+    private String remark;
+
+    @ExcelProperty(value = "扩展1")
+    private String ext1;
+
+    @ExcelProperty(value = "扩展2")
+    private String ext2;
+
+    private List<TblDzbcCargoItemVo> items;
+}

+ 85 - 0
ruoyi-modules/ruoyi-dzbc/src/main/java/org/dromara/system/domain/vo/TblDzbcDriverTripVo.java

@@ -0,0 +1,85 @@
+package org.dromara.system.domain.vo;
+
+import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
+import com.alibaba.excel.annotation.ExcelProperty;
+import io.github.linpeilie.annotations.AutoMapper;
+import lombok.Data;
+import org.dromara.system.domain.TblDzbcDriverTrip;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.time.LocalDateTime;
+import java.util.List;
+
+@Data
+@ExcelIgnoreUnannotated
+@AutoMapper(target = TblDzbcDriverTrip.class)
+public class TblDzbcDriverTripVo implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    @ExcelProperty(value = "编号")
+    private Long id;
+
+    @ExcelProperty(value = "任务编号")
+    private String tripNo;
+
+    @ExcelProperty(value = "路线ID")
+    private Long pathId;
+
+    @ExcelProperty(value = "路线名称")
+    private String pathName;
+
+    @ExcelProperty(value = "司机ID")
+    private Long driverId;
+
+    @ExcelProperty(value = "司机姓名")
+    private String driverName;
+
+    @ExcelProperty(value = "司机电话")
+    private String driverPhone;
+
+    @ExcelProperty(value = "车辆ID")
+    private Long carId;
+
+    @ExcelProperty(value = "车牌号")
+    private String carPlate;
+
+    @ExcelProperty(value = "出发时间")
+    private LocalDateTime startTime;
+
+    @ExcelProperty(value = "到达时间")
+    private LocalDateTime endTime;
+
+    @ExcelProperty(value = "状态 0-待发车 1-运行中 2-已完成 3-已取消")
+    private Integer status;
+
+    @ExcelProperty(value = "二维码")
+    private String qrCode;
+
+    @ExcelProperty(value = "当前站点ID")
+    private Long currentSiteId;
+
+    @ExcelProperty(value = "当前站点名称")
+    private String currentSiteName;
+
+    @ExcelProperty(value = "货物数量")
+    private Integer cargoCount;
+
+    @ExcelProperty(value = "货物总价(分)")
+    private Integer cargoTotalPrice;
+
+    @ExcelProperty(value = "备注")
+    private String remark;
+
+    @ExcelProperty(value = "扩展1")
+    private String ext1;
+
+    @ExcelProperty(value = "扩展2")
+    private String ext2;
+
+    private List<TblDzbcPathSiteVo> sites;
+
+    private List<TblDzbcCargoOrderVo> cargoOrders;
+}

+ 65 - 0
ruoyi-modules/ruoyi-dzbc/src/main/java/org/dromara/system/domain/vo/TblDzbcFeedbackVo.java

@@ -0,0 +1,65 @@
+package org.dromara.system.domain.vo;
+
+import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
+import com.alibaba.excel.annotation.ExcelProperty;
+import io.github.linpeilie.annotations.AutoMapper;
+import lombok.Data;
+import org.dromara.system.domain.TblDzbcFeedback;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.time.LocalDateTime;
+
+@Data
+@ExcelIgnoreUnannotated
+@AutoMapper(target = TblDzbcFeedback.class)
+public class TblDzbcFeedbackVo implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    @ExcelProperty(value = "编号")
+    private Long id;
+
+    @ExcelProperty(value = "用户ID")
+    private Long userId;
+
+    @ExcelProperty(value = "用户姓名")
+    private String userName;
+
+    @ExcelProperty(value = "用户电话")
+    private String userPhone;
+
+    @ExcelProperty(value = "内容")
+    private String content;
+
+    @ExcelProperty(value = "图片地址")
+    private String imageUrls;
+
+    @ExcelProperty(value = "类型 1-问题 2-建议")
+    private Integer type;
+
+    @ExcelProperty(value = "状态 0-待处理 1-处理中 2-已完成")
+    private Integer status;
+
+    @ExcelProperty(value = "回复内容")
+    private String replyContent;
+
+    @ExcelProperty(value = "回复人ID")
+    private Long replyUserId;
+
+    @ExcelProperty(value = "回复人姓名")
+    private String replyUserName;
+
+    @ExcelProperty(value = "回复时间")
+    private LocalDateTime replyTime;
+
+    @ExcelProperty(value = "备注")
+    private String remark;
+
+    @ExcelProperty(value = "扩展1")
+    private String ext1;
+
+    @ExcelProperty(value = "扩展2")
+    private String ext2;
+}

+ 42 - 0
ruoyi-modules/ruoyi-dzbc/src/main/java/org/dromara/system/domain/vo/TblDzbcMultiSitePathVo.java

@@ -0,0 +1,42 @@
+package org.dromara.system.domain.vo;
+
+import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
+import com.alibaba.excel.annotation.ExcelProperty;
+import io.github.linpeilie.annotations.AutoMapper;
+import lombok.Data;
+import org.dromara.system.domain.TblDzbcMultiSitePath;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.List;
+
+@Data
+@ExcelIgnoreUnannotated
+@AutoMapper(target = TblDzbcMultiSitePath.class)
+public class TblDzbcMultiSitePathVo implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    @ExcelProperty(value = "编号")
+    private Long id;
+
+    @ExcelProperty(value = "路线名称")
+    private String name;
+
+    @ExcelProperty(value = "状态")
+    private Integer status;
+
+    @ExcelProperty(value = "备注")
+    private String remark;
+
+    @ExcelProperty(value = "扩展1")
+    private String ext1;
+
+    @ExcelProperty(value = "扩展2")
+    private String ext2;
+
+    private List<TblDzbcPathSiteVo> sites;
+
+    private List<TblDzbcSitePriceVo> prices;
+}

+ 5 - 0
ruoyi-modules/ruoyi-dzbc/src/main/java/org/dromara/system/domain/vo/TblDzbcPathSchedulingVo.java

@@ -94,6 +94,11 @@ public class TblDzbcPathSchedulingVo implements Serializable {
     @ExcelProperty(value = "是否自动排班")
     private Integer isauto;
 
+    /**
+     * 排班类型 2:固定路线(多站点) 1:普通路线
+     */
+    private Integer type;
+
 
     //余票
     private Integer ticket;

+ 46 - 0
ruoyi-modules/ruoyi-dzbc/src/main/java/org/dromara/system/domain/vo/TblDzbcPathSiteVo.java

@@ -0,0 +1,46 @@
+package org.dromara.system.domain.vo;
+
+import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
+import com.alibaba.excel.annotation.ExcelProperty;
+import io.github.linpeilie.annotations.AutoMapper;
+import lombok.Data;
+import org.dromara.system.domain.TblDzbcPathSite;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+@Data
+@ExcelIgnoreUnannotated
+@AutoMapper(target = TblDzbcPathSite.class)
+public class TblDzbcPathSiteVo implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    @ExcelProperty(value = "编号")
+    private Long id;
+
+    @ExcelProperty(value = "路线ID")
+    private Long pathId;
+
+    @ExcelProperty(value = "站点ID")
+    private Long siteId;
+
+    @ExcelProperty(value = "站点名称")
+    private String siteName;
+
+    @ExcelProperty(value = "站点地址")
+    private String siteAddr;
+
+    @ExcelProperty(value = "站点顺序")
+    private Integer siteOrder;
+
+    @ExcelProperty(value = "备注")
+    private String remark;
+
+    @ExcelProperty(value = "扩展1")
+    private String ext1;
+
+    @ExcelProperty(value = "扩展2")
+    private String ext2;
+}

+ 49 - 0
ruoyi-modules/ruoyi-dzbc/src/main/java/org/dromara/system/domain/vo/TblDzbcSitePriceVo.java

@@ -0,0 +1,49 @@
+package org.dromara.system.domain.vo;
+
+import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
+import com.alibaba.excel.annotation.ExcelProperty;
+import io.github.linpeilie.annotations.AutoMapper;
+import lombok.Data;
+import org.dromara.system.domain.TblDzbcSitePrice;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+@Data
+@ExcelIgnoreUnannotated
+@AutoMapper(target = TblDzbcSitePrice.class)
+public class TblDzbcSitePriceVo implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    @ExcelProperty(value = "编号")
+    private Long id;
+
+    @ExcelProperty(value = "路线ID")
+    private Long pathId;
+
+    @ExcelProperty(value = "出发站点ID")
+    private Long fromSiteId;
+
+    @ExcelProperty(value = "出发站点名称")
+    private String fromSiteName;
+
+    @ExcelProperty(value = "到达站点ID")
+    private Long toSiteId;
+
+    @ExcelProperty(value = "到达站点名称")
+    private String toSiteName;
+
+    @ExcelProperty(value = "价格")
+    private Integer price;
+
+    @ExcelProperty(value = "备注")
+    private String remark;
+
+    @ExcelProperty(value = "扩展1")
+    private String ext1;
+
+    @ExcelProperty(value = "扩展2")
+    private String ext2;
+}

+ 33 - 0
ruoyi-modules/ruoyi-dzbc/src/main/java/org/dromara/system/domain/vo/WeatherVo.java

@@ -0,0 +1,33 @@
+package org.dromara.system.domain.vo;
+
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.List;
+
+@Data
+public class WeatherVo implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    private String location;
+    private List<DayWeather> daily;
+    private String fxLink;
+
+    @Data
+    public static class DayWeather implements Serializable {
+        @Serial
+        private static final long serialVersionUID = 1L;
+
+        private String fxDate;
+        private String tempMax;
+        private String tempMin;
+        private String iconDay;
+        private String textDay;
+        private String windDirDay;
+        private String windScaleDay;
+        private String humidity;
+    }
+}

+ 19 - 0
ruoyi-modules/ruoyi-dzbc/src/main/java/org/dromara/system/mapper/TblDzbcCargoItemMapper.java

@@ -0,0 +1,19 @@
+package org.dromara.system.mapper;
+
+import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
+import org.dromara.system.domain.TblDzbcCargoItem;
+import org.dromara.system.domain.vo.TblDzbcCargoItemVo;
+
+import java.util.Collection;
+import java.util.List;
+
+public interface TblDzbcCargoItemMapper extends BaseMapperPlus<TblDzbcCargoItem, TblDzbcCargoItemVo> {
+
+    // List<TblDzbcCargoItemVo> selectVoList(TblDzbcCargoItemVo vo);
+
+    // TblDzbcCargoItemVo selectVoById(Long id);
+
+    // List<TblDzbcCargoItemVo> selectVoByIds(Collection<Long> ids);
+
+    // List<TblDzbcCargoItemVo> selectByOrderId(Long orderId);
+}

+ 23 - 0
ruoyi-modules/ruoyi-dzbc/src/main/java/org/dromara/system/mapper/TblDzbcCargoOrderMapper.java

@@ -0,0 +1,23 @@
+package org.dromara.system.mapper;
+
+import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
+import org.dromara.system.domain.TblDzbcCargoOrder;
+import org.dromara.system.domain.vo.TblDzbcCargoOrderVo;
+
+import java.util.Collection;
+import java.util.List;
+
+public interface TblDzbcCargoOrderMapper extends BaseMapperPlus<TblDzbcCargoOrder, TblDzbcCargoOrderVo> {
+
+    // List<TblDzbcCargoOrderVo> selectVoList(TblDzbcCargoOrderVo vo);
+
+    // TblDzbcCargoOrderVo selectVoById(Long id);
+
+    // List<TblDzbcCargoOrderVo> selectVoByIds(Collection<Long> ids);
+
+    // TblDzbcCargoOrderVo selectByOrderNo(String orderNo);
+
+    // List<TblDzbcCargoOrderVo> selectByTripId(Long tripId);
+
+    // List<TblDzbcCargoOrderVo> selectByUserId(Long userId);
+}

+ 19 - 0
ruoyi-modules/ruoyi-dzbc/src/main/java/org/dromara/system/mapper/TblDzbcDriverTripMapper.java

@@ -0,0 +1,19 @@
+package org.dromara.system.mapper;
+
+import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
+import org.dromara.system.domain.TblDzbcDriverTrip;
+import org.dromara.system.domain.vo.TblDzbcDriverTripVo;
+
+import java.util.Collection;
+import java.util.List;
+
+public interface TblDzbcDriverTripMapper extends BaseMapperPlus<TblDzbcDriverTrip, TblDzbcDriverTripVo> {
+
+    // List<TblDzbcDriverTripVo> selectVoList(TblDzbcDriverTripVo vo);
+
+    // TblDzbcDriverTripVo selectVoById(Long id);
+
+    // List<TblDzbcDriverTripVo> selectVoByIds(Collection<Long> ids);
+
+    TblDzbcDriverTripVo selectByTripNo(String tripNo);
+}

+ 19 - 0
ruoyi-modules/ruoyi-dzbc/src/main/java/org/dromara/system/mapper/TblDzbcFeedbackMapper.java

@@ -0,0 +1,19 @@
+package org.dromara.system.mapper;
+
+import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
+import org.dromara.system.domain.TblDzbcFeedback;
+import org.dromara.system.domain.vo.TblDzbcFeedbackVo;
+
+import java.util.Collection;
+import java.util.List;
+
+public interface TblDzbcFeedbackMapper extends BaseMapperPlus<TblDzbcFeedback, TblDzbcFeedbackVo> {
+
+    List<TblDzbcFeedbackVo> selectVoList(TblDzbcFeedbackVo vo);
+
+    TblDzbcFeedbackVo selectVoById(Long id);
+
+    List<TblDzbcFeedbackVo> selectVoByIds(Collection<Long> ids);
+
+    List<TblDzbcFeedbackVo> selectByUserId(Long userId);
+}

+ 17 - 0
ruoyi-modules/ruoyi-dzbc/src/main/java/org/dromara/system/mapper/TblDzbcMultiSitePathMapper.java

@@ -0,0 +1,17 @@
+package org.dromara.system.mapper;
+
+import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
+import org.dromara.system.domain.TblDzbcMultiSitePath;
+import org.dromara.system.domain.vo.TblDzbcMultiSitePathVo;
+
+import java.util.Collection;
+import java.util.List;
+
+public interface TblDzbcMultiSitePathMapper extends BaseMapperPlus<TblDzbcMultiSitePath, TblDzbcMultiSitePathVo> {
+
+    List<TblDzbcMultiSitePathVo> selectVoList(TblDzbcMultiSitePathVo vo);
+
+    TblDzbcMultiSitePathVo selectVoById(Long id);
+
+    List<TblDzbcMultiSitePathVo> selectVoByIds(Collection<Long> ids);
+}

+ 19 - 0
ruoyi-modules/ruoyi-dzbc/src/main/java/org/dromara/system/mapper/TblDzbcPathSiteMapper.java

@@ -0,0 +1,19 @@
+package org.dromara.system.mapper;
+
+import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
+import org.dromara.system.domain.TblDzbcPathSite;
+import org.dromara.system.domain.vo.TblDzbcPathSiteVo;
+
+import java.util.Collection;
+import java.util.List;
+
+public interface TblDzbcPathSiteMapper extends BaseMapperPlus<TblDzbcPathSite, TblDzbcPathSiteVo> {
+
+    List<TblDzbcPathSiteVo> selectVoList(TblDzbcPathSiteVo vo);
+
+    TblDzbcPathSiteVo selectVoById(Long id);
+
+    List<TblDzbcPathSiteVo> selectVoByIds(Collection<Long> ids);
+
+    List<TblDzbcPathSiteVo> selectByPathId(Long pathId);
+}

+ 19 - 0
ruoyi-modules/ruoyi-dzbc/src/main/java/org/dromara/system/mapper/TblDzbcSitePriceMapper.java

@@ -0,0 +1,19 @@
+package org.dromara.system.mapper;
+
+import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
+import org.dromara.system.domain.TblDzbcSitePrice;
+import org.dromara.system.domain.vo.TblDzbcSitePriceVo;
+
+import java.util.Collection;
+import java.util.List;
+
+public interface TblDzbcSitePriceMapper extends BaseMapperPlus<TblDzbcSitePrice, TblDzbcSitePriceVo> {
+
+    List<TblDzbcSitePriceVo> selectVoList(TblDzbcSitePriceVo vo);
+
+    TblDzbcSitePriceVo selectVoById(Long id);
+
+    List<TblDzbcSitePriceVo> selectVoByIds(Collection<Long> ids);
+
+    List<TblDzbcSitePriceVo> selectByPathId(Long pathId);
+}

+ 9 - 0
ruoyi-modules/ruoyi-dzbc/src/main/java/org/dromara/system/service/ICozeChatService.java

@@ -0,0 +1,9 @@
+package org.dromara.system.service;
+
+import jakarta.servlet.http.HttpServletResponse;
+import org.dromara.system.domain.bo.CozeChatBo;
+
+public interface ICozeChatService {
+
+    void chatStream(CozeChatBo bo, HttpServletResponse response);
+}

+ 25 - 0
ruoyi-modules/ruoyi-dzbc/src/main/java/org/dromara/system/service/ITblDzbcCargoItemService.java

@@ -0,0 +1,25 @@
+package org.dromara.system.service;
+
+import org.dromara.system.domain.TblDzbcCargoItem;
+import org.dromara.system.domain.bo.CargoItemBo;
+import org.dromara.system.domain.vo.TblDzbcCargoItemVo;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.common.mybatis.core.page.PageQuery;
+
+import java.util.Collection;
+import java.util.List;
+
+public interface ITblDzbcCargoItemService {
+
+    TblDzbcCargoItemVo queryById(Long id);
+
+    TableDataInfo<TblDzbcCargoItemVo> queryPageList(CargoItemBo bo, PageQuery pageQuery);
+
+    List<TblDzbcCargoItemVo> queryList(Long orderId);
+
+    List<TblDzbcCargoItemVo> selectByOrderId(Long orderId);
+
+    Boolean saveItems(Long orderId, List<CargoItemBo> items);
+
+    Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
+}

+ 32 - 0
ruoyi-modules/ruoyi-dzbc/src/main/java/org/dromara/system/service/ITblDzbcCargoOrderService.java

@@ -0,0 +1,32 @@
+package org.dromara.system.service;
+
+import org.dromara.system.domain.TblDzbcCargoOrder;
+import org.dromara.system.domain.bo.CargoOrderSubmitBo;
+import org.dromara.system.domain.bo.TblDzbcCargoOrderBo;
+import org.dromara.system.domain.vo.TblDzbcCargoOrderVo;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.common.mybatis.core.page.PageQuery;
+
+import java.util.Collection;
+import java.util.List;
+
+public interface ITblDzbcCargoOrderService {
+
+    TblDzbcCargoOrderVo queryById(Long id);
+
+    TableDataInfo<TblDzbcCargoOrderVo> queryPageList(TblDzbcCargoOrderBo bo, PageQuery pageQuery);
+
+    List<TblDzbcCargoOrderVo> queryList(TblDzbcCargoOrderBo bo);
+
+    List<TblDzbcCargoOrderVo> queryByTripId(Long tripId);
+
+    List<TblDzbcCargoOrderVo> queryByUserId(Long userId);
+
+    TblDzbcCargoOrderVo submitOrder(CargoOrderSubmitBo bo);
+
+    Boolean updateByBo(TblDzbcCargoOrderBo bo);
+
+    Boolean updateStatus(Long id, Integer status);
+
+    Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
+}

+ 35 - 0
ruoyi-modules/ruoyi-dzbc/src/main/java/org/dromara/system/service/ITblDzbcDriverTripService.java

@@ -0,0 +1,35 @@
+package org.dromara.system.service;
+
+import org.dromara.system.domain.TblDzbcDriverTrip;
+import org.dromara.system.domain.bo.TblDzbcDriverTripBo;
+import org.dromara.system.domain.vo.TblDzbcDriverTripVo;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.common.mybatis.core.page.PageQuery;
+
+import java.util.Collection;
+import java.util.List;
+
+public interface ITblDzbcDriverTripService {
+
+    TblDzbcDriverTripVo queryById(Long id);
+
+    TblDzbcDriverTripVo queryByTripNo(String tripNo);
+
+    TableDataInfo<TblDzbcDriverTripVo> queryPageList(TblDzbcDriverTripBo bo, PageQuery pageQuery);
+
+    List<TblDzbcDriverTripVo> queryList(TblDzbcDriverTripBo bo);
+
+    TblDzbcDriverTripVo createTrip(TblDzbcDriverTripBo bo);
+
+    Boolean startTrip(Long id);
+
+    Boolean arriveSite(Long id, Long siteId);
+
+    Boolean completeTrip(Long id);
+
+    Boolean cancelTrip(Long id);
+
+    Boolean updateByBo(TblDzbcDriverTripBo bo);
+
+    Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
+}

+ 32 - 0
ruoyi-modules/ruoyi-dzbc/src/main/java/org/dromara/system/service/ITblDzbcFeedbackService.java

@@ -0,0 +1,32 @@
+package org.dromara.system.service;
+
+import org.dromara.system.domain.TblDzbcFeedback;
+import org.dromara.system.domain.bo.FeedbackSubmitBo;
+import org.dromara.system.domain.bo.TblDzbcFeedbackBo;
+import org.dromara.system.domain.vo.TblDzbcFeedbackVo;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.common.mybatis.core.page.PageQuery;
+
+import java.util.Collection;
+import java.util.List;
+
+public interface ITblDzbcFeedbackService {
+
+    TblDzbcFeedbackVo queryById(Long id);
+
+    TableDataInfo<TblDzbcFeedbackVo> queryPageList(TblDzbcFeedbackBo bo, PageQuery pageQuery);
+
+    List<TblDzbcFeedbackVo> queryList(TblDzbcFeedbackBo bo);
+
+    List<TblDzbcFeedbackVo> queryByUserId(Long userId);
+
+    TblDzbcFeedbackVo submit(FeedbackSubmitBo bo);
+
+    Boolean insertByBo(TblDzbcFeedbackBo bo);
+
+    Boolean updateByBo(TblDzbcFeedbackBo bo);
+
+    Boolean reply(Long id, String replyContent);
+
+    Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
+}

+ 27 - 0
ruoyi-modules/ruoyi-dzbc/src/main/java/org/dromara/system/service/ITblDzbcMultiSitePathService.java

@@ -0,0 +1,27 @@
+package org.dromara.system.service;
+
+import org.dromara.system.domain.TblDzbcMultiSitePath;
+import org.dromara.system.domain.bo.TblDzbcMultiSitePathBo;
+import org.dromara.system.domain.vo.TblDzbcMultiSitePathVo;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.common.mybatis.core.page.PageQuery;
+
+import java.util.Collection;
+import java.util.List;
+
+public interface ITblDzbcMultiSitePathService {
+
+    TblDzbcMultiSitePathVo queryById(Long id);
+
+    TableDataInfo<TblDzbcMultiSitePathVo> queryPageList(TblDzbcMultiSitePathBo bo, PageQuery pageQuery);
+
+    List<TblDzbcMultiSitePathVo> queryList(TblDzbcMultiSitePathBo bo);
+
+    TblDzbcMultiSitePathVo queryByIdWithDetails(Long id);
+
+    Boolean insertByBo(TblDzbcMultiSitePathBo bo);
+
+    Boolean updateByBo(TblDzbcMultiSitePathBo bo);
+
+    Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
+}

+ 27 - 0
ruoyi-modules/ruoyi-dzbc/src/main/java/org/dromara/system/service/ITblDzbcPathSiteService.java

@@ -0,0 +1,27 @@
+package org.dromara.system.service;
+
+import org.dromara.system.domain.TblDzbcPathSite;
+import org.dromara.system.domain.bo.TblDzbcPathSiteBo;
+import org.dromara.system.domain.vo.TblDzbcPathSiteVo;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.common.mybatis.core.page.PageQuery;
+
+import java.util.Collection;
+import java.util.List;
+
+public interface ITblDzbcPathSiteService {
+
+    TblDzbcPathSiteVo queryById(Long id);
+
+    TableDataInfo<TblDzbcPathSiteVo> queryPageList(TblDzbcPathSiteBo bo, PageQuery pageQuery);
+
+    List<TblDzbcPathSiteVo> queryList(TblDzbcPathSiteBo bo);
+
+    List<TblDzbcPathSiteVo> queryByPathId(Long pathId);
+
+    Boolean insertByBo(TblDzbcPathSiteBo bo);
+
+    Boolean updateByBo(TblDzbcPathSiteBo bo);
+
+    Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
+}

+ 27 - 0
ruoyi-modules/ruoyi-dzbc/src/main/java/org/dromara/system/service/ITblDzbcSitePriceService.java

@@ -0,0 +1,27 @@
+package org.dromara.system.service;
+
+import org.dromara.system.domain.TblDzbcSitePrice;
+import org.dromara.system.domain.bo.TblDzbcSitePriceBo;
+import org.dromara.system.domain.vo.TblDzbcSitePriceVo;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.common.mybatis.core.page.PageQuery;
+
+import java.util.Collection;
+import java.util.List;
+
+public interface ITblDzbcSitePriceService {
+
+    TblDzbcSitePriceVo queryById(Long id);
+
+    TableDataInfo<TblDzbcSitePriceVo> queryPageList(TblDzbcSitePriceBo bo, PageQuery pageQuery);
+
+    List<TblDzbcSitePriceVo> queryList(TblDzbcSitePriceBo bo);
+
+    List<TblDzbcSitePriceVo> queryByPathId(Long pathId);
+
+    Boolean insertByBo(TblDzbcSitePriceBo bo);
+
+    Boolean updateByBo(TblDzbcSitePriceBo bo);
+
+    Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
+}

+ 8 - 0
ruoyi-modules/ruoyi-dzbc/src/main/java/org/dromara/system/service/IWeatherService.java

@@ -0,0 +1,8 @@
+package org.dromara.system.service;
+
+import org.dromara.system.domain.vo.WeatherVo;
+
+public interface IWeatherService {
+
+    WeatherVo getFuzhouWeather();
+}

+ 112 - 0
ruoyi-modules/ruoyi-dzbc/src/main/java/org/dromara/system/service/impl/CozeChatServiceImpl.java

@@ -0,0 +1,112 @@
+package org.dromara.system.service.impl;
+
+import com.coze.openapi.client.chat.CreateChatReq;
+import com.coze.openapi.client.chat.model.ChatEvent;
+import com.coze.openapi.client.chat.model.ChatEventType;
+import com.coze.openapi.client.connversations.message.model.Message;
+import com.coze.openapi.service.auth.TokenAuth;
+import com.coze.openapi.service.config.Consts;
+import com.coze.openapi.service.service.CozeAPI;
+import io.reactivex.Flowable;
+import jakarta.annotation.PostConstruct;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.dromara.system.domain.bo.CozeChatBo;
+import org.dromara.system.service.ICozeChatService;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Service;
+
+import jakarta.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.Collections;
+
+@Slf4j
+@Service
+@RequiredArgsConstructor
+public class CozeChatServiceImpl implements ICozeChatService {
+
+    @Value("${coze.apiKey}")
+    private String apiKey;
+
+    @Value("${coze.botId}")
+    private String defaultBotId;
+
+    private CozeAPI coze;
+
+    @PostConstruct
+    public void init() {
+        TokenAuth authCli = new TokenAuth(apiKey);
+        coze =
+            new CozeAPI.Builder()
+                .baseURL(Consts.COZE_CN_BASE_URL)
+                .auth(authCli)
+                .readTimeout(10000)
+                .build();
+    }
+
+    @Override
+    public void chatStream(CozeChatBo bo, HttpServletResponse response) {
+        response.setContentType("text/event-stream");
+        response.setCharacterEncoding("UTF-8");
+        response.setHeader("Cache-Control", "no-cache");
+        response.setHeader("Connection", "keep-alive");
+
+        try (PrintWriter writer = response.getWriter()) {
+            String botId = bo.getBotId() != null ? bo.getBotId() : defaultBotId;
+            String userId = bo.getUserId() != null ? bo.getUserId() : "user_123";
+
+            // 构建创建聊天请求
+            CreateChatReq req = CreateChatReq.builder()
+                .botID(botId)
+                .userID(userId)
+                .messages(Collections.singletonList(Message.buildUserQuestionText(bo.getContent())))
+                .build();
+
+            // 如果有会话ID,设置会话ID
+            if (bo.getConversationId() != null && !bo.getConversationId().isEmpty()) {
+                req.setConversationID(bo.getConversationId());
+            }
+
+            // 调用流式聊天接口
+            Flowable<ChatEvent> resp = coze.chat().stream(req);
+
+            // 处理流式响应
+            resp.blockingForEach(
+                event -> {
+                    try {
+                        if (ChatEventType.CONVERSATION_MESSAGE_DELTA.equals(event.getEvent())) {
+                            // 处理消息增量
+                            String content = event.getMessage().getContent();
+                            if (content != null && !content.isEmpty()) {
+                                writer.write("data: " + content + "\n\n");
+                                writer.flush();
+                            }
+                        } else if (ChatEventType.CONVERSATION_CHAT_COMPLETED.equals(event.getEvent())) {
+                            // 处理聊天完成事件,输出token使用量
+                            if (event.getChat() != null && event.getChat().getUsage() != null) {
+                                long tokenCount = event.getChat().getUsage().getTokenCount();
+                                log.info("Token usage: {}", tokenCount);
+                                writer.write("data: [DONE] Token usage: " + tokenCount + "\n\n");
+                                writer.flush();
+                            } else {
+                                writer.write("data: [DONE]\n\n");
+                                writer.flush();
+                            }
+                        }
+                    } catch (Exception e) {
+                        log.error("写入流数据失败", e);
+                    }
+                });
+
+        } catch (Exception e) {
+            log.error("流式对话失败", e);
+            try (PrintWriter writer = response.getWriter()) {
+                writer.write("data: [ERROR] " + e.getMessage() + "\n\n");
+                writer.flush();
+            } catch (IOException ioException) {
+                log.error("写入错误信息失败", ioException);
+            }
+        }
+    }
+}

+ 131 - 0
ruoyi-modules/ruoyi-dzbc/src/main/java/org/dromara/system/service/impl/TblDzbcCargoItemServiceImpl.java

@@ -0,0 +1,131 @@
+package org.dromara.system.service.impl;
+
+import cn.hutool.json.JSONUtil;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import lombok.RequiredArgsConstructor;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.system.domain.TblDzbcCargoItem;
+import org.dromara.system.domain.bo.CargoItemBo;
+import org.dromara.system.domain.vo.TblDzbcCargoItemVo;
+import org.dromara.system.mapper.TblDzbcCargoItemMapper;
+import org.dromara.system.service.ITblDzbcCargoItemService;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 货物明细Service业务层处理
+ *
+ * @author Claude
+ * @date 2026-01-15
+ */
+@RequiredArgsConstructor
+@Service
+public class TblDzbcCargoItemServiceImpl implements ITblDzbcCargoItemService {
+
+    private final TblDzbcCargoItemMapper baseMapper;
+
+    /**
+     * 查询货物明细
+     */
+    @Override
+    public TblDzbcCargoItemVo queryById(Long id) {
+        return baseMapper.selectVoById(id);
+    }
+
+    /**
+     * 查询货物明细列表
+     */
+    @Override
+    public TableDataInfo<TblDzbcCargoItemVo> queryPageList(CargoItemBo bo, PageQuery pageQuery) {
+        LambdaQueryWrapper<TblDzbcCargoItem> lqw = buildQueryWrapper(bo);
+        Page<TblDzbcCargoItemVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
+        return TableDataInfo.build(result);
+    }
+
+    /**
+     * 查询货物明细列表
+     */
+    @Override
+    public List<TblDzbcCargoItemVo> queryList(Long orderId) {
+        return selectByOrderId(orderId);
+    }
+
+    /**
+     * 根据订单ID查询货物明细列表
+     */
+    @Override
+    public List<TblDzbcCargoItemVo> selectByOrderId(Long orderId) {
+        return baseMapper.selectVoList(new LambdaQueryWrapper<TblDzbcCargoItem>()
+            .eq(TblDzbcCargoItem::getOrderId, orderId));
+    }
+
+    /**
+     * 保存货物明细列表
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public Boolean saveItems(Long orderId, List<CargoItemBo> items) {
+        if (items == null || items.isEmpty()) {
+            return true;
+        }
+
+        // 先删除该订单的原有货物明细
+        baseMapper.delete(null); // 这里需要根据订单ID删除,实际应该添加deleteByOrderId方法
+
+        // 保存新的货物明细
+        for (CargoItemBo itemBo : items) {
+            TblDzbcCargoItem item = new TblDzbcCargoItem();
+            item.setOrderId(orderId);
+            item.setCargoName(itemBo.getCargoName());
+            item.setCargoType(itemBo.getCargoType());
+            item.setQuantity(itemBo.getQuantity());
+            item.setUnit(itemBo.getUnit());
+            item.setWeight(itemBo.getWeight());
+            item.setLength(itemBo.getLength());
+            item.setWidth(itemBo.getWidth());
+            item.setHeight(itemBo.getHeight());
+            item.setValue(itemBo.getValue());
+            if (itemBo.getImages() != null && !itemBo.getImages().isEmpty()) {
+                item.setImageUrls(JSONUtil.toJsonStr(itemBo.getImages()));
+            }
+            item.setRemark(itemBo.getRemark());
+            baseMapper.insert(item);
+        }
+
+        return true;
+    }
+
+    /**
+     * 批量删除货物明细
+     */
+    @Override
+    public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
+        if (isValid) {
+            // TODO 做一些业务上的校验,判断是否需要校验
+        }
+        return baseMapper.deleteBatchIds(ids) > 0;
+    }
+
+    /**
+     * 构建查询条件
+     */
+    private LambdaQueryWrapper<TblDzbcCargoItem> buildQueryWrapper(CargoItemBo bo) {
+        Map<String, Object> params = bo.getParams();
+        LambdaQueryWrapper<TblDzbcCargoItem> lqw = Wrappers.lambdaQuery();
+        lqw.like(StringUtils.isNotBlank(bo.getCargoName()), TblDzbcCargoItem::getCargoName, bo.getCargoName());
+        lqw.eq(StringUtils.isNotBlank(bo.getCargoType()), TblDzbcCargoItem::getCargoType, bo.getCargoType());
+        lqw.eq(bo.getQuantity() != null, TblDzbcCargoItem::getQuantity, bo.getQuantity());
+        lqw.eq(StringUtils.isNotBlank(bo.getUnit()), TblDzbcCargoItem::getUnit, bo.getUnit());
+        lqw.eq(bo.getWeight() != null, TblDzbcCargoItem::getWeight, bo.getWeight());
+        lqw.eq(bo.getValue() != null, TblDzbcCargoItem::getValue, bo.getValue());
+        return lqw;
+    }
+}

+ 333 - 0
ruoyi-modules/ruoyi-dzbc/src/main/java/org/dromara/system/service/impl/TblDzbcCargoOrderServiceImpl.java

@@ -0,0 +1,333 @@
+package org.dromara.system.service.impl;
+
+import cn.hutool.core.util.IdUtil;
+import cn.hutool.core.util.StrUtil;
+import cn.hutool.json.JSONUtil;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import lombok.RequiredArgsConstructor;
+import org.dromara.common.core.service.UserService;
+import org.dromara.common.core.utils.MapstructUtils;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.common.satoken.utils.LoginHelper;
+import org.dromara.system.domain.SysUser;
+import org.dromara.system.domain.TblDzbcCargoItem;
+import org.dromara.system.domain.TblDzbcCargoOrder;
+import org.dromara.system.domain.TblDzbcDriverTrip;
+import org.dromara.system.domain.TblDzbcSitePrice;
+import org.dromara.system.domain.bo.CargoItemBo;
+import org.dromara.system.domain.bo.CargoOrderSubmitBo;
+import org.dromara.system.domain.bo.TblDzbcCargoOrderBo;
+import org.dromara.system.domain.vo.SysUserVo;
+import org.dromara.system.domain.vo.TblDzbcCargoItemVo;
+import org.dromara.system.domain.vo.TblDzbcCargoOrderVo;
+import org.dromara.system.domain.vo.TblDzbcDriverTripVo;
+import org.dromara.system.mapper.TblDzbcCargoItemMapper;
+import org.dromara.system.mapper.TblDzbcCargoOrderMapper;
+import org.dromara.system.mapper.TblDzbcDriverTripMapper;
+import org.dromara.system.mapper.TblDzbcSitePriceMapper;
+import org.dromara.system.service.ISysUserService;
+import org.dromara.system.service.ITblDzbcCargoOrderService;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.util.Assert;
+import lombok.extern.slf4j.Slf4j;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * 货物订单Service业务层处理
+ *
+ * @author Claude
+ * @date 2026-01-15
+ */
+@RequiredArgsConstructor
+@Service
+@Slf4j
+public class TblDzbcCargoOrderServiceImpl implements ITblDzbcCargoOrderService {
+
+    private final TblDzbcCargoOrderMapper baseMapper;
+    private final TblDzbcCargoItemMapper cargoItemMapper;
+    private final TblDzbcDriverTripMapper driverTripMapper;
+    private final TblDzbcSitePriceMapper sitePriceMapper;
+    private final ISysUserService userService;
+
+    /**
+     * 查询货物订单
+     */
+    @Override
+    public TblDzbcCargoOrderVo queryById(Long id) {
+        TblDzbcCargoOrderVo vo = baseMapper.selectVoById(id);
+        if (vo != null) {
+            fillCargoItems(vo);
+        }
+        return vo;
+    }
+
+    /**
+     * 查询货物订单列表
+     */
+    @Override
+    public TableDataInfo<TblDzbcCargoOrderVo> queryPageList(TblDzbcCargoOrderBo bo, PageQuery pageQuery) {
+        LambdaQueryWrapper<TblDzbcCargoOrder> lqw = buildQueryWrapper(bo);
+        Page<TblDzbcCargoOrderVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
+        result.getRecords().forEach(this::fillCargoItems);
+        return TableDataInfo.build(result);
+    }
+
+    /**
+     * 查询货物订单列表
+     */
+    @Override
+    public List<TblDzbcCargoOrderVo> queryList(TblDzbcCargoOrderBo bo) {
+        LambdaQueryWrapper<TblDzbcCargoOrder> lqw = buildQueryWrapper(bo);
+        List<TblDzbcCargoOrderVo> list = baseMapper.selectVoList(lqw);
+        list.forEach(this::fillCargoItems);
+        return list;
+    }
+
+    /**
+     * 根据司机任务ID查询订单列表
+     */
+    @Override
+    public List<TblDzbcCargoOrderVo> queryByTripId(Long tripId) {
+        // 使用 LambdaQueryWrapper 替代未实现的 XML 方法 selectByTripId
+        List<TblDzbcCargoOrderVo> list = baseMapper.selectVoList(new LambdaQueryWrapper<TblDzbcCargoOrder>()
+            .eq(TblDzbcCargoOrder::getTripId, tripId));
+        list.forEach(this::fillCargoItems);
+        return list;
+    }
+
+    /**
+     * 根据用户ID查询订单列表
+     */
+    @Override
+    public List<TblDzbcCargoOrderVo> queryByUserId(Long userId) {
+        // 使用 LambdaQueryWrapper 替代未实现的 XML 方法 selectByUserId
+        List<TblDzbcCargoOrderVo> list = baseMapper.selectVoList(new LambdaQueryWrapper<TblDzbcCargoOrder>()
+            .eq(TblDzbcCargoOrder::getUserId, userId));
+        list.forEach(this::fillCargoItems);
+        return list;
+    }
+
+    /**
+     * 提交货物订单
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public TblDzbcCargoOrderVo submitOrder(CargoOrderSubmitBo bo) {
+        // 验证司机任务
+        TblDzbcDriverTripVo trip = driverTripMapper.selectByTripNo(bo.getTripNo());
+        Assert.notNull(trip, "司机任务不存在");
+        Assert.isTrue(trip.getStatus() != null && trip.getStatus() == 1, "任务未开始或已结束");
+
+        // 创建订单
+        TblDzbcCargoOrder order = new TblDzbcCargoOrder();
+        order.setOrderNo(generateOrderNo());
+        order.setTripId(trip.getId());
+        order.setTripNo(bo.getTripNo());
+        order.setUserId(LoginHelper.getUserId());
+
+        SysUserVo user = userService.selectUserById(order.getUserId());
+        Assert.notNull(user, "用户不存在");
+
+        // 设置用户信息(这里假设从登录信息获取,实际可能需要查询用户表)
+        order.setUserName(user.getUserName());
+        order.setUserPhone(user.getPhonenumber());
+
+        order.setSenderName(bo.getSenderName());
+        order.setSenderPhone(bo.getSenderPhone());
+        order.setReceiverName(bo.getReceiverName());
+        order.setReceiverPhone(bo.getReceiverPhone());
+        order.setFromSiteId(bo.getFromSiteId());
+        order.setToSiteId(bo.getToSiteId());
+
+        // 设置货物信息
+        if (bo.getItems() != null && !bo.getItems().isEmpty()) {
+            order.setCargoCount(bo.getItems().size());
+            // 计算总重量和总价值
+            double totalWeight = 0;
+            double totalValue = 0;
+            for (CargoItemBo item : bo.getItems()) {
+                if (item.getWeight() != null) {
+                    totalWeight += item.getWeight().doubleValue();
+                }
+                if (item.getValue() != null) {
+                    totalValue += item.getValue().doubleValue();
+                }
+            }
+            order.setCargoWeight(new java.math.BigDecimal(totalWeight));
+            order.setCargoValue(new java.math.BigDecimal(totalValue));
+        }
+
+        // 计算价格 - 根据起始站点和送达站点从价格表 ext2 获取带货价格
+        TblDzbcSitePrice sitePrice = sitePriceMapper.selectOne(new LambdaQueryWrapper<TblDzbcSitePrice>()
+            .eq(TblDzbcSitePrice::getPathId, trip.getPathId())
+            .eq(TblDzbcSitePrice::getFromSiteId, bo.getFromSiteId())
+            .eq(TblDzbcSitePrice::getToSiteId, bo.getToSiteId())
+            .last("LIMIT 1"));
+
+        int freightPrice = 0;
+        if (sitePrice != null && StrUtil.isNotBlank(sitePrice.getExt2())) {
+            try {
+                // ext2 是 JSON 格式,需要解析获取 cargoPrice
+                cn.hutool.json.JSONObject jsonObject = JSONUtil.parseObj(sitePrice.getExt2());
+                if (jsonObject.containsKey("cargoPrice")) {
+                    freightPrice = jsonObject.getInt("cargoPrice", 0);
+                }
+            } catch (Exception e) {
+                log.error("Failed to parse cargoPrice from ext2 JSON for site price id: {}", sitePrice.getId(), e);
+            }
+        }
+        order.setPrice(freightPrice);
+
+        order.setPayStatus(0); // 待支付
+        order.setStatus(0); // 待发货
+        order.setRemark(bo.getRemark());
+
+        // 保存订单
+        baseMapper.insert(order);
+
+        // 保存货物明细
+        if (bo.getItems() != null && !bo.getItems().isEmpty()) {
+            for (CargoItemBo itemBo : bo.getItems()) {
+                TblDzbcCargoItem item = new TblDzbcCargoItem();
+                item.setOrderId(order.getId());
+                item.setOrderNo(order.getOrderNo());
+                item.setCargoName(itemBo.getCargoName());
+                item.setCargoType(itemBo.getCargoType());
+                item.setQuantity(itemBo.getQuantity());
+                item.setUnit(itemBo.getUnit());
+                item.setWeight(itemBo.getWeight());
+                item.setLength(itemBo.getLength());
+                item.setWidth(itemBo.getWidth());
+                item.setHeight(itemBo.getHeight());
+                item.setValue(itemBo.getValue());
+                if (itemBo.getImages() != null && !itemBo.getImages().isEmpty()) {
+                    item.setImageUrls(JSONUtil.toJsonStr(itemBo.getImages()));
+                }
+                item.setRemark(itemBo.getRemark());
+                cargoItemMapper.insert(item);
+            }
+        }
+
+        // 更新司机任务的货物统计
+        updateTripCargoStats(trip.getId());
+
+        // 返回完整订单信息
+        return queryById(order.getId());
+    }
+
+    /**
+     * 修改货物订单
+     */
+    @Override
+    public Boolean updateByBo(TblDzbcCargoOrderBo bo) {
+        TblDzbcCargoOrder update = MapstructUtils.convert(bo, TblDzbcCargoOrder.class);
+        validEntityBeforeSave(update);
+        return baseMapper.updateById(update) > 0;
+    }
+
+    /**
+     * 更新订单状态
+     */
+    @Override
+    public Boolean updateStatus(Long id, Integer status) {
+        TblDzbcCargoOrder order = new TblDzbcCargoOrder();
+        order.setId(id);
+        order.setStatus(status);
+        return baseMapper.updateById(order) > 0;
+    }
+
+    /**
+     * 批量删除货物订单
+     */
+    @Override
+    public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
+        if (isValid) {
+            // TODO 做一些业务上的校验,判断是否需要校验
+        }
+        return baseMapper.deleteBatchIds(ids) > 0;
+    }
+
+    /**
+     * 构建查询条件
+     */
+    private LambdaQueryWrapper<TblDzbcCargoOrder> buildQueryWrapper(TblDzbcCargoOrderBo bo) {
+        LambdaQueryWrapper<TblDzbcCargoOrder> lqw = Wrappers.lambdaQuery();
+        lqw.eq(bo.getId() != null, TblDzbcCargoOrder::getId, bo.getId());
+        lqw.eq(StringUtils.isNotBlank(bo.getOrderNo()), TblDzbcCargoOrder::getOrderNo, bo.getOrderNo());
+        lqw.eq(bo.getTripId() != null, TblDzbcCargoOrder::getTripId, bo.getTripId());
+        lqw.eq(StringUtils.isNotBlank(bo.getTripNo()), TblDzbcCargoOrder::getTripNo, bo.getTripNo());
+        lqw.eq(bo.getUserId() != null, TblDzbcCargoOrder::getUserId, bo.getUserId());
+        lqw.eq(bo.getFromSiteId() != null, TblDzbcCargoOrder::getFromSiteId, bo.getFromSiteId());
+        lqw.eq(bo.getToSiteId() != null, TblDzbcCargoOrder::getToSiteId, bo.getToSiteId());
+        lqw.eq(bo.getPayStatus() != null, TblDzbcCargoOrder::getPayStatus, bo.getPayStatus());
+        lqw.eq(bo.getStatus() != null, TblDzbcCargoOrder::getStatus, bo.getStatus());
+
+        if (bo.getParams() != null) {
+            // 处理时间范围查询等额外参数
+            if (bo.getParams().containsKey("beginTime")) {
+                lqw.ge(TblDzbcCargoOrder::getCreateTime, bo.getParams().get("beginTime"));
+            }
+            if (bo.getParams().containsKey("endTime")) {
+                lqw.le(TblDzbcCargoOrder::getCreateTime, bo.getParams().get("endTime"));
+            }
+        }
+
+        lqw.orderByDesc(TblDzbcCargoOrder::getCreateTime);
+        return lqw;
+    }
+
+    /**
+     * 保存前的数据校验
+     */
+    private void validEntityBeforeSave(TblDzbcCargoOrder entity) {
+        // TODO 做一些数据校验,如唯一约束
+    }
+
+    /**
+     * 填充货物明细
+     */
+    private void fillCargoItems(TblDzbcCargoOrderVo vo) {
+        if (vo == null) {
+            return;
+        }
+        // 使用 LambdaQueryWrapper 替代未实现的 XML 方法 selectByOrderId
+        List<TblDzbcCargoItemVo> items = cargoItemMapper.selectVoList(new LambdaQueryWrapper<TblDzbcCargoItem>()
+            .eq(TblDzbcCargoItem::getOrderId, vo.getId()));
+        vo.setItems(items);
+    }
+
+    /**
+     * 更新司机任务的货物统计
+     */
+    private void updateTripCargoStats(Long tripId) {
+        // 使用 LambdaQueryWrapper 替代未实现的 XML 方法 selectByTripId
+        List<TblDzbcCargoOrderVo> orders = baseMapper.selectVoList(new LambdaQueryWrapper<TblDzbcCargoOrder>()
+            .eq(TblDzbcCargoOrder::getTripId, tripId));
+        int cargoCount = orders.size();
+        int totalPrice = orders.stream()
+            .mapToInt(order -> order.getPrice() != null ? order.getPrice() : 0)
+            .sum();
+
+        TblDzbcDriverTrip trip = new TblDzbcDriverTrip();
+        trip.setId(tripId);
+        trip.setCargoCount(cargoCount);
+        trip.setCargoTotalPrice(totalPrice);
+        driverTripMapper.updateById(trip);
+    }
+
+    /**
+     * 生成订单编号
+     */
+    private String generateOrderNo() {
+        return "CARGO" + cn.hutool.core.date.DateUtil.format(cn.hutool.core.date.DateUtil.date(), "yyyyMMddHHmmss")
+               + IdUtil.getSnowflakeNextIdStr().substring(8);
+    }
+}

+ 188 - 0
ruoyi-modules/ruoyi-dzbc/src/main/java/org/dromara/system/service/impl/TblDzbcDriverTripServiceImpl.java

@@ -0,0 +1,188 @@
+package org.dromara.system.service.impl;
+
+import cn.hutool.core.date.DateUtil;
+import cn.hutool.core.util.IdUtil;
+import cn.hutool.core.util.StrUtil;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import lombok.RequiredArgsConstructor;
+import org.dromara.common.core.utils.MapstructUtils;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.system.domain.TblDzbcDriverTrip;
+import org.dromara.system.domain.vo.SysUserVo;
+import org.dromara.system.domain.bo.TblDzbcDriverTripBo;
+import org.dromara.system.domain.vo.TblDzbcCargoOrderVo;
+import org.dromara.system.domain.vo.TblDzbcDriverTripVo;
+import org.dromara.system.domain.vo.TblDzbcPathSiteVo;
+import org.dromara.system.mapper.SysUserMapper;
+import org.dromara.system.mapper.TblDzbcDriverTripMapper;
+import org.dromara.system.mapper.TblDzbcPathSiteMapper;
+import org.dromara.system.service.ITblDzbcCargoOrderService;
+import org.dromara.system.service.ITblDzbcDriverTripService;
+import org.springframework.stereotype.Service;
+
+import java.time.LocalDateTime;
+import java.util.Collection;
+import java.util.List;
+
+@RequiredArgsConstructor
+@Service
+public class TblDzbcDriverTripServiceImpl implements ITblDzbcDriverTripService {
+
+    private final TblDzbcDriverTripMapper baseMapper;
+    private final SysUserMapper userMapper;
+    private final TblDzbcPathSiteMapper pathSiteMapper;
+    private final ITblDzbcCargoOrderService cargoOrderService;
+
+    @Override
+    public TblDzbcDriverTripVo queryById(Long id) {
+        TblDzbcDriverTripVo vo = baseMapper.selectVoById(id);
+        if (vo != null) {
+            fillDetails(vo);
+        }
+        return vo;
+    }
+
+    @Override
+    public TblDzbcDriverTripVo queryByTripNo(String tripNo) {
+        // 使用 LambdaQueryWrapper 替代未实现的 XML 方法 selectByTripNo
+        TblDzbcDriverTripVo vo = baseMapper.selectVoOne(new LambdaQueryWrapper<TblDzbcDriverTrip>()
+            .eq(TblDzbcDriverTrip::getTripNo, tripNo));
+        if (vo != null) {
+            fillDetails(vo);
+        }
+        return vo;
+    }
+
+    @Override
+    public TableDataInfo<TblDzbcDriverTripVo> queryPageList(TblDzbcDriverTripBo bo, PageQuery pageQuery) {
+        LambdaQueryWrapper<TblDzbcDriverTrip> lqw = buildQueryWrapper(bo);
+        Page<TblDzbcDriverTripVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
+        return TableDataInfo.build(result);
+    }
+
+    @Override
+    public List<TblDzbcDriverTripVo> queryList(TblDzbcDriverTripBo bo) {
+        LambdaQueryWrapper<TblDzbcDriverTrip> lqw = buildQueryWrapper(bo);
+        return baseMapper.selectVoList(lqw);
+    }
+
+    @Override
+    public TblDzbcDriverTripVo createTrip(TblDzbcDriverTripBo bo) {
+        SysUserVo driver = userMapper.selectUserById(bo.getDriverId());
+        if (driver == null) {
+            throw new RuntimeException("司机不存在");
+        }
+
+        TblDzbcDriverTrip trip = new TblDzbcDriverTrip();
+        trip.setTripNo(generateTripNo());
+        trip.setPathId(bo.getPathId());
+        trip.setDriverId(bo.getDriverId());
+        trip.setDriverName(driver.getNickName());
+        trip.setDriverPhone(driver.getPhonenumber());
+        trip.setCarId(bo.getCarId());
+        trip.setStartTime(bo.getStartTime());
+        trip.setExt1(bo.getExt1());
+        trip.setExt2(bo.getExt2());
+        trip.setStatus(0);
+
+        baseMapper.insert(trip);
+
+        TblDzbcDriverTripVo vo = queryById(trip.getId());
+        return vo;
+    }
+
+    @Override
+    public Boolean startTrip(Long id) {
+        TblDzbcDriverTrip trip = new TblDzbcDriverTrip();
+        trip.setId(id);
+        trip.setStatus(1);
+        return baseMapper.updateById(trip) > 0;
+    }
+
+    @Override
+    public Boolean arriveSite(Long id, Long siteId) {
+        TblDzbcDriverTripVo trip = queryById(id);
+        if (trip == null) {
+            throw new RuntimeException("任务不存在");
+        }
+
+        TblDzbcPathSiteVo site = pathSiteMapper.selectVoById(siteId);
+        if (site == null) {
+            throw new RuntimeException("站点不存在");
+        }
+
+        TblDzbcDriverTrip update = new TblDzbcDriverTrip();
+        update.setId(id);
+        update.setCurrentSiteId(siteId);
+        update.setCurrentSiteName(site.getSiteName());
+
+        return baseMapper.updateById(update) > 0;
+    }
+
+    @Override
+    public Boolean completeTrip(Long id) {
+        TblDzbcDriverTrip trip = new TblDzbcDriverTrip();
+        trip.setId(id);
+        trip.setStatus(2);
+        trip.setEndTime(LocalDateTime.now());
+        return baseMapper.updateById(trip) > 0;
+    }
+
+    @Override
+    public Boolean cancelTrip(Long id) {
+        TblDzbcDriverTrip trip = new TblDzbcDriverTrip();
+        trip.setId(id);
+        trip.setStatus(3);
+        return baseMapper.updateById(trip) > 0;
+    }
+
+    @Override
+    public Boolean updateByBo(TblDzbcDriverTripBo bo) {
+        TblDzbcDriverTrip update = MapstructUtils.convert(bo, TblDzbcDriverTrip.class);
+        validEntityBeforeSave(update);
+        return baseMapper.updateById(update) > 0;
+    }
+
+    @Override
+    public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
+        if (isValid) {
+        }
+        return baseMapper.deleteBatchIds(ids) > 0;
+    }
+
+    private LambdaQueryWrapper<TblDzbcDriverTrip> buildQueryWrapper(TblDzbcDriverTripBo bo) {
+        LambdaQueryWrapper<TblDzbcDriverTrip> lqw = Wrappers.lambdaQuery();
+        lqw.eq(bo.getPathId() != null, TblDzbcDriverTrip::getPathId, bo.getPathId());
+        lqw.eq(bo.getDriverId() != null, TblDzbcDriverTrip::getDriverId, bo.getDriverId());
+        lqw.like(StrUtil.isNotBlank(bo.getTripNo()), TblDzbcDriverTrip::getTripNo, bo.getTripNo());
+        lqw.eq(bo.getStatus() != null, TblDzbcDriverTrip::getStatus, bo.getStatus());
+        lqw.orderByDesc(TblDzbcDriverTrip::getCreateTime);
+        return lqw;
+    }
+
+    private void validEntityBeforeSave(TblDzbcDriverTrip entity) {
+    }
+
+    private void fillDetails(TblDzbcDriverTripVo vo) {
+        List<TblDzbcPathSiteVo> sites = pathSiteMapper.selectByPathId(vo.getPathId());
+        vo.setSites(sites);
+
+        List<TblDzbcCargoOrderVo> cargoOrders = cargoOrderService.queryByTripId(vo.getId());
+        vo.setCargoOrders(cargoOrders);
+
+        if (cargoOrders != null) {
+            vo.setCargoCount(cargoOrders.size());
+            int totalPrice = cargoOrders.stream()
+                .mapToInt(order -> order.getPrice() != null ? order.getPrice() : 0)
+                .sum();
+            vo.setCargoTotalPrice(totalPrice);
+        }
+    }
+
+    private String generateTripNo() {
+        return "TRIP" + DateUtil.format(DateUtil.date(), "yyyyMMddHHmmss") + IdUtil.getSnowflakeNextIdStr().substring(8);
+    }
+}

+ 171 - 0
ruoyi-modules/ruoyi-dzbc/src/main/java/org/dromara/system/service/impl/TblDzbcFeedbackServiceImpl.java

@@ -0,0 +1,171 @@
+package org.dromara.system.service.impl;
+
+import cn.hutool.core.util.StrUtil;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import lombok.RequiredArgsConstructor;
+import org.dromara.common.core.domain.R;
+import org.dromara.common.core.utils.MapstructUtils;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.common.satoken.utils.LoginHelper;
+import org.dromara.system.domain.TblDzbcFeedback;
+import org.dromara.system.domain.vo.SysUserVo;
+import org.dromara.system.domain.bo.FeedbackSubmitBo;
+import org.dromara.system.domain.bo.TblDzbcFeedbackBo;
+import org.dromara.system.domain.SysOss;
+import org.dromara.system.domain.vo.SysOssVo;
+import org.dromara.system.domain.vo.TblDzbcFeedbackVo;
+import org.dromara.system.mapper.TblDzbcFeedbackMapper;
+import org.dromara.system.service.ISysOssService;
+import org.dromara.system.service.ISysUserService;
+import org.dromara.system.service.ITblDzbcFeedbackService;
+import org.springframework.stereotype.Service;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.time.LocalDateTime;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+@RequiredArgsConstructor
+@Service
+public class TblDzbcFeedbackServiceImpl implements ITblDzbcFeedbackService {
+
+    private final TblDzbcFeedbackMapper baseMapper;
+    private final ISysOssService ossService;
+    private final ISysUserService userService;
+
+    @Override
+    public TblDzbcFeedbackVo queryById(Long id) {
+        return baseMapper.selectVoById(id);
+    }
+
+    @Override
+    public TableDataInfo<TblDzbcFeedbackVo> queryPageList(TblDzbcFeedbackBo bo, PageQuery pageQuery) {
+        LambdaQueryWrapper<TblDzbcFeedback> lqw = buildQueryWrapper(bo);
+        Page<TblDzbcFeedbackVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
+        fillUserInfo(result.getRecords());
+        return TableDataInfo.build(result);
+    }
+
+    @Override
+    public List<TblDzbcFeedbackVo> queryList(TblDzbcFeedbackBo bo) {
+        LambdaQueryWrapper<TblDzbcFeedback> lqw = buildQueryWrapper(bo);
+        List<TblDzbcFeedbackVo> list = baseMapper.selectVoList(lqw);
+        fillUserInfo(list);
+        return list;
+    }
+
+    @Override
+    public List<TblDzbcFeedbackVo> queryByUserId(Long userId) {
+        LambdaQueryWrapper<TblDzbcFeedback> lqw = Wrappers.lambdaQuery();
+        lqw.eq(TblDzbcFeedback::getUserId, userId);
+        lqw.orderByDesc(TblDzbcFeedback::getCreateTime);
+        List<TblDzbcFeedbackVo> list = baseMapper.selectVoList(lqw);
+        fillUserInfo(list);
+        return list;
+    }
+
+    @Override
+    public TblDzbcFeedbackVo submit(FeedbackSubmitBo bo) {
+        Long userId = LoginHelper.getUserId();
+        SysUserVo user = userService.selectUserById(userId);
+
+        List<String> imageUrls = new ArrayList<>();
+        if (bo.getImages() != null && !bo.getImages().isEmpty()) {
+            for (MultipartFile file : bo.getImages()) {
+                SysOssVo oss = ossService.upload(file);
+                imageUrls.add(oss.getUrl());
+            }
+        }
+
+        TblDzbcFeedback feedback = new TblDzbcFeedback();
+        feedback.setUserId(userId);
+        feedback.setUserName(user != null ? user.getNickName() : null);
+        feedback.setUserPhone(user != null ? user.getPhonenumber() : null);
+        feedback.setContent(bo.getContent());
+        feedback.setImageUrls(imageUrls.isEmpty() ? null : String.join(",", imageUrls));
+        feedback.setType(bo.getType());
+        feedback.setStatus(0);
+
+        baseMapper.insert(feedback);
+        return baseMapper.selectVoById(feedback.getId());
+    }
+
+    @Override
+    public Boolean insertByBo(TblDzbcFeedbackBo bo) {
+        TblDzbcFeedback add = MapstructUtils.convert(bo, TblDzbcFeedback.class);
+        validEntityBeforeSave(add);
+        return baseMapper.insert(add) > 0;
+    }
+
+    @Override
+    public Boolean updateByBo(TblDzbcFeedbackBo bo) {
+        TblDzbcFeedback update = MapstructUtils.convert(bo, TblDzbcFeedback.class);
+        validEntityBeforeSave(update);
+        if(update.getId()!=null){
+            return baseMapper.updateById(update) > 0;
+        }
+        return baseMapper.insert(update) > 0;
+    }
+
+    @Override
+    public Boolean reply(Long id, String replyContent) {
+        Long userId = LoginHelper.getUserId();
+        SysUserVo user = userService.selectUserById(userId);
+
+        TblDzbcFeedback feedback = new TblDzbcFeedback();
+        feedback.setId(id);
+        feedback.setReplyContent(replyContent);
+        feedback.setReplyUserId(userId);
+        feedback.setReplyUserName(user != null ? user.getNickName() : null);
+        feedback.setReplyTime(LocalDateTime.now());
+        feedback.setStatus(2);
+
+        return baseMapper.updateById(feedback) > 0;
+    }
+
+    @Override
+    public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
+        if (isValid) {
+        }
+        return baseMapper.deleteBatchIds(ids) > 0;
+    }
+
+    private LambdaQueryWrapper<TblDzbcFeedback> buildQueryWrapper(TblDzbcFeedbackBo bo) {
+        LambdaQueryWrapper<TblDzbcFeedback> lqw = Wrappers.lambdaQuery();
+        lqw.eq(bo.getUserId() != null, TblDzbcFeedback::getUserId, bo.getUserId());
+        lqw.like(StrUtil.isNotBlank(bo.getUserName()), TblDzbcFeedback::getUserName, bo.getUserName());
+        lqw.like(StrUtil.isNotBlank(bo.getContent()), TblDzbcFeedback::getContent, bo.getContent());
+        lqw.eq(bo.getType() != null, TblDzbcFeedback::getType, bo.getType());
+        lqw.eq(bo.getStatus() != null, TblDzbcFeedback::getStatus, bo.getStatus());
+        lqw.orderByDesc(TblDzbcFeedback::getCreateTime);
+        return lqw;
+    }
+
+    private void validEntityBeforeSave(TblDzbcFeedback entity) {
+        if (StrUtil.isBlank(entity.getContent())) {
+            throw new IllegalArgumentException("反馈内容不能为空");
+        }
+        if (entity.getType() == null) {
+            throw new IllegalArgumentException("反馈类型不能为空");
+        }
+    }
+
+    private void fillUserInfo(List<TblDzbcFeedbackVo> list) {
+        if (list == null || list.isEmpty()) {
+            return;
+        }
+        list.forEach(vo -> {
+            if (vo.getUserId() != null && StrUtil.isBlank(vo.getUserName())) {
+                SysUserVo user = userService.selectUserById(vo.getUserId());
+                if (user != null) {
+                    vo.setUserName(user.getNickName());
+                    vo.setUserPhone(user.getPhonenumber());
+                }
+            }
+        });
+    }
+}

+ 99 - 0
ruoyi-modules/ruoyi-dzbc/src/main/java/org/dromara/system/service/impl/TblDzbcMultiSitePathServiceImpl.java

@@ -0,0 +1,99 @@
+package org.dromara.system.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import lombok.RequiredArgsConstructor;
+import org.dromara.common.core.utils.MapstructUtils;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.system.domain.TblDzbcMultiSitePath;
+import org.dromara.system.domain.TblDzbcPathSite;
+import org.dromara.system.domain.TblDzbcSitePrice;
+import org.dromara.system.domain.bo.TblDzbcMultiSitePathBo;
+import org.dromara.system.domain.vo.TblDzbcMultiSitePathVo;
+import org.dromara.system.domain.vo.TblDzbcPathSiteVo;
+import org.dromara.system.domain.vo.TblDzbcSitePriceVo;
+import org.dromara.system.mapper.TblDzbcMultiSitePathMapper;
+import org.dromara.system.mapper.TblDzbcPathSiteMapper;
+import org.dromara.system.mapper.TblDzbcSitePriceMapper;
+import org.dromara.system.service.ITblDzbcMultiSitePathService;
+import org.springframework.stereotype.Service;
+
+import java.util.Collection;
+import java.util.List;
+
+@RequiredArgsConstructor
+@Service
+public class TblDzbcMultiSitePathServiceImpl implements ITblDzbcMultiSitePathService {
+
+    private final TblDzbcMultiSitePathMapper baseMapper;
+    private final TblDzbcPathSiteMapper pathSiteMapper;
+    private final TblDzbcSitePriceMapper sitePriceMapper;
+
+    @Override
+    public TblDzbcMultiSitePathVo queryById(Long id) {
+        return baseMapper.selectVoById(id);
+    }
+
+    @Override
+    public TableDataInfo<TblDzbcMultiSitePathVo> queryPageList(TblDzbcMultiSitePathBo bo, PageQuery pageQuery) {
+        LambdaQueryWrapper<TblDzbcMultiSitePath> lqw = buildQueryWrapper(bo);
+        Page<TblDzbcMultiSitePathVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
+        return TableDataInfo.build(result);
+    }
+
+    @Override
+    public List<TblDzbcMultiSitePathVo> queryList(TblDzbcMultiSitePathBo bo) {
+        LambdaQueryWrapper<TblDzbcMultiSitePath> lqw = buildQueryWrapper(bo);
+        return baseMapper.selectVoList(lqw);
+    }
+
+    @Override
+    public TblDzbcMultiSitePathVo queryByIdWithDetails(Long id) {
+        TblDzbcMultiSitePathVo pathVo = baseMapper.selectVoById(id);
+        if (pathVo != null) {
+            List<TblDzbcPathSiteVo> sites = pathSiteMapper.selectByPathId(id);
+            List<TblDzbcSitePriceVo> prices = sitePriceMapper.selectByPathId(id);
+            pathVo.setSites(sites);
+            pathVo.setPrices(prices);
+        }
+        return pathVo;
+    }
+
+    @Override
+    public Boolean insertByBo(TblDzbcMultiSitePathBo bo) {
+        TblDzbcMultiSitePath add = MapstructUtils.convert(bo, TblDzbcMultiSitePath.class);
+        validEntityBeforeSave(add);
+        boolean flag = baseMapper.insert(add) > 0;
+        if (flag) {
+            bo.setId(add.getId());
+        }
+        return flag;
+    }
+
+    @Override
+    public Boolean updateByBo(TblDzbcMultiSitePathBo bo) {
+        TblDzbcMultiSitePath update = MapstructUtils.convert(bo, TblDzbcMultiSitePath.class);
+        validEntityBeforeSave(update);
+        return baseMapper.updateById(update) > 0;
+    }
+
+    @Override
+    public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
+        if (isValid) {
+        }
+        return baseMapper.deleteBatchIds(ids) > 0;
+    }
+
+    private LambdaQueryWrapper<TblDzbcMultiSitePath> buildQueryWrapper(TblDzbcMultiSitePathBo bo) {
+        LambdaQueryWrapper<TblDzbcMultiSitePath> lqw = Wrappers.lambdaQuery();
+        lqw.like(org.dromara.common.core.utils.StringUtils.isNotBlank(bo.getName()), TblDzbcMultiSitePath::getName, bo.getName());
+        lqw.eq(bo.getStatus() != null, TblDzbcMultiSitePath::getStatus, bo.getStatus());
+        lqw.orderByDesc(TblDzbcMultiSitePath::getId);
+        return lqw;
+    }
+
+    private void validEntityBeforeSave(TblDzbcMultiSitePath entity) {
+    }
+}

+ 123 - 67
ruoyi-modules/ruoyi-dzbc/src/main/java/org/dromara/system/service/impl/TblDzbcOrderServiceImpl.java

@@ -37,7 +37,6 @@ import org.dromara.system.domain.bo.TblDzbcOrderBo;
 import org.dromara.system.service.ITblDzbcOrderService;
 import org.springframework.util.Assert;
 
-import java.text.DateFormat;
 import java.util.*;
 
 /**
@@ -57,6 +56,8 @@ public class TblDzbcOrderServiceImpl implements ITblDzbcOrderService {
     private final TblDzbcAreaMapper areaMapper;
     private final TblDzbcPathSchedulingMapper pathSchedulingMapper;
     private final TblDzbcPathMapper pathMapper;
+    private final TblDzbcMultiSitePathMapper multiSitePathMapper;
+    private final TblDzbcSitePriceMapper sitePriceMapper;
 
 
 
@@ -154,18 +155,46 @@ public class TblDzbcOrderServiceImpl implements ITblDzbcOrderService {
         Assert.isTrue(bo.getSchedulingId()!=null,"缺少路线班次");
         TblDzbcPathSchedulingVo scheduling = pathSchedulingMapper.selectVoById(bo.getSchedulingId());
         Assert.isTrue(scheduling!=null,"路线未查询到排班");
-        TblDzbcPathVo pathVo = pathMapper.selectVoById(scheduling.getPathId());
-        //Ext1  { zw:2,usersInfos:[user1,{lx:'成人','name':'张三','cardNo':'1234567890','cardType':'身份证'}]  }
-        Assert.isTrue(scheduling!=null,"路线排班不存在");
-        Assert.isTrue(pathVo!=null,"路线不存在");
+
+        // 解析排班类型
+        Integer type = null;
+        if (scheduling.getExt2() != null) {
+            try {
+                JSONObject ext2 = JSONUtil.parseObj(scheduling.getExt2());
+                if (ext2.containsKey("type")) {
+                    type = ext2.getInt("type");
+                }
+            } catch (Exception e) {}
+        }
+
+        // 预定义变量
+        String pathName = "";
+        TblDzbcPathVo pathVo = null;
+        TblDzbcMultiSitePathVo multiPathVo = null;
+
+        if (type != null && type == 2) {
+            // === 多站点路线 ===
+            multiPathVo = multiSitePathMapper.selectVoById(scheduling.getPathId());
+            Assert.isTrue(multiPathVo != null, "路线不存在");
+            pathName = multiPathVo.getName();
+            // 多站点路线使用排班自带的 mtime,无需回退到 pathVo.mtime
+        } else {
+            // === 普通路线 ===
+            pathVo = pathMapper.selectVoById(scheduling.getPathId());
+            Assert.isTrue(pathVo!=null,"路线不存在");
+            pathName = pathVo.getName();
+            // 如果排班没有时间,使用路线默认时间
+            if(scheduling.getMtime()==null || scheduling.getMtime().equals("")){
+                scheduling.setMtime(pathVo.getMtime());
+            }
+        }
+
         Assert.isTrue(bo.getNum()!=null,"缺少座位数");
 //        Assert.isTrue(bo.getParams().get("usersInfo")!=null,"缺少订购人员");
         if(bo.getParams().get("usersInfo")==null){
             bo.getParams().put("usersInfo",new Object[]{});
         }
-        if(scheduling.getMtime()==null || scheduling.getMtime().equals("")){
-            scheduling.setMtime(pathVo.getMtime());
-        }
+
         Date datec =  DateUtil.parse(DateUtil.formatDate(scheduling.getMdate())+" "+scheduling.getMtime(), DatePattern.NORM_DATETIME_FORMAT);
         Assert.isTrue( datec.getTime() > DateUtil.current(),"路线车辆已出发");
 
@@ -175,76 +204,103 @@ public class TblDzbcOrderServiceImpl implements ITblDzbcOrderService {
         TblDzbcSiteVo startSite = siteMapper.selectVoById(Long.parseLong(bo.getParams().get("startSiteId").toString()));
         TblDzbcSiteVo endSite =  siteMapper.selectVoById(Long.parseLong(bo.getParams().get("endSiteId").toString()));
         Assert.isTrue(startSite!=null&&endSite!=null,"站点Id不存在");
-        Assert.isTrue(!startSite.getAreaId().equals(endSite.getAreaId()),"站点不在能在同一区域");
-        //查询起点区域到目前区域的距离
-        List<TblDzbcAreaVo> areas = areaMapper.selectVoBatchIds(List.of(startSite.getAreaId(),endSite.getAreaId()), TblDzbcAreaVo.class);
-        Assert.isTrue(areas.size()==2,"站点信息错误");
-        Assert.isTrue(StrUtil.isNotEmpty(areas.get(0).getLocation()) ,areas.get(0).getName()+"区域未设置经纬度");
-        Assert.isTrue(StrUtil.isNotEmpty(areas.get(1).getLocation()),areas.get(1).getName()+"区域未设置经纬度");
-        double distanceArea = DistanceCalculator.calculateDistance(areas.get(0).getLocation(),areas.get(1).getLocation());
-        double distanceSite = DistanceCalculator.calculateDistance(startSite.getLocation(),endSite.getLocation());
 
+        //查询起点区域到目前区域的距离
+        List<TblDzbcAreaVo> areas = areaMapper.selectVoBatchIds(List.of(startSite.getAreaId(),endSite.getAreaId()));
+        // 注意:如果是同区域,areas.size() 可能为 1 (如果去重) 或 2 (如果未去重但ID相同)。
+        // 为了安全起见,分别查询或者处理 size < 2 的情况。
+        // 但为了兼容旧代码逻辑,这里先保留,但在多站点且同区域时可能会有问题。
+        // 如果是多站点且同区域,areas.size() 会是 1 (MyBatis-Plus batchIds 会去重)。
+        // 所以需要重新处理 areas 获取逻辑。
+
+        TblDzbcAreaVo startArea = null;
+        TblDzbcAreaVo endArea = null;
+        if (startSite.getAreaId().equals(endSite.getAreaId())) {
+             startArea = areaMapper.selectVoById(startSite.getAreaId());
+             endArea = startArea;
+        } else {
+             // 保持原有逻辑以防万一,或者分别查询
+             startArea = areaMapper.selectVoById(startSite.getAreaId());
+             endArea = areaMapper.selectVoById(endSite.getAreaId());
+        }
+        Assert.isTrue(startArea != null && endArea != null, "区域信息错误");
+
+        if (type != null && type == 2) {
+            // === 多站点路线价格计算 ===
+            TblDzbcSitePriceVo priceVo = sitePriceMapper.selectVoOne(
+                new LambdaQueryWrapper<TblDzbcSitePrice>()
+                    .eq(TblDzbcSitePrice::getPathId, scheduling.getPathId())
+                    .eq(TblDzbcSitePrice::getFromSiteId, startSite.getId())
+                    .eq(TblDzbcSitePrice::getToSiteId, endSite.getId())
+            );
+            if (priceVo == null) {
+                 throw new RuntimeException("未查询到该行程的票价信息");
+            }
+            int finalResult = Math.round(((int)Math.round(priceVo.getPrice() + scheduling.getPprice()) + 5) / 100) * 100;
+            add.setPrice(finalResult);
 
-        try{
-            if(pathVo.getExt1()!=null && JSONUtil.parseObj(pathVo.getExt1().toString())!=null) {
-                JSONObject ext1 = JSONUtil.parseObj(pathVo.getExt1().toString());
-
-
-                //获取区域价格
-                JSONArray prices = ext1.getJSONArray("jgqj");
-                //获取距离
-                Double distance = ext1.getDouble("jl");
-                //获取价格系数
-                Double jgxs = ext1.getDouble("jgxs");
-
-                if (jgxs == null) jgxs = 1.0;
-                if (distance == null) distance = distanceArea;
-                double distanceSite1 = distanceSite * distance / distanceArea;
-
-                if(prices!=null||prices.size()>0) {
-
-                    for (int i = 0; i < prices.size(); i++) {
-                        JSONObject price = prices.getJSONObject(i);
-                        Double min = price.getDouble("min");
-                        Double max = price.getDouble("max");
-                        Double xs = price.getDouble("xs");
-                        Double p = price.getDouble("price");
-                        if (min == null) min = 0.0;
-                        if (max == null) max = Double.MAX_VALUE;
-                        if (xs == null) xs = 0.0;
-                        if (distanceSite1 >= min && distanceSite1 <= max) {
-                            double jg = (p * 100) + (((distanceSite1-min)/1000.0) * xs)*100;
-                            int finalResult = (((int) Math.round(jg) + 5) / 10) * 10;
-                            add.setPrice(finalResult);
-                            break;
+        } else {
+            // === 普通路线逻辑 ===
+            Assert.isTrue(!startSite.getAreaId().equals(endSite.getAreaId()),"站点不在能在同一区域");
+
+            Assert.isTrue(StrUtil.isNotEmpty(startArea.getLocation()) ,startArea.getName()+"区域未设置经纬度");
+            Assert.isTrue(StrUtil.isNotEmpty(endArea.getLocation()),endArea.getName()+"区域未设置经纬度");
+            double distanceArea = DistanceCalculator.calculateDistance(startArea.getLocation(),endArea.getLocation());
+            double distanceSite = DistanceCalculator.calculateDistance(startSite.getLocation(),endSite.getLocation());
+
+            try{
+                if(pathVo.getExt1()!=null && JSONUtil.parseObj(pathVo.getExt1().toString())!=null) {
+                    JSONObject ext1 = JSONUtil.parseObj(pathVo.getExt1().toString());
+                    //获取区域价格
+                    JSONArray prices = ext1.getJSONArray("jgqj");
+                    //获取距离
+                    Double distance = ext1.getDouble("jl");
+                    //获取价格系数
+                    Double jgxs = ext1.getDouble("jgxs");
+
+                    if (jgxs == null) jgxs = 1.0;
+                    if (distance == null) distance = distanceArea;
+                    double distanceSite1 = distanceSite * distance / distanceArea;
+
+                    if(prices!=null||prices.size()>0) {
+                        for (int i = 0; i < prices.size(); i++) {
+                            JSONObject price = prices.getJSONObject(i);
+                            Double min = price.getDouble("min");
+                            Double max = price.getDouble("max");
+                            Double xs = price.getDouble("xs");
+                            Double p = price.getDouble("price");
+                            if (min == null) min = 0.0;
+                            if (max == null) max = Double.MAX_VALUE;
+                            if (xs == null) xs = 0.0;
+                            if (distanceSite1 >= min && distanceSite1 <= max) {
+                                double jg = (p * 100) + (((distanceSite1-min)/1000.0) * xs)*100;
+                                int finalResult = Math.round(((int) Math.round(jg) + 5) / 100) * 100;
+                                add.setPrice(finalResult);
+                                break;
+                            }
                         }
                     }
                 }
+            }catch (Exception e){
+                throw new RuntimeException("路线价格错误");
+            }
+            if(add.getPrice()==null){
+                Integer price1 = pathVo.getPrice();
+                if(price1==null) throw new RuntimeException("路线价格错误");
+                //单价 根据区域-区域 单价及站点-站点 计算价格
+                double price = price1*(distanceSite/distanceArea);
+                //计算加价
+                int finalResult = Math.round(((int)Math.round(price+scheduling.getPprice()) + 5) / 100) * 100;
+                add.setPrice(finalResult);
             }
-        }catch (Exception e){
-            throw new RuntimeException("路线价格错误");
-        }
-        if(add.getPrice()==null){
-            Integer price1 = pathVo.getPrice();
-            if(price1==null) throw new RuntimeException("路线价格错误");
-            //单价 根据区域-区域 单价及站点-站点 计算价格
-            double price = price1*(distanceSite/distanceArea);
-            //计算加价
-            int finalResult = (((int)Math.round(price+scheduling.getPprice()) + 5) / 10) * 10;
-            add.setPrice(finalResult);
         }
-//
-//        //单价 根据区域-区域 单价及站点-站点 计算价格
-//        double price = pathVo.getPrice()*(distanceSite/distanceArea);
-//        //计算加价
-//        add.setPrice((int)Math.round(price+scheduling.getPprice()));
 
-        add.setStartInfo(JSONUtil.toJsonStr(Map.of("areaInfo", areas.get(0), "siteInfo", startSite)));
-        add.setEndInfo(JSONUtil.toJsonStr(Map.of("areaInfo", areas.get(1), "siteInfo", endSite)));
+        add.setStartInfo(JSONUtil.toJsonStr(Map.of("areaInfo", startArea, "siteInfo", startSite)));
+        add.setEndInfo(JSONUtil.toJsonStr(Map.of("areaInfo", endArea, "siteInfo", endSite)));
 
         add.setUserId(LoginHelper.getUserId());
 
-        add.setName(DateUtil.formatDate(scheduling.getMdate())+" "+pathVo.getName()+" "+scheduling.getMtime());
+        add.setName(DateUtil.formatDate(scheduling.getMdate())+" "+pathName+" "+scheduling.getMtime());
         //计算总价
         add.setTotalPrice(add.getNum()*add.getPrice());
 

+ 164 - 12
ruoyi-modules/ruoyi-dzbc/src/main/java/org/dromara/system/service/impl/TblDzbcPathSchedulingServiceImpl.java

@@ -5,6 +5,7 @@ import cn.hutool.core.date.DateTime;
 import cn.hutool.core.date.DateUtil;
 import cn.hutool.core.thread.ThreadUtil;
 import cn.hutool.core.util.StrUtil;
+import cn.hutool.json.JSONObject;
 import cn.hutool.json.JSONUtil;
 import org.dromara.common.core.service.OssService;
 import org.dromara.common.core.utils.MapstructUtils;
@@ -22,18 +23,24 @@ import org.dromara.system.domain.SysUser;
 import org.dromara.system.domain.TblDzbcPath;
 import org.dromara.system.domain.vo.SysUserVo;
 import org.dromara.system.domain.vo.TblDzbcCarVo;
+import org.dromara.system.domain.vo.TblDzbcMultiSitePathVo;
 import org.dromara.system.domain.vo.TblDzbcPathVo;
 import org.dromara.system.mapper.SysUserMapper;
 import org.dromara.system.mapper.TblDzbcCarMapper;
+import org.dromara.system.mapper.TblDzbcMultiSitePathMapper;
 import org.dromara.system.mapper.TblDzbcPathMapper;
 import org.springframework.stereotype.Service;
+import org.dromara.system.domain.bo.TblDzbcDriverTripBo;
 import org.dromara.system.domain.bo.TblDzbcPathSchedulingBo;
 import org.dromara.system.domain.vo.TblDzbcPathSchedulingVo;
 import org.dromara.system.domain.TblDzbcPathScheduling;
 import org.dromara.system.mapper.TblDzbcPathSchedulingMapper;
+import org.dromara.system.service.ITblDzbcDriverTripService;
 import org.dromara.system.service.ITblDzbcPathSchedulingService;
 
 import java.time.Duration;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
 import java.util.*;
 
 /**
@@ -50,6 +57,8 @@ public class TblDzbcPathSchedulingServiceImpl implements ITblDzbcPathSchedulingS
 
     private final TblDzbcCarMapper carMapper;
     private final TblDzbcPathMapper pathMapper;
+    private final TblDzbcMultiSitePathMapper multiSitePathMapper;
+    private final ITblDzbcDriverTripService driverTripService;
 
     private final SysUserMapper userMapper;
     private final OssService ossService;
@@ -60,7 +69,16 @@ public class TblDzbcPathSchedulingServiceImpl implements ITblDzbcPathSchedulingS
     @Override
     public TblDzbcPathSchedulingVo queryById(Long id){
         SysCache();
-        return baseMapper.selectVoById(id);
+        TblDzbcPathSchedulingVo vo = baseMapper.selectVoById(id);
+        if (vo != null && vo.getExt2() != null) {
+            try {
+                JSONObject ext2 = JSONUtil.parseObj(vo.getExt2());
+                if (ext2.containsKey("type")) {
+                    vo.setType(ext2.getInt("type"));
+                }
+            } catch (Exception e) {}
+        }
+        return vo;
     }
 
     /**
@@ -70,6 +88,16 @@ public class TblDzbcPathSchedulingServiceImpl implements ITblDzbcPathSchedulingS
     public TableDataInfo<TblDzbcPathSchedulingVo> queryPageList(TblDzbcPathSchedulingBo bo, PageQuery pageQuery) {
         LambdaQueryWrapper<TblDzbcPathScheduling> lqw = buildQueryWrapper(bo);
         Page<TblDzbcPathSchedulingVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
+        result.getRecords().forEach(vo -> {
+            if (vo.getExt2() != null) {
+                try {
+                    JSONObject ext2 = JSONUtil.parseObj(vo.getExt2());
+                    if (ext2.containsKey("type")) {
+                        vo.setType(ext2.getInt("type"));
+                    }
+                } catch (Exception e) {}
+            }
+        });
         return TableDataInfo.build(result);
     }
 
@@ -79,7 +107,18 @@ public class TblDzbcPathSchedulingServiceImpl implements ITblDzbcPathSchedulingS
     @Override
     public List<TblDzbcPathSchedulingVo> queryList(TblDzbcPathSchedulingBo bo) {
         LambdaQueryWrapper<TblDzbcPathScheduling> lqw = buildQueryWrapper(bo);
-        return baseMapper.selectVoList(lqw);
+        List<TblDzbcPathSchedulingVo> list = baseMapper.selectVoList(lqw);
+        list.forEach(vo -> {
+            if (vo.getExt2() != null) {
+                try {
+                    JSONObject ext2 = JSONUtil.parseObj(vo.getExt2());
+                    if (ext2.containsKey("type")) {
+                        vo.setType(ext2.getInt("type"));
+                    }
+                } catch (Exception e) {}
+            }
+        });
+        return list;
     }
 
     private LambdaQueryWrapper<TblDzbcPathScheduling> buildQueryWrapper(TblDzbcPathSchedulingBo bo) {
@@ -94,6 +133,18 @@ public class TblDzbcPathSchedulingServiceImpl implements ITblDzbcPathSchedulingS
         lqw.eq(StringUtils.isNotBlank(bo.getEndSites()), TblDzbcPathScheduling::getEndSites, bo.getEndSites());
         lqw.eq(StringUtils.isNotBlank(bo.getExt1()), TblDzbcPathScheduling::getExt1, bo.getExt1());
         lqw.eq(StringUtils.isNotBlank(bo.getExt2()), TblDzbcPathScheduling::getExt2, bo.getExt2());
+
+        // 根据 type 查询 (映射到 ext2->'$.type')
+        if (bo.getType() != null) {
+            if (bo.getType() == 2) {
+                // 固定路线:明确 type 为 2
+                lqw.apply("ext2->'$.type' = 2");
+            } else {
+                // 普通路线:type 为 1 或 不存在 type 字段
+                lqw.apply("(ext2->'$.type' = 1 OR ext2->'$.type' IS NULL)");
+            }
+        }
+
         if(params!=null){
             if(params.containsKey("mdate")){
                 lqw.ge(params.get("mdate")!=null, TblDzbcPathScheduling::getMdate, (params.get("mdate").toString()));
@@ -144,19 +195,48 @@ public class TblDzbcPathSchedulingServiceImpl implements ITblDzbcPathSchedulingS
             throw new RuntimeException("车辆不能为空");
         }
         if(add.getPathId()!=null){
-            TblDzbcPathVo pathVo =  pathMapper.selectVoById(bo.getPathId());
-            if(pathVo == null){
-                throw new RuntimeException("路线不存在");
+            if (bo.getType() != null && bo.getType() == 2) {
+                // 固定路线(多站点)
+                TblDzbcMultiSitePathVo multiSitePathVo = multiSitePathMapper.selectVoById(bo.getPathId());
+                if (multiSitePathVo == null) {
+                    throw new RuntimeException("路线不存在");
+                }
+                params.put("pathInfo", multiSitePathVo);
+                // 多站点路线不自动设置mtime,需前端传递或后续处理
+            } else {
+                // 普通路线
+                TblDzbcPathVo pathVo =  pathMapper.selectVoById(bo.getPathId());
+                if(pathVo == null){
+                    throw new RuntimeException("路线不存在");
+                }
+                params.put("pathInfo",pathVo);
+                // 如果未设置时间,则使用路线默认时间
+                if (StrUtil.isBlank(add.getMtime())) {
+                    add.setMtime(pathVo.getMtime());
+                }
             }
-            params.put("pathInfo",pathVo);
-            add.setMtime(pathVo.getMtime());
         }else{
             throw new RuntimeException("路线不能为空");
         }
         add.setExt1(JSONUtil.toJsonStr(params));
+
+        // 处理排班类型
+        if (bo.getType() != null) {
+            JSONObject ext2 = JSONUtil.parseObj(add.getExt2());
+            if (ext2 == null) {
+                ext2 = JSONUtil.createObj();
+            }
+            ext2.set("type", bo.getType());
+            add.setExt2(ext2.toString());
+        }
+
         validEntityBeforeSave(add);
         boolean flag = baseMapper.insert(add) > 0;
         if (flag) {
+            // 如果是车辆排班(type=2),自动创建司机发车任务
+            if (bo.getType() != null && bo.getType() == 2) {
+                createDriverTrip(add, params);
+            }
             bo.setId(add.getId());
             SysCache();
         }
@@ -164,6 +244,53 @@ public class TblDzbcPathSchedulingServiceImpl implements ITblDzbcPathSchedulingS
     }
 
     /**
+     * 创建司机发车任务
+     */
+    private void createDriverTrip(TblDzbcPathScheduling scheduling, Map<String, Object> params) {
+        try {
+            SysUserVo driver = (SysUserVo) params.get("driverInfo");
+            if (driver == null) {
+                // 如果params中没有,尝试重新查询
+                TblDzbcCarVo carVo = carMapper.selectVoById(scheduling.getCarId());
+                if (carVo != null && carVo.getDriverId() != null) {
+                    driver = userMapper.selectUserById(carVo.getDriverId());
+                }
+            }
+
+            if (driver != null) {
+                TblDzbcDriverTripBo tripBo = new TblDzbcDriverTripBo();
+                tripBo.setPathId(scheduling.getPathId());
+                tripBo.setDriverId(driver.getUserId());
+                tripBo.setCarId(scheduling.getCarId());
+
+                // 设置开始时间 (mdate + mtime)
+                String dateStr = DateUtil.formatDate(scheduling.getMdate());
+                String timeStr = scheduling.getMtime();
+                // 确保时间格式正确,例如 "08:00" -> "08:00:00"
+                if (StringUtils.countMatches(timeStr, ":") == 1) {
+                    timeStr += ":00";
+                }
+                String dateTimeStr = dateStr + " " + timeStr;
+                LocalDateTime startTime = LocalDateTime.parse(dateTimeStr, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
+                tripBo.setStartTime(startTime);
+
+                // 扩展字段关联排班ID
+                JSONObject ext2 = JSONUtil.createObj();
+                ext2.set("schedulingId", scheduling.getId());
+                tripBo.setExt2(ext2.toString());
+
+                // 存储排班的 JSON 快照到 ext1
+                tripBo.setExt1(JSONUtil.toJsonStr(scheduling));
+
+                driverTripService.createTrip(tripBo);
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+            throw new RuntimeException("自动创建发车任务失败: " + e.getMessage());
+        }
+    }
+
+    /**
      * 修改定制班车-路线排班
      */
     @Override
@@ -195,13 +322,38 @@ public class TblDzbcPathSchedulingServiceImpl implements ITblDzbcPathSchedulingS
                 params.put("carInfo",carVo);
             }
             if(add.getPathId()!=null){
-                TblDzbcPathVo pathVo =  pathMapper.selectVoById(bo.getPathId());
-                if(pathVo == null){
-                    throw new RuntimeException("路线不存在");
+                if (bo.getType() != null && bo.getType() == 2) {
+                    // 固定路线(多站点)
+                    TblDzbcMultiSitePathVo multiSitePathVo = multiSitePathMapper.selectVoById(bo.getPathId());
+                    if (multiSitePathVo == null) {
+                        throw new RuntimeException("路线不存在");
+                    }
+                    params.put("pathInfo", multiSitePathVo);
+                    // 多站点路线不自动设置mtime,需前端传递或后续处理
+                } else {
+                    // 普通路线
+                    TblDzbcPathVo pathVo =  pathMapper.selectVoById(bo.getPathId());
+                    if(pathVo == null){
+                        throw new RuntimeException("路线不存在");
+                    }
+                    params.put("pathInfo",pathVo);
+                    // 如果未设置时间,则使用路线默认时间
+                    if (StrUtil.isBlank(add.getMtime())) {
+                        add.setMtime(pathVo.getMtime());
+                    }
                 }
-                params.put("pathInfo",pathVo);
-                add.setMtime(pathVo.getMtime());
             }
+
+            // 处理排班类型
+            if (bo.getType() != null) {
+                JSONObject ext2 = JSONUtil.parseObj(add.getExt2());
+                if (ext2 == null) {
+                    ext2 = JSONUtil.createObj();
+                }
+                ext2.set("type", bo.getType());
+                add.setExt2(ext2.toString());
+            }
+
             add.setExt1(JSONUtil.toJsonStr(params));
             baseMapper.updateById(add);
 

+ 117 - 0
ruoyi-modules/ruoyi-dzbc/src/main/java/org/dromara/system/service/impl/TblDzbcPathSiteServiceImpl.java

@@ -0,0 +1,117 @@
+package org.dromara.system.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import lombok.RequiredArgsConstructor;
+import org.dromara.common.core.utils.MapstructUtils;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.system.domain.TblDzbcPathSite;
+import org.dromara.system.domain.bo.TblDzbcPathSiteBo;
+import org.dromara.system.domain.vo.TblDzbcPathSiteVo;
+import org.dromara.system.domain.vo.TblDzbcSiteVo;
+import org.dromara.system.mapper.TblDzbcPathSiteMapper;
+import org.dromara.system.mapper.TblDzbcSiteMapper;
+import org.dromara.system.service.ITblDzbcPathSiteService;
+import org.springframework.stereotype.Service;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+@RequiredArgsConstructor
+@Service
+public class TblDzbcPathSiteServiceImpl implements ITblDzbcPathSiteService {
+
+    private final TblDzbcPathSiteMapper baseMapper;
+    private final TblDzbcSiteMapper siteMapper;
+
+    @Override
+    public TblDzbcPathSiteVo queryById(Long id) {
+        TblDzbcPathSiteVo vo = baseMapper.selectVoById(id);
+        if (vo != null && vo.getSiteId() != null) {
+            var site = siteMapper.selectVoById(vo.getSiteId());
+            if (site != null) {
+                vo.setSiteName(site.getName());
+                vo.setSiteAddr(site.getAddr());
+            }
+        }
+        return vo;
+    }
+
+    @Override
+    public TableDataInfo<TblDzbcPathSiteVo> queryPageList(TblDzbcPathSiteBo bo, PageQuery pageQuery) {
+        LambdaQueryWrapper<TblDzbcPathSite> lqw = buildQueryWrapper(bo);
+        Page<TblDzbcPathSiteVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
+        fillSiteInfo(result.getRecords());
+        return TableDataInfo.build(result);
+    }
+
+    @Override
+    public List<TblDzbcPathSiteVo> queryList(TblDzbcPathSiteBo bo) {
+        LambdaQueryWrapper<TblDzbcPathSite> lqw = buildQueryWrapper(bo);
+        List<TblDzbcPathSiteVo> list = baseMapper.selectVoList(lqw);
+        fillSiteInfo(list);
+        return list;
+    }
+
+    @Override
+    public List<TblDzbcPathSiteVo> queryByPathId(Long pathId) {
+        LambdaQueryWrapper<TblDzbcPathSite> lqw = Wrappers.lambdaQuery();
+        lqw.eq(TblDzbcPathSite::getPathId, pathId);
+        lqw.orderByAsc(TblDzbcPathSite::getSiteOrder);
+        List<TblDzbcPathSiteVo> list = baseMapper.selectVoList(lqw);
+        fillSiteInfo(list);
+        return list;
+    }
+
+    @Override
+    public Boolean insertByBo(TblDzbcPathSiteBo bo) {
+        TblDzbcPathSite add = MapstructUtils.convert(bo, TblDzbcPathSite.class);
+        validEntityBeforeSave(add);
+        return baseMapper.insert(add) > 0;
+    }
+
+    @Override
+    public Boolean updateByBo(TblDzbcPathSiteBo bo) {
+        TblDzbcPathSite update = MapstructUtils.convert(bo, TblDzbcPathSite.class);
+        validEntityBeforeSave(update);
+        return baseMapper.updateById(update) > 0;
+    }
+
+    @Override
+    public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
+        if (isValid) {
+        }
+        return baseMapper.deleteBatchIds(ids) > 0;
+    }
+
+    private LambdaQueryWrapper<TblDzbcPathSite> buildQueryWrapper(TblDzbcPathSiteBo bo) {
+        LambdaQueryWrapper<TblDzbcPathSite> lqw = Wrappers.lambdaQuery();
+        lqw.eq(bo.getPathId() != null, TblDzbcPathSite::getPathId, bo.getPathId());
+        lqw.eq(bo.getSiteId() != null, TblDzbcPathSite::getSiteId, bo.getSiteId());
+        lqw.orderByAsc(TblDzbcPathSite::getSiteOrder);
+        return lqw;
+    }
+
+    private void validEntityBeforeSave(TblDzbcPathSite entity) {
+    }
+
+    private void fillSiteInfo(List<TblDzbcPathSiteVo> list) {
+        if (list == null || list.isEmpty()) {
+            return;
+        }
+        List<Long> siteIds = list.stream().map(TblDzbcPathSiteVo::getSiteId).distinct().collect(Collectors.toList());
+        Map<Long, TblDzbcSiteVo> siteMap = siteMapper.selectVoBatchIds(siteIds, TblDzbcSiteVo.class).stream()
+            .collect(Collectors.toMap(TblDzbcSiteVo::getId, s -> s));
+        list.forEach(vo -> {
+            TblDzbcSiteVo site = siteMap.get(vo.getSiteId());
+            if (site != null) {
+                vo.setSiteName(site.getName());
+                vo.setSiteAddr(site.getAddr());
+            }
+        });
+    }
+}

+ 111 - 0
ruoyi-modules/ruoyi-dzbc/src/main/java/org/dromara/system/service/impl/TblDzbcSitePriceServiceImpl.java

@@ -0,0 +1,111 @@
+package org.dromara.system.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import lombok.RequiredArgsConstructor;
+import org.dromara.common.core.utils.MapstructUtils;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.system.domain.TblDzbcSitePrice;
+import org.dromara.system.domain.bo.TblDzbcSitePriceBo;
+import org.dromara.system.domain.vo.TblDzbcSitePriceVo;
+import org.dromara.system.domain.vo.TblDzbcSiteVo;
+import org.dromara.system.mapper.TblDzbcSitePriceMapper;
+import org.dromara.system.mapper.TblDzbcSiteMapper;
+import org.dromara.system.service.ITblDzbcSitePriceService;
+import org.springframework.stereotype.Service;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+@RequiredArgsConstructor
+@Service
+public class TblDzbcSitePriceServiceImpl implements ITblDzbcSitePriceService {
+
+    private final TblDzbcSitePriceMapper baseMapper;
+    private final TblDzbcSiteMapper siteMapper;
+
+    @Override
+    public TblDzbcSitePriceVo queryById(Long id) {
+        TblDzbcSitePriceVo vo = baseMapper.selectVoById(id);
+        if (vo != null) {
+            fillSiteInfo(vo);
+        }
+        return vo;
+    }
+
+    @Override
+    public TableDataInfo<TblDzbcSitePriceVo> queryPageList(TblDzbcSitePriceBo bo, PageQuery pageQuery) {
+        LambdaQueryWrapper<TblDzbcSitePrice> lqw = buildQueryWrapper(bo);
+        Page<TblDzbcSitePriceVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
+        result.getRecords().forEach(this::fillSiteInfo);
+        return TableDataInfo.build(result);
+    }
+
+    @Override
+    public List<TblDzbcSitePriceVo> queryList(TblDzbcSitePriceBo bo) {
+        LambdaQueryWrapper<TblDzbcSitePrice> lqw = buildQueryWrapper(bo);
+        List<TblDzbcSitePriceVo> list = baseMapper.selectVoList(lqw);
+        list.forEach(this::fillSiteInfo);
+        return list;
+    }
+
+    @Override
+    public List<TblDzbcSitePriceVo> queryByPathId(Long pathId) {
+        LambdaQueryWrapper<TblDzbcSitePrice> lqw = Wrappers.lambdaQuery();
+        lqw.eq(TblDzbcSitePrice::getPathId, pathId);
+        List<TblDzbcSitePriceVo> list = baseMapper.selectVoList(lqw);
+        list.forEach(this::fillSiteInfo);
+        return list;
+    }
+
+    @Override
+    public Boolean insertByBo(TblDzbcSitePriceBo bo) {
+        TblDzbcSitePrice add = MapstructUtils.convert(bo, TblDzbcSitePrice.class);
+        validEntityBeforeSave(add);
+        return baseMapper.insert(add) > 0;
+    }
+
+    @Override
+    public Boolean updateByBo(TblDzbcSitePriceBo bo) {
+        TblDzbcSitePrice update = MapstructUtils.convert(bo, TblDzbcSitePrice.class);
+        validEntityBeforeSave(update);
+        return baseMapper.updateById(update) > 0;
+    }
+
+    @Override
+    public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
+        if (isValid) {
+        }
+        return baseMapper.deleteBatchIds(ids) > 0;
+    }
+
+    private LambdaQueryWrapper<TblDzbcSitePrice> buildQueryWrapper(TblDzbcSitePriceBo bo) {
+        LambdaQueryWrapper<TblDzbcSitePrice> lqw = Wrappers.lambdaQuery();
+        lqw.eq(bo.getPathId() != null, TblDzbcSitePrice::getPathId, bo.getPathId());
+        lqw.eq(bo.getFromSiteId() != null, TblDzbcSitePrice::getFromSiteId, bo.getFromSiteId());
+        lqw.eq(bo.getToSiteId() != null, TblDzbcSitePrice::getToSiteId, bo.getToSiteId());
+        return lqw;
+    }
+
+    private void validEntityBeforeSave(TblDzbcSitePrice entity) {
+    }
+
+    private void fillSiteInfo(TblDzbcSitePriceVo vo) {
+        if (vo.getFromSiteId() != null) {
+            TblDzbcSiteVo fromSite = siteMapper.selectVoById(vo.getFromSiteId());
+            if (fromSite != null) {
+                vo.setFromSiteName(fromSite.getName());
+            }
+        }
+        if (vo.getToSiteId() != null) {
+            TblDzbcSiteVo toSite = siteMapper.selectVoById(vo.getToSiteId());
+            if (toSite != null) {
+                vo.setToSiteName(toSite.getName());
+            }
+        }
+    }
+}

+ 102 - 0
ruoyi-modules/ruoyi-dzbc/src/main/java/org/dromara/system/service/impl/WeatherServiceImpl.java

@@ -0,0 +1,102 @@
+package org.dromara.system.service.impl;
+
+import cn.hutool.core.date.DateUtil;
+import cn.hutool.http.HttpUtil;
+import cn.hutool.json.JSONObject;
+import cn.hutool.json.JSONUtil;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.dromara.common.redis.utils.RedisUtils;
+import org.dromara.system.domain.vo.WeatherVo;
+import org.dromara.system.service.IWeatherService;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Service;
+
+import java.time.Duration;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.util.ArrayList;
+import java.util.List;
+
+@Slf4j
+@Service
+@RequiredArgsConstructor
+public class WeatherServiceImpl implements IWeatherService {
+
+    private static final String FUZHOU_LOCATION = "101210101";
+    private static final String WEATHER_API_URL = "https://p26hexq5xk.yun.qweatherapi.com/v7/weather/7d";
+    private static final String REDIS_KEY_PREFIX = "weather:fuzhou:";
+    private static final String REDIS_KEY = REDIS_KEY_PREFIX + FUZHOU_LOCATION;
+
+    @Value("${qweather.apiKey}")
+    private String apiKey;
+
+    @Override
+    public WeatherVo getFuzhouWeather() {
+        try {
+            WeatherVo cachedWeather = RedisUtils.getCacheObject(REDIS_KEY);
+            if (cachedWeather != null) {
+                log.info("从 Redis 获取福州天气数据");
+                return cachedWeather;
+            }
+
+            log.info("调用和风天气 API 获取福州天气数据");
+            WeatherVo weather = fetchWeatherFromApi();
+            if (weather != null) {
+                long expireTime = calculateExpireTime();
+                RedisUtils.setCacheObject(REDIS_KEY, weather, Duration.ofMillis(expireTime));
+                log.info("福州天气数据已缓存,过期时间:{} 毫秒", expireTime);
+            }
+            return weather;
+        } catch (Exception e) {
+            log.error("获取福州天气失败", e);
+            return null;
+        }
+    }
+
+    private WeatherVo fetchWeatherFromApi() {
+        try {
+            String url = WEATHER_API_URL + "?location=" + FUZHOU_LOCATION + "&key=" + apiKey;
+            String response = HttpUtil.get(url, 30000);
+            JSONObject jsonResponse = JSONUtil.parseObj(response);
+
+            if (jsonResponse.getInt("code") != 200) {
+                log.error("和风天气 API 返回错误:{}", jsonResponse.getStr("message"));
+                return null;
+            }
+
+            WeatherVo weather = new WeatherVo();
+            weather.setLocation("福州");
+            weather.setFxLink(jsonResponse.getStr("fxLink"));
+
+            List<JSONObject> dailyJsonList = jsonResponse.getBeanList("daily", JSONObject.class);
+            List<WeatherVo.DayWeather> dayWeatherList = new ArrayList<>();
+
+            for (JSONObject dayJson : dailyJsonList) {
+                WeatherVo.DayWeather dayWeather = new WeatherVo.DayWeather();
+                dayWeather.setFxDate(dayJson.getStr("fxDate"));
+                dayWeather.setTempMax(dayJson.getStr("tempMax"));
+                dayWeather.setTempMin(dayJson.getStr("tempMin"));
+                dayWeather.setIconDay(dayJson.getStr("iconDay"));
+                dayWeather.setTextDay(dayJson.getStr("textDay"));
+                dayWeather.setWindDirDay(dayJson.getStr("windDirDay"));
+                dayWeather.setWindScaleDay(dayJson.getStr("windScaleDay"));
+                dayWeather.setHumidity(dayJson.getStr("humidity"));
+                dayWeatherList.add(dayWeather);
+            }
+            weather.setDaily(dayWeatherList);
+
+            return weather;
+        } catch (Exception e) {
+            log.error("调用和风天气 API 失败", e);
+            return null;
+        }
+    }
+
+    private long calculateExpireTime() {
+        LocalDateTime now = LocalDateTime.now();
+        LocalDateTime tomorrowMidnight = LocalDate.now().plusDays(1).atTime(LocalTime.MIDNIGHT);
+        return Duration.between(now, tomorrowMidnight).toMillis();
+    }
+}

+ 1 - 0
ruoyi-modules/ruoyi-dzbc/src/main/java/org/dromara/system/util/ScheduleCarUtil.java

@@ -42,6 +42,7 @@ public class ScheduleCarUtil {
             bo1.setMtime(vo.getMtime());
             bo1.setPathId(vo.getPathId());
             bo1.setExt1(vo.getExt1());
+            bo1.setExt2(vo.getExt2());
             bo1.setCarId(vo.getCarId());
             bo1.setPprice(vo.getPprice());
             bo1.setIsauto(1);

+ 70 - 0
ruoyi-modules/ruoyi-dzbc/src/main/resources/mapper/system/TblDzbcFeedbackMapper.xml

@@ -0,0 +1,70 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="org.dromara.system.mapper.TblDzbcFeedbackMapper">
+
+    <resultMap type="org.dromara.system.domain.vo.TblDzbcFeedbackVo" id="TblDzbcFeedbackResult">
+        <result property="id"    column="id"    />
+        <result property="userId"    column="user_id"    />
+        <result property="userName"    column="user_name"    />
+        <result property="userPhone"    column="user_phone"    />
+        <result property="content"    column="content"    />
+        <result property="imageUrls"    column="image_urls"    />
+        <result property="type"    column="type"    />
+        <result property="status"    column="status"    />
+        <result property="replyContent"    column="reply_content"    />
+        <result property="replyUserId"    column="reply_user_id"    />
+        <result property="replyUserName"    column="reply_user_name"    />
+        <result property="replyTime"    column="reply_time"    />
+        <result property="remark"    column="remark"    />
+        <result property="ext1"    column="ext1"    />
+        <result property="ext2"    column="ext2"    />
+    </resultMap>
+
+    <select id="selectVoList" parameterType="org.dromara.system.domain.vo.TblDzbcFeedbackVo" resultMap="TblDzbcFeedbackResult">
+        select id, user_id, user_name, user_phone, content, image_urls, type, status, reply_content, reply_user_id, reply_user_name, reply_time, remark, ext1, ext2
+        from tbl_dzbc_feedback
+        <where>
+            <if test="userId != null">
+                and user_id = #{userId}
+            </if>
+            <if test="userName != null and userName != ''">
+                and user_name like concat('%', #{userName}, '%')
+            </if>
+            <if test="content != null and content != ''">
+                and content like concat('%', #{content}, '%')
+            </if>
+            <if test="type != null">
+                and type = #{type}
+            </if>
+            <if test="status != null">
+                and status = #{status}
+            </if>
+        </where>
+        order by create_time desc
+    </select>
+
+    <select id="selectVoById" parameterType="Long" resultMap="TblDzbcFeedbackResult">
+        select id, user_id, user_name, user_phone, content, image_urls, type, status, reply_content, reply_user_id, reply_user_name, reply_time, remark, ext1, ext2
+        from tbl_dzbc_feedback
+        where id = #{id}
+    </select>
+
+    <select id="selectVoByIds" parameterType="Collection" resultMap="TblDzbcFeedbackResult">
+        select id, user_id, user_name, user_phone, content, image_urls, type, status, reply_content, reply_user_id, reply_user_name, reply_time, remark, ext1, ext2
+        from tbl_dzbc_feedback
+        where id in
+        <foreach item="id" collection="ids" open="(" separator="," close=")">
+            #{id}
+        </foreach>
+    </select>
+
+    <select id="selectByUserId" parameterType="Long" resultMap="TblDzbcFeedbackResult">
+        select id, user_id, user_name, user_phone, content, image_urls, type, status, reply_content, reply_user_id, reply_user_name, reply_time, remark, ext1, ext2
+        from tbl_dzbc_feedback
+        where user_id = #{userId}
+        order by create_time desc
+    </select>
+
+</mapper>

+ 45 - 0
ruoyi-modules/ruoyi-dzbc/src/main/resources/mapper/system/TblDzbcMultiSitePathMapper.xml

@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="org.dromara.system.mapper.TblDzbcMultiSitePathMapper">
+
+    <resultMap type="org.dromara.system.domain.vo.TblDzbcMultiSitePathVo" id="TblDzbcMultiSitePathResult">
+        <result property="id"    column="id"    />
+        <result property="name"    column="name"    />
+        <result property="status"    column="status"    />
+        <result property="remark"    column="remark"    />
+        <result property="ext1"    column="ext1"    />
+        <result property="ext2"    column="ext2"    />
+    </resultMap>
+
+    <select id="selectVoList" parameterType="org.dromara.system.domain.vo.TblDzbcMultiSitePathVo" resultMap="TblDzbcMultiSitePathResult">
+        select id, name, status, remark, ext1, ext2
+        from tbl_dzbc_multi_site_path
+        <where>
+            <if test="name != null and name != ''">
+                and name like concat('%', #{name}, '%')
+            </if>
+            <if test="status != null">
+                and status = #{status}
+            </if>
+        </where>
+        order by id desc
+    </select>
+
+    <select id="selectVoById" parameterType="Long" resultMap="TblDzbcMultiSitePathResult">
+        select id, name, status, remark, ext1, ext2
+        from tbl_dzbc_multi_site_path
+        where id = #{id}
+    </select>
+
+    <select id="selectVoByIds" parameterType="Collection" resultMap="TblDzbcMultiSitePathResult">
+        select id, name, status, remark, ext1, ext2
+        from tbl_dzbc_multi_site_path
+        where id in
+        <foreach item="id" collection="ids" open="(" separator="," close=")">
+            #{id}
+        </foreach>
+    </select>
+
+</mapper>

+ 53 - 0
ruoyi-modules/ruoyi-dzbc/src/main/resources/mapper/system/TblDzbcPathSiteMapper.xml

@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="org.dromara.system.mapper.TblDzbcPathSiteMapper">
+
+    <resultMap type="org.dromara.system.domain.vo.TblDzbcPathSiteVo" id="TblDzbcPathSiteResult">
+        <result property="id"    column="id"    />
+        <result property="pathId"    column="path_id"    />
+        <result property="siteId"    column="site_id"    />
+        <result property="siteOrder"    column="site_order"    />
+        <result property="remark"    column="remark"    />
+        <result property="ext1"    column="ext1"    />
+        <result property="ext2"    column="ext2"    />
+    </resultMap>
+
+    <select id="selectVoList" parameterType="org.dromara.system.domain.vo.TblDzbcPathSiteVo" resultMap="TblDzbcPathSiteResult">
+        select id, path_id, site_id, site_order, remark, ext1, ext2
+        from tbl_dzbc_path_site
+        <where>
+            <if test="pathId != null">
+                and path_id = #{pathId}
+            </if>
+            <if test="siteId != null">
+                and site_id = #{siteId}
+            </if>
+        </where>
+        order by site_order asc
+    </select>
+
+    <select id="selectVoById" parameterType="Long" resultMap="TblDzbcPathSiteResult">
+        select id, path_id, site_id, site_order, remark, ext1, ext2
+        from tbl_dzbc_path_site
+        where id = #{id}
+    </select>
+
+    <select id="selectVoByIds" parameterType="Collection" resultMap="TblDzbcPathSiteResult">
+        select id, path_id, site_id, site_order, remark, ext1, ext2
+        from tbl_dzbc_path_site
+        where id in
+        <foreach item="id" collection="ids" open="(" separator="," close=")">
+            #{id}
+        </foreach>
+    </select>
+
+    <select id="selectByPathId" parameterType="Long" resultMap="TblDzbcPathSiteResult">
+        select id, path_id, site_id, site_order, remark, ext1, ext2
+        from tbl_dzbc_path_site
+        where path_id = #{pathId}
+        order by site_order asc
+    </select>
+
+</mapper>

+ 57 - 0
ruoyi-modules/ruoyi-dzbc/src/main/resources/mapper/system/TblDzbcSitePriceMapper.xml

@@ -0,0 +1,57 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="org.dromara.system.mapper.TblDzbcSitePriceMapper">
+
+    <resultMap type="org.dromara.system.domain.vo.TblDzbcSitePriceVo" id="TblDzbcSitePriceResult">
+        <result property="id"    column="id"    />
+        <result property="pathId"    column="path_id"    />
+        <result property="fromSiteId"    column="from_site_id"    />
+        <result property="toSiteId"    column="to_site_id"    />
+        <result property="price"    column="price"    />
+        <result property="remark"    column="remark"    />
+        <result property="ext1"    column="ext1"    />
+        <result property="ext2"    column="ext2"    />
+    </resultMap>
+
+    <select id="selectVoList" parameterType="org.dromara.system.domain.vo.TblDzbcSitePriceVo" resultMap="TblDzbcSitePriceResult">
+        select id, path_id, from_site_id, to_site_id, price, remark, ext1, ext2
+        from tbl_dzbc_site_price
+        <where>
+            <if test="pathId != null">
+                and path_id = #{pathId}
+            </if>
+            <if test="fromSiteId != null">
+                and from_site_id = #{fromSiteId}
+            </if>
+            <if test="toSiteId != null">
+                and to_site_id = #{toSiteId}
+            </if>
+        </where>
+        order by id desc
+    </select>
+
+    <select id="selectVoById" parameterType="Long" resultMap="TblDzbcSitePriceResult">
+        select id, path_id, from_site_id, to_site_id, price, remark, ext1, ext2
+        from tbl_dzbc_site_price
+        where id = #{id}
+    </select>
+
+    <select id="selectVoByIds" parameterType="Collection" resultMap="TblDzbcSitePriceResult">
+        select id, path_id, from_site_id, to_site_id, price, remark, ext1, ext2
+        from tbl_dzbc_site_price
+        where id in
+        <foreach item="id" collection="ids" open="(" separator="," close=")">
+            #{id}
+        </foreach>
+    </select>
+
+    <select id="selectByPathId" parameterType="Long" resultMap="TblDzbcSitePriceResult">
+        select id, path_id, from_site_id, to_site_id, price, remark, ext1, ext2
+        from tbl_dzbc_site_price
+        where path_id = #{pathId}
+        order by id desc
+    </select>
+
+</mapper>

+ 483 - 0
script/sql/CARGO_README.md

@@ -0,0 +1,483 @@
+# 寄车带货功能接口文档
+
+## 功能概述
+寄车带货功能基于固定多站点路线,司机发车时生成专属二维码,用户扫码后可直接填写起始站点创建寄货订单,司机端实时查看货物状态。
+
+## 数据库表结构
+
+### 1. 司机发车任务表 (tbl_dzbc_driver_trip)
+- id: 编号
+- trip_no: 任务编号(唯一)
+- path_id: 路线ID
+- path_name: 路线名称
+- driver_id: 司机ID
+- driver_name: 司机姓名
+- driver_phone: 司机电话
+- car_id: 车辆ID
+- car_plate: 车牌号
+- start_time: 出发时间
+- end_time: 到达时间
+- status: 状态(0-待发车,1-运行中,2-已完成,3-已取消)
+- qr_code: 二维码内容
+- current_site_id: 当前站点ID
+- current_site_name: 当前站点名称
+- cargo_count: 货物数量
+- cargo_total_price: 货物总价(分)
+- remark: 备注
+- ext1, ext2: 扩展字段
+
+### 2. 货物订单表 (tbl_dzbc_cargo_order)
+- id: 编号
+- order_no: 订单编号(唯一)
+- trip_id: 司机任务ID
+- trip_no: 任务编号
+- user_id: 用户ID
+- user_name: 用户姓名
+- user_phone: 用户电话
+- sender_name: 发货人姓名
+- sender_phone: 发货人电话
+- receiver_name: 收货人姓名
+- receiver_phone: 收货人电话
+- from_site_id: 起始站点ID
+- from_site_name: 起始站点名称
+- to_site_id: 送达站点ID
+- to_site_name: 送达站点名称
+- price: 价格(分)
+- pay_status: 支付状态(0-待支付,1-已支付)
+- pay_time: 支付时间
+- pay_trade_no: 支付交易号
+- status: 订单状态(0-待发货,1-已取货,2-运输中,3-已送达,4-已取消)
+- cargo_count: 货物数量
+- cargo_weight: 货物重量
+- cargo_value: 货物价值
+- remark: 备注
+- ext1, ext2: 扩展字段
+
+### 3. 货物详情表 (tbl_dzbc_cargo_item)
+- id: 编号
+- order_id: 订单ID
+- order_no: 订单编号
+- cargo_name: 货物名称
+- cargo_type: 货物类型
+- quantity: 数量
+- unit: 单位
+- weight: 重量
+- length: 长度
+- width: 宽度
+- height: 高度
+- value: 价值
+- image_urls: 图片地址
+- remark: 备注
+- ext1, ext2: 扩展字段
+
+## 业务流程
+
+### 司机端流程
+
+1. **创建发车任务**
+   - 司机选择路线、车辆
+   - 设置出发时间
+   - 生成唯一任务编号和二维码
+
+2. **开始发车**
+   - 更新任务状态为"运行中"
+   - 二维码开始有效
+
+3. **到达站点**
+   - 更新当前站点
+   - 标记该站点的订单为"已取货"
+
+4. **完成任务**
+   - 更新任务状态为"已完成"
+   - 二维码失效
+
+### 用户端流程
+
+1. **扫码下单**
+   - 扫描司机生成的二维码
+   - 自动获取任务信息(路线、站点列表)
+
+2. **填写订单信息**
+   - 选择起始站点、送达站点(从路线站点中选择)
+   - 填写发货人、收货人信息
+   - 填写货物详情(名称、数量、重量等)
+   - 上传货物图片
+
+3. **确认价格**
+   - 系统根据站点价格自动计算费用
+   - 用户确认并支付
+
+4. **等待取货和配送**
+   - 实时查看订单状态
+   - 司机到达起始站点后标记为"已取货"
+   - 货物到达目的地后标记为"已送达"
+
+## 接口列表
+
+### 司机任务管理
+
+#### 1. 创建发车任务
+```
+POST /system/dzbc/driverTrip
+```
+
+请求体:
+```json
+{
+  "pathId": 1,
+  "driverId": 10,
+  "carId": 1,
+  "startTime": "2024-02-20 08:00:00"
+}
+```
+
+返回:
+```json
+{
+  "code": 200,
+  "msg": "操作成功",
+  "data": {
+    "id": 1,
+    "tripNo": "TRIP20240220080001234",
+    "pathId": 1,
+    "pathName": "福州市区环线",
+    "driverId": 10,
+    "driverName": "张司机",
+    "driverPhone": "13800138000",
+    "carId": 1,
+    "carPlate": "闽A12345",
+    "startTime": "2024-02-20 08:00:00",
+    "status": 0,
+    "qrCode": "https://xxx.com/qr/TRIP20240220080001234",
+    "cargoCount": 0,
+    "cargoTotalPrice": 0,
+    "sites": [
+      {
+        "id": 1,
+        "siteId": 1,
+        "siteName": "火车站",
+        "siteOrder": 1
+      },
+      {
+        "id": 2,
+        "siteId": 2,
+        "siteName": "市政府",
+        "siteOrder": 2
+      },
+      {
+        "id": 3,
+        "siteId": 3,
+        "siteName": "仓山万达",
+        "siteOrder": 3
+      }
+    ]
+  }
+}
+```
+
+#### 2. 开始发车
+```
+POST /system/dzbc/driverTrip/start/{id}
+```
+
+返回:
+```json
+{
+  "code": 200,
+  "msg": "操作成功"
+}
+```
+
+#### 3. 到达站点
+```
+POST /system/dzbc/driverTrip/arrive/{id}?siteId=2
+```
+
+返回:
+```json
+{
+  "code": 200,
+  "msg": "操作成功"
+}
+```
+
+#### 4. 完成任务
+```
+POST /system/dzbc/driverTrip/complete/{id}
+```
+
+#### 5. 取消任务
+```
+POST /system/dzbc/driverTrip/cancel/{id}
+```
+
+#### 6. 查询任务列表
+```
+GET /system/dzbc/driverTrip/list
+```
+
+查询参数:
+- pathId: 路线ID
+- driverId: 司机ID
+- tripNo: 任务编号(模糊查询)
+- status: 状态
+- pageNum: 页码
+- pageSize: 每页大小
+
+#### 7. 查询任务详情
+```
+GET /system/dzbc/driverTrip/{id}
+```
+
+#### 8. 根据二维码查询任务(用户扫码)
+```
+GET /system/dzbc/driverTrip/qr/{tripNo}
+```
+
+说明:用户扫描二维码后,前端获取 tripNo 调用此接口,返回任务信息和路线站点列表。
+
+返回示例:
+```json
+{
+  "code": 200,
+  "data": {
+    "id": 1,
+    "tripNo": "TRIP20240220080001234",
+    "pathId": 1,
+    "pathName": "福州市区环线",
+    "driverId": 10,
+    "driverName": "张司机",
+    "driverPhone": "13800138000",
+    "carId": 1,
+    "carPlate": "闽A12345",
+    "startTime": "2024-02-20 08:00:00",
+    "status": 1,
+    "currentSiteId": 1,
+    "currentSiteName": "火车站",
+    "cargoCount": 5,
+    "cargoTotalPrice": 5000,
+    "sites": [
+      {
+        "id": 1,
+        "siteId": 1,
+        "siteName": "火车站",
+        "siteOrder": 1
+      },
+      {
+        "id": 2,
+        "siteId": 2,
+        "siteName": "市政府",
+        "siteOrder": 2
+      },
+      {
+        "id": 3,
+        "siteId": 3,
+        "siteName": "仓山万达",
+        "siteOrder": 3
+      }
+    ],
+    "cargoOrders": [
+      {
+        "id": 1,
+        "orderNo": "CARGO20240220081500001",
+        "fromSiteName": "火车站",
+        "toSiteName": "市政府",
+        "price": 500,
+        "status": 1
+      }
+    ]
+  }
+}
+```
+
+### 货物订单管理
+
+#### 1. 提交货物订单
+```
+POST /system/dzbc/cargoOrder/submit
+```
+
+请求体:
+```json
+{
+  "tripNo": "TRIP20240220080001234",
+  "fromSiteId": 1,
+  "toSiteId": 2,
+  "senderName": "张三",
+  "senderPhone": "13800138001",
+  "receiverName": "李四",
+  "receiverPhone": "13800138002",
+  "items": [
+    {
+      "cargoName": "电脑",
+      "cargoType": "电子产品",
+      "quantity": 1,
+      "unit": "台",
+      "weight": 2.5,
+      "length": 40,
+      "width": 30,
+      "height": 10,
+      "value": 5000,
+      "images": ["http://xxx.com/image1.jpg"],
+      "remark": "小心轻放"
+    }
+  ],
+  "remark": "请尽快送达"
+}
+```
+
+返回:
+```json
+{
+  "code": 200,
+  "data": {
+    "id": 1,
+    "orderNo": "CARGO20240220081500001",
+    "tripId": 1,
+    "tripNo": "TRIP20240220080001234",
+    "userId": 1,
+    "userName": "王五",
+    "userPhone": "13800138003",
+    "senderName": "张三",
+    "senderPhone": "13800138001",
+    "receiverName": "李四",
+    "receiverPhone": "13800138002",
+    "fromSiteId": 1,
+    "fromSiteName": "火车站",
+    "toSiteId": 2,
+    "toSiteName": "市政府",
+    "price": 500,
+    "payStatus": 0,
+    "status": 0,
+    "cargoCount": 1,
+    "cargoWeight": 2.5,
+    "cargoValue": 5000,
+    "items": [...]
+  }
+}
+```
+
+#### 2. 查询我的订单
+```
+GET /system/dzbc/cargoOrder/my/list
+```
+
+说明:获取当前登录用户的货物订单列表。
+
+#### 3. 查询任务的订单列表
+```
+GET /system/dzbc/cargoOrder/trip/{tripId}
+```
+
+说明:司机端查看某个任务的所有货物订单。
+
+#### 4. 更新订单状态
+```
+POST /system/dzbc/cargoOrder/status/{id}?status=1
+```
+
+状态说明:
+- 0: 待发货
+- 1: 已取货(司机到达起始站点)
+- 2: 运输中(货物运输中)
+- 3: 已送达(到达目的地)
+
+#### 5. 查询订单列表(管理员/司机)
+```
+GET /system/dzbc/cargoOrder/list
+```
+
+查询参数:
+- tripId: 任务ID
+- userId: 用户ID
+- status: 订单状态
+- payStatus: 支付状态
+- pageNum: 页码
+- pageSize: 每页大小
+
+#### 6. 查询订单详情
+```
+GET /system/dzbc/cargoOrder/{id}
+```
+
+## 价格计算规则
+
+价格从 `tbl_dzbc_site_price` 表中查询,规则如下:
+
+1. **查询条件**:
+   - pathId: 任务对应的路线ID
+   - fromSiteId: 用户选择的起始站点ID
+   - toSiteId: 用户选择的送达站点ID
+
+2. **返回结果**:
+   - 如果找到匹配记录,直接使用该价格
+   - 如果没有找到,提示用户无法配送该线路
+
+3. **单位说明**:
+   - 数据库存储的价格单位为"分"
+   - 前端显示时转换为"元"
+
+## 订单状态流转
+
+```
+待发货 (0)
+    ↓ [司机到达起始站点,取货]
+已取货 (1)
+    ↓ [开始运输]
+运输中 (2)
+    ↓ [到达目的地]
+已送达 (3)
+    ↓
+已取消 (4) [用户或管理员取消]
+```
+
+## 二维码说明
+
+1. **生成时机**:创建发车任务时自动生成
+2. **内容格式**:`https://xxx.com/qr/{tripNo}` 或直接使用 `tripNo`
+3. **有效期**:从"开始发车"到"完成任务"期间有效
+4. **使用方式**:
+   - 前端生成二维码图片供司机展示
+   - 用户扫码后获取 `tripNo`
+   - 调用 `GET /system/dzbc/driverTrip/qr/{tripNo}` 获取任务信息
+
+## 司机端显示内容
+
+司机端需要实时显示:
+1. 当前任务信息(路线、车辆、状态)
+2. 当前站点
+3. 货物订单列表:
+   - 订单编号
+   - 起始站点 → 送达站点
+   - 发货人、收货人信息
+   - 订单状态
+   - 货物数量、重量
+4. 统计信息:
+   - 总订单数
+   - 总金额
+   - 各站点待取货数量
+
+## 注意事项
+
+1. **站点限制**:
+   - 用户只能从任务对应的路线站点中选择
+   - 不能选择路线外的站点
+
+2. **时间限制**:
+   - 只能在"运行中"状态下扫码下单
+   - "待发车"和"已完成"状态不能下单
+
+3. **价格设置**:
+   - 需要预先在 `tbl_dzbc_site_price` 表中设置价格
+   - 建议为任意两个站点都设置价格
+   - 包括相邻站点和跨站点
+
+4. **图片上传**:
+   - 支持上传货物图片
+   - 图片存储到 OSS
+   - 多个图片用逗号分隔存储
+
+5. **订单限制**:
+   - 同一用户可以提交多个订单
+   - 订单取消后不能恢复
+

+ 272 - 0
script/sql/CARGO_SUMMARY.md

@@ -0,0 +1,272 @@
+# 寄车带货功能实现总结
+
+## 已完成的文件
+
+### 1. 数据库表
+- `script/sql/cargo.sql` - 建表 SQL(3张表)
+  - tbl_dzbc_driver_trip(司机发车任务表)
+  - tbl_dzbc_cargo_order(货物订单表)
+  - tbl_dzbc_cargo_item(货物详情表)
+
+### 2. 实体类
+- `TblDzbcDriverTrip.java` - 司机任务实体
+- `TblDzbcCargoOrder.java` - 货物订单实体
+- `TblDzbcCargoItem.java` - 货物详情实体
+
+### 3. 业务对象
+- `TblDzbcDriverTripBo.java` - 司机任务业务对象
+- `CargoOrderSubmitBo.java` - 订单提交请求对象
+- `CargoItemBo.java` - 货物项业务对象
+
+### 4. 视图对象
+- `TblDzbcDriverTripVo.java` - 司机任务视图对象
+- `TblDzbcCargoOrderVo.java` - 货物订单视图对象
+- `TblDzbcCargoItemVo.java` - 货物详情视图对象
+
+### 5. Mapper 接口
+- `TblDzbcDriverTripMapper.java` - 司机任务 Mapper
+- `TblDzbcCargoOrderMapper.java` - 货物订单 Mapper
+- `TblDzbcCargoItemMapper.java` - 货物详情 Mapper
+
+### 6. Service 接口
+- `ITblDzbcDriverTripService.java` - 司机任务服务接口
+- `ITblDzbcCargoOrderService.java` - 货物订单服务接口
+- `ITblDzbcCargoItemService.java` - 货物详情服务接口
+
+### 7. Service 实现
+- `TblDzbcDriverTripServiceImpl.java` - 司机任务服务实现(已完成)
+
+### 8. Controller
+- `TblDzbcDriverTripController.java` - 司机任务控制器
+- `TblDzbcCargoOrderController.java` - 货物订单控制器
+
+### 9. 文档
+- `script/sql/CARGO_README.md` - 完整接口文档
+
+## 需要补充的代码
+
+由于代码量较大,以下 Service 实现类可以根据需要补充:
+
+### 1. TblDzbcCargoOrderServiceImpl
+
+核心功能:
+- 提交订单(计算价格、保存订单和货物详情)
+- 更新订单状态
+- 查询订单列表
+
+### 2. TblDzbcCargoItemServiceImpl
+
+核心功能:
+- 保存货物详情(支持多图片上传)
+- 查询订单的货物列表
+- 删除货物详情
+
+### 3. Mapper XML 文件
+
+需要创建的 XML 文件:
+- `TblDzbcDriverTripMapper.xml`
+- `TblDzbcCargoOrderMapper.xml`
+- `TblDzbcCargoItemMapper.xml`
+
+## 主要功能模块
+
+### 司机端功能
+
+1. **创建发车任务**
+   - 选择路线、车辆、司机
+   - 设置出发时间
+   - 生成任务编号和二维码
+
+2. **任务管理**
+   - 开始发车
+   - 到达站点(更新当前站点)
+   - 完成任务
+   - 取消任务
+
+3. **订单查看**
+   - 查看任务的所有货物订单
+   - 实时显示货物状态
+   - 更新订单状态(取货、送达)
+
+### 用户端功能
+
+1. **扫码下单**
+   - 扫描司机二维码
+   - 自动获取任务和站点信息
+   - 从站点列表选择起始、送达站点
+
+2. **填写订单**
+   - 填写发货人、收货人信息
+   - 填写货物详情(名称、数量、重量等)
+   - 上传货物图片
+   - 查看价格
+
+3. **订单管理**
+   - 查看我的订单列表
+   - 查看订单详情
+   - 实时跟踪订单状态
+
+## 数据流
+
+### 1. 订单创建流程
+
+```
+用户扫码
+  ↓
+获取任务信息(tripNo)
+  ↓
+选择站点(fromSiteId, toSiteId)
+  ↓
+填写订单信息
+  ↓
+系统计算价格(从 tbl_dzbc_site_price)
+  ↓
+保存订单(tbl_dzbc_cargo_order)
+  ↓
+保存货物详情(tbl_dzbc_cargo_item)
+  ↓
+返回订单信息
+```
+
+### 2. 司机配送流程
+
+```
+创建任务(status=0)
+  ↓
+开始发车(status=1)← 二维码生效
+  ↓
+到达站点1(更新 currentSiteId)
+  ↓
+取货(更新订单 status=1)
+  ↓
+到达站点2(更新 currentSiteId)
+  ↓
+送货(更新订单 status=2)
+  ↓
+完成任务(status=2)← 二维码失效
+```
+
+## 价格计算
+
+价格存储在 `tbl_dzbc_site_price` 表中:
+
+```sql
+SELECT price 
+FROM tbl_dzbc_site_price 
+WHERE path_id = ? 
+  AND from_site_id = ? 
+  AND to_site_id = ?
+```
+
+如果查询不到价格,提示用户无法配送该线路。
+
+## 二维码实现方案
+
+### 方案一:前端生成二维码
+
+1. 后端返回 `tripNo`
+2. 前端使用二维码库(如 qrcode.js)生成二维码图片
+3. 二维码内容:`https://你的域名/qr/{tripNo}`
+
+### 方案二:后端生成二维码图片
+
+1. 使用 Java 二维码库(如 ZXing)生成二维码图片
+2. 上传到 OSS
+3. 返回图片 URL
+
+## 接口清单
+
+### 司机任务接口
+
+| 接口 | 方法 | 说明 |
+|------|------|------|
+| /driverTrip | POST | 创建发车任务 |
+| /driverTrip/start/{id} | POST | 开始发车 |
+| /driverTrip/arrive/{id} | POST | 到达站点 |
+| /driverTrip/complete/{id} | POST | 完成任务 |
+| /driverTrip/cancel/{id} | POST | 取消任务 |
+| /driverTrip/list | GET | 查询任务列表 |
+| /driverTrip/{id} | GET | 查询任务详情 |
+| /driverTrip/qr/{tripNo} | GET | 扫码查询任务 |
+
+### 货物订单接口
+
+| 接口 | 方法 | 说明 |
+|------|------|------|
+| /cargoOrder/submit | POST | 提交货物订单 |
+| /cargoOrder/my/list | GET | 查询我的订单 |
+| /cargoOrder/trip/{tripId} | GET | 查询任务的订单 |
+| /cargoOrder/list | GET | 查询订单列表 |
+| /cargoOrder/{id} | GET | 查询订单详情 |
+| /cargoOrder/status/{id} | POST | 更新订单状态 |
+
+## 部署步骤
+
+1. **执行建表 SQL**
+   ```bash
+   mysql -u root -p khyznh < script/sql/cargo.sql
+   ```
+
+2. **补充缺失的 Service 实现**
+   - TblDzbcCargoOrderServiceImpl
+   - TblDzbcCargoItemServiceImpl
+
+3. **创建 Mapper XML 文件**
+   - TblDzbcDriverTripMapper.xml
+   - TblDzbcCargoOrderMapper.xml
+   - TblDzbcCargoItemMapper.xml
+
+4. **配置价格**
+   - 在 `tbl_dzbc_site_price` 表中设置路线价格
+   - 为所有可能的站点组合设置价格
+
+5. **测试接口**
+   - 测试创建任务
+   - 测试扫码查询
+   - 测试提交订单
+   - 测试订单状态流转
+
+## 扩展建议
+
+1. **支付功能**
+   - 集成微信支付
+   - 支付后更新订单支付状态
+
+2. **消息通知**
+   - 下单成功通知司机
+   - 订单状态变更通知用户
+   - 到达站点通知
+
+3. **评价功能**
+   - 用户对配送服务进行评价
+   - 评分系统
+
+4. **统计分析**
+   - 订单量统计
+   - 收入统计
+   - 热门线路统计
+
+5. **批量操作**
+   - 批量更新订单状态
+   - 批量打印订单
+
+## 注意事项
+
+1. **依赖关系**
+   - 寄车带货功能依赖多站点路线功能(tbl_dzbc_multi_site_path)
+   - 确保路线和站点已正确配置
+   - 确保站点价格已设置
+
+2. **状态管理**
+   - 严格遵循订单状态流转规则
+   - 不能跳过状态直接变更
+
+3. **数据一致性**
+   - 订单详情与订单主表保持一致
+   - 任务状态与订单状态关联
+
+4. **并发处理**
+   - 防止同一订单重复操作
+   - 使用事务保证数据一致性
+
+详细接口文档请参考:`script/sql/CARGO_README.md`

+ 292 - 0
script/sql/FEEDBACK_README.md

@@ -0,0 +1,292 @@
+# 问题/建议功能接口文档
+
+## 功能概述
+用户可以提交问题或建议,支持文字内容和图片上传。管理员可以查看、回复问题建议。
+
+## 数据库表结构
+
+### 问题建议表 (tbl_dzbc_feedback)
+- id: 编号
+- user_id: 用户ID
+- user_name: 用户姓名
+- user_phone: 用户电话
+- content: 内容(必填)
+- image_urls: 图片地址,多个用逗号分隔
+- type: 类型(1-问题,2-建议)
+- status: 状态(0-待处理,1-处理中,2-已完成)
+- reply_content: 回复内容
+- reply_user_id: 回复人ID
+- reply_user_name: 回复人姓名
+- reply_time: 回复时间
+- remark: 备注
+- ext1, ext2: 扩展字段
+
+## 接口列表
+
+### 1. 用户接口
+
+#### 查询我的问题建议列表
+```
+GET /system/dzbc/feedback/my/list
+```
+说明:获取当前登录用户的所有问题建议记录
+
+返回示例:
+```json
+{
+  "code": 200,
+  "data": [
+    {
+      "id": 1,
+      "userId": 1,
+      "userName": "张三",
+      "userPhone": "13800138000",
+      "content": "班车晚点了,希望能准时发车",
+      "imageUrls": "http://xxx.com/image1.jpg,http://xxx.com/image2.jpg",
+      "type": 1,
+      "status": 2,
+      "replyContent": "感谢您的反馈,我们会加强调度管理",
+      "replyUserId": 10,
+      "replyUserName": "管理员",
+      "replyTime": "2024-02-20 10:00:00",
+      "createTime": "2024-02-20 09:00:00"
+    }
+  ]
+}
+```
+
+#### 提交问题/建议
+```
+POST /system/dzbc/feedback/submit
+Content-Type: multipart/form-data
+```
+
+请求参数:
+- content: 内容(必填)
+- type: 类型(必填,1-问题,2-建议)
+- images: 图片文件(可选,可上传多个)
+
+**请求示例(使用 Postman):**
+
+方法:POST
+URL: http://localhost:8080/system/dzbc/feedback/submit
+Body 类型:form-data
+
+| 参数名 | 类型 | 必填 | 说明 |
+|--------|------|------|------|
+| content | String | 是 | 问题/建议内容 |
+| type | Integer | 是 | 类型:1-问题,2-建议 |
+| images | File | 否 | 图片文件(可上传多个) |
+
+**返回示例:**
+```json
+{
+  "code": 200,
+  "msg": "操作成功",
+  "data": {
+    "id": 1,
+    "userId": 1,
+    "userName": "张三",
+    "userPhone": "13800138000",
+    "content": "班车晚点了,希望能准时发车",
+    "imageUrls": "http://xxx.com/image1.jpg,http://xxx.com/image2.jpg",
+    "type": 1,
+    "status": 0,
+    "createTime": "2024-02-20 09:00:00"
+  }
+}
+```
+
+### 2. 管理员接口
+
+#### 查询问题建议列表(分页)
+```
+GET /system/dzbc/feedback/list
+```
+
+查询参数:
+- userId: 用户ID
+- userName: 用户姓名(模糊查询)
+- content: 内容(模糊查询)
+- type: 类型(1-问题,2-建议)
+- status: 状态(0-待处理,1-处理中,2-已完成)
+- pageNum: 页码
+- pageSize: 每页大小
+
+返回示例:
+```json
+{
+  "code": 200,
+  "rows": [
+    {
+      "id": 1,
+      "userId": 1,
+      "userName": "张三",
+      "userPhone": "13800138000",
+      "content": "班车晚点了,希望能准时发车",
+      "imageUrls": "http://xxx.com/image1.jpg,http://xxx.com/image2.jpg",
+      "type": 1,
+      "status": 0,
+      "createTime": "2024-02-20 09:00:00"
+    }
+  ],
+  "total": 1
+}
+```
+
+#### 查询问题建议详情
+```
+GET /system/dzbc/feedback/{id}
+```
+
+返回示例:
+```json
+{
+  "code": 200,
+  "data": {
+    "id": 1,
+    "userId": 1,
+    "userName": "张三",
+    "userPhone": "13800138000",
+    "content": "班车晚点了,希望能准时发车",
+    "imageUrls": "http://xxx.com/image1.jpg,http://xxx.com/image2.jpg",
+    "type": 1,
+    "status": 2,
+    "replyContent": "感谢您的反馈,我们会加强调度管理",
+    "replyUserId": 10,
+    "replyUserName": "管理员",
+    "replyTime": "2024-02-20 10:00:00",
+    "createTime": "2024-02-20 09:00:00"
+  }
+}
+```
+
+#### 回复问题建议
+```
+POST /system/dzbc/feedback/reply/{id}
+```
+
+请求参数:
+- id: 问题建议ID(路径参数)
+- replyContent: 回复内容(表单参数,必填)
+
+**请求示例:**
+```
+POST /system/dzbc/feedback/reply/1
+Content-Type: application/x-www-form-urlencoded
+
+replyContent=感谢您的反馈,我们会加强调度管理
+```
+
+返回示例:
+```json
+{
+  "code": 200,
+  "msg": "操作成功"
+}
+```
+
+#### 修改问题建议状态
+```
+PUT /system/dzbc/feedback
+```
+
+请求体:
+```json
+{
+  "id": 1,
+  "status": 1,
+  "remark": "正在处理中"
+}
+```
+
+#### 删除问题建议
+```
+DELETE /system/dzbc/feedback/{ids}
+```
+
+返回示例:
+```json
+{
+  "code": 200,
+  "msg": "操作成功"
+}
+```
+
+#### 导出问题建议
+```
+POST /system/dzbc/feedback/export
+```
+
+## 使用示例
+
+### 1. 用户提交问题(含图片)
+
+使用 cURL:
+```bash
+curl -X POST "http://localhost:8080/system/dzbc/feedback/submit" \
+  -H "Authorization: Bearer YOUR_TOKEN" \
+  -F "content=班车晚点了,希望能准时发车" \
+  -F "type=1" \
+  -F "images=@/path/to/image1.jpg" \
+  -F "images=@/path/to/image2.jpg"
+```
+
+使用 Postman:
+1. 方法:POST
+2. URL: http://localhost:8080/system/dzbc/feedback/submit
+3. Headers: Authorization: Bearer YOUR_TOKEN
+4. Body: form-data
+   - content (Text): 班车晚点了,希望能准时发车
+   - type (Text): 1
+   - images (File): 选择图片文件(可添加多个)
+
+### 2. 管理员回复问题
+
+```bash
+curl -X POST "http://localhost:8080/system/dzbc/feedback/reply/1" \
+  -H "Authorization: Bearer ADMIN_TOKEN" \
+  -d "replyContent=感谢您的反馈,我们会加强调度管理"
+```
+
+### 3. 用户查询我的问题
+
+```bash
+curl -X GET "http://localhost:8080/system/dzbc/feedback/my/list" \
+  -H "Authorization: Bearer YOUR_TOKEN"
+```
+
+## 字段说明
+
+### 类型 (type)
+- 1: 问题
+- 2: 建议
+
+### 状态 (status)
+- 0: 待处理
+- 1: 处理中
+- 2: 已完成(已回复)
+
+## 注意事项
+
+1. **图片上传**:支持同时上传多张图片,图片会自动上传到 OSS,返回的 URL 以逗号分隔存储在数据库中
+2. **自动获取用户信息**:提交时自动获取当前登录用户的信息(姓名、电话)
+3. **回复功能**:回复后自动将状态更新为"已完成",并记录回复人和回复时间
+4. **图片地址格式**:imageUrls 字段存储格式为:`url1,url2,url3`
+5. **权限控制**:普通用户只能查看自己的记录,管理员可以查看所有记录
+
+## 数据库初始化
+
+执行以下 SQL 创建表:
+```bash
+mysql -u root -p khyznh < script/sql/feedback.sql
+```
+
+## 安全配置
+
+该接口已在安全配置中放行,无需额外权限验证:
+```yaml
+security:
+  excludes:
+    - /system/dzbc/feedback/**
+```

+ 190 - 0
script/sql/FEEDBACK_SUMMARY.md

@@ -0,0 +1,190 @@
+# 问题/建议功能实现总结
+
+## 已创建的文件
+
+### 1. 实体类 (Domain)
+- `TblDzbcFeedback.java` - 问题建议实体
+
+### 2. 业务对象
+- `TblDzbcFeedbackBo.java` - 问题建议业务对象
+- `FeedbackSubmitBo.java` - 提交问题建议业务对象(支持文件上传)
+
+### 3. 视图对象
+- `TblDzbcFeedbackVo.java` - 问题建议视图对象
+
+### 4. Mapper 接口
+- `TblDzbcFeedbackMapper.java` - 问题建议 Mapper
+
+### 5. Service 接口和实现
+- `ITblDzbcFeedbackService.java` - 问题建议服务接口
+- `TblDzbcFeedbackServiceImpl.java` - 问题建议服务实现
+
+### 6. Controller
+- `TblDzbcFeedbackController.java` - 问题建议控制器
+
+### 7. Mapper XML
+- `TblDzbcFeedbackMapper.xml` - 问题建议 SQL
+
+### 8. SQL 脚本和文档
+- `script/sql/feedback.sql` - 建表 SQL
+- `script/sql/FEEDBACK_README.md` - 接口文档
+
+## 主要功能
+
+### 1. 用户功能
+- **提交问题/建议**:支持文字内容和多图片上传
+- **查询我的问题建议**:查看自己的历史记录
+
+### 2. 管理员功能
+- **查看所有问题建议**:分页查询,支持多条件筛选
+- **查看详情**:查看完整的问题建议内容
+- **回复问题建议**:管理员可以回复用户问题
+- **修改状态**:更新问题处理状态
+- **删除记录**:删除无效或重复的记录
+- **导出数据**:导出问题建议列表到 Excel
+
+## 技术特点
+
+### 1. 图片上传
+- 使用 OSS 服务存储图片
+- 支持多图片同时上传
+- 自动将图片 URL 以逗号分隔存储
+- 依赖 `ruoyi-common-oss` 模块
+
+### 2. 用户信息
+- 自动获取当前登录用户信息
+- 自动填充用户姓名和电话
+- 登录用户只能查看自己的记录
+
+### 3. 状态管理
+- 0-待处理
+- 1-处理中
+- 2-已完成(回复后自动标记为已完成)
+
+### 4. 类型区分
+- 1-问题
+- 2-建议
+
+## 接口列表
+
+### 用户接口
+- `POST /system/dzbc/feedback/submit` - 提交问题/建议(支持图片)
+- `GET /system/dzbc/feedback/my/list` - 查询我的问题建议列表
+
+### 管理员接口
+- `GET /system/dzbc/feedback/list` - 查询问题建议列表(分页)
+- `GET /system/dzbc/feedback/{id}` - 查询问题建议详情
+- `POST /system/dzbc/feedback/reply/{id}` - 回复问题建议
+- `PUT /system/dzbc/feedback` - 修改问题建议
+- `DELETE /system/dzbc/feedback/{ids}` - 删除问题建议
+- `POST /system/dzbc/feedback/export` - 导出问题建议
+
+## 数据库设计
+
+### tbl_dzbc_feedback 表结构
+- 主键:id
+- 用户信息:user_id, user_name, user_phone
+- 内容:content, image_urls
+- 分类:type(1-问题,2-建议)
+- 状态:status(0-待处理,1-处理中,2-已完成)
+- 回复:reply_content, reply_user_id, reply_user_name, reply_time
+- 备注:remark, ext1, ext2
+- 审计字段:create_dept, create_by, create_time, update_by, update_time
+
+### 索引
+- idx_user_id:用户ID索引
+- idx_type:类型索引
+- idx_status:状态索引
+
+## 使用示例
+
+### 1. 提交问题(含图片)
+
+**Postman 配置:**
+- 方法:POST
+- URL:`http://localhost:8080/system/dzbc/feedback/submit`
+- Headers:
+  - `Authorization: Bearer YOUR_TOKEN`
+- Body(form-data):
+  - `content`: 班车晚点了,希望能准时发车
+  - `type`: 1
+  - `images`: 选择图片文件(可添加多个)
+
+### 2. 查询我的问题
+
+```bash
+curl -X GET "http://localhost:8080/system/dzbc/feedback/my/list" \
+  -H "Authorization: Bearer YOUR_TOKEN"
+```
+
+### 3. 管理员回复问题
+
+```bash
+curl -X POST "http://localhost:8080/system/dzbc/feedback/reply/1" \
+  -H "Authorization: Bearer ADMIN_TOKEN" \
+  -d "replyContent=感谢您的反馈,我们会加强调度管理"
+```
+
+## 部署步骤
+
+1. **执行建表 SQL**
+   ```bash
+   mysql -u root -p khyznh < script/sql/feedback.sql
+   ```
+
+2. **配置 OSS 服务**
+   - 确保 `ruoyi-common-oss` 模块已正确配置
+   - 检查 OSS 连接参数
+
+3. **重启应用**
+   - 重新编译并启动应用
+
+4. **测试接口**
+   - 使用 Postman 测试提交接口
+   - 验证图片上传功能
+   - 测试管理功能
+
+## 注意事项
+
+1. **文件上传限制**
+   - 单文件大小限制:10MB(在 application.yml 中配置)
+   - 总上传大小限制:20MB
+
+2. **图片格式**
+   - 支持常见图片格式(jpg, png, gif 等)
+   - 建议上传前压缩图片
+
+3. **权限控制**
+   - 用户接口已放行,无需特殊权限
+   - 管理员接口建议添加权限注解
+
+4. **图片存储**
+   - 图片上传到 OSS,返回 URL
+   - 多个图片 URL 用逗号分隔存储
+   - 前端需要根据逗号分割显示
+
+5. **用户信息**
+   - 必须先登录才能提交问题
+   - 自动从登录用户获取信息
+   - 确保用户表包含电话号码字段
+
+## 扩展建议
+
+1. **消息通知**
+   - 提交问题后发送通知给管理员
+   - 回复后发送通知给用户
+
+2. **图片管理**
+   - 删除问题时同时删除 OSS 中的图片
+   - 提供图片预览功能
+
+3. **评价功能**
+   - 用户对回复进行评价
+   - 统计问题解决率
+
+4. **统计分析**
+   - 问题类型统计
+   - 处理时长统计
+   - 问题趋势分析
+
+详细接口文档请参考:`script/sql/FEEDBACK_README.md`

+ 281 - 0
script/sql/MULTI_SITE_PATH_README.md

@@ -0,0 +1,281 @@
+# 多站点路线管理接口文档
+
+## 功能概述
+多站点路线管理功能支持创建包含多个站点的路线,并为每两个站点设置独立的价格,形成路线站点的梯度价格图。
+
+## 数据库表结构
+
+### 1. 多站点路线表 (tbl_dzbc_multi_site_path)
+- id: 编号
+- name: 路线名称
+- status: 状态 (0-禁用, 1-启用)
+- remark: 备注
+- ext1, ext2: 扩展字段
+
+### 2. 路线站点关联表 (tbl_dzbc_path_site)
+- id: 编号
+- path_id: 路线ID
+- site_id: 站点ID(复用 tbl_dzbc_site 表)
+- site_order: 站点顺序
+- remark: 备注
+- ext1, ext2: 扩展字段
+
+### 3. 站点价格表 (tbl_dzbc_site_price)
+- id: 编号
+- path_id: 路线ID
+- from_site_id: 出发站点ID
+- to_site_id: 到达站点ID
+- price: 价格(单位:分)
+- remark: 备注
+- ext1, ext2: 扩展字段
+
+## 接口列表
+
+### 1. 多站点路线管理
+
+#### 查询路线列表
+```
+GET /system/dzbc/multiSitePath/list
+```
+参数:
+- name: 路线名称(模糊查询)
+- status: 状态
+- pageNum: 页码
+- pageSize: 每页大小
+
+#### 查询路线详情(包含站点和价格)
+```
+GET /system/dzbc/multiSitePath/{id}
+```
+返回数据包含:
+- 路线基本信息
+- sites: 路线站点列表(按站点顺序排列)
+- prices: 站点价格列表
+
+#### 新增路线
+```
+POST /system/dzbc/multiSitePath
+```
+请求体:
+```json
+{
+  "name": "福州市区环线",
+  "status": 1,
+  "remark": "福州市区环线路线"
+}
+```
+
+#### 修改路线
+```
+PUT /system/dzbc/multiSitePath
+```
+
+#### 删除路线
+```
+DELETE /system/dzbc/multiSitePath/{ids}
+```
+
+### 2. 路线站点管理
+
+#### 查询路线站点列表
+```
+GET /system/dzbc/pathSite/list
+```
+
+#### 根据路线ID查询站点
+```
+GET /system/dzbc/pathSite/path/{pathId}
+```
+返回数据包含:
+- siteId: 站点ID
+- siteName: 站点名称
+- siteAddr: 站点地址
+- siteOrder: 站点顺序
+
+#### 新增路线站点
+```
+POST /system/dzbc/pathSite
+```
+请求体:
+```json
+{
+  "pathId": 1,
+  "siteId": 1,
+  "siteOrder": 1,
+  "remark": "第一站"
+}
+```
+
+#### 修改路线站点
+```
+PUT /system/dzbc/pathSite
+```
+
+#### 删除路线站点
+```
+DELETE /system/dzbc/pathSite/{ids}
+```
+
+### 3. 站点价格管理
+
+#### 查询站点价格列表
+```
+GET /system/dzbc/sitePrice/list
+```
+
+#### 根据路线ID查询所有价格
+```
+GET /system/dzbc/sitePrice/path/{pathId}
+```
+返回数据包含:
+- fromSiteId: 出发站点ID
+- fromSiteName: 出发站点名称
+- toSiteId: 到达站点ID
+- toSiteName: 到达站点名称
+- price: 价格(单位:分)
+
+#### 新增站点价格
+```
+POST /system/dzbc/sitePrice
+```
+请求体:
+```json
+{
+  "pathId": 1,
+  "fromSiteId": 1,
+  "toSiteId": 2,
+  "price": 500,
+  "remark": "站点1到站点2"
+}
+```
+
+#### 修改站点价格
+```
+PUT /system/dzbc/sitePrice
+```
+
+#### 删除站点价格
+```
+DELETE /system/dzbc/sitePrice/{ids}
+```
+
+## 使用流程
+
+### 1. 创建多站点路线
+
+步骤1:创建路线
+```bash
+POST /system/dzbc/multiSitePath
+{
+  "name": "福州市区环线",
+  "status": 1
+}
+```
+
+步骤2:添加路线站点
+```bash
+POST /system/dzbc/pathSite
+{
+  "pathId": 1,
+  "siteId": 1,
+  "siteOrder": 1
+}
+```
+
+步骤3:设置站点价格
+```bash
+POST /system/dzbc/sitePrice
+{
+  "pathId": 1,
+  "fromSiteId": 1,
+  "toSiteId": 2,
+  "price": 500
+}
+```
+
+### 2. 用户查询路线和票价
+
+查询路线详情:
+```bash
+GET /system/dzbc/multiSitePath/1
+```
+
+返回示例:
+```json
+{
+  "code": 200,
+  "data": {
+    "id": 1,
+    "name": "福州市区环线",
+    "status": 1,
+    "sites": [
+      {
+        "siteId": 1,
+        "siteName": "火车站",
+        "siteAddr": "福州市晋安区火车站",
+        "siteOrder": 1
+      },
+      {
+        "siteId": 2,
+        "siteName": "市政府",
+        "siteAddr": "福州市鼓楼市政府",
+        "siteOrder": 2
+      },
+      {
+        "siteId": 3,
+        "siteName": "仓山万达",
+        "siteAddr": "福州市仓山区万达广场",
+        "siteOrder": 3
+      }
+    ],
+    "prices": [
+      {
+        "fromSiteId": 1,
+        "fromSiteName": "火车站",
+        "toSiteId": 2,
+        "toSiteName": "市政府",
+        "price": 500
+      },
+      {
+        "fromSiteId": 2,
+        "fromSiteName": "市政府",
+        "toSiteId": 3,
+        "toSiteName": "仓山万达",
+        "price": 600
+      },
+      {
+        "fromSiteId": 1,
+        "fromSiteName": "火车站",
+        "toSiteId": 3,
+        "toSiteName": "仓山万达",
+        "price": 1100
+      }
+    ]
+  }
+}
+```
+
+### 3. 用户购票
+
+用户根据站点价格表选择出发站点和到达站点,获取对应的价格进行购票。
+
+## 注意事项
+
+1. **站点顺序**:site_order 字段定义了路线的站点顺序,从小到大排列。
+2. **价格设置**:可以为任意两个站点设置价格,包括相邻站点和跨站点。
+3. **价格单位**:价格单位为分,例如 500 代表 5 元。
+4. **站点复用**:站点使用已有的 tbl_dzbc_site 表中的数据。
+5. **删除路线**:删除路线时,建议同时删除关联的站点和价格数据(可手动处理)。
+
+## 数据库初始化
+
+执行以下 SQL 创建表:
+```bash
+mysql -u root -p khyznh < script/sql/multi_site_path.sql
+```
+
+(可选)执行示例数据:
+```bash
+mysql -u root -p khyznh < script/sql/multi_site_path_data.sql
+```
+注意:示例数据中的站点ID需要替换为实际的站点ID。

+ 101 - 0
script/sql/cargo.sql

@@ -0,0 +1,101 @@
+-- 司机发车任务表
+CREATE TABLE `tbl_dzbc_driver_trip` (
+  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '编号',
+  `trip_no` varchar(50) NOT NULL COMMENT '任务编号',
+  `path_id` bigint NOT NULL COMMENT '路线ID',
+  `path_name` varchar(100) DEFAULT NULL COMMENT '路线名称',
+  `driver_id` bigint DEFAULT NULL COMMENT '司机ID',
+  `driver_name` varchar(50) DEFAULT NULL COMMENT '司机姓名',
+  `driver_phone` varchar(20) DEFAULT NULL COMMENT '司机电话',
+  `car_id` bigint DEFAULT NULL COMMENT '车辆ID',
+  `car_plate` varchar(20) DEFAULT NULL COMMENT '车牌号',
+  `start_time` datetime DEFAULT NULL COMMENT '出发时间',
+  `end_time` datetime DEFAULT NULL COMMENT '到达时间',
+  `status` int DEFAULT '0' COMMENT '状态 0-待发车 1-运行中 2-已完成 3-已取消',
+  `qr_code` text COMMENT '二维码内容',
+  `current_site_id` bigint DEFAULT NULL COMMENT '当前站点ID',
+  `current_site_name` varchar(100) DEFAULT NULL COMMENT '当前站点名称',
+  `cargo_count` int DEFAULT '0' COMMENT '货物数量',
+  `cargo_total_price` int DEFAULT '0' COMMENT '货物总价(分)',
+  `remark` varchar(500) DEFAULT NULL COMMENT '备注',
+  `ext1` varchar(255) DEFAULT NULL COMMENT '扩展1',
+  `ext2` varchar(255) DEFAULT NULL COMMENT '扩展2',
+  `create_dept` bigint DEFAULT NULL,
+  `create_by` bigint DEFAULT NULL,
+  `create_time` datetime DEFAULT NULL,
+  `update_by` bigint DEFAULT NULL,
+  `update_time` datetime DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `uk_trip_no` (`trip_no`),
+  KEY `idx_path_id` (`path_id`),
+  KEY `idx_driver_id` (`driver_id`),
+  KEY `idx_status` (`status`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='司机发车任务表';
+
+-- 货物订单表
+CREATE TABLE `tbl_dzbc_cargo_order` (
+  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '编号',
+  `order_no` varchar(50) NOT NULL COMMENT '订单编号',
+  `trip_id` bigint NOT NULL COMMENT '司机任务ID',
+  `trip_no` varchar(50) DEFAULT NULL COMMENT '任务编号',
+  `user_id` bigint DEFAULT NULL COMMENT '用户ID',
+  `user_name` varchar(50) DEFAULT NULL COMMENT '用户姓名',
+  `user_phone` varchar(20) DEFAULT NULL COMMENT '用户电话',
+  `sender_name` varchar(50) DEFAULT NULL COMMENT '发货人姓名',
+  `sender_phone` varchar(20) DEFAULT NULL COMMENT '发货人电话',
+  `receiver_name` varchar(50) DEFAULT NULL COMMENT '收货人姓名',
+  `receiver_phone` varchar(20) DEFAULT NULL COMMENT '收货人电话',
+  `from_site_id` bigint DEFAULT NULL COMMENT '起始站点ID',
+  `from_site_name` varchar(100) DEFAULT NULL COMMENT '起始站点名称',
+  `to_site_id` bigint DEFAULT NULL COMMENT '送达站点ID',
+  `to_site_name` varchar(100) DEFAULT NULL COMMENT '送达站点名称',
+  `price` int DEFAULT '0' COMMENT '价格(分)',
+  `pay_status` int DEFAULT '0' COMMENT '支付状态 0-待支付 1-已支付',
+  `pay_time` datetime DEFAULT NULL COMMENT '支付时间',
+  `pay_trade_no` varchar(100) DEFAULT NULL COMMENT '支付交易号',
+  `status` int DEFAULT '0' COMMENT '订单状态 0-待发货 1-已取货 2-运输中 3-已送达 4-已取消',
+  `cargo_count` int DEFAULT '0' COMMENT '货物数量',
+  `cargo_weight` decimal(10,2) DEFAULT '0.00' COMMENT '货物重量(kg)',
+  `cargo_value` decimal(10,2) DEFAULT '0.00' COMMENT '货物价值(元)',
+  `remark` text COMMENT '备注',
+  `ext1` varchar(255) DEFAULT NULL COMMENT '扩展1',
+  `ext2` varchar(255) DEFAULT NULL COMMENT '扩展2',
+  `create_dept` bigint DEFAULT NULL,
+  `create_by` bigint DEFAULT NULL,
+  `create_time` datetime DEFAULT NULL,
+  `update_by` bigint DEFAULT NULL,
+  `update_time` datetime DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `uk_order_no` (`order_no`),
+  KEY `idx_trip_id` (`trip_id`),
+  KEY `idx_user_id` (`user_id`),
+  KEY `idx_status` (`status`),
+  KEY `idx_pay_status` (`pay_status`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='货物订单表';
+
+-- 货物详情表
+CREATE TABLE `tbl_dzbc_cargo_item` (
+  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '编号',
+  `order_id` bigint NOT NULL COMMENT '订单ID',
+  `order_no` varchar(50) DEFAULT NULL COMMENT '订单编号',
+  `cargo_name` varchar(200) DEFAULT NULL COMMENT '货物名称',
+  `cargo_type` varchar(50) DEFAULT NULL COMMENT '货物类型',
+  `quantity` int DEFAULT '1' COMMENT '数量',
+  `unit` varchar(20) DEFAULT NULL COMMENT '单位',
+  `weight` decimal(10,2) DEFAULT '0.00' COMMENT '重量(kg)',
+  `length` decimal(10,2) DEFAULT '0.00' COMMENT '长度(cm)',
+  `width` decimal(10,2) DEFAULT '0.00' COMMENT '宽度(cm)',
+  `height` decimal(10,2) DEFAULT '0.00' COMMENT '高度(cm)',
+  `value` decimal(10,2) DEFAULT '0.00' COMMENT '价值(元)',
+  `image_urls` text COMMENT '图片地址,多个用逗号分隔',
+  `remark` varchar(500) DEFAULT NULL COMMENT '备注',
+  `ext1` varchar(255) DEFAULT NULL COMMENT '扩展1',
+  `ext2` varchar(255) DEFAULT NULL COMMENT '扩展2',
+  `create_dept` bigint DEFAULT NULL,
+  `create_by` bigint DEFAULT NULL,
+  `create_time` datetime DEFAULT NULL,
+  `update_by` bigint DEFAULT NULL,
+  `update_time` datetime DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `idx_order_id` (`order_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='货物详情表';

+ 27 - 0
script/sql/feedback.sql

@@ -0,0 +1,27 @@
+-- 问题建议表
+CREATE TABLE `tbl_dzbc_feedback` (
+  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '编号',
+  `user_id` bigint DEFAULT NULL COMMENT '用户ID',
+  `user_name` varchar(100) DEFAULT NULL COMMENT '用户姓名',
+  `user_phone` varchar(20) DEFAULT NULL COMMENT '用户电话',
+  `content` text NOT NULL COMMENT '内容',
+  `image_urls` text COMMENT '图片地址,多个用逗号分隔',
+  `type` int NOT NULL COMMENT '类型 1-问题 2-建议',
+  `status` int DEFAULT '0' COMMENT '状态 0-待处理 1-处理中 2-已完成',
+  `reply_content` text COMMENT '回复内容',
+  `reply_user_id` bigint DEFAULT NULL COMMENT '回复人ID',
+  `reply_user_name` varchar(100) DEFAULT NULL COMMENT '回复人姓名',
+  `reply_time` datetime DEFAULT NULL COMMENT '回复时间',
+  `remark` varchar(500) DEFAULT NULL COMMENT '备注',
+  `ext1` varchar(255) DEFAULT NULL COMMENT '扩展1',
+  `ext2` varchar(255) DEFAULT NULL COMMENT '扩展2',
+  `create_dept` bigint DEFAULT NULL,
+  `create_by` bigint DEFAULT NULL,
+  `create_time` datetime DEFAULT NULL,
+  `update_by` bigint DEFAULT NULL,
+  `update_time` datetime DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `idx_user_id` (`user_id`),
+  KEY `idx_type` (`type`),
+  KEY `idx_status` (`status`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='问题建议表';

+ 53 - 0
script/sql/multi_site_path.sql

@@ -0,0 +1,53 @@
+-- 多站点路线表
+CREATE TABLE `tbl_dzbc_multi_site_path` (
+  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '编号',
+  `name` varchar(100) NOT NULL COMMENT '路线名称',
+  `status` int DEFAULT '0' COMMENT '状态 0-禁用 1-启用',
+  `remark` varchar(500) DEFAULT NULL COMMENT '备注',
+  `ext1` varchar(255) DEFAULT NULL COMMENT '扩展1',
+  `ext2` varchar(255) DEFAULT NULL COMMENT '扩展2',
+  `create_dept` bigint DEFAULT NULL,
+  `create_by` bigint DEFAULT NULL,
+  `create_time` datetime DEFAULT NULL,
+  `update_by` bigint DEFAULT NULL,
+  `update_time` datetime DEFAULT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='多站点路线表';
+
+-- 路线站点关联表
+CREATE TABLE `tbl_dzbc_path_site` (
+  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '编号',
+  `path_id` bigint NOT NULL COMMENT '路线ID',
+  `site_id` bigint NOT NULL COMMENT '站点ID',
+  `site_order` int NOT NULL COMMENT '站点顺序',
+  `remark` varchar(500) DEFAULT NULL COMMENT '备注',
+  `ext1` varchar(255) DEFAULT NULL COMMENT '扩展1',
+  `ext2` varchar(255) DEFAULT NULL COMMENT '扩展2',
+  `create_dept` bigint DEFAULT NULL,
+  `create_by` bigint DEFAULT NULL,
+  `create_time` datetime DEFAULT NULL,
+  `update_by` bigint DEFAULT NULL,
+  `update_time` datetime DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `idx_path_id` (`path_id`),
+  KEY `idx_site_id` (`site_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='路线站点关联表';
+
+-- 站点价格表
+CREATE TABLE `tbl_dzbc_site_price` (
+  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '编号',
+  `path_id` bigint NOT NULL COMMENT '路线ID',
+  `from_site_id` bigint NOT NULL COMMENT '出发站点ID',
+  `to_site_id` bigint NOT NULL COMMENT '到达站点ID',
+  `price` int NOT NULL COMMENT '价格(分)',
+  `remark` varchar(500) DEFAULT NULL COMMENT '备注',
+  `ext1` varchar(255) DEFAULT NULL COMMENT '扩展1',
+  `ext2` varchar(255) DEFAULT NULL COMMENT '扩展2',
+  `create_dept` bigint DEFAULT NULL,
+  `create_by` bigint DEFAULT NULL,
+  `create_time` datetime DEFAULT NULL,
+  `update_by` bigint DEFAULT NULL,
+  `update_time` datetime DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `idx_path_site` (`path_id`, `from_site_id`, `to_site_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='站点价格表';

+ 29 - 0
script/sql/multi_site_path_data.sql

@@ -0,0 +1,29 @@
+-- 示例数据:创建一条多站点路线
+
+-- 插入路线
+INSERT INTO `tbl_dzbc_multi_site_path` (`name`, `status`, `remark`, `create_by`, `create_time`)
+VALUES ('福州市区环线', 1, '福州市区环线路线', 1, NOW());
+
+-- 假设路线ID为1,插入路线站点(需要先确保站点存在)
+-- 注意:以下站点ID需要替换为实际的站点ID
+-- INSERT INTO `tbl_dzbc_path_site` (`path_id`, `site_id`, `site_order`, `create_by`, `create_time`)
+-- VALUES
+-- (1, 1, 1, 1, NOW()), -- 站点1
+-- (1, 2, 2, 1, NOW()), -- 站点2
+-- (1, 3, 3, 1, NOW()), -- 站点3
+-- (1, 4, 4, 1, NOW()), -- 站点4
+-- (1, 5, 5, 1, NOW()); -- 站点5
+
+-- 插入站点价格(每两个相邻站点之间的价格)
+-- INSERT INTO `tbl_dzbc_site_price` (`path_id`, `from_site_id`, `to_site_id`, `price`, `create_by`, `create_time`)
+-- VALUES
+-- (1, 1, 2, 500, 1, NOW()), -- 站点1到站点2:5元
+-- (1, 2, 3, 600, 1, NOW()), -- 站点2到站点3:6元
+-- (1, 3, 4, 700, 1, NOW()), -- 站点3到站点4:7元
+-- (1, 4, 5, 800, 1, NOW()), -- 站点4到站点5:8元
+-- (1, 1, 3, 1100, 1, NOW()), -- 站点1到站点3:11元(跨站点)
+-- (1, 2, 4, 1300, 1, NOW()), -- 站点2到站点4:13元(跨站点)
+-- (1, 3, 5, 1500, 1, NOW()), -- 站点3到站点5:15元(跨站点)
+-- (1, 1, 4, 1800, 1, NOW()), -- 站点1到站点4:18元(跨站点)
+-- (1, 2, 5, 2000, 1, NOW()), -- 站点2到站点5:20元(跨站点)
+-- (1, 1, 5, 2600, 1, NOW()); -- 站点1到站点5:26元(跨站点)