組件化頁面:封裝el-form

目前在編寫我的項目,有一個是管理平臺,基本每一個頁面都有el-from,因此對el-form作了二次封裝, 組件在我的開發使用不錯,但不肯定能知足各類業務需求,因此這裏主要和你們分享一下設計思路。

設計組件

分析問題

el-form是element-ui庫的表單組件,若是咱們要將其進行二次封裝,那麼須要考慮幾個問題:vue

  • 如何設計表單渲染字段
  • 不一樣類型的el-form-item怎麼去渲染,好比input,select,或者自定義顯示內容等
  • 表單聯動怎麼去處理
  • 事件綁定
  • 表單驗證
  • 更多需求...

下面經過這些點,來實現封裝一個二次的el-form組件。git

從字段開始

圖片描述

拿業務用到的表單來舉例,在這個表單中的需求分析。github

首先是el-form-item的類型:element-ui

  • tag類型顯示
  • input輸入
  • select選擇
  • 按鈕或者圖片的顯示或者綁定操做
  • textarea輸入

而後再分析每一個節點:微信

  • label寬度
  • 是否須要驗證
  • placeholder顯示
  • 驗證規則
  • 綁定的相關事件
  • 是否可爲readonly/disabled
  • 節點的class/樣式 (一行顯示一個或者多個)

初步分析,大概會有這些需求,接下來對單個問題一一來分析解決。函數

設計渲染字段列表

正常狀況下,咱們使用form,它的子項會是這樣的,好比有input和select:組件化

// input類型
<el-form-item label="活動名稱" prop="name">
  <el-input v-model="ruleForm.name"></el-input>
</el-form-item>

// select類型
<el-form-item label="活動區域" prop="region">
  <el-select v-model="ruleForm.region" placeholder="請選擇活動區域">
    <el-option label="區域一" value="shanghai"></el-option>
    <el-option label="區域二" value="beijing"></el-option>
  </el-select>
</el-form-item>

仔細一看,外面那一層殼是能夠拿掉的,好比長這樣:post

<el-form-item label="label" prop="prop">
  // 若是是input類型 
  <el-input v-if="input" v-model="ruleForm.name"></el-input>
  // 若是是select類型 
  <el-select v-if="select" v-model="ruleForm.region" placeholder="請選擇活動區域">
    <el-option label="區域一" value="shanghai"></el-option>
    <el-option label="區域二" value="beijing"></el-option>
  </el-select>
</el-form-item>

那由此咱們能夠設計出循環form的字段列表:ui

  • label (字段名)
  • value (字段值 prop,後面會有value代替)
  • type (字段類型 input/select/password/textarea等)

而後除了上面的例子咱們還能夠本身擴展一些字段:this

  • event (綁定的方法)
  • list (列表 若是是select類型,須要有對於的list去渲染)
  • TimePickerOptions (時間配置 若是是時間類型,能夠傳入配置)
  • disabled (是否禁止)
  • filterable (是否可篩選)
  • clearable (是否可清除)
  • required (是否必填 根據這個字段,去設置對於的驗證規則)
  • validator (自定義驗證 驗證時將使用自定義驗證方法)
  • show (是否顯示, 布爾值或者是函數,下面會對聯動渲染詳細分析)
  • 更多(根據需求和場景擴展更多字段)

而後完整的字段配置列表大概是這樣的(我的使用場景,不須要使用到全部的設計字段):

fieldList: [
          { label: '帳號', value: 'account', type: 'input', required: true, validator: checkAccount },
          { label: '密碼', value: 'password', type: 'password', required: true, validator: checkPwd },
          { label: '暱稱', value: 'name', type: 'input', required: true },
          { label: '性別', value: 'sex', type: 'select', list: 'sexList', required: true },
          { label: '頭像', value: 'avatar', type: 'slot', className: 'el-form-block' },
          { label: '手機號碼', value: 'phone', type: 'input', validator: checkPhone },
          { label: '微信', value: 'wechat', type: 'input', validator: checkWechat },
          { label: 'QQ', value: 'qq', type: 'input', validator: checkQQ },
          { label: '郵箱', value: 'email', type: 'input', validator: checkEmail },
          { label: '描述', value: 'desc', type: 'textarea', className: 'el-form-block' },
          { label: '狀態', value: 'status', type: 'select', list: 'statusList', required: true }
        ]

組件內部怎麼操做,很簡單,根據規則,一一對應循環字段,綁定屬性就ok了,因此組件內部template就是這麼點代碼:

<el-form
    ref="form"
    class="page-form"
    :class="className"
    :model="data"
    :rules="rules"
    :label-width="labelWidth"
  >
    <el-form-item
      v-for="(item, index) in getConfigList()"
      :key="index"
      :prop="item.value"
      :label="item.label"
      :class="item.className"
    >
      <!-- solt -->
      <template v-if="item.type === 'slot'">
        <slot :name="'form-' + item.value" />
      </template>
      <!-- 普通輸入框 -->
      <el-input
        v-if="item.type === 'input' || item.type === 'password'"
        v-model="data[item.value]"
        :type="item.type"
        :disabled="item.disabled"
        :placeholder="getPlaceholder(item)"
        @focus="handleEvent(item.event)"
      />
      <!-- 文本輸入框 -->
      <el-input
        v-if="item.type === 'textarea'"
        v-model.trim="data[item.value]"
        :type="item.type"
        :disabled="item.disabled"
        :placeholder="getPlaceholder(item)"
        :autosize="{minRows: 2, maxRows: 10}"
        @focus="handleEvent(item.event)"
      />
      <!-- 計數器 -->
      <el-input-number
        v-if="item.type === 'inputNumber'"
        v-model="data[item.value]"
        size="small"
        :min="item.min"
        :max="item.max"
        @change="handleEvent(item.event)"
      />
      <!-- 選擇框 -->
      <el-select
        v-if="item.type === 'select'"
        v-model="data[item.value]"
        :disabled="item.disabled"
        :clearable="item.clearable"
        :filterable="item.filterable"
        :placeholder="getPlaceholder(item)"
        @change="handleEvent(item.event, data[item.value])"
      >
        <el-option
          v-for="(childItem, childIndex) in listTypeInfo[item.list]"
          :key="childIndex"
          :label="childItem.key"
          :value="childItem.value"
        />
      </el-select>
      <!-- 日期選擇框 -->
      <el-date-picker
        v-if="item.type === 'date'"
        v-model="data[item.value]"
        :type="item.dateType"
        :picker-options="item.TimePickerOptions"
        :clearable="item.clearable"
        :disabled="item.disabled"
        :placeholder="getPlaceholder(item)"
        @focus="handleEvent(item.event)"
      />
      <!-- 信息展現框 -->
      <el-tag v-if="item.type === 'tag'">
        {{ $fn.getDataName({dataList: listTypeInfo[item.list], value: 'value', label: 'key', data: data[item.value]}) || '-' }}
      </el-tag>
    </el-form-item>
  </el-form>

自定義和聯動

經過上面的操做,咱們完成了基本的表單動態渲染,可是隻是針對於模版型的場景,舉個例子,若是表單中我要顯示選擇頭像,或者須要增長一個第三方的控件,這個時候,上面的渲染規則就有些無能威力了,因此咱們須要表單組件支持自定義渲染。

slot-組件自定義神器

不瞭解的同窗請戳這個vue中的slot屬性
在上面,組件的代碼中有這樣一段:

<!-- solt -->
      <template v-if="item.type === 'slot'">
        <slot :name="'form-' + item.value" />
      </template>

這段代碼的意思是:渲染類型爲slot,插槽的名稱爲‘form-’前綴加上字段的值。
有什麼用呢?咱們回到使用組件的頁面。

圖片描述

在form中,這裏有一個子項,須要顯示圖片和按鈕,這個時候組件內部已經定義了插槽,而且有對於的名字,咱們只須要在外部將對應的插槽傳入到組件中,便可實現自定義內容,請看對應代碼:

<!-- 自定義插槽-選擇頭像 -->
        <template v-slot:form-avatar>
          <div class="slot-avatar">
            <img
              v-imgAlart
              :src="formInfo.data.avatar"
              style="height: 30px;"
            >
            <el-button
              v-waves
              type="primary"
              icon="el-icon-picture"
              size="mini"
              @click="handleClick('selectFile')"
            >
              {{ formInfo.data.avatar ? '更換頭像' : '選擇頭像' }}
            </el-button>
          </div>
        </template>
      </page-form>

組件內插槽除了能夠接收對應名字的內容外,還能夠將組件中的數據經過插槽傳輸到外部,在外部使用組件數據渲染內容,自定義組件神器,請務必瞭解

表單聯動

表單聯動,推薦閱讀element文章---不再想寫表單了
表單組件,重要點之一就是表單聯動了,咱們來分析一下聯動的場景:

  1. 建立用戶時,帳號能夠填寫,編輯用戶時,帳號只能查看
  2. 建立時表單顯示的字段只有三個,編輯時可查看到字段爲十個
  3. 單選框,選擇A,AA字段消失,選擇B, AA,BB字段消失,選擇C,全部字段都顯示,而且變成必填
  4. 更多聯動場景...

顯示聯動

1.字段定義爲布爾值時

在字段定義的時候,有定義一個字段,show,咱們能夠將show字段定義爲布爾值,而後在頁面中watch定義數據的是否顯示,好比這段代碼:

// 根據彈窗類型作數據聯動
    'dialogInfo.type' (val) {
      const fieldList = this.formInfo.fieldList
      switch (val) {
        case 'userInfo':
          fieldList.forEach(item => {
            if (['user_old_pwd', 'user_new_pwd', 'user_new1_pwd'].includes(item.value)) {
              item.show = true
            } else {
              item.show = false
            }
          })
          break
        case 'password':
          fieldList.forEach(item => {
            if (!['user_old_pwd', 'user_new_pwd', 'user_new1_pwd'].includes(item.value)) {
              item.show = true
            } else {
              item.show = false
            }
          })
          break
      }
    }

2.字段定義爲函數時

若是不想再組件外部操做,想把顯示隱藏的渲染邏輯交給組件內部處理,那咱們能夠定義show字段爲函數,好比這段代碼:

{label: '名稱', value: 'name', show: data => {
    return data.type === 'userInfo'
}}

這兩種方式是我目前自定義組件場景中有用到的,根據需求,使用不一樣的方法

邏輯聯動和事件中間件設計

1. 字段定義爲函數時
邏輯聯動表示form中某一項發生改變,其餘一項或者多項會根據對應的數據發生相對的改變,好比下面這段代碼(由於我的項目目前沒有這種業務,因此並無相關示例,代碼來自element博客):

[
    {
      title: '活動類型',
      key: 'act_type',
      type: 'radio',
      props: {
        options: { 1: '拉新', 2: '衝單', 3: '回饋' }
      }
    },
    {
      title: '生效方式',
      key: 'effect_type',
      type: 'radio',
      props(form) {
        const value;
        const map = { 1: '當即', 2: '按時間', 3: '按條件' };
        if (form.act_type === 3) {
          value = 1;
        }
        return { 
          value: value,
          options: map 
        };
      }
    }
  ]

將須要聯動的字段定義爲方法,方法接收表單數據,方法根據數據進行對應的聯動,這種方法能夠基本完美解決各類問題,在組件內部則須要對屬性添加一層判斷,普通類型只須要進行綁定,typeof爲function須要綁定運行的函數。

2. 定義事件字段,經過中間件派發到外部處理
定義事件字段,定義的字段列表中,咱們能夠設計一個event字段,當事件上綁定方法的時候,字段設計爲這樣:

{ label: '性別', value: 'sex', type: 'select', list: 'sexList', event: 'changeName', required: true }

示例代碼爲select類型,組件的input綁定代碼上也須要綁定相關事件:

<!-- 選擇框 -->
      <el-select
        v-if="item.type === 'select'"
        v-model="data[item.value]"
        :disabled="item.disabled"
        :clearable="item.clearable"
        :filterable="item.filterable"
        :placeholder="getPlaceholder(item)"
        @change="handleEvent(item.event, data[item.value])"
      >
        <el-option
          v-for="(childItem, childIndex) in listTypeInfo[item.list]"
          :key="childIndex"
          :label="childItem.key"
          :value="childItem.value"
        />
      </el-select>

經過handleEvent中間件,綁定對應類型和對應數據,中間件函數的用處就負責進行頁面和組件的通信,組件內部穿出事件類型和數據,頁面接收而且處理,不論多少的事件,只有一箇中間件便可以解決。
組件內部中間件處理:

// 綁定的相關事件
    handleEvent (evnet) {
      this.$emit('handleEvent', evnet)
    }

組件使用代碼:

<!-- form -->
      <page-form
        v-if="dialogInfo.type !== 'userTransfer'"
        :ref-obj.sync="formInfo.ref"
        :data="formInfo.data"
        :field-list="formInfo.fieldList"
        :rules="formInfo.rules"
        :label-width="formInfo.labelWidth"
        :list-type-info="listTypeInfo"
        @handleClick="handleClick"
        @handleEvent="handleEvent"
      >
        <!-- 自定義插槽-選擇頭像 -->
        <template v-slot:form-avatar>
          <div class="slot-avatar">
            <img
              v-imgAlart
              :src="formInfo.data.avatar"
              style="height: 30px;"
            >
            <el-button
              v-waves
              type="primary"
              icon="el-icon-picture"
              size="mini"
              @click="handleClick('selectFile')"
            >
              {{ formInfo.data.avatar ? '更換頭像' : '選擇頭像' }}
            </el-button>
          </div>
        </template>
      </page-form>

頁面中處理事件:

// 觸發事件
    handleEvent (event, data) {
      switch (event) {
        case 'changeName':
            console.log(data)
            // 觸發相關聯動邏輯
          break
      }
    }

兩種方式,各有優劣,這裏主要分享思路,你們根據本身業務選擇合適的方案。

表單驗證處理

表單驗證,戳這個,上次寫的驗證還熱乎的---->element-ui表單全局驗證的方法

最後

項目地址

組件地址

相關文章

實現elementUI表單的全局驗證

組件化頁面:封裝el-table

相關文章
相關標籤/搜索