表格封装之 useForm 封装

2024-01-05 05:04
文章标签 封装 表格 useform

本文主要是介绍表格封装之 useForm 封装,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

在日常开发中,后端管理系统中增删改查的开发大多是重复机械式的工作,为了减少解放自己的双手,我们可以对这部分工作的内容进行封装。

一般的增删改查页面,由顶部搜索区,中部表格区,底部功能区(分页、多选等)三部分组成,我们将分文三个部分进行封装处理、本篇文章我们将实现 useForm 及组价的封装。

  • 本文基于 Vue3TypeScriptElementPlus 进行封装。
  • 本文中不便于描述的功能在代码示例中注释标注。

封装目标

useForm 是对顶部搜索区域的封装,通过封装基于配置化实现表单的渲染,同时实现数据项的双向绑定和自定义插槽的功能。

如下示例代码为我们的目标实现

<template><Form v-model="formValue" :column="column" @search="onSearch"><el-button type="default" @click="onReset">重置</el-button></Form>
</template><script lang="ts" setup>
import { reactive } from 'vue'
import Form, { useForm, FormItem } from '@/hooks/useForm'const _column = reactive<FormItem[]>([{ type: 'input', prop: 'id', label: 'ID' },{type: 'select',prop: 'sex',label: '性别',options: [{ label: '男', value: 1 },{ label: '女', value: 0 },],},
])const { formValue, column, onReset } = useForm(_column)
const onSearch = () => {}
</script>

定义类型

既然是封装表单,我们需要考虑存在那些组件以及组件的特殊属性,比如以下的示例中定义了 Input 输入 Date 时间 Select 下拉 Cascader 级联 Slot 自定义 组件,当然你也可以根据具体的业务而进行修改。

// /hooks/useForm/type.d.ts
import type { VNodeChild } from 'vue'/*** 表单默认配置*/
interface FormDefault {label: stringplaceholder?: string
}/*** 输入框*/
export interface FormInput extends FormDefault {type: 'input'prop: stringvalue?: stringdataType?: 'number' | 'string'
}/*** 日期时间选择器*/
interface FormDateDefault extends FormDefault {type: 'date'prop: stringdateType?: 'date' | 'datetime'valueFormat?: stringvalue?: string
}interface FormDateRange extends FormDefault {type: 'date'dateType: 'daterange'prop: [string, string]value?: [string, string] | null
}export type FormDate = FormDateDefault | FormDateRange/*** 下拉框*/
export interface FormSelect1 extends FormDefault {type: 'select'prop: stringmultiple?: booleanvalue?: string | numberoptions: any[]labelKey?: string | 'label'valueKey?: string | 'value'
}
export interface FormSelect2 extends FormDefault {type: 'select'prop: stringmultiple?: booleanvalue?: string | numberapi: (any) => Promise<any>labelKey?: string | 'label'valueKey?: string | 'value'
}
export interface FormSelect3 extends FormDefault {type: 'select'prop: stringmultiple?: booleanvalue?: string | numbersearchApi: (any) => Promise<any>labelKey?: string | 'label'valueKey?: string | 'value'
}export type FormSelect = FormSelect1 | FormSelect2 | FormSelect3/*** 级联选择器*/
interface FormCascader1 extends FormDefault {type: 'cascader'prop: stringmultiple?: booleanvalue?: string | numberoptions: any[]labelKey?: string | 'label'valueKey?: string | 'value'childrenKey?: string | 'children'
}interface FormCascader2 extends FormDefault {type: 'cascader'prop: stringmultiple?: booleanvalue?: string | numberapi: (any) => Promise<any>labelKey?: string | 'label'valueKey?: string | 'value'childrenKey?: string | 'children'
}export type FormCascader = FormCascader1 | FormCascader2/*** 自定义组件*/
export interface FormSlot extends FormDefault {type: 'slot'prop: string[] | stringvalue?: anyrender: (row: any) => VNodeChild
}/*** 表单合集*/
export type FormItem = FormInput | FormDate | FormSelect | FormCascader | FormSlot

封装 Hook

根据组件的功能属性进行初始化处理,拿到初始的表单值、对表单项的配置进行初始化,以及封装通用的函数。

// /hooks/useForm/hooks.ts
import { reactive, toRefs } from 'vue'
import { FormItem } from './types.d'
import { isEmpty } from '@/utils/utils'interface FormParams {formValue: any
}export const useForm = (column: FormItem[]) => {const state = reactive<FormParams>({formValue: {},})// 拿到初始化 formValue 的值function initForm() {column.forEach(async item => {// 下拉框if (item.type === 'select') {const { prop, options, api, searchApi } = item as any// 字段检验if (isEmpty(api) && isEmpty(options) && isEmpty(searchApi)) {console.warn(`[useForm] ${prop} 字段配置 api 、searchApi 或 options 不能同时为空`)return}const _options: any[] = options || [];(item as any).options = _optionsstate.formValue[item.prop] = item.value || null// 下拉框的选项可能来自于远程,在这里获取if (api) {let v = await api({})// 返回的结果可能格式不一致,兼容处理v instanceof Array && (v = { total: v.length, data: v });(item as any).options = _options.concat(v.data)state.formValue[item.prop] = item.value || null}return}// 级联选择器if (item.type === 'cascader') {const { prop, options, api } = item as any// 字段检验if (isEmpty(api) && isEmpty(options)) {console.warn(`[useForm] ${prop} 字段配置 api 或 options 不能同时为空`)return}const _options: any[] = options || [];(item as any).options = _optionsstate.formValue[item.prop] = item.value || null// 级联选择器的选项可能来自于远程,在这里获取if (api) {let v = await api({})// 返回的结果可能格式不一致,兼容处理v instanceof Array && (v = { total: v.length, data: v });(item as any).options = _options.concat(v.data)state.formValue[item.prop] = item.value || null}return}// 时间if (item.type === 'date') {const { dateType } = item// 时间区间可能为两个字段if (dateType === 'daterange') {state.formValue[item.prop[0]] = item.value ? item.value[0] : nullstate.formValue[item.prop[1]] = item.value ? item.value[1] : nullreturn}state.formValue[item.prop as string] = item.value || nullreturn}// 自定义if (item.type === 'slot') {if (item.prop instanceof Array) {item.prop.forEach((v: string, i: number) => {state.formValue[v] = (item.value && item.value[i]) || null})return}}state.formValue[item.prop as string] = item.value || null})}// 重置表单时间function onReset() {column.forEach((item: any) => {// 时间区间if (item.type === 'daterange') {state.formValue[item.prop[0]] = nullstate.formValue[item.prop[1]] = nullitem.value = void 0return}// 时间区间if (item.type === 'time') {state.formValue[item.prop] = nullitem.value = void 0return}// 自定义if (item.type === 'slot') {if (item.prop instanceof Array) {item.prop.forEach((v: string) => {state.formValue[v] = null})return}}state.formValue[item.prop as string] = null})}// 初始化initForm()return {...toRefs(state),column,onReset,}
}

封装 useForm 可以实现基于配置化得到 formValuecolumnonReset

  • formValue

    基于表单项拿得初始化的表单值,也就是原始值,可用于表单的数据双向绑定或提供给外部使用。

  • column

    对表单项进行初始化,对一些需要从接口中获取数据的组件进行请求处理。

  • onReset

    通用函数,重置表单。

渲染组件

通过 useForm() 我们可拿到 formValue column onReset,接下来我们基于以上参数进行组件的封装。

<template><el-card shadow="never"><el-form :inline="true" :model="modelValue" label-width="auto"><el-row :gutter="20"><el-col :xs="12" :sm="12" :md="8" :lg="6" :xl="4" v-for="(prop, i) in column" :key="i"><el-form-item :label="prop.label"><template v-if="prop.type === 'input'"><template v-if="prop.dataType === 'number'"><el-input-numberv-model="modelValue[prop.prop]":placeholder="prop.placeholder || `请输入${prop.label}`"controls-position="right"@change="emits('search')"@keyup.enter="emits('search')"/></template><template v-else><el-inputv-model="modelValue[prop.prop]":placeholder="prop.placeholder || `请输入${prop.label}`"clearable@blur="emits('search')"@keyup.enter="emits('search')"/></template></template><template v-if="prop.type === 'date'"><template v-if="prop.dateType === 'daterange'"><el-date-pickerv-model="prop.value":type="prop.dateType"clearablestart-placeholder="起始时间"end-placeholder="结束时间"value-format="YYYY-MM-DD HH:mm:ss"@change="(v: [Date, Date] | null) => handleChangeDateRange(v, prop.prop)"/></template><template v-else><el-date-pickerv-model="modelValue[prop.prop]":type="prop.dateType || 'date'"clearable:placeholder="prop.placeholder || `请选择${prop.label}`":value-format="prop.valueFormat || 'YYYY-MM-DD HH:mm:ss'"@change="emits('search')"/></template></template><template v-if="prop.type === 'select'"><el-select-v2v-model="modelValue[prop.prop]":props="{label: prop.labelKey || 'label',value: prop.valueKey || 'value',}"filterableclearable:remote="!!(prop as any).searchApi":loading="(prop as any).loading":remote-method="(v: string | null) => handleRemoteMethod(v, prop)":multiple="prop.multiple":options="(prop as any).options":placeholder="prop.placeholder || `请选择${prop.label}`"@change="emits('search')"/></template><template v-if="prop.type === 'cascader'"><el-cascaderv-model="modelValue[prop.prop]":props="{multiple: prop.multiple,emitPath: false,label: prop.labelKey || 'label',value: prop.valueKey || 'value',children: prop.childrenKey || 'children',}":options="(prop as any).options"clearable@change="emits('search')"/></template><template v-if="prop.type === 'slot'"><component :is="RenderComponent(prop.render(modelValue))" /></template></el-form-item></el-col><!-- 判断是否存在 default slot --><template v-if="$slots.default"><el-col :xs="12" :sm="12" :md="8" :lg="6" :xl="4"><el-form-item><slot /></el-form-item></el-col></template></el-row></el-form></el-card>
</template><script setup lang="ts">
import { h } from 'vue'
import dayjs from 'dayjs'
import type { FormItem } from './types.d'const emits = defineEmits<{'update:modelValue': [val: any]search: []reset: []
}>()interface Props {modelValue: anycolumn: FormItem[]
}const props = defineProps<Props>()/*** 日期范围选择器切换事件* @param {Date} val 日期范围* @param {string} prop 日期范围对应的字段*/const handleChangeDateRange = (val: [Date, Date] | null, prop: [string, string]) => {const [start, end] = val || [null, null]props.modelValue[prop[0]] = start// 结束时间默认添加1天props.modelValue[prop[1]] = dayjs(end).add(1, 'day').format('YYYY-MM-DD HH:mm:ss')emits('update:modelValue', props.modelValue)emits('search')
}/*** 搜索结果* @param val 搜索关键字* @param item 表单项*/
const handleRemoteMethod = async (val: string | null, item: any) => {if (!item.searchApi) return!val && (item.options = [])if (!val) returnitem.loading = trueconst v = await item.searchApi({ name: val })item.loading = falseitem.options = v.data
}// 渲染组件包装层
const RenderComponent = (component: any) => {// 判断 component 是否是一个h函数if (typeof component === 'object' && component.type) {return component}// 数组、字符串return h('div', component)
}
</script>

总结

封装 useForm 可以实现基于配置化得到 formValuecolumnonReset

基于 useForm 的结果封装 Form 组件。

最后

感谢你的阅读~

如果你有任何的疑问欢迎您在后台私信,我们一同探讨学习!

如果觉得这篇文章对你有所帮助,点赞、在看是最大的支持!

这篇关于表格封装之 useForm 封装的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/571729

相关文章

SpringBoot中封装Cors自动配置方式

《SpringBoot中封装Cors自动配置方式》:本文主要介绍SpringBoot中封装Cors自动配置方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录SpringBoot封装Cors自动配置背景实现步骤1. 创建 GlobalCorsProperties

利用Python开发Markdown表格结构转换为Excel工具

《利用Python开发Markdown表格结构转换为Excel工具》在数据管理和文档编写过程中,我们经常使用Markdown来记录表格数据,但它没有Excel使用方便,所以本文将使用Python编写一... 目录1.完整代码2. 项目概述3. 代码解析3.1 依赖库3.2 GUI 设计3.3 解析 Mark

Java利用poi实现word表格转excel

《Java利用poi实现word表格转excel》这篇文章主要为大家详细介绍了Java如何利用poi实现word表格转excel,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 一、每行对象类需要针对不同的表格进行对应的创建。package org.example.wordToEx

Java导入、导出excel用法步骤保姆级教程(附封装好的工具类)

《Java导入、导出excel用法步骤保姆级教程(附封装好的工具类)》:本文主要介绍Java导入、导出excel的相关资料,讲解了使用Java和ApachePOI库将数据导出为Excel文件,包括... 目录前言一、引入Apache POI依赖二、用法&步骤2.1 创建Excel的元素2.3 样式和字体2.

使用EasyExcel实现简单的Excel表格解析操作

《使用EasyExcel实现简单的Excel表格解析操作》:本文主要介绍如何使用EasyExcel完成简单的表格解析操作,同时实现了大量数据情况下数据的分次批量入库,并记录每条数据入库的状态,感兴... 目录前言固定模板及表数据格式的解析实现Excel模板内容对应的实体类实现AnalysisEventLis

JAVA封装多线程实现的方式及原理

《JAVA封装多线程实现的方式及原理》:本文主要介绍Java中封装多线程的原理和常见方式,通过封装可以简化多线程的使用,提高安全性,并增强代码的可维护性和可扩展性,需要的朋友可以参考下... 目录前言一、封装的目标二、常见的封装方式及原理总结前言在 Java 中,封装多线程的原理主要围绕着将多线程相关的操

使用Python实现表格字段智能去重

《使用Python实现表格字段智能去重》在数据分析和处理过程中,数据清洗是一个至关重要的步骤,其中字段去重是一个常见且关键的任务,下面我们看看如何使用Python进行表格字段智能去重吧... 目录一、引言二、数据重复问题的常见场景与影响三、python在数据清洗中的优势四、基于Python的表格字段智能去重

C++实现封装的顺序表的操作与实践

《C++实现封装的顺序表的操作与实践》在程序设计中,顺序表是一种常见的线性数据结构,通常用于存储具有固定顺序的元素,与链表不同,顺序表中的元素是连续存储的,因此访问速度较快,但插入和删除操作的效率可能... 目录一、顺序表的基本概念二、顺序表类的设计1. 顺序表类的成员变量2. 构造函数和析构函数三、顺序表

如何利用Python实现给Excel表格截图

《如何利用Python实现给Excel表格截图》这篇文章主要为大家详细介绍了如何利用Python实现给Excel表格截图功能,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 我搜索了网络上的方案,感觉把 Excel 表格转换为 html 再用 platwright 截图是比China编程较顺

Go语言利用泛型封装常见的Map操作

《Go语言利用泛型封装常见的Map操作》Go语言在1.18版本中引入了泛型,这是Go语言发展的一个重要里程碑,它极大地增强了语言的表达能力和灵活性,本文将通过泛型实现封装常见的Map操作,感... 目录什么是泛型泛型解决了什么问题Go泛型基于泛型的常见Map操作代码合集总结什么是泛型泛型是一种编程范式,允