https://blog.csdn.net/hesi726/article/details/83115235  这儿说了我们 Wrap 一个 Vue组件后想要实现的3个目标:
也就是说此组件应该和被 wrap 的组件一样被另外的组件使用;但是又能够在需要时由此组件向被 Wrap 的组件设置数据源;
例如 ElementUi 里面的 el-select , 继承的话,我们就无法给此组件内部设置下拉菜单的数据源;

1. 组件需要能够自动把父组件传递的 prop 传递给里面的 el-input ;
就是 <EhayInput :max="10"></EhayInput> 这样写时, EhayInput 里面的 el-input 也应该自动绑定 max=10 
这个在文章中已经给出了 https://blog.csdn.net/hesi726/article/details/83115235   了,
 

2. 自动向上传递事件
     也就是里面 el-input 的 focus 之类的事件应该会自动传递到 EhayInput , 类似于这样 <EhayInput @focus="focus"></EhayInput> 
     这个有点麻烦,因为我们无法得知子组件会发送什么样的消息,
    但是,我们可以修改一下 Vue, 默认提供的 $emit 方法,使其发送我们自定义的消息;

static addAllEventHandle_Emit(wrapComponent: Vue) {
        let inner: Vue = wrapComponent.$refs['innerOriginal'] as Vue;
        (inner as any).$originalEmit = inner.$emit; //修改 默认的 $emit 方法
        inner.$emit =  (event, ...args) => {
            return ((inner as any).$originalEmit)('wrapEvent', event, ...args);  //总是发送自己的消息
        }
        inner.$on('wrapEvent', (event: string, ...args: any[]) => {
            wrapComponent.$emit(event, ...args);
        });
    }

我们外部 wrap 的组件只需要监听我们自定义的消息,并向上转发子组件实际发送的消息就可以了;
    

3. 自动绑定 slot, 
这个就比较麻烦了,麻烦的地方在于  vue 里面并没有一个可以查询到 vue组件的所有 slot;
$slot 和 scopedSlot 只有在外部传入了 slot 的内容时,才能够在 mounted 事件中访问到;
也就是说,通过 vue组件的属性,我们无法自动添加 slot 的定义来从外部组件接收 slot 内容并传送给子组件;
最后使用了一个野蛮的方法。。。
分析子组件 render 函数的文本内容,从中猜测可能时 slot 的部分;
 

static wrapAndCompile(bewrapedComponent: any , assignSlotContent: Record<string, string> = {}): (createElement: any) => VNode {
        //alert(c)
        let slotsString = "";
        let renderString: string = '' + bewrapedComponent.render.toString();  //从组件的 render 函数中获取 slots 的定义;
        let map: Set<string> = new Set<string>();
        let pos = renderString.indexOf(`_t("`);
        while (pos >= 0) {
            let posEnd = renderString.indexOf(`"`, pos + 4);
            let slotName = renderString.substring(pos + 4, posEnd);
            map.add(slotName);
            pos = renderString.indexOf(`_t("`, posEnd + 1);
        }
        let slotsInRender = map.values();
        let getSlotContent = (key: string) => assignSlotContent[key] || "";
        map.forEach((key) => {
          let slotContent = getSlotContent(key);
          if (key === "default") { return; } //默认的槽不输出;
          slotsString += `<template slot="${key}">
                    <slot name="${key}">${slotContent}</slot>
                </template>`;
        });
        console.log(slotsString);
        let result: string = '';
        result = "<" + bewrapedComponent.name + " ref='innerOriginal' ";
        for (let prop in bewrapedComponent.props) {   // 绑定组件中的属性
            result = result + ' :' + prop + "='" + prop + "' ";
        }

        result += ">";
        result += slotsString;

        assignSlotContent = assignSlotContent || [];
        /*for (let index in assignSlotContent) {
            let slot = assignSlotContent[index];
            if (slot === "" || slot === "default")
                result  = result + "<slot></slot>"; //默认槽;
            else result = result + "<slot name='" + slot + "'></slot>";
        }*/
        let defSlotContent = getSlotContent('default');
        result += defSlotContent;
        result += "</" + bewrapedComponent.name + ">";
        console.log("*********************************");
        console.log(result);
        return Vue.compile(result).render;
    }

看上去还不错。。。
好,现在我们实现了自己的目标了;
给出所有的代码:
VueComponentWrapperAssistant 工具类

import Vue, { VNode } from "vue";

/** 带有名称的接口 */
interface IName {
    name: string;
}

/** 带有名称的 Vue 接口 */
interface VueAndName extends Vue {
    name: string;
}


/**
 * Vue组件的Wrap助手类
*/
export default class VueComponentWrapperAssistant {
    /**
     * 根据组件定义产生 此Wrapp此组件的定义的 template 字符串,并通过 Vue编译后,产生 render 函数;
     * 例如,产生类似于 <ElInput ref='innerOriginal'  :value='value'  :size='size'  :resize='resize'  :form='form'  :disabled='disabled'  :readonly='readonly'  :type='type'  :autosize='autosize'  :autocomplete='autocomplete'  :autoComplete='autoComplete'  :validateEvent='validateEvent'  :suffixIcon='suffixIcon'  :prefixIcon='prefixIcon'  :label='label'  :clearable='clearable'  :tabindex='tabindex' ></ElInput>
     * 的字符串,并使用 Vue.compile() 编译此字符串后,返回编译后的 render 函数;
     * @param bewrapedComponent    被封装的ElementUI 组件的实例,好稀奇,
     *              传入 elementUI.Input 应该是一个 class 定义, 但是却传入了 类实例;
     * @param assignSlotContent 指定的槽内容,例如对于我们自定义的 EhaySelect 组件,应该给 slot default 设置默认的槽内容;
     */
    static wrapAndCompile(bewrapedComponent: any , assignSlotContent: Record<string, string> = {}): (createElement: any) => VNode {
        //alert(c)
        let slotsString = "";
        let renderString: string = '' + bewrapedComponent.render.toString();  //从组件的 render 函数中获取 slots 的定义;
        let map: Set<string> = new Set<string>();
        let pos = renderString.indexOf(`_t("`);
        while (pos >= 0) {
            let posEnd = renderString.indexOf(`"`, pos + 4);
            let slotName = renderString.substring(pos + 4, posEnd);
            map.add(slotName);
            pos = renderString.indexOf(`_t("`, posEnd + 1);
        }
        let slotsInRender = map.values();
        let getSlotContent = (key: string) => assignSlotContent[key] || "";
        map.forEach((key) => {
          let slotContent = getSlotContent(key);
          if (key === "default") { return; } //默认的槽不输出;
          slotsString += `<template slot="${key}">
                    <slot name="${key}">${slotContent}</slot>
                </template>`;
        });
        console.log(slotsString);
        let result: string = '';
        result = "<" + bewrapedComponent.name + " ref='innerOriginal' ";
        for (let prop in bewrapedComponent.props) {   // 绑定组件中的属性
            result = result + ' :' + prop + "='" + prop + "' ";
        }

        result += ">";
        result += slotsString;

        assignSlotContent = assignSlotContent || [];
        /*for (let index in assignSlotContent) {
            let slot = assignSlotContent[index];
            if (slot === "" || slot === "default")
                result  = result + "<slot></slot>"; //默认槽;
            else result = result + "<slot name='" + slot + "'></slot>";
        }*/
        let defSlotContent = getSlotContent('default');
        result += defSlotContent;
        result += "</" + bewrapedComponent.name + ">";
        console.log("*********************************");
        console.log(result);
        return Vue.compile(result).render;
    }
   
    /**
     * 给 wrapComponent 里面的 ref 名称为 innerOriginal (此组件再 wrapAndCompile 函数中创建)的组件添加事件的 emit;
     */
    static addAllEventHandle_Emit(wrapComponent: Vue) {
        let inner: Vue = wrapComponent.$refs['innerOriginal'] as Vue;
        (inner as any).$originalEmit = inner.$emit; //修改 默认的 $emit 方法
        inner.$emit =  (event, ...args) => {
            return ((inner as any).$originalEmit)('wrapEvent', event, ...args);  //在定义一个消息
        }
        inner.$on('wrapEvent', (event: string, ...args: any[]) => {
            wrapComponent.$emit(event, ...args);
        });
    }

}

AbstractEntityQueryComponent 实体查询 


import { Component, Prop, Vue, Watch } from "vue-property-decorator";
import { WebApi } from "../../api/auto_actions";
import * as EhayModel from "../../api/auto_interface";
import { ArrayUtil } from "../../utils/BrowserExtension";
import { FetchSuggestions, FetchSuggestionsCallback } from "element-ui/types/autocomplete";
import { QueryChangeHandler } from "element-ui/types/select";


/**
 * 定义实体查询的组件,
 * 包含和实体查询有关的方法;
 * 不要创建和本类相关的实例;
 **/
@Component
export default class AbstractEntityQueryComponent extends Vue  {

  /** 实体类型名称 */
  @Prop({required: true})
  entityTypeName: string;

  /** 查询实体时可能还需要的父实体Id */
  @Prop({ default: ''})
  parentEntityId: string;

  /** value 属性,此书属性用于初始化的查询, 需要注意一个事实是,如果 label 和 value 具有不同的值时,
   *  我们只需要绑定 value , 但此时将显示 value,
   *  这不是我们想要的,我们需要的是 label;
   *  所以此处需要添加一个 value 属性,以便传入到后台进行查询;
   */
  @Prop({ default: ''})
  value: string;

  /** 覆盖 autocomplete 中的 fetchSuggestions prop*/
  @Prop({
    default: () =>
        /**
         * @param key: 查询关键字,
         * @param cb:  在 autocomplete 里面对查询的回调;
        */
        (key: string, cb: FetchSuggestionsCallback) => {
          // tslint:disable-next-line:no-eval
          let vthis = (eval("this") as any).$parent;
          vthis.innerRemoteQuery(key, cb);
        },
  })
  fetchSuggestions: FetchSuggestions;

  /** 覆盖 sselect 中的 remote-method prop*/
  @Prop({
    default: () => (key: string) => {
      // tslint:disable-next-line:no-eval
      let vthis = (eval("this") as any).$parent;
      vthis.innerRemoteQuery(key);
    },
  })
  remoteMethod: QueryChangeHandler;

  /**
   * 查询函数
   * @param key 实体名称关键字
   * @param callback  回调函数,对于 autocomplete 组件有用;
   */
  private queryFunc: (key: string, callback?: any) => Promise<Array<EhayModel.LabelAndValue>> = (key: string, callback?: any) => new Promise <Array< EhayModel.LabelAndValue >>((resolve) => []) ;

  /**
   * 重新计算查询函数;
   */
  recalateQueryFunc() {
    this.queryFunc = (key: string) => new Promise<EhayModel.LabelAndValue[]>((resolve, reject) => {
      WebApi.EntityQuery.getEntityListByKey(this.entityTypeName, key,   this.parentEntityId)
        .then((data) => resolve(data.data));
    });
  }

  /**
   * mount 消息回调函数
   * 给 entityTypeName、parentEntityId 2个属性设置观察函数;
   */
  mounted() {
    this.$watch("entityTypeName", () => this.recalateQueryFunc(), { immediate: true });
    this.$watch("parentEntityId", () => this.recalateQueryFunc(), { immediate: true });
    this.recalateQueryFunc();
    //给定值发生变化时,根据值去加载名称;
    this.$watch("value", () => () => { this.queryEntityArrayAccordValue(); }, { immediate: true});
    this.queryEntityArrayAccordValue();
  }

  /** 根据初始值查询 名称和值 数组 */
  queryEntityArrayAccordValue() {
    if (this.value && this.entityArray.findIndex(a=> a.value == this.value) < 0) {
      WebApi.EntityQuery.getEntityListById(this.entityTypeName, this.value).then((data) => {
        ArrayUtil.clearAndCopy(this.entityArray, data.data);
      });
    }
  }

  /** 是否在加载数据 */
  isQuerying: boolean = false;

  /** 查询后的实体数组 */
  entityArray: EhayModel.LabelAndValue[] = [];

  /** 远程查询
   * @param key 实体名称关键字
   * @param callback  回调函数,对于 autocomplete 组件有用;
  */
  innerRemoteQuery(key: string, callback?: any) {
    this.isQuerying = true;
    setTimeout(() => {
      this.queryFunc(key).then((data) => {
        this.isQuerying = false;
        ArrayUtil.clearAndCopy(this.entityArray, data);
        if (callback) {
          callback(data);
        }
      });
    }, 50);
  }
}

 自定义的封装 el-select 组件的组件

import AbstractEntityQueryComponent from '../AbstractEntityQueryComponent';
import { Component, Prop, Mixins } from "vue-property-decorator";
import elementUI from "element-ui";
import VueComponentWrapperAssistant from "../VueComponentWrapperAssistant";

/**
 * 下拉选择组件,默认单选, 继承自  ElementUI.Select
 * <EhayEntitySelect entityTypeName='EH_Client' v-model='client'></EhayEntitySelect>
 */
@Component({
  render: VueComponentWrapperAssistant.wrapAndCompile(elementUI.Select, {
    //  给定 默认的 slot 定义
    'default': `<el-option   
      v-for="item in entityArray"
      :key="item.value"
      :label="item.label"
      :value="item.value">
    </el-option>
  `}),
})
export default class EhayEntitySelect extends Mixins(elementUI.Select, AbstractEntityQueryComponent) {
  /** 覆盖 sselect 中的 filterable prop,使其默认为 true*/
  @Prop({ default: true })
  filterable: boolean;

  /** 覆盖 sselect 中的 remote prop,使其默认为 true*/
  @Prop({ default: true })
  remote: boolean;

  /** 覆盖 sselect 中的 clearable prop,使其默认为 true*/
  @Prop({ default: true })
  clearable: boolean;

  /** 作为 value 唯一标识的键名,绑定值为对象类型时必填 */
  @Prop({ default: "value"})
  valueKey: string;

  /**
   * mounted 消息回调函数
   * 添加消息的向上传递;
   */
  mounted() {
    VueComponentWrapperAssistant.addAllEventHandle_Emit(this);
  }
}


 

如何使用?
就像普通的 el-select 组件一样使用,

<EhayEntitySelect v-model="ehayEntitytSelect_Value" entityTypeName="OA_User">

</EhayEntitySelect>

 





 



 

Logo

前往低代码交流专区

更多推荐