1

Legao 实战 —— 从 0 到 1 建设京欧拉

 2 years ago
source link: https://jelly.jd.com/article/613af5e4353de701908ffb4e
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
京欧拉依托 Legao 组件库完成建设,汇聚集团全部主体在京东企业业务全交易平台内的全链路数据,是一个数据可视化的中后台项目。

通过调研“总部运营商行业、总部金融行业、华北区能源行业、华东区交通行业”的项目组发现,每个项目组都会对重点客户的运营数据定期/不定期给客户汇报且都是零散的不成体系还是人工线下方式整理,数据来源和口径各异,容易产生数据不一致的问题,也引发了客户相关的投诉。

11d36b920f7b79f2.png

为了解决上述问题,京欧拉应运而生~

京欧拉架构设计

  1. 中后台项目,交互逻辑复杂:我们选用 Legao 组件库,可以覆盖 80% 左右的交互场景

  2. 数据可视化:图表类展示多,我们采用了 Echarts 图表库

  3. 数据展示和分析:数据复杂,数据传递频繁,我们采用 Mobx 进行数据状态管理

  4. 项目工程构建:采用 Vite 这个越来越火的构建工具;框架/语言选用了 React17 + TS

    11d36b920f7b79f2.png

为什么选择 Legao

看到我们的架构设计,可能有的小伙伴会比较疑惑,Legao 是什么?为什么选择它?那么接下来我们简单介绍一下 ~

Legao 是一套面向中后台的 PC 端 React 技术栈的 UI 基础组件库,拥有组件数量 60+ ,每个组件都是采用 TS 编写。京欧拉项目从界面上看,属于一个中后台项目,Legao 就是为这类项目量身打造的。Legao 可以覆盖京欧拉 80% 的场景,页面中很多模块都可以直接用 Legao 现有的组件来实现,包括页面重构和交互开发,极大提高了开发的效率。

Legao的优势

Legao 有自己的一套设计规范,保障了产品体验的统一性,具体可参考 设计规范 和文章 易用性思考-乐高V2.0改版复盘 。此规范也会持续进行升级。

Legao 还创新性的采用了原子类技术,带来的收益如下:

  • 样式复用与设计风格彻底解耦,原子样式可以跨框架,甚至跨端的。
  • 高可配置,原子样式复用能力更广泛,外部使用者有了更高的样式控制力
  • 开发者友好,组件开发几乎不需要再写自定义样式,专注于组件本身。
  • 代码更易于重构,原子类与 HTML 结构无关,HTML 复用更简单。非原子类(外层 class 控制内部元素)依赖 HTML 结构,牵一发而动全身。

Legao 每个组件的 props 也是经过大家反复讨论过的,不仅有很强的语义话来确保大家见名知意,而且组件之间相同的属性还保持着高度一致性。

Legao 也拥有自己的在线调试工具,可以让用户随时测试每个组件。Legao 经得起大家的考验,也欢迎大家来深度体验。出现问题并不可怕,可怕的是没有问题;出现问题 Legao 才会成长;有了需求,Legao 才能找到努力的方向。

此外,Legao 团队修复组件问题的效率,持续创新的动力等也给了我们很多选择 Legao 组件库的信心。

如何使用 Legao

了解完了为什么选择 Legao,接下来再简单讲下如何使用 Legao。

这里只讲如何通过 npm 包的形式使用,其他使用方式可以参考官网中的 快速上手

  1. 安装 Legao

    $ npm install @feb-team/legao-react --save    
    $ #or
    $ yarn add @feb-team/legao-react
  2. 引入 Legao 到项目中,比如需要用到带有默认主题的蓝色按钮

    import { Button } from '@feb-team/legao-react'; // Legao 默认支持按需加载 
    import '@feb-team/legao-react/dist/styles/css/legao.all.css'; // 引入样式
  3. <Button type="info"> Button </Button>

    至此,Legao 的 Button 组件已经被成功的使用到了项目中,更多使用案例请参考 Legao 官网

Legao 为京欧拉提效赋能

Legao 的提效组件举例

目前我们上线了我的采购/行业分析/采购概览三个页面,我们来看看 Legao 组件的覆盖情况:

11d36b920f7b79f2.png

京欧拉项目一期中,我们共使用了 12 个 Legao 组件, 每个页面/组件都有覆盖,整体覆盖率高达 80% ,大大提高了页面开发的效率,这里拿出三个比较典型的案例说明一下。

强大的 Table

中后台项目的一大特点是很多页面都使用到了 Table,往往这些页面上半部分是一系列的筛选条件,包括文本框、下拉框等,下半部分是通过表格来展示的结果数据。 像京欧拉这样的数据展示为主的中后台项目,表格的作用更是不言而喻。所以首当其冲必须找到一个稳定并且还能满足我们需求的表格组件,当然你也可以自己开发,但是表格组件相对比较复杂,如果全部自己封装非常耗时,所以使用现成的组件是理想的选择,我们把目光投向了 Legao 组件库和 Antd。 先来对比下这两个组件库里的表格组件:

功能LegaoAntd是否支持分页是是单元格对齐方式设置是是单元格支持 html是是组件体积小大乐高规范是否组件类型类组件函数式组件

经过分析,我们发现乐高跟我们的项目很匹配,我们也使用了类组件和乐高规范,于是我们选择了乐高。

乐高中的表格组件只需要简单的配置就可以满足京欧拉业务的需求,让我们把更多的时间放在业务开发当中:

分页的配置:

<Table data={data} columns={columns} pagination={pageSize:7,total:100}></Table>

当然也可以不展示分页:

<Table data={data} columns={columns} pagination={false}></Table>

单元格对齐方式的设置:

const columns = [
  {
    title:'年月',
    key:'b',
    dataIndex:'date',
    align:'right',
    width:'200'
  }
]

单元格支持 HTML:

const columns = [
    {
        title: 'GMV 月环比',
        key: 'd',
        dataIndex: '',
        render(record) {
            return (
                <Tooltip title="同比xxx-xxx,同比下降xxx">
                    <img src={arrowDown} alt="" />
                    {record.gmvMom}
                </Tooltip>
            );
        }
    }
]

说实话,发现这个功能之前,我们差点去找产品改需求。

总体效果如图所示:

11d36b920f7b79f2.png

综上所述,Table 组件很强大也很适合我们,不仅为我们开发节省了很多时间,而且使用起来也很方便。

功能丰富的 Select

Select 也是页面搜索中不可或缺的一个重要元素。顾名思义,它的作用就是从下拉列表中选中一项来作为搜索条件。

在京欧拉项目中,主要使用了它的以下几个功能。 首先当然是最基本的单选下拉了,引入也很简单:

<Select defaultValue={data[0].name} onChange={this.selectChange}>
  {data.map((sitem,sindex)=>{
      return <Select.Option value={sitem.name} key={sindex+'one'}>{sitem.name}</Select.Option>
     })
  }                      
</Select>

其次使用了它的多选功能:

<Select defaultValue={[data[0].name]} mode={'multiple'}  onChange={this.selectChangeTwo}>
  {data.map((sitem,sindex)=>{
    return <Select.Option value={sitem.name} key={sindex+'two'}>{sitem.name}</Select.Option>
   })
  }                      
</Select>

最后还使用了它的可编辑功能,这里重点说下,京欧拉的使用场景是通过输入公司,来筛选对应的数据,很明显是实时调用接口的。而且大多时候输入的都是中文,那么问题就来了,在输入中文的过程中(按回车之前)会不会频繁触发接口呢?如果是那样必将会带来很大的性能损耗。怎么办呢?翻文档呗!翻完让我们大家很惊喜的是人家作者已经给使用者暴漏出一个现成的 API,人家早已考虑到这种使用场景了。核心代码如下:

<Select value={corpNameStore} 
        onChange={this.selectCompany} 
        showSearch={true} 
        onSearch={this.searchItem}
>
   {companyInfo.map(val => {
       return <Select.Option value={val} key={val}>{val}</Select.Option>;
     })
   }
</Select>

不得不说,Select 组件的功能真丰富。

采购概览的快速重构

天下武功,唯快不破。做采购概览的时候,给我最大的感觉就是“快”,这也是选择使用组件库开发页面要达到的根本原因。我大概评估了下,切图效率至少提升了 50%。这主要归功于两个组件,Card 和 Statistic。先来预览下这个页面

11d36b920f7b79f2.png

抛开可视化的数据部分,可以分析下这个页面,基本上就是这两个组件。

这里提醒下大家,当拿到设计图时,别上来就闷头切图,应该先从整体和单个页面的角度分别分析下,对 CSS 的组织和开发思路都会有很大帮助。

这两个组件使用起来也很简单,举个例子:

<Card title={'订单及商品'} className="product-order">
    <div className="statistic-box">
        <div>
            <Statistic 
                title={'订单数量'} 
                value={ordQtty...} 
                suffix={ordQtty...}
            ></Statistic>
        </div>
    </div>
</Card>

这里很感谢两个组件的作者,感谢他们把组件做的这么健壮。只有保证了组件的健壮性,才会实现真正的“快”。

Legao 灵活的样式定制

大家从文章前半部分可以看到,京欧拉项目的主体色是红色的,但是 Legao 组件库的主题是蓝色的,包括一些 hover,animate 等动态效果。如果我们一点一点地做样式覆盖,那就太麻烦了,毕竟页面中使用到的组件比较多。

Legao 提供给我们一种解决方案:原子类库。下面我们简单介绍一下~

  • 市面上的组件库的实现方式

一般情况下,市面上的组件库也都支持主题的修改,是通过提取 Sass 公共样式变量实现的,但是这也导致仅能修改组件库的全局样式比如主色。

  • Legao 组件库的实现方式

Legao 设计规范和技术上使用原子类生成技术支持灵活的样式定制,以满足业务和品牌上多样化的视觉需求,包括但不限于全局样式(主色、圆角、边框)和指定组件的视觉定制。使得我们的主题更改更加灵活,可以更加满足和贴合实际业务需求。

原子类介绍

原子类的设计原型参考了 Tailwind 的理念,即框架在变,实现样式复用的方法也在变,但是本质上讲唯一不变的就是 HTML 标签上的 class 属性,基于这一点,我们的 CSS 设计方案以复用 CSS 中 class 类为导向。

我们将常用的 CSS 规则,如:颜色、字体、边距、边框、阴影等属性,单独抽离出来。动态生成适配某一设计风格的原子类 class 集合。

原子类优势

这可以带来什么好处呢?

  1. 规则可以解决大约 50% 的组件 内部 样式复用
  2. 规则同样可以被组件的 外部 使用者复用
  3. 稳定后基本万年不变,自动生成一劳永逸
在项目中使用

是的,原子类不仅使开发者在组件开发的时候可以做到样式复用,同时设计规则可以同样引用到使用者的项目中,并且是可以根据自己的项目需求灵活配置的。 原子类 CSS 生成 ,可以灵活的修改主题变量,配色,边距,动画等等,这可比一般的组件库是能修改主题颜色来的灵活多变,更能贴合业务需求了~

11d36b920f7b79f2.png

听起来还是比较酷的吧~

那我们这个项目怎么使用呢?

我们把我们要修改样式在原子类网站修改,比如主题色,全部修改成项目设计稿的色值,然后一键点击下载生成后的 CSS 文件,最后把生成的 CSS 文件引入我们项目的公共的 CSS 文件中,需要注意的是,保证你生成的这个CSS 在加载 Legao 组件 CSS 之后加载,这样就可以保证所有页面的组件就可以加载到我们修改后的样式了~

11d36b920f7b79f2.png

另外,原子类提供了大量基础的样式了,这个我们同样可以在我们京欧拉页面开发过程中使用,这样也就大大节省了我们的开发时间~

11d36b920f7b79f2.png

Legao 还能做什么

Legao 为类似京欧拉这样的项目,带来的远不止这些。

未来乐高打算做一些组合型组件或页面模版,让这样的项目项搭建起来更快。这类项目有个共同的特点,很多页面都是类似的,比如比较常见的上半部分是筛选条件,下面是数据表格,未来乐高会输出这些模版,真正开发时稍微修改下就能输出一个最终页面出来,毫不夸张的说,到那时,研发也能做前端了,同时也减少了前端的很多劳动力。

如果还不够,未来乐高会提供一个能运行起来的完整工程解决方案,里面涵盖了很多中后台常见的页面。当你接到类似京欧拉这样的项目时,直接在这个工程的基础上二次加工,做少量的删删改改就能搭建出你自己的项目。

再进一步,乐高会通过可视化方式控制左侧菜单;还会制定很多页面共用的的数据字典,可以让前端通过现成的接口进行调用。

这些并不是天方夜谭,在技术上实现是完全没有问题的,因为市场上早已出现类似的竞品了。

其他技术在项目中的应用

Vite 搭建项目工程

接下来,我们说说我们这个项目的构建工具。

在 Vite 之前,Webpack 几乎是打包界的霸主地位,随着 Vite 的诞生, 2.0 版本的发布,以及 Vue 的作者尤大的极力推荐,Vite 风声也越来越大,那我们就借着东风,用起来吧~

在开始用之前,我们先了解一下 Webpack 以及 Vite 的打包原理以及差异:

Webpack 打包过程
11d36b920f7b79f2.png
  • 识别入口文件
  • 通过逐层识别模块依赖( Commonjs 、 Amd 或者 Es6 的 Import ,Webpack 都会对其进行分析。来获取代码的依赖)
  • Webpack 做的就是分析代码,转换代码,编译代码,输出代码
  • 最终形成打包后的代码
Vite 的原理
11d36b920f7b79f2.png
  • 当声明一个 Script 标签类型为 Module 时,浏览器就会像服务器发起一个 Get
  • 浏览器请求到了 main.js 文件,检测到内部含有 Import 引入的包,又会对其内部的 Import 引用发起 Http 请求获取模块的内容文件
  • Vite 的主要功能就是通过劫持浏览器的这些请求,并在后端进行相应的处理将项目中使用的文件通过简单的分解与整合,然后再返回给浏览器,Vite 整个过程中没有对文件进行打包编译,所以其运行速度比原始的Webpack开发编译速度快出许多(实际上 Vite 的实现核心就是在按需加载的基础上通过拦截请求实现了实时按需编译)
Webpack VS Vite
11d36b920f7b79f2.png
Webpack 槽点
  • 缓慢的服务器启动: Webpack Dev Server 在启动时,需要先 Build —遍,而 Build 的过程是需要耗费很多时间的
  • 使用 Node.js 去实现,打包效率更低
  • 热更新效率更低, Webpack 修改某个文件过后,会自动以这个文件为入口重写 Build —次,所有的涉及到的依赖也都会被加载一遍,所以反应速度会慢很多
Vite 改进
  • Vite 通过在一开始将应用中的模块区分为 依赖 和 源码 两类,改进了开发服务器启动时间
  • Vite 将会使用 Esbuild 预构建依赖。Esbuild 使用 Go 编写,直接编译为 Native 代码,比以 JavaScript 编写的打包器预构建依赖快 10 - 100 倍
  • 在 Vite 中,是借用了 Koa 来启动了一个服务来监听端口变化,热更新的时候, Vite 只需要立即编译当前所修改的文件即可,所以响应速度非常快
Vite 在项目中使用

我们这个项目是自己搭建的工程,没有使用脚手架,使用 Vite 搭建一个项目工程也比较简单

$ npm install vite vite-plugin-babel-import vite-plugin-imp --save-dev
//vite-plugin-babel-import  按需引入组件库样式
//vite-plugin-imp 自动导入组件库样式
$ npm install @vitejs/plugin-react-refresh --save-dev //Vite 核心功能 热更新
//package.json
{
    // ...
    "scripts": {
         "dev": "vite",
                "build": "vite build",
    },
}
//其余的依赖大家按照自己项目的情况酌情安装即可~

Mobx 管理数据

京欧拉是一个数据可视化项目,并且可以多维度,多角色的展示数据,就代表着用户的可操作项会比较多,接下来我们看看如何使用 Mobx 有条不紊的管理这些状态变更带来的数据变化~

京欧拉项目每个页面的可操作项都比较多:

11d36b920f7b79f2.png
11d36b920f7b79f2.png

每一个可操作选项的变化,需要页面中所有的视图同步更新数据。如果使用原始的组件与组件之间传参的方式来更新数据,恐怕数据还没有更新,用户已经失去耐心,关闭页面了,所以我们使用 Mobx 实时更新数据状态,每当用户操作状态变化,Mobx 就会更新状态并通知订阅着,即所有依赖此状态的组件,组件内部直接重新请求接口,更新数据~

首先,我们给每个页面组件以及公共组件都单独建立的一个 Store

import { observable, action,makeObservable,runInAction} from "mobx";
import { fetch } from "@/utils/fetch";

class Store {
  constructor(){
    makeObservable(this)
  }
  @observable corpNameStore:string='';
  @action('resetCorpName')
  resetCorpName = async (corpName:string) => {
      // 修改选中公司名称
      runInAction(() => {
          if(corpName !='')
          this.corpNameStore = corpName;
      });
  };
};

然后,建立通过一个公共的文件实例化并抛出

import * as indexStore from './Index/store';
export const GlobalStore = {
    IndexStore:new indexStore.Store(),
}

然后在根组件通过 Provider 组件注入它

import { Provider } from 'mobx-react';
import { GlobalStore } from './store/index'

ReactDOM.render(
    <Provider {...GlobalStore}>
        <Router history={createHashHistory()}>
            <Suspense fallback={<div className="loading">loading...</div>}>
                <Switch>
                    <Route exact path="/" component={Index} />
                </Switch>
            </Suspense>
        </Router>
    </Provider>,
    document.getElementById('root')
);

最后在子组件中通过 Inject 获取 Store

@inject((stores: any) => ({
    getBPin: stores.CommonStore.getBPin,
    corpNameStore:stores.CommonStore.corpNameStore,
}))
@observer
class Head extends React.PureComponent {
      ...
}
export default Head;

Echarts 覆盖图表需求

京欧拉是一个数据可视化的项目,那么说明图表类的展示就会比较多,我们综合评估了京欧拉项目的图表,决定选用 Echarts 实现。

但是在后来我们实际开发的时候,甚至有些后悔这个决定,为啥呢? Echarts 的文档介绍不是很详细~

我们接下来拿地图实现举个例子,以后大家要是写地图类的 Echarts ( v4.8.0 ),可以参考~

Echarts 图表实现中国地图
11d36b920f7b79f2.png

如上图所示,我们在全局安装了 Echarts,使用 React 语法按照上图一顿操作之后,发现地图轮廓需要另外的第三方资源。但是我依稀记得,Echarts 本身是自带地图 Canvas 资源的,这是怎么回事儿?要不我们去 node_modules 里边看看?

经过一番搜索,我们发现在 echarts/map/js 这个路径下边,包含了中国地图以及省份地图的资源。那我们在组件中引用一下这个资源

11d36b920f7b79f2.png

然后配置项配置一下

11d36b920f7b79f2.png

完美~地图成功展示出来啦~

11d36b920f7b79f2.png

就在我们以为万事大吉的时候,又掉到了另外一个坑里:这个地图展示的需求是点击中国地图中的各个省市之后,需要跳转省市地图,也就是我们也需要同时实现每个省的地图~

按照常理,我们按照跟中国地图一样的实现方式,正常是可以实现的,但是怎么也出不来,官网文档也没有相关的实现~那怎么办呢~

保持理智,找原因 ing ~

经过一番 debug ,终于找到原因,就是省市地图的配置,需要使用汉字,不能按照 node_modules 资源里边的样子使用拼音~

11d36b920f7b79f2.png

标红的字段需要使用汉字才能实现省市地图,但是中国地图需要用 China ~

其他的功能项按照文档配置即可,这样,中国地图这个功能可算是实现了~两个小坑,希望能给大家带来一些小小的帮助~

京欧拉致力于通过多维度、多角色分析采购数据,助力采购管理者进行科学决策,权限分级清晰明了,数据隔离准确无误,授权机制严禁安全。目前京欧拉第一个完全版本已经上线,后续还会迭代、增加新的功能。 Legao 组件库也会更好的进行迭代以及更新,持续为京欧拉项目的迭代提供强有力的动力。也欢迎大家安装使用 Legao组件库


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK