🙏前言
还在为表单太多而烦恼吗,想要统一修改表单的某一个属性而烦恼吗,想要轻松维护表单?
😇根据element-plus表单进行二次封装
因为表单由各种各样的表单项组合而成的,所以表单项的封装也必不可少,我这里表单项是通过表单类型配置去处理的
以下代码是通过element-plus + vue3 + ts封装的
- types.ts
/**
* 表单配置对象类型
*/
export type FormObjType = {
[key: string]: FormObjItemType
};
/**
* 表单项类型
*/
export type FormObjItemType = {
/**
* 文本
* @default ''
* @type String
*/
label?: String;
/**
* 组件类型
* @default undefined
* @type ComponentTypes
*/
type?: ComponentTypes;
/**
* 选项数组
* @default undefined
* @type optionsType[]
*/
options?: optionsType[];
}
表单项的组件类型我目前只加了几个类型,类型可以根据需求扩展
/**
* 组件类型
*/
export type ComponentTypes = 'input' | 'select'
那有同学就有疑问了,那表单输出的参数值怎么存储,在这里我是通过配置的value字段去存储的
/**
* 表单项类型
*/
export type FormObjItemType = {
/**
* 文本
* @default ''
* @type String
*/
label?: String;
/**
* 组件类型
* @default undefined
* @type ComponentTypes
*/
type?: ComponentTypes;
/**
* 选项数组
* @default undefined
* @type optionsType[]
*/
options?: optionsType[];
/**
* 值
* @default null
* @type any
*/
value?: any;
}
例如select类型的选项的属性是根据以下对象去处理的
/**
* 选项类型
*/
export type optionsType = {
/**
* 文本
* @type String | Number
*/
label: String | Number
/**
* 值
* @type String | Number
*/
value: String | Number | Boolean
/**
* 是否禁用
* @type Boolean
*/
disabled?: Boolean
}
🌰以下封装表单组件引入实现示例
<template>
<div>
<base-form
:form-data="formConfig"
:submit-request="onRequest"
@cancel="onCancel"
@confirm="onConfirm"
/>
</div>
</template>
<script lang="ts">
import { FormObjType } from '@/components/ReForm/types';
import { BaseForm } from '@/components/ReForm';
export default defineComponent({
components: {
BaseForm
},
setup() {
const formConfig: FormObjType = {
name: {
label: '名称',
type: 'input',
value: '',
},
project: {
label: '项目',
type: 'select',
options: [
{
label: '项目1',
value: 1,
},
{
label: '项目2',
value: 2,
},
{
label: '项目3',
value: 3,
}
]
value: null
}
}
/**
* 异步提交操作
* @params params 这个参数是提交表单处理后的数据
*/
function onRequest(params:Object) {
// 如果要做异步操作就return出去一个异步方法
return submitApi(params);
}
/** 取消表单事件 */
function onCancel() {
console.log('取消成功回调');
}
/** 表单提交成功之后回调 */
function onConfirm() {
console.log('确定提交成功回调');
}
return {
formConfig,
onRequest,
onCancel,
onConfirm
}
}
})
</script>
onRequest方法是用于异步提交的一个方法,接收到的参数是表单组件提交处理成key和值组成的数据,当然你如果只是需要拿到参数不请求接口直接拿走参数即可
{
name: '我的名字',
project: 1
}
这样就只需要维护表单配置里面的选项就行,不管是统一新增一个属性还行根据某个表单项进行限制,只修改修改该表单选的默认配置项就行
const formConfig: FormObjType = {
name: {
label: '名称',
type: 'input',
value: '',
formItemProps: {
required: true // 表单项设置为必选
}
props: {
type: 'textarea', // 输入框改为多行输入框
},
on: {
change(value: string) {
console.log(value);
},
},
children: {
}
},
}
formItemProps:可以设置el-form-item里面所有的属性,例如修改验证规则和是否必选props:是可以设置type类型里面对应的组件属性on:是可以设置type类型里面对应的组件的事件处理
⁉️如果提交获取到的参数是对象呢,可以用children进行嵌套
const formConfig: FormObjType = {
extra: {
value: {},
children: {
extra1: {
label: '额外参数1',
type: 'input',
value: ''
},
extra2: {
label: '额外参数2',
type: 'input',
value: ''
}
}
}
}
提交输出格式如下:
{
extra: {
extra1: '',
extra2: '',
}
}
🦩完整表单组件代码参考
- BaseForm.vue
<template>
<div>
<!-- 表单 -->
<el-scrollbar
:max-height="maxHeight"
>
<el-form
ref="ruleFormDom"
:model="formData"
status-icon
style="padding-bottom: 10px;"
v-bind="formProps?formProps:{}"
@submit.prevent="confirm"
>
<base-form-item
:form-data="formData"
:default-label-width="defaultLabelWidth"
/>
</el-form>
</el-scrollbar>
<!-- 是否显示按钮 -->
<div
v-if="isShowButton"
class="form-control"
>
<el-button @click="cancel">
取消
</el-button>
<el-button
type="primary"
:loading="isLoading"
@click="confirm"
>
确定
</el-button>
</div>
</div>
</template>
<script lang="ts">
import { defineComponent, ref, toRefs } from 'vue';
import { ElButton, ElForm, ElMessage } from 'element-plus';
import BaseFormItem from './BaseFormItem.vue';
import { FormObjType, FormParamsType } from '../types';
export default defineComponent({
components: {
BaseFormItem,
ElForm,
ElButton
},
props: {
// 表单最大高度
maxHeight: {
type: [Number, String],
default: '100%'
},
// 表单数据
formData: {
type: Object,
required: true,
},
// 是否显示按钮
isShowButton: {
type: Boolean,
required: false,
default: true
},
// 表单属性
formProps: {
type: Object,
required: false,
default: ()=> {
return {};
}
},
// 默认文本宽度
defaultLabelWidth: {
type: Number,
required: false,
default: 80
},
// 提交请求
submitRequest: {
type: Function,
default: ()=>{
return Promise.resolve();
}
}
},
emits: ['cancel', 'confirm'],
setup(props, { emit }) {
const { formData } = toRefs(props);
const ruleFormDom = ref(null as any);
/** 取消 */
function cancel() {
ruleFormDom.value.resetFields();
emit('cancel', false);
}
/** 确定 */
async function confirm() {
let is_succeed = false;
// 表单验证
await ruleFormDom.value.validate((valid:Boolean, formRuleList:Object) => {
is_succeed = ruleFormTips(valid, formRuleList);
});
if (!is_succeed){
return;
}
const formParams = paramsFormat(formData.value);
if (formParams !== null) {
submitData(formParams);
}
}
/**
* 参数格式化
* @param obj 参数配置对象
*/
function paramsFormat(obj:Object):FormParamsType | null {
let formParams:FormParamsType = {};
for (const key in obj) {
if (obj[key]) {
const item:FormObjType = obj[key];
const paramsValue = item.value;
if (typeof paramsValue !== 'undefined' && paramsValue !== null) {
if (item.children) {
formParams[key] = paramsFormat(item.children);
} else {
formParams[key] = item.value;
}
}
}
}
if (JSON.stringify(formParams) === '{}') {
formParams = undefined;
}
return formParams;
}
const isLoading = ref(false);
/**
* 提交数据
* @param params 提交参数
*/
function submitData(params:Object = {}) {
isLoading.value = true;
if (props.submitRequest) {
props.submitRequest(params).then(()=>{
isLoading.value = false;
ruleFormDom.value.resetFields();
emit('confirm', params);
}).catch(()=>{
isLoading.value = false;
});
} else {
emit('confirm', params);
isLoading.value = false;
}
}
/**
* 表单验证提示
* @param valid 是否有效
* @param formRuleList 表单验证列表
*/
function ruleFormTips(valid:Boolean, formRuleList:Object) {
if (valid) {
return true;
} else {
for (const key in formRuleList) {
if (formRuleList[key]) {
const item = formRuleList[key][0];
ElMessage.warning(item.message);
break;
}
}
return false;
}
}
return {
ruleFormDom,
isLoading,
cancel,
confirm
};
}
});
</script>
<style lang="scss" scoped>
.form-control {
margin-top: 30px;
text-align: right;
}
</style>
- BaseFormItem.vue
<template>
<div style="display: inline">
<el-form-item
v-for="(formItem, formKey) in formData"
:key="formKey"
:label="formItem.label"
:prop="parentKey + formKey + '.value'"
:label-width="formItem.label ? defaultLabelWidth : 0"
:rules="formItem.rules"
v-bind="formItem.formItemProps"
:style="{ padding: formItem.children ? 0 : null, margin: formItem.children ? 0 : null }"
>
<!-- 表单文本提示 -->
<template
v-if="formItem.labelTips"
#label
>
{{ formItem.label }}
<el-tooltip
effect="dark"
:content="formItem.labelTips"
placement="bottom-start"
>
<el-icon><question-filled /></el-icon>
</el-tooltip>
</template>
<!-- 文本 -->
<label
v-if="formItem.type === 'label'"
:style="{
'word-wrap': 'break-word'
}"
v-bind="formItem.props"
v-on="formItem.on ? formItem.on : {}"
>{{ formItem.value }}</label>
<!-- 输入框 -->
<el-input
v-if="formItem.type === 'input'"
v-model="formItem.value"
placeholder="请输入"
clearable
v-bind="formItem.props"
v-on="formItem.on ? formItem.on : {}"
/>
<!-- 数字输入框 -->
<el-input-number
v-if="formItem.type === 'input-number'"
v-model="formItem.value"
placeholder="请输入"
clearable
v-bind="formItem.props"
v-on="formItem.on ? formItem.on : {}"
/>
<!-- 选项框 -->
<el-select
v-if="formItem.type === 'select'"
v-model="formItem.value"
style="width: 100%"
placeholder="请选择"
clearable
collapse-tags
v-bind="formItem.props"
@change="selectChange(formItem)"
v-on="formItem.on ? formItem.on : {}"
>
<div
v-if="formItem.isAll !== undefined"
style="padding: 5px 20px"
>
<el-checkbox
v-model="formItem.isAll"
:indeterminate="formItem.isIndeterminate"
@change="selectAllChange(formItem.isAll, formItem)"
>
全选
</el-checkbox>
</div>
<el-option
v-for="(item, index) in formItem.options"
:key="index"
:label="item.label"
:value="item.value"
/>
</el-select>
<!-- 级联选择框 -->
<el-cascader
v-if="formItem.type === 'cascader'"
v-model="formItem.value"
style="width: 100%"
:options="formItem.options"
clearable
v-bind="formItem.props"
v-on="formItem.on ? formItem.on : {}"
/>
<!-- 日期选择器 -->
<el-date-picker
v-if="formItem.type === 'date-picker'"
v-model="formItem.value"
placeholder="请选择"
clearable
v-bind="formItem.props"
v-on="formItem.on ? formItem.on : {}"
/>
<!-- 单选框 -->
<el-radio-group
v-if="formItem.type === 'radio-group'"
v-model="formItem.value"
v-bind="formItem.props"
v-on="formItem.on ? formItem.on : {}"
>
<template v-if="formItem.isButton">
<el-radio-button
v-for="(item, index) in formItem.options"
:key="index"
:label="item.value"
>
{{ item.label }}
</el-radio-button>
</template>
<template v-else>
<el-radio
v-for="(item, index) in formItem.options"
:key="index"
:label="item.value"
>
{{ item.label }}
</el-radio>
</template>
</el-radio-group>
<!-- 开关 -->
<el-switch
v-if="formItem.type === 'switch'"
v-model="formItem.value"
/>
<!-- 是否存在子级 -->
<div
v-if="formItem.children"
class="w-full"
>
<base-form-item
:default-label-width="defaultLabelWidth"
:form-data="formItem.children"
:parent-key="formKey ? parentKey + formKey + '.children.' : ''"
/>
</div>
</el-form-item>
</div>
</template>
<script lang='ts'>
import { defineComponent } from 'vue';
import {
ElTooltip,
ElIcon,
ElFormItem,
ElSwitch,
ElInput,
ElInputNumber,
ElRadioGroup,
ElRadio,
ElRadioButton,
ElCascader,
ElSelect,
ElOption,
ElDatePicker
} from 'element-plus';
import { QuestionFilled } from '@element-plus/icons-vue';
import { FormObjItemType } from '../types';
export default defineComponent({
components: {
ElTooltip,
ElIcon,
QuestionFilled,
ElFormItem,
ElSwitch,
ElInput,
ElInputNumber,
ElRadioGroup,
ElRadio,
ElRadioButton,
ElCascader,
ElSelect,
ElOption,
ElDatePicker
},
props: {
// 表单数据
formData: {
type: Object,
required: true,
},
// 父级键值
parentKey: {
type: String,
required: false,
default: ''
},
// 默认文本宽度
defaultLabelWidth: {
type: Number,
required: false,
default: 80
}
},
setup() {
/**
* 选择框变化事件
* @param formItem 表单项
*/
function selectChange(formItem: FormObjItemType) {
// 不存在全选项或选项不存在时不执行以下代码
if (formItem.isAll === undefined || formItem.options === undefined) {
return;
}
// 判断是否全选
if (formItem.value.length >= 0 && formItem.value.length < formItem.options.length) {
formItem.isAll = false;
formItem.isIndeterminate = formItem.value.length === 0 ? false : true;
} else {
formItem.isIndeterminate = false;
formItem.isAll = true;
}
}
/**
* 选择全选变化事件
* @param isAll 是否全选
* @param formItem 表单项
*/
function selectAllChange(isAll:Boolean, formItem: FormObjItemType) {
if (isAll) {
formItem.value = formItem.options?.map(item => item.value);
} else {
formItem.value = [];
}
if (formItem.isIndeterminate) {
formItem.isIndeterminate = false;
}
}
return {
selectChange,
selectAllChange
};
},
});
</script>
<style lang="scss" scoped>
.el-form-item {
padding: 9px 0;
margin-bottom: 0;
}
</style>
import { Rule } from 'async-validator';
/**
* 表单对象类型
*/
export type FormObjType = {
[key: string]: FormObjItemType
};
/**
* 表单项类型
*/
export type FormObjItemType = {
/**
* 文本
* @default ''
* @type String
*/
label?: String;
/**
* 组件类型
* @default undefined
* @type ComponentTypes
*/
type?: ComponentTypes;
/**
* 选项数组
* @default undefined
* @type optionsType[]
*/
options?: optionsType[];
/**
* 组件属性
* @default undefined
* @type Object
*/
props?: Record<string, any>
/**
* 组件事件
* @default {}
* @type Object
*/
on?: {
[key: string]: any
};
/**
* 表单选项属性
* @default {}
* @type Object
*/
formItemProps?: {
prop?: string,
labelWidth?: string | number,
rules?: Rule,
required?: Boolean,
[key:string]: any
};
/**
* 验证规则
* @default {}
*/
rules?: Array<Object> | Object;
/**
* 表单对象子级
* @default {[key]:{}}
* @type FormObjType
*/
children?: FormObjType;
/**
* 表单文本提示
*/
labelTips?: string;
/**
* 是否全选
* @default undefined
* @type Boolean
*/
isAll?: Boolean;
/**
* 选中个数不为0,且未全选
* @default undefined
* @type Boolean
*/
isIndeterminate?: Boolean;
/**
* 值
* @default null
* @type any
*/
value?: any;
/**
* 是否请求列表
* @default true
* @type Boolean
*/
isRequest?: Boolean;
/**
* 额外扩展参数
* @default {}
* @type Object
*/
extra?: {
[key: string]: any
};
};
/**
* 选项类型
*/
export type optionsType = {
/**
* 文本
* @type String | Number
*/
label: String | Number
/**
* 值
* @type String | Number
*/
value: String | Number | Boolean
/**
* 是否禁用
* @type Boolean
*/
disabled?: Boolean
}
/**
* 组件类型
*/
export type ComponentTypes = 'label' | 'switch' | 'radio-group' | 'input' | 'input-number' | 'select' | 'cascader' | 'date-picker'
/**
* 表单参数类型
*/
export type FormParamsType = Object | undefined
🍐写在最后
代码仅供参考学习,可以随意扩展自己的想法,大家一起学习🦏,觉得有用的同学动动小手点个赞