哪些网站可以做画赚钱,质量好的购物平台,遂溪手机网站建设,建设公司网站要注意什么以后主打超融开源社区 (jiangzhicheng88) - Gitee.com
render.js就是对vue的render函数的自己简单定制封装。
render.js实现的功能是将json表单中的__config__.tag解析为具体的vue组件#xff1b;
正常开发流程我们组件输入的时候会触发组件内的 this.$emit(getValue, val)…以后主打超融开源社区 (jiangzhicheng88) - Gitee.com
render.js就是对vue的render函数的自己简单定制封装。
render.js实现的功能是将json表单中的__config__.tag解析为具体的vue组件
正常开发流程我们组件输入的时候会触发组件内的 this.$emit(getValue, val); 引用组件的父组件需要响应子组件上的getValue方法调用自身的getValue方法处理里面的逻辑
转换成代码生成器内的流程就是 编辑器组件输入内容触发this.$emit(getValue, val) 子组件监听getValue事件父组件处理getValue事件setEditorValue
require.context 在组件内引入多个组件 我们可以通过 require.context() 函数来创建自己的 context。
可以给这个函数传入三个参数
要搜索的目录 标记表示是否还搜索其子目录 匹配文件的正则表达式。 webpack 会在构建中解析代码中的 require.context()
不熟悉正则的同学可以看看下面的解析 正则解析:
/^.*\.(jpg|gif|png|bmp)$/i 1 ^: 匹配字符串的开始位置 .*: .匹配任意字符*匹配数量0到正无穷 \.: 斜杠用来转义\.匹配. (jpg|gif|png|bmp): 匹配 jpg 或 gif 或 png 或 bmp $: 匹配字符串的结束位置 i: 不区分大小写。 合起来就是匹配以 .jpg 或 .GIF 或 … 结尾的任意字符串不区分大小写 const keys slotsFiles.keys() || []后结果keys如下
[ ./el-button.js, ./el-checkbox-group.js, ./el-input.js, ./el-radio-group.js, ./el-select.js, ./el-upload.js ]
render key ./el-button.js
const tag key.replace(/^\.\/(.*)\.\w$/, $1)
相当于把前面./和.js都替换掉了
render tag el-button import { deepClone } from /utils/indexconst componentChild {}
/*** 将./slots中的文件挂载到对象componentChild上* 文件名为key对应JSON配置中的__config__.tag* 文件内容为value解析JSON配置中的__slot__*/
const slotsFiles require.context(./slots, false, /\.js$/)
const keys slotsFiles.keys() || []
keys.forEach(key {const tag key.replace(/^\.\/(.*)\.\w$/, $1)const value slotsFiles(key).defaultcomponentChild[tag] value
})function vModel(dataObject, defaultValue) {dataObject.props.value defaultValuedataObject.on.input val {this.$emit(input, val)}
}function mountSlotFiles(h, confClone, children) {const childObjs componentChild[confClone.__config__.tag]if (childObjs) {Object.keys(childObjs).forEach(key {const childFunc childObjs[key]if (confClone.__slot__ confClone.__slot__[key]) {children.push(childFunc(h, confClone, key))}})}
}function emitEvents(confClone) {[on, nativeOn].forEach(attr {const eventKeyList Object.keys(confClone[attr] || {})eventKeyList.forEach(key {const val confClone[attr][key]if (typeof val string) {// 代码编辑器自定义事件注册// 将getValue的事件指向我们定义的setEditorValue去// confClone[on][getValue] event this.$emit(setEditorValue, event)confClone[attr][key] event this.$emit(val, event)}})})
}function buildDataObject(confClone, dataObject) {Object.keys(confClone).forEach(key {const val confClone[key]if (key __vModel__) {vModel.call(this, dataObject, confClone.__config__.defaultValue)} else if (dataObject[key] ! undefined) {if (dataObject[key] null|| dataObject[key] instanceof RegExp|| [boolean, string, number, function].includes(typeof dataObject[key])) {dataObject[key] val} else if (Array.isArray(dataObject[key])) {dataObject[key] [...dataObject[key], ...val]} else {dataObject[key] { ...dataObject[key], ...val }}} else {dataObject.attrs[key] val}})// 清理属性clearAttrs(dataObject)
}function clearAttrs(dataObject) {delete dataObject.attrs.__config__delete dataObject.attrs.__slot__delete dataObject.attrs.__methods__
}function makeDataObject() {// 深入数据对象// https://cn.vuejs.org/v2/guide/render-function.html#%E6%B7%B1%E5%85%A5%E6%95%B0%E6%8D%AE%E5%AF%B9%E8%B1%A1return {class: {},attrs: {},props: {},domProps: {},nativeOn: {},on: {},style: {},directives: [],scopedSlots: {},slot: null,key: null,ref: null,refInFor: true}
}export default {props: {conf: {type: Object,required: true}},render(h) {const dataObject makeDataObject()const confClone deepClone(this.conf)const children this.$slots.default || []// 如果slots文件夹存在与当前tag同名的文件则执行文件中的代码mountSlotFiles.call(this, h, confClone, children)// 将字符串类型的事件发送为消息emitEvents.call(this, confClone)// 将json表单配置转化为vue render可以识别的 “数据对象dataObject”buildDataObject.call(this, confClone, dataObject)return h(this.conf.__config__.tag, dataObject, children)}
}Parser.vue
script
import { deepClone } from /utils/index
import render from /components/render/render.jsconst ruleTrigger {el-input: blur,el-input-number: blur,el-select: change,el-radio-group: change,el-checkbox-group: change,el-cascader: change,el-time-picker: change,el-date-picker: change,el-rate: change
}const layouts {colFormItem(h, scheme) {const config scheme.__config__const listeners buildListeners.call(this, scheme)let labelWidth config.labelWidth ? ${config.labelWidth}px : nullif (config.showLabel false) labelWidth 0return (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(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)}
}function renderFrom(h) {const { formConfCopy } thisreturn (el-row gutter{formConfCopy.gutter}el-formsize{formConfCopy.size}label-position{formConfCopy.labelPosition}disabled{formConfCopy.disabled}label-width{${formConfCopy.labelWidth}px}ref{formConfCopy.formRef}// model不能直接赋值 https://github.com/vuejs/jsx/issues/49#issuecomment-472013664props{{ model: this[formConfCopy.formModel] }}rules{this[formConfCopy.formRules]}{renderFormItem.call(this, h, formConfCopy.fields)}{formConfCopy.formBtns formBtns.call(this, h)}/el-form/el-row)
}function formBtns(h) {return el-colel-form-item sizelargeel-button typeprimary onClick{this.submitForm}提交/el-buttonel-button onClick{this.resetForm}重置/el-button/el-form-item/el-col
}function renderFormItem(h, elementList) {return elementList.map(scheme {const config scheme.__config__const layout layouts[config.layout]if (layout) {return layout.call(this, h, scheme)}throw new Error(没有与${config.layout}匹配的layout)})
}function renderChildren(h, scheme) {const config scheme.__config__if (!Array.isArray(config.children)) return nullreturn renderFormItem.call(this, h, config.children)
}function setValue(event, config, scheme) {this.$set(config, defaultValue, event)this.$set(this[this.formConf.formModel], scheme.__vModel__, event)
}function buildListeners(scheme) {const config scheme.__config__const methods this.formConf.__methods__ || {}const listeners {}// 给__methods__中的方法绑定this和eventObject.keys(methods).forEach(key {listeners[key] event methods[key].call(this, event)})// 响应 render.js 中的 vModel $emit(input, val)listeners.input event setValue.call(this, event, config, scheme)return listeners
}export default {components: {render},props: {formConf: {type: Object,required: true}},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},methods: {initFormData(componentList, formData) {componentList.forEach(cur {const config cur.__config__if (cur.__vModel__) formData[cur.__vModel__] config.defaultValueif (config.children) this.initFormData(config.children, formData)})},buildRules(componentList, rules) {componentList.forEach(cur {const config cur.__config__if (Array.isArray(config.regList)) {if (config.required) {const required { required: config.required, message: cur.placeholder }if (Array.isArray(config.defaultValue)) {required.type arrayrequired.message 请至少选择一个${config.label}}required.message undefined (required.message ${config.label}不能为空)config.regList.push(required)}rules[cur.__vModel__] config.regList.map(item {item.pattern (item.pattern eval(item.pattern))item.trigger ruleTrigger ruleTrigger[config.tag]return item})}if (config.children) this.buildRules(config.children, rules)})},resetForm() {this.formConfCopy deepClone(this.formConf)this.$refs[this.formConf.formRef].resetFields()},submitForm() {this.$refs[this.formConf.formRef].validate(valid {if (!valid) return false// 触发sumit事件this.$emit(submit, this[this.formConf.formModel])return true})}},render(h) {return renderFrom.call(this, h)}
}
/script每个组件都对应一个config配置项,以单行文本框为例
{// 1. 组件配置信息__config__: {label: 单行文本,labelWidth: null,showLabel: true,changeTag: true,tag: el-input,tagIcon: input,defaultValue: undefined,required: true,layout: colFormItem,span: 24,document: https://element.eleme.cn/#/zh-CN/component/input,// 正则校验规则regList: []},// 2. 组件的插槽属性__slot__: {prepend: ,append: },// 3. 直接赋值给组件的属性placeholder: 请输入,style: { width: 100% },clearable: true,prefix-icon: ,suffix-icon: ,maxlength: null,show-word-limit: false,readonly: false,disabled: false},每个表单配置项有三个部分
组件配置信息组件的插槽属性( 没使用这里不讨论 )直接赋值给组件的属性
1和3的区别在于3上面的属性会赋值el-input :readonlyfalse :disabledfalse上而1上的属性不会让我们再看下生成后的表单项不用细看
{fields: [{__config__: {label: 单行文本,labelWidth: null,showLabel: true,changeTag: true,tag: el-input,tagIcon: input,defaultValue: 你好,required: true,layout: colFormItem,span: 24,document: https://element.eleme.cn/#/zh-CN/component/input,regList: [],formId: 101,renderKey: 1011693530948107},__slot__: {prepend: ,append: },placeholder: 请输入单行文本,style: {width: 100%},clearable: true,prefix-icon: ,suffix-icon: ,maxlength: null,show-word-limit: false,readonly: false,disabled: false,__vModel__: field101}],formRef: elForm,formModel: formData,size: medium,labelPosition: right,labelWidth: 100,formRules: rules,gutter: 15,disabled: false,span: 24,formBtns: true
}请注意这几个属性
{fields: [{__config__: {// 双向绑定的值defaultValue: 你好,// 绑定到组件上的keyrenderKey: 1011693530948107},// 字段名__vModel__: field101}]
}数据流向
通过上面一进一出我们知道了form-generator在中间做的是
批量产生配置项修改配置项 现在让我们看下form-generator是如何处理配置项数据的从右向左看。看不清请放大 从上图我们知道 首先通过点击或者拖拽的方式将config.js中的配置项转化成了唯一的表单配置项实现了批量生产。 在修改配置项时通过两个不同的表单渲染表单用来展示组件和修改值编辑表单用来修改属性
RightPanel.vue 这个组件是用来操配置项的属性的
activeData 标识当前选择的 配置项可以通过v-model绑定例如
template v-if[EditTable].includes(activeData.__config__.tag)el-divider表格属性/el-dividerel-form-item label-width100px label表格尺寸el-radio-group v-modelactiveData.size sizeminiel-radio-button labelmedium默认/el-radio-buttonel-radio-button labelsmall小号/el-radio-buttonel-radio-button labelmini迷你/el-radio-button/el-radio-group/el-form-itemel-form-item label-width100px label纵向边框el-switchv-modelactiveData.border sizesmall//el-form-item
/templaterender.js 这个组件是用来显示组件和操作值的
export default {props: {conf: {type: Object,required: true}},components: {EditTable},mounted() {// 动态请求数据catchData.call(this, this.conf)},render(h) {const dataObject makeDataObject()const confClone deepClone(this.conf)const children this.$slots.default || []// 如果slots文件夹存在与当前tag同名的文件则执行文件中的代码mountSlotFiles.call(this, h, confClone, children)// 将字符串类型的事件发送为消息emitEvents.call(this, confClone)// 将json表单配置转化为vue render可以识别的 “数据对象dataObject”buildDataObject.call(this, confClone, dataObject)return h(this.conf.__config__.tag, dataObject, children)}
}我们可以看到render.js是一个vue组件不过不是vue文件而是通过render函数和h函数来返回虚拟DOM h函数的具体可以看渲染函数简单理解就是h( 标签名,标签属性,子元素 ) 使用h函数根据__config__.tag返回特定的组件 标签属性就是绑定了诸如 style、attribute、on、slot等信息的对象。这里我们主要注意on上面会绑定一个input事件我们就是通过它来更新数据的。
数据流向总结 通过config.js设置配置信息 通过defaultValue和input进行绑定值 通过RightPanel操作值 通过理解数据流向我们就知道我们怎样扩展自己的组件了。下面通过一个案例来感受一下