jakhuang / form-generator Goto Github PK
View Code? Open in Web Editor NEW:sparkles:Element UI表单设计及代码生成器
Home Page: https://jakhuang.github.io/form-generator
License: MIT License
:sparkles:Element UI表单设计及代码生成器
Home Page: https://jakhuang.github.io/form-generator
License: MIT License
强烈建议支持table组件,在后台管理中,table用的地方太多了,
echart,现在很多项目都在用,希望能支持
你好,在vue的ui库中ant也是国内用的比较广的,有没有计划支持ant( https://github.com/vueComponent/ant-design-vue 以及 react的 https://github.com/ant-design/ant-design )?
封装一个vscode插件,让form-generator拥有写入文件的能力。
小白求救
本文描述的解析器,是一个能将form-generator导出的json表单,解析为一个真实表单的程序。
接下来的行文中使用【json表单】表示form-generator导出的json表单。
剧透:本文其实就是带大家阅读parser.vue源码,哈哈。
json表单目前支持两种布局:
colFormItem和rowFormItem
colFormItem布局(以el-input为例)对应的json形式如下:
{
"__config__": {
"label": "单行文本",
"labelWidth": null,
"showLabel": true,
"changeTag": true,
"tag": "el-input",
"tagIcon": "input",
"required": true,
"layout": "colFormItem",
"span": 12,
"document": "https://element.eleme.cn/#/zh-CN/component/input",
"regList": [{
"pattern": "/^1(3|4|5|7|8|9)\\d{9}$/",
"message": "手机号格式错误"
}]
},
"__slot__": {
"prepend": "",
"append": ""
},
"__vModel__": "mobile",
"placeholder": "请输入手机号",
"style": {
"width": "100%"
},
"clearable": true,
"prefix-icon": "el-icon-mobile",
"suffix-icon": "",
"maxlength": 11,
"show-word-limit": true,
"readonly": false,
"disabled": false
}
colFormItem布局对应的目标实际代码如下 :
<el-col :span="12">
<el-form-item label="单行文本" prop="mobile">
<el-input v-model="formData.mobile" placeholder="请输入手机号" :maxlength="11" show-word-limit clearable
prefix-icon='el-icon-mobile' :style="{width: '100%'}"></el-input>
</el-form-item>
</el-col>
在这个json到xml的解析过程中,form-generator的parser使用jsx来完成
...
const layouts = {
colFormItem(h, scheme) {
const config = scheme.__config__
const listeners = buildListeners.call(this, scheme)
let labelWidth = config.labelWidth ? `${config.labelWidth}px` : null
if (config.showLabel === false) labelWidth = '0'
return (
<el-col span={config.span}>
<el-form-item label-width={labelWidth} prop={scheme.__vModel__}
label={config.showLabel ? config.label : ''}>
<render conf={scheme} {...{ on: listeners }} />
</el-form-item>
</el-col>
)
},
...
}
rowFormItem布局对应的json形式如下:
{
"__config__": {
"layout": "rowFormItem",
"tagIcon": "row",
"layoutTree": true,
"document": "https://element.eleme.cn/#/zh-CN/component/layout#row-attributes",
"span": 12,
"formId": 104,
"renderKey": 1594570310282,
"componentName": "row104",
"children": []
},
"type": "default",
"justify": "start",
"align": "top"
}
对应的目标代码如下:
<el-col :span="12">
<el-row>
</el-row>
</el-col>
同样使用jsx来完成布局解析:
rowFormItem(h, scheme) {
let child = renderChildren.apply(this, arguments)
if (scheme.type === 'flex') {
child = <el-row type={scheme.type} justify={scheme.justify} align={scheme.align}>
{child}
</el-row>
}
return (
<el-col span={scheme.span}>
<el-row gutter={scheme.gutter}>
{child}
</el-row>
</el-col>
)
}
值得注意的是,json表单支持嵌套; 通过__config__.children记录嵌套关系。使用renderChildren递归解析。(目前仅对rowFormItem布局的children做解析)
function renderChildren(h, scheme) {
const config = scheme.__config__
if (!Array.isArray(config.children)) return null
return renderFormItem.call(this, h, config.children)
}
完整的代码,请阅读parse源码,此链接中的版本并不算复杂。
传统的vue格式表单,我们可能需要写如下格式的js,完成element UI表单的数据和逻辑。
export default {
data() {
return {
formData: {
mobile: undefined,
field103: undefined,
},
rules: {
mobile: [{
required: true,
message: '请输入手机号',
trigger: 'blur'
}, {
pattern: /^1(3|4|5|7|8|9)\d{9}$/,
message: '手机号格式错误',
trigger: 'blur'
}],
field103: [{
required: true,
message: '请输入密码',
trigger: 'blur'
}],
},
}
},
methods: {
submitForm() {
this.$refs['elForm'].validate(valid => {
if (!valid) return
// TODO 提交表单
})
},
resetForm() {
this.$refs['elForm'].resetFields()
},
}
}
对于解析器来说,这是一个抽象的过程:
data() {
const data = {
formConfCopy: deepClone(this.formConf),
[this.formConf.formModel]: {},
[this.formConf.formRules]: {}
}
this.initFormData(data.formConfCopy.fields, data[this.formConf.formModel])
this.buildRules(data.formConfCopy.fields, data[this.formConf.formRules])
return data
},
上边的一系列操作,都是建立在理解json表单都有哪些内容的基础上的。详细请参阅JSON参数对照表
render.js就是对vue的render函数的简单定制封装。如果你还不理解vue的render函数,请移步至:渲染函数 & JSX
render.js实现的功能是将json表单中的__config__.tag解析为具体的vue组件; 其工作过程可以理解为以下3个部分:
render(h) {
const dataObject = makeDataObject()
const confClone = deepClone(this.conf)
const children = []
// 1 如果slots文件夹存在与当前tag同名的文件,则执行文件中的代码
mountSlotFlies.call(this, h, confClone, children)
// 2 将字符串类型的事件,发送为消息
emitEvents.call(this, confClone)
// 3 将json表单配置转化为vue render可以识别的 “数据对象(dataObject)”
buildDataObject.call(this, confClone, dataObject)
return h(this.conf.__config__.tag, dataObject, children)
}
更多细节:源码render/render.js
本项目仅仅是开了个表单渲染头;实际要根据需求的差异,要做不一样的定制。之所以在issue写文章,是希望各位能充分利用好下边的评论功能,大家友好探讨。
系列教程:
《表单设计器 · 开发教程》
《表单解析器 · 开发教程》
《vue代码生成器 · 开发教程》
《vue代码预览器 · 开发教程》
目前只有设计器的教程,剩下的教程目前都没有,希望能完善下,拜谢
建议增加给表单添加背景颜色和背景图片的功能
如题,现在组件支持自定义事件吗?
目前功能只支持一行一个组件,建议支持一行可以放多个组件,并且每行的组件个数是自定义的
项目使用vue-cli4生成。用到了jsx,所以要对vue render比较熟悉!!! 如果对render和jsx还不熟悉,一定要反复阅读并理解:渲染函数 & JSX。二开对于初学者,有一定的难度。
项目由四部分组成:表单设计器,.vue代码生成器,.vue代码预览器,表单json解析器。
接下来通过添加一个《按钮 el-button》来带大家感受下这四部分。
前置准备:将项目下载到本地,然后安装依赖。如有需要可参阅运行
一、在添加一个新组件前,首先要思考的是,项目中有没有引入该组件?
对于el-button,它是随element UI全局注册的组件,所以不需要再引入。如果是一个没有引入的组件,需要引入,引入方法参阅vue官方文档组件注册
二、将组件添加到表单设计器
确保el-button组件可用后,将其添加到表单设计器。
2.1 在文件src\components\generator\config.js中添加一个布局型组件
...
export const layoutComponents = [
...,
{
__config__: {
label: '按钮',
showLabel: true,
changeTag: true,
labelWidth: null,
tag: 'el-button',
tagIcon: 'button',
defaultValue: undefined,
span: 24,
layout: 'colFormItem',
document: 'https://element.eleme.cn/#/zh-CN/component/button'
},
__slot__: {
default: '主要按钮'
},
type: 'primary',
icon: 'el-icon-search',
round: false,
size: 'medium',
plain: false,
circle: false,
disabled: false
}
]
其中__config__和__slot__是本项目自定义的属性,自定义属性的格式均为__XXX__;
其余属性与el-button组件属性对应;
config.tagIcon中使用的是svg图标。图标来自iconfont,下载后放在src\icons\svg文件夹中。
此时,左侧备选组件会出现【按钮】组件,但是,按钮不能显示文字。
2.2 新建与__config__.tag的值同名的__slot__解析文件el-button.js
src\components\render\slots\el-button.js,代码如下:
export default {
default(h, conf, key) {
return conf.__slot__[key]
}
}
default函数解析将json配置中的default属性:
__slot__: {
default: '主要按钮'
}
解析为按钮上的文字。
此时,中间设计器中,按钮上的文字已经可以显示出来了。但是,右侧面板中,可配置属性还比较少,需要添加属性配置。
__slot__解析文件是支持jsx语法的,本例中表现的不够具体,更多的使用方式可以翻阅源码中slots文件夹;其中el-input.js代表性强,建议理解。
__slot__的解析流程设计得比较绕,主要的出发点是为了:保证表单的配置是纯json格式的,方便数据库存储和用户配置。这里的【用户】指的是:没有编程基础的普通用户。
2.3 接下来我们让设计器支持type,icon等组件属性的可视化修改。
在src\views\index\RightPanel.vue中添加相应的编辑表单项。
2.3.1 type属性配置项:
<el-form-item
v-if="activeData.type !== undefined && activeData.__config__.tag === 'el-button'"
label="按钮类型"
>
<el-select v-model="activeData.type" :style="{ width: '100%' }">
<el-option label="primary" value="primary" />
<el-option label="success" value="success" />
<el-option label="warning" value="warning" />
<el-option label="danger" value="danger" />
<el-option label="info" value="info" />
<el-option label="text" value="text" />
</el-select>
</el-form-item>
2.3.2 size属性配置项:经过检查el-color-picker已经有size属性的配置项了,所以重用原有的就行了。
增加
activeData.__config__.tag === 'el-button'
增加后的配置项如下:
<el-form-item
v-if="activeData.size !== undefined &&
(activeData.__config__.optionType === 'button' ||
activeData.__config__.border ||
activeData.__config__.tag === 'el-color-picker' ||
activeData.__config__.tag === 'el-button')"
label="选项尺寸"
>
<el-radio-group v-model="activeData.size">
<el-radio-button label="medium">
中等
</el-radio-button>
<el-radio-button label="small">
较小
</el-radio-button>
<el-radio-button label="mini">
迷你
</el-radio-button>
</el-radio-group>
</el-form-item>
2.3.3 icon属性配置项:复制el-input的前图标配置项,修改为:
<el-form-item
v-if="activeData['icon']!==undefined && activeData.__config__.tag === 'el-button'"
label="按钮图标"
>
<el-input v-model="activeData['icon']" placeholder="请输入按钮图标名称">
<el-button slot="append" icon="el-icon-thumb" @click="openIconsDialog('icon')">
选择
</el-button>
</el-input>
</el-form-item>
此处使用了openIconsDialog调用封装好的图标选择器,方便快速选取图标。
组件属性的可视化配置是一项需要耐心的操作,以上列举了3个属性的配置,更多的属性也都是配置在RightPanel.vue中。当然,现有的配置方式存在一定的问题,这是需要在以后项目中逐步优化的。
表单设计器的开发流程基本就是上边这三步。config.js配置备选图标;在有使用__slot__时需要编写解析文件;在RightPanel.vue可视化配置组件属性。
接下来,当点击运行按钮的时候,发现新加的组件并不能显示。这是因为没有编写相应的.vue代码生成器生成规则。
本文相关代码,参阅表单设计器 · 开发教程代码
系列教程:
《表单设计器 · 开发教程》
《表单解析器 · 开发教程》
《vue代码生成器 · 开发教程》
《vue代码预览器 · 开发教程》
建议除表单之外的组件也能支持
问题:可以叫表单结构转成json格式么?后端要存呀
例如,提供一个文本输入框,每换行表示一个字段,批量生成组件,默认使用输入框。然后每个组件上再做一个切换组件类型的功能,完美!
有支持 iview的计划么
与 element ui
类似的 antd
使用的也比较多,希望可以支持 😃
调整,设置了很多配置,最后还是没弄出table的效果来,希望能有这种效果
目前有的布局方式:
属性 | 可选性 | 说明 | 默认值 |
---|---|---|---|
__config__.layout |
可选 | 组件使用的布局方式 | colFormItem |
__vModel__ |
必选 | 表单字段的属性名,可自定义 | 系统自增 |
__config__.defaultValue |
可选 | 默认值;与__vModel__ 对应使用,可指定表单字段的默认值;可用于表单数据回填 |
|
__config__.tag |
必选 | 组件名称 | |
__config__.changeTag |
必选 | 是否允许显示切换组件面板 | |
__config__.tagIcon |
必选 | 组件svg图标名称 | |
__config__.label |
必选 | 表单标题 | |
__config__.showLabel |
必选 | 是否显示表单标题 | |
__config__.labelWidth |
必选 | 表单标题区域宽度(px) | |
__config__.required |
必选 | 是否要求表单校验 | |
__config__.regList |
可选 | 表单正则校验;赋值为数组时,显示配置项 | |
__config__.span |
必选 | 24栅格系统,表示组件的栅格数 | |
__config__.children |
可选 | 子组件,目前仅保留字段,实际并没有做解析 | |
__config__.document |
可选 | 组件说明文档地址 | |
__slot__ |
可选 | 对应,需在工程文件夹src\components\render\slots中添加与__config__.tag 同名的.js文件解析该配置。 |
|
其余属性 | 可选 | 根据不同组件的属性灵活配置。属于本组件的属性写在一级(与__config__ 同级);若需自定义属性以达到控制右侧面板或其他目的的,可在__config__ 中自定义属性(如:__config__.showLabel ) |
属性 | 可选性 | 说明 | 默认值 |
---|---|---|---|
__config__.layout |
可选 | 组件使用的布局方式 | colFormItem |
__config__.componentName |
必选 | 组件名,无需操作 | 系统自增 |
__config__.tagIcon |
必选 | 组件svg图标名称 | |
__config__.layoutTree |
可选 | 是否显示布局树 | |
__config__.children |
必选 | 子组件,组件嵌套的关键 | [] |
__config__.document |
可选 | 组件说明文档地址 | |
其余属性 | 可选 | 可参照 el-row属性表按需配置 |
问题:
在表单设计的时候,发现有级联控件下有一个动态数据,但不知道怎么关联
html中使用httpVueLoader加载.vue没有效果,使用普通的vue文件可以成功
级联选择 我想做省市区街道的四级联动,怎么请求接口或者我生成的js省市区街道的js文件?
有没有API将提交的数据再回写回去
可否在增加一些组件
在《表单设计器 · 开发教程》el-button已经可以可视化配置属性了。如果你仅仅想使用json格式的表单配置,可以跳过本文,直接阅读《表单解析器 · 开发教程》。
本文将继续完成vue代码生成器部分的教程。
点击【导出vue文件】按钮的时候,需要选择一个【生成类型】。说明目前支持生成,文件和弹框两种类型的代码。其实文件类型的代码用el-dialog包裹下就是弹框类型的代码了。
而生成代码的本质就是简单的字符串拼接。分别拼接出html、js、css三种类型的代码,最后组装成vue代码。
代码生成器中大量使用了:es6 模板字符串
一、生成html代码
在文件src\components\generator\html.js中添加el-button的html代码生成规则:
1.1 在tags对象中添加el-button属性,生成html
...
'el-button': el => {
const {
tag, disabled
} = attrBuilder(el)
const type = el.type ? `type="${el.type}"` : ''
const icon = el.icon ? `icon="${el.icon}"` : ''
const round = el.round ? 'round' : ''
const size = el.size ? `size="${el.size}"` : ''
const plain = el.plain ? 'plain' : ''
const circle = el.circle ? 'circle' : ''
let child = buildElButtonChild(el)
if (child) child = `\n${child}\n` // 换行
return `<${tag} ${type} ${icon} ${round} ${size} ${plain} ${disabled} ${circle}>${child}</${tag}>`
},
...
attrBuilder会生成常用的属性,这里与el-button匹配的是tag, disabled;其余属性都是和el-button组件属性对应的,目标是生成字符串:
`<el-button type="success" icon="el-icon-warning" size="medium"> 主要按钮 </el-button>`
1.2 由于按钮内的文字是配置在__slot__中的
__slot__: {
default: '主要按钮'
}
所以相应的应该去读取__slot__.default。为了保持和其他组件的统一,定义了函数buildElButtonChild读取__slot__.default。
在文件src\components\generator\html.js中添加buildElButtonChild函数:
// el-buttin 子级
function buildElButtonChild(scheme) {
const children = []
const slot = scheme.__slot__ || {}
if (slot.default) {
children.push(slot.default)
}
return children.join('\n')
}
写好了tags['el-button']和buildElButtonChild函数后,当再次点击运行按钮预览时,发现el-button组件已经可以预览了。
html.js中的代码都是字符串拼接处理并不高深,如需进一步的处理可以从入口函数
makeUpHtml
顺着结构阅读源码。
二、生成js脚本代码
在文件src\components\generator\js.js中,依然是通过字符串拼接的方式,生成脚本代码。
由于el-button无需js脚本,所以本文用el-input组件做讲解:
假设我们有如下的json配置:
{
__config__: {
tag: 'el-input',
required: true,
regList: [{
pattern: '/^1(3|4|5|7|8|9)\\d{9}$/',
message: '手机号格式错误'
}]
},
__vModel__: 'mobile',
placeholder: '请输入手机号',
}
目标是生成element UI表单校验js代码:
mobile: [{
required: true,
message: '请输入手机号',
trigger: 'blur'
}, {
pattern: /^1(3|4|5|7|8|9)\d{9}$/,
message: '手机号格式错误',
trigger: 'blur'
}]
json配置中有两个校验规则:required和regList,我们要做的代码生成,无非就是将json配置中的key和value,转化成js代码字符串。源码中的转化实现如下:
// 构建校验规则
function buildRules(scheme, ruleList) {
const config = scheme.__config__
if (scheme.__vModel__ === undefined) return
const rules = []
if (ruleTrigger[config.tag]) {
if (config.required) {
const type = isArray(config.defaultValue) ? 'type: \'array\',' : ''
let message = isArray(config.defaultValue) ? `请至少选择一个${config.label}` : scheme.placeholder
if (message === undefined) message = `${config.label}不能为空`
rules.push(`{ required: true, ${type} message: '${message}', trigger: '${ruleTrigger[config.tag]}' }`)
}
if (config.regList && isArray(config.regList)) {
config.regList.forEach(item => {
if (item.pattern) {
rules.push(
`{ pattern: ${eval(item.pattern)}, message: '${item.message}', trigger: '${ruleTrigger[config.tag]}' }`
)
}
})
}
ruleList.push(`${scheme.__vModel__}: [${rules.join(',')}],`)
}
}
上边的函数就是一个json配置key和value的搬运工,很朴实的一段代码,所以js.js中其他生成脚本的代码也不神秘,如有需要放开去看源码就行了,入口函数:
makeUpJs
三、生成css
css部分请直接看源码。文件:src\components\generator\css.js
重点看:
const styles = {
'el-rate': '.el-rate{display: inline-block; vertical-align: text-top;}',
'el-upload': '.el-upload__tip{line-height: 1.2;}'
}
此文件只做了一件简单事情:遍历待生成代码的json表单配置。如果配置中使用了el-rate或el-upload,将他们的css样式生成出去。这就是全部。入口函数:
makeUpCss
如果你要改写某个组件的默认样式,比如el-button,将你需要的css加进styles对象中即可。
本文相关代码,参阅vue代码生成器 · 开发教程代码
系列教程:
《表单设计器 · 开发教程》
《表单解析器 · 开发教程》
《vue代码生成器 · 开发教程》
《vue代码预览器 · 开发教程》
请问自定义表单js中如何增加 自己的import的api 接口啊
JSON解析器啥时候可以支持生成移动端页面呢?:)
希望作者增加栅格布局组件
firefox 下 左侧组件拖拽 会新打卡一个选项卡
您好,请问怎样实现可视化设计器组件之间联动,事件呢
使用的时候预览或导出会报这个错误,然后就没有显示代码,也无法下载
第一次点击运行正常,之后再点击运行就会一直loading状态,回退至6月7日的“结构调整”就正常了。
富文本编辑应该属于比较常用的且功能强大的表单类,支持富文本基本上就可以实现很多功能的自定义了。希望作者大大考虑下
如果,所有的组件渲染都是确定,但是针对动态组件,如何处理,比如:
在表单中有一个单选按钮:A/B/C,当我选择A时,下面需要动态的出现A对应的子选项,比如:省份城市选择
当我选择B时,需要出现下拉框选择:小型车、中型车、大型车、、、
当我选择C时,需要出现文本框:用于用户输入一些信息
请问这种你的表单生成器能支持吗?
如上,不知道是否准备支持IE?
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.