这里使用的UI框架是ElementPlus,更换其他组件直接更换constant.ts中的type配置和对应的Form组件即可.
思路:
1.这里需要使用h函数方便控制要渲染的表单
2.传递type作为组件或html元素进行渲染,
3.默认包一层El-form,每个数组项会包一个El formItem
4.传入formDataKey,对绑定的表单项进行映射
5.通过next函数决定渲染的表单,next函数接收参数即表单数据
创建一个静态文件存放配置
import {
FormItemConfig } from "../components/DyForm/DyForm"
export class UserForm {
name?: string
organization?: string
date1?: string
date2?: string
delivery?: boolean
type?: []
resource?: string
desc?: string
personInfo?: string
developerName?: string
sponsorName?:string
}
export function useDyFormConfig(onsubmit=()=>null){
const dyFormConfig: FormItemConfig[] = [
{
type: ElInput,
formItemConfig: {
label: '请输入姓名'
},
formConfig: {
placeholder: '请输入姓名'
},
formDataKey: 'name'
},
{
type: ElSelect,
formItemConfig: {
label: '请选择组织'
},
formConfig: {
placeholder: '请选择您的组织'
},
formDataKey: 'organization',
children: [
{
type: ElOption,
formConfig: {
label: '个人', value: 'person' },
},
{
type: ElOption,
formConfig: {
label: '公司', value: 'company' }
},
{
type: ElOption,
formConfig: {
label: '无', value: '' }
}
],
next(formData) {
if (!formData.organization) {
return []
} else if (formData.organization === 'company') {
return [
{
type: 'div',
formConfig: {
style: {
width: '100%',
display: 'flex'
}
},
formItemConfig: {
label: '请选择日期'
},
children: [
{
type: ElCol,
formConfig: {
span: 11
},
children: [
{
type: ElDatePicker,
formDataKey: 'date1',
formConfig: {
type: "date",
placeholder: "请选择进入公司日期",
style: "width: 100%"
},
}
]
},
{
type: ElCol,
formConfig: {
span: 2
},
children: [
{
type: 'span',
children: '-'
}
]
},
{
type: ElCol,
formConfig: {
span: 11
},
children: [
{
type: ElDatePicker,
formDataKey: 'date2',
formConfig: {
type: "date",
placeholder: "请选择毕业日期",
style: "width: 100%"
},
}
]
},
],
next(formData) {
console.log(formData)
return [{
type: ElInput,
formItemConfig: {
label: '请输入个人信息'
},
formConfig: {
placeholder: '请输入个人信息'
}
}]
}
},
]
} else {
return [{
type: ElInput,
formDataKey: 'personInfo',
formItemConfig: {
label: '请输入个人信息'
},
formConfig: {
placeholder: '请输入个人信息'
}
}]
}
}
},
{
type: ElSwitch,
formDataKey: 'delivery',
formItemConfig: {
label: 'Instant delivery'
}
},
{
type: ElCheckboxGroup,
formDataKey: 'type',
formItemConfig: {
label: 'Activity type',
},
children: [
{
type: ElCheckbox,slots:{
default:()=>'活动1'} ,formConfig:{
name: 'type',label:'活动1'} },
{
type: ElCheckbox, slots:{
default:()=>'活动2'} ,formConfig:{
name: 'type',label:'活动2'}},
{
type: ElCheckbox, slots:{
default:()=>'活动3'} ,formConfig:{
name: 'type',label:'活动3'}},
{
type: ElCheckbox,slots:{
default:()=>'活动4'} ,formConfig:{
name: 'type',label:'活动4'}}
],
},
{
type: ElRadioGroup,
formDataKey: 'resource',
formItemConfig: {
label: 'Resources'
},
children: [
{
type: ElRadio,
formConfig: {
label: 'Sponsor'
}
},
{
type: ElRadio,
formConfig: {
label: 'Developer'
}
},
],
next(formData) {
const resource = formData.resource
const obj = {
'Sponsor': [
{
type: ElInput,
formDataKey:'sponsorName',
formItemConfig: {
label:'请输入赞助商名称'
},
}
],
'Developer': [
{
type: ElInput,
formDataKey:'developerName',
formItemConfig: {
label:'请输入开发商名称'
},
}
],
}as Record<string,FormItemConfig[]>
if (!resource) {
return []
} else {
return obj[resource]
}
},
},
{
type: ElInput,
formConfig: {
type: 'textarea'
},
formDataKey: 'desc',
formItemConfig: {
label: 'Activity form'
}
},
{
type: 'div',
formConfig: {
style: {
width: "100%",
display: "flex"
}
},
children: [
{
type: ElCol,
formConfig: {
span: 6
},
children: [
{
type: ElButton,
formConfig: {
type: 'warning',
onClick: onsubmit
},
slots: {
default: () => '确认'
}
}
]
},
{
type: ElCol,
formConfig: {
span: 18
},
children: [
{
type: ElButton,
formConfig: {
type: 'danger',
onClick: () => {
console.log('取消')
}
},
slots: {
default: () => '取消'
}
}
]
}
]
}
];
return {
dyFormConfig
}
}
使用示例
<script setup lang="ts">
import {
DyForm } from './components/DyForm/DyForm';
import {
useDyFormConfig, UserForm } from './utils/constant'
const {
dyFormConfig } = useDyFormConfig()
const form = ref<UserForm>({
name: '',
organization: '',
date1: '',
date2: '',
delivery: false,
type: [],
resource: '',
desc: '',
personInfo:''
});
watch(form, (n:UserForm) => {
console.log(n)
})
const formConfig = {
labelWidth: '120px',
};
</script>
<template>
<DyForm ref="dyForm" v-model="form" :dyFormConfig="dyFormConfig" :formConfig="formConfig" />
</template>
<style scoped>
#app,
html,
body {
width: 100vw;
height: 100vh;
}
</style>
动态组件源码
export interface FormItemConfig<T = Props['modelValue']> {
type: any
formConfig?: Record<string, any>
formDataKey?: keyof T
formItemConfig?: Record<string, any>
children?: FormItemConfig<T>[] | string
slots?: Record<string, () => any>
next?: (formData: T) => FormItemConfig<T>[]
}
interface Props {
modelValue: Record<string, any>
dyFormConfig:FormItemConfig[]
formConfig:Record<string, any>
}
export const DyForm = defineComponent<Props>(
(props, {
emit,expose }) => {
expose({
getFormData: () => {
return props.modelValue
}
})
const form = computed({
get() {
return props.modelValue
},
set() {
return false
}
})
const rederChild = () => props.dyFormConfig.map((item:FormItemConfig) => {
const traverChildren = (child: FormItemConfig['children']): any => {
return child && typeof child !== 'string' && child.map(
item => {
if (typeof item.children === 'string') {
return h(item.type, item.children)
}
return h(item.type, item.formDataKey ?
{
...item.formConfig,
modelValue: form.value[item.formDataKey],
'onUpdate:modelValue': (value: any) => emit('update:modelValue', {
...props.modelValue, [item.formDataKey as string]: value })
} :
item.formConfig,
[item.children && traverChildren(item.children), item.slots && renderSlots(item)])
}
)
}
const renderSlots = (options: FormItemConfig): any => {
return Object.keys(options.slots || {
}).map((slot: any) => {
return options.slots![slot]()
})
}
const render = (options: FormItemConfig): any => {
return h(ElFormItem, {
...options.formItemConfig }, [
h(
options.type,
options.formDataKey ?
{
...options.formConfig,
modelValue: form.value[options.formDataKey],
'onUpdate:modelValue': (value: any) => emit('update:modelValue', {
...props.modelValue, [options.formDataKey as string]: value })
} : options.formConfig,
options.slots ? renderSlots(options) : traverChildren(options.children || [])
)
])
}
if (!item.next) {
return render(item)
} else {
const nextOptions = item.next(props.modelValue)
if (!nextOptions || !Array .isArray(nextOptions)) {
console.error('请检查next函数返回值是否有误')
return item.children && render(item)
}
return [item.children && render(item), ...nextOptions.map((options: FormItemConfig) => render(options))]
}
})
return () => {
return h(ElForm, {
model: form,
...props.formConfig
}, {
default: rederChild()
})
}
},
{
props: ['modelValue', 'dyFormConfig', 'formConfig'],
emits: ['update:modelValue']
}
)
结果展示: