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

🙏向這次肺炎疫情中逝世的同胞表示哀悼。css

本文首發於政採雲前端團隊博客: 可視化搭建數據大屏系統的前端實現

背景

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

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

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

綜上,若是此類大屏的需求較多,業務的重要性明顯,就須要考慮是否是須要本身開發一套搭建大屏的系統,用以下降開發複雜度,提高研發效率,下降成本。本文嘗試基於政採雲前端團隊的數據大屏搭建系統 Big 的拆解說明,爲你們提供一種此類系統的設計和實施方案。數據庫

Big 是什麼

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

爲何叫 Big 呢? 打開百度翻譯,輸入 大屏,英文翻譯是 Big screen,四捨五入叫 Bigjson

本身作一套系統的優點

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

總覽

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

image-20191211203434089.png

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

大屏按場景劃分,可分爲編輯和查看。服務器

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

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

編輯

裁剪2.gif

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

ds1.png

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

設計思路

  • 頁面數據和依賴的組件由 SSR 注入到 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
  :class="['data-com', item.info.previewId === activePreviewId ? 'data-com-active' : '']"
  v-for="item in preCompList"
  :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 用於選擇組件、縮放組件大小,可參考官方文檔。這個組件不支持分組和多選對齊場景,須要定製開發。

navigator-line 顯示組件當前的標尺位置。這裏要注意避免由於畫布縮小致使座標看不清,除以縮放比例便可。

使用 Vue 動態組件 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。

未命名文件.png

待優化

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

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

總結

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

招賢納士

政採雲前端團隊(ZooTeam),一個年輕富有激情和創造力的前端團隊,隸屬於政採雲產品研發部,Base 在風景如畫的杭州。團隊現有 50 餘個前端小夥伴,平均年齡 27 歲,近 3 成是全棧工程師,妥妥的青年風暴團。成員構成既有來自於阿里、網易的「老」兵,也有浙大、中科大、杭電等校的應屆新人。團隊在平常的業務對接以外,還在物料體系、工程平臺、搭建平臺、性能體驗、雲端應用、數據分析及可視化等方向進行技術探索和實戰,推進並落地了一系列的內部技術產品,持續探索前端技術體系的新邊界。

若是你想改變一直被事折騰,但願開始能折騰事;若是你想改變一直被告誡須要多些想法,卻無從破局;若是你想改變你有能力去作成那個結果,卻不須要你;若是你想改變你想作成的事須要一個團隊去支撐,但沒你帶人的位置;若是你想改變既定的節奏,將會是「5 年工做時間 3 年工做經驗」;若是你想改變原本悟性不錯,但老是有那一層窗戶紙的模糊… 若是你相信相信的力量,相信平凡人能成就非凡事,相信能遇到更好的本身。若是你但願參與到隨着業務騰飛的過程,親手推進一個有着深刻的業務理解、完善的技術體系、技術創造價值、影響力外溢的前端團隊的成長曆程,我以爲咱們該聊聊。任什麼時候間,等着你寫點什麼,發給 ZooTeam@cai-inc.com

相關文章
相關標籤/搜索