likes
comments
collection
share

基于装饰器-我又是这么用TypeScript处理表格配置的

作者站长头像
站长
· 阅读数 16

一、先来看实现后的代码

1. view层

<template>
    <ATable
      v-loading="isLoading"
      :data-list="response.list"
      show-detail
      :entity="MaterialEntity"
      @on-detail="onDetail"
      @on-edit="onEdit"
      @on-delete="onDelete"
      @on-sort-change="request.sort = $event; getList()"
    />
</template>
<script lang="ts" setup>
import { ref } from 'vue'
import { ATable } from '@/airpower/component'
import { MaterialEntity } from '@/model/entity/MaterialEntity'
import { MaterialService } from '@/service/MaterialService'
import { AirRequestPage } from '@/airpower/model/AirRequestPage'
import { AirResponsePage } from '@/airpower/model/AirResponsePage'

const isLoading = ref(false)
const response = ref(new AirResponsePage<MaterialEntity>())
const request = ref(new AirRequestPage(MaterialEntity))

async function getList() {
  response.value = await MaterialService.create(isLoading).getPage(request.value)
}

async function onEdit(row: MaterialEntity) {
  await AirDialog.show(MaterialEditor, row)
  getList()
}

async function onDelete(data: MaterialEntity) {
  await MaterialService.create(isLoading).delete(data.id, '删除物料成功')
  getList()
}

async function onAdd() {
  await AirDialog.show(MaterialEditor)
  getList()
}

async function onDetail(data: MaterialEntity) {
  await AirDialog.show(MaterialDetail, data)
  getList()
}

getList()
</script>

实现的效果如下图

基于装饰器-我又是这么用TypeScript处理表格配置的

上面的 ATable 是我们封装的一个表格组件, 默认会按照传入 entity 属性配置的实体类进行表格列的读取——当然,每个列都支持插槽,如下:

<ATable :entity="MaterialEntity">
    <template #materialName="{data}">
        我是物料: {{ data.materialName }}
    <template>
</ATable>

这样,即使 materialName 属性标记了乱七八糟的配置, 都不如这个插槽的优先级来得高。

2. Model层

import { ClassName, Dictionary, FieldName } from '@/airpower/decorator/Custom'
import { TableField } from '@/airpower/decorator/TableField'
import { BaseEntity } from '@/base/BaseEntity'

/**
 * # 物料实体
 * @author Hamm
 */
@ClassName('物料')
export class MaterialEntity extends BaseEntity {
  @TableField({
    forceShow: true,
    isCopyField: true,
  })
  @FieldName('物料名称') materialName!: string

  @TableField()
  @FieldName('规格型号') materialSpc!: string

  @Dictionary(MaterialTypeDictionary)
  @TableField({
    showColor: true,
    width: 100,
  })
  @FieldName('物料类型') materialType!: MaterialType
}

/**
 * # 物料类型
 * @author Hamm
 */
export enum MaterialType {
  /**
   * # 公共物料
   */
  PUBLIC = 1,

  /**
   * # 私有物料
   */
  PRIVATE = 2
}

/**
 * # 物料类型枚举字典
 * @author Hamm
 */
export const MaterialTypeDictionary = AirDictionaryArray.createCustom<IMaterialTypeDictionary>([
  {
    key: MaterialType.PUBLIC,
    label: '公共物料',
    color: AirColor.SUCCESS,
    other: 1,
  },
  {
    key: MaterialType.PRIVATE,
    label: '私有物料',
    color: AirColor.NORMAL,
    other: 2,
  },
])

上述模型中一些项目中的其他依赖就不再一一附上了, 比如:

  • 基类BaseEntity(其中包含了一些固定实体必须包含的字段,如 id createTime 等等)

  • 字典数组创建字典的方法 AirDictionaryArray.createCustom<T extends IDictionary>(list: T[]): AirDictionaryArray<T>

  • 自定义的特殊字典 IMaterialTypeDictionary(继承来自标准字典 IDictionary, 一些组件限定了必须传入标准字典以及其子类或接口)

  • @TableField(config: ITableFieldConfig) 的接口 ITableFieldConfig,其实就是包含了一些通用性的表格配置,如下:

    /**
     * # 表格的字段配置接口
     * @author Hamm
     */
    export interface ITableFieldConfig extends IFieldConfig {
      /**
       * # 表格字段宽度
       */
      width?: number;
    
      /**
       * # 枚举字典
       * ---
       * ### 💡 如字典配置了 ```color```, 可使用 ```showColor``` 配置项显示颜色
       */
      dictionary?: AirDictionaryArray<IDictionary>;
    
      /**
       * # 如是日期 可传入转换规则
       */
      dateTimeFormatter?: AirDateTimeFormatter | string;
    
      /**
       * # 是否显示枚举字典的颜色状态灯
       * ---
       * ### 💡 如果显示 请确保传入的 ```dictionary``` 配置了 ```color```
       */
      showColor?: boolean;
    
      /**
       * # 是否字段允许排序 默认不排序
       * ---
       * ### 💡 ```custom``` 为自定义排序, ```ATable``` 组件将触发 ```onSortChange``` 事件
       */
      sortable?: boolean | 'custom';
    
      /**
       * # 列对齐方式
       */
      align?: 'right' | 'left' | 'center';
    
      /**
       * # 后置文字
       * ---
       * ### 💡 一般用于显示一些类似 单位 的文本
       */
      suffixText?: string;
    
      /**
       * # 是可复制的字段
       * ---
       * ### 💡 该表格列允许一键复制
       */
      isCopyField?: boolean;
    
      /**
       * # 图片字段
       * ---
       * ### 💡 可配置 ```imageWidth```, ```imageHeight``` 等
       */
      isImage?: boolean;
    
      /**
       * # 图片的宽度 默认60
       */
      imageWidth?: number;
    
      /**
       * # 图片的高度 默认60
       */
      imageHeight?: number;
    
      /**
       * # 空数据兜底字符串
       * ---
       * ### 💡 可在 ```AirConfig.defaultTableEmptyValue``` 进行全局兜底,
       * 此配置项将优先使用 仅支持普通字段和挂载字段
       */
      emptyValue?: string
      
      // 还有超多配置,不怕麻烦,反正有 ```TypeScript``` 的自动提示, 这很爽。
    }
    

3. Service层

/**
 * # 物料接口服务
 * @author Hamm
 */
export class MaterialService extends AbstractBaseService<MaterialEntity> {
  entityClass = MaterialEntity

  baseUrl = 'material'
}

同样的,上面的Service类也不再附上 AbstractBaseService<E extends BaseEntity> 了, 总之,一些通用的增删改查在 Service 基类中已经实现了

二、 为什么要这么写

其实是 Java 写习惯了, 任何东西都习惯用类或者接口来约束下,限制我需要的参数类型和数据结构。

还有 TypeScript 在 vscode 的加持下很多代码提示和重构的方便性。 当然, 只写 JavaScript 或者 AnyScript 是体会不到这种快乐的。

1. 基于 TypeScript 的代码提示和数据类型约束

基于装饰器-我又是这么用TypeScript处理表格配置的

基于装饰器-我又是这么用TypeScript处理表格配置的

2. 基于 Java Annotation 思路的装饰器配置实现

将所有关于 物料 的表格、表单、数据转换方式、验证、字典、搜索等配置信息全一股脑在 MaterialEntity 这个物料实体中通过装饰器来配置,其他使用的地方(如表格、表单、搜索框等)都可以直接传入这个实体类来读取这些配置,达到一处修改,处处生效的效果。

3. 基于面相对象思维的前端开发

本来这篇文章不太适合在面向对象上描述过多, 但上面都已经提到了很多 TypeScript 的新特性,这里再不说一下有点说不过去了。

总之,基于面向对象,前端目前还大有可为,一些很古老很传统的思维在前端上或许从来没有淋漓尽致过,但我相信,应该很快了,因为:

  • TypeScript 在前端的流行
  • 装饰器 在前端的普及(隔壁不争气的php都开始出装饰器了)
  • 面向对象、泛型、AOP思想、依赖注入等后端概念向前端的转移
  • Vue.js 这种入门相对较低的前端框架的出现
  • 贩卖前端已死焦虑等文章的出现
  • 还有很多

三、 That's all.

今天就说这么多,有兴趣的可以一起交流学习。

没错,我是个 Java仔运维仔前端仔, 不过,也不仅仅。

2023-07-26 17:31:14 更新

项目已经在Github开源: github.com/HammCn/AirP…

四、 更多文章可以查看这个专栏

转载自:https://juejin.cn/post/7259326557206020154
评论
请登录