vue3低代码平台-事件绑定

前言

​ 在上一篇文章中介绍了样式绑定的实现方式,可查看博客----,事件的绑定在低代码开发工具中是必不可少的,一个良好的设计方案可以提高事件绑定的灵活性,提高组件之间的交互通能力,在这篇文章中讲解一下事件绑定的实现原理。
项目地址要是觉着有意思不妨给颗星,目前项目开源欢迎大家参与项目的开发

实现

1.实现简介

​ 首先将编辑器中编写的object 文本通过eval()的方式转换为js可使用的object,再将object传入到对应的函数(EcVue())中进行处理,将处理过后的函数绑定到对应的页面中,便可在各组件中使用。

单个页面的数据格式如下:

{type:"page",
pageName:'',
label:'',
children:[],
status:{active:false},
data:{},
ecVueInfo:'',// 编辑器的object文本
EcVue:null, // 用于存放页面的object
css:"",
id:""}

2.EcVue

将如下格式的文本传入**createEcVue()**中便可返回EcVue函数对象,将EcVue函数对象放入到对应的页面中,便可在组件上通过对应的方法名、属性名称来进行调用,其中mounted、data、methods与vue中的是一样的作用,可以通过this的方式进行互通调用

export default{
    mounted(){
        
    },
    data(){
    return
        
    }},
    methods:{
        
    }}
// 将页面object文本生成函数并返回
export function createEcVue(ecVueInfo) {
    ecVueInfo = ecVueInfo.replace("export default", '')
    let ecVue = null
    try {
        if (ecVueInfo && ecVueInfo !== '') {
            let info = `()=>{return ${ecVueInfo}}`
            let a = eval(info)
            ecVue = new ECVue(a())
        }
    } catch (e) {
        console.log(e)
    }
    return ecVue
}

EcVue 的实现,可以通过这种方式在methods中可以通过this.属性名的方式调用data()中定义的变量

import {registryMethods} from "./registered/registeredMethods";

var noop = function () {}
var sharedPropertyDefinition = {
    get() {
    },
    set() {
    }
};

export function ECVue(options) {
    if (!this instanceof ECVue) {
        console.log('ECVue是一个构造函数,应使用“new”关键字调用');
    }
    this._init(options)
}

ECVue.prototype._init = function (options) {
    var sc = this;
    sc.$options = options
    //代码进行了删减
    initState(sc);
}

function initState(sc) {
    var opts = sc.$options; //这里是我们在创建实例的时候传的参数
    //如果传了methods 则去调用
    if (opts.methods) {
        initMethods(sc, opts.methods);
    }
    if (opts.data) {
        initData(sc);
    }
    if(registryMethods){
        initRegistryMethods(sc,registryMethods)
    }
    // 对应vue 中mounted函数的调用
    sc['mounted'] = typeof opts.mounted === 'function' ? opts.mounted : noop
    // 用于存放vue中的ref 可通过this.$refs['ref']来进行调用
    sc['$refs'] = {}
}
function initRegistryMethods(sc,registryMethods){
    for (var key in registryMethods) {
        sc[key] = typeof registryMethods[key] !== 'function' ? noop : bind(registryMethods[key], sc);
    }
}

function initMethods(sc, methods) {
    //循环methods对象
    for (var key in methods) {
        //给实例增加methods中的方法 这样其实我们就已经可以用vm访问 到methods中的方法了
        sc[key] = typeof methods[key] !== 'function' ? noop : bind(methods[key], sc);
    }
}

function initData(sc) {
    var data = sc.$options.data;
    data = sc._data = typeof data === 'function'
        ? getData(data, sc)
        : data || {};
    var keys = Object.keys(data);
    var methods = sc.$options.methods;
    var i = keys.length;
    while (i--) {
        var key = keys[i];
        //判断key值有没有跟methods中的key重名
        {
            if (methods && hasOwn(methods, key)) {
                console.log(key + "与methods重名");
            }
        }
        proxy(sc, `_data`, key)
    }
}

function proxy(target, sourceKey, key) {

    sharedPropertyDefinition.get = function proxyGetter() {
        return this[sourceKey][key]
    }
    sharedPropertyDefinition.set = function proxySetter(val) {
        this[sourceKey][key] = val
    }
    Object.defineProperty(target, key, sharedPropertyDefinition)
}

function bind(method, sc) {
    return method
}

function hasOwn(method, key) {
    return method[key] ? true : false
}

function getData(data, sc) {
    return sc.$options.data()
}

当有些函数需要重复使用时便可通过添加自定义的函数,在此处也可以绑定api等对应的方法,在methods中通过this.自定义方法名的形式进行调用。

import {ecRouter} from "../core";
export const registryMethods = {
    "router":ecRouter,// 路由跳转方法
}

3.组件的封装

在这里用input组件来演示讲解EcVue函数的使用

input组件的注册信息如下:

**component:**用于记录组件的配置信息如样式,事件,属性等相关内容

**setter:**用于配置组件的设置相关的内容

​ 1.attributes 用于配置组件的属性

bind: ‘value’ 标识的属性为需要进行v-model属性的绑定,当对应属性所需属性值为Function、object时可以设定bind:'value’通过数据绑定的方式来实现其效果。

bind:‘ref’ 标识的属性为需要进行ref的绑定,考虑到ref值的固定性,其设定为对应的ref名不可更改,若在添加组件时有冲突会进行名称增加数字后缀的操作以进行区分

​ 2.events 用于配置组件中的事件

export const ScInput = {
    component: {
        component: "ScInput",
        label: '输入框',
        events: {},
        attributes: {},
        styles: {},
    },
    setter:{
        component: "ScInput",
        setter: {
            attributes: [
                {
                    attributeName: "inputValue",
                    label: "value绑定",
                    detail:"绑定值",
                    bind: 'value',
                    type: "input",
                    value: "inputValue",
                    defaultValue: "inputValue",
                },
                 {
                    attributeName: "inputRef",//组件配置中属性字段名
                    label: "绑定inputRef",
                    bind:'ref',
                    type: "input",
                    value: "inputRef",
                    defaultValue: "inputRef",
                },
                {
                    attributeName: "type",
                    label: "类型",
                    type: "select",
                    value: "text",
                    defaultValue: "text",
                    detail:"类型",
                    typeArray: [{
                        value: 'text',
                        label: 'text'
                    }, {
                        value: 'textarea',
                        label: 'textarea'
                    },]
                },
            ],
            styles: {},
            events: [
                {
                    event: "blur", // 事件名称
                    enable: false,// 是否启用
                    detail:"当选择器的输入框失去焦点时触发",
                    method: ''// 绑定方法名
                },
                {
                    event: "focus", // 事件名称
                    enable: false,// 是否启用
                    detail:"当选择器的输入框获得焦点时触发",
                    method: ''// 绑定方法名
                },
                {
                    event: "change", // 事件名称
                    enable: false,// 是否启用
                    detail:"仅当 modelValue 改变时,当输入框失去焦点或用户按Enter时触发",
                    method: ''// 绑定方法名
                },
                {
                    event: "input", // 事件名称
                    enable: false,// 是否启用
                    detail:"在 Input 值改变时触发",
                    method: ''// 绑定方法名
                },
                {
                    event: "clear", // 事件名称
                    enable: false,// 是否启用
                    detail:"在点击由 clearable 属性生成的清空按钮时触发",
                    method: ''// 绑定方法名
                }
            ],
        }
    },
}
封装组件

v-model的绑定 考虑到要保留其响应式的效果采用computed的方式来进行处理,通过调用getPageData()与setPageData()的方法来与EcVue中的data()中设定的变量进行值的交换。

事件绑定 在组件调用的事件中,使用**this.EcVue[‘方法名’]**的形式调用在EcVue中注册的事件,在调用对应的事件之前通过执行execMethod()方法来确认对应事件是否可以执行。

this.EcVue[this.propValue.events['focus'].method](方法参数...)

ref绑定 在组件的mounted生命周期时将组件的ref="inputRef"通过bindRefs()方法传入到EcVue中的$refs中进行使用。

<template>
    <el-input
        v-bind = "propValue.attributes"
        v-model="inputValue"
  		ref="inputRef"
        @change="changeMethod"
        @blur = "blurMethod"
        @focus="focusMethod"
        @input="inputMethod"
        @clear="clearMethod"
    />
</template>
<script>
import { getPageData, setPageData,execMethod,bindRefs} from "@/utils/core";
export default {
  name: 'ScInput',
  props: {
    propValue: {
      type: Object, String,
      default: function () {
      }
    },
    EcVue:{
      type:Function,
      default:()=>{}
    }
  },
  computed:{
    inputValue: {
      get(){
        // 绑定事件监听
        return getPageData(this.propValue.attributes['inputValue'],this.EcVue)
      },
      set(value){
        setPageData(this.propValue.attributes['inputValue'],value,this.EcVue)
      }
    }
  },
  mounted() {
    bindRefs(this.propValue.attributes,this.$refs['inputRef'],'inputRef',this.EcVue)
  },
  methods: {
    changeMethod(val){
      if(execMethod(this.propValue.events['change'])){
        this.EcVue[this.propValue.events['change'].method](val)
      }
    },
    blurMethod(val){
      if(execMethod(this.propValue.events['blur'])){
        this.EcVue[this.propValue.events['blur'].method](val)
      }
    },
    focusMethod(val){
      if(execMethod(this.propValue.events['focus'])){
        this.EcVue[this.propValue.events['focus'].method](val)
      }
    },
    inputMethod(val){
      if(execMethod(this.propValue.events['input'])){
        this.EcVue[this.propValue.events['input'].method](val)
      }
    },
    clearMethod(){
      if(execMethod(this.propValue.events['clear'])){
        this.EcVue[this.propValue.events['clear'].method]()
      }
    }
  }
}
</script>
<style scoped>

</style>

getPageData方法

若想给组件属性绑定form中的name属性,可以通过form.name的形式进行绑定,getPageData()先通过analysisData()获得一个属性的数组[‘form’,‘name’]在进行循环调用以获取name中的数值,setPageData()同理。通过这种方式可以应用到表单组件的form-item中进行v-model的绑定

// 调用示范 form.name
form:{
	name:'Tom'
}

// 调用示范 formArrauy.0.name
formArrauy:[
    {name:'Tom'}
]

// 返回当前页面绑定的数据
export function getPageData(attribute, EcVue) {
    const params = analysisData(attribute)
    let setData = EcVue
    let result = ''
    if (params.length > 0) {
        let length = params.length
        result = (setData !== null && setData[params[0]]) ? setData[params[0]] : ''
        if (setData) {
            for (let i = 1; i < length; i++) {
                result = result[params[i]]
            }
        }
        return result
    }
}

setPageData方法

// 设置当前页面绑定的数据
export function setPageData(attribute, value, EcVue) {
    const params = analysisData(attribute)
    let setData = EcVue
    if (params.length > 0) {
        let length = params.length
        for (let i = 0; i < length - 1; i++) {
            setData = setData[params[i]]
        }
        setData[params[length - 1]] = value
    }
}

analysisDataf方法

// 解析数据绑定的数值
function analysisData(param) {
    if (!param) {
        return []
    }
    return param.split(".")
}

bindRefs方法

export function bindRefs(attr, refs, name, EcVue) {
    if (!EcVue.$refs) {
        EcVue['$refs'] = null
    }
    if (!refs) {
        refs = null
    }
    if (attr[name]) {
        let i = 0
        let value = attr[name].replace(/[0-9]+/g, "")
        while (EcVue.$refs[attr[name]]) {
            i++
            attr[name] = value + i
        }
        EcVue.$refs[attr[name]] = refs
    }
}
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值