Commit f7bfa5d7f42bc8cef3dea0ca9b33556132bfa4db
Merge remote-tracking branch 'origin/master-jdk17' into master-jdk17
Showing
152 changed files
with
11916 additions
and
760 deletions
Too many changes.
To preserve performance only 100 of 152 files are displayed.
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/ --> |
| ... | ... | @@ -260,4 +246,4 @@ |
| 260 | 246 | </dependency> |
| 261 | 247 | </dependencies> |
| 262 | 248 | |
| 263 | -</project> | |
| 264 | 249 | \ No newline at end of file |
| 250 | +</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/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-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
| ... | ... | @@ -145,7 +145,7 @@ const formData = reactive({ |
| 145 | 145 | #else |
| 146 | 146 | ${column.javaField}: undefined as number | undefined, |
| 147 | 147 | #end |
| 148 | - #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") | |
| 149 | 149 | ${column.javaField}: undefined as number | undefined, |
| 150 | 150 | #elseif(${javaType} == "boolean") |
| 151 | 151 | ${column.javaField}: undefined as boolean | undefined, |
| ... | ... | @@ -174,7 +174,7 @@ const placeholder = computed(() => { |
| 174 | 174 | conditions.push(`${comment}:#[[${]]#getDictLabel(DICT_TYPE.${dictType.toUpperCase()}, formData.${javaField})#[[}]]#`) |
| 175 | 175 | } |
| 176 | 176 | #else |
| 177 | - #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") | |
| 178 | 178 | if (formData.${javaField} !== undefined) { |
| 179 | 179 | conditions.push(`${comment}:#[[${]]#formData.${javaField}#[[}]]#`) |
| 180 | 180 | } | ... | ... |
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/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,10 +8,14 @@ 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; | |
| 12 | 16 | |
| 13 | 17 | import static org.junit.jupiter.api.Assertions.assertFalse; |
| 18 | +import static org.junit.jupiter.api.Assertions.assertThrows; | |
| 14 | 19 | import static org.junit.jupiter.api.Assertions.assertTrue; |
| 15 | 20 | |
| 16 | 21 | /** |
| ... | ... | @@ -59,4 +64,315 @@ public class CodegenEngineUniappTest extends CodegenEngineAbstractTest { |
| 59 | 64 | assertTrue(searchForm.contains("sex: undefined as string | undefined,")); |
| 60 | 65 | } |
| 61 | 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 | + | |
| 62 | 378 | } | ... | ... |
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 | ... | ... |
yudao-module-infra/src/test/resources/codegen/vue3_admin_uniapp_master_erp/java/InfraStudentServiceImpl
0 → 100644
| 1 | +package cn.iocoder.yudao.module.infra.service.demo; | |
| 2 | + | |
| 3 | +import cn.hutool.core.collection.CollUtil; | |
| 4 | +import org.springframework.stereotype.Service; | |
| 5 | +import jakarta.annotation.Resource; | |
| 6 | +import org.springframework.validation.annotation.Validated; | |
| 7 | +import org.springframework.transaction.annotation.Transactional; | |
| 8 | + | |
| 9 | +import java.util.*; | |
| 10 | +import cn.iocoder.yudao.module.infra.controller.admin.demo.vo.*; | |
| 11 | +import cn.iocoder.yudao.module.infra.dal.dataobject.demo.InfraStudentDO; | |
| 12 | +import cn.iocoder.yudao.module.infra.dal.dataobject.demo.InfraStudentContactDO; | |
| 13 | +import cn.iocoder.yudao.module.infra.dal.dataobject.demo.InfraStudentTeacherDO; | |
| 14 | +import cn.iocoder.yudao.framework.common.pojo.PageResult; | |
| 15 | +import cn.iocoder.yudao.framework.common.pojo.PageParam; | |
| 16 | +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; | |
| 17 | + | |
| 18 | +import cn.iocoder.yudao.module.infra.dal.mysql.demo.InfraStudentMapper; | |
| 19 | +import cn.iocoder.yudao.module.infra.dal.mysql.demo.InfraStudentContactMapper; | |
| 20 | +import cn.iocoder.yudao.module.infra.dal.mysql.demo.InfraStudentTeacherMapper; | |
| 21 | + | |
| 22 | +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; | |
| 23 | +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; | |
| 24 | +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.diffList; | |
| 25 | +import static cn.iocoder.yudao.module.infra.enums.ErrorCodeConstants.*; | |
| 26 | + | |
| 27 | +/** | |
| 28 | + * 学生 Service 实现类 | |
| 29 | + * | |
| 30 | + * @author 芋道源码 | |
| 31 | + */ | |
| 32 | +@Service | |
| 33 | +@Validated | |
| 34 | +public class InfraStudentServiceImpl implements InfraStudentService { | |
| 35 | + | |
| 36 | + @Resource | |
| 37 | + private InfraStudentMapper studentMapper; | |
| 38 | + @Resource | |
| 39 | + private InfraStudentContactMapper studentContactMapper; | |
| 40 | + @Resource | |
| 41 | + private InfraStudentTeacherMapper studentTeacherMapper; | |
| 42 | + | |
| 43 | + @Override | |
| 44 | + public Long createStudent(InfraStudentSaveReqVO createReqVO) { | |
| 45 | + // 插入 | |
| 46 | + InfraStudentDO student = BeanUtils.toBean(createReqVO, InfraStudentDO.class); | |
| 47 | + studentMapper.insert(student); | |
| 48 | + | |
| 49 | + // 返回 | |
| 50 | + return student.getId(); | |
| 51 | + } | |
| 52 | + | |
| 53 | + @Override | |
| 54 | + public void updateStudent(InfraStudentSaveReqVO updateReqVO) { | |
| 55 | + // 校验存在 | |
| 56 | + validateStudentExists(updateReqVO.getId()); | |
| 57 | + // 更新 | |
| 58 | + InfraStudentDO updateObj = BeanUtils.toBean(updateReqVO, InfraStudentDO.class); | |
| 59 | + studentMapper.updateById(updateObj); | |
| 60 | + } | |
| 61 | + | |
| 62 | + @Override | |
| 63 | + @Transactional(rollbackFor = Exception.class) | |
| 64 | + public void deleteStudent(Long id) { | |
| 65 | + // 校验存在 | |
| 66 | + validateStudentExists(id); | |
| 67 | + // 删除 | |
| 68 | + studentMapper.deleteById(id); | |
| 69 | + | |
| 70 | + // 删除子表 | |
| 71 | + deleteStudentContactByStudentId(id); | |
| 72 | + deleteStudentTeacherByStudentId(id); | |
| 73 | + } | |
| 74 | + | |
| 75 | + @Override | |
| 76 | + @Transactional(rollbackFor = Exception.class) | |
| 77 | + public void deleteStudentListByIds(List<Long> ids) { | |
| 78 | + // 删除 | |
| 79 | + studentMapper.deleteByIds(ids); | |
| 80 | + | |
| 81 | + // 删除子表 | |
| 82 | + deleteStudentContactByStudentIds(ids); | |
| 83 | + deleteStudentTeacherByStudentIds(ids); | |
| 84 | + } | |
| 85 | + | |
| 86 | + | |
| 87 | + private void validateStudentExists(Long id) { | |
| 88 | + if (studentMapper.selectById(id) == null) { | |
| 89 | + throw exception(STUDENT_NOT_EXISTS); | |
| 90 | + } | |
| 91 | + } | |
| 92 | + | |
| 93 | + @Override | |
| 94 | + public InfraStudentDO getStudent(Long id) { | |
| 95 | + return studentMapper.selectById(id); | |
| 96 | + } | |
| 97 | + | |
| 98 | + @Override | |
| 99 | + public PageResult<InfraStudentDO> getStudentPage(InfraStudentPageReqVO pageReqVO) { | |
| 100 | + return studentMapper.selectPage(pageReqVO); | |
| 101 | + } | |
| 102 | + | |
| 103 | + // ==================== 子表(学生联系人) ==================== | |
| 104 | + | |
| 105 | + @Override | |
| 106 | + public PageResult<InfraStudentContactDO> getStudentContactPage(PageParam pageReqVO, Long studentId) { | |
| 107 | + return studentContactMapper.selectPage(pageReqVO, studentId); | |
| 108 | + } | |
| 109 | + | |
| 110 | + @Override | |
| 111 | + public Long createStudentContact(InfraStudentContactDO studentContact) { | |
| 112 | + studentContact.clean(); // 清理掉创建、更新时间等相关属性值 | |
| 113 | + studentContactMapper.insert(studentContact); | |
| 114 | + return studentContact.getId(); | |
| 115 | + } | |
| 116 | + | |
| 117 | + @Override | |
| 118 | + public void updateStudentContact(InfraStudentContactDO studentContact) { | |
| 119 | + // 校验存在 | |
| 120 | + validateStudentContactExists(studentContact.getId()); | |
| 121 | + // 更新 | |
| 122 | + studentContact.clean(); // 解决更新情况下:updateTime 不更新 | |
| 123 | + studentContactMapper.updateById(studentContact); | |
| 124 | + } | |
| 125 | + | |
| 126 | + @Override | |
| 127 | + public void deleteStudentContact(Long id) { | |
| 128 | + // 删除 | |
| 129 | + studentContactMapper.deleteById(id); | |
| 130 | + } | |
| 131 | + | |
| 132 | + @Override | |
| 133 | + public void deleteStudentContactListByIds(List<Long> ids) { | |
| 134 | + // 删除 | |
| 135 | + studentContactMapper.deleteByIds(ids); | |
| 136 | + } | |
| 137 | + | |
| 138 | + @Override | |
| 139 | + public InfraStudentContactDO getStudentContact(Long id) { | |
| 140 | + return studentContactMapper.selectById(id); | |
| 141 | + } | |
| 142 | + | |
| 143 | + private void validateStudentContactExists(Long id) { | |
| 144 | + if (studentContactMapper.selectById(id) == null) { | |
| 145 | + throw exception(STUDENT_CONTACT_NOT_EXISTS); | |
| 146 | + } | |
| 147 | + } | |
| 148 | + | |
| 149 | + private void deleteStudentContactByStudentId(Long studentId) { | |
| 150 | + studentContactMapper.deleteByStudentId(studentId); | |
| 151 | + } | |
| 152 | + | |
| 153 | + private void deleteStudentContactByStudentIds(List<Long> studentIds) { | |
| 154 | + studentContactMapper.deleteByStudentIds(studentIds); | |
| 155 | + } | |
| 156 | + | |
| 157 | + // ==================== 子表(学生班主任) ==================== | |
| 158 | + | |
| 159 | + @Override | |
| 160 | + public PageResult<InfraStudentTeacherDO> getStudentTeacherPage(PageParam pageReqVO, Long studentId) { | |
| 161 | + return studentTeacherMapper.selectPage(pageReqVO, studentId); | |
| 162 | + } | |
| 163 | + | |
| 164 | + @Override | |
| 165 | + public Long createStudentTeacher(InfraStudentTeacherDO studentTeacher) { | |
| 166 | + // 校验是否已经存在 | |
| 167 | + if (studentTeacherMapper.selectByStudentId(studentTeacher.getStudentId()) != null) { | |
| 168 | + throw exception(STUDENT_TEACHER_EXISTS); | |
| 169 | + } | |
| 170 | + // 插入 | |
| 171 | + studentTeacher.clean(); // 清理掉创建、更新时间等相关属性值 | |
| 172 | + studentTeacherMapper.insert(studentTeacher); | |
| 173 | + return studentTeacher.getId(); | |
| 174 | + } | |
| 175 | + | |
| 176 | + @Override | |
| 177 | + public void updateStudentTeacher(InfraStudentTeacherDO studentTeacher) { | |
| 178 | + // 校验存在 | |
| 179 | + validateStudentTeacherExists(studentTeacher.getId()); | |
| 180 | + // 更新 | |
| 181 | + studentTeacher.clean(); // 解决更新情况下:updateTime 不更新 | |
| 182 | + studentTeacherMapper.updateById(studentTeacher); | |
| 183 | + } | |
| 184 | + | |
| 185 | + @Override | |
| 186 | + public void deleteStudentTeacher(Long id) { | |
| 187 | + // 删除 | |
| 188 | + studentTeacherMapper.deleteById(id); | |
| 189 | + } | |
| 190 | + | |
| 191 | + @Override | |
| 192 | + public void deleteStudentTeacherListByIds(List<Long> ids) { | |
| 193 | + // 删除 | |
| 194 | + studentTeacherMapper.deleteByIds(ids); | |
| 195 | + } | |
| 196 | + | |
| 197 | + @Override | |
| 198 | + public InfraStudentTeacherDO getStudentTeacher(Long id) { | |
| 199 | + return studentTeacherMapper.selectById(id); | |
| 200 | + } | |
| 201 | + | |
| 202 | + private void validateStudentTeacherExists(Long id) { | |
| 203 | + if (studentTeacherMapper.selectById(id) == null) { | |
| 204 | + throw exception(STUDENT_TEACHER_NOT_EXISTS); | |
| 205 | + } | |
| 206 | + } | |
| 207 | + | |
| 208 | + private void deleteStudentTeacherByStudentId(Long studentId) { | |
| 209 | + studentTeacherMapper.deleteByStudentId(studentId); | |
| 210 | + } | |
| 211 | + | |
| 212 | + private void deleteStudentTeacherByStudentIds(List<Long> studentIds) { | |
| 213 | + studentTeacherMapper.deleteByStudentIds(studentIds); | |
| 214 | + } | |
| 215 | + | |
| 216 | +} | |
| 0 | 217 | \ No newline at end of file | ... | ... |
yudao-module-infra/src/test/resources/codegen/vue3_admin_uniapp_master_erp/java/InfraStudentServiceImplTest
0 → 100644
| 1 | +package cn.iocoder.yudao.module.infra.service.demo; | |
| 2 | + | |
| 3 | +import org.junit.jupiter.api.Disabled; | |
| 4 | +import org.junit.jupiter.api.Test; | |
| 5 | + | |
| 6 | +import jakarta.annotation.Resource; | |
| 7 | + | |
| 8 | +import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest; | |
| 9 | + | |
| 10 | +import cn.iocoder.yudao.module.infra.controller.admin.demo.vo.*; | |
| 11 | +import cn.iocoder.yudao.module.infra.dal.dataobject.demo.InfraStudentDO; | |
| 12 | +import cn.iocoder.yudao.module.infra.dal.mysql.demo.InfraStudentMapper; | |
| 13 | +import cn.iocoder.yudao.framework.common.pojo.PageResult; | |
| 14 | + | |
| 15 | +import jakarta.annotation.Resource; | |
| 16 | +import org.springframework.context.annotation.Import; | |
| 17 | +import java.util.*; | |
| 18 | +import java.time.LocalDateTime; | |
| 19 | + | |
| 20 | +import static cn.hutool.core.util.RandomUtil.*; | |
| 21 | +import static cn.iocoder.yudao.module.infra.enums.ErrorCodeConstants.*; | |
| 22 | +import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.*; | |
| 23 | +import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*; | |
| 24 | +import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.*; | |
| 25 | +import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.*; | |
| 26 | +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.*; | |
| 27 | +import static org.junit.jupiter.api.Assertions.*; | |
| 28 | +import static org.mockito.Mockito.*; | |
| 29 | + | |
| 30 | +/** | |
| 31 | + * {@link InfraStudentServiceImpl} 的单元测试类 | |
| 32 | + * | |
| 33 | + * @author 芋道源码 | |
| 34 | + */ | |
| 35 | +@Import(InfraStudentServiceImpl.class) | |
| 36 | +public class InfraStudentServiceImplTest extends BaseDbUnitTest { | |
| 37 | + | |
| 38 | + @Resource | |
| 39 | + private InfraStudentServiceImpl studentService; | |
| 40 | + | |
| 41 | + @Resource | |
| 42 | + private InfraStudentMapper studentMapper; | |
| 43 | + | |
| 44 | + @Test | |
| 45 | + public void testCreateStudent_success() { | |
| 46 | + // 准备参数 | |
| 47 | + InfraStudentSaveReqVO createReqVO = randomPojo(InfraStudentSaveReqVO.class).setId(null); | |
| 48 | + | |
| 49 | + // 调用 | |
| 50 | + Long studentId = studentService.createStudent(createReqVO); | |
| 51 | + // 断言 | |
| 52 | + assertNotNull(studentId); | |
| 53 | + // 校验记录的属性是否正确 | |
| 54 | + InfraStudentDO student = studentMapper.selectById(studentId); | |
| 55 | + assertPojoEquals(createReqVO, student, "id"); | |
| 56 | + } | |
| 57 | + | |
| 58 | + @Test | |
| 59 | + public void testUpdateStudent_success() { | |
| 60 | + // mock 数据 | |
| 61 | + InfraStudentDO dbStudent = randomPojo(InfraStudentDO.class); | |
| 62 | + studentMapper.insert(dbStudent);// @Sql: 先插入出一条存在的数据 | |
| 63 | + // 准备参数 | |
| 64 | + InfraStudentSaveReqVO updateReqVO = randomPojo(InfraStudentSaveReqVO.class, o -> { | |
| 65 | + o.setId(dbStudent.getId()); // 设置更新的 ID | |
| 66 | + }); | |
| 67 | + | |
| 68 | + // 调用 | |
| 69 | + studentService.updateStudent(updateReqVO); | |
| 70 | + // 校验是否更新正确 | |
| 71 | + InfraStudentDO student = studentMapper.selectById(updateReqVO.getId()); // 获取最新的 | |
| 72 | + assertPojoEquals(updateReqVO, student); | |
| 73 | + } | |
| 74 | + | |
| 75 | + @Test | |
| 76 | + public void testUpdateStudent_notExists() { | |
| 77 | + // 准备参数 | |
| 78 | + InfraStudentSaveReqVO updateReqVO = randomPojo(InfraStudentSaveReqVO.class); | |
| 79 | + | |
| 80 | + // 调用, 并断言异常 | |
| 81 | + assertServiceException(() -> studentService.updateStudent(updateReqVO), STUDENT_NOT_EXISTS); | |
| 82 | + } | |
| 83 | + | |
| 84 | + @Test | |
| 85 | + public void testDeleteStudent_success() { | |
| 86 | + // mock 数据 | |
| 87 | + InfraStudentDO dbStudent = randomPojo(InfraStudentDO.class); | |
| 88 | + studentMapper.insert(dbStudent);// @Sql: 先插入出一条存在的数据 | |
| 89 | + // 准备参数 | |
| 90 | + Long id = dbStudent.getId(); | |
| 91 | + | |
| 92 | + // 调用 | |
| 93 | + studentService.deleteStudent(id); | |
| 94 | + // 校验数据不存在了 | |
| 95 | + assertNull(studentMapper.selectById(id)); | |
| 96 | + } | |
| 97 | + | |
| 98 | + @Test | |
| 99 | + public void testDeleteStudent_notExists() { | |
| 100 | + // 准备参数 | |
| 101 | + Long id = randomLongId(); | |
| 102 | + | |
| 103 | + // 调用, 并断言异常 | |
| 104 | + assertServiceException(() -> studentService.deleteStudent(id), STUDENT_NOT_EXISTS); | |
| 105 | + } | |
| 106 | + | |
| 107 | + @Test | |
| 108 | + @Disabled // TODO 请修改 null 为需要的值,然后删除 @Disabled 注解 | |
| 109 | + public void testGetStudentPage() { | |
| 110 | + // mock 数据 | |
| 111 | + InfraStudentDO dbStudent = randomPojo(InfraStudentDO.class, o -> { // 等会查询到 | |
| 112 | + o.setName(null); | |
| 113 | + o.setBirthday(null); | |
| 114 | + o.setSex(null); | |
| 115 | + o.setEnabled(null); | |
| 116 | + o.setCreateTime(null); | |
| 117 | + }); | |
| 118 | + studentMapper.insert(dbStudent); | |
| 119 | + // 测试 name 不匹配 | |
| 120 | + studentMapper.insert(cloneIgnoreId(dbStudent, o -> o.setName(null))); | |
| 121 | + // 测试 birthday 不匹配 | |
| 122 | + studentMapper.insert(cloneIgnoreId(dbStudent, o -> o.setBirthday(null))); | |
| 123 | + // 测试 sex 不匹配 | |
| 124 | + studentMapper.insert(cloneIgnoreId(dbStudent, o -> o.setSex(null))); | |
| 125 | + // 测试 enabled 不匹配 | |
| 126 | + studentMapper.insert(cloneIgnoreId(dbStudent, o -> o.setEnabled(null))); | |
| 127 | + // 测试 createTime 不匹配 | |
| 128 | + studentMapper.insert(cloneIgnoreId(dbStudent, o -> o.setCreateTime(null))); | |
| 129 | + // 准备参数 | |
| 130 | + InfraStudentPageReqVO reqVO = new InfraStudentPageReqVO(); | |
| 131 | + reqVO.setName(null); | |
| 132 | + reqVO.setBirthday(null); | |
| 133 | + reqVO.setSex(null); | |
| 134 | + reqVO.setEnabled(null); | |
| 135 | + reqVO.setCreateTime(buildBetweenTime(2023, 2, 1, 2023, 2, 28)); | |
| 136 | + | |
| 137 | + // 调用 | |
| 138 | + PageResult<InfraStudentDO> pageResult = studentService.getStudentPage(reqVO); | |
| 139 | + // 断言 | |
| 140 | + assertEquals(1, pageResult.getTotal()); | |
| 141 | + assertEquals(1, pageResult.getList().size()); | |
| 142 | + assertPojoEquals(dbStudent, pageResult.getList().get(0)); | |
| 143 | + } | |
| 144 | + | |
| 145 | +} | |
| 0 | 146 | \ No newline at end of file | ... | ... |
yudao-module-infra/src/test/resources/codegen/vue3_admin_uniapp_master_erp/java/InfraStudentTeacherDO
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_teacher") | |
| 16 | +@KeySequence("infra_student_teacher_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 InfraStudentTeacherDO 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/InfraStudentTeacherMapper
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.InfraStudentTeacherDO; | |
| 10 | +import org.apache.ibatis.annotations.Mapper; | |
| 11 | + | |
| 12 | +/** | |
| 13 | + * 学生班主任 Mapper | |
| 14 | + * | |
| 15 | + * @author 芋道源码 | |
| 16 | + */ | |
| 17 | +@Mapper | |
| 18 | +public interface InfraStudentTeacherMapper extends BaseMapperX<InfraStudentTeacherDO> { | |
| 19 | + | |
| 20 | + default PageResult<InfraStudentTeacherDO> selectPage(PageParam reqVO, Long studentId) { | |
| 21 | + return selectPage(reqVO, new LambdaQueryWrapperX<InfraStudentTeacherDO>() | |
| 22 | + .eq(InfraStudentTeacherDO::getStudentId, studentId) | |
| 23 | + .orderByDesc(InfraStudentTeacherDO::getId)); | |
| 24 | + } | |
| 25 | + default InfraStudentTeacherDO selectByStudentId(Long studentId) { | |
| 26 | + return selectOne(InfraStudentTeacherDO::getStudentId, studentId); | |
| 27 | + } | |
| 28 | + | |
| 29 | + default int deleteByStudentId(Long studentId) { | |
| 30 | + return delete(InfraStudentTeacherDO::getStudentId, studentId); | |
| 31 | + } | |
| 32 | + | |
| 33 | + default int deleteByStudentIds(List<Long> studentIds) { | |
| 34 | + return deleteBatch(InfraStudentTeacherDO::getStudentId, studentIds); | |
| 35 | + } | |
| 36 | + | |
| 37 | +} | |
| 0 | 38 | \ No newline at end of file | ... | ... |
yudao-module-infra/src/test/resources/codegen/vue3_admin_uniapp_master_erp/sql/h2
0 → 100644
| 1 | +-- 将该建表 SQL 语句,添加到 yudao-module-infra-biz 模块的 test/resources/sql/create_tables.sql 文件里 | |
| 2 | +CREATE TABLE IF NOT EXISTS "infra_student" ( | |
| 3 | + "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY, | |
| 4 | + "name" varchar NOT NULL, | |
| 5 | + "description" varchar NOT NULL, | |
| 6 | + "birthday" varchar NOT NULL, | |
| 7 | + "sex" int NOT NULL, | |
| 8 | + "enabled" bit NOT NULL, | |
| 9 | + "avatar" varchar NOT NULL, | |
| 10 | + "video" varchar NOT NULL, | |
| 11 | + "memo" varchar NOT NULL, | |
| 12 | + "create_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, | |
| 13 | + PRIMARY KEY ("id") | |
| 14 | +) COMMENT '学生表'; | |
| 15 | + | |
| 16 | +-- 将该删表 SQL 语句,添加到 yudao-module-infra-biz 模块的 test/resources/sql/clean.sql 文件里 | |
| 17 | +DELETE FROM "infra_student"; | |
| 0 | 18 | \ No newline at end of file | ... | ... |
yudao-module-infra/src/test/resources/codegen/vue3_admin_uniapp_master_erp/sql/sql
0 → 100644
| 1 | +-- 菜单 SQL | |
| 2 | +INSERT INTO system_menu( | |
| 3 | + name, permission, type, sort, parent_id, | |
| 4 | + path, icon, component, status, component_name | |
| 5 | +) | |
| 6 | +VALUES ( | |
| 7 | + '学生管理', '', 2, 0, 888, | |
| 8 | + 'student', '', 'infra/demo/index', 0, 'InfraStudent' | |
| 9 | +); | |
| 10 | + | |
| 11 | +-- 按钮父菜单ID | |
| 12 | +SELECT @parentId := LAST_INSERT_ID(); | |
| 13 | + | |
| 14 | +-- 按钮 SQL | |
| 15 | +INSERT INTO system_menu( | |
| 16 | + name, permission, type, sort, parent_id, | |
| 17 | + path, icon, component, status | |
| 18 | +) | |
| 19 | +VALUES ( | |
| 20 | + '学生查询', 'infra:student:query', 3, 1, @parentId, | |
| 21 | + '', '', '', 0 | |
| 22 | +); | |
| 23 | +INSERT INTO system_menu( | |
| 24 | + name, permission, type, sort, parent_id, | |
| 25 | + path, icon, component, status | |
| 26 | +) | |
| 27 | +VALUES ( | |
| 28 | + '学生创建', 'infra:student:create', 3, 2, @parentId, | |
| 29 | + '', '', '', 0 | |
| 30 | +); | |
| 31 | +INSERT INTO system_menu( | |
| 32 | + name, permission, type, sort, parent_id, | |
| 33 | + path, icon, component, status | |
| 34 | +) | |
| 35 | +VALUES ( | |
| 36 | + '学生更新', 'infra:student:update', 3, 3, @parentId, | |
| 37 | + '', '', '', 0 | |
| 38 | +); | |
| 39 | +INSERT INTO system_menu( | |
| 40 | + name, permission, type, sort, parent_id, | |
| 41 | + path, icon, component, status | |
| 42 | +) | |
| 43 | +VALUES ( | |
| 44 | + '学生删除', 'infra:student:delete', 3, 4, @parentId, | |
| 45 | + '', '', '', 0 | |
| 46 | +); | |
| 47 | +INSERT INTO system_menu( | |
| 48 | + name, permission, type, sort, parent_id, | |
| 49 | + path, icon, component, status | |
| 50 | +) | |
| 51 | +VALUES ( | |
| 52 | + '学生导出', 'infra:student:export', 3, 5, @parentId, | |
| 53 | + '', '', '', 0 | |
| 54 | +); | |
| 0 | 55 | \ No newline at end of file | ... | ... |
yudao-module-infra/src/test/resources/codegen/vue3_admin_uniapp_master_erp/ts/index
0 → 100644
| 1 | +import type { PageParam, PageResult } from '@/http/types' | |
| 2 | +import { http } from '@/http/http' | |
| 3 | + | |
| 4 | +/** 学生联系人信息 */ | |
| 5 | +export interface StudentContact { | |
| 6 | + id?: number | |
| 7 | + studentId: number | |
| 8 | + name: string | |
| 9 | + description: string | |
| 10 | + birthday: number | |
| 11 | + sex: number | |
| 12 | + enabled: boolean | |
| 13 | + avatar: string | |
| 14 | + video?: string | |
| 15 | + memo: string | |
| 16 | + createTime?: number | |
| 17 | +} | |
| 18 | + | |
| 19 | +/** 学生班主任信息 */ | |
| 20 | +export interface StudentTeacher { | |
| 21 | + id?: number | |
| 22 | + studentId: number | |
| 23 | + name: string | |
| 24 | + description: string | |
| 25 | + birthday: number | |
| 26 | + sex: number | |
| 27 | + enabled: boolean | |
| 28 | + avatar: string | |
| 29 | + video?: string | |
| 30 | + memo: string | |
| 31 | + createTime?: number | |
| 32 | +} | |
| 33 | + | |
| 34 | +/** 学生信息 */ | |
| 35 | +export interface Student { | |
| 36 | + id?: number | |
| 37 | + name: string | |
| 38 | + description: string | |
| 39 | + birthday: number | |
| 40 | + sex: number | |
| 41 | + enabled: boolean | |
| 42 | + avatar: string | |
| 43 | + video: string | |
| 44 | + memo: string | |
| 45 | + createTime?: number | |
| 46 | +} | |
| 47 | + | |
| 48 | +/** 获取学生分页列表 */ | |
| 49 | +export function getStudentPage(params: PageParam) { | |
| 50 | + return http.get<PageResult<Student>>('/infra/student/page', params) | |
| 51 | +} | |
| 52 | + | |
| 53 | +/** 获取学生详情 */ | |
| 54 | +export function getStudent(id: number) { | |
| 55 | + return http.get<Student>(`/infra/student/get?id=${id}`) | |
| 56 | +} | |
| 57 | + | |
| 58 | +/** 创建学生 */ | |
| 59 | +export function createStudent(data: Student) { | |
| 60 | + return http.post<number>('/infra/student/create', data) | |
| 61 | +} | |
| 62 | + | |
| 63 | +/** 更新学生 */ | |
| 64 | +export function updateStudent(data: Student) { | |
| 65 | + return http.put<boolean>('/infra/student/update', data) | |
| 66 | +} | |
| 67 | + | |
| 68 | +/** 删除学生 */ | |
| 69 | +export function deleteStudent(id: number) { | |
| 70 | + return http.delete<boolean>(`/infra/student/delete?id=${id}`) | |
| 71 | +} | |
| 72 | + | |
| 73 | +// ==================== 子表:学生联系人 ==================== | |
| 74 | + | |
| 75 | +/** 获取学生联系人分页列表 */ | |
| 76 | +export function getStudentContactPage(params: PageParam & { studentId: number }) { | |
| 77 | + return http.get<PageResult<StudentContact>>('/infra/student/student-contact/page', params) | |
| 78 | +} | |
| 79 | + | |
| 80 | +/** 获取学生联系人详情 */ | |
| 81 | +export function getStudentContact(id: number) { | |
| 82 | + return http.get<StudentContact>(`/infra/student/student-contact/get?id=${id}`) | |
| 83 | +} | |
| 84 | + | |
| 85 | +/** 创建学生联系人 */ | |
| 86 | +export function createStudentContact(data: StudentContact) { | |
| 87 | + return http.post<number>('/infra/student/student-contact/create', data) | |
| 88 | +} | |
| 89 | + | |
| 90 | +/** 更新学生联系人 */ | |
| 91 | +export function updateStudentContact(data: StudentContact) { | |
| 92 | + return http.put<boolean>('/infra/student/student-contact/update', data) | |
| 93 | +} | |
| 94 | + | |
| 95 | +/** 删除学生联系人 */ | |
| 96 | +export function deleteStudentContact(id: number) { | |
| 97 | + return http.delete<boolean>(`/infra/student/student-contact/delete?id=${id}`) | |
| 98 | +} | |
| 99 | + | |
| 100 | +// ==================== 子表:学生班主任 ==================== | |
| 101 | + | |
| 102 | +/** 获取学生班主任分页列表 */ | |
| 103 | +export function getStudentTeacherPage(params: PageParam & { studentId: number }) { | |
| 104 | + return http.get<PageResult<StudentTeacher>>('/infra/student/student-teacher/page', params) | |
| 105 | +} | |
| 106 | + | |
| 107 | +/** 获取学生班主任详情 */ | |
| 108 | +export function getStudentTeacher(id: number) { | |
| 109 | + return http.get<StudentTeacher>(`/infra/student/student-teacher/get?id=${id}`) | |
| 110 | +} | |
| 111 | + | |
| 112 | +/** 创建学生班主任 */ | |
| 113 | +export function createStudentTeacher(data: StudentTeacher) { | |
| 114 | + return http.post<number>('/infra/student/student-teacher/create', data) | |
| 115 | +} | |
| 116 | + | |
| 117 | +/** 更新学生班主任 */ | |
| 118 | +export function updateStudentTeacher(data: StudentTeacher) { | |
| 119 | + return http.put<boolean>('/infra/student/student-teacher/update', data) | |
| 120 | +} | |
| 121 | + | |
| 122 | +/** 删除学生班主任 */ | |
| 123 | +export function deleteStudentTeacher(id: number) { | |
| 124 | + return http.delete<boolean>(`/infra/student/student-teacher/delete?id=${id}`) | |
| 125 | +} | ... | ... |
yudao-module-infra/src/test/resources/codegen/vue3_admin_uniapp_master_erp/vue/detail/index
0 → 100644
| 1 | +<template> | |
| 2 | + <view class="yd-page-container"> | |
| 3 | + <!-- 顶部导航栏 --> | |
| 4 | + <wd-navbar title="学生详情" left-arrow placeholder safe-area-inset-top fixed @click-left="handleBack" /> | |
| 5 | + | |
| 6 | + <view class="pb-160rpx"> | |
| 7 | + <!-- 主表详情 --> | |
| 8 | + <wd-cell-group border title="学生信息"> | |
| 9 | + <wd-cell title="编号" :value="formData?.id ?? '-'" /> | |
| 10 | + <wd-cell title="名字" :value="formData?.name ?? '-'" /> | |
| 11 | + <wd-cell title="简介" :value="formData?.description ?? '-'" /> | |
| 12 | + <wd-cell title="出生日期" :value="formatDateTime(formData?.birthday) || '-'" /> | |
| 13 | + <wd-cell title="性别"> | |
| 14 | + <dict-tag :type="DICT_TYPE.SYSTEM_USER_SEX" :value="formData?.sex" /> | |
| 15 | + </wd-cell> | |
| 16 | + <wd-cell title="是否有效"> | |
| 17 | + <dict-tag :type="DICT_TYPE.INFRA_BOOLEAN_STRING" :value="formData?.enabled" /> | |
| 18 | + </wd-cell> | |
| 19 | + <wd-cell title="头像" :value="formData?.avatar ?? '-'" /> | |
| 20 | + <wd-cell title="附件" :value="formData?.video ?? '-'" /> | |
| 21 | + <wd-cell title="备注" :value="formData?.memo ?? '-'" /> | |
| 22 | + <wd-cell title="创建时间" :value="formatDateTime(formData?.createTime) || '-'" /> | |
| 23 | + </wd-cell-group> | |
| 24 | + | |
| 25 | + <!-- 子表:学生联系人 --> | |
| 26 | + <view class="mt-20rpx flex items-center justify-between px-24rpx py-16rpx"> | |
| 27 | + <text class="text-28rpx text-[#333] font-semibold">学生联系人({{ studentContactTotal }})</text> | |
| 28 | + <wd-button | |
| 29 | + v-if="hasAccessByCodes(['infra:student:create'])" | |
| 30 | + size="small" type="primary" variant="plain" @click="openStudentContact()" | |
| 31 | + > | |
| 32 | + 添加 | |
| 33 | + </wd-button> | |
| 34 | + </view> | |
| 35 | + <view class="px-24rpx"> | |
| 36 | + <z-paging | |
| 37 | + ref="studentContactPagingRef" | |
| 38 | + v-model="studentContactList" | |
| 39 | + :fixed="false" | |
| 40 | + :auto="false" | |
| 41 | + height="560rpx" | |
| 42 | + :default-page-size="10" | |
| 43 | + :refresher-enabled="false" | |
| 44 | + :inside-more="true" | |
| 45 | + :to-bottom-loading-more-enabled="false" | |
| 46 | + loading-more-default-text="点击加载更多" | |
| 47 | + empty-view-text="暂无学生联系人" | |
| 48 | + @query="queryStudentContactList" | |
| 49 | + > | |
| 50 | + <view | |
| 51 | + v-for="item in studentContactList" | |
| 52 | + :key="item.id" | |
| 53 | + class="mb-16rpx rounded-12rpx bg-white p-24rpx shadow-sm" | |
| 54 | + > | |
| 55 | + <view class="mb-8rpx text-28rpx text-[#666]"> | |
| 56 | + 学生编号:{{ item.studentId ?? '-' }} | |
| 57 | + </view> | |
| 58 | + <view class="mb-8rpx text-28rpx text-[#666]"> | |
| 59 | + 名字:{{ item.name ?? '-' }} | |
| 60 | + </view> | |
| 61 | + <view class="mb-8rpx text-28rpx text-[#666]"> | |
| 62 | + 简介:{{ item.description ?? '-' }} | |
| 63 | + </view> | |
| 64 | + <view class="mb-8rpx text-28rpx text-[#666]"> | |
| 65 | + 出生日期:{{ item.birthday ?? '-' }} | |
| 66 | + </view> | |
| 67 | + <view class="mb-8rpx text-28rpx text-[#666]"> | |
| 68 | + 性别:{{ item.sex ?? '-' }} | |
| 69 | + </view> | |
| 70 | + <view class="mb-8rpx text-28rpx text-[#666]"> | |
| 71 | + 是否有效:{{ item.enabled ?? '-' }} | |
| 72 | + </view> | |
| 73 | + <view class="mb-8rpx text-28rpx text-[#666]"> | |
| 74 | + 头像:{{ item.avatar ?? '-' }} | |
| 75 | + </view> | |
| 76 | + <view class="mb-8rpx text-28rpx text-[#666]"> | |
| 77 | + 附件:{{ item.video ?? '-' }} | |
| 78 | + </view> | |
| 79 | + <view class="mb-8rpx text-28rpx text-[#666]"> | |
| 80 | + 备注:{{ item.memo ?? '-' }} | |
| 81 | + </view> | |
| 82 | + <view class="mb-8rpx text-28rpx text-[#666]"> | |
| 83 | + 创建时间:{{ item.createTime ?? '-' }} | |
| 84 | + </view> | |
| 85 | + <view class="mt-16rpx flex justify-end gap-16rpx"> | |
| 86 | + <wd-button size="small" variant="plain" @click="openStudentContact(item)"> | |
| 87 | + 编辑 | |
| 88 | + </wd-button> | |
| 89 | + <wd-button size="small" type="danger" variant="plain" @click="removeStudentContact(item)"> | |
| 90 | + 删除 | |
| 91 | + </wd-button> | |
| 92 | + </view> | |
| 93 | + </view> | |
| 94 | + </z-paging> | |
| 95 | + </view> | |
| 96 | + <!-- 子表:学生班主任 --> | |
| 97 | + <view class="mt-20rpx flex items-center justify-between px-24rpx py-16rpx"> | |
| 98 | + <text class="text-28rpx text-[#333] font-semibold">学生班主任({{ studentTeacherTotal }})</text> | |
| 99 | + <wd-button | |
| 100 | + v-if="studentTeacherTotal === 0 && hasAccessByCodes(['infra:student:create'])" | |
| 101 | + size="small" type="primary" variant="plain" @click="openStudentTeacher()" | |
| 102 | + > | |
| 103 | + 添加 | |
| 104 | + </wd-button> | |
| 105 | + </view> | |
| 106 | + <view class="px-24rpx"> | |
| 107 | + <z-paging | |
| 108 | + ref="studentTeacherPagingRef" | |
| 109 | + v-model="studentTeacherList" | |
| 110 | + :fixed="false" | |
| 111 | + :auto="false" | |
| 112 | + height="560rpx" | |
| 113 | + :default-page-size="1" | |
| 114 | + :refresher-enabled="false" | |
| 115 | + :inside-more="true" | |
| 116 | + :to-bottom-loading-more-enabled="false" | |
| 117 | + loading-more-default-text="点击加载更多" | |
| 118 | + empty-view-text="暂无学生班主任" | |
| 119 | + @query="queryStudentTeacherList" | |
| 120 | + > | |
| 121 | + <view | |
| 122 | + v-for="item in studentTeacherList" | |
| 123 | + :key="item.id" | |
| 124 | + class="mb-16rpx rounded-12rpx bg-white p-24rpx shadow-sm" | |
| 125 | + > | |
| 126 | + <view class="mb-8rpx text-28rpx text-[#666]"> | |
| 127 | + 学生编号:{{ item.studentId ?? '-' }} | |
| 128 | + </view> | |
| 129 | + <view class="mb-8rpx text-28rpx text-[#666]"> | |
| 130 | + 名字:{{ item.name ?? '-' }} | |
| 131 | + </view> | |
| 132 | + <view class="mb-8rpx text-28rpx text-[#666]"> | |
| 133 | + 简介:{{ item.description ?? '-' }} | |
| 134 | + </view> | |
| 135 | + <view class="mb-8rpx text-28rpx text-[#666]"> | |
| 136 | + 出生日期:{{ item.birthday ?? '-' }} | |
| 137 | + </view> | |
| 138 | + <view class="mb-8rpx text-28rpx text-[#666]"> | |
| 139 | + 性别:{{ item.sex ?? '-' }} | |
| 140 | + </view> | |
| 141 | + <view class="mb-8rpx text-28rpx text-[#666]"> | |
| 142 | + 是否有效:{{ item.enabled ?? '-' }} | |
| 143 | + </view> | |
| 144 | + <view class="mb-8rpx text-28rpx text-[#666]"> | |
| 145 | + 头像:{{ item.avatar ?? '-' }} | |
| 146 | + </view> | |
| 147 | + <view class="mb-8rpx text-28rpx text-[#666]"> | |
| 148 | + 附件:{{ item.video ?? '-' }} | |
| 149 | + </view> | |
| 150 | + <view class="mb-8rpx text-28rpx text-[#666]"> | |
| 151 | + 备注:{{ item.memo ?? '-' }} | |
| 152 | + </view> | |
| 153 | + <view class="mb-8rpx text-28rpx text-[#666]"> | |
| 154 | + 创建时间:{{ item.createTime ?? '-' }} | |
| 155 | + </view> | |
| 156 | + <view class="mt-16rpx flex justify-end gap-16rpx"> | |
| 157 | + <wd-button size="small" variant="plain" @click="openStudentTeacher(item)"> | |
| 158 | + 编辑 | |
| 159 | + </wd-button> | |
| 160 | + <wd-button size="small" type="danger" variant="plain" @click="removeStudentTeacher(item)"> | |
| 161 | + 删除 | |
| 162 | + </wd-button> | |
| 163 | + </view> | |
| 164 | + </view> | |
| 165 | + </z-paging> | |
| 166 | + </view> | |
| 167 | + </view> | |
| 168 | + | |
| 169 | + <!-- 底部操作按钮 --> | |
| 170 | + <view class="yd-detail-footer"> | |
| 171 | + <view class="yd-detail-footer-actions"> | |
| 172 | + <wd-button v-if="hasAccessByCodes(['infra:student:update'])" class="flex-1" type="warning" @click="handleEdit"> | |
| 173 | + 编辑 | |
| 174 | + </wd-button> | |
| 175 | + <wd-button v-if="hasAccessByCodes(['infra:student:delete'])" class="flex-1" type="danger" :loading="deleting" @click="handleDelete"> | |
| 176 | + 删除 | |
| 177 | + </wd-button> | |
| 178 | + </view> | |
| 179 | + </view> | |
| 180 | + </view> | |
| 181 | +</template> | |
| 182 | + | |
| 183 | +<script lang="ts" setup> | |
| 184 | +import type { | |
| 185 | + Student, | |
| 186 | + StudentContact, | |
| 187 | + StudentTeacher, | |
| 188 | +} from '@/api/infra/demo' | |
| 189 | +import { onShow } from '@dcloudio/uni-app' | |
| 190 | +import { nextTick, ref } from 'vue' | |
| 191 | +import { useDialog } from '@wot-ui/ui/components/wd-dialog' | |
| 192 | +import { useToast } from '@wot-ui/ui/components/wd-toast' | |
| 193 | +import { | |
| 194 | + deleteStudent, | |
| 195 | + deleteStudentContact, | |
| 196 | + deleteStudentTeacher, | |
| 197 | + getStudent, | |
| 198 | + getStudentContactPage, | |
| 199 | + getStudentTeacherPage, | |
| 200 | +} from '@/api/infra/demo' | |
| 201 | +import { useAccess } from '@/hooks/useAccess' | |
| 202 | +import { delay, navigateBackPlus } from '@/utils' | |
| 203 | +import { DICT_TYPE } from '@/utils/constants' | |
| 204 | +import { formatDateTime } from '@/utils/date' | |
| 205 | + | |
| 206 | +const props = defineProps<{ id?: number }>() | |
| 207 | + | |
| 208 | +definePage({ | |
| 209 | + style: { navigationBarTitleText: '', navigationStyle: 'custom' }, | |
| 210 | +}) | |
| 211 | + | |
| 212 | +const { hasAccessByCodes } = useAccess() | |
| 213 | +const dialog = useDialog() | |
| 214 | +const toast = useToast() | |
| 215 | +const formData = ref<Student>() // 主表数据 | |
| 216 | +const studentContactList = ref<StudentContact[]>([]) // 学生联系人列表 | |
| 217 | +const studentContactTotal = ref(0) // 学生联系人总数 | |
| 218 | +const studentContactPagingRef = ref<any>() // 学生联系人分页组件引用 | |
| 219 | +const studentTeacherList = ref<StudentTeacher[]>([]) // 学生班主任列表 | |
| 220 | +const studentTeacherTotal = ref(0) // 学生班主任总数 | |
| 221 | +const studentTeacherPagingRef = ref<any>() // 学生班主任分页组件引用 | |
| 222 | +const deleting = ref(false) // 主表删除状态 | |
| 223 | + | |
| 224 | +/** 返回上一页 */ | |
| 225 | +function handleBack() { | |
| 226 | + navigateBackPlus('/pages-infra/demo/index') | |
| 227 | +} | |
| 228 | + | |
| 229 | +/** 加载主表 */ | |
| 230 | +async function loadMain() { | |
| 231 | + if (props.id) { | |
| 232 | + formData.value = await getStudent(props.id) | |
| 233 | + } | |
| 234 | +} | |
| 235 | + | |
| 236 | +/** 查询学生联系人列表 */ | |
| 237 | +async function queryStudentContactList(pageNo: number, pageSize: number) { | |
| 238 | + if (!props.id) { | |
| 239 | + studentContactPagingRef.value?.completeByTotal([], 0) | |
| 240 | + return | |
| 241 | + } | |
| 242 | + try { | |
| 243 | + const data = await getStudentContactPage({ studentId: props.id, pageNo, pageSize }) | |
| 244 | + studentContactTotal.value = data.total | |
| 245 | + studentContactPagingRef.value?.completeByTotal(data.list, data.total) | |
| 246 | + } catch { | |
| 247 | + studentContactPagingRef.value?.complete(false) | |
| 248 | + } | |
| 249 | +} | |
| 250 | + | |
| 251 | +/** 打开学生联系人表单 */ | |
| 252 | +function openStudentContact(item?: StudentContact) { | |
| 253 | + const query = [ | |
| 254 | + `studentId=${props.id}`, | |
| 255 | + item?.id ? `id=${item.id}` : '', | |
| 256 | + ].filter(Boolean).join('&') | |
| 257 | + uni.navigateTo({ url: `/pages-infra/demo/student-contact/form/index?${query}` }) | |
| 258 | +} | |
| 259 | + | |
| 260 | +/** 删除学生联系人 */ | |
| 261 | +async function removeStudentContact(item: StudentContact) { | |
| 262 | + try { | |
| 263 | + await dialog.confirm({ title: '提示', msg: '确定要删除该学生联系人吗?' }) | |
| 264 | + } catch { | |
| 265 | + return | |
| 266 | + } | |
| 267 | + await deleteStudentContact(item.id!) | |
| 268 | + toast.success('删除成功') | |
| 269 | + studentContactPagingRef.value?.reload() | |
| 270 | +} | |
| 271 | + | |
| 272 | +/** 查询学生班主任列表 */ | |
| 273 | +async function queryStudentTeacherList(pageNo: number, pageSize: number) { | |
| 274 | + if (!props.id) { | |
| 275 | + studentTeacherPagingRef.value?.completeByTotal([], 0) | |
| 276 | + return | |
| 277 | + } | |
| 278 | + try { | |
| 279 | + const data = await getStudentTeacherPage({ studentId: props.id, pageNo, pageSize }) | |
| 280 | + studentTeacherTotal.value = data.total | |
| 281 | + studentTeacherPagingRef.value?.completeByTotal(data.list, data.total) | |
| 282 | + } catch { | |
| 283 | + studentTeacherPagingRef.value?.complete(false) | |
| 284 | + } | |
| 285 | +} | |
| 286 | + | |
| 287 | +/** 打开学生班主任表单 */ | |
| 288 | +function openStudentTeacher(item?: StudentTeacher) { | |
| 289 | + const query = [ | |
| 290 | + `studentId=${props.id}`, | |
| 291 | + item?.id ? `id=${item.id}` : '', | |
| 292 | + ].filter(Boolean).join('&') | |
| 293 | + uni.navigateTo({ url: `/pages-infra/demo/student-teacher/form/index?${query}` }) | |
| 294 | +} | |
| 295 | + | |
| 296 | +/** 删除学生班主任 */ | |
| 297 | +async function removeStudentTeacher(item: StudentTeacher) { | |
| 298 | + try { | |
| 299 | + await dialog.confirm({ title: '提示', msg: '确定要删除该学生班主任吗?' }) | |
| 300 | + } catch { | |
| 301 | + return | |
| 302 | + } | |
| 303 | + await deleteStudentTeacher(item.id!) | |
| 304 | + toast.success('删除成功') | |
| 305 | + studentTeacherPagingRef.value?.reload() | |
| 306 | +} | |
| 307 | + | |
| 308 | +/** 编辑主表 */ | |
| 309 | +function handleEdit() { | |
| 310 | + uni.navigateTo({ url: `/pages-infra/demo/form/index?id=${props.id}` }) | |
| 311 | +} | |
| 312 | + | |
| 313 | +/** 删除主表 */ | |
| 314 | +async function handleDelete() { | |
| 315 | + if (!props.id) { | |
| 316 | + return | |
| 317 | + } | |
| 318 | + try { | |
| 319 | + await dialog.confirm({ title: '提示', msg: '确定要删除该学生吗?' }) | |
| 320 | + } catch { | |
| 321 | + return | |
| 322 | + } | |
| 323 | + deleting.value = true | |
| 324 | + try { | |
| 325 | + await deleteStudent(props.id) | |
| 326 | + toast.success('删除成功') | |
| 327 | + uni.$emit('infra:demo:reload') | |
| 328 | + delay(handleBack) | |
| 329 | + } finally { | |
| 330 | + deleting.value = false | |
| 331 | + } | |
| 332 | +} | |
| 333 | + | |
| 334 | +/** 进入页面加载(含编辑和子表返回后刷新) */ | |
| 335 | +onShow(() => { | |
| 336 | + loadMain() | |
| 337 | + nextTick(() => { | |
| 338 | + studentContactPagingRef.value?.reload() | |
| 339 | + studentTeacherPagingRef.value?.reload() | |
| 340 | + }) | |
| 341 | +}) | |
| 342 | +</script> | ... | ... |
yudao-module-infra/src/test/resources/codegen/vue3_admin_uniapp_master_erp/vue/form/index
0 → 100644
| 1 | +<template> | |
| 2 | + <view class="yd-page-container"> | |
| 3 | + <!-- 顶部导航栏 --> | |
| 4 | + <wd-navbar | |
| 5 | + :title="getTitle" | |
| 6 | + left-arrow placeholder safe-area-inset-top fixed | |
| 7 | + @click-left="handleBack" | |
| 8 | + /> | |
| 9 | + | |
| 10 | + <!-- 表单区域 --> | |
| 11 | + <view> | |
| 12 | + <wd-form ref="formRef" :model="formData" :schema="formSchema"> | |
| 13 | + <wd-cell-group border> | |
| 14 | + <wd-form-item title="名字" title-width="180rpx" prop="name"> | |
| 15 | + <wd-input | |
| 16 | + v-model="formData.name" | |
| 17 | + clearable | |
| 18 | + placeholder="请输入名字" | |
| 19 | + /> | |
| 20 | + </wd-form-item> | |
| 21 | + <wd-form-item title="简介" title-width="180rpx" prop="description"> | |
| 22 | + <wd-textarea | |
| 23 | + v-model="formData.description" | |
| 24 | + clearable | |
| 25 | + placeholder="请输入简介" | |
| 26 | + :maxlength="200" | |
| 27 | + show-word-limit | |
| 28 | + /> | |
| 29 | + </wd-form-item> | |
| 30 | + <wd-form-item | |
| 31 | + title="出生日期" | |
| 32 | + title-width="180rpx" | |
| 33 | + prop="birthday" | |
| 34 | + is-link | |
| 35 | + :value="formData.birthday ? formatDateTime(formData.birthday) : ''" | |
| 36 | + placeholder="请选择出生日期" | |
| 37 | + @click="birthdayVisible = true" | |
| 38 | + /> | |
| 39 | + <wd-datetime-picker | |
| 40 | + v-model:visible="birthdayVisible" | |
| 41 | + :model-value="formData.birthday || Date.now()" | |
| 42 | + type="datetime" | |
| 43 | + @update:model-value="value => formData.birthday = value" | |
| 44 | + /> | |
| 45 | + <yd-form-picker | |
| 46 | + v-model="formData.sex" | |
| 47 | + label="性别" | |
| 48 | + label-width="180rpx" | |
| 49 | + prop="sex" | |
| 50 | + :dict-type="DICT_TYPE.SYSTEM_USER_SEX" | |
| 51 | + placeholder="请选择性别" | |
| 52 | + /> | |
| 53 | + <wd-form-item title="是否有效" title-width="180rpx" prop="enabled" center> | |
| 54 | + <wd-radio-group v-model="formData.enabled" type="button"> | |
| 55 | + <wd-radio | |
| 56 | + v-for="dict in getBoolDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING)" | |
| 57 | + :key="dict.value" | |
| 58 | + :value="dict.value" | |
| 59 | + > | |
| 60 | + {{ dict.label }} | |
| 61 | + </wd-radio> | |
| 62 | + </wd-radio-group> | |
| 63 | + </wd-form-item> | |
| 64 | + <wd-form-item title="头像" title-width="180rpx" prop="avatar"> | |
| 65 | + <yd-upload-img v-model="formData.avatar" directory="infra/demo" /> | |
| 66 | + </wd-form-item> | |
| 67 | + <wd-form-item title="附件" title-width="180rpx" prop="video"> | |
| 68 | + <yd-upload-file v-model="formData.video" :limit="1" directory="infra/demo" /> | |
| 69 | + </wd-form-item> | |
| 70 | + <wd-form-item title="备注" title-width="180rpx" prop="memo"> | |
| 71 | + <wd-textarea | |
| 72 | + v-model="formData.memo" | |
| 73 | + clearable | |
| 74 | + placeholder="请输入备注" | |
| 75 | + :maxlength="2000" | |
| 76 | + show-word-limit | |
| 77 | + /> | |
| 78 | + </wd-form-item> | |
| 79 | + </wd-cell-group> | |
| 80 | + </wd-form> | |
| 81 | + </view> | |
| 82 | + | |
| 83 | + <!-- 底部保存按钮 --> | |
| 84 | + <view class="yd-detail-footer"> | |
| 85 | + <wd-button | |
| 86 | + type="primary" | |
| 87 | + block | |
| 88 | + :loading="formLoading" | |
| 89 | + @click="handleSubmit" | |
| 90 | + > | |
| 91 | + 保存 | |
| 92 | + </wd-button> | |
| 93 | + </view> | |
| 94 | + </view> | |
| 95 | +</template> | |
| 96 | + | |
| 97 | +<script lang="ts" setup> | |
| 98 | +import type { FormInstance } from '@wot-ui/ui/components/wd-form/types' | |
| 99 | +import type { | |
| 100 | + Student, | |
| 101 | +} from '@/api/infra/demo' | |
| 102 | +import { computed, onMounted, ref } from 'vue' | |
| 103 | +import { useToast } from '@wot-ui/ui/components/wd-toast' | |
| 104 | +import { createStudent, getStudent, updateStudent } from '@/api/infra/demo' | |
| 105 | +import { getBoolDictOptions } from '@/hooks/useDict' | |
| 106 | +import { delay, navigateBackPlus } from '@/utils' | |
| 107 | +import { DICT_TYPE } from '@/utils/constants' | |
| 108 | +import { formatDateTime } from '@/utils/date' | |
| 109 | +import { createFormSchema } from '@/utils/wot' | |
| 110 | + | |
| 111 | +const props = defineProps<{ | |
| 112 | + id?: number | |
| 113 | +}>() | |
| 114 | + | |
| 115 | +definePage({ | |
| 116 | + style: { | |
| 117 | + navigationBarTitleText: '', | |
| 118 | + navigationStyle: 'custom', | |
| 119 | + }, | |
| 120 | +}) | |
| 121 | + | |
| 122 | +const toast = useToast() | |
| 123 | +const getTitle = computed(() => props.id ? '编辑学生' : '新增学生') | |
| 124 | +const formLoading = ref(false) // 表单提交状态 | |
| 125 | +const formData = ref<Partial<Student>>({ | |
| 126 | + id: undefined, | |
| 127 | + name: '', | |
| 128 | + description: '', | |
| 129 | + birthday: undefined, | |
| 130 | + sex: undefined, | |
| 131 | + enabled: undefined, | |
| 132 | + avatar: '', | |
| 133 | + video: '', | |
| 134 | + memo: '', | |
| 135 | +}) // 表单数据 | |
| 136 | +const birthdayVisible = ref(false) // 出生日期选择器状态 | |
| 137 | +const formSchema = createFormSchema({ | |
| 138 | + name: [{ required: true, message: '名字不能为空' }], | |
| 139 | + description: [{ required: true, message: '简介不能为空' }], | |
| 140 | + birthday: [{ required: true, message: '出生日期不能为空' }], | |
| 141 | + sex: [{ required: true, message: '性别不能为空' }], | |
| 142 | + enabled: [{ required: true, message: '是否有效不能为空' }], | |
| 143 | + avatar: [{ required: true, message: '头像不能为空' }], | |
| 144 | + video: [{ required: true, message: '附件不能为空' }], | |
| 145 | + memo: [{ required: true, message: '备注不能为空' }], | |
| 146 | +}) // 表单校验规则 | |
| 147 | +const formRef = ref<FormInstance>() // 表单组件引用 | |
| 148 | + | |
| 149 | +/** 返回上一页 */ | |
| 150 | +function handleBack() { | |
| 151 | + navigateBackPlus('/pages-infra/demo/index') | |
| 152 | +} | |
| 153 | + | |
| 154 | +/** 加载学生详情 */ | |
| 155 | +async function getDetail() { | |
| 156 | + if (!props.id) { | |
| 157 | + return | |
| 158 | + } | |
| 159 | + formData.value = await getStudent(props.id) | |
| 160 | +} | |
| 161 | + | |
| 162 | +/** 提交表单 */ | |
| 163 | +async function handleSubmit() { | |
| 164 | + const { valid } = await formRef.value.validate() | |
| 165 | + if (!valid) { | |
| 166 | + return | |
| 167 | + } | |
| 168 | + | |
| 169 | + formLoading.value = true | |
| 170 | + try { | |
| 171 | + const data = formData.value as Student | |
| 172 | + if (props.id) { | |
| 173 | + await updateStudent(data) | |
| 174 | + toast.success('修改成功') | |
| 175 | + } else { | |
| 176 | + await createStudent(data) | |
| 177 | + toast.success('新增成功') | |
| 178 | + } | |
| 179 | + uni.$emit('infra:demo:reload') | |
| 180 | + delay(handleBack) | |
| 181 | + } finally { | |
| 182 | + formLoading.value = false | |
| 183 | + } | |
| 184 | +} | |
| 185 | + | |
| 186 | +/** 初始化 */ | |
| 187 | +onMounted(async () => { | |
| 188 | + getDetail() | |
| 189 | +}) | |
| 190 | +</script> | |
| 191 | + | |
| 192 | +<style lang="scss" scoped> | |
| 193 | +</style> | ... | ... |
yudao-module-infra/src/test/resources/codegen/vue3_admin_uniapp_master_erp/vue/index
0 → 100644
| 1 | +<template> | |
| 2 | + <view class="yd-page-container yd-page-container-paging"> | |
| 3 | + <!-- 顶部导航栏 --> | |
| 4 | + <wd-navbar | |
| 5 | + title="学生管理" | |
| 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 | + <z-paging | |
| 15 | + ref="pagingRef" | |
| 16 | + v-model="list" | |
| 17 | + :fixed="false" | |
| 18 | + class="min-h-0 flex-1" | |
| 19 | + :default-page-size="10" | |
| 20 | + :refresher-enabled="true" | |
| 21 | + :inside-more="true" | |
| 22 | + :loading-more-default-as-loading="true" | |
| 23 | + empty-view-text="暂无学生数据" | |
| 24 | + @query="queryList" | |
| 25 | + > | |
| 26 | + <view class="p-24rpx"> | |
| 27 | + <view | |
| 28 | + v-for="item in list" | |
| 29 | + :key="item.id" | |
| 30 | + class="mb-24rpx overflow-hidden rounded-12rpx bg-white shadow-sm" | |
| 31 | + @click="handleDetail(item)" | |
| 32 | + > | |
| 33 | + <view class="p-24rpx"> | |
| 34 | + <view class="mb-16rpx flex items-center justify-between"> | |
| 35 | + <view class="text-32rpx text-[#333] font-semibold"> | |
| 36 | + {{ item.name }} | |
| 37 | + </view> | |
| 38 | + <dict-tag :type="DICT_TYPE.SYSTEM_USER_SEX" :value="item.sex" /> | |
| 39 | + </view> | |
| 40 | + <view class="mb-12rpx flex items-center text-28rpx text-[#666]"> | |
| 41 | + <text class="mr-8rpx text-[#999]">简介:</text> | |
| 42 | + <text class="line-clamp-1">{{ item.description }}</text> | |
| 43 | + </view> | |
| 44 | + <view class="mb-12rpx flex items-center text-28rpx text-[#666]"> | |
| 45 | + <text class="mr-8rpx text-[#999]">出生日期:</text> | |
| 46 | + <text class="line-clamp-1">{{ formatDateTime(item.birthday) || '-' }}</text> | |
| 47 | + </view> | |
| 48 | + <view class="mb-12rpx flex items-center text-28rpx text-[#666]"> | |
| 49 | + <text class="mr-8rpx text-[#999]">是否有效:</text> | |
| 50 | + <dict-tag :type="DICT_TYPE.INFRA_BOOLEAN_STRING" :value="item.enabled" /> | |
| 51 | + </view> | |
| 52 | + <view class="mb-12rpx flex items-center text-28rpx text-[#666]"> | |
| 53 | + <text class="mr-8rpx text-[#999]">头像:</text> | |
| 54 | + <text class="line-clamp-1">{{ item.avatar }}</text> | |
| 55 | + </view> | |
| 56 | + <view class="mb-12rpx flex items-center text-28rpx text-[#666]"> | |
| 57 | + <text class="mr-8rpx text-[#999]">附件:</text> | |
| 58 | + <text class="line-clamp-1">{{ item.video }}</text> | |
| 59 | + </view> | |
| 60 | + <view class="mb-12rpx flex items-center text-28rpx text-[#666]"> | |
| 61 | + <text class="mr-8rpx text-[#999]">备注:</text> | |
| 62 | + <text class="line-clamp-1">{{ item.memo }}</text> | |
| 63 | + </view> | |
| 64 | + <view class="mb-12rpx flex items-center text-28rpx text-[#666]"> | |
| 65 | + <text class="mr-8rpx text-[#999]">创建时间:</text> | |
| 66 | + <text class="line-clamp-1">{{ formatDateTime(item.createTime) || '-' }}</text> | |
| 67 | + </view> | |
| 68 | + </view> | |
| 69 | + </view> | |
| 70 | + </view> | |
| 71 | + </z-paging> | |
| 72 | + | |
| 73 | + <!-- 新增按钮 --> | |
| 74 | + <wd-fab | |
| 75 | + v-if="hasAccessByCodes(['infra:student:create'])" | |
| 76 | + position="right-bottom" | |
| 77 | + type="primary" | |
| 78 | + :expandable="false" | |
| 79 | + @click="handleAdd" | |
| 80 | + /> | |
| 81 | + </view> | |
| 82 | +</template> | |
| 83 | + | |
| 84 | +<script lang="ts" setup> | |
| 85 | +import type { Student } from '@/api/infra/demo' | |
| 86 | +import { onUnload } from '@dcloudio/uni-app' | |
| 87 | +import { onMounted, ref } from 'vue' | |
| 88 | +import { getStudentPage } from '@/api/infra/demo' | |
| 89 | +import { useAccess } from '@/hooks/useAccess' | |
| 90 | +import { navigateBackPlus } from '@/utils' | |
| 91 | +import { DICT_TYPE } from '@/utils/constants' | |
| 92 | +import { formatDateTime } from '@/utils/date' | |
| 93 | +import SearchForm from './components/search-form.vue' | |
| 94 | + | |
| 95 | +definePage({ | |
| 96 | + style: { | |
| 97 | + navigationBarTitleText: '', | |
| 98 | + navigationStyle: 'custom', | |
| 99 | + }, | |
| 100 | +}) | |
| 101 | + | |
| 102 | +const { hasAccessByCodes } = useAccess() | |
| 103 | +const list = ref<Student[]>([]) // 列表数据 | |
| 104 | +const pagingRef = ref<any>() // 分页组件引用 | |
| 105 | +const queryParams = ref<Record<string, any>>({}) // 查询参数 | |
| 106 | + | |
| 107 | +/** 返回上一页 */ | |
| 108 | +function handleBack() { | |
| 109 | + navigateBackPlus() | |
| 110 | +} | |
| 111 | + | |
| 112 | +/** 查询学生列表 */ | |
| 113 | +async function queryList(pageNo: number, pageSize: number) { | |
| 114 | + try { | |
| 115 | + const params = { | |
| 116 | + ...queryParams.value, | |
| 117 | + pageNo, | |
| 118 | + pageSize, | |
| 119 | + } | |
| 120 | + const data = await getStudentPage(params) | |
| 121 | + pagingRef.value?.completeByTotal(data.list, data.total) | |
| 122 | + } catch { | |
| 123 | + pagingRef.value?.complete(false) | |
| 124 | + } | |
| 125 | +} | |
| 126 | + | |
| 127 | +/** 搜索按钮操作 */ | |
| 128 | +function handleQuery(data?: Record<string, any>) { | |
| 129 | + queryParams.value = { ...data } | |
| 130 | + reload() | |
| 131 | +} | |
| 132 | + | |
| 133 | +/** 重置按钮操作 */ | |
| 134 | +function handleReset() { | |
| 135 | + handleQuery() | |
| 136 | +} | |
| 137 | + | |
| 138 | +/** 重新加载 */ | |
| 139 | +function reload() { | |
| 140 | + pagingRef.value?.reload() | |
| 141 | +} | |
| 142 | + | |
| 143 | +/** 新增学生 */ | |
| 144 | +function handleAdd() { | |
| 145 | + uni.navigateTo({ | |
| 146 | + url: '/pages-infra/demo/form/index', | |
| 147 | + }) | |
| 148 | +} | |
| 149 | + | |
| 150 | +/** 查看详情 */ | |
| 151 | +function handleDetail(item: Student) { | |
| 152 | + uni.navigateTo({ | |
| 153 | + url: `/pages-infra/demo/detail/index?id=${item.id}`, | |
| 154 | + }) | |
| 155 | +} | |
| 156 | + | |
| 157 | +/** 初始化 */ | |
| 158 | +onMounted(() => { | |
| 159 | + uni.$on('infra:demo:reload', reload) | |
| 160 | +}) | |
| 161 | + | |
| 162 | +/** 卸载 */ | |
| 163 | +onUnload(() => { | |
| 164 | + uni.$off('infra:demo:reload', reload) | |
| 165 | +}) | |
| 166 | +</script> | |
| 167 | + | |
| 168 | +<style lang="scss" scoped> | |
| 169 | +</style> | ... | ... |
yudao-module-infra/src/test/resources/codegen/vue3_admin_uniapp_master_erp/vue/search-form
0 → 100644
| 1 | +<template> | |
| 2 | + <!-- 搜索框入口 --> | |
| 3 | + <view @click="visible = true"> | |
| 4 | + <wd-search :placeholder="placeholder" hide-cancel disabled /> | |
| 5 | + </view> | |
| 6 | + | |
| 7 | + <!-- 搜索弹窗 --> | |
| 8 | + <wd-popup v-model="visible" position="top" @close="visible = false"> | |
| 9 | + <view class="yd-search-form-container" :style="{ paddingTop: `${getNavbarHeight()}px` }"> | |
| 10 | + <view class="yd-search-form-item"> | |
| 11 | + <view class="yd-search-form-label"> | |
| 12 | + 名字 | |
| 13 | + </view> | |
| 14 | + <wd-input | |
| 15 | + v-model="formData.name" | |
| 16 | + placeholder="请输入名字" | |
| 17 | + clearable | |
| 18 | + /> | |
| 19 | + </view> | |
| 20 | + <view class="yd-search-form-item"> | |
| 21 | + <view class="yd-search-form-label"> | |
| 22 | + 出生日期 | |
| 23 | + </view> | |
| 24 | + <wd-input | |
| 25 | + v-model="formData.birthday" | |
| 26 | + placeholder="请输入出生日期" | |
| 27 | + clearable | |
| 28 | + /> | |
| 29 | + </view> | |
| 30 | + <yd-search-picker | |
| 31 | + v-model="formData.sex" | |
| 32 | + label="性别" | |
| 33 | + :columns="getIntDictOptions(DICT_TYPE.SYSTEM_USER_SEX)" | |
| 34 | + all-option | |
| 35 | + /> | |
| 36 | + <yd-search-picker | |
| 37 | + v-model="formData.enabled" | |
| 38 | + label="是否有效" | |
| 39 | + :columns="getBoolDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING)" | |
| 40 | + all-option | |
| 41 | + /> | |
| 42 | + <yd-search-date-range v-model="formData.createTime" label="创建时间" /> | |
| 43 | + <view class="yd-search-form-actions"> | |
| 44 | + <wd-button class="flex-1" plain @click="handleReset"> | |
| 45 | + 重置 | |
| 46 | + </wd-button> | |
| 47 | + <wd-button class="flex-1" type="primary" @click="handleSearch"> | |
| 48 | + 搜索 | |
| 49 | + </wd-button> | |
| 50 | + </view> | |
| 51 | + </view> | |
| 52 | + </wd-popup> | |
| 53 | +</template> | |
| 54 | + | |
| 55 | +<script lang="ts" setup> | |
| 56 | +import { computed, reactive, ref } from 'vue' | |
| 57 | +import { getNavbarHeight } from '@/utils' | |
| 58 | +import { formatDate, formatDateRange } from '@/utils/date' | |
| 59 | +import { getDictLabel, getIntDictOptions, getBoolDictOptions } from '@/hooks/useDict' | |
| 60 | +import { DICT_TYPE } from '@/utils/constants' | |
| 61 | + | |
| 62 | +const emit = defineEmits<{ | |
| 63 | + search: [data: Record<string, any>] | |
| 64 | + reset: [] | |
| 65 | +}>() | |
| 66 | + | |
| 67 | +const visible = ref(false) // 搜索弹窗显示状态 | |
| 68 | +const formData = reactive({ | |
| 69 | + name: undefined as string | undefined, | |
| 70 | + birthday: undefined as string | undefined, | |
| 71 | + sex: undefined as number | undefined, | |
| 72 | + enabled: undefined as boolean | undefined, | |
| 73 | + createTime: [undefined, undefined] as [number | undefined, number | undefined], | |
| 74 | +}) // 搜索表单数据 | |
| 75 | + | |
| 76 | +/** 搜索条件 placeholder 拼接 */ | |
| 77 | +const placeholder = computed(() => { | |
| 78 | + const conditions: string[] = [] | |
| 79 | + if (formData.name) { | |
| 80 | + conditions.push(`名字:${formData.name}`) | |
| 81 | + } | |
| 82 | + if (formData.birthday) { | |
| 83 | + conditions.push(`出生日期:${formData.birthday}`) | |
| 84 | + } | |
| 85 | + if (formData.sex !== undefined) { | |
| 86 | + conditions.push(`性别:${getDictLabel(DICT_TYPE.SYSTEM_USER_SEX, formData.sex)}`) | |
| 87 | + } | |
| 88 | + if (formData.enabled !== undefined) { | |
| 89 | + conditions.push(`是否有效:${getDictLabel(DICT_TYPE.INFRA_BOOLEAN_STRING, formData.enabled)}`) | |
| 90 | + } | |
| 91 | + if (formData.createTime?.[0] && formData.createTime?.[1]) { | |
| 92 | + conditions.push(`创建时间:${formatDate(formData.createTime[0])}~${formatDate(formData.createTime[1])}`) | |
| 93 | + } | |
| 94 | + return conditions.length > 0 ? conditions.join(' | ') : '搜索学生' | |
| 95 | +}) | |
| 96 | + | |
| 97 | +/** 搜索按钮操作 */ | |
| 98 | +function handleSearch() { | |
| 99 | + visible.value = false | |
| 100 | + emit('search', { | |
| 101 | + ...formData, | |
| 102 | + createTime: formatDateRange(formData.createTime), | |
| 103 | + }) | |
| 104 | +} | |
| 105 | + | |
| 106 | +/** 重置按钮操作 */ | |
| 107 | +function handleReset() { | |
| 108 | + formData.name = undefined | |
| 109 | + formData.birthday = undefined | |
| 110 | + formData.sex = undefined | |
| 111 | + formData.enabled = undefined | |
| 112 | + formData.createTime = [undefined, undefined] | |
| 113 | + visible.value = false | |
| 114 | + emit('reset') | |
| 115 | +} | |
| 116 | +</script> | ... | ... |
yudao-module-infra/src/test/resources/codegen/vue3_admin_uniapp_master_erp/vue/student-contact/form/index
0 → 100644
| 1 | +<template> | |
| 2 | + <view class="yd-page-container"> | |
| 3 | + <!-- 顶部导航栏 --> | |
| 4 | + <wd-navbar :title="getTitle" left-arrow placeholder safe-area-inset-top fixed @click-left="handleBack" /> | |
| 5 | + | |
| 6 | + <!-- 表单区域 --> | |
| 7 | + <wd-form ref="formRef" :model="formData" :schema="formSchema"> | |
| 8 | + <wd-cell-group border> | |
| 9 | + <wd-form-item title="名字" title-width="180rpx" prop="name"> | |
| 10 | + <wd-input v-model="formData.name" clearable placeholder="请输入名字" /> | |
| 11 | + </wd-form-item> | |
| 12 | + <wd-form-item title="简介" title-width="180rpx" prop="description"> | |
| 13 | + <wd-textarea | |
| 14 | + v-model="formData.description" | |
| 15 | + clearable | |
| 16 | + placeholder="请输入简介" | |
| 17 | + :maxlength="200" | |
| 18 | + /> | |
| 19 | + </wd-form-item> | |
| 20 | + <wd-form-item | |
| 21 | + title="出生日期" | |
| 22 | + title-width="180rpx" | |
| 23 | + prop="birthday" | |
| 24 | + is-link | |
| 25 | + :value="formData.birthday ? formatDateTime(formData.birthday) : ''" | |
| 26 | + placeholder="请选择出生日期" | |
| 27 | + @click="birthdayVisible = true" | |
| 28 | + /> | |
| 29 | + <wd-datetime-picker | |
| 30 | + v-model:visible="birthdayVisible" | |
| 31 | + :model-value="formData.birthday || Date.now()" | |
| 32 | + type="datetime" | |
| 33 | + @update:model-value="value => formData.birthday = value" | |
| 34 | + /> | |
| 35 | + <yd-form-picker | |
| 36 | + v-model="formData.sex" | |
| 37 | + label="性别" | |
| 38 | + label-width="180rpx" | |
| 39 | + prop="sex" | |
| 40 | + :dict-type="DICT_TYPE.SYSTEM_USER_SEX" | |
| 41 | + placeholder="请选择性别" | |
| 42 | + /> | |
| 43 | + <yd-form-picker | |
| 44 | + v-model="formData.enabled" | |
| 45 | + label="是否有效" | |
| 46 | + label-width="180rpx" | |
| 47 | + prop="enabled" | |
| 48 | + :columns="getBoolDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING)" | |
| 49 | + placeholder="请选择是否有效" | |
| 50 | + /> | |
| 51 | + <wd-form-item title="头像" title-width="180rpx" prop="avatar"> | |
| 52 | + <yd-upload-img v-model="formData.avatar" directory="infra/demo" /> | |
| 53 | + </wd-form-item> | |
| 54 | + <wd-form-item title="附件" title-width="180rpx" prop="video"> | |
| 55 | + <yd-upload-file v-model="formData.video" :limit="1" directory="infra/demo" /> | |
| 56 | + </wd-form-item> | |
| 57 | + <wd-form-item title="备注" title-width="180rpx" prop="memo"> | |
| 58 | + <wd-textarea | |
| 59 | + v-model="formData.memo" | |
| 60 | + clearable | |
| 61 | + placeholder="请输入备注" | |
| 62 | + :maxlength="2000" | |
| 63 | + /> | |
| 64 | + </wd-form-item> | |
| 65 | + </wd-cell-group> | |
| 66 | + </wd-form> | |
| 67 | + | |
| 68 | + <!-- 底部保存按钮 --> | |
| 69 | + <view class="yd-detail-footer"> | |
| 70 | + <wd-button type="primary" block :loading="formLoading" @click="handleSubmit"> | |
| 71 | + 保存 | |
| 72 | + </wd-button> | |
| 73 | + </view> | |
| 74 | + </view> | |
| 75 | +</template> | |
| 76 | + | |
| 77 | +<script lang="ts" setup> | |
| 78 | +import type { FormInstance } from '@wot-ui/ui/components/wd-form/types' | |
| 79 | +import type { StudentContact } from '@/api/infra/demo' | |
| 80 | +import { computed, onMounted, ref } from 'vue' | |
| 81 | +import { useToast } from '@wot-ui/ui/components/wd-toast' | |
| 82 | +import { createStudentContact, getStudentContact, updateStudentContact } from '@/api/infra/demo' | |
| 83 | +import { delay, navigateBackPlus } from '@/utils' | |
| 84 | +import { getBoolDictOptions } from '@/hooks/useDict' | |
| 85 | +import { DICT_TYPE } from '@/utils/constants' | |
| 86 | +import { formatDateTime } from '@/utils/date' | |
| 87 | +import { createFormSchema } from '@/utils/wot' | |
| 88 | + | |
| 89 | +const props = defineProps<{ | |
| 90 | + id?: number | |
| 91 | + studentId: number | |
| 92 | +}>() | |
| 93 | + | |
| 94 | +definePage({ | |
| 95 | + style: { navigationBarTitleText: '', navigationStyle: 'custom' }, | |
| 96 | +}) | |
| 97 | + | |
| 98 | +const toast = useToast() | |
| 99 | +const getTitle = computed(() => props.id ? '编辑学生联系人' : '新增学生联系人') | |
| 100 | +const formLoading = ref(false) // 表单提交状态 | |
| 101 | +const formData = ref<Partial<StudentContact>>({ | |
| 102 | + id: undefined, | |
| 103 | + studentId: props.studentId, | |
| 104 | + name: '', | |
| 105 | + description: '', | |
| 106 | + birthday: undefined, | |
| 107 | + sex: undefined, | |
| 108 | + enabled: undefined, | |
| 109 | + avatar: '', | |
| 110 | + video: undefined, | |
| 111 | + memo: '', | |
| 112 | +}) // 表单数据 | |
| 113 | +const birthdayVisible = ref(false) // 出生日期选择器状态 | |
| 114 | +const formSchema = createFormSchema({ | |
| 115 | + studentId: [{ required: true, message: '学生编号不能为空' }], | |
| 116 | + name: [{ required: true, message: '名字不能为空' }], | |
| 117 | + description: [{ required: true, message: '简介不能为空' }], | |
| 118 | + birthday: [{ required: true, message: '出生日期不能为空' }], | |
| 119 | + sex: [{ required: true, message: '性别不能为空' }], | |
| 120 | + enabled: [{ required: true, message: '是否有效不能为空' }], | |
| 121 | + avatar: [{ required: true, message: '头像不能为空' }], | |
| 122 | + memo: [{ required: true, message: '备注不能为空' }], | |
| 123 | +}) // 表单校验规则 | |
| 124 | +const formRef = ref<FormInstance>() // 表单组件引用 | |
| 125 | + | |
| 126 | +/** 返回上一页 */ | |
| 127 | +function handleBack() { | |
| 128 | + navigateBackPlus() | |
| 129 | +} | |
| 130 | + | |
| 131 | +/** 加载学生联系人详情 */ | |
| 132 | +async function getDetail() { | |
| 133 | + if (props.id) { | |
| 134 | + formData.value = await getStudentContact(props.id) | |
| 135 | + } | |
| 136 | +} | |
| 137 | + | |
| 138 | +/** 提交表单 */ | |
| 139 | +async function handleSubmit() { | |
| 140 | + const { valid } = await formRef.value.validate() | |
| 141 | + if (!valid) { | |
| 142 | + return | |
| 143 | + } | |
| 144 | + formLoading.value = true | |
| 145 | + try { | |
| 146 | + const data = { | |
| 147 | + ...formData.value, | |
| 148 | + studentId: props.studentId ?? formData.value.studentId, | |
| 149 | + } as StudentContact | |
| 150 | + if (props.id) { | |
| 151 | + await updateStudentContact(data) | |
| 152 | + toast.success('修改成功') | |
| 153 | + } else { | |
| 154 | + await createStudentContact(data) | |
| 155 | + toast.success('新增成功') | |
| 156 | + } | |
| 157 | + delay(handleBack) | |
| 158 | + } finally { | |
| 159 | + formLoading.value = false | |
| 160 | + } | |
| 161 | +} | |
| 162 | + | |
| 163 | +/** 初始化 */ | |
| 164 | +onMounted(() => { | |
| 165 | + getDetail() | |
| 166 | +}) | |
| 167 | +</script> | ... | ... |
yudao-module-infra/src/test/resources/codegen/vue3_admin_uniapp_master_erp/vue/student-teacher/form/index
0 → 100644
| 1 | +<template> | |
| 2 | + <view class="yd-page-container"> | |
| 3 | + <!-- 顶部导航栏 --> | |
| 4 | + <wd-navbar :title="getTitle" left-arrow placeholder safe-area-inset-top fixed @click-left="handleBack" /> | |
| 5 | + | |
| 6 | + <!-- 表单区域 --> | |
| 7 | + <wd-form ref="formRef" :model="formData" :schema="formSchema"> | |
| 8 | + <wd-cell-group border> | |
| 9 | + <wd-form-item title="名字" title-width="180rpx" prop="name"> | |
| 10 | + <wd-input v-model="formData.name" clearable placeholder="请输入名字" /> | |
| 11 | + </wd-form-item> | |
| 12 | + <wd-form-item title="简介" title-width="180rpx" prop="description"> | |
| 13 | + <wd-textarea | |
| 14 | + v-model="formData.description" | |
| 15 | + clearable | |
| 16 | + placeholder="请输入简介" | |
| 17 | + :maxlength="200" | |
| 18 | + /> | |
| 19 | + </wd-form-item> | |
| 20 | + <wd-form-item | |
| 21 | + title="出生日期" | |
| 22 | + title-width="180rpx" | |
| 23 | + prop="birthday" | |
| 24 | + is-link | |
| 25 | + :value="formData.birthday ? formatDateTime(formData.birthday) : ''" | |
| 26 | + placeholder="请选择出生日期" | |
| 27 | + @click="birthdayVisible = true" | |
| 28 | + /> | |
| 29 | + <wd-datetime-picker | |
| 30 | + v-model:visible="birthdayVisible" | |
| 31 | + :model-value="formData.birthday || Date.now()" | |
| 32 | + type="datetime" | |
| 33 | + @update:model-value="value => formData.birthday = value" | |
| 34 | + /> | |
| 35 | + <yd-form-picker | |
| 36 | + v-model="formData.sex" | |
| 37 | + label="性别" | |
| 38 | + label-width="180rpx" | |
| 39 | + prop="sex" | |
| 40 | + :dict-type="DICT_TYPE.SYSTEM_USER_SEX" | |
| 41 | + placeholder="请选择性别" | |
| 42 | + /> | |
| 43 | + <yd-form-picker | |
| 44 | + v-model="formData.enabled" | |
| 45 | + label="是否有效" | |
| 46 | + label-width="180rpx" | |
| 47 | + prop="enabled" | |
| 48 | + :columns="getBoolDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING)" | |
| 49 | + placeholder="请选择是否有效" | |
| 50 | + /> | |
| 51 | + <wd-form-item title="头像" title-width="180rpx" prop="avatar"> | |
| 52 | + <yd-upload-img v-model="formData.avatar" directory="infra/demo" /> | |
| 53 | + </wd-form-item> | |
| 54 | + <wd-form-item title="附件" title-width="180rpx" prop="video"> | |
| 55 | + <yd-upload-file v-model="formData.video" :limit="1" directory="infra/demo" /> | |
| 56 | + </wd-form-item> | |
| 57 | + <wd-form-item title="备注" title-width="180rpx" prop="memo"> | |
| 58 | + <wd-textarea | |
| 59 | + v-model="formData.memo" | |
| 60 | + clearable | |
| 61 | + placeholder="请输入备注" | |
| 62 | + :maxlength="2000" | |
| 63 | + /> | |
| 64 | + </wd-form-item> | |
| 65 | + </wd-cell-group> | |
| 66 | + </wd-form> | |
| 67 | + | |
| 68 | + <!-- 底部保存按钮 --> | |
| 69 | + <view class="yd-detail-footer"> | |
| 70 | + <wd-button type="primary" block :loading="formLoading" @click="handleSubmit"> | |
| 71 | + 保存 | |
| 72 | + </wd-button> | |
| 73 | + </view> | |
| 74 | + </view> | |
| 75 | +</template> | |
| 76 | + | |
| 77 | +<script lang="ts" setup> | |
| 78 | +import type { FormInstance } from '@wot-ui/ui/components/wd-form/types' | |
| 79 | +import type { StudentTeacher } from '@/api/infra/demo' | |
| 80 | +import { computed, onMounted, ref } from 'vue' | |
| 81 | +import { useToast } from '@wot-ui/ui/components/wd-toast' | |
| 82 | +import { createStudentTeacher, getStudentTeacher, updateStudentTeacher } from '@/api/infra/demo' | |
| 83 | +import { delay, navigateBackPlus } from '@/utils' | |
| 84 | +import { getBoolDictOptions } from '@/hooks/useDict' | |
| 85 | +import { DICT_TYPE } from '@/utils/constants' | |
| 86 | +import { formatDateTime } from '@/utils/date' | |
| 87 | +import { createFormSchema } from '@/utils/wot' | |
| 88 | + | |
| 89 | +const props = defineProps<{ | |
| 90 | + id?: number | |
| 91 | + studentId: number | |
| 92 | +}>() | |
| 93 | + | |
| 94 | +definePage({ | |
| 95 | + style: { navigationBarTitleText: '', navigationStyle: 'custom' }, | |
| 96 | +}) | |
| 97 | + | |
| 98 | +const toast = useToast() | |
| 99 | +const getTitle = computed(() => props.id ? '编辑学生班主任' : '新增学生班主任') | |
| 100 | +const formLoading = ref(false) // 表单提交状态 | |
| 101 | +const formData = ref<Partial<StudentTeacher>>({ | |
| 102 | + id: undefined, | |
| 103 | + studentId: props.studentId, | |
| 104 | + name: '', | |
| 105 | + description: '', | |
| 106 | + birthday: undefined, | |
| 107 | + sex: undefined, | |
| 108 | + enabled: undefined, | |
| 109 | + avatar: '', | |
| 110 | + video: undefined, | |
| 111 | + memo: '', | |
| 112 | +}) // 表单数据 | |
| 113 | +const birthdayVisible = ref(false) // 出生日期选择器状态 | |
| 114 | +const formSchema = createFormSchema({ | |
| 115 | + studentId: [{ required: true, message: '学生编号不能为空' }], | |
| 116 | + name: [{ required: true, message: '名字不能为空' }], | |
| 117 | + description: [{ required: true, message: '简介不能为空' }], | |
| 118 | + birthday: [{ required: true, message: '出生日期不能为空' }], | |
| 119 | + sex: [{ required: true, message: '性别不能为空' }], | |
| 120 | + enabled: [{ required: true, message: '是否有效不能为空' }], | |
| 121 | + avatar: [{ required: true, message: '头像不能为空' }], | |
| 122 | + memo: [{ required: true, message: '备注不能为空' }], | |
| 123 | +}) // 表单校验规则 | |
| 124 | +const formRef = ref<FormInstance>() // 表单组件引用 | |
| 125 | + | |
| 126 | +/** 返回上一页 */ | |
| 127 | +function handleBack() { | |
| 128 | + navigateBackPlus() | |
| 129 | +} | |
| 130 | + | |
| 131 | +/** 加载学生班主任详情 */ | |
| 132 | +async function getDetail() { | |
| 133 | + if (props.id) { | |
| 134 | + formData.value = await getStudentTeacher(props.id) | |
| 135 | + } | |
| 136 | +} | |
| 137 | + | |
| 138 | +/** 提交表单 */ | |
| 139 | +async function handleSubmit() { | |
| 140 | + const { valid } = await formRef.value.validate() | |
| 141 | + if (!valid) { | |
| 142 | + return | |
| 143 | + } | |
| 144 | + formLoading.value = true | |
| 145 | + try { | |
| 146 | + const data = { | |
| 147 | + ...formData.value, | |
| 148 | + studentId: props.studentId ?? formData.value.studentId, | |
| 149 | + } as StudentTeacher | |
| 150 | + if (props.id) { | |
| 151 | + await updateStudentTeacher(data) | |
| 152 | + toast.success('修改成功') | |
| 153 | + } else { | |
| 154 | + await createStudentTeacher(data) | |
| 155 | + toast.success('新增成功') | |
| 156 | + } | |
| 157 | + delay(handleBack) | |
| 158 | + } finally { | |
| 159 | + formLoading.value = false | |
| 160 | + } | |
| 161 | +} | |
| 162 | + | |
| 163 | +/** 初始化 */ | |
| 164 | +onMounted(() => { | |
| 165 | + getDetail() | |
| 166 | +}) | |
| 167 | +</script> | ... | ... |
yudao-module-infra/src/test/resources/codegen/vue3_admin_uniapp_master_erp/xml/InfraStudentMapper
0 → 100644
| 1 | +<?xml version="1.0" encoding="UTF-8"?> | |
| 2 | +<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> | |
| 3 | +<mapper namespace="cn.iocoder.yudao.module.infra.dal.mysql.demo.InfraStudentMapper"> | |
| 4 | + | |
| 5 | + <!-- | |
| 6 | + 一般情况下,尽可能使用 Mapper 进行 CRUD 增删改查即可。 | |
| 7 | + 无法满足的场景,例如说多表关联查询,才使用 XML 编写 SQL。 | |
| 8 | + 代码生成器暂时只生成 Mapper XML 文件本身,更多推荐 MybatisX 快速开发插件来生成查询。 | |
| 9 | + 文档可见:https://www.iocoder.cn/MyBatis/x-plugins/ | |
| 10 | + --> | |
| 11 | + | |
| 12 | +</mapper> | |
| 0 | 13 | \ No newline at end of file | ... | ... |
yudao-module-infra/src/test/resources/codegen/vue3_admin_uniapp_master_inner/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 | +} ] | |
| 0 | 68 | \ No newline at end of file | ... | ... |
yudao-module-infra/src/test/resources/codegen/vue3_admin_uniapp_master_inner/java/ErrorCodeConstants_手动操作
0 → 100644
yudao-module-infra/src/test/resources/codegen/vue3_admin_uniapp_master_inner/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_inner/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 List<InfraStudentContactDO> selectListByStudentId(Long studentId) { | |
| 21 | + return selectList(InfraStudentContactDO::getStudentId, studentId); | |
| 22 | + } | |
| 23 | + | |
| 24 | + default int deleteByStudentId(Long studentId) { | |
| 25 | + return delete(InfraStudentContactDO::getStudentId, studentId); | |
| 26 | + } | |
| 27 | + | |
| 28 | + default int deleteByStudentIds(List<Long> studentIds) { | |
| 29 | + return deleteBatch(InfraStudentContactDO::getStudentId, studentIds); | |
| 30 | + } | |
| 31 | + | |
| 32 | +} | |
| 0 | 33 | \ No newline at end of file | ... | ... |
yudao-module-infra/src/test/resources/codegen/vue3_admin_uniapp_master_inner/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/list-by-student-id") | |
| 109 | + @Operation(summary = "获得学生联系人列表") | |
| 110 | + @Parameter(name = "studentId", description = "学生编号") | |
| 111 | + @PreAuthorize("@ss.hasPermission('infra:student:query')") | |
| 112 | + public CommonResult<List<InfraStudentContactDO>> getStudentContactListByStudentId(@RequestParam("studentId") Long studentId) { | |
| 113 | + return success(studentService.getStudentContactListByStudentId(studentId)); | |
| 114 | + } | |
| 115 | + | |
| 116 | + // ==================== 子表(学生班主任) ==================== | |
| 117 | + | |
| 118 | + @GetMapping("/student-teacher/get-by-student-id") | |
| 119 | + @Operation(summary = "获得学生班主任") | |
| 120 | + @Parameter(name = "studentId", description = "学生编号") | |
| 121 | + @PreAuthorize("@ss.hasPermission('infra:student:query')") | |
| 122 | + public CommonResult<InfraStudentTeacherDO> getStudentTeacherByStudentId(@RequestParam("studentId") Long studentId) { | |
| 123 | + return success(studentService.getStudentTeacherByStudentId(studentId)); | |
| 124 | + } | |
| 125 | + | |
| 126 | +} | |
| 0 | 127 | \ No newline at end of file | ... | ... |
yudao-module-infra/src/test/resources/codegen/vue3_admin_uniapp_master_inner/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_inner/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_inner/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_inner/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_inner/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 | + @Schema(description = "学生联系人列表") | |
| 52 | + private List<InfraStudentContactDO> studentContacts; | |
| 53 | + | |
| 54 | + @Schema(description = "学生班主任") | |
| 55 | + private InfraStudentTeacherDO studentTeacher; | |
| 56 | + | |
| 57 | +} | |
| 0 | 58 | \ No newline at end of file | ... | ... |
yudao-module-infra/src/test/resources/codegen/vue3_admin_uniapp_master_inner/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 studentId 学生编号 | |
| 70 | + * @return 学生联系人列表 | |
| 71 | + */ | |
| 72 | + List<InfraStudentContactDO> getStudentContactListByStudentId(Long studentId); | |
| 73 | + | |
| 74 | + // ==================== 子表(学生班主任) ==================== | |
| 75 | + | |
| 76 | + /** | |
| 77 | + * 获得学生班主任 | |
| 78 | + * | |
| 79 | + * @param studentId 学生编号 | |
| 80 | + * @return 学生班主任 | |
| 81 | + */ | |
| 82 | + InfraStudentTeacherDO getStudentTeacherByStudentId(Long studentId); | |
| 83 | + | |
| 84 | +} | |
| 0 | 85 | \ No newline at end of file | ... | ... |
yudao-module-infra/src/test/resources/codegen/vue3_admin_uniapp_master_inner/java/InfraStudentServiceImpl
0 → 100644
| 1 | +package cn.iocoder.yudao.module.infra.service.demo; | |
| 2 | + | |
| 3 | +import cn.hutool.core.collection.CollUtil; | |
| 4 | +import cn.hutool.core.util.ObjectUtil; | |
| 5 | +import org.springframework.stereotype.Service; | |
| 6 | +import jakarta.annotation.Resource; | |
| 7 | +import org.springframework.validation.annotation.Validated; | |
| 8 | +import org.springframework.transaction.annotation.Transactional; | |
| 9 | + | |
| 10 | +import java.util.*; | |
| 11 | +import cn.iocoder.yudao.module.infra.controller.admin.demo.vo.*; | |
| 12 | +import cn.iocoder.yudao.module.infra.dal.dataobject.demo.InfraStudentDO; | |
| 13 | +import cn.iocoder.yudao.module.infra.dal.dataobject.demo.InfraStudentContactDO; | |
| 14 | +import cn.iocoder.yudao.module.infra.dal.dataobject.demo.InfraStudentTeacherDO; | |
| 15 | +import cn.iocoder.yudao.framework.common.pojo.PageResult; | |
| 16 | +import cn.iocoder.yudao.framework.common.pojo.PageParam; | |
| 17 | +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; | |
| 18 | + | |
| 19 | +import cn.iocoder.yudao.module.infra.dal.mysql.demo.InfraStudentMapper; | |
| 20 | +import cn.iocoder.yudao.module.infra.dal.mysql.demo.InfraStudentContactMapper; | |
| 21 | +import cn.iocoder.yudao.module.infra.dal.mysql.demo.InfraStudentTeacherMapper; | |
| 22 | + | |
| 23 | +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; | |
| 24 | +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; | |
| 25 | +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.diffList; | |
| 26 | +import static cn.iocoder.yudao.module.infra.enums.ErrorCodeConstants.*; | |
| 27 | + | |
| 28 | +/** | |
| 29 | + * 学生 Service 实现类 | |
| 30 | + * | |
| 31 | + * @author 芋道源码 | |
| 32 | + */ | |
| 33 | +@Service | |
| 34 | +@Validated | |
| 35 | +public class InfraStudentServiceImpl implements InfraStudentService { | |
| 36 | + | |
| 37 | + @Resource | |
| 38 | + private InfraStudentMapper studentMapper; | |
| 39 | + @Resource | |
| 40 | + private InfraStudentContactMapper studentContactMapper; | |
| 41 | + @Resource | |
| 42 | + private InfraStudentTeacherMapper studentTeacherMapper; | |
| 43 | + | |
| 44 | + @Override | |
| 45 | + @Transactional(rollbackFor = Exception.class) | |
| 46 | + public Long createStudent(InfraStudentSaveReqVO createReqVO) { | |
| 47 | + // 插入 | |
| 48 | + InfraStudentDO student = BeanUtils.toBean(createReqVO, InfraStudentDO.class); | |
| 49 | + studentMapper.insert(student); | |
| 50 | + | |
| 51 | + | |
| 52 | + // 插入子表 | |
| 53 | + createStudentContactList(student.getId(), createReqVO.getStudentContacts()); | |
| 54 | + createStudentTeacher(student.getId(), createReqVO.getStudentTeacher()); | |
| 55 | + // 返回 | |
| 56 | + return student.getId(); | |
| 57 | + } | |
| 58 | + | |
| 59 | + @Override | |
| 60 | + @Transactional(rollbackFor = Exception.class) | |
| 61 | + public void updateStudent(InfraStudentSaveReqVO updateReqVO) { | |
| 62 | + // 校验存在 | |
| 63 | + validateStudentExists(updateReqVO.getId()); | |
| 64 | + // 更新 | |
| 65 | + InfraStudentDO updateObj = BeanUtils.toBean(updateReqVO, InfraStudentDO.class); | |
| 66 | + studentMapper.updateById(updateObj); | |
| 67 | + | |
| 68 | + // 更新子表 | |
| 69 | + updateStudentContactList(updateReqVO.getId(), updateReqVO.getStudentContacts()); | |
| 70 | + updateStudentTeacher(updateReqVO.getId(), updateReqVO.getStudentTeacher()); | |
| 71 | + } | |
| 72 | + | |
| 73 | + @Override | |
| 74 | + @Transactional(rollbackFor = Exception.class) | |
| 75 | + public void deleteStudent(Long id) { | |
| 76 | + // 校验存在 | |
| 77 | + validateStudentExists(id); | |
| 78 | + // 删除 | |
| 79 | + studentMapper.deleteById(id); | |
| 80 | + | |
| 81 | + // 删除子表 | |
| 82 | + deleteStudentContactByStudentId(id); | |
| 83 | + deleteStudentTeacherByStudentId(id); | |
| 84 | + } | |
| 85 | + | |
| 86 | + @Override | |
| 87 | + @Transactional(rollbackFor = Exception.class) | |
| 88 | + public void deleteStudentListByIds(List<Long> ids) { | |
| 89 | + // 删除 | |
| 90 | + studentMapper.deleteByIds(ids); | |
| 91 | + | |
| 92 | + // 删除子表 | |
| 93 | + deleteStudentContactByStudentIds(ids); | |
| 94 | + deleteStudentTeacherByStudentIds(ids); | |
| 95 | + } | |
| 96 | + | |
| 97 | + | |
| 98 | + private void validateStudentExists(Long id) { | |
| 99 | + if (studentMapper.selectById(id) == null) { | |
| 100 | + throw exception(STUDENT_NOT_EXISTS); | |
| 101 | + } | |
| 102 | + } | |
| 103 | + | |
| 104 | + @Override | |
| 105 | + public InfraStudentDO getStudent(Long id) { | |
| 106 | + return studentMapper.selectById(id); | |
| 107 | + } | |
| 108 | + | |
| 109 | + @Override | |
| 110 | + public PageResult<InfraStudentDO> getStudentPage(InfraStudentPageReqVO pageReqVO) { | |
| 111 | + return studentMapper.selectPage(pageReqVO); | |
| 112 | + } | |
| 113 | + | |
| 114 | + // ==================== 子表(学生联系人) ==================== | |
| 115 | + | |
| 116 | + @Override | |
| 117 | + public List<InfraStudentContactDO> getStudentContactListByStudentId(Long studentId) { | |
| 118 | + return studentContactMapper.selectListByStudentId(studentId); | |
| 119 | + } | |
| 120 | + | |
| 121 | + private void createStudentContactList(Long studentId, List<InfraStudentContactDO> list) { | |
| 122 | + if (CollUtil.isEmpty(list)) { | |
| 123 | + return; | |
| 124 | + } | |
| 125 | + list.forEach(o -> o.setStudentId(studentId).clean()); | |
| 126 | + studentContactMapper.insertBatch(list); | |
| 127 | + } | |
| 128 | + | |
| 129 | + private void updateStudentContactList(Long studentId, List<InfraStudentContactDO> list) { | |
| 130 | + list.forEach(o -> o.setStudentId(studentId).clean()); | |
| 131 | + List<InfraStudentContactDO> oldList = studentContactMapper.selectListByStudentId(studentId); | |
| 132 | + List<List<InfraStudentContactDO>> diffList = diffList(oldList, list, (oldVal, newVal) -> { | |
| 133 | + boolean same = ObjectUtil.equal(oldVal.getId(), newVal.getId()); | |
| 134 | + if (same) { | |
| 135 | + newVal.setId(oldVal.getId()).clean(); // 解决更新情况下:updateTime 不更新 | |
| 136 | + } | |
| 137 | + return same; | |
| 138 | + }); | |
| 139 | + | |
| 140 | + // 第二步,批量添加、修改、删除 | |
| 141 | + if (CollUtil.isNotEmpty(diffList.get(0))) { | |
| 142 | + studentContactMapper.insertBatch(diffList.get(0)); | |
| 143 | + } | |
| 144 | + if (CollUtil.isNotEmpty(diffList.get(1))) { | |
| 145 | + studentContactMapper.updateBatch(diffList.get(1)); | |
| 146 | + } | |
| 147 | + if (CollUtil.isNotEmpty(diffList.get(2))) { | |
| 148 | + studentContactMapper.deleteByIds(convertList(diffList.get(2), InfraStudentContactDO::getId)); | |
| 149 | + } | |
| 150 | + } | |
| 151 | + | |
| 152 | + private void deleteStudentContactByStudentId(Long studentId) { | |
| 153 | + studentContactMapper.deleteByStudentId(studentId); | |
| 154 | + } | |
| 155 | + | |
| 156 | + private void deleteStudentContactByStudentIds(List<Long> studentIds) { | |
| 157 | + studentContactMapper.deleteByStudentIds(studentIds); | |
| 158 | + } | |
| 159 | + | |
| 160 | + // ==================== 子表(学生班主任) ==================== | |
| 161 | + | |
| 162 | + @Override | |
| 163 | + public InfraStudentTeacherDO getStudentTeacherByStudentId(Long studentId) { | |
| 164 | + return studentTeacherMapper.selectByStudentId(studentId); | |
| 165 | + } | |
| 166 | + | |
| 167 | + private void createStudentTeacher(Long studentId, InfraStudentTeacherDO studentTeacher) { | |
| 168 | + if (studentTeacher == null) { | |
| 169 | + return; | |
| 170 | + } | |
| 171 | + studentTeacher.setStudentId(studentId); | |
| 172 | + studentTeacherMapper.insert(studentTeacher); | |
| 173 | + } | |
| 174 | + | |
| 175 | + private void updateStudentTeacher(Long studentId, InfraStudentTeacherDO studentTeacher) { | |
| 176 | + if (studentTeacher == null) { | |
| 177 | + return; | |
| 178 | + } | |
| 179 | + studentTeacher.setStudentId(studentId).clean();// 解决更新情况下:updateTime 不更新 | |
| 180 | + studentTeacherMapper.insertOrUpdate(studentTeacher); | |
| 181 | + } | |
| 182 | + | |
| 183 | + private void deleteStudentTeacherByStudentId(Long studentId) { | |
| 184 | + studentTeacherMapper.deleteByStudentId(studentId); | |
| 185 | + } | |
| 186 | + | |
| 187 | + private void deleteStudentTeacherByStudentIds(List<Long> studentIds) { | |
| 188 | + studentTeacherMapper.deleteByStudentIds(studentIds); | |
| 189 | + } | |
| 190 | + | |
| 191 | +} | |
| 0 | 192 | \ No newline at end of file | ... | ... |
yudao-module-infra/src/test/resources/codegen/vue3_admin_uniapp_master_inner/java/InfraStudentServiceImplTest
0 → 100644
| 1 | +package cn.iocoder.yudao.module.infra.service.demo; | |
| 2 | + | |
| 3 | +import org.junit.jupiter.api.Disabled; | |
| 4 | +import org.junit.jupiter.api.Test; | |
| 5 | + | |
| 6 | +import jakarta.annotation.Resource; | |
| 7 | + | |
| 8 | +import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest; | |
| 9 | + | |
| 10 | +import cn.iocoder.yudao.module.infra.controller.admin.demo.vo.*; | |
| 11 | +import cn.iocoder.yudao.module.infra.dal.dataobject.demo.InfraStudentDO; | |
| 12 | +import cn.iocoder.yudao.module.infra.dal.mysql.demo.InfraStudentMapper; | |
| 13 | +import cn.iocoder.yudao.framework.common.pojo.PageResult; | |
| 14 | + | |
| 15 | +import jakarta.annotation.Resource; | |
| 16 | +import org.springframework.context.annotation.Import; | |
| 17 | +import java.util.*; | |
| 18 | +import java.time.LocalDateTime; | |
| 19 | + | |
| 20 | +import static cn.hutool.core.util.RandomUtil.*; | |
| 21 | +import static cn.iocoder.yudao.module.infra.enums.ErrorCodeConstants.*; | |
| 22 | +import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.*; | |
| 23 | +import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*; | |
| 24 | +import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.*; | |
| 25 | +import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.*; | |
| 26 | +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.*; | |
| 27 | +import static org.junit.jupiter.api.Assertions.*; | |
| 28 | +import static org.mockito.Mockito.*; | |
| 29 | + | |
| 30 | +/** | |
| 31 | + * {@link InfraStudentServiceImpl} 的单元测试类 | |
| 32 | + * | |
| 33 | + * @author 芋道源码 | |
| 34 | + */ | |
| 35 | +@Import(InfraStudentServiceImpl.class) | |
| 36 | +public class InfraStudentServiceImplTest extends BaseDbUnitTest { | |
| 37 | + | |
| 38 | + @Resource | |
| 39 | + private InfraStudentServiceImpl studentService; | |
| 40 | + | |
| 41 | + @Resource | |
| 42 | + private InfraStudentMapper studentMapper; | |
| 43 | + | |
| 44 | + @Test | |
| 45 | + public void testCreateStudent_success() { | |
| 46 | + // 准备参数 | |
| 47 | + InfraStudentSaveReqVO createReqVO = randomPojo(InfraStudentSaveReqVO.class).setId(null); | |
| 48 | + | |
| 49 | + // 调用 | |
| 50 | + Long studentId = studentService.createStudent(createReqVO); | |
| 51 | + // 断言 | |
| 52 | + assertNotNull(studentId); | |
| 53 | + // 校验记录的属性是否正确 | |
| 54 | + InfraStudentDO student = studentMapper.selectById(studentId); | |
| 55 | + assertPojoEquals(createReqVO, student, "id"); | |
| 56 | + } | |
| 57 | + | |
| 58 | + @Test | |
| 59 | + public void testUpdateStudent_success() { | |
| 60 | + // mock 数据 | |
| 61 | + InfraStudentDO dbStudent = randomPojo(InfraStudentDO.class); | |
| 62 | + studentMapper.insert(dbStudent);// @Sql: 先插入出一条存在的数据 | |
| 63 | + // 准备参数 | |
| 64 | + InfraStudentSaveReqVO updateReqVO = randomPojo(InfraStudentSaveReqVO.class, o -> { | |
| 65 | + o.setId(dbStudent.getId()); // 设置更新的 ID | |
| 66 | + }); | |
| 67 | + | |
| 68 | + // 调用 | |
| 69 | + studentService.updateStudent(updateReqVO); | |
| 70 | + // 校验是否更新正确 | |
| 71 | + InfraStudentDO student = studentMapper.selectById(updateReqVO.getId()); // 获取最新的 | |
| 72 | + assertPojoEquals(updateReqVO, student); | |
| 73 | + } | |
| 74 | + | |
| 75 | + @Test | |
| 76 | + public void testUpdateStudent_notExists() { | |
| 77 | + // 准备参数 | |
| 78 | + InfraStudentSaveReqVO updateReqVO = randomPojo(InfraStudentSaveReqVO.class); | |
| 79 | + | |
| 80 | + // 调用, 并断言异常 | |
| 81 | + assertServiceException(() -> studentService.updateStudent(updateReqVO), STUDENT_NOT_EXISTS); | |
| 82 | + } | |
| 83 | + | |
| 84 | + @Test | |
| 85 | + public void testDeleteStudent_success() { | |
| 86 | + // mock 数据 | |
| 87 | + InfraStudentDO dbStudent = randomPojo(InfraStudentDO.class); | |
| 88 | + studentMapper.insert(dbStudent);// @Sql: 先插入出一条存在的数据 | |
| 89 | + // 准备参数 | |
| 90 | + Long id = dbStudent.getId(); | |
| 91 | + | |
| 92 | + // 调用 | |
| 93 | + studentService.deleteStudent(id); | |
| 94 | + // 校验数据不存在了 | |
| 95 | + assertNull(studentMapper.selectById(id)); | |
| 96 | + } | |
| 97 | + | |
| 98 | + @Test | |
| 99 | + public void testDeleteStudent_notExists() { | |
| 100 | + // 准备参数 | |
| 101 | + Long id = randomLongId(); | |
| 102 | + | |
| 103 | + // 调用, 并断言异常 | |
| 104 | + assertServiceException(() -> studentService.deleteStudent(id), STUDENT_NOT_EXISTS); | |
| 105 | + } | |
| 106 | + | |
| 107 | + @Test | |
| 108 | + @Disabled // TODO 请修改 null 为需要的值,然后删除 @Disabled 注解 | |
| 109 | + public void testGetStudentPage() { | |
| 110 | + // mock 数据 | |
| 111 | + InfraStudentDO dbStudent = randomPojo(InfraStudentDO.class, o -> { // 等会查询到 | |
| 112 | + o.setName(null); | |
| 113 | + o.setBirthday(null); | |
| 114 | + o.setSex(null); | |
| 115 | + o.setEnabled(null); | |
| 116 | + o.setCreateTime(null); | |
| 117 | + }); | |
| 118 | + studentMapper.insert(dbStudent); | |
| 119 | + // 测试 name 不匹配 | |
| 120 | + studentMapper.insert(cloneIgnoreId(dbStudent, o -> o.setName(null))); | |
| 121 | + // 测试 birthday 不匹配 | |
| 122 | + studentMapper.insert(cloneIgnoreId(dbStudent, o -> o.setBirthday(null))); | |
| 123 | + // 测试 sex 不匹配 | |
| 124 | + studentMapper.insert(cloneIgnoreId(dbStudent, o -> o.setSex(null))); | |
| 125 | + // 测试 enabled 不匹配 | |
| 126 | + studentMapper.insert(cloneIgnoreId(dbStudent, o -> o.setEnabled(null))); | |
| 127 | + // 测试 createTime 不匹配 | |
| 128 | + studentMapper.insert(cloneIgnoreId(dbStudent, o -> o.setCreateTime(null))); | |
| 129 | + // 准备参数 | |
| 130 | + InfraStudentPageReqVO reqVO = new InfraStudentPageReqVO(); | |
| 131 | + reqVO.setName(null); | |
| 132 | + reqVO.setBirthday(null); | |
| 133 | + reqVO.setSex(null); | |
| 134 | + reqVO.setEnabled(null); | |
| 135 | + reqVO.setCreateTime(buildBetweenTime(2023, 2, 1, 2023, 2, 28)); | |
| 136 | + | |
| 137 | + // 调用 | |
| 138 | + PageResult<InfraStudentDO> pageResult = studentService.getStudentPage(reqVO); | |
| 139 | + // 断言 | |
| 140 | + assertEquals(1, pageResult.getTotal()); | |
| 141 | + assertEquals(1, pageResult.getList().size()); | |
| 142 | + assertPojoEquals(dbStudent, pageResult.getList().get(0)); | |
| 143 | + } | |
| 144 | + | |
| 145 | +} | |
| 0 | 146 | \ No newline at end of file | ... | ... |
yudao-module-infra/src/test/resources/codegen/vue3_admin_uniapp_master_inner/java/InfraStudentTeacherDO
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_teacher") | |
| 16 | +@KeySequence("infra_student_teacher_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 InfraStudentTeacherDO 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_inner/java/InfraStudentTeacherMapper
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.InfraStudentTeacherDO; | |
| 10 | +import org.apache.ibatis.annotations.Mapper; | |
| 11 | + | |
| 12 | +/** | |
| 13 | + * 学生班主任 Mapper | |
| 14 | + * | |
| 15 | + * @author 芋道源码 | |
| 16 | + */ | |
| 17 | +@Mapper | |
| 18 | +public interface InfraStudentTeacherMapper extends BaseMapperX<InfraStudentTeacherDO> { | |
| 19 | + | |
| 20 | + default InfraStudentTeacherDO selectByStudentId(Long studentId) { | |
| 21 | + return selectOne(InfraStudentTeacherDO::getStudentId, studentId); | |
| 22 | + } | |
| 23 | + | |
| 24 | + default int deleteByStudentId(Long studentId) { | |
| 25 | + return delete(InfraStudentTeacherDO::getStudentId, studentId); | |
| 26 | + } | |
| 27 | + | |
| 28 | + default int deleteByStudentIds(List<Long> studentIds) { | |
| 29 | + return deleteBatch(InfraStudentTeacherDO::getStudentId, studentIds); | |
| 30 | + } | |
| 31 | + | |
| 32 | +} | |
| 0 | 33 | \ No newline at end of file | ... | ... |
yudao-module-infra/src/test/resources/codegen/vue3_admin_uniapp_master_inner/sql/h2
0 → 100644
| 1 | +-- 将该建表 SQL 语句,添加到 yudao-module-infra-biz 模块的 test/resources/sql/create_tables.sql 文件里 | |
| 2 | +CREATE TABLE IF NOT EXISTS "infra_student" ( | |
| 3 | + "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY, | |
| 4 | + "name" varchar NOT NULL, | |
| 5 | + "description" varchar NOT NULL, | |
| 6 | + "birthday" varchar NOT NULL, | |
| 7 | + "sex" int NOT NULL, | |
| 8 | + "enabled" bit NOT NULL, | |
| 9 | + "avatar" varchar NOT NULL, | |
| 10 | + "video" varchar NOT NULL, | |
| 11 | + "memo" varchar NOT NULL, | |
| 12 | + "create_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, | |
| 13 | + PRIMARY KEY ("id") | |
| 14 | +) COMMENT '学生表'; | |
| 15 | + | |
| 16 | +-- 将该删表 SQL 语句,添加到 yudao-module-infra-biz 模块的 test/resources/sql/clean.sql 文件里 | |
| 17 | +DELETE FROM "infra_student"; | |
| 0 | 18 | \ No newline at end of file | ... | ... |
yudao-module-infra/src/test/resources/codegen/vue3_admin_uniapp_master_inner/sql/sql
0 → 100644
| 1 | +-- 菜单 SQL | |
| 2 | +INSERT INTO system_menu( | |
| 3 | + name, permission, type, sort, parent_id, | |
| 4 | + path, icon, component, status, component_name | |
| 5 | +) | |
| 6 | +VALUES ( | |
| 7 | + '学生管理', '', 2, 0, 888, | |
| 8 | + 'student', '', 'infra/demo/index', 0, 'InfraStudent' | |
| 9 | +); | |
| 10 | + | |
| 11 | +-- 按钮父菜单ID | |
| 12 | +SELECT @parentId := LAST_INSERT_ID(); | |
| 13 | + | |
| 14 | +-- 按钮 SQL | |
| 15 | +INSERT INTO system_menu( | |
| 16 | + name, permission, type, sort, parent_id, | |
| 17 | + path, icon, component, status | |
| 18 | +) | |
| 19 | +VALUES ( | |
| 20 | + '学生查询', 'infra:student:query', 3, 1, @parentId, | |
| 21 | + '', '', '', 0 | |
| 22 | +); | |
| 23 | +INSERT INTO system_menu( | |
| 24 | + name, permission, type, sort, parent_id, | |
| 25 | + path, icon, component, status | |
| 26 | +) | |
| 27 | +VALUES ( | |
| 28 | + '学生创建', 'infra:student:create', 3, 2, @parentId, | |
| 29 | + '', '', '', 0 | |
| 30 | +); | |
| 31 | +INSERT INTO system_menu( | |
| 32 | + name, permission, type, sort, parent_id, | |
| 33 | + path, icon, component, status | |
| 34 | +) | |
| 35 | +VALUES ( | |
| 36 | + '学生更新', 'infra:student:update', 3, 3, @parentId, | |
| 37 | + '', '', '', 0 | |
| 38 | +); | |
| 39 | +INSERT INTO system_menu( | |
| 40 | + name, permission, type, sort, parent_id, | |
| 41 | + path, icon, component, status | |
| 42 | +) | |
| 43 | +VALUES ( | |
| 44 | + '学生删除', 'infra:student:delete', 3, 4, @parentId, | |
| 45 | + '', '', '', 0 | |
| 46 | +); | |
| 47 | +INSERT INTO system_menu( | |
| 48 | + name, permission, type, sort, parent_id, | |
| 49 | + path, icon, component, status | |
| 50 | +) | |
| 51 | +VALUES ( | |
| 52 | + '学生导出', 'infra:student:export', 3, 5, @parentId, | |
| 53 | + '', '', '', 0 | |
| 54 | +); | |
| 0 | 55 | \ No newline at end of file | ... | ... |
yudao-module-infra/src/test/resources/codegen/vue3_admin_uniapp_master_inner/ts/index
0 → 100644
| 1 | +import type { PageParam, PageResult } from '@/http/types' | |
| 2 | +import { http } from '@/http/http' | |
| 3 | + | |
| 4 | +/** 学生联系人信息 */ | |
| 5 | +export interface StudentContact { | |
| 6 | + id?: number | |
| 7 | + studentId?: number | |
| 8 | + name: string | |
| 9 | + description: string | |
| 10 | + birthday: number | |
| 11 | + sex: number | |
| 12 | + enabled: boolean | |
| 13 | + avatar: string | |
| 14 | + video?: string | |
| 15 | + memo: string | |
| 16 | + createTime?: number | |
| 17 | +} | |
| 18 | + | |
| 19 | +/** 学生班主任信息 */ | |
| 20 | +export interface StudentTeacher { | |
| 21 | + id?: number | |
| 22 | + studentId?: number | |
| 23 | + name: string | |
| 24 | + description: string | |
| 25 | + birthday: number | |
| 26 | + sex: number | |
| 27 | + enabled: boolean | |
| 28 | + avatar: string | |
| 29 | + video?: string | |
| 30 | + memo: string | |
| 31 | + createTime?: number | |
| 32 | +} | |
| 33 | + | |
| 34 | +/** 学生信息 */ | |
| 35 | +export interface Student { | |
| 36 | + id?: number | |
| 37 | + name: string | |
| 38 | + description: string | |
| 39 | + birthday: number | |
| 40 | + sex: number | |
| 41 | + enabled: boolean | |
| 42 | + avatar: string | |
| 43 | + video: string | |
| 44 | + memo: string | |
| 45 | + createTime?: number | |
| 46 | + studentContacts?: StudentContact[] | |
| 47 | + studentTeacher?: StudentTeacher | null | |
| 48 | +} | |
| 49 | + | |
| 50 | +/** 获取学生分页列表 */ | |
| 51 | +export function getStudentPage(params: PageParam) { | |
| 52 | + return http.get<PageResult<Student>>('/infra/student/page', params) | |
| 53 | +} | |
| 54 | + | |
| 55 | +/** 获取学生详情 */ | |
| 56 | +export function getStudent(id: number) { | |
| 57 | + return http.get<Student>(`/infra/student/get?id=${id}`) | |
| 58 | +} | |
| 59 | + | |
| 60 | +/** 创建学生 */ | |
| 61 | +export function createStudent(data: Student) { | |
| 62 | + return http.post<number>('/infra/student/create', data) | |
| 63 | +} | |
| 64 | + | |
| 65 | +/** 更新学生 */ | |
| 66 | +export function updateStudent(data: Student) { | |
| 67 | + return http.put<boolean>('/infra/student/update', data) | |
| 68 | +} | |
| 69 | + | |
| 70 | +/** 删除学生 */ | |
| 71 | +export function deleteStudent(id: number) { | |
| 72 | + return http.delete<boolean>(`/infra/student/delete?id=${id}`) | |
| 73 | +} | |
| 74 | + | |
| 75 | +// ==================== 子表:学生联系人 ==================== | |
| 76 | + | |
| 77 | +/** 获取学生联系人列表 */ | |
| 78 | +export function getStudentContactListByStudentId(studentId: number) { | |
| 79 | + return http.get<StudentContact[]>(`/infra/student/student-contact/list-by-student-id?studentId=${studentId}`) | |
| 80 | +} | |
| 81 | + | |
| 82 | +// ==================== 子表:学生班主任 ==================== | |
| 83 | + | |
| 84 | +/** 获取学生班主任 */ | |
| 85 | +export function getStudentTeacherByStudentId(studentId: number) { | |
| 86 | + return http.get<StudentTeacher | null>(`/infra/student/student-teacher/get-by-student-id?studentId=${studentId}`) | |
| 87 | +} | ... | ... |