一、什么是数据看板,数据看板有什么用

在解释数据看板概念之前,我们要先知道,什么是数据可视化。

数据可视化被许多学科视为与视觉传达含义相同的现代概念。它涉及到数据的可视化表示的创建和研究。 为了清晰有效地传递信息,数据可视化使用统计图形、图表、信息图表和其他工具。可以使用点、线或条对数字数据进行编码,以便在视觉上传达定量信息。 有效的可视化可以帮助用户分析和推理数据和证据。它使复杂的数据更容易理解和使用。 - 维基百科

数据看板即是数据可视化的载体,通过合理的页面布局、效果设计来将可视化数据更好的展现。

个人认为,数据看板的作用大致为以下两种:

1、掌握情况

通过数据呈现,决策者们能较为清晰的掌握自己产品的运营情况。

2、问题解决

通过数据分析,能够通过数据可视化,从动态数据中提炼出规律,发现不符合预期的部分并给出修改意见。

二、配置文件数据结构设计

假设有如下这样的一个看板页面,让你用一份json文件来记录组件的信息,类似,宽高、位置以及标题等等,试想一下你会怎么设计 json 的数据结构(可以不用关心具体的值是什么)。




数据可视化看板讲解词 数据可视化看板制作_数据可视化看板讲解词


这个问题并不是很难,相信大部分人都能设计一份自己的数据结构,比如我设计的结构就是下面这样:

[
  {
    "type": 'line', // 类型
    "id": 1, // 唯一标识
    "data": [], // 数据存放
    "title": "货物销售情况", // 组件标题
    "layout": { x: 0, y: 0, w: 3, h: 2 }, // 页面布局信息(坐标、宽高)
  },
  {
    "type": 'bar',
    "id": 2,
    "data": [],
    "title": "货物留存情况",
    "layout": { i: 'c', x: 3, y: 0, w: 3, h: 2 },
  }
]
复制代码

有了这样一份数据结构之后,接下来,我们接下来就要开发组件,比如{type:line}的配置项就去用line组件渲染,并且我们需要把数据还有一些其他配置项传给组件。关于每个组件的开发,这里就不深入探讨了,需要考虑的就是你的内部组件要具有接受数据并处理配置的能力。

三、组件容器

现在组件有了,配置也有了,接下来就很简单了,根组件直接循环遍历下,就可以了,代码如下:

<div>
  {widgets.map(widget => {
    const WidgetComp = getWidgetComp(widget.type);
    return <WidgetComp config={widget} key={widget.id} />
  })}
</div>
复制代码

好了,大功告成,页面渲染完成!

先别着急,我们想想看,是不是每个组件,我们都需要去做同样的工作,比如数据请求、布局处理等等。

这时候,我们就需要在一个统一的地方去做这些同样且重复的工作。这里我想到了两种方式:

1、公共的 util 函数。

2、给所有组件增加一层包裹容器。

第一种方法虽然可以满足我们的要求,但是还是存在局限性,那就是当我们想要在 dom 上做一些处理的时候,还是不可避免的会有重复代码量,比如给组件添加点击事件、设置 dom 的 style 属性等。

我个人比较推荐第二种方法,也就是组件容器。一个组件容器大概长这样:

constWidget = ({config}) => {
  // 组件点击事件consthandleClick=() => {/**/};
  // 布局处理
  consthandleLayout=() => {/**/};
  // 获取组件数据
  constgetWidgetData=() => {/**/};
  // 其他的一些公共逻辑
  .......

  constWidgetComp = getWidgetComp(widget.type);
  return (
    return<WidgetCompconfig={widget} />
  )
}
复制代码

对应的根组件代码可以改成这样:

<div>
  {widgets.map(widget => <Widgetconfig={widget}key={widget.id} />)}
</div>复制代码

四、配置文件的编辑

上面的工作做完之后,我们可以做一个简单的渲染了,但是既然是搭建系统,怎么可能仅仅满足于渲染呢?接下来,我们要考虑下,给搭建的用户提供一个可以修改配置的地方。为了统一口径,我们把使用配置的地方,称用户侧,修改配置的地方,称编辑侧。

一个编辑侧可能长这样:


数据可视化看板讲解词 数据可视化看板制作_数据分析_02


大致分为三个区域,组件商店、展示区域和配置区域,这里我们先说右侧的组件配置区域。

我们这里拿 line 组件 举例。假设,现在产品小姐姐给你提了第一个需求,需要这个line组件支持修改名称。so easy!直接写个 form 表单:

<Form form={form}>
  <Item name="name" label="名称" rules={[{ required: true, message: '请输入' }]}>
    <Input placeholder="请输入" />
  </Item>
</Form>
复制代码

修改完之后,直接把新的名称发给后端存起来就完事了。

一天后,产品小姐姐又提了另一个需求,bar 组件需要支持修改宽度。没办法,接着改,不能让产品小姐姐看不起!于是你的代码可能变成了这样:

constrenderForm = ({type}) => {
  if(type === 'line') {
    return (
      <Itemname="name"label="名称"rules={[{required:true, message: '请输入' }]}>
        <Inputplaceholder="请输入" />
      </Item>
    )
  } elseif(type === 'bar') {
    return (
      <Itemname="width"label="宽度"rules={[{required:true, message: '请输入' }]}>
        <Inputplaceholder="请输入" />
      </Item>
    )
  }
}

return (<Formform={form}>
  {renderForm()}
</Form>)
复制代码

紧接着,产品小姐姐的需求与日增多,组件数量也越来越庞大,你的 if else 越写越多....。所以我们需要一个统一的修改器,和一个统一的描述组件配置的 schema

先说组件的 schema 文件,每一个组件都佩带一个 schema 文件,schema 里主要记录当前组件支持修改的配置项(fields),和当前组件配置项的默认值(models)。类似这样:

{
  "fields": [{
    "label": "名称",
    "type": "input",
    "name": "name"
  }],
  "models": {
    "name": "默认名称"
  }
}
复制代码

当点击一个组件的编辑按钮时,根据组件类型获取到对应的 schema 文件,将组件配置项默认值 models 和从后端拿到的数据,做一个 merge,将 merge 后的数据和fieds传给修改器。

修改器要做的工作就是,根据 fileds 动态渲染表单,关于表单动态渲染,我们团队有一篇不错的文章《表单数据形式配置化设计》,大家有兴趣可以参考下。

总结下编辑侧的工作,第一步,拿到对应组件的 schema 文件,传给修改器。第二步,修改器根据 fileds 动态渲染表单,并根据后端返回数据和 schema 中的默认数据,用做表单回显。最后就是,搭建用户修改配置项,再把修改后的数据发送给后端保存。

五、从远程组件商店加载组件

以上已经完成了配置的产出、使用和修改。接下来我们再思考思考组件。我们现在的组件都是存在本地的,后期随着组件数量的增多,页面js文件肯定会越来越大,即使你使用了代码分割,也不可避免会导致build后的包体积越来越大。

所以我们需要一个远端存放组件的地方,也就是组件商店。

那么商店既然是远端的,那把这个商店建在哪里呢?我们团队的解决方案就是,把每个组件打上版本号上传到静态服务器上这样,版本号的作用这里先不管,这样不管在编辑侧还是用户侧,我们可以根据项目的配置数据远程加载组件,关于远程组件的加载方案,可以参考下我们团队另一篇写的不错的文章《浅谈低代码平台远程组件加载方案》。

当然,一个组件商店不仅于此,我们目前只实现了一些基本功能,一个完整的组件商店功能包括:

  • 组件线上编辑(上传)模块。
  • 组件审核模块。
  • 组件更新/发布模块。
  • 组件管理(上架/下架/删除/下载/版本)。

六、静态化

现在我们在编辑侧修改提交,用户侧就能实时的得到修改后的配置了,对应的页面刷新就会变化。

可是这样会导致一个问题,假设以前的老版本 v1.0 需要修改到新版本 v2.0,并且工作量很大,需要两天才能完成,那么你第一天只能改一半,第二天早上你准备改另一半的时候,可能你们公司的投诉电话已经被打爆了,因为,你的客户早上打开页面的时候,是你第一天只改了一半配置的页面。

怎么解决这个问题呢?答案是增加一个发布操作,只有发布过后,你修改的配置才会在用户侧生效。

但是这样还是会有问题,假设你在项目 B 修改了一个组件,该组件在项目A也用到了,那一旦该组件引发了 bug,项目 A,项目 B 都发出问题,如果是十几个项目,那就会引起十几个项目的问题。

基于此,我们需要思考一种方法,针对发布过后的项目,即使对应组件有修改,也不会影响到它们,我们团队使用的解决方案就是组件静态化。

大致思路就是,发布时,首先获取对应的项目配置,根据项目配置获取组件列表。然后根据列表,获取远程组件商店的 js 文件,将获取到的 js 文件插入到对应的 html 模版中。最后,将拼装好的 html 放到静态服务器上。

这样,后续用户侧只需要访问静态服务器上的 html 就可以了,即使组件也修改,已发布的项目只要不重新发布,就不会影响已经发布的项目。

Tips: 进一步的话,我们可以把项目配置也做成静态化,这样,第一可以省去后端同学同步两份不同环境数据的工作量,但对于前端来说,就是顺手的事。第二,方便前端自己管理已发布的配置数据。

但是如果组件修改,后续已发布的项目也需要重新发布呢?怎么尽量减少发布风险呢?这个就需要做组件的版本管理和容器的版本管理了。

七、组件和容器的版本管理

前面我们在介绍组件商店的时候说到,上传组件的时候,我们给对应组件打上了版本号,后续组件有修改的时候,修改过的组件,会被打上新的版本号。这样,针对已发布的项目,只要不更新对应组件的版本号,便不会影响对应的项目组件了。

那为什么要做容器的版本管理呢?前面我们介绍了组件容器的作用就是处理组件的通用逻辑,如果说组件的修改有可能会影响到其他项目,那么组件容器的修改就是一定会影响到其他项目。所以容器的版本管理比组件的版本管理更加重要。思路其实跟组件差不多,我们可以把容器理解成一个特殊的组件,跟组件不同的是,这个组件不在配置文件解析出来的组件列表中。

八、组件之间的通信

在真实的搭建场景中,避免不了的就是,组件之间需要通信,比如,点击组件 A,组件 B 需要做出对应的响应。我们在这里借鉴的是【发布-订阅】。

我们设计一个事件调度中心,可以处理所有的事件注册与事件响应。订阅者发布对应的事件,事件调度中心负责将事件放入事件池中。发布者负责在对应的时机触发对应事件,上面例子中,组件 A 就是发布者,当组件 A 被点击的时候,就是触发事件的对应时机。事件在调度中心出发后,再将结果反馈给订阅者。订阅者拿到反馈结果做出行为。


数据可视化看板讲解词 数据可视化看板制作_信息可视化_03


关于事件的配置描述的数据结构,形式有很多种,这里贴一份我们目前项目中使用的数据结构:

exportdefault {
  publish:[ // 事件发布
    {
      name: "电子卖场预警发布/流程/onChange", // 发布事件名
      reflectName: "onChange"// 什么行为触发该事件
    }
  ],
  subscribe: [  // 事件订阅
    {
      list: ["电子卖场预警发布/onChange"], // 订阅的事件集合
      action: "setClass", // 事件行为,就是你订阅的事件被触发后,你要干什么
      handler: "function parse(params) {↵ return {};↵ }"// 结合事件行为与该事件的返回值,组件做出自身行为
    }
  ]
}

作者:政采云前端团队