Gitee千Star优质项目解析: ng-form-element低开引擎解析

好家伙,

在写项目的时候,我发现自己的平台的组件写的实在是太难看了,于是想去gitee上偷点东西,于是我们本期的受害者出现了

gitee项目地址

https://gitee.com/jjxliu306/ng-form-elementplus-sample.git

组件库以及引擎完全开源,非常牛逼的项目,非常牛逼的作者

项目名:ng-form-element

整体的布局,组件样式,编辑器模板,组件拖动时的过渡动画,都写的非常漂亮,(比我写的好看多了)

于是我决定,扒一下他的内裤,学习(抄袭)一下

0.项目解析

官方文档:NG-FORM

于是我们快速定位到引擎部分

1.开始分析

我们知道,低开的引擎做的事无非是

** * 把数据***

***  变成视图***

我们先来找数据

数据部分

复制代码
//packages/index.vue

  data() {
    return {
      selectItem: {},
      arrow: false,
      i18nkey: getUUID(),
      formTemplate: this.template || {
              list: [
              ],
              config: {
                labelPosition: 'left',
                labelWidth: 100,
                size: 'mini',
                outputHidden: true, //  是否输出隐藏字段的值 默认打开,所有字段都输出
                hideRequiredMark: false,
                syncLabelRequired: false,
                labelSuffix: '' , // 标签后缀
                customStyle: ''
              }
            },
    }
  },
  props: {
    template: {
      type: Object,
      default: () => {
        return {
          list: [],
          config: {
            labelPosition: 'top',
            labelWidth: 80,
            size: 'mini',
            outputHidden: true, //  是否输出隐藏字段的值 默认打开,所有字段都输出
            hideRequiredMark: false,
            syncLabelRequired: false,
            labelSuffix: '' , // 标签后缀
            customStyle: ''
          }
        }
      }
    },

然后来找视图部分

复制代码
//packages/index.vue

<ContainerPanel
              :formTemplate="formTemplate"
              @handleSelectItem="handleSelectItem"
              :selectItem="selectItem"
              :arrow="arrow"
          >
          </ContainerPanel>

import ContainerPanel from './panel-container/index.vue'
  1. :formTemplate:传模板数据的

  2. @handleSelectItem:这是一个事件处理器,当组件内的 handleSelectItem 方法被触发时,会执行传入的回调函数。handleSelectItem 是组件内部定义的事件处理函数名。

  3. :selectItem:这是绑定的数据属性,用于传递一个"被选中的数据"

  4. :arrow:暂时没看出来干嘛的

复制代码
//packages/panel-container/index.vue
<el-form  
          :label-width="formTemplate.config.labelWidth + 'px'" 
          class="ng-form"
          :label-position="formTemplate.config.labelPosition"
          :hide-required-asterisk="formTemplate.config.hideRequiredMark" 
          :label-suffix="formTemplate.config.labelSuffix"
          ref="form" 
          :style="formTemplate.config.customStyle" 
          :size="formTemplate.config.size"
        >
        <el-row :gutter="20" class="row"> 
                <draggable  
                    tag="div"
                    class="draggable-box"
                    v-bind="{
                      group: 'form-draggable',
                      ghostClass: 'moving',
                      animation: 180,
                      handle: '.drag-move'
                    }"
                    :force-fallback="true"
                    v-model="formTemplate.list" 
                    @add="dragEnd($event, formTemplate.list)" 
                      >
                    <transition-group tag="div" name="list" class="items-main"> 
                        <Node  
                                :class="{'drag-move' : record.drag_ == undefined || record.drag_  }"
                                v-for="record in formTemplate.list"
                            :key="record.key"
                            :record="record"
                            :isDrag="true"
                            :config="formTemplate.config"
                            :selectItem="selectItem"
                            @handleSelectItem="handleSelectItem"
                            @handleCopy="handleCopy(record)"
                            @handleDetele="handleDetele(record)"
                            >  
                        
                        </Node> 
                    </transition-group>
                </draggable> 
         
        </el-row> 
    </el-form> 

import Item from '../items/index.vue'

  1. <transition-group> 是 Vue.js 的内置过渡组件,用于给列表添加过渡效果。

  2. el-form的作用我们后面说

复制代码
//packages/form-design/items/index.vue
<template>    
  <ItemNode 
    v-if="isLayout"
    :record="record"
    :disabled="disabled" 
    :preview="preview"
    :isDragPanel="isDragPanel"
    :prop-prepend="propPrepend"
    :selectItem="selectItem" 
    :style="{'display': recordVisible ? '' : 'none'}"
    :models="models" 
    @handleSelectItem="handleSelectItem" 
    >
      <!-- 递归传递插槽!!! -->
      <template v-for="slot in Object.keys($slots)"  :slot="slot">
        <slot :name="slot" :record="record"/>
      </template>
    </ItemNode> 
  <el-form-item 
    v-else
    :label="label" 
    :style="{'display': recordVisible ? '' : 'none'}"
    :rules="recordRules"
    :prop="recordProps"
    :key="record.key"
    :required="recordRequired" 
    :id="record.model" 
    :name="record.model"
    :label-width="labelWidth"
    >       
    <ItemNode 
      :record="record"
      :disabled="disabled" 
      :preview="preview"
      :isDragPanel="isDragPanel"
      :selectItem="selectItem" 
      :prop-prepend="propPrepend"
      :models="models" 
      @handleSelectItem="handleSelectItem"
      >
        <!-- 递归传递插槽!!! -->
        <template v-for="slot in Object.keys($slots)"  :slot="slot">
          <slot :name="slot" :record="record"/>
        </template>
      </ItemNode> 
  </el-form-item>  
</template>

import ItemNode from './node.vue'

**  1.v-if="isLayout":是否为预览模式**

复制代码
//packages/form-design/items/node.vue

<template>

  <component
        :record="record"
        :style="{
          margin: record.margin && record.margin.length > 0 ? record.margin.join('px ') + 'px' : '0px',
          borderRadius: (record.itemBorderRadius ? record.itemBorderRadius : 0) + 'px',
          backgroundColor: record.backgroundColor ? record.backgroundColor  : '',

        }"
        :disabled="disabled"
        :preview="preview"
        :isDragPanel="isDragPanel"
        :selectItem="selectItem"
        :prop-prepend="propPrepend"
        :models.sync="models"
        @handleSelectItem="handleSelectItem"
        @handleFocus="handleFocus"
        @handleBlur="handleBlur"
        :is="customComponent">
      <!-- 递归传递插槽!!! -->
      <template v-for="slot in Object.keys($slots)"  :slot="slot">
        <slot :name="slot" :record="record"/>
      </template>  
  </component>
</template>

ok终于到了最后一层

最终的关键就是这么行代码

  • :is="customComponent":动态绑定组件名称,根据 customComponent 的值来渲染不同的组件。
复制代码
customComponent() {

      // 判断是否自定义组件
      if(this.customComponents && this.customComponents.length > 0) {
        const cs = this.customComponents.filter(t=> t.type == this.record.type)

        if(cs && cs.length > 0) {
          return cs[0].component
        }
      }

      const selectItemType = this.record.type
            // 将数组映射成json
      if(this.items && this.items.length > 0) {
            for(let i = 0 ; i < this.items.length ; i++) {
              const itemList = this.items[i]

              if(itemList.list && itemList.list.length > 0) {
                const fs = itemList.list.filter(t=>t.type == selectItemType)
                if(fs && fs.length > 0) {
                  return fs[0].component
                }
              }

            }
      }
      return null
    },

2.数据格式

在这个项目上随便做的一个表格并导出数据

复制代码
{
    "list": [
        {
            "type": "input",
            "options": {
                "defaultValue": "",
                "type": "text",
                "prepend": "",
                "append": "",
                "placeholder": "请输入",
                "maxLength": 0,
                "clearable": false,
                "hidden": false,
                "disabled": false
            },
            "label": "输入框",
            "labelWidth": -1,
            "width": "100%",
            "span": 24,
            "model": "input_17156872001522",
            "key": "input_17156872001522",
            "rules": [
                {
                    "required": false,
                    "message": "必填项",
                    "trigger": [
                        "blur"
                    ]
                }
            ],
            "dynamicLabel": false
        },
        {
            "type": "radio",
            "options": {
                "defaultValue": "",
                "placeholder": "请输入",
                "dynamic": 0,
                "options": [
                    {
                        "value": "1",
                        "label": "选项1"
                    },
                    {
                        "value": "2",
                        "label": "选项2"
                    }
                ],
                "methodType": "get",
                "dynamicPostData": "",
                "remoteFunc": "",
                "dataPath": "",
                "remoteValue": "",
                "remoteLabel": "",
                "dictType": "",
                "disableItemScript": "",
                "hidden": false,
                "disabled": false,
                "linkage": false,
                "linkData": []
            },
            "label": "单选框",
            "labelWidth": -1,
            "width": "100%",
            "span": 24,
            "model": "radio_17156872321432",
            "key": "radio_17156872321432",
            "rules": [
                {
                    "required": false,
                    "message": "必填项",
                    "trigger": [
                        "blur"
                    ]
                }
            ],
            "dynamicLabel": false
        },
        {
            "type": "button",
            "event_": false,
            "listen_": false,
            "options": {
                "size": "mini",
                "type": "primary",
                "align": "left",
                "control": "",
                "eventName": "",
                "script": "",
                "plain": false,
                "circle": false,
                "round": false,
                "disabled": false
            },
            "label": "按钮",
            "labelWidth": 0,
            "width": "100%",
            "span": 24,
            "model": "button_17156901763582",
            "key": "button_17156901763582",
            "dynamicLabel": false
        },
        {
            "type": "rate",
            "options": {
                "max": 5,
                "defaultValue": 0,
                "allowHalf": false,
                "hidden": false,
                "disabled": false
            },
            "label": "评分",
            "labelWidth": -1,
            "width": "100%",
            "span": 24,
            "model": "rate_17156901773022",
            "key": "rate_17156901773022",
            "rules": [
                {
                    "required": false,
                    "message": "必填项",
                    "trigger": [
                        "blur"
                    ]
                }
            ],
            "dynamicLabel": false
        }
    ],
    "config": {
        "labelPosition": "top",
        "labelWidth": 80,
        "size": "mini",
        "outputHidden": true,
        "hideRequiredMark": false,
        "syncLabelRequired": false,
        "labelSuffix": "",
        "customStyle": ""
    }
}
  1. type:表示该组件的类型,该对象的类型为 "rate",用于评分。
  2. options:表示该组件的选项,包括:
  3. label:表示该组件的标签文本,值为 "评分"。
  4. labelWidth:表示该组件的标签宽度,值为 -1,表示使用系统默认值。
  5. width:表示该组件的宽度,值为 "100%"。
  6. span:表示该组件所占的栅格数,值为 24。
  7. model:表示该组件的 v-model 绑定值的变量名,值为 "rate\_17156901773022"。
  8. key:表示该组件的唯一标识,值为 "rate\_17156901773022"。
  9. rules:表示该组件的校验规则,包括:
  10. dynamicLabel:表示该组件的标签是否动态显示,值为 false。

3.总结

在翻了许许多多的低开项目后,发现,

巨大多数的低开项目要么引擎核心闭源,要么物料组件库闭源

而这个项目,所有的东西都开源了,真真正正的开源,真的牛bi

非常值得自学的一个低开项目