Custom components
1, Custom component description
Custom components include vue files and configuration files
Configuration file format:
[ { type:"", The name of the component definition is the component vue file export default{ name:"" } Name of the definition icon:"", Show in widget-panel Component icon, which can be iconfont Icons or other icons plugin:true, The tag is a custom component. This is true That's all formItemFlag:true, Mark display fromItem options:{ Custom component properties, if yes size,disabled And other predefined attributes, you can automatically display the attribute configuration. If it is other attributes, you need to setting Set property configuration item in }, setting:{ Show in setting-panel Property configuration item for commonSetting:[], Common properties advancedSetting:[], Advanced properties eventSetting:[] Event properties }, i18n:{ International resources zh-CN:{}, en-US:{} } } ]
Description: the component name of widget panel displays i18n.zh-CN.designer.widgetLabel. {component name}
For example:
i18n:{ zh-CN:{ "designer": { "widgetLabel": { "deptSelect": "Department selection", } } } }
commonSetting and advancedSetting:
{ name: 'isMultiSelect', The attribute name of the custom component, that is, the name of the component props displayName: "setting.multiSelect", Display name during property configuration, supporting internationalization tooltip: "setting.multiSelect", Property configuration tooltip,If it is not configured, it will not be displayed. Internationalization is supported field: { type: "RadioGroup", `Property configuration component`Types of, including: Input,Select,Radiobox,Check,Checkbox,Grid, Prefix omitted Setting,See the modified document for details setting-pannel/controls part props: { `Property configuration component`Properties, using iview Corresponding component attribute definition size:'small' }, child: [{ `Property configuration component`Sub item, each component is different value: "choice", label: "Left", }, { value: "GET", label: "in", }, { value: "POST", label: "right", } ] } }
eventSetting:
{ eventName: 'onselect', Name of custom event eventParam: 'arg,arg1', For the parameters of custom events, you can directly use the names defined here in the event editing dialog box tooltip: "setting.muiltiSelect", setting-panel Description of custom attribute configuration, supporting internationalization displayName: "setting.muiltiSelect", setting-panel The name of the custom attribute configuration, supporting internationalization }
Component vue file
import i18n_lang from './i18n.js'; Import the internationalization configuration file of the component. I configure the main configuration of the component, setting to configure, i18n The configuration is split import plugin_mixin from '../plugin_mixin.js' mixin export default{ name: "projectSelect", Component name, that is, the name of the configuration file type mixins: [plugin_mixin], data(){ return { } }, props:{ value:{ Because when using components v-model Bind form field values, so it needs to be split into props of value(Get values) and methods Inside $emit("input"); type:String, default:"" } } watch:{ value:function(val){ if(val!=null){ this.selectedProject=this.value; }else{ this.selectedProject={}; } } }, created(){ }, methods:{ selected(val){ this.$emit("input",val); trigger v-model,Bind value to form formDataModel In the object. this.$emit("customEvent","selected",[this.selectedProject,row._id]); Trigger event properties, selected Is the name of the custom event, followed by array Is a parameter. corresponding json configuration file eventSetting->eventName,eventSetting->eventParam } } }
plugin_mixin.js
export default { inject:["i18n","formConfig","formDesigner"], i18n internationalization,formConfig,In order to realize the unified setting of forms size,formDesigner Judge the component usage environment, design mode or rendering mode, and the rendering mode is null computed:{ widgetSize(){ Get the uniform settings of the form size //If it is the preview interface of the designer if(!!this.formDesigner){ return this.size||this.formDesigner.formConfig.size||undefined; }else{ return this.size||this.formConfig().size||undefined; } } }, methods: { i18nt(key){ return this.i18n.methods.i18nt(key); }, setup_i18n(i18nlang){ This seems useless for(let key in i18nlang){ this.i18n.methods.appendResource(key,i18nlang[key]); } } } }
explain:
I always feel that the implementation of inject is not elegant... But it is troublesome to use i18n and formConfig in components. I didn't think of any other way.
2, Mechanism description
Show custom component Icon
1. Load the json configuration, write it to props - > customfields of widget panel / index.vue, and display the component name and icon according to the json configuration.
components-iview/form-designer/index.vue
Line: 88 add Props customFields
First write the json configuration content of the component into the customFields of vform designer, and then write it into the customFields of widget pannel and setting pannel respectively
customFields: { type: Array, default: () => {} }
widget-pannel/index.vue
Line 51 shows the custom widget
<Panel name="4" :title="i18nt('designer.customFieldTitle')" v-if="customFields.length>0"> {{i18nt('designer.customFieldTitle')}} <div slot="content"> <draggable tag="ul" :list="customFields" :group="{name: 'dragGroup', pull: 'clone', put: false}" :clone="handleFieldWidgetClone" ghost-class="ghost" :sort="false"> <li v-for="(fld, index) in customFields" :key="index" class="field-widget-item" :title="fld.displayName" @dblclick="addFieldByDbClick(fld)"> <span class="iconfont" :class="[fld.icon]"> {{i18nt(`designer.widgetLabel.${fld.type}`)}} </span> </li> </draggable> </div> </Panel>
Line 106 props add customFields
customFields:{ type:Array, default:()=> [] }
Display property configuration items
1. Six attribute configuration components are defined.
setting-pannel/controls
Six attribute configuration components are defined
SettingInput: used to edit string and numeric attributes
SettingSelect: used to edit drop-down properties
SettingCheckbox: used to edit the true/false attribute
SettingRadioGroup: used to edit radio attribute groups, such as field label alignment of common attributes
SettingCheckGroup: used to edit the checkgroup group. It doesn't seem to be very useful. It's changed according to the RadioGroup
SettingGrid: used to edit multiple options, such as "option settings" of checkbox multiple options
2. Load the json configuration, write it to props - > customfields of setting pannel / index.vue, and display the component name and icon according to the json configuration
setting-pannel/index.vue
Line: 1066 props add customFields
Get the configuration of custom components
customFields: Array
3. Parse the common attribute / advanced attribute / event attribute configuration in json, and dynamically load the attribute configuration component
setting-pannel/index.vue
Line 367: add the dynamic configuration of common attributes, and display the corresponding configuration item parts according to the configuration file
<template v-if="designer.selectedWidget.plugin"> <FormItem v-for="setting in designer.selectedWidget.setting.commonSetting"> <span slot="label"> {{i18nt(setting.displayName)}} <Tooltip v-if="setting.tooltip" :content="i18nt(setting.tooltip)"> <i class="ivu-icon ivu-icon-md-information-circle"></i> </Tooltip> </span> <component :is="'Setting'+setting.field.type" :default="setting.default" :field="setting.field" v-model="optionModel[setting.name]"></component> </FormItem> </template>
Line: 582 add dynamic configuration of advanced components
<template v-if="designer.selectedWidget.plugin"> <FormItem v-for="setting in designer.selectedWidget.setting.advancedSetting"> <span slot="label"> {{i18nt(setting.displayName)}} <Tooltip v-if="setting.tooltip" :content="i18nt(setting.tooltip)"> <i class="ivu-icon ivu-icon-md-information-circle"></i> </Tooltip> </span> <component :is="'Setting'+setting.field.type" :default="setting.default" :field="setting.field" v-model="optionModel[setting.name]"></component> </FormItem> </template>
Line: 684 add dynamic configuration of events
<template v-if="designer.selectedWidget.plugin"> <FormItem v-for="setting in designer.selectedWidget.setting.eventSetting"> <span slot="label"> {{i18nt(setting.displayName)}} <Tooltip v-if="setting.tooltip" :content="i18nt(setting.tooltip)"> <i class="ivu-icon ivu-icon-md-information-circle"></i> </Tooltip> </span> <Button type="info" icon="md-create" plain round @click="editEventHandler(setting.eventName)"> {{i18nt('designer.setting.addEventHandler')}} </Button> </FormItem> </template>
4. Handle custom events
setting-pannel/index.vue
Line: 1412 data add eventPluginParamsMap empty object
eventPluginParamsMap:{ },
Line 980: prompt in the edit event dialog box. If the event name is not found in eventParamsMap, load the displayed name and parameters from eventPluginParamsMap
<div class="codeEditTip">{{(optionModel?optionModel.name:'') + '.' + (eventParamsMap[curEventName]||eventPluginParamsMap[curEventName])}}</div>
Line: the 1500 watch section modifies the selectedWidget
If a custom component is selected, the spelling: method name (parameter) string is written to the eventPluginParamsMap object and provided to the edit event dialog box
'designer.selectedWidget': { handler(val) { if (!!val) { this.activeTab = "1" if(val.plugin){ for(let i in val.setting.eventSetting){ var event=val.setting.eventSetting[i]; this.eventPluginParamsMap[event.eventName]=`${event.eventName}(${event.eventParam}) {`; } } } } },
Custom component display mechanism
1. Use Vue.component to register custom components in advance
main-iview.js
import SettingControls from '@/components-iview/form-designer/setting-panel/controls/';
Vue.use(SettingControls);
2. Dynamically display and call the custom component, inject the options attribute of the custom component into the fieldModel of the two-way data binding form, and implement the custom event handler handlePluginEvent
form-widget/field-index.vue
form-widget/field-widget.vue
Line 321: add dynamic component
<template v-if="!!field.plugin"> <components :is="field.type" v-bind="field.options" v-on="field.event" v-model="fieldModel" @customEvent="handlePluginEvent"></components> </template>
Custom component handling events
1. All custom events of the component trigger customEvent
Component.vue file
Triggered where business needs
this.$emit("customEvent","Event property name",[Parameter 1, parameter 2...]);
2. Event program dynamically set with handlePluginEvent
form-widget/field-index.vue
Line 799: add personalized event processing
handlePluginEvent(eventName,eventArgs) { let param=[]; for(let i in this.field.setting.eventSetting){ let event=this.field.setting.eventSetting[i]; if(event.eventName===eventName){ param=event.eventParam.split(","); } } if (!!this.field.options[eventName]) { let customFunc = new Function(...param,this.field.options[eventName]) customFunc.apply(this,eventArgs) } },
International solutions
1. designer
components-iview/form-designer/index.vue
Line: 143 methods add the internationalization configuration for resolving custom components, and call for execution when mounted
initPluginI18n(){ for(let i in this.customFields){ let plugins=this.customFields[i]; if(plugins.i18n){ for(let lang in plugins.i18n){ i18n.methods.appendResource(lang,plugins.i18n[lang]); } } } },
2. Renderer
form-render/index.vue
Line 61 props add customFields
Getting the configuration of custom components mainly requires international configuration
customFields: { type: Array, default: () => {} }
Line: 123 watch adds the internationalization configuration for resolving custom components
customFields:{ deep: true, handler(val, oldVal) { val.forEach(plugin=>{ if(plugin.i18n){ for(let key in plugin.i18n){ i18n.methods.appendResource(key,plugin.i18n[key]); } } }) } }
3. Customize the internal internationalization of components, such as buttons, pop-up window titles and prompt text
components-iview/form-designer/index.vue
Line 108: add Provide i18n and provide the methods of i18n to the plug-in
provide() { return { i18n:i18n } },
form-render/index.vue
Line 76 provide s i18n, which is provided to the component
i18n:i18n
Component interior
Get the instance of i18n through inject, and then use the methods.i18nt method
Custom component response form global configuration
1. Inject formDesigner and formConfig to determine whether it is design mode or rendering mode through formDesigner
form-widget/field-index.vue
Line: 63 Provide add formDesigner
form-render/index.vue
Line 71 provide adds formDesigner and injects null
formDesigner:null, //Inject null so that the plug-in can distinguish whether it is called in designer mode or Render mode
2. Use plugin when developing custom components_ mixin.js
Other modified problems
Do you see if this is reasonable?
1. The dynamically obtained form configuration does not take effect
When using VFormRender, if formJson is written directly in the data, the form can be displayed, but if it is read from the database and modified the formData, the form cannot be displayed.
form-render/index.vue
Originally, formConfig was written in calculated and then provide d to the sub components. I moved it to methods, but in this way, the value was changed into a function reference
export default{ ... provide(){ return { ... formConfig:this.formConfig ... } }, computed:{ //formConfig(){ // return this.formJson.formConfig //} ... }, methods:{ formConfig(){ return this.formJson.formConfig } ... } }
Where the sub component uses this.config, it is changed to this.config()
It involves labelWidth, labelAlign and size in field-widget.vue computed
2. In the editing function, the modified content cannot be obtained when reading the database, setting the form field content (setFormData), and then obtaining (getFormData).
form-render/index.vue
The formDataModel was re assigned this_ provided.globalModel.formModel
getFormData(needValidation = true) { //Prevent editing mode from getting the latest data this.formDataModel=this._provided.globalModel.formModel; ... }
3. Form emptying function
Cannot empty
form-render/index.vue
resetForm() { //Reset Form ... // this.$refs.renderForm.clearValidate() customary this.$refs.renderForm.resetFields(); Change to }