Skip to content
vue3+elementplus表单组件封装

CustomForm 组件使用文档

1. 组件介绍

CustomForm 是一个基于 Element Plus 封装的表单组件,提供了丰富的表单控件类型和配置选项,支持水平浮动布局、插槽功能、表单验证等特性,大大简化了表单的开发流程。

主要特性

  • 支持多种表单控件类型(输入框、选择器、复选框、开关等)
  • 支持水平浮动布局,超出自动换行
  • 支持通过 newline 属性强制换行
  • 支持 startend 插槽,可在表单前后添加自定义内容
  • 支持字段级别的插槽,可添加自定义内容
  • 支持表单验证,可通过 required 属性或自定义规则
  • 支持表单提交、重置等操作
  • 支持父组件调用子组件的方法进行表单操作

2. 组件安装和引入

安装依赖

确保项目中已安装 Element Plus:

bash
npm install element-plus --save

引入组件

在需要使用的文件中引入 CustomForm 组件:

vue
<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 基本表单示例

vue
<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 行内表单示例

vue
<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 禁用表单示例

vue
<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 水平浮动表单示例

vue
<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 带插槽的表单示例

vue
<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 表单基本配置

配置项类型默认值说明
titleString-表单标题
widthString-表单宽度
heightStringauto表单高度
labelPositionStringright标签位置,可选值:left、right、top
labelWidthString-标签宽度
inlineBooleanfalse是否为行内表单
disabledBooleanfalse是否禁用整个表单
sizeStringdefault表单大小,可选值:large、default、small
showActionsBooleantrue是否显示操作按钮
showResetBooleantrue是否显示重置按钮
showSubmitBooleantrue是否显示提交按钮
resetTextString重置重置按钮文本
submitTextString提交提交按钮文本
fieldsArray[]表单字段配置

4.2 表单字段配置

配置项类型默认值说明适用控件类型
typeString-控件类型:input、textarea、number、select、radio、checkbox、switch、date、time、datetime、text、slot所有
labelString-字段标签所有
propString-字段属性名所有
placeholderString-占位符input、textarea、number、select、date、time、datetime
requiredBooleanfalse是否必填所有
rulesArray-验证规则所有
widthString-控件宽度input、textarea、number、select、date、time、datetime、text
labelWidthString-字段标签宽度(优先级高于表单级别的labelWidth)所有
disabledBooleanfalse是否禁用所有
newlineBooleanfalse是否强制换行所有
maxlengthNumber-最大输入长度input、textarea
showWordLimitBooleanfalse是否显示字数统计input、textarea
clearableBooleanfalse是否可清空input、select
prefixIconString-前缀图标input
suffixIconString-后缀图标input
inputTypeStringtext输入类型input
rowsNumber2文本域行数textarea
minNumber-最小值number
maxNumber-最大值number
stepNumber1步长number
multipleBooleanfalse是否多选select
optionsArray[]选项列表,格式:[{ label: '标签', value: '值' }]select、radio、checkbox
activeTextString-开关激活文本switch
inactiveTextString-开关非激活文本switch
activeValueBoolean/Number/Stringtrue开关激活值switch
inactiveValueBoolean/Number/Stringfalse开关非激活值switch
formatStringYYYY-MM-DD / HH:mm:ss / YYYY-MM-DD HH:mm:ss显示格式date、time、datetime
valueFormatStringYYYY-MM-DD / HH:mm:ss / YYYY-MM-DD HH:mm:ss值格式date、time、datetime
defaultValueString-默认值text
colorString#606266文本颜色text
slotNameString-插槽名称(若未指定则使用prop作为插槽名称)slot

5. 控件类型详解

5.1 输入框 (input)

javascript
{
  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)

javascript
{
  type: 'textarea',
  label: '地址',
  prop: 'address',
  placeholder: '请输入地址',
  rows: 3,
  maxlength: 100,
  showWordLimit: true,
  width: '300px'
}

5.3 数字输入框 (number)

javascript
{
  type: 'number',
  label: '年龄',
  prop: 'age',
  placeholder: '请输入年龄',
  min: 0,
  max: 150,
  step: 1,
  width: '120px'
}

5.4 选择器 (select)

javascript
{
  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)

javascript
{
  type: 'radio',
  label: '性别',
  prop: 'gender',
  options: [
    { label: '男', value: 'male' },
    { label: '女', value: 'female' }
  ]
}

5.6 复选框组 (checkbox)

javascript
{
  type: 'checkbox',
  label: '爱好',
  prop: 'hobbies',
  options: [
    { label: '阅读', value: 'reading' },
    { label: '运动', value: 'sports' },
    { label: '音乐', value: 'music' },
    { label: '旅行', value: 'travel' }
  ]
}

5.7 开关 (switch)

javascript
{
  type: 'switch',
  label: '状态',
  prop: 'status',
  activeText: '启用',
  inactiveText: '禁用',
  activeValue: true,
  inactiveValue: false
}

5.8 日期选择器 (date)

javascript
{
  type: 'date',
  label: '出生日期',
  prop: 'birthdate',
  placeholder: '请选择出生日期',
  format: 'YYYY-MM-DD',
  valueFormat: 'YYYY-MM-DD',
  width: '200px'
}

5.9 时间选择器 (time)

javascript
{
  type: 'time',
  label: '时间',
  prop: 'time',
  placeholder: '请选择时间',
  format: 'HH:mm:ss',
  valueFormat: 'HH:mm:ss',
  width: '200px'
}

5.10 日期时间选择器 (datetime)

javascript
{
  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)

javascript
{
  type: 'text',
  label: '订单来源',
  prop: 'orderSource',
  defaultValue: '爱儿心选服务订单',
  width: '200px',
  color: '#409EFF'
}

5.12 插槽 (slot)

javascript
{
  label: '自定义内容',
  prop: 'customContent',
  type: 'slot',
  slotName: 'customContent', // 插槽名称,若未指定则使用prop作为插槽名称
  width: '300px'
}

6. 插槽功能

6.1 表单级插槽

start 插槽

位于表单字段最前面,可用于添加表单说明、自定义头部等内容。

vue
<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 插槽

位于表单字段最后面,可用于添加表单说明、自定义尾部等内容。

vue
<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' 配置的字段,可以在组件内添加自定义内容。

vue
<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)

当点击提交按钮且表单验证通过时触发,参数为表单数据。

javascript
const handleSubmit = (data) => {
  console.log('表单提交:', data);
  // 处理表单提交逻辑
};

7.2 重置事件 (reset)

当点击重置按钮时触发。

javascript
const handleReset = () => {
  console.log('表单重置');
  // 处理表单重置逻辑
};

7.3 输入事件 (input)

当输入框输入内容时触发。

javascript
const handleInput = (event) => {
  console.log('输入事件:', event);
  // 处理输入事件逻辑
};

7.4 变更事件 (change)

当表单控件值发生变化时触发。

javascript
const handleChange = (event) => {
  console.log('变更事件:', event);
  // 处理变更事件逻辑
};

8. 方法调用

通过 ref 引用 CustomForm 组件,可以调用以下方法:

8.1 validate()

验证表单,返回 Promise,resolve 为布尔值表示校验是否通过。

javascript
const slotFormRef = ref(null);

const validateForm = async () => {
  if (slotFormRef.value) {
    const isValid = await slotFormRef.value.validate();
    if (isValid) {
      alert('表单验证通过!');
    } else {
      alert('表单验证失败,请检查必填项!');
    }
  }
};

8.2 resetFields()

重置表单字段。

javascript
const resetForm = () => {
  if (slotFormRef.value) {
    slotFormRef.value.resetFields();
    alert('表单已重置!');
  }
};

8.3 submitForm()

提交表单,触发 submit 事件。

javascript
const submitForm = () => {
  if (slotFormRef.value) {
    slotFormRef.value.submitForm();
  }
};

8.4 resetForm()

重置表单,触发 reset 事件。

javascript
const resetForm = () => {
  if (slotFormRef.value) {
    slotFormRef.value.resetForm();
  }
};

9. 响应式设计

CustomForm 组件支持响应式布局,在屏幕宽度小于等于 768px 时,表单字段会自动垂直排列,操作按钮也会垂直排列。

10. 注意事项

  1. 表单数据绑定:使用 v-model 绑定表单数据,组件内部会自动同步数据变化。

  2. 表单验证:可以通过 required 属性设置必填项,也可以通过 rules 属性设置自定义验证规则。

  3. 水平浮动布局:当 inlinefalse 时,表单字段会水平浮动,超出容器宽度时自动换行。可以通过 newline: true 强制字段换行。

  4. 插槽使用:可以通过 startend 插槽在表单前后添加自定义内容,也可以通过字段级插槽添加自定义控件。

  5. 方法调用:通过 ref 引用组件后,可以调用 validate()resetFields() 等方法进行表单操作。

  6. 事件处理:可以监听 submitresetinputchange 等事件处理表单交互。

11. 完整示例代码

11.1 App.vue 完整代码

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 完整代码

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 组件的基本用法、配置选项、事件处理和插槽功能。您可以根据实际需求对组件进行定制和扩展,以满足项目的特定要求。