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

最后

感谢你的阅读~

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

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/603572.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Unity 面试篇|(二)Unity基础篇 【全面总结 | 持续更新】

目录 1.Unity3d脚本从唤醒到销毁有着一套比较完整的生命周期&#xff0c;列出系统自带的几个重要的方法。2.Unity3D中的碰撞器和触发器的区别&#xff1f;3.物体发生碰撞的必要条件&#xff1f;4.简述Unity3D支持的作为脚本的语言的名称&#xff1f;5. .Net与Mono的关系&#x…

镜头选型和计算

3.5 补充知识 一、单像元分辨率&#xff08;单像素精度&#xff09; 单像素精度是表示视觉系统综合精度的指标&#xff0c;表示一个像元对应检测目标的实际物理尺寸&#xff0c;是客户重点关注的 视觉系统参数&#xff1b; 计算公式1&#xff1a;单像素精度视野范围FOV/相机分辨…

Unity 点击对话系统(含Demo)

点击对话系统 可实现点击物体后自动移动到物体附近&#xff0c;然后弹出对话框进行对话。 基于Unity 简单角色对话UI脚本的编写&#xff08;新版UI组件&#xff09;和Unity 关于点击不同物品移动并触发不同事件的结合体&#xff0c;有兴趣可以看一下之前文章。 下边代码为U…

【数据库原理】(11)SQL数据查询功能

基本格式 SELECT [ALL|DISTINCT]<目标列表达式>[,目标列表达式>]... FROM <表名或视图名>[,<表名或视图名>] ... [ WHERE <条件表达式>] [GROUP BY<列名 1>[HAVING <条件表达式>]] [ORDER BY <列名 2>[ASC DESC]];SELECT: 指定要…

QT上位机开发(文本编辑器的界面开发)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 文本编辑器是编程开发中经常使用到的一个软件&#xff0c;比如说notepad就是其中一种。这里说编写一个文本编辑器&#xff0c;并不是说真的要写一个…

Linux 部署 AI 换脸

我使用的系统是 Ubuntu 20.04 文章实操主要分为以下几个部分 1、python 环境安装 2、下载 FaceFusion 上传服务器 3、创建 python 虚拟环境 4、下载 FaceFusion 依赖&#xff08;这里的命令执行时间会很长&#xff0c;够你睡午觉了&#xff09; 5、运行 FaceFusion 6、开…

python基础—网络编程

网络基本协议 TCP协议 UDP协议 二者对比&#xff1a; 连接性&#xff1a; TCP是面向连接的协议&#xff0c;需要在传输数据之前先进行三次握手建立连接。而UDP是无连接的协议&#xff0c;可以直接发送数据&#xff0c;无需事先建立连接。 可靠性&#xff1a; TCP提供了数…

js数组元素的排序

JavaScript 中的数组可以使用多种方法进行排序。下面是一些常见的排序方法&#xff1a; sort() 方法 sort() 方法用于对数组的元素进行排序。默认情况下&#xff0c;sort() 方法将数组元素转换为字符串&#xff0c;然后按照字符的 Unicode 码点进行排序。这可能导致一些不符合…

Golang拼接字符串性能对比

g o l a n g golang golang的 s t r i n g string string类型是不可修改的&#xff0c;对于拼接字符串来说&#xff0c;本质上还是创建一个新的对象将数据放进去。主要有以下几种拼接方式 拼接方式介绍 1.使用 s t r i n g string string自带的运算符 ans ans s2. 使用…

如何将手机中termux用电脑的vnc显示

在电脑中我们同样需要下载 vnc 这里填写手机上的 IP&#xff1a;端口号 我的是 10.11.166.219:5902 下面填名字然后 手机端 输入sshd开始ssh这边就可以连接啦

java spring mvc 初探 web搭建过程详解

提前准备安装tomcat 设备&#xff1a;mac 第一步&#xff1a;下载 进入官网下载压缩包 注意&#xff1a;如果jdk版本是1.8&#xff0c;则tomcat需要v8才行&#xff0c;否则会报错 https://tomcat.apache.org/ 第二步&#xff1a;解压 解压后路径 /Users/you/Library/tomcat…

使用PyTorch实现去噪扩散模型

在深入研究去噪扩散概率模型(DDPM)如何工作的细节之前&#xff0c;让我们先看看生成式人工智能的一些发展&#xff0c;也就是DDPM的一些基础研究。 VAE VAE 采用了编码器、概率潜在空间和解码器。在训练过程中&#xff0c;编码器预测每个图像的均值和方差。然后从高斯分布中对…

CAN协议

文章目录 CAN介绍CAN的优势多主控制通信速度较快&#xff0c;通信距离远具有错误检测、错误通知和错误恢复功能故障封闭功能连接节点多 ISO11519-2物理层特性ISO11898物理层特性CAN 收发芯片 JTA1050 CAN 协议5 种帧5种帧介绍数据帧的构成帧起始仲裁段控制段数据段CRC段ACK段帧…

一文讲透使用Python绘制双纵轴线图

双纵轴线图主要用来展示两个因变量和一个自变量的关系&#xff0c;并且两个因变量的数值单位不同。具体来说&#xff0c;双纵轴线图是指在一幅图上有一个横轴和两个纵轴&#xff0c;适用于三个变量。两个纵轴分别表示一个变量&#xff0c;横轴变量同时适用于两个纵轴上的变量&a…

报错curl: (6) Could not resolve host: raw.githubusercontent...的解决办法

我起初想要在macOS系统安装pip包&#xff0c;首先在终端安装homebrew&#xff0c;敲了命令&#xff1a;/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent...)" 之后触发的报错&#xff0c;报错内容&#xff1a;curl: (6) Could not resolve host: raw.…

【大数据】Flink CDC 的概览和使用

Flink CDC 的概览和使用 1.什么是 CDC2.什么是 Flink CDC3.Flink CDC 前生今世3.1 Flink CDC 1.x3.2 Flink CDC 2.x3.3 Flink CDC 3.x 4.Flink CDC 使用5.Debezium 标准 CDC Event 格式详解 1.什么是 CDC CDC&#xff08;Change Data Capture&#xff0c;数据变更抓取&#xf…

专业级的渗透测试服务,助力航空业数字化安全启航

​某知名航空公司是中国首批民营航空公司之一&#xff0c;运营国内外航线200多条&#xff0c;也是国内民航最高客座率的航空公司之一。在数字化发展中&#xff0c;该航空公司以数据驱动决策&#xff0c;通过精细化管理、数字创新和模式优化等方式&#xff0c;实现了精准营销和个…

k8s之flink的几种创建方式

在此之前需要部署一下私人docker仓库&#xff0c;教程搭建 Docker 镜像仓库 注意&#xff1a;每台节点的daemon.json都需要配置"insecure-registries": ["http://主机IP:8080"] 并重启 一、session 模式 Session 模式是指在 Kubernetes 上启动一个共享的…

智慧旅游景区解决方案:PPT全文49页,附下载

关键词&#xff1a;智慧景区建设&#xff0c;智慧旅游平台&#xff0c;智慧旅游运营检测系统项目&#xff0c;智慧文旅&#xff0c;智慧景区开发与管理&#xff0c;智慧景区建设核心&#xff0c;智慧景区开发与管理 一、智慧景区建设现状 1、基础设施建设&#xff1a;智慧景区…

推荐收藏!万字长文带入快速使用 keras

这些年&#xff0c;有很多感悟&#xff1a;一个人精力是有限的&#xff0c;一个人视野也有有限的&#xff0c;你总会不经意间发现优秀人的就在身边。 看我文章的小伙伴应该经常听我说过的一句话&#xff1a;技术要学会交流、分享&#xff0c;不建议闭门造车。一个人可以走的很…