機櫃 U 位管理是一項突破性創新技術--繼承了 RFID 標籤(電子標籤)的優勢的同時,徹底解決了 RFID 技術(非接觸式的自動識別技術)在機房 U 位資產監控場應用景中的四大缺陷,採用工業互聯網雲平臺監控機房 U 位的方法,具備高可靠性、高準確性、精準定位、免維護的特色,知足了 U 位級實時監控、智能運維閉環管理的需求。設備上架、下架與遷移,自動變動和實時記錄,(用戶評價):部署工業互聯網雲平臺監控機房 U 位後節省了 99% 的登記變動記錄的時間,並且實現了變動後數據 100% 的準確,在這以前是不可思議的,真正實現運維管理最後的工做。html
https://hightopo.com/demo/rack-builder/index.htmlnode
整個 Demo 由最左側的樹,中間部分的列表以及右邊的拓撲圖總體構成,爲了讓整個佈局乾淨一點,這裏結合 splitView 和 borderPane 兩種佈局方式來進行。首先將場景分爲左右兩個部分,左邊爲樹,右邊是列表和拓撲圖的組合:數組
treeView = this.treeView = new ht.widget.TreeView(),// 樹組件 (http://www.hightopo.com/guide/guide/core/treeview/ht-treeview-guide.html)
splitView = this.splitView = new ht.widget.SplitView(treeView, null, 'h', 280);// 分割組件,將場景分爲左右兩個部分,左邊爲樹組件,右邊爲空,左邊的寬度爲280,右邊的組件先設置爲空到時候根據具體狀況分配 (http://www.hightopo.com/guide/guide/core/splitview/ht-splitview-guide.html)
this.splitView.addToDOM();
複製代碼
佈局結束記得將最外層組件的最底層 div 添加到 body 中,HT 的組件通常都會嵌入 BorderPane、SplitView 和 TabView 等容器中使用,而最外層的HT組件則須要用戶手工將 getView() 返回的底層 div 元素添加到頁面的 DOM 元素中,這裏須要注意的是,當父容器大小變化時,若是父容器是 BorderPane 和 SplitView 等這些HT預約義的容器組件,則HT的容器會自動遞歸調用孩子組件 invalidate 函數通知更新。但若是父容器是原生的 html 元素, 則 HT 組件沒法獲知須要更新,所以最外層的 HT 組件通常須要監聽 window 的窗口大小變化事件,調用最外層組件 invalidate 函數進行更新。bash
爲了最外層組件加載填充滿窗口的方便性,HT 的全部組件都有 addToDOM 函數,其實現邏輯以下,其中 iv 是 invalidate 的簡寫:app
addToDOM = function(){
var self = this,
view = self.getView(),//獲取組件的底層 div
style = view.style;
document.body.appendChild(view);//將組件底層div添加進body中
style.left = '0';//ht 默認將全部的組件的position都設置爲absolute絕對定位
style.right = '0';
style.top = '0';
style.bottom = '0';
window.addEventListener('resize', function () { self.iv(); }, false);//窗口大小改變事件,調用刷新函數
}
複製代碼
右邊的拓撲圖部分是在監聽選中變化事件的時候更新的,固然,初始化設置的選中樹上的第一個節點就觸發了選中變化事件:運維
cms.treeView.sm().ss(cms.treeView.dm().getDatas().get(0));// 設置選中樹上的第一個節點
treeView.sm().ms(function(){// 監聽選中變化事件
var ld = treeView.sm().ld();// 獲取最後選中的節點
if (ld) self.updateForm(ld.a('type'));
});
CMS.prototype.updateForm = function(type){
var self = this,
ld = this.treeView.sm().ld();// 獲取樹上選中的最後一個節點
if (type === self.TYPE_RACK_SPACE) {// 若是是在樹上選中了節點,那麼點擊「添加機櫃」就直接在樹上選中的節點下生成
if (!this.rackBuild) {
this.rackBuild = new RackBuild(this);// 此類中定義了場景的中間列表部分,右邊拓撲圖部分以及對應的邏輯
}
this.rackBuild.setData(ld);// 在樹上添加一個新的節點
this.splitView.setRightView(this.rackBuild.getHTView());// 設置分割組件右邊的內容爲整個場景的中間「列表」內容+右邊的拓撲內容
}
}
複製代碼
上面代碼中 splitView.setRightView 函數意爲設置右側組件,有了這個函數,我就能夠動態地改變 spliteView 組件中的右側組件了。ide
既然佈局布好了,就該向具體的位置添加內容了。先來看看如何向樹上添加節點。首先我定義了一個初始化的樹上的值 treeData,經過遍歷這個數組建立樹上的節點以及節點上的父子關係:函數
var treeData = [{
name: 'Racks',
type: 8,
children: [
{
name: 'rack1',
type: 18,
usize: 32
}, {
name: 'rack2',
type: 18
}
]
}];
CMS.prototype.loadTreeData = function(){// 加載樹上的節點
var self = this;
setTimeout(function(){
var data = treeData;
data.forEach(function(d) {// 遍歷 treeData 數組的值
self.createData(d, null);// 第一個節點父親爲空
});
self.treeView.expandAll();// 展開樹
}, 10);
}
複製代碼
經過 createData 函數建立節點,並給節點設置父子關係:工具
CMS.prototype.createData = function(data, parent){// 在樹上建立一個節點
var self = this,
htData = new ht.Data(),// 新建 Data 類型節點
dm = this.treeView.dm();// 獲取樹的數據容器
htData.a(data);// 設置節點業務屬性 data
htData.setName(data.name)// 設置節點的 name 屬性
if (parent) {
htData.setParent(parent);// 設置父親節點
}
dm.add(htData);// 將節點添加到數據容器中
if (data.children) {// 若是節點中有 children 對象
data.children.forEach(function(d){// 遍歷 children 對象
self.createData(d, htData);// 再建立 children 對象中的節點做爲孩子節點
});
}
return htData;
}
複製代碼
眼尖的同窗在前面的代碼中可能注意到了一個未聲明的 RackBuild 類,在此類的聲明中咱們將場景的右半部分主要分爲左右兩個部分,左邊又分爲上下兩個部分,右邊也分爲上下兩個部分。佈局
這裏先將整個右邊的部分進行佈局,下面代碼中的變量 listBorder 爲上圖的左半部分,變量 borderPane 爲上圖的右半部分,至於鷹眼組件部分,是添加到在 borderPane 的上層:
listView = this.listView = new ht.widget.ListView(),// 列表組件(http://www.hightopo.com/guide/guide/core/listview/ht-listview-guide.html)
listForm = this.listForm = new ht.widget.FormPane(),// 表單組件(http://www.hightopo.com/guide/guide/plugin/form/ht-form-guide.html)
listBorder = this.listBorder = new ht.widget.BorderPane(),// 場景中間邊框面板組件(http://www.hightopo.com/guide/guide/core/borderpane/ht-borderpane-guide.html)
gv = this.gv = new ht.graph.GraphView(),// 拓撲組件(http://www.hightopo.com/guide/guide/core/beginners/ht-beginners-guide.html#ref_graphview)
borderPane = this.borderPane = new ht.widget.BorderPane(),
toolbar = this.toolbar = new ht.widget.Toolbar(),// 工具條組件(http://www.hightopo.com/guide/guide/core/toolbar/ht-toolbar-guide.html)
splitView = this.splitView = new ht.widget.SplitView(listBorder, borderPane, 'h', 220),// 分割組件
overview = this.overview = new ht.graph.Overview(gv),// 鷹眼組件(http://www.hightopo.com/guide/guide/plugin/overview/ht-overview-guide.html)
overviewDiv = overview.getView();// 獲取鷹眼組件底層 div
overviewDiv.style.height = '120px';// HT 的組件默認都是絕對定位的
overviewDiv.style.width = '120px';
overviewDiv.style.left = '0';
overviewDiv.style.bottom = '0';
overviewDiv.style.zIndex = 10;
borderPane.getView().appendChild(overview.getView());// 將鷹眼組件底層 div 添加到面板組件的底層 div 中
listBorder.setTopView(listForm);// 設置頂部組件
listBorder.setCenterView(listView);// 設置中間組件
listBorder.setTopHeight(32);// 設置頂部組件高度
listForm.setVPadding(2);// 設置表單頂部和頂部與組件內容的間距
listForm.setHPadding(4);// 設置表單左邊和右邊與組件內容的間距
listForm.addRow([// 添加一行組件
{
comboBox: {// 組合框類
labels: ['All', 'Pathch Panel', 'Switch', 'Server', 'Backbone Switch/Router'],// 設置下拉可選值對應文本
values: [-1, 5, 9, 10, 11],// 設置下拉可選值
value: -1,// 設置當前值,可爲任意類型
onValueChanged: function(e) {// 值變化觸發函數
var val = this.getValue();// 獲取當前的值
self.listTypeFilter = val;
self.listView.ivm();// 最完全的刷新方式
}
}
}
], [0.1], 28);// 參數二爲行內元素的寬度,參數三爲該行高度
borderPane.setCenterView(gv);// 設置中間組件
borderPane.setTopView(toolbar);// 設置頂部組件
borderPane.setTopHeight(32);// 設置中間組件高度
複製代碼
從上面的代碼能夠看出,splitView 爲最外層組件,經過 getHTView 函數返回這個組件,在前面動態設置整個場景的右半部分的組件的時候咱們就是經過設置 this.splitView.setRightView(this.rackBuild.getHTView()) 設置場景的右半部分爲 rackBuild 的底層 div:
getHTView: function(){// 獲取最外層組件
return this.splitView;
}
複製代碼
toolbar 工具條中總共的元素就三個:添加機櫃,編輯機櫃和刪除機櫃。這三個元素只須要經過 setItems 的方式添加到 toolbar 工具條組件上便可,元素的具體定義以下:
var toolbarItems = [// 工具條上三個的元素
{
icon: self.getToolbarIcon('toolbar.add.rack'),// 用的是咱們前面聲明過的圖片
toolTip: 'Add a rack',// 文字提示顯示內容
action: function(){// 點擊按鈕後觸發的函數
self._editingRack = null;
self.addRackForm.reset();
self.addRackDialog.show();// 彈出對話框,添加一個新的機架,並填寫該機架的信息
}
},{
icon: self.getToolbarIcon('toolbar.edit.rack', function(){// 判斷右側拓撲圖上最後選中的節點 來決定這個圖標的顯示顏色(若是沒有選中機櫃,那麼此圖標顯示顏色爲灰色)
return self.gv.sm().ld() instanceof Rack;
}),
toolTip: 'Edit rack info',
action: function(){
var ld = self.gv.sm().ld();// 獲取 gv 中最後選中的節點
if (!ld) return;
self._editingRack = ld;
self.addRackForm.v('name', ld.a('name'));// 彈出框中的 name 賦值爲 ld 的業務屬性 name 的值
self.addRackForm.v('usize', ld.a('usize'));// 彈出框中的 usize 賦值爲 ld 的業務屬性 usize 的值
self.addRackDialog.show();// 點擊此按鈕會出現彈出框
}
},{
icon: self.getToolbarIcon('toolbar.delete', function(){
return self.gv.sm().ld() instanceof Rack;// 判斷右側拓撲圖上最後選中的節點的類型
}),
toolTip: 'Delete a rack',
action: function(){
self.handleRemoveRack();// 在拓撲圖上刪除機櫃,並刪除樹上此機櫃對應的節點
}
},
]
複製代碼
接下來只要把這個 item 添加到 toolbar 中並設置一下排布的方式便可:
toolbar.setItems(toolbarItems);// 設置工具條元素數組
toolbar.setStickToRight(true);// 設置工具條是否向右對齊排布
toolbar.enableToolTip(true);// 工具條容許文字提示
複製代碼
上面出現的點擊 toolbar 工具條按鈕觸發的事件中有一個「彈出對話框」的操做,經過 this.addRackDialog.show() 來實現,addRackDialog 對象定義在 initDialog 函數中,做用爲建立一個 dialog 對話框(http://www.hightopo.com/guide/guide/plugin/dialog/ht-dialog-guide.html),咱們設置此對話框中的內容爲一個 form 表單進行顯示,同時還設計了兩個按鈕,「OK」按鈕做爲執行建立/更改機櫃的屬性,「Cancel」按鈕不執行其餘操做,只是將對話框隱藏:
initDialog: function(){// 初始化點擊「增改」出現的對話框
var self = this,
addRackDialog = this.addRackDialog = new ht.widget.Dialog(),
addRackForm = this.addRackForm = new FormPane(),// 此類繼承於 ht.widget.FormPane
labelWidth = 72;
addRackForm.addRow([// 添加行
'Name',{
id: 'name',
textField: {}
}
], [labelWidth, 0.1]);
addRackForm.addRow([
'Height(U)',{
id: 'usize',
textField: {
type: 'number'
}
}
], [labelWidth, 0.1]);
addRackDialog.setConfig({// 配置對話框的標題,尺寸,內容等
title: "New Rack",// 對話框的標題
content: addRackForm,// 指定對話框的內容
width: 320,// 指定對話框的寬度
height: 220,// 指定對話框的高度
draggable: true,// 指定對話框是否可拖拽調整位置
closable: true,// 可選值爲true/false,表示是否顯示關閉按鈕
resizeMode: "none",// 鼠標移動到對話框右下角可改變對話框的大小 none 表示不可調整寬高
buttons: [// 指定對話框按鈕組內容
{
label: "Ok",// 按鈕顯示文本
action: function(button, e) {// action爲回調函數,當此按鈕被當點擊時,回調函數會執行
var formData = addRackForm.getValueObject(), rack;
if (!formData.usize) {// 若是沒有填寫 Height 的值,則默認高度爲18
formData.usize = 18;
}
if (self._editingRack) {// 若是是「編輯rack信息」的彈框
rack = self._editingRack;
rack.a(formData);
rack.a('treeNode').a(rack.getAttrObject());//
}
else {// 「增長」新的機櫃
rack = self.createRack(formData);// 建立一個新的 rack 模型
self.gv.dm().add(rack);// 在拓撲圖上添加這個rack
// update tree
formData.type = self.cms.TYPE_RACK;
var treeNode = self.cms.createData(formData, cms.treeView.sm().ld());
rack.a('treeNode', treeNode);
}
self.gv.fitContent(1);// 添加元素以後,讓全部的圖元顯示在界面上
addRackDialog.hide();// 隱藏對話框
}
}, {
label: 'Cancel',
action: function(){
addRackDialog.hide();// 隱藏對話框
}
}
],
buttonsAlign: "right"
});
}
複製代碼
上面代碼出現的 FormPane 類,繼承於 ht.widget.FormPane 類,在 htwidget.FormPane 的基礎上修改也增長了一些函數,主要的內容仍是 ht.widget.FormPane 的實現,文章篇幅有限,這裏就不貼代碼了,有興趣的能夠參考 FormPane.js 文件。
實現了添加和編輯機櫃的兩個功能,刪除機櫃的功能實現上很是容易,只要將節點從拓撲圖和樹上移除便可:
handleRemoveRack: function(){// 在拓撲圖上刪除機櫃,並刪除樹上此機櫃對應的節點
var ld = this.gv.sm().ld();// 獲取 gv 上選中的最後一個節點
if (ld && ld instanceof Rack) {// 機櫃是 Rack 類型
this.cms.treeView.dm().remove(ld.a('treeNode'));// 移出樹上的有 treeNode 屬性的節點
this.gv.dm().remove(ld);// 刪除 gv 中的節點
}
}
複製代碼
全部的內容都建立完畢,接下來要考慮的就是交互的內容了。列表組件中有 handleDragAndDrop 函數實現拖拽的功能:
listView.handleDragAndDrop = this.handleListDND.bind(this);// 列表上拖拽事件監聽(http://www.hightopo.com/guide/guide/core/listview/ht-listview-guide.html)
handleListDND: function(e, state){// 拖拽listView列表組件中的事件監聽
var self = this,
listView = self.listView,
gv = self.gv,
dm = gv.dm(),
dnd = self.dnd;
// handleDragAndDrop 函數有 prepare-begin-between-end 四種狀態
if (state ==='prepare') {
var data = listView.getDataAt(e);// 傳入邏輯座標點或者交互event事件參數,返回當前點下的數據元素
listView.sm().ss(data);// 在拖拽的過程當中設置列表組件中的被拖拽的元素被選中
if (dnd && dnd.parentNode) {
document.body.removeChild(dnd);
}
dnd = self.dnd = ht.Default.createDiv();// 建立一個 div
dnd.style.zIndex = 10;
dnd.innerText = data.getName();
}
else if (state === 'begin') {
if (dnd) {
var pagePoint = ht.Default.getPagePoint(e);// 返回頁面座標
dnd.style.left = pagePoint.x - dnd.offsetWidth * 0.5 + 'px';
dnd.style.top = pagePoint.y - dnd.offsetHeight * 0.5 + 'px';
document.body.appendChild(dnd)
}
}
else if (state === 'between') {
if (dnd) {
var pagePoint = ht.Default.getPagePoint(e);
dnd.style.left = pagePoint.x - dnd.offsetWidth * 0.5 + 'px';
dnd.style.top = pagePoint.y - dnd.offsetHeight * 0.5 + 'px';
self.showDragHelper(e);
}
}
else {// 拖拽「放開」鼠標後的操做
if (ht.Default.containedInView(e, self.gv)) {// 判斷交互事件所處位置是否在View組件之上
if (dm.contains(self.dragHelper)) {// 判斷容器是否包含該data對象
var rect = self.dragHelper.getRect(),// 獲取圖元的矩形區域(包括旋轉)
target = self.showDragHelper(e),//
node,
ld = self.listView.sm().ld(),
uindex = target.getCellIndex(rect.y);
node = self.createPane(rect, ld.getAttrObject(), target, uindex);// 建立設備
dm.add(node);
// update tree data
var treeNode = self.cms.createData(ld.getAttrObject(), target.a('treeNode'));// 在樹上建立節點,並設置父親節點
treeNode.a('uindex', uindex);
node.a('treeNode', treeNode);
dm.remove(self.dragHelper);
}
}
document.body.removeChild(dnd);
self.dnd = null;
}
}
複製代碼
既然有了從列表組件上拖拽下來的交互動做,接下來應該是作設備在機櫃上的拖拽改變位置的功能了,咱們經過監聽拓撲組件 gv 的交互事件來對節點移動進行事件處理:
gv.mi(this.handleInteractor.bind(this));// 監聽交互
handleInteractor: function(e){// 移動機櫃中的設備 的事件監聽
if (e.kind.indexOf('Move') < 0) return;// 若是非move事件則直接返回不作處理
var self = this,
listView = self.listView,
gv = self.gv,
dm = gv.dm(),// 獲取數據容器
target = gv.sm().ld(),// 獲取最後選中的節點
uHeight = target.a('uHeight') || 1;// target.a('uHeight')獲取最後選中的節點的高度
if (e.kind === 'prepareMove') {// 準備移動
self._oldPosition = target.p();// 獲取節點當前的位置
}
else if (e.kind === 'betweenMove') {// 正在移動
self.showDragHelper(e.event, uHeight);
dm.sendToTop(target);// 將data在拓撲上置頂,顯示在最頂層 不會被別的節點遮蓋
}
else if (e.kind === 'endMove') {// 結束移動
var rack = self.showDragHelper(e.event, uHeight);
if (dm.contains(self.dragHelper)) {// 判斷容器是否包含該data對象
target.p(self.dragHelper.p());// 設置節點的座標
target.a('uindex', rack.getCellIndex(target.p().y));// 設置節點的業務屬性 uindex
dm.remove(self.dragHelper);// 移除
self._savable = true;
self.toolbar.iv();
target.setHost(rack);// 設置宿主節點
target.setParent(rack);// 設置父親節點
// update tree
var treeNode = target.a('treeNode');// 獲取拓撲圖上對應的樹上的節點
treeNode.setParent(rack.a('treeNode'));
}
else {
target.p(self._oldPosition);
}
}
}
複製代碼
代碼中的 showDragHelper 就是在設備拖動的過程當中,顯示在機櫃上,設備下的做爲佔位的綠色的矩形,爲了方面看到當前移動的位置在機櫃上顯示的位置。有興趣的能夠本身瞭解一下,篇幅有限,這裏就不提了。
會不會有同窗對列表欄頂部的 form 表單作過濾有些好奇?這塊代碼很是簡單,只須要對選中的類型進行過濾便可:
listView.setVisibleFunc(function(data){// 設置可見過濾器
if (!self.listTypeFilter || self.listTypeFilter === -1)
return true;
return data.a('type') === self.listTypeFilter;// 根據節點的自定義屬性 type 來判斷節點屬於哪一個類型 返回與當前 form 表單中選中的名稱相同的全部節點進行顯示
});
複製代碼
主要的代碼就解釋到這裏,其餘部分的內容有興趣的同窗能夠本身去摳代碼瞭解 https://hightopo.com/demo/rack-builder/index.html。還有不懂的能夠上官網瞭解 https://hightopo.com/。
工業互聯網可視化監控除了 2D 機房的監控,固然還有 3D 的,涉及到工業生產以及建設的方方面面,目前 3D 的佔比比較重,比較直觀,視覺衝擊也相對來講比較強,好比動車的總體構造體現:
利用工業互聯網監控的 3D 工業園:
工業互聯網雲平臺可涉及到工業生產的方方面面,應用在工廠以及內部設備工做運行以及所回傳數據的可視化,能夠將任一時間段的數據以軌跡圖、儀表盤等各類方式加以展示,工業互聯網雲平臺能夠普遍應用於智慧城市的各個領域,除了本例中展現的機房 U 位監控,拓撲圖展現,數據的傳輸意外,其餘的工業領域,三維建模的展現以及能源和通信領域中,甚至軌道交通,醫療領域此工業互聯網雲平臺均可應用。