功能实现背景
通过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?: boolean
showReset?: boolean
textFinish?: string
textReset?: string
schema: 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>