经过低代码平台产出的代码,最终要运行在浏览器上,我们在平台上提供一个预览功能,可以让我们预览拖拽后生成的表单页面。
预览器的功能区由两部分组成:
- 源码预览
- script脚本代码预览
- template模板代码预览
- style样式代码预览
- 页面展示效果预览
效果图如下:
所谓预览就是要模拟真实的使用场景,这里也有两个选择
- 一个是直接产出dist包,在浏览器直接加载即可预览
- 一个是产出源码(vue文件),然后再编译源码,在浏览器预览,优点是可以针对源码进行二开
我们只看第二种方案,因为第二种其实也包括第一种了,产出源码就是要将schema源数据表示的表单转换为我们熟悉的vue单文件组件代码,整体的思路是:
- 将
schema
转换为vue单文件组件代码(字符串) - 使用
eval
执行字符串代码,得到一个vue组件 - 然后将vue组件挂载到页面上,即可预览
源码生成
下面展开来说:
script
脚本代码生成
js部分我们最终的产物应该如下结构所示:
js
const str = `export default {
components: {},
props: {},
data() {
return {
}
},
computed: {},
watch: {},
created() {
},
mounted() {},
methods: {
}
}`
整个结构都与常见的单文件组件的JS脚本部分类似,我们继续分析在js脚本部分需要处理的内容:
- 表单数据初始化,例如
el-form
的model
属性绑定的数据 - 表单校验规则,例如
el-form
的rules
属性绑定的数据
表单的数据初始化
表单数据初始化比较简单,我们只需要将formConfig
中的formModel
属性转换为data()
中的属性即可
js
data() {
return {
${formConfig.formModel}: {
}
}
}
接下来就是要把每个表单项的__vModel__
属性收集起来,绑定到formConfig.formModel
上,而表单项对应的value值为schema
中的defaultValue
js
data() {
return {
${formConfig.formModel}: {
${fields.map(el => `${el.__vModel__}: ${el.__config__.defaultValue}`).join(',\n')}
}
}
}
表单的校验规则
就像表单数据初始化一样,el-form
的rules
属性,只需要把schema
中的regList
属性转换为rules
属性即可,在data中定义一个formConfig.formRules
属性,用来存放校验规则,然后遍历schema
中的regList
属性,将其插入到formConfig.formRules
即可
js
data() {
return {
${formConfig.formRules}: {
${fields.map(el => {
const rules = []
el.__config__.regList.forEach(rule => {
rules.push({
required: el.__config__.required,
message: rule.message,
trigger: 'blur',
pattern: rule.pattern
})
})
return `${el.__vModel__}: ${JSON.stringify(rules)}`
}).join(',\n')}
}
}
}
表单数据初始化和校验规则的初始化完成后,schema
就转换为vue单文件组件中的的script代码,我们看下最终转换后的js代码:
js
export default {
components: {},
props: {},
data() {
return {
formData: {
mobile: ''
},
rules: {
mobile: [{
required: true,
message: '手机号格式错误',
trigger: 'blur',
pattern: '/^1(3|4|5|7|8|9)\d{9}$/'
}]
}
}
},
computed: {},
watch: {},
created() {},
mounted() {},
methods: {}
}
template
模板代码生成
template
模板代码的产出有点麻烦,我们需要根据schema
中的定义,产出对应的标签,并且需要绑定属性,除此之外还需要把这些标签组合在一起形成一个完整的表单组件
以下面的schema为例:
schmea
{
__config__: {
label: '手机号',
labelWidth: null,
showLabel: true,
changeTag: true,
tag: 'el-input',
tagIcon: 'input',
defaultValue: '',
required: true,
layout: 'colFormItem',
span: 24,
// 正则校验规则
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
}
最终要把上面的schema转换为如下的vue单文件组件中的template
代码:
html
<el-form ref="elForm" :model="formData" :rules="rules" label-width="100px">
<el-form-item label="手机号" required prop="mobile">
<el-input v-model="formData.mobile" clearable placeholder="请输入手机号" :style="{width: '100%'}" maxlength="11"
show-word-limit prefix-icon='el-icon-mobile'>
</el-form-item>
</el-form>
要把schema转换为vue单文件组件代码,可以拆成多个小任务:
- 任务1:构建
el-form
标签及其属性 - 任务2: 构建
el-form-item
并且将schema中的__config__
属性转换为el-form-item
标签的属性 - 任务3: 构建
el-input
并且将schema中的其他属性(非__config__
)转换为el-input
标签上的属性
任务1
构建el-form
标签及其属性需要依赖formConf
中的配置
js
const formConf = {
formRef: 'elForm',
formModel: 'formData',
size: 'medium',
labelPosition: 'right',
labelWidth: 100,
formRules: 'rules',
gutter: 15,
disabled: false,
span: 24,
formBtns: true
}
const disabled = schema.disabled ? `:disabled="${formConf.disabled}"` : ''
let str1 = `<el-form ref="${formConf.formRef}" :model="${formConf.formModel}" :rules="${formConf.formRules}" label-width="${formConf.labelWidth}px" ${labelPosition} ${disabled}>
</el-form>`
上述代码中,str就是最终的el-form
标签及其属性
任务2
构建el-form-item
标签及其属性需要依赖config
中的配置
javascript
let label = `label="${config.label}"`
let labelWidth = `label-width="${config.labelWidth}px"`
const required = config.required ? 'required' : ''
let str2 = `<el-form-item ${label} ${labelWidth} ${required} prop="${scheme.__vModel__}"></el-form-item>`
上面的代码中,str就是最终的el-form-item
标签及其属性
任务3
构建el-input
标签及其属性需要依赖schema
中的配置
javascript
const maxLength = el.maxlength ? `maxlength="${el.maxlength}"` : ''
const tag = el.__config__.tag
const vModel = `v-model="${formConf.formModel}.${el.__vModel__}"`
let str3 = `<${tag} ${vModel} ${disabled} ${maxLength}>`
在构建el-input
的时候,需要把el-input
的v-model
属性绑定到formData
对象中,所以需要把formData
对象中的属性名通过formModel
属性获取,然后拼接起来。
最后我们把任务1、任务2、任务3的代码组合起来,就可以把schema
转换为vue单文件组件代码的template
代码。
js
<str1>
<str2>
<str3>
</str3>
</str2>
</str1>
最终拼接后的template
代码:
html
<el-form ref="elForm" :model="formData" :rules="rules" label-width="100px">
<el-form-item label="单行文本" required prop="mobile">
<el-input v-model="formData.mobile" clearable placeholder="请输入手机号" :style="{width: '100%'}" maxlength="11"
show-word-limit prefix-icon='el-icon-mobile'>
</el-form-item>
</el-form>
通过以上两步,我们就可以把schema转换为vue单文件组件代码,style部分很少修改,这里就不写了
源码预览
代码的预览功能,我们通过beautify
插件和monaco-editor
编辑器来实现。 关于beautify
和monaco-editor
的使用,大家可以自行去问GPT,这里贴下部分代码:
js
loadBeautifier(btf => {
beautifier = btf
this.htmlCode = beautifier.html(this.htmlCode, beautifierConf.html)
this.jsCode = beautifier.js(this.jsCode, beautifierConf.js)
this.cssCode = beautifier.css(this.cssCode, beautifierConf.html)
loadMonaco(mo => {
monaco = mo
this.setEditorValue('editorHtml', 'html', this.htmlCode)
this.setEditorValue('editorJs', 'js', this.jsCode)
this.setEditorValue('editorCss', 'css', this.cssCode)
this.runCode()
})
})
loadBeautifier
是负责加载beautify
插件的,loadMonaco
是负责加载monaco-editor
编辑器的。htmlCode
和jsCode
就是我们之前生成的template
代码。 setEditorValue
是负责将代码设置到编辑器中的,这样就能看到生成的代码。
页面展示效果预览
页面展示效果预览功能我们通过iframe
的形式来实现,在此之前我们已经有了一个完整的单文件组件的代码,只要将这些代码字符串转换为可以运行的代码,就可以预览了。 在vue中,我们可以通过new Vue()
的形式来创建一个组件,然后通过template
属性来指定组件的模板,并添加一些属性,最后通过$mount
来挂载到页面上。 我们的模板字符串大致如下:
js
// preview.js
const str = `{
components: {},
props: {},
data() {
return {
}
},
computed: {},
watch: {},
created() {
},
mounted() {},
methods: {
}
}`
let html = `
<el-form ref="elForm" :model="formData" :rules="rules" label-width="100px">
<el-form-item label="手机号" required prop="mobile">
<el-input v-model="formData.mobile" clearable placeholder="请输入手机号" :style="{width: '100%'}" maxlength="11"
show-word-limit prefix-icon='el-icon-mobile'>
</el-form-item>
</el-form>`
如上所示,str
中的代码就是我们之前生成的单文件组件的js代码,html
就是我们之前生成的template
代码。 那么我们只需要使用eval
来执行str
中的代码,然后通过$mount
来挂载到页面上,就可以预览了。
js
let previewCom = eval(`(${str})`)
previewCom.template = `<div>${html}</div>`
new Vue({
components: {
previewCom: previewCom
},
template: `<div><previewCom/></div>`
}).$mount('#app')
就像上面的代码一样,我们把str
中的代码注册为一个组件,然后在template
中使用这个组件,这样就可以在页面展示效果预览功能中展示出来了。
至此,就实现了手搓低代码表单的预览功能,整体功能分为三个部分:
- 代码生成
- 代码预览
- 效果图预览 每个部分的核心功能都已经梳理了一遍,还有一些细节,我们没有提到,但不影响整体的实现。
我们整个手搓系列到今天也就完结了,这个系列主要讲了如何实现一个低代码表单,包括表单的配置、生成、预览等功能。 当然这个是一个非常基础的实现,并不像市面上的低代码平台那样复杂,但是通过这个系列,我们能够了解整个实现过程,了解其基本思想,并且能够自己实现一个简单的低代码表单,这样也就够了,生活不就是这样吗?知足常乐。
如果你喜欢我的内容,请点赞评论告诉我,我会努力做得更好!
系列链接
1. 手搓低代码表单(一)整体设计以及物料区开发 2. 手搓低代码表单(二)画布区开发 3. 手搓低代码表单(三)属性配置区