一、先来看实现后的代码
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>
实现的效果如下图
上面的 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
的代码提示和数据类型约束
2. 基于 Java Annotation
思路的装饰器配置实现
将所有关于 物料
的表格、表单、数据转换方式、验证、字典、搜索等配置信息全一股脑在 MaterialEntity
这个物料实体中通过装饰器来配置,其他使用的地方(如表格、表单、搜索框等)都可以直接传入这个实体类来读取这些配置,达到一处修改,处处生效的效果。
3. 基于面相对象思维的前端开发
本来这篇文章不太适合在面向对象上描述过多, 但上面都已经提到了很多 TypeScript
的新特性,这里再不说一下有点说不过去了。
先抨击下掘金上很多关于 TypeScript
的文章:
- 没有新意: 搬运教程、教你配环境和入门、教你用
interface
来约束数据结构 - 误导用户: 如上一点所说,搞得大伙都忘了
class
extends
implements
的存在 - 类型体操: 正如我之前在很多掘金文章下的评论
类型体操是Typescript最好玩的,也恰好是最不应该存在的
- 还有太多:下次在抨击吧,文章都快给带偏了。
总之,基于面向对象,前端目前还大有可为,一些很古老很传统的思维在前端上或许从来没有淋漓尽致过,但我相信,应该很快了,因为:
TypeScript
在前端的流行装饰器
在前端的普及(隔壁不争气的php
都开始出装饰器了)- 面向对象、泛型、AOP思想、依赖注入等后端概念向前端的转移
Vue.js
这种入门相对较低的前端框架的出现- 贩卖前端已死焦虑等文章的出现
- 还有很多
三、 That’s all.
今天就说这么多,有兴趣的可以一起交流学习。
没错,我是个 Java仔
、运维仔
、前端仔
, 不过,也不仅仅。