1. 模板的开发与制作
区别于mcms中生成htm的模板,代码生成器的模板更加强大,通过制作不同的模板可以生成任意类型任意内容的代码。
本文中所有模板均指代码生成器模板,和cms模板无关。
在代码生成器中一个项目中有多个表单(一个表单对应一张表),表单之中可以有多个组件,两两为一对多关系
项目图
表单图
1.1. 新建模板组
模板组名称需要在0-30个字之间;描述不得超过120个字;价格如果选择为0则代表这套模板您设定为免费,任何人都可以使用你创建的这套模板,如果价格大于0那只有收费用户以及购买了这套模板的用户可以使用(商城暂未开放)
图1
[!tip]请确保你创建的模板是原创的且合法合规不具有任何攻击性!
1.2. 新建模板
同属于一套模板组的模板是一个整体,如果模板组被删除则从属模板也会被全部删除,在制作模板时建议时常备份避免误删
1.2.1. 模板名称
模板名称长度需在0-30之间,名称可以是中文或者英文,同一套模板中不能存在相同的模板名称
1.2.2. 生成根路径
在下载的代码压缩包中,以最外层目录为起点的目录层级,通常设置为项目的固定路径 如/src/main/java/net/mingsoft/cms
当然这样的写法不够灵活,我们提供了占位符的方式来自动获取项目配置的方式,如:/src/main/java/${projectPath}/${projectName},使用规则如下:
${projectPath} 项目包路径,如 net/mingsoft
${projectName} 项目模块名,如 cms
${beanNameUpperCase} 首字母大写的bean名称,如 PeopleLog,一般用于文件名字段
${beanNameLowerCase} 首字母小写单词之间用横岗隔开,如 people-log,一般用于前端模板的路径字段
注意!目前只有四种占位符可用,且只能用于 "生成根路径"、"路径"、"生成文件名" 这三个字段才有效,在模板代码中使用占位符是不会生效的!
1.2.3. 路径
拼接在 "生成根路径" 之后的目录层级,一般可用来填写控制层的 controller或者action,也可以是 entity或dto 亦或者 biz/impl 等等,可用占位符
1.2.4. 生成文件名
模板的生成文件名可以为空,但是不会生成文件也不会被预览看到,如果勾选了不生成文件但是想要能参与预览可以随意设置一个生成文件名;
一般填写 ${beanNameUpperCase}Action.java,文件后缀是必须的。
[!tip]注意,文件名在固定项目风格后不应随意修改,否则会导致项目文件和文件内容的类名不一致编译失败的情况,请及时调整文件名或者模板代码
1.2.5. 是否生成文件
未开启生成的模板在项目下载代码时不会生成实际文件,但是不会影响到预览功能
1.2.6. 是否宏模板
宏模板不会生成文件也不会在预览代码中出现,它会在每个其他类型的模板渲染的时候自动注入,因此其他模板中可以使用宏模板中的自定义宏方法
1.2.7. 自定义排序
在模板渲染时会按照模板顺序由大到小依次渲染读取(注意和展示顺序无关)
1.2.8. 模板代码
模板代码遵循freemarker语法,如果熟悉freemarker语法可以跳过freemarker语法讲解的部分只阅读标签规则快速开发
freemarker语法快速解读
为了更好理解开发模板,这里需要对几个常用的freemarker语法进行简单讲解
[!tip]注意,标签闭合需要带/,否则会渲染失败
assign标签
<#assign item = 1>
定义了一个item变量,值为:${item}
输出结果:定义了一个item变量,值为:1
assign是定义变量的标签,如上示例,item被定义后就可以在定义后的模板部分用freemarker语法进行获取了, ${} 是freemarker取值的方式 官方文档
if标签
<#assign i = 10>
<#if i gt 10>
定义的变量i大于10
<#elseif i gt 5>
定义的变量i介于5到10之间
<#else>
定义的变量i小于5
</#if>
输出结果:定义的变量i介于5到10之间
if else标签和其他语言的语法本质相同,需要搭配布尔类型,可以结合函数运算或者freemarker其他语法,如<#if i+1 gt 10></#if> 或者 <#if item?has_content></#if> 官方文档
list标签
[
<#list formItem.options.attachField as _formItem>
<#if _formItem?index gt 0>,</#if>{"name": "${_formItem.name}"}
</#list>
]
输出结果:[{"name": "name1"},{"name": "name2"},{"name": "name3"}]
list标签和for循环相同,本质都是对一个集合进行遍历,list标签可以嵌套list实现嵌套循环,_formItem?index 作用是当前元素的下标 官方文档
示例
便于理解,下面展示五段模板作为示例内容
1.searchJson
<#--搜索json结构-->
[
<@widget field="getWidgetVue" key="condition"></@widget>
]
2.script
var custom_model = Vue.component("custom-model",{
el: '#custom-model',
data:function() {
return {
modelId:0,
modelName: "${project.projectModelObjects[0].pmoName}",
<@widget field="getWidgetVue" key="data"></@widget>
//表单数据
form: {
linkId:0,
<@widget field="getWidgetVue" key="form"></@widget>
},
rules:{
<@widget field="getWidgetVue" key="rule"></@widget>
},
}
},
watch:{
<@widget field="getWidgetVue" key="watch"></@widget>
},
components:{
<@widget field="getWidgetVue" key="component"></@widget>
},
computed:{
<@widget field="getWidgetVue" key="computed"></@widget>
},
methods: {
<@widget field="getWidgetVue" key="method"></@widget>
<#if table.pmoDialog>
open:function(id){
<@widget field="getWidgetVue" key="create"></@widget>
if (id) {
this.get(id);
}
<@widget field="getWidgetVue" key="get"></@widget>
this.$nextTick(function () {
this.dialogVisible = true;
})
},
</#if>
validate:function(){
var b = false
this.$refs.form.validate(function(valid){
b = valid;
});
return b;
},
save:function(callback) {
var that = this;
var url = this.formURL.save.url;
if (that.form.id > 0) {
url = this.formURL.update.url;
}
this.$refs.form.validate(function(valid) {
if (valid) {
<@widget field="getWidgetVue" key="saveValidate"></@widget>
<#--注意下面进行了数据转换-->
<@widget field="getWidgetVue" key="save"></@widget>
var form = JSON.parse(JSON.stringify(that.form));
form.modelId = that.modelId;
ms.http.post(url, form).then(function (res) {
if(callback) {
callback(res);
}
});
}
else{
callback({
result:false,msg:'请检查表单输入项'
});
}
})
},
//获取当前${table.remarks}
get:function(id) {
var that = this;
ms.http.get(this.formURL.get.url, Object.assign({"modelId":that.modelId},this.formURL.get.params)).then(function (res) {
<@widget field="getWidgetVue" key="get"></@widget>
if(res.result&&res.data){
that.form = res.data;
}
}).catch(function (err) {
console.log(err);
});
},
},
created:function() {
var that = this;
//渲染create
<@widget field="getWidgetVue" key="created"></@widget>
that.get(this.form.linkId);
}
});
[!tip]
数组值保存失败(如日期范围),一般都是模板中saveValidate的form指向错误;
如果需要对表单中的值做处理,需要在saveValidate中处理,避免修改双向绑定数据导致组件报错
3.html
<#assign beanName=table.beanName[0]?lower_case+table.beanName?substring(1)/>
<#-- 引入文件处理转换数据-->
<template id="custom-model">
<#assign formConfig=JSON.parse(table.attachJson)/>
<#--这里是整个form-->
<el-form ref="form" :model="form" :rules="rules" label-width="${formConfig.labelWidth}px" label-position="${formConfig.labelPosition}" size="${formConfig.size}">
<#list JSON.parse(table.json) as row>
<#--判断是否是栅格-->
<#if row.type == 'grid'>
<el-row
:gutter="${row.options.gutter}"
justify="${row.options.justify}" align="${row.options.align}">
<#-- 栅格就取列中的list进行遍历-->
<#list row.columns as column>
<el-col :span="${column.span}">
<#list column.list as formItem>
<#--这里循环渲染组件-->
<@widget field="getWidgetHtml" formItem=formItem></@widget>
</#list>
</el-col>
</#list>
</el-row>
<#else>
<#-- 不是就直接渲染,因为里面的模板都用了formItem作为参数所以需要赋值-->
<@widget field="getWidgetHtml" formItem=row></@widget>
</#if>
</#list>
</el-form>
</template>
4.field
[
<#list JSON.parse(table.json) as formItem>
<#if formItem.type !='primarykey' && (formItem.pmofType)?? && formItem.pmofAllowGenerater==1>
<@filedJson formItem=formItem index=formItem?index></@filedJson>
<#if formItem.options.attachField??>
<#list formItem.options.attachField as _formItem>
<#if _formItem?index == 0 >,</#if><@filedJson formItem=_formItem index=_formItem?index></@filedJson>
</#list>
</#if>
<#elseif formItem.type =='grid'>
<#list formItem.columns as col>
<#list col.list as __formItem>
<@filedJson formItem=__formItem index=__formItem?index></@filedJson>
</#list>
</#list>
</#if>
</#list>
]
<#macro filedJson formItem index>
<#if index gt 0 >,</#if>{
"model":"${JavaBeansUtil.getCamelCaseString(formItem.model,false)}",
"key":"${formItem.model}",
"field":"${formItem.model}",
"javaType":"${formItem.pmofJavaType}",
"jdbcType":"${formItem.pmofType}",
"name":"${formItem.name}",
"type":"${formItem.type}",
<#if formItem.type == "date">
"fmt":"${formItem.options.format}",
</#if>
<#if formItem.type == "date" && (formItem.options.type=="daterange" || formItem.options.type=="datetimerange") && formItem.options.attachField??>
"endModel":"${formItem.options.attachField[0].model?lower_case}",
</#if>
<#if formItem.type == "time" && formItem.options.isRange && formItem.options.attachField??>
"endModel":"${formItem.options.attachField[0].model?lower_case}",
</#if>
"length":"${formItem.pmofLength?default(0)?c}",
"isShow":"<#if formItem.options.table.show??>${formItem.options.table.show?c}<#else>false</#if>"
}
</#macro>
5.modelSql
<#list project.projectModelObjects as projectModelObject>
<#if projectModelObject?? && projectModelObject.pmoAllowGenerate==1>
<#--_prefix判断是否提前配置好了前缀,一般用于自定义模型 -->
<#assign _tableName= '{model}' + projectModelObject.pmoTableName?upper_case/>
-- ${projectModelObject.pmoTableName?upper_case}
CREATE TABLE `${_tableName}` (
`id` BIGINT(19) UNSIGNED NOT NULL AUTO_INCREMENT,
<#list projectModelObject.projectModelObjectFields as projectModelObjectField>
<#if !ignoreFields?seq_contains(projectModelObjectField.pmofName?lower_case) && projectModelObjectField.pmofAllowGenerater==1>
<#if projectModelObjectField.pmofType == "DATE" || projectModelObjectField.pmofType == "DATETIME" || projectModelObjectField.pmofType == "TIME" || projectModelObjectField.pmofType == "TEXT">
`${projectModelObjectField.pmofName}` ${projectModelObjectField.pmofType} DEFAULT NULL COMMENT '${projectModelObjectField.pmofDescription}',
<#elseif projectModelObjectField.pmofType == "DECIMAL">
`${projectModelObjectField.pmofName}` ${projectModelObjectField.pmofType?upper_case}(${projectModelObjectField.pmofLength?c},3) DEFAULT NULL COMMENT '${projectModelObjectField.pmofDescription}',
<#else>
`${projectModelObjectField.pmofName}` ${projectModelObjectField.pmofType?upper_case}(${projectModelObjectField.pmofLength?c}<#if projectModelObjectField.pmofType == "DOUBLE">,0</#if>) DEFAULT NULL COMMENT '${projectModelObjectField.pmofDescription}',
</#if>
</#if>
</#list>
`LINK_ID` BIGINT(20) DEFAULT NULL,
<#--判断是否是自定义表单-->
<#if projectModelObject.pmoTree>
`LEAF` INT(1) NULL DEFAULT NULL COMMENT '是否是叶子节点',
`TOP_ID` BIGINT(20) NULL DEFAULT NULL COMMENT '顶级ID',
`PARENT_ID` VARCHAR(255) CHARACTER SET UTF8 COLLATE UTF8_GENERAL_CI NULL DEFAULT NULL COMMENT '父节点',
`PARENT_IDS` VARCHAR(255) CHARACTER SET UTF8 COLLATE UTF8_GENERAL_CI NULL DEFAULT NULL COMMENT '父类型编号,多个ID逗号',
</#if>
`CREATE_DATE` DATETIME DEFAULT NULL COMMENT '创建时间',
`CREATE_BY` VARCHAR(50) DEFAULT NULL COMMENT '创建人',
`UPDATE_DATE` DATETIME DEFAULT NULL COMMENT '修改时间',
`UPDATE_BY` VARCHAR(50) DEFAULT NULL COMMENT '修改人',
`DEL` INT(1) DEFAULT 0 COMMENT '删除标记',
PRIMARY KEY (`ID`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC COMMENT='${projectModelObject.pmoDescription}';
ALTER TABLE `${_tableName}`
ADD UNIQUE INDEX `${_tableName}_UNIQUE_LINK_ID`(`LINK_ID`) USING BTREE COMMENT 'link_id唯一索引';
</#if>
</#list>
widget标签
widget标签用于获取一些组件的特殊代码,用法较为固定,实际开发模板过程中并不常用
如示例1的searchJson模板,widget标签用法为: <@widget field="getWidget*" * ></@widget>
其中field参数目前有五种写法:
1.getWidgetHtml,用于获取组件html代码,同样也常用于html模板中,固定写法 <@widget field="getWidgetHtml" formItem>
(formItem是变量,具体根据实际模板中获取的值进行填写,所有模板都有html部分)
2.getWidgetHead,获取组件head代码,常用于html模板头部获取额外资源文件,固定写法 <@widget field="getWidgetHead"></@widget>
。(目前只有高德地图和mce编辑器两个组件有需要)
3.getWidgetOnload,获取组件预加载代码,常用于vue实例外在页面初始化时执行,固定写法 <@widget field="getWidgetOnload"></@widget>
(目前只有高德地图组件有需要)
4.getWidgetCss,获取组件css代码,用于底部style代码中,固定写法 <@widget field="getWidgetCss"></@widget>
(目前只有mce编辑器有需要)
5.getWidgetVue,获取组件在vue实例中特殊代码部分,常用于scrip代码中。格式 <@widget field="getWidgetVue" key="data"></@widget>
(所有组件都有自己的vue对象)。
key值可以是 data、form、watch、method、created、get、rule、computed、component、save、saveValidate、condition,参考示例2。
[!tip]虽然一个组件不会包含所有值,但是在编写模板的时候应尽量考虑通用性,如getWidgetCss,尽管可能不会用到mce编辑器,但模板中应使用widget标签取值确保模板通用性
可用参数
在模板中通过${}来获取项目或表单以及组件的参数值,除此之外还有一些其他的固定可用参数。
- beanName,首字母小写驼峰式的表名称 如peopleLog
- localEnable,是否国际化,布尔类型可以直接在if标签里当做条件判断
- title,表单名称(只有预览的时候有效)
- ignoreFields,排除字段,固定值 {"id", "del", "update_date", "update_by", "create_date", "create_by", "rand_code"}
- JSON,json工具类,可以在freemarker中通过 ${JSON.parse(str)} 的方式执行工具类方法输出,可用方法有:
parse(Object o)
、toJSONString(String str)
- UnicodeUtil,unicode工具类,同json工具类,可用方法参考hutool5.8.9版本cn.hutool.core.text.UnicodeUtil
- JavaBeansUtil,bean工具类,同json工具类,可用方法有:
getCamelCaseString(String str)
转首字母小写驼峰式 - ConversionUtil,字符串转换工具类,同json工具类,可用方法有:
humpToLine(String str,String format)
驼峰转成单词之间用format隔开、toJSONStr(String str)
将字符串转换成json格式,就算不是json格式也会转义"、'并且用双引号包裹 - project,项目数据(下面详解)
- table,表单数据(下面详解)