用装饰器和ElementPlus,我们在NPM发布了这个好用的表格组件包

前言

如果你恰好也在使用 TypeScriptVue3ElementPlus,那么这个组件包你可以看看。

之前我们在一些文章里提到过如何使用 装饰器 来配置表格,也开源了一个 AirPower4T 的开源项目,但后来很多人反馈说,用子仓库的方式使用有些不太方便?

于是我们将 AirPower4T 这个项目做了拆分,分别独立成了下面的几个包:

  • AirPower-Transformer 核心的数据转换库,类似 class-transformer 的封装。

  • AirPower-Enum 核心的枚举封装库,这个也在之前的文章中提到过。

  • AirPower-i18n 核心的国际化封装库,在我们关于 I18n 的文章中也提到过。

  • AirPower-Util 核心的工具封装库。

  • AirPower-Web AirPower4T 的下一个库,做了解耦设计。

我们今天主要讲的是 AirPower-Web 这个库,这个库提供了 AirPower-Web 的核心功能,并且还封装了 ElementPlus 的一些组件,这样,AirPower-Web 就可以作为替代 AirPower4T 的一个库使用。

我们今天要实现的表格是下面这个图:

快速开始

初始化项目我们就不再提了,这里我们从安装包开始:

安装 @airpower/web

可以选择你喜欢的方式安装:

bash 复制代码
npm install @airpower/web
# or
yarn add @airpower/web
# or
cnpm install @airpower/web
# or ...

声明表格使用的实体类

我们要实现的是一个物料的表格,按后端接口文档,我们知道表格有这么几个列:

  • name: 物料名称 字符串
  • materialType: 物料类型 枚举值
  • spc: 规格型号 字符串
  • code: 物料编码 字符串

而枚举值,我们使用 AirPower-Enum 这个库来封装:

ts 复制代码
import { WebColor, WebEnum } from '@airpower/web'

/**
 * # 物料类型枚举
 */
export class MaterialTypeEnum extends WebEnum {
  /** 自产品 */
  static readonly PRODUCT = new MaterialTypeEnum(1, '自产品')
    // 设置图中的灯,后面装饰器会用到
    .setColor(WebColor.SUCCESS)

  /** 外购品 */
  static readonly PURCHASE = new MaterialTypeEnum(2, '外购品')
    .setColor(WebColor.WARNING)
}

于是我们可以很轻易的声明出这个物料的实体类,

请注意仔细看下面的一些注释:

ts 复制代码
@Model({
  // 标记这个东西叫做 物料。。。
  label: '物料',
})
export class MaterialEntity extends BaseEntity {
  // 标记为表格列
  @Table({
    // 这个列是可以复制的
    copy: true,
    // 这个列在列选择器中必须展示,无法取消
    force: true,
  })
  // 标记这个列可以搜索
  @Search()
  @Field({
    // 标记这个列的名字
    label: '物料编码',
  })
  code!: string

  @Table()
  @Search()
  @Field({
    label: '物料名称',
  })
  name!: string

  @Table({
    // 标记这个列要显示一个枚举的颜色灯
    color: true,
    // 列的宽度
    width: 100,
  })
  @Search()
  @Field({
    label: '物料类型',
    // 这个列要显示一个枚举字典
    dictionary: MaterialTypeEnum,
  })
  materialType!: number

  @Table({
    copy: true,
  })
  @Field({
    label: '规格型号',
  })
  spc!: string
}

实现实体对应的服务

有了这个实体的声明,那么我们可以使用这个物料的类来作为前后端对接数据的标准了。

当然,如果你的后端在你写完后要改,那可以继续标注一些装饰器来解决这些问题,可以参考 @airpower-transformer 这个库。

接下来,我们还要为这个实体类绑定一个服务:

ts 复制代码
export class MaterialService extends AbstractBaseService<MaterialEntity> {
  // 表示这个服务绑定的是 物料 这个实体类
  entityClass = MaterialEntity
  // 表示这个服务请求后端的 /material/xxxx 
  baseUrl = 'material'
}

好了,都准备好了,让我们开始写页面了

实现页面

好,接下来可以写页面 list.vue 了:

html 复制代码
<template>
  <APanel>
    <!-- 开始使用 ATable 这个我们提供的组件 -->
    <ATable
      v-loading="isLoading" // 绑定一个 Loading
      :data-list="response.list" // 绑定一个数据列表
      :entity="MaterialEntity" // 绑定一个实体类
      :service="MaterialService" // 绑定一个服务

      @xxx="onXxx" // 如果你需要各种方法,可以继续加
    />
    <template #footerLeft>
      <APage
        :response="response"
        @changed="onPageChanged"
      />
    </template>
  </APanel>
</template>

<script lang="ts" setup>
// 从Hooks中拿出这些属性和方法
const { 
  isLoading, 
  response, 
  // 更多你需要的属性
  onPageChanged, 
  onXxx // 更多你需要的事件
  } = useTable(MaterialService)
</script>

这就搞定了。

我们通过这种方式实现了:

  • 列的定义
  • 列的自定义选择(实例图右上角的按钮)
  • 列的各种状态

如果你的表格需要各种事件的处理,比如 添加、修改、删除、详情、排序、分页、选择等等等各种方法,都可以为 ATable 绑定各种 @xxx="onXxx",基本上很多的事件都可以从 useTable 这个 Hook 中拿到。

更多的自定义

你可能会说,我们家后端的标准和你不太一样,比如:接口名字的不同,数据结构的不同,状态码的不同等等,我们来一个个解决:

接口访问的名字不同

你可以通过 baseUrl 属性来配置公共的部分,也可以通过重写 urlForXXX 属性来覆盖我们的默认的访问路径:

ts 复制代码
export class MaterialService extends AbstractBaseService<MaterialEntity> {
  entityClass = MaterialEntity

  baseUrl = 'material'

  // 则你们的接口地址是 /material/paaaaaaaaage
  protected urlGetPage: string = 'paaaaaaaaage'
}

如果连 baseUrl 都需要自定义,你可以直接重写对应的方法:

ts 复制代码
export class MaterialService extends AbstractBaseService<MaterialEntity> {
  entityClass = MaterialEntity

  baseUrl = 'material'

  getPage(request: QueryRequest<MaterialEntity>, apiUrl?: string): Promise<QueryResponsePage<MaterialEntity>> {
    // 等于 /wuliao/getPage
    const responsePage: QueryResponsePage<E> = await this.api("getPage","物料").post(request, QueryResponsePage<E>)
    responsePage.list = responsePage.list.map(json => Transformer.parse(json, this.entityClass))
    return responsePage
  }
}

数据结构的不同

和上面一样,你可以通过重写这部分不一样的方法来解决:

ts 复制代码
export class MaterialService extends AbstractBaseService<MaterialEntity> {
  entityClass = MaterialEntity

  baseUrl = 'material'

  getPage(request: QueryRequest<MaterialEntity>, apiUrl?: string): Promise<QueryResponsePage<MaterialEntity>> {
    // 等于 /wuliao/getPage
    const responsePage: YourTypeClass = await this.api(apiUrl).post(request, YourTypeClass)
  }

  // 如果请求的数据类型也不同,那就直接写个新方法?

  // getMyPage.....
}

状态码的不同

AirPower-Web 这个包的 Http 服务也是提供了大量灵活的方案来解决这些问题:

通过 WebConfig

你可以通过 WebConfig 来配置全局的 HTTP 服务状态码:

ts 复制代码
//main.ts
WebConfig.successCode = 200000
// 更多的配置

通过重写 Service 调用你自己的方法

你可以和上面重写 getPage 方法一样,重新调用一个自己的其他方法即可。

还有太多太多

我们本意是不太建议大家各种重写来解决问题的,因为 @airpower/web 这个包本身是和 AirPower4J 这个后端标准后端服务来做兼容的全栈项目的,我们建议如果使用的话,可以考虑和 AirPower4J 这个项目的数据结构做一些兼容处理 :)

还提供了些什么

我们在 SPMS_Web 这个项目里,也使用了 AirPower-Web 这个包,具体可以参考这个开源项目。

当然,你也可以看看我们的设计图。

今天就这样啦,再见了各位。

附上设计图:

相关推荐
LuciferHuang6 小时前
震惊!三万star开源项目竟有致命Bug?
前端·javascript·debug
GISer_Jing6 小时前
前端实习总结——案例与大纲
前端·javascript
天天进步20156 小时前
前端工程化:Webpack从入门到精通
前端·webpack·node.js
姑苏洛言7 小时前
编写产品需求文档:黄历日历小程序
前端·javascript·后端
知识分享小能手7 小时前
Vue3 学习教程,从入门到精通,使用 VSCode 开发 Vue3 的详细指南(3)
前端·javascript·vue.js·学习·前端框架·vue·vue3
姑苏洛言7 小时前
搭建一款结合传统黄历功能的日历小程序
前端·javascript·后端
你的人类朋友8 小时前
🤔什么时候用BFF架构?
前端·javascript·后端
知识分享小能手9 小时前
Bootstrap 5学习教程,从入门到精通,Bootstrap 5 表单验证语法知识点及案例代码(34)
前端·javascript·学习·typescript·bootstrap·html·css3
一只小灿灿9 小时前
前端计算机视觉:使用 OpenCV.js 在浏览器中实现图像处理
前端·opencv·计算机视觉