文件驅動
想要做到文件驅動表單,首先要做幾個表單元素元件。基於原生的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是如何生成的,表單的程式碼到底是啥樣的?還有查詢和資料列表怎麼辦?等等都有解決方案。