Przeglądaj źródła

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

# Conflicts:
#	yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/message/MpMessageServiceImpl.java
YunaiV 6 miesięcy temu
rodzic
commit
8e0398bce6
39 zmienionych plików z 1022 dodań i 722 usunięć
  1. 1 1
      pom.xml
  2. 27 0
      yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/string/StrUtils.java
  3. 42 1
      yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/config/YudaoTenantAutoConfiguration.java
  4. 2 2
      yudao-framework/yudao-spring-boot-starter-protection/src/main/java/cn/iocoder/yudao/framework/idempotent/core/keyresolver/impl/DefaultIdempotentKeyResolver.java
  5. 2 2
      yudao-framework/yudao-spring-boot-starter-protection/src/main/java/cn/iocoder/yudao/framework/idempotent/core/keyresolver/impl/UserIdempotentKeyResolver.java
  6. 2 2
      yudao-framework/yudao-spring-boot-starter-protection/src/main/java/cn/iocoder/yudao/framework/ratelimiter/core/keyresolver/impl/ClientIpRateLimiterKeyResolver.java
  7. 2 2
      yudao-framework/yudao-spring-boot-starter-protection/src/main/java/cn/iocoder/yudao/framework/ratelimiter/core/keyresolver/impl/DefaultRateLimiterKeyResolver.java
  8. 2 2
      yudao-framework/yudao-spring-boot-starter-protection/src/main/java/cn/iocoder/yudao/framework/ratelimiter/core/keyresolver/impl/ServerNodeRateLimiterKeyResolver.java
  9. 2 2
      yudao-framework/yudao-spring-boot-starter-protection/src/main/java/cn/iocoder/yudao/framework/ratelimiter/core/keyresolver/impl/UserRateLimiterKeyResolver.java
  10. 2 0
      yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/FileController.java
  11. 0 2
      yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/codegen/inner/CodegenEngine.java
  12. 3 2
      yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3_vben5_antd/general/api/api.ts.vm
  13. 0 349
      yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3_vben5_antd/general/views/data.ts.vm
  14. 33 22
      yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3_vben5_antd/general/views/form.vue.vm
  15. 124 28
      yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3_vben5_antd/general/views/index.vue.vm
  16. 144 25
      yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3_vben5_antd/general/views/modules/form_sub_erp.vue.vm
  17. 1 1
      yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3_vben5_antd/general/views/modules/form_sub_inner.vue.vm
  18. 245 106
      yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3_vben5_antd/general/views/modules/form_sub_normal.vue.vm
  19. 302 96
      yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3_vben5_antd/general/views/modules/list_sub_erp.vue.vm
  20. 1 1
      yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3_vben5_antd/general/views/modules/list_sub_inner.vue.vm
  21. 3 2
      yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3_vben5_antd/schema/api/api.ts.vm
  22. 0 14
      yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3_vben5_antd/schema/views/form.vue.vm
  23. 4 7
      yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3_vben5_antd/schema/views/modules/form_sub_normal.vue.vm
  24. 1 1
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/point/PointActivityServiceImpl.java
  25. 1 1
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/seckill/SeckillActivityServiceImpl.java
  26. 3 3
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/framework/delivery/core/enums/ExpressClientEnum.java
  27. 23 20
      yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/open/MpOpenController.java
  28. 1 1
      yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/dal/mysql/user/MpUserMapper.java
  29. 0 3
      yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/handler/menu/MenuHandler.java
  30. 1 1
      yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/handler/message/MessageReceiveHandler.java
  31. 2 1
      yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/message/MpMessageService.java
  32. 26 10
      yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/message/MpMessageServiceImpl.java
  33. 4 0
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/notify/PayNotifyController.java
  34. 2 2
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/demo/PayDemoOrderServiceImpl.java
  35. 3 0
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/captcha/CaptchaController.java
  36. 5 0
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/sms/SmsCallbackController.java
  37. 4 0
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/tenant/TenantController.java
  38. 1 0
      yudao-server/src/main/resources/application-local.yaml
  39. 1 10
      yudao-server/src/main/resources/application.yaml

+ 1 - 1
pom.xml

@@ -16,7 +16,7 @@
         <module>yudao-module-system</module>
         <module>yudao-module-infra</module>
 <!--        <module>yudao-module-member</module>-->
-        <module>yudao-module-bpm</module>
+<!--        <module>yudao-module-bpm</module>-->
 <!--        <module>yudao-module-report</module>-->
 <!--        <module>yudao-module-mp</module>-->
 <!--        <module>yudao-module-pay</module>-->

+ 27 - 0
yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/string/StrUtils.java

@@ -3,6 +3,7 @@ package cn.iocoder.yudao.framework.common.util.string;
 import cn.hutool.core.text.StrPool;
 import cn.hutool.core.util.ArrayUtil;
 import cn.hutool.core.util.StrUtil;
+import org.aspectj.lang.JoinPoint;
 
 import java.util.Arrays;
 import java.util.Collection;
@@ -77,4 +78,30 @@ public class StrUtils {
                 .collect(Collectors.joining("\n"));
     }
 
+    /**
+     * 拼接方法的参数
+     *
+     * 特殊:排除一些无法序列化的参数,如 ServletRequest、ServletResponse、MultipartFile
+     *
+     * @param joinPoint 连接点
+     * @return 拼接后的参数
+     */
+    public static String joinMethodArgs(JoinPoint joinPoint) {
+        Object[] args = joinPoint.getArgs();
+        if (ArrayUtil.isEmpty(args)) {
+            return "";
+        }
+        return ArrayUtil.join(args, ",", item -> {
+            if (item == null) {
+                return "";
+            }
+            // 讨论可见:https://t.zsxq.com/XUJVk、https://t.zsxq.com/MnKcL
+            String clazzName = item.getClass().getName();
+            if (StrUtil.startWithAny(clazzName, "javax.servlet", "jakarta.servlet", "org.springframework.web")) {
+                return "";
+            }
+            return item;
+        });
+    }
+
 }

+ 42 - 1
yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/config/YudaoTenantAutoConfiguration.java

@@ -3,6 +3,7 @@ package cn.iocoder.yudao.framework.tenant.config;
 import cn.iocoder.yudao.framework.common.enums.WebFilterOrderEnum;
 import cn.iocoder.yudao.framework.mybatis.core.util.MyBatisUtils;
 import cn.iocoder.yudao.framework.redis.config.YudaoCacheProperties;
+import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore;
 import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnoreAspect;
 import cn.iocoder.yudao.framework.tenant.core.db.TenantDatabaseInterceptor;
 import cn.iocoder.yudao.framework.tenant.core.job.TenantJobAspect;
@@ -19,11 +20,13 @@ import cn.iocoder.yudao.framework.web.core.handler.GlobalExceptionHandler;
 import cn.iocoder.yudao.module.system.api.tenant.TenantApi;
 import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
 import com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor;
+import jakarta.annotation.Resource;
 import org.springframework.boot.autoconfigure.AutoConfiguration;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
 import org.springframework.boot.context.properties.EnableConfigurationProperties;
 import org.springframework.boot.web.servlet.FilterRegistrationBean;
+import org.springframework.context.ApplicationContext;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Primary;
 import org.springframework.data.redis.cache.BatchStrategies;
@@ -32,14 +35,24 @@ import org.springframework.data.redis.cache.RedisCacheManager;
 import org.springframework.data.redis.cache.RedisCacheWriter;
 import org.springframework.data.redis.connection.RedisConnectionFactory;
 import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.web.method.HandlerMethod;
+import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
+import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
+import org.springframework.web.util.pattern.PathPattern;
 
+import java.util.Map;
 import java.util.Objects;
 
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
+
 @AutoConfiguration
 @ConditionalOnProperty(prefix = "yudao.tenant", value = "enable", matchIfMissing = true) // 允许使用 yudao.tenant.enable=false 禁用多租户
 @EnableConfigurationProperties(TenantProperties.class)
 public class YudaoTenantAutoConfiguration {
 
+    @Resource
+    private ApplicationContext applicationContext;
+
     @Bean
     public TenantFrameworkService tenantFrameworkService(TenantApi tenantApi) {
         return new TenantFrameworkServiceImpl(tenantApi);
@@ -67,13 +80,41 @@ public class YudaoTenantAutoConfiguration {
     // ========== WEB ==========
 
     @Bean
-    public FilterRegistrationBean<TenantContextWebFilter> tenantContextWebFilter() {
+    public FilterRegistrationBean<TenantContextWebFilter> tenantContextWebFilter(TenantProperties tenantProperties) {
         FilterRegistrationBean<TenantContextWebFilter> registrationBean = new FilterRegistrationBean<>();
         registrationBean.setFilter(new TenantContextWebFilter());
         registrationBean.setOrder(WebFilterOrderEnum.TENANT_CONTEXT_FILTER);
+        addIgnoreUrls(tenantProperties);
         return registrationBean;
     }
 
+    /**
+     * 如果 Controller 接口上,有 {@link TenantIgnore} 注解,那么添加到忽略的 URL 中
+     *
+     * @param tenantProperties 租户配置
+     */
+    private void addIgnoreUrls(TenantProperties tenantProperties) {
+        // 获得接口对应的 HandlerMethod 集合
+        RequestMappingHandlerMapping requestMappingHandlerMapping = (RequestMappingHandlerMapping)
+                applicationContext.getBean("requestMappingHandlerMapping");
+        Map<RequestMappingInfo, HandlerMethod> handlerMethodMap = requestMappingHandlerMapping.getHandlerMethods();
+        // 获得有 @TenantIgnore 注解的接口
+        for (Map.Entry<RequestMappingInfo, HandlerMethod> entry : handlerMethodMap.entrySet()) {
+            HandlerMethod handlerMethod = entry.getValue();
+            if (!handlerMethod.hasMethodAnnotation(TenantIgnore.class)) {
+                continue;
+            }
+            // 添加到忽略的 URL 中
+            if (entry.getKey().getPatternsCondition() != null) {
+                tenantProperties.getIgnoreUrls().addAll(entry.getKey().getPatternsCondition().getPatterns());
+            }
+            if (entry.getKey().getPathPatternsCondition() != null) {
+                tenantProperties.getIgnoreUrls().addAll(
+                        convertList(entry.getKey().getPathPatternsCondition().getPatterns(), PathPattern::getPatternString));
+            }
+        }
+    }
+
     // ========== Security ==========
 
     @Bean

+ 2 - 2
yudao-framework/yudao-spring-boot-starter-protection/src/main/java/cn/iocoder/yudao/framework/idempotent/core/keyresolver/impl/DefaultIdempotentKeyResolver.java

@@ -1,7 +1,7 @@
 package cn.iocoder.yudao.framework.idempotent.core.keyresolver.impl;
 
-import cn.hutool.core.util.StrUtil;
 import cn.hutool.crypto.SecureUtil;
+import cn.iocoder.yudao.framework.common.util.string.StrUtils;
 import cn.iocoder.yudao.framework.idempotent.core.annotation.Idempotent;
 import cn.iocoder.yudao.framework.idempotent.core.keyresolver.IdempotentKeyResolver;
 import org.aspectj.lang.JoinPoint;
@@ -18,7 +18,7 @@ public class DefaultIdempotentKeyResolver implements IdempotentKeyResolver {
     @Override
     public String resolver(JoinPoint joinPoint, Idempotent idempotent) {
         String methodName = joinPoint.getSignature().toString();
-        String argsStr = StrUtil.join(",", joinPoint.getArgs());
+        String argsStr = StrUtils.joinMethodArgs(joinPoint);
         return SecureUtil.md5(methodName + argsStr);
     }
 

+ 2 - 2
yudao-framework/yudao-spring-boot-starter-protection/src/main/java/cn/iocoder/yudao/framework/idempotent/core/keyresolver/impl/UserIdempotentKeyResolver.java

@@ -1,7 +1,7 @@
 package cn.iocoder.yudao.framework.idempotent.core.keyresolver.impl;
 
-import cn.hutool.core.util.StrUtil;
 import cn.hutool.crypto.SecureUtil;
+import cn.iocoder.yudao.framework.common.util.string.StrUtils;
 import cn.iocoder.yudao.framework.idempotent.core.annotation.Idempotent;
 import cn.iocoder.yudao.framework.idempotent.core.keyresolver.IdempotentKeyResolver;
 import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils;
@@ -19,7 +19,7 @@ public class UserIdempotentKeyResolver implements IdempotentKeyResolver {
     @Override
     public String resolver(JoinPoint joinPoint, Idempotent idempotent) {
         String methodName = joinPoint.getSignature().toString();
-        String argsStr = StrUtil.join(",", joinPoint.getArgs());
+        String argsStr = StrUtils.joinMethodArgs(joinPoint);
         Long userId = WebFrameworkUtils.getLoginUserId();
         Integer userType = WebFrameworkUtils.getLoginUserType();
         return SecureUtil.md5(methodName + argsStr + userId + userType);

+ 2 - 2
yudao-framework/yudao-spring-boot-starter-protection/src/main/java/cn/iocoder/yudao/framework/ratelimiter/core/keyresolver/impl/ClientIpRateLimiterKeyResolver.java

@@ -1,8 +1,8 @@
 package cn.iocoder.yudao.framework.ratelimiter.core.keyresolver.impl;
 
-import cn.hutool.core.util.StrUtil;
 import cn.hutool.crypto.SecureUtil;
 import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
+import cn.iocoder.yudao.framework.common.util.string.StrUtils;
 import cn.iocoder.yudao.framework.ratelimiter.core.annotation.RateLimiter;
 import cn.iocoder.yudao.framework.ratelimiter.core.keyresolver.RateLimiterKeyResolver;
 import org.aspectj.lang.JoinPoint;
@@ -19,7 +19,7 @@ public class ClientIpRateLimiterKeyResolver implements RateLimiterKeyResolver {
     @Override
     public String resolver(JoinPoint joinPoint, RateLimiter rateLimiter) {
         String methodName = joinPoint.getSignature().toString();
-        String argsStr = StrUtil.join(",", joinPoint.getArgs());
+        String argsStr = StrUtils.joinMethodArgs(joinPoint);
         String clientIp = ServletUtils.getClientIP();
         return SecureUtil.md5(methodName + argsStr + clientIp);
     }

+ 2 - 2
yudao-framework/yudao-spring-boot-starter-protection/src/main/java/cn/iocoder/yudao/framework/ratelimiter/core/keyresolver/impl/DefaultRateLimiterKeyResolver.java

@@ -1,7 +1,7 @@
 package cn.iocoder.yudao.framework.ratelimiter.core.keyresolver.impl;
 
-import cn.hutool.core.util.StrUtil;
 import cn.hutool.crypto.SecureUtil;
+import cn.iocoder.yudao.framework.common.util.string.StrUtils;
 import cn.iocoder.yudao.framework.ratelimiter.core.annotation.RateLimiter;
 import cn.iocoder.yudao.framework.ratelimiter.core.keyresolver.RateLimiterKeyResolver;
 import org.aspectj.lang.JoinPoint;
@@ -18,7 +18,7 @@ public class DefaultRateLimiterKeyResolver implements RateLimiterKeyResolver {
     @Override
     public String resolver(JoinPoint joinPoint, RateLimiter rateLimiter) {
         String methodName = joinPoint.getSignature().toString();
-        String argsStr = StrUtil.join(",", joinPoint.getArgs());
+        String argsStr = StrUtils.joinMethodArgs(joinPoint);
         return SecureUtil.md5(methodName + argsStr);
     }
 

+ 2 - 2
yudao-framework/yudao-spring-boot-starter-protection/src/main/java/cn/iocoder/yudao/framework/ratelimiter/core/keyresolver/impl/ServerNodeRateLimiterKeyResolver.java

@@ -1,8 +1,8 @@
 package cn.iocoder.yudao.framework.ratelimiter.core.keyresolver.impl;
 
-import cn.hutool.core.util.StrUtil;
 import cn.hutool.crypto.SecureUtil;
 import cn.hutool.system.SystemUtil;
+import cn.iocoder.yudao.framework.common.util.string.StrUtils;
 import cn.iocoder.yudao.framework.ratelimiter.core.annotation.RateLimiter;
 import cn.iocoder.yudao.framework.ratelimiter.core.keyresolver.RateLimiterKeyResolver;
 import org.aspectj.lang.JoinPoint;
@@ -19,7 +19,7 @@ public class ServerNodeRateLimiterKeyResolver implements RateLimiterKeyResolver
     @Override
     public String resolver(JoinPoint joinPoint, RateLimiter rateLimiter) {
         String methodName = joinPoint.getSignature().toString();
-        String argsStr = StrUtil.join(",", joinPoint.getArgs());
+        String argsStr = StrUtils.joinMethodArgs(joinPoint);
         String serverNode = String.format("%s@%d", SystemUtil.getHostInfo().getAddress(), SystemUtil.getCurrentPID());
         return SecureUtil.md5(methodName + argsStr + serverNode);
     }

+ 2 - 2
yudao-framework/yudao-spring-boot-starter-protection/src/main/java/cn/iocoder/yudao/framework/ratelimiter/core/keyresolver/impl/UserRateLimiterKeyResolver.java

@@ -1,7 +1,7 @@
 package cn.iocoder.yudao.framework.ratelimiter.core.keyresolver.impl;
 
-import cn.hutool.core.util.StrUtil;
 import cn.hutool.crypto.SecureUtil;
+import cn.iocoder.yudao.framework.common.util.string.StrUtils;
 import cn.iocoder.yudao.framework.ratelimiter.core.annotation.RateLimiter;
 import cn.iocoder.yudao.framework.ratelimiter.core.keyresolver.RateLimiterKeyResolver;
 import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils;
@@ -19,7 +19,7 @@ public class UserRateLimiterKeyResolver implements RateLimiterKeyResolver {
     @Override
     public String resolver(JoinPoint joinPoint, RateLimiter rateLimiter) {
         String methodName = joinPoint.getSignature().toString();
-        String argsStr = StrUtil.join(",", joinPoint.getArgs());
+        String argsStr = StrUtils.joinMethodArgs(joinPoint);
         Long userId = WebFrameworkUtils.getLoginUserId();
         Integer userType = WebFrameworkUtils.getLoginUserType();
         return SecureUtil.md5(methodName + argsStr + userId + userType);

+ 2 - 0
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/FileController.java

@@ -6,6 +6,7 @@ import cn.hutool.core.util.URLUtil;
 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore;
 import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.*;
 import cn.iocoder.yudao.module.infra.dal.dataobject.file.FileDO;
 import cn.iocoder.yudao.module.infra.service.file.FileService;
@@ -77,6 +78,7 @@ public class FileController {
 
     @GetMapping("/{configId}/get/**")
     @PermitAll
+    @TenantIgnore
     @Operation(summary = "下载文件")
     @Parameter(name = "configId", description = "配置编号", required = true)
     public void getFileContent(HttpServletRequest request,

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

@@ -164,8 +164,6 @@ public class CodegenEngine {
             .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"),

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

@@ -1,4 +1,5 @@
 import type { PageParam, PageResult } from '@vben/request';
+import type { Dayjs } from 'dayjs';
 
 import { requestClient } from '#/api/request';
 #set ($baseURL = "/${table.moduleName}/${simpleClassName_strikeCase}")
@@ -16,7 +17,7 @@ export namespace ${simpleClassName}Api {
         #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}
+            ${column.javaField}#if($column.updateOperation && !$column.primaryKey && !$column.nullable)?#end: string | Dayjs; // ${column.columnComment}
         #else
             ${column.javaField}#if($column.updateOperation && !$column.primaryKey && !$column.nullable)?#end: ${column.javaType.toLowerCase()}; // ${column.columnComment}
         #end
@@ -32,7 +33,7 @@ export namespace ${simpleClassName}Api {
 #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}
+    ${column.javaField}#if($column.updateOperation && !$column.primaryKey && !$column.nullable)?#end: string | Dayjs; // ${column.columnComment}
 #else
     ${column.javaField}#if($column.updateOperation && !$column.primaryKey && !$column.nullable)?#end: ${column.javaType.toLowerCase()}; // ${column.columnComment}
 #end

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

@@ -1,349 +0,0 @@
-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

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

@@ -5,11 +5,11 @@ 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 { message, Tabs, Form, Input, Textarea, Select, RadioGroup, Radio, CheckboxGroup, Checkbox, DatePicker, TreeSelect } 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';
+import { handleTree } from '@vben/utils'
 #end
 ## 特殊:主子表专属逻辑
 #if ( $table.templateType == 10 || $table.templateType == 12 )
@@ -25,9 +25,8 @@ 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)
@@ -91,10 +90,12 @@ const resetForm = () => {
 /** 获得${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)
+  const data = await get${simpleClassName}List({});
+  data.unshift({
+    id: 0,
+    name: '顶级${table.classComment}',
+  });
+    ${classNameVar}Tree.value = handleTree(data);
 }
 #end
 
@@ -111,10 +112,11 @@ const [Modal, modalApi] = useVbenModal({
           #if ($subTable.subJoinMany) ## 一对多
             ## TODO 列表值校验?
           #else
-            const ${subClassNameVar}Valid = await ${subClassNameVar}FormRef.value?.validate();
-            if (!${subClassNameVar}Valid) {
-              subTabsName.value = '${subClassNameVar}';
-              return;
+            try {
+              await ${subClassNameVar}FormRef.value?.validate()
+            } catch (e) {
+              subTabsName.value = '${subClassNameVar}'
+              return
             }
           #end
         #end
@@ -133,7 +135,7 @@ const [Modal, modalApi] = useVbenModal({
           #if ($subTable.subJoinMany)
             data.${subClassNameVar}s = ${subClassNameVar}FormRef.value?.getData();
           #else
-            data.${subClassNameVar} = await ${subClassNameVar}FormRef.value?.getValues();
+            data.${subClassNameVar} = ${subClassNameVar}FormRef.value?.getValues();
           #end
         #end
       #end
@@ -156,7 +158,6 @@ const [Modal, modalApi] = useVbenModal({
       resetForm()
       return;
     }
-
     // 加载数据
     let data = modalApi.getData<${simpleClassName}Api.${simpleClassName}>();
     if (!data) {
@@ -185,8 +186,8 @@ const [Modal, modalApi] = useVbenModal({
       ref="formRef"
       :model="formData"
       :rules="rules"
-      :label-col="labelCol"
-      :wrapper-col="wrapperCol"
+      :label-col="{ span: 5 }"
+      :wrapper-col="{ span: 18 }"
     >
       #foreach($column in $columns)
         #if ($column.createOperation || $column.updateOperation)
@@ -207,9 +208,17 @@ const [Modal, modalApi] = useVbenModal({
                       v-model:value="formData.${javaField}"
                       :treeData="${classNameVar}Tree"
                 #if ($treeNameColumn.javaField == "name")
-                      :fieldNames="defaultProps"
+                      :fieldNames="{
+            label: 'name',
+            value: 'id',
+            children: 'children',
+          }"
                 #else
-                      :fieldNames="{...defaultProps, label: '$treeNameColumn.javaField'}"
+                      :fieldNames="{
+                        label: '$treeNameColumn.javaField',
+                        value: 'id',
+                        children: 'children',
+                        }"
                 #end
                       checkable
                       treeDefaultExpandAll
@@ -239,9 +248,10 @@ const [Modal, modalApi] = useVbenModal({
                   <Select.Option
                           v-for="dict in getDictOptions(DICT_TYPE.$dictType.toUpperCase(), '$dictMethod')"
                           :key="dict.value"
-                          :label="dict.label"
                           :value="dict.value"
-                  />
+                  >
+                    {{ dict.label }}
+                  </Select.Option>
                 #else##没数据字典
                   <Select.Option label="请选择字典生成" value="" />
                 #end
@@ -254,9 +264,10 @@ const [Modal, modalApi] = useVbenModal({
                   <Checkbox
                           v-for="dict in getDictOptions(DICT_TYPE.$dictType.toUpperCase(), '$dictMethod')"
                           :key="dict.value"
-                          :label="dict.label"
                           :value="dict.value"
-                  />
+                 >
+                    {{ dict.label }}
+                  </Checkbox>
                 #else##没数据字典
                   <Checkbox label="请选择字典生成" />
                 #end

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

@@ -1,8 +1,9 @@
 <script lang="ts" setup>
 import type { ${simpleClassName}Api } from '#/api/${table.moduleName}/${simpleClassName_strikeCase}';
+import type { VxeTableInstance } from 'vxe-table';
 
 import { Page, useVbenModal } from '@vben/common-ui';
-import { formatDateTime } from '@vben/utils';
+import { cloneDeep, 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';
@@ -10,6 +11,7 @@ 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 { TableToolbar } from '#/components/table-toolbar';
 import { getRangePickerDefaultProps } from '#/utils/date';
 
 ## 特殊:主子表专属逻辑
@@ -21,10 +23,10 @@ import { getRangePickerDefaultProps } from '#/utils/date';
     #end
 #end
 
-import { ref, h, reactive,onMounted } from 'vue';
+import { ref, h, reactive,onMounted,nextTick } from 'vue';
 import { $t } from '#/locales';
 #if (${table.templateType} == 2)## 树表接口
-import { handleTree } from '@/utils/tree'
+import { handleTree,isEmpty } from '@vben/utils'
 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}';
@@ -36,11 +38,19 @@ import { downloadByData } from '#/utils/download';
 const subTabsName = ref('$subClassNameVars.get(0)')
 #if ($table.templateType == 11)
 const select${simpleClassName} = ref<${simpleClassName}Api.${simpleClassName}>();
+async function onCellClick({ row }: { row: ${simpleClassName}Api.${simpleClassName} }) {
+  select${simpleClassName}.value = row
+}
 #end
 #end
 
 const loading = ref(true) // 列表的加载中
+#if ( $table.templateType == 2 )
+const list = ref<any[]>([]) // 树列表的数据
+#else
 const list = ref<${simpleClassName}Api.${simpleClassName}[]>([]) // 列表的数据
+#end
+
 ## 特殊:树表专属逻辑(树不需要分页接口)
 #if ( $table.templateType != 2 )
 const total = ref(0) // 列表的总页数
@@ -69,12 +79,21 @@ const exportLoading = ref(false) // 导出的加载中
 const getList = async () => {
   loading.value = true
   try {
+    const params = cloneDeep(queryParams) as any;
+      #foreach ($column in $columns)
+          #if ($column.listOperation)
+              #if ($column.htmlType == "datetime" || $column.listOperationCondition == "BETWEEN")
+                if (params.${column.javaField} && Array.isArray(params.${column.javaField})) {
+                  params.${column.javaField} = (params.${column.javaField} as string[]).join(',');
+                }
+              #end
+          #end
+      #end
       ## 特殊:树表专属逻辑(树不需要分页接口)
       #if ( $table.templateType == 2 )
-        const data = await get${simpleClassName}List(queryParams)
-        list.value = handleTree(data, 'id', '${treeParentColumn.javaField}')
+        list.value = await get${simpleClassName}List(params);
       #else
-        const data = await get${simpleClassName}Page(queryParams)
+        const data = await get${simpleClassName}Page(params)
         list.value = data.list
         total.value = data.total
       #end
@@ -85,7 +104,9 @@ const getList = async () => {
 
 /** 搜索按钮操作 */
 const handleQuery = () => {
+#if ( $table.templateType != 2 )
   queryParams.pageNo = 1
+#end
   getList()
 }
 
@@ -147,20 +168,40 @@ try {
 }
 }
 
-/** 初始化 **/
-onMounted(() => {
-  getList()
-})
+/** 隐藏搜索栏 */
+const hiddenSearchBar = ref(false);
+const tableToolbarRef = ref<InstanceType<typeof TableToolbar>>();
+const tableRef = ref<VxeTableInstance>();
+
+#if (${table.templateType} == 2)
+/** 切换树形展开/收缩状态 */
+const isExpanded = ref(true);
+function toggleExpand() {
+  isExpanded.value = !isExpanded.value;
+  tableRef.value?.setAllTreeExpand(isExpanded.value);
+}
+#end
+
+/** 初始化 */
+onMounted(async () => {
+  await getList();
+  await nextTick();
+  // 挂载 toolbar 工具栏
+  const table = tableRef.value;
+  const tableToolbar = tableToolbarRef.value;
+  if (table && tableToolbar) {
+    await table.connect(tableToolbar.getToolbarRef()!);
+  }
+});
 </script>
 
 <template>
   <Page auto-content-height>
     <FormModal @success="getList" />
 
-    <ContentWrap>
+    <ContentWrap v-if="!hiddenSearchBar">
       <!-- 搜索工作栏 -->
       <Form
-          class="-mb-15px"
           :model="queryParams"
           ref="queryFormRef"
           layout="inline"
@@ -185,7 +226,7 @@ onMounted(() => {
                           placeholder="请输入${comment}"
                           allowClear
                           @pressEnter="handleQuery"
-                          class="!w-240px"
+                           class="w-full"
                       />
                     </Form.Item>
                   #elseif ($column.htmlType == "select" || $column.htmlType == "radio" || $column.htmlType == "checkbox")
@@ -194,15 +235,16 @@ onMounted(() => {
                           v-model:value="queryParams.${javaField}"
                           placeholder="请选择${comment}"
                           allowClear
-                          class="!w-240px"
+                           class="w-full"
                       >
                           #if ("" != $dictType)## 设置了 dictType 数据字典的情况
                             <Select.Option
                                 v-for="dict in getDictOptions(DICT_TYPE.$dictType.toUpperCase(), '$dictMethod')"
                                 :key="dict.value"
-                                :label="dict.label"
                                 :value="dict.value"
-                            />
+                            >
+                              {{ dict.label }}
+                            </Select.Option>
                           #else## 未设置 dictType 数据字典的情况
                             <Select.Option label="请选择字典生成" value="" />
                           #end
@@ -216,7 +258,7 @@ onMounted(() => {
                               valueFormat="YYYY-MM-DD"
                               placeholder="选择${comment}"
                               allowClear
-                              class="!w-240px"
+                               class="w-full"
                           />
                         </Form.Item>
                       #else## 范围
@@ -224,7 +266,7 @@ onMounted(() => {
                           <RangePicker
                               v-model:value="queryParams.${javaField}"
                               v-bind="getRangePickerDefaultProps()"
-                              class="!w-220px"
+                              class="w-full"
                           />
                         </Form.Item>
                       #end
@@ -232,8 +274,26 @@ onMounted(() => {
               #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" @click="resetQuery"> 重置 </Button>
+          <Button class="ml-2" @click="handleQuery" type="primary">
+            搜索
+          </Button>
+        </Form.Item>
+      </Form>
+    </ContentWrap>
+    
+    <!-- 列表 -->
+    <ContentWrap title="${table.classComment}">
+      <template #extra>
+        <TableToolbar
+            ref="tableToolbarRef"
+            v-model:hidden-search="hiddenSearchBar"
+        >
+        #if (${table.templateType} == 2)
+          <Button @click="toggleExpand" class="mr-2">
+            {{ isExpanded ? '收缩' : '展开' }}
+          </Button>
+        #end
           <Button
               class="ml-2"
               :icon="h(Plus)"
@@ -241,7 +301,7 @@ onMounted(() => {
               @click="onCreate"
               v-access:code="['${permissionPrefix}:create']"
           >
-            {{ $t('ui.actionTitle.create', ['示例联系人']) }}
+            {{ $t('ui.actionTitle.create', ['${table.classComment}']) }}
           </Button>
           <Button
               :icon="h(Download)"
@@ -253,13 +313,31 @@ onMounted(() => {
           >
             {{ $t('ui.actionTitle.export') }}
           </Button>
-        </Form.Item>
-      </Form>
-    </ContentWrap>
-    
-    <!-- 列表 -->
-    <ContentWrap>
-      <vxe-table :data="list"  show-overflow :loading="loading">
+        </TableToolbar>
+      </template>
+      <vxe-table
+          ref="tableRef"
+          :data="list"
+        #if ( $table.templateType == 2 )
+          :tree-config="{
+          parentField: '${treeParentColumn.javaField}',
+          rowField: 'id',
+          transform: true,
+          expandAll: true,
+          reserve: true,
+        }"
+        #end
+#if ($table.templateType == 11) ## erp情况
+          @cell-click="onCellClick"
+          :row-config="{
+            keyField: 'id',
+            isHover: true,
+            isCurrent: true,
+          }"
+#end
+          show-overflow
+          :loading="loading"
+      >
           ## 特殊:主子表专属逻辑
           #if ( $table.templateType == 12 && $subTables && $subTables.size() > 0 )
             <!-- 子表的列表 -->
@@ -298,6 +376,8 @@ onMounted(() => {
                         <dict-tag :type="DICT_TYPE.$dictType.toUpperCase()" :value="row.${javaField}" />
                       </template>
                     </vxe-column>
+                  #elseif ($table.templateType == 2 && $javaField == $treeNameColumn.javaField)
+                    <vxe-column field="${javaField}" title="${comment}" align="center"  tree-node/>
                   #else
                     <vxe-column field="${javaField}" title="${comment}" align="center" />
                   #end
@@ -305,6 +385,16 @@ onMounted(() => {
           #end
         <vxe-column field="operation" title="操作" align="center">
           <template #default="{row}">
+#if ( $table.templateType == 2 )
+  <Button
+      size="small"
+      type="link"
+      @click="onAppend(row as any)"
+      v-access:code="['${permissionPrefix}:create']"
+  >
+    新增下级
+  </Button>
+#end
             <Button
                 size="small"
                 type="link"
@@ -316,7 +406,11 @@ onMounted(() => {
             <Button
                 size="small"
                 type="link"
+                danger
                 class="ml-2"
+                #if ( $table.templateType == 2 )
+                :disabled="!isEmpty(row?.children)"
+                #end
                 @click="onDelete(row as any)"
                 v-access:code="['${permissionPrefix}:delete']"
             >
@@ -325,6 +419,7 @@ onMounted(() => {
           </template>
         </vxe-column>
       </vxe-table>
+#if ( $table.templateType != 2 )
       <!-- 分页 -->
       <div class="mt-2 flex justify-end">
         <Pagination
@@ -335,6 +430,7 @@ onMounted(() => {
             @change="getList"
         />
       </div>
+#end
     </ContentWrap>
 
 #if ($table.templateType == 11) ## erp情况

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

@@ -4,49 +4,54 @@
 #set ($subSimpleClassName = $subSimpleClassNames.get($subIndex))
 <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 { message } from 'ant-design-vue';
+  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, TreeSelect } from 'ant-design-vue';
+  import { DICT_TYPE, getDictOptions } from '#/utils/dict';
 
   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';
+  import { get${subSimpleClassName}, create${subSimpleClassName}, update${subSimpleClassName} } from '#/api/${table.moduleName}/${simpleClassName_strikeCase}';
 
   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 formRef = ref();
+  const formData = ref<Partial<${simpleClassName}Api.${subSimpleClassName}>>({
+    #foreach ($column in $subColumns)
+      #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 $subColumns)
+      #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
+  };
 
   const [Modal, modalApi] = useVbenModal({
     async onConfirm() {
-      const { valid } = await formApi.validate();
-      if (!valid) {
-        return;
-      }
+      await formRef.value?.validate();
 
       modalApi.lock();
       // 提交表单
-      const data = (await formApi.getValues()) as ${simpleClassName}Api.${subSimpleClassName};
-      data.${subJoinColumn.javaField} = formData.value?.${subJoinColumn.javaField};
+      const data = formData.value as ${simpleClassName}Api.${subSimpleClassName};
       try {
         await (formData.value?.id ? update${subSimpleClassName}(data) : create${subSimpleClassName}(data));
         // 关闭并提示
@@ -62,7 +67,7 @@
     },
     async onOpenChange(isOpen: boolean) {
       if (!isOpen) {
-        formData.value = undefined;
+        resetForm()
         return;
       }
 
@@ -81,13 +86,127 @@
       }
       // 设置到 values
       formData.value = data;
-      await formApi.setValues(formData.value);
     },
   });
+
+  /** 重置表单 */
+  const resetForm = () => {
+    formData.value = {
+      #foreach ($column in $subColumns)
+        #if ($column.createOperation || $column.updateOperation)
+          #if ($column.htmlType == "checkbox")
+              $column.javaField: [],
+          #else
+              $column.javaField: undefined,
+          #end
+        #end
+      #end
+    };
+    formRef.value?.resetFields();
+  }
 </script>
 
 <template>
   <Modal :title="getTitle">
-    <Form class="mx-4" />
+    <Form
+      ref="formRef"
+      :model="formData"
+      :rules="rules"
+      :label-col="{ span: 5 }"
+      :wrapper-col="{ span: 18 }"
+    >
+      #foreach($column in $subColumns)
+        #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 ($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"
+                          :value="dict.value"
+                  >
+                    {{ dict.label }}
+                  </Select.Option>
+                #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"
+                          :value="dict.value"
+                  >
+                    {{ dict.label }}
+                  </Checkbox>
+                #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>
   </Modal>
 </template>

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

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

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

@@ -7,19 +7,19 @@
 <script lang="ts" setup>
   import type { ${simpleClassName}Api } from '#/api/${table.moduleName}/${simpleClassName_strikeCase}';
 
+  import { message, Tabs, Form, Input, Textarea,Button, Select, RadioGroup, Radio, CheckboxGroup, Checkbox, DatePicker } from 'ant-design-vue';
   import { computed, ref, h, onMounted,watch,nextTick } from 'vue';
   import { $t } from '#/locales';
+  import { DICT_TYPE, getDictOptions } from '#/utils/dict';
 
 #if ($subTable.subJoinMany) ## 一对多
+import type { VxeTableInstance } from 'vxe-table';
 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 { VxeColumn, VxeTable } from 'vxe-table';
 import { get${subSimpleClassName}ListBy${SubJoinColumnName} } from '#/api/${table.moduleName}/${simpleClassName_strikeCase}';
 #else
-import { useVbenForm } from '#/adapter/form';
-import { use${subSimpleClassName}FormSchema } from '../data';
+import type { Rule } from 'ant-design-vue/es/form';
+import { Tinymce as RichTextarea } from '#/components/tinymce';
 import { get${subSimpleClassName}By${SubJoinColumnName} } from '#/api/${table.moduleName}/${simpleClassName_strikeCase}';
 #end
 
@@ -28,57 +28,27 @@ const props = defineProps<{
 }>()
 
 #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,
-  },
-},
-});
-
+const list = ref<${simpleClassName}Api.${subSimpleClassName}[]>([]) // 列表的数据
+const tableRef = ref<VxeTableInstance>();
 /** 添加${subTable.classComment} */
 const onAdd = async () => {
-  await gridApi.grid.insertAt({} as ${simpleClassName}Api.${subSimpleClassName}, -1);
+  await tableRef.value?.insertAt({} as ${simpleClassName}Api.${subSimpleClassName}, -1);
 }
 
 /** 删除${subTable.classComment} */
 const onDelete =  async (row: ${simpleClassName}Api.${subSimpleClassName}) => {
-  await gridApi.grid.remove(row);
+  await tableRef.value?.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}[];
+    const data = list.value as ${simpleClassName}Api.${subSimpleClassName}[];
+    const removeRecords = tableRef.value?.getRemoveRecords() as ${simpleClassName}Api.${subSimpleClassName}[];
+    const insertRecords = tableRef.value?.getInsertRecords() as ${simpleClassName}Api.${subSimpleClassName}[];
     return data
         .filter((row) => !removeRecords.some((removed) => removed.id === row.id))
-        .concat(insertRecords.map((row: any) => ({ ...row, id: undefined })));
+        ?.concat(insertRecords.map((row: any) => ({ ...row, id: undefined })));
   },
 });
 
@@ -89,32 +59,35 @@ watch(
       if (!val) {
         return;
       }
-      await nextTick();
-      await gridApi.grid.loadData(await get${subSimpleClassName}ListBy${SubJoinColumnName}(props.${subJoinColumn.javaField}!));
+      list.value = 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
+const formRef = ref();
+const formData = ref<Partial<${simpleClassName}Api.${subSimpleClassName}>>({
+    #foreach ($column in $subColumns)
+        #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 $subColumns)
+        #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
+};
 /** 暴露出表单校验方法和表单值获取方法 */
 defineExpose({
-  validate: async () => {
-    const { valid } = await formApi.validate();
-    return valid;
-  },
-  getValues: formApi.getValues,
+  validate: async () => await formRef.value?.validate(),
+  getValues: ()=> formData.value,
 });
 
 /** 监听主表的关联字段的变化,加载对应的子表数据 */
@@ -125,7 +98,7 @@ watch(
         return;
       }
       await nextTick();
-      await formApi.setValues(await get${subSimpleClassName}By${SubJoinColumnName}(props.${subJoinColumn.javaField}!));
+      formData.value = await get${subSimpleClassName}By${SubJoinColumnName}(props.${subJoinColumn.javaField}!);
     },
     { immediate: true },
 );
@@ -134,66 +107,232 @@ watch(
 
 <template>
 #if ($subTable.subJoinMany) ## 一对多
-  <Grid class="mx-4">
+  <vxe-table ref="tableRef" :data="list" show-overflow class="mx-4">
       #foreach($column in $subColumns)
           #if ($column.createOperation || $column.updateOperation)
+              #set ($comment = $column.columnComment)
               #set ($javaField = $column.javaField)
+              #set ($javaType = $column.javaType)
+              #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.id == $subJoinColumn.id) ## 特殊:忽略主子表的 join 字段,不用填写
               #elseif ($column.htmlType == "input" && !$column.primaryKey)## 忽略主键,不用在表单里
-                <template #${javaField}="{ row }">
-                  <Input v-model:value="row.${javaField}" />
-                </template>
+                <vxe-column field="${javaField}" title="${comment}" align="center">
+                  <template #default="{row}">
+                    <Input v-model:value="row.${javaField}" />
+                  </template>
+                </vxe-column>
+              #elseif($column.htmlType == "imageUpload")## 图片上传
+                <vxe-column field="${javaField}" title="${comment}" align="center">
+                  <template #default="{row}">
+                    <ImageUpload v-model:value="row.${javaField}" />
+                  </template>
+                </vxe-column>
+              #elseif($column.htmlType == "fileUpload")## 文件上传
+                <vxe-column field="${javaField}" title="${comment}" align="center">
+                  <template #default="{row}">
+                    <FileUpload v-model:value="row.${javaField}" />
+                  </template>
+                </vxe-column>
+              #elseif($column.htmlType == "select")## 下拉框
+                <vxe-column field="${javaField}" title="${comment}" align="center">
+                  <template #default="{row}">
+                    <Select v-model:value="row.${javaField}" placeholder="请选择${comment}">
+                        #if ("" != $dictType)## 有数据字典
+                          <Select.Option
+                              v-for="dict in getDictOptions(DICT_TYPE.$dictType.toUpperCase(), '$dictMethod')"
+                              :key="dict.value"
+                              :value="dict.value"
+                          >
+                            {{ dict.label }}
+                          </Select.Option>
+                        #else##没数据字典
+                          <Select.Option label="请选择字典生成" value="" />
+                        #end
+                    </Select>
+                  </template>
+                </vxe-column>
+              #elseif($column.htmlType == "checkbox")## 多选框
+                <vxe-column field="${javaField}" title="${comment}" align="center">
+                  <template #default="{row}">
+                    <CheckboxGroup v-model:value="row.${javaField}">
+                        #if ("" != $dictType)## 有数据字典
+                          <Checkbox
+                              v-for="dict in getDictOptions(DICT_TYPE.$dictType.toUpperCase(), '$dictMethod')"
+                              :key="dict.value"
+                              :value="dict.value"
+                          >
+                            {{ dict.label }}
+                          </Checkbox>
+                        #else##没数据字典
+                          <Checkbox label="请选择字典生成" />
+                        #end
+                    </CheckboxGroup>
+                  </template>
+                </vxe-column>
+              #elseif($column.htmlType == "radio")## 单选框
+                <vxe-column field="${javaField}" title="${comment}" align="center">
+                  <template #default="{row}">
+                    <RadioGroup v-model:value="row.${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>
+                  </template>
+                </vxe-column>
+              #elseif($column.htmlType == "datetime")## 时间框
+                <vxe-column field="${javaField}" title="${comment}" align="center">
+                  <template #default="{row}">
+                    <DatePicker
+                        v-model:value="row.${javaField}"
+                        :showTime="true"
+                        format="YYYY-MM-DD HH:mm:ss"
+                        valueFormat='x'
+                    />
+                  </template>
+                </vxe-column>
+              #elseif($column.htmlType == "textarea" || $column.htmlType == "editor")## 文本框
+                <vxe-column field="${javaField}" title="${comment}" align="center">
+                  <template #default="{row}">
+                    <Textarea v-model:value="row.${javaField}" />
+                  </template>
+                </vxe-column>
+              #end
+          #end
+      #end
+    <vxe-column field="operation" title="操作" align="center">
+      <template #default="{row}">
+        <Button
+            size="small"
+            type="link"
+            danger
+            @click="onDelete(row as any)"
+            v-access:code="['${permissionPrefix}:delete']"
+        >
+          {{ $t('ui.actionTitle.delete') }}
+        </Button>
+      </template>
+    </vxe-column>
+  </vxe-table>
+  <div class="flex justify-center mt-4">
+    <Button :icon="h(Plus)" type="primary" ghost @click="onAdd" v-access:code="['${permissionPrefix}:create']">
+      {{ $t('ui.actionTitle.create', ['${subTable.classComment}']) }}
+    </Button>
+  </div>
+#else
+  <Form
+      ref="formRef"
+      class="mx-4"
+      :model="formData"
+      :rules="rules"
+      :label-col="{ span: 5 }"
+      :wrapper-col="{ span: 18 }"
+  >
+      #foreach($column in $subColumns)
+          #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 ($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")## 图片上传
-                <template #${javaField}="{ row }">
-                  <UploadImg v-model:value="row.${javaField}" />
-                </template>
+                <Form.Item label="${comment}" name="${javaField}">
+                  <ImageUpload v-model:value="formData.${javaField}" />
+                </Form.Item>
               #elseif($column.htmlType == "fileUpload")## 文件上传
-                <template #${javaField}="{ row }">
-                  <UploadFile v-model:value="row.${javaField}" />
-                </template>
+                <Form.Item label="${comment}" name="${javaField}">
+                  <FileUpload v-model:value="formData.${javaField}" />
+                </Form.Item>
               #elseif($column.htmlType == "editor")## 文本编辑器
-                <template #${javaField}="{ row }">
-                  <Textarea v-model:value="row.${javaField}" />
-                </template>
+                <Form.Item label="${comment}" name="${javaField}">
+                  <RichTextarea v-model="formData.${javaField}" height="500px" />
+                </Form.Item>
               #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>
+                <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"
+                            :value="dict.value"
+                        >
+                          {{ dict.label }}
+                        </Select.Option>
+                      #else##没数据字典
+                        <Select.Option label="请选择字典生成" value="" />
+                      #end
                   </Select>
-                </template>
+                </Form.Item>
               #elseif($column.htmlType == "checkbox")## 多选框
-                <template #${javaField}="{ row, column }">
-                  <CheckboxGroup v-model:value="row.${javaField}" :options="column.params.options" />
-                </template>
+                <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"
+                            :value="dict.value"
+                        >
+                          {{ dict.label }}
+                        </Checkbox>
+                      #else##没数据字典
+                        <Checkbox label="请选择字典生成" />
+                      #end
+                  </CheckboxGroup>
+                </Form.Item>
               #elseif($column.htmlType == "radio")## 单选框
-                <template #${javaField}="{ row, column }">
-                  <RadioGroup v-model:value="row.${javaField}" :options="column.params.options" />
-                </template>
+                <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")## 时间框
-                <template #${javaField}="{ row }">
+                <Form.Item label="${comment}" name="${javaField}">
                   <DatePicker
-                      v-model:value="row.${javaField}"
-                      :showTime="true"
-                      format="YYYY-MM-DD HH:mm:ss"
-                      valueFormat='x'
+                      v-model:value="formData.${javaField}"
+                      valueFormat="x"
+                      placeholder="选择${comment}"
                   />
-                </template>
+                </Form.Item>
               #elseif($column.htmlType == "textarea")## 文本框
-                <template #${javaField}="{ row }">
-                  <Textarea v-model:value="row.${javaField}" />
-                </template>
+                <Form.Item label="${comment}" name="${javaField}">
+                  <Textarea v-model:value="formData.${javaField}" placeholder="请输入${comment}" />
+                </Form.Item>
               #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" />
+  </Form>
 #end
 </template>

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

@@ -6,30 +6,37 @@
 #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}';
+  import type { VxeTableInstance } from 'vxe-table';
+
+  import { DictTag } from '#/components/dict-tag';
+  import { DICT_TYPE, getDictOptions } from '#/utils/dict';
+  import { VxeColumn, VxeTable } from 'vxe-table';
+  import { reactive,ref, h, nextTick,watch,onMounted } from 'vue';
+  import { cloneDeep, formatDateTime } from '@vben/utils';
+  import { ContentWrap } from "#/components/content-wrap";
 
 #if ($table.templateType == 11) ## erp
-  import ${subSimpleClassName}Form from './${subSimpleClassName_strikeCase}-form.vue'
+    import { useVbenModal } from '@vben/common-ui';
+    import ${subSimpleClassName}Form from './${subSimpleClassName_strikeCase}-form.vue'
+    import { Tinymce as RichTextarea } from '#/components/tinymce';
+    import { ImageUpload, FileUpload } from "#/components/upload";
+    import { message,Button, Tabs,Pagination, Form, Input, Textarea, Select, RadioGroup, Radio, CheckboxGroup, Checkbox,RangePicker, DatePicker, TreeSelect } from 'ant-design-vue';
+    import { DICT_TYPE, getDictOptions } from '#/utils/dict';
+    import { Plus } from '@vben/icons';
+    import { $t } from '#/locales';
+    import { TableToolbar } from '#/components/table-toolbar';
+    import { getRangePickerDefaultProps } from '#/utils/date';
 #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<{
@@ -69,90 +76,81 @@ async function onDelete(row: ${simpleClassName}Api.${subSimpleClassName}) {
       content: $t('ui.actionMessage.deleteSuccess', [row.id]),
       key: 'action_process_msg',
     });
-    onRefresh();
+    getList();
   } 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 ()=> {
+  const loading = ref(true) // 列表的加载中
+  const list = ref<${simpleClassName}Api.${subSimpleClassName}[]>([]) // 列表的数据
 #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
+  const total = ref(0) // 列表的总页数
 #end
+#if ($table.templateType == 11) ## erp
+  const queryFormRef = ref() // 搜索的表单
+  const queryParams = reactive({
+      pageNo: 1,
+      pageSize: 10,
+      #foreach ($column in $subColumns)
+          #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 handleQuery = () => {
+  queryParams.pageNo = 1
+  getList()
+}
+
+/** 重置按钮操作 */
+const resetQuery = () => {
+  queryFormRef.value.resetFields()
+  handleQuery()
 }
+#end
+  /** 查询列表 */
+  const getList = async () => {
+    loading.value = true
+    try {
+      if (!props.${subJoinColumn.javaField}){
+        return []
+      }
+        ## 特殊:树表专属逻辑(树不需要分页接口)
+        #if ($table.templateType == 11) ## erp
+          const params = cloneDeep(queryParams) as any;
+            #foreach ($column in $columns)
+                #if ($column.listOperation)
+                    #if ($column.htmlType == "datetime" || $column.listOperationCondition == "BETWEEN")
+                      if (params.${column.javaField} && Array.isArray(params.${column.javaField})) {
+                        params.${column.javaField} = (params.${column.javaField} as string[]).join(',');
+                      }
+                    #end
+                #end
+            #end
+          params.${subJoinColumn.javaField} = props.${subJoinColumn.javaField};
+          const data = await get${subSimpleClassName}Page(params)
+          list.value = data.list
+          total.value = data.total
+        #else
+            #if ($subTable.subJoinMany) ## 一对多
+             list.value = await get${subSimpleClassName}ListBy${SubJoinColumnName}(props.${subJoinColumn.javaField}!);
+            #else
+             list.value = [await get${subSimpleClassName}By${SubJoinColumnName}(props.${subJoinColumn.javaField}!)];
+            #end
+        #end
+    } finally {
+      loading.value = false
+    }
+  }
 
   /** 监听主表的关联字段的变化,加载对应的子表数据 */
   watch(
@@ -162,23 +160,231 @@ const onRefresh = async ()=> {
           return;
         }
         await nextTick();
-        await onRefresh()
+        await getList()
       },
       { immediate: true },
   );
+
+#if ($table.templateType == 11) ## erp
+/** 隐藏搜索栏 */
+const hiddenSearchBar = ref(false);
+const tableToolbarRef = ref<InstanceType<typeof TableToolbar>>();
+const tableRef = ref<VxeTableInstance>();
+
+/** 初始化 */
+onMounted(async () => {
+  await getList();
+  await nextTick();
+  // 挂载 toolbar 工具栏
+  const table = tableRef.value;
+  const tableToolbar = tableToolbarRef.value;
+  if (table && tableToolbar) {
+    await table.connect(tableToolbar.getToolbarRef()!);
+  }
+});
+#end
 </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>
+      <FormModal @success="getList" />
+      <div class="h-[600px]">
+        <ContentWrap v-if="!hiddenSearchBar">
+          <!-- 搜索工作栏 -->
+          <Form
+              :model="queryParams"
+              ref="queryFormRef"
+              layout="inline"
+          >
+              #foreach($column in $subColumns)
+                  #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-full"
+                          />
+                        </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-full"
+                          >
+                              #if ("" != $dictType)## 设置了 dictType 数据字典的情况
+                                <Select.Option
+                                    v-for="dict in getDictOptions(DICT_TYPE.$dictType.toUpperCase(), '$dictMethod')"
+                                    :key="dict.value"
+                                    :value="dict.value"
+                                >
+                                  {{ dict.label }}
+                                </Select.Option>
+                              #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-full"
+                              />
+                            </Form.Item>
+                          #else## 范围
+                            <Form.Item label="${comment}" name="${javaField}">
+                              <RangePicker
+                                  v-model:value="queryParams.${javaField}"
+                                  v-bind="getRangePickerDefaultProps()"
+                                  class="w-full"
+                              />
+                            </Form.Item>
+                          #end
+                      #end
+                  #end
+              #end
+            <Form.Item>
+              <Button class="ml-2" @click="resetQuery"> 重置 </Button>
+              <Button class="ml-2" @click="handleQuery" type="primary">
+                搜索
+              </Button>
+            </Form.Item>
+          </Form>
+        </ContentWrap>
+
+        <!-- 列表 -->
+        <ContentWrap title="${table.classComment}">
+          <template #extra>
+            <TableToolbar
+                ref="tableToolbarRef"
+                v-model:hidden-search="hiddenSearchBar"
+            >
+              <Button
+                  class="ml-2"
+                  :icon="h(Plus)"
+                  type="primary"
+                  @click="onCreate"
+                  v-access:code="['${permissionPrefix}:create']"
+              >
+                {{ $t('ui.actionTitle.create', ['${table.classComment}']) }}
+              </Button>
+            </TableToolbar>
+          </template>
+          <vxe-table
+              ref="tableRef"
+              :data="list"
+              show-overflow
+              :loading="loading"
+          >
+              #foreach($column in $subColumns)
+                  #if ($column.listOperationResult)
+                      #set ($dictType=$column.dictType)
+                      #set ($javaField = $column.javaField)
+                      #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>
+                      #elseif ($table.templateType == 2 && $javaField == $treeNameColumn.javaField)
+                        <vxe-column field="${javaField}" title="${comment}" align="center"  tree-node/>
+                      #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"
+                    danger
+                    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>
+      </div>
     #else
-      <Grid table-title="${subTable.classComment}列表" />
+    <ContentWrap title="${subTable.classComment}列表">
+      <vxe-table
+          :data="list"
+          show-overflow
+          :loading="loading"
+      >
+          #foreach($column in $subColumns)
+              #if ($column.listOperationResult)
+                  #set ($dictType=$column.dictType)
+                  #set ($javaField = $column.javaField)
+                  #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-table>
+    </ContentWrap>
     #end
 </template>

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

@@ -1,4 +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")
+#parse("codegen/vue3_vben5_antd/general/views/modules/list_sub_erp.vue.vm")

+ 3 - 2
yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3_vben5_antd/schema/api/api.ts.vm

@@ -1,4 +1,5 @@
 import type { PageParam, PageResult } from '@vben/request';
+import type { Dayjs } from 'dayjs';
 
 import { requestClient } from '#/api/request';
 #set ($baseURL = "/${table.moduleName}/${simpleClassName_strikeCase}")
@@ -16,7 +17,7 @@ export namespace ${simpleClassName}Api {
         #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}
+            ${column.javaField}#if($column.updateOperation && !$column.primaryKey && !$column.nullable)?#end: string | Dayjs; // ${column.columnComment}
         #else
             ${column.javaField}#if($column.updateOperation && !$column.primaryKey && !$column.nullable)?#end: ${column.javaType.toLowerCase()}; // ${column.columnComment}
         #end
@@ -32,7 +33,7 @@ export namespace ${simpleClassName}Api {
 #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}
+    ${column.javaField}#if($column.updateOperation && !$column.primaryKey && !$column.nullable)?#end: string | Dayjs; // ${column.columnComment}
 #else
     ${column.javaField}#if($column.updateOperation && !$column.primaryKey && !$column.nullable)?#end: ${column.javaType.toLowerCase()}; // ${column.columnComment}
 #end

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

@@ -21,24 +21,11 @@ import { useFormSchema } from '../data';
 
 const emit = defineEmits(['success']);
 const formData = ref<${simpleClassName}Api.${simpleClassName}>();
-#if (${table.templateType} == 2)## 树表特有:父ID处理
-const parentId = ref<number>(); // 新增下级时的父级 ID
-
-const getTitle = computed(() => {
-  if (formData.value?.id) {
-    return $t('ui.actionTitle.edit', ['${table.classComment}']);
-  }
-  return parentId.value
-    ? $t('ui.actionTitle.create', ['下级${table.classComment}'])
-    : $t('ui.actionTitle.create', ['${table.classComment}']);
-});
-#else## 标准表标题
 const getTitle = computed(() => {
   return formData.value?.id
     ? $t('ui.actionTitle.edit', ['${table.classComment}'])
     : $t('ui.actionTitle.create', ['${table.classComment}']);
 });
-#end
 
 ## 特殊:主子表专属逻辑
 #if ( $table.templateType == 10 || $table.templateType == 12 )
@@ -128,7 +115,6 @@ const [Modal, modalApi] = useVbenModal({
       formData.value = undefined;
       return;
     }
-
     // 加载数据
     let data = modalApi.getData<${simpleClassName}Api.${simpleClassName}>();
     if (!data) {

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

@@ -13,6 +13,7 @@
 #if ($subTable.subJoinMany) ## 一对多
 import { Plus } from "@vben/icons";
 import { Button, Tabs, Checkbox, Input, Textarea, Select,RadioGroup,CheckboxGroup, DatePicker } from 'ant-design-vue';
+import { ImageUpload, FileUpload } from "#/components/upload";
 import type { OnActionClickParams } from '#/adapter/vxe-table';
 import { useVbenVxeGrid } from '#/adapter/vxe-table';
 import { use${subSimpleClassName}GridEditColumns } from '../data';
@@ -145,15 +146,11 @@ watch(
                 </template>
               #elseif($column.htmlType == "imageUpload")## 图片上传
                 <template #${javaField}="{ row }">
-                  <UploadImg v-model:value="row.${javaField}" />
+                  <ImageUpload 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}" />
+                  <FileUpload v-model:value="row.${javaField}" />
                 </template>
               #elseif($column.htmlType == "select")## 下拉框
                 <template #${javaField}="{ row, column }">
@@ -180,7 +177,7 @@ watch(
                       valueFormat='x'
                   />
                 </template>
-              #elseif($column.htmlType == "textarea")## 文本框
+              #elseif($column.htmlType == "textarea" || $column.htmlType == "editor")## 文本框
                 <template #${javaField}="{ row }">
                   <Textarea v-model:value="row.${javaField}" />
                 </template>

+ 1 - 1
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/point/PointActivityServiceImpl.java

@@ -300,7 +300,7 @@ public class PointActivityServiceImpl implements PointActivityService {
             throw exception(POINT_ACTIVITY_JOIN_ACTIVITY_SINGLE_LIMIT_COUNT_EXCEED);
         }
         // 2.2 校验库存是否充足
-        if (count >= product.getStock()) {
+        if (count > product.getStock()) {
             throw exception(POINT_ACTIVITY_UPDATE_STOCK_FAIL);
         }
         return BeanUtils.toBean(product, PointValidateJoinRespDTO.class);

+ 1 - 1
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/seckill/SeckillActivityServiceImpl.java

@@ -317,7 +317,7 @@ public class SeckillActivityServiceImpl implements SeckillActivityService {
             throw exception(SECKILL_JOIN_ACTIVITY_PRODUCT_NOT_EXISTS);
         }
         // 2.2 校验库存是否充足
-        if (count >= product.getStock()) {
+        if (count > product.getStock()) {
             throw exception(SECKILL_ACTIVITY_UPDATE_STOCK_FAIL);
         }
         return SeckillActivityConvert.INSTANCE.convert02(activity, product);

+ 3 - 3
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/framework/delivery/core/enums/ExpressClientEnum.java

@@ -12,9 +12,9 @@ import lombok.Getter;
 @AllArgsConstructor
 public enum ExpressClientEnum {
 
-    NOT_PROVIDE("not-provide","未提供"),
-    KD_NIAO("kd-niao", "快递鸟"),
-    KD_100("kd-100", "快递100");
+    NOT_PROVIDE("not_provide","未提供"),
+    KD_NIAO("kd_niao", "快递鸟"),
+    KD_100("kd_100", "快递100");
 
     /**
      * 快递服务商唯一编码

+ 23 - 20
yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/open/MpOpenController.java

@@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.mp.controller.admin.open;
 
 import cn.hutool.core.lang.Assert;
 import cn.hutool.core.util.StrUtil;
+import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore;
 import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils;
 import cn.iocoder.yudao.module.mp.controller.admin.open.vo.MpOpenCheckSignatureReqVO;
 import cn.iocoder.yudao.module.mp.controller.admin.open.vo.MpOpenHandleMessageReqVO;
@@ -35,26 +36,6 @@ public class MpOpenController {
     @Resource
     private MpAccountService mpAccountService;
 
-    /**
-     * 接收微信公众号的校验签名
-     *
-     * 对应 <a href="https://developers.weixin.qq.com/doc/offiaccount/Basic_Information/Access_Overview.html">文档</a>
-     */
-    @Operation(summary = "校验签名") // 参见
-    @GetMapping(value = "/{appId}", produces = "text/plain;charset=utf-8")
-    public String checkSignature(@PathVariable("appId") String appId,
-                                 MpOpenCheckSignatureReqVO reqVO) {
-        log.info("[checkSignature][appId({}) 接收到来自微信服务器的认证消息({})]", appId, reqVO);
-        // 校验请求签名
-        WxMpService wxMpService = mpServiceFactory.getRequiredMpService(appId);
-        // 校验通过
-        if (wxMpService.checkSignature(reqVO.getTimestamp(), reqVO.getNonce(), reqVO.getSignature())) {
-            return reqVO.getEchostr();
-        }
-        // 校验不通过
-        return "非法请求";
-    }
-
     /**
      * 接收微信公众号的消息推送
      *
@@ -62,6 +43,7 @@ public class MpOpenController {
      */
     @Operation(summary = "处理消息")
     @PostMapping(value = "/{appId}", produces = "application/xml; charset=UTF-8")
+    @TenantIgnore
     public String handleMessage(@PathVariable("appId") String appId,
                                 @RequestBody String content,
                                 MpOpenHandleMessageReqVO reqVO) {
@@ -79,6 +61,27 @@ public class MpOpenController {
         }
     }
 
+    /**
+     * 接收微信公众号的校验签名
+     *
+     * 对应 <a href="https://developers.weixin.qq.com/doc/offiaccount/Basic_Information/Access_Overview.html">文档</a>
+     */
+    @Operation(summary = "校验签名") // 参见
+    @GetMapping(value = "/{appId}", produces = "text/plain;charset=utf-8")
+    @TenantIgnore
+    public String checkSignature(@PathVariable("appId") String appId,
+                                 MpOpenCheckSignatureReqVO reqVO) {
+        log.info("[checkSignature][appId({}) 接收到来自微信服务器的认证消息({})]", appId, reqVO);
+        // 校验请求签名
+        WxMpService wxMpService = mpServiceFactory.getRequiredMpService(appId);
+        // 校验通过
+        if (wxMpService.checkSignature(reqVO.getTimestamp(), reqVO.getNonce(), reqVO.getSignature())) {
+            return reqVO.getEchostr();
+        }
+        // 校验不通过
+        return "非法请求";
+    }
+
     private String handleMessage0(String appId, String content, MpOpenHandleMessageReqVO reqVO) {
         // 校验请求签名
         WxMpService mppService = mpServiceFactory.getRequiredMpService(appId);

+ 1 - 1
yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/dal/mysql/user/MpUserMapper.java

@@ -21,7 +21,7 @@ public interface MpUserMapper extends BaseMapperX<MpUserDO> {
     }
 
     default MpUserDO selectByAppIdAndOpenid(String appId, String openid) {
-        return selectOne(MpUserDO::getAppId, appId,
+        return selectFirstOne(MpUserDO::getAppId, appId,
                 MpUserDO::getOpenid, openid);
     }
 

+ 0 - 3
yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/handler/menu/MenuHandler.java

@@ -3,7 +3,6 @@ package cn.iocoder.yudao.module.mp.service.handler.menu;
 import cn.iocoder.yudao.module.mp.framework.mp.core.context.MpContextHolder;
 import cn.iocoder.yudao.module.mp.service.menu.MpMenuService;
 import me.chanjar.weixin.common.session.WxSessionManager;
-import me.chanjar.weixin.mp.api.WxMpMenuService;
 import me.chanjar.weixin.mp.api.WxMpMessageHandler;
 import me.chanjar.weixin.mp.api.WxMpService;
 import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage;
@@ -13,8 +12,6 @@ import org.springframework.stereotype.Component;
 import javax.annotation.Resource;
 import java.util.Map;
 
-import static me.chanjar.weixin.common.api.WxConsts.MenuButtonType;
-
 /**
  * 自定义菜单的事件处理器
  *

+ 1 - 1
yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/handler/message/MessageReceiveHandler.java

@@ -29,7 +29,7 @@ public class MessageReceiveHandler implements WxMpMessageHandler {
     public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage, Map<String, Object> context,
                                     WxMpService wxMpService, WxSessionManager sessionManager) {
         log.info("[handle][接收到请求消息,内容:{}]", wxMessage);
-        mpMessageService.receiveMessage(MpContextHolder.getAppId(), wxMessage);
+        mpMessageService.receiveMessage(wxMpService, MpContextHolder.getAppId(), wxMessage);
         return null;
     }
 

+ 2 - 1
yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/message/MpMessageService.java

@@ -5,6 +5,7 @@ import cn.iocoder.yudao.module.mp.controller.admin.message.vo.message.MpMessageP
 import cn.iocoder.yudao.module.mp.controller.admin.message.vo.message.MpMessageSendReqVO;
 import cn.iocoder.yudao.module.mp.dal.dataobject.message.MpMessageDO;
 import cn.iocoder.yudao.module.mp.service.message.bo.MpMessageSendOutReqBO;
+import me.chanjar.weixin.mp.api.WxMpService;
 import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage;
 import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage;
 
@@ -31,7 +32,7 @@ public interface MpMessageService {
      * @param appId 微信公众号 appId
      * @param wxMessage 消息
      */
-    void receiveMessage(String appId, WxMpXmlMessage wxMessage);
+    void receiveMessage(WxMpService weixinService, String appId, WxMpXmlMessage wxMessage);
 
     /**
      * 使用公众号,给粉丝回复消息

+ 26 - 10
yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/message/MpMessageServiceImpl.java

@@ -1,7 +1,7 @@
 package cn.iocoder.yudao.module.mp.service.message;
 
 import cn.hutool.core.lang.Assert;
-import cn.hutool.core.util.ObjUtil;
+import cn.hutool.core.thread.ThreadUtil;
 import cn.hutool.core.util.StrUtil;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.module.mp.controller.admin.message.vo.message.MpMessagePageReqVO;
@@ -18,6 +18,9 @@ import cn.iocoder.yudao.module.mp.service.account.MpAccountService;
 import cn.iocoder.yudao.module.mp.service.material.MpMaterialService;
 import cn.iocoder.yudao.module.mp.service.message.bo.MpMessageSendOutReqBO;
 import cn.iocoder.yudao.module.mp.service.user.MpUserService;
+import jakarta.annotation.Resource;
+import jakarta.validation.Validator;
+import lombok.SneakyThrows;
 import lombok.extern.slf4j.Slf4j;
 import me.chanjar.weixin.common.api.WxConsts;
 import me.chanjar.weixin.common.error.WxErrorException;
@@ -25,12 +28,12 @@ import me.chanjar.weixin.mp.api.WxMpService;
 import me.chanjar.weixin.mp.bean.kefu.WxMpKefuMessage;
 import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage;
 import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage;
+import me.chanjar.weixin.mp.bean.result.WxMpUser;
 import org.springframework.context.annotation.Lazy;
 import org.springframework.stereotype.Service;
 import org.springframework.validation.annotation.Validated;
 
-import javax.annotation.Resource;
-import javax.validation.Validator;
+import java.util.concurrent.TimeUnit;
 
 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
 import static cn.iocoder.yudao.module.mp.enums.ErrorCodeConstants.MESSAGE_SEND_FAIL;
@@ -69,18 +72,31 @@ public class MpMessageServiceImpl implements MpMessageService {
     }
 
     @Override
-    public void receiveMessage(String appId, WxMpXmlMessage wxMessage) {
+    @SneakyThrows
+    public void receiveMessage(WxMpService weixinService, String appId, WxMpXmlMessage wxMessage) {
         // 获得关联信息
         MpAccountDO account = mpAccountService.getAccountFromCache(appId);
         Assert.notNull(account, "公众号账号({}) 不存在", appId);
 
-        // 订阅事件不记录,因为此时公众号粉丝表中还没有此粉丝的数据
-        // TODO @芋艿:这个修复,后续看看还有啥问题
-        if (ObjUtil.equal(wxMessage.getEvent(), WxConsts.EventType.SUBSCRIBE)) {
-            return;
-        }
-
+        // 获取用户
         MpUserDO user = mpUserService.getUser(appId, wxMessage.getFromUser());
+        if (user == null) {
+            // 特殊情况:因为 receiveMessage 是异步记录,可能 SubscribeHandler 还没存储好 User,此时 sleep 轮询
+            for (int i = 0; i < 3; i++) {
+                ThreadUtil.sleep(5, TimeUnit.SECONDS);
+                user = mpUserService.getUser(appId, wxMessage.getFromUser());
+                if (user != null) {
+                    break;
+                }
+                log.warn("[receiveMessage][粉丝({}/{}) 不存在,第 {} 次重试失败]", appId, wxMessage.getFromUser(), i + 1);
+            }
+        }
+        // 特殊情况:可能 SubscribeHandler 没处理正确(例如说发生异常),则主动创建
+        if (user == null) {
+            log.warn("[receiveMessage][粉丝({}/{}) 不存在,主动创建]", appId, wxMessage.getFromUser());
+            WxMpUser wxMpUser = weixinService.getUserService().userInfo(wxMessage.getFromUser());
+            user = mpUserService.saveUser(appId, wxMpUser);
+        }
         Assert.notNull(user, "公众号粉丝({}/{}) 不存在", appId, wxMessage.getFromUser());
 
         // 记录消息

+ 4 - 0
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/notify/PayNotifyController.java

@@ -7,6 +7,7 @@ import cn.iocoder.yudao.framework.pay.core.client.PayClient;
 import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderRespDTO;
 import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundRespDTO;
 import cn.iocoder.yudao.framework.pay.core.client.dto.transfer.PayTransferRespDTO;
+import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore;
 import cn.iocoder.yudao.module.pay.controller.admin.notify.vo.PayNotifyTaskDetailRespVO;
 import cn.iocoder.yudao.module.pay.controller.admin.notify.vo.PayNotifyTaskPageReqVO;
 import cn.iocoder.yudao.module.pay.controller.admin.notify.vo.PayNotifyTaskRespVO;
@@ -62,6 +63,7 @@ public class PayNotifyController {
     @PostMapping(value = "/order/{channelId}")
     @Operation(summary = "支付渠道的统一【支付】回调")
     @PermitAll
+    @TenantIgnore
     public String notifyOrder(@PathVariable("channelId") Long channelId,
                               @RequestParam(required = false) Map<String, String> params,
                               @RequestBody(required = false) String body) {
@@ -82,6 +84,7 @@ public class PayNotifyController {
     @PostMapping(value = "/refund/{channelId}")
     @Operation(summary = "支付渠道的统一【退款】回调")
     @PermitAll
+    @TenantIgnore
     public String notifyRefund(@PathVariable("channelId") Long channelId,
                                @RequestParam(required = false) Map<String, String> params,
                                @RequestBody(required = false) String body) {
@@ -102,6 +105,7 @@ public class PayNotifyController {
     @PostMapping(value = "/transfer/{channelId}")
     @Operation(summary = "支付渠道的统一【转账】回调")
     @PermitAll
+    @TenantIgnore
     public String notifyTransfer(@PathVariable("channelId") Long channelId,
                                  @RequestParam(required = false) Map<String, String> params,
                                  @RequestBody(required = false) String body) {

+ 2 - 2
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/demo/PayDemoOrderServiceImpl.java

@@ -1,6 +1,7 @@
 package cn.iocoder.yudao.module.pay.service.demo;
 
 import cn.hutool.core.lang.Assert;
+import cn.hutool.core.util.ObjUtil;
 import cn.hutool.core.util.ObjectUtil;
 import cn.iocoder.yudao.framework.common.pojo.PageParam;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
@@ -24,7 +25,6 @@ import java.time.Duration;
 import java.time.LocalDateTime;
 import java.util.HashMap;
 import java.util.Map;
-import java.util.Objects;
 
 import static cn.hutool.core.util.ObjectUtil.notEqual;
 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
@@ -231,7 +231,7 @@ public class PayDemoOrderServiceImpl implements PayDemoOrderService {
             throw exception(DEMO_ORDER_NOT_FOUND);
         }
         // 1.2 校验退款订单匹配
-        if (Objects.equals(order.getPayRefundId(), payRefundId)) {
+        if (ObjUtil.notEqual(order.getPayRefundId(), payRefundId)) {
             log.error("[validateDemoOrderCanRefunded][order({}) 退款单不匹配({}),请进行处理!order 数据是:{}]",
                     id, payRefundId, toJsonString(order));
             throw exception(DEMO_ORDER_REFUND_FAIL_REFUND_ORDER_ID_ERROR);

+ 3 - 0
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/captcha/CaptchaController.java

@@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.system.controller.admin.captcha;
 
 import cn.hutool.core.util.StrUtil;
 import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
+import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore;
 import com.anji.captcha.model.common.ResponseModel;
 import com.anji.captcha.model.vo.CaptchaVO;
 import com.anji.captcha.service.CaptchaService;
@@ -27,6 +28,7 @@ public class CaptchaController {
     @PostMapping({"/get"})
     @Operation(summary = "获得验证码")
     @PermitAll
+    @TenantIgnore
     public ResponseModel get(@RequestBody CaptchaVO data, HttpServletRequest request) {
         assert request.getRemoteHost() != null;
         data.setBrowserInfo(getRemoteId(request));
@@ -36,6 +38,7 @@ public class CaptchaController {
     @PostMapping("/check")
     @Operation(summary = "校验验证码")
     @PermitAll
+    @TenantIgnore
     public ResponseModel check(@RequestBody CaptchaVO data, HttpServletRequest request) {
         data.setBrowserInfo(getRemoteId(request));
         return captchaService.check(data);

+ 5 - 0
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/sms/SmsCallbackController.java

@@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.system.controller.admin.sms;
 
 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
 import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
+import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore;
 import cn.iocoder.yudao.module.system.framework.sms.core.enums.SmsChannelEnum;
 import cn.iocoder.yudao.module.system.service.sms.SmsSendService;
 import io.swagger.v3.oas.annotations.Operation;
@@ -24,6 +25,7 @@ public class SmsCallbackController {
 
     @PostMapping("/aliyun")
     @PermitAll
+    @TenantIgnore
     @Operation(summary = "阿里云短信的回调", description = "参见 https://help.aliyun.com/document_detail/120998.html 文档")
     public CommonResult<Boolean> receiveAliyunSmsStatus(HttpServletRequest request) throws Throwable {
         String text = ServletUtils.getBody(request);
@@ -33,6 +35,7 @@ public class SmsCallbackController {
 
     @PostMapping("/tencent")
     @PermitAll
+    @TenantIgnore
     @Operation(summary = "腾讯云短信的回调", description = "参见 https://cloud.tencent.com/document/product/382/52077 文档")
     public CommonResult<Boolean> receiveTencentSmsStatus(HttpServletRequest request) throws Throwable {
         String text = ServletUtils.getBody(request);
@@ -43,6 +46,7 @@ public class SmsCallbackController {
 
     @PostMapping("/huawei")
     @PermitAll
+    @TenantIgnore
     @Operation(summary = "华为云短信的回调", description = "参见 https://support.huaweicloud.com/api-msgsms/sms_05_0003.html 文档")
     public CommonResult<Boolean> receiveHuaweiSmsStatus(@RequestBody String requestBody) throws Throwable {
         smsSendService.receiveSmsStatus(SmsChannelEnum.HUAWEI.getCode(), requestBody);
@@ -51,6 +55,7 @@ public class SmsCallbackController {
 
     @PostMapping("/qiniu")
     @PermitAll
+    @TenantIgnore
     @Operation(summary = "七牛云短信的回调", description = "参见 https://developer.qiniu.com/sms/5910/message-push 文档")
     public CommonResult<Boolean> receiveQiniuSmsStatus(@RequestBody String requestBody) throws Throwable {
         smsSendService.receiveSmsStatus(SmsChannelEnum.QINIU.getCode(), requestBody);

+ 4 - 0
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/tenant/TenantController.java

@@ -7,6 +7,7 @@ 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.framework.excel.core.util.ExcelUtils;
+import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore;
 import cn.iocoder.yudao.module.system.controller.admin.tenant.vo.tenant.TenantPageReqVO;
 import cn.iocoder.yudao.module.system.controller.admin.tenant.vo.tenant.TenantRespVO;
 import cn.iocoder.yudao.module.system.controller.admin.tenant.vo.tenant.TenantSaveReqVO;
@@ -39,6 +40,7 @@ public class TenantController {
 
     @GetMapping("/get-id-by-name")
     @PermitAll
+    @TenantIgnore
     @Operation(summary = "使用租户名,获得租户编号", description = "登录界面,根据用户的租户名,获得租户编号")
     @Parameter(name = "name", description = "租户名", required = true, example = "1024")
     public CommonResult<Long> getTenantIdByName(@RequestParam("name") String name) {
@@ -48,6 +50,7 @@ public class TenantController {
 
     @GetMapping({ "simple-list" })
     @PermitAll
+    @TenantIgnore
     @Operation(summary = "获取租户精简信息列表", description = "只包含被开启的租户,用于【首页】功能的选择租户选项")
     public CommonResult<List<TenantRespVO>> getTenantSimpleList() {
         List<TenantDO> list = tenantService.getTenantListByStatus(CommonStatusEnum.ENABLE.getStatus());
@@ -57,6 +60,7 @@ public class TenantController {
 
     @GetMapping("/get-by-website")
     @PermitAll
+    @TenantIgnore
     @Operation(summary = "使用域名,获得租户信息", description = "登录界面,根据用户的域名,获得租户信息")
     @Parameter(name = "website", description = "域名", required = true, example = "www.iocoder.cn")
     public CommonResult<TenantRespVO> getTenantByWebsite(@RequestParam("website") String website) {

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

@@ -4,6 +4,7 @@ server:
 --- #################### 数据库相关配置 ####################
 spring:
   autoconfigure:
+    # noinspection SpringBootApplicationYaml
     exclude:
       - com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure # 排除 Druid 的自动配置,使用 dynamic-datasource-spring-boot-starter 配置多数据源
       - org.springframework.boot.autoconfigure.quartz.QuartzAutoConfiguration # 默认 local 环境,不开启 Quartz 的自动配置

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

@@ -273,16 +273,7 @@ yudao:
   tenant: # 多租户相关配置项
     enable: true
     ignore-urls:
-      - /admin-api/system/tenant/get-id-by-name # 基于名字获取租户,不许带租户编号
-      - /admin-api/system/tenant/get-by-website # 基于域名获取租户,不许带租户编号
-      - /admin-api/system/tenant/simple-list # 获取租户列表,不许带租户编号
-      - /admin-api/system/captcha/get # 获取图片验证码,和租户无关
-      - /admin-api/system/captcha/check # 校验图片验证码,和租户无关
-      - /admin-api/infra/file/*/get/** # 获取图片,和租户无关
-      - /admin-api/system/sms/callback/* # 短信回调接口,无法带上租户编号
-      - /admin-api/pay/notify/** # 支付回调通知,不携带租户编号
       - /jmreport/* # 积木报表,无法携带租户编号
-      - /admin-api/mp/open/** # 微信公众号开放平台,微信回调接口,无法携带租户编号
     ignore-tables:
       - system_tenant
       - system_tenant_package
@@ -349,7 +340,7 @@ yudao:
       receive-expire-time: 14d # 收货的过期时间
       comment-expire-time: 7d # 评论的过期时间
     express:
-      client: KD_NIAO
+      client: kd_100
       kd-niao:
         api-key: cb022f1e-48f1-4c4a-a723-9001ac9676b8
         business-id: 1809751