引入
看到下面这两张图,是不是异常的熟悉。没错,这就是前端入门必备的中后台管理系统。但对于从事前端开发的你,除了熟悉感以外,是不是还带着一丝丝焦虑:长期从事这种 CRUD 的工作,对前端技术的提升有限。每次新建页面都把原来的代码复制过来修修改改。虽然也沉淀了一些组件,但随着需求迭代,发现也不那么通用。页面虽然相似,但每个页面却又有一些它独立的、不一样的点。
图一
图二
DSL
简介
DSL - domain specific language -领域特定语言 是一种具有有限的范围的计算机语言,旨在解决应用领域内的特定问题。而我们则是想通过 DSL 快速搭建中后台页面,解决重复性建设问题
架构说明
图三
如图,使用 DSL,同时借用面向对象的思想,从领域模型A基类(中后台系统的通用性)出发,结合不同项目的特性,拓展出不同的项目配置A-1、A-2...也就是面向对象中的基类与子类的关系。再通过前面文章一和文章二 所讲的BFF层和elpis-core,SSR渲染出来不同项目的首页。而首页中的菜单切换,是我们熟能生巧的CSR单页面切换。
图中蓝色、黄色、白色背景块是页面的通用性部分,只需配置,无需开发,可快速搭建,对应领域模型A基类。同时,对于项目中一些独特的需求,我们支持自定义组件,自定义页面,甚至也可以内嵌其他的页面,即图中的绿色背景部分。
详细说明
整个页面的搭建是从一份满足 json-schema 的 json 配置开始的。
以电商系统作为领域模型基类,拼多多电商系统项目作为基类衍生出来的子类为例。看如下这两份 json 配置。左边的为基类,右边为子类。
图四
电商系统的操作后台不可避免的会有商品管理、订单管理、客户管理等功能。而拼多多电商管理系统除了这些通用功能外,还有自己特有的数据分析、信息查询功能。
结合图一和图四可以看到,拼多多这份配置,不仅继承了基类,还重载了基类的部分配置,如“商品管理(拼多多)”,如“客户管理(拼多多)”的 customConfig 变为了 schemaConfig。对于完全继承的,如“订单管理”则无需在配置中体现,在代码中会有 merge 操作。
菜单
头部菜单的功能一般是一个菜单组(subMenu)、展示一个完整页面(customConfig、schemaConfig)、展示一个内嵌页面(iframeConfig)、展示侧边菜单,由侧边菜单栏的路由再渲染不同页面(siderConfig),侧边菜单栏的具体菜单又可以重复以上。
menu: [
// 可递归的 menuItem
{
key: "", // 菜单唯一标识
name: "", // 菜单名称
menuType: "", // 枚举值:group / module
// 当 menuType 为 group 时,需要配置 subMenu,可选
subMenu: [
{
// 可递归的 menuItem
},
],
// 当 menuType 为 module 时,需要配置 moduleType,可选
moduleType: "", // 枚举值:sider / iframe / custom / schema
// 当 moduleType 为 sider 时
siderConfig: {
menu: [{}],
},
// 当 moduleType 为 iframe 时
iframeConfig: {
path: "", // iframe 路径
},
// 当 moduleType 为 custom 时
customConfig: {
path: "", // 自定义路由路径
},
// 当 moduleType 为 schema 时
schemaConfig: {
}
},
],
通过以上这份配置,就可以很好地把菜单渲染出来,具体的代码逻辑在这就不展示了。其中,customConfig 是用来编写项目的特有页面,需要独自开发。而要搭建通用的页面,需要使用到schemaConfig
页面与组件
管理系统中,上边的搜索表单、下边的展示表格、右边弹出的新增或详情抽屉,其实来来回回都是操作的同一些字段、同一份数据源,同一张数据库表(这儿不涉及连表查询),因此我们可以在一份 schemaConfig 配置中抽象出来。 schemaConfig + 对应的解析器就可以搭建一个页面。
图五
schemaConfig 中的配置,大概有以下几个部分。详见这里
- 数据源API
- 管理系统中的 CRUD 对应post、get、put、delete 请求,因此只需要一个遵循 RESTFUL 规范 api 路径即可满足操作
- 一份拓展的 schema 配置
- 满足 json-schema 规范,可以用来做表单校验
- 除了标准的 json-schema 配置后,还进行了一些扩展。因为如上所说,操作的是同一份数据源。所以,以商品名称为例
- 如果需要在搜索表单中展示,配置 searchOption
- 如果需要在展示表格中展示,配置 tableOption
- 如果需要在新增商品的表单(右侧弹出抽屉)中展示,配置 createFormOption
- 如果需要在修改商品的表单(右侧弹出抽屉)中展示,配置 editFormOption
- 如果需要在商品详情的抽屉中展示,配置 detailPanelOption
- 在这些 option 中,
- 首先需要定义使用哪种组件,常见的如input、inputNumber、select、dynamicSelect等
- 其次可以使用 element-plus 的相关配置。无需一一实现,通过 v-bind 绑定
- 可以扩展配置,如用 visible 设置某个字段不可见;用 enumList 表示使用 select 时的下拉枚举
除了这份数据的配置外,还需要满足组件本身的一些配置
- 如展示表格上有些按钮,表格中行数据也有按钮,点击按钮会触发相应的事件。因此需要在 tableConfig 配置 headerButtons 和 rowButtons
- 如动态组件 createForm 和 editForm 中要展示标题和按钮文案。因此需要在 componentConfig 进行配置 createForm 和 editForm 的 title 和 saveBtnText。editForm 还需要一个请求数据的唯一标识 mainKey
有了 schemaConfig 配置和对应的解析器(图五),这个页面也就搭建出来了。后续,除非有新增的组件,需要对解析器进行拓展而修改代码。否则,只需要这么一份配置,就可以渲染出来一个页面,各字段都可以配置。
不止于此
但这份配置的功能可远远不至于搭建一个前端页面。配置中,有字段有了,有字段类型,有对应的校验,服务端完全可以通过这份配置进行接口定义和数据库表建立。这也是为什么不使用用 element-plus 的表单校验功能,通过这份配置,前后端的校验可保持一致。
总结
至此,算是了解了 “哲玄前端”-《大前端全栈实践课》的核心思想。
- 80%配置减少CRUD体力活
- 这不仅仅是一份配置,是一整个板块。前端、后端、数据库都可从中受益
- 20%定制化增加系统的拓展性
- 基类到具体项目的拓展
- 菜单功能的拓展
- 动态组件的拓展
- 可推动整个团队基于 Elpis 的思想进行规范设计与开发