bi-designer 是阿里数据中台团队自研的前端搭建引擎,基于它开发了阿里内部最大的数据分析平台,以及阿里云上的 QuickBI。 > bi-designer 目前没有开源,因此文中使用的私有 npm 源 `@alife/bi-designer` 是无法在公网访问的。 本文介绍 bi-designer 设计器的使用 API。 bi-designer 设计有如下几个特点: - **心智统一:编辑模式与渲染模式统一**。 - **通用搭建:支持接入任意通用 npm 组件**。 - **低入侵:围绕数据分析能力做了增强,但对组件代码无入侵**。 ## 渲染画布 做搭建,第一步是将画布渲染出来,需要用到 `Designer` 与 `Canvas` 组件: ```jsx import { Designer, Canvas } from '@alife/bi-designer' export () => ( ) ``` - `Designer`:数据容器,用于管理渲染引擎数据流。 - 参数 `defaultPageSchema`:页面 DSL 默认值。 - 参数 `defaultMode`:控制编辑渲染状态,`edit` or `render`。 - `Canvas`:渲染画布的所有组件,会根据 DSL 结构将组件一一渲染出来。 ## 编辑模式 编辑模式 = 渲染画布(编辑模式)+ 拓展一些自定义面板。 ```jsx import { Designer, Canvas } from '@alife/bi-designer' export () => (
Header
Footer
) ``` 编辑模式的拓展采用了 JSX 模式,没有增加任何新的语法,只要放置任意数量的组件,并将画布 `Canvas` 摆放在想要的位置即可。 `defaultMode` 描述了当前引擎所处状态,有 `edit` 与 `render` 两个可选值,可以通过 `{ mode } = useDesigner(modeSelector)` 获取。bi-designer 没有对 `mode` 做任何特殊处理,我们可以在 panel、组件中判断不同的 `mode` 走不同的逻辑,以此区分编辑与渲染态。 ## 页面 DSL 结构 `pageSchema` 描述了页面 DSL 信息,其结构是一个 `Map<组件 id, 组件实例信息>`。 这里统一一下名词: - 组件实例信息:`componentInstance`。 - 组件元信息:`componentMeta`。 那么 `pageSchema` 的结构大致如下: ```json { "componentInstances": { "1": { "id": "1", "componentName": "root", }, "2": { "id": "2", "parentId": "1", "componentName": "button", } } } ``` 根据 `id` `parentId` 关系描述了组件父子关系,对于同一个父节点在流式布局下的顺序,还会增加 `index` 标记顺序。 ## 注册组件 DSL 描述信息中最重要的是 `componentName`,为了告诉渲染引擎这个组件是什么,我们需要将组件元信息(`componentMetas`)传递给 `Designer`: ```jsx import { Designer, Canvas, Interfaces } from '@alife/bi-designer' export () => ( ) const componentMetas: Interfaces.ComponentMetas = { button: { componentName: 'button', element: Button } } ``` 关于 `componentMeta` 会在下一篇精读详细介绍,这里只说明两个最重要的属性: - `componentName`:组件名,唯一。 - `element`:组件 UI 对象,对应一个 React 组件实例。 注意这里就留下了不少拓展空间,`componentMetas` 可以存储在服务端,`element` 可以远程异步加载,也可以在项目代码中固化,但传递给渲染引擎的 API 是固定的。 ## 布局 bi-designer 支持流式布局、磁贴布局、自由布局三种模式,通过 `Designer.layout` 属性定义: ```jsx import { Designer, Canvas, Interfaces } from '@alife/bi-designer' import { LayoutMover } from '@alife/bi-designer-stream-layout' export () => ( ) ``` 我们提供了三种不同的布局包,切换对应的包即可切换布局,你甚至可以再包裹一层,通过代码控制在运行时切换布局。 `layout` 会包裹在每个组件外层,无论是流式、磁贴还是自由布局,都可以通过附着在每个组件外层来实现。 ## 操作/获取画布内容 只要在数据容器 `Designer` 下,就可以通过 `useDesigner()` 获取画布信息或者修改画布内容。 举个例子,比如实现组件配置面板,需要获取到 **当前选中组件**,以及实现操作 **更新 DSL 中某个组件信息**: ```jsx import { Designer, Canvas, useDesigner, selectedComponentsSelector } from '@alife/bi-designer'; const EditPanel = () => { const { updateComponentById, selectedComponents } = useDesigner(selectedComponentsSelector()); // 在合适的时候调用 updateComponentById 更新 selectedComponents // 渲染组件配置表单.. } export () => ( ) ``` 我们在 `Canvas` 下面渲染了一个自定义组件 `EditPanel` 作为组件配置面板,这个配置面板中,最重要的是这块代码: ```jsx import { useDesigner, selectedComponentsSelector } from '@alife/bi-designer'; const { updateComponentById, selectedComponents } = useDesigner(selectedComponentsSelector()); ``` - `useDesigner` 是 React Hook,导出的函数都是静态的,不会因为画布信息变更而导致组件重渲染。 - 如果需要监听一些会变化的元素,比如当前选中组件,就需要用 Selector 完成,当这些信息变更时,使用了这些 Selector 的组件也会重渲染,具体 Selector 有很多,比如: - `selectedComponentsSelector`: 当前选中的组件。 - `pageSchemaSelector`: 当前画布 DSL。 - `modeSelector`: 当前渲染模式。等等。 - 对画布组件操作有几个重要的静态方法,包括: - `updateComponentById`: 更新某个 id 组件信息。 - `addComponent`: 添加组件。 - `deleteComponent`: 删除组件。 - `moveComponent`: 移动组件。等等。 - 除此之外,`useDesigner` 还提供了很多有用的方法,在用到时再介绍。 ## 主题风格 通过 `pageSchema.theme` 设置主题风格: ```jsx import { Designer } from '@alife/bi-designer' const App = () => ( ) ``` 我们也可以在运行时使用 `setTheme` 动态修改主题风格,做到动态切换主题: ```jsx const { setTheme, theme } = useDesigner(); return