一、写在前面
本文使用 Vue3
+ Vite
+ TypeScript
+ Element-Plus
作为主要的环境和前提,不讨论其他状况下的问题。
在常见的后台管理系统中,前端往往需要大量的表格来显示数据,而表单的封装也经常需要重复的代码,因此,我们希望有一个简单的封装,使得我们能够快速地使用。
二、其他人的常规实现
-
封装个
Table
组件,要求传入需要显示的数据数组,以及需要显示的列的配置项JSON
数组,根据不同的配置项来展示不同形式的列; -
调用时声明一堆
JSON
来配置每个列的参数,高级一些的声明了一个interface
或者type
来定义表格列支持的配置列参数; -
声明一个
interface
或者type
来作为显示数据的属性,如User
-
大功告成。
嗯,看起来很棒的封装,先点个赞了再来吐槽问题。
三、我们不太一样的封装
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配置项封装表单?你不会还不知道装饰器吧?
如有侵权请联系站点删除!
技术合作服务热线,欢迎来电咨询!