从零搭建金融贷款低代码前端框架:xxx-editor 实践总结
最近有点忙,在做一个面向金融贷款业务的前端框架项目 xxx-editor,整体采用 pnpm Monorepo 架构,涵盖低代码编辑器、多个 Next.js 子应用和一套完整的组件库。这篇文章记录一下整体架构设计和几个值得分享的技术点。
项目背景
金融贷款类 H5 应用有几个典型特点:
- 页面结构高度相似:注册、登录、申请步骤、还款等页面在不同产品线之间大量复用
- 多地区多品牌:同一套业务逻辑需要支持菲律宾、香港等不同市场,UI 和文案各有差异
- 安全要求高:请求加密、活体检测、证件上传等功能必不可少
基于这些特点,我们决定用 Monorepo 把所有子应用和共享包统一管理,并在此基础上做一个低代码编辑器,让运营和产品可以快速配置页面。
Monorepo 结构设计
1 | xxx-editor/ |
包管理用的是 pnpm workspaces,pnpm-workspace.yaml 配置如下:
1 | packages: |
各子应用通过 workspace 协议引用内部包,例如:
1 | { |
这样改动组件库后,所有子应用都能实时感知,不需要发布 npm 包。
HTTP 层的设计
HTTP 层是整个框架里最复杂的部分之一,封装在 packages/utils/src/http/axios 下。核心是一个 VAxios 类,支持:
- 请求/响应拦截:统一处理 Token 注入、错误码映射
- 请求取消:通过
AxiosCanceler管理 pending 请求,页面切换时自动取消 - 自动重试:
AxiosRetry对网络抖动场景做指数退避重试 - 请求加密:敏感接口的请求体走 AES 加密
1 | // 创建实例时注入 transform 钩子 |
Token 刷新用的是经典的”请求队列”方案:刷新期间的所有请求挂起,刷新成功后统一重放。
组件库的 Variant 模式
packages/widgets 里的组件大量使用了 Variant(变体) 模式。以 Card 为例:
1 | Card/ |
每个 Variant 有独立的 definition.ts(props 类型定义)、styled.ts(样式)和主组件文件,互不干扰。这种结构的好处是:
- 新增变体不影响已有变体,改动范围可控
- Storybook 可以为每个变体单独写 story,方便 QA 验收
- 低代码编辑器可以把每个变体作为独立的”积木块”注册
低代码编辑器核心
packages/core 实现了一个轻量的低代码运行时,主要由三部分组成:
- Designer:拖拽画布,左侧 WidgetPanel 展示可用组件,右侧 SettingPanel 配置属性
- Renderer:根据 JSON Schema 渲染页面,通过
WidgetListRender递归渲染组件树 - register:组件注册机制,将 widgets 中的组件映射到编辑器可识别的 key
1 | // 注册一个组件 |
JSON Schema 的结构大致如下:
1 | { |
加密工具的分层设计
packages/utils/src/encrypted 里的加密工具做了比较清晰的分层:
1 | encrypted/ |
工厂类用单例模式管理各加密实例,避免重复初始化:
1 | class AesEncryption { |
对外只暴露 encryptByAES、decryptByAES、encryptByMd5 等函数,调用方不需要关心内部实现。
多语言方案
max-cash-portal 是一个多语言门户,用的是 Next.js App Router 的 [lang] 动态路由方案:
1 | app/ |
middleware.ts 负责检测用户语言并重定向:
1 | export function middleware(request: NextRequest) { |
字典文件放在 dictionaries/ 下,按语言分文件,generateStaticParams 在构建时生成所有语言的静态页面。
Storybook 组件文档
packages/stories 是整个组件库的 Storybook 工程,独立作为 @xxx/storybook 包存在,运行在 6006 端口。
这也是我认为架构设计里比较成功的一点,Storybook 和组件库虽然紧密相关,但职责不同,分开管理更清晰并且方便 UI 和 QA 访问而不需要跑整个 Next.js 应用。
配置要点
Storybook 使用的是 @storybook/react-webpack5,在 webpackFinal 里配置了 workspace 包的路径别名,让 stories 可以直接 import 内部包的源码而不是构建产物:
1 | // packages/.storybook/main.js |
这样改了组件源码后,Storybook 热更新可以直接生效,不需要先 build 包。
样式方面配置了完整的 Less / Sass / CSS Modules 支持,因为 widgets 里的组件混用了多种样式方案。
Story 的写法
项目里的 story 统一用 StoryFn 模板模式,以 Guide 组件为例:
1 | const Template: StoryFn<GuideProps> = (args) => <Guide {...args} />; |
每个组件至少有 Default 和边界场景(如 Empty)两个 story,复杂组件还会有 CustomStyles 等变体,方便 QA 和设计师对照验收。
Form 组件的 story 比较特殊,用了 argTypes 的 control: 'check' 让测试人员在 Storybook 面板上勾选需要展示的表单字段,动态渲染表单,不需要改代码就能验证各种字段组合:
1 | argTypes: { |
启动与构建
1 | # 开发模式 |
小结
整个项目下来,几个体会比较深的点:
- Monorepo 的收益在中后期才明显:前期搭架子比较费时,但后期多个子应用共享组件和工具函数时,维护成本大幅降低
- Variant 模式适合业务组件:纯 UI 组件用 props 控制变体就够了,但业务组件的差异往往不只是样式,Variant 模式更清晰
- 低代码不是银弹:低代码适合结构固定、配置频繁的场景,对于逻辑复杂的页面,还是直接写代码更高效
- 加密要早规划:加密方案如果后期才加,改动面会很大,最好在 HTTP 层统一处理,业务代码无感知