前言

上篇table 表格封装 讲到项目中经常会用到 table 表格,所以做了封装。当然,form 表单使用的频率依然很高,所以和封装 table 表格的思路相似,对 form 表单也做了一个二次封装的组件。

效果图

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

预览地址

1. EasyForm 表单组件封装

src/components/EasyForm/index.vue Form 表单组件封装

<template>
    <el-form @submit.prevent :model="formModel" 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="formModel[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>
            <!-- 复选框 -->
            <el-form-item
                :label="item.label"
                v-else-if="item.type === 'checkbox'"
                :rules="item.rules"
                :prop="[item.field]">
                <el-checkbox-group v-model="formModel[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]">
                <!-- <EasySelect
                    v-model="formModel[item.field]"
                    clearable
                    :disabled="item.disabled"
                    :label-key="item.options?.labelkey"
                    :value-key="item.options?.valueKey"
                    :select-data="item.options?.data" /> -->
                <el-select v-model="formModel[item.field]" :placeholder="item.options?.placeholder || '请选择'" :clearable="item.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>
            <!-- 默认输入框 -->
            <el-form-item :label="item.label" :rules="item.rules" :prop="[item.field]" v-else>
                <el-input
                    v-model="formModel[item.field]"
                    :readonly="item.readonly"
                    :type="item.type ?? 'text'"
                    :placeholder="item.placeholder || item.label"
                    :disabled="item.disabled" 
                    :showPassword="item.showPassword"
                    :clearable="item.clearable"
                    @keyup.enter="handleKeyUp(item.enterable)"/>
            </el-form-item>
        </template>

        <el-form-item>
            <slot name="buttons" :model="formModel" :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<Form.FieldItem['field'], Form.FieldItem['value']>
        options?: Form.Options
    }
    // 表单的数据
    const formModel = ref<Record<string, any>>({})
    const formRef = ref<FormInstance>()
    const props = defineProps<Props>()
    // 设置option默认值,如果传入自定义的配置则合并option配置项
    const _options: ComputedRef<Form.Options> = computed(() => {
        const option = {
            labelPosition: 'right',
            disabled: false,
            submitButtonText: '提交',
            resetButtonText: '重置',
            cancelButtonText: '取消'
        }
        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
    })
    // 根据fieldList初始化model, 如果formModel有传值就用传递的model数据模型,否则就给上面声明的formModel设置相应的(key,value) [item.field], item.value是表单的默认值(选填)
   watch(
	    () => props.model,
	    () => {
	        props.fieldList.map((item: Form.FieldItem) => {
	            // 如果类型为checkbox,默认值需要设置一个空数组
	            const value = item.type === 'checkbox' ? [] : ''
		            props.model ? (formModel.value = props.model) : (formModel.value[item.field] = item.value || value)
		        })
		    },
	    { immediate: true }
		)
    // 提交按钮
    const onSubmit = (formEl: FormInstance | undefined) => {
        if (!formEl) return
        formEl.validate((valid) => {
            if (valid) {
                emit('submit', formModel.value)
            } else {
                return false
            }
        })
    }
    // 输入框回车事件
	const handleKeyUp = (enterable: boolean | undefined) => {
	    if (!enterable) return
	    onSubmit(formRef.value)
	}
	// 重置
    const resetForm = (formEl: FormInstance | undefined) => {
        if (!formEl) return
        formEl.resetFields()
    }
</script>
<style lang="scss" scoped></style>

复制完Form组件的代码后,会报红线,Form.XXXXX 找不到,这个是全局类型声明。 声明文件已在下方贴出来,直接复制进项目中, 红色警告自然消失。

2. 基本表单使用

src/views/form/index.vue

<template>
    <el-card class="mb-5">
        <template #header> 基本表单 </template>
        <easy-form :fieldList="fieldList" :model="model" @submit="handleBaseSubmit">
            <!-- 如果不使用默认的按钮可以使用插槽自定义内容, 插槽返回的model就是当前表单的数据 -->
            <!-- <template #buttons="{ model }">
                    <el-button">提交</el-button>
                </template> -->
        </easy-form>
    </el-card>
</template>
<script lang="ts" setup>
    import { exampleForm } from '@/config/form' 
	// import { ref } from 'vue'
	// import EasyForm from '@/components/EasyForm/index.vue'
	// 本项目EasyForm组件自动引入,如复制此代码,需根据路径引入Form组件后使用
    const fieldList: Form.FieldItem[] = exampleForm.base
    const model = ref<Record<string, any>>({
        name: '张三',
        gender: 1,
        hobbies: [1],
        job: 3,
        readonly: '只读输入框',
        summary: '尤雨溪懂个锤子vue是什么梗'
    })
    /**
     * 注意: model数据模型非必填项,如果仅仅是用于数据收集,model参数可以不用填,表单的submit事件会返回所有搜集的数据对象
     *       如果是编辑的情况下,页面需要回显数据,则model数据模型必须要填写
     */
    const handleBaseSubmit = (model: Record<string, any>) => {
        console.log(model)
    }
</script>
<style lang="scss" scoped></style>

3. 自定义 key

src/views/form/index.vue

<template>
    <el-card class="mb-5">
        <template #header> 自定义key </template>
        <easy-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'
	// 本项目EasyForm组件自动引入,如复制此代码,需根据路径引入Form组件后使用
    const customKeyFieldList: Form.FieldItem[] = exampleForm.customkeyForm
    const model2 = ref<Record<string, any>>({
        name: '自定义key',
        gender: 1
    })
    /**
     * 注意: 如果使用到checkbox,radio,或者select等组件,需要传入组件额外需要的数据,本组件默认设定的读取数据的字段是 label, value
     *       可参考下方声明文件 FiledItem options的参数类型描述
     *       比如,当前传入的data数据字段名和label、value不匹配,可使用预留的参数 labelkey, valueKey指定字段名
     *         customkeyForm: [
                    { label: '标题', 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)
    }
</script>
<style lang="scss" scoped></style>

4. 自定义表单验证

src/views/form/index.vue

<template>
    <el-card class="mb-5">
        <template #header> 自定义验证的表单 (使用slot自定义按钮) </template>
        <easy-form :fieldList="ruleFieldList">
            <!-- 如果不使用默认的按钮可以使用插槽自定义内容, 插槽返回的model就是当前表单的数据, formRef是当前表单的FormInstance -->
            <template #buttons="{ model, formRef }">
                <el-button @click="handleSubmit(model, formRef)">保存</el-button>
            </template>
        </easy-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'
	// 本项目EasyForm组件自动引入,如复制此代码,需根据路径引入Form组件后使用
    const ruleFieldList: Form.FieldItem[] = exampleForm.ruleForm
    /**
     *  如果用到了表单验证,又使用slot自定义按钮的话,需要自行实现验证逻辑
     *  组件内部已经集成验证,及重置逻辑。表单验证建议使用内置的提交按钮。当通过验证规则,内置提交按钮才会出发submit事件
     */
    // 下方是使用slot自定义按钮,需要自己实现验证逻辑
    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="scss" scoped></style>

src/config/form.ts 表单配置项, 参数请参考下面参数介绍

// 自定义验证邮箱方法
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'))
}
// // 自定义验证表单配置数据
// 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: true },
        { label: '性别', field: 'gender', type: 'radio', options: { data: [{ label: '男', value: 1 }, { label: '女', value: 0 }] } },
        { label: '爱好', field: 'hobbies', type: 'checkbox', options: { data: [{ label: '吃饭', value: 1 }, { label: '睡觉', value: 2 }, { label: '写代码', value: 3 }] } },
        { label: '工作', field: 'job', type: 'select', options: { data: [{ label: '吃饭', value: 1 }, { label: '睡觉', value: 2 }, { label: '写代码', value: 3 }] } },
        { label: '密码', field: 'password', type: 'password', placeholder: '这是一个密码输入框' },
        { label: '只读', field: 'readonly', readonly: true, placeholder: '这是一个只读输入框' },
        { label: '留言板', field: 'summary', type: 'textarea', placeholder: '留言板' },
    ],
    customkeyForm: [
        { label: '标题', 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[]>

src/types/form/index.d.ts 参数类型声明(声明为全局的类型,方便使用)

import { type FormItemRule } from 'element-plus'
export = Form;
export as namespace Form;
declare namespace Form {
     type ItemType = 'password' | 'text'|'textarea' | 'radio' | 'checkbox' | 'select'
    // 当FiledItem的type === 'radio' | 'checkbox'时,options的参数类型
    interface IFieldOptions {
        labelkey?: string,
        valueKey?: string,
        placeholder?: string,
        data: Record<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,
        labelWidth?: string | number, // 标签宽度,例如 '50px'。 可以使用 auto。
        field: string,
        type?: ItemType,
        value?: any,
        placeholder?: string,
        disabled?: boolean,
        readonly?: boolean,
        options?: IFieldOptions,
        rules?: FormItemRule[]
        clearable?: boolean // 是否可清空
        showPassword?: boolean, // 是否显示切换密码图标
        enterable?: boolean, // 当为输入框时,是否启用回车触发提交功能
    }
}


参数介绍

Form 属性
参数说明类型是否必填默认值
model表单数据对象Record<string, any>
options自定义配置object
fieldListformItem 配置数组Array<object>
Options 配置项
参数说明类型是否必填默认值
labelWidth标签的长度,例如 ‘50px’。 作为 Form 直接子元素的 form-item 会继承该值。 可以使用 auto。string / number
labelPosition表单域标签的位置, 当设置为 left 或 right 时,则也需要设置 label-width 属性‘left’ / ‘right’ / ‘top’‘right’
size用于控制该表单内组件的尺寸large / default /small
disabled是否禁用该表单内的所有组件。 如果设置为 true, 它将覆盖内部组件的 disabled 属性。booleanfalse
submitButtonText提交按钮默认显示的文本内容string‘提交’
resetButtonText重置按钮默认显示的文本内容string‘重置’
cancelButtonText取消按钮默认显示的文本内容string‘取消’
showResetButton是否显示重置按钮boolean
showCancelButton是否显示取消按钮boolean
fieldItem 配置项
参数说明类型是否必填默认值
fieldmodel 的键名string
label标签文本string
type当前 fieldItem 的类型‘password’ / ‘text’ / ‘textarea’ / ‘radio’ / ‘checkbox’ / ‘select’‘text’
value默认显示的值any
placeholder输入框占位文本string
disabled是否禁用booleanfalse
options如果 type=‘checkbox’ / ‘radio’ / 'select’时,需传入此配置项。格式参考 fieldItem options 配置项object-
rules表单验证规则。格式参考element-plus form 表单 或者参数类型声明Array<RuleItem>-
clearable是否可清空booleanfalse
showPassword是否显示切换密码图标booleanfalse
enterable当为输入框时,是否启用回车触发提交功能booleanfalse
fieldItem options 配置项
参数说明类型是否必填默认值
labelkeylabel 自定义字段名string‘label’
valueKeyvalue 自定义字段名string‘value’
placeholder当 fieldItem type= 'select’时,选择框的提示语string-
datatype=‘checkbox’ / ‘radio’ / 'select’时, 需要的数据Array<object>-
Form 插槽
插槽名说明插槽作用域
buttons自定义按钮区域的内容{ model, formRef }
Form 事件
事件名说明回调参数
submit点击默认的提交按钮触发model
cancel点击取消按钮触发-
reset重置该表单项,将其值重置为初始值,并移除校验结果-

其他

此文档只提供基本的封装思路,如需使用到更多的业务场景,可自行扩展。
FiledItem type 类型可增加 富文本编辑器、 markdown 编辑器, 上传图片等类型,然后根据类型判断把封装好的富文本编辑器组件markdown 编辑器组件放入表单内。

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐