—— 封面攝於濟州島民宿
常規的表單
如果我們用 UI 框架做管理系統時候,關於表單的程式碼我們不會陌生,大致是這樣的,比如這是一個 iView 框架下的綜合性表單:
<template>
<Form :model="formItem" :label-width="80">
<FormItem label="Input">
<Input v-model="formItem.input" placeholder="Enter something..."></Input>
</FormItem>
<FormItem label="Select">
<Select v-model="formItem.select">
<Option value="beijing">New York</Option>
<Option value="shanghai">London</Option>
<Option value="shenzhen">Sydney</Option>
</Select>
</FormItem>
<FormItem label="Radio">
<RadioGroup v-model="formItem.radio">
<Radio label="male">Male</Radio>
<Radio label="female">Female</Radio>
</RadioGroup>
</FormItem>
</Form>
</template>
<script>
export default {
data () {
return {
formItem: {
input: '',
select: '',
radio: 'male'
}
}
}
}
</script>
複製程式碼
配置化表單
而我想要的方式是這樣的:
模板
<template slot="modalContent">
<AutoForm
:fileds="projectFields"
:model="projectFormData"
:formName="projectFormData"
class="my-form"
/>
</template>
<script>
// @ is an alias to /src
import { mapState } from 'vuex'
import { projectFields } from '@/utils/fieldsMap'
export default {
data () {
return {
// 表單配置列表
projectFields: projectFields
}
},
computed: {
...mapState({
// 專案列表頁編輯表單
projectFormData: state => state.project.projectFormData
})
},
}
</script>
複製程式碼
表單項的資料來源我會利用 Vuex 的 state 裡管理:
資料
import { projectFormData } from '@/api/project'
const state = {
// 專案列表頁編輯
projectFormData: {
projectInput: '',
projectSelect: '',
projectRadio: ''
}
}
// getters
const getters = { }
// action
const actions = {
// 表單項資料獲取
// 表單項資料提交
}
// mutations
const mutations = { }
export default {
namespaced: true,
state,
getters,
actions,
mutations
}
複製程式碼
表單項的配置也是通過單檔案(fieldsMap.js)
管理,方便維護:
表單項配置
// 表單配置項
// 注意:tag 和 type 需要根據使用的 UI 框架來匹配。
const projectFields = {
projectInput: {
label: '專案Input',
tag: 'Input',
type: 'text',
placeholder: '請輸入專案Input'
},
projectSelect: {
label: '專案下拉Select',
tag: 'Select',
options: [
{
key: 'beijing',
value: 'beijing'
},
{
key: 'hangzhou',
value: 'hangzhou'
}
]
},
projectRadio: {
label: '專案Radio',
tag: 'RadioGroup',
options: [
{
label: '是'
},
{
label: '否'
}
]
},
}
複製程式碼
OK,整個一個配置表單的檔案結構,使用方式就是這樣子,總結一下大致是三部曲:
- 引入
<AutoForm />
元件。 fieldsMap.js
中配置表單項,包括 label、type、tag、options等。Vuex state
中新增資料來源。
剩下的關鍵是 <AutoForm />
元件是如何實現配置化,其實本質是動態
生成表單項(根據配置檔案)的過程,對於 iView
來說,就是動態的生成 FormItem
,來拼成一個完整的表單。這時我們就需要用到 vue
提供的 render Api
了。
首先檢視一下官方文件 render
截圖:
render
三個引數的簡單用法:
<script>
Vue.component('Line', {
render: function(h) {
h('div', {
props: {} // 傳遞資料
},'文字 or 子節點')
}
})
</script>
複製程式碼
瞭解基礎用法後,我們來看下 <AutoForm />
元件的實現:
在上程式碼之前,我們先看一下 iView
表單的結構,從外層到內層,Form 容器固定
——FormItem 數量動態
——Input 型別動態
,元件最終是返回一個 Form
;根據配置項的數量來決定 FormItem
的數量,動態建立;根據配置項的 tag
和 type
來決定表單的型別;當然有些例如 Select
的表單項會有 options
下拉選項,也需要單獨生成。
根據上面的分析,那總結關於這個 <AutoForm />
元件,大致有 FormRender
、itemsRender
、componentUse
、型別(InputRender
、RadioRender
、SelectRender
)、options
(optionsRender
) 五個點。
AutoForm.vue
<script>
export default {
name: 'Form',
functional: true,
render (h, context) {
let fileds = context.props.fileds // 表單配置 from fieldsMap.js
let model = context.props.model // 表單資料 from state
let formName = context.props.formName // 表單名稱唯一
/**
* 渲染 FormItem
*/
function itemsRender () {
let res = []
// 遍歷配置項動態生成 FormItem
Object.keys(fileds).forEach((ele, i) => {
res.push(
h('FormItem',
{
props: {
label: fileds[ele].label // FormItem label 屬性
}
},
componentUse(fileds[ele], ele) // 子節點表單型別,利用 componentUse 函式控制
)
)
})
return res
}
/**
* 表單分發選擇
* @param { Object } _item - 當前 fields 配置項
* @param { String } _model - 當前配置項名
*/
function componentUse (_item, _model) {
let typeMap = {
'Input': InputRender,
'RadioGroup': RadioRender,
'Select': SelectRender
}
let component = typeMap[_item.tag](_item, _model)
return [component]
}
// Input
function InputRender (_item, _model) {
return h('Input',
{
props: {
'v-model': `${formName}.${_model}`,
'placeholder': _item.placeholder,
'type': _item.type
},
on: {
// iView 元件提供的方法,實現資料雙向繫結
'on-blur': (e) => {
model[_model] = e.target.value
}
}
}
)
}
// Radio
function RadioRender (_item, _model) {
return h('RadioGroup',
{
props: {
'v-model': `${formName}.${_model}`
},
on: {
'on-change': (e) => {
model[_model] = e === '是' ? 1 : 0
}
}
},
_item.options ? optionsRender(_item.options, 'Radio') : []
)
}
// Select
function SelectRender (_item, _model) {
return h('Select',
{
props: {
'v-model': `${formName}.${_model}`
},
on: {
'on-change': (e) => {
model[_model] = e
}
}
},
_item.options ? optionsRender(_item.options, 'Option') : []
)
}
// 有多選 options 配置 optionsRender
// Radio
// Select
function optionsRender (_options, _tag) {
let itemRes = []
_options.forEach((_option, i) => {
if (_tag === 'Radio') {
itemRes.push(
h(_tag,
{
props: {
'label': _option.label
}
}
)
)
} else if (_tag === 'Option') {
itemRes.push(
h(_tag,
{
props: {
'key': _option.key,
'value': _option.value
}
}
)
)
}
})
return itemRes
}
let items = itemsRender(h)
return h(
'Form',
{
class: context.data.staticStyle,
style: context.data.staticStyle,
props: context.props
},
items
)
}
}
</script>
複製程式碼
好了,有了上面的鋪路,你就可以在專案的任何頁面使用配置表單了,這樣你就不用重複去 copy
結構程式碼了,使得頁面中的程式碼看著清爽;更重要的是分檔案管理的方式,有利於維護。其實分頁列表也可以參考這樣的方式。
一個含分頁列表和基礎表單的檔案可以是這樣的:
<template>
<div class="hc-project-management">
<CommonList
:addSearch="addSearch"
:columns="columns"
:data="projectList"
:pageBean="pageBean"
:statePath="statePath"
/>
<MyModal
:isShow="modal.isShow"
:title="modal.title"
>
<template slot="modalContent">
<AutoForm
:fileds="projectFields"
:model="projectFormData"
:formName="projectFormData"
class="my-form"/>
</template>
</MyModal>
</div>
</template>
複製程式碼
如何根據 Select 框的選項動態新增表單項
有時候我們會有像標題描述的需求,當一個下拉選單選中後,自動的新增或者改變表單項。
實現:
我這邊會在 watch
中監聽 state
中資料變化來新增配置項
watch: {
// 通過這種語法來watch
'projectFormData': {
handler: function (val, oldVal) { // 不能使用箭頭函式 this 指向會出問題
if (val.projectSelect) {
this.projectFields = Object.assign({}, this.projectFields, { projectTextarea: {
label: '專案textarea',
tag: 'Input',
type: 'textarea',
placeholder: '請輸入textarea'
}})
}
console.log(val)
},
// 深度觀察
deep: true
}
},
複製程式碼
說兩句
其實配置化還是常規寫法,都是需要根據自身業務和開發成員等綜合考慮的,比如在配置化時,那麼就需要和組員約定好一個新增表單的流程和寫法,這個是相對固定的,不像常規的那麼自由;又比如,本身我們這個專案表單的數量只有2、3個,那是否有配置化的必要;再比如,成員間是否認可這樣的寫法,也是需要商量的。但是一旦形成文件規範,那麼回頭來看,配置化帶來的可維護性、易錯誤定位等好處,就顯得不用付出那麼多成本。