表格封装之 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

相关文章

Golang如何对cron进行二次封装实现指定时间执行定时任务

《Golang如何对cron进行二次封装实现指定时间执行定时任务》:本文主要介绍Golang如何对cron进行二次封装实现指定时间执行定时任务问题,具有很好的参考价值,希望对大家有所帮助,如有错误... 目录背景cron库下载代码示例【1】结构体定义【2】定时任务开启【3】使用示例【4】控制台输出总结背景

Java Web实现类似Excel表格锁定功能实战教程

《JavaWeb实现类似Excel表格锁定功能实战教程》本文将详细介绍通过创建特定div元素并利用CSS布局和JavaScript事件监听来实现类似Excel的锁定行和列效果的方法,感兴趣的朋友跟随... 目录1. 模拟Excel表格锁定功能2. 创建3个div元素实现表格锁定2.1 div元素布局设计2.

Python中对FFmpeg封装开发库FFmpy详解

《Python中对FFmpeg封装开发库FFmpy详解》:本文主要介绍Python中对FFmpeg封装开发库FFmpy,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐... 目录一、FFmpy简介与安装1.1 FFmpy概述1.2 安装方法二、FFmpy核心类与方法2.1 FF

Python实现精准提取 PDF中的文本,表格与图片

《Python实现精准提取PDF中的文本,表格与图片》在实际的系统开发中,处理PDF文件不仅限于读取整页文本,还有提取文档中的表格数据,图片或特定区域的内容,下面我们来看看如何使用Python实... 目录安装 python 库提取 PDF 文本内容:获取整页文本与指定区域内容获取页面上的所有文本内容获取

使用C#删除Excel表格中的重复行数据的代码详解

《使用C#删除Excel表格中的重复行数据的代码详解》重复行是指在Excel表格中完全相同的多行数据,删除这些重复行至关重要,因为它们不仅会干扰数据分析,还可能导致错误的决策和结论,所以本文给大家介绍... 目录简介使用工具C# 删除Excel工作表中的重复行语法工作原理实现代码C# 删除指定Excel单元

使用Python实现网页表格转换为markdown

《使用Python实现网页表格转换为markdown》在日常工作中,我们经常需要从网页上复制表格数据,并将其转换成Markdown格式,本文将使用Python编写一个网页表格转Markdown工具,需... 在日常工作中,我们经常需要从网页上复制表格数据,并将其转换成Markdown格式,以便在文档、邮件或

Python实现pdf电子发票信息提取到excel表格

《Python实现pdf电子发票信息提取到excel表格》这篇文章主要为大家详细介绍了如何使用Python实现pdf电子发票信息提取并保存到excel表格,文中的示例代码讲解详细,感兴趣的小伙伴可以跟... 目录应用场景详细代码步骤总结优化应用场景电子发票信息提取系统主要应用于以下场景:企业财务部门:需

Python实现获取带合并单元格的表格数据

《Python实现获取带合并单元格的表格数据》由于在日常运维中经常出现一些合并单元格的表格,如果要获取数据比较麻烦,所以本文我们就来聊聊如何使用Python实现获取带合并单元格的表格数据吧... 由于在日常运维中经常出现一些合并单元格的表格,如果要获取数据比较麻烦,现将将封装成类,并通过调用list_exc

一文详解如何在Vue3中封装API请求

《一文详解如何在Vue3中封装API请求》在现代前端开发中,API请求是不可避免的一部分,尤其是与后端交互时,下面我们来看看如何在Vue3项目中封装API请求,让你在实现功能时更加高效吧... 目录为什么要封装API请求1. vue 3项目结构2. 安装axIOS3. 创建API封装模块4. 封装API请求

使用Java将各种数据写入Excel表格的操作示例

《使用Java将各种数据写入Excel表格的操作示例》在数据处理与管理领域,Excel凭借其强大的功能和广泛的应用,成为了数据存储与展示的重要工具,在Java开发过程中,常常需要将不同类型的数据,本文... 目录前言安装免费Java库1. 写入文本、或数值到 Excel单元格2. 写入数组到 Excel表格