feat(cms): 添加 CMS 模块控制器

- 新增 CmsAdController、CmsAdRecordController、CmsArticleCategoryController、CmsArticleCommentController、CmsArticleContentController、CmsArticleController 和 CmsArticleCountController
- 实现了广告位、广告图片、文章分类、文章评论、文章记录、文章和点赞文章的 CRUD操作
- 添加了分页查询、批量操作等接口
-集成了 Swagger 文档注解
- 优化了代码结构,提高了可维护性
This commit is contained in:
2025-08-13 05:15:28 +08:00
parent 46dbf09d81
commit 70a760fb22
108 changed files with 7319 additions and 1498 deletions

View File

@@ -1,406 +0,0 @@
package com.gxwebsoft.generator;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.core.toolkit.StringPool;
import com.baomidou.mybatisplus.generator.AutoGenerator;
import com.baomidou.mybatisplus.generator.InjectionConfig;
import com.baomidou.mybatisplus.generator.config.*;
import com.baomidou.mybatisplus.generator.config.po.TableInfo;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
import com.gxwebsoft.generator.engine.BeetlTemplateEnginePlus;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.regex.Pattern;
import java.util.regex.Matcher;
/**
* CMS模块-代码生成工具
*
* @author WebSoft
* @since 2021-09-05 00:31:14
*/
public class CmsGenerator {
// 输出位置
private static final String OUTPUT_LOCATION = System.getProperty("user.dir");
//private static final String OUTPUT_LOCATION = "D:/codegen"; // 不想生成到项目中可以写磁盘路径
// 输出目录
private static final String OUTPUT_DIR = "/src/main/java";
// Vue文件输出位置
private static final String OUTPUT_LOCATION_VUE = "/Users/gxwebsoft/VUE/site";
// UniApp文件输出目录
private static final String OUTPUT_LOCATION_UNIAPP = "/Users/gxwebsoft/VUE/template-10550";
// Vue文件输出目录
private static final String OUTPUT_DIR_VUE = "/src";
// 作者名称
private static final String AUTHOR = "科技小王子";
// 是否在xml中添加二级缓存配置
private static final boolean ENABLE_CACHE = false;
// 数据库连接配置
private static final String DB_URL = "jdbc:mysql://47.119.165.234:3308/modules?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=GMT%2B8";
private static final String DB_DRIVER = "com.mysql.cj.jdbc.Driver";
private static final String DB_USERNAME = "modules";
private static final String DB_PASSWORD = "8YdLnk7KsPAyDXGA";
// 包名
private static final String PACKAGE_NAME = "com.gxwebsoft";
// 模块名
private static final String MODULE_NAME = "cms";
// 需要生成的表
private static final String[] TABLE_NAMES = new String[]{
// "cms_article",
// "cms_article_category",
// "cms_article_like",
// "cms_article_comment",
// "cms_article_content",
// "cms_docs",
// "cms_docs_book",
// "cms_docs_content",
// "cms_ad",
// "cms_ad_record",
// "cms_navigation",
// "cms_design",
// "cms_design_record",
// "cms_website",
// "cms_website_field",
// "cms_form",
// "cms_form_record",
// "cms_domain",
// "cms_mp_menu"
// "cms_mp_pages",
// "cms_mp"
// "cms_mp_field"
// "cms_mp_ad"
// "cms_components"
// "cms_mp_official_menu",
// "cms_order",
// "cms_model"
// "cms_lang",
// "cms_lang_log",
// "cms_website_setting",
// "cms_statistics"
};
// 需要去除的表前缀
private static final String[] TABLE_PREFIX = new String[]{
"tb_"
};
// 不需要作为查询参数的字段
private static final String[] PARAM_EXCLUDE_FIELDS = new String[]{
"tenant_id",
"create_time",
"update_time"
};
// 查询参数使用String的类型
private static final String[] PARAM_TO_STRING_TYPE = new String[]{
"Date",
"LocalDate",
"LocalTime",
"LocalDateTime"
};
// 查询参数使用EQ的类型
private static final String[] PARAM_EQ_TYPE = new String[]{
"Integer",
"Boolean",
"BigDecimal"
};
// 是否添加权限注解
private static final boolean AUTH_ANNOTATION = true;
// 是否添加日志注解
private static final boolean LOG_ANNOTATION = true;
// controller的mapping前缀
private static final String CONTROLLER_MAPPING_PREFIX = "/api";
// 模板所在位置
private static final String TEMPLATES_DIR = "/src/test/java/com/gxwebsoft/generator/templates";
public static void main(String[] args) {
// 代码生成器
AutoGenerator mpg = new AutoGenerator();
// 全局配置
GlobalConfig gc = new GlobalConfig();
gc.setOutputDir(OUTPUT_LOCATION + OUTPUT_DIR);
gc.setAuthor(AUTHOR);
gc.setOpen(false);
gc.setFileOverride(true);
gc.setEnableCache(ENABLE_CACHE);
gc.setSwagger2(true);
gc.setIdType(IdType.AUTO);
gc.setServiceName("%sService");
mpg.setGlobalConfig(gc);
// 数据源配置
DataSourceConfig dsc = new DataSourceConfig();
dsc.setUrl(DB_URL);
// dsc.setSchemaName("public");
dsc.setDriverName(DB_DRIVER);
dsc.setUsername(DB_USERNAME);
dsc.setPassword(DB_PASSWORD);
mpg.setDataSource(dsc);
// 包配置
PackageConfig pc = new PackageConfig();
pc.setModuleName(MODULE_NAME);
pc.setParent(PACKAGE_NAME);
mpg.setPackageInfo(pc);
// 策略配置
StrategyConfig strategy = new StrategyConfig();
strategy.setNaming(NamingStrategy.underline_to_camel);
strategy.setColumnNaming(NamingStrategy.underline_to_camel);
strategy.setInclude(TABLE_NAMES);
strategy.setTablePrefix(TABLE_PREFIX);
strategy.setSuperControllerClass(PACKAGE_NAME + ".common.core.web.BaseController");
strategy.setEntityLombokModel(true);
strategy.setRestControllerStyle(true);
strategy.setControllerMappingHyphenStyle(true);
strategy.setLogicDeleteFieldName("deleted");
mpg.setStrategy(strategy);
// 模板配置
TemplateConfig templateConfig = new TemplateConfig();
templateConfig.setController(TEMPLATES_DIR + "/controller.java");
templateConfig.setEntity(TEMPLATES_DIR + "/entity.java");
templateConfig.setMapper(TEMPLATES_DIR + "/mapper.java");
templateConfig.setXml(TEMPLATES_DIR + "/mapper.xml");
templateConfig.setService(TEMPLATES_DIR + "/service.java");
templateConfig.setServiceImpl(TEMPLATES_DIR + "/serviceImpl.java");
mpg.setTemplate(templateConfig);
mpg.setTemplateEngine(new BeetlTemplateEnginePlus());
// 自定义模板配置
InjectionConfig cfg = new InjectionConfig() {
@Override
public void initMap() {
Map<String, Object> map = new HashMap<>();
map.put("packageName", PACKAGE_NAME);
map.put("paramExcludeFields", PARAM_EXCLUDE_FIELDS);
map.put("paramToStringType", PARAM_TO_STRING_TYPE);
map.put("paramEqType", PARAM_EQ_TYPE);
map.put("authAnnotation", AUTH_ANNOTATION);
map.put("logAnnotation", LOG_ANNOTATION);
map.put("controllerMappingPrefix", CONTROLLER_MAPPING_PREFIX);
this.setMap(map);
}
};
String templatePath = TEMPLATES_DIR + "/param.java.btl";
List<FileOutConfig> focList = new ArrayList<>();
focList.add(new FileOutConfig(templatePath) {
@Override
public String outputFile(TableInfo tableInfo) {
return OUTPUT_LOCATION + OUTPUT_DIR + "/"
+ PACKAGE_NAME.replace(".", "/")
+ "/" + pc.getModuleName() + "/param/"
+ tableInfo.getEntityName() + "Param" + StringPool.DOT_JAVA;
}
});
/**
* 以下是生成VUE项目代码
* 生成文件的路径 /api/shop/goods/index.ts
*/
templatePath = TEMPLATES_DIR + "/index.ts.btl";
focList.add(new FileOutConfig(templatePath) {
@Override
public String outputFile(TableInfo tableInfo) {
return OUTPUT_LOCATION_VUE + OUTPUT_DIR_VUE
+ "/api/" + pc.getModuleName() + "/"
+ tableInfo.getEntityPath() + "/" + "index.ts";
}
});
focList.add(new FileOutConfig() {
@Override
public String outputFile(TableInfo tableInfo) {
return OUTPUT_LOCATION_UNIAPP + OUTPUT_DIR_VUE
+ "/api/" + pc.getModuleName() + "/"
+ tableInfo.getEntityPath() + "/" + "index.ts";
}
});
// 生成TS文件 (/api/shop/goods/model/index.ts)
templatePath = TEMPLATES_DIR + "/model.ts.btl";
focList.add(new FileOutConfig(templatePath) {
@Override
public String outputFile(TableInfo tableInfo) {
return OUTPUT_LOCATION_VUE + OUTPUT_DIR_VUE
+ "/api/" + pc.getModuleName() + "/"
+ tableInfo.getEntityPath() + "/model/" + "index.ts";
}
});
focList.add(new FileOutConfig(templatePath) {
@Override
public String outputFile(TableInfo tableInfo) {
return OUTPUT_LOCATION_UNIAPP + OUTPUT_DIR_VUE
+ "/api/" + pc.getModuleName() + "/"
+ tableInfo.getEntityPath() + "/model/" + "index.ts";
}
});
// 生成Vue文件(/views/shop/goods/index.vue)
templatePath = TEMPLATES_DIR + "/index.vue.btl";
focList.add(new FileOutConfig(templatePath) {
@Override
public String outputFile(TableInfo tableInfo) {
return OUTPUT_LOCATION_VUE + OUTPUT_DIR_VUE
+ "/views/" + pc.getModuleName() + "/"
+ tableInfo.getEntityPath() + "/" + "index.vue";
}
});
// 生成components文件(/views/shop/goods/components/edit.vue)
templatePath = TEMPLATES_DIR + "/components.edit.vue.btl";
focList.add(new FileOutConfig(templatePath) {
@Override
public String outputFile(TableInfo tableInfo) {
return OUTPUT_LOCATION_VUE + OUTPUT_DIR_VUE
+ "/views/" + pc.getModuleName() + "/"
+ tableInfo.getEntityPath() + "/components/" + tableInfo.getEntityPath() + "Edit.vue";
}
});
// 生成components文件(/views/shop/goods/components/search.vue)
templatePath = TEMPLATES_DIR + "/components.search.vue.btl";
focList.add(new FileOutConfig(templatePath) {
@Override
public String outputFile(TableInfo tableInfo) {
return OUTPUT_LOCATION_VUE + OUTPUT_DIR_VUE
+ "/views/" + pc.getModuleName() + "/"
+ tableInfo.getEntityPath() + "/components/" + "search.vue";
}
});
// ========== 移动端页面文件生成 ==========
// 生成移动端列表页面配置文件 (/src/cms/article/index.config.ts)
templatePath = TEMPLATES_DIR + "/index.config.ts.btl";
focList.add(new FileOutConfig(templatePath) {
@Override
public String outputFile(TableInfo tableInfo) {
return OUTPUT_LOCATION_UNIAPP + OUTPUT_DIR_VUE
+ "/" + pc.getModuleName() + "/"
+ tableInfo.getEntityPath() + "/" + "index.config.ts";
}
});
// 生成移动端列表页面组件文件 (/src/cms/article/index.tsx)
templatePath = TEMPLATES_DIR + "/index.tsx.btl";
focList.add(new FileOutConfig(templatePath) {
@Override
public String outputFile(TableInfo tableInfo) {
return OUTPUT_LOCATION_UNIAPP + OUTPUT_DIR_VUE
+ "/" + pc.getModuleName() + "/"
+ tableInfo.getEntityPath() + "/" + "index.tsx";
}
});
// 生成移动端新增/编辑页面配置文件 (/src/cms/article/add.config.ts)
templatePath = TEMPLATES_DIR + "/add.config.ts.btl";
focList.add(new FileOutConfig(templatePath) {
@Override
public String outputFile(TableInfo tableInfo) {
return OUTPUT_LOCATION_UNIAPP + OUTPUT_DIR_VUE
+ "/" + pc.getModuleName() + "/"
+ tableInfo.getEntityPath() + "/" + "add.config.ts";
}
});
// 生成移动端新增/编辑页面组件文件 (/src/cms/article/add.tsx)
templatePath = TEMPLATES_DIR + "/add.tsx.btl";
focList.add(new FileOutConfig(templatePath) {
@Override
public String outputFile(TableInfo tableInfo) {
return OUTPUT_LOCATION_UNIAPP + OUTPUT_DIR_VUE
+ "/" + pc.getModuleName() + "/"
+ tableInfo.getEntityPath() + "/" + "add.tsx";
}
});
cfg.setFileOutConfigList(focList);
mpg.setCfg(cfg);
mpg.execute();
// 自动更新 app.config.ts
updateAppConfig(TABLE_NAMES, MODULE_NAME);
}
/**
* 自动更新 app.config.ts 文件,添加新生成的页面路径
*/
private static void updateAppConfig(String[] tableNames, String moduleName) {
String appConfigPath = OUTPUT_LOCATION_UNIAPP + OUTPUT_DIR_VUE + "/app.config.ts";
try {
// 读取原文件内容
String content = new String(Files.readAllBytes(Paths.get(appConfigPath)));
// 为每个表生成页面路径
StringBuilder newPages = new StringBuilder();
for (String tableName : tableNames) {
String entityPath = tableName.replaceAll("_", "");
// 转换为驼峰命名
String[] parts = tableName.split("_");
StringBuilder camelCase = new StringBuilder(parts[0]);
for (int i = 1; i < parts.length; i++) {
camelCase.append(parts[i].substring(0, 1).toUpperCase()).append(parts[i].substring(1));
}
entityPath = camelCase.toString();
newPages.append(" '").append(entityPath).append("/index',\n");
newPages.append(" '").append(entityPath).append("/add',\n");
}
// 查找对应模块的子包配置
String modulePattern = "\"root\":\\s*\"" + moduleName + "\",\\s*\"pages\":\\s*\\[([^\\]]*)]";
Pattern pattern = Pattern.compile(modulePattern, Pattern.DOTALL);
Matcher matcher = pattern.matcher(content);
if (matcher.find()) {
String existingPages = matcher.group(1);
// 检查页面是否已存在,避免重复添加
boolean needUpdate = false;
String[] newPageArray = newPages.toString().split("\n");
for (String newPage : newPageArray) {
if (!newPage.trim().isEmpty() && !existingPages.contains(newPage.trim().replace(" ", "").replace(",", ""))) {
needUpdate = true;
break;
}
}
if (needUpdate) {
// 备份原文件
String backupPath = appConfigPath + ".backup." + System.currentTimeMillis();
Files.copy(Paths.get(appConfigPath), Paths.get(backupPath));
System.out.println("已备份原文件到: " + backupPath);
// 在现有页面列表末尾添加新页面
String updatedPages = existingPages.trim();
if (!updatedPages.endsWith(",")) {
updatedPages += ",";
}
updatedPages += "\n" + newPages.toString().trim();
// 替换内容
String updatedContent = content.replace(matcher.group(1), updatedPages);
// 写入更新后的内容
Files.write(Paths.get(appConfigPath), updatedContent.getBytes());
System.out.println("✅ 已自动更新 app.config.ts添加了以下页面路径:");
System.out.println(newPages.toString());
} else {
System.out.println(" app.config.ts 中已包含所有页面路径,无需更新");
}
} else {
System.out.println("⚠️ 未找到 " + moduleName + " 模块的子包配置,请手动添加页面路径");
}
} catch (Exception e) {
System.err.println("❌ 更新 app.config.ts 失败: " + e.getMessage());
e.printStackTrace();
}
}
}

View File

@@ -1,195 +0,0 @@
# 表格列生成规则优化方案
## 问题描述
原有的表格列生成规则会为所有字段除了tenantId生成列导致
- 表格列过多,显示混乱
- 重要信息被淹没在大量字段中
- 用户体验差,需要横向滚动查看
## 优化方案
### 1. 使用 hideInTable 属性(推荐方案)
**核心思想**:生成所有字段的列配置,但使用 `hideInTable: true` 隐藏非核心字段
**优势**
-**保留完整信息**:所有字段都会生成,不丢失任何数据
-**用户可自定义**:通过表格的列设置功能,用户可以勾选显示需要的字段
-**默认简洁**:只显示最重要的核心字段,保持表格整洁
-**灵活性强**:不同用户可以根据需要显示不同的列组合
**核心字段判断规则**
- 主键字段:始终显示
- 名称/标题字段:始终显示
- 编码字段:始终显示
- 状态字段:始终显示
- 排序字段:始终显示
- 创建时间:始终显示
- 其他字段:默认隐藏(`hideInTable: true`
### 2. 排除字段
以下字段不会显示在表格中:
- `tenantId` - 租户ID
- `deleted` - 逻辑删除标记
- `version` - 版本号
- `remark` - 备注(内容通常较长)
- `description` - 描述(内容通常较长)
- `content` - 内容(内容通常较长)
### 3. 字段宽度优化
根据字段类型和内容特点设置合适的宽度:
- ID字段90px
- 状态字段80px
- 时间字段120px
- 字符串字段150px
- 数值字段120px
## 使用方法
### 方法1使用 hideInTable 模板(强烈推荐)
已经修改了 `index.vue.btl` 模板,现在会自动:
1. **生成所有字段**:不丢失任何字段信息
2. **智能隐藏**:非核心字段添加 `hideInTable: true`
3. **用户可控**:用户可通过表格的列设置功能显示需要的字段
**生成的代码示例**
```javascript
const columns = ref<ColumnItem[]>([
{
title: 'ID',
dataIndex: 'id',
key: 'id',
width: 90,
// 核心字段,默认显示
},
{
title: '名称',
dataIndex: 'name',
key: 'name',
width: 150,
// 核心字段,默认显示
},
{
title: '创建人',
dataIndex: 'createBy',
key: 'createBy',
width: 120,
hideInTable: true, // 非核心字段,默认隐藏
},
{
title: '备注',
dataIndex: 'remark',
key: 'remark',
width: 200,
hideInTable: true, // 长文本字段,默认隐藏
}
]);
```
### 方法2前端列设置功能
参考 `table-with-column-settings.vue` 示例,可以添加列设置功能:
```javascript
// 列可见性控制
const allColumns = ref([
{ title: 'ID', visible: true, required: true },
{ title: '名称', visible: true, required: true },
{ title: '创建人', visible: false, required: false },
// ...
]);
// 根据可见性过滤列
const columns = computed(() => {
return allColumns.value.filter(col => col.visible);
});
```
### 方法3自定义核心字段规则
在模板中修改核心字段判断逻辑:
```javascript
// 在模板中添加你的业务字段
<% } else if(field.propertyName == 'yourBusinessField'){ %>
// 设为核心字段,默认显示
isCoreField = true;
```
## 生成效果对比
### 优化前
```
| ID | 名称 | 编码 | 状态 | 排序 | 创建人 | 创建时间 | 更新人 | 更新时间 | 备注 | 描述 | ... | 操作 |
```
*显示所有字段,表格过宽,信息混乱*
### 优化后(使用 hideInTable
**默认显示**
```
| ID | 名称 | 编码 | 状态 | 排序 | 创建时间 | 操作 |
```
*只显示核心字段,表格简洁*
**用户可通过列设置显示更多字段**
```
| ID | 名称 | 编码 | 状态 | ✓创建人 | 创建时间 | ✓更新时间 | ✓备注 | 操作 |
```
*用户勾选后可显示需要的字段*
### 列设置界面示例
```
列设置
☑ ID (固定显示)
☑ 名称 (固定显示)
☑ 编码 (固定显示)
☑ 状态 (固定显示)
☐ 创建人 (可选)
☑ 创建时间 (固定显示)
☐ 更新人 (可选)
☐ 更新时间 (可选)
☐ 备注 (可选)
☐ 描述 (可选)
```
## 配置说明
### 修改默认显示列数
在模板中修改:
```javascript
.slice(0, 6); // 改为你想要的列数
```
### 添加新的优先字段
在模板的优先级判断中添加:
```javascript
<% } else if(field.propertyName == 'yourField'){ %>
width: 120,
priority: 8, // 设置优先级
```
### 排除特定字段
在条件判断中添加:
```javascript
<% if(field.propertyName != 'tenantId' && field.propertyName != 'yourExcludedField'){ %>
```
## 建议
1. **保持默认6列**:这是最佳的用户体验,既能显示关键信息,又不会过于拥挤
2. **优先显示业务关键字段**:如名称、状态、创建时间等
3. **提供列设置功能**:让用户可以自定义显示哪些列
4. **响应式设计**:在移动端进一步减少显示列数
## 注意事项
- 修改模板后需要重新生成代码才能生效
- 建议在生成前备份原有模板文件
- 可以根据具体业务需求调整优先级规则

View File

@@ -1,149 +0,0 @@
package com.gxwebsoft.generator;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;
/**
* 简单的测试生成器,用于验证模板文件的完整性
*/
public class SimpleTestGenerator {
public static void main(String[] args) {
System.out.println("=== 代码生成器模板验证 ===");
String templatesDir = "src/test/java/com/gxwebsoft/generator/templates";
// 检查必要的模板文件
String[] requiredTemplates = {
"index.vue.btl",
"components.edit.vue.btl",
"components.search.vue.btl",
"index.tsx.btl",
"add.tsx.btl",
"index.config.ts.btl",
"add.config.ts.btl",
"index.ts.uniapp.btl",
"model.ts.uniapp.btl",
"controller.java.btl",
"service.java.btl",
"serviceImpl.java.btl",
"mapper.java.btl",
"mapper.xml.btl",
"entity.java.btl",
"param.java.btl"
};
boolean allTemplatesExist = true;
for (String template : requiredTemplates) {
String filePath = templatesDir + "/" + template;
File file = new File(filePath);
if (file.exists()) {
try {
long fileSize = Files.size(Paths.get(filePath));
System.out.println("" + template + " (大小: " + fileSize + " 字节)");
} catch (Exception e) {
System.out.println("⚠️ " + template + " (无法读取文件大小)");
}
} else {
System.out.println("" + template + " (文件不存在)");
allTemplatesExist = false;
}
}
System.out.println("\n=== 验证结果 ===");
if (allTemplatesExist) {
System.out.println("✅ 所有模板文件都存在且可读");
// 检查关键模板的内容完整性
checkTemplateIntegrity();
} else {
System.out.println("❌ 部分模板文件缺失");
}
}
private static void checkTemplateIntegrity() {
System.out.println("\n=== 模板内容完整性检查 ===");
// 检查Vue模板
checkVueTemplate();
// 检查移动端模板
checkMobileTemplate();
// 检查API模板
checkApiTemplate();
}
private static void checkVueTemplate() {
try {
String content = new String(Files.readAllBytes(
Paths.get("src/test/java/com/gxwebsoft/generator/templates/index.vue.btl")));
boolean hasTemplate = content.contains("<template>");
boolean hasScript = content.contains("<script");
boolean hasImports = content.contains("import");
boolean hasExport = content.contains("export default");
System.out.println("Vue模板检查:");
System.out.println(" - Template标签: " + (hasTemplate ? "" : ""));
System.out.println(" - Script标签: " + (hasScript ? "" : ""));
System.out.println(" - Import语句: " + (hasImports ? "" : ""));
System.out.println(" - Export语句: " + (hasExport ? "" : ""));
} catch (Exception e) {
System.out.println("Vue模板检查失败: " + e.getMessage());
}
}
private static void checkMobileTemplate() {
try {
String content = new String(Files.readAllBytes(
Paths.get("src/test/java/com/gxwebsoft/generator/templates/index.tsx.btl")));
boolean hasImports = content.contains("import");
boolean hasComponent = content.contains("const") && content.contains("Manage");
boolean hasExport = content.contains("export default");
boolean hasSearchBar = content.contains("SearchBar");
boolean hasInfiniteLoading = content.contains("InfiniteLoading");
System.out.println("移动端模板检查:");
System.out.println(" - Import语句: " + (hasImports ? "" : ""));
System.out.println(" - 组件定义: " + (hasComponent ? "" : ""));
System.out.println(" - Export语句: " + (hasExport ? "" : ""));
System.out.println(" - 搜索功能: " + (hasSearchBar ? "" : ""));
System.out.println(" - 无限滚动: " + (hasInfiniteLoading ? "" : ""));
} catch (Exception e) {
System.out.println("移动端模板检查失败: " + e.getMessage());
}
}
private static void checkApiTemplate() {
try {
String content = new String(Files.readAllBytes(
Paths.get("src/test/java/com/gxwebsoft/generator/templates/index.ts.uniapp.btl")));
boolean hasPageFunction = content.contains("export async function page");
boolean hasListFunction = content.contains("export async function list");
boolean hasAddFunction = content.contains("export async function add");
boolean hasUpdateFunction = content.contains("export async function update");
boolean hasRemoveFunction = content.contains("export async function remove");
boolean hasGetFunction = content.contains("export async function get");
System.out.println("API模板检查:");
System.out.println(" - 分页查询: " + (hasPageFunction ? "" : ""));
System.out.println(" - 列表查询: " + (hasListFunction ? "" : ""));
System.out.println(" - 添加功能: " + (hasAddFunction ? "" : ""));
System.out.println(" - 更新功能: " + (hasUpdateFunction ? "" : ""));
System.out.println(" - 删除功能: " + (hasRemoveFunction ? "" : ""));
System.out.println(" - 详情查询: " + (hasGetFunction ? "" : ""));
} catch (Exception e) {
System.out.println("API模板检查失败: " + e.getMessage());
}
}
}

View File

@@ -1,76 +0,0 @@
package com.gxwebsoft.generator;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.generator.AutoGenerator;
import com.baomidou.mybatisplus.generator.config.*;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
import com.gxwebsoft.generator.engine.BeetlTemplateEnginePlus;
/**
* 测试代码生成器
*
* @author WebSoft
* @since 2025-01-09
*/
public class TestGenerator {
public static void main(String[] args) {
System.out.println("=== 测试 MyBatis-Plus 代码生成器 ===");
try {
// 测试基本配置
AutoGenerator mpg = new AutoGenerator();
// 全局配置
GlobalConfig gc = new GlobalConfig();
gc.setOutputDir(System.getProperty("user.dir") + "/src/main/java");
gc.setAuthor("WebSoft");
gc.setOpen(false);
gc.setFileOverride(false); // 设置为false避免覆盖现有文件
gc.setSwagger2(true);
gc.setIdType(IdType.AUTO);
gc.setServiceName("%sService");
mpg.setGlobalConfig(gc);
// 数据源配置(使用测试配置,不连接真实数据库)
DataSourceConfig dsc = new DataSourceConfig();
dsc.setUrl("jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8");
dsc.setDriverName("com.mysql.cj.jdbc.Driver");
dsc.setUsername("test");
dsc.setPassword("test");
mpg.setDataSource(dsc);
// 包配置
PackageConfig pc = new PackageConfig();
pc.setModuleName("test");
pc.setParent("com.gxwebsoft");
mpg.setPackageInfo(pc);
// 策略配置
StrategyConfig strategy = new StrategyConfig();
strategy.setNaming(NamingStrategy.underline_to_camel);
strategy.setColumnNaming(NamingStrategy.underline_to_camel);
strategy.setEntityLombokModel(true);
strategy.setRestControllerStyle(true);
strategy.setInclude("test_table"); // 测试表名
strategy.setControllerMappingHyphenStyle(true);
strategy.setTablePrefix("tb_");
mpg.setStrategy(strategy);
// 模板引擎配置
mpg.setTemplateEngine(new BeetlTemplateEnginePlus());
System.out.println("✅ 代码生成器配置成功!");
System.out.println("✅ MyBatis-Plus Generator API 兼容性正常");
System.out.println("✅ BeetlTemplateEnginePlus 加载成功");
System.out.println("✅ 依赖版本降级成功,代码生成器已恢复正常");
// 注意:这里不执行实际的生成,只测试配置是否正确
// mpg.execute();
} catch (Exception e) {
System.err.println("❌ 代码生成器测试失败: " + e.getMessage());
e.printStackTrace();
}
}
}

View File

@@ -1,4 +0,0 @@
export default definePageConfig({
navigationBarTitleText: '新增收货地址',
navigationBarTextStyle: 'black'
})

View File

@@ -1,343 +0,0 @@
import {useEffect, useState, useRef} from "react";
import {useRouter} from '@tarojs/taro'
import {Button, Loading, CellGroup, Input, TextArea, Form} from '@nutui/nutui-react-taro'
import {Scan, ArrowRight} from '@nutui/icons-react-taro'
import Taro from '@tarojs/taro'
import {View} from '@tarojs/components'
import {Address} from '@nutui/nutui-react-taro'
import {ShopUserAddress} from "@/api/shop/shopUserAddress/model";
import {getShopUserAddress, listShopUserAddress, updateShopUserAddress} from "@/api/shop/shopUserAddress";
import RegionData from '@/api/json/regions-data.json';
const AddUserAddress = () => {
const {params} = useRouter();
const [loading, setLoading] = useState<boolean>(true)
const [text, setText] = useState<string>('')
const [optionsDemo1, setOptionsDemo1] = useState([])
const [visible, setVisible] = useState(false)
const [FormData, setFormData] = useState<ShopUserAddress>({})
const [inputText, setInputText] = useState<string>('')
const formRef = useRef<any>(null)
const reload = async () => {
const address = await getShopUserAddress(Number(params.id))
setFormData(address)
// 设置所在地区
setText(`${address.province} ${address.city} ${address.region}`)
// 整理地区数据
setRegionData()
}
/**
* 处理地区数据
*/
function setRegionData() {
// @ts-ignore
setOptionsDemo1(RegionData?.map((a) => {
return {
value: a.label,
text: a.label,
children: a.children?.map((b) => {
return {
value: b.label,
text: b.label,
children: b.children?.map((c) => {
return {
value: c.label,
text: c.label
}
})
}
})
}
}))
}
/**
* 地址识别功能
*/
const recognizeAddress = () => {
if (!inputText.trim()) {
Taro.showToast({
title: '请输入要识别的文本',
icon: 'none'
});
return;
}
try {
const result = parseAddressText(inputText);
// 更新表单数据
const newFormData = {
...FormData,
name: result.name || FormData.name,
phone: result.phone || FormData.phone,
address: result.address || FormData.address,
province: result.province || FormData.province,
city: result.city || FormData.city,
region: result.region || FormData.region
};
setFormData(newFormData);
// 更新地区显示文本
if (result.province && result.city && result.region) {
setText(`${result.province} ${result.city} ${result.region}`);
}
// 更新表单字段值
if (formRef.current) {
formRef.current.setFieldsValue(newFormData);
}
Taro.showToast({
title: '识别成功',
icon: 'success'
});
// 清空输入框
setInputText('');
} catch (error) {
Taro.showToast({
title: '识别失败,请检查文本格式',
icon: 'none'
});
}
};
/**
* 解析地址文本
*/
const parseAddressText = (text: string) => {
const result: any = {};
// 手机号正则 (11位数字)
const phoneRegex = /1[3-9]\d{9}/;
const phoneMatch = text.match(phoneRegex);
if (phoneMatch) {
result.phone = phoneMatch[0];
}
// 姓名正则 (2-4个中文字符通常在开头)
const nameRegex = /^[\u4e00-\u9fa5]{2,4}/;
const nameMatch = text.match(nameRegex);
if (nameMatch) {
result.name = nameMatch[0];
}
// 省市区识别
const regionResult = parseRegion(text);
if (regionResult) {
result.province = regionResult.province;
result.city = regionResult.city;
result.region = regionResult.region;
}
// 详细地址提取 (去除姓名、手机号、省市区后的剩余部分)
let addressText = text;
if (result.name) {
addressText = addressText.replace(result.name, '');
}
if (result.phone) {
addressText = addressText.replace(result.phone, '');
}
if (result.province) {
addressText = addressText.replace(result.province, '');
}
if (result.city) {
addressText = addressText.replace(result.city, '');
}
if (result.region) {
addressText = addressText.replace(result.region, '');
}
// 清理地址文本
result.address = addressText.replace(/[,。\s]+/g, '').trim();
return result;
};
/**
* 解析省市区
*/
const parseRegion = (text: string) => {
// @ts-ignore
for (const province of RegionData) {
if (text.includes(province.label)) {
const result: any = { province: province.label };
// 查找城市
if (province.children) {
for (const city of province.children) {
if (text.includes(city.label)) {
result.city = city.label;
// 查找区县
if (city.children) {
for (const region of city.children) {
if (text.includes(region.label)) {
result.region = region.label;
return result;
}
}
}
return result;
}
}
}
return result;
}
}
return null;
};
// 提交表单
const submitSucceed = async (values: any) => {
const defaultAddress = await listShopUserAddress({isDefault: true})
if(!defaultAddress) return
const setNotDefault = await updateShopUserAddress({
...defaultAddress[0],
isDefault: false
})
if(!setNotDefault) return
updateShopUserAddress({
...values,
id: Number(params.id),
province: FormData.province,
city: FormData.city,
region: FormData.region
}).then(() => {
Taro.showToast({title: `保存成功`, icon: 'success'})
setTimeout(() => {
return Taro.navigateBack()
}, 1000)
}).catch(() => {
Taro.showToast({
title: '保存失败',
icon: 'error'
});
})
}
const submitFailed = (error: any) => {
console.log(error, 'err...')
}
useEffect(() => {
reload().then(() => {
setLoading(false)
})
}, []);
if (loading) {
return <Loading className={'px-2'}></Loading>
}
return (
<>
<Form
ref={formRef}
divider
initialValues={FormData}
labelPosition="left"
onFinish={(values) => submitSucceed(values)}
onFinishFailed={(errors) => submitFailed(errors)}
footer={
<div
style={{
display: 'flex',
justifyContent: 'center',
width: '100%'
}}
>
<Button
nativeType="submit"
type="success"
size="large"
className={'w-full'}
block
>
使
</Button>
</div>
}
>
<CellGroup className={'px-3'}>
<div
style={{
border: '1px dashed #22c55e',
display: 'flex',
alignItems: 'flex-end',
justifyContent: 'space-between',
padding: '4px',
position: 'relative'
}}>
<TextArea
style={{height: '100px'}}
value={inputText}
onChange={(value) => setInputText(value)}
placeholder={'请粘贴或输入文本,点击"识别"自动识别收货人姓名、地址、电话'}
/>
<Button
icon={<Scan/>}
style={{position: 'absolute', right: '10px', bottom: '10px'}}
type="success"
size={'small'}
fill="dashed"
onClick={recognizeAddress}
>
</Button>
</div>
</CellGroup>
<View className={'bg-gray-100 h-3'}></View>
<CellGroup style={{padding: '4px 0'}}>
<Form.Item name="name" label="收货人" initialValue={FormData.name} required>
<Input placeholder="请输入收货人姓名" maxLength={10}/>
</Form.Item>
<Form.Item name="phone" label="手机号" initialValue={FormData.phone} required>
<Input placeholder="请输入手机号" maxLength={11}/>
</Form.Item>
<Form.Item
label="所在地区"
name="region"
initialValue={FormData.region}
rules={[{message: '请输入您的所在地区'}]}
required
>
<div className={'flex justify-between items-center'} onClick={() => setVisible(true)}>
<Input placeholder="选择所在地区" value={text} disabled/>
<ArrowRight className={'text-gray-400'}/>
</div>
</Form.Item>
<Form.Item name="address" label="收货地址" initialValue={FormData.address} required>
<TextArea maxLength={50} placeholder="请输入详细收货地址"/>
</Form.Item>
</CellGroup>
</Form>
<Address
visible={visible}
options={optionsDemo1}
title="选择地址"
onChange={(value, _) => {
setFormData({
...FormData,
province: `${value[0]}`,
city: `${value[1]}`,
region: `${value[2]}`
})
setText(value.join(' '))
}}
onClose={() => setVisible(false)}
/>
</>
);
};
export default AddUserAddress;

View File

@@ -1,4 +0,0 @@
export default definePageConfig({
navigationBarTitleText: '地址管理',
navigationBarTextStyle: 'black'
})

View File

@@ -1,151 +0,0 @@
import {useState} from "react";
import Taro, {useDidShow} from '@tarojs/taro'
import {Button, Cell, CellGroup, Space, Empty, ConfigProvider, Divider} from '@nutui/nutui-react-taro'
import {Dongdong, ArrowRight, CheckNormal, Checked} from '@nutui/icons-react-taro'
import {View} from '@tarojs/components'
import {ShopUserAddress} from "@/api/shop/shopUserAddress/model";
import {listShopUserAddress, removeShopUserAddress, updateShopUserAddress} from "@/api/shop/shopUserAddress";
const Address = () => {
const [list, setList] = useState<ShopUserAddress[]>([])
const [address, setAddress] = useState<ShopUserAddress>()
const reload = () => {
listShopUserAddress({
userId: Taro.getStorageSync('UserId')
})
.then(data => {
setList(data || [])
// 默认地址
setAddress(data.find(item => item.isDefault))
})
.catch(() => {
Taro.showToast({
title: '获取地址失败',
icon: 'error'
});
})
}
const onDefault = async (item: ShopUserAddress) => {
if (address) {
await updateShopUserAddress({
...address,
isDefault: false
})
}
await updateShopUserAddress({
id: item.id,
isDefault: true
})
Taro.showToast({
title: '设置成功',
icon: 'success'
});
reload();
}
const onDel = async (id?: number) => {
await removeShopUserAddress(id)
Taro.showToast({
title: '删除成功',
icon: 'success'
});
reload();
}
const selectAddress = async (item: ShopUserAddress) => {
if (address) {
await updateShopUserAddress({
...address,
isDefault: false
})
}
await updateShopUserAddress({
id: item.id,
isDefault: true
})
setTimeout(() => {
Taro.navigateBack()
},500)
}
useDidShow(() => {
reload()
});
if (list.length == 0) {
return (
<ConfigProvider>
<div className={'h-full flex flex-col justify-center items-center'} style={{
height: 'calc(100vh - 300px)',
}}>
<Empty
style={{
backgroundColor: 'transparent'
}}
description="您还没有地址哦"
/>
<Space>
<Button onClick={() => Taro.navigateTo({url: '/user/address/add'})}></Button>
<Button type="success" fill="dashed"
onClick={() => Taro.navigateTo({url: '/user/address/wxAddress'})}></Button>
</Space>
</div>
</ConfigProvider>
)
}
return (
<>
<CellGroup>
<Cell
onClick={() => Taro.navigateTo({url: '/user/address/wxAddress'})}
>
<div className={'flex justify-between items-center w-full'}>
<div className={'flex items-center gap-3'}>
<Dongdong className={'text-green-600'}/>
<div></div>
</div>
<ArrowRight className={'text-gray-400'}/>
</div>
</Cell>
</CellGroup>
{list.map((item, _) => (
<Cell.Group>
<Cell className={'flex flex-col gap-1'} onClick={() => selectAddress(item)}>
<View>
<View className={'font-medium text-sm'}>{item.name} {item.phone}</View>
</View>
<View className={'text-xs'}>
{item.province} {item.city} {item.region} {item.address}
</View>
</Cell>
<Cell
align="center"
title={
<View className={'flex items-center gap-1'} onClick={() => onDefault(item)}>
{item.isDefault ? <Checked className={'text-green-600'} size={16}/> : <CheckNormal size={16}/>}
<View className={'text-gray-400'}></View>
</View>
}
extra={
<>
<View className={'text-gray-400'} onClick={() => onDel(item.id)}>
</View>
<Divider direction={'vertical'}/>
<View className={'text-gray-400'}
onClick={() => Taro.navigateTo({url: '/user/address/add?id=' + item.id})}>
</View>
</>
}
/>
</Cell.Group>
))}
</>
);
};
export default Address;

View File

@@ -1,164 +0,0 @@
# hideInTable 表格列优化方案总结
## 🎯 方案概述
使用 `hideInTable: true` 属性来控制表格列的默认显示,这是一个既保留完整功能又保持界面简洁的最佳方案。
## ✨ 核心优势
### 1. 🔄 **完整性 + 简洁性**
- **生成所有字段**:不丢失任何数据库字段信息
- **默认简洁**:只显示最重要的核心字段
- **用户可控**:通过列设置功能显示需要的字段
### 2. 🎛️ **灵活的用户体验**
- **开箱即用**:默认显示最常用的字段
- **按需扩展**:用户可以勾选显示更多字段
- **个性化**:不同用户可以有不同的列显示偏好
### 3. 🚀 **技术优势**
- **标准化**:使用 Ant Design 标准的 `hideInTable` 属性
- **兼容性好**:与现有表格组件完美兼容
- **维护简单**:不需要复杂的逻辑,只需要简单的布尔值控制
## 📋 实现细节
### 核心字段判断规则
```javascript
// 以下字段默认显示最多6个
- 主键字段 (id)
- 名称字段 (name)
- 标题字段 (title)
- 编码字段 (code)
- 状态字段 (status)
- 排序字段 (sort)
- 创建时间 (createTime)
```
### 隐藏字段规则
```javascript
// 以下字段默认隐藏 (hideInTable: true)
- 更新时间 (updateTime)
- 创建人 (createBy)
- 更新人 (updateBy)
- 备注 (remark)
- 描述 (description)
- 内容 (content)
- 其他非核心业务字段
```
## 🔧 生成的代码示例
```javascript
const columns = ref<ColumnItem[]>([
{
title: 'ID',
dataIndex: 'id',
key: 'id',
width: 90,
align: 'center',
// 核心字段,默认显示
},
{
title: '文章标题',
dataIndex: 'title',
key: 'title',
width: 150,
align: 'center',
ellipsis: true,
// 核心字段,默认显示
},
{
title: '状态',
dataIndex: 'status',
key: 'status',
width: 80,
align: 'center',
// 核心字段,默认显示
},
{
title: '创建时间',
dataIndex: 'createTime',
key: 'createTime',
width: 120,
align: 'center',
sorter: true,
ellipsis: true,
customRender: ({ text }) => toDateString(text, 'yyyy-MM-dd'),
// 核心字段,默认显示
},
{
title: '创建人',
dataIndex: 'createBy',
key: 'createBy',
width: 120,
align: 'center',
hideInTable: true, // 非核心字段,默认隐藏
},
{
title: '文章内容',
dataIndex: 'content',
key: 'content',
width: 200,
align: 'center',
ellipsis: true,
hideInTable: true, // 长文本字段,默认隐藏
},
{
title: '操作',
key: 'action',
width: 180,
fixed: 'right',
align: 'center',
hideInSetting: true, // 操作列不允许隐藏
}
]);
```
## 🎨 用户界面效果
### 默认显示
```
┌────┬──────────┬────┬──────────┬────────┐
│ ID │ 文章标题 │状态│ 创建时间 │ 操作 │
├────┼──────────┼────┼──────────┼────────┤
│ 1 │ 技术文章 │启用│2024-01-01│修改|删除│
│ 2 │ 产品介绍 │禁用│2024-01-02│修改|删除│
└────┴──────────┴────┴──────────┴────────┘
```
### 用户勾选更多列后
```
┌────┬──────────┬────┬────────┬──────────┬──────────┬────────┐
│ ID │ 文章标题 │状态│ 创建人 │ 创建时间 │ 文章内容 │ 操作 │
├────┼──────────┼────┼────────┼──────────┼──────────┼────────┤
│ 1 │ 技术文章 │启用│ 张三 │2024-01-01│ 这是... │修改|删除│
│ 2 │ 产品介绍 │禁用│ 李四 │2024-01-02│ 产品... │修改|删除│
└────┴──────────┴────┴────────┴──────────┴──────────┴────────┘
```
## 🔄 与其他方案对比
| 方案 | 完整性 | 简洁性 | 用户控制 | 维护成本 | 推荐度 |
|------|--------|--------|----------|----------|--------|
| 显示所有字段 | ✅ | ❌ | ❌ | 低 | ⭐⭐ |
| 过滤显示部分字段 | ❌ | ✅ | ❌ | 中 | ⭐⭐⭐ |
| **hideInTable方案** | ✅ | ✅ | ✅ | 低 | ⭐⭐⭐⭐⭐ |
## 📝 使用建议
1. **保持核心字段数量在6个以内**:确保表格在标准屏幕上显示良好
2. **优先显示业务关键字段**:如名称、状态、时间等
3. **长文本字段默认隐藏**:如备注、描述、内容等
4. **提供列设置入口**:让用户可以方便地自定义显示列
5. **考虑移动端适配**:在小屏幕上进一步减少默认显示列
## 🎉 总结
`hideInTable` 方案是表格列显示的最佳实践,它完美平衡了:
- **开发效率**:自动生成,无需手动配置
- **用户体验**:默认简洁,按需扩展
- **功能完整**:不丢失任何字段信息
- **维护成本**:标准化实现,易于维护
这个方案让生成的表格既专业又实用,是代码生成器的理想选择!