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 | 15 | - 7880:HTTP / WebSocket 信令; |
| 16 | 16 | - 7881:WebRTC TCP fallback; |
| 17 | 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 | 19 | - macOS 上 host network(`network_mode: host`)需要 Docker Desktop 4.34+ 并在 Settings → Resources → Network 勾选「Enable host networking」,老版本静默失败(容器跑得起来但端口完全不通); |
| 20 | 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 | 10 | tcp_port: 7881 |
| 11 | 11 | udp_port: 7882 |
| 12 | 12 | use_external_ip: false |
| 13 | + # macOS / Windows 端口映射模式下显式广播宿主机回环地址,避免把容器 172.x 地址发给浏览器 | |
| 14 | + node_ip: 127.0.0.1 | |
| 13 | 15 | |
| 14 | 16 | # Webhook:成员离开 / 房间结束等事件回调到 yudao 后端做业务态兜底清理 |
| 15 | 17 | # host.docker.internal 让容器访问宿主机 macOS / Windows 上的 yudao 后端 | ... | ... |
yudao-dependencies/pom.xml
| ... | ... | @@ -14,7 +14,7 @@ |
| 14 | 14 | <url>https://github.com/YunaiV/ruoyi-vue-pro</url> |
| 15 | 15 | |
| 16 | 16 | <properties> |
| 17 | - <revision>2026.06-SNAPSHOT</revision> | |
| 17 | + <revision>2026.07-SNAPSHOT</revision> | |
| 18 | 18 | <flatten-maven-plugin.version>1.7.2</flatten-maven-plugin.version> |
| 19 | 19 | <!-- 统一依赖管理 --> |
| 20 | 20 | <spring.boot.version>3.5.15</spring.boot.version> |
| ... | ... | @@ -59,8 +59,7 @@ |
| 59 | 59 | <fastexcel.version>1.3.0</fastexcel.version> |
| 60 | 60 | <aviator.version>5.3.3</aviator.version> |
| 61 | 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 | 63 | <guava.version>33.6.0-jre</guava.version> |
| 65 | 64 | <transmittable-thread-local.version>2.14.5</transmittable-thread-local.version> |
| 66 | 65 | <commons-net.version>3.13.0</commons-net.version> |
| ... | ... | @@ -244,6 +243,12 @@ |
| 244 | 243 | <groupId>org.dromara</groupId> |
| 245 | 244 | <artifactId>easy-trans-anno</artifactId> |
| 246 | 245 | <version>${easy-trans.version}</version> |
| 246 | + <exclusions> | |
| 247 | + <exclusion> | |
| 248 | + <groupId>com.alibaba</groupId> | |
| 249 | + <artifactId>fastjson</artifactId> | |
| 250 | + </exclusion> | |
| 251 | + </exclusions> | |
| 247 | 252 | </dependency> |
| 248 | 253 | |
| 249 | 254 | <dependency> |
| ... | ... | @@ -315,6 +320,12 @@ |
| 315 | 320 | <groupId>org.apache.rocketmq</groupId> |
| 316 | 321 | <artifactId>rocketmq-spring-boot-starter</artifactId> |
| 317 | 322 | <version>${rocketmq-spring.version}</version> |
| 323 | + <exclusions> | |
| 324 | + <exclusion> | |
| 325 | + <groupId>com.alibaba</groupId> | |
| 326 | + <artifactId>fastjson</artifactId> | |
| 327 | + </exclusion> | |
| 328 | + </exclusions> | |
| 318 | 329 | </dependency> |
| 319 | 330 | |
| 320 | 331 | <!-- 服务保障相关 --> |
| ... | ... | @@ -509,7 +520,7 @@ |
| 509 | 520 | <dependency> |
| 510 | 521 | <groupId>com.alibaba</groupId> |
| 511 | 522 | <artifactId>fastjson</artifactId> |
| 512 | - <version>${fastjson.version}</version> | |
| 523 | + <version>${fastjson2.version}</version> <!-- fastjson2 官方提供的 1.x API 兼容实现 --> | |
| 513 | 524 | </dependency> |
| 514 | 525 | <dependency> |
| 515 | 526 | <groupId>com.alibaba.fastjson2</groupId> |
| ... | ... | @@ -638,6 +649,12 @@ |
| 638 | 649 | <groupId>me.zhyd.oauth</groupId> |
| 639 | 650 | <artifactId>JustAuth</artifactId> <!-- 社交登陆(例如说,个人微信、企业微信等等) --> |
| 640 | 651 | <version>${justauth.version}</version> |
| 652 | + <exclusions> | |
| 653 | + <exclusion> | |
| 654 | + <groupId>com.alibaba</groupId> | |
| 655 | + <artifactId>fastjson</artifactId> | |
| 656 | + </exclusion> | |
| 657 | + </exclusions> | |
| 641 | 658 | </dependency> |
| 642 | 659 | <dependency> |
| 643 | 660 | <groupId>com.xkcoding.justauth</groupId> |
| ... | ... | @@ -661,6 +678,10 @@ |
| 661 | 678 | <groupId>org.bouncycastle</groupId> |
| 662 | 679 | <artifactId>bcprov-jdk15on</artifactId> |
| 663 | 680 | </exclusion> |
| 681 | + <exclusion> | |
| 682 | + <groupId>com.alibaba</groupId> | |
| 683 | + <artifactId>fastjson</artifactId> | |
| 684 | + </exclusion> | |
| 664 | 685 | </exclusions> |
| 665 | 686 | </dependency> |
| 666 | 687 | |
| ... | ... | @@ -703,6 +724,12 @@ |
| 703 | 724 | <groupId>org.jeecgframework.jimureport</groupId> |
| 704 | 725 | <artifactId>jimureport-spring-boot3-starter</artifactId> |
| 705 | 726 | <version>${jimureport.version}</version> |
| 727 | + <exclusions> | |
| 728 | + <exclusion> | |
| 729 | + <groupId>com.alibaba</groupId> | |
| 730 | + <artifactId>fastjson</artifactId> | |
| 731 | + </exclusion> | |
| 732 | + </exclusions> | |
| 706 | 733 | </dependency> |
| 707 | 734 | <dependency> |
| 708 | 735 | <groupId>org.jeecgframework.jimureport</groupId> |
| ... | ... | @@ -717,6 +744,10 @@ |
| 717 | 744 | <groupId>cn.hutool</groupId> |
| 718 | 745 | <artifactId>hutool-core</artifactId> |
| 719 | 746 | </exclusion> |
| 747 | + <exclusion> | |
| 748 | + <groupId>com.alibaba</groupId> | |
| 749 | + <artifactId>fastjson</artifactId> | |
| 750 | + </exclusion> | |
| 720 | 751 | </exclusions> |
| 721 | 752 | </dependency> |
| 722 | 753 | </dependencies> | ... | ... |
yudao-framework/yudao-common/pom.xml
| ... | ... | @@ -134,6 +134,11 @@ |
| 134 | 134 | </dependency> |
| 135 | 135 | |
| 136 | 136 | <dependency> |
| 137 | + <groupId>com.alibaba</groupId> | |
| 138 | + <artifactId>fastjson</artifactId> <!-- JustAuth、Jimu 等三方库仍使用 1.x 包名,由项目统一提供 fastjson2 兼容实现 --> | |
| 139 | + </dependency> | |
| 140 | + | |
| 141 | + <dependency> | |
| 137 | 142 | <groupId>org.dromara</groupId> <!-- VO 数据翻译 --> |
| 138 | 143 | <artifactId>easy-trans-anno</artifactId> <!-- 默认引入的原因,方便 xxx-module-api 包使用 --> |
| 139 | 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 | 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 | 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 | 23 | import jakarta.validation.ValidationException; |
| 24 | 24 | import lombok.AllArgsConstructor; |
| 25 | 25 | import lombok.extern.slf4j.Slf4j; |
| 26 | +import org.springframework.core.annotation.Order; | |
| 26 | 27 | import org.springframework.http.converter.HttpMessageNotReadableException; |
| 27 | 28 | import org.springframework.security.access.AccessDeniedException; |
| 28 | 29 | import org.springframework.util.Assert; |
| ... | ... | @@ -53,6 +54,7 @@ import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeC |
| 53 | 54 | * @author 芋道源码 |
| 54 | 55 | */ |
| 55 | 56 | @RestControllerAdvice |
| 57 | +@Order(0) // 优先于三方库默认的全局异常处理器,例如 JimuReport | |
| 56 | 58 | @AllArgsConstructor |
| 57 | 59 | @Slf4j |
| 58 | 60 | public class GlobalExceptionHandler { | ... | ... |
yudao-module-ai/pom.xml
| ... | ... | @@ -19,12 +19,24 @@ |
| 19 | 19 | 国外:OpenAI、Ollama、Midjourney、StableDiffusion、Suno |
| 20 | 20 | </description> |
| 21 | 21 | <properties> |
| 22 | - <spring-ai.version>1.1.5</spring-ai.version> | |
| 22 | + <spring-ai.version>1.1.8</spring-ai.version> | |
| 23 | 23 | <!-- https://mvnrepository.com/artifact/com.alibaba.cloud.ai/spring-ai-alibaba --> |
| 24 | 24 | <alibaba-ai.version>1.1.2.2</alibaba-ai.version> |
| 25 | 25 | <tinyflow.version>1.2.6</tinyflow.version> |
| 26 | 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 | 40 | <dependencies> |
| 29 | 41 | <dependency> |
| 30 | 42 | <groupId>cn.iocoder.boot</groupId> |
| ... | ... | @@ -112,37 +124,11 @@ |
| 112 | 124 | <version>${spring-ai.version}</version> |
| 113 | 125 | </dependency> |
| 114 | 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 | 128 | <groupId>com.alibaba.cloud.ai</groupId> |
| 129 | 129 | <artifactId>spring-ai-alibaba-starter-dashscope</artifactId> |
| 130 | 130 | <version>${alibaba-ai.version}</version> |
| 131 | 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 | 132 | <!-- 向量存储:https://db-engines.com/en/ranking/vector+dbms --> |
| 147 | 133 | <dependency> |
| 148 | 134 | <!-- Qdrant:https://qdrant.tech/ --> |
| ... | ... | @@ -256,8 +242,12 @@ |
| 256 | 242 | <groupId>org.slf4j</groupId> |
| 257 | 243 | <artifactId>slf4j-reload4j</artifactId> |
| 258 | 244 | </exclusion> |
| 245 | + <exclusion> | |
| 246 | + <groupId>com.alibaba</groupId> | |
| 247 | + <artifactId>fastjson</artifactId> | |
| 248 | + </exclusion> | |
| 259 | 249 | </exclusions> |
| 260 | 250 | </dependency> |
| 261 | 251 | </dependencies> |
| 262 | 252 | |
| 263 | -</project> | |
| 264 | 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 | 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 | 123 | @Operation(summary = "管理员删除对话") |
| 110 | 124 | @DeleteMapping("/delete-by-admin") |
| 111 | 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 | 131 | |
| 132 | 132 | @GetMapping("/page") |
| 133 | 133 | @Operation(summary = "获得消息分页", description = "用于【对话管理】菜单") |
| 134 | - @PreAuthorize("@ss.hasPermission('ai:chat-conversation:query')") | |
| 134 | + @PreAuthorize("@ss.hasPermission('ai:chat-message:query')") | |
| 135 | 135 | public CommonResult<PageResult<AiChatMessageRespVO>> getChatMessagePage(AiChatMessagePageReqVO pageReqVO) { |
| 136 | 136 | PageResult<AiChatMessageDO> pageResult = chatMessageService.getChatMessagePage(pageReqVO); |
| 137 | 137 | if (CollUtil.isEmpty(pageResult.getList())) { |
| ... | ... | @@ -145,6 +145,20 @@ public class AiChatMessageController { |
| 145 | 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 | 162 | @Operation(summary = "管理员删除消息") |
| 149 | 163 | @DeleteMapping("/delete-by-admin") |
| 150 | 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 | 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 | 131 | @PutMapping("/update") |
| 123 | 132 | @Operation(summary = "更新绘画") |
| 124 | 133 | @PreAuthorize("@ss.hasPermission('ai:image:update')") |
| ... | ... | @@ -136,4 +145,4 @@ public class AiImageController { |
| 136 | 145 | return success(true); |
| 137 | 146 | } |
| 138 | 147 | |
| 139 | -} | |
| 140 | 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 | 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 | 58 | @GetMapping("/page") |
| 50 | 59 | @Operation(summary = "获得思维导图分页") |
| 51 | 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 | 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 | 90 | @DeleteMapping("/delete") |
| 82 | 91 | @Operation(summary = "删除音乐") |
| 83 | 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 | 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 | 58 | @GetMapping("/page") |
| 50 | 59 | @Operation(summary = "获得写作分页") |
| 51 | 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 | 12 | // ========== API 密钥 1-040-000-000 ========== |
| 13 | 13 | ErrorCode API_KEY_NOT_EXISTS = new ErrorCode(1_040_000_000, "API 密钥不存在"); |
| 14 | 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 | 17 | // ========== API 模型 1-040-001-000 ========== |
| 17 | 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 | 28 | MINI_MAX("MiniMax", "MiniMax"), // 稀宇科技 |
| 29 | 29 | MOONSHOT("Moonshot", "月之暗面"), // KIMI |
| 30 | 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 | 41 | STABLE_DIFFUSION("StableDiffusion", "StableDiffusion"), // Stability AI |
| 41 | 42 | MIDJOURNEY("Midjourney", "Midjourney"), // Midjourney |
| 42 | 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 | import cn.iocoder.yudao.module.ai.framework.ai.core.model.grok.GrokChatModel; |
| 11 | 11 | import cn.iocoder.yudao.module.ai.framework.ai.core.model.hunyuan.HunYuanChatModel; |
| 12 | 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 | 15 | import cn.iocoder.yudao.module.ai.framework.ai.core.model.siliconflow.SiliconFlowApiConstants; |
| 14 | 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 | 18 | import cn.iocoder.yudao.module.ai.framework.ai.core.model.suno.api.SunoApi; |
| 16 | 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 | 22 | import cn.iocoder.yudao.module.ai.framework.ai.core.webserch.AiWebSearchClient; |
| 18 | 23 | import cn.iocoder.yudao.module.ai.framework.ai.core.webserch.bocha.AiBoChaWebSearchClient; |
| 19 | 24 | import cn.iocoder.yudao.module.ai.tool.method.PersonService; |
| ... | ... | @@ -44,7 +49,6 @@ import org.springframework.context.annotation.Bean; |
| 44 | 49 | import org.springframework.context.annotation.Configuration; |
| 45 | 50 | |
| 46 | 51 | import java.util.List; |
| 47 | -import java.util.Optional; | |
| 48 | 52 | |
| 49 | 53 | /** |
| 50 | 54 | * 芋道 AI 自动配置 |
| ... | ... | @@ -113,13 +117,13 @@ public class AiAutoConfiguration { |
| 113 | 117 | if (StrUtil.isEmpty(properties.getModel())) { |
| 114 | 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 | 122 | .baseUrl(DouBaoChatModel.BASE_URL) |
| 119 | 123 | .completionsPath(DouBaoChatModel.COMPLETE_PATH) |
| 120 | 124 | .apiKey(properties.getApiKey()) |
| 121 | 125 | .build()) |
| 122 | - .defaultOptions(OpenAiChatOptions.builder() | |
| 126 | + .defaultOptions(DeepSeekChatOptions.builder() | |
| 123 | 127 | .model(properties.getModel()) |
| 124 | 128 | .temperature(properties.getTemperature()) |
| 125 | 129 | .maxTokens(properties.getMaxTokens()) |
| ... | ... | @@ -168,11 +172,8 @@ public class AiAutoConfiguration { |
| 168 | 172 | if (StrUtil.isEmpty(properties.getModel())) { |
| 169 | 173 | properties.setModel(HunYuanChatModel.MODEL_DEFAULT); |
| 170 | 174 | } |
| 171 | - // 特殊:由于混元大模型不提供 deepseek,而是通过知识引擎,所以需要区分下 URL | |
| 172 | 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 | 178 | // 创建 DeepSeekChatModel、HunYuanChatModel 对象 |
| 178 | 179 | DeepSeekChatModel openAiChatModel = DeepSeekChatModel.builder() |
| ... | ... | @@ -203,25 +204,15 @@ public class AiAutoConfiguration { |
| 203 | 204 | if (StrUtil.isEmpty(properties.getModel())) { |
| 204 | 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 | 210 | .model(properties.getModel()) |
| 217 | 211 | .temperature(properties.getTemperature()) |
| 218 | 212 | .maxTokens(properties.getMaxTokens()) |
| 219 | 213 | .topP(properties.getTopP()) |
| 220 | 214 | .build()) |
| 221 | - // TODO @芋艿:星火的 function call 有 bug,会报 ToolResponseMessage must have an id 错误!!! | |
| 222 | - .toolCallingManager(getToolCallingManager()) | |
| 223 | 215 | .build(); |
| 224 | - return new XingHuoChatModel(openAiChatModel); | |
| 225 | 216 | } |
| 226 | 217 | |
| 227 | 218 | @Bean |
| ... | ... | @@ -235,12 +226,13 @@ public class AiAutoConfiguration { |
| 235 | 226 | if (StrUtil.isEmpty(properties.getModel())) { |
| 236 | 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 | 231 | .baseUrl(BaiChuanChatModel.BASE_URL) |
| 241 | 232 | .apiKey(properties.getApiKey()) |
| 233 | + .completionsPath(BaiChuanChatModel.COMPLETE_PATH) | |
| 242 | 234 | .build()) |
| 243 | - .defaultOptions(OpenAiChatOptions.builder() | |
| 235 | + .defaultOptions(DeepSeekChatOptions.builder() | |
| 244 | 236 | .model(properties.getModel()) |
| 245 | 237 | .temperature(properties.getTemperature()) |
| 246 | 238 | .maxTokens(properties.getMaxTokens()) |
| ... | ... | @@ -248,7 +240,123 @@ public class AiAutoConfiguration { |
| 248 | 240 | .build()) |
| 249 | 241 | .toolCallingManager(getToolCallingManager()) |
| 250 | 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 | 362 | @Bean |
| ... | ... | @@ -264,14 +372,20 @@ public class AiAutoConfiguration { |
| 264 | 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 | 383 | if (StrUtil.isEmpty(properties.getModel())) { |
| 269 | 384 | properties.setModel(GrokChatModel.MODEL_DEFAULT); |
| 270 | 385 | } |
| 271 | 386 | OpenAiChatModel openAiChatModel = OpenAiChatModel.builder() |
| 272 | 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 | 389 | .completionsPath(GrokChatModel.COMPLETE_PATH) |
| 276 | 390 | .apiKey(properties.getApiKey()) |
| 277 | 391 | .build()) |
| ... | ... | @@ -283,7 +397,7 @@ public class AiAutoConfiguration { |
| 283 | 397 | .build()) |
| 284 | 398 | .toolCallingManager(getToolCallingManager()) |
| 285 | 399 | .build(); |
| 286 | - return new DouBaoChatModel(openAiChatModel); | |
| 400 | + return new GrokChatModel(openAiChatModel); | |
| 287 | 401 | } |
| 288 | 402 | |
| 289 | 403 | // ========== RAG 相关 ========== |
| ... | ... | @@ -320,4 +434,4 @@ public class AiAutoConfiguration { |
| 320 | 434 | return List.of(ToolCallbacks.from(personService)); |
| 321 | 435 | } |
| 322 | 436 | |
| 323 | -} | |
| 324 | 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 | 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 | 77 | * Midjourney 绘图 |
| 48 | 78 | */ |
| 49 | 79 | private Midjourney midjourney; |
| ... | ... | @@ -116,9 +146,7 @@ public class YudaoAiProperties { |
| 116 | 146 | public static class XingHuo { |
| 117 | 147 | |
| 118 | 148 | private String enable; |
| 119 | - private String appId; | |
| 120 | - private String appKey; | |
| 121 | - private String secretKey; | |
| 149 | + private String apiKey; | |
| 122 | 150 | |
| 123 | 151 | private String model; |
| 124 | 152 | private Double temperature; |
| ... | ... | @@ -141,6 +169,76 @@ public class YudaoAiProperties { |
| 141 | 169 | } |
| 142 | 170 | |
| 143 | 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 | 242 | public static class Midjourney { |
| 145 | 243 | |
| 146 | 244 | private String enable; | ... | ... |
yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/AiModelFactoryImpl.java
| 1 | 1 | package cn.iocoder.yudao.module.ai.framework.ai.core.model; |
| 2 | 2 | |
| 3 | 3 | import cn.hutool.core.io.FileUtil; |
| 4 | -import cn.hutool.core.lang.Assert; | |
| 5 | 4 | import cn.hutool.core.lang.Singleton; |
| 6 | 5 | import cn.hutool.core.lang.func.Func0; |
| 7 | 6 | import cn.hutool.core.util.ArrayUtil; |
| ... | ... | @@ -15,14 +14,21 @@ import cn.iocoder.yudao.module.ai.framework.ai.config.YudaoAiProperties; |
| 15 | 14 | import cn.iocoder.yudao.module.ai.framework.ai.core.model.baichuan.BaiChuanChatModel; |
| 16 | 15 | import cn.iocoder.yudao.module.ai.framework.ai.core.model.doubao.DouBaoChatModel; |
| 17 | 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 | 18 | import cn.iocoder.yudao.module.ai.framework.ai.core.model.hunyuan.HunYuanChatModel; |
| 19 | 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 | 22 | import cn.iocoder.yudao.module.ai.framework.ai.core.model.siliconflow.SiliconFlowApiConstants; |
| 21 | 23 | import cn.iocoder.yudao.module.ai.framework.ai.core.model.siliconflow.SiliconFlowChatModel; |
| 22 | 24 | import cn.iocoder.yudao.module.ai.framework.ai.core.model.siliconflow.SiliconFlowImageApi; |
| 23 | 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 | 27 | import cn.iocoder.yudao.module.ai.framework.ai.core.model.suno.api.SunoApi; |
| 25 | 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 | 32 | import com.alibaba.cloud.ai.autoconfigure.dashscope.DashScopeChatAutoConfiguration; |
| 27 | 33 | import com.alibaba.cloud.ai.autoconfigure.dashscope.DashScopeEmbeddingAutoConfiguration; |
| 28 | 34 | import com.alibaba.cloud.ai.autoconfigure.dashscope.DashScopeImageAutoConfiguration; |
| ... | ... | @@ -40,18 +46,6 @@ import io.milvus.client.MilvusServiceClient; |
| 40 | 46 | import io.qdrant.client.QdrantClient; |
| 41 | 47 | import io.qdrant.client.QdrantGrpcClient; |
| 42 | 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 | 49 | import org.springframework.ai.azure.openai.AzureOpenAiChatModel; |
| 56 | 50 | import org.springframework.ai.azure.openai.AzureOpenAiEmbeddingModel; |
| 57 | 51 | import org.springframework.ai.chat.model.ChatModel; |
| ... | ... | @@ -63,27 +57,17 @@ import org.springframework.ai.embedding.BatchingStrategy; |
| 63 | 57 | import org.springframework.ai.embedding.EmbeddingModel; |
| 64 | 58 | import org.springframework.ai.embedding.observation.EmbeddingModelObservationConvention; |
| 65 | 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 | 60 | import org.springframework.ai.model.anthropic.autoconfigure.AnthropicChatAutoConfiguration; |
| 72 | 61 | import org.springframework.ai.model.azure.openai.autoconfigure.AzureOpenAiChatAutoConfiguration; |
| 73 | 62 | import org.springframework.ai.model.azure.openai.autoconfigure.AzureOpenAiEmbeddingAutoConfiguration; |
| 74 | 63 | import org.springframework.ai.model.azure.openai.autoconfigure.AzureOpenAiEmbeddingProperties; |
| 75 | 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 | 65 | import org.springframework.ai.model.ollama.autoconfigure.OllamaChatAutoConfiguration; |
| 79 | 66 | import org.springframework.ai.model.openai.autoconfigure.OpenAiChatAutoConfiguration; |
| 80 | 67 | import org.springframework.ai.model.openai.autoconfigure.OpenAiEmbeddingAutoConfiguration; |
| 81 | 68 | import org.springframework.ai.model.openai.autoconfigure.OpenAiImageAutoConfiguration; |
| 82 | 69 | import org.springframework.ai.model.stabilityai.autoconfigure.StabilityAiImageAutoConfiguration; |
| 83 | 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 | 71 | import org.springframework.ai.ollama.OllamaChatModel; |
| 88 | 72 | import org.springframework.ai.ollama.OllamaEmbeddingModel; |
| 89 | 73 | import org.springframework.ai.ollama.api.OllamaApi; |
| ... | ... | @@ -114,13 +98,9 @@ import org.springframework.ai.vectorstore.qdrant.autoconfigure.QdrantVectorStore |
| 114 | 98 | import org.springframework.ai.vectorstore.redis.RedisVectorStore; |
| 115 | 99 | import org.springframework.ai.vectorstore.redis.autoconfigure.RedisVectorStoreAutoConfiguration; |
| 116 | 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 | 101 | import org.springframework.beans.BeansException; |
| 121 | 102 | import org.springframework.beans.factory.ObjectProvider; |
| 122 | 103 | import org.springframework.boot.autoconfigure.data.redis.RedisProperties; |
| 123 | -import org.springframework.web.client.RestClient; | |
| 124 | 104 | import redis.clients.jedis.JedisPooled; |
| 125 | 105 | |
| 126 | 106 | import java.io.File; |
| ... | ... | @@ -131,7 +111,6 @@ import java.util.Timer; |
| 131 | 111 | import java.util.TimerTask; |
| 132 | 112 | |
| 133 | 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 | 116 | * AI Model 模型工厂的实现类 |
| ... | ... | @@ -141,7 +120,9 @@ import static org.springframework.ai.retry.RetryUtils.DEFAULT_RETRY_TEMPLATE; |
| 141 | 120 | public class AiModelFactoryImpl implements AiModelFactory { |
| 142 | 121 | |
| 143 | 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 | 126 | String cacheKey = buildClientCacheKey(ChatModel.class, platform, apiKey, url); |
| 146 | 127 | return Singleton.get(cacheKey, (Func0<ChatModel>) () -> { |
| 147 | 128 | // noinspection EnhancedSwitchMigration |
| ... | ... | @@ -164,6 +145,8 @@ public class AiModelFactoryImpl implements AiModelFactory { |
| 164 | 145 | return buildMiniMaxChatModel(apiKey, url); |
| 165 | 146 | case MOONSHOT: |
| 166 | 147 | return buildMoonshotChatModel(apiKey, url); |
| 148 | + case STEP_FUN: | |
| 149 | + return buildStepFunChatModel(apiKey, url); | |
| 167 | 150 | case XING_HUO: |
| 168 | 151 | return buildXingHuoChatModel(apiKey); |
| 169 | 152 | case BAI_CHUAN: |
| ... | ... | @@ -179,7 +162,7 @@ public class AiModelFactoryImpl implements AiModelFactory { |
| 179 | 162 | case OLLAMA: |
| 180 | 163 | return buildOllamaChatModel(url); |
| 181 | 164 | case GROK: |
| 182 | - return buildGrokChatModel(apiKey,url); | |
| 165 | + return buildGrokChatModel(apiKey, url); | |
| 183 | 166 | default: |
| 184 | 167 | throw new IllegalArgumentException(StrUtil.format("未知平台({})", platform)); |
| 185 | 168 | } |
| ... | ... | @@ -193,7 +176,7 @@ public class AiModelFactoryImpl implements AiModelFactory { |
| 193 | 176 | case TONG_YI: |
| 194 | 177 | return SpringUtil.getBean(DashScopeChatModel.class); |
| 195 | 178 | case YI_YAN: |
| 196 | - return SpringUtil.getBean(QianFanChatModel.class); | |
| 179 | + return SpringUtil.getBean(YiYanChatModel.class); | |
| 197 | 180 | case DEEP_SEEK: |
| 198 | 181 | return SpringUtil.getBean(DeepSeekChatModel.class); |
| 199 | 182 | case DOU_BAO: |
| ... | ... | @@ -203,11 +186,13 @@ public class AiModelFactoryImpl implements AiModelFactory { |
| 203 | 186 | case SILICON_FLOW: |
| 204 | 187 | return SpringUtil.getBean(SiliconFlowChatModel.class); |
| 205 | 188 | case ZHI_PU: |
| 206 | - return SpringUtil.getBean(ZhiPuAiChatModel.class); | |
| 189 | + return SpringUtil.getBean(ZhiPuChatModel.class); | |
| 207 | 190 | case MINI_MAX: |
| 208 | 191 | return SpringUtil.getBean(MiniMaxChatModel.class); |
| 209 | 192 | case MOONSHOT: |
| 210 | 193 | return SpringUtil.getBean(MoonshotChatModel.class); |
| 194 | + case STEP_FUN: | |
| 195 | + return SpringUtil.getBean(StepFunChatModel.class); | |
| 211 | 196 | case XING_HUO: |
| 212 | 197 | return SpringUtil.getBean(XingHuoChatModel.class); |
| 213 | 198 | case BAI_CHUAN: |
| ... | ... | @@ -222,6 +207,8 @@ public class AiModelFactoryImpl implements AiModelFactory { |
| 222 | 207 | return SpringUtil.getBean(GeminiChatModel.class); |
| 223 | 208 | case OLLAMA: |
| 224 | 209 | return SpringUtil.getBean(OllamaChatModel.class); |
| 210 | + case GROK: | |
| 211 | + return SpringUtil.getBean(GrokChatModel.class); | |
| 225 | 212 | default: |
| 226 | 213 | throw new IllegalArgumentException(StrUtil.format("未知平台({})", platform)); |
| 227 | 214 | } |
| ... | ... | @@ -233,10 +220,6 @@ public class AiModelFactoryImpl implements AiModelFactory { |
| 233 | 220 | switch (platform) { |
| 234 | 221 | case TONG_YI: |
| 235 | 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 | 223 | case SILICON_FLOW: |
| 241 | 224 | return SpringUtil.getBean(SiliconFlowImageModel.class); |
| 242 | 225 | case OPENAI: |
| ... | ... | @@ -249,19 +232,17 @@ public class AiModelFactoryImpl implements AiModelFactory { |
| 249 | 232 | } |
| 250 | 233 | |
| 251 | 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 | 238 | // noinspection EnhancedSwitchMigration |
| 254 | 239 | switch (platform) { |
| 255 | 240 | case TONG_YI: |
| 256 | 241 | return buildTongYiImagesModel(apiKey); |
| 257 | - case YI_YAN: | |
| 258 | - return buildQianFanImageModel(apiKey); | |
| 259 | - case ZHI_PU: | |
| 260 | - return buildZhiPuAiImageModel(apiKey, url); | |
| 261 | 242 | case OPENAI: |
| 262 | 243 | return buildOpenAiImageModel(apiKey, url); |
| 263 | 244 | case SILICON_FLOW: |
| 264 | - return buildSiliconFlowImageModel(apiKey,url); | |
| 245 | + return buildSiliconFlowImageModel(apiKey, url); | |
| 265 | 246 | case STABLE_DIFFUSION: |
| 266 | 247 | return buildStabilityAiImageModel(apiKey, url); |
| 267 | 248 | default: |
| ... | ... | @@ -270,9 +251,11 @@ public class AiModelFactoryImpl implements AiModelFactory { |
| 270 | 251 | } |
| 271 | 252 | |
| 272 | 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 | 259 | return Singleton.get(cacheKey, (Func0<MidjourneyApi>) () -> { |
| 277 | 260 | YudaoAiProperties.Midjourney properties = SpringUtil.getBean(YudaoAiProperties.class) |
| 278 | 261 | .getMidjourney(); |
| ... | ... | @@ -281,25 +264,23 @@ public class AiModelFactoryImpl implements AiModelFactory { |
| 281 | 264 | } |
| 282 | 265 | |
| 283 | 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 | 270 | String cacheKey = buildClientCacheKey(SunoApi.class, AiPlatformEnum.SUNO.getPlatform(), apiKey, url); |
| 286 | 271 | return Singleton.get(cacheKey, (Func0<SunoApi>) () -> new SunoApi(url)); |
| 287 | 272 | } |
| 288 | 273 | |
| 289 | 274 | @Override |
| 290 | 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 | 279 | String cacheKey = buildClientCacheKey(EmbeddingModel.class, platform, apiKey, url, model); |
| 293 | 280 | return Singleton.get(cacheKey, (Func0<EmbeddingModel>) () -> { |
| 294 | 281 | switch (platform) { |
| 295 | 282 | case TONG_YI: |
| 296 | 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 | 284 | case OPENAI: |
| 304 | 285 | return buildOpenAiEmbeddingModel(apiKey, url, model); |
| 305 | 286 | case AZURE_OPENAI: |
| ... | ... | @@ -341,6 +322,11 @@ public class AiModelFactoryImpl implements AiModelFactory { |
| 341 | 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 | 330 | // ========== 各种创建 spring-ai 客户端的方法 ========== |
| 345 | 331 | |
| 346 | 332 | /** |
| ... | ... | @@ -367,30 +353,10 @@ public class AiModelFactoryImpl implements AiModelFactory { |
| 367 | 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 | 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 | 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 | 443 | YudaoAiProperties.XingHuo properties = new YudaoAiProperties.XingHuo() |
| 493 | - .setAppKey(keys.get(0)).setSecretKey(keys.get(1)); | |
| 444 | + .setApiKey(apiKey).setModel(XingHuoChatModel.MODEL_DEFAULT); | |
| 494 | 445 | return new AiAutoConfiguration().buildXingHuoChatClient(properties); |
| 495 | 446 | } |
| 496 | 447 | |
| ... | ... | @@ -590,7 +541,7 @@ public class AiModelFactoryImpl implements AiModelFactory { |
| 590 | 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 | 545 | YudaoAiProperties.Grok properties = new YudaoAiProperties.Grok() |
| 595 | 546 | .setBaseUrl(url) |
| 596 | 547 | .setApiKey(apiKey); |
| ... | ... | @@ -608,41 +559,6 @@ public class AiModelFactoryImpl implements AiModelFactory { |
| 608 | 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 | 562 | private OllamaEmbeddingModel buildOllamaEmbeddingModel(String url, String model) { |
| 647 | 563 | OllamaApi ollamaApi = OllamaApi.builder().baseUrl(url).build(); |
| 648 | 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 | 6 | import org.springframework.ai.chat.model.ChatResponse; |
| 7 | 7 | import org.springframework.ai.chat.prompt.ChatOptions; |
| 8 | 8 | import org.springframework.ai.chat.prompt.Prompt; |
| 9 | -import org.springframework.ai.openai.OpenAiChatModel; | |
| 9 | +import org.springframework.ai.deepseek.DeepSeekChatModel; | |
| 10 | 10 | import reactor.core.publisher.Flux; |
| 11 | 11 | |
| 12 | 12 | /** |
| ... | ... | @@ -20,26 +20,28 @@ public class BaiChuanChatModel implements ChatModel { |
| 20 | 20 | |
| 21 | 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 | 32 | @Override |
| 31 | 33 | public ChatResponse call(Prompt prompt) { |
| 32 | - return openAiChatModel.call(prompt); | |
| 34 | + return deepSeekChatModel.call(prompt); | |
| 33 | 35 | } |
| 34 | 36 | |
| 35 | 37 | @Override |
| 36 | 38 | public Flux<ChatResponse> stream(Prompt prompt) { |
| 37 | - return openAiChatModel.stream(prompt); | |
| 39 | + return deepSeekChatModel.stream(prompt); | |
| 38 | 40 | } |
| 39 | 41 | |
| 40 | 42 | @Override |
| 41 | 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 | 20 | public static final String BASE_URL = "https://ark.cn-beijing.volces.com/api"; |
| 21 | 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 | 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 | 19 | |
| 20 | 20 | public static final String BASE_URL = "https://api.x.ai"; |
| 21 | 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 | 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 | 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 | 16 | * @author fansili |
| 18 | 17 | */ |
| ... | ... | @@ -20,14 +19,10 @@ import reactor.core.publisher.Flux; |
| 20 | 19 | @RequiredArgsConstructor |
| 21 | 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 | 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 | 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 | 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 | 4 | import lombok.extern.slf4j.Slf4j; |
| 5 | 5 | import org.springframework.ai.chat.model.ChatModel; |
| 6 | 6 | import org.springframework.ai.chat.model.ChatResponse; |
| 7 | 7 | import org.springframework.ai.chat.prompt.ChatOptions; |
| 8 | 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 | 12 | import reactor.core.publisher.Flux; |
| 10 | 13 | |
| 11 | 14 | /** |
| ... | ... | @@ -14,37 +17,140 @@ import reactor.core.publisher.Flux; |
| 14 | 17 | * @author fansili |
| 15 | 18 | */ |
| 16 | 19 | @Slf4j |
| 17 | -@RequiredArgsConstructor | |
| 18 | 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 | 71 | @Override |
| 36 | 72 | public ChatResponse call(Prompt prompt) { |
| 37 | - return openAiChatModelV1.call(prompt); | |
| 73 | + return getChatModel(prompt).call(buildApiPrompt(prompt)); | |
| 38 | 74 | } |
| 39 | 75 | |
| 40 | 76 | @Override |
| 41 | 77 | public Flux<ChatResponse> stream(Prompt prompt) { |
| 42 | - return openAiChatModelV1.stream(prompt); | |
| 78 | + return getChatModel(prompt).stream(buildApiPrompt(prompt)); | |
| 43 | 79 | } |
| 44 | 80 | |
| 45 | 81 | @Override |
| 46 | 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 | 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 | 59 | * @param id 消息编号 |
| ... | ... | @@ -69,6 +77,14 @@ public interface AiChatMessageService { |
| 69 | 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 | 88 | * 获得聊天对话的消息数量 Map |
| 73 | 89 | * |
| 74 | 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 | 525 | } |
| 526 | 526 | |
| 527 | 527 | @Override |
| 528 | + public AiChatMessageDO getChatMessage(Long id) { | |
| 529 | + return chatMessageMapper.selectById(id); | |
| 530 | + } | |
| 531 | + | |
| 532 | + @Override | |
| 528 | 533 | public void deleteChatMessage(Long id, Long userId) { |
| 529 | 534 | // 1. 校验消息存在 |
| 530 | 535 | AiChatMessageDO message = chatMessageMapper.selectById(id); |
| ... | ... | @@ -558,6 +563,11 @@ public class AiChatMessageServiceImpl implements AiChatMessageService { |
| 558 | 563 | } |
| 559 | 564 | |
| 560 | 565 | @Override |
| 566 | + public Integer getChatMessageCount(Long conversationId) { | |
| 567 | + return chatMessageMapper.selectCount(AiChatMessageDO::getConversationId, conversationId).intValue(); | |
| 568 | + } | |
| 569 | + | |
| 570 | + @Override | |
| 561 | 571 | public Map<Long, Integer> getChatMessageCountMap(Collection<Long> conversationIds) { |
| 562 | 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 | 29 | import com.alibaba.cloud.ai.dashscope.image.DashScopeImageOptions; |
| 30 | 30 | import jakarta.annotation.Resource; |
| 31 | 31 | import lombok.extern.slf4j.Slf4j; |
| 32 | -import org.springaicommunity.qianfan.QianFanImageOptions; | |
| 33 | 32 | import org.springframework.ai.image.ImageModel; |
| 34 | 33 | import org.springframework.ai.image.ImageOptions; |
| 35 | 34 | import org.springframework.ai.image.ImagePrompt; |
| 36 | 35 | import org.springframework.ai.image.ImageResponse; |
| 37 | 36 | import org.springframework.ai.openai.OpenAiImageOptions; |
| 38 | 37 | import org.springframework.ai.stabilityai.api.StabilityAiImageOptions; |
| 39 | -import org.springframework.ai.zhipuai.ZhiPuAiImageOptions; | |
| 40 | 38 | import org.springframework.scheduling.annotation.Async; |
| 41 | 39 | import org.springframework.stereotype.Service; |
| 42 | 40 | import org.springframework.transaction.annotation.Transactional; |
| ... | ... | @@ -168,15 +166,6 @@ public class AiImageServiceImpl implements AiImageService { |
| 168 | 166 | .model(model.getModel()).n(1) |
| 169 | 167 | .height(draw.getHeight()).width(draw.getWidth()) |
| 170 | 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 | 170 | throw new IllegalArgumentException("不支持的 AI 平台:" + model.getPlatform()); |
| 182 | 171 | } | ... | ... |
yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/service/mindmap/AiMindMapService.java
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 | 154 | } |
| 155 | 155 | |
| 156 | 156 | @Override |
| 157 | + public AiMindMapDO getMindMap(Long id) { | |
| 158 | + return mindMapMapper.selectById(id); | |
| 159 | + } | |
| 160 | + | |
| 161 | + @Override | |
| 157 | 162 | public PageResult<AiMindMapDO> getMindMapPage(AiMindMapPageReqVO pageReqVO) { |
| 158 | 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 | 10 | import cn.iocoder.yudao.module.ai.dal.dataobject.workflow.AiWorkflowDO; |
| 11 | 11 | import cn.iocoder.yudao.module.ai.dal.mysql.workflow.AiWorkflowMapper; |
| 12 | 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 | 15 | import dev.tinyflow.core.Tinyflow; |
| 16 | 16 | import jakarta.annotation.Resource; |
| 17 | 17 | import lombok.extern.slf4j.Slf4j; | ... | ... |
yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/service/write/AiWriteService.java
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 | 171 | } |
| 172 | 172 | |
| 173 | 173 | @Override |
| 174 | + public AiWriteDO getWrite(Long id) { | |
| 175 | + return writeMapper.selectById(id); | |
| 176 | + } | |
| 177 | + | |
| 178 | + @Override | |
| 174 | 179 | public PageResult<AiWriteDO> getWritePage(AiWritePageReqVO pageReqVO) { |
| 175 | 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 | import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder; |
| 9 | 9 | import cn.iocoder.yudao.module.ai.enums.model.AiPlatformEnum; |
| 10 | 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 | 12 | import org.springframework.ai.anthropic.AnthropicChatOptions; |
| 14 | 13 | import org.springframework.ai.azure.openai.AzureOpenAiChatOptions; |
| 15 | 14 | import org.springframework.ai.chat.messages.*; |
| ... | ... | @@ -17,14 +16,16 @@ import org.springframework.ai.chat.model.ChatResponse; |
| 17 | 16 | import org.springframework.ai.chat.prompt.ChatOptions; |
| 18 | 17 | import org.springframework.ai.deepseek.DeepSeekAssistantMessage; |
| 19 | 18 | import org.springframework.ai.deepseek.DeepSeekChatOptions; |
| 20 | -import org.springframework.ai.minimax.MiniMaxChatOptions; | |
| 21 | 19 | import org.springframework.ai.ollama.api.OllamaChatOptions; |
| 22 | 20 | import org.springframework.ai.openai.OpenAiChatOptions; |
| 23 | 21 | import org.springframework.ai.tool.ToolCallback; |
| 24 | -import org.springframework.ai.zhipuai.ZhiPuAiChatOptions; | |
| 22 | +import org.springframework.core.env.Environment; | |
| 25 | 23 | |
| 26 | 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 | 30 | * Spring AI 工具类 |
| 30 | 31 | * |
| ... | ... | @@ -36,13 +37,42 @@ public class AiUtils { |
| 36 | 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 | 70 | * @see <a href="https://bailian.console.aliyun.com/cn-beijing/?tab=model#/model-market/all?providers=qwen&capabilities=VU">模型广场</a> |
| 42 | 71 | * @see <a href="https://help.aliyun.com/zh/model-studio/error-code#error-url">必须开启 withMultiModel 参数</a> |
| 43 | 72 | */ |
| 44 | 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 | 76 | "qwen3.6-plus", "qwen3.6-flash", |
| 47 | 77 | "qwen3.5-plus", "qwen3.5-flash", |
| 48 | 78 | // qwen-vl 视觉理解 |
| ... | ... | @@ -72,27 +102,21 @@ public class AiUtils { |
| 72 | 102 | .enableThinking(true) // TODO 芋艿:默认都开启 thinking 模式,后续可以让用户配置 |
| 73 | 103 | .multiModel(TONG_YI_MULTI_MODELS.contains(model)) // 是否多模态模型 |
| 74 | 104 | .toolCallbacks(toolCallbacks).toolContext(toolContext).build(); |
| 75 | - case YI_YAN: | |
| 76 | - return QianFanChatOptions.builder().model(model).temperature(temperature).maxTokens(maxTokens).build(); | |
| 77 | 105 | case DEEP_SEEK: |
| 78 | 106 | case DOU_BAO: // 复用 DeepSeek 客户端 |
| 79 | 107 | case HUN_YUAN: // 复用 DeepSeek 客户端 |
| 80 | 108 | case SILICON_FLOW: // 复用 DeepSeek 客户端 |
| 109 | + case YI_YAN: // 复用 DeepSeek 客户端 | |
| 110 | + case ZHI_PU: // 复用 DeepSeek 客户端 | |
| 81 | 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 | 116 | return DeepSeekChatOptions.builder().model(model).temperature(temperature).maxTokens(maxTokens) |
| 83 | 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 | 118 | case OPENAI: |
| 94 | 119 | case GEMINI: // 复用 OpenAI 客户端 |
| 95 | - case BAI_CHUAN: // 复用 OpenAI 客户端 | |
| 96 | 120 | case GROK: // 复用 OpenAI 客户端 |
| 97 | 121 | return OpenAiChatOptions.builder().model(model).temperature(temperature).maxTokens(maxTokens) |
| 98 | 122 | .toolCallbacks(toolCallbacks).toolContext(toolContext).build(); |
| ... | ... | @@ -159,4 +183,4 @@ public class AiUtils { |
| 159 | 183 | return MapUtil.getStr(output.getMetadata(), "reasoningContent"); |
| 160 | 184 | } |
| 161 | 185 | |
| 162 | -} | |
| 163 | 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 | 1 | package cn.iocoder.yudao.module.ai.framework.ai.core.model.chat; |
| 2 | 2 | |
| 3 | +import cn.hutool.system.SystemUtil; | |
| 3 | 4 | import cn.iocoder.yudao.module.ai.framework.ai.core.model.baichuan.BaiChuanChatModel; |
| 4 | 5 | import org.junit.jupiter.api.Disabled; |
| 5 | 6 | import org.junit.jupiter.api.Test; |
| ... | ... | @@ -8,13 +9,16 @@ import org.springframework.ai.chat.messages.SystemMessage; |
| 8 | 9 | import org.springframework.ai.chat.messages.UserMessage; |
| 9 | 10 | import org.springframework.ai.chat.model.ChatResponse; |
| 10 | 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 | 15 | import reactor.core.publisher.Flux; |
| 15 | 16 | |
| 16 | 17 | import java.util.ArrayList; |
| 17 | 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 | 24 | * {@link BaiChuanChatModel} 集成测试 |
| ... | ... | @@ -23,22 +27,27 @@ import java.util.List; |
| 23 | 27 | */ |
| 24 | 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 | 37 | .baseUrl(BaiChuanChatModel.BASE_URL) |
| 29 | - .apiKey("sk-61b6766a94c70786ed02673f5e16af3c") // apiKey | |
| 38 | + .completionsPath(BaiChuanChatModel.COMPLETE_PATH) | |
| 39 | + .apiKey(API_KEY) | |
| 30 | 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 | 43 | .temperature(0.7) |
| 34 | 44 | .build()) |
| 35 | - .build(); | |
| 36 | - | |
| 37 | - private final BaiChuanChatModel chatModel = new BaiChuanChatModel(openAiChatModel); | |
| 45 | + .build()); | |
| 38 | 46 | |
| 39 | 47 | @Test |
| 40 | 48 | @Disabled |
| 41 | 49 | public void testCall() { |
| 50 | + validateApiKey(API_KEY); | |
| 42 | 51 | // 准备参数 |
| 43 | 52 | List<Message> messages = new ArrayList<>(); |
| 44 | 53 | messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。")); |
| ... | ... | @@ -48,11 +57,13 @@ public class BaiChuanChatModelTests { |
| 48 | 57 | ChatResponse response = chatModel.call(new Prompt(messages)); |
| 49 | 58 | // 打印结果 |
| 50 | 59 | System.out.println(response); |
| 60 | + System.out.println(Objects.requireNonNull(response.getResult()).getOutput()); | |
| 51 | 61 | } |
| 52 | 62 | |
| 53 | 63 | @Test |
| 54 | 64 | @Disabled |
| 55 | 65 | public void testStream() { |
| 66 | + validateApiKey(API_KEY); | |
| 56 | 67 | // 准备参数 |
| 57 | 68 | List<Message> messages = new ArrayList<>(); |
| 58 | 69 | messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。")); |
| ... | ... | @@ -61,7 +72,10 @@ public class BaiChuanChatModelTests { |
| 61 | 72 | // 调用 |
| 62 | 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 | 1 | package cn.iocoder.yudao.module.ai.framework.ai.core.model.chat; |
| 2 | 2 | |
| 3 | +import cn.hutool.system.SystemUtil; | |
| 3 | 4 | import org.junit.jupiter.api.Disabled; |
| 4 | 5 | import org.junit.jupiter.api.Test; |
| 5 | 6 | import org.springframework.ai.chat.messages.Message; |
| ... | ... | @@ -14,6 +15,9 @@ import reactor.core.publisher.Flux; |
| 14 | 15 | |
| 15 | 16 | import java.util.ArrayList; |
| 16 | 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 | 23 | * {@link DeepSeekChatModel} 集成测试 |
| ... | ... | @@ -22,12 +26,17 @@ import java.util.List; |
| 22 | 26 | */ |
| 23 | 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 | 34 | private final DeepSeekChatModel chatModel = DeepSeekChatModel.builder() |
| 26 | 35 | .deepSeekApi(DeepSeekApi.builder() |
| 27 | - .apiKey("sk-eaf4172a057344dd9bc64b1f806b6axx") // apiKey | |
| 36 | + .apiKey(API_KEY) // apiKey | |
| 28 | 37 | .build()) |
| 29 | 38 | .defaultOptions(DeepSeekChatOptions.builder() |
| 30 | - .model("deepseek-chat") // 模型 | |
| 39 | + .model(MODEL) // 模型 | |
| 31 | 40 | .temperature(0.7) |
| 32 | 41 | .build()) |
| 33 | 42 | .build(); |
| ... | ... | @@ -35,6 +44,7 @@ public class DeepSeekChatModelTests { |
| 35 | 44 | @Test |
| 36 | 45 | @Disabled |
| 37 | 46 | public void testCall() { |
| 47 | + validateApiKey(API_KEY); | |
| 38 | 48 | // 准备参数 |
| 39 | 49 | List<Message> messages = new ArrayList<>(); |
| 40 | 50 | messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。")); |
| ... | ... | @@ -43,12 +53,13 @@ public class DeepSeekChatModelTests { |
| 43 | 53 | // 调用 |
| 44 | 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 | 59 | @Test |
| 50 | 60 | @Disabled |
| 51 | 61 | public void testStream() { |
| 62 | + validateApiKey(API_KEY); | |
| 52 | 63 | // 准备参数 |
| 53 | 64 | List<Message> messages = new ArrayList<>(); |
| 54 | 65 | messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。")); |
| ... | ... | @@ -63,11 +74,12 @@ public class DeepSeekChatModelTests { |
| 63 | 74 | @Test |
| 64 | 75 | @Disabled |
| 65 | 76 | public void testStream_thinking() { |
| 77 | + validateApiKey(API_KEY); | |
| 66 | 78 | // 准备参数 |
| 67 | 79 | List<Message> messages = new ArrayList<>(); |
| 68 | 80 | messages.add(new UserMessage("详细分析下,如何设计一个电商系统?")); |
| 69 | 81 | DeepSeekChatOptions options = DeepSeekChatOptions.builder() |
| 70 | - .model("deepseek-reasoner") | |
| 82 | + .model(MODEL) | |
| 71 | 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 | 1 | package cn.iocoder.yudao.module.ai.framework.ai.core.model.chat; |
| 2 | 2 | |
| 3 | +import cn.hutool.system.SystemUtil; | |
| 3 | 4 | import cn.iocoder.yudao.module.ai.framework.ai.core.model.doubao.DouBaoChatModel; |
| 4 | 5 | import org.junit.jupiter.api.Disabled; |
| 5 | 6 | import org.junit.jupiter.api.Test; |
| ... | ... | @@ -15,6 +16,9 @@ import reactor.core.publisher.Flux; |
| 15 | 16 | |
| 16 | 17 | import java.util.ArrayList; |
| 17 | 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 | 24 | * {@link DouBaoChatModel} 集成测试 |
| ... | ... | @@ -23,19 +27,19 @@ import java.util.List; |
| 23 | 27 | */ |
| 24 | 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 | 35 | private final DeepSeekChatModel openAiChatModel = DeepSeekChatModel.builder() |
| 30 | 36 | .deepSeekApi(DeepSeekApi.builder() |
| 31 | 37 | .baseUrl(DouBaoChatModel.BASE_URL) |
| 32 | 38 | .completionsPath(DouBaoChatModel.COMPLETE_PATH) |
| 33 | - .apiKey("5c1b5747-26d2-4ebd-a4e0-dd0e8d8b4272") // apiKey | |
| 39 | + .apiKey(API_KEY) | |
| 34 | 40 | .build()) |
| 35 | 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 | 43 | .temperature(0.7) |
| 40 | 44 | .build()) |
| 41 | 45 | .build(); |
| ... | ... | @@ -45,6 +49,7 @@ public class DouBaoChatModelTests { |
| 45 | 49 | @Test |
| 46 | 50 | @Disabled |
| 47 | 51 | public void testCall() { |
| 52 | + validateApiKey(API_KEY); | |
| 48 | 53 | // 准备参数 |
| 49 | 54 | List<Message> messages = new ArrayList<>(); |
| 50 | 55 | messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。")); |
| ... | ... | @@ -54,11 +59,13 @@ public class DouBaoChatModelTests { |
| 54 | 59 | ChatResponse response = chatModel.call(new Prompt(messages)); |
| 55 | 60 | // 打印结果 |
| 56 | 61 | System.out.println(response); |
| 62 | + System.out.println(Objects.requireNonNull(response.getResult()).getOutput()); | |
| 57 | 63 | } |
| 58 | 64 | |
| 59 | 65 | @Test |
| 60 | 66 | @Disabled |
| 61 | 67 | public void testStream() { |
| 68 | + validateApiKey(API_KEY); | |
| 62 | 69 | // 准备参数 |
| 63 | 70 | List<Message> messages = new ArrayList<>(); |
| 64 | 71 | messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。")); |
| ... | ... | @@ -67,25 +74,26 @@ public class DouBaoChatModelTests { |
| 67 | 74 | // 调用 |
| 68 | 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 | 83 | @Test |
| 74 | 84 | @Disabled |
| 75 | 85 | public void testStream_thinking() { |
| 86 | + validateApiKey(API_KEY); | |
| 76 | 87 | // 准备参数 |
| 77 | 88 | List<Message> messages = new ArrayList<>(); |
| 78 | 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 | 94 | flux.doOnNext(response -> { |
| 87 | 95 | // System.out.println(response); |
| 88 | - System.out.println(response.getResult().getOutput()); | |
| 96 | + System.out.println(Objects.requireNonNull(response.getResult()).getOutput()); | |
| 89 | 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 | 1 | package cn.iocoder.yudao.module.ai.framework.ai.core.model.chat; |
| 2 | 2 | |
| 3 | +import cn.hutool.system.SystemUtil; | |
| 3 | 4 | import cn.iocoder.yudao.module.ai.framework.ai.core.model.hunyuan.HunYuanChatModel; |
| 4 | 5 | import org.junit.jupiter.api.Disabled; |
| 5 | 6 | import org.junit.jupiter.api.Test; |
| ... | ... | @@ -15,6 +16,9 @@ import reactor.core.publisher.Flux; |
| 15 | 16 | |
| 16 | 17 | import java.util.ArrayList; |
| 17 | 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 | 24 | * {@link HunYuanChatModel} 集成测试 |
| ... | ... | @@ -23,14 +27,19 @@ import java.util.List; |
| 23 | 27 | */ |
| 24 | 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 | 35 | private final DeepSeekChatModel openAiChatModel = DeepSeekChatModel.builder() |
| 27 | 36 | .deepSeekApi(DeepSeekApi.builder() |
| 28 | 37 | .baseUrl(HunYuanChatModel.BASE_URL) |
| 29 | 38 | .completionsPath(HunYuanChatModel.COMPLETE_PATH) |
| 30 | - .apiKey("sk-abc") // apiKey | |
| 39 | + .apiKey(API_KEY) // apiKey | |
| 31 | 40 | .build()) |
| 32 | 41 | .defaultOptions(DeepSeekChatOptions.builder() |
| 33 | - .model(HunYuanChatModel.MODEL_DEFAULT) // 模型 | |
| 42 | + .model(MODEL) // 模型 | |
| 34 | 43 | .temperature(0.7) |
| 35 | 44 | .build()) |
| 36 | 45 | .build(); |
| ... | ... | @@ -40,6 +49,7 @@ public class HunYuanChatModelTests { |
| 40 | 49 | @Test |
| 41 | 50 | @Disabled |
| 42 | 51 | public void testCall() { |
| 52 | + validateApiKey(API_KEY); | |
| 43 | 53 | // 准备参数 |
| 44 | 54 | List<Message> messages = new ArrayList<>(); |
| 45 | 55 | messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。")); |
| ... | ... | @@ -48,12 +58,13 @@ public class HunYuanChatModelTests { |
| 48 | 58 | // 调用 |
| 49 | 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 | 64 | @Test |
| 55 | 65 | @Disabled |
| 56 | 66 | public void testStream() { |
| 67 | + validateApiKey(API_KEY); | |
| 57 | 68 | // 准备参数 |
| 58 | 69 | List<Message> messages = new ArrayList<>(); |
| 59 | 70 | messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。")); |
| ... | ... | @@ -68,12 +79,12 @@ public class HunYuanChatModelTests { |
| 68 | 79 | @Test |
| 69 | 80 | @Disabled |
| 70 | 81 | public void testStream_thinking() { |
| 82 | + validateApiKey(API_KEY); | |
| 71 | 83 | // 准备参数 |
| 72 | 84 | List<Message> messages = new ArrayList<>(); |
| 73 | 85 | messages.add(new UserMessage("详细分析下,如何设计一个电商系统?")); |
| 74 | 86 | DeepSeekChatOptions options = DeepSeekChatOptions.builder() |
| 75 | - .model("hunyuan-a13b") | |
| 76 | -// .model("hunyuan-turbos-latest") | |
| 87 | + .model(MODEL) | |
| 77 | 88 | .build(); |
| 78 | 89 | |
| 79 | 90 | // 调用 |
| ... | ... | @@ -85,66 +96,4 @@ public class HunYuanChatModelTests { |
| 85 | 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 | 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 | 5 | import org.junit.jupiter.api.Disabled; |
| 4 | 6 | import org.junit.jupiter.api.Test; |
| 5 | 7 | import org.springframework.ai.chat.messages.Message; |
| ... | ... | @@ -7,13 +9,16 @@ import org.springframework.ai.chat.messages.SystemMessage; |
| 7 | 9 | import org.springframework.ai.chat.messages.UserMessage; |
| 8 | 10 | import org.springframework.ai.chat.model.ChatResponse; |
| 9 | 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 | 15 | import reactor.core.publisher.Flux; |
| 14 | 16 | |
| 15 | 17 | import java.util.ArrayList; |
| 16 | 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 | 24 | * {@link MiniMaxChatModel} 的集成测试 |
| ... | ... | @@ -22,14 +27,24 @@ import java.util.List; |
| 22 | 27 | */ |
| 23 | 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 | 44 | @Test |
| 31 | 45 | @Disabled |
| 32 | 46 | public void testCall() { |
| 47 | + validateApiKey(API_KEY); | |
| 33 | 48 | // 准备参数 |
| 34 | 49 | List<Message> messages = new ArrayList<>(); |
| 35 | 50 | messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。")); |
| ... | ... | @@ -39,12 +54,13 @@ public class MiniMaxChatModelTests { |
| 39 | 54 | ChatResponse response = chatModel.call(new Prompt(messages)); |
| 40 | 55 | // 打印结果 |
| 41 | 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 | 60 | @Test |
| 46 | 61 | @Disabled |
| 47 | 62 | public void testStream() { |
| 63 | + validateApiKey(API_KEY); | |
| 48 | 64 | // 准备参数 |
| 49 | 65 | List<Message> messages = new ArrayList<>(); |
| 50 | 66 | messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。")); |
| ... | ... | @@ -55,19 +71,19 @@ public class MiniMaxChatModelTests { |
| 55 | 71 | // 打印结果 |
| 56 | 72 | flux.doOnNext(response -> { |
| 57 | 73 | // System.out.println(response); |
| 58 | - System.out.println(response.getResult().getOutput()); | |
| 74 | + System.out.println(Objects.requireNonNull(response.getResult()).getOutput()); | |
| 59 | 75 | }).then().block(); |
| 60 | 76 | } |
| 61 | 77 | |
| 62 | - // TODO @芋艿:暂时没解析 reasoning_content 结果,需要等官方修复 | |
| 63 | 78 | @Test |
| 64 | 79 | @Disabled |
| 65 | 80 | public void testStream_thinking() { |
| 81 | + validateApiKey(API_KEY); | |
| 66 | 82 | // 准备参数 |
| 67 | 83 | List<Message> messages = new ArrayList<>(); |
| 68 | 84 | messages.add(new UserMessage("详细分析下,如何设计一个电商系统?")); |
| 69 | - MiniMaxChatOptions options = MiniMaxChatOptions.builder() | |
| 70 | - .model("MiniMax-M1") | |
| 85 | + DeepSeekChatOptions options = DeepSeekChatOptions.builder() | |
| 86 | + .model(MODEL) | |
| 71 | 87 | .build(); |
| 72 | 88 | |
| 73 | 89 | // 调用 |
| ... | ... | @@ -75,7 +91,7 @@ public class MiniMaxChatModelTests { |
| 75 | 91 | // 打印结果 |
| 76 | 92 | flux.doOnNext(response -> { |
| 77 | 93 | // System.out.println(response); |
| 78 | - System.out.println(response.getResult().getOutput()); | |
| 94 | + System.out.println(Objects.requireNonNull(response.getResult()).getOutput()); | |
| 79 | 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 | 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 | 5 | import org.junit.jupiter.api.Disabled; |
| 4 | 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 | 7 | import org.springframework.ai.chat.messages.Message; |
| 9 | 8 | import org.springframework.ai.chat.messages.SystemMessage; |
| 10 | 9 | import org.springframework.ai.chat.messages.UserMessage; |
| 11 | 10 | import org.springframework.ai.chat.model.ChatResponse; |
| 12 | 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 | 15 | import reactor.core.publisher.Flux; |
| 14 | 16 | |
| 15 | 17 | import java.util.ArrayList; |
| 16 | 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 | 24 | * {@link MoonshotChatModel} 的集成测试 |
| ... | ... | @@ -22,18 +27,27 @@ import java.util.List; |
| 22 | 27 | */ |
| 23 | 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 | 40 | .build()) |
| 29 | - .defaultOptions(MoonshotChatOptions.builder() | |
| 30 | - .model("kimi-k2-0711-preview") // 模型 | |
| 41 | + .defaultOptions(DeepSeekChatOptions.builder() | |
| 42 | + .model(MODEL) | |
| 43 | + .temperature(1D) | |
| 31 | 44 | .build()) |
| 32 | - .build(); | |
| 45 | + .build()); | |
| 33 | 46 | |
| 34 | 47 | @Test |
| 35 | 48 | @Disabled |
| 36 | 49 | public void testCall() { |
| 50 | + validateApiKey(API_KEY); | |
| 37 | 51 | // 准备参数 |
| 38 | 52 | List<Message> messages = new ArrayList<>(); |
| 39 | 53 | messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。")); |
| ... | ... | @@ -43,12 +57,13 @@ public class MoonshotChatModelTests { |
| 43 | 57 | ChatResponse response = chatModel.call(new Prompt(messages)); |
| 44 | 58 | // 打印结果 |
| 45 | 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 | 63 | @Test |
| 50 | 64 | @Disabled |
| 51 | 65 | public void testStream() { |
| 66 | + validateApiKey(API_KEY); | |
| 52 | 67 | // 准备参数 |
| 53 | 68 | List<Message> messages = new ArrayList<>(); |
| 54 | 69 | messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。")); |
| ... | ... | @@ -59,20 +74,19 @@ public class MoonshotChatModelTests { |
| 59 | 74 | // 打印结果 |
| 60 | 75 | flux.doOnNext(response -> { |
| 61 | 76 | // System.out.println(response); |
| 62 | - System.out.println(response.getResult().getOutput()); | |
| 77 | + System.out.println(Objects.requireNonNull(response.getResult()).getOutput()); | |
| 63 | 78 | }).then().block(); |
| 64 | 79 | } |
| 65 | 80 | |
| 66 | - // TODO @芋艿:暂时没解析 reasoning_content 结果,需要等官方修复 | |
| 67 | 81 | @Test |
| 68 | 82 | @Disabled |
| 69 | 83 | public void testStream_thinking() { |
| 84 | + validateApiKey(API_KEY); | |
| 70 | 85 | // 准备参数 |
| 71 | 86 | List<Message> messages = new ArrayList<>(); |
| 72 | 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 | 90 | .build(); |
| 77 | 91 | |
| 78 | 92 | // 调用 |
| ... | ... | @@ -80,7 +94,7 @@ public class MoonshotChatModelTests { |
| 80 | 94 | // 打印结果 |
| 81 | 95 | flux.doOnNext(response -> { |
| 82 | 96 | // System.out.println(response); |
| 83 | - System.out.println(response.getResult().getOutput()); | |
| 97 | + System.out.println(Objects.requireNonNull(response.getResult()).getOutput()); | |
| 84 | 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 | 1 | package cn.iocoder.yudao.module.ai.framework.ai.core.model.chat; |
| 2 | 2 | |
| 3 | +import cn.hutool.system.SystemUtil; | |
| 3 | 4 | import cn.iocoder.yudao.module.ai.framework.ai.core.model.xinghuo.XingHuoChatModel; |
| 4 | 5 | import org.junit.jupiter.api.Disabled; |
| 5 | 6 | import org.junit.jupiter.api.Test; |
| ... | ... | @@ -8,13 +9,14 @@ import org.springframework.ai.chat.messages.SystemMessage; |
| 8 | 9 | import org.springframework.ai.chat.messages.UserMessage; |
| 9 | 10 | import org.springframework.ai.chat.model.ChatResponse; |
| 10 | 11 | import org.springframework.ai.chat.prompt.Prompt; |
| 11 | -import org.springframework.ai.deepseek.DeepSeekChatModel; | |
| 12 | 12 | import org.springframework.ai.deepseek.DeepSeekChatOptions; |
| 13 | -import org.springframework.ai.deepseek.api.DeepSeekApi; | |
| 14 | 13 | import reactor.core.publisher.Flux; |
| 15 | 14 | |
| 16 | 15 | import java.util.ArrayList; |
| 17 | 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 | 22 | * {@link XingHuoChatModel} 集成测试 |
| ... | ... | @@ -23,24 +25,23 @@ import java.util.List; |
| 23 | 25 | */ |
| 24 | 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 | 41 | @Test |
| 42 | 42 | @Disabled |
| 43 | 43 | public void testCall() { |
| 44 | + validateApiKey(API_KEY); | |
| 44 | 45 | // 准备参数 |
| 45 | 46 | List<Message> messages = new ArrayList<>(); |
| 46 | 47 | messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。")); |
| ... | ... | @@ -55,6 +56,7 @@ public class XingHuoChatModelTests { |
| 55 | 56 | @Test |
| 56 | 57 | @Disabled |
| 57 | 58 | public void testStream() { |
| 59 | + validateApiKey(API_KEY); | |
| 58 | 60 | // 准备参数 |
| 59 | 61 | List<Message> messages = new ArrayList<>(); |
| 60 | 62 | messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。")); |
| ... | ... | @@ -69,11 +71,12 @@ public class XingHuoChatModelTests { |
| 69 | 71 | @Test |
| 70 | 72 | @Disabled |
| 71 | 73 | public void testStream_thinking() { |
| 74 | + validateApiKey(API_KEY); | |
| 72 | 75 | // 准备参数 |
| 73 | 76 | List<Message> messages = new ArrayList<>(); |
| 74 | 77 | messages.add(new UserMessage("详细分析下,如何设计一个电商系统?")); |
| 75 | 78 | DeepSeekChatOptions options = DeepSeekChatOptions.builder() |
| 76 | - .model("x1") | |
| 79 | + .model(MODEL) | |
| 77 | 80 | .build(); |
| 78 | 81 | |
| 79 | 82 | // 调用 |
| ... | ... | @@ -81,7 +84,7 @@ public class XingHuoChatModelTests { |
| 81 | 84 | // 打印结果 |
| 82 | 85 | flux.doOnNext(response -> { |
| 83 | 86 | // System.out.println(response); |
| 84 | - System.out.println(response.getResult().getOutput()); | |
| 87 | + System.out.println(Objects.requireNonNull(response.getResult()).getOutput()); | |
| 85 | 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 | 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 | 5 | import org.junit.jupiter.api.Disabled; |
| 4 | 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 | 7 | import org.springframework.ai.chat.messages.Message; |
| 9 | 8 | import org.springframework.ai.chat.messages.UserMessage; |
| 10 | 9 | import org.springframework.ai.chat.model.ChatResponse; |
| 11 | 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 | 14 | import reactor.core.publisher.Flux; |
| 13 | 15 | |
| 14 | 16 | import java.util.ArrayList; |
| 15 | 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 | 25 | * @author fansili |
| 22 | 26 | */ |
| 23 | 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 | 44 | @Test |
| 33 | 45 | @Disabled |
| 34 | 46 | public void testCall() { |
| 47 | + validateApiKey(API_KEY); | |
| 35 | 48 | // 准备参数 |
| 36 | 49 | List<Message> messages = new ArrayList<>(); |
| 37 | - // TODO @芋艿:文心一言,只要带上 system message 就报错,已经各种测试,很莫名! | |
| 38 | -// messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。")); | |
| 39 | 50 | messages.add(new UserMessage("1 + 1 = ?")); |
| 40 | 51 | |
| 41 | 52 | // 调用 |
| 42 | 53 | ChatResponse response = chatModel.call(new Prompt(messages)); |
| 43 | 54 | // 打印结果 |
| 44 | 55 | System.out.println(response); |
| 56 | + System.out.println(Objects.requireNonNull(response.getResult()).getOutput()); | |
| 45 | 57 | } |
| 46 | 58 | |
| 47 | 59 | @Test |
| 48 | 60 | @Disabled |
| 49 | 61 | public void testStream() { |
| 62 | + validateApiKey(API_KEY); | |
| 50 | 63 | // 准备参数 |
| 51 | 64 | List<Message> messages = new ArrayList<>(); |
| 52 | - // TODO @芋艿:文心一言,只要带上 system message 就报错,已经各种测试,很莫名! | |
| 53 | -// messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。")); | |
| 54 | 65 | messages.add(new UserMessage("1 + 1 = ?")); |
| 55 | 66 | |
| 56 | 67 | // 调用 |
| 57 | 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 | 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 | 5 | import org.junit.jupiter.api.Disabled; |
| 4 | 6 | import org.junit.jupiter.api.Test; |
| 5 | 7 | import org.springframework.ai.chat.messages.Message; |
| ... | ... | @@ -7,31 +9,43 @@ import org.springframework.ai.chat.messages.SystemMessage; |
| 7 | 9 | import org.springframework.ai.chat.messages.UserMessage; |
| 8 | 10 | import org.springframework.ai.chat.model.ChatResponse; |
| 9 | 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 | 15 | import reactor.core.publisher.Flux; |
| 14 | 16 | |
| 15 | 17 | import java.util.ArrayList; |
| 16 | 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 | 26 | * @author 芋道源码 |
| 22 | 27 | */ |
| 23 | 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 | 42 | .build() |
| 30 | - ); | |
| 43 | + ).build()); | |
| 31 | 44 | |
| 32 | 45 | @Test |
| 33 | 46 | @Disabled |
| 34 | 47 | public void testCall() { |
| 48 | + validateApiKey(API_KEY); | |
| 35 | 49 | // 准备参数 |
| 36 | 50 | List<Message> messages = new ArrayList<>(); |
| 37 | 51 | messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。")); |
| ... | ... | @@ -41,12 +55,13 @@ public class ZhiPuAiChatModelTests { |
| 41 | 55 | ChatResponse response = chatModel.call(new Prompt(messages)); |
| 42 | 56 | // 打印结果 |
| 43 | 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 | 61 | @Test |
| 48 | 62 | @Disabled |
| 49 | 63 | public void testStream() { |
| 64 | + validateApiKey(API_KEY); | |
| 50 | 65 | // 准备参数 |
| 51 | 66 | List<Message> messages = new ArrayList<>(); |
| 52 | 67 | messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。")); |
| ... | ... | @@ -57,19 +72,19 @@ public class ZhiPuAiChatModelTests { |
| 57 | 72 | // 打印结果 |
| 58 | 73 | flux.doOnNext(response -> { |
| 59 | 74 | // System.out.println(response); |
| 60 | - System.out.println(response.getResult().getOutput()); | |
| 75 | + System.out.println(Objects.requireNonNull(response.getResult()).getOutput()); | |
| 61 | 76 | }).then().block(); |
| 62 | 77 | } |
| 63 | 78 | |
| 64 | - // TODO @芋艿:暂时没解析 reasoning_content 结果,需要等官方修复 | |
| 65 | 79 | @Test |
| 66 | 80 | @Disabled |
| 67 | 81 | public void testStream_thinking() { |
| 82 | + validateApiKey(API_KEY); | |
| 68 | 83 | // 准备参数 |
| 69 | 84 | List<Message> messages = new ArrayList<>(); |
| 70 | 85 | messages.add(new UserMessage("详细分析下,如何设计一个电商系统?")); |
| 71 | - ZhiPuAiChatOptions options = ZhiPuAiChatOptions.builder() | |
| 72 | - .model("GLM-4.5") | |
| 86 | + DeepSeekChatOptions options = DeepSeekChatOptions.builder() | |
| 87 | + .model(MODEL) | |
| 73 | 88 | .build(); |
| 74 | 89 | |
| 75 | 90 | // 调用 |
| ... | ... | @@ -77,7 +92,7 @@ public class ZhiPuAiChatModelTests { |
| 77 | 92 | // 打印结果 |
| 78 | 93 | flux.doOnNext(response -> { |
| 79 | 94 | // System.out.println(response); |
| 80 | - System.out.println(response.getResult().getOutput()); | |
| 95 | + System.out.println(Objects.requireNonNull(response.getResult()).getOutput()); | |
| 81 | 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 | 1 | package cn.iocoder.yudao.module.ai.framework.ai.core.model.image; |
| 2 | 2 | |
| 3 | +import cn.hutool.system.SystemUtil; | |
| 3 | 4 | import org.junit.jupiter.api.Disabled; |
| 4 | 5 | import org.junit.jupiter.api.Test; |
| 5 | 6 | import org.springframework.ai.image.ImageOptions; |
| ... | ... | @@ -9,6 +10,8 @@ import org.springframework.ai.openai.OpenAiImageModel; |
| 9 | 10 | import org.springframework.ai.openai.OpenAiImageOptions; |
| 10 | 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 | 16 | * {@link OpenAiImageModel} 集成测试类 |
| 14 | 17 | * |
| ... | ... | @@ -16,17 +19,20 @@ import org.springframework.ai.openai.api.OpenAiImageApi; |
| 16 | 19 | */ |
| 17 | 20 | public class OpenAiImageModelTests { |
| 18 | 21 | |
| 22 | + private static final String API_KEY = SystemUtil.get("OPENAI_API_KEY", "sk-xxxx"); | |
| 23 | + | |
| 19 | 24 | private final OpenAiImageModel imageModel = new OpenAiImageModel(OpenAiImageApi.builder() |
| 20 | 25 | .baseUrl("https://api.holdai.top") // apiKey |
| 21 | - .apiKey("sk-PytRecQlmjEteoa2RRN6cGnwslo72UUPLQVNEMS6K9yjbmpD") | |
| 26 | + .apiKey(API_KEY) | |
| 22 | 27 | .build()); |
| 23 | 28 | |
| 24 | 29 | @Test |
| 25 | 30 | @Disabled |
| 26 | 31 | public void testCall() { |
| 32 | + validateApiKey(API_KEY); | |
| 27 | 33 | // 准备参数 |
| 28 | 34 | ImageOptions options = OpenAiImageOptions.builder() |
| 29 | - .model(OpenAiImageApi.ImageModel.DALL_E_2.getValue()) // 这个模型比较便宜 | |
| 35 | + .model("gpt-image-1") | |
| 30 | 36 | .height(256).width(256) |
| 31 | 37 | .build(); |
| 32 | 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 | 1 | package cn.iocoder.yudao.module.bpm.controller.admin.definition; |
| 2 | 2 | |
| 3 | 3 | import cn.hutool.core.collection.CollUtil; |
| 4 | +import cn.hutool.core.util.StrUtil; | |
| 4 | 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 | 8 | import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.*; |
| 6 | 9 | import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO; |
| 7 | 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 | 21 | import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; |
| 19 | 22 | import io.swagger.v3.oas.annotations.Operation; |
| 20 | 23 | import io.swagger.v3.oas.annotations.Parameter; |
| 24 | +import io.swagger.v3.oas.annotations.Parameters; | |
| 21 | 25 | import io.swagger.v3.oas.annotations.tags.Tag; |
| 22 | 26 | import jakarta.annotation.Resource; |
| 23 | 27 | import jakarta.validation.Valid; |
| ... | ... | @@ -27,16 +31,20 @@ import org.flowable.engine.repository.ProcessDefinition; |
| 27 | 31 | import org.springframework.security.access.prepost.PreAuthorize; |
| 28 | 32 | import org.springframework.validation.annotation.Validated; |
| 29 | 33 | import org.springframework.web.bind.annotation.*; |
| 34 | +import org.springframework.web.multipart.MultipartFile; | |
| 30 | 35 | |
| 36 | +import java.io.IOException; | |
| 31 | 37 | import java.util.Collections; |
| 32 | 38 | import java.util.List; |
| 33 | 39 | import java.util.Map; |
| 34 | 40 | import java.util.Set; |
| 35 | 41 | import java.util.stream.Stream; |
| 36 | 42 | |
| 43 | +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; | |
| 37 | 44 | import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; |
| 38 | 45 | import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; |
| 39 | 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 | 49 | @Tag(name = "管理后台 - 流程模型") |
| 42 | 50 | @RestController |
| ... | ... | @@ -112,6 +120,14 @@ public class BpmModelController { |
| 112 | 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 | 131 | @PostMapping("/create") |
| 116 | 132 | @Operation(summary = "新建模型") |
| 117 | 133 | @PreAuthorize("@ss.hasPermission('bpm:model:create')") |
| ... | ... | @@ -119,6 +135,36 @@ public class BpmModelController { |
| 119 | 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 | 168 | @PutMapping("/update") |
| 123 | 169 | @Operation(summary = "修改模型") |
| 124 | 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 | 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 | 126 | default BpmModelMetaInfoVO parseMetaInfo(Model model) { |
| 115 | 127 | BpmModelMetaInfoVO vo = JsonUtils.parseObject(model.getMetaInfo(), BpmModelMetaInfoVO.class); |
| 116 | 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 | 25 | ErrorCode MODEL_DEPLOY_FAIL_BPMN_USER_TASK_NAME_NOT_EXISTS = new ErrorCode(1_009_002_006, "部署流程失败,原因:BPMN 流程图中,用户任务({})的名字不存在"); |
| 26 | 26 | ErrorCode MODEL_UPDATE_FAIL_NOT_MANAGER = new ErrorCode(1_009_002_007, "操作流程失败,原因:你不是该流程({})的管理员"); |
| 27 | 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 | 30 | // ========== 流程定义 1-009-003-000 ========== |
| 30 | 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 | 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 | 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 | 120 | } |
| 121 | 121 | |
| 122 | 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 | 159 | @Transactional(rollbackFor = Exception.class) // 因为进行多个操作,所以开启事务 |
| 124 | 160 | public void updateModel(Long userId, BpmModelSaveReqVO updateReqVO) { |
| 125 | 161 | // 1. 校验流程模型存在 |
| ... | ... | @@ -187,7 +223,8 @@ public class BpmModelServiceImpl implements BpmModelService { |
| 187 | 223 | } |
| 188 | 224 | |
| 189 | 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 | 228 | if (model == null) { |
| 192 | 229 | throw exception(MODEL_NOT_EXISTS); |
| 193 | 230 | } |
| ... | ... | @@ -429,7 +466,8 @@ public class BpmModelServiceImpl implements BpmModelService { |
| 429 | 466 | |
| 430 | 467 | @Override |
| 431 | 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 | 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 | 23 | import org.springframework.validation.annotation.Validated; |
| 24 | 24 | import org.springframework.web.bind.annotation.*; |
| 25 | 25 | |
| 26 | +import java.util.Collections; | |
| 27 | +import java.util.List; | |
| 26 | 28 | import java.util.Map; |
| 27 | 29 | |
| 28 | 30 | import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; |
| ... | ... | @@ -62,19 +64,42 @@ public class ImChannelMessageManagerController { |
| 62 | 64 | @PreAuthorize("@ss.hasPermission('im:manager:channel-message:query')") |
| 63 | 65 | public CommonResult<PageResult<ImChannelMessageRespVO>> getMessagePage(@Valid ImChannelMessagePageReqVO pageReqVO) { |
| 64 | 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 | 93 | Map<Long, ImChannelDO> channelMap = channelService.getChannelMap( |
| 70 | - convertSet(pageResult.getList(), ImChannelMessageDO::getChannelId)); | |
| 94 | + convertSet(messages, ImChannelMessageDO::getChannelId)); | |
| 71 | 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 | 100 | MapUtils.findAndThen(materialMap, vo.getMaterialId(), |
| 76 | 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 | 208 | /** |
| 209 | 209 | * 签发管理 Token;用于调 Server API(DeleteRoom / ListParticipants / RemoveParticipant 等) |
| 210 | 210 | * |
| 211 | + * @param room 房间名;LiveKit 的 roomAdmin 权限必须限定到具体房间 | |
| 211 | 212 | * @return JWT 字符串 |
| 212 | 213 | */ |
| 213 | - private String signAdminToken() { | |
| 214 | + private String signAdminToken(String room) { | |
| 214 | 215 | ImProperties.Rtc cfg = imProperties.getRtc(); |
| 215 | 216 | // roomAdmin claim 给管理类 API 必备 |
| 217 | + Map<String, Object> video = new HashMap<>(); | |
| 218 | + video.put("roomAdmin", true); | |
| 219 | + video.put("room", room); | |
| 216 | 220 | long nowSec = Instant.now().getEpochSecond(); |
| 217 | 221 | return JWT.create() |
| 218 | 222 | .setIssuer(cfg.getApiKey()) |
| 219 | 223 | .setNotBefore(new Date(nowSec * 1000)) |
| 220 | 224 | .setExpiresAt(new Date((nowSec + ADMIN_TOKEN_TTL.getSeconds()) * 1000)) |
| 221 | - .setPayload("video", MapUtil.of("roomAdmin", true)) | |
| 225 | + .setPayload("video", video) | |
| 222 | 226 | .setSigner(JWTSignerUtil.hs256(cfg.getApiSecret().getBytes(StandardCharsets.UTF_8))) |
| 223 | 227 | .sign(); |
| 224 | 228 | } |
| ... | ... | @@ -232,7 +236,7 @@ public class LiveKitClient { |
| 232 | 236 | */ |
| 233 | 237 | private HttpResponse postTwirp(String path, String room) { |
| 234 | 238 | Assert.notBlank(room, "room 不可为空"); |
| 235 | - String token = signAdminToken(); | |
| 239 | + String token = signAdminToken(room); | |
| 236 | 240 | return HttpRequest.post(HttpUtils.wsUrlToHttp(imProperties.getRtc().getLivekitUrl()) + path) |
| 237 | 241 | .header("Authorization", "Bearer " + token) |
| 238 | 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 | 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 | 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 | 138 | } |
| 139 | 139 | |
| 140 | 140 | @Override |
| 141 | + public ImChannelMessageDO getMessage(Long id) { | |
| 142 | + return channelMessageMapper.selectById(id); | |
| 143 | + } | |
| 144 | + | |
| 145 | + @Override | |
| 141 | 146 | public void deleteMessage(Long id) { |
| 142 | 147 | if (channelMessageMapper.selectById(id) == null) { |
| 143 | 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 | 48 | ErrorCode CODEGEN_MASTER_TABLE_NOT_EXISTS = new ErrorCode(1_001_004_010, "主表(id={})定义不存在,请检查"); |
| 49 | 49 | ErrorCode CODEGEN_SUB_COLUMN_NOT_EXISTS = new ErrorCode(1_001_004_011, "子表的字段(id={})不存在,请检查"); |
| 50 | 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 | 56 | // ========== 文件配置 1-001-006-000 ========== |
| 53 | 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 | 48 | |
| 49 | 49 | import static cn.hutool.core.map.MapUtil.getStr; |
| 50 | 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 | 155 | vue3UniappFilePath("pages-${table.moduleName}/${table.businessName}/form/index.vue")) |
| 153 | 156 | .put(CodegenFrontTypeEnum.VUE3_ADMIN_UNIAPP_WOT.getType(), vue3AdminUniappTemplatePath("views/detail/index.vue"), |
| 154 | 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 | 168 | // VUE3_VBEN2_ANTD_SCHEMA |
| 156 | 169 | .put(CodegenFrontTypeEnum.VUE3_VBEN2_ANTD_SCHEMA.getType(), vue3VbenTemplatePath("views/data.ts"), |
| 157 | 170 | vue3VbenFilePath("views/${table.moduleName}/${table.businessName}/${classNameVar}.data.ts")) |
| ... | ... | @@ -380,12 +393,17 @@ public class CodegenEngine { |
| 380 | 393 | |
| 381 | 394 | // 2. 执行生成 |
| 382 | 395 | Map<String, String> result = Maps.newLinkedHashMapWithExpectedSize(templates.size()); // 有序 |
| 396 | + Map<String, String> generatedSources = Maps.newHashMapWithExpectedSize(templates.size()); | |
| 383 | 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 | 403 | if (isSubTemplate(vmPath)) { |
| 386 | - generateSubCode(table, subTables, result, vmPath, filePath, bindingMap); | |
| 404 | + generateSubCode(table, subTables, result, generatedSources, vmPath, filePath, bindingMap); | |
| 387 | 405 | return; |
| 388 | - // 2.2 特殊:树表专属逻辑 | |
| 406 | + // 2.3 特殊:树表专属逻辑 | |
| 389 | 407 | } else if (isPageReqVOTemplate(vmPath)) { |
| 390 | 408 | // 减少多余的类生成,例如说 PageVO.java 类 |
| 391 | 409 | if (CodegenTemplateTypeEnum.isTree(table.getTemplateType())) { |
| ... | ... | @@ -402,23 +420,36 @@ public class CodegenEngine { |
| 402 | 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 | 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 | 430 | String filePath, Map<String, Object> bindingMap) { |
| 413 | 431 | filePath = formatFilePath(filePath, bindingMap); |
| 432 | + registerGeneratedSource(generatedSources, filePath, vmPath, bindingMap); | |
| 414 | 433 | String content = templateEngine.getTemplate(vmPath).render(bindingMap); |
| 415 | 434 | // 格式化代码 |
| 416 | 435 | content = prettyCode(content, vmPath); |
| 417 | 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 | 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 | 453 | String filePath, Map<String, Object> bindingMap) { |
| 423 | 454 | // 没有子表,所以不生成 |
| 424 | 455 | if (CollUtil.isEmpty(subTables)) { |
| ... | ... | @@ -441,7 +472,7 @@ public class CodegenEngine { |
| 441 | 472 | // 逐个生成 |
| 442 | 473 | for (int i = 0; i < subTables.size(); i++) { |
| 443 | 474 | bindingMap.put("subIndex", i); |
| 444 | - generateCode(result, vmPath, filePath, bindingMap); | |
| 475 | + generateCode(result, generatedSources, vmPath, filePath, bindingMap); | |
| 445 | 476 | } |
| 446 | 477 | bindingMap.remove("subIndex"); |
| 447 | 478 | } |
| ... | ... | @@ -482,6 +513,13 @@ public class CodegenEngine { |
| 482 | 513 | if (StrUtil.count(content, "DICT_TYPE.") == 0) { |
| 483 | 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 | 523 | return content; |
| 486 | 524 | } |
| 487 | 525 | |
| ... | ... | @@ -533,6 +571,14 @@ public class CodegenEngine { |
| 533 | 571 | List<String> subClassNameVars = new ArrayList<>(); |
| 534 | 572 | List<String> simpleClassNameUnderlineCases = new ArrayList<>(); |
| 535 | 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 | 582 | for (int i = 0; i < subTables.size(); i++) { |
| 537 | 583 | CodegenTableDO subTable = subTables.get(i); |
| 538 | 584 | List<CodegenColumnDO> subColumns = subColumnsList.get(i); |
| ... | ... | @@ -543,9 +589,18 @@ public class CodegenEngine { |
| 543 | 589 | subJoinColumnStrikeCases.add(toSymbolCase(subColumn.getJavaField(), '-')); // 将 DictType 转换成 dict-type |
| 544 | 590 | // className 相关 |
| 545 | 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 | 601 | subSimpleClassNames.add(subSimpleClassName); |
| 547 | 602 | simpleClassNameUnderlineCases.add(toUnderlineCase(subSimpleClassName)); // 将 DictType 转换成 dict_type |
| 548 | - subClassNameVars.add(lowerFirst(subSimpleClassName)); // 将 DictType 转换成 dictType,用于变量 | |
| 603 | + subClassNameVars.add(subClassNameVar); // 将 DictType 转换成 dictType,用于变量 | |
| 549 | 604 | subSimpleClassNameStrikeCases.add(toSymbolCase(subSimpleClassName, '-')); // 将 DictType 转换成 dict-type |
| 550 | 605 | } |
| 551 | 606 | bindingMap.put("subPrimaryColumns", subPrimaryColumns); |
| ... | ... | @@ -734,6 +789,33 @@ public class CodegenEngine { |
| 734 | 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 | 819 | private static boolean isPageReqVOTemplate(String path) { |
| 738 | 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 | 2 | import type { PageParam, PageResult } from '@/http/types' |
| 3 | +#end | |
| 2 | 4 | import { http } from '@/http/http' |
| 3 | 5 | |
| 4 | 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 | 8 | #set ($primaryTsType = "number") |
| 7 | 9 | #else |
| 8 | 10 | #set ($primaryTsType = "string") |
| 9 | 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 | 37 | /** ${table.classComment}信息 */ |
| 11 | 38 | export interface ${simpleClassName} { |
| 12 | 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 | 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 | 48 | ${column.javaField}#if($optional)?#end: boolean |
| 23 | - #else | |
| 49 | +#else | |
| 24 | 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 | 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 | 73 | /** 获取${table.classComment}分页列表 */ |
| 31 | 74 | export function get${simpleClassName}Page(params: PageParam) { |
| 32 | 75 | return http.get<PageResult<${simpleClassName}>>('/${table.moduleName}/${simpleClassName_strikeCase}/page', params) |
| 33 | 76 | } |
| 77 | +#end | |
| 34 | 78 | |
| 35 | 79 | /** 获取${table.classComment}详情 */ |
| 36 | 80 | export function get${simpleClassName}(id: ${primaryTsType}) { |
| ... | ... | @@ -39,7 +83,7 @@ export function get${simpleClassName}(id: ${primaryTsType}) { |
| 39 | 83 | |
| 40 | 84 | /** 创建${table.classComment} */ |
| 41 | 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 | 89 | /** 更新${table.classComment} */ |
| ... | ... | @@ -51,3 +95,65 @@ export function update${simpleClassName}(data: ${simpleClassName}) { |
| 51 | 95 | export function delete${simpleClassName}(id: ${primaryTsType}) { |
| 52 | 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 | 42 | #elseif ($column.htmlType == "datetime" && $listOperationCondition == "BETWEEN") |
| 43 | 43 | <yd-search-date-range v-model="formData.${javaField}" label="${comment}" /> |
| 44 | 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 | 51 | #else |
| 63 | 52 | <view class="yd-search-form-item"> |
| 64 | 53 | <view class="yd-search-form-label"> |
| ... | ... | @@ -150,13 +139,13 @@ const formData = reactive({ |
| 150 | 139 | ${column.javaField}: [undefined, undefined] as [number | undefined, number | undefined], |
| 151 | 140 | #elseif ($column.dictType && "" != $column.dictType) |
| 152 | 141 | #if(${javaType} == "string") |
| 153 | - ${column.javaField}: -1 as -1 | string, // -1 表示全部 | |
| 142 | + ${column.javaField}: undefined as string | undefined, | |
| 154 | 143 | #elseif(${javaType} == "boolean") |
| 155 | - ${column.javaField}: -1 as -1 | boolean, // -1 表示全部 | |
| 144 | + ${column.javaField}: undefined as boolean | undefined, | |
| 156 | 145 | #else |
| 157 | - ${column.javaField}: -1, // -1 表示全部 | |
| 146 | + ${column.javaField}: undefined as number | undefined, | |
| 158 | 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 | 149 | ${column.javaField}: undefined as number | undefined, |
| 161 | 150 | #elseif(${javaType} == "boolean") |
| 162 | 151 | ${column.javaField}: undefined as boolean | undefined, |
| ... | ... | @@ -181,11 +170,11 @@ const placeholder = computed(() => { |
| 181 | 170 | conditions.push(`${comment}:#[[${]]#formatDate(formData.${javaField}[0])#[[}]]#~#[[${]]#formatDate(formData.${javaField}[1])#[[}]]#`) |
| 182 | 171 | } |
| 183 | 172 | #elseif ($dictType && "" != $dictType) |
| 184 | - if (formData.${javaField} !== -1) { | |
| 173 | + if (formData.${javaField} !== undefined) { | |
| 185 | 174 | conditions.push(`${comment}:#[[${]]#getDictLabel(DICT_TYPE.${dictType.toUpperCase()}, formData.${javaField})#[[}]]#`) |
| 186 | 175 | } |
| 187 | 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 | 178 | if (formData.${javaField} !== undefined) { |
| 190 | 179 | conditions.push(`${comment}:#[[${]]#formData.${javaField}#[[}]]#`) |
| 191 | 180 | } |
| ... | ... | @@ -207,9 +196,7 @@ function handleSearch() { |
| 207 | 196 | ...formData, |
| 208 | 197 | #foreach($column in $columns) |
| 209 | 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 | 200 | ${column.javaField}: formatDateRange(formData.${column.javaField}), |
| 214 | 201 | #end |
| 215 | 202 | #end |
| ... | ... | @@ -224,7 +211,7 @@ function handleReset() { |
| 224 | 211 | #if ($column.htmlType == "datetime" && $column.listOperationCondition == "BETWEEN") |
| 225 | 212 | formData.${column.javaField} = [undefined, undefined] |
| 226 | 213 | #elseif ($column.dictType && "" != $column.dictType) |
| 227 | - formData.${column.javaField} = -1 | |
| 214 | + formData.${column.javaField} = undefined | |
| 228 | 215 | #else |
| 229 | 216 | formData.${column.javaField} = undefined |
| 230 | 217 | #end | ... | ... |
yudao-module-infra/src/main/resources/codegen/vue3_admin_uniapp/views/detail/index.vue.vm
| ... | ... | @@ -39,7 +39,7 @@ |
| 39 | 39 | </wd-button> |
| 40 | 40 | <wd-button |
| 41 | 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 | 45 | </wd-button> |
| ... | ... | @@ -49,6 +49,12 @@ |
| 49 | 49 | </template> |
| 50 | 50 | |
| 51 | 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 | 58 | #set ($hasDict = 0) |
| 53 | 59 | #foreach($column in $columns) |
| 54 | 60 | #if ($hasDict == 0 && $column.dictType && "" != $column.dictType) |
| ... | ... | @@ -64,6 +70,7 @@ |
| 64 | 70 | import type { ${simpleClassName} } from '@/api/${table.moduleName}/${table.businessName}' |
| 65 | 71 | import { onUnload } from '@dcloudio/uni-app' |
| 66 | 72 | import { onMounted, ref } from 'vue' |
| 73 | +import { useDialog } from '@wot-ui/ui/components/wd-dialog' | |
| 67 | 74 | import { useToast } from '@wot-ui/ui/components/wd-toast' |
| 68 | 75 | import { delete${simpleClassName}, get${simpleClassName} } from '@/api/${table.moduleName}/${table.businessName}' |
| 69 | 76 | import { useAccess } from '@/hooks/useAccess' |
| ... | ... | @@ -76,7 +83,7 @@ import { formatDateTime } from '@/utils/date' |
| 76 | 83 | #end |
| 77 | 84 | |
| 78 | 85 | const props = defineProps<{ |
| 79 | - id?: number | any | |
| 86 | + id?: ${primaryTsType} | |
| 80 | 87 | }>() |
| 81 | 88 | |
| 82 | 89 | definePage({ |
| ... | ... | @@ -87,6 +94,7 @@ definePage({ |
| 87 | 94 | }) |
| 88 | 95 | |
| 89 | 96 | const { hasAccessByCodes } = useAccess() |
| 97 | +const dialog = useDialog() | |
| 90 | 98 | const toast = useToast() |
| 91 | 99 | const formData = ref<${simpleClassName}>() // 详情数据 |
| 92 | 100 | const deleting = ref(false) // 删除状态 |
| ... | ... | @@ -117,28 +125,27 @@ function handleEdit() { |
| 117 | 125 | } |
| 118 | 126 | |
| 119 | 127 | /** 删除${table.classComment} */ |
| 120 | -function handleDelete() { | |
| 128 | +async function handleDelete() { | |
| 121 | 129 | if (!props.id) { |
| 122 | 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 | 12 | <wd-form ref="formRef" :model="formData" :schema="formSchema"> |
| 13 | 13 | <wd-cell-group border> |
| 14 | 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 | 40 | <yd-form-picker |
| 31 | 41 | v-model="formData.${javaField}" |
| 32 | 42 | label="${comment}" |
| 33 | 43 | label-width="180rpx" |
| 34 | 44 | prop="${javaField}" |
| 45 | +#if ($javaType == "Boolean") | |
| 46 | + :columns="getBoolDictOptions(DICT_TYPE.${dictType.toUpperCase()})" | |
| 47 | +#else | |
| 35 | 48 | :dict-type="DICT_TYPE.${dictType.toUpperCase()}" |
| 36 | 49 | #if ($javaType == "String") |
| 37 | 50 | dict-kind="str" |
| 38 | 51 | #end |
| 52 | +#end | |
| 39 | 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 | 57 | <wd-form-item title="${comment}" title-width="180rpx" prop="${javaField}" center> |
| 44 | 58 | <wd-radio-group v-model="formData.${javaField}" type="button"> |
| 45 | 59 | <wd-radio |
| ... | ... | @@ -51,70 +65,78 @@ |
| 51 | 65 | </wd-radio> |
| 52 | 66 | </wd-radio-group> |
| 53 | 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 | 70 | <wd-form-item title="${comment}" title-width="180rpx" prop="${javaField}"> |
| 57 | 71 | <wd-input-number |
| 58 | 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 | 77 | :precision="2" |
| 61 | 78 | input-type="number" |
| 62 | 79 | #end |
| 63 | 80 | :min="0" |
| 64 | 81 | /> |
| 65 | 82 | </wd-form-item> |
| 66 | - ## 布尔类型 | |
| 67 | - #elseif (${javaType.toLowerCase()} == "boolean") | |
| 83 | +## 布尔类型 | |
| 84 | +#elseif (${javaType.toLowerCase()} == "boolean") | |
| 68 | 85 | <wd-form-item title="${comment}" title-width="180rpx" prop="${javaField}" center> |
| 69 | 86 | <wd-switch v-model="formData.${javaField}" /> |
| 70 | 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 | 94 | <wd-form-item |
| 78 | 95 | title="${comment}" |
| 79 | 96 | title-width="180rpx" |
| 80 | 97 | prop="${javaField}" |
| 81 | 98 | is-link |
| 82 | 99 | #if ($pickerType == "datetime") |
| 83 | - :value="formatDateTime(formData.${javaField})" | |
| 100 | + :value="formData.${javaField} ? formatDateTime(formData.${javaField}) : ''" | |
| 84 | 101 | #else |
| 85 | - :value="formatDate(formData.${javaField})" | |
| 102 | + :value="formData.${javaField} ? formatDate(formData.${javaField}) : ''" | |
| 86 | 103 | #end |
| 87 | 104 | placeholder="请选择${comment}" |
| 88 | 105 | @click="${javaField}Visible = true" |
| 89 | 106 | /> |
| 90 | 107 | <wd-datetime-picker |
| 91 | - v-model="formData.${javaField}" | |
| 92 | 108 | v-model:visible="${javaField}Visible" |
| 109 | + :model-value="formData.${javaField} || Date.now()" | |
| 93 | 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 | 115 | <wd-form-item title="${comment}" title-width="180rpx" prop="${javaField}"> |
| 98 | 116 | <wd-textarea |
| 99 | 117 | v-model="formData.${javaField}" |
| 100 | 118 | clearable |
| 101 | 119 | placeholder="请输入${comment}" |
| 120 | +#if ($column.htmlType == "editor") | |
| 121 | + :maxlength="2000" | |
| 122 | +#else | |
| 102 | 123 | :maxlength="200" |
| 124 | +#end | |
| 103 | 125 | show-word-limit |
| 104 | 126 | /> |
| 105 | 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 | 130 | <wd-form-item title="${comment}" title-width="180rpx" prop="${javaField}"> |
| 109 | 131 | <yd-upload-img v-model="formData.${javaField}" directory="${table.moduleName}/${table.businessName}" /> |
| 110 | 132 | </wd-form-item> |
| 111 | - ## 文件上传 → yd-upload-file | |
| 112 | - #elseif ($column.htmlType == "fileUpload") | |
| 133 | +## 文件上传 → yd-upload-file | |
| 134 | +#elseif ($column.htmlType == "fileUpload") | |
| 113 | 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 | 137 | </wd-form-item> |
| 116 | - ## 默认:文本输入 | |
| 117 | - #else | |
| 138 | +## 默认:文本输入 | |
| 139 | +#else | |
| 118 | 140 | <wd-form-item title="${comment}" title-width="180rpx" prop="${javaField}"> |
| 119 | 141 | <wd-input |
| 120 | 142 | v-model="formData.${javaField}" |
| ... | ... | @@ -122,11 +144,277 @@ |
| 122 | 144 | placeholder="请输入${comment}" |
| 123 | 145 | /> |
| 124 | 146 | </wd-form-item> |
| 125 | - #end | |
| 126 | - #end | |
| 147 | +#end | |
| 148 | +#end | |
| 127 | 149 | #end |
| 128 | 150 | </wd-cell-group> |
| 129 | 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 | 418 | </view> |
| 131 | 419 | |
| 132 | 420 | <!-- 底部保存按钮 --> |
| ... | ... | @@ -146,7 +434,7 @@ |
| 146 | 434 | <script lang="ts" setup> |
| 147 | 435 | import type { FormInstance } from '@wot-ui/ui/components/wd-form/types' |
| 148 | 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 | 438 | #set ($primaryTsType = "number") |
| 151 | 439 | #else |
| 152 | 440 | #set ($primaryTsType = "string") |
| ... | ... | @@ -154,54 +442,113 @@ import type { FormInstance } from '@wot-ui/ui/components/wd-form/types' |
| 154 | 442 | #set ($hasDict = 0) |
| 155 | 443 | #set ($hasDate = 0) |
| 156 | 444 | #set ($hasDateTime = 0) |
| 445 | +#set ($hasSubDate = 0) | |
| 157 | 446 | #set ($hasGetDictOptions = 0) |
| 158 | 447 | #set ($hasGetIntDictOptions = 0) |
| 159 | 448 | #set ($hasGetStrDictOptions = 0) |
| 160 | 449 | #set ($hasGetBoolDictOptions = 0) |
| 161 | 450 | #foreach ($column in $columns) |
| 162 | - #if (($column.createOperation || $column.updateOperation) && !$column.primaryKey | |
| 451 | +#if (($column.createOperation || $column.updateOperation) && !$column.primaryKey | |
| 163 | 452 | && ($column.htmlType == "select" || $column.htmlType == "radio") |
| 164 | 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 | 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 | 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 | 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 | 512 | import { computed, onMounted, ref } from 'vue' |
| 187 | 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 | 534 | import { create${simpleClassName}, get${simpleClassName}, update${simpleClassName} } from '@/api/${table.moduleName}/${table.businessName}' |
| 535 | +#end | |
| 189 | 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 | 552 | import { $dictImportNames } from '@/hooks/useDict' |
| 206 | 553 | #end |
| 207 | 554 | import { delay, navigateBackPlus } from '@/utils' |
| ... | ... | @@ -209,21 +556,27 @@ import { delay, navigateBackPlus } from '@/utils' |
| 209 | 556 | import { DICT_TYPE } from '@/utils/constants' |
| 210 | 557 | #end |
| 211 | 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 | 568 | import { $dateImportNames } from '@/utils/date' |
| 222 | 569 | #end |
| 223 | 570 | import { createFormSchema } from '@/utils/wot' |
| 571 | +#if ($table.templateType == 2) | |
| 572 | +import { handleTree } from '@/utils/tree' | |
| 573 | +#end | |
| 224 | 574 | |
| 225 | 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 | 582 | definePage({ |
| ... | ... | @@ -236,42 +589,165 @@ definePage({ |
| 236 | 589 | const toast = useToast() |
| 237 | 590 | const getTitle = computed(() => props.id ? '编辑${table.classComment}' : '新增${table.classComment}') |
| 238 | 591 | const formLoading = ref(false) // 表单提交状态 |
| 239 | -const formData = ref<${simpleClassName}>({ | |
| 592 | +const formData = ref<Partial<${simpleClassName}>>({ | |
| 240 | 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 | 606 | ${column.javaField}: undefined, |
| 247 | - #elseif(${javaType} == "long" || ${javaType} == "integer" || ${javaType} == "short" || ${javaType} == "double" || ${javaType} == "bigdecimal" || ${javaType} == "byte") | |
| 607 | +#else | |
| 248 | 608 | ${column.javaField}: 0, |
| 249 | - #elseif(${javaType} == "boolean") | |
| 609 | +#end | |
| 610 | +#elseif(${javaType} == "boolean") | |
| 250 | 611 | ${column.javaField}: false, |
| 251 | - #elseif(${javaType} == "date" || ${javaType} == "localdate" || ${javaType} == "localdatetime") | |
| 612 | +#elseif(${javaType} == "date" || ${javaType} == "localdate" || ${javaType} == "localdatetime") | |
| 252 | 613 | ${column.javaField}: undefined, |
| 253 | - #else | |
| 614 | +#else | |
| 254 | 615 | ${column.javaField}: '', |
| 255 | - #end | |
| 256 | - #end | |
| 616 | +#end | |
| 617 | +#end | |
| 257 | 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 | 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 | 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 | 641 | #end |
| 264 | 642 | const formSchema = createFormSchema({ |
| 265 | 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 | 646 | && $javaFieldLower != "createtime" && $javaFieldLower != "updatetime") |
| 269 | 647 | ${column.javaField}: [{ required: true, message: '${column.columnComment}不能为空' }], |
| 270 | - #end | |
| 648 | +#end | |
| 271 | 649 | #end |
| 272 | 650 | }) // 表单校验规则 |
| 273 | 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 | 752 | function handleBack() { |
| 277 | 753 | navigateBackPlus('/pages-${table.moduleName}/${table.businessName}/index') |
| ... | ... | @@ -282,7 +758,34 @@ async function getDetail() { |
| 282 | 758 | if (!props.id) { |
| 283 | 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 | 787 | formData.value = await get${simpleClassName}(props.id) |
| 788 | +#end | |
| 286 | 789 | } |
| 287 | 790 | |
| 288 | 791 | /** 提交表单 */ |
| ... | ... | @@ -291,14 +794,31 @@ async function handleSubmit() { |
| 291 | 794 | if (!valid) { |
| 292 | 795 | return |
| 293 | 796 | } |
| 797 | +#if ($table.templateType == 10 || $table.templateType == 12) | |
| 798 | + if (!validateSubTables()) { | |
| 799 | + return | |
| 800 | + } | |
| 801 | +#end | |
| 294 | 802 | |
| 295 | 803 | formLoading.value = true |
| 296 | 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 | 817 | if (props.id) { |
| 298 | - await update${simpleClassName}(formData.value) | |
| 818 | + await update${simpleClassName}(data) | |
| 299 | 819 | toast.success('修改成功') |
| 300 | 820 | } else { |
| 301 | - await create${simpleClassName}(formData.value) | |
| 821 | + await create${simpleClassName}(data) | |
| 302 | 822 | toast.success('新增成功') |
| 303 | 823 | } |
| 304 | 824 | uni.$emit('${table.moduleName}:${table.businessName}:reload') |
| ... | ... | @@ -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 | 842 | getDetail() |
| 314 | 843 | }) |
| 315 | 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 | 7 | <script lang="ts" setup> |
| 2 | -import type { Rule } from 'antdv-next/es/form'; | |
| 8 | +import type { Rule } from 'antdv-next'; | |
| 3 | 9 | import type { ${simpleClassName}Api } from '#/api/${table.moduleName}/${table.businessName}'; |
| 4 | 10 | |
| 5 | 11 | import { computed, ref } from 'vue'; |
| ... | ... | @@ -9,7 +15,7 @@ import { DICT_TYPE } from '@vben/constants'; |
| 9 | 15 | import { getDictOptions } from '@vben/hooks'; |
| 10 | 16 | import { Tinymce as RichTextarea } from '#/components/tinymce'; |
| 11 | 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 | 19 | #if($table.templateType == 2)## 树表需要导入这些 |
| 14 | 20 | import { get${simpleClassName}List } from '#/api/${table.moduleName}/${table.businessName}'; |
| 15 | 21 | import { handleTree } from '@vben/utils' |
| ... | ... | @@ -204,7 +210,7 @@ const [Modal, modalApi] = useVbenModal({ |
| 204 | 210 | #set ($dictMethod = "boolean") |
| 205 | 211 | #end |
| 206 | 212 | #if ( $table.templateType == 2 && $column.id == $treeParentColumn.id ) |
| 207 | - <Form.Item label="${comment}" name="${javaField}"> | |
| 213 | + <FormItem label="${comment}" name="${javaField}"> | |
| 208 | 214 | <TreeSelect |
| 209 | 215 | v-model:value="formData.${javaField}" |
| 210 | 216 | :treeData="${classNameVar}Tree" |
| ... | ... | @@ -225,25 +231,25 @@ const [Modal, modalApi] = useVbenModal({ |
| 225 | 231 | treeDefaultExpandAll |
| 226 | 232 | placeholder="请选择${comment}" |
| 227 | 233 | /> |
| 228 | - </Form.Item> | |
| 234 | + </FormItem> | |
| 229 | 235 | #elseif ($column.htmlType == "input" && !$column.primaryKey)## 忽略主键,不用在表单里 |
| 230 | - <Form.Item label="${comment}" name="${javaField}"> | |
| 236 | + <FormItem label="${comment}" name="${javaField}"> | |
| 231 | 237 | <Input v-model:value="formData.${javaField}" placeholder="请输入${comment}" /> |
| 232 | - </Form.Item> | |
| 238 | + </FormItem> | |
| 233 | 239 | #elseif($column.htmlType == "imageUpload")## 图片上传 |
| 234 | - <Form.Item label="${comment}" name="${javaField}"> | |
| 240 | + <FormItem label="${comment}" name="${javaField}"> | |
| 235 | 241 | <ImageUpload v-model:value="formData.${javaField}" /> |
| 236 | - </Form.Item> | |
| 242 | + </FormItem> | |
| 237 | 243 | #elseif($column.htmlType == "fileUpload")## 文件上传 |
| 238 | - <Form.Item label="${comment}" name="${javaField}"> | |
| 244 | + <FormItem label="${comment}" name="${javaField}"> | |
| 239 | 245 | <FileUpload v-model:value="formData.${javaField}" /> |
| 240 | - </Form.Item> | |
| 246 | + </FormItem> | |
| 241 | 247 | #elseif($column.htmlType == "editor")## 文本编辑器 |
| 242 | - <Form.Item label="${comment}" name="${javaField}"> | |
| 248 | + <FormItem label="${comment}" name="${javaField}"> | |
| 243 | 249 | <RichTextarea v-model="formData.${javaField}" height="500px" /> |
| 244 | - </Form.Item> | |
| 250 | + </FormItem> | |
| 245 | 251 | #elseif($column.htmlType == "select")## 下拉框 |
| 246 | - <Form.Item label="${comment}" name="${javaField}"> | |
| 252 | + <FormItem label="${comment}" name="${javaField}"> | |
| 247 | 253 | <Select v-model:value="formData.${javaField}" placeholder="请选择${comment}"> |
| 248 | 254 | #if ("" != $dictType)## 有数据字典 |
| 249 | 255 | <SelectOption |
| ... | ... | @@ -257,9 +263,9 @@ const [Modal, modalApi] = useVbenModal({ |
| 257 | 263 | <SelectOption label="请选择字典生成" value="" /> |
| 258 | 264 | #end |
| 259 | 265 | </Select> |
| 260 | - </Form.Item> | |
| 266 | + </FormItem> | |
| 261 | 267 | #elseif($column.htmlType == "checkbox")## 多选框 |
| 262 | - <Form.Item label="${comment}" name="${javaField}"> | |
| 268 | + <FormItem label="${comment}" name="${javaField}"> | |
| 263 | 269 | <CheckboxGroup v-model:value="formData.${javaField}"> |
| 264 | 270 | #if ("" != $dictType)## 有数据字典 |
| 265 | 271 | <Checkbox |
| ... | ... | @@ -273,9 +279,9 @@ const [Modal, modalApi] = useVbenModal({ |
| 273 | 279 | <Checkbox label="请选择字典生成" /> |
| 274 | 280 | #end |
| 275 | 281 | </CheckboxGroup> |
| 276 | - </Form.Item> | |
| 282 | + </FormItem> | |
| 277 | 283 | #elseif($column.htmlType == "radio")## 单选框 |
| 278 | - <Form.Item label="${comment}" name="${javaField}"> | |
| 284 | + <FormItem label="${comment}" name="${javaField}"> | |
| 279 | 285 | <RadioGroup v-model:value="formData.${javaField}"> |
| 280 | 286 | #if ("" != $dictType)## 有数据字典 |
| 281 | 287 | <Radio |
| ... | ... | @@ -289,19 +295,19 @@ const [Modal, modalApi] = useVbenModal({ |
| 289 | 295 | <Radio value="1">请选择字典生成</Radio> |
| 290 | 296 | #end |
| 291 | 297 | </RadioGroup> |
| 292 | - </Form.Item> | |
| 298 | + </FormItem> | |
| 293 | 299 | #elseif($column.htmlType == "datetime")## 时间框 |
| 294 | - <Form.Item label="${comment}" name="${javaField}"> | |
| 300 | + <FormItem label="${comment}" name="${javaField}"> | |
| 295 | 301 | <DatePicker |
| 296 | 302 | v-model:value="formData.${javaField}" |
| 297 | 303 | valueFormat="x" |
| 298 | 304 | placeholder="选择${comment}" |
| 299 | 305 | /> |
| 300 | - </Form.Item> | |
| 306 | + </FormItem> | |
| 301 | 307 | #elseif($column.htmlType == "textarea")## 文本框 |
| 302 | - <Form.Item label="${comment}" name="${javaField}"> | |
| 308 | + <FormItem label="${comment}" name="${javaField}"> | |
| 303 | 309 | <TextArea v-model:value="formData.${javaField}" placeholder="请输入${comment}" /> |
| 304 | - </Form.Item> | |
| 310 | + </FormItem> | |
| 305 | 311 | #end |
| 306 | 312 | #end |
| 307 | 313 | #end | ... | ... |
yudao-module-infra/src/main/resources/codegen/vue3_vben5_antdv_next/general/views/import.vue.vm
| 1 | 1 | <script lang="ts" setup> |
| 2 | -import type { FileType } from 'antdv-next/es/upload/interface'; | |
| 3 | - | |
| 4 | 2 | import { ref } from 'vue'; |
| 5 | 3 | |
| 6 | 4 | import { useVbenModal } from '@vben/common-ui'; |
| ... | ... | @@ -43,8 +41,8 @@ const [Modal, modalApi] = useVbenModal({ |
| 43 | 41 | }); |
| 44 | 42 | |
| 45 | 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 | 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 | 11 | <script lang="ts" setup> |
| 2 | 12 | import type { ${simpleClassName}Api } from '#/api/${table.moduleName}/${table.businessName}'; |
| 3 | 13 | |
| ... | ... | @@ -8,12 +18,14 @@ import { DICT_TYPE } from '@vben/constants'; |
| 8 | 18 | import { getDictOptions } from '@vben/hooks'; |
| 9 | 19 | import { useTableToolbar, VbenVxeTableToolbar } from '@vben/plugins/vxe-table'; |
| 10 | 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 | 22 | import ${simpleClassName}Form from './modules/form.vue'; |
| 13 | 23 | import { Download, Plus, RefreshCw, Search, Trash2 } from '@vben/icons'; |
| 14 | 24 | import { DictTag } from '#/components/dict-tag'; |
| 15 | 25 | import { VxeColumn, VxeTable } from '#/adapter/vxe-table'; |
| 26 | +#if ($rangePickerEnable) | |
| 16 | 27 | import { getRangePickerDefaultProps } from '#/utils/rangePickerProps'; |
| 28 | +#end | |
| 17 | 29 | |
| 18 | 30 | ## 特殊:主子表专属逻辑 |
| 19 | 31 | #if ( $table.templateType == 11 || $table.templateType == 12 ) |
| ... | ... | @@ -249,7 +261,7 @@ onMounted(() => { |
| 249 | 261 | #set ($dictMethod = "boolean") |
| 250 | 262 | #end |
| 251 | 263 | #if ($column.htmlType == "input") |
| 252 | - <Form.Item label="${comment}" name="${javaField}"> | |
| 264 | + <FormItem label="${comment}" name="${javaField}"> | |
| 253 | 265 | <Input |
| 254 | 266 | v-model:value="queryParams.${javaField}" |
| 255 | 267 | placeholder="请输入${comment}" |
| ... | ... | @@ -257,9 +269,9 @@ onMounted(() => { |
| 257 | 269 | @pressEnter="handleQuery" |
| 258 | 270 | class="w-full" |
| 259 | 271 | /> |
| 260 | - </Form.Item> | |
| 272 | + </FormItem> | |
| 261 | 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 | 275 | <Select |
| 264 | 276 | v-model:value="queryParams.${javaField}" |
| 265 | 277 | placeholder="请选择${comment}" |
| ... | ... | @@ -278,10 +290,10 @@ onMounted(() => { |
| 278 | 290 | <SelectOption label="请选择字典生成" value="" /> |
| 279 | 291 | #end |
| 280 | 292 | </Select> |
| 281 | - </Form.Item> | |
| 293 | + </FormItem> | |
| 282 | 294 | #elseif($column.htmlType == "datetime") |
| 283 | 295 | #if ($column.listOperationCondition != "BETWEEN")## 非范围 |
| 284 | - <Form.Item label="${comment}" name="${javaField}"> | |
| 296 | + <FormItem label="${comment}" name="${javaField}"> | |
| 285 | 297 | <DatePicker |
| 286 | 298 | v-model:value="queryParams.${javaField}" |
| 287 | 299 | valueFormat="YYYY-MM-DD" |
| ... | ... | @@ -289,25 +301,25 @@ onMounted(() => { |
| 289 | 301 | allowClear |
| 290 | 302 | class="w-full" |
| 291 | 303 | /> |
| 292 | - </Form.Item> | |
| 304 | + </FormItem> | |
| 293 | 305 | #else## 范围 |
| 294 | - <Form.Item label="${comment}" name="${javaField}"> | |
| 306 | + <FormItem label="${comment}" name="${javaField}"> | |
| 295 | 307 | <RangePicker |
| 296 | 308 | v-model:value="queryParams.${javaField}" |
| 297 | 309 | v-bind="getRangePickerDefaultProps()" |
| 298 | 310 | class="w-full" |
| 299 | 311 | /> |
| 300 | - </Form.Item> | |
| 312 | + </FormItem> | |
| 301 | 313 | #end |
| 302 | 314 | #end |
| 303 | 315 | #end |
| 304 | 316 | #end |
| 305 | - <Form.Item> | |
| 317 | + <FormItem> | |
| 306 | 318 | <Button class="ml-2" @click="resetQuery"> 重置 </Button> |
| 307 | 319 | <Button class="ml-2" @click="handleQuery" type="primary"> |
| 308 | 320 | 搜索 |
| 309 | 321 | </Button> |
| 310 | - </Form.Item> | |
| 322 | + </FormItem> | |
| 311 | 323 | </Form> |
| 312 | 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 | 2 | #set ($subColumns = $subColumnsList.get($subIndex))##当前字段数组 |
| 3 | 3 | #set ($subJoinColumn = $subJoinColumns.get($subIndex))##当前 join 字段 |
| 4 | 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 | 11 | <script lang="ts" setup> |
| 6 | -import type { Rule } from 'antdv-next/es/form'; | |
| 12 | +import type { Rule } from 'antdv-next'; | |
| 7 | 13 | import type { ${simpleClassName}Api } from '#/api/${table.moduleName}/${table.businessName}'; |
| 8 | 14 | |
| 9 | 15 | import { computed, ref } from 'vue'; |
| ... | ... | @@ -13,7 +19,7 @@ import { DICT_TYPE } from '@vben/constants'; |
| 13 | 19 | import { getDictOptions } from '@vben/hooks'; |
| 14 | 20 | import { Tinymce as RichTextarea } from '#/components/tinymce'; |
| 15 | 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 | 24 | import { $t } from '#/locales'; |
| 19 | 25 | |
| ... | ... | @@ -130,23 +136,23 @@ function resetForm(){ |
| 130 | 136 | #set ($dictMethod = "boolean") |
| 131 | 137 | #end |
| 132 | 138 | #if ($column.htmlType == "input" && !$column.primaryKey)## 忽略主键,不用在表单里 |
| 133 | - <Form.Item label="${comment}" name="${javaField}"> | |
| 139 | + <FormItem label="${comment}" name="${javaField}"> | |
| 134 | 140 | <Input v-model:value="formData.${javaField}" placeholder="请输入${comment}" /> |
| 135 | - </Form.Item> | |
| 141 | + </FormItem> | |
| 136 | 142 | #elseif($column.htmlType == "imageUpload")## 图片上传 |
| 137 | - <Form.Item label="${comment}" name="${javaField}"> | |
| 143 | + <FormItem label="${comment}" name="${javaField}"> | |
| 138 | 144 | <ImageUpload v-model:value="formData.${javaField}" /> |
| 139 | - </Form.Item> | |
| 145 | + </FormItem> | |
| 140 | 146 | #elseif($column.htmlType == "fileUpload")## 文件上传 |
| 141 | - <Form.Item label="${comment}" name="${javaField}"> | |
| 147 | + <FormItem label="${comment}" name="${javaField}"> | |
| 142 | 148 | <FileUpload v-model:value="formData.${javaField}" /> |
| 143 | - </Form.Item> | |
| 149 | + </FormItem> | |
| 144 | 150 | #elseif($column.htmlType == "editor")## 文本编辑器 |
| 145 | - <Form.Item label="${comment}" name="${javaField}"> | |
| 151 | + <FormItem label="${comment}" name="${javaField}"> | |
| 146 | 152 | <RichTextarea v-model="formData.${javaField}" height="500px" /> |
| 147 | - </Form.Item> | |
| 153 | + </FormItem> | |
| 148 | 154 | #elseif($column.htmlType == "select")## 下拉框 |
| 149 | - <Form.Item label="${comment}" name="${javaField}"> | |
| 155 | + <FormItem label="${comment}" name="${javaField}"> | |
| 150 | 156 | <Select v-model:value="formData.${javaField}" placeholder="请选择${comment}"> |
| 151 | 157 | #if ("" != $dictType)## 有数据字典 |
| 152 | 158 | <SelectOption |
| ... | ... | @@ -160,9 +166,9 @@ function resetForm(){ |
| 160 | 166 | <SelectOption label="请选择字典生成" value="" /> |
| 161 | 167 | #end |
| 162 | 168 | </Select> |
| 163 | - </Form.Item> | |
| 169 | + </FormItem> | |
| 164 | 170 | #elseif($column.htmlType == "checkbox")## 多选框 |
| 165 | - <Form.Item label="${comment}" name="${javaField}"> | |
| 171 | + <FormItem label="${comment}" name="${javaField}"> | |
| 166 | 172 | <CheckboxGroup v-model:value="formData.${javaField}"> |
| 167 | 173 | #if ("" != $dictType)## 有数据字典 |
| 168 | 174 | <Checkbox |
| ... | ... | @@ -176,9 +182,9 @@ function resetForm(){ |
| 176 | 182 | <Checkbox label="请选择字典生成" /> |
| 177 | 183 | #end |
| 178 | 184 | </CheckboxGroup> |
| 179 | - </Form.Item> | |
| 185 | + </FormItem> | |
| 180 | 186 | #elseif($column.htmlType == "radio")## 单选框 |
| 181 | - <Form.Item label="${comment}" name="${javaField}"> | |
| 187 | + <FormItem label="${comment}" name="${javaField}"> | |
| 182 | 188 | <RadioGroup v-model:value="formData.${javaField}"> |
| 183 | 189 | #if ("" != $dictType)## 有数据字典 |
| 184 | 190 | <Radio |
| ... | ... | @@ -192,19 +198,19 @@ function resetForm(){ |
| 192 | 198 | <Radio value="1">请选择字典生成</Radio> |
| 193 | 199 | #end |
| 194 | 200 | </RadioGroup> |
| 195 | - </Form.Item> | |
| 201 | + </FormItem> | |
| 196 | 202 | #elseif($column.htmlType == "datetime")## 时间框 |
| 197 | - <Form.Item label="${comment}" name="${javaField}"> | |
| 203 | + <FormItem label="${comment}" name="${javaField}"> | |
| 198 | 204 | <DatePicker |
| 199 | 205 | v-model:value="formData.${javaField}" |
| 200 | 206 | valueFormat="x" |
| 201 | 207 | placeholder="选择${comment}" |
| 202 | 208 | /> |
| 203 | - </Form.Item> | |
| 209 | + </FormItem> | |
| 204 | 210 | #elseif($column.htmlType == "textarea")## 文本框 |
| 205 | - <Form.Item label="${comment}" name="${javaField}"> | |
| 211 | + <FormItem label="${comment}" name="${javaField}"> | |
| 206 | 212 | <TextArea v-model:value="formData.${javaField}" placeholder="请输入${comment}" /> |
| 207 | - </Form.Item> | |
| 213 | + </FormItem> | |
| 208 | 214 | #end |
| 209 | 215 | #end |
| 210 | 216 | #end | ... | ... |
yudao-module-infra/src/main/resources/codegen/vue3_vben5_antdv_next/general/views/modules/form_sub_inner.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 | 4 | #set ($subSimpleClassName = $subSimpleClassNames.get($subIndex)) |
| 5 | 5 | #set ($subClassNameVar = $subClassNameVars.get($subIndex)) |
| 6 | 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 | 13 | <script lang="ts" setup> |
| 8 | 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 | 18 | import { DICT_TYPE } from '@vben/constants'; |
| 13 | 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 | 22 | import { $t } from '#/locales'; |
| 17 | 23 | |
| 18 | 24 | #if ($subTable.subJoinMany) ## 一对多 |
| ... | ... | @@ -22,7 +28,7 @@ import { VxeColumn, VxeTable } from '#/adapter/vxe-table'; |
| 22 | 28 | import { ImageUpload, FileUpload } from "#/components/upload"; |
| 23 | 29 | import { get${subSimpleClassName}ListBy${SubJoinColumnName} } from '#/api/${table.moduleName}/${table.businessName}'; |
| 24 | 30 | #else |
| 25 | -import type { Rule } from 'antdv-next/es/form'; | |
| 31 | +import type { Rule } from 'antdv-next'; | |
| 26 | 32 | import { Tinymce as RichTextarea } from '#/components/tinymce'; |
| 27 | 33 | import { ImageUpload, FileUpload } from "#/components/upload"; |
| 28 | 34 | import { get${subSimpleClassName}By${SubJoinColumnName} } from '#/api/${table.moduleName}/${table.businessName}'; |
| ... | ... | @@ -264,23 +270,23 @@ watch( |
| 264 | 270 | #set ($dictMethod = "boolean") |
| 265 | 271 | #end |
| 266 | 272 | #if ($column.htmlType == "input" && !$column.primaryKey)## 忽略主键,不用在表单里 |
| 267 | - <Form.Item label="${comment}" name="${javaField}"> | |
| 273 | + <FormItem label="${comment}" name="${javaField}"> | |
| 268 | 274 | <Input v-model:value="formData.${javaField}" placeholder="请输入${comment}" /> |
| 269 | - </Form.Item> | |
| 275 | + </FormItem> | |
| 270 | 276 | #elseif($column.htmlType == "imageUpload")## 图片上传 |
| 271 | - <Form.Item label="${comment}" name="${javaField}"> | |
| 277 | + <FormItem label="${comment}" name="${javaField}"> | |
| 272 | 278 | <ImageUpload v-model:value="formData.${javaField}" /> |
| 273 | - </Form.Item> | |
| 279 | + </FormItem> | |
| 274 | 280 | #elseif($column.htmlType == "fileUpload")## 文件上传 |
| 275 | - <Form.Item label="${comment}" name="${javaField}"> | |
| 281 | + <FormItem label="${comment}" name="${javaField}"> | |
| 276 | 282 | <FileUpload v-model:value="formData.${javaField}" /> |
| 277 | - </Form.Item> | |
| 283 | + </FormItem> | |
| 278 | 284 | #elseif($column.htmlType == "editor")## 文本编辑器 |
| 279 | - <Form.Item label="${comment}" name="${javaField}"> | |
| 285 | + <FormItem label="${comment}" name="${javaField}"> | |
| 280 | 286 | <RichTextarea v-model="formData.${javaField}" height="500px" /> |
| 281 | - </Form.Item> | |
| 287 | + </FormItem> | |
| 282 | 288 | #elseif($column.htmlType == "select")## 下拉框 |
| 283 | - <Form.Item label="${comment}" name="${javaField}"> | |
| 289 | + <FormItem label="${comment}" name="${javaField}"> | |
| 284 | 290 | <Select v-model:value="formData.${javaField}" placeholder="请选择${comment}"> |
| 285 | 291 | #if ("" != $dictType)## 有数据字典 |
| 286 | 292 | <SelectOption |
| ... | ... | @@ -294,9 +300,9 @@ watch( |
| 294 | 300 | <SelectOption label="请选择字典生成" value="" /> |
| 295 | 301 | #end |
| 296 | 302 | </Select> |
| 297 | - </Form.Item> | |
| 303 | + </FormItem> | |
| 298 | 304 | #elseif($column.htmlType == "checkbox")## 多选框 |
| 299 | - <Form.Item label="${comment}" name="${javaField}"> | |
| 305 | + <FormItem label="${comment}" name="${javaField}"> | |
| 300 | 306 | <CheckboxGroup v-model:value="formData.${javaField}"> |
| 301 | 307 | #if ("" != $dictType)## 有数据字典 |
| 302 | 308 | <Checkbox |
| ... | ... | @@ -310,9 +316,9 @@ watch( |
| 310 | 316 | <Checkbox label="请选择字典生成" /> |
| 311 | 317 | #end |
| 312 | 318 | </CheckboxGroup> |
| 313 | - </Form.Item> | |
| 319 | + </FormItem> | |
| 314 | 320 | #elseif($column.htmlType == "radio")## 单选框 |
| 315 | - <Form.Item label="${comment}" name="${javaField}"> | |
| 321 | + <FormItem label="${comment}" name="${javaField}"> | |
| 316 | 322 | <RadioGroup v-model:value="formData.${javaField}"> |
| 317 | 323 | #if ("" != $dictType)## 有数据字典 |
| 318 | 324 | <Radio |
| ... | ... | @@ -326,19 +332,19 @@ watch( |
| 326 | 332 | <Radio value="1">请选择字典生成</Radio> |
| 327 | 333 | #end |
| 328 | 334 | </RadioGroup> |
| 329 | - </Form.Item> | |
| 335 | + </FormItem> | |
| 330 | 336 | #elseif($column.htmlType == "datetime")## 时间框 |
| 331 | - <Form.Item label="${comment}" name="${javaField}"> | |
| 337 | + <FormItem label="${comment}" name="${javaField}"> | |
| 332 | 338 | <DatePicker |
| 333 | 339 | v-model:value="formData.${javaField}" |
| 334 | 340 | valueFormat="x" |
| 335 | 341 | placeholder="选择${comment}" |
| 336 | 342 | /> |
| 337 | - </Form.Item> | |
| 343 | + </FormItem> | |
| 338 | 344 | #elseif($column.htmlType == "textarea")## 文本框 |
| 339 | - <Form.Item label="${comment}" name="${javaField}"> | |
| 345 | + <FormItem label="${comment}" name="${javaField}"> | |
| 340 | 346 | <TextArea v-model:value="formData.${javaField}" placeholder="请输入${comment}" /> |
| 341 | - </Form.Item> | |
| 347 | + </FormItem> | |
| 342 | 348 | #end |
| 343 | 349 | #end |
| 344 | 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 | 5 | #set ($subJoinColumn = $subJoinColumns.get($subIndex))##当前 join 字段 |
| 6 | 6 | #set ($subSimpleClassName_strikeCase = $subSimpleClassName_strikeCases.get($subIndex)) |
| 7 | 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 | 18 | <script lang="ts" setup> |
| 9 | 19 | import type { ${simpleClassName}Api } from '#/api/${table.moduleName}/${table.businessName}'; |
| 10 | 20 | import type { VxeTableInstance } from '#/adapter/vxe-table'; |
| ... | ... | @@ -15,7 +25,9 @@ import { DICT_TYPE } from '@vben/constants'; |
| 15 | 25 | import { getDictOptions } from '@vben/hooks'; |
| 16 | 26 | |
| 17 | 27 | import { DictTag } from '#/components/dict-tag'; |
| 28 | +#if ($table.templateType == 11 && $rangePickerEnable) | |
| 18 | 29 | import { getRangePickerDefaultProps } from '#/utils/rangePickerProps'; |
| 30 | +#end | |
| 19 | 31 | import { VxeColumn, VxeTable } from '#/adapter/vxe-table'; |
| 20 | 32 | import { formatDateTime } from '@vben/utils'; |
| 21 | 33 | |
| ... | ... | @@ -25,7 +37,7 @@ import { useTableToolbar, VbenVxeTableToolbar } from '@vben/plugins/vxe-table'; |
| 25 | 37 | import ${subSimpleClassName}Form from './${subSimpleClassName_strikeCase}-form.vue' |
| 26 | 38 | import { Tinymce as RichTextarea } from '#/components/tinymce'; |
| 27 | 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 | 41 | import { Plus, Trash2 } from '@vben/icons'; |
| 30 | 42 | import { $t } from '#/locales'; |
| 31 | 43 | #end |
| ... | ... | @@ -224,7 +236,7 @@ onMounted(() => { |
| 224 | 236 | #set ($dictMethod = "boolean") |
| 225 | 237 | #end |
| 226 | 238 | #if ($column.htmlType == "input") |
| 227 | - <Form.Item label="${comment}" name="${javaField}"> | |
| 239 | + <FormItem label="${comment}" name="${javaField}"> | |
| 228 | 240 | <Input |
| 229 | 241 | v-model:value="queryParams.${javaField}" |
| 230 | 242 | placeholder="请输入${comment}" |
| ... | ... | @@ -232,9 +244,9 @@ onMounted(() => { |
| 232 | 244 | @pressEnter="handleQuery" |
| 233 | 245 | class="w-full" |
| 234 | 246 | /> |
| 235 | - </Form.Item> | |
| 247 | + </FormItem> | |
| 236 | 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 | 250 | <Select |
| 239 | 251 | v-model:value="queryParams.${javaField}" |
| 240 | 252 | placeholder="请选择${comment}" |
| ... | ... | @@ -253,10 +265,10 @@ onMounted(() => { |
| 253 | 265 | <SelectOption label="请选择字典生成" value="" /> |
| 254 | 266 | #end |
| 255 | 267 | </Select> |
| 256 | - </Form.Item> | |
| 268 | + </FormItem> | |
| 257 | 269 | #elseif($column.htmlType == "datetime") |
| 258 | 270 | #if ($column.listOperationCondition != "BETWEEN")## 非范围 |
| 259 | - <Form.Item label="${comment}" name="${javaField}"> | |
| 271 | + <FormItem label="${comment}" name="${javaField}"> | |
| 260 | 272 | <DatePicker |
| 261 | 273 | v-model:value="queryParams.${javaField}" |
| 262 | 274 | valueFormat="YYYY-MM-DD" |
| ... | ... | @@ -264,25 +276,25 @@ onMounted(() => { |
| 264 | 276 | allowClear |
| 265 | 277 | class="w-full" |
| 266 | 278 | /> |
| 267 | - </Form.Item> | |
| 279 | + </FormItem> | |
| 268 | 280 | #else## 范围 |
| 269 | - <Form.Item label="${comment}" name="${javaField}"> | |
| 281 | + <FormItem label="${comment}" name="${javaField}"> | |
| 270 | 282 | <RangePicker |
| 271 | 283 | v-model:value="queryParams.${javaField}" |
| 272 | 284 | v-bind="getRangePickerDefaultProps()" |
| 273 | 285 | class="w-full" |
| 274 | 286 | /> |
| 275 | - </Form.Item> | |
| 287 | + </FormItem> | |
| 276 | 288 | #end |
| 277 | 289 | #end |
| 278 | 290 | #end |
| 279 | 291 | #end |
| 280 | - <Form.Item> | |
| 292 | + <FormItem> | |
| 281 | 293 | <Button class="ml-2" @click="resetQuery"> 重置 </Button> |
| 282 | 294 | <Button class="ml-2" @click="handleQuery" type="primary"> |
| 283 | 295 | 搜索 |
| 284 | 296 | </Button> |
| 285 | - </Form.Item> | |
| 297 | + </FormItem> | |
| 286 | 298 | </Form> |
| 287 | 299 | </Card> |
| 288 | 300 | ... | ... |
yudao-module-infra/src/main/resources/codegen/vue3_vben5_antdv_next/general/views/modules/list_sub_inner.vue.vm
| 1 | 1 | ## 子表的 erp 和 inner 使用相似的 list 列表,差异主要两点: |
| 2 | 2 | ## 1)inner 使用 list 不分页,erp 使用 page 分页 |
| 3 | 3 | ## 2)erp 支持单个子表的新增、修改、删除,inner 不支持 |
| 4 | -#parse("codegen/vue3_vben5_antd/general/views/modules/list_sub_erp.vue.vm") | |
| 5 | 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 | 6 | |
| 7 | 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 | 11 | import { useVbenForm } from '#/adapter/form'; |
| 12 | 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 | 1 | <script lang="ts" setup> |
| 2 | -import type { FileType } from 'antdv-next/es/upload/interface'; | |
| 3 | - | |
| 4 | 2 | import { useVbenModal } from '@vben/common-ui'; |
| 5 | 3 | import { downloadFileFromBlobPart } from '@vben/utils'; |
| 6 | 4 | |
| ... | ... | @@ -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 | 48 | formApi.setFieldValue('file', file); |
| 51 | 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 | 8 | import {#if ($table.templateType != 2 && $deleteBatchEnable) confirm,#end Page, useVbenModal } from '@vben/common-ui'; |
| 9 | 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 | 13 | import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table'; |
| 14 | 14 | #if (${table.templateType} == 2)## 树表接口 | ... | ... |
yudao-module-infra/src/main/resources/codegen/vue3_vben5_antdv_next/schema/views/modules/form_sub_inner.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 | 5 | #set ($subClassNameVar = $subClassNameVars.get($subIndex)) |
| 6 | 6 | #set ($SubJoinColumnName = $subJoinColumn.javaField.substring(0,1).toUpperCase() + ${subJoinColumn.javaField.substring(1)})##首字母大写 |
| 7 | 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 | 14 | <script lang="ts" setup> |
| 9 | 15 | import type { ${apiName} } from '#/api/${table.moduleName}/${table.businessName}'; |
| 10 | 16 | |
| ... | ... | @@ -14,7 +20,19 @@ |
| 14 | 20 | |
| 15 | 21 | #if ($subTable.subJoinMany) ## 一对多 |
| 16 | 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 | 36 | import { ImageUpload, FileUpload } from "#/components/upload"; |
| 19 | 37 | import { useVbenVxeGrid } from '#/adapter/vxe-table'; |
| 20 | 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 | 1 | ## 子表的 erp 和 inner 使用相似的 list 列表,差异主要两点: |
| 2 | 2 | ## 1)inner 使用 list 不分页,erp 使用 page 分页 |
| 3 | 3 | ## 2)erp 支持单个子表的新增、修改、删除,inner 不支持 |
| 4 | -#parse("codegen/vue3_vben5_antd/schema/views/modules/list_sub_erp.vue.vm") | |
| 5 | 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 | 157 | String lastFilePath = StrUtil.subAfter(filePath, '/', true); |
| 158 | 158 | String ext = StrUtil.subAfter(lastFilePath, '.', true); |
| 159 | 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 | 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 | 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 | 173 | usedContentPaths.add(contentPath); | ... | ... |
yudao-module-infra/src/test/java/cn/iocoder/yudao/module/infra/service/codegen/inner/CodegenEngineUniappTest.java
| 1 | 1 | package cn.iocoder.yudao.module.infra.service.codegen.inner; |
| 2 | 2 | |
| 3 | +import cn.iocoder.yudao.framework.common.exception.ServiceException; | |
| 3 | 4 | import cn.iocoder.yudao.module.infra.dal.dataobject.codegen.CodegenColumnDO; |
| 4 | 5 | import cn.iocoder.yudao.module.infra.dal.dataobject.codegen.CodegenTableDO; |
| 5 | 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 | import com.baomidou.mybatisplus.annotation.DbType; |
| 8 | 9 | import org.junit.jupiter.api.Test; |
| 9 | 10 | |
| 11 | +import java.util.Arrays; | |
| 12 | +import java.util.HashMap; | |
| 10 | 13 | import java.util.List; |
| 11 | 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 | 22 | * {@link CodegenEngine} 的 Vue3 Admin Uniapp + Wot UI 单元测试 |
| ... | ... | @@ -31,4 +39,340 @@ public class CodegenEngineUniappTest extends CodegenEngineAbstractTest { |
| 31 | 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 | 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 | 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 | 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 | 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 | 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 | 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 | 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 | 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 | 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 | 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 | 161 | \ No newline at end of file | ... | ... |