| rsf-framework/src/main/java/com/vincent/rsf/framework/generators/RsfDesignGenerator.java | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| rsf-framework/src/main/resources/templates/rsf-design/Api.txt | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| rsf-framework/src/main/resources/templates/rsf-design/Controller.txt | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| rsf-framework/src/main/resources/templates/rsf-design/EditDialog.txt | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| rsf-framework/src/main/resources/templates/rsf-design/Index.txt | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| rsf-framework/src/main/resources/templates/rsf-design/PageHelpers.txt | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| rsf-framework/src/main/resources/templates/rsf-design/Search.txt | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| rsf-framework/src/main/resources/templates/rsf-design/TableColumns.txt | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| rsf-server/src/main/java/com/vincent/rsf/server/common/CodeBuilder.java | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 |
rsf-framework/src/main/java/com/vincent/rsf/framework/generators/RsfDesignGenerator.java
New file @@ -0,0 +1,946 @@ package com.vincent.rsf.framework.generators; import com.vincent.rsf.framework.common.Cools; import com.vincent.rsf.framework.generators.constant.SqlOsType; import com.vincent.rsf.framework.generators.domain.Column; import com.vincent.rsf.framework.generators.utils.GeneratorUtils; import org.springframework.core.io.ClassPathResource; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.nio.charset.StandardCharsets; import java.sql.Connection; import java.sql.DriverManager; import java.util.ArrayList; import java.util.Arrays; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Objects; public class RsfDesignGenerator { private static final String BASE_DIR = "src/main/"; private static final String JAVA_DIR = BASE_DIR + "java/"; private static final String MODULES_DIR = "modules/"; private static final List<String> MANAGED_FIELDS = Arrays.asList( "deleted", "tenantId", "createBy", "createTime", "updateBy", "updateTime", "version" ); private static final List<String> SEARCH_EXCLUDED_FIELDS = Arrays.asList( "deleted", "tenantId", "createBy", "createTime", "updateBy", "updateTime", "version" ); public String url; public String username; public String password; public String table; public String tableDesc; public String packagePath; public boolean controller = true; public boolean service = true; public boolean mapper = true; public boolean entity = true; public boolean xml = true; public boolean frontend = true; public boolean sql = true; public SqlOsType sqlOsType; public String backendPrefixPath; public String frontendPrefixPath; public String frontendViewPath; public String frontendApiModule; private List<Column> columns = new ArrayList<>(); private String fullEntityName; private String simpleEntityName; private String kebabEntityName; private String constantPrefix; private String primaryKeyColumn; private String majorColumn; private String itemName; private String normalizedFrontendViewPath; private String normalizedFrontendApiModule; public void build() throws Exception { init(); buildBackendArtifacts(); if (controller) { writeTemplate("Controller", resolveControllerDirectory(), fullEntityName + "Controller.java"); } if (frontend) { buildFrontendArtifacts(); } } private void init() throws Exception { validateBaseConfig(); gainDbInfo(); fullEntityName = GeneratorUtils.getNameSpace(table); simpleEntityName = GeneratorUtils.firstCharConvert(fullEntityName, true); kebabEntityName = humpToKebab(simpleEntityName); constantPrefix = GeneratorUtils.humpToLine(simpleEntityName).toUpperCase(); primaryKeyColumn = resolvePrimaryKeyColumn(); majorColumn = resolveMajorColumn(); String[] packagePathSplit = packagePath.split("\\."); itemName = packagePathSplit[packagePathSplit.length - 1]; normalizedFrontendViewPath = normalizePath(frontendViewPath); normalizedFrontendApiModule = normalizeApiModule(frontendApiModule); } private void validateBaseConfig() { if (this.sqlOsType == null) { throw new RuntimeException("请选择sqlOsType!"); } if (Cools.isEmpty(this.table)) { throw new RuntimeException("请输入table!"); } if (Cools.isEmpty(this.tableDesc)) { throw new RuntimeException("请输入tableDesc!"); } if (Cools.isEmpty(this.packagePath)) { throw new RuntimeException("请输入packagePath!"); } if (frontend) { if (Cools.isEmpty(frontendPrefixPath)) { throw new RuntimeException("请输入frontendPrefixPath!"); } if (Cools.isEmpty(frontendViewPath)) { throw new RuntimeException("请输入frontendViewPath!"); } if (Cools.isEmpty(frontendApiModule)) { throw new RuntimeException("请输入frontendApiModule!"); } } } private void gainDbInfo() throws Exception { Connection connection = null; try { switch (this.sqlOsType) { case MYSQL: Class.forName("com.mysql.cj.jdbc.Driver").newInstance(); connection = DriverManager.getConnection("jdbc:mysql://" + url, username, password); this.columns = ReactGenerator.getMysqlColumns(connection, table, true, sqlOsType); break; case SQL_SERVER: Class.forName("com.microsoft.sqlserver.jdbc.SQLServerDriver").newInstance(); connection = DriverManager.getConnection("jdbc:sqlserver://" + url, username, password); this.columns = ReactGenerator.getSqlServerColumns(connection, table, true, sqlOsType); break; default: throw new RuntimeException("请指定数据库类型!"); } } finally { if (connection != null) { try { connection.close(); } catch (Exception ignore) { } } } } private void buildBackendArtifacts() throws Exception { ReactGenerator generator = new ReactGenerator(); generator.url = url; generator.username = username; generator.password = password; generator.table = table; generator.tableDesc = tableDesc; generator.packagePath = packagePath; generator.controller = false; generator.service = service; generator.mapper = mapper; generator.entity = entity; generator.xml = xml; generator.react = false; generator.sql = sql; generator.sqlOsType = sqlOsType; generator.backendPrefixPath = backendPrefixPath; generator.frontendPrefixPath = frontendPrefixPath; generator.build(); } private void buildFrontendArtifacts() throws IOException { String pageDirectory = resolveFrontendPageDirectory(); String modulesDirectory = pageDirectory + MODULES_DIR; writeTemplate("Index", pageDirectory, "index.vue"); writeTemplate("PageHelpers", pageDirectory, simpleEntityName + "Page.helpers.js"); writeTemplate("TableColumns", pageDirectory, simpleEntityName + "Table.columns.js"); writeTemplate("Search", modulesDirectory, kebabEntityName + "-search.vue"); writeTemplate("EditDialog", modulesDirectory, kebabEntityName + "-edit-dialog.vue"); writeTemplate("Api", resolveFrontendApiDirectory(), normalizedFrontendApiModule + ".js"); } private String resolveControllerDirectory() { return ensureTrailingSlash(backendPrefixPath) + JAVA_DIR + packagePath.replace(".", "/") + "/controller/"; } private String resolveFrontendPageDirectory() { return ensureTrailingSlash(frontendPrefixPath) + "src/views/" + normalizedFrontendViewPath + "/"; } private String resolveFrontendApiDirectory() { String directory = ensureTrailingSlash(frontendPrefixPath) + "src/api/"; int index = normalizedFrontendApiModule.lastIndexOf('/'); if (index < 0) { return directory; } return directory + normalizedFrontendApiModule.substring(0, index + 1); } private void writeTemplate(String templateName, String directory, String fileName) throws IOException { String content = readTemplate(templateName); writeFile(applyReplacements(content), directory, fileName, templateName); } private String readTemplate(String templateName) throws IOException { StringBuilder builder = new StringBuilder(); ClassPathResource classPath = new ClassPathResource("templates/rsf-design/" + templateName + ".txt"); try (BufferedReader reader = new BufferedReader( new InputStreamReader(classPath.getInputStream(), StandardCharsets.UTF_8))) { String lineContent; while ((lineContent = reader.readLine()) != null) { builder.append(lineContent).append("\n"); } } return builder.toString(); } private String applyReplacements(String content) { Map<String, String> replacements = buildReplacements(); String resolved = content; for (Map.Entry<String, String> entry : replacements.entrySet()) { resolved = resolved.replace("@{" + entry.getKey() + "}", Objects.toString(entry.getValue(), "")); } return resolved; } private Map<String, String> buildReplacements() { Map<String, String> replacements = new LinkedHashMap<>(); replacements.put("TABLENAME", table); replacements.put("TABLEDESC", safeText(tableDesc)); replacements.put("ENTITYNAME", fullEntityName); replacements.put("SIMPLEENTITYNAME", simpleEntityName); replacements.put("UENTITYNAME", GeneratorUtils.firstCharConvert(simpleEntityName, false)); replacements.put("KEBABENTITYNAME", kebabEntityName); replacements.put("COMPANYNAME", packagePath); replacements.put("ITEMNAME", itemName); replacements.put("PRIMARYKEYCOLUMN", GeneratorUtils.firstCharConvert(primaryKeyColumn, false)); replacements.put("PRIMARYKEYCOLUMN0", GeneratorUtils.firstCharConvert(primaryKeyColumn, true)); replacements.put("MAJORCOLUMN", GeneratorUtils.firstCharConvert(majorColumn, false)); replacements.put("MAJORCOLUMN0", GeneratorUtils.firstCharConvert(majorColumn, true)); replacements.put("ENTITYPREFIX", constantPrefix); replacements.put("APIMODULE", normalizedFrontendApiModule); replacements.put("SEARCHSTATECONTENT", buildSearchStateContent()); replacements.put("FORMSTATECONTENT", buildFormStateContent()); replacements.put("FIELDOPTIONSCONTENT", buildFieldOptionsContent()); replacements.put("SEARCHPARAMSCONTENT", buildSearchParamsContent()); replacements.put("SAVEPAYLOADCONTENT", buildSavePayloadContent()); replacements.put("DIALOGMODELCONTENT", buildDialogModelContent()); replacements.put("NORMALIZEROWCONTENT", buildNormalizeRowContent()); replacements.put("REPORTCOLUMNSCONTENT", buildReportColumnsContent()); replacements.put("REPORTSOURCEALIASCONTENT", buildReportSourceAliasContent()); replacements.put("SEARCHITEMSCONTENT", buildSearchItemsContent()); replacements.put("FORMITEMSCONTENT", buildFormItemsContent()); replacements.put("RULESCONTENT", buildRulesContent()); replacements.put("TABLECOLUMNSCONTENT", buildTableColumnsContent()); replacements.put("EXPORTROWCONTENT", buildExportRowContent()); replacements.put("SAVEINITCONTENT", buildSaveInitContent()); replacements.put("UPDATEINITCONTENT", buildUpdateInitContent()); return replacements; } private void writeFile(String content, String directory, String fileName, String templateName) throws IOException { File codeDirectory = new File(directory); if (!codeDirectory.exists()) { codeDirectory.mkdirs(); } File writerFile = new File(directory + fileName); if (writerFile.exists()) { System.out.println(fullEntityName + templateName + " 源文件已经存在创建失败!"); return; } writerFile.createNewFile(); try (BufferedWriter writer = new BufferedWriter( new OutputStreamWriter(new FileOutputStream(writerFile), StandardCharsets.UTF_8))) { writer.write(content); writer.flush(); } System.out.println(fullEntityName + templateName + " 源文件创建成功!"); } private String resolvePrimaryKeyColumn() { for (Column column : columns) { if (column.isPrimaryKey() || column.isMainKey()) { return column.getHumpName(); } } return "id"; } private String resolveMajorColumn() { for (Column column : columns) { if (column.isMajor()) { return column.getHumpName(); } } for (String preferred : Arrays.asList("name", "code", "title", "uuid")) { Column column = findColumn(preferred); if (column != null) { return column.getHumpName(); } } for (Column column : columns) { if (!MANAGED_FIELDS.contains(column.getHumpName()) && !column.isPrimaryKey()) { return column.getHumpName(); } } return primaryKeyColumn; } private Column findColumn(String humpName) { for (Column column : columns) { if (humpName.equals(column.getHumpName())) { return column; } } return null; } private boolean hasColumn(String humpName) { return findColumn(humpName) != null; } private boolean isManagedColumn(Column column) { return MANAGED_FIELDS.contains(column.getHumpName()); } private boolean isSearchExcludedColumn(Column column) { return SEARCH_EXCLUDED_FIELDS.contains(column.getHumpName()); } private boolean isBooleanColumn(Column column) { return "Boolean".equals(column.getType()); } private boolean isNumericColumn(Column column) { return "Short".equals(column.getType()) || "Integer".equals(column.getType()) || "Long".equals(column.getType()) || "Double".equals(column.getType()); } private boolean isDateColumn(Column column) { return "Date".equals(column.getType()); } private boolean hasEnumOptions(Column column) { return !Cools.isEmpty(column.getEnums()); } private boolean hasForeignDisplay(Column column) { return !Cools.isEmpty(column.getForeignKeyMajor()); } private boolean isStatusColumn(Column column) { return "status".equals(column.getHumpName()); } private boolean isMemoColumn(Column column) { return "memo".equals(column.getHumpName()); } private boolean isSelectableColumn(Column column) { return isBooleanColumn(column) || hasEnumOptions(column); } private boolean isDisplayTextColumn(Column column) { return isBooleanColumn(column) || isDateColumn(column) || hasEnumOptions(column) || hasForeignDisplay(column); } private List<Column> getSearchColumns() { List<Column> result = new ArrayList<>(); for (Column column : columns) { if (column.isPrimaryKey() || isSearchExcludedColumn(column) || isDateColumn(column)) { continue; } result.add(column); } return result; } private List<Column> getFormColumns() { List<Column> result = new ArrayList<>(); for (Column column : columns) { if (column.isPrimaryKey() || isManagedColumn(column)) { continue; } result.add(column); } return result; } private List<Column> getListColumns() { List<Column> result = new ArrayList<>(); for (Column column : columns) { if (column.isPrimaryKey()) { continue; } if ("deleted".equals(column.getHumpName()) || "tenantId".equals(column.getHumpName())) { continue; } result.add(column); } return result; } private List<Column> getReportColumns() { return getListColumns(); } private List<Column> getPayloadColumns() { List<Column> result = new ArrayList<>(); Column primaryColumn = findColumn(primaryKeyColumn); if (primaryColumn != null) { result.add(primaryColumn); } Column versionColumn = findColumn("version"); if (versionColumn != null) { result.add(versionColumn); } result.addAll(getFormColumns()); return result; } private String buildSearchStateContent() { StringBuilder sb = new StringBuilder(); for (Column column : getSearchColumns()) { sb.append(" ").append(column.getHumpName()).append(": '',\n"); } return trimTrailingLineBreak(sb); } private String buildFormStateContent() { StringBuilder sb = new StringBuilder(); for (Column column : getPayloadColumns()) { sb.append(" ") .append(column.getHumpName()) .append(": ") .append(resolveFormDefaultValue(column)) .append(",\n"); } return trimTrailingLineBreak(sb); } private String buildFieldOptionsContent() { StringBuilder sb = new StringBuilder(); sb.append("{\n"); boolean hasOptions = false; for (Column column : columns) { if (!isSelectableColumn(column)) { continue; } hasOptions = true; sb.append(" ").append(column.getHumpName()).append(": [\n"); for (Map<String, Object> option : buildColumnOptions(column)) { sb.append(" { label: '") .append(escapeJs(String.valueOf(option.get("label")))) .append("', value: ") .append(option.get("value")) .append(" },\n"); } sb.append(" ],\n"); } if (!hasOptions) { return "{}"; } sb.append("}"); return sb.toString(); } private String buildSearchParamsContent() { StringBuilder sb = new StringBuilder(); for (Column column : getSearchColumns()) { sb.append(" ") .append(column.getHumpName()) .append(": ") .append(resolveParamNormalizer("params", column)) .append(",\n"); } return trimTrailingLineBreak(sb); } private String buildSavePayloadContent() { StringBuilder sb = new StringBuilder(); for (Column column : getPayloadColumns()) { sb.append(resolvePayloadLine("formData", column)); } return trimTrailingLineBreak(sb); } private String buildDialogModelContent() { StringBuilder sb = new StringBuilder(); for (Column column : getPayloadColumns()) { sb.append(" ") .append(column.getHumpName()) .append(": ") .append(resolveDialogValue(column)) .append(",\n"); } return trimTrailingLineBreak(sb); } private String buildNormalizeRowContent() { StringBuilder sb = new StringBuilder(); Column statusColumn = findColumn("status"); if (statusColumn != null) { sb.append(" statusBool: record.statusBool !== void 0 ? Boolean(record.statusBool) : statusMeta.bool,\n") .append(" statusText: normalizeText(record.statusText || record.status$ || statusMeta.text),\n") .append(" statusType: statusMeta.type,\n"); } for (Column column : getReportColumns()) { if (isStatusColumn(column)) { continue; } if (isBooleanColumn(column)) { sb.append(" ") .append(column.getHumpName()) .append("Text: formatBooleanText(toOptionalBoolean(record.") .append(column.getHumpName()) .append(")),\n"); continue; } if (isDisplayTextColumn(column)) { sb.append(" ") .append(column.getHumpName()) .append("Text: normalizeText(record.") .append(column.getHumpName()) .append("$ || record.") .append(column.getHumpName()) .append("Text || record.") .append(column.getHumpName()) .append("),\n"); } } return trimTrailingLineBreak(sb); } private String buildReportColumnsContent() { StringBuilder sb = new StringBuilder(); for (Column column : getReportColumns()) { sb.append(" { source: '") .append(resolveReportSource(column)) .append("', label: '") .append(escapeJs(resolveFieldLabel(column))) .append("' },\n"); } return trimTrailingLineBreak(sb); } private String buildReportSourceAliasContent() { Column statusColumn = findColumn("status"); if (statusColumn == null) { return ""; } return " status: 'statusText'"; } private String buildSearchItemsContent() { StringBuilder sb = new StringBuilder(); for (Column column : getSearchColumns()) { if (isSelectableColumn(column)) { sb.append(" createSelectSearchItem('") .append(escapeJs(resolveFieldLabel(column))) .append("', '") .append(column.getHumpName()) .append("', '请选择") .append(escapeJs(resolveFieldLabel(column))) .append("', get") .append(fullEntityName) .append("FieldOptions('") .append(column.getHumpName()) .append("')),\n"); } else { sb.append(" createInputSearchItem('") .append(escapeJs(resolveFieldLabel(column))) .append("', '") .append(column.getHumpName()) .append("', '请输入") .append(escapeJs(resolveFieldLabel(column))) .append("'),\n"); } } return trimTrailingLineBreak(sb); } private String buildFormItemsContent() { StringBuilder sb = new StringBuilder(); for (Column column : getFormColumns()) { String label = escapeJs(resolveFieldLabel(column)); String key = column.getHumpName(); if (isSelectableColumn(column)) { sb.append(" createSelectFormItem('") .append(label) .append("', '") .append(key) .append("', '请选择") .append(label) .append("', get") .append(fullEntityName) .append("FieldOptions('") .append(key) .append("')),\n"); continue; } if (isMemoColumn(column) || (column.getLength() != null && column.getLength() > 255)) { sb.append(" createInputFormItem('") .append(label) .append("', '") .append(key) .append("', '请输入") .append(label) .append("', { type: 'textarea', rows: 3 }, { span: 24 }),\n"); continue; } if (isNumericColumn(column)) { sb.append(" createInputFormItem('") .append(label) .append("', '") .append(key) .append("', '请输入") .append(label) .append("', { type: 'number' }),\n"); continue; } sb.append(" createInputFormItem('") .append(label) .append("', '") .append(key) .append("', '请输入") .append(label) .append("'),\n"); } return trimTrailingLineBreak(sb); } private String buildRulesContent() { StringBuilder sb = new StringBuilder(); for (Column column : getFormColumns()) { if (!column.isNotNull()) { continue; } String label = escapeJs(resolveFieldLabel(column)); String message = (isSelectableColumn(column) ? "请选择" : "请输入") + label; String trigger = isSelectableColumn(column) ? "change" : "blur"; sb.append(" ") .append(column.getHumpName()) .append(": [{ required: true, message: '") .append(message) .append("', trigger: '") .append(trigger) .append("' }],\n"); } return trimTrailingLineBreak(sb); } private String buildTableColumnsContent() { StringBuilder sb = new StringBuilder(); for (Column column : getListColumns()) { if (isStatusColumn(column)) { sb.append(" createTagColumn('status', '") .append(escapeJs(resolveFieldLabel(column))) .append("', 120, (row) => get") .append(fullEntityName) .append("StatusMeta(row.statusBool ?? row.status)),\n"); continue; } if (isNumericColumn(column)) { sb.append(" createNumberColumn('") .append(column.getHumpName()) .append("', '") .append(escapeJs(resolveFieldLabel(column))) .append("', 120),\n"); continue; } if (isDisplayTextColumn(column)) { sb.append(" createTextColumn('") .append(column.getHumpName()) .append("Text', '") .append(escapeJs(resolveFieldLabel(column))) .append("', ") .append(resolveTextColumnWidth(column)) .append("),\n"); continue; } sb.append(" createTextColumn('") .append(column.getHumpName()) .append("', '") .append(escapeJs(resolveFieldLabel(column))) .append("', ") .append(resolveTextColumnWidth(column)) .append("),\n"); } return trimTrailingLineBreak(sb); } private String buildExportRowContent() { StringBuilder sb = new StringBuilder(); for (Column column : getReportColumns()) { sb.append(" row.put(\"") .append(resolveReportSource(column)) .append("\", ") .append(resolveExportExpression(column)) .append(");\n"); } return trimTrailingLineBreak(sb); } private String buildSaveInitContent() { StringBuilder sb = new StringBuilder(); if (hasColumn("createBy")) { sb.append(" ").append(simpleEntityName).append(".setCreateBy(getLoginUserId());\n"); } if (hasColumn("createTime")) { sb.append(" ").append(simpleEntityName).append(".setCreateTime(new Date());\n"); } if (hasColumn("updateBy")) { sb.append(" ").append(simpleEntityName).append(".setUpdateBy(getLoginUserId());\n"); } if (hasColumn("updateTime")) { sb.append(" ").append(simpleEntityName).append(".setUpdateTime(new Date());\n"); } return trimTrailingLineBreak(sb); } private String buildUpdateInitContent() { StringBuilder sb = new StringBuilder(); if (hasColumn("updateBy")) { sb.append(" ").append(simpleEntityName).append(".setUpdateBy(getLoginUserId());\n"); } if (hasColumn("updateTime")) { sb.append(" ").append(simpleEntityName).append(".setUpdateTime(new Date());\n"); } return trimTrailingLineBreak(sb); } private List<Map<String, Object>> buildColumnOptions(Column column) { List<Map<String, Object>> options = new ArrayList<>(); if (isBooleanColumn(column) && Cools.isEmpty(column.getEnums())) { options.add(buildOption("是", "true")); options.add(buildOption("否", "false")); return options; } if (Cools.isEmpty(column.getEnums())) { return options; } for (Map<String, Object> item : column.getEnums()) { for (Map.Entry<String, Object> entry : item.entrySet()) { options.add(buildOption(String.valueOf(entry.getValue()), toJsValue(column, String.valueOf(entry.getKey())))); } } return options; } private Map<String, Object> buildOption(String label, String jsValue) { Map<String, Object> option = new LinkedHashMap<>(); option.put("label", label); option.put("value", jsValue); return option; } private String resolveFormDefaultValue(Column column) { if (column == null) { return "void 0"; } if (isStatusColumn(column) && isSelectableColumn(column)) { List<Map<String, Object>> options = buildColumnOptions(column); if (!options.isEmpty()) { return String.valueOf(options.get(0).get("value")); } } if (isBooleanColumn(column) || isNumericColumn(column)) { return "void 0"; } return "''"; } private String resolveParamNormalizer(String sourceName, Column column) { String field = sourceName + "." + column.getHumpName(); if (isBooleanColumn(column)) { return "toOptionalBoolean(" + field + ")"; } if (isNumericColumn(column)) { return "toOptionalNumber(" + field + ")"; } return "normalizeText(" + field + ")"; } private String resolvePayloadLine(String sourceName, Column column) { String field = sourceName + "." + column.getHumpName(); if (isBooleanColumn(column)) { return " ...(hasValue(" + field + ") ? { " + column.getHumpName() + ": toOptionalBoolean(" + field + ") } : {}),\n"; } if (isNumericColumn(column)) { return " ...buildNumberField('" + column.getHumpName() + "', " + field + "),\n"; } return " " + column.getHumpName() + ": normalizeText(" + field + ") || '',\n"; } private String resolveDialogValue(Column column) { String field = "record." + column.getHumpName(); if (isStatusColumn(column) && isSelectableColumn(column)) { List<Map<String, Object>> options = buildColumnOptions(column); String fallback = options.isEmpty() ? "void 0" : String.valueOf(options.get(0).get("value")); if (isBooleanColumn(column)) { return "hasValue(" + field + ") ? toOptionalBoolean(" + field + ") : " + fallback; } if (isNumericColumn(column)) { return "hasValue(" + field + ") ? toOptionalNumber(" + field + ") : " + fallback; } return "normalizeText(" + field + ") || " + fallback; } if (isBooleanColumn(column)) { return "toOptionalBoolean(" + field + ")"; } if (isNumericColumn(column)) { return "toOptionalNumber(" + field + ")"; } return "normalizeText(" + field + " || '')"; } private String resolveFieldLabel(Column column) { if (column == null || Cools.isEmpty(column.getComment())) { return column == null ? "" : column.getHumpName(); } return column.getComment().trim(); } private int resolveTextColumnWidth(Column column) { if (isMemoColumn(column)) { return 220; } if (isDateColumn(column)) { return 180; } return 160; } private String resolveReportSource(Column column) { if (isStatusColumn(column) || isDisplayTextColumn(column)) { return column.getHumpName() + "Text"; } return column.getHumpName(); } private String resolveExportExpression(Column column) { String getter = "record." + getterName(column); if (isBooleanColumn(column)) { return "Boolean.TRUE.equals(" + getter + "()) ? \"是\" : Boolean.FALSE.equals(" + getter + "()) ? \"否\" : \"\""; } if (isStatusColumn(column) || isDisplayTextColumn(column)) { return getter + "$()"; } return getter + "()"; } private String getterName(Column column) { return "get" + GeneratorUtils.firstCharConvert(column.getHumpName(), false); } private String toJsValue(Column column, String rawValue) { if (isBooleanColumn(column)) { if ("1".equals(rawValue) || "true".equalsIgnoreCase(rawValue)) { return "true"; } if ("0".equals(rawValue) || "false".equalsIgnoreCase(rawValue)) { return "false"; } } if (isNumericColumn(column)) { return rawValue; } return "'" + escapeJs(rawValue) + "'"; } private String trimTrailingLineBreak(StringBuilder sb) { if (sb.length() == 0) { return ""; } while (sb.length() > 0 && (sb.charAt(sb.length() - 1) == '\n' || sb.charAt(sb.length() - 1) == '\r')) { sb.deleteCharAt(sb.length() - 1); } if (sb.length() > 0 && sb.charAt(sb.length() - 1) == ',') { sb.deleteCharAt(sb.length() - 1); } return sb.toString(); } private String safeText(String value) { return value == null ? "" : value.trim(); } private String escapeJs(String value) { return value .replace("\\", "\\\\") .replace("'", "\\'"); } private String humpToKebab(String value) { if (Cools.isEmpty(value)) { return ""; } return value.replaceAll("([a-z0-9])([A-Z])", "$1-$2").toLowerCase(); } private String ensureTrailingSlash(String prefix) { if (Cools.isEmpty(prefix)) { return ""; } return prefix.endsWith("/") ? prefix : prefix + "/"; } private String normalizePath(String value) { if (Cools.isEmpty(value)) { return ""; } String normalized = value.replace("\\", "/"); while (normalized.startsWith("/")) { normalized = normalized.substring(1); } while (normalized.endsWith("/")) { normalized = normalized.substring(0, normalized.length() - 1); } return normalized; } private String normalizeApiModule(String value) { String normalized = normalizePath(value); if (normalized.endsWith(".js")) { return normalized.substring(0, normalized.length() - 3); } return normalized; } } rsf-framework/src/main/resources/templates/rsf-design/Api.txt
New file @@ -0,0 +1,84 @@ import request from '@/utils/http' function normalizeIds(ids) { if (Array.isArray(ids)) { return ids .map((id) => String(id).trim()) .filter(Boolean) .join(',') } if (ids === null || ids === undefined) { return '' } return String(ids).trim() } function normalizeText(value) { return typeof value === 'string' ? value.trim() : value } export function fetch@{ENTITYNAME}Page(params = {}) { return request.post({ url: '/@{SIMPLEENTITYNAME}/page', params }) } export function fetch@{ENTITYNAME}List(params = {}) { return request.post({ url: '/@{SIMPLEENTITYNAME}/list', params }) } export function fetchGet@{ENTITYNAME}Detail(id) { return request.get({ url: `/@{SIMPLEENTITYNAME}/${id}` }) } export function fetchGet@{ENTITYNAME}Many(ids) { return request.post({ url: `/@{SIMPLEENTITYNAME}/many/${normalizeIds(ids)}` }) } export function fetchSave@{ENTITYNAME}(params = {}) { return request.post({ url: '/@{SIMPLEENTITYNAME}/save', params }) } export function fetchUpdate@{ENTITYNAME}(params = {}) { return request.post({ url: '/@{SIMPLEENTITYNAME}/update', params }) } export function fetchDelete@{ENTITYNAME}(ids) { return request.post({ url: `/@{SIMPLEENTITYNAME}/remove/${normalizeIds(ids)}` }) } export function fetch@{ENTITYNAME}Query(condition = '') { return request.post({ url: '/@{SIMPLEENTITYNAME}/query', params: { condition: normalizeText(condition) } }) } export async function fetchExport@{ENTITYNAME}Report(payload = {}, options = {}) { return fetch(`${import.meta.env.VITE_API_URL}/@{SIMPLEENTITYNAME}/export`, { method: 'POST', headers: { 'Content-Type': 'application/json', ...(options.headers || {}) }, body: JSON.stringify(payload) }) } rsf-framework/src/main/resources/templates/rsf-design/Controller.txt
New file @@ -0,0 +1,140 @@ package @{COMPANYNAME}.controller; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.vincent.rsf.framework.common.Cools; import com.vincent.rsf.framework.common.R; import com.vincent.rsf.server.common.annotation.OperationLog; import com.vincent.rsf.server.common.domain.BaseParam; import com.vincent.rsf.server.common.domain.KeyValVo; import com.vincent.rsf.server.common.domain.PageParam; import com.vincent.rsf.server.common.service.ListExportHandler; import com.vincent.rsf.server.common.service.ListExportService; import com.vincent.rsf.server.common.utils.ExcelUtil; import @{COMPANYNAME}.entity.@{ENTITYNAME}; import @{COMPANYNAME}.service.@{ENTITYNAME}Service; import com.vincent.rsf.server.system.controller.BaseController; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.*; import jakarta.servlet.http.HttpServletResponse; import java.util.ArrayList; import java.util.Arrays; import java.util.Date; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @RestController public class @{ENTITYNAME}Controller extends BaseController { @Autowired private @{ENTITYNAME}Service @{SIMPLEENTITYNAME}Service; @Autowired private ListExportService listExportService; private final ListExportHandler<@{ENTITYNAME}, BaseParam> @{SIMPLEENTITYNAME}ExportHandler = new ListExportHandler<@{ENTITYNAME}, BaseParam>() { @Override public List<@{ENTITYNAME}> listByIds(List<Long> ids) { return @{SIMPLEENTITYNAME}Service.listByIds(ids); } @Override public List<@{ENTITYNAME}> listByFilter(Map<String, Object> sanitizedMap, BaseParam baseParam) { PageParam<@{ENTITYNAME}, BaseParam> pageParam = new PageParam<>(baseParam, @{ENTITYNAME}.class); return @{SIMPLEENTITYNAME}Service.list(pageParam.buildWrapper(true)); } @Override public Map<String, Object> toExportRow(@{ENTITYNAME} record, List<ExcelUtil.ExportColumn> columns) { Map<String, Object> row = new LinkedHashMap<>(); @{EXPORTROWCONTENT} return row; } @Override public String defaultReportTitle() { return "@{TABLEDESC}报表"; } }; @PreAuthorize("hasAuthority('@{ITEMNAME}:@{SIMPLEENTITYNAME}:list')") @PostMapping("/@{SIMPLEENTITYNAME}/page") public R page(@RequestBody Map<String, Object> map) { BaseParam baseParam = buildParam(map, BaseParam.class); PageParam<@{ENTITYNAME}, BaseParam> pageParam = new PageParam<>(baseParam, @{ENTITYNAME}.class); return R.ok().add(@{SIMPLEENTITYNAME}Service.page(pageParam, pageParam.buildWrapper(true))); } @PreAuthorize("hasAuthority('@{ITEMNAME}:@{SIMPLEENTITYNAME}:list')") @PostMapping("/@{SIMPLEENTITYNAME}/list") public R list(@RequestBody Map<String, Object> map) { return R.ok().add(@{SIMPLEENTITYNAME}Service.list()); } @PreAuthorize("hasAuthority('@{ITEMNAME}:@{SIMPLEENTITYNAME}:list')") @PostMapping({"/@{SIMPLEENTITYNAME}/many/{ids}", "/@{SIMPLEENTITYNAME}s/many/{ids}"}) public R many(@PathVariable Long[] ids) { return R.ok().add(@{SIMPLEENTITYNAME}Service.listByIds(Arrays.asList(ids))); } @PreAuthorize("hasAuthority('@{ITEMNAME}:@{SIMPLEENTITYNAME}:list')") @GetMapping("/@{SIMPLEENTITYNAME}/{id}") public R get(@PathVariable("id") Long id) { return R.ok().add(@{SIMPLEENTITYNAME}Service.getById(id)); } @PreAuthorize("hasAuthority('@{ITEMNAME}:@{SIMPLEENTITYNAME}:save')") @OperationLog("Create @{TABLEDESC}") @PostMapping("/@{SIMPLEENTITYNAME}/save") public R save(@RequestBody @{ENTITYNAME} @{SIMPLEENTITYNAME}) { @{SAVEINITCONTENT} if (!@{SIMPLEENTITYNAME}Service.save(@{SIMPLEENTITYNAME})) { return R.error("Save Fail"); } return R.ok("Save Success").add(@{SIMPLEENTITYNAME}); } @PreAuthorize("hasAuthority('@{ITEMNAME}:@{SIMPLEENTITYNAME}:update')") @OperationLog("Update @{TABLEDESC}") @PostMapping("/@{SIMPLEENTITYNAME}/update") public R update(@RequestBody @{ENTITYNAME} @{SIMPLEENTITYNAME}) { @{UPDATEINITCONTENT} if (!@{SIMPLEENTITYNAME}Service.updateById(@{SIMPLEENTITYNAME})) { return R.error("Update Fail"); } return R.ok("Update Success").add(@{SIMPLEENTITYNAME}); } @PreAuthorize("hasAuthority('@{ITEMNAME}:@{SIMPLEENTITYNAME}:remove')") @OperationLog("Delete @{TABLEDESC}") @PostMapping("/@{SIMPLEENTITYNAME}/remove/{ids}") public R remove(@PathVariable Long[] ids) { if (!@{SIMPLEENTITYNAME}Service.removeByIds(Arrays.asList(ids))) { return R.error("Delete Fail"); } return R.ok("Delete Success").add(ids); } @PreAuthorize("hasAuthority('@{ITEMNAME}:@{SIMPLEENTITYNAME}:list')") @PostMapping("/@{SIMPLEENTITYNAME}/query") public R query(@RequestParam(required = false) String condition) { List<KeyValVo> vos = new ArrayList<>(); LambdaQueryWrapper<@{ENTITYNAME}> wrapper = new LambdaQueryWrapper<>(); if (!Cools.isEmpty(condition)) { wrapper.like(@{ENTITYNAME}::get@{MAJORCOLUMN}, condition); } @{SIMPLEENTITYNAME}Service.page(new Page<>(1, 30), wrapper).getRecords().forEach( item -> vos.add(new KeyValVo(item.get@{PRIMARYKEYCOLUMN}(), item.get@{MAJORCOLUMN}())) ); return R.ok().add(vos); } @PreAuthorize("hasAuthority('@{ITEMNAME}:@{SIMPLEENTITYNAME}:list')") @PostMapping("/@{SIMPLEENTITYNAME}/export") public void export(@RequestBody Map<String, Object> map, HttpServletResponse response) throws Exception { listExportService.export(map, exportMap -> buildParam(exportMap, BaseParam.class), @{SIMPLEENTITYNAME}ExportHandler, response); } } rsf-framework/src/main/resources/templates/rsf-design/EditDialog.txt
New file @@ -0,0 +1,136 @@ <template> <ElDialog :title="dialogTitle" :model-value="visible" width="820px" align-center @update:model-value="handleCancel" @closed="handleClosed" > <ArtForm ref="formRef" v-model="form" :items="formItems" :rules="rules" :span="12" :gutter="20" label-width="110px" :show-reset="false" :show-submit="false" /> <template #footer> <span class="dialog-footer"> <ElButton @click="handleCancel">取消</ElButton> <ElButton type="primary" @click="handleSubmit">确定</ElButton> </span> </template> </ElDialog> </template> <script setup> import ArtForm from '@/components/core/forms/art-form/index.vue' import { build@{ENTITYNAME}DialogModel, create@{ENTITYNAME}FormState, get@{ENTITYNAME}FieldOptions } from '../@{SIMPLEENTITYNAME}Page.helpers' const props = defineProps({ visible: { required: false, default: false }, dialogType: { required: false, default: 'add' }, record: { required: false, default: () => ({}) } }) const emit = defineEmits(['update:visible', 'submit']) const formRef = ref() const form = reactive(create@{ENTITYNAME}FormState()) const isEdit = computed(() => props.dialogType === 'edit') const dialogTitle = computed(() => (isEdit.value ? '编辑@{TABLEDESC}' : '新增@{TABLEDESC}')) const rules = computed(() => ({ @{RULESCONTENT} })) function createInputFormItem(label, key, placeholder, extraProps = {}, extraConfig = {}) { return { label, key, type: 'input', props: { placeholder, clearable: true, ...extraProps }, ...extraConfig } } function createSelectFormItem(label, key, placeholder, options, extraProps = {}, extraConfig = {}) { return { label, key, type: 'select', props: { placeholder, clearable: true, options, ...extraProps }, ...extraConfig } } const formItems = computed(() => [ @{FORMITEMSCONTENT} ]) function resetForm() { Object.assign(form, create@{ENTITYNAME}FormState()) formRef.value?.clearValidate?.() } function loadFormData() { Object.assign(form, build@{ENTITYNAME}DialogModel(props.record)) } async function handleSubmit() { if (!formRef.value) return try { await formRef.value.validate() emit('submit', { ...form }) } catch { return } } function handleCancel() { emit('update:visible', false) } function handleClosed() { resetForm() } watch( () => props.visible, (visible) => { if (visible) { loadFormData() nextTick(() => formRef.value?.clearValidate?.()) } }, { immediate: true } ) watch( () => props.record, () => { if (props.visible) { loadFormData() } }, { deep: true } ) </script> rsf-framework/src/main/resources/templates/rsf-design/Index.txt
New file @@ -0,0 +1,240 @@ <template> <div class="art-full-height"> <@{ENTITYNAME}Search v-show="showSearchBar" v-model="searchForm" @search="handleSearch" @reset="handleReset" /> <ElCard class="art-table-card" :style="{ 'margin-top': showSearchBar ? '12px' : '0' }"> <ArtTableHeader v-model:columns="columnChecks" v-model:showSearchBar="showSearchBar" :loading="loading" @refresh="refreshData" > <template #left> <ElSpace wrap> <ElButton v-auth="'add'" @click="showDialog('add')" v-ripple>新增@{TABLEDESC}</ElButton> <ElButton v-auth="'delete'" type="danger" :disabled="selectedRows.length === 0" @click="handleBatchDelete" v-ripple > 批量删除 </ElButton> <span v-auth="'query'" class="inline-flex"> <ListExportPrint :preview-visible="previewVisible" @update:previewVisible="handlePreviewVisibleChange" :report-title="reportTitle" :selected-rows="selectedRows" :query-params="reportQueryParams" :columns="reportColumns" :preview-rows="previewRows" :preview-meta="resolvedPreviewMeta" :total="pagination.total" :disabled="loading" @export="handleExport" @print="handlePrint" /> </span> </ElSpace> </template> </ArtTableHeader> <ArtTable :loading="loading" :data="data" :columns="columns" :pagination="pagination" @selection-change="handleSelectionChange" @pagination:size-change="handleSizeChange" @pagination:current-change="handleCurrentChange" /> </ElCard> <@{ENTITYNAME}EditDialog v-model:visible="dialogVisible" :dialog-type="dialogType" :record="currentRecordData" @submit="handleDialogSubmit" /> </div> </template> <script setup> import { useUserStore } from '@/store/modules/user' import { fetchDelete@{ENTITYNAME}, fetchExport@{ENTITYNAME}Report, fetchGet@{ENTITYNAME}Many, fetch@{ENTITYNAME}Page, fetchSave@{ENTITYNAME}, fetchUpdate@{ENTITYNAME} } from '@/api/@{APIMODULE}' import { useTable } from '@/hooks/core/useTable' import { useCrudPage } from '@/views/system/common/useCrudPage' import { usePrintExportPage } from '@/views/system/common/usePrintExportPage' import { defaultResponseAdapter } from '@/utils/table/tableUtils' import ListExportPrint from '@/components/biz/list-export-print/index.vue' import @{ENTITYNAME}Search from './modules/@{KEBABENTITYNAME}-search.vue' import @{ENTITYNAME}EditDialog from './modules/@{KEBABENTITYNAME}-edit-dialog.vue' import { create@{ENTITYNAME}TableColumns } from './@{SIMPLEENTITYNAME}Table.columns' import { build@{ENTITYNAME}DialogModel, build@{ENTITYNAME}PageQueryParams, build@{ENTITYNAME}PrintRows, build@{ENTITYNAME}ReportMeta, build@{ENTITYNAME}SavePayload, build@{ENTITYNAME}SearchParams, create@{ENTITYNAME}SearchState, get@{ENTITYNAME}PaginationKey, normalize@{ENTITYNAME}ListRow, @{ENTITYPREFIX}_REPORT_STYLE, @{ENTITYPREFIX}_REPORT_TITLE, resolve@{ENTITYNAME}ReportColumns } from './@{SIMPLEENTITYNAME}Page.helpers' defineOptions({ name: '@{ENTITYNAME}' }) const userStore = useUserStore() const searchForm = ref(create@{ENTITYNAME}SearchState()) const showSearchBar = ref(false) const reportTitle = @{ENTITYPREFIX}_REPORT_TITLE const reportQueryParams = computed(() => build@{ENTITYNAME}SearchParams(searchForm.value)) let handleEditAction = null let handleDeleteAction = null const { columns, columnChecks, data, loading, pagination, getData, replaceSearchParams, resetSearchParams, handleSizeChange, handleCurrentChange, refreshData, refreshCreate, refreshUpdate, refreshRemove } = useTable({ core: { apiFn: fetch@{ENTITYNAME}Page, apiParams: build@{ENTITYNAME}PageQueryParams(searchForm.value), paginationKey: get@{ENTITYNAME}PaginationKey(), columnsFactory: () => create@{ENTITYNAME}TableColumns({ handleEdit: (row) => handleEditAction?.(row), handleDelete: (row) => handleDeleteAction?.(row) }) }, transform: { dataTransformer: (records) => { if (!Array.isArray(records)) { return [] } return records.map((item) => normalize@{ENTITYNAME}ListRow(item)) } } }) const { dialogVisible, dialogType, currentRecord: currentRecordData, selectedRows, handleSelectionChange, showDialog, handleDialogSubmit, handleDelete, handleBatchDelete } = useCrudPage({ createEmptyModel: () => build@{ENTITYNAME}DialogModel(), buildEditModel: (record) => build@{ENTITYNAME}DialogModel(record), buildSavePayload: (formData) => build@{ENTITYNAME}SavePayload(formData), saveRequest: fetchSave@{ENTITYNAME}, updateRequest: fetchUpdate@{ENTITYNAME}, deleteRequest: fetchDelete@{ENTITYNAME}, entityName: '@{TABLEDESC}', resolveRecordLabel: (record) => record?.@{MAJORCOLUMN0} || record?.@{PRIMARYKEYCOLUMN0}, refreshCreate, refreshUpdate, refreshRemove }) handleEditAction = (row) => showDialog('edit', row) handleDeleteAction = handleDelete const buildPreviewDialogMeta = (rows) => { const now = new Date() return { reportTitle, reportDate: now.toLocaleDateString('zh-CN'), printedAt: now.toLocaleString('zh-CN', { hour12: false }), operator: userStore.getUserInfo?.name || userStore.getUserInfo?.username || '', count: rows.length } } const resolvePrintRecords = async (payload) => { const response = Array.isArray(payload?.ids) && payload.ids.length > 0 ? await fetchGet@{ENTITYNAME}Many(payload.ids) : await fetch@{ENTITYNAME}Page({ ...reportQueryParams.value, current: 1, pageSize: Number(pagination.total) > 0 ? Number(pagination.total) : Number(payload?.pageSize) || 20 }) return defaultResponseAdapter(response).records } const { previewVisible, previewRows, previewMeta, handlePreviewVisibleChange, handleExport, handlePrint } = usePrintExportPage({ downloadFileName: '@{KEBABENTITYNAME}.xlsx', requestExport: (payload) => fetchExport@{ENTITYNAME}Report(payload, { headers: { Authorization: userStore.accessToken || '' } }), resolvePrintRecords, buildPreviewRows: (records) => build@{ENTITYNAME}PrintRows(records), buildPreviewMeta: (rows) => buildPreviewDialogMeta(rows) }) const reportColumns = computed(() => resolve@{ENTITYNAME}ReportColumns(columns.value)) const resolvedPreviewMeta = computed(() => build@{ENTITYNAME}ReportMeta({ previewMeta: previewMeta.value, count: previewRows.value.length, titleAlign: @{ENTITYPREFIX}_REPORT_STYLE.titleAlign, titleLevel: @{ENTITYPREFIX}_REPORT_STYLE.titleLevel }) ) function handleSearch(params) { replaceSearchParams(build@{ENTITYNAME}SearchParams(params)) getData() } function handleReset() { Object.assign(searchForm.value, create@{ENTITYNAME}SearchState()) resetSearchParams() } </script> rsf-framework/src/main/resources/templates/rsf-design/PageHelpers.txt
New file @@ -0,0 +1,245 @@ const FIELD_OPTIONS = @{FIELDOPTIONSCONTENT} export const @{ENTITYPREFIX}_REPORT_TITLE = '@{TABLEDESC}报表' export const @{ENTITYPREFIX}_REPORT_STYLE = { titleAlign: 'center', titleLevel: 'strong', orientation: 'portrait', density: 'compact', showSequence: true } function normalizeText(value) { return String(value ?? '').trim() } function hasValue(value) { return value !== '' && value !== null && value !== undefined } function toOptionalNumber(value) { if (!hasValue(value)) { return void 0 } const parsed = Number(value) return Number.isNaN(parsed) ? void 0 : parsed } function toOptionalBoolean(value) { if (!hasValue(value)) { return void 0 } if (value === true || value === false) { return value } if (value === 1 || value === '1' || String(value).toLowerCase() === 'true') { return true } if (value === 0 || value === '0' || String(value).toLowerCase() === 'false') { return false } return Boolean(value) } function buildNumberField(key, value) { return hasValue(value) ? { [key]: Number(value) } : {} } function cloneOptions(options = []) { return options.map((option) => ({ ...option })) } function normalizeStatusValue(status) { if (!hasValue(status)) { return '' } return String(status) } function formatBooleanText(value) { if (value === true) { return '是' } if (value === false) { return '否' } return '' } function resolveStatusType(option = {}) { const label = String(option.label || '') const value = normalizeStatusValue(option.value) if (value === '1' || value === 'true' || /正常|启用|有效|开启|是/.test(label)) { return 'success' } if (value === '0' || value === 'false' || /禁用|冻结|停用|关闭|否/.test(label)) { return 'danger' } return 'info' } function resolveStatusBool(option = {}) { const value = normalizeStatusValue(option.value) if (value === '1' || value === 'true') { return true } if (value === '0' || value === 'false') { return false } return false } export function create@{ENTITYNAME}SearchState() { return { condition: '', @{SEARCHSTATECONTENT} } } export function create@{ENTITYNAME}FormState() { return { @{FORMSTATECONTENT} } } export function get@{ENTITYNAME}PaginationKey() { return { current: 'current', size: 'pageSize' } } export function get@{ENTITYNAME}FieldOptions(field) { return cloneOptions(FIELD_OPTIONS[field] || []) } export function get@{ENTITYNAME}StatusOptions() { return get@{ENTITYNAME}FieldOptions('status') } export function get@{ENTITYNAME}StatusMeta(status) { const options = FIELD_OPTIONS.status || [] const normalizedStatus = normalizeStatusValue(status) const matchedOption = options.find((option) => normalizeStatusValue(option.value) === normalizedStatus) if (!matchedOption) { return { type: 'info', text: normalizeText(status) || '未知', bool: false } } return { type: resolveStatusType(matchedOption), text: matchedOption.label, bool: resolveStatusBool(matchedOption) } } export function build@{ENTITYNAME}SearchParams(params = {}) { const searchParams = { condition: normalizeText(params.condition), @{SEARCHPARAMSCONTENT} } return Object.fromEntries(Object.entries(searchParams).filter(([, value]) => hasValue(value))) } export function build@{ENTITYNAME}PageQueryParams(params = {}) { return { current: params.current || 1, pageSize: params.pageSize || params.size || 20, ...build@{ENTITYNAME}SearchParams(params) } } export const @{ENTITYPREFIX}_REPORT_COLUMNS = [ @{REPORTCOLUMNSCONTENT} ] const @{ENTITYPREFIX}_REPORT_SOURCE_ALIAS = { @{REPORTSOURCEALIASCONTENT} } export function get@{ENTITYNAME}ReportColumns() { return @{ENTITYPREFIX}_REPORT_COLUMNS.map((column) => ({ ...column })) } export function resolve@{ENTITYNAME}ReportColumns(columns = []) { if (!Array.isArray(columns)) { return [] } const allowedColumns = new Map(@{ENTITYPREFIX}_REPORT_COLUMNS.map((column) => [column.source, column])) const seenSources = new Set() return columns .map((column) => { if (!column || typeof column !== 'object') { return null } const source = @{ENTITYPREFIX}_REPORT_SOURCE_ALIAS[column.source ?? column.prop] ?? column.source ?? column.prop if (!source || !allowedColumns.has(source) || seenSources.has(source)) { return null } seenSources.add(source) const allowedColumn = allowedColumns.get(source) return { source, label: column.label || allowedColumn.label } }) .filter(Boolean) } export function build@{ENTITYNAME}ReportMeta({ previewMeta = {}, count = 0, titleAlign = @{ENTITYPREFIX}_REPORT_STYLE.titleAlign, titleLevel = @{ENTITYPREFIX}_REPORT_STYLE.titleLevel } = {}) { return { reportTitle: @{ENTITYPREFIX}_REPORT_TITLE, reportDate: previewMeta.reportDate, printedAt: previewMeta.printedAt, operator: previewMeta.operator, count, reportStyle: { ...@{ENTITYPREFIX}_REPORT_STYLE, titleAlign, titleLevel } } } export function build@{ENTITYNAME}DialogModel(record = {}) { return { ...create@{ENTITYNAME}FormState(), @{DIALOGMODELCONTENT} } } export function build@{ENTITYNAME}SavePayload(formData = {}) { return { @{SAVEPAYLOADCONTENT} } } export function normalize@{ENTITYNAME}ListRow(record = {}) { const statusMeta = get@{ENTITYNAME}StatusMeta(record.statusBool ?? record.status) return { ...record, @{NORMALIZEROWCONTENT} } } export function build@{ENTITYNAME}PrintRows(records = []) { if (!Array.isArray(records)) { return [] } return records.map((record) => normalize@{ENTITYNAME}ListRow(record)) } rsf-framework/src/main/resources/templates/rsf-design/Search.txt
New file @@ -0,0 +1,66 @@ <template> <ArtSearchBar ref="searchBarRef" v-model="formData" :items="formItems" :showExpand="false" @reset="handleReset" @search="handleSearch" /> </template> <script setup> import { create@{ENTITYNAME}SearchState, get@{ENTITYNAME}FieldOptions } from '../@{SIMPLEENTITYNAME}Page.helpers' const props = defineProps({ modelValue: { required: true } }) const emit = defineEmits(['update:modelValue', 'search', 'reset']) const searchBarRef = ref() const formData = computed({ get: () => props.modelValue, set: (val) => emit('update:modelValue', val) }) function createInputSearchItem(label, key, placeholder) { return { label, key, type: 'input', props: { placeholder, clearable: true } } } function createSelectSearchItem(label, key, placeholder, options) { return { label, key, type: 'select', props: { placeholder, clearable: true, options } } } const formItems = computed(() => [ createInputSearchItem('关键字', 'condition', '请输入@{TABLEDESC}关键字'), @{SEARCHITEMSCONTENT} ]) function handleReset() { emit('update:modelValue', create@{ENTITYNAME}SearchState()) emit('reset') } async function handleSearch(params) { await searchBarRef.value.validate() emit('search', params) } </script> rsf-framework/src/main/resources/templates/rsf-design/TableColumns.txt
New file @@ -0,0 +1,96 @@ import { h } from 'vue' import ArtButtonMore from '@/components/core/forms/art-button-more/index.vue' import { ElTag } from 'element-plus' import { get@{ENTITYNAME}StatusMeta } from './@{SIMPLEENTITYNAME}Page.helpers' const MORE_ACTIONS = [ { key: 'edit', label: '编辑', icon: 'ri:edit-2-line', auth: 'edit' }, { key: 'delete', label: '删除', icon: 'ri:delete-bin-4-line', color: '#f56c6c', auth: 'delete' } ] function resolveCellValue(row, prop) { const value = row?.[prop] return value === '' || value === void 0 || value === null ? '-' : value } function createTextColumn(prop, label, minWidth, extra = {}) { return { prop, label, minWidth, showOverflowTooltip: true, formatter: (row) => resolveCellValue(row, prop), ...extra } } function createNumberColumn(prop, label, width, extra = {}) { return { prop, label, width, formatter: (row) => resolveCellValue(row, prop), ...extra } } function createTagColumn(prop, label, width, resolveMeta) { return { prop, label, width, formatter: (row) => { const statusMeta = resolveMeta(row) return h(ElTag, { type: statusMeta.type, effect: 'light' }, () => statusMeta.text) } } } function buildMoreActions(handleEdit, handleDelete) { return MORE_ACTIONS.filter((item) => { if (item.key === 'edit') { return typeof handleEdit === 'function' } if (item.key === 'delete') { return typeof handleDelete === 'function' } return true }) } export function create@{ENTITYNAME}TableColumns({ handleEdit, handleDelete } = {}) { return [ { type: 'selection', width: 52, fixed: 'left' }, @{TABLECOLUMNSCONTENT} { prop: 'operation', label: '操作', width: 120, align: 'center', fixed: 'right', formatter: (row) => h(ArtButtonMore, { list: buildMoreActions(handleEdit, handleDelete), onClick: (item) => { if (item.key === 'edit') { handleEdit?.(row) } if (item.key === 'delete') { handleDelete?.(row) } } }) } ] } rsf-server/src/main/java/com/vincent/rsf/server/common/CodeBuilder.java
@@ -2,6 +2,7 @@ import com.vincent.rsf.framework.generators.ReactGenerator; import com.vincent.rsf.framework.generators.RsfDesignGenerator; import com.vincent.rsf.framework.generators.constant.SqlOsType; /** @@ -10,6 +11,10 @@ public class CodeBuilder { public static void main(String[] args) throws Exception { buildRsfDesign(); } public static void buildReactAdmin() throws Exception { ReactGenerator generator = new ReactGenerator(); generator.backendPrefixPath = "rsf-server/"; generator.frontendPrefixPath = "rsf-admin/"; @@ -25,7 +30,27 @@ generator.table = "man_matnr_restriction_warehouse"; generator.tableDesc = "物料限制"; generator.packagePath = "com.vincent.rsf.server.manager"; generator.build(); } public static void buildRsfDesign() throws Exception { RsfDesignGenerator generator = new RsfDesignGenerator(); generator.backendPrefixPath = "rsf-server/"; generator.frontendPrefixPath = "rsf-design/"; generator.sqlOsType = SqlOsType.MYSQL; generator.url = "127.0.0.1:3306/rsf"; generator.username = "root"; generator.password = "root"; // generator.url="47.97.1.152:51433;databasename=jkasrs"; // generator.username="sa"; // generator.password="Zoneyung@zy56$"; generator.table = "man_matnr_restriction_warehouse"; generator.tableDesc = "物料限制"; generator.packagePath = "com.vincent.rsf.server.manager"; generator.frontendViewPath = "manager/matnr-restriction-warehouse"; generator.frontendApiModule = "matnr-restriction-warehouse"; generator.build(); } /*