vue封裝form表單組件拒絕重復(fù)寫(xiě)form表單
前言
在日常工作中,當(dāng)需要處理的form表單很多時(shí),我們沒(méi)有必要一遍又一遍地重復(fù)寫(xiě)form表單,直接封裝一個(gè)組件去處理就好。其實(shí)很早之前就有涉獵通過(guò)使用類(lèi)似配置json方法寫(xiě)form表單的文章,雖然當(dāng)時(shí)也沒(méi)怎么認(rèn)真看...我們前端組也是使用這種思想配置的。
然而,這個(gè)思想和方法很早就有出現(xiàn)過(guò),并不怎么新穎,還望不喜勿噴...在此我封裝了一個(gè)最最最基礎(chǔ)的form表單,離我們前端組封裝的組件差距還很大,有興趣朋友們的可以繼續(xù)往下完善。
核心思想:
- 通過(guò)配置
js文件的變量,使用vue的is屬性動(dòng)態(tài)切換組件,默認(rèn)顯示的組件為el-input - 通過(guò)
element的分欄和柵格屬性,對(duì)form表單進(jìn)行響應(yīng)式布局 baseForm在組件初始化時(shí),需要?jiǎng)討B(tài)添加校驗(yàn)規(guī)則、請(qǐng)求接口以及初始化form的部分值- 正統(tǒng)思想是對(duì)
element組件的各個(gè)組件進(jìn)行二次封裝,然后通過(guò)is屬性切換二次封裝后的組件,在此不做過(guò)多描述,有興趣的朋友可以自行研究 - 更好的思想是將頁(yè)面請(qǐng)求、搜索項(xiàng)、表格、分頁(yè)封裝到一起,形成一個(gè)整體,這也是我們前端小組目前的處理思路
實(shí)現(xiàn)重點(diǎn):
- 任何標(biāo)簽或者組件都可以通過(guò)
vue的is屬性來(lái)動(dòng)態(tài)切換組件。本組件中使用div,將它的寬度設(shè)置為100%,使得element組件能夠完全撐開(kāi)。(使用vue內(nèi)置組件component會(huì)與el-radio-group相沖突,因?yàn)樗讓泳褪怯?code>component實(shí)現(xiàn)的) - 表單上添加
validate-on-rule-change="false"屬性,防止在表單初始化時(shí)就校驗(yàn)表單 - 當(dāng)為對(duì)象添加不存在的字段屬性時(shí),需要使用
$set實(shí)現(xiàn)數(shù)據(jù)的響應(yīng)式 - 如果
form表單中只有一個(gè)輸入框,在輸入框中按下回車(chē)會(huì)提交表單,刷新頁(yè)面。為了阻止這一默認(rèn)行為,需要在el-form標(biāo)簽上添加@submit.native.prevent - 使用
lodash中的get方法獲取對(duì)象的屬性值,如果屬性值不存在,可以給一個(gè)默認(rèn)值 baseForm子組件中可以傳一個(gè)form對(duì)象給父組件,那么添加或者編輯form對(duì)象,就都可以在父組件中進(jìn)行。
表單雙向綁定的方式有兩種:
1.使用v-model進(jìn)行雙向綁定
<div v-else clearable style="width: 100%" type="daterange" range-separator="至" start-placeholder="開(kāi)始日期" end-placeholder="結(jié)束日期" v-model="form[column.prop]" :label-width="get(column, 'size', column || defaultFormSize)" :disabled="get(column, 'disabled', false)" :is="get(column, 'type', 'el-input')" >
2.使用v-model的語(yǔ)法糖
(`:value以及@input`)進(jìn)行雙向綁定
<div
v-else
clearable
style="width: 100%"
type="daterange"
range-separator="至"
start-placeholder="開(kāi)始日期"
end-placeholder="結(jié)束日期"
:value="form[column.prop]"
:label-width="get(column, 'size', column || defaultFormSize)"
:disabled="get(column, 'disabled', false)"
:is="get(column, 'type', 'el-input')"
@input="input($event,column.prop)"
>
methods: {
input(e,prop) {
this.$set(this.form, prop, e)
}
}
配置項(xiàng)
(本組件寫(xiě)得比較基礎(chǔ),目前僅支持element的五個(gè)常用組件):
整體字段:
formSize (表單中各element組件的整體大小)
column數(shù)組中每一個(gè)對(duì)象對(duì)應(yīng)的字段(非請(qǐng)求接口):
label(表單label的名稱(chēng))span(這個(gè)表單項(xiàng)占據(jù)的份數(shù),一行為24,默認(rèn)為12)labelWidth(這個(gè)表單項(xiàng)的label寬度,默認(rèn)為90px)labelHeight(這個(gè)表單項(xiàng)占據(jù)的高度,默認(rèn)為50px)slotName(插槽名)prop(這個(gè)表單項(xiàng)綁定的屬性名稱(chēng))size(這個(gè)表單項(xiàng)組件的大小,默認(rèn)為small)disabled(是否禁用這個(gè)表單項(xiàng))type(使用的element組件,默認(rèn)為el-input)dic(非接口請(qǐng)求的靜態(tài)表單數(shù)據(jù),使用{label以及value字段}表示的數(shù)組形式)
column數(shù)組中每一個(gè)對(duì)象對(duì)應(yīng)的字段(請(qǐng)求接口):
url(接口的api地址)requestParams(非必填項(xiàng),需要額外傳入的傳參)requestLabel(接口返回對(duì)應(yīng)的id)requestValue(接口返回對(duì)應(yīng)的value)
效果瀏覽



源碼放送
1. baseForm組件
<template>
<el-form
ref="form"
:model="form"
:rules="formRules"
:size="get(option, 'formSize', defaultFormSize)"
:validate-on-rule-change="false"
@submit.native.prevent
>
<el-row :gutter="20" :span="24">
<el-col
v-for="column in formColumn"
:key="column.label"
:md="column.span || 12"
:sm="12"
:xs="24"
>
<el-form-item
:label="`${column.label}:`"
:prop="column.prop"
:label-width="get(column, 'labelWidth', column.labelWidth || defaultLabelWidth)"
:style="{
height: get(column, 'labelHeight', column.labelHeight || defaultLabelHeight)
}"
>
<slot
v-if="column.slotName"
:name="column.slotName"
:form="form"
:prop="column.prop"
:value="form[column.prop]"
></slot>
<div
v-else
clearable
style="width: 100%"
type="daterange"
range-separator="-"
start-placeholder="開(kāi)始日期"
end-placeholder="結(jié)束日期"
v-model="form[column.prop]"
:placeholder="getPlaceholder(column.type, column.label)"
:label-width="get(column, 'size', column || defaultFormSize)"
:disabled="get(column, 'disabled', false)"
:is="get(column, 'type', 'el-input')"
>
<template v-if="column.type == 'el-select'">
<el-option
v-for="item in column.dic"
:key="item.value"
:label="item.label"
:value="item.value"
>
</el-option>
</template>
<template v-if="column.type == 'el-radio-group'">
<el-radio
v-for="item in column.dic"
:key="item.value"
:label="item.label"
>
{{ item.value }}
</el-radio>
</template>
<template v-if="column.type == 'el-checkbox-group'">
<el-checkbox
v-for="item in column.dic"
:key="item.label"
:label="item.value"
>
{{ item.label }}
</el-checkbox>
</template>
</div>
</el-form-item>
</el-col>
</el-row>
</el-form>
</template>
<script>
import get from 'lodash/get'
import request from '@/service/request'
export default {
props: {
option: {
type: Object,
default: () => {}
},
form: {
type: Object,
default: () => {}
}
},
data() {
return {
formRules: {},
defaultFormSize: 'small',
defaultLabelWidth: '90px',
defaultLabelHeight: '50px'
}
},
computed: {
formColumn() {
return this.option.column
}
},
created() {
this.initRules()
this.initRequest()
this.initValue()
},
methods: {
get,
getPlaceholder(type, label) {
return type == 'el-select' ? `請(qǐng)選擇${label}` : `請(qǐng)輸入${label}`
},
initRequest() {
if (!Array.isArray(this.formColumn)) return
// 根據(jù)實(shí)際請(qǐng)求接口地址的前綴來(lái)判斷
const urls = this.formColumn?.filter((item) => item.url && item.url.indexOf('/emes') == 0) || []
urls.forEach(async (item) => {
const data = { page: { pageIndex: 1, pageSize: 0 }, ...item.requestParams }
const { detail } = await request({
url: item.url,
method: 'post',
data
}) || []
const finalResult = detail.map((result) => ({
label: result[item.requestLabel],
value: result[item.requestValue]
}))
this.$set(item, 'dic', finalResult)
})
},
initRules() {
if (!Array.isArray(this.formColumn)) return
this.formColumn?.forEach((item) => {
if (item.rules) {
item.rules.map((rule, index) => {
if (rule.required) {
item.rules.splice(index, 1, {
message: ['el-radio-group', 'el-checkbox-group'].includes(item.type) ? `${item.label}必選` : `${item.label}必填`,
...rule
})
}
})
this.$set(this.formRules, item.prop, item.rules)
}
})
},
initValue() {
const selectList = this.formColumn.filter((item) => ['el-radio-group', 'el-checkbox-group'].includes(item.type))
selectList.forEach((item) => {
this.$set(this.form, item.prop, item.type == 'el-radio-group' ? item.dic[0].label : [item.dic[0].value])
})
}
}
}
</script>
2. 父組件
<template>
<div class="app-container">
<myForm :option="option" :form="form">
<template #usageSlot="{form, prop}">
<el-input
size="small"
placeholder="請(qǐng)輸入插槽使用"
v-model="form[prop]"
clearable
>
</el-input>
</template>
</myForm>
</div>
</template>
<script>
import { option } from './const.js'
export default {
data() {
return {
option,
form: {}
}
}
}
</script>
3. 配置項(xiàng)
export const option = {
column: [
{
label: '姓名',
prop: 'name',
span: 8,
rules: [
{
required: true
}
]
},
{
label: '職業(yè)',
prop: 'job',
type: 'el-select',
span: 8,
dic: [
{
label: '教師',
value: 0
},
{
label: '程序猿',
value: 1
},
{
label: '作家',
value: 2
}
],
rules: [
{
required: true
}
]
},
{
label: '性別',
prop: 'sex',
span: 8,
type: 'el-radio-group',
dic: [
{
label: 0,
value: '男'
},
{
label: 1,
value: '女'
}
],
rules: [
{
required: true
}
]
},
{
label: '城市',
prop: 'city',
type: 'el-checkbox-group',
span: 8,
dic: [
{
label: '仙桃',
value: 0
},
{
label: '泉州',
value: 1
},
{
label: '武漢',
value: 2
}
],
rules: [
{
required: true
}
]
},
{
label: '出生日期',
prop: 'data',
type: 'el-date-picker',
span: 8,
rules: [
{
required: true
}
]
},
{
label: '測(cè)試',
prop: 'test',
type: 'el-select',
span: 8,
url:'/emes/factoryOrderService/warehouse/list',
requestLabel: 'warehouseName',
requestValue: 'id',
rules: [
{
required: true
}
]
},
{
label: '插槽使用',
prop: 'usage',
slotName: 'usageSlot',
span: 8,
rules: [
{
required: true
}
]
}
]
}
4. 添加或編輯
- 添加: 如果是添加狀態(tài),直接在父組件中引入就好。在點(diǎn)擊確定按鈕時(shí),拿到子組件的
ref并進(jìn)行表單校驗(yàn)。校驗(yàn)通過(guò)后,使用后端定義好的接口進(jìn)行傳參; - 編輯: 如果是編輯狀態(tài),則需要在父組件頁(yè)面初始化時(shí),將后端返回的數(shù)據(jù)使用
$set進(jìn)行初始賦值,其余操作同添加狀態(tài)。
以上就是vue封裝form表單組件拒絕重復(fù)寫(xiě)form表單的詳細(xì)內(nèi)容,更多關(guān)于vue封裝form表單組件的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Vue利用History記錄上一頁(yè)面的數(shù)據(jù)方法實(shí)例
這篇文章主要給大家介紹了關(guān)于Vue利用History記錄上一頁(yè)面的數(shù)據(jù)的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2018-11-11
vue3結(jié)合typescript中使用class封裝axios
這篇文章主要為大家介紹了vue3結(jié)合typescript中使用class封裝axios實(shí)現(xiàn)示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-06-06
vue項(xiàng)目登錄頁(yè)面實(shí)現(xiàn)記住用戶(hù)名和密碼的示例代碼
本文主要介紹了vue項(xiàng)目登錄頁(yè)面實(shí)現(xiàn)記住用戶(hù)名和密碼的示例代碼,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2023-07-07
Vue響應(yīng)式原理及雙向數(shù)據(jù)綁定示例分析
這篇文章主要為大家介紹了Vue響應(yīng)式原理及雙向數(shù)據(jù)綁定的示例分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-07-07
vue 里面的 $forceUpdate() 強(qiáng)制實(shí)例重新渲染操作
這篇文章主要介紹了vue 里面的 $forceUpdate() 強(qiáng)制實(shí)例重新渲染操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-09-09

