可視化搭建數據大屏系統的前端實現
- 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-resizable
的 drag
和 resize
方法,改變對應組件的屬性。組件採用絕對定位,拖動時修改 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 時代,數據可視化將會越來越重要。相信有越來越多的同學會遇到大屏的場景。通過可視化搭建大屏系統,可以賦能相關的業務方,讓非專業人士做出專業的大屏效果,同時滿足公司的一些定製化需求。這裡做了一個比較淺的大屏構建方案,目前還在開發階段,希望拋磚引玉,有更多的可視化數據搭建方案分享出來,謝謝閱讀。