Просмотр исходного кода

Merge branch 'develop' of https://gitee.com/zhijiantianya/ruoyi-vue-pro

# Conflicts:
#	yudao-dependencies/pom.xml
#	yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/ota/vo/firmware/IotOtaFirmwareCreateReqVO.java
#	yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/ota/vo/firmware/IotOtaFirmwareUpdateReqVO.java
#	yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/ota/vo/upgrade/record/IotOtaUpgradeRecordPageReqVO.java
#	yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/ota/vo/upgrade/task/IotOtaUpgradeTaskPageReqVO.java
#	yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/aftersale/AfterSaleServiceImpl.java
#	yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/auth/MemberAuthServiceImpl.java
#	yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/social/SocialClientService.java
#	yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/social/SocialClientServiceImpl.java
#	yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/social/SocialUserServiceImpl.java
#	yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/social/SocialClientServiceImplTest.java
#	yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/social/SocialUserServiceImplTest.java
YunaiV 6 месяцев назад
Родитель
Сommit
54941f1361
71 измененных файлов с 2342 добавлено и 246 удалено
  1. 14 0
      yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/collection/CollectionUtils.java
  2. 3 3
      yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/form/BpmTaskCandidateFormUserStrategy.java
  3. 3 3
      yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/other/BpmTaskCandidateExpressionStrategy.java
  4. 2 2
      yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java
  5. 4 0
      yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/enums/codegen/CodegenFrontTypeEnum.java
  6. 3 1
      yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/framework/file/core/client/s3/S3FileClient.java
  7. 19 0
      yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/codegen/inner/CodegenEngine.java
  8. 152 0
      yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3_vben5_antd/general/api/api.ts.vm
  9. 0 0
      yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3_vben5_antd/general/index.vue.vm
  10. 349 0
      yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3_vben5_antd/general/views/data.ts.vm
  11. 313 0
      yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3_vben5_antd/general/views/form.vue.vm
  12. 357 0
      yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3_vben5_antd/general/views/index.vue.vm
  13. 93 0
      yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3_vben5_antd/general/views/modules/form_sub_erp.vue.vm
  14. 2 0
      yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3_vben5_antd/general/views/modules/form_sub_inner.vue.vm
  15. 199 0
      yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3_vben5_antd/general/views/modules/form_sub_normal.vue.vm
  16. 184 0
      yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3_vben5_antd/general/views/modules/list_sub_erp.vue.vm
  17. 4 0
      yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3_vben5_antd/general/views/modules/list_sub_inner.vue.vm
  18. 6 38
      yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3_vben5_antd/schema/views/data.ts.vm
  19. 9 2
      yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3_vben5_antd/schema/views/form.vue.vm
  20. 9 2
      yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3_vben5_antd/schema/views/modules/form_sub_erp.vue.vm
  21. 10 3
      yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3_vben5_antd/schema/views/modules/form_sub_normal.vue.vm
  22. 6 9
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/ota/vo/firmware/IotOtaFirmwareCreateReqVO.java
  23. 8 10
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/ota/vo/firmware/IotOtaFirmwareRespVO.java
  24. 4 7
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/ota/vo/firmware/IotOtaFirmwareUpdateReqVO.java
  25. 3 6
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/ota/vo/upgrade/record/IotOtaUpgradeRecordPageReqVO.java
  26. 14 16
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/ota/vo/upgrade/record/IotOtaUpgradeRecordRespVO.java
  27. 2 5
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/ota/vo/upgrade/task/IotOtaUpgradeTaskPageReqVO.java
  28. 8 10
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/ota/vo/upgrade/task/IotOtaUpgradeTaskRespVO.java
  29. 4 6
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/ota/vo/upgrade/task/IotOtaUpgradeTaskSaveReqVO.java
  30. 13 0
      yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/api/sku/ProductSkuApi.java
  31. 1 1
      yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/api/spu/ProductSpuApi.java
  32. 3 5
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/history/vo/ProductBrowseHistoryRespVO.java
  33. 1 3
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/favorite/vo/AppFavoriteBatchReqVO.java
  34. 1 3
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/favorite/vo/AppFavoriteReqVO.java
  35. 2 3
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/favorite/vo/AppFavoriteRespVO.java
  36. 1 3
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/history/vo/AppProductBrowseHistoryDeleteReqVO.java
  37. 7 9
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/history/vo/AppProductBrowseHistoryRespVO.java
  38. 1 1
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/point/PointActivityController.java
  39. 1 1
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/point/AppPointActivityController.java
  40. 9 8
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponServiceImpl.java
  41. 8 6
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/aftersale/AppAfterSaleController.java
  42. 20 0
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/aftersale/vo/AppAfterSalePageReqVO.java
  43. 6 2
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/base/spu/AppProductSpuBaseRespVO.java
  44. 14 0
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/delivery/vo/pickup/AppDeliveryPickUpStoreRespVO.java
  45. 0 5
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/convert/aftersale/AfterSaleConvert.java
  46. 3 5
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/convert/cart/TradeCartConvert.java
  47. 5 4
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/aftersale/AfterSaleMapper.java
  48. 3 3
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/aftersale/AfterSaleService.java
  49. 5 5
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/aftersale/AfterSaleServiceImpl.java
  50. 1 1
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/BrokerageWithdrawServiceImpl.java
  51. 18 2
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceImpl.java
  52. 10 5
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeBrokerageOrderHandler.java
  53. 2 3
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/auth/MemberAuthServiceImpl.java
  54. 1 0
      yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/ErrorCodeConstants.java
  55. 1 1
      yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/social/SocialTypeEnum.java
  56. 6 2
      yudao-module-system/yudao-module-system-biz/pom.xml
  57. 1 1
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/api/social/SocialClientApiImpl.java
  58. 1 1
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/social/SocialClientDO.java
  59. 39 0
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/justauth/config/YudaoJustAuthConfiguration.java
  60. 321 0
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/justauth/core/AuthRequestFactory.java
  61. 6 0
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/justauth/package-info.java
  62. 2 3
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/social/SocialClientService.java
  63. 9 9
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/social/SocialClientServiceImpl.java
  64. 3 4
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/social/SocialUserServiceImpl.java
  65. 2 0
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/tenant/TenantServiceImpl.java
  66. 9 2
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/user/AdminUserServiceImpl.java
  67. 13 14
      yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/social/SocialClientServiceImplTest.java
  68. 2 2
      yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/social/SocialUserServiceImplTest.java
  69. 1 0
      yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/user/AdminUserServiceImplTest.java
  70. 1 1
      yudao-server/src/main/resources/application-dev.yaml
  71. 5 5
      yudao-server/src/main/resources/application-local.yaml

+ 14 - 0
yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/collection/CollectionUtils.java

@@ -11,6 +11,7 @@ import java.util.function.*;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
+import static cn.hutool.core.convert.Convert.toCollection;
 import static java.util.Arrays.asList;
 
 /**
@@ -335,4 +336,17 @@ public class CollectionUtils {
         return list.stream().filter(Objects::nonNull).flatMap(Collection::stream).collect(Collectors.toList());
     }
 
+    /**
+     * 转换为 LinkedHashSet
+     *
+     * @param <T>         元素类型
+     * @param elementType 集合中元素类型
+     * @param value       被转换的值
+     * @return {@link LinkedHashSet}
+     */
+    @SuppressWarnings("unchecked")
+    public static <T> LinkedHashSet<T> toLinkedHashSet(Class<T> elementType, Object value) {
+        return (LinkedHashSet<T>) toCollection(LinkedHashSet.class, elementType, value);
+    }
+
 }

+ 3 - 3
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/form/BpmTaskCandidateFormUserStrategy.java

@@ -1,7 +1,7 @@
 package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.form;
 
-import cn.hutool.core.convert.Convert;
 import cn.hutool.core.lang.Assert;
+import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
 import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy;
 import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.user.BpmTaskCandidateUserStrategy;
 import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum;
@@ -33,7 +33,7 @@ public class BpmTaskCandidateFormUserStrategy implements BpmTaskCandidateStrateg
     @Override
     public Set<Long> calculateUsersByTask(DelegateExecution execution, String param) {
         Object result = execution.getVariable(param);
-        return Convert.toSet(Long.class, result);
+        return CollectionUtils.toLinkedHashSet(Long.class, result);
     }
 
     @Override
@@ -41,7 +41,7 @@ public class BpmTaskCandidateFormUserStrategy implements BpmTaskCandidateStrateg
                                               String param, Long startUserId, String processDefinitionId,
                                               Map<String, Object> processVariables) {
         Object result = processVariables == null ? null : processVariables.get(param);
-        return Convert.toSet(Long.class, result);
+        return CollectionUtils.toLinkedHashSet(Long.class, result);
     }
 
 }

+ 3 - 3
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/other/BpmTaskCandidateExpressionStrategy.java

@@ -1,6 +1,6 @@
 package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.other;
 
-import cn.hutool.core.convert.Convert;
+import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
 import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy;
 import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum;
 import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils;
@@ -37,7 +37,7 @@ public class BpmTaskCandidateExpressionStrategy implements BpmTaskCandidateStrat
     @Override
     public Set<Long> calculateUsersByTask(DelegateExecution execution, String param) {
         Object result = FlowableUtils.getExpressionValue(execution, param);
-        return Convert.toSet(Long.class, result);
+        return CollectionUtils.toLinkedHashSet(Long.class, result);
     }
 
     @Override
@@ -46,7 +46,7 @@ public class BpmTaskCandidateExpressionStrategy implements BpmTaskCandidateStrat
         Map<String, Object> variables = processVariables == null ? new HashMap<>() : processVariables;
         try {
             Object result = FlowableUtils.getExpressionValue(variables, param);
-            return Convert.toSet(Long.class, result);
+            return CollectionUtils.toLinkedHashSet(Long.class, result);
         } catch (FlowableException ex) {
             // 预测未运行的节点时候,表达式如果包含 execution 或者不存在的流程变量会抛异常,
             log.warn("[calculateUsersByActivity][表达式({}) 变量({}) 解析报错", param, variables, ex);

+ 2 - 2
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java

@@ -6,7 +6,6 @@ import cn.hutool.core.lang.Assert;
 import cn.hutool.core.util.*;
 import cn.hutool.extra.spring.SpringUtil;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
-import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
 import cn.iocoder.yudao.framework.common.util.date.DateUtils;
 import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
 import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
@@ -874,12 +873,14 @@ public class BpmTaskServiceImpl implements BpmTaskService {
         List<UserTask> returnUserTaskList = BpmnModelUtils.iteratorFindChildUserTasks(targetElement, runTaskKeyList, null, null);
         List<String> returnTaskKeyList = convertList(returnUserTaskList, UserTask::getId);
 
+        List<String> runExecutionIds = new ArrayList<>();
         // 2. 给当前要被退回的 task 数组,设置退回意见
         taskList.forEach(task -> {
             // 需要排除掉,不需要设置退回意见的任务
             if (!returnTaskKeyList.contains(task.getTaskDefinitionKey())) {
                 return;
             }
+            runExecutionIds.add(task.getExecutionId());
 
             // 判断是否分配给自己任务,因为会签任务,一个节点会有多个任务
             if (isAssignUserTask(userId, task)) { // 情况一:自己的任务,进行 RETURN 标记
@@ -899,7 +900,6 @@ public class BpmTaskServiceImpl implements BpmTaskService {
         // 4. 执行驳回
         // 使用 moveExecutionsToSingleActivityId 替换 moveActivityIdsToSingleActivityId 原因:
         // 当多实例任务回退的时候有问题。相关 issue: https://github.com/flowable/flowable-engine/issues/3944
-        List<String> runExecutionIds = convertList(taskList, Task::getExecutionId);
         runtimeService.createChangeActivityStateBuilder()
                 .processInstanceId(currentTask.getProcessInstanceId())
                 .moveExecutionsToSingleActivityId(runExecutionIds, reqVO.getTargetTaskDefinitionKey())

+ 4 - 0
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/enums/codegen/CodegenFrontTypeEnum.java

@@ -13,9 +13,13 @@ import lombok.Getter;
 public enum CodegenFrontTypeEnum {
 
     VUE2_ELEMENT_UI(10), // Vue2 Element UI 标准模版
+
     VUE3_ELEMENT_PLUS(20), // Vue3 Element Plus 标准模版
+
     VUE3_VBEN2_ANTD_SCHEMA(30), // Vue3 VBEN2 + ANTD + Schema 模版
+
     VUE3_VBEN5_ANTD_SCHEMA(40), // Vue3 VBEN5 + ANTD + schema 模版
+    VUE3_VBEN5_ANTD_GENERAL(41), // Vue3 VBEN5 + ANTD 标准模版
     ;
 
     /**

+ 3 - 1
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/framework/file/core/client/s3/S3FileClient.java

@@ -46,7 +46,9 @@ public class S3FileClient extends AbstractFileClient<S3FileClientConfig> {
                 AwsBasicCredentials.create(config.getAccessKey(), config.getAccessSecret()));
         URI endpoint = URI.create(buildEndpoint());
         S3Configuration serviceConfiguration = S3Configuration.builder() // Path-style 访问
-                .pathStyleAccessEnabled(Boolean.TRUE.equals(config.getEnablePathStyleAccess())).build();
+                .pathStyleAccessEnabled(Boolean.TRUE.equals(config.getEnablePathStyleAccess()))
+                .chunkedEncodingEnabled(false) // 禁用分块编码,参见 https://t.zsxq.com/kBy57
+                .build();
         client = S3Client.builder()
                 .credentialsProvider(credentialsProvider)
                 .region(region)

+ 19 - 0
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/codegen/inner/CodegenEngine.java

@@ -163,6 +163,25 @@ public class CodegenEngine {
                     vue3FilePath("views/${table.moduleName}/${table.businessName}/modules/${subSimpleClassName_strikeCase}-list.vue"))
             .put(CodegenFrontTypeEnum.VUE3_VBEN5_ANTD_SCHEMA.getType(), vue3Vben5AntdSchemaTemplatePath("views/modules/list_sub_erp.vue"),  // 特殊:主子表专属逻辑
                     vue3FilePath("views/${table.moduleName}/${table.businessName}/modules/${subSimpleClassName_strikeCase}-list.vue"))
+            // VUE3_VBEN5_ANTD
+            .put(CodegenFrontTypeEnum.VUE3_VBEN5_ANTD_GENERAL.getType(), vue3Vben5AntdGeneralTemplatePath("views/data.ts"),
+                    vue3FilePath("views/${table.moduleName}/${table.businessName}/data.ts"))
+            .put(CodegenFrontTypeEnum.VUE3_VBEN5_ANTD_GENERAL.getType(), vue3Vben5AntdGeneralTemplatePath("views/index.vue"),
+                    vue3FilePath("views/${table.moduleName}/${table.businessName}/index.vue"))
+            .put(CodegenFrontTypeEnum.VUE3_VBEN5_ANTD_GENERAL.getType(), vue3Vben5AntdGeneralTemplatePath("views/form.vue"),
+                    vue3FilePath("views/${table.moduleName}/${table.businessName}/modules/form.vue"))
+            .put(CodegenFrontTypeEnum.VUE3_VBEN5_ANTD_GENERAL.getType(), vue3Vben5AntdGeneralTemplatePath("api/api.ts"),
+                    vue3FilePath("api/${table.moduleName}/${table.businessName}/index.ts"))
+            .put(CodegenFrontTypeEnum.VUE3_VBEN5_ANTD_GENERAL.getType(), vue3Vben5AntdGeneralTemplatePath("views/modules/form_sub_normal.vue"),  // 特殊:主子表专属逻辑
+                    vue3FilePath("views/${table.moduleName}/${table.businessName}/modules/${subSimpleClassName_strikeCase}-form.vue"))
+            .put(CodegenFrontTypeEnum.VUE3_VBEN5_ANTD_GENERAL.getType(), vue3Vben5AntdGeneralTemplatePath("views/modules/form_sub_inner.vue"),  // 特殊:主子表专属逻辑
+                    vue3FilePath("views/${table.moduleName}/${table.businessName}/modules/${subSimpleClassName_strikeCase}-form.vue"))
+            .put(CodegenFrontTypeEnum.VUE3_VBEN5_ANTD_GENERAL.getType(), vue3Vben5AntdGeneralTemplatePath("views/modules/form_sub_erp.vue"),  // 特殊:主子表专属逻辑
+                    vue3FilePath("views/${table.moduleName}/${table.businessName}/modules/${subSimpleClassName_strikeCase}-form.vue"))
+            .put(CodegenFrontTypeEnum.VUE3_VBEN5_ANTD_GENERAL.getType(), vue3Vben5AntdGeneralTemplatePath("views/modules/list_sub_inner.vue"),  // 特殊:主子表专属逻辑
+                    vue3FilePath("views/${table.moduleName}/${table.businessName}/modules/${subSimpleClassName_strikeCase}-list.vue"))
+            .put(CodegenFrontTypeEnum.VUE3_VBEN5_ANTD_GENERAL.getType(), vue3Vben5AntdGeneralTemplatePath("views/modules/list_sub_erp.vue"),  // 特殊:主子表专属逻辑
+                    vue3FilePath("views/${table.moduleName}/${table.businessName}/modules/${subSimpleClassName_strikeCase}-list.vue"))
             .build();
 
     @Resource

+ 152 - 0
yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3_vben5_antd/general/api/api.ts.vm

@@ -0,0 +1,152 @@
+import type { PageParam, PageResult } from '@vben/request';
+
+import { requestClient } from '#/api/request';
+#set ($baseURL = "/${table.moduleName}/${simpleClassName_strikeCase}")
+
+export namespace ${simpleClassName}Api {
+## 特殊:主子表专属逻辑
+#foreach ($subTable in $subTables)
+  #set ($index = $foreach.count - 1)
+  #set ($subSimpleClassName = $subSimpleClassNames.get($index))
+  #set ($subColumns = $subColumnsList.get($index))##当前字段数组
+  /** ${subTable.classComment}信息 */
+  export interface ${subSimpleClassName} {
+    #foreach ($column in $subColumns)
+      #if ($column.createOperation || $column.updateOperation)
+        #if(${column.javaType.toLowerCase()} == "long" || ${column.javaType.toLowerCase()} == "integer" || ${column.javaType.toLowerCase()} == "short" || ${column.javaType.toLowerCase()} == "double" || ${column.javaType.toLowerCase()} == "bigdecimal")
+            ${column.javaField}#if($column.updateOperation && !$column.primaryKey && !$column.nullable)?#end: number; // ${column.columnComment}
+        #elseif(${column.javaType.toLowerCase()} == "date" || ${column.javaType.toLowerCase()} == "localdate" || ${column.javaType.toLowerCase()} == "localdatetime")
+            ${column.javaField}#if($column.updateOperation && !$column.primaryKey && !$column.nullable)?#end: Date; // ${column.columnComment}
+        #else
+            ${column.javaField}#if($column.updateOperation && !$column.primaryKey && !$column.nullable)?#end: ${column.javaType.toLowerCase()}; // ${column.columnComment}
+        #end
+      #end
+    #end
+  }
+
+#end
+  /** ${table.classComment}信息 */
+  export interface ${simpleClassName} {
+#foreach ($column in $columns)
+#if ($column.createOperation || $column.updateOperation)
+#if(${column.javaType.toLowerCase()} == "long" || ${column.javaType.toLowerCase()} == "integer" || ${column.javaType.toLowerCase()} == "short" || ${column.javaType.toLowerCase()} == "double" || ${column.javaType.toLowerCase()} == "bigdecimal")
+    ${column.javaField}#if($column.updateOperation && !$column.primaryKey && !$column.nullable)?#end: number; // ${column.columnComment}
+#elseif(${column.javaType.toLowerCase()} == "date" || ${column.javaType.toLowerCase()} == "localdate" || ${column.javaType.toLowerCase()} == "localdatetime")
+    ${column.javaField}#if($column.updateOperation && !$column.primaryKey && !$column.nullable)?#end: Date; // ${column.columnComment}
+#else
+    ${column.javaField}#if($column.updateOperation && !$column.primaryKey && !$column.nullable)?#end: ${column.javaType.toLowerCase()}; // ${column.columnComment}
+#end
+#end
+#end
+#if ( $table.templateType == 2 )
+  children?: ${simpleClassName}[];
+#end
+## 特殊:主子表专属逻辑
+#if ( $table.templateType == 10 || $table.templateType == 12 )
+  #foreach ($subTable in $subTables)
+    #set ($index = $foreach.count - 1)
+    #set ($subSimpleClassName = $subSimpleClassNames.get($index))
+    #if ( $subTable.subJoinMany )
+        ${subSimpleClassName.toLowerCase()}s?: ${subSimpleClassName}[]
+    #else
+        ${subSimpleClassName.toLowerCase()}?: ${subSimpleClassName}
+    #end
+  #end
+#end
+  }
+}
+
+#if ( $table.templateType != 2 )
+/** 查询${table.classComment}分页 */
+export function get${simpleClassName}Page(params: PageParam) {
+  return requestClient.get<PageResult<${simpleClassName}Api.${simpleClassName}>>('${baseURL}/page', { params });
+}
+#else
+/** 查询${table.classComment}列表 */
+export function get${simpleClassName}List(params: any) {
+  return requestClient.get<${simpleClassName}Api.${simpleClassName}[]>('${baseURL}/list', { params });
+}
+#end
+
+/** 查询${table.classComment}详情 */
+export function get${simpleClassName}(id: number) {
+  return requestClient.get<${simpleClassName}Api.${simpleClassName}>(`${baseURL}/get?id=${id}`);
+}
+
+/** 新增${table.classComment} */
+export function create${simpleClassName}(data: ${simpleClassName}Api.${simpleClassName}) {
+  return requestClient.post('${baseURL}/create', data);
+}
+
+/** 修改${table.classComment} */
+export function update${simpleClassName}(data: ${simpleClassName}Api.${simpleClassName}) {
+  return requestClient.put('${baseURL}/update', data);
+}
+
+/** 删除${table.classComment} */
+export function delete${simpleClassName}(id: number) {
+  return requestClient.delete(`${baseURL}/delete?id=${id}`);
+}
+
+/** 导出${table.classComment} */
+export function export${simpleClassName}(params: any) {
+  return requestClient.download('${baseURL}/export-excel', params);
+}
+
+## 特殊:主子表专属逻辑
+#foreach ($subTable in $subTables)
+#set ($index = $foreach.count - 1)
+#set ($subSimpleClassName = $subSimpleClassNames.get($index))
+#set ($subPrimaryColumn = $subPrimaryColumns.get($index))##当前 primary 字段
+#set ($subJoinColumn = $subJoinColumns.get($index))##当前 join 字段
+#set ($SubJoinColumnName = $subJoinColumn.javaField.substring(0,1).toUpperCase() + ${subJoinColumn.javaField.substring(1)})##首字母大写
+#set ($subSimpleClassName_strikeCase = $subSimpleClassName_strikeCases.get($index))
+#set ($subJoinColumn_strikeCase = $subJoinColumn_strikeCases.get($index))
+#set ($subClassNameVar = $subClassNameVars.get($index))
+
+// ==================== 子表($subTable.classComment) ====================
+
+## 情况一:MASTER_ERP 时,需要分查询页子表
+#if ( $table.templateType == 11 )
+/** 获得${subTable.classComment}分页 */
+export function get${subSimpleClassName}Page(params: PageParam) {
+  return requestClient.get<PageResult<${simpleClassName}Api.${subSimpleClassName}>>(`${baseURL}/${subSimpleClassName_strikeCase}/page`, { params });
+}
+## 情况二:非 MASTER_ERP 时,需要列表查询子表
+#else
+  #if ( $subTable.subJoinMany )
+/** 获得${subTable.classComment}列表 */
+export function get${subSimpleClassName}ListBy${SubJoinColumnName}(${subJoinColumn.javaField}: number) {
+  return requestClient.get<${simpleClassName}Api.${subSimpleClassName}[]>(`${baseURL}/${subSimpleClassName_strikeCase}/list-by-${subJoinColumn_strikeCase}?${subJoinColumn.javaField}=${${subJoinColumn.javaField}}`);
+}
+  #else
+/** 获得${subTable.classComment} */
+export function get${subSimpleClassName}By${SubJoinColumnName}(${subJoinColumn.javaField}: number) {
+  return requestClient.get<${simpleClassName}Api.${subSimpleClassName}>(`${baseURL}/${subSimpleClassName_strikeCase}/get-by-${subJoinColumn_strikeCase}?${subJoinColumn.javaField}=${${subJoinColumn.javaField}}`);
+}
+  #end
+#end
+## 特殊:MASTER_ERP 时,支持单个的新增、修改、删除操作
+#if ( $table.templateType == 11 )
+/** 新增${subTable.classComment} */
+export function create${subSimpleClassName}(data: ${simpleClassName}Api.${subSimpleClassName}) {
+  return requestClient.post(`${baseURL}/${subSimpleClassName_strikeCase}/create`, data);
+}
+
+/** 修改${subTable.classComment} */
+export function update${subSimpleClassName}(data: ${simpleClassName}Api.${subSimpleClassName}) {
+  return requestClient.put(`${baseURL}/${subSimpleClassName_strikeCase}/update`, data);
+}
+
+/** 删除${subTable.classComment} */
+export function delete${subSimpleClassName}(id: number) {
+  return requestClient.delete(`${baseURL}/${subSimpleClassName_strikeCase}/delete?id=${id}`);
+}
+
+/** 获得${subTable.classComment} */
+export function get${subSimpleClassName}(id: number) {
+  return requestClient.get<${simpleClassName}Api.${subSimpleClassName}>(`${baseURL}/${subSimpleClassName_strikeCase}/get?id=${id}`);
+}
+#end
+#end
+

+ 0 - 0
yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3_vben5_antd/general/index.vue.vm


+ 349 - 0
yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3_vben5_antd/general/views/data.ts.vm

@@ -0,0 +1,349 @@
+import type { VxeTableGridOptions } from '@vben/plugins/vxe-table';
+import type { VbenFormSchema } from '#/adapter/form';
+import type { OnActionClickFn } from '#/adapter/vxe-table';
+import type { ${simpleClassName}Api } from '#/api/${table.moduleName}/${simpleClassName_strikeCase}';
+
+import { z } from '#/adapter/form';
+#if(${table.templateType} == 2)## 树表需要导入这些
+import { get${simpleClassName}List } from '#/api/${table.moduleName}/${simpleClassName_strikeCase}';
+import { handleTree } from '#/utils/tree';
+#end
+import { DICT_TYPE, getDictOptions } from '#/utils/dict';
+import { getRangePickerDefaultProps } from '#/utils/date';
+import { useAccess } from '@vben/access';
+
+const { hasAccessByCodes } = useAccess();
+
+/** 列表的搜索表单 */
+export function useGridFormSchema(): VbenFormSchema[] {
+  return [
+#foreach($column in $columns)
+#if ($column.listOperation)
+  #set ($dictType = $column.dictType)
+  #set ($javaType = $column.javaType)
+  #set ($javaField = $column.javaField)
+  #set ($comment = $column.columnComment)
+  #if ($javaType == "Integer" || $javaType == "Long" || $javaType == "Byte" || $javaType == "Short")
+    #set ($dictMethod = "number")
+  #elseif ($javaType == "String")
+    #set ($dictMethod = "string")
+  #elseif ($javaType == "Boolean")
+    #set ($dictMethod = "boolean")
+  #end
+    {
+      fieldName: '${javaField}',
+      label: '${comment}',
+  #if ($column.htmlType == "input" || $column.htmlType == "textarea" || $column.htmlType == "editor")
+      component: 'Input',
+      componentProps: {
+        allowClear: true,
+        placeholder: '请输入${comment}',
+      },
+  #elseif ($column.htmlType == "select" || $column.htmlType == "radio")
+      component: 'Select',
+      componentProps: {
+        allowClear: true,
+        #if ("" != $dictType)## 设置了 dictType 数据字典的情况
+        options: getDictOptions(DICT_TYPE.$dictType.toUpperCase(), '$dictMethod'),
+        #else## 未设置 dictType 数据字典的情况
+        options: [],
+        #end
+        placeholder: '请选择${comment}',
+      },
+  #elseif($column.htmlType == "datetime")
+      component: 'RangePicker',
+      componentProps: {
+        ...getRangePickerDefaultProps(),
+        allowClear: true,
+      },
+  #end
+    },
+#end
+#end
+  ];
+}
+
+/** 列表的字段 */
+export function useGridColumns(
+  onActionClick?: OnActionClickFn<${simpleClassName}Api.${simpleClassName}>,
+): VxeTableGridOptions<${simpleClassName}Api.${simpleClassName}>['columns'] {
+  return [
+#if ($table.templateType == 12) ## 内嵌情况
+      { type: 'expand', width: 80, slots: { content: 'expand_content' } },
+#end
+#foreach($column in $columns)
+#if ($column.listOperationResult)
+  #set ($dictType = $column.dictType)
+  #set ($javaField = $column.javaField)
+  #set ($comment = $column.columnComment)
+    {
+      field: '${javaField}',
+      title: '${comment}',
+      minWidth: 120,
+  #if ($column.javaType == "LocalDateTime")## 时间类型
+      formatter: 'formatDateTime',
+  #elseif("" != $dictType)## 数据字典
+      cellRender: {
+        name: 'CellDict',
+        props: { type: DICT_TYPE.$dictType.toUpperCase() },
+      },
+  #end
+  #if (${table.templateType} == 2 && $column.id == $treeNameColumn.id)## 树表特有:标记树节点列
+      treeNode: true,
+  #end
+    },
+#end
+#end
+    {
+      field: 'operation',
+      title: '操作',
+      minWidth: 200,
+      align: 'center',
+      fixed: 'right',
+      headerAlign: 'center',
+      showOverflow: false,
+      cellRender: {
+        attrs: {
+          nameField: '${columns[0].javaField}',
+          nameTitle: '${table.classComment}',
+          onClick: onActionClick,
+        },
+        name: 'CellOperation',
+        options: [
+#if (${table.templateType} == 2)## 树表特有操作
+          {
+            code: 'append',
+            text: '新增下级',
+            show: hasAccessByCodes(['${table.moduleName}:${simpleClassName_strikeCase}:create']),
+          },
+#end
+          {
+            code: 'edit',
+            show: hasAccessByCodes(['${table.moduleName}:${simpleClassName_strikeCase}:update']),
+          },
+          {
+            code: 'delete',
+            show: hasAccessByCodes(['${table.moduleName}:${simpleClassName_strikeCase}:delete']),
+#if (${table.templateType} == 2)## 树表禁止删除带有子节点的数据
+            disabled: (row: ${simpleClassName}Api.${simpleClassName}) => {
+                return !!(row.children && row.children.length > 0);
+            },
+#end
+          },
+        ],
+      },
+    },
+  ];
+}
+
+## 标准模式和内嵌模式时,主子关系一对一则生成表单schema,一对多则生成列表schema(内嵌模式时表单schema也要生成)。erp 模式时都生成
+## 特殊:主子表专属逻辑
+#foreach ($subTable in $subTables)
+    #set ($index = $foreach.count - 1)
+    #set ($subColumns = $subColumnsList.get($index))##当前字段数组
+    #set ($subSimpleClassName = $subSimpleClassNames.get($index))
+    #set ($subJoinColumn = $subJoinColumns.get($index))##当前 join 字段
+    #set ($subSimpleClassName_strikeCase = $subSimpleClassName_strikeCases.get($index))
+// ==================== 子表($subTable.classComment) ====================
+
+#if ($table.templateType == 11) ## erp 情况
+/** 列表的搜索表单 */
+export function use${subSimpleClassName}GridFormSchema(): VbenFormSchema[] {
+    return [
+        #foreach($column in $subColumns)
+            #if ($column.listOperation)
+                #set ($dictType = $column.dictType)
+                #set ($javaType = $column.javaType)
+                #set ($javaField = $column.javaField)
+                #set ($comment = $column.columnComment)
+                #if ($javaType == "Integer" || $javaType == "Long" || $javaType == "Byte" || $javaType == "Short")
+                    #set ($dictMethod = "number")
+                #elseif ($javaType == "String")
+                    #set ($dictMethod = "string")
+                #elseif ($javaType == "Boolean")
+                    #set ($dictMethod = "boolean")
+                #end
+                {
+                    fieldName: '${javaField}',
+                    label: '${comment}',
+                    #if ($column.htmlType == "input" || $column.htmlType == "textarea" || $column.htmlType == "editor")
+                        component: 'Input',
+                        componentProps: {
+                            allowClear: true,
+                            placeholder: '请输入${comment}',
+                        },
+                    #elseif ($column.htmlType == "select" || $column.htmlType == "radio")
+                        component: 'Select',
+                        componentProps: {
+                            allowClear: true,
+                            #if ("" != $dictType)## 设置了 dictType 数据字典的情况
+                                options: getDictOptions(DICT_TYPE.$dictType.toUpperCase(), '$dictMethod'),
+                            #else## 未设置 dictType 数据字典的情况
+                                options: [],
+                            #end
+                            placeholder: '请选择${comment}',
+                        },
+                    #elseif($column.htmlType == "datetime")
+                        component: 'RangePicker',
+                        componentProps: {
+                            ...getRangePickerDefaultProps(),
+                            allowClear: true,
+                        },
+                    #end
+                },
+            #end
+        #end
+    ];
+}
+
+/** 列表的字段 */
+export function use${subSimpleClassName}GridColumns(
+    onActionClick?: OnActionClickFn<${simpleClassName}Api.${subSimpleClassName}>,
+): VxeTableGridOptions<${simpleClassName}Api.${subSimpleClassName}>['columns'] {
+    return [
+        #foreach($column in $subColumns)
+            #if ($column.listOperationResult)
+                #set ($dictType = $column.dictType)
+                #set ($javaField = $column.javaField)
+                #set ($comment = $column.columnComment)
+                {
+                    field: '${javaField}',
+                    title: '${comment}',
+                    minWidth: 120,
+                    #if ($column.javaType == "LocalDateTime")## 时间类型
+                        formatter: 'formatDateTime',
+                    #elseif("" != $dictType)## 数据字典
+                        cellRender: {
+                            name: 'CellDict',
+                            props: { type: DICT_TYPE.$dictType.toUpperCase() },
+                        },
+                    #end
+                },
+            #end
+        #end
+        {
+            field: 'operation',
+            title: '操作',
+            minWidth: 200,
+            align: 'center',
+            fixed: 'right',
+            headerAlign: 'center',
+            showOverflow: false,
+            cellRender: {
+                attrs: {
+                    nameField: '${columns[0].javaField}',
+                    nameTitle: '${subTable.classComment}',
+                    onClick: onActionClick,
+                },
+                name: 'CellOperation',
+                options: [
+                    {
+                        code: 'edit',
+                        show: hasAccessByCodes(['${table.moduleName}:${simpleClassName_strikeCase}:update']),
+                    },
+                    {
+                        code: 'delete',
+                        show: hasAccessByCodes(['${table.moduleName}:${simpleClassName_strikeCase}:delete']),
+                    },
+                ],
+            },
+        },
+    ];
+}
+
+#else
+    #if ($subTable.subJoinMany) ## 一对多
+    /** 新增/修改列表的字段 */
+    export function use${subSimpleClassName}GridEditColumns(
+        onActionClick?: OnActionClickFn<${simpleClassName}Api.${subSimpleClassName}>,
+    ): VxeTableGridOptions<${simpleClassName}Api.${subSimpleClassName}>['columns'] {
+        return [
+            #foreach($column in $subColumns)
+                #if ($column.createOperation || $column.updateOperation)
+                    #if (!$column.primaryKey && $column.listOperationResult && $column.id != $subJoinColumn.id) ## 特殊:忽略主子表的 join 字段,不用填写
+                        #set ($dictType = $column.dictType)
+                        #set ($javaField = $column.javaField)
+                        #set ($comment = $column.columnComment)
+                        #if ($javaType == "Integer" || $javaType == "Long" || $javaType == "Byte" || $javaType == "Short")
+                            #set ($dictMethod = "number")
+                        #elseif ($javaType == "String")
+                            #set ($dictMethod = "string")
+                        #elseif ($javaType == "Boolean")
+                            #set ($dictMethod = "boolean")
+                        #end
+                        {
+                            field: '${javaField}',
+                            title: '${comment}',
+                            minWidth: 120,
+                            slots: { default: '${javaField}' },
+                            #if ($column.htmlType == "select" || $column.htmlType == "checkbox" || $column.htmlType == "radio")
+                                #if ("" != $dictType)## 有数据字典
+                                    params: {
+                                        options: getDictOptions(DICT_TYPE.$dictType.toUpperCase(), '$dictMethod'),
+                                    },
+                                #else
+                                    params: {
+                                        options: [],
+                                    },
+                                #end
+                            #end
+                        },
+                    #end
+                #end
+            #end
+            {
+                field: 'operation',
+                title: '操作',
+                minWidth: 60,
+                align: 'center',
+                fixed: 'right',
+                headerAlign: 'center',
+                showOverflow: false,
+                cellRender: {
+                    attrs: {
+                        nameField: '${columns[0].javaField}',
+                        nameTitle: '${table.classComment}',
+                        onClick: onActionClick,
+                    },
+                    name: 'CellOperation',
+                    options: [
+                        {
+                            code: 'delete',
+                            show: hasAccessByCodes(['${table.moduleName}:${simpleClassName_strikeCase}:delete']),
+                        },
+                    ],
+                },
+            },
+        ];
+    }
+    #end
+    #if ($table.templateType == 12) ## 内嵌情况
+    /** 列表的字段 */
+    export function use${subSimpleClassName}GridColumns(): VxeTableGridOptions<${simpleClassName}Api.${subSimpleClassName}>['columns'] {
+        return [
+            #foreach($column in $subColumns)
+                #if ($column.listOperationResult)
+                    #set ($dictType = $column.dictType)
+                    #set ($javaField = $column.javaField)
+                    #set ($comment = $column.columnComment)
+                    {
+                        field: '${javaField}',
+                        title: '${comment}',
+                        minWidth: 120,
+                        #if ($column.javaType == "LocalDateTime")## 时间类型
+                            formatter: 'formatDateTime',
+                        #elseif("" != $dictType)## 数据字典
+                            cellRender: {
+                                name: 'CellDict',
+                                props: { type: DICT_TYPE.$dictType.toUpperCase() },
+                            },
+                        #end
+                    },
+                #end
+            #end
+        ];
+    }
+
+    #end
+#end
+#end

+ 313 - 0
yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3_vben5_antd/general/views/form.vue.vm

@@ -0,0 +1,313 @@
+<script lang="ts" setup>
+import type { ${simpleClassName}Api } from '#/api/${table.moduleName}/${simpleClassName_strikeCase}';
+import type { Rule } from 'ant-design-vue/es/form';
+
+import { useVbenModal } from '@vben/common-ui';
+import { Tinymce as RichTextarea } from '#/components/tinymce';
+import { ImageUpload, FileUpload } from "#/components/upload";
+import { message, Tabs, Form, Input, Textarea, Select, RadioGroup, Radio, CheckboxGroup, Checkbox, DatePicker } from 'ant-design-vue';
+import { DICT_TYPE, getDictOptions } from '#/utils/dict';
+#if($table.templateType == 2)## 树表需要导入这些
+import { get${simpleClassName}List } from '#/api/${table.moduleName}/${simpleClassName_strikeCase}';
+import { handleTree } from '#/utils/tree';
+#end
+## 特殊:主子表专属逻辑
+#if ( $table.templateType == 10 || $table.templateType == 12 )
+  #foreach ($subSimpleClassName in $subSimpleClassNames)
+  #set ($index = $foreach.count - 1)
+  #set ($subSimpleClassName_strikeCase = $subSimpleClassName_strikeCases.get($index))
+  import ${subSimpleClassName}Form from './${subSimpleClassName_strikeCase}-form.vue'
+  #end
+#end
+
+import { computed, ref } from 'vue';
+import { $t } from '#/locales';
+import { get${simpleClassName}, create${simpleClassName}, update${simpleClassName} } from '#/api/${table.moduleName}/${simpleClassName_strikeCase}';
+
+const emit = defineEmits(['success']);
+const formRef = ref();
+const labelCol = { span: 5 };
+const wrapperCol = { span: 13 };
+const formData = ref<Partial<${simpleClassName}Api.${simpleClassName}>>({
+#foreach ($column in $columns)
+  #if ($column.createOperation || $column.updateOperation)
+    #if ($column.htmlType == "checkbox")
+        $column.javaField: [],
+    #else
+        $column.javaField: undefined,
+    #end
+  #end
+#end
+});
+const rules: Record<string, Rule[]> = {
+  #foreach ($column in $columns)
+    #if (($column.createOperation || $column.updateOperation) && !$column.nullable && !${column.primaryKey})## 创建或者更新操作 && 要求非空 && 非主键
+      #set($comment=$column.columnComment)
+        $column.javaField: [{ required: true, message: '${comment}不能为空', trigger: #if($column.htmlType == 'select')'change'#else'blur'#end }],
+    #end
+  #end
+};
+## 特殊:树表专属逻辑
+#if ( $table.templateType == 2 )
+const ${classNameVar}Tree = ref<any[]>([]) // 树形结构
+#end
+const getTitle = computed(() => {
+  return formData.value?.id
+    ? $t('ui.actionTitle.edit', ['${table.classComment}'])
+    : $t('ui.actionTitle.create', ['${table.classComment}']);
+});
+
+## 特殊:主子表专属逻辑
+#if ( $table.templateType == 10 || $table.templateType == 12 )
+  #if ( $subTables && $subTables.size() > 0 )
+  /** 子表的表单 */
+  const subTabsName = ref('$subClassNameVars.get(0)')
+    #foreach ($subClassNameVar in $subClassNameVars)
+      #set ($index = $foreach.count - 1)
+      #set ($subSimpleClassName = $subSimpleClassNames.get($index))
+      const ${subClassNameVar}FormRef = ref<InstanceType<typeof ${subSimpleClassName}Form>>()
+    #end
+  #end
+#end
+
+/** 重置表单 */
+const resetForm = () => {
+  formData.value = {
+    #foreach ($column in $columns)
+      #if ($column.createOperation || $column.updateOperation)
+        #if ($column.htmlType == "checkbox")
+            $column.javaField: [],
+        #else
+            $column.javaField: undefined,
+        #end
+      #end
+    #end
+  };
+  formRef.value?.resetFields();
+}
+
+## 特殊:树表专属逻辑
+#if ( $table.templateType == 2 )
+/** 获得${table.classComment}树 */
+const get${simpleClassName}Tree = async () => {
+  ${classNameVar}Tree.value = []
+  const data = await get${simpleClassName}List()
+  const root: Tree = { id: 0, name: '顶级${table.classComment}', children: [] }
+  root.children = handleTree(data, 'id', '${treeParentColumn.javaField}')
+  ${classNameVar}Tree.value.push(root)
+}
+#end
+
+const [Modal, modalApi] = useVbenModal({
+  async onConfirm() {
+    await formRef.value?.validate();
+    ## 特殊:主子表专属逻辑
+    #if ( $table.templateType == 10 || $table.templateType == 12 )
+      #if ( $subTables && $subTables.size() > 0 )
+        // 校验子表单
+        #foreach ($subTable in $subTables)
+          #set ($index = $foreach.count - 1)
+          #set ($subClassNameVar = $subClassNameVars.get($index))
+          #if ($subTable.subJoinMany) ## 一对多
+            ## TODO 列表值校验?
+          #else
+            const ${subClassNameVar}Valid = await ${subClassNameVar}FormRef.value?.validate();
+            if (!${subClassNameVar}Valid) {
+              subTabsName.value = '${subClassNameVar}';
+              return;
+            }
+          #end
+        #end
+      #end
+    #end
+    modalApi.lock();
+    // 提交表单
+    const data = formData.value as ${simpleClassName}Api.${simpleClassName};
+    ## 特殊:主子表专属逻辑
+    #if ( $table.templateType == 10 || $table.templateType == 12 )
+      #if ( $subTables && $subTables.size() > 0 )
+        // 拼接子表的数据
+        #foreach ($subTable in $subTables)
+          #set ($index = $foreach.count - 1)
+          #set ($subClassNameVar = $subClassNameVars.get($index))
+          #if ($subTable.subJoinMany)
+            data.${subClassNameVar}s = ${subClassNameVar}FormRef.value?.getData();
+          #else
+            data.${subClassNameVar} = await ${subClassNameVar}FormRef.value?.getValues();
+          #end
+        #end
+      #end
+    #end
+    try {
+      await (formData.value?.id ? update${simpleClassName}(data) : create${simpleClassName}(data));
+      // 关闭并提示
+      await modalApi.close();
+      emit('success');
+      message.success({
+        content: $t('ui.actionMessage.operationSuccess'),
+        key: 'action_process_msg',
+      });
+    } finally {
+      modalApi.unlock();
+    }
+  },
+  async onOpenChange(isOpen: boolean) {
+    if (!isOpen) {
+      resetForm()
+      return;
+    }
+
+    // 加载数据
+    let data = modalApi.getData<${simpleClassName}Api.${simpleClassName}>();
+    if (!data) {
+      return;
+    }
+    if (data.id) {
+      modalApi.lock();
+      try {
+        data = await get${simpleClassName}(data.id);
+      } finally {
+        modalApi.unlock();
+      }
+    }
+    formData.value = data;
+#if ( $table.templateType == 2 )
+    // 加载树数据
+    await get${simpleClassName}Tree()
+#end
+  },
+});
+</script>
+
+<template>
+  <Modal :title="getTitle">
+    <Form
+      ref="formRef"
+      :model="formData"
+      :rules="rules"
+      :label-col="labelCol"
+      :wrapper-col="wrapperCol"
+    >
+      #foreach($column in $columns)
+        #if ($column.createOperation || $column.updateOperation)
+          #set ($dictType = $column.dictType)
+          #set ($javaField = $column.javaField)
+          #set ($javaType = $column.javaType)
+          #set ($comment = $column.columnComment)
+          #if ($javaType == "Integer" || $javaType == "Long" || $javaType == "Byte" || $javaType == "Short")
+            #set ($dictMethod = "number")
+          #elseif ($javaType == "String")
+            #set ($dictMethod = "string")
+          #elseif ($javaType == "Boolean")
+            #set ($dictMethod = "boolean")
+          #end
+          #if ( $table.templateType == 2 && $column.id == $treeParentColumn.id )
+            <Form.Item label="${comment}" name="${javaField}">
+              <TreeSelect
+                      v-model:value="formData.${javaField}"
+                      :treeData="${classNameVar}Tree"
+                #if ($treeNameColumn.javaField == "name")
+                      :fieldNames="defaultProps"
+                #else
+                      :fieldNames="{...defaultProps, label: '$treeNameColumn.javaField'}"
+                #end
+                      checkable
+                      treeDefaultExpandAll
+                      placeholder="请选择${comment}"
+              />
+            </Form.Item>
+          #elseif ($column.htmlType == "input" && !$column.primaryKey)## 忽略主键,不用在表单里
+            <Form.Item label="${comment}" name="${javaField}">
+              <Input v-model:value="formData.${javaField}" placeholder="请输入${comment}" />
+            </Form.Item>
+          #elseif($column.htmlType == "imageUpload")## 图片上传
+            <Form.Item label="${comment}" name="${javaField}">
+              <ImageUpload v-model:value="formData.${javaField}" />
+            </Form.Item>
+          #elseif($column.htmlType == "fileUpload")## 文件上传
+            <Form.Item label="${comment}" name="${javaField}">
+              <FileUpload v-model:value="formData.${javaField}" />
+            </Form.Item>
+          #elseif($column.htmlType == "editor")## 文本编辑器
+            <Form.Item label="${comment}" name="${javaField}">
+              <RichTextarea v-model="formData.${javaField}" height="500px" />
+            </Form.Item>
+          #elseif($column.htmlType == "select")## 下拉框
+            <Form.Item label="${comment}" name="${javaField}">
+              <Select v-model:value="formData.${javaField}" placeholder="请选择${comment}">
+                #if ("" != $dictType)## 有数据字典
+                  <Select.Option
+                          v-for="dict in getDictOptions(DICT_TYPE.$dictType.toUpperCase(), '$dictMethod')"
+                          :key="dict.value"
+                          :label="dict.label"
+                          :value="dict.value"
+                  />
+                #else##没数据字典
+                  <Select.Option label="请选择字典生成" value="" />
+                #end
+              </Select>
+            </Form.Item>
+          #elseif($column.htmlType == "checkbox")## 多选框
+            <Form.Item label="${comment}" name="${javaField}">
+              <CheckboxGroup v-model:value="formData.${javaField}">
+                #if ("" != $dictType)## 有数据字典
+                  <Checkbox
+                          v-for="dict in getDictOptions(DICT_TYPE.$dictType.toUpperCase(), '$dictMethod')"
+                          :key="dict.value"
+                          :label="dict.label"
+                          :value="dict.value"
+                  />
+                #else##没数据字典
+                  <Checkbox label="请选择字典生成" />
+                #end
+              </CheckboxGroup>
+            </Form.Item>
+          #elseif($column.htmlType == "radio")## 单选框
+            <Form.Item label="${comment}" name="${javaField}">
+              <RadioGroup v-model:value="formData.${javaField}">
+                #if ("" != $dictType)## 有数据字典
+                  <Radio
+                          v-for="dict in getDictOptions(DICT_TYPE.$dictType.toUpperCase(), '$dictMethod')"
+                          :key="dict.value"
+                          :value="dict.value"
+                  >
+                    {{ dict.label }}
+                  </Radio>
+                #else##没数据字典
+                  <Radio value="1">请选择字典生成</Radio>
+                #end
+              </RadioGroup>
+            </Form.Item>
+          #elseif($column.htmlType == "datetime")## 时间框
+            <Form.Item label="${comment}" name="${javaField}">
+              <DatePicker
+                      v-model:value="formData.${javaField}"
+                      valueFormat="x"
+                      placeholder="选择${comment}"
+              />
+            </Form.Item>
+          #elseif($column.htmlType == "textarea")## 文本框
+            <Form.Item label="${comment}" name="${javaField}">
+              <Textarea v-model:value="formData.${javaField}" placeholder="请输入${comment}" />
+            </Form.Item>
+          #end
+        #end
+      #end
+    </Form>
+    ## 特殊:主子表专属逻辑
+    #if ( $table.templateType == 10 || $table.templateType == 12 )
+      <!-- 子表的表单 -->
+      <Tabs v-model:active-key="subTabsName">
+        #foreach ($subTable in $subTables)
+          #set ($index = $foreach.count - 1)
+          #set ($subClassNameVar = $subClassNameVars.get($index))
+          #set ($subSimpleClassName = $subSimpleClassNames.get($index))
+          #set ($subJoinColumn_strikeCase = $subJoinColumn_strikeCases.get($index))
+          <Tabs.TabPane key="$subClassNameVar" tab="${subTable.classComment}" force-render>
+            <${subSimpleClassName}Form ref="${subClassNameVar}FormRef" :${subJoinColumn_strikeCase}="formData?.id" />
+          </Tabs.TabPane>
+        #end
+      </Tabs>
+    #end
+  </Modal>
+</template>

+ 357 - 0
yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3_vben5_antd/general/views/index.vue.vm

@@ -0,0 +1,357 @@
+<script lang="ts" setup>
+import type { ${simpleClassName}Api } from '#/api/${table.moduleName}/${simpleClassName_strikeCase}';
+
+import { Page, useVbenModal } from '@vben/common-ui';
+import { formatDateTime } from '@vben/utils';
+import { Button, message,Tabs,Pagination,Form,RangePicker,DatePicker,Select,Input } from 'ant-design-vue';
+import { DictTag } from '#/components/dict-tag';
+import { DICT_TYPE, getDictOptions } from '#/utils/dict';
+import ${simpleClassName}Form from './modules/form.vue';
+import { Download, Plus, RefreshCw, Search } from '@vben/icons';
+import { ContentWrap } from "#/components/content-wrap";
+import { VxeColumn, VxeTable } from 'vxe-table';
+import { getRangePickerDefaultProps } from '#/utils/date';
+
+## 特殊:主子表专属逻辑
+#if ( $table.templateType == 11 || $table.templateType == 12 )
+    #foreach ($subSimpleClassName in $subSimpleClassNames)
+    #set ($index = $foreach.count - 1)
+    #set ($subSimpleClassName_strikeCase = $subSimpleClassName_strikeCases.get($index))
+    import ${subSimpleClassName}List from './modules/${subSimpleClassName_strikeCase}-list.vue'
+    #end
+#end
+
+import { ref, h, reactive,onMounted } from 'vue';
+import { $t } from '#/locales';
+#if (${table.templateType} == 2)## 树表接口
+import { handleTree } from '@/utils/tree'
+import { get${simpleClassName}List, delete${simpleClassName}, export${simpleClassName} } from '#/api/${table.moduleName}/${simpleClassName_strikeCase}';
+#else## 标准表接口
+import { get${simpleClassName}Page, delete${simpleClassName}, export${simpleClassName} } from '#/api/${table.moduleName}/${simpleClassName_strikeCase}';
+#end
+import { downloadByData } from '#/utils/download';
+
+#if ($table.templateType == 12 || $table.templateType == 11) ## 内嵌和erp情况
+/** 子表的列表 */
+const subTabsName = ref('$subClassNameVars.get(0)')
+#if ($table.templateType == 11)
+const select${simpleClassName} = ref<${simpleClassName}Api.${simpleClassName}>();
+#end
+#end
+
+const loading = ref(true) // 列表的加载中
+const list = ref<${simpleClassName}Api.${simpleClassName}[]>([]) // 列表的数据
+## 特殊:树表专属逻辑(树不需要分页接口)
+#if ( $table.templateType != 2 )
+const total = ref(0) // 列表的总页数
+#end
+const queryParams = reactive({
+## 特殊:树表专属逻辑(树不需要分页接口)
+#if ( $table.templateType != 2 )
+  pageNo: 1,
+  pageSize: 10,
+#end
+#foreach ($column in $columns)
+    #if ($column.listOperation)
+        #if ($column.listOperationCondition != 'BETWEEN')
+                $column.javaField: undefined,
+        #end
+        #if ($column.htmlType == "datetime" || $column.listOperationCondition == "BETWEEN")
+                $column.javaField: undefined,
+        #end
+    #end
+#end
+})
+const queryFormRef = ref() // 搜索的表单
+const exportLoading = ref(false) // 导出的加载中
+
+/** 查询列表 */
+const getList = async () => {
+  loading.value = true
+  try {
+      ## 特殊:树表专属逻辑(树不需要分页接口)
+      #if ( $table.templateType == 2 )
+        const data = await get${simpleClassName}List(queryParams)
+        list.value = handleTree(data, 'id', '${treeParentColumn.javaField}')
+      #else
+        const data = await get${simpleClassName}Page(queryParams)
+        list.value = data.list
+        total.value = data.total
+      #end
+  } finally {
+    loading.value = false
+  }
+}
+
+/** 搜索按钮操作 */
+const handleQuery = () => {
+  queryParams.pageNo = 1
+  getList()
+}
+
+/** 重置按钮操作 */
+const resetQuery = () => {
+  queryFormRef.value.resetFields()
+  handleQuery()
+}
+
+const [FormModal, formModalApi] = useVbenModal({
+  connectedComponent: ${simpleClassName}Form,
+  destroyOnClose: true,
+});
+
+/** 创建${table.classComment} */
+function onCreate() {
+  formModalApi.setData({}).open();
+}
+
+/** 编辑${table.classComment} */
+function onEdit(row: ${simpleClassName}Api.${simpleClassName}) {
+  formModalApi.setData(row).open();
+}
+
+#if (${table.templateType} == 2)## 树表特有:新增下级
+/** 新增下级${table.classComment} */
+function onAppend(row: ${simpleClassName}Api.${simpleClassName}) {
+  formModalApi.setData({ ${treeParentColumn.javaField}: row.id }).open();
+}
+#end
+
+/** 删除${table.classComment} */
+async function onDelete(row: ${simpleClassName}Api.${simpleClassName}) {
+  const hideLoading = message.loading({
+    content: $t('ui.actionMessage.deleting', [row.id]),
+    duration: 0,
+    key: 'action_process_msg',
+  });
+  try {
+    await delete${simpleClassName}(row.id as number);
+    message.success({
+      content: $t('ui.actionMessage.deleteSuccess', [row.id]),
+      key: 'action_process_msg',
+    });
+    await getList();
+  } catch {
+    hideLoading();
+  }
+}
+
+/** 导出表格 */
+async function onExport() {
+try {
+  exportLoading.value = true;
+  const data = await export${simpleClassName}(queryParams);
+  downloadByData(data, '${table.classComment}.xls');
+}finally {
+  exportLoading.value = false;
+}
+}
+
+/** 初始化 **/
+onMounted(() => {
+  getList()
+})
+</script>
+
+<template>
+  <Page auto-content-height>
+    <FormModal @success="getList" />
+
+    <ContentWrap>
+      <!-- 搜索工作栏 -->
+      <Form
+          class="-mb-15px"
+          :model="queryParams"
+          ref="queryFormRef"
+          layout="inline"
+      >
+          #foreach($column in $columns)
+              #if ($column.listOperation)
+                  #set ($dictType = $column.dictType)
+                  #set ($javaField = $column.javaField)
+                  #set ($javaType = $column.javaType)
+                  #set ($comment = $column.columnComment)
+                  #if ($javaType == "Integer" || $javaType == "Long" || $javaType == "Byte" || $javaType == "Short")
+                      #set ($dictMethod = "number")
+                  #elseif ($javaType == "String")
+                      #set ($dictMethod = "string")
+                  #elseif ($javaType == "Boolean")
+                      #set ($dictMethod = "boolean")
+                  #end
+                  #if ($column.htmlType == "input")
+                    <Form.Item label="${comment}" name="${javaField}">
+                      <Input
+                          v-model:value="queryParams.${javaField}"
+                          placeholder="请输入${comment}"
+                          allowClear
+                          @pressEnter="handleQuery"
+                          class="!w-240px"
+                      />
+                    </Form.Item>
+                  #elseif ($column.htmlType == "select" || $column.htmlType == "radio" || $column.htmlType == "checkbox")
+                    <Form.Item label="${comment}" name="${javaField}">
+                      <Select
+                          v-model:value="queryParams.${javaField}"
+                          placeholder="请选择${comment}"
+                          allowClear
+                          class="!w-240px"
+                      >
+                          #if ("" != $dictType)## 设置了 dictType 数据字典的情况
+                            <Select.Option
+                                v-for="dict in getDictOptions(DICT_TYPE.$dictType.toUpperCase(), '$dictMethod')"
+                                :key="dict.value"
+                                :label="dict.label"
+                                :value="dict.value"
+                            />
+                          #else## 未设置 dictType 数据字典的情况
+                            <Select.Option label="请选择字典生成" value="" />
+                          #end
+                      </Select>
+                    </Form.Item>
+                  #elseif($column.htmlType == "datetime")
+                      #if ($column.listOperationCondition != "BETWEEN")## 非范围
+                        <Form.Item label="${comment}" name="${javaField}">
+                          <DatePicker
+                              v-model:value="queryParams.${javaField}"
+                              valueFormat="YYYY-MM-DD"
+                              placeholder="选择${comment}"
+                              allowClear
+                              class="!w-240px"
+                          />
+                        </Form.Item>
+                      #else## 范围
+                        <Form.Item label="${comment}" name="${javaField}">
+                          <RangePicker
+                              v-model:value="queryParams.${javaField}"
+                              v-bind="getRangePickerDefaultProps()"
+                              class="!w-220px"
+                          />
+                        </Form.Item>
+                      #end
+                  #end
+              #end
+          #end
+        <Form.Item>
+          <Button class="ml-2" @click="handleQuery" :icon="h(Search)">搜索</Button>
+          <Button class="ml-2" @click="resetQuery" :icon="h(RefreshCw)">重置</Button>
+          <Button
+              class="ml-2"
+              :icon="h(Plus)"
+              type="primary"
+              @click="onCreate"
+              v-access:code="['${permissionPrefix}:create']"
+          >
+            {{ $t('ui.actionTitle.create', ['示例联系人']) }}
+          </Button>
+          <Button
+              :icon="h(Download)"
+              type="primary"
+              class="ml-2"
+              :loading="exportLoading"
+              @click="onExport"
+              v-access:code="['${permissionPrefix}:export']"
+          >
+            {{ $t('ui.actionTitle.export') }}
+          </Button>
+        </Form.Item>
+      </Form>
+    </ContentWrap>
+    
+    <!-- 列表 -->
+    <ContentWrap>
+      <vxe-table :data="list"  show-overflow :loading="loading">
+          ## 特殊:主子表专属逻辑
+          #if ( $table.templateType == 12 && $subTables && $subTables.size() > 0 )
+            <!-- 子表的列表 -->
+            <vxe-column type="expand" width="60">
+              <template #content="{ row }">
+                <!-- 子表的表单 -->
+                <Tabs v-model:active-key="subTabsName" class="mx-8">
+                    #foreach ($subTable in $subTables)
+                        #set ($index = $foreach.count - 1)
+                        #set ($subClassNameVar = $subClassNameVars.get($index))
+                        #set ($subSimpleClassName = $subSimpleClassNames.get($index))
+                        #set ($subJoinColumn_strikeCase = $subJoinColumn_strikeCases.get($index))
+                      <Tabs.TabPane key="$subClassNameVar" tab="${subTable.classComment}" force-render>
+                        <${subSimpleClassName}List :${subJoinColumn_strikeCase}="row?.id" />
+                      </Tabs.TabPane>
+                    #end
+                </Tabs>
+              </template>
+            </vxe-column>
+          #end
+          #foreach($column in $columns)
+              #if ($column.listOperationResult)
+                  #set ($dictType=$column.dictType)
+                  #set ($javaField = $column.javaField)
+                  #set ($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
+                  #set ($comment=$column.columnComment)
+                  #if ($column.javaType == "LocalDateTime")## 时间类型
+                    <vxe-column field="${javaField}" title="${comment}" align="center">
+                      <template #default="{row}">
+                        {{formatDateTime(row.${javaField})}}
+                      </template>
+                    </vxe-column>
+                  #elseif($column.dictType && "" != $column.dictType)## 数据字典
+                    <vxe-column field="${javaField}" title="${comment}" align="center">
+                      <template #default="{row}">
+                        <dict-tag :type="DICT_TYPE.$dictType.toUpperCase()" :value="row.${javaField}" />
+                      </template>
+                    </vxe-column>
+                  #else
+                    <vxe-column field="${javaField}" title="${comment}" align="center" />
+                  #end
+              #end
+          #end
+        <vxe-column field="operation" title="操作" align="center">
+          <template #default="{row}">
+            <Button
+                size="small"
+                type="link"
+                @click="onEdit(row as any)"
+                v-access:code="['${permissionPrefix}:update']"
+            >
+              {{ $t('ui.actionTitle.edit') }}
+            </Button>
+            <Button
+                size="small"
+                type="link"
+                class="ml-2"
+                @click="onDelete(row as any)"
+                v-access:code="['${permissionPrefix}:delete']"
+            >
+              {{ $t('ui.actionTitle.delete') }}
+            </Button>
+          </template>
+        </vxe-column>
+      </vxe-table>
+      <!-- 分页 -->
+      <div class="mt-2 flex justify-end">
+        <Pagination
+            :total="total"
+            v-model:current="queryParams.pageNo"
+            v-model:page-size="queryParams.pageSize"
+            show-size-changer
+            @change="getList"
+        />
+      </div>
+    </ContentWrap>
+
+#if ($table.templateType == 11) ## erp情况
+  <ContentWrap>
+    <!-- 子表的表单 -->
+    <Tabs v-model:active-key="subTabsName">
+        #foreach ($subTable in $subTables)
+            #set ($index = $foreach.count - 1)
+            #set ($subClassNameVar = $subClassNameVars.get($index))
+            #set ($subSimpleClassName = $subSimpleClassNames.get($index))
+            #set ($subJoinColumn_strikeCase = $subJoinColumn_strikeCases.get($index))
+          <Tabs.TabPane key="$subClassNameVar" tab="${subTable.classComment}" force-render>
+            <${subSimpleClassName}List :${subJoinColumn_strikeCase}="select${simpleClassName}?.id" />
+          </Tabs.TabPane>
+        #end
+    </Tabs>
+  </ContentWrap>
+#end
+  </Page>
+</template>

+ 93 - 0
yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3_vben5_antd/general/views/modules/form_sub_erp.vue.vm

@@ -0,0 +1,93 @@
+#set ($subTable = $subTables.get($subIndex))##当前表
+#set ($subColumns = $subColumnsList.get($subIndex))##当前字段数组
+#set ($subJoinColumn = $subJoinColumns.get($subIndex))##当前 join 字段
+#set ($subSimpleClassName = $subSimpleClassNames.get($subIndex))
+<script lang="ts" setup>
+  import type { ${simpleClassName}Api } from '#/api/${table.moduleName}/${simpleClassName_strikeCase}';
+
+  import { useVbenModal } from '@vben/common-ui';
+  import { message } from 'ant-design-vue';
+
+  import { computed, ref } from 'vue';
+  import { $t } from '#/locales';
+  import { useVbenForm } from '#/adapter/form';
+  import { get${subSimpleClassName}, create${subSimpleClassName}, update${subSimpleClassName} } from '#/api/${table.moduleName}/${simpleClassName_strikeCase}';
+
+  import { use${subSimpleClassName}FormSchema } from '../data';
+
+  const emit = defineEmits(['success']);
+  const formData = ref<${simpleClassName}Api.${subSimpleClassName}>();
+  const getTitle = computed(() => {
+    return formData.value?.id
+        ? $t('ui.actionTitle.edit', ['${subTable.classComment}'])
+        : $t('ui.actionTitle.create', ['${subTable.classComment}']);
+  });
+
+  const [Form, formApi] = useVbenForm({
+    commonConfig: {
+      componentProps: {
+        class: 'w-full',
+      },
+      formItemClass: 'col-span-2',
+      labelWidth: 80,
+    },
+    layout: 'horizontal',
+    schema: use${subSimpleClassName}FormSchema(),
+    showDefaultActions: false
+  });
+
+  const [Modal, modalApi] = useVbenModal({
+    async onConfirm() {
+      const { valid } = await formApi.validate();
+      if (!valid) {
+        return;
+      }
+
+      modalApi.lock();
+      // 提交表单
+      const data = (await formApi.getValues()) as ${simpleClassName}Api.${subSimpleClassName};
+      data.${subJoinColumn.javaField} = formData.value?.${subJoinColumn.javaField};
+      try {
+        await (formData.value?.id ? update${subSimpleClassName}(data) : create${subSimpleClassName}(data));
+        // 关闭并提示
+        await modalApi.close();
+        emit('success');
+        message.success({
+          content: $t('ui.actionMessage.operationSuccess'),
+          key: 'action_process_msg',
+        });
+      } finally {
+        modalApi.unlock();
+      }
+    },
+    async onOpenChange(isOpen: boolean) {
+      if (!isOpen) {
+        formData.value = undefined;
+        return;
+      }
+
+      // 加载数据
+      let data = modalApi.getData<${simpleClassName}Api.${subSimpleClassName}>();
+      if (!data) {
+        return;
+      }
+      if (data.id) {
+        modalApi.lock();
+        try {
+          data = await get${subSimpleClassName}(data.id);
+        } finally {
+          modalApi.unlock();
+        }
+      }
+      // 设置到 values
+      formData.value = data;
+      await formApi.setValues(formData.value);
+    },
+  });
+</script>
+
+<template>
+  <Modal :title="getTitle">
+    <Form class="mx-4" />
+  </Modal>
+</template>

+ 2 - 0
yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3_vben5_antd/general/views/modules/form_sub_inner.vue.vm

@@ -0,0 +1,2 @@
+## 主表的 normal 和 inner 使用相同的 form 表单
+#parse("codegen/vue3_vben5_antd/schema/views/modules/form_sub_normal.vue.vm")

+ 199 - 0
yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3_vben5_antd/general/views/modules/form_sub_normal.vue.vm

@@ -0,0 +1,199 @@
+#set ($subTable = $subTables.get($subIndex))##当前表
+#set ($subColumns = $subColumnsList.get($subIndex))##当前字段数组
+#set ($subJoinColumn = $subJoinColumns.get($subIndex))##当前 join 字段
+#set ($subSimpleClassName = $subSimpleClassNames.get($subIndex))
+#set ($subClassNameVar = $subClassNameVars.get($subIndex))
+#set ($SubJoinColumnName = $subJoinColumn.javaField.substring(0,1).toUpperCase() + ${subJoinColumn.javaField.substring(1)})##首字母大写
+<script lang="ts" setup>
+  import type { ${simpleClassName}Api } from '#/api/${table.moduleName}/${simpleClassName_strikeCase}';
+
+  import { computed, ref, h, onMounted,watch,nextTick } from 'vue';
+  import { $t } from '#/locales';
+
+#if ($subTable.subJoinMany) ## 一对多
+import { Plus } from "@vben/icons";
+import { Button, Tabs, Checkbox, Input, Textarea, Select,RadioGroup,CheckboxGroup, DatePicker } from 'ant-design-vue';
+import type { OnActionClickParams } from '#/adapter/vxe-table';
+import { useVbenVxeGrid } from '#/adapter/vxe-table';
+import { use${subSimpleClassName}GridEditColumns } from '../data';
+import { get${subSimpleClassName}ListBy${SubJoinColumnName} } from '#/api/${table.moduleName}/${simpleClassName_strikeCase}';
+#else
+import { useVbenForm } from '#/adapter/form';
+import { use${subSimpleClassName}FormSchema } from '../data';
+import { get${subSimpleClassName}By${SubJoinColumnName} } from '#/api/${table.moduleName}/${simpleClassName_strikeCase}';
+#end
+
+const props = defineProps<{
+   ${subJoinColumn.javaField}?: number // ${subJoinColumn.columnComment}(主表的关联字段)
+}>()
+
+#if ($subTable.subJoinMany) ## 一对多
+/** 表格操作按钮的回调函数 */
+function onActionClick({
+ code,
+ row,
+}: OnActionClickParams<${simpleClassName}Api.${subSimpleClassName}>) {
+  switch (code) {
+    case 'delete': {
+      onDelete(row);
+      break;
+    }
+  }
+}
+
+const [Grid, gridApi] = useVbenVxeGrid({
+gridOptions: {
+  columns: use${subSimpleClassName}GridEditColumns(onActionClick),
+  border: true,
+  showOverflow: true,
+  autoResize: true,
+  keepSource: true,
+  rowConfig: {
+    keyField: 'id',
+  },
+  pagerConfig: {
+    enabled: false,
+  },
+  toolbarConfig: {
+    enabled: false,
+  },
+},
+});
+
+/** 添加${subTable.classComment} */
+const onAdd = async () => {
+  await gridApi.grid.insertAt({} as ${simpleClassName}Api.${subSimpleClassName}, -1);
+}
+
+/** 删除${subTable.classComment} */
+const onDelete =  async (row: ${simpleClassName}Api.${subSimpleClassName}) => {
+  await gridApi.grid.remove(row);
+}
+
+/** 提供获取表格数据的方法供父组件调用 */
+defineExpose({
+  getData: (): ${simpleClassName}Api.${subSimpleClassName}[] => {
+    const data = gridApi.grid.getData() as ${simpleClassName}Api.${subSimpleClassName}[];
+    const removeRecords = gridApi.grid.getRemoveRecords() as ${simpleClassName}Api.${subSimpleClassName}[];
+    const insertRecords = gridApi.grid.getInsertRecords() as ${simpleClassName}Api.${subSimpleClassName}[];
+    return data
+        .filter((row) => !removeRecords.some((removed) => removed.id === row.id))
+        .concat(insertRecords.map((row: any) => ({ ...row, id: undefined })));
+  },
+});
+
+/** 监听主表的关联字段的变化,加载对应的子表数据 */
+watch(
+    () => props.${subJoinColumn.javaField},
+    async (val) => {
+      if (!val) {
+        return;
+      }
+      await nextTick();
+      await gridApi.grid.loadData(await get${subSimpleClassName}ListBy${SubJoinColumnName}(props.${subJoinColumn.javaField}!));
+    },
+    { immediate: true },
+);
+#else
+const [Form, formApi] = useVbenForm({
+  commonConfig: {
+    componentProps: {
+      class: 'w-full',
+    },
+    formItemClass: 'col-span-2',
+    labelWidth: 80,
+  },
+  layout: 'horizontal',
+  schema: use${subSimpleClassName}FormSchema(),
+  showDefaultActions: false
+});
+
+/** 暴露出表单校验方法和表单值获取方法 */
+defineExpose({
+  validate: async () => {
+    const { valid } = await formApi.validate();
+    return valid;
+  },
+  getValues: formApi.getValues,
+});
+
+/** 监听主表的关联字段的变化,加载对应的子表数据 */
+watch(
+    () => props.${subJoinColumn.javaField},
+    async (val) => {
+      if (!val) {
+        return;
+      }
+      await nextTick();
+      await formApi.setValues(await get${subSimpleClassName}By${SubJoinColumnName}(props.${subJoinColumn.javaField}!));
+    },
+    { immediate: true },
+);
+#end
+</script>
+
+<template>
+#if ($subTable.subJoinMany) ## 一对多
+  <Grid class="mx-4">
+      #foreach($column in $subColumns)
+          #if ($column.createOperation || $column.updateOperation)
+              #set ($javaField = $column.javaField)
+              #if ( $column.id == $subJoinColumn.id) ## 特殊:忽略主子表的 join 字段,不用填写
+              #elseif ($column.htmlType == "input" && !$column.primaryKey)## 忽略主键,不用在表单里
+                <template #${javaField}="{ row }">
+                  <Input v-model:value="row.${javaField}" />
+                </template>
+              #elseif($column.htmlType == "imageUpload")## 图片上传
+                <template #${javaField}="{ row }">
+                  <UploadImg v-model:value="row.${javaField}" />
+                </template>
+              #elseif($column.htmlType == "fileUpload")## 文件上传
+                <template #${javaField}="{ row }">
+                  <UploadFile v-model:value="row.${javaField}" />
+                </template>
+              #elseif($column.htmlType == "editor")## 文本编辑器
+                <template #${javaField}="{ row }">
+                  <Textarea v-model:value="row.${javaField}" />
+                </template>
+              #elseif($column.htmlType == "select")## 下拉框
+                <template #${javaField}="{ row, column }">
+                  <Select v-model:value="row.${javaField}" class="w-full">
+                    <Select.Option v-for="option in column.params.options" :key="option.value" :value="option.value">
+                      {{ option.label }}
+                    </Select.Option>
+                  </Select>
+                </template>
+              #elseif($column.htmlType == "checkbox")## 多选框
+                <template #${javaField}="{ row, column }">
+                  <CheckboxGroup v-model:value="row.${javaField}" :options="column.params.options" />
+                </template>
+              #elseif($column.htmlType == "radio")## 单选框
+                <template #${javaField}="{ row, column }">
+                  <RadioGroup v-model:value="row.${javaField}" :options="column.params.options" />
+                </template>
+              #elseif($column.htmlType == "datetime")## 时间框
+                <template #${javaField}="{ row }">
+                  <DatePicker
+                      v-model:value="row.${javaField}"
+                      :showTime="true"
+                      format="YYYY-MM-DD HH:mm:ss"
+                      valueFormat='x'
+                  />
+                </template>
+              #elseif($column.htmlType == "textarea")## 文本框
+                <template #${javaField}="{ row }">
+                  <Textarea v-model:value="row.${javaField}" />
+                </template>
+              #end
+          #end
+      #end
+  </Grid>
+  <div class="flex justify-center -mt-4">
+    <Button :icon="h(Plus)" type="primary" ghost @click="onAdd" v-access:code="['${subTable.moduleName}:${simpleClassName_strikeCase}:create']">
+      {{ $t('ui.actionTitle.create', ['${subTable.classComment}']) }}
+    </Button>
+  </div>
+#else
+  <Form class="mx-4" />
+#end
+</template>

+ 184 - 0
yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3_vben5_antd/general/views/modules/list_sub_erp.vue.vm

@@ -0,0 +1,184 @@
+#set ($subTable = $subTables.get($subIndex))##当前表
+#set ($subColumns = $subColumnsList.get($subIndex))##当前字段数组
+#set ($subJoinColumn = $subJoinColumns.get($subIndex))##当前 join 字段
+#set ($subSimpleClassName = $subSimpleClassNames.get($subIndex))
+#set ($subJoinColumn = $subJoinColumns.get($subIndex))##当前 join 字段
+#set ($subSimpleClassName_strikeCase = $subSimpleClassName_strikeCases.get($subIndex))
+#set ($SubJoinColumnName = $subJoinColumn.javaField.substring(0,1).toUpperCase() + ${subJoinColumn.javaField.substring(1)})##首字母大写
+<script lang="ts" setup>
+  import type { OnActionClickParams, VxeTableGridOptions } from '#/adapter/vxe-table';
+  import type { ${simpleClassName}Api } from '#/api/${table.moduleName}/${simpleClassName_strikeCase}';
+
+#if ($table.templateType == 11) ## erp
+  import ${subSimpleClassName}Form from './${subSimpleClassName_strikeCase}-form.vue'
+#end
+  import { useVbenModal } from '@vben/common-ui';
+  import { Button, message } from 'ant-design-vue';
+  import { Plus } from '@vben/icons';
+  import { #if($table.templateType != 11)ref,#end h, nextTick,watch } from 'vue';
+  import { $t } from '#/locales';
+  import { useVbenVxeGrid } from '#/adapter/vxe-table';
+
+
+#if ($table.templateType == 11) ## erp
+  import { delete${subSimpleClassName}, get${subSimpleClassName}Page } from '#/api/${table.moduleName}/${simpleClassName_strikeCase}';
+  import { use${subSimpleClassName}GridFormSchema, use${subSimpleClassName}GridColumns } from '../data';
+  #else
+  #if ($subTable.subJoinMany) ## 一对多
+  import { get${subSimpleClassName}ListBy${SubJoinColumnName} } from '#/api/${table.moduleName}/${simpleClassName_strikeCase}';
+  #else
+  import { get${subSimpleClassName}By${SubJoinColumnName} } from '#/api/${table.moduleName}/${simpleClassName_strikeCase}';
+  #end
+  import { use${subSimpleClassName}GridColumns } from '../data';
+#end
+
+const props = defineProps<{
+      ${subJoinColumn.javaField}?: number // ${subJoinColumn.columnComment}(主表的关联字段)
+}>()
+
+#if ($table.templateType == 11) ## erp
+  const [FormModal, formModalApi] = useVbenModal({
+    connectedComponent: ${subSimpleClassName}Form,
+    destroyOnClose: true,
+  });
+
+/** 创建${subTable.classComment} */
+function onCreate() {
+  if (!props.${subJoinColumn.javaField}){
+    message.warning("请先选择一个${table.classComment}!")
+    return
+  }
+  formModalApi.setData({${subJoinColumn.javaField}: props.${subJoinColumn.javaField}}).open();
+}
+
+/** 编辑${subTable.classComment} */
+function onEdit(row: ${simpleClassName}Api.${subSimpleClassName}) {
+  formModalApi.setData(row).open();
+}
+
+/** 删除${subTable.classComment} */
+async function onDelete(row: ${simpleClassName}Api.${subSimpleClassName}) {
+  const hideLoading = message.loading({
+    content: $t('ui.actionMessage.deleting', [row.id]),
+    duration: 0,
+    key: 'action_process_msg',
+  });
+  try {
+    await delete${subSimpleClassName}(row.id as number);
+    message.success({
+      content: $t('ui.actionMessage.deleteSuccess', [row.id]),
+      key: 'action_process_msg',
+    });
+    onRefresh();
+  } catch {
+    hideLoading();
+  }
+}
+
+/** 表格操作按钮的回调函数 */
+function onActionClick({
+ code,
+ row,
+}: OnActionClickParams<${simpleClassName}Api.${subSimpleClassName}>) {
+  switch (code) {
+    case 'edit': {
+      onEdit(row);
+      break;
+    }
+    case 'delete': {
+      onDelete(row);
+      break;
+    }
+  }
+}
+
+#end
+  const [Grid, gridApi] = useVbenVxeGrid({
+#if ($table.templateType == 11)
+    formOptions: {
+      schema: use${subSimpleClassName}GridFormSchema(),
+    },
+#end
+    gridOptions: {
+#if ($table.templateType == 11)
+    columns: use${subSimpleClassName}GridColumns(onActionClick),
+      proxyConfig: {
+        ajax: {
+          query: async ({ page }, formValues) => {
+              if (!props.${subJoinColumn.javaField}){
+                  return []
+              }
+            return await get${subSimpleClassName}Page({
+              pageNo: page.currentPage,
+              pageSize: page.pageSize,
+              ${subJoinColumn.javaField}: props.${subJoinColumn.javaField},
+              ...formValues,
+            });
+          },
+        },
+      },
+    pagerConfig: {
+        enabled: true,
+    },
+    toolbarConfig: {
+        refresh: { code: 'query' },
+        search: true,
+    },
+#else
+    columns: use${subSimpleClassName}GridColumns(),
+    pagerConfig: {
+        enabled: false,
+    },
+    toolbarConfig: {
+        enabled: false,
+    },
+#end
+    height: '600px',
+    rowConfig: {
+        keyField: 'id',
+        isHover: true,
+    },
+    } as VxeTableGridOptions<${simpleClassName}Api.${subSimpleClassName}>,
+  });
+
+/** 刷新表格 */
+const onRefresh = async ()=> {
+#if ($table.templateType == 11) ## erp
+    await gridApi.query();
+#else
+    #if ($subTable.subJoinMany) ## 一对多
+    await gridApi.grid.loadData(await get${subSimpleClassName}ListBy${SubJoinColumnName}(props.${subJoinColumn.javaField}!));
+    #else
+    await gridApi.grid.loadData([await get${subSimpleClassName}By${SubJoinColumnName}(props.${subJoinColumn.javaField}!)]);
+    #end
+#end
+}
+
+  /** 监听主表的关联字段的变化,加载对应的子表数据 */
+  watch(
+      () => props.${subJoinColumn.javaField},
+      async (val) => {
+        if (!val) {
+          return;
+        }
+        await nextTick();
+        await onRefresh()
+      },
+      { immediate: true },
+  );
+</script>
+
+<template>
+    #if ($table.templateType == 11) ## erp
+      <FormModal @success="onRefresh" />
+      <Grid table-title="${subTable.classComment}列表">
+        <template #toolbar-tools>
+          <Button :icon="h(Plus)" type="primary" @click="onCreate" v-access:code="['${table.moduleName}:${simpleClassName_strikeCase}:create']">
+            {{ $t('ui.actionTitle.create', ['${subTable.classComment}']) }}
+          </Button>
+        </template>
+      </Grid>
+    #else
+      <Grid table-title="${subTable.classComment}列表" />
+    #end
+</template>

+ 4 - 0
yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3_vben5_antd/general/views/modules/list_sub_inner.vue.vm

@@ -0,0 +1,4 @@
+## 子表的 erp 和 inner 使用相似的 list 列表,差异主要两点:
+## 1)inner 使用 list 不分页,erp 使用 page 分页
+## 2)erp 支持单个子表的新增、修改、删除,inner 不支持
+#parse("codegen/vue3_vben5_antd/schema/views/modules/list_sub_erp.vue.vm")

+ 6 - 38
yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3_vben5_antd/schema/views/data.ts.vm

@@ -1,6 +1,5 @@
-import type { VxeTableGridOptions } from '@vben/plugins/vxe-table';
 import type { VbenFormSchema } from '#/adapter/form';
-import type { OnActionClickFn } from '#/adapter/vxe-table';
+import type { OnActionClickFn, VxeTableGridOptions } from '#/adapter/vxe-table';
 import type { ${simpleClassName}Api } from '#/api/${table.moduleName}/${simpleClassName_strikeCase}';
 
 import { z } from '#/adapter/form';
@@ -40,7 +39,6 @@ export function useFormSchema(): VbenFormSchema[] {
           });
           return handleTree(data);
         },
-        class: 'w-full',
         labelField: '${treeNameColumn.javaField}',
         valueField: 'id',
         childrenField: 'children',
@@ -75,18 +73,10 @@ export function useFormSchema(): VbenFormSchema[] {
       componentProps: {
         placeholder: '请输入${comment}',
       },
-  #elseif($column.htmlType == "imageUpload")## 图片上传 TODO @puhui999:目前分成了图片和文件上传,可以不用 fileType 之类哈,可以用下;
-      component: 'FileUpload',
-      componentProps: {
-        fileType: 'image',
-        maxCount: 1,
-      },
+  #elseif($column.htmlType == "imageUpload")## 图片上传
+      component: 'ImageUpload',
   #elseif($column.htmlType == "fileUpload")## 文件上传
       component: 'FileUpload',
-      componentProps: {
-        fileType: 'file',
-        maxCount: 1,
-      },
   #elseif($column.htmlType == "editor")## 文本编辑器
       component: 'RichTextarea',
   #elseif($column.htmlType == "select")## 下拉框
@@ -98,7 +88,6 @@ export function useFormSchema(): VbenFormSchema[] {
         options: [],
         #end
         placeholder: '请选择${comment}',
-        class: 'w-full',
       },
   #elseif($column.htmlType == "checkbox")## 多选框
       component: 'Checkbox',
@@ -136,7 +125,6 @@ export function useFormSchema(): VbenFormSchema[] {
       component: 'InputNumber',
       componentProps: {
         min: 0,
-        class: 'w-full',
         controlsPosition: 'right',
         placeholder: '请输入${comment}',
       },
@@ -320,17 +308,9 @@ export function use${subSimpleClassName}FormSchema(): VbenFormSchema[] {
                                     placeholder: '请输入${comment}',
                                 },
                             #elseif($column.htmlType == "imageUpload")## 图片上传
-                                component: 'FileUpload',
-                                componentProps: {
-                                    fileType: 'image',
-                                    maxCount: 1,
-                                },
+                                component: 'ImageUpload',
                             #elseif($column.htmlType == "fileUpload")## 文件上传
                                 component: 'FileUpload',
-                                componentProps: {
-                                    fileType: 'file',
-                                    maxCount: 1,
-                                },
                             #elseif($column.htmlType == "editor")## 文本编辑器
                                 component: 'RichTextarea',
                             #elseif($column.htmlType == "select")## 下拉框
@@ -342,7 +322,6 @@ export function use${subSimpleClassName}FormSchema(): VbenFormSchema[] {
                                         options: [],
                                     #end
                                     placeholder: '请选择${comment}',
-                                    class: 'w-full',
                                 },
                             #elseif($column.htmlType == "checkbox")## 多选框
                                 component: 'Checkbox',
@@ -380,7 +359,6 @@ export function use${subSimpleClassName}FormSchema(): VbenFormSchema[] {
                                 component: 'InputNumber',
                                 componentProps: {
                                     min: 0,
-                                    class: 'w-full',
                                     controlsPosition: 'right',
                                     placeholder: '请输入${comment}',
                                 },
@@ -603,17 +581,9 @@ export function use${subSimpleClassName}GridColumns(
                                         placeholder: '请输入${comment}',
                                     },
                                 #elseif($column.htmlType == "imageUpload")## 图片上传
-                                    component: 'FileUpload',
-                                    componentProps: {
-                                        fileType: 'image',
-                                        maxCount: 1,
-                                    },
+                                    component: 'ImageUpload',
                                 #elseif($column.htmlType == "fileUpload")## 文件上传
                                     component: 'FileUpload',
-                                    componentProps: {
-                                        fileType: 'file',
-                                        maxCount: 1,
-                                    },
                                 #elseif($column.htmlType == "editor")## 文本编辑器
                                     component: 'RichTextarea',
                                 #elseif($column.htmlType == "select")## 下拉框
@@ -625,7 +595,6 @@ export function use${subSimpleClassName}GridColumns(
                                             options: [],
                                         #end
                                         placeholder: '请选择${comment}',
-                                        class: 'w-full',
                                     },
                                 #elseif($column.htmlType == "checkbox")## 多选框
                                     component: 'Checkbox',
@@ -663,7 +632,6 @@ export function use${subSimpleClassName}GridColumns(
                                     component: 'InputNumber',
                                     componentProps: {
                                         min: 0,
-                                        class: 'w-full',
                                         controlsPosition: 'right',
                                         placeholder: '请输入${comment}',
                                     },
@@ -706,4 +674,4 @@ export function use${subSimpleClassName}GridColumns(
 
     #end
 #end
-#end
+#end

+ 9 - 2
yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3_vben5_antd/schema/views/form.vue.vm

@@ -55,6 +55,13 @@ const getTitle = computed(() => {
 #end
 
 const [Form, formApi] = useVbenForm({
+  commonConfig: {
+    componentProps: {
+      class: 'w-full',
+    },
+    formItemClass: 'col-span-2',
+    labelWidth: 80,
+  },
   layout: 'horizontal',
   schema: useFormSchema(),
   showDefaultActions: false
@@ -113,7 +120,7 @@ const [Modal, modalApi] = useVbenModal({
         key: 'action_process_msg',
       });
     } finally {
-      modalApi.lock(false);
+      modalApi.unlock();
     }
   },
   async onOpenChange(isOpen: boolean) {
@@ -132,7 +139,7 @@ const [Modal, modalApi] = useVbenModal({
       try {
         data = await get${simpleClassName}(data.id);
       } finally {
-        modalApi.lock(false);
+        modalApi.unlock();
       }
     }
     // 设置到 values

+ 9 - 2
yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3_vben5_antd/schema/views/modules/form_sub_erp.vue.vm

@@ -24,6 +24,13 @@
   });
 
   const [Form, formApi] = useVbenForm({
+    commonConfig: {
+      componentProps: {
+        class: 'w-full',
+      },
+      formItemClass: 'col-span-2',
+      labelWidth: 80,
+    },
     layout: 'horizontal',
     schema: use${subSimpleClassName}FormSchema(),
     showDefaultActions: false
@@ -50,7 +57,7 @@
           key: 'action_process_msg',
         });
       } finally {
-        modalApi.lock(false);
+        modalApi.unlock();
       }
     },
     async onOpenChange(isOpen: boolean) {
@@ -69,7 +76,7 @@
         try {
           data = await get${subSimpleClassName}(data.id);
         } finally {
-          modalApi.lock(false);
+          modalApi.unlock();
         }
       }
       // 设置到 values

+ 10 - 3
yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3_vben5_antd/schema/views/modules/form_sub_normal.vue.vm

@@ -96,9 +96,16 @@ watch(
 );
 #else
 const [Form, formApi] = useVbenForm({
-layout: 'horizontal',
-schema: use${subSimpleClassName}FormSchema(),
-showDefaultActions: false
+  commonConfig: {
+    componentProps: {
+      class: 'w-full',
+    },
+    formItemClass: 'col-span-2',
+    labelWidth: 80,
+  },
+  layout: 'horizontal',
+  schema: use${subSimpleClassName}FormSchema(),
+  showDefaultActions: false
 });
 
 /** 暴露出表单校验方法和表单值获取方法 */

+ 6 - 9
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/ota/vo/firmware/IotOtaFirmwareCreateReqVO.java

@@ -1,29 +1,26 @@
 package cn.iocoder.yudao.module.iot.controller.admin.ota.vo.firmware;
 
 import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotEmpty;
+import jakarta.validation.constraints.NotNull;
 import lombok.Data;
 
-import javax.validation.constraints.NotEmpty;
-import javax.validation.constraints.NotNull;
-
-import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED;
-
 @Schema(description = "管理后台 - IoT OTA 固件创建 Request VO")
 @Data
 public class IotOtaFirmwareCreateReqVO {
 
-    @Schema(description = "固件名称", requiredMode = REQUIRED, example = "智能开关固件")
+    @Schema(description = "固件名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "智能开关固件")
     @NotEmpty(message = "固件名称不能为空")
     private String name;
 
     @Schema(description = "固件描述", example = "某品牌型号固件,测试用")
     private String description;
 
-    @Schema(description = "版本号", requiredMode = REQUIRED, example = "1.0.0")
+    @Schema(description = "版本号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1.0.0")
     @NotEmpty(message = "版本号不能为空")
     private String version;
 
-    @Schema(description = "产品编号", requiredMode = REQUIRED, example = "1024")
+    @Schema(description = "产品编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
     @NotNull(message = "产品编号不能为空")
     private String productId;
 
@@ -31,7 +28,7 @@ public class IotOtaFirmwareCreateReqVO {
     // TODO @li:是不是必传哈
     private String signMethod;
 
-    @Schema(description = "固件文件 URL", requiredMode = REQUIRED, example = "https://www.iocoder.cn/yudao-firmware.zip")
+    @Schema(description = "固件文件 URL", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn/yudao-firmware.zip")
     @NotEmpty(message = "固件文件 URL 不能为空")
     private String fileUrl;
 

+ 8 - 10
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/ota/vo/firmware/IotOtaFirmwareRespVO.java

@@ -7,8 +7,6 @@ import com.fhs.core.trans.vo.VO;
 import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.Data;
 
-import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED;
-
 @Data
 @Schema(description = "管理后台 - IoT OTA 固件 Response VO")
 public class IotOtaFirmwareRespVO implements VO {
@@ -16,12 +14,12 @@ public class IotOtaFirmwareRespVO implements VO {
     /**
      * 固件编号
      */
-    @Schema(description = "固件编号", requiredMode = REQUIRED, example = "1024")
+    @Schema(description = "固件编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
     private Long id;
     /**
      * 固件名称
      */
-    @Schema(description = "固件名称", requiredMode = REQUIRED, example = "OTA固件")
+    @Schema(description = "固件名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "OTA固件")
     private String name;
     /**
      * 固件描述
@@ -31,7 +29,7 @@ public class IotOtaFirmwareRespVO implements VO {
     /**
      * 版本号
      */
-    @Schema(description = "版本号", requiredMode = REQUIRED, example = "1.0.0")
+    @Schema(description = "版本号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1.0.0")
     private String version;
 
     /**
@@ -39,7 +37,7 @@ public class IotOtaFirmwareRespVO implements VO {
      * <p>
      * 关联 {@link cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO#getId()}
      */
-    @Schema(description = "产品编号", requiredMode = REQUIRED, example = "1024")
+    @Schema(description = "产品编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
     @Trans(type = TransType.SIMPLE, target = IotProductDO.class, fields = {"name"}, refs = {"productName"})
     private String productId;
     /**
@@ -47,12 +45,12 @@ public class IotOtaFirmwareRespVO implements VO {
      * <p>
      * 冗余 {@link cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO#getProductKey()}
      */
-    @Schema(description = "产品标识", requiredMode = REQUIRED, example = "iot-product-key")
+    @Schema(description = "产品标识", requiredMode = Schema.RequiredMode.REQUIRED, example = "iot-product-key")
     private String productKey;
     /**
      * 产品名称
      */
-    @Schema(description = "产品名称", requiredMode = REQUIRED, example = "OTA产品")
+    @Schema(description = "产品名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "OTA产品")
     private String productName;
     /**
      * 签名方式
@@ -69,12 +67,12 @@ public class IotOtaFirmwareRespVO implements VO {
     /**
      * 固件文件大小
      */
-    @Schema(description = "固件文件大小", requiredMode = REQUIRED, example = "1024")
+    @Schema(description = "固件文件大小", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
     private Long fileSize;
     /**
      * 固件文件 URL
      */
-    @Schema(description = "固件文件 URL", requiredMode = REQUIRED, example = "https://www.iocoder.cn")
+    @Schema(description = "固件文件 URL", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn")
     private String fileUrl;
     /**
      * 自定义信息,建议使用 JSON 格式

+ 4 - 7
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/ota/vo/firmware/IotOtaFirmwareUpdateReqVO.java

@@ -1,23 +1,20 @@
 package cn.iocoder.yudao.module.iot.controller.admin.ota.vo.firmware;
 
 import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotEmpty;
+import jakarta.validation.constraints.NotNull;
 import lombok.Data;
 
-import javax.validation.constraints.NotEmpty;
-import javax.validation.constraints.NotNull;
-
-import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED;
-
 @Schema(description = "管理后台 - IoT OTA 固件更新 Request VO")
 @Data
 public class IotOtaFirmwareUpdateReqVO {
 
-    @Schema(description = "固件编号", requiredMode = REQUIRED, example = "1024")
+    @Schema(description = "固件编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
     @NotNull(message = "固件编号不能为空")
     private Long id;
 
     // TODO @li:name 是不是可以飞必传哈
-    @Schema(description = "固件名称", requiredMode = REQUIRED, example = "智能开关固件")
+    @Schema(description = "固件名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "智能开关固件")
     @NotEmpty(message = "固件名称不能为空")
     private String name;
 

+ 3 - 6
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/ota/vo/upgrade/record/IotOtaUpgradeRecordPageReqVO.java

@@ -2,12 +2,9 @@ package cn.iocoder.yudao.module.iot.controller.admin.ota.vo.upgrade.record;
 
 import cn.iocoder.yudao.framework.common.pojo.PageParam;
 import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotNull;
 import lombok.Data;
 
-import javax.validation.constraints.NotNull;
-
-import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED;
-
 @Data
 @Schema(description = "管理后台 - IoT OTA 升级记录分页 Request VO")
 public class IotOtaUpgradeRecordPageReqVO extends PageParam {
@@ -18,7 +15,7 @@ public class IotOtaUpgradeRecordPageReqVO extends PageParam {
      * <p>
      * 该字段用于标识升级任务的唯一编号,不能为空。
      */
-    @Schema(description = "升级任务编号", requiredMode = REQUIRED, example = "1024")
+    @Schema(description = "升级任务编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
     @NotNull(message = "升级任务编号不能为空")
     private Long taskId;
 
@@ -27,7 +24,7 @@ public class IotOtaUpgradeRecordPageReqVO extends PageParam {
      * <p>
      * 该字段用于标识设备的名称,通常用于区分不同的设备。
      */
-    @Schema(description = "设备标识", requiredMode = REQUIRED, example = "摄像头A1-1")
+    @Schema(description = "设备标识", requiredMode = Schema.RequiredMode.REQUIRED, example = "摄像头A1-1")
     private String deviceName;
 
 }

+ 14 - 16
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/ota/vo/upgrade/record/IotOtaUpgradeRecordRespVO.java

@@ -10,8 +10,6 @@ import lombok.Data;
 
 import java.time.LocalDateTime;
 
-import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED;
-
 @Data
 @Schema(description = "管理后台 - IoT OTA 升级记录 Response VO")
 public class IotOtaUpgradeRecordRespVO {
@@ -19,73 +17,73 @@ public class IotOtaUpgradeRecordRespVO {
     /**
      * 升级记录编号
      */
-    @Schema(description = "升级记录编号", requiredMode = REQUIRED, example = "1024")
+    @Schema(description = "升级记录编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
     private Long id;
     /**
      * 固件编号
      * <p>
      * 关联 {@link IotOtaFirmwareDO#getId()}
      */
-    @Schema(description = "固件编号", requiredMode = REQUIRED, example = "1024")
+    @Schema(description = "固件编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
     @Trans(type = TransType.SIMPLE, target = IotOtaFirmwareDO.class, fields = {"version"}, refs = {"firmwareVersion"})
     private Long firmwareId;
     /**
      * 固件版本
      */
-    @Schema(description = "固件版本", requiredMode = REQUIRED, example = "v1.0.0")
+    @Schema(description = "固件版本", requiredMode = Schema.RequiredMode.REQUIRED, example = "v1.0.0")
     private String firmwareVersion;
     /**
      * 任务编号
      * <p>
      * 关联 {@link IotOtaUpgradeTaskDO#getId()}
      */
-    @Schema(description = "任务编号", requiredMode = REQUIRED, example = "1024")
+    @Schema(description = "任务编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
     private Long taskId;
     /**
      * 产品标识
      * <p>
      * 关联 {@link cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO#getId()}
      */
-    @Schema(description = "产品标识", requiredMode = REQUIRED, example = "iot")
+    @Schema(description = "产品标识", requiredMode = Schema.RequiredMode.REQUIRED, example = "iot")
     private String productKey;
     /**
      * 设备名称
      * <p>
      * 关联 {@link cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO#getId()}
      */
-    @Schema(description = "设备名称", requiredMode = REQUIRED, example = "iot")
+    @Schema(description = "设备名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "iot")
     private String deviceName;
     /**
      * 设备编号
      * <p>
      * 关联 {@link cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO#getId()}
      */
-    @Schema(description = "设备编号", requiredMode = REQUIRED, example = "1024")
+    @Schema(description = "设备编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
     private String deviceId;
     /**
      * 来源的固件编号
      * <p>
      * 关联 {@link IotDeviceDO#getFirmwareId()}
      */
-    @Schema(description = "来源的固件编号", requiredMode = REQUIRED, example = "1024")
+    @Schema(description = "来源的固件编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
     @Trans(type = TransType.SIMPLE, target = IotOtaFirmwareDO.class, fields = {"version"}, refs = {"fromFirmwareVersion"})
     private Long fromFirmwareId;
     /**
      * 来源的固件版本
      */
-    @Schema(description = "来源的固件版本", requiredMode = REQUIRED, example = "v1.0.0")
+    @Schema(description = "来源的固件版本", requiredMode = Schema.RequiredMode.REQUIRED, example = "v1.0.0")
     private String fromFirmwareVersion;
     /**
      * 升级状态
      * <p>
      * 关联 {@link cn.iocoder.yudao.module.iot.enums.ota.IotOtaUpgradeRecordStatusEnum}
      */
-    @Schema(description = "升级状态", requiredMode = REQUIRED, allowableValues = {"0", "10", "20", "30", "40", "50"})
+    @Schema(description = "升级状态", requiredMode = Schema.RequiredMode.REQUIRED, allowableValues = {"0", "10", "20", "30", "40", "50"})
     private Integer status;
     /**
      * 升级进度,百分比
      */
-    @Schema(description = "升级进度,百分比", requiredMode = REQUIRED, example = "10")
+    @Schema(description = "升级进度,百分比", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
     private Integer progress;
     /**
      * 升级进度描述
@@ -93,17 +91,17 @@ public class IotOtaUpgradeRecordRespVO {
      * 注意,只记录设备最后一次的升级进度描述
      * 如果想看历史记录,可以查看 {@link cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceLogDO} 设备日志
      */
-    @Schema(description = "升级进度描述", requiredMode = REQUIRED, example = "10")
+    @Schema(description = "升级进度描述", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
     private String description;
     /**
      * 升级开始时间
      */
-    @Schema(description = "升级开始时间", requiredMode = REQUIRED, example = "2022-07-08 07:30:00")
+    @Schema(description = "升级开始时间", requiredMode = Schema.RequiredMode.REQUIRED, example = "2022-07-08 07:30:00")
     private LocalDateTime startTime;
     /**
      * 升级结束时间
      */
-    @Schema(description = "升级结束时间", requiredMode = REQUIRED, example = "2022-07-08 07:30:00")
+    @Schema(description = "升级结束时间", requiredMode = Schema.RequiredMode.REQUIRED, example = "2022-07-08 07:30:00")
     private LocalDateTime endTime;
 
 }

+ 2 - 5
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/ota/vo/upgrade/task/IotOtaUpgradeTaskPageReqVO.java

@@ -2,12 +2,9 @@ package cn.iocoder.yudao.module.iot.controller.admin.ota.vo.upgrade.task;
 
 import cn.iocoder.yudao.framework.common.pojo.PageParam;
 import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotNull;
 import lombok.Data;
 
-import javax.validation.constraints.NotNull;
-
-import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED;
-
 @Data
 @Schema(description = "管理后台 - IoT OTA 升级任务分页 Request VO")
 public class IotOtaUpgradeTaskPageReqVO extends PageParam {
@@ -22,7 +19,7 @@ public class IotOtaUpgradeTaskPageReqVO extends PageParam {
      * 固件编号字段,用于唯一标识固件,不能为空
      */
     @NotNull(message = "固件编号不能为空")
-    @Schema(description = "固件编号", requiredMode = REQUIRED, example = "1024")
+    @Schema(description = "固件编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
     private Long firmwareId;
 
 }

+ 8 - 10
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/ota/vo/upgrade/task/IotOtaUpgradeTaskRespVO.java

@@ -9,8 +9,6 @@ import lombok.Data;
 import java.time.LocalDateTime;
 import java.util.List;
 
-import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED;
-
 @Data
 @Schema(description = "管理后台 - IoT OTA 升级任务 Response VO")
 public class IotOtaUpgradeTaskRespVO implements VO {
@@ -18,12 +16,12 @@ public class IotOtaUpgradeTaskRespVO implements VO {
     /**
      * 任务编号
      */
-    @Schema(description = "任务编号", requiredMode = REQUIRED, example = "1024")
+    @Schema(description = "任务编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
     private Long id;
     /**
      * 任务名称
      */
-    @Schema(description = "任务名称", requiredMode = REQUIRED, example = "升级任务")
+    @Schema(description = "任务名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "升级任务")
     private String name;
     /**
      * 任务描述
@@ -35,31 +33,31 @@ public class IotOtaUpgradeTaskRespVO implements VO {
      * <p>
      * 关联 {@link IotOtaFirmwareDO#getId()}
      */
-    @Schema(description = "固件编号", requiredMode = REQUIRED, example = "1024")
+    @Schema(description = "固件编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
     private Long firmwareId;
     /**
      * 任务状态
      * <p>
      * 关联 {@link cn.iocoder.yudao.module.iot.enums.ota.IotOtaUpgradeTaskStatusEnum}
      */
-    @Schema(description = "任务状态", requiredMode = REQUIRED, allowableValues = {"10", "20", "21", "30"})
+    @Schema(description = "任务状态", requiredMode = Schema.RequiredMode.REQUIRED, allowableValues = {"10", "20", "21", "30"})
     private Integer status;
     /**
      * 任务状态名称
      */
-    @Schema(description = "任务状态名称", requiredMode = REQUIRED, example = "进行中")
+    @Schema(description = "任务状态名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "进行中")
     private String statusName;
     /**
      * 升级范围
      * <p>
      * 关联 {@link cn.iocoder.yudao.module.iot.enums.ota.IotOtaUpgradeTaskScopeEnum}
      */
-    @Schema(description = "升级范围", requiredMode = REQUIRED, allowableValues = {"1", "2"})
+    @Schema(description = "升级范围", requiredMode = Schema.RequiredMode.REQUIRED, allowableValues = {"1", "2"})
     private Integer scope;
     /**
      * 设备数量
      */
-    @Schema(description = "设备数量", requiredMode = REQUIRED, example = "1024")
+    @Schema(description = "设备数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
     private Long deviceCount;
     /**
      * 选中的设备编号数组
@@ -78,7 +76,7 @@ public class IotOtaUpgradeTaskRespVO implements VO {
     /**
      * 创建时间
      */
-    @Schema(description = "创建时间", requiredMode = REQUIRED, example = "2022-07-08 07:30:00")
+    @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED, example = "2022-07-08 07:30:00")
     private LocalDateTime createTime;
 
 }

+ 4 - 6
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/ota/vo/upgrade/task/IotOtaUpgradeTaskSaveReqVO.java

@@ -11,8 +11,6 @@ import javax.validation.constraints.NotEmpty;
 import javax.validation.constraints.NotNull;
 import java.util.List;
 
-import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED;
-
 @Data
 @Schema(description = "管理后台 - IoT OTA 升级任务创建/修改 Request VO")
 public class IotOtaUpgradeTaskSaveReqVO {
@@ -24,7 +22,7 @@ public class IotOtaUpgradeTaskSaveReqVO {
      * 任务名称
      */
     @NotEmpty(message = "任务名称不能为空")
-    @Schema(description = "任务名称", requiredMode = REQUIRED, example = "升级任务")
+    @Schema(description = "任务名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "升级任务")
     private String name;
 
     /**
@@ -39,7 +37,7 @@ public class IotOtaUpgradeTaskSaveReqVO {
      * 关联 {@link IotOtaFirmwareDO#getId()}
      */
     @NotNull(message = "固件编号不能为空")
-    @Schema(description = "固件编号", requiredMode = REQUIRED, example = "1024")
+    @Schema(description = "固件编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
     private Long firmwareId;
 
     /**
@@ -49,7 +47,7 @@ public class IotOtaUpgradeTaskSaveReqVO {
      */
     @NotNull(message = "升级范围不能为空")
     @InEnum(value = IotOtaUpgradeTaskScopeEnum.class)
-    @Schema(description = "升级范围", requiredMode = REQUIRED, example = "1")
+    @Schema(description = "升级范围", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
     private Integer scope;
 
     /**
@@ -57,7 +55,7 @@ public class IotOtaUpgradeTaskSaveReqVO {
      * <p>
      * 关联 {@link IotDeviceDO#getId()}
      */
-    @Schema(description = "选中的设备编号数组", requiredMode = REQUIRED, example = "[1,2,3,4]")
+    @Schema(description = "选中的设备编号数组", requiredMode = Schema.RequiredMode.REQUIRED, example = "[1,2,3,4]")
     private List<Long> deviceIds;
 
 }

+ 13 - 0
yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/api/sku/ProductSkuApi.java

@@ -5,6 +5,9 @@ import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuUpdateStockReqDTO;
 
 import java.util.Collection;
 import java.util.List;
+import java.util.Map;
+
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
 
 /**
  * 商品 SKU API 接口
@@ -30,6 +33,16 @@ public interface ProductSkuApi {
      */
     List<ProductSkuRespDTO> getSkuList(Collection<Long> ids);
 
+    /**
+     * 批量查询 SKU MAP
+     *
+     * @param ids SKU 编号列表
+     * @return SKU MAP
+     */
+    default Map<Long, ProductSkuRespDTO> getSkuMap(Collection<Long> ids) {
+        return convertMap(getSkuList(ids), ProductSkuRespDTO::getId);
+    }
+
     /**
      * 批量查询 SKU 数组
      *

+ 1 - 1
yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/api/spu/ProductSpuApi.java

@@ -30,7 +30,7 @@ public interface ProductSpuApi {
      * @param ids SPU 编号列表
      * @return SPU MAP
      */
-    default Map<Long, ProductSpuRespDTO> getSpusMap(Collection<Long> ids) {
+    default Map<Long, ProductSpuRespDTO> getSpuMap(Collection<Long> ids) {
         return convertMap(getSpuList(ids), ProductSpuRespDTO::getId);
     }
 

+ 3 - 5
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/history/vo/ProductBrowseHistoryRespVO.java

@@ -4,17 +4,15 @@ import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
 import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.Data;
 
-import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED;
-
 @Schema(description = "管理后台 - 商品浏览记录 Response VO")
 @Data
 @ExcelIgnoreUnannotated
 public class ProductBrowseHistoryRespVO {
 
-    @Schema(description = "编号", requiredMode = REQUIRED, example = "1")
+    @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
     private Long id;
 
-    @Schema(description = "商品 SPU 编号", requiredMode = REQUIRED, example = "29502")
+    @Schema(description = "商品 SPU 编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "29502")
     private Long spuId;
 
     // ========== 商品相关字段 ==========
@@ -34,4 +32,4 @@ public class ProductBrowseHistoryRespVO {
     @Schema(description = "库存", example = "100")
     private Integer stock;
 
-}
+}

+ 1 - 3
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/favorite/vo/AppFavoriteBatchReqVO.java

@@ -6,13 +6,11 @@ import lombok.Data;
 import javax.validation.constraints.NotEmpty;
 import java.util.List;
 
-import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED;
-
 @Schema(description = "用户 APP - 商品收藏的批量 Request VO") // 用于收藏、取消收藏、获取收藏
 @Data
 public class AppFavoriteBatchReqVO {
 
-    @Schema(description = "商品 SPU 编号数组", requiredMode = REQUIRED, example = "29502")
+    @Schema(description = "商品 SPU 编号数组", requiredMode = Schema.RequiredMode.REQUIRED, example = "29502")
     @NotEmpty(message = "商品 SPU 编号数组不能为空")
     private List<Long> spuIds;
 

+ 1 - 3
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/favorite/vo/AppFavoriteReqVO.java

@@ -5,13 +5,11 @@ import lombok.Data;
 
 import javax.validation.constraints.NotNull;
 
-import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED;
-
 @Schema(description = "用户 APP - 商品收藏的单个 Request VO") // 用于收藏、取消收藏、获取收藏
 @Data
 public class AppFavoriteReqVO {
 
-    @Schema(description = "商品 SPU 编号", requiredMode = REQUIRED, example = "29502")
+    @Schema(description = "商品 SPU 编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "29502")
     @NotNull(message = "商品 SPU 编号不能为空")
     private Long spuId;
 

+ 2 - 3
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/favorite/vo/AppFavoriteRespVO.java

@@ -2,16 +2,15 @@ package cn.iocoder.yudao.module.product.controller.app.favorite.vo;
 
 import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.Data;
-import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED;
 
 @Schema(description = "用户 App - 商品收藏 Response VO")
 @Data
 public class AppFavoriteRespVO {
 
-    @Schema(description = "编号", requiredMode = REQUIRED, example = "1")
+    @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
     private Long id;
 
-    @Schema(description = "商品 SPU 编号", requiredMode = REQUIRED, example = "29502")
+    @Schema(description = "商品 SPU 编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "29502")
     private Long spuId;
 
     // ========== 商品相关字段 ==========

+ 1 - 3
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/history/vo/AppProductBrowseHistoryDeleteReqVO.java

@@ -6,13 +6,11 @@ import lombok.Data;
 import javax.validation.constraints.NotEmpty;
 import java.util.List;
 
-import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED;
-
 @Schema(description = "用户 APP - 删除商品浏览记录的 Request VO")
 @Data
 public class AppProductBrowseHistoryDeleteReqVO {
 
-    @Schema(description = "商品 SPU 编号数组", requiredMode = REQUIRED, example = "29502")
+    @Schema(description = "商品 SPU 编号数组", requiredMode = Schema.RequiredMode.REQUIRED, example = "29502")
     @NotEmpty(message = "商品 SPU 编号数组不能为空")
     private List<Long> spuIds;
 

+ 7 - 9
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/history/vo/AppProductBrowseHistoryRespVO.java

@@ -3,33 +3,31 @@ package cn.iocoder.yudao.module.product.controller.app.history.vo;
 import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.Data;
 
-import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED;
-
 @Schema(description = "用户 App - 商品浏览记录 Response VO")
 @Data
 public class AppProductBrowseHistoryRespVO {
 
-    @Schema(description = "编号", requiredMode = REQUIRED, example = "1")
+    @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
     private Long id;
 
-    @Schema(description = "商品 SPU 编号", requiredMode = REQUIRED, example = "29502")
+    @Schema(description = "商品 SPU 编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "29502")
     private Long spuId;
 
     // ========== 商品相关字段 ==========
 
-    @Schema(description = "商品 SPU 名称", requiredMode = REQUIRED, example = "赵六")
+    @Schema(description = "商品 SPU 名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "赵六")
     private String spuName;
 
-    @Schema(description = "商品封面图", requiredMode = REQUIRED, example = "https://www.iocoder.cn/pic.png")
+    @Schema(description = "商品封面图", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn/pic.png")
     private String picUrl;
 
-    @Schema(description = "商品单价", requiredMode = REQUIRED, example = "50")
+    @Schema(description = "商品单价", requiredMode = Schema.RequiredMode.REQUIRED, example = "50")
     private Integer price;
 
-    @Schema(description = "商品销量", requiredMode = REQUIRED, example = "60")
+    @Schema(description = "商品销量", requiredMode = Schema.RequiredMode.REQUIRED, example = "60")
     private Integer salesCount;
 
-    @Schema(description = "库存", requiredMode = REQUIRED, example = "80")
+    @Schema(description = "库存", requiredMode = Schema.RequiredMode.REQUIRED, example = "80")
     private Integer stock;
 
 }

+ 1 - 1
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/point/PointActivityController.java

@@ -124,7 +124,7 @@ public class PointActivityController {
         List<PointProductDO> products = pointActivityService.getPointProductListByActivityIds(
                 convertSet(activityList, PointActivityDO::getId));
         Map<Long, List<PointProductDO>> productsMap = convertMultiMap(products, PointProductDO::getActivityId);
-        Map<Long, ProductSpuRespDTO> spuMap = productSpuApi.getSpusMap(
+        Map<Long, ProductSpuRespDTO> spuMap = productSpuApi.getSpuMap(
                 convertSet(activityList, PointActivityDO::getSpuId));
         List<PointActivityRespVO> result = BeanUtils.toBean(activityList, PointActivityRespVO.class);
         result.forEach(activity -> {

+ 1 - 1
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/point/AppPointActivityController.java

@@ -104,7 +104,7 @@ public class AppPointActivityController {
         List<PointProductDO> products = pointActivityService.getPointProductListByActivityIds(
                 convertSet(activityList, PointActivityDO::getId));
         Map<Long, List<PointProductDO>> productsMap = convertMultiMap(products, PointProductDO::getActivityId);
-        Map<Long, ProductSpuRespDTO> spuMap = productSpuApi.getSpusMap(
+        Map<Long, ProductSpuRespDTO> spuMap = productSpuApi.getSpuMap(
                 convertSet(activityList, PointActivityDO::getSpuId));
         List<AppPointActivityRespVO> result = BeanUtils.toBean(activityList, AppPointActivityRespVO.class);
         result.forEach(activity -> {

+ 9 - 8
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponServiceImpl.java

@@ -21,6 +21,7 @@ import cn.iocoder.yudao.module.promotion.enums.coupon.CouponTakeTypeEnum;
 import cn.iocoder.yudao.module.promotion.enums.coupon.CouponTemplateValidityTypeEnum;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Propagation;
 import org.springframework.transaction.annotation.Transactional;
 import org.springframework.validation.annotation.Validated;
 
@@ -180,7 +181,7 @@ public class CouponServiceImpl implements CouponService {
      * @param couponId 模版编号
      * @param userId   用户编号
      */
-    @Transactional(rollbackFor = Exception.class)
+    @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRES_NEW) // 每次调用开启一个新的事务,避免在一个大的事务里面
     public void invalidateCoupon(Long couponId, Long userId) {
         if (couponId == null || couponId <= 0) {
             return;
@@ -270,13 +271,17 @@ public class CouponServiceImpl implements CouponService {
         if (CollUtil.isEmpty(userIds)) {
             throw exception(COUPON_TEMPLATE_USER_ALREADY_TAKE);
         }
-
         // 校验模板
         if (couponTemplate == null) {
             throw exception(COUPON_TEMPLATE_NOT_EXISTS);
         }
-        // 校验剩余数量
-        if (ObjUtil.notEqual(couponTemplate.getTakeLimitCount(), CouponTemplateDO.TIME_LIMIT_COUNT_MAX) // 非不限制
+        // 校验领取方式
+        if (ObjUtil.notEqual(couponTemplate.getTakeType(), takeType.getType())) {
+            throw exception(COUPON_TEMPLATE_CANNOT_TAKE);
+        }
+        // 校验发放数量不能过小(仅在 CouponTakeTypeEnum.USER 用户领取时)
+        if (CouponTakeTypeEnum.isUser(couponTemplate.getTakeType())
+                && ObjUtil.notEqual(couponTemplate.getTakeLimitCount(), CouponTemplateDO.TIME_LIMIT_COUNT_MAX) // 非不限制
                 && couponTemplate.getTakeCount() + userIds.size() > couponTemplate.getTotalCount()) {
             throw exception(COUPON_TEMPLATE_NOT_ENOUGH);
         }
@@ -286,10 +291,6 @@ public class CouponServiceImpl implements CouponService {
                 throw exception(COUPON_TEMPLATE_EXPIRED);
             }
         }
-        // 校验领取方式
-        if (ObjectUtil.notEqual(couponTemplate.getTakeType(), takeType.getType())) {
-            throw exception(COUPON_TEMPLATE_CANNOT_TAKE);
-        }
     }
 
     /**

+ 8 - 6
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/aftersale/AppAfterSaleController.java

@@ -1,12 +1,13 @@
 package cn.iocoder.yudao.module.trade.controller.app.aftersale;
 
 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
-import cn.iocoder.yudao.framework.common.pojo.PageParam;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
 import cn.iocoder.yudao.module.trade.controller.app.aftersale.vo.AppAfterSaleCreateReqVO;
 import cn.iocoder.yudao.module.trade.controller.app.aftersale.vo.AppAfterSaleDeliveryReqVO;
+import cn.iocoder.yudao.module.trade.controller.app.aftersale.vo.AppAfterSalePageReqVO;
 import cn.iocoder.yudao.module.trade.controller.app.aftersale.vo.AppAfterSaleRespVO;
-import cn.iocoder.yudao.module.trade.convert.aftersale.AfterSaleConvert;
+import cn.iocoder.yudao.module.trade.dal.dataobject.aftersale.AfterSaleDO;
 import cn.iocoder.yudao.module.trade.service.aftersale.AfterSaleService;
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.Parameter;
@@ -32,16 +33,17 @@ public class AppAfterSaleController {
 
     @GetMapping(value = "/page")
     @Operation(summary = "获得售后分页")
-    public CommonResult<PageResult<AppAfterSaleRespVO>> getAfterSalePage(PageParam pageParam) {
-        return success(AfterSaleConvert.INSTANCE.convertPage02(
-                afterSaleService.getAfterSalePage(getLoginUserId(), pageParam)));
+    public CommonResult<PageResult<AppAfterSaleRespVO>> getAfterSalePage(AppAfterSalePageReqVO pageReqVO) {
+        PageResult<AfterSaleDO> pageResult = afterSaleService.getAfterSalePage(getLoginUserId(), pageReqVO);
+        return success(BeanUtils.toBean(pageResult, AppAfterSaleRespVO.class));
     }
 
     @GetMapping(value = "/get")
     @Operation(summary = "获得售后订单")
     @Parameter(name = "id", description = "售后编号", required = true, example = "1")
     public CommonResult<AppAfterSaleRespVO> getAfterSale(@RequestParam("id") Long id) {
-        return success(AfterSaleConvert.INSTANCE.convert(afterSaleService.getAfterSale(getLoginUserId(), id)));
+        AfterSaleDO afterSale = afterSaleService.getAfterSale(getLoginUserId(), id);
+        return success(BeanUtils.toBean(afterSale, AppAfterSaleRespVO.class));
     }
 
     @PostMapping(value = "/create")

+ 20 - 0
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/aftersale/vo/AppAfterSalePageReqVO.java

@@ -0,0 +1,20 @@
+package cn.iocoder.yudao.module.trade.controller.app.aftersale.vo;
+
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+import java.util.Set;
+
+@Schema(description = "用户 App - 交易售后分页 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class AppAfterSalePageReqVO extends PageParam {
+
+    @Schema(description = "售后状态", example = "10, 20")
+    private Set<Integer> statuses;
+
+}

+ 6 - 2
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/base/spu/AppProductSpuBaseRespVO.java

@@ -3,8 +3,6 @@ package cn.iocoder.yudao.module.trade.controller.app.base.spu;
 import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.Data;
 
-import java.util.List;
-
 /**
  * 商品 SPU 基础 Response VO
  *
@@ -25,4 +23,10 @@ public class AppProductSpuBaseRespVO {
     @Schema(description = "商品分类编号", example = "1")
     private Long categoryId;
 
+    @Schema(description = "商品库存", requiredMode = Schema.RequiredMode.REQUIRED, example = "10000")
+    private Integer stock;
+
+    @Schema(description = "商品状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    private Integer status;
+
 }

+ 14 - 0
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/delivery/vo/pickup/AppDeliveryPickUpStoreRespVO.java

@@ -1,8 +1,12 @@
 package cn.iocoder.yudao.module.trade.controller.app.delivery.vo.pickup;
 
+import com.fasterxml.jackson.annotation.JsonFormat;
 import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotNull;
 import lombok.Data;
 
+import java.time.LocalTime;
+
 @Schema(description = "用户 App - 自提门店 Response VO")
 @Data
 public class AppDeliveryPickUpStoreRespVO {
@@ -28,6 +32,16 @@ public class AppDeliveryPickUpStoreRespVO {
     @Schema(description = "门店详细地址", requiredMode = Schema.RequiredMode.REQUIRED, example = "复旦大学路 188 号")
     private String detailAddress;
 
+    @Schema(description = "营业开始时间", requiredMode = Schema.RequiredMode.REQUIRED)
+    @NotNull(message = "营业开始时间不能为空")
+    @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "HH:mm")
+    private LocalTime openingTime;
+
+    @Schema(description = "营业结束时间", requiredMode = Schema.RequiredMode.REQUIRED)
+    @NotNull(message = "营业结束时间不能为空")
+    @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "HH:mm")
+    private LocalTime closingTime;
+
     @Schema(description = "纬度", requiredMode = Schema.RequiredMode.REQUIRED, example = "5.88")
     private Double latitude;
 

+ 0 - 5
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/convert/aftersale/AfterSaleConvert.java

@@ -11,7 +11,6 @@ import cn.iocoder.yudao.module.trade.controller.admin.base.member.user.MemberUse
 import cn.iocoder.yudao.module.trade.controller.admin.base.product.property.ProductPropertyValueDetailRespVO;
 import cn.iocoder.yudao.module.trade.controller.admin.order.vo.TradeOrderBaseVO;
 import cn.iocoder.yudao.module.trade.controller.app.aftersale.vo.AppAfterSaleCreateReqVO;
-import cn.iocoder.yudao.module.trade.controller.app.aftersale.vo.AppAfterSaleRespVO;
 import cn.iocoder.yudao.module.trade.dal.dataobject.aftersale.AfterSaleDO;
 import cn.iocoder.yudao.module.trade.dal.dataobject.aftersale.AfterSaleLogDO;
 import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO;
@@ -63,10 +62,6 @@ public interface AfterSaleConvert {
 
     ProductPropertyValueDetailRespVO convert(ProductPropertyValueDetailRespDTO bean);
 
-    AppAfterSaleRespVO convert(AfterSaleDO bean);
-
-    PageResult<AppAfterSaleRespVO> convertPage02(PageResult<AfterSaleDO> page);
-
     default AfterSaleDetailRespVO convert(AfterSaleDO afterSale, TradeOrderDO order, TradeOrderItemDO orderItem,
                                           MemberUserRespDTO user, List<AfterSaleLogDO> logs) {
         AfterSaleDetailRespVO respVO = convert02(afterSale);

+ 3 - 5
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/convert/cart/TradeCartConvert.java

@@ -1,5 +1,6 @@
 package cn.iocoder.yudao.module.trade.convert.cart;
 
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
 import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO;
 import cn.iocoder.yudao.module.product.api.spu.dto.ProductSpuRespDTO;
 import cn.iocoder.yudao.module.product.enums.spu.ProductSpuStatusEnum;
@@ -33,21 +34,18 @@ public interface TradeCartConvert {
             cartVO.setId(cart.getId()).setCount(cart.getCount()).setSelected(cart.getSelected());
             ProductSpuRespDTO spu = spuMap.get(cart.getSpuId());
             ProductSkuRespDTO sku = skuMap.get(cart.getSkuId());
-            cartVO.setSpu(convert(spu)).setSku(convert(sku));
+            cartVO.setSpu(BeanUtils.toBean(spu, AppProductSpuBaseRespVO.class))
+                    .setSku(BeanUtils.toBean(sku, AppProductSkuBaseRespVO.class));
             // 如果 SPU 不存在,或者下架,或者库存不足,说明是无效的
             if (spu == null
                 || !ProductSpuStatusEnum.isEnable(spu.getStatus())
                 || spu.getStock() <= 0) {
-                cartVO.setSelected(false); // 强制设置成不可选中
                 invalidList.add(cartVO);
             } else {
-                // 虽然 SKU 可能也会不存在,但是可以通过购物车重新选择
                 validList.add(cartVO);
             }
         });
         return new AppCartListRespVO().setValidList(validList).setInvalidList(invalidList);
     }
-    AppProductSpuBaseRespVO convert(ProductSpuRespDTO spu);
-    AppProductSkuBaseRespVO convert(ProductSkuRespDTO sku);
 
 }

+ 5 - 4
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/aftersale/AfterSaleMapper.java

@@ -1,10 +1,10 @@
 package cn.iocoder.yudao.module.trade.dal.mysql.aftersale;
 
-import cn.iocoder.yudao.framework.common.pojo.PageParam;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
 import cn.iocoder.yudao.module.trade.controller.admin.aftersale.vo.AfterSalePageReqVO;
+import cn.iocoder.yudao.module.trade.controller.app.aftersale.vo.AppAfterSalePageReqVO;
 import cn.iocoder.yudao.module.trade.dal.dataobject.aftersale.AfterSaleDO;
 import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
 import org.apache.ibatis.annotations.Mapper;
@@ -27,9 +27,10 @@ public interface AfterSaleMapper extends BaseMapperX<AfterSaleDO> {
                 .orderByDesc(AfterSaleDO::getId));
     }
 
-    default PageResult<AfterSaleDO> selectPage(Long userId, PageParam pageParam) {
-        return selectPage(pageParam, new LambdaQueryWrapperX<AfterSaleDO>()
-                .eqIfPresent(AfterSaleDO::getUserId, userId)
+    default PageResult<AfterSaleDO> selectPage(Long userId, AppAfterSalePageReqVO pageReqVO) {
+        return selectPage(pageReqVO, new LambdaQueryWrapperX<AfterSaleDO>()
+                .eq(AfterSaleDO::getUserId, userId)
+                .inIfPresent(AfterSaleDO::getStatus, pageReqVO.getStatuses())
                 .orderByDesc(AfterSaleDO::getId));
     }
 

+ 3 - 3
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/aftersale/AfterSaleService.java

@@ -1,12 +1,12 @@
 package cn.iocoder.yudao.module.trade.service.aftersale;
 
-import cn.iocoder.yudao.framework.common.pojo.PageParam;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.module.trade.controller.admin.aftersale.vo.AfterSaleDisagreeReqVO;
 import cn.iocoder.yudao.module.trade.controller.admin.aftersale.vo.AfterSalePageReqVO;
 import cn.iocoder.yudao.module.trade.controller.admin.aftersale.vo.AfterSaleRefuseReqVO;
 import cn.iocoder.yudao.module.trade.controller.app.aftersale.vo.AppAfterSaleCreateReqVO;
 import cn.iocoder.yudao.module.trade.controller.app.aftersale.vo.AppAfterSaleDeliveryReqVO;
+import cn.iocoder.yudao.module.trade.controller.app.aftersale.vo.AppAfterSalePageReqVO;
 import cn.iocoder.yudao.module.trade.dal.dataobject.aftersale.AfterSaleDO;
 
 /**
@@ -28,10 +28,10 @@ public interface AfterSaleService {
      * 【会员】获得售后订单分页
      *
      * @param userId    用户编号
-     * @param pageParam 分页参数
+     * @param pageReqVO 分页参数
      * @return 售后订单分页
      */
-    PageResult<AfterSaleDO> getAfterSalePage(Long userId, PageParam pageParam);
+    PageResult<AfterSaleDO> getAfterSalePage(Long userId, AppAfterSalePageReqVO pageReqVO);
 
     /**
      * 【会员】获得售后单

+ 5 - 5
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/aftersale/AfterSaleServiceImpl.java

@@ -3,7 +3,6 @@ package cn.iocoder.yudao.module.trade.service.aftersale;
 import cn.hutool.core.map.MapUtil;
 import cn.hutool.core.util.ObjectUtil;
 import cn.hutool.core.util.StrUtil;
-import cn.iocoder.yudao.framework.common.pojo.PageParam;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
 import cn.iocoder.yudao.module.pay.api.refund.PayRefundApi;
@@ -16,6 +15,7 @@ import cn.iocoder.yudao.module.trade.controller.admin.aftersale.vo.AfterSalePage
 import cn.iocoder.yudao.module.trade.controller.admin.aftersale.vo.AfterSaleRefuseReqVO;
 import cn.iocoder.yudao.module.trade.controller.app.aftersale.vo.AppAfterSaleCreateReqVO;
 import cn.iocoder.yudao.module.trade.controller.app.aftersale.vo.AppAfterSaleDeliveryReqVO;
+import cn.iocoder.yudao.module.trade.controller.app.aftersale.vo.AppAfterSalePageReqVO;
 import cn.iocoder.yudao.module.trade.convert.aftersale.AfterSaleConvert;
 import cn.iocoder.yudao.module.trade.dal.dataobject.aftersale.AfterSaleDO;
 import cn.iocoder.yudao.module.trade.dal.dataobject.delivery.DeliveryExpressDO;
@@ -36,6 +36,7 @@ import cn.iocoder.yudao.module.trade.framework.order.config.TradeOrderProperties
 import cn.iocoder.yudao.module.trade.service.delivery.DeliveryExpressService;
 import cn.iocoder.yudao.module.trade.service.order.TradeOrderQueryService;
 import cn.iocoder.yudao.module.trade.service.order.TradeOrderUpdateService;
+import jakarta.annotation.Resource;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.context.annotation.Lazy;
 import org.springframework.stereotype.Service;
@@ -44,7 +45,6 @@ import org.springframework.transaction.support.TransactionSynchronization;
 import org.springframework.transaction.support.TransactionSynchronizationManager;
 import org.springframework.validation.annotation.Validated;
 
-import javax.annotation.Resource;
 import java.time.LocalDateTime;
 
 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
@@ -87,8 +87,8 @@ public class AfterSaleServiceImpl implements AfterSaleService {
     }
 
     @Override
-    public PageResult<AfterSaleDO> getAfterSalePage(Long userId, PageParam pageParam) {
-        return tradeAfterSaleMapper.selectPage(userId, pageParam);
+    public PageResult<AfterSaleDO> getAfterSalePage(Long userId, AppAfterSalePageReqVO pageReqVO) {
+        return tradeAfterSaleMapper.selectPage(userId, pageReqVO);
     }
 
     @Override
@@ -386,7 +386,7 @@ public class AfterSaleServiceImpl implements AfterSaleService {
             public void afterCommit() {
                 // 创建退款单
                 PayRefundCreateReqDTO createReqDTO = AfterSaleConvert.INSTANCE.convert(userIp, afterSale, tradeOrderProperties)
-                        .setReason(StrUtil.format("退款【{}】", afterSale.getSpuName()));;
+                        .setReason(StrUtil.format("退款【{}】", afterSale.getSpuName()));
                 Long payRefundId = payRefundApi.createRefund(createReqDTO);
                 // 更新售后单的退款单号
                 tradeAfterSaleMapper.updateById(new AfterSaleDO().setId(afterSale.getId()).setPayRefundId(payRefundId));

+ 1 - 1
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/BrokerageWithdrawServiceImpl.java

@@ -135,7 +135,7 @@ public class BrokerageWithdrawServiceImpl implements BrokerageWithdrawService {
     private Long createPayTransfer(BrokerageWithdrawDO withdraw) {
         // 1.1 获取微信 openid
         SocialUserRespDTO socialUser = socialUserApi.getSocialUserByUserId(
-                UserTypeEnum.MEMBER.getValue(), withdraw.getUserId(), SocialTypeEnum.WECHAT_MINI_APP.getType());
+                UserTypeEnum.MEMBER.getValue(), withdraw.getUserId(), SocialTypeEnum.WECHAT_MINI_PROGRAM.getType());
         // TODO @luchi:这里,需要校验非空。如果空的话,要有业务异常哈;
         // 1.2 构建请求
         PayTransferCreateReqDTO payTransferCreateReqDTO = new PayTransferCreateReqDTO()

+ 18 - 2
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceImpl.java

@@ -545,6 +545,14 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
         if (ObjectUtil.notEqual(order.getStatus(), TradeOrderStatusEnum.UNPAID.getStatus())) {
             throw exception(ORDER_CANCEL_FAIL_STATUS_NOT_UNPAID);
         }
+        // 1.3 校验是否支持延迟(不允许取消)
+        if (TradeOrderStatusEnum.isUnpaid(order.getStatus())) {
+            PayOrderRespDTO payOrder = payOrderApi.getOrder(order.getPayOrderId());
+            if (payOrder != null && PayOrderStatusEnum.isSuccess(payOrder.getStatus())) {
+                log.warn("[cancelOrderByMember][order({}) 支付单已支付(支付回调延迟),不支持取消]", order.getId());
+                throw exception(ORDER_CANCEL_FAIL_STATUS_NOT_UNPAID);
+            }
+        }
 
         // 2. 取消订单
         cancelOrder0(order, TradeOrderCancelTypeEnum.MEMBER_CANCEL);
@@ -581,6 +589,15 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
     @Transactional(rollbackFor = Exception.class)
     @TradeOrderLog(operateType = TradeOrderOperateTypeEnum.SYSTEM_CANCEL)
     public void cancelOrderBySystem(TradeOrderDO order) {
+        // 校验是否支持延迟(不允许取消)
+        if (TradeOrderStatusEnum.isUnpaid(order.getStatus())) {
+            PayOrderRespDTO payOrder = payOrderApi.getOrder(order.getPayOrderId());
+            if (payOrder != null && PayOrderStatusEnum.isSuccess(payOrder.getStatus())) {
+                log.warn("[cancelOrderBySystem][order({}) 支付单已支付(支付回调延迟),不支持取消]", order.getId());
+                return;
+            }
+        }
+
         cancelOrder0(order, TradeOrderCancelTypeEnum.PAY_TIMEOUT);
     }
 
@@ -895,12 +912,11 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
         if (order == null) {
             throw exception(ORDER_NOT_FOUND);
         }
-
         // 1.3 校验订单是否支付
         if (!order.getPayStatus()) {
             throw exception(ORDER_CANCEL_PAID_FAIL, "已支付");
         }
-        // 1.3 校验订单是否未退款
+        // 1.4 校验订单是否未退款
         if (ObjUtil.notEqual(TradeOrderRefundStatusEnum.NONE.getStatus(), order.getRefundStatus())) {
             throw exception(ORDER_CANCEL_PAID_FAIL, "未退款");
         }

+ 10 - 5
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeBrokerageOrderHandler.java

@@ -20,6 +20,7 @@ import org.springframework.stereotype.Component;
 
 import javax.annotation.Resource;
 import java.util.List;
+import java.util.Map;
 
 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
 
@@ -101,13 +102,17 @@ public class TradeBrokerageOrderHandler implements TradeOrderHandler {
     protected void addBrokerage(Long userId, List<TradeOrderItemDO> orderItems) {
         MemberUserRespDTO user = memberUserApi.getUser(userId);
         Assert.notNull(user);
-        ProductSpuRespDTO spu = productSpuApi.getSpu(orderItems.get(0).getSpuId());
-        Assert.notNull(spu);
-        ProductSkuRespDTO sku = productSkuApi.getSku(orderItems.get(0).getSkuId());
+        Map<Long, ProductSpuRespDTO> spusMap = productSpuApi.getSpuMap(convertList(orderItems, TradeOrderItemDO::getSpuId));
+        Map<Long, ProductSkuRespDTO> skusMap = productSkuApi.getSkuMap(convertList(orderItems, TradeOrderItemDO::getSkuId));
 
         // 每一个订单项,都会去生成分销记录
-        List<BrokerageAddReqBO> addList = convertList(orderItems,
-                item -> TradeOrderConvert.INSTANCE.convert(user, item, spu, sku));
+        List<BrokerageAddReqBO> addList = convertList(orderItems, item -> {
+            ProductSpuRespDTO spu = spusMap.get(item.getSpuId());
+            Assert.notNull(spu);
+            ProductSkuRespDTO sku = skusMap.get(item.getSkuId());
+            Assert.notNull(sku);
+            return TradeOrderConvert.INSTANCE.convert(user, item, spu, sku);
+        });
         brokerageRecordService.addBrokerage(userId, BrokerageRecordBizTypeEnum.ORDER, addList);
     }
 

+ 2 - 3
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/auth/MemberAuthServiceImpl.java

@@ -1,7 +1,6 @@
 package cn.iocoder.yudao.module.member.service.auth;
 
 import cn.hutool.core.lang.Assert;
-import cn.hutool.core.util.ObjectUtil;
 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
 import cn.iocoder.yudao.framework.common.enums.TerminalEnum;
 import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
@@ -27,11 +26,11 @@ import cn.iocoder.yudao.module.system.enums.logger.LoginResultEnum;
 import cn.iocoder.yudao.module.system.enums.oauth2.OAuth2ClientConstants;
 import cn.iocoder.yudao.module.system.enums.sms.SmsSceneEnum;
 import cn.iocoder.yudao.module.system.enums.social.SocialTypeEnum;
+import jakarta.annotation.Resource;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 
-import javax.annotation.Resource;
 import java.util.Objects;
 
 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
@@ -147,7 +146,7 @@ public class MemberAuthServiceImpl implements MemberAuthService {
 
         // 绑定社交用户
         String openid = socialUserApi.bindSocialUser(new SocialUserBindReqDTO(user.getId(), getUserType().getValue(),
-                SocialTypeEnum.WECHAT_MINI_APP.getType(), reqVO.getLoginCode(), reqVO.getState()));
+                SocialTypeEnum.WECHAT_MINI_PROGRAM.getType(), reqVO.getLoginCode(), reqVO.getState()));
 
         // 创建 Token 令牌,记录登录日志
         return createTokenAfterLoginSuccess(user, user.getMobile(), LoginLogTypeEnum.LOGIN_SOCIAL, openid);

+ 1 - 0
yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/ErrorCodeConstants.java

@@ -45,6 +45,7 @@ public interface ErrorCodeConstants {
     ErrorCode USER_COUNT_MAX = new ErrorCode(1_002_003_008, "创建用户失败,原因:超过租户最大租户配额({})!");
     ErrorCode USER_IMPORT_INIT_PASSWORD = new ErrorCode(1_002_003_009, "初始密码不能为空");
     ErrorCode USER_MOBILE_NOT_EXISTS = new ErrorCode(1_002_003_010, "该手机号尚未注册");
+    ErrorCode USER_REGISTER_DISABLED = new ErrorCode(1_002_003_011, "注册功能已关闭");
 
     // ========== 部门模块 1-002-004-000 ==========
     ErrorCode DEPT_NAME_DUPLICATE = new ErrorCode(1_002_004_000, "已经存在该名字的部门");

+ 1 - 1
yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/social/SocialTypeEnum.java

@@ -52,7 +52,7 @@ public enum SocialTypeEnum implements ArrayValuable<Integer> {
      *
      * @see <a href="https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/login.html">接入文档</a>
      */
-    WECHAT_MINI_APP(34, "WECHAT_MINI_APP"),
+    WECHAT_MINI_PROGRAM(34, "WECHAT_MINI_PROGRAM"),
     ;
 
     public static final Integer[] ARRAYS = Arrays.stream(values()).map(SocialTypeEnum::getType).toArray(Integer[]::new);

+ 6 - 2
yudao-module-system/yudao-module-system-biz/pom.xml

@@ -97,8 +97,12 @@
 
         <!-- 三方云服务相关 -->
         <dependency>
-            <groupId>com.xingyuv</groupId>
-            <artifactId>spring-boot-starter-justauth</artifactId> <!-- 社交登陆(例如说,个人微信、企业微信等等) -->
+            <groupId>me.zhyd.oauth</groupId>
+            <artifactId>JustAuth</artifactId> <!-- 社交登陆(例如说,个人微信、企业微信等等) -->
+        </dependency>
+        <dependency>
+            <groupId>com.xkcoding.justauth</groupId>
+            <artifactId>justauth-spring-boot-starter</artifactId>
         </dependency>
 
         <dependency>

+ 1 - 1
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/api/social/SocialClientApiImpl.java

@@ -84,7 +84,7 @@ public class SocialClientApiImpl implements SocialClientApi {
 
         // 2. 获得社交用户
         SocialUserRespDTO socialUser = socialUserService.getSocialUserByUserId(reqDTO.getUserType(), reqDTO.getUserId(),
-                SocialTypeEnum.WECHAT_MINI_APP.getType());
+                SocialTypeEnum.WECHAT_MINI_PROGRAM.getType());
         if (StrUtil.isBlankIfStr(socialUser.getOpenid())) {
             log.warn("[sendWxaSubscribeMessage][reqDTO({}) 发送订阅消息失败,原因:会员 openid 缺失]", reqDTO);
             return;

+ 1 - 1
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/social/SocialClientDO.java

@@ -7,8 +7,8 @@ import cn.iocoder.yudao.module.system.enums.social.SocialTypeEnum;
 import com.baomidou.mybatisplus.annotation.KeySequence;
 import com.baomidou.mybatisplus.annotation.TableId;
 import com.baomidou.mybatisplus.annotation.TableName;
-import com.xingyuv.jushauth.config.AuthConfig;
 import lombok.*;
+import me.zhyd.oauth.config.AuthConfig;
 
 /**
  * 社交客户端 DO

+ 39 - 0
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/justauth/config/YudaoJustAuthConfiguration.java

@@ -0,0 +1,39 @@
+package cn.iocoder.yudao.module.system.framework.justauth.config;
+
+import cn.iocoder.yudao.module.system.framework.justauth.core.AuthRequestFactory;
+import com.xkcoding.justauth.autoconfigure.JustAuthProperties;
+import com.xkcoding.justauth.support.cache.RedisStateCache;
+import me.zhyd.oauth.cache.AuthStateCache;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.data.redis.core.RedisTemplate;
+
+/**
+ * JustAuth 配置类 TODO 芋艿:等 justauth 1.4.1 版本发布!!!
+ *
+ * @author 芋道源码
+ */
+@Configuration(proxyBeanMethods = false)
+@EnableConfigurationProperties({JustAuthProperties.class})
+public class YudaoJustAuthConfiguration {
+
+    @Bean
+    @ConditionalOnProperty(
+            prefix = "justauth",
+            value = {"enabled"},
+            havingValue = "true",
+            matchIfMissing = true
+    )
+    public AuthRequestFactory authRequestFactory(JustAuthProperties properties, AuthStateCache authStateCache) {
+        return new AuthRequestFactory(properties, authStateCache);
+    }
+
+    @Bean
+    public AuthStateCache authStateCache(RedisTemplate<String, String> justAuthRedisCacheTemplate,
+                                         JustAuthProperties justAuthProperties) {
+        return new RedisStateCache(justAuthRedisCacheTemplate, justAuthProperties.getCache());
+    }
+
+}

+ 321 - 0
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/justauth/core/AuthRequestFactory.java

@@ -0,0 +1,321 @@
+/*
+ * Copyright (c) 2019-2029, xkcoding & Yangkai.Shen & 沈扬凯 (237497819@qq.com & xkcoding.com).
+ * <p>
+ * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * <p>
+ * http://www.gnu.org/licenses/lgpl.html
+ * <p>
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package cn.iocoder.yudao.module.system.framework.justauth.core;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.EnumUtil;
+import cn.hutool.core.util.ReflectUtil;
+import cn.hutool.core.util.StrUtil;
+import com.xkcoding.http.config.HttpConfig;
+import com.xkcoding.justauth.autoconfigure.ExtendProperties;
+import com.xkcoding.justauth.autoconfigure.JustAuthProperties;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import me.zhyd.oauth.cache.AuthStateCache;
+import me.zhyd.oauth.config.AuthConfig;
+import me.zhyd.oauth.config.AuthDefaultSource;
+import me.zhyd.oauth.config.AuthSource;
+import me.zhyd.oauth.enums.AuthResponseStatus;
+import me.zhyd.oauth.exception.AuthException;
+import me.zhyd.oauth.request.*;
+import org.springframework.util.CollectionUtils;
+
+import java.net.InetSocketAddress;
+import java.net.Proxy;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+// TODO @芋艿:等官方发布 1.4.1!!!
+/**
+ * <p>
+ * AuthRequest工厂类
+ * </p>
+ *
+ * @author yangkai.shen
+ * @date Created in 2019-07-22 14:21
+ */
+@Slf4j
+@RequiredArgsConstructor
+public class AuthRequestFactory {
+    private final JustAuthProperties properties;
+    private final AuthStateCache authStateCache;
+
+    /**
+     * 返回当前Oauth列表
+     *
+     * @return Oauth列表
+     */
+    @SuppressWarnings({"unchecked", "rawtypes"})
+    public List<String> oauthList() {
+        // 默认列表
+        List<String> defaultList = new ArrayList<>(properties.getType().keySet());
+        // 扩展列表
+        List<String> extendList = new ArrayList<>();
+        ExtendProperties extend = properties.getExtend();
+        if (null != extend) {
+            Class enumClass = extend.getEnumClass();
+            List<String> names = EnumUtil.getNames(enumClass);
+            // 扩展列表
+            extendList = extend.getConfig()
+                    .keySet()
+                    .stream()
+                    .filter(x -> names.contains(x.toUpperCase()))
+                    .map(String::toUpperCase)
+                    .collect(Collectors.toList());
+        }
+
+        // 合并
+        return (List<String>) CollUtil.addAll(defaultList, extendList);
+    }
+
+    /**
+     * 返回AuthRequest对象
+     *
+     * @param source {@link AuthSource}
+     * @return {@link AuthRequest}
+     */
+    public AuthRequest get(String source) {
+        if (StrUtil.isBlank(source)) {
+            throw new AuthException(AuthResponseStatus.NO_AUTH_SOURCE);
+        }
+
+        // 获取 JustAuth 中已存在的
+        AuthRequest authRequest = getDefaultRequest(source);
+
+        // 如果获取不到则尝试取自定义的
+        if (authRequest == null) {
+            authRequest = getExtendRequest(properties.getExtend().getEnumClass(), source);
+        }
+
+        if (authRequest == null) {
+            throw new AuthException(AuthResponseStatus.UNSUPPORTED);
+        }
+
+        return authRequest;
+    }
+
+    /**
+     * 获取自定义的 request
+     *
+     * @param clazz  枚举类 {@link AuthSource}
+     * @param source {@link AuthSource}
+     * @return {@link AuthRequest}
+     */
+    @SuppressWarnings({"unchecked", "rawtypes"})
+    private AuthRequest getExtendRequest(Class clazz, String source) {
+        String upperSource = source.toUpperCase();
+        try {
+            EnumUtil.fromString(clazz, upperSource);
+        } catch (IllegalArgumentException e) {
+            // 无自定义匹配
+            return null;
+        }
+
+        Map<String, ExtendProperties.ExtendRequestConfig> extendConfig = properties.getExtend().getConfig();
+
+        // key 转大写
+        Map<String, ExtendProperties.ExtendRequestConfig> upperConfig = new HashMap<>(6);
+        extendConfig.forEach((k, v) -> upperConfig.put(k.toUpperCase(), v));
+
+        ExtendProperties.ExtendRequestConfig extendRequestConfig = upperConfig.get(upperSource);
+        if (extendRequestConfig != null) {
+
+            // 配置 http config
+            configureHttpConfig(upperSource, extendRequestConfig, properties.getHttpConfig());
+
+            Class<? extends AuthRequest> requestClass = extendRequestConfig.getRequestClass();
+
+            if (requestClass != null) {
+                // 反射获取 Request 对象,所以必须实现 2 个参数的构造方法
+                return ReflectUtil.newInstance(requestClass, (AuthConfig) extendRequestConfig, authStateCache);
+            }
+        }
+
+        return null;
+    }
+
+
+    /**
+     * 获取默认的 Request
+     *
+     * @param source {@link AuthSource}
+     * @return {@link AuthRequest}
+     */
+    private AuthRequest getDefaultRequest(String source) {
+        AuthDefaultSource authDefaultSource;
+
+        try {
+            authDefaultSource = EnumUtil.fromString(AuthDefaultSource.class, source.toUpperCase());
+        } catch (IllegalArgumentException e) {
+            // 无自定义匹配
+            return null;
+        }
+
+        AuthConfig config = properties.getType().get(authDefaultSource.name());
+        // 找不到对应关系,直接返回空
+        if (config == null) {
+            return null;
+        }
+
+        // 配置 http config
+        configureHttpConfig(authDefaultSource.name(), config, properties.getHttpConfig());
+
+        switch (authDefaultSource) {
+            case GITHUB:
+                return new AuthGithubRequest(config, authStateCache);
+            case WEIBO:
+                return new AuthWeiboRequest(config, authStateCache);
+            case GITEE:
+                return new AuthGiteeRequest(config, authStateCache);
+            case DINGTALK:
+                return new AuthDingTalkRequest(config, authStateCache);
+            case DINGTALK_V2:
+                return new AuthDingTalkV2Request(config, authStateCache);
+            case DINGTALK_ACCOUNT:
+                return new AuthDingTalkAccountRequest(config, authStateCache);
+            case BAIDU:
+                return new AuthBaiduRequest(config, authStateCache);
+            case CSDN:
+                return new AuthCsdnRequest(config, authStateCache);
+            case CODING:
+                return new AuthCodingRequest(config, authStateCache);
+            case OSCHINA:
+                return new AuthOschinaRequest(config, authStateCache);
+            case ALIPAY:
+                return new AuthAlipayRequest(config, authStateCache);
+            case QQ:
+                return new AuthQqRequest(config, authStateCache);
+            case WECHAT_OPEN:
+                return new AuthWeChatOpenRequest(config, authStateCache);
+            case WECHAT_MP:
+                return new AuthWeChatMpRequest(config, authStateCache);
+            case TAOBAO:
+                return new AuthTaobaoRequest(config, authStateCache);
+            case GOOGLE:
+                return new AuthGoogleRequest(config, authStateCache);
+            case FACEBOOK:
+                return new AuthFacebookRequest(config, authStateCache);
+            case DOUYIN:
+                return new AuthDouyinRequest(config, authStateCache);
+            case LINKEDIN:
+                return new AuthLinkedinRequest(config, authStateCache);
+            case MICROSOFT:
+                return new AuthMicrosoftRequest(config, authStateCache);
+            case MICROSOFT_CN:
+                return new AuthMicrosoftCnRequest(config, authStateCache);
+
+            case MI:
+                return new AuthMiRequest(config, authStateCache);
+            case TOUTIAO:
+                return new AuthToutiaoRequest(config, authStateCache);
+            case TEAMBITION:
+                return new AuthTeambitionRequest(config, authStateCache);
+            case RENREN:
+                return new AuthRenrenRequest(config, authStateCache);
+            case PINTEREST:
+                return new AuthPinterestRequest(config, authStateCache);
+            case STACK_OVERFLOW:
+                return new AuthStackOverflowRequest(config, authStateCache);
+            case HUAWEI:
+                return new AuthHuaweiRequest(config, authStateCache);
+            case HUAWEI_V3:
+                return new AuthHuaweiV3Request(config, authStateCache);
+            case WECHAT_ENTERPRISE:
+                return new AuthWeChatEnterpriseQrcodeRequest(config, authStateCache);
+            case WECHAT_ENTERPRISE_V2:
+                return new AuthWeChatEnterpriseQrcodeV2Request(config, authStateCache);
+            case WECHAT_ENTERPRISE_QRCODE_THIRD:
+                return new AuthWeChatEnterpriseThirdQrcodeRequest(config, authStateCache);
+            case WECHAT_ENTERPRISE_WEB:
+                return new AuthWeChatEnterpriseWebRequest(config, authStateCache);
+            case KUJIALE:
+                return new AuthKujialeRequest(config, authStateCache);
+            case GITLAB:
+                return new AuthGitlabRequest(config, authStateCache);
+            case MEITUAN:
+                return new AuthMeituanRequest(config, authStateCache);
+            case ELEME:
+                return new AuthElemeRequest(config, authStateCache);
+            case TWITTER:
+                return new AuthTwitterRequest(config, authStateCache);
+            case FEISHU:
+                return new AuthFeishuRequest(config, authStateCache);
+            case JD:
+                return new AuthJdRequest(config, authStateCache);
+            case ALIYUN:
+                return new AuthAliyunRequest(config, authStateCache);
+            case XMLY:
+                return new AuthXmlyRequest(config, authStateCache);
+            case AMAZON:
+                return new AuthAmazonRequest(config, authStateCache);
+            case SLACK:
+                return new AuthSlackRequest(config, authStateCache);
+            case LINE:
+                return new AuthLineRequest(config, authStateCache);
+            case OKTA:
+                return new AuthOktaRequest(config, authStateCache);
+            case PROGINN:
+                return new AuthProginnRequest(config,authStateCache);
+            case AFDIAN:
+                return new AuthAfDianRequest(config,authStateCache);
+            case APPLE:
+                return new AuthAppleRequest(config,authStateCache);
+            case FIGMA:
+                return new AuthFigmaRequest(config,authStateCache);
+            case WECHAT_MINI_PROGRAM:
+                config.setIgnoreCheckRedirectUri(true);
+                config.setIgnoreCheckState(true);
+                return new AuthWechatMiniProgramRequest(config, authStateCache);
+            case QQ_MINI_PROGRAM:
+                config.setIgnoreCheckRedirectUri(true);
+                config.setIgnoreCheckState(true);
+                return new AuthQQMiniProgramRequest(config, authStateCache);
+            default:
+                return null;
+        }
+    }
+
+    /**
+     * 配置 http 相关的配置
+     *
+     * @param authSource {@link AuthSource}
+     * @param authConfig {@link AuthConfig}
+     */
+    private void configureHttpConfig(String authSource, AuthConfig authConfig, JustAuthProperties.JustAuthHttpConfig httpConfig) {
+        if (null == httpConfig) {
+            return;
+        }
+        Map<String, JustAuthProperties.JustAuthProxyConfig> proxyConfigMap = httpConfig.getProxy();
+        if (CollectionUtils.isEmpty(proxyConfigMap)) {
+            return;
+        }
+        JustAuthProperties.JustAuthProxyConfig proxyConfig = proxyConfigMap.get(authSource);
+
+        if (null == proxyConfig) {
+            return;
+        }
+
+        authConfig.setHttpConfig(HttpConfig.builder()
+                .timeout(httpConfig.getTimeout())
+                .proxy(new Proxy(Proxy.Type.valueOf(proxyConfig.getType()), new InetSocketAddress(proxyConfig.getHostname(), proxyConfig.getPort())))
+                .build());
+    }
+}

+ 6 - 0
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/justauth/package-info.java

@@ -0,0 +1,6 @@
+/**
+ * justauth 三方登录的拓展
+ *
+ * @author 芋道源码
+ */
+package cn.iocoder.yudao.module.system.framework.justauth;

+ 2 - 3
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/social/SocialClientService.java

@@ -8,14 +8,13 @@ import cn.iocoder.yudao.module.system.controller.admin.socail.vo.client.SocialCl
 import cn.iocoder.yudao.module.system.controller.admin.socail.vo.client.SocialClientSaveReqVO;
 import cn.iocoder.yudao.module.system.dal.dataobject.social.SocialClientDO;
 import cn.iocoder.yudao.module.system.enums.social.SocialTypeEnum;
-import com.xingyuv.jushauth.model.AuthUser;
+import jakarta.validation.Valid;
 import me.chanjar.weixin.common.bean.WxJsapiSignature;
 import me.chanjar.weixin.common.bean.subscribemsg.TemplateInfo;
+import me.zhyd.oauth.model.AuthUser;
 
 import java.util.List;
 
-import javax.validation.Valid;
-
 /**
  * 社交应用 Service 接口
  *

+ 9 - 9
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/social/SocialClientServiceImpl.java

@@ -26,18 +26,13 @@ import cn.iocoder.yudao.module.system.dal.dataobject.social.SocialClientDO;
 import cn.iocoder.yudao.module.system.dal.mysql.social.SocialClientMapper;
 import cn.iocoder.yudao.module.system.dal.redis.RedisKeyConstants;
 import cn.iocoder.yudao.module.system.enums.social.SocialTypeEnum;
+import cn.iocoder.yudao.module.system.framework.justauth.core.AuthRequestFactory;
 import com.binarywang.spring.starter.wxjava.miniapp.properties.WxMaProperties;
 import com.binarywang.spring.starter.wxjava.mp.properties.WxMpProperties;
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.cache.CacheLoader;
 import com.google.common.cache.LoadingCache;
-import com.xingyuv.jushauth.config.AuthConfig;
-import com.xingyuv.jushauth.model.AuthCallback;
-import com.xingyuv.jushauth.model.AuthResponse;
-import com.xingyuv.jushauth.model.AuthUser;
-import com.xingyuv.jushauth.request.AuthRequest;
-import com.xingyuv.jushauth.utils.AuthStateUtils;
-import com.xingyuv.justauth.AuthRequestFactory;
+import jakarta.annotation.Resource;
 import lombok.SneakyThrows;
 import lombok.extern.slf4j.Slf4j;
 import me.chanjar.weixin.common.bean.WxJsapiSignature;
@@ -47,12 +42,17 @@ import me.chanjar.weixin.common.redis.RedisTemplateWxRedisOps;
 import me.chanjar.weixin.mp.api.WxMpService;
 import me.chanjar.weixin.mp.api.impl.WxMpServiceImpl;
 import me.chanjar.weixin.mp.config.impl.WxMpRedisConfigImpl;
+import me.zhyd.oauth.config.AuthConfig;
+import me.zhyd.oauth.model.AuthCallback;
+import me.zhyd.oauth.model.AuthResponse;
+import me.zhyd.oauth.model.AuthUser;
+import me.zhyd.oauth.request.AuthRequest;
+import me.zhyd.oauth.utils.AuthStateUtils;
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.cache.annotation.Cacheable;
 import org.springframework.data.redis.core.StringRedisTemplate;
 import org.springframework.stereotype.Service;
 
-import javax.annotation.Resource;
 import java.time.Duration;
 import java.util.List;
 import java.util.Map;
@@ -337,7 +337,7 @@ public class SocialClientServiceImpl implements SocialClientService {
     WxMaService getWxMaService(Integer userType) {
         // 第一步,查询 DB 的配置项,获得对应的 WxMaService 对象
         SocialClientDO client = socialClientMapper.selectBySocialTypeAndUserType(
-                SocialTypeEnum.WECHAT_MINI_APP.getType(), userType);
+                SocialTypeEnum.WECHAT_MINI_PROGRAM.getType(), userType);
         if (client != null && Objects.equals(client.getStatus(), CommonStatusEnum.ENABLE.getStatus())) {
             return wxMaServiceCache.getUnchecked(client.getClientId() + ":" + client.getClientSecret());
         }

+ 3 - 4
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/social/SocialUserServiceImpl.java

@@ -6,21 +6,20 @@ import cn.iocoder.yudao.framework.common.exception.ServiceException;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.module.system.api.social.dto.SocialUserBindReqDTO;
 import cn.iocoder.yudao.module.system.api.social.dto.SocialUserRespDTO;
-import cn.iocoder.yudao.module.system.api.social.dto.SocialWxQrcodeReqDTO;
 import cn.iocoder.yudao.module.system.controller.admin.socail.vo.user.SocialUserPageReqVO;
 import cn.iocoder.yudao.module.system.dal.dataobject.social.SocialUserBindDO;
 import cn.iocoder.yudao.module.system.dal.dataobject.social.SocialUserDO;
 import cn.iocoder.yudao.module.system.dal.mysql.social.SocialUserBindMapper;
 import cn.iocoder.yudao.module.system.dal.mysql.social.SocialUserMapper;
 import cn.iocoder.yudao.module.system.enums.social.SocialTypeEnum;
-import com.xingyuv.jushauth.model.AuthUser;
+import jakarta.annotation.Resource;
+import jakarta.validation.constraints.NotNull;
 import lombok.extern.slf4j.Slf4j;
+import me.zhyd.oauth.model.AuthUser;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 import org.springframework.validation.annotation.Validated;
 
-import javax.annotation.Resource;
-import javax.validation.constraints.NotNull;
 import java.util.Collections;
 import java.util.List;
 

+ 2 - 0
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/tenant/TenantServiceImpl.java

@@ -9,6 +9,7 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
 import cn.iocoder.yudao.framework.common.util.date.DateUtils;
 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.framework.datapermission.core.annotation.DataPermission;
 import cn.iocoder.yudao.framework.tenant.config.TenantProperties;
 import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
 import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils;
@@ -96,6 +97,7 @@ public class TenantServiceImpl implements TenantService {
 
     @Override
     @DSTransactional // 多数据源,使用 @DSTransactional 保证本地事务,以及数据源的切换
+    @DataPermission(enable = false) // 参见 https://gitee.com/zhijiantianya/ruoyi-vue-pro/pulls/1154 说明
     public Long createTenant(TenantSaveReqVO createReqVO) {
         // 校验租户名称是否重复
         validTenantNameDuplicate(createReqVO.getName(), null);

+ 9 - 2
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/user/AdminUserServiceImpl.java

@@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.system.service.user;
 import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.collection.CollectionUtil;
 import cn.hutool.core.io.IoUtil;
+import cn.hutool.core.util.ObjUtil;
 import cn.hutool.core.util.StrUtil;
 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
 import cn.iocoder.yudao.framework.common.exception.ServiceException;
@@ -61,6 +62,8 @@ public class AdminUserServiceImpl implements AdminUserService {
 
     static final String USER_INIT_PASSWORD_KEY = "system.user.init-password";
 
+    static final String USER_REGISTER_ENABLED_KEY = "system.user.register-enabled";
+
     @Resource
     private AdminUserMapper userMapper;
 
@@ -117,14 +120,18 @@ public class AdminUserServiceImpl implements AdminUserService {
 
     @Override
     public Long registerUser(AuthRegisterReqVO registerReqVO) {
-        // 1.1 校验账户配合
+        // 1.1 校验是否开启注册
+        if (ObjUtil.notEqual(configApi.getConfigValueByKey(USER_REGISTER_ENABLED_KEY), "true")) {
+            throw exception(USER_REGISTER_DISABLED);
+        }
+        // 1.2 校验账户配合
         tenantService.handleTenantInfo(tenant -> {
             long count = userMapper.selectCount();
             if (count >= tenant.getAccountCount()) {
                 throw exception(USER_COUNT_MAX, tenant.getAccountCount());
             }
         });
-        // 1.2 校验正确性
+        // 1.3 校验正确性
         validateUserForCreateOrUpdate(null, registerReqVO.getUsername(), null, null, null, null);
 
         // 2. 插入用户

+ 13 - 14
yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/social/SocialClientServiceImplTest.java

@@ -13,26 +13,25 @@ import cn.iocoder.yudao.module.system.controller.admin.socail.vo.client.SocialCl
 import cn.iocoder.yudao.module.system.dal.dataobject.social.SocialClientDO;
 import cn.iocoder.yudao.module.system.dal.mysql.social.SocialClientMapper;
 import cn.iocoder.yudao.module.system.enums.social.SocialTypeEnum;
+import cn.iocoder.yudao.module.system.framework.justauth.core.AuthRequestFactory;
 import com.binarywang.spring.starter.wxjava.miniapp.properties.WxMaProperties;
 import com.binarywang.spring.starter.wxjava.mp.properties.WxMpProperties;
-import com.xingyuv.jushauth.config.AuthConfig;
-import com.xingyuv.jushauth.model.AuthResponse;
-import com.xingyuv.jushauth.model.AuthUser;
-import com.xingyuv.jushauth.request.AuthDefaultRequest;
-import com.xingyuv.jushauth.request.AuthRequest;
-import com.xingyuv.jushauth.utils.AuthStateUtils;
-import com.xingyuv.justauth.AuthRequestFactory;
+import jakarta.annotation.Resource;
 import me.chanjar.weixin.common.bean.WxJsapiSignature;
 import me.chanjar.weixin.common.error.WxErrorException;
 import me.chanjar.weixin.mp.api.WxMpService;
+import me.zhyd.oauth.config.AuthConfig;
+import me.zhyd.oauth.model.AuthResponse;
+import me.zhyd.oauth.model.AuthUser;
+import me.zhyd.oauth.request.AuthDefaultRequest;
+import me.zhyd.oauth.request.AuthRequest;
+import me.zhyd.oauth.utils.AuthStateUtils;
 import org.junit.jupiter.api.Test;
 import org.mockito.MockedStatic;
 import org.springframework.boot.test.mock.mockito.MockBean;
 import org.springframework.context.annotation.Import;
 import org.springframework.data.redis.core.StringRedisTemplate;
 
-import javax.annotation.Resource;
-
 import static cn.hutool.core.util.RandomUtil.randomEle;
 import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId;
 import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals;
@@ -103,7 +102,7 @@ public class SocialClientServiceImplTest extends BaseDbUnitTest {
         when(authRequestFactory.get(eq("WECHAT_MP"))).thenReturn(authRequest);
         // mock 方法(AuthResponse)
         AuthUser authUser = randomPojo(AuthUser.class);
-        AuthResponse<?> authResponse = new AuthResponse<>(2000, null, authUser);
+        AuthResponse<AuthUser> authResponse = new AuthResponse<>(2000, null, authUser);
         when(authRequest.login(argThat(authCallback -> {
             assertEquals(code, authCallback.getCode());
             assertEquals(state, authCallback.getState());
@@ -127,7 +126,7 @@ public class SocialClientServiceImplTest extends BaseDbUnitTest {
         AuthRequest authRequest = mock(AuthRequest.class);
         when(authRequestFactory.get(eq("WECHAT_MP"))).thenReturn(authRequest);
         // mock 方法(AuthResponse)
-        AuthResponse<?> authResponse = new AuthResponse<>(0, "模拟失败", null);
+        AuthResponse<AuthUser> authResponse = new AuthResponse<>(0, "模拟失败", null);
         when(authRequest.login(argThat(authCallback -> {
             assertEquals(code, authCallback.getCode());
             assertEquals(state, authCallback.getState());
@@ -291,7 +290,7 @@ public class SocialClientServiceImplTest extends BaseDbUnitTest {
         // mock 方法
         WxMaUserService userService = mock(WxMaUserService.class);
         when(wxMaService.getUserService()).thenReturn(userService);
-        WxErrorException wxErrorException = randomPojo(WxErrorException.class);
+        WxErrorException wxErrorException = new WxErrorException(new NullPointerException());
         when(userService.getPhoneNoInfo(eq(phoneCode))).thenThrow(wxErrorException);
 
         // 调用并断言异常
@@ -317,7 +316,7 @@ public class SocialClientServiceImplTest extends BaseDbUnitTest {
         Integer userType = randomPojo(UserTypeEnum.class).getValue();
         // mock 数据
         SocialClientDO client = randomPojo(SocialClientDO.class, o -> o.setStatus(CommonStatusEnum.DISABLE.getStatus())
-                .setUserType(userType).setSocialType(SocialTypeEnum.WECHAT_MINI_APP.getType()));
+                .setUserType(userType).setSocialType(SocialTypeEnum.WECHAT_MINI_PROGRAM.getType()));
         socialClientMapper.insert(client);
 
         // 调用
@@ -332,7 +331,7 @@ public class SocialClientServiceImplTest extends BaseDbUnitTest {
         Integer userType = randomPojo(UserTypeEnum.class).getValue();
         // mock 数据
         SocialClientDO client = randomPojo(SocialClientDO.class, o -> o.setStatus(CommonStatusEnum.ENABLE.getStatus())
-                .setUserType(userType).setSocialType(SocialTypeEnum.WECHAT_MINI_APP.getType()));
+                .setUserType(userType).setSocialType(SocialTypeEnum.WECHAT_MINI_PROGRAM.getType()));
         socialClientMapper.insert(client);
         // mock 方法
         WxMaProperties.ConfigStorage configStorage = mock(WxMaProperties.ConfigStorage.class);

+ 2 - 2
yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/social/SocialUserServiceImplTest.java

@@ -11,12 +11,12 @@ import cn.iocoder.yudao.module.system.dal.dataobject.social.SocialUserDO;
 import cn.iocoder.yudao.module.system.dal.mysql.social.SocialUserBindMapper;
 import cn.iocoder.yudao.module.system.dal.mysql.social.SocialUserMapper;
 import cn.iocoder.yudao.module.system.enums.social.SocialTypeEnum;
-import com.xingyuv.jushauth.model.AuthUser;
+import jakarta.annotation.Resource;
+import me.zhyd.oauth.model.AuthUser;
 import org.junit.jupiter.api.Test;
 import org.springframework.boot.test.mock.mockito.MockBean;
 import org.springframework.context.annotation.Import;
 
-import javax.annotation.Resource;
 import java.util.List;
 
 import static cn.hutool.core.util.RandomUtil.randomEle;

+ 1 - 0
yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/user/AdminUserServiceImplTest.java

@@ -212,6 +212,7 @@ public class AdminUserServiceImplTest extends BaseDbUnitTest {
         UserProfileUpdateReqVO reqVO = randomPojo(UserProfileUpdateReqVO.class, o -> {
             o.setMobile(randomString());
             o.setSex(RandomUtil.randomEle(SexEnum.values()).getSex());
+            o.setAvatar(randomURL());
         });
 
         // 调用

+ 1 - 1
yudao-server/src/main/resources/application-dev.yaml

@@ -188,7 +188,7 @@ justauth:
       agent-id: 1000004
       ignore-check-redirect-uri: true
     # noinspection SpringBootApplicationYaml
-    WECHAT_MINI_APP: # 微信小程序
+    WECHAT_MINI_PROGRAM: # 微信小程序
       client-id: ${wx.miniapp.appid}
       client-secret: ${wx.miniapp.secret}
       ignore-check-redirect-uri: true

+ 5 - 5
yudao-server/src/main/resources/application-local.yaml

@@ -209,10 +209,10 @@ wx:
     #    secret: 333ae72f41552af1e998fe1f54e1584a
 #    appid: wx63c280fe3248a3e7 # wenhualian的接口测试号
 #    secret: 6f270509224a7ae1296bbf1c8cb97aed
-#    appid: wxc4598c446f8a9cb3 # 测试号(Kongdy 提供的)
-#    secret: 4a1a04e07f6a4a0751b39c3064a92c8b
-    appid: wx66186af0759f47c9 # 测试号(puhui 提供的)
-    secret: 3218bcbd112cbc614c7264ceb20144ac
+    appid: wxc4598c446f8a9cb3 # 测试号(Kongdy 提供的)
+    secret: 4a1a04e07f6a4a0751b39c3064a92c8b
+#    appid: wx66186af0759f47c9 # 测试号(puhui 提供的)
+#    secret: 3218bcbd112cbc614c7264ceb20144ac
     config-storage:
       type: RedisTemplate # 采用 RedisTemplate 操作 Redis,会自动从 Spring 中获取
       key-prefix: wa # Redis Key 的前缀
@@ -252,7 +252,7 @@ justauth:
       agent-id: 1000004
       ignore-check-redirect-uri: true
     # noinspection SpringBootApplicationYaml
-    WECHAT_MINI_APP: # 微信小程序
+    WECHAT_MINI_PROGRAM: # 微信小程序
       client-id: ${wx.miniapp.appid}
       client-secret: ${wx.miniapp.secret}
       ignore-check-redirect-uri: true