jiangping
2025-07-18 22603b2519b8dcb7f9a7c7d9ba636d9aba240a59
Merge remote-tracking branch 'origin/master'

# Conflicts:
# small-program/unpackage/dist/dev/.sourcemap/mp-weixin/common/main.js.map
# small-program/unpackage/dist/dev/.sourcemap/mp-weixin/common/vendor.js.map
# small-program/unpackage/dist/dev/.sourcemap/mp-weixin/components/sunui-mverify/sunui-mverify.js.map
# small-program/unpackage/dist/dev/.sourcemap/mp-weixin/components/t-index-address/components/t-index-address/t-index-address.js.map
# small-program/unpackage/dist/dev/.sourcemap/mp-weixin/node-modules/uview-ui/components/u--textarea/u--textarea.js.map
# small-program/unpackage/dist/dev/.sourcemap/mp-weixin/node-modules/uview-ui/components/u-button/u-button.js.map
# small-program/unpackage/dist/dev/.sourcemap/mp-weixin/node-modules/uview-ui/components/u-calendar/header.js.map
# small-program/unpackage/dist/dev/.sourcemap/mp-weixin/node-modules/uview-ui/components/u-calendar/month.js.map
# small-program/unpackage/dist/dev/.sourcemap/mp-weixin/node-modules/uview-ui/components/u-calendar/u-calendar.js.map
# small-program/unpackage/dist/dev/.sourcemap/mp-weixin/node-modules/uview-ui/components/u-icon/u-icon.js.map
# small-program/unpackage/dist/dev/.sourcemap/mp-weixin/node-modules/uview-ui/components/u-line/u-line.js.map
# small-program/unpackage/dist/dev/.sourcemap/mp-weixin/node-modules/uview-ui/components/u-loading-icon/u-loading-icon.js.map
# small-program/unpackage/dist/dev/.sourcemap/mp-weixin/node-modules/uview-ui/components/u-modal/u-modal.js.map
# small-program/unpackage/dist/dev/.sourcemap/mp-weixin/node-modules/uview-ui/components/u-overlay/u-overlay.js.map
# small-program/unpackage/dist/dev/.sourcemap/mp-weixin/node-modules/uview-ui/components/u-parse/node/node.js.map
# small-program/unpackage/dist/dev/.sourcemap/mp-weixin/node-modules/uview-ui/components/u-parse/u-parse.js.map
# small-program/unpackage/dist/dev/.sourcemap/mp-weixin/node-modules/uview-ui/components/u-picker/u-picker.js.map
# small-program/unpackage/dist/dev/.sourcemap/mp-weixin/node-modules/uview-ui/components/u-popup/u-popup.js.map
# small-program/unpackage/dist/dev/.sourcemap/mp-weixin/node-modules/uview-ui/components/u-rate/u-rate.js.map
# small-program/unpackage/dist/dev/.sourcemap/mp-weixin/node-modules/uview-ui/components/u-safe-bottom/u-safe-bottom.js.map
# small-program/unpackage/dist/dev/.sourcemap/mp-weixin/node-modules/uview-ui/components/u-status-bar/u-status-bar.js.map
# small-program/unpackage/dist/dev/.sourcemap/mp-weixin/node-modules/uview-ui/components/u-switch/u-switch.js.map
# small-program/unpackage/dist/dev/.sourcemap/mp-weixin/node-modules/uview-ui/components/u-textarea/u-textarea.js.map
# small-program/unpackage/dist/dev/.sourcemap/mp-weixin/node-modules/uview-ui/components/u-toolbar/u-toolbar.js.map
# small-program/unpackage/dist/dev/.sourcemap/mp-weixin/node-modules/uview-ui/components/u-transition/u-transition.js.map
# small-program/unpackage/dist/dev/.sourcemap/mp-weixin/pages/balance-details/balance-details.js.map
# small-program/unpackage/dist/dev/.sourcemap/mp-weixin/pages/catering-certification/catering-certification.js.map
# small-program/unpackage/dist/dev/.sourcemap/mp-weixin/pages/certification-results/certification-results.js.map
# small-program/unpackage/dist/dev/.sourcemap/mp-weixin/pages/certification-successful/certification-successful.js.map
# small-program/unpackage/dist/dev/.sourcemap/mp-weixin/pages/demand-hall/demand-hall.js.map
# small-program/unpackage/dist/dev/.sourcemap/mp-weixin/pages/employment-certification/employment-certification.js.map
# small-program/unpackage/dist/dev/.sourcemap/mp-weixin/pages/freight-certification/freight-certification.js.map
# small-program/unpackage/dist/dev/.sourcemap/mp-weixin/pages/freight-order/freight-order.js.map
# small-program/unpackage/dist/dev/.sourcemap/mp-weixin/pages/freight/freight.js.map
# small-program/unpackage/dist/dev/.sourcemap/mp-weixin/pages/index/index.js.map
# small-program/unpackage/dist/dev/.sourcemap/mp-weixin/pages/login/login.js.map
# small-program/unpackage/dist/dev/.sourcemap/mp-weixin/pages/mine/mine.js.map
# small-program/unpackage/dist/dev/.sourcemap/mp-weixin/pages/order-details/order-details.js.map
# small-program/unpackage/dist/dev/.sourcemap/mp-weixin/pages/order-food/order-food.js.map
# small-program/unpackage/dist/dev/.sourcemap/mp-weixin/pages/packaging-worker/packaging-worker.js.map
# small-program/unpackage/dist/dev/.sourcemap/mp-weixin/pages/rich-text-page/rich-text-page.js.map
# small-program/unpackage/dist/dev/.sourcemap/mp-weixin/pages/seleCity/seleCity.js.map
# small-program/unpackage/dist/dev/.sourcemap/mp-weixin/pages/set-up/set-up.js.map
# small-program/unpackage/dist/dev/.sourcemap/mp-weixin/pages/sorting/sorting.js.map
# small-program/unpackage/dist/dev/.sourcemap/mp-weixin/pages/success/success.js.map
# small-program/unpackage/dist/dev/.sourcemap/mp-weixin/pages/using-workers/using-workers.js.map
# small-program/unpackage/dist/dev/.sourcemap/mp-weixin/pages/withdrawal-successful/withdrawal-successful.js.map
# small-program/unpackage/dist/dev/.sourcemap/mp-weixin/pages/withdrawal/withdrawal.js.map
# small-program/unpackage/dist/dev/mp-weixin/common/vendor.js
# small-program/unpackage/dist/dev/mp-weixin/components/sunui-mverify/sunui-mverify.js
# small-program/unpackage/dist/dev/mp-weixin/components/t-index-address/components/t-index-address/t-index-address.js
# small-program/unpackage/dist/dev/mp-weixin/node-modules/uview-ui/components/u--textarea/u--textarea.js
# small-program/unpackage/dist/dev/mp-weixin/node-modules/uview-ui/components/u-button/u-button.js
# small-program/unpackage/dist/dev/mp-weixin/node-modules/uview-ui/components/u-calendar/header.js
# small-program/unpackage/dist/dev/mp-weixin/node-modules/uview-ui/components/u-calendar/month.js
# small-program/unpackage/dist/dev/mp-weixin/node-modules/uview-ui/components/u-calendar/u-calendar.js
# small-program/unpackage/dist/dev/mp-weixin/node-modules/uview-ui/components/u-icon/u-icon.js
# small-program/unpackage/dist/dev/mp-weixin/node-modules/uview-ui/components/u-line/u-line.js
# small-program/unpackage/dist/dev/mp-weixin/node-modules/uview-ui/components/u-loading-icon/u-loading-icon.js
# small-program/unpackage/dist/dev/mp-weixin/node-modules/uview-ui/components/u-modal/u-modal.js
# small-program/unpackage/dist/dev/mp-weixin/node-modules/uview-ui/components/u-overlay/u-overlay.js
# small-program/unpackage/dist/dev/mp-weixin/node-modules/uview-ui/components/u-parse/node/node.js
# small-program/unpackage/dist/dev/mp-weixin/node-modules/uview-ui/components/u-parse/u-parse.js
# small-program/unpackage/dist/dev/mp-weixin/node-modules/uview-ui/components/u-picker/u-picker.js
# small-program/unpackage/dist/dev/mp-weixin/node-modules/uview-ui/components/u-picker/u-picker.json
# small-program/unpackage/dist/dev/mp-weixin/node-modules/uview-ui/components/u-popup/u-popup.js
# small-program/unpackage/dist/dev/mp-weixin/node-modules/uview-ui/components/u-rate/u-rate.js
# small-program/unpackage/dist/dev/mp-weixin/node-modules/uview-ui/components/u-safe-bottom/u-safe-bottom.js
# small-program/unpackage/dist/dev/mp-weixin/node-modules/uview-ui/components/u-status-bar/u-status-bar.js
# small-program/unpackage/dist/dev/mp-weixin/node-modules/uview-ui/components/u-switch/u-switch.js
# small-program/unpackage/dist/dev/mp-weixin/node-modules/uview-ui/components/u-textarea/u-textarea.js
# small-program/unpackage/dist/dev/mp-weixin/node-modules/uview-ui/components/u-toolbar/u-toolbar.js
# small-program/unpackage/dist/dev/mp-weixin/node-modules/uview-ui/components/u-transition/u-transition.js
# small-program/unpackage/dist/dev/mp-weixin/pages/balance-details/balance-details.js
# small-program/unpackage/dist/dev/mp-weixin/pages/catering-certification/catering-certification.js
# small-program/unpackage/dist/dev/mp-weixin/pages/certification-results/certification-results.js
# small-program/unpackage/dist/dev/mp-weixin/pages/certification-successful/certification-successful.js
# small-program/unpackage/dist/dev/mp-weixin/pages/demand-hall/demand-hall.js
# small-program/unpackage/dist/dev/mp-weixin/pages/employment-certification/employment-certification.js
# small-program/unpackage/dist/dev/mp-weixin/pages/freight-certification/freight-certification.js
# small-program/unpackage/dist/dev/mp-weixin/pages/freight-order/freight-order.js
# small-program/unpackage/dist/dev/mp-weixin/pages/freight/freight.js
# small-program/unpackage/dist/dev/mp-weixin/pages/freight/freight.json
# small-program/unpackage/dist/dev/mp-weixin/pages/freight/freight.wxml
# small-program/unpackage/dist/dev/mp-weixin/pages/freight/freight.wxss
# small-program/unpackage/dist/dev/mp-weixin/pages/index/index.js
# small-program/unpackage/dist/dev/mp-weixin/pages/index/index.wxml
# small-program/unpackage/dist/dev/mp-weixin/pages/index/index.wxss
# small-program/unpackage/dist/dev/mp-weixin/pages/login/login.js
# small-program/unpackage/dist/dev/mp-weixin/pages/login/login.wxml
# small-program/unpackage/dist/dev/mp-weixin/pages/mine/mine.js
# small-program/unpackage/dist/dev/mp-weixin/pages/mine/mine.wxss
# small-program/unpackage/dist/dev/mp-weixin/pages/order-details/order-details.js
# small-program/unpackage/dist/dev/mp-weixin/pages/order-food/order-food.js
# small-program/unpackage/dist/dev/mp-weixin/pages/packaging-worker/packaging-worker.js
# small-program/unpackage/dist/dev/mp-weixin/pages/packaging-worker/packaging-worker.wxml
# small-program/unpackage/dist/dev/mp-weixin/pages/rich-text-page/rich-text-page.js
# small-program/unpackage/dist/dev/mp-weixin/pages/seleCity/seleCity.js
# small-program/unpackage/dist/dev/mp-weixin/pages/set-up/set-up.wxss
# small-program/unpackage/dist/dev/mp-weixin/pages/sorting/sorting.js
# small-program/unpackage/dist/dev/mp-weixin/pages/success/success.js
# small-program/unpackage/dist/dev/mp-weixin/pages/using-workers/using-workers.js
# small-program/unpackage/dist/dev/mp-weixin/pages/withdrawal-successful/withdrawal-successful.js
# small-program/unpackage/dist/dev/mp-weixin/pages/withdrawal/withdrawal.js
# small-program/unpackage/dist/dev/mp-weixin/project.private.config.json
已添加7个文件
已修改26个文件
1932 ■■■■■ 文件已修改
server/admin/src/main/java/com/doumee/job/AutoCommentJob.java 34 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/admin/src/main/java/com/doumee/job/AutoConfirmJob.java 34 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/admin/src/main/java/com/doumee/job/AutoGrabOrdersJob.java 34 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/admin/src/main/java/com/doumee/job/InitializeCodeJob.java 34 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/pom.xml 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/api/common/PublicCloudController.java 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/config/jwt/JwtTokenUtil.java 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/config/wx/TransferToUser.java 312 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/config/wx/WXPayUtility.java 456 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/config/wx/WxMiniConfig.java 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/config/wx/WxPayProperties.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/dao/business/model/Member.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/dao/business/model/MemberRevenue.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/dao/business/model/Orders.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/dao/business/model/WithdrawalOrders.java 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/dao/vo/OrderReleaseVO.java 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/service/business/MemberService.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/service/business/OrdersService.java 9 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/service/business/WithdrawalOrdersService.java 5 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/service/business/impl/CategoryServiceImpl.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/service/business/impl/MemberServiceImpl.java 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/service/business/impl/OrdersServiceImpl.java 152 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/service/business/impl/WithdrawalOrdersServiceImpl.java 103 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/resources/application-dev.yml 16 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/web/src/main/java/com/doumee/api/web/AccountApi.java 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/web/src/main/java/com/doumee/api/web/ConfigApi.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/web/src/main/java/com/doumee/api/web/OrdersApi.java 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/web/src/main/java/com/doumee/api/web/UserApi.java 29 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
small-program/pages/freight/freight.vue 386 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
small-program/pages/index/index.vue 193 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
small-program/pages/packaging-worker/packaging-worker.vue 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
small-program/pages/sorting/sorting.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
small-program/pages/using-workers/using-workers.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/admin/src/main/java/com/doumee/job/AutoCommentJob.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,34 @@
package com.doumee.job;
import com.doumee.core.job.BaseJob;
import com.doumee.core.job.JobContext;
import com.doumee.core.job.JobParam;
import com.doumee.service.business.OrdersService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
 * è‡ªåŠ¨è¯„ä»·
 * @author  dm
 * @since 2025/03/31 16:44
 */
@Slf4j
@Component("AutoCommentJob")
public class AutoCommentJob extends BaseJob {
    @Autowired
    private OrdersService ordersService;
    @Override
    public JobContext execute(JobParam param) {
        JobContext jobContext = new JobContext();
        try {
            ordersService.autoComment();
            jobContext.setContext("自动评价");
        } catch (Exception e) {
            e.printStackTrace();
        }
        return jobContext;
    }
}
server/admin/src/main/java/com/doumee/job/AutoConfirmJob.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,34 @@
package com.doumee.job;
import com.doumee.core.job.BaseJob;
import com.doumee.core.job.JobContext;
import com.doumee.core.job.JobParam;
import com.doumee.service.business.OrdersService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
 * è‡ªåŠ¨ç¡®è®¤è®¢å•ä¿®æ”¹
 * @author  dm
 * @since 2025/03/31 16:44
 */
@Slf4j
@Component("AutoConfirmJob")
public class AutoConfirmJob extends BaseJob {
    @Autowired
    private OrdersService ordersService;
    @Override
    public JobContext execute(JobParam param) {
        JobContext jobContext = new JobContext();
        try {
            ordersService.autoConfirm();
            jobContext.setContext("自动确认订单修改");
        } catch (Exception e) {
            e.printStackTrace();
        }
        return jobContext;
    }
}
server/admin/src/main/java/com/doumee/job/AutoGrabOrdersJob.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,34 @@
package com.doumee.job;
import com.doumee.core.job.BaseJob;
import com.doumee.core.job.JobContext;
import com.doumee.core.job.JobParam;
import com.doumee.service.business.OrdersService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
 * è‡ªåŠ¨æ´¾å•
 * @author  dm
 * @since 2025/03/31 16:44
 */
@Slf4j
@Component("AutoGrabOrdersJob")
public class AutoGrabOrdersJob extends BaseJob {
    @Autowired
    private OrdersService ordersService;
    @Override
    public JobContext execute(JobParam param) {
        JobContext jobContext = new JobContext();
        try {
            ordersService.autoGrabOrders();
            jobContext.setContext("自动派单");
        } catch (Exception e) {
            e.printStackTrace();
        }
        return jobContext;
    }
}
server/admin/src/main/java/com/doumee/job/InitializeCodeJob.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,34 @@
package com.doumee.job;
import com.doumee.core.job.BaseJob;
import com.doumee.core.job.JobContext;
import com.doumee.core.job.JobParam;
import com.doumee.service.business.OrdersService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
 * åˆå§‹åŒ–订单编号
 * @author  dm
 * @since 2025/03/31 16:44
 */
@Slf4j
@Component("InitializeCodeJob")
public class InitializeCodeJob extends BaseJob {
    @Autowired
    private OrdersService ordersService;
    @Override
    public JobContext execute(JobParam param) {
        JobContext jobContext = new JobContext();
        try {
            ordersService.initializeCode();
            jobContext.setContext("初始化订单编号");
        } catch (Exception e) {
            e.printStackTrace();
        }
        return jobContext;
    }
}
server/pom.xml
@@ -262,6 +262,8 @@
      <artifactId>wechatpay-java</artifactId>
      <version>0.2.12</version>
    </dependency>
 <!--   <dependency>
      <groupId>com.azure.spring</groupId>
      <artifactId>spring-cloud-azure-starter-storage-blob</artifactId>
server/services/src/main/java/com/doumee/api/common/PublicCloudController.java
@@ -61,7 +61,7 @@
    })
    @PostMapping(value = "/upload", headers = "content-type=multipart/form-data")
    @ResponseBody
    public void upload(HttpServletRequest request, HttpServletResponse response, String folder) throws Exception {
    public void upload(HttpServletRequest request, HttpServletResponse response, String folder,String flag) throws Exception {
        Date d1 = new Date();
        log.error("总得上传文件成功=============开始========="+DateUtil.getPlusTime2(d1));
        if(Objects.isNull(folder)){
@@ -105,6 +105,7 @@
                        fileJSON.put("imgaddr", fName);
                        fileJSON.put("imgname", fileName);
                        fileJSON.put("originname", originname);
                        fileJSON.put("flag", flag);
                        context.put("data",fileJSON);
                        context.put("message","请求成功");
                        writerJson(response, context);
server/services/src/main/java/com/doumee/config/jwt/JwtTokenUtil.java
@@ -112,7 +112,8 @@
                .setExpiration(expirationDate)
                .signWith(SignatureAlgorithm.HS512, jwtProperties.getSecret())
                .compact();
        redisTemplate.opsForValue().set(Constants.REDIS_TOKEN_KEY+token,JSONObject.toJSONString(member),jwtProperties.getExpiration(), TimeUnit.MILLISECONDS);
        redisTemplate.opsForValue().set(Constants.REDIS_TOKEN_KEY+token,JSONObject.toJSONString(member),
                jwtProperties.getExpiration(), TimeUnit.MILLISECONDS);
        return token;
    }
server/services/src/main/java/com/doumee/config/wx/TransferToUser.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,312 @@
package com.doumee.config.wx;
import com.alibaba.fastjson.JSONObject;
import com.google.gson.annotations.Expose;
import com.google.gson.annotations.SerializedName;
import okhttp3.*;
import org.springframework.beans.factory.annotation.Autowired;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.ArrayList;
import java.util.List;
/**
 * Created by IntelliJ IDEA.
 *
 * @Author : Rk
 * @create 2025/7/17 9:19
 */
public class TransferToUser {
    private static String HOST = "https://api.mch.weixin.qq.com";
    private static String METHOD = "POST";
    private static String PATH = "/v3/fund-app/mch-transfer/transfer-bills";
    private static String CANCEL_PATH = "/v3/fund-app/mch-transfer/transfer-bills/out-bill-no/{out_bill_no}/cancel";
    public static void main(String[] args) {
        // TODO: è¯·å‡†å¤‡å•†æˆ·å¼€å‘必要参数,参考:https://pay.weixin.qq.com/doc/v3/merchant/4013070756
        TransferToUser client = new TransferToUser(
                "1229817002",                    // å•†æˆ·å·ï¼Œæ˜¯ç”±å¾®ä¿¡æ”¯ä»˜ç³»ç»Ÿç”Ÿæˆå¹¶åˆ†é…ç»™æ¯ä¸ªå•†æˆ·çš„唯一标识符,商户号获取方式参考 https://pay.weixin.qq.com/doc/v3/merchant/4013070756
                "3FE90C2F3D40A56E1C51926F31B8A8D22426CCE0",         // å•†æˆ·API证书序列号,如何获取请参考 https://pay.weixin.qq.com/doc/v3/merchant/4013053053
                "d://wechatApiclient_key.pem",    // å•†æˆ·API证书私钥文件路径,本地文件路径
                "PUB_KEY_ID_0112298170022025071700291836000600",      // å¾®ä¿¡æ”¯ä»˜å…¬é’¥ID,如何获取请参考 https://pay.weixin.qq.com/doc/v3/merchant/4013038816
                "d://pub_key.pem"          // å¾®ä¿¡æ”¯ä»˜å…¬é’¥æ–‡ä»¶è·¯å¾„,本地文件路径
        );
        TransferToUserRequest request = new TransferToUserRequest();
        request.appid = "wxcd2b89fd2ff065f8"; //小程序id
        request.outBillNo = "plfk2020042016";
        request.transferSceneId = "1005";
        request.openid = "oKKHU5IFKpss_DIbFX1lqghFJOEg";
        request.userName = client.encrypt("施旭辉");
        request.transferAmount = 91L;
        request.transferRemark = "~~~";
        request.notifyUrl = "https://www.weixin.qq.com/wxpay/pay.php";
        request.userRecvPerception = "劳务报酬";
        request.transferSceneReportInfos = new ArrayList<>();
        {
            TransferSceneReportInfo item0 = new TransferSceneReportInfo();
            item0.infoType = "岗位类型";
            item0.infoContent = "完成订单";
            request.transferSceneReportInfos.add(item0);
            TransferSceneReportInfo item1 = new TransferSceneReportInfo();
            item1.infoType = "报酬说明";
            item1.infoContent = "订单报酬";
            request.transferSceneReportInfos.add(item1);
        };
        try {
            TransferToUserResponse response = client.testRun(request);
            // TODO: è¯·æ±‚成功,继续业务逻辑
            System.out.println(JSONObject.toJSONString(response));
        } catch (WXPayUtility.ApiException e) {
            // TODO: è¯·æ±‚失败,根据状态码执行不同的逻辑
            e.printStackTrace();
        }
    }
    private final String mchid;
    private final String certificateSerialNo;
    private final PrivateKey privateKey;
    private final String wechatPayPublicKeyId;
    private final PublicKey wechatPayPublicKey;
    public TransferToUser(String mchid, String certificateSerialNo, String privateKeyFilePath, String wechatPayPublicKeyId, String wechatPayPublicKeyFilePath) {
        this.mchid = mchid;
        this.certificateSerialNo = certificateSerialNo;
        this.privateKey = WXPayUtility.loadPrivateKeyFromPath(privateKeyFilePath);
        this.wechatPayPublicKeyId = wechatPayPublicKeyId;
        this.wechatPayPublicKey = WXPayUtility.loadPublicKeyFromPath(wechatPayPublicKeyFilePath);
    }
    public TransferToUserResponse testRun(TransferToUserRequest request) {
        String uri = PATH;
        String reqBody = WXPayUtility.toJson(request);
        Request.Builder reqBuilder = new Request.Builder().url(HOST + uri);
        reqBuilder.addHeader("Accept", "application/json");
        reqBuilder.addHeader("Wechatpay-Serial", wechatPayPublicKeyId);
        reqBuilder.addHeader("Authorization", WXPayUtility.buildAuthorization(mchid, certificateSerialNo,privateKey, METHOD, uri, reqBody));
        reqBuilder.addHeader("Content-Type", "application/json");
        RequestBody requestBody = RequestBody.create(MediaType.parse("application/json; charset=utf-8"), reqBody);
        reqBuilder.method(METHOD, requestBody);
        Request httpRequest = reqBuilder.build();
        // å‘送HTTP请求
        OkHttpClient client = new OkHttpClient.Builder().build();
        try (Response httpResponse = client.newCall(httpRequest).execute()) {
            String respBody = WXPayUtility.extractBody(httpResponse);
            if (httpResponse.code() >= 200 && httpResponse.code() < 300) {
                // 2XX æˆåŠŸï¼ŒéªŒè¯åº”ç­”ç­¾å
                WXPayUtility.validateResponse(this.wechatPayPublicKeyId, this.wechatPayPublicKey,
                        httpResponse.headers(), respBody);
                // ä»ŽHTTP应答报文构建返回数据
                return WXPayUtility.fromJson(respBody, TransferToUserResponse.class);
            } else {
                throw new WXPayUtility.ApiException(httpResponse.code(), respBody, httpResponse.headers());
            }
        } catch (IOException e) {
            throw new UncheckedIOException("Sending request to " + uri + " failed.", e);
        }
    }
    public TransferToUserResponse run(TransferToUserRequest request,String name) {
        String uri = PATH;
        request.appid = WxMiniConfig.wxProperties.getSubAppId();
        request.transferSceneId = "1005";
        request.userRecvPerception = "劳务报酬";
        if(request.transferAmount >= 30){
            request.userName = this.encrypt(name);
        }
        request.transferSceneReportInfos = new ArrayList<>();
        {
            TransferSceneReportInfo item0 = new TransferSceneReportInfo();
            item0.infoType = "岗位类型";
            item0.infoContent = "完成订单";
            request.transferSceneReportInfos.add(item0);
            TransferSceneReportInfo item1 = new TransferSceneReportInfo();
            item1.infoType = "报酬说明";
            item1.infoContent = "订单报酬";
            request.transferSceneReportInfos.add(item1);
        };
        String reqBody = WXPayUtility.toJson(request);
        Request.Builder reqBuilder = new Request.Builder().url(HOST + uri);
        reqBuilder.addHeader("Accept", "application/json");
        reqBuilder.addHeader("Wechatpay-Serial", WxMiniConfig.wxProperties.getWechatPayPublicKeyId());
        reqBuilder.addHeader("Authorization", WXPayUtility.buildAuthorization(WxMiniConfig.wxProperties.getMchId(),
                WxMiniConfig.wxProperties.getWechatSerialNumer(),
                WXPayUtility.loadPrivateKeyFromPath(WxMiniConfig.wxProperties.getWechatPrivateKeyPath()),
                METHOD, uri, reqBody));
//        reqBuilder.addHeader("Authorization", WXPayUtility.buildAuthorization(mchid, certificateSerialNo,privateKey, METHOD, uri, reqBody));
        reqBuilder.addHeader("Content-Type", "application/json");
        RequestBody requestBody = RequestBody.create(MediaType.parse("application/json; charset=utf-8"), reqBody);
        reqBuilder.method(METHOD, requestBody);
        Request httpRequest = reqBuilder.build();
        // å‘送HTTP请求
        OkHttpClient client = new OkHttpClient.Builder().build();
        try (Response httpResponse = client.newCall(httpRequest).execute()) {
            String respBody = WXPayUtility.extractBody(httpResponse);
            if (httpResponse.code() >= 200 && httpResponse.code() < 300) {
                // 2XX æˆåŠŸï¼ŒéªŒè¯åº”ç­”ç­¾å
                WXPayUtility.validateResponse(WxMiniConfig.wxProperties.getWechatPayPublicKeyId(), WXPayUtility.loadPublicKeyFromPath(WxMiniConfig.wxProperties.getWechatPubKeyPath()),
                        httpResponse.headers(), respBody);
                // ä»ŽHTTP应答报文构建返回数据
                return WXPayUtility.fromJson(respBody, TransferToUserResponse.class);
            } else {
                throw new WXPayUtility.ApiException(httpResponse.code(), respBody, httpResponse.headers());
            }
        } catch (IOException e) {
            throw new UncheckedIOException("Sending request to " + uri + " failed.", e);
        }
    }
    public TransferToUser.CancelTransferResponse cancelRun(TransferToUser.CancelTransferRequest request) {
        String uri = CANCEL_PATH;
        uri = uri.replace("{out_bill_no}", WXPayUtility.urlEncode(request.outBillNo));
        Request.Builder reqBuilder = new Request.Builder().url(HOST + uri);
        reqBuilder.addHeader("Accept", "application/json");
        reqBuilder.addHeader("Wechatpay-Serial", wechatPayPublicKeyId);
        reqBuilder.addHeader("Authorization", WXPayUtility.buildAuthorization(WxMiniConfig.wxProperties.getMchId(),
                WxMiniConfig.wxProperties.getWechatSerialNumer(),
                WXPayUtility.loadPrivateKeyFromPath(WxMiniConfig.wxProperties.getWechatPrivateKeyPath()),
                METHOD, uri, null));
        reqBuilder.addHeader("Content-Type", "application/json");
        RequestBody emptyBody = RequestBody.create(null, "");
        reqBuilder.method(METHOD, emptyBody);
        Request httpRequest = reqBuilder.build();
        // å‘送HTTP请求
        OkHttpClient client = new OkHttpClient.Builder().build();
        try (Response httpResponse = client.newCall(httpRequest).execute()) {
            String respBody = WXPayUtility.extractBody(httpResponse);
            if (httpResponse.code() >= 200 && httpResponse.code() < 300) {
                // 2XX æˆåŠŸï¼ŒéªŒè¯åº”ç­”ç­¾å
                WXPayUtility.validateResponse(WxMiniConfig.wxProperties.getWechatPayPublicKeyId(), WXPayUtility.loadPublicKeyFromPath(WxMiniConfig.wxProperties.getWechatPubKeyPath()),
                        httpResponse.headers(), respBody);
                // ä»ŽHTTP应答报文构建返回数据
                return WXPayUtility.fromJson(respBody, TransferToUser.CancelTransferResponse.class);
            } else {
                throw new WXPayUtility.ApiException(httpResponse.code(), respBody, httpResponse.headers());
            }
        } catch (IOException e) {
            throw new UncheckedIOException("Sending request to " + uri + " failed.", e);
        }
    }
    public String encrypt(String plainText) {
        return WXPayUtility.encrypt(this.wechatPayPublicKey, plainText);
    }
    public static class TransferToUserResponse {
        @SerializedName("out_bill_no")
        public String outBillNo;
        @SerializedName("transfer_bill_no")
        public String transferBillNo;
        @SerializedName("create_time")
        public String createTime;
        @SerializedName("state")
        public TransferBillStatus state;
        @SerializedName("package_info")
        public String packageInfo;
    }
    public enum TransferBillStatus {
        @SerializedName("ACCEPTED")
        ACCEPTED,
        @SerializedName("PROCESSING")
        PROCESSING,
        @SerializedName("WAIT_USER_CONFIRM")
        WAIT_USER_CONFIRM,
        @SerializedName("TRANSFERING")
        TRANSFERING,
        @SerializedName("SUCCESS")
        SUCCESS,
        @SerializedName("FAIL")
        FAIL,
        @SerializedName("CANCELING")
        CANCELING,
        @SerializedName("CANCELLED")
        CANCELLED
    }
    public static class TransferSceneReportInfo {
        @SerializedName("info_type")
        public String infoType;
        @SerializedName("info_content")
        public String infoContent;
    }
    public static class TransferToUserRequest {
        @SerializedName("appid")
        public String appid;
        @SerializedName("out_bill_no")
        public String outBillNo;
        @SerializedName("transfer_scene_id")
        public String transferSceneId;
        @SerializedName("openid")
        public String openid;
        @SerializedName("user_name")
        public String userName;
        @SerializedName("transfer_amount")
        public Long transferAmount;
        @SerializedName("transfer_remark")
        public String transferRemark;
        @SerializedName("notify_url")
        public String notifyUrl;
        @SerializedName("user_recv_perception")
        public String userRecvPerception;
        @SerializedName("transfer_scene_report_infos")
        public List<TransferSceneReportInfo> transferSceneReportInfos;
    }
    public static class CancelTransferResponse {
        @SerializedName("out_bill_no")
        public String outBillNo;
        @SerializedName("transfer_bill_no")
        public String transferBillNo;
        @SerializedName("state")
        public String state;
        @SerializedName("update_time")
        public String updateTime;
    }
    public static class CancelTransferRequest {
        @SerializedName("out_bill_no")
        @Expose(serialize = false)
        public String outBillNo;
    }
}
server/services/src/main/java/com/doumee/config/wx/WXPayUtility.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,456 @@
package com.doumee.config.wx;
import com.google.gson.ExclusionStrategy;
import com.google.gson.FieldAttributes;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.google.gson.JsonSyntaxException;
import com.google.gson.annotations.Expose;
import com.wechat.pay.java.core.util.GsonUtil;
import okhttp3.Headers;
import okhttp3.Response;
import okio.BufferedSource;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.Signature;
import java.security.SignatureException;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.time.DateTimeException;
import java.time.Duration;
import java.time.Instant;
import java.util.Base64;
import java.util.Map;
import java.util.Objects;
public class WXPayUtility {
    private static final Gson gson = new GsonBuilder()
            .disableHtmlEscaping()
            .addSerializationExclusionStrategy(new ExclusionStrategy() {
                @Override
                public boolean shouldSkipField(FieldAttributes fieldAttributes) {
                    final Expose expose = fieldAttributes.getAnnotation(Expose.class);
                    return expose != null && !expose.serialize();
                }
                @Override
                public boolean shouldSkipClass(Class<?> aClass) {
                    return false;
                }
            })
            .addDeserializationExclusionStrategy(new ExclusionStrategy() {
                @Override
                public boolean shouldSkipField(FieldAttributes fieldAttributes) {
                    final Expose expose = fieldAttributes.getAnnotation(Expose.class);
                    return expose != null && !expose.deserialize();
                }
                @Override
                public boolean shouldSkipClass(Class<?> aClass) {
                    return false;
                }
            })
            .create();
    private static final char[] SYMBOLS =
            "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ".toCharArray();
    private static final SecureRandom random = new SecureRandom();
    /**
     * å°† Object è½¬æ¢ä¸º JSON å­—符串
     */
    public static String toJson(Object object) {
        return gson.toJson(object);
    }
    /**
     * å°† JSON å­—符串解析为特定类型的实例
     */
    public static <T> T fromJson(String json, Class<T> classOfT) throws JsonSyntaxException {
        return gson.fromJson(json, classOfT);
    }
    /**
     * ä»Žå…¬ç§é’¥æ–‡ä»¶è·¯å¾„中读取文件内容
     *
     * @param keyPath æ–‡ä»¶è·¯å¾„
     * @return æ–‡ä»¶å†…容
     */
    private static String readKeyStringFromPath(String keyPath) {
        try {
            return new String(Files.readAllBytes(Paths.get(keyPath)), StandardCharsets.UTF_8);
        } catch (IOException e) {
            throw new UncheckedIOException(e);
        }
    }
    /**
     * è¯»å– PKCS#8 æ ¼å¼çš„私钥字符串并加载为私钥对象
     *
     * @param keyString ç§é’¥æ–‡ä»¶å†…容,以 -----BEGIN PRIVATE KEY----- å¼€å¤´
     * @return PrivateKey å¯¹è±¡
     */
    public static PrivateKey loadPrivateKeyFromString(String keyString) {
        try {
            keyString = keyString.replace("-----BEGIN PRIVATE KEY-----", "")
                    .replace("-----END PRIVATE KEY-----", "")
                    .replaceAll("\\s+", "");
            return KeyFactory.getInstance("RSA").generatePrivate(
                    new PKCS8EncodedKeySpec(Base64.getDecoder().decode(keyString)));
        } catch (NoSuchAlgorithmException e) {
            throw new UnsupportedOperationException(e);
        } catch (InvalidKeySpecException e) {
            throw new IllegalArgumentException(e);
        }
    }
    /**
     * ä»Ž PKCS#8 æ ¼å¼çš„私钥文件中加载私钥
     *
     * @param keyPath ç§é’¥æ–‡ä»¶è·¯å¾„
     * @return PrivateKey å¯¹è±¡
     */
    public static PrivateKey loadPrivateKeyFromPath(String keyPath) {
        return loadPrivateKeyFromString(readKeyStringFromPath(keyPath));
    }
    /**
     * è¯»å– PKCS#8 æ ¼å¼çš„公钥字符串并加载为公钥对象
     *
     * @param keyString å…¬é’¥æ–‡ä»¶å†…容,以 -----BEGIN PUBLIC KEY----- å¼€å¤´
     * @return PublicKey å¯¹è±¡
     */
    public static PublicKey loadPublicKeyFromString(String keyString) {
        try {
            keyString = keyString.replace("-----BEGIN PUBLIC KEY-----", "")
                    .replace("-----END PUBLIC KEY-----", "")
                    .replaceAll("\\s+", "");
            return KeyFactory.getInstance("RSA").generatePublic(
                    new X509EncodedKeySpec(Base64.getDecoder().decode(keyString)));
        } catch (NoSuchAlgorithmException e) {
            throw new UnsupportedOperationException(e);
        } catch (InvalidKeySpecException e) {
            throw new IllegalArgumentException(e);
        }
    }
    /**
     * ä»Ž PKCS#8 æ ¼å¼çš„公钥文件中加载公钥
     *
     * @param keyPath å…¬é’¥æ–‡ä»¶è·¯å¾„
     * @return PublicKey å¯¹è±¡
     */
    public static PublicKey loadPublicKeyFromPath(String keyPath) {
        return loadPublicKeyFromString(readKeyStringFromPath(keyPath));
    }
    /**
     * åˆ›å»ºæŒ‡å®šé•¿åº¦çš„随机字符串,字符集为[0-9a-zA-Z],可用于安全相关用途
     */
    public static String createNonce(int length) {
        char[] buf = new char[length];
        for (int i = 0; i < length; ++i) {
            buf[i] = SYMBOLS[random.nextInt(SYMBOLS.length)];
        }
        return new String(buf);
    }
    /**
     * ä½¿ç”¨å…¬é’¥æŒ‰ç…§ RSA_PKCS1_OAEP_PADDING ç®—法进行加密
     *
     * @param publicKey åŠ å¯†ç”¨å…¬é’¥å¯¹è±¡
     * @param plaintext å¾…加密明文
     * @return åŠ å¯†åŽå¯†æ–‡
     */
    public static String encrypt(PublicKey publicKey, String plaintext) {
        final String transformation = "RSA/ECB/OAEPWithSHA-1AndMGF1Padding";
        try {
            Cipher cipher = Cipher.getInstance(transformation);
            cipher.init(Cipher.ENCRYPT_MODE, publicKey);
            return Base64.getEncoder().encodeToString(cipher.doFinal(plaintext.getBytes(StandardCharsets.UTF_8)));
        } catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
            throw new IllegalArgumentException("The current Java environment does not support " + transformation, e);
        } catch (InvalidKeyException e) {
            throw new IllegalArgumentException("RSA encryption using an illegal publicKey", e);
        } catch (BadPaddingException | IllegalBlockSizeException e) {
            throw new IllegalArgumentException("Plaintext is too long", e);
        }
    }
    /**
     * ä½¿ç”¨ç§é’¥æŒ‰ç…§æŒ‡å®šç®—法进行签名
     *
     * @param message å¾…签名串
     * @param algorithm ç­¾åç®—法,如 SHA256withRSA
     * @param privateKey ç­¾åç”¨ç§é’¥å¯¹è±¡
     * @return ç­¾åç»“æžœ
     */
    public static String sign(String message, String algorithm, PrivateKey privateKey) {
        byte[] sign;
        try {
            Signature signature = Signature.getInstance(algorithm);
            signature.initSign(privateKey);
            signature.update(message.getBytes(StandardCharsets.UTF_8));
            sign = signature.sign();
        } catch (NoSuchAlgorithmException e) {
            throw new UnsupportedOperationException("The current Java environment does not support " + algorithm, e);
        } catch (InvalidKeyException e) {
            throw new IllegalArgumentException(algorithm + " signature uses an illegal privateKey.", e);
        } catch (SignatureException e) {
            throw new RuntimeException("An error occurred during the sign process.", e);
        }
        return Base64.getEncoder().encodeToString(sign);
    }
    /**
     * ä½¿ç”¨å…¬é’¥æŒ‰ç…§ç‰¹å®šç®—法验证签名
     *
     * @param message å¾…签名串
     * @param signature å¾…验证的签名内容
     * @param algorithm ç­¾åç®—法,如:SHA256withRSA
     * @param publicKey éªŒç­¾ç”¨å…¬é’¥å¯¹è±¡
     * @return ç­¾åéªŒè¯æ˜¯å¦é€šè¿‡
     */
    public static boolean verify(String message, String signature, String algorithm,
                                 PublicKey publicKey) {
        try {
            Signature sign = Signature.getInstance(algorithm);
            sign.initVerify(publicKey);
            sign.update(message.getBytes(StandardCharsets.UTF_8));
            return sign.verify(Base64.getDecoder().decode(signature));
        } catch (SignatureException e) {
            return false;
        } catch (InvalidKeyException e) {
            throw new IllegalArgumentException("verify uses an illegal publickey.", e);
        } catch (NoSuchAlgorithmException e) {
            throw new UnsupportedOperationException("The current Java environment does not support" + algorithm, e);
        }
    }
    /**
     * æ ¹æ®å¾®ä¿¡æ”¯ä»˜APIv3请求签名规则构造 Authorization ç­¾å
     *
     * @param mchid å•†æˆ·å·
     * @param certificateSerialNo å•†æˆ·API证书序列号
     * @param privateKey å•†æˆ·API证书私钥
     * @param method è¯·æ±‚接口的HTTP方法,请使用全大写表述,如 GET、POST、PUT、DELETE
     * @param uri è¯·æ±‚接口的URL
     * @param body è¯·æ±‚接口的Body
     * @return æž„造好的微信支付APIv3 Authorization å¤´
     */
    public static String buildAuthorization(String mchid, String certificateSerialNo,
                                            PrivateKey privateKey,
                                            String method, String uri, String body) {
        String nonce = createNonce(32);
        long timestamp = Instant.now().getEpochSecond();
        String message = String.format("%s\n%s\n%d\n%s\n%s\n", method, uri, timestamp, nonce,
                body == null ? "" : body);
        String signature = sign(message, "SHA256withRSA", privateKey);
        return String.format(
                "WECHATPAY2-SHA256-RSA2048 mchid=\"%s\",nonce_str=\"%s\",signature=\"%s\"," +
                        "timestamp=\"%d\",serial_no=\"%s\"",
                mchid, nonce, signature, timestamp, certificateSerialNo);
    }
    /**
     * å¯¹å‚数进行 URL ç¼–码
     *
     * @param content å‚数内容
     * @return ç¼–码后的内容
     */
    public static String urlEncode(String content) {
        try {
            return URLEncoder.encode(content, StandardCharsets.UTF_8.name());
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException(e);
        }
    }
    /**
     * å¯¹å‚æ•°Map进行 URL ç¼–码,生成 QueryString
     *
     * @param params Query参数Map
     * @return QueryString
     */
    public static String urlEncode(Map<String, Object> params) {
        if (params == null || params.isEmpty()) {
            return "";
        }
        int index = 0;
        StringBuilder result = new StringBuilder();
        for (Map.Entry<String, Object> entry : params.entrySet()) {
            result.append(entry.getKey())
                    .append("=")
                    .append(urlEncode(entry.getValue().toString()));
            index++;
            if (index < params.size()) {
                result.append("&");
            }
        }
        return result.toString();
    }
    /**
     * ä»Žåº”答中提取 Body
     *
     * @param response HTTP è¯·æ±‚应答对象
     * @return åº”答中的Body内容,Body为空时返回空字符串
     */
    public static String extractBody(Response response) {
        if (response.body() == null) {
            return "";
        }
        try {
            BufferedSource source = response.body().source();
            return source.readUtf8();
        } catch (IOException e) {
            throw new RuntimeException(String.format("An error occurred during reading response body. Status: %d", response.code()), e);
        }
    }
    /**
     * æ ¹æ®å¾®ä¿¡æ”¯ä»˜APIv3应答验签规则对应答签名进行验证,验证不通过时抛出异常
     *
     * @param wechatpayPublicKeyId å¾®ä¿¡æ”¯ä»˜å…¬é’¥ID
     * @param wechatpayPublicKey å¾®ä¿¡æ”¯ä»˜å…¬é’¥å¯¹è±¡
     * @param headers å¾®ä¿¡æ”¯ä»˜åº”ç­” Header åˆ—表
     * @param body å¾®ä¿¡æ”¯ä»˜åº”ç­” Body
     */
    public static void validateResponse(String wechatpayPublicKeyId, PublicKey wechatpayPublicKey,
                                        Headers headers,
                                        String body) {
        String timestamp = headers.get("Wechatpay-Timestamp");
        try {
            Instant responseTime = Instant.ofEpochSecond(Long.parseLong(timestamp));
            // æ‹’绝过期请求
            if (Duration.between(responseTime, Instant.now()).abs().toMinutes() >= 5) {
                throw new IllegalArgumentException(
                        String.format("Validate http response,timestamp[%s] of httpResponse is expires, "
                                        + "request-id[%s]",
                                timestamp, headers.get("Request-ID")));
            }
        } catch (DateTimeException | NumberFormatException e) {
            throw new IllegalArgumentException(
                    String.format("Validate http response,timestamp[%s] of httpResponse is invalid, " +
                                    "request-id[%s]", timestamp,
                            headers.get("Request-ID")));
        }
        String message = String.format("%s\n%s\n%s\n", timestamp, headers.get("Wechatpay-Nonce"),
                body == null ? "" : body);
        String serialNumber = headers.get("Wechatpay-Serial");
        if (!Objects.equals(serialNumber, wechatpayPublicKeyId)) {
            throw new IllegalArgumentException(
                    String.format("Invalid Wechatpay-Serial, Local: %s, Remote: %s", wechatpayPublicKeyId,
                            serialNumber));
        }
        String signature = headers.get("Wechatpay-Signature");
        boolean success = verify(message, signature, "SHA256withRSA", wechatpayPublicKey);
        if (!success) {
            throw new IllegalArgumentException(
                    String.format("Validate response failed,the WechatPay signature is incorrect.%n"
                                    + "Request-ID[%s]\tresponseHeader[%s]\tresponseBody[%.1024s]",
                            headers.get("Request-ID"), headers, body));
        }
    }
    /**
     * å¾®ä¿¡æ”¯ä»˜API错误异常,发送HTTP请求成功,但返回状态码不是 2XX æ—¶æŠ›å‡ºæœ¬å¼‚常
     */
    public static class ApiException extends RuntimeException {
        private static final long serialVersionUID = 2261086748874802175L;
        private final int statusCode;
        private final String body;
        private final Headers headers;
        private final String errorCode;
        private final String errorMessage;
        public ApiException(int statusCode, String body, Headers headers) {
            super(String.format("微信支付API访问失败,StatusCode: [%s], Body: [%s], Headers: [%s]", statusCode, body, headers));
            this.statusCode = statusCode;
            this.body = body;
            this.headers = headers;
            if (body != null && !body.isEmpty()) {
                JsonElement code;
                JsonElement message;
                try {
                    JsonObject jsonObject = GsonUtil.getGson().fromJson(body, JsonObject.class);
                    code = jsonObject.get("code");
                    message = jsonObject.get("message");
                } catch (JsonSyntaxException ignored) {
                    code = null;
                    message = null;
                }
                this.errorCode = code == null ? null : code.getAsString();
                this.errorMessage = message == null ? null : message.getAsString();
            } else {
                this.errorCode = null;
                this.errorMessage = null;
            }
        }
        /**
         * èŽ·å– HTTP åº”答状态码
         */
        public int getStatusCode() {
            return statusCode;
        }
        /**
         * èŽ·å– HTTP åº”答包体内容
         */
        public String getBody() {
            return body;
        }
        /**
         * èŽ·å– HTTP åº”ç­” Header
         */
        public Headers getHeaders() {
            return headers;
        }
        /**
         * èŽ·å– é”™è¯¯ç  ï¼ˆé”™è¯¯åº”答中的 code å­—段)
         */
        public String getErrorCode() {
            return errorCode;
        }
        /**
         * èŽ·å– é”™è¯¯æ¶ˆæ¯ ï¼ˆé”™è¯¯åº”答中的 message å­—段)
         */
        public String getErrorMessage() {
            return errorMessage;
        }
    }
}
server/services/src/main/java/com/doumee/config/wx/WxMiniConfig.java
@@ -36,7 +36,7 @@
    public static JsapiServiceExtension jsapiExtService;
    public static BillDownloadService billDownloadService;
    public static WxPayProperties wxProperties;
    public static  TransferToUser transferToUser;
    @Autowired
    private WxPayProperties wxPayProperties;
@@ -49,6 +49,8 @@
        this.load_WxMaService();
        this.load_wxPayService();
        this.load_wxPayV2Service();
        this.load_transferToUser();
        this.load_transferToUser();
        this.wxProperties = wxPayProperties;
    }
    /**
@@ -134,7 +136,18 @@
//    }
    public void load_transferToUser()
    {
        TransferToUser transferToUser = new TransferToUser(
                StringUtils.trimToNull(wxPayProperties.getMchId()), //商户id
                StringUtils.trimToNull(wxPayProperties.getWechatSerialNumer()), //商户API证书序列号,如何获取请参考 https://pay.weixin.qq.com/doc/v3/merchant/4013053053
                StringUtils.trimToNull(wxPayProperties.getWechatPrivateKeyPath()), // å•†æˆ·API证书私钥文件路径,本地文件路径
                StringUtils.trimToNull(wxPayProperties.getWechatPayPublicKeyId()),   // å¾®ä¿¡æ”¯ä»˜å…¬é’¥ID,如何获取请参考 https://pay.weixin.qq.com/doc/v3/merchant/4013038816
                StringUtils.trimToNull(wxPayProperties.getWechatPubKeyPath()) // å¾®ä¿¡æ”¯ä»˜å…¬é’¥æ–‡ä»¶è·¯å¾„,本地文件路径
        );
        this.transferToUser = transferToUser;
    }
}
server/services/src/main/java/com/doumee/config/wx/WxPayProperties.java
@@ -63,4 +63,10 @@
    private String privateKeyPath ;
    private String privateCertPath;// gybike
    private String wechatSerialNumer;
    private String wechatPayPublicKeyId; // å¾®ä¿¡æ”¯ä»˜å…¬é’¥ID
    private String wechatPubKeyPath; //微信支付公钥文件路径
    private String wechatPrivateKeyPath;
}
server/services/src/main/java/com/doumee/dao/business/model/Member.java
@@ -190,4 +190,11 @@
    @TableField(exist = false)
    private IdentityInfo chefIdentityModel;
    @ApiModelProperty(value = "接单权重", example = "1")
    @TableField(exist = false)
    private Integer level;
    @ApiModelProperty(value = "距离", example = "1")
    @TableField(exist = false)
    private BigDecimal distance;
}
server/services/src/main/java/com/doumee/dao/business/model/MemberRevenue.java
@@ -47,7 +47,7 @@
    @ApiModelProperty(value = "会员主键", example = "1")
    private Integer memberId;
    @ApiModelProperty(value = "变动类型:0=用工单收入;1=货运单收入;2=供餐单收入;3=提现申请;", example = "1")
    @ApiModelProperty(value = "变动类型:0=用工单收入;1=货运单收入;2=供餐单收入;3=提现申请;4=提现失败退回", example = "1")
    private Integer type;
    @ApiModelProperty(value = "收支类型:1=收入;-1=支出;", example = "1")
server/services/src/main/java/com/doumee/dao/business/model/Orders.java
@@ -90,10 +90,10 @@
    @ApiModelProperty(value = "用工类型:0=采摘工;1=分拣工;2=包装工;(用工订单)", example = "1")
    private Integer workType;
    @ApiModelProperty(value = "计价数量1(天数/用车次数/小时/斤数)", example = "1")
    @ApiModelProperty(value = "计价数量1((重量/人数/(天数/小时/重量)/(天数/次数)/用餐天数)", example = "1")
    private Integer priceNum1;
    @ApiModelProperty(value = "计价数量2(人数/用餐份数)", example = "1")
    @ApiModelProperty(value = "计价数量2(分拣工/包装工 äººæ•°)", example = "1")
    private Integer priceNum2;
    @ApiModelProperty(value = "需求补充")
@@ -102,10 +102,8 @@
    @ApiModelProperty(value = "费用标准", example = "1")
    private Long price;
    @ApiModelProperty(value = "预估费用", example = "1")
    private Long estimatedAccount;
    @ApiModelProperty(value = "实际支付费用", example = "1")
    private Long payAccount;
server/services/src/main/java/com/doumee/dao/business/model/WithdrawalOrders.java
@@ -27,7 +27,7 @@
    @ApiModelProperty(value = "是否已删除 0未删除 1已删除", example = "1")
    @ExcelColumn(name="是否已删除 0未删除 1已删除")
    private Byte deleted;
    private Integer deleted;
    @ApiModelProperty(value = "创建人编码", example = "1")
    @ExcelColumn(name="创建人编码")
@@ -84,4 +84,10 @@
    @ApiModelProperty(value = "关联订单主键 orders", example = "1")
    @ExcelColumn(name="关联订单主键 orders")
    private Integer objId;
    @ApiModelProperty(value = "提现申请单号")
    @ExcelColumn(name="提现申请单号")
    private String outBillNo;
}
server/services/src/main/java/com/doumee/dao/vo/OrderReleaseVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,21 @@
package com.doumee.dao.vo;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
/**
 * Created by IntelliJ IDEA.
 *
 * @Author : Rk
 * @create 2025/7/10 15:09
 */
@Data
public class OrderReleaseVO {
    @ApiModelProperty(value = "订单主键")
    private Integer id;
    @ApiModelProperty(value = "支付详情")
    private Object object;
}
server/services/src/main/java/com/doumee/service/business/MemberService.java
@@ -149,4 +149,6 @@
    Member getMemberInfo(Integer memberId);
    UserCenterVO getPlatformAboutUs();
    void logOff(String token,Integer memberId);
}
server/services/src/main/java/com/doumee/service/business/OrdersService.java
@@ -9,6 +9,7 @@
import com.doumee.dao.dto.ConfirmUpdOrderDTO;
import com.doumee.dao.dto.DoneOrderDTO;
import com.doumee.dao.dto.UpdOrderDataDTO;
import com.doumee.dao.vo.OrderReleaseVO;
import com.wechat.pay.java.service.refund.model.RefundNotification;
import java.util.List;
@@ -26,7 +27,7 @@
     * @param orders å®žä½“对象
     * @return Integer
     */
    Object create(Orders orders);
    OrderReleaseVO create(Orders orders);
    /**
     * ä¸»é”®åˆ é™¤
@@ -137,4 +138,10 @@
    void autoComment();
    void autoConfirm();
    Long getTotal(Orders orders);
    void autoGrabOrders();
    void initializeCode();
}
server/services/src/main/java/com/doumee/service/business/WithdrawalOrdersService.java
@@ -1,5 +1,6 @@
package com.doumee.service.business;
import com.doumee.config.wx.TransferToUser;
import com.doumee.core.model.PageData;
import com.doumee.core.model.PageWrap;
import com.doumee.dao.business.model.WithdrawalOrders;
@@ -97,6 +98,8 @@
     */
    long count(WithdrawalOrders withdrawalOrders);
    void  applyWithdrawal(WithdrawalDTO withdrawalDTO);
    TransferToUser.TransferToUserResponse  applyWithdrawal(WithdrawalDTO withdrawalDTO);
    void cancelTransfer(TransferToUser.CancelTransferRequest request);
}
server/services/src/main/java/com/doumee/service/business/impl/CategoryServiceImpl.java
@@ -254,10 +254,15 @@
        List<Category> categoryList = categoryMapper.selectList(new QueryWrapper<Category>().lambda().eq(Category::getDeleted,Constants.ZERO).eq(Category::getStatus,Constants.ZERO)
                .eq(Objects.nonNull(type),Category::getType,type));
        if(com.github.xiaoymin.knife4j.core.util.CollectionUtils.isNotEmpty(categoryList)){
            String path  = systemDictDataBiz.queryByCode(Constants.SYSTEM,Constants.RESOURCE_PATH).getCode()
                    +systemDictDataBiz.queryByCode(Constants.SYSTEM,Constants.CATEGORY_FILES).getCode();
            for (Category category:categoryList) {
                if(StringUtils.isNotBlank(category.getDetail())){
                    category.setDetailList(JSONArray.parseArray(category.getDetail()));
                }
                if(StringUtils.isNotBlank(category.getIcon())){
                    category.setIconFull(path + category.getIcon());
                }
            }
        }
        return categoryList;
server/services/src/main/java/com/doumee/service/business/impl/MemberServiceImpl.java
@@ -2,6 +2,7 @@
import cn.binarywang.wx.miniapp.bean.WxMaJscode2SessionResult;
import cn.binarywang.wx.miniapp.bean.WxMaPhoneNumberInfo;
import com.alibaba.fastjson.JSONObject;
import com.doumee.biz.system.SystemDictDataBiz;
import com.doumee.config.jwt.JwtTokenUtil;
import com.doumee.config.wx.WxMiniConfig;
@@ -33,13 +34,16 @@
import nonapi.io.github.classgraph.json.Id;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import javax.annotation.Resource;
import java.util.Date;
import java.util.List;
import java.util.Objects;
import java.util.Random;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
/**
@@ -342,7 +346,10 @@
    @Override
    public void editMemberInfo(Member member){
        if(Objects.isNull(member)
            || ( StringUtils.isBlank(member.getCoverImage()) || StringUtils.isBlank(member.getName()) || StringUtils.isBlank(member.getNickName()) || Objects.isNull(member.getAutoReceiveStatus()) )){
            || ( StringUtils.isBlank(member.getCoverImage())
                && StringUtils.isBlank(member.getName())
                && StringUtils.isBlank(member.getNickName())
                && Objects.isNull(member.getAutoReceiveStatus()) )){
            throw new BusinessException(ResponseStatus.BAD_REQUEST);
        }
        memberMapper.update(new UpdateWrapper<Member>().lambda()
@@ -381,6 +388,11 @@
        Member member  = this.findById(memberId);
        if(Objects.isNull(member)){
            throw new BusinessException(ResponseStatus.DATA_EMPTY);
        }
        if(StringUtils.isNotBlank(member.getCoverImage())){
            String path  = systemDictDataBiz.queryByCode(Constants.SYSTEM,Constants.RESOURCE_PATH).getCode()
                    +systemDictDataBiz.queryByCode(Constants.SYSTEM,Constants.MEMBER_FILES).getCode();
            member.setFullCoverImage(path + member.getCoverImage());
        }
        UserCenterVO userCenterVO = new UserCenterVO();
        userCenterVO.setReleaseTaskTotal(Constants.ZERO);
@@ -422,6 +434,11 @@
        return userCenterVO;
    }
    @Override
    public void logOff(String token,Integer memberId){
        memberMapper.update(new UpdateWrapper<Member>().lambda().setSql(" openid = null ").eq(Member::getId,memberId));
        jwtTokenUtil.logoutForH5(token);
    }
server/services/src/main/java/com/doumee/service/business/impl/OrdersServiceImpl.java
@@ -17,6 +17,7 @@
import com.doumee.dao.business.*;
import com.doumee.dao.business.model.*;
import com.doumee.dao.dto.*;
import com.doumee.dao.vo.OrderReleaseVO;
import com.doumee.service.business.OrdersService;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
@@ -42,10 +43,7 @@
import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.Date;
import java.util.List;
import java.util.Objects;
import java.util.UUID;
import java.util.*;
import java.util.concurrent.TimeUnit;
/**
@@ -91,7 +89,8 @@
    @Override
    @Transactional(rollbackFor = {Exception.class,BusinessException.class})
    public Object create(Orders orders) {
    public OrderReleaseVO create(Orders orders) {
        OrderReleaseVO orderReleaseVO = new OrderReleaseVO();
        Object objects = null;
        this.initVerification(orders);
        orders.setCreateTime(new Date());
@@ -120,15 +119,16 @@
            orders.setStatus(Constants.ordersStatus.waitPay.getKey());
            //唤起支付业务
            objects = this.getWxPayResponse(orders,orders.getMember().getOpenid());
            orderReleaseVO.setObject(objects);
        }else{
            orders.setStatus(Constants.ordersStatus.wait.getKey());
        }
        ordersMapper.insert(orders);
        orderReleaseVO.setId(orders.getId());
        if(com.github.xiaoymin.knife4j.core.util.CollectionUtils.isNotEmpty(orders.getMultifileList())){
            List<Multifile> multifileList = orders.getMultifileList();
            for (Multifile multifile:multifileList) {
                if(Objects.isNull(multifile)
                || Objects.isNull(multifile.getObjType())
                || StringUtils.isEmpty(multifile.getFileurl())
                || StringUtils.isEmpty(multifile.getName())){
                    throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(),"附件信息错误");
@@ -159,7 +159,7 @@
                .replace("{param}",orderTypeInfo);
        this.saveOrderLog(orders,ordersLog,
                logInfo,orders.getReleaseMemberId(),null);
        return objects;
        return orderReleaseVO;
    }
    private Object getWxPayResponse(Orders orders,String openid){
@@ -219,7 +219,7 @@
                || Objects.isNull(orders.getStartDate())
                || Objects.isNull(orders.getEndDate())
                || StringUtil.isBlank(orders.getLocation())
                || StringUtil.isBlank(orders.getLocationRemark())
                || (!Constants.equalsInteger(Constants.ONE,orders.getType())&&StringUtil.isBlank(orders.getLocationRemark()))
                || StringUtil.isBlank(orders.getLinkPhone())
                || Objects.isNull(orders.getLat())
                || Objects.isNull(orders.getLgt())
@@ -263,9 +263,9 @@
            for (WayInfoDTO wayInfoDTO:orders.getWayInfoDTOList()) {
                if(Objects.isNull(wayInfoDTO)
                        ||StringUtils.isEmpty(wayInfoDTO.getLocation())
                        ||StringUtils.isEmpty(wayInfoDTO.getProvince())
                        ||StringUtils.isEmpty(wayInfoDTO.getCity())
                        ||StringUtils.isEmpty(wayInfoDTO.getArea())
//                        ||StringUtils.isEmpty(wayInfoDTO.getProvince())
//                        ||StringUtils.isEmpty(wayInfoDTO.getCity())
//                        ||StringUtils.isEmpty(wayInfoDTO.getArea())
                        ||Objects.isNull(wayInfoDTO.getLat())
                        ||Objects.isNull(wayInfoDTO.getLgt())
                ){
@@ -298,6 +298,9 @@
            throw new BusinessException(ResponseStatus.NOT_ALLOWED.getCode(),"金额计算错误!");
        }
    }
@@ -600,12 +603,17 @@
                throw new BusinessException(ResponseStatus.NOT_ALLOWED.getCode(),"身份信息异常,请联系管理员");
            }
        }
        orders.setUpdateTime(new Date());
        orders.setAcceptType(Constants.ZERO);
        orders.setAcceptTime(new Date());
        orders.setAcceptMemberId(member.getId());
        orders.setStatus(Constants.ordersStatus.accept.getKey());
        ordersMapper.updateById(orders);
        ordersMapper.update(new UpdateWrapper<Orders>().lambda().eq(Orders::getId,orders.getId())
                .set(Orders::getUpdateTime,new Date())
                .set(Orders::getAcceptType,Constants.ZERO)
                .set(Orders::getAcceptTime,new Date())
                .set(Orders::getAcceptMemberId,member.getId())
                .set(Orders::getStatus,Constants.ordersStatus.accept.getKey())
        );
        //更新接单量
        memberMapper.update(new UpdateWrapper<Member>().lambda().setSql(" publish_num = (publish_num + 1 )").eq(Member::getId,member.getId()));
@@ -701,8 +709,8 @@
                .set(Orders::getEndDate,updOrderDataDTO.getEndDate())
                .set(Orders::getTotalDays,updOrderDataDTO.getTotalDays())
                .set(Orders::getIsUpdate,Constants.ONE)
                .set(Orders::getIsUpdateTime,"now()")
                .set(Orders::getUpdateTime,"now()")
                .set(Orders::getIsUpdateTime,new Date())
                .set(Orders::getUpdateTime,new Date())
                .set(Orders::getEstimatedAccount,total)
                .eq(Orders::getId,orders.getId())
        );
@@ -715,12 +723,22 @@
    }
    @Override
    public Long getTotal(Orders orders){
        if(Objects.isNull(orders)
                ||Objects.isNull(orders.getPrice())
                ||Objects.isNull(orders.getPriceNum1())){
            throw new BusinessException(ResponseStatus.BAD_REQUEST);
        }
        if(
            (Constants.equalsInteger(orders.getType(),Constants.ZERO) && Constants.equalsInteger(orders.getWorkType(),Constants.ZERO ))
            || Constants.equalsInteger(orders.getType(),Constants.ONE)
        ){
            orders.setPriceNum2(Constants.ONE);
        }else{
            if(Objects.isNull(orders.getPriceNum2())){
                throw new BusinessException(ResponseStatus.BAD_REQUEST);
            }
        }
        return orders.getPrice() * orders.getPriceNum1() * orders.getPriceNum2();
    }
@@ -757,7 +775,7 @@
        if( Constants.equalsInteger(confirmUpdOrderDTO.getStatus(),Constants.ONE)){
            ordersMapper.update(new UpdateWrapper<Orders>().lambda()
                    .set(Orders::getIsUpdate,Constants.TWO)
                    .set(Orders::getUpdateTime,"now()")
                    .set(Orders::getUpdateTime,new Date())
                    .eq(Orders::getId,orders.getId())
            );
            //记录同意修改的日志
@@ -767,7 +785,7 @@
        }else{
            ordersMapper.update(new UpdateWrapper<Orders>().lambda()
                    .set(Orders::getIsUpdate,Constants.TWO)
                    .set(Orders::getUpdateTime,"now()")
                    .set(Orders::getUpdateTime,new Date())
                    .set(Orders::getStatus,Constants.ordersStatus.wait.getKey())
                    .set(Orders::getAcceptMemberId,null)
                    .set(Orders::getAcceptType,null)
@@ -824,8 +842,8 @@
            }
            ordersMapper.update(new UpdateWrapper<Orders>().lambda()
                    .set(Orders::getStatus,Constants.ordersStatus.cancel.getKey())
                    .set(Orders::getUpdateTime,"now()")
                    .set(Orders::getCancelTime,"now()")
                    .set(Orders::getUpdateTime,new Date())
                    .set(Orders::getCancelTime,new Date())
                    .set(Orders::getCancelType,Constants.ZERO)
                    .eq(Orders::getId,orders.getId())
            );
@@ -862,8 +880,8 @@
            }
            ordersMapper.update(new UpdateWrapper<Orders>().lambda()
                    .set(Orders::getStatus,Constants.ordersStatus.cancel.getKey())
                    .set(Orders::getUpdateTime,"now()")
                    .set(Orders::getCancelTime,"now()")
                    .set(Orders::getUpdateTime,new Date())
                    .set(Orders::getCancelTime,new Date())
                    .set(Orders::getCancelType,Constants.ONE)
                    .eq(Orders::getId,orders.getId())
            );
@@ -917,7 +935,7 @@
            throw new BusinessException(ResponseStatus.NOT_ALLOWED.getCode(),"距离订单开始时间不足"+cancelTimeHour+"小时,无法取消订单,如需处理请联系客服");
        }
        ordersMapper.update(new UpdateWrapper<Orders>().lambda()
                .set(Orders::getUpdateTime,"now()")
                .set(Orders::getUpdateTime,new Date())
                .set(Orders::getStatus,Constants.ordersStatus.wait.getKey())
                .set(Orders::getAcceptMemberId,null)
                .set(Orders::getAcceptType,null)
@@ -1059,8 +1077,8 @@
        ordersMapper.update(new UpdateWrapper<Orders>().lambda()
                .set(Orders::getCommentStatus,Constants.ONE)
                .set(Orders::getUpdateTime,"now()")
                .set(Orders::getCommentTime,"now()")
                .set(Orders::getUpdateTime,new Date())
                .set(Orders::getCommentTime,new Date())
                .set(Orders::getCommentLevel,commentDTO.getLevel())
                .set(Orders::getCommentType,Constants.ZERO)
                .eq(Orders::getId,orders.getId())
@@ -1098,10 +1116,10 @@
            }else{
                //处理支付完成逻辑
                ordersMapper.update(new UpdateWrapper<Orders>().lambda()
                        .set(Orders::getUpdateTime,"now()")
                        .set(Orders::getUpdateTime,new Date())
                        .set(Orders::getStatus,Constants.ordersStatus.wait)
                        .set(Orders::getPayStatus,Constants.ONE)
                        .set(Orders::getPayTime,"now()")
                        .set(Orders::getPayTime,new Date())
                        .set(Orders::getPayMethod,Constants.ZERO)
                        .set(Orders::getWxExternalNo,paymentNo)
                        .eq(Orders::getId,orders.getId())
@@ -1119,13 +1137,13 @@
            }else{
                //处理支付完成逻辑
                ordersMapper.update(new UpdateWrapper<Orders>().lambda()
                        .set(Orders::getUpdateTime,"now()")
                        .set(Orders::getUpdateTime,new Date())
                        .set(Orders::getStatus,Constants.FOUR)
                        .set(Orders::getPayStatus,Constants.ONE)
                        .set(Orders::getPayTime,"now()")
                        .set(Orders::getPayTime,new Date())
                        .set(Orders::getPayMethod,Constants.ZERO)
                        .set(Orders::getWxExternalNo,paymentNo)
                        .set(Orders::getFinishTime,"now()")
                        .set(Orders::getFinishTime,new Date())
                        .eq(Orders::getId,orders.getId())
                );
@@ -1367,19 +1385,60 @@
    }
    //todo è‡ªåŠ¨æ´¾å•
    /**
     * è‡ªåŠ¨æ´¾å•
     */
    @Override
    public void autoGrabOrders(){
//        String autoConfirmTime = systemDictDataBiz.queryByCode(Constants.SYSTEM,Constants.AUTO_DISPATCH_DISTANCE).getCode();
//        List<Orders> ordersList = ordersMapper.selectList(new QueryWrapper<Orders>().lambda()
//                .eq(Orders::getStatus,Constants.FOUR).eq(Orders::getCommentStatus,Constants.ZERO)
//                .apply(" DATE_ADD(finish_time, INTERVAL 7 DAY) < now() ")
//                .last("limit 100")
//        );
        String autoConfirmTime = systemDictDataBiz.queryByCode(Constants.SYSTEM,Constants.AUTO_DISPATCH).getCode();
        List<Orders> ordersList = ordersMapper.selectList(new QueryWrapper<Orders>().lambda()
                .eq(Orders::getStatus,Constants.ONE).eq(Orders::getCommentStatus,Constants.ZERO)
                .apply(" DATE_ADD(create_time, INTERVAL "+autoConfirmTime+" MINUTE) < now() ")
                .last("limit 100")
        );
        for (Orders orders:ordersList) {
            BigDecimal lat = orders.getLat();
            BigDecimal lgt = orders.getLgt();
            //查询范围内的会员
            List<Member> memberList = memberMapper.selectList(new MPJLambdaWrapper<Member>().selectAll(Member.class)
                            .select(" ifnull((select r.level from receive_weight r where r.RECEIVE_MAX > t.RECEIVE_NUM and t.RECEIVE_NUM > r.RECEIVE_MIN limit 1  ),0) " ,Member::getLevel)
                            .select(  " ifnull( (select CONVERT( ST_Distance_Sphere ( POINT ( ii.lgt, ii.lat ), POINT ( "+lgt+", "+lat+" )) /1000,DECIMAL(15,2)) from identity_info ii where ii.AUDIT_STATUS = 2 and type = 0 and ii.member_id = t.ID limit  1 ),0) ",Member::getDistance )
                    .apply(" id in (" +
                            " select ii.member_id from identity_info ii where ii.AUDIT_STATUS = 2 and type = '"+orders.getType()+"' " +
                            " and ( CONVERT( ST_Distance_Sphere ( POINT ( ii.lgt, ii.lat ), POINT ( "+lgt+", "+lat+" )) /1000,DECIMAL(15,2))) < 100 " +
                            ") ")
                    .orderByDesc(Member::getLevel)
                    .orderByDesc(Member::getScore)
                    .orderByAsc(Member::getDistance)
                    .last(" limit 1 ")
            );
            if(CollectionUtils.isEmpty(memberList)){
                continue;
            }
            Member member = memberList.get(Constants.ZERO);
            //自动派单
            Orders model = ordersMapper.selectById(orders.getStatus());
            if(!Constants.equalsInteger(model.getStatus(),Constants.ONE)){
                continue;
            }
            ordersMapper.update(new UpdateWrapper<Orders>().lambda().eq(Orders::getId,model.getId())
                    .set(Orders::getUpdateTime,new Date())
                    .set(Orders::getAcceptType,Constants.ONE)
                    .set(Orders::getAcceptTime,new Date())
                    .set(Orders::getAcceptMemberId,member.getId())
                    .set(Orders::getStatus,Constants.ordersStatus.accept.getKey())
            );
            //更新接单量
            memberMapper.update(new UpdateWrapper<Member>().lambda().setSql(" publish_num = (publish_num + 1 )").eq(Member::getId,member.getId()));
            //创建操作日志
            Constants.OrdersLog ordersLog = Constants.OrdersLog.AUTO;
            this.saveOrderLog(orders,ordersLog,
                    ordersLog.getInfo(),member.getId(),null);
        }
    }
    //自动评价 è®¢å•完成7天后自动评价4星
@@ -1395,8 +1454,8 @@
        for (Orders orders:ordersList) {
            ordersMapper.update(new UpdateWrapper<Orders>().lambda()
                    .set(Orders::getCommentStatus,Constants.ONE)
                    .set(Orders::getUpdateTime,"now()")
                    .set(Orders::getCommentTime,"now()")
                    .set(Orders::getUpdateTime,new Date())
                    .set(Orders::getCommentTime,new Date())
                    .set(Orders::getCommentLevel,Constants.FOUR)
                    .set(Orders::getCommentType,Constants.ZERO)
                    .eq(Orders::getId,orders.getId())
@@ -1430,7 +1489,7 @@
            for (Orders orders:ordersList) {
                ordersMapper.update(new UpdateWrapper<Orders>().lambda()
                        .set(Orders::getIsUpdate,Constants.TWO)
                        .set(Orders::getUpdateTime,"now()")
                        .set(Orders::getUpdateTime,new Date())
                        .eq(Orders::getId,orders.getId())
                );
                //记录同意修改的日志
@@ -1442,6 +1501,11 @@
    }
    @Override
    public  void initializeCode(){
        //更新缓存
        redisTemplate.opsForValue().set(Constants.RedisKeys.ORDER_CODE,0);
    }
server/services/src/main/java/com/doumee/service/business/impl/WithdrawalOrdersServiceImpl.java
@@ -1,5 +1,10 @@
package com.doumee.service.business.impl;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.doumee.config.wx.TransferToUser;
import com.doumee.config.wx.WXPayUtility;
import com.doumee.config.wx.WxMiniConfig;
import com.doumee.core.constants.Constants;
import com.doumee.core.constants.ResponseStatus;
import com.doumee.core.exception.BusinessException;
@@ -22,10 +27,12 @@
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import java.util.Date;
import java.util.List;
import java.util.Objects;
import java.util.UUID;
/**
 * æçŽ°ç”³è¯·è®°å½•Service实现
@@ -171,10 +178,9 @@
     * æçŽ°ç”³è¯·
     * @param withdrawalDTO
     */
    @Override
    @Transactional(rollbackFor = {BusinessException.class,Exception.class})
    public void  applyWithdrawal(WithdrawalDTO withdrawalDTO){
    public TransferToUser.TransferToUserResponse  applyWithdrawal(WithdrawalDTO withdrawalDTO){
        if(Objects.isNull(withdrawalDTO)
        || Objects.isNull(withdrawalDTO.getAmount())){
            throw new BusinessException(ResponseStatus.BAD_REQUEST);
@@ -186,23 +192,108 @@
        if(member.getAmount() < withdrawalDTO.getAmount()){
            throw new BusinessException(ResponseStatus.NOT_ALLOWED.getCode(),"对不起,可提现余额不足。");
        }
        if(StringUtils.isEmpty(member.getName())){
            throw new BusinessException(ResponseStatus.NOT_ALLOWED.getCode(),"请先去维护真实姓名后进行提现申请");
        }
        WithdrawalOrders withdrawalOrders = new WithdrawalOrders();
        withdrawalOrders.setCreateTime(new Date());
        withdrawalOrders.setMemberId(member.getId());
        withdrawalOrders.setOutBillNo(UUID.randomUUID().toString().replace("-",""));
        withdrawalOrders.setAmount(withdrawalDTO.getAmount());
        withdrawalOrders.setStatus(Constants.ZERO);
        withdrawalOrders.setType(Constants.ZERO);
        withdrawalOrders.setDeleted(Constants.ZERO);
        //TODO å‘起提现申请
        //发起提现申请
        TransferToUser.TransferToUserRequest transferToUserRequest = new TransferToUser.TransferToUserRequest();
        transferToUserRequest.openid = member.getOpenid();
        transferToUserRequest.outBillNo =  withdrawalOrders.getOutBillNo();
        transferToUserRequest.transferAmount = withdrawalDTO.getAmount();
        transferToUserRequest.transferRemark = "提现申请";
//        transferToUserRequest.userName = member.getName();
        try {
            TransferToUser.TransferToUserResponse response =  WxMiniConfig.transferToUser.run(transferToUserRequest,member.getName());
            withdrawalOrders.setRemark(JSONObject.toJSONString(response));
            if(response.state.name().equals("WAIT_USER_CONFIRM") || response.state.name().equals("ACCEPTED")){
                withdrawalOrders.setWxExternalNo(response.transferBillNo);
            }
            withdrawalOrdersMapper.insert(withdrawalOrders);
            //更新用户余额
            memberMapper.update(new UpdateWrapper<Member>().lambda().setSql(" AMOUNT = AMOUNT -  " + withdrawalDTO.getAmount() ).eq(Member::getId,member.getId()));
            //存储流水记录
            MemberRevenue memberRevenue = new MemberRevenue();
            memberRevenue.setCreateTime(new Date());
            memberRevenue.setMemberId(member.getId());
            memberRevenue.setType(Constants.THREE);
            memberRevenue.setOptType(-Constants.ONE);
            memberRevenue.setBeforeAmount(member.getAmount());
            memberRevenue.setAmount(withdrawalOrders.getAmount());
            memberRevenue.setAfterAmount(member.getAmount() - withdrawalOrders.getAmount());
            memberRevenue.setObjId(withdrawalOrders.getId());
            memberRevenue.setObjType(Constants.ONE);
            memberRevenue.setDeleted(Constants.ZERO);
            memberRevenue.setStatus(Constants.TWO);
            memberRevenueMapper.insert(memberRevenue);
            return response;
        } catch (WXPayUtility.ApiException e) {
            String message = JSONObject.parseObject(e.getBody()).getString("message");
            throw new BusinessException(ResponseStatus.NOT_ALLOWED.getCode(),message);
        }
    }
    @Override
    public void cancelTransfer(TransferToUser.CancelTransferRequest request){
        if(Objects.isNull(request)
        || StringUtils.isEmpty(request.outBillNo)){
            throw new BusinessException(ResponseStatus.BAD_REQUEST);
        }
        WithdrawalOrders withdrawalOrders = withdrawalOrdersMapper.selectOne(new QueryWrapper<WithdrawalOrders>().lambda().eq(WithdrawalOrders::getOutBillNo,request.outBillNo).last("limit 1"));
        if(Objects.isNull(withdrawalOrders)){
            throw new BusinessException(ResponseStatus.DATA_EMPTY);
        }
        Member member = memberMapper.selectById(withdrawalOrders.getMemberId());
        if(Objects.isNull(member)){
            throw new BusinessException(ResponseStatus.DATA_EMPTY);
        }
        try {
            TransferToUser.CancelTransferResponse response =  WxMiniConfig.transferToUser.cancelRun(request);
            if(response.state.equals("CANCELING")){
                //标记提现失败 åŠ å›žå¯¹åº”çš„æçŽ°é‡‘é¢
                withdrawalOrdersMapper.update(new UpdateWrapper<WithdrawalOrders>()
                        .lambda().set(WithdrawalOrders::getStatus,Constants.TWO)
                        .eq(WithdrawalOrders::getId,withdrawalOrders.getId()));
        //更新用户余额
                memberRevenueMapper.update(new UpdateWrapper<MemberRevenue>().lambda().set(MemberRevenue::getStatus,Constants.ONE)
                                .set(MemberRevenue::getUpdateTime,new Date())
                        .eq(MemberRevenue::getObjId,withdrawalOrders.getId()))
                ;
                //更新用户余额
                memberMapper.update(new UpdateWrapper<Member>().lambda().setSql(" AMOUNT = AMOUNT + " + withdrawalOrders.getAmount() ).eq(Member::getId,withdrawalOrders.getMemberId()));
        //存储流水记录
                //存储流水记录
                MemberRevenue memberRevenue = new MemberRevenue();
                memberRevenue.setCreateTime(new Date());
                memberRevenue.setMemberId(withdrawalOrders.getMemberId());
                memberRevenue.setType(Constants.FOUR);
                memberRevenue.setOptType(Constants.ONE);
                memberRevenue.setBeforeAmount(member.getAmount());
                memberRevenue.setAmount(withdrawalOrders.getAmount());
                memberRevenue.setAfterAmount(member.getAmount() + withdrawalOrders.getAmount());
                memberRevenue.setObjId(withdrawalOrders.getId());
                memberRevenue.setObjType(Constants.ONE);
                memberRevenue.setDeleted(Constants.ZERO);
                memberRevenue.setStatus(Constants.ZERO);
                memberRevenueMapper.insert(memberRevenue);
            }
            System.out.println(JSONObject.toJSONString(response));
        } catch (WXPayUtility.ApiException e) {
            String message = JSONObject.parseObject(e.getBody()).getString("message");
            throw new BusinessException(ResponseStatus.NOT_ALLOWED.getCode(),message);
        }
    }
server/services/src/main/resources/application-dev.yml
@@ -85,7 +85,7 @@
  pay:
    #服务商---------start-------
    appId: wx48fd8faa35cc8277
    mchId: 1661770902
    mchId: 1229817002 #1661770902
    apiV3Key: iF3kC8pL8dZ9iU3hN5fX9zI6eF4xQ6fT
    serialNumer: 368B835A194384FD583B83B77977B84127D2F655
    mchKey: W97N53Q71326D6JZ2E9HY5M4VT4BAC8S
@@ -96,9 +96,17 @@
    #keyPath: /usr/local/aliConfig/bike/apiclient_cert.p12
    #privateCertPath: /usr/local/aliConfig/bike/apiclient_cert.pem
    #privateKeyPath: /usr/local/aliConfig/bike/apiclient_key.pem
    #    keyPath: d://apiclient_cert.p12
    #    privateCertPath: d://apiclient_cert.pem
    #    privateKeyPath: d://apiclient_key.pem
    keyPath: d://apiclient_cert.p12
    privateCertPath: d://apiclient_cert.pem
    privateKeyPath: d://apiclient_key.pem
    #商户信息
    wechatSerialNumer: 3FE90C2F3D40A56E1C51926F31B8A8D22426CCE0
    wechatPayPublicKeyId: PUB_KEY_ID_0112298170022025071700291836000600
    wechatPubKeyPath: d://pub_key.pem
    wechatPrivateKeyPath: d://wechatApiclient_key.pem
    #服务商-------------end---
    existsSub: 1
    appSecret: 1ceb7c9dff3c4330d653adc3ca55ea24
server/web/src/main/java/com/doumee/api/web/AccountApi.java
@@ -1,5 +1,6 @@
package com.doumee.api.web;
import com.doumee.config.jwt.JwtTokenUtil;
import com.doumee.core.annotation.LoginRequired;
import com.doumee.core.annotation.trace.Trace;
import com.doumee.core.model.ApiResponse;
@@ -53,6 +54,16 @@
        return  ApiResponse.success("操作成功",memberService.wxAuthPhone(wxPhoneRequest));
    }
    @LoginRequired
    @ApiOperation(value = "退出登录", notes = "小程序端")
    @GetMapping("/logOff")
    @ApiImplicitParams({
            @ApiImplicitParam(paramType = "header", dataType = "String", name = "token", value = "用户token值", required = true),
    })
    public ApiResponse logOff() {
        String token = this.getRequest().getHeader(JwtTokenUtil.HEADER_KEY);
        memberService.logOff(token,getMemberId());
        return  ApiResponse.success("操作成功");
    }
}
server/web/src/main/java/com/doumee/api/web/ConfigApi.java
@@ -1,5 +1,6 @@
package com.doumee.api.web;
import com.doumee.core.annotation.LoginRequired;
import com.doumee.core.annotation.trace.Trace;
import com.doumee.core.model.ApiResponse;
import com.doumee.dao.business.model.Category;
@@ -34,6 +35,7 @@
    @Autowired
    private CategoryService categoryService;
    @LoginRequired
    @ApiOperation(value = "获取分类列表", notes = "小程序端")
    @GetMapping("/getCategoryList")
    @ApiImplicitParams({
server/web/src/main/java/com/doumee/api/web/OrdersApi.java
@@ -13,6 +13,7 @@
import com.doumee.dao.dto.ConfirmUpdOrderDTO;
import com.doumee.dao.dto.DoneOrderDTO;
import com.doumee.dao.dto.UpdOrderDataDTO;
import com.doumee.dao.vo.OrderReleaseVO;
import com.doumee.service.business.IdentityInfoService;
import com.doumee.service.business.MemberService;
import com.doumee.service.business.OrdersService;
@@ -51,7 +52,7 @@
    @ApiImplicitParams({
            @ApiImplicitParam(paramType = "header", dataType = "String", name = "token", value = "用户token值", required = true),
    })
    public ApiResponse<Object> release(@RequestBody Orders orders) {
    public ApiResponse<OrderReleaseVO> release(@RequestBody Orders orders) {
        orders.setMember(this.getMemberResponse());
        return  ApiResponse.success("操作成功",ordersService.create(orders));
    }
@@ -185,6 +186,15 @@
        return  ApiResponse.success("操作成功",ordersService.getDetail(orderId,this.getMemberResponse().getUseIdentity()));
    }
    @LoginRequired
    @ApiOperation("获取预计金额(分)")
    @PostMapping("/getTotal")
    @ApiImplicitParams({
            @ApiImplicitParam(paramType = "header", dataType = "String", name = "token", value = "用户token值", required = true),
    })
    public ApiResponse<Long> getTotal(@RequestBody Orders orders) {
        return ApiResponse.success(ordersService.getTotal(orders));
    }
}
server/web/src/main/java/com/doumee/api/web/UserApi.java
@@ -1,5 +1,6 @@
package com.doumee.api.web;
import com.doumee.config.wx.TransferToUser;
import com.doumee.core.annotation.LoginRequired;
import com.doumee.core.annotation.trace.Trace;
import com.doumee.core.model.ApiResponse;
@@ -8,12 +9,14 @@
import com.doumee.dao.business.model.IdentityInfo;
import com.doumee.dao.business.model.Member;
import com.doumee.dao.business.model.MemberRevenue;
import com.doumee.dao.dto.WithdrawalDTO;
import com.doumee.dao.dto.WxPhoneRequest;
import com.doumee.dao.vo.AccountResponse;
import com.doumee.dao.vo.UserCenterVO;
import com.doumee.service.business.IdentityInfoService;
import com.doumee.service.business.MemberRevenueService;
import com.doumee.service.business.MemberService;
import com.doumee.service.business.WithdrawalOrdersService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
@@ -46,6 +49,9 @@
    @Autowired
    private MemberRevenueService memberRevenueService;
    @Autowired
    private WithdrawalOrdersService withdrawalOrdersService;
    @ApiOperation(value = "获取系统配置", notes = "小程序端")
@@ -153,6 +159,29 @@
        return ApiResponse.success(memberRevenueService.findPage(pageWrap));
    }
    @LoginRequired
    @ApiOperation("提现申请")
    @PostMapping("/applyWithdrawal")
    @ApiImplicitParams({
            @ApiImplicitParam(paramType = "header", dataType = "String", name = "token", value = "用户token值", required = true),
    })
    public ApiResponse<TransferToUser.TransferToUserResponse> applyWithdrawal (@RequestBody WithdrawalDTO withdrawalDTO) {
        withdrawalDTO.setMember(this.getMemberResponse());
        return ApiResponse.success("操作成功",withdrawalOrdersService.applyWithdrawal(withdrawalDTO));
    }
    @LoginRequired
    @ApiOperation("撤销提现申请")
    @PostMapping("/cancelTransfer")
    @ApiImplicitParams({
            @ApiImplicitParam(paramType = "header", dataType = "String", name = "token", value = "用户token值", required = true),
    })
    public ApiResponse cancelTransfer (@RequestBody TransferToUser.CancelTransferRequest request) {
        withdrawalOrdersService.cancelTransfer(request);
        return ApiResponse.success("操作成功");
    }
}
small-program/pages/freight/freight.vue
@@ -6,51 +6,54 @@
                    <view class="list-item-label-x"></view>
                    <text>车辆及路线</text>
                </view>
                <view class="list-item-row">
                <view class="list-item-row" @click="show = true">
                    <view class="list-item-row-label">车辆类型<b>*</b></view>
                    <view class="list-item-row-val">
                        <text>请选择</text>
                        <text :style="{ color: form.categoryName ? '#111111' : '' }">{{form.categoryName ? form.categoryName : '请选择'}}</text>
                        <u-icon name="arrow-right" color="#111111" size="16"></u-icon>
                    </view>
                </view>
                <view class="list-item-row">
                <view class="list-item-row" @click="show3 = true">
                    <view class="list-item-row-label">货车型号<b>*</b></view>
                    <view class="list-item-row-val">
                        <text>请选择</text>
                        <text :style="{ color: form.carUnit ? '#111111' : '' }">{{form.carUnit ? form.carUnit : '请选择'}}</text>
                        <u-icon name="arrow-right" color="#111111" size="16"></u-icon>
                    </view>
                </view>
                <view class="list-item-row">
                <view class="list-item-row" @click="show2 = true">
                    <view class="list-item-row-label">用车时间<b>*</b></view>
                    <view class="list-item-row-val">
                        <text>请选择</text>
                        <text :style="{ color: form.startDate ? '#111111' : '' }">{{form.startDate ? form.startDate + ' è‡³ ' + form.endDate + ' (' + form.priceNum1 + '天) ' : '请选择'}}</text>
                        <u-icon name="arrow-right" color="#111111" size="16"></u-icon>
                    </view>
                </view>
                <view class="list-item-row">
                    <view class="list-item-row-label">
                        è¿è´§åœ°å€<b>*</b>
                        <view class="list-item-row-label-add">+增加途经点</view>
                        <view class="list-item-row-label-add" @click="addAddr">+增加途经点</view>
                    </view>
                    <view class="address">
                        <view class="address-item">
                        <view class="address-item" @click="selectAddress(2)">
                            <view class="address-item-img">
                                <image src="/static/icon/ic_qidian@2x.png" mode="widthFix"></image>
                                <text>请选择起点</text>
                                <text :style="{color: form.location ? '#111111' : ''}">{{form.location ? form.location : '请选择起点'}}</text>
                            </view>
                            <u-icon name="arrow-right" color="#111111" size="16"></u-icon>
                        </view>
                        <view class="address-item">
                        <view class="address-item" v-for="(item, index) in form.wayInfoDTOList" :key="index" @click="selectAddress(4, index)">
                            <view class="address-item-img">
                                <image src="/static/icon/ic_jingguo@2x.png" mode="widthFix"></image>
                                <text>请选择途经地点</text>
                                <text :style="{color: item.location ? '#111111' : ''}">{{item.location ? item.location : '请选择途经地点'}}</text>
                            </view>
                            <u-icon name="arrow-right" color="#111111" size="16"></u-icon>
                            <view style="display: flex; align-items: center; flex-shrink: 0;">
                                <image src="/static/icon/ic_delete@2x.png" style="margin-right: 22rpx; width: 32rpx; height: 32rpx;" @click.stop="form.wayInfoDTOList.splice(index, 1)" mode="widthFix"></image>
                                <u-icon name="arrow-right" color="#111111" size="16"></u-icon>
                            </view>
                        </view>
                        <view class="address-item">
                        <view class="address-item" @click="selectAddress(3)">
                            <view class="address-item-img">
                                <image src="/static/icon/ic_zhongdian@2x.png" mode="widthFix"></image>
                                <text>请选择终点</text>
                                <text :style="{color: form.locationEnd ? '#111111' : ''}">{{form.locationEnd ? form.locationEnd : '请选择终点'}}</text>
                            </view>
                            <u-icon name="arrow-right" color="#111111" size="16"></u-icon>
                        </view>
@@ -62,49 +65,50 @@
                    <view class="list-item-label-x"></view>
                    <text>需求</text>
                </view>
                <view class="list-item-row">
                <view class="list-item-row" @click="show1 = true">
                    <view class="list-item-row-label">运输品种<b>*</b></view>
                    <view class="list-item-row-val">
                        <text>请选择</text>
                        <text :style="{ color: form.transportTypeName ? '#111111' : '' }">{{form.transportTypeName ? form.transportTypeName : '请选择'}}</text>
                        <u-icon name="arrow-right" color="#111111" size="16"></u-icon>
                    </view>
                </view>
                <view class="list-item-row">
                    <view class="list-item-row-label">用车数量<b>*</b></view>
                    <view class="list-item-row-val">
                        <text>请选择</text>
                        <u-icon name="arrow-right" color="#111111" size="16"></u-icon>
                        <input type="number" v-model="form.priceNum2" disabled placeholder="1" />
                        <text>辆</text>
                    </view>
                </view>
                <view class="list-item-row">
                    <view class="list-item-row-label">运输重量/数量<b>*</b></view>
                    <view class="list-item-row-val">
                        <input type="text" placeholder="请输入" />
                        <text>个</text>
                        <input type="text" v-model="form.transportNum" placeholder="请输入" />
                        <text @click="show4 = true">{{form.transportUnit}}</text>
                        <u-icon name="arrow-down" color="#111111" size="16"></u-icon>
                    </view>
                </view>
                <view class="list-item-row">
                <view class="list-item-row" v-if="viewStatus">
                    <view class="list-item-row-label">需求补充</view>
                    <view class="list-item-row-val">
                        <textarea cols="30" rows="10" placeholder="请输入" maxlength="200"></textarea>
                        <textarea cols="30" v-model="form.supplement" rows="10" placeholder="请输入" maxlength="200"></textarea>
                    </view>
                </view>
                <view class="list-item-row">
                <view class="list-item-row" v-if="viewStatus">
                    <view class="list-item-row-label">图片</view>
                    <view class="list-item-row-upload">
                        <view class="upload-item">
                            <image src="/static/logo.png" mode="widthFix"></image>
                        <view class="upload-item" v-for="(item, index) in form.multifileList" :key="index">
                            <image :src="item.url" mode="widthFix"></image>
                            <image class="upload-item-dele" @click="form.multifileList.splice(index, 1)" src="/static/icon/ic_delete1@2x.png" mode="widthFix"></image>
                        </view>
                        <view class="upload-item">
                        <view class="upload-item" @click="uploadImg">
                            <u-icon name="plus" color="#999999" size="24"></u-icon>
                            <text>点击上传</text>
                        </view>
                    </view>
                </view>
                <view class="list-item-zk">
                    <text>补充需求</text>
                    <u-icon name="arrow-down" color="#00BC12" size="16"></u-icon>
                <view class="list-item-zk" @click="viewStatus = !viewStatus">
                    <text>{{viewStatus ? '收起' : '补充需求'}}</text>
                    <u-icon :name="viewStatus ? 'arrow-up' : 'arrow-down'" color="#00BC12" size="16"></u-icon>
                </view>
            </view>
            <view class="list-item">
@@ -113,21 +117,39 @@
                    <text>费用</text>
                </view>
                <view class="list-item-cate">
                    <view class="list-item-cate-item active">按天付费</view>
                    <view class="list-item-cate-item">按次付费</view>
                    <view :class="item.id === form.carType ? 'list-item-cate-item active' : 'list-item-cate-item'" v-for="(item, index) in ff" :key="index" @click="form.carType = item.id">按天付费</view>
                </view>
                <view class="list-item-row">
                    <view class="list-item-row-label">天数</view>
                    <view class="list-item-row-val">
                        <input type="text" placeholder="请输入" />
                        <input type="number" v-model="form.priceNum1" disabled placeholder="请输入" />
                        <text>天</text>
                    </view>
                </view>
                <view class="list-item-row">
                    <view class="list-item-row-label">费用标准<b>*</b></view>
                    <view class="list-item-row-val">
                        <input type="text" placeholder="请输入" />
                        <text>元/辆*天</text>
                        <input type="number" @blur="getPrice" v-model="form.price" placeholder="请输入" />
                        <text>{{form.priceUnit}}</text>
                    </view>
                </view>
            </view>
            <view class="list-item">
                <view class="list-item-label">
                    <view class="list-item-label-x"></view>
                    <text>联系人信息</text>
                    <text class="list-item-label-info">默认发单方联系方式,可修改联系人</text>
                </view>
                <view class="list-item-row">
                    <view class="list-item-row-label">联系人姓名</view>
                    <view class="list-item-row-val">
                        <input type="text" v-model="form.linkName" placeholder="请补充" />
                    </view>
                </view>
                <view class="list-item-row">
                    <view class="list-item-row-label">联系电话<b>*</b></view>
                    <view class="list-item-row-val">
                        <input type="text" v-model="form.linkPhone" maxlength="11" placeholder="请补充" />
                    </view>
                </view>
            </view>
@@ -137,27 +159,296 @@
            <view class="footer-price">
                <view class="footer-price-info">预估总费用</view>
                <view class="footer-price-num">
                    <text>0</text>
                    <text>{{totalPrice || 0}}</text>
                    <text>元</text>
                </view>
            </view>
            <view class="footer-bottom">
                <div class="footer-bottom-btn">
                <div class="footer-bottom-btn" @click="submit">
                    <text>立即下单</text>
                    <text>(服务完成后付款)</text>
                </div>
            </view>
            <view style="width: 100%; height: env(safe-area-inset-bottom);"></view>
        </view>
        <!-- é€‰æ‹©è½¦è¾†ç±»åž‹ -->
        <u-picker
            :show="show"
            :columns="car"
            confirmColor="#00BC12"
            keyName="name"
            @confirm="confirmCategary"
            @cancel="show = false"/>
        <!-- é€‰æ‹©å“ç§ -->
        <u-picker
            :show="show1"
            :columns="variety"
            confirmColor="#00BC12"
            keyName="name"
            @confirm="confirmVariety"
            @cancel="show1 = false"/>
        <!-- è´§è½¦åž‹å· -->
        <u-picker
            :show="show3"
            :columns="carArr"
            confirmColor="#00BC12"
            @confirm="confirmCarArr"
            @cancel="show3 = false"/>
        <!-- å•位 -->
        <u-picker
            :show="show4"
            :columns="dw"
            confirmColor="#00BC12"
            @confirm="confirmDw"
            @cancel="show4 = false"/>
        <!-- é€‰æ‹©æ—¥æœŸèŒƒå›´ -->
        <u-calendar
            :show="show2"
            color="#00BC12"
            mode="range"
            @close="show2 = false"
            @confirm="confirmDate" />
    </view>
</template>
<script>
    import { mapState } from 'vuex'
    export default {
        computed: {
            ...mapState(['userInfo']),
            totalPrice() {
                return this.form.estimatedAccount / 100
            }
        },
        data() {
            return {
                show: false,
                show1: false,
                show2: false,
                show3: false,
                show4: false,
                form: {
                    startDate: '',
                    endDate: '',
                    lat: '',
                    lgt: '',
                    location: '',
                    latEnd: '',
                    lgtEnd: '',
                    locationEnd: '',
                    carUnit: '',
                    categoryId: '',
                    categoryName: '',
                    wayInfoDTOList: [],
                    priceNum1: '',
                    priceNum2: 1,
                    transportNum: '',
                    transportTypeId: '',
                    transportTypeName: '',
                    carType: 0,
                    supplement: '',
                    multifileList: [],
                    priceUnit: '元/天',
                    linkName: '',
                    linkPhone: '',
                    price: '',
                    type: 1,
                    transportUnit: '个',
                    estimatedAccount: ''
                },
                car: [],
                variety: [],
                ff: [
                    { name: '按天付费', id: 0 },
                    { name: '按次付费', id: 1 }
                ],
                dw: [['个', '斤']],
                carArr: [],
                viewStatus: false
            };
        },
        watch: {
            'form.carType': {
                handler(news, olds) {
                    if (news === 0) {
                        this.form.priceUnit = '元/天'
                    } else if (news === 1) {
                        this.form.priceUnit = '元/次'
                    }
                }
            }
        },
        onLoad() {
            const res = uni.getStorageSync('carData');
            this.form.linkPhone = this.userInfo.telephone
            this.form.startDate = res.startDate
            this.form.endDate = res.endDate
            this.form.lat = res.lat
            this.form.lgt = res.lgt
            this.form.priceNum1 = res.days
            this.form.categoryId = res.categoryId
            this.form.categoryName = res.categoryName
            this.form.location = res.location
            this.form.latEnd = res.latEnd
            this.form.lgtEnd = res.lgtEnd
            this.form.locationEnd = res.locationEnd
            this.form.wayInfoDTOList = res.wayInfoDTOList
            uni.removeStorageSync('carData');
            this.getCategoryLists()
        },
        methods: {
            confirmDw(e) {
                this.form.transportUnit = e.value[0]
                this.show4 = false
            },
            // æäº¤è®¢å•
            submit() {
                if (!this.form.transportTypeName) {
                    return uni.showToast({ title: '请选择运输品种', icon: 'none' })
                }
                if (!this.form.priceNum2) {
                    return uni.showToast({ title: '请输入用车数量', icon: 'none' })
                }
                if (!this.form.transportNum) {
                    return uni.showToast({ title: '请输入运输重量/数量', icon: 'none' })
                }
                if (!this.form.price) {
                    return uni.showToast({ title: '请输入费用标准', icon: 'none' })
                }
                if (!this.form.linkPhone) {
                    return uni.showToast({ title: '请输入联系电话', icon: 'none' })
                }
                this.$u.api.release({
                    ...this.form,
                    price: Number(this.form.price) * 100,
                    wayInfoDTOList: [
                        {
                            lat: this.form.lat,
                            lgt: this.form.lgt,
                            location: this.form.location
                        },
                        ...this.form.wayInfoDTOList,
                        {
                            lat: this.form.latEnd,
                            lgt: this.form.lgtEnd,
                            location: this.form.locationEnd
                        }
                    ]
                }).then(res => {
                    if (res.code == 200) {
                        uni.navigateTo({
                            url: `/pages/success/success?orderId=${res.data.id}`
                        })
                    }
                })
            },
            uploadImg() {
                uni.chooseImage({
                    success: (chooseImageRes) => {
                        const tempFilePaths = chooseImageRes.tempFilePaths;
                        uni.uploadFile({
                            url: this.$baseUrl + '/web/public/upload',
                            filePath: tempFilePaths[0],
                            name: 'file',
                            formData: {
                                'folder': 'orders'
                            },
                            success: (uploadFileRes) => {
                                const res = JSON.parse(uploadFileRes.data)
                                this.form.multifileList.push({
                                    fileurl: res.data.imgaddr,
                                    name: res.data.originname,
                                    url: res.data.url,
                                    type: 0
                                })
                            }
                        });
                    }
                });
            },
            // è®¡ç®—金额
            getPrice() {
                if (this.form.price && this.form.priceNum1 && this.form.priceNum2) {
                    this.$u.api.getTotal({
                        price: Number(this.form.price) * 100,
                        priceUnit: this.form.priceUnit,
                        priceNum1: this.form.priceNum1,
                        priceNum2: this.form.priceNum2,
                        type: 1
                    }).then(res => {
                        this.form.estimatedAccount = res.data
                    })
                }
            },
            getCategoryLists() {
                this.$u.api.getCategoryList({ type: 1 })
                    .then(res => {
                        this.car = [res.data]
                        res.data.forEach(item => {
                            if (item.id === this.form.categoryId) {
                                this.carArr = [item.detailList]
                            }
                        })
                    })
                this.$u.api.getCategoryList({ type: 0 })
                    .then(res => {
                        this.variety = [res.data]
                    })
            },
            confirmCategary(e) {
                this.form.categoryId = e.value[0].id
                this.form.categoryName = e.value[0].name
                this.carArr = [e.value[0].detailList]
                this.form.carUnit = ''
                this.show = false
            },
            confirmVariety(e) {
                this.form.transportTypeId = e.value[0].id
                this.form.transportTypeName = e.value[0].name
                this.show1 = false
            },
            confirmCarArr(e) {
                this.form.carUnit = e.value[0]
                this.show3 = false
            },
            confirmDate(e) {
                this.form.startDate = e[0]
                this.form.endDate = e[e.length - 1]
                this.form.priceNum1 = e.length;
                this.show2 = false
            },
            addAddr() {
                this.form.wayInfoDTOList.push({
                    lat: '',
                    lgt: '',
                    location: ''
                })
            },
            selectAddress(type, index) {
                uni.chooseLocation({
                    success: (res) => {
                        if ([2].includes(type)) {
                            this.form.latitude = res.latitude
                            this.form.longitude = res.longitude
                            this.form.address = res.address
                        } else if (type === 3) {
                            this.form.locationEnd = res.address
                            this.form.latEnd = res.latitude
                            this.form.lgtEnd = res.longitude
                        } else if (type === 4) {
                            this.form.wayInfoDTOList[0].lat = res.latitude
                            this.form.wayInfoDTOList[0].lgt = res.longitude
                            this.form.wayInfoDTOList[0].location = res.address
                        }
                    }
                });
            },
        }
    }
</script>
@@ -323,6 +614,12 @@
                        border-radius: 4rpx;
                        margin-right: 20rpx;
                    }
                    .list-item-label-info {
                        font-weight: 400;
                        font-size: 26rpx;
                        color: #999999;
                        margin-left: 26rpx;
                    }
                }
                .list-item-zk {
                    width: 100%;
@@ -354,7 +651,9 @@
                        margin-top: 20rpx;
                        .address-item {
                            width: 100%;
                            height: 100rpx;
                            // height: 100rpx;
                            padding: 30rpx 0;
                            box-sizing: border-box;
                            display: flex;
                            align-items: center;
                            justify-content: space-between;
@@ -524,6 +823,7 @@
                            border-radius: 8rpx;
                            margin-right: 20rpx;
                            border: 2rpx solid #EEEEEE;
                            position: relative;
                            &:last-child {
                                margin: 0 !important;
                            }
@@ -535,6 +835,14 @@
                                font-size: 26rpx;
                                color: #666666;
                                margin-top: 16rpx;
                            }
                            .upload-item-dele {
                                position: absolute;
                                top: -20rpx;
                                right: -20rpx;
                                width: 40rpx;
                                height: 40rpx;
                                z-index: 9;
                            }
                        }
                    }
@@ -549,7 +857,7 @@
                            flex-shrink: 0;
                            font-weight: 400;
                            font-size: 30rpx;
                            color: #111111;
                            color: #999999;
                        }
                        input {
                            width: 100%;
small-program/pages/index/index.vue
@@ -323,10 +323,10 @@
                            </view>
                            <view class="box-hz-list-item" @click="selectAddress(1)">
                                <view class="box-hz-list-item-label"><text>用工地点:</text></view>
                                <view :class="form.address ? 'box-hz-list-item-val active' :'box-hz-list-item-val'">
                                <view :class="form.location ? 'box-hz-list-item-val active' :'box-hz-list-item-val'">
                                    <view class="box-hz-list-item-val-left">
                                        <image class="icon" src="/static/icon/ic_location@2x.png" mode="widthFix"></image>
                                        <text>{{form.address ? form.address : '请选择用工地点'}}</text>
                                        <text>{{form.location ? form.location : '请选择用工地点'}}</text>
                                    </view>
                                    <u-icon name="arrow-right" color="#111111" size="16"></u-icon>
                                </view>
@@ -337,65 +337,64 @@
                    <!-- è¿è´§ -->
                    <template v-if="typeId === 1">
                        <view class="box-hz-cate">
                            <view :class="carId === item.id ? 'box-hz-cate-item active' : 'box-hz-cate-item'" v-for="(item, index) in car" :key="index" @click="clickCar(item.id)">
                            <view :class="form.categoryId === item.id ? 'box-hz-cate-item active' : 'box-hz-cate-item'" v-for="(item, index) in car" :key="index" @click="clickCar(item)">
                                <text>{{item.name}}</text>
                                <view class="box-hz-cate-item-x" v-if="carId === item.id"></view>
                                <view class="box-hz-cate-item-x" v-if="form.categoryId === item.id"></view>
                            </view>
                        </view>
                        <view class="box-hz-car">
                            <view class="box-hz-car-image">
                                <image src="/static/image/ic_sanlunche@2x.png" mode="widthFix" v-if="carId === 1"></image>
                                <image src="/static/image/ic_xiaohuoche@2x.png" mode="widthFix" v-else-if="carId === 2"></image>
                                <image :src="carImage" mode="widthFix"></image>
                            </view>
                            <view class="box-hz-car-info" v-if="carId === 1">
                                åŽ¢é•¿3.8~4.0ç±³  è½½é‡1.5~2.0吨  è½½æ–¹12.3~18.4方
                            </view>
                            <view class="box-hz-car-info" v-else-if="carId === 2">
                                åŽ¢é•¿3.8~4.0ç±³  è½½é‡1.5~2.0吨  è½½æ–¹12.3~18.4方
                            <view class="box-hz-car-info" v-if="carRemark">
                                {{carRemark || ''}}
                            </view>
                        </view>
                        <view class="box-hz-list">
                            <view class="box-hz-list-item">
                            <view class="box-hz-list-item" @click="show = true">
                                <view class="box-hz-list-item-label"><text>用车时间:</text></view>
                                <view class="box-hz-list-item-val">
                                <view :class="form.startDate ? 'box-hz-list-item-val active' : 'box-hz-list-item-val'">
                                    <view class="box-hz-list-item-val-left">
                                        <image class="icon" src="/static/icon/ic_time@2x.png" mode="widthFix"></image>
                                        <text>请选择用车时间</text>
                                        <text>{{ form.startDate ? form.startDate + ' è‡³ ' + form.endDate : '请选择用工时间'}}</text>
                                    </view>
                                    <image class="icon1" src="/static/icon/home_ar_next@2x.png" mode="widthFix"></image>
                                    <u-icon name="arrow-right" color="#111111" size="16"></u-icon>
                                </view>
                            </view>
                            <view class="box-hz-list-item">
                                <view class="box-hz-list-item-label">
                                    <text>用车时间:</text>
                                    <text>+增加途经点</text>
                                    <text @click="addAddr">+增加途经点</text>
                                </view>
                                <view class="item-d">
                                    <view class="item-d-row">
                                    <view class="item-d-row" @click="selectAddress(2)">
                                        <view class="item-d-row-icon">
                                            <image src="/static/icon/ic_qidian@2x.png" mode="widthFix"></image>
                                        </view>
                                        <view class="item-d-row-val">
                                            <text>请选择用车起点</text>
                                            <image src="/static/icon/home_ar_next@2x.png" mode="widthFix"></image>
                                            <text>{{form.address ? form.address : '请选择用车起点'}}</text>
                                            <u-icon name="arrow-right" color="#111111" size="16"></u-icon>
                                        </view>
                                    </view>
                                    <view class="item-d-row">
                                    <view class="item-d-row" v-for="(item, index) in form.wayInfoDTOList" :key="index" @click="selectAddress(4, index)">
                                        <view class="item-d-row-icon">
                                            <image src="/static/icon/ic_jingguo@2x.png" mode="widthFix"></image>
                                        </view>
                                        <view class="item-d-row-val">
                                            <text style="color: #999999;">请选择途经地点</text>
                                            <image src="/static/icon/home_ar_next@2x.png" mode="widthFix"></image>
                                            <text>{{item.location ? item.location : '请选择途经地点'}}</text>
                                            <view style="display: flex; align-items: center; flex-shrink: 0;">
                                                <image src="/static/icon/ic_delete@2x.png" style="margin-right: 22rpx; width: 32rpx; height: 32rpx;" @click.stop="form.wayInfoDTOList.splice(index, 1)" mode="widthFix"></image>
                                                <u-icon name="arrow-right" color="#111111" size="16"></u-icon>
                                            </view>
                                        </view>
                                    </view>
                                    <view class="item-d-row">
                                    <view class="item-d-row" @click="selectAddress(3)">
                                        <view class="item-d-row-icon">
                                            <image src="/static/icon/ic_zhongdian@2x.png" mode="widthFix"></image>
                                        </view>
                                        <view class="item-d-row-val">
                                            <text>请选择用车终点</text>
                                            <image src="/static/icon/home_ar_next@2x.png" mode="widthFix"></image>
                                            <text>{{form.locationEnd ? form.locationEnd : '请选择用车终点'}}</text>
                                            <u-icon name="arrow-right" color="#111111" size="16"></u-icon>
                                        </view>
                                    </view>
                                </view>
@@ -439,7 +438,6 @@
                show: false,
                isLogin: false,
                typeId: 0,
                carId: 1,
                type: [
                    { name: '用工', id: 0 },
                    { name: '运货', id: 1 },
@@ -450,47 +448,62 @@
                    { name: '分拣工', id: 1 },
                    { name: '包装工', id: 2 }
                ],
                car: [
                    { name: '三轮车', id: 1 },
                    { name: '小货车', id: 2 }
                ],
                car: [],
                btn: require('@/static/image/btn.png'),
                typeViewId: 0,
                carImage: '',
                carRemark: '',
                
                form: {
                    startDate: '',
                    endDate: '',
                    latitude: '',
                    longitude: '',
                    address: '',
                    location: '',
                    locationEnd: '',
                    wayInfoDTOList: [],
                    workType: 0,
                    categoryId: '',
                    categoryName: '',
                    days: ''
                }
            }
        },
        onLoad() {
            this.getCategoryLists()
        },
        methods: {
            addAddr() {
                this.form.wayInfoDTOList.push({
                    lat: '',
                    lgt: '',
                    location: ''
                })
            },
            confirmDate(e) {
                this.form.startDate = e[0]
                this.form.endDate = e[e.length - 1]
                this.form.days = e.length;
                this.show = false
            },
            selectAddress(type) {
            selectAddress(type, index) {
                uni.chooseLocation({
                    success: (res) => {
                        if (type === 1) {
                        if ([1,2].includes(type)) {
                            this.form.latitude = res.latitude
                            this.form.longitude = res.longitude
                            this.form.address = res.address
                        } else if (type === 3) {
                            this.form.locationEnd = res.address
                            this.form.latEnd = res.latitude
                            this.form.lgtEnd = res.longitude
                        } else if (type === 4) {
                            this.form.wayInfoDTOList[0].lat = res.latitude
                            this.form.wayInfoDTOList[0].lgt = res.longitude
                            this.form.wayInfoDTOList[0].location = res.address
                        }
                    }
                });
                // uni.getLocation({
                //     type: 'gcj02',
                //     success: function (res) {
                //         console.log(res)
                //     }
                // });
            },
            clickType(e) {
                this.typeId = e
@@ -498,8 +511,11 @@
            clickTypeWork(e) {
                this.form.workType = e
            },
            clickCar(e) {
                this.carId = e
            clickCar(item) {
                this.form.categoryId = item.id
                this.form.categoryName = item.name
                this.carImage = item.iconFull
                this.carRemark = item.remark
            },
            jump() {
                if(!this.userInfo || !this.token){
@@ -511,31 +527,76 @@
                
                const { form } = this;
                
                if (!form.startDate || !form.endDate) {
                    return uni.showToast({ title: '请选择用工时间', icon: 'none' })
                } else if (!form.latitude || !form.longitude) {
                    return uni.showToast({ title: '请选择用工地点', icon: 'none' })
                }
                // é‡‡æ‘˜å·¥
                if (this.form.workType === 0) {
                // ç”¨å·¥
                if (this.typeId === 0) {
                    if (!form.startDate || !form.endDate) {
                        return uni.showToast({ title: '请选择用工时间', icon: 'none' })
                    } else if (!form.latitude || !form.longitude) {
                        return uni.showToast({ title: '请选择用工地点', icon: 'none' })
                    }
                    // é‡‡æ‘˜å·¥
                    if (this.form.workType === 0) {
                        uni.navigateTo({
                            url: `/pages/using-workers/using-workers?days=${form.days}&startDate=${form.startDate}&endDate=${form.endDate}&latitude=${form.latitude}&longitude=${form.longitude}&address=${form.location}&workType=${form.workType}`
                        })
                    // åˆ†æ‹£å·¥
                    } else if (this.form.workType === 1) {
                        uni.navigateTo({
                            url: `/pages/sorting/sorting?days=${form.days}&startDate=${form.startDate}&endDate=${form.endDate}&latitude=${form.latitude}&longitude=${form.longitude}&address=${form.location}&workType=${form.workType}`
                        })
                    // åŒ…装工
                    } else if (this.form.workType === 2) {
                        uni.navigateTo({
                            url: `/pages/packaging-worker/packaging-worker?days=${form.days}&startDate=${form.startDate}&endDate=${form.endDate}&latitude=${form.latitude}&longitude=${form.longitude}&address=${form.location}&workType=${form.workType}`
                        })
                    }
                // è¿è´§
                } else if (this.typeId === 1) {
                    if (!form.startDate || !form.endDate) {
                        return uni.showToast({ title: '请选择用车时间', icon: 'none' })
                    } else if (!form.latitude || !form.longitude) {
                        return uni.showToast({ title: '请选择用车起点', icon: 'none' })
                    } else if (!form.latEnd || !form.lgtEnd) {
                        return uni.showToast({ title: '请选择用车终点', icon: 'none' })
                    }
                    let carData = {
                        startDate: form.startDate,
                        endDate: form.endDate,
                        lat: form.latitude,
                        lgt: form.longitude,
                        location: form.address,
                        latEnd: form.latEnd,
                        lgtEnd: form.lgtEnd,
                        locationEnd: form.locationEnd,
                        wayInfoDTOList: form.wayInfoDTOList,
                        categoryId: form.categoryId,
                        categoryName: form.categoryName,
                        days: form.days
                    }
                    uni.setStorageSync('carData', carData);
                    uni.navigateTo({
                        url: `/pages/using-workers/using-workers?days=${form.days}&startDate=${form.startDate}&endDate=${form.endDate}&latitude=${form.latitude}&longitude=${form.longitude}&address=${form.address}&workType=${form.workType}`
                    })
                // åˆ†æ‹£å·¥
                } else if (this.form.workType === 1) {
                    uni.navigateTo({
                        url: `/pages/sorting/sorting?days=${form.days}&startDate=${form.startDate}&endDate=${form.endDate}&latitude=${form.latitude}&longitude=${form.longitude}&address=${form.address}&workType=${form.workType}`
                    })
                // åŒ…装工
                } else if (this.form.workType === 2) {
                    uni.navigateTo({
                        url: `/pages/packaging-worker/packaging-worker?days=${form.days}&startDate=${form.startDate}&endDate=${form.endDate}&latitude=${form.latitude}&longitude=${form.longitude}&address=${form.address}&workType=${form.workType}`
                        url: `/pages/freight/freight`
                    })
                }
            },
            mverify(e){
                console.log('验证结果:',e);
            },
            getCategoryLists() {
                this.$u.api.getCategoryList({ type: 1 })
                    .then(res => {
                        this.car = res.data
                        this.form.categoryId = res.data[0].id
                        this.form.categoryName = res.data[0].name
                        this.carRemark = res.data[0].remark
                        this.carImage = res.data[0].iconFull
                    })
            }
        }
    }
@@ -1179,9 +1240,12 @@
                        .box-hz-car-image {
                            width: 432rpx;
                            height: 200rpx;
                            display: flex;
                            align-items: center;
                            justify-content: center;
                            overflow: hidden;
                            image {
                                width: 100%;
                                height: 100%;
                            }
                        }
                        .box-hz-car-info {
@@ -1244,7 +1308,8 @@
                                border: 2rpx solid #FD9E24;
                                .item-d-row {
                                    width: 100%;
                                    height: 100rpx;
                                    // height: 100rpx;
                                    height: auto;
                                    display: flex;
                                    align-items: center;
                                    justify-content: space-between;
@@ -1260,7 +1325,9 @@
                                    }
                                    .item-d-row-val {
                                        flex: 1;
                                        height: 100%;
                                        // height: 100%;
                                        padding: 30rpx 0;
                                        box-sizing: border-box;
                                        display: flex;
                                        align-items: center;
                                        justify-content: space-between;
small-program/pages/packaging-worker/packaging-worker.vue
@@ -89,7 +89,7 @@
                    <view class="list-item-row-label">费用标准<b>*</b></view>
                    <view class="list-item-row-val">
                        <input v-model="form.price" type="number" @blur="getPrice" placeholder="请输入" />
                        <text>元/人/天</text>
                        <text>{{form.priceUnit}}</text>
                    </view>
                </view>
            </view>
@@ -226,7 +226,7 @@
                    .then(res => {
                        if (res.code == 200) {
                            uni.navigateTo({
                                url: `/pages/success/success?orderId=${res.data}`
                                url: `/pages/success/success?orderId=${res.data.id}`
                            })
                        }
                    })
@@ -236,7 +236,6 @@
                if (this.form.price && this.form.priceNum1 && this.form.priceNum2) {
                    this.$u.api.getTotal({
                        price: Number(this.form.price) * 100,
                        priceUnit: this.form.priceUnit,
                        priceNum1: this.form.priceNum1,
                        priceNum2: this.form.priceNum2,
                        type: this.form.type,
small-program/pages/sorting/sorting.vue
@@ -217,7 +217,7 @@
                    .then(res => {
                        if (res.code == 200) {
                            uni.navigateTo({
                                url: `/pages/success/success?orderId=${res.data}`
                                url: `/pages/success/success?orderId=${res.data.id}`
                            })
                        }
                    })
small-program/pages/using-workers/using-workers.vue
@@ -209,7 +209,7 @@
                    .then(res => {
                        if (res.code == 200) {
                            uni.navigateTo({
                                url: `/pages/success/success?orderId=${res.data}`
                                url: `/pages/success/success?orderId=${res.data.id}`
                            })
                        }
                    })