如何做一个看板搭建系统

如何做一个看板搭建系统


http://zoo.zhengcaiyun.cn/blog/article/buildingsystem


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

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


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

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


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


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 属性等。


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


const Widget = ({config}) => {

  // 组件点击事件

 const handleClick=() => {/**/};

  // 布局处理

  const handleLayout=() => {/**/};

  // 获取组件数据

  const getWidgetData=() => {/**/};

  // 其他的一些公共逻辑

  .......


 const WidgetComp = getWidgetComp(widget.type);

 return (

  return <WidgetComp config={widget} />

 )

}

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


<div>

 {widgets.map(widget => <Widget config={widget} key={widget.id} />)}

</div>

四、配置文件的编辑

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


一个编辑侧可能长这样:


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


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


<Form form={form}>

  <Item name="name" label="名称" rules={[{ required: true, message: '请输入' }]}>

    <Input placeholder="请输入" />

  </Item>

</Form>

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


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


const renderForm = ({type}) => {

 if(type === 'line') {

  return (

   <Item name="name" label="名称" rules={[{ required: true, message: '请输入' }]}>

        <Input placeholder="请输入" />

      </Item>

  )

 } else if(type === 'bar') {

  return (

   <Item name="width" label="宽度" rules={[{ required: true, message: '请输入' }]}>

        <Input placeholder="请输入" />

      </Item>

  )

 }

}


return (<Form form={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 动态渲染表单,关于表单动态渲染,我们团队有一篇不错的文章《表单数据形式配置化设计》(https://juejin.cn/post/7119639489567260686),大家有兴趣可以参考下。


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


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

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


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


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


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


组件线上编辑(上传)模块。

组件审核模块。

组件更新/发布模块。

组件管理(上架/下架/删除/下载/版本)。

六、静态化

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


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


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


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


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


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


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


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

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


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

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


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


八、组件之间的通信

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


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



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


export default {

 publish:[ // 事件发布

  {

      name: "电子卖场预警发布/流程/onChange", // 发布事件名

   reflectName: "onChange" // 什么行为触发该事件

    }

 ],

  subscribe: [  // 事件订阅

  {

      list: ["电子卖场预警发布/onChange"], // 订阅的事件集合

   action: "setClass", // 事件行为,就是你订阅的事件被触发后,你要干什么

      handler: "function parse(params) {↵ return {};↵ }" // 结合事件行为与该事件的返回值,组件做出自身行为

  }

 ]

}

九、参考

1、《如何设计可视化搭建平台的组件商店》(https://juejin.cn/post/6986824393653485605)




作者:立航


欢迎关注微信公众号 :前端民工