vue3封装element-plus的Form表单
# 新建 Form.vue
src/components/Form.vue
<script setup lang="ts">
import { ref, reactive, useSlots, nextTick } from "vue";
import type { FormInstance, FormRules } from 'element-plus';
import { Key, Plus } from '@element-plus/icons-vue';
// 【接口】通用设置key
interface IKey {
[key: string]: any;
}
// 【接口】form表单模型
interface IFormModel {
label: string;
key: string;
type: string;
rule?: any;
placeholder?: string;
disabled?: boolean;
options?: any;
props?: any;
}
// 【接口】接受传参字段
interface IProps {
formModels: IFormModel[];
ruleForm: IKey;
showSubmitBtn?: boolean;
submitText?: string;
showResetBtn?: boolean;
}
// 初始化默认参数
const props = withDefaults(defineProps<IProps>(), {
showSubmitBtn: true,
submitText: '提交',
showResetBtn: true,
});
// 获取“占位符”
const getPlaceholder = (type: string, label: string): string => {
if (type === ('input' || 'number' || 'tag')) {
return '请输入' + label;
}
if (type === 'upload') {
return '请上传' + label;
}
return '请选择' + label;
};
// form表单校验规则
const rules = reactive<FormRules>({});
props.formModels.forEach(formModel => {
if (!formModel.placeholder) {
formModel.placeholder = getPlaceholder(formModel.type, formModel.label);
}
if (formModel.rule) {
if ((typeof formModel.rule) === 'boolean') {
rules[formModel.key] = [{
required: true,
message: formModel.placeholder,
trigger: 'blur',
}];
} else {
rules[formModel.key] = formModel.rule;
}
}
});
// 插槽对象
const slots = useSlots();
// 表单ref
const ruleFormRef = ref<FormInstance>();
// 校验
const validate = () => {
return ruleFormRef.value?.validate();
};
// 清除验证
const clearValidate = (): void => {
ruleFormRef.value?.clearValidate();
};
// 重置表单
const resetForm = (): void => {
ruleFormRef.value?.resetFields();
};
// 事件
const emit = defineEmits<{
(e: "submitForm", ruleForm: IKey): void;
}>();
// 提交表单
const submitForm = async (): Promise<void> => {
await validate();
emit('submitForm', props.ruleForm);
};
// 设置form表单值
const setRuleForm = (newRuleForm: IKey): void => {
Object.assign(props.ruleForm, newRuleForm);
setTimeout(() => {
clearValidate();
}, 30);
}
// 清空form表单值
const resetRuleForm = (): void => {
const oldRuleForm = Object.assign({}, props.ruleForm);
for (const key in oldRuleForm) {
delete props.ruleForm[key];
}
setTimeout(() => {
clearValidate();
}, 30);
}
// 暴露函数
defineExpose({
validate,
clearValidate,
resetForm,
setRuleForm,
resetRuleForm,
});
interface IDialog {
visible: boolean;
imageUrl: string;
};
const dialog = reactive<IDialog>({
visible: false,
imageUrl: '',
});
const handlePictureCardPreview = (uploadFile: any) => {
dialog.imageUrl = uploadFile.url!
dialog.visible = true
};
</script>
<template>
<!-- 表单 -->
<el-form :model="props.ruleForm" :rules="rules" ref="ruleFormRef" label-width="120px">
<el-form-item :label="formModel.label" v-for="formModel in props.formModels" :prop="formModel.key">
<!-- 预留自定义表单 -->
<slot v-if="slots[formModel.key]" :name="formModel.key" v-bind="{ key: props.ruleForm[formModel.key] }"></slot>
<!-- 输入框 -->
<el-input v-else-if="formModel.type === 'input'" v-model="props.ruleForm[formModel.key]"
:placeholder="formModel.placeholder" :disabled="formModel.disabled" clearable />
<!-- 密码框 -->
<el-input v-else-if="formModel.type === 'password'" v-model="props.ruleForm[formModel.key]" type="password"
:placeholder="formModel.placeholder" :disabled="formModel.disabled" show-password clearable />
<!-- 文本框 -->
<el-input v-else-if="formModel.type === 'textarea'" v-model="props.ruleForm[formModel.key]"
:autosize="{ minRows: 4, maxRows: 6 }" type="textarea" :placeholder="formModel.placeholder"
:disabled="formModel.disabled" clearable />
<!-- 数字输入框 -->
<el-input-number v-else-if="formModel.type === 'number'" v-model="props.ruleForm[formModel.key]"
:disabled="formModel.disabled" :precision="formModel.props?.precision ?? 2" />
<!-- 单选 -->
<el-radio-group v-else-if="formModel.type === 'radio'" v-model="props.ruleForm[formModel.key]">
<el-radio v-for="item in formModel?.options" :key="item.key" :label="item.value">{{ item.label }}</el-radio>
</el-radio-group>
<!-- 多选框 -->
<el-checkbox-group v-else-if="formModel.type === 'checkbox'" v-model="props.ruleForm[formModel.key]">
<el-checkbox v-for="item in formModel?.options" :key="item.key" :label="item.value">
{{ item.label }}
</el-checkbox>
</el-checkbox-group>
<!-- 下拉单选 -->
<el-select v-else-if="formModel.type === 'select'" v-model="props.ruleForm[formModel.key]"
:placeholder="formModel.placeholder" :disabled="formModel.disabled">
<el-option v-for="item in formModel?.options" :key="item.key" :label="item.label" :value="item.value" />
</el-select>
<!-- 联级单选 -->
<el-cascader v-else-if="formModel.type === 'cascader'" v-model="props.ruleForm[formModel.key]"
:options="formModel?.options" :props="formModel?.props" :placeholder="formModel.placeholder"
:disabled="formModel.disabled" />
<!-- 开关 -->
<el-switch v-else-if="formModel.type === 'switch'" v-model="props.ruleForm[formModel.key]"
:disabled="formModel.disabled" />
<!-- 时间 -->
<el-time-picker v-else-if="formModel.type === 'time'" v-model="props.ruleForm[formModel.key]"
:placeholder="formModel.placeholder" :disabled="formModel.disabled" />
<!-- 时间 to 时间 -->
<el-time-picker v-else-if="formModel.type === 'time-to-time'" v-model="props.ruleForm[formModel.key]" is-range
range-separator="至" start-placeholder="开始日期" end-placeholder="结束日期" :disabled="formModel.disabled" />
<!-- 日期 -->
<el-date-picker v-else-if="formModel.type === 'date'" v-model="props.ruleForm[formModel.key]" type="date"
:placeholder="formModel.placeholder" :disabled="formModel.disabled" />
<!-- 日期 to 日期 -->
<el-date-picker v-else-if="formModel.type === 'date-to-date'" v-model="props.ruleForm[formModel.key]"
:placeholder="formModel.placeholder" type="daterange" range-separator="至" start-placeholder="开始日期"
end-placeholder="结束日期" :disabled="formModel.disabled" />
<!-- 日期时间 -->
<el-date-picker v-else-if="formModel.type === 'datetime'" v-model="props.ruleForm[formModel.key]" type="datetime"
:placeholder="formModel.placeholder" :disabled="formModel.disabled" />
<!-- 日期时间 to 日期时间 -->
<el-date-picker v-else-if="formModel.type === 'datetime-to-datetime'" v-model="props.ruleForm[formModel.key]"
type="datetimerange" range-separator="至" start-placeholder="开始日期" end-placeholder="结束日期"
:disabled="formModel.disabled" />
<!-- 文件上传 -->
<el-upload v-else-if="formModel.type === 'upload'" v-model:file-list="props.ruleForm[formModel.key]"
:action="formModel?.props?.action" :limit="formModel?.props?.limit || 1" list-type="picture-card"
:on-preview="handlePictureCardPreview" :disabled="formModel.disabled"
:class="{'el-upload--none': props.ruleForm[formModel.key].length === formModel?.props?.limit}">
<el-icon>
<Plus />
</el-icon>
</el-upload>
<!-- 文件上传--mini -->
<el-upload v-else-if="formModel.type === 'upload-mini'" v-model:file-list="props.ruleForm[formModel.key]"
:action="formModel?.props?.action" :limit="formModel?.props?.limit || 1" list-type="picture-card"
:on-preview="handlePictureCardPreview" :disabled="formModel.disabled" class="upload-mini"
:class="{'el-upload--none': props.ruleForm[formModel.key].length === formModel?.props?.limit}">
<el-icon>
<Plus />
</el-icon>
</el-upload>
</el-form-item>
<el-form-item class="form-operations">
<!-- 预留自定义区域 -->
<slot name="operations"></slot>
<!-- 默认【提交】按钮和【重置】按钮 -->
<el-button type="primary" @click="submitForm" v-if="showSubmitBtn">{{ submitText }}</el-button>
<el-button @click="resetForm" v-if="showResetBtn">重置</el-button>
</el-form-item>
<el-dialog v-model="dialog.visible">
<img w-full :src="dialog.imageUrl" alt="Preview Image" />
</el-dialog>
</el-form>
</template>
<style scoped>
:deep(.el-upload-list__item .el-icon--close-tip) {
display: none !important;
}
:deep(.upload-mini .el-upload--picture-card) {
width: 80px;
height: 80px;
}
:deep(.upload-mini .el-upload-list__item) {
width: 80px;
height: 80px;
}
:deep(.el-upload--none .el-upload--picture-card) {
display: none;
}
:deep(.el-form-item .el-form-item) {
margin-bottom: 18px;
}
</style>
# 参数说明
属性 | 描述 | 默认值 | 必需 |
---|
formModels | 表单配置 | [] | 是 |
ruleForm | form表单 | {} | 是 |
label-width | 表单表头宽度 | 120px | 否 |
inline | 是否开启行内表单 | false | 否 |
size | 控件大小 | default | 否 |
showSubmitBtn | 是否显示【提交】按钮 | true | 否 |
submitText | 【提交】按钮“文字” | 提交 | 否 |
showResetBtn | 是否显示【重置】按钮 | true | 否 |
# formModels
属性 | 描述 | 必需 |
---|
label | 表单表头 | 是 |
key | 表单对应的字段 | 是 |
type | 控件类型 | 是 |
rules | 是否开启验证 | 否 |
placeholder | 自定义提示 | 否 |
disabled | 是否禁用 | 否 |
options | 控件配置(用于单选、联级单选) | 否 |
props | 控件配置(用于联级单选) | 否 |
# type
属性 | 描述 |
---|
input | 输入框 |
password | 密码框 |
textarea | 文本框 |
number | 数字输入框 |
radio | 单选 |
checkbox | 多选框 |
select | 下拉单选 |
cascader | 联级单选 |
switch | 开关 |
time | 时间 |
time-to-time | 时间 to 时间 |
date | 日期 |
date-to-date | 日期 to 日期 |
datetime | 日期时间 |
datetime-to-datetime | 日期时间 to 日期时间 |
upload | 上传图片 |
upload-mini | 上传图片 |
# 事件
事件 | 描述 | 返回值 |
---|
@submitForm | 点击【提交】按钮事件 | form值 |
# 使用
src/views/home.vue
<script setup lang="ts">
import { ref, reactive, onMounted } from 'vue';
import Form from '../components/Form.vue';
const form = reactive({
formModels: [
{
label: '输入框',
key: 'input',
type: 'input',
rule: true,
},
{
label: '下拉选择',
key: 'select',
type: 'select',
value: 1,
disabled: false,
options: [
{
key: 1,
label: "女",
value: 0
},
{
key: 2,
label: "男",
value: 1
}
]
},
{
label: '开关',
key: 'switch',
type: 'switch',
},
{
label: '时间',
key: 'time',
type: 'time',
},
{
label: '时间',
key: 'time2',
type: 'time-to-time',
},
{
label: '联级单选',
key: 'cascader',
type: 'cascader',
value: 0,
options: [
{
value: 'disciplines',
label: 'Disciplines',
children: [
{
value: 'consistency',
label: 'Consistency',
children: [
{
value: 'consistency',
label: 'Consistency',
},
{
value: 'feedback',
label: 'Feedback',
},
],
},
{
value: 'feedback',
label: 'Feedback',
},
],
},
{
value: 'navigation',
label: 'Navigation',
children: [
{
value: 'side nav',
label: 'Side Navigation',
},
{
value: 'top nav',
label: 'Top Navigation',
},
],
},
],
},
{
label: '日期',
key: 'date',
type: 'date',
},
{
label: '日期2',
key: 'date2',
type: 'date-to-date',
},
{
label: '日期时间',
key: 'datetime',
type: 'datetime',
},
{
label: '日期时间2',
key: 'datetime2',
type: 'datetime-to-datetime',
},
{
label: '图片上传',
key: 'images',
type: 'upload',
rule: true,
props: {
action: '上传图片接口',
limit: 1,
}
},
],
ruleForm: {
images: []
}
});
const submitForm = (data: any) => {
console.log('提交按钮事件 =>', data);
}
const myFormRef = ref();
onMounted(() => {
// 调用子组件“resetRuleForm”函数
// myFormRef.value.resetRuleForm();
});
</script>
<template>
<p>================================</p>
<Form :formModels="form.formModels" :ruleForm="form.ruleForm" @submitForm="submitForm" ref="myFormRef"></Form>
<p>================================</p>
</template>
<style scoped>
</style>
# 效果图
![image-20220820161002986](https://i-blog.csdnimg.cn/blog_migrate/2b0be496122d07668b1cdbc01bd8dd37.png)