Commit 5f8d9ac8288360e637d86b95d407aa36428e016f
Merge branch 'master-jdk17' of https://gitee.com/zhijiantianya/ruoyi-vue-pro into feature/hrm
Showing
212 changed files
with
13327 additions
and
1003 deletions
Too many changes.
To preserve performance only 100 of 212 files are displayed.
script/livekit-poc/README.md
| @@ -15,7 +15,7 @@ bash verify.sh | @@ -15,7 +15,7 @@ bash verify.sh | ||
| 15 | - 7880:HTTP / WebSocket 信令; | 15 | - 7880:HTTP / WebSocket 信令; |
| 16 | - 7881:WebRTC TCP fallback; | 16 | - 7881:WebRTC TCP fallback; |
| 17 | - 7882/UDP:WebRTC 媒体; | 17 | - 7882/UDP:WebRTC 媒体; |
| 18 | -- macOS / Windows:当前 `docker-compose.yml` 走端口映射模式,webhook URL 用 `host.docker.internal:48080` 让容器访问到宿主机 yudao 后端; | 18 | +- macOS / Windows:当前 `docker-compose.yml` 走端口映射模式,`rtc.node_ip` 使用 `127.0.0.1` 让浏览器访问映射后的媒体端口,webhook URL 用 `host.docker.internal:48080` 让容器访问到宿主机 yudao 后端; |
| 19 | - macOS 上 host network(`network_mode: host`)需要 Docker Desktop 4.34+ 并在 Settings → Resources → Network 勾选「Enable host networking」,老版本静默失败(容器跑得起来但端口完全不通); | 19 | - macOS 上 host network(`network_mode: host`)需要 Docker Desktop 4.34+ 并在 Settings → Resources → Network 勾选「Enable host networking」,老版本静默失败(容器跑得起来但端口完全不通); |
| 20 | - Linux:可以把 `docker-compose.yml` 改成 `network_mode: host` + 删 `ports:` 段,并把 `livekit.yaml` 的 webhook URL 改为 `http://127.0.0.1:48080/admin-api/im/livekit/webhook`。 | 20 | - Linux:可以把 `docker-compose.yml` 改成 `network_mode: host` + 删 `ports:` 段,并把 `livekit.yaml` 的 webhook URL 改为 `http://127.0.0.1:48080/admin-api/im/livekit/webhook`。 |
| 21 | 21 |
script/livekit-poc/livekit.yaml
| @@ -10,6 +10,8 @@ rtc: | @@ -10,6 +10,8 @@ rtc: | ||
| 10 | tcp_port: 7881 | 10 | tcp_port: 7881 |
| 11 | udp_port: 7882 | 11 | udp_port: 7882 |
| 12 | use_external_ip: false | 12 | use_external_ip: false |
| 13 | + # macOS / Windows 端口映射模式下显式广播宿主机回环地址,避免把容器 172.x 地址发给浏览器 | ||
| 14 | + node_ip: 127.0.0.1 | ||
| 13 | 15 | ||
| 14 | # Webhook:成员离开 / 房间结束等事件回调到 yudao 后端做业务态兜底清理 | 16 | # Webhook:成员离开 / 房间结束等事件回调到 yudao 后端做业务态兜底清理 |
| 15 | # host.docker.internal 让容器访问宿主机 macOS / Windows 上的 yudao 后端 | 17 | # host.docker.internal 让容器访问宿主机 macOS / Windows 上的 yudao 后端 |
yudao-dependencies/pom.xml
| @@ -14,7 +14,7 @@ | @@ -14,7 +14,7 @@ | ||
| 14 | <url>https://github.com/YunaiV/ruoyi-vue-pro</url> | 14 | <url>https://github.com/YunaiV/ruoyi-vue-pro</url> |
| 15 | 15 | ||
| 16 | <properties> | 16 | <properties> |
| 17 | - <revision>2026.06-SNAPSHOT</revision> | 17 | + <revision>2026.07-SNAPSHOT</revision> |
| 18 | <flatten-maven-plugin.version>1.7.2</flatten-maven-plugin.version> | 18 | <flatten-maven-plugin.version>1.7.2</flatten-maven-plugin.version> |
| 19 | <!-- 统一依赖管理 --> | 19 | <!-- 统一依赖管理 --> |
| 20 | <spring.boot.version>3.5.15</spring.boot.version> | 20 | <spring.boot.version>3.5.15</spring.boot.version> |
| @@ -59,8 +59,7 @@ | @@ -59,8 +59,7 @@ | ||
| 59 | <fastexcel.version>1.3.0</fastexcel.version> | 59 | <fastexcel.version>1.3.0</fastexcel.version> |
| 60 | <aviator.version>5.3.3</aviator.version> | 60 | <aviator.version>5.3.3</aviator.version> |
| 61 | <velocity.version>2.4.1</velocity.version> | 61 | <velocity.version>2.4.1</velocity.version> |
| 62 | - <fastjson.version>1.2.83</fastjson.version> | ||
| 63 | - <fastjson2.version>2.0.62</fastjson2.version> | 62 | + <fastjson2.version>2.0.63</fastjson2.version> |
| 64 | <guava.version>33.6.0-jre</guava.version> | 63 | <guava.version>33.6.0-jre</guava.version> |
| 65 | <transmittable-thread-local.version>2.14.5</transmittable-thread-local.version> | 64 | <transmittable-thread-local.version>2.14.5</transmittable-thread-local.version> |
| 66 | <commons-net.version>3.13.0</commons-net.version> | 65 | <commons-net.version>3.13.0</commons-net.version> |
| @@ -244,6 +243,12 @@ | @@ -244,6 +243,12 @@ | ||
| 244 | <groupId>org.dromara</groupId> | 243 | <groupId>org.dromara</groupId> |
| 245 | <artifactId>easy-trans-anno</artifactId> | 244 | <artifactId>easy-trans-anno</artifactId> |
| 246 | <version>${easy-trans.version}</version> | 245 | <version>${easy-trans.version}</version> |
| 246 | + <exclusions> | ||
| 247 | + <exclusion> | ||
| 248 | + <groupId>com.alibaba</groupId> | ||
| 249 | + <artifactId>fastjson</artifactId> | ||
| 250 | + </exclusion> | ||
| 251 | + </exclusions> | ||
| 247 | </dependency> | 252 | </dependency> |
| 248 | 253 | ||
| 249 | <dependency> | 254 | <dependency> |
| @@ -315,6 +320,12 @@ | @@ -315,6 +320,12 @@ | ||
| 315 | <groupId>org.apache.rocketmq</groupId> | 320 | <groupId>org.apache.rocketmq</groupId> |
| 316 | <artifactId>rocketmq-spring-boot-starter</artifactId> | 321 | <artifactId>rocketmq-spring-boot-starter</artifactId> |
| 317 | <version>${rocketmq-spring.version}</version> | 322 | <version>${rocketmq-spring.version}</version> |
| 323 | + <exclusions> | ||
| 324 | + <exclusion> | ||
| 325 | + <groupId>com.alibaba</groupId> | ||
| 326 | + <artifactId>fastjson</artifactId> | ||
| 327 | + </exclusion> | ||
| 328 | + </exclusions> | ||
| 318 | </dependency> | 329 | </dependency> |
| 319 | 330 | ||
| 320 | <!-- 服务保障相关 --> | 331 | <!-- 服务保障相关 --> |
| @@ -509,7 +520,7 @@ | @@ -509,7 +520,7 @@ | ||
| 509 | <dependency> | 520 | <dependency> |
| 510 | <groupId>com.alibaba</groupId> | 521 | <groupId>com.alibaba</groupId> |
| 511 | <artifactId>fastjson</artifactId> | 522 | <artifactId>fastjson</artifactId> |
| 512 | - <version>${fastjson.version}</version> | 523 | + <version>${fastjson2.version}</version> <!-- fastjson2 官方提供的 1.x API 兼容实现 --> |
| 513 | </dependency> | 524 | </dependency> |
| 514 | <dependency> | 525 | <dependency> |
| 515 | <groupId>com.alibaba.fastjson2</groupId> | 526 | <groupId>com.alibaba.fastjson2</groupId> |
| @@ -638,6 +649,12 @@ | @@ -638,6 +649,12 @@ | ||
| 638 | <groupId>me.zhyd.oauth</groupId> | 649 | <groupId>me.zhyd.oauth</groupId> |
| 639 | <artifactId>JustAuth</artifactId> <!-- 社交登陆(例如说,个人微信、企业微信等等) --> | 650 | <artifactId>JustAuth</artifactId> <!-- 社交登陆(例如说,个人微信、企业微信等等) --> |
| 640 | <version>${justauth.version}</version> | 651 | <version>${justauth.version}</version> |
| 652 | + <exclusions> | ||
| 653 | + <exclusion> | ||
| 654 | + <groupId>com.alibaba</groupId> | ||
| 655 | + <artifactId>fastjson</artifactId> | ||
| 656 | + </exclusion> | ||
| 657 | + </exclusions> | ||
| 641 | </dependency> | 658 | </dependency> |
| 642 | <dependency> | 659 | <dependency> |
| 643 | <groupId>com.xkcoding.justauth</groupId> | 660 | <groupId>com.xkcoding.justauth</groupId> |
| @@ -661,6 +678,10 @@ | @@ -661,6 +678,10 @@ | ||
| 661 | <groupId>org.bouncycastle</groupId> | 678 | <groupId>org.bouncycastle</groupId> |
| 662 | <artifactId>bcprov-jdk15on</artifactId> | 679 | <artifactId>bcprov-jdk15on</artifactId> |
| 663 | </exclusion> | 680 | </exclusion> |
| 681 | + <exclusion> | ||
| 682 | + <groupId>com.alibaba</groupId> | ||
| 683 | + <artifactId>fastjson</artifactId> | ||
| 684 | + </exclusion> | ||
| 664 | </exclusions> | 685 | </exclusions> |
| 665 | </dependency> | 686 | </dependency> |
| 666 | 687 | ||
| @@ -703,6 +724,12 @@ | @@ -703,6 +724,12 @@ | ||
| 703 | <groupId>org.jeecgframework.jimureport</groupId> | 724 | <groupId>org.jeecgframework.jimureport</groupId> |
| 704 | <artifactId>jimureport-spring-boot3-starter</artifactId> | 725 | <artifactId>jimureport-spring-boot3-starter</artifactId> |
| 705 | <version>${jimureport.version}</version> | 726 | <version>${jimureport.version}</version> |
| 727 | + <exclusions> | ||
| 728 | + <exclusion> | ||
| 729 | + <groupId>com.alibaba</groupId> | ||
| 730 | + <artifactId>fastjson</artifactId> | ||
| 731 | + </exclusion> | ||
| 732 | + </exclusions> | ||
| 706 | </dependency> | 733 | </dependency> |
| 707 | <dependency> | 734 | <dependency> |
| 708 | <groupId>org.jeecgframework.jimureport</groupId> | 735 | <groupId>org.jeecgframework.jimureport</groupId> |
| @@ -717,6 +744,10 @@ | @@ -717,6 +744,10 @@ | ||
| 717 | <groupId>cn.hutool</groupId> | 744 | <groupId>cn.hutool</groupId> |
| 718 | <artifactId>hutool-core</artifactId> | 745 | <artifactId>hutool-core</artifactId> |
| 719 | </exclusion> | 746 | </exclusion> |
| 747 | + <exclusion> | ||
| 748 | + <groupId>com.alibaba</groupId> | ||
| 749 | + <artifactId>fastjson</artifactId> | ||
| 750 | + </exclusion> | ||
| 720 | </exclusions> | 751 | </exclusions> |
| 721 | </dependency> | 752 | </dependency> |
| 722 | </dependencies> | 753 | </dependencies> |
yudao-framework/yudao-common/pom.xml
| @@ -134,6 +134,11 @@ | @@ -134,6 +134,11 @@ | ||
| 134 | </dependency> | 134 | </dependency> |
| 135 | 135 | ||
| 136 | <dependency> | 136 | <dependency> |
| 137 | + <groupId>com.alibaba</groupId> | ||
| 138 | + <artifactId>fastjson</artifactId> <!-- JustAuth、Jimu 等三方库仍使用 1.x 包名,由项目统一提供 fastjson2 兼容实现 --> | ||
| 139 | + </dependency> | ||
| 140 | + | ||
| 141 | + <dependency> | ||
| 137 | <groupId>org.dromara</groupId> <!-- VO 数据翻译 --> | 142 | <groupId>org.dromara</groupId> <!-- VO 数据翻译 --> |
| 138 | <artifactId>easy-trans-anno</artifactId> <!-- 默认引入的原因,方便 xxx-module-api 包使用 --> | 143 | <artifactId>easy-trans-anno</artifactId> <!-- 默认引入的原因,方便 xxx-module-api 包使用 --> |
| 139 | </dependency> | 144 | </dependency> |
yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/biz/system/oauth2/OAuth2TokenCommonApi.java
| @@ -38,6 +38,15 @@ public interface OAuth2TokenCommonApi { | @@ -38,6 +38,15 @@ public interface OAuth2TokenCommonApi { | ||
| 38 | OAuth2AccessTokenRespDTO removeAccessToken(String accessToken); | 38 | OAuth2AccessTokenRespDTO removeAccessToken(String accessToken); |
| 39 | 39 | ||
| 40 | /** | 40 | /** |
| 41 | + * 移除用户的所有访问令牌 | ||
| 42 | + * 注意:该流程中,会移除相关的刷新令牌 | ||
| 43 | + * | ||
| 44 | + * @param userId 用户编号 | ||
| 45 | + * @param userType 用户类型 | ||
| 46 | + */ | ||
| 47 | + void removeAccessToken(Long userId, Integer userType); | ||
| 48 | + | ||
| 49 | + /** | ||
| 41 | * 刷新访问令牌 | 50 | * 刷新访问令牌 |
| 42 | * | 51 | * |
| 43 | * @param refreshToken 刷新令牌 | 52 | * @param refreshToken 刷新令牌 |
yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/core/handler/GlobalExceptionHandler.java
| @@ -23,6 +23,7 @@ import jakarta.validation.ConstraintViolationException; | @@ -23,6 +23,7 @@ import jakarta.validation.ConstraintViolationException; | ||
| 23 | import jakarta.validation.ValidationException; | 23 | import jakarta.validation.ValidationException; |
| 24 | import lombok.AllArgsConstructor; | 24 | import lombok.AllArgsConstructor; |
| 25 | import lombok.extern.slf4j.Slf4j; | 25 | import lombok.extern.slf4j.Slf4j; |
| 26 | +import org.springframework.core.annotation.Order; | ||
| 26 | import org.springframework.http.converter.HttpMessageNotReadableException; | 27 | import org.springframework.http.converter.HttpMessageNotReadableException; |
| 27 | import org.springframework.security.access.AccessDeniedException; | 28 | import org.springframework.security.access.AccessDeniedException; |
| 28 | import org.springframework.util.Assert; | 29 | import org.springframework.util.Assert; |
| @@ -53,6 +54,7 @@ import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeC | @@ -53,6 +54,7 @@ import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeC | ||
| 53 | * @author 芋道源码 | 54 | * @author 芋道源码 |
| 54 | */ | 55 | */ |
| 55 | @RestControllerAdvice | 56 | @RestControllerAdvice |
| 57 | +@Order(0) // 优先于三方库默认的全局异常处理器,例如 JimuReport | ||
| 56 | @AllArgsConstructor | 58 | @AllArgsConstructor |
| 57 | @Slf4j | 59 | @Slf4j |
| 58 | public class GlobalExceptionHandler { | 60 | public class GlobalExceptionHandler { |
yudao-module-ai/pom.xml
| @@ -19,12 +19,24 @@ | @@ -19,12 +19,24 @@ | ||
| 19 | 国外:OpenAI、Ollama、Midjourney、StableDiffusion、Suno | 19 | 国外:OpenAI、Ollama、Midjourney、StableDiffusion、Suno |
| 20 | </description> | 20 | </description> |
| 21 | <properties> | 21 | <properties> |
| 22 | - <spring-ai.version>1.1.5</spring-ai.version> | 22 | + <spring-ai.version>1.1.8</spring-ai.version> |
| 23 | <!-- https://mvnrepository.com/artifact/com.alibaba.cloud.ai/spring-ai-alibaba --> | 23 | <!-- https://mvnrepository.com/artifact/com.alibaba.cloud.ai/spring-ai-alibaba --> |
| 24 | <alibaba-ai.version>1.1.2.2</alibaba-ai.version> | 24 | <alibaba-ai.version>1.1.2.2</alibaba-ai.version> |
| 25 | <tinyflow.version>1.2.6</tinyflow.version> | 25 | <tinyflow.version>1.2.6</tinyflow.version> |
| 26 | </properties> | 26 | </properties> |
| 27 | 27 | ||
| 28 | + <dependencyManagement> | ||
| 29 | + <dependencies> | ||
| 30 | + <dependency> | ||
| 31 | + <groupId>org.springframework.ai</groupId> | ||
| 32 | + <artifactId>spring-ai-bom</artifactId> | ||
| 33 | + <version>${spring-ai.version}</version> | ||
| 34 | + <type>pom</type> | ||
| 35 | + <scope>import</scope> | ||
| 36 | + </dependency> | ||
| 37 | + </dependencies> | ||
| 38 | + </dependencyManagement> | ||
| 39 | + | ||
| 28 | <dependencies> | 40 | <dependencies> |
| 29 | <dependency> | 41 | <dependency> |
| 30 | <groupId>cn.iocoder.boot</groupId> | 42 | <groupId>cn.iocoder.boot</groupId> |
| @@ -112,37 +124,11 @@ | @@ -112,37 +124,11 @@ | ||
| 112 | <version>${spring-ai.version}</version> | 124 | <version>${spring-ai.version}</version> |
| 113 | </dependency> | 125 | </dependency> |
| 114 | <dependency> | 126 | <dependency> |
| 115 | - <!-- 智谱 GLM --> | ||
| 116 | - <groupId>org.springframework.ai</groupId> | ||
| 117 | - <artifactId>spring-ai-starter-model-zhipuai</artifactId> | ||
| 118 | - <version>${spring-ai.version}</version> | ||
| 119 | - </dependency> | ||
| 120 | - <dependency> | ||
| 121 | - <groupId>org.springframework.ai</groupId> | ||
| 122 | - <artifactId>spring-ai-starter-model-minimax</artifactId> | ||
| 123 | - <version>${spring-ai.version}</version> | ||
| 124 | - </dependency> | ||
| 125 | - | ||
| 126 | - <dependency> | ||
| 127 | <!-- 通义千问 --> | 127 | <!-- 通义千问 --> |
| 128 | <groupId>com.alibaba.cloud.ai</groupId> | 128 | <groupId>com.alibaba.cloud.ai</groupId> |
| 129 | <artifactId>spring-ai-alibaba-starter-dashscope</artifactId> | 129 | <artifactId>spring-ai-alibaba-starter-dashscope</artifactId> |
| 130 | <version>${alibaba-ai.version}</version> | 130 | <version>${alibaba-ai.version}</version> |
| 131 | </dependency> | 131 | </dependency> |
| 132 | - | ||
| 133 | - <dependency> | ||
| 134 | - <!-- 文心一言 --> | ||
| 135 | - <groupId>org.springaicommunity</groupId> | ||
| 136 | - <artifactId>qianfan-spring-boot-starter</artifactId> | ||
| 137 | - <version>1.0.0</version> | ||
| 138 | - </dependency> | ||
| 139 | - <dependency> | ||
| 140 | - <!-- 月之暗面 --> | ||
| 141 | - <groupId>org.springaicommunity</groupId> | ||
| 142 | - <artifactId>moonshot-spring-boot-starter</artifactId> | ||
| 143 | - <version>1.0.0</version> | ||
| 144 | - </dependency> | ||
| 145 | - | ||
| 146 | <!-- 向量存储:https://db-engines.com/en/ranking/vector+dbms --> | 132 | <!-- 向量存储:https://db-engines.com/en/ranking/vector+dbms --> |
| 147 | <dependency> | 133 | <dependency> |
| 148 | <!-- Qdrant:https://qdrant.tech/ --> | 134 | <!-- Qdrant:https://qdrant.tech/ --> |
| @@ -256,8 +242,12 @@ | @@ -256,8 +242,12 @@ | ||
| 256 | <groupId>org.slf4j</groupId> | 242 | <groupId>org.slf4j</groupId> |
| 257 | <artifactId>slf4j-reload4j</artifactId> | 243 | <artifactId>slf4j-reload4j</artifactId> |
| 258 | </exclusion> | 244 | </exclusion> |
| 245 | + <exclusion> | ||
| 246 | + <groupId>com.alibaba</groupId> | ||
| 247 | + <artifactId>fastjson</artifactId> | ||
| 248 | + </exclusion> | ||
| 259 | </exclusions> | 249 | </exclusions> |
| 260 | </dependency> | 250 | </dependency> |
| 261 | </dependencies> | 251 | </dependencies> |
| 262 | 252 | ||
| 263 | -</project> | ||
| 264 | \ No newline at end of file | 253 | \ No newline at end of file |
| 254 | +</project> |
yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/chat/AiChatConversationController.java
| @@ -106,6 +106,20 @@ public class AiChatConversationController { | @@ -106,6 +106,20 @@ public class AiChatConversationController { | ||
| 106 | conversation -> conversation.setMessageCount(messageCountMap.getOrDefault(conversation.getId(), 0)))); | 106 | conversation -> conversation.setMessageCount(messageCountMap.getOrDefault(conversation.getId(), 0)))); |
| 107 | } | 107 | } |
| 108 | 108 | ||
| 109 | + @GetMapping("/get") | ||
| 110 | + @Operation(summary = "获得对话", description = "用于【对话管理】菜单") | ||
| 111 | + @Parameter(name = "id", required = true, description = "对话编号", example = "1024") | ||
| 112 | + @PreAuthorize("@ss.hasPermission('ai:chat-conversation:query')") | ||
| 113 | + @TransMethodResult | ||
| 114 | + public CommonResult<AiChatConversationRespVO> getChatConversation(@RequestParam("id") Long id) { | ||
| 115 | + AiChatConversationDO conversation = chatConversationService.getChatConversation(id); | ||
| 116 | + AiChatConversationRespVO respVO = BeanUtils.toBean(conversation, AiChatConversationRespVO.class); | ||
| 117 | + if (respVO != null) { | ||
| 118 | + respVO.setMessageCount(chatMessageService.getChatMessageCount(id)); | ||
| 119 | + } | ||
| 120 | + return success(respVO); | ||
| 121 | + } | ||
| 122 | + | ||
| 109 | @Operation(summary = "管理员删除对话") | 123 | @Operation(summary = "管理员删除对话") |
| 110 | @DeleteMapping("/delete-by-admin") | 124 | @DeleteMapping("/delete-by-admin") |
| 111 | @Parameter(name = "id", required = true, description = "对话编号", example = "1024") | 125 | @Parameter(name = "id", required = true, description = "对话编号", example = "1024") |
yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/chat/AiChatMessageController.java
| @@ -131,7 +131,7 @@ public class AiChatMessageController { | @@ -131,7 +131,7 @@ public class AiChatMessageController { | ||
| 131 | 131 | ||
| 132 | @GetMapping("/page") | 132 | @GetMapping("/page") |
| 133 | @Operation(summary = "获得消息分页", description = "用于【对话管理】菜单") | 133 | @Operation(summary = "获得消息分页", description = "用于【对话管理】菜单") |
| 134 | - @PreAuthorize("@ss.hasPermission('ai:chat-conversation:query')") | 134 | + @PreAuthorize("@ss.hasPermission('ai:chat-message:query')") |
| 135 | public CommonResult<PageResult<AiChatMessageRespVO>> getChatMessagePage(AiChatMessagePageReqVO pageReqVO) { | 135 | public CommonResult<PageResult<AiChatMessageRespVO>> getChatMessagePage(AiChatMessagePageReqVO pageReqVO) { |
| 136 | PageResult<AiChatMessageDO> pageResult = chatMessageService.getChatMessagePage(pageReqVO); | 136 | PageResult<AiChatMessageDO> pageResult = chatMessageService.getChatMessagePage(pageReqVO); |
| 137 | if (CollUtil.isEmpty(pageResult.getList())) { | 137 | if (CollUtil.isEmpty(pageResult.getList())) { |
| @@ -145,6 +145,20 @@ public class AiChatMessageController { | @@ -145,6 +145,20 @@ public class AiChatMessageController { | ||
| 145 | role -> respVO.setRoleName(role.getName())))); | 145 | role -> respVO.setRoleName(role.getName())))); |
| 146 | } | 146 | } |
| 147 | 147 | ||
| 148 | + @GetMapping("/get") | ||
| 149 | + @Operation(summary = "获得消息", description = "用于【对话管理】菜单") | ||
| 150 | + @Parameter(name = "id", required = true, description = "消息编号", example = "1024") | ||
| 151 | + @PreAuthorize("@ss.hasPermission('ai:chat-message:query')") | ||
| 152 | + public CommonResult<AiChatMessageRespVO> getChatMessage(@RequestParam("id") Long id) { | ||
| 153 | + AiChatMessageDO message = chatMessageService.getChatMessage(id); | ||
| 154 | + AiChatMessageRespVO respVO = BeanUtils.toBean(message, AiChatMessageRespVO.class); | ||
| 155 | + if (respVO != null && respVO.getRoleId() != null) { | ||
| 156 | + AiChatRoleDO role = chatRoleService.getChatRole(respVO.getRoleId()); | ||
| 157 | + respVO.setRoleName(role != null ? role.getName() : null); | ||
| 158 | + } | ||
| 159 | + return success(respVO); | ||
| 160 | + } | ||
| 161 | + | ||
| 148 | @Operation(summary = "管理员删除消息") | 162 | @Operation(summary = "管理员删除消息") |
| 149 | @DeleteMapping("/delete-by-admin") | 163 | @DeleteMapping("/delete-by-admin") |
| 150 | @Parameter(name = "id", required = true, description = "消息编号", example = "1024") | 164 | @Parameter(name = "id", required = true, description = "消息编号", example = "1024") |
yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/image/AiImageController.java
| @@ -119,6 +119,15 @@ public class AiImageController { | @@ -119,6 +119,15 @@ public class AiImageController { | ||
| 119 | return success(BeanUtils.toBean(pageResult, AiImageRespVO.class)); | 119 | return success(BeanUtils.toBean(pageResult, AiImageRespVO.class)); |
| 120 | } | 120 | } |
| 121 | 121 | ||
| 122 | + @GetMapping("/get") | ||
| 123 | + @Operation(summary = "获得绘画") | ||
| 124 | + @Parameter(name = "id", description = "编号", required = true, example = "1024") | ||
| 125 | + @PreAuthorize("@ss.hasPermission('ai:image:query')") | ||
| 126 | + public CommonResult<AiImageRespVO> getImage(@RequestParam("id") Long id) { | ||
| 127 | + AiImageDO image = imageService.getImage(id); | ||
| 128 | + return success(BeanUtils.toBean(image, AiImageRespVO.class)); | ||
| 129 | + } | ||
| 130 | + | ||
| 122 | @PutMapping("/update") | 131 | @PutMapping("/update") |
| 123 | @Operation(summary = "更新绘画") | 132 | @Operation(summary = "更新绘画") |
| 124 | @PreAuthorize("@ss.hasPermission('ai:image:update')") | 133 | @PreAuthorize("@ss.hasPermission('ai:image:update')") |
| @@ -136,4 +145,4 @@ public class AiImageController { | @@ -136,4 +145,4 @@ public class AiImageController { | ||
| 136 | return success(true); | 145 | return success(true); |
| 137 | } | 146 | } |
| 138 | 147 | ||
| 139 | -} | ||
| 140 | \ No newline at end of file | 148 | \ No newline at end of file |
| 149 | +} |
yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/mindmap/AiMindMapController.java
| @@ -46,6 +46,15 @@ public class AiMindMapController { | @@ -46,6 +46,15 @@ public class AiMindMapController { | ||
| 46 | return success(true); | 46 | return success(true); |
| 47 | } | 47 | } |
| 48 | 48 | ||
| 49 | + @GetMapping("/get") | ||
| 50 | + @Operation(summary = "获得思维导图") | ||
| 51 | + @Parameter(name = "id", description = "编号", required = true, example = "1024") | ||
| 52 | + @PreAuthorize("@ss.hasPermission('ai:mind-map:query')") | ||
| 53 | + public CommonResult<AiMindMapRespVO> getMindMap(@RequestParam("id") Long id) { | ||
| 54 | + AiMindMapDO mindMap = mindMapService.getMindMap(id); | ||
| 55 | + return success(BeanUtils.toBean(mindMap, AiMindMapRespVO.class)); | ||
| 56 | + } | ||
| 57 | + | ||
| 49 | @GetMapping("/page") | 58 | @GetMapping("/page") |
| 50 | @Operation(summary = "获得思维导图分页") | 59 | @Operation(summary = "获得思维导图分页") |
| 51 | @PreAuthorize("@ss.hasPermission('ai:mind-map:query')") | 60 | @PreAuthorize("@ss.hasPermission('ai:mind-map:query')") |
yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/music/AiMusicController.java
| @@ -78,6 +78,15 @@ public class AiMusicController { | @@ -78,6 +78,15 @@ public class AiMusicController { | ||
| 78 | return success(BeanUtils.toBean(pageResult, AiMusicRespVO.class)); | 78 | return success(BeanUtils.toBean(pageResult, AiMusicRespVO.class)); |
| 79 | } | 79 | } |
| 80 | 80 | ||
| 81 | + @GetMapping("/get") | ||
| 82 | + @Operation(summary = "获得音乐") | ||
| 83 | + @Parameter(name = "id", description = "编号", required = true, example = "1024") | ||
| 84 | + @PreAuthorize("@ss.hasPermission('ai:music:query')") | ||
| 85 | + public CommonResult<AiMusicRespVO> getMusic(@RequestParam("id") Long id) { | ||
| 86 | + AiMusicDO music = musicService.getMusic(id); | ||
| 87 | + return success(BeanUtils.toBean(music, AiMusicRespVO.class)); | ||
| 88 | + } | ||
| 89 | + | ||
| 81 | @DeleteMapping("/delete") | 90 | @DeleteMapping("/delete") |
| 82 | @Operation(summary = "删除音乐") | 91 | @Operation(summary = "删除音乐") |
| 83 | @Parameter(name = "id", description = "编号", required = true) | 92 | @Parameter(name = "id", description = "编号", required = true) |
yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/write/AiWriteController.java
| @@ -46,6 +46,15 @@ public class AiWriteController { | @@ -46,6 +46,15 @@ public class AiWriteController { | ||
| 46 | return success(true); | 46 | return success(true); |
| 47 | } | 47 | } |
| 48 | 48 | ||
| 49 | + @GetMapping("/get") | ||
| 50 | + @Operation(summary = "获得写作") | ||
| 51 | + @Parameter(name = "id", description = "编号", required = true, example = "1024") | ||
| 52 | + @PreAuthorize("@ss.hasPermission('ai:write:query')") | ||
| 53 | + public CommonResult<AiWriteRespVO> getWrite(@RequestParam("id") Long id) { | ||
| 54 | + AiWriteDO write = writeService.getWrite(id); | ||
| 55 | + return success(BeanUtils.toBean(write, AiWriteRespVO.class)); | ||
| 56 | + } | ||
| 57 | + | ||
| 49 | @GetMapping("/page") | 58 | @GetMapping("/page") |
| 50 | @Operation(summary = "获得写作分页") | 59 | @Operation(summary = "获得写作分页") |
| 51 | @PreAuthorize("@ss.hasPermission('ai:write:query')") | 60 | @PreAuthorize("@ss.hasPermission('ai:write:query')") |
yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/enums/ErrorCodeConstants.java
| @@ -12,6 +12,7 @@ public interface ErrorCodeConstants { | @@ -12,6 +12,7 @@ public interface ErrorCodeConstants { | ||
| 12 | // ========== API 密钥 1-040-000-000 ========== | 12 | // ========== API 密钥 1-040-000-000 ========== |
| 13 | ErrorCode API_KEY_NOT_EXISTS = new ErrorCode(1_040_000_000, "API 密钥不存在"); | 13 | ErrorCode API_KEY_NOT_EXISTS = new ErrorCode(1_040_000_000, "API 密钥不存在"); |
| 14 | ErrorCode API_KEY_DISABLE = new ErrorCode(1_040_000_001, "API 密钥已禁用!"); | 14 | ErrorCode API_KEY_DISABLE = new ErrorCode(1_040_000_001, "API 密钥已禁用!"); |
| 15 | + ErrorCode API_CONFIG_PLACEHOLDER_NOT_RESOLVED = new ErrorCode(1_040_000_002, "AI 配置({})无法解析,请检查环境变量或配置项"); | ||
| 15 | 16 | ||
| 16 | // ========== API 模型 1-040-001-000 ========== | 17 | // ========== API 模型 1-040-001-000 ========== |
| 17 | ErrorCode MODEL_NOT_EXISTS = new ErrorCode(1_040_001_000, "模型不存在!"); | 18 | ErrorCode MODEL_NOT_EXISTS = new ErrorCode(1_040_001_000, "模型不存在!"); |
yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/enums/model/AiPlatformEnum.java
| @@ -28,6 +28,7 @@ public enum AiPlatformEnum implements ArrayValuable<String> { | @@ -28,6 +28,7 @@ public enum AiPlatformEnum implements ArrayValuable<String> { | ||
| 28 | MINI_MAX("MiniMax", "MiniMax"), // 稀宇科技 | 28 | MINI_MAX("MiniMax", "MiniMax"), // 稀宇科技 |
| 29 | MOONSHOT("Moonshot", "月之暗面"), // KIMI | 29 | MOONSHOT("Moonshot", "月之暗面"), // KIMI |
| 30 | BAI_CHUAN("BaiChuan", "百川智能"), // 百川智能 | 30 | BAI_CHUAN("BaiChuan", "百川智能"), // 百川智能 |
| 31 | + STEP_FUN("StepFun", "阶跃星辰"), // 阶跃星辰 | ||
| 31 | 32 | ||
| 32 | // ========== 国外平台 ========== | 33 | // ========== 国外平台 ========== |
| 33 | 34 | ||
| @@ -40,7 +41,7 @@ public enum AiPlatformEnum implements ArrayValuable<String> { | @@ -40,7 +41,7 @@ public enum AiPlatformEnum implements ArrayValuable<String> { | ||
| 40 | STABLE_DIFFUSION("StableDiffusion", "StableDiffusion"), // Stability AI | 41 | STABLE_DIFFUSION("StableDiffusion", "StableDiffusion"), // Stability AI |
| 41 | MIDJOURNEY("Midjourney", "Midjourney"), // Midjourney | 42 | MIDJOURNEY("Midjourney", "Midjourney"), // Midjourney |
| 42 | SUNO("Suno", "Suno"), // Suno AI | 43 | SUNO("Suno", "Suno"), // Suno AI |
| 43 | - GROK("Grok","Grok"), // Grok | 44 | + GROK("Grok", "Grok"), // Grok |
| 44 | 45 | ||
| 45 | ; | 46 | ; |
| 46 | 47 |
yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/config/AiAutoConfiguration.java
| @@ -10,10 +10,15 @@ import cn.iocoder.yudao.module.ai.framework.ai.core.model.gemini.GeminiChatModel | @@ -10,10 +10,15 @@ import cn.iocoder.yudao.module.ai.framework.ai.core.model.gemini.GeminiChatModel | ||
| 10 | import cn.iocoder.yudao.module.ai.framework.ai.core.model.grok.GrokChatModel; | 10 | import cn.iocoder.yudao.module.ai.framework.ai.core.model.grok.GrokChatModel; |
| 11 | import cn.iocoder.yudao.module.ai.framework.ai.core.model.hunyuan.HunYuanChatModel; | 11 | import cn.iocoder.yudao.module.ai.framework.ai.core.model.hunyuan.HunYuanChatModel; |
| 12 | import cn.iocoder.yudao.module.ai.framework.ai.core.model.midjourney.api.MidjourneyApi; | 12 | import cn.iocoder.yudao.module.ai.framework.ai.core.model.midjourney.api.MidjourneyApi; |
| 13 | +import cn.iocoder.yudao.module.ai.framework.ai.core.model.minimax.MiniMaxChatModel; | ||
| 14 | +import cn.iocoder.yudao.module.ai.framework.ai.core.model.moonshot.MoonshotChatModel; | ||
| 13 | import cn.iocoder.yudao.module.ai.framework.ai.core.model.siliconflow.SiliconFlowApiConstants; | 15 | import cn.iocoder.yudao.module.ai.framework.ai.core.model.siliconflow.SiliconFlowApiConstants; |
| 14 | import cn.iocoder.yudao.module.ai.framework.ai.core.model.siliconflow.SiliconFlowChatModel; | 16 | import cn.iocoder.yudao.module.ai.framework.ai.core.model.siliconflow.SiliconFlowChatModel; |
| 17 | +import cn.iocoder.yudao.module.ai.framework.ai.core.model.stepfun.StepFunChatModel; | ||
| 15 | import cn.iocoder.yudao.module.ai.framework.ai.core.model.suno.api.SunoApi; | 18 | import cn.iocoder.yudao.module.ai.framework.ai.core.model.suno.api.SunoApi; |
| 16 | import cn.iocoder.yudao.module.ai.framework.ai.core.model.xinghuo.XingHuoChatModel; | 19 | import cn.iocoder.yudao.module.ai.framework.ai.core.model.xinghuo.XingHuoChatModel; |
| 20 | +import cn.iocoder.yudao.module.ai.framework.ai.core.model.yiyan.YiYanChatModel; | ||
| 21 | +import cn.iocoder.yudao.module.ai.framework.ai.core.model.zhipu.ZhiPuChatModel; | ||
| 17 | import cn.iocoder.yudao.module.ai.framework.ai.core.webserch.AiWebSearchClient; | 22 | import cn.iocoder.yudao.module.ai.framework.ai.core.webserch.AiWebSearchClient; |
| 18 | import cn.iocoder.yudao.module.ai.framework.ai.core.webserch.bocha.AiBoChaWebSearchClient; | 23 | import cn.iocoder.yudao.module.ai.framework.ai.core.webserch.bocha.AiBoChaWebSearchClient; |
| 19 | import cn.iocoder.yudao.module.ai.tool.method.PersonService; | 24 | import cn.iocoder.yudao.module.ai.tool.method.PersonService; |
| @@ -44,7 +49,6 @@ import org.springframework.context.annotation.Bean; | @@ -44,7 +49,6 @@ import org.springframework.context.annotation.Bean; | ||
| 44 | import org.springframework.context.annotation.Configuration; | 49 | import org.springframework.context.annotation.Configuration; |
| 45 | 50 | ||
| 46 | import java.util.List; | 51 | import java.util.List; |
| 47 | -import java.util.Optional; | ||
| 48 | 52 | ||
| 49 | /** | 53 | /** |
| 50 | * 芋道 AI 自动配置 | 54 | * 芋道 AI 自动配置 |
| @@ -113,13 +117,13 @@ public class AiAutoConfiguration { | @@ -113,13 +117,13 @@ public class AiAutoConfiguration { | ||
| 113 | if (StrUtil.isEmpty(properties.getModel())) { | 117 | if (StrUtil.isEmpty(properties.getModel())) { |
| 114 | properties.setModel(DouBaoChatModel.MODEL_DEFAULT); | 118 | properties.setModel(DouBaoChatModel.MODEL_DEFAULT); |
| 115 | } | 119 | } |
| 116 | - OpenAiChatModel openAiChatModel = OpenAiChatModel.builder() | ||
| 117 | - .openAiApi(OpenAiApi.builder() | 120 | + DeepSeekChatModel openAiChatModel = DeepSeekChatModel.builder() |
| 121 | + .deepSeekApi(DeepSeekApi.builder() | ||
| 118 | .baseUrl(DouBaoChatModel.BASE_URL) | 122 | .baseUrl(DouBaoChatModel.BASE_URL) |
| 119 | .completionsPath(DouBaoChatModel.COMPLETE_PATH) | 123 | .completionsPath(DouBaoChatModel.COMPLETE_PATH) |
| 120 | .apiKey(properties.getApiKey()) | 124 | .apiKey(properties.getApiKey()) |
| 121 | .build()) | 125 | .build()) |
| 122 | - .defaultOptions(OpenAiChatOptions.builder() | 126 | + .defaultOptions(DeepSeekChatOptions.builder() |
| 123 | .model(properties.getModel()) | 127 | .model(properties.getModel()) |
| 124 | .temperature(properties.getTemperature()) | 128 | .temperature(properties.getTemperature()) |
| 125 | .maxTokens(properties.getMaxTokens()) | 129 | .maxTokens(properties.getMaxTokens()) |
| @@ -168,11 +172,8 @@ public class AiAutoConfiguration { | @@ -168,11 +172,8 @@ public class AiAutoConfiguration { | ||
| 168 | if (StrUtil.isEmpty(properties.getModel())) { | 172 | if (StrUtil.isEmpty(properties.getModel())) { |
| 169 | properties.setModel(HunYuanChatModel.MODEL_DEFAULT); | 173 | properties.setModel(HunYuanChatModel.MODEL_DEFAULT); |
| 170 | } | 174 | } |
| 171 | - // 特殊:由于混元大模型不提供 deepseek,而是通过知识引擎,所以需要区分下 URL | ||
| 172 | if (StrUtil.isEmpty(properties.getBaseUrl())) { | 175 | if (StrUtil.isEmpty(properties.getBaseUrl())) { |
| 173 | - properties.setBaseUrl( | ||
| 174 | - StrUtil.startWithIgnoreCase(properties.getModel(), "deepseek") ? HunYuanChatModel.DEEP_SEEK_BASE_URL | ||
| 175 | - : HunYuanChatModel.BASE_URL); | 176 | + properties.setBaseUrl(HunYuanChatModel.BASE_URL); |
| 176 | } | 177 | } |
| 177 | // 创建 DeepSeekChatModel、HunYuanChatModel 对象 | 178 | // 创建 DeepSeekChatModel、HunYuanChatModel 对象 |
| 178 | DeepSeekChatModel openAiChatModel = DeepSeekChatModel.builder() | 179 | DeepSeekChatModel openAiChatModel = DeepSeekChatModel.builder() |
| @@ -203,25 +204,15 @@ public class AiAutoConfiguration { | @@ -203,25 +204,15 @@ public class AiAutoConfiguration { | ||
| 203 | if (StrUtil.isEmpty(properties.getModel())) { | 204 | if (StrUtil.isEmpty(properties.getModel())) { |
| 204 | properties.setModel(XingHuoChatModel.MODEL_DEFAULT); | 205 | properties.setModel(XingHuoChatModel.MODEL_DEFAULT); |
| 205 | } | 206 | } |
| 206 | - OpenAiApi.Builder builder = OpenAiApi.builder() | ||
| 207 | - .baseUrl(XingHuoChatModel.BASE_URL_V1) | ||
| 208 | - .apiKey(properties.getAppKey() + ":" + properties.getSecretKey()); | ||
| 209 | - if ("x1".equals(properties.getModel())) { | ||
| 210 | - builder.baseUrl(XingHuoChatModel.BASE_URL_V2) | ||
| 211 | - .completionsPath(XingHuoChatModel.BASE_COMPLETIONS_PATH_V2); | ||
| 212 | - } | ||
| 213 | - OpenAiChatModel openAiChatModel = OpenAiChatModel.builder() | ||
| 214 | - .openAiApi(builder.build()) | ||
| 215 | - .defaultOptions(OpenAiChatOptions.builder() | 207 | + return XingHuoChatModel.builder() |
| 208 | + .apiKey(properties.getApiKey()) | ||
| 209 | + .options(DeepSeekChatOptions.builder() | ||
| 216 | .model(properties.getModel()) | 210 | .model(properties.getModel()) |
| 217 | .temperature(properties.getTemperature()) | 211 | .temperature(properties.getTemperature()) |
| 218 | .maxTokens(properties.getMaxTokens()) | 212 | .maxTokens(properties.getMaxTokens()) |
| 219 | .topP(properties.getTopP()) | 213 | .topP(properties.getTopP()) |
| 220 | .build()) | 214 | .build()) |
| 221 | - // TODO @芋艿:星火的 function call 有 bug,会报 ToolResponseMessage must have an id 错误!!! | ||
| 222 | - .toolCallingManager(getToolCallingManager()) | ||
| 223 | .build(); | 215 | .build(); |
| 224 | - return new XingHuoChatModel(openAiChatModel); | ||
| 225 | } | 216 | } |
| 226 | 217 | ||
| 227 | @Bean | 218 | @Bean |
| @@ -235,12 +226,13 @@ public class AiAutoConfiguration { | @@ -235,12 +226,13 @@ public class AiAutoConfiguration { | ||
| 235 | if (StrUtil.isEmpty(properties.getModel())) { | 226 | if (StrUtil.isEmpty(properties.getModel())) { |
| 236 | properties.setModel(BaiChuanChatModel.MODEL_DEFAULT); | 227 | properties.setModel(BaiChuanChatModel.MODEL_DEFAULT); |
| 237 | } | 228 | } |
| 238 | - OpenAiChatModel openAiChatModel = OpenAiChatModel.builder() | ||
| 239 | - .openAiApi(OpenAiApi.builder() | 229 | + DeepSeekChatModel deepSeekChatModel = DeepSeekChatModel.builder() |
| 230 | + .deepSeekApi(DeepSeekApi.builder() | ||
| 240 | .baseUrl(BaiChuanChatModel.BASE_URL) | 231 | .baseUrl(BaiChuanChatModel.BASE_URL) |
| 241 | .apiKey(properties.getApiKey()) | 232 | .apiKey(properties.getApiKey()) |
| 233 | + .completionsPath(BaiChuanChatModel.COMPLETE_PATH) | ||
| 242 | .build()) | 234 | .build()) |
| 243 | - .defaultOptions(OpenAiChatOptions.builder() | 235 | + .defaultOptions(DeepSeekChatOptions.builder() |
| 244 | .model(properties.getModel()) | 236 | .model(properties.getModel()) |
| 245 | .temperature(properties.getTemperature()) | 237 | .temperature(properties.getTemperature()) |
| 246 | .maxTokens(properties.getMaxTokens()) | 238 | .maxTokens(properties.getMaxTokens()) |
| @@ -248,7 +240,123 @@ public class AiAutoConfiguration { | @@ -248,7 +240,123 @@ public class AiAutoConfiguration { | ||
| 248 | .build()) | 240 | .build()) |
| 249 | .toolCallingManager(getToolCallingManager()) | 241 | .toolCallingManager(getToolCallingManager()) |
| 250 | .build(); | 242 | .build(); |
| 251 | - return new BaiChuanChatModel(openAiChatModel); | 243 | + return new BaiChuanChatModel(deepSeekChatModel); |
| 244 | + } | ||
| 245 | + | ||
| 246 | + @Bean | ||
| 247 | + @ConditionalOnProperty(value = "yudao.ai.yiyan.enable", havingValue = "true") | ||
| 248 | + public YiYanChatModel yiYanChatClient(YudaoAiProperties yudaoAiProperties) { | ||
| 249 | + YudaoAiProperties.YiYan properties = yudaoAiProperties.getYiyan(); | ||
| 250 | + return buildYiYanChatClient(properties); | ||
| 251 | + } | ||
| 252 | + | ||
| 253 | + public YiYanChatModel buildYiYanChatClient(YudaoAiProperties.YiYan properties) { | ||
| 254 | + if (StrUtil.isEmpty(properties.getModel())) { | ||
| 255 | + properties.setModel(YiYanChatModel.MODEL_DEFAULT); | ||
| 256 | + } | ||
| 257 | + return new YiYanChatModel(buildDeepSeekCompatibleChatModel( | ||
| 258 | + StrUtil.blankToDefault(properties.getBaseUrl(), YiYanChatModel.BASE_URL), | ||
| 259 | + null, properties.getApiKey(), properties.getModel(), properties.getTemperature(), | ||
| 260 | + properties.getMaxTokens(), properties.getTopP())); | ||
| 261 | + } | ||
| 262 | + | ||
| 263 | + @Bean | ||
| 264 | + @ConditionalOnProperty(value = "yudao.ai.zhipu.enable", havingValue = "true") | ||
| 265 | + public ZhiPuChatModel zhiPuChatClient(YudaoAiProperties yudaoAiProperties) { | ||
| 266 | + YudaoAiProperties.ZhiPu properties = yudaoAiProperties.getZhipu(); | ||
| 267 | + return buildZhiPuChatClient(properties); | ||
| 268 | + } | ||
| 269 | + | ||
| 270 | + public ZhiPuChatModel buildZhiPuChatClient(YudaoAiProperties.ZhiPu properties) { | ||
| 271 | + if (StrUtil.isEmpty(properties.getModel())) { | ||
| 272 | + properties.setModel(ZhiPuChatModel.MODEL_DEFAULT); | ||
| 273 | + } | ||
| 274 | + DeepSeekChatModel deepSeekChatModel = DeepSeekChatModel.builder() | ||
| 275 | + .deepSeekApi(DeepSeekApi.builder() | ||
| 276 | + .baseUrl(StrUtil.blankToDefault(properties.getBaseUrl(), ZhiPuChatModel.BASE_URL)) | ||
| 277 | + .apiKey(properties.getApiKey()) | ||
| 278 | + .build()) | ||
| 279 | + .defaultOptions(DeepSeekChatOptions.builder() | ||
| 280 | + .model(properties.getModel()) | ||
| 281 | + .temperature(properties.getTemperature()) | ||
| 282 | + .maxTokens(properties.getMaxTokens()) | ||
| 283 | + .topP(properties.getTopP()) | ||
| 284 | + .build()) | ||
| 285 | + .build(); | ||
| 286 | + return new ZhiPuChatModel(deepSeekChatModel); | ||
| 287 | + } | ||
| 288 | + | ||
| 289 | + @Bean | ||
| 290 | + @ConditionalOnProperty(value = "yudao.ai.minimax.enable", havingValue = "true") | ||
| 291 | + public MiniMaxChatModel miniMaxChatClient(YudaoAiProperties yudaoAiProperties) { | ||
| 292 | + YudaoAiProperties.MiniMax properties = yudaoAiProperties.getMinimax(); | ||
| 293 | + return buildMiniMaxChatClient(properties); | ||
| 294 | + } | ||
| 295 | + | ||
| 296 | + public MiniMaxChatModel buildMiniMaxChatClient(YudaoAiProperties.MiniMax properties) { | ||
| 297 | + if (StrUtil.isEmpty(properties.getModel())) { | ||
| 298 | + properties.setModel(MiniMaxChatModel.MODEL_DEFAULT); | ||
| 299 | + } | ||
| 300 | + return new MiniMaxChatModel(buildDeepSeekCompatibleChatModel( | ||
| 301 | + StrUtil.blankToDefault(properties.getBaseUrl(), MiniMaxChatModel.BASE_URL), | ||
| 302 | + null, properties.getApiKey(), properties.getModel(), properties.getTemperature(), | ||
| 303 | + properties.getMaxTokens(), properties.getTopP())); | ||
| 304 | + } | ||
| 305 | + | ||
| 306 | + @Bean | ||
| 307 | + @ConditionalOnProperty(value = "yudao.ai.moonshot.enable", havingValue = "true") | ||
| 308 | + public MoonshotChatModel moonshotChatClient(YudaoAiProperties yudaoAiProperties) { | ||
| 309 | + YudaoAiProperties.Moonshot properties = yudaoAiProperties.getMoonshot(); | ||
| 310 | + return buildMoonshotChatClient(properties); | ||
| 311 | + } | ||
| 312 | + | ||
| 313 | + public MoonshotChatModel buildMoonshotChatClient(YudaoAiProperties.Moonshot properties) { | ||
| 314 | + if (StrUtil.isEmpty(properties.getModel())) { | ||
| 315 | + properties.setModel(MoonshotChatModel.MODEL_DEFAULT); | ||
| 316 | + } | ||
| 317 | + return new MoonshotChatModel(buildDeepSeekCompatibleChatModel( | ||
| 318 | + StrUtil.blankToDefault(properties.getBaseUrl(), MoonshotChatModel.BASE_URL), | ||
| 319 | + MoonshotChatModel.COMPLETE_PATH, properties.getApiKey(), properties.getModel(), | ||
| 320 | + properties.getTemperature(), properties.getMaxTokens(), properties.getTopP())); | ||
| 321 | + } | ||
| 322 | + | ||
| 323 | + @Bean | ||
| 324 | + @ConditionalOnProperty(value = "yudao.ai.stepfun.enable", havingValue = "true") | ||
| 325 | + public StepFunChatModel stepFunChatClient(YudaoAiProperties yudaoAiProperties) { | ||
| 326 | + YudaoAiProperties.StepFun properties = yudaoAiProperties.getStepfun(); | ||
| 327 | + return buildStepFunChatClient(properties); | ||
| 328 | + } | ||
| 329 | + | ||
| 330 | + public StepFunChatModel buildStepFunChatClient(YudaoAiProperties.StepFun properties) { | ||
| 331 | + if (StrUtil.isEmpty(properties.getModel())) { | ||
| 332 | + properties.setModel(StepFunChatModel.MODEL_DEFAULT); | ||
| 333 | + } | ||
| 334 | + return new StepFunChatModel(buildDeepSeekCompatibleChatModel( | ||
| 335 | + StrUtil.blankToDefault(properties.getBaseUrl(), StepFunChatModel.BASE_URL), | ||
| 336 | + StepFunChatModel.COMPLETE_PATH, properties.getApiKey(), properties.getModel(), | ||
| 337 | + properties.getTemperature(), properties.getMaxTokens(), properties.getTopP())); | ||
| 338 | + } | ||
| 339 | + | ||
| 340 | + private static DeepSeekChatModel buildDeepSeekCompatibleChatModel(String baseUrl, String completionsPath, | ||
| 341 | + String apiKey, String model, | ||
| 342 | + Double temperature, Integer maxTokens, | ||
| 343 | + Double topP) { | ||
| 344 | + DeepSeekApi.Builder apiBuilder = DeepSeekApi.builder() | ||
| 345 | + .baseUrl(baseUrl) | ||
| 346 | + .apiKey(apiKey); | ||
| 347 | + if (StrUtil.isNotEmpty(completionsPath)) { | ||
| 348 | + apiBuilder.completionsPath(completionsPath); | ||
| 349 | + } | ||
| 350 | + return DeepSeekChatModel.builder() | ||
| 351 | + .deepSeekApi(apiBuilder.build()) | ||
| 352 | + .defaultOptions(DeepSeekChatOptions.builder() | ||
| 353 | + .model(model) | ||
| 354 | + .temperature(temperature) | ||
| 355 | + .maxTokens(maxTokens) | ||
| 356 | + .topP(topP) | ||
| 357 | + .build()) | ||
| 358 | + .toolCallingManager(getToolCallingManager()) | ||
| 359 | + .build(); | ||
| 252 | } | 360 | } |
| 253 | 361 | ||
| 254 | @Bean | 362 | @Bean |
| @@ -264,14 +372,20 @@ public class AiAutoConfiguration { | @@ -264,14 +372,20 @@ public class AiAutoConfiguration { | ||
| 264 | return new SunoApi(yudaoAiProperties.getSuno().getBaseUrl()); | 372 | return new SunoApi(yudaoAiProperties.getSuno().getBaseUrl()); |
| 265 | } | 373 | } |
| 266 | 374 | ||
| 267 | - public ChatModel buildGrokChatClient(YudaoAiProperties.Grok properties) { | 375 | + @Bean |
| 376 | + @ConditionalOnProperty(value = "yudao.ai.grok.enable", havingValue = "true") | ||
| 377 | + public GrokChatModel grokChatClient(YudaoAiProperties yudaoAiProperties) { | ||
| 378 | + YudaoAiProperties.Grok properties = yudaoAiProperties.getGrok(); | ||
| 379 | + return buildGrokChatClient(properties); | ||
| 380 | + } | ||
| 381 | + | ||
| 382 | + public GrokChatModel buildGrokChatClient(YudaoAiProperties.Grok properties) { | ||
| 268 | if (StrUtil.isEmpty(properties.getModel())) { | 383 | if (StrUtil.isEmpty(properties.getModel())) { |
| 269 | properties.setModel(GrokChatModel.MODEL_DEFAULT); | 384 | properties.setModel(GrokChatModel.MODEL_DEFAULT); |
| 270 | } | 385 | } |
| 271 | OpenAiChatModel openAiChatModel = OpenAiChatModel.builder() | 386 | OpenAiChatModel openAiChatModel = OpenAiChatModel.builder() |
| 272 | .openAiApi(OpenAiApi.builder() | 387 | .openAiApi(OpenAiApi.builder() |
| 273 | - .baseUrl(Optional.ofNullable(properties.getBaseUrl()) | ||
| 274 | - .orElse(GrokChatModel.BASE_URL)) | 388 | + .baseUrl(StrUtil.blankToDefault(properties.getBaseUrl(), GrokChatModel.BASE_URL)) |
| 275 | .completionsPath(GrokChatModel.COMPLETE_PATH) | 389 | .completionsPath(GrokChatModel.COMPLETE_PATH) |
| 276 | .apiKey(properties.getApiKey()) | 390 | .apiKey(properties.getApiKey()) |
| 277 | .build()) | 391 | .build()) |
| @@ -283,7 +397,7 @@ public class AiAutoConfiguration { | @@ -283,7 +397,7 @@ public class AiAutoConfiguration { | ||
| 283 | .build()) | 397 | .build()) |
| 284 | .toolCallingManager(getToolCallingManager()) | 398 | .toolCallingManager(getToolCallingManager()) |
| 285 | .build(); | 399 | .build(); |
| 286 | - return new DouBaoChatModel(openAiChatModel); | 400 | + return new GrokChatModel(openAiChatModel); |
| 287 | } | 401 | } |
| 288 | 402 | ||
| 289 | // ========== RAG 相关 ========== | 403 | // ========== RAG 相关 ========== |
| @@ -320,4 +434,4 @@ public class AiAutoConfiguration { | @@ -320,4 +434,4 @@ public class AiAutoConfiguration { | ||
| 320 | return List.of(ToolCallbacks.from(personService)); | 434 | return List.of(ToolCallbacks.from(personService)); |
| 321 | } | 435 | } |
| 322 | 436 | ||
| 323 | -} | ||
| 324 | \ No newline at end of file | 437 | \ No newline at end of file |
| 438 | +} |
yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/config/YudaoAiProperties.java
| @@ -44,6 +44,36 @@ public class YudaoAiProperties { | @@ -44,6 +44,36 @@ public class YudaoAiProperties { | ||
| 44 | private BaiChuan baichuan; | 44 | private BaiChuan baichuan; |
| 45 | 45 | ||
| 46 | /** | 46 | /** |
| 47 | + * 文心一言 | ||
| 48 | + */ | ||
| 49 | + private YiYan yiyan; | ||
| 50 | + | ||
| 51 | + /** | ||
| 52 | + * 智谱 | ||
| 53 | + */ | ||
| 54 | + private ZhiPu zhipu; | ||
| 55 | + | ||
| 56 | + /** | ||
| 57 | + * MiniMax | ||
| 58 | + */ | ||
| 59 | + private MiniMax minimax; | ||
| 60 | + | ||
| 61 | + /** | ||
| 62 | + * 月之暗面 | ||
| 63 | + */ | ||
| 64 | + private Moonshot moonshot; | ||
| 65 | + | ||
| 66 | + /** | ||
| 67 | + * 阶跃星辰 | ||
| 68 | + */ | ||
| 69 | + private StepFun stepfun; | ||
| 70 | + | ||
| 71 | + /** | ||
| 72 | + * Grok | ||
| 73 | + */ | ||
| 74 | + private Grok grok; | ||
| 75 | + | ||
| 76 | + /** | ||
| 47 | * Midjourney 绘图 | 77 | * Midjourney 绘图 |
| 48 | */ | 78 | */ |
| 49 | private Midjourney midjourney; | 79 | private Midjourney midjourney; |
| @@ -116,9 +146,7 @@ public class YudaoAiProperties { | @@ -116,9 +146,7 @@ public class YudaoAiProperties { | ||
| 116 | public static class XingHuo { | 146 | public static class XingHuo { |
| 117 | 147 | ||
| 118 | private String enable; | 148 | private String enable; |
| 119 | - private String appId; | ||
| 120 | - private String appKey; | ||
| 121 | - private String secretKey; | 149 | + private String apiKey; |
| 122 | 150 | ||
| 123 | private String model; | 151 | private String model; |
| 124 | private Double temperature; | 152 | private Double temperature; |
| @@ -141,6 +169,76 @@ public class YudaoAiProperties { | @@ -141,6 +169,76 @@ public class YudaoAiProperties { | ||
| 141 | } | 169 | } |
| 142 | 170 | ||
| 143 | @Data | 171 | @Data |
| 172 | + public static class YiYan { | ||
| 173 | + | ||
| 174 | + private String enable; | ||
| 175 | + private String baseUrl; | ||
| 176 | + private String apiKey; | ||
| 177 | + | ||
| 178 | + private String model; | ||
| 179 | + private Double temperature; | ||
| 180 | + private Integer maxTokens; | ||
| 181 | + private Double topP; | ||
| 182 | + | ||
| 183 | + } | ||
| 184 | + | ||
| 185 | + @Data | ||
| 186 | + public static class ZhiPu { | ||
| 187 | + | ||
| 188 | + private String enable; | ||
| 189 | + private String baseUrl; | ||
| 190 | + private String apiKey; | ||
| 191 | + | ||
| 192 | + private String model; | ||
| 193 | + private Double temperature; | ||
| 194 | + private Integer maxTokens; | ||
| 195 | + private Double topP; | ||
| 196 | + | ||
| 197 | + } | ||
| 198 | + | ||
| 199 | + @Data | ||
| 200 | + public static class MiniMax { | ||
| 201 | + | ||
| 202 | + private String enable; | ||
| 203 | + private String baseUrl; | ||
| 204 | + private String apiKey; | ||
| 205 | + | ||
| 206 | + private String model; | ||
| 207 | + private Double temperature; | ||
| 208 | + private Integer maxTokens; | ||
| 209 | + private Double topP; | ||
| 210 | + | ||
| 211 | + } | ||
| 212 | + | ||
| 213 | + @Data | ||
| 214 | + public static class Moonshot { | ||
| 215 | + | ||
| 216 | + private String enable; | ||
| 217 | + private String baseUrl; | ||
| 218 | + private String apiKey; | ||
| 219 | + | ||
| 220 | + private String model; | ||
| 221 | + private Double temperature; | ||
| 222 | + private Integer maxTokens; | ||
| 223 | + private Double topP; | ||
| 224 | + | ||
| 225 | + } | ||
| 226 | + | ||
| 227 | + @Data | ||
| 228 | + public static class StepFun { | ||
| 229 | + | ||
| 230 | + private String enable; | ||
| 231 | + private String apiKey; | ||
| 232 | + private String baseUrl; | ||
| 233 | + | ||
| 234 | + private String model; | ||
| 235 | + private Double temperature; | ||
| 236 | + private Integer maxTokens; | ||
| 237 | + private Double topP; | ||
| 238 | + | ||
| 239 | + } | ||
| 240 | + | ||
| 241 | + @Data | ||
| 144 | public static class Midjourney { | 242 | public static class Midjourney { |
| 145 | 243 | ||
| 146 | private String enable; | 244 | private String enable; |
yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/AiModelFactoryImpl.java
| 1 | package cn.iocoder.yudao.module.ai.framework.ai.core.model; | 1 | package cn.iocoder.yudao.module.ai.framework.ai.core.model; |
| 2 | 2 | ||
| 3 | import cn.hutool.core.io.FileUtil; | 3 | import cn.hutool.core.io.FileUtil; |
| 4 | -import cn.hutool.core.lang.Assert; | ||
| 5 | import cn.hutool.core.lang.Singleton; | 4 | import cn.hutool.core.lang.Singleton; |
| 6 | import cn.hutool.core.lang.func.Func0; | 5 | import cn.hutool.core.lang.func.Func0; |
| 7 | import cn.hutool.core.util.ArrayUtil; | 6 | import cn.hutool.core.util.ArrayUtil; |
| @@ -15,14 +14,21 @@ import cn.iocoder.yudao.module.ai.framework.ai.config.YudaoAiProperties; | @@ -15,14 +14,21 @@ import cn.iocoder.yudao.module.ai.framework.ai.config.YudaoAiProperties; | ||
| 15 | import cn.iocoder.yudao.module.ai.framework.ai.core.model.baichuan.BaiChuanChatModel; | 14 | import cn.iocoder.yudao.module.ai.framework.ai.core.model.baichuan.BaiChuanChatModel; |
| 16 | import cn.iocoder.yudao.module.ai.framework.ai.core.model.doubao.DouBaoChatModel; | 15 | import cn.iocoder.yudao.module.ai.framework.ai.core.model.doubao.DouBaoChatModel; |
| 17 | import cn.iocoder.yudao.module.ai.framework.ai.core.model.gemini.GeminiChatModel; | 16 | import cn.iocoder.yudao.module.ai.framework.ai.core.model.gemini.GeminiChatModel; |
| 17 | +import cn.iocoder.yudao.module.ai.framework.ai.core.model.grok.GrokChatModel; | ||
| 18 | import cn.iocoder.yudao.module.ai.framework.ai.core.model.hunyuan.HunYuanChatModel; | 18 | import cn.iocoder.yudao.module.ai.framework.ai.core.model.hunyuan.HunYuanChatModel; |
| 19 | import cn.iocoder.yudao.module.ai.framework.ai.core.model.midjourney.api.MidjourneyApi; | 19 | import cn.iocoder.yudao.module.ai.framework.ai.core.model.midjourney.api.MidjourneyApi; |
| 20 | +import cn.iocoder.yudao.module.ai.framework.ai.core.model.minimax.MiniMaxChatModel; | ||
| 21 | +import cn.iocoder.yudao.module.ai.framework.ai.core.model.moonshot.MoonshotChatModel; | ||
| 20 | import cn.iocoder.yudao.module.ai.framework.ai.core.model.siliconflow.SiliconFlowApiConstants; | 22 | import cn.iocoder.yudao.module.ai.framework.ai.core.model.siliconflow.SiliconFlowApiConstants; |
| 21 | import cn.iocoder.yudao.module.ai.framework.ai.core.model.siliconflow.SiliconFlowChatModel; | 23 | import cn.iocoder.yudao.module.ai.framework.ai.core.model.siliconflow.SiliconFlowChatModel; |
| 22 | import cn.iocoder.yudao.module.ai.framework.ai.core.model.siliconflow.SiliconFlowImageApi; | 24 | import cn.iocoder.yudao.module.ai.framework.ai.core.model.siliconflow.SiliconFlowImageApi; |
| 23 | import cn.iocoder.yudao.module.ai.framework.ai.core.model.siliconflow.SiliconFlowImageModel; | 25 | import cn.iocoder.yudao.module.ai.framework.ai.core.model.siliconflow.SiliconFlowImageModel; |
| 26 | +import cn.iocoder.yudao.module.ai.framework.ai.core.model.stepfun.StepFunChatModel; | ||
| 24 | import cn.iocoder.yudao.module.ai.framework.ai.core.model.suno.api.SunoApi; | 27 | import cn.iocoder.yudao.module.ai.framework.ai.core.model.suno.api.SunoApi; |
| 25 | import cn.iocoder.yudao.module.ai.framework.ai.core.model.xinghuo.XingHuoChatModel; | 28 | import cn.iocoder.yudao.module.ai.framework.ai.core.model.xinghuo.XingHuoChatModel; |
| 29 | +import cn.iocoder.yudao.module.ai.framework.ai.core.model.yiyan.YiYanChatModel; | ||
| 30 | +import cn.iocoder.yudao.module.ai.framework.ai.core.model.zhipu.ZhiPuChatModel; | ||
| 31 | +import cn.iocoder.yudao.module.ai.util.AiUtils; | ||
| 26 | import com.alibaba.cloud.ai.autoconfigure.dashscope.DashScopeChatAutoConfiguration; | 32 | import com.alibaba.cloud.ai.autoconfigure.dashscope.DashScopeChatAutoConfiguration; |
| 27 | import com.alibaba.cloud.ai.autoconfigure.dashscope.DashScopeEmbeddingAutoConfiguration; | 33 | import com.alibaba.cloud.ai.autoconfigure.dashscope.DashScopeEmbeddingAutoConfiguration; |
| 28 | import com.alibaba.cloud.ai.autoconfigure.dashscope.DashScopeImageAutoConfiguration; | 34 | import com.alibaba.cloud.ai.autoconfigure.dashscope.DashScopeImageAutoConfiguration; |
| @@ -40,18 +46,6 @@ import io.milvus.client.MilvusServiceClient; | @@ -40,18 +46,6 @@ import io.milvus.client.MilvusServiceClient; | ||
| 40 | import io.qdrant.client.QdrantClient; | 46 | import io.qdrant.client.QdrantClient; |
| 41 | import io.qdrant.client.QdrantGrpcClient; | 47 | import io.qdrant.client.QdrantGrpcClient; |
| 42 | import lombok.SneakyThrows; | 48 | import lombok.SneakyThrows; |
| 43 | -import org.springaicommunity.moonshot.MoonshotChatModel; | ||
| 44 | -import org.springaicommunity.moonshot.MoonshotChatOptions; | ||
| 45 | -import org.springaicommunity.moonshot.api.MoonshotApi; | ||
| 46 | -import org.springaicommunity.moonshot.autoconfigure.MoonshotChatAutoConfiguration; | ||
| 47 | -import org.springaicommunity.qianfan.QianFanChatModel; | ||
| 48 | -import org.springaicommunity.qianfan.QianFanEmbeddingModel; | ||
| 49 | -import org.springaicommunity.qianfan.QianFanEmbeddingOptions; | ||
| 50 | -import org.springaicommunity.qianfan.QianFanImageModel; | ||
| 51 | -import org.springaicommunity.qianfan.api.QianFanApi; | ||
| 52 | -import org.springaicommunity.qianfan.api.QianFanImageApi; | ||
| 53 | -import org.springaicommunity.qianfan.autoconfigure.QianFanChatAutoConfiguration; | ||
| 54 | -import org.springaicommunity.qianfan.autoconfigure.QianFanEmbeddingAutoConfiguration; | ||
| 55 | import org.springframework.ai.azure.openai.AzureOpenAiChatModel; | 49 | import org.springframework.ai.azure.openai.AzureOpenAiChatModel; |
| 56 | import org.springframework.ai.azure.openai.AzureOpenAiEmbeddingModel; | 50 | import org.springframework.ai.azure.openai.AzureOpenAiEmbeddingModel; |
| 57 | import org.springframework.ai.chat.model.ChatModel; | 51 | import org.springframework.ai.chat.model.ChatModel; |
| @@ -63,27 +57,17 @@ import org.springframework.ai.embedding.BatchingStrategy; | @@ -63,27 +57,17 @@ import org.springframework.ai.embedding.BatchingStrategy; | ||
| 63 | import org.springframework.ai.embedding.EmbeddingModel; | 57 | import org.springframework.ai.embedding.EmbeddingModel; |
| 64 | import org.springframework.ai.embedding.observation.EmbeddingModelObservationConvention; | 58 | import org.springframework.ai.embedding.observation.EmbeddingModelObservationConvention; |
| 65 | import org.springframework.ai.image.ImageModel; | 59 | import org.springframework.ai.image.ImageModel; |
| 66 | -import org.springframework.ai.minimax.MiniMaxChatModel; | ||
| 67 | -import org.springframework.ai.minimax.MiniMaxChatOptions; | ||
| 68 | -import org.springframework.ai.minimax.MiniMaxEmbeddingModel; | ||
| 69 | -import org.springframework.ai.minimax.MiniMaxEmbeddingOptions; | ||
| 70 | -import org.springframework.ai.minimax.api.MiniMaxApi; | ||
| 71 | import org.springframework.ai.model.anthropic.autoconfigure.AnthropicChatAutoConfiguration; | 60 | import org.springframework.ai.model.anthropic.autoconfigure.AnthropicChatAutoConfiguration; |
| 72 | import org.springframework.ai.model.azure.openai.autoconfigure.AzureOpenAiChatAutoConfiguration; | 61 | import org.springframework.ai.model.azure.openai.autoconfigure.AzureOpenAiChatAutoConfiguration; |
| 73 | import org.springframework.ai.model.azure.openai.autoconfigure.AzureOpenAiEmbeddingAutoConfiguration; | 62 | import org.springframework.ai.model.azure.openai.autoconfigure.AzureOpenAiEmbeddingAutoConfiguration; |
| 74 | import org.springframework.ai.model.azure.openai.autoconfigure.AzureOpenAiEmbeddingProperties; | 63 | import org.springframework.ai.model.azure.openai.autoconfigure.AzureOpenAiEmbeddingProperties; |
| 75 | import org.springframework.ai.model.deepseek.autoconfigure.DeepSeekChatAutoConfiguration; | 64 | import org.springframework.ai.model.deepseek.autoconfigure.DeepSeekChatAutoConfiguration; |
| 76 | -import org.springframework.ai.model.minimax.autoconfigure.MiniMaxChatAutoConfiguration; | ||
| 77 | -import org.springframework.ai.model.minimax.autoconfigure.MiniMaxEmbeddingAutoConfiguration; | ||
| 78 | import org.springframework.ai.model.ollama.autoconfigure.OllamaChatAutoConfiguration; | 65 | import org.springframework.ai.model.ollama.autoconfigure.OllamaChatAutoConfiguration; |
| 79 | import org.springframework.ai.model.openai.autoconfigure.OpenAiChatAutoConfiguration; | 66 | import org.springframework.ai.model.openai.autoconfigure.OpenAiChatAutoConfiguration; |
| 80 | import org.springframework.ai.model.openai.autoconfigure.OpenAiEmbeddingAutoConfiguration; | 67 | import org.springframework.ai.model.openai.autoconfigure.OpenAiEmbeddingAutoConfiguration; |
| 81 | import org.springframework.ai.model.openai.autoconfigure.OpenAiImageAutoConfiguration; | 68 | import org.springframework.ai.model.openai.autoconfigure.OpenAiImageAutoConfiguration; |
| 82 | import org.springframework.ai.model.stabilityai.autoconfigure.StabilityAiImageAutoConfiguration; | 69 | import org.springframework.ai.model.stabilityai.autoconfigure.StabilityAiImageAutoConfiguration; |
| 83 | import org.springframework.ai.model.tool.ToolCallingManager; | 70 | import org.springframework.ai.model.tool.ToolCallingManager; |
| 84 | -import org.springframework.ai.model.zhipuai.autoconfigure.ZhiPuAiChatAutoConfiguration; | ||
| 85 | -import org.springframework.ai.model.zhipuai.autoconfigure.ZhiPuAiEmbeddingAutoConfiguration; | ||
| 86 | -import org.springframework.ai.model.zhipuai.autoconfigure.ZhiPuAiImageAutoConfiguration; | ||
| 87 | import org.springframework.ai.ollama.OllamaChatModel; | 71 | import org.springframework.ai.ollama.OllamaChatModel; |
| 88 | import org.springframework.ai.ollama.OllamaEmbeddingModel; | 72 | import org.springframework.ai.ollama.OllamaEmbeddingModel; |
| 89 | import org.springframework.ai.ollama.api.OllamaApi; | 73 | import org.springframework.ai.ollama.api.OllamaApi; |
| @@ -114,13 +98,9 @@ import org.springframework.ai.vectorstore.qdrant.autoconfigure.QdrantVectorStore | @@ -114,13 +98,9 @@ import org.springframework.ai.vectorstore.qdrant.autoconfigure.QdrantVectorStore | ||
| 114 | import org.springframework.ai.vectorstore.redis.RedisVectorStore; | 98 | import org.springframework.ai.vectorstore.redis.RedisVectorStore; |
| 115 | import org.springframework.ai.vectorstore.redis.autoconfigure.RedisVectorStoreAutoConfiguration; | 99 | import org.springframework.ai.vectorstore.redis.autoconfigure.RedisVectorStoreAutoConfiguration; |
| 116 | import org.springframework.ai.vectorstore.redis.autoconfigure.RedisVectorStoreProperties; | 100 | import org.springframework.ai.vectorstore.redis.autoconfigure.RedisVectorStoreProperties; |
| 117 | -import org.springframework.ai.zhipuai.*; | ||
| 118 | -import org.springframework.ai.zhipuai.api.ZhiPuAiApi; | ||
| 119 | -import org.springframework.ai.zhipuai.api.ZhiPuAiImageApi; | ||
| 120 | import org.springframework.beans.BeansException; | 101 | import org.springframework.beans.BeansException; |
| 121 | import org.springframework.beans.factory.ObjectProvider; | 102 | import org.springframework.beans.factory.ObjectProvider; |
| 122 | import org.springframework.boot.autoconfigure.data.redis.RedisProperties; | 103 | import org.springframework.boot.autoconfigure.data.redis.RedisProperties; |
| 123 | -import org.springframework.web.client.RestClient; | ||
| 124 | import redis.clients.jedis.JedisPooled; | 104 | import redis.clients.jedis.JedisPooled; |
| 125 | 105 | ||
| 126 | import java.io.File; | 106 | import java.io.File; |
| @@ -131,7 +111,6 @@ import java.util.Timer; | @@ -131,7 +111,6 @@ import java.util.Timer; | ||
| 131 | import java.util.TimerTask; | 111 | import java.util.TimerTask; |
| 132 | 112 | ||
| 133 | import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; | 113 | import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; |
| 134 | -import static org.springframework.ai.retry.RetryUtils.DEFAULT_RETRY_TEMPLATE; | ||
| 135 | 114 | ||
| 136 | /** | 115 | /** |
| 137 | * AI Model 模型工厂的实现类 | 116 | * AI Model 模型工厂的实现类 |
| @@ -141,7 +120,9 @@ import static org.springframework.ai.retry.RetryUtils.DEFAULT_RETRY_TEMPLATE; | @@ -141,7 +120,9 @@ import static org.springframework.ai.retry.RetryUtils.DEFAULT_RETRY_TEMPLATE; | ||
| 141 | public class AiModelFactoryImpl implements AiModelFactory { | 120 | public class AiModelFactoryImpl implements AiModelFactory { |
| 142 | 121 | ||
| 143 | @Override | 122 | @Override |
| 144 | - public ChatModel getOrCreateChatModel(AiPlatformEnum platform, String apiKey, String url) { | 123 | + public ChatModel getOrCreateChatModel(AiPlatformEnum platform, String rawApiKey, String rawUrl) { |
| 124 | + final String apiKey = resolveSpringPlaceholders(rawApiKey); | ||
| 125 | + final String url = resolveSpringPlaceholders(rawUrl); | ||
| 145 | String cacheKey = buildClientCacheKey(ChatModel.class, platform, apiKey, url); | 126 | String cacheKey = buildClientCacheKey(ChatModel.class, platform, apiKey, url); |
| 146 | return Singleton.get(cacheKey, (Func0<ChatModel>) () -> { | 127 | return Singleton.get(cacheKey, (Func0<ChatModel>) () -> { |
| 147 | // noinspection EnhancedSwitchMigration | 128 | // noinspection EnhancedSwitchMigration |
| @@ -164,6 +145,8 @@ public class AiModelFactoryImpl implements AiModelFactory { | @@ -164,6 +145,8 @@ public class AiModelFactoryImpl implements AiModelFactory { | ||
| 164 | return buildMiniMaxChatModel(apiKey, url); | 145 | return buildMiniMaxChatModel(apiKey, url); |
| 165 | case MOONSHOT: | 146 | case MOONSHOT: |
| 166 | return buildMoonshotChatModel(apiKey, url); | 147 | return buildMoonshotChatModel(apiKey, url); |
| 148 | + case STEP_FUN: | ||
| 149 | + return buildStepFunChatModel(apiKey, url); | ||
| 167 | case XING_HUO: | 150 | case XING_HUO: |
| 168 | return buildXingHuoChatModel(apiKey); | 151 | return buildXingHuoChatModel(apiKey); |
| 169 | case BAI_CHUAN: | 152 | case BAI_CHUAN: |
| @@ -179,7 +162,7 @@ public class AiModelFactoryImpl implements AiModelFactory { | @@ -179,7 +162,7 @@ public class AiModelFactoryImpl implements AiModelFactory { | ||
| 179 | case OLLAMA: | 162 | case OLLAMA: |
| 180 | return buildOllamaChatModel(url); | 163 | return buildOllamaChatModel(url); |
| 181 | case GROK: | 164 | case GROK: |
| 182 | - return buildGrokChatModel(apiKey,url); | 165 | + return buildGrokChatModel(apiKey, url); |
| 183 | default: | 166 | default: |
| 184 | throw new IllegalArgumentException(StrUtil.format("未知平台({})", platform)); | 167 | throw new IllegalArgumentException(StrUtil.format("未知平台({})", platform)); |
| 185 | } | 168 | } |
| @@ -193,7 +176,7 @@ public class AiModelFactoryImpl implements AiModelFactory { | @@ -193,7 +176,7 @@ public class AiModelFactoryImpl implements AiModelFactory { | ||
| 193 | case TONG_YI: | 176 | case TONG_YI: |
| 194 | return SpringUtil.getBean(DashScopeChatModel.class); | 177 | return SpringUtil.getBean(DashScopeChatModel.class); |
| 195 | case YI_YAN: | 178 | case YI_YAN: |
| 196 | - return SpringUtil.getBean(QianFanChatModel.class); | 179 | + return SpringUtil.getBean(YiYanChatModel.class); |
| 197 | case DEEP_SEEK: | 180 | case DEEP_SEEK: |
| 198 | return SpringUtil.getBean(DeepSeekChatModel.class); | 181 | return SpringUtil.getBean(DeepSeekChatModel.class); |
| 199 | case DOU_BAO: | 182 | case DOU_BAO: |
| @@ -203,11 +186,13 @@ public class AiModelFactoryImpl implements AiModelFactory { | @@ -203,11 +186,13 @@ public class AiModelFactoryImpl implements AiModelFactory { | ||
| 203 | case SILICON_FLOW: | 186 | case SILICON_FLOW: |
| 204 | return SpringUtil.getBean(SiliconFlowChatModel.class); | 187 | return SpringUtil.getBean(SiliconFlowChatModel.class); |
| 205 | case ZHI_PU: | 188 | case ZHI_PU: |
| 206 | - return SpringUtil.getBean(ZhiPuAiChatModel.class); | 189 | + return SpringUtil.getBean(ZhiPuChatModel.class); |
| 207 | case MINI_MAX: | 190 | case MINI_MAX: |
| 208 | return SpringUtil.getBean(MiniMaxChatModel.class); | 191 | return SpringUtil.getBean(MiniMaxChatModel.class); |
| 209 | case MOONSHOT: | 192 | case MOONSHOT: |
| 210 | return SpringUtil.getBean(MoonshotChatModel.class); | 193 | return SpringUtil.getBean(MoonshotChatModel.class); |
| 194 | + case STEP_FUN: | ||
| 195 | + return SpringUtil.getBean(StepFunChatModel.class); | ||
| 211 | case XING_HUO: | 196 | case XING_HUO: |
| 212 | return SpringUtil.getBean(XingHuoChatModel.class); | 197 | return SpringUtil.getBean(XingHuoChatModel.class); |
| 213 | case BAI_CHUAN: | 198 | case BAI_CHUAN: |
| @@ -222,6 +207,8 @@ public class AiModelFactoryImpl implements AiModelFactory { | @@ -222,6 +207,8 @@ public class AiModelFactoryImpl implements AiModelFactory { | ||
| 222 | return SpringUtil.getBean(GeminiChatModel.class); | 207 | return SpringUtil.getBean(GeminiChatModel.class); |
| 223 | case OLLAMA: | 208 | case OLLAMA: |
| 224 | return SpringUtil.getBean(OllamaChatModel.class); | 209 | return SpringUtil.getBean(OllamaChatModel.class); |
| 210 | + case GROK: | ||
| 211 | + return SpringUtil.getBean(GrokChatModel.class); | ||
| 225 | default: | 212 | default: |
| 226 | throw new IllegalArgumentException(StrUtil.format("未知平台({})", platform)); | 213 | throw new IllegalArgumentException(StrUtil.format("未知平台({})", platform)); |
| 227 | } | 214 | } |
| @@ -233,10 +220,6 @@ public class AiModelFactoryImpl implements AiModelFactory { | @@ -233,10 +220,6 @@ public class AiModelFactoryImpl implements AiModelFactory { | ||
| 233 | switch (platform) { | 220 | switch (platform) { |
| 234 | case TONG_YI: | 221 | case TONG_YI: |
| 235 | return SpringUtil.getBean(DashScopeImageModel.class); | 222 | return SpringUtil.getBean(DashScopeImageModel.class); |
| 236 | - case YI_YAN: | ||
| 237 | - return SpringUtil.getBean(QianFanImageModel.class); | ||
| 238 | - case ZHI_PU: | ||
| 239 | - return SpringUtil.getBean(ZhiPuAiImageModel.class); | ||
| 240 | case SILICON_FLOW: | 223 | case SILICON_FLOW: |
| 241 | return SpringUtil.getBean(SiliconFlowImageModel.class); | 224 | return SpringUtil.getBean(SiliconFlowImageModel.class); |
| 242 | case OPENAI: | 225 | case OPENAI: |
| @@ -249,19 +232,17 @@ public class AiModelFactoryImpl implements AiModelFactory { | @@ -249,19 +232,17 @@ public class AiModelFactoryImpl implements AiModelFactory { | ||
| 249 | } | 232 | } |
| 250 | 233 | ||
| 251 | @Override | 234 | @Override |
| 252 | - public ImageModel getOrCreateImageModel(AiPlatformEnum platform, String apiKey, String url) { | 235 | + public ImageModel getOrCreateImageModel(AiPlatformEnum platform, String rawApiKey, String rawUrl) { |
| 236 | + String apiKey = resolveSpringPlaceholders(rawApiKey); | ||
| 237 | + String url = resolveSpringPlaceholders(rawUrl); | ||
| 253 | // noinspection EnhancedSwitchMigration | 238 | // noinspection EnhancedSwitchMigration |
| 254 | switch (platform) { | 239 | switch (platform) { |
| 255 | case TONG_YI: | 240 | case TONG_YI: |
| 256 | return buildTongYiImagesModel(apiKey); | 241 | return buildTongYiImagesModel(apiKey); |
| 257 | - case YI_YAN: | ||
| 258 | - return buildQianFanImageModel(apiKey); | ||
| 259 | - case ZHI_PU: | ||
| 260 | - return buildZhiPuAiImageModel(apiKey, url); | ||
| 261 | case OPENAI: | 242 | case OPENAI: |
| 262 | return buildOpenAiImageModel(apiKey, url); | 243 | return buildOpenAiImageModel(apiKey, url); |
| 263 | case SILICON_FLOW: | 244 | case SILICON_FLOW: |
| 264 | - return buildSiliconFlowImageModel(apiKey,url); | 245 | + return buildSiliconFlowImageModel(apiKey, url); |
| 265 | case STABLE_DIFFUSION: | 246 | case STABLE_DIFFUSION: |
| 266 | return buildStabilityAiImageModel(apiKey, url); | 247 | return buildStabilityAiImageModel(apiKey, url); |
| 267 | default: | 248 | default: |
| @@ -270,9 +251,11 @@ public class AiModelFactoryImpl implements AiModelFactory { | @@ -270,9 +251,11 @@ public class AiModelFactoryImpl implements AiModelFactory { | ||
| 270 | } | 251 | } |
| 271 | 252 | ||
| 272 | @Override | 253 | @Override |
| 273 | - public MidjourneyApi getOrCreateMidjourneyApi(String apiKey, String url) { | ||
| 274 | - String cacheKey = buildClientCacheKey(MidjourneyApi.class, AiPlatformEnum.MIDJOURNEY.getPlatform(), apiKey, | ||
| 275 | - url); | 254 | + public MidjourneyApi getOrCreateMidjourneyApi(String rawApiKey, String rawUrl) { |
| 255 | + final String apiKey = resolveSpringPlaceholders(rawApiKey); | ||
| 256 | + final String url = resolveSpringPlaceholders(rawUrl); | ||
| 257 | + String cacheKey = buildClientCacheKey(MidjourneyApi.class, AiPlatformEnum.MIDJOURNEY.getPlatform(), | ||
| 258 | + apiKey, url); | ||
| 276 | return Singleton.get(cacheKey, (Func0<MidjourneyApi>) () -> { | 259 | return Singleton.get(cacheKey, (Func0<MidjourneyApi>) () -> { |
| 277 | YudaoAiProperties.Midjourney properties = SpringUtil.getBean(YudaoAiProperties.class) | 260 | YudaoAiProperties.Midjourney properties = SpringUtil.getBean(YudaoAiProperties.class) |
| 278 | .getMidjourney(); | 261 | .getMidjourney(); |
| @@ -281,25 +264,23 @@ public class AiModelFactoryImpl implements AiModelFactory { | @@ -281,25 +264,23 @@ public class AiModelFactoryImpl implements AiModelFactory { | ||
| 281 | } | 264 | } |
| 282 | 265 | ||
| 283 | @Override | 266 | @Override |
| 284 | - public SunoApi getOrCreateSunoApi(String apiKey, String url) { | 267 | + public SunoApi getOrCreateSunoApi(String rawApiKey, String rawUrl) { |
| 268 | + final String apiKey = resolveSpringPlaceholders(rawApiKey); | ||
| 269 | + final String url = resolveSpringPlaceholders(rawUrl); | ||
| 285 | String cacheKey = buildClientCacheKey(SunoApi.class, AiPlatformEnum.SUNO.getPlatform(), apiKey, url); | 270 | String cacheKey = buildClientCacheKey(SunoApi.class, AiPlatformEnum.SUNO.getPlatform(), apiKey, url); |
| 286 | return Singleton.get(cacheKey, (Func0<SunoApi>) () -> new SunoApi(url)); | 271 | return Singleton.get(cacheKey, (Func0<SunoApi>) () -> new SunoApi(url)); |
| 287 | } | 272 | } |
| 288 | 273 | ||
| 289 | @Override | 274 | @Override |
| 290 | @SuppressWarnings("EnhancedSwitchMigration") | 275 | @SuppressWarnings("EnhancedSwitchMigration") |
| 291 | - public EmbeddingModel getOrCreateEmbeddingModel(AiPlatformEnum platform, String apiKey, String url, String model) { | 276 | + public EmbeddingModel getOrCreateEmbeddingModel(AiPlatformEnum platform, String rawApiKey, String rawUrl, String model) { |
| 277 | + final String apiKey = resolveSpringPlaceholders(rawApiKey); | ||
| 278 | + final String url = resolveSpringPlaceholders(rawUrl); | ||
| 292 | String cacheKey = buildClientCacheKey(EmbeddingModel.class, platform, apiKey, url, model); | 279 | String cacheKey = buildClientCacheKey(EmbeddingModel.class, platform, apiKey, url, model); |
| 293 | return Singleton.get(cacheKey, (Func0<EmbeddingModel>) () -> { | 280 | return Singleton.get(cacheKey, (Func0<EmbeddingModel>) () -> { |
| 294 | switch (platform) { | 281 | switch (platform) { |
| 295 | case TONG_YI: | 282 | case TONG_YI: |
| 296 | return buildTongYiEmbeddingModel(apiKey, model); | 283 | return buildTongYiEmbeddingModel(apiKey, model); |
| 297 | - case YI_YAN: | ||
| 298 | - return buildYiYanEmbeddingModel(apiKey, model); | ||
| 299 | - case ZHI_PU: | ||
| 300 | - return buildZhiPuEmbeddingModel(apiKey, url, model); | ||
| 301 | - case MINI_MAX: | ||
| 302 | - return buildMiniMaxEmbeddingModel(apiKey, url, model); | ||
| 303 | case OPENAI: | 284 | case OPENAI: |
| 304 | return buildOpenAiEmbeddingModel(apiKey, url, model); | 285 | return buildOpenAiEmbeddingModel(apiKey, url, model); |
| 305 | case AZURE_OPENAI: | 286 | case AZURE_OPENAI: |
| @@ -341,6 +322,11 @@ public class AiModelFactoryImpl implements AiModelFactory { | @@ -341,6 +322,11 @@ public class AiModelFactoryImpl implements AiModelFactory { | ||
| 341 | return StrUtil.format("{}#{}", clazz.getName(), ArrayUtil.join(params, "_")); | 322 | return StrUtil.format("{}#{}", clazz.getName(), ArrayUtil.join(params, "_")); |
| 342 | } | 323 | } |
| 343 | 324 | ||
| 325 | + private static String resolveSpringPlaceholders(String value) { | ||
| 326 | + // yml 配置的占位符由 Spring 自动解析;DB 里保存的 ${xxx} 需要在这里手动解析。 | ||
| 327 | + return AiUtils.resolveSpringPlaceholders(value); | ||
| 328 | + } | ||
| 329 | + | ||
| 344 | // ========== 各种创建 spring-ai 客户端的方法 ========== | 330 | // ========== 各种创建 spring-ai 客户端的方法 ========== |
| 345 | 331 | ||
| 346 | /** | 332 | /** |
| @@ -367,30 +353,10 @@ public class AiModelFactoryImpl implements AiModelFactory { | @@ -367,30 +353,10 @@ public class AiModelFactoryImpl implements AiModelFactory { | ||
| 367 | .build(); | 353 | .build(); |
| 368 | } | 354 | } |
| 369 | 355 | ||
| 370 | - /** | ||
| 371 | - * 可参考 {@link QianFanChatAutoConfiguration} 的 qianFanChatModel 方法 | ||
| 372 | - */ | ||
| 373 | - private static QianFanChatModel buildYiYanChatModel(String key) { | ||
| 374 | - // TODO spring ai qianfan 有 bug,无法使用 https://github.com/spring-ai-community/qianfan/issues/6 | ||
| 375 | - List<String> keys = StrUtil.split(key, '|'); | ||
| 376 | - Assert.equals(keys.size(), 2, "YiYanChatClient 的密钥需要 (appKey|secretKey) 格式"); | ||
| 377 | - String appKey = keys.get(0); | ||
| 378 | - String secretKey = keys.get(1); | ||
| 379 | - QianFanApi qianFanApi = new QianFanApi(appKey, secretKey); | ||
| 380 | - return new QianFanChatModel(qianFanApi); | ||
| 381 | - } | ||
| 382 | - | ||
| 383 | - /** | ||
| 384 | - * 可参考 {@link QianFanEmbeddingAutoConfiguration} 的 qianFanImageModel 方法 | ||
| 385 | - */ | ||
| 386 | - private QianFanImageModel buildQianFanImageModel(String key) { | ||
| 387 | - // TODO spring ai qianfan 有 bug,无法使用 https://github.com/spring-ai-community/qianfan/issues/6 | ||
| 388 | - List<String> keys = StrUtil.split(key, '|'); | ||
| 389 | - Assert.equals(keys.size(), 2, "YiYanChatClient 的密钥需要 (appKey|secretKey) 格式"); | ||
| 390 | - String appKey = keys.get(0); | ||
| 391 | - String secretKey = keys.get(1); | ||
| 392 | - QianFanImageApi qianFanApi = new QianFanImageApi(appKey, secretKey); | ||
| 393 | - return new QianFanImageModel(qianFanApi); | 356 | + private ChatModel buildYiYanChatModel(String apiKey) { |
| 357 | + YudaoAiProperties.YiYan properties = new YudaoAiProperties.YiYan() | ||
| 358 | + .setApiKey(apiKey); | ||
| 359 | + return new AiAutoConfiguration().buildYiYanChatClient(properties); | ||
| 394 | } | 360 | } |
| 395 | 361 | ||
| 396 | /** | 362 | /** |
| @@ -435,62 +401,47 @@ public class AiModelFactoryImpl implements AiModelFactory { | @@ -435,62 +401,47 @@ public class AiModelFactoryImpl implements AiModelFactory { | ||
| 435 | } | 401 | } |
| 436 | 402 | ||
| 437 | /** | 403 | /** |
| 438 | - * 可参考 {@link ZhiPuAiChatAutoConfiguration} 的 zhiPuAiChatModel 方法 | 404 | + * 可参考 {@link AiAutoConfiguration#zhiPuChatClient(YudaoAiProperties)} |
| 439 | */ | 405 | */ |
| 440 | - private ZhiPuAiChatModel buildZhiPuChatModel(String apiKey, String url) { | ||
| 441 | - ZhiPuAiApi.Builder zhiPuAiApiBuilder = ZhiPuAiApi.builder().apiKey(apiKey); | ||
| 442 | - if (StrUtil.isNotEmpty(url)) { | ||
| 443 | - zhiPuAiApiBuilder.baseUrl(url); | ||
| 444 | - } | ||
| 445 | - ZhiPuAiChatOptions options = ZhiPuAiChatOptions.builder().model(ZhiPuAiApi.DEFAULT_CHAT_MODEL).temperature(0.7).build(); | ||
| 446 | - return new ZhiPuAiChatModel(zhiPuAiApiBuilder.build(), options, getToolCallingManager(), DEFAULT_RETRY_TEMPLATE, | ||
| 447 | - getObservationRegistry().getIfAvailable()); | 406 | + private ZhiPuChatModel buildZhiPuChatModel(String apiKey, String url) { |
| 407 | + YudaoAiProperties.ZhiPu properties = new YudaoAiProperties.ZhiPu() | ||
| 408 | + .setBaseUrl(url).setApiKey(apiKey); | ||
| 409 | + return new AiAutoConfiguration().buildZhiPuChatClient(properties); | ||
| 448 | } | 410 | } |
| 449 | 411 | ||
| 450 | /** | 412 | /** |
| 451 | - * 可参考 {@link ZhiPuAiImageAutoConfiguration} 的 zhiPuAiImageModel 方法 | 413 | + * 可参考 {@link AiAutoConfiguration#miniMaxChatClient(YudaoAiProperties)} |
| 452 | */ | 414 | */ |
| 453 | - private ZhiPuAiImageModel buildZhiPuAiImageModel(String apiKey, String url) { | ||
| 454 | - ZhiPuAiImageApi zhiPuAiApi = StrUtil.isEmpty(url) ? new ZhiPuAiImageApi(apiKey) | ||
| 455 | - : new ZhiPuAiImageApi(url, apiKey, RestClient.builder()); | ||
| 456 | - return new ZhiPuAiImageModel(zhiPuAiApi); | 415 | + private MiniMaxChatModel buildMiniMaxChatModel(String apiKey, String url) { |
| 416 | + YudaoAiProperties.MiniMax properties = new YudaoAiProperties.MiniMax() | ||
| 417 | + .setBaseUrl(url).setApiKey(apiKey); | ||
| 418 | + return new AiAutoConfiguration().buildMiniMaxChatClient(properties); | ||
| 457 | } | 419 | } |
| 458 | 420 | ||
| 459 | /** | 421 | /** |
| 460 | - * 可参考 {@link MiniMaxChatAutoConfiguration} 的 miniMaxChatModel 方法 | 422 | + * 可参考 {@link AiAutoConfiguration#moonshotChatClient(YudaoAiProperties)} |
| 461 | */ | 423 | */ |
| 462 | - private MiniMaxChatModel buildMiniMaxChatModel(String apiKey, String url) { | ||
| 463 | - MiniMaxApi miniMaxApi = StrUtil.isEmpty(url) ? new MiniMaxApi(apiKey) | ||
| 464 | - : new MiniMaxApi(url, apiKey); | ||
| 465 | - MiniMaxChatOptions options = MiniMaxChatOptions.builder().model(MiniMaxApi.DEFAULT_CHAT_MODEL).temperature(0.7).build(); | ||
| 466 | - return new MiniMaxChatModel(miniMaxApi, options, getToolCallingManager(), DEFAULT_RETRY_TEMPLATE); | 424 | + private MoonshotChatModel buildMoonshotChatModel(String apiKey, String url) { |
| 425 | + YudaoAiProperties.Moonshot properties = new YudaoAiProperties.Moonshot() | ||
| 426 | + .setBaseUrl(url).setApiKey(apiKey); | ||
| 427 | + return new AiAutoConfiguration().buildMoonshotChatClient(properties); | ||
| 467 | } | 428 | } |
| 468 | 429 | ||
| 469 | /** | 430 | /** |
| 470 | - * 可参考 {@link MoonshotChatAutoConfiguration} 的 moonshotChatModel 方法 | 431 | + * 可参考 {@link AiAutoConfiguration#stepFunChatClient(YudaoAiProperties)} |
| 471 | */ | 432 | */ |
| 472 | - private MoonshotChatModel buildMoonshotChatModel(String apiKey, String url) { | ||
| 473 | - MoonshotApi.Builder moonshotApiBuilder = MoonshotApi.builder() | ||
| 474 | - .apiKey(apiKey); | ||
| 475 | - if (StrUtil.isNotEmpty(url)) { | ||
| 476 | - moonshotApiBuilder.baseUrl(url); | ||
| 477 | - } | ||
| 478 | - MoonshotChatOptions options = MoonshotChatOptions.builder().model(MoonshotApi.DEFAULT_CHAT_MODEL).build(); | ||
| 479 | - return MoonshotChatModel.builder() | ||
| 480 | - .moonshotApi(moonshotApiBuilder.build()) | ||
| 481 | - .defaultOptions(options) | ||
| 482 | - .toolCallingManager(getToolCallingManager()) | ||
| 483 | - .build(); | 433 | + private StepFunChatModel buildStepFunChatModel(String apiKey, String url) { |
| 434 | + YudaoAiProperties.StepFun properties = new YudaoAiProperties.StepFun() | ||
| 435 | + .setBaseUrl(url).setApiKey(apiKey); | ||
| 436 | + return new AiAutoConfiguration().buildStepFunChatClient(properties); | ||
| 484 | } | 437 | } |
| 485 | 438 | ||
| 486 | /** | 439 | /** |
| 487 | * 可参考 {@link AiAutoConfiguration#xingHuoChatClient(YudaoAiProperties)} | 440 | * 可参考 {@link AiAutoConfiguration#xingHuoChatClient(YudaoAiProperties)} |
| 488 | */ | 441 | */ |
| 489 | - private static XingHuoChatModel buildXingHuoChatModel(String key) { | ||
| 490 | - List<String> keys = StrUtil.split(key, '|'); | ||
| 491 | - Assert.equals(keys.size(), 2, "XingHuoChatClient 的密钥需要 (appKey|secretKey) 格式"); | 442 | + private static XingHuoChatModel buildXingHuoChatModel(String apiKey) { |
| 492 | YudaoAiProperties.XingHuo properties = new YudaoAiProperties.XingHuo() | 443 | YudaoAiProperties.XingHuo properties = new YudaoAiProperties.XingHuo() |
| 493 | - .setAppKey(keys.get(0)).setSecretKey(keys.get(1)); | 444 | + .setApiKey(apiKey).setModel(XingHuoChatModel.MODEL_DEFAULT); |
| 494 | return new AiAutoConfiguration().buildXingHuoChatClient(properties); | 445 | return new AiAutoConfiguration().buildXingHuoChatClient(properties); |
| 495 | } | 446 | } |
| 496 | 447 | ||
| @@ -590,7 +541,7 @@ public class AiModelFactoryImpl implements AiModelFactory { | @@ -590,7 +541,7 @@ public class AiModelFactoryImpl implements AiModelFactory { | ||
| 590 | return new StabilityAiImageModel(stabilityAiApi); | 541 | return new StabilityAiImageModel(stabilityAiApi); |
| 591 | } | 542 | } |
| 592 | 543 | ||
| 593 | - private ChatModel buildGrokChatModel(String apiKey,String url) { | 544 | + private GrokChatModel buildGrokChatModel(String apiKey, String url) { |
| 594 | YudaoAiProperties.Grok properties = new YudaoAiProperties.Grok() | 545 | YudaoAiProperties.Grok properties = new YudaoAiProperties.Grok() |
| 595 | .setBaseUrl(url) | 546 | .setBaseUrl(url) |
| 596 | .setApiKey(apiKey); | 547 | .setApiKey(apiKey); |
| @@ -608,41 +559,6 @@ public class AiModelFactoryImpl implements AiModelFactory { | @@ -608,41 +559,6 @@ public class AiModelFactoryImpl implements AiModelFactory { | ||
| 608 | return new DashScopeEmbeddingModel(dashScopeApi, MetadataMode.EMBED, dashScopeEmbeddingOptions); | 559 | return new DashScopeEmbeddingModel(dashScopeApi, MetadataMode.EMBED, dashScopeEmbeddingOptions); |
| 609 | } | 560 | } |
| 610 | 561 | ||
| 611 | - /** | ||
| 612 | - * 可参考 {@link ZhiPuAiEmbeddingAutoConfiguration} 的 zhiPuAiEmbeddingModel 方法 | ||
| 613 | - */ | ||
| 614 | - private ZhiPuAiEmbeddingModel buildZhiPuEmbeddingModel(String apiKey, String url, String model) { | ||
| 615 | - ZhiPuAiApi.Builder zhiPuAiApiBuilder = ZhiPuAiApi.builder().apiKey(apiKey); | ||
| 616 | - if (StrUtil.isNotEmpty(url)) { | ||
| 617 | - zhiPuAiApiBuilder.baseUrl(url); | ||
| 618 | - } | ||
| 619 | - ZhiPuAiEmbeddingOptions zhiPuAiEmbeddingOptions = ZhiPuAiEmbeddingOptions.builder().model(model).build(); | ||
| 620 | - return new ZhiPuAiEmbeddingModel(zhiPuAiApiBuilder.build(), MetadataMode.EMBED, zhiPuAiEmbeddingOptions); | ||
| 621 | - } | ||
| 622 | - | ||
| 623 | - /** | ||
| 624 | - * 可参考 {@link MiniMaxEmbeddingAutoConfiguration} 的 miniMaxEmbeddingModel 方法 | ||
| 625 | - */ | ||
| 626 | - private EmbeddingModel buildMiniMaxEmbeddingModel(String apiKey, String url, String model) { | ||
| 627 | - MiniMaxApi miniMaxApi = StrUtil.isEmpty(url)? new MiniMaxApi(apiKey) | ||
| 628 | - : new MiniMaxApi(url, apiKey); | ||
| 629 | - MiniMaxEmbeddingOptions miniMaxEmbeddingOptions = MiniMaxEmbeddingOptions.builder().model(model).build(); | ||
| 630 | - return new MiniMaxEmbeddingModel(miniMaxApi, MetadataMode.EMBED, miniMaxEmbeddingOptions); | ||
| 631 | - } | ||
| 632 | - | ||
| 633 | - /** | ||
| 634 | - * 可参考 {@link QianFanEmbeddingAutoConfiguration} 的 qianFanEmbeddingModel 方法 | ||
| 635 | - */ | ||
| 636 | - private QianFanEmbeddingModel buildYiYanEmbeddingModel(String key, String model) { | ||
| 637 | - List<String> keys = StrUtil.split(key, '|'); | ||
| 638 | - Assert.equals(keys.size(), 2, "YiYanChatClient 的密钥需要 (appKey|secretKey) 格式"); | ||
| 639 | - String appKey = keys.get(0); | ||
| 640 | - String secretKey = keys.get(1); | ||
| 641 | - QianFanApi qianFanApi = new QianFanApi(appKey, secretKey); | ||
| 642 | - QianFanEmbeddingOptions qianFanEmbeddingOptions = QianFanEmbeddingOptions.builder().model(model).build(); | ||
| 643 | - return new QianFanEmbeddingModel(qianFanApi, MetadataMode.EMBED, qianFanEmbeddingOptions); | ||
| 644 | - } | ||
| 645 | - | ||
| 646 | private OllamaEmbeddingModel buildOllamaEmbeddingModel(String url, String model) { | 562 | private OllamaEmbeddingModel buildOllamaEmbeddingModel(String url, String model) { |
| 647 | OllamaApi ollamaApi = OllamaApi.builder().baseUrl(url).build(); | 563 | OllamaApi ollamaApi = OllamaApi.builder().baseUrl(url).build(); |
| 648 | OllamaEmbeddingOptions ollamaOptions = OllamaEmbeddingOptions.builder().model(model).build(); | 564 | OllamaEmbeddingOptions ollamaOptions = OllamaEmbeddingOptions.builder().model(model).build(); |
yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/baichuan/BaiChuanChatModel.java
| @@ -6,7 +6,7 @@ import org.springframework.ai.chat.model.ChatModel; | @@ -6,7 +6,7 @@ import org.springframework.ai.chat.model.ChatModel; | ||
| 6 | import org.springframework.ai.chat.model.ChatResponse; | 6 | import org.springframework.ai.chat.model.ChatResponse; |
| 7 | import org.springframework.ai.chat.prompt.ChatOptions; | 7 | import org.springframework.ai.chat.prompt.ChatOptions; |
| 8 | import org.springframework.ai.chat.prompt.Prompt; | 8 | import org.springframework.ai.chat.prompt.Prompt; |
| 9 | -import org.springframework.ai.openai.OpenAiChatModel; | 9 | +import org.springframework.ai.deepseek.DeepSeekChatModel; |
| 10 | import reactor.core.publisher.Flux; | 10 | import reactor.core.publisher.Flux; |
| 11 | 11 | ||
| 12 | /** | 12 | /** |
| @@ -20,26 +20,28 @@ public class BaiChuanChatModel implements ChatModel { | @@ -20,26 +20,28 @@ public class BaiChuanChatModel implements ChatModel { | ||
| 20 | 20 | ||
| 21 | public static final String BASE_URL = "https://api.baichuan-ai.com"; | 21 | public static final String BASE_URL = "https://api.baichuan-ai.com"; |
| 22 | 22 | ||
| 23 | - public static final String MODEL_DEFAULT = "Baichuan4-Turbo"; | 23 | + public static final String COMPLETE_PATH = "/v1/chat/completions"; |
| 24 | + | ||
| 25 | + public static final String MODEL_DEFAULT = "Baichuan-M3"; | ||
| 24 | 26 | ||
| 25 | /** | 27 | /** |
| 26 | - * 兼容 OpenAI 接口,进行复用 | 28 | + * 兼容 OpenAI 接口,复用 DeepSeek 客户端 |
| 27 | */ | 29 | */ |
| 28 | - private final OpenAiChatModel openAiChatModel; | 30 | + private final DeepSeekChatModel deepSeekChatModel; |
| 29 | 31 | ||
| 30 | @Override | 32 | @Override |
| 31 | public ChatResponse call(Prompt prompt) { | 33 | public ChatResponse call(Prompt prompt) { |
| 32 | - return openAiChatModel.call(prompt); | 34 | + return deepSeekChatModel.call(prompt); |
| 33 | } | 35 | } |
| 34 | 36 | ||
| 35 | @Override | 37 | @Override |
| 36 | public Flux<ChatResponse> stream(Prompt prompt) { | 38 | public Flux<ChatResponse> stream(Prompt prompt) { |
| 37 | - return openAiChatModel.stream(prompt); | 39 | + return deepSeekChatModel.stream(prompt); |
| 38 | } | 40 | } |
| 39 | 41 | ||
| 40 | @Override | 42 | @Override |
| 41 | public ChatOptions getDefaultOptions() { | 43 | public ChatOptions getDefaultOptions() { |
| 42 | - return openAiChatModel.getDefaultOptions(); | 44 | + return deepSeekChatModel.getDefaultOptions(); |
| 43 | } | 45 | } |
| 44 | 46 | ||
| 45 | } | 47 | } |
yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/doubao/DouBaoChatModel.java
| @@ -20,7 +20,7 @@ public class DouBaoChatModel implements ChatModel { | @@ -20,7 +20,7 @@ public class DouBaoChatModel implements ChatModel { | ||
| 20 | public static final String BASE_URL = "https://ark.cn-beijing.volces.com/api"; | 20 | public static final String BASE_URL = "https://ark.cn-beijing.volces.com/api"; |
| 21 | public static final String COMPLETE_PATH = "/v3/chat/completions"; | 21 | public static final String COMPLETE_PATH = "/v3/chat/completions"; |
| 22 | 22 | ||
| 23 | - public static final String MODEL_DEFAULT = "doubao-1-5-lite-32k-250115"; | 23 | + public static final String MODEL_DEFAULT = "doubao-seed-2-1-turbo-260628"; |
| 24 | 24 | ||
| 25 | /** | 25 | /** |
| 26 | * 兼容 OpenAI 接口,进行复用 | 26 | * 兼容 OpenAI 接口,进行复用 |
yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/grok/GrokChatModel.java
| @@ -19,7 +19,7 @@ public class GrokChatModel implements ChatModel { | @@ -19,7 +19,7 @@ public class GrokChatModel implements ChatModel { | ||
| 19 | 19 | ||
| 20 | public static final String BASE_URL = "https://api.x.ai"; | 20 | public static final String BASE_URL = "https://api.x.ai"; |
| 21 | public static final String COMPLETE_PATH = "/v1/chat/completions"; | 21 | public static final String COMPLETE_PATH = "/v1/chat/completions"; |
| 22 | - public static final String MODEL_DEFAULT = "grok-4-fast-reasoning"; | 22 | + public static final String MODEL_DEFAULT = "grok-4.3"; |
| 23 | 23 | ||
| 24 | /** | 24 | /** |
| 25 | * 兼容 OpenAI 接口,进行复用 | 25 | * 兼容 OpenAI 接口,进行复用 |
yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/hunyuan/HunYuanChatModel.java
| @@ -9,10 +9,9 @@ import org.springframework.ai.chat.prompt.Prompt; | @@ -9,10 +9,9 @@ import org.springframework.ai.chat.prompt.Prompt; | ||
| 9 | import reactor.core.publisher.Flux; | 9 | import reactor.core.publisher.Flux; |
| 10 | 10 | ||
| 11 | /** | 11 | /** |
| 12 | - * 腾云混元 {@link ChatModel} 实现类 | 12 | + * 腾讯混元 {@link ChatModel} 实现类 |
| 13 | * | 13 | * |
| 14 | - * 1. 混元大模型:基于 <a href="https://cloud.tencent.com/document/product/1729/111007">知识引擎原子能力</a> 实现 | ||
| 15 | - * 2. 知识引擎原子能力:基于 <a href="https://cloud.tencent.com/document/product/1772/115969">知识引擎原子能力</a> 实现 | 14 | + * 基于 <a href="https://cloud.tencent.com/document/product/1823/132252">TokenHub OpenAI 兼容接口</a> 实现 |
| 16 | * | 15 | * |
| 17 | * @author fansili | 16 | * @author fansili |
| 18 | */ | 17 | */ |
| @@ -20,14 +19,10 @@ import reactor.core.publisher.Flux; | @@ -20,14 +19,10 @@ import reactor.core.publisher.Flux; | ||
| 20 | @RequiredArgsConstructor | 19 | @RequiredArgsConstructor |
| 21 | public class HunYuanChatModel implements ChatModel { | 20 | public class HunYuanChatModel implements ChatModel { |
| 22 | 21 | ||
| 23 | - public static final String BASE_URL = "https://api.hunyuan.cloud.tencent.com"; | 22 | + public static final String BASE_URL = "https://tokenhub.tencentmaas.com"; |
| 24 | public static final String COMPLETE_PATH = "/v1/chat/completions"; | 23 | public static final String COMPLETE_PATH = "/v1/chat/completions"; |
| 25 | 24 | ||
| 26 | - public static final String MODEL_DEFAULT = "hunyuan-turbo"; | ||
| 27 | - | ||
| 28 | - public static final String DEEP_SEEK_BASE_URL = "https://api.lkeap.cloud.tencent.com"; | ||
| 29 | - | ||
| 30 | - public static final String DEEP_SEEK_MODEL_DEFAULT = "deepseek-v3"; | 25 | + public static final String MODEL_DEFAULT = "hy3-preview"; |
| 31 | 26 | ||
| 32 | /** | 27 | /** |
| 33 | * 兼容 OpenAI 接口,进行复用 | 28 | * 兼容 OpenAI 接口,进行复用 |
yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/minimax/MiniMaxChatModel.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.ai.framework.ai.core.model.minimax; | ||
| 2 | + | ||
| 3 | +import lombok.RequiredArgsConstructor; | ||
| 4 | +import lombok.extern.slf4j.Slf4j; | ||
| 5 | +import org.springframework.ai.chat.model.ChatModel; | ||
| 6 | +import org.springframework.ai.chat.model.ChatResponse; | ||
| 7 | +import org.springframework.ai.chat.prompt.ChatOptions; | ||
| 8 | +import org.springframework.ai.chat.prompt.Prompt; | ||
| 9 | +import org.springframework.ai.deepseek.DeepSeekChatModel; | ||
| 10 | +import reactor.core.publisher.Flux; | ||
| 11 | + | ||
| 12 | +/** | ||
| 13 | + * MiniMax {@link ChatModel} 实现类 | ||
| 14 | + * | ||
| 15 | + * @author 芋道源码 | ||
| 16 | + */ | ||
| 17 | +@Slf4j | ||
| 18 | +@RequiredArgsConstructor | ||
| 19 | +public class MiniMaxChatModel implements ChatModel { | ||
| 20 | + | ||
| 21 | + public static final String BASE_URL = "https://api.minimaxi.com/v1"; | ||
| 22 | + | ||
| 23 | + public static final String MODEL_DEFAULT = "MiniMax-M3"; | ||
| 24 | + | ||
| 25 | + /** | ||
| 26 | + * 兼容 OpenAI 接口,复用 DeepSeek 客户端 | ||
| 27 | + */ | ||
| 28 | + private final DeepSeekChatModel deepSeekChatModel; | ||
| 29 | + | ||
| 30 | + @Override | ||
| 31 | + public ChatResponse call(Prompt prompt) { | ||
| 32 | + return deepSeekChatModel.call(prompt); | ||
| 33 | + } | ||
| 34 | + | ||
| 35 | + @Override | ||
| 36 | + public Flux<ChatResponse> stream(Prompt prompt) { | ||
| 37 | + return deepSeekChatModel.stream(prompt); | ||
| 38 | + } | ||
| 39 | + | ||
| 40 | + @Override | ||
| 41 | + public ChatOptions getDefaultOptions() { | ||
| 42 | + return deepSeekChatModel.getDefaultOptions(); | ||
| 43 | + } | ||
| 44 | + | ||
| 45 | +} |
yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/moonshot/MoonshotChatModel.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.ai.framework.ai.core.model.moonshot; | ||
| 2 | + | ||
| 3 | +import lombok.RequiredArgsConstructor; | ||
| 4 | +import lombok.extern.slf4j.Slf4j; | ||
| 5 | +import org.springframework.ai.chat.model.ChatModel; | ||
| 6 | +import org.springframework.ai.chat.model.ChatResponse; | ||
| 7 | +import org.springframework.ai.chat.prompt.ChatOptions; | ||
| 8 | +import org.springframework.ai.chat.prompt.Prompt; | ||
| 9 | +import org.springframework.ai.deepseek.DeepSeekChatModel; | ||
| 10 | +import reactor.core.publisher.Flux; | ||
| 11 | + | ||
| 12 | +/** | ||
| 13 | + * 月之暗面 {@link ChatModel} 实现类 | ||
| 14 | + * | ||
| 15 | + * @author 芋道源码 | ||
| 16 | + */ | ||
| 17 | +@Slf4j | ||
| 18 | +@RequiredArgsConstructor | ||
| 19 | +public class MoonshotChatModel implements ChatModel { | ||
| 20 | + | ||
| 21 | + public static final String BASE_URL = "https://api.moonshot.cn"; | ||
| 22 | + | ||
| 23 | + public static final String COMPLETE_PATH = "/v1/chat/completions"; | ||
| 24 | + | ||
| 25 | + public static final String MODEL_DEFAULT = "kimi-k2.6"; | ||
| 26 | + | ||
| 27 | + /** | ||
| 28 | + * 兼容 OpenAI 接口,复用 DeepSeek 客户端 | ||
| 29 | + */ | ||
| 30 | + private final DeepSeekChatModel deepSeekChatModel; | ||
| 31 | + | ||
| 32 | + @Override | ||
| 33 | + public ChatResponse call(Prompt prompt) { | ||
| 34 | + return deepSeekChatModel.call(prompt); | ||
| 35 | + } | ||
| 36 | + | ||
| 37 | + @Override | ||
| 38 | + public Flux<ChatResponse> stream(Prompt prompt) { | ||
| 39 | + return deepSeekChatModel.stream(prompt); | ||
| 40 | + } | ||
| 41 | + | ||
| 42 | + @Override | ||
| 43 | + public ChatOptions getDefaultOptions() { | ||
| 44 | + return deepSeekChatModel.getDefaultOptions(); | ||
| 45 | + } | ||
| 46 | + | ||
| 47 | +} |
yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/stepfun/StepFunChatModel.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.ai.framework.ai.core.model.stepfun; | ||
| 2 | + | ||
| 3 | +import lombok.RequiredArgsConstructor; | ||
| 4 | +import lombok.extern.slf4j.Slf4j; | ||
| 5 | +import org.springframework.ai.chat.model.ChatModel; | ||
| 6 | +import org.springframework.ai.chat.model.ChatResponse; | ||
| 7 | +import org.springframework.ai.chat.prompt.ChatOptions; | ||
| 8 | +import org.springframework.ai.chat.prompt.Prompt; | ||
| 9 | +import org.springframework.ai.deepseek.DeepSeekChatModel; | ||
| 10 | +import reactor.core.publisher.Flux; | ||
| 11 | + | ||
| 12 | +/** | ||
| 13 | + * 阶跃星辰 {@link ChatModel} 实现类 | ||
| 14 | + * | ||
| 15 | + * @author 芋道源码 | ||
| 16 | + */ | ||
| 17 | +@Slf4j | ||
| 18 | +@RequiredArgsConstructor | ||
| 19 | +public class StepFunChatModel implements ChatModel { | ||
| 20 | + | ||
| 21 | + public static final String BASE_URL = "https://api.stepfun.com"; | ||
| 22 | + | ||
| 23 | + public static final String COMPLETE_PATH = "/v1/chat/completions"; | ||
| 24 | + | ||
| 25 | + public static final String MODEL_DEFAULT = "step-3.7-flash"; | ||
| 26 | + | ||
| 27 | + /** | ||
| 28 | + * 兼容 OpenAI 接口,复用 DeepSeek 客户端 | ||
| 29 | + */ | ||
| 30 | + private final DeepSeekChatModel deepSeekChatModel; | ||
| 31 | + | ||
| 32 | + @Override | ||
| 33 | + public ChatResponse call(Prompt prompt) { | ||
| 34 | + return deepSeekChatModel.call(prompt); | ||
| 35 | + } | ||
| 36 | + | ||
| 37 | + @Override | ||
| 38 | + public Flux<ChatResponse> stream(Prompt prompt) { | ||
| 39 | + return deepSeekChatModel.stream(prompt); | ||
| 40 | + } | ||
| 41 | + | ||
| 42 | + @Override | ||
| 43 | + public ChatOptions getDefaultOptions() { | ||
| 44 | + return deepSeekChatModel.getDefaultOptions(); | ||
| 45 | + } | ||
| 46 | + | ||
| 47 | +} |
yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/xinghuo/XingHuoChatModel.java
| 1 | package cn.iocoder.yudao.module.ai.framework.ai.core.model.xinghuo; | 1 | package cn.iocoder.yudao.module.ai.framework.ai.core.model.xinghuo; |
| 2 | 2 | ||
| 3 | -import lombok.RequiredArgsConstructor; | 3 | +import cn.hutool.core.util.StrUtil; |
| 4 | import lombok.extern.slf4j.Slf4j; | 4 | import lombok.extern.slf4j.Slf4j; |
| 5 | import org.springframework.ai.chat.model.ChatModel; | 5 | import org.springframework.ai.chat.model.ChatModel; |
| 6 | import org.springframework.ai.chat.model.ChatResponse; | 6 | import org.springframework.ai.chat.model.ChatResponse; |
| 7 | import org.springframework.ai.chat.prompt.ChatOptions; | 7 | import org.springframework.ai.chat.prompt.ChatOptions; |
| 8 | import org.springframework.ai.chat.prompt.Prompt; | 8 | import org.springframework.ai.chat.prompt.Prompt; |
| 9 | +import org.springframework.ai.deepseek.DeepSeekChatModel; | ||
| 10 | +import org.springframework.ai.deepseek.DeepSeekChatOptions; | ||
| 11 | +import org.springframework.ai.deepseek.api.DeepSeekApi; | ||
| 9 | import reactor.core.publisher.Flux; | 12 | import reactor.core.publisher.Flux; |
| 10 | 13 | ||
| 11 | /** | 14 | /** |
| @@ -14,37 +17,140 @@ import reactor.core.publisher.Flux; | @@ -14,37 +17,140 @@ import reactor.core.publisher.Flux; | ||
| 14 | * @author fansili | 17 | * @author fansili |
| 15 | */ | 18 | */ |
| 16 | @Slf4j | 19 | @Slf4j |
| 17 | -@RequiredArgsConstructor | ||
| 18 | public class XingHuoChatModel implements ChatModel { | 20 | public class XingHuoChatModel implements ChatModel { |
| 19 | 21 | ||
| 20 | - public static final String BASE_URL_V1 = "https://spark-api-open.xf-yun.com"; | ||
| 21 | - | ||
| 22 | - public static final String BASE_URL_V2 = "https://spark-api-open.xf-yun.com"; | ||
| 23 | - public static final String BASE_COMPLETIONS_PATH_V2 = "/v2/chat/completions"; | 22 | + /** |
| 23 | + * 星火 X2 | ||
| 24 | + * | ||
| 25 | + * @see <a href="https://spark-api-open.xf-yun.com/x2/chat/completions">接口地址</a> | ||
| 26 | + */ | ||
| 27 | + public static final String MODEL_X2 = "x2"; | ||
| 24 | 28 | ||
| 25 | /** | 29 | /** |
| 26 | - * 已知模型名列表:x1、4.0Ultra、generalv3.5、max-32k、generalv3、pro-128k、lite | 30 | + * 星火 X2 Flash |
| 31 | + * | ||
| 32 | + * @see <a href="https://spark-api-open.xf-yun.com/agent/v1/chat/completions">接口地址</a> | ||
| 27 | */ | 33 | */ |
| 28 | - public static final String MODEL_DEFAULT = "4.0Ultra"; | 34 | + public static final String MODEL_X2_FLASH = "x2-flash"; |
| 35 | + | ||
| 36 | + private static final String BASE_URL_X2 = "https://spark-api-open.xf-yun.com/x2"; | ||
| 37 | + | ||
| 38 | + private static final String BASE_URL_X2_FLASH = "https://spark-api-open.xf-yun.com/agent/v1"; | ||
| 39 | + | ||
| 40 | + public static final String MODEL_DEFAULT = MODEL_X2_FLASH; | ||
| 41 | + | ||
| 42 | + private static String getBaseUrl(String model) { | ||
| 43 | + if (MODEL_X2_FLASH.equals(model)) { | ||
| 44 | + return BASE_URL_X2_FLASH; | ||
| 45 | + } | ||
| 46 | + return BASE_URL_X2; | ||
| 47 | + } | ||
| 48 | + | ||
| 49 | + private final String apiKey; | ||
| 50 | + | ||
| 51 | + private final DeepSeekChatOptions options; | ||
| 29 | 52 | ||
| 30 | /** | 53 | /** |
| 31 | - * v1 兼容 OpenAI 接口,进行复用 | 54 | + * 兼容 OpenAI 接口,进行复用 |
| 32 | */ | 55 | */ |
| 33 | - private final ChatModel openAiChatModelV1; | 56 | + private final ChatModel chatModelX2; |
| 57 | + | ||
| 58 | + private final ChatModel chatModelX2Flash; | ||
| 59 | + | ||
| 60 | + private XingHuoChatModel(String apiKey, DeepSeekChatOptions options) { | ||
| 61 | + this.apiKey = apiKey; | ||
| 62 | + this.options = options; | ||
| 63 | + this.chatModelX2 = buildChatModel(MODEL_X2); | ||
| 64 | + this.chatModelX2Flash = buildChatModel(MODEL_X2_FLASH); | ||
| 65 | + } | ||
| 66 | + | ||
| 67 | + public static Builder builder() { | ||
| 68 | + return new Builder(); | ||
| 69 | + } | ||
| 34 | 70 | ||
| 35 | @Override | 71 | @Override |
| 36 | public ChatResponse call(Prompt prompt) { | 72 | public ChatResponse call(Prompt prompt) { |
| 37 | - return openAiChatModelV1.call(prompt); | 73 | + return getChatModel(prompt).call(buildApiPrompt(prompt)); |
| 38 | } | 74 | } |
| 39 | 75 | ||
| 40 | @Override | 76 | @Override |
| 41 | public Flux<ChatResponse> stream(Prompt prompt) { | 77 | public Flux<ChatResponse> stream(Prompt prompt) { |
| 42 | - return openAiChatModelV1.stream(prompt); | 78 | + return getChatModel(prompt).stream(buildApiPrompt(prompt)); |
| 43 | } | 79 | } |
| 44 | 80 | ||
| 45 | @Override | 81 | @Override |
| 46 | public ChatOptions getDefaultOptions() { | 82 | public ChatOptions getDefaultOptions() { |
| 47 | - return openAiChatModelV1.getDefaultOptions(); | 83 | + return options; |
| 84 | + } | ||
| 85 | + | ||
| 86 | + private ChatModel getChatModel(Prompt prompt) { | ||
| 87 | + String model = options.getModel(); | ||
| 88 | + ChatOptions options = prompt.getOptions(); | ||
| 89 | + if (options != null && isBusinessModel(options.getModel())) { | ||
| 90 | + model = options.getModel(); | ||
| 91 | + } | ||
| 92 | + return getChatModel(model); | ||
| 93 | + } | ||
| 94 | + | ||
| 95 | + private ChatModel getChatModel(String model) { | ||
| 96 | + if (MODEL_X2_FLASH.equals(model)) { | ||
| 97 | + return chatModelX2Flash; | ||
| 98 | + } | ||
| 99 | + return chatModelX2; | ||
| 100 | + } | ||
| 101 | + | ||
| 102 | + private ChatModel buildChatModel(String model) { | ||
| 103 | + return DeepSeekChatModel.builder() | ||
| 104 | + .deepSeekApi(DeepSeekApi.builder() | ||
| 105 | + .baseUrl(getBaseUrl(model)) | ||
| 106 | + .apiKey(apiKey) | ||
| 107 | + .build()) | ||
| 108 | + .defaultOptions(new DeepSeekChatOptions.Builder(options).model("spark-x").build()) | ||
| 109 | + .build(); | ||
| 110 | + } | ||
| 111 | + | ||
| 112 | + private static Prompt buildApiPrompt(Prompt prompt) { | ||
| 113 | + ChatOptions options = prompt.getOptions(); | ||
| 114 | + if (options == null) { | ||
| 115 | + return prompt; | ||
| 116 | + } | ||
| 117 | + if (!(options instanceof DeepSeekChatOptions)) { | ||
| 118 | + return prompt; | ||
| 119 | + } | ||
| 120 | + return Prompt.builder() | ||
| 121 | + .messages(prompt.getInstructions()) | ||
| 122 | + .chatOptions(new DeepSeekChatOptions.Builder((DeepSeekChatOptions) options).model("spark-x").build()) | ||
| 123 | + .build(); | ||
| 124 | + } | ||
| 125 | + | ||
| 126 | + private static boolean isBusinessModel(String model) { | ||
| 127 | + return MODEL_X2.equals(model) || MODEL_X2_FLASH.equals(model); | ||
| 128 | + } | ||
| 129 | + | ||
| 130 | + public static final class Builder { | ||
| 131 | + | ||
| 132 | + private String apiKey; | ||
| 133 | + | ||
| 134 | + private DeepSeekChatOptions options; | ||
| 135 | + | ||
| 136 | + public Builder apiKey(String apiKey) { | ||
| 137 | + this.apiKey = apiKey; | ||
| 138 | + return this; | ||
| 139 | + } | ||
| 140 | + | ||
| 141 | + public Builder options(DeepSeekChatOptions options) { | ||
| 142 | + this.options = options; | ||
| 143 | + return this; | ||
| 144 | + } | ||
| 145 | + | ||
| 146 | + public XingHuoChatModel build() { | ||
| 147 | + DeepSeekChatOptions options = this.options != null ? this.options : DeepSeekChatOptions.builder().build(); | ||
| 148 | + if (StrUtil.isEmpty(options.getModel())) { | ||
| 149 | + options = new DeepSeekChatOptions.Builder(options).model(MODEL_DEFAULT).build(); | ||
| 150 | + } | ||
| 151 | + return new XingHuoChatModel(apiKey, options); | ||
| 152 | + } | ||
| 153 | + | ||
| 48 | } | 154 | } |
| 49 | 155 | ||
| 50 | } | 156 | } |
yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/yiyan/YiYanChatModel.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.ai.framework.ai.core.model.yiyan; | ||
| 2 | + | ||
| 3 | +import lombok.RequiredArgsConstructor; | ||
| 4 | +import lombok.extern.slf4j.Slf4j; | ||
| 5 | +import org.springframework.ai.chat.model.ChatModel; | ||
| 6 | +import org.springframework.ai.chat.model.ChatResponse; | ||
| 7 | +import org.springframework.ai.chat.prompt.ChatOptions; | ||
| 8 | +import org.springframework.ai.chat.prompt.Prompt; | ||
| 9 | +import org.springframework.ai.deepseek.DeepSeekChatModel; | ||
| 10 | +import reactor.core.publisher.Flux; | ||
| 11 | + | ||
| 12 | +/** | ||
| 13 | + * 文心一言 {@link ChatModel} 实现类 | ||
| 14 | + * | ||
| 15 | + * @author 芋道源码 | ||
| 16 | + */ | ||
| 17 | +@Slf4j | ||
| 18 | +@RequiredArgsConstructor | ||
| 19 | +public class YiYanChatModel implements ChatModel { | ||
| 20 | + | ||
| 21 | + public static final String BASE_URL = "https://qianfan.baidubce.com/v2"; | ||
| 22 | + | ||
| 23 | + public static final String MODEL_DEFAULT = "ernie-5.1"; | ||
| 24 | + | ||
| 25 | + /** | ||
| 26 | + * 兼容 OpenAI 接口,复用 DeepSeek 客户端 | ||
| 27 | + */ | ||
| 28 | + private final DeepSeekChatModel deepSeekChatModel; | ||
| 29 | + | ||
| 30 | + @Override | ||
| 31 | + public ChatResponse call(Prompt prompt) { | ||
| 32 | + return deepSeekChatModel.call(prompt); | ||
| 33 | + } | ||
| 34 | + | ||
| 35 | + @Override | ||
| 36 | + public Flux<ChatResponse> stream(Prompt prompt) { | ||
| 37 | + return deepSeekChatModel.stream(prompt); | ||
| 38 | + } | ||
| 39 | + | ||
| 40 | + @Override | ||
| 41 | + public ChatOptions getDefaultOptions() { | ||
| 42 | + return deepSeekChatModel.getDefaultOptions(); | ||
| 43 | + } | ||
| 44 | + | ||
| 45 | +} |
yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/zhipu/ZhiPuChatModel.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.ai.framework.ai.core.model.zhipu; | ||
| 2 | + | ||
| 3 | +import lombok.RequiredArgsConstructor; | ||
| 4 | +import lombok.extern.slf4j.Slf4j; | ||
| 5 | +import org.springframework.ai.chat.model.ChatModel; | ||
| 6 | +import org.springframework.ai.chat.model.ChatResponse; | ||
| 7 | +import org.springframework.ai.chat.prompt.ChatOptions; | ||
| 8 | +import org.springframework.ai.chat.prompt.Prompt; | ||
| 9 | +import org.springframework.ai.deepseek.DeepSeekChatModel; | ||
| 10 | +import reactor.core.publisher.Flux; | ||
| 11 | + | ||
| 12 | +/** | ||
| 13 | + * 智谱 {@link ChatModel} 实现类 | ||
| 14 | + * | ||
| 15 | + * @author 芋道源码 | ||
| 16 | + */ | ||
| 17 | +@Slf4j | ||
| 18 | +@RequiredArgsConstructor | ||
| 19 | +public class ZhiPuChatModel implements ChatModel { | ||
| 20 | + | ||
| 21 | + public static final String BASE_URL = "https://open.bigmodel.cn/api/paas/v4"; | ||
| 22 | + | ||
| 23 | + public static final String MODEL_DEFAULT = "glm-5.2"; | ||
| 24 | + | ||
| 25 | + /** | ||
| 26 | + * 兼容 OpenAI 接口,复用 DeepSeek 客户端 | ||
| 27 | + */ | ||
| 28 | + private final DeepSeekChatModel deepSeekChatModel; | ||
| 29 | + | ||
| 30 | + @Override | ||
| 31 | + public ChatResponse call(Prompt prompt) { | ||
| 32 | + return deepSeekChatModel.call(prompt); | ||
| 33 | + } | ||
| 34 | + | ||
| 35 | + @Override | ||
| 36 | + public Flux<ChatResponse> stream(Prompt prompt) { | ||
| 37 | + return deepSeekChatModel.stream(prompt); | ||
| 38 | + } | ||
| 39 | + | ||
| 40 | + @Override | ||
| 41 | + public ChatOptions getDefaultOptions() { | ||
| 42 | + return deepSeekChatModel.getDefaultOptions(); | ||
| 43 | + } | ||
| 44 | + | ||
| 45 | +} |
yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/service/chat/AiChatMessageService.java
| @@ -46,6 +46,14 @@ public interface AiChatMessageService { | @@ -46,6 +46,14 @@ public interface AiChatMessageService { | ||
| 46 | List<AiChatMessageDO> getChatMessageListByConversationId(Long conversationId); | 46 | List<AiChatMessageDO> getChatMessageListByConversationId(Long conversationId); |
| 47 | 47 | ||
| 48 | /** | 48 | /** |
| 49 | + * 获得聊天消息 | ||
| 50 | + * | ||
| 51 | + * @param id 消息编号 | ||
| 52 | + * @return 聊天消息 | ||
| 53 | + */ | ||
| 54 | + AiChatMessageDO getChatMessage(Long id); | ||
| 55 | + | ||
| 56 | + /** | ||
| 49 | * 删除消息 | 57 | * 删除消息 |
| 50 | * | 58 | * |
| 51 | * @param id 消息编号 | 59 | * @param id 消息编号 |
| @@ -69,6 +77,14 @@ public interface AiChatMessageService { | @@ -69,6 +77,14 @@ public interface AiChatMessageService { | ||
| 69 | void deleteChatMessageByAdmin(Long id); | 77 | void deleteChatMessageByAdmin(Long id); |
| 70 | 78 | ||
| 71 | /** | 79 | /** |
| 80 | + * 获得聊天对话的消息数量 | ||
| 81 | + * | ||
| 82 | + * @param conversationId 对话编号 | ||
| 83 | + * @return 消息数量 | ||
| 84 | + */ | ||
| 85 | + Integer getChatMessageCount(Long conversationId); | ||
| 86 | + | ||
| 87 | + /** | ||
| 72 | * 获得聊天对话的消息数量 Map | 88 | * 获得聊天对话的消息数量 Map |
| 73 | * | 89 | * |
| 74 | * @param conversationIds 对话编号数组 | 90 | * @param conversationIds 对话编号数组 |
yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/service/chat/AiChatMessageServiceImpl.java
| @@ -525,6 +525,11 @@ public class AiChatMessageServiceImpl implements AiChatMessageService { | @@ -525,6 +525,11 @@ public class AiChatMessageServiceImpl implements AiChatMessageService { | ||
| 525 | } | 525 | } |
| 526 | 526 | ||
| 527 | @Override | 527 | @Override |
| 528 | + public AiChatMessageDO getChatMessage(Long id) { | ||
| 529 | + return chatMessageMapper.selectById(id); | ||
| 530 | + } | ||
| 531 | + | ||
| 532 | + @Override | ||
| 528 | public void deleteChatMessage(Long id, Long userId) { | 533 | public void deleteChatMessage(Long id, Long userId) { |
| 529 | // 1. 校验消息存在 | 534 | // 1. 校验消息存在 |
| 530 | AiChatMessageDO message = chatMessageMapper.selectById(id); | 535 | AiChatMessageDO message = chatMessageMapper.selectById(id); |
| @@ -558,6 +563,11 @@ public class AiChatMessageServiceImpl implements AiChatMessageService { | @@ -558,6 +563,11 @@ public class AiChatMessageServiceImpl implements AiChatMessageService { | ||
| 558 | } | 563 | } |
| 559 | 564 | ||
| 560 | @Override | 565 | @Override |
| 566 | + public Integer getChatMessageCount(Long conversationId) { | ||
| 567 | + return chatMessageMapper.selectCount(AiChatMessageDO::getConversationId, conversationId).intValue(); | ||
| 568 | + } | ||
| 569 | + | ||
| 570 | + @Override | ||
| 561 | public Map<Long, Integer> getChatMessageCountMap(Collection<Long> conversationIds) { | 571 | public Map<Long, Integer> getChatMessageCountMap(Collection<Long> conversationIds) { |
| 562 | return chatMessageMapper.selectCountMapByConversationId(conversationIds); | 572 | return chatMessageMapper.selectCountMapByConversationId(conversationIds); |
| 563 | } | 573 | } |
yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/service/image/AiImageServiceImpl.java
| @@ -29,14 +29,12 @@ import cn.iocoder.yudao.module.infra.api.file.FileApi; | @@ -29,14 +29,12 @@ import cn.iocoder.yudao.module.infra.api.file.FileApi; | ||
| 29 | import com.alibaba.cloud.ai.dashscope.image.DashScopeImageOptions; | 29 | import com.alibaba.cloud.ai.dashscope.image.DashScopeImageOptions; |
| 30 | import jakarta.annotation.Resource; | 30 | import jakarta.annotation.Resource; |
| 31 | import lombok.extern.slf4j.Slf4j; | 31 | import lombok.extern.slf4j.Slf4j; |
| 32 | -import org.springaicommunity.qianfan.QianFanImageOptions; | ||
| 33 | import org.springframework.ai.image.ImageModel; | 32 | import org.springframework.ai.image.ImageModel; |
| 34 | import org.springframework.ai.image.ImageOptions; | 33 | import org.springframework.ai.image.ImageOptions; |
| 35 | import org.springframework.ai.image.ImagePrompt; | 34 | import org.springframework.ai.image.ImagePrompt; |
| 36 | import org.springframework.ai.image.ImageResponse; | 35 | import org.springframework.ai.image.ImageResponse; |
| 37 | import org.springframework.ai.openai.OpenAiImageOptions; | 36 | import org.springframework.ai.openai.OpenAiImageOptions; |
| 38 | import org.springframework.ai.stabilityai.api.StabilityAiImageOptions; | 37 | import org.springframework.ai.stabilityai.api.StabilityAiImageOptions; |
| 39 | -import org.springframework.ai.zhipuai.ZhiPuAiImageOptions; | ||
| 40 | import org.springframework.scheduling.annotation.Async; | 38 | import org.springframework.scheduling.annotation.Async; |
| 41 | import org.springframework.stereotype.Service; | 39 | import org.springframework.stereotype.Service; |
| 42 | import org.springframework.transaction.annotation.Transactional; | 40 | import org.springframework.transaction.annotation.Transactional; |
| @@ -168,15 +166,6 @@ public class AiImageServiceImpl implements AiImageService { | @@ -168,15 +166,6 @@ public class AiImageServiceImpl implements AiImageService { | ||
| 168 | .model(model.getModel()).n(1) | 166 | .model(model.getModel()).n(1) |
| 169 | .height(draw.getHeight()).width(draw.getWidth()) | 167 | .height(draw.getHeight()).width(draw.getWidth()) |
| 170 | .build(); | 168 | .build(); |
| 171 | - } else if (ObjUtil.equal(model.getPlatform(), AiPlatformEnum.YI_YAN.getPlatform())) { | ||
| 172 | - return QianFanImageOptions.builder() | ||
| 173 | - .model(model.getModel()).N(1) | ||
| 174 | - .height(draw.getHeight()).width(draw.getWidth()) | ||
| 175 | - .build(); | ||
| 176 | - } else if (ObjUtil.equal(model.getPlatform(), AiPlatformEnum.ZHI_PU.getPlatform())) { | ||
| 177 | - return ZhiPuAiImageOptions.builder() | ||
| 178 | - .model(model.getModel()) | ||
| 179 | - .build(); | ||
| 180 | } | 169 | } |
| 181 | throw new IllegalArgumentException("不支持的 AI 平台:" + model.getPlatform()); | 170 | throw new IllegalArgumentException("不支持的 AI 平台:" + model.getPlatform()); |
| 182 | } | 171 | } |
yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/service/mindmap/AiMindMapService.java
| @@ -31,6 +31,14 @@ public interface AiMindMapService { | @@ -31,6 +31,14 @@ public interface AiMindMapService { | ||
| 31 | void deleteMindMap(Long id); | 31 | void deleteMindMap(Long id); |
| 32 | 32 | ||
| 33 | /** | 33 | /** |
| 34 | + * 获得思维导图 | ||
| 35 | + * | ||
| 36 | + * @param id 编号 | ||
| 37 | + * @return 思维导图 | ||
| 38 | + */ | ||
| 39 | + AiMindMapDO getMindMap(Long id); | ||
| 40 | + | ||
| 41 | + /** | ||
| 34 | * 获得思维导图分页 | 42 | * 获得思维导图分页 |
| 35 | * | 43 | * |
| 36 | * @param pageReqVO 分页查询 | 44 | * @param pageReqVO 分页查询 |
yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/service/mindmap/AiMindMapServiceImpl.java
| @@ -154,6 +154,11 @@ public class AiMindMapServiceImpl implements AiMindMapService { | @@ -154,6 +154,11 @@ public class AiMindMapServiceImpl implements AiMindMapService { | ||
| 154 | } | 154 | } |
| 155 | 155 | ||
| 156 | @Override | 156 | @Override |
| 157 | + public AiMindMapDO getMindMap(Long id) { | ||
| 158 | + return mindMapMapper.selectById(id); | ||
| 159 | + } | ||
| 160 | + | ||
| 161 | + @Override | ||
| 157 | public PageResult<AiMindMapDO> getMindMapPage(AiMindMapPageReqVO pageReqVO) { | 162 | public PageResult<AiMindMapDO> getMindMapPage(AiMindMapPageReqVO pageReqVO) { |
| 158 | return mindMapMapper.selectPage(pageReqVO); | 163 | return mindMapMapper.selectPage(pageReqVO); |
| 159 | } | 164 | } |
yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/service/workflow/AiWorkflowServiceImpl.java
| @@ -10,8 +10,8 @@ import cn.iocoder.yudao.module.ai.controller.admin.workflow.vo.AiWorkflowTestReq | @@ -10,8 +10,8 @@ import cn.iocoder.yudao.module.ai.controller.admin.workflow.vo.AiWorkflowTestReq | ||
| 10 | import cn.iocoder.yudao.module.ai.dal.dataobject.workflow.AiWorkflowDO; | 10 | import cn.iocoder.yudao.module.ai.dal.dataobject.workflow.AiWorkflowDO; |
| 11 | import cn.iocoder.yudao.module.ai.dal.mysql.workflow.AiWorkflowMapper; | 11 | import cn.iocoder.yudao.module.ai.dal.mysql.workflow.AiWorkflowMapper; |
| 12 | import cn.iocoder.yudao.module.ai.service.model.AiModelService; | 12 | import cn.iocoder.yudao.module.ai.service.model.AiModelService; |
| 13 | -import com.alibaba.fastjson.JSONArray; | ||
| 14 | -import com.alibaba.fastjson.JSONObject; | 13 | +import com.alibaba.fastjson2.JSONArray; |
| 14 | +import com.alibaba.fastjson2.JSONObject; | ||
| 15 | import dev.tinyflow.core.Tinyflow; | 15 | import dev.tinyflow.core.Tinyflow; |
| 16 | import jakarta.annotation.Resource; | 16 | import jakarta.annotation.Resource; |
| 17 | import lombok.extern.slf4j.Slf4j; | 17 | import lombok.extern.slf4j.Slf4j; |
yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/service/write/AiWriteService.java
| @@ -31,6 +31,14 @@ public interface AiWriteService { | @@ -31,6 +31,14 @@ public interface AiWriteService { | ||
| 31 | void deleteWrite(Long id); | 31 | void deleteWrite(Long id); |
| 32 | 32 | ||
| 33 | /** | 33 | /** |
| 34 | + * 获得写作 | ||
| 35 | + * | ||
| 36 | + * @param id 编号 | ||
| 37 | + * @return AI 写作 | ||
| 38 | + */ | ||
| 39 | + AiWriteDO getWrite(Long id); | ||
| 40 | + | ||
| 41 | + /** | ||
| 34 | * 获得写作分页 | 42 | * 获得写作分页 |
| 35 | * | 43 | * |
| 36 | * @param pageReqVO 分页查询 | 44 | * @param pageReqVO 分页查询 |
yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/service/write/AiWriteServiceImpl.java
| @@ -171,6 +171,11 @@ public class AiWriteServiceImpl implements AiWriteService { | @@ -171,6 +171,11 @@ public class AiWriteServiceImpl implements AiWriteService { | ||
| 171 | } | 171 | } |
| 172 | 172 | ||
| 173 | @Override | 173 | @Override |
| 174 | + public AiWriteDO getWrite(Long id) { | ||
| 175 | + return writeMapper.selectById(id); | ||
| 176 | + } | ||
| 177 | + | ||
| 178 | + @Override | ||
| 174 | public PageResult<AiWriteDO> getWritePage(AiWritePageReqVO pageReqVO) { | 179 | public PageResult<AiWriteDO> getWritePage(AiWritePageReqVO pageReqVO) { |
| 175 | return writeMapper.selectPage(pageReqVO); | 180 | return writeMapper.selectPage(pageReqVO); |
| 176 | } | 181 | } |
yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/util/AiUtils.java
| @@ -8,8 +8,7 @@ import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils; | @@ -8,8 +8,7 @@ import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils; | ||
| 8 | import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder; | 8 | import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder; |
| 9 | import cn.iocoder.yudao.module.ai.enums.model.AiPlatformEnum; | 9 | import cn.iocoder.yudao.module.ai.enums.model.AiPlatformEnum; |
| 10 | import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatOptions; | 10 | import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatOptions; |
| 11 | -import org.springaicommunity.moonshot.MoonshotChatOptions; | ||
| 12 | -import org.springaicommunity.qianfan.QianFanChatOptions; | 11 | +import cn.hutool.extra.spring.SpringUtil; |
| 13 | import org.springframework.ai.anthropic.AnthropicChatOptions; | 12 | import org.springframework.ai.anthropic.AnthropicChatOptions; |
| 14 | import org.springframework.ai.azure.openai.AzureOpenAiChatOptions; | 13 | import org.springframework.ai.azure.openai.AzureOpenAiChatOptions; |
| 15 | import org.springframework.ai.chat.messages.*; | 14 | import org.springframework.ai.chat.messages.*; |
| @@ -17,14 +16,16 @@ import org.springframework.ai.chat.model.ChatResponse; | @@ -17,14 +16,16 @@ import org.springframework.ai.chat.model.ChatResponse; | ||
| 17 | import org.springframework.ai.chat.prompt.ChatOptions; | 16 | import org.springframework.ai.chat.prompt.ChatOptions; |
| 18 | import org.springframework.ai.deepseek.DeepSeekAssistantMessage; | 17 | import org.springframework.ai.deepseek.DeepSeekAssistantMessage; |
| 19 | import org.springframework.ai.deepseek.DeepSeekChatOptions; | 18 | import org.springframework.ai.deepseek.DeepSeekChatOptions; |
| 20 | -import org.springframework.ai.minimax.MiniMaxChatOptions; | ||
| 21 | import org.springframework.ai.ollama.api.OllamaChatOptions; | 19 | import org.springframework.ai.ollama.api.OllamaChatOptions; |
| 22 | import org.springframework.ai.openai.OpenAiChatOptions; | 20 | import org.springframework.ai.openai.OpenAiChatOptions; |
| 23 | import org.springframework.ai.tool.ToolCallback; | 21 | import org.springframework.ai.tool.ToolCallback; |
| 24 | -import org.springframework.ai.zhipuai.ZhiPuAiChatOptions; | 22 | +import org.springframework.core.env.Environment; |
| 25 | 23 | ||
| 26 | import java.util.*; | 24 | import java.util.*; |
| 27 | 25 | ||
| 26 | +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; | ||
| 27 | +import static cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants.API_CONFIG_PLACEHOLDER_NOT_RESOLVED; | ||
| 28 | + | ||
| 28 | /** | 29 | /** |
| 29 | * Spring AI 工具类 | 30 | * Spring AI 工具类 |
| 30 | * | 31 | * |
| @@ -36,13 +37,42 @@ public class AiUtils { | @@ -36,13 +37,42 @@ public class AiUtils { | ||
| 36 | public static final String TOOL_CONTEXT_TENANT_ID = "TENANT_ID"; | 37 | public static final String TOOL_CONTEXT_TENANT_ID = "TENANT_ID"; |
| 37 | 38 | ||
| 38 | /** | 39 | /** |
| 40 | + * 解析 DB 等动态配置里的 Spring 占位符,例如 ${OPENAI_API_KEY} | ||
| 41 | + * | ||
| 42 | + * @param value 待解析的配置值 | ||
| 43 | + * @return 解析后的配置值 | ||
| 44 | + */ | ||
| 45 | + public static String resolveSpringPlaceholders(String value) { | ||
| 46 | + if (StrUtil.isBlank(value) || !StrUtil.contains(value, "${")) { | ||
| 47 | + return value; | ||
| 48 | + } | ||
| 49 | + try { | ||
| 50 | + return SpringUtil.getBean(Environment.class).resolveRequiredPlaceholders(value); | ||
| 51 | + } catch (IllegalArgumentException ex) { | ||
| 52 | + throw exception(API_CONFIG_PLACEHOLDER_NOT_RESOLVED, value); | ||
| 53 | + } | ||
| 54 | + } | ||
| 55 | + | ||
| 56 | + /** | ||
| 57 | + * 校验 API Key,避免集成测试使用默认占位值发起调用。 | ||
| 58 | + * | ||
| 59 | + * @param apiKey API Key | ||
| 60 | + */ | ||
| 61 | + public static void validateApiKey(String apiKey) { | ||
| 62 | + if (StrUtil.isBlank(apiKey) || "sk-xxxx".equals(apiKey)) { | ||
| 63 | + throw new IllegalStateException("apiKey 不能为空"); | ||
| 64 | + } | ||
| 65 | + } | ||
| 66 | + | ||
| 67 | + /** | ||
| 39 | * 通义千问支持多模态的模型 | 68 | * 通义千问支持多模态的模型 |
| 40 | * | 69 | * |
| 41 | * @see <a href="https://bailian.console.aliyun.com/cn-beijing/?tab=model#/model-market/all?providers=qwen&capabilities=VU">模型广场</a> | 70 | * @see <a href="https://bailian.console.aliyun.com/cn-beijing/?tab=model#/model-market/all?providers=qwen&capabilities=VU">模型广场</a> |
| 42 | * @see <a href="https://help.aliyun.com/zh/model-studio/error-code#error-url">必须开启 withMultiModel 参数</a> | 71 | * @see <a href="https://help.aliyun.com/zh/model-studio/error-code#error-url">必须开启 withMultiModel 参数</a> |
| 43 | */ | 72 | */ |
| 44 | public static final Set<String> TONG_YI_MULTI_MODELS = SetUtils.asSet( | 73 | public static final Set<String> TONG_YI_MULTI_MODELS = SetUtils.asSet( |
| 45 | - // qwen3.5 / 3.6 系列(统一多模态主干) | 74 | + // qwen3.5 / 3.6 / 3.7 系列(统一多模态主干) |
| 75 | + "qwen3.7-max", "qwen3.7-plus", "qwen3.7-flash", | ||
| 46 | "qwen3.6-plus", "qwen3.6-flash", | 76 | "qwen3.6-plus", "qwen3.6-flash", |
| 47 | "qwen3.5-plus", "qwen3.5-flash", | 77 | "qwen3.5-plus", "qwen3.5-flash", |
| 48 | // qwen-vl 视觉理解 | 78 | // qwen-vl 视觉理解 |
| @@ -72,27 +102,21 @@ public class AiUtils { | @@ -72,27 +102,21 @@ public class AiUtils { | ||
| 72 | .enableThinking(true) // TODO 芋艿:默认都开启 thinking 模式,后续可以让用户配置 | 102 | .enableThinking(true) // TODO 芋艿:默认都开启 thinking 模式,后续可以让用户配置 |
| 73 | .multiModel(TONG_YI_MULTI_MODELS.contains(model)) // 是否多模态模型 | 103 | .multiModel(TONG_YI_MULTI_MODELS.contains(model)) // 是否多模态模型 |
| 74 | .toolCallbacks(toolCallbacks).toolContext(toolContext).build(); | 104 | .toolCallbacks(toolCallbacks).toolContext(toolContext).build(); |
| 75 | - case YI_YAN: | ||
| 76 | - return QianFanChatOptions.builder().model(model).temperature(temperature).maxTokens(maxTokens).build(); | ||
| 77 | case DEEP_SEEK: | 105 | case DEEP_SEEK: |
| 78 | case DOU_BAO: // 复用 DeepSeek 客户端 | 106 | case DOU_BAO: // 复用 DeepSeek 客户端 |
| 79 | case HUN_YUAN: // 复用 DeepSeek 客户端 | 107 | case HUN_YUAN: // 复用 DeepSeek 客户端 |
| 80 | case SILICON_FLOW: // 复用 DeepSeek 客户端 | 108 | case SILICON_FLOW: // 复用 DeepSeek 客户端 |
| 109 | + case YI_YAN: // 复用 DeepSeek 客户端 | ||
| 110 | + case ZHI_PU: // 复用 DeepSeek 客户端 | ||
| 81 | case XING_HUO: // 复用 DeepSeek 客户端 | 111 | case XING_HUO: // 复用 DeepSeek 客户端 |
| 112 | + case MINI_MAX: // 复用 DeepSeek 客户端 | ||
| 113 | + case MOONSHOT: // 复用 DeepSeek 客户端 | ||
| 114 | + case BAI_CHUAN: // 复用 DeepSeek 客户端 | ||
| 115 | + case STEP_FUN: // 复用 DeepSeek 客户端 | ||
| 82 | return DeepSeekChatOptions.builder().model(model).temperature(temperature).maxTokens(maxTokens) | 116 | return DeepSeekChatOptions.builder().model(model).temperature(temperature).maxTokens(maxTokens) |
| 83 | .toolCallbacks(toolCallbacks).toolContext(toolContext).build(); | 117 | .toolCallbacks(toolCallbacks).toolContext(toolContext).build(); |
| 84 | - case ZHI_PU: | ||
| 85 | - return ZhiPuAiChatOptions.builder().model(model).temperature(temperature).maxTokens(maxTokens) | ||
| 86 | - .toolCallbacks(toolCallbacks).toolContext(toolContext).build(); | ||
| 87 | - case MINI_MAX: | ||
| 88 | - return MiniMaxChatOptions.builder().model(model).temperature(temperature).maxTokens(maxTokens) | ||
| 89 | - .toolCallbacks(toolCallbacks).toolContext(toolContext).build(); | ||
| 90 | - case MOONSHOT: | ||
| 91 | - return MoonshotChatOptions.builder().model(model).temperature(temperature).maxTokens(maxTokens) | ||
| 92 | - .toolCallbacks(toolCallbacks).toolContext(toolContext).build(); | ||
| 93 | case OPENAI: | 118 | case OPENAI: |
| 94 | case GEMINI: // 复用 OpenAI 客户端 | 119 | case GEMINI: // 复用 OpenAI 客户端 |
| 95 | - case BAI_CHUAN: // 复用 OpenAI 客户端 | ||
| 96 | case GROK: // 复用 OpenAI 客户端 | 120 | case GROK: // 复用 OpenAI 客户端 |
| 97 | return OpenAiChatOptions.builder().model(model).temperature(temperature).maxTokens(maxTokens) | 121 | return OpenAiChatOptions.builder().model(model).temperature(temperature).maxTokens(maxTokens) |
| 98 | .toolCallbacks(toolCallbacks).toolContext(toolContext).build(); | 122 | .toolCallbacks(toolCallbacks).toolContext(toolContext).build(); |
| @@ -159,4 +183,4 @@ public class AiUtils { | @@ -159,4 +183,4 @@ public class AiUtils { | ||
| 159 | return MapUtil.getStr(output.getMetadata(), "reasoningContent"); | 183 | return MapUtil.getStr(output.getMetadata(), "reasoningContent"); |
| 160 | } | 184 | } |
| 161 | 185 | ||
| 162 | -} | ||
| 163 | \ No newline at end of file | 186 | \ No newline at end of file |
| 187 | +} |
yudao-module-ai/src/test/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/chat/BaiChuanChatModelTests.java
| 1 | package cn.iocoder.yudao.module.ai.framework.ai.core.model.chat; | 1 | package cn.iocoder.yudao.module.ai.framework.ai.core.model.chat; |
| 2 | 2 | ||
| 3 | +import cn.hutool.system.SystemUtil; | ||
| 3 | import cn.iocoder.yudao.module.ai.framework.ai.core.model.baichuan.BaiChuanChatModel; | 4 | import cn.iocoder.yudao.module.ai.framework.ai.core.model.baichuan.BaiChuanChatModel; |
| 4 | import org.junit.jupiter.api.Disabled; | 5 | import org.junit.jupiter.api.Disabled; |
| 5 | import org.junit.jupiter.api.Test; | 6 | import org.junit.jupiter.api.Test; |
| @@ -8,13 +9,16 @@ import org.springframework.ai.chat.messages.SystemMessage; | @@ -8,13 +9,16 @@ import org.springframework.ai.chat.messages.SystemMessage; | ||
| 8 | import org.springframework.ai.chat.messages.UserMessage; | 9 | import org.springframework.ai.chat.messages.UserMessage; |
| 9 | import org.springframework.ai.chat.model.ChatResponse; | 10 | import org.springframework.ai.chat.model.ChatResponse; |
| 10 | import org.springframework.ai.chat.prompt.Prompt; | 11 | import org.springframework.ai.chat.prompt.Prompt; |
| 11 | -import org.springframework.ai.openai.OpenAiChatModel; | ||
| 12 | -import org.springframework.ai.openai.OpenAiChatOptions; | ||
| 13 | -import org.springframework.ai.openai.api.OpenAiApi; | 12 | +import org.springframework.ai.deepseek.DeepSeekChatModel; |
| 13 | +import org.springframework.ai.deepseek.DeepSeekChatOptions; | ||
| 14 | +import org.springframework.ai.deepseek.api.DeepSeekApi; | ||
| 14 | import reactor.core.publisher.Flux; | 15 | import reactor.core.publisher.Flux; |
| 15 | 16 | ||
| 16 | import java.util.ArrayList; | 17 | import java.util.ArrayList; |
| 17 | import java.util.List; | 18 | import java.util.List; |
| 19 | +import java.util.Objects; | ||
| 20 | + | ||
| 21 | +import static cn.iocoder.yudao.module.ai.util.AiUtils.validateApiKey; | ||
| 18 | 22 | ||
| 19 | /** | 23 | /** |
| 20 | * {@link BaiChuanChatModel} 集成测试 | 24 | * {@link BaiChuanChatModel} 集成测试 |
| @@ -23,22 +27,27 @@ import java.util.List; | @@ -23,22 +27,27 @@ import java.util.List; | ||
| 23 | */ | 27 | */ |
| 24 | public class BaiChuanChatModelTests { | 28 | public class BaiChuanChatModelTests { |
| 25 | 29 | ||
| 26 | - private final OpenAiChatModel openAiChatModel = OpenAiChatModel.builder() | ||
| 27 | - .openAiApi(OpenAiApi.builder() | 30 | + private static final String API_KEY = SystemUtil.get("BAICHUAN_API_KEY", |
| 31 | + "sk-xxxx"); // 按需改成你的百川 API Key | ||
| 32 | + private static final String MODEL = SystemUtil.get("BAICHUAN_MODEL", | ||
| 33 | + BaiChuanChatModel.MODEL_DEFAULT); | ||
| 34 | + | ||
| 35 | + private final BaiChuanChatModel chatModel = new BaiChuanChatModel(DeepSeekChatModel.builder() | ||
| 36 | + .deepSeekApi(DeepSeekApi.builder() | ||
| 28 | .baseUrl(BaiChuanChatModel.BASE_URL) | 37 | .baseUrl(BaiChuanChatModel.BASE_URL) |
| 29 | - .apiKey("sk-61b6766a94c70786ed02673f5e16af3c") // apiKey | 38 | + .completionsPath(BaiChuanChatModel.COMPLETE_PATH) |
| 39 | + .apiKey(API_KEY) | ||
| 30 | .build()) | 40 | .build()) |
| 31 | - .defaultOptions(OpenAiChatOptions.builder() | ||
| 32 | - .model("Baichuan4-Turbo") // 模型(https://platform.baichuan-ai.com/docs/api) | 41 | + .defaultOptions(DeepSeekChatOptions.builder() |
| 42 | + .model(MODEL) | ||
| 33 | .temperature(0.7) | 43 | .temperature(0.7) |
| 34 | .build()) | 44 | .build()) |
| 35 | - .build(); | ||
| 36 | - | ||
| 37 | - private final BaiChuanChatModel chatModel = new BaiChuanChatModel(openAiChatModel); | 45 | + .build()); |
| 38 | 46 | ||
| 39 | @Test | 47 | @Test |
| 40 | @Disabled | 48 | @Disabled |
| 41 | public void testCall() { | 49 | public void testCall() { |
| 50 | + validateApiKey(API_KEY); | ||
| 42 | // 准备参数 | 51 | // 准备参数 |
| 43 | List<Message> messages = new ArrayList<>(); | 52 | List<Message> messages = new ArrayList<>(); |
| 44 | messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。")); | 53 | messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。")); |
| @@ -48,11 +57,13 @@ public class BaiChuanChatModelTests { | @@ -48,11 +57,13 @@ public class BaiChuanChatModelTests { | ||
| 48 | ChatResponse response = chatModel.call(new Prompt(messages)); | 57 | ChatResponse response = chatModel.call(new Prompt(messages)); |
| 49 | // 打印结果 | 58 | // 打印结果 |
| 50 | System.out.println(response); | 59 | System.out.println(response); |
| 60 | + System.out.println(Objects.requireNonNull(response.getResult()).getOutput()); | ||
| 51 | } | 61 | } |
| 52 | 62 | ||
| 53 | @Test | 63 | @Test |
| 54 | @Disabled | 64 | @Disabled |
| 55 | public void testStream() { | 65 | public void testStream() { |
| 66 | + validateApiKey(API_KEY); | ||
| 56 | // 准备参数 | 67 | // 准备参数 |
| 57 | List<Message> messages = new ArrayList<>(); | 68 | List<Message> messages = new ArrayList<>(); |
| 58 | messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。")); | 69 | messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。")); |
| @@ -61,7 +72,10 @@ public class BaiChuanChatModelTests { | @@ -61,7 +72,10 @@ public class BaiChuanChatModelTests { | ||
| 61 | // 调用 | 72 | // 调用 |
| 62 | Flux<ChatResponse> flux = chatModel.stream(new Prompt(messages)); | 73 | Flux<ChatResponse> flux = chatModel.stream(new Prompt(messages)); |
| 63 | // 打印结果 | 74 | // 打印结果 |
| 64 | - flux.doOnNext(System.out::println).then().block(); | 75 | + flux.doOnNext(response -> { |
| 76 | +// System.out.println(response); | ||
| 77 | + System.out.println(Objects.requireNonNull(response.getResult()).getOutput()); | ||
| 78 | + }).then().block(); | ||
| 65 | } | 79 | } |
| 66 | 80 | ||
| 67 | } | 81 | } |
yudao-module-ai/src/test/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/chat/DeepSeekChatModelTests.java
| 1 | package cn.iocoder.yudao.module.ai.framework.ai.core.model.chat; | 1 | package cn.iocoder.yudao.module.ai.framework.ai.core.model.chat; |
| 2 | 2 | ||
| 3 | +import cn.hutool.system.SystemUtil; | ||
| 3 | import org.junit.jupiter.api.Disabled; | 4 | import org.junit.jupiter.api.Disabled; |
| 4 | import org.junit.jupiter.api.Test; | 5 | import org.junit.jupiter.api.Test; |
| 5 | import org.springframework.ai.chat.messages.Message; | 6 | import org.springframework.ai.chat.messages.Message; |
| @@ -14,6 +15,9 @@ import reactor.core.publisher.Flux; | @@ -14,6 +15,9 @@ import reactor.core.publisher.Flux; | ||
| 14 | 15 | ||
| 15 | import java.util.ArrayList; | 16 | import java.util.ArrayList; |
| 16 | import java.util.List; | 17 | import java.util.List; |
| 18 | +import java.util.Objects; | ||
| 19 | + | ||
| 20 | +import static cn.iocoder.yudao.module.ai.util.AiUtils.validateApiKey; | ||
| 17 | 21 | ||
| 18 | /** | 22 | /** |
| 19 | * {@link DeepSeekChatModel} 集成测试 | 23 | * {@link DeepSeekChatModel} 集成测试 |
| @@ -22,12 +26,17 @@ import java.util.List; | @@ -22,12 +26,17 @@ import java.util.List; | ||
| 22 | */ | 26 | */ |
| 23 | public class DeepSeekChatModelTests { | 27 | public class DeepSeekChatModelTests { |
| 24 | 28 | ||
| 29 | + private static final String API_KEY = SystemUtil.get("DEEPSEEK_API_KEY", | ||
| 30 | + "sk-xxxx"); | ||
| 31 | + private static final String MODEL = SystemUtil.get("DEEPSEEK_MODEL", | ||
| 32 | + "deepseek-v4-flash"); | ||
| 33 | + | ||
| 25 | private final DeepSeekChatModel chatModel = DeepSeekChatModel.builder() | 34 | private final DeepSeekChatModel chatModel = DeepSeekChatModel.builder() |
| 26 | .deepSeekApi(DeepSeekApi.builder() | 35 | .deepSeekApi(DeepSeekApi.builder() |
| 27 | - .apiKey("sk-eaf4172a057344dd9bc64b1f806b6axx") // apiKey | 36 | + .apiKey(API_KEY) // apiKey |
| 28 | .build()) | 37 | .build()) |
| 29 | .defaultOptions(DeepSeekChatOptions.builder() | 38 | .defaultOptions(DeepSeekChatOptions.builder() |
| 30 | - .model("deepseek-chat") // 模型 | 39 | + .model(MODEL) // 模型 |
| 31 | .temperature(0.7) | 40 | .temperature(0.7) |
| 32 | .build()) | 41 | .build()) |
| 33 | .build(); | 42 | .build(); |
| @@ -35,6 +44,7 @@ public class DeepSeekChatModelTests { | @@ -35,6 +44,7 @@ public class DeepSeekChatModelTests { | ||
| 35 | @Test | 44 | @Test |
| 36 | @Disabled | 45 | @Disabled |
| 37 | public void testCall() { | 46 | public void testCall() { |
| 47 | + validateApiKey(API_KEY); | ||
| 38 | // 准备参数 | 48 | // 准备参数 |
| 39 | List<Message> messages = new ArrayList<>(); | 49 | List<Message> messages = new ArrayList<>(); |
| 40 | messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。")); | 50 | messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。")); |
| @@ -43,12 +53,13 @@ public class DeepSeekChatModelTests { | @@ -43,12 +53,13 @@ public class DeepSeekChatModelTests { | ||
| 43 | // 调用 | 53 | // 调用 |
| 44 | ChatResponse response = chatModel.call(new Prompt(messages)); | 54 | ChatResponse response = chatModel.call(new Prompt(messages)); |
| 45 | // 打印结果 | 55 | // 打印结果 |
| 46 | - System.out.println(response); | 56 | + System.out.println(Objects.requireNonNull(response.getResult()).getOutput()); |
| 47 | } | 57 | } |
| 48 | 58 | ||
| 49 | @Test | 59 | @Test |
| 50 | @Disabled | 60 | @Disabled |
| 51 | public void testStream() { | 61 | public void testStream() { |
| 62 | + validateApiKey(API_KEY); | ||
| 52 | // 准备参数 | 63 | // 准备参数 |
| 53 | List<Message> messages = new ArrayList<>(); | 64 | List<Message> messages = new ArrayList<>(); |
| 54 | messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。")); | 65 | messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。")); |
| @@ -63,11 +74,12 @@ public class DeepSeekChatModelTests { | @@ -63,11 +74,12 @@ public class DeepSeekChatModelTests { | ||
| 63 | @Test | 74 | @Test |
| 64 | @Disabled | 75 | @Disabled |
| 65 | public void testStream_thinking() { | 76 | public void testStream_thinking() { |
| 77 | + validateApiKey(API_KEY); | ||
| 66 | // 准备参数 | 78 | // 准备参数 |
| 67 | List<Message> messages = new ArrayList<>(); | 79 | List<Message> messages = new ArrayList<>(); |
| 68 | messages.add(new UserMessage("详细分析下,如何设计一个电商系统?")); | 80 | messages.add(new UserMessage("详细分析下,如何设计一个电商系统?")); |
| 69 | DeepSeekChatOptions options = DeepSeekChatOptions.builder() | 81 | DeepSeekChatOptions options = DeepSeekChatOptions.builder() |
| 70 | - .model("deepseek-reasoner") | 82 | + .model(MODEL) |
| 71 | .build(); | 83 | .build(); |
| 72 | 84 | ||
| 73 | // 调用 | 85 | // 调用 |
yudao-module-ai/src/test/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/chat/DouBaoChatModelTests.java
| 1 | package cn.iocoder.yudao.module.ai.framework.ai.core.model.chat; | 1 | package cn.iocoder.yudao.module.ai.framework.ai.core.model.chat; |
| 2 | 2 | ||
| 3 | +import cn.hutool.system.SystemUtil; | ||
| 3 | import cn.iocoder.yudao.module.ai.framework.ai.core.model.doubao.DouBaoChatModel; | 4 | import cn.iocoder.yudao.module.ai.framework.ai.core.model.doubao.DouBaoChatModel; |
| 4 | import org.junit.jupiter.api.Disabled; | 5 | import org.junit.jupiter.api.Disabled; |
| 5 | import org.junit.jupiter.api.Test; | 6 | import org.junit.jupiter.api.Test; |
| @@ -15,6 +16,9 @@ import reactor.core.publisher.Flux; | @@ -15,6 +16,9 @@ import reactor.core.publisher.Flux; | ||
| 15 | 16 | ||
| 16 | import java.util.ArrayList; | 17 | import java.util.ArrayList; |
| 17 | import java.util.List; | 18 | import java.util.List; |
| 19 | +import java.util.Objects; | ||
| 20 | + | ||
| 21 | +import static cn.iocoder.yudao.module.ai.util.AiUtils.validateApiKey; | ||
| 18 | 22 | ||
| 19 | /** | 23 | /** |
| 20 | * {@link DouBaoChatModel} 集成测试 | 24 | * {@link DouBaoChatModel} 集成测试 |
| @@ -23,19 +27,19 @@ import java.util.List; | @@ -23,19 +27,19 @@ import java.util.List; | ||
| 23 | */ | 27 | */ |
| 24 | public class DouBaoChatModelTests { | 28 | public class DouBaoChatModelTests { |
| 25 | 29 | ||
| 26 | - /** | ||
| 27 | - * 相比 OpenAIChatModel 来说,DeepSeekChatModel 可以兼容豆包的 thinking 能力! | ||
| 28 | - */ | 30 | + private static final String API_KEY = SystemUtil.get("DOUBAO_API_KEY", |
| 31 | + "sk-xxxx"); // 按需改成你的豆包 API Key | ||
| 32 | + private static final String MODEL = SystemUtil.get("DOUBAO_MODEL", | ||
| 33 | + DouBaoChatModel.MODEL_DEFAULT); | ||
| 34 | + | ||
| 29 | private final DeepSeekChatModel openAiChatModel = DeepSeekChatModel.builder() | 35 | private final DeepSeekChatModel openAiChatModel = DeepSeekChatModel.builder() |
| 30 | .deepSeekApi(DeepSeekApi.builder() | 36 | .deepSeekApi(DeepSeekApi.builder() |
| 31 | .baseUrl(DouBaoChatModel.BASE_URL) | 37 | .baseUrl(DouBaoChatModel.BASE_URL) |
| 32 | .completionsPath(DouBaoChatModel.COMPLETE_PATH) | 38 | .completionsPath(DouBaoChatModel.COMPLETE_PATH) |
| 33 | - .apiKey("5c1b5747-26d2-4ebd-a4e0-dd0e8d8b4272") // apiKey | 39 | + .apiKey(API_KEY) |
| 34 | .build()) | 40 | .build()) |
| 35 | .defaultOptions(DeepSeekChatOptions.builder() | 41 | .defaultOptions(DeepSeekChatOptions.builder() |
| 36 | - .model("doubao-1-5-lite-32k-250115") // 模型(doubao) | ||
| 37 | -// .model("doubao-seed-1-6-thinking-250715") // 模型(doubao) | ||
| 38 | -// .model("deepseek-r1-250120") // 模型(deepseek) | 42 | + .model(MODEL) |
| 39 | .temperature(0.7) | 43 | .temperature(0.7) |
| 40 | .build()) | 44 | .build()) |
| 41 | .build(); | 45 | .build(); |
| @@ -45,6 +49,7 @@ public class DouBaoChatModelTests { | @@ -45,6 +49,7 @@ public class DouBaoChatModelTests { | ||
| 45 | @Test | 49 | @Test |
| 46 | @Disabled | 50 | @Disabled |
| 47 | public void testCall() { | 51 | public void testCall() { |
| 52 | + validateApiKey(API_KEY); | ||
| 48 | // 准备参数 | 53 | // 准备参数 |
| 49 | List<Message> messages = new ArrayList<>(); | 54 | List<Message> messages = new ArrayList<>(); |
| 50 | messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。")); | 55 | messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。")); |
| @@ -54,11 +59,13 @@ public class DouBaoChatModelTests { | @@ -54,11 +59,13 @@ public class DouBaoChatModelTests { | ||
| 54 | ChatResponse response = chatModel.call(new Prompt(messages)); | 59 | ChatResponse response = chatModel.call(new Prompt(messages)); |
| 55 | // 打印结果 | 60 | // 打印结果 |
| 56 | System.out.println(response); | 61 | System.out.println(response); |
| 62 | + System.out.println(Objects.requireNonNull(response.getResult()).getOutput()); | ||
| 57 | } | 63 | } |
| 58 | 64 | ||
| 59 | @Test | 65 | @Test |
| 60 | @Disabled | 66 | @Disabled |
| 61 | public void testStream() { | 67 | public void testStream() { |
| 68 | + validateApiKey(API_KEY); | ||
| 62 | // 准备参数 | 69 | // 准备参数 |
| 63 | List<Message> messages = new ArrayList<>(); | 70 | List<Message> messages = new ArrayList<>(); |
| 64 | messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。")); | 71 | messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。")); |
| @@ -67,25 +74,26 @@ public class DouBaoChatModelTests { | @@ -67,25 +74,26 @@ public class DouBaoChatModelTests { | ||
| 67 | // 调用 | 74 | // 调用 |
| 68 | Flux<ChatResponse> flux = chatModel.stream(new Prompt(messages)); | 75 | Flux<ChatResponse> flux = chatModel.stream(new Prompt(messages)); |
| 69 | // 打印结果 | 76 | // 打印结果 |
| 70 | - flux.doOnNext(System.out::println).then().block(); | 77 | + flux.doOnNext(response -> { |
| 78 | +// System.out.println(response); | ||
| 79 | + System.out.println(Objects.requireNonNull(response.getResult()).getOutput()); | ||
| 80 | + }).then().block(); | ||
| 71 | } | 81 | } |
| 72 | 82 | ||
| 73 | @Test | 83 | @Test |
| 74 | @Disabled | 84 | @Disabled |
| 75 | public void testStream_thinking() { | 85 | public void testStream_thinking() { |
| 86 | + validateApiKey(API_KEY); | ||
| 76 | // 准备参数 | 87 | // 准备参数 |
| 77 | List<Message> messages = new ArrayList<>(); | 88 | List<Message> messages = new ArrayList<>(); |
| 78 | messages.add(new UserMessage("详细分析下,如何设计一个电商系统?")); | 89 | messages.add(new UserMessage("详细分析下,如何设计一个电商系统?")); |
| 79 | - DeepSeekChatOptions options = DeepSeekChatOptions.builder() | ||
| 80 | - .model("doubao-seed-1-6-thinking-250715") | ||
| 81 | - .build(); | ||
| 82 | 90 | ||
| 83 | // 调用 | 91 | // 调用 |
| 84 | - Flux<ChatResponse> flux = chatModel.stream(new Prompt(messages, options)); | 92 | + Flux<ChatResponse> flux = chatModel.stream(new Prompt(messages)); |
| 85 | // 打印结果 | 93 | // 打印结果 |
| 86 | flux.doOnNext(response -> { | 94 | flux.doOnNext(response -> { |
| 87 | // System.out.println(response); | 95 | // System.out.println(response); |
| 88 | - System.out.println(response.getResult().getOutput()); | 96 | + System.out.println(Objects.requireNonNull(response.getResult()).getOutput()); |
| 89 | }).then().block(); | 97 | }).then().block(); |
| 90 | } | 98 | } |
| 91 | 99 |
yudao-module-ai/src/test/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/chat/GrokChatModelTests.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.ai.framework.ai.core.model.chat; | ||
| 2 | + | ||
| 3 | +import cn.hutool.system.SystemUtil; | ||
| 4 | +import cn.iocoder.yudao.module.ai.framework.ai.core.model.grok.GrokChatModel; | ||
| 5 | +import org.junit.jupiter.api.Disabled; | ||
| 6 | +import org.junit.jupiter.api.Test; | ||
| 7 | +import org.springframework.ai.chat.messages.Message; | ||
| 8 | +import org.springframework.ai.chat.messages.SystemMessage; | ||
| 9 | +import org.springframework.ai.chat.messages.UserMessage; | ||
| 10 | +import org.springframework.ai.chat.model.ChatResponse; | ||
| 11 | +import org.springframework.ai.chat.prompt.Prompt; | ||
| 12 | +import org.springframework.ai.openai.OpenAiChatModel; | ||
| 13 | +import org.springframework.ai.openai.OpenAiChatOptions; | ||
| 14 | +import org.springframework.ai.openai.api.OpenAiApi; | ||
| 15 | +import reactor.core.publisher.Flux; | ||
| 16 | + | ||
| 17 | +import java.util.ArrayList; | ||
| 18 | +import java.util.List; | ||
| 19 | +import java.util.Objects; | ||
| 20 | + | ||
| 21 | +import static cn.iocoder.yudao.module.ai.util.AiUtils.validateApiKey; | ||
| 22 | + | ||
| 23 | +/** | ||
| 24 | + * {@link GrokChatModel} 集成测试 | ||
| 25 | + * | ||
| 26 | + * @author 芋道源码 | ||
| 27 | + */ | ||
| 28 | +public class GrokChatModelTests { | ||
| 29 | + | ||
| 30 | + private static final String API_KEY = SystemUtil.get("GROK_API_KEY", | ||
| 31 | + "sk-xxxx"); | ||
| 32 | + private static final String MODEL = SystemUtil.get("GROK_MODEL", | ||
| 33 | + GrokChatModel.MODEL_DEFAULT); | ||
| 34 | + | ||
| 35 | + private final GrokChatModel chatModel = new GrokChatModel(OpenAiChatModel.builder() | ||
| 36 | + .openAiApi(OpenAiApi.builder() | ||
| 37 | + .baseUrl(GrokChatModel.BASE_URL) | ||
| 38 | + .completionsPath(GrokChatModel.COMPLETE_PATH) | ||
| 39 | + .apiKey(API_KEY) | ||
| 40 | + .build()) | ||
| 41 | + .defaultOptions(OpenAiChatOptions.builder() | ||
| 42 | + .model(MODEL) | ||
| 43 | + .temperature(0.7) | ||
| 44 | + .build()) | ||
| 45 | + .build()); | ||
| 46 | + | ||
| 47 | + @Test | ||
| 48 | + @Disabled | ||
| 49 | + public void testCall() { | ||
| 50 | + validateApiKey(API_KEY); | ||
| 51 | + // 准备参数 | ||
| 52 | + List<Message> messages = new ArrayList<>(); | ||
| 53 | + messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。")); | ||
| 54 | + messages.add(new UserMessage("1 + 1 = ?")); | ||
| 55 | + | ||
| 56 | + // 调用 | ||
| 57 | + ChatResponse response = chatModel.call(new Prompt(messages)); | ||
| 58 | + // 打印结果 | ||
| 59 | + System.out.println(response); | ||
| 60 | + System.out.println(Objects.requireNonNull(response.getResult()).getOutput()); | ||
| 61 | + } | ||
| 62 | + | ||
| 63 | + @Test | ||
| 64 | + @Disabled | ||
| 65 | + public void testStream() { | ||
| 66 | + validateApiKey(API_KEY); | ||
| 67 | + // 准备参数 | ||
| 68 | + List<Message> messages = new ArrayList<>(); | ||
| 69 | + messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。")); | ||
| 70 | + messages.add(new UserMessage("1 + 1 = ?")); | ||
| 71 | + | ||
| 72 | + // 调用 | ||
| 73 | + Flux<ChatResponse> flux = chatModel.stream(new Prompt(messages)); | ||
| 74 | + // 打印结果 | ||
| 75 | + flux.doOnNext(response -> { | ||
| 76 | +// System.out.println(response); | ||
| 77 | + System.out.println(Objects.requireNonNull(response.getResult()).getOutput()); | ||
| 78 | + }).then().block(); | ||
| 79 | + } | ||
| 80 | + | ||
| 81 | +} |
yudao-module-ai/src/test/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/chat/HunYuanChatModelTests.java
| 1 | package cn.iocoder.yudao.module.ai.framework.ai.core.model.chat; | 1 | package cn.iocoder.yudao.module.ai.framework.ai.core.model.chat; |
| 2 | 2 | ||
| 3 | +import cn.hutool.system.SystemUtil; | ||
| 3 | import cn.iocoder.yudao.module.ai.framework.ai.core.model.hunyuan.HunYuanChatModel; | 4 | import cn.iocoder.yudao.module.ai.framework.ai.core.model.hunyuan.HunYuanChatModel; |
| 4 | import org.junit.jupiter.api.Disabled; | 5 | import org.junit.jupiter.api.Disabled; |
| 5 | import org.junit.jupiter.api.Test; | 6 | import org.junit.jupiter.api.Test; |
| @@ -15,6 +16,9 @@ import reactor.core.publisher.Flux; | @@ -15,6 +16,9 @@ import reactor.core.publisher.Flux; | ||
| 15 | 16 | ||
| 16 | import java.util.ArrayList; | 17 | import java.util.ArrayList; |
| 17 | import java.util.List; | 18 | import java.util.List; |
| 19 | +import java.util.Objects; | ||
| 20 | + | ||
| 21 | +import static cn.iocoder.yudao.module.ai.util.AiUtils.validateApiKey; | ||
| 18 | 22 | ||
| 19 | /** | 23 | /** |
| 20 | * {@link HunYuanChatModel} 集成测试 | 24 | * {@link HunYuanChatModel} 集成测试 |
| @@ -23,14 +27,19 @@ import java.util.List; | @@ -23,14 +27,19 @@ import java.util.List; | ||
| 23 | */ | 27 | */ |
| 24 | public class HunYuanChatModelTests { | 28 | public class HunYuanChatModelTests { |
| 25 | 29 | ||
| 30 | + private static final String API_KEY = SystemUtil.get("HUNYUAN_API_KEY", | ||
| 31 | + "sk-xxxx"); | ||
| 32 | + private static final String MODEL = SystemUtil.get("HUNYUAN_MODEL", | ||
| 33 | + HunYuanChatModel.MODEL_DEFAULT); | ||
| 34 | + | ||
| 26 | private final DeepSeekChatModel openAiChatModel = DeepSeekChatModel.builder() | 35 | private final DeepSeekChatModel openAiChatModel = DeepSeekChatModel.builder() |
| 27 | .deepSeekApi(DeepSeekApi.builder() | 36 | .deepSeekApi(DeepSeekApi.builder() |
| 28 | .baseUrl(HunYuanChatModel.BASE_URL) | 37 | .baseUrl(HunYuanChatModel.BASE_URL) |
| 29 | .completionsPath(HunYuanChatModel.COMPLETE_PATH) | 38 | .completionsPath(HunYuanChatModel.COMPLETE_PATH) |
| 30 | - .apiKey("sk-abc") // apiKey | 39 | + .apiKey(API_KEY) // apiKey |
| 31 | .build()) | 40 | .build()) |
| 32 | .defaultOptions(DeepSeekChatOptions.builder() | 41 | .defaultOptions(DeepSeekChatOptions.builder() |
| 33 | - .model(HunYuanChatModel.MODEL_DEFAULT) // 模型 | 42 | + .model(MODEL) // 模型 |
| 34 | .temperature(0.7) | 43 | .temperature(0.7) |
| 35 | .build()) | 44 | .build()) |
| 36 | .build(); | 45 | .build(); |
| @@ -40,6 +49,7 @@ public class HunYuanChatModelTests { | @@ -40,6 +49,7 @@ public class HunYuanChatModelTests { | ||
| 40 | @Test | 49 | @Test |
| 41 | @Disabled | 50 | @Disabled |
| 42 | public void testCall() { | 51 | public void testCall() { |
| 52 | + validateApiKey(API_KEY); | ||
| 43 | // 准备参数 | 53 | // 准备参数 |
| 44 | List<Message> messages = new ArrayList<>(); | 54 | List<Message> messages = new ArrayList<>(); |
| 45 | messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。")); | 55 | messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。")); |
| @@ -48,12 +58,13 @@ public class HunYuanChatModelTests { | @@ -48,12 +58,13 @@ public class HunYuanChatModelTests { | ||
| 48 | // 调用 | 58 | // 调用 |
| 49 | ChatResponse response = chatModel.call(new Prompt(messages)); | 59 | ChatResponse response = chatModel.call(new Prompt(messages)); |
| 50 | // 打印结果 | 60 | // 打印结果 |
| 51 | - System.out.println(response); | 61 | + System.out.println(Objects.requireNonNull(response.getResult()).getOutput()); |
| 52 | } | 62 | } |
| 53 | 63 | ||
| 54 | @Test | 64 | @Test |
| 55 | @Disabled | 65 | @Disabled |
| 56 | public void testStream() { | 66 | public void testStream() { |
| 67 | + validateApiKey(API_KEY); | ||
| 57 | // 准备参数 | 68 | // 准备参数 |
| 58 | List<Message> messages = new ArrayList<>(); | 69 | List<Message> messages = new ArrayList<>(); |
| 59 | messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。")); | 70 | messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。")); |
| @@ -68,12 +79,12 @@ public class HunYuanChatModelTests { | @@ -68,12 +79,12 @@ public class HunYuanChatModelTests { | ||
| 68 | @Test | 79 | @Test |
| 69 | @Disabled | 80 | @Disabled |
| 70 | public void testStream_thinking() { | 81 | public void testStream_thinking() { |
| 82 | + validateApiKey(API_KEY); | ||
| 71 | // 准备参数 | 83 | // 准备参数 |
| 72 | List<Message> messages = new ArrayList<>(); | 84 | List<Message> messages = new ArrayList<>(); |
| 73 | messages.add(new UserMessage("详细分析下,如何设计一个电商系统?")); | 85 | messages.add(new UserMessage("详细分析下,如何设计一个电商系统?")); |
| 74 | DeepSeekChatOptions options = DeepSeekChatOptions.builder() | 86 | DeepSeekChatOptions options = DeepSeekChatOptions.builder() |
| 75 | - .model("hunyuan-a13b") | ||
| 76 | -// .model("hunyuan-turbos-latest") | 87 | + .model(MODEL) |
| 77 | .build(); | 88 | .build(); |
| 78 | 89 | ||
| 79 | // 调用 | 90 | // 调用 |
| @@ -85,66 +96,4 @@ public class HunYuanChatModelTests { | @@ -85,66 +96,4 @@ public class HunYuanChatModelTests { | ||
| 85 | }).then().block(); | 96 | }).then().block(); |
| 86 | } | 97 | } |
| 87 | 98 | ||
| 88 | - private final DeepSeekChatModel deepSeekOpenAiChatModel = DeepSeekChatModel.builder() | ||
| 89 | - .deepSeekApi(DeepSeekApi.builder() | ||
| 90 | - .baseUrl(HunYuanChatModel.DEEP_SEEK_BASE_URL) | ||
| 91 | - .completionsPath(HunYuanChatModel.COMPLETE_PATH) | ||
| 92 | - .apiKey("sk-abc") // apiKey | ||
| 93 | - .build()) | ||
| 94 | - .defaultOptions(DeepSeekChatOptions.builder() | ||
| 95 | -// .model(HunYuanChatModel.DEEP_SEEK_MODEL_DEFAULT) // 模型("deepseek-v3") | ||
| 96 | - .model("deepseek-r1") // 模型("deepseek-r1") | ||
| 97 | - .temperature(0.7) | ||
| 98 | - .build()) | ||
| 99 | - .build(); | ||
| 100 | - | ||
| 101 | - private final HunYuanChatModel deepSeekChatModel = new HunYuanChatModel(deepSeekOpenAiChatModel); | ||
| 102 | - | ||
| 103 | - @Test | ||
| 104 | - @Disabled | ||
| 105 | - public void testCall_deepseek() { | ||
| 106 | - // 准备参数 | ||
| 107 | - List<Message> messages = new ArrayList<>(); | ||
| 108 | - messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。")); | ||
| 109 | - messages.add(new UserMessage("1 + 1 = ?")); | ||
| 110 | - | ||
| 111 | - // 调用 | ||
| 112 | - ChatResponse response = deepSeekChatModel.call(new Prompt(messages)); | ||
| 113 | - // 打印结果 | ||
| 114 | - System.out.println(response); | ||
| 115 | - } | ||
| 116 | - | ||
| 117 | - @Test | ||
| 118 | - @Disabled | ||
| 119 | - public void testStream_deepseek() { | ||
| 120 | - // 准备参数 | ||
| 121 | - List<Message> messages = new ArrayList<>(); | ||
| 122 | - messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。")); | ||
| 123 | - messages.add(new UserMessage("1 + 1 = ?")); | ||
| 124 | - | ||
| 125 | - // 调用 | ||
| 126 | - Flux<ChatResponse> flux = deepSeekChatModel.stream(new Prompt(messages)); | ||
| 127 | - // 打印结果 | ||
| 128 | - flux.doOnNext(System.out::println).then().block(); | ||
| 129 | - } | ||
| 130 | - | ||
| 131 | - @Test | ||
| 132 | - @Disabled | ||
| 133 | - public void testStream_deepseek_thinking() { | ||
| 134 | - // 准备参数 | ||
| 135 | - List<Message> messages = new ArrayList<>(); | ||
| 136 | - messages.add(new UserMessage("详细分析下,如何设计一个电商系统?")); | ||
| 137 | - DeepSeekChatOptions options = DeepSeekChatOptions.builder() | ||
| 138 | - .model("deepseek-r1") | ||
| 139 | - .build(); | ||
| 140 | - | ||
| 141 | - // 调用 | ||
| 142 | - Flux<ChatResponse> flux = deepSeekChatModel.stream(new Prompt(messages, options)); | ||
| 143 | - // 打印结果 | ||
| 144 | - flux.doOnNext(response -> { | ||
| 145 | -// System.out.println(response); | ||
| 146 | - System.out.println(response.getResult().getOutput()); | ||
| 147 | - }).then().block(); | ||
| 148 | - } | ||
| 149 | - | ||
| 150 | } | 99 | } |
yudao-module-ai/src/test/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/chat/MiniMaxChatModelTests.java
| 1 | package cn.iocoder.yudao.module.ai.framework.ai.core.model.chat; | 1 | package cn.iocoder.yudao.module.ai.framework.ai.core.model.chat; |
| 2 | 2 | ||
| 3 | +import cn.hutool.system.SystemUtil; | ||
| 4 | +import cn.iocoder.yudao.module.ai.framework.ai.core.model.minimax.MiniMaxChatModel; | ||
| 3 | import org.junit.jupiter.api.Disabled; | 5 | import org.junit.jupiter.api.Disabled; |
| 4 | import org.junit.jupiter.api.Test; | 6 | import org.junit.jupiter.api.Test; |
| 5 | import org.springframework.ai.chat.messages.Message; | 7 | import org.springframework.ai.chat.messages.Message; |
| @@ -7,13 +9,16 @@ import org.springframework.ai.chat.messages.SystemMessage; | @@ -7,13 +9,16 @@ import org.springframework.ai.chat.messages.SystemMessage; | ||
| 7 | import org.springframework.ai.chat.messages.UserMessage; | 9 | import org.springframework.ai.chat.messages.UserMessage; |
| 8 | import org.springframework.ai.chat.model.ChatResponse; | 10 | import org.springframework.ai.chat.model.ChatResponse; |
| 9 | import org.springframework.ai.chat.prompt.Prompt; | 11 | import org.springframework.ai.chat.prompt.Prompt; |
| 10 | -import org.springframework.ai.minimax.MiniMaxChatModel; | ||
| 11 | -import org.springframework.ai.minimax.MiniMaxChatOptions; | ||
| 12 | -import org.springframework.ai.minimax.api.MiniMaxApi; | 12 | +import org.springframework.ai.deepseek.DeepSeekChatModel; |
| 13 | +import org.springframework.ai.deepseek.DeepSeekChatOptions; | ||
| 14 | +import org.springframework.ai.deepseek.api.DeepSeekApi; | ||
| 13 | import reactor.core.publisher.Flux; | 15 | import reactor.core.publisher.Flux; |
| 14 | 16 | ||
| 15 | import java.util.ArrayList; | 17 | import java.util.ArrayList; |
| 16 | import java.util.List; | 18 | import java.util.List; |
| 19 | +import java.util.Objects; | ||
| 20 | + | ||
| 21 | +import static cn.iocoder.yudao.module.ai.util.AiUtils.validateApiKey; | ||
| 17 | 22 | ||
| 18 | /** | 23 | /** |
| 19 | * {@link MiniMaxChatModel} 的集成测试 | 24 | * {@link MiniMaxChatModel} 的集成测试 |
| @@ -22,14 +27,24 @@ import java.util.List; | @@ -22,14 +27,24 @@ import java.util.List; | ||
| 22 | */ | 27 | */ |
| 23 | public class MiniMaxChatModelTests { | 28 | public class MiniMaxChatModelTests { |
| 24 | 29 | ||
| 25 | - private final MiniMaxChatModel chatModel = new MiniMaxChatModel( | ||
| 26 | - new MiniMaxApi("eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJHcm91cE5hbWUiOiLnjovmlofmlowiLCJVc2VyTmFtZSI6IueOi-aWh-aWjCIsIkFjY291bnQiOiIiLCJTdWJqZWN0SUQiOiIxODk3Mjg3MjQ5NDU2ODA4MzQ2IiwiUGhvbmUiOiIxNTYwMTY5MTM5OSIsIkdyb3VwSUQiOiIxODk3Mjg3MjQ5NDQ4NDE5NzM4IiwiUGFnZU5hbWUiOiIiLCJNYWlsIjoiIiwiQ3JlYXRlVGltZSI6IjIwMjUtMDMtMTEgMTI6NTI6MDIiLCJUb2tlblR5cGUiOjEsImlzcyI6Im1pbmltYXgifQ.aAuB7gWW_oA4IYhh-CF7c9MfWWxKN49B_HK-DYjXaDwwffhiG-H1571z1WQhp9QytWG-DqgLejneeSxkiq1wQIe3FsEP2wz4BmGBct31LehbJu8ehLxg_vg75Uod1nFAHbm5mZz6JSVLNIlSo87Xr3UtSzJhAXlapEkcqlA4YOzOpKrZ8l5_OJPTORTCmHWZYgJcRS-faNiH62ZnUEHUozesTFhubJHo5GfJCw_edlnmfSUocERV1BjWvenhZ9My-aYXNktcW9WaSj9l6gayV7A0Ium_PL55T9ln1PcI8gayiVUKJGJDoqNyF1AF9_aF9NOKtTnQzwNqnZdlTYH6hw"), // 密钥 | ||
| 27 | - MiniMaxChatOptions.builder() | ||
| 28 | - .model(MiniMaxApi.ChatModel.ABAB_6_5_G_Chat.getValue()) // 模型 | ||
| 29 | - .build()); | 30 | + private static final String API_KEY = SystemUtil.get("MINIMAX_API_KEY", |
| 31 | + "sk-xxxx"); // 按需改成你的 MiniMax API Key | ||
| 32 | + private static final String MODEL = SystemUtil.get("MINIMAX_MODEL", | ||
| 33 | + MiniMaxChatModel.MODEL_DEFAULT); | ||
| 34 | + | ||
| 35 | + private final MiniMaxChatModel chatModel = new MiniMaxChatModel(DeepSeekChatModel.builder() | ||
| 36 | + .deepSeekApi(DeepSeekApi.builder() | ||
| 37 | + .baseUrl(MiniMaxChatModel.BASE_URL) | ||
| 38 | + .apiKey(API_KEY) | ||
| 39 | + .build()) | ||
| 40 | + .defaultOptions(DeepSeekChatOptions.builder() | ||
| 41 | + .model(MODEL) | ||
| 42 | + .build()) | ||
| 43 | + .build()); | ||
| 30 | @Test | 44 | @Test |
| 31 | @Disabled | 45 | @Disabled |
| 32 | public void testCall() { | 46 | public void testCall() { |
| 47 | + validateApiKey(API_KEY); | ||
| 33 | // 准备参数 | 48 | // 准备参数 |
| 34 | List<Message> messages = new ArrayList<>(); | 49 | List<Message> messages = new ArrayList<>(); |
| 35 | messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。")); | 50 | messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。")); |
| @@ -39,12 +54,13 @@ public class MiniMaxChatModelTests { | @@ -39,12 +54,13 @@ public class MiniMaxChatModelTests { | ||
| 39 | ChatResponse response = chatModel.call(new Prompt(messages)); | 54 | ChatResponse response = chatModel.call(new Prompt(messages)); |
| 40 | // 打印结果 | 55 | // 打印结果 |
| 41 | System.out.println(response); | 56 | System.out.println(response); |
| 42 | - System.out.println(response.getResult().getOutput()); | 57 | + System.out.println(Objects.requireNonNull(response.getResult()).getOutput()); |
| 43 | } | 58 | } |
| 44 | 59 | ||
| 45 | @Test | 60 | @Test |
| 46 | @Disabled | 61 | @Disabled |
| 47 | public void testStream() { | 62 | public void testStream() { |
| 63 | + validateApiKey(API_KEY); | ||
| 48 | // 准备参数 | 64 | // 准备参数 |
| 49 | List<Message> messages = new ArrayList<>(); | 65 | List<Message> messages = new ArrayList<>(); |
| 50 | messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。")); | 66 | messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。")); |
| @@ -55,19 +71,19 @@ public class MiniMaxChatModelTests { | @@ -55,19 +71,19 @@ public class MiniMaxChatModelTests { | ||
| 55 | // 打印结果 | 71 | // 打印结果 |
| 56 | flux.doOnNext(response -> { | 72 | flux.doOnNext(response -> { |
| 57 | // System.out.println(response); | 73 | // System.out.println(response); |
| 58 | - System.out.println(response.getResult().getOutput()); | 74 | + System.out.println(Objects.requireNonNull(response.getResult()).getOutput()); |
| 59 | }).then().block(); | 75 | }).then().block(); |
| 60 | } | 76 | } |
| 61 | 77 | ||
| 62 | - // TODO @芋艿:暂时没解析 reasoning_content 结果,需要等官方修复 | ||
| 63 | @Test | 78 | @Test |
| 64 | @Disabled | 79 | @Disabled |
| 65 | public void testStream_thinking() { | 80 | public void testStream_thinking() { |
| 81 | + validateApiKey(API_KEY); | ||
| 66 | // 准备参数 | 82 | // 准备参数 |
| 67 | List<Message> messages = new ArrayList<>(); | 83 | List<Message> messages = new ArrayList<>(); |
| 68 | messages.add(new UserMessage("详细分析下,如何设计一个电商系统?")); | 84 | messages.add(new UserMessage("详细分析下,如何设计一个电商系统?")); |
| 69 | - MiniMaxChatOptions options = MiniMaxChatOptions.builder() | ||
| 70 | - .model("MiniMax-M1") | 85 | + DeepSeekChatOptions options = DeepSeekChatOptions.builder() |
| 86 | + .model(MODEL) | ||
| 71 | .build(); | 87 | .build(); |
| 72 | 88 | ||
| 73 | // 调用 | 89 | // 调用 |
| @@ -75,7 +91,7 @@ public class MiniMaxChatModelTests { | @@ -75,7 +91,7 @@ public class MiniMaxChatModelTests { | ||
| 75 | // 打印结果 | 91 | // 打印结果 |
| 76 | flux.doOnNext(response -> { | 92 | flux.doOnNext(response -> { |
| 77 | // System.out.println(response); | 93 | // System.out.println(response); |
| 78 | - System.out.println(response.getResult().getOutput()); | 94 | + System.out.println(Objects.requireNonNull(response.getResult()).getOutput()); |
| 79 | }).then().block(); | 95 | }).then().block(); |
| 80 | } | 96 | } |
| 81 | 97 |
yudao-module-ai/src/test/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/chat/MoonshotChatModelTests.java
| 1 | package cn.iocoder.yudao.module.ai.framework.ai.core.model.chat; | 1 | package cn.iocoder.yudao.module.ai.framework.ai.core.model.chat; |
| 2 | 2 | ||
| 3 | +import cn.hutool.system.SystemUtil; | ||
| 4 | +import cn.iocoder.yudao.module.ai.framework.ai.core.model.moonshot.MoonshotChatModel; | ||
| 3 | import org.junit.jupiter.api.Disabled; | 5 | import org.junit.jupiter.api.Disabled; |
| 4 | import org.junit.jupiter.api.Test; | 6 | import org.junit.jupiter.api.Test; |
| 5 | -import org.springaicommunity.moonshot.MoonshotChatModel; | ||
| 6 | -import org.springaicommunity.moonshot.MoonshotChatOptions; | ||
| 7 | -import org.springaicommunity.moonshot.api.MoonshotApi; | ||
| 8 | import org.springframework.ai.chat.messages.Message; | 7 | import org.springframework.ai.chat.messages.Message; |
| 9 | import org.springframework.ai.chat.messages.SystemMessage; | 8 | import org.springframework.ai.chat.messages.SystemMessage; |
| 10 | import org.springframework.ai.chat.messages.UserMessage; | 9 | import org.springframework.ai.chat.messages.UserMessage; |
| 11 | import org.springframework.ai.chat.model.ChatResponse; | 10 | import org.springframework.ai.chat.model.ChatResponse; |
| 12 | import org.springframework.ai.chat.prompt.Prompt; | 11 | import org.springframework.ai.chat.prompt.Prompt; |
| 12 | +import org.springframework.ai.deepseek.DeepSeekChatModel; | ||
| 13 | +import org.springframework.ai.deepseek.DeepSeekChatOptions; | ||
| 14 | +import org.springframework.ai.deepseek.api.DeepSeekApi; | ||
| 13 | import reactor.core.publisher.Flux; | 15 | import reactor.core.publisher.Flux; |
| 14 | 16 | ||
| 15 | import java.util.ArrayList; | 17 | import java.util.ArrayList; |
| 16 | import java.util.List; | 18 | import java.util.List; |
| 19 | +import java.util.Objects; | ||
| 20 | + | ||
| 21 | +import static cn.iocoder.yudao.module.ai.util.AiUtils.validateApiKey; | ||
| 17 | 22 | ||
| 18 | /** | 23 | /** |
| 19 | * {@link MoonshotChatModel} 的集成测试 | 24 | * {@link MoonshotChatModel} 的集成测试 |
| @@ -22,18 +27,27 @@ import java.util.List; | @@ -22,18 +27,27 @@ import java.util.List; | ||
| 22 | */ | 27 | */ |
| 23 | public class MoonshotChatModelTests { | 28 | public class MoonshotChatModelTests { |
| 24 | 29 | ||
| 25 | - private final MoonshotChatModel chatModel = MoonshotChatModel.builder() | ||
| 26 | - .moonshotApi(MoonshotApi.builder() | ||
| 27 | - .apiKey("sk-aHYYV1SARscItye5QQRRNbXij4fy65Ee7pNZlC9gsSQnUKXA") // 密钥 | 30 | + private static final String API_KEY = SystemUtil.get("MOONSHOT_API_KEY", |
| 31 | + "sk-xxxx"); // 按需改成你的 Moonshot API Key | ||
| 32 | + private static final String MODEL = SystemUtil.get("MOONSHOT_MODEL", | ||
| 33 | + MoonshotChatModel.MODEL_DEFAULT); | ||
| 34 | + | ||
| 35 | + private final MoonshotChatModel chatModel = new MoonshotChatModel(DeepSeekChatModel.builder() | ||
| 36 | + .deepSeekApi(DeepSeekApi.builder() | ||
| 37 | + .baseUrl(MoonshotChatModel.BASE_URL) | ||
| 38 | + .completionsPath(MoonshotChatModel.COMPLETE_PATH) | ||
| 39 | + .apiKey(API_KEY) | ||
| 28 | .build()) | 40 | .build()) |
| 29 | - .defaultOptions(MoonshotChatOptions.builder() | ||
| 30 | - .model("kimi-k2-0711-preview") // 模型 | 41 | + .defaultOptions(DeepSeekChatOptions.builder() |
| 42 | + .model(MODEL) | ||
| 43 | + .temperature(1D) | ||
| 31 | .build()) | 44 | .build()) |
| 32 | - .build(); | 45 | + .build()); |
| 33 | 46 | ||
| 34 | @Test | 47 | @Test |
| 35 | @Disabled | 48 | @Disabled |
| 36 | public void testCall() { | 49 | public void testCall() { |
| 50 | + validateApiKey(API_KEY); | ||
| 37 | // 准备参数 | 51 | // 准备参数 |
| 38 | List<Message> messages = new ArrayList<>(); | 52 | List<Message> messages = new ArrayList<>(); |
| 39 | messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。")); | 53 | messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。")); |
| @@ -43,12 +57,13 @@ public class MoonshotChatModelTests { | @@ -43,12 +57,13 @@ public class MoonshotChatModelTests { | ||
| 43 | ChatResponse response = chatModel.call(new Prompt(messages)); | 57 | ChatResponse response = chatModel.call(new Prompt(messages)); |
| 44 | // 打印结果 | 58 | // 打印结果 |
| 45 | System.out.println(response); | 59 | System.out.println(response); |
| 46 | - System.out.println(response.getResult().getOutput()); | 60 | + System.out.println(Objects.requireNonNull(response.getResult()).getOutput()); |
| 47 | } | 61 | } |
| 48 | 62 | ||
| 49 | @Test | 63 | @Test |
| 50 | @Disabled | 64 | @Disabled |
| 51 | public void testStream() { | 65 | public void testStream() { |
| 66 | + validateApiKey(API_KEY); | ||
| 52 | // 准备参数 | 67 | // 准备参数 |
| 53 | List<Message> messages = new ArrayList<>(); | 68 | List<Message> messages = new ArrayList<>(); |
| 54 | messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。")); | 69 | messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。")); |
| @@ -59,20 +74,19 @@ public class MoonshotChatModelTests { | @@ -59,20 +74,19 @@ public class MoonshotChatModelTests { | ||
| 59 | // 打印结果 | 74 | // 打印结果 |
| 60 | flux.doOnNext(response -> { | 75 | flux.doOnNext(response -> { |
| 61 | // System.out.println(response); | 76 | // System.out.println(response); |
| 62 | - System.out.println(response.getResult().getOutput()); | 77 | + System.out.println(Objects.requireNonNull(response.getResult()).getOutput()); |
| 63 | }).then().block(); | 78 | }).then().block(); |
| 64 | } | 79 | } |
| 65 | 80 | ||
| 66 | - // TODO @芋艿:暂时没解析 reasoning_content 结果,需要等官方修复 | ||
| 67 | @Test | 81 | @Test |
| 68 | @Disabled | 82 | @Disabled |
| 69 | public void testStream_thinking() { | 83 | public void testStream_thinking() { |
| 84 | + validateApiKey(API_KEY); | ||
| 70 | // 准备参数 | 85 | // 准备参数 |
| 71 | List<Message> messages = new ArrayList<>(); | 86 | List<Message> messages = new ArrayList<>(); |
| 72 | messages.add(new UserMessage("详细分析下,如何设计一个电商系统?")); | 87 | messages.add(new UserMessage("详细分析下,如何设计一个电商系统?")); |
| 73 | - MoonshotChatOptions options = MoonshotChatOptions.builder() | ||
| 74 | -// .model("kimi-k2-0711-preview") | ||
| 75 | - .model("kimi-thinking-preview") | 88 | + DeepSeekChatOptions options = DeepSeekChatOptions.builder() |
| 89 | + .model(MODEL) | ||
| 76 | .build(); | 90 | .build(); |
| 77 | 91 | ||
| 78 | // 调用 | 92 | // 调用 |
| @@ -80,7 +94,7 @@ public class MoonshotChatModelTests { | @@ -80,7 +94,7 @@ public class MoonshotChatModelTests { | ||
| 80 | // 打印结果 | 94 | // 打印结果 |
| 81 | flux.doOnNext(response -> { | 95 | flux.doOnNext(response -> { |
| 82 | // System.out.println(response); | 96 | // System.out.println(response); |
| 83 | - System.out.println(response.getResult().getOutput()); | 97 | + System.out.println(Objects.requireNonNull(response.getResult()).getOutput()); |
| 84 | }).then().block(); | 98 | }).then().block(); |
| 85 | } | 99 | } |
| 86 | 100 |
yudao-module-ai/src/test/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/chat/StepFunChatModelTests.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.ai.framework.ai.core.model.chat; | ||
| 2 | + | ||
| 3 | +import cn.hutool.system.SystemUtil; | ||
| 4 | +import cn.iocoder.yudao.module.ai.framework.ai.core.model.stepfun.StepFunChatModel; | ||
| 5 | +import org.junit.jupiter.api.Disabled; | ||
| 6 | +import org.junit.jupiter.api.Test; | ||
| 7 | +import org.springframework.ai.chat.messages.Message; | ||
| 8 | +import org.springframework.ai.chat.messages.SystemMessage; | ||
| 9 | +import org.springframework.ai.chat.messages.UserMessage; | ||
| 10 | +import org.springframework.ai.chat.model.ChatResponse; | ||
| 11 | +import org.springframework.ai.chat.prompt.Prompt; | ||
| 12 | +import org.springframework.ai.deepseek.DeepSeekChatModel; | ||
| 13 | +import org.springframework.ai.deepseek.DeepSeekChatOptions; | ||
| 14 | +import org.springframework.ai.deepseek.api.DeepSeekApi; | ||
| 15 | +import reactor.core.publisher.Flux; | ||
| 16 | + | ||
| 17 | +import java.util.ArrayList; | ||
| 18 | +import java.util.List; | ||
| 19 | +import java.util.Objects; | ||
| 20 | + | ||
| 21 | +import static cn.iocoder.yudao.module.ai.util.AiUtils.validateApiKey; | ||
| 22 | + | ||
| 23 | +/** | ||
| 24 | + * {@link StepFunChatModel} 集成测试 | ||
| 25 | + * | ||
| 26 | + * @author 芋道源码 | ||
| 27 | + */ | ||
| 28 | +public class StepFunChatModelTests { | ||
| 29 | + | ||
| 30 | + private static final String API_KEY = SystemUtil.get("STEPFUN_API_KEY", | ||
| 31 | + "sk-xxxx"); | ||
| 32 | + private static final String MODEL = SystemUtil.get("STEPFUN_MODEL", | ||
| 33 | + StepFunChatModel.MODEL_DEFAULT); | ||
| 34 | + | ||
| 35 | + private final StepFunChatModel chatModel = new StepFunChatModel(DeepSeekChatModel.builder() | ||
| 36 | + .deepSeekApi(DeepSeekApi.builder() | ||
| 37 | + .baseUrl(StepFunChatModel.BASE_URL) | ||
| 38 | + .completionsPath(StepFunChatModel.COMPLETE_PATH) | ||
| 39 | + .apiKey(API_KEY) | ||
| 40 | + .build()) | ||
| 41 | + .defaultOptions(DeepSeekChatOptions.builder() | ||
| 42 | + .model(MODEL) | ||
| 43 | + .temperature(0.7) | ||
| 44 | + .build()) | ||
| 45 | + .build()); | ||
| 46 | + | ||
| 47 | + @Test | ||
| 48 | + @Disabled | ||
| 49 | + public void testCall() { | ||
| 50 | + validateApiKey(API_KEY); | ||
| 51 | + // 准备参数 | ||
| 52 | + List<Message> messages = new ArrayList<>(); | ||
| 53 | + messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。")); | ||
| 54 | + messages.add(new UserMessage("1 + 1 = ?")); | ||
| 55 | + | ||
| 56 | + // 调用 | ||
| 57 | + ChatResponse response = chatModel.call(new Prompt(messages)); | ||
| 58 | + // 打印结果 | ||
| 59 | + System.out.println(response); | ||
| 60 | + System.out.println(Objects.requireNonNull(response.getResult()).getOutput()); | ||
| 61 | + } | ||
| 62 | + | ||
| 63 | + @Test | ||
| 64 | + @Disabled | ||
| 65 | + public void testStream() { | ||
| 66 | + validateApiKey(API_KEY); | ||
| 67 | + // 准备参数 | ||
| 68 | + List<Message> messages = new ArrayList<>(); | ||
| 69 | + messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。")); | ||
| 70 | + messages.add(new UserMessage("1 + 1 = ?")); | ||
| 71 | + | ||
| 72 | + // 调用 | ||
| 73 | + Flux<ChatResponse> flux = chatModel.stream(new Prompt(messages)); | ||
| 74 | + // 打印结果 | ||
| 75 | + flux.doOnNext(response -> { | ||
| 76 | +// System.out.println(response); | ||
| 77 | +// System.out.println(Objects.requireNonNull(response.getResult()).getOutput()); | ||
| 78 | + System.out.println(response.getResult() != null ? response.getResult().getOutput() : null); | ||
| 79 | + }).then().block(); | ||
| 80 | + } | ||
| 81 | + | ||
| 82 | +} |
yudao-module-ai/src/test/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/chat/XingHuoChatModelTests.java
| 1 | package cn.iocoder.yudao.module.ai.framework.ai.core.model.chat; | 1 | package cn.iocoder.yudao.module.ai.framework.ai.core.model.chat; |
| 2 | 2 | ||
| 3 | +import cn.hutool.system.SystemUtil; | ||
| 3 | import cn.iocoder.yudao.module.ai.framework.ai.core.model.xinghuo.XingHuoChatModel; | 4 | import cn.iocoder.yudao.module.ai.framework.ai.core.model.xinghuo.XingHuoChatModel; |
| 4 | import org.junit.jupiter.api.Disabled; | 5 | import org.junit.jupiter.api.Disabled; |
| 5 | import org.junit.jupiter.api.Test; | 6 | import org.junit.jupiter.api.Test; |
| @@ -8,13 +9,14 @@ import org.springframework.ai.chat.messages.SystemMessage; | @@ -8,13 +9,14 @@ import org.springframework.ai.chat.messages.SystemMessage; | ||
| 8 | import org.springframework.ai.chat.messages.UserMessage; | 9 | import org.springframework.ai.chat.messages.UserMessage; |
| 9 | import org.springframework.ai.chat.model.ChatResponse; | 10 | import org.springframework.ai.chat.model.ChatResponse; |
| 10 | import org.springframework.ai.chat.prompt.Prompt; | 11 | import org.springframework.ai.chat.prompt.Prompt; |
| 11 | -import org.springframework.ai.deepseek.DeepSeekChatModel; | ||
| 12 | import org.springframework.ai.deepseek.DeepSeekChatOptions; | 12 | import org.springframework.ai.deepseek.DeepSeekChatOptions; |
| 13 | -import org.springframework.ai.deepseek.api.DeepSeekApi; | ||
| 14 | import reactor.core.publisher.Flux; | 13 | import reactor.core.publisher.Flux; |
| 15 | 14 | ||
| 16 | import java.util.ArrayList; | 15 | import java.util.ArrayList; |
| 17 | import java.util.List; | 16 | import java.util.List; |
| 17 | +import java.util.Objects; | ||
| 18 | + | ||
| 19 | +import static cn.iocoder.yudao.module.ai.util.AiUtils.validateApiKey; | ||
| 18 | 20 | ||
| 19 | /** | 21 | /** |
| 20 | * {@link XingHuoChatModel} 集成测试 | 22 | * {@link XingHuoChatModel} 集成测试 |
| @@ -23,24 +25,23 @@ import java.util.List; | @@ -23,24 +25,23 @@ import java.util.List; | ||
| 23 | */ | 25 | */ |
| 24 | public class XingHuoChatModelTests { | 26 | public class XingHuoChatModelTests { |
| 25 | 27 | ||
| 26 | - private final DeepSeekChatModel openAiChatModel = DeepSeekChatModel.builder() | ||
| 27 | - .deepSeekApi(DeepSeekApi.builder() | ||
| 28 | - .baseUrl(XingHuoChatModel.BASE_URL_V2) | ||
| 29 | - .completionsPath(XingHuoChatModel.BASE_COMPLETIONS_PATH_V2) | ||
| 30 | - .apiKey("75b161ed2aef4719b275d6e7f2a4d4cd:YWYxYWI2MTA4ODI2NGZlYTQyNjAzZTcz") // appKey:secretKey | ||
| 31 | - .build()) | ||
| 32 | - .defaultOptions(DeepSeekChatOptions.builder() | ||
| 33 | -// .model("generalv3.5") // 模型 | ||
| 34 | - .model("x1") // 模型 | ||
| 35 | - .temperature(0.7) | ||
| 36 | - .build()) | ||
| 37 | - .build(); | 28 | + private static final String API_KEY = SystemUtil.get("XINGHUO_API_KEY", |
| 29 | + "sk-xxxx"); // 按需改成你的讯飞星火 API Key | ||
| 30 | + private static final String MODEL = SystemUtil.get("XINGHUO_MODEL", | ||
| 31 | + XingHuoChatModel.MODEL_DEFAULT); | ||
| 38 | 32 | ||
| 39 | - private final XingHuoChatModel chatModel = new XingHuoChatModel(openAiChatModel); | 33 | + private final XingHuoChatModel chatModel = XingHuoChatModel.builder() |
| 34 | + .apiKey(API_KEY) | ||
| 35 | + .options(DeepSeekChatOptions.builder() | ||
| 36 | + .model(MODEL) | ||
| 37 | + .temperature(0.7) | ||
| 38 | + .build()) | ||
| 39 | + .build(); | ||
| 40 | 40 | ||
| 41 | @Test | 41 | @Test |
| 42 | @Disabled | 42 | @Disabled |
| 43 | public void testCall() { | 43 | public void testCall() { |
| 44 | + validateApiKey(API_KEY); | ||
| 44 | // 准备参数 | 45 | // 准备参数 |
| 45 | List<Message> messages = new ArrayList<>(); | 46 | List<Message> messages = new ArrayList<>(); |
| 46 | messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。")); | 47 | messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。")); |
| @@ -55,6 +56,7 @@ public class XingHuoChatModelTests { | @@ -55,6 +56,7 @@ public class XingHuoChatModelTests { | ||
| 55 | @Test | 56 | @Test |
| 56 | @Disabled | 57 | @Disabled |
| 57 | public void testStream() { | 58 | public void testStream() { |
| 59 | + validateApiKey(API_KEY); | ||
| 58 | // 准备参数 | 60 | // 准备参数 |
| 59 | List<Message> messages = new ArrayList<>(); | 61 | List<Message> messages = new ArrayList<>(); |
| 60 | messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。")); | 62 | messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。")); |
| @@ -69,11 +71,12 @@ public class XingHuoChatModelTests { | @@ -69,11 +71,12 @@ public class XingHuoChatModelTests { | ||
| 69 | @Test | 71 | @Test |
| 70 | @Disabled | 72 | @Disabled |
| 71 | public void testStream_thinking() { | 73 | public void testStream_thinking() { |
| 74 | + validateApiKey(API_KEY); | ||
| 72 | // 准备参数 | 75 | // 准备参数 |
| 73 | List<Message> messages = new ArrayList<>(); | 76 | List<Message> messages = new ArrayList<>(); |
| 74 | messages.add(new UserMessage("详细分析下,如何设计一个电商系统?")); | 77 | messages.add(new UserMessage("详细分析下,如何设计一个电商系统?")); |
| 75 | DeepSeekChatOptions options = DeepSeekChatOptions.builder() | 78 | DeepSeekChatOptions options = DeepSeekChatOptions.builder() |
| 76 | - .model("x1") | 79 | + .model(MODEL) |
| 77 | .build(); | 80 | .build(); |
| 78 | 81 | ||
| 79 | // 调用 | 82 | // 调用 |
| @@ -81,7 +84,7 @@ public class XingHuoChatModelTests { | @@ -81,7 +84,7 @@ public class XingHuoChatModelTests { | ||
| 81 | // 打印结果 | 84 | // 打印结果 |
| 82 | flux.doOnNext(response -> { | 85 | flux.doOnNext(response -> { |
| 83 | // System.out.println(response); | 86 | // System.out.println(response); |
| 84 | - System.out.println(response.getResult().getOutput()); | 87 | + System.out.println(Objects.requireNonNull(response.getResult()).getOutput()); |
| 85 | }).then().block(); | 88 | }).then().block(); |
| 86 | } | 89 | } |
| 87 | 90 |
yudao-module-ai/src/test/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/chat/YiYanChatModelTests.java
| 1 | package cn.iocoder.yudao.module.ai.framework.ai.core.model.chat; | 1 | package cn.iocoder.yudao.module.ai.framework.ai.core.model.chat; |
| 2 | 2 | ||
| 3 | +import cn.hutool.system.SystemUtil; | ||
| 4 | +import cn.iocoder.yudao.module.ai.framework.ai.core.model.yiyan.YiYanChatModel; | ||
| 3 | import org.junit.jupiter.api.Disabled; | 5 | import org.junit.jupiter.api.Disabled; |
| 4 | import org.junit.jupiter.api.Test; | 6 | import org.junit.jupiter.api.Test; |
| 5 | -import org.springaicommunity.qianfan.QianFanChatModel; | ||
| 6 | -import org.springaicommunity.qianfan.QianFanChatOptions; | ||
| 7 | -import org.springaicommunity.qianfan.api.QianFanApi; | ||
| 8 | import org.springframework.ai.chat.messages.Message; | 7 | import org.springframework.ai.chat.messages.Message; |
| 9 | import org.springframework.ai.chat.messages.UserMessage; | 8 | import org.springframework.ai.chat.messages.UserMessage; |
| 10 | import org.springframework.ai.chat.model.ChatResponse; | 9 | import org.springframework.ai.chat.model.ChatResponse; |
| 11 | import org.springframework.ai.chat.prompt.Prompt; | 10 | import org.springframework.ai.chat.prompt.Prompt; |
| 11 | +import org.springframework.ai.deepseek.DeepSeekChatModel; | ||
| 12 | +import org.springframework.ai.deepseek.DeepSeekChatOptions; | ||
| 13 | +import org.springframework.ai.deepseek.api.DeepSeekApi; | ||
| 12 | import reactor.core.publisher.Flux; | 14 | import reactor.core.publisher.Flux; |
| 13 | 15 | ||
| 14 | import java.util.ArrayList; | 16 | import java.util.ArrayList; |
| 15 | import java.util.List; | 17 | import java.util.List; |
| 18 | +import java.util.Objects; | ||
| 19 | + | ||
| 20 | +import static cn.iocoder.yudao.module.ai.util.AiUtils.validateApiKey; | ||
| 16 | 21 | ||
| 17 | -// TODO @芋艿:百度千帆 API 提供了 V2 版本,目前 Spring AI 不兼容,可关键 <https://github.com/spring-projects/spring-ai/issues/2179> 进展 | ||
| 18 | /** | 22 | /** |
| 19 | - * {@link QianFanChatModel} 的集成测试 | 23 | + * {@link YiYanChatModel} 的集成测试 |
| 20 | * | 24 | * |
| 21 | * @author fansili | 25 | * @author fansili |
| 22 | */ | 26 | */ |
| 23 | public class YiYanChatModelTests { | 27 | public class YiYanChatModelTests { |
| 24 | 28 | ||
| 25 | - private final QianFanChatModel chatModel = new QianFanChatModel( | ||
| 26 | - new QianFanApi("DGnyzREuaY7av7c38bOM9Ji2", "9aR8myflEOPDrEeLhoXv0FdqANOAyIZW"), // 密钥 | ||
| 27 | - QianFanChatOptions.builder() | ||
| 28 | - .model("ERNIE-4.5-8K-Preview") | ||
| 29 | - .build() | ||
| 30 | - ); | 29 | + private static final String API_KEY = SystemUtil.get("YIYAN_API_KEY", |
| 30 | + "sk-xxxx"); // 按需改成你的文心一言 API Key | ||
| 31 | + private static final String MODEL = SystemUtil.get("YIYAN_MODEL", | ||
| 32 | + YiYanChatModel.MODEL_DEFAULT); | ||
| 33 | + | ||
| 34 | + private final YiYanChatModel chatModel = new YiYanChatModel(DeepSeekChatModel.builder() | ||
| 35 | + .deepSeekApi(DeepSeekApi.builder() | ||
| 36 | + .baseUrl(YiYanChatModel.BASE_URL) | ||
| 37 | + .apiKey(API_KEY) | ||
| 38 | + .build()) | ||
| 39 | + .defaultOptions(DeepSeekChatOptions.builder() | ||
| 40 | + .model(MODEL) | ||
| 41 | + .build()) | ||
| 42 | + .build()); | ||
| 31 | 43 | ||
| 32 | @Test | 44 | @Test |
| 33 | @Disabled | 45 | @Disabled |
| 34 | public void testCall() { | 46 | public void testCall() { |
| 47 | + validateApiKey(API_KEY); | ||
| 35 | // 准备参数 | 48 | // 准备参数 |
| 36 | List<Message> messages = new ArrayList<>(); | 49 | List<Message> messages = new ArrayList<>(); |
| 37 | - // TODO @芋艿:文心一言,只要带上 system message 就报错,已经各种测试,很莫名! | ||
| 38 | -// messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。")); | ||
| 39 | messages.add(new UserMessage("1 + 1 = ?")); | 50 | messages.add(new UserMessage("1 + 1 = ?")); |
| 40 | 51 | ||
| 41 | // 调用 | 52 | // 调用 |
| 42 | ChatResponse response = chatModel.call(new Prompt(messages)); | 53 | ChatResponse response = chatModel.call(new Prompt(messages)); |
| 43 | // 打印结果 | 54 | // 打印结果 |
| 44 | System.out.println(response); | 55 | System.out.println(response); |
| 56 | + System.out.println(Objects.requireNonNull(response.getResult()).getOutput()); | ||
| 45 | } | 57 | } |
| 46 | 58 | ||
| 47 | @Test | 59 | @Test |
| 48 | @Disabled | 60 | @Disabled |
| 49 | public void testStream() { | 61 | public void testStream() { |
| 62 | + validateApiKey(API_KEY); | ||
| 50 | // 准备参数 | 63 | // 准备参数 |
| 51 | List<Message> messages = new ArrayList<>(); | 64 | List<Message> messages = new ArrayList<>(); |
| 52 | - // TODO @芋艿:文心一言,只要带上 system message 就报错,已经各种测试,很莫名! | ||
| 53 | -// messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。")); | ||
| 54 | messages.add(new UserMessage("1 + 1 = ?")); | 65 | messages.add(new UserMessage("1 + 1 = ?")); |
| 55 | 66 | ||
| 56 | // 调用 | 67 | // 调用 |
| 57 | Flux<ChatResponse> flux = chatModel.stream(new Prompt(messages)); | 68 | Flux<ChatResponse> flux = chatModel.stream(new Prompt(messages)); |
| 58 | // 打印结果 | 69 | // 打印结果 |
| 59 | - flux.doOnNext(System.out::println).then().block(); | 70 | + flux.doOnNext(response -> { |
| 71 | +// System.out.println(response); | ||
| 72 | + System.out.println(Objects.requireNonNull(response.getResult()).getOutput()); | ||
| 73 | + }).then().block(); | ||
| 60 | } | 74 | } |
| 61 | 75 | ||
| 62 | } | 76 | } |
yudao-module-ai/src/test/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/chat/ZhiPuAiChatModelTests.java
| 1 | package cn.iocoder.yudao.module.ai.framework.ai.core.model.chat; | 1 | package cn.iocoder.yudao.module.ai.framework.ai.core.model.chat; |
| 2 | 2 | ||
| 3 | +import cn.hutool.system.SystemUtil; | ||
| 4 | +import cn.iocoder.yudao.module.ai.framework.ai.core.model.zhipu.ZhiPuChatModel; | ||
| 3 | import org.junit.jupiter.api.Disabled; | 5 | import org.junit.jupiter.api.Disabled; |
| 4 | import org.junit.jupiter.api.Test; | 6 | import org.junit.jupiter.api.Test; |
| 5 | import org.springframework.ai.chat.messages.Message; | 7 | import org.springframework.ai.chat.messages.Message; |
| @@ -7,31 +9,43 @@ import org.springframework.ai.chat.messages.SystemMessage; | @@ -7,31 +9,43 @@ import org.springframework.ai.chat.messages.SystemMessage; | ||
| 7 | import org.springframework.ai.chat.messages.UserMessage; | 9 | import org.springframework.ai.chat.messages.UserMessage; |
| 8 | import org.springframework.ai.chat.model.ChatResponse; | 10 | import org.springframework.ai.chat.model.ChatResponse; |
| 9 | import org.springframework.ai.chat.prompt.Prompt; | 11 | import org.springframework.ai.chat.prompt.Prompt; |
| 10 | -import org.springframework.ai.zhipuai.ZhiPuAiChatModel; | ||
| 11 | -import org.springframework.ai.zhipuai.ZhiPuAiChatOptions; | ||
| 12 | -import org.springframework.ai.zhipuai.api.ZhiPuAiApi; | 12 | +import org.springframework.ai.deepseek.DeepSeekChatModel; |
| 13 | +import org.springframework.ai.deepseek.DeepSeekChatOptions; | ||
| 14 | +import org.springframework.ai.deepseek.api.DeepSeekApi; | ||
| 13 | import reactor.core.publisher.Flux; | 15 | import reactor.core.publisher.Flux; |
| 14 | 16 | ||
| 15 | import java.util.ArrayList; | 17 | import java.util.ArrayList; |
| 16 | import java.util.List; | 18 | import java.util.List; |
| 19 | +import java.util.Objects; | ||
| 20 | + | ||
| 21 | +import static cn.iocoder.yudao.module.ai.util.AiUtils.validateApiKey; | ||
| 17 | 22 | ||
| 18 | /** | 23 | /** |
| 19 | - * {@link ZhiPuAiChatModel} 的集成测试 | 24 | + * {@link ZhiPuChatModel} 的集成测试 |
| 20 | * | 25 | * |
| 21 | * @author 芋道源码 | 26 | * @author 芋道源码 |
| 22 | */ | 27 | */ |
| 23 | public class ZhiPuAiChatModelTests { | 28 | public class ZhiPuAiChatModelTests { |
| 24 | 29 | ||
| 25 | - private final ZhiPuAiChatModel chatModel = new ZhiPuAiChatModel( | ||
| 26 | - ZhiPuAiApi.builder().apiKey("2f35fb6ca4ea41fab898729b7fac086c.6ESSfPcCkxaKEUlR").build(), // 密钥 | ||
| 27 | - ZhiPuAiChatOptions.builder() | ||
| 28 | - .model(ZhiPuAiApi.ChatModel.GLM_4.getName()) // 模型 | 30 | + private static final String API_KEY = SystemUtil.get("ZHIPU_API_KEY", |
| 31 | + "sk-xxxx"); // 按需改成你的智谱 API Key | ||
| 32 | + private static final String MODEL = SystemUtil.get("ZHIPU_MODEL", | ||
| 33 | + ZhiPuChatModel.MODEL_DEFAULT); | ||
| 34 | + | ||
| 35 | + private final ZhiPuChatModel chatModel = new ZhiPuChatModel(DeepSeekChatModel.builder() | ||
| 36 | + .deepSeekApi(DeepSeekApi.builder() | ||
| 37 | + .baseUrl(ZhiPuChatModel.BASE_URL) | ||
| 38 | + .apiKey(API_KEY) | ||
| 39 | + .build()) | ||
| 40 | + .defaultOptions(DeepSeekChatOptions.builder() | ||
| 41 | + .model(MODEL) | ||
| 29 | .build() | 42 | .build() |
| 30 | - ); | 43 | + ).build()); |
| 31 | 44 | ||
| 32 | @Test | 45 | @Test |
| 33 | @Disabled | 46 | @Disabled |
| 34 | public void testCall() { | 47 | public void testCall() { |
| 48 | + validateApiKey(API_KEY); | ||
| 35 | // 准备参数 | 49 | // 准备参数 |
| 36 | List<Message> messages = new ArrayList<>(); | 50 | List<Message> messages = new ArrayList<>(); |
| 37 | messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。")); | 51 | messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。")); |
| @@ -41,12 +55,13 @@ public class ZhiPuAiChatModelTests { | @@ -41,12 +55,13 @@ public class ZhiPuAiChatModelTests { | ||
| 41 | ChatResponse response = chatModel.call(new Prompt(messages)); | 55 | ChatResponse response = chatModel.call(new Prompt(messages)); |
| 42 | // 打印结果 | 56 | // 打印结果 |
| 43 | System.out.println(response); | 57 | System.out.println(response); |
| 44 | - System.out.println(response.getResult().getOutput()); | 58 | + System.out.println(Objects.requireNonNull(response.getResult()).getOutput()); |
| 45 | } | 59 | } |
| 46 | 60 | ||
| 47 | @Test | 61 | @Test |
| 48 | @Disabled | 62 | @Disabled |
| 49 | public void testStream() { | 63 | public void testStream() { |
| 64 | + validateApiKey(API_KEY); | ||
| 50 | // 准备参数 | 65 | // 准备参数 |
| 51 | List<Message> messages = new ArrayList<>(); | 66 | List<Message> messages = new ArrayList<>(); |
| 52 | messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。")); | 67 | messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。")); |
| @@ -57,19 +72,19 @@ public class ZhiPuAiChatModelTests { | @@ -57,19 +72,19 @@ public class ZhiPuAiChatModelTests { | ||
| 57 | // 打印结果 | 72 | // 打印结果 |
| 58 | flux.doOnNext(response -> { | 73 | flux.doOnNext(response -> { |
| 59 | // System.out.println(response); | 74 | // System.out.println(response); |
| 60 | - System.out.println(response.getResult().getOutput()); | 75 | + System.out.println(Objects.requireNonNull(response.getResult()).getOutput()); |
| 61 | }).then().block(); | 76 | }).then().block(); |
| 62 | } | 77 | } |
| 63 | 78 | ||
| 64 | - // TODO @芋艿:暂时没解析 reasoning_content 结果,需要等官方修复 | ||
| 65 | @Test | 79 | @Test |
| 66 | @Disabled | 80 | @Disabled |
| 67 | public void testStream_thinking() { | 81 | public void testStream_thinking() { |
| 82 | + validateApiKey(API_KEY); | ||
| 68 | // 准备参数 | 83 | // 准备参数 |
| 69 | List<Message> messages = new ArrayList<>(); | 84 | List<Message> messages = new ArrayList<>(); |
| 70 | messages.add(new UserMessage("详细分析下,如何设计一个电商系统?")); | 85 | messages.add(new UserMessage("详细分析下,如何设计一个电商系统?")); |
| 71 | - ZhiPuAiChatOptions options = ZhiPuAiChatOptions.builder() | ||
| 72 | - .model("GLM-4.5") | 86 | + DeepSeekChatOptions options = DeepSeekChatOptions.builder() |
| 87 | + .model(MODEL) | ||
| 73 | .build(); | 88 | .build(); |
| 74 | 89 | ||
| 75 | // 调用 | 90 | // 调用 |
| @@ -77,7 +92,7 @@ public class ZhiPuAiChatModelTests { | @@ -77,7 +92,7 @@ public class ZhiPuAiChatModelTests { | ||
| 77 | // 打印结果 | 92 | // 打印结果 |
| 78 | flux.doOnNext(response -> { | 93 | flux.doOnNext(response -> { |
| 79 | // System.out.println(response); | 94 | // System.out.println(response); |
| 80 | - System.out.println(response.getResult().getOutput()); | 95 | + System.out.println(Objects.requireNonNull(response.getResult()).getOutput()); |
| 81 | }).then().block(); | 96 | }).then().block(); |
| 82 | } | 97 | } |
| 83 | 98 |
yudao-module-ai/src/test/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/image/OpenAiImageModelTests.java
| 1 | package cn.iocoder.yudao.module.ai.framework.ai.core.model.image; | 1 | package cn.iocoder.yudao.module.ai.framework.ai.core.model.image; |
| 2 | 2 | ||
| 3 | +import cn.hutool.system.SystemUtil; | ||
| 3 | import org.junit.jupiter.api.Disabled; | 4 | import org.junit.jupiter.api.Disabled; |
| 4 | import org.junit.jupiter.api.Test; | 5 | import org.junit.jupiter.api.Test; |
| 5 | import org.springframework.ai.image.ImageOptions; | 6 | import org.springframework.ai.image.ImageOptions; |
| @@ -9,6 +10,8 @@ import org.springframework.ai.openai.OpenAiImageModel; | @@ -9,6 +10,8 @@ import org.springframework.ai.openai.OpenAiImageModel; | ||
| 9 | import org.springframework.ai.openai.OpenAiImageOptions; | 10 | import org.springframework.ai.openai.OpenAiImageOptions; |
| 10 | import org.springframework.ai.openai.api.OpenAiImageApi; | 11 | import org.springframework.ai.openai.api.OpenAiImageApi; |
| 11 | 12 | ||
| 13 | +import static cn.iocoder.yudao.module.ai.util.AiUtils.validateApiKey; | ||
| 14 | + | ||
| 12 | /** | 15 | /** |
| 13 | * {@link OpenAiImageModel} 集成测试类 | 16 | * {@link OpenAiImageModel} 集成测试类 |
| 14 | * | 17 | * |
| @@ -16,17 +19,20 @@ import org.springframework.ai.openai.api.OpenAiImageApi; | @@ -16,17 +19,20 @@ import org.springframework.ai.openai.api.OpenAiImageApi; | ||
| 16 | */ | 19 | */ |
| 17 | public class OpenAiImageModelTests { | 20 | public class OpenAiImageModelTests { |
| 18 | 21 | ||
| 22 | + private static final String API_KEY = SystemUtil.get("OPENAI_API_KEY", "sk-xxxx"); | ||
| 23 | + | ||
| 19 | private final OpenAiImageModel imageModel = new OpenAiImageModel(OpenAiImageApi.builder() | 24 | private final OpenAiImageModel imageModel = new OpenAiImageModel(OpenAiImageApi.builder() |
| 20 | .baseUrl("https://api.holdai.top") // apiKey | 25 | .baseUrl("https://api.holdai.top") // apiKey |
| 21 | - .apiKey("sk-PytRecQlmjEteoa2RRN6cGnwslo72UUPLQVNEMS6K9yjbmpD") | 26 | + .apiKey(API_KEY) |
| 22 | .build()); | 27 | .build()); |
| 23 | 28 | ||
| 24 | @Test | 29 | @Test |
| 25 | @Disabled | 30 | @Disabled |
| 26 | public void testCall() { | 31 | public void testCall() { |
| 32 | + validateApiKey(API_KEY); | ||
| 27 | // 准备参数 | 33 | // 准备参数 |
| 28 | ImageOptions options = OpenAiImageOptions.builder() | 34 | ImageOptions options = OpenAiImageOptions.builder() |
| 29 | - .model(OpenAiImageApi.ImageModel.DALL_E_2.getValue()) // 这个模型比较便宜 | 35 | + .model("gpt-image-1") |
| 30 | .height(256).width(256) | 36 | .height(256).width(256) |
| 31 | .build(); | 37 | .build(); |
| 32 | ImagePrompt prompt = new ImagePrompt("中国长城!", options); | 38 | ImagePrompt prompt = new ImagePrompt("中国长城!", options); |
yudao-module-ai/src/test/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/image/QianFanImageTests.java deleted
| 1 | -package cn.iocoder.yudao.module.ai.framework.ai.core.model.image; | ||
| 2 | - | ||
| 3 | -import org.junit.jupiter.api.Disabled; | ||
| 4 | -import org.junit.jupiter.api.Test; | ||
| 5 | -import org.springaicommunity.qianfan.QianFanImageModel; | ||
| 6 | -import org.springaicommunity.qianfan.QianFanImageOptions; | ||
| 7 | -import org.springaicommunity.qianfan.api.QianFanImageApi; | ||
| 8 | -import org.springframework.ai.image.ImagePrompt; | ||
| 9 | -import org.springframework.ai.image.ImageResponse; | ||
| 10 | - | ||
| 11 | -import static cn.iocoder.yudao.module.ai.framework.ai.core.model.image.StabilityAiImageModelTests.viewImage; | ||
| 12 | - | ||
| 13 | -// TODO @芋艿:百度千帆 API 提供了 V2 版本,目前 Spring AI 不兼容,可关键 <https://github.com/spring-projects/spring-ai/issues/2179> 进展 | ||
| 14 | - | ||
| 15 | -/** | ||
| 16 | - * {@link QianFanImageModel} 集成测试类 | ||
| 17 | - */ | ||
| 18 | -public class QianFanImageTests { | ||
| 19 | - | ||
| 20 | - private final QianFanImageModel imageModel = new QianFanImageModel( | ||
| 21 | - new QianFanImageApi("qS8k8dYr2nXunagK4SSU8Xjj", "pHGbx51ql2f0hOyabQvSZezahVC3hh3e")); // 密钥 | ||
| 22 | - | ||
| 23 | - @Test | ||
| 24 | - @Disabled | ||
| 25 | - public void testCall() { | ||
| 26 | - // 准备参数 | ||
| 27 | - // 只支持 1024x1024、768x768、768x1024、1024x768、576x1024、1024x576 | ||
| 28 | - QianFanImageOptions imageOptions = QianFanImageOptions.builder() | ||
| 29 | - .model(QianFanImageApi.ImageModel.Stable_Diffusion_XL.getValue()) | ||
| 30 | - .width(1024).height(1024) | ||
| 31 | - .N(1) | ||
| 32 | - .build(); | ||
| 33 | - ImagePrompt prompt = new ImagePrompt("good", imageOptions); | ||
| 34 | - | ||
| 35 | - // 方法调用 | ||
| 36 | - ImageResponse response = imageModel.call(prompt); | ||
| 37 | - // 打印结果 | ||
| 38 | - String b64Json = response.getResult().getOutput().getB64Json(); | ||
| 39 | - System.out.println(response); | ||
| 40 | - viewImage(b64Json); | ||
| 41 | - } | ||
| 42 | - | ||
| 43 | -} |
yudao-module-ai/src/test/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/image/ZhiPuAiImageModelTests.java deleted
| 1 | -package cn.iocoder.yudao.module.ai.framework.ai.core.model.image; | ||
| 2 | - | ||
| 3 | -import org.junit.jupiter.api.Disabled; | ||
| 4 | -import org.junit.jupiter.api.Test; | ||
| 5 | -import org.springframework.ai.image.ImagePrompt; | ||
| 6 | -import org.springframework.ai.image.ImageResponse; | ||
| 7 | -import org.springframework.ai.zhipuai.ZhiPuAiImageModel; | ||
| 8 | -import org.springframework.ai.zhipuai.ZhiPuAiImageOptions; | ||
| 9 | -import org.springframework.ai.zhipuai.api.ZhiPuAiImageApi; | ||
| 10 | - | ||
| 11 | -/** | ||
| 12 | - * {@link ZhiPuAiImageModel} 集成测试 | ||
| 13 | - */ | ||
| 14 | -public class ZhiPuAiImageModelTests { | ||
| 15 | - | ||
| 16 | - private final ZhiPuAiImageModel imageModel = new ZhiPuAiImageModel( | ||
| 17 | - new ZhiPuAiImageApi("78d3228c1d9e5e342a3e1ab349e2dd7b.VXLoq5vrwK2ofboy") // 密钥 | ||
| 18 | - ); | ||
| 19 | - | ||
| 20 | - @Test | ||
| 21 | - @Disabled | ||
| 22 | - public void testCall() { | ||
| 23 | - // 准备参数 | ||
| 24 | - ZhiPuAiImageOptions imageOptions = ZhiPuAiImageOptions.builder() | ||
| 25 | - .model(ZhiPuAiImageApi.ImageModel.CogView_3.getValue()) | ||
| 26 | - .build(); | ||
| 27 | - ImagePrompt prompt = new ImagePrompt("万里长城", imageOptions); | ||
| 28 | - | ||
| 29 | - // 方法调用 | ||
| 30 | - ImageResponse response = imageModel.call(prompt); | ||
| 31 | - // 打印结果 | ||
| 32 | - System.out.println(response); | ||
| 33 | - } | ||
| 34 | - | ||
| 35 | -} |
yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/BpmModelController.java
| 1 | package cn.iocoder.yudao.module.bpm.controller.admin.definition; | 1 | package cn.iocoder.yudao.module.bpm.controller.admin.definition; |
| 2 | 2 | ||
| 3 | import cn.hutool.core.collection.CollUtil; | 3 | import cn.hutool.core.collection.CollUtil; |
| 4 | +import cn.hutool.core.util.StrUtil; | ||
| 4 | import cn.iocoder.yudao.framework.common.pojo.CommonResult; | 5 | import cn.iocoder.yudao.framework.common.pojo.CommonResult; |
| 6 | +import cn.iocoder.yudao.framework.common.util.json.JsonUtils; | ||
| 7 | +import cn.iocoder.yudao.framework.common.util.validation.ValidationUtils; | ||
| 5 | import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.*; | 8 | import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.*; |
| 6 | import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO; | 9 | import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO; |
| 7 | import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelUpdateReqVO; | 10 | import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelUpdateReqVO; |
| @@ -18,6 +21,7 @@ import cn.iocoder.yudao.module.system.api.user.AdminUserApi; | @@ -18,6 +21,7 @@ import cn.iocoder.yudao.module.system.api.user.AdminUserApi; | ||
| 18 | import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; | 21 | import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; |
| 19 | import io.swagger.v3.oas.annotations.Operation; | 22 | import io.swagger.v3.oas.annotations.Operation; |
| 20 | import io.swagger.v3.oas.annotations.Parameter; | 23 | import io.swagger.v3.oas.annotations.Parameter; |
| 24 | +import io.swagger.v3.oas.annotations.Parameters; | ||
| 21 | import io.swagger.v3.oas.annotations.tags.Tag; | 25 | import io.swagger.v3.oas.annotations.tags.Tag; |
| 22 | import jakarta.annotation.Resource; | 26 | import jakarta.annotation.Resource; |
| 23 | import jakarta.validation.Valid; | 27 | import jakarta.validation.Valid; |
| @@ -27,16 +31,20 @@ import org.flowable.engine.repository.ProcessDefinition; | @@ -27,16 +31,20 @@ import org.flowable.engine.repository.ProcessDefinition; | ||
| 27 | import org.springframework.security.access.prepost.PreAuthorize; | 31 | import org.springframework.security.access.prepost.PreAuthorize; |
| 28 | import org.springframework.validation.annotation.Validated; | 32 | import org.springframework.validation.annotation.Validated; |
| 29 | import org.springframework.web.bind.annotation.*; | 33 | import org.springframework.web.bind.annotation.*; |
| 34 | +import org.springframework.web.multipart.MultipartFile; | ||
| 30 | 35 | ||
| 36 | +import java.io.IOException; | ||
| 31 | import java.util.Collections; | 37 | import java.util.Collections; |
| 32 | import java.util.List; | 38 | import java.util.List; |
| 33 | import java.util.Map; | 39 | import java.util.Map; |
| 34 | import java.util.Set; | 40 | import java.util.Set; |
| 35 | import java.util.stream.Stream; | 41 | import java.util.stream.Stream; |
| 36 | 42 | ||
| 43 | +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; | ||
| 37 | import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; | 44 | import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; |
| 38 | import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; | 45 | import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; |
| 39 | import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; | 46 | import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; |
| 47 | +import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.MODEL_IMPORT_FAIL_INVALID; | ||
| 40 | 48 | ||
| 41 | @Tag(name = "管理后台 - 流程模型") | 49 | @Tag(name = "管理后台 - 流程模型") |
| 42 | @RestController | 50 | @RestController |
| @@ -112,6 +120,14 @@ public class BpmModelController { | @@ -112,6 +120,14 @@ public class BpmModelController { | ||
| 112 | return success(BpmModelConvert.INSTANCE.buildModel(model, bpmnBytes, simpleModel)); | 120 | return success(BpmModelConvert.INSTANCE.buildModel(model, bpmnBytes, simpleModel)); |
| 113 | } | 121 | } |
| 114 | 122 | ||
| 123 | + @GetMapping("/export") | ||
| 124 | + @Operation(summary = "导出模型") | ||
| 125 | + @Parameter(name = "id", description = "编号", required = true, example = "1024") | ||
| 126 | + @PreAuthorize("@ss.hasPermission('bpm:model:export')") | ||
| 127 | + public CommonResult<BpmModelSaveReqVO> exportModel(@RequestParam("id") String id) { | ||
| 128 | + return success(modelService.exportModel(id)); | ||
| 129 | + } | ||
| 130 | + | ||
| 115 | @PostMapping("/create") | 131 | @PostMapping("/create") |
| 116 | @Operation(summary = "新建模型") | 132 | @Operation(summary = "新建模型") |
| 117 | @PreAuthorize("@ss.hasPermission('bpm:model:create')") | 133 | @PreAuthorize("@ss.hasPermission('bpm:model:create')") |
| @@ -119,6 +135,36 @@ public class BpmModelController { | @@ -119,6 +135,36 @@ public class BpmModelController { | ||
| 119 | return success(modelService.createModel(createRetVO)); | 135 | return success(modelService.createModel(createRetVO)); |
| 120 | } | 136 | } |
| 121 | 137 | ||
| 138 | + @PostMapping("/import") | ||
| 139 | + @Operation(summary = "导入模型") | ||
| 140 | + @Parameters({ | ||
| 141 | + @Parameter(name = "file", description = "流程模型 JSON 文件", required = true), | ||
| 142 | + @Parameter(name = "key", description = "流程标识,替换导入文件中的标识", example = "process_yudao"), | ||
| 143 | + @Parameter(name = "name", description = "流程名称,替换导入文件中的名称", example = "芋道") | ||
| 144 | + }) | ||
| 145 | + @PreAuthorize("@ss.hasPermission('bpm:model:import')") | ||
| 146 | + public CommonResult<String> importModel(@RequestParam("file") MultipartFile file, | ||
| 147 | + @RequestParam(value = "key", required = false) String key, | ||
| 148 | + @RequestParam(value = "name", required = false) String name) throws IOException { | ||
| 149 | + BpmModelSaveReqVO reqVO; | ||
| 150 | + try { | ||
| 151 | + reqVO = JsonUtils.parseObject(file.getBytes(), BpmModelSaveReqVO.class); | ||
| 152 | + } catch (RuntimeException ex) { | ||
| 153 | + throw exception(MODEL_IMPORT_FAIL_INVALID); | ||
| 154 | + } | ||
| 155 | + if (reqVO == null) { | ||
| 156 | + throw exception(MODEL_IMPORT_FAIL_INVALID); | ||
| 157 | + } | ||
| 158 | + if (StrUtil.isNotEmpty(key)) { | ||
| 159 | + reqVO.setKey(key); | ||
| 160 | + } | ||
| 161 | + if (StrUtil.isNotEmpty(name)) { | ||
| 162 | + reqVO.setName(name); | ||
| 163 | + } | ||
| 164 | + ValidationUtils.validate(reqVO); | ||
| 165 | + return success(modelService.importModel(reqVO)); | ||
| 166 | + } | ||
| 167 | + | ||
| 122 | @PutMapping("/update") | 168 | @PutMapping("/update") |
| 123 | @Operation(summary = "修改模型") | 169 | @Operation(summary = "修改模型") |
| 124 | @PreAuthorize("@ss.hasPermission('bpm:model:update')") | 170 | @PreAuthorize("@ss.hasPermission('bpm:model:update')") |
yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/convert/definition/BpmModelConvert.java
| @@ -111,6 +111,18 @@ public interface BpmModelConvert { | @@ -111,6 +111,18 @@ public interface BpmModelConvert { | ||
| 111 | model.setMetaInfo(JsonUtils.toJsonString(BeanUtils.toBean(reqVO, BpmModelMetaInfoVO.class))); | 111 | model.setMetaInfo(JsonUtils.toJsonString(BeanUtils.toBean(reqVO, BpmModelMetaInfoVO.class))); |
| 112 | } | 112 | } |
| 113 | 113 | ||
| 114 | + default BpmModelSaveReqVO convertToSaveReqVO(Model model) { | ||
| 115 | + BpmModelMetaInfoVO metaInfo = parseMetaInfo(model); | ||
| 116 | + BpmModelSaveReqVO reqVO = new BpmModelSaveReqVO(); | ||
| 117 | + reqVO.setKey(model.getKey()); | ||
| 118 | + reqVO.setName(model.getName()); | ||
| 119 | + reqVO.setCategory(model.getCategory()); | ||
| 120 | + if (metaInfo != null) { | ||
| 121 | + BeanUtils.copyProperties(metaInfo, reqVO); | ||
| 122 | + } | ||
| 123 | + return reqVO; | ||
| 124 | + } | ||
| 125 | + | ||
| 114 | default BpmModelMetaInfoVO parseMetaInfo(Model model) { | 126 | default BpmModelMetaInfoVO parseMetaInfo(Model model) { |
| 115 | BpmModelMetaInfoVO vo = JsonUtils.parseObject(model.getMetaInfo(), BpmModelMetaInfoVO.class); | 127 | BpmModelMetaInfoVO vo = JsonUtils.parseObject(model.getMetaInfo(), BpmModelMetaInfoVO.class); |
| 116 | if (vo == null) { | 128 | if (vo == null) { |
yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/enums/ErrorCodeConstants.java
| @@ -25,6 +25,7 @@ public interface ErrorCodeConstants { | @@ -25,6 +25,7 @@ public interface ErrorCodeConstants { | ||
| 25 | ErrorCode MODEL_DEPLOY_FAIL_BPMN_USER_TASK_NAME_NOT_EXISTS = new ErrorCode(1_009_002_006, "部署流程失败,原因:BPMN 流程图中,用户任务({})的名字不存在"); | 25 | ErrorCode MODEL_DEPLOY_FAIL_BPMN_USER_TASK_NAME_NOT_EXISTS = new ErrorCode(1_009_002_006, "部署流程失败,原因:BPMN 流程图中,用户任务({})的名字不存在"); |
| 26 | ErrorCode MODEL_UPDATE_FAIL_NOT_MANAGER = new ErrorCode(1_009_002_007, "操作流程失败,原因:你不是该流程({})的管理员"); | 26 | ErrorCode MODEL_UPDATE_FAIL_NOT_MANAGER = new ErrorCode(1_009_002_007, "操作流程失败,原因:你不是该流程({})的管理员"); |
| 27 | ErrorCode MODEL_DEPLOY_FAIL_FIRST_USER_TASK_CANDIDATE_STRATEGY_ERROR = new ErrorCode(1_009_002_008, "部署流程失败,原因:首个任务({})的审批人不能是【审批人自选】"); | 27 | ErrorCode MODEL_DEPLOY_FAIL_FIRST_USER_TASK_CANDIDATE_STRATEGY_ERROR = new ErrorCode(1_009_002_008, "部署流程失败,原因:首个任务({})的审批人不能是【审批人自选】"); |
| 28 | + ErrorCode MODEL_IMPORT_FAIL_INVALID = new ErrorCode(1_009_002_009, "导入流程失败,流程模型文件为空或 JSON 格式不正确"); | ||
| 28 | 29 | ||
| 29 | // ========== 流程定义 1-009-003-000 ========== | 30 | // ========== 流程定义 1-009-003-000 ========== |
| 30 | ErrorCode PROCESS_DEFINITION_KEY_NOT_MATCH = new ErrorCode(1_009_003_000, "流程定义的标识期望是({}),当前是({}),请修改 BPMN 流程图"); | 31 | ErrorCode PROCESS_DEFINITION_KEY_NOT_MATCH = new ErrorCode(1_009_003_000, "流程定义的标识期望是({}),当前是({}),请修改 BPMN 流程图"); |
yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelService.java
| @@ -41,6 +41,22 @@ public interface BpmModelService { | @@ -41,6 +41,22 @@ public interface BpmModelService { | ||
| 41 | String createModel(@Valid BpmModelSaveReqVO modelVO); | 41 | String createModel(@Valid BpmModelSaveReqVO modelVO); |
| 42 | 42 | ||
| 43 | /** | 43 | /** |
| 44 | + * 导入流程模型 | ||
| 45 | + * | ||
| 46 | + * @param reqVO 导入信息 | ||
| 47 | + * @return 创建的流程模型的编号 | ||
| 48 | + */ | ||
| 49 | + String importModel(@Valid BpmModelSaveReqVO reqVO); | ||
| 50 | + | ||
| 51 | + /** | ||
| 52 | + * 导出流程模型 | ||
| 53 | + * | ||
| 54 | + * @param id 编号 | ||
| 55 | + * @return 导出的流程模型 | ||
| 56 | + */ | ||
| 57 | + BpmModelSaveReqVO exportModel(String id); | ||
| 58 | + | ||
| 59 | + /** | ||
| 44 | * 获得流程模块 | 60 | * 获得流程模块 |
| 45 | * | 61 | * |
| 46 | * @param id 编号 | 62 | * @param id 编号 |
yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelServiceImpl.java
| @@ -120,6 +120,42 @@ public class BpmModelServiceImpl implements BpmModelService { | @@ -120,6 +120,42 @@ public class BpmModelServiceImpl implements BpmModelService { | ||
| 120 | } | 120 | } |
| 121 | 121 | ||
| 122 | @Override | 122 | @Override |
| 123 | + @Transactional(rollbackFor = Exception.class) | ||
| 124 | + public String importModel(@Valid BpmModelSaveReqVO reqVO) { | ||
| 125 | + if (!ValidationUtils.isXmlNCName(reqVO.getKey())) { | ||
| 126 | + throw exception(MODEL_KEY_VALID); | ||
| 127 | + } | ||
| 128 | + // 1. 校验流程标识已经存在 | ||
| 129 | + Model keyModel = getModelByKey(reqVO.getKey()); | ||
| 130 | + if (keyModel != null) { | ||
| 131 | + throw exception(MODEL_KEY_EXISTS, reqVO.getKey()); | ||
| 132 | + } | ||
| 133 | + | ||
| 134 | + // 2. 创建 Model 对象,并归属到当前租户 | ||
| 135 | + reqVO.setSort(System.currentTimeMillis()); | ||
| 136 | + Model model = repositoryService.newModel(); | ||
| 137 | + BpmModelConvert.INSTANCE.copyToModel(model, reqVO); | ||
| 138 | + model.setTenantId(FlowableUtils.getTenantId()); | ||
| 139 | + | ||
| 140 | + // 3. 保存模型 | ||
| 141 | + saveModel(model, reqVO); | ||
| 142 | + return model.getId(); | ||
| 143 | + } | ||
| 144 | + | ||
| 145 | + @Override | ||
| 146 | + public BpmModelSaveReqVO exportModel(String id) { | ||
| 147 | + Model model = validateModelExists(id); | ||
| 148 | + BpmModelSaveReqVO result = BpmModelConvert.INSTANCE.convertToSaveReqVO(model); | ||
| 149 | + if (Objects.equals(result.getType(), BpmModelTypeEnum.BPMN.getType())) { | ||
| 150 | + byte[] bpmnBytes = getModelBpmnXML(id); | ||
| 151 | + result.setBpmnXml(ArrayUtil.isEmpty(bpmnBytes) ? null : StrUtil.utf8Str(bpmnBytes)); | ||
| 152 | + } else if (Objects.equals(result.getType(), BpmModelTypeEnum.SIMPLE.getType())) { | ||
| 153 | + result.setSimpleModel(JsonUtils.parseObject(getModelSimpleJson(id), BpmSimpleModelNodeVO.class)); | ||
| 154 | + } | ||
| 155 | + return result; | ||
| 156 | + } | ||
| 157 | + | ||
| 158 | + @Override | ||
| 123 | @Transactional(rollbackFor = Exception.class) // 因为进行多个操作,所以开启事务 | 159 | @Transactional(rollbackFor = Exception.class) // 因为进行多个操作,所以开启事务 |
| 124 | public void updateModel(Long userId, BpmModelSaveReqVO updateReqVO) { | 160 | public void updateModel(Long userId, BpmModelSaveReqVO updateReqVO) { |
| 125 | // 1. 校验流程模型存在 | 161 | // 1. 校验流程模型存在 |
| @@ -187,7 +223,8 @@ public class BpmModelServiceImpl implements BpmModelService { | @@ -187,7 +223,8 @@ public class BpmModelServiceImpl implements BpmModelService { | ||
| 187 | } | 223 | } |
| 188 | 224 | ||
| 189 | private Model validateModelExists(String id) { | 225 | private Model validateModelExists(String id) { |
| 190 | - Model model = repositoryService.getModel(id); | 226 | + Model model = repositoryService.createModelQuery() |
| 227 | + .modelId(id).modelTenantId(FlowableUtils.getTenantId()).singleResult(); | ||
| 191 | if (model == null) { | 228 | if (model == null) { |
| 192 | throw exception(MODEL_NOT_EXISTS); | 229 | throw exception(MODEL_NOT_EXISTS); |
| 193 | } | 230 | } |
| @@ -429,7 +466,8 @@ public class BpmModelServiceImpl implements BpmModelService { | @@ -429,7 +466,8 @@ public class BpmModelServiceImpl implements BpmModelService { | ||
| 429 | 466 | ||
| 430 | @Override | 467 | @Override |
| 431 | public Model getModel(String id) { | 468 | public Model getModel(String id) { |
| 432 | - return repositoryService.getModel(id); | 469 | + return repositoryService.createModelQuery() |
| 470 | + .modelId(id).modelTenantId(FlowableUtils.getTenantId()).singleResult(); | ||
| 433 | } | 471 | } |
| 434 | 472 | ||
| 435 | @Override | 473 | @Override |
yudao-module-bpm/src/test/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelServiceImplTest.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.bpm.service.definition; | ||
| 2 | + | ||
| 3 | +import cn.hutool.core.util.StrUtil; | ||
| 4 | +import cn.iocoder.yudao.framework.common.util.json.JsonUtils; | ||
| 5 | +import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder; | ||
| 6 | +import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; | ||
| 7 | +import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.BpmModelMetaInfoVO; | ||
| 8 | +import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.BpmModelSaveReqVO; | ||
| 9 | +import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO; | ||
| 10 | +import cn.iocoder.yudao.module.bpm.enums.definition.BpmModelTypeEnum; | ||
| 11 | +import cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeTypeEnum; | ||
| 12 | +import org.flowable.engine.RepositoryService; | ||
| 13 | +import org.flowable.engine.repository.Model; | ||
| 14 | +import org.flowable.engine.repository.ModelQuery; | ||
| 15 | +import org.junit.jupiter.api.AfterEach; | ||
| 16 | +import org.junit.jupiter.api.BeforeEach; | ||
| 17 | +import org.junit.jupiter.api.Test; | ||
| 18 | +import org.mockito.ArgumentCaptor; | ||
| 19 | +import org.mockito.InjectMocks; | ||
| 20 | +import org.mockito.Mock; | ||
| 21 | + | ||
| 22 | +import java.util.Arrays; | ||
| 23 | +import java.util.Collections; | ||
| 24 | +import java.util.Date; | ||
| 25 | + | ||
| 26 | +import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException; | ||
| 27 | +import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.MODEL_NOT_EXISTS; | ||
| 28 | +import static org.junit.jupiter.api.Assertions.*; | ||
| 29 | +import static org.mockito.AdditionalMatchers.aryEq; | ||
| 30 | +import static org.mockito.ArgumentMatchers.eq; | ||
| 31 | +import static org.mockito.Mockito.*; | ||
| 32 | + | ||
| 33 | +/** | ||
| 34 | + * {@link BpmModelServiceImpl} 的单元测试 | ||
| 35 | + * | ||
| 36 | + * @author 芋道源码 | ||
| 37 | + */ | ||
| 38 | +public class BpmModelServiceImplTest extends BaseMockitoUnitTest { | ||
| 39 | + | ||
| 40 | + private static final Long TENANT_ID = 1L; | ||
| 41 | + private static final String MODEL_ID = "model-id"; | ||
| 42 | + | ||
| 43 | + @InjectMocks | ||
| 44 | + private BpmModelServiceImpl modelService; | ||
| 45 | + | ||
| 46 | + @Mock | ||
| 47 | + private RepositoryService repositoryService; | ||
| 48 | + @Mock | ||
| 49 | + private ModelQuery modelQuery; | ||
| 50 | + | ||
| 51 | + @BeforeEach | ||
| 52 | + public void setUp() { | ||
| 53 | + TenantContextHolder.setTenantId(TENANT_ID); | ||
| 54 | + } | ||
| 55 | + | ||
| 56 | + @AfterEach | ||
| 57 | + public void tearDown() { | ||
| 58 | + TenantContextHolder.clear(); | ||
| 59 | + } | ||
| 60 | + | ||
| 61 | + @Test | ||
| 62 | + public void testExportModel_bpmn() { | ||
| 63 | + // 准备参数 | ||
| 64 | + Model model = mockModel(BpmModelTypeEnum.BPMN.getType()); | ||
| 65 | + String bpmnXml = "<definitions />"; | ||
| 66 | + // mock 方法(repositoryService) | ||
| 67 | + mockGetModel(model); | ||
| 68 | + when(repositoryService.getModelEditorSource(eq(MODEL_ID))).thenReturn(StrUtil.utf8Bytes(bpmnXml)); | ||
| 69 | + | ||
| 70 | + // 调用 | ||
| 71 | + BpmModelSaveReqVO result = modelService.exportModel(MODEL_ID); | ||
| 72 | + | ||
| 73 | + // 断言 | ||
| 74 | + assertEquals(model.getKey(), result.getKey()); | ||
| 75 | + assertEquals(model.getName(), result.getName()); | ||
| 76 | + assertEquals(model.getCategory(), result.getCategory()); | ||
| 77 | + assertEquals(BpmModelTypeEnum.BPMN.getType(), result.getType()); | ||
| 78 | + assertEquals(bpmnXml, result.getBpmnXml()); | ||
| 79 | + assertNull(result.getSimpleModel()); | ||
| 80 | + } | ||
| 81 | + | ||
| 82 | + @Test | ||
| 83 | + public void testExportModel_simple() { | ||
| 84 | + // 准备参数 | ||
| 85 | + Model model = mockModel(BpmModelTypeEnum.SIMPLE.getType()); | ||
| 86 | + BpmSimpleModelNodeVO simpleModel = new BpmSimpleModelNodeVO(); | ||
| 87 | + simpleModel.setId("start"); | ||
| 88 | + simpleModel.setType(BpmSimpleModelNodeTypeEnum.START_NODE.getType()); | ||
| 89 | + // mock 方法(repositoryService) | ||
| 90 | + mockGetModel(model); | ||
| 91 | + when(repositoryService.getModelEditorSourceExtra(eq(MODEL_ID))) | ||
| 92 | + .thenReturn(JsonUtils.toJsonByte(simpleModel)); | ||
| 93 | + | ||
| 94 | + // 调用 | ||
| 95 | + BpmModelSaveReqVO result = modelService.exportModel(MODEL_ID); | ||
| 96 | + | ||
| 97 | + // 断言 | ||
| 98 | + assertEquals(BpmModelTypeEnum.SIMPLE.getType(), result.getType()); | ||
| 99 | + assertNotNull(result.getSimpleModel()); | ||
| 100 | + assertEquals(simpleModel.getId(), result.getSimpleModel().getId()); | ||
| 101 | + assertEquals(simpleModel.getType(), result.getSimpleModel().getType()); | ||
| 102 | + assertNull(result.getBpmnXml()); | ||
| 103 | + } | ||
| 104 | + | ||
| 105 | + @Test | ||
| 106 | + public void testExportModel_notExists() { | ||
| 107 | + // mock 方法(repositoryService) | ||
| 108 | + when(repositoryService.createModelQuery()).thenReturn(modelQuery); | ||
| 109 | + when(modelQuery.modelId(eq(MODEL_ID))).thenReturn(modelQuery); | ||
| 110 | + when(modelQuery.modelTenantId(eq(TENANT_ID.toString()))).thenReturn(modelQuery); | ||
| 111 | + when(modelQuery.singleResult()).thenReturn(null); | ||
| 112 | + | ||
| 113 | + // 调用,并断言异常 | ||
| 114 | + assertServiceException(() -> modelService.exportModel(MODEL_ID), MODEL_NOT_EXISTS); | ||
| 115 | + } | ||
| 116 | + | ||
| 117 | + @Test | ||
| 118 | + public void testImportModel_bpmn() { | ||
| 119 | + // 准备参数 | ||
| 120 | + BpmModelSaveReqVO reqVO = new BpmModelSaveReqVO(); | ||
| 121 | + reqVO.setKey("test_process"); | ||
| 122 | + reqVO.setName("测试流程"); | ||
| 123 | + reqVO.setCategory("OA"); | ||
| 124 | + reqVO.setType(BpmModelTypeEnum.BPMN.getType()); | ||
| 125 | + reqVO.setBpmnXml("<definitions />"); | ||
| 126 | + reqVO.setStartUserIds(Arrays.asList(10L, 20L)); | ||
| 127 | + reqVO.setStartDeptIds(Collections.singletonList(30L)); | ||
| 128 | + reqVO.setManagerUserIds(Collections.singletonList(40L)); | ||
| 129 | + Model model = mock(Model.class); | ||
| 130 | + when(model.getId()).thenReturn(MODEL_ID); | ||
| 131 | + // mock 方法(repositoryService) | ||
| 132 | + when(repositoryService.createModelQuery()).thenReturn(modelQuery); | ||
| 133 | + when(modelQuery.modelTenantId(eq(TENANT_ID.toString()))).thenReturn(modelQuery); | ||
| 134 | + when(modelQuery.modelKey(eq(reqVO.getKey()))).thenReturn(modelQuery); | ||
| 135 | + when(modelQuery.singleResult()).thenReturn(null); | ||
| 136 | + when(repositoryService.newModel()).thenReturn(model); | ||
| 137 | + | ||
| 138 | + // 调用 | ||
| 139 | + String result = modelService.importModel(reqVO); | ||
| 140 | + | ||
| 141 | + // 断言 | ||
| 142 | + assertEquals(MODEL_ID, result); | ||
| 143 | + verify(model).setTenantId(TENANT_ID.toString()); | ||
| 144 | + verify(repositoryService).saveModel(same(model)); | ||
| 145 | + verify(repositoryService).addModelEditorSource(eq(MODEL_ID), | ||
| 146 | + aryEq(StrUtil.utf8Bytes(reqVO.getBpmnXml()))); | ||
| 147 | + ArgumentCaptor<String> metaInfoCaptor = ArgumentCaptor.forClass(String.class); | ||
| 148 | + verify(model).setMetaInfo(metaInfoCaptor.capture()); | ||
| 149 | + BpmModelMetaInfoVO metaInfo = JsonUtils.parseObject(metaInfoCaptor.getValue(), BpmModelMetaInfoVO.class); | ||
| 150 | + assertNotNull(metaInfo); | ||
| 151 | + assertEquals(Arrays.asList(10L, 20L), metaInfo.getStartUserIds()); | ||
| 152 | + assertEquals(Collections.singletonList(30L), metaInfo.getStartDeptIds()); | ||
| 153 | + assertEquals(Collections.singletonList(40L), metaInfo.getManagerUserIds()); | ||
| 154 | + } | ||
| 155 | + | ||
| 156 | + private Model mockModel(Integer type) { | ||
| 157 | + Model model = mock(Model.class); | ||
| 158 | + when(model.getKey()).thenReturn("test_process"); | ||
| 159 | + when(model.getName()).thenReturn("测试流程"); | ||
| 160 | + when(model.getCategory()).thenReturn("OA"); | ||
| 161 | + when(model.getCreateTime()).thenReturn(new Date(1_000L)); | ||
| 162 | + BpmModelMetaInfoVO metaInfo = new BpmModelMetaInfoVO(); | ||
| 163 | + metaInfo.setType(type); | ||
| 164 | + when(model.getMetaInfo()).thenReturn(JsonUtils.toJsonString(metaInfo)); | ||
| 165 | + return model; | ||
| 166 | + } | ||
| 167 | + | ||
| 168 | + private void mockGetModel(Model model) { | ||
| 169 | + when(repositoryService.createModelQuery()).thenReturn(modelQuery); | ||
| 170 | + when(modelQuery.modelId(eq(MODEL_ID))).thenReturn(modelQuery); | ||
| 171 | + when(modelQuery.modelTenantId(eq(TENANT_ID.toString()))).thenReturn(modelQuery); | ||
| 172 | + when(modelQuery.singleResult()).thenReturn(model); | ||
| 173 | + } | ||
| 174 | + | ||
| 175 | +} |
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/message/ImChannelMessageManagerController.java
| @@ -23,6 +23,8 @@ import org.springframework.security.access.prepost.PreAuthorize; | @@ -23,6 +23,8 @@ import org.springframework.security.access.prepost.PreAuthorize; | ||
| 23 | import org.springframework.validation.annotation.Validated; | 23 | import org.springframework.validation.annotation.Validated; |
| 24 | import org.springframework.web.bind.annotation.*; | 24 | import org.springframework.web.bind.annotation.*; |
| 25 | 25 | ||
| 26 | +import java.util.Collections; | ||
| 27 | +import java.util.List; | ||
| 26 | import java.util.Map; | 28 | import java.util.Map; |
| 27 | 29 | ||
| 28 | import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; | 30 | import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; |
| @@ -62,19 +64,42 @@ public class ImChannelMessageManagerController { | @@ -62,19 +64,42 @@ public class ImChannelMessageManagerController { | ||
| 62 | @PreAuthorize("@ss.hasPermission('im:manager:channel-message:query')") | 64 | @PreAuthorize("@ss.hasPermission('im:manager:channel-message:query')") |
| 63 | public CommonResult<PageResult<ImChannelMessageRespVO>> getMessagePage(@Valid ImChannelMessagePageReqVO pageReqVO) { | 65 | public CommonResult<PageResult<ImChannelMessageRespVO>> getMessagePage(@Valid ImChannelMessagePageReqVO pageReqVO) { |
| 64 | PageResult<ImChannelMessageDO> pageResult = channelMessageService.getMessagePage(pageReqVO); | 66 | PageResult<ImChannelMessageDO> pageResult = channelMessageService.getMessagePage(pageReqVO); |
| 65 | - if (CollUtil.isEmpty(pageResult.getList())) { | ||
| 66 | - return success(PageResult.empty(pageResult.getTotal())); | 67 | + return success(new PageResult<>(buildMessageRespVOList(pageResult.getList()), pageResult.getTotal())); |
| 68 | + } | ||
| 69 | + | ||
| 70 | + @GetMapping("/get") | ||
| 71 | + @Operation(summary = "获得频道消息详情") | ||
| 72 | + @Parameter(name = "id", description = "消息编号", required = true, example = "1024") | ||
| 73 | + @PreAuthorize("@ss.hasPermission('im:manager:channel-message:query')") | ||
| 74 | + public CommonResult<ImChannelMessageRespVO> getMessage(@RequestParam("id") Long id) { | ||
| 75 | + ImChannelMessageDO message = channelMessageService.getMessage(id); | ||
| 76 | + return success(buildMessageRespVO(message)); | ||
| 77 | + } | ||
| 78 | + | ||
| 79 | + // ==================== 拼接 VO ==================== | ||
| 80 | + | ||
| 81 | + private ImChannelMessageRespVO buildMessageRespVO(ImChannelMessageDO message) { | ||
| 82 | + if (message == null) { | ||
| 83 | + return null; | ||
| 84 | + } | ||
| 85 | + return CollUtil.getFirst(buildMessageRespVOList(Collections.singletonList(message))); | ||
| 86 | + } | ||
| 87 | + | ||
| 88 | + private List<ImChannelMessageRespVO> buildMessageRespVOList(List<ImChannelMessageDO> messages) { | ||
| 89 | + if (CollUtil.isEmpty(messages)) { | ||
| 90 | + return Collections.emptyList(); | ||
| 67 | } | 91 | } |
| 68 | - // 批量查询频道和素材,并回填频道名 / 素材标题 | 92 | + // 1. 查询关联数据 |
| 69 | Map<Long, ImChannelDO> channelMap = channelService.getChannelMap( | 93 | Map<Long, ImChannelDO> channelMap = channelService.getChannelMap( |
| 70 | - convertSet(pageResult.getList(), ImChannelMessageDO::getChannelId)); | 94 | + convertSet(messages, ImChannelMessageDO::getChannelId)); |
| 71 | Map<Long, ImChannelMaterialDO> materialMap = channelMaterialService.getMaterialMap( | 95 | Map<Long, ImChannelMaterialDO> materialMap = channelMaterialService.getMaterialMap( |
| 72 | - convertSet(pageResult.getList(), ImChannelMessageDO::getMaterialId)); | ||
| 73 | - return success(BeanUtils.toBean(pageResult, ImChannelMessageRespVO.class, vo -> { | ||
| 74 | - MapUtils.findAndThen(channelMap, vo.getChannelId(), c -> vo.setChannelName(c.getName())); | 96 | + convertSet(messages, ImChannelMessageDO::getMaterialId)); |
| 97 | + // 2. 拼接 VO | ||
| 98 | + return BeanUtils.toBean(messages, ImChannelMessageRespVO.class, vo -> { | ||
| 99 | + MapUtils.findAndThen(channelMap, vo.getChannelId(), channel -> vo.setChannelName(channel.getName())); | ||
| 75 | MapUtils.findAndThen(materialMap, vo.getMaterialId(), | 100 | MapUtils.findAndThen(materialMap, vo.getMaterialId(), |
| 76 | material -> vo.setMaterialTitle(material.getTitle()).setMaterialCoverUrl(material.getCoverUrl())); | 101 | material -> vo.setMaterialTitle(material.getTitle()).setMaterialCoverUrl(material.getCoverUrl())); |
| 77 | - })); | 102 | + }); |
| 78 | } | 103 | } |
| 79 | 104 | ||
| 80 | } | 105 | } |
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/framework/rtc/core/LiveKitClient.java
| @@ -208,17 +208,21 @@ public class LiveKitClient { | @@ -208,17 +208,21 @@ public class LiveKitClient { | ||
| 208 | /** | 208 | /** |
| 209 | * 签发管理 Token;用于调 Server API(DeleteRoom / ListParticipants / RemoveParticipant 等) | 209 | * 签发管理 Token;用于调 Server API(DeleteRoom / ListParticipants / RemoveParticipant 等) |
| 210 | * | 210 | * |
| 211 | + * @param room 房间名;LiveKit 的 roomAdmin 权限必须限定到具体房间 | ||
| 211 | * @return JWT 字符串 | 212 | * @return JWT 字符串 |
| 212 | */ | 213 | */ |
| 213 | - private String signAdminToken() { | 214 | + private String signAdminToken(String room) { |
| 214 | ImProperties.Rtc cfg = imProperties.getRtc(); | 215 | ImProperties.Rtc cfg = imProperties.getRtc(); |
| 215 | // roomAdmin claim 给管理类 API 必备 | 216 | // roomAdmin claim 给管理类 API 必备 |
| 217 | + Map<String, Object> video = new HashMap<>(); | ||
| 218 | + video.put("roomAdmin", true); | ||
| 219 | + video.put("room", room); | ||
| 216 | long nowSec = Instant.now().getEpochSecond(); | 220 | long nowSec = Instant.now().getEpochSecond(); |
| 217 | return JWT.create() | 221 | return JWT.create() |
| 218 | .setIssuer(cfg.getApiKey()) | 222 | .setIssuer(cfg.getApiKey()) |
| 219 | .setNotBefore(new Date(nowSec * 1000)) | 223 | .setNotBefore(new Date(nowSec * 1000)) |
| 220 | .setExpiresAt(new Date((nowSec + ADMIN_TOKEN_TTL.getSeconds()) * 1000)) | 224 | .setExpiresAt(new Date((nowSec + ADMIN_TOKEN_TTL.getSeconds()) * 1000)) |
| 221 | - .setPayload("video", MapUtil.of("roomAdmin", true)) | 225 | + .setPayload("video", video) |
| 222 | .setSigner(JWTSignerUtil.hs256(cfg.getApiSecret().getBytes(StandardCharsets.UTF_8))) | 226 | .setSigner(JWTSignerUtil.hs256(cfg.getApiSecret().getBytes(StandardCharsets.UTF_8))) |
| 223 | .sign(); | 227 | .sign(); |
| 224 | } | 228 | } |
| @@ -232,7 +236,7 @@ public class LiveKitClient { | @@ -232,7 +236,7 @@ public class LiveKitClient { | ||
| 232 | */ | 236 | */ |
| 233 | private HttpResponse postTwirp(String path, String room) { | 237 | private HttpResponse postTwirp(String path, String room) { |
| 234 | Assert.notBlank(room, "room 不可为空"); | 238 | Assert.notBlank(room, "room 不可为空"); |
| 235 | - String token = signAdminToken(); | 239 | + String token = signAdminToken(room); |
| 236 | return HttpRequest.post(HttpUtils.wsUrlToHttp(imProperties.getRtc().getLivekitUrl()) + path) | 240 | return HttpRequest.post(HttpUtils.wsUrlToHttp(imProperties.getRtc().getLivekitUrl()) + path) |
| 237 | .header("Authorization", "Bearer " + token) | 241 | .header("Authorization", "Bearer " + token) |
| 238 | .header("Content-Type", "application/json") | 242 | .header("Content-Type", "application/json") |
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/ImChannelMessageService.java
| @@ -66,6 +66,14 @@ public interface ImChannelMessageService { | @@ -66,6 +66,14 @@ public interface ImChannelMessageService { | ||
| 66 | PageResult<ImChannelMessageDO> getMessagePage(ImChannelMessagePageReqVO reqVO); | 66 | PageResult<ImChannelMessageDO> getMessagePage(ImChannelMessagePageReqVO reqVO); |
| 67 | 67 | ||
| 68 | /** | 68 | /** |
| 69 | + * 获得频道消息 | ||
| 70 | + * | ||
| 71 | + * @param id 消息编号 | ||
| 72 | + * @return 频道消息 | ||
| 73 | + */ | ||
| 74 | + ImChannelMessageDO getMessage(Long id); | ||
| 75 | + | ||
| 76 | + /** | ||
| 69 | * 删除消息 | 77 | * 删除消息 |
| 70 | * | 78 | * |
| 71 | * @param id 消息编号 | 79 | * @param id 消息编号 |
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/ImChannelMessageServiceImpl.java
| @@ -138,6 +138,11 @@ public class ImChannelMessageServiceImpl implements ImChannelMessageService { | @@ -138,6 +138,11 @@ public class ImChannelMessageServiceImpl implements ImChannelMessageService { | ||
| 138 | } | 138 | } |
| 139 | 139 | ||
| 140 | @Override | 140 | @Override |
| 141 | + public ImChannelMessageDO getMessage(Long id) { | ||
| 142 | + return channelMessageMapper.selectById(id); | ||
| 143 | + } | ||
| 144 | + | ||
| 145 | + @Override | ||
| 141 | public void deleteMessage(Long id) { | 146 | public void deleteMessage(Long id) { |
| 142 | if (channelMessageMapper.selectById(id) == null) { | 147 | if (channelMessageMapper.selectById(id) == null) { |
| 143 | throw exception(IM_CHANNEL_MESSAGE_NOT_EXISTS); | 148 | throw exception(IM_CHANNEL_MESSAGE_NOT_EXISTS); |
yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/enums/ErrorCodeConstants.java
| @@ -48,6 +48,10 @@ public interface ErrorCodeConstants { | @@ -48,6 +48,10 @@ public interface ErrorCodeConstants { | ||
| 48 | ErrorCode CODEGEN_MASTER_TABLE_NOT_EXISTS = new ErrorCode(1_001_004_010, "主表(id={})定义不存在,请检查"); | 48 | ErrorCode CODEGEN_MASTER_TABLE_NOT_EXISTS = new ErrorCode(1_001_004_010, "主表(id={})定义不存在,请检查"); |
| 49 | ErrorCode CODEGEN_SUB_COLUMN_NOT_EXISTS = new ErrorCode(1_001_004_011, "子表的字段(id={})不存在,请检查"); | 49 | ErrorCode CODEGEN_SUB_COLUMN_NOT_EXISTS = new ErrorCode(1_001_004_011, "子表的字段(id={})不存在,请检查"); |
| 50 | ErrorCode CODEGEN_MASTER_GENERATION_FAIL_NO_SUB_TABLE = new ErrorCode(1_001_004_012, "主表生成代码失败,原因:它没有子表"); | 50 | ErrorCode CODEGEN_MASTER_GENERATION_FAIL_NO_SUB_TABLE = new ErrorCode(1_001_004_012, "主表生成代码失败,原因:它没有子表"); |
| 51 | + ErrorCode CODEGEN_MASTER_TABLE_NAME_DUPLICATE = new ErrorCode(1_001_004_013, | ||
| 52 | + "主子表规范化类名({})重复,请调整主表或子表类名"); | ||
| 53 | + ErrorCode CODEGEN_MASTER_TABLE_FIELD_DUPLICATE = new ErrorCode(1_001_004_014, | ||
| 54 | + "主子表属性名({})重复,请调整主表字段、子表类名或关联关系"); | ||
| 51 | 55 | ||
| 52 | // ========== 文件配置 1-001-006-000 ========== | 56 | // ========== 文件配置 1-001-006-000 ========== |
| 53 | ErrorCode FILE_CONFIG_NOT_EXISTS = new ErrorCode(1_001_006_000, "文件配置不存在"); | 57 | ErrorCode FILE_CONFIG_NOT_EXISTS = new ErrorCode(1_001_006_000, "文件配置不存在"); |
yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/service/codegen/inner/CodegenEngine.java
| @@ -48,6 +48,9 @@ import java.util.*; | @@ -48,6 +48,9 @@ import java.util.*; | ||
| 48 | 48 | ||
| 49 | import static cn.hutool.core.map.MapUtil.getStr; | 49 | import static cn.hutool.core.map.MapUtil.getStr; |
| 50 | import static cn.hutool.core.text.CharSequenceUtil.*; | 50 | import static cn.hutool.core.text.CharSequenceUtil.*; |
| 51 | +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; | ||
| 52 | +import static cn.iocoder.yudao.module.infra.enums.ErrorCodeConstants.CODEGEN_MASTER_TABLE_FIELD_DUPLICATE; | ||
| 53 | +import static cn.iocoder.yudao.module.infra.enums.ErrorCodeConstants.CODEGEN_MASTER_TABLE_NAME_DUPLICATE; | ||
| 51 | 54 | ||
| 52 | /** | 55 | /** |
| 53 | * 代码生成的引擎,用于具体生成代码 | 56 | * 代码生成的引擎,用于具体生成代码 |
| @@ -152,6 +155,16 @@ public class CodegenEngine { | @@ -152,6 +155,16 @@ public class CodegenEngine { | ||
| 152 | vue3UniappFilePath("pages-${table.moduleName}/${table.businessName}/form/index.vue")) | 155 | vue3UniappFilePath("pages-${table.moduleName}/${table.businessName}/form/index.vue")) |
| 153 | .put(CodegenFrontTypeEnum.VUE3_ADMIN_UNIAPP_WOT.getType(), vue3AdminUniappTemplatePath("views/detail/index.vue"), | 156 | .put(CodegenFrontTypeEnum.VUE3_ADMIN_UNIAPP_WOT.getType(), vue3AdminUniappTemplatePath("views/detail/index.vue"), |
| 154 | vue3UniappFilePath("pages-${table.moduleName}/${table.businessName}/detail/index.vue")) | 157 | vue3UniappFilePath("pages-${table.moduleName}/${table.businessName}/detail/index.vue")) |
| 158 | + .put(CodegenFrontTypeEnum.VUE3_ADMIN_UNIAPP_WOT.getType(), vue3AdminUniappTemplatePath("views/index_tree.vue"), | ||
| 159 | + vue3UniappFilePath("pages-${table.moduleName}/${table.businessName}/index.vue")) | ||
| 160 | + .put(CodegenFrontTypeEnum.VUE3_ADMIN_UNIAPP_WOT.getType(), vue3AdminUniappTemplatePath("components/breadcrumb_tree.vue"), | ||
| 161 | + vue3UniappFilePath("pages-${table.moduleName}/${table.businessName}/components/breadcrumb.vue")) | ||
| 162 | + .put(CodegenFrontTypeEnum.VUE3_ADMIN_UNIAPP_WOT.getType(), vue3AdminUniappTemplatePath("views/detail_master.vue"), | ||
| 163 | + vue3UniappFilePath("pages-${table.moduleName}/${table.businessName}/detail/index.vue")) | ||
| 164 | + .put(CodegenFrontTypeEnum.VUE3_ADMIN_UNIAPP_WOT.getType(), vue3AdminUniappTemplatePath("views/detail_erp.vue"), | ||
| 165 | + vue3UniappFilePath("pages-${table.moduleName}/${table.businessName}/detail/index.vue")) | ||
| 166 | + .put(CodegenFrontTypeEnum.VUE3_ADMIN_UNIAPP_WOT.getType(), vue3AdminUniappTemplatePath("views/form_sub_erp.vue"), | ||
| 167 | + vue3UniappFilePath("pages-${table.moduleName}/${table.businessName}/${subSimpleClassName_strikeCase}/form/index.vue")) | ||
| 155 | // VUE3_VBEN2_ANTD_SCHEMA | 168 | // VUE3_VBEN2_ANTD_SCHEMA |
| 156 | .put(CodegenFrontTypeEnum.VUE3_VBEN2_ANTD_SCHEMA.getType(), vue3VbenTemplatePath("views/data.ts"), | 169 | .put(CodegenFrontTypeEnum.VUE3_VBEN2_ANTD_SCHEMA.getType(), vue3VbenTemplatePath("views/data.ts"), |
| 157 | vue3VbenFilePath("views/${table.moduleName}/${table.businessName}/${classNameVar}.data.ts")) | 170 | vue3VbenFilePath("views/${table.moduleName}/${table.businessName}/${classNameVar}.data.ts")) |
| @@ -380,12 +393,17 @@ public class CodegenEngine { | @@ -380,12 +393,17 @@ public class CodegenEngine { | ||
| 380 | 393 | ||
| 381 | // 2. 执行生成 | 394 | // 2. 执行生成 |
| 382 | Map<String, String> result = Maps.newLinkedHashMapWithExpectedSize(templates.size()); // 有序 | 395 | Map<String, String> result = Maps.newLinkedHashMapWithExpectedSize(templates.size()); // 有序 |
| 396 | + Map<String, String> generatedSources = Maps.newHashMapWithExpectedSize(templates.size()); | ||
| 383 | templates.forEach((vmPath, filePath) -> { | 397 | templates.forEach((vmPath, filePath) -> { |
| 384 | - // 2.1 特殊:主子表专属逻辑 | 398 | + // 2.1 特殊:主子表、Uniapp 树表使用独立页面模版 |
| 399 | + if (isVue3AdminUniappTemplate(vmPath) && !matchVue3AdminUniappTemplate(table, vmPath)) { | ||
| 400 | + return; | ||
| 401 | + } | ||
| 402 | + // 2.2 特殊:主子表专属逻辑 | ||
| 385 | if (isSubTemplate(vmPath)) { | 403 | if (isSubTemplate(vmPath)) { |
| 386 | - generateSubCode(table, subTables, result, vmPath, filePath, bindingMap); | 404 | + generateSubCode(table, subTables, result, generatedSources, vmPath, filePath, bindingMap); |
| 387 | return; | 405 | return; |
| 388 | - // 2.2 特殊:树表专属逻辑 | 406 | + // 2.3 特殊:树表专属逻辑 |
| 389 | } else if (isPageReqVOTemplate(vmPath)) { | 407 | } else if (isPageReqVOTemplate(vmPath)) { |
| 390 | // 减少多余的类生成,例如说 PageVO.java 类 | 408 | // 减少多余的类生成,例如说 PageVO.java 类 |
| 391 | if (CodegenTemplateTypeEnum.isTree(table.getTemplateType())) { | 409 | if (CodegenTemplateTypeEnum.isTree(table.getTemplateType())) { |
| @@ -402,23 +420,36 @@ public class CodegenEngine { | @@ -402,23 +420,36 @@ public class CodegenEngine { | ||
| 402 | return; | 420 | return; |
| 403 | } | 421 | } |
| 404 | } | 422 | } |
| 405 | - // 2.3 默认生成 | ||
| 406 | - generateCode(result, vmPath, filePath, bindingMap); | 423 | + // 2.4 默认生成 |
| 424 | + generateCode(result, generatedSources, vmPath, filePath, bindingMap); | ||
| 407 | }); | 425 | }); |
| 408 | return result; | 426 | return result; |
| 409 | } | 427 | } |
| 410 | 428 | ||
| 411 | - private void generateCode(Map<String, String> result, String vmPath, | 429 | + private void generateCode(Map<String, String> result, Map<String, String> generatedSources, String vmPath, |
| 412 | String filePath, Map<String, Object> bindingMap) { | 430 | String filePath, Map<String, Object> bindingMap) { |
| 413 | filePath = formatFilePath(filePath, bindingMap); | 431 | filePath = formatFilePath(filePath, bindingMap); |
| 432 | + registerGeneratedSource(generatedSources, filePath, vmPath, bindingMap); | ||
| 414 | String content = templateEngine.getTemplate(vmPath).render(bindingMap); | 433 | String content = templateEngine.getTemplate(vmPath).render(bindingMap); |
| 415 | // 格式化代码 | 434 | // 格式化代码 |
| 416 | content = prettyCode(content, vmPath); | 435 | content = prettyCode(content, vmPath); |
| 417 | result.put(filePath, content); | 436 | result.put(filePath, content); |
| 418 | } | 437 | } |
| 419 | 438 | ||
| 439 | + @VisibleForTesting | ||
| 440 | + static void registerGeneratedSource(Map<String, String> generatedSources, String filePath, String vmPath, | ||
| 441 | + Map<String, Object> bindingMap) { | ||
| 442 | + String source = vmPath | ||
| 443 | + + (bindingMap.containsKey("subIndex") ? "[subIndex=" + bindingMap.get("subIndex") + "]" : ""); | ||
| 444 | + String previousSource = generatedSources.putIfAbsent(filePath, source); | ||
| 445 | + if (previousSource != null) { | ||
| 446 | + throw new IllegalStateException(String.format("生成文件路径重复:%s,来源模板:%s、%s", | ||
| 447 | + filePath, previousSource, source)); | ||
| 448 | + } | ||
| 449 | + } | ||
| 450 | + | ||
| 420 | private void generateSubCode(CodegenTableDO table, List<CodegenTableDO> subTables, | 451 | private void generateSubCode(CodegenTableDO table, List<CodegenTableDO> subTables, |
| 421 | - Map<String, String> result, String vmPath, | 452 | + Map<String, String> result, Map<String, String> generatedSources, String vmPath, |
| 422 | String filePath, Map<String, Object> bindingMap) { | 453 | String filePath, Map<String, Object> bindingMap) { |
| 423 | // 没有子表,所以不生成 | 454 | // 没有子表,所以不生成 |
| 424 | if (CollUtil.isEmpty(subTables)) { | 455 | if (CollUtil.isEmpty(subTables)) { |
| @@ -441,7 +472,7 @@ public class CodegenEngine { | @@ -441,7 +472,7 @@ public class CodegenEngine { | ||
| 441 | // 逐个生成 | 472 | // 逐个生成 |
| 442 | for (int i = 0; i < subTables.size(); i++) { | 473 | for (int i = 0; i < subTables.size(); i++) { |
| 443 | bindingMap.put("subIndex", i); | 474 | bindingMap.put("subIndex", i); |
| 444 | - generateCode(result, vmPath, filePath, bindingMap); | 475 | + generateCode(result, generatedSources, vmPath, filePath, bindingMap); |
| 445 | } | 476 | } |
| 446 | bindingMap.remove("subIndex"); | 477 | bindingMap.remove("subIndex"); |
| 447 | } | 478 | } |
| @@ -482,6 +513,13 @@ public class CodegenEngine { | @@ -482,6 +513,13 @@ public class CodegenEngine { | ||
| 482 | if (StrUtil.count(content, "DICT_TYPE.") == 0) { | 513 | if (StrUtil.count(content, "DICT_TYPE.") == 0) { |
| 483 | content = StrUtils.removeLineContains(content, "DICT_TYPE"); | 514 | content = StrUtils.removeLineContains(content, "DICT_TYPE"); |
| 484 | } | 515 | } |
| 516 | + // Vue3 Admin UniApp:清理空白行,并补充文件末尾换行,满足 ESLint 格式要求 | ||
| 517 | + if (vmPath.contains("vue3_admin_uniapp")) { | ||
| 518 | + content = content.replaceAll("(?m)^[\\t ]+$", ""); | ||
| 519 | + if (!content.endsWith("\n")) { | ||
| 520 | + content += "\n"; | ||
| 521 | + } | ||
| 522 | + } | ||
| 485 | return content; | 523 | return content; |
| 486 | } | 524 | } |
| 487 | 525 | ||
| @@ -533,6 +571,14 @@ public class CodegenEngine { | @@ -533,6 +571,14 @@ public class CodegenEngine { | ||
| 533 | List<String> subClassNameVars = new ArrayList<>(); | 571 | List<String> subClassNameVars = new ArrayList<>(); |
| 534 | List<String> simpleClassNameUnderlineCases = new ArrayList<>(); | 572 | List<String> simpleClassNameUnderlineCases = new ArrayList<>(); |
| 535 | List<String> subSimpleClassNameStrikeCases = new ArrayList<>(); | 573 | List<String> subSimpleClassNameStrikeCases = new ArrayList<>(); |
| 574 | + Set<String> masterSymbols = new HashSet<>(); | ||
| 575 | + masterSymbols.add(simpleClassName); | ||
| 576 | + boolean mergeSubTableFields = ObjectUtils.equalsAny(table.getTemplateType(), | ||
| 577 | + CodegenTemplateTypeEnum.MASTER_NORMAL.getType(), CodegenTemplateTypeEnum.MASTER_INNER.getType()); | ||
| 578 | + Set<String> masterFieldSymbols = new HashSet<>(); | ||
| 579 | + if (mergeSubTableFields) { | ||
| 580 | + columns.forEach(column -> masterFieldSymbols.add(column.getJavaField())); | ||
| 581 | + } | ||
| 536 | for (int i = 0; i < subTables.size(); i++) { | 582 | for (int i = 0; i < subTables.size(); i++) { |
| 537 | CodegenTableDO subTable = subTables.get(i); | 583 | CodegenTableDO subTable = subTables.get(i); |
| 538 | List<CodegenColumnDO> subColumns = subColumnsList.get(i); | 584 | List<CodegenColumnDO> subColumns = subColumnsList.get(i); |
| @@ -543,9 +589,18 @@ public class CodegenEngine { | @@ -543,9 +589,18 @@ public class CodegenEngine { | ||
| 543 | subJoinColumnStrikeCases.add(toSymbolCase(subColumn.getJavaField(), '-')); // 将 DictType 转换成 dict-type | 589 | subJoinColumnStrikeCases.add(toSymbolCase(subColumn.getJavaField(), '-')); // 将 DictType 转换成 dict-type |
| 544 | // className 相关 | 590 | // className 相关 |
| 545 | String subSimpleClassName = removePrefix(subTable.getClassName(), upperFirst(subTable.getModuleName())); | 591 | String subSimpleClassName = removePrefix(subTable.getClassName(), upperFirst(subTable.getModuleName())); |
| 592 | + if (!masterSymbols.add(subSimpleClassName)) { | ||
| 593 | + throw exception(CODEGEN_MASTER_TABLE_NAME_DUPLICATE, subSimpleClassName); | ||
| 594 | + } | ||
| 595 | + String subClassNameVar = lowerFirst(subSimpleClassName); | ||
| 596 | + String subFieldName = subClassNameVar | ||
| 597 | + + (Boolean.TRUE.equals(subTable.getSubJoinMany()) ? "s" : ""); | ||
| 598 | + if (mergeSubTableFields && !masterFieldSymbols.add(subFieldName)) { | ||
| 599 | + throw exception(CODEGEN_MASTER_TABLE_FIELD_DUPLICATE, subFieldName); | ||
| 600 | + } | ||
| 546 | subSimpleClassNames.add(subSimpleClassName); | 601 | subSimpleClassNames.add(subSimpleClassName); |
| 547 | simpleClassNameUnderlineCases.add(toUnderlineCase(subSimpleClassName)); // 将 DictType 转换成 dict_type | 602 | simpleClassNameUnderlineCases.add(toUnderlineCase(subSimpleClassName)); // 将 DictType 转换成 dict_type |
| 548 | - subClassNameVars.add(lowerFirst(subSimpleClassName)); // 将 DictType 转换成 dictType,用于变量 | 603 | + subClassNameVars.add(subClassNameVar); // 将 DictType 转换成 dictType,用于变量 |
| 549 | subSimpleClassNameStrikeCases.add(toSymbolCase(subSimpleClassName, '-')); // 将 DictType 转换成 dict-type | 604 | subSimpleClassNameStrikeCases.add(toSymbolCase(subSimpleClassName, '-')); // 将 DictType 转换成 dict-type |
| 550 | } | 605 | } |
| 551 | bindingMap.put("subPrimaryColumns", subPrimaryColumns); | 606 | bindingMap.put("subPrimaryColumns", subPrimaryColumns); |
| @@ -734,6 +789,33 @@ public class CodegenEngine { | @@ -734,6 +789,33 @@ public class CodegenEngine { | ||
| 734 | return path.contains("_sub"); | 789 | return path.contains("_sub"); |
| 735 | } | 790 | } |
| 736 | 791 | ||
| 792 | + private static boolean isVue3AdminUniappTemplate(String path) { | ||
| 793 | + return path.contains("codegen/vue3_admin_uniapp/"); | ||
| 794 | + } | ||
| 795 | + | ||
| 796 | + /** | ||
| 797 | + * 匹配 Uniapp 不同表类型的专用页面模版 | ||
| 798 | + */ | ||
| 799 | + private static boolean matchVue3AdminUniappTemplate(CodegenTableDO table, String path) { | ||
| 800 | + boolean tree = CodegenTemplateTypeEnum.isTree(table.getTemplateType()); | ||
| 801 | + boolean masterSubmit = ObjectUtil.equals(table.getTemplateType(), CodegenTemplateTypeEnum.MASTER_NORMAL.getType()) | ||
| 802 | + || ObjectUtil.equals(table.getTemplateType(), CodegenTemplateTypeEnum.MASTER_INNER.getType()); | ||
| 803 | + boolean masterErp = ObjectUtil.equals(table.getTemplateType(), CodegenTemplateTypeEnum.MASTER_ERP.getType()); | ||
| 804 | + if (path.contains("_tree")) { | ||
| 805 | + return tree; | ||
| 806 | + } | ||
| 807 | + if (path.contains("detail_master")) { | ||
| 808 | + return masterSubmit; | ||
| 809 | + } | ||
| 810 | + if (path.contains("_erp")) { | ||
| 811 | + return masterErp; | ||
| 812 | + } | ||
| 813 | + if (tree && path.endsWith("views/index.vue.vm")) { | ||
| 814 | + return false; | ||
| 815 | + } | ||
| 816 | + return !(masterSubmit || masterErp) || !path.endsWith("views/detail/index.vue.vm"); | ||
| 817 | + } | ||
| 818 | + | ||
| 737 | private static boolean isPageReqVOTemplate(String path) { | 819 | private static boolean isPageReqVOTemplate(String path) { |
| 738 | return path.contains("pageReqVO"); | 820 | return path.contains("pageReqVO"); |
| 739 | } | 821 | } |
yudao-module-infra/src/main/resources/codegen/vue3_admin_uniapp/api/api.ts.vm
| 1 | +#if ($table.templateType != 2) | ||
| 1 | import type { PageParam, PageResult } from '@/http/types' | 2 | import type { PageParam, PageResult } from '@/http/types' |
| 3 | +#end | ||
| 2 | import { http } from '@/http/http' | 4 | import { http } from '@/http/http' |
| 3 | 5 | ||
| 4 | #set ($primaryJavaType = $primaryColumn.javaType.toLowerCase()) | 6 | #set ($primaryJavaType = $primaryColumn.javaType.toLowerCase()) |
| 5 | -#if(${primaryJavaType} == "long" || ${primaryJavaType} == "integer" || ${primaryJavaType} == "short" || ${primaryJavaType} == "double" || ${primaryJavaType} == "bigdecimal" || ${primaryJavaType} == "byte") | 7 | +#if(${primaryJavaType} == "long" || ${primaryJavaType} == "integer" || ${primaryJavaType} == "short" || ${primaryJavaType} == "float" || ${primaryJavaType} == "double" || ${primaryJavaType} == "bigdecimal" || ${primaryJavaType} == "byte") |
| 6 | #set ($primaryTsType = "number") | 8 | #set ($primaryTsType = "number") |
| 7 | #else | 9 | #else |
| 8 | #set ($primaryTsType = "string") | 10 | #set ($primaryTsType = "string") |
| 9 | #end | 11 | #end |
| 12 | +#foreach ($subTable in $subTables) | ||
| 13 | +#set ($index = $foreach.count - 1) | ||
| 14 | +#set ($subSimpleClassName = $subSimpleClassNames.get($index)) | ||
| 15 | +#set ($subColumns = $subColumnsList.get($index)) | ||
| 16 | +#set ($subJoinColumn = $subJoinColumns.get($index)) | ||
| 17 | +/** ${subTable.classComment}信息 */ | ||
| 18 | +export interface ${subSimpleClassName} { | ||
| 19 | +#foreach ($column in $subColumns) | ||
| 20 | +#if ($column.primaryKey || $column.createOperation || $column.updateOperation || $column.listOperationResult) | ||
| 21 | +#set ($javaType = $column.javaType.toLowerCase()) | ||
| 22 | +#set ($optional = $column.nullable || $column.primaryKey || ($table.templateType != 11 && $column.id == $subJoinColumn.id)) | ||
| 23 | +#if(${javaType} == "long" || ${javaType} == "integer" || ${javaType} == "short" || ${javaType} == "float" || ${javaType} == "double" || ${javaType} == "bigdecimal" || ${javaType} == "byte") | ||
| 24 | + ${column.javaField}#if($optional)?#end: number | ||
| 25 | +#elseif(${javaType} == "date" || ${javaType} == "localdate" || ${javaType} == "localdatetime") | ||
| 26 | + ${column.javaField}#if($optional)?#end: number | ||
| 27 | +#elseif(${javaType} == "boolean") | ||
| 28 | + ${column.javaField}#if($optional)?#end: boolean | ||
| 29 | +#else | ||
| 30 | + ${column.javaField}#if($optional)?#end: string | ||
| 31 | +#end | ||
| 32 | +#end | ||
| 33 | +#end | ||
| 34 | +} | ||
| 35 | + | ||
| 36 | +#end | ||
| 10 | /** ${table.classComment}信息 */ | 37 | /** ${table.classComment}信息 */ |
| 11 | export interface ${simpleClassName} { | 38 | export interface ${simpleClassName} { |
| 12 | #foreach ($column in $columns) | 39 | #foreach ($column in $columns) |
| 13 | - #if ($column.primaryKey || $column.createOperation || $column.updateOperation || $column.listOperationResult) | ||
| 14 | - #set ($javaType = $column.javaType.toLowerCase()) | ||
| 15 | - #set ($javaFieldLower = $column.javaField.toLowerCase()) | ||
| 16 | - #set ($optional = $column.nullable || $column.primaryKey || $javaFieldLower == "createtime" || $javaFieldLower == "updatetime") | ||
| 17 | - #if(${javaType} == "long" || ${javaType} == "integer" || ${javaType} == "short" || ${javaType} == "double" || ${javaType} == "bigdecimal" || ${javaType} == "byte") | 40 | +#if ($column.primaryKey || $column.createOperation || $column.updateOperation || $column.listOperationResult) |
| 41 | +#set ($javaType = $column.javaType.toLowerCase()) | ||
| 42 | +#set ($optional = $column.nullable || $column.primaryKey) | ||
| 43 | +#if(${javaType} == "long" || ${javaType} == "integer" || ${javaType} == "short" || ${javaType} == "float" || ${javaType} == "double" || ${javaType} == "bigdecimal" || ${javaType} == "byte") | ||
| 44 | + ${column.javaField}#if($optional)?#end: number | ||
| 45 | +#elseif(${javaType} == "date" || ${javaType} == "localdate" || ${javaType} == "localdatetime") | ||
| 18 | ${column.javaField}#if($optional)?#end: number | 46 | ${column.javaField}#if($optional)?#end: number |
| 19 | - #elseif(${javaType} == "date" || ${javaType} == "localdate" || ${javaType} == "localdatetime") | ||
| 20 | - ${column.javaField}#if($optional)?#end: Date | ||
| 21 | - #elseif(${javaType} == "boolean") | 47 | +#elseif(${javaType} == "boolean") |
| 22 | ${column.javaField}#if($optional)?#end: boolean | 48 | ${column.javaField}#if($optional)?#end: boolean |
| 23 | - #else | 49 | +#else |
| 24 | ${column.javaField}#if($optional)?#end: string | 50 | ${column.javaField}#if($optional)?#end: string |
| 25 | - #end | ||
| 26 | - #end | 51 | +#end |
| 52 | +#end | ||
| 53 | +#end | ||
| 54 | +#if ($table.templateType == 2) | ||
| 55 | + children?: ${simpleClassName}[] | ||
| 56 | +#end | ||
| 57 | +#if ($table.templateType == 10 || $table.templateType == 12) | ||
| 58 | +#foreach ($subTable in $subTables) | ||
| 59 | +#set ($index = $foreach.count - 1) | ||
| 60 | +#set ($subSimpleClassName = $subSimpleClassNames.get($index)) | ||
| 61 | +#set ($subClassNameVar = $subClassNameVars.get($index)) | ||
| 62 | + ${subClassNameVar}#if($subTable.subJoinMany)s#end?: ${subSimpleClassName}#if($subTable.subJoinMany)[]#else | null#end | ||
| 63 | +#end | ||
| 27 | #end | 64 | #end |
| 28 | } | 65 | } |
| 29 | 66 | ||
| 67 | +#if ($table.templateType == 2) | ||
| 68 | +/** 获取${table.classComment}列表 */ | ||
| 69 | +export function get${simpleClassName}List(params?: Record<string, any>) { | ||
| 70 | + return http.get<${simpleClassName}[]>('/${table.moduleName}/${simpleClassName_strikeCase}/list', params) | ||
| 71 | +} | ||
| 72 | +#else | ||
| 30 | /** 获取${table.classComment}分页列表 */ | 73 | /** 获取${table.classComment}分页列表 */ |
| 31 | export function get${simpleClassName}Page(params: PageParam) { | 74 | export function get${simpleClassName}Page(params: PageParam) { |
| 32 | return http.get<PageResult<${simpleClassName}>>('/${table.moduleName}/${simpleClassName_strikeCase}/page', params) | 75 | return http.get<PageResult<${simpleClassName}>>('/${table.moduleName}/${simpleClassName_strikeCase}/page', params) |
| 33 | } | 76 | } |
| 77 | +#end | ||
| 34 | 78 | ||
| 35 | /** 获取${table.classComment}详情 */ | 79 | /** 获取${table.classComment}详情 */ |
| 36 | export function get${simpleClassName}(id: ${primaryTsType}) { | 80 | export function get${simpleClassName}(id: ${primaryTsType}) { |
| @@ -39,7 +83,7 @@ export function get${simpleClassName}(id: ${primaryTsType}) { | @@ -39,7 +83,7 @@ export function get${simpleClassName}(id: ${primaryTsType}) { | ||
| 39 | 83 | ||
| 40 | /** 创建${table.classComment} */ | 84 | /** 创建${table.classComment} */ |
| 41 | export function create${simpleClassName}(data: ${simpleClassName}) { | 85 | export function create${simpleClassName}(data: ${simpleClassName}) { |
| 42 | - return http.post<number>('/${table.moduleName}/${simpleClassName_strikeCase}/create', data) | 86 | + return http.post<${primaryTsType}>('/${table.moduleName}/${simpleClassName_strikeCase}/create', data) |
| 43 | } | 87 | } |
| 44 | 88 | ||
| 45 | /** 更新${table.classComment} */ | 89 | /** 更新${table.classComment} */ |
| @@ -51,3 +95,65 @@ export function update${simpleClassName}(data: ${simpleClassName}) { | @@ -51,3 +95,65 @@ export function update${simpleClassName}(data: ${simpleClassName}) { | ||
| 51 | export function delete${simpleClassName}(id: ${primaryTsType}) { | 95 | export function delete${simpleClassName}(id: ${primaryTsType}) { |
| 52 | return http.delete<boolean>(`/${table.moduleName}/${simpleClassName_strikeCase}/delete?id=${id}`) | 96 | return http.delete<boolean>(`/${table.moduleName}/${simpleClassName_strikeCase}/delete?id=${id}`) |
| 53 | } | 97 | } |
| 98 | +#foreach ($subTable in $subTables) | ||
| 99 | +#set ($index = $foreach.count - 1) | ||
| 100 | +#set ($subSimpleClassName = $subSimpleClassNames.get($index)) | ||
| 101 | +#set ($subPrimaryColumn = $subPrimaryColumns.get($index)) | ||
| 102 | +#set ($subJoinColumn = $subJoinColumns.get($index)) | ||
| 103 | +#set ($subPrimaryJavaType = $subPrimaryColumn.javaType.toLowerCase()) | ||
| 104 | +#if(${subPrimaryJavaType} == "long" || ${subPrimaryJavaType} == "integer" || ${subPrimaryJavaType} == "short" || ${subPrimaryJavaType} == "float" || ${subPrimaryJavaType} == "double" || ${subPrimaryJavaType} == "bigdecimal" || ${subPrimaryJavaType} == "byte") | ||
| 105 | +#set ($subPrimaryTsType = "number") | ||
| 106 | +#else | ||
| 107 | +#set ($subPrimaryTsType = "string") | ||
| 108 | +#end | ||
| 109 | +#set ($subJoinJavaType = $subJoinColumn.javaType.toLowerCase()) | ||
| 110 | +#if(${subJoinJavaType} == "long" || ${subJoinJavaType} == "integer" || ${subJoinJavaType} == "short" || ${subJoinJavaType} == "float" || ${subJoinJavaType} == "double" || ${subJoinJavaType} == "bigdecimal" || ${subJoinJavaType} == "byte") | ||
| 111 | +#set ($subJoinTsType = "number") | ||
| 112 | +#else | ||
| 113 | +#set ($subJoinTsType = "string") | ||
| 114 | +#end | ||
| 115 | +#set ($SubJoinColumnName = $subJoinColumn.javaField.substring(0,1).toUpperCase() + $subJoinColumn.javaField.substring(1)) | ||
| 116 | +#set ($subSimpleClassName_strikeCase = $subSimpleClassName_strikeCases.get($index)) | ||
| 117 | +#set ($subJoinColumn_strikeCase = $subJoinColumn_strikeCases.get($index)) | ||
| 118 | + | ||
| 119 | +// ==================== 子表:${subTable.classComment} ==================== | ||
| 120 | +#if ($table.templateType == 11) | ||
| 121 | + | ||
| 122 | +/** 获取${subTable.classComment}分页列表 */ | ||
| 123 | +export function get${subSimpleClassName}Page(params: PageParam & { ${subJoinColumn.javaField}: ${subJoinTsType} }) { | ||
| 124 | + return http.get<PageResult<${subSimpleClassName}>>('/${table.moduleName}/${simpleClassName_strikeCase}/${subSimpleClassName_strikeCase}/page', params) | ||
| 125 | +} | ||
| 126 | + | ||
| 127 | +/** 获取${subTable.classComment}详情 */ | ||
| 128 | +export function get${subSimpleClassName}(id: ${subPrimaryTsType}) { | ||
| 129 | + return http.get<${subSimpleClassName}>(`/${table.moduleName}/${simpleClassName_strikeCase}/${subSimpleClassName_strikeCase}/get?id=${id}`) | ||
| 130 | +} | ||
| 131 | + | ||
| 132 | +/** 创建${subTable.classComment} */ | ||
| 133 | +export function create${subSimpleClassName}(data: ${subSimpleClassName}) { | ||
| 134 | + return http.post<${subPrimaryTsType}>('/${table.moduleName}/${simpleClassName_strikeCase}/${subSimpleClassName_strikeCase}/create', data) | ||
| 135 | +} | ||
| 136 | + | ||
| 137 | +/** 更新${subTable.classComment} */ | ||
| 138 | +export function update${subSimpleClassName}(data: ${subSimpleClassName}) { | ||
| 139 | + return http.put<boolean>('/${table.moduleName}/${simpleClassName_strikeCase}/${subSimpleClassName_strikeCase}/update', data) | ||
| 140 | +} | ||
| 141 | + | ||
| 142 | +/** 删除${subTable.classComment} */ | ||
| 143 | +export function delete${subSimpleClassName}(id: ${subPrimaryTsType}) { | ||
| 144 | + return http.delete<boolean>(`/${table.moduleName}/${simpleClassName_strikeCase}/${subSimpleClassName_strikeCase}/delete?id=${id}`) | ||
| 145 | +} | ||
| 146 | +#elseif ($subTable.subJoinMany) | ||
| 147 | + | ||
| 148 | +/** 获取${subTable.classComment}列表 */ | ||
| 149 | +export function get${subSimpleClassName}ListBy${SubJoinColumnName}(${subJoinColumn.javaField}: ${subJoinTsType}) { | ||
| 150 | + return http.get<${subSimpleClassName}[]>(`/${table.moduleName}/${simpleClassName_strikeCase}/${subSimpleClassName_strikeCase}/list-by-${subJoinColumn_strikeCase}?${subJoinColumn.javaField}=#[[${]]#${subJoinColumn.javaField}#[[}]]#`) | ||
| 151 | +} | ||
| 152 | +#else | ||
| 153 | + | ||
| 154 | +/** 获取${subTable.classComment} */ | ||
| 155 | +export function get${subSimpleClassName}By${SubJoinColumnName}(${subJoinColumn.javaField}: ${subJoinTsType}) { | ||
| 156 | + return http.get<${subSimpleClassName} | null>(`/${table.moduleName}/${simpleClassName_strikeCase}/${subSimpleClassName_strikeCase}/get-by-${subJoinColumn_strikeCase}?${subJoinColumn.javaField}=#[[${]]#${subJoinColumn.javaField}#[[}]]#`) | ||
| 157 | +} | ||
| 158 | +#end | ||
| 159 | +#end |
yudao-module-infra/src/main/resources/codegen/vue3_admin_uniapp/components/breadcrumb_tree.vue.vm
0 → 100644
| 1 | +<template> | ||
| 2 | + <view class="bg-white px-24rpx py-16rpx"> | ||
| 3 | + <scroll-view scroll-x class="whitespace-nowrap"> | ||
| 4 | + <view class="inline-flex items-center"> | ||
| 5 | + <view | ||
| 6 | + class="flex items-center text-28rpx" | ||
| 7 | + :class="items.length > 0 ? 'text-[#1890ff]' : 'text-[#333]'" | ||
| 8 | + @click="handleClick(-1)" | ||
| 9 | + > | ||
| 10 | + <text>全部${table.classComment}</text> | ||
| 11 | + </view> | ||
| 12 | + <template v-for="(item, index) in items" :key="item.id"> | ||
| 13 | + <wd-icon name="arrow-right" size="12px" color="#999" custom-class="mx-8rpx" /> | ||
| 14 | + <view | ||
| 15 | + class="flex items-center text-28rpx" | ||
| 16 | + :class="index < items.length - 1 ? 'text-[#1890ff]' : 'text-[#333]'" | ||
| 17 | + @click="handleClick(index)" | ||
| 18 | + > | ||
| 19 | + <text>{{ item.name }}</text> | ||
| 20 | + </view> | ||
| 21 | + </template> | ||
| 22 | + </view> | ||
| 23 | + </scroll-view> | ||
| 24 | + </view> | ||
| 25 | +</template> | ||
| 26 | + | ||
| 27 | +<script lang="ts" setup> | ||
| 28 | +import { ref, watch } from 'vue' | ||
| 29 | + | ||
| 30 | +interface BreadcrumbItem { | ||
| 31 | + id: number | ||
| 32 | + name: string | ||
| 33 | +} | ||
| 34 | + | ||
| 35 | +const props = defineProps<{ | ||
| 36 | + modelValue: number | ||
| 37 | +}>() | ||
| 38 | + | ||
| 39 | +const emit = defineEmits<{ | ||
| 40 | + 'update:modelValue': [value: number] | ||
| 41 | +}>() | ||
| 42 | + | ||
| 43 | +const items = ref<BreadcrumbItem[]>([]) // 面包屑数据 | ||
| 44 | + | ||
| 45 | +/** 监听外部值变化 */ | ||
| 46 | +watch(() => props.modelValue, (value) => { | ||
| 47 | + if (value === 0) { | ||
| 48 | + items.value = [] | ||
| 49 | + } | ||
| 50 | +}) | ||
| 51 | + | ||
| 52 | +/** 点击面包屑 */ | ||
| 53 | +function handleClick(index: number) { | ||
| 54 | + if (index === -1) { | ||
| 55 | + reset() | ||
| 56 | + return | ||
| 57 | + } | ||
| 58 | + if (index < items.value.length - 1) { | ||
| 59 | + items.value = items.value.slice(0, index + 1) | ||
| 60 | + emit('update:modelValue', items.value[index].id) | ||
| 61 | + } | ||
| 62 | +} | ||
| 63 | + | ||
| 64 | +/** 进入子层级 */ | ||
| 65 | +function enter(item: BreadcrumbItem) { | ||
| 66 | + items.value.push(item) | ||
| 67 | + emit('update:modelValue', item.id) | ||
| 68 | +} | ||
| 69 | + | ||
| 70 | +/** 返回上一层级 */ | ||
| 71 | +function back(): boolean { | ||
| 72 | + if (items.value.length === 0) { | ||
| 73 | + return false | ||
| 74 | + } | ||
| 75 | + items.value.pop() | ||
| 76 | + emit('update:modelValue', items.value.at(-1)?.id ?? 0) | ||
| 77 | + return true | ||
| 78 | +} | ||
| 79 | + | ||
| 80 | +/** 重置面包屑 */ | ||
| 81 | +function reset() { | ||
| 82 | + items.value = [] | ||
| 83 | + emit('update:modelValue', 0) | ||
| 84 | +} | ||
| 85 | + | ||
| 86 | +defineExpose({ back, enter, reset }) | ||
| 87 | +</script> |
yudao-module-infra/src/main/resources/codegen/vue3_admin_uniapp/components/search-form.vue.vm
| @@ -42,23 +42,12 @@ | @@ -42,23 +42,12 @@ | ||
| 42 | #elseif ($column.htmlType == "datetime" && $listOperationCondition == "BETWEEN") | 42 | #elseif ($column.htmlType == "datetime" && $listOperationCondition == "BETWEEN") |
| 43 | <yd-search-date-range v-model="formData.${javaField}" label="${comment}" /> | 43 | <yd-search-date-range v-model="formData.${javaField}" label="${comment}" /> |
| 44 | #elseif (($column.htmlType == "select" || $column.htmlType == "radio") && $dictType && "" != $dictType) | 44 | #elseif (($column.htmlType == "select" || $column.htmlType == "radio") && $dictType && "" != $dictType) |
| 45 | - <view class="yd-search-form-item"> | ||
| 46 | - <view class="yd-search-form-label"> | ||
| 47 | - ${comment} | ||
| 48 | - </view> | ||
| 49 | - <wd-radio-group v-model="formData.${javaField}" type="button"> | ||
| 50 | - <wd-radio :value="-1"> | ||
| 51 | - 全部 | ||
| 52 | - </wd-radio> | ||
| 53 | - <wd-radio | ||
| 54 | - v-for="dict in $dictMethod(DICT_TYPE.$dictType.toUpperCase())" | ||
| 55 | - :key="dict.value" | ||
| 56 | - :value="dict.value" | ||
| 57 | - > | ||
| 58 | - {{ dict.label }} | ||
| 59 | - </wd-radio> | ||
| 60 | - </wd-radio-group> | ||
| 61 | - </view> | 45 | + <yd-search-picker |
| 46 | + v-model="formData.${javaField}" | ||
| 47 | + label="${comment}" | ||
| 48 | + :columns="$dictMethod(DICT_TYPE.$dictType.toUpperCase())" | ||
| 49 | + all-option | ||
| 50 | + /> | ||
| 62 | #else | 51 | #else |
| 63 | <view class="yd-search-form-item"> | 52 | <view class="yd-search-form-item"> |
| 64 | <view class="yd-search-form-label"> | 53 | <view class="yd-search-form-label"> |
| @@ -150,13 +139,13 @@ const formData = reactive({ | @@ -150,13 +139,13 @@ const formData = reactive({ | ||
| 150 | ${column.javaField}: [undefined, undefined] as [number | undefined, number | undefined], | 139 | ${column.javaField}: [undefined, undefined] as [number | undefined, number | undefined], |
| 151 | #elseif ($column.dictType && "" != $column.dictType) | 140 | #elseif ($column.dictType && "" != $column.dictType) |
| 152 | #if(${javaType} == "string") | 141 | #if(${javaType} == "string") |
| 153 | - ${column.javaField}: -1 as -1 | string, // -1 表示全部 | 142 | + ${column.javaField}: undefined as string | undefined, |
| 154 | #elseif(${javaType} == "boolean") | 143 | #elseif(${javaType} == "boolean") |
| 155 | - ${column.javaField}: -1 as -1 | boolean, // -1 表示全部 | 144 | + ${column.javaField}: undefined as boolean | undefined, |
| 156 | #else | 145 | #else |
| 157 | - ${column.javaField}: -1, // -1 表示全部 | 146 | + ${column.javaField}: undefined as number | undefined, |
| 158 | #end | 147 | #end |
| 159 | - #elseif(${javaType} == "long" || ${javaType} == "integer" || ${javaType} == "short" || ${javaType} == "double" || ${javaType} == "bigdecimal" || ${javaType} == "byte") | 148 | + #elseif(${javaType} == "long" || ${javaType} == "integer" || ${javaType} == "short" || ${javaType} == "float" || ${javaType} == "double" || ${javaType} == "bigdecimal" || ${javaType} == "byte") |
| 160 | ${column.javaField}: undefined as number | undefined, | 149 | ${column.javaField}: undefined as number | undefined, |
| 161 | #elseif(${javaType} == "boolean") | 150 | #elseif(${javaType} == "boolean") |
| 162 | ${column.javaField}: undefined as boolean | undefined, | 151 | ${column.javaField}: undefined as boolean | undefined, |
| @@ -181,11 +170,11 @@ const placeholder = computed(() => { | @@ -181,11 +170,11 @@ const placeholder = computed(() => { | ||
| 181 | conditions.push(`${comment}:#[[${]]#formatDate(formData.${javaField}[0])#[[}]]#~#[[${]]#formatDate(formData.${javaField}[1])#[[}]]#`) | 170 | conditions.push(`${comment}:#[[${]]#formatDate(formData.${javaField}[0])#[[}]]#~#[[${]]#formatDate(formData.${javaField}[1])#[[}]]#`) |
| 182 | } | 171 | } |
| 183 | #elseif ($dictType && "" != $dictType) | 172 | #elseif ($dictType && "" != $dictType) |
| 184 | - if (formData.${javaField} !== -1) { | 173 | + if (formData.${javaField} !== undefined) { |
| 185 | conditions.push(`${comment}:#[[${]]#getDictLabel(DICT_TYPE.${dictType.toUpperCase()}, formData.${javaField})#[[}]]#`) | 174 | conditions.push(`${comment}:#[[${]]#getDictLabel(DICT_TYPE.${dictType.toUpperCase()}, formData.${javaField})#[[}]]#`) |
| 186 | } | 175 | } |
| 187 | #else | 176 | #else |
| 188 | - #if(${javaType} == "long" || ${javaType} == "integer" || ${javaType} == "short" || ${javaType} == "double" || ${javaType} == "bigdecimal" || ${javaType} == "byte" || ${javaType} == "boolean") | 177 | + #if(${javaType} == "long" || ${javaType} == "integer" || ${javaType} == "short" || ${javaType} == "float" || ${javaType} == "double" || ${javaType} == "bigdecimal" || ${javaType} == "byte" || ${javaType} == "boolean") |
| 189 | if (formData.${javaField} !== undefined) { | 178 | if (formData.${javaField} !== undefined) { |
| 190 | conditions.push(`${comment}:#[[${]]#formData.${javaField}#[[}]]#`) | 179 | conditions.push(`${comment}:#[[${]]#formData.${javaField}#[[}]]#`) |
| 191 | } | 180 | } |
| @@ -207,9 +196,7 @@ function handleSearch() { | @@ -207,9 +196,7 @@ function handleSearch() { | ||
| 207 | ...formData, | 196 | ...formData, |
| 208 | #foreach($column in $columns) | 197 | #foreach($column in $columns) |
| 209 | #if ($column.listOperation) | 198 | #if ($column.listOperation) |
| 210 | - #if ($column.dictType && "" != $column.dictType) | ||
| 211 | - ${column.javaField}: formData.${column.javaField} === -1 ? undefined : formData.${column.javaField}, | ||
| 212 | - #elseif ($column.htmlType == "datetime" && $column.listOperationCondition == "BETWEEN") | 199 | + #if ($column.htmlType == "datetime" && $column.listOperationCondition == "BETWEEN") |
| 213 | ${column.javaField}: formatDateRange(formData.${column.javaField}), | 200 | ${column.javaField}: formatDateRange(formData.${column.javaField}), |
| 214 | #end | 201 | #end |
| 215 | #end | 202 | #end |
| @@ -224,7 +211,7 @@ function handleReset() { | @@ -224,7 +211,7 @@ function handleReset() { | ||
| 224 | #if ($column.htmlType == "datetime" && $column.listOperationCondition == "BETWEEN") | 211 | #if ($column.htmlType == "datetime" && $column.listOperationCondition == "BETWEEN") |
| 225 | formData.${column.javaField} = [undefined, undefined] | 212 | formData.${column.javaField} = [undefined, undefined] |
| 226 | #elseif ($column.dictType && "" != $column.dictType) | 213 | #elseif ($column.dictType && "" != $column.dictType) |
| 227 | - formData.${column.javaField} = -1 | 214 | + formData.${column.javaField} = undefined |
| 228 | #else | 215 | #else |
| 229 | formData.${column.javaField} = undefined | 216 | formData.${column.javaField} = undefined |
| 230 | #end | 217 | #end |
yudao-module-infra/src/main/resources/codegen/vue3_admin_uniapp/views/detail/index.vue.vm
| @@ -39,7 +39,7 @@ | @@ -39,7 +39,7 @@ | ||
| 39 | </wd-button> | 39 | </wd-button> |
| 40 | <wd-button | 40 | <wd-button |
| 41 | v-if="hasAccessByCodes(['${permissionPrefix}:delete'])" | 41 | v-if="hasAccessByCodes(['${permissionPrefix}:delete'])" |
| 42 | - class="flex-1" type="error" :loading="deleting" @click="handleDelete" | 42 | + class="flex-1" type="danger" :loading="deleting" @click="handleDelete" |
| 43 | > | 43 | > |
| 44 | 删除 | 44 | 删除 |
| 45 | </wd-button> | 45 | </wd-button> |
| @@ -49,6 +49,12 @@ | @@ -49,6 +49,12 @@ | ||
| 49 | </template> | 49 | </template> |
| 50 | 50 | ||
| 51 | <script lang="ts" setup> | 51 | <script lang="ts" setup> |
| 52 | +#set ($primaryJavaType = $primaryColumn.javaType.toLowerCase()) | ||
| 53 | +#if(${primaryJavaType} == "long" || ${primaryJavaType} == "integer" || ${primaryJavaType} == "short" || ${primaryJavaType} == "float" || ${primaryJavaType} == "double" || ${primaryJavaType} == "bigdecimal" || ${primaryJavaType} == "byte") | ||
| 54 | +#set ($primaryTsType = "number") | ||
| 55 | +#else | ||
| 56 | +#set ($primaryTsType = "string") | ||
| 57 | +#end | ||
| 52 | #set ($hasDict = 0) | 58 | #set ($hasDict = 0) |
| 53 | #foreach($column in $columns) | 59 | #foreach($column in $columns) |
| 54 | #if ($hasDict == 0 && $column.dictType && "" != $column.dictType) | 60 | #if ($hasDict == 0 && $column.dictType && "" != $column.dictType) |
| @@ -64,6 +70,7 @@ | @@ -64,6 +70,7 @@ | ||
| 64 | import type { ${simpleClassName} } from '@/api/${table.moduleName}/${table.businessName}' | 70 | import type { ${simpleClassName} } from '@/api/${table.moduleName}/${table.businessName}' |
| 65 | import { onUnload } from '@dcloudio/uni-app' | 71 | import { onUnload } from '@dcloudio/uni-app' |
| 66 | import { onMounted, ref } from 'vue' | 72 | import { onMounted, ref } from 'vue' |
| 73 | +import { useDialog } from '@wot-ui/ui/components/wd-dialog' | ||
| 67 | import { useToast } from '@wot-ui/ui/components/wd-toast' | 74 | import { useToast } from '@wot-ui/ui/components/wd-toast' |
| 68 | import { delete${simpleClassName}, get${simpleClassName} } from '@/api/${table.moduleName}/${table.businessName}' | 75 | import { delete${simpleClassName}, get${simpleClassName} } from '@/api/${table.moduleName}/${table.businessName}' |
| 69 | import { useAccess } from '@/hooks/useAccess' | 76 | import { useAccess } from '@/hooks/useAccess' |
| @@ -76,7 +83,7 @@ import { formatDateTime } from '@/utils/date' | @@ -76,7 +83,7 @@ import { formatDateTime } from '@/utils/date' | ||
| 76 | #end | 83 | #end |
| 77 | 84 | ||
| 78 | const props = defineProps<{ | 85 | const props = defineProps<{ |
| 79 | - id?: number | any | 86 | + id?: ${primaryTsType} |
| 80 | }>() | 87 | }>() |
| 81 | 88 | ||
| 82 | definePage({ | 89 | definePage({ |
| @@ -87,6 +94,7 @@ definePage({ | @@ -87,6 +94,7 @@ definePage({ | ||
| 87 | }) | 94 | }) |
| 88 | 95 | ||
| 89 | const { hasAccessByCodes } = useAccess() | 96 | const { hasAccessByCodes } = useAccess() |
| 97 | +const dialog = useDialog() | ||
| 90 | const toast = useToast() | 98 | const toast = useToast() |
| 91 | const formData = ref<${simpleClassName}>() // 详情数据 | 99 | const formData = ref<${simpleClassName}>() // 详情数据 |
| 92 | const deleting = ref(false) // 删除状态 | 100 | const deleting = ref(false) // 删除状态 |
| @@ -117,28 +125,27 @@ function handleEdit() { | @@ -117,28 +125,27 @@ function handleEdit() { | ||
| 117 | } | 125 | } |
| 118 | 126 | ||
| 119 | /** 删除${table.classComment} */ | 127 | /** 删除${table.classComment} */ |
| 120 | -function handleDelete() { | 128 | +async function handleDelete() { |
| 121 | if (!props.id) { | 129 | if (!props.id) { |
| 122 | return | 130 | return |
| 123 | } | 131 | } |
| 124 | - uni.showModal({ | ||
| 125 | - title: '提示', | ||
| 126 | - content: '确定要删除该${table.classComment}吗?', | ||
| 127 | - success: async (res) => { | ||
| 128 | - if (!res.confirm) { | ||
| 129 | - return | ||
| 130 | - } | ||
| 131 | - deleting.value = true | ||
| 132 | - try { | ||
| 133 | - await delete${simpleClassName}(props.id) | ||
| 134 | - toast.success('删除成功') | ||
| 135 | - uni.$emit('${table.moduleName}:${table.businessName}:reload') | ||
| 136 | - delay(handleBack) | ||
| 137 | - } finally { | ||
| 138 | - deleting.value = false | ||
| 139 | - } | ||
| 140 | - }, | ||
| 141 | - }) | 132 | + try { |
| 133 | + await dialog.confirm({ | ||
| 134 | + title: '提示', | ||
| 135 | + msg: '确定要删除该${table.classComment}吗?', | ||
| 136 | + }) | ||
| 137 | + } catch { | ||
| 138 | + return | ||
| 139 | + } | ||
| 140 | + deleting.value = true | ||
| 141 | + try { | ||
| 142 | + await delete${simpleClassName}(props.id) | ||
| 143 | + toast.success('删除成功') | ||
| 144 | + uni.$emit('${table.moduleName}:${table.businessName}:reload') | ||
| 145 | + delay(handleBack) | ||
| 146 | + } finally { | ||
| 147 | + deleting.value = false | ||
| 148 | + } | ||
| 142 | } | 149 | } |
| 143 | 150 | ||
| 144 | /** 初始化 */ | 151 | /** 初始化 */ |
yudao-module-infra/src/main/resources/codegen/vue3_admin_uniapp/views/detail_erp.vue.vm
0 → 100644
| 1 | +<template> | ||
| 2 | + <view class="yd-page-container"> | ||
| 3 | + <!-- 顶部导航栏 --> | ||
| 4 | + <wd-navbar title="${table.classComment}详情" left-arrow placeholder safe-area-inset-top fixed @click-left="handleBack" /> | ||
| 5 | + | ||
| 6 | + <view class="pb-160rpx"> | ||
| 7 | + <!-- 主表详情 --> | ||
| 8 | + <wd-cell-group border title="${table.classComment}信息"> | ||
| 9 | +#foreach($column in $columns) | ||
| 10 | +#if ($column.primaryKey || $column.listOperationResult || $column.createOperation || $column.updateOperation) | ||
| 11 | +#if ($column.dictType && "" != $column.dictType) | ||
| 12 | + <wd-cell title="${column.columnComment}"> | ||
| 13 | + <dict-tag :type="DICT_TYPE.${column.dictType.toUpperCase()}" :value="formData?.${column.javaField}" /> | ||
| 14 | + </wd-cell> | ||
| 15 | +#elseif ($column.javaType == "LocalDateTime") | ||
| 16 | + <wd-cell title="${column.columnComment}" :value="formatDateTime(formData?.${column.javaField}) || '-'" /> | ||
| 17 | +#else | ||
| 18 | + <wd-cell title="${column.columnComment}" :value="formData?.${column.javaField} ?? '-'" /> | ||
| 19 | +#end | ||
| 20 | +#end | ||
| 21 | +#end | ||
| 22 | + </wd-cell-group> | ||
| 23 | + | ||
| 24 | +#foreach ($subTable in $subTables) | ||
| 25 | +#set ($index = $foreach.count - 1) | ||
| 26 | +#set ($subColumns = $subColumnsList.get($index)) | ||
| 27 | +#set ($subClassNameVar = $subClassNameVars.get($index)) | ||
| 28 | +#set ($subPrimaryColumn = $subPrimaryColumns.get($index)) | ||
| 29 | +#set ($pageSize = 10) | ||
| 30 | +#if (!$subTable.subJoinMany) | ||
| 31 | +#set ($pageSize = 1) | ||
| 32 | +#end | ||
| 33 | + <!-- 子表:${subTable.classComment} --> | ||
| 34 | + <view class="mt-20rpx flex items-center justify-between px-24rpx py-16rpx"> | ||
| 35 | + <text class="text-28rpx text-[#333] font-semibold">${subTable.classComment}({{ ${subClassNameVar}Total }})</text> | ||
| 36 | + <wd-button | ||
| 37 | +#if (!$subTable.subJoinMany) | ||
| 38 | + v-if="${subClassNameVar}Total === 0 && hasAccessByCodes(['${permissionPrefix}:create'])" | ||
| 39 | +#else | ||
| 40 | + v-if="hasAccessByCodes(['${permissionPrefix}:create'])" | ||
| 41 | +#end | ||
| 42 | + size="small" type="primary" variant="plain" @click="open${subClassNameVar.substring(0,1).toUpperCase()}${subClassNameVar.substring(1)}()" | ||
| 43 | + > | ||
| 44 | + 添加 | ||
| 45 | + </wd-button> | ||
| 46 | + </view> | ||
| 47 | + <view class="px-24rpx"> | ||
| 48 | + <z-paging | ||
| 49 | + ref="${subClassNameVar}PagingRef" | ||
| 50 | + v-model="${subClassNameVar}List" | ||
| 51 | + :fixed="false" | ||
| 52 | + :auto="false" | ||
| 53 | + height="560rpx" | ||
| 54 | + :default-page-size="${pageSize}" | ||
| 55 | + :refresher-enabled="false" | ||
| 56 | + :inside-more="true" | ||
| 57 | + :to-bottom-loading-more-enabled="false" | ||
| 58 | + loading-more-default-text="点击加载更多" | ||
| 59 | + empty-view-text="暂无${subTable.classComment}" | ||
| 60 | + @query="query${subClassNameVar.substring(0,1).toUpperCase()}${subClassNameVar.substring(1)}List" | ||
| 61 | + > | ||
| 62 | + <view | ||
| 63 | + v-for="item in ${subClassNameVar}List" | ||
| 64 | + :key="item.${subPrimaryColumn.javaField}" | ||
| 65 | + class="mb-16rpx rounded-12rpx bg-white p-24rpx shadow-sm" | ||
| 66 | + > | ||
| 67 | +#foreach ($column in $subColumns) | ||
| 68 | +#if ($column.listOperationResult && !$column.primaryKey) | ||
| 69 | + <view class="mb-8rpx text-28rpx text-[#666]"> | ||
| 70 | + ${column.columnComment}:{{ item.${column.javaField} ?? '-' }} | ||
| 71 | + </view> | ||
| 72 | +#end | ||
| 73 | +#end | ||
| 74 | + <view class="mt-16rpx flex justify-end gap-16rpx"> | ||
| 75 | + <wd-button size="small" variant="plain" @click="open${subClassNameVar.substring(0,1).toUpperCase()}${subClassNameVar.substring(1)}(item)"> | ||
| 76 | + 编辑 | ||
| 77 | + </wd-button> | ||
| 78 | + <wd-button size="small" type="danger" variant="plain" @click="remove${subClassNameVar.substring(0,1).toUpperCase()}${subClassNameVar.substring(1)}(item)"> | ||
| 79 | + 删除 | ||
| 80 | + </wd-button> | ||
| 81 | + </view> | ||
| 82 | + </view> | ||
| 83 | + </z-paging> | ||
| 84 | + </view> | ||
| 85 | +#end | ||
| 86 | + </view> | ||
| 87 | + | ||
| 88 | + <!-- 底部操作按钮 --> | ||
| 89 | + <view class="yd-detail-footer"> | ||
| 90 | + <view class="yd-detail-footer-actions"> | ||
| 91 | + <wd-button v-if="hasAccessByCodes(['${permissionPrefix}:update'])" class="flex-1" type="warning" @click="handleEdit"> | ||
| 92 | + 编辑 | ||
| 93 | + </wd-button> | ||
| 94 | + <wd-button v-if="hasAccessByCodes(['${permissionPrefix}:delete'])" class="flex-1" type="danger" :loading="deleting" @click="handleDelete"> | ||
| 95 | + 删除 | ||
| 96 | + </wd-button> | ||
| 97 | + </view> | ||
| 98 | + </view> | ||
| 99 | + </view> | ||
| 100 | +</template> | ||
| 101 | + | ||
| 102 | +<script lang="ts" setup> | ||
| 103 | +#set ($primaryJavaType = $primaryColumn.javaType.toLowerCase()) | ||
| 104 | +#if(${primaryJavaType} == "long" || ${primaryJavaType} == "integer" || ${primaryJavaType} == "short" || ${primaryJavaType} == "float" || ${primaryJavaType} == "double" || ${primaryJavaType} == "bigdecimal" || ${primaryJavaType} == "byte") | ||
| 105 | +#set ($primaryTsType = "number") | ||
| 106 | +#else | ||
| 107 | +#set ($primaryTsType = "string") | ||
| 108 | +#end | ||
| 109 | +#set ($hasDict = 0) | ||
| 110 | +#set ($hasDateTime = 0) | ||
| 111 | +#foreach($column in $columns) | ||
| 112 | +#if ($column.dictType && "" != $column.dictType) | ||
| 113 | +#set ($hasDict = 1) | ||
| 114 | +#end | ||
| 115 | +#if ($column.javaType == "LocalDateTime") | ||
| 116 | +#set ($hasDateTime = 1) | ||
| 117 | +#end | ||
| 118 | +#end | ||
| 119 | +import type { | ||
| 120 | + ${simpleClassName}, | ||
| 121 | +#foreach ($subSimpleClassName in $subSimpleClassNames) | ||
| 122 | + ${subSimpleClassName}, | ||
| 123 | +#end | ||
| 124 | +} from '@/api/${table.moduleName}/${table.businessName}' | ||
| 125 | +import { onShow } from '@dcloudio/uni-app' | ||
| 126 | +import { nextTick, ref } from 'vue' | ||
| 127 | +import { useDialog } from '@wot-ui/ui/components/wd-dialog' | ||
| 128 | +import { useToast } from '@wot-ui/ui/components/wd-toast' | ||
| 129 | +import { | ||
| 130 | + delete${simpleClassName}, | ||
| 131 | +#foreach ($subSimpleClassName in $subSimpleClassNames) | ||
| 132 | + delete${subSimpleClassName}, | ||
| 133 | +#end | ||
| 134 | + get${simpleClassName}, | ||
| 135 | +#foreach ($subSimpleClassName in $subSimpleClassNames) | ||
| 136 | + get${subSimpleClassName}Page, | ||
| 137 | +#end | ||
| 138 | +} from '@/api/${table.moduleName}/${table.businessName}' | ||
| 139 | +import { useAccess } from '@/hooks/useAccess' | ||
| 140 | +import { delay, navigateBackPlus } from '@/utils' | ||
| 141 | +#if ($hasDict == 1) | ||
| 142 | +import { DICT_TYPE } from '@/utils/constants' | ||
| 143 | +#end | ||
| 144 | +#if ($hasDateTime == 1) | ||
| 145 | +import { formatDateTime } from '@/utils/date' | ||
| 146 | +#end | ||
| 147 | + | ||
| 148 | +const props = defineProps<{ id?: ${primaryTsType} }>() | ||
| 149 | + | ||
| 150 | +definePage({ | ||
| 151 | + style: { navigationBarTitleText: '', navigationStyle: 'custom' }, | ||
| 152 | +}) | ||
| 153 | + | ||
| 154 | +const { hasAccessByCodes } = useAccess() | ||
| 155 | +const dialog = useDialog() | ||
| 156 | +const toast = useToast() | ||
| 157 | +const formData = ref<${simpleClassName}>() // 主表数据 | ||
| 158 | +#foreach ($subTable in $subTables) | ||
| 159 | +#set ($index = $foreach.count - 1) | ||
| 160 | +#set ($subSimpleClassName = $subSimpleClassNames.get($index)) | ||
| 161 | +#set ($subClassNameVar = $subClassNameVars.get($index)) | ||
| 162 | +const ${subClassNameVar}List = ref<${subSimpleClassName}[]>([]) // ${subTable.classComment}列表 | ||
| 163 | +const ${subClassNameVar}Total = ref(0) // ${subTable.classComment}总数 | ||
| 164 | +const ${subClassNameVar}PagingRef = ref<any>() // ${subTable.classComment}分页组件引用 | ||
| 165 | +#end | ||
| 166 | +const deleting = ref(false) // 主表删除状态 | ||
| 167 | + | ||
| 168 | +/** 返回上一页 */ | ||
| 169 | +function handleBack() { | ||
| 170 | + navigateBackPlus('/pages-${table.moduleName}/${table.businessName}/index') | ||
| 171 | +} | ||
| 172 | + | ||
| 173 | +/** 加载主表 */ | ||
| 174 | +async function loadMain() { | ||
| 175 | + if (props.id) { | ||
| 176 | + formData.value = await get${simpleClassName}(props.id) | ||
| 177 | + } | ||
| 178 | +} | ||
| 179 | + | ||
| 180 | +#foreach ($subTable in $subTables) | ||
| 181 | +#set ($index = $foreach.count - 1) | ||
| 182 | +#set ($subSimpleClassName = $subSimpleClassNames.get($index)) | ||
| 183 | +#set ($subClassNameVar = $subClassNameVars.get($index)) | ||
| 184 | +#set ($subJoinColumn = $subJoinColumns.get($index)) | ||
| 185 | +#set ($subPrimaryColumn = $subPrimaryColumns.get($index)) | ||
| 186 | +/** 查询${subTable.classComment}列表 */ | ||
| 187 | +async function query${subClassNameVar.substring(0,1).toUpperCase()}${subClassNameVar.substring(1)}List(pageNo: number, pageSize: number) { | ||
| 188 | + if (!props.id) { | ||
| 189 | + ${subClassNameVar}PagingRef.value?.completeByTotal([], 0) | ||
| 190 | + return | ||
| 191 | + } | ||
| 192 | + try { | ||
| 193 | + const data = await get${subSimpleClassName}Page({ ${subJoinColumn.javaField}: props.id, pageNo, pageSize }) | ||
| 194 | + ${subClassNameVar}Total.value = data.total | ||
| 195 | + ${subClassNameVar}PagingRef.value?.completeByTotal(data.list, data.total) | ||
| 196 | + } catch { | ||
| 197 | + ${subClassNameVar}PagingRef.value?.complete(false) | ||
| 198 | + } | ||
| 199 | +} | ||
| 200 | + | ||
| 201 | +/** 打开${subTable.classComment}表单 */ | ||
| 202 | +function open${subClassNameVar.substring(0,1).toUpperCase()}${subClassNameVar.substring(1)}(item?: ${subSimpleClassName}) { | ||
| 203 | + const query = [ | ||
| 204 | + `${subJoinColumn.javaField}=#[[${]]#props.id#[[}]]#`, | ||
| 205 | + item?.${subPrimaryColumn.javaField} ? `id=#[[${]]#item.${subPrimaryColumn.javaField}#[[}]]#` : '', | ||
| 206 | + ].filter(Boolean).join('&') | ||
| 207 | + uni.navigateTo({ url: `/pages-${table.moduleName}/${table.businessName}/${subSimpleClassName_strikeCases.get($index)}/form/index?#[[${]]#query#[[}]]#` }) | ||
| 208 | +} | ||
| 209 | + | ||
| 210 | +/** 删除${subTable.classComment} */ | ||
| 211 | +async function remove${subClassNameVar.substring(0,1).toUpperCase()}${subClassNameVar.substring(1)}(item: ${subSimpleClassName}) { | ||
| 212 | + try { | ||
| 213 | + await dialog.confirm({ title: '提示', msg: '确定要删除该${subTable.classComment}吗?' }) | ||
| 214 | + } catch { | ||
| 215 | + return | ||
| 216 | + } | ||
| 217 | + await delete${subSimpleClassName}(item.${subPrimaryColumn.javaField}!) | ||
| 218 | + toast.success('删除成功') | ||
| 219 | + ${subClassNameVar}PagingRef.value?.reload() | ||
| 220 | +} | ||
| 221 | + | ||
| 222 | +#end | ||
| 223 | +/** 编辑主表 */ | ||
| 224 | +function handleEdit() { | ||
| 225 | + uni.navigateTo({ url: `/pages-${table.moduleName}/${table.businessName}/form/index?id=#[[${]]#props.id#[[}]]#` }) | ||
| 226 | +} | ||
| 227 | + | ||
| 228 | +/** 删除主表 */ | ||
| 229 | +async function handleDelete() { | ||
| 230 | + if (!props.id) { | ||
| 231 | + return | ||
| 232 | + } | ||
| 233 | + try { | ||
| 234 | + await dialog.confirm({ title: '提示', msg: '确定要删除该${table.classComment}吗?' }) | ||
| 235 | + } catch { | ||
| 236 | + return | ||
| 237 | + } | ||
| 238 | + deleting.value = true | ||
| 239 | + try { | ||
| 240 | + await delete${simpleClassName}(props.id) | ||
| 241 | + toast.success('删除成功') | ||
| 242 | + uni.$emit('${table.moduleName}:${table.businessName}:reload') | ||
| 243 | + delay(handleBack) | ||
| 244 | + } finally { | ||
| 245 | + deleting.value = false | ||
| 246 | + } | ||
| 247 | +} | ||
| 248 | + | ||
| 249 | +/** 进入页面加载(含编辑和子表返回后刷新) */ | ||
| 250 | +onShow(() => { | ||
| 251 | + loadMain() | ||
| 252 | + nextTick(() => { | ||
| 253 | +#foreach ($subClassNameVar in $subClassNameVars) | ||
| 254 | + ${subClassNameVar}PagingRef.value?.reload() | ||
| 255 | +#end | ||
| 256 | + }) | ||
| 257 | +}) | ||
| 258 | +</script> |
yudao-module-infra/src/main/resources/codegen/vue3_admin_uniapp/views/detail_master.vue.vm
0 → 100644
| 1 | +<template> | ||
| 2 | + <view class="yd-page-container"> | ||
| 3 | + <!-- 顶部导航栏 --> | ||
| 4 | + <wd-navbar | ||
| 5 | + title="${table.classComment}详情" | ||
| 6 | + left-arrow placeholder safe-area-inset-top fixed | ||
| 7 | + @click-left="handleBack" | ||
| 8 | + /> | ||
| 9 | + | ||
| 10 | + <view class="pb-160rpx"> | ||
| 11 | + <!-- 主表详情 --> | ||
| 12 | + <wd-cell-group border title="${table.classComment}信息"> | ||
| 13 | +#foreach($column in $columns) | ||
| 14 | +#if ($column.primaryKey || $column.listOperationResult || $column.createOperation || $column.updateOperation) | ||
| 15 | +#if ($column.dictType && "" != $column.dictType) | ||
| 16 | + <wd-cell title="${column.columnComment}"> | ||
| 17 | + <dict-tag :type="DICT_TYPE.${column.dictType.toUpperCase()}" :value="formData?.${column.javaField}" /> | ||
| 18 | + </wd-cell> | ||
| 19 | +#elseif ($column.javaType == "LocalDateTime") | ||
| 20 | + <wd-cell title="${column.columnComment}" :value="formatDateTime(formData?.${column.javaField}) || '-'" /> | ||
| 21 | +#else | ||
| 22 | + <wd-cell title="${column.columnComment}" :value="formData?.${column.javaField} ?? '-'" /> | ||
| 23 | +#end | ||
| 24 | +#end | ||
| 25 | +#end | ||
| 26 | + </wd-cell-group> | ||
| 27 | + | ||
| 28 | +#foreach ($subTable in $subTables) | ||
| 29 | +#set ($index = $foreach.count - 1) | ||
| 30 | +#set ($subColumns = $subColumnsList.get($index)) | ||
| 31 | +#set ($subClassNameVar = $subClassNameVars.get($index)) | ||
| 32 | +#set ($subPrimaryColumn = $subPrimaryColumns.get($index)) | ||
| 33 | + <!-- 子表:${subTable.classComment} --> | ||
| 34 | + <view class="mt-20rpx px-24rpx py-16rpx text-28rpx text-[#999]"> | ||
| 35 | + ${subTable.classComment}#if($subTable.subJoinMany)({{ ${subClassNameVar}s.length }})#end | ||
| 36 | + </view> | ||
| 37 | + <view class="px-24rpx"> | ||
| 38 | +#if ($subTable.subJoinMany) | ||
| 39 | + <view v-if="!${subClassNameVar}s.length" class="rounded-12rpx bg-white py-40rpx text-center text-26rpx text-[#999]"> | ||
| 40 | + 暂无${subTable.classComment} | ||
| 41 | + </view> | ||
| 42 | + <view | ||
| 43 | + v-for="item in ${subClassNameVar}s" | ||
| 44 | + :key="item.${subPrimaryColumn.javaField}" | ||
| 45 | + class="mb-16rpx rounded-12rpx bg-white p-24rpx shadow-sm" | ||
| 46 | + > | ||
| 47 | +#foreach ($column in $subColumns) | ||
| 48 | +#if ($column.listOperationResult && !$column.primaryKey) | ||
| 49 | + <view class="mb-8rpx text-28rpx text-[#666]"> | ||
| 50 | + ${column.columnComment}:{{ item.${column.javaField} ?? '-' }} | ||
| 51 | + </view> | ||
| 52 | +#end | ||
| 53 | +#end | ||
| 54 | + </view> | ||
| 55 | +#else | ||
| 56 | + <view class="rounded-12rpx bg-white p-24rpx shadow-sm"> | ||
| 57 | + <template v-if="${subClassNameVar}"> | ||
| 58 | +#foreach ($column in $subColumns) | ||
| 59 | +#if ($column.listOperationResult && !$column.primaryKey) | ||
| 60 | + <view class="mb-8rpx text-28rpx text-[#666]"> | ||
| 61 | + ${column.columnComment}:{{ ${subClassNameVar}.${column.javaField} ?? '-' }} | ||
| 62 | + </view> | ||
| 63 | +#end | ||
| 64 | +#end | ||
| 65 | + </template> | ||
| 66 | + <view v-else class="text-center text-26rpx text-[#999]"> | ||
| 67 | + 暂无${subTable.classComment} | ||
| 68 | + </view> | ||
| 69 | + </view> | ||
| 70 | +#end | ||
| 71 | + </view> | ||
| 72 | +#end | ||
| 73 | + </view> | ||
| 74 | + | ||
| 75 | + <!-- 底部操作按钮 --> | ||
| 76 | + <view class="yd-detail-footer"> | ||
| 77 | + <view class="yd-detail-footer-actions"> | ||
| 78 | + <wd-button | ||
| 79 | + v-if="hasAccessByCodes(['${permissionPrefix}:update'])" | ||
| 80 | + class="flex-1" type="warning" @click="handleEdit" | ||
| 81 | + > | ||
| 82 | + 编辑 | ||
| 83 | + </wd-button> | ||
| 84 | + <wd-button | ||
| 85 | + v-if="hasAccessByCodes(['${permissionPrefix}:delete'])" | ||
| 86 | + class="flex-1" type="danger" :loading="deleting" @click="handleDelete" | ||
| 87 | + > | ||
| 88 | + 删除 | ||
| 89 | + </wd-button> | ||
| 90 | + </view> | ||
| 91 | + </view> | ||
| 92 | + </view> | ||
| 93 | +</template> | ||
| 94 | + | ||
| 95 | +<script lang="ts" setup> | ||
| 96 | +#set ($primaryJavaType = $primaryColumn.javaType.toLowerCase()) | ||
| 97 | +#if(${primaryJavaType} == "long" || ${primaryJavaType} == "integer" || ${primaryJavaType} == "short" || ${primaryJavaType} == "float" || ${primaryJavaType} == "double" || ${primaryJavaType} == "bigdecimal" || ${primaryJavaType} == "byte") | ||
| 98 | +#set ($primaryTsType = "number") | ||
| 99 | +#else | ||
| 100 | +#set ($primaryTsType = "string") | ||
| 101 | +#end | ||
| 102 | +#set ($hasDict = 0) | ||
| 103 | +#set ($hasDateTime = 0) | ||
| 104 | +#foreach($column in $columns) | ||
| 105 | +#if ($column.dictType && "" != $column.dictType) | ||
| 106 | +#set ($hasDict = 1) | ||
| 107 | +#end | ||
| 108 | +#if ($column.javaType == "LocalDateTime") | ||
| 109 | +#set ($hasDateTime = 1) | ||
| 110 | +#end | ||
| 111 | +#end | ||
| 112 | +import type { | ||
| 113 | + ${simpleClassName}, | ||
| 114 | +#foreach ($subSimpleClassName in $subSimpleClassNames) | ||
| 115 | + ${subSimpleClassName}, | ||
| 116 | +#end | ||
| 117 | +} from '@/api/${table.moduleName}/${table.businessName}' | ||
| 118 | +import { onShow } from '@dcloudio/uni-app' | ||
| 119 | +import { ref } from 'vue' | ||
| 120 | +import { useDialog } from '@wot-ui/ui/components/wd-dialog' | ||
| 121 | +import { useToast } from '@wot-ui/ui/components/wd-toast' | ||
| 122 | +import { | ||
| 123 | + delete${simpleClassName}, | ||
| 124 | + get${simpleClassName}, | ||
| 125 | +#foreach ($subTable in $subTables) | ||
| 126 | +#set ($index = $foreach.count - 1) | ||
| 127 | +#set ($subSimpleClassName = $subSimpleClassNames.get($index)) | ||
| 128 | +#set ($subJoinColumn = $subJoinColumns.get($index)) | ||
| 129 | +#set ($SubJoinColumnName = $subJoinColumn.javaField.substring(0,1).toUpperCase() + $subJoinColumn.javaField.substring(1)) | ||
| 130 | +#if ($subTable.subJoinMany) | ||
| 131 | + get${subSimpleClassName}ListBy${SubJoinColumnName}, | ||
| 132 | +#else | ||
| 133 | + get${subSimpleClassName}By${SubJoinColumnName}, | ||
| 134 | +#end | ||
| 135 | +#end | ||
| 136 | +} from '@/api/${table.moduleName}/${table.businessName}' | ||
| 137 | +import { useAccess } from '@/hooks/useAccess' | ||
| 138 | +import { delay, navigateBackPlus } from '@/utils' | ||
| 139 | +#if ($hasDict == 1) | ||
| 140 | +import { DICT_TYPE } from '@/utils/constants' | ||
| 141 | +#end | ||
| 142 | +#if ($hasDateTime == 1) | ||
| 143 | +import { formatDateTime } from '@/utils/date' | ||
| 144 | +#end | ||
| 145 | + | ||
| 146 | +const props = defineProps<{ | ||
| 147 | + id?: ${primaryTsType} | ||
| 148 | +}>() | ||
| 149 | + | ||
| 150 | +definePage({ | ||
| 151 | + style: { | ||
| 152 | + navigationBarTitleText: '', | ||
| 153 | + navigationStyle: 'custom', | ||
| 154 | + }, | ||
| 155 | +}) | ||
| 156 | + | ||
| 157 | +const { hasAccessByCodes } = useAccess() | ||
| 158 | +const dialog = useDialog() | ||
| 159 | +const toast = useToast() | ||
| 160 | +const formData = ref<${simpleClassName}>() // 主表数据 | ||
| 161 | +#foreach ($subTable in $subTables) | ||
| 162 | +#set ($index = $foreach.count - 1) | ||
| 163 | +#set ($subSimpleClassName = $subSimpleClassNames.get($index)) | ||
| 164 | +#set ($subClassNameVar = $subClassNameVars.get($index)) | ||
| 165 | +#if ($subTable.subJoinMany) | ||
| 166 | +const ${subClassNameVar}s = ref<${subSimpleClassName}[]>([]) // ${subTable.classComment}数据 | ||
| 167 | +#else | ||
| 168 | +const ${subClassNameVar} = ref<${subSimpleClassName} | null>(null) // ${subTable.classComment}数据 | ||
| 169 | +#end | ||
| 170 | +#end | ||
| 171 | +const deleting = ref(false) // 删除状态 | ||
| 172 | + | ||
| 173 | +/** 返回上一页 */ | ||
| 174 | +function handleBack() { | ||
| 175 | + navigateBackPlus('/pages-${table.moduleName}/${table.businessName}/index') | ||
| 176 | +} | ||
| 177 | + | ||
| 178 | +/** 加载主表和子表详情 */ | ||
| 179 | +async function getDetail() { | ||
| 180 | + if (!props.id || deleting.value) { | ||
| 181 | + return | ||
| 182 | + } | ||
| 183 | + const [data#foreach ($subTable in $subTables)#set ($index = $foreach.count - 1), ${subClassNameVars.get($index)}Data#end] = await Promise.all([ | ||
| 184 | + get${simpleClassName}(props.id), | ||
| 185 | +#foreach ($subTable in $subTables) | ||
| 186 | +#set ($index = $foreach.count - 1) | ||
| 187 | +#set ($subSimpleClassName = $subSimpleClassNames.get($index)) | ||
| 188 | +#set ($subJoinColumn = $subJoinColumns.get($index)) | ||
| 189 | +#set ($SubJoinColumnName = $subJoinColumn.javaField.substring(0,1).toUpperCase() + $subJoinColumn.javaField.substring(1)) | ||
| 190 | +#if ($subTable.subJoinMany) | ||
| 191 | + get${subSimpleClassName}ListBy${SubJoinColumnName}(props.id), | ||
| 192 | +#else | ||
| 193 | + get${subSimpleClassName}By${SubJoinColumnName}(props.id), | ||
| 194 | +#end | ||
| 195 | +#end | ||
| 196 | + ]) | ||
| 197 | + formData.value = data | ||
| 198 | +#foreach ($subTable in $subTables) | ||
| 199 | +#set ($index = $foreach.count - 1) | ||
| 200 | +#set ($subClassNameVar = $subClassNameVars.get($index)) | ||
| 201 | +#if ($subTable.subJoinMany) | ||
| 202 | + ${subClassNameVar}s.value = ${subClassNameVar}Data || [] | ||
| 203 | +#else | ||
| 204 | + ${subClassNameVar}.value = ${subClassNameVar}Data || null | ||
| 205 | +#end | ||
| 206 | +#end | ||
| 207 | +} | ||
| 208 | + | ||
| 209 | +/** 编辑${table.classComment} */ | ||
| 210 | +function handleEdit() { | ||
| 211 | + uni.navigateTo({ url: `/pages-${table.moduleName}/${table.businessName}/form/index?id=#[[${]]#props.id#[[}]]#` }) | ||
| 212 | +} | ||
| 213 | + | ||
| 214 | +/** 删除${table.classComment} */ | ||
| 215 | +async function handleDelete() { | ||
| 216 | + if (!props.id) { | ||
| 217 | + return | ||
| 218 | + } | ||
| 219 | + try { | ||
| 220 | + await dialog.confirm({ title: '提示', msg: '确定要删除该${table.classComment}吗?' }) | ||
| 221 | + } catch { | ||
| 222 | + return | ||
| 223 | + } | ||
| 224 | + deleting.value = true | ||
| 225 | + try { | ||
| 226 | + await delete${simpleClassName}(props.id) | ||
| 227 | + toast.success('删除成功') | ||
| 228 | + uni.$emit('${table.moduleName}:${table.businessName}:reload') | ||
| 229 | + delay(handleBack) | ||
| 230 | + } finally { | ||
| 231 | + deleting.value = false | ||
| 232 | + } | ||
| 233 | +} | ||
| 234 | + | ||
| 235 | +/** 进入页面加载(含编辑返回后刷新) */ | ||
| 236 | +onShow(() => { | ||
| 237 | + getDetail() | ||
| 238 | +}) | ||
| 239 | +</script> |
yudao-module-infra/src/main/resources/codegen/vue3_admin_uniapp/views/form/index.vue.vm
| @@ -12,34 +12,48 @@ | @@ -12,34 +12,48 @@ | ||
| 12 | <wd-form ref="formRef" :model="formData" :schema="formSchema"> | 12 | <wd-form ref="formRef" :model="formData" :schema="formSchema"> |
| 13 | <wd-cell-group border> | 13 | <wd-cell-group border> |
| 14 | #foreach($column in $columns) | 14 | #foreach($column in $columns) |
| 15 | - #if (($column.createOperation || $column.updateOperation) && !$column.primaryKey) | ||
| 16 | - #set ($dictType = $column.dictType) | ||
| 17 | - #set ($javaField = $column.javaField) | ||
| 18 | - #set ($javaType = $column.javaType) | ||
| 19 | - #set ($comment = $column.columnComment) | ||
| 20 | - #set ($dictMethod = "getDictOptions") | ||
| 21 | - #if ($javaType == "Integer" || $javaType == "Long" || $javaType == "Byte" || $javaType == "Short") | ||
| 22 | - #set ($dictMethod = "getIntDictOptions") | ||
| 23 | - #elseif ($javaType == "String") | ||
| 24 | - #set ($dictMethod = "getStrDictOptions") | ||
| 25 | - #elseif ($javaType == "Boolean") | ||
| 26 | - #set ($dictMethod = "getBoolDictOptions") | ||
| 27 | - #end | ||
| 28 | - ## 下拉选择(字典)→ yd-form-picker,内部按 dictType 解析选项,适合选项较多 | ||
| 29 | - #if ($column.htmlType == "select" && $dictType && "" != $dictType) | 15 | +#if (($column.createOperation || $column.updateOperation) && !$column.primaryKey) |
| 16 | +#set ($dictType = $column.dictType) | ||
| 17 | +#set ($javaField = $column.javaField) | ||
| 18 | +#set ($javaType = $column.javaType) | ||
| 19 | +#set ($comment = $column.columnComment) | ||
| 20 | +#set ($dictMethod = "getDictOptions") | ||
| 21 | +#if ($javaType == "Integer" || $javaType == "Long" || $javaType == "Byte" || $javaType == "Short") | ||
| 22 | +#set ($dictMethod = "getIntDictOptions") | ||
| 23 | +#elseif ($javaType == "String") | ||
| 24 | +#set ($dictMethod = "getStrDictOptions") | ||
| 25 | +#elseif ($javaType == "Boolean") | ||
| 26 | +#set ($dictMethod = "getBoolDictOptions") | ||
| 27 | +#end | ||
| 28 | +## 树表的父级选择 | ||
| 29 | +#if ($table.templateType == 2 && $column.id == $treeParentColumn.id) | ||
| 30 | + <yd-tree-select | ||
| 31 | + v-model="formData.${javaField}" | ||
| 32 | + :data="treeData" | ||
| 33 | + label="${comment}" | ||
| 34 | + prop="${javaField}" | ||
| 35 | + :props="{ label: '${treeNameColumn.javaField}', value: '${primaryColumn.javaField}' }" | ||
| 36 | + placeholder="请选择${comment}" | ||
| 37 | + /> | ||
| 38 | +## 下拉选择(字典)→ yd-form-picker,内部按 dictType 解析选项,适合选项较多 | ||
| 39 | +#elseif ($column.htmlType == "select" && $dictType && "" != $dictType) | ||
| 30 | <yd-form-picker | 40 | <yd-form-picker |
| 31 | v-model="formData.${javaField}" | 41 | v-model="formData.${javaField}" |
| 32 | label="${comment}" | 42 | label="${comment}" |
| 33 | label-width="180rpx" | 43 | label-width="180rpx" |
| 34 | prop="${javaField}" | 44 | prop="${javaField}" |
| 45 | +#if ($javaType == "Boolean") | ||
| 46 | + :columns="getBoolDictOptions(DICT_TYPE.${dictType.toUpperCase()})" | ||
| 47 | +#else | ||
| 35 | :dict-type="DICT_TYPE.${dictType.toUpperCase()}" | 48 | :dict-type="DICT_TYPE.${dictType.toUpperCase()}" |
| 36 | #if ($javaType == "String") | 49 | #if ($javaType == "String") |
| 37 | dict-kind="str" | 50 | dict-kind="str" |
| 38 | #end | 51 | #end |
| 52 | +#end | ||
| 39 | placeholder="请选择${comment}" | 53 | placeholder="请选择${comment}" |
| 40 | /> | 54 | /> |
| 41 | - ## 单选(字典)→ radio-group,适合选项较少 | ||
| 42 | - #elseif ($column.htmlType == "radio" && $dictType && "" != $dictType) | 55 | +## 单选(字典)→ radio-group,适合选项较少 |
| 56 | +#elseif ($column.htmlType == "radio" && $dictType && "" != $dictType) | ||
| 43 | <wd-form-item title="${comment}" title-width="180rpx" prop="${javaField}" center> | 57 | <wd-form-item title="${comment}" title-width="180rpx" prop="${javaField}" center> |
| 44 | <wd-radio-group v-model="formData.${javaField}" type="button"> | 58 | <wd-radio-group v-model="formData.${javaField}" type="button"> |
| 45 | <wd-radio | 59 | <wd-radio |
| @@ -51,70 +65,78 @@ | @@ -51,70 +65,78 @@ | ||
| 51 | </wd-radio> | 65 | </wd-radio> |
| 52 | </wd-radio-group> | 66 | </wd-radio-group> |
| 53 | </wd-form-item> | 67 | </wd-form-item> |
| 54 | - ## 数字类型(无字典) | ||
| 55 | - #elseif (${javaType.toLowerCase()} == "long" || ${javaType.toLowerCase()} == "integer" || ${javaType.toLowerCase()} == "short" || ${javaType.toLowerCase()} == "double" || ${javaType.toLowerCase()} == "bigdecimal" || ${javaType.toLowerCase()} == "byte") | 68 | +## 数字类型(无字典) |
| 69 | +#elseif (${javaType.toLowerCase()} == "long" || ${javaType.toLowerCase()} == "integer" || ${javaType.toLowerCase()} == "short" || ${javaType.toLowerCase()} == "float" || ${javaType.toLowerCase()} == "double" || ${javaType.toLowerCase()} == "bigdecimal" || ${javaType.toLowerCase()} == "byte") | ||
| 56 | <wd-form-item title="${comment}" title-width="180rpx" prop="${javaField}"> | 70 | <wd-form-item title="${comment}" title-width="180rpx" prop="${javaField}"> |
| 57 | <wd-input-number | 71 | <wd-input-number |
| 58 | v-model="formData.${javaField}" | 72 | v-model="formData.${javaField}" |
| 59 | -#if (${javaType.toLowerCase()} == "double" || ${javaType.toLowerCase()} == "bigdecimal") | 73 | +#if ($column.nullable) |
| 74 | + allow-null | ||
| 75 | +#end | ||
| 76 | +#if (${javaType.toLowerCase()} == "float" || ${javaType.toLowerCase()} == "double" || ${javaType.toLowerCase()} == "bigdecimal") | ||
| 60 | :precision="2" | 77 | :precision="2" |
| 61 | input-type="number" | 78 | input-type="number" |
| 62 | #end | 79 | #end |
| 63 | :min="0" | 80 | :min="0" |
| 64 | /> | 81 | /> |
| 65 | </wd-form-item> | 82 | </wd-form-item> |
| 66 | - ## 布尔类型 | ||
| 67 | - #elseif (${javaType.toLowerCase()} == "boolean") | 83 | +## 布尔类型 |
| 84 | +#elseif (${javaType.toLowerCase()} == "boolean") | ||
| 68 | <wd-form-item title="${comment}" title-width="180rpx" prop="${javaField}" center> | 85 | <wd-form-item title="${comment}" title-width="180rpx" prop="${javaField}" center> |
| 69 | <wd-switch v-model="formData.${javaField}" /> | 86 | <wd-switch v-model="formData.${javaField}" /> |
| 70 | </wd-form-item> | 87 | </wd-form-item> |
| 71 | - ## 日期时间类型 | ||
| 72 | - #elseif (${javaType.toLowerCase()} == "date" || ${javaType.toLowerCase()} == "localdate" || ${javaType.toLowerCase()} == "localdatetime") | ||
| 73 | - #set ($pickerType = "date") | ||
| 74 | - #if (${javaType.toLowerCase()} == "localdatetime") | ||
| 75 | - #set ($pickerType = "datetime") | ||
| 76 | - #end | 88 | +## 日期时间类型 |
| 89 | +#elseif (${javaType.toLowerCase()} == "date" || ${javaType.toLowerCase()} == "localdate" || ${javaType.toLowerCase()} == "localdatetime") | ||
| 90 | +#set ($pickerType = "date") | ||
| 91 | +#if (${javaType.toLowerCase()} == "localdatetime") | ||
| 92 | +#set ($pickerType = "datetime") | ||
| 93 | +#end | ||
| 77 | <wd-form-item | 94 | <wd-form-item |
| 78 | title="${comment}" | 95 | title="${comment}" |
| 79 | title-width="180rpx" | 96 | title-width="180rpx" |
| 80 | prop="${javaField}" | 97 | prop="${javaField}" |
| 81 | is-link | 98 | is-link |
| 82 | #if ($pickerType == "datetime") | 99 | #if ($pickerType == "datetime") |
| 83 | - :value="formatDateTime(formData.${javaField})" | 100 | + :value="formData.${javaField} ? formatDateTime(formData.${javaField}) : ''" |
| 84 | #else | 101 | #else |
| 85 | - :value="formatDate(formData.${javaField})" | 102 | + :value="formData.${javaField} ? formatDate(formData.${javaField}) : ''" |
| 86 | #end | 103 | #end |
| 87 | placeholder="请选择${comment}" | 104 | placeholder="请选择${comment}" |
| 88 | @click="${javaField}Visible = true" | 105 | @click="${javaField}Visible = true" |
| 89 | /> | 106 | /> |
| 90 | <wd-datetime-picker | 107 | <wd-datetime-picker |
| 91 | - v-model="formData.${javaField}" | ||
| 92 | v-model:visible="${javaField}Visible" | 108 | v-model:visible="${javaField}Visible" |
| 109 | + :model-value="formData.${javaField} || Date.now()" | ||
| 93 | type="${pickerType}" | 110 | type="${pickerType}" |
| 111 | + @update:model-value="value => formData.${javaField} = value" | ||
| 94 | /> | 112 | /> |
| 95 | - ## 文本域 | ||
| 96 | - #elseif ($column.htmlType == "textarea") | 113 | +## 文本域 |
| 114 | +#elseif ($column.htmlType == "textarea" || $column.htmlType == "editor") | ||
| 97 | <wd-form-item title="${comment}" title-width="180rpx" prop="${javaField}"> | 115 | <wd-form-item title="${comment}" title-width="180rpx" prop="${javaField}"> |
| 98 | <wd-textarea | 116 | <wd-textarea |
| 99 | v-model="formData.${javaField}" | 117 | v-model="formData.${javaField}" |
| 100 | clearable | 118 | clearable |
| 101 | placeholder="请输入${comment}" | 119 | placeholder="请输入${comment}" |
| 120 | +#if ($column.htmlType == "editor") | ||
| 121 | + :maxlength="2000" | ||
| 122 | +#else | ||
| 102 | :maxlength="200" | 123 | :maxlength="200" |
| 124 | +#end | ||
| 103 | show-word-limit | 125 | show-word-limit |
| 104 | /> | 126 | /> |
| 105 | </wd-form-item> | 127 | </wd-form-item> |
| 106 | - ## 图片上传 → yd-upload-img(包 wd-form-item 才能注册到 wd-form 做校验/错误展示) | ||
| 107 | - #elseif ($column.htmlType == "imageUpload") | 128 | +## 图片上传 → yd-upload-img(包 wd-form-item 才能注册到 wd-form 做校验/错误展示) |
| 129 | +#elseif ($column.htmlType == "imageUpload") | ||
| 108 | <wd-form-item title="${comment}" title-width="180rpx" prop="${javaField}"> | 130 | <wd-form-item title="${comment}" title-width="180rpx" prop="${javaField}"> |
| 109 | <yd-upload-img v-model="formData.${javaField}" directory="${table.moduleName}/${table.businessName}" /> | 131 | <yd-upload-img v-model="formData.${javaField}" directory="${table.moduleName}/${table.businessName}" /> |
| 110 | </wd-form-item> | 132 | </wd-form-item> |
| 111 | - ## 文件上传 → yd-upload-file | ||
| 112 | - #elseif ($column.htmlType == "fileUpload") | 133 | +## 文件上传 → yd-upload-file |
| 134 | +#elseif ($column.htmlType == "fileUpload") | ||
| 113 | <wd-form-item title="${comment}" title-width="180rpx" prop="${javaField}"> | 135 | <wd-form-item title="${comment}" title-width="180rpx" prop="${javaField}"> |
| 114 | - <yd-upload-file v-model="formData.${javaField}" directory="${table.moduleName}/${table.businessName}" /> | 136 | + <yd-upload-file v-model="formData.${javaField}" :limit="1" directory="${table.moduleName}/${table.businessName}" /> |
| 115 | </wd-form-item> | 137 | </wd-form-item> |
| 116 | - ## 默认:文本输入 | ||
| 117 | - #else | 138 | +## 默认:文本输入 |
| 139 | +#else | ||
| 118 | <wd-form-item title="${comment}" title-width="180rpx" prop="${javaField}"> | 140 | <wd-form-item title="${comment}" title-width="180rpx" prop="${javaField}"> |
| 119 | <wd-input | 141 | <wd-input |
| 120 | v-model="formData.${javaField}" | 142 | v-model="formData.${javaField}" |
| @@ -122,11 +144,277 @@ | @@ -122,11 +144,277 @@ | ||
| 122 | placeholder="请输入${comment}" | 144 | placeholder="请输入${comment}" |
| 123 | /> | 145 | /> |
| 124 | </wd-form-item> | 146 | </wd-form-item> |
| 125 | - #end | ||
| 126 | - #end | 147 | +#end |
| 148 | +#end | ||
| 127 | #end | 149 | #end |
| 128 | </wd-cell-group> | 150 | </wd-cell-group> |
| 129 | </wd-form> | 151 | </wd-form> |
| 152 | +#if ($table.templateType == 10 || $table.templateType == 12) | ||
| 153 | +#foreach ($subTable in $subTables) | ||
| 154 | +#set ($index = $foreach.count - 1) | ||
| 155 | +#set ($subColumns = $subColumnsList.get($index)) | ||
| 156 | +#set ($subJoinColumn = $subJoinColumns.get($index)) | ||
| 157 | +#set ($subClassNameVar = $subClassNameVars.get($index)) | ||
| 158 | + | ||
| 159 | + <!-- 子表:${subTable.classComment} --> | ||
| 160 | + <view class="mt-20rpx flex items-center justify-between px-24rpx py-16rpx"> | ||
| 161 | + <text class="text-28rpx text-[#333] font-semibold">${subTable.classComment}</text> | ||
| 162 | +#if ($subTable.subJoinMany) | ||
| 163 | + <wd-button size="small" type="primary" variant="plain" @click="add${subClassNameVar.substring(0,1).toUpperCase()}${subClassNameVar.substring(1)}"> | ||
| 164 | + 添加 | ||
| 165 | + </wd-button> | ||
| 166 | +#else | ||
| 167 | + <wd-button | ||
| 168 | + v-if="!${subClassNameVar}" | ||
| 169 | + size="small" | ||
| 170 | + type="primary" | ||
| 171 | + variant="plain" | ||
| 172 | + @click="add${subClassNameVar.substring(0,1).toUpperCase()}${subClassNameVar.substring(1)}()" | ||
| 173 | + > | ||
| 174 | + 添加 | ||
| 175 | + </wd-button> | ||
| 176 | +#end | ||
| 177 | + </view> | ||
| 178 | + <view class="px-24rpx"> | ||
| 179 | +#if ($subTable.subJoinMany) | ||
| 180 | + <view v-if="!${subClassNameVar}s.length" class="rounded-12rpx bg-white py-40rpx text-center text-26rpx text-[#999]"> | ||
| 181 | + 暂无${subTable.classComment} | ||
| 182 | + </view> | ||
| 183 | + <view | ||
| 184 | + v-for="(item, index) in ${subClassNameVar}s" | ||
| 185 | + :key="index" | ||
| 186 | + class="mb-20rpx rounded-12rpx bg-white p-24rpx shadow-sm" | ||
| 187 | + > | ||
| 188 | + <view class="mb-16rpx flex justify-end"> | ||
| 189 | + <wd-button size="small" type="danger" variant="plain" @click="remove${subClassNameVar.substring(0,1).toUpperCase()}${subClassNameVar.substring(1)}(index)"> | ||
| 190 | + 删除 | ||
| 191 | + </wd-button> | ||
| 192 | + </view> | ||
| 193 | +#foreach ($column in $subColumns) | ||
| 194 | +#if (($column.createOperation || $column.updateOperation) && !$column.primaryKey && $column.id != $subJoinColumn.id) | ||
| 195 | +#set ($javaType = $column.javaType.toLowerCase()) | ||
| 196 | +#set ($dictMethod = "getDictOptions") | ||
| 197 | +#if ($column.javaType == "Integer" || $column.javaType == "Long" || $column.javaType == "Byte" || $column.javaType == "Short") | ||
| 198 | +#set ($dictMethod = "getIntDictOptions") | ||
| 199 | +#elseif ($column.javaType == "String") | ||
| 200 | +#set ($dictMethod = "getStrDictOptions") | ||
| 201 | +#elseif ($column.javaType == "Boolean") | ||
| 202 | +#set ($dictMethod = "getBoolDictOptions") | ||
| 203 | +#end | ||
| 204 | +#if ($column.htmlType == "select" && $column.dictType && "" != $column.dictType) | ||
| 205 | + <yd-form-picker | ||
| 206 | + v-model="item.${column.javaField}" | ||
| 207 | + label="${column.columnComment}" | ||
| 208 | + label-width="180rpx" | ||
| 209 | +#if ($column.javaType == "Boolean") | ||
| 210 | + :columns="getBoolDictOptions(DICT_TYPE.${column.dictType.toUpperCase()})" | ||
| 211 | +#else | ||
| 212 | + :dict-type="DICT_TYPE.${column.dictType.toUpperCase()}" | ||
| 213 | +#if ($column.javaType == "String") | ||
| 214 | + dict-kind="str" | ||
| 215 | +#end | ||
| 216 | +#end | ||
| 217 | + placeholder="请选择${column.columnComment}" | ||
| 218 | + /> | ||
| 219 | +#elseif ($column.htmlType == "radio" && $column.dictType && "" != $column.dictType) | ||
| 220 | + <wd-form-item title="${column.columnComment}" title-width="180rpx" center> | ||
| 221 | + <wd-radio-group v-model="item.${column.javaField}" type="button"> | ||
| 222 | + <wd-radio | ||
| 223 | + v-for="dict in $dictMethod(DICT_TYPE.${column.dictType.toUpperCase()})" | ||
| 224 | + :key="dict.value" | ||
| 225 | + :value="dict.value" | ||
| 226 | + > | ||
| 227 | + {{ dict.label }} | ||
| 228 | + </wd-radio> | ||
| 229 | + </wd-radio-group> | ||
| 230 | + </wd-form-item> | ||
| 231 | +#elseif ($javaType == "boolean") | ||
| 232 | + <wd-form-item title="${column.columnComment}" title-width="180rpx" center> | ||
| 233 | + <wd-switch v-model="item.${column.javaField}" /> | ||
| 234 | + </wd-form-item> | ||
| 235 | +#elseif ($javaType == "long" || $javaType == "integer" || $javaType == "short" || $javaType == "float" || $javaType == "double" || $javaType == "bigdecimal" || $javaType == "byte") | ||
| 236 | + <wd-form-item title="${column.columnComment}" title-width="180rpx"> | ||
| 237 | + <wd-input-number | ||
| 238 | + v-model="item.${column.javaField}" | ||
| 239 | +#if($column.nullable) | ||
| 240 | + allow-null | ||
| 241 | +#end | ||
| 242 | +#if ($javaType == "float" || $javaType == "double" || $javaType == "bigdecimal") | ||
| 243 | + :precision="2" | ||
| 244 | + input-type="number" | ||
| 245 | +#end | ||
| 246 | + :min="0" | ||
| 247 | + /> | ||
| 248 | + </wd-form-item> | ||
| 249 | +#elseif ($javaType == "date" || $javaType == "localdate" || $javaType == "localdatetime") | ||
| 250 | +#set ($subPickerType = "date") | ||
| 251 | +#if ($javaType == "localdatetime") | ||
| 252 | +#set ($subPickerType = "datetime") | ||
| 253 | +#end | ||
| 254 | + <wd-form-item | ||
| 255 | + title="${column.columnComment}" | ||
| 256 | + title-width="180rpx" | ||
| 257 | + is-link | ||
| 258 | +#if ($javaType == "localdatetime") | ||
| 259 | + :value="item.${column.javaField} ? formatDateTime(item.${column.javaField}) : ''" | ||
| 260 | +#else | ||
| 261 | + :value="item.${column.javaField} ? formatDate(item.${column.javaField}) : ''" | ||
| 262 | +#end | ||
| 263 | + placeholder="请选择${column.columnComment}" | ||
| 264 | + @click="subDateVisible[`${subClassNameVar}s.#[[${]]#index#[[}]]#.${column.javaField}`] = true" | ||
| 265 | + /> | ||
| 266 | + <wd-datetime-picker | ||
| 267 | + v-model:visible="subDateVisible[`${subClassNameVar}s.#[[${]]#index#[[}]]#.${column.javaField}`]" | ||
| 268 | + :model-value="item.${column.javaField} || Date.now()" | ||
| 269 | + type="${subPickerType}" | ||
| 270 | + @update:model-value="value => item.${column.javaField} = value" | ||
| 271 | + /> | ||
| 272 | +#elseif ($column.htmlType == "textarea" || $column.htmlType == "editor") | ||
| 273 | + <wd-form-item title="${column.columnComment}" title-width="180rpx"> | ||
| 274 | + <wd-textarea | ||
| 275 | + v-model="item.${column.javaField}" | ||
| 276 | + clearable | ||
| 277 | + placeholder="请输入${column.columnComment}" | ||
| 278 | +#if ($column.htmlType == "editor") | ||
| 279 | + :maxlength="2000" | ||
| 280 | +#else | ||
| 281 | + :maxlength="200" | ||
| 282 | +#end | ||
| 283 | + /> | ||
| 284 | + </wd-form-item> | ||
| 285 | +#elseif ($column.htmlType == "imageUpload") | ||
| 286 | + <wd-form-item title="${column.columnComment}" title-width="180rpx"> | ||
| 287 | + <yd-upload-img v-model="item.${column.javaField}" directory="${table.moduleName}/${table.businessName}" /> | ||
| 288 | + </wd-form-item> | ||
| 289 | +#elseif ($column.htmlType == "fileUpload") | ||
| 290 | + <wd-form-item title="${column.columnComment}" title-width="180rpx"> | ||
| 291 | + <yd-upload-file v-model="item.${column.javaField}" :limit="1" directory="${table.moduleName}/${table.businessName}" /> | ||
| 292 | + </wd-form-item> | ||
| 293 | +#else | ||
| 294 | + <wd-form-item title="${column.columnComment}" title-width="180rpx"> | ||
| 295 | + <wd-input v-model="item.${column.javaField}" clearable placeholder="请输入${column.columnComment}" /> | ||
| 296 | + </wd-form-item> | ||
| 297 | +#end | ||
| 298 | +#end | ||
| 299 | +#end | ||
| 300 | + </view> | ||
| 301 | +#else | ||
| 302 | + <view v-if="!${subClassNameVar}" class="rounded-12rpx bg-white py-40rpx text-center text-26rpx text-[#999]"> | ||
| 303 | + 暂无${subTable.classComment} | ||
| 304 | + </view> | ||
| 305 | + <view v-else class="rounded-12rpx bg-white p-24rpx shadow-sm"> | ||
| 306 | +#foreach ($column in $subColumns) | ||
| 307 | +#if (($column.createOperation || $column.updateOperation) && !$column.primaryKey && $column.id != $subJoinColumn.id) | ||
| 308 | +#set ($javaType = $column.javaType.toLowerCase()) | ||
| 309 | +#set ($dictMethod = "getDictOptions") | ||
| 310 | +#if ($column.javaType == "Integer" || $column.javaType == "Long" || $column.javaType == "Byte" || $column.javaType == "Short") | ||
| 311 | +#set ($dictMethod = "getIntDictOptions") | ||
| 312 | +#elseif ($column.javaType == "String") | ||
| 313 | +#set ($dictMethod = "getStrDictOptions") | ||
| 314 | +#elseif ($column.javaType == "Boolean") | ||
| 315 | +#set ($dictMethod = "getBoolDictOptions") | ||
| 316 | +#end | ||
| 317 | +#if ($column.htmlType == "select" && $column.dictType && "" != $column.dictType) | ||
| 318 | + <yd-form-picker | ||
| 319 | + v-model="${subClassNameVar}.${column.javaField}" | ||
| 320 | + label="${column.columnComment}" | ||
| 321 | + label-width="180rpx" | ||
| 322 | +#if ($column.javaType == "Boolean") | ||
| 323 | + :columns="getBoolDictOptions(DICT_TYPE.${column.dictType.toUpperCase()})" | ||
| 324 | +#else | ||
| 325 | + :dict-type="DICT_TYPE.${column.dictType.toUpperCase()}" | ||
| 326 | +#if ($column.javaType == "String") | ||
| 327 | + dict-kind="str" | ||
| 328 | +#end | ||
| 329 | +#end | ||
| 330 | + placeholder="请选择${column.columnComment}" | ||
| 331 | + /> | ||
| 332 | +#elseif ($column.htmlType == "radio" && $column.dictType && "" != $column.dictType) | ||
| 333 | + <wd-form-item title="${column.columnComment}" title-width="180rpx" center> | ||
| 334 | + <wd-radio-group v-model="${subClassNameVar}.${column.javaField}" type="button"> | ||
| 335 | + <wd-radio | ||
| 336 | + v-for="dict in $dictMethod(DICT_TYPE.${column.dictType.toUpperCase()})" | ||
| 337 | + :key="dict.value" | ||
| 338 | + :value="dict.value" | ||
| 339 | + > | ||
| 340 | + {{ dict.label }} | ||
| 341 | + </wd-radio> | ||
| 342 | + </wd-radio-group> | ||
| 343 | + </wd-form-item> | ||
| 344 | +#elseif ($javaType == "boolean") | ||
| 345 | + <wd-form-item title="${column.columnComment}" title-width="180rpx" center> | ||
| 346 | + <wd-switch v-model="${subClassNameVar}.${column.javaField}" /> | ||
| 347 | + </wd-form-item> | ||
| 348 | +#elseif ($javaType == "long" || $javaType == "integer" || $javaType == "short" || $javaType == "float" || $javaType == "double" || $javaType == "bigdecimal" || $javaType == "byte") | ||
| 349 | + <wd-form-item title="${column.columnComment}" title-width="180rpx"> | ||
| 350 | + <wd-input-number | ||
| 351 | + v-model="${subClassNameVar}.${column.javaField}" | ||
| 352 | +#if($column.nullable) | ||
| 353 | + allow-null | ||
| 354 | +#end | ||
| 355 | +#if ($javaType == "float" || $javaType == "double" || $javaType == "bigdecimal") | ||
| 356 | + :precision="2" | ||
| 357 | + input-type="number" | ||
| 358 | +#end | ||
| 359 | + :min="0" | ||
| 360 | + /> | ||
| 361 | + </wd-form-item> | ||
| 362 | +#elseif ($javaType == "date" || $javaType == "localdate" || $javaType == "localdatetime") | ||
| 363 | +#set ($subPickerType = "date") | ||
| 364 | +#if ($javaType == "localdatetime") | ||
| 365 | +#set ($subPickerType = "datetime") | ||
| 366 | +#end | ||
| 367 | + <wd-form-item | ||
| 368 | + title="${column.columnComment}" | ||
| 369 | + title-width="180rpx" | ||
| 370 | + is-link | ||
| 371 | +#if ($javaType == "localdatetime") | ||
| 372 | + :value="${subClassNameVar}.${column.javaField} ? formatDateTime(${subClassNameVar}.${column.javaField}) : ''" | ||
| 373 | +#else | ||
| 374 | + :value="${subClassNameVar}.${column.javaField} ? formatDate(${subClassNameVar}.${column.javaField}) : ''" | ||
| 375 | +#end | ||
| 376 | + placeholder="请选择${column.columnComment}" | ||
| 377 | + @click="subDateVisible['${subClassNameVar}.${column.javaField}'] = true" | ||
| 378 | + /> | ||
| 379 | + <wd-datetime-picker | ||
| 380 | + v-model:visible="subDateVisible['${subClassNameVar}.${column.javaField}']" | ||
| 381 | + :model-value="${subClassNameVar}.${column.javaField} || Date.now()" | ||
| 382 | + type="${subPickerType}" | ||
| 383 | + @update:model-value="value => ${subClassNameVar}.${column.javaField} = value" | ||
| 384 | + /> | ||
| 385 | +#elseif ($column.htmlType == "textarea" || $column.htmlType == "editor") | ||
| 386 | + <wd-form-item title="${column.columnComment}" title-width="180rpx"> | ||
| 387 | + <wd-textarea | ||
| 388 | + v-model="${subClassNameVar}.${column.javaField}" | ||
| 389 | + clearable | ||
| 390 | + placeholder="请输入${column.columnComment}" | ||
| 391 | +#if ($column.htmlType == "editor") | ||
| 392 | + :maxlength="2000" | ||
| 393 | +#else | ||
| 394 | + :maxlength="200" | ||
| 395 | +#end | ||
| 396 | + /> | ||
| 397 | + </wd-form-item> | ||
| 398 | +#elseif ($column.htmlType == "imageUpload") | ||
| 399 | + <wd-form-item title="${column.columnComment}" title-width="180rpx"> | ||
| 400 | + <yd-upload-img v-model="${subClassNameVar}.${column.javaField}" directory="${table.moduleName}/${table.businessName}" /> | ||
| 401 | + </wd-form-item> | ||
| 402 | +#elseif ($column.htmlType == "fileUpload") | ||
| 403 | + <wd-form-item title="${column.columnComment}" title-width="180rpx"> | ||
| 404 | + <yd-upload-file v-model="${subClassNameVar}.${column.javaField}" :limit="1" directory="${table.moduleName}/${table.businessName}" /> | ||
| 405 | + </wd-form-item> | ||
| 406 | +#else | ||
| 407 | + <wd-form-item title="${column.columnComment}" title-width="180rpx"> | ||
| 408 | + <wd-input v-model="${subClassNameVar}.${column.javaField}" clearable placeholder="请输入${column.columnComment}" /> | ||
| 409 | + </wd-form-item> | ||
| 410 | +#end | ||
| 411 | +#end | ||
| 412 | +#end | ||
| 413 | + </view> | ||
| 414 | +#end | ||
| 415 | + </view> | ||
| 416 | +#end | ||
| 417 | +#end | ||
| 130 | </view> | 418 | </view> |
| 131 | 419 | ||
| 132 | <!-- 底部保存按钮 --> | 420 | <!-- 底部保存按钮 --> |
| @@ -146,7 +434,7 @@ | @@ -146,7 +434,7 @@ | ||
| 146 | <script lang="ts" setup> | 434 | <script lang="ts" setup> |
| 147 | import type { FormInstance } from '@wot-ui/ui/components/wd-form/types' | 435 | import type { FormInstance } from '@wot-ui/ui/components/wd-form/types' |
| 148 | #set ($primaryJavaType = $primaryColumn.javaType.toLowerCase()) | 436 | #set ($primaryJavaType = $primaryColumn.javaType.toLowerCase()) |
| 149 | -#if(${primaryJavaType} == "long" || ${primaryJavaType} == "integer" || ${primaryJavaType} == "short" || ${primaryJavaType} == "double" || ${primaryJavaType} == "bigdecimal" || ${primaryJavaType} == "byte") | 437 | +#if(${primaryJavaType} == "long" || ${primaryJavaType} == "integer" || ${primaryJavaType} == "short" || ${primaryJavaType} == "float" || ${primaryJavaType} == "double" || ${primaryJavaType} == "bigdecimal" || ${primaryJavaType} == "byte") |
| 150 | #set ($primaryTsType = "number") | 438 | #set ($primaryTsType = "number") |
| 151 | #else | 439 | #else |
| 152 | #set ($primaryTsType = "string") | 440 | #set ($primaryTsType = "string") |
| @@ -154,54 +442,113 @@ import type { FormInstance } from '@wot-ui/ui/components/wd-form/types' | @@ -154,54 +442,113 @@ import type { FormInstance } from '@wot-ui/ui/components/wd-form/types' | ||
| 154 | #set ($hasDict = 0) | 442 | #set ($hasDict = 0) |
| 155 | #set ($hasDate = 0) | 443 | #set ($hasDate = 0) |
| 156 | #set ($hasDateTime = 0) | 444 | #set ($hasDateTime = 0) |
| 445 | +#set ($hasSubDate = 0) | ||
| 157 | #set ($hasGetDictOptions = 0) | 446 | #set ($hasGetDictOptions = 0) |
| 158 | #set ($hasGetIntDictOptions = 0) | 447 | #set ($hasGetIntDictOptions = 0) |
| 159 | #set ($hasGetStrDictOptions = 0) | 448 | #set ($hasGetStrDictOptions = 0) |
| 160 | #set ($hasGetBoolDictOptions = 0) | 449 | #set ($hasGetBoolDictOptions = 0) |
| 161 | #foreach ($column in $columns) | 450 | #foreach ($column in $columns) |
| 162 | - #if (($column.createOperation || $column.updateOperation) && !$column.primaryKey | 451 | +#if (($column.createOperation || $column.updateOperation) && !$column.primaryKey |
| 163 | && ($column.htmlType == "select" || $column.htmlType == "radio") | 452 | && ($column.htmlType == "select" || $column.htmlType == "radio") |
| 164 | && $column.dictType && "" != $column.dictType) | 453 | && $column.dictType && "" != $column.dictType) |
| 165 | - #set ($hasDict = 1) | ||
| 166 | - #if ($column.htmlType == "radio" && ($column.javaType == "Integer" || $column.javaType == "Long" || $column.javaType == "Byte" || $column.javaType == "Short")) | ||
| 167 | - #set ($hasGetIntDictOptions = 1) | ||
| 168 | - #elseif ($column.htmlType == "radio" && $column.javaType == "String") | ||
| 169 | - #set ($hasGetStrDictOptions = 1) | ||
| 170 | - #elseif ($column.htmlType == "radio" && $column.javaType == "Boolean") | ||
| 171 | - #set ($hasGetBoolDictOptions = 1) | ||
| 172 | - #elseif ($column.htmlType == "radio") | ||
| 173 | - #set ($hasGetDictOptions = 1) | ||
| 174 | - #end | ||
| 175 | - #end | ||
| 176 | - #if (($column.createOperation || $column.updateOperation) && !$column.primaryKey | 454 | +#set ($hasDict = 1) |
| 455 | +#if ($column.htmlType == "radio" && ($column.javaType == "Integer" || $column.javaType == "Long" || $column.javaType == "Byte" || $column.javaType == "Short")) | ||
| 456 | +#set ($hasGetIntDictOptions = 1) | ||
| 457 | +#elseif ($column.htmlType == "radio" && $column.javaType == "String") | ||
| 458 | +#set ($hasGetStrDictOptions = 1) | ||
| 459 | +#elseif (($column.htmlType == "select" || $column.htmlType == "radio") && $column.javaType == "Boolean") | ||
| 460 | +#set ($hasGetBoolDictOptions = 1) | ||
| 461 | +#elseif ($column.htmlType == "radio") | ||
| 462 | +#set ($hasGetDictOptions = 1) | ||
| 463 | +#end | ||
| 464 | +#end | ||
| 465 | +#if (($column.createOperation || $column.updateOperation) && !$column.primaryKey | ||
| 177 | && (${column.javaType.toLowerCase()} == "date" || ${column.javaType.toLowerCase()} == "localdate")) | 466 | && (${column.javaType.toLowerCase()} == "date" || ${column.javaType.toLowerCase()} == "localdate")) |
| 178 | - #set ($hasDate = 1) | ||
| 179 | - #end | ||
| 180 | - #if (($column.createOperation || $column.updateOperation) && !$column.primaryKey | 467 | +#set ($hasDate = 1) |
| 468 | +#end | ||
| 469 | +#if (($column.createOperation || $column.updateOperation) && !$column.primaryKey | ||
| 181 | && ${column.javaType.toLowerCase()} == "localdatetime") | 470 | && ${column.javaType.toLowerCase()} == "localdatetime") |
| 182 | - #set ($hasDateTime = 1) | ||
| 183 | - #end | 471 | +#set ($hasDateTime = 1) |
| 472 | +#end | ||
| 473 | +#end | ||
| 474 | +#if ($table.templateType == 10 || $table.templateType == 12) | ||
| 475 | +#foreach ($subColumns in $subColumnsList) | ||
| 476 | +#foreach ($column in $subColumns) | ||
| 477 | +#if (($column.createOperation || $column.updateOperation) && !$column.primaryKey | ||
| 478 | + && ($column.htmlType == "select" || $column.htmlType == "radio") | ||
| 479 | + && $column.dictType && "" != $column.dictType) | ||
| 480 | +#set ($hasDict = 1) | ||
| 481 | +#if ($column.htmlType == "radio" && ($column.javaType == "Integer" || $column.javaType == "Long" || $column.javaType == "Byte" || $column.javaType == "Short")) | ||
| 482 | +#set ($hasGetIntDictOptions = 1) | ||
| 483 | +#elseif ($column.htmlType == "radio" && $column.javaType == "String") | ||
| 484 | +#set ($hasGetStrDictOptions = 1) | ||
| 485 | +#elseif (($column.htmlType == "select" || $column.htmlType == "radio") && $column.javaType == "Boolean") | ||
| 486 | +#set ($hasGetBoolDictOptions = 1) | ||
| 487 | +#elseif ($column.htmlType == "radio") | ||
| 488 | +#set ($hasGetDictOptions = 1) | ||
| 489 | +#end | ||
| 490 | +#end | ||
| 491 | +#if (($column.createOperation || $column.updateOperation) && !$column.primaryKey | ||
| 492 | + && (${column.javaType.toLowerCase()} == "date" || ${column.javaType.toLowerCase()} == "localdate")) | ||
| 493 | +#set ($hasDate = 1) | ||
| 494 | +#set ($hasSubDate = 1) | ||
| 495 | +#end | ||
| 496 | +#if (($column.createOperation || $column.updateOperation) && !$column.primaryKey | ||
| 497 | + && ${column.javaType.toLowerCase()} == "localdatetime") | ||
| 498 | +#set ($hasDateTime = 1) | ||
| 499 | +#set ($hasSubDate = 1) | ||
| 500 | +#end | ||
| 501 | +#end | ||
| 502 | +#end | ||
| 184 | #end | 503 | #end |
| 185 | -import type { ${simpleClassName} } from '@/api/${table.moduleName}/${table.businessName}' | 504 | +import type { |
| 505 | + ${simpleClassName}, | ||
| 506 | +#if ($table.templateType == 10 || $table.templateType == 12) | ||
| 507 | +#foreach ($subSimpleClassName in $subSimpleClassNames) | ||
| 508 | + ${subSimpleClassName}, | ||
| 509 | +#end | ||
| 510 | +#end | ||
| 511 | +} from '@/api/${table.moduleName}/${table.businessName}' | ||
| 186 | import { computed, onMounted, ref } from 'vue' | 512 | import { computed, onMounted, ref } from 'vue' |
| 187 | import { useToast } from '@wot-ui/ui/components/wd-toast' | 513 | import { useToast } from '@wot-ui/ui/components/wd-toast' |
| 514 | +#if ($table.templateType == 10 || $table.templateType == 12) | ||
| 515 | +import { | ||
| 516 | + create${simpleClassName}, | ||
| 517 | + get${simpleClassName}, | ||
| 518 | +#foreach ($subTable in $subTables) | ||
| 519 | +#set ($index = $foreach.count - 1) | ||
| 520 | +#set ($subSimpleClassName = $subSimpleClassNames.get($index)) | ||
| 521 | +#set ($subJoinColumn = $subJoinColumns.get($index)) | ||
| 522 | +#set ($SubJoinColumnName = $subJoinColumn.javaField.substring(0,1).toUpperCase() + $subJoinColumn.javaField.substring(1)) | ||
| 523 | +#if ($subTable.subJoinMany) | ||
| 524 | + get${subSimpleClassName}ListBy${SubJoinColumnName}, | ||
| 525 | +#else | ||
| 526 | + get${subSimpleClassName}By${SubJoinColumnName}, | ||
| 527 | +#end | ||
| 528 | +#end | ||
| 529 | + update${simpleClassName}, | ||
| 530 | +} from '@/api/${table.moduleName}/${table.businessName}' | ||
| 531 | +#elseif ($table.templateType == 2) | ||
| 532 | +import { create${simpleClassName}, get${simpleClassName}, get${simpleClassName}List, update${simpleClassName} } from '@/api/${table.moduleName}/${table.businessName}' | ||
| 533 | +#else | ||
| 188 | import { create${simpleClassName}, get${simpleClassName}, update${simpleClassName} } from '@/api/${table.moduleName}/${table.businessName}' | 534 | import { create${simpleClassName}, get${simpleClassName}, update${simpleClassName} } from '@/api/${table.moduleName}/${table.businessName}' |
| 535 | +#end | ||
| 189 | #if ($hasGetDictOptions == 1 || $hasGetIntDictOptions == 1 || $hasGetStrDictOptions == 1 || $hasGetBoolDictOptions == 1) | 536 | #if ($hasGetDictOptions == 1 || $hasGetIntDictOptions == 1 || $hasGetStrDictOptions == 1 || $hasGetBoolDictOptions == 1) |
| 190 | - #set ($dictImportNames = "") | ||
| 191 | - #if ($hasGetDictOptions == 1) | ||
| 192 | - #set ($dictImportNames = "${dictImportNames}getDictOptions, ") | ||
| 193 | - #end | ||
| 194 | - #if ($hasGetIntDictOptions == 1) | ||
| 195 | - #set ($dictImportNames = "${dictImportNames}getIntDictOptions, ") | ||
| 196 | - #end | ||
| 197 | - #if ($hasGetStrDictOptions == 1) | ||
| 198 | - #set ($dictImportNames = "${dictImportNames}getStrDictOptions, ") | ||
| 199 | - #end | ||
| 200 | - #if ($hasGetBoolDictOptions == 1) | ||
| 201 | - #set ($dictImportNames = "${dictImportNames}getBoolDictOptions, ") | ||
| 202 | - #end | ||
| 203 | - #set ($dictImportNames = $dictImportNames.trim()) | ||
| 204 | - #set ($dictImportNames = $dictImportNames.substring(0, $dictImportNames.length() - 1)) | 537 | +#set ($dictImportNames = "") |
| 538 | +#if ($hasGetDictOptions == 1) | ||
| 539 | +#set ($dictImportNames = "${dictImportNames}getDictOptions, ") | ||
| 540 | +#end | ||
| 541 | +#if ($hasGetIntDictOptions == 1) | ||
| 542 | +#set ($dictImportNames = "${dictImportNames}getIntDictOptions, ") | ||
| 543 | +#end | ||
| 544 | +#if ($hasGetStrDictOptions == 1) | ||
| 545 | +#set ($dictImportNames = "${dictImportNames}getStrDictOptions, ") | ||
| 546 | +#end | ||
| 547 | +#if ($hasGetBoolDictOptions == 1) | ||
| 548 | +#set ($dictImportNames = "${dictImportNames}getBoolDictOptions, ") | ||
| 549 | +#end | ||
| 550 | +#set ($dictImportNames = $dictImportNames.trim()) | ||
| 551 | +#set ($dictImportNames = $dictImportNames.substring(0, $dictImportNames.length() - 1)) | ||
| 205 | import { $dictImportNames } from '@/hooks/useDict' | 552 | import { $dictImportNames } from '@/hooks/useDict' |
| 206 | #end | 553 | #end |
| 207 | import { delay, navigateBackPlus } from '@/utils' | 554 | import { delay, navigateBackPlus } from '@/utils' |
| @@ -209,21 +556,27 @@ import { delay, navigateBackPlus } from '@/utils' | @@ -209,21 +556,27 @@ import { delay, navigateBackPlus } from '@/utils' | ||
| 209 | import { DICT_TYPE } from '@/utils/constants' | 556 | import { DICT_TYPE } from '@/utils/constants' |
| 210 | #end | 557 | #end |
| 211 | #if ($hasDate == 1 || $hasDateTime == 1) | 558 | #if ($hasDate == 1 || $hasDateTime == 1) |
| 212 | - #set ($dateImportNames = "") | ||
| 213 | - #if ($hasDate == 1) | ||
| 214 | - #set ($dateImportNames = "${dateImportNames}formatDate, ") | ||
| 215 | - #end | ||
| 216 | - #if ($hasDateTime == 1) | ||
| 217 | - #set ($dateImportNames = "${dateImportNames}formatDateTime, ") | ||
| 218 | - #end | ||
| 219 | - #set ($dateImportNames = $dateImportNames.trim()) | ||
| 220 | - #set ($dateImportNames = $dateImportNames.substring(0, $dateImportNames.length() - 1)) | 559 | +#set ($dateImportNames = "") |
| 560 | +#if ($hasDate == 1) | ||
| 561 | +#set ($dateImportNames = "${dateImportNames}formatDate, ") | ||
| 562 | +#end | ||
| 563 | +#if ($hasDateTime == 1) | ||
| 564 | +#set ($dateImportNames = "${dateImportNames}formatDateTime, ") | ||
| 565 | +#end | ||
| 566 | +#set ($dateImportNames = $dateImportNames.trim()) | ||
| 567 | +#set ($dateImportNames = $dateImportNames.substring(0, $dateImportNames.length() - 1)) | ||
| 221 | import { $dateImportNames } from '@/utils/date' | 568 | import { $dateImportNames } from '@/utils/date' |
| 222 | #end | 569 | #end |
| 223 | import { createFormSchema } from '@/utils/wot' | 570 | import { createFormSchema } from '@/utils/wot' |
| 571 | +#if ($table.templateType == 2) | ||
| 572 | +import { handleTree } from '@/utils/tree' | ||
| 573 | +#end | ||
| 224 | 574 | ||
| 225 | const props = defineProps<{ | 575 | const props = defineProps<{ |
| 226 | - id?: ${primaryTsType} | any | 576 | + id?: ${primaryTsType} |
| 577 | +#if ($table.templateType == 2) | ||
| 578 | + ${treeParentColumn.javaField}?: ${primaryTsType} | ||
| 579 | +#end | ||
| 227 | }>() | 580 | }>() |
| 228 | 581 | ||
| 229 | definePage({ | 582 | definePage({ |
| @@ -236,42 +589,165 @@ definePage({ | @@ -236,42 +589,165 @@ definePage({ | ||
| 236 | const toast = useToast() | 589 | const toast = useToast() |
| 237 | const getTitle = computed(() => props.id ? '编辑${table.classComment}' : '新增${table.classComment}') | 590 | const getTitle = computed(() => props.id ? '编辑${table.classComment}' : '新增${table.classComment}') |
| 238 | const formLoading = ref(false) // 表单提交状态 | 591 | const formLoading = ref(false) // 表单提交状态 |
| 239 | -const formData = ref<${simpleClassName}>({ | 592 | +const formData = ref<Partial<${simpleClassName}>>({ |
| 240 | #foreach($column in $columns) | 593 | #foreach($column in $columns) |
| 241 | - #if (($column.createOperation || $column.updateOperation) || $column.primaryKey) | ||
| 242 | - #set ($javaType = $column.javaType.toLowerCase()) | ||
| 243 | - #set ($javaFieldLower = $column.javaField.toLowerCase()) | ||
| 244 | - #set ($optional = $column.nullable || $column.primaryKey || $javaFieldLower == "createtime" || $javaFieldLower == "updatetime") | ||
| 245 | - #if ($column.primaryKey) | 594 | +#if (($column.createOperation || $column.updateOperation) || $column.primaryKey) |
| 595 | +#set ($javaType = $column.javaType.toLowerCase()) | ||
| 596 | +#set ($javaFieldLower = $column.javaField.toLowerCase()) | ||
| 597 | +#set ($optional = $column.nullable || $column.primaryKey || $javaFieldLower == "createtime" || $javaFieldLower == "updatetime") | ||
| 598 | +#if ($column.primaryKey) | ||
| 599 | + ${column.javaField}: undefined, | ||
| 600 | +#elseif($table.templateType == 2 && $column.id == $treeParentColumn.id) | ||
| 601 | + ${column.javaField}: props.${column.javaField} ?? 0, | ||
| 602 | +#elseif(($column.htmlType == "select" || $column.htmlType == "radio") && $column.dictType && "" != $column.dictType) | ||
| 603 | + ${column.javaField}: undefined, | ||
| 604 | +#elseif(${javaType} == "long" || ${javaType} == "integer" || ${javaType} == "short" || ${javaType} == "float" || ${javaType} == "double" || ${javaType} == "bigdecimal" || ${javaType} == "byte") | ||
| 605 | +#if($column.nullable) | ||
| 246 | ${column.javaField}: undefined, | 606 | ${column.javaField}: undefined, |
| 247 | - #elseif(${javaType} == "long" || ${javaType} == "integer" || ${javaType} == "short" || ${javaType} == "double" || ${javaType} == "bigdecimal" || ${javaType} == "byte") | 607 | +#else |
| 248 | ${column.javaField}: 0, | 608 | ${column.javaField}: 0, |
| 249 | - #elseif(${javaType} == "boolean") | 609 | +#end |
| 610 | +#elseif(${javaType} == "boolean") | ||
| 250 | ${column.javaField}: false, | 611 | ${column.javaField}: false, |
| 251 | - #elseif(${javaType} == "date" || ${javaType} == "localdate" || ${javaType} == "localdatetime") | 612 | +#elseif(${javaType} == "date" || ${javaType} == "localdate" || ${javaType} == "localdatetime") |
| 252 | ${column.javaField}: undefined, | 613 | ${column.javaField}: undefined, |
| 253 | - #else | 614 | +#else |
| 254 | ${column.javaField}: '', | 615 | ${column.javaField}: '', |
| 255 | - #end | ||
| 256 | - #end | 616 | +#end |
| 617 | +#end | ||
| 257 | #end | 618 | #end |
| 258 | }) // 表单数据 | 619 | }) // 表单数据 |
| 620 | +#if ($table.templateType == 10 || $table.templateType == 12) | ||
| 621 | +#foreach ($subTable in $subTables) | ||
| 622 | +#set ($index = $foreach.count - 1) | ||
| 623 | +#set ($subSimpleClassName = $subSimpleClassNames.get($index)) | ||
| 624 | +#set ($subClassNameVar = $subClassNameVars.get($index)) | ||
| 625 | +#set ($subColumns = $subColumnsList.get($index)) | ||
| 626 | +#set ($subJoinColumn = $subJoinColumns.get($index)) | ||
| 627 | +#if ($subTable.subJoinMany) | ||
| 628 | +const ${subClassNameVar}s = ref<Partial<${subSimpleClassName}>[]>([]) // ${subTable.classComment}数据 | ||
| 629 | +#else | ||
| 630 | +const ${subClassNameVar} = ref<Partial<${subSimpleClassName}> | null>(null) // ${subTable.classComment}数据 | ||
| 631 | +#end | ||
| 632 | +#end | ||
| 633 | +#end | ||
| 259 | #foreach($column in $columns) | 634 | #foreach($column in $columns) |
| 260 | - #if (($column.createOperation || $column.updateOperation) && !$column.primaryKey && (${column.javaType.toLowerCase()} == "date" || ${column.javaType.toLowerCase()} == "localdate" || ${column.javaType.toLowerCase()} == "localdatetime")) | 635 | +#if (($column.createOperation || $column.updateOperation) && !$column.primaryKey && (${column.javaType.toLowerCase()} == "date" || ${column.javaType.toLowerCase()} == "localdate" || ${column.javaType.toLowerCase()} == "localdatetime")) |
| 261 | const ${column.javaField}Visible = ref(false) // ${column.columnComment}选择器状态 | 636 | const ${column.javaField}Visible = ref(false) // ${column.columnComment}选择器状态 |
| 262 | - #end | 637 | +#end |
| 638 | +#end | ||
| 639 | +#if ($hasSubDate == 1) | ||
| 640 | +const subDateVisible = ref<Record<string, boolean>>({}) // 子表日期选择器状态 | ||
| 263 | #end | 641 | #end |
| 264 | const formSchema = createFormSchema({ | 642 | const formSchema = createFormSchema({ |
| 265 | #foreach($column in $columns) | 643 | #foreach($column in $columns) |
| 266 | - #set ($javaFieldLower = $column.javaField.toLowerCase()) | ||
| 267 | - #if (($column.createOperation || $column.updateOperation) && !$column.nullable && !$column.primaryKey | 644 | +#set ($javaFieldLower = $column.javaField.toLowerCase()) |
| 645 | +#if (($column.createOperation || $column.updateOperation) && !$column.nullable && !$column.primaryKey | ||
| 268 | && $javaFieldLower != "createtime" && $javaFieldLower != "updatetime") | 646 | && $javaFieldLower != "createtime" && $javaFieldLower != "updatetime") |
| 269 | ${column.javaField}: [{ required: true, message: '${column.columnComment}不能为空' }], | 647 | ${column.javaField}: [{ required: true, message: '${column.columnComment}不能为空' }], |
| 270 | - #end | 648 | +#end |
| 271 | #end | 649 | #end |
| 272 | }) // 表单校验规则 | 650 | }) // 表单校验规则 |
| 273 | const formRef = ref<FormInstance>() // 表单组件引用 | 651 | const formRef = ref<FormInstance>() // 表单组件引用 |
| 652 | +#if ($table.templateType == 2) | ||
| 653 | +const treeData = ref<${simpleClassName}[]>([]) // 父级树形选项 | ||
| 654 | +#end | ||
| 655 | + | ||
| 656 | +#if ($table.templateType == 10 || $table.templateType == 12) | ||
| 657 | +#foreach ($subTable in $subTables) | ||
| 658 | +#set ($index = $foreach.count - 1) | ||
| 659 | +#set ($subSimpleClassName = $subSimpleClassNames.get($index)) | ||
| 660 | +#set ($subClassNameVar = $subClassNameVars.get($index)) | ||
| 661 | +#set ($subColumns = $subColumnsList.get($index)) | ||
| 662 | +#set ($subJoinColumn = $subJoinColumns.get($index)) | ||
| 663 | +#if ($subTable.subJoinMany) | ||
| 664 | +#set ($subClassNameVar = $subClassNameVars.get($index)) | ||
| 665 | +#set ($subColumns = $subColumnsList.get($index)) | ||
| 666 | +#set ($subJoinColumn = $subJoinColumns.get($index)) | ||
| 667 | +/** 添加${subTable.classComment} */ | ||
| 668 | +function add${subClassNameVar.substring(0,1).toUpperCase()}${subClassNameVar.substring(1)}() { | ||
| 669 | + ${subClassNameVar}s.value.push({ | ||
| 670 | +#foreach ($column in $subColumns) | ||
| 671 | +#if (($column.createOperation || $column.updateOperation) && !$column.primaryKey && $column.id != $subJoinColumn.id) | ||
| 672 | +#set ($javaType = $column.javaType.toLowerCase()) | ||
| 673 | +#if((($column.htmlType == "select" || $column.htmlType == "radio") && $column.dictType && "" != $column.dictType) || $javaType == "date" || $javaType == "localdate" || $javaType == "localdatetime") | ||
| 674 | + ${column.javaField}: undefined, | ||
| 675 | +#else | ||
| 676 | + ${column.javaField}:#if($column.nullable) undefined#elseif($javaType == "long" || $javaType == "integer" || $javaType == "short" || $javaType == "float" || $javaType == "double" || $javaType == "bigdecimal" || $javaType == "byte") 0#elseif($javaType == "boolean") false#else ''#end, | ||
| 677 | +#end | ||
| 678 | +#end | ||
| 679 | +#end | ||
| 680 | + }) | ||
| 681 | +} | ||
| 274 | 682 | ||
| 683 | +/** 删除${subTable.classComment} */ | ||
| 684 | +function remove${subClassNameVar.substring(0,1).toUpperCase()}${subClassNameVar.substring(1)}(index: number) { | ||
| 685 | + ${subClassNameVar}s.value.splice(index, 1) | ||
| 686 | +} | ||
| 687 | + | ||
| 688 | +#else | ||
| 689 | +/** 添加${subTable.classComment} */ | ||
| 690 | +function add${subClassNameVar.substring(0,1).toUpperCase()}${subClassNameVar.substring(1)}() { | ||
| 691 | + ${subClassNameVar}.value = { | ||
| 692 | +#foreach ($column in $subColumns) | ||
| 693 | +#if (($column.createOperation || $column.updateOperation) && !$column.primaryKey && $column.id != $subJoinColumn.id) | ||
| 694 | +#set ($javaType = $column.javaType.toLowerCase()) | ||
| 695 | +#if((($column.htmlType == "select" || $column.htmlType == "radio") && $column.dictType && "" != $column.dictType) || $javaType == "date" || $javaType == "localdate" || $javaType == "localdatetime") | ||
| 696 | + ${column.javaField}: undefined, | ||
| 697 | +#else | ||
| 698 | + ${column.javaField}:#if($column.nullable) undefined#elseif($javaType == "long" || $javaType == "integer" || $javaType == "short" || $javaType == "float" || $javaType == "double" || $javaType == "bigdecimal" || $javaType == "byte") 0#elseif($javaType == "boolean") false#else ''#end, | ||
| 699 | +#end | ||
| 700 | +#end | ||
| 701 | +#end | ||
| 702 | + } | ||
| 703 | +} | ||
| 704 | + | ||
| 705 | +#end | ||
| 706 | +#end | ||
| 707 | +/** 校验主子表数据 */ | ||
| 708 | +function isEmptySubValue(value: unknown) { | ||
| 709 | + return value === undefined || value === null || value === '' | ||
| 710 | +} | ||
| 711 | + | ||
| 712 | +function validateSubTables() { | ||
| 713 | +#foreach ($subTable in $subTables) | ||
| 714 | +#set ($index = $foreach.count - 1) | ||
| 715 | +#set ($subClassNameVar = $subClassNameVars.get($index)) | ||
| 716 | +#set ($subColumns = $subColumnsList.get($index)) | ||
| 717 | +#set ($subJoinColumn = $subJoinColumns.get($index)) | ||
| 718 | +#if ($subTable.subJoinMany) | ||
| 719 | + for (let index = 0; index < ${subClassNameVar}s.value.length; index++) { | ||
| 720 | + const item = ${subClassNameVar}s.value[index]! | ||
| 721 | +#foreach ($column in $subColumns) | ||
| 722 | +#set ($javaFieldLower = $column.javaField.toLowerCase()) | ||
| 723 | +#if (($column.createOperation || $column.updateOperation) && !$column.nullable && !$column.primaryKey | ||
| 724 | + && $column.id != $subJoinColumn.id && $javaFieldLower != "createtime" && $javaFieldLower != "updatetime") | ||
| 725 | + if (isEmptySubValue(item.${column.javaField})) { | ||
| 726 | + toast.warning(`${subTable.classComment}第#[[${]]#index + 1#[[}]]#项的${column.columnComment}不能为空`) | ||
| 727 | + return false | ||
| 728 | + } | ||
| 729 | +#end | ||
| 730 | +#end | ||
| 731 | + } | ||
| 732 | +#else | ||
| 733 | + if (${subClassNameVar}.value) { | ||
| 734 | +#foreach ($column in $subColumns) | ||
| 735 | +#set ($javaFieldLower = $column.javaField.toLowerCase()) | ||
| 736 | +#if (($column.createOperation || $column.updateOperation) && !$column.nullable && !$column.primaryKey | ||
| 737 | + && $column.id != $subJoinColumn.id && $javaFieldLower != "createtime" && $javaFieldLower != "updatetime") | ||
| 738 | + if (isEmptySubValue(${subClassNameVar}.value.${column.javaField})) { | ||
| 739 | + toast.warning('${subTable.classComment}的${column.columnComment}不能为空') | ||
| 740 | + return false | ||
| 741 | + } | ||
| 742 | +#end | ||
| 743 | +#end | ||
| 744 | + } | ||
| 745 | +#end | ||
| 746 | +#end | ||
| 747 | + return true | ||
| 748 | +} | ||
| 749 | + | ||
| 750 | +#end | ||
| 275 | /** 返回上一页 */ | 751 | /** 返回上一页 */ |
| 276 | function handleBack() { | 752 | function handleBack() { |
| 277 | navigateBackPlus('/pages-${table.moduleName}/${table.businessName}/index') | 753 | navigateBackPlus('/pages-${table.moduleName}/${table.businessName}/index') |
| @@ -282,7 +758,34 @@ async function getDetail() { | @@ -282,7 +758,34 @@ async function getDetail() { | ||
| 282 | if (!props.id) { | 758 | if (!props.id) { |
| 283 | return | 759 | return |
| 284 | } | 760 | } |
| 761 | +#if ($table.templateType == 10 || $table.templateType == 12) | ||
| 762 | + const [data#foreach ($subTable in $subTables)#set ($index = $foreach.count - 1), ${subClassNameVars.get($index)}Data#end] = await Promise.all([ | ||
| 763 | + get${simpleClassName}(props.id), | ||
| 764 | +#foreach ($subTable in $subTables) | ||
| 765 | +#set ($index = $foreach.count - 1) | ||
| 766 | +#set ($subSimpleClassName = $subSimpleClassNames.get($index)) | ||
| 767 | +#set ($subJoinColumn = $subJoinColumns.get($index)) | ||
| 768 | +#set ($SubJoinColumnName = $subJoinColumn.javaField.substring(0,1).toUpperCase() + $subJoinColumn.javaField.substring(1)) | ||
| 769 | +#if ($subTable.subJoinMany) | ||
| 770 | + get${subSimpleClassName}ListBy${SubJoinColumnName}(props.id), | ||
| 771 | +#else | ||
| 772 | + get${subSimpleClassName}By${SubJoinColumnName}(props.id), | ||
| 773 | +#end | ||
| 774 | +#end | ||
| 775 | + ]) | ||
| 776 | + formData.value = data | ||
| 777 | +#foreach ($subTable in $subTables) | ||
| 778 | +#set ($index = $foreach.count - 1) | ||
| 779 | +#set ($subClassNameVar = $subClassNameVars.get($index)) | ||
| 780 | +#if ($subTable.subJoinMany) | ||
| 781 | + ${subClassNameVar}s.value = ${subClassNameVar}Data || [] | ||
| 782 | +#else | ||
| 783 | + ${subClassNameVar}.value = ${subClassNameVar}Data || null | ||
| 784 | +#end | ||
| 785 | +#end | ||
| 786 | +#else | ||
| 285 | formData.value = await get${simpleClassName}(props.id) | 787 | formData.value = await get${simpleClassName}(props.id) |
| 788 | +#end | ||
| 286 | } | 789 | } |
| 287 | 790 | ||
| 288 | /** 提交表单 */ | 791 | /** 提交表单 */ |
| @@ -291,14 +794,31 @@ async function handleSubmit() { | @@ -291,14 +794,31 @@ async function handleSubmit() { | ||
| 291 | if (!valid) { | 794 | if (!valid) { |
| 292 | return | 795 | return |
| 293 | } | 796 | } |
| 797 | +#if ($table.templateType == 10 || $table.templateType == 12) | ||
| 798 | + if (!validateSubTables()) { | ||
| 799 | + return | ||
| 800 | + } | ||
| 801 | +#end | ||
| 294 | 802 | ||
| 295 | formLoading.value = true | 803 | formLoading.value = true |
| 296 | try { | 804 | try { |
| 805 | +#if ($table.templateType == 10 || $table.templateType == 12) | ||
| 806 | + const data = { | ||
| 807 | + ...formData.value, | ||
| 808 | +#foreach ($subTable in $subTables) | ||
| 809 | +#set ($index = $foreach.count - 1) | ||
| 810 | +#set ($subClassNameVar = $subClassNameVars.get($index)) | ||
| 811 | + ${subClassNameVar}#if($subTable.subJoinMany)s#end: ${subClassNameVar}#if($subTable.subJoinMany)s#end.value, | ||
| 812 | +#end | ||
| 813 | + } as ${simpleClassName} | ||
| 814 | +#else | ||
| 815 | + const data = formData.value as ${simpleClassName} | ||
| 816 | +#end | ||
| 297 | if (props.id) { | 817 | if (props.id) { |
| 298 | - await update${simpleClassName}(formData.value) | 818 | + await update${simpleClassName}(data) |
| 299 | toast.success('修改成功') | 819 | toast.success('修改成功') |
| 300 | } else { | 820 | } else { |
| 301 | - await create${simpleClassName}(formData.value) | 821 | + await create${simpleClassName}(data) |
| 302 | toast.success('新增成功') | 822 | toast.success('新增成功') |
| 303 | } | 823 | } |
| 304 | uni.$emit('${table.moduleName}:${table.businessName}:reload') | 824 | uni.$emit('${table.moduleName}:${table.businessName}:reload') |
| @@ -309,7 +829,16 @@ async function handleSubmit() { | @@ -309,7 +829,16 @@ async function handleSubmit() { | ||
| 309 | } | 829 | } |
| 310 | 830 | ||
| 311 | /** 初始化 */ | 831 | /** 初始化 */ |
| 312 | -onMounted(() => { | 832 | +onMounted(async () => { |
| 833 | +#if ($table.templateType == 2) | ||
| 834 | + const data = await get${simpleClassName}List() | ||
| 835 | + treeData.value = [{ | ||
| 836 | + ${primaryColumn.javaField}: 0, | ||
| 837 | + ${treeNameColumn.javaField}: '顶级${table.classComment}', | ||
| 838 | + ${treeParentColumn.javaField}: 0, | ||
| 839 | + children: handleTree(data, '${primaryColumn.javaField}', '${treeParentColumn.javaField}'), | ||
| 840 | + } as ${simpleClassName}] | ||
| 841 | +#end | ||
| 313 | getDetail() | 842 | getDetail() |
| 314 | }) | 843 | }) |
| 315 | </script> | 844 | </script> |
yudao-module-infra/src/main/resources/codegen/vue3_admin_uniapp/views/form_sub_erp.vue.vm
0 → 100644
| 1 | +#set ($subTable = $subTables.get($subIndex)) | ||
| 2 | +#set ($subColumns = $subColumnsList.get($subIndex)) | ||
| 3 | +#set ($subSimpleClassName = $subSimpleClassNames.get($subIndex)) | ||
| 4 | +#set ($subPrimaryColumn = $subPrimaryColumns.get($subIndex)) | ||
| 5 | +#set ($subJoinColumn = $subJoinColumns.get($subIndex)) | ||
| 6 | +#set ($subPrimaryJavaType = $subPrimaryColumn.javaType.toLowerCase()) | ||
| 7 | +#if(${subPrimaryJavaType} == "long" || ${subPrimaryJavaType} == "integer" || ${subPrimaryJavaType} == "short" || ${subPrimaryJavaType} == "float" || ${subPrimaryJavaType} == "double" || ${subPrimaryJavaType} == "bigdecimal" || ${subPrimaryJavaType} == "byte") | ||
| 8 | +#set ($subPrimaryTsType = "number") | ||
| 9 | +#else | ||
| 10 | +#set ($subPrimaryTsType = "string") | ||
| 11 | +#end | ||
| 12 | +#set ($subJoinJavaType = $subJoinColumn.javaType.toLowerCase()) | ||
| 13 | +#if(${subJoinJavaType} == "long" || ${subJoinJavaType} == "integer" || ${subJoinJavaType} == "short" || ${subJoinJavaType} == "float" || ${subJoinJavaType} == "double" || ${subJoinJavaType} == "bigdecimal" || ${subJoinJavaType} == "byte") | ||
| 14 | +#set ($subJoinTsType = "number") | ||
| 15 | +#else | ||
| 16 | +#set ($subJoinTsType = "string") | ||
| 17 | +#end | ||
| 18 | +#set ($hasDict = 0) | ||
| 19 | +#set ($hasBoolDict = 0) | ||
| 20 | +#set ($hasDate = 0) | ||
| 21 | +#set ($hasDateTime = 0) | ||
| 22 | +#foreach ($column in $subColumns) | ||
| 23 | +#if (($column.createOperation || $column.updateOperation) && !$column.primaryKey && $column.id != $subJoinColumn.id) | ||
| 24 | +#if (($column.htmlType == "select" || $column.htmlType == "radio") && $column.dictType && "" != $column.dictType) | ||
| 25 | +#set ($hasDict = 1) | ||
| 26 | +#if ($column.javaType == "Boolean") | ||
| 27 | +#set ($hasBoolDict = 1) | ||
| 28 | +#end | ||
| 29 | +#end | ||
| 30 | +#if (${column.javaType.toLowerCase()} == "date" || ${column.javaType.toLowerCase()} == "localdate") | ||
| 31 | +#set ($hasDate = 1) | ||
| 32 | +#end | ||
| 33 | +#if (${column.javaType.toLowerCase()} == "localdatetime") | ||
| 34 | +#set ($hasDateTime = 1) | ||
| 35 | +#end | ||
| 36 | +#end | ||
| 37 | +#end | ||
| 38 | +<template> | ||
| 39 | + <view class="yd-page-container"> | ||
| 40 | + <!-- 顶部导航栏 --> | ||
| 41 | + <wd-navbar :title="getTitle" left-arrow placeholder safe-area-inset-top fixed @click-left="handleBack" /> | ||
| 42 | + | ||
| 43 | + <!-- 表单区域 --> | ||
| 44 | + <wd-form ref="formRef" :model="formData" :schema="formSchema"> | ||
| 45 | + <wd-cell-group border> | ||
| 46 | +#foreach ($column in $subColumns) | ||
| 47 | +#if (($column.createOperation || $column.updateOperation) && !$column.primaryKey && $column.id != $subJoinColumn.id) | ||
| 48 | +#set ($javaType = $column.javaType.toLowerCase()) | ||
| 49 | +#if (($column.htmlType == "select" || $column.htmlType == "radio") && $column.dictType && "" != $column.dictType) | ||
| 50 | + <yd-form-picker | ||
| 51 | + v-model="formData.${column.javaField}" | ||
| 52 | + label="${column.columnComment}" | ||
| 53 | + label-width="180rpx" | ||
| 54 | + prop="${column.javaField}" | ||
| 55 | +#if ($column.javaType == "Boolean") | ||
| 56 | + :columns="getBoolDictOptions(DICT_TYPE.${column.dictType.toUpperCase()})" | ||
| 57 | +#else | ||
| 58 | + :dict-type="DICT_TYPE.${column.dictType.toUpperCase()}" | ||
| 59 | +#if ($column.javaType == "String") | ||
| 60 | + dict-kind="str" | ||
| 61 | +#end | ||
| 62 | +#end | ||
| 63 | + placeholder="请选择${column.columnComment}" | ||
| 64 | + /> | ||
| 65 | +#elseif ($javaType == "date" || $javaType == "localdate" || $javaType == "localdatetime") | ||
| 66 | +#set ($pickerType = "date") | ||
| 67 | +#if ($javaType == "localdatetime") | ||
| 68 | +#set ($pickerType = "datetime") | ||
| 69 | +#end | ||
| 70 | + <wd-form-item | ||
| 71 | + title="${column.columnComment}" | ||
| 72 | + title-width="180rpx" | ||
| 73 | + prop="${column.javaField}" | ||
| 74 | + is-link | ||
| 75 | +#if ($javaType == "localdatetime") | ||
| 76 | + :value="formData.${column.javaField} ? formatDateTime(formData.${column.javaField}) : ''" | ||
| 77 | +#else | ||
| 78 | + :value="formData.${column.javaField} ? formatDate(formData.${column.javaField}) : ''" | ||
| 79 | +#end | ||
| 80 | + placeholder="请选择${column.columnComment}" | ||
| 81 | + @click="${column.javaField}Visible = true" | ||
| 82 | + /> | ||
| 83 | + <wd-datetime-picker | ||
| 84 | + v-model:visible="${column.javaField}Visible" | ||
| 85 | + :model-value="formData.${column.javaField} || Date.now()" | ||
| 86 | + type="${pickerType}" | ||
| 87 | + @update:model-value="value => formData.${column.javaField} = value" | ||
| 88 | + /> | ||
| 89 | +#else | ||
| 90 | + <wd-form-item title="${column.columnComment}" title-width="180rpx" prop="${column.javaField}"> | ||
| 91 | +#if ($javaType == "boolean") | ||
| 92 | + <wd-switch v-model="formData.${column.javaField}" /> | ||
| 93 | +#elseif ($javaType == "long" || $javaType == "integer" || $javaType == "short" || $javaType == "float" || $javaType == "double" || $javaType == "bigdecimal" || $javaType == "byte") | ||
| 94 | + <wd-input-number | ||
| 95 | + v-model="formData.${column.javaField}" | ||
| 96 | +#if($column.nullable) | ||
| 97 | + allow-null | ||
| 98 | +#end | ||
| 99 | +#if ($javaType == "float" || $javaType == "double" || $javaType == "bigdecimal") | ||
| 100 | + :precision="2" | ||
| 101 | + input-type="number" | ||
| 102 | +#end | ||
| 103 | + :min="0" | ||
| 104 | + /> | ||
| 105 | +#elseif ($column.htmlType == "textarea" || $column.htmlType == "editor") | ||
| 106 | + <wd-textarea | ||
| 107 | + v-model="formData.${column.javaField}" | ||
| 108 | + clearable | ||
| 109 | + placeholder="请输入${column.columnComment}" | ||
| 110 | +#if ($column.htmlType == "editor") | ||
| 111 | + :maxlength="2000" | ||
| 112 | +#else | ||
| 113 | + :maxlength="200" | ||
| 114 | +#end | ||
| 115 | + /> | ||
| 116 | +#elseif ($column.htmlType == "imageUpload") | ||
| 117 | + <yd-upload-img v-model="formData.${column.javaField}" directory="${table.moduleName}/${table.businessName}" /> | ||
| 118 | +#elseif ($column.htmlType == "fileUpload") | ||
| 119 | + <yd-upload-file v-model="formData.${column.javaField}" :limit="1" directory="${table.moduleName}/${table.businessName}" /> | ||
| 120 | +#else | ||
| 121 | + <wd-input v-model="formData.${column.javaField}" clearable placeholder="请输入${column.columnComment}" /> | ||
| 122 | +#end | ||
| 123 | + </wd-form-item> | ||
| 124 | +#end | ||
| 125 | +#end | ||
| 126 | +#end | ||
| 127 | + </wd-cell-group> | ||
| 128 | + </wd-form> | ||
| 129 | + | ||
| 130 | + <!-- 底部保存按钮 --> | ||
| 131 | + <view class="yd-detail-footer"> | ||
| 132 | + <wd-button type="primary" block :loading="formLoading" @click="handleSubmit"> | ||
| 133 | + 保存 | ||
| 134 | + </wd-button> | ||
| 135 | + </view> | ||
| 136 | + </view> | ||
| 137 | +</template> | ||
| 138 | + | ||
| 139 | +<script lang="ts" setup> | ||
| 140 | +import type { FormInstance } from '@wot-ui/ui/components/wd-form/types' | ||
| 141 | +import type { ${subSimpleClassName} } from '@/api/${table.moduleName}/${table.businessName}' | ||
| 142 | +import { computed, onMounted, ref } from 'vue' | ||
| 143 | +import { useToast } from '@wot-ui/ui/components/wd-toast' | ||
| 144 | +import { create${subSimpleClassName}, get${subSimpleClassName}, update${subSimpleClassName} } from '@/api/${table.moduleName}/${table.businessName}' | ||
| 145 | +import { delay, navigateBackPlus } from '@/utils' | ||
| 146 | +#if ($hasBoolDict == 1) | ||
| 147 | +import { getBoolDictOptions } from '@/hooks/useDict' | ||
| 148 | +#end | ||
| 149 | +#if ($hasDict == 1) | ||
| 150 | +import { DICT_TYPE } from '@/utils/constants' | ||
| 151 | +#end | ||
| 152 | +#if ($hasDate == 1 || $hasDateTime == 1) | ||
| 153 | +#set ($dateImportNames = "") | ||
| 154 | +#if ($hasDate == 1) | ||
| 155 | +#set ($dateImportNames = "${dateImportNames}formatDate, ") | ||
| 156 | +#end | ||
| 157 | +#if ($hasDateTime == 1) | ||
| 158 | +#set ($dateImportNames = "${dateImportNames}formatDateTime, ") | ||
| 159 | +#end | ||
| 160 | +#set ($dateImportNames = $dateImportNames.trim()) | ||
| 161 | +#set ($dateImportNames = $dateImportNames.substring(0, $dateImportNames.length() - 1)) | ||
| 162 | +import { $dateImportNames } from '@/utils/date' | ||
| 163 | +#end | ||
| 164 | +import { createFormSchema } from '@/utils/wot' | ||
| 165 | + | ||
| 166 | +const props = defineProps<{ | ||
| 167 | + id?: ${subPrimaryTsType} | ||
| 168 | + ${subJoinColumn.javaField}: ${subJoinTsType} | ||
| 169 | +}>() | ||
| 170 | + | ||
| 171 | +definePage({ | ||
| 172 | + style: { navigationBarTitleText: '', navigationStyle: 'custom' }, | ||
| 173 | +}) | ||
| 174 | + | ||
| 175 | +const toast = useToast() | ||
| 176 | +const getTitle = computed(() => props.id ? '编辑${subTable.classComment}' : '新增${subTable.classComment}') | ||
| 177 | +const formLoading = ref(false) // 表单提交状态 | ||
| 178 | +const formData = ref<Partial<${subSimpleClassName}>>({ | ||
| 179 | +#foreach ($column in $subColumns) | ||
| 180 | +#if ($column.primaryKey) | ||
| 181 | + ${column.javaField}: undefined, | ||
| 182 | +#elseif ($column.id == $subJoinColumn.id) | ||
| 183 | + ${column.javaField}: props.${column.javaField}, | ||
| 184 | +#elseif ($column.createOperation || $column.updateOperation) | ||
| 185 | +#set ($javaType = $column.javaType.toLowerCase()) | ||
| 186 | +#if((($column.htmlType == "select" || $column.htmlType == "radio") && $column.dictType && "" != $column.dictType) || $javaType == "date" || $javaType == "localdate" || $javaType == "localdatetime") | ||
| 187 | + ${column.javaField}: undefined, | ||
| 188 | +#else | ||
| 189 | + ${column.javaField}:#if($column.nullable) undefined#elseif($javaType == "long" || $javaType == "integer" || $javaType == "short" || $javaType == "float" || $javaType == "double" || $javaType == "bigdecimal" || $javaType == "byte") 0#elseif($javaType == "boolean") false#else ''#end, | ||
| 190 | +#end | ||
| 191 | +#end | ||
| 192 | +#end | ||
| 193 | +}) // 表单数据 | ||
| 194 | +#foreach ($column in $subColumns) | ||
| 195 | +#if (($column.createOperation || $column.updateOperation) && !$column.primaryKey && $column.id != $subJoinColumn.id | ||
| 196 | + && (${column.javaType.toLowerCase()} == "date" || ${column.javaType.toLowerCase()} == "localdate" || ${column.javaType.toLowerCase()} == "localdatetime")) | ||
| 197 | +const ${column.javaField}Visible = ref(false) // ${column.columnComment}选择器状态 | ||
| 198 | +#end | ||
| 199 | +#end | ||
| 200 | +const formSchema = createFormSchema({ | ||
| 201 | +#foreach ($column in $subColumns) | ||
| 202 | +#if (($column.createOperation || $column.updateOperation) && !$column.nullable && !$column.primaryKey) | ||
| 203 | + ${column.javaField}: [{ required: true, message: '${column.columnComment}不能为空' }], | ||
| 204 | +#end | ||
| 205 | +#end | ||
| 206 | +}) // 表单校验规则 | ||
| 207 | +const formRef = ref<FormInstance>() // 表单组件引用 | ||
| 208 | + | ||
| 209 | +/** 返回上一页 */ | ||
| 210 | +function handleBack() { | ||
| 211 | + navigateBackPlus() | ||
| 212 | +} | ||
| 213 | + | ||
| 214 | +/** 加载${subTable.classComment}详情 */ | ||
| 215 | +async function getDetail() { | ||
| 216 | + if (props.id) { | ||
| 217 | + formData.value = await get${subSimpleClassName}(props.id) | ||
| 218 | + } | ||
| 219 | +} | ||
| 220 | + | ||
| 221 | +/** 提交表单 */ | ||
| 222 | +async function handleSubmit() { | ||
| 223 | + const { valid } = await formRef.value.validate() | ||
| 224 | + if (!valid) { | ||
| 225 | + return | ||
| 226 | + } | ||
| 227 | + formLoading.value = true | ||
| 228 | + try { | ||
| 229 | + const data = { | ||
| 230 | + ...formData.value, | ||
| 231 | + ${subJoinColumn.javaField}: props.${subJoinColumn.javaField} ?? formData.value.${subJoinColumn.javaField}, | ||
| 232 | + } as ${subSimpleClassName} | ||
| 233 | + if (props.id) { | ||
| 234 | + await update${subSimpleClassName}(data) | ||
| 235 | + toast.success('修改成功') | ||
| 236 | + } else { | ||
| 237 | + await create${subSimpleClassName}(data) | ||
| 238 | + toast.success('新增成功') | ||
| 239 | + } | ||
| 240 | + delay(handleBack) | ||
| 241 | + } finally { | ||
| 242 | + formLoading.value = false | ||
| 243 | + } | ||
| 244 | +} | ||
| 245 | + | ||
| 246 | +/** 初始化 */ | ||
| 247 | +onMounted(() => { | ||
| 248 | + getDetail() | ||
| 249 | +}) | ||
| 250 | +</script> |
yudao-module-infra/src/main/resources/codegen/vue3_admin_uniapp/views/index_tree.vue.vm
0 → 100644
| 1 | +<template> | ||
| 2 | + <view class="yd-page-container"> | ||
| 3 | + <!-- 顶部导航栏 --> | ||
| 4 | + <wd-navbar | ||
| 5 | + title="${table.classComment}管理" | ||
| 6 | + left-arrow placeholder safe-area-inset-top fixed | ||
| 7 | + @click-left="handleBack" | ||
| 8 | + /> | ||
| 9 | + | ||
| 10 | + <!-- 搜索组件 --> | ||
| 11 | + <SearchForm @search="handleQuery" @reset="handleReset" /> | ||
| 12 | + | ||
| 13 | + <!-- 面包屑导航 --> | ||
| 14 | + <Breadcrumb ref="breadcrumbRef" v-model="currentParentId" /> | ||
| 15 | + | ||
| 16 | + <!-- ${table.classComment}列表 --> | ||
| 17 | + <view class="p-24rpx"> | ||
| 18 | + <view | ||
| 19 | + v-for="item in currentList" | ||
| 20 | + :key="item.${primaryColumn.javaField}" | ||
| 21 | + class="mb-24rpx overflow-hidden rounded-12rpx bg-white shadow-sm" | ||
| 22 | + > | ||
| 23 | + <view class="p-24rpx" @click="handleDetail(item)"> | ||
| 24 | + <view class="flex items-center justify-between"> | ||
| 25 | + <view class="flex min-w-0 items-center"> | ||
| 26 | + <view class="mr-16rpx h-48rpx w-48rpx shrink-0 flex items-center justify-center rounded-8rpx bg-[#1890ff]"> | ||
| 27 | + <wd-icon name="folder" size="20px" color="#fff" /> | ||
| 28 | + </view> | ||
| 29 | + <view class="truncate text-32rpx text-[#333] font-semibold"> | ||
| 30 | + {{ item.${treeNameColumn.javaField} }} | ||
| 31 | + </view> | ||
| 32 | + </view> | ||
| 33 | + <view | ||
| 34 | + v-if="item.children?.length" | ||
| 35 | + class="ml-16rpx shrink-0 flex items-center" | ||
| 36 | + @click.stop="handleEnterChildren(item)" | ||
| 37 | + > | ||
| 38 | + <text class="text-24rpx text-[#1890ff]">子级 ({{ item.children.length }})</text> | ||
| 39 | + <wd-icon name="arrow-right" size="12px" color="#1890ff" /> | ||
| 40 | + </view> | ||
| 41 | + </view> | ||
| 42 | + </view> | ||
| 43 | + </view> | ||
| 44 | + | ||
| 45 | + <!-- 空状态 --> | ||
| 46 | + <view v-if="!loading && currentList.length === 0" class="py-100rpx text-center"> | ||
| 47 | + <wd-empty icon="content" tip="暂无${table.classComment}数据" /> | ||
| 48 | + </view> | ||
| 49 | + </view> | ||
| 50 | + | ||
| 51 | + <!-- 新增按钮 --> | ||
| 52 | + <wd-fab | ||
| 53 | + v-if="hasAccessByCodes(['${permissionPrefix}:create'])" | ||
| 54 | + position="right-bottom" | ||
| 55 | + type="primary" | ||
| 56 | + :expandable="false" | ||
| 57 | + @click="handleAdd" | ||
| 58 | + /> | ||
| 59 | + </view> | ||
| 60 | +</template> | ||
| 61 | + | ||
| 62 | +<script lang="ts" setup> | ||
| 63 | +#set ($primaryJavaType = $primaryColumn.javaType.toLowerCase()) | ||
| 64 | +#if(${primaryJavaType} == "long" || ${primaryJavaType} == "integer" || ${primaryJavaType} == "short" || ${primaryJavaType} == "float" || ${primaryJavaType} == "double" || ${primaryJavaType} == "bigdecimal" || ${primaryJavaType} == "byte") | ||
| 65 | +#set ($primaryTsType = "number") | ||
| 66 | +#else | ||
| 67 | +#set ($primaryTsType = "string") | ||
| 68 | +#end | ||
| 69 | +import type { ${simpleClassName} } from '@/api/${table.moduleName}/${table.businessName}' | ||
| 70 | +import { onUnload } from '@dcloudio/uni-app' | ||
| 71 | +import { computed, onMounted, ref } from 'vue' | ||
| 72 | +import { get${simpleClassName}List } from '@/api/${table.moduleName}/${table.businessName}' | ||
| 73 | +import { useAccess } from '@/hooks/useAccess' | ||
| 74 | +import { navigateBackPlus } from '@/utils' | ||
| 75 | +import { findChildren, handleTree } from '@/utils/tree' | ||
| 76 | +import Breadcrumb from './components/breadcrumb.vue' | ||
| 77 | +import SearchForm from './components/search-form.vue' | ||
| 78 | + | ||
| 79 | +definePage({ | ||
| 80 | + style: { | ||
| 81 | + navigationBarTitleText: '', | ||
| 82 | + navigationStyle: 'custom', | ||
| 83 | + }, | ||
| 84 | +}) | ||
| 85 | + | ||
| 86 | +const { hasAccessByCodes } = useAccess() | ||
| 87 | +const loading = ref(false) // 列表加载状态 | ||
| 88 | +const list = ref<${simpleClassName}[]>([]) // 完整树形列表 | ||
| 89 | +const currentParentId = ref<${primaryTsType}>(0) // 当前层级的父节点编号 | ||
| 90 | +const currentList = computed(() => currentParentId.value === 0 | ||
| 91 | + ? list.value | ||
| 92 | + : findChildren(list.value, currentParentId.value, '${primaryColumn.javaField}')) // 当前层级列表 | ||
| 93 | +const breadcrumbRef = ref<InstanceType<typeof Breadcrumb>>() // 面包屑组件引用 | ||
| 94 | +const queryParams = ref<Record<string, any>>({}) // 查询参数 | ||
| 95 | + | ||
| 96 | +/** 返回上一页或上一层级 */ | ||
| 97 | +function handleBack() { | ||
| 98 | + if (!breadcrumbRef.value?.back()) { | ||
| 99 | + navigateBackPlus() | ||
| 100 | + } | ||
| 101 | +} | ||
| 102 | + | ||
| 103 | +/** 进入子层级 */ | ||
| 104 | +function handleEnterChildren(item: ${simpleClassName}) { | ||
| 105 | + breadcrumbRef.value?.enter({ id: item.${primaryColumn.javaField}!, name: item.${treeNameColumn.javaField} }) | ||
| 106 | +} | ||
| 107 | + | ||
| 108 | +/** 查询${table.classComment}列表 */ | ||
| 109 | +async function getList() { | ||
| 110 | + loading.value = true | ||
| 111 | + try { | ||
| 112 | + list.value = handleTree(await get${simpleClassName}List(queryParams.value), '${primaryColumn.javaField}', '${treeParentColumn.javaField}') | ||
| 113 | + } finally { | ||
| 114 | + loading.value = false | ||
| 115 | + } | ||
| 116 | +} | ||
| 117 | + | ||
| 118 | +/** 搜索按钮操作 */ | ||
| 119 | +function handleQuery(data?: Record<string, any>) { | ||
| 120 | + queryParams.value = { ...data } | ||
| 121 | + currentParentId.value = 0 | ||
| 122 | + breadcrumbRef.value?.reset() | ||
| 123 | + getList() | ||
| 124 | +} | ||
| 125 | + | ||
| 126 | +/** 重置按钮操作 */ | ||
| 127 | +function handleReset() { | ||
| 128 | + handleQuery() | ||
| 129 | +} | ||
| 130 | + | ||
| 131 | +/** 新增${table.classComment} */ | ||
| 132 | +function handleAdd() { | ||
| 133 | + uni.navigateTo({ url: `/pages-${table.moduleName}/${table.businessName}/form/index?${treeParentColumn.javaField}=#[[${]]#currentParentId.value#[[}]]#` }) | ||
| 134 | +} | ||
| 135 | + | ||
| 136 | +/** 查看详情 */ | ||
| 137 | +function handleDetail(item: ${simpleClassName}) { | ||
| 138 | + uni.navigateTo({ url: `/pages-${table.moduleName}/${table.businessName}/detail/index?id=#[[${]]#item.${primaryColumn.javaField}#[[}]]#` }) | ||
| 139 | +} | ||
| 140 | + | ||
| 141 | +/** 初始化 */ | ||
| 142 | +onMounted(() => { | ||
| 143 | + getList() | ||
| 144 | + uni.$on('${table.moduleName}:${table.businessName}:reload', getList) | ||
| 145 | +}) | ||
| 146 | + | ||
| 147 | +/** 卸载 */ | ||
| 148 | +onUnload(() => { | ||
| 149 | + uni.$off('${table.moduleName}:${table.businessName}:reload', getList) | ||
| 150 | +}) | ||
| 151 | +</script> |
yudao-module-infra/src/main/resources/codegen/vue3_vben5_antdv_next/general/views/form.vue.vm
| 1 | +#set ($selectOptionEnable = false) | ||
| 2 | +#foreach ($column in $columns) | ||
| 3 | + #if (($column.createOperation || $column.updateOperation) && $column.htmlType == "select") | ||
| 4 | + #set ($selectOptionEnable = true) | ||
| 5 | + #end | ||
| 6 | +#end | ||
| 1 | <script lang="ts" setup> | 7 | <script lang="ts" setup> |
| 2 | -import type { Rule } from 'antdv-next/es/form'; | 8 | +import type { Rule } from 'antdv-next'; |
| 3 | import type { ${simpleClassName}Api } from '#/api/${table.moduleName}/${table.businessName}'; | 9 | import type { ${simpleClassName}Api } from '#/api/${table.moduleName}/${table.businessName}'; |
| 4 | 10 | ||
| 5 | import { computed, ref } from 'vue'; | 11 | import { computed, ref } from 'vue'; |
| @@ -9,7 +15,7 @@ import { DICT_TYPE } from '@vben/constants'; | @@ -9,7 +15,7 @@ import { DICT_TYPE } from '@vben/constants'; | ||
| 9 | import { getDictOptions } from '@vben/hooks'; | 15 | import { getDictOptions } from '@vben/hooks'; |
| 10 | import { Tinymce as RichTextarea } from '#/components/tinymce'; | 16 | import { Tinymce as RichTextarea } from '#/components/tinymce'; |
| 11 | import { ImageUpload, FileUpload } from "#/components/upload"; | 17 | import { ImageUpload, FileUpload } from "#/components/upload"; |
| 12 | -import { message, Tabs, Form, Input, TextArea, Select, RadioGroup, Radio, CheckboxGroup, Checkbox, DatePicker, TreeSelect } from 'antdv-next'; | 18 | +import { Checkbox, CheckboxGroup, DatePicker, Form, FormItem, Input, message, Radio, RadioGroup, Select#if ($selectOptionEnable), SelectOption#end#if ( $table.templateType == 10 || $table.templateType == 12 ), TabPane, Tabs#end, TextArea, TreeSelect } from 'antdv-next'; |
| 13 | #if($table.templateType == 2)## 树表需要导入这些 | 19 | #if($table.templateType == 2)## 树表需要导入这些 |
| 14 | import { get${simpleClassName}List } from '#/api/${table.moduleName}/${table.businessName}'; | 20 | import { get${simpleClassName}List } from '#/api/${table.moduleName}/${table.businessName}'; |
| 15 | import { handleTree } from '@vben/utils' | 21 | import { handleTree } from '@vben/utils' |
| @@ -204,7 +210,7 @@ const [Modal, modalApi] = useVbenModal({ | @@ -204,7 +210,7 @@ const [Modal, modalApi] = useVbenModal({ | ||
| 204 | #set ($dictMethod = "boolean") | 210 | #set ($dictMethod = "boolean") |
| 205 | #end | 211 | #end |
| 206 | #if ( $table.templateType == 2 && $column.id == $treeParentColumn.id ) | 212 | #if ( $table.templateType == 2 && $column.id == $treeParentColumn.id ) |
| 207 | - <Form.Item label="${comment}" name="${javaField}"> | 213 | + <FormItem label="${comment}" name="${javaField}"> |
| 208 | <TreeSelect | 214 | <TreeSelect |
| 209 | v-model:value="formData.${javaField}" | 215 | v-model:value="formData.${javaField}" |
| 210 | :treeData="${classNameVar}Tree" | 216 | :treeData="${classNameVar}Tree" |
| @@ -225,25 +231,25 @@ const [Modal, modalApi] = useVbenModal({ | @@ -225,25 +231,25 @@ const [Modal, modalApi] = useVbenModal({ | ||
| 225 | treeDefaultExpandAll | 231 | treeDefaultExpandAll |
| 226 | placeholder="请选择${comment}" | 232 | placeholder="请选择${comment}" |
| 227 | /> | 233 | /> |
| 228 | - </Form.Item> | 234 | + </FormItem> |
| 229 | #elseif ($column.htmlType == "input" && !$column.primaryKey)## 忽略主键,不用在表单里 | 235 | #elseif ($column.htmlType == "input" && !$column.primaryKey)## 忽略主键,不用在表单里 |
| 230 | - <Form.Item label="${comment}" name="${javaField}"> | 236 | + <FormItem label="${comment}" name="${javaField}"> |
| 231 | <Input v-model:value="formData.${javaField}" placeholder="请输入${comment}" /> | 237 | <Input v-model:value="formData.${javaField}" placeholder="请输入${comment}" /> |
| 232 | - </Form.Item> | 238 | + </FormItem> |
| 233 | #elseif($column.htmlType == "imageUpload")## 图片上传 | 239 | #elseif($column.htmlType == "imageUpload")## 图片上传 |
| 234 | - <Form.Item label="${comment}" name="${javaField}"> | 240 | + <FormItem label="${comment}" name="${javaField}"> |
| 235 | <ImageUpload v-model:value="formData.${javaField}" /> | 241 | <ImageUpload v-model:value="formData.${javaField}" /> |
| 236 | - </Form.Item> | 242 | + </FormItem> |
| 237 | #elseif($column.htmlType == "fileUpload")## 文件上传 | 243 | #elseif($column.htmlType == "fileUpload")## 文件上传 |
| 238 | - <Form.Item label="${comment}" name="${javaField}"> | 244 | + <FormItem label="${comment}" name="${javaField}"> |
| 239 | <FileUpload v-model:value="formData.${javaField}" /> | 245 | <FileUpload v-model:value="formData.${javaField}" /> |
| 240 | - </Form.Item> | 246 | + </FormItem> |
| 241 | #elseif($column.htmlType == "editor")## 文本编辑器 | 247 | #elseif($column.htmlType == "editor")## 文本编辑器 |
| 242 | - <Form.Item label="${comment}" name="${javaField}"> | 248 | + <FormItem label="${comment}" name="${javaField}"> |
| 243 | <RichTextarea v-model="formData.${javaField}" height="500px" /> | 249 | <RichTextarea v-model="formData.${javaField}" height="500px" /> |
| 244 | - </Form.Item> | 250 | + </FormItem> |
| 245 | #elseif($column.htmlType == "select")## 下拉框 | 251 | #elseif($column.htmlType == "select")## 下拉框 |
| 246 | - <Form.Item label="${comment}" name="${javaField}"> | 252 | + <FormItem label="${comment}" name="${javaField}"> |
| 247 | <Select v-model:value="formData.${javaField}" placeholder="请选择${comment}"> | 253 | <Select v-model:value="formData.${javaField}" placeholder="请选择${comment}"> |
| 248 | #if ("" != $dictType)## 有数据字典 | 254 | #if ("" != $dictType)## 有数据字典 |
| 249 | <SelectOption | 255 | <SelectOption |
| @@ -257,9 +263,9 @@ const [Modal, modalApi] = useVbenModal({ | @@ -257,9 +263,9 @@ const [Modal, modalApi] = useVbenModal({ | ||
| 257 | <SelectOption label="请选择字典生成" value="" /> | 263 | <SelectOption label="请选择字典生成" value="" /> |
| 258 | #end | 264 | #end |
| 259 | </Select> | 265 | </Select> |
| 260 | - </Form.Item> | 266 | + </FormItem> |
| 261 | #elseif($column.htmlType == "checkbox")## 多选框 | 267 | #elseif($column.htmlType == "checkbox")## 多选框 |
| 262 | - <Form.Item label="${comment}" name="${javaField}"> | 268 | + <FormItem label="${comment}" name="${javaField}"> |
| 263 | <CheckboxGroup v-model:value="formData.${javaField}"> | 269 | <CheckboxGroup v-model:value="formData.${javaField}"> |
| 264 | #if ("" != $dictType)## 有数据字典 | 270 | #if ("" != $dictType)## 有数据字典 |
| 265 | <Checkbox | 271 | <Checkbox |
| @@ -273,9 +279,9 @@ const [Modal, modalApi] = useVbenModal({ | @@ -273,9 +279,9 @@ const [Modal, modalApi] = useVbenModal({ | ||
| 273 | <Checkbox label="请选择字典生成" /> | 279 | <Checkbox label="请选择字典生成" /> |
| 274 | #end | 280 | #end |
| 275 | </CheckboxGroup> | 281 | </CheckboxGroup> |
| 276 | - </Form.Item> | 282 | + </FormItem> |
| 277 | #elseif($column.htmlType == "radio")## 单选框 | 283 | #elseif($column.htmlType == "radio")## 单选框 |
| 278 | - <Form.Item label="${comment}" name="${javaField}"> | 284 | + <FormItem label="${comment}" name="${javaField}"> |
| 279 | <RadioGroup v-model:value="formData.${javaField}"> | 285 | <RadioGroup v-model:value="formData.${javaField}"> |
| 280 | #if ("" != $dictType)## 有数据字典 | 286 | #if ("" != $dictType)## 有数据字典 |
| 281 | <Radio | 287 | <Radio |
| @@ -289,19 +295,19 @@ const [Modal, modalApi] = useVbenModal({ | @@ -289,19 +295,19 @@ const [Modal, modalApi] = useVbenModal({ | ||
| 289 | <Radio value="1">请选择字典生成</Radio> | 295 | <Radio value="1">请选择字典生成</Radio> |
| 290 | #end | 296 | #end |
| 291 | </RadioGroup> | 297 | </RadioGroup> |
| 292 | - </Form.Item> | 298 | + </FormItem> |
| 293 | #elseif($column.htmlType == "datetime")## 时间框 | 299 | #elseif($column.htmlType == "datetime")## 时间框 |
| 294 | - <Form.Item label="${comment}" name="${javaField}"> | 300 | + <FormItem label="${comment}" name="${javaField}"> |
| 295 | <DatePicker | 301 | <DatePicker |
| 296 | v-model:value="formData.${javaField}" | 302 | v-model:value="formData.${javaField}" |
| 297 | valueFormat="x" | 303 | valueFormat="x" |
| 298 | placeholder="选择${comment}" | 304 | placeholder="选择${comment}" |
| 299 | /> | 305 | /> |
| 300 | - </Form.Item> | 306 | + </FormItem> |
| 301 | #elseif($column.htmlType == "textarea")## 文本框 | 307 | #elseif($column.htmlType == "textarea")## 文本框 |
| 302 | - <Form.Item label="${comment}" name="${javaField}"> | 308 | + <FormItem label="${comment}" name="${javaField}"> |
| 303 | <TextArea v-model:value="formData.${javaField}" placeholder="请输入${comment}" /> | 309 | <TextArea v-model:value="formData.${javaField}" placeholder="请输入${comment}" /> |
| 304 | - </Form.Item> | 310 | + </FormItem> |
| 305 | #end | 311 | #end |
| 306 | #end | 312 | #end |
| 307 | #end | 313 | #end |
yudao-module-infra/src/main/resources/codegen/vue3_vben5_antdv_next/general/views/import.vue.vm
| 1 | <script lang="ts" setup> | 1 | <script lang="ts" setup> |
| 2 | -import type { FileType } from 'antdv-next/es/upload/interface'; | ||
| 3 | - | ||
| 4 | import { ref } from 'vue'; | 2 | import { ref } from 'vue'; |
| 5 | 3 | ||
| 6 | import { useVbenModal } from '@vben/common-ui'; | 4 | import { useVbenModal } from '@vben/common-ui'; |
| @@ -43,8 +41,8 @@ const [Modal, modalApi] = useVbenModal({ | @@ -43,8 +41,8 @@ const [Modal, modalApi] = useVbenModal({ | ||
| 43 | }); | 41 | }); |
| 44 | 42 | ||
| 45 | /** 上传前:拦截 antd Upload 的自动上传,文件存到 ref */ | 43 | /** 上传前:拦截 antd Upload 的自动上传,文件存到 ref */ |
| 46 | -function beforeUpload(file: FileType) { | ||
| 47 | - fileRef.value = file as unknown as File; | 44 | +function beforeUpload(file: File) { |
| 45 | + fileRef.value = file; | ||
| 48 | return false; | 46 | return false; |
| 49 | } | 47 | } |
| 50 | 48 |
yudao-module-infra/src/main/resources/codegen/vue3_vben5_antdv_next/general/views/index.vue.vm
| 1 | +#set ($rangePickerEnable = false) | ||
| 2 | +#set ($selectOptionEnable = false) | ||
| 3 | +#foreach ($column in $columns) | ||
| 4 | + #if ($column.listOperation && $column.htmlType == "datetime" && $column.listOperationCondition == "BETWEEN") | ||
| 5 | + #set ($rangePickerEnable = true) | ||
| 6 | + #end | ||
| 7 | + #if ($column.listOperation && ($column.htmlType == "select" || $column.htmlType == "radio" || $column.htmlType == "checkbox")) | ||
| 8 | + #set ($selectOptionEnable = true) | ||
| 9 | + #end | ||
| 10 | +#end | ||
| 1 | <script lang="ts" setup> | 11 | <script lang="ts" setup> |
| 2 | import type { ${simpleClassName}Api } from '#/api/${table.moduleName}/${table.businessName}'; | 12 | import type { ${simpleClassName}Api } from '#/api/${table.moduleName}/${table.businessName}'; |
| 3 | 13 | ||
| @@ -8,12 +18,14 @@ import { DICT_TYPE } from '@vben/constants'; | @@ -8,12 +18,14 @@ import { DICT_TYPE } from '@vben/constants'; | ||
| 8 | import { getDictOptions } from '@vben/hooks'; | 18 | import { getDictOptions } from '@vben/hooks'; |
| 9 | import { useTableToolbar, VbenVxeTableToolbar } from '@vben/plugins/vxe-table'; | 19 | import { useTableToolbar, VbenVxeTableToolbar } from '@vben/plugins/vxe-table'; |
| 10 | import { cloneDeep, downloadFileFromBlobPart, formatDateTime, isEmpty } from '@vben/utils'; | 20 | import { cloneDeep, downloadFileFromBlobPart, formatDateTime, isEmpty } from '@vben/utils'; |
| 11 | -import { Button, Card, message, Tabs, Pagination, Form, RangePicker, DatePicker, Select, Input } from 'antdv-next'; | 21 | +import { Button, Card, DatePicker, Form, FormItem, Input, message, Pagination#if ($rangePickerEnable), DateRangePicker as RangePicker#end, Select#if ($selectOptionEnable), SelectOption#end#if ( $table.templateType == 11 || $table.templateType == 12 ), TabPane, Tabs#end } from 'antdv-next'; |
| 12 | import ${simpleClassName}Form from './modules/form.vue'; | 22 | import ${simpleClassName}Form from './modules/form.vue'; |
| 13 | import { Download, Plus, RefreshCw, Search, Trash2 } from '@vben/icons'; | 23 | import { Download, Plus, RefreshCw, Search, Trash2 } from '@vben/icons'; |
| 14 | import { DictTag } from '#/components/dict-tag'; | 24 | import { DictTag } from '#/components/dict-tag'; |
| 15 | import { VxeColumn, VxeTable } from '#/adapter/vxe-table'; | 25 | import { VxeColumn, VxeTable } from '#/adapter/vxe-table'; |
| 26 | +#if ($rangePickerEnable) | ||
| 16 | import { getRangePickerDefaultProps } from '#/utils/rangePickerProps'; | 27 | import { getRangePickerDefaultProps } from '#/utils/rangePickerProps'; |
| 28 | +#end | ||
| 17 | 29 | ||
| 18 | ## 特殊:主子表专属逻辑 | 30 | ## 特殊:主子表专属逻辑 |
| 19 | #if ( $table.templateType == 11 || $table.templateType == 12 ) | 31 | #if ( $table.templateType == 11 || $table.templateType == 12 ) |
| @@ -249,7 +261,7 @@ onMounted(() => { | @@ -249,7 +261,7 @@ onMounted(() => { | ||
| 249 | #set ($dictMethod = "boolean") | 261 | #set ($dictMethod = "boolean") |
| 250 | #end | 262 | #end |
| 251 | #if ($column.htmlType == "input") | 263 | #if ($column.htmlType == "input") |
| 252 | - <Form.Item label="${comment}" name="${javaField}"> | 264 | + <FormItem label="${comment}" name="${javaField}"> |
| 253 | <Input | 265 | <Input |
| 254 | v-model:value="queryParams.${javaField}" | 266 | v-model:value="queryParams.${javaField}" |
| 255 | placeholder="请输入${comment}" | 267 | placeholder="请输入${comment}" |
| @@ -257,9 +269,9 @@ onMounted(() => { | @@ -257,9 +269,9 @@ onMounted(() => { | ||
| 257 | @pressEnter="handleQuery" | 269 | @pressEnter="handleQuery" |
| 258 | class="w-full" | 270 | class="w-full" |
| 259 | /> | 271 | /> |
| 260 | - </Form.Item> | 272 | + </FormItem> |
| 261 | #elseif ($column.htmlType == "select" || $column.htmlType == "radio" || $column.htmlType == "checkbox") | 273 | #elseif ($column.htmlType == "select" || $column.htmlType == "radio" || $column.htmlType == "checkbox") |
| 262 | - <Form.Item label="${comment}" name="${javaField}"> | 274 | + <FormItem label="${comment}" name="${javaField}"> |
| 263 | <Select | 275 | <Select |
| 264 | v-model:value="queryParams.${javaField}" | 276 | v-model:value="queryParams.${javaField}" |
| 265 | placeholder="请选择${comment}" | 277 | placeholder="请选择${comment}" |
| @@ -278,10 +290,10 @@ onMounted(() => { | @@ -278,10 +290,10 @@ onMounted(() => { | ||
| 278 | <SelectOption label="请选择字典生成" value="" /> | 290 | <SelectOption label="请选择字典生成" value="" /> |
| 279 | #end | 291 | #end |
| 280 | </Select> | 292 | </Select> |
| 281 | - </Form.Item> | 293 | + </FormItem> |
| 282 | #elseif($column.htmlType == "datetime") | 294 | #elseif($column.htmlType == "datetime") |
| 283 | #if ($column.listOperationCondition != "BETWEEN")## 非范围 | 295 | #if ($column.listOperationCondition != "BETWEEN")## 非范围 |
| 284 | - <Form.Item label="${comment}" name="${javaField}"> | 296 | + <FormItem label="${comment}" name="${javaField}"> |
| 285 | <DatePicker | 297 | <DatePicker |
| 286 | v-model:value="queryParams.${javaField}" | 298 | v-model:value="queryParams.${javaField}" |
| 287 | valueFormat="YYYY-MM-DD" | 299 | valueFormat="YYYY-MM-DD" |
| @@ -289,25 +301,25 @@ onMounted(() => { | @@ -289,25 +301,25 @@ onMounted(() => { | ||
| 289 | allowClear | 301 | allowClear |
| 290 | class="w-full" | 302 | class="w-full" |
| 291 | /> | 303 | /> |
| 292 | - </Form.Item> | 304 | + </FormItem> |
| 293 | #else## 范围 | 305 | #else## 范围 |
| 294 | - <Form.Item label="${comment}" name="${javaField}"> | 306 | + <FormItem label="${comment}" name="${javaField}"> |
| 295 | <RangePicker | 307 | <RangePicker |
| 296 | v-model:value="queryParams.${javaField}" | 308 | v-model:value="queryParams.${javaField}" |
| 297 | v-bind="getRangePickerDefaultProps()" | 309 | v-bind="getRangePickerDefaultProps()" |
| 298 | class="w-full" | 310 | class="w-full" |
| 299 | /> | 311 | /> |
| 300 | - </Form.Item> | 312 | + </FormItem> |
| 301 | #end | 313 | #end |
| 302 | #end | 314 | #end |
| 303 | #end | 315 | #end |
| 304 | #end | 316 | #end |
| 305 | - <Form.Item> | 317 | + <FormItem> |
| 306 | <Button class="ml-2" @click="resetQuery"> 重置 </Button> | 318 | <Button class="ml-2" @click="resetQuery"> 重置 </Button> |
| 307 | <Button class="ml-2" @click="handleQuery" type="primary"> | 319 | <Button class="ml-2" @click="handleQuery" type="primary"> |
| 308 | 搜索 | 320 | 搜索 |
| 309 | </Button> | 321 | </Button> |
| 310 | - </Form.Item> | 322 | + </FormItem> |
| 311 | </Form> | 323 | </Form> |
| 312 | </Card> | 324 | </Card> |
| 313 | 325 |
yudao-module-infra/src/main/resources/codegen/vue3_vben5_antdv_next/general/views/modules/form_sub_erp.vue.vm
| @@ -2,8 +2,14 @@ | @@ -2,8 +2,14 @@ | ||
| 2 | #set ($subColumns = $subColumnsList.get($subIndex))##当前字段数组 | 2 | #set ($subColumns = $subColumnsList.get($subIndex))##当前字段数组 |
| 3 | #set ($subJoinColumn = $subJoinColumns.get($subIndex))##当前 join 字段 | 3 | #set ($subJoinColumn = $subJoinColumns.get($subIndex))##当前 join 字段 |
| 4 | #set ($subSimpleClassName = $subSimpleClassNames.get($subIndex)) | 4 | #set ($subSimpleClassName = $subSimpleClassNames.get($subIndex)) |
| 5 | +#set ($selectOptionEnable = false) | ||
| 6 | +#foreach ($column in $subColumns) | ||
| 7 | + #if (($column.createOperation || $column.updateOperation) && $column.htmlType == "select") | ||
| 8 | + #set ($selectOptionEnable = true) | ||
| 9 | + #end | ||
| 10 | +#end | ||
| 5 | <script lang="ts" setup> | 11 | <script lang="ts" setup> |
| 6 | -import type { Rule } from 'antdv-next/es/form'; | 12 | +import type { Rule } from 'antdv-next'; |
| 7 | import type { ${simpleClassName}Api } from '#/api/${table.moduleName}/${table.businessName}'; | 13 | import type { ${simpleClassName}Api } from '#/api/${table.moduleName}/${table.businessName}'; |
| 8 | 14 | ||
| 9 | import { computed, ref } from 'vue'; | 15 | import { computed, ref } from 'vue'; |
| @@ -13,7 +19,7 @@ import { DICT_TYPE } from '@vben/constants'; | @@ -13,7 +19,7 @@ import { DICT_TYPE } from '@vben/constants'; | ||
| 13 | import { getDictOptions } from '@vben/hooks'; | 19 | import { getDictOptions } from '@vben/hooks'; |
| 14 | import { Tinymce as RichTextarea } from '#/components/tinymce'; | 20 | import { Tinymce as RichTextarea } from '#/components/tinymce'; |
| 15 | import { ImageUpload, FileUpload } from "#/components/upload"; | 21 | import { ImageUpload, FileUpload } from "#/components/upload"; |
| 16 | -import { message, Tabs, Form, Input, TextArea, Select, RadioGroup, Radio, CheckboxGroup, Checkbox, DatePicker, TreeSelect } from 'antdv-next'; | 22 | +import { Checkbox, CheckboxGroup, DatePicker, Form, FormItem, Input, message, Radio, RadioGroup, Select#if ($selectOptionEnable), SelectOption#end, TextArea, TreeSelect } from 'antdv-next'; |
| 17 | 23 | ||
| 18 | import { $t } from '#/locales'; | 24 | import { $t } from '#/locales'; |
| 19 | 25 | ||
| @@ -130,23 +136,23 @@ function resetForm(){ | @@ -130,23 +136,23 @@ function resetForm(){ | ||
| 130 | #set ($dictMethod = "boolean") | 136 | #set ($dictMethod = "boolean") |
| 131 | #end | 137 | #end |
| 132 | #if ($column.htmlType == "input" && !$column.primaryKey)## 忽略主键,不用在表单里 | 138 | #if ($column.htmlType == "input" && !$column.primaryKey)## 忽略主键,不用在表单里 |
| 133 | - <Form.Item label="${comment}" name="${javaField}"> | 139 | + <FormItem label="${comment}" name="${javaField}"> |
| 134 | <Input v-model:value="formData.${javaField}" placeholder="请输入${comment}" /> | 140 | <Input v-model:value="formData.${javaField}" placeholder="请输入${comment}" /> |
| 135 | - </Form.Item> | 141 | + </FormItem> |
| 136 | #elseif($column.htmlType == "imageUpload")## 图片上传 | 142 | #elseif($column.htmlType == "imageUpload")## 图片上传 |
| 137 | - <Form.Item label="${comment}" name="${javaField}"> | 143 | + <FormItem label="${comment}" name="${javaField}"> |
| 138 | <ImageUpload v-model:value="formData.${javaField}" /> | 144 | <ImageUpload v-model:value="formData.${javaField}" /> |
| 139 | - </Form.Item> | 145 | + </FormItem> |
| 140 | #elseif($column.htmlType == "fileUpload")## 文件上传 | 146 | #elseif($column.htmlType == "fileUpload")## 文件上传 |
| 141 | - <Form.Item label="${comment}" name="${javaField}"> | 147 | + <FormItem label="${comment}" name="${javaField}"> |
| 142 | <FileUpload v-model:value="formData.${javaField}" /> | 148 | <FileUpload v-model:value="formData.${javaField}" /> |
| 143 | - </Form.Item> | 149 | + </FormItem> |
| 144 | #elseif($column.htmlType == "editor")## 文本编辑器 | 150 | #elseif($column.htmlType == "editor")## 文本编辑器 |
| 145 | - <Form.Item label="${comment}" name="${javaField}"> | 151 | + <FormItem label="${comment}" name="${javaField}"> |
| 146 | <RichTextarea v-model="formData.${javaField}" height="500px" /> | 152 | <RichTextarea v-model="formData.${javaField}" height="500px" /> |
| 147 | - </Form.Item> | 153 | + </FormItem> |
| 148 | #elseif($column.htmlType == "select")## 下拉框 | 154 | #elseif($column.htmlType == "select")## 下拉框 |
| 149 | - <Form.Item label="${comment}" name="${javaField}"> | 155 | + <FormItem label="${comment}" name="${javaField}"> |
| 150 | <Select v-model:value="formData.${javaField}" placeholder="请选择${comment}"> | 156 | <Select v-model:value="formData.${javaField}" placeholder="请选择${comment}"> |
| 151 | #if ("" != $dictType)## 有数据字典 | 157 | #if ("" != $dictType)## 有数据字典 |
| 152 | <SelectOption | 158 | <SelectOption |
| @@ -160,9 +166,9 @@ function resetForm(){ | @@ -160,9 +166,9 @@ function resetForm(){ | ||
| 160 | <SelectOption label="请选择字典生成" value="" /> | 166 | <SelectOption label="请选择字典生成" value="" /> |
| 161 | #end | 167 | #end |
| 162 | </Select> | 168 | </Select> |
| 163 | - </Form.Item> | 169 | + </FormItem> |
| 164 | #elseif($column.htmlType == "checkbox")## 多选框 | 170 | #elseif($column.htmlType == "checkbox")## 多选框 |
| 165 | - <Form.Item label="${comment}" name="${javaField}"> | 171 | + <FormItem label="${comment}" name="${javaField}"> |
| 166 | <CheckboxGroup v-model:value="formData.${javaField}"> | 172 | <CheckboxGroup v-model:value="formData.${javaField}"> |
| 167 | #if ("" != $dictType)## 有数据字典 | 173 | #if ("" != $dictType)## 有数据字典 |
| 168 | <Checkbox | 174 | <Checkbox |
| @@ -176,9 +182,9 @@ function resetForm(){ | @@ -176,9 +182,9 @@ function resetForm(){ | ||
| 176 | <Checkbox label="请选择字典生成" /> | 182 | <Checkbox label="请选择字典生成" /> |
| 177 | #end | 183 | #end |
| 178 | </CheckboxGroup> | 184 | </CheckboxGroup> |
| 179 | - </Form.Item> | 185 | + </FormItem> |
| 180 | #elseif($column.htmlType == "radio")## 单选框 | 186 | #elseif($column.htmlType == "radio")## 单选框 |
| 181 | - <Form.Item label="${comment}" name="${javaField}"> | 187 | + <FormItem label="${comment}" name="${javaField}"> |
| 182 | <RadioGroup v-model:value="formData.${javaField}"> | 188 | <RadioGroup v-model:value="formData.${javaField}"> |
| 183 | #if ("" != $dictType)## 有数据字典 | 189 | #if ("" != $dictType)## 有数据字典 |
| 184 | <Radio | 190 | <Radio |
| @@ -192,19 +198,19 @@ function resetForm(){ | @@ -192,19 +198,19 @@ function resetForm(){ | ||
| 192 | <Radio value="1">请选择字典生成</Radio> | 198 | <Radio value="1">请选择字典生成</Radio> |
| 193 | #end | 199 | #end |
| 194 | </RadioGroup> | 200 | </RadioGroup> |
| 195 | - </Form.Item> | 201 | + </FormItem> |
| 196 | #elseif($column.htmlType == "datetime")## 时间框 | 202 | #elseif($column.htmlType == "datetime")## 时间框 |
| 197 | - <Form.Item label="${comment}" name="${javaField}"> | 203 | + <FormItem label="${comment}" name="${javaField}"> |
| 198 | <DatePicker | 204 | <DatePicker |
| 199 | v-model:value="formData.${javaField}" | 205 | v-model:value="formData.${javaField}" |
| 200 | valueFormat="x" | 206 | valueFormat="x" |
| 201 | placeholder="选择${comment}" | 207 | placeholder="选择${comment}" |
| 202 | /> | 208 | /> |
| 203 | - </Form.Item> | 209 | + </FormItem> |
| 204 | #elseif($column.htmlType == "textarea")## 文本框 | 210 | #elseif($column.htmlType == "textarea")## 文本框 |
| 205 | - <Form.Item label="${comment}" name="${javaField}"> | 211 | + <FormItem label="${comment}" name="${javaField}"> |
| 206 | <TextArea v-model:value="formData.${javaField}" placeholder="请输入${comment}" /> | 212 | <TextArea v-model:value="formData.${javaField}" placeholder="请输入${comment}" /> |
| 207 | - </Form.Item> | 213 | + </FormItem> |
| 208 | #end | 214 | #end |
| 209 | #end | 215 | #end |
| 210 | #end | 216 | #end |
yudao-module-infra/src/main/resources/codegen/vue3_vben5_antdv_next/general/views/modules/form_sub_inner.vue.vm
| 1 | ## 主表的 normal 和 inner 使用相同的 form 表单 | 1 | ## 主表的 normal 和 inner 使用相同的 form 表单 |
| 2 | -#parse("codegen/vue3_vben5_antd/general/views/modules/form_sub_normal.vue.vm") | ||
| 3 | \ No newline at end of file | 2 | \ No newline at end of file |
| 3 | +#parse("codegen/vue3_vben5_antdv_next/general/views/modules/form_sub_normal.vue.vm") |
yudao-module-infra/src/main/resources/codegen/vue3_vben5_antdv_next/general/views/modules/form_sub_normal.vue.vm
| @@ -4,6 +4,12 @@ | @@ -4,6 +4,12 @@ | ||
| 4 | #set ($subSimpleClassName = $subSimpleClassNames.get($subIndex)) | 4 | #set ($subSimpleClassName = $subSimpleClassNames.get($subIndex)) |
| 5 | #set ($subClassNameVar = $subClassNameVars.get($subIndex)) | 5 | #set ($subClassNameVar = $subClassNameVars.get($subIndex)) |
| 6 | #set ($SubJoinColumnName = $subJoinColumn.javaField.substring(0,1).toUpperCase() + ${subJoinColumn.javaField.substring(1)})##首字母大写 | 6 | #set ($SubJoinColumnName = $subJoinColumn.javaField.substring(0,1).toUpperCase() + ${subJoinColumn.javaField.substring(1)})##首字母大写 |
| 7 | +#set ($selectOptionEnable = false) | ||
| 8 | +#foreach ($column in $subColumns) | ||
| 9 | + #if (($column.createOperation || $column.updateOperation) && (!$subTable.subJoinMany || $column.id != $subJoinColumn.id) && $column.htmlType == "select") | ||
| 10 | + #set ($selectOptionEnable = true) | ||
| 11 | + #end | ||
| 12 | +#end | ||
| 7 | <script lang="ts" setup> | 13 | <script lang="ts" setup> |
| 8 | import type { ${simpleClassName}Api } from '#/api/${table.moduleName}/${table.businessName}'; | 14 | import type { ${simpleClassName}Api } from '#/api/${table.moduleName}/${table.businessName}'; |
| 9 | 15 | ||
| @@ -12,7 +18,7 @@ import { computed, ref, h, onMounted, watch, nextTick } from 'vue'; | @@ -12,7 +18,7 @@ import { computed, ref, h, onMounted, watch, nextTick } from 'vue'; | ||
| 12 | import { DICT_TYPE } from '@vben/constants'; | 18 | import { DICT_TYPE } from '@vben/constants'; |
| 13 | import { getDictOptions } from '@vben/hooks'; | 19 | import { getDictOptions } from '@vben/hooks'; |
| 14 | 20 | ||
| 15 | -import { message, Tabs, Form, Input, TextArea, Button, Select, RadioGroup, Radio, CheckboxGroup, Checkbox, DatePicker } from 'antdv-next'; | 21 | +import { Button, Checkbox, CheckboxGroup, DatePicker#if (!$subTable.subJoinMany), Form, FormItem#end, Input, Radio, RadioGroup, Select#if ($selectOptionEnable), SelectOption#end, TextArea } from 'antdv-next'; |
| 16 | import { $t } from '#/locales'; | 22 | import { $t } from '#/locales'; |
| 17 | 23 | ||
| 18 | #if ($subTable.subJoinMany) ## 一对多 | 24 | #if ($subTable.subJoinMany) ## 一对多 |
| @@ -22,7 +28,7 @@ import { VxeColumn, VxeTable } from '#/adapter/vxe-table'; | @@ -22,7 +28,7 @@ import { VxeColumn, VxeTable } from '#/adapter/vxe-table'; | ||
| 22 | import { ImageUpload, FileUpload } from "#/components/upload"; | 28 | import { ImageUpload, FileUpload } from "#/components/upload"; |
| 23 | import { get${subSimpleClassName}ListBy${SubJoinColumnName} } from '#/api/${table.moduleName}/${table.businessName}'; | 29 | import { get${subSimpleClassName}ListBy${SubJoinColumnName} } from '#/api/${table.moduleName}/${table.businessName}'; |
| 24 | #else | 30 | #else |
| 25 | -import type { Rule } from 'antdv-next/es/form'; | 31 | +import type { Rule } from 'antdv-next'; |
| 26 | import { Tinymce as RichTextarea } from '#/components/tinymce'; | 32 | import { Tinymce as RichTextarea } from '#/components/tinymce'; |
| 27 | import { ImageUpload, FileUpload } from "#/components/upload"; | 33 | import { ImageUpload, FileUpload } from "#/components/upload"; |
| 28 | import { get${subSimpleClassName}By${SubJoinColumnName} } from '#/api/${table.moduleName}/${table.businessName}'; | 34 | import { get${subSimpleClassName}By${SubJoinColumnName} } from '#/api/${table.moduleName}/${table.businessName}'; |
| @@ -264,23 +270,23 @@ watch( | @@ -264,23 +270,23 @@ watch( | ||
| 264 | #set ($dictMethod = "boolean") | 270 | #set ($dictMethod = "boolean") |
| 265 | #end | 271 | #end |
| 266 | #if ($column.htmlType == "input" && !$column.primaryKey)## 忽略主键,不用在表单里 | 272 | #if ($column.htmlType == "input" && !$column.primaryKey)## 忽略主键,不用在表单里 |
| 267 | - <Form.Item label="${comment}" name="${javaField}"> | 273 | + <FormItem label="${comment}" name="${javaField}"> |
| 268 | <Input v-model:value="formData.${javaField}" placeholder="请输入${comment}" /> | 274 | <Input v-model:value="formData.${javaField}" placeholder="请输入${comment}" /> |
| 269 | - </Form.Item> | 275 | + </FormItem> |
| 270 | #elseif($column.htmlType == "imageUpload")## 图片上传 | 276 | #elseif($column.htmlType == "imageUpload")## 图片上传 |
| 271 | - <Form.Item label="${comment}" name="${javaField}"> | 277 | + <FormItem label="${comment}" name="${javaField}"> |
| 272 | <ImageUpload v-model:value="formData.${javaField}" /> | 278 | <ImageUpload v-model:value="formData.${javaField}" /> |
| 273 | - </Form.Item> | 279 | + </FormItem> |
| 274 | #elseif($column.htmlType == "fileUpload")## 文件上传 | 280 | #elseif($column.htmlType == "fileUpload")## 文件上传 |
| 275 | - <Form.Item label="${comment}" name="${javaField}"> | 281 | + <FormItem label="${comment}" name="${javaField}"> |
| 276 | <FileUpload v-model:value="formData.${javaField}" /> | 282 | <FileUpload v-model:value="formData.${javaField}" /> |
| 277 | - </Form.Item> | 283 | + </FormItem> |
| 278 | #elseif($column.htmlType == "editor")## 文本编辑器 | 284 | #elseif($column.htmlType == "editor")## 文本编辑器 |
| 279 | - <Form.Item label="${comment}" name="${javaField}"> | 285 | + <FormItem label="${comment}" name="${javaField}"> |
| 280 | <RichTextarea v-model="formData.${javaField}" height="500px" /> | 286 | <RichTextarea v-model="formData.${javaField}" height="500px" /> |
| 281 | - </Form.Item> | 287 | + </FormItem> |
| 282 | #elseif($column.htmlType == "select")## 下拉框 | 288 | #elseif($column.htmlType == "select")## 下拉框 |
| 283 | - <Form.Item label="${comment}" name="${javaField}"> | 289 | + <FormItem label="${comment}" name="${javaField}"> |
| 284 | <Select v-model:value="formData.${javaField}" placeholder="请选择${comment}"> | 290 | <Select v-model:value="formData.${javaField}" placeholder="请选择${comment}"> |
| 285 | #if ("" != $dictType)## 有数据字典 | 291 | #if ("" != $dictType)## 有数据字典 |
| 286 | <SelectOption | 292 | <SelectOption |
| @@ -294,9 +300,9 @@ watch( | @@ -294,9 +300,9 @@ watch( | ||
| 294 | <SelectOption label="请选择字典生成" value="" /> | 300 | <SelectOption label="请选择字典生成" value="" /> |
| 295 | #end | 301 | #end |
| 296 | </Select> | 302 | </Select> |
| 297 | - </Form.Item> | 303 | + </FormItem> |
| 298 | #elseif($column.htmlType == "checkbox")## 多选框 | 304 | #elseif($column.htmlType == "checkbox")## 多选框 |
| 299 | - <Form.Item label="${comment}" name="${javaField}"> | 305 | + <FormItem label="${comment}" name="${javaField}"> |
| 300 | <CheckboxGroup v-model:value="formData.${javaField}"> | 306 | <CheckboxGroup v-model:value="formData.${javaField}"> |
| 301 | #if ("" != $dictType)## 有数据字典 | 307 | #if ("" != $dictType)## 有数据字典 |
| 302 | <Checkbox | 308 | <Checkbox |
| @@ -310,9 +316,9 @@ watch( | @@ -310,9 +316,9 @@ watch( | ||
| 310 | <Checkbox label="请选择字典生成" /> | 316 | <Checkbox label="请选择字典生成" /> |
| 311 | #end | 317 | #end |
| 312 | </CheckboxGroup> | 318 | </CheckboxGroup> |
| 313 | - </Form.Item> | 319 | + </FormItem> |
| 314 | #elseif($column.htmlType == "radio")## 单选框 | 320 | #elseif($column.htmlType == "radio")## 单选框 |
| 315 | - <Form.Item label="${comment}" name="${javaField}"> | 321 | + <FormItem label="${comment}" name="${javaField}"> |
| 316 | <RadioGroup v-model:value="formData.${javaField}"> | 322 | <RadioGroup v-model:value="formData.${javaField}"> |
| 317 | #if ("" != $dictType)## 有数据字典 | 323 | #if ("" != $dictType)## 有数据字典 |
| 318 | <Radio | 324 | <Radio |
| @@ -326,19 +332,19 @@ watch( | @@ -326,19 +332,19 @@ watch( | ||
| 326 | <Radio value="1">请选择字典生成</Radio> | 332 | <Radio value="1">请选择字典生成</Radio> |
| 327 | #end | 333 | #end |
| 328 | </RadioGroup> | 334 | </RadioGroup> |
| 329 | - </Form.Item> | 335 | + </FormItem> |
| 330 | #elseif($column.htmlType == "datetime")## 时间框 | 336 | #elseif($column.htmlType == "datetime")## 时间框 |
| 331 | - <Form.Item label="${comment}" name="${javaField}"> | 337 | + <FormItem label="${comment}" name="${javaField}"> |
| 332 | <DatePicker | 338 | <DatePicker |
| 333 | v-model:value="formData.${javaField}" | 339 | v-model:value="formData.${javaField}" |
| 334 | valueFormat="x" | 340 | valueFormat="x" |
| 335 | placeholder="选择${comment}" | 341 | placeholder="选择${comment}" |
| 336 | /> | 342 | /> |
| 337 | - </Form.Item> | 343 | + </FormItem> |
| 338 | #elseif($column.htmlType == "textarea")## 文本框 | 344 | #elseif($column.htmlType == "textarea")## 文本框 |
| 339 | - <Form.Item label="${comment}" name="${javaField}"> | 345 | + <FormItem label="${comment}" name="${javaField}"> |
| 340 | <TextArea v-model:value="formData.${javaField}" placeholder="请输入${comment}" /> | 346 | <TextArea v-model:value="formData.${javaField}" placeholder="请输入${comment}" /> |
| 341 | - </Form.Item> | 347 | + </FormItem> |
| 342 | #end | 348 | #end |
| 343 | #end | 349 | #end |
| 344 | #end | 350 | #end |
yudao-module-infra/src/main/resources/codegen/vue3_vben5_antdv_next/general/views/modules/list_sub_erp.vue.vm
| @@ -5,6 +5,16 @@ | @@ -5,6 +5,16 @@ | ||
| 5 | #set ($subJoinColumn = $subJoinColumns.get($subIndex))##当前 join 字段 | 5 | #set ($subJoinColumn = $subJoinColumns.get($subIndex))##当前 join 字段 |
| 6 | #set ($subSimpleClassName_strikeCase = $subSimpleClassName_strikeCases.get($subIndex)) | 6 | #set ($subSimpleClassName_strikeCase = $subSimpleClassName_strikeCases.get($subIndex)) |
| 7 | #set ($SubJoinColumnName = $subJoinColumn.javaField.substring(0,1).toUpperCase() + ${subJoinColumn.javaField.substring(1)})##首字母大写 | 7 | #set ($SubJoinColumnName = $subJoinColumn.javaField.substring(0,1).toUpperCase() + ${subJoinColumn.javaField.substring(1)})##首字母大写 |
| 8 | +#set ($rangePickerEnable = false) | ||
| 9 | +#set ($selectOptionEnable = false) | ||
| 10 | +#foreach ($column in $subColumns) | ||
| 11 | + #if ($column.listOperation && $column.htmlType == "datetime" && $column.listOperationCondition == "BETWEEN") | ||
| 12 | + #set ($rangePickerEnable = true) | ||
| 13 | + #end | ||
| 14 | + #if ($column.listOperation && ($column.htmlType == "select" || $column.htmlType == "radio" || $column.htmlType == "checkbox")) | ||
| 15 | + #set ($selectOptionEnable = true) | ||
| 16 | + #end | ||
| 17 | +#end | ||
| 8 | <script lang="ts" setup> | 18 | <script lang="ts" setup> |
| 9 | import type { ${simpleClassName}Api } from '#/api/${table.moduleName}/${table.businessName}'; | 19 | import type { ${simpleClassName}Api } from '#/api/${table.moduleName}/${table.businessName}'; |
| 10 | import type { VxeTableInstance } from '#/adapter/vxe-table'; | 20 | import type { VxeTableInstance } from '#/adapter/vxe-table'; |
| @@ -15,7 +25,9 @@ import { DICT_TYPE } from '@vben/constants'; | @@ -15,7 +25,9 @@ import { DICT_TYPE } from '@vben/constants'; | ||
| 15 | import { getDictOptions } from '@vben/hooks'; | 25 | import { getDictOptions } from '@vben/hooks'; |
| 16 | 26 | ||
| 17 | import { DictTag } from '#/components/dict-tag'; | 27 | import { DictTag } from '#/components/dict-tag'; |
| 28 | +#if ($table.templateType == 11 && $rangePickerEnable) | ||
| 18 | import { getRangePickerDefaultProps } from '#/utils/rangePickerProps'; | 29 | import { getRangePickerDefaultProps } from '#/utils/rangePickerProps'; |
| 30 | +#end | ||
| 19 | import { VxeColumn, VxeTable } from '#/adapter/vxe-table'; | 31 | import { VxeColumn, VxeTable } from '#/adapter/vxe-table'; |
| 20 | import { formatDateTime } from '@vben/utils'; | 32 | import { formatDateTime } from '@vben/utils'; |
| 21 | 33 | ||
| @@ -25,7 +37,7 @@ import { useTableToolbar, VbenVxeTableToolbar } from '@vben/plugins/vxe-table'; | @@ -25,7 +37,7 @@ import { useTableToolbar, VbenVxeTableToolbar } from '@vben/plugins/vxe-table'; | ||
| 25 | import ${subSimpleClassName}Form from './${subSimpleClassName_strikeCase}-form.vue' | 37 | import ${subSimpleClassName}Form from './${subSimpleClassName_strikeCase}-form.vue' |
| 26 | import { Tinymce as RichTextarea } from '#/components/tinymce'; | 38 | import { Tinymce as RichTextarea } from '#/components/tinymce'; |
| 27 | import { ImageUpload, FileUpload } from "#/components/upload"; | 39 | import { ImageUpload, FileUpload } from "#/components/upload"; |
| 28 | -import { message, Button, Card, Tabs, Pagination, Form, Input, TextArea, Select, RadioGroup, Radio, CheckboxGroup, Checkbox, RangePicker, DatePicker, TreeSelect } from 'antdv-next'; | 40 | +import { Button, Card, Checkbox, CheckboxGroup, DatePicker, Form, FormItem, Input, message, Pagination, Radio, RadioGroup#if ($rangePickerEnable), DateRangePicker as RangePicker#end, Select#if ($selectOptionEnable), SelectOption#end, TextArea, TreeSelect } from 'antdv-next'; |
| 29 | import { Plus, Trash2 } from '@vben/icons'; | 41 | import { Plus, Trash2 } from '@vben/icons'; |
| 30 | import { $t } from '#/locales'; | 42 | import { $t } from '#/locales'; |
| 31 | #end | 43 | #end |
| @@ -224,7 +236,7 @@ onMounted(() => { | @@ -224,7 +236,7 @@ onMounted(() => { | ||
| 224 | #set ($dictMethod = "boolean") | 236 | #set ($dictMethod = "boolean") |
| 225 | #end | 237 | #end |
| 226 | #if ($column.htmlType == "input") | 238 | #if ($column.htmlType == "input") |
| 227 | - <Form.Item label="${comment}" name="${javaField}"> | 239 | + <FormItem label="${comment}" name="${javaField}"> |
| 228 | <Input | 240 | <Input |
| 229 | v-model:value="queryParams.${javaField}" | 241 | v-model:value="queryParams.${javaField}" |
| 230 | placeholder="请输入${comment}" | 242 | placeholder="请输入${comment}" |
| @@ -232,9 +244,9 @@ onMounted(() => { | @@ -232,9 +244,9 @@ onMounted(() => { | ||
| 232 | @pressEnter="handleQuery" | 244 | @pressEnter="handleQuery" |
| 233 | class="w-full" | 245 | class="w-full" |
| 234 | /> | 246 | /> |
| 235 | - </Form.Item> | 247 | + </FormItem> |
| 236 | #elseif ($column.htmlType == "select" || $column.htmlType == "radio" || $column.htmlType == "checkbox") | 248 | #elseif ($column.htmlType == "select" || $column.htmlType == "radio" || $column.htmlType == "checkbox") |
| 237 | - <Form.Item label="${comment}" name="${javaField}"> | 249 | + <FormItem label="${comment}" name="${javaField}"> |
| 238 | <Select | 250 | <Select |
| 239 | v-model:value="queryParams.${javaField}" | 251 | v-model:value="queryParams.${javaField}" |
| 240 | placeholder="请选择${comment}" | 252 | placeholder="请选择${comment}" |
| @@ -253,10 +265,10 @@ onMounted(() => { | @@ -253,10 +265,10 @@ onMounted(() => { | ||
| 253 | <SelectOption label="请选择字典生成" value="" /> | 265 | <SelectOption label="请选择字典生成" value="" /> |
| 254 | #end | 266 | #end |
| 255 | </Select> | 267 | </Select> |
| 256 | - </Form.Item> | 268 | + </FormItem> |
| 257 | #elseif($column.htmlType == "datetime") | 269 | #elseif($column.htmlType == "datetime") |
| 258 | #if ($column.listOperationCondition != "BETWEEN")## 非范围 | 270 | #if ($column.listOperationCondition != "BETWEEN")## 非范围 |
| 259 | - <Form.Item label="${comment}" name="${javaField}"> | 271 | + <FormItem label="${comment}" name="${javaField}"> |
| 260 | <DatePicker | 272 | <DatePicker |
| 261 | v-model:value="queryParams.${javaField}" | 273 | v-model:value="queryParams.${javaField}" |
| 262 | valueFormat="YYYY-MM-DD" | 274 | valueFormat="YYYY-MM-DD" |
| @@ -264,25 +276,25 @@ onMounted(() => { | @@ -264,25 +276,25 @@ onMounted(() => { | ||
| 264 | allowClear | 276 | allowClear |
| 265 | class="w-full" | 277 | class="w-full" |
| 266 | /> | 278 | /> |
| 267 | - </Form.Item> | 279 | + </FormItem> |
| 268 | #else## 范围 | 280 | #else## 范围 |
| 269 | - <Form.Item label="${comment}" name="${javaField}"> | 281 | + <FormItem label="${comment}" name="${javaField}"> |
| 270 | <RangePicker | 282 | <RangePicker |
| 271 | v-model:value="queryParams.${javaField}" | 283 | v-model:value="queryParams.${javaField}" |
| 272 | v-bind="getRangePickerDefaultProps()" | 284 | v-bind="getRangePickerDefaultProps()" |
| 273 | class="w-full" | 285 | class="w-full" |
| 274 | /> | 286 | /> |
| 275 | - </Form.Item> | 287 | + </FormItem> |
| 276 | #end | 288 | #end |
| 277 | #end | 289 | #end |
| 278 | #end | 290 | #end |
| 279 | #end | 291 | #end |
| 280 | - <Form.Item> | 292 | + <FormItem> |
| 281 | <Button class="ml-2" @click="resetQuery"> 重置 </Button> | 293 | <Button class="ml-2" @click="resetQuery"> 重置 </Button> |
| 282 | <Button class="ml-2" @click="handleQuery" type="primary"> | 294 | <Button class="ml-2" @click="handleQuery" type="primary"> |
| 283 | 搜索 | 295 | 搜索 |
| 284 | </Button> | 296 | </Button> |
| 285 | - </Form.Item> | 297 | + </FormItem> |
| 286 | </Form> | 298 | </Form> |
| 287 | </Card> | 299 | </Card> |
| 288 | 300 |
yudao-module-infra/src/main/resources/codegen/vue3_vben5_antdv_next/general/views/modules/list_sub_inner.vue.vm
| 1 | ## 子表的 erp 和 inner 使用相似的 list 列表,差异主要两点: | 1 | ## 子表的 erp 和 inner 使用相似的 list 列表,差异主要两点: |
| 2 | ## 1)inner 使用 list 不分页,erp 使用 page 分页 | 2 | ## 1)inner 使用 list 不分页,erp 使用 page 分页 |
| 3 | ## 2)erp 支持单个子表的新增、修改、删除,inner 不支持 | 3 | ## 2)erp 支持单个子表的新增、修改、删除,inner 不支持 |
| 4 | -#parse("codegen/vue3_vben5_antd/general/views/modules/list_sub_erp.vue.vm") | ||
| 5 | \ No newline at end of file | 4 | \ No newline at end of file |
| 5 | +#parse("codegen/vue3_vben5_antdv_next/general/views/modules/list_sub_erp.vue.vm") |
yudao-module-infra/src/main/resources/codegen/vue3_vben5_antdv_next/schema/views/form.vue.vm
| @@ -6,7 +6,7 @@ import { computed, ref } from 'vue'; | @@ -6,7 +6,7 @@ import { computed, ref } from 'vue'; | ||
| 6 | 6 | ||
| 7 | import { useVbenModal } from '@vben/common-ui'; | 7 | import { useVbenModal } from '@vben/common-ui'; |
| 8 | 8 | ||
| 9 | -import { message#if ($table.templateType == 11), Tabs#end } from 'antdv-next'; | 9 | +import { message#if ($table.templateType == 10 || $table.templateType == 12), TabPane, Tabs#end } from 'antdv-next'; |
| 10 | 10 | ||
| 11 | import { useVbenForm } from '#/adapter/form'; | 11 | import { useVbenForm } from '#/adapter/form'; |
| 12 | import { create${simpleClassName}, get${simpleClassName}, update${simpleClassName} } from '#/api/${table.moduleName}/${table.businessName}'; | 12 | import { create${simpleClassName}, get${simpleClassName}, update${simpleClassName} } from '#/api/${table.moduleName}/${table.businessName}'; |
yudao-module-infra/src/main/resources/codegen/vue3_vben5_antdv_next/schema/views/import.vue.vm
| 1 | <script lang="ts" setup> | 1 | <script lang="ts" setup> |
| 2 | -import type { FileType } from 'antdv-next/es/upload/interface'; | ||
| 3 | - | ||
| 4 | import { useVbenModal } from '@vben/common-ui'; | 2 | import { useVbenModal } from '@vben/common-ui'; |
| 5 | import { downloadFileFromBlobPart } from '@vben/utils'; | 3 | import { downloadFileFromBlobPart } from '@vben/utils'; |
| 6 | 4 | ||
| @@ -46,7 +44,7 @@ const [Modal, modalApi] = useVbenModal({ | @@ -46,7 +44,7 @@ const [Modal, modalApi] = useVbenModal({ | ||
| 46 | }); | 44 | }); |
| 47 | 45 | ||
| 48 | /** 上传前 */ | 46 | /** 上传前 */ |
| 49 | -function beforeUpload(file: FileType) { | 47 | +function beforeUpload(file: File) { |
| 50 | formApi.setFieldValue('file', file); | 48 | formApi.setFieldValue('file', file); |
| 51 | return false; | 49 | return false; |
| 52 | } | 50 | } |
yudao-module-infra/src/main/resources/codegen/vue3_vben5_antdv_next/schema/views/index.vue.vm
| @@ -8,7 +8,7 @@ import { ref } from 'vue'; | @@ -8,7 +8,7 @@ import { ref } from 'vue'; | ||
| 8 | import {#if ($table.templateType != 2 && $deleteBatchEnable) confirm,#end Page, useVbenModal } from '@vben/common-ui'; | 8 | import {#if ($table.templateType != 2 && $deleteBatchEnable) confirm,#end Page, useVbenModal } from '@vben/common-ui'; |
| 9 | import { downloadFileFromBlobPart#if ($table.templateType != 2 && $deleteBatchEnable), isEmpty#end } from '@vben/utils'; | 9 | import { downloadFileFromBlobPart#if ($table.templateType != 2 && $deleteBatchEnable), isEmpty#end } from '@vben/utils'; |
| 10 | 10 | ||
| 11 | -import { message#if ($table.templateType == 11), Tabs#end } from 'antdv-next'; | 11 | +import { message#if ($table.templateType == 11 || $table.templateType == 12), TabPane, Tabs#end } from 'antdv-next'; |
| 12 | 12 | ||
| 13 | import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table'; | 13 | import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table'; |
| 14 | #if (${table.templateType} == 2)## 树表接口 | 14 | #if (${table.templateType} == 2)## 树表接口 |
yudao-module-infra/src/main/resources/codegen/vue3_vben5_antdv_next/schema/views/modules/form_sub_inner.vue.vm
| 1 | ## 主表的 normal 和 inner 使用相同的 form 表单 | 1 | ## 主表的 normal 和 inner 使用相同的 form 表单 |
| 2 | -#parse("codegen/vue3_vben5_antd/schema/views/modules/form_sub_normal.vue.vm") | ||
| 3 | \ No newline at end of file | 2 | \ No newline at end of file |
| 3 | +#parse("codegen/vue3_vben5_antdv_next/schema/views/modules/form_sub_normal.vue.vm") |
yudao-module-infra/src/main/resources/codegen/vue3_vben5_antdv_next/schema/views/modules/form_sub_normal.vue.vm
| @@ -5,6 +5,12 @@ | @@ -5,6 +5,12 @@ | ||
| 5 | #set ($subClassNameVar = $subClassNameVars.get($subIndex)) | 5 | #set ($subClassNameVar = $subClassNameVars.get($subIndex)) |
| 6 | #set ($SubJoinColumnName = $subJoinColumn.javaField.substring(0,1).toUpperCase() + ${subJoinColumn.javaField.substring(1)})##首字母大写 | 6 | #set ($SubJoinColumnName = $subJoinColumn.javaField.substring(0,1).toUpperCase() + ${subJoinColumn.javaField.substring(1)})##首字母大写 |
| 7 | #set ($apiName = "${table.moduleName.substring(0,1).toUpperCase()}${table.moduleName.substring(1)}${simpleClassName}Api") | 7 | #set ($apiName = "${table.moduleName.substring(0,1).toUpperCase()}${table.moduleName.substring(1)}${simpleClassName}Api") |
| 8 | +#set ($selectOptionEnable = false) | ||
| 9 | +#foreach ($column in $subColumns) | ||
| 10 | + #if (($column.createOperation || $column.updateOperation) && $column.id != $subJoinColumn.id && $column.htmlType == "select") | ||
| 11 | + #set ($selectOptionEnable = true) | ||
| 12 | + #end | ||
| 13 | +#end | ||
| 8 | <script lang="ts" setup> | 14 | <script lang="ts" setup> |
| 9 | import type { ${apiName} } from '#/api/${table.moduleName}/${table.businessName}'; | 15 | import type { ${apiName} } from '#/api/${table.moduleName}/${table.businessName}'; |
| 10 | 16 | ||
| @@ -14,7 +20,19 @@ | @@ -14,7 +20,19 @@ | ||
| 14 | 20 | ||
| 15 | #if ($subTable.subJoinMany) ## 一对多 | 21 | #if ($subTable.subJoinMany) ## 一对多 |
| 16 | import { Plus } from "@vben/icons"; | 22 | import { Plus } from "@vben/icons"; |
| 17 | -import { Button, Tabs, Checkbox, Input, TextArea, Select,RadioGroup,CheckboxGroup, DatePicker } from 'antdv-next'; | 23 | +import { |
| 24 | + Button, | ||
| 25 | + Checkbox, | ||
| 26 | + CheckboxGroup, | ||
| 27 | + DatePicker, | ||
| 28 | + Input, | ||
| 29 | + RadioGroup, | ||
| 30 | + Select, | ||
| 31 | +#if ($selectOptionEnable) | ||
| 32 | + SelectOption, | ||
| 33 | +#end | ||
| 34 | + TextArea, | ||
| 35 | +} from 'antdv-next'; | ||
| 18 | import { ImageUpload, FileUpload } from "#/components/upload"; | 36 | import { ImageUpload, FileUpload } from "#/components/upload"; |
| 19 | import { useVbenVxeGrid } from '#/adapter/vxe-table'; | 37 | import { useVbenVxeGrid } from '#/adapter/vxe-table'; |
| 20 | import { use${subSimpleClassName}GridEditColumns } from '../data'; | 38 | import { use${subSimpleClassName}GridEditColumns } from '../data'; |
yudao-module-infra/src/main/resources/codegen/vue3_vben5_antdv_next/schema/views/modules/list_sub_inner.vue.vm
| 1 | ## 子表的 erp 和 inner 使用相似的 list 列表,差异主要两点: | 1 | ## 子表的 erp 和 inner 使用相似的 list 列表,差异主要两点: |
| 2 | ## 1)inner 使用 list 不分页,erp 使用 page 分页 | 2 | ## 1)inner 使用 list 不分页,erp 使用 page 分页 |
| 3 | ## 2)erp 支持单个子表的新增、修改、删除,inner 不支持 | 3 | ## 2)erp 支持单个子表的新增、修改、删除,inner 不支持 |
| 4 | -#parse("codegen/vue3_vben5_antd/schema/views/modules/list_sub_erp.vue.vm") | ||
| 5 | \ No newline at end of file | 4 | \ No newline at end of file |
| 5 | +#parse("codegen/vue3_vben5_antdv_next/schema/views/modules/list_sub_erp.vue.vm") |
yudao-module-infra/src/test/java/cn/iocoder/yudao/module/infra/service/codegen/inner/CodegenEngineAbstractTest.java
| @@ -157,13 +157,17 @@ public abstract class CodegenEngineAbstractTest extends BaseMockitoUnitTest { | @@ -157,13 +157,17 @@ public abstract class CodegenEngineAbstractTest extends BaseMockitoUnitTest { | ||
| 157 | String lastFilePath = StrUtil.subAfter(filePath, '/', true); | 157 | String lastFilePath = StrUtil.subAfter(filePath, '/', true); |
| 158 | String ext = StrUtil.subAfter(lastFilePath, '.', true); | 158 | String ext = StrUtil.subAfter(lastFilePath, '.', true); |
| 159 | String name = StrUtil.subBefore(lastFilePath, '.', true); | 159 | String name = StrUtil.subBefore(lastFilePath, '.', true); |
| 160 | - String contentPath = ext + '/' + name; | ||
| 161 | - // 同名文件(如 index.vue 同时出现在 列表/form/detail)会撞名,撞名时前缀补上级目录区分;仍撞则加序号兜底,避免快照互相覆盖 | 160 | + String parentPath = StrUtil.subBefore(filePath, '/' + lastFilePath, true); |
| 161 | + String parentDir = StrUtil.subAfter(parentPath, '/', true); | ||
| 162 | + // index.vue 按页面语义稳定归档,避免结果迭代顺序导致列表页和表单页的快照路径互换 | ||
| 163 | + String contentPath = "index".equals(name) && ("form".equals(parentDir) || "detail".equals(parentDir)) | ||
| 164 | + ? ext + '/' + parentDir + '/' + name : ext + '/' + name; | ||
| 165 | + // ERP 多个子表的 form/index.vue 仍可能撞名,继续补充祖父目录区分 | ||
| 162 | if (usedContentPaths.contains(contentPath)) { | 166 | if (usedContentPaths.contains(contentPath)) { |
| 163 | - String parentDir = StrUtil.subAfter(StrUtil.subBefore(filePath, '/' + lastFilePath, true), '/', true); | ||
| 164 | - contentPath = ext + '/' + parentDir + '/' + name; | 167 | + String grandParentDir = StrUtil.subAfter(StrUtil.subBefore(parentPath, '/' + parentDir, true), '/', true); |
| 168 | + contentPath = ext + '/' + grandParentDir + '/' + parentDir + '/' + name; | ||
| 165 | for (int i = 2; usedContentPaths.contains(contentPath); i++) { | 169 | for (int i = 2; usedContentPaths.contains(contentPath); i++) { |
| 166 | - contentPath = ext + '/' + parentDir + '/' + name + '-' + i; | 170 | + contentPath = ext + '/' + grandParentDir + '/' + parentDir + '/' + name + '-' + i; |
| 167 | } | 171 | } |
| 168 | } | 172 | } |
| 169 | usedContentPaths.add(contentPath); | 173 | usedContentPaths.add(contentPath); |
yudao-module-infra/src/test/java/cn/iocoder/yudao/module/infra/service/codegen/inner/CodegenEngineUniappTest.java
| 1 | package cn.iocoder.yudao.module.infra.service.codegen.inner; | 1 | package cn.iocoder.yudao.module.infra.service.codegen.inner; |
| 2 | 2 | ||
| 3 | +import cn.iocoder.yudao.framework.common.exception.ServiceException; | ||
| 3 | import cn.iocoder.yudao.module.infra.dal.dataobject.codegen.CodegenColumnDO; | 4 | import cn.iocoder.yudao.module.infra.dal.dataobject.codegen.CodegenColumnDO; |
| 4 | import cn.iocoder.yudao.module.infra.dal.dataobject.codegen.CodegenTableDO; | 5 | import cn.iocoder.yudao.module.infra.dal.dataobject.codegen.CodegenTableDO; |
| 5 | import cn.iocoder.yudao.module.infra.enums.codegen.CodegenFrontTypeEnum; | 6 | import cn.iocoder.yudao.module.infra.enums.codegen.CodegenFrontTypeEnum; |
| @@ -7,8 +8,15 @@ import cn.iocoder.yudao.module.infra.enums.codegen.CodegenTemplateTypeEnum; | @@ -7,8 +8,15 @@ import cn.iocoder.yudao.module.infra.enums.codegen.CodegenTemplateTypeEnum; | ||
| 7 | import com.baomidou.mybatisplus.annotation.DbType; | 8 | import com.baomidou.mybatisplus.annotation.DbType; |
| 8 | import org.junit.jupiter.api.Test; | 9 | import org.junit.jupiter.api.Test; |
| 9 | 10 | ||
| 11 | +import java.util.Arrays; | ||
| 12 | +import java.util.HashMap; | ||
| 10 | import java.util.List; | 13 | import java.util.List; |
| 11 | import java.util.Map; | 14 | import java.util.Map; |
| 15 | +import java.util.Objects; | ||
| 16 | + | ||
| 17 | +import static org.junit.jupiter.api.Assertions.assertFalse; | ||
| 18 | +import static org.junit.jupiter.api.Assertions.assertThrows; | ||
| 19 | +import static org.junit.jupiter.api.Assertions.assertTrue; | ||
| 12 | 20 | ||
| 13 | /** | 21 | /** |
| 14 | * {@link CodegenEngine} 的 Vue3 Admin Uniapp + Wot UI 单元测试 | 22 | * {@link CodegenEngine} 的 Vue3 Admin Uniapp + Wot UI 单元测试 |
| @@ -31,4 +39,340 @@ public class CodegenEngineUniappTest extends CodegenEngineAbstractTest { | @@ -31,4 +39,340 @@ public class CodegenEngineUniappTest extends CodegenEngineAbstractTest { | ||
| 31 | assertResult(result, "/vue3_admin_uniapp_one"); | 39 | assertResult(result, "/vue3_admin_uniapp_one"); |
| 32 | } | 40 | } |
| 33 | 41 | ||
| 42 | + @Test | ||
| 43 | + public void testExecute_dictSearch() { | ||
| 44 | + // 准备参数 | ||
| 45 | + CodegenTableDO table = getTable("student") | ||
| 46 | + .setFrontType(CodegenFrontTypeEnum.VUE3_ADMIN_UNIAPP_WOT.getType()) | ||
| 47 | + .setTemplateType(CodegenTemplateTypeEnum.ONE.getType()); | ||
| 48 | + List<CodegenColumnDO> columns = getColumnList("student"); | ||
| 49 | + | ||
| 50 | + // 调用并断言数字、布尔字典 | ||
| 51 | + Map<String, String> result = codegenEngine.execute(DbType.MYSQL, table, columns, null, null); | ||
| 52 | + String searchForm = result.get("yudao-ui-admin-uniapp/src/pages-infra/demo/components/search-form.vue"); | ||
| 53 | + assertFalse(searchForm.contains("<wd-radio")); | ||
| 54 | + assertTrue(searchForm.contains("all-option")); | ||
| 55 | + assertTrue(searchForm.contains(":columns=\"getIntDictOptions(DICT_TYPE.SYSTEM_USER_SEX)\"")); | ||
| 56 | + assertTrue(searchForm.contains(":columns=\"getBoolDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING)\"")); | ||
| 57 | + | ||
| 58 | + // 调用并断言字符串字典 | ||
| 59 | + columns.stream().filter(column -> "sex".equals(column.getJavaField())).findFirst().orElseThrow() | ||
| 60 | + .setJavaType("String"); | ||
| 61 | + result = codegenEngine.execute(DbType.MYSQL, table, columns, null, null); | ||
| 62 | + searchForm = result.get("yudao-ui-admin-uniapp/src/pages-infra/demo/components/search-form.vue"); | ||
| 63 | + assertTrue(searchForm.contains(":columns=\"getStrDictOptions(DICT_TYPE.SYSTEM_USER_SEX)\"")); | ||
| 64 | + assertTrue(searchForm.contains("sex: undefined as string | undefined,")); | ||
| 65 | + } | ||
| 66 | + | ||
| 67 | + @Test | ||
| 68 | + public void testExecute_tree() { | ||
| 69 | + // 准备参数 | ||
| 70 | + CodegenTableDO table = getTable("category") | ||
| 71 | + .setFrontType(CodegenFrontTypeEnum.VUE3_ADMIN_UNIAPP_WOT.getType()) | ||
| 72 | + .setTemplateType(CodegenTemplateTypeEnum.TREE.getType()); | ||
| 73 | + List<CodegenColumnDO> columns = getColumnList("category"); | ||
| 74 | + | ||
| 75 | + // 调用 | ||
| 76 | + Map<String, String> result = codegenEngine.execute(DbType.MYSQL, table, columns, null, null); | ||
| 77 | + // 断言 | ||
| 78 | + assertResult(result, "/vue3_admin_uniapp_tree"); | ||
| 79 | + } | ||
| 80 | + | ||
| 81 | + @Test | ||
| 82 | + public void testExecute_semantics() { | ||
| 83 | + // 准备参数 | ||
| 84 | + CodegenTableDO table = getTable("student") | ||
| 85 | + .setFrontType(CodegenFrontTypeEnum.VUE3_ADMIN_UNIAPP_WOT.getType()) | ||
| 86 | + .setTemplateType(CodegenTemplateTypeEnum.ONE.getType()); | ||
| 87 | + List<CodegenColumnDO> columns = getColumnList("student"); | ||
| 88 | + columns.stream().filter(column -> "memo".equals(column.getJavaField())).findFirst().orElseThrow() | ||
| 89 | + .setJavaType("Float").setHtmlType("input"); | ||
| 90 | + | ||
| 91 | + // 调用 | ||
| 92 | + Map<String, String> result = codegenEngine.execute(DbType.MYSQL, table, columns, null, null); | ||
| 93 | + // 断言 | ||
| 94 | + String api = result.get("yudao-ui-admin-uniapp/src/api/infra/demo/index.ts"); | ||
| 95 | + assertTrue(api.contains("birthday: number")); | ||
| 96 | + assertTrue(api.contains("createTime?: number")); | ||
| 97 | + assertTrue(api.contains("memo: number")); | ||
| 98 | + assertFalse(api.contains("createTime?: Date")); | ||
| 99 | + String form = result.get("yudao-ui-admin-uniapp/src/pages-infra/demo/form/index.vue"); | ||
| 100 | + assertTrue(form.contains("v-model=\"formData.memo\"")); | ||
| 101 | + assertTrue(form.contains(":precision=\"2\"")); | ||
| 102 | + String detail = result.get("yudao-ui-admin-uniapp/src/pages-infra/demo/detail/index.vue"); | ||
| 103 | + assertTrue(detail.contains("type=\"danger\"")); | ||
| 104 | + assertFalse(detail.contains("type=\"error\"")); | ||
| 105 | + } | ||
| 106 | + | ||
| 107 | + @Test | ||
| 108 | + public void testExecute_stringPrimaryKey() { | ||
| 109 | + // 准备主表 | ||
| 110 | + CodegenTableDO table = getTable("student") | ||
| 111 | + .setFrontType(CodegenFrontTypeEnum.VUE3_ADMIN_UNIAPP_WOT.getType()) | ||
| 112 | + .setTemplateType(CodegenTemplateTypeEnum.MASTER_ERP.getType()); | ||
| 113 | + List<CodegenColumnDO> columns = getColumnList("student"); | ||
| 114 | + columns.stream().filter(CodegenColumnDO::getPrimaryKey).findFirst().orElseThrow().setJavaType("String"); | ||
| 115 | + // 准备子表 | ||
| 116 | + CodegenTableDO contactTable = getTable("contact") | ||
| 117 | + .setTemplateType(CodegenTemplateTypeEnum.SUB.getType()) | ||
| 118 | + .setFrontType(CodegenFrontTypeEnum.VUE3_ADMIN_UNIAPP_WOT.getType()) | ||
| 119 | + .setSubJoinColumnId(100L).setSubJoinMany(true); | ||
| 120 | + List<CodegenColumnDO> contactColumns = getColumnList("contact"); | ||
| 121 | + contactColumns.stream().filter(CodegenColumnDO::getPrimaryKey).findFirst().orElseThrow().setJavaType("String"); | ||
| 122 | + contactColumns.stream().filter(column -> Objects.equals(column.getId(), 100L)) | ||
| 123 | + .findFirst().orElseThrow().setJavaType("String"); | ||
| 124 | + | ||
| 125 | + // 调用 | ||
| 126 | + Map<String, String> result = codegenEngine.execute(DbType.MYSQL, table, columns, | ||
| 127 | + List.of(contactTable), List.of(contactColumns)); | ||
| 128 | + // 断言 | ||
| 129 | + String api = result.get("yudao-ui-admin-uniapp/src/api/infra/demo/index.ts"); | ||
| 130 | + assertTrue(api.contains("return http.post<string>('/infra/student/create', data)")); | ||
| 131 | + assertTrue(api.contains("studentId: string")); | ||
| 132 | + assertTrue(api.contains("getStudentContact(id: string)")); | ||
| 133 | + assertTrue(api.contains("deleteStudentContact(id: string)")); | ||
| 134 | + String detail = result.get("yudao-ui-admin-uniapp/src/pages-infra/demo/detail/index.vue"); | ||
| 135 | + assertFalse(detail.contains("Number(props.id)")); | ||
| 136 | + String subForm = result.get("yudao-ui-admin-uniapp/src/pages-infra/demo/student-contact/form/index.vue"); | ||
| 137 | + assertTrue(subForm.contains("id?: string")); | ||
| 138 | + assertTrue(subForm.contains("studentId: string")); | ||
| 139 | + } | ||
| 140 | + | ||
| 141 | + @Test | ||
| 142 | + public void testExecute_treeSearch() { | ||
| 143 | + // 准备参数 | ||
| 144 | + CodegenTableDO table = getTable("category") | ||
| 145 | + .setFrontType(CodegenFrontTypeEnum.VUE3_ADMIN_UNIAPP_WOT.getType()) | ||
| 146 | + .setTemplateType(CodegenTemplateTypeEnum.TREE.getType()); | ||
| 147 | + List<CodegenColumnDO> columns = getColumnList("category"); | ||
| 148 | + | ||
| 149 | + // 调用 | ||
| 150 | + Map<String, String> result = codegenEngine.execute(DbType.MYSQL, table, columns, null, null); | ||
| 151 | + // 断言 | ||
| 152 | + String index = result.get("yudao-ui-admin-uniapp/src/pages-infra/demo/index.vue"); | ||
| 153 | + assertTrue(index.contains("currentParentId.value === 0\n ? list.value")); | ||
| 154 | + assertFalse(index.contains("list.value.filter")); | ||
| 155 | + String breadcrumb = result.get("yudao-ui-admin-uniapp/src/pages-infra/demo/components/breadcrumb.vue"); | ||
| 156 | + assertTrue(breadcrumb.contains("modelValue: number")); | ||
| 157 | + assertFalse(breadcrumb.contains("TreeValue")); | ||
| 158 | + } | ||
| 159 | + | ||
| 160 | + @Test | ||
| 161 | + public void testExecute_duplicateErpSymbol() { | ||
| 162 | + // 准备主表 | ||
| 163 | + CodegenTableDO table = getTable("student") | ||
| 164 | + .setFrontType(CodegenFrontTypeEnum.VUE3_ADMIN_UNIAPP_WOT.getType()) | ||
| 165 | + .setTemplateType(CodegenTemplateTypeEnum.MASTER_ERP.getType()); | ||
| 166 | + List<CodegenColumnDO> columns = getColumnList("student"); | ||
| 167 | + // 准备两个归一化名称相同的子表 | ||
| 168 | + CodegenTableDO crmItemTable = getTable("contact").setModuleName("crm").setClassName("CrmOrderItem") | ||
| 169 | + .setTemplateType(CodegenTemplateTypeEnum.SUB.getType()) | ||
| 170 | + .setFrontType(CodegenFrontTypeEnum.VUE3_ADMIN_UNIAPP_WOT.getType()) | ||
| 171 | + .setSubJoinColumnId(100L).setSubJoinMany(true); | ||
| 172 | + CodegenTableDO erpItemTable = getTable("contact").setModuleName("erp").setClassName("ErpOrderItem") | ||
| 173 | + .setTemplateType(CodegenTemplateTypeEnum.SUB.getType()) | ||
| 174 | + .setFrontType(CodegenFrontTypeEnum.VUE3_ADMIN_UNIAPP_WOT.getType()) | ||
| 175 | + .setSubJoinColumnId(100L).setSubJoinMany(true); | ||
| 176 | + List<CodegenColumnDO> crmItemColumns = getColumnList("contact"); | ||
| 177 | + List<CodegenColumnDO> erpItemColumns = getColumnList("contact"); | ||
| 178 | + | ||
| 179 | + // 调用 | ||
| 180 | + ServiceException exception = assertThrows(ServiceException.class, | ||
| 181 | + () -> codegenEngine.execute(DbType.MYSQL, table, columns, | ||
| 182 | + Arrays.asList(crmItemTable, erpItemTable), Arrays.asList(crmItemColumns, erpItemColumns))); | ||
| 183 | + // 断言 | ||
| 184 | + assertTrue(exception.getMessage().contains("主子表规范化类名(OrderItem)重复")); | ||
| 185 | + } | ||
| 186 | + | ||
| 187 | + @Test | ||
| 188 | + public void testExecute_duplicateMasterSymbol() { | ||
| 189 | + // 准备主表 | ||
| 190 | + CodegenTableDO table = getTable("student") | ||
| 191 | + .setFrontType(CodegenFrontTypeEnum.VUE3_ELEMENT_PLUS.getType()) | ||
| 192 | + .setTemplateType(CodegenTemplateTypeEnum.MASTER_NORMAL.getType()); | ||
| 193 | + List<CodegenColumnDO> columns = getColumnList("student"); | ||
| 194 | + // 准备两个规范化名称相同的子表 | ||
| 195 | + CodegenTableDO crmItemTable = getTable("contact").setModuleName("crm").setClassName("CrmOrderItem") | ||
| 196 | + .setTemplateType(CodegenTemplateTypeEnum.SUB.getType()).setSubJoinColumnId(100L).setSubJoinMany(true); | ||
| 197 | + CodegenTableDO erpItemTable = getTable("contact").setModuleName("erp").setClassName("ErpOrderItem") | ||
| 198 | + .setTemplateType(CodegenTemplateTypeEnum.SUB.getType()).setSubJoinColumnId(100L).setSubJoinMany(true); | ||
| 199 | + List<CodegenColumnDO> crmItemColumns = getColumnList("contact"); | ||
| 200 | + List<CodegenColumnDO> erpItemColumns = getColumnList("contact"); | ||
| 201 | + | ||
| 202 | + // 调用并断言 | ||
| 203 | + ServiceException exception = assertThrows(ServiceException.class, | ||
| 204 | + () -> codegenEngine.execute(DbType.MYSQL, table, columns, | ||
| 205 | + Arrays.asList(crmItemTable, erpItemTable), Arrays.asList(crmItemColumns, erpItemColumns))); | ||
| 206 | + assertTrue(exception.getMessage().contains("主子表规范化类名(OrderItem)重复")); | ||
| 207 | + } | ||
| 208 | + | ||
| 209 | + @Test | ||
| 210 | + public void testExecute_duplicateMasterFieldNormal() { | ||
| 211 | + testExecute_duplicateMasterField(CodegenTemplateTypeEnum.MASTER_NORMAL); | ||
| 212 | + } | ||
| 213 | + | ||
| 214 | + @Test | ||
| 215 | + public void testExecute_duplicateMasterFieldInner() { | ||
| 216 | + testExecute_duplicateMasterField(CodegenTemplateTypeEnum.MASTER_INNER); | ||
| 217 | + } | ||
| 218 | + | ||
| 219 | + @Test | ||
| 220 | + public void testExecute_duplicateMasterFieldWithMainNormal() { | ||
| 221 | + testExecute_duplicateMasterFieldWithMain(CodegenTemplateTypeEnum.MASTER_NORMAL); | ||
| 222 | + } | ||
| 223 | + | ||
| 224 | + @Test | ||
| 225 | + public void testExecute_duplicateMasterFieldWithMainInner() { | ||
| 226 | + testExecute_duplicateMasterFieldWithMain(CodegenTemplateTypeEnum.MASTER_INNER); | ||
| 227 | + } | ||
| 228 | + | ||
| 229 | + @Test | ||
| 230 | + public void testExecute_duplicateMasterFieldErp() { | ||
| 231 | + // 准备主表 | ||
| 232 | + CodegenTableDO table = getTable("student") | ||
| 233 | + .setFrontType(CodegenFrontTypeEnum.VUE3_ADMIN_UNIAPP_WOT.getType()) | ||
| 234 | + .setTemplateType(CodegenTemplateTypeEnum.MASTER_ERP.getType()); | ||
| 235 | + List<CodegenColumnDO> columns = getColumnList("student"); | ||
| 236 | + // 准备属性名同为 items 的子表:Item 一对多、Items 一对一 | ||
| 237 | + CodegenTableDO crmItemTable = getTable("contact").setModuleName("crm").setClassName("CrmItem") | ||
| 238 | + .setTemplateType(CodegenTemplateTypeEnum.SUB.getType()).setSubJoinColumnId(100L).setSubJoinMany(true); | ||
| 239 | + CodegenTableDO erpItemsTable = getTable("contact").setModuleName("erp").setClassName("ErpItems") | ||
| 240 | + .setTemplateType(CodegenTemplateTypeEnum.SUB.getType()).setSubJoinColumnId(100L).setSubJoinMany(false); | ||
| 241 | + | ||
| 242 | + // 调用 | ||
| 243 | + Map<String, String> result = codegenEngine.execute(DbType.MYSQL, table, columns, | ||
| 244 | + Arrays.asList(crmItemTable, erpItemsTable), | ||
| 245 | + Arrays.asList(getColumnList("contact"), getColumnList("contact"))); | ||
| 246 | + // 断言 | ||
| 247 | + assertTrue(result.containsKey("yudao-ui-admin-uniapp/src/pages-infra/demo/item/form/index.vue")); | ||
| 248 | + assertTrue(result.containsKey("yudao-ui-admin-uniapp/src/pages-infra/demo/items/form/index.vue")); | ||
| 249 | + } | ||
| 250 | + | ||
| 251 | + @Test | ||
| 252 | + public void testRegisterGeneratedSource_duplicatePath() { | ||
| 253 | + // 准备参数 | ||
| 254 | + Map<String, String> generatedSources = new HashMap<>(); | ||
| 255 | + Map<String, Object> bindingMap = new HashMap<>(); | ||
| 256 | + bindingMap.put("subIndex", 0); | ||
| 257 | + CodegenEngine.registerGeneratedSource(generatedSources, "same.vue", "form_sub.vm", bindingMap); | ||
| 258 | + bindingMap.put("subIndex", 1); | ||
| 259 | + | ||
| 260 | + // 调用 | ||
| 261 | + IllegalStateException exception = assertThrows(IllegalStateException.class, | ||
| 262 | + () -> CodegenEngine.registerGeneratedSource(generatedSources, "same.vue", "form_sub.vm", bindingMap)); | ||
| 263 | + // 断言 | ||
| 264 | + assertTrue(exception.getMessage().contains("生成文件路径重复:same.vue")); | ||
| 265 | + assertTrue(exception.getMessage().contains("form_sub.vm[subIndex=0]")); | ||
| 266 | + assertTrue(exception.getMessage().contains("form_sub.vm[subIndex=1]")); | ||
| 267 | + } | ||
| 268 | + | ||
| 269 | + @Test | ||
| 270 | + public void testExecute_masterNormal() { | ||
| 271 | + testExecute_master(CodegenTemplateTypeEnum.MASTER_NORMAL, "/vue3_admin_uniapp_master_normal"); | ||
| 272 | + } | ||
| 273 | + | ||
| 274 | + @Test | ||
| 275 | + public void testExecute_masterInner() { | ||
| 276 | + testExecute_master(CodegenTemplateTypeEnum.MASTER_INNER, "/vue3_admin_uniapp_master_inner"); | ||
| 277 | + } | ||
| 278 | + | ||
| 279 | + @Test | ||
| 280 | + public void testExecute_masterErp() { | ||
| 281 | + testExecute_master(CodegenTemplateTypeEnum.MASTER_ERP, "/vue3_admin_uniapp_master_erp"); | ||
| 282 | + } | ||
| 283 | + | ||
| 284 | + private void testExecute_duplicateMasterField(CodegenTemplateTypeEnum templateType) { | ||
| 285 | + // 准备主表 | ||
| 286 | + CodegenTableDO table = getTable("student") | ||
| 287 | + .setFrontType(CodegenFrontTypeEnum.VUE3_ELEMENT_PLUS.getType()) | ||
| 288 | + .setTemplateType(templateType.getType()); | ||
| 289 | + List<CodegenColumnDO> columns = getColumnList("student"); | ||
| 290 | + // 准备属性名同为 items 的子表:Item 一对多、Items 一对一 | ||
| 291 | + CodegenTableDO crmItemTable = getTable("contact").setModuleName("crm").setClassName("CrmItem") | ||
| 292 | + .setTemplateType(CodegenTemplateTypeEnum.SUB.getType()).setSubJoinColumnId(100L).setSubJoinMany(true); | ||
| 293 | + CodegenTableDO erpItemsTable = getTable("contact").setModuleName("erp").setClassName("ErpItems") | ||
| 294 | + .setTemplateType(CodegenTemplateTypeEnum.SUB.getType()).setSubJoinColumnId(100L).setSubJoinMany(false); | ||
| 295 | + List<CodegenColumnDO> crmItemColumns = getColumnList("contact"); | ||
| 296 | + List<CodegenColumnDO> erpItemsColumns = getColumnList("contact"); | ||
| 297 | + | ||
| 298 | + // 调用并断言 | ||
| 299 | + ServiceException exception = assertThrows(ServiceException.class, | ||
| 300 | + () -> codegenEngine.execute(DbType.MYSQL, table, columns, | ||
| 301 | + Arrays.asList(crmItemTable, erpItemsTable), Arrays.asList(crmItemColumns, erpItemsColumns))); | ||
| 302 | + assertTrue(exception.getMessage().contains("主子表属性名(items)重复")); | ||
| 303 | + } | ||
| 304 | + | ||
| 305 | + private void testExecute_duplicateMasterFieldWithMain(CodegenTemplateTypeEnum templateType) { | ||
| 306 | + // 准备主表 | ||
| 307 | + CodegenTableDO table = getTable("student") | ||
| 308 | + .setFrontType(CodegenFrontTypeEnum.VUE3_ELEMENT_PLUS.getType()) | ||
| 309 | + .setTemplateType(templateType.getType()); | ||
| 310 | + List<CodegenColumnDO> columns = getColumnList("student"); | ||
| 311 | + columns.stream().filter(column -> "memo".equals(column.getJavaField())).findFirst().orElseThrow() | ||
| 312 | + .setJavaField("items"); | ||
| 313 | + // 准备一对多子表 Item,生成属性名 items | ||
| 314 | + CodegenTableDO itemTable = getTable("contact").setModuleName("crm").setClassName("CrmItem") | ||
| 315 | + .setTemplateType(CodegenTemplateTypeEnum.SUB.getType()).setSubJoinColumnId(100L).setSubJoinMany(true); | ||
| 316 | + | ||
| 317 | + // 调用并断言 | ||
| 318 | + ServiceException exception = assertThrows(ServiceException.class, | ||
| 319 | + () -> codegenEngine.execute(DbType.MYSQL, table, columns, | ||
| 320 | + List.of(itemTable), List.of(getColumnList("contact")))); | ||
| 321 | + assertTrue(exception.getMessage().contains("主子表属性名(items)重复")); | ||
| 322 | + } | ||
| 323 | + | ||
| 324 | + private void testExecute_master(CodegenTemplateTypeEnum templateType, String path) { | ||
| 325 | + // 准备主表 | ||
| 326 | + CodegenTableDO table = getTable("student") | ||
| 327 | + .setFrontType(CodegenFrontTypeEnum.VUE3_ADMIN_UNIAPP_WOT.getType()) | ||
| 328 | + .setTemplateType(templateType.getType()); | ||
| 329 | + List<CodegenColumnDO> columns = getColumnList("student"); | ||
| 330 | + // 准备一对多子表 | ||
| 331 | + CodegenTableDO contactTable = getTable("contact") | ||
| 332 | + .setTemplateType(CodegenTemplateTypeEnum.SUB.getType()) | ||
| 333 | + .setFrontType(CodegenFrontTypeEnum.VUE3_ADMIN_UNIAPP_WOT.getType()) | ||
| 334 | + .setSubJoinColumnId(100L).setSubJoinMany(true); | ||
| 335 | + List<CodegenColumnDO> contactColumns = getColumnList("contact"); | ||
| 336 | + // 准备一对一子表 | ||
| 337 | + CodegenTableDO teacherTable = getTable("teacher") | ||
| 338 | + .setTemplateType(CodegenTemplateTypeEnum.SUB.getType()) | ||
| 339 | + .setFrontType(CodegenFrontTypeEnum.VUE3_ADMIN_UNIAPP_WOT.getType()) | ||
| 340 | + .setSubJoinColumnId(200L).setSubJoinMany(false); | ||
| 341 | + List<CodegenColumnDO> teacherColumns = getColumnList("teacher"); | ||
| 342 | + | ||
| 343 | + // 调用 | ||
| 344 | + Map<String, String> result = codegenEngine.execute(DbType.MYSQL, table, columns, | ||
| 345 | + Arrays.asList(contactTable, teacherTable), Arrays.asList(contactColumns, teacherColumns)); | ||
| 346 | + // 断言 | ||
| 347 | + assertResult(result, path); | ||
| 348 | + if (templateType != CodegenTemplateTypeEnum.MASTER_ERP) { | ||
| 349 | + String api = result.get("yudao-ui-admin-uniapp/src/api/infra/demo/index.ts"); | ||
| 350 | + assertTrue(api.contains("studentTeacher?: StudentTeacher | null")); | ||
| 351 | + String form = result.get("yudao-ui-admin-uniapp/src/pages-infra/demo/form/index.vue"); | ||
| 352 | + assertTrue(form.contains("ref<Partial<StudentTeacher> | null>(null)")); | ||
| 353 | + assertTrue(form.contains("studentTeacher.value = studentTeacherData || null")); | ||
| 354 | + assertTrue(form.contains(":model-value=\"item.birthday || Date.now()\"")); | ||
| 355 | + assertTrue(form.contains("v-model=\"item.sex\"")); | ||
| 356 | + assertTrue(form.contains("<yd-upload-img v-model=\"item.avatar\"")); | ||
| 357 | + assertTrue(form.contains("<yd-upload-file v-model=\"item.video\"")); | ||
| 358 | + assertTrue(form.contains("<yd-upload-file v-model=\"item.video\" :limit=\"1\"")); | ||
| 359 | + assertTrue(form.contains("v-model=\"item.description\"")); | ||
| 360 | + assertTrue(form.contains("v-model=\"item.memo\"")); | ||
| 361 | + assertTrue(form.contains("birthday: undefined")); | ||
| 362 | + assertTrue(form.contains("function validateSubTables()")); | ||
| 363 | + } else { | ||
| 364 | + String contactForm = result.get( | ||
| 365 | + "yudao-ui-admin-uniapp/src/pages-infra/demo/student-contact/form/index.vue"); | ||
| 366 | + assertTrue(contactForm.contains(":model-value=\"formData.birthday || Date.now()\"")); | ||
| 367 | + assertTrue(contactForm.contains("v-model=\"formData.sex\"")); | ||
| 368 | + assertTrue(contactForm.contains("<yd-upload-img v-model=\"formData.avatar\"")); | ||
| 369 | + assertTrue(contactForm.contains("<yd-upload-file v-model=\"formData.video\"")); | ||
| 370 | + assertTrue(contactForm.contains("<yd-upload-file v-model=\"formData.video\" :limit=\"1\"")); | ||
| 371 | + assertTrue(contactForm.contains("v-model=\"formData.description\"")); | ||
| 372 | + assertTrue(contactForm.contains("v-model=\"formData.memo\"")); | ||
| 373 | + assertTrue(contactForm.contains("birthday: undefined")); | ||
| 374 | + assertTrue(contactForm.contains(":columns=\"getBoolDictOptions")); | ||
| 375 | + } | ||
| 376 | + } | ||
| 377 | + | ||
| 34 | } | 378 | } |
yudao-module-infra/src/test/java/cn/iocoder/yudao/module/infra/service/codegen/inner/CodegenEngineVben5AntdvNextTest.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.infra.service.codegen.inner; | ||
| 2 | + | ||
| 3 | +import cn.hutool.core.io.resource.ResourceUtil; | ||
| 4 | +import cn.iocoder.yudao.module.infra.dal.dataobject.codegen.CodegenColumnDO; | ||
| 5 | +import cn.iocoder.yudao.module.infra.dal.dataobject.codegen.CodegenTableDO; | ||
| 6 | +import cn.iocoder.yudao.module.infra.enums.codegen.CodegenFrontTypeEnum; | ||
| 7 | +import cn.iocoder.yudao.module.infra.enums.codegen.CodegenTemplateTypeEnum; | ||
| 8 | +import com.baomidou.mybatisplus.annotation.DbType; | ||
| 9 | +import org.junit.jupiter.api.Test; | ||
| 10 | + | ||
| 11 | +import java.util.Arrays; | ||
| 12 | +import java.util.List; | ||
| 13 | +import java.util.Map; | ||
| 14 | +import java.util.regex.Pattern; | ||
| 15 | + | ||
| 16 | +import static org.junit.jupiter.api.Assertions.assertEquals; | ||
| 17 | +import static org.junit.jupiter.api.Assertions.assertFalse; | ||
| 18 | +import static org.junit.jupiter.api.Assertions.assertNotNull; | ||
| 19 | +import static org.junit.jupiter.api.Assertions.assertTrue; | ||
| 20 | + | ||
| 21 | +/** | ||
| 22 | + * {@link CodegenEngine} 的 Vue3 + Vben5 + Antdv Next 单元测试 | ||
| 23 | + * | ||
| 24 | + * @author 芋道源码 | ||
| 25 | + */ | ||
| 26 | +public class CodegenEngineVben5AntdvNextTest extends CodegenEngineAbstractTest { | ||
| 27 | + | ||
| 28 | + private static final Pattern ANTDV_NEXT_VALUE_IMPORT_PATTERN = Pattern.compile( | ||
| 29 | + "import\\s*\\{([^}]*)}\\s*from\\s*'antdv-next';?", Pattern.DOTALL); | ||
| 30 | + private static final Pattern RANGE_PICKER_COMPONENT_PATTERN = Pattern.compile("<RangePicker\\b"); | ||
| 31 | + private static final Pattern RANGE_PICKER_HELPER_BINDING_PATTERN = Pattern.compile( | ||
| 32 | + "\\bv-bind\\s*=\\s*([\"'])\\s*getRangePickerDefaultProps\\s*\\(\\s*\\)\\s*\\1"); | ||
| 33 | + private static final Pattern RANGE_PICKER_HELPER_CALL_PATTERN = Pattern.compile( | ||
| 34 | + "\\bgetRangePickerDefaultProps\\s*\\(\\s*\\)"); | ||
| 35 | + private static final Pattern RANGE_PICKER_HELPER_IMPORT_PATTERN = Pattern.compile( | ||
| 36 | + "import\\s*\\{[^}]*\\bgetRangePickerDefaultProps\\b[^}]*}" | ||
| 37 | + + "\\s*from\\s*'#/utils(?:/rangePickerProps)?';?", Pattern.DOTALL); | ||
| 38 | + private static final Pattern SCHEMA_RANGE_PICKER_COMPONENT_PATTERN = Pattern.compile( | ||
| 39 | + "\\bcomponent\\s*:\\s*([\"'])RangePicker\\1"); | ||
| 40 | + private static final Pattern SCHEMA_RANGE_PICKER_HELPER_SPREAD_PATTERN = Pattern.compile( | ||
| 41 | + "\\.\\.\\.\\s*getRangePickerDefaultProps\\s*\\(\\s*\\)"); | ||
| 42 | + | ||
| 43 | + private static final String IMPORT_FORM_PATH = | ||
| 44 | + "yudao-ui-admin-vben/src/views/infra/demo/modules/import-form.vue"; | ||
| 45 | + private static final String SCHEMA_DATA_PATH = | ||
| 46 | + "yudao-ui-admin-vben/src/views/infra/demo/data.ts"; | ||
| 47 | + private static final String STUDENT_CONTACT_FORM_PATH = | ||
| 48 | + "yudao-ui-admin-vben/src/views/infra/demo/modules/student-contact-form.vue"; | ||
| 49 | + private static final String STUDENT_TEACHER_FORM_PATH = | ||
| 50 | + "yudao-ui-admin-vben/src/views/infra/demo/modules/student-teacher-form.vue"; | ||
| 51 | + | ||
| 52 | + private static final List<String> INNER_TEMPLATE_PATHS = Arrays.asList( | ||
| 53 | + "codegen/vue3_vben5_antdv_next/general/views/modules/form_sub_inner.vue.vm", | ||
| 54 | + "codegen/vue3_vben5_antdv_next/general/views/modules/list_sub_inner.vue.vm", | ||
| 55 | + "codegen/vue3_vben5_antdv_next/schema/views/modules/form_sub_inner.vue.vm", | ||
| 56 | + "codegen/vue3_vben5_antdv_next/schema/views/modules/list_sub_inner.vue.vm"); | ||
| 57 | + | ||
| 58 | + @Test | ||
| 59 | + public void testExecute_schemaMasterNormal() { | ||
| 60 | + // 调用 | ||
| 61 | + Map<String, String> result = executeMaster(CodegenFrontTypeEnum.VUE3_VBEN5_ANTDV_NEXT_SCHEMA.getType(), | ||
| 62 | + CodegenTemplateTypeEnum.MASTER_NORMAL); | ||
| 63 | + | ||
| 64 | + // 断言 | ||
| 65 | + assertGeneratedCodeCompatibleWithAntdvNext(result); | ||
| 66 | + assertSchemaRangePickerGenerated(result); | ||
| 67 | + assertTabsGenerated(result); | ||
| 68 | + } | ||
| 69 | + | ||
| 70 | + @Test | ||
| 71 | + public void testExecute_schemaMasterErp() { | ||
| 72 | + // 调用 | ||
| 73 | + Map<String, String> result = executeMaster(CodegenFrontTypeEnum.VUE3_VBEN5_ANTDV_NEXT_SCHEMA.getType(), | ||
| 74 | + CodegenTemplateTypeEnum.MASTER_ERP); | ||
| 75 | + | ||
| 76 | + // 断言 | ||
| 77 | + assertGeneratedCodeCompatibleWithAntdvNext(result); | ||
| 78 | + assertSchemaRangePickerGenerated(result); | ||
| 79 | + assertTabsGenerated(result); | ||
| 80 | + } | ||
| 81 | + | ||
| 82 | + @Test | ||
| 83 | + public void testExecute_schemaMasterInner() { | ||
| 84 | + // 调用 | ||
| 85 | + Map<String, String> result = executeMaster(CodegenFrontTypeEnum.VUE3_VBEN5_ANTDV_NEXT_SCHEMA.getType(), | ||
| 86 | + CodegenTemplateTypeEnum.MASTER_INNER); | ||
| 87 | + | ||
| 88 | + // 断言 | ||
| 89 | + assertGeneratedCodeCompatibleWithAntdvNext(result); | ||
| 90 | + assertSchemaRangePickerGenerated(result); | ||
| 91 | + assertTabsGenerated(result); | ||
| 92 | + } | ||
| 93 | + | ||
| 94 | + @Test | ||
| 95 | + public void testExecute_generalTree() { | ||
| 96 | + // 调用 | ||
| 97 | + Map<String, String> result = executeTree(CodegenFrontTypeEnum.VUE3_VBEN5_ANTDV_NEXT_GENERAL.getType()); | ||
| 98 | + | ||
| 99 | + // 断言 | ||
| 100 | + assertGeneratedCodeCompatibleWithAntdvNext(result); | ||
| 101 | + assertRuleGenerated(result); | ||
| 102 | + assertComponentGenerated(result, "FormItem"); | ||
| 103 | + assertComponentGenerated(result, "TreeSelect"); | ||
| 104 | + } | ||
| 105 | + | ||
| 106 | + @Test | ||
| 107 | + public void testExecute_generalMasterNormal() { | ||
| 108 | + // 调用 | ||
| 109 | + Map<String, String> result = executeMaster(CodegenFrontTypeEnum.VUE3_VBEN5_ANTDV_NEXT_GENERAL.getType(), | ||
| 110 | + CodegenTemplateTypeEnum.MASTER_NORMAL); | ||
| 111 | + | ||
| 112 | + // 断言 | ||
| 113 | + assertGeneratedCodeCompatibleWithAntdvNext(result); | ||
| 114 | + assertRuleGenerated(result); | ||
| 115 | + assertGeneralCrudComponentsGenerated(result); | ||
| 116 | + assertTabsGenerated(result); | ||
| 117 | + } | ||
| 118 | + | ||
| 119 | + @Test | ||
| 120 | + public void testExecute_generalMasterNormal_oneToOneJoinColumnOnlySelect() { | ||
| 121 | + // 调用 | ||
| 122 | + Map<String, String> result = executeGeneralMasterNormalWithOneToOneJoinSelect(); | ||
| 123 | + | ||
| 124 | + // 断言 | ||
| 125 | + assertGeneratedCodeCompatibleWithAntdvNext(result); | ||
| 126 | + String content = result.get(STUDENT_TEACHER_FORM_PATH); | ||
| 127 | + assertNotNull(content, STUDENT_TEACHER_FORM_PATH + ":未生成一对一子表表单"); | ||
| 128 | + assertTrue(content.contains("<Select v-model:value=\"formData.studentId\""), | ||
| 129 | + STUDENT_TEACHER_FORM_PATH + ":应渲染 join 列的 Select 组件"); | ||
| 130 | + assertTrue(content.contains("<SelectOption"), STUDENT_TEACHER_FORM_PATH + ":应渲染 SelectOption 组件"); | ||
| 131 | + assertRootValueImport(STUDENT_TEACHER_FORM_PATH, content, "\\bSelectOption\\b"); | ||
| 132 | + } | ||
| 133 | + | ||
| 134 | + @Test | ||
| 135 | + public void testExecute_generalMasterNormal_oneToManyJoinColumnOnlySelect() { | ||
| 136 | + // 调用 | ||
| 137 | + Map<String, String> result = executeGeneralMasterNormalWithOneToManyJoinSelect(); | ||
| 138 | + | ||
| 139 | + // 断言 | ||
| 140 | + assertGeneratedCodeCompatibleWithAntdvNext(result); | ||
| 141 | + String content = result.get(STUDENT_CONTACT_FORM_PATH); | ||
| 142 | + assertNotNull(content, STUDENT_CONTACT_FORM_PATH + ":未生成一对多子表表单"); | ||
| 143 | + assertTrue(content.contains("<VxeTable"), STUDENT_CONTACT_FORM_PATH + ":应生成一对多子表表格"); | ||
| 144 | + assertFalse(content.contains("<VxeColumn field=\"studentId\""), | ||
| 145 | + STUDENT_CONTACT_FORM_PATH + ":不应生成 join 列"); | ||
| 146 | + assertFalse(content.contains("<Select v-model:value=\"row.studentId\""), | ||
| 147 | + STUDENT_CONTACT_FORM_PATH + ":不应渲染 join 列的 Select 组件"); | ||
| 148 | + assertFalse(content.contains("<SelectOption"), STUDENT_CONTACT_FORM_PATH + ":不应渲染 SelectOption 组件"); | ||
| 149 | + assertFalse(createRootImportPattern("import\\s*\\{", "\\bSelectOption\\b").matcher(content).find(), | ||
| 150 | + STUDENT_CONTACT_FORM_PATH + ":不应导入 SelectOption 组件"); | ||
| 151 | + } | ||
| 152 | + | ||
| 153 | + @Test | ||
| 154 | + public void testExecute_generalMasterInner() { | ||
| 155 | + // 调用 | ||
| 156 | + Map<String, String> result = executeMaster(CodegenFrontTypeEnum.VUE3_VBEN5_ANTDV_NEXT_GENERAL.getType(), | ||
| 157 | + CodegenTemplateTypeEnum.MASTER_INNER); | ||
| 158 | + | ||
| 159 | + // 断言 | ||
| 160 | + assertGeneratedCodeCompatibleWithAntdvNext(result); | ||
| 161 | + assertRuleGenerated(result); | ||
| 162 | + assertGeneralCrudComponentsGenerated(result); | ||
| 163 | + assertTabsGenerated(result); | ||
| 164 | + } | ||
| 165 | + | ||
| 166 | + @Test | ||
| 167 | + public void testExecute_generalMasterErp() { | ||
| 168 | + // 调用 | ||
| 169 | + Map<String, String> result = executeMaster(CodegenFrontTypeEnum.VUE3_VBEN5_ANTDV_NEXT_GENERAL.getType(), | ||
| 170 | + CodegenTemplateTypeEnum.MASTER_ERP); | ||
| 171 | + | ||
| 172 | + // 断言 | ||
| 173 | + assertGeneratedCodeCompatibleWithAntdvNext(result); | ||
| 174 | + assertRuleGenerated(result); | ||
| 175 | + assertGeneralCrudComponentsGenerated(result); | ||
| 176 | + assertTabsGenerated(result); | ||
| 177 | + } | ||
| 178 | + | ||
| 179 | + @Test | ||
| 180 | + public void testExecute_oneImportEnable() { | ||
| 181 | + // 开启 import 开关 | ||
| 182 | + codegenProperties.setImportEnable(true); | ||
| 183 | + codegenEngine.initGlobalBindingMap(); | ||
| 184 | + | ||
| 185 | + // 调用并断言 | ||
| 186 | + assertImportFormCompatibleWithAntdvNext( | ||
| 187 | + executeOne(CodegenFrontTypeEnum.VUE3_VBEN5_ANTDV_NEXT_SCHEMA.getType())); | ||
| 188 | + assertImportFormCompatibleWithAntdvNext( | ||
| 189 | + executeOne(CodegenFrontTypeEnum.VUE3_VBEN5_ANTDV_NEXT_GENERAL.getType())); | ||
| 190 | + } | ||
| 191 | + | ||
| 192 | + @Test | ||
| 193 | + public void testInnerTemplatesParseAntdvNext() { | ||
| 194 | + INNER_TEMPLATE_PATHS.forEach(path -> { | ||
| 195 | + String template = ResourceUtil.readUtf8Str(path); | ||
| 196 | + assertFalse(template.contains("codegen/vue3_vben5_antd/"), path + ":不应引用 Ant Design Vue 模板"); | ||
| 197 | + assertTrue(template.contains("codegen/vue3_vben5_antdv_next/"), path + ":应引用 Antdv Next 模板"); | ||
| 198 | + }); | ||
| 199 | + } | ||
| 200 | + | ||
| 201 | + private Map<String, String> executeOne(Integer frontType) { | ||
| 202 | + CodegenTableDO table = getTable("student") | ||
| 203 | + .setFrontType(frontType) | ||
| 204 | + .setTemplateType(CodegenTemplateTypeEnum.ONE.getType()); | ||
| 205 | + return codegenEngine.execute(DbType.MYSQL, table, getColumnList("student"), null, null); | ||
| 206 | + } | ||
| 207 | + | ||
| 208 | + private Map<String, String> executeTree(Integer frontType) { | ||
| 209 | + CodegenTableDO table = getTable("category") | ||
| 210 | + .setFrontType(frontType) | ||
| 211 | + .setTemplateType(CodegenTemplateTypeEnum.TREE.getType()); | ||
| 212 | + return codegenEngine.execute(DbType.MYSQL, table, getColumnList("category"), null, null); | ||
| 213 | + } | ||
| 214 | + | ||
| 215 | + private Map<String, String> executeGeneralMasterNormalWithOneToOneJoinSelect() { | ||
| 216 | + return executeGeneralMasterNormalWithJoinSelect("teacher", 200L, false); | ||
| 217 | + } | ||
| 218 | + | ||
| 219 | + private Map<String, String> executeGeneralMasterNormalWithOneToManyJoinSelect() { | ||
| 220 | + return executeGeneralMasterNormalWithJoinSelect("contact", 100L, true); | ||
| 221 | + } | ||
| 222 | + | ||
| 223 | + private Map<String, String> executeGeneralMasterNormalWithJoinSelect(String subTableName, | ||
| 224 | + Long subJoinColumnId, | ||
| 225 | + boolean subJoinMany) { | ||
| 226 | + CodegenTableDO table = getTable("student") | ||
| 227 | + .setFrontType(CodegenFrontTypeEnum.VUE3_VBEN5_ANTDV_NEXT_GENERAL.getType()) | ||
| 228 | + .setTemplateType(CodegenTemplateTypeEnum.MASTER_NORMAL.getType()); | ||
| 229 | + CodegenTableDO subTable = getTable(subTableName) | ||
| 230 | + .setTemplateType(CodegenTemplateTypeEnum.SUB.getType()) | ||
| 231 | + .setFrontType(CodegenFrontTypeEnum.VUE3_VBEN5_ANTDV_NEXT_GENERAL.getType()) | ||
| 232 | + .setSubJoinColumnId(subJoinColumnId).setSubJoinMany(subJoinMany); | ||
| 233 | + List<CodegenColumnDO> subColumns = getColumnList(subTableName); | ||
| 234 | + // 稀疏场景:join 列是唯一的 select,避免其它 select 掩盖 join 列对导入判定的影响。 | ||
| 235 | + subColumns.forEach(column -> { | ||
| 236 | + if (subJoinColumnId.equals(column.getId())) { | ||
| 237 | + column.setHtmlType("select"); | ||
| 238 | + column.setDictType(""); | ||
| 239 | + } else if ("select".equals(column.getHtmlType())) { | ||
| 240 | + column.setHtmlType("input"); | ||
| 241 | + } | ||
| 242 | + }); | ||
| 243 | + return codegenEngine.execute(DbType.MYSQL, table, getColumnList("student"), | ||
| 244 | + List.of(subTable), List.of(subColumns)); | ||
| 245 | + } | ||
| 246 | + | ||
| 247 | + private Map<String, String> executeMaster(Integer frontType, CodegenTemplateTypeEnum templateType) { | ||
| 248 | + // 准备参数 | ||
| 249 | + CodegenTableDO table = getTable("student") | ||
| 250 | + .setFrontType(frontType) | ||
| 251 | + .setTemplateType(templateType.getType()); | ||
| 252 | + List<CodegenColumnDO> columns = getColumnList("student"); | ||
| 253 | + // 准备参数(子表) | ||
| 254 | + CodegenTableDO contactTable = getTable("contact") | ||
| 255 | + .setTemplateType(CodegenTemplateTypeEnum.SUB.getType()) | ||
| 256 | + .setFrontType(frontType) | ||
| 257 | + .setSubJoinColumnId(100L).setSubJoinMany(true); | ||
| 258 | + List<CodegenColumnDO> contactColumns = getColumnList("contact"); | ||
| 259 | + // 准备参数(班主任) | ||
| 260 | + CodegenTableDO teacherTable = getTable("teacher") | ||
| 261 | + .setTemplateType(CodegenTemplateTypeEnum.SUB.getType()) | ||
| 262 | + .setFrontType(frontType) | ||
| 263 | + .setSubJoinColumnId(200L).setSubJoinMany(false); | ||
| 264 | + List<CodegenColumnDO> teacherColumns = getColumnList("teacher"); | ||
| 265 | + | ||
| 266 | + // 调用 | ||
| 267 | + return codegenEngine.execute(DbType.MYSQL, table, columns, | ||
| 268 | + Arrays.asList(contactTable, teacherTable), Arrays.asList(contactColumns, teacherColumns)); | ||
| 269 | + } | ||
| 270 | + | ||
| 271 | + private static void assertGeneratedCodeCompatibleWithAntdvNext(Map<String, String> result) { | ||
| 272 | + assertTrue(result.values().stream().anyMatch(content -> content.contains("from 'antdv-next")), | ||
| 273 | + "生成结果应使用 antdv-next"); | ||
| 274 | + result.forEach((filePath, content) -> { | ||
| 275 | + assertFalse(content.contains("ant-design-vue"), filePath + ":不应使用 ant-design-vue"); | ||
| 276 | + assertFalse(content.contains("antdv-next/es/"), filePath + ":不应使用未导出的 antdv-next 深层路径"); | ||
| 277 | + assertFalse(content.contains("<Form.Item"), filePath + ":不应使用 Form.Item"); | ||
| 278 | + assertFalse(content.contains("</Form.Item>"), filePath + ":不应使用 Form.Item"); | ||
| 279 | + assertRootValueImportsSorted(filePath, content); | ||
| 280 | + assertRootValueImportMatchesUsage(filePath, content, | ||
| 281 | + "\\bDateRangePicker\\s+as\\s+RangePicker\\b", "<RangePicker\\b", "RangePicker"); | ||
| 282 | + assertRootValueImportMatchesUsage(filePath, content, | ||
| 283 | + "\\bFormItem\\b", "<FormItem\\b", "FormItem"); | ||
| 284 | + assertRootValueImportMatchesUsage(filePath, content, | ||
| 285 | + "\\bSelectOption\\b", "<SelectOption\\b", "SelectOption"); | ||
| 286 | + assertRootValueImportMatchesUsage(filePath, content, | ||
| 287 | + "\\bTabPane\\b", "<TabPane\\b", "TabPane"); | ||
| 288 | + assertRootValueImportMatchesUsage(filePath, content, | ||
| 289 | + "\\bTabs\\b", "<Tabs\\b", "Tabs"); | ||
| 290 | + assertRootTypeImportMatchesUsage(filePath, content, | ||
| 291 | + "\\bRule\\b", "\\bRule\\b", "Rule"); | ||
| 292 | + assertRangePickerHelperImportMatchesUsage(filePath, content); | ||
| 293 | + if (filePath.endsWith(".vue")) { | ||
| 294 | + assertRangePickerHelperBindingMatchesComponent(filePath, content); | ||
| 295 | + } else if (filePath.endsWith(".ts")) { | ||
| 296 | + assertSchemaRangePickerHelperMatchesComponent(filePath, content); | ||
| 297 | + } | ||
| 298 | + }); | ||
| 299 | + } | ||
| 300 | + | ||
| 301 | + private static void assertImportFormCompatibleWithAntdvNext(Map<String, String> result) { | ||
| 302 | + assertGeneratedCodeCompatibleWithAntdvNext(result); | ||
| 303 | + String content = result.get(IMPORT_FORM_PATH); | ||
| 304 | + assertNotNull(content, IMPORT_FORM_PATH + ":未生成导入表单"); | ||
| 305 | + assertTrue(content.contains("<Upload"), IMPORT_FORM_PATH + ":应生成 Upload 组件"); | ||
| 306 | + assertRootValueImport(IMPORT_FORM_PATH, content, "\\bUpload\\b"); | ||
| 307 | + assertTrue(content.contains("beforeUpload(file: File)"), IMPORT_FORM_PATH + ":上传参数应使用原生 File 类型"); | ||
| 308 | + assertFalse(content.contains("UploadFile"), IMPORT_FORM_PATH + ":不应使用上传列表项类型 UploadFile"); | ||
| 309 | + assertFalse(content.contains("as unknown as File"), IMPORT_FORM_PATH + ":不应通过双重断言转换上传文件"); | ||
| 310 | + assertFalse(content.contains("FileType"), IMPORT_FORM_PATH + ":不应继续使用旧 FileType"); | ||
| 311 | + } | ||
| 312 | + | ||
| 313 | + private static void assertTabsGenerated(Map<String, String> result) { | ||
| 314 | + assertComponentGenerated(result, "Tabs"); | ||
| 315 | + assertComponentGenerated(result, "TabPane"); | ||
| 316 | + } | ||
| 317 | + | ||
| 318 | + private static void assertGeneralCrudComponentsGenerated(Map<String, String> result) { | ||
| 319 | + assertComponentGenerated(result, "FormItem"); | ||
| 320 | + assertComponentGenerated(result, "RangePicker"); | ||
| 321 | + assertComponentGenerated(result, "SelectOption"); | ||
| 322 | + } | ||
| 323 | + | ||
| 324 | + private static void assertRuleGenerated(Map<String, String> result) { | ||
| 325 | + assertTrue(result.values().stream().map(CodegenEngineVben5AntdvNextTest::removeAntdvNextRootImports) | ||
| 326 | + .anyMatch(content -> Pattern.compile("\\bRule\\b").matcher(content).find()), | ||
| 327 | + "生成结果应包含 Rule 类型校验规则"); | ||
| 328 | + } | ||
| 329 | + | ||
| 330 | + private static void assertSchemaRangePickerGenerated(Map<String, String> result) { | ||
| 331 | + String content = result.get(SCHEMA_DATA_PATH); | ||
| 332 | + assertNotNull(content, SCHEMA_DATA_PATH + ":未生成 Schema 配置"); | ||
| 333 | + long componentCount = countMatches(SCHEMA_RANGE_PICKER_COMPONENT_PATTERN, content); | ||
| 334 | + assertTrue(componentCount > 0, SCHEMA_DATA_PATH + ":应生成 RangePicker 组件配置"); | ||
| 335 | + } | ||
| 336 | + | ||
| 337 | + private static void assertComponentGenerated(Map<String, String> result, String component) { | ||
| 338 | + assertTrue(result.values().stream().anyMatch(content -> content.contains("<" + component)), | ||
| 339 | + "生成结果应包含 " + component + " 组件"); | ||
| 340 | + } | ||
| 341 | + | ||
| 342 | + private static void assertRootValueImportMatchesUsage(String filePath, String content, | ||
| 343 | + String expectedImportRegex, String usageRegex, | ||
| 344 | + String bindingName) { | ||
| 345 | + assertRootImportMatchesUsage(filePath, content, "import\\s*\\{", expectedImportRegex, | ||
| 346 | + usageRegex, bindingName); | ||
| 347 | + } | ||
| 348 | + | ||
| 349 | + private static void assertRootTypeImportMatchesUsage(String filePath, String content, | ||
| 350 | + String expectedImportRegex, String usageRegex, | ||
| 351 | + String bindingName) { | ||
| 352 | + assertRootImportMatchesUsage(filePath, content, "import\\s+type\\s*\\{", expectedImportRegex, | ||
| 353 | + usageRegex, bindingName); | ||
| 354 | + } | ||
| 355 | + | ||
| 356 | + private static void assertRootImportMatchesUsage(String filePath, String content, String importPrefix, | ||
| 357 | + String expectedImportRegex, String usageRegex, | ||
| 358 | + String bindingName) { | ||
| 359 | + boolean imported = createRootImportPattern(importPrefix, expectedImportRegex).matcher(content).find(); | ||
| 360 | + boolean used = Pattern.compile(usageRegex).matcher(removeAntdvNextRootImports(content)).find(); | ||
| 361 | + assertEquals(used, imported, filePath + ":" + bindingName + (used ? " 缺少合法根导入" : " 存在孤立根导入")); | ||
| 362 | + } | ||
| 363 | + | ||
| 364 | + private static String removeAntdvNextRootImports(String content) { | ||
| 365 | + Pattern pattern = Pattern.compile("import\\s+(?:type\\s+)?\\{[^}]*}\\s*from\\s*'antdv-next';?", | ||
| 366 | + Pattern.DOTALL); | ||
| 367 | + return pattern.matcher(content).replaceAll(""); | ||
| 368 | + } | ||
| 369 | + | ||
| 370 | + private static void assertRootValueImport(String filePath, String content, String expectedRegex) { | ||
| 371 | + assertRootImport(filePath, content, "import\\s*\\{", expectedRegex); | ||
| 372 | + } | ||
| 373 | + | ||
| 374 | + private static void assertRootValueImportsSorted(String filePath, String content) { | ||
| 375 | + var matcher = ANTDV_NEXT_VALUE_IMPORT_PATTERN.matcher(content); | ||
| 376 | + while (matcher.find()) { | ||
| 377 | + List<String> actual = Arrays.stream(matcher.group(1).split(",")) | ||
| 378 | + .map(String::trim) | ||
| 379 | + .filter(item -> !item.isEmpty()) | ||
| 380 | + .map(item -> item.replaceFirst("^.*\\s+as\\s+", "")) | ||
| 381 | + .toList(); | ||
| 382 | + List<String> expected = actual.stream().sorted(String.CASE_INSENSITIVE_ORDER).toList(); | ||
| 383 | + assertEquals(expected, actual, filePath + ":antdv-next 命名导入未按 natural 顺序排列"); | ||
| 384 | + } | ||
| 385 | + } | ||
| 386 | + | ||
| 387 | + private static void assertRangePickerHelperImportMatchesUsage(String filePath, String content) { | ||
| 388 | + boolean imported = RANGE_PICKER_HELPER_IMPORT_PATTERN.matcher(content).find(); | ||
| 389 | + boolean called = RANGE_PICKER_HELPER_CALL_PATTERN.matcher(content).find(); | ||
| 390 | + assertEquals(called, imported, | ||
| 391 | + filePath + ":getRangePickerDefaultProps() 调用与导入不一致"); | ||
| 392 | + } | ||
| 393 | + | ||
| 394 | + private static void assertRangePickerHelperBindingMatchesComponent(String filePath, String content) { | ||
| 395 | + long componentCount = countMatches(RANGE_PICKER_COMPONENT_PATTERN, content); | ||
| 396 | + long bindingCount = countMatches(RANGE_PICKER_HELPER_BINDING_PATTERN, content); | ||
| 397 | + assertEquals(componentCount, bindingCount, | ||
| 398 | + filePath + ":RangePicker 与 getRangePickerDefaultProps() 绑定数量不一致"); | ||
| 399 | + } | ||
| 400 | + | ||
| 401 | + private static void assertSchemaRangePickerHelperMatchesComponent(String filePath, String content) { | ||
| 402 | + long componentCount = countMatches(SCHEMA_RANGE_PICKER_COMPONENT_PATTERN, content); | ||
| 403 | + long helperSpreadCount = countMatches(SCHEMA_RANGE_PICKER_HELPER_SPREAD_PATTERN, content); | ||
| 404 | + assertEquals(componentCount, helperSpreadCount, | ||
| 405 | + filePath + ":Schema RangePicker 与 getRangePickerDefaultProps() 展开数量不一致"); | ||
| 406 | + } | ||
| 407 | + | ||
| 408 | + private static long countMatches(Pattern pattern, String content) { | ||
| 409 | + return pattern.matcher(content).results().count(); | ||
| 410 | + } | ||
| 411 | + | ||
| 412 | + private static void assertRootImport(String filePath, String content, String importPrefix, String expectedRegex) { | ||
| 413 | + Pattern pattern = createRootImportPattern(importPrefix, expectedRegex); | ||
| 414 | + assertTrue(pattern.matcher(content).find(), filePath + ":缺少合法的 antdv-next 根导入 " + expectedRegex); | ||
| 415 | + } | ||
| 416 | + | ||
| 417 | + private static Pattern createRootImportPattern(String importPrefix, String expectedRegex) { | ||
| 418 | + return Pattern.compile(importPrefix + "[^}]*" + expectedRegex | ||
| 419 | + + "[^}]*}\\s*from\\s*'antdv-next';?", Pattern.DOTALL); | ||
| 420 | + } | ||
| 421 | + | ||
| 422 | +} |
yudao-module-infra/src/test/resources/codegen/vue3_admin_uniapp_master_erp/assert.json
0 → 100644
| 1 | +[ { | ||
| 2 | + "contentPath" : "java/InfraStudentPageReqVO", | ||
| 3 | + "filePath" : "yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/demo/vo/InfraStudentPageReqVO.java" | ||
| 4 | +}, { | ||
| 5 | + "contentPath" : "java/InfraStudentRespVO", | ||
| 6 | + "filePath" : "yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/demo/vo/InfraStudentRespVO.java" | ||
| 7 | +}, { | ||
| 8 | + "contentPath" : "java/InfraStudentSaveReqVO", | ||
| 9 | + "filePath" : "yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/demo/vo/InfraStudentSaveReqVO.java" | ||
| 10 | +}, { | ||
| 11 | + "contentPath" : "java/InfraStudentController", | ||
| 12 | + "filePath" : "yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/demo/InfraStudentController.java" | ||
| 13 | +}, { | ||
| 14 | + "contentPath" : "java/InfraStudentDO", | ||
| 15 | + "filePath" : "yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/dal/dataobject/demo/InfraStudentDO.java" | ||
| 16 | +}, { | ||
| 17 | + "contentPath" : "java/InfraStudentContactDO", | ||
| 18 | + "filePath" : "yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/dal/dataobject/demo/InfraStudentContactDO.java" | ||
| 19 | +}, { | ||
| 20 | + "contentPath" : "java/InfraStudentTeacherDO", | ||
| 21 | + "filePath" : "yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/dal/dataobject/demo/InfraStudentTeacherDO.java" | ||
| 22 | +}, { | ||
| 23 | + "contentPath" : "java/InfraStudentMapper", | ||
| 24 | + "filePath" : "yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/dal/mysql/demo/InfraStudentMapper.java" | ||
| 25 | +}, { | ||
| 26 | + "contentPath" : "java/InfraStudentContactMapper", | ||
| 27 | + "filePath" : "yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/dal/mysql/demo/InfraStudentContactMapper.java" | ||
| 28 | +}, { | ||
| 29 | + "contentPath" : "java/InfraStudentTeacherMapper", | ||
| 30 | + "filePath" : "yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/dal/mysql/demo/InfraStudentTeacherMapper.java" | ||
| 31 | +}, { | ||
| 32 | + "contentPath" : "xml/InfraStudentMapper", | ||
| 33 | + "filePath" : "yudao-module-infra/src/main/resources/mapper/demo/InfraStudentMapper.xml" | ||
| 34 | +}, { | ||
| 35 | + "contentPath" : "java/InfraStudentServiceImpl", | ||
| 36 | + "filePath" : "yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/service/demo/InfraStudentServiceImpl.java" | ||
| 37 | +}, { | ||
| 38 | + "contentPath" : "java/InfraStudentService", | ||
| 39 | + "filePath" : "yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/service/demo/InfraStudentService.java" | ||
| 40 | +}, { | ||
| 41 | + "contentPath" : "java/InfraStudentServiceImplTest", | ||
| 42 | + "filePath" : "yudao-module-infra/src/test/java/cn/iocoder/yudao/module/infra/service/demo/InfraStudentServiceImplTest.java" | ||
| 43 | +}, { | ||
| 44 | + "contentPath" : "java/ErrorCodeConstants_手动操作", | ||
| 45 | + "filePath" : "yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/enums/ErrorCodeConstants_手动操作.java" | ||
| 46 | +}, { | ||
| 47 | + "contentPath" : "sql/sql", | ||
| 48 | + "filePath" : "sql/sql.sql" | ||
| 49 | +}, { | ||
| 50 | + "contentPath" : "sql/h2", | ||
| 51 | + "filePath" : "sql/h2.sql" | ||
| 52 | +}, { | ||
| 53 | + "contentPath" : "ts/index", | ||
| 54 | + "filePath" : "yudao-ui-admin-uniapp/src/api/infra/demo/index.ts" | ||
| 55 | +}, { | ||
| 56 | + "contentPath" : "vue/index", | ||
| 57 | + "filePath" : "yudao-ui-admin-uniapp/src/pages-infra/demo/index.vue" | ||
| 58 | +}, { | ||
| 59 | + "contentPath" : "vue/search-form", | ||
| 60 | + "filePath" : "yudao-ui-admin-uniapp/src/pages-infra/demo/components/search-form.vue" | ||
| 61 | +}, { | ||
| 62 | + "contentPath" : "vue/form/index", | ||
| 63 | + "filePath" : "yudao-ui-admin-uniapp/src/pages-infra/demo/form/index.vue" | ||
| 64 | +}, { | ||
| 65 | + "contentPath" : "vue/detail/index", | ||
| 66 | + "filePath" : "yudao-ui-admin-uniapp/src/pages-infra/demo/detail/index.vue" | ||
| 67 | +}, { | ||
| 68 | + "contentPath" : "vue/student-contact/form/index", | ||
| 69 | + "filePath" : "yudao-ui-admin-uniapp/src/pages-infra/demo/student-contact/form/index.vue" | ||
| 70 | +}, { | ||
| 71 | + "contentPath" : "vue/student-teacher/form/index", | ||
| 72 | + "filePath" : "yudao-ui-admin-uniapp/src/pages-infra/demo/student-teacher/form/index.vue" | ||
| 73 | +} ] | ||
| 0 | \ No newline at end of file | 74 | \ No newline at end of file |
yudao-module-infra/src/test/resources/codegen/vue3_admin_uniapp_master_erp/java/ErrorCodeConstants_手动操作
0 → 100644
| 1 | +// TODO 待办:请将下面的错误码复制到 yudao-module-infra 模块的 ErrorCodeConstants 类中。注意,请给“TODO 补充编号”设置一个错误码编号!!! | ||
| 2 | +// ========== 学生 TODO 补充编号 ========== | ||
| 3 | +ErrorCode STUDENT_NOT_EXISTS = new ErrorCode(TODO 补充编号, "学生不存在"); | ||
| 4 | +ErrorCode STUDENT_CONTACT_NOT_EXISTS = new ErrorCode(TODO 补充编号, "学生联系人不存在"); | ||
| 5 | +ErrorCode STUDENT_TEACHER_NOT_EXISTS = new ErrorCode(TODO 补充编号, "学生班主任不存在"); | ||
| 6 | +ErrorCode STUDENT_TEACHER_EXISTS = new ErrorCode(TODO 补充编号, "学生班主任已存在"); | ||
| 0 | \ No newline at end of file | 7 | \ No newline at end of file |
yudao-module-infra/src/test/resources/codegen/vue3_admin_uniapp_master_erp/java/InfraStudentContactDO
0 → 100644
| 1 | +package cn.iocoder.yudao.module.infra.dal.dataobject.demo; | ||
| 2 | + | ||
| 3 | +import lombok.*; | ||
| 4 | +import java.util.*; | ||
| 5 | +import java.time.LocalDateTime; | ||
| 6 | +import java.time.LocalDateTime; | ||
| 7 | +import com.baomidou.mybatisplus.annotation.*; | ||
| 8 | +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; | ||
| 9 | + | ||
| 10 | +/** | ||
| 11 | + * 学生联系人 DO | ||
| 12 | + * | ||
| 13 | + * @author 芋道源码 | ||
| 14 | + */ | ||
| 15 | +@TableName("infra_student_contact") | ||
| 16 | +@KeySequence("infra_student_contact_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 | ||
| 17 | +@Data | ||
| 18 | +@EqualsAndHashCode(callSuper = true) | ||
| 19 | +@ToString(callSuper = true) | ||
| 20 | +@Builder | ||
| 21 | +@NoArgsConstructor | ||
| 22 | +@AllArgsConstructor | ||
| 23 | +public class InfraStudentContactDO extends BaseDO { | ||
| 24 | + | ||
| 25 | + /** | ||
| 26 | + * 编号 | ||
| 27 | + */ | ||
| 28 | + @TableId | ||
| 29 | + private Long id; | ||
| 30 | + /** | ||
| 31 | + * 学生编号 | ||
| 32 | + */ | ||
| 33 | + private Long studentId; | ||
| 34 | + /** | ||
| 35 | + * 名字 | ||
| 36 | + */ | ||
| 37 | + private String name; | ||
| 38 | + /** | ||
| 39 | + * 简介 | ||
| 40 | + */ | ||
| 41 | + private String description; | ||
| 42 | + /** | ||
| 43 | + * 出生日期 | ||
| 44 | + */ | ||
| 45 | + private LocalDateTime birthday; | ||
| 46 | + /** | ||
| 47 | + * 性别 | ||
| 48 | + * | ||
| 49 | + * 枚举 {@link TODO system_user_sex 对应的类} | ||
| 50 | + */ | ||
| 51 | + private Integer sex; | ||
| 52 | + /** | ||
| 53 | + * 是否有效 | ||
| 54 | + * | ||
| 55 | + * 枚举 {@link TODO infra_boolean_string 对应的类} | ||
| 56 | + */ | ||
| 57 | + private Boolean enabled; | ||
| 58 | + /** | ||
| 59 | + * 头像 | ||
| 60 | + */ | ||
| 61 | + private String avatar; | ||
| 62 | + /** | ||
| 63 | + * 附件 | ||
| 64 | + */ | ||
| 65 | + private String video; | ||
| 66 | + /** | ||
| 67 | + * 备注 | ||
| 68 | + */ | ||
| 69 | + private String memo; | ||
| 70 | + | ||
| 71 | +} | ||
| 0 | \ No newline at end of file | 72 | \ No newline at end of file |
yudao-module-infra/src/test/resources/codegen/vue3_admin_uniapp_master_erp/java/InfraStudentContactMapper
0 → 100644
| 1 | +package cn.iocoder.yudao.module.infra.dal.mysql.demo; | ||
| 2 | + | ||
| 3 | +import java.util.*; | ||
| 4 | + | ||
| 5 | +import cn.iocoder.yudao.framework.common.pojo.PageResult; | ||
| 6 | +import cn.iocoder.yudao.framework.common.pojo.PageParam; | ||
| 7 | +import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; | ||
| 8 | +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; | ||
| 9 | +import cn.iocoder.yudao.module.infra.dal.dataobject.demo.InfraStudentContactDO; | ||
| 10 | +import org.apache.ibatis.annotations.Mapper; | ||
| 11 | + | ||
| 12 | +/** | ||
| 13 | + * 学生联系人 Mapper | ||
| 14 | + * | ||
| 15 | + * @author 芋道源码 | ||
| 16 | + */ | ||
| 17 | +@Mapper | ||
| 18 | +public interface InfraStudentContactMapper extends BaseMapperX<InfraStudentContactDO> { | ||
| 19 | + | ||
| 20 | + default PageResult<InfraStudentContactDO> selectPage(PageParam reqVO, Long studentId) { | ||
| 21 | + return selectPage(reqVO, new LambdaQueryWrapperX<InfraStudentContactDO>() | ||
| 22 | + .eq(InfraStudentContactDO::getStudentId, studentId) | ||
| 23 | + .orderByDesc(InfraStudentContactDO::getId)); | ||
| 24 | + } | ||
| 25 | + | ||
| 26 | + default int deleteByStudentId(Long studentId) { | ||
| 27 | + return delete(InfraStudentContactDO::getStudentId, studentId); | ||
| 28 | + } | ||
| 29 | + | ||
| 30 | + default int deleteByStudentIds(List<Long> studentIds) { | ||
| 31 | + return deleteBatch(InfraStudentContactDO::getStudentId, studentIds); | ||
| 32 | + } | ||
| 33 | + | ||
| 34 | +} | ||
| 0 | \ No newline at end of file | 35 | \ No newline at end of file |
yudao-module-infra/src/test/resources/codegen/vue3_admin_uniapp_master_erp/java/InfraStudentController
0 → 100644
| 1 | +package cn.iocoder.yudao.module.infra.controller.admin.demo; | ||
| 2 | + | ||
| 3 | +import org.springframework.web.bind.annotation.*; | ||
| 4 | +import jakarta.annotation.Resource; | ||
| 5 | +import org.springframework.validation.annotation.Validated; | ||
| 6 | +import org.springframework.security.access.prepost.PreAuthorize; | ||
| 7 | +import io.swagger.v3.oas.annotations.tags.Tag; | ||
| 8 | +import io.swagger.v3.oas.annotations.Parameter; | ||
| 9 | +import io.swagger.v3.oas.annotations.Operation; | ||
| 10 | + | ||
| 11 | +import jakarta.validation.constraints.*; | ||
| 12 | +import jakarta.validation.*; | ||
| 13 | +import jakarta.servlet.http.*; | ||
| 14 | +import java.util.*; | ||
| 15 | +import java.io.IOException; | ||
| 16 | + | ||
| 17 | +import cn.iocoder.yudao.framework.common.pojo.PageParam; | ||
| 18 | +import cn.iocoder.yudao.framework.common.pojo.PageResult; | ||
| 19 | +import cn.iocoder.yudao.framework.common.pojo.CommonResult; | ||
| 20 | +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; | ||
| 21 | +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; | ||
| 22 | + | ||
| 23 | +import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils; | ||
| 24 | + | ||
| 25 | +import cn.iocoder.yudao.framework.apilog.core.annotation.ApiAccessLog; | ||
| 26 | +import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.*; | ||
| 27 | + | ||
| 28 | +import cn.iocoder.yudao.module.infra.controller.admin.demo.vo.*; | ||
| 29 | +import cn.iocoder.yudao.module.infra.dal.dataobject.demo.InfraStudentDO; | ||
| 30 | +import cn.iocoder.yudao.module.infra.dal.dataobject.demo.InfraStudentContactDO; | ||
| 31 | +import cn.iocoder.yudao.module.infra.dal.dataobject.demo.InfraStudentTeacherDO; | ||
| 32 | +import cn.iocoder.yudao.module.infra.service.demo.InfraStudentService; | ||
| 33 | + | ||
| 34 | +@Tag(name = "管理后台 - 学生") | ||
| 35 | +@RestController | ||
| 36 | +@RequestMapping("/infra/student") | ||
| 37 | +@Validated | ||
| 38 | +public class InfraStudentController { | ||
| 39 | + | ||
| 40 | + @Resource | ||
| 41 | + private InfraStudentService studentService; | ||
| 42 | + | ||
| 43 | + @PostMapping("/create") | ||
| 44 | + @Operation(summary = "创建学生") | ||
| 45 | + @PreAuthorize("@ss.hasPermission('infra:student:create')") | ||
| 46 | + public CommonResult<Long> createStudent(@Valid @RequestBody InfraStudentSaveReqVO createReqVO) { | ||
| 47 | + return success(studentService.createStudent(createReqVO)); | ||
| 48 | + } | ||
| 49 | + | ||
| 50 | + @PutMapping("/update") | ||
| 51 | + @Operation(summary = "更新学生") | ||
| 52 | + @PreAuthorize("@ss.hasPermission('infra:student:update')") | ||
| 53 | + public CommonResult<Boolean> updateStudent(@Valid @RequestBody InfraStudentSaveReqVO updateReqVO) { | ||
| 54 | + studentService.updateStudent(updateReqVO); | ||
| 55 | + return success(true); | ||
| 56 | + } | ||
| 57 | + | ||
| 58 | + @DeleteMapping("/delete") | ||
| 59 | + @Operation(summary = "删除学生") | ||
| 60 | + @Parameter(name = "id", description = "编号", required = true) | ||
| 61 | + @PreAuthorize("@ss.hasPermission('infra:student:delete')") | ||
| 62 | + public CommonResult<Boolean> deleteStudent(@RequestParam("id") Long id) { | ||
| 63 | + studentService.deleteStudent(id); | ||
| 64 | + return success(true); | ||
| 65 | + } | ||
| 66 | + | ||
| 67 | + @DeleteMapping("/delete-list") | ||
| 68 | + @Parameter(name = "ids", description = "编号", required = true) | ||
| 69 | + @Operation(summary = "批量删除学生") | ||
| 70 | + @PreAuthorize("@ss.hasPermission('infra:student:delete')") | ||
| 71 | + public CommonResult<Boolean> deleteStudentList(@RequestParam("ids") List<Long> ids) { | ||
| 72 | + studentService.deleteStudentListByIds(ids); | ||
| 73 | + return success(true); | ||
| 74 | + } | ||
| 75 | + | ||
| 76 | + @GetMapping("/get") | ||
| 77 | + @Operation(summary = "获得学生") | ||
| 78 | + @Parameter(name = "id", description = "编号", required = true, example = "1024") | ||
| 79 | + @PreAuthorize("@ss.hasPermission('infra:student:query')") | ||
| 80 | + public CommonResult<InfraStudentRespVO> getStudent(@RequestParam("id") Long id) { | ||
| 81 | + InfraStudentDO student = studentService.getStudent(id); | ||
| 82 | + return success(BeanUtils.toBean(student, InfraStudentRespVO.class)); | ||
| 83 | + } | ||
| 84 | + | ||
| 85 | + @GetMapping("/page") | ||
| 86 | + @Operation(summary = "获得学生分页") | ||
| 87 | + @PreAuthorize("@ss.hasPermission('infra:student:query')") | ||
| 88 | + public CommonResult<PageResult<InfraStudentRespVO>> getStudentPage(@Valid InfraStudentPageReqVO pageReqVO) { | ||
| 89 | + PageResult<InfraStudentDO> pageResult = studentService.getStudentPage(pageReqVO); | ||
| 90 | + return success(BeanUtils.toBean(pageResult, InfraStudentRespVO.class)); | ||
| 91 | + } | ||
| 92 | + | ||
| 93 | + @GetMapping("/export-excel") | ||
| 94 | + @Operation(summary = "导出学生 Excel") | ||
| 95 | + @PreAuthorize("@ss.hasPermission('infra:student:export')") | ||
| 96 | + @ApiAccessLog(operateType = EXPORT) | ||
| 97 | + public void exportStudentExcel(@Valid InfraStudentPageReqVO pageReqVO, | ||
| 98 | + HttpServletResponse response) throws IOException { | ||
| 99 | + pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE); | ||
| 100 | + List<InfraStudentDO> list = studentService.getStudentPage(pageReqVO).getList(); | ||
| 101 | + // 导出 Excel | ||
| 102 | + ExcelUtils.write(response, "学生.xls", "数据", InfraStudentRespVO.class, | ||
| 103 | + BeanUtils.toBean(list, InfraStudentRespVO.class)); | ||
| 104 | + } | ||
| 105 | + | ||
| 106 | + // ==================== 子表(学生联系人) ==================== | ||
| 107 | + | ||
| 108 | + @GetMapping("/student-contact/page") | ||
| 109 | + @Operation(summary = "获得学生联系人分页") | ||
| 110 | + @Parameter(name = "studentId", description = "学生编号") | ||
| 111 | + @PreAuthorize("@ss.hasPermission('infra:student:query')") | ||
| 112 | + public CommonResult<PageResult<InfraStudentContactDO>> getStudentContactPage(PageParam pageReqVO, | ||
| 113 | + @RequestParam("studentId") Long studentId) { | ||
| 114 | + return success(studentService.getStudentContactPage(pageReqVO, studentId)); | ||
| 115 | + } | ||
| 116 | + | ||
| 117 | + @PostMapping("/student-contact/create") | ||
| 118 | + @Operation(summary = "创建学生联系人") | ||
| 119 | + @PreAuthorize("@ss.hasPermission('infra:student:create')") | ||
| 120 | + public CommonResult<Long> createStudentContact(@Valid @RequestBody InfraStudentContactDO studentContact) { | ||
| 121 | + return success(studentService.createStudentContact(studentContact)); | ||
| 122 | + } | ||
| 123 | + | ||
| 124 | + @PutMapping("/student-contact/update") | ||
| 125 | + @Operation(summary = "更新学生联系人") | ||
| 126 | + @PreAuthorize("@ss.hasPermission('infra:student:update')") | ||
| 127 | + public CommonResult<Boolean> updateStudentContact(@Valid @RequestBody InfraStudentContactDO studentContact) { | ||
| 128 | + studentService.updateStudentContact(studentContact); | ||
| 129 | + return success(true); | ||
| 130 | + } | ||
| 131 | + | ||
| 132 | + @DeleteMapping("/student-contact/delete") | ||
| 133 | + @Parameter(name = "id", description = "编号", required = true) | ||
| 134 | + @Operation(summary = "删除学生联系人") | ||
| 135 | + @PreAuthorize("@ss.hasPermission('infra:student:delete')") | ||
| 136 | + public CommonResult<Boolean> deleteStudentContact(@RequestParam("id") Long id) { | ||
| 137 | + studentService.deleteStudentContact(id); | ||
| 138 | + return success(true); | ||
| 139 | + } | ||
| 140 | + | ||
| 141 | + @DeleteMapping("/student-contact/delete-list") | ||
| 142 | + @Parameter(name = "ids", description = "编号", required = true) | ||
| 143 | + @Operation(summary = "批量删除学生联系人") | ||
| 144 | + @PreAuthorize("@ss.hasPermission('infra:student:delete')") | ||
| 145 | + public CommonResult<Boolean> deleteStudentContactList(@RequestParam("ids") List<Long> ids) { | ||
| 146 | + studentService.deleteStudentContactListByIds(ids); | ||
| 147 | + return success(true); | ||
| 148 | + } | ||
| 149 | + | ||
| 150 | + @GetMapping("/student-contact/get") | ||
| 151 | + @Operation(summary = "获得学生联系人") | ||
| 152 | + @Parameter(name = "id", description = "编号", required = true) | ||
| 153 | + @PreAuthorize("@ss.hasPermission('infra:student:query')") | ||
| 154 | + public CommonResult<InfraStudentContactDO> getStudentContact(@RequestParam("id") Long id) { | ||
| 155 | + return success(studentService.getStudentContact(id)); | ||
| 156 | + } | ||
| 157 | + | ||
| 158 | + // ==================== 子表(学生班主任) ==================== | ||
| 159 | + | ||
| 160 | + @GetMapping("/student-teacher/page") | ||
| 161 | + @Operation(summary = "获得学生班主任分页") | ||
| 162 | + @Parameter(name = "studentId", description = "学生编号") | ||
| 163 | + @PreAuthorize("@ss.hasPermission('infra:student:query')") | ||
| 164 | + public CommonResult<PageResult<InfraStudentTeacherDO>> getStudentTeacherPage(PageParam pageReqVO, | ||
| 165 | + @RequestParam("studentId") Long studentId) { | ||
| 166 | + return success(studentService.getStudentTeacherPage(pageReqVO, studentId)); | ||
| 167 | + } | ||
| 168 | + | ||
| 169 | + @PostMapping("/student-teacher/create") | ||
| 170 | + @Operation(summary = "创建学生班主任") | ||
| 171 | + @PreAuthorize("@ss.hasPermission('infra:student:create')") | ||
| 172 | + public CommonResult<Long> createStudentTeacher(@Valid @RequestBody InfraStudentTeacherDO studentTeacher) { | ||
| 173 | + return success(studentService.createStudentTeacher(studentTeacher)); | ||
| 174 | + } | ||
| 175 | + | ||
| 176 | + @PutMapping("/student-teacher/update") | ||
| 177 | + @Operation(summary = "更新学生班主任") | ||
| 178 | + @PreAuthorize("@ss.hasPermission('infra:student:update')") | ||
| 179 | + public CommonResult<Boolean> updateStudentTeacher(@Valid @RequestBody InfraStudentTeacherDO studentTeacher) { | ||
| 180 | + studentService.updateStudentTeacher(studentTeacher); | ||
| 181 | + return success(true); | ||
| 182 | + } | ||
| 183 | + | ||
| 184 | + @DeleteMapping("/student-teacher/delete") | ||
| 185 | + @Parameter(name = "id", description = "编号", required = true) | ||
| 186 | + @Operation(summary = "删除学生班主任") | ||
| 187 | + @PreAuthorize("@ss.hasPermission('infra:student:delete')") | ||
| 188 | + public CommonResult<Boolean> deleteStudentTeacher(@RequestParam("id") Long id) { | ||
| 189 | + studentService.deleteStudentTeacher(id); | ||
| 190 | + return success(true); | ||
| 191 | + } | ||
| 192 | + | ||
| 193 | + @DeleteMapping("/student-teacher/delete-list") | ||
| 194 | + @Parameter(name = "ids", description = "编号", required = true) | ||
| 195 | + @Operation(summary = "批量删除学生班主任") | ||
| 196 | + @PreAuthorize("@ss.hasPermission('infra:student:delete')") | ||
| 197 | + public CommonResult<Boolean> deleteStudentTeacherList(@RequestParam("ids") List<Long> ids) { | ||
| 198 | + studentService.deleteStudentTeacherListByIds(ids); | ||
| 199 | + return success(true); | ||
| 200 | + } | ||
| 201 | + | ||
| 202 | + @GetMapping("/student-teacher/get") | ||
| 203 | + @Operation(summary = "获得学生班主任") | ||
| 204 | + @Parameter(name = "id", description = "编号", required = true) | ||
| 205 | + @PreAuthorize("@ss.hasPermission('infra:student:query')") | ||
| 206 | + public CommonResult<InfraStudentTeacherDO> getStudentTeacher(@RequestParam("id") Long id) { | ||
| 207 | + return success(studentService.getStudentTeacher(id)); | ||
| 208 | + } | ||
| 209 | + | ||
| 210 | +} | ||
| 0 | \ No newline at end of file | 211 | \ No newline at end of file |
yudao-module-infra/src/test/resources/codegen/vue3_admin_uniapp_master_erp/java/InfraStudentDO
0 → 100644
| 1 | +package cn.iocoder.yudao.module.infra.dal.dataobject.demo; | ||
| 2 | + | ||
| 3 | +import lombok.*; | ||
| 4 | +import java.util.*; | ||
| 5 | +import java.time.LocalDateTime; | ||
| 6 | +import java.time.LocalDateTime; | ||
| 7 | +import com.baomidou.mybatisplus.annotation.*; | ||
| 8 | +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; | ||
| 9 | + | ||
| 10 | +/** | ||
| 11 | + * 学生 DO | ||
| 12 | + * | ||
| 13 | + * @author 芋道源码 | ||
| 14 | + */ | ||
| 15 | +@TableName("infra_student") | ||
| 16 | +@KeySequence("infra_student_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 | ||
| 17 | +@Data | ||
| 18 | +@EqualsAndHashCode(callSuper = true) | ||
| 19 | +@ToString(callSuper = true) | ||
| 20 | +@Builder | ||
| 21 | +@NoArgsConstructor | ||
| 22 | +@AllArgsConstructor | ||
| 23 | +public class InfraStudentDO extends BaseDO { | ||
| 24 | + | ||
| 25 | + /** | ||
| 26 | + * 编号 | ||
| 27 | + */ | ||
| 28 | + @TableId | ||
| 29 | + private Long id; | ||
| 30 | + /** | ||
| 31 | + * 名字 | ||
| 32 | + */ | ||
| 33 | + private String name; | ||
| 34 | + /** | ||
| 35 | + * 简介 | ||
| 36 | + */ | ||
| 37 | + private String description; | ||
| 38 | + /** | ||
| 39 | + * 出生日期 | ||
| 40 | + */ | ||
| 41 | + private LocalDateTime birthday; | ||
| 42 | + /** | ||
| 43 | + * 性别 | ||
| 44 | + * | ||
| 45 | + * 枚举 {@link TODO system_user_sex 对应的类} | ||
| 46 | + */ | ||
| 47 | + private Integer sex; | ||
| 48 | + /** | ||
| 49 | + * 是否有效 | ||
| 50 | + * | ||
| 51 | + * 枚举 {@link TODO infra_boolean_string 对应的类} | ||
| 52 | + */ | ||
| 53 | + private Boolean enabled; | ||
| 54 | + /** | ||
| 55 | + * 头像 | ||
| 56 | + */ | ||
| 57 | + private String avatar; | ||
| 58 | + /** | ||
| 59 | + * 附件 | ||
| 60 | + */ | ||
| 61 | + private String video; | ||
| 62 | + /** | ||
| 63 | + * 备注 | ||
| 64 | + */ | ||
| 65 | + private String memo; | ||
| 66 | + | ||
| 67 | + | ||
| 68 | +} | ||
| 0 | \ No newline at end of file | 69 | \ No newline at end of file |
yudao-module-infra/src/test/resources/codegen/vue3_admin_uniapp_master_erp/java/InfraStudentMapper
0 → 100644
| 1 | +package cn.iocoder.yudao.module.infra.dal.mysql.demo; | ||
| 2 | + | ||
| 3 | +import java.util.*; | ||
| 4 | + | ||
| 5 | +import cn.iocoder.yudao.framework.common.pojo.PageResult; | ||
| 6 | +import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; | ||
| 7 | +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; | ||
| 8 | +import cn.iocoder.yudao.module.infra.dal.dataobject.demo.InfraStudentDO; | ||
| 9 | +import org.apache.ibatis.annotations.Mapper; | ||
| 10 | +import cn.iocoder.yudao.module.infra.controller.admin.demo.vo.*; | ||
| 11 | + | ||
| 12 | +/** | ||
| 13 | + * 学生 Mapper | ||
| 14 | + * | ||
| 15 | + * @author 芋道源码 | ||
| 16 | + */ | ||
| 17 | +@Mapper | ||
| 18 | +public interface InfraStudentMapper extends BaseMapperX<InfraStudentDO> { | ||
| 19 | + | ||
| 20 | + default PageResult<InfraStudentDO> selectPage(InfraStudentPageReqVO reqVO) { | ||
| 21 | + return selectPage(reqVO, new LambdaQueryWrapperX<InfraStudentDO>() | ||
| 22 | + .likeIfPresent(InfraStudentDO::getName, reqVO.getName()) | ||
| 23 | + .eqIfPresent(InfraStudentDO::getBirthday, reqVO.getBirthday()) | ||
| 24 | + .eqIfPresent(InfraStudentDO::getSex, reqVO.getSex()) | ||
| 25 | + .eqIfPresent(InfraStudentDO::getEnabled, reqVO.getEnabled()) | ||
| 26 | + .betweenIfPresent(InfraStudentDO::getCreateTime, reqVO.getCreateTime()) | ||
| 27 | + .orderByDesc(InfraStudentDO::getId)); | ||
| 28 | + } | ||
| 29 | + | ||
| 30 | +} | ||
| 0 | \ No newline at end of file | 31 | \ No newline at end of file |
yudao-module-infra/src/test/resources/codegen/vue3_admin_uniapp_master_erp/java/InfraStudentPageReqVO
0 → 100644
| 1 | +package cn.iocoder.yudao.module.infra.controller.admin.demo.vo; | ||
| 2 | + | ||
| 3 | +import lombok.*; | ||
| 4 | +import java.util.*; | ||
| 5 | +import io.swagger.v3.oas.annotations.media.Schema; | ||
| 6 | +import cn.iocoder.yudao.framework.common.pojo.PageParam; | ||
| 7 | +import org.springframework.format.annotation.DateTimeFormat; | ||
| 8 | +import java.time.LocalDateTime; | ||
| 9 | + | ||
| 10 | +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; | ||
| 11 | + | ||
| 12 | +@Schema(description = "管理后台 - 学生分页 Request VO") | ||
| 13 | +@Data | ||
| 14 | +public class InfraStudentPageReqVO extends PageParam { | ||
| 15 | + | ||
| 16 | + @Schema(description = "名字", example = "芋头") | ||
| 17 | + private String name; | ||
| 18 | + | ||
| 19 | + @Schema(description = "出生日期") | ||
| 20 | + private LocalDateTime birthday; | ||
| 21 | + | ||
| 22 | + @Schema(description = "性别", example = "1") | ||
| 23 | + private Integer sex; | ||
| 24 | + | ||
| 25 | + @Schema(description = "是否有效", example = "true") | ||
| 26 | + private Boolean enabled; | ||
| 27 | + | ||
| 28 | + @Schema(description = "创建时间") | ||
| 29 | + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) | ||
| 30 | + private LocalDateTime[] createTime; | ||
| 31 | + | ||
| 32 | +} | ||
| 0 | \ No newline at end of file | 33 | \ No newline at end of file |
yudao-module-infra/src/test/resources/codegen/vue3_admin_uniapp_master_erp/java/InfraStudentRespVO
0 → 100644
| 1 | +package cn.iocoder.yudao.module.infra.controller.admin.demo.vo; | ||
| 2 | + | ||
| 3 | +import io.swagger.v3.oas.annotations.media.Schema; | ||
| 4 | +import lombok.*; | ||
| 5 | +import java.util.*; | ||
| 6 | +import org.springframework.format.annotation.DateTimeFormat; | ||
| 7 | +import java.time.LocalDateTime; | ||
| 8 | +import cn.idev.excel.annotation.*; | ||
| 9 | +import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; | ||
| 10 | +import cn.iocoder.yudao.framework.excel.core.convert.DictConvert; | ||
| 11 | + | ||
| 12 | +@Schema(description = "管理后台 - 学生 Response VO") | ||
| 13 | +@Data | ||
| 14 | +@ExcelIgnoreUnannotated | ||
| 15 | +public class InfraStudentRespVO { | ||
| 16 | + | ||
| 17 | + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") | ||
| 18 | + @ExcelProperty("编号") | ||
| 19 | + private Long id; | ||
| 20 | + | ||
| 21 | + @Schema(description = "名字", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋头") | ||
| 22 | + @ExcelProperty("名字") | ||
| 23 | + private String name; | ||
| 24 | + | ||
| 25 | + @Schema(description = "简介", requiredMode = Schema.RequiredMode.REQUIRED, example = "我是介绍") | ||
| 26 | + @ExcelProperty("简介") | ||
| 27 | + private String description; | ||
| 28 | + | ||
| 29 | + @Schema(description = "出生日期", requiredMode = Schema.RequiredMode.REQUIRED) | ||
| 30 | + @ExcelProperty("出生日期") | ||
| 31 | + private LocalDateTime birthday; | ||
| 32 | + | ||
| 33 | + @Schema(description = "性别", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") | ||
| 34 | + @ExcelProperty(value = "性别", converter = DictConvert.class) | ||
| 35 | + @DictFormat("system_user_sex") // TODO 代码优化:建议设置到对应的 DictTypeConstants 枚举类中 | ||
| 36 | + private Integer sex; | ||
| 37 | + | ||
| 38 | + @Schema(description = "是否有效", requiredMode = Schema.RequiredMode.REQUIRED, example = "true") | ||
| 39 | + @ExcelProperty(value = "是否有效", converter = DictConvert.class) | ||
| 40 | + @DictFormat("infra_boolean_string") // TODO 代码优化:建议设置到对应的 DictTypeConstants 枚举类中 | ||
| 41 | + private Boolean enabled; | ||
| 42 | + | ||
| 43 | + @Schema(description = "头像", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn/1.png") | ||
| 44 | + @ExcelProperty("头像") | ||
| 45 | + private String avatar; | ||
| 46 | + | ||
| 47 | + @Schema(description = "附件", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn/1.mp4") | ||
| 48 | + @ExcelProperty("附件") | ||
| 49 | + private String video; | ||
| 50 | + | ||
| 51 | + @Schema(description = "备注", requiredMode = Schema.RequiredMode.REQUIRED, example = "我是备注") | ||
| 52 | + @ExcelProperty("备注") | ||
| 53 | + private String memo; | ||
| 54 | + | ||
| 55 | + @Schema(description = "创建时间") | ||
| 56 | + @ExcelProperty("创建时间") | ||
| 57 | + private LocalDateTime createTime; | ||
| 58 | + | ||
| 59 | +} | ||
| 0 | \ No newline at end of file | 60 | \ No newline at end of file |
yudao-module-infra/src/test/resources/codegen/vue3_admin_uniapp_master_erp/java/InfraStudentSaveReqVO
0 → 100644
| 1 | +package cn.iocoder.yudao.module.infra.controller.admin.demo.vo; | ||
| 2 | + | ||
| 3 | +import io.swagger.v3.oas.annotations.media.Schema; | ||
| 4 | +import lombok.*; | ||
| 5 | +import java.util.*; | ||
| 6 | +import jakarta.validation.constraints.*; | ||
| 7 | +import org.springframework.format.annotation.DateTimeFormat; | ||
| 8 | +import java.time.LocalDateTime; | ||
| 9 | +import cn.iocoder.yudao.module.infra.dal.dataobject.demo.InfraStudentContactDO; | ||
| 10 | +import cn.iocoder.yudao.module.infra.dal.dataobject.demo.InfraStudentTeacherDO; | ||
| 11 | + | ||
| 12 | +@Schema(description = "管理后台 - 学生新增/修改 Request VO") | ||
| 13 | +@Data | ||
| 14 | +public class InfraStudentSaveReqVO { | ||
| 15 | + | ||
| 16 | + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") | ||
| 17 | + private Long id; | ||
| 18 | + | ||
| 19 | + @Schema(description = "名字", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋头") | ||
| 20 | + @NotEmpty(message = "名字不能为空") | ||
| 21 | + private String name; | ||
| 22 | + | ||
| 23 | + @Schema(description = "简介", requiredMode = Schema.RequiredMode.REQUIRED, example = "我是介绍") | ||
| 24 | + @NotEmpty(message = "简介不能为空") | ||
| 25 | + private String description; | ||
| 26 | + | ||
| 27 | + @Schema(description = "出生日期", requiredMode = Schema.RequiredMode.REQUIRED) | ||
| 28 | + @NotNull(message = "出生日期不能为空") | ||
| 29 | + private LocalDateTime birthday; | ||
| 30 | + | ||
| 31 | + @Schema(description = "性别", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") | ||
| 32 | + @NotNull(message = "性别不能为空") | ||
| 33 | + private Integer sex; | ||
| 34 | + | ||
| 35 | + @Schema(description = "是否有效", requiredMode = Schema.RequiredMode.REQUIRED, example = "true") | ||
| 36 | + @NotNull(message = "是否有效不能为空") | ||
| 37 | + private Boolean enabled; | ||
| 38 | + | ||
| 39 | + @Schema(description = "头像", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn/1.png") | ||
| 40 | + @NotEmpty(message = "头像不能为空") | ||
| 41 | + private String avatar; | ||
| 42 | + | ||
| 43 | + @Schema(description = "附件", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn/1.mp4") | ||
| 44 | + @NotEmpty(message = "附件不能为空") | ||
| 45 | + private String video; | ||
| 46 | + | ||
| 47 | + @Schema(description = "备注", requiredMode = Schema.RequiredMode.REQUIRED, example = "我是备注") | ||
| 48 | + @NotEmpty(message = "备注不能为空") | ||
| 49 | + private String memo; | ||
| 50 | + | ||
| 51 | +} | ||
| 0 | \ No newline at end of file | 52 | \ No newline at end of file |
yudao-module-infra/src/test/resources/codegen/vue3_admin_uniapp_master_erp/java/InfraStudentService
0 → 100644
| 1 | +package cn.iocoder.yudao.module.infra.service.demo; | ||
| 2 | + | ||
| 3 | +import java.util.*; | ||
| 4 | +import jakarta.validation.*; | ||
| 5 | +import cn.iocoder.yudao.module.infra.controller.admin.demo.vo.*; | ||
| 6 | +import cn.iocoder.yudao.module.infra.dal.dataobject.demo.InfraStudentDO; | ||
| 7 | +import cn.iocoder.yudao.module.infra.dal.dataobject.demo.InfraStudentContactDO; | ||
| 8 | +import cn.iocoder.yudao.module.infra.dal.dataobject.demo.InfraStudentTeacherDO; | ||
| 9 | +import cn.iocoder.yudao.framework.common.pojo.PageResult; | ||
| 10 | +import cn.iocoder.yudao.framework.common.pojo.PageParam; | ||
| 11 | + | ||
| 12 | +/** | ||
| 13 | + * 学生 Service 接口 | ||
| 14 | + * | ||
| 15 | + * @author 芋道源码 | ||
| 16 | + */ | ||
| 17 | +public interface InfraStudentService { | ||
| 18 | + | ||
| 19 | + /** | ||
| 20 | + * 创建学生 | ||
| 21 | + * | ||
| 22 | + * @param createReqVO 创建信息 | ||
| 23 | + * @return 编号 | ||
| 24 | + */ | ||
| 25 | + Long createStudent(@Valid InfraStudentSaveReqVO createReqVO); | ||
| 26 | + | ||
| 27 | + /** | ||
| 28 | + * 更新学生 | ||
| 29 | + * | ||
| 30 | + * @param updateReqVO 更新信息 | ||
| 31 | + */ | ||
| 32 | + void updateStudent(@Valid InfraStudentSaveReqVO updateReqVO); | ||
| 33 | + | ||
| 34 | + /** | ||
| 35 | + * 删除学生 | ||
| 36 | + * | ||
| 37 | + * @param id 编号 | ||
| 38 | + */ | ||
| 39 | + void deleteStudent(Long id); | ||
| 40 | + | ||
| 41 | + /** | ||
| 42 | + * 批量删除学生 | ||
| 43 | + * | ||
| 44 | + * @param ids 编号 | ||
| 45 | + */ | ||
| 46 | + void deleteStudentListByIds(List<Long> ids); | ||
| 47 | + | ||
| 48 | + /** | ||
| 49 | + * 获得学生 | ||
| 50 | + * | ||
| 51 | + * @param id 编号 | ||
| 52 | + * @return 学生 | ||
| 53 | + */ | ||
| 54 | + InfraStudentDO getStudent(Long id); | ||
| 55 | + | ||
| 56 | + /** | ||
| 57 | + * 获得学生分页 | ||
| 58 | + * | ||
| 59 | + * @param pageReqVO 分页查询 | ||
| 60 | + * @return 学生分页 | ||
| 61 | + */ | ||
| 62 | + PageResult<InfraStudentDO> getStudentPage(InfraStudentPageReqVO pageReqVO); | ||
| 63 | + | ||
| 64 | + // ==================== 子表(学生联系人) ==================== | ||
| 65 | + | ||
| 66 | + /** | ||
| 67 | + * 获得学生联系人分页 | ||
| 68 | + * | ||
| 69 | + * @param pageReqVO 分页查询 | ||
| 70 | + * @param studentId 学生编号 | ||
| 71 | + * @return 学生联系人分页 | ||
| 72 | + */ | ||
| 73 | + PageResult<InfraStudentContactDO> getStudentContactPage(PageParam pageReqVO, Long studentId); | ||
| 74 | + | ||
| 75 | + /** | ||
| 76 | + * 创建学生联系人 | ||
| 77 | + * | ||
| 78 | + * @param studentContact 创建信息 | ||
| 79 | + * @return 编号 | ||
| 80 | + */ | ||
| 81 | + Long createStudentContact(@Valid InfraStudentContactDO studentContact); | ||
| 82 | + | ||
| 83 | + /** | ||
| 84 | + * 更新学生联系人 | ||
| 85 | + * | ||
| 86 | + * @param studentContact 更新信息 | ||
| 87 | + */ | ||
| 88 | + void updateStudentContact(@Valid InfraStudentContactDO studentContact); | ||
| 89 | + | ||
| 90 | + /** | ||
| 91 | + * 删除学生联系人 | ||
| 92 | + * | ||
| 93 | + * @param id 编号 | ||
| 94 | + */ | ||
| 95 | + void deleteStudentContact(Long id); | ||
| 96 | + | ||
| 97 | + /** | ||
| 98 | + * 批量删除学生联系人 | ||
| 99 | + * | ||
| 100 | + * @param ids 编号 | ||
| 101 | + */ | ||
| 102 | + void deleteStudentContactListByIds(List<Long> ids); | ||
| 103 | + | ||
| 104 | + /** | ||
| 105 | + * 获得学生联系人 | ||
| 106 | + * | ||
| 107 | + * @param id 编号 | ||
| 108 | + * @return 学生联系人 | ||
| 109 | + */ | ||
| 110 | + InfraStudentContactDO getStudentContact(Long id); | ||
| 111 | + | ||
| 112 | + // ==================== 子表(学生班主任) ==================== | ||
| 113 | + | ||
| 114 | + /** | ||
| 115 | + * 获得学生班主任分页 | ||
| 116 | + * | ||
| 117 | + * @param pageReqVO 分页查询 | ||
| 118 | + * @param studentId 学生编号 | ||
| 119 | + * @return 学生班主任分页 | ||
| 120 | + */ | ||
| 121 | + PageResult<InfraStudentTeacherDO> getStudentTeacherPage(PageParam pageReqVO, Long studentId); | ||
| 122 | + | ||
| 123 | + /** | ||
| 124 | + * 创建学生班主任 | ||
| 125 | + * | ||
| 126 | + * @param studentTeacher 创建信息 | ||
| 127 | + * @return 编号 | ||
| 128 | + */ | ||
| 129 | + Long createStudentTeacher(@Valid InfraStudentTeacherDO studentTeacher); | ||
| 130 | + | ||
| 131 | + /** | ||
| 132 | + * 更新学生班主任 | ||
| 133 | + * | ||
| 134 | + * @param studentTeacher 更新信息 | ||
| 135 | + */ | ||
| 136 | + void updateStudentTeacher(@Valid InfraStudentTeacherDO studentTeacher); | ||
| 137 | + | ||
| 138 | + /** | ||
| 139 | + * 删除学生班主任 | ||
| 140 | + * | ||
| 141 | + * @param id 编号 | ||
| 142 | + */ | ||
| 143 | + void deleteStudentTeacher(Long id); | ||
| 144 | + | ||
| 145 | + /** | ||
| 146 | + * 批量删除学生班主任 | ||
| 147 | + * | ||
| 148 | + * @param ids 编号 | ||
| 149 | + */ | ||
| 150 | + void deleteStudentTeacherListByIds(List<Long> ids); | ||
| 151 | + | ||
| 152 | + /** | ||
| 153 | + * 获得学生班主任 | ||
| 154 | + * | ||
| 155 | + * @param id 编号 | ||
| 156 | + * @return 学生班主任 | ||
| 157 | + */ | ||
| 158 | + InfraStudentTeacherDO getStudentTeacher(Long id); | ||
| 159 | + | ||
| 160 | +} | ||
| 0 | \ No newline at end of file | 161 | \ No newline at end of file |