refactor: 代码生成支持js,已知问题修复
This commit is contained in:
@@ -7,7 +7,7 @@ import org.springframework.stereotype.Component;
|
|||||||
/**
|
/**
|
||||||
* 验证码 属性配置
|
* 验证码 属性配置
|
||||||
*
|
*
|
||||||
* @author haoxr
|
* @author Ray.Hao
|
||||||
* @since 2023/11/24
|
* @since 2023/11/24
|
||||||
*/
|
*/
|
||||||
@Component
|
@Component
|
||||||
|
|||||||
@@ -10,27 +10,21 @@ import com.youlai.boot.platform.ai.model.query.AiAssistantQuery;
|
|||||||
import com.youlai.boot.platform.ai.model.vo.AiAssistantRecordVO;
|
import com.youlai.boot.platform.ai.model.vo.AiAssistantRecordVO;
|
||||||
import com.youlai.boot.platform.ai.service.AiAssistantRecordService;
|
import com.youlai.boot.platform.ai.service.AiAssistantRecordService;
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import io.swagger.v3.oas.annotations.Parameter;
|
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* AI 助手控制器
|
* AI 助手控制器
|
||||||
* <p>
|
* <p>
|
||||||
* 负责 AI 命令的解析、执行、记录管理及回滚操作,
|
* 负责 AI 命令的解析、执行与记录查询。
|
||||||
* 表示一次 AI 助手完整的指令生命周期。
|
|
||||||
*
|
*
|
||||||
* @author Ray.Hao
|
* @author Ray.Hao
|
||||||
* @since 3.0.0
|
* @since 3.0.0
|
||||||
*/
|
*/
|
||||||
@Tag(name = "AI 助手接口")
|
@Tag(name = "13.AI 助手接口")
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/api/v1/ai/assistant")
|
@RequestMapping("/api/v1/ai/assistant")
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
@@ -81,29 +75,5 @@ public class AiAssistantController {
|
|||||||
return PageResult.success(page);
|
return PageResult.success(page);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Operation(summary = "删除 AI 命令记录")
|
// 记录类接口按需扩展,当前开放 parse/execute/records
|
||||||
@DeleteMapping("/records/{ids}")
|
|
||||||
public Result<Void> deleteRecords(
|
|
||||||
@Parameter(description = "记录ID,多个以英文逗号(,)分割")
|
|
||||||
@PathVariable String ids
|
|
||||||
) {
|
|
||||||
List<Long> idList = Arrays.stream(ids.split(","))
|
|
||||||
.filter(s -> s != null && !s.isBlank())
|
|
||||||
.map(String::trim)
|
|
||||||
.map(Long::valueOf)
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
|
|
||||||
boolean removed = aiAssistantRecordService.deleteRecords(idList);
|
|
||||||
return Result.judge(removed);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Operation(summary = "撤销命令执行")
|
|
||||||
@PostMapping("/records/{recordId}/rollback")
|
|
||||||
public Result<Void> rollbackCommand(
|
|
||||||
@Parameter(description = "记录ID")
|
|
||||||
@PathVariable String recordId
|
|
||||||
) {
|
|
||||||
aiAssistantRecordService.rollbackCommand(recordId);
|
|
||||||
return Result.success();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -49,18 +49,4 @@ public interface AiAssistantRecordService extends IService<AiAssistantRecord> {
|
|||||||
*/
|
*/
|
||||||
IPage<AiAssistantRecordVO> getRecordPage(AiAssistantQuery queryParams);
|
IPage<AiAssistantRecordVO> getRecordPage(AiAssistantQuery queryParams);
|
||||||
|
|
||||||
/**
|
|
||||||
* 删除 AI 助手行为记录。
|
|
||||||
*
|
|
||||||
* @param ids 记录ID列表
|
|
||||||
* @return 是否删除成功
|
|
||||||
*/
|
|
||||||
boolean deleteRecords(List<Long> ids);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 撤销命令执行
|
|
||||||
*
|
|
||||||
* @param logId 记录ID
|
|
||||||
*/
|
|
||||||
void rollbackCommand(String logId);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -299,26 +299,4 @@ public class AiAssistantRecordServiceImpl
|
|||||||
return this.baseMapper.getRecordPage(page, queryParams);
|
return this.baseMapper.getRecordPage(page, queryParams);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean deleteRecords(List<Long> ids) {
|
|
||||||
if (ids == null || ids.isEmpty()) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return this.removeByIds(ids);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void rollbackCommand(String logId) {
|
|
||||||
AiAssistantRecord commandRecord = this.getById(logId);
|
|
||||||
if (commandRecord == null) {
|
|
||||||
throw new RuntimeException("命令记录不存在");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (commandRecord.getExecuteStatus() == null || commandRecord.getExecuteStatus() != 1) {
|
|
||||||
throw new RuntimeException("只能撤销成功执行的命令");
|
|
||||||
}
|
|
||||||
|
|
||||||
log.info("撤销命令执行: logId={}, function={}", logId, commandRecord.getFunctionName());
|
|
||||||
throw new UnsupportedOperationException("回滚功能尚未实现");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -83,8 +83,9 @@ public class CodegenController {
|
|||||||
@GetMapping("/{tableName}/preview")
|
@GetMapping("/{tableName}/preview")
|
||||||
@Log(value = "预览生成代码", module = LogModuleEnum.OTHER)
|
@Log(value = "预览生成代码", module = LogModuleEnum.OTHER)
|
||||||
public Result<List<CodegenPreviewVO>> getTablePreviewData(@PathVariable String tableName,
|
public Result<List<CodegenPreviewVO>> getTablePreviewData(@PathVariable String tableName,
|
||||||
@RequestParam(value = "pageType", required = false, defaultValue = "classic") String pageType) {
|
@RequestParam(value = "pageType", required = false, defaultValue = "classic") String pageType,
|
||||||
List<CodegenPreviewVO> list = codegenService.getCodegenPreviewData(tableName, pageType);
|
@RequestParam(value = "type", required = false, defaultValue = "ts") String type) {
|
||||||
|
List<CodegenPreviewVO> list = codegenService.getCodegenPreviewData(tableName, pageType, type);
|
||||||
return Result.success(list);
|
return Result.success(list);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -92,9 +93,10 @@ public class CodegenController {
|
|||||||
@GetMapping("/{tableName}/download")
|
@GetMapping("/{tableName}/download")
|
||||||
@Log(value = "下载代码", module = LogModuleEnum.OTHER)
|
@Log(value = "下载代码", module = LogModuleEnum.OTHER)
|
||||||
public void downloadZip(HttpServletResponse response, @PathVariable String tableName,
|
public void downloadZip(HttpServletResponse response, @PathVariable String tableName,
|
||||||
@RequestParam(value = "pageType", required = false, defaultValue = "classic") String pageType) {
|
@RequestParam(value = "pageType", required = false, defaultValue = "classic") String pageType,
|
||||||
|
@RequestParam(value = "type", required = false, defaultValue = "ts") String type) {
|
||||||
String[] tableNames = tableName.split(",");
|
String[] tableNames = tableName.split(",");
|
||||||
byte[] data = codegenService.downloadCode(tableNames, pageType);
|
byte[] data = codegenService.downloadCode(tableNames, pageType, type);
|
||||||
|
|
||||||
response.reset();
|
response.reset();
|
||||||
response.setHeader("Content-Disposition", "attachment; filename=" + URLEncoder.encode(codegenProperties.getDownloadFileName(), StandardCharsets.UTF_8));
|
response.setHeader("Content-Disposition", "attachment; filename=" + URLEncoder.encode(codegenProperties.getDownloadFileName(), StandardCharsets.UTF_8));
|
||||||
|
|||||||
@@ -29,12 +29,12 @@ public interface CodegenService {
|
|||||||
* @param tableName 表名
|
* @param tableName 表名
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
List<CodegenPreviewVO> getCodegenPreviewData(String tableName, String pageType);
|
List<CodegenPreviewVO> getCodegenPreviewData(String tableName, String pageType, String type);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 下载代码
|
* 下载代码
|
||||||
* @param tableNames 表名
|
* @param tableNames 表名
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
byte[] downloadCode(String[] tableNames, String pageType);
|
byte[] downloadCode(String[] tableNames, String pageType, String type);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -70,6 +70,52 @@ public class CodegenServiceImpl implements CodegenService {
|
|||||||
return databaseMapper.getTablePage(page, queryParams);
|
return databaseMapper.getTablePage(page, queryParams);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析前端模板路径
|
||||||
|
*
|
||||||
|
* @param templateName 模板标识
|
||||||
|
* @param templateConfig 模板配置
|
||||||
|
* @param frontendType 前端类型
|
||||||
|
* @return 模板路径
|
||||||
|
*/
|
||||||
|
private String resolveFrontendTemplatePath(String templateName,
|
||||||
|
CodegenProperties.TemplateConfig templateConfig,
|
||||||
|
String frontendType) {
|
||||||
|
if (!"js".equals(frontendType)) {
|
||||||
|
return templateConfig.getTemplatePath();
|
||||||
|
}
|
||||||
|
if ("API".equals(templateName)) {
|
||||||
|
return "codegen/api.js.vm";
|
||||||
|
}
|
||||||
|
if ("VIEW".equals(templateName)) {
|
||||||
|
return "codegen/index.js.vue.vm";
|
||||||
|
}
|
||||||
|
if ("API_TYPES".equals(templateName)) {
|
||||||
|
return "codegen/api-types.js.vm";
|
||||||
|
}
|
||||||
|
return templateConfig.getTemplatePath();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析前端文件后缀
|
||||||
|
*
|
||||||
|
* @param templateName 模板标识
|
||||||
|
* @param templateConfig 模板配置
|
||||||
|
* @param frontendType 前端类型
|
||||||
|
* @return 文件后缀
|
||||||
|
*/
|
||||||
|
private String resolveFrontendExtension(String templateName,
|
||||||
|
CodegenProperties.TemplateConfig templateConfig,
|
||||||
|
String frontendType) {
|
||||||
|
if (!"js".equals(frontendType)) {
|
||||||
|
return templateConfig.getExtension();
|
||||||
|
}
|
||||||
|
if ("API".equals(templateName) || "API_TYPES".equals(templateName)) {
|
||||||
|
return ".js";
|
||||||
|
}
|
||||||
|
return templateConfig.getExtension();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取预览生成代码
|
* 获取预览生成代码
|
||||||
*
|
*
|
||||||
@@ -77,7 +123,7 @@ public class CodegenServiceImpl implements CodegenService {
|
|||||||
* @return 预览数据
|
* @return 预览数据
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public List<CodegenPreviewVO> getCodegenPreviewData(String tableName, String pageType) {
|
public List<CodegenPreviewVO> getCodegenPreviewData(String tableName, String pageType, String type) {
|
||||||
|
|
||||||
List<CodegenPreviewVO> list = new ArrayList<>();
|
List<CodegenPreviewVO> list = new ArrayList<>();
|
||||||
|
|
||||||
@@ -99,18 +145,25 @@ public class CodegenServiceImpl implements CodegenService {
|
|||||||
|
|
||||||
// 遍历模板配置
|
// 遍历模板配置
|
||||||
Map<String, CodegenProperties.TemplateConfig> templateConfigs = codegenProperties.getTemplateConfigs();
|
Map<String, CodegenProperties.TemplateConfig> templateConfigs = codegenProperties.getTemplateConfigs();
|
||||||
|
String frontendType = StrUtil.blankToDefault(type, "ts").toLowerCase();
|
||||||
for (Map.Entry<String, CodegenProperties.TemplateConfig> templateConfigEntry : templateConfigs.entrySet()) {
|
for (Map.Entry<String, CodegenProperties.TemplateConfig> templateConfigEntry : templateConfigs.entrySet()) {
|
||||||
CodegenPreviewVO previewVo = new CodegenPreviewVO();
|
CodegenPreviewVO previewVo = new CodegenPreviewVO();
|
||||||
|
|
||||||
CodegenProperties.TemplateConfig templateConfig = templateConfigEntry.getValue();
|
CodegenProperties.TemplateConfig templateConfig = templateConfigEntry.getValue();
|
||||||
|
|
||||||
|
String templateName = templateConfigEntry.getKey();
|
||||||
|
if ("js".equals(frontendType) && "API_TYPES".equals(templateName)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
String effectiveTemplatePath = resolveFrontendTemplatePath(templateName, templateConfig, frontendType);
|
||||||
|
String extension = resolveFrontendExtension(templateName, templateConfig, frontendType);
|
||||||
|
|
||||||
/* 1. 生成文件名 UserController */
|
/* 1. 生成文件名 UserController */
|
||||||
// User Role Menu Dept
|
// User Role Menu Dept
|
||||||
String entityName = genTable.getEntityName();
|
String entityName = genTable.getEntityName();
|
||||||
// Controller Service Mapper Entity
|
// Controller Service Mapper Entity
|
||||||
String templateName = templateConfigEntry.getKey();
|
|
||||||
// .java .ts .vue
|
// .java .ts .vue
|
||||||
String extension = templateConfig.getExtension();
|
|
||||||
|
|
||||||
// 文件名 UserController.java
|
// 文件名 UserController.java
|
||||||
String fileName = getFileName(entityName, templateName, extension);
|
String fileName = getFileName(entityName, templateName, extension);
|
||||||
@@ -131,7 +184,13 @@ public class CodegenServiceImpl implements CodegenService {
|
|||||||
// 将模板文件中的变量替换为具体的值 生成代码内容
|
// 将模板文件中的变量替换为具体的值 生成代码内容
|
||||||
// 优先使用保存的 ui,没有则使用请求参数
|
// 优先使用保存的 ui,没有则使用请求参数
|
||||||
String finalType = StrUtil.blankToDefault(genTable.getPageType(), pageType);
|
String finalType = StrUtil.blankToDefault(genTable.getPageType(), pageType);
|
||||||
String content = getCodeContent(templateConfig, genTable, fieldConfigs, finalType);
|
String content = getCodeContent(
|
||||||
|
effectiveTemplatePath,
|
||||||
|
templateConfig.getSubpackageName(),
|
||||||
|
genTable,
|
||||||
|
fieldConfigs,
|
||||||
|
finalType
|
||||||
|
);
|
||||||
previewVo.setContent(content);
|
previewVo.setContent(content);
|
||||||
|
|
||||||
list.add(previewVo);
|
list.add(previewVo);
|
||||||
@@ -232,7 +291,11 @@ public class CodegenServiceImpl implements CodegenService {
|
|||||||
* @param pageType 前端页面类型
|
* @param pageType 前端页面类型
|
||||||
* @return 渲染后的代码内容
|
* @return 渲染后的代码内容
|
||||||
*/
|
*/
|
||||||
private String getCodeContent(CodegenProperties.TemplateConfig templateConfig, GenTable genTable, List<GenTableColumn> fieldConfigs, String pageType) {
|
private String getCodeContent(String templatePath,
|
||||||
|
String subpackageName,
|
||||||
|
GenTable genTable,
|
||||||
|
List<GenTableColumn> fieldConfigs,
|
||||||
|
String pageType) {
|
||||||
|
|
||||||
Map<String, Object> bindMap = new HashMap<>();
|
Map<String, Object> bindMap = new HashMap<>();
|
||||||
|
|
||||||
@@ -240,7 +303,7 @@ public class CodegenServiceImpl implements CodegenService {
|
|||||||
|
|
||||||
bindMap.put("packageName", genTable.getPackageName());
|
bindMap.put("packageName", genTable.getPackageName());
|
||||||
bindMap.put("moduleName", genTable.getModuleName());
|
bindMap.put("moduleName", genTable.getModuleName());
|
||||||
bindMap.put("subpackageName", templateConfig.getSubpackageName());
|
bindMap.put("subpackageName", subpackageName);
|
||||||
bindMap.put("date", DateUtil.format(new Date(), "yyyy-MM-dd HH:mm"));
|
bindMap.put("date", DateUtil.format(new Date(), "yyyy-MM-dd HH:mm"));
|
||||||
bindMap.put("entityName", entityName);
|
bindMap.put("entityName", entityName);
|
||||||
bindMap.put("tableName", genTable.getTableName());
|
bindMap.put("tableName", genTable.getTableName());
|
||||||
@@ -282,9 +345,13 @@ public class CodegenServiceImpl implements CodegenService {
|
|||||||
|
|
||||||
TemplateEngine templateEngine = TemplateUtil.createEngine(new TemplateConfig("templates", TemplateConfig.ResourceMode.CLASSPATH));
|
TemplateEngine templateEngine = TemplateUtil.createEngine(new TemplateConfig("templates", TemplateConfig.ResourceMode.CLASSPATH));
|
||||||
// 根据 ui 选择不同的前端页面模板:默认 index.vue.vm;封装版使用 index.curd.vue.vm
|
// 根据 ui 选择不同的前端页面模板:默认 index.vue.vm;封装版使用 index.curd.vue.vm
|
||||||
String path = templateConfig.getTemplatePath();
|
String path = templatePath;
|
||||||
if ("curd".equalsIgnoreCase(pageType) && path.endsWith("index.vue.vm")) {
|
if ("curd".equalsIgnoreCase(pageType)) {
|
||||||
path = path.replace("index.vue.vm", "index.curd.vue.vm");
|
if (path.endsWith("index.js.vue.vm")) {
|
||||||
|
path = path.replace("index.js.vue.vm", "index.curd.js.vue.vm");
|
||||||
|
} else if (path.endsWith("index.vue.vm")) {
|
||||||
|
path = path.replace("index.vue.vm", "index.curd.vue.vm");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Template template = templateEngine.getTemplate(path);
|
Template template = templateEngine.getTemplate(path);
|
||||||
|
|
||||||
@@ -299,13 +366,13 @@ public class CodegenServiceImpl implements CodegenService {
|
|||||||
* @return zip 压缩文件字节数组
|
* @return zip 压缩文件字节数组
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public byte[] downloadCode(String[] tableNames, String ui) {
|
public byte[] downloadCode(String[] tableNames, String ui, String type) {
|
||||||
try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||||
ZipOutputStream zip = new ZipOutputStream(outputStream)) {
|
ZipOutputStream zip = new ZipOutputStream(outputStream)) {
|
||||||
|
|
||||||
// 遍历每个表名,生成对应的代码并压缩到 zip 文件中
|
// 遍历每个表名,生成对应的代码并压缩到 zip 文件中
|
||||||
for (String tableName : tableNames) {
|
for (String tableName : tableNames) {
|
||||||
generateAndZipCode(tableName, zip, ui);
|
generateAndZipCode(tableName, zip, ui, type);
|
||||||
}
|
}
|
||||||
// 确保所有压缩数据写入输出流,避免数据残留在内存缓冲区引发的数据不完整
|
// 确保所有压缩数据写入输出流,避免数据残留在内存缓冲区引发的数据不完整
|
||||||
zip.finish();
|
zip.finish();
|
||||||
@@ -324,8 +391,8 @@ public class CodegenServiceImpl implements CodegenService {
|
|||||||
* @param zip 压缩文件输出流
|
* @param zip 压缩文件输出流
|
||||||
* @param ui 页面类型
|
* @param ui 页面类型
|
||||||
*/
|
*/
|
||||||
private void generateAndZipCode(String tableName, ZipOutputStream zip, String ui) {
|
private void generateAndZipCode(String tableName, ZipOutputStream zip, String ui, String type) {
|
||||||
List<CodegenPreviewVO> codePreviewList = getCodegenPreviewData(tableName, ui);
|
List<CodegenPreviewVO> codePreviewList = getCodegenPreviewData(tableName, ui, type);
|
||||||
|
|
||||||
for (CodegenPreviewVO codePreview : codePreviewList) {
|
for (CodegenPreviewVO codePreview : codePreviewList) {
|
||||||
String fileName = codePreview.getFileName();
|
String fileName = codePreview.getFileName();
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ import org.springframework.web.multipart.MultipartFile;
|
|||||||
* @author Ray.Hao
|
* @author Ray.Hao
|
||||||
* @since 2022/10/16
|
* @since 2022/10/16
|
||||||
*/
|
*/
|
||||||
@Tag(name = "07.文件接口")
|
@Tag(name = "10.文件接口")
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/api/v1/files")
|
@RequestMapping("/api/v1/files")
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ import org.springframework.security.access.prepost.PreAuthorize;
|
|||||||
@Slf4j
|
@Slf4j
|
||||||
@RestController
|
@RestController
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
@Tag(name = "08.系统配置")
|
@Tag(name = "07.系统配置")
|
||||||
@RequestMapping("/api/v1/configs")
|
@RequestMapping("/api/v1/configs")
|
||||||
public class ConfigController {
|
public class ConfigController {
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import org.springframework.web.bind.annotation.*;
|
|||||||
* @author Ray.Hao
|
* @author Ray.Hao
|
||||||
* @since 2.10.0
|
* @since 2.10.0
|
||||||
*/
|
*/
|
||||||
@Tag(name = "10.日志接口")
|
@Tag(name = "09.日志接口")
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/api/v1/logs")
|
@RequestMapping("/api/v1/logs")
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ import org.springframework.web.bind.annotation.*;
|
|||||||
* @author youlaitech
|
* @author youlaitech
|
||||||
* @since 2024-08-27 10:31
|
* @since 2024-08-27 10:31
|
||||||
*/
|
*/
|
||||||
@Tag(name = "09.通知公告")
|
@Tag(name = "08.通知公告")
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/api/v1/notices")
|
@RequestMapping("/api/v1/notices")
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import java.time.LocalDate;
|
|||||||
* @author Ray.Hao
|
* @author Ray.Hao
|
||||||
* @since 2025-12-15
|
* @since 2025-12-15
|
||||||
*/
|
*/
|
||||||
@Tag(name = "11.统计分析")
|
@Tag(name = "12.统计分析")
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/api/v1/statistics")
|
@RequestMapping("/api/v1/statistics")
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
|
|||||||
@@ -125,6 +125,17 @@ public class UserController {
|
|||||||
return Result.judge(result);
|
return Result.judge(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "重置指定用户密码")
|
||||||
|
@PutMapping(value = "/{userId}/password/reset")
|
||||||
|
@PreAuthorize("@ss.hasPerm('sys:user:reset-password')")
|
||||||
|
public Result<?> resetUserPassword(
|
||||||
|
@Parameter(description = "用户ID") @PathVariable Long userId,
|
||||||
|
@RequestParam String password
|
||||||
|
) {
|
||||||
|
boolean result = userService.resetUserPassword(userId, password);
|
||||||
|
return Result.judge(result);
|
||||||
|
}
|
||||||
|
|
||||||
@Operation(summary = "获取当前登录用户信息")
|
@Operation(summary = "获取当前登录用户信息")
|
||||||
@GetMapping("/me")
|
@GetMapping("/me")
|
||||||
@Log(value = "获取当前登录用户信息", module = LogModuleEnum.USER)
|
@Log(value = "获取当前登录用户信息", module = LogModuleEnum.USER)
|
||||||
@@ -176,6 +187,13 @@ public class UserController {
|
|||||||
.doWrite(exportUserList);
|
.doWrite(exportUserList);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "获取用户下拉选项")
|
||||||
|
@GetMapping("/options")
|
||||||
|
public Result<List<Option<String>>> listUserOptions() {
|
||||||
|
List<Option<String>> list = userService.listUserOptions();
|
||||||
|
return Result.success(list);
|
||||||
|
}
|
||||||
|
|
||||||
@Operation(summary = "获取个人中心用户信息")
|
@Operation(summary = "获取个人中心用户信息")
|
||||||
@GetMapping("/profile")
|
@GetMapping("/profile")
|
||||||
@Log(value = "获取个人中心用户信息", module = LogModuleEnum.USER)
|
@Log(value = "获取个人中心用户信息", module = LogModuleEnum.USER)
|
||||||
@@ -193,16 +211,6 @@ public class UserController {
|
|||||||
return Result.judge(result);
|
return Result.judge(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Operation(summary = "重置指定用户密码")
|
|
||||||
@PutMapping(value = "/{userId}/password/reset")
|
|
||||||
@PreAuthorize("@ss.hasPerm('sys:user:reset-password')")
|
|
||||||
public Result<?> resetUserPassword(
|
|
||||||
@Parameter(description = "用户ID") @PathVariable Long userId,
|
|
||||||
@RequestParam String password
|
|
||||||
) {
|
|
||||||
boolean result = userService.resetUserPassword(userId, password);
|
|
||||||
return Result.judge(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Operation(summary = "当前用户修改密码")
|
@Operation(summary = "当前用户修改密码")
|
||||||
@PutMapping(value = "/password")
|
@PutMapping(value = "/password")
|
||||||
@@ -232,6 +240,15 @@ public class UserController {
|
|||||||
return Result.judge(result);
|
return Result.judge(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "解绑手机号")
|
||||||
|
@DeleteMapping(value = "/mobile")
|
||||||
|
public Result<?> unbindMobile(
|
||||||
|
@RequestBody @Validated PasswordVerifyForm data
|
||||||
|
) {
|
||||||
|
boolean result = userService.unbindMobile(data);
|
||||||
|
return Result.judge(result);
|
||||||
|
}
|
||||||
|
|
||||||
@Operation(summary = "发送邮箱验证码(绑定或更换邮箱)")
|
@Operation(summary = "发送邮箱验证码(绑定或更换邮箱)")
|
||||||
@PostMapping(value = "/email/code")
|
@PostMapping(value = "/email/code")
|
||||||
public Result<Void> sendEmailCode(
|
public Result<Void> sendEmailCode(
|
||||||
@@ -250,10 +267,14 @@ public class UserController {
|
|||||||
return Result.judge(result);
|
return Result.judge(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Operation(summary = "获取用户下拉选项")
|
@Operation(summary = "解绑邮箱")
|
||||||
@GetMapping("/options")
|
@DeleteMapping(value = "/email")
|
||||||
public Result<List<Option<String>>> listUserOptions() {
|
public Result<?> unbindEmail(
|
||||||
List<Option<String>> list = userService.listUserOptions();
|
@RequestBody @Validated PasswordVerifyForm data
|
||||||
return Result.success(list);
|
) {
|
||||||
|
boolean result = userService.unbindEmail(data);
|
||||||
|
return Result.judge(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package com.youlai.boot.system.model.form;
|
package com.youlai.boot.system.model.form;
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import jakarta.validation.constraints.Email;
|
||||||
import jakarta.validation.constraints.NotBlank;
|
import jakarta.validation.constraints.NotBlank;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
@@ -16,10 +17,15 @@ public class EmailUpdateForm {
|
|||||||
|
|
||||||
@Schema(description = "邮箱")
|
@Schema(description = "邮箱")
|
||||||
@NotBlank(message = "邮箱不能为空")
|
@NotBlank(message = "邮箱不能为空")
|
||||||
|
@Email(message = "邮箱格式不正确")
|
||||||
private String email;
|
private String email;
|
||||||
|
|
||||||
@Schema(description = "验证码")
|
@Schema(description = "验证码")
|
||||||
@NotBlank(message = "验证码不能为空")
|
@NotBlank(message = "验证码不能为空")
|
||||||
private String code;
|
private String code;
|
||||||
|
|
||||||
|
@Schema(description = "当前密码")
|
||||||
|
@NotBlank(message = "当前密码不能为空")
|
||||||
|
private String password;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package com.youlai.boot.system.model.form;
|
|||||||
|
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import jakarta.validation.constraints.NotBlank;
|
import jakarta.validation.constraints.NotBlank;
|
||||||
|
import jakarta.validation.constraints.Pattern;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -16,10 +17,15 @@ public class MobileUpdateForm {
|
|||||||
|
|
||||||
@Schema(description = "手机号码")
|
@Schema(description = "手机号码")
|
||||||
@NotBlank(message = "手机号码不能为空")
|
@NotBlank(message = "手机号码不能为空")
|
||||||
|
@Pattern(regexp = "^1(3\\d|4[5-9]|5[0-35-9]|6[2567]|7[0-8]|8\\d|9[0-35-9])\\d{8}$", message = "手机号码格式不正确")
|
||||||
private String mobile;
|
private String mobile;
|
||||||
|
|
||||||
@Schema(description = "验证码")
|
@Schema(description = "验证码")
|
||||||
@NotBlank(message = "验证码不能为空")
|
@NotBlank(message = "验证码不能为空")
|
||||||
private String code;
|
private String code;
|
||||||
|
|
||||||
|
@Schema(description = "当前密码")
|
||||||
|
@NotBlank(message = "当前密码不能为空")
|
||||||
|
private String password;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package com.youlai.boot.system.model.form;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import jakarta.validation.constraints.NotBlank;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Schema(description = "密码校验表单")
|
||||||
|
@Data
|
||||||
|
public class PasswordVerifyForm {
|
||||||
|
|
||||||
|
@Schema(description = "当前密码")
|
||||||
|
@NotBlank(message = "当前密码不能为空")
|
||||||
|
private String password;
|
||||||
|
}
|
||||||
@@ -12,13 +12,6 @@ import lombok.Data;
|
|||||||
@Schema(description = "个人中心用户信息")
|
@Schema(description = "个人中心用户信息")
|
||||||
@Data
|
@Data
|
||||||
public class UserProfileForm {
|
public class UserProfileForm {
|
||||||
|
|
||||||
@Schema(description = "用户ID")
|
|
||||||
private Long id;
|
|
||||||
|
|
||||||
@Schema(description = "用户名")
|
|
||||||
private String username;
|
|
||||||
|
|
||||||
@Schema(description = "用户昵称")
|
@Schema(description = "用户昵称")
|
||||||
private String nickname;
|
private String nickname;
|
||||||
|
|
||||||
@@ -28,11 +21,4 @@ public class UserProfileForm {
|
|||||||
@Schema(description = "性别")
|
@Schema(description = "性别")
|
||||||
private Integer gender;
|
private Integer gender;
|
||||||
|
|
||||||
@Schema(description = "手机号")
|
|
||||||
private String mobile;
|
|
||||||
|
|
||||||
@Schema(description = "邮箱")
|
|
||||||
private String email;
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -158,6 +158,22 @@ public interface UserService extends IService<User> {
|
|||||||
*/
|
*/
|
||||||
boolean bindOrChangeEmail(EmailUpdateForm data);
|
boolean bindOrChangeEmail(EmailUpdateForm data);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解绑手机号
|
||||||
|
*
|
||||||
|
* @param data 表单数据
|
||||||
|
* @return {@link Boolean} 是否解绑成功
|
||||||
|
*/
|
||||||
|
boolean unbindMobile(PasswordVerifyForm data);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解绑邮箱
|
||||||
|
*
|
||||||
|
* @param data 表单数据
|
||||||
|
* @return {@link Boolean} 是否解绑成功
|
||||||
|
*/
|
||||||
|
boolean unbindEmail(PasswordVerifyForm data);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取用户选项列表
|
* 获取用户选项列表
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -491,9 +491,17 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements Us
|
|||||||
@Override
|
@Override
|
||||||
public boolean updateUserProfile(UserProfileForm formData) {
|
public boolean updateUserProfile(UserProfileForm formData) {
|
||||||
Long userId = SecurityUtils.getUserId();
|
Long userId = SecurityUtils.getUserId();
|
||||||
User entity = userConverter.toEntity(formData);
|
|
||||||
entity.setId(userId);
|
if (formData.getNickname() == null && formData.getAvatar() == null && formData.getGender() == null) {
|
||||||
return this.updateById(entity);
|
throw new BusinessException("请修改至少一个字段");
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.update(new LambdaUpdateWrapper<User>()
|
||||||
|
.eq(User::getId, userId)
|
||||||
|
.set(formData.getNickname() != null, User::getNickname, formData.getNickname())
|
||||||
|
.set(formData.getAvatar() != null, User::getAvatar, formData.getAvatar())
|
||||||
|
.set(formData.getGender() != null, User::getGender, formData.getGender())
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -523,7 +531,7 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements Us
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 判断新密码和确认密码是否一致
|
// 判断新密码和确认密码是否一致
|
||||||
if (passwordEncoder.matches(data.getNewPassword(), data.getConfirmPassword())) {
|
if (!Objects.equals(data.getNewPassword(), data.getConfirmPassword())) {
|
||||||
throw new BusinessException("新密码和确认密码不一致");
|
throw new BusinessException("新密码和确认密码不一致");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -569,6 +577,15 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements Us
|
|||||||
@Override
|
@Override
|
||||||
public boolean sendMobileCode(String mobile) {
|
public boolean sendMobileCode(String mobile) {
|
||||||
|
|
||||||
|
Long currentUserId = SecurityUtils.getUserId();
|
||||||
|
long mobileCount = this.count(new LambdaQueryWrapper<User>()
|
||||||
|
.eq(User::getMobile, mobile)
|
||||||
|
.ne(User::getId, currentUserId)
|
||||||
|
);
|
||||||
|
if (mobileCount > 0) {
|
||||||
|
throw new BusinessException("手机号已被其他账号绑定");
|
||||||
|
}
|
||||||
|
|
||||||
// String code = String.valueOf((int) ((Math.random() * 9 + 1) * 1000));
|
// String code = String.valueOf((int) ((Math.random() * 9 + 1) * 1000));
|
||||||
// TODO 为了方便测试,验证码固定为 1234,实际开发中在配置了厂商短信服务后,可以使用上面的随机验证码
|
// TODO 为了方便测试,验证码固定为 1234,实际开发中在配置了厂商短信服务后,可以使用上面的随机验证码
|
||||||
String code = "1234";
|
String code = "1234";
|
||||||
@@ -600,6 +617,10 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements Us
|
|||||||
throw new BusinessException("用户不存在");
|
throw new BusinessException("用户不存在");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!passwordEncoder.matches(form.getPassword(), currentUser.getPassword())) {
|
||||||
|
throw new BusinessException("当前密码错误");
|
||||||
|
}
|
||||||
|
|
||||||
// 校验验证码
|
// 校验验证码
|
||||||
String inputVerifyCode = form.getCode();
|
String inputVerifyCode = form.getCode();
|
||||||
String mobile = form.getMobile();
|
String mobile = form.getMobile();
|
||||||
@@ -614,7 +635,15 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements Us
|
|||||||
if (!inputVerifyCode.equals(cachedVerifyCode)) {
|
if (!inputVerifyCode.equals(cachedVerifyCode)) {
|
||||||
throw new BusinessException("验证码错误");
|
throw new BusinessException("验证码错误");
|
||||||
}
|
}
|
||||||
// 验证完成删除验证码
|
|
||||||
|
long mobileCount = this.count(new LambdaQueryWrapper<User>()
|
||||||
|
.eq(User::getMobile, mobile)
|
||||||
|
.ne(User::getId, currentUserId)
|
||||||
|
);
|
||||||
|
if (mobileCount > 0) {
|
||||||
|
throw new BusinessException("手机号已被其他账号绑定");
|
||||||
|
}
|
||||||
|
|
||||||
redisTemplate.delete(cacheKey);
|
redisTemplate.delete(cacheKey);
|
||||||
|
|
||||||
// 更新手机号码
|
// 更新手机号码
|
||||||
@@ -633,6 +662,15 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements Us
|
|||||||
@Override
|
@Override
|
||||||
public void sendEmailCode(String email) {
|
public void sendEmailCode(String email) {
|
||||||
|
|
||||||
|
Long currentUserId = SecurityUtils.getUserId();
|
||||||
|
long emailCount = this.count(new LambdaQueryWrapper<User>()
|
||||||
|
.eq(User::getEmail, email)
|
||||||
|
.ne(User::getId, currentUserId)
|
||||||
|
);
|
||||||
|
if (emailCount > 0) {
|
||||||
|
throw new BusinessException("邮箱已被其他账号绑定");
|
||||||
|
}
|
||||||
|
|
||||||
// String code = String.valueOf((int) ((Math.random() * 9 + 1) * 1000));
|
// String code = String.valueOf((int) ((Math.random() * 9 + 1) * 1000));
|
||||||
// TODO 为了方便测试,验证码固定为 1234,实际开发中在配置了邮箱服务后,可以使用上面的随机验证码
|
// TODO 为了方便测试,验证码固定为 1234,实际开发中在配置了邮箱服务后,可以使用上面的随机验证码
|
||||||
String code = "1234";
|
String code = "1234";
|
||||||
@@ -659,6 +697,10 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements Us
|
|||||||
throw new BusinessException("用户不存在");
|
throw new BusinessException("用户不存在");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!passwordEncoder.matches(form.getPassword(), currentUser.getPassword())) {
|
||||||
|
throw new BusinessException("当前密码错误");
|
||||||
|
}
|
||||||
|
|
||||||
// 获取前端输入的验证码
|
// 获取前端输入的验证码
|
||||||
String inputVerifyCode = form.getCode();
|
String inputVerifyCode = form.getCode();
|
||||||
|
|
||||||
@@ -674,7 +716,15 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements Us
|
|||||||
if (!inputVerifyCode.equals(cachedVerifyCode)) {
|
if (!inputVerifyCode.equals(cachedVerifyCode)) {
|
||||||
throw new BusinessException("验证码错误");
|
throw new BusinessException("验证码错误");
|
||||||
}
|
}
|
||||||
// 验证完成删除验证码
|
|
||||||
|
long emailCount = this.count(new LambdaQueryWrapper<User>()
|
||||||
|
.eq(User::getEmail, email)
|
||||||
|
.ne(User::getId, currentUserId)
|
||||||
|
);
|
||||||
|
if (emailCount > 0) {
|
||||||
|
throw new BusinessException("邮箱已被其他账号绑定");
|
||||||
|
}
|
||||||
|
|
||||||
redisTemplate.delete(redisCacheKey);
|
redisTemplate.delete(redisCacheKey);
|
||||||
|
|
||||||
// 更新邮箱地址
|
// 更新邮箱地址
|
||||||
@@ -685,6 +735,66 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements Us
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解绑手机号
|
||||||
|
*
|
||||||
|
* @param form 表单数据
|
||||||
|
* @return true|false
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean unbindMobile(PasswordVerifyForm form) {
|
||||||
|
|
||||||
|
Long currentUserId = SecurityUtils.getUserId();
|
||||||
|
User currentUser = this.getById(currentUserId);
|
||||||
|
|
||||||
|
if (currentUser == null) {
|
||||||
|
throw new BusinessException("用户不存在");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (StrUtil.isBlank(currentUser.getMobile())) {
|
||||||
|
throw new BusinessException("当前账号未绑定手机号");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!passwordEncoder.matches(form.getPassword(), currentUser.getPassword())) {
|
||||||
|
throw new BusinessException("当前密码错误");
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.update(new LambdaUpdateWrapper<User>()
|
||||||
|
.eq(User::getId, currentUserId)
|
||||||
|
.set(User::getMobile, null)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解绑邮箱
|
||||||
|
*
|
||||||
|
* @param form 表单数据
|
||||||
|
* @return true|false
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean unbindEmail(PasswordVerifyForm form) {
|
||||||
|
|
||||||
|
Long currentUserId = SecurityUtils.getUserId();
|
||||||
|
User currentUser = this.getById(currentUserId);
|
||||||
|
|
||||||
|
if (currentUser == null) {
|
||||||
|
throw new BusinessException("用户不存在");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (StrUtil.isBlank(currentUser.getEmail())) {
|
||||||
|
throw new BusinessException("当前账号未绑定邮箱");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!passwordEncoder.matches(form.getPassword(), currentUser.getPassword())) {
|
||||||
|
throw new BusinessException("当前密码错误");
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.update(new LambdaUpdateWrapper<User>()
|
||||||
|
.eq(User::getId, currentUserId)
|
||||||
|
.set(User::getEmail, null)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取用户选项列表
|
* 获取用户选项列表
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -27,17 +27,15 @@
|
|||||||
<where>
|
<where>
|
||||||
u.is_deleted = 0
|
u.is_deleted = 0
|
||||||
<!-- 非超级管理员用户限制查看超级管理员 -->
|
<!-- 非超级管理员用户限制查看超级管理员 -->
|
||||||
<if test="!queryParams.isRoot">
|
AND NOT EXISTS (
|
||||||
AND NOT EXISTS (
|
SELECT
|
||||||
SELECT
|
1
|
||||||
1
|
FROM sys_user_role sur
|
||||||
FROM sys_user_role sur
|
INNER JOIN sys_role r ON sur.role_id = r.id
|
||||||
INNER JOIN sys_role r ON sur.role_id = r.id
|
WHERE
|
||||||
WHERE
|
sur.user_id = u.id
|
||||||
sur.user_id = u.id
|
AND r.code = '${@com.youlai.boot.common.constant.SystemConstants@ROOT_ROLE_CODE}'
|
||||||
AND r.code = '${@com.youlai.boot.common.constant.SystemConstants@ROOT_ROLE_CODE}'
|
)
|
||||||
)
|
|
||||||
</if>
|
|
||||||
<if test='queryParams.keywords!=null and queryParams.keywords.trim() neq ""'>
|
<if test='queryParams.keywords!=null and queryParams.keywords.trim() neq ""'>
|
||||||
AND (
|
AND (
|
||||||
u.username LIKE CONCAT('%',#{queryParams.keywords},'%')
|
u.username LIKE CONCAT('%',#{queryParams.keywords},'%')
|
||||||
@@ -199,7 +197,6 @@
|
|||||||
LEFT JOIN sys_dept d ON u.dept_id = d.id
|
LEFT JOIN sys_dept d ON u.dept_id = d.id
|
||||||
<where>
|
<where>
|
||||||
u.is_deleted = 0
|
u.is_deleted = 0
|
||||||
<if test="!isRoot">
|
|
||||||
AND NOT EXISTS (
|
AND NOT EXISTS (
|
||||||
SELECT
|
SELECT
|
||||||
1
|
1
|
||||||
@@ -209,7 +206,6 @@
|
|||||||
sur.user_id = u.id
|
sur.user_id = u.id
|
||||||
AND r.code = '${@com.youlai.boot.common.constant.SystemConstants@ROOT_ROLE_CODE}'
|
AND r.code = '${@com.youlai.boot.common.constant.SystemConstants@ROOT_ROLE_CODE}'
|
||||||
)
|
)
|
||||||
</if>
|
|
||||||
<if test='keywords!=null and keywords.trim() neq ""'>
|
<if test='keywords!=null and keywords.trim() neq ""'>
|
||||||
AND (u.username LIKE CONCAT('%',#{keywords},'%')
|
AND (u.username LIKE CONCAT('%',#{keywords},'%')
|
||||||
OR u.nickname LIKE CONCAT('%',#{keywords},'%')
|
OR u.nickname LIKE CONCAT('%',#{keywords},'%')
|
||||||
|
|||||||
63
src/main/resources/templates/codegen/api.js.vm
Normal file
63
src/main/resources/templates/codegen/api.js.vm
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
import request from "@/utils/request";
|
||||||
|
|
||||||
|
const ${entityUpperSnake}_BASE_URL = "/api/v1/${entityKebab}";
|
||||||
|
|
||||||
|
const ${entityName}API = {
|
||||||
|
/** 获取${businessName}分页数据 */
|
||||||
|
getPage(queryParams) {
|
||||||
|
return request({
|
||||||
|
url: `${${entityUpperSnake}_BASE_URL}`,
|
||||||
|
method: "get",
|
||||||
|
params: queryParams,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取${businessName}表单数据
|
||||||
|
* @param {string} id ${businessName}ID
|
||||||
|
*/
|
||||||
|
getFormData(id) {
|
||||||
|
return request({
|
||||||
|
url: `${${entityUpperSnake}_BASE_URL}/${id}/form`,
|
||||||
|
method: "get",
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 新增${businessName}
|
||||||
|
* @param {Object} data ${businessName}表单数据
|
||||||
|
*/
|
||||||
|
create(data) {
|
||||||
|
return request({
|
||||||
|
url: `${${entityUpperSnake}_BASE_URL}`,
|
||||||
|
method: "post",
|
||||||
|
data,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新${businessName}
|
||||||
|
* @param {string} id ${businessName}ID
|
||||||
|
* @param {Object} data ${businessName}表单数据
|
||||||
|
*/
|
||||||
|
update(id, data) {
|
||||||
|
return request({
|
||||||
|
url: `${${entityUpperSnake}_BASE_URL}/${id}`,
|
||||||
|
method: "put",
|
||||||
|
data,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量删除${businessName},多个以英文逗号分割
|
||||||
|
* @param {string} ids ${businessName}ID字符串
|
||||||
|
*/
|
||||||
|
deleteByIds(ids) {
|
||||||
|
return request({
|
||||||
|
url: `${${entityUpperSnake}_BASE_URL}/${ids}`,
|
||||||
|
method: "delete",
|
||||||
|
});
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ${entityName}API;
|
||||||
369
src/main/resources/templates/codegen/index.curd.js.vue.vm
Normal file
369
src/main/resources/templates/codegen/index.curd.js.vue.vm
Normal file
@@ -0,0 +1,369 @@
|
|||||||
|
<template>
|
||||||
|
<div class="app-container h-full flex flex-1 flex-col">
|
||||||
|
<!-- 搜索 -->
|
||||||
|
<PageSearch
|
||||||
|
ref="searchRef"
|
||||||
|
:search-config="searchConfig"
|
||||||
|
@query-click="handleQueryClick"
|
||||||
|
@reset-click="handleResetClick"
|
||||||
|
>
|
||||||
|
#foreach($fieldConfig in $fieldConfigs)
|
||||||
|
#if($fieldConfig.isShowInQuery == 1 && $fieldConfig.dictType && $fieldConfig.dictType.trim() != "")
|
||||||
|
<template #$fieldConfig.fieldName="scope">
|
||||||
|
<Dict v-model="scope.formData[scope.prop]" code="$fieldConfig.dictType" v-bind="scope.attrs" />
|
||||||
|
</template>
|
||||||
|
#end
|
||||||
|
#end
|
||||||
|
</PageSearch>
|
||||||
|
|
||||||
|
<!-- 列表 -->
|
||||||
|
<PageContent
|
||||||
|
ref="contentRef"
|
||||||
|
:content-config="contentConfig"
|
||||||
|
@add-click="handleAddClick"
|
||||||
|
@export-click="handleExportClick"
|
||||||
|
@search-click="handleSearchClick"
|
||||||
|
@toolbar-click="handleToolbarClick"
|
||||||
|
@operate-click="handleOperateClick"
|
||||||
|
@filter-change="handleFilterChange"
|
||||||
|
>
|
||||||
|
#foreach($fieldConfig in $fieldConfigs)
|
||||||
|
#if($fieldConfig.isShowInList == 1 && $fieldConfig.dictType && $fieldConfig.dictType.trim() != "")
|
||||||
|
<template #$fieldConfig.fieldName="scope">
|
||||||
|
<DictLabel v-model="scope.row[scope.prop]" code="$fieldConfig.dictType" />
|
||||||
|
</template>
|
||||||
|
#end
|
||||||
|
#end
|
||||||
|
</PageContent>
|
||||||
|
|
||||||
|
<!-- 新增 -->
|
||||||
|
<PageModal ref="addModalRef" :modal-config="addModalConfig" @submit-click="handleSubmitClick">
|
||||||
|
#foreach($fieldConfig in $fieldConfigs)
|
||||||
|
#if($fieldConfig.isShowInForm == 1 && $fieldConfig.formType != "HIDDEN" && $fieldConfig.dictType && $fieldConfig.dictType.trim() != "")
|
||||||
|
<template #$fieldConfig.fieldName="scope">
|
||||||
|
<Dict v-model="scope.formData[scope.prop]" code="$fieldConfig.dictType" v-bind="scope.attrs" />
|
||||||
|
</template>
|
||||||
|
#end
|
||||||
|
#end
|
||||||
|
</PageModal>
|
||||||
|
|
||||||
|
<!-- 编辑 -->
|
||||||
|
<PageModal ref="editModalRef" :modal-config="editModalConfig" @submit-click="handleSubmitClick">
|
||||||
|
#foreach($fieldConfig in $fieldConfigs)
|
||||||
|
#if($fieldConfig.isShowInForm == 1 && $fieldConfig.formType != "HIDDEN" && $fieldConfig.dictType && $fieldConfig.dictType.trim() != "")
|
||||||
|
<template #$fieldConfig.fieldName="scope">
|
||||||
|
<Dict v-model="scope.formData[scope.prop]" code="$fieldConfig.dictType" v-bind="scope.attrs" />
|
||||||
|
</template>
|
||||||
|
#end
|
||||||
|
#end
|
||||||
|
</PageModal>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
defineOptions({ name: "$entityName" });
|
||||||
|
|
||||||
|
import ${entityName}API from "@/api/${moduleName}/${entityKebab}";
|
||||||
|
import usePage from "@/components/CURD/usePage";
|
||||||
|
|
||||||
|
// 组合式 CRUD
|
||||||
|
const {
|
||||||
|
searchRef,
|
||||||
|
contentRef,
|
||||||
|
addModalRef,
|
||||||
|
editModalRef,
|
||||||
|
handleQueryClick,
|
||||||
|
handleResetClick,
|
||||||
|
handleAddClick,
|
||||||
|
handleEditClick,
|
||||||
|
handleSubmitClick,
|
||||||
|
handleExportClick,
|
||||||
|
handleSearchClick,
|
||||||
|
handleFilterChange,
|
||||||
|
} = usePage();
|
||||||
|
|
||||||
|
// 搜索配置
|
||||||
|
const searchConfig = reactive({
|
||||||
|
pageName: "${moduleName}:${entityKebab}",
|
||||||
|
formItems: [
|
||||||
|
#foreach($fieldConfig in $fieldConfigs)
|
||||||
|
#if($fieldConfig.isShowInQuery == 1)
|
||||||
|
#if($fieldConfig.dictType && $fieldConfig.dictType.trim() != "")
|
||||||
|
{
|
||||||
|
type: "custom",
|
||||||
|
slotName: "$fieldConfig.fieldName",
|
||||||
|
label: "$fieldConfig.fieldComment",
|
||||||
|
prop: "$fieldConfig.fieldName",
|
||||||
|
attrs: {
|
||||||
|
placeholder: "$fieldConfig.fieldComment",
|
||||||
|
clearable: true,
|
||||||
|
style: { width: "200px" },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
#else
|
||||||
|
{
|
||||||
|
#if($fieldConfig.javaType == "String")
|
||||||
|
type: "input",
|
||||||
|
#elseif($fieldConfig.javaType == "Integer" || $fieldConfig.javaType == "Long")
|
||||||
|
type: "input-number",
|
||||||
|
#elseif($fieldConfig.javaType == "Date" || $fieldConfig.javaType == "LocalDateTime")
|
||||||
|
type: "date-picker",
|
||||||
|
#else
|
||||||
|
type: "input",
|
||||||
|
#end
|
||||||
|
label: "$fieldConfig.fieldComment",
|
||||||
|
prop: "$fieldConfig.fieldName",
|
||||||
|
attrs: {
|
||||||
|
placeholder: "$fieldConfig.fieldComment",
|
||||||
|
#if($fieldConfig.javaType == "Date" || $fieldConfig.javaType == "LocalDateTime")
|
||||||
|
type: "daterange",
|
||||||
|
"range-separator": "~",
|
||||||
|
"start-placeholder": "开始时间",
|
||||||
|
"end-placeholder": "截止时间",
|
||||||
|
"value-format": "YYYY-MM-DD",
|
||||||
|
#end
|
||||||
|
clearable: true,
|
||||||
|
style: { width: "200px" },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
#end
|
||||||
|
#end
|
||||||
|
#end
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
// 列表配置
|
||||||
|
const contentConfig = reactive({
|
||||||
|
// 权限前缀
|
||||||
|
pageName: "${moduleName}:${entityKebab}",
|
||||||
|
table: {
|
||||||
|
border: true,
|
||||||
|
highlightCurrentRow: true,
|
||||||
|
},
|
||||||
|
// 主键
|
||||||
|
pk: "id",
|
||||||
|
// 列表查询接口
|
||||||
|
indexAction: ${entityName}API.getPage,
|
||||||
|
// 删除接口
|
||||||
|
deleteAction: ${entityName}API.deleteByIds,
|
||||||
|
// 数据解析函数
|
||||||
|
parseData(res) {
|
||||||
|
return {
|
||||||
|
total: res?.page?.total ?? 0,
|
||||||
|
list: res?.data ?? [],
|
||||||
|
};
|
||||||
|
},
|
||||||
|
// 分页配置
|
||||||
|
pagination: {
|
||||||
|
background: true,
|
||||||
|
layout: "total, sizes, prev, pager, next, jumper",
|
||||||
|
pageSize: 20,
|
||||||
|
pageSizes: [10, 20, 30, 50],
|
||||||
|
},
|
||||||
|
// 工具栏配置
|
||||||
|
toolbar: ["add", "delete"],
|
||||||
|
defaultToolbar: ["refresh", "filter"],
|
||||||
|
// 表格列配置
|
||||||
|
cols: [
|
||||||
|
{ type: "selection", width: 55, align: "center" },
|
||||||
|
#foreach($fieldConfig in $fieldConfigs)
|
||||||
|
#if($fieldConfig.isShowInList == 1)
|
||||||
|
#if($fieldConfig.dictType && $fieldConfig.dictType.trim() != "")
|
||||||
|
{
|
||||||
|
label: "$fieldConfig.fieldComment",
|
||||||
|
prop: "$fieldConfig.fieldName",
|
||||||
|
templet: "custom",
|
||||||
|
slotName: "$fieldConfig.fieldName"
|
||||||
|
},
|
||||||
|
#else
|
||||||
|
{ label: "$fieldConfig.fieldComment", prop: "$fieldConfig.fieldName" },
|
||||||
|
#end
|
||||||
|
#end
|
||||||
|
#end
|
||||||
|
{
|
||||||
|
label: "操作",
|
||||||
|
prop: "operation",
|
||||||
|
width: 220,
|
||||||
|
templet: "tool",
|
||||||
|
operat: ["edit", "delete"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
// 新增配置
|
||||||
|
const addModalConfig = reactive({
|
||||||
|
// 权限前缀
|
||||||
|
pageName: "${moduleName}:${entityKebab}",
|
||||||
|
// 主键
|
||||||
|
pk: "id",
|
||||||
|
// 弹窗配置
|
||||||
|
dialog: {
|
||||||
|
title: "新增$!{entityComment}",
|
||||||
|
width: 800,
|
||||||
|
draggable: true,
|
||||||
|
},
|
||||||
|
form: {
|
||||||
|
labelWidth: 100,
|
||||||
|
},
|
||||||
|
// 表单项配置
|
||||||
|
formItems: [
|
||||||
|
#foreach($fieldConfig in $fieldConfigs)
|
||||||
|
#if($fieldConfig.isShowInForm == 1 && $fieldConfig.formType != "HIDDEN")
|
||||||
|
#if($fieldConfig.dictType && $fieldConfig.dictType.trim() != "")
|
||||||
|
{
|
||||||
|
type: "custom",
|
||||||
|
label: "$fieldConfig.fieldComment",
|
||||||
|
prop: "$fieldConfig.fieldName",
|
||||||
|
slotName: "$fieldConfig.fieldName",
|
||||||
|
attrs: {
|
||||||
|
placeholder: "$fieldConfig.fieldComment",
|
||||||
|
style: { width: "100%" }
|
||||||
|
},
|
||||||
|
#if($fieldConfig.isRequired == 1)
|
||||||
|
rules: [{ required: true, message: "$fieldConfig.fieldComment不能为空", trigger: "change" }],
|
||||||
|
#end
|
||||||
|
},
|
||||||
|
#else
|
||||||
|
{
|
||||||
|
#if($fieldConfig.javaType == "String")
|
||||||
|
#if($fieldConfig.fieldName.contains("password") || $fieldConfig.fieldName.contains("pwd"))
|
||||||
|
type: "input",
|
||||||
|
attrs: {
|
||||||
|
type: "password",
|
||||||
|
placeholder: "$fieldConfig.fieldComment",
|
||||||
|
showPassword: true
|
||||||
|
},
|
||||||
|
#if($fieldConfig.isRequired == 1)
|
||||||
|
rules: [{ required: true, message: "$fieldConfig.fieldComment不能为空", trigger: "blur" }],
|
||||||
|
#end
|
||||||
|
#elseif($fieldConfig.fieldName.contains("email"))
|
||||||
|
type: "input",
|
||||||
|
attrs: {
|
||||||
|
placeholder: "$fieldConfig.fieldComment",
|
||||||
|
maxlength: 50
|
||||||
|
},
|
||||||
|
rules: [
|
||||||
|
#if($fieldConfig.isRequired == 1)
|
||||||
|
{ required: true, message: "$fieldConfig.fieldComment不能为空", trigger: "blur" },
|
||||||
|
#end
|
||||||
|
{ pattern: /\w[-\w.+]*@([A-Za-z0-9][-A-Za-z0-9]+\.)+[A-Za-z]{2,14}/, message: "请输入正确的邮箱地址", trigger: "blur" }
|
||||||
|
],
|
||||||
|
#elseif($fieldConfig.fieldName.contains("mobile") || $fieldConfig.fieldName.contains("phone"))
|
||||||
|
type: "input",
|
||||||
|
attrs: {
|
||||||
|
placeholder: "$fieldConfig.fieldComment",
|
||||||
|
maxlength: 11
|
||||||
|
},
|
||||||
|
rules: [
|
||||||
|
#if($fieldConfig.isRequired == 1)
|
||||||
|
{ required: true, message: "$fieldConfig.fieldComment不能为空", trigger: "blur" },
|
||||||
|
#end
|
||||||
|
{ pattern: /^1[3|4|5|6|7|8|9][0-9]\d{8}$/, message: "请输入正确的手机号码", trigger: "blur" }
|
||||||
|
],
|
||||||
|
#else
|
||||||
|
type: "input",
|
||||||
|
attrs: {
|
||||||
|
placeholder: "$fieldConfig.fieldComment"
|
||||||
|
},
|
||||||
|
#if($fieldConfig.isRequired == 1)
|
||||||
|
rules: [{ required: true, message: "$fieldConfig.fieldComment不能为空", trigger: "blur" }],
|
||||||
|
#end
|
||||||
|
#end
|
||||||
|
#elseif($fieldConfig.javaType == "Integer" || $fieldConfig.javaType == "Long")
|
||||||
|
type: "input-number",
|
||||||
|
attrs: {
|
||||||
|
placeholder: "$fieldConfig.fieldComment",
|
||||||
|
style: { width: "100%" }
|
||||||
|
},
|
||||||
|
#if($fieldConfig.isRequired == 1)
|
||||||
|
rules: [{ required: true, message: "$fieldConfig.fieldComment不能为空", trigger: "blur" }],
|
||||||
|
#end
|
||||||
|
#elseif($fieldConfig.javaType == "BigDecimal" || $fieldConfig.javaType == "Double")
|
||||||
|
type: "input-number",
|
||||||
|
attrs: {
|
||||||
|
placeholder: "$fieldConfig.fieldComment",
|
||||||
|
precision: 2,
|
||||||
|
style: { width: "100%" }
|
||||||
|
},
|
||||||
|
#if($fieldConfig.isRequired == 1)
|
||||||
|
rules: [{ required: true, message: "$fieldConfig.fieldComment不能为空", trigger: "blur" }],
|
||||||
|
#end
|
||||||
|
#elseif($fieldConfig.javaType == "Date" || $fieldConfig.javaType == "LocalDateTime")
|
||||||
|
type: "date-picker",
|
||||||
|
attrs: {
|
||||||
|
type: "datetime",
|
||||||
|
placeholder: "$fieldConfig.fieldComment",
|
||||||
|
format: "YYYY-MM-DD HH:mm:ss",
|
||||||
|
"value-format": "YYYY-MM-DD HH:mm:ss",
|
||||||
|
style: { width: "100%" }
|
||||||
|
},
|
||||||
|
#if($fieldConfig.isRequired == 1)
|
||||||
|
rules: [{ required: true, message: "$fieldConfig.fieldComment不能为空", trigger: "change" }],
|
||||||
|
#end
|
||||||
|
#elseif($fieldConfig.javaType == "Boolean" || $fieldConfig.fieldName.contains("status"))
|
||||||
|
type: "switch",
|
||||||
|
attrs: {
|
||||||
|
activeText: "启用",
|
||||||
|
inactiveText: "禁用",
|
||||||
|
activeValue: 1,
|
||||||
|
inactiveValue: 0
|
||||||
|
},
|
||||||
|
initialValue: 1,
|
||||||
|
#else
|
||||||
|
type: "input",
|
||||||
|
attrs: {
|
||||||
|
placeholder: "$fieldConfig.fieldComment"
|
||||||
|
},
|
||||||
|
#if($fieldConfig.isRequired == 1)
|
||||||
|
rules: [{ required: true, message: "$fieldConfig.fieldComment不能为空", trigger: "blur" }],
|
||||||
|
#end
|
||||||
|
#end
|
||||||
|
label: "$fieldConfig.fieldComment",
|
||||||
|
prop: "$fieldConfig.fieldName",
|
||||||
|
},
|
||||||
|
#end
|
||||||
|
#end
|
||||||
|
#end
|
||||||
|
],
|
||||||
|
// 提交函数
|
||||||
|
formAction(data) {
|
||||||
|
if (data.id) {
|
||||||
|
// 编辑
|
||||||
|
return ${entityName}API.update(data.id, data);
|
||||||
|
} else {
|
||||||
|
// 新增
|
||||||
|
return ${entityName}API.create(data);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// 编辑配置
|
||||||
|
const editModalConfig = reactive({
|
||||||
|
pageName: "${moduleName}:${entityKebab}",
|
||||||
|
component: "drawer",
|
||||||
|
drawer: {
|
||||||
|
title: "编辑$!{entityComment}",
|
||||||
|
size: 500,
|
||||||
|
},
|
||||||
|
pk: "id",
|
||||||
|
formAction(data) {
|
||||||
|
return ${entityName}API.update(data.id, data);
|
||||||
|
},
|
||||||
|
formItems: addModalConfig.formItems, // 复用新增的表单项
|
||||||
|
});
|
||||||
|
|
||||||
|
// 处理操作按钮点击
|
||||||
|
const handleOperateClick = (data) => {
|
||||||
|
if (data.name === "edit") {
|
||||||
|
handleEditClick(data.row, async () => {
|
||||||
|
return await ${entityName}API.getFormData(String(data.row.id));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 处理工具栏按钮点击(删除等)
|
||||||
|
const handleToolbarClick = (name) => {
|
||||||
|
console.log(name);
|
||||||
|
};
|
||||||
|
|
||||||
|
</script>
|
||||||
417
src/main/resources/templates/codegen/index.js.vue.vm
Normal file
417
src/main/resources/templates/codegen/index.js.vue.vm
Normal file
@@ -0,0 +1,417 @@
|
|||||||
|
<template>
|
||||||
|
<div class="app-container">
|
||||||
|
<div class="search-container">
|
||||||
|
<el-form ref="queryFormRef" :model="queryParams" :inline="true">
|
||||||
|
#foreach($fieldConfig in $fieldConfigs)
|
||||||
|
#if($fieldConfig.isShowInQuery == 1)
|
||||||
|
<el-form-item label="$fieldConfig.fieldComment" prop="$fieldConfig.fieldName">
|
||||||
|
#if($fieldConfig.formType == "INPUT")
|
||||||
|
<el-input
|
||||||
|
v-model="queryParams.$fieldConfig.fieldName"
|
||||||
|
placeholder="$fieldConfig.fieldComment"
|
||||||
|
clearable
|
||||||
|
@keyup.enter="handleQuery()"
|
||||||
|
/>
|
||||||
|
#elseif($fieldConfig.formType == "SELECT")
|
||||||
|
#if($fieldConfig.dictType && $fieldConfig.dictType.trim() != "")
|
||||||
|
<Dict v-model="queryParams.$fieldConfig.fieldName" code="$fieldConfig.dictType" />
|
||||||
|
#else
|
||||||
|
<el-select v-model="queryParams.$fieldConfig.fieldName" placeholder="请选择$fieldConfig.fieldComment">
|
||||||
|
<el-option :key="1" :value="1" label="选项一"/>
|
||||||
|
<el-option :key="2" :value="2" label="选项二"/>
|
||||||
|
</el-select>
|
||||||
|
#end
|
||||||
|
#elseif($fieldConfig.formType == "RADIO")
|
||||||
|
#if($fieldConfig.dictType && $fieldConfig.dictType.trim() != "")
|
||||||
|
<Dict v-model="formData.$fieldConfig.fieldName" type="radio" code="$fieldConfig.dictType" />
|
||||||
|
#else
|
||||||
|
<el-radio-group v-model="queryParams.$fieldConfig.fieldName">
|
||||||
|
<el-radio :key="1" :value="1">选项一</el-radio>
|
||||||
|
<el-radio :key="2" :value="2">选项二</el-radio>
|
||||||
|
</el-radio-group>
|
||||||
|
#end
|
||||||
|
#elseif($fieldConfig.formType == "CHECK_BOX")
|
||||||
|
#if($fieldConfig.dictType && $fieldConfig.dictType.trim() != "")
|
||||||
|
<Dict v-model="formData.$fieldConfig.fieldName" type="checkbox" code="$fieldConfig.dictType" />
|
||||||
|
#else
|
||||||
|
<el-checkbox-group v-model="queryParams.$fieldConfig.fieldName">
|
||||||
|
<el-checkbox :key="1" :label="1">选项一</el-checkbox>
|
||||||
|
<el-checkbox :key="2" :label="2">选项二</el-checkbox>
|
||||||
|
</el-checkbox-group>
|
||||||
|
#end
|
||||||
|
#elseif($fieldConfig.formType == "INPUT_NUMBER")
|
||||||
|
<el-input-number
|
||||||
|
v-model="queryParams.$fieldConfig.fieldName"
|
||||||
|
placeholder="$fieldConfig.fieldComment"
|
||||||
|
/>
|
||||||
|
#elseif($fieldConfig.formType == "SWITCH")
|
||||||
|
<el-switch
|
||||||
|
v-model="queryParams.$fieldConfig.fieldName"
|
||||||
|
:active-value="1"
|
||||||
|
:inactive-value="0"
|
||||||
|
/>
|
||||||
|
#elseif($fieldConfig.formType == "TEXT_AREA")
|
||||||
|
<el-input type="textarea"
|
||||||
|
v-model="queryParams.$fieldConfig.fieldName"
|
||||||
|
placeholder="$fieldConfig.fieldComment"
|
||||||
|
/>
|
||||||
|
#elseif($fieldConfig.formType == "DATE_TIME")
|
||||||
|
<el-date-picker
|
||||||
|
v-model="queryParams.$fieldConfig.fieldName"
|
||||||
|
#if($fieldConfig.queryType == "BETWEEN")
|
||||||
|
type="daterange"
|
||||||
|
range-separator="~"
|
||||||
|
start-placeholder="开始时间"
|
||||||
|
end-placeholder="结束时间"
|
||||||
|
#else
|
||||||
|
type="datetime"
|
||||||
|
placeholder="$fieldConfig.fieldComment"
|
||||||
|
#end
|
||||||
|
value-format="YYYY-MM-DD HH:mm:ss"
|
||||||
|
/>
|
||||||
|
#elseif($fieldConfig.formType == "DATE")
|
||||||
|
<el-date-picker
|
||||||
|
class="!w-[240px]"
|
||||||
|
v-model="queryParams.$fieldConfig.fieldName"
|
||||||
|
#if($fieldConfig.queryType == "BETWEEN")
|
||||||
|
type="daterange"
|
||||||
|
range-separator="~"
|
||||||
|
start-placeholder="开始时间"
|
||||||
|
end-placeholder="结束时间"
|
||||||
|
#else
|
||||||
|
type="date"
|
||||||
|
placeholder="$fieldConfig.fieldComment"
|
||||||
|
#end
|
||||||
|
value-format="YYYY-MM-DD"
|
||||||
|
/>
|
||||||
|
#end
|
||||||
|
</el-form-item>
|
||||||
|
#end
|
||||||
|
#end
|
||||||
|
<el-form-item class="search-buttons">
|
||||||
|
<el-button type="primary" icon="search" @click="handleQuery">搜索</el-button>
|
||||||
|
<el-button icon="refresh" @click="handleResetQuery">重置</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<el-card shadow="never">
|
||||||
|
<div class="mb-10px">
|
||||||
|
<el-button
|
||||||
|
v-hasPerm="['${moduleName}:${entityKebab}:add']"
|
||||||
|
type="success"
|
||||||
|
icon="plus"
|
||||||
|
@click="handleOpenDialog()"
|
||||||
|
>新增</el-button>
|
||||||
|
<el-button
|
||||||
|
v-hasPerm="['${moduleName}:${entityKebab}:delete']"
|
||||||
|
type="danger"
|
||||||
|
:disabled="removeIds.length === 0"
|
||||||
|
icon="delete"
|
||||||
|
@click="handleDelete()"
|
||||||
|
>删除</el-button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<el-table
|
||||||
|
ref="dataTableRef"
|
||||||
|
v-loading="loading"
|
||||||
|
:data="pageData"
|
||||||
|
highlight-current-row
|
||||||
|
border
|
||||||
|
@selection-change="handleSelectionChange"
|
||||||
|
>
|
||||||
|
<el-table-column type="selection" width="55" align="center" />
|
||||||
|
#foreach($fieldConfig in $fieldConfigs)
|
||||||
|
#if($fieldConfig.isShowInList == 1)
|
||||||
|
#if($fieldConfig.dictType && $fieldConfig.dictType.trim() != "")
|
||||||
|
<el-table-column label="$fieldConfig.fieldComment" width="150" align="center">
|
||||||
|
<template #default="scope">
|
||||||
|
<DictLabel v-model="scope.row.$fieldConfig.fieldName" code="$fieldConfig.dictType" />
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
#else
|
||||||
|
<el-table-column
|
||||||
|
key="$fieldConfig.fieldName"
|
||||||
|
label="$fieldConfig.fieldComment"
|
||||||
|
prop="$fieldConfig.fieldName"
|
||||||
|
min-width="150"
|
||||||
|
align="center"
|
||||||
|
/>
|
||||||
|
#end
|
||||||
|
#end
|
||||||
|
#end
|
||||||
|
<el-table-column fixed="right" label="操作" width="220">
|
||||||
|
<template #default="scope">
|
||||||
|
<el-button
|
||||||
|
v-hasPerm="['${moduleName}:${entityKebab}:edit']"
|
||||||
|
type="primary"
|
||||||
|
size="small"
|
||||||
|
link
|
||||||
|
icon="edit"
|
||||||
|
@click="handleOpenDialog(String(scope.row.id))"
|
||||||
|
>
|
||||||
|
编辑
|
||||||
|
</el-button>
|
||||||
|
<el-button
|
||||||
|
v-hasPerm="['${moduleName}:${entityKebab}:delete']"
|
||||||
|
type="danger"
|
||||||
|
size="small"
|
||||||
|
link
|
||||||
|
icon="delete"
|
||||||
|
@click="handleDelete(String(scope.row.id))"
|
||||||
|
>
|
||||||
|
删除
|
||||||
|
</el-button>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
|
||||||
|
<pagination
|
||||||
|
v-if="total > 0"
|
||||||
|
v-model:total="total"
|
||||||
|
v-model:page="queryParams.pageNum"
|
||||||
|
v-model:limit="queryParams.pageSize"
|
||||||
|
@pagination="handleQuery()"
|
||||||
|
/>
|
||||||
|
</el-card>
|
||||||
|
|
||||||
|
<!-- $!{businessName}表单弹窗 -->
|
||||||
|
<el-dialog
|
||||||
|
v-model="dialog.visible"
|
||||||
|
:title="dialog.title"
|
||||||
|
width="500px"
|
||||||
|
@close="handleCloseDialog"
|
||||||
|
>
|
||||||
|
<el-form ref="dataFormRef" :model="formData" :rules="rules" label-width="100px">
|
||||||
|
#foreach($fieldConfig in $fieldConfigs)
|
||||||
|
#if($fieldConfig.isShowInForm == 1 && $fieldConfig.formType != "HIDDEN")
|
||||||
|
<el-form-item label="$fieldConfig.fieldComment" prop="$fieldConfig.fieldName">
|
||||||
|
#if($fieldConfig.formType == "INPUT")
|
||||||
|
<el-input
|
||||||
|
v-model="formData.$fieldConfig.fieldName"
|
||||||
|
placeholder="$fieldConfig.fieldComment"
|
||||||
|
/>
|
||||||
|
#elseif($fieldConfig.formType == "SELECT")
|
||||||
|
#if($fieldConfig.dictType && $fieldConfig.dictType.trim() != "")
|
||||||
|
<Dict v-model="formData.$fieldConfig.fieldName" code="$fieldConfig.dictType" />
|
||||||
|
#else
|
||||||
|
<el-select v-model="formData.$fieldConfig.fieldName" placeholder="请选择$fieldConfig.fieldComment">
|
||||||
|
<el-option :value="0" label="选项一"/>
|
||||||
|
<el-option :value="1" label="选项二"/>
|
||||||
|
</el-select>
|
||||||
|
#end
|
||||||
|
#elseif($fieldConfig.formType == "RADIO")
|
||||||
|
#if($fieldConfig.dictType && $fieldConfig.dictType.trim() != "")
|
||||||
|
<Dict v-model="queryParams.$fieldConfig.fieldName" type="radio" code="$fieldConfig.dictType" />
|
||||||
|
#else
|
||||||
|
<el-radio-group v-model="formData.$fieldConfig.fieldName">
|
||||||
|
<el-radio :value="0">选项一</el-radio>
|
||||||
|
<el-radio :value="1">选项二</el-radio>
|
||||||
|
</el-radio-group>
|
||||||
|
#end
|
||||||
|
#elseif($fieldConfig.formType == "CHECK_BOX")
|
||||||
|
#if($fieldConfig.dictType && $fieldConfig.dictType.trim() != "")
|
||||||
|
<Dict v-model="queryParams.$fieldConfig.fieldName" type="checkbox" code="$fieldConfig.dictType" />
|
||||||
|
#else
|
||||||
|
<el-checkbox-group v-model="formData.$fieldConfig.fieldName">
|
||||||
|
<el-checkbox :value="0">选项一</el-checkbox>
|
||||||
|
<el-checkbox :value="1">选项二</el-checkbox>
|
||||||
|
</el-checkbox-group>
|
||||||
|
#end
|
||||||
|
#elseif($fieldConfig.formType == "INPUT_NUMBER")
|
||||||
|
<el-input-number
|
||||||
|
v-model="formData.$fieldConfig.fieldName"
|
||||||
|
placeholder="$fieldConfig.fieldComment"
|
||||||
|
/>
|
||||||
|
#elseif($fieldConfig.formType == "SWITCH")
|
||||||
|
<el-switch
|
||||||
|
v-model="formData.$fieldConfig.fieldName"
|
||||||
|
:active-value="1"
|
||||||
|
:inactive-value="0"
|
||||||
|
/>
|
||||||
|
#elseif($fieldConfig.formType == "TEXT_AREA")
|
||||||
|
<el-input type="textarea"
|
||||||
|
v-model="formData.$fieldConfig.fieldName"
|
||||||
|
placeholder="$fieldConfig.fieldComment"
|
||||||
|
/>
|
||||||
|
#elseif($fieldConfig.formType == "DATE_TIME")
|
||||||
|
<el-date-picker
|
||||||
|
v-model="formData.$fieldConfig.fieldName"
|
||||||
|
type="datetime"
|
||||||
|
placeholder="$fieldConfig.fieldComment"
|
||||||
|
value-format="YYYY-MM-DD HH:mm:ss"
|
||||||
|
/>
|
||||||
|
#elseif($fieldConfig.formType == "DATE")
|
||||||
|
<el-date-picker
|
||||||
|
class="!w-[240px]"
|
||||||
|
v-model="formData.$fieldConfig.fieldName"
|
||||||
|
type="date"
|
||||||
|
placeholder="$fieldConfig.fieldComment"
|
||||||
|
value-format="YYYY-MM-DD"
|
||||||
|
/>
|
||||||
|
#end
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
|
#elseif($fieldConfig.formType == "HIDDEN")
|
||||||
|
<el-input type="hidden" v-model="formData.$fieldConfig.fieldName" />
|
||||||
|
#end
|
||||||
|
#end
|
||||||
|
</el-form>
|
||||||
|
<template #footer>
|
||||||
|
<div class="dialog-footer">
|
||||||
|
<el-button type="primary" @click="handleSubmit()">确定</el-button>
|
||||||
|
<el-button @click="handleCloseDialog()">取消</el-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
defineOptions({
|
||||||
|
name: "${entityName}",
|
||||||
|
inheritAttrs: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
import ${entityName}API from "@/api/${moduleName}/${entityKebab}";
|
||||||
|
|
||||||
|
const queryFormRef = ref();
|
||||||
|
const dataFormRef = ref();
|
||||||
|
|
||||||
|
const loading = ref(false);
|
||||||
|
const removeIds = ref([]);
|
||||||
|
const total = ref(0);
|
||||||
|
|
||||||
|
const queryParams = reactive({
|
||||||
|
pageNum: 1,
|
||||||
|
pageSize: 10,
|
||||||
|
});
|
||||||
|
|
||||||
|
// $!{businessName}表格数据
|
||||||
|
const pageData = ref([]);
|
||||||
|
|
||||||
|
// 弹窗
|
||||||
|
const dialog = reactive({
|
||||||
|
title: "",
|
||||||
|
visible: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
// $!{businessName}表单数据
|
||||||
|
const formData = reactive({});
|
||||||
|
|
||||||
|
// $!{businessName}表单校验规则
|
||||||
|
const rules = reactive({
|
||||||
|
#if($fieldConfigs)
|
||||||
|
#foreach($fieldConfig in ${fieldConfigs})
|
||||||
|
#if($fieldConfig.isShowInForm && $fieldConfig.isRequired)
|
||||||
|
${fieldConfig.fieldName}: [{ required: true, message: "请输入${fieldConfig.fieldComment}", trigger: "blur" }],
|
||||||
|
#end
|
||||||
|
#end
|
||||||
|
#end
|
||||||
|
});
|
||||||
|
|
||||||
|
/** 查询$!{businessName} */
|
||||||
|
function handleQuery() {
|
||||||
|
loading.value = true;
|
||||||
|
${entityName}API.getPage(queryParams)
|
||||||
|
.then((res) => {
|
||||||
|
pageData.value = res.data;
|
||||||
|
total.value = res.page?.total ?? 0;
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
loading.value = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 重置$!{businessName}查询 */
|
||||||
|
function handleResetQuery() {
|
||||||
|
queryFormRef.value?.resetFields();
|
||||||
|
queryParams.pageNum = 1;
|
||||||
|
handleQuery();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 行复选框选中记录选中ID集合 */
|
||||||
|
function handleSelectionChange(selection) {
|
||||||
|
removeIds.value = selection.map((item) => String(item.id));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 打开$!{businessName}弹窗 */
|
||||||
|
function handleOpenDialog(id) {
|
||||||
|
dialog.visible = true;
|
||||||
|
if (id) {
|
||||||
|
dialog.title = "修改$!{businessName}";
|
||||||
|
${entityName}API.getFormData(id).then((data) => {
|
||||||
|
Object.assign(formData, data);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
dialog.title = "新增$!{businessName}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 提交$!{businessName}表单 */
|
||||||
|
function handleSubmit() {
|
||||||
|
dataFormRef.value?.validate((valid) => {
|
||||||
|
if (valid) {
|
||||||
|
loading.value = true;
|
||||||
|
const id = formData.id;
|
||||||
|
if (id) {
|
||||||
|
${entityName}API.update(id, formData)
|
||||||
|
.then(() => {
|
||||||
|
ElMessage.success("修改成功");
|
||||||
|
handleCloseDialog();
|
||||||
|
handleResetQuery();
|
||||||
|
})
|
||||||
|
.finally(() => (loading.value = false));
|
||||||
|
} else {
|
||||||
|
${entityName}API.create(formData)
|
||||||
|
.then(() => {
|
||||||
|
ElMessage.success("新增成功");
|
||||||
|
handleCloseDialog();
|
||||||
|
handleResetQuery();
|
||||||
|
})
|
||||||
|
.finally(() => (loading.value = false));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 关闭$!{businessName}弹窗 */
|
||||||
|
function handleCloseDialog() {
|
||||||
|
dialog.visible = false;
|
||||||
|
dataFormRef.value?.resetFields();
|
||||||
|
dataFormRef.value?.clearValidate();
|
||||||
|
formData.id = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 删除$!{businessName} */
|
||||||
|
function handleDelete(id) {
|
||||||
|
const ids = [id || removeIds.value].join(",");
|
||||||
|
if (!ids) {
|
||||||
|
ElMessage.warning("请勾选删除项");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ElMessageBox.confirm("确认删除已选中的数据项?", "警告", {
|
||||||
|
confirmButtonText: "确定",
|
||||||
|
cancelButtonText: "取消",
|
||||||
|
type: "warning",
|
||||||
|
}).then(
|
||||||
|
() => {
|
||||||
|
loading.value = true;
|
||||||
|
${entityName}API.deleteByIds(ids)
|
||||||
|
.then(() => {
|
||||||
|
ElMessage.success("删除成功");
|
||||||
|
handleResetQuery();
|
||||||
|
})
|
||||||
|
.finally(() => (loading.value = false));
|
||||||
|
},
|
||||||
|
() => {
|
||||||
|
ElMessage.info("已取消删除");
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
handleQuery();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
@@ -23,7 +23,7 @@
|
|||||||
#end
|
#end
|
||||||
#elseif($fieldConfig.formType == "RADIO")
|
#elseif($fieldConfig.formType == "RADIO")
|
||||||
#if($fieldConfig.dictType && $fieldConfig.dictType.trim() != "")
|
#if($fieldConfig.dictType && $fieldConfig.dictType.trim() != "")
|
||||||
<Dict v-model="queryParams.$fieldConfig.fieldName" type="radio" code="$fieldConfig.dictType" />
|
<Dict v-model="formData.$fieldConfig.fieldName" type="radio" code="$fieldConfig.dictType" />
|
||||||
#else
|
#else
|
||||||
<el-radio-group v-model="queryParams.$fieldConfig.fieldName">
|
<el-radio-group v-model="queryParams.$fieldConfig.fieldName">
|
||||||
<el-radio :key="1" :value="1">选项一</el-radio>
|
<el-radio :key="1" :value="1">选项一</el-radio>
|
||||||
@@ -32,7 +32,7 @@
|
|||||||
#end
|
#end
|
||||||
#elseif($fieldConfig.formType == "CHECK_BOX")
|
#elseif($fieldConfig.formType == "CHECK_BOX")
|
||||||
#if($fieldConfig.dictType && $fieldConfig.dictType.trim() != "")
|
#if($fieldConfig.dictType && $fieldConfig.dictType.trim() != "")
|
||||||
<Dict v-model="queryParams.$fieldConfig.fieldName" type="checkbox" code="$fieldConfig.dictType" />
|
<Dict v-model="formData.$fieldConfig.fieldName" type="checkbox" code="$fieldConfig.dictType" />
|
||||||
#else
|
#else
|
||||||
<el-checkbox-group v-model="queryParams.$fieldConfig.fieldName">
|
<el-checkbox-group v-model="queryParams.$fieldConfig.fieldName">
|
||||||
<el-checkbox :key="1" :label="1">选项一</el-checkbox>
|
<el-checkbox :key="1" :label="1">选项一</el-checkbox>
|
||||||
|
|||||||
Reference in New Issue
Block a user