还在用一堆JSON配置项封装表单?你不会还不知道装饰器吧?

还在用一堆JSON配置项封装表单?你不会还不知道装饰器吧?

技术博客 admin 510 浏览

一、写在前面

本文使用 Vue3 + Vite + TypeScript + Element-Plus 作为主要的环境和前提,不讨论其他状况下的问题。

在常见的后台管理系统中,前端往往需要大量的表格来显示数据,而表单的封装也经常需要重复的代码,因此,我们希望有一个简单的封装,使得我们能够快速地使用。

二、其他人的常规实现

  1. 封装个 Table 组件,要求传入需要显示的数据数组,以及需要显示的列的配置项 JSON 数组,根据不同的配置项来展示不同形式的列;

  2. 调用时声明一堆 JSON 来配置每个列的参数,高级一些的声明了一个 interface 或者 type 来定义表格列支持的配置列参数;

  3. 声明一个 interface 或者 type 来作为显示数据的属性,如 User

  4. 大功告成。

嗯,看起来很棒的封装,先点个赞了再来吐槽问题。

三、我们不太一样的封装

1)封装原则

  • 相同的数据或配置,全局只存在一份。
  • 不允许使用 interface type 作为显示数据的类型定义方式
  • 不允许使用 JSON 作为配置项的声明方式

2)封装思路

  • 1. 定义数据结构类

首先,我们需要定义一些标准化的基类,如 Entity,Service等;

typescript
复制代码
class BaseEntity{ id!: number createTime!: number } class AbstractBaseService<E extends BaseEntity> { abstract baseUrl: string getDetail(id: number): Promise<E>{ // 查询详情 } getList(): Promise<E[]>{ // 查询列表 } update(entity: E): Promise<E>{ // 更新 } delete(id: number): Promise<E>{ // 删除 } }

接下来,我们定义用户的实体和Service

typescript
复制代码
class User extends BaseEntity{ name!: string age!: number } class UserService extends BaseService<UserEntity>{ // ... baseUrl = "user" }
  • 2. 声明装饰器配置

接下来,我们需要定义一些表格可以使用的配置项

typescript
复制代码
class ITableConfig{ key!: string label!: string width?: string align?: "left"|"center"|"right" // 更多配置 }
  • 3. 定义装饰器

接下来我们需要定义一个装饰器,用来在类上配置一些参数:

typescript
复制代码
function Table(config: ITableFieldConfig = {}): Function { return (target: any, key: string) => { config.key = key return AirDecorator.setFieldConfig(target, key, FIELD_CONFIG_KEY, config, FIELD_LIST_KEY) } }
  • 4. 封装Table组件

html
复制代码
<template> <el-table :data="data"> <el-table-column v-for="item in fields" :key="item.key"> <!--解析 item 配置项 渲染不同的表格列 这里大同小异 --> </el-table-column> </el-table> <template> <script lang="ts" setup generic="E extends BaseEntity"> const props = defineProps({ /** * # 表格显示的数据数组 */ dataList: { type: Array<E>, required: true, }, /** * # 表格绑定的数据实体 */ entity: { type: Function as unknown as PropType<ClassConstructor<E>>, required: true, }, }) const fields = computed(()=>{ // 查询装饰器上配置的表格列配置列表 return AirDecorator.getFieldList(props.entity) }) // 一些常规的事件抛出 const emits = defineEmits(["add","delete","detail","edit"]) </script>

上面只演示了一些简单的需求实现,当然我们实际的封装过程中提供了大量的参数和配置可供使用。

  • 5. Hooks封装

使用过程中,我们还是需要去传入大量的配置和参数,为了简化操作,我们继续封装点 Hooks

typescript
复制代码
export function useAirTable<E extends BaseEntity, S extends AbstractBaseService<E>>(entityClass: ClassConstructor<E>, serviceClass: ClassConstructor<S>, option: IUseTableOption<E> = {}): IUseTableResult<E, S> { /** * # 表格Hook返回对象 */ const result = airTableHook(entityClass, serviceClass, option) /** * # 表格行编辑事件 * @param row 行数据 */ async function onEdit(row: E) { } /** * # 表格行删除事件 * @param row 行数据 */ async function onDelete(row: E) { } /** * # 表格行禁用事件 * @param row 行数据 */ async function onDisable(row: E) { } /** * # 表格行启用事件 * @param row 行数据 */ async function onEnable(row: E) { } return Object.assign(result, { onEdit, onDelete, onDisable, onEnable, }) as IUseTableResult<E, S> }

四、我们真实封装的使用代码示例

1. 实体定义

typescript
复制代码
@Model('供应商') export class SupplierEntity extends BaseEntity { /** * # 供应商编码 */ @Table({ copyField: true, forceShow: true, orderNumber: 99, }) @Form({ orderNumber: 99, requiredString: true, }) @Field('供应商编码') code!: string /** * # 供应商名称 */ @Table({ forceShow: true, }) @Form({ orderNumber: 98, requiredString: true, }) @Field('供应商名称') name!: string /** * # 联系电话 */ @Table() @Form({ mobilePhone: true, }) @Field('联系电话') phone!: string }

2. Service定义

typescript
复制代码
export class SupplierService extends AbstractBaseService<SupplierEntity> { entityClass = SupplierEntity baseUrl = 'supplier' }

3. List.vue

html
复制代码
<template> <APanel> <AToolBar :loading="isLoading" :entity="SupplierEntity" :service="SupplierService" @on-add="onAdd" @on-search="onSearch" > <template #afterButton> <AButton v-if="selectList.length > 0" type="DELETE_LIST" danger @click="AirNotification.warning('就是玩'); selectList = []" > 批量删除 </AButton> </template> </AToolBar> <ATable v-loading="isLoading" :data-list="response.list" :entity="SupplierEntity" :select-list="selectList" show-select :ctrl-width="90" @on-edit="onEdit" @on-delete="onDelete" @on-sort-change="onSortChanged" @on-select="onSelected" /> <template #footerLeft> <APage :response="response" @on-change="onPageChanged" /> </template> </APanel> </template> <script lang="ts" setup> const { isLoading, response, selectList, onSearch, onAdd, onDelete, onEdit, onPageChanged, onSortChanged, onSelected, } = useAirTable(SupplierEntity, SupplierService, { editView: SupplierEditor, }) </script> <style scoped lang="scss"></style>

五、总结

通过上述的封装,我们可以非常方便的使用表格组件,而且关于相同的数据配置都集中在了对应的实体类的装饰器上,比如与用户相关的表格、表单、验证器、搜索条件等等,非常方便。

基于装饰器的更多文章,可以阅读我的专栏 “用TypeScript写前端”。

相关的源代码可以参考:

Github: github.com/HammCn/AirP…

Gitee: gitee.com/air-power/A…

源文:还在用一堆JSON配置项封装表单?你不会还不知道装饰器吧?

如有侵权请联系站点删除!

技术合作服务热线,欢迎来电咨询!