低代码研学笔记之 VForm(五)

原创
2024/12/17 10:25
阅读数 33

低代码研学笔记之 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]
      },

    }
  }
展开阅读全文
加载中
点击引领话题📣 发布并加入讨论🔥
0 评论
0 收藏
0
分享
返回顶部
顶部