文檔驅動 —— 表單組件(一):表單元素組件


文檔驅動

想要做到文檔驅動表單,首先要做幾個表單元素組件。基於原生的HTML5的表單元素,做了一下分類,比如文本類、數字、日期、選擇等,具體如下圖。
【圖片】

然后就是

文檔 >> json >> vue >> UI >>表單

這個流程了。
其中Vue提供了很方便的數據雙向綁定的功能,
UI提供了非常好看的視覺效果。
那么既然他們都做好了,我就不重復制造輪子,直接拿過來使用。

可能你會奇怪,UI庫提供了一些列的組件,為啥還要自己封裝組件呢?
主要原因就是使用方式和我的想法有點不太一樣,比如element的select要這么用:(代碼來自官方)

<template>
  <el-select v-model="value" placeholder="請選擇">
    <el-option
      v-for="item in options"
      :key="item.value"
      :label="item.label"
      :value="item.value"
      :disabled="item.disabled">
    </el-option>
  </el-select>
</template>
<script>
  export default {
    data() {
      return {
        options: [{
          value: '選項1',
          label: '黃金糕'
        }, {
          value: '選項2',
          label: '雙皮奶',
          disabled: true
        }, {
          value: '選項3',
          label: '蚵仔煎'
        }, {
          value: '選項4',
          label: '龍須面'
        }, {
          value: '選項5',
          label: '北京烤鴨'
        }],
        value: ''
      }
    }
  }
</script>
 

這是非常標准的用法,也非常靈活,只是有一點點麻煩。好吧,我承認是我太懶了,我不想每次用的時候都寫這么多的代碼(html+js)。我想這樣寫:

<template>
  <nfSelect v-model="modelValue" :meta="meta"/>
</template>

<script>
export default {
  name: 'Form',
  setup () {
    const modelValue = ref('') // model
    const meta = require('./FormDemo.json') // 加載meta信息,json格式
    
    return {
      modelValue,
      meta
    }
  }
}

</script>

三行代碼搞定,一行html,兩行js。
組件只需要設置兩個屬性,一個是model,一個是元數據(meta),也就是json格式的屬性信息。

實現方法

其實方法也很簡單,只需要自己做一個組件,把上面那段el的select(原生的HTML5測試通過,el的還沒測試,應該可以吧)放進去,把需要的各種屬性值(包含options的數據項)做成json通過meta屬性傳遞進去就可以了。

優點

非常簡單,可以大大減少代碼量,而且還可以用v-for來遍歷,這樣就算再大的表單,一個for就搞定了。

缺點

靈活度不夠,肯定沒有直接使用select來的靈活。

選擇

不過最終“懶惰戰勝了靈活的需求”,我還是想按照我的想法做出來一套東東玩玩。

代碼

文本類的Input

下面是文本類的input的封裝方式,基於原生html5。為啥不用element呢?因為我跳過了vue2.*,直接使用vue3.0來寫的,但是在安裝element的時候,報了一大堆錯誤。
我基本功太差沒搞不定,所以就先不用element了。
用原生的做驗證我的想法是否可以實現,以后搞定了在加上其他UI。
本來我的想法就是基於每個UI都做一套,可以跨UI,甚至跨架構。

/** 文本類的,text、密碼、url、郵件等 */
<template>
  <span>
    <input :id="'c' + meta.controlId"
    :type="type[meta.controlType]"
    :name="'c' + meta.controlId"
    :value="modelValue"
    :disabled="meta.disabled"
    :readonly="meta.readonly"
    :class="meta.class"
    :placeholder="meta.placeholder"
    :title="meta.title"
    :size="meta.size"
    :maxlength="meta.maxlength"
    :autocomplete="meta.autocomplete"
    :list="meta.optionKey"
    @input="myInput"
    :key="'ckey_'+meta.controlId">
    <!--文本框的備選項-->
    <datalist v-if="typeof(meta.optionKey)!=='undefined'" :id="meta.optionKey">
      <option :key="item.value" v-for="item in meta.optionList" :label="item.value" :value="item.title" />
    </datalist>
  </span>
</template>
<script>

export default {
  name: 'nf-form-input',
  model: {
    prop: 'modelValue',
    event: 'input'
  },
  props: {
    modelValue: String,
    meta: {
      type: Object,
      default: () => {
        return {
          // 通用
          controlId: Number, // 編號,區別同一個表單里的其他控件
          colName: String, // 字段名稱
          controlType: Number, // 用類型編號表示type
          isClear: {
            // isClear  連續添加時是否恢復默認值
            type: Boolean,
            default: false
          },
          defaultValue: String, // 默認值
          autofocus: { // 是否自動獲得焦點
            type: Boolean,
            default: false
          },
          disabled: {
            // 是否禁用
            type: Boolean,
            default: false
          },
          required: { // 必填
            type: Boolean,
            default: true
          },
          readonly: { // 只讀
            type: Boolean,
            default: false
          },
          pattern: String, // 用正則做驗證。
          class: String, // 'cssTxt input_t1'
          placeholder: String,
          title: String, // 提示信息
          size: Number, // 字符寬度
          maxlength: Number, // 最大字符數
          autocomplete: { // off
            type: String,
            default: 'on'
          },
          optionKey: String, // 備選文字標識
          optionItem: Object // 備選的選項
        }
      }
    }
  },
  data: () => {
    return {
      type: {
        101: 'text', // 單行文本框
        102: 'password', // 密碼
        103: 'tel', // 電話
        104: 'email', // 電子郵件
        105: 'url', // url
        106: 'search' // 搜索
      }
    }
  },
  methods: {
    myInput: function (e) {
      var returnValue = event.target.value
      var colName = this.meta.colName // 字段屬性名
      this.$emit('update:modelValue', returnValue) // 返回給調用者,vue3.0的新寫法
      this.$emit('getvalue', returnValue, colName) // 返回給中間組件
    }
  }
}
</script>

check 多選

再貼一個多選的組件,使用type=”check”實現,這個因為要實現多選,所以代碼有些不同。其他的代碼都是雷同的,就不一一貼了。想看代碼可以到這里看 。https://github.com/naturefwvue/nfComponents

/**多選組,返回逗號連接的value值,比如:“1,2,3” */
<template>
  <span>
    <!--多選組item.checked-->
    <label role="checkbox" v-for="item in meta.optionList"
      :class="meta.class"
      :key="'lblchks'+item.value">
        <input :id="'c'+meta.controlId"
        type="checkbox"
        :name="'c'+meta.controlId"
        :class="meta.class"
        :value="item.value"
        :checked="(','+modelValue+',').indexOf(','+item.value+',') != -1"
        :readonly="meta.readonly"
        :key="'chks'+item.value"
        @input="myCheck"
        >
        <span>{{item.title}}</span>
    </label>
  </span>
</template>
<script>

export default {
  name: 'nf-form-checks',
  model: {
    prop: 'modelValue',
    event: 'input'
  },
  props: {
    modelValue: String,
    meta: {
      type: Object,
      default: () => {
        return {
          controlId: Number, // 編號,區別同一個表單里的其他控件
          controlType: Number, // 用類型編號表示type
          colName: String, // 中文名稱
          isClear: {
            // isClear  連續添加時是否恢復默認值
            type: Boolean,
            default: false
          },
          // 通用
          disabled: {
            // 是否禁用
            type: Boolean,
            default: false
          },
          required: { // 必填
            type: Boolean,
            default: true
          },
          class: String, // 
          title: String // 提示信息
        }
      }
    }
  },
  data: function () {
    return {
      optionsChecks: {}
    }
  },
  methods: {
    myCheck: function (e) {
      var returnValue = e.target.value
      var colName = this.meta.colName  
      if (e.target.checked) {
        this.optionsChecks[returnValue] = 1
      } else {
        delete this.optionsChecks[returnValue]
      }
   
      var arr = []
      for (var key in this.optionsChecks) {
        arr.push(key)
      }
      returnValue = arr.join(',')
      this.$emit('update:modelValue', returnValue)
      this.$emit('getvalue', returnValue, colName)
    }
  }
}
</script>

分還是合?

其實這些零散的組件可以合在一個組件里面的,代碼也不會太多,但是我想來想去,還是分開的話,便於以后的擴展。

只是這么零散,用的時候還要想我到底用哪個組件,這不符合我懶惰的人設,所以我又做了一個“組合”組件,
就是把分散的各個組件,組成一個組件,這樣在使用的時候引用這一個就可以了。

/** 表單元素的綜合組件,根據類型自動加載相應的組件 */
<template>
  <span class="hello">
    <nfInput v-if="meta.controlType == 100" :modelValue="modelValue" @getvalue="sendValue" :meta="meta"/>
    <nfArea v-else-if="meta.controlType <= 119" :modelValue="modelValue" @getvalue="sendValue" :meta="meta"/>
    <nfNumber v-else-if="meta.controlType <= 139" :modelValue="modelValue" @getvalue="sendValue" :meta="meta"/>
    <nfDatetime v-else-if="meta.controlType <= 149" :modelValue="modelValue" @getvalue="sendValue" :meta="meta"/>
    <nfUpload v-else-if="meta.controlType <= 159" :modelValue="modelValue" @getvalue="sendValue" :meta="meta"/>
    <nfColor v-else-if="meta.controlType == 160" :modelValue="modelValue" @getvalue="sendValue" :meta="meta"/>
    <nfCheck v-else-if="meta.controlType == 180" :modelValue="modelValue" @getvalue="sendValue" :meta="meta"/>
    <nfChecks v-else-if="meta.controlType == 182" :modelValue="modelValue" @getvalue="sendValue" :meta="meta"/>
    <nfRadios v-else-if="meta.controlType == 183" :modelValue="modelValue" @getvalue="sendValue" :meta="meta"/>
    <nfSelect v-else-if="meta.controlType == 190" :modelValue="modelValue" @getvalue="sendValue" :meta="meta"/>
    <nfInputMore v-else-if="meta.controlType == 200" :modelValue="modelValue" @getvalue="sendValue" :meta="meta"/>
  </span>
</template>
<script>
import nfInput from '@/components/nf-form-textarea.vue' // 100-107
import nfArea from '@/components/nf-form-input.vue' // 100-107
import nfNumber from '@/components/nf-form-number.vue' // 131,132
import nfDatetime from '@/components/nf-form-datetime.vue' // 140-144
import nfUpload from '@/components/nf-form-upload.vue' // 150-151
import nfColor from '@/components/nf-form-color.vue' // 160
import nfCheck from '@/components/nf-form-check.vue' // 180
import nfChecks from '@/components/nf-form-checks.vue' // 182
import nfRadios from '@/components/nf-form-radios.vue' // 183
import nfSelect from '@/components/nf-form-select.vue' // 190
import nfInputMore from '@/components/nf-form-inputmore.vue' // 200

export default {
  name: 'nf-form-item',
  components: {
    nfInput,
    nfArea,
    nfNumber,
    nfDatetime,
    nfUpload,
    nfColor,
    nfCheck,
    nfChecks,
    nfRadios,
    nfSelect,
    nfInputMore
  },
  props: {
    modelValue: Object,
    meta: Object
  },
  methods: {
    sendValue: function (value, colName) {
      this.$emit('update:modelValue', value)
      this.$emit('getvalue', value, colName) // 返回給中間組件
    }
  }
}
</script>

value的類型問題

這里有個數據類型的問題,各個子組件可以規定 modelValue的類型,但是這個組合組件的類型怎么定呢?我寫成了 object,雖然運行的時候雖然不會報紅色的錯誤,但是總會報數據類型驗證錯誤的提示,按F12,滿眼全是,很煩。

one more thing

代碼還在不斷完善中,希望能夠找到自同道合的人。
還有很多后續,比如meta是如何生成的,表單的代碼到底是啥樣的?還有查詢和數據列表怎么辦?等等都有解決方案。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM