封装组件
<template>
<el-form
:model="currentValue"
ref="dymanicForm"
v-bind="$attrs"
@submit.native.prevent
>
<el-form-item
:prop="index"
:rules="item.rules"
v-for="(item, index) in currentSchema"
:key="index"
v-bind="item.formItem || {}"
>
<component
v-model="currentValue[index]"
:is="item.componentName"
v-bind="item"
v-on="item.methods || {}"
/>
</el-form-item>
<slot />
</el-form>
</template>
<script lang="ts" setup>
import checkValidator from "~/utils/validator/index";
import {
ElInput,
ElSelect,
ElDatePicker,
ElCheckbox,
ElRadio,
ElRadioGroup,
ElCheckboxGroup,
ElSwitch,
} from "element-plus";
const componentMap = {
ElInput: ElInput,
ElSelect: ElSelect,
ElDatePicker: ElDatePicker,
ElCheckbox: ElCheckbox,
ElRadio: ElRadio,
ElRadioGroup: ElRadioGroup,
ElCheckboxGroup: ElCheckboxGroup,
};
const props = defineProps({
schema: Object,
modelValue: [String, Number, Boolean, Array, Date, Object],
});
const emit = defineEmits(["update:modelValue"]);
const currentValue = computed({
get: () => props.modelValue,
set: (val) => emit("update:modelValue", val),
});
const currentSchema = computed(() => {
const schema = {};
for (const key in props.schema) {
let { visible = true, ...item } = props.schema[key];
// 动态表单的组件名
item.componentName = componentMap[item.componentName];
// 是否显示
visible =
typeof visible !== "function" ? visible : visible(currentValue, item);
if (visible) {
schema[key] = { clearable: true, ...item };
}
// 表单验证规则
if (schema[key].validator && schema[key].validator.length) {
if (!schema[key].rules) schema[key].rules = [];
schema[key].validator?.forEach(([valida, args]) => {
if (checkValidator[valida]) {
schema[key].rules.unshift(checkValidator[valida](args));
}
});
}
}
return schema;
});
const dymanicForm = ref(null);
const validate = (func) => {
if (typeof func === "function") {
dymanicForm.value.validate((valid) => {
func(valid);
});
} else {
return new Promise((resolve) => {
dymanicForm.value.validate((valid) => {
resolve(valid);
});
});
}
};
const resetFields = () => {
dymanicForm.value.resetFields();
};
defineExpose({
resetFields,
validate,
});
</script>
调用组件示例
<template>
<div class="">
<DymanicForm
ref="formRef"
:inline="true"
:schema="schema"
v-model="searchValue"
>
<el-form-item>
<el-button type="primary" @click="handleQuery">查询</el-button>
<el-button @click="handleReset">重置</el-button>
</el-form-item>
</DymanicForm>
</div>
</template>
<script setup lang="ts">
import { ref, reactive } from "vue";
import type { ItemSchema } from "~/types/dymanic";
import DymanicForm from "~/components/DymanicForm.vue";
const formRef = ref<InstanceType<typeof DymanicForm>>();
const schema = ref<ItemSchema>({
userId: {
formItem: {
label: "用户ID:",
},
style: {
width: "200px",
},
componentName: "ElInput",
placeholder: "请输入用户ID",
maxlength: 20,
validator: [
[
"checkRequired",
{
msg: "用户ID不能为空",
},
],
],
},
username: {
formItem: {
label: "账号:",
},
style: {
width: "200px",
},
componentName: "ElInput",
placeholder: "请输入账号",
maxlength: 20,
},
name: {
formItem: { label: "用户名:" },
style: {
width: "200px",
},
componentName: "ElInput",
placeholder: "请输入用户名",
maxlength: 20,
},
});
const searchValue = reactive({
userId: "",
username: "",
name: "",
});
const handleQuery = () => {
formRef.value.validate((valid: boolean) => {
if (valid) {
console.log("查询", searchValue);
// 查询逻辑
}
});
};
const handleReset = () => {
formRef.value.resetFields();
};
</script>
<style lang="scss" scoped></style>
校验规则示例
// 校验内容不能为空的验证
const checkRequired = ({ msg, trigger = ['blur'] }) => {
return {
required: true,
message: msg,
trigger,
}
}
export default checkRequired
Vue3 +ElementPlus 表单组件的封装
FormItem.tsx,index.vue,type.ts
FormItem.tsx
import filter from '@/utils/filters'
import {
ElCheckbox,
ElCheckboxGroup,
ElDatePicker,
ElInput,
ElInputNumber,
ElOption,
ElRadio,
ElRadioGroup,
ElSelect,
ElTimePicker
} from 'element-plus'
import { defineComponent } from 'vue'
// 普通显示
const Span = (form: Record<string, any>, data: Record<string, any>) => (
<span>{data.valueProp ? form[data.valueProp] : (data.filter ? filter(form[data.prop], data.filter) : form[data.prop] || '无')}</span>
)
// 输入框
const Input = (form: Record<string, any>, data: Record<string, any>) => (
<ElInput
v-model={form[data.prop]}
type={data.type}
size='small'
show-password={data.type == 'password'}
clearable
placeholder={'请输入' + data.label}
autosize = {{
minRows: 3,
maxRows: 4,
}}
{...data.props}
>
</ElInput>
)
// 数字输入框
const InputNumber = (form: Record<string, any>, data: Record<string, any>) => (
<ElInputNumber
size='small'
v-model={form[data.prop]}
controls-position="right"
{...data.props}
/>
)
const setLabelValue = (_item: any, { optionsKey }: any = {}) => {
return {
label: optionsKey ? _item[optionsKey.label] : _item.label,
value: optionsKey ? _item[optionsKey.value] : _item.value,
}
}
// 选择框
const Select = (form: Record<string, any>, data: Record<string, any>) => (
<ElSelect
size='small'
v-model={form[data.prop]}
filterable
clearable
placeholder={'请选择' + data.label}
{...data.props}
>
{data.options.map((item: any) => {
return <ElOption {...setLabelValue(item, data)} />
})}
</ElSelect>
)
// 单选/区间日期
const Date = (form: Record<string, any>, data: Record<string, any>) => (
<ElDatePicker
size='small'
v-model={form[data.prop]}
type={data.type}
value-format={data.valueFormat}
format = {data.format}
range-separator="至"
start-placeholder={data.startPlaceholder}
end-placeholder={data.endPlaceholder}
placeholder={'请选择' + data.label}
{...data.props}
/>
)
// 单选/区间时间
const Time = (form: Record<string, any>, data: Record<string, any>) => (
<ElTimePicker
size='small'
v-model={[form[data.prop]]}
value-format={data.valueFormat}
format = {data.format}
range-separator="至"
disabled = {form.editable}
start-placeholder={data.start}
is-range={data.isRange}
end-placeholder={data.end}
{...data.props}
/>
)
// 单选
const Radio = (form: Record<string, any>, data: Record<string, any>) => (
<ElRadioGroup v-model={form[data.prop]}>
{data.radios.map(
(item: { label: string | number | boolean; value: any }) => {
return (
<ElRadio label={setLabelValue(item, data.prop).label}>
{setLabelValue(item, data.prop).value}
</ElRadio>
)
},
)}
</ElRadioGroup>
)
// 多选
const Checkbox = (form: Record<string, any>, data: Record<string, any>) => (
<ElCheckboxGroup size='small' v-model={form[data.prop]}>
{data.checkboxs.map(
(item: { label: string | number | boolean; value: any }) => {
return (
<ElCheckbox label={setLabelValue(item, data.prop).label}>
{setLabelValue(item, data.prop).value}
</ElCheckbox>
)
},
)}
</ElCheckboxGroup>
)
const setFormItem = (
form: Record<string, any> | undefined,
data: Record<string, any>,
editable: Boolean,
) => {
if (!form) return null
if (!editable) return Span(form, data)
switch (data.type) {
case 'input':
return Input(form, data)
case 'textarea':
return Input(form, data)
case 'password':
return Input(form, data)
case 'inputNumber':
return InputNumber(form, data)
case 'select':
return Select(form, data)
case 'date':
case 'daterange':
return Date(form, data)
case 'time':
return Time(form, data)
case 'radio':
return Radio(form, data)
case 'checkbox':
return Checkbox(form, data)
default:
return null
}
}
export default () =>
defineComponent({
props: {
data: Object,
formData: Object,
editable: Boolean,
},
setup(props) {
return () =>
props.data
? setFormItem(props.formData, props.data, props.editable)
: null
},
})
index.vue
<template>
<el-form ref="FormRef"
:model="prop.data.data"
:rules="editable ? prop.data.rules : {}"
:inline="inline"
:label-position="labelPosition"
label-width="atuo">
<el-row :gutter="prop.data.elRowGutter">
<el-col v-for="item in prop.data.formItems"
:span="item.span">
<el-form-item :label="item.label ? item.label + ':' : ''"
:prop="item.prop"
:label-width="item.width">
<FormItem :formData="prop.data.data"
:editable="editable"
:data="item">
</FormItem>
</el-form-item>
</el-col>
<el-col v-if="btnList && btnList.length"
:span="24">
<el-form-item>
<template v-for="item in btnList">
<Btn :props="item"
@click="onClick(item)"></Btn>
</template>
</el-form-item>
</el-col>
</el-row>
</el-form>
</template>
<script lang="ts" setup>
import { computed } from '@vue/reactivity'
import type { FormInstance } from 'element-plus'
import { ref } from 'vue'
import formItem from './FormItem'
import type { commonForm } from './type'
interface Props {
data: commonForm
}
const prop = defineProps<Props>()
const editable = computed(() => !!prop.data?.editable)
const inline = computed(() => !!prop.data.formProps?.inline)
const labelWidth = computed(() => prop.data.formProps?.labelWidth || '100px')
const labelPosition = computed(
() => prop.data.formProps?.labelPosition || 'top',
)
const btnList = computed(() => {
return prop.data.formProps?.btn
})
// tsx组件
const FormItem = formItem()
const FormRef = ref<FormInstance>()
// 表单按钮
function onClick(data: { onClick?: () => void }) {
if (!data.onClick) return
data.onClick()
}
// 表单校验
async function validate() {
if (!FormRef.value) return
const result = await FormRef.value.validate()
return result
}
// 清除表单验证
async function resetFields() {
return await FormRef.value.resetFields()
}
defineExpose({
validate,
resetFields,
})
</script>
<style scoped>
.el-form-item {
margin: 0 10px !important;
}
.el-form-item__label {
position: absolute;
}
.el-form-item__content {
width: 100%;
padding-left: 80px;
}
.el-select,
.el-input_inner {
width: 100%;
}
</style>
type.ts
type itemType =
| 'input'
| 'select'
| 'switch'
| 'radio'
| 'date'
| 'time'
| 'checkbox'
| 'daterange'
interface FormProps {
inline?: Boolean
labelWidth?: string | number
labelPosition?: 'left' | 'top' | 'right'
btn?: object[]
}
interface FormItems {
type: itemType
label?: string
prop: string
valueProp?: string
width?: string | number
span?: number
filter?: string
}
export class commonForm {
public data: any
private rules?: object
public elRowGutter?: number
public editable?: boolean
public formProps?: FormProps
public formItems: FormItems[]
public dataArray?:object[]
constructor({
data = {},
rules = {},
editable = true,
formProps = {},
formItems = [],
elRowGutter = 0,
}: any) {
this.data = data
this.rules = rules
this.elRowGutter = elRowGutter
this.editable = editable
this.formItems = formItems
this.formProps = formProps
}
}
``