update 优化 代码生成支持接入各种不同前端 只需要将模板放到vm文件夹下 前端传对应的文件夹名即可

This commit is contained in:
疯狂的狮子Li
2026-06-03 15:27:39 +08:00
parent 626496f4ca
commit 97524974d5
14 changed files with 1266 additions and 45 deletions
@@ -2,11 +2,19 @@
## 优先参考的代码来源
- `ruoyi-modules/ruoyi-gen/src/main/resources/vm/vue/*.vm`
- `ruoyi-modules/ruoyi-gen/src/main/resources/vm/<frontendType>/*.vm`
- 默认 Vue 模板在 `vm/vue`React 模板在 `vm/react`
- 前端工程中与目标模块最接近的现有页面
当前 boot4 仓库通常只含后端与 generator 前端模板;如果前端工程不在当前 root,先以 generator 模板约定为准,再对照用户提供的前端目录或相邻仓库。
## 前端模板选择规则
- `gen_table.frontend_type` 存字符串,值直接对应 `vm` 下的模板目录,例如 `vue``react`
- 生成器按 `vm/<frontendType>/api.ts.vm``types.ts.vm``index.*.vm``index-tree.*.vm` 查找模板。
- 页面输出后缀由页面模板文件名决定:`index.vue.vm` 输出 `index.vue``index.tsx.vm` 输出 `index.tsx`
- 新增其他前端时优先只新增 `vm/<frontendType>` 目录和对应 VM 文件,不在 Java 代码里增加数字枚举或硬编码分支。
## API 文件规则
-`@/utils/request` 引入 `request`
@@ -80,6 +80,11 @@ public interface GenConstants {
*/
String SORT_FIELD = "sortField";
/**
* 默认前端模板类型,对应 vm/vue 目录。
*/
String FRONTEND_TYPE_VUE = "vue";
/**
* 树根节点值
*/
@@ -268,10 +273,10 @@ public interface GenConstants {
// MyBatis MapperXML 模板
String XML_MAPPER_TEMPLATE_PATH = "vm/xml/mapper.xml.vm";
// 前端源码模板
String TS_API_TEMPLATE_PATH = "vm/vue/api.ts.vm";
String TS_TYPES_TEMPLATE_PATH = "vm/vue/types.ts.vm";
String VUE_INDEX_TEMPLATE_PATH = "vm/vue/index.vue.vm";
String VUE_INDEX_TREE_TEMPLATE_PATH = "vm/vue/index-tree.vue.vm";
String FRONTEND_API_TEMPLATE_NAME = "api.ts.vm";
String FRONTEND_TYPES_TEMPLATE_NAME = "types.ts.vm";
String FRONTEND_INDEX_TEMPLATE_PREFIX = "index";
String FRONTEND_INDEX_TREE_TEMPLATE_PREFIX = "index-tree";
// 数据库SQL模板
String SQL_ORACLE_TEMPLATE_PATH = "vm/sql/oracle.sql.vm";
String SQL_POSTGRES_TEMPLATE_PATH = "vm/sql/postgres.sql.vm";
@@ -290,10 +295,14 @@ public interface GenConstants {
, JAVA_SERVICE_IMPL_TEMPLATE_PATH
, JAVA_CONTROLLER_TEMPLATE_PATH
, XML_MAPPER_TEMPLATE_PATH
, TS_API_TEMPLATE_PATH
, TS_TYPES_TEMPLATE_PATH
, VUE_INDEX_TEMPLATE_PATH
, VUE_INDEX_TREE_TEMPLATE_PATH
, "vm/vue/api.ts.vm"
, "vm/vue/types.ts.vm"
, "vm/vue/index.vue.vm"
, "vm/vue/index-tree.vue.vm"
, "vm/react/api.ts.vm"
, "vm/react/types.ts.vm"
, "vm/react/index.tsx.vm"
, "vm/react/index-tree.tsx.vm"
, SQL_ORACLE_TEMPLATE_PATH
, SQL_POSTGRES_TEMPLATE_PATH
, SQL_SQLSERVER_TEMPLATE_PATH
@@ -61,6 +61,11 @@ public class GenTable extends BaseEntity {
*/
private String tplCategory;
/**
* 前端模板类型,对应 vm 下的模板目录
*/
private String frontendType;
/**
* 生成包路径
*/
@@ -453,7 +453,7 @@ public class GenTableServiceImpl implements IGenTableService {
table.setMenuIds(menuIds);
setPkColumn(table);
Dict context = TemplateEngineUtils.buildContext(table);
List<PathNamedTemplate> templates = TemplateEngineUtils.getTemplateList(table.getTplCategory(), table.getDataName());
List<PathNamedTemplate> templates = TemplateEngineUtils.getTemplateList(table.getTplCategory(), table.getDataName(), table.getFrontendType());
return new RenderContext(table, context, templates);
}
@@ -34,6 +34,7 @@ public class GenUtils {
genTable.setBusinessName(getBusinessName(genTable.getTableName()));
genTable.setFunctionName(replaceText(genTable.getTableComment()));
genTable.setFunctionAuthor(PROPERTIES.getAuthor());
genTable.setFrontendType(GenConstants.FRONTEND_TYPE_VUE);
genTable.setCreateTime(null);
genTable.setUpdateTime(null);
}
@@ -9,6 +9,7 @@ import cn.hutool.extra.template.TemplateUtil;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.utils.DateUtils;
import org.dromara.common.core.utils.SpringUtils;
import org.dromara.common.core.utils.StringUtils;
@@ -20,7 +21,11 @@ import org.dromara.gen.constant.GenConstants;
import org.dromara.gen.domain.GenTable;
import org.dromara.gen.domain.GenTableColumn;
import org.dromara.gen.util.template.PathNamedTemplate;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import java.io.IOException;
import java.util.*;
import java.util.function.Consumer;
@@ -49,6 +54,7 @@ public class TemplateEngineUtils {
// 模板引擎
private static final TemplateEngine TEMPLATE_ENGINE;
private static final Map<String, PathNamedTemplate> TEMPLATE_MAPPER;
private static final ResourcePatternResolver RESOURCE_PATTERN_RESOLVER = new PathMatchingResourcePatternResolver();
static {
// 模板引擎初始化
@@ -85,6 +91,7 @@ public class TemplateEngineUtils {
String functionName = genTable.getFunctionName();
context.put("tplCategory", genTable.getTplCategory());
context.put("frontendType", resolveFrontendType(genTable.getFrontendType()));
context.put("tableName", genTable.getTableName());
context.put("functionName", StringUtils.isNotEmpty(functionName) ? functionName : "【请填写功能名称】");
context.put("ClassName", genTable.getClassName());
@@ -186,6 +193,18 @@ public class TemplateEngineUtils {
* @return 模板列表
*/
public static List<PathNamedTemplate> getTemplateList(String tplCategory, String dsName) {
return getTemplateList(tplCategory, dsName, GenConstants.FRONTEND_TYPE_VUE);
}
/**
* 获取模板信息
*
* @param tplCategory 前端页面模板分类
* @param dsName 数据源名称
* @param frontendType 前端模板类型
* @return 模板列表
*/
public static List<PathNamedTemplate> getTemplateList(String tplCategory, String dsName, String frontendType) {
List<PathNamedTemplate> templates = new ArrayList<>();
// 后端源码模板
templates.add(TEMPLATE_MAPPER.get(GenConstants.JAVA_DOMAIN_TEMPLATE_PATH));
@@ -198,8 +217,8 @@ public class TemplateEngineUtils {
// MyBatis MapperXML 模板
templates.add(TEMPLATE_MAPPER.get(GenConstants.XML_MAPPER_TEMPLATE_PATH));
// 前端 API 与类型模板
templates.add(TEMPLATE_MAPPER.get(GenConstants.TS_API_TEMPLATE_PATH));
templates.add(TEMPLATE_MAPPER.get(GenConstants.TS_TYPES_TEMPLATE_PATH));
templates.add(getTemplate(getFrontendApiTemplatePath(frontendType)));
templates.add(getTemplate(getFrontendTypesTemplatePath(frontendType)));
// 数据库模板
DataBaseType dataBaseType = DataBaseHelper.getDataBaseType(dsName);
if (dataBaseType.isOracle()) {
@@ -214,13 +233,27 @@ public class TemplateEngineUtils {
}
// 前端页面模板
if (GenConstants.TPL_CRUD.equals(tplCategory)) {
templates.add(TEMPLATE_MAPPER.get(GenConstants.VUE_INDEX_TEMPLATE_PATH));
templates.add(getTemplate(getFrontendIndexTemplatePath(frontendType)));
} else if (GenConstants.TPL_TREE.equals(tplCategory)) {
templates.add(TEMPLATE_MAPPER.get(GenConstants.VUE_INDEX_TREE_TEMPLATE_PATH));
templates.add(getTemplate(getFrontendIndexTreeTemplatePath(frontendType)));
}
return templates;
}
/**
* 获取模板,支持按前端模板类型动态加载 vm/{frontendType} 下的模板。
*
* @param templatePath 模板路径
* @return 带路径名的模板
*/
private static PathNamedTemplate getTemplate(String templatePath) {
PathNamedTemplate template = TEMPLATE_MAPPER.get(templatePath);
if (ObjectUtil.isNotNull(template)) {
return template;
}
return PathNamedTemplate.form(TEMPLATE_ENGINE, templatePath);
}
/**
* 获取文件名
*
@@ -242,7 +275,7 @@ public class TemplateEngineUtils {
String javaPath = PROJECT_PATH + "/" + StringUtils.replace(packageName, ".", "/");
String mybatisPath = MYBATIS_PATH + "/" + moduleName;
String vuePath = "vue";
String frontendPath = getFrontendPath(genTable.getFrontendType());
// templatePath
// genFilePathFormat
if (template.contains("domain.java.vm")) {
@@ -264,17 +297,112 @@ public class TemplateEngineUtils {
} else if (template.contains("sql.vm")) {
fileName = businessName + "Menu.sql";
} else if (template.contains("api.ts.vm")) {
fileName = StringUtils.format("{}/api/{}/{}/index.ts", vuePath, moduleName, businessName);
fileName = StringUtils.format("{}/api/{}/{}/index.ts", frontendPath, moduleName, businessName);
} else if (template.contains("types.ts.vm")) {
fileName = StringUtils.format("{}/api/{}/{}/types.ts", vuePath, moduleName, businessName);
} else if (template.contains("index.vue.vm")) {
fileName = StringUtils.format("{}/views/{}/{}/index.vue", vuePath, moduleName, businessName);
} else if (template.contains("index-tree.vue.vm")) {
fileName = StringUtils.format("{}/views/{}/{}/index.vue", vuePath, moduleName, businessName);
fileName = StringUtils.format("{}/api/{}/{}/types.ts", frontendPath, moduleName, businessName);
} else if (isFrontendPageTemplate(template, GenConstants.FRONTEND_INDEX_TEMPLATE_PREFIX)) {
fileName = StringUtils.format("{}/views/{}/{}/index.{}", frontendPath, moduleName, businessName,
getFrontendPageExtension(template, GenConstants.FRONTEND_INDEX_TEMPLATE_PREFIX));
} else if (isFrontendPageTemplate(template, GenConstants.FRONTEND_INDEX_TREE_TEMPLATE_PREFIX)) {
fileName = StringUtils.format("{}/views/{}/{}/index.{}", frontendPath, moduleName, businessName,
getFrontendPageExtension(template, GenConstants.FRONTEND_INDEX_TREE_TEMPLATE_PREFIX));
}
return fileName;
}
/**
* 获取前端输出目录。
*
* @param frontendType 前端模板类型
* @return 前端输出目录
*/
private static String getFrontendPath(String frontendType) {
return resolveFrontendType(frontendType);
}
/**
* 获取前端页面文件扩展名。
*
* @param template 模板路径
* @param templatePrefix 模板文件名前缀
* @return 文件扩展名
*/
private static String getFrontendPageExtension(String template, String templatePrefix) {
String fileName = getTemplateFileName(template);
return fileName.substring((templatePrefix + ".").length(), fileName.length() - ".vm".length());
}
private static String getFrontendApiTemplatePath(String frontendType) {
return getFrontendTemplatePath(frontendType, GenConstants.FRONTEND_API_TEMPLATE_NAME);
}
private static String getFrontendTypesTemplatePath(String frontendType) {
return getFrontendTemplatePath(frontendType, GenConstants.FRONTEND_TYPES_TEMPLATE_NAME);
}
private static String getFrontendIndexTemplatePath(String frontendType) {
return getFrontendPageTemplatePath(frontendType, GenConstants.FRONTEND_INDEX_TEMPLATE_PREFIX);
}
private static String getFrontendIndexTreeTemplatePath(String frontendType) {
return getFrontendPageTemplatePath(frontendType, GenConstants.FRONTEND_INDEX_TREE_TEMPLATE_PREFIX);
}
/**
* 解析前端模板类型。
*
* @param frontendType 前端模板类型
* @return 已规范化的模板目录名
*/
private static String resolveFrontendType(String frontendType) {
String type = StringUtils.blankToDefault(frontendType, GenConstants.FRONTEND_TYPE_VUE);
if (!type.matches("[A-Za-z0-9_-]+")) {
throw new ServiceException("前端模板类型仅支持字母、数字、下划线和中划线");
}
return type;
}
private static String getFrontendTemplatePath(String frontendType, String templateName) {
return StringUtils.format("vm/{}/{}", resolveFrontendType(frontendType), templateName);
}
/**
* 获取前端页面模板路径,页面输出扩展名由模板文件名决定。
*
* @param frontendType 前端模板类型
* @param templatePrefix 页面模板文件名前缀
* @return 页面模板路径
*/
private static String getFrontendPageTemplatePath(String frontendType, String templatePrefix) {
String type = resolveFrontendType(frontendType);
String pattern = StringUtils.format("classpath*:vm/{}/{}.*.vm", type, templatePrefix);
try {
Resource[] resources = RESOURCE_PATTERN_RESOLVER.getResources(pattern);
if (resources.length == 0) {
throw new ServiceException(StringUtils.format("未找到前端模板: vm/{}/{}.*.vm", type, templatePrefix));
}
return Arrays.stream(resources)
.map(Resource::getFilename)
.filter(StringUtils::isNotBlank)
.sorted()
.findFirst()
.map(fileName -> StringUtils.format("vm/{}/{}", type, fileName))
.orElseThrow(() -> new ServiceException(StringUtils.format("未找到前端模板: vm/{}/{}.*.vm", type, templatePrefix)));
} catch (IOException e) {
throw new ServiceException(StringUtils.format("读取前端模板失败: vm/{}/{}.*.vm", type, templatePrefix), e);
}
}
private static boolean isFrontendPageTemplate(String template, String templatePrefix) {
String fileName = getTemplateFileName(template);
return fileName.startsWith(templatePrefix + ".") && fileName.endsWith(".vm");
}
private static String getTemplateFileName(String template) {
int index = Math.max(template.lastIndexOf('/'), template.lastIndexOf('\\'));
return index >= 0 ? template.substring(index + 1) : template;
}
/**
* 获取包前缀
*
@@ -0,0 +1,94 @@
import type { #if(!$table.tree)PageResult, #end R } from '@/api/types';
import request from '@/api/request';
import type { ${BusinessName}Form, ${BusinessName}Query, ${BusinessName}VO } from './types';
/**
* 查询${functionName}列表
*/
export function list${BusinessName}(query?: ${BusinessName}Query) {
return request<R<#if($table.tree)${BusinessName}VO[]#else PageResult<${BusinessName}VO>#end>>({
url: '/${moduleName}/${businessName}/list',
method: 'get',
params: query
});
}
/**
* 查询${functionName}详细
*/
export function get${BusinessName}(${pkColumn.javaField}: string | number) {
return request<R<${BusinessName}VO>>({
url: '/${moduleName}/${businessName}/' + ${pkColumn.javaField},
method: 'get'
});
}
/**
* 新增${functionName}
*/
export function add${BusinessName}(data: ${BusinessName}Form) {
return request<R>({
url: '/${moduleName}/${businessName}',
method: 'post',
data
});
}
/**
* 修改${functionName}
*/
export function update${BusinessName}(data: ${BusinessName}Form) {
return request<R>({
url: '/${moduleName}/${businessName}',
method: 'put',
data
});
}
#if($enableStatus)
/**
* 修改${functionName}状态
*/
export function change${BusinessName}Status(
${pkColumn.javaField}: string | number,
${statusField}: #if($statusColumn.javaType == 'Boolean')boolean#elseif($statusColumn.javaType == 'Integer' || $statusColumn.javaType == 'Long')number#else string#end
) {
return request<R>({
url: '/${moduleName}/${businessName}/changeStatus',
method: 'put',
data: {
${pkColumn.javaField},
${statusField}
}
});
}
#end
#if($enableSort)
/**
* 调整${functionName}排序
*/
export function update${BusinessName}Sort(
${pkColumn.javaField}: string | number,
${sortField}: #if($sortColumn.javaType == 'LocalDateTime' || $sortColumn.javaType == 'String')string#else number#end
) {
return request<R>({
url: '/${moduleName}/${businessName}/updateSort',
method: 'put',
data: {
${pkColumn.javaField},
${sortField}
}
});
}
#end
/**
* 删除${functionName}
*/
export function del${BusinessName}(${pkColumn.javaField}: string | number | Array<string | number>) {
return request<R>({
url: '/${moduleName}/${businessName}/' + ${pkColumn.javaField},
method: 'delete'
});
}
@@ -0,0 +1,472 @@
#set($needDict = $dicts != '')
#set($needImagePreview = false)
#set($needImageUpload = false)
#set($needFileUpload = false)
#set($needEditor = false)
#set($needCheckbox = false)
#set($needSelect = false)
#set($needTextArea = false)
#set($needDigit = false)
#set($needDateField = false)
#set($needSwitchField = false)
#foreach($column in $columns)
#if($column.list && $column.htmlType == "imageUpload")
#set($needImagePreview = true)
#end
#if(($column.insert || $column.edit) && $column.htmlType == "imageUpload")
#set($needImageUpload = true)
#end
#if(($column.insert || $column.edit) && $column.htmlType == "fileUpload")
#set($needFileUpload = true)
#end
#if(($column.insert || $column.edit) && $column.htmlType == "editor")
#set($needEditor = true)
#end
#if(($column.insert || $column.edit) && $column.htmlType == "checkbox")
#set($needCheckbox = true)
#end
#if(($column.insert || $column.edit || $column.query) && ($column.htmlType == "select" || $column.htmlType == "radio" || $column.htmlType == "switch"))
#set($needSelect = true)
#end
#if(($column.insert || $column.edit) && $column.htmlType == "textarea")
#set($needTextArea = true)
#end
#if(($column.insert || $column.edit) && $column.htmlType == "inputNumber")
#set($needDigit = true)
#end
#if(($column.insert || $column.edit) && $column.htmlType == "datetime")
#set($needDateField = true)
#end
#if(($column.insert || $column.edit || $column.list) && $column.htmlType == "switch")
#set($needSwitchField = true)
#end
#end
#macro(comment $column)#set($idx=$column.columnComment.indexOf(""))#if($idx != -1)$column.columnComment.substring(0, $idx)#else$column.columnComment#end#end
import { DeleteOutlined#if($enableExport), DownloadOutlined#end, EditOutlined, PlusOutlined, SortAscendingOutlined } from '@ant-design/icons';
import {
ModalForm,
PageContainer,
#if($needCheckbox)
ProFormCheckbox,
#end
#if($needDateField)
ProFormDateTimePicker,
#end
#if($needDigit)
ProFormDigit,
#end
#if($needSelect)
ProFormSelect,
#end
#if($needTextArea)
ProFormTextArea,
#end
ProFormText,
ProFormTreeSelect,
ProTable,
type ActionType,
type ProColumns
} from '@ant-design/pro-components';
import { useBoolean } from 'ahooks';
import { Button, Form, message#if($enableStatus || $needSwitchField), Switch#end#if($enableSort), InputNumber#end } from 'antd';
import { useMemo, useRef, useState } from 'react';
import type { ${BusinessName}Form, ${BusinessName}Query, ${BusinessName}VO } from '@/api/${moduleName}/${businessName}/types';
import {
add${BusinessName},
#if($enableStatus)
change${BusinessName}Status,
#end
del${BusinessName},
get${BusinessName},
list${BusinessName},
#if($enableSort)
update${BusinessName}Sort,
#end
update${BusinessName}
} from '@/api/${moduleName}/${businessName}';
#if($needDict)
import DictTag from '@/components/common/DictTag';
#end
#if($needFileUpload)
import FileUpload from '@/components/common/FileUpload';
#end
#if($needImagePreview)
import ImagePreview from '@/components/common/ImagePreview';
#end
#if($needImageUpload)
import ImageUpload from '@/components/common/ImageUpload';
#end
#if($needEditor)
import RichTextEditor from '@/components/common/RichTextEditor';
#end
import RowActions from '@/components/common/RowActions';
#if($needDict)
import { useDict } from '@/hooks/useDict';
#end
#if($enableExport)
import { useTableExport } from '@/hooks/useTableExport';
#end
import { useTreeTableExpand } from '@/hooks/useTreeTableExpand';
import { useUserStore } from '@/stores/userStore';
#if($needDict)
import { dictOptions } from '@/utils/dict';
#end
#if($enableStatus)
import { confirmAction } from '@/utils/modal';
#end
import { hasPermi } from '@/utils/permission';
import { filterTree#if($needDateField), formatDateTimeFields#end, handleTree#if($needDateField), toDayjsFields#end } from '@/utils/ruoyi';
const default${BusinessName}Form: ${BusinessName}Form = {
${treeParentCode}: ${treeRootValueTsLiteral},
#foreach($column in $columns)
#if(($column.insert || $column.edit) && !$column.pk && $column.htmlType == "checkbox")
${column.javaField}: [],
#end
#end
};
interface ${BusinessName}SelectNode {
title: string;
value: string | number;
children?: ${BusinessName}SelectNode[];
}
function toTreeSelectData(nodes: ${BusinessName}VO[]): ${BusinessName}SelectNode[] {
return nodes.map(node => ({
title: String(node.${treeName} || ''),
value: node.${treeCode},
children: node.children ? toTreeSelectData(node.children) : undefined
}));
}
#macro(switchActiveValue $column)#if($column.javaType == "Boolean")true#elseif($column.javaType == "Integer" || $column.javaType == "Long")0#else'0'#end#end
#macro(switchInactiveValue $column)#if($column.javaType == "Boolean")false#elseif($column.javaType == "Integer" || $column.javaType == "Long")1#else'1'#end#end
#if($enableStatus)
const ${statusField}ActiveValue = #if($statusColumn.javaType == "Boolean")true#elseif($statusColumn.javaType == "Integer" || $statusColumn.javaType == "Long")0#else'0'#end;
const ${statusField}InactiveValue = #if($statusColumn.javaType == "Boolean")false#elseif($statusColumn.javaType == "Integer" || $statusColumn.javaType == "Long")1#else'1'#end;
#end
export default function ${BusinessName}Page() {
const actionRef = useRef<ActionType | undefined>(undefined);
const [form] = Form.useForm<${BusinessName}Form>();
const userInfo = useUserStore(state => state.userInfo);
#if($needDict)
const dicts = useDict(${dicts});
#end
const [treeOptions, setTreeOptions] = useState<${BusinessName}VO[]>([]);
const [tableRows, setTableRows] = useState<${BusinessName}VO[]>([]);
const { expandAll, expandedRowKeys, onExpandedRowsChange, syncExpandedRows, toggleExpandAll } =
useTreeTableExpand<${BusinessName}VO>(row => row.${treeCode});
const [modalOpen, { setTrue: openModal, setFalse: closeModal }] = useBoolean(false);
const [modalTitle, setModalTitle] = useState('');
#if($enableExport)
const { updateExportParams, exportFile } = useTableExport();
#end
const canAdd = hasPermi(userInfo, ['${permissionPrefix}:add']);
const canEdit = hasPermi(userInfo, ['${permissionPrefix}:edit']);
const canRemove = hasPermi(userInfo, ['${permissionPrefix}:remove']);
#if($enableExport)
const canExport = hasPermi(userInfo, ['${permissionPrefix}:export']);
#end
const treeSelectData = useMemo(
() => [{ title: '顶级节点', value: ${treeRootValueTsLiteral}, children: toTreeSelectData(treeOptions) }],
[treeOptions]
);
const loadTreeOptions = async (excludeId?: string | number) => {
const res = await list${BusinessName}();
const rows = handleTree<${BusinessName}VO>(res.data || [], '${treeCode}', '${treeParentCode}');
setTreeOptions(excludeId ? filterTree(rows, node => node.${treeCode} !== excludeId) : rows);
};
const openAdd = async (row?: ${BusinessName}VO) => {
await loadTreeOptions();
form.resetFields();
form.setFieldsValue({ ...default${BusinessName}Form, ${treeParentCode}: row?.${treeCode} || ${treeRootValueTsLiteral} });
setModalTitle('添加${functionName}');
openModal();
};
const openEdit = async (row: ${BusinessName}VO) => {
await loadTreeOptions(row.${treeCode});
const res = await get${BusinessName}(row.${pkColumn.javaField});
const data = #if($needDateField)toDayjsFields({ ...res.data }, [
#foreach($column in $columns)
#if(($column.insert || $column.edit) && $column.htmlType == "datetime")
'${column.javaField}',
#end
#end
])#else{ ...res.data }#end;
#foreach($column in $columns)
#if(($column.insert || $column.edit) && $column.htmlType == "checkbox")
if (typeof data.${column.javaField} === 'string') {
data.${column.javaField} = data.${column.javaField}.split(',');
}
#end
#end
form.resetFields();
form.setFieldsValue(data);
setModalTitle('修改${functionName}');
openModal();
};
const submitForm = async (values: ${BusinessName}Form) => {
const submitValues = #if($needDateField)formatDateTimeFields({ ...values }, [
#foreach($column in $columns)
#if(($column.insert || $column.edit) && $column.htmlType == "datetime")
'${column.javaField}',
#end
#end
])#else{ ...values }#end;
#foreach($column in $columns)
#if(($column.insert || $column.edit) && $column.htmlType == "checkbox")
if (Array.isArray(submitValues.${column.javaField})) {
submitValues.${column.javaField} = submitValues.${column.javaField}.join(',');
}
#end
#end
submitValues.${pkColumn.javaField} ? await update${BusinessName}(submitValues) : await add${BusinessName}(submitValues);
message.success('操作成功');
form.resetFields();
actionRef.current?.reload();
return true;
};
const remove = async (row: ${BusinessName}VO) => {
await del${BusinessName}(row.${pkColumn.javaField});
message.success('删除成功');
actionRef.current?.reload();
};
#if($enableStatus)
const handleStatusChange = async (row: ${BusinessName}VO, checked: boolean) => {
const previousStatus = row.${statusField};
const status = checked ? ${statusField}ActiveValue : ${statusField}InactiveValue;
const text = checked ? '启用' : '停用';
try {
await confirmAction(`确认要"${text}"吗?`);
await change${BusinessName}Status(row.${pkColumn.javaField}, status);
message.success(`${text}成功`);
actionRef.current?.reload();
} catch {
row.${statusField} = previousStatus;
actionRef.current?.reload();
}
};
#end
#if($enableSort)
const handleSortChange = async (row: ${BusinessName}VO, value?: number | null) => {
await update${BusinessName}Sort(row.${pkColumn.javaField}, value || 0);
message.success('排序更新成功');
actionRef.current?.reload();
};
#end
const columns: ProColumns<${BusinessName}VO>[] = [
#foreach($column in $columns)
#set($javaField=$column.javaField)
#set($dictType=$column.dictType)
#if($column.list && !$column.pk)
#if($enableStatus && $statusField == $javaField)
{
title: '#comment($column)',
dataIndex: '${javaField}',
valueType: 'select',
width: 100,
#if("" != $dictType)
fieldProps: { options: dictOptions(dicts.${dictType}) },
#end
render: (_, row) => (
<Switch
checked={row.${javaField} === ${statusField}ActiveValue}
disabled={!canEdit}
onChange={checked => handleStatusChange(row, checked)}
/>
)
},
#elseif($enableSort && $sortField == $javaField)
{
title: '#comment($column)',
dataIndex: '${javaField}',
search: false,
width: 130,
render: (_, row) => <InputNumber min={0} value={row.${javaField} as number} onChange={value => handleSortChange(row, value)} />
},
#elseif($column.htmlType == "datetime")
{ title: '#comment($column)', dataIndex: '${javaField}', valueType: 'dateTime', search: #if($column.query)true#else false#end, width: 170 },
#elseif($column.htmlType == "imageUpload")
{
title: '#comment($column)',
dataIndex: '${javaField}Url',
search: false,
width: 100,
render: (_, row) => <ImagePreview src={row.${javaField}Url || String(row.${javaField} || '')} width={50} height={50} />
},
#elseif($column.dictColumn)
{
title: '#comment($column)',
dataIndex: '${javaField}',
valueType: 'select',
fieldProps: { options: dictOptions(dicts.${dictType}) },
render: (_, row) => <DictTag options={dicts.${dictType}} value={row.${javaField}} />
},
#elseif($column.htmlType == "switch")
{
title: '#comment($column)',
dataIndex: '${javaField}',
valueType: 'select',
width: 100,
render: (_, row) => <Switch checked={row.${javaField} === #switchActiveValue($column)} disabled />
},
#else
{ title: '#comment($column)', dataIndex: '${javaField}'#if(!$column.query), search: false#end },
#end
#end
#end
{
title: '操作',
valueType: 'option',
width: 130,
fixed: 'right',
render: (_, row) => (
<RowActions
actions={[
canEdit && { key: 'edit', label: '修改', icon: <EditOutlined />, onClick: () => openEdit(row) },
canAdd && { key: 'add', label: '新增', icon: <PlusOutlined />, onClick: () => openAdd(row) },
canRemove && {
key: 'delete',
label: '删除',
icon: <DeleteOutlined />,
danger: true,
confirm: `是否确认删除${functionName}编号为"#[[${row.]]#${pkColumn.javaField}#[[}]]#"的数据项?`,
onClick: () => remove(row)
}
]}
/>
)
}
];
return (
<PageContainer title="${functionName}">
<ProTable<${BusinessName}VO, ${BusinessName}Query>
actionRef={actionRef}
rowKey="${treeCode}"
columns={columns}
scroll={{ x: 1000 }}
pagination={false}
search={{ labelWidth: 90 }}
expandable={{
expandedRowKeys,
onExpandedRowsChange
}}
request={async params => {
const res = await list${BusinessName}(params);
const rows = handleTree<${BusinessName}VO>(res.data || [], '${treeCode}', '${treeParentCode}');
setTableRows(rows);
syncExpandedRows(rows, expandAll);
#if($enableExport)
updateExportParams(params);
#end
return { data: rows, total: rows.length, success: true };
}}
toolbar={{ title: '${functionName}列表' }}
toolBarRender={() => [
canAdd && (
<Button key="add" type="primary" icon={<PlusOutlined />} onClick={() => openAdd()}>
新增
</Button>
),
<Button
key="expand"
icon={<SortAscendingOutlined />}
onClick={() => toggleExpandAll(tableRows)}
>
展开/折叠
</Button>#if($enableExport),
canExport && (
<Button
key="export"
icon={<DownloadOutlined />}
onClick={() => exportFile('/${moduleName}/${businessName}/export', () => `${businessName}_#[[${Date.now()}]]#.xlsx`)}
>
导出
</Button>
)#end
]}
/>
<ModalForm<${BusinessName}Form>
title={modalTitle}
open={modalOpen}
width={560}
form={form}
layout="vertical"
initialValues={default${BusinessName}Form}
modalProps={{ destroyOnHidden: true, onCancel: closeModal }}
onOpenChange={open => !open && closeModal()}
onFinish={submitForm}
>
<ProFormText name="${pkColumn.javaField}" hidden />
#foreach($column in $columns)
#if(($column.insert || $column.edit) && !$column.pk)
#set($field=$column.javaField)
#set($dictType=$column.dictType)
#if($field == $treeParentCode)
<ProFormTreeSelect
name="${field}"
label="#comment($column)"
#if($column.required)rules={[{ required: true, message: '#comment($column)不能为空' }]}#end
fieldProps={{
allowClear: true,
treeDefaultExpandAll: true,
treeData: treeSelectData,
placeholder: '请选择#comment($column)'
}}
/>
#elseif($column.htmlType == "input")
<ProFormText name="${field}" label="#comment($column)" #if($column.required)rules={[{ required: true, message: '#comment($column)不能为空' }]}#end />
#elseif($column.htmlType == "textarea")
<ProFormTextArea name="${field}" label="#comment($column)" #if($column.required)rules={[{ required: true, message: '#comment($column)不能为空' }]}#end />
#elseif($column.htmlType == "inputNumber")
<ProFormDigit name="${field}" label="#comment($column)" min={0} #if($column.required)rules={[{ required: true, message: '#comment($column)不能为空' }]}#end />
#elseif(($column.htmlType == "select" || $column.htmlType == "radio" || $column.htmlType == "switch") && "" != $dictType)
<ProFormSelect name="${field}" label="#comment($column)" options={dictOptions(dicts.${dictType})} #if($column.required)rules={[{ required: true, message: '#comment($column)不能为空' }]}#end />
#elseif($column.htmlType == "checkbox" && "" != $dictType)
<ProFormCheckbox.Group name="${field}" label="#comment($column)" options={dictOptions(dicts.${dictType})} #if($column.required)rules={[{ required: true, message: '#comment($column)不能为空' }]}#end />
#elseif($column.htmlType == "switch")
<Form.Item
name="${field}"
label="#comment($column)"
valuePropName="checked"
getValueProps={value => ({ checked: value === #switchActiveValue($column) })}
normalize={checked => (checked ? #switchActiveValue($column) : #switchInactiveValue($column))}
>
<Switch />
</Form.Item>
#elseif($column.htmlType == "datetime")
<ProFormDateTimePicker name="${field}" label="#comment($column)" #if($column.required)rules={[{ required: true, message: '#comment($column)不能为空' }]}#end />
#elseif($column.htmlType == "imageUpload")
<Form.Item name="${field}" label="#comment($column)" #if($column.required)rules={[{ required: true, message: '#comment($column)不能为空' }]}#end>
<ImageUpload />
</Form.Item>
#elseif($column.htmlType == "fileUpload")
<Form.Item name="${field}" label="#comment($column)" #if($column.required)rules={[{ required: true, message: '#comment($column)不能为空' }]}#end>
<FileUpload />
</Form.Item>
#elseif($column.htmlType == "editor")
<Form.Item name="${field}" label="#comment($column)" #if($column.required)rules={[{ required: true, message: '#comment($column)不能为空' }]}#end>
<RichTextEditor />
</Form.Item>
#else
<ProFormText name="${field}" label="#comment($column)" #if($column.required)rules={[{ required: true, message: '#comment($column)不能为空' }]}#end />
#end
#end
#end
</ModalForm>
</PageContainer>
);
}
@@ -0,0 +1,460 @@
#set($needDict = $dicts != '')
#set($needDateRange = false)
#set($needImagePreview = false)
#set($needImageUpload = false)
#set($needFileUpload = false)
#set($needEditor = false)
#set($needCheckbox = false)
#set($needSelect = false)
#set($needTextArea = false)
#set($needDigit = false)
#set($needDateField = false)
#set($needSwitchField = false)
#foreach($column in $columns)
#if($column.htmlType == "datetime" && $column.queryType == "BETWEEN")
#set($needDateRange = true)
#end
#if($column.list && $column.htmlType == "imageUpload")
#set($needImagePreview = true)
#end
#if(($column.insert || $column.edit) && $column.htmlType == "imageUpload")
#set($needImageUpload = true)
#end
#if(($column.insert || $column.edit) && $column.htmlType == "fileUpload")
#set($needFileUpload = true)
#end
#if(($column.insert || $column.edit) && $column.htmlType == "editor")
#set($needEditor = true)
#end
#if(($column.insert || $column.edit) && $column.htmlType == "checkbox")
#set($needCheckbox = true)
#end
#if(($column.insert || $column.edit || $column.query) && ($column.htmlType == "select" || $column.htmlType == "radio" || $column.htmlType == "switch"))
#set($needSelect = true)
#end
#if(($column.insert || $column.edit) && $column.htmlType == "textarea")
#set($needTextArea = true)
#end
#if(($column.insert || $column.edit) && $column.htmlType == "inputNumber")
#set($needDigit = true)
#end
#if(($column.insert || $column.edit) && $column.htmlType == "datetime")
#set($needDateField = true)
#end
#if(($column.insert || $column.edit || $column.list) && $column.htmlType == "switch")
#set($needSwitchField = true)
#end
#end
#macro(comment $column)#set($idx=$column.columnComment.indexOf(""))#if($idx != -1)$column.columnComment.substring(0, $idx)#else$column.columnComment#end#end
import { DeleteOutlined#if($enableExport), DownloadOutlined#end, EditOutlined, PlusOutlined } from '@ant-design/icons';
import {
ModalForm,
PageContainer,
#if($needCheckbox)
ProFormCheckbox,
#end
#if($needDateField)
ProFormDateTimePicker,
#end
#if($needDigit)
ProFormDigit,
#end
#if($needSelect)
ProFormSelect,
#end
#if($needTextArea)
ProFormTextArea,
#end
ProFormText,
ProTable,
type ActionType,
type ProColumns
} from '@ant-design/pro-components';
import { useBoolean } from 'ahooks';
import { Button, Form, message, Popconfirm#if($enableStatus || $needSwitchField), Switch#end#if($enableSort), InputNumber#end } from 'antd';
import { useRef, useState } from 'react';
import type { ${BusinessName}Form, ${BusinessName}Query, ${BusinessName}VO } from '@/api/${moduleName}/${businessName}/types';
import {
add${BusinessName},
#if($enableStatus)
change${BusinessName}Status,
#end
del${BusinessName},
get${BusinessName},
list${BusinessName},
#if($enableSort)
update${BusinessName}Sort,
#end
update${BusinessName}
} from '@/api/${moduleName}/${businessName}';
#if($needDict)
import DictTag from '@/components/common/DictTag';
#end
#if($needFileUpload)
import FileUpload from '@/components/common/FileUpload';
#end
#if($needImagePreview)
import ImagePreview from '@/components/common/ImagePreview';
#end
#if($needImageUpload)
import ImageUpload from '@/components/common/ImageUpload';
#end
#if($needEditor)
import RichTextEditor from '@/components/common/RichTextEditor';
#end
import RowActions from '@/components/common/RowActions';
#if($needDict)
import { useDict } from '@/hooks/useDict';
#end
#if($needDateRange)
import { useDateRangeQuery } from '@/hooks/useDateRangeQuery';
#end
#if($enableExport)
import { useTableExport } from '@/hooks/useTableExport';
#end
import { useTableSelection } from '@/hooks/useTableSelection';
import { useUserStore } from '@/stores/userStore';
#if($needDict)
import { dictOptions } from '@/utils/dict';
#end
#if($enableStatus)
import { confirmAction } from '@/utils/modal';
#end
import { hasPermi } from '@/utils/permission';
import { #if($needDateField)formatDateTimeFields, toDayjsFields, #end toPageQuery, toTableData } from '@/utils/ruoyi';
const default${BusinessName}Form: ${BusinessName}Form = {
#foreach($column in $columns)
#if(($column.insert || $column.edit) && !$column.pk && $column.htmlType == "checkbox")
${column.javaField}: [],
#end
#end
};
#macro(switchActiveValue $column)#if($column.javaType == "Boolean")true#elseif($column.javaType == "Integer" || $column.javaType == "Long")0#else'0'#end#end
#macro(switchInactiveValue $column)#if($column.javaType == "Boolean")false#elseif($column.javaType == "Integer" || $column.javaType == "Long")1#else'1'#end#end
#if($enableStatus)
const ${statusField}ActiveValue = #if($statusColumn.javaType == "Boolean")true#elseif($statusColumn.javaType == "Integer" || $statusColumn.javaType == "Long")0#else'0'#end;
const ${statusField}InactiveValue = #if($statusColumn.javaType == "Boolean")false#elseif($statusColumn.javaType == "Integer" || $statusColumn.javaType == "Long")1#else'1'#end;
#end
export default function ${BusinessName}Page() {
const actionRef = useRef<ActionType | undefined>(undefined);
const [form] = Form.useForm<${BusinessName}Form>();
const userInfo = useUserStore(state => state.userInfo);
#if($needDict)
const dicts = useDict(${dicts});
#end
const { ids, selectedOne, handleSelectionChange, clearSelection } = useTableSelection<${BusinessName}VO>(
row => row.${pkColumn.javaField}
);
const [modalOpen, { setTrue: openModal, setFalse: closeModal }] = useBoolean(false);
const [modalTitle, setModalTitle] = useState('');
#if($enableExport)
const { updateExportParams, exportFile } = useTableExport();
#end
#foreach($column in $columns)
#if($column.query && $column.htmlType == "datetime" && $column.queryType == "BETWEEN")
#set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
const { applyDateRange: apply${AttrName}DateRange } = useDateRangeQuery('${AttrName}');
#end
#end
const canAdd = hasPermi(userInfo, ['${permissionPrefix}:add']);
const canEdit = hasPermi(userInfo, ['${permissionPrefix}:edit']);
const canRemove = hasPermi(userInfo, ['${permissionPrefix}:remove']);
#if($enableExport)
const canExport = hasPermi(userInfo, ['${permissionPrefix}:export']);
#end
const openAdd = () => {
form.resetFields();
form.setFieldsValue(default${BusinessName}Form);
setModalTitle('添加${functionName}');
openModal();
};
const openEdit = async (row?: ${BusinessName}VO) => {
const target = row || selectedOne;
if (!target?.${pkColumn.javaField}) return;
const res = await get${BusinessName}(target.${pkColumn.javaField});
const data = #if($needDateField)toDayjsFields({ ...res.data }, [
#foreach($column in $columns)
#if(($column.insert || $column.edit) && $column.htmlType == "datetime")
'${column.javaField}',
#end
#end
])#else{ ...res.data }#end;
#foreach($column in $columns)
#if(($column.insert || $column.edit) && $column.htmlType == "checkbox")
if (typeof data.${column.javaField} === 'string') {
data.${column.javaField} = data.${column.javaField}.split(',');
}
#end
#end
form.resetFields();
form.setFieldsValue(data);
setModalTitle('修改${functionName}');
openModal();
};
const submitForm = async (values: ${BusinessName}Form) => {
const submitValues = #if($needDateField)formatDateTimeFields({ ...values }, [
#foreach($column in $columns)
#if(($column.insert || $column.edit) && $column.htmlType == "datetime")
'${column.javaField}',
#end
#end
])#else{ ...values }#end;
#foreach($column in $columns)
#if(($column.insert || $column.edit) && $column.htmlType == "checkbox")
if (Array.isArray(submitValues.${column.javaField})) {
submitValues.${column.javaField} = submitValues.${column.javaField}.join(',');
}
#end
#end
submitValues.${pkColumn.javaField} ? await update${BusinessName}(submitValues) : await add${BusinessName}(submitValues);
message.success('操作成功');
form.resetFields();
actionRef.current?.reload();
return true;
};
const remove = async (row?: ${BusinessName}VO) => {
await del${BusinessName}(row?.${pkColumn.javaField} || ids);
message.success('删除成功');
clearSelection();
actionRef.current?.reloadAndRest?.();
};
#if($enableStatus)
const handleStatusChange = async (row: ${BusinessName}VO, checked: boolean) => {
const previousStatus = row.${statusField};
const status = checked ? ${statusField}ActiveValue : ${statusField}InactiveValue;
const text = checked ? '启用' : '停用';
try {
await confirmAction(`确认要"${text}"吗?`);
await change${BusinessName}Status(row.${pkColumn.javaField}, status);
message.success(`${text}成功`);
actionRef.current?.reload();
} catch {
row.${statusField} = previousStatus;
actionRef.current?.reload();
}
};
#end
#if($enableSort)
const handleSortChange = async (row: ${BusinessName}VO, value?: number | null) => {
await update${BusinessName}Sort(row.${pkColumn.javaField}, value || 0);
message.success('排序更新成功');
actionRef.current?.reload();
};
#end
const columns: ProColumns<${BusinessName}VO>[] = [
#foreach($column in $columns)
#set($javaField=$column.javaField)
#set($dictType=$column.dictType)
#if($column.list)
#if($enableStatus && $statusField == $javaField)
{
title: '#comment($column)',
dataIndex: '${javaField}',
valueType: 'select',
width: 100,
#if("" != $dictType)
fieldProps: { options: dictOptions(dicts.${dictType}) },
#end
render: (_, row) => (
<Switch
checked={row.${javaField} === ${statusField}ActiveValue}
disabled={!canEdit}
onChange={checked => handleStatusChange(row, checked)}
/>
)
},
#elseif($enableSort && $sortField == $javaField)
{
title: '#comment($column)',
dataIndex: '${javaField}',
search: false,
width: 130,
render: (_, row) => <InputNumber min={0} value={row.${javaField} as number} onChange={value => handleSortChange(row, value)} />
},
#elseif($column.htmlType == "datetime")
{ title: '#comment($column)', dataIndex: '${javaField}', valueType: 'dateTime', search: #if($column.query && $column.queryType != "BETWEEN")true#else false#end, width: 170 },
#elseif($column.htmlType == "imageUpload")
{
title: '#comment($column)',
dataIndex: '${javaField}Url',
search: false,
width: 100,
render: (_, row) => <ImagePreview src={row.${javaField}Url || String(row.${javaField} || '')} width={50} height={50} />
},
#elseif($column.dictColumn)
{
title: '#comment($column)',
dataIndex: '${javaField}',
valueType: 'select',
fieldProps: { options: dictOptions(dicts.${dictType}) },
render: (_, row) => <DictTag options={dicts.${dictType}} value={row.${javaField}} />
},
#elseif($column.htmlType == "switch")
{
title: '#comment($column)',
dataIndex: '${javaField}',
valueType: 'select',
width: 100,
render: (_, row) => <Switch checked={row.${javaField} === #switchActiveValue($column)} disabled />
},
#else
{ title: '#comment($column)', dataIndex: '${javaField}'#if(!$column.query), search: false#end },
#end
#end
#if($column.query && $column.htmlType == "datetime" && $column.queryType == "BETWEEN")
{ title: '#comment($column)', dataIndex: '${javaField}Range', valueType: 'dateTimeRange', hideInTable: true },
#end
#end
{
title: '操作',
valueType: 'option',
width: 92,
fixed: 'right',
render: (_, row) => (
<RowActions
actions={[
canEdit && { key: 'edit', label: '修改', icon: <EditOutlined />, onClick: () => openEdit(row) },
canRemove && {
key: 'delete',
label: '删除',
icon: <DeleteOutlined />,
danger: true,
confirm: `是否确认删除${functionName}编号为"#[[${row.]]#${pkColumn.javaField}#[[}]]#"的数据项?`,
onClick: () => remove(row)
}
]}
/>
)
}
];
return (
<PageContainer title="${functionName}">
<ProTable<${BusinessName}VO, ${BusinessName}Query#if($needDateRange) & Record<string, unknown>#end>
actionRef={actionRef}
rowKey="${pkColumn.javaField}"
columns={columns}
scroll={{ x: 1000 }}
search={{ labelWidth: 90 }}
pagination={{ defaultPageSize: 10, showSizeChanger: true }}
rowSelection={{ selectedRowKeys: ids, onChange: handleSelectionChange }}
request={async params => {
#if($needDateRange)
let query = toPageQuery(params);
#foreach($column in $columns)
#if($column.query && $column.htmlType == "datetime" && $column.queryType == "BETWEEN")
#set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
query = apply${AttrName}DateRange(query, params.${column.javaField}Range as [string, string] | undefined);
delete query.${column.javaField}Range;
#end
#end
#else
const query = toPageQuery(params);
#end
#if($enableExport)
updateExportParams(query);
#end
const res = await list${BusinessName}(query);
return toTableData(res);
}}
toolbar={{ title: '${functionName}列表' }}
toolBarRender={() => [
canAdd && (
<Button key="add" type="primary" icon={<PlusOutlined />} onClick={openAdd}>
新增
</Button>
),
canEdit && (
<Button key="edit" disabled={!selectedOne} icon={<EditOutlined />} onClick={() => openEdit()}>
修改
</Button>
),
canRemove && (
<Popconfirm key="delete" title={`是否确认删除${functionName}编号为"#[[${ids}]]#"的数据项?`} onConfirm={() => remove()}>
<Button danger disabled={!ids.length} icon={<DeleteOutlined />}>
删除
</Button>
</Popconfirm>
)#if($enableExport),
canExport && (
<Button
key="export"
icon={<DownloadOutlined />}
onClick={() => exportFile('/${moduleName}/${businessName}/export', () => `${businessName}_#[[${Date.now()}]]#.xlsx`)}
>
导出
</Button>
)#end
]}
/>
<ModalForm<${BusinessName}Form>
title={modalTitle}
open={modalOpen}
width={560}
form={form}
layout="vertical"
initialValues={default${BusinessName}Form}
modalProps={{ destroyOnHidden: true, onCancel: closeModal }}
onOpenChange={open => !open && closeModal()}
onFinish={submitForm}
>
<ProFormText name="${pkColumn.javaField}" hidden />
#foreach($column in $columns)
#if(($column.insert || $column.edit) && !$column.pk)
#set($field=$column.javaField)
#set($dictType=$column.dictType)
#if($column.htmlType == "input")
<ProFormText name="${field}" label="#comment($column)" #if($column.required)rules={[{ required: true, message: '#comment($column)不能为空' }]}#end />
#elseif($column.htmlType == "textarea")
<ProFormTextArea name="${field}" label="#comment($column)" #if($column.required)rules={[{ required: true, message: '#comment($column)不能为空' }]}#end />
#elseif($column.htmlType == "inputNumber")
<ProFormDigit name="${field}" label="#comment($column)" min={0} #if($column.required)rules={[{ required: true, message: '#comment($column)不能为空' }]}#end />
#elseif(($column.htmlType == "select" || $column.htmlType == "radio" || $column.htmlType == "switch") && "" != $dictType)
<ProFormSelect name="${field}" label="#comment($column)" options={dictOptions(dicts.${dictType})} #if($column.required)rules={[{ required: true, message: '#comment($column)不能为空' }]}#end />
#elseif($column.htmlType == "checkbox" && "" != $dictType)
<ProFormCheckbox.Group name="${field}" label="#comment($column)" options={dictOptions(dicts.${dictType})} #if($column.required)rules={[{ required: true, message: '#comment($column)不能为空' }]}#end />
#elseif($column.htmlType == "switch")
<Form.Item
name="${field}"
label="#comment($column)"
valuePropName="checked"
getValueProps={value => ({ checked: value === #switchActiveValue($column) })}
normalize={checked => (checked ? #switchActiveValue($column) : #switchInactiveValue($column))}
>
<Switch />
</Form.Item>
#elseif($column.htmlType == "datetime")
<ProFormDateTimePicker name="${field}" label="#comment($column)" #if($column.required)rules={[{ required: true, message: '#comment($column)不能为空' }]}#end />
#elseif($column.htmlType == "imageUpload")
<Form.Item name="${field}" label="#comment($column)" #if($column.required)rules={[{ required: true, message: '#comment($column)不能为空' }]}#end>
<ImageUpload />
</Form.Item>
#elseif($column.htmlType == "fileUpload")
<Form.Item name="${field}" label="#comment($column)" #if($column.required)rules={[{ required: true, message: '#comment($column)不能为空' }]}#end>
<FileUpload />
</Form.Item>
#elseif($column.htmlType == "editor")
<Form.Item name="${field}" label="#comment($column)" #if($column.required)rules={[{ required: true, message: '#comment($column)不能为空' }]}#end>
<RichTextEditor />
</Form.Item>
#else
<ProFormText name="${field}" label="#comment($column)" #if($column.required)rules={[{ required: true, message: '#comment($column)不能为空' }]}#end />
#end
#end
#end
</ModalForm>
</PageContainer>
);
}
@@ -0,0 +1,56 @@
import type { BaseEntity#if(!$table.tree), PageQuery#end } from '@/api/types';
#macro(tsType $column)#if($column.javaField.indexOf("id") != -1 || $column.javaField.indexOf("Id") != -1)string | number#elseif($column.javaType == 'Long' || $column.javaType == 'Integer' || $column.javaType == 'Double' || $column.javaType == 'Float' || $column.javaType == 'BigDecimal')number#elseif($column.javaType == 'Boolean')boolean#else string#end#end
export interface ${BusinessName}VO {
#foreach ($column in $columns)
#if($column.list || $column.pk)
/**
* $column.columnComment
*/
$column.javaField: #tsType($column);
#if($column.htmlType == "imageUpload")
/**
* ${column.columnComment}Url
*/
${column.javaField}Url?: string;
#end
#end
#end
#if($table.tree)
/**
* 子对象
*/
children?: ${BusinessName}VO[];
#end
}
export interface ${BusinessName}Form extends BaseEntity {
#foreach ($column in $columns)
#if($column.insert || $column.edit || $column.pk)
/**
* $column.columnComment
*/
#if($column.htmlType == "checkbox")
$column.javaField?: string | string[];
#else
$column.javaField?: #tsType($column);
#end
#end
#end
}
export interface ${BusinessName}Query#if(!$table.tree) extends PageQuery#end {
#foreach ($column in $columns)
#if($column.query)
/**
* $column.columnComment
*/
$column.javaField?: #tsType($column);
#end
#end
/**
* 日期范围参数
*/
params?: Record<string, unknown>;
}
+2 -4
View File
@@ -1005,10 +1005,9 @@ create table gen_table (
data_name varchar2(200) default '',
table_name varchar2(200) default '',
table_comment varchar2(500) default '',
sub_table_name varchar2(64) default null,
sub_table_fk_name varchar2(64) default null,
class_name varchar2(100) default '',
tpl_category varchar2(200) default 'crud',
frontend_type varchar2(50) default 'vue',
package_name varchar2(100),
module_name varchar2(30),
business_name varchar2(30),
@@ -1032,10 +1031,9 @@ comment on column gen_table.table_id is '编号';
comment on column gen_table.data_name is '数据源名称';
comment on column gen_table.table_name is '表名称';
comment on column gen_table.table_comment is '表描述';
comment on column gen_table.sub_table_name is '关联子表的表名';
comment on column gen_table.sub_table_fk_name is '子表关联的外键名';
comment on column gen_table.class_name is '实体类名称';
comment on column gen_table.tpl_category is '使用的模板(crud单表操作 tree树表操作)';
comment on column gen_table.frontend_type is '前端模板类型,对应 vm 下的模板目录';
comment on column gen_table.package_name is '生成包路径';
comment on column gen_table.module_name is '生成模块名';
comment on column gen_table.business_name is '生成业务名';
+2 -4
View File
@@ -1004,10 +1004,9 @@ create table if not exists gen_table
data_name varchar(200) default ''::varchar,
table_name varchar(200) default ''::varchar,
table_comment varchar(500) default ''::varchar,
sub_table_name varchar(64) default ''::varchar,
sub_table_fk_name varchar(64) default ''::varchar,
class_name varchar(100) default ''::varchar,
tpl_category varchar(200) default 'crud'::varchar,
frontend_type varchar(50) default 'vue'::varchar,
package_name varchar(100) default null::varchar,
module_name varchar(30) default null::varchar,
business_name varchar(30) default null::varchar,
@@ -1030,10 +1029,9 @@ comment on column gen_table.table_id is '编号';
comment on column gen_table.data_name is '数据源名称';
comment on column gen_table.table_name is '表名称';
comment on column gen_table.table_comment is '表描述';
comment on column gen_table.sub_table_name is '关联子表的表名';
comment on column gen_table.sub_table_fk_name is '子表关联的外键名';
comment on column gen_table.class_name is '实体类名称';
comment on column gen_table.tpl_category is '使用的模板(CRUD单表操作 TREE树表操作)';
comment on column gen_table.frontend_type is '前端模板类型,对应 vm 下的模板目录';
comment on column gen_table.package_name is '生成包路径';
comment on column gen_table.module_name is '生成模块名';
comment on column gen_table.business_name is '生成业务名';
+1 -2
View File
@@ -736,10 +736,9 @@ create table gen_table (
data_name varchar(200) default '' comment '数据源名称',
table_name varchar(200) default '' comment '表名称',
table_comment varchar(500) default '' comment '表描述',
sub_table_name varchar(64) default null comment '关联子表的表名',
sub_table_fk_name varchar(64) default null comment '子表关联的外键名',
class_name varchar(100) default '' comment '实体类名称',
tpl_category varchar(200) default 'crud' comment '使用的模板(crud单表操作 tree树表操作)',
frontend_type varchar(50) default 'vue' comment '前端模板类型,对应 vm 下的模板目录',
package_name varchar(100) comment '生成包路径',
module_name varchar(30) comment '生成模块名',
business_name varchar(30) comment '生成业务名',
+7 -14
View File
@@ -216,10 +216,9 @@ CREATE TABLE gen_table
data_name nvarchar(200) DEFAULT '' NULL,
table_name nvarchar(200) DEFAULT '' NULL,
table_comment nvarchar(500) DEFAULT '' NULL,
sub_table_name nvarchar(64) NULL,
sub_table_fk_name nvarchar(64) NULL,
class_name nvarchar(100) DEFAULT '' NULL,
tpl_category nvarchar(200) DEFAULT ('crud') NULL,
frontend_type nvarchar(50) DEFAULT ('vue') NULL,
package_name nvarchar(100) NULL,
module_name nvarchar(30) NULL,
business_name nvarchar(30) NULL,
@@ -265,18 +264,6 @@ EXEC sys.sp_addextendedproperty
'TABLE', N'gen_table',
'COLUMN', N'table_comment'
GO
EXEC sys.sp_addextendedproperty
'MS_Description', N'关联子表的表名' ,
'SCHEMA', N'dbo',
'TABLE', N'gen_table',
'COLUMN', N'sub_table_name'
GO
EXEC sys.sp_addextendedproperty
'MS_Description', N'子表关联的外键名' ,
'SCHEMA', N'dbo',
'TABLE', N'gen_table',
'COLUMN', N'sub_table_fk_name'
GO
EXEC sys.sp_addextendedproperty
'MS_Description', N'实体类名称' ,
'SCHEMA', N'dbo',
@@ -289,6 +276,12 @@ EXEC sys.sp_addextendedproperty
'TABLE', N'gen_table',
'COLUMN', N'tpl_category'
GO
EXEC sys.sp_addextendedproperty
'MS_Description', N'前端模板类型,对应 vm 下的模板目录' ,
'SCHEMA', N'dbo',
'TABLE', N'gen_table',
'COLUMN', N'frontend_type'
GO
EXEC sys.sp_addextendedproperty
'MS_Description', N'生成包路径' ,
'SCHEMA', N'dbo',