├── web-project # web项目源码根目录
│ ├── mock # 项目mock 模拟数据
│ │ ├── json
│ │ │ └── createPage.js # 生成器对应的expressjs 服务
│ │ └── mock-server.js # mock-server加载器
│ ├── src
│ │ ├── apis # ajax
│ │ │ └── create-page.js # 生成器对应的ajax
│ │ ├── mixins
│ │ │ └── create.js # 生成器视图的构建核心方法
│ │ └── views
│ │ │ └── create
│ │ │ │ ├── components
│ │ │ │ │ ├── item-setting # 表单元素配置组件存放目录
│ │ │ │ │ │ ├── _com # 表单元素配置组件基础组件
│ │ │ │ │ │ ├── element-ui # 基于 element-ui 规则的表单配置组件
│ │ │ │ │ │ │ └── set-select.vue # select 组件配置功能组件
│ │ │ │ │ │ └── element-plus # 基于 element-plus 规则的表单配置组件
│ │ │ │ │ │ └── set-datePicker.vue # datePicker 组件配置功能组件
│ │ │ │ │ ├── form-item-valid.vue # 表单元素验证(valid)组件
│ │ │ │ │ └── global-item-setting.js # 表单元素配置加载器
│ │ │ │ ├── dialog
│ │ │ │ │ └── dialog-form-item-setting.vue # 表单元素配置组件
│ │ │ │ ├── create-form.vue # 表单类视图创建功能组件
│ │ │ │ ├── create-info.vue # 详情类视图创建功能组件
│ │ │ │ ├── create-list.vue # 列表类视图创建功能组件
│ │ │ │ └── index.vue
├── server # 服务端根目录
│ ├── www # web项目生产环境目录(编译后的代码)
│ ├── create_cfg_tmpl # 生成器模板及配置文件目录
│ │ ├── vue2 # vue2 + element-ui 项目模板
│ │ │ ├── config # 生成器配置文件目录
│ │ │ │ ├── config-form-valid-msg.js # 表单验证(valid)提示信息配置
│ │ │ │ └── config.js # 全局配置文件
│ │ │ └── template # 生成器模板目录
│ │ │ │ ├── base # 基础模板
│ │ │ │ │ └── form # 表单元素模板
│ │ │ │ ├── dialog # 弹窗类模板
│ │ │ │ └── page # 页面视图类模板
│ │ ├── vue3 # vue3 + element-plus 项目模板
│ │ │ ├── config # 生成器配置文件目录
│ │ │ └── template # 生成器模板目录
| ├── import_cfg.js # 导入配置文件
| ├── web-server.js # web服务
| └── main.js # 主服务
模板文件在server/create_cfg_tmpl/**/template/ 目录下,修改后不用重启即可生效;
注意:web-project/public/tmpl_cfg 及 server/www/tmpl_cfg 目录下的配置文件及模板均为临时文件,修改无效,npm run xx 启动时,这部分会被server/create_cfg_tmpl 中与 xx 匹配的文件完全覆盖;
${{value}}
${{data.key}}
${{data['key']}}
${{a ? b : c}}
${{a || b}}
${{a + b}}
// 或; 与上面的输出效果一致
<%= value %>
<%= data.key %>
<%= data['key'] %>
<%= a ? b : c %>
<%= a || b %>
<%= a + b %>
${{@ value }}
// 或
<%- value %>
${{if value}} ... ${{/if}}
${{if v1}} ... ${{else if v2}} ... ${{/if}}
${{each target}}
${{$index}} ${{$value}}
${{/each}}
{{set temp = data.sub.content}}
// 或;
<% var temp = data.sub.content; %>
{{include './header.art'}}
{{include './header.art' data}}
// 注意模板的根路径为 /template 目录, 例如:include 'header.art' 会自动匹配到 /template/header.art
// 相对路径的模板引入,例如:include './header.art' ,且假设当前模板目录为 /template/base, 会自动匹配到 /template/base/header.art
以VUE的 router 对象模板为例
{
path: '/${{lever1Path}}',
component: Layout,
name:'${{lever1RouterName}}',
meta: {
title: '${{lever1PageName}}',
icon: 'el-icon-orange',
code: '' // 设置code即可启用权限控制
},
redirect: '${{lever2Path}}',
children: [
{
path: '${{lever2Path}}',
component: () => import('@/views/${{filePath}}'),
name: '${{lever2RouterName}}',
meta: { title: '${{lever2PageName}}', code: '' }
}
]
}
{
name, // api名称
nameHump, // api名称, 大驼峰格式
nameToPathfilter, // 过滤后的api名称,主要用于生成 view 页面
nameToPath, // 连接符(-)的 api名称
desc, // 接口描述
type, // 请求类型 get|post|del|put, 注意delete统一转换为del
uri, // 接口地址
fileName, // API 所属分组 tags,连接符(-)格式,用于生成api文件名
fileNameHump, // API 所属分组 tags, 大驼峰格式
fileDesc // API 所属分组的描述
}
{
rootPath, // 项目根路径
formData: [ // 列表页检索项
{
column, // 列名 示例:username
isShow, // 是否显示
opts: {
range: range_, // 双日历配置项
valid: null, // 表单验证配置项
attr: null // 可选,表单组件配置属性
}, // 配置项
needValidateOpts: false, // 是否需要验证配置项
label, // 示例:用户名
labelDesc, // 字段描述 示例:用户名,英文字母
columnType, // 类型 string|integer|array
formItemType, // 表单元素类型 input|dataPicker 对应配置文件中 formItemOpts
range: { // 默认: null , 仅当是双日历表单组件时返回
f_: '__DateTime', // 所执行的自动匹配模式 ,注意仅 'startDateTime' 与 'endDateTime' 可自动匹配为 '__DateTime';'startDateTime' 与 'dateTimeEnd' 不会自动匹配
to_: 'endDateTime', // 自动匹配到的对象列名称
isDatePickerRange: 'true|false', // 是否是双日历
isStart: 'true|false' // 是否是start 否则为end
}
}
],
tableData: [ // 列表项
{
column,
label,
columnType,
isShow,
showTips, // 单元格内容溢出后是否显示气泡
alignType // 列文本对齐方式 ,默认数字居中,文本居左;参照配置文件中 listPage 的配置
}
],
tableConfig: { // 列表页全局配置
showBtnCol: false, // 显示按钮列
showNumCol: true, // 显示序号列
showFormRightBtns: false // 显示表单右侧按钮区域
},
apiConfig, // 参照上方 API 预设变量的说明
}
模板函数可全局访问,且不区分模板类型,任何类型的模板中均可访问;但是要注意其接收的参数需要满足;
// 方法 :$imports.getCusPluginsListImport(formData) ; 其中 formData 为预设页面模板变量
// 示例 :
// 1、声明变量
<% var _CusPluginsList_ = $imports.getCusPluginsListImport(formData) %>
// 2、注入自定义组件
${{each _CusPluginsList_}}
import ${{$value.name}} from '${{$value.path}}'
${{/each}}
// Tips: $value.name 与 $value.path 分别对应配置文件中 formItemOpts 对象的 value 与 path
// 3、注册自定义组件
export default {
components: {
${{each _CusPluginsList_}}${{$value.name}},${{/each}}
}
}
// 方法 :$imports.getValidationListImport(formData) ; 其中 formData 为预设页面模板变量
// 示例 :
// 1、声明变量
<% var _ValidationList_ = $imports.getValidationListImport(formData) %>
// 2、绑定到form组件
<el-form ref="claFrom" :model="claForm"
${{if _ValidationList_.length}} :rules="rules" ${{/if}}
label-width="auto" />
// 3、在data()中声明 rules
${{if _ValidationList_.length}}
rules:{
${{include 'base/base-form-rule.art' _ValidationList_}}
},
${{/if}}
方法 :$imports.getFormItemAttr(itemOpts) ; 其中 itemOpts 为预设页面模板变量: opts.attr
描述:返回属性的键值对字符串,注意此方法不会返回以'_'开头并且以 '_'结尾 (/^_[\w-]+_$/)的私有属性或临时变量
示例 :
<el-input
v-model="claForm.${{column}}"
placeholder="${{label}}"
${{@ opts.attr | getFormItemAttr}}
/>
// 详见模板 template/base/base-plugins-data-source.art
// 用法:
<% var _pluginsDataSourceList_ = $imports.getPluginsDataSourceImport(formData) %>
${{each _pluginsDataSourceList_}}
// ${{$value.label}}
${{$value.name}}:${{@ $value.data}}${{if _pluginsDataSourceList_.length-1 != $index}},${{/if}}
${{/each}}
// 输出示例:
userStatusOpts:[
{
value: '1',
label: '正常'
},
{
value: '2',
label: '禁用'
}
]
// 用法:
${{each formData}} ${{$value.column}}: ${{@ $value.columnType | getFormItemValue }} ${{if formData.length-1 != $index}},${{/if}} // ${{@ $value.label}}
${{/each}}
// 输出示例:
name: '', // 学生姓名
city: '', // 城市
phone: '', // 手机号
edu: [] // 教育经历
用于可视化配置form表单元素的属性,例如:文本框类型,时间格式等
如果目前预设的UI组件配置属性,与您使用的UI组件属性不匹配,可通过新建UI组件分组的方式解决;例如新建 Ant Design Vue ;
1、在 components/item-setting 目录下新建一个 ant 文件夹;
2、在 ant 文件夹下新建一个组件配置文件,例如:set-input.vue;文件名格式为set-xxx.vue; 其中xxx是form组件名称,且需在config.js -> formItemOpts 中声明(常用的都已声明);
3、【可选】在 set-input.vue 中编写组件配置模板,具体参照 element-ui 下的配置文件;例如:
<template>
<el-form
ref="form"
:model="formData"
label-width="120px"
>
<el-form-item label="前置元素">
<el-input v-model="formData._prependVal_" :disabled="formData._prependType_ === ''">
<el-select
slot="prepend"
v-model="formData._prependType_"
class="cus-allowed"
style="width: 120px;"
>
<el-option label="不使用" value="" />
<el-option label="文字" value="text" />
<el-option label="图标按钮" value="btnIcon" />
</el-select>
<el-button
v-if="formData._prependType_ === 'btnIcon'"
slot="append"
icon="el-icon-search"
@click="$message('暂未支持图标库选取,请输入icon的class')"
/>
</el-input>
</el-form-item>
<!-- 后置元素 -->
</el-form>
</template>
<script>
export default {
name: 'SetInput',
inject: ['itemSetIns'],
data() {
return {
rowData: this.itemSetIns.rowData, // 【原型链对象】,当前操作行的数据,直接修改原型链
tmpRowData: this.itemSetIns.tmpRowData,
/* 注意以 '_'开头并且以 '_'结尾 (/^_[\w-]+_$/)的属性为私有属性,或临时变量,不会出现在模板属性过滤器 getFormItemAttr 中 */
formData: {
_prependType_: '', // 前置对象的类型
_prependVal_: '', // 前置对象的值
_appendType_: '',
_appendVal_: ''
},
tmpFormData: {},
/* 需要覆写的属性名称, 即在formDate中修改了组件默认属性值的属性的名称, 例如 'type';注意:具有非空值的私有属性(_xx_)会自动应用,无需维护在这里 */
rewriteAttr: []
}
},
mounted() {
this.init()
},
methods: {
init() {
if (this.tmpRowData.opts.attr) {
Object.assign(this.formData, this.tmpRowData.opts.attr || {})
}
},
/**
* 配置组件提交方法,此方法中修改数据原型链
* @return Boolean 返回 true 则关闭弹窗,其它则无;
*/
toSubmitFn() { // 直接修改原型链
if (this.formData._prependType_ === '' && this.formData._appendType_ === '') {
this.rowData.opts.attr = null
} else {
const tmp = {}
const oldData = this.$options.data.call(this).formData
// 只保留当前最新的属性,忽略已移除的历史属性和组件默认值,以减少冗余输出。
const tempData = JSON.parse(JSON.stringify(this.formData))
Object.keys(oldData).forEach(k => {
if (this.rewriteAttr.includes(k) || /^_[\w-]+_$/.test(k) || tempData[k] !== oldData[k]) {
tmp[k] = tempData[k]
}
})
this.rowData.opts.attr = { ...tmp }
}
return true
}
}
}
</script>
4、在 server/create_cfg_tmpl 下创建文件夹 ant , 并将VUE2(js语法) 或 VUE3(TS语法) 目录下的文件拷贝过来;
5、【可选】修改 config/config.js 中的配置信息,主要是修改文件路径和后缀名;
6、在 server/create_cfg_tmpl 下找到input.art 文件,修改如下:
// 原 vue2 模板的源码
<el-input
${{if columnType == 'integer'}} v-model.number="claForm.${{column}}" ${{else}} v-model.trim="claForm.${{column}}" ${{/if}}
placeholder="${{label}}"
style="width:120px;"
${{@ opts.attr | getFormItemAttr}}
>
${{if opts.attr?._prependType_ == 'text'}}
<template slot="prepend">${{opts.attr._prependVal_ || 'text'}}</template>
${{/if}}
${{if opts.attr?._prependType_ == 'btnIcon'}}
<el-button slot="prepend" icon="${{opts.attr._prependVal_ || 'el-icon-search'}}"></el-button>
${{/if}}
// 修改后的匹配 Ant Design Vue 模板的源码
<a-input v-model:value="claForm.${{column}}"
placeholder="${{label}}"
style="width:120px;"
${{@ opts.attr | getFormItemAttr}}
>
${{if opts.attr?._prependType_ == 'text'}}
<template #addonBefore>
${{opts.attr._prependVal_ || 'text'}}
</template>
${{/if}}
${{if opts.attr?._prependType_ == 'btnIcon'}}
<template #addonBefore>
<${{opts.attr._prependVal_ || 'setting-outlined'}} />
</template>
${{/if}}
</a-input>
1、在 config/config.js -> formItemOpts 中声明需要新增的自定义form组件, 例如:cusDatePicker
/**
* 表单元素类型
* disabled : 在构造模板中,不可使用(暂不支持)的元素
* [undefined] : 不禁用, 默认值;
* [boolean] true : 全部禁用;
* [string] 只有完全匹配的才禁用,例如:'list|form' 则表示为 list 或 form 禁用;
* path : 可选,自定义组件的路径 例如:'@/components/cusDatePicker/index.vue'
* valid : 必须,表单验证配置
* -- trigger: 必须,触发方式 'blur' 、'change' 等
* -- type: 可选,数据类型 'string'(默认)、'date'、'array' 等
* dataSource : 可选,数据源配置,例如:下拉选择框的选项
* -- dataType: 必须,数据格式,默认array,可选:array、object
* -- default: 可选,数据源默认值,例如:下拉选择框的默认值 [{value: null, label: '全部'}] ; 如果没有对组件进行配置,则使用默认值
*/
formItemOpts: [
{
value: 'cusDatePicker',
label: '日期选择器(双)',
path: '@/components/cusDatePicker/index.vue',
valid: {
trigger: 'change',
type: 'date'
}
}
]
2、【可选】创建组件配置模板,如果不需要可视化配置,则跳过此步骤
在目录 /template/base/form 下创建名为 cusDatePicker.art 的文件,注意文件名称必须与上面声明的 formItemOpt中的名称一致;
// 继承 inject: ['itemSetIns'] ,其指向 dialog-form-item-setting.vue 的实例(this);
inject: ['itemSetIns'],
data () {
return {
rowData: this.itemSetIns.rowData, // 【原型链对象】,当前操作行的数据,直接修改原型链
tmpRowData: this.itemSetIns.tmpRowData, // rowData的克隆对象,submit 后才修改数据
rowList: this.itemSetIns.rowList, // 【原型链对象】,当前操作行的数据,直接修改原型链
/* 注意以 '_'开头并且以 '_'结尾 (/^_[\w-]+_$/)的属性为私有属性,或临时变量,不会出现在模板属性过滤器 getFormItemAttr 中 */
formData: {
_start_: '',
_end_: '',
type: 'daterange',
format: 'yyyy-MM-dd',
'value-format': 'yyyy-MM-dd'
//。。。 可配置的属性
},
tmpFormData: {}
。。。
}
},
methods: {
init () {
Object.assign(this.formData, this.tmpRowData.opts.attr || {})
// 。。。 组件的业务逻辑代码
},
/**
* 配置组件提交方法,此方法中修改数据原型链
* @return Boolean 返回 true 则关闭弹窗,其它则无;
*/
toSubmitFn () {
// 。。。 组件的业务逻辑代码
return true
},
/**
* 自动初始化配置项
* 【内置钩子,静态工具类】【原型链修改】
* 组件在被初始化前,会调用此方法,用于初始化配置项,此方法会在组件实例化前调用,所以无法访问组件实例,并且其中的 this 指向的是 methods 对象;
* 尽量不要在此方法中使用this,或不在当前实例中使用此方法,以免造成this指向错误
* 可访问的全局变量:
* _$cusConfig$_ 配置项,对应config.js中的配置项
* _$cusComponents$_ 组件列表,对应item-setting 目录下的组件列表;_$cusComponents$_['SetInput'] 为 SetInput 组件,注意不是组件实例
* 访问组件内的方法: _$cusComponents$_['SetInput'].methods.__autoInitConfig, 注意:被访问的方法中不能使用this
* 访问组件内的data: _$cusComponents$_['SetInput'].data.call({}), 注意:不能获取data中包含this的引用,均为undefined 或 异常
* @param {Object} row 当前操作行的数据 【原型链修改】 其中 row.formItemType 始终为修改后的新值,而 __toResetFn 方法中的 row.formItemType 始终为修改前的旧值
* @param {Array | false} tableDataSearch 当前视图的数据 【原型链修改】,如果是false则表示只操作当前行的数据,不操作关联行的数据
* @param {Object} other 其它参数
*/
__autoInitConfig (row, tableDataSearch = [], other = {}) {
// 示例,注意示例功能为判断下拉框是否是多选模式,与组件 cusDatePicker 无关; 因为cusDatePicker 的这部分代码比较多,不方便作为示例展示;
if (row.formItemType === 'select' && row.columnType === 'array') { // 数组类型的select,默认多选
row.opts.attr = Object.assign({}, (row.opts.attr || {}), {
multiple: true // 是否多选
})
}
},
/**
* 重置关联数据的配置项
* 注意当前操作行的数据,或默认自动重置不需要在这里处理(组件私有数据除外),这里只处理关联数据的配置项
* 例如 开始时间与结束时间的关联,开始时间的配置项修改后,同时需要在这里重置结束时间的配置项
*/
__toResetFn (row, tableDataSearch = [], newFormItemType) {},
/**
* 选择组件类型
* 【内置钩子,静态工具类】注意不能修改 row 的值
* @param {object} row 当前操作行的数据
* @param {object} other 其它参数
* @return {number | null} 返回值为null则不做任何操作,返回值为数字则代表匹配优先级,数字越大优先级越高
*/
__formItemTypeChoice (row, other = {}) {
if (!config_.formFieldDetection.findDate) {
return null
}
const name_ = row.name || ''
if (/(date|time)/i.test(name_) && other.rangeObj[name_] && other.rangeObj[name_].isDatePickerRange) { // 日期范围
return 5
}
return null
},
/**
* 验证方法
* 验证组件配置信息是否有效
* @param {Object} row 当前操作行的数据, 禁止修改
* @param {Array} tableDataSearch 当前视图的数据, 禁止修改
* @return {Boolean} 返回 false 则验证不通过
*/
__validationFn (row, tableDataSearch = []) {
if (!row.opts.range) {
this.$alert(`字段【${row.column}】未指定【开始时间或结束时间】`, '错误')
return false
}
}
}
$imports.comConfig
输出作者 ${{$imports.comConfig.author}}
{
url: '${{apiConfig.uri | getMockRouterUrl}}', // getMockRouterUrl 返回带参数匹配规则的mock路由地址, 详见express.js官网
type: '${{apiConfig.type}}',
response: config => {
if (!demo.__cacheKey__['${{apiConfig.name}}']) {
const tobj = {
${{@ tableData | getMockRouterResData}} // getMockRouterResData 返回mock数据
}
demo.demoFnGetTheListKey(tobj) // mock 数据字段猜想匹配关系,返回字段对应类型的mock数据,例如 数字、字符串、省份、城市、手机号等
demo.__cacheKey__['${{apiConfig.name}}'] = { ...tobj }
}
return demo.demoFnMakeListPageData('${{apiConfig.name}}', config.query) // 处理分页、检索
}
}