vue3+elementplus表单组件封装
CustomForm 组件使用文档
1. 组件介绍
CustomForm 是一个基于 Element Plus 封装的表单组件,提供了丰富的表单控件类型和配置选项,支持水平浮动布局、插槽功能、表单验证等特性,大大简化了表单的开发流程。
主要特性
- 支持多种表单控件类型(输入框、选择器、复选框、开关等)
- 支持水平浮动布局,超出自动换行
- 支持通过
newline属性强制换行 - 支持
start和end插槽,可在表单前后添加自定义内容 - 支持字段级别的插槽,可添加自定义内容
- 支持表单验证,可通过
required属性或自定义规则 - 支持表单提交、重置等操作
- 支持父组件调用子组件的方法进行表单操作
2. 组件安装和引入
安装依赖
确保项目中已安装 Element Plus:
npm install element-plus --save引入组件
在需要使用的文件中引入 CustomForm 组件:
<template>
<CustomForm
v-model="formData"
:form-config="formConfig"
@submit="handleSubmit"
@reset="handleReset"
/>
</template>
<script setup>
import CustomForm from './components/CustomForm.vue';
// 表单数据
const formData = reactive({
// 表单字段
});
// 表单配置
const formConfig = {
// 配置选项
};
// 处理提交
const handleSubmit = (data) => {
console.log('表单提交:', data);
};
// 处理重置
const handleReset = () => {
console.log('表单重置');
};
</script>3. 基本使用方式
3.1 基本表单示例
<template>
<CustomForm
v-model="basicFormData"
:form-config="basicFormConfig"
@submit="handleBasicSubmit"
@reset="handleBasicReset"
@input="handleInput"
@change="handleChange"
/>
</template>
<script setup>
import { reactive } from 'vue';
import CustomForm from './components/CustomForm.vue';
// 基本表单数据
let basicFormData = reactive({
name: '',
email: '',
age: null,
gender: '',
hobbies: [],
address: '',
birthdate: '',
status: false
});
// 基本表单配置
const basicFormConfig = {
title: '用户信息表单',
width: '800px',
height: 'auto',
labelPosition: 'right',
labelWidth: '100px',
inline: false,
disabled: false,
size: 'default',
showActions: true,
showReset: true,
showSubmit: true,
resetText: '重置',
submitText: '提交',
fields: [
{
type: 'input',
label: '姓名',
prop: 'name',
placeholder: '请输入姓名',
required: true,
maxlength: 20,
showWordLimit: true,
clearable: true
},
// 其他字段配置...
]
};
// 处理表单提交
const handleBasicSubmit = (data) => {
console.log('基本表单提交:', data);
alert('基本表单提交成功!');
};
// 处理表单重置
const handleBasicReset = () => {
console.log('基本表单重置');
};
// 处理输入事件
const handleInput = (event) => {
console.log('输入事件:', event);
};
// 处理变更事件
const handleChange = (event) => {
console.log('变更事件:', event);
};
</script>3.2 行内表单示例
<template>
<CustomForm
v-model="inlineFormData"
:form-config="inlineFormConfig"
@submit="handleInlineSubmit"
@reset="handleInlineReset"
>
<!-- 自定义插槽内容 -->
<template #customContent="{ formData, field }">
<el-button type="primary" size="small">
自定义按钮{{ field }}
</el-button>
<el-button type="success" size="small" style="margin-left: 10px;">
另一个按钮{{ formData }}
</el-button>
</template>
</CustomForm>
</template>
<script setup>
import { reactive } from 'vue';
import CustomForm from './components/CustomForm.vue';
// 行内表单数据
let inlineFormData = reactive({
search: '',
category: '',
status: ''
});
// 行内表单配置
const inlineFormConfig = {
title: '搜索表单',
width: '100%',
height: 'auto',
labelPosition: 'right',
labelWidth: '80px',
inline: true, // 行内表单
disabled: false,
size: 'small',
showActions: true,
showReset: true,
showSubmit: true,
resetText: '重置',
submitText: '搜索',
fields: [
{
type: 'input',
label: '搜索关键词',
labelWidth: '90px',
prop: 'search',
placeholder: '请输入搜索关键词',
clearable: true,
prefixIcon: 'el-icon-search'
},
// 纯文本显示示例
{
type: 'text',
label: '订单来源',
prop: 'orderSource',
defaultValue: '爱儿心选服务订单',
width: '200px',
newline: true
},
// 插槽
{
label: '自定义插槽',
labelWidth: '90px',
prop: 'customContent',
type: 'slot',
slotName: 'customContent',
width: '300px',
newline: true
},
// 其他字段配置...
]
};
// 处理行内表单提交
const handleInlineSubmit = (data) => {
console.log('行内表单提交:', data);
alert('搜索提交成功!');
};
// 处理行内表单重置
const handleInlineReset = () => {
console.log('行内表单重置');
};
</script>3.3 禁用表单示例
<template>
<CustomForm
v-model="disabledFormData"
:form-config="disabledFormConfig"
@submit="handleDisabledSubmit"
@reset="handleDisabledReset"
/>
</template>
<script setup>
import { reactive } from 'vue';
import CustomForm from './components/CustomForm.vue';
// 禁用表单数据
let disabledFormData = reactive({
name: '张三',
email: 'zhangsan@example.com',
age: 25,
gender: 'male',
hobbies: ['reading', 'sports'],
address: '北京市朝阳区',
birthdate: '1998-01-01',
status: true
});
// 禁用表单配置
const disabledFormConfig = {
title: '禁用表单',
width: '600px',
height: 'auto',
labelPosition: 'right',
labelWidth: '100px',
inline: false,
disabled: true, // 禁用整个表单
size: 'default',
showActions: true,
showReset: true,
showSubmit: true,
resetText: '重置',
submitText: '提交',
fields: [
// 字段配置...
]
};
// 处理禁用表单提交
const handleDisabledSubmit = (data) => {
console.log('禁用表单提交:', data);
alert('禁用表单提交成功!');
};
// 处理禁用表单重置
const handleDisabledReset = () => {
console.log('禁用表单重置');
};
</script>3.4 水平浮动表单示例
<template>
<CustomForm
v-model="horizontalFormData"
:form-config="horizontalFormConfig"
@submit="handleHorizontalSubmit"
@reset="handleHorizontalReset"
@input="handleInput"
@change="handleChange"
/>
</template>
<script setup>
import { reactive } from 'vue';
import CustomForm from './components/CustomForm.vue';
// 水平浮动表单数据
const horizontalFormData = reactive({
name: '张三',
age: 25,
gender: 'male',
email: 'zhangsan@example.com',
phone: '13800138000',
address: '北京市朝阳区',
status: true,
role: 'user',
birthdate: '1998-01-01',
interests: ['reading', 'music'],
orderSource: '爱儿心选服务订单'
});
// 水平浮动表单配置
const horizontalFormConfig = {
title: '用户信息表',
width: '100%',
labelWidth: '100px',
inline: false,
showActions: true,
fields: [
{
type: 'input',
label: '姓名',
prop: 'name',
placeholder: '请输入姓名',
width: '200px',
required: true
},
{
type: 'number',
label: '年龄',
prop: 'age',
placeholder: '请输入年龄',
width: '120px',
min: 0,
max: 150
},
{
type: 'select',
label: '性别',
prop: 'gender',
placeholder: '请选择性别',
width: '150px',
options: [
{ label: '男', value: 'male' },
{ label: '女', value: 'female' }
],
newline: true // 强制换行
},
// 其他字段配置...
]
};
// 处理水平浮动表单提交
const handleHorizontalSubmit = (data) => {
console.log('水平浮动表单提交:', data);
alert('表单提交成功!');
};
// 处理水平浮动表单重置
const handleHorizontalReset = () => {
console.log('水平浮动表单重置');
};
</script>3.5 带插槽的表单示例
<template>
<CustomForm
ref="slotFormRef"
v-model="slotFormData"
:form-config="slotFormConfig"
@submit="handleSlotSubmit"
@reset="handleSlotReset"
>
<!-- 开始插槽内容 -->
<template #start="{ formData }">
<div class="form-slot-content" style="margin-bottom: 20px; padding: 10px; background: #f5f7fa; border-radius: 4px;">
<span>这是开始插槽内容 - 表单数据: {{ formData.name }}</span>
<el-button type="info" size="small" style="margin-left: 10px;">
开始按钮
</el-button>
</div>
</template>
<!-- 结束插槽内容 -->
<template #end="{ formData }">
<div class="form-slot-content" style="margin-top: 20px; padding: 10px; background: #f5f7fa; border-radius: 4px;">
<span>这是结束插槽内容 - 表单数据: {{ formData.name }}</span>
<el-button type="warning" size="small" style="margin-left: 10px;">
结束按钮
</el-button>
</div>
</template>
</CustomForm>
<!-- 父组件的提交按钮 -->
<div style="margin-top: 20px;">
<el-button type="primary" @click="validateForm">
父组件校验表单
</el-button>
<el-button type="success" @click="resetForm">
父组件重置表单
</el-button>
</div>
</template>
<script setup>
import { ref, reactive } from 'vue';
import CustomForm from './components/CustomForm.vue';
// 带插槽的表单数据
const slotFormData = reactive({
name: '',
email: '',
phone: ''
});
// 带插槽的表单配置
const slotFormConfig = {
title: '带插槽的表单',
width: '800px',
labelPosition: 'right',
labelWidth: '100px',
inline: false,
showActions: true,
fields: [
{
type: 'input',
label: '姓名',
prop: 'name',
placeholder: '请输入姓名',
required: true,
width: '300px'
},
// 其他字段配置...
]
};
// 表单引用
const slotFormRef = ref(null);
// 处理带插槽的表单提交
const handleSlotSubmit = (data) => {
console.log('带插槽的表单提交:', data);
alert('带插槽的表单提交成功!');
};
// 处理带插槽的表单重置
const handleSlotReset = () => {
console.log('带插槽的表单重置');
};
// 父组件校验表单
const validateForm = async () => {
if (slotFormRef.value) {
const isValid = await slotFormRef.value.validate();
if (isValid) {
alert('表单验证通过!');
} else {
alert('表单验证失败,请检查必填项!');
}
}
};
// 父组件重置表单
const resetForm = () => {
if (slotFormRef.value) {
slotFormRef.value.resetFields();
alert('表单已重置!');
}
};
</script>4. 详细配置选项
4.1 表单基本配置
| 配置项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| title | String | - | 表单标题 |
| width | String | - | 表单宽度 |
| height | String | auto | 表单高度 |
| labelPosition | String | right | 标签位置,可选值:left、right、top |
| labelWidth | String | - | 标签宽度 |
| inline | Boolean | false | 是否为行内表单 |
| disabled | Boolean | false | 是否禁用整个表单 |
| size | String | default | 表单大小,可选值:large、default、small |
| showActions | Boolean | true | 是否显示操作按钮 |
| showReset | Boolean | true | 是否显示重置按钮 |
| showSubmit | Boolean | true | 是否显示提交按钮 |
| resetText | String | 重置 | 重置按钮文本 |
| submitText | String | 提交 | 提交按钮文本 |
| fields | Array | [] | 表单字段配置 |
4.2 表单字段配置
| 配置项 | 类型 | 默认值 | 说明 | 适用控件类型 |
|---|---|---|---|---|
| type | String | - | 控件类型:input、textarea、number、select、radio、checkbox、switch、date、time、datetime、text、slot | 所有 |
| label | String | - | 字段标签 | 所有 |
| prop | String | - | 字段属性名 | 所有 |
| placeholder | String | - | 占位符 | input、textarea、number、select、date、time、datetime |
| required | Boolean | false | 是否必填 | 所有 |
| rules | Array | - | 验证规则 | 所有 |
| width | String | - | 控件宽度 | input、textarea、number、select、date、time、datetime、text |
| labelWidth | String | - | 字段标签宽度(优先级高于表单级别的labelWidth) | 所有 |
| disabled | Boolean | false | 是否禁用 | 所有 |
| newline | Boolean | false | 是否强制换行 | 所有 |
| maxlength | Number | - | 最大输入长度 | input、textarea |
| showWordLimit | Boolean | false | 是否显示字数统计 | input、textarea |
| clearable | Boolean | false | 是否可清空 | input、select |
| prefixIcon | String | - | 前缀图标 | input |
| suffixIcon | String | - | 后缀图标 | input |
| inputType | String | text | 输入类型 | input |
| rows | Number | 2 | 文本域行数 | textarea |
| min | Number | - | 最小值 | number |
| max | Number | - | 最大值 | number |
| step | Number | 1 | 步长 | number |
| multiple | Boolean | false | 是否多选 | select |
| options | Array | [] | 选项列表,格式:[{ label: '标签', value: '值' }] | select、radio、checkbox |
| activeText | String | - | 开关激活文本 | switch |
| inactiveText | String | - | 开关非激活文本 | switch |
| activeValue | Boolean/Number/String | true | 开关激活值 | switch |
| inactiveValue | Boolean/Number/String | false | 开关非激活值 | switch |
| format | String | YYYY-MM-DD / HH:mm:ss / YYYY-MM-DD HH:mm:ss | 显示格式 | date、time、datetime |
| valueFormat | String | YYYY-MM-DD / HH:mm:ss / YYYY-MM-DD HH:mm:ss | 值格式 | date、time、datetime |
| defaultValue | String | - | 默认值 | text |
| color | String | #606266 | 文本颜色 | text |
| slotName | String | - | 插槽名称(若未指定则使用prop作为插槽名称) | slot |
5. 控件类型详解
5.1 输入框 (input)
{
type: 'input',
label: '姓名',
prop: 'name',
placeholder: '请输入姓名',
required: true,
maxlength: 20,
showWordLimit: true,
clearable: true,
prefixIcon: 'el-icon-search',
suffixIcon: 'el-icon-date',
inputType: 'text',
width: '300px'
}5.2 文本域 (textarea)
{
type: 'textarea',
label: '地址',
prop: 'address',
placeholder: '请输入地址',
rows: 3,
maxlength: 100,
showWordLimit: true,
width: '300px'
}5.3 数字输入框 (number)
{
type: 'number',
label: '年龄',
prop: 'age',
placeholder: '请输入年龄',
min: 0,
max: 150,
step: 1,
width: '120px'
}5.4 选择器 (select)
{
type: 'select',
label: '性别',
prop: 'gender',
placeholder: '请选择性别',
required: true,
options: [
{ label: '男', value: 'male' },
{ label: '女', value: 'female' },
{ label: '其他', value: 'other' }
],
clearable: true,
multiple: false, // 是否多选
width: '150px'
}5.5 单选框组 (radio)
{
type: 'radio',
label: '性别',
prop: 'gender',
options: [
{ label: '男', value: 'male' },
{ label: '女', value: 'female' }
]
}5.6 复选框组 (checkbox)
{
type: 'checkbox',
label: '爱好',
prop: 'hobbies',
options: [
{ label: '阅读', value: 'reading' },
{ label: '运动', value: 'sports' },
{ label: '音乐', value: 'music' },
{ label: '旅行', value: 'travel' }
]
}5.7 开关 (switch)
{
type: 'switch',
label: '状态',
prop: 'status',
activeText: '启用',
inactiveText: '禁用',
activeValue: true,
inactiveValue: false
}5.8 日期选择器 (date)
{
type: 'date',
label: '出生日期',
prop: 'birthdate',
placeholder: '请选择出生日期',
format: 'YYYY-MM-DD',
valueFormat: 'YYYY-MM-DD',
width: '200px'
}5.9 时间选择器 (time)
{
type: 'time',
label: '时间',
prop: 'time',
placeholder: '请选择时间',
format: 'HH:mm:ss',
valueFormat: 'HH:mm:ss',
width: '200px'
}5.10 日期时间选择器 (datetime)
{
type: 'datetime',
label: '日期时间',
prop: 'datetime',
placeholder: '请选择日期时间',
format: 'YYYY-MM-DD HH:mm:ss',
valueFormat: 'YYYY-MM-DD HH:mm:ss',
width: '250px'
}5.11 纯文本显示 (text)
{
type: 'text',
label: '订单来源',
prop: 'orderSource',
defaultValue: '爱儿心选服务订单',
width: '200px',
color: '#409EFF'
}5.12 插槽 (slot)
{
label: '自定义内容',
prop: 'customContent',
type: 'slot',
slotName: 'customContent', // 插槽名称,若未指定则使用prop作为插槽名称
width: '300px'
}6. 插槽功能
6.1 表单级插槽
start 插槽
位于表单字段最前面,可用于添加表单说明、自定义头部等内容。
<template #start="{ formData }">
<div class="form-slot-content" style="margin-bottom: 20px; padding: 10px; background: #f5f7fa; border-radius: 4px;">
<span>这是开始插槽内容 - 表单数据: {{ formData.name }}</span>
<el-button type="info" size="small" style="margin-left: 10px;">
开始按钮
</el-button>
</div>
</template>end 插槽
位于表单字段最后面,可用于添加表单说明、自定义尾部等内容。
<template #end="{ formData }">
<div class="form-slot-content" style="margin-top: 20px; padding: 10px; background: #f5f7fa; border-radius: 4px;">
<span>这是结束插槽内容 - 表单数据: {{ formData.name }}</span>
<el-button type="warning" size="small" style="margin-left: 10px;">
结束按钮
</el-button>
</div>
</template>6.2 字段级插槽
通过 type: 'slot' 配置的字段,可以在组件内添加自定义内容。
<template #customContent="{ formData, field }">
<el-button type="primary" size="small">
自定义按钮{{ field }}
</el-button>
<el-button type="success" size="small" style="margin-left: 10px;">
另一个按钮{{ formData }}
</el-button>
</template>7. 事件处理
7.1 提交事件 (submit)
当点击提交按钮且表单验证通过时触发,参数为表单数据。
const handleSubmit = (data) => {
console.log('表单提交:', data);
// 处理表单提交逻辑
};7.2 重置事件 (reset)
当点击重置按钮时触发。
const handleReset = () => {
console.log('表单重置');
// 处理表单重置逻辑
};7.3 输入事件 (input)
当输入框输入内容时触发。
const handleInput = (event) => {
console.log('输入事件:', event);
// 处理输入事件逻辑
};7.4 变更事件 (change)
当表单控件值发生变化时触发。
const handleChange = (event) => {
console.log('变更事件:', event);
// 处理变更事件逻辑
};8. 方法调用
通过 ref 引用 CustomForm 组件,可以调用以下方法:
8.1 validate()
验证表单,返回 Promise,resolve 为布尔值表示校验是否通过。
const slotFormRef = ref(null);
const validateForm = async () => {
if (slotFormRef.value) {
const isValid = await slotFormRef.value.validate();
if (isValid) {
alert('表单验证通过!');
} else {
alert('表单验证失败,请检查必填项!');
}
}
};8.2 resetFields()
重置表单字段。
const resetForm = () => {
if (slotFormRef.value) {
slotFormRef.value.resetFields();
alert('表单已重置!');
}
};8.3 submitForm()
提交表单,触发 submit 事件。
const submitForm = () => {
if (slotFormRef.value) {
slotFormRef.value.submitForm();
}
};8.4 resetForm()
重置表单,触发 reset 事件。
const resetForm = () => {
if (slotFormRef.value) {
slotFormRef.value.resetForm();
}
};9. 响应式设计
CustomForm 组件支持响应式布局,在屏幕宽度小于等于 768px 时,表单字段会自动垂直排列,操作按钮也会垂直排列。
10. 注意事项
表单数据绑定:使用
v-model绑定表单数据,组件内部会自动同步数据变化。表单验证:可以通过
required属性设置必填项,也可以通过rules属性设置自定义验证规则。水平浮动布局:当
inline为false时,表单字段会水平浮动,超出容器宽度时自动换行。可以通过newline: true强制字段换行。插槽使用:可以通过
start和end插槽在表单前后添加自定义内容,也可以通过字段级插槽添加自定义控件。方法调用:通过 ref 引用组件后,可以调用
validate()、resetFields()等方法进行表单操作。事件处理:可以监听
submit、reset、input、change等事件处理表单交互。
11. 完整示例代码
11.1 App.vue 完整代码
<template>
<div class="app-container">
<h1>CustomForm 组件使用示例</h1>
<!-- 基本表单示例 -->
<section class="example-section">
<h2>基本表单示例</h2>
<CustomForm
v-model="basicFormData"
:form-config="basicFormConfig"
@submit="handleBasicSubmit"
@reset="handleBasicReset"
@input="handleInput"
@change="handleChange"
/>
</section>
<!-- 行内表单示例 -->
<section class="example-section">
<h2>行内表单示例</h2>
<CustomForm
v-model="inlineFormData"
:form-config="inlineFormConfig"
@submit="handleInlineSubmit"
@reset="handleInlineReset"
>
<!-- 自定义插槽内容 -->
<template #customContent="{ formData, field }">
<el-button type="primary" size="small">
自定义按钮{{ field }}
</el-button>
<el-button type="success" size="small" style="margin-left: 10px;">
另一个按钮{{ formData }}
</el-button>
</template>
</CustomForm>
</section>
<!-- 禁用表单示例 -->
<section class="example-section">
<h2>禁用表单示例</h2>
<CustomForm
v-model="disabledFormData"
:form-config="disabledFormConfig"
@submit="handleDisabledSubmit"
@reset="handleDisabledReset"
/>
</section>
<!-- 水平浮动表单示例 -->
<section class="example-section">
<h2>水平浮动表单(超出自动换行,支持强制换行)</h2>
<CustomForm
v-model="horizontalFormData"
:form-config="horizontalFormConfig"
@submit="handleHorizontalSubmit"
@reset="handleHorizontalReset"
@input="handleInput"
@change="handleChange"
/>
</section>
<!-- 带start和end插槽的表单示例 -->
<section class="example-section">
<h2>带start和end插槽的表单示例</h2>
<CustomForm
ref="slotFormRef"
v-model="slotFormData"
:form-config="slotFormConfig"
@submit="handleSlotSubmit"
@reset="handleSlotReset"
>
<!-- 开始插槽内容 -->
<template #start="{ formData }">
<div class="form-slot-content" style="margin-bottom: 20px; padding: 10px; background: #f5f7fa; border-radius: 4px;">
<span>这是开始插槽内容 - 表单数据: {{ formData.name }}</span>
<el-button type="info" size="small" style="margin-left: 10px;">
开始按钮
</el-button>
</div>
</template>
<!-- 结束插槽内容 -->
<template #end="{ formData }">
<div class="form-slot-content" style="margin-top: 20px; padding: 10px; background: #f5f7fa; border-radius: 4px;">
<span>这是结束插槽内容 - 表单数据: {{ formData.name }}</span>
<el-button type="warning" size="small" style="margin-left: 10px;">
结束按钮
</el-button>
</div>
</template>
</CustomForm>
<!-- 父组件的提交按钮 -->
<div style="margin-top: 20px;">
<el-button type="primary" @click="validateForm">
父组件校验表单
</el-button>
<el-button type="success" @click="resetForm">
父组件重置表单
</el-button>
</div>
</section>
</div>
</template>
<script setup>
import { ref, reactive } from 'vue';
import CustomForm from './components/CustomForm.vue';
// 基本表单数据
let basicFormData = reactive({
name: '',
email: '',
age: null,
gender: '',
hobbies: [],
address: '',
birthdate: '',
status: false
});
// 基本表单配置
const basicFormConfig = {
title: '用户信息表单',
width: '800px', // 自定义宽度
height: 'auto', // 自定义高度
labelPosition: 'right',
labelWidth: '100px',
inline: false,
disabled: false,
size: 'default',
showActions: true,
showReset: true,
showSubmit: true,
resetText: '重置',
submitText: '提交',
fields: [
{
type: 'input',
label: '姓名',
prop: 'name',
placeholder: '请输入姓名',
required: true,
maxlength: 20,
showWordLimit: true,
clearable: true
},
{
type: 'input',
label: '邮箱',
prop: 'email',
placeholder: '请输入邮箱',
required: true,
inputType: 'email',
clearable: true,
rules: [
{
required: true,
message: '请输入邮箱',
trigger: 'blur'
},
{
type: 'email',
message: '请输入正确的邮箱格式',
trigger: 'blur'
}
],
newline: true // 换行标识符
},
{
type: 'number',
label: '年龄',
prop: 'age',
placeholder: '请输入年龄',
min: 0,
max: 150,
step: 1
},
{
type: 'select',
label: '性别',
prop: 'gender',
placeholder: '请选择性别',
required: true,
options: [
{ label: '男', value: 'male' },
{ label: '女', value: 'female' },
{ label: '其他', value: 'other' }
],
clearable: true
},
{
type: 'checkbox',
label: '爱好',
prop: 'hobbies',
options: [
{ label: '阅读', value: 'reading' },
{ label: '运动', value: 'sports' },
{ label: '音乐', value: 'music' },
{ label: '旅行', value: 'travel' }
],
newline: true // 换行标识符
},
{
type: 'textarea',
label: '地址',
prop: 'address',
placeholder: '请输入地址',
rows: 3,
maxlength: 100,
showWordLimit: true
},
{
type: 'date',
label: '出生日期',
prop: 'birthdate',
placeholder: '请选择出生日期',
valueFormat: 'YYYY-MM-DD'
},
{
type: 'switch',
label: '状态',
prop: 'status',
activeText: '启用',
inactiveText: '禁用',
activeValue: true,
inactiveValue: false
}
]
};
// 行内表单数据
let inlineFormData = reactive({
search: '',
category: '',
status: ''
});
// 行内表单配置
const inlineFormConfig = {
title: '搜索表单',
width: '100%',
height: 'auto',
labelPosition: 'right',
labelWidth: '80px',
inline: true, // 行内表单
disabled: false,
size: 'small',
showActions: true,
showReset: true,
showSubmit: true,
resetText: '重置',
submitText: '搜索',
fields: [
// 搜索类
{
type: 'input',
label: '搜索关键词',
labelWidth: '90px',
prop: 'search',
placeholder: '请输入搜索关键词',
clearable: true,
prefixIcon: 'el-icon-search'
},
// 纯文本显示示例
{
type: 'text',
label: '订单来源',
prop: 'orderSource',
type: 'text',
defaultValue: '爱儿心选服务订单',
width: '200px',
newline: true
},
// 插槽
{
label: '自定义插槽',
labelWidth: '90px',
prop: 'customContent',
type: 'slot',
slotName: 'customContent',
width: '300px',
newline: true
},
{
type: 'select',
label: '分类',
prop: 'category',
width: '200px',
placeholder: '请选择分类',
options: [
{ label: '全部', value: '' },
{ label: '类别1', value: 'cat1' },
{ label: '类别2', value: 'cat2' },
{ label: '类别3', value: 'cat3' }
],
clearable: true
},
{
type: 'select',
label: '状态',
prop: 'status',
placeholder: '请选择状态',
width: '200px',
options: [
{ label: '全部', value: '' },
{ label: '启用', value: 'enabled' },
{ label: '禁用', value: 'disabled' }
],
clearable: true
}
]
};
// 禁用表单数据
let disabledFormData = reactive({
name: '张三',
email: 'zhangsan@example.com',
age: 25,
gender: 'male',
hobbies: ['reading', 'sports'],
address: '北京市朝阳区',
birthdate: '1998-01-01',
status: true
});
// 禁用表单配置
const disabledFormConfig = {
title: '禁用表单',
width: '600px',
height: 'auto',
labelPosition: 'right',
labelWidth: '100px',
inline: false,
disabled: true, // 禁用整个表单
size: 'default',
showActions: true,
showReset: true,
showSubmit: true,
resetText: '重置',
submitText: '提交',
fields: [
{
type: 'input',
label: '姓名',
prop: 'name',
placeholder: '请输入姓名'
},
{
type: 'input',
label: '邮箱',
prop: 'email',
placeholder: '请输入邮箱',
inputType: 'email'
},
{
type: 'number',
label: '年龄',
prop: 'age',
placeholder: '请输入年龄'
},
{
type: 'select',
label: '性别',
prop: 'gender',
placeholder: '请选择性别',
options: [
{ label: '男', value: 'male' },
{ label: '女', value: 'female' },
{ label: '其他', value: 'other' }
]
},
{
type: 'checkbox',
label: '爱好',
prop: 'hobbies',
options: [
{ label: '阅读', value: 'reading' },
{ label: '运动', value: 'sports' },
{ label: '音乐', value: 'music' },
{ label: '旅行', value: 'travel' }
]
},
{
type: 'textarea',
label: '地址',
prop: 'address',
placeholder: '请输入地址',
rows: 3
},
{
type: 'date',
label: '出生日期',
prop: 'birthdate',
placeholder: '请选择出生日期',
valueFormat: 'YYYY-MM-DD'
},
{
type: 'switch',
label: '状态',
prop: 'status',
activeText: '启用',
inactiveText: '禁用'
}
]
};
// 水平浮动表单数据
const horizontalFormData = reactive({
name: '张三',
age: 25,
gender: 'male',
email: 'zhangsan@example.com',
phone: '13800138000',
address: '北京市朝阳区',
status: true,
role: 'user',
birthdate: '1998-01-01',
interests: ['reading', 'music'],
orderSource: '爱儿心选服务订单'
});
// 水平浮动表单配置
const horizontalFormConfig = {
title: '用户信息表',
width: '100%',
labelWidth: '100px',
inline: false,
showActions: true,
fields: [
{
type: 'input',
label: '姓名',
prop: 'name',
placeholder: '请输入姓名',
width: '200px',
required: true
},
{
type: 'number',
label: '年龄',
prop: 'age',
placeholder: '请输入年龄',
width: '120px',
min: 0,
max: 150
},
{
type: 'select',
label: '性别',
prop: 'gender',
placeholder: '请选择性别',
width: '150px',
options: [
{ label: '男', value: 'male' },
{ label: '女', value: 'female' }
],
newline: true
},
{
type: 'input',
label: '邮箱',
prop: 'email',
placeholder: '请输入邮箱',
width: '250px',
newline: true // 强制换行
},
{
type: 'input',
label: '电话',
prop: 'phone',
placeholder: '请输入电话',
width: '200px'
},
{
type: 'textarea',
label: '地址',
prop: 'address',
placeholder: '请输入地址',
width: '300px',
rows: 2
},
{
type: 'switch',
label: '状态',
prop: 'status',
activeText: '启用',
inactiveText: '禁用',
newline: true // 强制换行
},
{
type: 'select',
label: '角色',
prop: 'role',
placeholder: '请选择角色',
width: '150px',
options: [
{ label: '管理员', value: 'admin' },
{ label: '用户', value: 'user' },
{ label: '访客', value: 'guest' }
]
},
{
type: 'date',
label: '出生日期',
prop: 'birthdate',
placeholder: '请选择出生日期',
width: '200px'
},
{
type: 'checkbox',
label: '兴趣爱好',
prop: 'interests',
options: [
{ label: '阅读', value: 'reading' },
{ label: '音乐', value: 'music' },
{ label: '运动', value: 'sports' },
{ label: '旅行', value: 'travel' }
],
newline: true // 强制换行
},
{
type: 'text',
label: '订单来源',
prop: 'orderSource',
defaultValue: '爱儿心选服务订单',
width: '300px',
color: '#409EFF', // 颜色
}
]
};
// 带插槽的表单数据
const slotFormData = reactive({
name: '',
email: '',
phone: ''
});
// 带插槽的表单配置
const slotFormConfig = {
title: '带插槽的表单',
width: '800px',
labelPosition: 'right',
labelWidth: '100px',
inline: false,
showActions: true,
fields: [
{
type: 'input',
label: '姓名',
prop: 'name',
placeholder: '请输入姓名',
required: true,
width: '300px'
},
{
type: 'input',
label: '邮箱',
prop: 'email',
placeholder: '请输入邮箱',
required: true,
inputType: 'email',
width: '300px'
},
{
type: 'input',
label: '电话',
prop: 'phone',
placeholder: '请输入电话',
required: true,
width: '300px'
}
]
};
// 表单引用
const slotFormRef = ref(null);
// 处理基本表单提交
const handleBasicSubmit = (data) => {
console.log('基本表单提交:', data);
alert('基本表单提交成功!');
};
// 处理基本表单重置
const handleBasicReset = () => {
console.log('基本表单重置');
};
// 处理行内表单提交
const handleInlineSubmit = (data) => {
console.log('行内表单提交:', data);
alert('搜索提交成功!');
};
// 处理行内表单重置
const handleInlineReset = () => {
console.log('行内表单重置');
};
// 处理禁用表单提交
const handleDisabledSubmit = (data) => {
console.log('禁用表单提交:', data);
alert('禁用表单提交成功!');
};
// 处理禁用表单重置
const handleDisabledReset = () => {
console.log('禁用表单重置');
};
// 处理水平浮动表单提交
const handleHorizontalSubmit = (data) => {
console.log('水平浮动表单提交:', data);
alert('表单提交成功!');
};
// 处理水平浮动表单重置
const handleHorizontalReset = () => {
console.log('水平浮动表单重置');
};
// 处理带插槽的表单提交
const handleSlotSubmit = (data) => {
console.log('带插槽的表单提交:', data);
alert('带插槽的表单提交成功!');
};
// 处理带插槽的表单重置
const handleSlotReset = () => {
console.log('带插槽的表单重置');
};
// 处理输入事件
const handleInput = (event) => {
console.log('输入事件:', event);
};
// 处理变更事件
const handleChange = (event) => {
console.log('变更事件:', event);
};
// 父组件校验表单
const validateForm = async () => {
if (slotFormRef.value) {
const isValid = await slotFormRef.value.validate();
if (isValid) {
alert('表单验证通过!');
} else {
alert('表单验证失败,请检查必填项!');
}
}
};
// 父组件重置表单
const resetForm = () => {
if (slotFormRef.value) {
slotFormRef.value.resetFields();
alert('表单已重置!');
}
};
</script>
<style scoped>
.app-container {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
font-family: Arial, sans-serif;
}
h1 {
text-align: center;
color: #303133;
margin-bottom: 40px;
}
h2 {
color: #409EFF;
margin-top: 40px;
margin-bottom: 20px;
border-bottom: 1px solid #e4e7ed;
padding-bottom: 10px;
}
.example-section {
background: #f5f7fa;
padding: 20px;
border-radius: 8px;
margin-bottom: 30px;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05);
}
.form-data {
background: #fff;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05);
}
.form-data pre {
background: #f0f2f5;
padding: 15px;
border-radius: 4px;
overflow-x: auto;
font-family: 'Courier New', Courier, monospace;
font-size: 14px;
}
.form-data h3 {
margin-top: 20px;
margin-bottom: 10px;
color: #606266;
}
.form-data h3:first-child {
margin-top: 0;
}
.form-slot-content {
display: flex;
align-items: center;
justify-content: space-between;
}
</style>11.2 CustomForm.vue 完整代码
<template>
<div class="custom-form" :style="{ width: formConfig.width, height: formConfig.height }">
<!-- 表单标题 -->
<div v-if="formConfig.title" class="form-title">{{ formConfig.title }}</div>
<!-- 表单容器 -->
<el-form
ref="formRef"
:model="formData"
:rules="formRules"
:label-position="formConfig.labelPosition"
:label-width="formConfig.labelWidth"
:inline="formConfig.inline"
:disabled="formConfig.disabled"
:size="formConfig.size"
@submit.prevent
>
<!-- 开始插槽 -->
<slot name="start" :formData="formData"></slot>
<!-- 表单字段容器 -->
<div class="form-fields-container">
<div
v-for="item in formConfig.fields"
:key="item.prop"
class="form-field-item"
:class="{ 'form-field-newline': item.newline }"
>
<el-form-item
:label="item.label"
:prop="item.prop"
:rules="item.rules"
:required="item.required"
:label-width="item.labelWidth"
>
<!-- 文本框 -->
<el-input
v-if="item.type === 'input'"
v-model="formData[item.prop]"
:placeholder="item.placeholder"
:type="item.inputType || 'text'"
:maxlength="item.maxlength"
:show-word-limit="item.showWordLimit"
:clearable="item.clearable"
:prefix-icon="item.prefixIcon"
:suffix-icon="item.suffixIcon"
:disabled="item.disabled"
:style="{ width: item.width }"
@input="handleInput"
@change="handleChange"
/>
<!-- 文本域 -->
<el-input
v-else-if="item.type === 'textarea'"
v-model="formData[item.prop]"
type="textarea"
:rows="item.rows || 2"
:placeholder="item.placeholder"
:maxlength="item.maxlength"
:show-word-limit="item.showWordLimit"
:disabled="item.disabled"
:style="{ width: item.width }"
@input="handleInput"
@change="handleChange"
/>
<!-- 数字输入框 -->
<el-input-number
v-else-if="item.type === 'number'"
v-model="formData[item.prop]"
:min="item.min"
:max="item.max"
:step="item.step"
:placeholder="item.placeholder"
:disabled="item.disabled"
:style="{ width: item.width }"
@input="handleInput"
@change="handleChange"
/>
<!-- 选择器 -->
<el-select
v-else-if="item.type === 'select'"
v-model="formData[item.prop]"
:placeholder="item.placeholder"
:disabled="item.disabled"
:multiple="item.multiple"
:clearable="item.clearable"
:style="{ width: item.width }"
@change="handleChange"
>
<el-option
v-for="option in item.options"
:key="option.value"
:label="option.label"
:value="option.value"
/>
</el-select>
<!-- 单选框组 -->
<el-radio-group
v-else-if="item.type === 'radio'"
v-model="formData[item.prop]"
:disabled="item.disabled"
@change="handleChange"
>
<el-radio
v-for="option in item.options"
:key="option.value"
:label="option.value"
>
{{ option.label }}
</el-radio>
</el-radio-group>
<!-- 复选框组 -->
<el-checkbox-group
v-else-if="item.type === 'checkbox'"
v-model="formData[item.prop]"
:disabled="item.disabled"
@change="handleChange"
>
<el-checkbox
v-for="option in item.options"
:key="option.value"
:label="option.value"
>
{{ option.label }}
</el-checkbox>
</el-checkbox-group>
<!-- 开关 -->
<el-switch
v-else-if="item.type === 'switch'"
v-model="formData[item.prop]"
:active-text="item.activeText"
:inactive-text="item.inactiveText"
:active-value="item.activeValue"
:inactive-value="item.inactiveValue"
:disabled="item.disabled"
@change="handleChange"
/>
<!-- 日期选择器 -->
<el-date-picker
v-else-if="item.type === 'date'"
v-model="formData[item.prop]"
type="date"
:placeholder="item.placeholder"
:format="item.format || 'YYYY-MM-DD'"
:value-format="item.valueFormat || 'YYYY-MM-DD'"
:disabled="item.disabled"
:style="{ width: item.width }"
@change="handleChange"
/>
<!-- 时间选择器 -->
<el-time-picker
v-else-if="item.type === 'time'"
v-model="formData[item.prop]"
:placeholder="item.placeholder"
:format="item.format || 'HH:mm:ss'"
:value-format="item.valueFormat || 'HH:mm:ss'"
:disabled="item.disabled"
:style="{ width: item.width }"
@change="handleChange"
/>
<!-- 日期时间选择器 -->
<el-date-picker
v-else-if="item.type === 'datetime'"
v-model="formData[item.prop]"
type="datetime"
:placeholder="item.placeholder"
:format="item.format || 'YYYY-MM-DD HH:mm:ss'"
:value-format="item.valueFormat || 'YYYY-MM-DD HH:mm:ss'"
:disabled="item.disabled"
:style="{ width: item.width }"
@change="handleChange"
/>
<!-- 纯文本显示 -->
<div
v-else-if="item.type === 'text'"
class="form-text"
:style="{ color: item.color, width: item.width }"
>
{{ formData[item.prop] || item.defaultValue }}
</div>
<!-- 插槽 -->
<slot
v-else-if="item.type === 'slot'"
:name="item.slotName || item.prop"
:formData="formData"
:field="item"
></slot>
</el-form-item>
</div>
</div>
<!-- 结束插槽 -->
<slot name="end" :formData="formData"></slot>
<!-- 表单操作按钮 -->
<div v-if="formConfig.showActions" class="form-actions">
<el-button
v-if="formConfig.showReset"
type="info"
@click="resetForm"
>
{{ formConfig.resetText || '重置' }}
</el-button>
<el-button
v-if="formConfig.showSubmit"
type="primary"
@click="submitForm"
>
{{ formConfig.submitText || '提交' }}
</el-button>
</div>
</el-form>
</div>
</template>
<script setup>
import { ref, reactive, computed, watch } from 'vue';
// 定义组件属性
const props = defineProps({
modelValue: {
type: Object,
required: true
},
formConfig: {
type: Object,
required: true
}
});
// 定义事件
const emit = defineEmits([
'update:modelValue',
'submit',
'reset',
'input',
'change'
]);
// 表单引用
const formRef = ref(null);
// 表单数据
const formData = reactive({ ...props.modelValue });
// 监听 modelValue 变化
watch(() => props.modelValue, (newValue) => {
Object.assign(formData, newValue);
}, { deep: true });
// 计算表单规则
const formRules = computed(() => {
const rules = {};
props.formConfig.fields.forEach(field => {
if (field.required && !field.rules) {
rules[field.prop] = [
{ required: true, message: `请输入${field.label}`, trigger: 'blur' }
];
} else if (field.rules) {
rules[field.prop] = field.rules;
}
});
return rules;
});
// 提交表单
const submitForm = async () => {
if (!formRef.value) return;
try {
await formRef.value.validate();
emit('update:modelValue', { ...formData });
emit('submit', { ...formData });
} catch (error) {
console.error('表单验证失败:', error);
}
};
// 重置表单
const resetForm = () => {
if (formRef.value) {
formRef.value.resetFields();
emit('reset');
}
};
// 处理输入事件
const handleInput = (value) => {
emit('input', { value, field: event.target.name });
};
// 处理变更事件
const handleChange = (value) => {
emit('change', { value, field: event.target.name });
};
// 暴露方法给父组件
defineExpose({
submitForm,
resetForm,
validate: async () => {
if (!formRef.value) return false;
try {
await formRef.value.validate();
return true;
} catch (error) {
return false;
}
},
resetFields: () => {
if (formRef.value) {
formRef.value.resetFields();
}
}
});
</script>
<style scoped>
.custom-form {
background: #fff;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05);
}
.form-title {
font-size: 18px;
font-weight: bold;
margin-bottom: 20px;
color: #303133;
}
/* 表单字段容器 - 水平浮动布局 */
.form-fields-container {
display: flex;
flex-wrap: wrap;
gap: 16px;
margin-bottom: 20px;
}
/* 表单字段项 */
.form-field-item {
display: flex;
align-items: center;
}
/* 强制换行样式 */
.form-field-newline {
break-before: always;
margin-top: 16px;
}
/* 表单操作按钮 */
.form-actions {
display: flex;
gap: 10px;
margin-top: 20px;
}
/* 纯文本显示样式 */
.form-text {
color: #606266;
}
/* 响应式样式 */
@media (max-width: 768px) {
.form-fields-container {
flex-direction: column;
gap: 0;
}
.form-field-item {
width: 100%;
margin-bottom: 16px;
}
.form-actions {
flex-direction: column;
}
.form-actions .el-button {
width: 100%;
}
}
</style>12. 总结
CustomForm 组件是一个功能强大、配置灵活的表单组件,通过简单的配置即可快速创建各种类型的表单。它支持多种控件类型、水平浮动布局、插槽功能、表单验证等特性,大大简化了表单的开发流程。
通过本文档的详细说明,您应该已经了解了 CustomForm 组件的基本用法、配置选项、事件处理和插槽功能。您可以根据实际需求对组件进行定制和扩展,以满足项目的特定要求。
