代码生成器前端模板升级 TS + Composition API 产出,增强实体租户与主键序列化判定

This commit is contained in:
smallchill 2026-08-07 16:24:46 +08:00
parent e518473599
commit 3d93b3d587
12 changed files with 141 additions and 77 deletions

View File

@ -60,7 +60,7 @@ public class CodeGenerator {
/** /**
* 基础业务字段 * 基础业务字段
*/ */
public static String[] SUPER_ENTITY_COLUMNS = {"create_time", "create_user", "update_time", "update_user", "status", "is_deleted"}; public static String[] SUPER_ENTITY_COLUMNS = {"create_time", "create_user", "create_dept", "update_time", "update_user", "status", "is_deleted"};
/** /**
* 是否包含包装器 * 是否包含包装器
*/ */

View File

@ -15,11 +15,13 @@
*/ */
package org.springblade.develop.support; package org.springblade.develop.support;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.core.toolkit.IdWorker; import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.baomidou.mybatisplus.core.toolkit.StringPool; import com.baomidou.mybatisplus.core.toolkit.StringPool;
import com.baomidou.mybatisplus.generator.FastAutoGenerator; import com.baomidou.mybatisplus.generator.FastAutoGenerator;
import com.baomidou.mybatisplus.generator.config.OutputFile; import com.baomidou.mybatisplus.generator.config.OutputFile;
import com.baomidou.mybatisplus.generator.config.builder.CustomFile; import com.baomidou.mybatisplus.generator.config.builder.CustomFile;
import com.baomidou.mybatisplus.generator.config.builder.Entity;
import com.baomidou.mybatisplus.generator.config.po.TableInfo; import com.baomidou.mybatisplus.generator.config.po.TableInfo;
import com.baomidou.mybatisplus.generator.config.rules.DateType; import com.baomidou.mybatisplus.generator.config.rules.DateType;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy; import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
@ -148,25 +150,33 @@ public class BladeCodeGenerator {
customFile.put("wrapper.java", "/templates/api/wrapper.java.vm"); customFile.put("wrapper.java", "/templates/api/wrapper.java.vm");
} }
if (Func.isNotBlank(packageWebDir)) { if (Func.isNotBlank(packageWebDir)) {
customFile.put("api.js", "/templates/saber/api.js.vm"); customFile.put("api.ts", "/templates/saber/api.ts.vm");
customFile.put("crud.vue", "/templates/saber/crud.vue.vm"); customFile.put("crud.vue", "/templates/saber/crud.vue.vm");
} }
// 无基础业务字段时实体不再继承 BaseEntity服务层须同步降级为 MyBatis-Plus 原生接口3.5.17 起位于 spring
String superServiceClass = hasSuperEntity ? "org.springblade.core.mp.base.BaseService" : "com.baomidou.mybatisplus.spring.service.IService";
String superServiceImplClass = hasSuperEntity ? "org.springblade.core.mp.base.BaseServiceImpl" : "com.baomidou.mybatisplus.spring.service.impl.ServiceImpl";
FastAutoGenerator.create(url, username, password) FastAutoGenerator.create(url, username, password)
.globalConfig(builder -> builder.author(props.getProperty("author")).dateType(DateType.TIME_PACK).enableSwagger().outputDir(getOutputDir()).disableOpenDir()) .globalConfig(builder -> builder.author(props.getProperty("author")).dateType(DateType.TIME_PACK).enableSwagger().outputDir(getOutputDir()).disableOpenDir())
.packageConfig(builder -> builder.parent(packageName).controller("controller").entity("pojo.entity").service("service").serviceImpl("service.impl").mapper("mapper").xml("mapper")) .packageConfig(builder -> builder.parent(packageName).controller("controller").entity("pojo.entity").service("service").serviceImpl("service.impl").mapper("mapper").xml("mapper"))
.strategyConfig(builder -> builder.addTablePrefix(tablePrefix).addInclude(includeTables).addExclude(excludeTables) .strategyConfig(builder -> {
.entityBuilder().naming(NamingStrategy.underline_to_camel).columnNaming(NamingStrategy.underline_to_camel).enableLombok().superClass("org.springblade.core.mp.base.BaseEntity").addSuperEntityColumns(superEntityColumns).enableFileOverride() Entity.Builder entityBuilder = builder.addTablePrefix(tablePrefix).addInclude(includeTables).addExclude(excludeTables)
.javaTemplate("/templates/api/entity.java.vm") .entityBuilder().naming(NamingStrategy.underline_to_camel).columnNaming(NamingStrategy.underline_to_camel).enableLombok().idType(IdType.ASSIGN_ID).enableFileOverride()
.serviceBuilder().superServiceClass("org.springblade.core.mp.base.BaseService").superServiceImplClass("org.springblade.core.mp.base.BaseServiceImpl").formatServiceFileName("I%sService").formatServiceImplFileName("%sServiceImpl").enableFileOverride() .javaTemplate("/templates/api/entity.java.vm");
if (hasSuperEntity) {
entityBuilder.superClass("org.springblade.core.mp.base.BaseEntity").addSuperEntityColumns(superEntityColumns);
}
entityBuilder.serviceBuilder().superServiceClass(superServiceClass).superServiceImplClass(superServiceImplClass).formatServiceFileName("I%sService").formatServiceImplFileName("%sServiceImpl").enableFileOverride()
.serviceTemplate("/templates/api/service.java.vm") .serviceTemplate("/templates/api/service.java.vm")
.serviceImplTemplate("/templates/api/serviceImpl.java.vm") .serviceImplTemplate("/templates/api/serviceImpl.java.vm")
.mapperBuilder().mapperAnnotation(Mapper.class).enableBaseResultMap().enableBaseColumnList().formatMapperFileName("%sMapper").formatXmlFileName("%sMapper").enableFileOverride() .mapperBuilder().mapperAnnotation(Mapper.class).enableBaseResultMap().enableBaseColumnList().formatMapperFileName("%sMapper").formatXmlFileName("%sMapper").enableFileOverride()
.mapperTemplate("/templates/api/mapper.java.vm") .mapperTemplate("/templates/api/mapper.java.vm")
.mapperXmlTemplate("/templates/api/mapper.xml.vm") .mapperXmlTemplate("/templates/api/mapper.xml.vm")
.controllerBuilder().superClass("org.springblade.core.boot.ctrl.BladeController").formatFileName("%sController").enableRestStyle().enableHyphenStyle().enableFileOverride() .controllerBuilder().superClass("org.springblade.core.boot.ctrl.BladeController").formatFileName("%sController").enableRestStyle().enableHyphenStyle().enableFileOverride()
.template("/templates/api/controller.java.vm") .template("/templates/api/controller.java.vm");
) })
.injectionConfig(builder -> builder.beforeOutputFile( .injectionConfig(builder -> builder.beforeOutputFile(
(tableInfo, objectMap) -> System.out.println("tableInfo: " + tableInfo.getEntityName() + " objectMap: " + objectMap.size()) (tableInfo, objectMap) -> System.out.println("tableInfo: " + tableInfo.getEntityName() + " objectMap: " + objectMap.size())
).customMap(customMap).customFile(customFile) ).customMap(customMap).customFile(customFile)
@ -202,8 +212,8 @@ public class BladeCodeGenerator {
outputPath = getOutputDir() + StringPool.SLASH + packageName.replace(StringPool.DOT, StringPool.SLASH) + StringPool.SLASH + "wrapper" + StringPool.SLASH + entityName + "Wrapper" + StringPool.DOT_JAVA; outputPath = getOutputDir() + StringPool.SLASH + packageName.replace(StringPool.DOT, StringPool.SLASH) + StringPool.SLASH + "wrapper" + StringPool.SLASH + entityName + "Wrapper" + StringPool.DOT_JAVA;
} }
if (StringUtil.equals(key, "api.js")) { if (StringUtil.equals(key, "api.ts")) {
outputPath = getOutputWebDir() + StringPool.SLASH + "api" + StringPool.SLASH + servicePackage.toLowerCase() + StringPool.SLASH + entityNameLower + ".js"; outputPath = getOutputWebDir() + StringPool.SLASH + "api" + StringPool.SLASH + servicePackage.toLowerCase() + StringPool.SLASH + entityNameLower + ".ts";
} }
if (StringUtil.equals(key, "crud.vue")) { if (StringUtil.equals(key, "crud.vue")) {

View File

@ -1,11 +1,11 @@
/** /**
* Copyright (c) 2018-2099, Chill Zhuang 庄骞 (bladejava@qq.com). * Copyright (c) 2018-2099, Chill Zhuang 庄骞 (bladejava@qq.com).
* <p> * <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0; * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* <p> * <p>
* http://www.gnu.org/licenses/lgpl.html * http://www.apache.org/licenses/LICENSE-2.0
* <p> * <p>
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
@ -15,35 +15,30 @@
*/ */
package $!{package.Controller}; package $!{package.Controller};
import com.baomidou.mybatisplus.core.metadata.IPage;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
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.Parameter;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import jakarta.validation.Valid; #if($!{superControllerClassPackage})
import $!{superControllerClassPackage};
#end
import org.springblade.core.mp.support.Condition; import org.springblade.core.mp.support.Condition;
import org.springblade.core.mp.support.Query; import org.springblade.core.mp.support.Query;
import org.springblade.core.swagger.annotation.ApiOrder; import org.springblade.core.swagger.annotation.ApiOrder;
import org.springblade.core.tool.api.R; import org.springblade.core.tool.api.R;
import org.springblade.core.tool.utils.Func; import org.springblade.core.tool.utils.Func;
import org.springframework.web.bind.annotation.*;
#if($!{superEntityClass})
import org.springframework.web.bind.annotation.RequestParam;
#end
import com.baomidou.mybatisplus.core.metadata.IPage;
import $!{package.Entity}.$!{entity}; import $!{package.Entity}.$!{entity};
#set($voPackage=$package.Entity.replace("entity","vo")) #set($voPackage=$package.Entity.replace("entity","vo"))
import $!{voPackage}.$!{entity}VO; import $!{voPackage}.$!{entity}VO;
#set($wrapperPackage=$package.Entity.replace("pojo.entity","wrapper"))
#if($!{hasWrapper}) #if($!{hasWrapper})
#set($wrapperPackage=$package.Entity.replace("pojo.entity","wrapper"))
import $!{wrapperPackage}.$!{entity}Wrapper; import $!{wrapperPackage}.$!{entity}Wrapper;
#end #end
import $!{package.Service}.$!{table.serviceName}; import $!{package.Service}.$!{table.serviceName};
#if($!{superControllerClassPackage}) import org.springframework.web.bind.annotation.*;
import $!{superControllerClassPackage};
#end import jakarta.validation.Valid;
#if(!$!{superEntityClass})
#end
/** /**
* $!{table.comment} 控制器 * $!{table.comment} 控制器
@ -84,7 +79,6 @@ public class $!{table.controllerName} {
IPage<$!{entity}> pages = $!{table.entityPath}Service.page(Condition.getPage(query), Condition.getQueryWrapper($!{table.entityPath})); IPage<$!{entity}> pages = $!{table.entityPath}Service.page(Condition.getPage(query), Condition.getQueryWrapper($!{table.entityPath}));
return R.data($!{entity}Wrapper.build().pageVO(pages)); return R.data($!{entity}Wrapper.build().pageVO(pages));
} }
#else #else
/** /**
* 详情 * 详情
@ -144,8 +138,7 @@ public class $!{table.controllerName} {
return R.status($!{table.entityPath}Service.saveOrUpdate($!{table.entityPath})); return R.status($!{table.entityPath}Service.saveOrUpdate($!{table.entityPath}));
} }
#if($!{superEntityClass}) #if($!{superEntityClass})
/** /**
* 删除 $!{table.comment} * 删除 $!{table.comment}
*/ */
@ -155,8 +148,7 @@ public class $!{table.controllerName} {
return R.status($!{table.entityPath}Service.deleteLogic(Func.toLongList(ids))); return R.status($!{table.entityPath}Service.deleteLogic(Func.toLongList(ids)));
} }
#else #else
/** /**
* 删除 $!{table.comment} * 删除 $!{table.comment}
*/ */
@ -166,6 +158,5 @@ public class $!{table.controllerName} {
return R.status($!{table.entityPath}Service.removeByIds(Func.toLongList(ids))); return R.status($!{table.entityPath}Service.removeByIds(Func.toLongList(ids)));
} }
#end #end
} }

View File

@ -1,3 +1,21 @@
#set($hasTenant = false)
#set($hasKeyField = false)
#set($hasLongKey = false)
#foreach($field in $!{table.fields})
#if($!{field.name} == $!{tenantColumn})
#set($hasTenant = true)
#end
#if($!{field.keyFlag})
#set($hasKeyField = true)
#end
#if($!{field.keyFlag} && $!{field.propertyType} == "Long")
#set($hasLongKey = true)
#end
#end
#set($useTenantEntity = false)
#if($hasTenant && "$!superEntityClass" == "BaseEntity")
#set($useTenantEntity = true)
#end
/** /**
* Copyright (c) 2018-2099, Chill Zhuang 庄骞 (bladejava@qq.com). * Copyright (c) 2018-2099, Chill Zhuang 庄骞 (bladejava@qq.com).
* <p> * <p>
@ -16,8 +34,20 @@
package $!{package.Entity}; package $!{package.Entity};
#foreach($pkg in $!{table.importPackages}) #foreach($pkg in $!{table.importPackages})
#if($useTenantEntity && $!{pkg} == "org.springblade.core.mp.base.BaseEntity")
import org.springblade.core.mp.base.TenantEntity;
#elseif($!{pkg} == "java.io.Serializable" && $!{superEntityClass} && !$!{activeRecord})
## 继承父类时不直接实现 Serializable跳过强制注入的导入
#elseif(!$hasKeyField && ($!{pkg} == "com.baomidou.mybatisplus.annotation.IdType" || $!{pkg} == "com.baomidou.mybatisplus.annotation.TableId"))
## 主键列归入公共字段时实体内不产出主键注解,跳过全局 idType 注入的导入
#else
import $!{pkg}; import $!{pkg};
#end #end
#end
#if($hasLongKey)
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
#end
#if($!{entityLombokModel}) #if($!{entityLombokModel})
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
@ -46,7 +76,7 @@ import java.io.Serial;
@Schema(description = #if ("$!{table.comment}"=="")"$!{entity}对象"#else"$!{table.comment}"#end) @Schema(description = #if ("$!{table.comment}"=="")"$!{entity}对象"#else"$!{table.comment}"#end)
#end #end
#if($!{superEntityClass}) #if($!{superEntityClass})
public class $!{entity} extends $!{superEntityClass}#if($!{activeRecord})<$!{entity}>#end { public class $!{entity} extends #if($useTenantEntity)TenantEntity#{else}$!{superEntityClass}#end#if($!{activeRecord})<$!{entity}>#end {
#elseif($!{activeRecord}) #elseif($!{activeRecord})
@Accessors(chain = true) @Accessors(chain = true)
public class $!{entity} extends Model<$!{entity}> { public class $!{entity} extends Model<$!{entity}> {
@ -59,7 +89,7 @@ public class $!{entity} implements Serializable {
## ---------- BEGIN 字段循环遍历 ---------- ## ---------- BEGIN 字段循环遍历 ----------
#foreach($field in $!{table.fields}) #foreach($field in $!{table.fields})
#if($!{field.name}!=$!{tenantColumn}) #if($!{field.name} != $!{tenantColumn} || !$useTenantEntity)
#if($!{field.keyFlag}) #if($!{field.keyFlag})
#set($keyPropertyName=$!{field.propertyName}) #set($keyPropertyName=$!{field.propertyName})
#end #end
@ -80,6 +110,9 @@ public class $!{entity} implements Serializable {
#elseif($!{field.convert}) #elseif($!{field.convert})
@TableId("$!{field.name}") @TableId("$!{field.name}")
#end #end
#if($!{field.propertyType} == "Long")
@JsonSerialize(using = ToStringSerializer.class)
#end
## 普通字段 ## 普通字段
#elseif($!{field.fill}) #elseif($!{field.fill})
## ----- 存在字段填充设置 ----- ## ----- 存在字段填充设置 -----

View File

@ -15,11 +15,12 @@
*/ */
package $!{package.Mapper}; package $!{package.Mapper};
import $!{superMapperClassPackage};
import com.baomidou.mybatisplus.core.metadata.IPage;
import $!{package.Entity}.$!{entity}; import $!{package.Entity}.$!{entity};
#set($voPackage=$package.Entity.replace("entity","vo")) #set($voPackage=$package.Entity.replace("entity","vo"))
import $!{voPackage}.$!{entity}VO; import $!{voPackage}.$!{entity}VO;
import $!{superMapperClassPackage};
import com.baomidou.mybatisplus.core.metadata.IPage;
import java.util.List; import java.util.List;
/** /**

View File

@ -1,3 +1,13 @@
#set($hasTenant = false)
#foreach($field in $!{table.fields})
#if($!{field.name} == $!{tenantColumn})
#set($hasTenant = true)
#end
#end
#set($useTenantEntity = false)
#if($hasTenant && "$!superEntityClass" == "BaseEntity")
#set($useTenantEntity = true)
#end
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="$!{package.Mapper}.$!{table.mapperName}"> <mapper namespace="$!{package.Mapper}.$!{table.mapperName}">
@ -19,7 +29,7 @@
<result column="$!{field.name}" property="$!{field.propertyName}"/> <result column="$!{field.name}" property="$!{field.propertyName}"/>
#end #end
#foreach($field in $!{table.fields}) #foreach($field in $!{table.fields})
#if(!$!{field.keyFlag} && $!{field.name} != $!{tenantColumn})##生成普通字段 #if(!$!{field.keyFlag} && ($!{field.name} != $!{tenantColumn} || !$useTenantEntity))##生成普通字段
<result column="$!{field.name}" property="$!{field.propertyName}"/> <result column="$!{field.name}" property="$!{field.propertyName}"/>
#end #end
#end #end
@ -28,7 +38,11 @@
#end #end
<select id="select$!{entity}Page" resultMap="$!{table.entityPath}ResultMap"> <select id="select$!{entity}Page" resultMap="$!{table.entityPath}ResultMap">
#if($!{superEntityClass})
select * from $!{table.name} where is_deleted = 0 select * from $!{table.name} where is_deleted = 0
#else
select * from $!{table.name}
#end
</select> </select>
</mapper> </mapper>

View File

@ -15,11 +15,11 @@
*/ */
package $!{package.Service}; package $!{package.Service};
import com.baomidou.mybatisplus.core.metadata.IPage;
import $!{superServiceClassPackage};
import $!{package.Entity}.$!{entity}; import $!{package.Entity}.$!{entity};
#set($voPackage=$package.Entity.replace("entity","vo")) #set($voPackage=$package.Entity.replace("entity","vo"))
import $!{voPackage}.$!{entity}VO; import $!{voPackage}.$!{entity}VO;
import $!{superServiceClassPackage};
import com.baomidou.mybatisplus.core.metadata.IPage;
/** /**
* $!{table.comment} 服务类 * $!{table.comment} 服务类

View File

@ -15,14 +15,14 @@
*/ */
package $!{package.ServiceImpl}; package $!{package.ServiceImpl};
import com.baomidou.mybatisplus.core.metadata.IPage;
import $!{superServiceImplClassPackage};
import $!{package.Entity}.$!{entity}; import $!{package.Entity}.$!{entity};
#set($voPackage=$package.Entity.replace("entity","vo")) #set($voPackage=$package.Entity.replace("entity","vo"))
import $!{voPackage}.$!{entity}VO; import $!{voPackage}.$!{entity}VO;
import $!{package.Mapper}.$!{table.mapperName}; import $!{package.Mapper}.$!{table.mapperName};
import $!{package.Service}.$!{table.serviceName}; import $!{package.Service}.$!{table.serviceName};
import $!{superServiceImplClassPackage};
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import com.baomidou.mybatisplus.core.metadata.IPage;
/** /**
* $!{table.comment} 服务实现类 * $!{table.comment} 服务实现类

View File

@ -16,7 +16,6 @@
#set($wrapperPackage=$package.Entity.replace("pojo.entity","wrapper")) #set($wrapperPackage=$package.Entity.replace("pojo.entity","wrapper"))
package $!{wrapperPackage}; package $!{wrapperPackage};
import lombok.AllArgsConstructor;
import org.springblade.core.mp.support.BaseEntityWrapper; import org.springblade.core.mp.support.BaseEntityWrapper;
import org.springblade.core.tool.utils.BeanUtil; import org.springblade.core.tool.utils.BeanUtil;
import $!{package.Entity}.$!{entity}; import $!{package.Entity}.$!{entity};

View File

@ -1,14 +1,23 @@
#set($hasTenant = false)
#foreach($field in $!{table.fields})
#if($!{field.name} == $!{tenantColumn})
#set($hasTenant = true)
#end
#end
#set($useTenantEntity = false)
#if($hasTenant && "$!superEntityClass" == "BaseEntity")
#set($useTenantEntity = true)
#end
<template> <template>
<basic-container> <basic-container>
<avue-crud :option="option" <avue-crud :option="option"
:table-loading="loading" :table-loading="loading"
:data="data" :data="data"
ref="crudRef" :page="page"
v-model="form"
v-model:search="search"
v-model:page="page"
:permission="permissionList" :permission="permissionList"
:before-open="beforeOpen" :before-open="beforeOpen"
v-model="form"
ref="crudRef"
@row-update="rowUpdate" @row-update="rowUpdate"
@row-save="rowSave" @row-save="rowSave"
@row-del="rowDel" @row-del="rowDel"
@ -17,6 +26,7 @@
@selection-change="selectionChange" @selection-change="selectionChange"
@current-change="currentChange" @current-change="currentChange"
@size-change="sizeChange" @size-change="sizeChange"
@refresh-change="refreshChange"
@on-load="onLoad"> @on-load="onLoad">
<template #menu-left> <template #menu-left>
<el-button type="danger" <el-button type="danger"
@ -41,8 +51,8 @@ import { validData } from '@/utils/util';
interface $!{entity}Entity { interface $!{entity}Entity {
id: string; id: string;
#foreach($field in $!{table.fields}) #foreach($field in $!{table.fields})
#if($!{field.name}!=$!{tenantColumn}) #if($!{field.name} != $!{tenantColumn} || !$useTenantEntity)
#if($!{field.propertyName} != "id") #if(!$!{field.keyFlag})
#if($!{field.propertyType} == "Integer" || $!{field.propertyType} == "Long" || $!{field.propertyType} == "Short" || $!{field.propertyType} == "Byte" || $!{field.propertyType} == "Double" || $!{field.propertyType} == "Float" || $!{field.propertyType} == "BigDecimal") #if($!{field.propertyType} == "Integer" || $!{field.propertyType} == "Long" || $!{field.propertyType} == "Short" || $!{field.propertyType} == "Byte" || $!{field.propertyType} == "Double" || $!{field.propertyType} == "Float" || $!{field.propertyType} == "BigDecimal")
$!{field.propertyName}?: number; $!{field.propertyName}?: number;
#elseif($!{field.propertyType} == "Boolean") #elseif($!{field.propertyType} == "Boolean")
@ -65,14 +75,13 @@ const permission = computed(() => store.getters.permission);
// 表格实例与数据状态 // 表格实例与数据状态
const crudRef = ref(); const crudRef = ref();
const form = ref<$!{entity}Form>({}); const form = ref<$!{entity}Form>({});
const search = ref<Partial<$!{entity}Entity>>({});
const data = ref<$!{entity}Entity[]>([]); const data = ref<$!{entity}Entity[]>([]);
const selectionList = ref<$!{entity}Entity[]>([]); const selectionList = ref<$!{entity}Entity[]>([]);
const query = ref<Partial<$!{entity}Entity>>({}); const query = ref<Partial<$!{entity}Entity>>({});
const loading = ref(true); const loading = ref(true);
// 分页参数(整体替换,需用 ref // 分页参数
const page = ref({ const page = reactive({
pageSize: 10, pageSize: 10,
currentPage: 1, currentPage: 1,
total: 0, total: 0,
@ -85,16 +94,17 @@ const ids = computed(() => selectionList.value.map(ele => ele.id).join(','));
const option = reactive({ const option = reactive({
height: 'auto', height: 'auto',
calcHeight: 210, calcHeight: 210,
tip: false,
searchShow: true, searchShow: true,
searchMenuSpan: 6, searchMenuSpan: 6,
tip: false,
border: true, border: true,
index: true, index: true,
viewBtn: true, viewBtn: true,
selection: true, selection: true,
column: [ column: [
#foreach($field in $!{table.fields}) #foreach($field in $!{table.fields})
#if($!{field.name}!=$!{tenantColumn}) #if($!{field.name} != $!{tenantColumn} || !$useTenantEntity)
#if(!$!{field.keyFlag})
{ {
label: '$!{field.comment}', label: '$!{field.comment}',
prop: '$!{field.propertyName}', prop: '$!{field.propertyName}',
@ -105,6 +115,7 @@ const option = reactive({
}], }],
}, },
#end #end
#end
#end #end
], ],
}); });
@ -121,9 +132,9 @@ const permissionList = computed(() => ({
const onLoad = (pageData: { currentPage: number; pageSize: number }, params: Partial<$!{entity}Entity> = {}) => { const onLoad = (pageData: { currentPage: number; pageSize: number }, params: Partial<$!{entity}Entity> = {}) => {
loading.value = true; loading.value = true;
getList(pageData.currentPage, pageData.pageSize, Object.assign(params, query.value)).then(res => { getList(pageData.currentPage, pageData.pageSize, Object.assign(params, query.value)).then(res => {
const list = res.data.data; const listData = res.data.data;
page.value.total = list.total; page.total = listData.total;
data.value = list.records; data.value = listData.records;
loading.value = false; loading.value = false;
selectionClear(); selectionClear();
}); });
@ -132,25 +143,30 @@ const onLoad = (pageData: { currentPage: number; pageSize: number }, params: Par
// 条件检索 // 条件检索
const searchChange = (params: Partial<$!{entity}Entity>, done: () => void) => { const searchChange = (params: Partial<$!{entity}Entity>, done: () => void) => {
query.value = params; query.value = params;
page.value.currentPage = 1; page.currentPage = 1;
onLoad(page.value, params); onLoad(page, params);
done(); done();
}; };
// 重置检索条件 // 重置检索条件
const searchReset = () => { const searchReset = () => {
query.value = {}; query.value = {};
onLoad(page.value); onLoad(page);
}; };
// 切换页码 // 切换页码
const currentChange = (currentPage: number) => { const currentChange = (currentPage: number) => {
page.value.currentPage = currentPage; page.currentPage = currentPage;
}; };
// 调整每页条数 // 调整每页条数
const sizeChange = (pageSize: number) => { const sizeChange = (pageSize: number) => {
page.value.pageSize = pageSize; page.pageSize = pageSize;
};
// 刷新当前列表
const refreshChange = () => {
onLoad(page, query.value);
}; };
// 记录当前选中行 // 记录当前选中行
@ -168,7 +184,7 @@ const selectionClear = () => {
const rowSave = (row: $!{entity}Form, done: () => void, loading: () => void) => { const rowSave = (row: $!{entity}Form, done: () => void, loading: () => void) => {
add(row).then(() => { add(row).then(() => {
done(); done();
onLoad(page.value); onLoad(page);
ElMessage({ ElMessage({
type: 'success', type: 'success',
message: '操作成功!', message: '操作成功!',
@ -183,7 +199,7 @@ const rowSave = (row: $!{entity}Form, done: () => void, loading: () => void) =>
const rowUpdate = (row: $!{entity}Form, index: number, done: () => void, loading: () => void) => { const rowUpdate = (row: $!{entity}Form, index: number, done: () => void, loading: () => void) => {
update(row).then(() => { update(row).then(() => {
done(); done();
onLoad(page.value); onLoad(page);
ElMessage({ ElMessage({
type: 'success', type: 'success',
message: '操作成功!', message: '操作成功!',
@ -205,7 +221,7 @@ const rowDel = (row: $!{entity}Entity) => {
return remove(row.id); return remove(row.id);
}) })
.then(() => { .then(() => {
onLoad(page.value); onLoad(page);
ElMessage({ ElMessage({
type: 'success', type: 'success',
message: '操作成功!', message: '操作成功!',
@ -228,7 +244,7 @@ const handleDelete = () => {
return remove(ids.value); return remove(ids.value);
}) })
.then(() => { .then(() => {
onLoad(page.value); onLoad(page);
ElMessage({ ElMessage({
type: 'success', type: 'success',
message: '操作成功!', message: '操作成功!',
@ -237,7 +253,7 @@ const handleDelete = () => {
}); });
}; };
// 打开弹窗前置处理:编辑与查看回填详情 // 打开弹窗前加载编辑/查看详情
const beforeOpen = (done: () => void, type: string) => { const beforeOpen = (done: () => void, type: string) => {
if (['edit', 'view'].includes(type)) { if (['edit', 'view'].includes(type)) {
getDetail(form.value.id).then(res => { getDetail(form.value.id).then(res => {

View File

@ -652,7 +652,7 @@ import com.baomidou.mybatisplus.spring.service.impl.ServiceImpl;
**⑥ 安全放行(springdoc 路径)**:springdoc UI 与规格端点需匿名可达。框架默认放行清单 `blade-core-secure``SecureRegistry.defaultExcludePatterns` 已含 `/v3/api-docs/**`、`/swagger-ui/**`、`/swagger-ui.html`(`/swagger-ui/**` 为 UI 静态资源,`/swagger-ui.html` 为跳转入口),下游 servlet 应用继承即可。若应用另有自建放行清单(Boot 单体的 `BladeConfiguration`、Cloud 网关的 `AuthProvider`),须同步补齐这三项,并去掉 `/doc.html`、`/swagger-resources/**` 等 knife4j / springfox 旧路径。 **⑥ 安全放行(springdoc 路径)**:springdoc UI 与规格端点需匿名可达。框架默认放行清单 `blade-core-secure``SecureRegistry.defaultExcludePatterns` 已含 `/v3/api-docs/**`、`/swagger-ui/**`、`/swagger-ui.html`(`/swagger-ui/**` 为 UI 静态资源,`/swagger-ui.html` 为跳转入口),下游 servlet 应用继承即可。若应用另有自建放行清单(Boot 单体的 `BladeConfiguration`、Cloud 网关的 `AuthProvider`),须同步补齐这三项,并去掉 `/doc.html`、`/swagger-resources/**` 等 knife4j / springfox 旧路径。
**⑦ 代码生成器模板**:`blade-starter-develop/src/main/resources/templates/controller.java.vm` 里的 `@ApiOperationSupport` import 与注解一并删除——它是 `.vm` 资源,下游按 `*.java` 批量清理的脚本覆盖不到,须单独处理,否则该生成器产出的 Controller 引用已移除的 knife4j 包、编译不过(排序改用 §9⑧ 的 `@ApiOrder`)。删干净后**还须在模板 `@Tag` 上方补一行无参 `@ApiOrder` + 其 import**(`org.springblade.core.swagger.annotation.ApiOrder`),让生成的 Controller 与手写控制器一致带排序(见下「落地约定」);下游 Boot(`src/{main,test}/resources/templates/`)、Cloud(`blade-ops/blade-develop/src/{main,test}/resources/templates/`)的同名模板副本一并补。 **⑦ 代码生成器模板**:`blade-starter-develop/src/main/resources/templates/controller.java.vm` 里的 `@ApiOperationSupport` import 与注解一并删除——它是 `.vm` 资源,下游按 `*.java` 批量清理的脚本覆盖不到,须单独处理,否则该生成器产出的 Controller 引用已移除的 knife4j 包、编译不过(排序改用 §9⑧ 的 `@ApiOrder`)。删干净后**还须在模板 `@Tag` 上方补一行无参 `@ApiOrder` + 其 import**(`org.springblade.core.swagger.annotation.ApiOrder`),让生成的 Controller 与手写控制器一致带排序(见下「落地约定」);下游 Boot(`src/main/resources/templates/`)、Cloud(`blade-ops/blade-develop/src/main/resources/templates/`)的同名模板副本一并补。`src/test/resources/templates/` 下的旧扁平路径 `.vm` 副本自模板重组为 `api/` 子目录后不再被加载,应直接删除,test 侧仅保留 `code.properties` 作为数据源配置覆盖点。
**⑧ 接口排序:`@ApiOrder`(替代 knife4j 的 `@ApiOperationSupport(order = n)`)** **⑧ 接口排序:`@ApiOrder`(替代 knife4j 的 `@ApiOperationSupport(order = n)`)**
@ -664,7 +664,7 @@ springdoc / OpenAPI 3 无接口排序原生注解,blade-starter-swagger 新增 `
实现在 `SwaggerAutoConfiguration`:`GlobalOperationCustomizer` 读注解写入 `x-order` 扩展(类级用字节码行号取声明序,反射不保证方法顺序)并记录「tag → 类级序号」;`GlobalOpenApiCustomizer` 按 `x-order` 重排 `paths`、按类级序号重排顶层 `tags`。swagger-ui 仅在两个 sorter 未设时保留 spec 顺序(§9⑤),并须保持 `springdoc.writer-with-order-by-keys` 默认 `false` 实现在 `SwaggerAutoConfiguration`:`GlobalOperationCustomizer` 读注解写入 `x-order` 扩展(类级用字节码行号取声明序,反射不保证方法顺序)并记录「tag → 类级序号」;`GlobalOpenApiCustomizer` 按 `x-order` 重排 `paths`、按类级序号重排顶层 `tags`。swagger-ui 仅在两个 sorter 未设时保留 spec 顺序(§9⑤),并须保持 `springdoc.writer-with-order-by-keys` 默认 `false`
**落地约定(本次统一)**:三工程**每个带 `@Tag` 的控制器**均在 `@Tag` 上方补一行**无参 `@ApiOrder`**(值取默认 `Integer.MAX_VALUE`),方法体只保留 `@Operation`——效果是分组内接口按**源码声明顺序**展示、各 `@Tag` 分组之间因类级序号相等而回落 tag 名字母序;仅当个别接口要打破源码顺序时,才在该方法上补 `@ApiOrder(n)` 精确覆盖。删 knife4j 排序注解(§3.1 / §4.2)后**只删不补**会让接口在 springdoc UI 下按字母序、丢失原有顺序,故「删旧 + 补 `@ApiOrder`」是同一件事的两半。代码生成器模板 `controller.java.vm`(blade-tool `blade-starter-develop` 源模板 + 下游 Boot/Cloud `blade-develop` 的 main/test 副本,共 5 份)同步在 `@Tag` 上方生成 `@ApiOrder`,新生成的 Controller 开箱即带排序。 **落地约定(本次统一)**:三工程**每个带 `@Tag` 的控制器**均在 `@Tag` 上方补一行**无参 `@ApiOrder`**(值取默认 `Integer.MAX_VALUE`),方法体只保留 `@Operation`——效果是分组内接口按**源码声明顺序**展示、各 `@Tag` 分组之间因类级序号相等而回落 tag 名字母序;仅当个别接口要打破源码顺序时,才在该方法上补 `@ApiOrder(n)` 精确覆盖。删 knife4j 排序注解(§3.1 / §4.2)后**只删不补**会让接口在 springdoc UI 下按字母序、丢失原有顺序,故「删旧 + 补 `@ApiOrder`」是同一件事的两半。代码生成器模板 `controller.java.vm`(blade-tool `blade-starter-develop` 源模板 + 下游 Boot/Cloud `blade-develop` 的 main 副本,共 3 份)同步在 `@Tag` 上方生成 `@ApiOrder`,新生成的 Controller 开箱即带排序。
> 提示:类级 `@ApiOrder(n)``value` 用作 `@Tag` 分组之间的排序序号(`SwaggerAutoConfiguration#recordTagOrder` 写入、`GlobalOpenApiCustomizer` 据此重排顶层 `tags`);分组内各接口不受此值影响,按源码声明顺序排列。本次全量采用**无参**形式,`value` 恒为默认值、各分组落 tag 名字母序;如需固定分组顺序,给对应控制器标 `@ApiOrder(n)` 即可。 > 提示:类级 `@ApiOrder(n)``value` 用作 `@Tag` 分组之间的排序序号(`SwaggerAutoConfiguration#recordTagOrder` 写入、`GlobalOpenApiCustomizer` 据此重排顶层 `tags`);分组内各接口不受此值影响,按源码声明顺序排列。本次全量采用**无参**形式,`value` 恒为默认值、各分组落 tag 名字母序;如需固定分组顺序,给对应控制器标 `@ApiOrder(n)` 即可。