今天聊:前端如何快速搭建财务 BI 报表平台

5,580 阅读24分钟

前端早早聊大会,与掘金联合举办。加 codingdreamer 进大会技术群,赢在新的起跑线。


第二十七届|前端 Flutter 专场,了解 Web 渲染引擎|UI 框架|性能优化,6-5 下午直播,6 位讲师(淘宝/京东/闲鱼等),点我上车👉 (报名地址):

大会海报.png

所有往期都有全程录播,上手年票一次性解锁全部


正文如下

本文是第十五届 - 前端早早聊报表专场,也是早早聊第 108 场,来自 易快报 - 寇云 的分享。

Hello!小伙伴下午好,接下来是由我来给大家做一个偏前端的分享,前面两个讲师分享的内容都很硬,货都很干,话题很高端,就像交响乐一样,接下来我分享的主题是比较偏前端的,希望能通过这次的分享,大家都能带着走,好,咱们进入正题。

一、自我介绍

首先做一下自我介绍,我叫寇云,目前是在易快报做前端架构,同时也做一些团队管理方面的工作。在大公司的架构师是真正的架构师,但是小公司的架构师可能更多的工作是补位、救火,下面简单介绍一下我们公司。

我们公司的业务是以企业敏捷报销为入口,做企业相关的财务管理软件、企业消费聚合平台。

这是整个公司的一个大概业务范围,跟主题没有什么关系,大家看一下就好了。

二、业务场景

给大家介绍一下业务场景,为了更方便的带入到场景里面,先给大家介绍一下,什么是报表?报表在具体的业务中怎么去使用?然后再介绍一下整个项目的背景。在企业的财务管理当中,通常会有这样的情况,比如企业的预算编制,比如一个企业中的某些部门或某些项目,一个季度或是一年的花销是多少钱?公司在做财务管理的时候,就会把这些信息用数据的方式展示出来,并进行管理。前端负责的部分就是把这些财务信息以图表、表格的形式展示给用户。

另一个场景就是纯数据的,是一个数据的报表,它包含了在财务数据以不同的数据维度同步过来之后,然后把不同的数据根据数据维度去聚合,再把这些数据拿出来去做一些数据分析。在整个高级报表里面它会涉及到不同数据的数据可视化,不同数据的 Excel,类似于前面讲师分享的 LuckySheet 的一些类似于 Excel 或者是 DataGrid 的一个数据的分析。

三、技术选型

在我们去做这个报表项目的时候,因为人员资源有限,就是一个产品、一个后端架构和我,我们三个人组成了一个小团队,在人力资源有限的情况下,怎么快速的把这个项目搭建起来,怎么去做技术选型,这个过程给大家分享一下。

数据报表的话无非就是三板斧,第一个是表格,怎么用表格的方式,就是用 DataGrid 的方式把数据给展示出来。第二个就是透视表,透视表里包含对于数据 Excel 的分析,对于数据导入、导出或者是不同维度的分析。第三块就是数据可视化,数据可视化我们早早聊前面有一期已经分享过了,所以这块的话我弱化一下数据可视化的部分,专门去分享讲一下,前端如何去优化表格和透视表这部分。

在不同场景下的表格和透视表我们做了一些前期的技术研究和踩坑,然后我现在把这个经历分享给大家。

Antd Table

第一代大概是两年前,用的是 Antd Table,那个时候 Antd Table 是 3.0 的时代,Antd Table 已经基本能满足我们的需求了。但是随着业务的发展,我们的业务和客户不断的从小公司扩大到中型公司,在发展过程中,有一些数据从百级到千级,然后到万级,到万级的时候,Antd Table 在表格和树型组件上的性能已经撑不住了。这个时候我们又不能随意的切换技术栈,当时 Antd Table 已经开始支持 Virtual,也就是说支持虚拟 DOM 的技术了,但是是在高版本的情况下,而我们整个项目又不能随意的去切换版本,于是我们又尝试了 Handsontable。

Handsontable

Handsontable 的场景和第一位讲师分享的 LuckySheet 是非常相似的,它的应用场景适用于 Excel 的导入和导出、展示一个 Excel 数据表、对数据进行常规的 Excel 编辑,这些它是非常适合的,但是我们的场景又需要有更多外部的对于数据的操作,所以 Handsontable 当时是一个过渡的方案,撑了一段时间。

React Virtualized

当时我们在做性能优化这块,又关注了 React Virtualized 库,这个库的优点是性能好,也就是渲染性能好,但是功能是没有 Handsontable 那么强大的。React Virtualized 的表在库的大小上非常优秀,在展示大数据量,比如说上万级的那种数据量,它的渲染性能是非常好的,然后我们保留了库的技术栈,把它应用到了移动端,因为在移动端要考虑到 JS Bundle 的大小,要考虑到渲染性能,在这种纯展示性、弱交互性的情况下,它是一个非常好的选择。

DevExtreme

最后我们把目光落到了 DevExtreme,这是一个非常老牌的框架,但是因为它是付费的,是纯商业的软件,然后在整个开发社区的热度可能少一些,大家知道的也比较少一些。

最终选择

给大家介绍一下为什么选择了 DevExtreme 这个底层框架,选择它最主要的原因就是因为它非常适合强交互场景且易于扩展。为什么说易于扩展,它除了提供 Table、Excel、Tree 这种组件以外,他还把自己的上下文暴露给外部使用者,这样的话外部使用者就可以非常方便的对它的上层进行封装,然后做一些 UI 和操作交互上面的优化。

第二个原因是它不挑框架,因为它既提供了自身的核心部分,又提供了和其它框架的结合部分。比如说 React、Vue 和 Angular 都提供了,这样我们在做选择的时候,我们的选择性会更多一些。比如说我们的项目有基于 React 的,也有基于 Vue 的,这样它就可以在两端使用。

第三个原因是它经得起大数据量的考验,我们当时用了 6 万到 10 万级的数据去测试,结果撑住了,没有什么问题。

第四个原因是它的覆盖场景丰富,除了满足表格和透视表以外,它还提供了一些比如说树形选择、树形编辑的操作,当然它还有一些 UI 组件,那些 UI 组件我们是没有用的,我们直接用了 Antd。最后一个原因也是非常有意思的一个事情,就是我们公司买了 DevExtreme 框架的付费功能,但是实际上我开发的时候,我忘了这事。然后在开发过程中一直用的免费部分提供的功能,也把我们的需求开发完了,开发完之后才知道买了 DevExtreme 付费版,这件事情也很有意思,通过这件事,大家完全可以把它直接拿过来用,它是有开源版本的,开源版本就已经基本满足了我们的一些需求。

完整技术栈

上面这张 PPT 是比较核心的部分,也就是比较扣题“如何快速搭建 BI 报表”的。什么是快速?在人力资源有限的前提下,尽量避免重复造轮子,能快速的把这些任务去完成,现在就是把整套的方案分享给大家,然后大家能基于这套方案很快的把符合自己业务场景的项目快速地搭建起来。

基础框架部分选的是 React 和 TS,没啥说的了,已经是标配了。工程部分是用的是 Umi,这个也是阿里开源出来的,UI 框架是用的是 Antd,也基于 Antd 和公司内部的业务去封装一些复杂的、带业务场景的 UI Components,hooks 用的也是阿里开源的 ahooks,大部分都是阿里的,hooks 提供了很多比较方便实用的函数库,在 React 的开发场景下,它能很方便的帮我们去做一些补位的工作。

数据可视化部分,这部分选的是 Antv,PC 端是用的 G2,移动端用的是 F2。最后这里边的表格和透视表,是基于 DevExtreme 去封装了两个 DataGrid 和 PivotGrid 组件。说到这里给大家提个醒,DevExtreme 是完全开放了它的 Context API,用户可以基于它去做一些封装,接下来去给大家做一个项目内容的过程分享,它不是我们公司内部完全自研的,是基于上面这些技术真真正正做出来的一个项目。

四、前端交互体验创新

介绍完完整的技术栈之后,再给大家演示一下我们在前端体验上的创新部分,主要介绍我们在表格透视表这部分做了哪些工作。

这张 PPT 是我们常见的一个后台管理系统,这个结论的图也是腾讯开源的一个后台管理系统的项目。大概后台的报表项目或者是后台管理系统的项目,基本都是这样的布局。左边是菜单,右边是数据表格,在这个数据表格里面大家也看到了,如果你数据表格的字段非常多的话,整个数据都密密麻麻的,然后大家就会去想,这些字段真的是完全都要吗?

我举一个极限的情况,比如说我们自身的业务有一个叫机票对账单的业务,也就是用户在我们这买了机票,然后要和用户去对公的账户去对账,那本身你交易流水部分的字段就已经够多的了,然后他又挂了机票的订单,机票上面的字段又非常多,极限情况下机票的对账单它有 146 个字段,在 146 个字段里面,你要说都用了,确实都需要,因为运营同学或者是财务人员,他真的需要多看,你要说都展示出来了,这是一个很大的问号。基于这个东西,我们去做了一个什么样的优化呢?然后接下来给大家演示一下,我们去思考一个 DataGrid 到底需要什么?

快速筛选器

首先给大家介绍一下,在它的表头部分,我们加入了一个快速筛选器,来创建不同的业务场景。虽然我们有很多的人或者是很多场景,同时在一个表格上面有很多的数据,但我们可以通过快速筛选的功能,去创建一个场景。在快速筛选器里面,我们可以去定义它的字段,比如说在 146 个字段里面,假如我是专职对账的人员,我只关注交易流水和结算金额这两个字段,极限情况下我就可以把我的表格配置成只显示这两个字段。

它还提供了一些基于字段的操作,比如说我某些字段等于或大于某些字段。在上面 PPT 的右边,我们就以费用承担部门字段为例,假如费用承担部门只包含研发部一个部门的时候,这个数据集里面就会只展示研发部的数据,然后根据它的数据类型来进行操作,比如说这个数据集,它肯定是树形数据,因为部门下面会有子部门,子部门会有一些叶子节点,这种树形数据的话,它就会有一个操作是否包含子集。

如果勾选的话,那就包含它下面所有的子集。在条件跟条件之间,我们会去计算与和或的关系,也就是到底是交集还是并集,通过把这种场景化的东西持久化出来,然后生成一个 JSON Schema 的描述,在通过这个描述把它持久化过来。不同的用户角色对他进行操作之后,我就可以把它保存起来,在这个例子里面我们就模拟一个财务人员,我把它分好类,第一个是报销单、借款单和申请单,很快很方便的就把这个场景带过来了,在这个报表里面就很方便的去把这些数据过滤和筛选出来。

字段优化

第二个创新就是字段的优化,在财务系统里边或者是在整个对账系统里面,报表的数据可能是非常多的,在报表里面只展示一部分我需要的数据,这样的操作要怎么实现呢?在整个 DataGrid 的右上方有一个列选器,在列选器中,可以把字段拖到右边这个图标(箭头所指位置)里面,右边的这个图标只要你点确定后保存,他就会在 Table 里面只展示这一部分字段,然后这个部分又支持拖拽排序,能很方便的去展示数据。

表头优化

在表头上面,有一些细节部分的优化。比如我们要看某些字段的对应关系,表头部分是支持拖拽的,比如在最左边部分,我要拖拽单号和日期并放在一起,就去把单号拖拽过来,整个数据就展示成 PPT 上面的样子。在表头上面还支持排序和筛选,筛选的话要支持字段的不同类型,比如说字段是枚举型的,我就支持可以勾选。以审批状态字段为例,我就可以勾选审批中和待支付状态,Table 就会按照勾选状态把它筛选出来,比如说这个字段是日期类型的,就会提供一个具体的日期或者是日期的时间段去筛选。

还有一个是拖拽字段到表头分组的,比如说我们想看某一个人或者是某一个部门的数据并且对它进行分组,我们就把字段拖到表头字段这块,然后 Table 就会对它进行分组,比如说我是一个财务人员,在进行审批操作的时候,我就可以按照不同的人去分组,这样就很方便的去管理这一批人下面的单据。

底部优化

最后是一个底部的小创新,在表格底部,为了让 DataGrid 的组件能在所有的表格中通用,我们支持 footer 渲染,渲染的是什么东西?就是我们对于业务和权限的定义,这部分业务是由后端去下发给前端,然后前端把它展示出来,后端去定义当前动作的 Action 的业务描述和业务定义,以及它的权限范围。

比如说我在这张 PPT 里面展示了他拥有的权限,然后他所有的 Action 都是通过权限过滤之后再下发给前端,前端把这些 Action 渲染出来之后,在触发后端的 Action,然后对当前的报表数据进行一系列操作,PPT 右边是不同的分页模式,比如说翻页,有支持每个月显示多少条,也有不支持翻页的连续滚动,连续滚动是动态去加载一批数据,比如说滚到下面,然后不停的往里面去推数据。

透视表的这部分其实和刚才展示的差不多,这部分给大家也看一下。刚才还有一个小的细节没跟大家说,当你每次拖动或者是你每一次筛选的时候,他都会进入一个变更状态,他会做一个快二标的地图计算,如果监测到你的计算结果不一样的话,它在这个地方会有一个保存变更状态,也就是说你在某一个菜单下面维持操作之后,然后你点一下保存变更,它就会把你当前对于表格操作的状态记录到当前的标签下面,然后给大家去展示一下透视表。

透视表和 Table 的差别是,Table 是一个二维的数据,透视表是把整个的数据打散,然后多维度的去展示,透视表的字段都是可以随意的去拖动的,比如说我们按照分摊金额、费用类型来展示,在这个部分他就可以按照不同类型去做一些分组和小计,或者是加和的计算。在这一部分透视表的能力是完全可以开放给组件的,组件通过引入到透视表的一些功能,然后对它进行一些扩展,比如说包括前面去讲的公式,做一些小计、同比、环比的计算。当然我们可以做到比如说按单号和提交人去换,非常的灵活,这部分是透视表的一个配置。

程序员正名

在这里我需要给 ToB 行业的程序员正名,因为之前在管理群里面聊天,就是说 ToB 行业的程序员是跑到这种稳定业务线上去养老了,但其实不是这样的。ToB 公司虽然没有 ToC 公司更大的知名度,ToC 公司比如说你是字节、淘系或者是腾讯系的员工,他们研发的产品很容易触达到人们,但是 ToB 公司的产品很难有知名度,比如说我们公司在做企业报销门类的项目,已经做到行业第一,但是还是很少有用户知道,所以这部分项目需要你对行业知识有一个很大的积累。

第二个就是说做 ToB 行业,它不像 ToC 那样去追求高并发量或者是极致的用户体验,他追求的是在复杂的业务场景里面去做切换和穿梭,在这个里面的话还要有一些比如说定制的需求,整个公司的产品软件,它有一部分是面向普通的中小型公司,当你的公司面向的用户公司越大的时候,比如说从一两百人切换到 300 人左右,切换到 3000 人左右的时候,这种体量公司的用户,他们整个的财务系统也好,整个公司对于财务的管理也是非常不同的,受公司管理文化和管理风格的影响,他们的业务是非常复杂的,需要我们对它进行定制,慢慢的就会有大量的这种定制化的需求。

我们在平时写代码的时候会经常去加一些特异性的代码,如果我们面向的是某航空公司,我们就给它搞了一些特殊权限。假如是某航空公司的航班,要怎样处理,我们去写一个 if else 逻辑,然后去把这个场景覆盖到。当不断的有这样的航次进来的时候,if else 逻辑就会写很多,我们怎么在这种不停定制化的需求里面,把它抽象出来变成可配置的项,这个工作是非常有挑战性的。

第三个其实也是在复杂的场景里面需要不断的去抽象业务模块、设计模块,或者是前端的一些模块,比如说包括前端分享在内的 UI 组件模块。最后一个我觉得做 ToB 行业还是需要一点点情怀的,商业公司是建立在整个社会环境的商业不断繁荣的前提下,这么多公司一起去推动社会的发展,我们在 ToB 行业是为这些商业的底层去做服务的,所以我们还是有一点点情怀。

五、踩坑经历

最后再给大家分享一下在做整个项目的过程中,遇到的踩坑经历。

数据渲染性能

遇到问题最多的地方是数据的渲染性能,我们当时用的是 Antd,Antd 3.8 版本还没有提供表格列表或者是树形数据的虚拟渲染技术,所以那个时候的数据,当数据量大的时候,它就是一个渲染瓶颈,大家在做这部分业务和需求的时候,大家一定要注意 Antd 的版本,现在开源的社区已经基于 Antd 提供了 Virtualized 的解决方案,但这个地方大家一定要注意。

复杂数据计算

第二个遇到的困难就是一些图形数据和复杂树形数据的计算,以图形数据场景举例,比如说有一个单据 A 依赖另一个单据 B,另一个单据 B 又依赖单据 C,单据 C 又依赖单据 A,这样它就会有一个图形数据循环计算的性能损耗,还有树形计算,比如说我们公司很大有几万人,那么我们的部门就有很多,部门纵深就很深,或者说我们的项目很大,在整个项目里面会有很多的子部门或者是部门下面又有不同的小项目,小项目下面还有更小的项目,这个地方我们提供了一个叫 Datx 的解决方案,这个也是开源社区一个欧洲人去做的,它是和 Mbox 混搭去做的一个数据管理。

它利用了 Mbox 的 Observe 能力,以及 Mobox 和组件之间的通信能力,去处理数据依赖的这一部分。我给大家简单讲一下 Datx 工作机制,它首先会定义数据模型,我们还是拿研发部门为例,先定一个研发部的数据类型,在研发部的数据类型中,你一定要定义它的父类型和子类型,在父类型和子类型中,你要给它加一个装饰器和一个特殊标识,加了装饰器和特殊标识,它就会给图形数据或者说是树形数据自动创建一个很大的哈希表。

它把这一部分数据做一个拦截,当遍历到树的某个节点时,比如说我要找某个部门的父部门,当我拿到它父部门的时候,它就去这张哈希表上去拦截这个父部门的数据计算,然后去哈希表上找父部门的数据,这样整个计算的性能非常好,然后 Datx 本身是没有框架绑定的,Mobx 也没有框架绑定,也就是说它可以应用于 Vue、React,甚至于 Flutter 都可以。

报表类型需求

第三个困难是报表类型需求,这里需要反向要求产品经理,这一部分的需求就是一定要有数据范围,什么是数据范围呢?就是说我们在跟产品去沟通的时候,一定要让他们定义,比如说我这一个业务场景,它到底服务多少条数据?这样我们部门的业务,要先跟产品去定义好了,部门的数据上限可能就是百条。

经过一段时间的发展,可能有更大的公司,部门数就有上千个部门,这个一定要约束好。为什么要约束好这些呢?我们约束好数据范围,第一是避免过度设计,比如说这个地方数据量不会膨胀,我们对它进行的设计就是短平快的。如果说这个数据可能是上万条的,我们针对这一块的话,就会有一些有针对性的数据计算。

服务端计算 or 客户端计算

第四个问题,到底是服务端计算还是客户端计算。在我们最开始上线 Antd 的时候,我们用了 Handsontable,在技术选型的时候,我们是把整个 Handsontable 作为一个类似于 Canvas 的东西,然后把数据传递到前端页面上进行展示,对它进行的每一步操作,比如说排序、筛选,在交互上的操作都依赖于后端重新计算,前后端的大量交互,导致用户操作不是及时响应,整个的体验不是很好。当然服务端计算是有一定优势的,比如说我们是不能去预估用户的网络和机器的,那么在用户网络不好或者是机器性能不好的前提下,用服务端计算去缓解这部分的压力。

客户端计算的好处就正好跟服务端是对应的,客户端计算响应快,但客户端计算也有它面临的考验,同样也是网络和用户机器性能的考验,到底是用客户端计算还是服务端计算?这个是在不同业务场景下做出的选择,如果说你就是想纯展示的,展示的这部分推荐去用服务端计算,然后直接下发给客户端去做展示,如果说你有复杂的交互场景,那么你更适合用于客户端的计算。

六、恰饭广告

到这我的分享已经差不多结束了,然后我现在还要带着业务团队,现在做的是企业消费的一个聚合平台,这个企业聚合平台包含了平时我们常用的场景,比如说用车、打车、定加班餐,或者是机票、酒店、火车票这种对公的消费平台。

七、招聘

现在压力也是蛮大的,然后需要招人,这边打开一个没有节操的恰饭广告,比如说大家有对于我讲的没有理解清楚的地方,可以给大家一个补充,加一下微信进一步交流,也可以加我去聊一下工作机会,公司在北京。

好了,我的分享就到这结束了,刚才第一个同学说要分享书,我也没有准备,这块的话给大家分享一本我近期在看的书,这本书的名字叫《我心归处是敦煌》,这是我最近在读的一本书,因为前一段时间生病了,生病之后对于自己的目标管理有一些摇摆,我是有目的性的去读这本书的,书讲的是樊锦诗,她是做敦煌研究的,她在《敦煌》里被称为“敦煌的女儿”,她在敦煌那个地方已经研究了一辈子。

我当时读这本书就是说我要学习一下樊锦诗,她到底是怎么去选择一个行业,一个具体的事情能做一辈子那么久,可能我在目标管理的时候,有一些摇摆,我去通过读她的人生经历去寻找一些思路,怎么去明确自己最初的那些目标,好,我的分享结束了,谢谢大家。


别忘了6-5 下午直播哦,点我上车👉 (报名地址):

大会海报.png

所有往期都有全程录播,上手年票一次性解锁全部


期待更多文章,点个赞