一、功能点
(一)组件功能点
- 动态表单生成
根据formItems
配置动态生成表单项,支持多种类型(如input
、radio
、select
)。 - 表单校验
通过rules
定义表单校验规则,调用validate
方法触发校验。 - 双向绑定
表单项通过v-model
绑定到form
对象,实现数据同步。 - 支持扩展性
支持通过formItems
扩展表单项类型,例如checkbox
、switch
等。
(二)核心改进点(2024/1/6日)
-
动态绑定属性
- 使用
v-bind="item.props"
动态绑定组件的props
,避免每个组件手动声明属性。
- 使用
-
动态插槽支持
- 利用
item.slots
支持动态插槽内容,通过v-for
和v-slot:[slotName]
渲染自定义插槽。
- 利用
-
统一组件类型
- 使用
<component :is="item.component">
动态渲染不同类型的表单组件。
- 使用
(二)优点
-
简化代码
减少了重复性代码,表单逻辑更加集中化,易于维护。 -
更高的扩展性
支持任意表单组件类型的扩展,只需在formItems
中添加对应的component
和props
。 -
动态插槽灵活
插槽配置更加灵活,能够适应复杂的表单布局和自定义需求。
(二)父组件功能点
- 表单调用与交互
父组件通过ref
引用子组件的方法(如validate
)。 - 数据提交
提供提交按钮,调用后端接口完成数据更新并进行状态提示(成功/失败)。 - 提交按钮加载状态
在提交过程中设置按钮loading
状态,防止重复点击。
二、知识点
(一)Vue 3 特性
-
defineProps
和defineExpose
- 通过
defineProps
接收父组件传递的属性,包括form
、formItems
、rules
等。 - 使用
defineExpose
暴露子组件方法(如formRef
),供父组件调用。
- 通过
-
ref
和reactive
- 使用
ref
定义可响应的表单引用formRef
。 - 表单数据通过响应式对象绑定到
form
。
- 使用
-
插槽与动态模板
component :is
,slot
- 使用动态模板渲染不同类型的表单组件(如
input
、radio
、select
)。 - 通过
v-for
循环生成表单项。component :is
语法配合template
slot
动态插槽
- 使用动态模板渲染不同类型的表单组件(如
(二)Element Plus 使用
-
el-form
与el-form-item
- 使用
el-form
作为表单容器,配置size
和rules
等属性。 el-form-item
绑定标签和校验规则,展示动态生成的表单项。
- 使用
-
表单组件
el-input
:文本输入框。el-radio-group
和el-radio
:单选框组。el-select
和el-option
:下拉选择框。
-
表单校验
- 使用
validate
方法触发校验,提供valid
回调参数判断校验状态。
- 使用
(三)其他知识点
-
动态表单生成
- 利用 JSON 数据(
formItems
)描述表单结构,方便配置化生成表单。
- 利用 JSON 数据(
-
异步提交与状态提示
- 通过
then
和catch
处理异步请求的成功或失败状态,并显示相应的提示信息。
- 通过
-
数字格式处理
- 提交前通过
Number()
转换字段类型(如RATIO
字段)为数值,确保数据正确性。
- 提交前通过
改进后的代码通过 v-bind
动态传递 form-item
中不同组件的 props
,避免重复声明,从而使代码更加简洁和易于维护。以下是优化后的代码和功能点说明:
三、源码
(一)组件代码
<script setup>
import { ref } from 'vue';
defineOptions({
name: 'MyForm',
});
// eslint-disable-next-line no-unused-vars, unused-imports/no-unused-vars
const props = defineProps({
form: {
type: Object,
required: true,
default: () => ({}),
},
formLabelWidth: {
type: String,
default: '50px',
},
// 表单配置项
formItems: {
type: Array,
required: true,
},
rules: {
type: Object,
required: true,
},
});
const formRef = ref(null);
// 暴露 formRef
defineExpose({
formRef,
});
</script>
<template>
<div>
<el-form
ref="formRef"
:model="form"
:rules="rules"
size="small"
style="margin-top: 0"
>
<el-form-item
v-for="(item, index) in formItems"
:key="index"
:label="item.label"
:label-width="formLabelWidth"
:prop="item.prop"
>
<component
:is="item.component"
v-model="form[item.prop]"
v-bind="item.props"
>
<!-- 动态渲染插槽内容 -->
<template
v-for="(slotItem, slotIndex) in item.slots?.default || []"
:key="slotIndex"
>
<component :is="slotItem.component" v-bind="slotItem.props" />
</template>
</component>
</el-form-item>
</el-form>
</div>
</template>
<style scoped></style>
(二)父组件调用
<script setup>
import { ref } from 'vue';
import MyForm from '../../components/self-components/element_ui_plus/form.vue';
const formItems = [
{
label: '用户名',
prop: 'username',
component: 'el-input',
props: {
placeholder: '请输入用户名',
},
},
{
label: '性别',
prop: 'gender',
component: 'el-radio-group',
props: {},
slots: {
default: [
{
component: 'el-radio',
props: { value: 'male', label: '男' },
},
{
component: 'el-radio',
props: { value: 'female', label: '女' },
},
],
},
},
{
label: '国家',
prop: 'country',
component: 'el-select',
props: {
placeholder: '请选择国家',
},
slots: {
default: [
{
component: 'el-option',
props: { label: '中国', value: 'China' },
},
{
component: 'el-option',
props: { label: '美国', value: 'USA' },
},
],
},
},
];
const editForm = ref({
username: '',
gender: '',
country: '',
});
const rules = ref({});
const submitEdit = () => {};
</script>
<template>
<MyForm
ref="editFormRef"
:form="editForm"
:form-items="formItems"
:rules="rules"
/>
<el-button
:loading="false"
size="small"
type="primary"
@click="submitEdit(editFormRef)"
>
确 定
</el-button>
</template>
四、注意点
(1) 使用自动按需导入是无法使用的,需要全局注册对应elementUiPlus组件使用
import {
ElInput,
ElOption,
ElRadio,
ElRadioGroup,
ElSelect,
} from 'element-plus';
// 在main.ts/js组件中注册
app.use(ElInput).use(ElRadioGroup).use(ElSelect).use(ElOption).use(ElRadio);