完成代码生成器~

This commit is contained in:
YunaiV 2021-02-13 13:40:29 +08:00
parent 95757db6be
commit 2f66829a41
14 changed files with 1147 additions and 1329 deletions

View File

@ -1,30 +0,0 @@
package com.ruoyi.generator.config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.PropertySource;
import org.springframework.stereotype.Component;
/**
* 读取代码生成相关配置
*
* @author ruoyi
*/
@Component
@ConfigurationProperties(prefix = "gen")
@PropertySource(value = { "classpath:generator.yml" })
public class GenConfig {
/** 作者 */
public static String author;
/** 生成包路径 */
public static String packageName;
/** 自动去除表前缀默认是false */
public static boolean autoRemovePre;
/** 表前缀(类名不会包含表前缀) */
public static String tablePrefix;
}

View File

@ -1,45 +0,0 @@
package com.ruoyi.generator.domain;
import java.util.List;
import javax.validation.Valid;
import javax.validation.constraints.NotBlank;
import org.apache.commons.lang3.ArrayUtils;
import com.ruoyi.common.constant.GenConstants;
import com.ruoyi.common.core.domain.BaseEntity;
import com.ruoyi.common.utils.StringUtils;
/**
* 业务表 gen_table
*
* @author ruoyi
*/
public class GenTable extends BaseEntity {
/**
* 生成包路径
*/
@NotBlank(message = "生成包路径不能为空")
private String packageName;
/**
* 其它生成选项
*/
private String options;
/**
* 树编码字段
*/
private String treeCode;
/**
* 树父编码字段
*/
private String treeParentCode;
/**
* 树名称字段
*/
private String treeName;
}

View File

@ -1,265 +0,0 @@
package com.ruoyi.generator.util;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import org.apache.velocity.VelocityContext;
import com.alibaba.fastjson.JSONObject;
import com.ruoyi.common.constant.GenConstants;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.generator.domain.GenTable;
import com.ruoyi.generator.domain.GenTableColumn;
/**
* 模板处理工具类
*
* @author ruoyi
*/
public class VelocityUtils {
/**
* 默认上级菜单系统工具
*/
private static final String DEFAULT_PARENT_MENU_ID = "3";
/**
* 设置模板变量信息
*
* @return 模板列表
*/
public static VelocityContext prepareContext(GenTable genTable) {
String moduleName = genTable.getModuleName();
String businessName = genTable.getBusinessName();
String packageName = genTable.getPackageName();
String tplCategory = genTable.getTplCategory();
String functionName = genTable.getFunctionName();
VelocityContext velocityContext = new VelocityContext();
velocityContext.put("tplCategory", genTable.getTplCategory());
velocityContext.put("tableName", genTable.getTableName());
velocityContext.put("functionName", StringUtils.isNotEmpty(functionName) ? functionName : "【请填写功能名称】");
velocityContext.put("ClassName", genTable.getClassName());
velocityContext.put("className", StringUtils.uncapitalize(genTable.getClassName()));
velocityContext.put("moduleName", genTable.getModuleName());
velocityContext.put("BusinessName", StringUtils.capitalize(genTable.getBusinessName()));
velocityContext.put("businessName", genTable.getBusinessName());
velocityContext.put("basePackage", getPackagePrefix(packageName));
velocityContext.put("packageName", packageName);
velocityContext.put("author", genTable.getFunctionAuthor());
velocityContext.put("datetime", DateUtils.getDate());
velocityContext.put("pkColumn", genTable.getPkColumn());
velocityContext.put("importList", getImportList(genTable.getColumns()));
velocityContext.put("permissionPrefix", getPermissionPrefix(moduleName, businessName));
velocityContext.put("columns", genTable.getColumns());
velocityContext.put("table", genTable);
setMenuVelocityContext(velocityContext, genTable);
if (GenConstants.TPL_TREE.equals(tplCategory)) {
setTreeVelocityContext(velocityContext, genTable);
}
return velocityContext;
}
public static void setTreeVelocityContext(VelocityContext context, GenTable genTable) {
String options = genTable.getOptions();
JSONObject paramsObj = JSONObject.parseObject(options);
String treeCode = getTreecode(paramsObj);
String treeParentCode = getTreeParentCode(paramsObj);
String treeName = getTreeName(paramsObj);
context.put("treeCode", treeCode);
context.put("treeParentCode", treeParentCode);
context.put("treeName", treeName);
context.put("expandColumn", getExpandColumn(genTable));
if (paramsObj.containsKey(GenConstants.TREE_PARENT_CODE)) {
context.put("tree_parent_code", paramsObj.getString(GenConstants.TREE_PARENT_CODE));
}
if (paramsObj.containsKey(GenConstants.TREE_NAME)) {
context.put("tree_name", paramsObj.getString(GenConstants.TREE_NAME));
}
}
/**
* 获取模板信息
*
* @return 模板列表
*/
public static List<String> getTemplateList(String tplCategory) {
List<String> templates = new ArrayList<String>();
templates.add("vm/java/domain.java.vm");
templates.add("vm/java/mapper.java.vm");
templates.add("vm/java/service.java.vm");
templates.add("vm/java/serviceImpl.java.vm");
templates.add("vm/java/controller.java.vm");
templates.add("vm/xml/mapper.xml.vm");
templates.add("vm/sql/sql.vm");
templates.add("vm/js/api.js.vm");
if (GenConstants.TPL_CRUD.equals(tplCategory)) {
templates.add("vm/vue/index.vue.vm");
} else if (GenConstants.TPL_TREE.equals(tplCategory)) {
templates.add("vm/vue/index-tree.vue.vm");
}
return templates;
}
/**
* 获取文件名
*/
public static String getFileName(String template, GenTable genTable) {
// 文件名称
String fileName = "";
// 包路径
String packageName = genTable.getPackageName();
// 模块名
String moduleName = genTable.getModuleName();
// 大写类名
String className = genTable.getClassName();
// 业务名称
String businessName = genTable.getBusinessName();
String javaPath = PROJECT_PATH + "/" + StringUtils.replace(packageName, ".", "/");
String mybatisPath = MYBATIS_PATH + "/" + moduleName;
String vuePath = "vue";
if (template.contains("domain.java.vm")) {
fileName = StringUtils.format("{}/domain/{}.java", javaPath, className);
} else if (template.contains("mapper.java.vm")) {
fileName = StringUtils.format("{}/mapper/{}Mapper.java", javaPath, className);
} else if (template.contains("service.java.vm")) {
fileName = StringUtils.format("{}/service/I{}Service.java", javaPath, className);
} else if (template.contains("serviceImpl.java.vm")) {
fileName = StringUtils.format("{}/service/impl/{}ServiceImpl.java", javaPath, className);
} else if (template.contains("controller.java.vm")) {
fileName = StringUtils.format("{}/controller/{}Controller.java", javaPath, className);
} else if (template.contains("mapper.xml.vm")) {
fileName = StringUtils.format("{}/{}Mapper.xml", mybatisPath, className);
} else if (template.contains("sql.vm")) {
fileName = businessName + "Menu.sql";
} else if (template.contains("api.js.vm")) {
fileName = StringUtils.format("{}/api/{}/{}.js", 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);
}
return fileName;
}
/**
* 获取包前缀
*
* @param packageName 包名称
* @return 包前缀名称
*/
public static String getPackagePrefix(String packageName) {
int lastIndex = packageName.lastIndexOf(".");
String basePackage = StringUtils.substring(packageName, 0, lastIndex);
return basePackage;
}
/**
* 根据列类型获取导入包
*
* @param columns 列集合
* @return 返回需要导入的包列表
*/
public static HashSet<String> getImportList(List<GenTableColumn> columns) {
HashSet<String> importList = new HashSet<String>();
for (GenTableColumn column : columns) {
if (!column.isSuperColumn() && GenConstants.TYPE_DATE.equals(column.getJavaType())) {
importList.add("java.util.Date");
importList.add("com.fasterxml.jackson.annotation.JsonFormat");
} else if (!column.isSuperColumn() && GenConstants.TYPE_BIGDECIMAL.equals(column.getJavaType())) {
importList.add("java.math.BigDecimal");
}
}
return importList;
}
/**
* 获取权限前缀
*
* @param moduleName 模块名称
* @param businessName 业务名称
* @return 返回权限前缀
*/
public static String getPermissionPrefix(String moduleName, String businessName) {
return StringUtils.format("{}:{}", moduleName, businessName);
}
/**
* 获取上级菜单ID字段
*
* @param paramsObj 生成其他选项
* @return 上级菜单ID字段
*/
public static String getParentMenuId(JSONObject paramsObj) {
if (StringUtils.isNotEmpty(paramsObj) && paramsObj.containsKey(GenConstants.PARENT_MENU_ID)) {
return paramsObj.getString(GenConstants.PARENT_MENU_ID);
}
return DEFAULT_PARENT_MENU_ID;
}
/**
* 获取树编码
*
* @param paramsObj 生成其他选项
* @return 树编码
*/
public static String getTreecode(JSONObject paramsObj) {
if (paramsObj.containsKey(GenConstants.TREE_CODE)) {
return StringUtils.toCamelCase(paramsObj.getString(GenConstants.TREE_CODE));
}
return StringUtils.EMPTY;
}
/**
* 获取树父编码
*
* @param paramsObj 生成其他选项
* @return 树父编码
*/
public static String getTreeParentCode(JSONObject paramsObj) {
if (paramsObj.containsKey(GenConstants.TREE_PARENT_CODE)) {
return StringUtils.toCamelCase(paramsObj.getString(GenConstants.TREE_PARENT_CODE));
}
return StringUtils.EMPTY;
}
/**
* 获取树名称
*
* @param paramsObj 生成其他选项
* @return 树名称
*/
public static String getTreeName(JSONObject paramsObj) {
if (paramsObj.containsKey(GenConstants.TREE_NAME)) {
return StringUtils.toCamelCase(paramsObj.getString(GenConstants.TREE_NAME));
}
return StringUtils.EMPTY;
}
/**
* 获取需要在哪一列上面显示展开按钮
*
* @param genTable 业务表对象
* @return 展开按钮列序号
*/
public static int getExpandColumn(GenTable genTable) {
String options = genTable.getOptions();
JSONObject paramsObj = JSONObject.parseObject(options);
String treeName = paramsObj.getString(GenConstants.TREE_NAME);
int num = 0;
for (GenTableColumn column : genTable.getColumns()) {
if (column.isList()) {
num++;
String columnName = column.getColumnName();
if (columnName.equals(treeName)) {
break;
}
}
}
return num;
}
}

View File

@ -1,10 +0,0 @@
# 代码生成
gen:
# 作者
author: ruoyi
# 默认生成包路径 system 需改成自己的模块名称 如 system monitor tool
packageName: com.ruoyi.system
# 自动去除表前缀默认是false
autoRemovePre: false
# 表前缀(生成类名不会包含表前缀,多个用逗号分隔)
tablePrefix: sys_

View File

@ -27,13 +27,25 @@ export function updateCodegen(data) {
} }
// 基于数据库的表结构,同步数据库的表和字段定义 // 基于数据库的表结构,同步数据库的表和字段定义
export function syncCodegen(tableId) { export function syncCodegenFromDB(tableId) {
return request({ return request({
url: '/tool/codegen/sync?tableId=' + tableId, url: '/tool/codegen/sync-from-db?tableId=' + tableId,
method: 'put' method: 'put'
}) })
} }
// 基于 SQL 建表语句,同步数据库的表和字段定义
export function syncCodegenFromSQL(tableId, sql) {
return request({
url: '/tool/codegen/sync-from-sql?tableId=' + tableId,
method: 'put',
headers:{
'Content-type': 'application/x-www-form-urlencoded'
},
data: 'tableId=' + tableId + "&sql=" + sql,
})
}
// 预览生成代码 // 预览生成代码
export function previewCodegen(tableId) { export function previewCodegen(tableId) {
return request({ return request({
@ -61,9 +73,9 @@ export function getSchemaTableList(query) {
} }
// 基于数据库的表结构,创建代码生成器的表定义 // 基于数据库的表结构,创建代码生成器的表定义
export function createCodegenList(tableNames) { export function createCodegenListFromDB(tableNames) {
return request({ return request({
url: '/tool/codegen/create-list', url: '/tool/codegen/create-list-from-db',
method: 'post', method: 'post',
headers:{ headers:{
'Content-type': 'application/x-www-form-urlencoded' 'Content-type': 'application/x-www-form-urlencoded'
@ -72,6 +84,18 @@ export function createCodegenList(tableNames) {
}) })
} }
// 基于 SQL 建表语句,创建代码生成器的表定义
export function createCodegenListFromSQL(data) {
return request({
url: '/tool/codegen/create-list-from-sql',
method: 'post',
headers:{
'Content-type': 'application/x-www-form-urlencoded'
},
data: 'sql=' + data.sql,
})
}
// 删除数据库的表和字段定义 // 删除数据库的表和字段定义
export function deleteCodegen(tableId) { export function deleteCodegen(tableId) {
return request({ return request({

View File

@ -107,13 +107,13 @@ export const constantRoutes = [
] ]
}, },
{ {
path: '/gen', path: '/codegen',
component: Layout, component: Layout,
hidden: true, hidden: true,
children: [ children: [
{ {
path: 'edit/:tableId(\\d+)', path: 'edit/:tableId(\\d+)',
component: (resolve) => require(['@/views/tool/gen/editTable'], resolve), component: (resolve) => require(['@/views/tool/codegen/editTable'], resolve),
name: 'GenEdit', name: 'GenEdit',
meta: { title: '修改生成配置' } meta: { title: '修改生成配置' }
} }

View File

@ -1,61 +1,61 @@
<template> <template>
<el-form ref="basicInfoForm" :model="info" :rules="rules" label-width="150px"> <el-form ref="basicInfoForm" :model="info" :rules="rules" label-width="150px">
<el-row> <el-row>
<el-col :span="12"> <el-col :span="12">
<el-form-item label="表名称" prop="tableName"> <el-form-item label="表名称" prop="tableName">
<el-input placeholder="请输入仓库名称" v-model="info.tableName" /> <el-input placeholder="请输入仓库名称" v-model="info.tableName" />
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="12"> <el-col :span="12">
<el-form-item label="表描述" prop="tableComment"> <el-form-item label="表描述" prop="tableComment">
<el-input placeholder="请输入" v-model="info.tableComment" /> <el-input placeholder="请输入" v-model="info.tableComment" />
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="12"> <el-col :span="12">
<el-form-item label="实体类名称" prop="className"> <el-form-item label="实体类名称" prop="className">
<el-input placeholder="请输入" v-model="info.className" /> <el-input placeholder="请输入" v-model="info.className" />
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="12"> <el-col :span="12">
<el-form-item label="作者" prop="author"> <el-form-item label="作者" prop="author">
<el-input placeholder="请输入" v-model="info.author" /> <el-input placeholder="请输入" v-model="info.author" />
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="24"> <el-col :span="24">
<el-form-item label="备注" prop="remark"> <el-form-item label="备注" prop="remark">
<el-input type="textarea" :rows="3" v-model="info.remark"></el-input> <el-input type="textarea" :rows="3" v-model="info.remark"></el-input>
</el-form-item> </el-form-item>
</el-col> </el-col>
</el-row> </el-row>
</el-form> </el-form>
</template> </template>
<script> <script>
export default { export default {
name: "BasicInfoForm", name: "BasicInfoForm",
props: { props: {
info: { info: {
type: Object, type: Object,
default: null default: null
} }
}, },
data() { data() {
return { return {
rules: { rules: {
tableName: [ tableName: [
{ required: true, message: "请输入表名称", trigger: "blur" } { required: true, message: "请输入表名称", trigger: "blur" }
], ],
tableComment: [ tableComment: [
{ required: true, message: "请输入表描述", trigger: "blur" } { required: true, message: "请输入表描述", trigger: "blur" }
], ],
className: [ className: [
{ required: true, message: "请输入实体类名称", trigger: "blur" } { required: true, message: "请输入实体类名称", trigger: "blur" }
], ],
author: [ author: [
{ required: true, message: "请输入作者", trigger: "blur" } { required: true, message: "请输入作者", trigger: "blur" }
] ]
} }
}; };
} }
}; };
</script> </script>

View File

@ -1,233 +1,232 @@
<template> <template>
<el-card> <el-card>
<el-tabs v-model="activeName"> <el-tabs v-model="activeName">
<el-tab-pane label="基本信息" name="basic"> <el-tab-pane label="基本信息" name="basic">
<basic-info-form ref="basicInfo" :info="table" /> <basic-info-form ref="basicInfo" :info="table" />
</el-tab-pane> </el-tab-pane>
<el-tab-pane label="字段信息" name="cloum"> <el-tab-pane label="字段信息" name="cloum">
<el-table ref="dragTable" :data="columns" row-key="columnId" :max-height="tableHeight"> <el-table ref="dragTable" :data="columns" row-key="columnId" :max-height="tableHeight">
<el-table-column <el-table-column
label="字段列名" label="字段列名"
prop="columnName" prop="columnName"
min-width="10%" min-width="10%"
:show-overflow-tooltip="true" :show-overflow-tooltip="true"
/> />
<el-table-column label="字段描述" min-width="10%"> <el-table-column label="字段描述" min-width="10%">
<template slot-scope="scope"> <template slot-scope="scope">
<el-input v-model="scope.row.columnComment"></el-input> <el-input v-model="scope.row.columnComment"></el-input>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column <el-table-column
label="物理类型" label="物理类型"
prop="columnType" prop="columnType"
min-width="10%" min-width="10%"
:show-overflow-tooltip="true" :show-overflow-tooltip="true"
/> />
<el-table-column label="Java类型" min-width="11%"> <el-table-column label="Java类型" min-width="11%">
<template slot-scope="scope"> <template slot-scope="scope">
<el-select v-model="scope.row.javaType"> <el-select v-model="scope.row.javaType">
<el-option label="Long" value="Long" /> <el-option label="Long" value="Long" />
<el-option label="String" value="String" /> <el-option label="String" value="String" />
<el-option label="Integer" value="Integer" /> <el-option label="Integer" value="Integer" />
<el-option label="Double" value="Double" /> <el-option label="Double" value="Double" />
<el-option label="BigDecimal" value="BigDecimal" /> <el-option label="BigDecimal" value="BigDecimal" />
<el-option label="Date" value="Date" /> <el-option label="Date" value="Date" />
</el-select> </el-select>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="java属性" min-width="10%"> <el-table-column label="java属性" min-width="10%">
<template slot-scope="scope"> <template slot-scope="scope">
<el-input v-model="scope.row.javaField"></el-input> <el-input v-model="scope.row.javaField"></el-input>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="插入" min-width="4%"> <el-table-column label="插入" min-width="4%">
<template slot-scope="scope"> <template slot-scope="scope">
<el-checkbox true-label="true" false-label="false" v-model="scope.row.createOperation"></el-checkbox> <el-checkbox true-label="true" false-label="false" v-model="scope.row.createOperation"></el-checkbox>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="编辑" min-width="4%"> <el-table-column label="编辑" min-width="4%">
<template slot-scope="scope"> <template slot-scope="scope">
<el-checkbox true-label="true" false-label="false" v-model="scope.row.updateOperation"></el-checkbox> <el-checkbox true-label="true" false-label="false" v-model="scope.row.updateOperation"></el-checkbox>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="列表" min-width="4%"> <el-table-column label="列表" min-width="4%">
<template slot-scope="scope"> <template slot-scope="scope">
<el-checkbox true-label="true" false-label="false" v-model="scope.row.listOperationResult"></el-checkbox> <el-checkbox true-label="true" false-label="false" v-model="scope.row.listOperationResult"></el-checkbox>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="查询" min-width="4%"> <el-table-column label="查询" min-width="4%">
<template slot-scope="scope"> <template slot-scope="scope">
<el-checkbox true-label="true" false-label="false" v-model="scope.row.listOperation"></el-checkbox> <el-checkbox true-label="true" false-label="false" v-model="scope.row.listOperation"></el-checkbox>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="查询方式" min-width="10%"> <el-table-column label="查询方式" min-width="10%">
<template slot-scope="scope"> <template slot-scope="scope">
<el-select v-model="scope.row.listOperationCondition"> <el-select v-model="scope.row.listOperationCondition">
<el-option label="=" value="=" /> <el-option label="=" value="=" />
<el-option label="!=" value="!=" /> <el-option label="!=" value="!=" />
<el-option label=">" value=">" /> <el-option label=">" value=">" />
<el-option label=">=" value=">=" /> <el-option label=">=" value=">=" />
<el-option label="<" value="<>" /> <el-option label="<" value="<>" />
<el-option label="<=" value="<=" /> <el-option label="<=" value="<=" />
<el-option label="LIKE" value="LIKE" /> <el-option label="LIKE" value="LIKE" />
<el-option label="BETWEEN" value="BETWEEN" /> <el-option label="BETWEEN" value="BETWEEN" />
</el-select> </el-select>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="允许空" min-width="5%"> <el-table-column label="允许空" min-width="5%">
<template slot-scope="scope"> <template slot-scope="scope">
<el-checkbox true-label="true" false-label="false" v-model="scope.row.nullable"></el-checkbox> <el-checkbox true-label="true" false-label="false" v-model="scope.row.nullable"></el-checkbox>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="显示类型" min-width="12%"> <el-table-column label="显示类型" min-width="12%">
<template slot-scope="scope"> <template slot-scope="scope">
<el-select v-model="scope.row.htmlType"> <el-select v-model="scope.row.htmlType">
<el-option label="文本框" value="input" /> <el-option label="文本框" value="input" />
<el-option label="文本域" value="textarea" /> <el-option label="文本域" value="textarea" />
<el-option label="下拉框" value="select" /> <el-option label="下拉框" value="select" />
<el-option label="单选框" value="radio" /> <el-option label="单选框" value="radio" />
<el-option label="复选框" value="checkbox" /> <el-option label="复选框" value="checkbox" />
<el-option label="日期控件" value="datetime" /> <el-option label="日期控件" value="datetime" />
<el-option label="图片上传" value="imageUpload" /> <el-option label="图片上传" value="imageUpload" />
<el-option label="文件上传" value="fileUpload" /> <el-option label="文件上传" value="fileUpload" />
<el-option label="富文本控件" value="editor" /> <el-option label="富文本控件" value="editor" />
</el-select> </el-select>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="字典类型" min-width="12%"> <el-table-column label="字典类型" min-width="12%">
<template slot-scope="scope"> <template slot-scope="scope">
<el-select v-model="scope.row.dictType" clearable filterable placeholder="请选择"> <el-select v-model="scope.row.dictType" clearable filterable placeholder="请选择">
<el-option <el-option
v-for="dict in dictOptions" v-for="dict in dictOptions"
:key="dict.id" :key="dict.id"
:label="dict.name" :label="dict.name"
:value="dict.type" :value="dict.type"
/> />
</el-select> </el-select>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="示例" min-width="10%"> <el-table-column label="示例" min-width="10%">
<template slot-scope="scope"> <template slot-scope="scope">
<el-input v-model="scope.row.example"></el-input> <el-input v-model="scope.row.example"></el-input>
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
</el-tab-pane> </el-tab-pane>
<el-tab-pane label="生成信息" name="genInfo"> <el-tab-pane label="生成信息" name="genInfo">
<gen-info-form ref="genInfo" :info="table" :tables="tables" :menus="menus"/> <gen-info-form ref="genInfo" :info="table" :tables="tables" :menus="menus"/>
</el-tab-pane> </el-tab-pane>
</el-tabs> </el-tabs>
<el-form label-width="100px"> <el-form label-width="100px">
<el-form-item style="text-align: center;margin-left:-100px;margin-top:10px;"> <el-form-item style="text-align: center;margin-left:-100px;margin-top:10px;">
<el-button type="primary" @click="submitForm()">提交</el-button> <el-button type="primary" @click="submitForm()">提交</el-button>
<el-button @click="close()">返回</el-button> <el-button @click="close()">返回</el-button>
</el-form-item> </el-form-item>
</el-form> </el-form>
</el-card> </el-card>
</template> </template>
<script> <script>
import { getCodegenDetail, updateCodegen } from "@/api/tool/codegen"; import { getCodegenDetail, updateCodegen } from "@/api/tool/codegen";
import { listAllSimple as listAllSimpleDictType } from "@/api/system/dict/type"; import { listAllSimple as listAllSimpleDictType } from "@/api/system/dict/type";
import { listMenu as getMenuTreeselect } from "@/api/system/menu"; import { listSimpleMenus } from "@/api/system/menu";
import { listSimpleMenus } from "@/api/system/menu"; import basicInfoForm from "./basicInfoForm";
import basicInfoForm from "./basicInfoForm"; import genInfoForm from "./genInfoForm";
import genInfoForm from "./genInfoForm"; import Sortable from 'sortablejs'
import Sortable from 'sortablejs'
export default {
export default { name: "GenEdit",
name: "GenEdit", components: {
components: { basicInfoForm,
basicInfoForm, genInfoForm
genInfoForm },
}, data() {
data() { return {
return { // name
// name activeName: "cloum",
activeName: "cloum", //
// tableHeight: document.documentElement.scrollHeight - 245 + "px",
tableHeight: document.documentElement.scrollHeight - 245 + "px", //
// tables: [],
tables: [], //
// columns: [],
columns: [], //
// dictOptions: [],
dictOptions: [], //
// menus: [],
menus: [], //
// table: {}
table: {} };
}; },
}, created() {
created() { const tableId = this.$route.params && this.$route.params.tableId;
const tableId = this.$route.params && this.$route.params.tableId; if (tableId) {
if (tableId) { //
// getCodegenDetail(tableId).then(res => {
getCodegenDetail(tableId).then(res => { this.table = res.data.table;
this.table = res.data.table; this.columns = res.data.columns;
this.columns = res.data.columns; });
}); /** 查询字典下拉列表 */
/** 查询字典下拉列表 */ listAllSimpleDictType().then(response => {
listAllSimpleDictType().then(response => { this.dictOptions = response.data;
this.dictOptions = response.data; });
}); /** 查询菜单下拉列表 */
/** 查询菜单下拉列表 */ listSimpleMenus().then(response => {
listSimpleMenus().then(response => { this.menus = [];
this.menus = []; this.menus.push(...this.handleTree(response.data, "id"));
this.menus.push(...this.handleTree(response.data, "id")); });
}); }
} },
}, methods: {
methods: { /** 提交按钮 */
/** 提交按钮 */ submitForm() {
submitForm() { const basicForm = this.$refs.basicInfo.$refs.basicInfoForm;
const basicForm = this.$refs.basicInfo.$refs.basicInfoForm; const genForm = this.$refs.genInfo.$refs.genInfoForm;
const genForm = this.$refs.genInfo.$refs.genInfoForm; Promise.all([basicForm, genForm].map(this.getFormPromise)).then(res => {
Promise.all([basicForm, genForm].map(this.getFormPromise)).then(res => { const validateResult = res.every(item => !!item);
const validateResult = res.every(item => !!item); if (validateResult) {
if (validateResult) { const genTable = {};
const genTable = {}; genTable.table = Object.assign({}, basicForm.model, genForm.model);
genTable.table = Object.assign({}, basicForm.model, genForm.model); genTable.columns = this.columns;
genTable.columns = this.columns; genTable.params = {
genTable.params = { treeCode: genTable.treeCode,
treeCode: genTable.treeCode, treeName: genTable.treeName,
treeName: genTable.treeName, treeParentCode: genTable.treeParentCode,
treeParentCode: genTable.treeParentCode, parentMenuId: genTable.parentMenuId
parentMenuId: genTable.parentMenuId };
}; updateCodegen(genTable).then(res => {
updateCodegen(genTable).then(res => { this.msgSuccess("修改成功!");
this.msgSuccess("修改成功!"); this.close();
this.close(); });
}); } else {
} else { this.msgError("表单校验未通过,请重新检查提交内容");
this.msgError("表单校验未通过,请重新检查提交内容"); }
} });
}); },
}, getFormPromise(form) {
getFormPromise(form) { return new Promise(resolve => {
return new Promise(resolve => { form.validate(res => {
form.validate(res => { resolve(res);
resolve(res); });
}); });
}); },
}, /** 关闭按钮 */
/** 关闭按钮 */ close() {
close() { this.$store.dispatch("tagsView/delView", this.$route);
this.$store.dispatch("tagsView/delView", this.$route); this.$router.push({ path: "/tool/gen", query: { t: Date.now()}})
this.$router.push({ path: "/tool/gen", query: { t: Date.now()}}) }
} },
}, mounted() {
mounted() { const el = this.$refs.dragTable.$el.querySelectorAll(".el-table__body-wrapper > table > tbody")[0];
const el = this.$refs.dragTable.$el.querySelectorAll(".el-table__body-wrapper > table > tbody")[0]; const sortable = Sortable.create(el, {
const sortable = Sortable.create(el, { handle: ".allowDrag",
handle: ".allowDrag", onEnd: evt => {
onEnd: evt => { const targetRow = this.columns.splice(evt.oldIndex, 1)[0];
const targetRow = this.columns.splice(evt.oldIndex, 1)[0]; this.columns.splice(evt.newIndex, 0, targetRow);
this.columns.splice(evt.newIndex, 0, targetRow); for (let index in this.columns) {
for (let index in this.columns) { this.columns[index].sort = parseInt(index) + 1;
this.columns[index].sort = parseInt(index) + 1; }
} }
} });
}); }
} };
}; </script>
</script>

View File

@ -1,319 +1,319 @@
<template> <template>
<el-form ref="genInfoForm" :model="info" :rules="rules" label-width="150px"> <el-form ref="genInfoForm" :model="info" :rules="rules" label-width="150px">
<el-row> <el-row>
<el-col :span="12"> <el-col :span="12">
<el-form-item prop="tplCategory"> <el-form-item prop="tplCategory">
<span slot="label">生成模板</span> <span slot="label">生成模板</span>
<el-select v-model="info.templateType" @change="tplSelectChange"> <el-select v-model="info.templateType" @change="tplSelectChange">
<el-option <el-option
v-for="dict in this.getDictDatas(DICT_TYPE.TOOL_CODEGEN_TEMPLATE_TYPE)" v-for="dict in this.getDictDatas(DICT_TYPE.TOOL_CODEGEN_TEMPLATE_TYPE)"
:key="parseInt(dict.value)" :key="parseInt(dict.value)"
:label="dict.label" :label="dict.label"
:value="parseInt(dict.value)" :value="parseInt(dict.value)"
/> />
</el-select> </el-select>
</el-form-item> </el-form-item>
</el-col> </el-col>
<!-- <el-col :span="12">--> <!-- <el-col :span="12">-->
<!-- <el-form-item prop="packageName">--> <!-- <el-form-item prop="packageName">-->
<!-- <span slot="label">--> <!-- <span slot="label">-->
<!-- 生成包路径--> <!-- 生成包路径-->
<!-- <el-tooltip content="生成在哪个java包下例如 com.ruoyi.system" placement="top">--> <!-- <el-tooltip content="生成在哪个java包下例如 com.ruoyi.system" placement="top">-->
<!-- <i class="el-icon-question"></i>--> <!-- <i class="el-icon-question"></i>-->
<!-- </el-tooltip>--> <!-- </el-tooltip>-->
<!-- </span>--> <!-- </span>-->
<!-- <el-input v-model="info.packageName" />--> <!-- <el-input v-model="info.packageName" />-->
<!-- </el-form-item>--> <!-- </el-form-item>-->
<!-- </el-col>--> <!-- </el-col>-->
<el-col :span="12"> <el-col :span="12">
<el-form-item prop="moduleName"> <el-form-item prop="moduleName">
<span slot="label"> <span slot="label">
模块名 模块名
<el-tooltip content="模块名,即一级目录,例如 system、infra、tool 等等" placement="top"> <el-tooltip content="模块名,即一级目录,例如 system、infra、tool 等等" placement="top">
<i class="el-icon-question"></i> <i class="el-icon-question"></i>
</el-tooltip> </el-tooltip>
</span> </span>
<el-input v-model="info.moduleName" /> <el-input v-model="info.moduleName" />
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="12"> <el-col :span="12">
<el-form-item prop="businessName"> <el-form-item prop="businessName">
<span slot="label"> <span slot="label">
业务名 业务名
<el-tooltip content="业务名,即二级目录,例如 user、permission、dict 等等" placement="top"> <el-tooltip content="业务名,即二级目录,例如 user、permission、dict 等等" placement="top">
<i class="el-icon-question"></i> <i class="el-icon-question"></i>
</el-tooltip> </el-tooltip>
</span> </span>
<el-input v-model="info.businessName" /> <el-input v-model="info.businessName" />
</el-form-item> </el-form-item>
</el-col> </el-col>
<!-- <el-col :span="12">--> <!-- <el-col :span="12">-->
<!-- <el-form-item prop="businessPackage">--> <!-- <el-form-item prop="businessPackage">-->
<!-- <span slot="label">--> <!-- <span slot="label">-->
<!-- 业务包--> <!-- 业务包-->
<!-- <el-tooltip content="业务包,自定义二级目录。例如说,我们希望将 dictType 和 dictData 归类成 dict 业务" placement="top">--> <!-- <el-tooltip content="业务包,自定义二级目录。例如说,我们希望将 dictType 和 dictData 归类成 dict 业务" placement="top">-->
<!-- <i class="el-icon-question"></i>--> <!-- <i class="el-icon-question"></i>-->
<!-- </el-tooltip>--> <!-- </el-tooltip>-->
<!-- </span>--> <!-- </span>-->
<!-- <el-input v-model="info.businessPackage" />--> <!-- <el-input v-model="info.businessPackage" />-->
<!-- </el-form-item>--> <!-- </el-form-item>-->
<!-- </el-col>--> <!-- </el-col>-->
<el-col :span="12"> <el-col :span="12">
<el-form-item prop="className"> <el-form-item prop="className">
<span slot="label"> <span slot="label">
类名称 类名称
<el-tooltip content="类名称首字母大写例如SysUser、SysMenu、SysDictData 等等" placement="top"> <el-tooltip content="类名称首字母大写例如SysUser、SysMenu、SysDictData 等等" placement="top">
<i class="el-icon-question"></i> <i class="el-icon-question"></i>
</el-tooltip> </el-tooltip>
</span> </span>
<el-input v-model="info.className" /> <el-input v-model="info.className" />
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="12"> <el-col :span="12">
<el-form-item prop="classComment"> <el-form-item prop="classComment">
<span slot="label"> <span slot="label">
类描述 类描述
<el-tooltip content="用作类描述,例如 用户" placement="top"> <el-tooltip content="用作类描述,例如 用户" placement="top">
<i class="el-icon-question"></i> <i class="el-icon-question"></i>
</el-tooltip> </el-tooltip>
</span> </span>
<el-input v-model="info.classComment" /> <el-input v-model="info.classComment" />
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="12"> <el-col :span="12">
<el-form-item> <el-form-item>
<span slot="label"> <span slot="label">
上级菜单 上级菜单
<el-tooltip content="分配到指定菜单下,例如 系统管理" placement="top"> <el-tooltip content="分配到指定菜单下,例如 系统管理" placement="top">
<i class="el-icon-question"></i> <i class="el-icon-question"></i>
</el-tooltip> </el-tooltip>
</span> </span>
<treeselect :append-to-body="true" v-model="info.parentMenuId" :options="menus" <treeselect :append-to-body="true" v-model="info.parentMenuId" :options="menus"
:normalizer="normalizer" :show-count="true" placeholder="请选择系统菜单" /> :normalizer="normalizer" :show-count="true" placeholder="请选择系统菜单" />
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="24" v-if="info.genType == '1'"> <el-col :span="24" v-if="info.genType == '1'">
<el-form-item prop="genPath"> <el-form-item prop="genPath">
<span slot="label"> <span slot="label">
自定义路径 自定义路径
<el-tooltip content="填写磁盘绝对路径若不填写则生成到当前Web项目下" placement="top"> <el-tooltip content="填写磁盘绝对路径若不填写则生成到当前Web项目下" placement="top">
<i class="el-icon-question"></i> <i class="el-icon-question"></i>
</el-tooltip> </el-tooltip>
</span> </span>
<el-input v-model="info.genPath"> <el-input v-model="info.genPath">
<el-dropdown slot="append"> <el-dropdown slot="append">
<el-button type="primary"> <el-button type="primary">
最近路径快速选择 最近路径快速选择
<i class="el-icon-arrow-down el-icon--right"></i> <i class="el-icon-arrow-down el-icon--right"></i>
</el-button> </el-button>
<el-dropdown-menu slot="dropdown"> <el-dropdown-menu slot="dropdown">
<el-dropdown-item @click.native="info.genPath = '/'">恢复默认的生成基础路径</el-dropdown-item> <el-dropdown-item @click.native="info.genPath = '/'">恢复默认的生成基础路径</el-dropdown-item>
</el-dropdown-menu> </el-dropdown-menu>
</el-dropdown> </el-dropdown>
</el-input> </el-input>
</el-form-item> </el-form-item>
</el-col> </el-col>
</el-row> </el-row>
<el-row v-show="info.tplCategory == 'tree'"> <el-row v-show="info.tplCategory == 'tree'">
<h4 class="form-header">其他信息</h4> <h4 class="form-header">其他信息</h4>
<el-col :span="12"> <el-col :span="12">
<el-form-item> <el-form-item>
<span slot="label"> <span slot="label">
树编码字段 树编码字段
<el-tooltip content="树显示的编码字段名, 如dept_id" placement="top"> <el-tooltip content="树显示的编码字段名, 如dept_id" placement="top">
<i class="el-icon-question"></i> <i class="el-icon-question"></i>
</el-tooltip> </el-tooltip>
</span> </span>
<el-select v-model="info.treeCode" placeholder="请选择"> <el-select v-model="info.treeCode" placeholder="请选择">
<el-option <el-option
v-for="(column, index) in info.columns" v-for="(column, index) in info.columns"
:key="index" :key="index"
:label="column.columnName + '' + column.columnComment" :label="column.columnName + '' + column.columnComment"
:value="column.columnName" :value="column.columnName"
></el-option> ></el-option>
</el-select> </el-select>
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="12"> <el-col :span="12">
<el-form-item> <el-form-item>
<span slot="label"> <span slot="label">
树父编码字段 树父编码字段
<el-tooltip content="树显示的父编码字段名, 如parent_Id" placement="top"> <el-tooltip content="树显示的父编码字段名, 如parent_Id" placement="top">
<i class="el-icon-question"></i> <i class="el-icon-question"></i>
</el-tooltip> </el-tooltip>
</span> </span>
<el-select v-model="info.treeParentCode" placeholder="请选择"> <el-select v-model="info.treeParentCode" placeholder="请选择">
<el-option <el-option
v-for="(column, index) in info.columns" v-for="(column, index) in info.columns"
:key="index" :key="index"
:label="column.columnName + '' + column.columnComment" :label="column.columnName + '' + column.columnComment"
:value="column.columnName" :value="column.columnName"
></el-option> ></el-option>
</el-select> </el-select>
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="12"> <el-col :span="12">
<el-form-item> <el-form-item>
<span slot="label"> <span slot="label">
树名称字段 树名称字段
<el-tooltip content="树节点的显示名称字段名, 如dept_name" placement="top"> <el-tooltip content="树节点的显示名称字段名, 如dept_name" placement="top">
<i class="el-icon-question"></i> <i class="el-icon-question"></i>
</el-tooltip> </el-tooltip>
</span> </span>
<el-select v-model="info.treeName" placeholder="请选择"> <el-select v-model="info.treeName" placeholder="请选择">
<el-option <el-option
v-for="(column, index) in info.columns" v-for="(column, index) in info.columns"
:key="index" :key="index"
:label="column.columnName + '' + column.columnComment" :label="column.columnName + '' + column.columnComment"
:value="column.columnName" :value="column.columnName"
></el-option> ></el-option>
</el-select> </el-select>
</el-form-item> </el-form-item>
</el-col> </el-col>
</el-row> </el-row>
<el-row v-show="info.tplCategory == 'sub'"> <el-row v-show="info.tplCategory == 'sub'">
<h4 class="form-header">关联信息</h4> <h4 class="form-header">关联信息</h4>
<el-col :span="12"> <el-col :span="12">
<el-form-item> <el-form-item>
<span slot="label"> <span slot="label">
关联子表的表名 关联子表的表名
<el-tooltip content="关联子表的表名, 如sys_user" placement="top"> <el-tooltip content="关联子表的表名, 如sys_user" placement="top">
<i class="el-icon-question"></i> <i class="el-icon-question"></i>
</el-tooltip> </el-tooltip>
</span> </span>
<el-select v-model="info.subTableName" placeholder="请选择" @change="subSelectChange"> <el-select v-model="info.subTableName" placeholder="请选择" @change="subSelectChange">
<el-option <el-option
v-for="(table, index) in tables" v-for="(table, index) in tables"
:key="index" :key="index"
:label="table.tableName + '' + table.tableComment" :label="table.tableName + '' + table.tableComment"
:value="table.tableName" :value="table.tableName"
></el-option> ></el-option>
</el-select> </el-select>
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="12"> <el-col :span="12">
<el-form-item> <el-form-item>
<span slot="label"> <span slot="label">
子表关联的外键名 子表关联的外键名
<el-tooltip content="子表关联的外键名, 如user_id" placement="top"> <el-tooltip content="子表关联的外键名, 如user_id" placement="top">
<i class="el-icon-question"></i> <i class="el-icon-question"></i>
</el-tooltip> </el-tooltip>
</span> </span>
<el-select v-model="info.subTableFkName" placeholder="请选择"> <el-select v-model="info.subTableFkName" placeholder="请选择">
<el-option <el-option
v-for="(column, index) in subColumns" v-for="(column, index) in subColumns"
:key="index" :key="index"
:label="column.columnName + '' + column.columnComment" :label="column.columnName + '' + column.columnComment"
:value="column.columnName" :value="column.columnName"
></el-option> ></el-option>
</el-select> </el-select>
</el-form-item> </el-form-item>
</el-col> </el-col>
</el-row> </el-row>
</el-form> </el-form>
</template> </template>
<script> <script>
import Treeselect from "@riophae/vue-treeselect"; import Treeselect from "@riophae/vue-treeselect";
import "@riophae/vue-treeselect/dist/vue-treeselect.css"; import "@riophae/vue-treeselect/dist/vue-treeselect.css";
export default { export default {
name: "BasicInfoForm", name: "BasicInfoForm",
components: { Treeselect }, components: { Treeselect },
props: { props: {
info: { info: {
type: Object, type: Object,
default: null default: null
}, },
tables: { tables: {
type: Array, type: Array,
default: null default: null
}, },
menus: { menus: {
type: Array, type: Array,
default: [] default: []
}, },
}, },
data() { data() {
return { return {
subColumns: [], subColumns: [],
rules: { rules: {
templateType: [ templateType: [
{ required: true, message: "请选择生成模板", trigger: "blur" } { required: true, message: "请选择生成模板", trigger: "blur" }
], ],
// packageName: [ // packageName: [
// { required: true, message: "", trigger: "blur" } // { required: true, message: "", trigger: "blur" }
// ], // ],
moduleName: [ moduleName: [
{ required: true, message: "请输入生成模块名", trigger: "blur" } { required: true, message: "请输入生成模块名", trigger: "blur" }
], ],
businessName: [ businessName: [
{ required: true, message: "请输入生成业务名", trigger: "blur" } { required: true, message: "请输入生成业务名", trigger: "blur" }
], ],
businessPackage: [ businessPackage: [
{ required: true, message: "请输入生成业务包", trigger: "blur" } { required: true, message: "请输入生成业务包", trigger: "blur" }
], ],
className: [ className: [
{ required: true, message: "请输入生成类名称", trigger: "blur" } { required: true, message: "请输入生成类名称", trigger: "blur" }
], ],
classComment: [ classComment: [
{ required: true, message: "请输入生成类描述", trigger: "blur" } { required: true, message: "请输入生成类描述", trigger: "blur" }
], ],
} }
}; };
}, },
created() {}, created() {},
watch: { watch: {
'info.subTableName': function(val) { 'info.subTableName': function(val) {
this.setSubTableColumns(val); this.setSubTableColumns(val);
} }
}, },
methods: { methods: {
/** 转换菜单数据结构 */ /** 转换菜单数据结构 */
normalizer(node) { normalizer(node) {
if (node.children && !node.children.length) { if (node.children && !node.children.length) {
delete node.children; delete node.children;
} }
return { return {
id: node.id, id: node.id,
label: node.name, label: node.name,
children: node.children children: node.children
}; };
}, },
/** 选择子表名触发 */ /** 选择子表名触发 */
subSelectChange(value) { subSelectChange(value) {
this.info.subTableFkName = ''; this.info.subTableFkName = '';
}, },
/** 选择生成模板触发 */ /** 选择生成模板触发 */
tplSelectChange(value) { tplSelectChange(value) {
if (value !== 1) { if (value !== 1) {
// TODO // TODO
this.msgError('暂时不考虑支持【树形】和【主子表】的代码生成。原因是:导致 vm 模板过于复杂,不利于胖友二次开发'); this.msgError('暂时不考虑支持【树形】和【主子表】的代码生成。原因是:导致 vm 模板过于复杂,不利于胖友二次开发');
return false; return false;
} }
if(value !== 'sub') { if(value !== 'sub') {
this.info.subTableName = ''; this.info.subTableName = '';
this.info.subTableFkName = ''; this.info.subTableFkName = '';
} }
}, },
/** 设置关联外键 */ /** 设置关联外键 */
setSubTableColumns(value) { setSubTableColumns(value) {
for (var item in this.tables) { for (var item in this.tables) {
const name = this.tables[item].tableName; const name = this.tables[item].tableName;
if (value === name) { if (value === name) {
this.subColumns = this.tables[item].columns; this.subColumns = this.tables[item].columns;
break; break;
} }
} }
} }
} }
}; };
</script> </script>

View File

@ -1,106 +1,106 @@
<template> <template>
<!-- 导入表 --> <!-- 导入表 -->
<el-dialog title="导入表" :visible.sync="visible" width="800px" top="5vh" append-to-body> <el-dialog title="导入表" :visible.sync="visible" width="800px" top="5vh" append-to-body>
<el-form :model="queryParams" ref="queryForm" :inline="true"> <el-form :model="queryParams" ref="queryForm" :inline="true">
<el-form-item label="表名称" prop="tableName"> <el-form-item label="表名称" prop="tableName">
<el-input <el-input
v-model="queryParams.tableName" v-model="queryParams.tableName"
placeholder="请输入表名称" placeholder="请输入表名称"
clearable clearable
size="small" size="small"
@keyup.enter.native="handleQuery" @keyup.enter.native="handleQuery"
/> />
</el-form-item> </el-form-item>
<el-form-item label="表描述" prop="tableComment"> <el-form-item label="表描述" prop="tableComment">
<el-input <el-input
v-model="queryParams.tableComment" v-model="queryParams.tableComment"
placeholder="请输入表描述" placeholder="请输入表描述"
clearable clearable
size="small" size="small"
@keyup.enter.native="handleQuery" @keyup.enter.native="handleQuery"
/> />
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button> <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button> <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
</el-form-item> </el-form-item>
</el-form> </el-form>
<el-row> <el-row>
<el-table @row-click="clickRow" ref="table" :data="dbTableList" @selection-change="handleSelectionChange" height="260px"> <el-table @row-click="clickRow" ref="table" :data="dbTableList" @selection-change="handleSelectionChange" height="260px">
<el-table-column type="selection" width="55"></el-table-column> <el-table-column type="selection" width="55"></el-table-column>
<el-table-column prop="tableSchema" label="数据库" :show-overflow-tooltip="true"></el-table-column> <el-table-column prop="tableSchema" label="数据库" :show-overflow-tooltip="true"></el-table-column>
<el-table-column prop="tableName" label="表名称" :show-overflow-tooltip="true"></el-table-column> <el-table-column prop="tableName" label="表名称" :show-overflow-tooltip="true"></el-table-column>
<el-table-column prop="tableComment" label="表描述" :show-overflow-tooltip="true"></el-table-column> <el-table-column prop="tableComment" label="表描述" :show-overflow-tooltip="true"></el-table-column>
<el-table-column prop="createTime" label="创建时间"> <el-table-column prop="createTime" label="创建时间">
<template slot-scope="scope"> <template slot-scope="scope">
<span>{{ parseTime(scope.row.createTime) }}</span> <span>{{ parseTime(scope.row.createTime) }}</span>
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
</el-row> </el-row>
<div slot="footer" class="dialog-footer"> <div slot="footer" class="dialog-footer">
<el-button type="primary" @click="handleImportTable"> </el-button> <el-button type="primary" @click="handleImportTable"> </el-button>
<el-button @click="visible = false"> </el-button> <el-button @click="visible = false"> </el-button>
</div> </div>
</el-dialog> </el-dialog>
</template> </template>
<script> <script>
import { getSchemaTableList, createCodegenList } from "@/api/tool/codegen"; import { getSchemaTableList, createCodegenListFromDB } from "@/api/tool/codegen";
export default { export default {
data() { data() {
return { return {
// //
visible: false, visible: false,
// //
tables: [], tables: [],
// //
total: 0, total: 0,
// //
dbTableList: [], dbTableList: [],
// //
queryParams: { queryParams: {
tableName: undefined, tableName: undefined,
tableComment: undefined tableComment: undefined
} }
}; };
}, },
methods: { methods: {
// //
show() { show() {
this.getList(); this.getList();
this.visible = true; this.visible = true;
}, },
clickRow(row) { clickRow(row) {
this.$refs.table.toggleRowSelection(row); this.$refs.table.toggleRowSelection(row);
}, },
// //
handleSelectionChange(selection) { handleSelectionChange(selection) {
this.tables = selection.map(item => item.tableName); this.tables = selection.map(item => item.tableName);
}, },
// //
getList() { getList() {
getSchemaTableList(this.queryParams).then(res => { getSchemaTableList(this.queryParams).then(res => {
this.dbTableList = res.data; this.dbTableList = res.data;
}); });
}, },
/** 搜索按钮操作 */ /** 搜索按钮操作 */
handleQuery() { handleQuery() {
this.getList(); this.getList();
}, },
/** 重置按钮操作 */ /** 重置按钮操作 */
resetQuery() { resetQuery() {
this.resetForm("queryForm"); this.resetForm("queryForm");
this.handleQuery(); this.handleQuery();
}, },
/** 导入按钮操作 */ /** 导入按钮操作 */
handleImportTable() { handleImportTable() {
createCodegenList(this.tables.join(",")).then(res => { createCodegenListFromDB(this.tables.join(",")).then(res => {
this.msgSuccess("导入成功"); this.msgSuccess("导入成功");
this.visible = false; this.visible = false;
this.$emit("ok"); this.$emit("ok");
}); });
} }
} }
}; };
</script> </script>

View File

@ -1,228 +1,316 @@
<template> <template>
<div class="app-container"> <div class="app-container">
<el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px"> <!-- 操作工作栏 -->
<el-form-item label="表名称" prop="tableName"> <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
<el-input <el-form-item label="表名称" prop="tableName">
v-model="queryParams.tableName" <el-input
placeholder="请输入表名称" v-model="queryParams.tableName"
clearable placeholder="请输入表名称"
size="small" clearable
@keyup.enter.native="handleQuery" size="small"
/> @keyup.enter.native="handleQuery"
</el-form-item> />
<el-form-item label="表描述" prop="tableComment"> </el-form-item>
<el-input <el-form-item label="表描述" prop="tableComment">
v-model="queryParams.tableComment" <el-input
placeholder="请输入表描述" v-model="queryParams.tableComment"
clearable placeholder="请输入表描述"
size="small" clearable
@keyup.enter.native="handleQuery" size="small"
/> @keyup.enter.native="handleQuery"
</el-form-item> />
<el-form-item label="创建时间"> </el-form-item>
<el-date-picker <el-form-item label="创建时间">
v-model="dateRange" <el-date-picker
size="small" v-model="dateRange"
style="width: 240px" size="small"
value-format="yyyy-MM-dd" style="width: 240px"
type="daterange" value-format="yyyy-MM-dd"
range-separator="-" type="daterange"
start-placeholder="开始日期" range-separator="-"
end-placeholder="结束日期" start-placeholder="开始日期"
></el-date-picker> end-placeholder="结束日期"
</el-form-item> ></el-date-picker>
<el-form-item> </el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button> <el-form-item>
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button> <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
</el-form-item> <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
</el-form> </el-form-item>
</el-form>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5"> <!-- 操作工作栏 -->
<el-button type="info" plain icon="el-icon-upload" size="mini" @click="openImportTable" v-hasPermi="['tool:gen:import']">导入</el-button> <el-row :gutter="10" class="mb8">
</el-col> <el-col :span="1.5">
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar> <el-button type="info" plain icon="el-icon-upload" size="mini" @click="openImportTable"
</el-row> v-hasPermi="['tool:codegen:create']">基于 DB 导入</el-button>
<el-button type="info" plain icon="el-icon-upload" size="mini" @click="openImportSQL"
<el-table v-loading="loading" :data="tableList"> v-hasPermi="['tool:codegen:create']">基于 SQL 导入</el-button>
<el-table-column label="表名称" align="center" prop="tableName" :show-overflow-tooltip="true" width="200"/> </el-col>
<el-table-column label="表描述" align="center" prop="tableComment" :show-overflow-tooltip="true" width="120"/> <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
<el-table-column label="实体" align="center" prop="className" :show-overflow-tooltip="true" width="200"/> </el-row>
<el-table-column label="创建时间" align="center" prop="createTime" width="160">
<template slot-scope="scope"> <!-- 列表 -->
<span>{{ parseTime(scope.row.createTime) }}</span> <el-table v-loading="loading" :data="tableList">
</template> <el-table-column label="表名称" align="center" prop="tableName" :show-overflow-tooltip="true" width="200"/>
</el-table-column> <el-table-column label="表描述" align="center" prop="tableComment" :show-overflow-tooltip="true" width="120"/>
<el-table-column label="创建时间" align="center" prop="createTime" width="160"> <el-table-column label="实体" align="center" prop="className" :show-overflow-tooltip="true" width="200"/>
<template slot-scope="scope"> <el-table-column label="创建时间" align="center" prop="createTime" width="160">
<span>{{ parseTime(scope.row.updateTime) }}</span> <template slot-scope="scope">
</template> <span>{{ parseTime(scope.row.createTime) }}</span>
</el-table-column> </template>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width"> </el-table-column>
<template slot-scope="scope"> <el-table-column label="创建时间" align="center" prop="createTime" width="160">
<el-button type="text" size="small" icon="el-icon-view" @click="handlePreview(scope.row)" v-hasPermi="['tool:gen:preview']">预览</el-button> <template slot-scope="scope">
<el-button type="text" size="small" icon="el-icon-edit" @click="handleEditTable(scope.row)" v-hasPermi="['tool:gen:edit']">编辑</el-button> <span>{{ parseTime(scope.row.updateTime) }}</span>
<el-button type="text" size="small" icon="el-icon-delete" @click="handleDelete(scope.row)" v-hasPermi="['tool:gen:remove']">删除</el-button> </template>
<el-button type="text" size="small" icon="el-icon-refresh" @click="handleSynchDb(scope.row)" v-hasPermi="['tool:gen:edit']">同步</el-button> </el-table-column>
<el-button type="text" size="small" icon="el-icon-download" @click="handleGenTable(scope.row)" v-hasPermi="['tool:gen:code']">生成代码</el-button> <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
</template> <template slot-scope="scope">
</el-table-column> <el-button type="text" size="small" icon="el-icon-view" @click="handlePreview(scope.row)" v-hasPermi="['tool:codegen:preview']">预览</el-button>
</el-table> <el-button type="text" size="small" icon="el-icon-edit" @click="handleEditTable(scope.row)" v-hasPermi="['tool:codegen:update']">编辑</el-button>
<pagination v-show="total>0" :total="total" :page.sync="queryParams.pageNo" :limit.sync="queryParams.pageSize" @pagination="getList"/> <el-button type="text" size="small" icon="el-icon-delete" @click="handleDelete(scope.row)" v-hasPermi="['tool:codegen:delete']">删除</el-button>
<!-- 预览界面 --> <el-button type="text" size="small" icon="el-icon-refresh" @click="handleSynchDb(scope.row)" v-hasPermi="['tool:codegen:update']">同步</el-button>
<el-dialog :title="preview.title" :visible.sync="preview.open" width="80%" top="5vh" append-to-body> <el-button type="text" size="small" icon="el-icon-download" @click="handleGenTable(scope.row)" v-hasPermi="['tool:codegen:download']">生成代码</el-button>
<el-tabs tab-position="left" v-model="preview.activeName"> </template>
<el-tab-pane v-for="item in preview.data" :label="item.filePath.substring(item.filePath.lastIndexOf('/') + 1)" :name="item.filePath" :key="item.filePath"> </el-table-column>
<pre><code class="hljs" v-html="highlightedCode(item)"></code></pre> </el-table>
</el-tab-pane> <pagination v-show="total>0" :total="total" :page.sync="queryParams.pageNo" :limit.sync="queryParams.pageSize" @pagination="getList"/>
</el-tabs>
</el-dialog> <!-- 预览界面 -->
<import-table ref="import" @ok="handleQuery" /> <el-dialog :title="preview.title" :visible.sync="preview.open" width="80%" top="5vh" append-to-body>
</div> <el-tabs tab-position="left" v-model="preview.activeName">
</template> <el-tab-pane v-for="item in preview.data" :label="item.filePath.substring(item.filePath.lastIndexOf('/') + 1)" :name="item.filePath" :key="item.filePath">
<pre><code class="hljs" v-html="highlightedCode(item)"></code></pre>
<script> </el-tab-pane>
import { getCodegenTablePage, previewCodegen, downloadCodegen, deleteCodegen, syncCodegen } from "@/api/tool/codegen"; </el-tabs>
</el-dialog>
import importTable from "./importTable";
// <!-- 基于 DB 导入 -->
import hljs from "highlight.js/lib/highlight"; <import-table ref="import" @ok="handleQuery" />
import "highlight.js/styles/github-gist.css";
hljs.registerLanguage("java", require("highlight.js/lib/languages/java")); <!-- 基于 SQL 导入 -->
hljs.registerLanguage("xml", require("highlight.js/lib/languages/xml")); <el-dialog :title="importSQL.title" :visible.sync="importSQL.open" width="800px" append-to-body>
hljs.registerLanguage("html", require("highlight.js/lib/languages/xml")); <el-form ref="importSQLForm" :model="importSQL.form" :rules="importSQL.rules" label-width="120px">
hljs.registerLanguage("vue", require("highlight.js/lib/languages/xml")); <el-row>
hljs.registerLanguage("javascript", require("highlight.js/lib/languages/javascript")); <el-col :span="12">
hljs.registerLanguage("sql", require("highlight.js/lib/languages/sql")); <el-form-item label="建表 SQL 语句" prop="sql">
<el-input v-model="importSQL.form.sql" type="textarea" rows="30" style="width: 650px;" placeholder="请输入建 SQL 语句" />
export default { </el-form-item>
name: "Gen", </el-col>
components: { importTable }, </el-row>
data() { </el-form>
return { <div slot="footer" class="dialog-footer">
// <el-button type="primary" @click="submitImportSQLForm"> </el-button>
loading: true, <el-button @click="cancel"> </el-button>
// </div>
uniqueId: "", </el-dialog>
// </div>
tableNames: [], </template>
//
showSearch: true, <script>
// import { getCodegenTablePage, previewCodegen, downloadCodegen, deleteCodegen,
total: 0, syncCodegenFromDB, syncCodegenFromSQL, createCodegenListFromSQL } from "@/api/tool/codegen";
//
tableList: [], import importTable from "./importTable";
// //
dateRange: "", import hljs from "highlight.js/lib/highlight";
// import "highlight.js/styles/github-gist.css";
queryParams: { import {SysCommonStatusEnum} from "@/utils/constants";
pageNo: 1, import {createTestDemo, updateTestDemo} from "@/api/tool/testDemo";
pageSize: 10, hljs.registerLanguage("java", require("highlight.js/lib/languages/java"));
tableName: undefined, hljs.registerLanguage("xml", require("highlight.js/lib/languages/xml"));
tableComment: undefined hljs.registerLanguage("html", require("highlight.js/lib/languages/xml"));
}, hljs.registerLanguage("vue", require("highlight.js/lib/languages/xml"));
// hljs.registerLanguage("javascript", require("highlight.js/lib/languages/javascript"));
preview: { hljs.registerLanguage("sql", require("highlight.js/lib/languages/sql"));
open: false,
title: "代码预览", export default {
data: {}, name: "Codegen",
activeName: "domain.java" components: { importTable },
} data() {
}; return {
}, //
created() { loading: true,
this.getList(); //
}, uniqueId: "",
activated() { //
const time = this.$route.query.t; tableNames: [],
if (time != null && time !== this.uniqueId) { //
this.uniqueId = time; showSearch: true,
this.resetQuery(); //
} total: 0,
}, //
methods: { tableList: [],
/** 查询表集合 */ //
getList() { dateRange: "",
this.loading = true; //
getCodegenTablePage(this.addDateRange(this.queryParams, [ queryParams: {
this.dateRange[0] ? this.dateRange[0] + ' 00:00:00' : undefined, pageNo: 1,
this.dateRange[1] ? this.dateRange[1] + ' 23:59:59' : undefined, pageSize: 10,
], 'CreateTime')).then(response => { tableName: undefined,
this.tableList = response.data.list; tableComment: undefined
this.total = response.data.total; },
this.loading = false; //
} preview: {
); open: false,
}, title: "代码预览",
/** 搜索按钮操作 */ data: {},
handleQuery() { activeName: "domain.java"
this.queryParams.pageNo = 1; },
this.getList(); // SQL
}, importSQL: {
/** 生成代码操作 */ open: false,
handleGenTable(row) { title: "",
downloadCodegen(row.id).then(response => { form: {
this.downloadZip(response, 'codegen-' + row.tableName + '.zip');
}) },
}, rules: {
/** 同步数据库操作 */ sql: [{ required: true, message: "SQL 不能为空", trigger: "blur" }]
handleSynchDb(row) { }
const tableName = row.tableName; }
this.$confirm('确认要强制同步"' + tableName + '"表结构吗?', "警告", { };
confirmButtonText: "确定", },
cancelButtonText: "取消", created() {
type: "warning" this.getList();
}).then(function() { },
return syncCodegen(row.id); activated() {
}).then(() => { const time = this.$route.query.t;
this.msgSuccess("同步成功"); if (time != null && time !== this.uniqueId) {
}) this.uniqueId = time;
}, this.resetQuery();
/** 打开导入表弹窗 */ }
openImportTable() { },
this.$refs.import.show(); methods: {
}, /** 查询表集合 */
/** 重置按钮操作 */ getList() {
resetQuery() { this.loading = true;
this.dateRange = []; getCodegenTablePage(this.addDateRange(this.queryParams, [
this.resetForm("queryForm"); this.dateRange[0] ? this.dateRange[0] + ' 00:00:00' : undefined,
this.handleQuery(); this.dateRange[1] ? this.dateRange[1] + ' 23:59:59' : undefined,
}, ], 'CreateTime')).then(response => {
/** 预览按钮 */ this.tableList = response.data.list;
handlePreview(row) { this.total = response.data.total;
previewCodegen(row.id).then(response => { this.loading = false;
this.preview.data = response.data; }
this.preview.activeName = response.data[0].filePath; );
this.preview.open = true; },
}); /** 搜索按钮操作 */
}, handleQuery() {
/** 高亮显示 */ this.queryParams.pageNo = 1;
highlightedCode(item) { this.getList();
// const vmName = key.substring(key.lastIndexOf("/") + 1, key.indexOf(".vm")); },
// var language = vmName.substring(vmName.indexOf(".") + 1, vmName.length); /** 生成代码操作 */
var language = item.filePath.substring(item.filePath.lastIndexOf(".") + 1); handleGenTable(row) {
const result = hljs.highlight(language, item.code || "", true); downloadCodegen(row.id).then(response => {
return result.value || '&nbsp;'; this.downloadZip(response, 'codegen-' + row.tableName + '.zip');
}, })
/** 修改按钮操作 */ },
handleEditTable(row) { /** 同步数据库操作 */
const tableId = row.id; handleSynchDb(row) {
this.$router.push("/gen/edit/" + tableId); // SQL
}, if (row.importType === 2) {
/** 删除按钮操作 */ this.importSQL.open = true;
handleDelete(row) { this.importSQL.form.tableId = row.id;
const tableIds = row.id; return;
this.$confirm('是否确认删除表名称为"' + row.tableName + '"的数据项?', "警告", { }
confirmButtonText: "确定", // DB
cancelButtonText: "取消", const tableName = row.tableName;
type: "warning" this.$confirm('确认要强制同步"' + tableName + '"表结构吗?', "警告", {
}).then(function() { confirmButtonText: "确定",
return deleteCodegen(tableIds); cancelButtonText: "取消",
}).then(() => { type: "warning"
this.getList(); }).then(function() {
this.msgSuccess("删除成功"); return syncCodegenFromDB(row.id);
}) }).then(() => {
} this.msgSuccess("同步成功");
} })
}; },
</script> /** 打开导入表弹窗 */
openImportTable() {
this.$refs.import.show();
},
/** 打开 SQL 导入的弹窗 **/
openImportSQL() {
this.importSQL.open = true;
},
/** 重置按钮操作 */
resetQuery() {
this.dateRange = [];
this.resetForm("queryForm");
this.handleQuery();
},
/** 预览按钮 */
handlePreview(row) {
previewCodegen(row.id).then(response => {
this.preview.data = response.data;
this.preview.activeName = response.data[0].filePath;
this.preview.open = true;
});
},
/** 高亮显示 */
highlightedCode(item) {
// const vmName = key.substring(key.lastIndexOf("/") + 1, key.indexOf(".vm"));
// var language = vmName.substring(vmName.indexOf(".") + 1, vmName.length);
var language = item.filePath.substring(item.filePath.lastIndexOf(".") + 1);
const result = hljs.highlight(language, item.code || "", true);
return result.value || '&nbsp;';
},
/** 修改按钮操作 */
handleEditTable(row) {
const tableId = row.id;
this.$router.push("/codegen/edit/" + tableId);
},
/** 删除按钮操作 */
handleDelete(row) {
const tableIds = row.id;
this.$confirm('是否确认删除表名称为"' + row.tableName + '"的数据项?', "警告", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
}).then(function() {
return deleteCodegen(tableIds);
}).then(() => {
this.getList();
this.msgSuccess("删除成功");
})
},
//
cancel() {
this.importSQL.open = false;
this.reset();
},
//
reset() {
this.importSQL.form = {
tableId: undefined,
sql: undefined,
};
this.resetForm("importSQLForm");
},
// import SQL
submitImportSQLForm() {
this.$refs["importSQLForm"].validate(valid => {
if (!valid) {
return;
}
//
let form = this.importSQL.form;
if (form.tableId != null) {
syncCodegenFromSQL(form.tableId, form.sql).then(response => {
this.msgSuccess("同步成功");
this.importSQL.open = false;
this.getList();
});
return;
}
//
createCodegenListFromSQL(form).then(response => {
this.msgSuccess("导入成功");
this.importSQL.open = false;
this.getList();
});
});
}
}
};
</script>

File diff suppressed because one or more lines are too long

View File

@ -56,9 +56,6 @@ public class ToolCodegenServiceImpl implements ToolCodegenService {
@Resource @Resource
private CodegenProperties codegenProperties; private CodegenProperties codegenProperties;
@Resource
private ToolCodegenServiceImpl self;
private Long createCodegen0(ToolCodegenImportTypeEnum importType, private Long createCodegen0(ToolCodegenImportTypeEnum importType,
ToolSchemaTableDO schemaTable, List<ToolSchemaColumnDO> schemaColumns) { ToolSchemaTableDO schemaTable, List<ToolSchemaColumnDO> schemaColumns) {
// 校验导入的表和字段非空 // 校验导入的表和字段非空
@ -99,7 +96,7 @@ public class ToolCodegenServiceImpl implements ToolCodegenService {
throw exception(CODEGEN_PARSE_SQL_ERROR); throw exception(CODEGEN_PARSE_SQL_ERROR);
} }
// 导入 // 导入
return self.createCodegen0(ToolCodegenImportTypeEnum.SQL, schemaTable, schemaColumns); return this.createCodegen0(ToolCodegenImportTypeEnum.SQL, schemaTable, schemaColumns);
} }
@Override @Override
@ -108,7 +105,7 @@ public class ToolCodegenServiceImpl implements ToolCodegenService {
ToolSchemaTableDO schemaTable = schemaTableMapper.selectByTableName(tableName); ToolSchemaTableDO schemaTable = schemaTableMapper.selectByTableName(tableName);
List<ToolSchemaColumnDO> schemaColumns = schemaColumnMapper.selectListByTableName(tableName); List<ToolSchemaColumnDO> schemaColumns = schemaColumnMapper.selectListByTableName(tableName);
// 导入 // 导入
return self.createCodegen0(ToolCodegenImportTypeEnum.DB, schemaTable, schemaColumns); return this.createCodegen0(ToolCodegenImportTypeEnum.DB, schemaTable, schemaColumns);
} }
@Override @Override
@ -147,7 +144,7 @@ public class ToolCodegenServiceImpl implements ToolCodegenService {
List<ToolSchemaColumnDO> schemaColumns = schemaColumnMapper.selectListByTableName(table.getTableName()); List<ToolSchemaColumnDO> schemaColumns = schemaColumnMapper.selectListByTableName(table.getTableName());
// 执行同步 // 执行同步
self.syncCodegen0(tableId, schemaColumns); this.syncCodegen0(tableId, schemaColumns);
} }
@Override @Override
@ -167,7 +164,7 @@ public class ToolCodegenServiceImpl implements ToolCodegenService {
} }
// 执行同步 // 执行同步
self.syncCodegen0(tableId, schemaColumns); this.syncCodegen0(tableId, schemaColumns);
} }
private void syncCodegen0(Long tableId, List<ToolSchemaColumnDO> schemaColumns) { private void syncCodegen0(Long tableId, List<ToolSchemaColumnDO> schemaColumns) {

View File

@ -268,12 +268,12 @@ export default {
this.loading = false; this.loading = false;
}); });
}, },
// 取消按钮 /** 取消按钮 */
cancel() { cancel() {
this.open = false; this.open = false;
this.reset(); this.reset();
}, },
// 表单重置 /** 表单重置 */
reset() { reset() {
this.form = { this.form = {
#foreach ($column in $columns) #foreach ($column in $columns)