【2】vform添加自己diy的组件

323 阅读5分钟

场景

当vform中所存在的组件并不能满足需求的,需要自己新增自己公司的组件。基于这种需求下,根据vform的代码,进行修改实现需求。

定位文件

vform这个低代码框架,扩展组件都放在extension文件夹中。在2022版本中他内置了两个扩展card和alert,用得较多的就是card卡片容器。

// extension-loader.js
import Vue from 'vue'

import {
  addContainerWidgetSchema,
  addCustomWidgetSchema
} from '@/components/form-designer/widget-panel/widgetsConfig'
import * as PERegister from '@/components/form-designer/setting-panel/propertyRegister'
import * as PEFactory from '@/components/form-designer/setting-panel/property-editor-factory'

import {cardSchema} from "@/extension/samples/extension-schema"
import CardWidget from '@/extension/samples/card/card-widget'
import CardItem from '@/extension/samples/card/card-item'
import {registerCWGenerator} from '@/utils/sfc-generator'
import {cardTemplateGenerator} from '@/extension/samples/extension-sfc-generator'

import {alertSchema} from "@/extension/samples/extension-schema"
import AlertWidget from '@/extension/samples/alert/alert-widget'
import {registerFWGenerator} from '@/utils/sfc-generator'
import {alertTemplateGenerator} from '@/extension/samples/extension-sfc-generator'

// INFO  micro组件开始导入
/* -------------------micro components-------------------- */
import {microCustomerSchema} from '@/extension/samples/extension-schema'
import MicroCustomerWidget from '@/extension/samples/micro/micro-customer-widget'

/* ------------------------------------------------------- */

export const loadExtension = function () {

  /**
   * 加载容器组件步骤:
   * 1. 加载组件Json Schema;
   * 2. 全局注册容器组件,容器组件有两种状态——设计期和运行期,故需要注册两个组件;
   * 3. 全局注册属性编辑器组件(基本属性、高级属性、事件属性);
   * 4. 注册容器组件的代码生成器;
   * 5. 加载完毕。
   */
  addContainerWidgetSchema(cardSchema)  //加载组件Json Schema
  /* -------------------------------------------------- */
  Vue.component(CardWidget.name, CardWidget)  //注册设计期的容器组件
  Vue.component(CardItem.name, CardItem)  //注册运行期的容器组件
  /* -------------------------------------------------- */
  PERegister.registerCPEditor('card-folded', 'card-folded-editor',
      PEFactory.createBooleanEditor('folded', 'extension.setting.cardFolded'))

  PERegister.registerCPEditor('card-showFold', 'card-showFold-editor',
      PEFactory.createBooleanEditor('showFold', 'extension.setting.cardShowFold'))

  PERegister.registerCPEditor('card-cardWidth', 'card-cardWidth-editor',
      PEFactory.createInputTextEditor('cardWidth', 'extension.setting.cardWidth'))

  let shadowOptions = [
    {label: 'never', value: 'never'},
    {label: 'hover', value: 'hover'},
    {label: 'always', value: 'always'},
  ]
  PERegister.registerCPEditor('card-shadow', 'card-shadow-editor',
      PEFactory.createSelectEditor('shadow', 'extension.setting.cardShadow',
          {optionItems: shadowOptions}))
  /* -------------------------------------------------- */
  registerCWGenerator('card', cardTemplateGenerator)  //注册容器组件的代码生成器
  /* -------------------------------------------------- */
  /* 容器组件加载完毕 end */

  /**
   * 加载字段组件步骤:
   * 1. 加载组件Json Schema;
   * 2. 全局注册字段组件,字段组件设计期和运行期共用,故需要仅需注册一个组件;
   * 3. 全局注册属性编辑器组件(基本属性、高级属性、事件属性);
   * 4. 注册字段组件的代码生成器;
   * 5. 加载完毕。
   */
  addCustomWidgetSchema(alertSchema)  //加载组件Json Schema
  /* -------------------------------------------------- */
  Vue.component(AlertWidget.name, AlertWidget)  //注册组件
  /* -------------------------------------------------- */
  PERegister.registerCPEditor('alert-title', 'alert-title-editor',
      PEFactory.createInputTextEditor('title', 'extension.setting.alertTitle'))

  let typeOptions = [
    {label: 'success', value: 'success'},
    {label: 'warning', value: 'warning'},
    {label: 'info', value: 'info'},
    {label: 'error', value: 'error'},
  ]
  // PERegister.registerCPEditor('alert-type', 'alert-type-editor',
  //     PEFactory.createSelectEditor('type', 'extension.setting.alertType',
  //         {optionItems: typeOptions}))
  /* type属性映射已存在,无须再注册,故只需注册属性编辑器即可!! */
  Vue.component('alert-type-editor',
      PEFactory.createSelectEditor('type', 'extension.setting.alertType',
          {optionItems: typeOptions}))

  PERegister.registerCPEditor('alert-description', 'alert-description-editor',
      PEFactory.createInputTextEditor('description', 'extension.setting.description'))

  PERegister.registerCPEditor('alert-closable', 'alert-closable-editor',
      PEFactory.createBooleanEditor('closable', 'extension.setting.closable'))

  PERegister.registerCPEditor('alert-closeText', 'alert-closeText-editor',
      PEFactory.createInputTextEditor('closeText', 'extension.setting.closeText'))

  PERegister.registerCPEditor('alert-center', 'alert-center-editor',
      PEFactory.createBooleanEditor('center', 'extension.setting.center'))

  PERegister.registerCPEditor('alert-showIcon', 'alert-showIcon-editor',
      PEFactory.createBooleanEditor('showIcon', 'extension.setting.showIcon'))

  let effectOptions = [
    {label: 'light', value: 'light'},
    {label: 'dark', value: 'dark'},
  ]
  PERegister.registerCPEditor('alert-effect', 'alert-effect-editor',
      PEFactory.createRadioButtonGroupEditor('effect', 'extension.setting.effect',
          {optionItems: effectOptions}))

  PERegister.registerEPEditor('alert-onClose', 'alert-onClose-editor',
      PEFactory.createEventHandlerEditor('onClose', []))
  /* -------------------------------------------------- */
  registerFWGenerator('alert', alertTemplateGenerator)  //注册字段组件的代码生成器
  /* -------------------------------------------------- */
  /* 字段组件加载完毕 end */

  /*INFO: xy-micro组件 */
  addCustomWidgetSchema(microCustomerSchema) //加载到widgetConfig的customWidget内部
  /* -------------------------------------------------- */
  Vue.component(MicroCustomerWidget.name, MicroCustomerWidget) //注册客户组件
  /* -------------------------------------------------- */

  PERegister.registerCPEditor(  'micro-button-span', 'micro-button-span-editor')
  PERegister.registerCPEditor(  'micro-customer-span', 'micro-customer-span-editor')

  /*INFO: END 注册自定义组件结束 */   
}

因为代码杂糅故将他重构,

import { initCardWidget } from './widget/card/card-widget-loader'
import { initAlertWidget } from "./widget/alert/alert-widget-loader"
import { initDictWidget } from './widget/dict/dict-widget-loader'
import { initFuzzySearchWidget } from './widget/fuzzy-search/fuzzy-search-widget-loader'
import { initButtonFields } from './widget/button/button-widget-loader'
import { initComponentWidget } from './widget/component/component-widget-loader'

import { initBodyWidget } from "./widget/body-area/body-widget-loader"
import { initGridColFields } from "./widget/grid-col/grid-col-widget-loader"
import { initInputFields } from "./widget/input/input-widget-loader"

import { initStaticTextFields } from "./widget/static-text/static-text-widget-loader"
import { initSubformContainer } from "./widget/subform/subform-container"
import { initDataTableLoader } from "./widget/data-table/data-table-loader"
import { initDateFields } from "./widget/date/date-widget-loader"
import { initProgressWidget } from "./widget/progress/progress-widget-loader"

// @todo 后续自动加载插件
export const loadExtension = function () {
    // 容器组件加载
    initCardWidget()
    initBodyWidget()
    initSubformContainer()

    // 字段组件加载
    initAlertWidget()
    initDictWidget()
    initFuzzySearchWidget()

    // 添加button的类型属性
    initButtonFields()
    initComponentWidget()
    initGridColFields()
    initInputFields()
    initStaticTextFields()

    initDataTableLoader()
    initDateFields()
    initProgressWidget()
}

添加的步骤

首先是config的配置,即导出JSON中属于这个组件的参数JSON,就是schema

这个schema添加到

他有了这个配置,导出的JSON就会有这个配置信息。 那他组件是怎么弄上去的呢?

自定义组件格式

用form-item-wrapper进行包裹,内部就放自己想添加的组件的名字。字段根据配置自己绑定就可。需要记住的是setting的参数如果没有,就需要自己注册setting的设置组件。才能绑定上值。

widget的组件

<template>
<form-item-wrapper :designer="designer" :field="field" :rules="rules"
                       :design-state="designState"
                       :parent-widget="parentWidget"
                       :parent-list="parentList"
                       :index-of-parent-list="indexOfParentList"
                       :sub-form-row-index="subFormRowIndex"
                       :sub-form-col-index="subFormColIndex"
                       :sub-form-row-id="subFormRowId">

    <hydra-fuzzy-search :class="[selected ? 'selected' : '']"
                                    :span="field.options.span"
                                    :label="field.options.label"
                                    v-model="fieldModel"
                                    :data-value="field.options.dataValue"
                                    :config="field.options.cfgMeta"
                                    prop="customerId"
                                    displayProp="customerName"
                                    :disabled="field.options.disabled"
                                    :multiple="field.options.multiple"
                                    v-bind="transferExternal(field.options)"
                                    @change="onChangeFuzzySearch" />
</form-item-wrapper>
</template>

<script>
import FormItemWrapper from "@/components/form-designer/form-widget/field-widget/form-item-wrapper"
import emitter from '@/utils/emitter'
import i18n, { translate } from "@/utils/i18n";
import fieldMixin from "@/components/form-designer/form-widget/field-widget/fieldMixin";

export default {
    name: "fuzzy-search-widget",
    componentName: 'FieldWidget',  //必须固定为FieldWidget,用于接收父级组件的broadcast事件

    components: {
        FormItemWrapper
    },

    inject: ['refList', 'globalOptionData', 'globalModel'],

    mixins: [emitter, fieldMixin, i18n],

    props: {
        field: Object,
        parentWidget: Object,
        parentList: Array,
        indexOfParentList: Number,
        designer: Object,

        designState: {
            type: Boolean,
            default: false
        },

        subFormRowIndex: { /* 子表单组件行索引,从0开始计数 */
            type: Number,
            default: -1
        },

        subFormColIndex: { /* 子表单组件列索引,从0开始计数 */
            type: Number,
            default: -1
        },

        subFormRowId: { /* 子表单组件行Id,唯一id且不可变 */
            type: String,
            default: ''
        },
    },

    data() {
        return {
            fieldModel: "",
            rules: []
        }
    },

    computed: {
        selected() {
            return !!this.designer && this.field.id === this.designer.selectedId
        },
    },

    beforeCreate() {
        /* 这里不能访问方法和属性!! */
    },

    created() {
        /* 注意:子组件mounted在父组件created之后、父组件mounted之前触发,故子组件mounted需要用到的prop
           需要在父组件created中初始化!! */
        this.registerToRefList()
        this.initOptionItems()
        this.initFieldModel()
        this.initEventHandler()
        this.buildFieldRules()

        this.handleOnCreated()
    },

    mounted() {
        this.handleOnMounted()
    },

    beforeDestroy() {
        this.unregisterFromRefList()
    },

    methods: {
        onChangeFuzzySearch(val) {
            // let d = val ? val.id : ''
            this.handleChangeEvent(val)
        },

        transferExternal(options) {
            let query = {};
            if (options.hasOwnProperty('vfromSetQueryLimit') && !!options.vfromSetQueryLimit) {
                query.vfromSetQueryLimit = new Function( 'query', options.vfromSetQueryLimit);
            }
            if (options.hasOwnProperty('vformLimitData') && !!options.vformLimitData) {
                query.vformLimitData = new Function( 'query', options.vformLimitData);
            }
            return query;
        }
    }
}
</script>

<style lang="scss" scoped>
@import "@/styles/global.scss";
</style>

schemal数据

export const fuzzySearchSchema = {
    type: 'fuzzy-search',
    icon: 'rate-field',
    formItemFlag: true,
    isFuzzySearch: true,
    options: {
        dataValue: '',
        limitValue: '',
        cfgMeta: {},

        name: '',
        label: '对象引用',
        labelAlign: '',
        type: 'text',
        defaultValue: "",
        placeholder: '',
        columnWidth: '200px',
        size: '',
        labelWidth: null,
        labelHidden: false,
        readonly: false,
        disabled: false,
        hidden: false,
        clearable: true,
        showPassword: false,
        required: false,
        requiredHint: '',
        validation: '',
        validationHint: '',
        multiple: false,
        jsonable: false,
        chain: false,

        //-------------------
        onCreated: '',
        onMounted: '',
        onInput: '',
        onChange: '',
        onFocus: '',
        onBlur: '',
        onValidate: '',
        vfromSetQueryLimit: '',
        vformLimitData: ''
    }
}

schemaLoader的setting设置组件

fuzzy-search-widget-loader.js

import Vue from 'vue'

import * as PERegister from '@/components/form-designer/setting-panel/propertyRegister'
import * as PEFactory from '@/components/form-designer/setting-panel/property-editor-factory'
import FuzzySearchWidget from './fuzzy-search-widget'

export function initFuzzySearchWidget() {
    Vue.component(FuzzySearchWidget.name, FuzzySearchWidget)

    let fuzzyDataValue = PEFactory.createInputTextEditor('dataValue', 'extension.fuzzySearch.dataValue')
    PERegister.registerCPEditor('fuzzy-search-dataValue', 'data-value-editor', fuzzyDataValue)

    let limitValue = PEFactory.createInputTextEditor('limitValue', 'extension.fuzzySearch.limitValue')
    PERegister.registerCPEditor('fuzzy-search-limitValue', 'limit-value-editor', limitValue)

    let cfgMeta = PEFactory.createInputTextEditor('cfgMeta', 'extension.fuzzySearch.cfgMeta')
    PERegister.registerCPEditor('fuzzy-search-cfgMeta', 'cfgmeta-value-editor', cfgMeta)

    let isJson = PEFactory.createBooleanEditor('jsonable', 'extension.fuzzySearch.jsonable')
    PERegister.registerCPEditor('fuzzy-search-jsonable', 'jsonable-value-editor', isJson)

    let isMultiple = PEFactory.createBooleanEditor('multiple', 'extension.fuzzySearch.multiple')
    PERegister.registerCPEditor('fuzzy-search-multiple', 'multiple-value-editor', isMultiple)

    let dataTableComponent = PEFactory.createEventHandlerEditor('vfromSetQueryLimit', ['query'])
    PERegister.registerEPEditor('vfromSetQueryLimit', 'vform-setquery-limit', dataTableComponent)

    let vformLimitDataComponent = PEFactory.createEventHandlerEditor('vformLimitData', ['query'])
    PERegister.registerEPEditor('vformLimitData', 'vform-limit-data', vformLimitDataComponent)


    let Chain = PEFactory.createBooleanEditor('chain', 'extension.input.chain')
    PERegister.registerCPEditor('chain', 'fuzzy-search-chain-editor', Chain)
}

PEFactory中有创建setting组件的方法。会挂载到Vue的component上面,根据规则就有setting右边的控制组件

总结

当form-designer的widget-panel中widgetsConfig有了定义组件的schema,同时setting有了控制schema的loader的时候,自己的组件就添加成功了。