vue2實(shí)現(xiàn)封裝動態(tài)表單組件
封裝組件注意點(diǎn)
- 不要為了封裝而封裝
- 只封裝不變的部分,將變化的部分通過slot或其他方式,暴露出去,交給調(diào)用者實(shí)現(xiàn)
- 為了提供封裝組件的復(fù)用率,優(yōu)先封裝為UI組件,而不是封裝為業(yè)務(wù)組件(即:封裝組件內(nèi)部使用到的數(shù)據(jù),都通過prop獲取,而不是直接通過ajax請求或vuex中獲?。?/li>
動態(tài)表單動態(tài)控制的是什么?
已知的需要動態(tài)控制的場景:
- 在A界面顯示:A, B, C三個表單項(xiàng),B界面顯示:A,B,E,F三個表單項(xiàng)
- 在A界面默認(rèn)顯示:A,B兩個表單項(xiàng),當(dāng)A項(xiàng)的值為x時,C表單項(xiàng)才顯示出來
- 在A界面默認(rèn)顯示:A,B,C三個表單項(xiàng),A,B默認(rèn)可用,C默認(rèn)禁用,當(dāng)A項(xiàng)的值為x時,C表單項(xiàng)才可用
- 在A界面B表單項(xiàng)可選擇的值,依賴于A表單項(xiàng)的選擇/填寫結(jié)果
- 根據(jù)不同的分辨率每行可顯示的表單項(xiàng)數(shù)量不同
動態(tài)表單封裝
請優(yōu)先考慮風(fēng)格二的封裝方式
風(fēng)格一
特點(diǎn)簡介
將el-form, el-form-item, el-input等等完全封裝到動態(tài)表單組件內(nèi),通過for循環(huán)來刷表單項(xiàng),外部通過傳json配置對象的方式,完成字段的動態(tài)顯示/隱藏控制。
優(yōu)點(diǎn):
- 減少了
<el-form-item><el-input></el-input></el-form-item>這類代碼的重復(fù)編寫 - 可以輕易的改變表單項(xiàng)的先后順序
缺點(diǎn):
- 如果所有用到表單的地方,都用這一個表單組件實(shí)現(xiàn),那么這個組件后期一定會變得非常龐大,充斥大量的邏輯控制代碼,導(dǎo)致后期難以維護(hù)
- 要想最大程度的保留原始表單的配置和事件,那就需要使用
v-bind=attrs和v-on=evts方式進(jìn)行配置,這相對于template語法來說,json對象的配置方式,就沒那么讓vue開發(fā)者習(xí)慣了。 - 通過
v-bind=attrs方式設(shè)置原組件的屬性,那么如果想設(shè)置一些默認(rèn)值就變得麻煩起來,因?yàn)関ue2中,設(shè)置了v-bind=attrs之后,沒法再在模板中設(shè)置默認(rèn)屬性,必須在這個封裝的組件中,在獲取到配置對象時,進(jìn)行一些比較繁瑣的初始值設(shè)置。 - 如果表單model對象是通過prop傳入,那么表單內(nèi)部修改這個表單對象需要做特殊處理,來規(guī)避eslint對單項(xiàng)數(shù)據(jù)流的檢查報錯
代碼實(shí)現(xiàn)示例
動態(tài)表單組件
<template>
<el-form :model="formModel" v-bind="elFormAttrs">
<el-form-item
v-for="(formItemConfig, index) in formItemConfigArr"
:key="`${formItemConfig.prop}-${index}`"
:prop="formItemConfig.prop"
:rules="formItemConfig.rules"
>
<template #label>
<div class="o-custom-label">{{ formItemConfig.label }}</div>
</template>
<el-input
v-if="formItemConfig.itemType === 'input'"
v-model="formModel[formItemConfig.prop]"
></el-input>
<el-select
v-else-if="formItemConfig.itemType === 'select'"
v-model="formModel[formItemConfig.prop]"
>
<template v-if="formItemConfig.optionArr">
<el-option
v-for="(option, pos) in formItemConfig.optionArr"
:key="`${option.value}-${pos}`"
:value="option.value"
:label="option.label"
></el-option>
</template>
</el-select>
</el-form-item>
</el-form>
</template>
<script>
export default {
name: "DynForm",
model: {
event: "change",
prop: "formData",
},
props: {
formData: {
type: Object,
},
/**
* prop: 唯一標(biāo)識
* itemType: 表單項(xiàng)類型
* rules: 表單驗(yàn)證規(guī)則
* label: 顯示標(biāo)簽
* optionArr: 下拉值
*/
formItemConfigArr: {
type: Array,
default: () => [],
},
// el-form支持的所有屬性
elFormAttrs: {
type: Object,
},
},
data() {
return {
formModel: this.formData ? this.formData : {},
};
},
watch: {
// 監(jiān)聽formData改變,將formData的值設(shè)置給組件內(nèi)部的formModel,規(guī)避eslint對單項(xiàng)數(shù)據(jù)流的規(guī)則檢測報錯
formData: {
handler(newVal) {
this.formModel = newVal;
},
},
},
methods: {},
};
</script>
<style scoped></style>調(diào)用動態(tài)表單組件
<template>
<div class="demo-form">
<DynForm
v-model="formData"
:form-item-config-arr="formItemConfigArr"
:el-form-attrs="elFormAttrs"
></DynForm>
</div>
</template>
<script>
import DynForm from "@/components/form/dyn/DynForm";
export default {
name: "FormTemplate3",
components: {
DynForm,
},
props: {
// input文本框數(shù)量
count: {
type: Number,
default: 50,
},
},
data() {
// 表單項(xiàng)配置
const formItemConfigArr = [
{
prop: "sex",
label: "成語故",
itemType: "select",
optionArr: [],
},
{
prop: "name",
label: "姓名",
itemType: "input",
},
{
prop: "three",
label: "成語故事",
itemType: "select",
optionArr: [],
},
{
prop: "four",
label: "一二三四五",
itemType: "select",
optionArr: [],
},
{
prop: "five",
label: "一二三四五六",
itemType: "select",
optionArr: [],
},
{
prop: "six",
label: "一二三四五六七",
itemType: "select",
optionArr: [],
},
{
prop: "seven",
label: "一二三四五六七八",
itemType: "select",
optionArr: [],
},
{
prop: "eight",
label: "一二三四五六七八九",
itemType: "select",
optionArr: [],
},
{
prop: "ten",
label: "ten",
itemType: "select",
optionArr: [],
},
{
prop: "zero",
label: "zero",
itemType: "select",
optionArr: [],
},
{
prop: "a",
label: "hello",
itemType: "select",
optionArr: [],
},
{
prop: "b",
label: "hello world",
itemType: "select",
optionArr: [],
},
{
prop: "c",
label: "good idea thank",
itemType: "select",
optionArr: [],
},
{
prop: "d",
label: "good configuration",
itemType: "select",
optionArr: [],
},
{
prop: "d",
label: "good idea thank configuration",
itemType: "select",
optionArr: [],
},
];
return {
elFormAttrs: {
inline: true,
labelWidth: "81px",
},
formData: null,
formItemConfigArr,
};
},
created() {
this.loadFormData();
this.loadSexOptions();
},
methods: {
loadFormData() {
setTimeout(() => {
this.formData = {
name: "張三",
sex: 1,
};
}, 1000);
},
loadSexOptions() {
setTimeout(() => {
const item = this.formItemConfigArr.find((item) => item.prop === "sex");
if (item) {
const sexOptionArr = [
{ value: 1, label: "選項(xiàng)1" },
{ value: 2, label: "選項(xiàng)2" },
{ value: 3, label: "選項(xiàng)3" },
];
item.optionArr = sexOptionArr;
}
}, 1500);
},
},
};
</script>
<style scoped lang="scss">
.js-validate-form ::v-deep(.is-error .o-show-data) {
color: red;
}
.demo-form ::v-deep(.el-form-item__label) {
line-height: 1.6;
display: inline-flex;
height: 40px;
justify-items: right;
justify-content: flex-end;
align-items: center;
}
.demo-form ::v-deep(.el-form-item__label .o-custom-label) {
word-break: break-word;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2; /* 這里是超出幾行省略 */
overflow: hidden;
}
</style>風(fēng)格二 (推薦優(yōu)先采用此風(fēng)格)
特點(diǎn)介紹
通過函數(shù)組件,以jsx語法對el-form-item進(jìn)行封裝,僅封裝模板代碼部分,動態(tài)部分全部由調(diào)用者自行實(shí)現(xiàn)
優(yōu)點(diǎn):
- 能夠針對界面特點(diǎn),僅封裝不變的模板代碼部分,能夠最大程度保留template編程風(fēng)格
- 因?yàn)楸韱雾?xiàng)都是通過slot實(shí)現(xiàn),不會導(dǎo)致這個表單組件隨著應(yīng)用的場景增多和變得越來越復(fù)雜,復(fù)雜度會基本保持不變
缺點(diǎn):
- 需要掌握jsx語法(jsx僅用于封裝組件,調(diào)用這個組件使用template語法即可)
代碼實(shí)現(xiàn)示例
表單項(xiàng)組件封裝
<script>
export default {
functional: true,
name: "DynFormItemsFunction",
props: {
formItemConfigArr: {
type: Array,
required: true,
},
},
render(h, context) {
// console.log(context);
const { props, scopedSlots } = context;
const { formItemConfigArr } = props;
// 獲得插槽里的 vNodes
const vNodes = scopedSlots.default();
return vNodes.map((node, idx) => {
const formItemConfig = formItemConfigArr[idx];
return (
<el-form-item prop={formItemConfig.prop} rules={formItemConfig.rules}>
<span slot="label" className="o-custom-label">
{formItemConfig.label}
</span>
{node}
</el-form-item>
);
});
},
};
</script>
<style scoped>
.o-custom-label {
color: blue;
}
</style>代碼調(diào)用示例
<template>
<div>
<el-form :model="formData" inline :validate-on-rule-change="false">
<!-- DynFormItemsFunction 這個組件,僅封裝了el-form-item的邏輯 -->
<DynFormItemsFunction :form-item-config-arr="formItemConfigArrComp">
<!--
注意組件的循環(huán)是在slot中進(jìn)行的,el-form-item的包裝邏輯,
交給了DynFormItemsFunction組件實(shí)現(xiàn)
-->
<template v-for="(formItemConfig, idx) in formItemConfigArrComp">
<el-input
v-if="formItemConfig.itemType === 'input'"
v-model="formData[formItemConfig.prop]"
:key="idx"
></el-input>
<el-select
v-else-if="formItemConfig.itemType === 'select'"
v-model="formData.sex"
:key="idx"
>
<template v-if="formItemConfig.optionArr">
<el-option
v-for="option in formItemConfig.optionArr"
:key="option.value"
:value="option.value"
:label="option.label"
></el-option>
</template>
</el-select>
</template>
</DynFormItemsFunction>
</el-form>
</div>
</template>
<script>
import DynFormItemsFunction from "@/components/form/dyn/DynFormItemsFunction";
export default {
name: "FormTemplate",
components: {
DynFormItemsFunction,
},
props: {
count: {
type: Number,
default: 50,
},
},
data() {
/*
完整的表單配置放在data中,數(shù)據(jù)中的元素個數(shù)不會變,
動態(tài)值也填充到formItemConfigArr對應(yīng)元素中(如性別的下拉選項(xiàng)值),
formItemConfigArr只需要關(guān)注完整表單需要哪些表單項(xiàng)字段,
以及下拉選項(xiàng)數(shù)據(jù)的填充,無需考慮表單項(xiàng)字段的顯示/隱藏邏輯控制
*/
const formItemConfigArr = [
{
prop: "sex",
label: "性別",
itemType: "select",
optionArr: [],
},
{
prop: "name",
label: "姓名",
itemType: "input",
rules: [{ required: true, message: "該項(xiàng)必填", trigger: "change" }],
},
{
prop: "school",
label: "學(xué)校",
itemType: "input",
rules: [],
},
];
// for (let i = 0; i < this.count; i++) {
// formItemConfigArr.push({
// prop: "name" + i,
// label: "姓名" + i,
// itemType: "input",
// });
// }
return {
formData: {
name: null,
sex: null,
school: null,
},
formItemConfigArr,
};
},
computed: {
/*
表單項(xiàng)的顯示/隱藏通過計算屬性實(shí)現(xiàn),可以認(rèn)為計算屬性就只關(guān)注控制哪些表單項(xiàng)需要顯示,
哪些需要隱藏,就可以了。算是一種職責(zé)分離
*/
formItemConfigArrComp() {
return this.formItemConfigArr.filter((item) => {
if (this.formData.sex === 3 && item.prop === "name") {
return null;
}
return item;
});
},
},
created() {
this.loadOptions(1500);
},
methods: {
loadOptions(timeout) {
setTimeout(() => {
const item = this.formItemConfigArr.find((item) => item.prop === "sex");
if (item) {
const sexOptionArr = new Array(10)
.fill(true)
.map((item, idx) => ({ value: idx, label: "選項(xiàng):" + idx }));
item.optionArr = sexOptionArr;
}
}, timeout);
},
},
};
</script>
<style scoped lang="scss">
.js-validate-form ::v-deep(.is-error .o-show-data) {
color: red;
}
</style>到此這篇關(guān)于vue2實(shí)現(xiàn)封裝動態(tài)表單組件的文章就介紹到這了,更多相關(guān)vue封裝表單組件內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Vue中computed屬性和watch,methods的區(qū)別
本文主要介紹了Vue中computed屬性和watch,methods的區(qū)別,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-05-05
JavaScript的MVVM庫Vue.js入門學(xué)習(xí)筆記
這篇文章主要介紹了JavaScript的MVVM庫Vue.js入門學(xué)習(xí)筆記,Vue.js是一個新興的js庫,主要用于實(shí)現(xiàn)響應(yīng)的數(shù)據(jù)綁定和組合的視圖組件,需要的朋友可以參考下2016-05-05
uniapp微信小程序axios庫的封裝及使用詳細(xì)教程
這篇文章主要給大家介紹了關(guān)于uniapp微信小程序axios庫的封裝及使用的相關(guān)資料,Axios是一個基于promise網(wǎng)絡(luò)請求庫,作用于node.js和瀏覽器中axios-miniprogram-adapteraxios的小程序適配器,需要的朋友可以參考下2023-08-08
如何在vue3+ts項(xiàng)目中使用query和params傳參
Vue3中的路由傳參有兩種方式:query和params,下面這篇文章主要給大家介紹了關(guān)于如何在vue3+ts項(xiàng)目中使用query和params傳參的相關(guān)資料,文中通過實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2023-04-04
vue打開新窗口并實(shí)現(xiàn)傳參的圖文實(shí)例
這篇文章主要給大家介紹了關(guān)于vue打開新窗口并實(shí)現(xiàn)傳參的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-03-03

