前言
上篇 小程序开发定制讲到项目中经常会用到 table 表格,小程序开发定制所以做了封装。当然,form 小程序开发定制表单使用的频率依然很高,小程序开发定制所以和封装 table 小程序开发定制表格的思路相似,对 form 小程序开发定制表单也做了一个二次封装的组件。
效果图
1. EasyForm 表单
src/components/EasyForm/index.vue
Form 小程序开发定制表单组件封装
<template> <el-form @submit.prevent :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> <!-- 复选框 --> <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]"> <!-- <EasySelect v-model="model[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="model[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="model[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="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 } // 小程序开发定制表单的数据 const model = 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 }) // const model = ref<Record<string, any>>({}) // 根据fieldList初始化model, 如果model有传值就用传递的model数据模型,否则就给上面声明的model设置相应的(key,value) [item.field], item.value是表单的默认值(选填) props.fieldList.map((item: Form.FieldItem) => { // 如果类型为checkbox,默认值需要设置一个空数组 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.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>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
- 129
- 130
- 131
- 132
- 133
- 134
- 135
- 136
- 137
- 138
- 139
- 140
- 141
复制完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>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
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>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
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>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
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[]>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
src/types/form/index.d.ts
参数类型声明(声明为全局的类型,方便使用)
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: 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, labelWidth?: string | number, // 标签宽度,例如 '50px'。 可以使用 auto。 field: string, type?: ItemType, value?: any, placeholder?: string, disabled?: boolean, readonly?: boolean, options?: IFieldOptions, rules?: import('element-plus').FormItemRule[] clearable?: boolean // 是否可清空 showPassword?: boolean, // 是否显示切换密码图标 enterable?: boolean, // 当为输入框时,是否启用回车触发提交功能 }}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
参数介绍
Form 属性
参数 | 说明 | 类型 | 是否必填 | 默认值 |
---|---|---|---|---|
model | 表单数据对象 | Record<string, any> | 否 | — |
options | 自定义配置 | object | 否 | — |
fieldList | formItem 配置数组 | 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 属性。 | boolean | 否 | false |
submitButtonText | 提交按钮默认显示的文本内容 | string | 否 | ‘提交’ |
resetButtonText | 重置按钮默认显示的文本内容 | string | 否 | ‘重置’ |
cancelButtonText | 取消按钮默认显示的文本内容 | string | 否 | ‘取消’ |
showResetButton | 是否显示重置按钮 | boolean | 否 | — |
showCancelButton | 是否显示取消按钮 | boolean | 否 | — |
fieldItem 配置项
参数 | 说明 | 类型 | 是否必填 | 默认值 |
---|---|---|---|---|
field | model 的键名 | string | 是 | — |
label | 标签文本 | string | 是 | — |
type | 当前 fieldItem 的类型 | ‘password’ / ‘text’ / ‘textarea’ / ‘radio’ / ‘checkbox’ / ‘select’ | 否 | ‘text’ |
value | 默认显示的值 | any | 否 | — |
placeholder | 输入框占位文本 | string | 否 | — |
disabled | 是否禁用 | boolean | 否 | false |
options | 如果 type=‘checkbox’ / ‘radio’ / 'select’时,需传入此配置项。格式参考 fieldItem options 配置项 | object | 否 | - |
rules | 表单验证规则。格式参考 或者参数类型声明 | Array<RuleItem> | 否 | - |
clearable | 是否可清空 | boolean | 否 | false |
showPassword | 是否显示切换密码图标 | boolean | 否 | false |
enterable | 当为输入框时,是否启用回车触发提交功能 | boolean | 否 | false |
fieldItem options 配置项
参数 | 说明 | 类型 | 是否必填 | 默认值 |
---|---|---|---|---|
labelkey | label 自定义字段名 | string | 否 | ‘label’ |
value | value 自定义字段名 | string | 否 | ‘value’ |
placeholder | 当 fieldItem type= 'select’时,选择框的提示语 | string | 否 | - |
data | type=‘checkbox’ / ‘radio’ / 'select’时, 需要的数据 | Array<object> | 否 | - |
Form 插槽
插槽名 | 说明 | 插槽作用域 |
---|---|---|
buttons | 自定义按钮区域的内容 | { model, formRef } |
Form 事件
事件名 | 说明 | 回调参数 |
---|---|---|
submit | 点击默认的提交按钮触发 | model |
cancel | 点击取消按钮触发 | - |
reset | 重置该表单项,将其值重置为初始值,并移除校验结果 | - |
其他
此文档只提供基本的封装思路,如需使用到更多的业务场景,可自行扩展。
FiledItem type 类型可增加 富文本编辑器、 markdown 编辑器, 上传图片等类型,然后根据类型判断把封装好的、放入表单内。