低代码研学笔记之 VForm(四) WidgetPanel 左边栏的可拖拽组件
低代码研学笔记之 VForm(五) VFormWidget 设计器容器组件
低代码研学笔记之 VForm(六) SettingPanel 组件属性和表单属性设计组件
低代码研学笔记之 VForm(七) ToolbarPanel 设计器顶部的工具栏组件
学习关键字
:is 传递组件,核心知识点,组件动态渲染的关键功能。
require.context: 模块的自动化导入,极大的简化了组件导入。
语法:require.context(directory, useSubdirectories, regExp); 注册为全局组件
具体用法示例参见:
src/components/form-designer/form-widget/container-widget/index.js
src/components/form-designer/form-widget/field-widget/index.js
页面布局分析
<template>
<div class="form-widget-container">
<!-- 表单组件-->
<el-form class="full-height-width widget-form" :label-position="labelPosition"
:class="[customClass, layoutType + '-layout']" :size="size" :validate-on-rule-change="false">
<div v-if="designer.widgetList.length === 0" class="no-widget-hint">{{i18nt('designer.noWidgetHint')}}</div>
<!-- 可拖动区域
:list 组件数据源;
:handle=“.mover” 只有当鼠标在class为mover类的元素上才能触发拖到事件
@start 开始移动事件
@end 结束移动事件
@move 当拖动元素经过另一个元素触发
@change 元素位置改变后触发,可获取当前元素数据moved.element,以及当前元素原来的位置oldIndex,和移动后的newIndex
group 如果一个页面有多个拖拽区域,通过设置group名称可以实现多个区域之间相互拖拽 ,
同一名称的group之间可以互相拖拽,或者 { name: “…”, pull: [true, false, ‘clone’, array , function], put: [true, false, array , function] }
:ghost-class=“ghostClass” 设置拖动元素的占位符类名,你的自定义样式可能需要加!important才能生效,
并把forceFallback属性设置成true
animation 拖动时的动画效果,如设置animation=1000表示1秒过渡动画效果
-->
<draggable :list="designer.widgetList" v-bind="{group:'dragGroup', ghostClass: 'ghost',animation: 300}"
handle=".drag-handler" @end="onDragEnd" @add="onDragAdd" @update="onDragUpdate" :move="checkMove">
<transition-group name="fade" tag="div" class="form-widget-list">
<template v-for="(widget, index) in designer.widgetList">
<!-- 容器组件,todo 需要研究数据绑定的原理-->
<template v-if="'container' === widget.category">
<!-- :is 是否组件库的组件 -->
<component :is="getWidgetName(widget)" :widget="widget" :designer="designer" :key="widget.id" :parent-list="designer.widgetList"
:index-of-parent-list="index" :parent-widget="null"></component>
</template>
<!--
表单控件,
:is 传递一个组件, 而组件import批量导入了 input-widget、button-widget
-->
<template v-else>
<component :is="getWidgetName(widget)" :field="widget" :designer="designer" :key="widget.id" :parent-list="designer.widgetList"
:index-of-parent-list="index" :parent-widget="null" :design-state="true"></component>
</template>
</template>
</transition-group>
</draggable>
</el-form>
</div>
</template>
导入组件依赖分析
import Draggable from 'vuedraggable'
import '@/components/form-designer/form-widget/container-widget/index'
import FieldComponents from '@/components/form-designer/form-widget/field-widget/index'
import i18n from "@/utils/i18n"
脚本分析
export default {
name: "VFormWidget",
componentName: "VFormWidget",
mixins: [i18n],
components: {
Draggable,
...FieldComponents,
},
//接收组件传参
props: {
designer: Object,
formConfig: Object,
optionData: { //prop传入的选项数据
type: Object,
default: () => ({})
},
globalDsv: {
type: Object,
default: () => ({})
},
},
/**
* provide的使用方法:
* 在父组件中定义provide
* provide和data,methods同级
* provide中返回值是一个对象,相当于将对象中的内容注入到子孙组件
* 子孙组件使用inject: [ 由provide键名组成的数组 ] ,来获取父级组件的方法或者其他属性
* @returns {{refList: {}, globalModel: {formModel: {}}, getGlobalDsv: (function(): Object), formConfig: Object, globalOptionData: Object, getOptionData: (function(): Object)}}
*/
provide() {
return {
refList: this.widgetRefList,
formConfig: this.formConfig,
getGlobalDsv: () => this.globalDsv, // 全局数据源变量
globalOptionData: this.optionData,
getOptionData: () => this.optionData,
globalModel: {
formModel: this.formModel,
}
}
},
//注入父组件provide提供的数据
inject: ['getDesignerConfig'],
data() {
return {
formModel: {},
widgetRefList: {},
}
},
/**
* computed主要用于计算属性,它基于data或props中的数据通过计算得到一个新值。
* 这个新值会根据依赖数据的变化而更新,并且支持缓存。当依赖数据没有变化时,它会读取缓存数据,从而提升性能。
* computed属性不支持异步操作,如果尝试在其中使用异步操作,将不会生效。
*/
computed: {
labelPosition() {
if (!!this.designer.formConfig && !!this.designer.formConfig.labelPosition) {
return this.designer.formConfig.labelPosition
}
return 'left'
},
size() {
if (!!this.designer.formConfig && !!this.designer.formConfig.size) {
return this.designer.formConfig.size
}
return 'medium'
},
customClass() {
return this.designer.formConfig.customClass || ''
},
layoutType() {
return this.designer.getLayoutType()
},
},
/**
* watch主要用于监听某些特定数据的变化,并在数据变化时执行特定的操作。
* watch支持异步操作,适合在数据变化时执行异步或开销较大的操作。
* watch不支持缓存,每次数据变化时都会执行相应的操作。
* watch的监听函数接收两个参数:最新的值和之前的值,这使得它在处理复杂逻辑时更为灵活。
* 在实际应用中,computed适用于当一个属性受多个属性影响时使用,例如反转字符串、计算总价等场景。
* 而watch适合于一条数据影响多条数据的情况,或者需要深度监听对象内部值变化时使用。
*/
watch: {
'designer.widgetList': {
deep: true,
handler(val) {
//
}
},
'designer.formConfig': {
deep: true,
handler(val) {
//
}
},
},
/**
* created阶段无法访问组件的DOM元素,而mounted阶段可以访问组件的DOM元素。
* created常用于初始化数据和执行异步操作,mounted常用于执行与DOM相关的操作
*/
created() {
this.designer.initDesigner( !!this.getDesignerConfig().resetFormJson );
this.designer.loadPresetCssCode( this.getDesignerConfig().presetCssCode )
},
mounted() {
this.disableFirefoxDefaultDrop() /* 禁用Firefox默认拖拽搜索功能!! */
this.designer.registerFormWidget(this)
},
methods: {
/**
* 根据组件类型获取组件名称
* @param widget
* @returns {string}
*/
getWidgetName(widget) {
return widget.type + '-widget'
},
/**
* 禁用Firefox默认拖拽搜索功能
*/
disableFirefoxDefaultDrop() {
let isFirefox = (navigator.userAgent.toLowerCase().indexOf("firefox") !== -1)
if (isFirefox) {
document.body.ondrop = function (event) {
event.stopPropagation();
event.preventDefault();
}
}
},
/**
* 拖拽结束
* @param evt
*/
onDragEnd(evt) {
//console.log('drag end000', evt)
},
/**
* 拖拽添加
* @param evt
*/
onDragAdd(evt) {
const newIndex = evt.newIndex
if (!!this.designer.widgetList[newIndex]) {
this.designer.setSelected( this.designer.widgetList[newIndex] )
}
this.designer.emitHistoryChange()
this.designer.emitEvent('field-selected', null)
},
onDragUpdate() { /* 在VueDraggable内拖拽组件发生位置变化时会触发update,未发生组件位置变化不会触发!! */
this.designer.emitHistoryChange()
},
checkMove(evt) {
return this.designer.checkWidgetMove(evt)
},
getFormData() {
return this.formModel
},
/**
* 获取组件实例
* @param widgetName
* @param showError
* @returns {*}
*/
getWidgetRef(widgetName, showError = false) {
let foundRef = this.widgetRefList[widgetName]
if (!foundRef && !!showError) {
this.$message.error(this.i18nt('render.hint.refNotFound') + widgetName)
}
return foundRef
},
/**
* 获取当前选中的组件实例
* @returns {*}
*/
getSelectedWidgetRef() {
let wName = this.designer.selectedWidgetName
return this.getWidgetRef(wName)
},
/**
* 清空组件引用列表
*/
clearWidgetRefList() {
Object.keys(this.widgetRefList).forEach(key => {
delete this.widgetRefList[key]
})
},
/**
* 删除组件引用
* @param widgetRefName
*/
deleteWidgetRef(widgetRefName) {
delete this.widgetRefList[widgetRefName]
},
}
}