Mr_HJ / form-generator项目文档学习与记录(续)

以后主打超融开源社区 (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/index'const 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.js'const 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 = '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(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-col><el-form-item size="large"><el-button type="primary" onClick={this.submitForm}>提交</el-button><el-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 = 'array'required.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. 组件配置信息
  2. 组件的插槽属性( 没使用这里不讨论 )
  3. 直接赋值给组件的属性

1和3的区别在于3上面的属性会赋值<el-input :readonly="false" :disabled="false">上而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": "你好",// 绑定到组件上的key"renderKey": "1011693530948107"},// 字段名"__vModel__": "field101"}]
}
数据流向

通过上面一进一出我们知道了,form-generator在中间做的是

  1. 批量产生配置项
  2. 修改配置项
    现在让我们看下form-generator是如何处理配置项数据的,从右向左看。看不清请放大

从上图我们知道,
首先通过点击或者拖拽的方式将config.js中的配置项转化成了唯一的表单配置项,实现了批量生产。
在修改配置项时通过两个不同的表单,渲染表单用来展示组件和修改值,编辑表单用来修改属性

RightPanel.vue 这个组件是用来操配置项的属性的
  • activeData 标识当前选择的 配置项
  • 可以通过v-model绑定例如
<template v-if="['EditTable'].includes(activeData.__config__.tag)"><el-divider>表格属性</el-divider><el-form-item label-width="100px" label="表格尺寸"><el-radio-group v-model="activeData.size" size="mini"><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><el-form-item label-width="100px" label="纵向边框"><el-switchv-model="activeData.border" size="small"/></el-form-item>
</template>
render.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操作值
通过理解数据流向我们就知道我们怎样扩展自己的组件了。下面通过一个案例来感受一下
 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/225077.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

PyQt6 安装Qt Designer

前言&#xff1a;在Python自带的环境下&#xff0c;安装Qt Designer&#xff0c;并在PyCharm中配置designer工具。 在项目开发中&#xff0c;使用Python虚拟环境安装PyQt6-tools时&#xff0c;designer.exe会安装在虚拟环境的目录中&#xff1a;.venv\Lib\site-packages\qt6_a…

NPM开发工具的简介和使用方法及代码示例

NPM&#xff08;Node Package Manager&#xff09;是Node.js的包管理工具&#xff0c;用于管理和共享被发布到模块仓库的JavaScript代码。本文将介绍NPM的定义、使用方法、代码示例以及总结。 一、NPM的定义 NPM是Node.js的默认包管理工具&#xff0c;它的功能包括安装、管理、…

机器学习算法---回归

1. 线性回归&#xff08;Linear Regression&#xff09; 原理&#xff1a; 通过拟合一个线性方程来预测连续响应变量。线性回归假设特征和响应变量之间存在线性关系&#xff0c;并通过最小化误差的平方和来优化模型。优点&#xff1a; 简单、直观&#xff0c;易于理解和实现。…

【日常笔记】notepad++ 正则表达式基本用法

一、场景 二、正则表达式--语法 2.1、学习基本的匹配字符&#xff1a; 2.2、学习特殊字符和量词&#xff1a; 2.3、学习转义字符 2.4、学习分组和捕获 2.5、区分大小写 和 匹配整个单词 2.6、引用分组 三、实战 ▶ 希望把课程目录中 -- 前面的都去掉 一、场景 希望把…

Jrebel 在 Idea 2023.3中无法以 debug 的模式启动问题

Jrebel 在 Idea 2023.3中无法以 debug 的模式启动问题 Idea 在升级了2023.3以后&#xff0c;Jrebel 无法以 debug 的模式启动&#xff0c;找了半天&#xff0c;最后在插件主页的评论区找到了解决方案 特此记录一下

Dockerfile:创建镜像,创建自定义的镜像。

Docker的创建镜像的方式&#xff1a; 基于已有镜像进行创建。 根据官方提供的镜像源&#xff0c;创建镜像&#xff0c;然后拉起容器。是一个白板&#xff0c;只能提供基础的功能&#xff0c;扩展性的功能还是需要自己定义&#xff08;进入容器进行操作&#xff09; 基于模板进…

SpringBoot 基础概念:SpringApplication#getSpringFactoriesInstances

SpringBoot 基础概念&#xff1a;SpringApplication#getSpringFactoriesInstances SpringApplication#getSpringFactoriesInstances SpringApplication#getSpringFactoriesInstances private <T> Collection<T> getSpringFactoriesInstances(Class<T> type,…

在 Spring Boot 中发送邮件简单实现

Spring Boot 对于发送邮件这种常用功能也提供了开箱即用的 Starter&#xff1a;spring-boot-starter-mail。 通过这个 starter&#xff0c;只需要简单的几行配置就可以在 Spring Boot 中实现邮件发送&#xff0c;可用于发送验证码、账户激活等等业务场景。 本文将通过实际的案…

【AI美图】第03期效果图,AI人工智能全自动绘画,二次元美图欣赏

带来一组二次元人工智能自动绘图 对比分析&#xff1a; 标题手画二次元需要技巧&#xff1a; 二次元高清图片的绘制技巧主要包括以下几点&#xff1a; 线条的运用&#xff1a;在二次元风格的绘画中&#xff0c;线条的运用非常重要。要绘制出流畅、细腻的线条&#xff0c;需…

用于自动驾驶的基于深度学习的图像 3D 物体检测:综述

论文地址&#xff1a;https://ieeexplore.ieee.org/abstract/document/10017184/ 背景 准确、鲁棒的感知系统是理解自动驾驶和机器人驾驶环境的关键。自动驾驶需要目标的 3D 信息&#xff0c;包括目标的位置和姿态&#xff0c;以清楚地了解驾驶环境。 摄像头传感器因其颜色和…

初识JVM底层知识,一文读懂JVM知识文集。

&#x1f3c6;作者简介&#xff0c;普修罗双战士&#xff0c;一直追求不断学习和成长&#xff0c;在技术的道路上持续探索和实践。 &#x1f3c6;多年互联网行业从业经验&#xff0c;历任核心研发工程师&#xff0c;项目技术负责人。 &#x1f389;欢迎 &#x1f44d;点赞✍评论…

nginx反向代理实践指南:访问Tomcat

目录 前言1 实现的效果2 访问流程分析3 安装tomcat并测试4 配置4.1 在Windows系统的hosts文件进行域名和IP对应关系的配置4.2 在NGINX进行请求转发的配置&#xff08;反向代理配置&#xff09; 5 最终测试结论 前言 从Windows系统访问Tomcat Web应用程序&#xff0c;设置和配置…

VUE-脚手架搭建

文章目录 一、概述二、前提准备1. 安装 node-js2. npm 镜像设置3. 安装 vs-code 三、脚手架搭建1. Vue-2 搭建1. Vue-3 搭建 一、概述 官网&#xff1a;http://cn.vuejs.org/ vue 有两个大版本&#xff0c;分别是 vue-2 和 vue-3&#xff0c;目前新项目的话用 vue-3 的会比较多…

Elasticsearch 进阶(索引、类型、字段、分片、副本、集群等详细说明)-06

笔记来源&#xff1a;Elasticsearch Elasticsearch进阶 进阶-核心概念 索引Index 一个索引就是一个拥有几分相似特征的文档的集合。比如说&#xff0c;你可以有一个客户数据的索引&#xff0c;另一个产品目录的索引&#xff0c;还有一个订单数据的索引。一个索引由一个名字…

RT-DETR 图片目标计数 | 特定目标进行计数

全类别计数特定类别计数如何使用 RT-DETR 进行对象计数 有很多同学留言说想学 RT-DETR 目标计数。那么今天这篇博客,我将教大家如何使用 RT-DETR 进行对象计数。RT-DETR 是一种非常强大的对象检测模型,它可以识别图像中的各种对象。我们将学习如何利用这个模型对特定对象进行…

迅为RK3568开发板使用OpenCV处理图像-ROI区域-位置提取ROI

在图像处理过程中&#xff0c;我们可能会对图像的某一个特定区域感兴趣&#xff0c;该区域被称为感兴趣区域&#xff08;Region of Interest, ROI&#xff09;。在设定感兴趣区域 ROI 后&#xff0c;就可以对该区域进行整体操作。 位置提取 ROI 本小节代码在配套资料“iTOP-3…

C++ 学习系列 -- 模板 template

一 C 模板介绍&#xff1f; C 为什么引入模板&#xff1f; 我的理解是&#xff1a; C 引入模板的概念&#xff0c;是为了复用重复的代码&#xff0c;当某些代码除了操作的数据类型不同以外&#xff0c;其他逻辑全都相同&#xff0c;此时就适合采用模板的方式。 定义模板类或者…

黑豹程序员-axios+springmvc传递数组

问题 奇怪的现象&#xff0c;axios在往后台传递数组时&#xff0c;springmvc竟然接收不到 解决 尝试多次无果&#xff0c;突然看一篇文章写vue中的数组不是真正的数组需要强转转化JSON.stringify 将信将疑下测试了一把&#xff0c;还真的传递成功了。 不光要JSON.stringify…

Github 2023-12-15 开源项目日报 Top10

根据Github Trendings的统计&#xff0c;今日(2023-12-15统计)共有10个项目上榜。根据开发语言中项目的数量&#xff0c;汇总情况如下&#xff1a; 开发语言项目数量TypeScript项目3非开发语言项目3JavaScript项目1Python项目1Rust项目1PHP项目1 基于项目的学习 创建周期&am…

微服务组件Sentinel的学习(3)

Sentinel 隔离和降级Feign整合Sentinel线程隔离熔断降级熔断策略 授权规则&#xff1a;自定义异常 隔离和降级 虽然限流可以尽量避免因高并发而引起的服务故障&#xff0c;但服务还会因为其它原因而故障。而要将这些故障控制在一定范用避免雪崩&#xff0c;就要靠线程隔离(舱壁…