mirror of
https://gitee.com/yudaocode/yudao-boot-mini.git
synced 2026-10-06 14:13:14 +08:00
Merge remote-tracking branch 'origin/master-jdk17' into master-jdk17
This commit is contained in:
+14
-28
@@ -19,12 +19,24 @@
|
||||
国外:OpenAI、Ollama、Midjourney、StableDiffusion、Suno
|
||||
</description>
|
||||
<properties>
|
||||
<spring-ai.version>1.1.5</spring-ai.version>
|
||||
<spring-ai.version>1.1.8</spring-ai.version>
|
||||
<!-- https://mvnrepository.com/artifact/com.alibaba.cloud.ai/spring-ai-alibaba -->
|
||||
<alibaba-ai.version>1.1.2.2</alibaba-ai.version>
|
||||
<tinyflow.version>1.2.6</tinyflow.version>
|
||||
</properties>
|
||||
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-bom</artifactId>
|
||||
<version>${spring-ai.version}</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.boot</groupId>
|
||||
@@ -111,38 +123,12 @@
|
||||
<artifactId>spring-ai-starter-model-stability-ai</artifactId>
|
||||
<version>${spring-ai.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<!-- 智谱 GLM -->
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-starter-model-zhipuai</artifactId>
|
||||
<version>${spring-ai.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-starter-model-minimax</artifactId>
|
||||
<version>${spring-ai.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<!-- 通义千问 -->
|
||||
<groupId>com.alibaba.cloud.ai</groupId>
|
||||
<artifactId>spring-ai-alibaba-starter-dashscope</artifactId>
|
||||
<version>${alibaba-ai.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<!-- 文心一言 -->
|
||||
<groupId>org.springaicommunity</groupId>
|
||||
<artifactId>qianfan-spring-boot-starter</artifactId>
|
||||
<version>1.0.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<!-- 月之暗面 -->
|
||||
<groupId>org.springaicommunity</groupId>
|
||||
<artifactId>moonshot-spring-boot-starter</artifactId>
|
||||
<version>1.0.0</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 向量存储:https://db-engines.com/en/ranking/vector+dbms -->
|
||||
<dependency>
|
||||
<!-- Qdrant:https://qdrant.tech/ -->
|
||||
@@ -260,4 +246,4 @@
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
</project>
|
||||
|
||||
+14
@@ -106,6 +106,20 @@ public class AiChatConversationController {
|
||||
conversation -> conversation.setMessageCount(messageCountMap.getOrDefault(conversation.getId(), 0))));
|
||||
}
|
||||
|
||||
@GetMapping("/get")
|
||||
@Operation(summary = "获得对话", description = "用于【对话管理】菜单")
|
||||
@Parameter(name = "id", required = true, description = "对话编号", example = "1024")
|
||||
@PreAuthorize("@ss.hasPermission('ai:chat-conversation:query')")
|
||||
@TransMethodResult
|
||||
public CommonResult<AiChatConversationRespVO> getChatConversation(@RequestParam("id") Long id) {
|
||||
AiChatConversationDO conversation = chatConversationService.getChatConversation(id);
|
||||
AiChatConversationRespVO respVO = BeanUtils.toBean(conversation, AiChatConversationRespVO.class);
|
||||
if (respVO != null) {
|
||||
respVO.setMessageCount(chatMessageService.getChatMessageCount(id));
|
||||
}
|
||||
return success(respVO);
|
||||
}
|
||||
|
||||
@Operation(summary = "管理员删除对话")
|
||||
@DeleteMapping("/delete-by-admin")
|
||||
@Parameter(name = "id", required = true, description = "对话编号", example = "1024")
|
||||
|
||||
+15
-1
@@ -131,7 +131,7 @@ public class AiChatMessageController {
|
||||
|
||||
@GetMapping("/page")
|
||||
@Operation(summary = "获得消息分页", description = "用于【对话管理】菜单")
|
||||
@PreAuthorize("@ss.hasPermission('ai:chat-conversation:query')")
|
||||
@PreAuthorize("@ss.hasPermission('ai:chat-message:query')")
|
||||
public CommonResult<PageResult<AiChatMessageRespVO>> getChatMessagePage(AiChatMessagePageReqVO pageReqVO) {
|
||||
PageResult<AiChatMessageDO> pageResult = chatMessageService.getChatMessagePage(pageReqVO);
|
||||
if (CollUtil.isEmpty(pageResult.getList())) {
|
||||
@@ -145,6 +145,20 @@ public class AiChatMessageController {
|
||||
role -> respVO.setRoleName(role.getName()))));
|
||||
}
|
||||
|
||||
@GetMapping("/get")
|
||||
@Operation(summary = "获得消息", description = "用于【对话管理】菜单")
|
||||
@Parameter(name = "id", required = true, description = "消息编号", example = "1024")
|
||||
@PreAuthorize("@ss.hasPermission('ai:chat-message:query')")
|
||||
public CommonResult<AiChatMessageRespVO> getChatMessage(@RequestParam("id") Long id) {
|
||||
AiChatMessageDO message = chatMessageService.getChatMessage(id);
|
||||
AiChatMessageRespVO respVO = BeanUtils.toBean(message, AiChatMessageRespVO.class);
|
||||
if (respVO != null && respVO.getRoleId() != null) {
|
||||
AiChatRoleDO role = chatRoleService.getChatRole(respVO.getRoleId());
|
||||
respVO.setRoleName(role != null ? role.getName() : null);
|
||||
}
|
||||
return success(respVO);
|
||||
}
|
||||
|
||||
@Operation(summary = "管理员删除消息")
|
||||
@DeleteMapping("/delete-by-admin")
|
||||
@Parameter(name = "id", required = true, description = "消息编号", example = "1024")
|
||||
|
||||
+10
-1
@@ -119,6 +119,15 @@ public class AiImageController {
|
||||
return success(BeanUtils.toBean(pageResult, AiImageRespVO.class));
|
||||
}
|
||||
|
||||
@GetMapping("/get")
|
||||
@Operation(summary = "获得绘画")
|
||||
@Parameter(name = "id", description = "编号", required = true, example = "1024")
|
||||
@PreAuthorize("@ss.hasPermission('ai:image:query')")
|
||||
public CommonResult<AiImageRespVO> getImage(@RequestParam("id") Long id) {
|
||||
AiImageDO image = imageService.getImage(id);
|
||||
return success(BeanUtils.toBean(image, AiImageRespVO.class));
|
||||
}
|
||||
|
||||
@PutMapping("/update")
|
||||
@Operation(summary = "更新绘画")
|
||||
@PreAuthorize("@ss.hasPermission('ai:image:update')")
|
||||
@@ -136,4 +145,4 @@ public class AiImageController {
|
||||
return success(true);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
+9
@@ -46,6 +46,15 @@ public class AiMindMapController {
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@GetMapping("/get")
|
||||
@Operation(summary = "获得思维导图")
|
||||
@Parameter(name = "id", description = "编号", required = true, example = "1024")
|
||||
@PreAuthorize("@ss.hasPermission('ai:mind-map:query')")
|
||||
public CommonResult<AiMindMapRespVO> getMindMap(@RequestParam("id") Long id) {
|
||||
AiMindMapDO mindMap = mindMapService.getMindMap(id);
|
||||
return success(BeanUtils.toBean(mindMap, AiMindMapRespVO.class));
|
||||
}
|
||||
|
||||
@GetMapping("/page")
|
||||
@Operation(summary = "获得思维导图分页")
|
||||
@PreAuthorize("@ss.hasPermission('ai:mind-map:query')")
|
||||
|
||||
+9
@@ -78,6 +78,15 @@ public class AiMusicController {
|
||||
return success(BeanUtils.toBean(pageResult, AiMusicRespVO.class));
|
||||
}
|
||||
|
||||
@GetMapping("/get")
|
||||
@Operation(summary = "获得音乐")
|
||||
@Parameter(name = "id", description = "编号", required = true, example = "1024")
|
||||
@PreAuthorize("@ss.hasPermission('ai:music:query')")
|
||||
public CommonResult<AiMusicRespVO> getMusic(@RequestParam("id") Long id) {
|
||||
AiMusicDO music = musicService.getMusic(id);
|
||||
return success(BeanUtils.toBean(music, AiMusicRespVO.class));
|
||||
}
|
||||
|
||||
@DeleteMapping("/delete")
|
||||
@Operation(summary = "删除音乐")
|
||||
@Parameter(name = "id", description = "编号", required = true)
|
||||
|
||||
+9
@@ -46,6 +46,15 @@ public class AiWriteController {
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@GetMapping("/get")
|
||||
@Operation(summary = "获得写作")
|
||||
@Parameter(name = "id", description = "编号", required = true, example = "1024")
|
||||
@PreAuthorize("@ss.hasPermission('ai:write:query')")
|
||||
public CommonResult<AiWriteRespVO> getWrite(@RequestParam("id") Long id) {
|
||||
AiWriteDO write = writeService.getWrite(id);
|
||||
return success(BeanUtils.toBean(write, AiWriteRespVO.class));
|
||||
}
|
||||
|
||||
@GetMapping("/page")
|
||||
@Operation(summary = "获得写作分页")
|
||||
@PreAuthorize("@ss.hasPermission('ai:write:query')")
|
||||
|
||||
@@ -12,6 +12,7 @@ public interface ErrorCodeConstants {
|
||||
// ========== API 密钥 1-040-000-000 ==========
|
||||
ErrorCode API_KEY_NOT_EXISTS = new ErrorCode(1_040_000_000, "API 密钥不存在");
|
||||
ErrorCode API_KEY_DISABLE = new ErrorCode(1_040_000_001, "API 密钥已禁用!");
|
||||
ErrorCode API_CONFIG_PLACEHOLDER_NOT_RESOLVED = new ErrorCode(1_040_000_002, "AI 配置({})无法解析,请检查环境变量或配置项");
|
||||
|
||||
// ========== API 模型 1-040-001-000 ==========
|
||||
ErrorCode MODEL_NOT_EXISTS = new ErrorCode(1_040_001_000, "模型不存在!");
|
||||
|
||||
+2
-1
@@ -28,6 +28,7 @@ public enum AiPlatformEnum implements ArrayValuable<String> {
|
||||
MINI_MAX("MiniMax", "MiniMax"), // 稀宇科技
|
||||
MOONSHOT("Moonshot", "月之暗面"), // KIMI
|
||||
BAI_CHUAN("BaiChuan", "百川智能"), // 百川智能
|
||||
STEP_FUN("StepFun", "阶跃星辰"), // 阶跃星辰
|
||||
|
||||
// ========== 国外平台 ==========
|
||||
|
||||
@@ -40,7 +41,7 @@ public enum AiPlatformEnum implements ArrayValuable<String> {
|
||||
STABLE_DIFFUSION("StableDiffusion", "StableDiffusion"), // Stability AI
|
||||
MIDJOURNEY("Midjourney", "Midjourney"), // Midjourney
|
||||
SUNO("Suno", "Suno"), // Suno AI
|
||||
GROK("Grok","Grok"), // Grok
|
||||
GROK("Grok", "Grok"), // Grok
|
||||
|
||||
;
|
||||
|
||||
|
||||
+144
-30
@@ -10,10 +10,15 @@ import cn.iocoder.yudao.module.ai.framework.ai.core.model.gemini.GeminiChatModel
|
||||
import cn.iocoder.yudao.module.ai.framework.ai.core.model.grok.GrokChatModel;
|
||||
import cn.iocoder.yudao.module.ai.framework.ai.core.model.hunyuan.HunYuanChatModel;
|
||||
import cn.iocoder.yudao.module.ai.framework.ai.core.model.midjourney.api.MidjourneyApi;
|
||||
import cn.iocoder.yudao.module.ai.framework.ai.core.model.minimax.MiniMaxChatModel;
|
||||
import cn.iocoder.yudao.module.ai.framework.ai.core.model.moonshot.MoonshotChatModel;
|
||||
import cn.iocoder.yudao.module.ai.framework.ai.core.model.siliconflow.SiliconFlowApiConstants;
|
||||
import cn.iocoder.yudao.module.ai.framework.ai.core.model.siliconflow.SiliconFlowChatModel;
|
||||
import cn.iocoder.yudao.module.ai.framework.ai.core.model.stepfun.StepFunChatModel;
|
||||
import cn.iocoder.yudao.module.ai.framework.ai.core.model.suno.api.SunoApi;
|
||||
import cn.iocoder.yudao.module.ai.framework.ai.core.model.xinghuo.XingHuoChatModel;
|
||||
import cn.iocoder.yudao.module.ai.framework.ai.core.model.yiyan.YiYanChatModel;
|
||||
import cn.iocoder.yudao.module.ai.framework.ai.core.model.zhipu.ZhiPuChatModel;
|
||||
import cn.iocoder.yudao.module.ai.framework.ai.core.webserch.AiWebSearchClient;
|
||||
import cn.iocoder.yudao.module.ai.framework.ai.core.webserch.bocha.AiBoChaWebSearchClient;
|
||||
import cn.iocoder.yudao.module.ai.tool.method.PersonService;
|
||||
@@ -44,7 +49,6 @@ import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* 芋道 AI 自动配置
|
||||
@@ -113,13 +117,13 @@ public class AiAutoConfiguration {
|
||||
if (StrUtil.isEmpty(properties.getModel())) {
|
||||
properties.setModel(DouBaoChatModel.MODEL_DEFAULT);
|
||||
}
|
||||
OpenAiChatModel openAiChatModel = OpenAiChatModel.builder()
|
||||
.openAiApi(OpenAiApi.builder()
|
||||
DeepSeekChatModel openAiChatModel = DeepSeekChatModel.builder()
|
||||
.deepSeekApi(DeepSeekApi.builder()
|
||||
.baseUrl(DouBaoChatModel.BASE_URL)
|
||||
.completionsPath(DouBaoChatModel.COMPLETE_PATH)
|
||||
.apiKey(properties.getApiKey())
|
||||
.build())
|
||||
.defaultOptions(OpenAiChatOptions.builder()
|
||||
.defaultOptions(DeepSeekChatOptions.builder()
|
||||
.model(properties.getModel())
|
||||
.temperature(properties.getTemperature())
|
||||
.maxTokens(properties.getMaxTokens())
|
||||
@@ -168,11 +172,8 @@ public class AiAutoConfiguration {
|
||||
if (StrUtil.isEmpty(properties.getModel())) {
|
||||
properties.setModel(HunYuanChatModel.MODEL_DEFAULT);
|
||||
}
|
||||
// 特殊:由于混元大模型不提供 deepseek,而是通过知识引擎,所以需要区分下 URL
|
||||
if (StrUtil.isEmpty(properties.getBaseUrl())) {
|
||||
properties.setBaseUrl(
|
||||
StrUtil.startWithIgnoreCase(properties.getModel(), "deepseek") ? HunYuanChatModel.DEEP_SEEK_BASE_URL
|
||||
: HunYuanChatModel.BASE_URL);
|
||||
properties.setBaseUrl(HunYuanChatModel.BASE_URL);
|
||||
}
|
||||
// 创建 DeepSeekChatModel、HunYuanChatModel 对象
|
||||
DeepSeekChatModel openAiChatModel = DeepSeekChatModel.builder()
|
||||
@@ -203,25 +204,15 @@ public class AiAutoConfiguration {
|
||||
if (StrUtil.isEmpty(properties.getModel())) {
|
||||
properties.setModel(XingHuoChatModel.MODEL_DEFAULT);
|
||||
}
|
||||
OpenAiApi.Builder builder = OpenAiApi.builder()
|
||||
.baseUrl(XingHuoChatModel.BASE_URL_V1)
|
||||
.apiKey(properties.getAppKey() + ":" + properties.getSecretKey());
|
||||
if ("x1".equals(properties.getModel())) {
|
||||
builder.baseUrl(XingHuoChatModel.BASE_URL_V2)
|
||||
.completionsPath(XingHuoChatModel.BASE_COMPLETIONS_PATH_V2);
|
||||
}
|
||||
OpenAiChatModel openAiChatModel = OpenAiChatModel.builder()
|
||||
.openAiApi(builder.build())
|
||||
.defaultOptions(OpenAiChatOptions.builder()
|
||||
return XingHuoChatModel.builder()
|
||||
.apiKey(properties.getApiKey())
|
||||
.options(DeepSeekChatOptions.builder()
|
||||
.model(properties.getModel())
|
||||
.temperature(properties.getTemperature())
|
||||
.maxTokens(properties.getMaxTokens())
|
||||
.topP(properties.getTopP())
|
||||
.build())
|
||||
// TODO @芋艿:星火的 function call 有 bug,会报 ToolResponseMessage must have an id 错误!!!
|
||||
.toolCallingManager(getToolCallingManager())
|
||||
.build();
|
||||
return new XingHuoChatModel(openAiChatModel);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@@ -235,12 +226,13 @@ public class AiAutoConfiguration {
|
||||
if (StrUtil.isEmpty(properties.getModel())) {
|
||||
properties.setModel(BaiChuanChatModel.MODEL_DEFAULT);
|
||||
}
|
||||
OpenAiChatModel openAiChatModel = OpenAiChatModel.builder()
|
||||
.openAiApi(OpenAiApi.builder()
|
||||
DeepSeekChatModel deepSeekChatModel = DeepSeekChatModel.builder()
|
||||
.deepSeekApi(DeepSeekApi.builder()
|
||||
.baseUrl(BaiChuanChatModel.BASE_URL)
|
||||
.apiKey(properties.getApiKey())
|
||||
.completionsPath(BaiChuanChatModel.COMPLETE_PATH)
|
||||
.build())
|
||||
.defaultOptions(OpenAiChatOptions.builder()
|
||||
.defaultOptions(DeepSeekChatOptions.builder()
|
||||
.model(properties.getModel())
|
||||
.temperature(properties.getTemperature())
|
||||
.maxTokens(properties.getMaxTokens())
|
||||
@@ -248,7 +240,123 @@ public class AiAutoConfiguration {
|
||||
.build())
|
||||
.toolCallingManager(getToolCallingManager())
|
||||
.build();
|
||||
return new BaiChuanChatModel(openAiChatModel);
|
||||
return new BaiChuanChatModel(deepSeekChatModel);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnProperty(value = "yudao.ai.yiyan.enable", havingValue = "true")
|
||||
public YiYanChatModel yiYanChatClient(YudaoAiProperties yudaoAiProperties) {
|
||||
YudaoAiProperties.YiYan properties = yudaoAiProperties.getYiyan();
|
||||
return buildYiYanChatClient(properties);
|
||||
}
|
||||
|
||||
public YiYanChatModel buildYiYanChatClient(YudaoAiProperties.YiYan properties) {
|
||||
if (StrUtil.isEmpty(properties.getModel())) {
|
||||
properties.setModel(YiYanChatModel.MODEL_DEFAULT);
|
||||
}
|
||||
return new YiYanChatModel(buildDeepSeekCompatibleChatModel(
|
||||
StrUtil.blankToDefault(properties.getBaseUrl(), YiYanChatModel.BASE_URL),
|
||||
null, properties.getApiKey(), properties.getModel(), properties.getTemperature(),
|
||||
properties.getMaxTokens(), properties.getTopP()));
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnProperty(value = "yudao.ai.zhipu.enable", havingValue = "true")
|
||||
public ZhiPuChatModel zhiPuChatClient(YudaoAiProperties yudaoAiProperties) {
|
||||
YudaoAiProperties.ZhiPu properties = yudaoAiProperties.getZhipu();
|
||||
return buildZhiPuChatClient(properties);
|
||||
}
|
||||
|
||||
public ZhiPuChatModel buildZhiPuChatClient(YudaoAiProperties.ZhiPu properties) {
|
||||
if (StrUtil.isEmpty(properties.getModel())) {
|
||||
properties.setModel(ZhiPuChatModel.MODEL_DEFAULT);
|
||||
}
|
||||
DeepSeekChatModel deepSeekChatModel = DeepSeekChatModel.builder()
|
||||
.deepSeekApi(DeepSeekApi.builder()
|
||||
.baseUrl(StrUtil.blankToDefault(properties.getBaseUrl(), ZhiPuChatModel.BASE_URL))
|
||||
.apiKey(properties.getApiKey())
|
||||
.build())
|
||||
.defaultOptions(DeepSeekChatOptions.builder()
|
||||
.model(properties.getModel())
|
||||
.temperature(properties.getTemperature())
|
||||
.maxTokens(properties.getMaxTokens())
|
||||
.topP(properties.getTopP())
|
||||
.build())
|
||||
.build();
|
||||
return new ZhiPuChatModel(deepSeekChatModel);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnProperty(value = "yudao.ai.minimax.enable", havingValue = "true")
|
||||
public MiniMaxChatModel miniMaxChatClient(YudaoAiProperties yudaoAiProperties) {
|
||||
YudaoAiProperties.MiniMax properties = yudaoAiProperties.getMinimax();
|
||||
return buildMiniMaxChatClient(properties);
|
||||
}
|
||||
|
||||
public MiniMaxChatModel buildMiniMaxChatClient(YudaoAiProperties.MiniMax properties) {
|
||||
if (StrUtil.isEmpty(properties.getModel())) {
|
||||
properties.setModel(MiniMaxChatModel.MODEL_DEFAULT);
|
||||
}
|
||||
return new MiniMaxChatModel(buildDeepSeekCompatibleChatModel(
|
||||
StrUtil.blankToDefault(properties.getBaseUrl(), MiniMaxChatModel.BASE_URL),
|
||||
null, properties.getApiKey(), properties.getModel(), properties.getTemperature(),
|
||||
properties.getMaxTokens(), properties.getTopP()));
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnProperty(value = "yudao.ai.moonshot.enable", havingValue = "true")
|
||||
public MoonshotChatModel moonshotChatClient(YudaoAiProperties yudaoAiProperties) {
|
||||
YudaoAiProperties.Moonshot properties = yudaoAiProperties.getMoonshot();
|
||||
return buildMoonshotChatClient(properties);
|
||||
}
|
||||
|
||||
public MoonshotChatModel buildMoonshotChatClient(YudaoAiProperties.Moonshot properties) {
|
||||
if (StrUtil.isEmpty(properties.getModel())) {
|
||||
properties.setModel(MoonshotChatModel.MODEL_DEFAULT);
|
||||
}
|
||||
return new MoonshotChatModel(buildDeepSeekCompatibleChatModel(
|
||||
StrUtil.blankToDefault(properties.getBaseUrl(), MoonshotChatModel.BASE_URL),
|
||||
MoonshotChatModel.COMPLETE_PATH, properties.getApiKey(), properties.getModel(),
|
||||
properties.getTemperature(), properties.getMaxTokens(), properties.getTopP()));
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnProperty(value = "yudao.ai.stepfun.enable", havingValue = "true")
|
||||
public StepFunChatModel stepFunChatClient(YudaoAiProperties yudaoAiProperties) {
|
||||
YudaoAiProperties.StepFun properties = yudaoAiProperties.getStepfun();
|
||||
return buildStepFunChatClient(properties);
|
||||
}
|
||||
|
||||
public StepFunChatModel buildStepFunChatClient(YudaoAiProperties.StepFun properties) {
|
||||
if (StrUtil.isEmpty(properties.getModel())) {
|
||||
properties.setModel(StepFunChatModel.MODEL_DEFAULT);
|
||||
}
|
||||
return new StepFunChatModel(buildDeepSeekCompatibleChatModel(
|
||||
StrUtil.blankToDefault(properties.getBaseUrl(), StepFunChatModel.BASE_URL),
|
||||
StepFunChatModel.COMPLETE_PATH, properties.getApiKey(), properties.getModel(),
|
||||
properties.getTemperature(), properties.getMaxTokens(), properties.getTopP()));
|
||||
}
|
||||
|
||||
private static DeepSeekChatModel buildDeepSeekCompatibleChatModel(String baseUrl, String completionsPath,
|
||||
String apiKey, String model,
|
||||
Double temperature, Integer maxTokens,
|
||||
Double topP) {
|
||||
DeepSeekApi.Builder apiBuilder = DeepSeekApi.builder()
|
||||
.baseUrl(baseUrl)
|
||||
.apiKey(apiKey);
|
||||
if (StrUtil.isNotEmpty(completionsPath)) {
|
||||
apiBuilder.completionsPath(completionsPath);
|
||||
}
|
||||
return DeepSeekChatModel.builder()
|
||||
.deepSeekApi(apiBuilder.build())
|
||||
.defaultOptions(DeepSeekChatOptions.builder()
|
||||
.model(model)
|
||||
.temperature(temperature)
|
||||
.maxTokens(maxTokens)
|
||||
.topP(topP)
|
||||
.build())
|
||||
.toolCallingManager(getToolCallingManager())
|
||||
.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@@ -264,14 +372,20 @@ public class AiAutoConfiguration {
|
||||
return new SunoApi(yudaoAiProperties.getSuno().getBaseUrl());
|
||||
}
|
||||
|
||||
public ChatModel buildGrokChatClient(YudaoAiProperties.Grok properties) {
|
||||
@Bean
|
||||
@ConditionalOnProperty(value = "yudao.ai.grok.enable", havingValue = "true")
|
||||
public GrokChatModel grokChatClient(YudaoAiProperties yudaoAiProperties) {
|
||||
YudaoAiProperties.Grok properties = yudaoAiProperties.getGrok();
|
||||
return buildGrokChatClient(properties);
|
||||
}
|
||||
|
||||
public GrokChatModel buildGrokChatClient(YudaoAiProperties.Grok properties) {
|
||||
if (StrUtil.isEmpty(properties.getModel())) {
|
||||
properties.setModel(GrokChatModel.MODEL_DEFAULT);
|
||||
}
|
||||
OpenAiChatModel openAiChatModel = OpenAiChatModel.builder()
|
||||
.openAiApi(OpenAiApi.builder()
|
||||
.baseUrl(Optional.ofNullable(properties.getBaseUrl())
|
||||
.orElse(GrokChatModel.BASE_URL))
|
||||
.baseUrl(StrUtil.blankToDefault(properties.getBaseUrl(), GrokChatModel.BASE_URL))
|
||||
.completionsPath(GrokChatModel.COMPLETE_PATH)
|
||||
.apiKey(properties.getApiKey())
|
||||
.build())
|
||||
@@ -283,7 +397,7 @@ public class AiAutoConfiguration {
|
||||
.build())
|
||||
.toolCallingManager(getToolCallingManager())
|
||||
.build();
|
||||
return new DouBaoChatModel(openAiChatModel);
|
||||
return new GrokChatModel(openAiChatModel);
|
||||
}
|
||||
|
||||
// ========== RAG 相关 ==========
|
||||
@@ -320,4 +434,4 @@ public class AiAutoConfiguration {
|
||||
return List.of(ToolCallbacks.from(personService));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
+101
-3
@@ -43,6 +43,36 @@ public class YudaoAiProperties {
|
||||
*/
|
||||
private BaiChuan baichuan;
|
||||
|
||||
/**
|
||||
* 文心一言
|
||||
*/
|
||||
private YiYan yiyan;
|
||||
|
||||
/**
|
||||
* 智谱
|
||||
*/
|
||||
private ZhiPu zhipu;
|
||||
|
||||
/**
|
||||
* MiniMax
|
||||
*/
|
||||
private MiniMax minimax;
|
||||
|
||||
/**
|
||||
* 月之暗面
|
||||
*/
|
||||
private Moonshot moonshot;
|
||||
|
||||
/**
|
||||
* 阶跃星辰
|
||||
*/
|
||||
private StepFun stepfun;
|
||||
|
||||
/**
|
||||
* Grok
|
||||
*/
|
||||
private Grok grok;
|
||||
|
||||
/**
|
||||
* Midjourney 绘图
|
||||
*/
|
||||
@@ -116,9 +146,7 @@ public class YudaoAiProperties {
|
||||
public static class XingHuo {
|
||||
|
||||
private String enable;
|
||||
private String appId;
|
||||
private String appKey;
|
||||
private String secretKey;
|
||||
private String apiKey;
|
||||
|
||||
private String model;
|
||||
private Double temperature;
|
||||
@@ -140,6 +168,76 @@ public class YudaoAiProperties {
|
||||
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class YiYan {
|
||||
|
||||
private String enable;
|
||||
private String baseUrl;
|
||||
private String apiKey;
|
||||
|
||||
private String model;
|
||||
private Double temperature;
|
||||
private Integer maxTokens;
|
||||
private Double topP;
|
||||
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class ZhiPu {
|
||||
|
||||
private String enable;
|
||||
private String baseUrl;
|
||||
private String apiKey;
|
||||
|
||||
private String model;
|
||||
private Double temperature;
|
||||
private Integer maxTokens;
|
||||
private Double topP;
|
||||
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class MiniMax {
|
||||
|
||||
private String enable;
|
||||
private String baseUrl;
|
||||
private String apiKey;
|
||||
|
||||
private String model;
|
||||
private Double temperature;
|
||||
private Integer maxTokens;
|
||||
private Double topP;
|
||||
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class Moonshot {
|
||||
|
||||
private String enable;
|
||||
private String baseUrl;
|
||||
private String apiKey;
|
||||
|
||||
private String model;
|
||||
private Double temperature;
|
||||
private Integer maxTokens;
|
||||
private Double topP;
|
||||
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class StepFun {
|
||||
|
||||
private String enable;
|
||||
private String apiKey;
|
||||
private String baseUrl;
|
||||
|
||||
private String model;
|
||||
private Double temperature;
|
||||
private Integer maxTokens;
|
||||
private Double topP;
|
||||
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class Midjourney {
|
||||
|
||||
|
||||
+68
-152
@@ -1,7 +1,6 @@
|
||||
package cn.iocoder.yudao.module.ai.framework.ai.core.model;
|
||||
|
||||
import cn.hutool.core.io.FileUtil;
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import cn.hutool.core.lang.Singleton;
|
||||
import cn.hutool.core.lang.func.Func0;
|
||||
import cn.hutool.core.util.ArrayUtil;
|
||||
@@ -15,14 +14,21 @@ import cn.iocoder.yudao.module.ai.framework.ai.config.YudaoAiProperties;
|
||||
import cn.iocoder.yudao.module.ai.framework.ai.core.model.baichuan.BaiChuanChatModel;
|
||||
import cn.iocoder.yudao.module.ai.framework.ai.core.model.doubao.DouBaoChatModel;
|
||||
import cn.iocoder.yudao.module.ai.framework.ai.core.model.gemini.GeminiChatModel;
|
||||
import cn.iocoder.yudao.module.ai.framework.ai.core.model.grok.GrokChatModel;
|
||||
import cn.iocoder.yudao.module.ai.framework.ai.core.model.hunyuan.HunYuanChatModel;
|
||||
import cn.iocoder.yudao.module.ai.framework.ai.core.model.midjourney.api.MidjourneyApi;
|
||||
import cn.iocoder.yudao.module.ai.framework.ai.core.model.minimax.MiniMaxChatModel;
|
||||
import cn.iocoder.yudao.module.ai.framework.ai.core.model.moonshot.MoonshotChatModel;
|
||||
import cn.iocoder.yudao.module.ai.framework.ai.core.model.siliconflow.SiliconFlowApiConstants;
|
||||
import cn.iocoder.yudao.module.ai.framework.ai.core.model.siliconflow.SiliconFlowChatModel;
|
||||
import cn.iocoder.yudao.module.ai.framework.ai.core.model.siliconflow.SiliconFlowImageApi;
|
||||
import cn.iocoder.yudao.module.ai.framework.ai.core.model.siliconflow.SiliconFlowImageModel;
|
||||
import cn.iocoder.yudao.module.ai.framework.ai.core.model.stepfun.StepFunChatModel;
|
||||
import cn.iocoder.yudao.module.ai.framework.ai.core.model.suno.api.SunoApi;
|
||||
import cn.iocoder.yudao.module.ai.framework.ai.core.model.xinghuo.XingHuoChatModel;
|
||||
import cn.iocoder.yudao.module.ai.framework.ai.core.model.yiyan.YiYanChatModel;
|
||||
import cn.iocoder.yudao.module.ai.framework.ai.core.model.zhipu.ZhiPuChatModel;
|
||||
import cn.iocoder.yudao.module.ai.util.AiUtils;
|
||||
import com.alibaba.cloud.ai.autoconfigure.dashscope.DashScopeChatAutoConfiguration;
|
||||
import com.alibaba.cloud.ai.autoconfigure.dashscope.DashScopeEmbeddingAutoConfiguration;
|
||||
import com.alibaba.cloud.ai.autoconfigure.dashscope.DashScopeImageAutoConfiguration;
|
||||
@@ -40,18 +46,6 @@ import io.milvus.client.MilvusServiceClient;
|
||||
import io.qdrant.client.QdrantClient;
|
||||
import io.qdrant.client.QdrantGrpcClient;
|
||||
import lombok.SneakyThrows;
|
||||
import org.springaicommunity.moonshot.MoonshotChatModel;
|
||||
import org.springaicommunity.moonshot.MoonshotChatOptions;
|
||||
import org.springaicommunity.moonshot.api.MoonshotApi;
|
||||
import org.springaicommunity.moonshot.autoconfigure.MoonshotChatAutoConfiguration;
|
||||
import org.springaicommunity.qianfan.QianFanChatModel;
|
||||
import org.springaicommunity.qianfan.QianFanEmbeddingModel;
|
||||
import org.springaicommunity.qianfan.QianFanEmbeddingOptions;
|
||||
import org.springaicommunity.qianfan.QianFanImageModel;
|
||||
import org.springaicommunity.qianfan.api.QianFanApi;
|
||||
import org.springaicommunity.qianfan.api.QianFanImageApi;
|
||||
import org.springaicommunity.qianfan.autoconfigure.QianFanChatAutoConfiguration;
|
||||
import org.springaicommunity.qianfan.autoconfigure.QianFanEmbeddingAutoConfiguration;
|
||||
import org.springframework.ai.azure.openai.AzureOpenAiChatModel;
|
||||
import org.springframework.ai.azure.openai.AzureOpenAiEmbeddingModel;
|
||||
import org.springframework.ai.chat.model.ChatModel;
|
||||
@@ -63,27 +57,17 @@ import org.springframework.ai.embedding.BatchingStrategy;
|
||||
import org.springframework.ai.embedding.EmbeddingModel;
|
||||
import org.springframework.ai.embedding.observation.EmbeddingModelObservationConvention;
|
||||
import org.springframework.ai.image.ImageModel;
|
||||
import org.springframework.ai.minimax.MiniMaxChatModel;
|
||||
import org.springframework.ai.minimax.MiniMaxChatOptions;
|
||||
import org.springframework.ai.minimax.MiniMaxEmbeddingModel;
|
||||
import org.springframework.ai.minimax.MiniMaxEmbeddingOptions;
|
||||
import org.springframework.ai.minimax.api.MiniMaxApi;
|
||||
import org.springframework.ai.model.anthropic.autoconfigure.AnthropicChatAutoConfiguration;
|
||||
import org.springframework.ai.model.azure.openai.autoconfigure.AzureOpenAiChatAutoConfiguration;
|
||||
import org.springframework.ai.model.azure.openai.autoconfigure.AzureOpenAiEmbeddingAutoConfiguration;
|
||||
import org.springframework.ai.model.azure.openai.autoconfigure.AzureOpenAiEmbeddingProperties;
|
||||
import org.springframework.ai.model.deepseek.autoconfigure.DeepSeekChatAutoConfiguration;
|
||||
import org.springframework.ai.model.minimax.autoconfigure.MiniMaxChatAutoConfiguration;
|
||||
import org.springframework.ai.model.minimax.autoconfigure.MiniMaxEmbeddingAutoConfiguration;
|
||||
import org.springframework.ai.model.ollama.autoconfigure.OllamaChatAutoConfiguration;
|
||||
import org.springframework.ai.model.openai.autoconfigure.OpenAiChatAutoConfiguration;
|
||||
import org.springframework.ai.model.openai.autoconfigure.OpenAiEmbeddingAutoConfiguration;
|
||||
import org.springframework.ai.model.openai.autoconfigure.OpenAiImageAutoConfiguration;
|
||||
import org.springframework.ai.model.stabilityai.autoconfigure.StabilityAiImageAutoConfiguration;
|
||||
import org.springframework.ai.model.tool.ToolCallingManager;
|
||||
import org.springframework.ai.model.zhipuai.autoconfigure.ZhiPuAiChatAutoConfiguration;
|
||||
import org.springframework.ai.model.zhipuai.autoconfigure.ZhiPuAiEmbeddingAutoConfiguration;
|
||||
import org.springframework.ai.model.zhipuai.autoconfigure.ZhiPuAiImageAutoConfiguration;
|
||||
import org.springframework.ai.ollama.OllamaChatModel;
|
||||
import org.springframework.ai.ollama.OllamaEmbeddingModel;
|
||||
import org.springframework.ai.ollama.api.OllamaApi;
|
||||
@@ -114,13 +98,9 @@ import org.springframework.ai.vectorstore.qdrant.autoconfigure.QdrantVectorStore
|
||||
import org.springframework.ai.vectorstore.redis.RedisVectorStore;
|
||||
import org.springframework.ai.vectorstore.redis.autoconfigure.RedisVectorStoreAutoConfiguration;
|
||||
import org.springframework.ai.vectorstore.redis.autoconfigure.RedisVectorStoreProperties;
|
||||
import org.springframework.ai.zhipuai.*;
|
||||
import org.springframework.ai.zhipuai.api.ZhiPuAiApi;
|
||||
import org.springframework.ai.zhipuai.api.ZhiPuAiImageApi;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
|
||||
import org.springframework.web.client.RestClient;
|
||||
import redis.clients.jedis.JedisPooled;
|
||||
|
||||
import java.io.File;
|
||||
@@ -131,7 +111,6 @@ import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
|
||||
import static org.springframework.ai.retry.RetryUtils.DEFAULT_RETRY_TEMPLATE;
|
||||
|
||||
/**
|
||||
* AI Model 模型工厂的实现类
|
||||
@@ -141,7 +120,9 @@ import static org.springframework.ai.retry.RetryUtils.DEFAULT_RETRY_TEMPLATE;
|
||||
public class AiModelFactoryImpl implements AiModelFactory {
|
||||
|
||||
@Override
|
||||
public ChatModel getOrCreateChatModel(AiPlatformEnum platform, String apiKey, String url) {
|
||||
public ChatModel getOrCreateChatModel(AiPlatformEnum platform, String rawApiKey, String rawUrl) {
|
||||
final String apiKey = resolveSpringPlaceholders(rawApiKey);
|
||||
final String url = resolveSpringPlaceholders(rawUrl);
|
||||
String cacheKey = buildClientCacheKey(ChatModel.class, platform, apiKey, url);
|
||||
return Singleton.get(cacheKey, (Func0<ChatModel>) () -> {
|
||||
// noinspection EnhancedSwitchMigration
|
||||
@@ -164,6 +145,8 @@ public class AiModelFactoryImpl implements AiModelFactory {
|
||||
return buildMiniMaxChatModel(apiKey, url);
|
||||
case MOONSHOT:
|
||||
return buildMoonshotChatModel(apiKey, url);
|
||||
case STEP_FUN:
|
||||
return buildStepFunChatModel(apiKey, url);
|
||||
case XING_HUO:
|
||||
return buildXingHuoChatModel(apiKey);
|
||||
case BAI_CHUAN:
|
||||
@@ -179,7 +162,7 @@ public class AiModelFactoryImpl implements AiModelFactory {
|
||||
case OLLAMA:
|
||||
return buildOllamaChatModel(url);
|
||||
case GROK:
|
||||
return buildGrokChatModel(apiKey,url);
|
||||
return buildGrokChatModel(apiKey, url);
|
||||
default:
|
||||
throw new IllegalArgumentException(StrUtil.format("未知平台({})", platform));
|
||||
}
|
||||
@@ -193,7 +176,7 @@ public class AiModelFactoryImpl implements AiModelFactory {
|
||||
case TONG_YI:
|
||||
return SpringUtil.getBean(DashScopeChatModel.class);
|
||||
case YI_YAN:
|
||||
return SpringUtil.getBean(QianFanChatModel.class);
|
||||
return SpringUtil.getBean(YiYanChatModel.class);
|
||||
case DEEP_SEEK:
|
||||
return SpringUtil.getBean(DeepSeekChatModel.class);
|
||||
case DOU_BAO:
|
||||
@@ -203,11 +186,13 @@ public class AiModelFactoryImpl implements AiModelFactory {
|
||||
case SILICON_FLOW:
|
||||
return SpringUtil.getBean(SiliconFlowChatModel.class);
|
||||
case ZHI_PU:
|
||||
return SpringUtil.getBean(ZhiPuAiChatModel.class);
|
||||
return SpringUtil.getBean(ZhiPuChatModel.class);
|
||||
case MINI_MAX:
|
||||
return SpringUtil.getBean(MiniMaxChatModel.class);
|
||||
case MOONSHOT:
|
||||
return SpringUtil.getBean(MoonshotChatModel.class);
|
||||
case STEP_FUN:
|
||||
return SpringUtil.getBean(StepFunChatModel.class);
|
||||
case XING_HUO:
|
||||
return SpringUtil.getBean(XingHuoChatModel.class);
|
||||
case BAI_CHUAN:
|
||||
@@ -222,6 +207,8 @@ public class AiModelFactoryImpl implements AiModelFactory {
|
||||
return SpringUtil.getBean(GeminiChatModel.class);
|
||||
case OLLAMA:
|
||||
return SpringUtil.getBean(OllamaChatModel.class);
|
||||
case GROK:
|
||||
return SpringUtil.getBean(GrokChatModel.class);
|
||||
default:
|
||||
throw new IllegalArgumentException(StrUtil.format("未知平台({})", platform));
|
||||
}
|
||||
@@ -233,10 +220,6 @@ public class AiModelFactoryImpl implements AiModelFactory {
|
||||
switch (platform) {
|
||||
case TONG_YI:
|
||||
return SpringUtil.getBean(DashScopeImageModel.class);
|
||||
case YI_YAN:
|
||||
return SpringUtil.getBean(QianFanImageModel.class);
|
||||
case ZHI_PU:
|
||||
return SpringUtil.getBean(ZhiPuAiImageModel.class);
|
||||
case SILICON_FLOW:
|
||||
return SpringUtil.getBean(SiliconFlowImageModel.class);
|
||||
case OPENAI:
|
||||
@@ -249,19 +232,17 @@ public class AiModelFactoryImpl implements AiModelFactory {
|
||||
}
|
||||
|
||||
@Override
|
||||
public ImageModel getOrCreateImageModel(AiPlatformEnum platform, String apiKey, String url) {
|
||||
public ImageModel getOrCreateImageModel(AiPlatformEnum platform, String rawApiKey, String rawUrl) {
|
||||
String apiKey = resolveSpringPlaceholders(rawApiKey);
|
||||
String url = resolveSpringPlaceholders(rawUrl);
|
||||
// noinspection EnhancedSwitchMigration
|
||||
switch (platform) {
|
||||
case TONG_YI:
|
||||
return buildTongYiImagesModel(apiKey);
|
||||
case YI_YAN:
|
||||
return buildQianFanImageModel(apiKey);
|
||||
case ZHI_PU:
|
||||
return buildZhiPuAiImageModel(apiKey, url);
|
||||
case OPENAI:
|
||||
return buildOpenAiImageModel(apiKey, url);
|
||||
case SILICON_FLOW:
|
||||
return buildSiliconFlowImageModel(apiKey,url);
|
||||
return buildSiliconFlowImageModel(apiKey, url);
|
||||
case STABLE_DIFFUSION:
|
||||
return buildStabilityAiImageModel(apiKey, url);
|
||||
default:
|
||||
@@ -270,9 +251,11 @@ public class AiModelFactoryImpl implements AiModelFactory {
|
||||
}
|
||||
|
||||
@Override
|
||||
public MidjourneyApi getOrCreateMidjourneyApi(String apiKey, String url) {
|
||||
String cacheKey = buildClientCacheKey(MidjourneyApi.class, AiPlatformEnum.MIDJOURNEY.getPlatform(), apiKey,
|
||||
url);
|
||||
public MidjourneyApi getOrCreateMidjourneyApi(String rawApiKey, String rawUrl) {
|
||||
final String apiKey = resolveSpringPlaceholders(rawApiKey);
|
||||
final String url = resolveSpringPlaceholders(rawUrl);
|
||||
String cacheKey = buildClientCacheKey(MidjourneyApi.class, AiPlatformEnum.MIDJOURNEY.getPlatform(),
|
||||
apiKey, url);
|
||||
return Singleton.get(cacheKey, (Func0<MidjourneyApi>) () -> {
|
||||
YudaoAiProperties.Midjourney properties = SpringUtil.getBean(YudaoAiProperties.class)
|
||||
.getMidjourney();
|
||||
@@ -281,25 +264,23 @@ public class AiModelFactoryImpl implements AiModelFactory {
|
||||
}
|
||||
|
||||
@Override
|
||||
public SunoApi getOrCreateSunoApi(String apiKey, String url) {
|
||||
public SunoApi getOrCreateSunoApi(String rawApiKey, String rawUrl) {
|
||||
final String apiKey = resolveSpringPlaceholders(rawApiKey);
|
||||
final String url = resolveSpringPlaceholders(rawUrl);
|
||||
String cacheKey = buildClientCacheKey(SunoApi.class, AiPlatformEnum.SUNO.getPlatform(), apiKey, url);
|
||||
return Singleton.get(cacheKey, (Func0<SunoApi>) () -> new SunoApi(url));
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("EnhancedSwitchMigration")
|
||||
public EmbeddingModel getOrCreateEmbeddingModel(AiPlatformEnum platform, String apiKey, String url, String model) {
|
||||
public EmbeddingModel getOrCreateEmbeddingModel(AiPlatformEnum platform, String rawApiKey, String rawUrl, String model) {
|
||||
final String apiKey = resolveSpringPlaceholders(rawApiKey);
|
||||
final String url = resolveSpringPlaceholders(rawUrl);
|
||||
String cacheKey = buildClientCacheKey(EmbeddingModel.class, platform, apiKey, url, model);
|
||||
return Singleton.get(cacheKey, (Func0<EmbeddingModel>) () -> {
|
||||
switch (platform) {
|
||||
case TONG_YI:
|
||||
return buildTongYiEmbeddingModel(apiKey, model);
|
||||
case YI_YAN:
|
||||
return buildYiYanEmbeddingModel(apiKey, model);
|
||||
case ZHI_PU:
|
||||
return buildZhiPuEmbeddingModel(apiKey, url, model);
|
||||
case MINI_MAX:
|
||||
return buildMiniMaxEmbeddingModel(apiKey, url, model);
|
||||
case OPENAI:
|
||||
return buildOpenAiEmbeddingModel(apiKey, url, model);
|
||||
case AZURE_OPENAI:
|
||||
@@ -341,6 +322,11 @@ public class AiModelFactoryImpl implements AiModelFactory {
|
||||
return StrUtil.format("{}#{}", clazz.getName(), ArrayUtil.join(params, "_"));
|
||||
}
|
||||
|
||||
private static String resolveSpringPlaceholders(String value) {
|
||||
// yml 配置的占位符由 Spring 自动解析;DB 里保存的 ${xxx} 需要在这里手动解析。
|
||||
return AiUtils.resolveSpringPlaceholders(value);
|
||||
}
|
||||
|
||||
// ========== 各种创建 spring-ai 客户端的方法 ==========
|
||||
|
||||
/**
|
||||
@@ -367,30 +353,10 @@ public class AiModelFactoryImpl implements AiModelFactory {
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 可参考 {@link QianFanChatAutoConfiguration} 的 qianFanChatModel 方法
|
||||
*/
|
||||
private static QianFanChatModel buildYiYanChatModel(String key) {
|
||||
// TODO spring ai qianfan 有 bug,无法使用 https://github.com/spring-ai-community/qianfan/issues/6
|
||||
List<String> keys = StrUtil.split(key, '|');
|
||||
Assert.equals(keys.size(), 2, "YiYanChatClient 的密钥需要 (appKey|secretKey) 格式");
|
||||
String appKey = keys.get(0);
|
||||
String secretKey = keys.get(1);
|
||||
QianFanApi qianFanApi = new QianFanApi(appKey, secretKey);
|
||||
return new QianFanChatModel(qianFanApi);
|
||||
}
|
||||
|
||||
/**
|
||||
* 可参考 {@link QianFanEmbeddingAutoConfiguration} 的 qianFanImageModel 方法
|
||||
*/
|
||||
private QianFanImageModel buildQianFanImageModel(String key) {
|
||||
// TODO spring ai qianfan 有 bug,无法使用 https://github.com/spring-ai-community/qianfan/issues/6
|
||||
List<String> keys = StrUtil.split(key, '|');
|
||||
Assert.equals(keys.size(), 2, "YiYanChatClient 的密钥需要 (appKey|secretKey) 格式");
|
||||
String appKey = keys.get(0);
|
||||
String secretKey = keys.get(1);
|
||||
QianFanImageApi qianFanApi = new QianFanImageApi(appKey, secretKey);
|
||||
return new QianFanImageModel(qianFanApi);
|
||||
private ChatModel buildYiYanChatModel(String apiKey) {
|
||||
YudaoAiProperties.YiYan properties = new YudaoAiProperties.YiYan()
|
||||
.setApiKey(apiKey);
|
||||
return new AiAutoConfiguration().buildYiYanChatClient(properties);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -435,62 +401,47 @@ public class AiModelFactoryImpl implements AiModelFactory {
|
||||
}
|
||||
|
||||
/**
|
||||
* 可参考 {@link ZhiPuAiChatAutoConfiguration} 的 zhiPuAiChatModel 方法
|
||||
* 可参考 {@link AiAutoConfiguration#zhiPuChatClient(YudaoAiProperties)}
|
||||
*/
|
||||
private ZhiPuAiChatModel buildZhiPuChatModel(String apiKey, String url) {
|
||||
ZhiPuAiApi.Builder zhiPuAiApiBuilder = ZhiPuAiApi.builder().apiKey(apiKey);
|
||||
if (StrUtil.isNotEmpty(url)) {
|
||||
zhiPuAiApiBuilder.baseUrl(url);
|
||||
}
|
||||
ZhiPuAiChatOptions options = ZhiPuAiChatOptions.builder().model(ZhiPuAiApi.DEFAULT_CHAT_MODEL).temperature(0.7).build();
|
||||
return new ZhiPuAiChatModel(zhiPuAiApiBuilder.build(), options, getToolCallingManager(), DEFAULT_RETRY_TEMPLATE,
|
||||
getObservationRegistry().getIfAvailable());
|
||||
private ZhiPuChatModel buildZhiPuChatModel(String apiKey, String url) {
|
||||
YudaoAiProperties.ZhiPu properties = new YudaoAiProperties.ZhiPu()
|
||||
.setBaseUrl(url).setApiKey(apiKey);
|
||||
return new AiAutoConfiguration().buildZhiPuChatClient(properties);
|
||||
}
|
||||
|
||||
/**
|
||||
* 可参考 {@link ZhiPuAiImageAutoConfiguration} 的 zhiPuAiImageModel 方法
|
||||
*/
|
||||
private ZhiPuAiImageModel buildZhiPuAiImageModel(String apiKey, String url) {
|
||||
ZhiPuAiImageApi zhiPuAiApi = StrUtil.isEmpty(url) ? new ZhiPuAiImageApi(apiKey)
|
||||
: new ZhiPuAiImageApi(url, apiKey, RestClient.builder());
|
||||
return new ZhiPuAiImageModel(zhiPuAiApi);
|
||||
}
|
||||
|
||||
/**
|
||||
* 可参考 {@link MiniMaxChatAutoConfiguration} 的 miniMaxChatModel 方法
|
||||
* 可参考 {@link AiAutoConfiguration#miniMaxChatClient(YudaoAiProperties)}
|
||||
*/
|
||||
private MiniMaxChatModel buildMiniMaxChatModel(String apiKey, String url) {
|
||||
MiniMaxApi miniMaxApi = StrUtil.isEmpty(url) ? new MiniMaxApi(apiKey)
|
||||
: new MiniMaxApi(url, apiKey);
|
||||
MiniMaxChatOptions options = MiniMaxChatOptions.builder().model(MiniMaxApi.DEFAULT_CHAT_MODEL).temperature(0.7).build();
|
||||
return new MiniMaxChatModel(miniMaxApi, options, getToolCallingManager(), DEFAULT_RETRY_TEMPLATE);
|
||||
YudaoAiProperties.MiniMax properties = new YudaoAiProperties.MiniMax()
|
||||
.setBaseUrl(url).setApiKey(apiKey);
|
||||
return new AiAutoConfiguration().buildMiniMaxChatClient(properties);
|
||||
}
|
||||
|
||||
/**
|
||||
* 可参考 {@link MoonshotChatAutoConfiguration} 的 moonshotChatModel 方法
|
||||
* 可参考 {@link AiAutoConfiguration#moonshotChatClient(YudaoAiProperties)}
|
||||
*/
|
||||
private MoonshotChatModel buildMoonshotChatModel(String apiKey, String url) {
|
||||
MoonshotApi.Builder moonshotApiBuilder = MoonshotApi.builder()
|
||||
.apiKey(apiKey);
|
||||
if (StrUtil.isNotEmpty(url)) {
|
||||
moonshotApiBuilder.baseUrl(url);
|
||||
}
|
||||
MoonshotChatOptions options = MoonshotChatOptions.builder().model(MoonshotApi.DEFAULT_CHAT_MODEL).build();
|
||||
return MoonshotChatModel.builder()
|
||||
.moonshotApi(moonshotApiBuilder.build())
|
||||
.defaultOptions(options)
|
||||
.toolCallingManager(getToolCallingManager())
|
||||
.build();
|
||||
YudaoAiProperties.Moonshot properties = new YudaoAiProperties.Moonshot()
|
||||
.setBaseUrl(url).setApiKey(apiKey);
|
||||
return new AiAutoConfiguration().buildMoonshotChatClient(properties);
|
||||
}
|
||||
|
||||
/**
|
||||
* 可参考 {@link AiAutoConfiguration#stepFunChatClient(YudaoAiProperties)}
|
||||
*/
|
||||
private StepFunChatModel buildStepFunChatModel(String apiKey, String url) {
|
||||
YudaoAiProperties.StepFun properties = new YudaoAiProperties.StepFun()
|
||||
.setBaseUrl(url).setApiKey(apiKey);
|
||||
return new AiAutoConfiguration().buildStepFunChatClient(properties);
|
||||
}
|
||||
|
||||
/**
|
||||
* 可参考 {@link AiAutoConfiguration#xingHuoChatClient(YudaoAiProperties)}
|
||||
*/
|
||||
private static XingHuoChatModel buildXingHuoChatModel(String key) {
|
||||
List<String> keys = StrUtil.split(key, '|');
|
||||
Assert.equals(keys.size(), 2, "XingHuoChatClient 的密钥需要 (appKey|secretKey) 格式");
|
||||
private static XingHuoChatModel buildXingHuoChatModel(String apiKey) {
|
||||
YudaoAiProperties.XingHuo properties = new YudaoAiProperties.XingHuo()
|
||||
.setAppKey(keys.get(0)).setSecretKey(keys.get(1));
|
||||
.setApiKey(apiKey).setModel(XingHuoChatModel.MODEL_DEFAULT);
|
||||
return new AiAutoConfiguration().buildXingHuoChatClient(properties);
|
||||
}
|
||||
|
||||
@@ -590,7 +541,7 @@ public class AiModelFactoryImpl implements AiModelFactory {
|
||||
return new StabilityAiImageModel(stabilityAiApi);
|
||||
}
|
||||
|
||||
private ChatModel buildGrokChatModel(String apiKey,String url) {
|
||||
private GrokChatModel buildGrokChatModel(String apiKey, String url) {
|
||||
YudaoAiProperties.Grok properties = new YudaoAiProperties.Grok()
|
||||
.setBaseUrl(url)
|
||||
.setApiKey(apiKey);
|
||||
@@ -608,41 +559,6 @@ public class AiModelFactoryImpl implements AiModelFactory {
|
||||
return new DashScopeEmbeddingModel(dashScopeApi, MetadataMode.EMBED, dashScopeEmbeddingOptions);
|
||||
}
|
||||
|
||||
/**
|
||||
* 可参考 {@link ZhiPuAiEmbeddingAutoConfiguration} 的 zhiPuAiEmbeddingModel 方法
|
||||
*/
|
||||
private ZhiPuAiEmbeddingModel buildZhiPuEmbeddingModel(String apiKey, String url, String model) {
|
||||
ZhiPuAiApi.Builder zhiPuAiApiBuilder = ZhiPuAiApi.builder().apiKey(apiKey);
|
||||
if (StrUtil.isNotEmpty(url)) {
|
||||
zhiPuAiApiBuilder.baseUrl(url);
|
||||
}
|
||||
ZhiPuAiEmbeddingOptions zhiPuAiEmbeddingOptions = ZhiPuAiEmbeddingOptions.builder().model(model).build();
|
||||
return new ZhiPuAiEmbeddingModel(zhiPuAiApiBuilder.build(), MetadataMode.EMBED, zhiPuAiEmbeddingOptions);
|
||||
}
|
||||
|
||||
/**
|
||||
* 可参考 {@link MiniMaxEmbeddingAutoConfiguration} 的 miniMaxEmbeddingModel 方法
|
||||
*/
|
||||
private EmbeddingModel buildMiniMaxEmbeddingModel(String apiKey, String url, String model) {
|
||||
MiniMaxApi miniMaxApi = StrUtil.isEmpty(url)? new MiniMaxApi(apiKey)
|
||||
: new MiniMaxApi(url, apiKey);
|
||||
MiniMaxEmbeddingOptions miniMaxEmbeddingOptions = MiniMaxEmbeddingOptions.builder().model(model).build();
|
||||
return new MiniMaxEmbeddingModel(miniMaxApi, MetadataMode.EMBED, miniMaxEmbeddingOptions);
|
||||
}
|
||||
|
||||
/**
|
||||
* 可参考 {@link QianFanEmbeddingAutoConfiguration} 的 qianFanEmbeddingModel 方法
|
||||
*/
|
||||
private QianFanEmbeddingModel buildYiYanEmbeddingModel(String key, String model) {
|
||||
List<String> keys = StrUtil.split(key, '|');
|
||||
Assert.equals(keys.size(), 2, "YiYanChatClient 的密钥需要 (appKey|secretKey) 格式");
|
||||
String appKey = keys.get(0);
|
||||
String secretKey = keys.get(1);
|
||||
QianFanApi qianFanApi = new QianFanApi(appKey, secretKey);
|
||||
QianFanEmbeddingOptions qianFanEmbeddingOptions = QianFanEmbeddingOptions.builder().model(model).build();
|
||||
return new QianFanEmbeddingModel(qianFanApi, MetadataMode.EMBED, qianFanEmbeddingOptions);
|
||||
}
|
||||
|
||||
private OllamaEmbeddingModel buildOllamaEmbeddingModel(String url, String model) {
|
||||
OllamaApi ollamaApi = OllamaApi.builder().baseUrl(url).build();
|
||||
OllamaEmbeddingOptions ollamaOptions = OllamaEmbeddingOptions.builder().model(model).build();
|
||||
|
||||
+9
-7
@@ -6,7 +6,7 @@ import org.springframework.ai.chat.model.ChatModel;
|
||||
import org.springframework.ai.chat.model.ChatResponse;
|
||||
import org.springframework.ai.chat.prompt.ChatOptions;
|
||||
import org.springframework.ai.chat.prompt.Prompt;
|
||||
import org.springframework.ai.openai.OpenAiChatModel;
|
||||
import org.springframework.ai.deepseek.DeepSeekChatModel;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
/**
|
||||
@@ -20,26 +20,28 @@ public class BaiChuanChatModel implements ChatModel {
|
||||
|
||||
public static final String BASE_URL = "https://api.baichuan-ai.com";
|
||||
|
||||
public static final String MODEL_DEFAULT = "Baichuan4-Turbo";
|
||||
public static final String COMPLETE_PATH = "/v1/chat/completions";
|
||||
|
||||
public static final String MODEL_DEFAULT = "Baichuan-M3";
|
||||
|
||||
/**
|
||||
* 兼容 OpenAI 接口,进行复用
|
||||
* 兼容 OpenAI 接口,复用 DeepSeek 客户端
|
||||
*/
|
||||
private final OpenAiChatModel openAiChatModel;
|
||||
private final DeepSeekChatModel deepSeekChatModel;
|
||||
|
||||
@Override
|
||||
public ChatResponse call(Prompt prompt) {
|
||||
return openAiChatModel.call(prompt);
|
||||
return deepSeekChatModel.call(prompt);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<ChatResponse> stream(Prompt prompt) {
|
||||
return openAiChatModel.stream(prompt);
|
||||
return deepSeekChatModel.stream(prompt);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChatOptions getDefaultOptions() {
|
||||
return openAiChatModel.getDefaultOptions();
|
||||
return deepSeekChatModel.getDefaultOptions();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+1
-1
@@ -20,7 +20,7 @@ public class DouBaoChatModel implements ChatModel {
|
||||
public static final String BASE_URL = "https://ark.cn-beijing.volces.com/api";
|
||||
public static final String COMPLETE_PATH = "/v3/chat/completions";
|
||||
|
||||
public static final String MODEL_DEFAULT = "doubao-1-5-lite-32k-250115";
|
||||
public static final String MODEL_DEFAULT = "doubao-seed-2-1-turbo-260628";
|
||||
|
||||
/**
|
||||
* 兼容 OpenAI 接口,进行复用
|
||||
|
||||
+1
-1
@@ -19,7 +19,7 @@ public class GrokChatModel implements ChatModel {
|
||||
|
||||
public static final String BASE_URL = "https://api.x.ai";
|
||||
public static final String COMPLETE_PATH = "/v1/chat/completions";
|
||||
public static final String MODEL_DEFAULT = "grok-4-fast-reasoning";
|
||||
public static final String MODEL_DEFAULT = "grok-4.3";
|
||||
|
||||
/**
|
||||
* 兼容 OpenAI 接口,进行复用
|
||||
|
||||
+4
-9
@@ -9,10 +9,9 @@ import org.springframework.ai.chat.prompt.Prompt;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
/**
|
||||
* 腾云混元 {@link ChatModel} 实现类
|
||||
* 腾讯混元 {@link ChatModel} 实现类
|
||||
*
|
||||
* 1. 混元大模型:基于 <a href="https://cloud.tencent.com/document/product/1729/111007">知识引擎原子能力</a> 实现
|
||||
* 2. 知识引擎原子能力:基于 <a href="https://cloud.tencent.com/document/product/1772/115969">知识引擎原子能力</a> 实现
|
||||
* 基于 <a href="https://cloud.tencent.com/document/product/1823/132252">TokenHub OpenAI 兼容接口</a> 实现
|
||||
*
|
||||
* @author fansili
|
||||
*/
|
||||
@@ -20,14 +19,10 @@ import reactor.core.publisher.Flux;
|
||||
@RequiredArgsConstructor
|
||||
public class HunYuanChatModel implements ChatModel {
|
||||
|
||||
public static final String BASE_URL = "https://api.hunyuan.cloud.tencent.com";
|
||||
public static final String BASE_URL = "https://tokenhub.tencentmaas.com";
|
||||
public static final String COMPLETE_PATH = "/v1/chat/completions";
|
||||
|
||||
public static final String MODEL_DEFAULT = "hunyuan-turbo";
|
||||
|
||||
public static final String DEEP_SEEK_BASE_URL = "https://api.lkeap.cloud.tencent.com";
|
||||
|
||||
public static final String DEEP_SEEK_MODEL_DEFAULT = "deepseek-v3";
|
||||
public static final String MODEL_DEFAULT = "hy3-preview";
|
||||
|
||||
/**
|
||||
* 兼容 OpenAI 接口,进行复用
|
||||
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
package cn.iocoder.yudao.module.ai.framework.ai.core.model.minimax;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.ai.chat.model.ChatModel;
|
||||
import org.springframework.ai.chat.model.ChatResponse;
|
||||
import org.springframework.ai.chat.prompt.ChatOptions;
|
||||
import org.springframework.ai.chat.prompt.Prompt;
|
||||
import org.springframework.ai.deepseek.DeepSeekChatModel;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
/**
|
||||
* MiniMax {@link ChatModel} 实现类
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
public class MiniMaxChatModel implements ChatModel {
|
||||
|
||||
public static final String BASE_URL = "https://api.minimaxi.com/v1";
|
||||
|
||||
public static final String MODEL_DEFAULT = "MiniMax-M3";
|
||||
|
||||
/**
|
||||
* 兼容 OpenAI 接口,复用 DeepSeek 客户端
|
||||
*/
|
||||
private final DeepSeekChatModel deepSeekChatModel;
|
||||
|
||||
@Override
|
||||
public ChatResponse call(Prompt prompt) {
|
||||
return deepSeekChatModel.call(prompt);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<ChatResponse> stream(Prompt prompt) {
|
||||
return deepSeekChatModel.stream(prompt);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChatOptions getDefaultOptions() {
|
||||
return deepSeekChatModel.getDefaultOptions();
|
||||
}
|
||||
|
||||
}
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
package cn.iocoder.yudao.module.ai.framework.ai.core.model.moonshot;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.ai.chat.model.ChatModel;
|
||||
import org.springframework.ai.chat.model.ChatResponse;
|
||||
import org.springframework.ai.chat.prompt.ChatOptions;
|
||||
import org.springframework.ai.chat.prompt.Prompt;
|
||||
import org.springframework.ai.deepseek.DeepSeekChatModel;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
/**
|
||||
* 月之暗面 {@link ChatModel} 实现类
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
public class MoonshotChatModel implements ChatModel {
|
||||
|
||||
public static final String BASE_URL = "https://api.moonshot.cn";
|
||||
|
||||
public static final String COMPLETE_PATH = "/v1/chat/completions";
|
||||
|
||||
public static final String MODEL_DEFAULT = "kimi-k2.6";
|
||||
|
||||
/**
|
||||
* 兼容 OpenAI 接口,复用 DeepSeek 客户端
|
||||
*/
|
||||
private final DeepSeekChatModel deepSeekChatModel;
|
||||
|
||||
@Override
|
||||
public ChatResponse call(Prompt prompt) {
|
||||
return deepSeekChatModel.call(prompt);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<ChatResponse> stream(Prompt prompt) {
|
||||
return deepSeekChatModel.stream(prompt);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChatOptions getDefaultOptions() {
|
||||
return deepSeekChatModel.getDefaultOptions();
|
||||
}
|
||||
|
||||
}
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
package cn.iocoder.yudao.module.ai.framework.ai.core.model.stepfun;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.ai.chat.model.ChatModel;
|
||||
import org.springframework.ai.chat.model.ChatResponse;
|
||||
import org.springframework.ai.chat.prompt.ChatOptions;
|
||||
import org.springframework.ai.chat.prompt.Prompt;
|
||||
import org.springframework.ai.deepseek.DeepSeekChatModel;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
/**
|
||||
* 阶跃星辰 {@link ChatModel} 实现类
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
public class StepFunChatModel implements ChatModel {
|
||||
|
||||
public static final String BASE_URL = "https://api.stepfun.com";
|
||||
|
||||
public static final String COMPLETE_PATH = "/v1/chat/completions";
|
||||
|
||||
public static final String MODEL_DEFAULT = "step-3.7-flash";
|
||||
|
||||
/**
|
||||
* 兼容 OpenAI 接口,复用 DeepSeek 客户端
|
||||
*/
|
||||
private final DeepSeekChatModel deepSeekChatModel;
|
||||
|
||||
@Override
|
||||
public ChatResponse call(Prompt prompt) {
|
||||
return deepSeekChatModel.call(prompt);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<ChatResponse> stream(Prompt prompt) {
|
||||
return deepSeekChatModel.stream(prompt);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChatOptions getDefaultOptions() {
|
||||
return deepSeekChatModel.getDefaultOptions();
|
||||
}
|
||||
|
||||
}
|
||||
+119
-13
@@ -1,11 +1,14 @@
|
||||
package cn.iocoder.yudao.module.ai.framework.ai.core.model.xinghuo;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.ai.chat.model.ChatModel;
|
||||
import org.springframework.ai.chat.model.ChatResponse;
|
||||
import org.springframework.ai.chat.prompt.ChatOptions;
|
||||
import org.springframework.ai.chat.prompt.Prompt;
|
||||
import org.springframework.ai.deepseek.DeepSeekChatModel;
|
||||
import org.springframework.ai.deepseek.DeepSeekChatOptions;
|
||||
import org.springframework.ai.deepseek.api.DeepSeekApi;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
/**
|
||||
@@ -14,37 +17,140 @@ import reactor.core.publisher.Flux;
|
||||
* @author fansili
|
||||
*/
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
public class XingHuoChatModel implements ChatModel {
|
||||
|
||||
public static final String BASE_URL_V1 = "https://spark-api-open.xf-yun.com";
|
||||
|
||||
public static final String BASE_URL_V2 = "https://spark-api-open.xf-yun.com";
|
||||
public static final String BASE_COMPLETIONS_PATH_V2 = "/v2/chat/completions";
|
||||
/**
|
||||
* 星火 X2
|
||||
*
|
||||
* @see <a href="https://spark-api-open.xf-yun.com/x2/chat/completions">接口地址</a>
|
||||
*/
|
||||
public static final String MODEL_X2 = "x2";
|
||||
|
||||
/**
|
||||
* 已知模型名列表:x1、4.0Ultra、generalv3.5、max-32k、generalv3、pro-128k、lite
|
||||
* 星火 X2 Flash
|
||||
*
|
||||
* @see <a href="https://spark-api-open.xf-yun.com/agent/v1/chat/completions">接口地址</a>
|
||||
*/
|
||||
public static final String MODEL_DEFAULT = "4.0Ultra";
|
||||
public static final String MODEL_X2_FLASH = "x2-flash";
|
||||
|
||||
private static final String BASE_URL_X2 = "https://spark-api-open.xf-yun.com/x2";
|
||||
|
||||
private static final String BASE_URL_X2_FLASH = "https://spark-api-open.xf-yun.com/agent/v1";
|
||||
|
||||
public static final String MODEL_DEFAULT = MODEL_X2_FLASH;
|
||||
|
||||
private static String getBaseUrl(String model) {
|
||||
if (MODEL_X2_FLASH.equals(model)) {
|
||||
return BASE_URL_X2_FLASH;
|
||||
}
|
||||
return BASE_URL_X2;
|
||||
}
|
||||
|
||||
private final String apiKey;
|
||||
|
||||
private final DeepSeekChatOptions options;
|
||||
|
||||
/**
|
||||
* v1 兼容 OpenAI 接口,进行复用
|
||||
* 兼容 OpenAI 接口,进行复用
|
||||
*/
|
||||
private final ChatModel openAiChatModelV1;
|
||||
private final ChatModel chatModelX2;
|
||||
|
||||
private final ChatModel chatModelX2Flash;
|
||||
|
||||
private XingHuoChatModel(String apiKey, DeepSeekChatOptions options) {
|
||||
this.apiKey = apiKey;
|
||||
this.options = options;
|
||||
this.chatModelX2 = buildChatModel(MODEL_X2);
|
||||
this.chatModelX2Flash = buildChatModel(MODEL_X2_FLASH);
|
||||
}
|
||||
|
||||
public static Builder builder() {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChatResponse call(Prompt prompt) {
|
||||
return openAiChatModelV1.call(prompt);
|
||||
return getChatModel(prompt).call(buildApiPrompt(prompt));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<ChatResponse> stream(Prompt prompt) {
|
||||
return openAiChatModelV1.stream(prompt);
|
||||
return getChatModel(prompt).stream(buildApiPrompt(prompt));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChatOptions getDefaultOptions() {
|
||||
return openAiChatModelV1.getDefaultOptions();
|
||||
return options;
|
||||
}
|
||||
|
||||
private ChatModel getChatModel(Prompt prompt) {
|
||||
String model = options.getModel();
|
||||
ChatOptions options = prompt.getOptions();
|
||||
if (options != null && isBusinessModel(options.getModel())) {
|
||||
model = options.getModel();
|
||||
}
|
||||
return getChatModel(model);
|
||||
}
|
||||
|
||||
private ChatModel getChatModel(String model) {
|
||||
if (MODEL_X2_FLASH.equals(model)) {
|
||||
return chatModelX2Flash;
|
||||
}
|
||||
return chatModelX2;
|
||||
}
|
||||
|
||||
private ChatModel buildChatModel(String model) {
|
||||
return DeepSeekChatModel.builder()
|
||||
.deepSeekApi(DeepSeekApi.builder()
|
||||
.baseUrl(getBaseUrl(model))
|
||||
.apiKey(apiKey)
|
||||
.build())
|
||||
.defaultOptions(new DeepSeekChatOptions.Builder(options).model("spark-x").build())
|
||||
.build();
|
||||
}
|
||||
|
||||
private static Prompt buildApiPrompt(Prompt prompt) {
|
||||
ChatOptions options = prompt.getOptions();
|
||||
if (options == null) {
|
||||
return prompt;
|
||||
}
|
||||
if (!(options instanceof DeepSeekChatOptions)) {
|
||||
return prompt;
|
||||
}
|
||||
return Prompt.builder()
|
||||
.messages(prompt.getInstructions())
|
||||
.chatOptions(new DeepSeekChatOptions.Builder((DeepSeekChatOptions) options).model("spark-x").build())
|
||||
.build();
|
||||
}
|
||||
|
||||
private static boolean isBusinessModel(String model) {
|
||||
return MODEL_X2.equals(model) || MODEL_X2_FLASH.equals(model);
|
||||
}
|
||||
|
||||
public static final class Builder {
|
||||
|
||||
private String apiKey;
|
||||
|
||||
private DeepSeekChatOptions options;
|
||||
|
||||
public Builder apiKey(String apiKey) {
|
||||
this.apiKey = apiKey;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder options(DeepSeekChatOptions options) {
|
||||
this.options = options;
|
||||
return this;
|
||||
}
|
||||
|
||||
public XingHuoChatModel build() {
|
||||
DeepSeekChatOptions options = this.options != null ? this.options : DeepSeekChatOptions.builder().build();
|
||||
if (StrUtil.isEmpty(options.getModel())) {
|
||||
options = new DeepSeekChatOptions.Builder(options).model(MODEL_DEFAULT).build();
|
||||
}
|
||||
return new XingHuoChatModel(apiKey, options);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
package cn.iocoder.yudao.module.ai.framework.ai.core.model.yiyan;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.ai.chat.model.ChatModel;
|
||||
import org.springframework.ai.chat.model.ChatResponse;
|
||||
import org.springframework.ai.chat.prompt.ChatOptions;
|
||||
import org.springframework.ai.chat.prompt.Prompt;
|
||||
import org.springframework.ai.deepseek.DeepSeekChatModel;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
/**
|
||||
* 文心一言 {@link ChatModel} 实现类
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
public class YiYanChatModel implements ChatModel {
|
||||
|
||||
public static final String BASE_URL = "https://qianfan.baidubce.com/v2";
|
||||
|
||||
public static final String MODEL_DEFAULT = "ernie-5.1";
|
||||
|
||||
/**
|
||||
* 兼容 OpenAI 接口,复用 DeepSeek 客户端
|
||||
*/
|
||||
private final DeepSeekChatModel deepSeekChatModel;
|
||||
|
||||
@Override
|
||||
public ChatResponse call(Prompt prompt) {
|
||||
return deepSeekChatModel.call(prompt);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<ChatResponse> stream(Prompt prompt) {
|
||||
return deepSeekChatModel.stream(prompt);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChatOptions getDefaultOptions() {
|
||||
return deepSeekChatModel.getDefaultOptions();
|
||||
}
|
||||
|
||||
}
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
package cn.iocoder.yudao.module.ai.framework.ai.core.model.zhipu;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.ai.chat.model.ChatModel;
|
||||
import org.springframework.ai.chat.model.ChatResponse;
|
||||
import org.springframework.ai.chat.prompt.ChatOptions;
|
||||
import org.springframework.ai.chat.prompt.Prompt;
|
||||
import org.springframework.ai.deepseek.DeepSeekChatModel;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
/**
|
||||
* 智谱 {@link ChatModel} 实现类
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
public class ZhiPuChatModel implements ChatModel {
|
||||
|
||||
public static final String BASE_URL = "https://open.bigmodel.cn/api/paas/v4";
|
||||
|
||||
public static final String MODEL_DEFAULT = "glm-5.2";
|
||||
|
||||
/**
|
||||
* 兼容 OpenAI 接口,复用 DeepSeek 客户端
|
||||
*/
|
||||
private final DeepSeekChatModel deepSeekChatModel;
|
||||
|
||||
@Override
|
||||
public ChatResponse call(Prompt prompt) {
|
||||
return deepSeekChatModel.call(prompt);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<ChatResponse> stream(Prompt prompt) {
|
||||
return deepSeekChatModel.stream(prompt);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChatOptions getDefaultOptions() {
|
||||
return deepSeekChatModel.getDefaultOptions();
|
||||
}
|
||||
|
||||
}
|
||||
+16
@@ -45,6 +45,14 @@ public interface AiChatMessageService {
|
||||
*/
|
||||
List<AiChatMessageDO> getChatMessageListByConversationId(Long conversationId);
|
||||
|
||||
/**
|
||||
* 获得聊天消息
|
||||
*
|
||||
* @param id 消息编号
|
||||
* @return 聊天消息
|
||||
*/
|
||||
AiChatMessageDO getChatMessage(Long id);
|
||||
|
||||
/**
|
||||
* 删除消息
|
||||
*
|
||||
@@ -68,6 +76,14 @@ public interface AiChatMessageService {
|
||||
*/
|
||||
void deleteChatMessageByAdmin(Long id);
|
||||
|
||||
/**
|
||||
* 获得聊天对话的消息数量
|
||||
*
|
||||
* @param conversationId 对话编号
|
||||
* @return 消息数量
|
||||
*/
|
||||
Integer getChatMessageCount(Long conversationId);
|
||||
|
||||
/**
|
||||
* 获得聊天对话的消息数量 Map
|
||||
*
|
||||
|
||||
+10
@@ -524,6 +524,11 @@ public class AiChatMessageServiceImpl implements AiChatMessageService {
|
||||
return chatMessageMapper.selectListByConversationId(conversationId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AiChatMessageDO getChatMessage(Long id) {
|
||||
return chatMessageMapper.selectById(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteChatMessage(Long id, Long userId) {
|
||||
// 1. 校验消息存在
|
||||
@@ -557,6 +562,11 @@ public class AiChatMessageServiceImpl implements AiChatMessageService {
|
||||
chatMessageMapper.deleteById(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer getChatMessageCount(Long conversationId) {
|
||||
return chatMessageMapper.selectCount(AiChatMessageDO::getConversationId, conversationId).intValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<Long, Integer> getChatMessageCountMap(Collection<Long> conversationIds) {
|
||||
return chatMessageMapper.selectCountMapByConversationId(conversationIds);
|
||||
|
||||
-11
@@ -29,14 +29,12 @@ import cn.iocoder.yudao.module.infra.api.file.FileApi;
|
||||
import com.alibaba.cloud.ai.dashscope.image.DashScopeImageOptions;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springaicommunity.qianfan.QianFanImageOptions;
|
||||
import org.springframework.ai.image.ImageModel;
|
||||
import org.springframework.ai.image.ImageOptions;
|
||||
import org.springframework.ai.image.ImagePrompt;
|
||||
import org.springframework.ai.image.ImageResponse;
|
||||
import org.springframework.ai.openai.OpenAiImageOptions;
|
||||
import org.springframework.ai.stabilityai.api.StabilityAiImageOptions;
|
||||
import org.springframework.ai.zhipuai.ZhiPuAiImageOptions;
|
||||
import org.springframework.scheduling.annotation.Async;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
@@ -168,15 +166,6 @@ public class AiImageServiceImpl implements AiImageService {
|
||||
.model(model.getModel()).n(1)
|
||||
.height(draw.getHeight()).width(draw.getWidth())
|
||||
.build();
|
||||
} else if (ObjUtil.equal(model.getPlatform(), AiPlatformEnum.YI_YAN.getPlatform())) {
|
||||
return QianFanImageOptions.builder()
|
||||
.model(model.getModel()).N(1)
|
||||
.height(draw.getHeight()).width(draw.getWidth())
|
||||
.build();
|
||||
} else if (ObjUtil.equal(model.getPlatform(), AiPlatformEnum.ZHI_PU.getPlatform())) {
|
||||
return ZhiPuAiImageOptions.builder()
|
||||
.model(model.getModel())
|
||||
.build();
|
||||
}
|
||||
throw new IllegalArgumentException("不支持的 AI 平台:" + model.getPlatform());
|
||||
}
|
||||
|
||||
+8
@@ -30,6 +30,14 @@ public interface AiMindMapService {
|
||||
*/
|
||||
void deleteMindMap(Long id);
|
||||
|
||||
/**
|
||||
* 获得思维导图
|
||||
*
|
||||
* @param id 编号
|
||||
* @return 思维导图
|
||||
*/
|
||||
AiMindMapDO getMindMap(Long id);
|
||||
|
||||
/**
|
||||
* 获得思维导图分页
|
||||
*
|
||||
|
||||
+5
@@ -153,6 +153,11 @@ public class AiMindMapServiceImpl implements AiMindMapService {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public AiMindMapDO getMindMap(Long id) {
|
||||
return mindMapMapper.selectById(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PageResult<AiMindMapDO> getMindMapPage(AiMindMapPageReqVO pageReqVO) {
|
||||
return mindMapMapper.selectPage(pageReqVO);
|
||||
|
||||
+8
@@ -30,6 +30,14 @@ public interface AiWriteService {
|
||||
*/
|
||||
void deleteWrite(Long id);
|
||||
|
||||
/**
|
||||
* 获得写作
|
||||
*
|
||||
* @param id 编号
|
||||
* @return AI 写作
|
||||
*/
|
||||
AiWriteDO getWrite(Long id);
|
||||
|
||||
/**
|
||||
* 获得写作分页
|
||||
*
|
||||
|
||||
+5
@@ -170,6 +170,11 @@ public class AiWriteServiceImpl implements AiWriteService {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public AiWriteDO getWrite(Long id) {
|
||||
return writeMapper.selectById(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PageResult<AiWriteDO> getWritePage(AiWritePageReqVO pageReqVO) {
|
||||
return writeMapper.selectPage(pageReqVO);
|
||||
|
||||
@@ -8,8 +8,7 @@ import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
|
||||
import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
|
||||
import cn.iocoder.yudao.module.ai.enums.model.AiPlatformEnum;
|
||||
import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatOptions;
|
||||
import org.springaicommunity.moonshot.MoonshotChatOptions;
|
||||
import org.springaicommunity.qianfan.QianFanChatOptions;
|
||||
import cn.hutool.extra.spring.SpringUtil;
|
||||
import org.springframework.ai.anthropic.AnthropicChatOptions;
|
||||
import org.springframework.ai.azure.openai.AzureOpenAiChatOptions;
|
||||
import org.springframework.ai.chat.messages.*;
|
||||
@@ -17,14 +16,16 @@ import org.springframework.ai.chat.model.ChatResponse;
|
||||
import org.springframework.ai.chat.prompt.ChatOptions;
|
||||
import org.springframework.ai.deepseek.DeepSeekAssistantMessage;
|
||||
import org.springframework.ai.deepseek.DeepSeekChatOptions;
|
||||
import org.springframework.ai.minimax.MiniMaxChatOptions;
|
||||
import org.springframework.ai.ollama.api.OllamaChatOptions;
|
||||
import org.springframework.ai.openai.OpenAiChatOptions;
|
||||
import org.springframework.ai.tool.ToolCallback;
|
||||
import org.springframework.ai.zhipuai.ZhiPuAiChatOptions;
|
||||
import org.springframework.core.env.Environment;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
|
||||
import static cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants.API_CONFIG_PLACEHOLDER_NOT_RESOLVED;
|
||||
|
||||
/**
|
||||
* Spring AI 工具类
|
||||
*
|
||||
@@ -35,6 +36,34 @@ public class AiUtils {
|
||||
public static final String TOOL_CONTEXT_LOGIN_USER = "LOGIN_USER";
|
||||
public static final String TOOL_CONTEXT_TENANT_ID = "TENANT_ID";
|
||||
|
||||
/**
|
||||
* 解析 DB 等动态配置里的 Spring 占位符,例如 ${OPENAI_API_KEY}
|
||||
*
|
||||
* @param value 待解析的配置值
|
||||
* @return 解析后的配置值
|
||||
*/
|
||||
public static String resolveSpringPlaceholders(String value) {
|
||||
if (StrUtil.isBlank(value) || !StrUtil.contains(value, "${")) {
|
||||
return value;
|
||||
}
|
||||
try {
|
||||
return SpringUtil.getBean(Environment.class).resolveRequiredPlaceholders(value);
|
||||
} catch (IllegalArgumentException ex) {
|
||||
throw exception(API_CONFIG_PLACEHOLDER_NOT_RESOLVED, value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验 API Key,避免集成测试使用默认占位值发起调用。
|
||||
*
|
||||
* @param apiKey API Key
|
||||
*/
|
||||
public static void validateApiKey(String apiKey) {
|
||||
if (StrUtil.isBlank(apiKey) || "sk-xxxx".equals(apiKey)) {
|
||||
throw new IllegalStateException("apiKey 不能为空");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 通义千问支持多模态的模型
|
||||
*
|
||||
@@ -42,7 +71,8 @@ public class AiUtils {
|
||||
* @see <a href="https://help.aliyun.com/zh/model-studio/error-code#error-url">必须开启 withMultiModel 参数</a>
|
||||
*/
|
||||
public static final Set<String> TONG_YI_MULTI_MODELS = SetUtils.asSet(
|
||||
// qwen3.5 / 3.6 系列(统一多模态主干)
|
||||
// qwen3.5 / 3.6 / 3.7 系列(统一多模态主干)
|
||||
"qwen3.7-max", "qwen3.7-plus", "qwen3.7-flash",
|
||||
"qwen3.6-plus", "qwen3.6-flash",
|
||||
"qwen3.5-plus", "qwen3.5-flash",
|
||||
// qwen-vl 视觉理解
|
||||
@@ -72,27 +102,21 @@ public class AiUtils {
|
||||
.enableThinking(true) // TODO 芋艿:默认都开启 thinking 模式,后续可以让用户配置
|
||||
.multiModel(TONG_YI_MULTI_MODELS.contains(model)) // 是否多模态模型
|
||||
.toolCallbacks(toolCallbacks).toolContext(toolContext).build();
|
||||
case YI_YAN:
|
||||
return QianFanChatOptions.builder().model(model).temperature(temperature).maxTokens(maxTokens).build();
|
||||
case DEEP_SEEK:
|
||||
case DOU_BAO: // 复用 DeepSeek 客户端
|
||||
case HUN_YUAN: // 复用 DeepSeek 客户端
|
||||
case SILICON_FLOW: // 复用 DeepSeek 客户端
|
||||
case YI_YAN: // 复用 DeepSeek 客户端
|
||||
case ZHI_PU: // 复用 DeepSeek 客户端
|
||||
case XING_HUO: // 复用 DeepSeek 客户端
|
||||
case MINI_MAX: // 复用 DeepSeek 客户端
|
||||
case MOONSHOT: // 复用 DeepSeek 客户端
|
||||
case BAI_CHUAN: // 复用 DeepSeek 客户端
|
||||
case STEP_FUN: // 复用 DeepSeek 客户端
|
||||
return DeepSeekChatOptions.builder().model(model).temperature(temperature).maxTokens(maxTokens)
|
||||
.toolCallbacks(toolCallbacks).toolContext(toolContext).build();
|
||||
case ZHI_PU:
|
||||
return ZhiPuAiChatOptions.builder().model(model).temperature(temperature).maxTokens(maxTokens)
|
||||
.toolCallbacks(toolCallbacks).toolContext(toolContext).build();
|
||||
case MINI_MAX:
|
||||
return MiniMaxChatOptions.builder().model(model).temperature(temperature).maxTokens(maxTokens)
|
||||
.toolCallbacks(toolCallbacks).toolContext(toolContext).build();
|
||||
case MOONSHOT:
|
||||
return MoonshotChatOptions.builder().model(model).temperature(temperature).maxTokens(maxTokens)
|
||||
.toolCallbacks(toolCallbacks).toolContext(toolContext).build();
|
||||
case OPENAI:
|
||||
case GEMINI: // 复用 OpenAI 客户端
|
||||
case BAI_CHUAN: // 复用 OpenAI 客户端
|
||||
case GROK: // 复用 OpenAI 客户端
|
||||
return OpenAiChatOptions.builder().model(model).temperature(temperature).maxTokens(maxTokens)
|
||||
.toolCallbacks(toolCallbacks).toolContext(toolContext).build();
|
||||
@@ -159,4 +183,4 @@ public class AiUtils {
|
||||
return MapUtil.getStr(output.getMetadata(), "reasoningContent");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
+26
-12
@@ -1,5 +1,6 @@
|
||||
package cn.iocoder.yudao.module.ai.framework.ai.core.model.chat;
|
||||
|
||||
import cn.hutool.system.SystemUtil;
|
||||
import cn.iocoder.yudao.module.ai.framework.ai.core.model.baichuan.BaiChuanChatModel;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@@ -8,13 +9,16 @@ import org.springframework.ai.chat.messages.SystemMessage;
|
||||
import org.springframework.ai.chat.messages.UserMessage;
|
||||
import org.springframework.ai.chat.model.ChatResponse;
|
||||
import org.springframework.ai.chat.prompt.Prompt;
|
||||
import org.springframework.ai.openai.OpenAiChatModel;
|
||||
import org.springframework.ai.openai.OpenAiChatOptions;
|
||||
import org.springframework.ai.openai.api.OpenAiApi;
|
||||
import org.springframework.ai.deepseek.DeepSeekChatModel;
|
||||
import org.springframework.ai.deepseek.DeepSeekChatOptions;
|
||||
import org.springframework.ai.deepseek.api.DeepSeekApi;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import static cn.iocoder.yudao.module.ai.util.AiUtils.validateApiKey;
|
||||
|
||||
/**
|
||||
* {@link BaiChuanChatModel} 集成测试
|
||||
@@ -23,22 +27,27 @@ import java.util.List;
|
||||
*/
|
||||
public class BaiChuanChatModelTests {
|
||||
|
||||
private final OpenAiChatModel openAiChatModel = OpenAiChatModel.builder()
|
||||
.openAiApi(OpenAiApi.builder()
|
||||
private static final String API_KEY = SystemUtil.get("BAICHUAN_API_KEY",
|
||||
"sk-xxxx"); // 按需改成你的百川 API Key
|
||||
private static final String MODEL = SystemUtil.get("BAICHUAN_MODEL",
|
||||
BaiChuanChatModel.MODEL_DEFAULT);
|
||||
|
||||
private final BaiChuanChatModel chatModel = new BaiChuanChatModel(DeepSeekChatModel.builder()
|
||||
.deepSeekApi(DeepSeekApi.builder()
|
||||
.baseUrl(BaiChuanChatModel.BASE_URL)
|
||||
.apiKey("sk-61b6766a94c70786ed02673f5e16af3c") // apiKey
|
||||
.completionsPath(BaiChuanChatModel.COMPLETE_PATH)
|
||||
.apiKey(API_KEY)
|
||||
.build())
|
||||
.defaultOptions(OpenAiChatOptions.builder()
|
||||
.model("Baichuan4-Turbo") // 模型(https://platform.baichuan-ai.com/docs/api)
|
||||
.defaultOptions(DeepSeekChatOptions.builder()
|
||||
.model(MODEL)
|
||||
.temperature(0.7)
|
||||
.build())
|
||||
.build();
|
||||
|
||||
private final BaiChuanChatModel chatModel = new BaiChuanChatModel(openAiChatModel);
|
||||
.build());
|
||||
|
||||
@Test
|
||||
@Disabled
|
||||
public void testCall() {
|
||||
validateApiKey(API_KEY);
|
||||
// 准备参数
|
||||
List<Message> messages = new ArrayList<>();
|
||||
messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。"));
|
||||
@@ -48,11 +57,13 @@ public class BaiChuanChatModelTests {
|
||||
ChatResponse response = chatModel.call(new Prompt(messages));
|
||||
// 打印结果
|
||||
System.out.println(response);
|
||||
System.out.println(Objects.requireNonNull(response.getResult()).getOutput());
|
||||
}
|
||||
|
||||
@Test
|
||||
@Disabled
|
||||
public void testStream() {
|
||||
validateApiKey(API_KEY);
|
||||
// 准备参数
|
||||
List<Message> messages = new ArrayList<>();
|
||||
messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。"));
|
||||
@@ -61,7 +72,10 @@ public class BaiChuanChatModelTests {
|
||||
// 调用
|
||||
Flux<ChatResponse> flux = chatModel.stream(new Prompt(messages));
|
||||
// 打印结果
|
||||
flux.doOnNext(System.out::println).then().block();
|
||||
flux.doOnNext(response -> {
|
||||
// System.out.println(response);
|
||||
System.out.println(Objects.requireNonNull(response.getResult()).getOutput());
|
||||
}).then().block();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+16
-4
@@ -1,5 +1,6 @@
|
||||
package cn.iocoder.yudao.module.ai.framework.ai.core.model.chat;
|
||||
|
||||
import cn.hutool.system.SystemUtil;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.ai.chat.messages.Message;
|
||||
@@ -14,6 +15,9 @@ import reactor.core.publisher.Flux;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import static cn.iocoder.yudao.module.ai.util.AiUtils.validateApiKey;
|
||||
|
||||
/**
|
||||
* {@link DeepSeekChatModel} 集成测试
|
||||
@@ -22,12 +26,17 @@ import java.util.List;
|
||||
*/
|
||||
public class DeepSeekChatModelTests {
|
||||
|
||||
private static final String API_KEY = SystemUtil.get("DEEPSEEK_API_KEY",
|
||||
"sk-xxxx");
|
||||
private static final String MODEL = SystemUtil.get("DEEPSEEK_MODEL",
|
||||
"deepseek-v4-flash");
|
||||
|
||||
private final DeepSeekChatModel chatModel = DeepSeekChatModel.builder()
|
||||
.deepSeekApi(DeepSeekApi.builder()
|
||||
.apiKey("sk-eaf4172a057344dd9bc64b1f806b6axx") // apiKey
|
||||
.apiKey(API_KEY) // apiKey
|
||||
.build())
|
||||
.defaultOptions(DeepSeekChatOptions.builder()
|
||||
.model("deepseek-chat") // 模型
|
||||
.model(MODEL) // 模型
|
||||
.temperature(0.7)
|
||||
.build())
|
||||
.build();
|
||||
@@ -35,6 +44,7 @@ public class DeepSeekChatModelTests {
|
||||
@Test
|
||||
@Disabled
|
||||
public void testCall() {
|
||||
validateApiKey(API_KEY);
|
||||
// 准备参数
|
||||
List<Message> messages = new ArrayList<>();
|
||||
messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。"));
|
||||
@@ -43,12 +53,13 @@ public class DeepSeekChatModelTests {
|
||||
// 调用
|
||||
ChatResponse response = chatModel.call(new Prompt(messages));
|
||||
// 打印结果
|
||||
System.out.println(response);
|
||||
System.out.println(Objects.requireNonNull(response.getResult()).getOutput());
|
||||
}
|
||||
|
||||
@Test
|
||||
@Disabled
|
||||
public void testStream() {
|
||||
validateApiKey(API_KEY);
|
||||
// 准备参数
|
||||
List<Message> messages = new ArrayList<>();
|
||||
messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。"));
|
||||
@@ -63,11 +74,12 @@ public class DeepSeekChatModelTests {
|
||||
@Test
|
||||
@Disabled
|
||||
public void testStream_thinking() {
|
||||
validateApiKey(API_KEY);
|
||||
// 准备参数
|
||||
List<Message> messages = new ArrayList<>();
|
||||
messages.add(new UserMessage("详细分析下,如何设计一个电商系统?"));
|
||||
DeepSeekChatOptions options = DeepSeekChatOptions.builder()
|
||||
.model("deepseek-reasoner")
|
||||
.model(MODEL)
|
||||
.build();
|
||||
|
||||
// 调用
|
||||
|
||||
+21
-13
@@ -1,5 +1,6 @@
|
||||
package cn.iocoder.yudao.module.ai.framework.ai.core.model.chat;
|
||||
|
||||
import cn.hutool.system.SystemUtil;
|
||||
import cn.iocoder.yudao.module.ai.framework.ai.core.model.doubao.DouBaoChatModel;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@@ -15,6 +16,9 @@ import reactor.core.publisher.Flux;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import static cn.iocoder.yudao.module.ai.util.AiUtils.validateApiKey;
|
||||
|
||||
/**
|
||||
* {@link DouBaoChatModel} 集成测试
|
||||
@@ -23,19 +27,19 @@ import java.util.List;
|
||||
*/
|
||||
public class DouBaoChatModelTests {
|
||||
|
||||
/**
|
||||
* 相比 OpenAIChatModel 来说,DeepSeekChatModel 可以兼容豆包的 thinking 能力!
|
||||
*/
|
||||
private static final String API_KEY = SystemUtil.get("DOUBAO_API_KEY",
|
||||
"sk-xxxx"); // 按需改成你的豆包 API Key
|
||||
private static final String MODEL = SystemUtil.get("DOUBAO_MODEL",
|
||||
DouBaoChatModel.MODEL_DEFAULT);
|
||||
|
||||
private final DeepSeekChatModel openAiChatModel = DeepSeekChatModel.builder()
|
||||
.deepSeekApi(DeepSeekApi.builder()
|
||||
.baseUrl(DouBaoChatModel.BASE_URL)
|
||||
.completionsPath(DouBaoChatModel.COMPLETE_PATH)
|
||||
.apiKey("5c1b5747-26d2-4ebd-a4e0-dd0e8d8b4272") // apiKey
|
||||
.apiKey(API_KEY)
|
||||
.build())
|
||||
.defaultOptions(DeepSeekChatOptions.builder()
|
||||
.model("doubao-1-5-lite-32k-250115") // 模型(doubao)
|
||||
// .model("doubao-seed-1-6-thinking-250715") // 模型(doubao)
|
||||
// .model("deepseek-r1-250120") // 模型(deepseek)
|
||||
.model(MODEL)
|
||||
.temperature(0.7)
|
||||
.build())
|
||||
.build();
|
||||
@@ -45,6 +49,7 @@ public class DouBaoChatModelTests {
|
||||
@Test
|
||||
@Disabled
|
||||
public void testCall() {
|
||||
validateApiKey(API_KEY);
|
||||
// 准备参数
|
||||
List<Message> messages = new ArrayList<>();
|
||||
messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。"));
|
||||
@@ -54,11 +59,13 @@ public class DouBaoChatModelTests {
|
||||
ChatResponse response = chatModel.call(new Prompt(messages));
|
||||
// 打印结果
|
||||
System.out.println(response);
|
||||
System.out.println(Objects.requireNonNull(response.getResult()).getOutput());
|
||||
}
|
||||
|
||||
@Test
|
||||
@Disabled
|
||||
public void testStream() {
|
||||
validateApiKey(API_KEY);
|
||||
// 准备参数
|
||||
List<Message> messages = new ArrayList<>();
|
||||
messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。"));
|
||||
@@ -67,25 +74,26 @@ public class DouBaoChatModelTests {
|
||||
// 调用
|
||||
Flux<ChatResponse> flux = chatModel.stream(new Prompt(messages));
|
||||
// 打印结果
|
||||
flux.doOnNext(System.out::println).then().block();
|
||||
flux.doOnNext(response -> {
|
||||
// System.out.println(response);
|
||||
System.out.println(Objects.requireNonNull(response.getResult()).getOutput());
|
||||
}).then().block();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Disabled
|
||||
public void testStream_thinking() {
|
||||
validateApiKey(API_KEY);
|
||||
// 准备参数
|
||||
List<Message> messages = new ArrayList<>();
|
||||
messages.add(new UserMessage("详细分析下,如何设计一个电商系统?"));
|
||||
DeepSeekChatOptions options = DeepSeekChatOptions.builder()
|
||||
.model("doubao-seed-1-6-thinking-250715")
|
||||
.build();
|
||||
|
||||
// 调用
|
||||
Flux<ChatResponse> flux = chatModel.stream(new Prompt(messages, options));
|
||||
Flux<ChatResponse> flux = chatModel.stream(new Prompt(messages));
|
||||
// 打印结果
|
||||
flux.doOnNext(response -> {
|
||||
// System.out.println(response);
|
||||
System.out.println(response.getResult().getOutput());
|
||||
System.out.println(Objects.requireNonNull(response.getResult()).getOutput());
|
||||
}).then().block();
|
||||
}
|
||||
|
||||
|
||||
+81
@@ -0,0 +1,81 @@
|
||||
package cn.iocoder.yudao.module.ai.framework.ai.core.model.chat;
|
||||
|
||||
import cn.hutool.system.SystemUtil;
|
||||
import cn.iocoder.yudao.module.ai.framework.ai.core.model.grok.GrokChatModel;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.ai.chat.messages.Message;
|
||||
import org.springframework.ai.chat.messages.SystemMessage;
|
||||
import org.springframework.ai.chat.messages.UserMessage;
|
||||
import org.springframework.ai.chat.model.ChatResponse;
|
||||
import org.springframework.ai.chat.prompt.Prompt;
|
||||
import org.springframework.ai.openai.OpenAiChatModel;
|
||||
import org.springframework.ai.openai.OpenAiChatOptions;
|
||||
import org.springframework.ai.openai.api.OpenAiApi;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import static cn.iocoder.yudao.module.ai.util.AiUtils.validateApiKey;
|
||||
|
||||
/**
|
||||
* {@link GrokChatModel} 集成测试
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
public class GrokChatModelTests {
|
||||
|
||||
private static final String API_KEY = SystemUtil.get("GROK_API_KEY",
|
||||
"sk-xxxx");
|
||||
private static final String MODEL = SystemUtil.get("GROK_MODEL",
|
||||
GrokChatModel.MODEL_DEFAULT);
|
||||
|
||||
private final GrokChatModel chatModel = new GrokChatModel(OpenAiChatModel.builder()
|
||||
.openAiApi(OpenAiApi.builder()
|
||||
.baseUrl(GrokChatModel.BASE_URL)
|
||||
.completionsPath(GrokChatModel.COMPLETE_PATH)
|
||||
.apiKey(API_KEY)
|
||||
.build())
|
||||
.defaultOptions(OpenAiChatOptions.builder()
|
||||
.model(MODEL)
|
||||
.temperature(0.7)
|
||||
.build())
|
||||
.build());
|
||||
|
||||
@Test
|
||||
@Disabled
|
||||
public void testCall() {
|
||||
validateApiKey(API_KEY);
|
||||
// 准备参数
|
||||
List<Message> messages = new ArrayList<>();
|
||||
messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。"));
|
||||
messages.add(new UserMessage("1 + 1 = ?"));
|
||||
|
||||
// 调用
|
||||
ChatResponse response = chatModel.call(new Prompt(messages));
|
||||
// 打印结果
|
||||
System.out.println(response);
|
||||
System.out.println(Objects.requireNonNull(response.getResult()).getOutput());
|
||||
}
|
||||
|
||||
@Test
|
||||
@Disabled
|
||||
public void testStream() {
|
||||
validateApiKey(API_KEY);
|
||||
// 准备参数
|
||||
List<Message> messages = new ArrayList<>();
|
||||
messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。"));
|
||||
messages.add(new UserMessage("1 + 1 = ?"));
|
||||
|
||||
// 调用
|
||||
Flux<ChatResponse> flux = chatModel.stream(new Prompt(messages));
|
||||
// 打印结果
|
||||
flux.doOnNext(response -> {
|
||||
// System.out.println(response);
|
||||
System.out.println(Objects.requireNonNull(response.getResult()).getOutput());
|
||||
}).then().block();
|
||||
}
|
||||
|
||||
}
|
||||
+16
-67
@@ -1,5 +1,6 @@
|
||||
package cn.iocoder.yudao.module.ai.framework.ai.core.model.chat;
|
||||
|
||||
import cn.hutool.system.SystemUtil;
|
||||
import cn.iocoder.yudao.module.ai.framework.ai.core.model.hunyuan.HunYuanChatModel;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@@ -15,6 +16,9 @@ import reactor.core.publisher.Flux;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import static cn.iocoder.yudao.module.ai.util.AiUtils.validateApiKey;
|
||||
|
||||
/**
|
||||
* {@link HunYuanChatModel} 集成测试
|
||||
@@ -23,14 +27,19 @@ import java.util.List;
|
||||
*/
|
||||
public class HunYuanChatModelTests {
|
||||
|
||||
private static final String API_KEY = SystemUtil.get("HUNYUAN_API_KEY",
|
||||
"sk-xxxx");
|
||||
private static final String MODEL = SystemUtil.get("HUNYUAN_MODEL",
|
||||
HunYuanChatModel.MODEL_DEFAULT);
|
||||
|
||||
private final DeepSeekChatModel openAiChatModel = DeepSeekChatModel.builder()
|
||||
.deepSeekApi(DeepSeekApi.builder()
|
||||
.baseUrl(HunYuanChatModel.BASE_URL)
|
||||
.completionsPath(HunYuanChatModel.COMPLETE_PATH)
|
||||
.apiKey("sk-abc") // apiKey
|
||||
.apiKey(API_KEY) // apiKey
|
||||
.build())
|
||||
.defaultOptions(DeepSeekChatOptions.builder()
|
||||
.model(HunYuanChatModel.MODEL_DEFAULT) // 模型
|
||||
.model(MODEL) // 模型
|
||||
.temperature(0.7)
|
||||
.build())
|
||||
.build();
|
||||
@@ -40,6 +49,7 @@ public class HunYuanChatModelTests {
|
||||
@Test
|
||||
@Disabled
|
||||
public void testCall() {
|
||||
validateApiKey(API_KEY);
|
||||
// 准备参数
|
||||
List<Message> messages = new ArrayList<>();
|
||||
messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。"));
|
||||
@@ -48,12 +58,13 @@ public class HunYuanChatModelTests {
|
||||
// 调用
|
||||
ChatResponse response = chatModel.call(new Prompt(messages));
|
||||
// 打印结果
|
||||
System.out.println(response);
|
||||
System.out.println(Objects.requireNonNull(response.getResult()).getOutput());
|
||||
}
|
||||
|
||||
@Test
|
||||
@Disabled
|
||||
public void testStream() {
|
||||
validateApiKey(API_KEY);
|
||||
// 准备参数
|
||||
List<Message> messages = new ArrayList<>();
|
||||
messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。"));
|
||||
@@ -68,12 +79,12 @@ public class HunYuanChatModelTests {
|
||||
@Test
|
||||
@Disabled
|
||||
public void testStream_thinking() {
|
||||
validateApiKey(API_KEY);
|
||||
// 准备参数
|
||||
List<Message> messages = new ArrayList<>();
|
||||
messages.add(new UserMessage("详细分析下,如何设计一个电商系统?"));
|
||||
DeepSeekChatOptions options = DeepSeekChatOptions.builder()
|
||||
.model("hunyuan-a13b")
|
||||
// .model("hunyuan-turbos-latest")
|
||||
.model(MODEL)
|
||||
.build();
|
||||
|
||||
// 调用
|
||||
@@ -85,66 +96,4 @@ public class HunYuanChatModelTests {
|
||||
}).then().block();
|
||||
}
|
||||
|
||||
private final DeepSeekChatModel deepSeekOpenAiChatModel = DeepSeekChatModel.builder()
|
||||
.deepSeekApi(DeepSeekApi.builder()
|
||||
.baseUrl(HunYuanChatModel.DEEP_SEEK_BASE_URL)
|
||||
.completionsPath(HunYuanChatModel.COMPLETE_PATH)
|
||||
.apiKey("sk-abc") // apiKey
|
||||
.build())
|
||||
.defaultOptions(DeepSeekChatOptions.builder()
|
||||
// .model(HunYuanChatModel.DEEP_SEEK_MODEL_DEFAULT) // 模型("deepseek-v3")
|
||||
.model("deepseek-r1") // 模型("deepseek-r1")
|
||||
.temperature(0.7)
|
||||
.build())
|
||||
.build();
|
||||
|
||||
private final HunYuanChatModel deepSeekChatModel = new HunYuanChatModel(deepSeekOpenAiChatModel);
|
||||
|
||||
@Test
|
||||
@Disabled
|
||||
public void testCall_deepseek() {
|
||||
// 准备参数
|
||||
List<Message> messages = new ArrayList<>();
|
||||
messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。"));
|
||||
messages.add(new UserMessage("1 + 1 = ?"));
|
||||
|
||||
// 调用
|
||||
ChatResponse response = deepSeekChatModel.call(new Prompt(messages));
|
||||
// 打印结果
|
||||
System.out.println(response);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Disabled
|
||||
public void testStream_deepseek() {
|
||||
// 准备参数
|
||||
List<Message> messages = new ArrayList<>();
|
||||
messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。"));
|
||||
messages.add(new UserMessage("1 + 1 = ?"));
|
||||
|
||||
// 调用
|
||||
Flux<ChatResponse> flux = deepSeekChatModel.stream(new Prompt(messages));
|
||||
// 打印结果
|
||||
flux.doOnNext(System.out::println).then().block();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Disabled
|
||||
public void testStream_deepseek_thinking() {
|
||||
// 准备参数
|
||||
List<Message> messages = new ArrayList<>();
|
||||
messages.add(new UserMessage("详细分析下,如何设计一个电商系统?"));
|
||||
DeepSeekChatOptions options = DeepSeekChatOptions.builder()
|
||||
.model("deepseek-r1")
|
||||
.build();
|
||||
|
||||
// 调用
|
||||
Flux<ChatResponse> flux = deepSeekChatModel.stream(new Prompt(messages, options));
|
||||
// 打印结果
|
||||
flux.doOnNext(response -> {
|
||||
// System.out.println(response);
|
||||
System.out.println(response.getResult().getOutput());
|
||||
}).then().block();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+30
-14
@@ -1,5 +1,7 @@
|
||||
package cn.iocoder.yudao.module.ai.framework.ai.core.model.chat;
|
||||
|
||||
import cn.hutool.system.SystemUtil;
|
||||
import cn.iocoder.yudao.module.ai.framework.ai.core.model.minimax.MiniMaxChatModel;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.ai.chat.messages.Message;
|
||||
@@ -7,13 +9,16 @@ import org.springframework.ai.chat.messages.SystemMessage;
|
||||
import org.springframework.ai.chat.messages.UserMessage;
|
||||
import org.springframework.ai.chat.model.ChatResponse;
|
||||
import org.springframework.ai.chat.prompt.Prompt;
|
||||
import org.springframework.ai.minimax.MiniMaxChatModel;
|
||||
import org.springframework.ai.minimax.MiniMaxChatOptions;
|
||||
import org.springframework.ai.minimax.api.MiniMaxApi;
|
||||
import org.springframework.ai.deepseek.DeepSeekChatModel;
|
||||
import org.springframework.ai.deepseek.DeepSeekChatOptions;
|
||||
import org.springframework.ai.deepseek.api.DeepSeekApi;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import static cn.iocoder.yudao.module.ai.util.AiUtils.validateApiKey;
|
||||
|
||||
/**
|
||||
* {@link MiniMaxChatModel} 的集成测试
|
||||
@@ -22,14 +27,24 @@ import java.util.List;
|
||||
*/
|
||||
public class MiniMaxChatModelTests {
|
||||
|
||||
private final MiniMaxChatModel chatModel = new MiniMaxChatModel(
|
||||
new MiniMaxApi("eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJHcm91cE5hbWUiOiLnjovmlofmlowiLCJVc2VyTmFtZSI6IueOi-aWh-aWjCIsIkFjY291bnQiOiIiLCJTdWJqZWN0SUQiOiIxODk3Mjg3MjQ5NDU2ODA4MzQ2IiwiUGhvbmUiOiIxNTYwMTY5MTM5OSIsIkdyb3VwSUQiOiIxODk3Mjg3MjQ5NDQ4NDE5NzM4IiwiUGFnZU5hbWUiOiIiLCJNYWlsIjoiIiwiQ3JlYXRlVGltZSI6IjIwMjUtMDMtMTEgMTI6NTI6MDIiLCJUb2tlblR5cGUiOjEsImlzcyI6Im1pbmltYXgifQ.aAuB7gWW_oA4IYhh-CF7c9MfWWxKN49B_HK-DYjXaDwwffhiG-H1571z1WQhp9QytWG-DqgLejneeSxkiq1wQIe3FsEP2wz4BmGBct31LehbJu8ehLxg_vg75Uod1nFAHbm5mZz6JSVLNIlSo87Xr3UtSzJhAXlapEkcqlA4YOzOpKrZ8l5_OJPTORTCmHWZYgJcRS-faNiH62ZnUEHUozesTFhubJHo5GfJCw_edlnmfSUocERV1BjWvenhZ9My-aYXNktcW9WaSj9l6gayV7A0Ium_PL55T9ln1PcI8gayiVUKJGJDoqNyF1AF9_aF9NOKtTnQzwNqnZdlTYH6hw"), // 密钥
|
||||
MiniMaxChatOptions.builder()
|
||||
.model(MiniMaxApi.ChatModel.ABAB_6_5_G_Chat.getValue()) // 模型
|
||||
.build());
|
||||
private static final String API_KEY = SystemUtil.get("MINIMAX_API_KEY",
|
||||
"sk-xxxx"); // 按需改成你的 MiniMax API Key
|
||||
private static final String MODEL = SystemUtil.get("MINIMAX_MODEL",
|
||||
MiniMaxChatModel.MODEL_DEFAULT);
|
||||
|
||||
private final MiniMaxChatModel chatModel = new MiniMaxChatModel(DeepSeekChatModel.builder()
|
||||
.deepSeekApi(DeepSeekApi.builder()
|
||||
.baseUrl(MiniMaxChatModel.BASE_URL)
|
||||
.apiKey(API_KEY)
|
||||
.build())
|
||||
.defaultOptions(DeepSeekChatOptions.builder()
|
||||
.model(MODEL)
|
||||
.build())
|
||||
.build());
|
||||
@Test
|
||||
@Disabled
|
||||
public void testCall() {
|
||||
validateApiKey(API_KEY);
|
||||
// 准备参数
|
||||
List<Message> messages = new ArrayList<>();
|
||||
messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。"));
|
||||
@@ -39,12 +54,13 @@ public class MiniMaxChatModelTests {
|
||||
ChatResponse response = chatModel.call(new Prompt(messages));
|
||||
// 打印结果
|
||||
System.out.println(response);
|
||||
System.out.println(response.getResult().getOutput());
|
||||
System.out.println(Objects.requireNonNull(response.getResult()).getOutput());
|
||||
}
|
||||
|
||||
@Test
|
||||
@Disabled
|
||||
public void testStream() {
|
||||
validateApiKey(API_KEY);
|
||||
// 准备参数
|
||||
List<Message> messages = new ArrayList<>();
|
||||
messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。"));
|
||||
@@ -55,19 +71,19 @@ public class MiniMaxChatModelTests {
|
||||
// 打印结果
|
||||
flux.doOnNext(response -> {
|
||||
// System.out.println(response);
|
||||
System.out.println(response.getResult().getOutput());
|
||||
System.out.println(Objects.requireNonNull(response.getResult()).getOutput());
|
||||
}).then().block();
|
||||
}
|
||||
|
||||
// TODO @芋艿:暂时没解析 reasoning_content 结果,需要等官方修复
|
||||
@Test
|
||||
@Disabled
|
||||
public void testStream_thinking() {
|
||||
validateApiKey(API_KEY);
|
||||
// 准备参数
|
||||
List<Message> messages = new ArrayList<>();
|
||||
messages.add(new UserMessage("详细分析下,如何设计一个电商系统?"));
|
||||
MiniMaxChatOptions options = MiniMaxChatOptions.builder()
|
||||
.model("MiniMax-M1")
|
||||
DeepSeekChatOptions options = DeepSeekChatOptions.builder()
|
||||
.model(MODEL)
|
||||
.build();
|
||||
|
||||
// 调用
|
||||
@@ -75,7 +91,7 @@ public class MiniMaxChatModelTests {
|
||||
// 打印结果
|
||||
flux.doOnNext(response -> {
|
||||
// System.out.println(response);
|
||||
System.out.println(response.getResult().getOutput());
|
||||
System.out.println(Objects.requireNonNull(response.getResult()).getOutput());
|
||||
}).then().block();
|
||||
}
|
||||
|
||||
|
||||
+30
-16
@@ -1,19 +1,24 @@
|
||||
package cn.iocoder.yudao.module.ai.framework.ai.core.model.chat;
|
||||
|
||||
import cn.hutool.system.SystemUtil;
|
||||
import cn.iocoder.yudao.module.ai.framework.ai.core.model.moonshot.MoonshotChatModel;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springaicommunity.moonshot.MoonshotChatModel;
|
||||
import org.springaicommunity.moonshot.MoonshotChatOptions;
|
||||
import org.springaicommunity.moonshot.api.MoonshotApi;
|
||||
import org.springframework.ai.chat.messages.Message;
|
||||
import org.springframework.ai.chat.messages.SystemMessage;
|
||||
import org.springframework.ai.chat.messages.UserMessage;
|
||||
import org.springframework.ai.chat.model.ChatResponse;
|
||||
import org.springframework.ai.chat.prompt.Prompt;
|
||||
import org.springframework.ai.deepseek.DeepSeekChatModel;
|
||||
import org.springframework.ai.deepseek.DeepSeekChatOptions;
|
||||
import org.springframework.ai.deepseek.api.DeepSeekApi;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import static cn.iocoder.yudao.module.ai.util.AiUtils.validateApiKey;
|
||||
|
||||
/**
|
||||
* {@link MoonshotChatModel} 的集成测试
|
||||
@@ -22,18 +27,27 @@ import java.util.List;
|
||||
*/
|
||||
public class MoonshotChatModelTests {
|
||||
|
||||
private final MoonshotChatModel chatModel = MoonshotChatModel.builder()
|
||||
.moonshotApi(MoonshotApi.builder()
|
||||
.apiKey("sk-aHYYV1SARscItye5QQRRNbXij4fy65Ee7pNZlC9gsSQnUKXA") // 密钥
|
||||
private static final String API_KEY = SystemUtil.get("MOONSHOT_API_KEY",
|
||||
"sk-xxxx"); // 按需改成你的 Moonshot API Key
|
||||
private static final String MODEL = SystemUtil.get("MOONSHOT_MODEL",
|
||||
MoonshotChatModel.MODEL_DEFAULT);
|
||||
|
||||
private final MoonshotChatModel chatModel = new MoonshotChatModel(DeepSeekChatModel.builder()
|
||||
.deepSeekApi(DeepSeekApi.builder()
|
||||
.baseUrl(MoonshotChatModel.BASE_URL)
|
||||
.completionsPath(MoonshotChatModel.COMPLETE_PATH)
|
||||
.apiKey(API_KEY)
|
||||
.build())
|
||||
.defaultOptions(MoonshotChatOptions.builder()
|
||||
.model("kimi-k2-0711-preview") // 模型
|
||||
.defaultOptions(DeepSeekChatOptions.builder()
|
||||
.model(MODEL)
|
||||
.temperature(1D)
|
||||
.build())
|
||||
.build();
|
||||
.build());
|
||||
|
||||
@Test
|
||||
@Disabled
|
||||
public void testCall() {
|
||||
validateApiKey(API_KEY);
|
||||
// 准备参数
|
||||
List<Message> messages = new ArrayList<>();
|
||||
messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。"));
|
||||
@@ -43,12 +57,13 @@ public class MoonshotChatModelTests {
|
||||
ChatResponse response = chatModel.call(new Prompt(messages));
|
||||
// 打印结果
|
||||
System.out.println(response);
|
||||
System.out.println(response.getResult().getOutput());
|
||||
System.out.println(Objects.requireNonNull(response.getResult()).getOutput());
|
||||
}
|
||||
|
||||
@Test
|
||||
@Disabled
|
||||
public void testStream() {
|
||||
validateApiKey(API_KEY);
|
||||
// 准备参数
|
||||
List<Message> messages = new ArrayList<>();
|
||||
messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。"));
|
||||
@@ -59,20 +74,19 @@ public class MoonshotChatModelTests {
|
||||
// 打印结果
|
||||
flux.doOnNext(response -> {
|
||||
// System.out.println(response);
|
||||
System.out.println(response.getResult().getOutput());
|
||||
System.out.println(Objects.requireNonNull(response.getResult()).getOutput());
|
||||
}).then().block();
|
||||
}
|
||||
|
||||
// TODO @芋艿:暂时没解析 reasoning_content 结果,需要等官方修复
|
||||
@Test
|
||||
@Disabled
|
||||
public void testStream_thinking() {
|
||||
validateApiKey(API_KEY);
|
||||
// 准备参数
|
||||
List<Message> messages = new ArrayList<>();
|
||||
messages.add(new UserMessage("详细分析下,如何设计一个电商系统?"));
|
||||
MoonshotChatOptions options = MoonshotChatOptions.builder()
|
||||
// .model("kimi-k2-0711-preview")
|
||||
.model("kimi-thinking-preview")
|
||||
DeepSeekChatOptions options = DeepSeekChatOptions.builder()
|
||||
.model(MODEL)
|
||||
.build();
|
||||
|
||||
// 调用
|
||||
@@ -80,7 +94,7 @@ public class MoonshotChatModelTests {
|
||||
// 打印结果
|
||||
flux.doOnNext(response -> {
|
||||
// System.out.println(response);
|
||||
System.out.println(response.getResult().getOutput());
|
||||
System.out.println(Objects.requireNonNull(response.getResult()).getOutput());
|
||||
}).then().block();
|
||||
}
|
||||
|
||||
|
||||
+82
@@ -0,0 +1,82 @@
|
||||
package cn.iocoder.yudao.module.ai.framework.ai.core.model.chat;
|
||||
|
||||
import cn.hutool.system.SystemUtil;
|
||||
import cn.iocoder.yudao.module.ai.framework.ai.core.model.stepfun.StepFunChatModel;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.ai.chat.messages.Message;
|
||||
import org.springframework.ai.chat.messages.SystemMessage;
|
||||
import org.springframework.ai.chat.messages.UserMessage;
|
||||
import org.springframework.ai.chat.model.ChatResponse;
|
||||
import org.springframework.ai.chat.prompt.Prompt;
|
||||
import org.springframework.ai.deepseek.DeepSeekChatModel;
|
||||
import org.springframework.ai.deepseek.DeepSeekChatOptions;
|
||||
import org.springframework.ai.deepseek.api.DeepSeekApi;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import static cn.iocoder.yudao.module.ai.util.AiUtils.validateApiKey;
|
||||
|
||||
/**
|
||||
* {@link StepFunChatModel} 集成测试
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
public class StepFunChatModelTests {
|
||||
|
||||
private static final String API_KEY = SystemUtil.get("STEPFUN_API_KEY",
|
||||
"sk-xxxx");
|
||||
private static final String MODEL = SystemUtil.get("STEPFUN_MODEL",
|
||||
StepFunChatModel.MODEL_DEFAULT);
|
||||
|
||||
private final StepFunChatModel chatModel = new StepFunChatModel(DeepSeekChatModel.builder()
|
||||
.deepSeekApi(DeepSeekApi.builder()
|
||||
.baseUrl(StepFunChatModel.BASE_URL)
|
||||
.completionsPath(StepFunChatModel.COMPLETE_PATH)
|
||||
.apiKey(API_KEY)
|
||||
.build())
|
||||
.defaultOptions(DeepSeekChatOptions.builder()
|
||||
.model(MODEL)
|
||||
.temperature(0.7)
|
||||
.build())
|
||||
.build());
|
||||
|
||||
@Test
|
||||
@Disabled
|
||||
public void testCall() {
|
||||
validateApiKey(API_KEY);
|
||||
// 准备参数
|
||||
List<Message> messages = new ArrayList<>();
|
||||
messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。"));
|
||||
messages.add(new UserMessage("1 + 1 = ?"));
|
||||
|
||||
// 调用
|
||||
ChatResponse response = chatModel.call(new Prompt(messages));
|
||||
// 打印结果
|
||||
System.out.println(response);
|
||||
System.out.println(Objects.requireNonNull(response.getResult()).getOutput());
|
||||
}
|
||||
|
||||
@Test
|
||||
@Disabled
|
||||
public void testStream() {
|
||||
validateApiKey(API_KEY);
|
||||
// 准备参数
|
||||
List<Message> messages = new ArrayList<>();
|
||||
messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。"));
|
||||
messages.add(new UserMessage("1 + 1 = ?"));
|
||||
|
||||
// 调用
|
||||
Flux<ChatResponse> flux = chatModel.stream(new Prompt(messages));
|
||||
// 打印结果
|
||||
flux.doOnNext(response -> {
|
||||
// System.out.println(response);
|
||||
// System.out.println(Objects.requireNonNull(response.getResult()).getOutput());
|
||||
System.out.println(response.getResult() != null ? response.getResult().getOutput() : null);
|
||||
}).then().block();
|
||||
}
|
||||
|
||||
}
|
||||
+20
-17
@@ -1,5 +1,6 @@
|
||||
package cn.iocoder.yudao.module.ai.framework.ai.core.model.chat;
|
||||
|
||||
import cn.hutool.system.SystemUtil;
|
||||
import cn.iocoder.yudao.module.ai.framework.ai.core.model.xinghuo.XingHuoChatModel;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@@ -8,13 +9,14 @@ import org.springframework.ai.chat.messages.SystemMessage;
|
||||
import org.springframework.ai.chat.messages.UserMessage;
|
||||
import org.springframework.ai.chat.model.ChatResponse;
|
||||
import org.springframework.ai.chat.prompt.Prompt;
|
||||
import org.springframework.ai.deepseek.DeepSeekChatModel;
|
||||
import org.springframework.ai.deepseek.DeepSeekChatOptions;
|
||||
import org.springframework.ai.deepseek.api.DeepSeekApi;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import static cn.iocoder.yudao.module.ai.util.AiUtils.validateApiKey;
|
||||
|
||||
/**
|
||||
* {@link XingHuoChatModel} 集成测试
|
||||
@@ -23,24 +25,23 @@ import java.util.List;
|
||||
*/
|
||||
public class XingHuoChatModelTests {
|
||||
|
||||
private final DeepSeekChatModel openAiChatModel = DeepSeekChatModel.builder()
|
||||
.deepSeekApi(DeepSeekApi.builder()
|
||||
.baseUrl(XingHuoChatModel.BASE_URL_V2)
|
||||
.completionsPath(XingHuoChatModel.BASE_COMPLETIONS_PATH_V2)
|
||||
.apiKey("75b161ed2aef4719b275d6e7f2a4d4cd:YWYxYWI2MTA4ODI2NGZlYTQyNjAzZTcz") // appKey:secretKey
|
||||
.build())
|
||||
.defaultOptions(DeepSeekChatOptions.builder()
|
||||
// .model("generalv3.5") // 模型
|
||||
.model("x1") // 模型
|
||||
.temperature(0.7)
|
||||
.build())
|
||||
.build();
|
||||
private static final String API_KEY = SystemUtil.get("XINGHUO_API_KEY",
|
||||
"sk-xxxx"); // 按需改成你的讯飞星火 API Key
|
||||
private static final String MODEL = SystemUtil.get("XINGHUO_MODEL",
|
||||
XingHuoChatModel.MODEL_DEFAULT);
|
||||
|
||||
private final XingHuoChatModel chatModel = new XingHuoChatModel(openAiChatModel);
|
||||
private final XingHuoChatModel chatModel = XingHuoChatModel.builder()
|
||||
.apiKey(API_KEY)
|
||||
.options(DeepSeekChatOptions.builder()
|
||||
.model(MODEL)
|
||||
.temperature(0.7)
|
||||
.build())
|
||||
.build();
|
||||
|
||||
@Test
|
||||
@Disabled
|
||||
public void testCall() {
|
||||
validateApiKey(API_KEY);
|
||||
// 准备参数
|
||||
List<Message> messages = new ArrayList<>();
|
||||
messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。"));
|
||||
@@ -55,6 +56,7 @@ public class XingHuoChatModelTests {
|
||||
@Test
|
||||
@Disabled
|
||||
public void testStream() {
|
||||
validateApiKey(API_KEY);
|
||||
// 准备参数
|
||||
List<Message> messages = new ArrayList<>();
|
||||
messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。"));
|
||||
@@ -69,11 +71,12 @@ public class XingHuoChatModelTests {
|
||||
@Test
|
||||
@Disabled
|
||||
public void testStream_thinking() {
|
||||
validateApiKey(API_KEY);
|
||||
// 准备参数
|
||||
List<Message> messages = new ArrayList<>();
|
||||
messages.add(new UserMessage("详细分析下,如何设计一个电商系统?"));
|
||||
DeepSeekChatOptions options = DeepSeekChatOptions.builder()
|
||||
.model("x1")
|
||||
.model(MODEL)
|
||||
.build();
|
||||
|
||||
// 调用
|
||||
@@ -81,7 +84,7 @@ public class XingHuoChatModelTests {
|
||||
// 打印结果
|
||||
flux.doOnNext(response -> {
|
||||
// System.out.println(response);
|
||||
System.out.println(response.getResult().getOutput());
|
||||
System.out.println(Objects.requireNonNull(response.getResult()).getOutput());
|
||||
}).then().block();
|
||||
}
|
||||
|
||||
|
||||
+30
-16
@@ -1,62 +1,76 @@
|
||||
package cn.iocoder.yudao.module.ai.framework.ai.core.model.chat;
|
||||
|
||||
import cn.hutool.system.SystemUtil;
|
||||
import cn.iocoder.yudao.module.ai.framework.ai.core.model.yiyan.YiYanChatModel;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springaicommunity.qianfan.QianFanChatModel;
|
||||
import org.springaicommunity.qianfan.QianFanChatOptions;
|
||||
import org.springaicommunity.qianfan.api.QianFanApi;
|
||||
import org.springframework.ai.chat.messages.Message;
|
||||
import org.springframework.ai.chat.messages.UserMessage;
|
||||
import org.springframework.ai.chat.model.ChatResponse;
|
||||
import org.springframework.ai.chat.prompt.Prompt;
|
||||
import org.springframework.ai.deepseek.DeepSeekChatModel;
|
||||
import org.springframework.ai.deepseek.DeepSeekChatOptions;
|
||||
import org.springframework.ai.deepseek.api.DeepSeekApi;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import static cn.iocoder.yudao.module.ai.util.AiUtils.validateApiKey;
|
||||
|
||||
// TODO @芋艿:百度千帆 API 提供了 V2 版本,目前 Spring AI 不兼容,可关键 <https://github.com/spring-projects/spring-ai/issues/2179> 进展
|
||||
/**
|
||||
* {@link QianFanChatModel} 的集成测试
|
||||
* {@link YiYanChatModel} 的集成测试
|
||||
*
|
||||
* @author fansili
|
||||
*/
|
||||
public class YiYanChatModelTests {
|
||||
|
||||
private final QianFanChatModel chatModel = new QianFanChatModel(
|
||||
new QianFanApi("DGnyzREuaY7av7c38bOM9Ji2", "9aR8myflEOPDrEeLhoXv0FdqANOAyIZW"), // 密钥
|
||||
QianFanChatOptions.builder()
|
||||
.model("ERNIE-4.5-8K-Preview")
|
||||
.build()
|
||||
);
|
||||
private static final String API_KEY = SystemUtil.get("YIYAN_API_KEY",
|
||||
"sk-xxxx"); // 按需改成你的文心一言 API Key
|
||||
private static final String MODEL = SystemUtil.get("YIYAN_MODEL",
|
||||
YiYanChatModel.MODEL_DEFAULT);
|
||||
|
||||
private final YiYanChatModel chatModel = new YiYanChatModel(DeepSeekChatModel.builder()
|
||||
.deepSeekApi(DeepSeekApi.builder()
|
||||
.baseUrl(YiYanChatModel.BASE_URL)
|
||||
.apiKey(API_KEY)
|
||||
.build())
|
||||
.defaultOptions(DeepSeekChatOptions.builder()
|
||||
.model(MODEL)
|
||||
.build())
|
||||
.build());
|
||||
|
||||
@Test
|
||||
@Disabled
|
||||
public void testCall() {
|
||||
validateApiKey(API_KEY);
|
||||
// 准备参数
|
||||
List<Message> messages = new ArrayList<>();
|
||||
// TODO @芋艿:文心一言,只要带上 system message 就报错,已经各种测试,很莫名!
|
||||
// messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。"));
|
||||
messages.add(new UserMessage("1 + 1 = ?"));
|
||||
|
||||
// 调用
|
||||
ChatResponse response = chatModel.call(new Prompt(messages));
|
||||
// 打印结果
|
||||
System.out.println(response);
|
||||
System.out.println(Objects.requireNonNull(response.getResult()).getOutput());
|
||||
}
|
||||
|
||||
@Test
|
||||
@Disabled
|
||||
public void testStream() {
|
||||
validateApiKey(API_KEY);
|
||||
// 准备参数
|
||||
List<Message> messages = new ArrayList<>();
|
||||
// TODO @芋艿:文心一言,只要带上 system message 就报错,已经各种测试,很莫名!
|
||||
// messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。"));
|
||||
messages.add(new UserMessage("1 + 1 = ?"));
|
||||
|
||||
// 调用
|
||||
Flux<ChatResponse> flux = chatModel.stream(new Prompt(messages));
|
||||
// 打印结果
|
||||
flux.doOnNext(System.out::println).then().block();
|
||||
flux.doOnNext(response -> {
|
||||
// System.out.println(response);
|
||||
System.out.println(Objects.requireNonNull(response.getResult()).getOutput());
|
||||
}).then().block();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+30
-15
@@ -1,5 +1,7 @@
|
||||
package cn.iocoder.yudao.module.ai.framework.ai.core.model.chat;
|
||||
|
||||
import cn.hutool.system.SystemUtil;
|
||||
import cn.iocoder.yudao.module.ai.framework.ai.core.model.zhipu.ZhiPuChatModel;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.ai.chat.messages.Message;
|
||||
@@ -7,31 +9,43 @@ import org.springframework.ai.chat.messages.SystemMessage;
|
||||
import org.springframework.ai.chat.messages.UserMessage;
|
||||
import org.springframework.ai.chat.model.ChatResponse;
|
||||
import org.springframework.ai.chat.prompt.Prompt;
|
||||
import org.springframework.ai.zhipuai.ZhiPuAiChatModel;
|
||||
import org.springframework.ai.zhipuai.ZhiPuAiChatOptions;
|
||||
import org.springframework.ai.zhipuai.api.ZhiPuAiApi;
|
||||
import org.springframework.ai.deepseek.DeepSeekChatModel;
|
||||
import org.springframework.ai.deepseek.DeepSeekChatOptions;
|
||||
import org.springframework.ai.deepseek.api.DeepSeekApi;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import static cn.iocoder.yudao.module.ai.util.AiUtils.validateApiKey;
|
||||
|
||||
/**
|
||||
* {@link ZhiPuAiChatModel} 的集成测试
|
||||
* {@link ZhiPuChatModel} 的集成测试
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
public class ZhiPuAiChatModelTests {
|
||||
|
||||
private final ZhiPuAiChatModel chatModel = new ZhiPuAiChatModel(
|
||||
ZhiPuAiApi.builder().apiKey("2f35fb6ca4ea41fab898729b7fac086c.6ESSfPcCkxaKEUlR").build(), // 密钥
|
||||
ZhiPuAiChatOptions.builder()
|
||||
.model(ZhiPuAiApi.ChatModel.GLM_4.getName()) // 模型
|
||||
private static final String API_KEY = SystemUtil.get("ZHIPU_API_KEY",
|
||||
"sk-xxxx"); // 按需改成你的智谱 API Key
|
||||
private static final String MODEL = SystemUtil.get("ZHIPU_MODEL",
|
||||
ZhiPuChatModel.MODEL_DEFAULT);
|
||||
|
||||
private final ZhiPuChatModel chatModel = new ZhiPuChatModel(DeepSeekChatModel.builder()
|
||||
.deepSeekApi(DeepSeekApi.builder()
|
||||
.baseUrl(ZhiPuChatModel.BASE_URL)
|
||||
.apiKey(API_KEY)
|
||||
.build())
|
||||
.defaultOptions(DeepSeekChatOptions.builder()
|
||||
.model(MODEL)
|
||||
.build()
|
||||
);
|
||||
).build());
|
||||
|
||||
@Test
|
||||
@Disabled
|
||||
public void testCall() {
|
||||
validateApiKey(API_KEY);
|
||||
// 准备参数
|
||||
List<Message> messages = new ArrayList<>();
|
||||
messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。"));
|
||||
@@ -41,12 +55,13 @@ public class ZhiPuAiChatModelTests {
|
||||
ChatResponse response = chatModel.call(new Prompt(messages));
|
||||
// 打印结果
|
||||
System.out.println(response);
|
||||
System.out.println(response.getResult().getOutput());
|
||||
System.out.println(Objects.requireNonNull(response.getResult()).getOutput());
|
||||
}
|
||||
|
||||
@Test
|
||||
@Disabled
|
||||
public void testStream() {
|
||||
validateApiKey(API_KEY);
|
||||
// 准备参数
|
||||
List<Message> messages = new ArrayList<>();
|
||||
messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。"));
|
||||
@@ -57,19 +72,19 @@ public class ZhiPuAiChatModelTests {
|
||||
// 打印结果
|
||||
flux.doOnNext(response -> {
|
||||
// System.out.println(response);
|
||||
System.out.println(response.getResult().getOutput());
|
||||
System.out.println(Objects.requireNonNull(response.getResult()).getOutput());
|
||||
}).then().block();
|
||||
}
|
||||
|
||||
// TODO @芋艿:暂时没解析 reasoning_content 结果,需要等官方修复
|
||||
@Test
|
||||
@Disabled
|
||||
public void testStream_thinking() {
|
||||
validateApiKey(API_KEY);
|
||||
// 准备参数
|
||||
List<Message> messages = new ArrayList<>();
|
||||
messages.add(new UserMessage("详细分析下,如何设计一个电商系统?"));
|
||||
ZhiPuAiChatOptions options = ZhiPuAiChatOptions.builder()
|
||||
.model("GLM-4.5")
|
||||
DeepSeekChatOptions options = DeepSeekChatOptions.builder()
|
||||
.model(MODEL)
|
||||
.build();
|
||||
|
||||
// 调用
|
||||
@@ -77,7 +92,7 @@ public class ZhiPuAiChatModelTests {
|
||||
// 打印结果
|
||||
flux.doOnNext(response -> {
|
||||
// System.out.println(response);
|
||||
System.out.println(response.getResult().getOutput());
|
||||
System.out.println(Objects.requireNonNull(response.getResult()).getOutput());
|
||||
}).then().block();
|
||||
}
|
||||
|
||||
|
||||
+8
-2
@@ -1,5 +1,6 @@
|
||||
package cn.iocoder.yudao.module.ai.framework.ai.core.model.image;
|
||||
|
||||
import cn.hutool.system.SystemUtil;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.ai.image.ImageOptions;
|
||||
@@ -9,6 +10,8 @@ import org.springframework.ai.openai.OpenAiImageModel;
|
||||
import org.springframework.ai.openai.OpenAiImageOptions;
|
||||
import org.springframework.ai.openai.api.OpenAiImageApi;
|
||||
|
||||
import static cn.iocoder.yudao.module.ai.util.AiUtils.validateApiKey;
|
||||
|
||||
/**
|
||||
* {@link OpenAiImageModel} 集成测试类
|
||||
*
|
||||
@@ -16,17 +19,20 @@ import org.springframework.ai.openai.api.OpenAiImageApi;
|
||||
*/
|
||||
public class OpenAiImageModelTests {
|
||||
|
||||
private static final String API_KEY = SystemUtil.get("OPENAI_API_KEY", "sk-xxxx");
|
||||
|
||||
private final OpenAiImageModel imageModel = new OpenAiImageModel(OpenAiImageApi.builder()
|
||||
.baseUrl("https://api.holdai.top") // apiKey
|
||||
.apiKey("sk-PytRecQlmjEteoa2RRN6cGnwslo72UUPLQVNEMS6K9yjbmpD")
|
||||
.apiKey(API_KEY)
|
||||
.build());
|
||||
|
||||
@Test
|
||||
@Disabled
|
||||
public void testCall() {
|
||||
validateApiKey(API_KEY);
|
||||
// 准备参数
|
||||
ImageOptions options = OpenAiImageOptions.builder()
|
||||
.model(OpenAiImageApi.ImageModel.DALL_E_2.getValue()) // 这个模型比较便宜
|
||||
.model("gpt-image-1")
|
||||
.height(256).width(256)
|
||||
.build();
|
||||
ImagePrompt prompt = new ImagePrompt("中国长城!", options);
|
||||
|
||||
-43
@@ -1,43 +0,0 @@
|
||||
package cn.iocoder.yudao.module.ai.framework.ai.core.model.image;
|
||||
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springaicommunity.qianfan.QianFanImageModel;
|
||||
import org.springaicommunity.qianfan.QianFanImageOptions;
|
||||
import org.springaicommunity.qianfan.api.QianFanImageApi;
|
||||
import org.springframework.ai.image.ImagePrompt;
|
||||
import org.springframework.ai.image.ImageResponse;
|
||||
|
||||
import static cn.iocoder.yudao.module.ai.framework.ai.core.model.image.StabilityAiImageModelTests.viewImage;
|
||||
|
||||
// TODO @芋艿:百度千帆 API 提供了 V2 版本,目前 Spring AI 不兼容,可关键 <https://github.com/spring-projects/spring-ai/issues/2179> 进展
|
||||
|
||||
/**
|
||||
* {@link QianFanImageModel} 集成测试类
|
||||
*/
|
||||
public class QianFanImageTests {
|
||||
|
||||
private final QianFanImageModel imageModel = new QianFanImageModel(
|
||||
new QianFanImageApi("qS8k8dYr2nXunagK4SSU8Xjj", "pHGbx51ql2f0hOyabQvSZezahVC3hh3e")); // 密钥
|
||||
|
||||
@Test
|
||||
@Disabled
|
||||
public void testCall() {
|
||||
// 准备参数
|
||||
// 只支持 1024x1024、768x768、768x1024、1024x768、576x1024、1024x576
|
||||
QianFanImageOptions imageOptions = QianFanImageOptions.builder()
|
||||
.model(QianFanImageApi.ImageModel.Stable_Diffusion_XL.getValue())
|
||||
.width(1024).height(1024)
|
||||
.N(1)
|
||||
.build();
|
||||
ImagePrompt prompt = new ImagePrompt("good", imageOptions);
|
||||
|
||||
// 方法调用
|
||||
ImageResponse response = imageModel.call(prompt);
|
||||
// 打印结果
|
||||
String b64Json = response.getResult().getOutput().getB64Json();
|
||||
System.out.println(response);
|
||||
viewImage(b64Json);
|
||||
}
|
||||
|
||||
}
|
||||
-35
@@ -1,35 +0,0 @@
|
||||
package cn.iocoder.yudao.module.ai.framework.ai.core.model.image;
|
||||
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.ai.image.ImagePrompt;
|
||||
import org.springframework.ai.image.ImageResponse;
|
||||
import org.springframework.ai.zhipuai.ZhiPuAiImageModel;
|
||||
import org.springframework.ai.zhipuai.ZhiPuAiImageOptions;
|
||||
import org.springframework.ai.zhipuai.api.ZhiPuAiImageApi;
|
||||
|
||||
/**
|
||||
* {@link ZhiPuAiImageModel} 集成测试
|
||||
*/
|
||||
public class ZhiPuAiImageModelTests {
|
||||
|
||||
private final ZhiPuAiImageModel imageModel = new ZhiPuAiImageModel(
|
||||
new ZhiPuAiImageApi("78d3228c1d9e5e342a3e1ab349e2dd7b.VXLoq5vrwK2ofboy") // 密钥
|
||||
);
|
||||
|
||||
@Test
|
||||
@Disabled
|
||||
public void testCall() {
|
||||
// 准备参数
|
||||
ZhiPuAiImageOptions imageOptions = ZhiPuAiImageOptions.builder()
|
||||
.model(ZhiPuAiImageApi.ImageModel.CogView_3.getValue())
|
||||
.build();
|
||||
ImagePrompt prompt = new ImagePrompt("万里长城", imageOptions);
|
||||
|
||||
// 方法调用
|
||||
ImageResponse response = imageModel.call(prompt);
|
||||
// 打印结果
|
||||
System.out.println(response);
|
||||
}
|
||||
|
||||
}
|
||||
+4
@@ -48,6 +48,10 @@ public interface ErrorCodeConstants {
|
||||
ErrorCode CODEGEN_MASTER_TABLE_NOT_EXISTS = new ErrorCode(1_001_004_010, "主表(id={})定义不存在,请检查");
|
||||
ErrorCode CODEGEN_SUB_COLUMN_NOT_EXISTS = new ErrorCode(1_001_004_011, "子表的字段(id={})不存在,请检查");
|
||||
ErrorCode CODEGEN_MASTER_GENERATION_FAIL_NO_SUB_TABLE = new ErrorCode(1_001_004_012, "主表生成代码失败,原因:它没有子表");
|
||||
ErrorCode CODEGEN_MASTER_TABLE_NAME_DUPLICATE = new ErrorCode(1_001_004_013,
|
||||
"主子表规范化类名({})重复,请调整主表或子表类名");
|
||||
ErrorCode CODEGEN_MASTER_TABLE_FIELD_DUPLICATE = new ErrorCode(1_001_004_014,
|
||||
"主子表属性名({})重复,请调整主表字段、子表类名或关联关系");
|
||||
|
||||
// ========== 文件配置 1-001-006-000 ==========
|
||||
ErrorCode FILE_CONFIG_NOT_EXISTS = new ErrorCode(1_001_006_000, "文件配置不存在");
|
||||
|
||||
+92
-10
@@ -48,6 +48,9 @@ import java.util.*;
|
||||
|
||||
import static cn.hutool.core.map.MapUtil.getStr;
|
||||
import static cn.hutool.core.text.CharSequenceUtil.*;
|
||||
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
|
||||
import static cn.iocoder.yudao.module.infra.enums.ErrorCodeConstants.CODEGEN_MASTER_TABLE_FIELD_DUPLICATE;
|
||||
import static cn.iocoder.yudao.module.infra.enums.ErrorCodeConstants.CODEGEN_MASTER_TABLE_NAME_DUPLICATE;
|
||||
|
||||
/**
|
||||
* 代码生成的引擎,用于具体生成代码
|
||||
@@ -152,6 +155,16 @@ public class CodegenEngine {
|
||||
vue3UniappFilePath("pages-${table.moduleName}/${table.businessName}/form/index.vue"))
|
||||
.put(CodegenFrontTypeEnum.VUE3_ADMIN_UNIAPP_WOT.getType(), vue3AdminUniappTemplatePath("views/detail/index.vue"),
|
||||
vue3UniappFilePath("pages-${table.moduleName}/${table.businessName}/detail/index.vue"))
|
||||
.put(CodegenFrontTypeEnum.VUE3_ADMIN_UNIAPP_WOT.getType(), vue3AdminUniappTemplatePath("views/index_tree.vue"),
|
||||
vue3UniappFilePath("pages-${table.moduleName}/${table.businessName}/index.vue"))
|
||||
.put(CodegenFrontTypeEnum.VUE3_ADMIN_UNIAPP_WOT.getType(), vue3AdminUniappTemplatePath("components/breadcrumb_tree.vue"),
|
||||
vue3UniappFilePath("pages-${table.moduleName}/${table.businessName}/components/breadcrumb.vue"))
|
||||
.put(CodegenFrontTypeEnum.VUE3_ADMIN_UNIAPP_WOT.getType(), vue3AdminUniappTemplatePath("views/detail_master.vue"),
|
||||
vue3UniappFilePath("pages-${table.moduleName}/${table.businessName}/detail/index.vue"))
|
||||
.put(CodegenFrontTypeEnum.VUE3_ADMIN_UNIAPP_WOT.getType(), vue3AdminUniappTemplatePath("views/detail_erp.vue"),
|
||||
vue3UniappFilePath("pages-${table.moduleName}/${table.businessName}/detail/index.vue"))
|
||||
.put(CodegenFrontTypeEnum.VUE3_ADMIN_UNIAPP_WOT.getType(), vue3AdminUniappTemplatePath("views/form_sub_erp.vue"),
|
||||
vue3UniappFilePath("pages-${table.moduleName}/${table.businessName}/${subSimpleClassName_strikeCase}/form/index.vue"))
|
||||
// VUE3_VBEN2_ANTD_SCHEMA
|
||||
.put(CodegenFrontTypeEnum.VUE3_VBEN2_ANTD_SCHEMA.getType(), vue3VbenTemplatePath("views/data.ts"),
|
||||
vue3VbenFilePath("views/${table.moduleName}/${table.businessName}/${classNameVar}.data.ts"))
|
||||
@@ -380,12 +393,17 @@ public class CodegenEngine {
|
||||
|
||||
// 2. 执行生成
|
||||
Map<String, String> result = Maps.newLinkedHashMapWithExpectedSize(templates.size()); // 有序
|
||||
Map<String, String> generatedSources = Maps.newHashMapWithExpectedSize(templates.size());
|
||||
templates.forEach((vmPath, filePath) -> {
|
||||
// 2.1 特殊:主子表专属逻辑
|
||||
if (isSubTemplate(vmPath)) {
|
||||
generateSubCode(table, subTables, result, vmPath, filePath, bindingMap);
|
||||
// 2.1 特殊:主子表、Uniapp 树表使用独立页面模版
|
||||
if (isVue3AdminUniappTemplate(vmPath) && !matchVue3AdminUniappTemplate(table, vmPath)) {
|
||||
return;
|
||||
// 2.2 特殊:树表专属逻辑
|
||||
}
|
||||
// 2.2 特殊:主子表专属逻辑
|
||||
if (isSubTemplate(vmPath)) {
|
||||
generateSubCode(table, subTables, result, generatedSources, vmPath, filePath, bindingMap);
|
||||
return;
|
||||
// 2.3 特殊:树表专属逻辑
|
||||
} else if (isPageReqVOTemplate(vmPath)) {
|
||||
// 减少多余的类生成,例如说 PageVO.java 类
|
||||
if (CodegenTemplateTypeEnum.isTree(table.getTemplateType())) {
|
||||
@@ -402,23 +420,36 @@ public class CodegenEngine {
|
||||
return;
|
||||
}
|
||||
}
|
||||
// 2.3 默认生成
|
||||
generateCode(result, vmPath, filePath, bindingMap);
|
||||
// 2.4 默认生成
|
||||
generateCode(result, generatedSources, vmPath, filePath, bindingMap);
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
private void generateCode(Map<String, String> result, String vmPath,
|
||||
private void generateCode(Map<String, String> result, Map<String, String> generatedSources, String vmPath,
|
||||
String filePath, Map<String, Object> bindingMap) {
|
||||
filePath = formatFilePath(filePath, bindingMap);
|
||||
registerGeneratedSource(generatedSources, filePath, vmPath, bindingMap);
|
||||
String content = templateEngine.getTemplate(vmPath).render(bindingMap);
|
||||
// 格式化代码
|
||||
content = prettyCode(content, vmPath);
|
||||
result.put(filePath, content);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
static void registerGeneratedSource(Map<String, String> generatedSources, String filePath, String vmPath,
|
||||
Map<String, Object> bindingMap) {
|
||||
String source = vmPath
|
||||
+ (bindingMap.containsKey("subIndex") ? "[subIndex=" + bindingMap.get("subIndex") + "]" : "");
|
||||
String previousSource = generatedSources.putIfAbsent(filePath, source);
|
||||
if (previousSource != null) {
|
||||
throw new IllegalStateException(String.format("生成文件路径重复:%s,来源模板:%s、%s",
|
||||
filePath, previousSource, source));
|
||||
}
|
||||
}
|
||||
|
||||
private void generateSubCode(CodegenTableDO table, List<CodegenTableDO> subTables,
|
||||
Map<String, String> result, String vmPath,
|
||||
Map<String, String> result, Map<String, String> generatedSources, String vmPath,
|
||||
String filePath, Map<String, Object> bindingMap) {
|
||||
// 没有子表,所以不生成
|
||||
if (CollUtil.isEmpty(subTables)) {
|
||||
@@ -441,7 +472,7 @@ public class CodegenEngine {
|
||||
// 逐个生成
|
||||
for (int i = 0; i < subTables.size(); i++) {
|
||||
bindingMap.put("subIndex", i);
|
||||
generateCode(result, vmPath, filePath, bindingMap);
|
||||
generateCode(result, generatedSources, vmPath, filePath, bindingMap);
|
||||
}
|
||||
bindingMap.remove("subIndex");
|
||||
}
|
||||
@@ -482,6 +513,13 @@ public class CodegenEngine {
|
||||
if (StrUtil.count(content, "DICT_TYPE.") == 0) {
|
||||
content = StrUtils.removeLineContains(content, "DICT_TYPE");
|
||||
}
|
||||
// Vue3 Admin UniApp:清理空白行,并补充文件末尾换行,满足 ESLint 格式要求
|
||||
if (vmPath.contains("vue3_admin_uniapp")) {
|
||||
content = content.replaceAll("(?m)^[\\t ]+$", "");
|
||||
if (!content.endsWith("\n")) {
|
||||
content += "\n";
|
||||
}
|
||||
}
|
||||
return content;
|
||||
}
|
||||
|
||||
@@ -533,6 +571,14 @@ public class CodegenEngine {
|
||||
List<String> subClassNameVars = new ArrayList<>();
|
||||
List<String> simpleClassNameUnderlineCases = new ArrayList<>();
|
||||
List<String> subSimpleClassNameStrikeCases = new ArrayList<>();
|
||||
Set<String> masterSymbols = new HashSet<>();
|
||||
masterSymbols.add(simpleClassName);
|
||||
boolean mergeSubTableFields = ObjectUtils.equalsAny(table.getTemplateType(),
|
||||
CodegenTemplateTypeEnum.MASTER_NORMAL.getType(), CodegenTemplateTypeEnum.MASTER_INNER.getType());
|
||||
Set<String> masterFieldSymbols = new HashSet<>();
|
||||
if (mergeSubTableFields) {
|
||||
columns.forEach(column -> masterFieldSymbols.add(column.getJavaField()));
|
||||
}
|
||||
for (int i = 0; i < subTables.size(); i++) {
|
||||
CodegenTableDO subTable = subTables.get(i);
|
||||
List<CodegenColumnDO> subColumns = subColumnsList.get(i);
|
||||
@@ -543,9 +589,18 @@ public class CodegenEngine {
|
||||
subJoinColumnStrikeCases.add(toSymbolCase(subColumn.getJavaField(), '-')); // 将 DictType 转换成 dict-type
|
||||
// className 相关
|
||||
String subSimpleClassName = removePrefix(subTable.getClassName(), upperFirst(subTable.getModuleName()));
|
||||
if (!masterSymbols.add(subSimpleClassName)) {
|
||||
throw exception(CODEGEN_MASTER_TABLE_NAME_DUPLICATE, subSimpleClassName);
|
||||
}
|
||||
String subClassNameVar = lowerFirst(subSimpleClassName);
|
||||
String subFieldName = subClassNameVar
|
||||
+ (Boolean.TRUE.equals(subTable.getSubJoinMany()) ? "s" : "");
|
||||
if (mergeSubTableFields && !masterFieldSymbols.add(subFieldName)) {
|
||||
throw exception(CODEGEN_MASTER_TABLE_FIELD_DUPLICATE, subFieldName);
|
||||
}
|
||||
subSimpleClassNames.add(subSimpleClassName);
|
||||
simpleClassNameUnderlineCases.add(toUnderlineCase(subSimpleClassName)); // 将 DictType 转换成 dict_type
|
||||
subClassNameVars.add(lowerFirst(subSimpleClassName)); // 将 DictType 转换成 dictType,用于变量
|
||||
subClassNameVars.add(subClassNameVar); // 将 DictType 转换成 dictType,用于变量
|
||||
subSimpleClassNameStrikeCases.add(toSymbolCase(subSimpleClassName, '-')); // 将 DictType 转换成 dict-type
|
||||
}
|
||||
bindingMap.put("subPrimaryColumns", subPrimaryColumns);
|
||||
@@ -734,6 +789,33 @@ public class CodegenEngine {
|
||||
return path.contains("_sub");
|
||||
}
|
||||
|
||||
private static boolean isVue3AdminUniappTemplate(String path) {
|
||||
return path.contains("codegen/vue3_admin_uniapp/");
|
||||
}
|
||||
|
||||
/**
|
||||
* 匹配 Uniapp 不同表类型的专用页面模版
|
||||
*/
|
||||
private static boolean matchVue3AdminUniappTemplate(CodegenTableDO table, String path) {
|
||||
boolean tree = CodegenTemplateTypeEnum.isTree(table.getTemplateType());
|
||||
boolean masterSubmit = ObjectUtil.equals(table.getTemplateType(), CodegenTemplateTypeEnum.MASTER_NORMAL.getType())
|
||||
|| ObjectUtil.equals(table.getTemplateType(), CodegenTemplateTypeEnum.MASTER_INNER.getType());
|
||||
boolean masterErp = ObjectUtil.equals(table.getTemplateType(), CodegenTemplateTypeEnum.MASTER_ERP.getType());
|
||||
if (path.contains("_tree")) {
|
||||
return tree;
|
||||
}
|
||||
if (path.contains("detail_master")) {
|
||||
return masterSubmit;
|
||||
}
|
||||
if (path.contains("_erp")) {
|
||||
return masterErp;
|
||||
}
|
||||
if (tree && path.endsWith("views/index.vue.vm")) {
|
||||
return false;
|
||||
}
|
||||
return !(masterSubmit || masterErp) || !path.endsWith("views/detail/index.vue.vm");
|
||||
}
|
||||
|
||||
private static boolean isPageReqVOTemplate(String path) {
|
||||
return path.contains("pageReqVO");
|
||||
}
|
||||
|
||||
@@ -1,36 +1,80 @@
|
||||
#if ($table.templateType != 2)
|
||||
import type { PageParam, PageResult } from '@/http/types'
|
||||
#end
|
||||
import { http } from '@/http/http'
|
||||
|
||||
#set ($primaryJavaType = $primaryColumn.javaType.toLowerCase())
|
||||
#if(${primaryJavaType} == "long" || ${primaryJavaType} == "integer" || ${primaryJavaType} == "short" || ${primaryJavaType} == "double" || ${primaryJavaType} == "bigdecimal" || ${primaryJavaType} == "byte")
|
||||
#if(${primaryJavaType} == "long" || ${primaryJavaType} == "integer" || ${primaryJavaType} == "short" || ${primaryJavaType} == "float" || ${primaryJavaType} == "double" || ${primaryJavaType} == "bigdecimal" || ${primaryJavaType} == "byte")
|
||||
#set ($primaryTsType = "number")
|
||||
#else
|
||||
#set ($primaryTsType = "string")
|
||||
#end
|
||||
/** ${table.classComment}信息 */
|
||||
export interface ${simpleClassName} {
|
||||
#foreach ($column in $columns)
|
||||
#if ($column.primaryKey || $column.createOperation || $column.updateOperation || $column.listOperationResult)
|
||||
#set ($javaType = $column.javaType.toLowerCase())
|
||||
#set ($javaFieldLower = $column.javaField.toLowerCase())
|
||||
#set ($optional = $column.nullable || $column.primaryKey || $javaFieldLower == "createtime" || $javaFieldLower == "updatetime")
|
||||
#if(${javaType} == "long" || ${javaType} == "integer" || ${javaType} == "short" || ${javaType} == "double" || ${javaType} == "bigdecimal" || ${javaType} == "byte")
|
||||
#foreach ($subTable in $subTables)
|
||||
#set ($index = $foreach.count - 1)
|
||||
#set ($subSimpleClassName = $subSimpleClassNames.get($index))
|
||||
#set ($subColumns = $subColumnsList.get($index))
|
||||
#set ($subJoinColumn = $subJoinColumns.get($index))
|
||||
/** ${subTable.classComment}信息 */
|
||||
export interface ${subSimpleClassName} {
|
||||
#foreach ($column in $subColumns)
|
||||
#if ($column.primaryKey || $column.createOperation || $column.updateOperation || $column.listOperationResult)
|
||||
#set ($javaType = $column.javaType.toLowerCase())
|
||||
#set ($optional = $column.nullable || $column.primaryKey || ($table.templateType != 11 && $column.id == $subJoinColumn.id))
|
||||
#if(${javaType} == "long" || ${javaType} == "integer" || ${javaType} == "short" || ${javaType} == "float" || ${javaType} == "double" || ${javaType} == "bigdecimal" || ${javaType} == "byte")
|
||||
${column.javaField}#if($optional)?#end: number
|
||||
#elseif(${javaType} == "date" || ${javaType} == "localdate" || ${javaType} == "localdatetime")
|
||||
${column.javaField}#if($optional)?#end: Date
|
||||
#elseif(${javaType} == "boolean")
|
||||
#elseif(${javaType} == "date" || ${javaType} == "localdate" || ${javaType} == "localdatetime")
|
||||
${column.javaField}#if($optional)?#end: number
|
||||
#elseif(${javaType} == "boolean")
|
||||
${column.javaField}#if($optional)?#end: boolean
|
||||
#else
|
||||
#else
|
||||
${column.javaField}#if($optional)?#end: string
|
||||
#end
|
||||
#end
|
||||
#end
|
||||
#end
|
||||
#end
|
||||
}
|
||||
|
||||
#end
|
||||
/** ${table.classComment}信息 */
|
||||
export interface ${simpleClassName} {
|
||||
#foreach ($column in $columns)
|
||||
#if ($column.primaryKey || $column.createOperation || $column.updateOperation || $column.listOperationResult)
|
||||
#set ($javaType = $column.javaType.toLowerCase())
|
||||
#set ($optional = $column.nullable || $column.primaryKey)
|
||||
#if(${javaType} == "long" || ${javaType} == "integer" || ${javaType} == "short" || ${javaType} == "float" || ${javaType} == "double" || ${javaType} == "bigdecimal" || ${javaType} == "byte")
|
||||
${column.javaField}#if($optional)?#end: number
|
||||
#elseif(${javaType} == "date" || ${javaType} == "localdate" || ${javaType} == "localdatetime")
|
||||
${column.javaField}#if($optional)?#end: number
|
||||
#elseif(${javaType} == "boolean")
|
||||
${column.javaField}#if($optional)?#end: boolean
|
||||
#else
|
||||
${column.javaField}#if($optional)?#end: string
|
||||
#end
|
||||
#end
|
||||
#end
|
||||
#if ($table.templateType == 2)
|
||||
children?: ${simpleClassName}[]
|
||||
#end
|
||||
#if ($table.templateType == 10 || $table.templateType == 12)
|
||||
#foreach ($subTable in $subTables)
|
||||
#set ($index = $foreach.count - 1)
|
||||
#set ($subSimpleClassName = $subSimpleClassNames.get($index))
|
||||
#set ($subClassNameVar = $subClassNameVars.get($index))
|
||||
${subClassNameVar}#if($subTable.subJoinMany)s#end?: ${subSimpleClassName}#if($subTable.subJoinMany)[]#else | null#end
|
||||
#end
|
||||
#end
|
||||
}
|
||||
|
||||
#if ($table.templateType == 2)
|
||||
/** 获取${table.classComment}列表 */
|
||||
export function get${simpleClassName}List(params?: Record<string, any>) {
|
||||
return http.get<${simpleClassName}[]>('/${table.moduleName}/${simpleClassName_strikeCase}/list', params)
|
||||
}
|
||||
#else
|
||||
/** 获取${table.classComment}分页列表 */
|
||||
export function get${simpleClassName}Page(params: PageParam) {
|
||||
return http.get<PageResult<${simpleClassName}>>('/${table.moduleName}/${simpleClassName_strikeCase}/page', params)
|
||||
}
|
||||
#end
|
||||
|
||||
/** 获取${table.classComment}详情 */
|
||||
export function get${simpleClassName}(id: ${primaryTsType}) {
|
||||
@@ -39,7 +83,7 @@ export function get${simpleClassName}(id: ${primaryTsType}) {
|
||||
|
||||
/** 创建${table.classComment} */
|
||||
export function create${simpleClassName}(data: ${simpleClassName}) {
|
||||
return http.post<number>('/${table.moduleName}/${simpleClassName_strikeCase}/create', data)
|
||||
return http.post<${primaryTsType}>('/${table.moduleName}/${simpleClassName_strikeCase}/create', data)
|
||||
}
|
||||
|
||||
/** 更新${table.classComment} */
|
||||
@@ -51,3 +95,65 @@ export function update${simpleClassName}(data: ${simpleClassName}) {
|
||||
export function delete${simpleClassName}(id: ${primaryTsType}) {
|
||||
return http.delete<boolean>(`/${table.moduleName}/${simpleClassName_strikeCase}/delete?id=${id}`)
|
||||
}
|
||||
#foreach ($subTable in $subTables)
|
||||
#set ($index = $foreach.count - 1)
|
||||
#set ($subSimpleClassName = $subSimpleClassNames.get($index))
|
||||
#set ($subPrimaryColumn = $subPrimaryColumns.get($index))
|
||||
#set ($subJoinColumn = $subJoinColumns.get($index))
|
||||
#set ($subPrimaryJavaType = $subPrimaryColumn.javaType.toLowerCase())
|
||||
#if(${subPrimaryJavaType} == "long" || ${subPrimaryJavaType} == "integer" || ${subPrimaryJavaType} == "short" || ${subPrimaryJavaType} == "float" || ${subPrimaryJavaType} == "double" || ${subPrimaryJavaType} == "bigdecimal" || ${subPrimaryJavaType} == "byte")
|
||||
#set ($subPrimaryTsType = "number")
|
||||
#else
|
||||
#set ($subPrimaryTsType = "string")
|
||||
#end
|
||||
#set ($subJoinJavaType = $subJoinColumn.javaType.toLowerCase())
|
||||
#if(${subJoinJavaType} == "long" || ${subJoinJavaType} == "integer" || ${subJoinJavaType} == "short" || ${subJoinJavaType} == "float" || ${subJoinJavaType} == "double" || ${subJoinJavaType} == "bigdecimal" || ${subJoinJavaType} == "byte")
|
||||
#set ($subJoinTsType = "number")
|
||||
#else
|
||||
#set ($subJoinTsType = "string")
|
||||
#end
|
||||
#set ($SubJoinColumnName = $subJoinColumn.javaField.substring(0,1).toUpperCase() + $subJoinColumn.javaField.substring(1))
|
||||
#set ($subSimpleClassName_strikeCase = $subSimpleClassName_strikeCases.get($index))
|
||||
#set ($subJoinColumn_strikeCase = $subJoinColumn_strikeCases.get($index))
|
||||
|
||||
// ==================== 子表:${subTable.classComment} ====================
|
||||
#if ($table.templateType == 11)
|
||||
|
||||
/** 获取${subTable.classComment}分页列表 */
|
||||
export function get${subSimpleClassName}Page(params: PageParam & { ${subJoinColumn.javaField}: ${subJoinTsType} }) {
|
||||
return http.get<PageResult<${subSimpleClassName}>>('/${table.moduleName}/${simpleClassName_strikeCase}/${subSimpleClassName_strikeCase}/page', params)
|
||||
}
|
||||
|
||||
/** 获取${subTable.classComment}详情 */
|
||||
export function get${subSimpleClassName}(id: ${subPrimaryTsType}) {
|
||||
return http.get<${subSimpleClassName}>(`/${table.moduleName}/${simpleClassName_strikeCase}/${subSimpleClassName_strikeCase}/get?id=${id}`)
|
||||
}
|
||||
|
||||
/** 创建${subTable.classComment} */
|
||||
export function create${subSimpleClassName}(data: ${subSimpleClassName}) {
|
||||
return http.post<${subPrimaryTsType}>('/${table.moduleName}/${simpleClassName_strikeCase}/${subSimpleClassName_strikeCase}/create', data)
|
||||
}
|
||||
|
||||
/** 更新${subTable.classComment} */
|
||||
export function update${subSimpleClassName}(data: ${subSimpleClassName}) {
|
||||
return http.put<boolean>('/${table.moduleName}/${simpleClassName_strikeCase}/${subSimpleClassName_strikeCase}/update', data)
|
||||
}
|
||||
|
||||
/** 删除${subTable.classComment} */
|
||||
export function delete${subSimpleClassName}(id: ${subPrimaryTsType}) {
|
||||
return http.delete<boolean>(`/${table.moduleName}/${simpleClassName_strikeCase}/${subSimpleClassName_strikeCase}/delete?id=${id}`)
|
||||
}
|
||||
#elseif ($subTable.subJoinMany)
|
||||
|
||||
/** 获取${subTable.classComment}列表 */
|
||||
export function get${subSimpleClassName}ListBy${SubJoinColumnName}(${subJoinColumn.javaField}: ${subJoinTsType}) {
|
||||
return http.get<${subSimpleClassName}[]>(`/${table.moduleName}/${simpleClassName_strikeCase}/${subSimpleClassName_strikeCase}/list-by-${subJoinColumn_strikeCase}?${subJoinColumn.javaField}=#[[${]]#${subJoinColumn.javaField}#[[}]]#`)
|
||||
}
|
||||
#else
|
||||
|
||||
/** 获取${subTable.classComment} */
|
||||
export function get${subSimpleClassName}By${SubJoinColumnName}(${subJoinColumn.javaField}: ${subJoinTsType}) {
|
||||
return http.get<${subSimpleClassName} | null>(`/${table.moduleName}/${simpleClassName_strikeCase}/${subSimpleClassName_strikeCase}/get-by-${subJoinColumn_strikeCase}?${subJoinColumn.javaField}=#[[${]]#${subJoinColumn.javaField}#[[}]]#`)
|
||||
}
|
||||
#end
|
||||
#end
|
||||
|
||||
+87
@@ -0,0 +1,87 @@
|
||||
<template>
|
||||
<view class="bg-white px-24rpx py-16rpx">
|
||||
<scroll-view scroll-x class="whitespace-nowrap">
|
||||
<view class="inline-flex items-center">
|
||||
<view
|
||||
class="flex items-center text-28rpx"
|
||||
:class="items.length > 0 ? 'text-[#1890ff]' : 'text-[#333]'"
|
||||
@click="handleClick(-1)"
|
||||
>
|
||||
<text>全部${table.classComment}</text>
|
||||
</view>
|
||||
<template v-for="(item, index) in items" :key="item.id">
|
||||
<wd-icon name="arrow-right" size="12px" color="#999" custom-class="mx-8rpx" />
|
||||
<view
|
||||
class="flex items-center text-28rpx"
|
||||
:class="index < items.length - 1 ? 'text-[#1890ff]' : 'text-[#333]'"
|
||||
@click="handleClick(index)"
|
||||
>
|
||||
<text>{{ item.name }}</text>
|
||||
</view>
|
||||
</template>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, watch } from 'vue'
|
||||
|
||||
interface BreadcrumbItem {
|
||||
id: number
|
||||
name: string
|
||||
}
|
||||
|
||||
const props = defineProps<{
|
||||
modelValue: number
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
'update:modelValue': [value: number]
|
||||
}>()
|
||||
|
||||
const items = ref<BreadcrumbItem[]>([]) // 面包屑数据
|
||||
|
||||
/** 监听外部值变化 */
|
||||
watch(() => props.modelValue, (value) => {
|
||||
if (value === 0) {
|
||||
items.value = []
|
||||
}
|
||||
})
|
||||
|
||||
/** 点击面包屑 */
|
||||
function handleClick(index: number) {
|
||||
if (index === -1) {
|
||||
reset()
|
||||
return
|
||||
}
|
||||
if (index < items.value.length - 1) {
|
||||
items.value = items.value.slice(0, index + 1)
|
||||
emit('update:modelValue', items.value[index].id)
|
||||
}
|
||||
}
|
||||
|
||||
/** 进入子层级 */
|
||||
function enter(item: BreadcrumbItem) {
|
||||
items.value.push(item)
|
||||
emit('update:modelValue', item.id)
|
||||
}
|
||||
|
||||
/** 返回上一层级 */
|
||||
function back(): boolean {
|
||||
if (items.value.length === 0) {
|
||||
return false
|
||||
}
|
||||
items.value.pop()
|
||||
emit('update:modelValue', items.value.at(-1)?.id ?? 0)
|
||||
return true
|
||||
}
|
||||
|
||||
/** 重置面包屑 */
|
||||
function reset() {
|
||||
items.value = []
|
||||
emit('update:modelValue', 0)
|
||||
}
|
||||
|
||||
defineExpose({ back, enter, reset })
|
||||
</script>
|
||||
+2
-2
@@ -145,7 +145,7 @@ const formData = reactive({
|
||||
#else
|
||||
${column.javaField}: undefined as number | undefined,
|
||||
#end
|
||||
#elseif(${javaType} == "long" || ${javaType} == "integer" || ${javaType} == "short" || ${javaType} == "double" || ${javaType} == "bigdecimal" || ${javaType} == "byte")
|
||||
#elseif(${javaType} == "long" || ${javaType} == "integer" || ${javaType} == "short" || ${javaType} == "float" || ${javaType} == "double" || ${javaType} == "bigdecimal" || ${javaType} == "byte")
|
||||
${column.javaField}: undefined as number | undefined,
|
||||
#elseif(${javaType} == "boolean")
|
||||
${column.javaField}: undefined as boolean | undefined,
|
||||
@@ -174,7 +174,7 @@ const placeholder = computed(() => {
|
||||
conditions.push(`${comment}:#[[${]]#getDictLabel(DICT_TYPE.${dictType.toUpperCase()}, formData.${javaField})#[[}]]#`)
|
||||
}
|
||||
#else
|
||||
#if(${javaType} == "long" || ${javaType} == "integer" || ${javaType} == "short" || ${javaType} == "double" || ${javaType} == "bigdecimal" || ${javaType} == "byte" || ${javaType} == "boolean")
|
||||
#if(${javaType} == "long" || ${javaType} == "integer" || ${javaType} == "short" || ${javaType} == "float" || ${javaType} == "double" || ${javaType} == "bigdecimal" || ${javaType} == "byte" || ${javaType} == "boolean")
|
||||
if (formData.${javaField} !== undefined) {
|
||||
conditions.push(`${comment}:#[[${]]#formData.${javaField}#[[}]]#`)
|
||||
}
|
||||
|
||||
+28
-21
@@ -39,7 +39,7 @@
|
||||
</wd-button>
|
||||
<wd-button
|
||||
v-if="hasAccessByCodes(['${permissionPrefix}:delete'])"
|
||||
class="flex-1" type="error" :loading="deleting" @click="handleDelete"
|
||||
class="flex-1" type="danger" :loading="deleting" @click="handleDelete"
|
||||
>
|
||||
删除
|
||||
</wd-button>
|
||||
@@ -49,6 +49,12 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
#set ($primaryJavaType = $primaryColumn.javaType.toLowerCase())
|
||||
#if(${primaryJavaType} == "long" || ${primaryJavaType} == "integer" || ${primaryJavaType} == "short" || ${primaryJavaType} == "float" || ${primaryJavaType} == "double" || ${primaryJavaType} == "bigdecimal" || ${primaryJavaType} == "byte")
|
||||
#set ($primaryTsType = "number")
|
||||
#else
|
||||
#set ($primaryTsType = "string")
|
||||
#end
|
||||
#set ($hasDict = 0)
|
||||
#foreach($column in $columns)
|
||||
#if ($hasDict == 0 && $column.dictType && "" != $column.dictType)
|
||||
@@ -64,6 +70,7 @@
|
||||
import type { ${simpleClassName} } from '@/api/${table.moduleName}/${table.businessName}'
|
||||
import { onUnload } from '@dcloudio/uni-app'
|
||||
import { onMounted, ref } from 'vue'
|
||||
import { useDialog } from '@wot-ui/ui/components/wd-dialog'
|
||||
import { useToast } from '@wot-ui/ui/components/wd-toast'
|
||||
import { delete${simpleClassName}, get${simpleClassName} } from '@/api/${table.moduleName}/${table.businessName}'
|
||||
import { useAccess } from '@/hooks/useAccess'
|
||||
@@ -76,7 +83,7 @@ import { formatDateTime } from '@/utils/date'
|
||||
#end
|
||||
|
||||
const props = defineProps<{
|
||||
id?: number | any
|
||||
id?: ${primaryTsType}
|
||||
}>()
|
||||
|
||||
definePage({
|
||||
@@ -87,6 +94,7 @@ definePage({
|
||||
})
|
||||
|
||||
const { hasAccessByCodes } = useAccess()
|
||||
const dialog = useDialog()
|
||||
const toast = useToast()
|
||||
const formData = ref<${simpleClassName}>() // 详情数据
|
||||
const deleting = ref(false) // 删除状态
|
||||
@@ -117,28 +125,27 @@ function handleEdit() {
|
||||
}
|
||||
|
||||
/** 删除${table.classComment} */
|
||||
function handleDelete() {
|
||||
async function handleDelete() {
|
||||
if (!props.id) {
|
||||
return
|
||||
}
|
||||
uni.showModal({
|
||||
title: '提示',
|
||||
content: '确定要删除该${table.classComment}吗?',
|
||||
success: async (res) => {
|
||||
if (!res.confirm) {
|
||||
return
|
||||
}
|
||||
deleting.value = true
|
||||
try {
|
||||
await delete${simpleClassName}(props.id)
|
||||
toast.success('删除成功')
|
||||
uni.$emit('${table.moduleName}:${table.businessName}:reload')
|
||||
delay(handleBack)
|
||||
} finally {
|
||||
deleting.value = false
|
||||
}
|
||||
},
|
||||
})
|
||||
try {
|
||||
await dialog.confirm({
|
||||
title: '提示',
|
||||
msg: '确定要删除该${table.classComment}吗?',
|
||||
})
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
deleting.value = true
|
||||
try {
|
||||
await delete${simpleClassName}(props.id)
|
||||
toast.success('删除成功')
|
||||
uni.$emit('${table.moduleName}:${table.businessName}:reload')
|
||||
delay(handleBack)
|
||||
} finally {
|
||||
deleting.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 初始化 */
|
||||
|
||||
+258
@@ -0,0 +1,258 @@
|
||||
<template>
|
||||
<view class="yd-page-container">
|
||||
<!-- 顶部导航栏 -->
|
||||
<wd-navbar title="${table.classComment}详情" left-arrow placeholder safe-area-inset-top fixed @click-left="handleBack" />
|
||||
|
||||
<view class="pb-160rpx">
|
||||
<!-- 主表详情 -->
|
||||
<wd-cell-group border title="${table.classComment}信息">
|
||||
#foreach($column in $columns)
|
||||
#if ($column.primaryKey || $column.listOperationResult || $column.createOperation || $column.updateOperation)
|
||||
#if ($column.dictType && "" != $column.dictType)
|
||||
<wd-cell title="${column.columnComment}">
|
||||
<dict-tag :type="DICT_TYPE.${column.dictType.toUpperCase()}" :value="formData?.${column.javaField}" />
|
||||
</wd-cell>
|
||||
#elseif ($column.javaType == "LocalDateTime")
|
||||
<wd-cell title="${column.columnComment}" :value="formatDateTime(formData?.${column.javaField}) || '-'" />
|
||||
#else
|
||||
<wd-cell title="${column.columnComment}" :value="formData?.${column.javaField} ?? '-'" />
|
||||
#end
|
||||
#end
|
||||
#end
|
||||
</wd-cell-group>
|
||||
|
||||
#foreach ($subTable in $subTables)
|
||||
#set ($index = $foreach.count - 1)
|
||||
#set ($subColumns = $subColumnsList.get($index))
|
||||
#set ($subClassNameVar = $subClassNameVars.get($index))
|
||||
#set ($subPrimaryColumn = $subPrimaryColumns.get($index))
|
||||
#set ($pageSize = 10)
|
||||
#if (!$subTable.subJoinMany)
|
||||
#set ($pageSize = 1)
|
||||
#end
|
||||
<!-- 子表:${subTable.classComment} -->
|
||||
<view class="mt-20rpx flex items-center justify-between px-24rpx py-16rpx">
|
||||
<text class="text-28rpx text-[#333] font-semibold">${subTable.classComment}({{ ${subClassNameVar}Total }})</text>
|
||||
<wd-button
|
||||
#if (!$subTable.subJoinMany)
|
||||
v-if="${subClassNameVar}Total === 0 && hasAccessByCodes(['${permissionPrefix}:create'])"
|
||||
#else
|
||||
v-if="hasAccessByCodes(['${permissionPrefix}:create'])"
|
||||
#end
|
||||
size="small" type="primary" variant="plain" @click="open${subClassNameVar.substring(0,1).toUpperCase()}${subClassNameVar.substring(1)}()"
|
||||
>
|
||||
添加
|
||||
</wd-button>
|
||||
</view>
|
||||
<view class="px-24rpx">
|
||||
<z-paging
|
||||
ref="${subClassNameVar}PagingRef"
|
||||
v-model="${subClassNameVar}List"
|
||||
:fixed="false"
|
||||
:auto="false"
|
||||
height="560rpx"
|
||||
:default-page-size="${pageSize}"
|
||||
:refresher-enabled="false"
|
||||
:inside-more="true"
|
||||
:to-bottom-loading-more-enabled="false"
|
||||
loading-more-default-text="点击加载更多"
|
||||
empty-view-text="暂无${subTable.classComment}"
|
||||
@query="query${subClassNameVar.substring(0,1).toUpperCase()}${subClassNameVar.substring(1)}List"
|
||||
>
|
||||
<view
|
||||
v-for="item in ${subClassNameVar}List"
|
||||
:key="item.${subPrimaryColumn.javaField}"
|
||||
class="mb-16rpx rounded-12rpx bg-white p-24rpx shadow-sm"
|
||||
>
|
||||
#foreach ($column in $subColumns)
|
||||
#if ($column.listOperationResult && !$column.primaryKey)
|
||||
<view class="mb-8rpx text-28rpx text-[#666]">
|
||||
${column.columnComment}:{{ item.${column.javaField} ?? '-' }}
|
||||
</view>
|
||||
#end
|
||||
#end
|
||||
<view class="mt-16rpx flex justify-end gap-16rpx">
|
||||
<wd-button size="small" variant="plain" @click="open${subClassNameVar.substring(0,1).toUpperCase()}${subClassNameVar.substring(1)}(item)">
|
||||
编辑
|
||||
</wd-button>
|
||||
<wd-button size="small" type="danger" variant="plain" @click="remove${subClassNameVar.substring(0,1).toUpperCase()}${subClassNameVar.substring(1)}(item)">
|
||||
删除
|
||||
</wd-button>
|
||||
</view>
|
||||
</view>
|
||||
</z-paging>
|
||||
</view>
|
||||
#end
|
||||
</view>
|
||||
|
||||
<!-- 底部操作按钮 -->
|
||||
<view class="yd-detail-footer">
|
||||
<view class="yd-detail-footer-actions">
|
||||
<wd-button v-if="hasAccessByCodes(['${permissionPrefix}:update'])" class="flex-1" type="warning" @click="handleEdit">
|
||||
编辑
|
||||
</wd-button>
|
||||
<wd-button v-if="hasAccessByCodes(['${permissionPrefix}:delete'])" class="flex-1" type="danger" :loading="deleting" @click="handleDelete">
|
||||
删除
|
||||
</wd-button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
#set ($primaryJavaType = $primaryColumn.javaType.toLowerCase())
|
||||
#if(${primaryJavaType} == "long" || ${primaryJavaType} == "integer" || ${primaryJavaType} == "short" || ${primaryJavaType} == "float" || ${primaryJavaType} == "double" || ${primaryJavaType} == "bigdecimal" || ${primaryJavaType} == "byte")
|
||||
#set ($primaryTsType = "number")
|
||||
#else
|
||||
#set ($primaryTsType = "string")
|
||||
#end
|
||||
#set ($hasDict = 0)
|
||||
#set ($hasDateTime = 0)
|
||||
#foreach($column in $columns)
|
||||
#if ($column.dictType && "" != $column.dictType)
|
||||
#set ($hasDict = 1)
|
||||
#end
|
||||
#if ($column.javaType == "LocalDateTime")
|
||||
#set ($hasDateTime = 1)
|
||||
#end
|
||||
#end
|
||||
import type {
|
||||
${simpleClassName},
|
||||
#foreach ($subSimpleClassName in $subSimpleClassNames)
|
||||
${subSimpleClassName},
|
||||
#end
|
||||
} from '@/api/${table.moduleName}/${table.businessName}'
|
||||
import { onShow } from '@dcloudio/uni-app'
|
||||
import { nextTick, ref } from 'vue'
|
||||
import { useDialog } from '@wot-ui/ui/components/wd-dialog'
|
||||
import { useToast } from '@wot-ui/ui/components/wd-toast'
|
||||
import {
|
||||
delete${simpleClassName},
|
||||
#foreach ($subSimpleClassName in $subSimpleClassNames)
|
||||
delete${subSimpleClassName},
|
||||
#end
|
||||
get${simpleClassName},
|
||||
#foreach ($subSimpleClassName in $subSimpleClassNames)
|
||||
get${subSimpleClassName}Page,
|
||||
#end
|
||||
} from '@/api/${table.moduleName}/${table.businessName}'
|
||||
import { useAccess } from '@/hooks/useAccess'
|
||||
import { delay, navigateBackPlus } from '@/utils'
|
||||
#if ($hasDict == 1)
|
||||
import { DICT_TYPE } from '@/utils/constants'
|
||||
#end
|
||||
#if ($hasDateTime == 1)
|
||||
import { formatDateTime } from '@/utils/date'
|
||||
#end
|
||||
|
||||
const props = defineProps<{ id?: ${primaryTsType} }>()
|
||||
|
||||
definePage({
|
||||
style: { navigationBarTitleText: '', navigationStyle: 'custom' },
|
||||
})
|
||||
|
||||
const { hasAccessByCodes } = useAccess()
|
||||
const dialog = useDialog()
|
||||
const toast = useToast()
|
||||
const formData = ref<${simpleClassName}>() // 主表数据
|
||||
#foreach ($subTable in $subTables)
|
||||
#set ($index = $foreach.count - 1)
|
||||
#set ($subSimpleClassName = $subSimpleClassNames.get($index))
|
||||
#set ($subClassNameVar = $subClassNameVars.get($index))
|
||||
const ${subClassNameVar}List = ref<${subSimpleClassName}[]>([]) // ${subTable.classComment}列表
|
||||
const ${subClassNameVar}Total = ref(0) // ${subTable.classComment}总数
|
||||
const ${subClassNameVar}PagingRef = ref<any>() // ${subTable.classComment}分页组件引用
|
||||
#end
|
||||
const deleting = ref(false) // 主表删除状态
|
||||
|
||||
/** 返回上一页 */
|
||||
function handleBack() {
|
||||
navigateBackPlus('/pages-${table.moduleName}/${table.businessName}/index')
|
||||
}
|
||||
|
||||
/** 加载主表 */
|
||||
async function loadMain() {
|
||||
if (props.id) {
|
||||
formData.value = await get${simpleClassName}(props.id)
|
||||
}
|
||||
}
|
||||
|
||||
#foreach ($subTable in $subTables)
|
||||
#set ($index = $foreach.count - 1)
|
||||
#set ($subSimpleClassName = $subSimpleClassNames.get($index))
|
||||
#set ($subClassNameVar = $subClassNameVars.get($index))
|
||||
#set ($subJoinColumn = $subJoinColumns.get($index))
|
||||
#set ($subPrimaryColumn = $subPrimaryColumns.get($index))
|
||||
/** 查询${subTable.classComment}列表 */
|
||||
async function query${subClassNameVar.substring(0,1).toUpperCase()}${subClassNameVar.substring(1)}List(pageNo: number, pageSize: number) {
|
||||
if (!props.id) {
|
||||
${subClassNameVar}PagingRef.value?.completeByTotal([], 0)
|
||||
return
|
||||
}
|
||||
try {
|
||||
const data = await get${subSimpleClassName}Page({ ${subJoinColumn.javaField}: props.id, pageNo, pageSize })
|
||||
${subClassNameVar}Total.value = data.total
|
||||
${subClassNameVar}PagingRef.value?.completeByTotal(data.list, data.total)
|
||||
} catch {
|
||||
${subClassNameVar}PagingRef.value?.complete(false)
|
||||
}
|
||||
}
|
||||
|
||||
/** 打开${subTable.classComment}表单 */
|
||||
function open${subClassNameVar.substring(0,1).toUpperCase()}${subClassNameVar.substring(1)}(item?: ${subSimpleClassName}) {
|
||||
const query = [
|
||||
`${subJoinColumn.javaField}=#[[${]]#props.id#[[}]]#`,
|
||||
item?.${subPrimaryColumn.javaField} ? `id=#[[${]]#item.${subPrimaryColumn.javaField}#[[}]]#` : '',
|
||||
].filter(Boolean).join('&')
|
||||
uni.navigateTo({ url: `/pages-${table.moduleName}/${table.businessName}/${subSimpleClassName_strikeCases.get($index)}/form/index?#[[${]]#query#[[}]]#` })
|
||||
}
|
||||
|
||||
/** 删除${subTable.classComment} */
|
||||
async function remove${subClassNameVar.substring(0,1).toUpperCase()}${subClassNameVar.substring(1)}(item: ${subSimpleClassName}) {
|
||||
try {
|
||||
await dialog.confirm({ title: '提示', msg: '确定要删除该${subTable.classComment}吗?' })
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
await delete${subSimpleClassName}(item.${subPrimaryColumn.javaField}!)
|
||||
toast.success('删除成功')
|
||||
${subClassNameVar}PagingRef.value?.reload()
|
||||
}
|
||||
|
||||
#end
|
||||
/** 编辑主表 */
|
||||
function handleEdit() {
|
||||
uni.navigateTo({ url: `/pages-${table.moduleName}/${table.businessName}/form/index?id=#[[${]]#props.id#[[}]]#` })
|
||||
}
|
||||
|
||||
/** 删除主表 */
|
||||
async function handleDelete() {
|
||||
if (!props.id) {
|
||||
return
|
||||
}
|
||||
try {
|
||||
await dialog.confirm({ title: '提示', msg: '确定要删除该${table.classComment}吗?' })
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
deleting.value = true
|
||||
try {
|
||||
await delete${simpleClassName}(props.id)
|
||||
toast.success('删除成功')
|
||||
uni.$emit('${table.moduleName}:${table.businessName}:reload')
|
||||
delay(handleBack)
|
||||
} finally {
|
||||
deleting.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 进入页面加载(含编辑和子表返回后刷新) */
|
||||
onShow(() => {
|
||||
loadMain()
|
||||
nextTick(() => {
|
||||
#foreach ($subClassNameVar in $subClassNameVars)
|
||||
${subClassNameVar}PagingRef.value?.reload()
|
||||
#end
|
||||
})
|
||||
})
|
||||
</script>
|
||||
+239
@@ -0,0 +1,239 @@
|
||||
<template>
|
||||
<view class="yd-page-container">
|
||||
<!-- 顶部导航栏 -->
|
||||
<wd-navbar
|
||||
title="${table.classComment}详情"
|
||||
left-arrow placeholder safe-area-inset-top fixed
|
||||
@click-left="handleBack"
|
||||
/>
|
||||
|
||||
<view class="pb-160rpx">
|
||||
<!-- 主表详情 -->
|
||||
<wd-cell-group border title="${table.classComment}信息">
|
||||
#foreach($column in $columns)
|
||||
#if ($column.primaryKey || $column.listOperationResult || $column.createOperation || $column.updateOperation)
|
||||
#if ($column.dictType && "" != $column.dictType)
|
||||
<wd-cell title="${column.columnComment}">
|
||||
<dict-tag :type="DICT_TYPE.${column.dictType.toUpperCase()}" :value="formData?.${column.javaField}" />
|
||||
</wd-cell>
|
||||
#elseif ($column.javaType == "LocalDateTime")
|
||||
<wd-cell title="${column.columnComment}" :value="formatDateTime(formData?.${column.javaField}) || '-'" />
|
||||
#else
|
||||
<wd-cell title="${column.columnComment}" :value="formData?.${column.javaField} ?? '-'" />
|
||||
#end
|
||||
#end
|
||||
#end
|
||||
</wd-cell-group>
|
||||
|
||||
#foreach ($subTable in $subTables)
|
||||
#set ($index = $foreach.count - 1)
|
||||
#set ($subColumns = $subColumnsList.get($index))
|
||||
#set ($subClassNameVar = $subClassNameVars.get($index))
|
||||
#set ($subPrimaryColumn = $subPrimaryColumns.get($index))
|
||||
<!-- 子表:${subTable.classComment} -->
|
||||
<view class="mt-20rpx px-24rpx py-16rpx text-28rpx text-[#999]">
|
||||
${subTable.classComment}#if($subTable.subJoinMany)({{ ${subClassNameVar}s.length }})#end
|
||||
</view>
|
||||
<view class="px-24rpx">
|
||||
#if ($subTable.subJoinMany)
|
||||
<view v-if="!${subClassNameVar}s.length" class="rounded-12rpx bg-white py-40rpx text-center text-26rpx text-[#999]">
|
||||
暂无${subTable.classComment}
|
||||
</view>
|
||||
<view
|
||||
v-for="item in ${subClassNameVar}s"
|
||||
:key="item.${subPrimaryColumn.javaField}"
|
||||
class="mb-16rpx rounded-12rpx bg-white p-24rpx shadow-sm"
|
||||
>
|
||||
#foreach ($column in $subColumns)
|
||||
#if ($column.listOperationResult && !$column.primaryKey)
|
||||
<view class="mb-8rpx text-28rpx text-[#666]">
|
||||
${column.columnComment}:{{ item.${column.javaField} ?? '-' }}
|
||||
</view>
|
||||
#end
|
||||
#end
|
||||
</view>
|
||||
#else
|
||||
<view class="rounded-12rpx bg-white p-24rpx shadow-sm">
|
||||
<template v-if="${subClassNameVar}">
|
||||
#foreach ($column in $subColumns)
|
||||
#if ($column.listOperationResult && !$column.primaryKey)
|
||||
<view class="mb-8rpx text-28rpx text-[#666]">
|
||||
${column.columnComment}:{{ ${subClassNameVar}.${column.javaField} ?? '-' }}
|
||||
</view>
|
||||
#end
|
||||
#end
|
||||
</template>
|
||||
<view v-else class="text-center text-26rpx text-[#999]">
|
||||
暂无${subTable.classComment}
|
||||
</view>
|
||||
</view>
|
||||
#end
|
||||
</view>
|
||||
#end
|
||||
</view>
|
||||
|
||||
<!-- 底部操作按钮 -->
|
||||
<view class="yd-detail-footer">
|
||||
<view class="yd-detail-footer-actions">
|
||||
<wd-button
|
||||
v-if="hasAccessByCodes(['${permissionPrefix}:update'])"
|
||||
class="flex-1" type="warning" @click="handleEdit"
|
||||
>
|
||||
编辑
|
||||
</wd-button>
|
||||
<wd-button
|
||||
v-if="hasAccessByCodes(['${permissionPrefix}:delete'])"
|
||||
class="flex-1" type="danger" :loading="deleting" @click="handleDelete"
|
||||
>
|
||||
删除
|
||||
</wd-button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
#set ($primaryJavaType = $primaryColumn.javaType.toLowerCase())
|
||||
#if(${primaryJavaType} == "long" || ${primaryJavaType} == "integer" || ${primaryJavaType} == "short" || ${primaryJavaType} == "float" || ${primaryJavaType} == "double" || ${primaryJavaType} == "bigdecimal" || ${primaryJavaType} == "byte")
|
||||
#set ($primaryTsType = "number")
|
||||
#else
|
||||
#set ($primaryTsType = "string")
|
||||
#end
|
||||
#set ($hasDict = 0)
|
||||
#set ($hasDateTime = 0)
|
||||
#foreach($column in $columns)
|
||||
#if ($column.dictType && "" != $column.dictType)
|
||||
#set ($hasDict = 1)
|
||||
#end
|
||||
#if ($column.javaType == "LocalDateTime")
|
||||
#set ($hasDateTime = 1)
|
||||
#end
|
||||
#end
|
||||
import type {
|
||||
${simpleClassName},
|
||||
#foreach ($subSimpleClassName in $subSimpleClassNames)
|
||||
${subSimpleClassName},
|
||||
#end
|
||||
} from '@/api/${table.moduleName}/${table.businessName}'
|
||||
import { onShow } from '@dcloudio/uni-app'
|
||||
import { ref } from 'vue'
|
||||
import { useDialog } from '@wot-ui/ui/components/wd-dialog'
|
||||
import { useToast } from '@wot-ui/ui/components/wd-toast'
|
||||
import {
|
||||
delete${simpleClassName},
|
||||
get${simpleClassName},
|
||||
#foreach ($subTable in $subTables)
|
||||
#set ($index = $foreach.count - 1)
|
||||
#set ($subSimpleClassName = $subSimpleClassNames.get($index))
|
||||
#set ($subJoinColumn = $subJoinColumns.get($index))
|
||||
#set ($SubJoinColumnName = $subJoinColumn.javaField.substring(0,1).toUpperCase() + $subJoinColumn.javaField.substring(1))
|
||||
#if ($subTable.subJoinMany)
|
||||
get${subSimpleClassName}ListBy${SubJoinColumnName},
|
||||
#else
|
||||
get${subSimpleClassName}By${SubJoinColumnName},
|
||||
#end
|
||||
#end
|
||||
} from '@/api/${table.moduleName}/${table.businessName}'
|
||||
import { useAccess } from '@/hooks/useAccess'
|
||||
import { delay, navigateBackPlus } from '@/utils'
|
||||
#if ($hasDict == 1)
|
||||
import { DICT_TYPE } from '@/utils/constants'
|
||||
#end
|
||||
#if ($hasDateTime == 1)
|
||||
import { formatDateTime } from '@/utils/date'
|
||||
#end
|
||||
|
||||
const props = defineProps<{
|
||||
id?: ${primaryTsType}
|
||||
}>()
|
||||
|
||||
definePage({
|
||||
style: {
|
||||
navigationBarTitleText: '',
|
||||
navigationStyle: 'custom',
|
||||
},
|
||||
})
|
||||
|
||||
const { hasAccessByCodes } = useAccess()
|
||||
const dialog = useDialog()
|
||||
const toast = useToast()
|
||||
const formData = ref<${simpleClassName}>() // 主表数据
|
||||
#foreach ($subTable in $subTables)
|
||||
#set ($index = $foreach.count - 1)
|
||||
#set ($subSimpleClassName = $subSimpleClassNames.get($index))
|
||||
#set ($subClassNameVar = $subClassNameVars.get($index))
|
||||
#if ($subTable.subJoinMany)
|
||||
const ${subClassNameVar}s = ref<${subSimpleClassName}[]>([]) // ${subTable.classComment}数据
|
||||
#else
|
||||
const ${subClassNameVar} = ref<${subSimpleClassName} | null>(null) // ${subTable.classComment}数据
|
||||
#end
|
||||
#end
|
||||
const deleting = ref(false) // 删除状态
|
||||
|
||||
/** 返回上一页 */
|
||||
function handleBack() {
|
||||
navigateBackPlus('/pages-${table.moduleName}/${table.businessName}/index')
|
||||
}
|
||||
|
||||
/** 加载主表和子表详情 */
|
||||
async function getDetail() {
|
||||
if (!props.id || deleting.value) {
|
||||
return
|
||||
}
|
||||
const [data#foreach ($subTable in $subTables)#set ($index = $foreach.count - 1), ${subClassNameVars.get($index)}Data#end] = await Promise.all([
|
||||
get${simpleClassName}(props.id),
|
||||
#foreach ($subTable in $subTables)
|
||||
#set ($index = $foreach.count - 1)
|
||||
#set ($subSimpleClassName = $subSimpleClassNames.get($index))
|
||||
#set ($subJoinColumn = $subJoinColumns.get($index))
|
||||
#set ($SubJoinColumnName = $subJoinColumn.javaField.substring(0,1).toUpperCase() + $subJoinColumn.javaField.substring(1))
|
||||
#if ($subTable.subJoinMany)
|
||||
get${subSimpleClassName}ListBy${SubJoinColumnName}(props.id),
|
||||
#else
|
||||
get${subSimpleClassName}By${SubJoinColumnName}(props.id),
|
||||
#end
|
||||
#end
|
||||
])
|
||||
formData.value = data
|
||||
#foreach ($subTable in $subTables)
|
||||
#set ($index = $foreach.count - 1)
|
||||
#set ($subClassNameVar = $subClassNameVars.get($index))
|
||||
#if ($subTable.subJoinMany)
|
||||
${subClassNameVar}s.value = ${subClassNameVar}Data || []
|
||||
#else
|
||||
${subClassNameVar}.value = ${subClassNameVar}Data || null
|
||||
#end
|
||||
#end
|
||||
}
|
||||
|
||||
/** 编辑${table.classComment} */
|
||||
function handleEdit() {
|
||||
uni.navigateTo({ url: `/pages-${table.moduleName}/${table.businessName}/form/index?id=#[[${]]#props.id#[[}]]#` })
|
||||
}
|
||||
|
||||
/** 删除${table.classComment} */
|
||||
async function handleDelete() {
|
||||
if (!props.id) {
|
||||
return
|
||||
}
|
||||
try {
|
||||
await dialog.confirm({ title: '提示', msg: '确定要删除该${table.classComment}吗?' })
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
deleting.value = true
|
||||
try {
|
||||
await delete${simpleClassName}(props.id)
|
||||
toast.success('删除成功')
|
||||
uni.$emit('${table.moduleName}:${table.businessName}:reload')
|
||||
delay(handleBack)
|
||||
} finally {
|
||||
deleting.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 进入页面加载(含编辑返回后刷新) */
|
||||
onShow(() => {
|
||||
getDetail()
|
||||
})
|
||||
</script>
|
||||
+638
-109
@@ -12,34 +12,48 @@
|
||||
<wd-form ref="formRef" :model="formData" :schema="formSchema">
|
||||
<wd-cell-group border>
|
||||
#foreach($column in $columns)
|
||||
#if (($column.createOperation || $column.updateOperation) && !$column.primaryKey)
|
||||
#set ($dictType = $column.dictType)
|
||||
#set ($javaField = $column.javaField)
|
||||
#set ($javaType = $column.javaType)
|
||||
#set ($comment = $column.columnComment)
|
||||
#set ($dictMethod = "getDictOptions")
|
||||
#if ($javaType == "Integer" || $javaType == "Long" || $javaType == "Byte" || $javaType == "Short")
|
||||
#set ($dictMethod = "getIntDictOptions")
|
||||
#elseif ($javaType == "String")
|
||||
#set ($dictMethod = "getStrDictOptions")
|
||||
#elseif ($javaType == "Boolean")
|
||||
#set ($dictMethod = "getBoolDictOptions")
|
||||
#end
|
||||
## 下拉选择(字典)→ yd-form-picker,内部按 dictType 解析选项,适合选项较多
|
||||
#if ($column.htmlType == "select" && $dictType && "" != $dictType)
|
||||
#if (($column.createOperation || $column.updateOperation) && !$column.primaryKey)
|
||||
#set ($dictType = $column.dictType)
|
||||
#set ($javaField = $column.javaField)
|
||||
#set ($javaType = $column.javaType)
|
||||
#set ($comment = $column.columnComment)
|
||||
#set ($dictMethod = "getDictOptions")
|
||||
#if ($javaType == "Integer" || $javaType == "Long" || $javaType == "Byte" || $javaType == "Short")
|
||||
#set ($dictMethod = "getIntDictOptions")
|
||||
#elseif ($javaType == "String")
|
||||
#set ($dictMethod = "getStrDictOptions")
|
||||
#elseif ($javaType == "Boolean")
|
||||
#set ($dictMethod = "getBoolDictOptions")
|
||||
#end
|
||||
## 树表的父级选择
|
||||
#if ($table.templateType == 2 && $column.id == $treeParentColumn.id)
|
||||
<yd-tree-select
|
||||
v-model="formData.${javaField}"
|
||||
:data="treeData"
|
||||
label="${comment}"
|
||||
prop="${javaField}"
|
||||
:props="{ label: '${treeNameColumn.javaField}', value: '${primaryColumn.javaField}' }"
|
||||
placeholder="请选择${comment}"
|
||||
/>
|
||||
## 下拉选择(字典)→ yd-form-picker,内部按 dictType 解析选项,适合选项较多
|
||||
#elseif ($column.htmlType == "select" && $dictType && "" != $dictType)
|
||||
<yd-form-picker
|
||||
v-model="formData.${javaField}"
|
||||
label="${comment}"
|
||||
label-width="180rpx"
|
||||
prop="${javaField}"
|
||||
#if ($javaType == "Boolean")
|
||||
:columns="getBoolDictOptions(DICT_TYPE.${dictType.toUpperCase()})"
|
||||
#else
|
||||
:dict-type="DICT_TYPE.${dictType.toUpperCase()}"
|
||||
#if ($javaType == "String")
|
||||
dict-kind="str"
|
||||
#end
|
||||
#end
|
||||
placeholder="请选择${comment}"
|
||||
/>
|
||||
## 单选(字典)→ radio-group,适合选项较少
|
||||
#elseif ($column.htmlType == "radio" && $dictType && "" != $dictType)
|
||||
## 单选(字典)→ radio-group,适合选项较少
|
||||
#elseif ($column.htmlType == "radio" && $dictType && "" != $dictType)
|
||||
<wd-form-item title="${comment}" title-width="180rpx" prop="${javaField}" center>
|
||||
<wd-radio-group v-model="formData.${javaField}" type="button">
|
||||
<wd-radio
|
||||
@@ -51,70 +65,78 @@
|
||||
</wd-radio>
|
||||
</wd-radio-group>
|
||||
</wd-form-item>
|
||||
## 数字类型(无字典)
|
||||
#elseif (${javaType.toLowerCase()} == "long" || ${javaType.toLowerCase()} == "integer" || ${javaType.toLowerCase()} == "short" || ${javaType.toLowerCase()} == "double" || ${javaType.toLowerCase()} == "bigdecimal" || ${javaType.toLowerCase()} == "byte")
|
||||
## 数字类型(无字典)
|
||||
#elseif (${javaType.toLowerCase()} == "long" || ${javaType.toLowerCase()} == "integer" || ${javaType.toLowerCase()} == "short" || ${javaType.toLowerCase()} == "float" || ${javaType.toLowerCase()} == "double" || ${javaType.toLowerCase()} == "bigdecimal" || ${javaType.toLowerCase()} == "byte")
|
||||
<wd-form-item title="${comment}" title-width="180rpx" prop="${javaField}">
|
||||
<wd-input-number
|
||||
v-model="formData.${javaField}"
|
||||
#if (${javaType.toLowerCase()} == "double" || ${javaType.toLowerCase()} == "bigdecimal")
|
||||
#if ($column.nullable)
|
||||
allow-null
|
||||
#end
|
||||
#if (${javaType.toLowerCase()} == "float" || ${javaType.toLowerCase()} == "double" || ${javaType.toLowerCase()} == "bigdecimal")
|
||||
:precision="2"
|
||||
input-type="number"
|
||||
#end
|
||||
:min="0"
|
||||
/>
|
||||
</wd-form-item>
|
||||
## 布尔类型
|
||||
#elseif (${javaType.toLowerCase()} == "boolean")
|
||||
## 布尔类型
|
||||
#elseif (${javaType.toLowerCase()} == "boolean")
|
||||
<wd-form-item title="${comment}" title-width="180rpx" prop="${javaField}" center>
|
||||
<wd-switch v-model="formData.${javaField}" />
|
||||
</wd-form-item>
|
||||
## 日期时间类型
|
||||
#elseif (${javaType.toLowerCase()} == "date" || ${javaType.toLowerCase()} == "localdate" || ${javaType.toLowerCase()} == "localdatetime")
|
||||
#set ($pickerType = "date")
|
||||
#if (${javaType.toLowerCase()} == "localdatetime")
|
||||
#set ($pickerType = "datetime")
|
||||
#end
|
||||
## 日期时间类型
|
||||
#elseif (${javaType.toLowerCase()} == "date" || ${javaType.toLowerCase()} == "localdate" || ${javaType.toLowerCase()} == "localdatetime")
|
||||
#set ($pickerType = "date")
|
||||
#if (${javaType.toLowerCase()} == "localdatetime")
|
||||
#set ($pickerType = "datetime")
|
||||
#end
|
||||
<wd-form-item
|
||||
title="${comment}"
|
||||
title-width="180rpx"
|
||||
prop="${javaField}"
|
||||
is-link
|
||||
#if ($pickerType == "datetime")
|
||||
:value="formatDateTime(formData.${javaField})"
|
||||
:value="formData.${javaField} ? formatDateTime(formData.${javaField}) : ''"
|
||||
#else
|
||||
:value="formatDate(formData.${javaField})"
|
||||
:value="formData.${javaField} ? formatDate(formData.${javaField}) : ''"
|
||||
#end
|
||||
placeholder="请选择${comment}"
|
||||
@click="${javaField}Visible = true"
|
||||
/>
|
||||
<wd-datetime-picker
|
||||
v-model="formData.${javaField}"
|
||||
v-model:visible="${javaField}Visible"
|
||||
:model-value="formData.${javaField} || Date.now()"
|
||||
type="${pickerType}"
|
||||
@update:model-value="value => formData.${javaField} = value"
|
||||
/>
|
||||
## 文本域
|
||||
#elseif ($column.htmlType == "textarea")
|
||||
## 文本域
|
||||
#elseif ($column.htmlType == "textarea" || $column.htmlType == "editor")
|
||||
<wd-form-item title="${comment}" title-width="180rpx" prop="${javaField}">
|
||||
<wd-textarea
|
||||
v-model="formData.${javaField}"
|
||||
clearable
|
||||
placeholder="请输入${comment}"
|
||||
#if ($column.htmlType == "editor")
|
||||
:maxlength="2000"
|
||||
#else
|
||||
:maxlength="200"
|
||||
#end
|
||||
show-word-limit
|
||||
/>
|
||||
</wd-form-item>
|
||||
## 图片上传 → yd-upload-img(包 wd-form-item 才能注册到 wd-form 做校验/错误展示)
|
||||
#elseif ($column.htmlType == "imageUpload")
|
||||
## 图片上传 → yd-upload-img(包 wd-form-item 才能注册到 wd-form 做校验/错误展示)
|
||||
#elseif ($column.htmlType == "imageUpload")
|
||||
<wd-form-item title="${comment}" title-width="180rpx" prop="${javaField}">
|
||||
<yd-upload-img v-model="formData.${javaField}" directory="${table.moduleName}/${table.businessName}" />
|
||||
</wd-form-item>
|
||||
## 文件上传 → yd-upload-file
|
||||
#elseif ($column.htmlType == "fileUpload")
|
||||
## 文件上传 → yd-upload-file
|
||||
#elseif ($column.htmlType == "fileUpload")
|
||||
<wd-form-item title="${comment}" title-width="180rpx" prop="${javaField}">
|
||||
<yd-upload-file v-model="formData.${javaField}" directory="${table.moduleName}/${table.businessName}" />
|
||||
<yd-upload-file v-model="formData.${javaField}" :limit="1" directory="${table.moduleName}/${table.businessName}" />
|
||||
</wd-form-item>
|
||||
## 默认:文本输入
|
||||
#else
|
||||
## 默认:文本输入
|
||||
#else
|
||||
<wd-form-item title="${comment}" title-width="180rpx" prop="${javaField}">
|
||||
<wd-input
|
||||
v-model="formData.${javaField}"
|
||||
@@ -122,11 +144,277 @@
|
||||
placeholder="请输入${comment}"
|
||||
/>
|
||||
</wd-form-item>
|
||||
#end
|
||||
#end
|
||||
#end
|
||||
#end
|
||||
#end
|
||||
</wd-cell-group>
|
||||
</wd-form>
|
||||
#if ($table.templateType == 10 || $table.templateType == 12)
|
||||
#foreach ($subTable in $subTables)
|
||||
#set ($index = $foreach.count - 1)
|
||||
#set ($subColumns = $subColumnsList.get($index))
|
||||
#set ($subJoinColumn = $subJoinColumns.get($index))
|
||||
#set ($subClassNameVar = $subClassNameVars.get($index))
|
||||
|
||||
<!-- 子表:${subTable.classComment} -->
|
||||
<view class="mt-20rpx flex items-center justify-between px-24rpx py-16rpx">
|
||||
<text class="text-28rpx text-[#333] font-semibold">${subTable.classComment}</text>
|
||||
#if ($subTable.subJoinMany)
|
||||
<wd-button size="small" type="primary" variant="plain" @click="add${subClassNameVar.substring(0,1).toUpperCase()}${subClassNameVar.substring(1)}">
|
||||
添加
|
||||
</wd-button>
|
||||
#else
|
||||
<wd-button
|
||||
v-if="!${subClassNameVar}"
|
||||
size="small"
|
||||
type="primary"
|
||||
variant="plain"
|
||||
@click="add${subClassNameVar.substring(0,1).toUpperCase()}${subClassNameVar.substring(1)}()"
|
||||
>
|
||||
添加
|
||||
</wd-button>
|
||||
#end
|
||||
</view>
|
||||
<view class="px-24rpx">
|
||||
#if ($subTable.subJoinMany)
|
||||
<view v-if="!${subClassNameVar}s.length" class="rounded-12rpx bg-white py-40rpx text-center text-26rpx text-[#999]">
|
||||
暂无${subTable.classComment}
|
||||
</view>
|
||||
<view
|
||||
v-for="(item, index) in ${subClassNameVar}s"
|
||||
:key="index"
|
||||
class="mb-20rpx rounded-12rpx bg-white p-24rpx shadow-sm"
|
||||
>
|
||||
<view class="mb-16rpx flex justify-end">
|
||||
<wd-button size="small" type="danger" variant="plain" @click="remove${subClassNameVar.substring(0,1).toUpperCase()}${subClassNameVar.substring(1)}(index)">
|
||||
删除
|
||||
</wd-button>
|
||||
</view>
|
||||
#foreach ($column in $subColumns)
|
||||
#if (($column.createOperation || $column.updateOperation) && !$column.primaryKey && $column.id != $subJoinColumn.id)
|
||||
#set ($javaType = $column.javaType.toLowerCase())
|
||||
#set ($dictMethod = "getDictOptions")
|
||||
#if ($column.javaType == "Integer" || $column.javaType == "Long" || $column.javaType == "Byte" || $column.javaType == "Short")
|
||||
#set ($dictMethod = "getIntDictOptions")
|
||||
#elseif ($column.javaType == "String")
|
||||
#set ($dictMethod = "getStrDictOptions")
|
||||
#elseif ($column.javaType == "Boolean")
|
||||
#set ($dictMethod = "getBoolDictOptions")
|
||||
#end
|
||||
#if ($column.htmlType == "select" && $column.dictType && "" != $column.dictType)
|
||||
<yd-form-picker
|
||||
v-model="item.${column.javaField}"
|
||||
label="${column.columnComment}"
|
||||
label-width="180rpx"
|
||||
#if ($column.javaType == "Boolean")
|
||||
:columns="getBoolDictOptions(DICT_TYPE.${column.dictType.toUpperCase()})"
|
||||
#else
|
||||
:dict-type="DICT_TYPE.${column.dictType.toUpperCase()}"
|
||||
#if ($column.javaType == "String")
|
||||
dict-kind="str"
|
||||
#end
|
||||
#end
|
||||
placeholder="请选择${column.columnComment}"
|
||||
/>
|
||||
#elseif ($column.htmlType == "radio" && $column.dictType && "" != $column.dictType)
|
||||
<wd-form-item title="${column.columnComment}" title-width="180rpx" center>
|
||||
<wd-radio-group v-model="item.${column.javaField}" type="button">
|
||||
<wd-radio
|
||||
v-for="dict in $dictMethod(DICT_TYPE.${column.dictType.toUpperCase()})"
|
||||
:key="dict.value"
|
||||
:value="dict.value"
|
||||
>
|
||||
{{ dict.label }}
|
||||
</wd-radio>
|
||||
</wd-radio-group>
|
||||
</wd-form-item>
|
||||
#elseif ($javaType == "boolean")
|
||||
<wd-form-item title="${column.columnComment}" title-width="180rpx" center>
|
||||
<wd-switch v-model="item.${column.javaField}" />
|
||||
</wd-form-item>
|
||||
#elseif ($javaType == "long" || $javaType == "integer" || $javaType == "short" || $javaType == "float" || $javaType == "double" || $javaType == "bigdecimal" || $javaType == "byte")
|
||||
<wd-form-item title="${column.columnComment}" title-width="180rpx">
|
||||
<wd-input-number
|
||||
v-model="item.${column.javaField}"
|
||||
#if($column.nullable)
|
||||
allow-null
|
||||
#end
|
||||
#if ($javaType == "float" || $javaType == "double" || $javaType == "bigdecimal")
|
||||
:precision="2"
|
||||
input-type="number"
|
||||
#end
|
||||
:min="0"
|
||||
/>
|
||||
</wd-form-item>
|
||||
#elseif ($javaType == "date" || $javaType == "localdate" || $javaType == "localdatetime")
|
||||
#set ($subPickerType = "date")
|
||||
#if ($javaType == "localdatetime")
|
||||
#set ($subPickerType = "datetime")
|
||||
#end
|
||||
<wd-form-item
|
||||
title="${column.columnComment}"
|
||||
title-width="180rpx"
|
||||
is-link
|
||||
#if ($javaType == "localdatetime")
|
||||
:value="item.${column.javaField} ? formatDateTime(item.${column.javaField}) : ''"
|
||||
#else
|
||||
:value="item.${column.javaField} ? formatDate(item.${column.javaField}) : ''"
|
||||
#end
|
||||
placeholder="请选择${column.columnComment}"
|
||||
@click="subDateVisible[`${subClassNameVar}s.#[[${]]#index#[[}]]#.${column.javaField}`] = true"
|
||||
/>
|
||||
<wd-datetime-picker
|
||||
v-model:visible="subDateVisible[`${subClassNameVar}s.#[[${]]#index#[[}]]#.${column.javaField}`]"
|
||||
:model-value="item.${column.javaField} || Date.now()"
|
||||
type="${subPickerType}"
|
||||
@update:model-value="value => item.${column.javaField} = value"
|
||||
/>
|
||||
#elseif ($column.htmlType == "textarea" || $column.htmlType == "editor")
|
||||
<wd-form-item title="${column.columnComment}" title-width="180rpx">
|
||||
<wd-textarea
|
||||
v-model="item.${column.javaField}"
|
||||
clearable
|
||||
placeholder="请输入${column.columnComment}"
|
||||
#if ($column.htmlType == "editor")
|
||||
:maxlength="2000"
|
||||
#else
|
||||
:maxlength="200"
|
||||
#end
|
||||
/>
|
||||
</wd-form-item>
|
||||
#elseif ($column.htmlType == "imageUpload")
|
||||
<wd-form-item title="${column.columnComment}" title-width="180rpx">
|
||||
<yd-upload-img v-model="item.${column.javaField}" directory="${table.moduleName}/${table.businessName}" />
|
||||
</wd-form-item>
|
||||
#elseif ($column.htmlType == "fileUpload")
|
||||
<wd-form-item title="${column.columnComment}" title-width="180rpx">
|
||||
<yd-upload-file v-model="item.${column.javaField}" :limit="1" directory="${table.moduleName}/${table.businessName}" />
|
||||
</wd-form-item>
|
||||
#else
|
||||
<wd-form-item title="${column.columnComment}" title-width="180rpx">
|
||||
<wd-input v-model="item.${column.javaField}" clearable placeholder="请输入${column.columnComment}" />
|
||||
</wd-form-item>
|
||||
#end
|
||||
#end
|
||||
#end
|
||||
</view>
|
||||
#else
|
||||
<view v-if="!${subClassNameVar}" class="rounded-12rpx bg-white py-40rpx text-center text-26rpx text-[#999]">
|
||||
暂无${subTable.classComment}
|
||||
</view>
|
||||
<view v-else class="rounded-12rpx bg-white p-24rpx shadow-sm">
|
||||
#foreach ($column in $subColumns)
|
||||
#if (($column.createOperation || $column.updateOperation) && !$column.primaryKey && $column.id != $subJoinColumn.id)
|
||||
#set ($javaType = $column.javaType.toLowerCase())
|
||||
#set ($dictMethod = "getDictOptions")
|
||||
#if ($column.javaType == "Integer" || $column.javaType == "Long" || $column.javaType == "Byte" || $column.javaType == "Short")
|
||||
#set ($dictMethod = "getIntDictOptions")
|
||||
#elseif ($column.javaType == "String")
|
||||
#set ($dictMethod = "getStrDictOptions")
|
||||
#elseif ($column.javaType == "Boolean")
|
||||
#set ($dictMethod = "getBoolDictOptions")
|
||||
#end
|
||||
#if ($column.htmlType == "select" && $column.dictType && "" != $column.dictType)
|
||||
<yd-form-picker
|
||||
v-model="${subClassNameVar}.${column.javaField}"
|
||||
label="${column.columnComment}"
|
||||
label-width="180rpx"
|
||||
#if ($column.javaType == "Boolean")
|
||||
:columns="getBoolDictOptions(DICT_TYPE.${column.dictType.toUpperCase()})"
|
||||
#else
|
||||
:dict-type="DICT_TYPE.${column.dictType.toUpperCase()}"
|
||||
#if ($column.javaType == "String")
|
||||
dict-kind="str"
|
||||
#end
|
||||
#end
|
||||
placeholder="请选择${column.columnComment}"
|
||||
/>
|
||||
#elseif ($column.htmlType == "radio" && $column.dictType && "" != $column.dictType)
|
||||
<wd-form-item title="${column.columnComment}" title-width="180rpx" center>
|
||||
<wd-radio-group v-model="${subClassNameVar}.${column.javaField}" type="button">
|
||||
<wd-radio
|
||||
v-for="dict in $dictMethod(DICT_TYPE.${column.dictType.toUpperCase()})"
|
||||
:key="dict.value"
|
||||
:value="dict.value"
|
||||
>
|
||||
{{ dict.label }}
|
||||
</wd-radio>
|
||||
</wd-radio-group>
|
||||
</wd-form-item>
|
||||
#elseif ($javaType == "boolean")
|
||||
<wd-form-item title="${column.columnComment}" title-width="180rpx" center>
|
||||
<wd-switch v-model="${subClassNameVar}.${column.javaField}" />
|
||||
</wd-form-item>
|
||||
#elseif ($javaType == "long" || $javaType == "integer" || $javaType == "short" || $javaType == "float" || $javaType == "double" || $javaType == "bigdecimal" || $javaType == "byte")
|
||||
<wd-form-item title="${column.columnComment}" title-width="180rpx">
|
||||
<wd-input-number
|
||||
v-model="${subClassNameVar}.${column.javaField}"
|
||||
#if($column.nullable)
|
||||
allow-null
|
||||
#end
|
||||
#if ($javaType == "float" || $javaType == "double" || $javaType == "bigdecimal")
|
||||
:precision="2"
|
||||
input-type="number"
|
||||
#end
|
||||
:min="0"
|
||||
/>
|
||||
</wd-form-item>
|
||||
#elseif ($javaType == "date" || $javaType == "localdate" || $javaType == "localdatetime")
|
||||
#set ($subPickerType = "date")
|
||||
#if ($javaType == "localdatetime")
|
||||
#set ($subPickerType = "datetime")
|
||||
#end
|
||||
<wd-form-item
|
||||
title="${column.columnComment}"
|
||||
title-width="180rpx"
|
||||
is-link
|
||||
#if ($javaType == "localdatetime")
|
||||
:value="${subClassNameVar}.${column.javaField} ? formatDateTime(${subClassNameVar}.${column.javaField}) : ''"
|
||||
#else
|
||||
:value="${subClassNameVar}.${column.javaField} ? formatDate(${subClassNameVar}.${column.javaField}) : ''"
|
||||
#end
|
||||
placeholder="请选择${column.columnComment}"
|
||||
@click="subDateVisible['${subClassNameVar}.${column.javaField}'] = true"
|
||||
/>
|
||||
<wd-datetime-picker
|
||||
v-model:visible="subDateVisible['${subClassNameVar}.${column.javaField}']"
|
||||
:model-value="${subClassNameVar}.${column.javaField} || Date.now()"
|
||||
type="${subPickerType}"
|
||||
@update:model-value="value => ${subClassNameVar}.${column.javaField} = value"
|
||||
/>
|
||||
#elseif ($column.htmlType == "textarea" || $column.htmlType == "editor")
|
||||
<wd-form-item title="${column.columnComment}" title-width="180rpx">
|
||||
<wd-textarea
|
||||
v-model="${subClassNameVar}.${column.javaField}"
|
||||
clearable
|
||||
placeholder="请输入${column.columnComment}"
|
||||
#if ($column.htmlType == "editor")
|
||||
:maxlength="2000"
|
||||
#else
|
||||
:maxlength="200"
|
||||
#end
|
||||
/>
|
||||
</wd-form-item>
|
||||
#elseif ($column.htmlType == "imageUpload")
|
||||
<wd-form-item title="${column.columnComment}" title-width="180rpx">
|
||||
<yd-upload-img v-model="${subClassNameVar}.${column.javaField}" directory="${table.moduleName}/${table.businessName}" />
|
||||
</wd-form-item>
|
||||
#elseif ($column.htmlType == "fileUpload")
|
||||
<wd-form-item title="${column.columnComment}" title-width="180rpx">
|
||||
<yd-upload-file v-model="${subClassNameVar}.${column.javaField}" :limit="1" directory="${table.moduleName}/${table.businessName}" />
|
||||
</wd-form-item>
|
||||
#else
|
||||
<wd-form-item title="${column.columnComment}" title-width="180rpx">
|
||||
<wd-input v-model="${subClassNameVar}.${column.javaField}" clearable placeholder="请输入${column.columnComment}" />
|
||||
</wd-form-item>
|
||||
#end
|
||||
#end
|
||||
#end
|
||||
</view>
|
||||
#end
|
||||
</view>
|
||||
#end
|
||||
#end
|
||||
</view>
|
||||
|
||||
<!-- 底部保存按钮 -->
|
||||
@@ -146,7 +434,7 @@
|
||||
<script lang="ts" setup>
|
||||
import type { FormInstance } from '@wot-ui/ui/components/wd-form/types'
|
||||
#set ($primaryJavaType = $primaryColumn.javaType.toLowerCase())
|
||||
#if(${primaryJavaType} == "long" || ${primaryJavaType} == "integer" || ${primaryJavaType} == "short" || ${primaryJavaType} == "double" || ${primaryJavaType} == "bigdecimal" || ${primaryJavaType} == "byte")
|
||||
#if(${primaryJavaType} == "long" || ${primaryJavaType} == "integer" || ${primaryJavaType} == "short" || ${primaryJavaType} == "float" || ${primaryJavaType} == "double" || ${primaryJavaType} == "bigdecimal" || ${primaryJavaType} == "byte")
|
||||
#set ($primaryTsType = "number")
|
||||
#else
|
||||
#set ($primaryTsType = "string")
|
||||
@@ -154,54 +442,113 @@ import type { FormInstance } from '@wot-ui/ui/components/wd-form/types'
|
||||
#set ($hasDict = 0)
|
||||
#set ($hasDate = 0)
|
||||
#set ($hasDateTime = 0)
|
||||
#set ($hasSubDate = 0)
|
||||
#set ($hasGetDictOptions = 0)
|
||||
#set ($hasGetIntDictOptions = 0)
|
||||
#set ($hasGetStrDictOptions = 0)
|
||||
#set ($hasGetBoolDictOptions = 0)
|
||||
#foreach ($column in $columns)
|
||||
#if (($column.createOperation || $column.updateOperation) && !$column.primaryKey
|
||||
#if (($column.createOperation || $column.updateOperation) && !$column.primaryKey
|
||||
&& ($column.htmlType == "select" || $column.htmlType == "radio")
|
||||
&& $column.dictType && "" != $column.dictType)
|
||||
#set ($hasDict = 1)
|
||||
#if ($column.htmlType == "radio" && ($column.javaType == "Integer" || $column.javaType == "Long" || $column.javaType == "Byte" || $column.javaType == "Short"))
|
||||
#set ($hasGetIntDictOptions = 1)
|
||||
#elseif ($column.htmlType == "radio" && $column.javaType == "String")
|
||||
#set ($hasGetStrDictOptions = 1)
|
||||
#elseif ($column.htmlType == "radio" && $column.javaType == "Boolean")
|
||||
#set ($hasGetBoolDictOptions = 1)
|
||||
#elseif ($column.htmlType == "radio")
|
||||
#set ($hasGetDictOptions = 1)
|
||||
#end
|
||||
#end
|
||||
#if (($column.createOperation || $column.updateOperation) && !$column.primaryKey
|
||||
&& (${column.javaType.toLowerCase()} == "date" || ${column.javaType.toLowerCase()} == "localdate"))
|
||||
#set ($hasDate = 1)
|
||||
#end
|
||||
#if (($column.createOperation || $column.updateOperation) && !$column.primaryKey
|
||||
&& ${column.javaType.toLowerCase()} == "localdatetime")
|
||||
#set ($hasDateTime = 1)
|
||||
#end
|
||||
#set ($hasDict = 1)
|
||||
#if ($column.htmlType == "radio" && ($column.javaType == "Integer" || $column.javaType == "Long" || $column.javaType == "Byte" || $column.javaType == "Short"))
|
||||
#set ($hasGetIntDictOptions = 1)
|
||||
#elseif ($column.htmlType == "radio" && $column.javaType == "String")
|
||||
#set ($hasGetStrDictOptions = 1)
|
||||
#elseif (($column.htmlType == "select" || $column.htmlType == "radio") && $column.javaType == "Boolean")
|
||||
#set ($hasGetBoolDictOptions = 1)
|
||||
#elseif ($column.htmlType == "radio")
|
||||
#set ($hasGetDictOptions = 1)
|
||||
#end
|
||||
import type { ${simpleClassName} } from '@/api/${table.moduleName}/${table.businessName}'
|
||||
#end
|
||||
#if (($column.createOperation || $column.updateOperation) && !$column.primaryKey
|
||||
&& (${column.javaType.toLowerCase()} == "date" || ${column.javaType.toLowerCase()} == "localdate"))
|
||||
#set ($hasDate = 1)
|
||||
#end
|
||||
#if (($column.createOperation || $column.updateOperation) && !$column.primaryKey
|
||||
&& ${column.javaType.toLowerCase()} == "localdatetime")
|
||||
#set ($hasDateTime = 1)
|
||||
#end
|
||||
#end
|
||||
#if ($table.templateType == 10 || $table.templateType == 12)
|
||||
#foreach ($subColumns in $subColumnsList)
|
||||
#foreach ($column in $subColumns)
|
||||
#if (($column.createOperation || $column.updateOperation) && !$column.primaryKey
|
||||
&& ($column.htmlType == "select" || $column.htmlType == "radio")
|
||||
&& $column.dictType && "" != $column.dictType)
|
||||
#set ($hasDict = 1)
|
||||
#if ($column.htmlType == "radio" && ($column.javaType == "Integer" || $column.javaType == "Long" || $column.javaType == "Byte" || $column.javaType == "Short"))
|
||||
#set ($hasGetIntDictOptions = 1)
|
||||
#elseif ($column.htmlType == "radio" && $column.javaType == "String")
|
||||
#set ($hasGetStrDictOptions = 1)
|
||||
#elseif (($column.htmlType == "select" || $column.htmlType == "radio") && $column.javaType == "Boolean")
|
||||
#set ($hasGetBoolDictOptions = 1)
|
||||
#elseif ($column.htmlType == "radio")
|
||||
#set ($hasGetDictOptions = 1)
|
||||
#end
|
||||
#end
|
||||
#if (($column.createOperation || $column.updateOperation) && !$column.primaryKey
|
||||
&& (${column.javaType.toLowerCase()} == "date" || ${column.javaType.toLowerCase()} == "localdate"))
|
||||
#set ($hasDate = 1)
|
||||
#set ($hasSubDate = 1)
|
||||
#end
|
||||
#if (($column.createOperation || $column.updateOperation) && !$column.primaryKey
|
||||
&& ${column.javaType.toLowerCase()} == "localdatetime")
|
||||
#set ($hasDateTime = 1)
|
||||
#set ($hasSubDate = 1)
|
||||
#end
|
||||
#end
|
||||
#end
|
||||
#end
|
||||
import type {
|
||||
${simpleClassName},
|
||||
#if ($table.templateType == 10 || $table.templateType == 12)
|
||||
#foreach ($subSimpleClassName in $subSimpleClassNames)
|
||||
${subSimpleClassName},
|
||||
#end
|
||||
#end
|
||||
} from '@/api/${table.moduleName}/${table.businessName}'
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
import { useToast } from '@wot-ui/ui/components/wd-toast'
|
||||
#if ($table.templateType == 10 || $table.templateType == 12)
|
||||
import {
|
||||
create${simpleClassName},
|
||||
get${simpleClassName},
|
||||
#foreach ($subTable in $subTables)
|
||||
#set ($index = $foreach.count - 1)
|
||||
#set ($subSimpleClassName = $subSimpleClassNames.get($index))
|
||||
#set ($subJoinColumn = $subJoinColumns.get($index))
|
||||
#set ($SubJoinColumnName = $subJoinColumn.javaField.substring(0,1).toUpperCase() + $subJoinColumn.javaField.substring(1))
|
||||
#if ($subTable.subJoinMany)
|
||||
get${subSimpleClassName}ListBy${SubJoinColumnName},
|
||||
#else
|
||||
get${subSimpleClassName}By${SubJoinColumnName},
|
||||
#end
|
||||
#end
|
||||
update${simpleClassName},
|
||||
} from '@/api/${table.moduleName}/${table.businessName}'
|
||||
#elseif ($table.templateType == 2)
|
||||
import { create${simpleClassName}, get${simpleClassName}, get${simpleClassName}List, update${simpleClassName} } from '@/api/${table.moduleName}/${table.businessName}'
|
||||
#else
|
||||
import { create${simpleClassName}, get${simpleClassName}, update${simpleClassName} } from '@/api/${table.moduleName}/${table.businessName}'
|
||||
#end
|
||||
#if ($hasGetDictOptions == 1 || $hasGetIntDictOptions == 1 || $hasGetStrDictOptions == 1 || $hasGetBoolDictOptions == 1)
|
||||
#set ($dictImportNames = "")
|
||||
#if ($hasGetDictOptions == 1)
|
||||
#set ($dictImportNames = "${dictImportNames}getDictOptions, ")
|
||||
#end
|
||||
#if ($hasGetIntDictOptions == 1)
|
||||
#set ($dictImportNames = "${dictImportNames}getIntDictOptions, ")
|
||||
#end
|
||||
#if ($hasGetStrDictOptions == 1)
|
||||
#set ($dictImportNames = "${dictImportNames}getStrDictOptions, ")
|
||||
#end
|
||||
#if ($hasGetBoolDictOptions == 1)
|
||||
#set ($dictImportNames = "${dictImportNames}getBoolDictOptions, ")
|
||||
#end
|
||||
#set ($dictImportNames = $dictImportNames.trim())
|
||||
#set ($dictImportNames = $dictImportNames.substring(0, $dictImportNames.length() - 1))
|
||||
#set ($dictImportNames = "")
|
||||
#if ($hasGetDictOptions == 1)
|
||||
#set ($dictImportNames = "${dictImportNames}getDictOptions, ")
|
||||
#end
|
||||
#if ($hasGetIntDictOptions == 1)
|
||||
#set ($dictImportNames = "${dictImportNames}getIntDictOptions, ")
|
||||
#end
|
||||
#if ($hasGetStrDictOptions == 1)
|
||||
#set ($dictImportNames = "${dictImportNames}getStrDictOptions, ")
|
||||
#end
|
||||
#if ($hasGetBoolDictOptions == 1)
|
||||
#set ($dictImportNames = "${dictImportNames}getBoolDictOptions, ")
|
||||
#end
|
||||
#set ($dictImportNames = $dictImportNames.trim())
|
||||
#set ($dictImportNames = $dictImportNames.substring(0, $dictImportNames.length() - 1))
|
||||
import { $dictImportNames } from '@/hooks/useDict'
|
||||
#end
|
||||
import { delay, navigateBackPlus } from '@/utils'
|
||||
@@ -209,21 +556,27 @@ import { delay, navigateBackPlus } from '@/utils'
|
||||
import { DICT_TYPE } from '@/utils/constants'
|
||||
#end
|
||||
#if ($hasDate == 1 || $hasDateTime == 1)
|
||||
#set ($dateImportNames = "")
|
||||
#if ($hasDate == 1)
|
||||
#set ($dateImportNames = "${dateImportNames}formatDate, ")
|
||||
#end
|
||||
#if ($hasDateTime == 1)
|
||||
#set ($dateImportNames = "${dateImportNames}formatDateTime, ")
|
||||
#end
|
||||
#set ($dateImportNames = $dateImportNames.trim())
|
||||
#set ($dateImportNames = $dateImportNames.substring(0, $dateImportNames.length() - 1))
|
||||
#set ($dateImportNames = "")
|
||||
#if ($hasDate == 1)
|
||||
#set ($dateImportNames = "${dateImportNames}formatDate, ")
|
||||
#end
|
||||
#if ($hasDateTime == 1)
|
||||
#set ($dateImportNames = "${dateImportNames}formatDateTime, ")
|
||||
#end
|
||||
#set ($dateImportNames = $dateImportNames.trim())
|
||||
#set ($dateImportNames = $dateImportNames.substring(0, $dateImportNames.length() - 1))
|
||||
import { $dateImportNames } from '@/utils/date'
|
||||
#end
|
||||
import { createFormSchema } from '@/utils/wot'
|
||||
#if ($table.templateType == 2)
|
||||
import { handleTree } from '@/utils/tree'
|
||||
#end
|
||||
|
||||
const props = defineProps<{
|
||||
id?: ${primaryTsType} | any
|
||||
id?: ${primaryTsType}
|
||||
#if ($table.templateType == 2)
|
||||
${treeParentColumn.javaField}?: ${primaryTsType}
|
||||
#end
|
||||
}>()
|
||||
|
||||
definePage({
|
||||
@@ -236,42 +589,165 @@ definePage({
|
||||
const toast = useToast()
|
||||
const getTitle = computed(() => props.id ? '编辑${table.classComment}' : '新增${table.classComment}')
|
||||
const formLoading = ref(false) // 表单提交状态
|
||||
const formData = ref<${simpleClassName}>({
|
||||
const formData = ref<Partial<${simpleClassName}>>({
|
||||
#foreach($column in $columns)
|
||||
#if (($column.createOperation || $column.updateOperation) || $column.primaryKey)
|
||||
#set ($javaType = $column.javaType.toLowerCase())
|
||||
#set ($javaFieldLower = $column.javaField.toLowerCase())
|
||||
#set ($optional = $column.nullable || $column.primaryKey || $javaFieldLower == "createtime" || $javaFieldLower == "updatetime")
|
||||
#if ($column.primaryKey)
|
||||
#if (($column.createOperation || $column.updateOperation) || $column.primaryKey)
|
||||
#set ($javaType = $column.javaType.toLowerCase())
|
||||
#set ($javaFieldLower = $column.javaField.toLowerCase())
|
||||
#set ($optional = $column.nullable || $column.primaryKey || $javaFieldLower == "createtime" || $javaFieldLower == "updatetime")
|
||||
#if ($column.primaryKey)
|
||||
${column.javaField}: undefined,
|
||||
#elseif(${javaType} == "long" || ${javaType} == "integer" || ${javaType} == "short" || ${javaType} == "double" || ${javaType} == "bigdecimal" || ${javaType} == "byte")
|
||||
#elseif($table.templateType == 2 && $column.id == $treeParentColumn.id)
|
||||
${column.javaField}: props.${column.javaField} ?? 0,
|
||||
#elseif(($column.htmlType == "select" || $column.htmlType == "radio") && $column.dictType && "" != $column.dictType)
|
||||
${column.javaField}: undefined,
|
||||
#elseif(${javaType} == "long" || ${javaType} == "integer" || ${javaType} == "short" || ${javaType} == "float" || ${javaType} == "double" || ${javaType} == "bigdecimal" || ${javaType} == "byte")
|
||||
#if($column.nullable)
|
||||
${column.javaField}: undefined,
|
||||
#else
|
||||
${column.javaField}: 0,
|
||||
#elseif(${javaType} == "boolean")
|
||||
#end
|
||||
#elseif(${javaType} == "boolean")
|
||||
${column.javaField}: false,
|
||||
#elseif(${javaType} == "date" || ${javaType} == "localdate" || ${javaType} == "localdatetime")
|
||||
#elseif(${javaType} == "date" || ${javaType} == "localdate" || ${javaType} == "localdatetime")
|
||||
${column.javaField}: undefined,
|
||||
#else
|
||||
#else
|
||||
${column.javaField}: '',
|
||||
#end
|
||||
#end
|
||||
#end
|
||||
#end
|
||||
#end
|
||||
}) // 表单数据
|
||||
#if ($table.templateType == 10 || $table.templateType == 12)
|
||||
#foreach ($subTable in $subTables)
|
||||
#set ($index = $foreach.count - 1)
|
||||
#set ($subSimpleClassName = $subSimpleClassNames.get($index))
|
||||
#set ($subClassNameVar = $subClassNameVars.get($index))
|
||||
#set ($subColumns = $subColumnsList.get($index))
|
||||
#set ($subJoinColumn = $subJoinColumns.get($index))
|
||||
#if ($subTable.subJoinMany)
|
||||
const ${subClassNameVar}s = ref<Partial<${subSimpleClassName}>[]>([]) // ${subTable.classComment}数据
|
||||
#else
|
||||
const ${subClassNameVar} = ref<Partial<${subSimpleClassName}> | null>(null) // ${subTable.classComment}数据
|
||||
#end
|
||||
#end
|
||||
#end
|
||||
#foreach($column in $columns)
|
||||
#if (($column.createOperation || $column.updateOperation) && !$column.primaryKey && (${column.javaType.toLowerCase()} == "date" || ${column.javaType.toLowerCase()} == "localdate" || ${column.javaType.toLowerCase()} == "localdatetime"))
|
||||
#if (($column.createOperation || $column.updateOperation) && !$column.primaryKey && (${column.javaType.toLowerCase()} == "date" || ${column.javaType.toLowerCase()} == "localdate" || ${column.javaType.toLowerCase()} == "localdatetime"))
|
||||
const ${column.javaField}Visible = ref(false) // ${column.columnComment}选择器状态
|
||||
#end
|
||||
#end
|
||||
#end
|
||||
#if ($hasSubDate == 1)
|
||||
const subDateVisible = ref<Record<string, boolean>>({}) // 子表日期选择器状态
|
||||
#end
|
||||
const formSchema = createFormSchema({
|
||||
#foreach($column in $columns)
|
||||
#set ($javaFieldLower = $column.javaField.toLowerCase())
|
||||
#if (($column.createOperation || $column.updateOperation) && !$column.nullable && !$column.primaryKey
|
||||
#set ($javaFieldLower = $column.javaField.toLowerCase())
|
||||
#if (($column.createOperation || $column.updateOperation) && !$column.nullable && !$column.primaryKey
|
||||
&& $javaFieldLower != "createtime" && $javaFieldLower != "updatetime")
|
||||
${column.javaField}: [{ required: true, message: '${column.columnComment}不能为空' }],
|
||||
#end
|
||||
#end
|
||||
#end
|
||||
}) // 表单校验规则
|
||||
const formRef = ref<FormInstance>() // 表单组件引用
|
||||
#if ($table.templateType == 2)
|
||||
const treeData = ref<${simpleClassName}[]>([]) // 父级树形选项
|
||||
#end
|
||||
|
||||
#if ($table.templateType == 10 || $table.templateType == 12)
|
||||
#foreach ($subTable in $subTables)
|
||||
#set ($index = $foreach.count - 1)
|
||||
#set ($subSimpleClassName = $subSimpleClassNames.get($index))
|
||||
#set ($subClassNameVar = $subClassNameVars.get($index))
|
||||
#set ($subColumns = $subColumnsList.get($index))
|
||||
#set ($subJoinColumn = $subJoinColumns.get($index))
|
||||
#if ($subTable.subJoinMany)
|
||||
#set ($subClassNameVar = $subClassNameVars.get($index))
|
||||
#set ($subColumns = $subColumnsList.get($index))
|
||||
#set ($subJoinColumn = $subJoinColumns.get($index))
|
||||
/** 添加${subTable.classComment} */
|
||||
function add${subClassNameVar.substring(0,1).toUpperCase()}${subClassNameVar.substring(1)}() {
|
||||
${subClassNameVar}s.value.push({
|
||||
#foreach ($column in $subColumns)
|
||||
#if (($column.createOperation || $column.updateOperation) && !$column.primaryKey && $column.id != $subJoinColumn.id)
|
||||
#set ($javaType = $column.javaType.toLowerCase())
|
||||
#if((($column.htmlType == "select" || $column.htmlType == "radio") && $column.dictType && "" != $column.dictType) || $javaType == "date" || $javaType == "localdate" || $javaType == "localdatetime")
|
||||
${column.javaField}: undefined,
|
||||
#else
|
||||
${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,
|
||||
#end
|
||||
#end
|
||||
#end
|
||||
})
|
||||
}
|
||||
|
||||
/** 删除${subTable.classComment} */
|
||||
function remove${subClassNameVar.substring(0,1).toUpperCase()}${subClassNameVar.substring(1)}(index: number) {
|
||||
${subClassNameVar}s.value.splice(index, 1)
|
||||
}
|
||||
|
||||
#else
|
||||
/** 添加${subTable.classComment} */
|
||||
function add${subClassNameVar.substring(0,1).toUpperCase()}${subClassNameVar.substring(1)}() {
|
||||
${subClassNameVar}.value = {
|
||||
#foreach ($column in $subColumns)
|
||||
#if (($column.createOperation || $column.updateOperation) && !$column.primaryKey && $column.id != $subJoinColumn.id)
|
||||
#set ($javaType = $column.javaType.toLowerCase())
|
||||
#if((($column.htmlType == "select" || $column.htmlType == "radio") && $column.dictType && "" != $column.dictType) || $javaType == "date" || $javaType == "localdate" || $javaType == "localdatetime")
|
||||
${column.javaField}: undefined,
|
||||
#else
|
||||
${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,
|
||||
#end
|
||||
#end
|
||||
#end
|
||||
}
|
||||
}
|
||||
|
||||
#end
|
||||
#end
|
||||
/** 校验主子表数据 */
|
||||
function isEmptySubValue(value: unknown) {
|
||||
return value === undefined || value === null || value === ''
|
||||
}
|
||||
|
||||
function validateSubTables() {
|
||||
#foreach ($subTable in $subTables)
|
||||
#set ($index = $foreach.count - 1)
|
||||
#set ($subClassNameVar = $subClassNameVars.get($index))
|
||||
#set ($subColumns = $subColumnsList.get($index))
|
||||
#set ($subJoinColumn = $subJoinColumns.get($index))
|
||||
#if ($subTable.subJoinMany)
|
||||
for (let index = 0; index < ${subClassNameVar}s.value.length; index++) {
|
||||
const item = ${subClassNameVar}s.value[index]!
|
||||
#foreach ($column in $subColumns)
|
||||
#set ($javaFieldLower = $column.javaField.toLowerCase())
|
||||
#if (($column.createOperation || $column.updateOperation) && !$column.nullable && !$column.primaryKey
|
||||
&& $column.id != $subJoinColumn.id && $javaFieldLower != "createtime" && $javaFieldLower != "updatetime")
|
||||
if (isEmptySubValue(item.${column.javaField})) {
|
||||
toast.warning(`${subTable.classComment}第#[[${]]#index + 1#[[}]]#项的${column.columnComment}不能为空`)
|
||||
return false
|
||||
}
|
||||
#end
|
||||
#end
|
||||
}
|
||||
#else
|
||||
if (${subClassNameVar}.value) {
|
||||
#foreach ($column in $subColumns)
|
||||
#set ($javaFieldLower = $column.javaField.toLowerCase())
|
||||
#if (($column.createOperation || $column.updateOperation) && !$column.nullable && !$column.primaryKey
|
||||
&& $column.id != $subJoinColumn.id && $javaFieldLower != "createtime" && $javaFieldLower != "updatetime")
|
||||
if (isEmptySubValue(${subClassNameVar}.value.${column.javaField})) {
|
||||
toast.warning('${subTable.classComment}的${column.columnComment}不能为空')
|
||||
return false
|
||||
}
|
||||
#end
|
||||
#end
|
||||
}
|
||||
#end
|
||||
#end
|
||||
return true
|
||||
}
|
||||
|
||||
#end
|
||||
/** 返回上一页 */
|
||||
function handleBack() {
|
||||
navigateBackPlus('/pages-${table.moduleName}/${table.businessName}/index')
|
||||
@@ -282,7 +758,34 @@ async function getDetail() {
|
||||
if (!props.id) {
|
||||
return
|
||||
}
|
||||
#if ($table.templateType == 10 || $table.templateType == 12)
|
||||
const [data#foreach ($subTable in $subTables)#set ($index = $foreach.count - 1), ${subClassNameVars.get($index)}Data#end] = await Promise.all([
|
||||
get${simpleClassName}(props.id),
|
||||
#foreach ($subTable in $subTables)
|
||||
#set ($index = $foreach.count - 1)
|
||||
#set ($subSimpleClassName = $subSimpleClassNames.get($index))
|
||||
#set ($subJoinColumn = $subJoinColumns.get($index))
|
||||
#set ($SubJoinColumnName = $subJoinColumn.javaField.substring(0,1).toUpperCase() + $subJoinColumn.javaField.substring(1))
|
||||
#if ($subTable.subJoinMany)
|
||||
get${subSimpleClassName}ListBy${SubJoinColumnName}(props.id),
|
||||
#else
|
||||
get${subSimpleClassName}By${SubJoinColumnName}(props.id),
|
||||
#end
|
||||
#end
|
||||
])
|
||||
formData.value = data
|
||||
#foreach ($subTable in $subTables)
|
||||
#set ($index = $foreach.count - 1)
|
||||
#set ($subClassNameVar = $subClassNameVars.get($index))
|
||||
#if ($subTable.subJoinMany)
|
||||
${subClassNameVar}s.value = ${subClassNameVar}Data || []
|
||||
#else
|
||||
${subClassNameVar}.value = ${subClassNameVar}Data || null
|
||||
#end
|
||||
#end
|
||||
#else
|
||||
formData.value = await get${simpleClassName}(props.id)
|
||||
#end
|
||||
}
|
||||
|
||||
/** 提交表单 */
|
||||
@@ -291,14 +794,31 @@ async function handleSubmit() {
|
||||
if (!valid) {
|
||||
return
|
||||
}
|
||||
#if ($table.templateType == 10 || $table.templateType == 12)
|
||||
if (!validateSubTables()) {
|
||||
return
|
||||
}
|
||||
#end
|
||||
|
||||
formLoading.value = true
|
||||
try {
|
||||
#if ($table.templateType == 10 || $table.templateType == 12)
|
||||
const data = {
|
||||
...formData.value,
|
||||
#foreach ($subTable in $subTables)
|
||||
#set ($index = $foreach.count - 1)
|
||||
#set ($subClassNameVar = $subClassNameVars.get($index))
|
||||
${subClassNameVar}#if($subTable.subJoinMany)s#end: ${subClassNameVar}#if($subTable.subJoinMany)s#end.value,
|
||||
#end
|
||||
} as ${simpleClassName}
|
||||
#else
|
||||
const data = formData.value as ${simpleClassName}
|
||||
#end
|
||||
if (props.id) {
|
||||
await update${simpleClassName}(formData.value)
|
||||
await update${simpleClassName}(data)
|
||||
toast.success('修改成功')
|
||||
} else {
|
||||
await create${simpleClassName}(formData.value)
|
||||
await create${simpleClassName}(data)
|
||||
toast.success('新增成功')
|
||||
}
|
||||
uni.$emit('${table.moduleName}:${table.businessName}:reload')
|
||||
@@ -309,7 +829,16 @@ async function handleSubmit() {
|
||||
}
|
||||
|
||||
/** 初始化 */
|
||||
onMounted(() => {
|
||||
onMounted(async () => {
|
||||
#if ($table.templateType == 2)
|
||||
const data = await get${simpleClassName}List()
|
||||
treeData.value = [{
|
||||
${primaryColumn.javaField}: 0,
|
||||
${treeNameColumn.javaField}: '顶级${table.classComment}',
|
||||
${treeParentColumn.javaField}: 0,
|
||||
children: handleTree(data, '${primaryColumn.javaField}', '${treeParentColumn.javaField}'),
|
||||
} as ${simpleClassName}]
|
||||
#end
|
||||
getDetail()
|
||||
})
|
||||
</script>
|
||||
|
||||
+250
@@ -0,0 +1,250 @@
|
||||
#set ($subTable = $subTables.get($subIndex))
|
||||
#set ($subColumns = $subColumnsList.get($subIndex))
|
||||
#set ($subSimpleClassName = $subSimpleClassNames.get($subIndex))
|
||||
#set ($subPrimaryColumn = $subPrimaryColumns.get($subIndex))
|
||||
#set ($subJoinColumn = $subJoinColumns.get($subIndex))
|
||||
#set ($subPrimaryJavaType = $subPrimaryColumn.javaType.toLowerCase())
|
||||
#if(${subPrimaryJavaType} == "long" || ${subPrimaryJavaType} == "integer" || ${subPrimaryJavaType} == "short" || ${subPrimaryJavaType} == "float" || ${subPrimaryJavaType} == "double" || ${subPrimaryJavaType} == "bigdecimal" || ${subPrimaryJavaType} == "byte")
|
||||
#set ($subPrimaryTsType = "number")
|
||||
#else
|
||||
#set ($subPrimaryTsType = "string")
|
||||
#end
|
||||
#set ($subJoinJavaType = $subJoinColumn.javaType.toLowerCase())
|
||||
#if(${subJoinJavaType} == "long" || ${subJoinJavaType} == "integer" || ${subJoinJavaType} == "short" || ${subJoinJavaType} == "float" || ${subJoinJavaType} == "double" || ${subJoinJavaType} == "bigdecimal" || ${subJoinJavaType} == "byte")
|
||||
#set ($subJoinTsType = "number")
|
||||
#else
|
||||
#set ($subJoinTsType = "string")
|
||||
#end
|
||||
#set ($hasDict = 0)
|
||||
#set ($hasBoolDict = 0)
|
||||
#set ($hasDate = 0)
|
||||
#set ($hasDateTime = 0)
|
||||
#foreach ($column in $subColumns)
|
||||
#if (($column.createOperation || $column.updateOperation) && !$column.primaryKey && $column.id != $subJoinColumn.id)
|
||||
#if (($column.htmlType == "select" || $column.htmlType == "radio") && $column.dictType && "" != $column.dictType)
|
||||
#set ($hasDict = 1)
|
||||
#if ($column.javaType == "Boolean")
|
||||
#set ($hasBoolDict = 1)
|
||||
#end
|
||||
#end
|
||||
#if (${column.javaType.toLowerCase()} == "date" || ${column.javaType.toLowerCase()} == "localdate")
|
||||
#set ($hasDate = 1)
|
||||
#end
|
||||
#if (${column.javaType.toLowerCase()} == "localdatetime")
|
||||
#set ($hasDateTime = 1)
|
||||
#end
|
||||
#end
|
||||
#end
|
||||
<template>
|
||||
<view class="yd-page-container">
|
||||
<!-- 顶部导航栏 -->
|
||||
<wd-navbar :title="getTitle" left-arrow placeholder safe-area-inset-top fixed @click-left="handleBack" />
|
||||
|
||||
<!-- 表单区域 -->
|
||||
<wd-form ref="formRef" :model="formData" :schema="formSchema">
|
||||
<wd-cell-group border>
|
||||
#foreach ($column in $subColumns)
|
||||
#if (($column.createOperation || $column.updateOperation) && !$column.primaryKey && $column.id != $subJoinColumn.id)
|
||||
#set ($javaType = $column.javaType.toLowerCase())
|
||||
#if (($column.htmlType == "select" || $column.htmlType == "radio") && $column.dictType && "" != $column.dictType)
|
||||
<yd-form-picker
|
||||
v-model="formData.${column.javaField}"
|
||||
label="${column.columnComment}"
|
||||
label-width="180rpx"
|
||||
prop="${column.javaField}"
|
||||
#if ($column.javaType == "Boolean")
|
||||
:columns="getBoolDictOptions(DICT_TYPE.${column.dictType.toUpperCase()})"
|
||||
#else
|
||||
:dict-type="DICT_TYPE.${column.dictType.toUpperCase()}"
|
||||
#if ($column.javaType == "String")
|
||||
dict-kind="str"
|
||||
#end
|
||||
#end
|
||||
placeholder="请选择${column.columnComment}"
|
||||
/>
|
||||
#elseif ($javaType == "date" || $javaType == "localdate" || $javaType == "localdatetime")
|
||||
#set ($pickerType = "date")
|
||||
#if ($javaType == "localdatetime")
|
||||
#set ($pickerType = "datetime")
|
||||
#end
|
||||
<wd-form-item
|
||||
title="${column.columnComment}"
|
||||
title-width="180rpx"
|
||||
prop="${column.javaField}"
|
||||
is-link
|
||||
#if ($javaType == "localdatetime")
|
||||
:value="formData.${column.javaField} ? formatDateTime(formData.${column.javaField}) : ''"
|
||||
#else
|
||||
:value="formData.${column.javaField} ? formatDate(formData.${column.javaField}) : ''"
|
||||
#end
|
||||
placeholder="请选择${column.columnComment}"
|
||||
@click="${column.javaField}Visible = true"
|
||||
/>
|
||||
<wd-datetime-picker
|
||||
v-model:visible="${column.javaField}Visible"
|
||||
:model-value="formData.${column.javaField} || Date.now()"
|
||||
type="${pickerType}"
|
||||
@update:model-value="value => formData.${column.javaField} = value"
|
||||
/>
|
||||
#else
|
||||
<wd-form-item title="${column.columnComment}" title-width="180rpx" prop="${column.javaField}">
|
||||
#if ($javaType == "boolean")
|
||||
<wd-switch v-model="formData.${column.javaField}" />
|
||||
#elseif ($javaType == "long" || $javaType == "integer" || $javaType == "short" || $javaType == "float" || $javaType == "double" || $javaType == "bigdecimal" || $javaType == "byte")
|
||||
<wd-input-number
|
||||
v-model="formData.${column.javaField}"
|
||||
#if($column.nullable)
|
||||
allow-null
|
||||
#end
|
||||
#if ($javaType == "float" || $javaType == "double" || $javaType == "bigdecimal")
|
||||
:precision="2"
|
||||
input-type="number"
|
||||
#end
|
||||
:min="0"
|
||||
/>
|
||||
#elseif ($column.htmlType == "textarea" || $column.htmlType == "editor")
|
||||
<wd-textarea
|
||||
v-model="formData.${column.javaField}"
|
||||
clearable
|
||||
placeholder="请输入${column.columnComment}"
|
||||
#if ($column.htmlType == "editor")
|
||||
:maxlength="2000"
|
||||
#else
|
||||
:maxlength="200"
|
||||
#end
|
||||
/>
|
||||
#elseif ($column.htmlType == "imageUpload")
|
||||
<yd-upload-img v-model="formData.${column.javaField}" directory="${table.moduleName}/${table.businessName}" />
|
||||
#elseif ($column.htmlType == "fileUpload")
|
||||
<yd-upload-file v-model="formData.${column.javaField}" :limit="1" directory="${table.moduleName}/${table.businessName}" />
|
||||
#else
|
||||
<wd-input v-model="formData.${column.javaField}" clearable placeholder="请输入${column.columnComment}" />
|
||||
#end
|
||||
</wd-form-item>
|
||||
#end
|
||||
#end
|
||||
#end
|
||||
</wd-cell-group>
|
||||
</wd-form>
|
||||
|
||||
<!-- 底部保存按钮 -->
|
||||
<view class="yd-detail-footer">
|
||||
<wd-button type="primary" block :loading="formLoading" @click="handleSubmit">
|
||||
保存
|
||||
</wd-button>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { FormInstance } from '@wot-ui/ui/components/wd-form/types'
|
||||
import type { ${subSimpleClassName} } from '@/api/${table.moduleName}/${table.businessName}'
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
import { useToast } from '@wot-ui/ui/components/wd-toast'
|
||||
import { create${subSimpleClassName}, get${subSimpleClassName}, update${subSimpleClassName} } from '@/api/${table.moduleName}/${table.businessName}'
|
||||
import { delay, navigateBackPlus } from '@/utils'
|
||||
#if ($hasBoolDict == 1)
|
||||
import { getBoolDictOptions } from '@/hooks/useDict'
|
||||
#end
|
||||
#if ($hasDict == 1)
|
||||
import { DICT_TYPE } from '@/utils/constants'
|
||||
#end
|
||||
#if ($hasDate == 1 || $hasDateTime == 1)
|
||||
#set ($dateImportNames = "")
|
||||
#if ($hasDate == 1)
|
||||
#set ($dateImportNames = "${dateImportNames}formatDate, ")
|
||||
#end
|
||||
#if ($hasDateTime == 1)
|
||||
#set ($dateImportNames = "${dateImportNames}formatDateTime, ")
|
||||
#end
|
||||
#set ($dateImportNames = $dateImportNames.trim())
|
||||
#set ($dateImportNames = $dateImportNames.substring(0, $dateImportNames.length() - 1))
|
||||
import { $dateImportNames } from '@/utils/date'
|
||||
#end
|
||||
import { createFormSchema } from '@/utils/wot'
|
||||
|
||||
const props = defineProps<{
|
||||
id?: ${subPrimaryTsType}
|
||||
${subJoinColumn.javaField}: ${subJoinTsType}
|
||||
}>()
|
||||
|
||||
definePage({
|
||||
style: { navigationBarTitleText: '', navigationStyle: 'custom' },
|
||||
})
|
||||
|
||||
const toast = useToast()
|
||||
const getTitle = computed(() => props.id ? '编辑${subTable.classComment}' : '新增${subTable.classComment}')
|
||||
const formLoading = ref(false) // 表单提交状态
|
||||
const formData = ref<Partial<${subSimpleClassName}>>({
|
||||
#foreach ($column in $subColumns)
|
||||
#if ($column.primaryKey)
|
||||
${column.javaField}: undefined,
|
||||
#elseif ($column.id == $subJoinColumn.id)
|
||||
${column.javaField}: props.${column.javaField},
|
||||
#elseif ($column.createOperation || $column.updateOperation)
|
||||
#set ($javaType = $column.javaType.toLowerCase())
|
||||
#if((($column.htmlType == "select" || $column.htmlType == "radio") && $column.dictType && "" != $column.dictType) || $javaType == "date" || $javaType == "localdate" || $javaType == "localdatetime")
|
||||
${column.javaField}: undefined,
|
||||
#else
|
||||
${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,
|
||||
#end
|
||||
#end
|
||||
#end
|
||||
}) // 表单数据
|
||||
#foreach ($column in $subColumns)
|
||||
#if (($column.createOperation || $column.updateOperation) && !$column.primaryKey && $column.id != $subJoinColumn.id
|
||||
&& (${column.javaType.toLowerCase()} == "date" || ${column.javaType.toLowerCase()} == "localdate" || ${column.javaType.toLowerCase()} == "localdatetime"))
|
||||
const ${column.javaField}Visible = ref(false) // ${column.columnComment}选择器状态
|
||||
#end
|
||||
#end
|
||||
const formSchema = createFormSchema({
|
||||
#foreach ($column in $subColumns)
|
||||
#if (($column.createOperation || $column.updateOperation) && !$column.nullable && !$column.primaryKey)
|
||||
${column.javaField}: [{ required: true, message: '${column.columnComment}不能为空' }],
|
||||
#end
|
||||
#end
|
||||
}) // 表单校验规则
|
||||
const formRef = ref<FormInstance>() // 表单组件引用
|
||||
|
||||
/** 返回上一页 */
|
||||
function handleBack() {
|
||||
navigateBackPlus()
|
||||
}
|
||||
|
||||
/** 加载${subTable.classComment}详情 */
|
||||
async function getDetail() {
|
||||
if (props.id) {
|
||||
formData.value = await get${subSimpleClassName}(props.id)
|
||||
}
|
||||
}
|
||||
|
||||
/** 提交表单 */
|
||||
async function handleSubmit() {
|
||||
const { valid } = await formRef.value.validate()
|
||||
if (!valid) {
|
||||
return
|
||||
}
|
||||
formLoading.value = true
|
||||
try {
|
||||
const data = {
|
||||
...formData.value,
|
||||
${subJoinColumn.javaField}: props.${subJoinColumn.javaField} ?? formData.value.${subJoinColumn.javaField},
|
||||
} as ${subSimpleClassName}
|
||||
if (props.id) {
|
||||
await update${subSimpleClassName}(data)
|
||||
toast.success('修改成功')
|
||||
} else {
|
||||
await create${subSimpleClassName}(data)
|
||||
toast.success('新增成功')
|
||||
}
|
||||
delay(handleBack)
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 初始化 */
|
||||
onMounted(() => {
|
||||
getDetail()
|
||||
})
|
||||
</script>
|
||||
+151
@@ -0,0 +1,151 @@
|
||||
<template>
|
||||
<view class="yd-page-container">
|
||||
<!-- 顶部导航栏 -->
|
||||
<wd-navbar
|
||||
title="${table.classComment}管理"
|
||||
left-arrow placeholder safe-area-inset-top fixed
|
||||
@click-left="handleBack"
|
||||
/>
|
||||
|
||||
<!-- 搜索组件 -->
|
||||
<SearchForm @search="handleQuery" @reset="handleReset" />
|
||||
|
||||
<!-- 面包屑导航 -->
|
||||
<Breadcrumb ref="breadcrumbRef" v-model="currentParentId" />
|
||||
|
||||
<!-- ${table.classComment}列表 -->
|
||||
<view class="p-24rpx">
|
||||
<view
|
||||
v-for="item in currentList"
|
||||
:key="item.${primaryColumn.javaField}"
|
||||
class="mb-24rpx overflow-hidden rounded-12rpx bg-white shadow-sm"
|
||||
>
|
||||
<view class="p-24rpx" @click="handleDetail(item)">
|
||||
<view class="flex items-center justify-between">
|
||||
<view class="flex min-w-0 items-center">
|
||||
<view class="mr-16rpx h-48rpx w-48rpx shrink-0 flex items-center justify-center rounded-8rpx bg-[#1890ff]">
|
||||
<wd-icon name="folder" size="20px" color="#fff" />
|
||||
</view>
|
||||
<view class="truncate text-32rpx text-[#333] font-semibold">
|
||||
{{ item.${treeNameColumn.javaField} }}
|
||||
</view>
|
||||
</view>
|
||||
<view
|
||||
v-if="item.children?.length"
|
||||
class="ml-16rpx shrink-0 flex items-center"
|
||||
@click.stop="handleEnterChildren(item)"
|
||||
>
|
||||
<text class="text-24rpx text-[#1890ff]">子级 ({{ item.children.length }})</text>
|
||||
<wd-icon name="arrow-right" size="12px" color="#1890ff" />
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<view v-if="!loading && currentList.length === 0" class="py-100rpx text-center">
|
||||
<wd-empty icon="content" tip="暂无${table.classComment}数据" />
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 新增按钮 -->
|
||||
<wd-fab
|
||||
v-if="hasAccessByCodes(['${permissionPrefix}:create'])"
|
||||
position="right-bottom"
|
||||
type="primary"
|
||||
:expandable="false"
|
||||
@click="handleAdd"
|
||||
/>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
#set ($primaryJavaType = $primaryColumn.javaType.toLowerCase())
|
||||
#if(${primaryJavaType} == "long" || ${primaryJavaType} == "integer" || ${primaryJavaType} == "short" || ${primaryJavaType} == "float" || ${primaryJavaType} == "double" || ${primaryJavaType} == "bigdecimal" || ${primaryJavaType} == "byte")
|
||||
#set ($primaryTsType = "number")
|
||||
#else
|
||||
#set ($primaryTsType = "string")
|
||||
#end
|
||||
import type { ${simpleClassName} } from '@/api/${table.moduleName}/${table.businessName}'
|
||||
import { onUnload } from '@dcloudio/uni-app'
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
import { get${simpleClassName}List } from '@/api/${table.moduleName}/${table.businessName}'
|
||||
import { useAccess } from '@/hooks/useAccess'
|
||||
import { navigateBackPlus } from '@/utils'
|
||||
import { findChildren, handleTree } from '@/utils/tree'
|
||||
import Breadcrumb from './components/breadcrumb.vue'
|
||||
import SearchForm from './components/search-form.vue'
|
||||
|
||||
definePage({
|
||||
style: {
|
||||
navigationBarTitleText: '',
|
||||
navigationStyle: 'custom',
|
||||
},
|
||||
})
|
||||
|
||||
const { hasAccessByCodes } = useAccess()
|
||||
const loading = ref(false) // 列表加载状态
|
||||
const list = ref<${simpleClassName}[]>([]) // 完整树形列表
|
||||
const currentParentId = ref<${primaryTsType}>(0) // 当前层级的父节点编号
|
||||
const currentList = computed(() => currentParentId.value === 0
|
||||
? list.value
|
||||
: findChildren(list.value, currentParentId.value, '${primaryColumn.javaField}')) // 当前层级列表
|
||||
const breadcrumbRef = ref<InstanceType<typeof Breadcrumb>>() // 面包屑组件引用
|
||||
const queryParams = ref<Record<string, any>>({}) // 查询参数
|
||||
|
||||
/** 返回上一页或上一层级 */
|
||||
function handleBack() {
|
||||
if (!breadcrumbRef.value?.back()) {
|
||||
navigateBackPlus()
|
||||
}
|
||||
}
|
||||
|
||||
/** 进入子层级 */
|
||||
function handleEnterChildren(item: ${simpleClassName}) {
|
||||
breadcrumbRef.value?.enter({ id: item.${primaryColumn.javaField}!, name: item.${treeNameColumn.javaField} })
|
||||
}
|
||||
|
||||
/** 查询${table.classComment}列表 */
|
||||
async function getList() {
|
||||
loading.value = true
|
||||
try {
|
||||
list.value = handleTree(await get${simpleClassName}List(queryParams.value), '${primaryColumn.javaField}', '${treeParentColumn.javaField}')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
function handleQuery(data?: Record<string, any>) {
|
||||
queryParams.value = { ...data }
|
||||
currentParentId.value = 0
|
||||
breadcrumbRef.value?.reset()
|
||||
getList()
|
||||
}
|
||||
|
||||
/** 重置按钮操作 */
|
||||
function handleReset() {
|
||||
handleQuery()
|
||||
}
|
||||
|
||||
/** 新增${table.classComment} */
|
||||
function handleAdd() {
|
||||
uni.navigateTo({ url: `/pages-${table.moduleName}/${table.businessName}/form/index?${treeParentColumn.javaField}=#[[${]]#currentParentId.value#[[}]]#` })
|
||||
}
|
||||
|
||||
/** 查看详情 */
|
||||
function handleDetail(item: ${simpleClassName}) {
|
||||
uni.navigateTo({ url: `/pages-${table.moduleName}/${table.businessName}/detail/index?id=#[[${]]#item.${primaryColumn.javaField}#[[}]]#` })
|
||||
}
|
||||
|
||||
/** 初始化 */
|
||||
onMounted(() => {
|
||||
getList()
|
||||
uni.$on('${table.moduleName}:${table.businessName}:reload', getList)
|
||||
})
|
||||
|
||||
/** 卸载 */
|
||||
onUnload(() => {
|
||||
uni.$off('${table.moduleName}:${table.businessName}:reload', getList)
|
||||
})
|
||||
</script>
|
||||
+9
-5
@@ -157,13 +157,17 @@ public abstract class CodegenEngineAbstractTest extends BaseMockitoUnitTest {
|
||||
String lastFilePath = StrUtil.subAfter(filePath, '/', true);
|
||||
String ext = StrUtil.subAfter(lastFilePath, '.', true);
|
||||
String name = StrUtil.subBefore(lastFilePath, '.', true);
|
||||
String contentPath = ext + '/' + name;
|
||||
// 同名文件(如 index.vue 同时出现在 列表/form/detail)会撞名,撞名时前缀补上级目录区分;仍撞则加序号兜底,避免快照互相覆盖
|
||||
String parentPath = StrUtil.subBefore(filePath, '/' + lastFilePath, true);
|
||||
String parentDir = StrUtil.subAfter(parentPath, '/', true);
|
||||
// index.vue 按页面语义稳定归档,避免结果迭代顺序导致列表页和表单页的快照路径互换
|
||||
String contentPath = "index".equals(name) && ("form".equals(parentDir) || "detail".equals(parentDir))
|
||||
? ext + '/' + parentDir + '/' + name : ext + '/' + name;
|
||||
// ERP 多个子表的 form/index.vue 仍可能撞名,继续补充祖父目录区分
|
||||
if (usedContentPaths.contains(contentPath)) {
|
||||
String parentDir = StrUtil.subAfter(StrUtil.subBefore(filePath, '/' + lastFilePath, true), '/', true);
|
||||
contentPath = ext + '/' + parentDir + '/' + name;
|
||||
String grandParentDir = StrUtil.subAfter(StrUtil.subBefore(parentPath, '/' + parentDir, true), '/', true);
|
||||
contentPath = ext + '/' + grandParentDir + '/' + parentDir + '/' + name;
|
||||
for (int i = 2; usedContentPaths.contains(contentPath); i++) {
|
||||
contentPath = ext + '/' + parentDir + '/' + name + '-' + i;
|
||||
contentPath = ext + '/' + grandParentDir + '/' + parentDir + '/' + name + '-' + i;
|
||||
}
|
||||
}
|
||||
usedContentPaths.add(contentPath);
|
||||
|
||||
+316
@@ -1,5 +1,6 @@
|
||||
package cn.iocoder.yudao.module.infra.service.codegen.inner;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.exception.ServiceException;
|
||||
import cn.iocoder.yudao.module.infra.dal.dataobject.codegen.CodegenColumnDO;
|
||||
import cn.iocoder.yudao.module.infra.dal.dataobject.codegen.CodegenTableDO;
|
||||
import cn.iocoder.yudao.module.infra.enums.codegen.CodegenFrontTypeEnum;
|
||||
@@ -7,10 +8,14 @@ import cn.iocoder.yudao.module.infra.enums.codegen.CodegenTemplateTypeEnum;
|
||||
import com.baomidou.mybatisplus.annotation.DbType;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
/**
|
||||
@@ -59,4 +64,315 @@ public class CodegenEngineUniappTest extends CodegenEngineAbstractTest {
|
||||
assertTrue(searchForm.contains("sex: undefined as string | undefined,"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExecute_tree() {
|
||||
// 准备参数
|
||||
CodegenTableDO table = getTable("category")
|
||||
.setFrontType(CodegenFrontTypeEnum.VUE3_ADMIN_UNIAPP_WOT.getType())
|
||||
.setTemplateType(CodegenTemplateTypeEnum.TREE.getType());
|
||||
List<CodegenColumnDO> columns = getColumnList("category");
|
||||
|
||||
// 调用
|
||||
Map<String, String> result = codegenEngine.execute(DbType.MYSQL, table, columns, null, null);
|
||||
// 断言
|
||||
assertResult(result, "/vue3_admin_uniapp_tree");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExecute_semantics() {
|
||||
// 准备参数
|
||||
CodegenTableDO table = getTable("student")
|
||||
.setFrontType(CodegenFrontTypeEnum.VUE3_ADMIN_UNIAPP_WOT.getType())
|
||||
.setTemplateType(CodegenTemplateTypeEnum.ONE.getType());
|
||||
List<CodegenColumnDO> columns = getColumnList("student");
|
||||
columns.stream().filter(column -> "memo".equals(column.getJavaField())).findFirst().orElseThrow()
|
||||
.setJavaType("Float").setHtmlType("input");
|
||||
|
||||
// 调用
|
||||
Map<String, String> result = codegenEngine.execute(DbType.MYSQL, table, columns, null, null);
|
||||
// 断言
|
||||
String api = result.get("yudao-ui-admin-uniapp/src/api/infra/demo/index.ts");
|
||||
assertTrue(api.contains("birthday: number"));
|
||||
assertTrue(api.contains("createTime?: number"));
|
||||
assertTrue(api.contains("memo: number"));
|
||||
assertFalse(api.contains("createTime?: Date"));
|
||||
String form = result.get("yudao-ui-admin-uniapp/src/pages-infra/demo/form/index.vue");
|
||||
assertTrue(form.contains("v-model=\"formData.memo\""));
|
||||
assertTrue(form.contains(":precision=\"2\""));
|
||||
String detail = result.get("yudao-ui-admin-uniapp/src/pages-infra/demo/detail/index.vue");
|
||||
assertTrue(detail.contains("type=\"danger\""));
|
||||
assertFalse(detail.contains("type=\"error\""));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExecute_stringPrimaryKey() {
|
||||
// 准备主表
|
||||
CodegenTableDO table = getTable("student")
|
||||
.setFrontType(CodegenFrontTypeEnum.VUE3_ADMIN_UNIAPP_WOT.getType())
|
||||
.setTemplateType(CodegenTemplateTypeEnum.MASTER_ERP.getType());
|
||||
List<CodegenColumnDO> columns = getColumnList("student");
|
||||
columns.stream().filter(CodegenColumnDO::getPrimaryKey).findFirst().orElseThrow().setJavaType("String");
|
||||
// 准备子表
|
||||
CodegenTableDO contactTable = getTable("contact")
|
||||
.setTemplateType(CodegenTemplateTypeEnum.SUB.getType())
|
||||
.setFrontType(CodegenFrontTypeEnum.VUE3_ADMIN_UNIAPP_WOT.getType())
|
||||
.setSubJoinColumnId(100L).setSubJoinMany(true);
|
||||
List<CodegenColumnDO> contactColumns = getColumnList("contact");
|
||||
contactColumns.stream().filter(CodegenColumnDO::getPrimaryKey).findFirst().orElseThrow().setJavaType("String");
|
||||
contactColumns.stream().filter(column -> Objects.equals(column.getId(), 100L))
|
||||
.findFirst().orElseThrow().setJavaType("String");
|
||||
|
||||
// 调用
|
||||
Map<String, String> result = codegenEngine.execute(DbType.MYSQL, table, columns,
|
||||
List.of(contactTable), List.of(contactColumns));
|
||||
// 断言
|
||||
String api = result.get("yudao-ui-admin-uniapp/src/api/infra/demo/index.ts");
|
||||
assertTrue(api.contains("return http.post<string>('/infra/student/create', data)"));
|
||||
assertTrue(api.contains("studentId: string"));
|
||||
assertTrue(api.contains("getStudentContact(id: string)"));
|
||||
assertTrue(api.contains("deleteStudentContact(id: string)"));
|
||||
String detail = result.get("yudao-ui-admin-uniapp/src/pages-infra/demo/detail/index.vue");
|
||||
assertFalse(detail.contains("Number(props.id)"));
|
||||
String subForm = result.get("yudao-ui-admin-uniapp/src/pages-infra/demo/student-contact/form/index.vue");
|
||||
assertTrue(subForm.contains("id?: string"));
|
||||
assertTrue(subForm.contains("studentId: string"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExecute_treeSearch() {
|
||||
// 准备参数
|
||||
CodegenTableDO table = getTable("category")
|
||||
.setFrontType(CodegenFrontTypeEnum.VUE3_ADMIN_UNIAPP_WOT.getType())
|
||||
.setTemplateType(CodegenTemplateTypeEnum.TREE.getType());
|
||||
List<CodegenColumnDO> columns = getColumnList("category");
|
||||
|
||||
// 调用
|
||||
Map<String, String> result = codegenEngine.execute(DbType.MYSQL, table, columns, null, null);
|
||||
// 断言
|
||||
String index = result.get("yudao-ui-admin-uniapp/src/pages-infra/demo/index.vue");
|
||||
assertTrue(index.contains("currentParentId.value === 0\n ? list.value"));
|
||||
assertFalse(index.contains("list.value.filter"));
|
||||
String breadcrumb = result.get("yudao-ui-admin-uniapp/src/pages-infra/demo/components/breadcrumb.vue");
|
||||
assertTrue(breadcrumb.contains("modelValue: number"));
|
||||
assertFalse(breadcrumb.contains("TreeValue"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExecute_duplicateErpSymbol() {
|
||||
// 准备主表
|
||||
CodegenTableDO table = getTable("student")
|
||||
.setFrontType(CodegenFrontTypeEnum.VUE3_ADMIN_UNIAPP_WOT.getType())
|
||||
.setTemplateType(CodegenTemplateTypeEnum.MASTER_ERP.getType());
|
||||
List<CodegenColumnDO> columns = getColumnList("student");
|
||||
// 准备两个归一化名称相同的子表
|
||||
CodegenTableDO crmItemTable = getTable("contact").setModuleName("crm").setClassName("CrmOrderItem")
|
||||
.setTemplateType(CodegenTemplateTypeEnum.SUB.getType())
|
||||
.setFrontType(CodegenFrontTypeEnum.VUE3_ADMIN_UNIAPP_WOT.getType())
|
||||
.setSubJoinColumnId(100L).setSubJoinMany(true);
|
||||
CodegenTableDO erpItemTable = getTable("contact").setModuleName("erp").setClassName("ErpOrderItem")
|
||||
.setTemplateType(CodegenTemplateTypeEnum.SUB.getType())
|
||||
.setFrontType(CodegenFrontTypeEnum.VUE3_ADMIN_UNIAPP_WOT.getType())
|
||||
.setSubJoinColumnId(100L).setSubJoinMany(true);
|
||||
List<CodegenColumnDO> crmItemColumns = getColumnList("contact");
|
||||
List<CodegenColumnDO> erpItemColumns = getColumnList("contact");
|
||||
|
||||
// 调用
|
||||
ServiceException exception = assertThrows(ServiceException.class,
|
||||
() -> codegenEngine.execute(DbType.MYSQL, table, columns,
|
||||
Arrays.asList(crmItemTable, erpItemTable), Arrays.asList(crmItemColumns, erpItemColumns)));
|
||||
// 断言
|
||||
assertTrue(exception.getMessage().contains("主子表规范化类名(OrderItem)重复"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExecute_duplicateMasterSymbol() {
|
||||
// 准备主表
|
||||
CodegenTableDO table = getTable("student")
|
||||
.setFrontType(CodegenFrontTypeEnum.VUE3_ELEMENT_PLUS.getType())
|
||||
.setTemplateType(CodegenTemplateTypeEnum.MASTER_NORMAL.getType());
|
||||
List<CodegenColumnDO> columns = getColumnList("student");
|
||||
// 准备两个规范化名称相同的子表
|
||||
CodegenTableDO crmItemTable = getTable("contact").setModuleName("crm").setClassName("CrmOrderItem")
|
||||
.setTemplateType(CodegenTemplateTypeEnum.SUB.getType()).setSubJoinColumnId(100L).setSubJoinMany(true);
|
||||
CodegenTableDO erpItemTable = getTable("contact").setModuleName("erp").setClassName("ErpOrderItem")
|
||||
.setTemplateType(CodegenTemplateTypeEnum.SUB.getType()).setSubJoinColumnId(100L).setSubJoinMany(true);
|
||||
List<CodegenColumnDO> crmItemColumns = getColumnList("contact");
|
||||
List<CodegenColumnDO> erpItemColumns = getColumnList("contact");
|
||||
|
||||
// 调用并断言
|
||||
ServiceException exception = assertThrows(ServiceException.class,
|
||||
() -> codegenEngine.execute(DbType.MYSQL, table, columns,
|
||||
Arrays.asList(crmItemTable, erpItemTable), Arrays.asList(crmItemColumns, erpItemColumns)));
|
||||
assertTrue(exception.getMessage().contains("主子表规范化类名(OrderItem)重复"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExecute_duplicateMasterFieldNormal() {
|
||||
testExecute_duplicateMasterField(CodegenTemplateTypeEnum.MASTER_NORMAL);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExecute_duplicateMasterFieldInner() {
|
||||
testExecute_duplicateMasterField(CodegenTemplateTypeEnum.MASTER_INNER);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExecute_duplicateMasterFieldWithMainNormal() {
|
||||
testExecute_duplicateMasterFieldWithMain(CodegenTemplateTypeEnum.MASTER_NORMAL);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExecute_duplicateMasterFieldWithMainInner() {
|
||||
testExecute_duplicateMasterFieldWithMain(CodegenTemplateTypeEnum.MASTER_INNER);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExecute_duplicateMasterFieldErp() {
|
||||
// 准备主表
|
||||
CodegenTableDO table = getTable("student")
|
||||
.setFrontType(CodegenFrontTypeEnum.VUE3_ADMIN_UNIAPP_WOT.getType())
|
||||
.setTemplateType(CodegenTemplateTypeEnum.MASTER_ERP.getType());
|
||||
List<CodegenColumnDO> columns = getColumnList("student");
|
||||
// 准备属性名同为 items 的子表:Item 一对多、Items 一对一
|
||||
CodegenTableDO crmItemTable = getTable("contact").setModuleName("crm").setClassName("CrmItem")
|
||||
.setTemplateType(CodegenTemplateTypeEnum.SUB.getType()).setSubJoinColumnId(100L).setSubJoinMany(true);
|
||||
CodegenTableDO erpItemsTable = getTable("contact").setModuleName("erp").setClassName("ErpItems")
|
||||
.setTemplateType(CodegenTemplateTypeEnum.SUB.getType()).setSubJoinColumnId(100L).setSubJoinMany(false);
|
||||
|
||||
// 调用
|
||||
Map<String, String> result = codegenEngine.execute(DbType.MYSQL, table, columns,
|
||||
Arrays.asList(crmItemTable, erpItemsTable),
|
||||
Arrays.asList(getColumnList("contact"), getColumnList("contact")));
|
||||
// 断言
|
||||
assertTrue(result.containsKey("yudao-ui-admin-uniapp/src/pages-infra/demo/item/form/index.vue"));
|
||||
assertTrue(result.containsKey("yudao-ui-admin-uniapp/src/pages-infra/demo/items/form/index.vue"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRegisterGeneratedSource_duplicatePath() {
|
||||
// 准备参数
|
||||
Map<String, String> generatedSources = new HashMap<>();
|
||||
Map<String, Object> bindingMap = new HashMap<>();
|
||||
bindingMap.put("subIndex", 0);
|
||||
CodegenEngine.registerGeneratedSource(generatedSources, "same.vue", "form_sub.vm", bindingMap);
|
||||
bindingMap.put("subIndex", 1);
|
||||
|
||||
// 调用
|
||||
IllegalStateException exception = assertThrows(IllegalStateException.class,
|
||||
() -> CodegenEngine.registerGeneratedSource(generatedSources, "same.vue", "form_sub.vm", bindingMap));
|
||||
// 断言
|
||||
assertTrue(exception.getMessage().contains("生成文件路径重复:same.vue"));
|
||||
assertTrue(exception.getMessage().contains("form_sub.vm[subIndex=0]"));
|
||||
assertTrue(exception.getMessage().contains("form_sub.vm[subIndex=1]"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExecute_masterNormal() {
|
||||
testExecute_master(CodegenTemplateTypeEnum.MASTER_NORMAL, "/vue3_admin_uniapp_master_normal");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExecute_masterInner() {
|
||||
testExecute_master(CodegenTemplateTypeEnum.MASTER_INNER, "/vue3_admin_uniapp_master_inner");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExecute_masterErp() {
|
||||
testExecute_master(CodegenTemplateTypeEnum.MASTER_ERP, "/vue3_admin_uniapp_master_erp");
|
||||
}
|
||||
|
||||
private void testExecute_duplicateMasterField(CodegenTemplateTypeEnum templateType) {
|
||||
// 准备主表
|
||||
CodegenTableDO table = getTable("student")
|
||||
.setFrontType(CodegenFrontTypeEnum.VUE3_ELEMENT_PLUS.getType())
|
||||
.setTemplateType(templateType.getType());
|
||||
List<CodegenColumnDO> columns = getColumnList("student");
|
||||
// 准备属性名同为 items 的子表:Item 一对多、Items 一对一
|
||||
CodegenTableDO crmItemTable = getTable("contact").setModuleName("crm").setClassName("CrmItem")
|
||||
.setTemplateType(CodegenTemplateTypeEnum.SUB.getType()).setSubJoinColumnId(100L).setSubJoinMany(true);
|
||||
CodegenTableDO erpItemsTable = getTable("contact").setModuleName("erp").setClassName("ErpItems")
|
||||
.setTemplateType(CodegenTemplateTypeEnum.SUB.getType()).setSubJoinColumnId(100L).setSubJoinMany(false);
|
||||
List<CodegenColumnDO> crmItemColumns = getColumnList("contact");
|
||||
List<CodegenColumnDO> erpItemsColumns = getColumnList("contact");
|
||||
|
||||
// 调用并断言
|
||||
ServiceException exception = assertThrows(ServiceException.class,
|
||||
() -> codegenEngine.execute(DbType.MYSQL, table, columns,
|
||||
Arrays.asList(crmItemTable, erpItemsTable), Arrays.asList(crmItemColumns, erpItemsColumns)));
|
||||
assertTrue(exception.getMessage().contains("主子表属性名(items)重复"));
|
||||
}
|
||||
|
||||
private void testExecute_duplicateMasterFieldWithMain(CodegenTemplateTypeEnum templateType) {
|
||||
// 准备主表
|
||||
CodegenTableDO table = getTable("student")
|
||||
.setFrontType(CodegenFrontTypeEnum.VUE3_ELEMENT_PLUS.getType())
|
||||
.setTemplateType(templateType.getType());
|
||||
List<CodegenColumnDO> columns = getColumnList("student");
|
||||
columns.stream().filter(column -> "memo".equals(column.getJavaField())).findFirst().orElseThrow()
|
||||
.setJavaField("items");
|
||||
// 准备一对多子表 Item,生成属性名 items
|
||||
CodegenTableDO itemTable = getTable("contact").setModuleName("crm").setClassName("CrmItem")
|
||||
.setTemplateType(CodegenTemplateTypeEnum.SUB.getType()).setSubJoinColumnId(100L).setSubJoinMany(true);
|
||||
|
||||
// 调用并断言
|
||||
ServiceException exception = assertThrows(ServiceException.class,
|
||||
() -> codegenEngine.execute(DbType.MYSQL, table, columns,
|
||||
List.of(itemTable), List.of(getColumnList("contact"))));
|
||||
assertTrue(exception.getMessage().contains("主子表属性名(items)重复"));
|
||||
}
|
||||
|
||||
private void testExecute_master(CodegenTemplateTypeEnum templateType, String path) {
|
||||
// 准备主表
|
||||
CodegenTableDO table = getTable("student")
|
||||
.setFrontType(CodegenFrontTypeEnum.VUE3_ADMIN_UNIAPP_WOT.getType())
|
||||
.setTemplateType(templateType.getType());
|
||||
List<CodegenColumnDO> columns = getColumnList("student");
|
||||
// 准备一对多子表
|
||||
CodegenTableDO contactTable = getTable("contact")
|
||||
.setTemplateType(CodegenTemplateTypeEnum.SUB.getType())
|
||||
.setFrontType(CodegenFrontTypeEnum.VUE3_ADMIN_UNIAPP_WOT.getType())
|
||||
.setSubJoinColumnId(100L).setSubJoinMany(true);
|
||||
List<CodegenColumnDO> contactColumns = getColumnList("contact");
|
||||
// 准备一对一子表
|
||||
CodegenTableDO teacherTable = getTable("teacher")
|
||||
.setTemplateType(CodegenTemplateTypeEnum.SUB.getType())
|
||||
.setFrontType(CodegenFrontTypeEnum.VUE3_ADMIN_UNIAPP_WOT.getType())
|
||||
.setSubJoinColumnId(200L).setSubJoinMany(false);
|
||||
List<CodegenColumnDO> teacherColumns = getColumnList("teacher");
|
||||
|
||||
// 调用
|
||||
Map<String, String> result = codegenEngine.execute(DbType.MYSQL, table, columns,
|
||||
Arrays.asList(contactTable, teacherTable), Arrays.asList(contactColumns, teacherColumns));
|
||||
// 断言
|
||||
assertResult(result, path);
|
||||
if (templateType != CodegenTemplateTypeEnum.MASTER_ERP) {
|
||||
String api = result.get("yudao-ui-admin-uniapp/src/api/infra/demo/index.ts");
|
||||
assertTrue(api.contains("studentTeacher?: StudentTeacher | null"));
|
||||
String form = result.get("yudao-ui-admin-uniapp/src/pages-infra/demo/form/index.vue");
|
||||
assertTrue(form.contains("ref<Partial<StudentTeacher> | null>(null)"));
|
||||
assertTrue(form.contains("studentTeacher.value = studentTeacherData || null"));
|
||||
assertTrue(form.contains(":model-value=\"item.birthday || Date.now()\""));
|
||||
assertTrue(form.contains("v-model=\"item.sex\""));
|
||||
assertTrue(form.contains("<yd-upload-img v-model=\"item.avatar\""));
|
||||
assertTrue(form.contains("<yd-upload-file v-model=\"item.video\""));
|
||||
assertTrue(form.contains("<yd-upload-file v-model=\"item.video\" :limit=\"1\""));
|
||||
assertTrue(form.contains("v-model=\"item.description\""));
|
||||
assertTrue(form.contains("v-model=\"item.memo\""));
|
||||
assertTrue(form.contains("birthday: undefined"));
|
||||
assertTrue(form.contains("function validateSubTables()"));
|
||||
} else {
|
||||
String contactForm = result.get(
|
||||
"yudao-ui-admin-uniapp/src/pages-infra/demo/student-contact/form/index.vue");
|
||||
assertTrue(contactForm.contains(":model-value=\"formData.birthday || Date.now()\""));
|
||||
assertTrue(contactForm.contains("v-model=\"formData.sex\""));
|
||||
assertTrue(contactForm.contains("<yd-upload-img v-model=\"formData.avatar\""));
|
||||
assertTrue(contactForm.contains("<yd-upload-file v-model=\"formData.video\""));
|
||||
assertTrue(contactForm.contains("<yd-upload-file v-model=\"formData.video\" :limit=\"1\""));
|
||||
assertTrue(contactForm.contains("v-model=\"formData.description\""));
|
||||
assertTrue(contactForm.contains("v-model=\"formData.memo\""));
|
||||
assertTrue(contactForm.contains("birthday: undefined"));
|
||||
assertTrue(contactForm.contains(":columns=\"getBoolDictOptions"));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+73
@@ -0,0 +1,73 @@
|
||||
[ {
|
||||
"contentPath" : "java/InfraStudentPageReqVO",
|
||||
"filePath" : "yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/demo/vo/InfraStudentPageReqVO.java"
|
||||
}, {
|
||||
"contentPath" : "java/InfraStudentRespVO",
|
||||
"filePath" : "yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/demo/vo/InfraStudentRespVO.java"
|
||||
}, {
|
||||
"contentPath" : "java/InfraStudentSaveReqVO",
|
||||
"filePath" : "yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/demo/vo/InfraStudentSaveReqVO.java"
|
||||
}, {
|
||||
"contentPath" : "java/InfraStudentController",
|
||||
"filePath" : "yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/demo/InfraStudentController.java"
|
||||
}, {
|
||||
"contentPath" : "java/InfraStudentDO",
|
||||
"filePath" : "yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/dal/dataobject/demo/InfraStudentDO.java"
|
||||
}, {
|
||||
"contentPath" : "java/InfraStudentContactDO",
|
||||
"filePath" : "yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/dal/dataobject/demo/InfraStudentContactDO.java"
|
||||
}, {
|
||||
"contentPath" : "java/InfraStudentTeacherDO",
|
||||
"filePath" : "yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/dal/dataobject/demo/InfraStudentTeacherDO.java"
|
||||
}, {
|
||||
"contentPath" : "java/InfraStudentMapper",
|
||||
"filePath" : "yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/dal/mysql/demo/InfraStudentMapper.java"
|
||||
}, {
|
||||
"contentPath" : "java/InfraStudentContactMapper",
|
||||
"filePath" : "yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/dal/mysql/demo/InfraStudentContactMapper.java"
|
||||
}, {
|
||||
"contentPath" : "java/InfraStudentTeacherMapper",
|
||||
"filePath" : "yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/dal/mysql/demo/InfraStudentTeacherMapper.java"
|
||||
}, {
|
||||
"contentPath" : "xml/InfraStudentMapper",
|
||||
"filePath" : "yudao-module-infra/src/main/resources/mapper/demo/InfraStudentMapper.xml"
|
||||
}, {
|
||||
"contentPath" : "java/InfraStudentServiceImpl",
|
||||
"filePath" : "yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/service/demo/InfraStudentServiceImpl.java"
|
||||
}, {
|
||||
"contentPath" : "java/InfraStudentService",
|
||||
"filePath" : "yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/service/demo/InfraStudentService.java"
|
||||
}, {
|
||||
"contentPath" : "java/InfraStudentServiceImplTest",
|
||||
"filePath" : "yudao-module-infra/src/test/java/cn/iocoder/yudao/module/infra/service/demo/InfraStudentServiceImplTest.java"
|
||||
}, {
|
||||
"contentPath" : "java/ErrorCodeConstants_手动操作",
|
||||
"filePath" : "yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/enums/ErrorCodeConstants_手动操作.java"
|
||||
}, {
|
||||
"contentPath" : "sql/sql",
|
||||
"filePath" : "sql/sql.sql"
|
||||
}, {
|
||||
"contentPath" : "sql/h2",
|
||||
"filePath" : "sql/h2.sql"
|
||||
}, {
|
||||
"contentPath" : "ts/index",
|
||||
"filePath" : "yudao-ui-admin-uniapp/src/api/infra/demo/index.ts"
|
||||
}, {
|
||||
"contentPath" : "vue/index",
|
||||
"filePath" : "yudao-ui-admin-uniapp/src/pages-infra/demo/index.vue"
|
||||
}, {
|
||||
"contentPath" : "vue/search-form",
|
||||
"filePath" : "yudao-ui-admin-uniapp/src/pages-infra/demo/components/search-form.vue"
|
||||
}, {
|
||||
"contentPath" : "vue/form/index",
|
||||
"filePath" : "yudao-ui-admin-uniapp/src/pages-infra/demo/form/index.vue"
|
||||
}, {
|
||||
"contentPath" : "vue/detail/index",
|
||||
"filePath" : "yudao-ui-admin-uniapp/src/pages-infra/demo/detail/index.vue"
|
||||
}, {
|
||||
"contentPath" : "vue/student-contact/form/index",
|
||||
"filePath" : "yudao-ui-admin-uniapp/src/pages-infra/demo/student-contact/form/index.vue"
|
||||
}, {
|
||||
"contentPath" : "vue/student-teacher/form/index",
|
||||
"filePath" : "yudao-ui-admin-uniapp/src/pages-infra/demo/student-teacher/form/index.vue"
|
||||
} ]
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
// TODO 待办:请将下面的错误码复制到 yudao-module-infra 模块的 ErrorCodeConstants 类中。注意,请给“TODO 补充编号”设置一个错误码编号!!!
|
||||
// ========== 学生 TODO 补充编号 ==========
|
||||
ErrorCode STUDENT_NOT_EXISTS = new ErrorCode(TODO 补充编号, "学生不存在");
|
||||
ErrorCode STUDENT_CONTACT_NOT_EXISTS = new ErrorCode(TODO 补充编号, "学生联系人不存在");
|
||||
ErrorCode STUDENT_TEACHER_NOT_EXISTS = new ErrorCode(TODO 补充编号, "学生班主任不存在");
|
||||
ErrorCode STUDENT_TEACHER_EXISTS = new ErrorCode(TODO 补充编号, "学生班主任已存在");
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
package cn.iocoder.yudao.module.infra.dal.dataobject.demo;
|
||||
|
||||
import lombok.*;
|
||||
import java.util.*;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.LocalDateTime;
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
|
||||
|
||||
/**
|
||||
* 学生联系人 DO
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@TableName("infra_student_contact")
|
||||
@KeySequence("infra_student_contact_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@ToString(callSuper = true)
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class InfraStudentContactDO extends BaseDO {
|
||||
|
||||
/**
|
||||
* 编号
|
||||
*/
|
||||
@TableId
|
||||
private Long id;
|
||||
/**
|
||||
* 学生编号
|
||||
*/
|
||||
private Long studentId;
|
||||
/**
|
||||
* 名字
|
||||
*/
|
||||
private String name;
|
||||
/**
|
||||
* 简介
|
||||
*/
|
||||
private String description;
|
||||
/**
|
||||
* 出生日期
|
||||
*/
|
||||
private LocalDateTime birthday;
|
||||
/**
|
||||
* 性别
|
||||
*
|
||||
* 枚举 {@link TODO system_user_sex 对应的类}
|
||||
*/
|
||||
private Integer sex;
|
||||
/**
|
||||
* 是否有效
|
||||
*
|
||||
* 枚举 {@link TODO infra_boolean_string 对应的类}
|
||||
*/
|
||||
private Boolean enabled;
|
||||
/**
|
||||
* 头像
|
||||
*/
|
||||
private String avatar;
|
||||
/**
|
||||
* 附件
|
||||
*/
|
||||
private String video;
|
||||
/**
|
||||
* 备注
|
||||
*/
|
||||
private String memo;
|
||||
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
package cn.iocoder.yudao.module.infra.dal.mysql.demo;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageParam;
|
||||
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
|
||||
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
|
||||
import cn.iocoder.yudao.module.infra.dal.dataobject.demo.InfraStudentContactDO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
/**
|
||||
* 学生联系人 Mapper
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Mapper
|
||||
public interface InfraStudentContactMapper extends BaseMapperX<InfraStudentContactDO> {
|
||||
|
||||
default PageResult<InfraStudentContactDO> selectPage(PageParam reqVO, Long studentId) {
|
||||
return selectPage(reqVO, new LambdaQueryWrapperX<InfraStudentContactDO>()
|
||||
.eq(InfraStudentContactDO::getStudentId, studentId)
|
||||
.orderByDesc(InfraStudentContactDO::getId));
|
||||
}
|
||||
|
||||
default int deleteByStudentId(Long studentId) {
|
||||
return delete(InfraStudentContactDO::getStudentId, studentId);
|
||||
}
|
||||
|
||||
default int deleteByStudentIds(List<Long> studentIds) {
|
||||
return deleteBatch(InfraStudentContactDO::getStudentId, studentIds);
|
||||
}
|
||||
|
||||
}
|
||||
+210
@@ -0,0 +1,210 @@
|
||||
package cn.iocoder.yudao.module.infra.controller.admin.demo;
|
||||
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
|
||||
import jakarta.validation.constraints.*;
|
||||
import jakarta.validation.*;
|
||||
import jakarta.servlet.http.*;
|
||||
import java.util.*;
|
||||
import java.io.IOException;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageParam;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
|
||||
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
|
||||
|
||||
import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
|
||||
|
||||
import cn.iocoder.yudao.framework.apilog.core.annotation.ApiAccessLog;
|
||||
import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.*;
|
||||
|
||||
import cn.iocoder.yudao.module.infra.controller.admin.demo.vo.*;
|
||||
import cn.iocoder.yudao.module.infra.dal.dataobject.demo.InfraStudentDO;
|
||||
import cn.iocoder.yudao.module.infra.dal.dataobject.demo.InfraStudentContactDO;
|
||||
import cn.iocoder.yudao.module.infra.dal.dataobject.demo.InfraStudentTeacherDO;
|
||||
import cn.iocoder.yudao.module.infra.service.demo.InfraStudentService;
|
||||
|
||||
@Tag(name = "管理后台 - 学生")
|
||||
@RestController
|
||||
@RequestMapping("/infra/student")
|
||||
@Validated
|
||||
public class InfraStudentController {
|
||||
|
||||
@Resource
|
||||
private InfraStudentService studentService;
|
||||
|
||||
@PostMapping("/create")
|
||||
@Operation(summary = "创建学生")
|
||||
@PreAuthorize("@ss.hasPermission('infra:student:create')")
|
||||
public CommonResult<Long> createStudent(@Valid @RequestBody InfraStudentSaveReqVO createReqVO) {
|
||||
return success(studentService.createStudent(createReqVO));
|
||||
}
|
||||
|
||||
@PutMapping("/update")
|
||||
@Operation(summary = "更新学生")
|
||||
@PreAuthorize("@ss.hasPermission('infra:student:update')")
|
||||
public CommonResult<Boolean> updateStudent(@Valid @RequestBody InfraStudentSaveReqVO updateReqVO) {
|
||||
studentService.updateStudent(updateReqVO);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@DeleteMapping("/delete")
|
||||
@Operation(summary = "删除学生")
|
||||
@Parameter(name = "id", description = "编号", required = true)
|
||||
@PreAuthorize("@ss.hasPermission('infra:student:delete')")
|
||||
public CommonResult<Boolean> deleteStudent(@RequestParam("id") Long id) {
|
||||
studentService.deleteStudent(id);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@DeleteMapping("/delete-list")
|
||||
@Parameter(name = "ids", description = "编号", required = true)
|
||||
@Operation(summary = "批量删除学生")
|
||||
@PreAuthorize("@ss.hasPermission('infra:student:delete')")
|
||||
public CommonResult<Boolean> deleteStudentList(@RequestParam("ids") List<Long> ids) {
|
||||
studentService.deleteStudentListByIds(ids);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@GetMapping("/get")
|
||||
@Operation(summary = "获得学生")
|
||||
@Parameter(name = "id", description = "编号", required = true, example = "1024")
|
||||
@PreAuthorize("@ss.hasPermission('infra:student:query')")
|
||||
public CommonResult<InfraStudentRespVO> getStudent(@RequestParam("id") Long id) {
|
||||
InfraStudentDO student = studentService.getStudent(id);
|
||||
return success(BeanUtils.toBean(student, InfraStudentRespVO.class));
|
||||
}
|
||||
|
||||
@GetMapping("/page")
|
||||
@Operation(summary = "获得学生分页")
|
||||
@PreAuthorize("@ss.hasPermission('infra:student:query')")
|
||||
public CommonResult<PageResult<InfraStudentRespVO>> getStudentPage(@Valid InfraStudentPageReqVO pageReqVO) {
|
||||
PageResult<InfraStudentDO> pageResult = studentService.getStudentPage(pageReqVO);
|
||||
return success(BeanUtils.toBean(pageResult, InfraStudentRespVO.class));
|
||||
}
|
||||
|
||||
@GetMapping("/export-excel")
|
||||
@Operation(summary = "导出学生 Excel")
|
||||
@PreAuthorize("@ss.hasPermission('infra:student:export')")
|
||||
@ApiAccessLog(operateType = EXPORT)
|
||||
public void exportStudentExcel(@Valid InfraStudentPageReqVO pageReqVO,
|
||||
HttpServletResponse response) throws IOException {
|
||||
pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
|
||||
List<InfraStudentDO> list = studentService.getStudentPage(pageReqVO).getList();
|
||||
// 导出 Excel
|
||||
ExcelUtils.write(response, "学生.xls", "数据", InfraStudentRespVO.class,
|
||||
BeanUtils.toBean(list, InfraStudentRespVO.class));
|
||||
}
|
||||
|
||||
// ==================== 子表(学生联系人) ====================
|
||||
|
||||
@GetMapping("/student-contact/page")
|
||||
@Operation(summary = "获得学生联系人分页")
|
||||
@Parameter(name = "studentId", description = "学生编号")
|
||||
@PreAuthorize("@ss.hasPermission('infra:student:query')")
|
||||
public CommonResult<PageResult<InfraStudentContactDO>> getStudentContactPage(PageParam pageReqVO,
|
||||
@RequestParam("studentId") Long studentId) {
|
||||
return success(studentService.getStudentContactPage(pageReqVO, studentId));
|
||||
}
|
||||
|
||||
@PostMapping("/student-contact/create")
|
||||
@Operation(summary = "创建学生联系人")
|
||||
@PreAuthorize("@ss.hasPermission('infra:student:create')")
|
||||
public CommonResult<Long> createStudentContact(@Valid @RequestBody InfraStudentContactDO studentContact) {
|
||||
return success(studentService.createStudentContact(studentContact));
|
||||
}
|
||||
|
||||
@PutMapping("/student-contact/update")
|
||||
@Operation(summary = "更新学生联系人")
|
||||
@PreAuthorize("@ss.hasPermission('infra:student:update')")
|
||||
public CommonResult<Boolean> updateStudentContact(@Valid @RequestBody InfraStudentContactDO studentContact) {
|
||||
studentService.updateStudentContact(studentContact);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@DeleteMapping("/student-contact/delete")
|
||||
@Parameter(name = "id", description = "编号", required = true)
|
||||
@Operation(summary = "删除学生联系人")
|
||||
@PreAuthorize("@ss.hasPermission('infra:student:delete')")
|
||||
public CommonResult<Boolean> deleteStudentContact(@RequestParam("id") Long id) {
|
||||
studentService.deleteStudentContact(id);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@DeleteMapping("/student-contact/delete-list")
|
||||
@Parameter(name = "ids", description = "编号", required = true)
|
||||
@Operation(summary = "批量删除学生联系人")
|
||||
@PreAuthorize("@ss.hasPermission('infra:student:delete')")
|
||||
public CommonResult<Boolean> deleteStudentContactList(@RequestParam("ids") List<Long> ids) {
|
||||
studentService.deleteStudentContactListByIds(ids);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@GetMapping("/student-contact/get")
|
||||
@Operation(summary = "获得学生联系人")
|
||||
@Parameter(name = "id", description = "编号", required = true)
|
||||
@PreAuthorize("@ss.hasPermission('infra:student:query')")
|
||||
public CommonResult<InfraStudentContactDO> getStudentContact(@RequestParam("id") Long id) {
|
||||
return success(studentService.getStudentContact(id));
|
||||
}
|
||||
|
||||
// ==================== 子表(学生班主任) ====================
|
||||
|
||||
@GetMapping("/student-teacher/page")
|
||||
@Operation(summary = "获得学生班主任分页")
|
||||
@Parameter(name = "studentId", description = "学生编号")
|
||||
@PreAuthorize("@ss.hasPermission('infra:student:query')")
|
||||
public CommonResult<PageResult<InfraStudentTeacherDO>> getStudentTeacherPage(PageParam pageReqVO,
|
||||
@RequestParam("studentId") Long studentId) {
|
||||
return success(studentService.getStudentTeacherPage(pageReqVO, studentId));
|
||||
}
|
||||
|
||||
@PostMapping("/student-teacher/create")
|
||||
@Operation(summary = "创建学生班主任")
|
||||
@PreAuthorize("@ss.hasPermission('infra:student:create')")
|
||||
public CommonResult<Long> createStudentTeacher(@Valid @RequestBody InfraStudentTeacherDO studentTeacher) {
|
||||
return success(studentService.createStudentTeacher(studentTeacher));
|
||||
}
|
||||
|
||||
@PutMapping("/student-teacher/update")
|
||||
@Operation(summary = "更新学生班主任")
|
||||
@PreAuthorize("@ss.hasPermission('infra:student:update')")
|
||||
public CommonResult<Boolean> updateStudentTeacher(@Valid @RequestBody InfraStudentTeacherDO studentTeacher) {
|
||||
studentService.updateStudentTeacher(studentTeacher);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@DeleteMapping("/student-teacher/delete")
|
||||
@Parameter(name = "id", description = "编号", required = true)
|
||||
@Operation(summary = "删除学生班主任")
|
||||
@PreAuthorize("@ss.hasPermission('infra:student:delete')")
|
||||
public CommonResult<Boolean> deleteStudentTeacher(@RequestParam("id") Long id) {
|
||||
studentService.deleteStudentTeacher(id);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@DeleteMapping("/student-teacher/delete-list")
|
||||
@Parameter(name = "ids", description = "编号", required = true)
|
||||
@Operation(summary = "批量删除学生班主任")
|
||||
@PreAuthorize("@ss.hasPermission('infra:student:delete')")
|
||||
public CommonResult<Boolean> deleteStudentTeacherList(@RequestParam("ids") List<Long> ids) {
|
||||
studentService.deleteStudentTeacherListByIds(ids);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@GetMapping("/student-teacher/get")
|
||||
@Operation(summary = "获得学生班主任")
|
||||
@Parameter(name = "id", description = "编号", required = true)
|
||||
@PreAuthorize("@ss.hasPermission('infra:student:query')")
|
||||
public CommonResult<InfraStudentTeacherDO> getStudentTeacher(@RequestParam("id") Long id) {
|
||||
return success(studentService.getStudentTeacher(id));
|
||||
}
|
||||
|
||||
}
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
package cn.iocoder.yudao.module.infra.dal.dataobject.demo;
|
||||
|
||||
import lombok.*;
|
||||
import java.util.*;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.LocalDateTime;
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
|
||||
|
||||
/**
|
||||
* 学生 DO
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@TableName("infra_student")
|
||||
@KeySequence("infra_student_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@ToString(callSuper = true)
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class InfraStudentDO extends BaseDO {
|
||||
|
||||
/**
|
||||
* 编号
|
||||
*/
|
||||
@TableId
|
||||
private Long id;
|
||||
/**
|
||||
* 名字
|
||||
*/
|
||||
private String name;
|
||||
/**
|
||||
* 简介
|
||||
*/
|
||||
private String description;
|
||||
/**
|
||||
* 出生日期
|
||||
*/
|
||||
private LocalDateTime birthday;
|
||||
/**
|
||||
* 性别
|
||||
*
|
||||
* 枚举 {@link TODO system_user_sex 对应的类}
|
||||
*/
|
||||
private Integer sex;
|
||||
/**
|
||||
* 是否有效
|
||||
*
|
||||
* 枚举 {@link TODO infra_boolean_string 对应的类}
|
||||
*/
|
||||
private Boolean enabled;
|
||||
/**
|
||||
* 头像
|
||||
*/
|
||||
private String avatar;
|
||||
/**
|
||||
* 附件
|
||||
*/
|
||||
private String video;
|
||||
/**
|
||||
* 备注
|
||||
*/
|
||||
private String memo;
|
||||
|
||||
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
package cn.iocoder.yudao.module.infra.dal.mysql.demo;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
|
||||
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
|
||||
import cn.iocoder.yudao.module.infra.dal.dataobject.demo.InfraStudentDO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import cn.iocoder.yudao.module.infra.controller.admin.demo.vo.*;
|
||||
|
||||
/**
|
||||
* 学生 Mapper
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Mapper
|
||||
public interface InfraStudentMapper extends BaseMapperX<InfraStudentDO> {
|
||||
|
||||
default PageResult<InfraStudentDO> selectPage(InfraStudentPageReqVO reqVO) {
|
||||
return selectPage(reqVO, new LambdaQueryWrapperX<InfraStudentDO>()
|
||||
.likeIfPresent(InfraStudentDO::getName, reqVO.getName())
|
||||
.eqIfPresent(InfraStudentDO::getBirthday, reqVO.getBirthday())
|
||||
.eqIfPresent(InfraStudentDO::getSex, reqVO.getSex())
|
||||
.eqIfPresent(InfraStudentDO::getEnabled, reqVO.getEnabled())
|
||||
.betweenIfPresent(InfraStudentDO::getCreateTime, reqVO.getCreateTime())
|
||||
.orderByDesc(InfraStudentDO::getId));
|
||||
}
|
||||
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
package cn.iocoder.yudao.module.infra.controller.admin.demo.vo;
|
||||
|
||||
import lombok.*;
|
||||
import java.util.*;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageParam;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
|
||||
|
||||
@Schema(description = "管理后台 - 学生分页 Request VO")
|
||||
@Data
|
||||
public class InfraStudentPageReqVO extends PageParam {
|
||||
|
||||
@Schema(description = "名字", example = "芋头")
|
||||
private String name;
|
||||
|
||||
@Schema(description = "出生日期")
|
||||
private LocalDateTime birthday;
|
||||
|
||||
@Schema(description = "性别", example = "1")
|
||||
private Integer sex;
|
||||
|
||||
@Schema(description = "是否有效", example = "true")
|
||||
private Boolean enabled;
|
||||
|
||||
@Schema(description = "创建时间")
|
||||
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
|
||||
private LocalDateTime[] createTime;
|
||||
|
||||
}
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
package cn.iocoder.yudao.module.infra.controller.admin.demo.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.*;
|
||||
import java.util.*;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
import java.time.LocalDateTime;
|
||||
import cn.idev.excel.annotation.*;
|
||||
import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
|
||||
import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
|
||||
|
||||
@Schema(description = "管理后台 - 学生 Response VO")
|
||||
@Data
|
||||
@ExcelIgnoreUnannotated
|
||||
public class InfraStudentRespVO {
|
||||
|
||||
@Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
|
||||
@ExcelProperty("编号")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "名字", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋头")
|
||||
@ExcelProperty("名字")
|
||||
private String name;
|
||||
|
||||
@Schema(description = "简介", requiredMode = Schema.RequiredMode.REQUIRED, example = "我是介绍")
|
||||
@ExcelProperty("简介")
|
||||
private String description;
|
||||
|
||||
@Schema(description = "出生日期", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@ExcelProperty("出生日期")
|
||||
private LocalDateTime birthday;
|
||||
|
||||
@Schema(description = "性别", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
@ExcelProperty(value = "性别", converter = DictConvert.class)
|
||||
@DictFormat("system_user_sex") // TODO 代码优化:建议设置到对应的 DictTypeConstants 枚举类中
|
||||
private Integer sex;
|
||||
|
||||
@Schema(description = "是否有效", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
|
||||
@ExcelProperty(value = "是否有效", converter = DictConvert.class)
|
||||
@DictFormat("infra_boolean_string") // TODO 代码优化:建议设置到对应的 DictTypeConstants 枚举类中
|
||||
private Boolean enabled;
|
||||
|
||||
@Schema(description = "头像", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn/1.png")
|
||||
@ExcelProperty("头像")
|
||||
private String avatar;
|
||||
|
||||
@Schema(description = "附件", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn/1.mp4")
|
||||
@ExcelProperty("附件")
|
||||
private String video;
|
||||
|
||||
@Schema(description = "备注", requiredMode = Schema.RequiredMode.REQUIRED, example = "我是备注")
|
||||
@ExcelProperty("备注")
|
||||
private String memo;
|
||||
|
||||
@Schema(description = "创建时间")
|
||||
@ExcelProperty("创建时间")
|
||||
private LocalDateTime createTime;
|
||||
|
||||
}
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
package cn.iocoder.yudao.module.infra.controller.admin.demo.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.*;
|
||||
import java.util.*;
|
||||
import jakarta.validation.constraints.*;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
import java.time.LocalDateTime;
|
||||
import cn.iocoder.yudao.module.infra.dal.dataobject.demo.InfraStudentContactDO;
|
||||
import cn.iocoder.yudao.module.infra.dal.dataobject.demo.InfraStudentTeacherDO;
|
||||
|
||||
@Schema(description = "管理后台 - 学生新增/修改 Request VO")
|
||||
@Data
|
||||
public class InfraStudentSaveReqVO {
|
||||
|
||||
@Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "名字", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋头")
|
||||
@NotEmpty(message = "名字不能为空")
|
||||
private String name;
|
||||
|
||||
@Schema(description = "简介", requiredMode = Schema.RequiredMode.REQUIRED, example = "我是介绍")
|
||||
@NotEmpty(message = "简介不能为空")
|
||||
private String description;
|
||||
|
||||
@Schema(description = "出生日期", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@NotNull(message = "出生日期不能为空")
|
||||
private LocalDateTime birthday;
|
||||
|
||||
@Schema(description = "性别", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
@NotNull(message = "性别不能为空")
|
||||
private Integer sex;
|
||||
|
||||
@Schema(description = "是否有效", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
|
||||
@NotNull(message = "是否有效不能为空")
|
||||
private Boolean enabled;
|
||||
|
||||
@Schema(description = "头像", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn/1.png")
|
||||
@NotEmpty(message = "头像不能为空")
|
||||
private String avatar;
|
||||
|
||||
@Schema(description = "附件", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn/1.mp4")
|
||||
@NotEmpty(message = "附件不能为空")
|
||||
private String video;
|
||||
|
||||
@Schema(description = "备注", requiredMode = Schema.RequiredMode.REQUIRED, example = "我是备注")
|
||||
@NotEmpty(message = "备注不能为空")
|
||||
private String memo;
|
||||
|
||||
}
|
||||
+160
@@ -0,0 +1,160 @@
|
||||
package cn.iocoder.yudao.module.infra.service.demo;
|
||||
|
||||
import java.util.*;
|
||||
import jakarta.validation.*;
|
||||
import cn.iocoder.yudao.module.infra.controller.admin.demo.vo.*;
|
||||
import cn.iocoder.yudao.module.infra.dal.dataobject.demo.InfraStudentDO;
|
||||
import cn.iocoder.yudao.module.infra.dal.dataobject.demo.InfraStudentContactDO;
|
||||
import cn.iocoder.yudao.module.infra.dal.dataobject.demo.InfraStudentTeacherDO;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageParam;
|
||||
|
||||
/**
|
||||
* 学生 Service 接口
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
public interface InfraStudentService {
|
||||
|
||||
/**
|
||||
* 创建学生
|
||||
*
|
||||
* @param createReqVO 创建信息
|
||||
* @return 编号
|
||||
*/
|
||||
Long createStudent(@Valid InfraStudentSaveReqVO createReqVO);
|
||||
|
||||
/**
|
||||
* 更新学生
|
||||
*
|
||||
* @param updateReqVO 更新信息
|
||||
*/
|
||||
void updateStudent(@Valid InfraStudentSaveReqVO updateReqVO);
|
||||
|
||||
/**
|
||||
* 删除学生
|
||||
*
|
||||
* @param id 编号
|
||||
*/
|
||||
void deleteStudent(Long id);
|
||||
|
||||
/**
|
||||
* 批量删除学生
|
||||
*
|
||||
* @param ids 编号
|
||||
*/
|
||||
void deleteStudentListByIds(List<Long> ids);
|
||||
|
||||
/**
|
||||
* 获得学生
|
||||
*
|
||||
* @param id 编号
|
||||
* @return 学生
|
||||
*/
|
||||
InfraStudentDO getStudent(Long id);
|
||||
|
||||
/**
|
||||
* 获得学生分页
|
||||
*
|
||||
* @param pageReqVO 分页查询
|
||||
* @return 学生分页
|
||||
*/
|
||||
PageResult<InfraStudentDO> getStudentPage(InfraStudentPageReqVO pageReqVO);
|
||||
|
||||
// ==================== 子表(学生联系人) ====================
|
||||
|
||||
/**
|
||||
* 获得学生联系人分页
|
||||
*
|
||||
* @param pageReqVO 分页查询
|
||||
* @param studentId 学生编号
|
||||
* @return 学生联系人分页
|
||||
*/
|
||||
PageResult<InfraStudentContactDO> getStudentContactPage(PageParam pageReqVO, Long studentId);
|
||||
|
||||
/**
|
||||
* 创建学生联系人
|
||||
*
|
||||
* @param studentContact 创建信息
|
||||
* @return 编号
|
||||
*/
|
||||
Long createStudentContact(@Valid InfraStudentContactDO studentContact);
|
||||
|
||||
/**
|
||||
* 更新学生联系人
|
||||
*
|
||||
* @param studentContact 更新信息
|
||||
*/
|
||||
void updateStudentContact(@Valid InfraStudentContactDO studentContact);
|
||||
|
||||
/**
|
||||
* 删除学生联系人
|
||||
*
|
||||
* @param id 编号
|
||||
*/
|
||||
void deleteStudentContact(Long id);
|
||||
|
||||
/**
|
||||
* 批量删除学生联系人
|
||||
*
|
||||
* @param ids 编号
|
||||
*/
|
||||
void deleteStudentContactListByIds(List<Long> ids);
|
||||
|
||||
/**
|
||||
* 获得学生联系人
|
||||
*
|
||||
* @param id 编号
|
||||
* @return 学生联系人
|
||||
*/
|
||||
InfraStudentContactDO getStudentContact(Long id);
|
||||
|
||||
// ==================== 子表(学生班主任) ====================
|
||||
|
||||
/**
|
||||
* 获得学生班主任分页
|
||||
*
|
||||
* @param pageReqVO 分页查询
|
||||
* @param studentId 学生编号
|
||||
* @return 学生班主任分页
|
||||
*/
|
||||
PageResult<InfraStudentTeacherDO> getStudentTeacherPage(PageParam pageReqVO, Long studentId);
|
||||
|
||||
/**
|
||||
* 创建学生班主任
|
||||
*
|
||||
* @param studentTeacher 创建信息
|
||||
* @return 编号
|
||||
*/
|
||||
Long createStudentTeacher(@Valid InfraStudentTeacherDO studentTeacher);
|
||||
|
||||
/**
|
||||
* 更新学生班主任
|
||||
*
|
||||
* @param studentTeacher 更新信息
|
||||
*/
|
||||
void updateStudentTeacher(@Valid InfraStudentTeacherDO studentTeacher);
|
||||
|
||||
/**
|
||||
* 删除学生班主任
|
||||
*
|
||||
* @param id 编号
|
||||
*/
|
||||
void deleteStudentTeacher(Long id);
|
||||
|
||||
/**
|
||||
* 批量删除学生班主任
|
||||
*
|
||||
* @param ids 编号
|
||||
*/
|
||||
void deleteStudentTeacherListByIds(List<Long> ids);
|
||||
|
||||
/**
|
||||
* 获得学生班主任
|
||||
*
|
||||
* @param id 编号
|
||||
* @return 学生班主任
|
||||
*/
|
||||
InfraStudentTeacherDO getStudentTeacher(Long id);
|
||||
|
||||
}
|
||||
+216
@@ -0,0 +1,216 @@
|
||||
package cn.iocoder.yudao.module.infra.service.demo;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import org.springframework.stereotype.Service;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.*;
|
||||
import cn.iocoder.yudao.module.infra.controller.admin.demo.vo.*;
|
||||
import cn.iocoder.yudao.module.infra.dal.dataobject.demo.InfraStudentDO;
|
||||
import cn.iocoder.yudao.module.infra.dal.dataobject.demo.InfraStudentContactDO;
|
||||
import cn.iocoder.yudao.module.infra.dal.dataobject.demo.InfraStudentTeacherDO;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageParam;
|
||||
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
|
||||
|
||||
import cn.iocoder.yudao.module.infra.dal.mysql.demo.InfraStudentMapper;
|
||||
import cn.iocoder.yudao.module.infra.dal.mysql.demo.InfraStudentContactMapper;
|
||||
import cn.iocoder.yudao.module.infra.dal.mysql.demo.InfraStudentTeacherMapper;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
|
||||
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
|
||||
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.diffList;
|
||||
import static cn.iocoder.yudao.module.infra.enums.ErrorCodeConstants.*;
|
||||
|
||||
/**
|
||||
* 学生 Service 实现类
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Service
|
||||
@Validated
|
||||
public class InfraStudentServiceImpl implements InfraStudentService {
|
||||
|
||||
@Resource
|
||||
private InfraStudentMapper studentMapper;
|
||||
@Resource
|
||||
private InfraStudentContactMapper studentContactMapper;
|
||||
@Resource
|
||||
private InfraStudentTeacherMapper studentTeacherMapper;
|
||||
|
||||
@Override
|
||||
public Long createStudent(InfraStudentSaveReqVO createReqVO) {
|
||||
// 插入
|
||||
InfraStudentDO student = BeanUtils.toBean(createReqVO, InfraStudentDO.class);
|
||||
studentMapper.insert(student);
|
||||
|
||||
// 返回
|
||||
return student.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateStudent(InfraStudentSaveReqVO updateReqVO) {
|
||||
// 校验存在
|
||||
validateStudentExists(updateReqVO.getId());
|
||||
// 更新
|
||||
InfraStudentDO updateObj = BeanUtils.toBean(updateReqVO, InfraStudentDO.class);
|
||||
studentMapper.updateById(updateObj);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void deleteStudent(Long id) {
|
||||
// 校验存在
|
||||
validateStudentExists(id);
|
||||
// 删除
|
||||
studentMapper.deleteById(id);
|
||||
|
||||
// 删除子表
|
||||
deleteStudentContactByStudentId(id);
|
||||
deleteStudentTeacherByStudentId(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void deleteStudentListByIds(List<Long> ids) {
|
||||
// 删除
|
||||
studentMapper.deleteByIds(ids);
|
||||
|
||||
// 删除子表
|
||||
deleteStudentContactByStudentIds(ids);
|
||||
deleteStudentTeacherByStudentIds(ids);
|
||||
}
|
||||
|
||||
|
||||
private void validateStudentExists(Long id) {
|
||||
if (studentMapper.selectById(id) == null) {
|
||||
throw exception(STUDENT_NOT_EXISTS);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public InfraStudentDO getStudent(Long id) {
|
||||
return studentMapper.selectById(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PageResult<InfraStudentDO> getStudentPage(InfraStudentPageReqVO pageReqVO) {
|
||||
return studentMapper.selectPage(pageReqVO);
|
||||
}
|
||||
|
||||
// ==================== 子表(学生联系人) ====================
|
||||
|
||||
@Override
|
||||
public PageResult<InfraStudentContactDO> getStudentContactPage(PageParam pageReqVO, Long studentId) {
|
||||
return studentContactMapper.selectPage(pageReqVO, studentId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long createStudentContact(InfraStudentContactDO studentContact) {
|
||||
studentContact.clean(); // 清理掉创建、更新时间等相关属性值
|
||||
studentContactMapper.insert(studentContact);
|
||||
return studentContact.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateStudentContact(InfraStudentContactDO studentContact) {
|
||||
// 校验存在
|
||||
validateStudentContactExists(studentContact.getId());
|
||||
// 更新
|
||||
studentContact.clean(); // 解决更新情况下:updateTime 不更新
|
||||
studentContactMapper.updateById(studentContact);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteStudentContact(Long id) {
|
||||
// 删除
|
||||
studentContactMapper.deleteById(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteStudentContactListByIds(List<Long> ids) {
|
||||
// 删除
|
||||
studentContactMapper.deleteByIds(ids);
|
||||
}
|
||||
|
||||
@Override
|
||||
public InfraStudentContactDO getStudentContact(Long id) {
|
||||
return studentContactMapper.selectById(id);
|
||||
}
|
||||
|
||||
private void validateStudentContactExists(Long id) {
|
||||
if (studentContactMapper.selectById(id) == null) {
|
||||
throw exception(STUDENT_CONTACT_NOT_EXISTS);
|
||||
}
|
||||
}
|
||||
|
||||
private void deleteStudentContactByStudentId(Long studentId) {
|
||||
studentContactMapper.deleteByStudentId(studentId);
|
||||
}
|
||||
|
||||
private void deleteStudentContactByStudentIds(List<Long> studentIds) {
|
||||
studentContactMapper.deleteByStudentIds(studentIds);
|
||||
}
|
||||
|
||||
// ==================== 子表(学生班主任) ====================
|
||||
|
||||
@Override
|
||||
public PageResult<InfraStudentTeacherDO> getStudentTeacherPage(PageParam pageReqVO, Long studentId) {
|
||||
return studentTeacherMapper.selectPage(pageReqVO, studentId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long createStudentTeacher(InfraStudentTeacherDO studentTeacher) {
|
||||
// 校验是否已经存在
|
||||
if (studentTeacherMapper.selectByStudentId(studentTeacher.getStudentId()) != null) {
|
||||
throw exception(STUDENT_TEACHER_EXISTS);
|
||||
}
|
||||
// 插入
|
||||
studentTeacher.clean(); // 清理掉创建、更新时间等相关属性值
|
||||
studentTeacherMapper.insert(studentTeacher);
|
||||
return studentTeacher.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateStudentTeacher(InfraStudentTeacherDO studentTeacher) {
|
||||
// 校验存在
|
||||
validateStudentTeacherExists(studentTeacher.getId());
|
||||
// 更新
|
||||
studentTeacher.clean(); // 解决更新情况下:updateTime 不更新
|
||||
studentTeacherMapper.updateById(studentTeacher);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteStudentTeacher(Long id) {
|
||||
// 删除
|
||||
studentTeacherMapper.deleteById(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteStudentTeacherListByIds(List<Long> ids) {
|
||||
// 删除
|
||||
studentTeacherMapper.deleteByIds(ids);
|
||||
}
|
||||
|
||||
@Override
|
||||
public InfraStudentTeacherDO getStudentTeacher(Long id) {
|
||||
return studentTeacherMapper.selectById(id);
|
||||
}
|
||||
|
||||
private void validateStudentTeacherExists(Long id) {
|
||||
if (studentTeacherMapper.selectById(id) == null) {
|
||||
throw exception(STUDENT_TEACHER_NOT_EXISTS);
|
||||
}
|
||||
}
|
||||
|
||||
private void deleteStudentTeacherByStudentId(Long studentId) {
|
||||
studentTeacherMapper.deleteByStudentId(studentId);
|
||||
}
|
||||
|
||||
private void deleteStudentTeacherByStudentIds(List<Long> studentIds) {
|
||||
studentTeacherMapper.deleteByStudentIds(studentIds);
|
||||
}
|
||||
|
||||
}
|
||||
+145
@@ -0,0 +1,145 @@
|
||||
package cn.iocoder.yudao.module.infra.service.demo;
|
||||
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
|
||||
import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
|
||||
|
||||
import cn.iocoder.yudao.module.infra.controller.admin.demo.vo.*;
|
||||
import cn.iocoder.yudao.module.infra.dal.dataobject.demo.InfraStudentDO;
|
||||
import cn.iocoder.yudao.module.infra.dal.mysql.demo.InfraStudentMapper;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import java.util.*;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
import static cn.hutool.core.util.RandomUtil.*;
|
||||
import static cn.iocoder.yudao.module.infra.enums.ErrorCodeConstants.*;
|
||||
import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.*;
|
||||
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*;
|
||||
import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.*;
|
||||
import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.*;
|
||||
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.*;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
/**
|
||||
* {@link InfraStudentServiceImpl} 的单元测试类
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Import(InfraStudentServiceImpl.class)
|
||||
public class InfraStudentServiceImplTest extends BaseDbUnitTest {
|
||||
|
||||
@Resource
|
||||
private InfraStudentServiceImpl studentService;
|
||||
|
||||
@Resource
|
||||
private InfraStudentMapper studentMapper;
|
||||
|
||||
@Test
|
||||
public void testCreateStudent_success() {
|
||||
// 准备参数
|
||||
InfraStudentSaveReqVO createReqVO = randomPojo(InfraStudentSaveReqVO.class).setId(null);
|
||||
|
||||
// 调用
|
||||
Long studentId = studentService.createStudent(createReqVO);
|
||||
// 断言
|
||||
assertNotNull(studentId);
|
||||
// 校验记录的属性是否正确
|
||||
InfraStudentDO student = studentMapper.selectById(studentId);
|
||||
assertPojoEquals(createReqVO, student, "id");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateStudent_success() {
|
||||
// mock 数据
|
||||
InfraStudentDO dbStudent = randomPojo(InfraStudentDO.class);
|
||||
studentMapper.insert(dbStudent);// @Sql: 先插入出一条存在的数据
|
||||
// 准备参数
|
||||
InfraStudentSaveReqVO updateReqVO = randomPojo(InfraStudentSaveReqVO.class, o -> {
|
||||
o.setId(dbStudent.getId()); // 设置更新的 ID
|
||||
});
|
||||
|
||||
// 调用
|
||||
studentService.updateStudent(updateReqVO);
|
||||
// 校验是否更新正确
|
||||
InfraStudentDO student = studentMapper.selectById(updateReqVO.getId()); // 获取最新的
|
||||
assertPojoEquals(updateReqVO, student);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateStudent_notExists() {
|
||||
// 准备参数
|
||||
InfraStudentSaveReqVO updateReqVO = randomPojo(InfraStudentSaveReqVO.class);
|
||||
|
||||
// 调用, 并断言异常
|
||||
assertServiceException(() -> studentService.updateStudent(updateReqVO), STUDENT_NOT_EXISTS);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeleteStudent_success() {
|
||||
// mock 数据
|
||||
InfraStudentDO dbStudent = randomPojo(InfraStudentDO.class);
|
||||
studentMapper.insert(dbStudent);// @Sql: 先插入出一条存在的数据
|
||||
// 准备参数
|
||||
Long id = dbStudent.getId();
|
||||
|
||||
// 调用
|
||||
studentService.deleteStudent(id);
|
||||
// 校验数据不存在了
|
||||
assertNull(studentMapper.selectById(id));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeleteStudent_notExists() {
|
||||
// 准备参数
|
||||
Long id = randomLongId();
|
||||
|
||||
// 调用, 并断言异常
|
||||
assertServiceException(() -> studentService.deleteStudent(id), STUDENT_NOT_EXISTS);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Disabled // TODO 请修改 null 为需要的值,然后删除 @Disabled 注解
|
||||
public void testGetStudentPage() {
|
||||
// mock 数据
|
||||
InfraStudentDO dbStudent = randomPojo(InfraStudentDO.class, o -> { // 等会查询到
|
||||
o.setName(null);
|
||||
o.setBirthday(null);
|
||||
o.setSex(null);
|
||||
o.setEnabled(null);
|
||||
o.setCreateTime(null);
|
||||
});
|
||||
studentMapper.insert(dbStudent);
|
||||
// 测试 name 不匹配
|
||||
studentMapper.insert(cloneIgnoreId(dbStudent, o -> o.setName(null)));
|
||||
// 测试 birthday 不匹配
|
||||
studentMapper.insert(cloneIgnoreId(dbStudent, o -> o.setBirthday(null)));
|
||||
// 测试 sex 不匹配
|
||||
studentMapper.insert(cloneIgnoreId(dbStudent, o -> o.setSex(null)));
|
||||
// 测试 enabled 不匹配
|
||||
studentMapper.insert(cloneIgnoreId(dbStudent, o -> o.setEnabled(null)));
|
||||
// 测试 createTime 不匹配
|
||||
studentMapper.insert(cloneIgnoreId(dbStudent, o -> o.setCreateTime(null)));
|
||||
// 准备参数
|
||||
InfraStudentPageReqVO reqVO = new InfraStudentPageReqVO();
|
||||
reqVO.setName(null);
|
||||
reqVO.setBirthday(null);
|
||||
reqVO.setSex(null);
|
||||
reqVO.setEnabled(null);
|
||||
reqVO.setCreateTime(buildBetweenTime(2023, 2, 1, 2023, 2, 28));
|
||||
|
||||
// 调用
|
||||
PageResult<InfraStudentDO> pageResult = studentService.getStudentPage(reqVO);
|
||||
// 断言
|
||||
assertEquals(1, pageResult.getTotal());
|
||||
assertEquals(1, pageResult.getList().size());
|
||||
assertPojoEquals(dbStudent, pageResult.getList().get(0));
|
||||
}
|
||||
|
||||
}
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
package cn.iocoder.yudao.module.infra.dal.dataobject.demo;
|
||||
|
||||
import lombok.*;
|
||||
import java.util.*;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.LocalDateTime;
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
|
||||
|
||||
/**
|
||||
* 学生班主任 DO
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@TableName("infra_student_teacher")
|
||||
@KeySequence("infra_student_teacher_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@ToString(callSuper = true)
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class InfraStudentTeacherDO extends BaseDO {
|
||||
|
||||
/**
|
||||
* 编号
|
||||
*/
|
||||
@TableId
|
||||
private Long id;
|
||||
/**
|
||||
* 学生编号
|
||||
*/
|
||||
private Long studentId;
|
||||
/**
|
||||
* 名字
|
||||
*/
|
||||
private String name;
|
||||
/**
|
||||
* 简介
|
||||
*/
|
||||
private String description;
|
||||
/**
|
||||
* 出生日期
|
||||
*/
|
||||
private LocalDateTime birthday;
|
||||
/**
|
||||
* 性别
|
||||
*
|
||||
* 枚举 {@link TODO system_user_sex 对应的类}
|
||||
*/
|
||||
private Integer sex;
|
||||
/**
|
||||
* 是否有效
|
||||
*
|
||||
* 枚举 {@link TODO infra_boolean_string 对应的类}
|
||||
*/
|
||||
private Boolean enabled;
|
||||
/**
|
||||
* 头像
|
||||
*/
|
||||
private String avatar;
|
||||
/**
|
||||
* 附件
|
||||
*/
|
||||
private String video;
|
||||
/**
|
||||
* 备注
|
||||
*/
|
||||
private String memo;
|
||||
|
||||
}
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
package cn.iocoder.yudao.module.infra.dal.mysql.demo;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageParam;
|
||||
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
|
||||
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
|
||||
import cn.iocoder.yudao.module.infra.dal.dataobject.demo.InfraStudentTeacherDO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
/**
|
||||
* 学生班主任 Mapper
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Mapper
|
||||
public interface InfraStudentTeacherMapper extends BaseMapperX<InfraStudentTeacherDO> {
|
||||
|
||||
default PageResult<InfraStudentTeacherDO> selectPage(PageParam reqVO, Long studentId) {
|
||||
return selectPage(reqVO, new LambdaQueryWrapperX<InfraStudentTeacherDO>()
|
||||
.eq(InfraStudentTeacherDO::getStudentId, studentId)
|
||||
.orderByDesc(InfraStudentTeacherDO::getId));
|
||||
}
|
||||
default InfraStudentTeacherDO selectByStudentId(Long studentId) {
|
||||
return selectOne(InfraStudentTeacherDO::getStudentId, studentId);
|
||||
}
|
||||
|
||||
default int deleteByStudentId(Long studentId) {
|
||||
return delete(InfraStudentTeacherDO::getStudentId, studentId);
|
||||
}
|
||||
|
||||
default int deleteByStudentIds(List<Long> studentIds) {
|
||||
return deleteBatch(InfraStudentTeacherDO::getStudentId, studentIds);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
-- 将该建表 SQL 语句,添加到 yudao-module-infra-biz 模块的 test/resources/sql/create_tables.sql 文件里
|
||||
CREATE TABLE IF NOT EXISTS "infra_student" (
|
||||
"id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
|
||||
"name" varchar NOT NULL,
|
||||
"description" varchar NOT NULL,
|
||||
"birthday" varchar NOT NULL,
|
||||
"sex" int NOT NULL,
|
||||
"enabled" bit NOT NULL,
|
||||
"avatar" varchar NOT NULL,
|
||||
"video" varchar NOT NULL,
|
||||
"memo" varchar NOT NULL,
|
||||
"create_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY ("id")
|
||||
) COMMENT '学生表';
|
||||
|
||||
-- 将该删表 SQL 语句,添加到 yudao-module-infra-biz 模块的 test/resources/sql/clean.sql 文件里
|
||||
DELETE FROM "infra_student";
|
||||
@@ -0,0 +1,54 @@
|
||||
-- 菜单 SQL
|
||||
INSERT INTO system_menu(
|
||||
name, permission, type, sort, parent_id,
|
||||
path, icon, component, status, component_name
|
||||
)
|
||||
VALUES (
|
||||
'学生管理', '', 2, 0, 888,
|
||||
'student', '', 'infra/demo/index', 0, 'InfraStudent'
|
||||
);
|
||||
|
||||
-- 按钮父菜单ID
|
||||
SELECT @parentId := LAST_INSERT_ID();
|
||||
|
||||
-- 按钮 SQL
|
||||
INSERT INTO system_menu(
|
||||
name, permission, type, sort, parent_id,
|
||||
path, icon, component, status
|
||||
)
|
||||
VALUES (
|
||||
'学生查询', 'infra:student:query', 3, 1, @parentId,
|
||||
'', '', '', 0
|
||||
);
|
||||
INSERT INTO system_menu(
|
||||
name, permission, type, sort, parent_id,
|
||||
path, icon, component, status
|
||||
)
|
||||
VALUES (
|
||||
'学生创建', 'infra:student:create', 3, 2, @parentId,
|
||||
'', '', '', 0
|
||||
);
|
||||
INSERT INTO system_menu(
|
||||
name, permission, type, sort, parent_id,
|
||||
path, icon, component, status
|
||||
)
|
||||
VALUES (
|
||||
'学生更新', 'infra:student:update', 3, 3, @parentId,
|
||||
'', '', '', 0
|
||||
);
|
||||
INSERT INTO system_menu(
|
||||
name, permission, type, sort, parent_id,
|
||||
path, icon, component, status
|
||||
)
|
||||
VALUES (
|
||||
'学生删除', 'infra:student:delete', 3, 4, @parentId,
|
||||
'', '', '', 0
|
||||
);
|
||||
INSERT INTO system_menu(
|
||||
name, permission, type, sort, parent_id,
|
||||
path, icon, component, status
|
||||
)
|
||||
VALUES (
|
||||
'学生导出', 'infra:student:export', 3, 5, @parentId,
|
||||
'', '', '', 0
|
||||
);
|
||||
@@ -0,0 +1,125 @@
|
||||
import type { PageParam, PageResult } from '@/http/types'
|
||||
import { http } from '@/http/http'
|
||||
|
||||
/** 学生联系人信息 */
|
||||
export interface StudentContact {
|
||||
id?: number
|
||||
studentId: number
|
||||
name: string
|
||||
description: string
|
||||
birthday: number
|
||||
sex: number
|
||||
enabled: boolean
|
||||
avatar: string
|
||||
video?: string
|
||||
memo: string
|
||||
createTime?: number
|
||||
}
|
||||
|
||||
/** 学生班主任信息 */
|
||||
export interface StudentTeacher {
|
||||
id?: number
|
||||
studentId: number
|
||||
name: string
|
||||
description: string
|
||||
birthday: number
|
||||
sex: number
|
||||
enabled: boolean
|
||||
avatar: string
|
||||
video?: string
|
||||
memo: string
|
||||
createTime?: number
|
||||
}
|
||||
|
||||
/** 学生信息 */
|
||||
export interface Student {
|
||||
id?: number
|
||||
name: string
|
||||
description: string
|
||||
birthday: number
|
||||
sex: number
|
||||
enabled: boolean
|
||||
avatar: string
|
||||
video: string
|
||||
memo: string
|
||||
createTime?: number
|
||||
}
|
||||
|
||||
/** 获取学生分页列表 */
|
||||
export function getStudentPage(params: PageParam) {
|
||||
return http.get<PageResult<Student>>('/infra/student/page', params)
|
||||
}
|
||||
|
||||
/** 获取学生详情 */
|
||||
export function getStudent(id: number) {
|
||||
return http.get<Student>(`/infra/student/get?id=${id}`)
|
||||
}
|
||||
|
||||
/** 创建学生 */
|
||||
export function createStudent(data: Student) {
|
||||
return http.post<number>('/infra/student/create', data)
|
||||
}
|
||||
|
||||
/** 更新学生 */
|
||||
export function updateStudent(data: Student) {
|
||||
return http.put<boolean>('/infra/student/update', data)
|
||||
}
|
||||
|
||||
/** 删除学生 */
|
||||
export function deleteStudent(id: number) {
|
||||
return http.delete<boolean>(`/infra/student/delete?id=${id}`)
|
||||
}
|
||||
|
||||
// ==================== 子表:学生联系人 ====================
|
||||
|
||||
/** 获取学生联系人分页列表 */
|
||||
export function getStudentContactPage(params: PageParam & { studentId: number }) {
|
||||
return http.get<PageResult<StudentContact>>('/infra/student/student-contact/page', params)
|
||||
}
|
||||
|
||||
/** 获取学生联系人详情 */
|
||||
export function getStudentContact(id: number) {
|
||||
return http.get<StudentContact>(`/infra/student/student-contact/get?id=${id}`)
|
||||
}
|
||||
|
||||
/** 创建学生联系人 */
|
||||
export function createStudentContact(data: StudentContact) {
|
||||
return http.post<number>('/infra/student/student-contact/create', data)
|
||||
}
|
||||
|
||||
/** 更新学生联系人 */
|
||||
export function updateStudentContact(data: StudentContact) {
|
||||
return http.put<boolean>('/infra/student/student-contact/update', data)
|
||||
}
|
||||
|
||||
/** 删除学生联系人 */
|
||||
export function deleteStudentContact(id: number) {
|
||||
return http.delete<boolean>(`/infra/student/student-contact/delete?id=${id}`)
|
||||
}
|
||||
|
||||
// ==================== 子表:学生班主任 ====================
|
||||
|
||||
/** 获取学生班主任分页列表 */
|
||||
export function getStudentTeacherPage(params: PageParam & { studentId: number }) {
|
||||
return http.get<PageResult<StudentTeacher>>('/infra/student/student-teacher/page', params)
|
||||
}
|
||||
|
||||
/** 获取学生班主任详情 */
|
||||
export function getStudentTeacher(id: number) {
|
||||
return http.get<StudentTeacher>(`/infra/student/student-teacher/get?id=${id}`)
|
||||
}
|
||||
|
||||
/** 创建学生班主任 */
|
||||
export function createStudentTeacher(data: StudentTeacher) {
|
||||
return http.post<number>('/infra/student/student-teacher/create', data)
|
||||
}
|
||||
|
||||
/** 更新学生班主任 */
|
||||
export function updateStudentTeacher(data: StudentTeacher) {
|
||||
return http.put<boolean>('/infra/student/student-teacher/update', data)
|
||||
}
|
||||
|
||||
/** 删除学生班主任 */
|
||||
export function deleteStudentTeacher(id: number) {
|
||||
return http.delete<boolean>(`/infra/student/student-teacher/delete?id=${id}`)
|
||||
}
|
||||
+342
@@ -0,0 +1,342 @@
|
||||
<template>
|
||||
<view class="yd-page-container">
|
||||
<!-- 顶部导航栏 -->
|
||||
<wd-navbar title="学生详情" left-arrow placeholder safe-area-inset-top fixed @click-left="handleBack" />
|
||||
|
||||
<view class="pb-160rpx">
|
||||
<!-- 主表详情 -->
|
||||
<wd-cell-group border title="学生信息">
|
||||
<wd-cell title="编号" :value="formData?.id ?? '-'" />
|
||||
<wd-cell title="名字" :value="formData?.name ?? '-'" />
|
||||
<wd-cell title="简介" :value="formData?.description ?? '-'" />
|
||||
<wd-cell title="出生日期" :value="formatDateTime(formData?.birthday) || '-'" />
|
||||
<wd-cell title="性别">
|
||||
<dict-tag :type="DICT_TYPE.SYSTEM_USER_SEX" :value="formData?.sex" />
|
||||
</wd-cell>
|
||||
<wd-cell title="是否有效">
|
||||
<dict-tag :type="DICT_TYPE.INFRA_BOOLEAN_STRING" :value="formData?.enabled" />
|
||||
</wd-cell>
|
||||
<wd-cell title="头像" :value="formData?.avatar ?? '-'" />
|
||||
<wd-cell title="附件" :value="formData?.video ?? '-'" />
|
||||
<wd-cell title="备注" :value="formData?.memo ?? '-'" />
|
||||
<wd-cell title="创建时间" :value="formatDateTime(formData?.createTime) || '-'" />
|
||||
</wd-cell-group>
|
||||
|
||||
<!-- 子表:学生联系人 -->
|
||||
<view class="mt-20rpx flex items-center justify-between px-24rpx py-16rpx">
|
||||
<text class="text-28rpx text-[#333] font-semibold">学生联系人({{ studentContactTotal }})</text>
|
||||
<wd-button
|
||||
v-if="hasAccessByCodes(['infra:student:create'])"
|
||||
size="small" type="primary" variant="plain" @click="openStudentContact()"
|
||||
>
|
||||
添加
|
||||
</wd-button>
|
||||
</view>
|
||||
<view class="px-24rpx">
|
||||
<z-paging
|
||||
ref="studentContactPagingRef"
|
||||
v-model="studentContactList"
|
||||
:fixed="false"
|
||||
:auto="false"
|
||||
height="560rpx"
|
||||
:default-page-size="10"
|
||||
:refresher-enabled="false"
|
||||
:inside-more="true"
|
||||
:to-bottom-loading-more-enabled="false"
|
||||
loading-more-default-text="点击加载更多"
|
||||
empty-view-text="暂无学生联系人"
|
||||
@query="queryStudentContactList"
|
||||
>
|
||||
<view
|
||||
v-for="item in studentContactList"
|
||||
:key="item.id"
|
||||
class="mb-16rpx rounded-12rpx bg-white p-24rpx shadow-sm"
|
||||
>
|
||||
<view class="mb-8rpx text-28rpx text-[#666]">
|
||||
学生编号:{{ item.studentId ?? '-' }}
|
||||
</view>
|
||||
<view class="mb-8rpx text-28rpx text-[#666]">
|
||||
名字:{{ item.name ?? '-' }}
|
||||
</view>
|
||||
<view class="mb-8rpx text-28rpx text-[#666]">
|
||||
简介:{{ item.description ?? '-' }}
|
||||
</view>
|
||||
<view class="mb-8rpx text-28rpx text-[#666]">
|
||||
出生日期:{{ item.birthday ?? '-' }}
|
||||
</view>
|
||||
<view class="mb-8rpx text-28rpx text-[#666]">
|
||||
性别:{{ item.sex ?? '-' }}
|
||||
</view>
|
||||
<view class="mb-8rpx text-28rpx text-[#666]">
|
||||
是否有效:{{ item.enabled ?? '-' }}
|
||||
</view>
|
||||
<view class="mb-8rpx text-28rpx text-[#666]">
|
||||
头像:{{ item.avatar ?? '-' }}
|
||||
</view>
|
||||
<view class="mb-8rpx text-28rpx text-[#666]">
|
||||
附件:{{ item.video ?? '-' }}
|
||||
</view>
|
||||
<view class="mb-8rpx text-28rpx text-[#666]">
|
||||
备注:{{ item.memo ?? '-' }}
|
||||
</view>
|
||||
<view class="mb-8rpx text-28rpx text-[#666]">
|
||||
创建时间:{{ item.createTime ?? '-' }}
|
||||
</view>
|
||||
<view class="mt-16rpx flex justify-end gap-16rpx">
|
||||
<wd-button size="small" variant="plain" @click="openStudentContact(item)">
|
||||
编辑
|
||||
</wd-button>
|
||||
<wd-button size="small" type="danger" variant="plain" @click="removeStudentContact(item)">
|
||||
删除
|
||||
</wd-button>
|
||||
</view>
|
||||
</view>
|
||||
</z-paging>
|
||||
</view>
|
||||
<!-- 子表:学生班主任 -->
|
||||
<view class="mt-20rpx flex items-center justify-between px-24rpx py-16rpx">
|
||||
<text class="text-28rpx text-[#333] font-semibold">学生班主任({{ studentTeacherTotal }})</text>
|
||||
<wd-button
|
||||
v-if="studentTeacherTotal === 0 && hasAccessByCodes(['infra:student:create'])"
|
||||
size="small" type="primary" variant="plain" @click="openStudentTeacher()"
|
||||
>
|
||||
添加
|
||||
</wd-button>
|
||||
</view>
|
||||
<view class="px-24rpx">
|
||||
<z-paging
|
||||
ref="studentTeacherPagingRef"
|
||||
v-model="studentTeacherList"
|
||||
:fixed="false"
|
||||
:auto="false"
|
||||
height="560rpx"
|
||||
:default-page-size="1"
|
||||
:refresher-enabled="false"
|
||||
:inside-more="true"
|
||||
:to-bottom-loading-more-enabled="false"
|
||||
loading-more-default-text="点击加载更多"
|
||||
empty-view-text="暂无学生班主任"
|
||||
@query="queryStudentTeacherList"
|
||||
>
|
||||
<view
|
||||
v-for="item in studentTeacherList"
|
||||
:key="item.id"
|
||||
class="mb-16rpx rounded-12rpx bg-white p-24rpx shadow-sm"
|
||||
>
|
||||
<view class="mb-8rpx text-28rpx text-[#666]">
|
||||
学生编号:{{ item.studentId ?? '-' }}
|
||||
</view>
|
||||
<view class="mb-8rpx text-28rpx text-[#666]">
|
||||
名字:{{ item.name ?? '-' }}
|
||||
</view>
|
||||
<view class="mb-8rpx text-28rpx text-[#666]">
|
||||
简介:{{ item.description ?? '-' }}
|
||||
</view>
|
||||
<view class="mb-8rpx text-28rpx text-[#666]">
|
||||
出生日期:{{ item.birthday ?? '-' }}
|
||||
</view>
|
||||
<view class="mb-8rpx text-28rpx text-[#666]">
|
||||
性别:{{ item.sex ?? '-' }}
|
||||
</view>
|
||||
<view class="mb-8rpx text-28rpx text-[#666]">
|
||||
是否有效:{{ item.enabled ?? '-' }}
|
||||
</view>
|
||||
<view class="mb-8rpx text-28rpx text-[#666]">
|
||||
头像:{{ item.avatar ?? '-' }}
|
||||
</view>
|
||||
<view class="mb-8rpx text-28rpx text-[#666]">
|
||||
附件:{{ item.video ?? '-' }}
|
||||
</view>
|
||||
<view class="mb-8rpx text-28rpx text-[#666]">
|
||||
备注:{{ item.memo ?? '-' }}
|
||||
</view>
|
||||
<view class="mb-8rpx text-28rpx text-[#666]">
|
||||
创建时间:{{ item.createTime ?? '-' }}
|
||||
</view>
|
||||
<view class="mt-16rpx flex justify-end gap-16rpx">
|
||||
<wd-button size="small" variant="plain" @click="openStudentTeacher(item)">
|
||||
编辑
|
||||
</wd-button>
|
||||
<wd-button size="small" type="danger" variant="plain" @click="removeStudentTeacher(item)">
|
||||
删除
|
||||
</wd-button>
|
||||
</view>
|
||||
</view>
|
||||
</z-paging>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 底部操作按钮 -->
|
||||
<view class="yd-detail-footer">
|
||||
<view class="yd-detail-footer-actions">
|
||||
<wd-button v-if="hasAccessByCodes(['infra:student:update'])" class="flex-1" type="warning" @click="handleEdit">
|
||||
编辑
|
||||
</wd-button>
|
||||
<wd-button v-if="hasAccessByCodes(['infra:student:delete'])" class="flex-1" type="danger" :loading="deleting" @click="handleDelete">
|
||||
删除
|
||||
</wd-button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type {
|
||||
Student,
|
||||
StudentContact,
|
||||
StudentTeacher,
|
||||
} from '@/api/infra/demo'
|
||||
import { onShow } from '@dcloudio/uni-app'
|
||||
import { nextTick, ref } from 'vue'
|
||||
import { useDialog } from '@wot-ui/ui/components/wd-dialog'
|
||||
import { useToast } from '@wot-ui/ui/components/wd-toast'
|
||||
import {
|
||||
deleteStudent,
|
||||
deleteStudentContact,
|
||||
deleteStudentTeacher,
|
||||
getStudent,
|
||||
getStudentContactPage,
|
||||
getStudentTeacherPage,
|
||||
} from '@/api/infra/demo'
|
||||
import { useAccess } from '@/hooks/useAccess'
|
||||
import { delay, navigateBackPlus } from '@/utils'
|
||||
import { DICT_TYPE } from '@/utils/constants'
|
||||
import { formatDateTime } from '@/utils/date'
|
||||
|
||||
const props = defineProps<{ id?: number }>()
|
||||
|
||||
definePage({
|
||||
style: { navigationBarTitleText: '', navigationStyle: 'custom' },
|
||||
})
|
||||
|
||||
const { hasAccessByCodes } = useAccess()
|
||||
const dialog = useDialog()
|
||||
const toast = useToast()
|
||||
const formData = ref<Student>() // 主表数据
|
||||
const studentContactList = ref<StudentContact[]>([]) // 学生联系人列表
|
||||
const studentContactTotal = ref(0) // 学生联系人总数
|
||||
const studentContactPagingRef = ref<any>() // 学生联系人分页组件引用
|
||||
const studentTeacherList = ref<StudentTeacher[]>([]) // 学生班主任列表
|
||||
const studentTeacherTotal = ref(0) // 学生班主任总数
|
||||
const studentTeacherPagingRef = ref<any>() // 学生班主任分页组件引用
|
||||
const deleting = ref(false) // 主表删除状态
|
||||
|
||||
/** 返回上一页 */
|
||||
function handleBack() {
|
||||
navigateBackPlus('/pages-infra/demo/index')
|
||||
}
|
||||
|
||||
/** 加载主表 */
|
||||
async function loadMain() {
|
||||
if (props.id) {
|
||||
formData.value = await getStudent(props.id)
|
||||
}
|
||||
}
|
||||
|
||||
/** 查询学生联系人列表 */
|
||||
async function queryStudentContactList(pageNo: number, pageSize: number) {
|
||||
if (!props.id) {
|
||||
studentContactPagingRef.value?.completeByTotal([], 0)
|
||||
return
|
||||
}
|
||||
try {
|
||||
const data = await getStudentContactPage({ studentId: props.id, pageNo, pageSize })
|
||||
studentContactTotal.value = data.total
|
||||
studentContactPagingRef.value?.completeByTotal(data.list, data.total)
|
||||
} catch {
|
||||
studentContactPagingRef.value?.complete(false)
|
||||
}
|
||||
}
|
||||
|
||||
/** 打开学生联系人表单 */
|
||||
function openStudentContact(item?: StudentContact) {
|
||||
const query = [
|
||||
`studentId=${props.id}`,
|
||||
item?.id ? `id=${item.id}` : '',
|
||||
].filter(Boolean).join('&')
|
||||
uni.navigateTo({ url: `/pages-infra/demo/student-contact/form/index?${query}` })
|
||||
}
|
||||
|
||||
/** 删除学生联系人 */
|
||||
async function removeStudentContact(item: StudentContact) {
|
||||
try {
|
||||
await dialog.confirm({ title: '提示', msg: '确定要删除该学生联系人吗?' })
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
await deleteStudentContact(item.id!)
|
||||
toast.success('删除成功')
|
||||
studentContactPagingRef.value?.reload()
|
||||
}
|
||||
|
||||
/** 查询学生班主任列表 */
|
||||
async function queryStudentTeacherList(pageNo: number, pageSize: number) {
|
||||
if (!props.id) {
|
||||
studentTeacherPagingRef.value?.completeByTotal([], 0)
|
||||
return
|
||||
}
|
||||
try {
|
||||
const data = await getStudentTeacherPage({ studentId: props.id, pageNo, pageSize })
|
||||
studentTeacherTotal.value = data.total
|
||||
studentTeacherPagingRef.value?.completeByTotal(data.list, data.total)
|
||||
} catch {
|
||||
studentTeacherPagingRef.value?.complete(false)
|
||||
}
|
||||
}
|
||||
|
||||
/** 打开学生班主任表单 */
|
||||
function openStudentTeacher(item?: StudentTeacher) {
|
||||
const query = [
|
||||
`studentId=${props.id}`,
|
||||
item?.id ? `id=${item.id}` : '',
|
||||
].filter(Boolean).join('&')
|
||||
uni.navigateTo({ url: `/pages-infra/demo/student-teacher/form/index?${query}` })
|
||||
}
|
||||
|
||||
/** 删除学生班主任 */
|
||||
async function removeStudentTeacher(item: StudentTeacher) {
|
||||
try {
|
||||
await dialog.confirm({ title: '提示', msg: '确定要删除该学生班主任吗?' })
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
await deleteStudentTeacher(item.id!)
|
||||
toast.success('删除成功')
|
||||
studentTeacherPagingRef.value?.reload()
|
||||
}
|
||||
|
||||
/** 编辑主表 */
|
||||
function handleEdit() {
|
||||
uni.navigateTo({ url: `/pages-infra/demo/form/index?id=${props.id}` })
|
||||
}
|
||||
|
||||
/** 删除主表 */
|
||||
async function handleDelete() {
|
||||
if (!props.id) {
|
||||
return
|
||||
}
|
||||
try {
|
||||
await dialog.confirm({ title: '提示', msg: '确定要删除该学生吗?' })
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
deleting.value = true
|
||||
try {
|
||||
await deleteStudent(props.id)
|
||||
toast.success('删除成功')
|
||||
uni.$emit('infra:demo:reload')
|
||||
delay(handleBack)
|
||||
} finally {
|
||||
deleting.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 进入页面加载(含编辑和子表返回后刷新) */
|
||||
onShow(() => {
|
||||
loadMain()
|
||||
nextTick(() => {
|
||||
studentContactPagingRef.value?.reload()
|
||||
studentTeacherPagingRef.value?.reload()
|
||||
})
|
||||
})
|
||||
</script>
|
||||
+193
@@ -0,0 +1,193 @@
|
||||
<template>
|
||||
<view class="yd-page-container">
|
||||
<!-- 顶部导航栏 -->
|
||||
<wd-navbar
|
||||
:title="getTitle"
|
||||
left-arrow placeholder safe-area-inset-top fixed
|
||||
@click-left="handleBack"
|
||||
/>
|
||||
|
||||
<!-- 表单区域 -->
|
||||
<view>
|
||||
<wd-form ref="formRef" :model="formData" :schema="formSchema">
|
||||
<wd-cell-group border>
|
||||
<wd-form-item title="名字" title-width="180rpx" prop="name">
|
||||
<wd-input
|
||||
v-model="formData.name"
|
||||
clearable
|
||||
placeholder="请输入名字"
|
||||
/>
|
||||
</wd-form-item>
|
||||
<wd-form-item title="简介" title-width="180rpx" prop="description">
|
||||
<wd-textarea
|
||||
v-model="formData.description"
|
||||
clearable
|
||||
placeholder="请输入简介"
|
||||
:maxlength="200"
|
||||
show-word-limit
|
||||
/>
|
||||
</wd-form-item>
|
||||
<wd-form-item
|
||||
title="出生日期"
|
||||
title-width="180rpx"
|
||||
prop="birthday"
|
||||
is-link
|
||||
:value="formData.birthday ? formatDateTime(formData.birthday) : ''"
|
||||
placeholder="请选择出生日期"
|
||||
@click="birthdayVisible = true"
|
||||
/>
|
||||
<wd-datetime-picker
|
||||
v-model:visible="birthdayVisible"
|
||||
:model-value="formData.birthday || Date.now()"
|
||||
type="datetime"
|
||||
@update:model-value="value => formData.birthday = value"
|
||||
/>
|
||||
<yd-form-picker
|
||||
v-model="formData.sex"
|
||||
label="性别"
|
||||
label-width="180rpx"
|
||||
prop="sex"
|
||||
:dict-type="DICT_TYPE.SYSTEM_USER_SEX"
|
||||
placeholder="请选择性别"
|
||||
/>
|
||||
<wd-form-item title="是否有效" title-width="180rpx" prop="enabled" center>
|
||||
<wd-radio-group v-model="formData.enabled" type="button">
|
||||
<wd-radio
|
||||
v-for="dict in getBoolDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING)"
|
||||
:key="dict.value"
|
||||
:value="dict.value"
|
||||
>
|
||||
{{ dict.label }}
|
||||
</wd-radio>
|
||||
</wd-radio-group>
|
||||
</wd-form-item>
|
||||
<wd-form-item title="头像" title-width="180rpx" prop="avatar">
|
||||
<yd-upload-img v-model="formData.avatar" directory="infra/demo" />
|
||||
</wd-form-item>
|
||||
<wd-form-item title="附件" title-width="180rpx" prop="video">
|
||||
<yd-upload-file v-model="formData.video" :limit="1" directory="infra/demo" />
|
||||
</wd-form-item>
|
||||
<wd-form-item title="备注" title-width="180rpx" prop="memo">
|
||||
<wd-textarea
|
||||
v-model="formData.memo"
|
||||
clearable
|
||||
placeholder="请输入备注"
|
||||
:maxlength="2000"
|
||||
show-word-limit
|
||||
/>
|
||||
</wd-form-item>
|
||||
</wd-cell-group>
|
||||
</wd-form>
|
||||
</view>
|
||||
|
||||
<!-- 底部保存按钮 -->
|
||||
<view class="yd-detail-footer">
|
||||
<wd-button
|
||||
type="primary"
|
||||
block
|
||||
:loading="formLoading"
|
||||
@click="handleSubmit"
|
||||
>
|
||||
保存
|
||||
</wd-button>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { FormInstance } from '@wot-ui/ui/components/wd-form/types'
|
||||
import type {
|
||||
Student,
|
||||
} from '@/api/infra/demo'
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
import { useToast } from '@wot-ui/ui/components/wd-toast'
|
||||
import { createStudent, getStudent, updateStudent } from '@/api/infra/demo'
|
||||
import { getBoolDictOptions } from '@/hooks/useDict'
|
||||
import { delay, navigateBackPlus } from '@/utils'
|
||||
import { DICT_TYPE } from '@/utils/constants'
|
||||
import { formatDateTime } from '@/utils/date'
|
||||
import { createFormSchema } from '@/utils/wot'
|
||||
|
||||
const props = defineProps<{
|
||||
id?: number
|
||||
}>()
|
||||
|
||||
definePage({
|
||||
style: {
|
||||
navigationBarTitleText: '',
|
||||
navigationStyle: 'custom',
|
||||
},
|
||||
})
|
||||
|
||||
const toast = useToast()
|
||||
const getTitle = computed(() => props.id ? '编辑学生' : '新增学生')
|
||||
const formLoading = ref(false) // 表单提交状态
|
||||
const formData = ref<Partial<Student>>({
|
||||
id: undefined,
|
||||
name: '',
|
||||
description: '',
|
||||
birthday: undefined,
|
||||
sex: undefined,
|
||||
enabled: undefined,
|
||||
avatar: '',
|
||||
video: '',
|
||||
memo: '',
|
||||
}) // 表单数据
|
||||
const birthdayVisible = ref(false) // 出生日期选择器状态
|
||||
const formSchema = createFormSchema({
|
||||
name: [{ required: true, message: '名字不能为空' }],
|
||||
description: [{ required: true, message: '简介不能为空' }],
|
||||
birthday: [{ required: true, message: '出生日期不能为空' }],
|
||||
sex: [{ required: true, message: '性别不能为空' }],
|
||||
enabled: [{ required: true, message: '是否有效不能为空' }],
|
||||
avatar: [{ required: true, message: '头像不能为空' }],
|
||||
video: [{ required: true, message: '附件不能为空' }],
|
||||
memo: [{ required: true, message: '备注不能为空' }],
|
||||
}) // 表单校验规则
|
||||
const formRef = ref<FormInstance>() // 表单组件引用
|
||||
|
||||
/** 返回上一页 */
|
||||
function handleBack() {
|
||||
navigateBackPlus('/pages-infra/demo/index')
|
||||
}
|
||||
|
||||
/** 加载学生详情 */
|
||||
async function getDetail() {
|
||||
if (!props.id) {
|
||||
return
|
||||
}
|
||||
formData.value = await getStudent(props.id)
|
||||
}
|
||||
|
||||
/** 提交表单 */
|
||||
async function handleSubmit() {
|
||||
const { valid } = await formRef.value.validate()
|
||||
if (!valid) {
|
||||
return
|
||||
}
|
||||
|
||||
formLoading.value = true
|
||||
try {
|
||||
const data = formData.value as Student
|
||||
if (props.id) {
|
||||
await updateStudent(data)
|
||||
toast.success('修改成功')
|
||||
} else {
|
||||
await createStudent(data)
|
||||
toast.success('新增成功')
|
||||
}
|
||||
uni.$emit('infra:demo:reload')
|
||||
delay(handleBack)
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 初始化 */
|
||||
onMounted(async () => {
|
||||
getDetail()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
</style>
|
||||
@@ -0,0 +1,169 @@
|
||||
<template>
|
||||
<view class="yd-page-container yd-page-container-paging">
|
||||
<!-- 顶部导航栏 -->
|
||||
<wd-navbar
|
||||
title="学生管理"
|
||||
left-arrow placeholder safe-area-inset-top fixed
|
||||
@click-left="handleBack"
|
||||
/>
|
||||
|
||||
<!-- 搜索组件 -->
|
||||
<SearchForm @search="handleQuery" @reset="handleReset" />
|
||||
|
||||
<!-- 学生列表 -->
|
||||
<z-paging
|
||||
ref="pagingRef"
|
||||
v-model="list"
|
||||
:fixed="false"
|
||||
class="min-h-0 flex-1"
|
||||
:default-page-size="10"
|
||||
:refresher-enabled="true"
|
||||
:inside-more="true"
|
||||
:loading-more-default-as-loading="true"
|
||||
empty-view-text="暂无学生数据"
|
||||
@query="queryList"
|
||||
>
|
||||
<view class="p-24rpx">
|
||||
<view
|
||||
v-for="item in list"
|
||||
:key="item.id"
|
||||
class="mb-24rpx overflow-hidden rounded-12rpx bg-white shadow-sm"
|
||||
@click="handleDetail(item)"
|
||||
>
|
||||
<view class="p-24rpx">
|
||||
<view class="mb-16rpx flex items-center justify-between">
|
||||
<view class="text-32rpx text-[#333] font-semibold">
|
||||
{{ item.name }}
|
||||
</view>
|
||||
<dict-tag :type="DICT_TYPE.SYSTEM_USER_SEX" :value="item.sex" />
|
||||
</view>
|
||||
<view class="mb-12rpx flex items-center text-28rpx text-[#666]">
|
||||
<text class="mr-8rpx text-[#999]">简介:</text>
|
||||
<text class="line-clamp-1">{{ item.description }}</text>
|
||||
</view>
|
||||
<view class="mb-12rpx flex items-center text-28rpx text-[#666]">
|
||||
<text class="mr-8rpx text-[#999]">出生日期:</text>
|
||||
<text class="line-clamp-1">{{ formatDateTime(item.birthday) || '-' }}</text>
|
||||
</view>
|
||||
<view class="mb-12rpx flex items-center text-28rpx text-[#666]">
|
||||
<text class="mr-8rpx text-[#999]">是否有效:</text>
|
||||
<dict-tag :type="DICT_TYPE.INFRA_BOOLEAN_STRING" :value="item.enabled" />
|
||||
</view>
|
||||
<view class="mb-12rpx flex items-center text-28rpx text-[#666]">
|
||||
<text class="mr-8rpx text-[#999]">头像:</text>
|
||||
<text class="line-clamp-1">{{ item.avatar }}</text>
|
||||
</view>
|
||||
<view class="mb-12rpx flex items-center text-28rpx text-[#666]">
|
||||
<text class="mr-8rpx text-[#999]">附件:</text>
|
||||
<text class="line-clamp-1">{{ item.video }}</text>
|
||||
</view>
|
||||
<view class="mb-12rpx flex items-center text-28rpx text-[#666]">
|
||||
<text class="mr-8rpx text-[#999]">备注:</text>
|
||||
<text class="line-clamp-1">{{ item.memo }}</text>
|
||||
</view>
|
||||
<view class="mb-12rpx flex items-center text-28rpx text-[#666]">
|
||||
<text class="mr-8rpx text-[#999]">创建时间:</text>
|
||||
<text class="line-clamp-1">{{ formatDateTime(item.createTime) || '-' }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</z-paging>
|
||||
|
||||
<!-- 新增按钮 -->
|
||||
<wd-fab
|
||||
v-if="hasAccessByCodes(['infra:student:create'])"
|
||||
position="right-bottom"
|
||||
type="primary"
|
||||
:expandable="false"
|
||||
@click="handleAdd"
|
||||
/>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { Student } from '@/api/infra/demo'
|
||||
import { onUnload } from '@dcloudio/uni-app'
|
||||
import { onMounted, ref } from 'vue'
|
||||
import { getStudentPage } from '@/api/infra/demo'
|
||||
import { useAccess } from '@/hooks/useAccess'
|
||||
import { navigateBackPlus } from '@/utils'
|
||||
import { DICT_TYPE } from '@/utils/constants'
|
||||
import { formatDateTime } from '@/utils/date'
|
||||
import SearchForm from './components/search-form.vue'
|
||||
|
||||
definePage({
|
||||
style: {
|
||||
navigationBarTitleText: '',
|
||||
navigationStyle: 'custom',
|
||||
},
|
||||
})
|
||||
|
||||
const { hasAccessByCodes } = useAccess()
|
||||
const list = ref<Student[]>([]) // 列表数据
|
||||
const pagingRef = ref<any>() // 分页组件引用
|
||||
const queryParams = ref<Record<string, any>>({}) // 查询参数
|
||||
|
||||
/** 返回上一页 */
|
||||
function handleBack() {
|
||||
navigateBackPlus()
|
||||
}
|
||||
|
||||
/** 查询学生列表 */
|
||||
async function queryList(pageNo: number, pageSize: number) {
|
||||
try {
|
||||
const params = {
|
||||
...queryParams.value,
|
||||
pageNo,
|
||||
pageSize,
|
||||
}
|
||||
const data = await getStudentPage(params)
|
||||
pagingRef.value?.completeByTotal(data.list, data.total)
|
||||
} catch {
|
||||
pagingRef.value?.complete(false)
|
||||
}
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
function handleQuery(data?: Record<string, any>) {
|
||||
queryParams.value = { ...data }
|
||||
reload()
|
||||
}
|
||||
|
||||
/** 重置按钮操作 */
|
||||
function handleReset() {
|
||||
handleQuery()
|
||||
}
|
||||
|
||||
/** 重新加载 */
|
||||
function reload() {
|
||||
pagingRef.value?.reload()
|
||||
}
|
||||
|
||||
/** 新增学生 */
|
||||
function handleAdd() {
|
||||
uni.navigateTo({
|
||||
url: '/pages-infra/demo/form/index',
|
||||
})
|
||||
}
|
||||
|
||||
/** 查看详情 */
|
||||
function handleDetail(item: Student) {
|
||||
uni.navigateTo({
|
||||
url: `/pages-infra/demo/detail/index?id=${item.id}`,
|
||||
})
|
||||
}
|
||||
|
||||
/** 初始化 */
|
||||
onMounted(() => {
|
||||
uni.$on('infra:demo:reload', reload)
|
||||
})
|
||||
|
||||
/** 卸载 */
|
||||
onUnload(() => {
|
||||
uni.$off('infra:demo:reload', reload)
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
</style>
|
||||
+116
@@ -0,0 +1,116 @@
|
||||
<template>
|
||||
<!-- 搜索框入口 -->
|
||||
<view @click="visible = true">
|
||||
<wd-search :placeholder="placeholder" hide-cancel disabled />
|
||||
</view>
|
||||
|
||||
<!-- 搜索弹窗 -->
|
||||
<wd-popup v-model="visible" position="top" @close="visible = false">
|
||||
<view class="yd-search-form-container" :style="{ paddingTop: `${getNavbarHeight()}px` }">
|
||||
<view class="yd-search-form-item">
|
||||
<view class="yd-search-form-label">
|
||||
名字
|
||||
</view>
|
||||
<wd-input
|
||||
v-model="formData.name"
|
||||
placeholder="请输入名字"
|
||||
clearable
|
||||
/>
|
||||
</view>
|
||||
<view class="yd-search-form-item">
|
||||
<view class="yd-search-form-label">
|
||||
出生日期
|
||||
</view>
|
||||
<wd-input
|
||||
v-model="formData.birthday"
|
||||
placeholder="请输入出生日期"
|
||||
clearable
|
||||
/>
|
||||
</view>
|
||||
<yd-search-picker
|
||||
v-model="formData.sex"
|
||||
label="性别"
|
||||
:columns="getIntDictOptions(DICT_TYPE.SYSTEM_USER_SEX)"
|
||||
all-option
|
||||
/>
|
||||
<yd-search-picker
|
||||
v-model="formData.enabled"
|
||||
label="是否有效"
|
||||
:columns="getBoolDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING)"
|
||||
all-option
|
||||
/>
|
||||
<yd-search-date-range v-model="formData.createTime" label="创建时间" />
|
||||
<view class="yd-search-form-actions">
|
||||
<wd-button class="flex-1" plain @click="handleReset">
|
||||
重置
|
||||
</wd-button>
|
||||
<wd-button class="flex-1" type="primary" @click="handleSearch">
|
||||
搜索
|
||||
</wd-button>
|
||||
</view>
|
||||
</view>
|
||||
</wd-popup>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, reactive, ref } from 'vue'
|
||||
import { getNavbarHeight } from '@/utils'
|
||||
import { formatDate, formatDateRange } from '@/utils/date'
|
||||
import { getDictLabel, getIntDictOptions, getBoolDictOptions } from '@/hooks/useDict'
|
||||
import { DICT_TYPE } from '@/utils/constants'
|
||||
|
||||
const emit = defineEmits<{
|
||||
search: [data: Record<string, any>]
|
||||
reset: []
|
||||
}>()
|
||||
|
||||
const visible = ref(false) // 搜索弹窗显示状态
|
||||
const formData = reactive({
|
||||
name: undefined as string | undefined,
|
||||
birthday: undefined as string | undefined,
|
||||
sex: undefined as number | undefined,
|
||||
enabled: undefined as boolean | undefined,
|
||||
createTime: [undefined, undefined] as [number | undefined, number | undefined],
|
||||
}) // 搜索表单数据
|
||||
|
||||
/** 搜索条件 placeholder 拼接 */
|
||||
const placeholder = computed(() => {
|
||||
const conditions: string[] = []
|
||||
if (formData.name) {
|
||||
conditions.push(`名字:${formData.name}`)
|
||||
}
|
||||
if (formData.birthday) {
|
||||
conditions.push(`出生日期:${formData.birthday}`)
|
||||
}
|
||||
if (formData.sex !== undefined) {
|
||||
conditions.push(`性别:${getDictLabel(DICT_TYPE.SYSTEM_USER_SEX, formData.sex)}`)
|
||||
}
|
||||
if (formData.enabled !== undefined) {
|
||||
conditions.push(`是否有效:${getDictLabel(DICT_TYPE.INFRA_BOOLEAN_STRING, formData.enabled)}`)
|
||||
}
|
||||
if (formData.createTime?.[0] && formData.createTime?.[1]) {
|
||||
conditions.push(`创建时间:${formatDate(formData.createTime[0])}~${formatDate(formData.createTime[1])}`)
|
||||
}
|
||||
return conditions.length > 0 ? conditions.join(' | ') : '搜索学生'
|
||||
})
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
function handleSearch() {
|
||||
visible.value = false
|
||||
emit('search', {
|
||||
...formData,
|
||||
createTime: formatDateRange(formData.createTime),
|
||||
})
|
||||
}
|
||||
|
||||
/** 重置按钮操作 */
|
||||
function handleReset() {
|
||||
formData.name = undefined
|
||||
formData.birthday = undefined
|
||||
formData.sex = undefined
|
||||
formData.enabled = undefined
|
||||
formData.createTime = [undefined, undefined]
|
||||
visible.value = false
|
||||
emit('reset')
|
||||
}
|
||||
</script>
|
||||
+167
@@ -0,0 +1,167 @@
|
||||
<template>
|
||||
<view class="yd-page-container">
|
||||
<!-- 顶部导航栏 -->
|
||||
<wd-navbar :title="getTitle" left-arrow placeholder safe-area-inset-top fixed @click-left="handleBack" />
|
||||
|
||||
<!-- 表单区域 -->
|
||||
<wd-form ref="formRef" :model="formData" :schema="formSchema">
|
||||
<wd-cell-group border>
|
||||
<wd-form-item title="名字" title-width="180rpx" prop="name">
|
||||
<wd-input v-model="formData.name" clearable placeholder="请输入名字" />
|
||||
</wd-form-item>
|
||||
<wd-form-item title="简介" title-width="180rpx" prop="description">
|
||||
<wd-textarea
|
||||
v-model="formData.description"
|
||||
clearable
|
||||
placeholder="请输入简介"
|
||||
:maxlength="200"
|
||||
/>
|
||||
</wd-form-item>
|
||||
<wd-form-item
|
||||
title="出生日期"
|
||||
title-width="180rpx"
|
||||
prop="birthday"
|
||||
is-link
|
||||
:value="formData.birthday ? formatDateTime(formData.birthday) : ''"
|
||||
placeholder="请选择出生日期"
|
||||
@click="birthdayVisible = true"
|
||||
/>
|
||||
<wd-datetime-picker
|
||||
v-model:visible="birthdayVisible"
|
||||
:model-value="formData.birthday || Date.now()"
|
||||
type="datetime"
|
||||
@update:model-value="value => formData.birthday = value"
|
||||
/>
|
||||
<yd-form-picker
|
||||
v-model="formData.sex"
|
||||
label="性别"
|
||||
label-width="180rpx"
|
||||
prop="sex"
|
||||
:dict-type="DICT_TYPE.SYSTEM_USER_SEX"
|
||||
placeholder="请选择性别"
|
||||
/>
|
||||
<yd-form-picker
|
||||
v-model="formData.enabled"
|
||||
label="是否有效"
|
||||
label-width="180rpx"
|
||||
prop="enabled"
|
||||
:columns="getBoolDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING)"
|
||||
placeholder="请选择是否有效"
|
||||
/>
|
||||
<wd-form-item title="头像" title-width="180rpx" prop="avatar">
|
||||
<yd-upload-img v-model="formData.avatar" directory="infra/demo" />
|
||||
</wd-form-item>
|
||||
<wd-form-item title="附件" title-width="180rpx" prop="video">
|
||||
<yd-upload-file v-model="formData.video" :limit="1" directory="infra/demo" />
|
||||
</wd-form-item>
|
||||
<wd-form-item title="备注" title-width="180rpx" prop="memo">
|
||||
<wd-textarea
|
||||
v-model="formData.memo"
|
||||
clearable
|
||||
placeholder="请输入备注"
|
||||
:maxlength="2000"
|
||||
/>
|
||||
</wd-form-item>
|
||||
</wd-cell-group>
|
||||
</wd-form>
|
||||
|
||||
<!-- 底部保存按钮 -->
|
||||
<view class="yd-detail-footer">
|
||||
<wd-button type="primary" block :loading="formLoading" @click="handleSubmit">
|
||||
保存
|
||||
</wd-button>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { FormInstance } from '@wot-ui/ui/components/wd-form/types'
|
||||
import type { StudentContact } from '@/api/infra/demo'
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
import { useToast } from '@wot-ui/ui/components/wd-toast'
|
||||
import { createStudentContact, getStudentContact, updateStudentContact } from '@/api/infra/demo'
|
||||
import { delay, navigateBackPlus } from '@/utils'
|
||||
import { getBoolDictOptions } from '@/hooks/useDict'
|
||||
import { DICT_TYPE } from '@/utils/constants'
|
||||
import { formatDateTime } from '@/utils/date'
|
||||
import { createFormSchema } from '@/utils/wot'
|
||||
|
||||
const props = defineProps<{
|
||||
id?: number
|
||||
studentId: number
|
||||
}>()
|
||||
|
||||
definePage({
|
||||
style: { navigationBarTitleText: '', navigationStyle: 'custom' },
|
||||
})
|
||||
|
||||
const toast = useToast()
|
||||
const getTitle = computed(() => props.id ? '编辑学生联系人' : '新增学生联系人')
|
||||
const formLoading = ref(false) // 表单提交状态
|
||||
const formData = ref<Partial<StudentContact>>({
|
||||
id: undefined,
|
||||
studentId: props.studentId,
|
||||
name: '',
|
||||
description: '',
|
||||
birthday: undefined,
|
||||
sex: undefined,
|
||||
enabled: undefined,
|
||||
avatar: '',
|
||||
video: undefined,
|
||||
memo: '',
|
||||
}) // 表单数据
|
||||
const birthdayVisible = ref(false) // 出生日期选择器状态
|
||||
const formSchema = createFormSchema({
|
||||
studentId: [{ required: true, message: '学生编号不能为空' }],
|
||||
name: [{ required: true, message: '名字不能为空' }],
|
||||
description: [{ required: true, message: '简介不能为空' }],
|
||||
birthday: [{ required: true, message: '出生日期不能为空' }],
|
||||
sex: [{ required: true, message: '性别不能为空' }],
|
||||
enabled: [{ required: true, message: '是否有效不能为空' }],
|
||||
avatar: [{ required: true, message: '头像不能为空' }],
|
||||
memo: [{ required: true, message: '备注不能为空' }],
|
||||
}) // 表单校验规则
|
||||
const formRef = ref<FormInstance>() // 表单组件引用
|
||||
|
||||
/** 返回上一页 */
|
||||
function handleBack() {
|
||||
navigateBackPlus()
|
||||
}
|
||||
|
||||
/** 加载学生联系人详情 */
|
||||
async function getDetail() {
|
||||
if (props.id) {
|
||||
formData.value = await getStudentContact(props.id)
|
||||
}
|
||||
}
|
||||
|
||||
/** 提交表单 */
|
||||
async function handleSubmit() {
|
||||
const { valid } = await formRef.value.validate()
|
||||
if (!valid) {
|
||||
return
|
||||
}
|
||||
formLoading.value = true
|
||||
try {
|
||||
const data = {
|
||||
...formData.value,
|
||||
studentId: props.studentId ?? formData.value.studentId,
|
||||
} as StudentContact
|
||||
if (props.id) {
|
||||
await updateStudentContact(data)
|
||||
toast.success('修改成功')
|
||||
} else {
|
||||
await createStudentContact(data)
|
||||
toast.success('新增成功')
|
||||
}
|
||||
delay(handleBack)
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 初始化 */
|
||||
onMounted(() => {
|
||||
getDetail()
|
||||
})
|
||||
</script>
|
||||
+167
@@ -0,0 +1,167 @@
|
||||
<template>
|
||||
<view class="yd-page-container">
|
||||
<!-- 顶部导航栏 -->
|
||||
<wd-navbar :title="getTitle" left-arrow placeholder safe-area-inset-top fixed @click-left="handleBack" />
|
||||
|
||||
<!-- 表单区域 -->
|
||||
<wd-form ref="formRef" :model="formData" :schema="formSchema">
|
||||
<wd-cell-group border>
|
||||
<wd-form-item title="名字" title-width="180rpx" prop="name">
|
||||
<wd-input v-model="formData.name" clearable placeholder="请输入名字" />
|
||||
</wd-form-item>
|
||||
<wd-form-item title="简介" title-width="180rpx" prop="description">
|
||||
<wd-textarea
|
||||
v-model="formData.description"
|
||||
clearable
|
||||
placeholder="请输入简介"
|
||||
:maxlength="200"
|
||||
/>
|
||||
</wd-form-item>
|
||||
<wd-form-item
|
||||
title="出生日期"
|
||||
title-width="180rpx"
|
||||
prop="birthday"
|
||||
is-link
|
||||
:value="formData.birthday ? formatDateTime(formData.birthday) : ''"
|
||||
placeholder="请选择出生日期"
|
||||
@click="birthdayVisible = true"
|
||||
/>
|
||||
<wd-datetime-picker
|
||||
v-model:visible="birthdayVisible"
|
||||
:model-value="formData.birthday || Date.now()"
|
||||
type="datetime"
|
||||
@update:model-value="value => formData.birthday = value"
|
||||
/>
|
||||
<yd-form-picker
|
||||
v-model="formData.sex"
|
||||
label="性别"
|
||||
label-width="180rpx"
|
||||
prop="sex"
|
||||
:dict-type="DICT_TYPE.SYSTEM_USER_SEX"
|
||||
placeholder="请选择性别"
|
||||
/>
|
||||
<yd-form-picker
|
||||
v-model="formData.enabled"
|
||||
label="是否有效"
|
||||
label-width="180rpx"
|
||||
prop="enabled"
|
||||
:columns="getBoolDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING)"
|
||||
placeholder="请选择是否有效"
|
||||
/>
|
||||
<wd-form-item title="头像" title-width="180rpx" prop="avatar">
|
||||
<yd-upload-img v-model="formData.avatar" directory="infra/demo" />
|
||||
</wd-form-item>
|
||||
<wd-form-item title="附件" title-width="180rpx" prop="video">
|
||||
<yd-upload-file v-model="formData.video" :limit="1" directory="infra/demo" />
|
||||
</wd-form-item>
|
||||
<wd-form-item title="备注" title-width="180rpx" prop="memo">
|
||||
<wd-textarea
|
||||
v-model="formData.memo"
|
||||
clearable
|
||||
placeholder="请输入备注"
|
||||
:maxlength="2000"
|
||||
/>
|
||||
</wd-form-item>
|
||||
</wd-cell-group>
|
||||
</wd-form>
|
||||
|
||||
<!-- 底部保存按钮 -->
|
||||
<view class="yd-detail-footer">
|
||||
<wd-button type="primary" block :loading="formLoading" @click="handleSubmit">
|
||||
保存
|
||||
</wd-button>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { FormInstance } from '@wot-ui/ui/components/wd-form/types'
|
||||
import type { StudentTeacher } from '@/api/infra/demo'
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
import { useToast } from '@wot-ui/ui/components/wd-toast'
|
||||
import { createStudentTeacher, getStudentTeacher, updateStudentTeacher } from '@/api/infra/demo'
|
||||
import { delay, navigateBackPlus } from '@/utils'
|
||||
import { getBoolDictOptions } from '@/hooks/useDict'
|
||||
import { DICT_TYPE } from '@/utils/constants'
|
||||
import { formatDateTime } from '@/utils/date'
|
||||
import { createFormSchema } from '@/utils/wot'
|
||||
|
||||
const props = defineProps<{
|
||||
id?: number
|
||||
studentId: number
|
||||
}>()
|
||||
|
||||
definePage({
|
||||
style: { navigationBarTitleText: '', navigationStyle: 'custom' },
|
||||
})
|
||||
|
||||
const toast = useToast()
|
||||
const getTitle = computed(() => props.id ? '编辑学生班主任' : '新增学生班主任')
|
||||
const formLoading = ref(false) // 表单提交状态
|
||||
const formData = ref<Partial<StudentTeacher>>({
|
||||
id: undefined,
|
||||
studentId: props.studentId,
|
||||
name: '',
|
||||
description: '',
|
||||
birthday: undefined,
|
||||
sex: undefined,
|
||||
enabled: undefined,
|
||||
avatar: '',
|
||||
video: undefined,
|
||||
memo: '',
|
||||
}) // 表单数据
|
||||
const birthdayVisible = ref(false) // 出生日期选择器状态
|
||||
const formSchema = createFormSchema({
|
||||
studentId: [{ required: true, message: '学生编号不能为空' }],
|
||||
name: [{ required: true, message: '名字不能为空' }],
|
||||
description: [{ required: true, message: '简介不能为空' }],
|
||||
birthday: [{ required: true, message: '出生日期不能为空' }],
|
||||
sex: [{ required: true, message: '性别不能为空' }],
|
||||
enabled: [{ required: true, message: '是否有效不能为空' }],
|
||||
avatar: [{ required: true, message: '头像不能为空' }],
|
||||
memo: [{ required: true, message: '备注不能为空' }],
|
||||
}) // 表单校验规则
|
||||
const formRef = ref<FormInstance>() // 表单组件引用
|
||||
|
||||
/** 返回上一页 */
|
||||
function handleBack() {
|
||||
navigateBackPlus()
|
||||
}
|
||||
|
||||
/** 加载学生班主任详情 */
|
||||
async function getDetail() {
|
||||
if (props.id) {
|
||||
formData.value = await getStudentTeacher(props.id)
|
||||
}
|
||||
}
|
||||
|
||||
/** 提交表单 */
|
||||
async function handleSubmit() {
|
||||
const { valid } = await formRef.value.validate()
|
||||
if (!valid) {
|
||||
return
|
||||
}
|
||||
formLoading.value = true
|
||||
try {
|
||||
const data = {
|
||||
...formData.value,
|
||||
studentId: props.studentId ?? formData.value.studentId,
|
||||
} as StudentTeacher
|
||||
if (props.id) {
|
||||
await updateStudentTeacher(data)
|
||||
toast.success('修改成功')
|
||||
} else {
|
||||
await createStudentTeacher(data)
|
||||
toast.success('新增成功')
|
||||
}
|
||||
delay(handleBack)
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 初始化 */
|
||||
onMounted(() => {
|
||||
getDetail()
|
||||
})
|
||||
</script>
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="cn.iocoder.yudao.module.infra.dal.mysql.demo.InfraStudentMapper">
|
||||
|
||||
<!--
|
||||
一般情况下,尽可能使用 Mapper 进行 CRUD 增删改查即可。
|
||||
无法满足的场景,例如说多表关联查询,才使用 XML 编写 SQL。
|
||||
代码生成器暂时只生成 Mapper XML 文件本身,更多推荐 MybatisX 快速开发插件来生成查询。
|
||||
文档可见:https://www.iocoder.cn/MyBatis/x-plugins/
|
||||
-->
|
||||
|
||||
</mapper>
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
[ {
|
||||
"contentPath" : "java/InfraStudentPageReqVO",
|
||||
"filePath" : "yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/demo/vo/InfraStudentPageReqVO.java"
|
||||
}, {
|
||||
"contentPath" : "java/InfraStudentRespVO",
|
||||
"filePath" : "yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/demo/vo/InfraStudentRespVO.java"
|
||||
}, {
|
||||
"contentPath" : "java/InfraStudentSaveReqVO",
|
||||
"filePath" : "yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/demo/vo/InfraStudentSaveReqVO.java"
|
||||
}, {
|
||||
"contentPath" : "java/InfraStudentController",
|
||||
"filePath" : "yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/demo/InfraStudentController.java"
|
||||
}, {
|
||||
"contentPath" : "java/InfraStudentDO",
|
||||
"filePath" : "yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/dal/dataobject/demo/InfraStudentDO.java"
|
||||
}, {
|
||||
"contentPath" : "java/InfraStudentContactDO",
|
||||
"filePath" : "yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/dal/dataobject/demo/InfraStudentContactDO.java"
|
||||
}, {
|
||||
"contentPath" : "java/InfraStudentTeacherDO",
|
||||
"filePath" : "yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/dal/dataobject/demo/InfraStudentTeacherDO.java"
|
||||
}, {
|
||||
"contentPath" : "java/InfraStudentMapper",
|
||||
"filePath" : "yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/dal/mysql/demo/InfraStudentMapper.java"
|
||||
}, {
|
||||
"contentPath" : "java/InfraStudentContactMapper",
|
||||
"filePath" : "yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/dal/mysql/demo/InfraStudentContactMapper.java"
|
||||
}, {
|
||||
"contentPath" : "java/InfraStudentTeacherMapper",
|
||||
"filePath" : "yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/dal/mysql/demo/InfraStudentTeacherMapper.java"
|
||||
}, {
|
||||
"contentPath" : "xml/InfraStudentMapper",
|
||||
"filePath" : "yudao-module-infra/src/main/resources/mapper/demo/InfraStudentMapper.xml"
|
||||
}, {
|
||||
"contentPath" : "java/InfraStudentServiceImpl",
|
||||
"filePath" : "yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/service/demo/InfraStudentServiceImpl.java"
|
||||
}, {
|
||||
"contentPath" : "java/InfraStudentService",
|
||||
"filePath" : "yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/service/demo/InfraStudentService.java"
|
||||
}, {
|
||||
"contentPath" : "java/InfraStudentServiceImplTest",
|
||||
"filePath" : "yudao-module-infra/src/test/java/cn/iocoder/yudao/module/infra/service/demo/InfraStudentServiceImplTest.java"
|
||||
}, {
|
||||
"contentPath" : "java/ErrorCodeConstants_手动操作",
|
||||
"filePath" : "yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/enums/ErrorCodeConstants_手动操作.java"
|
||||
}, {
|
||||
"contentPath" : "sql/sql",
|
||||
"filePath" : "sql/sql.sql"
|
||||
}, {
|
||||
"contentPath" : "sql/h2",
|
||||
"filePath" : "sql/h2.sql"
|
||||
}, {
|
||||
"contentPath" : "ts/index",
|
||||
"filePath" : "yudao-ui-admin-uniapp/src/api/infra/demo/index.ts"
|
||||
}, {
|
||||
"contentPath" : "vue/index",
|
||||
"filePath" : "yudao-ui-admin-uniapp/src/pages-infra/demo/index.vue"
|
||||
}, {
|
||||
"contentPath" : "vue/search-form",
|
||||
"filePath" : "yudao-ui-admin-uniapp/src/pages-infra/demo/components/search-form.vue"
|
||||
}, {
|
||||
"contentPath" : "vue/form/index",
|
||||
"filePath" : "yudao-ui-admin-uniapp/src/pages-infra/demo/form/index.vue"
|
||||
}, {
|
||||
"contentPath" : "vue/detail/index",
|
||||
"filePath" : "yudao-ui-admin-uniapp/src/pages-infra/demo/detail/index.vue"
|
||||
} ]
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
// TODO 待办:请将下面的错误码复制到 yudao-module-infra 模块的 ErrorCodeConstants 类中。注意,请给“TODO 补充编号”设置一个错误码编号!!!
|
||||
// ========== 学生 TODO 补充编号 ==========
|
||||
ErrorCode STUDENT_NOT_EXISTS = new ErrorCode(TODO 补充编号, "学生不存在");
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
package cn.iocoder.yudao.module.infra.dal.dataobject.demo;
|
||||
|
||||
import lombok.*;
|
||||
import java.util.*;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.LocalDateTime;
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
|
||||
|
||||
/**
|
||||
* 学生联系人 DO
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@TableName("infra_student_contact")
|
||||
@KeySequence("infra_student_contact_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@ToString(callSuper = true)
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class InfraStudentContactDO extends BaseDO {
|
||||
|
||||
/**
|
||||
* 编号
|
||||
*/
|
||||
@TableId
|
||||
private Long id;
|
||||
/**
|
||||
* 学生编号
|
||||
*/
|
||||
private Long studentId;
|
||||
/**
|
||||
* 名字
|
||||
*/
|
||||
private String name;
|
||||
/**
|
||||
* 简介
|
||||
*/
|
||||
private String description;
|
||||
/**
|
||||
* 出生日期
|
||||
*/
|
||||
private LocalDateTime birthday;
|
||||
/**
|
||||
* 性别
|
||||
*
|
||||
* 枚举 {@link TODO system_user_sex 对应的类}
|
||||
*/
|
||||
private Integer sex;
|
||||
/**
|
||||
* 是否有效
|
||||
*
|
||||
* 枚举 {@link TODO infra_boolean_string 对应的类}
|
||||
*/
|
||||
private Boolean enabled;
|
||||
/**
|
||||
* 头像
|
||||
*/
|
||||
private String avatar;
|
||||
/**
|
||||
* 附件
|
||||
*/
|
||||
private String video;
|
||||
/**
|
||||
* 备注
|
||||
*/
|
||||
private String memo;
|
||||
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
package cn.iocoder.yudao.module.infra.dal.mysql.demo;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageParam;
|
||||
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
|
||||
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
|
||||
import cn.iocoder.yudao.module.infra.dal.dataobject.demo.InfraStudentContactDO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
/**
|
||||
* 学生联系人 Mapper
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Mapper
|
||||
public interface InfraStudentContactMapper extends BaseMapperX<InfraStudentContactDO> {
|
||||
|
||||
default List<InfraStudentContactDO> selectListByStudentId(Long studentId) {
|
||||
return selectList(InfraStudentContactDO::getStudentId, studentId);
|
||||
}
|
||||
|
||||
default int deleteByStudentId(Long studentId) {
|
||||
return delete(InfraStudentContactDO::getStudentId, studentId);
|
||||
}
|
||||
|
||||
default int deleteByStudentIds(List<Long> studentIds) {
|
||||
return deleteBatch(InfraStudentContactDO::getStudentId, studentIds);
|
||||
}
|
||||
|
||||
}
|
||||
+126
@@ -0,0 +1,126 @@
|
||||
package cn.iocoder.yudao.module.infra.controller.admin.demo;
|
||||
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
|
||||
import jakarta.validation.constraints.*;
|
||||
import jakarta.validation.*;
|
||||
import jakarta.servlet.http.*;
|
||||
import java.util.*;
|
||||
import java.io.IOException;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageParam;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
|
||||
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
|
||||
|
||||
import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
|
||||
|
||||
import cn.iocoder.yudao.framework.apilog.core.annotation.ApiAccessLog;
|
||||
import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.*;
|
||||
|
||||
import cn.iocoder.yudao.module.infra.controller.admin.demo.vo.*;
|
||||
import cn.iocoder.yudao.module.infra.dal.dataobject.demo.InfraStudentDO;
|
||||
import cn.iocoder.yudao.module.infra.dal.dataobject.demo.InfraStudentContactDO;
|
||||
import cn.iocoder.yudao.module.infra.dal.dataobject.demo.InfraStudentTeacherDO;
|
||||
import cn.iocoder.yudao.module.infra.service.demo.InfraStudentService;
|
||||
|
||||
@Tag(name = "管理后台 - 学生")
|
||||
@RestController
|
||||
@RequestMapping("/infra/student")
|
||||
@Validated
|
||||
public class InfraStudentController {
|
||||
|
||||
@Resource
|
||||
private InfraStudentService studentService;
|
||||
|
||||
@PostMapping("/create")
|
||||
@Operation(summary = "创建学生")
|
||||
@PreAuthorize("@ss.hasPermission('infra:student:create')")
|
||||
public CommonResult<Long> createStudent(@Valid @RequestBody InfraStudentSaveReqVO createReqVO) {
|
||||
return success(studentService.createStudent(createReqVO));
|
||||
}
|
||||
|
||||
@PutMapping("/update")
|
||||
@Operation(summary = "更新学生")
|
||||
@PreAuthorize("@ss.hasPermission('infra:student:update')")
|
||||
public CommonResult<Boolean> updateStudent(@Valid @RequestBody InfraStudentSaveReqVO updateReqVO) {
|
||||
studentService.updateStudent(updateReqVO);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@DeleteMapping("/delete")
|
||||
@Operation(summary = "删除学生")
|
||||
@Parameter(name = "id", description = "编号", required = true)
|
||||
@PreAuthorize("@ss.hasPermission('infra:student:delete')")
|
||||
public CommonResult<Boolean> deleteStudent(@RequestParam("id") Long id) {
|
||||
studentService.deleteStudent(id);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@DeleteMapping("/delete-list")
|
||||
@Parameter(name = "ids", description = "编号", required = true)
|
||||
@Operation(summary = "批量删除学生")
|
||||
@PreAuthorize("@ss.hasPermission('infra:student:delete')")
|
||||
public CommonResult<Boolean> deleteStudentList(@RequestParam("ids") List<Long> ids) {
|
||||
studentService.deleteStudentListByIds(ids);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@GetMapping("/get")
|
||||
@Operation(summary = "获得学生")
|
||||
@Parameter(name = "id", description = "编号", required = true, example = "1024")
|
||||
@PreAuthorize("@ss.hasPermission('infra:student:query')")
|
||||
public CommonResult<InfraStudentRespVO> getStudent(@RequestParam("id") Long id) {
|
||||
InfraStudentDO student = studentService.getStudent(id);
|
||||
return success(BeanUtils.toBean(student, InfraStudentRespVO.class));
|
||||
}
|
||||
|
||||
@GetMapping("/page")
|
||||
@Operation(summary = "获得学生分页")
|
||||
@PreAuthorize("@ss.hasPermission('infra:student:query')")
|
||||
public CommonResult<PageResult<InfraStudentRespVO>> getStudentPage(@Valid InfraStudentPageReqVO pageReqVO) {
|
||||
PageResult<InfraStudentDO> pageResult = studentService.getStudentPage(pageReqVO);
|
||||
return success(BeanUtils.toBean(pageResult, InfraStudentRespVO.class));
|
||||
}
|
||||
|
||||
@GetMapping("/export-excel")
|
||||
@Operation(summary = "导出学生 Excel")
|
||||
@PreAuthorize("@ss.hasPermission('infra:student:export')")
|
||||
@ApiAccessLog(operateType = EXPORT)
|
||||
public void exportStudentExcel(@Valid InfraStudentPageReqVO pageReqVO,
|
||||
HttpServletResponse response) throws IOException {
|
||||
pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
|
||||
List<InfraStudentDO> list = studentService.getStudentPage(pageReqVO).getList();
|
||||
// 导出 Excel
|
||||
ExcelUtils.write(response, "学生.xls", "数据", InfraStudentRespVO.class,
|
||||
BeanUtils.toBean(list, InfraStudentRespVO.class));
|
||||
}
|
||||
|
||||
// ==================== 子表(学生联系人) ====================
|
||||
|
||||
@GetMapping("/student-contact/list-by-student-id")
|
||||
@Operation(summary = "获得学生联系人列表")
|
||||
@Parameter(name = "studentId", description = "学生编号")
|
||||
@PreAuthorize("@ss.hasPermission('infra:student:query')")
|
||||
public CommonResult<List<InfraStudentContactDO>> getStudentContactListByStudentId(@RequestParam("studentId") Long studentId) {
|
||||
return success(studentService.getStudentContactListByStudentId(studentId));
|
||||
}
|
||||
|
||||
// ==================== 子表(学生班主任) ====================
|
||||
|
||||
@GetMapping("/student-teacher/get-by-student-id")
|
||||
@Operation(summary = "获得学生班主任")
|
||||
@Parameter(name = "studentId", description = "学生编号")
|
||||
@PreAuthorize("@ss.hasPermission('infra:student:query')")
|
||||
public CommonResult<InfraStudentTeacherDO> getStudentTeacherByStudentId(@RequestParam("studentId") Long studentId) {
|
||||
return success(studentService.getStudentTeacherByStudentId(studentId));
|
||||
}
|
||||
|
||||
}
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
package cn.iocoder.yudao.module.infra.dal.dataobject.demo;
|
||||
|
||||
import lombok.*;
|
||||
import java.util.*;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.LocalDateTime;
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
|
||||
|
||||
/**
|
||||
* 学生 DO
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@TableName("infra_student")
|
||||
@KeySequence("infra_student_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@ToString(callSuper = true)
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class InfraStudentDO extends BaseDO {
|
||||
|
||||
/**
|
||||
* 编号
|
||||
*/
|
||||
@TableId
|
||||
private Long id;
|
||||
/**
|
||||
* 名字
|
||||
*/
|
||||
private String name;
|
||||
/**
|
||||
* 简介
|
||||
*/
|
||||
private String description;
|
||||
/**
|
||||
* 出生日期
|
||||
*/
|
||||
private LocalDateTime birthday;
|
||||
/**
|
||||
* 性别
|
||||
*
|
||||
* 枚举 {@link TODO system_user_sex 对应的类}
|
||||
*/
|
||||
private Integer sex;
|
||||
/**
|
||||
* 是否有效
|
||||
*
|
||||
* 枚举 {@link TODO infra_boolean_string 对应的类}
|
||||
*/
|
||||
private Boolean enabled;
|
||||
/**
|
||||
* 头像
|
||||
*/
|
||||
private String avatar;
|
||||
/**
|
||||
* 附件
|
||||
*/
|
||||
private String video;
|
||||
/**
|
||||
* 备注
|
||||
*/
|
||||
private String memo;
|
||||
|
||||
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
package cn.iocoder.yudao.module.infra.dal.mysql.demo;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
|
||||
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
|
||||
import cn.iocoder.yudao.module.infra.dal.dataobject.demo.InfraStudentDO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import cn.iocoder.yudao.module.infra.controller.admin.demo.vo.*;
|
||||
|
||||
/**
|
||||
* 学生 Mapper
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Mapper
|
||||
public interface InfraStudentMapper extends BaseMapperX<InfraStudentDO> {
|
||||
|
||||
default PageResult<InfraStudentDO> selectPage(InfraStudentPageReqVO reqVO) {
|
||||
return selectPage(reqVO, new LambdaQueryWrapperX<InfraStudentDO>()
|
||||
.likeIfPresent(InfraStudentDO::getName, reqVO.getName())
|
||||
.eqIfPresent(InfraStudentDO::getBirthday, reqVO.getBirthday())
|
||||
.eqIfPresent(InfraStudentDO::getSex, reqVO.getSex())
|
||||
.eqIfPresent(InfraStudentDO::getEnabled, reqVO.getEnabled())
|
||||
.betweenIfPresent(InfraStudentDO::getCreateTime, reqVO.getCreateTime())
|
||||
.orderByDesc(InfraStudentDO::getId));
|
||||
}
|
||||
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
package cn.iocoder.yudao.module.infra.controller.admin.demo.vo;
|
||||
|
||||
import lombok.*;
|
||||
import java.util.*;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageParam;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
|
||||
|
||||
@Schema(description = "管理后台 - 学生分页 Request VO")
|
||||
@Data
|
||||
public class InfraStudentPageReqVO extends PageParam {
|
||||
|
||||
@Schema(description = "名字", example = "芋头")
|
||||
private String name;
|
||||
|
||||
@Schema(description = "出生日期")
|
||||
private LocalDateTime birthday;
|
||||
|
||||
@Schema(description = "性别", example = "1")
|
||||
private Integer sex;
|
||||
|
||||
@Schema(description = "是否有效", example = "true")
|
||||
private Boolean enabled;
|
||||
|
||||
@Schema(description = "创建时间")
|
||||
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
|
||||
private LocalDateTime[] createTime;
|
||||
|
||||
}
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
package cn.iocoder.yudao.module.infra.controller.admin.demo.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.*;
|
||||
import java.util.*;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
import java.time.LocalDateTime;
|
||||
import cn.idev.excel.annotation.*;
|
||||
import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
|
||||
import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
|
||||
|
||||
@Schema(description = "管理后台 - 学生 Response VO")
|
||||
@Data
|
||||
@ExcelIgnoreUnannotated
|
||||
public class InfraStudentRespVO {
|
||||
|
||||
@Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
|
||||
@ExcelProperty("编号")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "名字", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋头")
|
||||
@ExcelProperty("名字")
|
||||
private String name;
|
||||
|
||||
@Schema(description = "简介", requiredMode = Schema.RequiredMode.REQUIRED, example = "我是介绍")
|
||||
@ExcelProperty("简介")
|
||||
private String description;
|
||||
|
||||
@Schema(description = "出生日期", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@ExcelProperty("出生日期")
|
||||
private LocalDateTime birthday;
|
||||
|
||||
@Schema(description = "性别", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
@ExcelProperty(value = "性别", converter = DictConvert.class)
|
||||
@DictFormat("system_user_sex") // TODO 代码优化:建议设置到对应的 DictTypeConstants 枚举类中
|
||||
private Integer sex;
|
||||
|
||||
@Schema(description = "是否有效", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
|
||||
@ExcelProperty(value = "是否有效", converter = DictConvert.class)
|
||||
@DictFormat("infra_boolean_string") // TODO 代码优化:建议设置到对应的 DictTypeConstants 枚举类中
|
||||
private Boolean enabled;
|
||||
|
||||
@Schema(description = "头像", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn/1.png")
|
||||
@ExcelProperty("头像")
|
||||
private String avatar;
|
||||
|
||||
@Schema(description = "附件", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn/1.mp4")
|
||||
@ExcelProperty("附件")
|
||||
private String video;
|
||||
|
||||
@Schema(description = "备注", requiredMode = Schema.RequiredMode.REQUIRED, example = "我是备注")
|
||||
@ExcelProperty("备注")
|
||||
private String memo;
|
||||
|
||||
@Schema(description = "创建时间")
|
||||
@ExcelProperty("创建时间")
|
||||
private LocalDateTime createTime;
|
||||
|
||||
}
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
package cn.iocoder.yudao.module.infra.controller.admin.demo.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.*;
|
||||
import java.util.*;
|
||||
import jakarta.validation.constraints.*;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
import java.time.LocalDateTime;
|
||||
import cn.iocoder.yudao.module.infra.dal.dataobject.demo.InfraStudentContactDO;
|
||||
import cn.iocoder.yudao.module.infra.dal.dataobject.demo.InfraStudentTeacherDO;
|
||||
|
||||
@Schema(description = "管理后台 - 学生新增/修改 Request VO")
|
||||
@Data
|
||||
public class InfraStudentSaveReqVO {
|
||||
|
||||
@Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "名字", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋头")
|
||||
@NotEmpty(message = "名字不能为空")
|
||||
private String name;
|
||||
|
||||
@Schema(description = "简介", requiredMode = Schema.RequiredMode.REQUIRED, example = "我是介绍")
|
||||
@NotEmpty(message = "简介不能为空")
|
||||
private String description;
|
||||
|
||||
@Schema(description = "出生日期", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@NotNull(message = "出生日期不能为空")
|
||||
private LocalDateTime birthday;
|
||||
|
||||
@Schema(description = "性别", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
@NotNull(message = "性别不能为空")
|
||||
private Integer sex;
|
||||
|
||||
@Schema(description = "是否有效", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
|
||||
@NotNull(message = "是否有效不能为空")
|
||||
private Boolean enabled;
|
||||
|
||||
@Schema(description = "头像", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn/1.png")
|
||||
@NotEmpty(message = "头像不能为空")
|
||||
private String avatar;
|
||||
|
||||
@Schema(description = "附件", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn/1.mp4")
|
||||
@NotEmpty(message = "附件不能为空")
|
||||
private String video;
|
||||
|
||||
@Schema(description = "备注", requiredMode = Schema.RequiredMode.REQUIRED, example = "我是备注")
|
||||
@NotEmpty(message = "备注不能为空")
|
||||
private String memo;
|
||||
|
||||
@Schema(description = "学生联系人列表")
|
||||
private List<InfraStudentContactDO> studentContacts;
|
||||
|
||||
@Schema(description = "学生班主任")
|
||||
private InfraStudentTeacherDO studentTeacher;
|
||||
|
||||
}
|
||||
+84
@@ -0,0 +1,84 @@
|
||||
package cn.iocoder.yudao.module.infra.service.demo;
|
||||
|
||||
import java.util.*;
|
||||
import jakarta.validation.*;
|
||||
import cn.iocoder.yudao.module.infra.controller.admin.demo.vo.*;
|
||||
import cn.iocoder.yudao.module.infra.dal.dataobject.demo.InfraStudentDO;
|
||||
import cn.iocoder.yudao.module.infra.dal.dataobject.demo.InfraStudentContactDO;
|
||||
import cn.iocoder.yudao.module.infra.dal.dataobject.demo.InfraStudentTeacherDO;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageParam;
|
||||
|
||||
/**
|
||||
* 学生 Service 接口
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
public interface InfraStudentService {
|
||||
|
||||
/**
|
||||
* 创建学生
|
||||
*
|
||||
* @param createReqVO 创建信息
|
||||
* @return 编号
|
||||
*/
|
||||
Long createStudent(@Valid InfraStudentSaveReqVO createReqVO);
|
||||
|
||||
/**
|
||||
* 更新学生
|
||||
*
|
||||
* @param updateReqVO 更新信息
|
||||
*/
|
||||
void updateStudent(@Valid InfraStudentSaveReqVO updateReqVO);
|
||||
|
||||
/**
|
||||
* 删除学生
|
||||
*
|
||||
* @param id 编号
|
||||
*/
|
||||
void deleteStudent(Long id);
|
||||
|
||||
/**
|
||||
* 批量删除学生
|
||||
*
|
||||
* @param ids 编号
|
||||
*/
|
||||
void deleteStudentListByIds(List<Long> ids);
|
||||
|
||||
/**
|
||||
* 获得学生
|
||||
*
|
||||
* @param id 编号
|
||||
* @return 学生
|
||||
*/
|
||||
InfraStudentDO getStudent(Long id);
|
||||
|
||||
/**
|
||||
* 获得学生分页
|
||||
*
|
||||
* @param pageReqVO 分页查询
|
||||
* @return 学生分页
|
||||
*/
|
||||
PageResult<InfraStudentDO> getStudentPage(InfraStudentPageReqVO pageReqVO);
|
||||
|
||||
// ==================== 子表(学生联系人) ====================
|
||||
|
||||
/**
|
||||
* 获得学生联系人列表
|
||||
*
|
||||
* @param studentId 学生编号
|
||||
* @return 学生联系人列表
|
||||
*/
|
||||
List<InfraStudentContactDO> getStudentContactListByStudentId(Long studentId);
|
||||
|
||||
// ==================== 子表(学生班主任) ====================
|
||||
|
||||
/**
|
||||
* 获得学生班主任
|
||||
*
|
||||
* @param studentId 学生编号
|
||||
* @return 学生班主任
|
||||
*/
|
||||
InfraStudentTeacherDO getStudentTeacherByStudentId(Long studentId);
|
||||
|
||||
}
|
||||
+191
@@ -0,0 +1,191 @@
|
||||
package cn.iocoder.yudao.module.infra.service.demo;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import org.springframework.stereotype.Service;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.*;
|
||||
import cn.iocoder.yudao.module.infra.controller.admin.demo.vo.*;
|
||||
import cn.iocoder.yudao.module.infra.dal.dataobject.demo.InfraStudentDO;
|
||||
import cn.iocoder.yudao.module.infra.dal.dataobject.demo.InfraStudentContactDO;
|
||||
import cn.iocoder.yudao.module.infra.dal.dataobject.demo.InfraStudentTeacherDO;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageParam;
|
||||
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
|
||||
|
||||
import cn.iocoder.yudao.module.infra.dal.mysql.demo.InfraStudentMapper;
|
||||
import cn.iocoder.yudao.module.infra.dal.mysql.demo.InfraStudentContactMapper;
|
||||
import cn.iocoder.yudao.module.infra.dal.mysql.demo.InfraStudentTeacherMapper;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
|
||||
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
|
||||
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.diffList;
|
||||
import static cn.iocoder.yudao.module.infra.enums.ErrorCodeConstants.*;
|
||||
|
||||
/**
|
||||
* 学生 Service 实现类
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Service
|
||||
@Validated
|
||||
public class InfraStudentServiceImpl implements InfraStudentService {
|
||||
|
||||
@Resource
|
||||
private InfraStudentMapper studentMapper;
|
||||
@Resource
|
||||
private InfraStudentContactMapper studentContactMapper;
|
||||
@Resource
|
||||
private InfraStudentTeacherMapper studentTeacherMapper;
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Long createStudent(InfraStudentSaveReqVO createReqVO) {
|
||||
// 插入
|
||||
InfraStudentDO student = BeanUtils.toBean(createReqVO, InfraStudentDO.class);
|
||||
studentMapper.insert(student);
|
||||
|
||||
|
||||
// 插入子表
|
||||
createStudentContactList(student.getId(), createReqVO.getStudentContacts());
|
||||
createStudentTeacher(student.getId(), createReqVO.getStudentTeacher());
|
||||
// 返回
|
||||
return student.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void updateStudent(InfraStudentSaveReqVO updateReqVO) {
|
||||
// 校验存在
|
||||
validateStudentExists(updateReqVO.getId());
|
||||
// 更新
|
||||
InfraStudentDO updateObj = BeanUtils.toBean(updateReqVO, InfraStudentDO.class);
|
||||
studentMapper.updateById(updateObj);
|
||||
|
||||
// 更新子表
|
||||
updateStudentContactList(updateReqVO.getId(), updateReqVO.getStudentContacts());
|
||||
updateStudentTeacher(updateReqVO.getId(), updateReqVO.getStudentTeacher());
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void deleteStudent(Long id) {
|
||||
// 校验存在
|
||||
validateStudentExists(id);
|
||||
// 删除
|
||||
studentMapper.deleteById(id);
|
||||
|
||||
// 删除子表
|
||||
deleteStudentContactByStudentId(id);
|
||||
deleteStudentTeacherByStudentId(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void deleteStudentListByIds(List<Long> ids) {
|
||||
// 删除
|
||||
studentMapper.deleteByIds(ids);
|
||||
|
||||
// 删除子表
|
||||
deleteStudentContactByStudentIds(ids);
|
||||
deleteStudentTeacherByStudentIds(ids);
|
||||
}
|
||||
|
||||
|
||||
private void validateStudentExists(Long id) {
|
||||
if (studentMapper.selectById(id) == null) {
|
||||
throw exception(STUDENT_NOT_EXISTS);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public InfraStudentDO getStudent(Long id) {
|
||||
return studentMapper.selectById(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PageResult<InfraStudentDO> getStudentPage(InfraStudentPageReqVO pageReqVO) {
|
||||
return studentMapper.selectPage(pageReqVO);
|
||||
}
|
||||
|
||||
// ==================== 子表(学生联系人) ====================
|
||||
|
||||
@Override
|
||||
public List<InfraStudentContactDO> getStudentContactListByStudentId(Long studentId) {
|
||||
return studentContactMapper.selectListByStudentId(studentId);
|
||||
}
|
||||
|
||||
private void createStudentContactList(Long studentId, List<InfraStudentContactDO> list) {
|
||||
if (CollUtil.isEmpty(list)) {
|
||||
return;
|
||||
}
|
||||
list.forEach(o -> o.setStudentId(studentId).clean());
|
||||
studentContactMapper.insertBatch(list);
|
||||
}
|
||||
|
||||
private void updateStudentContactList(Long studentId, List<InfraStudentContactDO> list) {
|
||||
list.forEach(o -> o.setStudentId(studentId).clean());
|
||||
List<InfraStudentContactDO> oldList = studentContactMapper.selectListByStudentId(studentId);
|
||||
List<List<InfraStudentContactDO>> diffList = diffList(oldList, list, (oldVal, newVal) -> {
|
||||
boolean same = ObjectUtil.equal(oldVal.getId(), newVal.getId());
|
||||
if (same) {
|
||||
newVal.setId(oldVal.getId()).clean(); // 解决更新情况下:updateTime 不更新
|
||||
}
|
||||
return same;
|
||||
});
|
||||
|
||||
// 第二步,批量添加、修改、删除
|
||||
if (CollUtil.isNotEmpty(diffList.get(0))) {
|
||||
studentContactMapper.insertBatch(diffList.get(0));
|
||||
}
|
||||
if (CollUtil.isNotEmpty(diffList.get(1))) {
|
||||
studentContactMapper.updateBatch(diffList.get(1));
|
||||
}
|
||||
if (CollUtil.isNotEmpty(diffList.get(2))) {
|
||||
studentContactMapper.deleteByIds(convertList(diffList.get(2), InfraStudentContactDO::getId));
|
||||
}
|
||||
}
|
||||
|
||||
private void deleteStudentContactByStudentId(Long studentId) {
|
||||
studentContactMapper.deleteByStudentId(studentId);
|
||||
}
|
||||
|
||||
private void deleteStudentContactByStudentIds(List<Long> studentIds) {
|
||||
studentContactMapper.deleteByStudentIds(studentIds);
|
||||
}
|
||||
|
||||
// ==================== 子表(学生班主任) ====================
|
||||
|
||||
@Override
|
||||
public InfraStudentTeacherDO getStudentTeacherByStudentId(Long studentId) {
|
||||
return studentTeacherMapper.selectByStudentId(studentId);
|
||||
}
|
||||
|
||||
private void createStudentTeacher(Long studentId, InfraStudentTeacherDO studentTeacher) {
|
||||
if (studentTeacher == null) {
|
||||
return;
|
||||
}
|
||||
studentTeacher.setStudentId(studentId);
|
||||
studentTeacherMapper.insert(studentTeacher);
|
||||
}
|
||||
|
||||
private void updateStudentTeacher(Long studentId, InfraStudentTeacherDO studentTeacher) {
|
||||
if (studentTeacher == null) {
|
||||
return;
|
||||
}
|
||||
studentTeacher.setStudentId(studentId).clean();// 解决更新情况下:updateTime 不更新
|
||||
studentTeacherMapper.insertOrUpdate(studentTeacher);
|
||||
}
|
||||
|
||||
private void deleteStudentTeacherByStudentId(Long studentId) {
|
||||
studentTeacherMapper.deleteByStudentId(studentId);
|
||||
}
|
||||
|
||||
private void deleteStudentTeacherByStudentIds(List<Long> studentIds) {
|
||||
studentTeacherMapper.deleteByStudentIds(studentIds);
|
||||
}
|
||||
|
||||
}
|
||||
+145
@@ -0,0 +1,145 @@
|
||||
package cn.iocoder.yudao.module.infra.service.demo;
|
||||
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
|
||||
import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
|
||||
|
||||
import cn.iocoder.yudao.module.infra.controller.admin.demo.vo.*;
|
||||
import cn.iocoder.yudao.module.infra.dal.dataobject.demo.InfraStudentDO;
|
||||
import cn.iocoder.yudao.module.infra.dal.mysql.demo.InfraStudentMapper;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import java.util.*;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
import static cn.hutool.core.util.RandomUtil.*;
|
||||
import static cn.iocoder.yudao.module.infra.enums.ErrorCodeConstants.*;
|
||||
import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.*;
|
||||
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*;
|
||||
import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.*;
|
||||
import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.*;
|
||||
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.*;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
/**
|
||||
* {@link InfraStudentServiceImpl} 的单元测试类
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Import(InfraStudentServiceImpl.class)
|
||||
public class InfraStudentServiceImplTest extends BaseDbUnitTest {
|
||||
|
||||
@Resource
|
||||
private InfraStudentServiceImpl studentService;
|
||||
|
||||
@Resource
|
||||
private InfraStudentMapper studentMapper;
|
||||
|
||||
@Test
|
||||
public void testCreateStudent_success() {
|
||||
// 准备参数
|
||||
InfraStudentSaveReqVO createReqVO = randomPojo(InfraStudentSaveReqVO.class).setId(null);
|
||||
|
||||
// 调用
|
||||
Long studentId = studentService.createStudent(createReqVO);
|
||||
// 断言
|
||||
assertNotNull(studentId);
|
||||
// 校验记录的属性是否正确
|
||||
InfraStudentDO student = studentMapper.selectById(studentId);
|
||||
assertPojoEquals(createReqVO, student, "id");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateStudent_success() {
|
||||
// mock 数据
|
||||
InfraStudentDO dbStudent = randomPojo(InfraStudentDO.class);
|
||||
studentMapper.insert(dbStudent);// @Sql: 先插入出一条存在的数据
|
||||
// 准备参数
|
||||
InfraStudentSaveReqVO updateReqVO = randomPojo(InfraStudentSaveReqVO.class, o -> {
|
||||
o.setId(dbStudent.getId()); // 设置更新的 ID
|
||||
});
|
||||
|
||||
// 调用
|
||||
studentService.updateStudent(updateReqVO);
|
||||
// 校验是否更新正确
|
||||
InfraStudentDO student = studentMapper.selectById(updateReqVO.getId()); // 获取最新的
|
||||
assertPojoEquals(updateReqVO, student);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateStudent_notExists() {
|
||||
// 准备参数
|
||||
InfraStudentSaveReqVO updateReqVO = randomPojo(InfraStudentSaveReqVO.class);
|
||||
|
||||
// 调用, 并断言异常
|
||||
assertServiceException(() -> studentService.updateStudent(updateReqVO), STUDENT_NOT_EXISTS);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeleteStudent_success() {
|
||||
// mock 数据
|
||||
InfraStudentDO dbStudent = randomPojo(InfraStudentDO.class);
|
||||
studentMapper.insert(dbStudent);// @Sql: 先插入出一条存在的数据
|
||||
// 准备参数
|
||||
Long id = dbStudent.getId();
|
||||
|
||||
// 调用
|
||||
studentService.deleteStudent(id);
|
||||
// 校验数据不存在了
|
||||
assertNull(studentMapper.selectById(id));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeleteStudent_notExists() {
|
||||
// 准备参数
|
||||
Long id = randomLongId();
|
||||
|
||||
// 调用, 并断言异常
|
||||
assertServiceException(() -> studentService.deleteStudent(id), STUDENT_NOT_EXISTS);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Disabled // TODO 请修改 null 为需要的值,然后删除 @Disabled 注解
|
||||
public void testGetStudentPage() {
|
||||
// mock 数据
|
||||
InfraStudentDO dbStudent = randomPojo(InfraStudentDO.class, o -> { // 等会查询到
|
||||
o.setName(null);
|
||||
o.setBirthday(null);
|
||||
o.setSex(null);
|
||||
o.setEnabled(null);
|
||||
o.setCreateTime(null);
|
||||
});
|
||||
studentMapper.insert(dbStudent);
|
||||
// 测试 name 不匹配
|
||||
studentMapper.insert(cloneIgnoreId(dbStudent, o -> o.setName(null)));
|
||||
// 测试 birthday 不匹配
|
||||
studentMapper.insert(cloneIgnoreId(dbStudent, o -> o.setBirthday(null)));
|
||||
// 测试 sex 不匹配
|
||||
studentMapper.insert(cloneIgnoreId(dbStudent, o -> o.setSex(null)));
|
||||
// 测试 enabled 不匹配
|
||||
studentMapper.insert(cloneIgnoreId(dbStudent, o -> o.setEnabled(null)));
|
||||
// 测试 createTime 不匹配
|
||||
studentMapper.insert(cloneIgnoreId(dbStudent, o -> o.setCreateTime(null)));
|
||||
// 准备参数
|
||||
InfraStudentPageReqVO reqVO = new InfraStudentPageReqVO();
|
||||
reqVO.setName(null);
|
||||
reqVO.setBirthday(null);
|
||||
reqVO.setSex(null);
|
||||
reqVO.setEnabled(null);
|
||||
reqVO.setCreateTime(buildBetweenTime(2023, 2, 1, 2023, 2, 28));
|
||||
|
||||
// 调用
|
||||
PageResult<InfraStudentDO> pageResult = studentService.getStudentPage(reqVO);
|
||||
// 断言
|
||||
assertEquals(1, pageResult.getTotal());
|
||||
assertEquals(1, pageResult.getList().size());
|
||||
assertPojoEquals(dbStudent, pageResult.getList().get(0));
|
||||
}
|
||||
|
||||
}
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
package cn.iocoder.yudao.module.infra.dal.dataobject.demo;
|
||||
|
||||
import lombok.*;
|
||||
import java.util.*;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.LocalDateTime;
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
|
||||
|
||||
/**
|
||||
* 学生班主任 DO
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@TableName("infra_student_teacher")
|
||||
@KeySequence("infra_student_teacher_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@ToString(callSuper = true)
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class InfraStudentTeacherDO extends BaseDO {
|
||||
|
||||
/**
|
||||
* 编号
|
||||
*/
|
||||
@TableId
|
||||
private Long id;
|
||||
/**
|
||||
* 学生编号
|
||||
*/
|
||||
private Long studentId;
|
||||
/**
|
||||
* 名字
|
||||
*/
|
||||
private String name;
|
||||
/**
|
||||
* 简介
|
||||
*/
|
||||
private String description;
|
||||
/**
|
||||
* 出生日期
|
||||
*/
|
||||
private LocalDateTime birthday;
|
||||
/**
|
||||
* 性别
|
||||
*
|
||||
* 枚举 {@link TODO system_user_sex 对应的类}
|
||||
*/
|
||||
private Integer sex;
|
||||
/**
|
||||
* 是否有效
|
||||
*
|
||||
* 枚举 {@link TODO infra_boolean_string 对应的类}
|
||||
*/
|
||||
private Boolean enabled;
|
||||
/**
|
||||
* 头像
|
||||
*/
|
||||
private String avatar;
|
||||
/**
|
||||
* 附件
|
||||
*/
|
||||
private String video;
|
||||
/**
|
||||
* 备注
|
||||
*/
|
||||
private String memo;
|
||||
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
package cn.iocoder.yudao.module.infra.dal.mysql.demo;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageParam;
|
||||
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
|
||||
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
|
||||
import cn.iocoder.yudao.module.infra.dal.dataobject.demo.InfraStudentTeacherDO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
/**
|
||||
* 学生班主任 Mapper
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Mapper
|
||||
public interface InfraStudentTeacherMapper extends BaseMapperX<InfraStudentTeacherDO> {
|
||||
|
||||
default InfraStudentTeacherDO selectByStudentId(Long studentId) {
|
||||
return selectOne(InfraStudentTeacherDO::getStudentId, studentId);
|
||||
}
|
||||
|
||||
default int deleteByStudentId(Long studentId) {
|
||||
return delete(InfraStudentTeacherDO::getStudentId, studentId);
|
||||
}
|
||||
|
||||
default int deleteByStudentIds(List<Long> studentIds) {
|
||||
return deleteBatch(InfraStudentTeacherDO::getStudentId, studentIds);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
-- 将该建表 SQL 语句,添加到 yudao-module-infra-biz 模块的 test/resources/sql/create_tables.sql 文件里
|
||||
CREATE TABLE IF NOT EXISTS "infra_student" (
|
||||
"id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
|
||||
"name" varchar NOT NULL,
|
||||
"description" varchar NOT NULL,
|
||||
"birthday" varchar NOT NULL,
|
||||
"sex" int NOT NULL,
|
||||
"enabled" bit NOT NULL,
|
||||
"avatar" varchar NOT NULL,
|
||||
"video" varchar NOT NULL,
|
||||
"memo" varchar NOT NULL,
|
||||
"create_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY ("id")
|
||||
) COMMENT '学生表';
|
||||
|
||||
-- 将该删表 SQL 语句,添加到 yudao-module-infra-biz 模块的 test/resources/sql/clean.sql 文件里
|
||||
DELETE FROM "infra_student";
|
||||
@@ -0,0 +1,54 @@
|
||||
-- 菜单 SQL
|
||||
INSERT INTO system_menu(
|
||||
name, permission, type, sort, parent_id,
|
||||
path, icon, component, status, component_name
|
||||
)
|
||||
VALUES (
|
||||
'学生管理', '', 2, 0, 888,
|
||||
'student', '', 'infra/demo/index', 0, 'InfraStudent'
|
||||
);
|
||||
|
||||
-- 按钮父菜单ID
|
||||
SELECT @parentId := LAST_INSERT_ID();
|
||||
|
||||
-- 按钮 SQL
|
||||
INSERT INTO system_menu(
|
||||
name, permission, type, sort, parent_id,
|
||||
path, icon, component, status
|
||||
)
|
||||
VALUES (
|
||||
'学生查询', 'infra:student:query', 3, 1, @parentId,
|
||||
'', '', '', 0
|
||||
);
|
||||
INSERT INTO system_menu(
|
||||
name, permission, type, sort, parent_id,
|
||||
path, icon, component, status
|
||||
)
|
||||
VALUES (
|
||||
'学生创建', 'infra:student:create', 3, 2, @parentId,
|
||||
'', '', '', 0
|
||||
);
|
||||
INSERT INTO system_menu(
|
||||
name, permission, type, sort, parent_id,
|
||||
path, icon, component, status
|
||||
)
|
||||
VALUES (
|
||||
'学生更新', 'infra:student:update', 3, 3, @parentId,
|
||||
'', '', '', 0
|
||||
);
|
||||
INSERT INTO system_menu(
|
||||
name, permission, type, sort, parent_id,
|
||||
path, icon, component, status
|
||||
)
|
||||
VALUES (
|
||||
'学生删除', 'infra:student:delete', 3, 4, @parentId,
|
||||
'', '', '', 0
|
||||
);
|
||||
INSERT INTO system_menu(
|
||||
name, permission, type, sort, parent_id,
|
||||
path, icon, component, status
|
||||
)
|
||||
VALUES (
|
||||
'学生导出', 'infra:student:export', 3, 5, @parentId,
|
||||
'', '', '', 0
|
||||
);
|
||||
@@ -0,0 +1,87 @@
|
||||
import type { PageParam, PageResult } from '@/http/types'
|
||||
import { http } from '@/http/http'
|
||||
|
||||
/** 学生联系人信息 */
|
||||
export interface StudentContact {
|
||||
id?: number
|
||||
studentId?: number
|
||||
name: string
|
||||
description: string
|
||||
birthday: number
|
||||
sex: number
|
||||
enabled: boolean
|
||||
avatar: string
|
||||
video?: string
|
||||
memo: string
|
||||
createTime?: number
|
||||
}
|
||||
|
||||
/** 学生班主任信息 */
|
||||
export interface StudentTeacher {
|
||||
id?: number
|
||||
studentId?: number
|
||||
name: string
|
||||
description: string
|
||||
birthday: number
|
||||
sex: number
|
||||
enabled: boolean
|
||||
avatar: string
|
||||
video?: string
|
||||
memo: string
|
||||
createTime?: number
|
||||
}
|
||||
|
||||
/** 学生信息 */
|
||||
export interface Student {
|
||||
id?: number
|
||||
name: string
|
||||
description: string
|
||||
birthday: number
|
||||
sex: number
|
||||
enabled: boolean
|
||||
avatar: string
|
||||
video: string
|
||||
memo: string
|
||||
createTime?: number
|
||||
studentContacts?: StudentContact[]
|
||||
studentTeacher?: StudentTeacher | null
|
||||
}
|
||||
|
||||
/** 获取学生分页列表 */
|
||||
export function getStudentPage(params: PageParam) {
|
||||
return http.get<PageResult<Student>>('/infra/student/page', params)
|
||||
}
|
||||
|
||||
/** 获取学生详情 */
|
||||
export function getStudent(id: number) {
|
||||
return http.get<Student>(`/infra/student/get?id=${id}`)
|
||||
}
|
||||
|
||||
/** 创建学生 */
|
||||
export function createStudent(data: Student) {
|
||||
return http.post<number>('/infra/student/create', data)
|
||||
}
|
||||
|
||||
/** 更新学生 */
|
||||
export function updateStudent(data: Student) {
|
||||
return http.put<boolean>('/infra/student/update', data)
|
||||
}
|
||||
|
||||
/** 删除学生 */
|
||||
export function deleteStudent(id: number) {
|
||||
return http.delete<boolean>(`/infra/student/delete?id=${id}`)
|
||||
}
|
||||
|
||||
// ==================== 子表:学生联系人 ====================
|
||||
|
||||
/** 获取学生联系人列表 */
|
||||
export function getStudentContactListByStudentId(studentId: number) {
|
||||
return http.get<StudentContact[]>(`/infra/student/student-contact/list-by-student-id?studentId=${studentId}`)
|
||||
}
|
||||
|
||||
// ==================== 子表:学生班主任 ====================
|
||||
|
||||
/** 获取学生班主任 */
|
||||
export function getStudentTeacherByStudentId(studentId: number) {
|
||||
return http.get<StudentTeacher | null>(`/infra/student/student-teacher/get-by-student-id?studentId=${studentId}`)
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user