前言
去年年底,我写了一篇《如何做好一款管理后台框架》的文章,这是我对开发 Fantastic-admin 这款基于 Vue 的中后台管理系统框架两年多时间的一个思考与总结。
很意外这么一篇标题平平无奇的文章能收获 30k 的浏览以及 600 多个收藏,似乎大家对这种非干货的文章也挺感兴趣。于是在这个三年的时间点上(没错,也就是1100天),我打算继续出来和大家唠唠,这一年我又做了些什么事,或者说,如何把一款好的后台框架变得通用?
题外话:如果你对我以前的文章感兴趣,可以点我头像进入主页查看;如果你期待我以后的文章,也可以点个关注。
痛点
因为 Fantastic-admin 是基于 Element Plus 这款 UI 组件库进行开发的,于是今年我陆陆续续被问到一些问题:
- 以后会有 Ant Design Vue 版本么?会有 Naive UI 版本么?会有 …… 版本么?
- 我们公司/团队有一套内部的 UI 组件库,可以在 Fantastic-admin 里使用么?会和 Element Plus 有冲突么?
- 我们有一些老项目希望迁移到 Fantastic-admin 上来,但 UI 组件库用的不是 Element Plus ,有什么办法么?
- …
类似的问题一多,我也在思考一个问题:我的这款框架是不是被 Element Plus 绑架了?如果开发者在做技术选型的时候,因为 UI 组件库不符合预期,而将我的框架筛掉,这是我不希望看到的结果。
基于这个潜在隐患,我开始计划对框架进行转型。
方案
方案一
既然开发者对 UI 组件库有各自的偏好,我又想拉拢这部分开发者,那是不是多出几套不同 UI 组件库版本的就可以了呢?没错,这是我最开始冒出来的念头。
我参考了一些同类产品的做法,尽管它们把不同 UI 组件库版本做得很像,但在使用体验过程中,还是会带来操作上的割裂感。并且因为无法抹平不同 UI 组件库在 API 上的差异,导致在框架功能上,不同版本之间也会有一些差异。
你可以分别对比左右或者上下两张图,包括左侧导航栏的样式、导航收起/展开按钮的位置、右侧项目配置中提供的功能等,都能明显发现它们的差异。
虽然这可能不是什么大问题,但我认为视觉风格上的统一是能帮助产品提高识别度的。就比如上面 4 款基于不同 UI 组件库开发的后台框架,虽然它们属于同一个产品,但如果我不告诉你,你未必能通过图片确定它们师出同门。
其次就是后台框架提供的功能不统一,这里面有一定的原因是因为 UI 组件库导致的。试想一个场景,如果你要从 Element Plus 版本的后台,迁移到 Ant Design Vue 版本的后台,框架的配置文件是否能原封不动的复制过去?如果导航(路由)数据是后端返回的,数据结构能否保持完全一致,后端无需做任何修改?因为不同 UI 组件库对菜单组件的使用方式是完全不同的,比如 Element Plus 是需要手动拼装的,而 Naive UI 则是数据驱动的,只需要传入一个树形结构的数据给组件即可。如果数据结构无法保证一致,就会增加迁移和学习的成本。
最后就是我的一点私心,因为多一个 UI 组件库的版本,势必会占据我更多的业余时间,如果同时维护 4、5 个版本,那我大概下班后的所有时间都要投入到其中,并且如果未来又有新的 UI 组件库成为流行,那就又多一个版本的维护,这并不是一个可持续发展的方案。
方案二
既然上一个方案不符合我的期望,于是我开始思考,框架本身能不能不依赖这些 UI 组件库?如果框架本身不依赖于三方的 UI 组件库,那开发者不就可以根据需要自行引入想要的组件库了么。
就如上图,主/次导航和顶栏是属于框架的部分,而这部分其实并没有用到太多 UI 组件库提供的组件,以 Element Plus 举例,我统计了一下目前 Fantastic-admin 用到的组件:
- Menu 菜单(主/次导航)
- Breadcrumb 面包屑(顶栏)
- Popover 气泡卡片(顶栏右侧的工具栏)
- Dropdown 下拉菜单(顶栏右侧的工具栏)
Drawer 抽屉(应用配置)
- Message 消息提示
- Button 按钮
- Input 输入框
- Radio 单选框
- Select 选择器
- Switch 开关
- …(等等表单类组件)
可以看到,虽然抽屉组件里用了很多表单类的组件,但这部分组件都是在应用配置里使用的,而应用配置这个模块,主要是方便在线测试框架提供的各种功能,在实际业务开发中,是完全不需要这个模块的。
所以初步算下来,后台框架真正依赖于 Element Plus 实现的组件就只有 4 个:
- Menu 菜单
- Breadcrumb 面包屑
- Popover 气泡卡片
- Dropdown 下拉菜单
那我为什么不找一些独立的第三方插件替代呢?是的,这是我第二个方案,就是找一些独立的插件替换 UI 组件库中的组件。但问题也立马迎面而来,就是偌大一个 Github ,居然找不到符合我需求和审美的插件。
比如菜单插件,我希望它和 Element Plus 里的菜单组件在功能上没有太大差异,支持水平/垂直模式、支持折叠收起、支持设置默认激活菜单、支持默认展开等。
比如面包屑插件,或许是因为这个插件功能太简单,并且大部分 UI 组件库都有提供,在 Github 能搜到独立的面包屑插件很少,搜到的也基本上是 N 年前的上传的,既没有人维护,风格样式也很丑。
这个方案似乎也行不通……吗?
方案三
虽然方案二在实施的第一步就扑街了,但有一点思路还是正确的,就是让框架本身不依赖于三方 UI 组件库。既然网上搜不到合适的插件,那我为什么不自己写一个呢。
比如面包屑,这是一个很简单的功能,任何前端初学者应该都可以写一个面包屑组件。
而气泡卡片和下拉菜单我没有计划自己写,因为找到了一个还不错的插件 Floating Vue,它由 Vue 团队核心人员开发并维护,并且最重要的是它支持自定义样式,意味着我可以将它魔改成想要的样子,尽可能和我的框架在视觉风格上保持统一。
最后一个比较难啃的骨头就是菜单,因为找不到合适的替代品,自己写的话又比较有挑战,虽然我有一点实现思路,但不多。当然最终还是决定自己写一个,因为觉得三方 UI 组件库这么多,实在写不出来我就去读他们源码,总不能每一个源码我都读不懂吧。
这 4 个组件的替换方案确定后,剩下就是抽屉组件和它里面的一些表单组件了,这些要怎么解决呢?这会我想到了 Headless UI ,它是完全无样式的 UI 组件库,通过与 Tailwind CSS / UnoCSS 集成使用,可以快速构建出属于自己风格的组件。
但是 Headless UI 提供的组件非常有限,并不能覆盖我需要的表单组件。不过它的设计给了我启发。表单组件我并不需要非常复杂的功能,原生的表单控件其实就能满足我的使用需求,只是原生的样式比较丑,和我想要的风格不统一,那我只需要给他们定制一套统一的风格就可以了,也就写一套原子化的 CSS 样式。
于是,方案敲定,开始实操。
实操
我决定从易到难开始处理,因为这样在初期能快速看到进度推进,也避免一上来就被一个菜单功能卡住好几天,甚至十几天都没有进展,打击到自己的信心。
1. 面包屑
和预期一样,并没有什么难度,很轻松就实现了。只不过目前还是保持和 Element Plus 一样的使用方式,就是需要手动拼装,后期计划改成数据驱动的使用方式。
2. 气泡卡片 & 下拉菜单
这部分参考了 nuxt/devtools 中 Floating Vue 的自定义样式,以及 nuxt/ui 中下拉菜单的样式风格,最终形成了我自己满意的风格
3. 抽屉
使用了 Headless UI 中的 Dialog 组件,因为它和抽屉组件有相同的交互方式,它们都是在遮罩层上展示内容,只不过 Dialog 更多时候是居中展示,而抽屉则是在左右两侧展示。
其次在使用过程中,发现 Headless UI 中的 Transition 组件是一个惊喜。虽然 Vue 本身就有提供 <transition>
组件用于处理过渡动画,但有一个场景会比较难处理,官方的描述是:
This technique is needed when you want to coordinate different animations for different child elements – for example, fading in a Dialog's backdrop, while at the same time sliding in the contents of the Dialog from one side of the screen.
当您要为不同的子元素协调不同的动画时,就需要使用这种技术,例如,在淡入对话框背景的同时,从屏幕的一侧滑入对话框的内容。
这说的不就是抽屉组件么?于是按照官方的示例,修改了整体风格,最终效果也就出来了。
4. 表单组件
之前的计划是修改原生表单控件的样式,但在开发过程中发现会有一定的局限性。比如 <select>
无法控制弹出选项框的样式,我的解决办法就是用 Floating Vue 封装模拟一个 select 组件。
同时也在开发过程中发现了一些被遗漏组件,于是边做边补,最终大概做了 10 多个组件。虽然看着不少,它们都秉持着最小可用的状态。什么意思呢?就是我不会给它们设计太多的 API ,因为它们的定位和三方 UI 组件库不同,它们只要满足框架本身使用即可,用不到的 API 不会进行开发。并且使用上也不会有太大负担,如果不是对框架进行二次开发,开发者是可以完全不用关注这部分组件。
5. 菜单
菜单组件确实是个难啃的骨头,我差不多用了 3 周的晚上时间去开发。
第一周,按照自己的思路徒手撸,做到一半卡壳,做不下去了;
第二周,开始看 Element Plus 、Naive UI 、Ant Design Vue 里菜单的源码;
Ant Design Vue 的没看懂,放弃;
Naive UI 的看到一半发现核心实现被作者封装到 treemate 这个独立包中了,虽然这个包是开源的,目的也是针对树形结构的一体化解决方案。但我粗略看了一遍文档,感觉有点大材小用,因为它有很多 API 我是用不到的,而我对菜单组件又有一些自己的想法,不确定是否它这个包能否满足我的需求,放弃;
最后选择看 Element Plus 的,通过在本地一点点打印数据,大概理解了实现思路,但组件递归调用,父子组件通过 provide / inject 传递数据和函数的方式,数据状态的变动也是一层层向上级组件通知,直到通知到顶层组件,在我看来有点不太优雅,如果数据能统一在顶层组件里操作就好了。其次我的计划是写一个数据驱动的菜单组件,而不是像 Element Plus 需要手动拼装的,所以虽然我大致看懂了 Element Plus 菜单组件是怎么实现的,但在我自己实现的时候,还是有很大的不同,能参考的代码并不多。
这部分的开发总结,我可能会在以后单独写一篇文章详细说说,因为这部分也是整个方案中唯一的难点。
第三周,因为实现思路大致有了,所以开发上就没有太多的卡壳,最终结果也还不错,基本达到了我的需求。
同时因为组件完全可控,顺带解决了之前使用 Element Plus 菜单组件上无法解决的 bug ,比如当菜单收起时,弹出的悬浮菜单如果数量过多,超出屏幕高度,超出的部分就无法查看了,就像这样:
但是现在则会有滚动条,使用体验上更舒服。
验证
至此,我的后台框架已经摆脱对 Element Plus 的依赖,接下来就需要验证一下是否可以方便的替换成其他 UI 组件库。
我分别用 Ant Design Vue 、Arco Design Vue 、Naive UI 、TDesign 这四款热度比较高的组件库进行了验证:
Ant Design Vue | Arco Design Vue | Naive UI | TDesign |
---|---|---|---|
<img src="https://z1.ax1x.com/2023/10/25/piVsQ4f.png" /> | <img src="https://z1.ax1x.com/2023/10/25/piVsYuj.png" /> | <img src="https://z1.ax1x.com/2023/10/25/piVsNbn.png" /> | <img src="https://z1.ax1x.com/2023/10/25/piVsw5V.png" /> |
结果还是很满意的,都能够顺利替换,并且替换过程并没有花费很多时间,一个小时内就可以替换成功。
由于登录页这个特殊的存在,替换组件库后是需要对其用到的 Element Plus 组件进行手动修改的,这部分会比较花时间,因为会涉及到表单验证之类的东西,不同组件库的写法差异还是比较大的。
详细的替换步骤可以在 Fantastic-admin 官方文档里找到。
回顾
让我们重新看下一开始的痛点是否都解决了么:
以后会有 Ant Design Vue 版本么?会有 Naive UI 版本么?会有 …… 版本么?
虽然不会有,但可以自己动手,根据教程将默认的 Element Plus 替换成你想要的 UI 组件库就可以了
我们公司/团队有一套内部的 UI 组件库,可以在 Fantastic-admin 里使用么?会和 Element Plus 有冲突么?
不会有冲突,现在可以彻底移除 Element Plus ,安装并使用自己的 UI 组件库
我们有一些老项目希望迁移到 Fantastic-admin 上来,但 UI 组件库用的不是 Element Plus ,有什么办法么?
可以用 Fantastic-admin 源码先进行 UI 组件库的替换,之后再将老项目的业务代码逐部迁移
除了解决这些痛点,甚至还有新收获:
帮助公司/企业打造视觉风格统一的产品,提高产品辨识度
大公司可能有不止一个项目团队,不同项目团队的技术偏好可能无法完全统一,导致开发的后台长得也千变万化。但即使在这种情况下,使用 Fantastic-admin 依旧可以保持整体视觉风格上的统一。
近乎于 0 的上手成本
因为后台框架始终都只有一套,开发者不会因为切换 UI 组件库后,要重新了解后台框架的使用
维护成本更低,产品生命周期更长
这一点是对我自己说的,不管未来会出现多少个新的 UI 组件库,我都不需要去新增一个版本进行单独维护;或者 Element Plus 如果有一天停止维护了,我的产品也不会因此进入了死亡倒计时
总结
文章写到这里,差不多就结束了,虽然阅读一遍可能只花了不到10分钟,但为了做成这件事,我大概从今年 6 月份就开始构思了,也是花了蛮多的精力,所以很感谢你的耐心。
当一款产品做到第 4 个年头,周围大部分同类产品都进入到半停更的状态,这一年里我经常思考如何延长产品的生命周期,如何让更多人来使用,而这篇文章就是对我自己今年的一个总结,也是一份答卷,希望大家能喜欢。
另外,Fantastic-admin V4.0 已经正式发布,感兴趣的朋友可以来看看,或许你的下一个项目,就可以用上了。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。