elementui表格描述-前端如何打破CRUD循环

据说西西弗斯是一位非常英明的国王,但他也非常自负和骄傲。 他甚至敢欺骗神灵,将伤员带回人间。 为此,他受到了宙斯的惩罚,每天晚上他被迫推一块巨石上山,但当他接近山顶时,巨石总是会掉下来,他不得不重新开始推石头,被困在里面。如此循环直到永远……

很多开发工作也是那么单调、枯燥,比如明天要讲的中后台开发的场景。 中后台业务基本上就是一些数据、图表的增删改查。 技术集中度不高,更容易常态化。

前端如何摆脱这些CRUD单调循环呢?

低代码

这几年,后端低代码非常流行。 这些低代码平台一般都支持创建数据模型,并一键生成相应的增删改查页面:

模型驱动生成的页面

本文所说的低代码是狭义上的低代码,你可以把它看成是一个可视化构建平台。

过去几年,低代码一直是“雷大,雨小”,这与今天的AI颇为相似。

不管是大厂还是小厂,都在搞低代码,包括笔者也参与过几个低代码项目elementui表格描述,但是小厂无法支撑这样的资源投入,最后就死掉了。 相信很多读者也都经历过这些情况。 大多数企业只是追随营销噱头,盲目跟风,根本没有规划和沉淀这些低代码的平台资源。

作为后端,能够参与低代码项目的开发是一件非常令人兴奋的事情。 毕竟能由后端主导的项目是少数。 架构、组件设计、编辑器实现都非常可玩elementui表格描述,可以和同行吹牛很久。

作为用户(开发者)呢? 或许有敌意和指责,但无论如何,都没有发挥出市场预期的价值。

主要原因是:它不能解决复杂的问题。

低代码直观,门槛低,早期开发真的很爽。 可视化数据建模、拖拽生成页面、流程编排,可以快速开发一些简单的业务。

然而,根据ChatGPT的估计,软件编码本身在开发过程中所占的比例仅为20%至30%左右。 而且业务不断变化,代码需要不断迭代。 试想一下如何在这个低代码平台上构建和检索?

总的来说,有一些缺点:

当然,低代码有低代码适用的场景,比如解决特定领域问题(营销活动页面、海报、大数据屏、表单引擎、商城家装、首页)、POC验证等。 也就是一些临时的/非核心的敏感业务。

目前,一些低代码平台也具备“代码输出能力”,这使得二次开发具有一定的可行性。

人工智能增强的低代码可能会变得更加强大。 但笔者仍保持观望心态。 毕竟,准确描述软件需求本身就是软件开发的难点之一。 否则,我们不需要DDD中的各种方法论,不需要召开各种拉通会议,也许我们也不需要需求。 分析师、产品…

非专业用户直接描述制作软件的需求,大多是不切实际的猜测

中间形式

可视化低代码平台和专业代码之间是否存在中间形式? 既能保持像低代码平台一样的易用性,又能保持代码的灵活性和可维护性。

我认为这就是DSL(领域特定语言),对吧? DSL背后是特定领域问题的具体表示,其方法和句型是次要的。

DSL的方式有很多种,可以创建新的微语言(如SQL、GraphQL); 它可以是 JSON 或 YAML 形式; 你也可以基于现有的元语言(如Ruby、Groovy、Rust...)来创建,这些元语言提供了元编程能力,可以以简单优雅的方式表达领域问题,同时可以复用元语言本身的语言能力和基础设施。

严格来说,可视化低代码平台也是一种“可视化”DSL。 笔者认为其局限性更多来自于“可视化”。 相对而言,它的优势大部分来自于“可视化”。

这又涉及到持续了半个多世纪的争论:GUI vs CLI(编程/文本)。 《UNIX 编程艺术》对此进行了深入介绍。 命令行和命令语言比视觉套接字更具表现力,特别是对于复杂的任务。 此外,命令行界面是高度可脚本化的。 缺点是占用内存大、可用性差、透明度低。 当问题规模增大且程序行为变得更加单调、程序化和重复性时,CLI 也经常会发挥作用。

如果按照问题域的友好性和复杂性/规模两个维度来定义,可以画出如下曲线:

中间会有一个交点,之后命令行的简洁性和表现力比防止记忆负担更有价值。

《Anti-Mac Interface》一书还总结道:视觉套接字在处理少量对象的简单行为时效果很好,但当行为或对象数量减少时,直接操作很快就变成机械重复的苦差事……

也就是说,DSL的方式会限制DSL本身的表达能力。

正如前面提到的,如果“低代码”只是将原始编码工作转换为 GUI 形式,那么它没有多大意义,因为没有具体性。

反例:JSON GUI 与 JSON

JSON GUI 与 JSON

正例:VSCode案例

在json中设置

在gui中设置

充分利用 GUI,提供更好的目录组织、文本提示、数据输入约束和校准。

可以说GUI形式的用户体验更好,门槛越来越低,不需要关心底层细节。 其实它不一定是GUI带来的,而是可视化的结果。 GUI只不过是一种插入方式。

回到题外话,为了摆脱管理后台CRUD的“ Sysyphus Stone”:我们可以创建一个DSL,将管理端的各种场景抽象出来,封装复杂的实现细节和重复的工作,暴露简单性和高贵的用户界面(User Interface)。

概括。 DSL 是介于可视化低代码和专业代码之间的中间形式,它权衡易用性/灵活性和实现成本。 DSL的方式会直接影响其表达能力,但比方式更重要的是DSL对于特定问题领域的具体性。

为什么我们要重新发明一种语言,而是复用元语言的能力和生态,这基本上是零成本的。

抽象过程

典型的CRUD页面:

增删改查

分析过程:

后端CRUD主要由表单和表两个组件组成。

表单和表格又是由更原子的“字段”组成。字段的类型决定了存储类型、输入形式和显示方式

字段有两种状态:编辑状态和预览状态。 表格列和详情页一般处于预览状态,而表单和表格过滤器则使用编辑状态。

预览和编辑

参考低代码平台的组件库/节点库,我们可以将这种“场”提取为表单和表单的“原子”单位。 这里我们给它起个名字,姑且称之为原版(Atomic)。

低代码平台

原来会取代组件库上面的表单组件作为我们CRUD页面的最小单位。 它有且仅有的职责:

原来的

然后结合原来的实现表单和表格组件,满足CRUD场景:

增删改查

理想情况下,我们只需要声明性地指定表的列和原始类型,其余的技术细节应该被隐藏。 表伪代码示例:

# 创建包含 名称、创建时间、状态三列的表格,其中可以搜索名称和创建时间
Table(
  columns(
    column(名称,name, queryable=true)
    column(创建时间, created, data-range, queryable=true)
    column(状态, status, select, options=[{label: 启用,value: 1, {label: 禁用, value: 0}}])
  )
)

表单伪代码示例:

# 创建包含 名称、状态、地址的表单
Form(
  item(名称,name, required=true)
  item(状态,status, select, options=[{label: 启用,value: 1, {label: 禁用, value: 0}}])
  item(地址, address, address)
)

如上所示,本质上,开发人员应该只关注业务数据本身,而应该忽略后端技术实现的噪音(例如状态管理、显示样式、分页、异常处理等)。

为了满足不同的需求,表格也会衍生出不同的解读方式:

概览图

原始文档+核心表单/表格能力+场景/展示方式,一套“组合拳”基本可以满足常见的后台CRUD需求。

约定小于配置

前端在开发过程中处于相对下游。 如果上游的产品定义、UI设计、后端契约不一致,就很难处理各种混乱的差异,复用性也就无从谈起。

最大限度地减少样板代码和通信成本,实现开箱即用的功效。 我们最好把上下游连接起来,确定相关的规格。 前端开发者要发挥好串联作用。

这些规范包括但不限于:

概览图

组件库可以外部化此约定,或提供全局形式的配置。 这些规格固化后,我们就可以享受开箱即用的快感了。

实施示例

基于以上思路,我们开发了一套组件库(基于Vue和element-ui)配合一套简单的DSL来快速开发CRUD页面。

这组组件库结合了我们自己的约定。 因此,它可能不适合外部常见场景。 这篇文章的意义更多的是启发读者构建适合自己的解决方案。

列表页面定义:

表格示例

import { defineFatTable } from '@wakeadmin/components'

/**
* 表格项类型
*/
export interface Item {
id: number
name: string
createDate: number
}

export const MyTable = defineFatTable(({ column }) => {
// 可以在这里放置 Vue hooks
return () => ({
async request(params) {
/* 数据获取,自动处理异常和加载状态 */
},
// 删除操作
async remove(list, ids) {
/*列删除*/
},
// 表格列
columns: [
// queryable 标记为查询字段
column({ prop: 'name', label: '名称', queryable: true }),
column({ prop: 'createDate', valueType: 'date-range', label: '创建时间', queryable: true }),
column({
type: 'actions',
label: '操作',
actions: [{ name: '编辑' }, { name: '删除', onClick: (table, row) => table.remove(row) }],
}),
],
})
})

语法类似于 Vue DefineComponent,传入 'setup', 。 这个设置中可以放置一些逻辑和状态或者Vue钩子,这与Vue的defineComponent的定义一样灵活。

返回有关表结构的“语句”。 在最佳情况下,开发人员只需要定义表结构和前端套接字,其余的将由组件库处理。

当然,复杂的定制场景也能满足,这里你可以使用JSX,监控storm,并传递组件支持的任何props和slots。

表单页面示例:

表格示例

import { defineFatForm } from '@wakeadmin/components'
import { ElMessageBox } from 'element-plus'

export default defineFatForm<{
// 这里的泛型变量可以定义表单数据结构
name: string
nickName: string
}>(({ item, form, consumer, group }) => {
// 这里可以放置 Vue Hooks

// 返回表单定义
return () => ({
// FatForm props 定义
initialValue: {
name: 'ivan',
nickName: '狗蛋',
},

submit: async (values) => {
await ElMessageBox.confirm('确认保存')
console.log('保存成功', values)
},

// 子节点
children: [
item({ prop: 'name', label: '账号名' }),
item({
prop: 'nickName',
label: '昵称',
}),
],
})
})

配合tailwind吃更美味。我们假设页面整体符合UI规范,使用tw进行微调会非常方便

全局配置:

import { provideFatConfigurable } from '@wakeadmin/components'
import { Message } from 'element-ui'

export function injectFatConfigurations() {
provideFatConfigurable({
// ...
// 统一处理 images 原件上传
aImagesProps: {
action: '/upload',
},
// 统一 date-range 原件属性
aDateRangeProps: {
rangeSeparator: '至',
startPlaceholder: '开始日期',
endPlaceholder: '结束日期',
valueFormat: 'yyyy-MM-dd',
shortcuts: [
{
text: '最近一周',
onClick(picker: any) {
picker.$emit('pick', getTime(7))
},
},
{
text: '最近一个月',
onClick(picker: any) {
picker.$emit('pick', getTime(30))
},
},
{
text: '最近三个月',
onClick(picker: any) {
picker.$emit('pick', getTime(90))
},
},
],
},
})
}