Gitee千Star優質專案解析: ng-form-element低開引擎解析

养肥胖虎發表於2024-05-14

好傢伙,

在寫專案的時候,我發現自己的平臺的元件寫的實在是太難看了,於是想去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

非常值得自學的一個低開專案

相關文章