代码生成,支持配置前端 UI 类型

This commit is contained in:
YunaiV 2023-04-13 08:50:22 +08:00
parent c5b6a2a53d
commit ca9656a40f
27 changed files with 985 additions and 423 deletions

View File

@ -37,7 +37,7 @@ public class CodegenUpdateReqVO {
@Schema(description = "编号", required = true, example = "1")
private Long id;
@AssertTrue(message = "上级菜单不能为空")
@AssertTrue(message = "上级菜单不能为空,请前往 [修改生成配置 -> 生成信息] 界面,设置“上级菜单”字段")
public boolean isParentMenuIdValid() {
// 生成场景为管理后台时必须设置上级菜单不然生成的菜单 SQL 是无父级菜单的
return ObjectUtil.notEqual(getScene(), CodegenSceneEnum.ADMIN.getScene())
@ -57,4 +57,4 @@ public class CodegenUpdateReqVO {
}
}
}

View File

@ -51,7 +51,11 @@ public class CodegenTableBaseVO {
@NotNull(message = "模板类型不能为空")
private Integer templateType;
@Schema(description = "前端类型,参见 CodegenFrontTypeEnum 枚举", required = true, example = "20")
@NotNull(message = "前端类型不能为空")
private Integer frontType;
@Schema(description = "父菜单编号", example = "1024")
private Long parentMenuId;
}
}

View File

@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.infra.dal.dataobject.codegen;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import cn.iocoder.yudao.module.infra.dal.dataobject.db.DataSourceConfigDO;
import cn.iocoder.yudao.module.infra.enums.codegen.CodegenFrontTypeEnum;
import cn.iocoder.yudao.module.infra.enums.codegen.CodegenSceneEnum;
import cn.iocoder.yudao.module.infra.enums.codegen.CodegenTemplateTypeEnum;
import com.baomidou.mybatisplus.annotation.IdType;
@ -100,6 +101,12 @@ public class CodegenTableDO extends BaseDO {
* 枚举 {@link CodegenTemplateTypeEnum}
*/
private Integer templateType;
/**
* 代码生成的前端类型
*
* 枚举 {@link CodegenFrontTypeEnum}
*/
private Integer frontType;
// ========== 菜单相关字段 ==========

View File

@ -0,0 +1,25 @@
package cn.iocoder.yudao.module.infra.enums.codegen;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 代码生成的前端类型枚举
*
* @author 芋道源码
*/
@AllArgsConstructor
@Getter
public enum CodegenFrontTypeEnum {
VUE2(10), // Vue2 Element UI 标准模版
VUE3(20), // Vue3 Element Plus 标准模版
VUE3_SCHEMA(21), // Vue3 Element Plus Schema 模版
;
/**
* 类型
*/
private final Integer type;
}

View File

@ -1,5 +1,6 @@
package cn.iocoder.yudao.module.infra.framework.codegen.config;
import cn.iocoder.yudao.module.infra.enums.codegen.CodegenFrontTypeEnum;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.validation.annotation.Validated;
@ -25,4 +26,12 @@ public class CodegenProperties {
@NotEmpty(message = "数据库不能为空")
private Collection<String> dbSchemas;
/**
* 代码生成的前端类型默认
*
* 枚举 {@link CodegenFrontTypeEnum#getType()}
*/
@NotNull(message = "代码生成的前端类型不能为空")
private Integer frontType;
}

View File

@ -14,6 +14,7 @@ import cn.iocoder.yudao.module.infra.dal.dataobject.codegen.CodegenTableDO;
import cn.iocoder.yudao.module.infra.dal.mysql.codegen.CodegenColumnMapper;
import cn.iocoder.yudao.module.infra.dal.mysql.codegen.CodegenTableMapper;
import cn.iocoder.yudao.module.infra.enums.codegen.CodegenSceneEnum;
import cn.iocoder.yudao.module.infra.framework.codegen.config.CodegenProperties;
import cn.iocoder.yudao.module.infra.service.codegen.inner.CodegenBuilder;
import cn.iocoder.yudao.module.infra.service.codegen.inner.CodegenEngine;
import cn.iocoder.yudao.module.infra.service.db.DatabaseTableService;
@ -58,6 +59,9 @@ public class CodegenServiceImpl implements CodegenService {
@Resource
private CodegenEngine codegenEngine;
@Resource
private CodegenProperties codegenProperties;
@Override
@Transactional(rollbackFor = Exception.class)
public List<Long> createCodegenList(Long userId, CodegenCreateListReqVO reqVO) {
@ -87,6 +91,7 @@ public class CodegenServiceImpl implements CodegenService {
CodegenTableDO table = codegenBuilder.buildTable(tableInfo);
table.setDataSourceConfigId(dataSourceConfigId);
table.setScene(CodegenSceneEnum.ADMIN.getScene()); // 默认配置下使用管理后台的模板
table.setFrontType(codegenProperties.getFrontType());
table.setAuthor(userApi.getUser(userId).getCheckedData().getNickname());
codegenTableMapper.insert(table);

View File

@ -23,9 +23,12 @@ import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
import cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum;
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;
import cn.iocoder.yudao.module.infra.enums.codegen.CodegenSceneEnum;
import cn.iocoder.yudao.module.infra.framework.codegen.config.CodegenProperties;
import com.google.common.collect.ImmutableTable;
import com.google.common.collect.Maps;
import com.google.common.collect.Table;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
@ -50,11 +53,12 @@ import static cn.hutool.core.text.CharSequenceUtil.*;
public class CodegenEngine {
/**
* 模板配置
* 后端的模板配置
*
* key模板在 resources 的地址
* value生成的路径
*/
private static final Map<String, String> TEMPLATES = MapUtil.<String, String>builder(new LinkedHashMap<>()) // 有序
private static final Map<String, String> SERVER_TEMPLATES = MapUtil.<String, String>builder(new LinkedHashMap<>()) // 有序
// Java module-biz Main
.put(javaTemplatePath("controller/vo/baseVO"), javaModuleImplVOFilePath("BaseVO"))
.put(javaTemplatePath("controller/vo/createReqVO"), javaModuleImplVOFilePath("CreateReqVO"))
@ -80,25 +84,40 @@ public class CodegenEngine {
javaModuleImplTestFilePath("service/${table.businessName}/${table.className}ServiceImplTest"))
// Java module-api Main
.put(javaTemplatePath("enums/errorcode"), javaModuleApiMainFilePath("enums/ErrorCodeConstants_手动操作"))
// Vue2
.put(vueTemplatePath("views/index.vue"),
vueFilePath("views/${table.moduleName}/${classNameVar}/index.vue"))
.put(vueTemplatePath("api/api.js"),
vueFilePath("api/${table.moduleName}/${classNameVar}.js"))
// Vue3
.put(vue3TemplatePath("views/index.vue"),
vue3FilePath("views/${table.moduleName}/${classNameVar}/index.vue"))
.put(vue3TemplatePath("views/data.ts"),
vue3FilePath("views/${table.moduleName}/${classNameVar}/${classNameVar}.data.ts"))
.put(vue3TemplatePath("api/api.ts"),
vue3FilePath("api/${table.moduleName}/${classNameVar}/index.ts"))
.put(vue3TemplatePath("api/types.ts"),
vue3FilePath("api/${table.moduleName}/${classNameVar}/types.ts"))
// SQL
.put("codegen/sql/sql.vm", "sql/sql.sql")
.put("codegen/sql/h2.vm", "sql/h2.sql")
.build();
/**
* 后端的配置模版
*
* key1UI 模版的类型 {@link CodegenFrontTypeEnum#getType()}
* key2模板在 resources 的地址
* value生成的路径
*/
private static final Table<Integer, String, String> FRONT_TEMPLATES = ImmutableTable.<Integer, String, String>builder()
// Vue2 标准模版
.put(CodegenFrontTypeEnum.VUE2.getType(), vueTemplatePath("views/index.vue"),
vueFilePath("views/${table.moduleName}/${classNameVar}/index.vue"))
.put(CodegenFrontTypeEnum.VUE2.getType(), vueTemplatePath("api/api.js"),
vueFilePath("api/${table.moduleName}/${classNameVar}.js"))
// Vue3 标准模版
.put(CodegenFrontTypeEnum.VUE3.getType(), vue3TemplatePath("views/index.vue"),
vue3FilePath("views/${table.moduleName}/${classNameVar}/index.vue"))
.put(CodegenFrontTypeEnum.VUE3.getType(), vue3TemplatePath("views/form.vue"),
vue3FilePath("views/${table.moduleName}/${classNameVar}/${simpleClassName}Form.vue"))
.put(CodegenFrontTypeEnum.VUE3.getType(), vue3TemplatePath("api/api.ts"),
vue3FilePath("api/${table.moduleName}/${classNameVar}/index.ts"))
// Vue3 Schema 模版
.put(CodegenFrontTypeEnum.VUE3_SCHEMA.getType(), vue3SchemaTemplatePath("views/data.ts"),
vue3FilePath("views/${table.moduleName}/${classNameVar}/${classNameVar}.data.ts"))
.put(CodegenFrontTypeEnum.VUE3_SCHEMA.getType(), vue3SchemaTemplatePath("views/index.vue"),
vue3FilePath("views/${table.moduleName}/${classNameVar}/index.vue"))
.put(CodegenFrontTypeEnum.VUE3_SCHEMA.getType(), vue3SchemaTemplatePath("views/form.vue"),
vue3FilePath("views/${table.moduleName}/${classNameVar}/${simpleClassName}Form.vue"))
.build();
@Resource
private CodegenProperties codegenProperties;
@ -167,20 +186,32 @@ public class CodegenEngine {
bindingMap.put("permissionPrefix", table.getModuleName() + ":" + simpleClassNameStrikeCase);
// 执行生成
final Map<String, String> result = Maps.newLinkedHashMapWithExpectedSize(TEMPLATES.size()); // 有序
TEMPLATES.forEach((vmPath, filePath) -> {
Map<String, String> templates = getTemplates(table.getFrontType());
Map<String, String> result = Maps.newLinkedHashMapWithExpectedSize(templates.size()); // 有序
templates.forEach((vmPath, filePath) -> {
filePath = formatFilePath(filePath, bindingMap);
String content = templateEngine.getTemplate(vmPath).render(bindingMap);
// 去除字段后面多余的 , 逗号
content = content.replaceAll(",\n}", "\n}").replaceAll(",\n }", "\n }");
result.put(filePath, content);
});
return result;
}
private Map<String, String> getTemplates(Integer frontType) {
Map<String, String> templates = new LinkedHashMap<>();
templates.putAll(SERVER_TEMPLATES);
templates.putAll(FRONT_TEMPLATES.row(frontType));
return templates;
}
private String formatFilePath(String filePath, Map<String, Object> bindingMap) {
filePath = StrUtil.replace(filePath, "${basePackage}",
getStr(bindingMap, "basePackage").replaceAll("\\.", "/"));
filePath = StrUtil.replace(filePath, "${classNameVar}",
getStr(bindingMap, "classNameVar"));
filePath = StrUtil.replace(filePath, "${simpleClassName}",
getStr(bindingMap, "simpleClassName"));
// sceneEnum 包含的字段
CodegenSceneEnum sceneEnum = (CodegenSceneEnum) bindingMap.get("sceneEnum");
filePath = StrUtil.replace(filePath, "${sceneEnum.prefixClass}", sceneEnum.getPrefixClass());
@ -239,6 +270,7 @@ public class CodegenEngine {
return "yudao-ui-${sceneEnum.basePackage}/" + // 顶级目录
"src/" + path;
}
private static String vue3TemplatePath(String path) {
return "codegen/vue3/" + path + ".vm";
}
@ -247,4 +279,8 @@ public class CodegenEngine {
return "yudao-ui-${sceneEnum.basePackage}-vue3/" + // 顶级目录
"src/" + path;
}
private static String vue3SchemaTemplatePath(String path) {
return "codegen/vue3_schema/" + path + ".vm";
}
}

View File

@ -128,6 +128,7 @@ yudao:
codegen:
base-package: cn.iocoder.yudao
db-schemas: ${spring.datasource.dynamic.datasource.master.name}
front-type: 10 # 前端模版的类型,参见 CodegenFrontTypeEnum 枚举类
error-code: # 错误码相关配置项
constants-class-list:
- cn.iocoder.yudao.module.infra.enums.ErrorCodeConstants

View File

@ -1,17 +1,17 @@
## @formatter:off
package ${basePackage}.module.${table.moduleName}.controller.${sceneEnum.basePackage}.${table.businessName};
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import org.springframework.validation.annotation.Validated;
#if ($sceneEnum.scene == 1)import org.springframework.security.access.prepost.PreAuthorize;#end
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.Parameter;
import io.swagger.v3.oas.annotations.Operation;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.Valid;
import javax.validation.constraints.*;
import javax.validation.*;
import javax.servlet.http.*;
import java.util.*;
import java.io.IOException;
@ -24,7 +24,6 @@ import ${ExcelUtilsClassName};
import ${OperateLogClassName};
import static ${OperateTypeEnumClassName}.*;
import ${basePackage}.framework.operatelog.core.annotations.OperateLog;
import ${basePackage}.module.${table.moduleName}.controller.${sceneEnum.basePackage}.${table.businessName}.vo.*;
import ${basePackage}.module.${table.moduleName}.dal.dataobject.${table.businessName}.${table.className}DO;
import ${basePackage}.module.${table.moduleName}.convert.${table.businessName}.${table.className}Convert;
@ -42,18 +41,16 @@ public class ${sceneEnum.prefixClass}${table.className}Controller {
@PostMapping("/create")
@Operation(summary = "创建${table.classComment}")
#if ($sceneEnum.scene == 1)
@PreAuthorize("@ss.hasPermission('${permissionPrefix}:create')")
#end
#if ($sceneEnum.scene == 1) @PreAuthorize("@ss.hasPermission('${permissionPrefix}:create')")#end
public CommonResult<${primaryColumn.javaType}> create${simpleClassName}(@Valid @RequestBody ${sceneEnum.prefixClass}${table.className}CreateReqVO createReqVO) {
return success(${classNameVar}Service.create${simpleClassName}(createReqVO));
}
@PutMapping("/update")
@Operation(summary = "更新${table.classComment}")
#if ($sceneEnum.scene == 1)
@PreAuthorize("@ss.hasPermission('${permissionPrefix}:update')")
#end
#if ($sceneEnum.scene == 1) @PreAuthorize("@ss.hasPermission('${permissionPrefix}:update')")#end
public CommonResult<Boolean> update${simpleClassName}(@Valid @RequestBody ${sceneEnum.prefixClass}${table.className}UpdateReqVO updateReqVO) {
${classNameVar}Service.update${simpleClassName}(updateReqVO);
return success(true);
@ -62,9 +59,8 @@ public class ${sceneEnum.prefixClass}${table.className}Controller {
@DeleteMapping("/delete")
@Operation(summary = "删除${table.classComment}")
@Parameter(name = "id", description = "编号", required = true)
#if ($sceneEnum.scene == 1)
@PreAuthorize("@ss.hasPermission('${permissionPrefix}:delete')")
#end
#if ($sceneEnum.scene == 1) @PreAuthorize("@ss.hasPermission('${permissionPrefix}:delete')")#end
public CommonResult<Boolean> delete${simpleClassName}(@RequestParam("id") ${primaryColumn.javaType} id) {
${classNameVar}Service.delete${simpleClassName}(id);
return success(true);
@ -73,9 +69,8 @@ public class ${sceneEnum.prefixClass}${table.className}Controller {
@GetMapping("/get")
@Operation(summary = "获得${table.classComment}")
@Parameter(name = "id", description = "编号", required = true, example = "1024")
#if ($sceneEnum.scene == 1)
@PreAuthorize("@ss.hasPermission('${permissionPrefix}:query')")
#end
#if ($sceneEnum.scene == 1) @PreAuthorize("@ss.hasPermission('${permissionPrefix}:query')")#end
public CommonResult<${sceneEnum.prefixClass}${table.className}RespVO> get${simpleClassName}(@RequestParam("id") ${primaryColumn.javaType} id) {
${table.className}DO ${classNameVar} = ${classNameVar}Service.get${simpleClassName}(id);
return success(${table.className}Convert.INSTANCE.convert(${classNameVar}));
@ -84,9 +79,8 @@ public class ${sceneEnum.prefixClass}${table.className}Controller {
@GetMapping("/list")
@Operation(summary = "获得${table.classComment}列表")
@Parameter(name = "ids", description = "编号列表", required = true, example = "1024,2048")
#if ($sceneEnum.scene == 1)
@PreAuthorize("@ss.hasPermission('${permissionPrefix}:query')")
#end
#if ($sceneEnum.scene == 1) @PreAuthorize("@ss.hasPermission('${permissionPrefix}:query')")#end
public CommonResult<List<${sceneEnum.prefixClass}${table.className}RespVO>> get${simpleClassName}List(@RequestParam("ids") Collection<${primaryColumn.javaType}> ids) {
List<${table.className}DO> list = ${classNameVar}Service.get${simpleClassName}List(ids);
return success(${table.className}Convert.INSTANCE.convertList(list));
@ -94,9 +88,8 @@ public class ${sceneEnum.prefixClass}${table.className}Controller {
@GetMapping("/page")
@Operation(summary = "获得${table.classComment}分页")
#if ($sceneEnum.scene == 1)
@PreAuthorize("@ss.hasPermission('${permissionPrefix}:query')")
#end
#if ($sceneEnum.scene == 1) @PreAuthorize("@ss.hasPermission('${permissionPrefix}:query')")#end
public CommonResult<PageResult<${sceneEnum.prefixClass}${table.className}RespVO>> get${simpleClassName}Page(@Valid ${sceneEnum.prefixClass}${table.className}PageReqVO pageVO) {
PageResult<${table.className}DO> pageResult = ${classNameVar}Service.get${simpleClassName}Page(pageVO);
return success(${table.className}Convert.INSTANCE.convertPage(pageResult));
@ -104,16 +97,15 @@ public class ${sceneEnum.prefixClass}${table.className}Controller {
@GetMapping("/export-excel")
@Operation(summary = "导出${table.classComment} Excel")
#if ($sceneEnum.scene == 1)
@PreAuthorize("@ss.hasPermission('${permissionPrefix}:export')")
#end
#if ($sceneEnum.scene == 1) @PreAuthorize("@ss.hasPermission('${permissionPrefix}:export')")#end
@OperateLog(type = EXPORT)
public void export${simpleClassName}Excel(@Valid ${sceneEnum.prefixClass}${table.className}ExportReqVO exportReqVO,
HttpServletResponse response) throws IOException {
List<${table.className}DO> list = ${classNameVar}Service.get${simpleClassName}List(exportReqVO);
// 导出 Excel
List<${sceneEnum.prefixClass}${table.className}ExcelVO> datas = ${table.className}Convert.INSTANCE.convertList02(list);
ExcelUtils.write(response, "${table.classComment}.xls","数据", ${sceneEnum.prefixClass}${table.className}ExcelVO.class, datas);
ExcelUtils.write(response, "${table.classComment}.xls", "数据", ${sceneEnum.prefixClass}${table.className}ExcelVO.class, datas);
}
}

View File

@ -1,6 +1,5 @@
## @formatter:off
## 提供给 baseVO、createVO、updateVO 生成字段
@Schema(description = "${column.columnComment}"#if (!${column.nullable}), requiredMode = Schema.RequiredMode.REQUIRED#end#if ("$!column.example" != ""), example = "${column.example}"#end)
@Schema(description = "${column.columnComment}"#if (!${column.nullable}), required = true#end#if ("$!column.example" != ""), example = "${column.example}"#end)
#if (!${column.nullable})## 判断 @NotEmpty 和 @NotNull 注解
#if (${field.fieldType} == 'String')
@NotEmpty(message = "${column.columnComment}不能为空")
@ -11,4 +10,4 @@
#if (${column.javaType} == "LocalDateTime")## 时间类型
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
#end
private ${column.javaType} ${column.javaField};
private ${column.javaType} ${column.javaField};

View File

@ -1,6 +1,6 @@
## @formatter:off
package ${basePackage}.module.${table.moduleName}.controller.${sceneEnum.basePackage}.${table.businessName}.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.*;
import java.util.*;
#foreach ($column in $columns)
@ -11,11 +11,11 @@ import java.math.BigDecimal;
import java.time.LocalDateTime;
#end
#end
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.*;
import javax.validation.constraints.*;
## 处理 Date 字段的引入
#foreach ($column in $columns)
#if (${column.createOperation} && ${column.updateOperation} && ${column.listOperationResult} && ${column.javaType} == "LocalDateTime")## 时间类型
#if (${column.createOperation} && ${column.updateOperation} && ${column.listOperationResult}
&& ${column.javaType} == "LocalDateTime")## 时间类型
import org.springframework.format.annotation.DateTimeFormat;
import static ${DateUtilsClassName}.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;

View File

@ -1,13 +1,13 @@
## @formatter:off
package ${basePackage}.module.${table.moduleName}.controller.${sceneEnum.basePackage}.${table.businessName}.vo;
import lombok.*;
import java.util.*;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.*;
import javax.validation.constraints.*;
## 处理 Date 字段的引入
#foreach ($column in $columns)
#if (${column.createOperation} && (!${column.updateOperation} || !${column.listOperationResult}) && ${column.javaType} == "LocalDateTime")## 时间类型
#if (${column.createOperation} && (!${column.updateOperation} || !${column.listOperationResult})
&& ${column.javaType} == "LocalDateTime")## 时间类型
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;
import static ${DateUtilsClassName}.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
@ -15,7 +15,7 @@ import static ${DateUtilsClassName}.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
#end
#end
@Schema(name = "${sceneEnum.name} - ${table.classComment}创建 Request VO")
@Schema(description = "${sceneEnum.name} - ${table.classComment}创建 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)

View File

@ -1,6 +1,6 @@
## @formatter:off
package ${basePackage}.module.${table.moduleName}.controller.${sceneEnum.basePackage}.${table.businessName}.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.*;
import java.util.*;
#foreach ($column in $columns)
@ -11,6 +11,7 @@ import java.math.BigDecimal;
import java.time.LocalDateTime;
#end
#end
import com.alibaba.excel.annotation.ExcelProperty;
#foreach ($column in $columns)
#if ("$!column.dictType" != "")## 有设置数据字典
@ -22,10 +23,10 @@ import ${DictConvertClassName};
#end
/**
* ${table.classComment} Excel VO
*
* @author ${table.author}
*/
* ${table.classComment} Excel VO
*
* @author ${table.author}
*/
@Data
public class ${sceneEnum.prefixClass}${table.className}ExcelVO {

View File

@ -1,4 +1,3 @@
## @formatter:off
package ${basePackage}.module.${table.moduleName}.controller.${sceneEnum.basePackage}.${table.businessName}.vo;
import lombok.*;
@ -21,7 +20,7 @@ import static ${DateUtilsClassName}.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
private ${column.javaType}#if ("$!prefix" != "") ${prefix}${JavaField}#else ${column.javaField}#end;
#end
@Schema(name = "${sceneEnum.name} - ${table.classComment} Excel 导出 Request VO", description = "参数和 ${table.className}PageReqVO 是一致的")
@Schema(description = "${sceneEnum.name} - ${table.classComment} Excel 导出 Request VO参数和 ${table.className}PageReqVO 是一致的")
@Data
public class ${sceneEnum.prefixClass}${table.className}ExportReqVO {

View File

@ -1,4 +1,3 @@
## @formatter:off
package ${basePackage}.module.${table.moduleName}.controller.${sceneEnum.basePackage}.${table.businessName}.vo;
import lombok.*;
@ -21,7 +20,7 @@ import static ${DateUtilsClassName}.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
private ${column.javaType}#if ("$!prefix" != "") ${prefix}${JavaField}#else ${column.javaField}#end;
#end
@Schema(name = "${sceneEnum.name} - ${table.classComment}分页 Request VO")
@Schema(description = "${sceneEnum.name} - ${table.classComment}分页 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)

View File

@ -1,8 +1,7 @@
## @formatter:off
package ${basePackage}.module.${table.moduleName}.controller.${sceneEnum.basePackage}.${table.businessName}.vo;
import lombok.*;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.*;
#foreach ($column in $columns)
#if (${column.javaType} == "LocalDateTime")
import java.time.LocalDateTime;
@ -10,7 +9,7 @@ import java.time.LocalDateTime;
#end
#end
@Schema(name = "${sceneEnum.name} - ${table.classComment} Response VO")
@Schema(description = "${sceneEnum.name} - ${table.classComment} Response VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@ -18,7 +17,7 @@ public class ${sceneEnum.prefixClass}${table.className}RespVO extends ${sceneEnu
#foreach ($column in $columns)
#if (${column.listOperationResult} && (!${column.createOperation} || !${column.updateOperation}))##不是通用字段
@Schema(description = "${column.columnComment}"#if (!${column.nullable}), requiredMode = Schema.RequiredMode.REQUIRED#end#if ("$!column.example" != ""), example = "${column.example}"#end)
@Schema(description = "${column.columnComment}"#if (!${column.nullable}), required = true#end#if ("$!column.example" != ""), example = "${column.example}"#end)
private ${column.javaType} ${column.javaField};
#end

View File

@ -1,13 +1,13 @@
## @formatter:off
package ${basePackage}.module.${table.moduleName}.controller.${sceneEnum.basePackage}.${table.businessName}.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.*;
import java.util.*;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.*;
import javax.validation.constraints.*;
## 处理 Date 字段的引入
#foreach ($column in $columns)
#if (${column.updateOperation} && (!${column.createOperation} || !${column.listOperationResult}) && ${column.javaType} == "LocalDateTime")## 时间类型
#if (${column.updateOperation} && (!${column.createOperation} || !${column.listOperationResult})
&& ${column.javaType} == "LocalDateTime")## 时间类型
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;
import static ${DateUtilsClassName}.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
@ -15,7 +15,7 @@ import static ${DateUtilsClassName}.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
#end
#end
@Schema(name = "${sceneEnum.name} - ${table.classComment}更新 Request VO")
@Schema(description = "${sceneEnum.name} - ${table.classComment}更新 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)

View File

@ -1,11 +1,11 @@
-- 菜单 SQL
INSERT INTO system_menu(
name, permission, type, sort, parent_id,
path, icon, component, status
path, icon, component, status, component_name
)
VALUES (
'${table.classComment}管理', '', 2, 0, ${table.parentMenuId},
'${simpleClassName_strikeCase}', '', '${table.moduleName}/${classNameVar}/index', 0
'${simpleClassName_strikeCase}', '', '${table.moduleName}/${classNameVar}/index', 0, '${table.className}'
);
-- 按钮父菜单ID

View File

@ -14,61 +14,32 @@ export interface ${simpleClassName}VO {
#end
}
export interface ${simpleClassName}PageReqVO extends PageParam {
#foreach ($column in $columns)
#if (${column.listOperation})##查询操作
#if(${column.javaType.toLowerCase()} == "long" || ${column.javaType.toLowerCase()} == "integer" || ${column.javaType.toLowerCase()} == "double" || ${column.javaType.toLowerCase()} == "bigdecimal")
${column.javaField}?: number
#elseif(${column.javaType.toLowerCase()} == "date" || ${column.javaType.toLowerCase()} == "localdatetime")
${column.javaField}?: Date[]
#else
${column.javaField}?: ${column.javaType.toLowerCase()}
#end
#end
#end
}
export interface ${simpleClassName}ExcelReqVO {
#foreach ($column in $columns)
#if (${column.listOperation})##查询操作
#if(${column.javaType.toLowerCase()} == "long" || ${column.javaType.toLowerCase()} == "integer" || ${column.javaType.toLowerCase()} == "double" || ${column.javaType.toLowerCase()} == "bigdecimal")
${column.javaField}?: number
#elseif(${column.javaType.toLowerCase()} == "date" || ${column.javaType.toLowerCase()} == "localdatetime")
${column.javaField}?: Date[]
#else
${column.javaField}?: ${column.javaType.toLowerCase()}
#end
#end
#end
}
#set ($baseURL = "/${table.moduleName}/${simpleClassName_strikeCase}")
// 查询${table.classComment}列表
export const get${simpleClassName}PageApi = async (params: ${simpleClassName}PageReqVO) => {
export const get${simpleClassName}Page = async (params: ${simpleClassName}PageReqVO) => {
return await request.get({ url: '${baseURL}/page', params })
}
// 查询${table.classComment}详情
export const get${simpleClassName}Api = async (id: number) => {
export const get${simpleClassName} = async (id: number) => {
return await request.get({ url: '${baseURL}/get?id=' + id })
}
// 新增${table.classComment}
export const create${simpleClassName}Api = async (data: ${simpleClassName}VO) => {
export const create${simpleClassName} = async (data: ${simpleClassName}VO) => {
return await request.post({ url: '${baseURL}/create', data })
}
// 修改${table.classComment}
export const update${simpleClassName}Api = async (data: ${simpleClassName}VO) => {
export const update${simpleClassName} = async (data: ${simpleClassName}VO) => {
return await request.put({ url: '${baseURL}/update', data })
}
// 删除${table.classComment}
export const delete${simpleClassName}Api = async (id: number) => {
export const delete${simpleClassName} = async (id: number) => {
return await request.delete({ url: '${baseURL}/delete?id=' + id })
}
// 导出${table.classComment} Excel
export const export${simpleClassName}Api = async (params: ${simpleClassName}ExcelReqVO) => {
export const export${simpleClassName}Api = async (params) => {
return await request.download({ url: '${baseURL}/export-excel', params })
}

View File

@ -1,41 +0,0 @@
export type ${simpleClassName}VO = {
#foreach ($column in $columns)
#if ($column.createOperation || $column.updateOperation)
#if(${column.javaType.toLowerCase()} == "long" || ${column.javaType.toLowerCase()} == "integer")
${column.javaField}: number
#elseif(${column.javaType.toLowerCase()} == "date")
${column.javaField}: string
#else
${column.javaField}: ${column.javaType.toLowerCase()}
#end
#end
#end
}
export type ${simpleClassName}PageReqVO = {
#foreach ($column in $columns)
#if (${column.listOperation})##查询操作
#if(${column.javaType.toLowerCase()} == "long" || ${column.javaType.toLowerCase()} == "integer")
${column.javaField}: number
#elseif(${column.javaType.toLowerCase()} == "date")
${column.javaField}: string
#else
${column.javaField}: ${column.javaType.toLowerCase()}
#end
#end
#end
}
export type ${simpleClassName}ExcelReqVO = {
#foreach ($column in $columns)
#if (${column.listOperation})##查询操作
#if(${column.javaType.toLowerCase()} == "long" || ${column.javaType.toLowerCase()} == "integer")
${column.javaField}: number
#elseif(${column.javaType.toLowerCase()} == "date")
${column.javaField}: string
#else
${column.javaField}: ${column.javaType.toLowerCase()}
#end
#end
#end
}

View File

@ -1,110 +0,0 @@
import { reactive } from 'vue'
import { useI18n } from '@/hooks/web/useI18n'
import { DICT_TYPE } from '@/utils/dict'
import { required } from '@/utils/formRules'
import { VxeCrudSchema, useVxeCrudSchemas } from '@/hooks/web/useVxeCrudSchemas'
const { t } = useI18n() // 国际化
// 表单校验
export const rules = reactive({
#foreach ($column in $columns)
#if (($column.createOperation || $column.updateOperation) && !$column.nullable && !${column.primaryKey})## 创建或者更新操作 && 要求非空 && 非主键
#set($comment=$column.columnComment)
$column.javaField: [required],
#end
#end
})
// CrudSchema
const crudSchemas = reactive<VxeCrudSchema>({
primaryKey: 'id', // 默认的主键ID
primaryTitle: t('common.index'), // 默认显示的值
primaryType: 'seq', // 默认为seq序号模式
action: true,
actionWidth: '200', // 3个按钮默认200如有删减对应增减即可
columns: [
#foreach($column in $columns)
#if ($column.listOperation || $column.listOperationResult || $column.createOperation || $column.updateOperation)
#set ($dictType = $column.dictType)
#if(!$column.primaryKey)
{
title: '${column.columnComment}',
field: '${column.javaField}',
#if (!$column.listOperationResult)
isTable: false,
#end
#if ("" != $dictType)## 有数据字典
dictType: DICT_TYPE.$dictType.toUpperCase(),
#if (${column.javaType.toLowerCase()} == "long" || ${column.javaType.toLowerCase()} == "integer")
dictClass: 'number',
#else
dictClass: 'string',
#end
#end
#if (!$column.createOperation && !$column.updateOperation)
isForm: false,
#elseif(!("" != $column.dictType))
#if (${column.javaType.toLowerCase()} == "date" || ${column.javaType.toLowerCase()} == "localdatetime")
form: {
component: 'DatePicker',
componentProps: {
type: 'datetime',
valueFormat: 'x'
}
},
#elseif($column.htmlType == "editor")## 文本编辑器
form: {
component: 'Editor',
colProps: {
span: 24
},
componentProps: {
valueHtml: ''
}
},
#elseif($column.htmlType == "textarea")## 文本框
form: {
component: 'Input',
componentProps: {
type: 'textarea',
rows: 4
},
colProps: {
span: 24
}
},
#elseif(${column.javaType.toLowerCase()} == "long" || ${column.javaType.toLowerCase()} == "integer")## 数字类型
form: {
component: 'InputNumber',
value: 0
},
#elseif($column.htmlType == "imageUpload")## 图片上传
form: {
component: 'UploadImg' // 单图上传多图为UploadImgs
},
#elseif($column.htmlType == "fileUpload")## 图片上传
form: {
component: 'UploadFile'
},
#end
#end
#if ($column.listOperation)
#if($column.htmlType == "input")
isSearch: true,
#elseif("" != $dictType)
isSearch: true,
#elseif($column.htmlType == "datetime")
formatter: 'formatDate',
search: {
show: true,
itemRender: {
name: 'XDataTimePicker'
}
},
#end
#end
},
#end
#end
#end
]
})
export const { allSchemas } = useVxeCrudSchemas(crudSchemas)

View File

@ -0,0 +1,234 @@
<template>
<Dialog :title="dialogTitle" v-model="dialogVisible">
<el-form
ref="formRef"
:model="formData"
:rules="formRules"
label-width="100px"
v-loading="formLoading"
>
#set ($dictMethods = [])## 使用到的 dict 字典方法
#foreach($column in $columns)
#if ($column.createOperation || $column.updateOperation)
#set ($dictType = $column.dictType)
#set ($javaField = $column.javaField)
#set ($javaType = $column.javaType)
#set ($AttrName = $column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
#set ($comment = $column.columnComment)
#set ($dictMethod = "getDictOptions")## 计算使用哪个 dict 字典方法
#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 ($column.htmlType == "input" && !$column.primaryKey)## 忽略主键,不用在表单里
<el-form-item label="${comment}" prop="${javaField}">
<el-input v-model="formData.${javaField}" placeholder="请输入${comment}" />
</el-form-item>
#elseif($column.htmlType == "imageUpload")## 图片上传
#set ($hasImageUploadColumn = true)
<el-form-item label="${comment}">
<UploadImg v-model="formData.${javaField}" />
</el-form-item>
#elseif($column.htmlType == "fileUpload")## 文件上传
#set ($hasFileUploadColumn = true)
<el-form-item label="${comment}">
<UploadFile v-model="formData.${javaField}" />
</el-form-item>
#elseif($column.htmlType == "editor")## 文本编辑器
<el-form-item label="${comment}">
<Editor :model-value="formData.${javaField}" height="150px" />
</el-form-item>
#elseif($column.htmlType == "select")## 下拉框
<el-form-item label="${comment}" prop="${javaField}">
<el-select v-model="formData.${javaField}" placeholder="请选择${comment}">
#if ("" != $dictType)## 有数据字典
#if (!$dictMethods.contains($dictMethod))## 如果不存在,则添加到 dictMethods 数组中,后续好 import
#set($ignore = $dictMethods.add($dictMethod) )
#end
<el-option
v-for="dict in $dictMethod(DICT_TYPE.$dictType.toUpperCase())"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
#else##没数据字典
<el-option label="请选择字典生成" value="" />
#end
</el-select>
</el-form-item>
#elseif($column.htmlType == "checkbox")## 多选框
<el-form-item label="${comment}" prop="${javaField}">
<el-checkbox-group v-model="formData.${javaField}">
#if ("" != $dictType)## 有数据字典
#if (!$dictMethods.contains($dictMethod))## 如果不存在,则添加到 dictMethods 数组中,后续好 import
#set($ignore = $dictMethods.add($dictMethod) )
#end
<el-checkbox
v-for="dict in $dictMethod(DICT_TYPE.$dictType.toUpperCase())"
:key="dict.value"
:label="dict.value"
>
{{ dict.label }}
</el-checkbox>
#else##没数据字典
<el-checkbox>请选择字典生成</el-checkbox>
#end
</el-checkbox-group>
</el-form-item>
#elseif($column.htmlType == "radio")## 单选框
<el-form-item label="${comment}" prop="${javaField}">
<el-radio-group v-model="formData.${javaField}">
#if ("" != $dictType)## 有数据字典
#if (!$dictMethods.contains($dictMethod))## 如果不存在,则添加到 dictMethods 数组中,后续好 import
#set($ignore = $dictMethods.add($dictMethod) )
#end
<el-radio
v-for="dict in $dictMethod(DICT_TYPE.$dictType.toUpperCase())"
:key="dict.value"
:label="dict.value"
>
{{ dict.label }}
</el-radio>
#else##没数据字典
<el-radio label="1">请选择字典生成</el-radio>
#end
</el-radio-group>
</el-form-item>
#elseif($column.htmlType == "datetime")## 时间框
<el-form-item label="${comment}" prop="${javaField}">
<el-date-picker
v-model="formData.${javaField}"
type="date"
value-format="timestamp"
placeholder="选择${comment}"
/>
</el-form-item>
#elseif($column.htmlType == "textarea")## 文本框
<el-form-item label="${comment}" prop="${javaField}">
<el-input v-model="formData.${javaField}" type="textarea" placeholder="请输入${comment}" />
</el-form-item>
#end
#end
#end
</el-form>
<template #footer>
<el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button>
<el-button @click="dialogVisible = false">取 消</el-button>
</template>
</Dialog>
</template>
<script setup lang="ts">
#if ($dictMethods.size() > 0)
import { DICT_TYPE#foreach ($dictMethod in $dictMethods), ${dictMethod}#end } from '@/utils/dict'
#end
import * as ${simpleClassName}Api from '@/api/${table.moduleName}/${classNameVar}'
const { t } = useI18n() // 国际化
const message = useMessage() // 消息弹窗
const dialogVisible = ref(false) // 弹窗的是否展示
const dialogTitle = ref('') // 弹窗的标题
const formLoading = ref(false) // 表单的加载中1修改时的数据加载2提交的按钮禁用
const formType = ref('') // 表单的类型create - 新增update - 修改
const formData = ref({
#set ($listOperationLastIndex = -1)## 求最后一个需要 , 的地方
#foreach ($column in $columns)
#if ($column.createOperation || $column.updateOperation)
#set ($listOperationLastIndex = $foreach.index)
#end
#end
#foreach ($column in $columns)
#if ($column.createOperation || $column.updateOperation)
#if ($column.htmlType == "checkbox")
$column.javaField: []#if($foreach.index < $listOperationLastIndex),#end
#else
$column.javaField: undefined#if($foreach.index < $listOperationLastIndex),#end
#end
#end
#end
})
const formRules = reactive({
#set ($listOperationLastIndex = -1)## 求最后一个需要 , 的地方
#foreach ($column in $columns)
#if (($column.createOperation || $column.updateOperation) && !$column.nullable && !${column.primaryKey})## 创建或者更新操作 && 要求非空 && 非主键
#set ($listOperationLastIndex = $foreach.index)
#end
#end
#foreach ($column in $columns)
#if (($column.createOperation || $column.updateOperation) && !$column.nullable && !${column.primaryKey})## 创建或者更新操作 && 要求非空 && 非主键
#set($comment=$column.columnComment)
$column.javaField: [{ required: true, message: '${comment}不能为空', trigger: #if($column.htmlType == 'select')'change'#else'blur'#end }]#if($foreach.index < $listOperationLastIndex),#end
#end
#end
})
const formRef = ref() // 表单 Ref
/** 打开弹窗 */
const open = async (type: string, id?: number) => {
dialogVisible.value = true
dialogTitle.value = t('action.' + type)
formType.value = type
resetForm()
// 修改时,设置数据
if (id) {
formLoading.value = true
try {
formData.value = await ${simpleClassName}Api.get${simpleClassName}(id)
} finally {
formLoading.value = false
}
}
}
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
/** 提交表单 */
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
const submitForm = async () => {
// 校验表单
if (!formRef) return
const valid = await formRef.value.validate()
if (!valid) return
// 提交请求
formLoading.value = true
try {
const data = formData.value as unknown as ${simpleClassName}Api.${simpleClassName}VO
if (formType.value === 'create') {
await ${simpleClassName}Api.create${simpleClassName}(data)
message.success(t('common.createSuccess'))
} else {
await ${simpleClassName}Api.update${simpleClassName}(data)
message.success(t('common.updateSuccess'))
}
dialogVisible.value = false
// 发送操作成功的事件
emit('success')
} finally {
formLoading.value = false
}
}
/** 重置表单 */
const resetForm = () => {
formData.value = {
#set ($listOperationLastIndex = -1)## 求最后一个需要 , 的地方
#foreach ($column in $columns)
#if ($column.createOperation || $column.updateOperation)
#set ($listOperationLastIndex = $foreach.index)
#end
#end
#foreach ($column in $columns)
#if ($column.createOperation || $column.updateOperation)
#if ($column.htmlType == "checkbox")
$column.javaField: []#if($foreach.index < $listOperationLastIndex),#end
#else
$column.javaField: undefined#if($foreach.index < $listOperationLastIndex),#end
#end
#end
#end
}
formRef.value?.resetFields()
}
</script>

View File

@ -1,177 +1,285 @@
<template>
<ContentWrap>
<!-- 列表 -->
<vxe-grid ref="xGrid" v-bind="gridOptions" class="xtable-scrollbar">
<template #toolbar_buttons>
<!-- 操作:新增 -->
<XButton
<!-- 搜索工作栏 -->
<el-form
class="-mb-15px"
:model="queryParams"
ref="queryFormRef"
:inline="true"
label-width="68px"
>
#set ($dictMethods = [])## 使用到的 dict 字典方法
#foreach($column in $columns)
#if ($column.listOperation)
#set ($dictType = $column.dictType)
#set ($javaField = $column.javaField)
#set ($javaType = $column.javaType)
#set ($AttrName = $column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
#set ($comment = $column.columnComment)
#set ($dictMethod = "getDictOptions")## 计算使用哪个 dict 字典方法
#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 ($column.htmlType == "input")
<el-form-item label="${comment}" prop="${javaField}">
<el-input
v-model="queryParams.${javaField}"
placeholder="请输入${comment}"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
#elseif ($column.htmlType == "select" || $column.htmlType == "radio")
<el-form-item label="${comment}" prop="${javaField}">
#if ($javaField.length() + $comment.length() > 8)
<el-select
v-model="queryParams.${javaField}"
placeholder="请选择${comment}"
clearable
class="!w-240px"
>
#else
<el-select v-model="queryParams.${javaField}" placeholder="请选择${comment}" clearable class="!w-240px">
#end
#if ("" != $dictType)## 设置了 dictType 数据字典的情况
#if (!$dictMethods.contains($dictMethod))## 如果不存在,则添加到 dictMethods 数组中,后续好 import
#set($ignore = $dictMethods.add($dictMethod) )
#end
<el-option
v-for="dict in $dictMethod(DICT_TYPE.$dictType.toUpperCase())"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
#else## 未设置 dictType 数据字典的情况
<el-option label="请选择字典生成" value="" />
#end
</el-select>
</el-form-item>
#elseif($column.htmlType == "datetime")
#if ($column.listOperationCondition != "BETWEEN")## 非范围
<el-form-item label="${comment}" prop="${javaField}">
<el-date-picker
v-model="queryParams.${javaField}"
value-format="yyyy-MM-dd"
type="date"
placeholder="选择${comment}"
clearable
class="!w-240px"
/>
</el-form-item>
#else## 范围
<el-form-item label="${comment}" prop="${javaField}">
<el-date-picker
v-model="queryParams.${javaField}"
value-format="yyyy-MM-dd HH:mm:ss"
type="daterange"
start-placeholder="开始日期"
end-placeholder="结束日期"
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
class="!w-240px"
/>
</el-form-item>
#end
#end
#end
#end
<el-form-item>
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
#if ($permissionPrefix.length() <= 12)
<el-button type="primary" @click="openForm('create')" v-hasPermi="['${permissionPrefix}:create']">
#else
<el-button
type="primary"
preIcon="ep:zoom-in"
:title="t('action.add')"
plain
@click="openForm('create')"
v-hasPermi="['${permissionPrefix}:create']"
@click="handleCreate()"
/>
<!-- 操作:导出 -->
<XButton
type="warning"
preIcon="ep:download"
:title="t('action.export')"
>
#end
<Icon icon="ep:plus" class="mr-5px" /> 新增
</el-button>
<el-button
type="success"
plain
@click="handleExport"
:loading="exportLoading"
v-hasPermi="['${permissionPrefix}:export']"
@click="handleExport()"
/>
</template>
<template #actionbtns_default="{ row }">
<!-- 操作:修改 -->
<XTextButton
preIcon="ep:edit"
:title="t('action.edit')"
v-hasPermi="['${permissionPrefix}:update']"
@click="handleUpdate(row.id)"
/>
<!-- 操作:详情 -->
<XTextButton
preIcon="ep:view"
:title="t('action.detail')"
v-hasPermi="['${permissionPrefix}:query']"
@click="handleDetail(row.id)"
/>
<!-- 操作:删除 -->
<XTextButton
preIcon="ep:delete"
:title="t('action.del')"
v-hasPermi="['${permissionPrefix}:delete']"
@click="handleDelete(row.id)"
/>
</template>
</vxe-grid>
>
<Icon icon="ep:download" class="mr-5px" /> 导出
</el-button>
</el-form-item>
</el-form>
</ContentWrap>
<!-- 弹窗 -->
<XModal id="${classNameVar}Model" :loading="modelLoading" v-model="modelVisible" :title="modelTitle">
<!-- 表单:添加/修改 -->
<Form
ref="formRef"
v-if="['create', 'update'].includes(actionType)"
:schema="allSchemas.formSchema"
:rules="rules"
/>
<!-- 表单:详情 -->
<Descriptions
v-if="actionType === 'detail'"
:schema="allSchemas.detailSchema"
:data="detailData"
/>
<template #footer>
<!-- 按钮:保存 -->
<XButton
v-if="['create', 'update'].includes(actionType)"
type="primary"
:title="t('action.save')"
:loading="actionLoading"
@click="submitForm()"
<!-- 列表 -->
<ContentWrap>
<el-table v-loading="loading" :data="list">
#foreach($column in $columns)
#if ($column.listOperationResult)
#set ($dictType=$column.dictType)
#set ($javaField = $column.javaField)
#set ($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
#set ($comment=$column.columnComment)
#if ($column.javaType == "LocalDateTime")## 时间类型
<el-table-column
label="${comment}"
align="center"
prop="${javaField}"
:formatter="dateFormatter"
/>
<!-- 按钮:关闭 -->
<XButton :loading="actionLoading" :title="t('dialog.close')" @click="modelVisible = false" />
</template>
</XModal>
#elseif("" != $column.dictType)## 数据字典
<el-table-column label="${comment}" align="center" prop="${javaField}">
<template #default="scope">
<dict-tag :type="DICT_TYPE.$dictType.toUpperCase()" :value="scope.row.${column.javaField}" />
</template>
</el-table-column>
#else
<el-table-column label="${comment}" align="center" prop="${javaField}" />
#end
#end
#end
<el-table-column label="操作" align="center">
<template #default="scope">
<el-button
link
type="primary"
@click="openForm('update', scope.row.id)"
v-hasPermi="['${permissionPrefix}:update']"
>
编辑
</el-button>
<el-button
link
type="danger"
@click="handleDelete(scope.row.id)"
v-hasPermi="['${permissionPrefix}:delete']"
>
删除
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<Pagination
:total="total"
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
</ContentWrap>
<!-- 表单弹窗:添加/修改 -->
<${simpleClassName}Form ref="formRef" @success="getList" />
</template>
<script setup lang="ts" name="${simpleClassName}">
// 全局相关的 import
import { ref, unref } from 'vue'
import { useI18n } from '@/hooks/web/useI18n'
import { useMessage } from '@/hooks/web/useMessage'
import { useVxeGrid } from '@/hooks/web/useVxeGrid'
import { VxeGridInstance } from 'vxe-table'
import { FormExpose } from '@/components/Form'
// 业务相关的 import
import { rules, allSchemas } from './${classNameVar}.data'
<script setup lang="ts" name="${table.className}">
#if ($dictMethods.size() > 0)
import { DICT_TYPE#foreach ($dictMethod in $dictMethods), ${dictMethod}#end } from '@/utils/dict'
#end
#foreach ($column in $columns)
#if ($column.listOperationResult && $column.htmlType == "datetime")
import { dateFormatter } from '@/utils/formatTime'
#break
#end
#end
import download from '@/utils/download'
import * as ${simpleClassName}Api from '@/api/${table.moduleName}/${classNameVar}'
const { t } = useI18n() // 国际化
import ${simpleClassName}Form from './${simpleClassName}Form.vue'
const message = useMessage() // 消息弹窗
const { t } = useI18n() // 国际化
// 列表相关的变量
const xGrid = ref<VxeGridInstance>() // 列表 Grid Ref
const { gridOptions, getList, deleteData, exportList } = useVxeGrid<${simpleClassName}Api.${simpleClassName}VO>({
allSchemas: allSchemas,
getListApi: ${simpleClassName}Api.get${simpleClassName}PageApi,
deleteApi: ${simpleClassName}Api.delete${simpleClassName}Api,
exportListApi: ${simpleClassName}Api.export${simpleClassName}Api
const loading = ref(true) // 列表的加载中
const total = ref(0) // 列表的总页数
const list = ref([]) // 列表的数据
const queryParams = reactive({
pageNo: 1,
pageSize: 10,
#set ($listOperationLastIndex = -1)## 求最后一个需要 , 的地方
#foreach ($column in $columns)
#if ($column.listOperation)
#set ($listOperationLastIndex = $foreach.index)
#end
#end
#foreach ($column in $columns)
#if ($column.listOperation)
#if ($column.listOperationCondition != 'BETWEEN')
$column.javaField: null#if($foreach.index < $listOperationLastIndex),#end
#end
#if ($column.htmlType == "datetime" || $column.listOperationCondition == "BETWEEN")
$column.javaField: []#if($foreach.index < $listOperationLastIndex),#end
#end
#end
#end
})
const queryFormRef = ref() // 搜索的表单
const exportLoading = ref(false) // 导出的加载中
// 弹窗相关的变量
const modelVisible = ref(false) // 是否显示弹出层
const modelTitle = ref('edit') // 弹出层标题
const modelLoading = ref(false) // 弹出层loading
const actionType = ref('') // 操作按钮的类型
const actionLoading = ref(false) // 按钮 Loading
const formRef = ref<FormExpose>() // 表单 Ref
const detailData = ref() // 详情 Ref
// 设置标题
const setDialogTile = (type: string) => {
modelLoading.value = true
modelTitle.value = t('action.' + type)
actionType.value = type
modelVisible.value = true
/** 查询列表 */
const getList = async () => {
loading.value = true
try {
const data = await ${simpleClassName}Api.get${simpleClassName}Page(queryParams)
list.value = data.list
total.value = data.total
} finally {
loading.value = false
}
}
// 新增操作
const handleCreate = () => {
setDialogTile('create')
modelLoading.value = false
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.pageNo = 1
getList()
}
// 导出操作
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value.resetFields()
handleQuery()
}
/** 添加/修改操作 */
const formRef = ref()
const openForm = (type: string, id?: number) => {
formRef.value.open(type, id)
}
/** 删除按钮操作 */
const handleDelete = async (id: number) => {
try {
// 删除的二次确认
await message.delConfirm()
// 发起删除
await ${simpleClassName}Api.delete${simpleClassName}(id)
message.success(t('common.delSuccess'))
// 刷新列表
await getList()
} catch {}
}
/** 导出按钮操作 */
const handleExport = async () => {
await exportList(xGrid, '${table.classComment}.xls')
try {
// 导出的二次确认
await message.exportConfirm()
// 发起导出
exportLoading.value = true
const data = await ${simpleClassName}Api.export${simpleClassName}(queryParams)
download.excel(data, '${table.classComment}.xls')
} catch {
} finally {
exportLoading.value = false
}
}
// 修改操作
const handleUpdate = async (rowId: number) => {
setDialogTile('update')
// 设置数据
const res = await ${simpleClassName}Api.get${simpleClassName}Api(rowId)
unref(formRef)?.setValues(res)
modelLoading.value = false
}
// 详情操作
const handleDetail = async (rowId: number) => {
setDialogTile('detail')
const res = await ${simpleClassName}Api.get${simpleClassName}Api(rowId)
detailData.value = res
modelLoading.value = false
}
// 删除操作
const handleDelete = async (rowId: number) => {
await deleteData(xGrid, rowId)
}
// 提交按钮
const submitForm = async () => {
const elForm = unref(formRef)?.getElFormRef()
if (!elForm) return
elForm.validate(async (valid) => {
if (valid) {
actionLoading.value = true
// 提交请求
try {
const data = unref(formRef)?.formModel as ${simpleClassName}Api.${simpleClassName}VO
if (actionType.value === 'create') {
await ${simpleClassName}Api.create${simpleClassName}Api(data)
message.success(t('common.createSuccess'))
} else {
await ${simpleClassName}Api.update${simpleClassName}Api(data)
message.success(t('common.updateSuccess'))
}
modelVisible.value = false
} finally {
actionLoading.value = false
// 刷新列表
await getList(xGrid)
}
}
})
}
/** 初始化 **/
onMounted(() => {
getList()
})
</script>

View File

@ -0,0 +1,45 @@
import request from '@/config/axios'
export interface ${simpleClassName}VO {
#foreach ($column in $columns)
#if ($column.createOperation || $column.updateOperation)
#if(${column.javaType.toLowerCase()} == "long" || ${column.javaType.toLowerCase()} == "integer" || ${column.javaType.toLowerCase()} == "double" || ${column.javaType.toLowerCase()} == "bigdecimal")
${column.javaField}: number
#elseif(${column.javaType.toLowerCase()} == "date" || ${column.javaType.toLowerCase()} == "localdatetime")
${column.javaField}: Date
#else
${column.javaField}: ${column.javaType.toLowerCase()}
#end
#end
#end
}
// 查询${table.classComment}列表
export const get${simpleClassName}Page = async (params: ${simpleClassName}PageReqVO) => {
return await request.get({ url: '${baseURL}/page', params })
}
// 查询${table.classComment}详情
export const get${simpleClassName} = async (id: number) => {
return await request.get({ url: '${baseURL}/get?id=' + id })
}
// 新增${table.classComment}
export const create${simpleClassName} = async (data: ${simpleClassName}VO) => {
return await request.post({ url: '${baseURL}/create', data })
}
// 修改${table.classComment}
export const update${simpleClassName} = async (data: ${simpleClassName}VO) => {
return await request.put({ url: '${baseURL}/update', data })
}
// 删除${table.classComment}
export const delete${simpleClassName} = async (id: number) => {
return await request.delete({ url: '${baseURL}/delete?id=' + id })
}
// 导出${table.classComment} Excel
export const export${simpleClassName}Api = async (params) => {
return await request.download({ url: '${baseURL}/export-excel', params })
}

View File

@ -0,0 +1,129 @@
import type { CrudSchema } from '@/hooks/web/useCrudSchemas'
#foreach ($column in $columns)
#if ($column.listOperationResult && $column.htmlType == "datetime")
import { dateFormatter } from '@/utils/formatTime'
#break
#end
#end
// 表单校验
export const rules = reactive({
#foreach ($column in $columns)
#if (($column.createOperation || $column.updateOperation) && !$column.nullable && !${column.primaryKey})## 创建或者更新操作 && 要求非空 && 非主键
#set($comment=$column.columnComment)
$column.javaField: [required],
#end
#end
})
// CrudSchema https://doc.iocoder.cn/vue3/crud-schema/
const crudSchemas = reactive<CrudSchema[]>([
#foreach($column in $columns)
#if ($column.listOperation || $column.listOperationResult || $column.createOperation || $column.updateOperation)
#set ($dictType = $column.dictType)
#set ($javaField = $column.javaField)
#set ($javaType = $column.javaType)
{
label: '${column.columnComment}',
field: '${column.javaField}',
## ========= 字典部分 =========
#if ("" != $dictType)## 有数据字典
dictType: DICT_TYPE.$dictType.toUpperCase(),
#if ($javaType == "Integer" || $javaType == "Long" || $javaType == "Byte" || $javaType == "Short")
dictClass: 'number',
#elseif ($javaType == "String")
dictClass: 'string',
#elseif ($javaType == "Boolean")
dictClass: 'boolean',
#end
#end
## ========= Table 表格部分 =========
#if (!$column.listOperationResult)
isTable: false,
#else
#if ($column.htmlType == "datetime")
formatter: dateFormatter,
#end
#end
## ========= Search 表格部分 =========
#if ($column.listOperation)
isSearch: true,
#if ($column.htmlType == "datetime")
search: {
component: 'DatePicker',
componentProps: {
valueFormat: 'YYYY-MM-DD HH:mm:ss',
type: 'daterange',
defaultTime: [new Date('1 00:00:00'), new Date('1 23:59:59')]
}
},
#end
#end
## ========= Form 表单部分 =========
#if ((!$column.createOperation && !$column.updateOperation) || $column.primaryKey)
isForm: false,
#else
#if($column.htmlType == "imageUpload")## 图片上传
form: {
component: 'UploadImg'
},
#elseif($column.htmlType == "fileUpload")## 文件上传
form: {
component: 'UploadFile'
},
#elseif($column.htmlType == "editor")## 文本编辑器
form: {
component: 'Editor',
componentProps: {
valueHtml: '',
height: 200
}
},
#elseif($column.htmlType == "select")## 下拉框
form: {
component: 'SelectV2'
},
#elseif($column.htmlType == "checkbox")## 多选框
form: {
component: 'Checkbox'
},
#elseif($column.htmlType == "radio")## 单选框
form: {
component: 'Radio'
},
#elseif($column.htmlType == "datetime")## 时间框
form: {
component: 'DatePicker',
componentProps: {
type: 'datetime',
valueFormat: 'x'
}
},
#elseif($column.htmlType == "textarea")## 文本框
form: {
component: 'Input',
componentProps: {
type: 'textarea',
rows: 4
},
colProps: {
span: 24
}
},
#elseif(${javaType.toLowerCase()} == "long" || ${javaType.toLowerCase()} == "integer")## 文本框
form: {
component: 'InputNumber',
value: 0
},
#end
#end
},
#end
#end
{
label: '操作',
field: 'action',
isForm: false
}
])
export const { allSchemas } = useCrudSchemas(crudSchemas)

View File

@ -0,0 +1,65 @@
<template>
<Dialog :title="dialogTitle" v-model="dialogVisible">
<Form ref="formRef" :schema="allSchemas.formSchema" :rules="rules" v-loading="formLoading" />
<template #footer>
<el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button>
<el-button @click="dialogVisible = false">取 消</el-button>
</template>
</Dialog>
</template>
<script setup lang="ts">
import * as ${simpleClassName}Api from '@/api/${table.moduleName}/${classNameVar}'
import { rules, allSchemas } from './${classNameVar}.data'
const { t } = useI18n() // 国际化
const message = useMessage() // 消息弹窗
const dialogVisible = ref(false) // 弹窗的是否展示
const dialogTitle = ref('') // 弹窗的标题
const formLoading = ref(false) // 表单的加载中1修改时的数据加载2提交的按钮禁用
const formType = ref('') // 表单的类型create - 新增update - 修改
const formRef = ref() // 表单 Ref
/** 打开弹窗 */
const open = async (type: string, id?: number) => {
dialogVisible.value = true
dialogTitle.value = t('action.' + type)
formType.value = type
// 修改时,设置数据
if (id) {
formLoading.value = true
try {
const data = await ${simpleClassName}Api.get${simpleClassName}(id)
formRef.value.setValues(data)
} finally {
formLoading.value = false
}
}
}
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
/** 提交表单 */
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
const submitForm = async () => {
// 校验表单
if (!formRef) return
const valid = await formRef.value.getElFormRef().validate()
if (!valid) return
// 提交请求
formLoading.value = true
try {
const data = formRef.value.formModel as ${simpleClassName}Api.${simpleClassName}VO
if (formType.value === 'create') {
await ${simpleClassName}Api.create${simpleClassName}(data)
message.success(t('common.createSuccess'))
} else {
await ${simpleClassName}Api.update${simpleClassName}(data)
message.success(t('common.updateSuccess'))
}
dialogVisible.value = false
// 发送操作成功的事件
emit('success')
} finally {
formLoading.value = false
}
}
</script>

View File

@ -0,0 +1,85 @@
<template>
<!-- 搜索工作栏 -->
<ContentWrap>
<Search :schema="allSchemas.searchSchema" @search="setSearchParams" @reset="setSearchParams">
<!-- 新增等操作按钮 -->
<template #actionMore>
<el-button
type="primary"
plain
@click="openForm('create')"
v-hasPermi="['${permissionPrefix}:create']"
>
<Icon icon="ep:plus" class="mr-5px" /> 新增
</el-button>
</template>
</Search>
</ContentWrap>
<!-- 列表 -->
<ContentWrap>
<Table
:columns="allSchemas.tableColumns"
:data="tableObject.tableList"
:loading="tableObject.loading"
:pagination="{
total: tableObject.total
}"
v-model:pageSize="tableObject.pageSize"
v-model:currentPage="tableObject.currentPage"
>
<template #action="{ row }">
<el-button
link
type="primary"
@click="openForm('update', row.id)"
v-hasPermi="['${permissionPrefix}:update']"
>
编辑
</el-button>
<el-button
link
type="danger"
v-hasPermi="['${permissionPrefix}:delete']"
@click="handleDelete(row.id)"
>
删除
</el-button>
</template>
</Table>
</ContentWrap>
<!-- 表单弹窗:添加/修改 -->
<${simpleClassName}Form ref="formRef" @success="getList" />
</template>
<script setup lang="ts" name="${table.className}">
import { allSchemas } from './${classNameVar}.data'
import * as ${simpleClassName}Api from '@/api/${table.moduleName}/${classNameVar}'
import ${simpleClassName}Form from './${simpleClassName}Form.vue'
// tableObject表格的属性对象可获得分页大小、条数等属性
// tableMethods表格的操作对象可进行获得分页、删除记录等操作
// 详细可见https://doc.iocoder.cn/vue3/crud-schema/
const { tableObject, tableMethods } = useTable({
getListApi: ${simpleClassName}Api.get${simpleClassName}Page, // 分页接口
delListApi: ${simpleClassName}Api.delete${simpleClassName} // 删除接口
})
// 获得表格的各种操作
const { getList, setSearchParams } = tableMethods
/** 添加/修改操作 */
const formRef = ref()
const openForm = (type: string, id?: number) => {
formRef.value.open(type, id)
}
/** 删除按钮操作 */
const handleDelete = (id: number) => {
tableMethods.delList(id, false)
}
/** 初始化 **/
onMounted(() => {
getList()
})
</script>