配電室監控可以說是我做的比較傑出的一個功能了,貌似是去年7月份左右做完的,至今也有一年了,要不是今天看到,我都忘了我曾寫過這樣一個功能。好的東西還是應該記錄一下的,今天花點時間簡單寫個備忘吧。
首先介紹對象,以及對象的增刪改,然后介紹監控頁面,最后總結開發經驗。
配電室有1-3個變壓器

一個進線一個出線,進線是一定有的,而出線則可有可無

新增頁面

列表頁面

變壓器可以添加1-3個


每個變壓器有默認的18個檢測項目(新增變壓器的時候,后台默認添加18個檢測項目)

進線、出線也有固定的檢測項目
進線有固定的8個檢測項目

出線有固定的4個檢測項目

列表頁面會列出一個配電室的所有檢測項目

由於(莫斯配電室)添加了3個變壓器,且有(出線),此處檢測項目會有3個變壓器和一個進線一個出線的所有檢測項目


配電室監控的時候,由於用代碼臨時畫線路圖頁面加載效率會比較低,所以采用的是先畫好線路圖,監控的時候動態的選擇線路圖展示的方案,線路圖一共有6張,分一個變壓器有出線和無出線兩張圖、兩個變壓器有出線和無出線兩張圖、三個變壓器有出線和無出線兩張圖

道閘的開關由兩張道閘的圖片切換

監控頁面

值得一說的有已下三點
一、首先要說的是,如何動態匹配底圖圖紙?
配電室對象根據自己是否有出線及有幾個變壓器給出對應的類型,js寫json,配置不同的類型對應不同的圖紙url
public String getRoomJson() { JSONObject data = new JSONObject(); try { data.put("name", "[" + area.getName() + "]" + name); data.put("id", this.getId()); data.put("inlinename", this.getIncomingLine().getName()); int num = voltageTransformerList.size(); if (num == 0) { data.put("msg", "配電室結構異常,沒有變壓器!"); } else if (num > 3) { data.put("msg", "配電室結構異常,有[" + num + "]個變壓器!"); } if (haveOutgoingLine) { data.put("type", num + "out"); } else { data.put("type", num + "v"); } } catch (JSONException e) { e.printStackTrace(); } roomJson = data.toString(); return roomJson; }

二、框里的檢測項目,出現的位置怎么控制,怎么能讓對應的檢測項目出線在底圖對應的位置上?
如下圖,紅色線標注出來的那些,是怎么控制顯示的位置的?

不同類型的圖紙,對應的配電室的檢測項目是固定的,只要針對不同的圖紙,提前設計好哪些檢測項目應該放在哪里展示,然后計算出相對位置,在js里寫好json配置,頁面加載的時候根據頁面的寬高,再結合這個json配置,就可以計算出頁面中各個對象展現的位置
var RoomData = { "1v": [ {A: "inline", B: "U", x: 380.0, y: 360.0, pos: "RB"}, {A: "inline", B: "I", x: 480.0, y: 360.0, pos: "LB"}, {A: "voltage1", B: "capacity", x: 750, y: 580, pos: "RB", nomenu: true}, {A: "voltage1", B: "IHigh", x: 910, y: 360, pos: "LB"}, {A: "voltage1", B: "T", x: 840, y: 580, pos: "LB"}, {A: "voltage1", B: "U", x: 750, y: 630, pos: "RT"}, {A: "voltage1", B: "ILow", x: 840, y: 630, pos: "LT"} ], "1out": [ {A: "inline", B: "U", x: 380.0, y: 360.0, pos: "RB"}, {A: "inline", B: "I", x: 480.0, y: 360.0, pos: "LB"}, {A: "voltage1", B: "IHigh", x: 820, y: 360, pos: "LB"}, {A: "voltage1", B: "T", x: 760, y: 580, pos: "LB"}, {A: "voltage1", B: "U", x: 650, y: 630, pos: "RT"}, {A: "voltage1", B: "ILow", x: 760, y: 630, pos: "LT"}, {A: "voltage1", B: "capacity", x: 650, y: 580, pos: "RB", nomenu: true}, {A: "outline", B: "I", x: 1130.0, y: 360.0, pos: "LB"} ], "2v": [ {A: "inline", B: "U", x: 200.0, y: 360.0, pos: "RB"}, {A: "inline", B: "I", x: 300.0, y: 360.0, pos: "LB"}, {A: "voltage1", B: "IHigh", x: 650, y: 360, pos: "LB"}, {A: "voltage1", B: "ILow", x: 490, y: 630, pos: "RT", rely: {B: "U", move: "left"}}, {A: "voltage1", B: "capacity", x: 490, y: 580, pos: "RB", rely: {B: "T", move: "leftB"}, nomenu: true}, {A: "voltage2", B: "capacity", x: 980, y: 580, pos: "LB", rely: {B: "T", move: "rightB"}, nomenu: true}, {A: "voltage2", B: "IHigh", x: 1050, y: 360, pos: "LB"}, {A: "voltage2", B: "U", x: 980, y: 630, pos: "LT", rely: {B: "ILow", move: "right"}} ], "2out": [ {A: "inline", B: "U", x: 200.0, y: 360.0, pos: "RB"}, {A: "inline", B: "I", x: 300.0, y: 360.0, pos: "LB"}, {A: "voltage1", B: "IHigh", x: 610, y: 360, pos: "LB"}, {A: "voltage1", B: "ILow", x: 440, y: 630, pos: "RT", rely: {B: "U", move: "left"}}, {A: "voltage2", B: "IHigh", x: 930, y: 360, pos: "LB"}, {A: "voltage2", B: "U", x: 870, y: 630, pos: "LT", rely: {B: "ILow", move: "right"}}, {A: "voltage1", B: "capacity", x: 440, y: 580, pos: "RB", rely: {B: "T", move: "leftB"}, nomenu: true}, {A: "voltage2", B: "capacity", x: 870, y: 580, pos: "LB", rely: {B: "T", move: "rightB"}, nomenu: true}, {A: "outline", B: "I", x: 1190.0, y: 360.0, pos: "LB"} ], "3v": [ {A: "inline", B: "U", x: 200.0, y: 360.0, pos: "RB"}, {A: "inline", B: "I", x: 300.0, y: 360.0, pos: "LB"}, {A: "voltage1", B: "IHigh", x: 560, y: 360, pos: "LB"}, {A: "voltage1", B: "ILow", x: 400, y: 630, pos: "RT", rely: {B: "U", move: "left"}}, {A: "voltage2", B: "IHigh", x: 880, y: 360, pos: "LB"}, {A: "voltage2", B: "T", x: 810, y: 580, pos: "LB"}, {A: "voltage2", B: "U", x: 720, y: 630, pos: "RT"}, {A: "voltage2", B: "ILow", x: 810, y: 630, pos: "LT"}, {A: "voltage1", B: "capacity", x: 400, y: 580, pos: "RB", rely: {B: "T", move: "leftB"}, nomenu: true}, {A: "voltage2", B: "capacity", x: 720, y: 580, pos: "RB", nomenu: true}, {A: "voltage3", B: "capacity", x: 1130, y: 580, pos: "LB", rely: {B: "T", move: "rightB"}, nomenu: true}, {A: "voltage3", B: "IHigh", x: 1200, y: 360, pos: "LB"}, {A: "voltage3", B: "U", x: 1130, y: 630, pos: "LT", rely: {B: "ILow", move: "right"}} ], "3out": [ {A: "inline", B: "U", x: 200.0, y: 360.0, pos: "RB"}, {A: "inline", B: "I", x: 300.0, y: 360.0, pos: "LB"}, {A: "voltage1", B: "IHigh", x: 530, y: 360, pos: "LB"}, {A: "voltage1", B: "ILow", x: 380, y: 630, pos: "RT", rely: {B: "U", move: "left"}}, {A: "voltage2", B: "IHigh", x: 820, y: 360, pos: "LB"}, {A: "voltage2", B: "T", x: 750, y: 580, pos: "LB"}, {A: "voltage2", B: "U", x: 670, y: 630, pos: "RT"}, {A: "voltage2", B: "ILow", x: 750, y: 630, pos: "LT"}, {A: "voltage3", B: "IHigh", x: 1100, y: 360, pos: "LB"}, {A: "voltage3", B: "U", x: 1030, y: 630, pos: "LT", rely: {B: "ILow", move: "right"}},/*move right 表示依賴對象移動到右邊*/ {A: "voltage1", B: "capacity", x: 380, y: 580, pos: "RB", rely: {B: "T", move: "leftB"}, nomenu: true}, {A: "voltage2", B: "capacity", x: 670, y: 580, pos: "RB", nomenu: true}, {A: "voltage3", B: "capacity", x: 1030, y: 580, pos: "LB", rely: {B: "T", move: "rightB"}, nomenu: true}, {A: "outline", B: "I", x: 1290.0, y: 360.0, pos: "LB"} ], SW: 1418.0, SH: 1000.0 };
SW、SH是原圖尺寸,x、y是設計的時候,檢測項目集合放置的位置,這些數據就可以計算出在圖中位置的比例,根據這個比例乘以頁面后底圖實際的寬高,就可以得出顯示的位置。這個pos是說明這個x、y這一點的位置,是這個集合框的四個頂點的哪個頂點的位置,T表示上(top),R表示右(right),B表示底部(bottom),L表示左(left),兩兩結合,便可以表示一個頂點,比如RB表示右下角的頂點。
為什么要區分頂點來計算放置集合框?
舉例說吧,比如進線的(電壓數據框)和(電流數據框),如下圖,就拿電壓數據框說,電壓框如果記錄左上角的位置,如果ua的值變成120.35KV,這個寬度肯定比0KV的寬度寬吧,那么顯示后會怎么樣呢?很明顯,左上角位置不動,寬度增加,就會把進線的圖給遮蓋了,如下面的第二張圖,這個肯定不是想要的結果。

我們肯定希望,無論頁面的寬高怎么變,文字的內容怎么變,我都要顯示在進線的左方,母線的上方,那么唯一希望它不要變的一個點的位置,就是右下角的位置。
rely是依賴關系,表示一個數據框的位置,要依賴另一個數據框的位置,move表示移動的方向,比如變壓器的電壓和電流兩個數據框,如下圖,電壓框的位置是不固定的,他要依賴電流框左上角的位置,來計算自己的位置,也就是說,要先畫出電流框,然后根據move的值來判斷是顯示在電流框的左邊、右邊、上邊還是下邊。詳細的不說了,太多東西要寫了。

進線名稱的位置、道閘的位置也是同一個道理
/** * 進線名稱位置 */ var InLineTitleData = { "1v": {x: 423.0, y: 160.0}, "1out": {x: 422.0, y: 160.0}, "2v": {x: 241.0, y: 160.0}, "2out": {x: 243.0, y: 160.0}, "3v": {x: 246.0, y: 160.0}, "3out": {x: 243.0, y: 160.0} };
/** * 道閘位置 * VC: Vertical centering 垂直居中對齊 */ var KnifeData = { "1v": { "inline": {x: 423, y: 280, pos: "VC"}, "voltage1": {x: 790, y: 800, pos: "VC"}, "v1-high": {x: 847, y: 280, pos: "VC"} }, "1out": { "inline": {x: 422, y: 280, pos: "VC"}, "voltage1": {x: 710, y: 800, pos: "VC"}, "v1-high": {x: 768, y: 280, pos: "VC"}, "outline": {x: 1068, y: 280, pos: "VC"} }, "2v": { "inline": {x: 241, y: 280, pos: "VC"}, "voltage1": {x: 540, y: 800, pos: "VC"}, "v1-high": {x: 597, y: 280, pos: "VC"}, "voltage2": {x: 937, y: 800, pos: "VC"}, "v2-high": {x: 994, y: 280, pos: "VC"} }, "2out": { "inline": {x: 243, y: 280, pos: "VC"}, "voltage1": {x: 497, y: 800, pos: "VC"}, "v1-high": {x: 555, y: 280, pos: "VC"}, "voltage2": {x: 814, y: 800, pos: "VC"}, "v2-high": {x: 872, y: 280, pos: "VC"}, "outline": {x: 1131, y: 280, pos: "VC"} }, "3v": { "inline": {x: 246, y: 280, pos: "VC"}, "voltage1": {x: 445, y: 800, pos: "VC"}, "v1-high": {x: 504, y: 280, pos: "VC"}, "voltage2": {x: 765, y: 800, pos: "VC"}, "v2-high": {x: 822, y: 280, pos: "VC"}, "voltage3": {x: 1081, y: 800, pos: "VC"}, "v3-high": {x: 1138, y: 280, pos: "VC"} }, "3out": { "inline": {x: 243, y: 280, pos: "VC"}, "voltage1": {x: 421, y: 800, pos: "VC"}, "v1-high": {x: 479, y: 280, pos: "VC"}, "voltage2": {x: 706, y: 800, pos: "VC"}, "v2-high": {x: 762, y: 280, pos: "VC"}, "voltage3": {x: 987, y: 800, pos: "VC"}, "v3-high": {x: 1045, y: 280, pos: "VC"}, "outline": {x: 1234, y: 280, pos: "VC"} } };
計算位置的代碼
/** * 封裝有位置關聯關系的兩個bean */ function MsgCompoundBean(r, data) { var MBAR = this; var H = r.height, W = r.width; var SH = RoomData.SH, SW = RoomData.SW; var x = data.x * W / SW, y = data.y * H / SH; var levelA = data.A, levelB = data.B; var msg = r.room.msg[levelA][levelB]; var nomenu = data.nomenu || false; var bean = new MsgBean(r, {x: x, y: y, pos: data.pos, msg: msg, nomenu: nomenu}); var relyBean = null; if (data.rely) { var move = data.rely.move; var rx = 0, ry = 0; var rPos = ""; if (move == "right") { rx = bean.x + bean.wi + 10; ry = bean.y; rPos = "LT"; } else if (move == "left") { rx = bean.x - 10; ry = bean.y; rPos = "RT"; } else if (move == "rightB") { rx = bean.x + bean.wi + 10; ry = bean.y + bean.he; rPos = "LB"; } else if (move == "leftB") { rx = bean.x - 10; ry = bean.y + bean.he; rPos = "RB"; } var rLeveB = data.rely.B; var rMsg = r.room.msg[levelA][rLeveB]; var nomenu = data.rely.nomenu || false; relyBean = new MsgBean(r, {x: rx, y: ry, pos: rPos, msg: rMsg, nomenu: nomenu}); } MBAR.remove = function(){ bean.remove(); if (relyBean != null) { relyBean.remove(); } }; }
if (data.rely) 就是判斷是否有依賴的數據框要畫,下面就是畫具體的數據框的封裝
/** * 封裝單個數據塊 */ function MsgBean(r, data) { var DRB = this; var msgset = new MySet(r); var msg = data.msg, posX = data.x, posY = data.y, pos = data.pos; var gap = 25,/*每條信息的高度*/ marginLR = 10; var wi = tool_GetMsgWi(r, msg, marginLR, TextStyle), he = msg.length * gap; var x = 0, y = 0; if (pos == "RB") { x = posX - wi; y = posY - he; } else if (pos == "LB") { x = posX; y = posY - he; } else if (pos == "RT") { x = posX - wi; y = posY; } else { /*LT*/ x = posX; y = posY; } var myBody = r.rect(x, y, wi, he, 5).attr({fill: "#2B2727", stroke: "gray", "stroke-width": 1}); var texty = y; for (var i in msg) { var textI = new MyText(r, x, texty, wi, gap, msg[i], marginLR, TextStyle, data.nomenu); msgset.push(textI); texty = texty + gap; } DRB.wi = wi; DRB.he = he; DRB.x = x; DRB.y = y; DRB.remove = function(){ msgset.remove(); myBody.remove(); }; }
整個配電室封裝的js
/** * 封裝整個配電室對象 */ function MyRoom(r, data) { var MR = this; var id = data.id; var type = data.type; var roomname = data.name; var H = r.height, W = r.width; var baseimg = BaseMapImg[type]; var map = r.image(baseimg, 0, 0, W, H); var title = r.text(W / 2, 30, roomname).attr(TitleTextStyle); //進線名稱 var inlinedata = InLineTitleData[type]; var inlinename = data.inlinename || "none"; var inlinetitle = r.text(0, 0, inlinename).attr(TextStyle); var data = RoomData[type]; var beanset = new MySet(r); var flashData = function() { //進線名稱 inlinetitle.remove(); var SH = RoomData.SH, SW = RoomData.SW; var inx = inlinedata.x * r.width / SW, iny = inlinedata.y * r.height / SH; inlinetitle = r.text(inx, iny, inlinename).attr(TextStyle).attr({cursor: "default"}); //監測項目 beanset.clear(); for (var i in data) { var bean = new MsgCompoundBean(r, data[i]); beanset.push(bean); } //刀閘 var knife = new MyKnifeGate(r); beanset.push(knife); }; var selectMe = function(){ r.map = map; r.title = title; r.type = type; r.roomid = id; map.show(); title.show(); flashData(); }; MR.remove = function(){ beanset.clear(); inlinetitle.remove(); map.hide(); title.hide(); }; MR.flashData = flashData; MR.selectMe = selectMe; }
var flashData = function() 是監控的時候,檢測項目值改變后,重新渲染頁面(僅僅是局部渲染,僅渲染檢測項目,這樣效率比較高)
三、右鍵菜單功能及右邊欄隱藏的功能

這個右鍵就是在檢測項目上加一個半透明的遮罩層,鼠標移動上去后,控制遮罩層的顯示和隱藏,有個動態的效果,再有就是計算下右鍵菜單顯示的位置,一般是顯示在右下角,除非是超出邊界,需要計算下位置。查看曲線查看報表也就是攜帶檢測項目id,去查找檢測項目的歷史數據,然后出圖出報表。
右邊欄隱藏和展示,就是重新渲染一下檢測項目的位置


細節太多,代碼就不再貼了,中午了,好餓,吃飯去。
