1. 首先谋作,我們需要在components新建一個(gè)文件夾CustomForm洲鸠,然后新建一個(gè)index.vue的文件
// element-plus form表單的二次封裝 自定義form表單
<template>
<el-form :model="model" v-bind="_options" ref="formRef">
<template v-for="(item, index) in fieldList" :key="index">
<!-- 單選框 -->
<el-form-item :label="item.label" v-if="item.type === 'radio'" :rules="item.rules" :prop="[item.field]">
<el-radio-group v-model="model[item.field]" :disabled="item.disabled">
<el-radio :label="val[item.options?.valueKey || 'value']" size="large" v-for="val in item.options?.data"
:key="val[item.options?.valueKey || 'value']">
{{ val[item.options?.labelkey || 'label'] }}
</el-radio>
</el-radio-group>
</el-form-item>
<!-- 復(fù)選框 -->
<el-form-item :label="item.label" v-else-if="item.type === 'checkbox'" :rules="item.rules" :prop="[item.field]">
<el-checkbox-group v-model="model[item.field]" :disabled="item.disabled">
<el-checkbox v-for="c in item.options?.data" :key="c[item.options?.valueKey || 'value']"
:label="c[item.options?.valueKey || 'value']">{{ c[item.options?.labelkey || 'label'] }}</el-checkbox>
</el-checkbox-group>
</el-form-item>
<!-- 下拉框 -->
<el-form-item :label="item.label" v-else-if="item.type === 'select'" :rules="item.rules" :prop="[item.field]">
<el-select v-model="model[item.field]" :placeholder="item.options?.placeholder || '請(qǐng)選擇'" clearable>
<el-option v-for="s in item.options?.data" :key="s[item.options?.valueKey || 'value']"
:label="s[item.options?.labelkey || 'label']" :value="s[item.options?.valueKey || 'value']" />
</el-select>
</el-form-item>
<!-- 默認(rèn)輸入框 -->
<el-form-item :label="item.label" :rules="item.rules" :prop="[item.field]" v-else>
<el-input v-model="model[item.field]" :readonly="item.readonly" :type="item.type ?? 'text'"
:placeholder="item.label" :disabled="item.disabled" />
</el-form-item>
</template>
<el-form-item>
<slot name="buttons" :model="model" :formRef="formRef">
<el-button type="primary" @click="onSubmit(formRef)">{{ _options.submitButtonText }}</el-button>
<el-button v-if="_options.showResetButton" type="info" @click="resetForm(formRef)">
{{ _options.resetButtonText }}
</el-button>
<el-button v-if="_options.showCancelButton" @click="emit('cancel')">
{{ _options.cancelButtonText }}
</el-button>
</slot>
</el-form-item>
</el-form>
</template>
<script lang="ts" setup>
import type { FormInstance } from "element-plus";
import { ComputedRef, ref, computed } from "vue";
// 父組件傳遞的值
interface Props {
fieldList: Form.FieldItem[];
model?: Record<string, any>;
options?: Form.Options;
}
// 表單的數(shù)據(jù)
const model = ref<Record<string, any>>({});
const formRef = ref<FormInstance>();
const props = defineProps<Props>();
// 設(shè)置option默認(rèn)值疯兼,如果傳入自定義的配置則合并option配置項(xiàng)
const _options: ComputedRef<Form.Options> = computed(() => {
const option = {
labelWidth: 120,
labelPosition: "right",
disabled: false,
submitButtonText: "提交",
resetButtonText: "重置",
cancelButtonText: "取消",
showResetButton: false,
showCancelButton: false,
};
return Object.assign(option, props?.options);
});
interface EmitEvent {
(e: "submit", params: any): void;
(e: "reset"): void;
(e: "cancel"): void;
}
const emit = defineEmits<EmitEvent>();
defineExpose({
formRef,
});
// 根據(jù)fieldList初始化model乔遮, 如果model有傳值就用傳遞的model數(shù)據(jù)模型扮超,否則就給上面聲明的model設(shè)置相應(yīng)的(key,value) [item.field], item.value是表單的默認(rèn)值(選填)
props.fieldList.map((item: Form.FieldItem) => {
// 如果類型為checkbox蹋肮,默認(rèn)值需要設(shè)置一個(gè)空數(shù)組
const value = item.type === "checkbox" ? [] : "";
props.model
? (model.value = props.model)
: (model.value[item.field] = item.value || value);
});
// 提交按鈕
const onSubmit = (formEl: FormInstance | undefined) => {
if (!formEl) return;
formEl.validate((valid) => {
if (valid) {
emit("submit", model);
} else {
return false;
}
});
};
// 重置按鈕
const resetForm = (formEl: FormInstance | undefined) => {
if (!formEl) return;
formEl.resetFields();
};
</script>
<style lang="less" scoped></style>
寫完Form組件的代碼后出刷,會(huì)報(bào)紅線,F(xiàn)orm.XXXXX 找不到坯辩,這個(gè)是Form表單的全局類型聲明馁龟。
聲明文件在下方,直接復(fù)制進(jìn)項(xiàng)目中漆魔, 紅色警告自然消失坷檩。
聲明文件可以直接放在src下即可。(因?yàn)楹罄m(xù)我們項(xiàng)目可能需要二次封裝多個(gè)組件改抡,例如table, pagination, date-picker等矢炼,所以在此我們新建一個(gè)type文件夾,里面再創(chuàng)建各個(gè)組件的聲明文件)
// src/type/form/index.d.ts
declare namespace Form {
type ItemType = 'password' | 'text' | 'textarea' | 'radio' | 'checkbox' | 'select'
// 當(dāng)FiledItem的type === 'radio' | 'checkbox'時(shí)阿纤,options的參數(shù)類型
interface IFieldOptions {
labelkey?: string,
valueKey?: string,
placeholder?: string,
data: Recode<string, any>[]
}
interface Options {
labelWidth?: string | number,
labelPosition?: 'left' | 'right' | 'top',
disabled?: boolean,
size?: 'large' | 'small' | 'default',
showResetButton?: boolean, // 是否展示重置按鈕
showCancelButton?: boolean, // 是否展示取消按鈕
submitButtonText?: string,
resetButtonText?: string,
cancelButtonText?: string
}
interface FieldItem {
label: string,
field: string,
type?: ItemType,
value?: any,
placeholder?: string,
disabled?: boolean,
readonly?: boolean,
options?: IFieldOptions,
rules?: RuleItem[]
}
interface RuleItem {
type?: RuleType;
required?: boolean;
pattern?: RegExp | string;
min?: number;
max?: number;
len?: number;
enum?: Array<string | number | boolean | null | undefined>;
whitespace?: boolean;
fields?: Record<string, Rule>;
options?: ValidateOption;
defaultField?: Rule;
transform?: (value: Value) => Value;
message?: string | ((a?: string) => string);
asyncValidator?: (rule: InternalRuleItem, value: Value, callback: (error?: string | Error) => void, source: Values, options: ValidateOption) => void | Promise<void>;
validator?: (rule: InternalRuleItem, value: Value, callback: (error?: string | Error) => void, source: Values, options: ValidateOption) => SyncValidateResult | void;
trigger?: 'blur' | 'change'
}
}
2. 然后我們需要配置form的基本信息(基本表單信息句灌,驗(yàn)證規(guī)則,Options等)
// 自定義驗(yàn)證郵箱方法
const checkEmail = (rule: any, value: any, callback: any) => {
if (!value) callback(new Error('Please input the email'))
const regExp = /^[a-zA-Z0-9_.-]+@[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)*\.(com|cn|net)$/
regExp.test(value) ? callback() : callback(new Error('Please input the correct email address'))
}
// // 自定義驗(yàn)證表單配置數(shù)據(jù)
// export const validationFormFieldList = [
// { label: '姓名', field: 'name', rules: [{ required: true, message: 'name is required' }] },
// { label: '郵箱', field: 'email', rules: [{ required: true, validator: checkEmail }] },
// ] as Form.FieldItem[]
// 表單配置示例
export const exampleForm = {
base: [
{ label: '姓名', field: 'name', disabled: false },
{ label: '性別', field: 'gender', type: 'radio', options: { data: [{ label: '男', value: 1 }, { label: '女', value: 0 }] } },
{
label: '愛(ài)好',
field: 'hobbies', type: 'checkbox',
options: {
data: [
{ label: '吃飯', value: 1 },
{ label: '睡覺(jué)', value: 2 },
{ label: '寫代碼', value: 3 }
]
}
},
{
label: '工作', field: 'job', type: 'select',
options: {
data: [{ label: '吃飯', value: 1 }, { label: '睡覺(jué)', value: 2 }, { label: '寫代碼', value: 3 }]
}
},
{ label: '密碼', field: 'password', type: 'password', placeholder: '這是一個(gè)密碼輸入框' },
{ label: '只讀', field: 'readonly', readonly: true, placeholder: '這是一個(gè)只讀輸入框' },
{ label: '留言板', field: 'summary', type: 'textarea', placeholder: '留言板' },
],
customkeyForm: [
{ label: '標(biāo)題', field: 'name' },
{ label: '性別', field: 'gender', type: 'radio', options: { labelkey: 'title', valueKey: 'val', data: [{ title: '男', val: 1 }, { title: '女', val: 0 }] } },
],
ruleForm: [
{ label: '姓名', field: 'name', rules: [{ required: true, message: 'name is required' }] },
{ label: '郵箱', field: 'email', rules: [{ required: true, validator: checkEmail }] },
]
} as Record<string, Form.FieldItem[]>
export const Options = {
// 自定義form1表單
form1: {
showResetButton: true,
showCancelButton: false,
resetButtonText: "重置1212",
}
}
3. 接下來(lái)欠拾,我們就到了使用環(huán)節(jié)
// src/views/form/index.vue
<template>
<el-card class="mb-5">
<template #header> 基本表單 </template>
<custom-form :fieldList="fieldList" :model="model" @submit="handleBaseSubmit">
<!-- 如果不使用默認(rèn)的按鈕可以使用插槽自定義內(nèi)容胰锌, 插槽返回的model就是當(dāng)前表單的數(shù)據(jù) -->
<!-- <template #buttons="{ model }">
<el-button">提交</el-button>
</template> -->
</custom-form>
</el-card>
</template>
<script lang="ts" setup>
import { exampleForm } from '@/config/form'
import { ref } from 'vue'
// 本項(xiàng)目EasyForm組件自動(dòng)引入骗绕,如復(fù)制此代碼,需根據(jù)路徑引入Form組件后使用
const fieldList: Form.FieldItem[] = exampleForm.base
const model = ref<Record<string, any>>({
name: '張三',
gender: 1,
hobbies: [1],
job: 3,
readonly: '只讀輸入框',
summary: '尤雨溪懂個(gè)錘子vue是什么梗'
})
/**
* 注意: model數(shù)據(jù)模型非必填項(xiàng)匕荸,如果僅僅是用于數(shù)據(jù)收集爹谭,model參數(shù)可以不用填枷邪,表單的submit事件會(huì)返回所有搜集的數(shù)據(jù)對(duì)象
* 如果是編輯的情況下榛搔,頁(yè)面需要回顯數(shù)據(jù),則model數(shù)據(jù)模型必須要填寫
*/
const handleBaseSubmit = (model: Record<string, any>) => {
console.log(model.value)
}
</script>
<style lang="less" scoped></style>
此時(shí)運(yùn)行項(xiàng)目东揣,我們可以得到的界面
基礎(chǔ)表單image.png
自定義key
// src/views/form/index.vue
<template>
<el-card class="mb-5">
<template #header> 自定義key </template>
<custom-form :fieldList="customKeyFieldList" :model="model2" />
</el-card>
</template>
<script lang="ts" setup>
import { exampleForm } from '@/config/form'
import { ref } from 'vue'
// import EasyForm from '@/components/EasyForm/index.vue'
// 本項(xiàng)目EasyForm組件自動(dòng)引入践惑,如復(fù)制此代碼,需根據(jù)路徑引入Form組件后使用
const customKeyFieldList: Form.FieldItem[] = exampleForm.customkeyForm
const model2 = ref<Record<string, any>>({
name: '自定義key',
gender: 1
})
/**
* 注意: 如果使用到checkbox嘶卧,radio尔觉,或者select等組件,需要傳入組件額外需要的數(shù)據(jù)芥吟,本組件默認(rèn)設(shè)定的讀取數(shù)據(jù)的字段是 label侦铜, value
* 可參考下方聲明文件 FiledItem options的參數(shù)類型描述
* 比如,當(dāng)前傳入的data數(shù)據(jù)字段名和label钟鸵、value不匹配钉稍,可使用預(yù)留的參數(shù) labelkey, valueKey指定字段名
* customkeyForm: [
{ label: '標(biāo)題', field: 'name' },
{ label: '性別', field: 'gender', type: 'radio', options: { labelkey: 'title', valueKey: 'val', data: [{ title: '男', val: 1 }, { title: '女', val: 0 }] } },
],
*/
const handleBaseSubmit = (model: Record<string, any>) => {
console.log(model.value)
}
</script>
<style lang="less" scoped></style>
界面效果如下image.png
自定義表單驗(yàn)證
// src/views/form/index.vue
<template>
<el-card class="mb-5">
<template #header> 自定義驗(yàn)證的表單 (使用slot自定義按鈕) </template>
<custom-form :fieldList="ruleFieldList">
<!-- 如果不使用默認(rèn)的按鈕可以使用插槽自定義內(nèi)容, 插槽返回的model就是當(dāng)前表單的數(shù)據(jù)棺耍, formRef是當(dāng)前表單的FormInstance -->
<template #buttons="{ model, formRef }">
<el-button @click="handleSubmit(model, formRef)">保存</el-button>
</template>
</custom-form>
</el-card>
</template>
<script lang="ts" setup>
import type { FormInstance } from 'element-plus'
import { exampleForm } from '@/config/form'
// import EasyForm from '@/components/EasyForm/index.vue'
// 本項(xiàng)目EasyForm組件自動(dòng)引入贡未,如復(fù)制此代碼,需根據(jù)路徑引入Form組件后使用
const ruleFieldList: Form.FieldItem[] = exampleForm.ruleForm
/**
* 如果用到了表單驗(yàn)證蒙袍,又使用slot自定義按鈕的話俊卤,需要自行實(shí)現(xiàn)驗(yàn)證邏輯
* 組件內(nèi)部已經(jīng)集成驗(yàn)證,及重置邏輯害幅。表單驗(yàn)證建議使用內(nèi)置的提交按鈕消恍。當(dāng)通過(guò)驗(yàn)證規(guī)則,內(nèi)置提交按鈕才會(huì)出發(fā)submit事件
*/
// 下方是使用slot自定義按鈕以现,需要自己實(shí)現(xiàn)驗(yàn)證邏輯
const handleSubmit = (model: any, formEl: FormInstance | undefined) => {
if (!formEl) return
formEl.validate((valid) => {
if (valid) {
console.log('submit!', model)
} else {
console.log('error submit!')
return false
}
})
}
</script>
<style lang="less" scoped></style>
頁(yè)面效果如下image.png
4. 如果我們需要根據(jù)不同表單狠怨,展示不一樣的效果,我們可以通過(guò)options去設(shè)置
比如叼风,由于我們?cè)赾ustomForm中取董,默認(rèn)是不展示重置和取消按鈕的
// 改變這兩個(gè)值的屬性,可顯示隱藏按鈕
showResetButton: false,
showCancelButton: false,
從下面參數(shù)介紹无宿,我們可以看到options是一個(gè)對(duì)象茵汰,所以我們可以這樣寫
<template>
<div class="home">
<el-card class="mb-5">
<template #header> 基本表單 </template>
<custom-form :fieldList="ruleFieldList" :options="options" @submit="handleSubmit">
<!-- 如果不使用默認(rèn)的按鈕可以使用插槽自定義內(nèi)容, 插槽返回的model就是當(dāng)前表單的數(shù)據(jù) -->
<!-- <template #buttons="{ model, formRef }">
<el-button @click="handleSubmit(model, formRef)">保存</el-button>
</template> -->
</custom-form>
</el-card>
</div>
</template>
<script lang="ts" setup>
import type { FormInstance } from "element-plus";
import { exampleForm, Options } from "@/config/form";
import { ref } from "vue";
// 本項(xiàng)目EasyForm組件自動(dòng)引入孽鸡,如復(fù)制此代碼蹂午,需根據(jù)路徑引入Form組件后使用
const options: Form.Options = Options.form1;
const ruleFieldList: Form.FieldItem[] = exampleForm.ruleForm;
console.log("options", options);
/**
* 注意: model數(shù)據(jù)模型非必填項(xiàng)栏豺,如果僅僅是用于數(shù)據(jù)收集,model參數(shù)可以不用填豆胸,表單的submit事件會(huì)返回所有搜集的數(shù)據(jù)對(duì)象
* 如果是編輯的情況下奥洼,頁(yè)面需要回顯數(shù)據(jù),則model數(shù)據(jù)模型必須要填寫
*/
const handleSubmit = (model: Record<string, any>) => {
console.log(model.value)
}
</script>
頁(yè)面效果如下image.png
參數(shù)介紹
Form 屬性
參數(shù) | 說(shuō)明 | 類型 | 是否必填 | 默認(rèn)值 |
---|---|---|---|---|
model | 表單數(shù)據(jù)對(duì)象 | Record<string, any> | 否 | — |
options | 自定義配置 | object | 否 | — |
fieldList | formItem 配置數(shù)組 | Array<object> | 是 | — |
Options 配置項(xiàng)
參數(shù) | 說(shuō)明 | 類型 | 是否必填 | 默認(rèn)值 |
---|---|---|---|---|
labelWidth | 標(biāo)簽的長(zhǎng)度晚胡,例如 ‘50px’灵奖。 作為 Form 直接子元素的 form-item 會(huì)繼承該值。 可以使用 auto估盘。 | string / number | 否 | — |
labelPosition | 表單域標(biāo)簽的位置瓷患, 當(dāng)設(shè)置為 left 或 right 時(shí),則也需要設(shè)置 | label-width 屬性‘left’ / ‘right’ / ‘top’ | 否 | right |
size | 用于控制該表單內(nèi)組件的尺寸 | large / default /small | 否 | — |
disabled | 是否禁用該表單內(nèi)的所有組件遣妥。 如果設(shè)置為 true, 它將覆蓋內(nèi)部組件的 disabled 屬性擅编。 | boolean | 否 | false |
submitButtonText | 提交按鈕默認(rèn)顯示的文本內(nèi)容 | string | 否 | 提交 |
resetButtonText | 重置按鈕默認(rèn)顯示的文本內(nèi)容 | string | 否 | 重置 |
cancelButtonText | 取消按鈕默認(rèn)顯示的文本內(nèi)容 | string | 否 | 取消 |
showResetButton | 是否顯示重置按鈕 | boolean | 否 | — |
showCancelButton | 是否顯示取消按鈕 | boolean | 否 | — |
fieldItem 配置項(xiàng)
參數(shù) | 說(shuō)明 | 類型 | 是否必填 | 默認(rèn)值 |
---|---|---|---|---|
field | model 的鍵名 | string | 是 | — |
label | 標(biāo)簽文本 | string | 是 | — |
type | 當(dāng)前 fieldItem 的類型 | ‘password’ / ‘text’ / ‘textarea’ / ‘radio’ / ‘checkbox’ / ‘select’ | 否 | text |
value | 默認(rèn)顯示的值 | any | 否 | — |
placeholder | 輸入框占位文本 | string | 否 | — |
disabled | 是否禁用 | boolean | 否 | false |
options | 如果 type=‘checkbox’ / ‘radio’ / 'select’時(shí),需傳入此配置項(xiàng)箫踩。格式參考 fieldItem options 配置項(xiàng) | object | 否 | - |
rules | 表單驗(yàn)證規(guī)則爱态。格式參考element-plus form 表單 或者參數(shù)類型聲明 | Array<RuleItem> | 否 | - |
fieldItem options 配置項(xiàng)
參數(shù) | 說(shuō)明 | 類型 | 是否必填 | 默認(rèn)值 |
---|---|---|---|---|
labelkey | label 自定義字段名 | string | 否 | label |
value | value 自定義字段名 | string | 否 | value |
placeholder | 當(dāng) fieldItem type= 'select’時(shí),選擇框的提示語(yǔ) | string | 否 | - |
data | type=‘checkbox’ / ‘radio’ / 'select’時(shí), 需要的數(shù)據(jù) | Array<object> | 否 | - |
Form 插槽
插槽名 | 說(shuō)明 | 插槽作用域 |
---|---|---|
buttons | 自定義按鈕區(qū)域的內(nèi)容 | { model, formRef } |
Form 事件
事件名 | 說(shuō)明 | 回調(diào)參數(shù) |
---|---|---|
submit | 點(diǎn)擊默認(rèn)的提交按鈕觸發(fā) | model |
cancel | 點(diǎn)擊取消按鈕觸發(fā) | - |
reset | 重置該表單項(xiàng)境钟,將其值重置為初始值锦担,并移除校驗(yàn)結(jié)果 | - |