可視化搭建數據大屏系統的前端實現

  • 2020 年 2 月 25 日
  • 筆記

本文首發於政采雲前端團隊博客:可視化搭建數據大屏系統的前端實現 https://www.zoo.team/article/data-visualization

背景

隨着公司業務的發展,經常會收到一些數據大屏的需求。目前我司有兩種實現方案,一是人肉搭建,二是用阿里雲 DataV 搭建。

人肉搭建,在本地腳手架開發環境中進行編碼,有大量的重複勞動,能力復用性差,佔用前端寶貴的開發時間。

DataV 功能強大,但需要付費使用,且好用的組件還要額外收費,不支持本地化部署,還需要維護兩套數倉。

綜上,如果此類大屏的需求較多,業務的重要性明顯,就需要考慮是不是需要自己開發一套搭建大屏的系統,用以降低開發複雜度,提升研發效率,降低成本。本文嘗試基於政采雲前端團隊的數據大屏搭建系統 Big 的拆解說明,為大家提供一種此類系統的設計和實施方案。

Big 是什麼

Big 是基於政采雲前端搭建系統 魯班,和數據大屏組件庫,進行快速搭建數據大屏的可視化系統。

為什麼叫 Big 呢? 打開百度翻譯,輸入大屏,英文翻譯是 Big screen,四捨五入叫 Big

自己做一套系統的優勢

  • 可定製性:內部產品,組件和展示形式私人訂製
  • 支持本地化部署:業務需要決定部分業務只能在內網訪問,無法訪問外網(包括阿里雲)
  • 解決 DataV 需要維護兩套數倉的問題
  • 節約公司成本,增強公司數據產品能力,助力營收

總覽

數據大屏是用可視化的方式展示龐雜數據的產品,經常會用在會議展覽、業務監控、風險預警、地理信息分析等多種業務場景。下圖是阿里雲 DataV 的一個模板:

從前端實現來看,大屏是由線圖、柱狀圖、餅圖、標題、背景、邊框等基本元素組成。實現思路是以這些基本元素為組件,通過選擇組件、拖拽方式布局,配置樣式、數據來源,將這些數據保存在數據庫中。展示頁面獲取依賴的組件、樣式和數據信息,呈現給用戶。

大屏按場景劃分,可分為編輯和查看。

編輯:指的是大屏製作者製作大屏。

查看:包含兩種情況,大屏製作者預覽和實際用戶查看大屏。

編輯

編輯大屏是數據可視化系統核心,頁面布局參考 DataV:

拆解為 4 個部分:頂部、組件區、畫布、數據配置區。先講下設計思路,再依次分解各區。

設計思路

  • 頁面數據和依賴的組件由 SSR (https://juejin.im/post/5b063962f265da0ddb63dac3) 注入到 HTML 文件中
  • App 數據保存在 App state 中,未使用 Vuex(後續會考慮使用 Vuex)
  • 數據用 props 傳遞給子組件
  • 數據從子組件採用事件中心傳遞給祖父級組件

頂部

頂部區域包含三部分:左側開關區、控制圖層、組件列表、數據配置區的顯示隱藏;中間是大屏的標題;右側是保存和預覽。

組件區

組件區分為左側圖層(已添加的組件)和右側組件列表,具備添加組件、選擇操作圖層、分組對齊的功能。

圖層

  • 圖層支持上移、下移、置頂、刪除的操作,支持右鍵顯示操作菜單(暫不支持多選和分組)。實現原理是使用數組的基本方法改變數組
  • 單擊組件選擇該組件,畫布區選中組件,數據配置區顯示配置項

組件列表

  • 所有組件展示所有大屏組件,點擊或拖動添加組件
  • 添加組件採用異步獲取組件的 JS、CSS 、配置 Schema,將 CSS、JS 插入 DOM 中,配置傳入屬性配置區
  • 支持按組件類型分組,便於用戶使用。

畫布

畫布用於實時展示大屏組件的位置、尺寸、屬性和數據修改後的效果。

位置和尺寸改變通過註冊組件 vue-draggable-resizabledragresize 方法,改變對應組件的屬性。組件採用絕對定位,拖動時修改 top 和 left 的值。

屬性改變通過修改對應組件的 props.models 的值修改。

數據分為靜態數據和接口數據。啟用靜態數據時,數據從用戶填寫的數據獲取。否則組件 watch 接口 id ,每次改變時重新發送請求獲取數據。

畫布上邊和左邊是標尺,畫布縮放時標尺要跟隨變動。在標尺上移動時顯示一條移動的參考線。點擊時增加一條參考線。雙擊參考線刪除。標尺用 Canvas 畫出,旋轉 90 度可獲得 Y 軸。

右下是縮放滑塊,方便用戶縮放查看。進入頁面默認縮放到可查看全屏大小。縮放實現使用 CSS3 的transform: scale(${this.scale})

畫布上未選擇組件時,顯示頁面的基本配置,包括大屏的寬高、背景圖。

選擇組件後,高亮顯示當前組件,標識位置,右側數據配置區顯示組件 Schema 定義的配置項。

核心代碼

<div    v-for="item in preCompList"    :class="[      'data-com',      item.info.previewId === activePreviewId ? 'data-com-active' : ''    ]"    :key="item.info.activePreviewId">    <vue-draggable-resizable      :w="item.models.width || 100"      :h="item.models.height || 100"      :x="item.models.x || 0"      :y="item.models.y || 0"      :active="item.info.previewId === activePreviewId"      @dragging="onDrag"      @resizing="onResize"      @activated="        () => {          onCompActivated(item.info.previewId);        }"        :prevent-deactivation="true">      <navigator-line        :x="item.models.x"        :y="item.models.y"        :scale="scale"      />      <div        :is="item.info.name"        :models="item.models"        :extraProps="extraProps">      </div>    </vue-draggable-resizable>  </div>

vue-draggable-resizable (https://npm.taobao.org/package/vue-draggable-resizable) 用於選擇組件、縮放組件大小,可參考官方文檔。這個組件不支持分組和多選對齊場景,需要定製開發。

navigator-line 顯示組件當前的標尺位置。這裡要注意避免因為畫布縮小導致坐標看不清,除以縮放比例即可。

使用 Vue 動態組件 is (https://cn.vuejs.org/v2/api/#is) 控制組件顯示。

數據配置區

數據配置區有 2 種情況:

  • 未選中組件展示頁面級配置:大屏寬高、背景色、背景圖等
  • 選中組件:展示組件配置信息

實現邏輯:根據當前用戶的選擇來動態渲染出組件的屬性編輯域,並回填屬性的初始值,從而達到良好的編輯交互效果。用戶拖拽組件時同步更新編輯域中的屬性值,在屬性編輯域修改屬性時通知大屏觸發組件的刷新動作,達到實時編輯的效果。

數據配置區界面由組件 Schema 定義,props 定義展示,models 表示默認數據,詳細介紹見下面 Schema。

編輯類型由 fileds 里的 type 決定,實現 Input、Select、Image、Border 等各種類型組件,再利用 Vue 的動態組件 is 屬性來展示。

數據回傳:每個子組件值的修改會通知父組件 <Setting /> 更新回傳給父組件 App,這裡採用全量回傳,避免 App 對 models 查找更新數據。

查看

查看是將數據庫里保存的數據,配合組件渲染出來。實現原理是通過頁面 id 獲取組件、數據渲染。代碼如下:

<div  class="preview">    <div class="layout">      <div        :class="[          'preview-line',          preComp.info.name + '-' + preComp.info.previewId        ]"        v-for="(preComp, index) in preCompList"        :key="preComp.info.previewId"        :style="formatCompStyle(preComp, index)"      >        <div         :is="preComp.info.name"         :models="preComp.models"         :isPreview="isPreview"         :extraProps="extraProps"></div>      </div>    </div>  </div>

全屏展示

需要注意大屏是全屏展示,根據大屏配置的屏幕寬高、背景圖、背景色設置 body 樣式,設置 <meta name="viewport" content="width=' + window.screen.width + '"/> viewport 的 width 讓屏幕佔滿全屏,再監聽屏幕的變化設置壓縮比例。自適應關鍵代碼如下:

// 獲取設置的大屏寬高、背景圖、背景色  if (window.__INITIAL_STATE__) {    const { width, height, backgroundImage, backgroundColor } =          __INITIAL_STATE__.preview.pageConfig.models;    window.scr = {      width: width,      height: height,      backgroundImage: `url(${backgroundImage})`,      backgroundColor: backgroundColor,    };  } else {    window.scr = {      width: window.screen.width,      height: window.screen.height,    };  }    // 全屏展示  function resizeFull() {    if (!window.scr.height || !window.scr.width) return resizeFullBak();    var ratioX = $(window).width() / window.scr.width;    var ratioY = $(window).height() / window.scr.height;    $('body').css({      transform: "scale(" + ratioX + ", " + ratioY + ")",      transformOrigin: "left top",      backgroundSize: "100% 100%",    });  }  function resizeFullBak() {    var ratioX = $(window).width() / $('body').width();    var ratioY = $(window).height() / $('body').height();    $('body').css({      transform: "scale(" + ratioX + ", " + ratioY + ")",      transformOrigin: "left top",      backgroundSize: "100% " + ratioY * 100 + "%",    });  }

組件設計

組件是整個大屏設計的基礎。組件由組件模板來初始化,模板提供了兩個主要功能,一是實現一個可開發的簡單 Demo,二是提供打包發佈功能。

模板代碼很簡單,通過傳入的 props 控制組件的展示和業務邏輯。組件自動安裝,這樣在異步加載組件的時候頁面可以識別組件。重點講下組件的 Schema 設計。

schema.json

schema.json 是用來定義組件的可編輯項和默認配置。決定組件哪些東西可以配置,配置的形式是什麼樣子的(Input、Select 等有默認值)。所以 Schema 包含 props 和 models 兩個屬性。

props: 數組,每個元素是 tab 的一項。info 是 tab 頭部信息,fields 是配置項。fields 的 name 對應 models 的屬性名,type 決定了配置的類型,title 是中文名。還可以定義其他屬性,比如下拉框選擇項、數字輸入框最大最小值等。

models: 默認數據,props.fileds 里每個 name 的默認值。

下面是一個簡單 Schema 的定義:

{    "props": [      {        "info": {          "title": "配置",          "icon": "icon-setting"        },        "fields": [          {            "title": "組件寬度",            "name": "width",            "description": "組件寬度",            "type": "number"          },          {            "title": "組件高度",            "name": "height",            "description": "組件高度",            "type": "number"          },          {            "title": "x軸坐標",            "name": "x",            "description": "組件x軸坐標",            "type": "number"          },          {            "title": "y軸坐標",            "name": "y",            "description": "組件y軸坐標",            "type": "number"          }        ]      }    ],    "models": {      "width": 300,      "height": 200,      "x": 0,      "y": 0    }  }

碰到的問題

通信

大屏組件之間如何通信?要確保大屏組件可以通信。

採用事件中心來處理組件間的通信。核心代碼如下:

// 全局事件中心  Vue.prototype.$eventBus = new Vue();  // 觸發, 在組件內部  this.$eventBus.$emit('eventName', '這裡傳值');  // 監聽, 獲取值  this.$eventBus.on('eventName', v => {    console.log(v);  })    // 組件通知父組件區劃變動或其他變動  this.$eventBus.$emit('component__update-extraProps', { dist: '選擇的區劃' });

App 統一管理通信對象 extraProps,以 props 形式注入到每個組件。組件可以監聽 extraProps 的屬性變化。

// 組件代碼  {    ...,    props: {      extraProps: {        type: Object,        default: () => {}      }    },    computed: {      dist() {        return (this.extraProps && this.extraProps.dist) || '';      }    },    watch: {      dist(val, oldVal){        // 添加區劃改變時獲取新數據的邏輯      }    }  }

權限

大屏數據需要做權限控制,有權限的人才能查看大屏,而魯班原來頁面訪問邏輯是沒有權限的。實現方案是編輯、預覽頁面調用的免登接口訪問中間 Server,中間 Server 實現登錄,去 Server 請求數據。用戶的查看頁面內嵌魯班 iframe,該地址由實際服務器提供並帶上權限 token。訪問該魯班地址時先去 Server 鑒權,有權限返回大屏頁面,否則返回 401。

待優化

Big 處於初級階段,還有好多地方需要完善:

  • 分組:像 PS、Sketch 里一樣分組,方便歸類和操作
  • 多選:多選後選擇對齊方式。也是方便用戶操作
  • 代碼優化
  • 體驗優化

總結

DT 時代,數據可視化將會越來越重要。相信有越來越多的同學會遇到大屏的場景。通過可視化搭建大屏系統,可以賦能相關的業務方,讓非專業人士做出專業的大屏效果,同時滿足公司的一些定製化需求。這裡做了一個比較淺的大屏構建方案,目前還在開發階段,希望拋磚引玉,有更多的可視化數據搭建方案分享出來,謝謝閱讀。