From 97524974d549a4a9c0e5fa7a02b25587f5cb1e1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=96=AF=E7=8B=82=E7=9A=84=E7=8B=AE=E5=AD=90Li?= <15040126243@163.com> Date: Wed, 3 Jun 2026 15:27:39 +0800 Subject: [PATCH] =?UTF-8?q?update=20=E4=BC=98=E5=8C=96=20=E4=BB=A3?= =?UTF-8?q?=E7=A0=81=E7=94=9F=E6=88=90=E6=94=AF=E6=8C=81=E6=8E=A5=E5=85=A5?= =?UTF-8?q?=E5=90=84=E7=A7=8D=E4=B8=8D=E5=90=8C=E5=89=8D=E7=AB=AF=20?= =?UTF-8?q?=E5=8F=AA=E9=9C=80=E8=A6=81=E5=B0=86=E6=A8=A1=E6=9D=BF=E6=94=BE?= =?UTF-8?q?=E5=88=B0vm=E6=96=87=E4=BB=B6=E5=A4=B9=E4=B8=8B=20=E5=89=8D?= =?UTF-8?q?=E7=AB=AF=E4=BC=A0=E5=AF=B9=E5=BA=94=E7=9A=84=E6=96=87=E4=BB=B6?= =?UTF-8?q?=E5=A4=B9=E5=90=8D=E5=8D=B3=E5=8F=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../references/frontend.md | 10 +- .../dromara/gen/constant/GenConstants.java | 25 +- .../java/org/dromara/gen/domain/GenTable.java | 5 + .../gen/service/GenTableServiceImpl.java | 2 +- .../java/org/dromara/gen/util/GenUtils.java | 1 + .../dromara/gen/util/TemplateEngineUtils.java | 150 +++++- .../src/main/resources/vm/react/api.ts.vm | 94 ++++ .../main/resources/vm/react/index-tree.tsx.vm | 472 ++++++++++++++++++ .../src/main/resources/vm/react/index.tsx.vm | 460 +++++++++++++++++ .../src/main/resources/vm/react/types.ts.vm | 56 +++ script/sql/oracle/oracle_ry_vue.sql | 6 +- script/sql/postgres/postgres_ry_vue.sql | 6 +- script/sql/ry_vue.sql | 3 +- script/sql/sqlserver/sqlserver_ry_vue.sql | 21 +- 14 files changed, 1266 insertions(+), 45 deletions(-) create mode 100644 ruoyi-modules/ruoyi-gen/src/main/resources/vm/react/api.ts.vm create mode 100644 ruoyi-modules/ruoyi-gen/src/main/resources/vm/react/index-tree.tsx.vm create mode 100644 ruoyi-modules/ruoyi-gen/src/main/resources/vm/react/index.tsx.vm create mode 100644 ruoyi-modules/ruoyi-gen/src/main/resources/vm/react/types.ts.vm diff --git a/.codex/skills/ruoyi-plus-ai-coding/references/frontend.md b/.codex/skills/ruoyi-plus-ai-coding/references/frontend.md index 2ba176945..72e17a9a8 100644 --- a/.codex/skills/ruoyi-plus-ai-coding/references/frontend.md +++ b/.codex/skills/ruoyi-plus-ai-coding/references/frontend.md @@ -2,11 +2,19 @@ ## 优先参考的代码来源 -- `ruoyi-modules/ruoyi-gen/src/main/resources/vm/vue/*.vm` +- `ruoyi-modules/ruoyi-gen/src/main/resources/vm//*.vm` +- 默认 Vue 模板在 `vm/vue`,React 模板在 `vm/react` - 前端工程中与目标模块最接近的现有页面 当前 boot4 仓库通常只含后端与 generator 前端模板;如果前端工程不在当前 root,先以 generator 模板约定为准,再对照用户提供的前端目录或相邻仓库。 +## 前端模板选择规则 + +- `gen_table.frontend_type` 存字符串,值直接对应 `vm` 下的模板目录,例如 `vue`、`react`。 +- 生成器按 `vm//api.ts.vm`、`types.ts.vm`、`index.*.vm`、`index-tree.*.vm` 查找模板。 +- 页面输出后缀由页面模板文件名决定:`index.vue.vm` 输出 `index.vue`,`index.tsx.vm` 输出 `index.tsx`。 +- 新增其他前端时优先只新增 `vm/` 目录和对应 VM 文件,不在 Java 代码里增加数字枚举或硬编码分支。 + ## API 文件规则 - 从 `@/utils/request` 引入 `request`。 diff --git a/ruoyi-modules/ruoyi-gen/src/main/java/org/dromara/gen/constant/GenConstants.java b/ruoyi-modules/ruoyi-gen/src/main/java/org/dromara/gen/constant/GenConstants.java index 9996b40e8..8de67d966 100644 --- a/ruoyi-modules/ruoyi-gen/src/main/java/org/dromara/gen/constant/GenConstants.java +++ b/ruoyi-modules/ruoyi-gen/src/main/java/org/dromara/gen/constant/GenConstants.java @@ -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 diff --git a/ruoyi-modules/ruoyi-gen/src/main/java/org/dromara/gen/domain/GenTable.java b/ruoyi-modules/ruoyi-gen/src/main/java/org/dromara/gen/domain/GenTable.java index 581a6380e..51555b038 100644 --- a/ruoyi-modules/ruoyi-gen/src/main/java/org/dromara/gen/domain/GenTable.java +++ b/ruoyi-modules/ruoyi-gen/src/main/java/org/dromara/gen/domain/GenTable.java @@ -61,6 +61,11 @@ public class GenTable extends BaseEntity { */ private String tplCategory; + /** + * 前端模板类型,对应 vm 下的模板目录 + */ + private String frontendType; + /** * 生成包路径 */ diff --git a/ruoyi-modules/ruoyi-gen/src/main/java/org/dromara/gen/service/GenTableServiceImpl.java b/ruoyi-modules/ruoyi-gen/src/main/java/org/dromara/gen/service/GenTableServiceImpl.java index 5c3442fc7..5482a9836 100644 --- a/ruoyi-modules/ruoyi-gen/src/main/java/org/dromara/gen/service/GenTableServiceImpl.java +++ b/ruoyi-modules/ruoyi-gen/src/main/java/org/dromara/gen/service/GenTableServiceImpl.java @@ -453,7 +453,7 @@ public class GenTableServiceImpl implements IGenTableService { table.setMenuIds(menuIds); setPkColumn(table); Dict context = TemplateEngineUtils.buildContext(table); - List templates = TemplateEngineUtils.getTemplateList(table.getTplCategory(), table.getDataName()); + List templates = TemplateEngineUtils.getTemplateList(table.getTplCategory(), table.getDataName(), table.getFrontendType()); return new RenderContext(table, context, templates); } diff --git a/ruoyi-modules/ruoyi-gen/src/main/java/org/dromara/gen/util/GenUtils.java b/ruoyi-modules/ruoyi-gen/src/main/java/org/dromara/gen/util/GenUtils.java index 0bb21d6a5..6d5bf98bd 100644 --- a/ruoyi-modules/ruoyi-gen/src/main/java/org/dromara/gen/util/GenUtils.java +++ b/ruoyi-modules/ruoyi-gen/src/main/java/org/dromara/gen/util/GenUtils.java @@ -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); } diff --git a/ruoyi-modules/ruoyi-gen/src/main/java/org/dromara/gen/util/TemplateEngineUtils.java b/ruoyi-modules/ruoyi-gen/src/main/java/org/dromara/gen/util/TemplateEngineUtils.java index 09196476a..1dd10fe89 100644 --- a/ruoyi-modules/ruoyi-gen/src/main/java/org/dromara/gen/util/TemplateEngineUtils.java +++ b/ruoyi-modules/ruoyi-gen/src/main/java/org/dromara/gen/util/TemplateEngineUtils.java @@ -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 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 getTemplateList(String tplCategory, String dsName) { + return getTemplateList(tplCategory, dsName, GenConstants.FRONTEND_TYPE_VUE); + } + + /** + * 获取模板信息 + * + * @param tplCategory 前端页面模板分类 + * @param dsName 数据源名称 + * @param frontendType 前端模板类型 + * @return 模板列表 + */ + public static List getTemplateList(String tplCategory, String dsName, String frontendType) { List 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; + } + /** * 获取包前缀 * diff --git a/ruoyi-modules/ruoyi-gen/src/main/resources/vm/react/api.ts.vm b/ruoyi-modules/ruoyi-gen/src/main/resources/vm/react/api.ts.vm new file mode 100644 index 000000000..0ff896bd5 --- /dev/null +++ b/ruoyi-modules/ruoyi-gen/src/main/resources/vm/react/api.ts.vm @@ -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#end>>({ + url: '/${moduleName}/${businessName}/list', + method: 'get', + params: query + }); +} + +/** + * 查询${functionName}详细 + */ +export function get${BusinessName}(${pkColumn.javaField}: string | number) { + return request>({ + url: '/${moduleName}/${businessName}/' + ${pkColumn.javaField}, + method: 'get' + }); +} + +/** + * 新增${functionName} + */ +export function add${BusinessName}(data: ${BusinessName}Form) { + return request({ + url: '/${moduleName}/${businessName}', + method: 'post', + data + }); +} + +/** + * 修改${functionName} + */ +export function update${BusinessName}(data: ${BusinessName}Form) { + return request({ + 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({ + 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({ + url: '/${moduleName}/${businessName}/updateSort', + method: 'put', + data: { + ${pkColumn.javaField}, + ${sortField} + } + }); +} + +#end +/** + * 删除${functionName} + */ +export function del${BusinessName}(${pkColumn.javaField}: string | number | Array) { + return request({ + url: '/${moduleName}/${businessName}/' + ${pkColumn.javaField}, + method: 'delete' + }); +} diff --git a/ruoyi-modules/ruoyi-gen/src/main/resources/vm/react/index-tree.tsx.vm b/ruoyi-modules/ruoyi-gen/src/main/resources/vm/react/index-tree.tsx.vm new file mode 100644 index 000000000..c3952c827 --- /dev/null +++ b/ruoyi-modules/ruoyi-gen/src/main/resources/vm/react/index-tree.tsx.vm @@ -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(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) => ( + handleStatusChange(row, checked)} + /> + ) + }, +#elseif($enableSort && $sortField == $javaField) + { + title: '#comment($column)', + dataIndex: '${javaField}', + search: false, + width: 130, + render: (_, row) => 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) => + }, +#elseif($column.dictColumn) + { + title: '#comment($column)', + dataIndex: '${javaField}', + valueType: 'select', + fieldProps: { options: dictOptions(dicts.${dictType}) }, + render: (_, row) => + }, +#elseif($column.htmlType == "switch") + { + title: '#comment($column)', + dataIndex: '${javaField}', + valueType: 'select', + width: 100, + render: (_, row) => + }, +#else + { title: '#comment($column)', dataIndex: '${javaField}'#if(!$column.query), search: false#end }, +#end +#end +#end + { + title: '操作', + valueType: 'option', + width: 130, + fixed: 'right', + render: (_, row) => ( + , onClick: () => openEdit(row) }, + canAdd && { key: 'add', label: '新增', icon: , onClick: () => openAdd(row) }, + canRemove && { + key: 'delete', + label: '删除', + icon: , + danger: true, + confirm: `是否确认删除${functionName}编号为"#[[${row.]]#${pkColumn.javaField}#[[}]]#"的数据项?`, + onClick: () => remove(row) + } + ]} + /> + ) + } + ]; + + return ( + + + 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 && ( + + ), + #if($enableExport), + canExport && ( + + )#end + ]} + /> + + + 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} + > + + ); +} diff --git a/ruoyi-modules/ruoyi-gen/src/main/resources/vm/react/index.tsx.vm b/ruoyi-modules/ruoyi-gen/src/main/resources/vm/react/index.tsx.vm new file mode 100644 index 000000000..eddfbfe78 --- /dev/null +++ b/ruoyi-modules/ruoyi-gen/src/main/resources/vm/react/index.tsx.vm @@ -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(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) => ( + handleStatusChange(row, checked)} + /> + ) + }, +#elseif($enableSort && $sortField == $javaField) + { + title: '#comment($column)', + dataIndex: '${javaField}', + search: false, + width: 130, + render: (_, row) => 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) => + }, +#elseif($column.dictColumn) + { + title: '#comment($column)', + dataIndex: '${javaField}', + valueType: 'select', + fieldProps: { options: dictOptions(dicts.${dictType}) }, + render: (_, row) => + }, +#elseif($column.htmlType == "switch") + { + title: '#comment($column)', + dataIndex: '${javaField}', + valueType: 'select', + width: 100, + render: (_, row) => + }, +#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) => ( + , onClick: () => openEdit(row) }, + canRemove && { + key: 'delete', + label: '删除', + icon: , + danger: true, + confirm: `是否确认删除${functionName}编号为"#[[${row.]]#${pkColumn.javaField}#[[}]]#"的数据项?`, + onClick: () => remove(row) + } + ]} + /> + ) + } + ]; + + return ( + + #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 && ( + + ), + canEdit && ( + + ), + canRemove && ( + remove()}> + + + )#if($enableExport), + canExport && ( + + )#end + ]} + /> + + + 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} + > + + ); +} diff --git a/ruoyi-modules/ruoyi-gen/src/main/resources/vm/react/types.ts.vm b/ruoyi-modules/ruoyi-gen/src/main/resources/vm/react/types.ts.vm new file mode 100644 index 000000000..39b7df2a1 --- /dev/null +++ b/ruoyi-modules/ruoyi-gen/src/main/resources/vm/react/types.ts.vm @@ -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; +} diff --git a/script/sql/oracle/oracle_ry_vue.sql b/script/sql/oracle/oracle_ry_vue.sql index 4306c2a24..5553fdfb3 100644 --- a/script/sql/oracle/oracle_ry_vue.sql +++ b/script/sql/oracle/oracle_ry_vue.sql @@ -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 '生成业务名'; diff --git a/script/sql/postgres/postgres_ry_vue.sql b/script/sql/postgres/postgres_ry_vue.sql index 9a323f92f..f5c2267b1 100644 --- a/script/sql/postgres/postgres_ry_vue.sql +++ b/script/sql/postgres/postgres_ry_vue.sql @@ -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 '生成业务名'; diff --git a/script/sql/ry_vue.sql b/script/sql/ry_vue.sql index 785ecccb5..492190f8c 100644 --- a/script/sql/ry_vue.sql +++ b/script/sql/ry_vue.sql @@ -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 '生成业务名', diff --git a/script/sql/sqlserver/sqlserver_ry_vue.sql b/script/sql/sqlserver/sqlserver_ry_vue.sql index 6ea65753f..3cc43c2e7 100644 --- a/script/sql/sqlserver/sqlserver_ry_vue.sql +++ b/script/sql/sqlserver/sqlserver_ry_vue.sql @@ -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',