Vue3 通过json配置生成查询表单
功能实现背景
通过Vue3实现后台管理项目一定含有表格功能,通常离不开表单。于是通过json配置来生成表单的想法由然而生
注意:
1.项目依赖element-plus
使用规则
1. 组件支持使用v-model管理值,当v-model不配置时也可以通过@finish事件获取表单值
2. 当FormRender组件
设置v-model后,schema
配置项中defaultValue设置的默认值无效
3. 项目默认查询和重置事件,也支持插槽自定义事件
4. 通过插槽自定义事件时,可以通过插槽获取表单值,也可以通过组件暴露属性和方法获取el-form属性&方法和表单值
项目代码
- 创建
type/index.ts
文件
import { type Component } from 'vue'export type ObjAny = { [T: string]: any }
export interface SchemaItem {key: string // 唯一标识 & 表单项v-model的属性label: string // form-item 的label属性type?: 'input' | 'select' // 支持 el-input 和 el-select组件defaultValue?: any // 当组件未配置v-model属性,可自定义默认值component?: Component // 自定义组件props?: { [K: string]: any } // 组件属性:继承el-form表单组件属性
}
- 创建
FormRender.vue
文件
<template><el-form ref="formRef" :model="dataForm" :inline="true"><el-form-item v-for="item in schema" :key="item.key" :label="item.label" :prop="item.key"><!-- 自定义组件 --><template v-if="item.component"><component :is="item.component" v-bind="item.props" v-model="dataForm[item.key]" /></template><!-- el-select --><template v-else-if="item.type === 'select'"><el-select v-bind="item.props" v-model="dataForm[item.key]" /></template><!-- 默认: el-input --><template v-else><el-input v-bind="item.props" v-model="dataForm[item.key]" /></template></el-form-item><!-- 事件插槽,默认查询和重置功能,支持自定义 --><slot name="handle" :data="{ ...dataForm }"><el-form-item v-if="showFinish || showReset"><el-button v-if="showFinish" :loading="loading" type="primary" @click="handleClick">{{ textFinish }}</el-button><el-button v-if="showReset" type="primary" @click="handleReset">{{ textReset }}</el-button></el-form-item></slot></el-form>
</template><script setup lang="ts">
import type { FormInstance } from 'element-plus'
import { reactive, useTemplateRef } from 'vue'
import {ObjAny, SchemaItem} from 'type/index.ts'defineOptions({name: 'FormRender'
})const props = withDefaults(defineProps<{showFinish?: booleanshowReset?: booleantextFinish?: stringtextReset?: stringschema: SchemaItem[]}>(),{showFinish: true,showReset: true,textFinish: '查询',textReset: '重置'}
)
const emit = defineEmits<{(e: 'finish', data: ObjAny): void(e: 'reset', data: ObjAny): void
}>()const dataForm = defineModel() as ObjAny
const loading = defineModel('loading', { type: Boolean, default: false })
const formRef = useTemplateRef<FormInstance | null>('formRef')initForm()/*** 当组件未定义 v-model,内部生成form data*/
function initForm() {if (dataForm.value === undefined) {const defaultForm: { [T: string]: any } = reactive({})props.schema.forEach(item => {defaultForm[item.key] = item.defaultValue || ''})if (dataForm.value === undefined) {dataForm.value = defaultForm}}
}
/*** finish*/
function handleClick() {emit('finish', { ...dataForm.value })
}/*** reset*/
function handleReset() {formRef.value?.resetFields()emit('reset', { ...dataForm.value })
}
// 默认暴露的属性和方法,可自行添加
defineExpose({elFormInstance: formRef,reset: handleReset
})
</script>
案例
- 简单渲染和取值:未使用v-model
<FormRender :schema="schema" @finish="handleSubmit" /><script lang="ts" setup>
import FormRender from './FormRender.vue'
import {ElInput} from 'element-plus'
import {ref, onMounted, computed} from 'vue'
import {ObjAny, SchemaItem} from 'type/index.ts'const options = ref<{ label: string; value: string }[]>([])
// 默写数据需要通过网络请求获取,所有需要使用到 computed
const schema = computed<SchemaItem[]>(() => [{key: 'content',label: '名称',type: 'input',defaultValue: '张三',props: {placeholder: '请输入名称',clearable: true,style: {width: '200px'}}},{key: 'orderNo',label: '订单号',defaultValue: '20250012',component: ElInput,props: {placeholder: '请输入订单号',clearable: true,style: {width: '200px'}}},{key: 'state',label: '状态',type: 'select',props: {placeholder: '请选择状态',clearable: true,options: options.value,style: {width: '200px'}}}
])function handleSubmit(value: ObjAny) {console.log(value)
}
onMounted(() => {// 模拟网络请求数据options.value = [{value: '1',label: 'Option1'},{value: '2',label: 'Option2'},{value: '3',label: 'Option3'}]
})
</script>
- 使用v-model
<FormRender v-model='data' :schema="schema" @finish="handleSubmit" /><script lang="ts" setup>
import FormRender from './FormRender.vue'
import {ElInput} from 'element-plus'
import {ref, onMounted, computed} from 'vue'
import {ObjAny, SchemaItem} from 'type/index.ts'const data = ref({content: '张三',orderNo: '20250012',state: ''
})
const options = ref<{ label: string; value: string }[]>([])
// 默写数据需要通过网络请求获取,所有需要使用到 computed
// 当使用v-model时, defaultValue值将失效
const schema = computed<SchemaItem[]>(() => [{key: 'content',label: '名称',type: 'input',props: {placeholder: '请输入名称',clearable: true,style: {width: '200px'}}},{key: 'orderNo',label: '订单号',component: ElInput,props: {placeholder: '请输入订单号',clearable: true,style: {width: '200px'}}},{key: 'state',label: '状态',type: 'select',props: {placeholder: '请选择状态',clearable: true,options: options.value,style: {width: '200px'}}}
])function handleSubmit(value: ObjAny) {console.log(value)
}
onMounted(() => {// 模拟网络请求数据options.value = [{value: '1',label: 'Option1'},{value: '2',label: 'Option2'},{value: '3',label: 'Option3'}]
})
</script>
- 使用slot自定义事件
<FormRender ref="formRenderRef" v-model='data' :schema="schema"><template v-slot:handle="{ data }"><el-form-item><el-button type="primary" @click="handleSubmit(data)">查询</el-button><el-button @click="handleReset">重置</el-button></el-form-item></template>
</FormRender><script lang="ts" setup>
import FormRender from './FormRender.vue'
import {ElInput} from 'element-plus'
import {ref, onMounted, computed} from 'vue'
import {ObjAny, SchemaItem} from 'type/index.ts'const formRenderRef = useTemplateRef('formRenderRef')const data = ref({content: '张三',orderNo: '20250012',state: ''
})
const options = ref<{ label: string; value: string }[]>([])// 默写数据需要通过网络请求获取,所有需要使用到 computed
// 当使用v-model时, defaultValue值将失效
const schema = computed<SchemaItem[]>(() => [{key: 'content',label: '名称',type: 'input',props: {placeholder: '请输入名称',clearable: true,style: {width: '200px'}}},{key: 'orderNo',label: '订单号',component: ElInput,props: {placeholder: '请输入订单号',clearable: true,style: {width: '200px'}}},{key: 'state',label: '状态',type: 'select',props: {placeholder: '请选择状态',clearable: true,options: options.value,style: {width: '200px'}}}
])function handleSubmit(value: ObjAny) {console.log(value)
}
const handleReset = () => {formRenderRef.value?.reset()
}
onMounted(() => {// 模拟网络请求数据options.value = [{value: '1',label: 'Option1'},{value: '2',label: 'Option2'},{value: '3',label: 'Option3'}]
})
</script>