├── LICENSE ├── demo ├── demo.html ├── img │ ├── device.png │ ├── icon.png │ ├── setting.png │ └── title-bg.png ├── js │ ├── Guid.js │ ├── Smt_topology.js │ ├── createList.js │ ├── go-debug.js │ ├── go.js │ └── index.js └── scss │ ├── .sass-cache │ ├── d7f3ed3a3437288f346dda7527e5414673bf7ee9 │ │ └── index.scssc │ └── f8ca3efc0b08d7728b2da59b5eea8823b40971d9 │ │ └── index.scssc │ ├── css │ ├── index.css │ ├── index.css.map │ └── reset.css │ └── index.scss └── readme.md /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 taiaiwu 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /demo/demo.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Document 6 | 7 | 8 | 9 | 10 |
11 |
12 |

设备库

13 |
14 | 15 |
16 |
17 |
18 |

参数设置

19 |
20 |
21 | 当前没有选择设备 22 |
23 |
24 |
25 | 26 | 27 |
28 |
29 | 30 |
31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /demo/img/device.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maskTAQ/topology/2558022db9bcf5bf0039cc9617885a9f98aa6d8b/demo/img/device.png -------------------------------------------------------------------------------- /demo/img/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maskTAQ/topology/2558022db9bcf5bf0039cc9617885a9f98aa6d8b/demo/img/icon.png -------------------------------------------------------------------------------- /demo/img/setting.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maskTAQ/topology/2558022db9bcf5bf0039cc9617885a9f98aa6d8b/demo/img/setting.png -------------------------------------------------------------------------------- /demo/img/title-bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maskTAQ/topology/2558022db9bcf5bf0039cc9617885a9f98aa6d8b/demo/img/title-bg.png -------------------------------------------------------------------------------- /demo/js/Guid.js: -------------------------------------------------------------------------------- 1 | /* 2 | *1、 生成一个新GUID:var guid = Guid.NewGuid(); 3 | 4 | 2、 生成一个所有值均为0的GUID: 5 | 6 | a) var guid = new Guid(); 7 | 8 | b) var guid = Guid.Empty; 9 | 10 | 3、 比较两个GUID是否相等:g1.Equals(g2); 11 | 12 | 4、 获取Guid的字符串形式。其中, format为String类型的可选参数,其含义为: 13 | 14 | a) “N”: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 15 | 16 | b) “D” 由连字符分隔的 32 位数字 xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx 17 | 18 | c) “B” 括在大括号中、由连字符分隔的 32 位数字:{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx} 19 | 20 | d) “P” 括在圆括号中、由连字符分隔的 32 位数字:(xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx) 21 | */ 22 | function Guid(g) { 23 | 24 | var arr = new Array(); //存放32位数值的数组 25 | 26 | if (typeof(g) == "string") { //如果构造函数的参数为字符串 27 | InitByString(arr, g); 28 | 29 | } else { 30 | 31 | InitByOther(arr); 32 | 33 | } 34 | 35 | //返回一个值,该值指示 Guid 的两个实例是否表示同一个值。 36 | this.Equals = function(o) { 37 | 38 | if (o && o.IsGuid) { 39 | 40 | return this.ToString() == o.ToString(); 41 | 42 | } else { 43 | 44 | return false; 45 | 46 | } 47 | 48 | } 49 | 50 | //Guid对象的标记 51 | this.IsGuid = function() {} 52 | 53 | //返回 Guid 类的此实例值的 String 表示形式。 54 | this.ToString = function(format) { 55 | 56 | if (typeof(format) == "string") { 57 | 58 | if (format == "N" || format == "D" || format == "B" || format == "P") { 59 | 60 | return ToStringWithFormat(arr, format); 61 | 62 | } else { 63 | 64 | return ToStringWithFormat(arr, "D"); 65 | 66 | } 67 | 68 | } else { 69 | 70 | return ToStringWithFormat(arr, "D"); 71 | 72 | } 73 | 74 | } 75 | 76 | //由字符串加载 77 | function InitByString(arr, g) { 78 | 79 | g = g.replace(/\{|\(|\)|\}|-/g, ""); 80 | 81 | g = g.toLowerCase(); 82 | 83 | if (g.length != 32 || g.search(/[^0-9,a-f]/i) != -1) { 84 | 85 | InitByOther(arr); 86 | 87 | } else { 88 | 89 | for (var i = 0; i < g.length; i++) { 90 | 91 | arr.push(g[i]); 92 | 93 | } 94 | 95 | } 96 | 97 | } 98 | 99 | //由其他类型加载 100 | function InitByOther(arr) { 101 | 102 | var i = 32; 103 | 104 | while (i--) { 105 | 106 | arr.push("0"); 107 | 108 | } 109 | 110 | } 111 | 112 | /* 113 | 114 | 根据所提供的格式说明符,返回此 Guid 实例值的 String 表示形式。 115 | 116 | N 32 位: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 117 | 118 | D 由连字符分隔的 32 位数字 xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx 119 | 120 | B 括在大括号中、由连字符分隔的 32 位数字:{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx} 121 | 122 | P 括在圆括号中、由连字符分隔的 32 位数字:(xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx) 123 | 124 | */ 125 | 126 | function ToStringWithFormat(arr, format) { 127 | 128 | switch (format) { 129 | 130 | case "N": 131 | 132 | return arr.toString().replace(/,/g, ""); 133 | 134 | case "D": 135 | 136 | var str = arr.slice(0, 8) + "-" + arr.slice(8, 12) + "-" + arr.slice(12, 16) + "-" + arr.slice(16, 20) + "-" + arr.slice(20, 32); 137 | 138 | str = str.replace(/,/g, ""); 139 | 140 | return str; 141 | 142 | case "B": 143 | 144 | var str = ToStringWithFormat(arr, "D"); 145 | 146 | str = "{" + str + "}"; 147 | 148 | return str; 149 | 150 | case "P": 151 | 152 | var str = ToStringWithFormat(arr, "D"); 153 | 154 | str = "(" + str + ")"; 155 | 156 | return str; 157 | 158 | default: 159 | 160 | return new Guid(); 161 | 162 | } 163 | 164 | } 165 | 166 | } 167 | 168 | //Guid 类的默认实例,其值保证均为零。 169 | Guid.Empty = new Guid(); 170 | 171 | //初始化 Guid 类的一个新实例。 172 | Guid.NewGuid = function() { 173 | 174 | var g = ""; 175 | 176 | var i = 32; 177 | 178 | while (i--) { 179 | 180 | g += Math.floor(Math.random() * 16.0).toString(16); 181 | 182 | } 183 | 184 | return new Guid(g); 185 | 186 | } -------------------------------------------------------------------------------- /demo/js/Smt_topology.js: -------------------------------------------------------------------------------- 1 | /* 2 | *includes go-debug.js or go.js,Guid.js 3 | ================使用说明================ 4 | ## init 5 | ==============初始化拓扑图================ 6 | Smt_topology.init({ 7 | domId: 'domId', 8 | //dom id 9 | // 设备数据 10 | nodeDataArray: [{ 11 | //设备key 12 | key: '1', 13 | //设备名称 14 | name: 'co2', 15 | //设备可接入其他设备的名称集 必须是数组 可不填此选项 16 | to: ['co2', 'co3'] 17 | }, 18 | { 19 | key: '2', 20 | name: 'co3', 21 | to: ['co4', 'co3'] 22 | }], 23 | linkDataArray: [] //线条数据 可填空数组 24 | }, 25 | { 26 | domId: 'id',//控制面板id 27 | model: [{ 28 | name: 'router', 29 | to: [] 30 | }, 31 | { 32 | name: 'gateway', 33 | to: ['router'] 34 | }, 35 | { 36 | type: 'SRC001', 37 | name: 'SRC001', 38 | to: ['网关'] 39 | }, 40 | { 41 | type: 'SRC002', 42 | name: 'SRC002', 43 | to: ['网关'] 44 | }, 45 | { 46 | name: '传感器', 47 | to: ['SRC002'] 48 | }] 49 | }); 50 | 51 | *****第二参数可不填 填写即开启配置控制面板辅助编辑拓扑图***** 52 | 53 | ## API 54 | ==============添加设备============== 55 | Smt_topology.createDevice({ 56 | name: 'deviceName',//设备名 57 | to: ['co2']//设备可接入其他设备的名称集 必须是数组 可不填此选项 58 | }); 59 | ==============保存数据============== 60 | Smt_topology.save();//返回包含设备信息和线条信息的对象 61 | 62 | 63 | ================2017-3-8 15.43================ 64 | */ 65 | ; 66 | (function() { 67 | //定义视图 在initView中初始化 68 | var diagram, model, $; 69 | 70 | //在全局初始化 以变在M.monitorPortChange 中使用 71 | var portChangeCallback; 72 | 73 | var getDeviceDetailCallback; 74 | 75 | var initView = function(domId, panelInfo) { 76 | $ = go.GraphObject.make; 77 | 78 | diagram = $(go.Diagram, domId, { //内容居顶部显示 79 | initialContentAlignment: go.Spot.Top, 80 | "undoManager.isEnabled": true, 81 | allowDrop: Boolean(panelInfo) // must be true to accept drops from the Palette 判断是否传了面板信息开启面板功能 82 | }); 83 | 84 | //端口名字 是否是接出端口 多端口时的端口数量(单一单口不填) 85 | function makePort(name, leftside, portSize) { 86 | //设置端口的目的是如果不给节点设置端口 那么整个节点都是端口 拖动节点的时候默认值画线的 用其他比如字体代替节点也不够形象 87 | var port = $(go.Shape, "RoundedRectangle", { 88 | stroke: null, 89 | //端口的大小 90 | desiredSize: new go.Size(85, 9), 91 | //端口id 用以识别线段连到什么端口了(必须添加此属性 不然没法连到端口上) 92 | portId: name, 93 | //一个设备最多可以接到一个设备上(一个设备出口最多只有一个) 94 | fromMaxLinks: 1, 95 | //鼠标移到端口上的手势 96 | cursor: "pointer" 97 | }); 98 | 99 | 100 | //定义端口承载面板 101 | var panel = $(go.Panel, "Vertical"); 102 | 103 | //为true的端口是出只能接到别的设备上 反之端口只允许别的设备接入 104 | if (leftside) { 105 | port.fromLinkable = true; 106 | port.fill = '#40a0ff'; //修改输出端口颜色 107 | port.margin = new go.Margin(0, 0, -5, 0); 108 | panel.add(port); 109 | } else { 110 | port.toLinkable = true; 111 | port.fill = "#82a8c0"; 112 | port.margin = new go.Margin(-5, 0, 0, 0); 113 | panel.add(port); 114 | } 115 | 116 | //如果是RTU这种接入是多个端口的 117 | if (portSize) { 118 | port.toMaxLinks = 1; //设置输出端口数量 就是RTU端口可以接入的数量 119 | 120 | if (portSize == 20) { 121 | port.desiredSize = new go.Size(4, 4); 122 | port.margin = new go.Margin(0, 0.5, 5, 0); 123 | 124 | } else if (portSize == 8) { 125 | port.desiredSize = new go.Size(10, 4, 5, 0); 126 | port.margin = new go.Margin(0, 1.4); 127 | } 128 | 129 | } 130 | 131 | 132 | 133 | return panel; 134 | } 135 | 136 | function makeMultiPort(size) { 137 | var portArr = []; 138 | for (var i = 0; i < size; i++) { 139 | portArr.push(makePort('DO' + (i + 1), false, size)); 140 | } 141 | return portArr 142 | } 143 | 144 | //定义模板样式 145 | function createDeviceTemplate(outputPortArr, inputPortArr, bodyWidth) { 146 | //不同的设备只是 端口不一样 其他都是共用的 //一个节点由 节点类型 样式 子节点构成 147 | return $(go.Node 148 | //节点以表格形式展出 可分row column 149 | , "Table" 150 | //设备的位置信息 151 | , new go.Binding("location", "loc", go.Point.parse).makeTwoWay(go.Point.stringify) 152 | //节点的出端口 153 | , $(go.Panel, "Horizontal", { 154 | row: 0, 155 | column: 0, 156 | //端口组件的位置 157 | alignment: go.Spot.Center, 158 | alignmentFocus: new go.Spot(0, 0.5, -8, 0) 159 | }, outputPortArr) 160 | //设备的主题 内容 包含一个字和图标 161 | , $(go.Panel, "Table", { 162 | row: 1, 163 | background: "#fff", 164 | width: bodyWidth, 165 | height: 60, 166 | }, 167 | $(go.Picture, { 168 | width: bodyWidth, 169 | height: 60, 170 | background: "#fafafa" 171 | }, 172 | new go.Binding("source")), $(go.TextBlock, "device name", { 173 | margin: 50, 174 | stroke: "#4b4b4b", 175 | font: "18px 微软雅黑" 176 | }, 177 | new go.Binding("text", "name")), 178 | //给主题一个形状 shape只能给panel part?? 179 | $(go.Shape, "RoundedRectangle", { 180 | stroke: "#ccc", 181 | strokeWidth: 2, 182 | strokeJoin: "miter", 183 | strokeCap: "round", 184 | width: bodyWidth, 185 | height: 60, 186 | fill: 'transparent' 187 | })) 188 | //设备的入端口 189 | , $(go.Panel, "Horizontal", { 190 | row: 2, 191 | column: 0, 192 | //端口组件的位置 193 | alignment: go.Spot.Center, 194 | alignmentFocus: new go.Spot(1, 0.5, 8, 0) 195 | }, inputPortArr), { 196 | contextMenu: // define a context menu for each node 197 | $(go.Adornment, "Vertical", // that has one button 198 | $("ContextMenuButton", { 199 | width: 90, 200 | cursor: 'pointer', 201 | }, 202 | $(go.TextBlock, "查看设备信息", { 203 | margin: new go.Margin(4, 0, 0, 0), 204 | stroke: '#000', 205 | }), { 206 | click: function(e, o) { 207 | M.getDeviceInfo(o.part.data); 208 | }, 209 | mouseEnter: function(a, b) { // change group's background brush 210 | var e = b.oe("ButtonBorder"); 211 | e.stroke = '#40a0ff'; 212 | }, 213 | mouseLeave: function(a, b, next) { // restore to original brush 214 | var e = b.oe("ButtonBorder"); 215 | e.stroke = '#000'; 216 | } 217 | })), 218 | selectionAdornmentTemplate: $(go.Adornment, "Auto", 219 | $(go.Shape, "RoundedRectangle", { 220 | fill: null, 221 | stroke: "dodgerblue", 222 | strokeWidth: 1 223 | }), 224 | $(go.Placeholder) 225 | ) // end Adornment 226 | }); 227 | 228 | } 229 | //默认设备 230 | var device_default = createDeviceTemplate([makePort("output", true)], [makePort("input", false)], 100); 231 | //RTU设备 232 | var device_SRC001 = createDeviceTemplate([makePort("output", true)], makeMultiPort(20), 118); 233 | var device_SRC002 = createDeviceTemplate([makePort("output", true)], makeMultiPort(8), 116); 234 | //添加默认节点模板 235 | diagram.nodeTemplateMap.add('', device_default); //此方法用以添加不同的模板 236 | //添加SRC001 设备模板 237 | diagram.nodeTemplateMap.add('SRC001', device_SRC001); 238 | //添加SRC001 设备模板 239 | diagram.nodeTemplateMap.add('SRC002', device_SRC002); 240 | //diagram.nodeTemplate = device_node; 241 | //添加链接默认模板 //当link匹配不到模板就会匹配名称为空的模板 242 | diagram.linkTemplateMap.add('', $(go.Link, { 243 | //可重置线条起始点 244 | relinkableFrom: true, 245 | relinkableTo: true, 246 | //线条风格 路由 避开节点 247 | routing: go.Link.AvoidsNodes, 248 | corner: 2, 249 | resegmentable: true, 250 | }, 251 | $(go.Shape, { 252 | stroke: '#2b2b2b', 253 | strokeWidth: 1 254 | }), 255 | $(go.TextBlock, "label", { 256 | stroke: "#000", 257 | font: "18px" 258 | }, 259 | new go.Binding("text", "label")) 260 | 261 | )); 262 | 263 | 264 | //定义模型 可绘制线条的模型 265 | model = $(go.GraphLinksModel); 266 | 267 | //定义线段的端口属性值 设置了之后可以重新绘制线条 268 | model.linkFromPortIdProperty = "fromPort"; 269 | model.linkToPortIdProperty = "toPort"; 270 | //将 线的样式跟 toDevice属性绑定在一起这样就可以根据链接到不同的设备上 显示不同的线 toDevice属性是通过监听model->线条改变添加上去的 ??好像不行只能通过自身的属性绑定设置其他属性没效果 所以还是设置为to 271 | model.linkCategoryProperty = 'to'; 272 | 273 | //将设备的类型与type绑定在一起 274 | model.nodeCategoryProperty = 'type'; 275 | 276 | model.linkKeyProperty = '__gohashid'; 277 | //赋予模型节点数据 此步骤交于init初始化 278 | 279 | // model.nodeDataArray = [{key:'1',name:'co2',to:['ccc','bbb']},{key:'2',name:'co2'},{key:'3',name:'co2'},{key:'4',name:'co2'}]; 280 | // model.linkDataArray = [] 281 | // //绑定模型到图表 282 | // diagram.model = model; 283 | //添加节点 model.addNodeData([{category:'co2',key:'6'}]) 284 | //model.findNodeDataForKey(4) 285 | 286 | //监听画线条 287 | diagram.addModelChangedListener(function(evt) { 288 | //当改变类型为 绘制线条或者重置线条的之后验证线条是否符合条件 289 | if (evt.Ov === "linkDataArray") { //绘制新的线条 290 | M.initLine(evt.newValue); 291 | } else if (evt.Ov === "linkToKey" || evt.Ov === "linkToPortId") { //重置线条 292 | M.initLine(evt.object); 293 | } 294 | 295 | //当添加设备时 给予key 296 | if (evt.Ov === "nodeDataArray") { 297 | //设备的key采用guid生成 298 | //evt.newValue.key = Guid.NewGuid().ToString();??手动改key会影响内部机制 299 | model.setKeyForNodeData(evt.newValue, Guid.NewGuid().ToString()); 300 | } 301 | }); 302 | 303 | 304 | 305 | //添加面板控制 306 | if (Boolean(panelInfo)) { 307 | var s = (function() { 308 | return Date.now() 309 | })(); 310 | var palette = $(go.Palette, panelInfo.domId, // must name or refer to the DIV HTML element 311 | { 312 | "animationManager.duration": 800, // slightly longer than default (600ms) animation 313 | nodeTemplateMap: diagram.nodeTemplateMap, // share the templates used by myDiagram 314 | model: new go.GraphLinksModel(panelInfo.model) // specify the contents of the Palette 315 | }); 316 | 317 | //将控制面板的model类别(内部用作样式区分)跟mode的类别设置成一样的值 318 | palette.model.nodeCategoryProperty = model.nodeCategoryProperty; 319 | } 320 | 321 | 322 | 323 | //设置连接中 的俩个端口的样式 324 | diagram.toolManager.relinkingTool.temporaryLink = diagram.toolManager.linkingTool.temporaryLink = 325 | $(go.Link, { 326 | layerName: "Tool" 327 | }, 328 | $(go.Shape, { 329 | stroke: "red", 330 | strokeWidth: 2, 331 | strokeDashArray: [4, 2] 332 | }) 333 | ); 334 | 335 | var tempfromnode = 336 | $(go.Node, { 337 | layerName: "Tool" 338 | }, 339 | $(go.Shape, "Rectangle", { 340 | stroke: "#e50ae5", 341 | strokeWidth: 1, 342 | fill: null, 343 | portId: "", 344 | width: 1, 345 | height: 1 346 | }) 347 | ); 348 | // diagram.toolManager.relinkingTool.temporaryFromNode = diagram.toolManager.linkingTool.temporaryFromNode = tempfromnode; 349 | // diagram.toolManager.relinkingTool.temporaryFromPort = diagram.toolManager.linkingTool.temporaryFromPort = tempfromnode.port; 350 | 351 | var temptonode = 352 | $(go.Node, { 353 | layerName: "Tool" 354 | }, 355 | $(go.Shape, "Rectangle", { 356 | stroke: "#e50ae5", 357 | strokeWidth: 1, 358 | fill: null, 359 | portId: "", 360 | width: 1, 361 | height: 4 362 | })); 363 | // diagram.toolManager.relinkingTool.temporaryToNode = diagram.toolManager.linkingTool.temporaryToNode = temptonode; 364 | //diagram.toolManager.relinkingTool.temporaryToPort = diagram.toolManager.linkingTool.temporaryToPort = temptonode.port; 365 | 366 | 367 | //监听portTarget 可以在这里 验证端口 return true就是可以在俩个端口之前绘制 参数详情请见api [重新链接已有的链接] 368 | diagram.toolManager.relinkingTool.portTargeted = function() { 369 | //如果此对象存在 监听端口的变化 370 | if (diagram.toolManager.relinkingTool.targetPort) { 371 | M.monitorPortChange(diagram.toolManager.relinkingTool.targetPort.portId); 372 | } 373 | 374 | //return true 允许再俩个任意俩个端口之间绘制 375 | return true 376 | }; 377 | //监听portTarget 可以在这里 验证端口 return true就是可以在俩个端口之前绘制 参数详情请见api [绘制仙新的链接] 378 | diagram.toolManager.linkingTool.portTargeted = function(fromNode, fromGraphObject, toNode, toGraphObject, boolean) { 379 | 380 | //如果此对象存在 监听端口的变化 381 | if (diagram.toolManager.linkingTool.targetPort) { 382 | M.monitorPortChange(diagram.toolManager.linkingTool.targetPort.portId); 383 | } 384 | 385 | //return true 允许再俩个任意俩个端口之间绘制 386 | return true 387 | }; 388 | 389 | console.log(model) 390 | } 391 | 392 | //定义组件内用到的方法 393 | var M = { 394 | initLine: function(lineObject) { 395 | //当删除线条的时候传进来的是null 396 | if (!lineObject) { 397 | return 398 | } 399 | M.addLabel(lineObject, M.verifyLine); 400 | 401 | }, 402 | //先添加标签 在 验证线条是否合法 403 | addLabel: function(lineObject, next) { 404 | 405 | //添加线条的label 属性 label属性跟label绑定在一起 406 | model.setDataProperty(lineObject, 'label', lineObject.toPort); 407 | 408 | //交给验证 409 | next && next(lineObject); 410 | }, 411 | verifyLine: function(lineObject) { 412 | //出口设备 413 | var device_key = lineObject.from, 414 | //入口设备 415 | device_parent_key = lineObject.to, 416 | //设备对象 417 | output_device = model.findNodeDataForKey(device_key), 418 | //父级设备对象(就是设备连接的对象) 419 | input_device = model.findNodeDataForKey(device_parent_key); 420 | 421 | (function() { 422 | //当to属性 不是数组 默认不可接入其他设备 423 | if (M.getType(output_device.to) != "[object Array]") { 424 | return model.removeLinkData(lineObject); 425 | } 426 | //初始化验证结果 427 | verify_result = false; 428 | for (var i = 0, l = output_device.to.length; i < l; i++) { 429 | var astrict = output_device.to[i]; 430 | //将设备允许链接的设备限制取出 [设备名,设备端口(可能存在)] 431 | astrict = astrict.split('.'); 432 | //如果设备的接出口包含 接入的设备的设备名 433 | if (astrict.shift().indexOf(input_device.name) > -1) { 434 | //如果有端口的限制条件 435 | if (astrict.length) { 436 | if (astrict.shift().indexOf(lineObject.toPort) > -1) { 437 | return verify_result = true; 438 | } else { 439 | verify_result = false; 440 | } 441 | //没有端口的限制条件就验证成功 442 | } else { 443 | return verify_result = true; 444 | } 445 | 446 | } else { 447 | verify_result = false; 448 | } 449 | } 450 | //验证失败删除线条 451 | !verify_result && model.removeLinkData(lineObject); 452 | })(); 453 | }, 454 | setLineStyle: function(linkStyle) { 455 | for (var item in linkStyle) { 456 | if (linkStyle.hasOwnProperty(item)) { 457 | 458 | diagram.linkTemplateMap.add(item, $(go.Link, { 459 | //可重置线条起始点 460 | relinkableFrom: true, 461 | relinkableTo: true, 462 | //线条风格 路由 避开节点 463 | routing: go.Link.AvoidsNodes, 464 | corner: 2, 465 | resegmentable: true, 466 | }, 467 | $(go.Shape, { 468 | stroke: linkStyle[item], 469 | strokeWidth: 1 470 | }) 471 | 472 | )); 473 | } 474 | } 475 | }, 476 | Guid: function() { 477 | return Date.now(); 478 | }, 479 | monitorPortChange: (function() { 480 | var initToPortId = undefined, 481 | body = document.getElementsByTagName('body')[0]; 482 | 483 | var portChangeEnd = function() { 484 | if (typeof initToPortId !== "undefined") { 485 | portChangeCallback(initToPortId, 'end'); 486 | //重置 initToPortId 487 | return initToPortId = undefined; 488 | } 489 | } 490 | 491 | //当放下鼠标之后就是结束绘制了 492 | body.addEventListener('mouseup', portChangeEnd); 493 | return function(toPortId) { 494 | //如果 initToPortId 未定义则说明 刚开始绘制 495 | if (typeof initToPortId === "undefined") { 496 | //更新 initToPortId 497 | initToPortId = toPortId; 498 | return portChangeCallback(toPortId, 'start'); 499 | } else if (initToPortId != toPortId) { //如果 initToPortIdu等于toPortId 则说明 绘制的端口发生了变化 500 | //更新 initToPortId 501 | initToPortId = toPortId; 502 | return portChangeCallback(toPortId, 'changing') 503 | } 504 | 505 | 506 | } 507 | })(), 508 | getDeviceInfo: function(device_node_info) { 509 | var fromDevice = [], 510 | toDevice = []; 511 | 512 | model.linkDataArray.forEach(function(lineObject) { 513 | if (lineObject.from == device_node_info.key) { 514 | var device = Object.assign(model.findNodeDataForKey(lineObject.to)); 515 | device.port = lineObject.fromPort; 516 | toDevice.push(device); 517 | } else if (lineObject.to == device_node_info.key) { 518 | var device = Object.assign({}, model.findNodeDataForKey(lineObject.from)); 519 | device.port = lineObject.toPort; 520 | fromDevice.push(device); 521 | } 522 | }); 523 | 524 | 525 | getDeviceDetailCallback(device_node_info, fromDevice, toDevice); 526 | }, 527 | getType: function(v) { 528 | return ({}).toString.call(v); 529 | } 530 | } 531 | 532 | 533 | 534 | //定义暴露出去的接口 535 | var o = { 536 | init: function(viewOptions, panelOptions, callback) { 537 | //如果参数不存在 或不符合要求 538 | if (typeof viewOptions === 'undefined' || M.getType(viewOptions.nodeDataArray) != "[object Array]" || M.getType(viewOptions.linkDataArray) != "[object Array]") { 539 | throw '初始化失败 视图参数不符合要求'; 540 | } 541 | 542 | if (panelOptions) { 543 | if (M.getType(panelOptions.model) != "[object Array]") { 544 | throw '初始化失败 面板参数不符合要求'; 545 | } else { 546 | panelOptions.model.forEach(function(device) { 547 | if (!device.name) { 548 | throw '初始化失败 请填写设备名称'; 549 | } 550 | }) 551 | } 552 | } 553 | 554 | //初始化视图 555 | initView(viewOptions.domId, panelOptions); 556 | 557 | if (callback) { 558 | //赋值 portChangeCallback 559 | portChangeCallback = callback.portChange || function() {}; 560 | 561 | getDeviceDetailCallback = callback.getDeviceDetail || function() {}; 562 | } 563 | 564 | //只能先加载设备然后 设置link的模板 565 | viewOptions.nodeDataArray.forEach(function(item, i) { 566 | if (item.name === 'router') { 567 | M.setLineStyle({ 568 | [item.key]: '#82a8c0' 569 | }); 570 | } else if (item.name === '网关') { 571 | M.setLineStyle({ 572 | [item.key]: '#e45106' 573 | }); 574 | } 575 | }); 576 | 577 | //赋予模型节点数据 578 | model.nodeDataArray = viewOptions.nodeDataArray; 579 | model.linkDataArray = viewOptions.linkDataArray; 580 | 581 | //绑定模型到图表 582 | diagram.model = model; 583 | }, 584 | createDevice: function(options) { 585 | //如果参数不存在 或不符合要求 586 | if (typeof options === 'undefined' || M.getType(options) != "[object Object]" || !options.name || M.getType(options.to) != "[object Array]") { 587 | throw '添加设备失败 参数不符合要求'; 588 | } 589 | 590 | //设备的key采用guid生成 591 | // var key = Guid.NewGuid().ToString(), 592 | //设备的名字 593 | name = options.name, 594 | //设备的连接范围 595 | to = options.to; 596 | 597 | //添加设备到model 598 | //自定义事件的类型 599 | diagram.startTransaction("add device"); 600 | model.addNodeData({ 601 | // key: key,//?? 当通过面板生成设备的时候 只能通过modelchange函数监听 动态给予key 602 | name: name, 603 | to: to 604 | }); 605 | //完成自定义事件 606 | diagram.commitTransaction("add device"); 607 | }, 608 | save: function() { 609 | return { 610 | nodeDataArray: model.nodeDataArray, 611 | linkDataArray: model.linkDataArray 612 | } 613 | }, 614 | setLineStyle: function(linkStyle) { 615 | M.setLineStyle(linkStyle); 616 | } 617 | } 618 | 619 | return window.Smt_topology = o; 620 | })(); -------------------------------------------------------------------------------- /demo/js/createList.js: -------------------------------------------------------------------------------- 1 | var data = [{ 2 | name: 'address', 3 | data: [{ 4 | "name": 'Address', 5 | "value": 1, 6 | "readonly": true 7 | }] 8 | }, { 9 | name: 'version', 10 | data: [{ 11 | "name": 'Version', 12 | "value": 'V1.0', 13 | "readonly": true 14 | }] 15 | }, { 16 | name: 'remark', 17 | data: [{ 18 | "name": 'Remark', 19 | "value": '温湿光一体设备,采集温度、湿度、光照三个参数.', 20 | "readonly": false 21 | }] 22 | }, { 23 | name: 'basis', 24 | data: [{ 25 | "name": 'Company', 26 | "value": '斯玛特', 27 | "readonly": true 28 | }, { 29 | "name": 'Guid', 30 | "value": '1111-2222', 31 | "readonly": true 32 | }, { 33 | "name": 'Name', 34 | "value": '温湿光设备', 35 | options: ['A', 'b', 'V'], 36 | "readonly": false 37 | }] 38 | }, { 39 | name: 'protocolType', 40 | data: [{ 41 | name: 'protocolType', 42 | "value": "Modbus" 43 | }] 44 | }]; 45 | 46 | function createList(data) { 47 | var group_wrapper_html = '
' 48 | data.forEach(function(group) { 49 | var group_box_html = '
\ 50 |

' + group.name + '

\ 51 |
' 52 | group.data.forEach(function(item) { 53 | var class_html = item.readonly ? ' readonly' : ' editable'; 54 | if (item.options) { 55 | var list_html = '' 56 | item.options.forEach(function(list) { 57 | list_html += '
  • ' + list + '
  • '; 58 | }); 59 | group_box_html += '
    \ 60 |

    ' + item.name + '

    \ 61 |

    ' + item.value + '

      ' + list_html + '
    \ 62 |
    '; 63 | } else { 64 | group_box_html += '
    \ 65 |

    ' + item.name + '

    \ 66 |

    ' + item.value + '

    \ 67 |
    '; 68 | } 69 | 70 | }); 71 | 72 | 73 | group_box_html += '
    '; 74 | group_wrapper_html = group_wrapper_html + group_box_html; 75 | 76 | 77 | }); 78 | group_wrapper_html = group_wrapper_html + '
    '; 79 | $('.device-info-wrapper').append(group_wrapper_html); 80 | } -------------------------------------------------------------------------------- /demo/js/index.js: -------------------------------------------------------------------------------- 1 | //下拉列表 2 | $.fn.dropList = function() { 3 | var _this = $(this), 4 | button = _this.children('p'), 5 | //button_icon = button.children('.fa'), 6 | list_group = _this.children('ul'); 7 | 8 | //定义下拉方法 切换class 9 | button.on('click', function(e) { 10 | e.stopPropagation(); 11 | _this.toggleClass('active'); 12 | //list_group.slideToggle('active'); 13 | 14 | }); 15 | 16 | //点击item 17 | list_group.children().each(function(i, item) { 18 | $(item).on('click', function(e) { 19 | e.stopPropagation(); 20 | button.text($(this).text()); 21 | button.click(); 22 | }); 23 | }); 24 | 25 | $('body').on('click', function() { 26 | if (_this.hasClass('active')) { 27 | button.click(); 28 | } 29 | }) 30 | } 31 | 32 | function bindListEnevt() { 33 | M = { 34 | submitEdit: function(oldValue, newValue, dom) { 35 | 36 | 37 | if (oldValue == newValue) { 38 | return 39 | } else { 40 | dom.addClass('saving'); 41 | setTimeout(function() { 42 | dom.removeClass('saving'); 43 | }, 2000) 44 | } 45 | 46 | }, 47 | hintUser: function(v) { 48 | console.log(v) 49 | } 50 | } 51 | $('.options.editable').dropList(); 52 | $('.group-title').on('click', function() { 53 | $(this).toggleClass('active'); 54 | $(this).siblings('.group-item-wrapper').slideToggle(); 55 | }) 56 | $('.item-content').on('click', function(e) { 57 | e.stopPropagation(); 58 | var _this = $(this), 59 | is_text_status = Boolean(_this.children('p').length), 60 | is_editable = Boolean(!$(this).hasClass('readonly')), 61 | is_saving = Boolean($(this).hasClass('saving')); 62 | 63 | //处在未正在保存状态 64 | if (is_saving) { 65 | M.hintUser('上次编辑的还未保存完毕哦'); 66 | return 67 | } 68 | //如果此项可编辑、处在文本状态 69 | if (is_editable && is_text_status) { 70 | var text_dom = _this.children('p'), 71 | text = text_dom.text(), 72 | input_dom = $(''); 73 | 74 | _this.empty().append(input_dom.val(text)); 75 | 76 | var emitSubmit = function() { 77 | var input_value = input_dom.val(); 78 | _this.empty().append('

    ' + input_value + '

    '); 79 | M.submitEdit(text, input_value, _this); 80 | } 81 | //让input获得焦点 82 | input_dom.focus(); 83 | input_dom.off('blur', emitSubmit); 84 | input_dom.on('blur', emitSubmit); 85 | 86 | 87 | } 88 | }); 89 | } 90 | window.onload = function() { 91 | var viewOptions = { 92 | domId: 'diagramDiv', 93 | //dom id 94 | // 设备数据 95 | nodeDataArray: [{ 96 | "key": "1", 97 | "name": "router", 98 | "to": [], 99 | "__gohashid": 1144, 100 | "loc": "0 0" 101 | }, { 102 | "key": "2", 103 | "name": "网关", 104 | //"to": ["router"], 105 | "__gohashid": 1145, 106 | "loc": "-130.0000000000001 102" 107 | }, { 108 | "key": "3", 109 | "name": "SRC001", 110 | type: 'SRC001', 111 | "to": ["网关"], 112 | "__gohashid": 1146, 113 | "loc": "-197.0000000000002 296" 114 | }, { 115 | "key": "4", 116 | "name": "tts", 117 | "to": ["网关", "LED"], 118 | "__gohashid": 1147, 119 | "loc": "-36 297.0000000000001" 120 | }, { 121 | "key": "5", 122 | "name": "LED", 123 | "to": ["router"], 124 | "__gohashid": 1148, 125 | "loc": "151.9999999999999 142.0000000000001" 126 | }, { 127 | "key": "8", 128 | "name": "SRC001", 129 | type: 'SRC001', 130 | "to": ["SRC001"], 131 | "loc": "-197.0000000000002 478.0000000000001" 132 | }, { 133 | "key": "9", 134 | "name": "SRC002", 135 | type: 'SRC002', 136 | "to": ["SRC001.DO2"], 137 | "loc": "-127.0000000000002 378.0000000000001" 138 | }], 139 | linkDataArray: [{ 140 | "__gohashid": 2582, 141 | "from": "2", 142 | "to": "1", 143 | 'label': 'sss', 144 | "fromPort": "output", 145 | "toPort": "input" 146 | }, { 147 | "__gohashid": 2917, 148 | "from": "3", 149 | "to": "2", 150 | "fromPort": "output", 151 | "toPort": "input" 152 | }, { 153 | "__gohashid": 3048, 154 | "from": "4", 155 | "to": "2", 156 | "fromPort": "output", 157 | "toPort": "input" 158 | }, { 159 | "__gohashid": 5333, 160 | "from": "5", 161 | "to": "1", 162 | "fromPort": "output", 163 | "toPort": "input" 164 | }] //线条数据 可填空数组 165 | }; 166 | var panelOptions = { 167 | domId: 'myPaletteDiv', 168 | //控制面板id 169 | model: [{ 170 | name: 'router', 171 | to: [] 172 | }, { 173 | name: 'gateway', 174 | to: ['router'] 175 | }, { 176 | type: 'SRC001', 177 | name: 'SRC001', 178 | to: ['网关'] 179 | }, { 180 | type: 'SRC002', 181 | name: 'SRC002', 182 | to: ['网关'] 183 | }, { 184 | name: '传感器', 185 | to: ['SRC002'] 186 | }] 187 | }; 188 | 189 | var portChange = function(toPortId, status) { 190 | switch (true) { 191 | case status === 'start': 192 | $('.port-hint').text(toPortId).show(0); 193 | return; 194 | case status === 'changing': 195 | $('.port-hint').text(toPortId); 196 | return; 197 | case status === 'end': 198 | $('.port-hint').text(toPortId).hide(0); 199 | return; 200 | } 201 | } 202 | 203 | var listParse = function() { 204 | var listData = []; 205 | $('.group-box').each(function(i, group) { 206 | group = $(group); 207 | var name = group.children('h2').text(); 208 | var data = []; 209 | group.find('.group-item-box').each(function(i, item) { 210 | item = $(item); 211 | if (item.find('.item-content').length) { 212 | data.push({ 213 | name: item.find('.item-title p').text(), 214 | value: item.find('.item-content p').text() 215 | }); 216 | } else { 217 | data.push({ 218 | name: item.find('.item-title p').text(), 219 | value: item.find('.options p').text() 220 | }); 221 | } 222 | 223 | }); 224 | listData.push({ 225 | name: name, 226 | data: data 227 | }); 228 | }); 229 | return listData; 230 | } 231 | 232 | var getDeiveDetail = function(a, b, c) { 233 | $('.device-info-wrapper').empty(); 234 | createList(data); 235 | bindListEnevt(); 236 | } 237 | 238 | 239 | Smt_topology.init(viewOptions, panelOptions, { 240 | getDeviceDetail: getDeiveDetail, 241 | portChange: portChange 242 | }); 243 | 244 | $('.save').on('click', function() { 245 | console.log(listParse()); 246 | console.log(Smt_topology.save()); 247 | setTimeout(function() { 248 | layer.msg('保存成功'); 249 | }, 500); 250 | }); 251 | 252 | $('.reset').on('click', function() { 253 | layer.msg('重置成功'); 254 | setTimeout(function() { 255 | location.reload(); 256 | }, 500); 257 | 258 | }); 259 | } -------------------------------------------------------------------------------- /demo/scss/.sass-cache/d7f3ed3a3437288f346dda7527e5414673bf7ee9/index.scssc: -------------------------------------------------------------------------------- 1 | 3.4.22 (Selective Steve) 2 | 8c60a9686efd3385efcc3cf4b2f91e034a7da17a 3 | o:Sass::Tree::RootNode :@children[o:Sass::Tree::ImportNode :@imported_filenameI"css/reset.css:ET;[:@filename0: @options{:@template0: 4 | @linei:@source_rangeo:Sass::Source::Range :@start_poso:Sass::Source::Position; i: @offseti: @end_poso;; i;i: 5 | @fileI"index.scss: encoding"GBK:@importero: Sass::Importers::Filesystem: 6 | @rootI"=C:/Users/Public/Nwt/cache/recv/张睿/workflow/demo/scss; T:@real_rootI"=C:/Users/Public/Nwt/cache/recv/张睿/workflow/demo/scss; T:@same_name_warningso:Set: 7 | @hash}F:@imported_file0; 8 | 0; @ 9 | ; I"@import 'css/reset.css'; T; i;o; ;o;; i;i;o;; i;i;@;@:@has_childrenT -------------------------------------------------------------------------------- /demo/scss/.sass-cache/f8ca3efc0b08d7728b2da59b5eea8823b40971d9/index.scssc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maskTAQ/topology/2558022db9bcf5bf0039cc9617885a9f98aa6d8b/demo/scss/.sass-cache/f8ca3efc0b08d7728b2da59b5eea8823b40971d9/index.scssc -------------------------------------------------------------------------------- /demo/scss/css/index.css: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | @import url(reset.css); 3 | html, body { 4 | width: 100%; 5 | height: 100%; } 6 | 7 | .container { 8 | min-width: 1200px; 9 | width: 100%; 10 | height: 100%; 11 | position: relative; 12 | font-family: '微软雅黑'; } 13 | .container > div { 14 | height: 100%; 15 | position: absolute; } 16 | .container .container-panel { 17 | width: 14%; 18 | height: 100%; 19 | box-shadow: 2px 0px 12px 0px rgba(0, 128, 255, 0.2); 20 | z-index: 9; } 21 | .container .container-panel .device-library { 22 | display: flex; 23 | align-items: center; 24 | width: 100%; 25 | height: 4%; 26 | font-size: 16px; 27 | color: #515151; 28 | text-indent: 40px; 29 | background-image: url("../../img/device.png"), url("../../img/title-bg.png"); 30 | background-repeat: no-repeat; 31 | background-position: 12px center,0px 0px; } 32 | .container .container-panel #myPaletteDiv { 33 | height: 96%; } 34 | .container .container-view { 35 | left: 14%; 36 | width: 60%; 37 | background: #f1f5f8; } 38 | .container .port-hint { 39 | display: none; 40 | left: 14%; 41 | bottom: 0; 42 | width: 100px; 43 | height: 100px; 44 | line-height: 100px; 45 | text-align: center; 46 | border: 1px solid #ccc; 47 | background: #fff; 48 | color: #82a8c0; 49 | font-size: 28px; } 50 | .container .container-device-info { 51 | display: block; 52 | left: 74%; 53 | width: 26%; 54 | height: 100%; 55 | overflow: auto; 56 | position: relative; 57 | border: 1px solid #e0e0e0; } 58 | .container .container-device-info .param-setting { 59 | display: flex; 60 | align-items: center; 61 | width: 100%; 62 | height: 4%; 63 | font-size: 16px; 64 | color: #515151; 65 | text-indent: 40px; 66 | background-image: url("../../img/setting.png"), url("../../img/title-bg.png"); 67 | background-repeat: no-repeat; 68 | background-position: 12px center,0px 0px; } 69 | .container .container-device-info .device-info-wrapper { 70 | height: auto; } 71 | .container .container-device-info .device-info-wrapper .has-nothing { 72 | height: 100px; 73 | line-height: 100px; 74 | text-align: center; 75 | font-size: 20px; 76 | color: #ccc; } 77 | .container .container-device-info .button-group { 78 | position: absolute; 79 | left: 0; 80 | bottom: 0; 81 | width: 100%; 82 | height: 80px; 83 | line-height: 80px; 84 | background: #f5f5f5; 85 | border-top: 1px solid #e0e0e0; 86 | text-align: center; 87 | font-size: 0; } 88 | .container .container-device-info .button-group button { 89 | border: 1px solid red; 90 | display: inline-block; 91 | width: 100px; 92 | height: 36px; 93 | font-size: 16px; 94 | background: #ebebeb; 95 | border: 1px solid #e0e0e0; 96 | border-radius: 4px; 97 | cursor: pointer; } 98 | .container .container-device-info .button-group .save { 99 | margin-right: 10%; 100 | color: #8f8f8f; } 101 | .container .container-device-info .button-group .save:hover { 102 | color: #0d79ce; 103 | border: 1px solid #0d79ce; } 104 | .container .container-device-info .button-group .reset { 105 | color: #f55655; } 106 | .container .container-device-info .button-group .reset:hover { 107 | border: 1px solid #f55655; } 108 | 109 | .group-wrapper { 110 | width: 100%; 111 | height: 100%; 112 | padding: 20px; } 113 | .group-wrapper .group-box { 114 | margin-bottom: 20px; } 115 | .group-wrapper .group-box .group-title { 116 | display: inline-block; 117 | height: 36px; 118 | line-height: 36px; 119 | color: #6496c1; 120 | text-indent: 10px; 121 | cursor: pointer; } 122 | .group-wrapper .group-box .group-title.active i { 123 | background: url("../../img/icon.png") no-repeat 0px 0px; } 124 | .group-wrapper .group-box .group-title i { 125 | box-sizing: border-box; 126 | margin-top: 6px; 127 | float: left; 128 | width: 24px; 129 | height: 24px; 130 | background: url("../../img/icon.png") no-repeat 0px -60px; 131 | cursor: pointer; } 132 | .group-wrapper .group-box .group-item-wrapper { 133 | margin-top: 10px; 134 | border: 1px solid #e0e0e0; 135 | border-right: none; 136 | border-bottom: none; } 137 | .group-wrapper .group-box .group-item-wrapper .group-item-box { 138 | height: 36px; 139 | line-height: 36px; 140 | background: #f5f5f5; } 141 | .group-wrapper .group-box .group-item-wrapper .group-item-box .item-title, .group-wrapper .group-box .group-item-wrapper .group-item-box .item-content, .group-wrapper .group-box .group-item-wrapper .group-item-box .options { 142 | float: left; 143 | height: 100%; 144 | border: 1px solid #e0e0e0; 145 | border-top: none; 146 | border-left: none; 147 | color: #717171; } 148 | .group-wrapper .group-box .group-item-wrapper .group-item-box .item-title p, .group-wrapper .group-box .group-item-wrapper .group-item-box .item-content p, .group-wrapper .group-box .group-item-wrapper .group-item-box .options p { 149 | text-overflow: ellipsis; 150 | white-space: nowrap; 151 | overflow: hidden; } 152 | .group-wrapper .group-box .group-item-wrapper .group-item-box .item-title { 153 | width: 30%; 154 | text-indent: 20px; } 155 | .group-wrapper .group-box .group-item-wrapper .group-item-box .item-content, .group-wrapper .group-box .group-item-wrapper .group-item-box .options { 156 | width: 70%; 157 | text-align: center; } 158 | .group-wrapper .group-box .group-item-wrapper .group-item-box .item-content.readonly p, .group-wrapper .group-box .group-item-wrapper .group-item-box .options.readonly p { 159 | color: #8f8f8f; } 160 | .group-wrapper .group-box .group-item-wrapper .group-item-box .item-content.editable p, .group-wrapper .group-box .group-item-wrapper .group-item-box .options.editable p { 161 | color: #3f7ba9; } 162 | .group-wrapper .group-box .group-item-wrapper .group-item-box .item-content.editable p:hover, .group-wrapper .group-box .group-item-wrapper .group-item-box .options.editable p:hover { 163 | border: 1px solid #2b2b2b; 164 | line-height: 34px; } 165 | .group-wrapper .group-box .group-item-wrapper .group-item-box .item-content input, .group-wrapper .group-box .group-item-wrapper .group-item-box .options input { 166 | width: 100%; 167 | height: 100%; 168 | border: none; 169 | text-align: center; } 170 | .group-wrapper .group-box .group-item-wrapper .group-item-box .options { 171 | width: 70%; 172 | position: relative; } 173 | .group-wrapper .group-box .group-item-wrapper .group-item-box .options.active ul { 174 | display: block; } 175 | .group-wrapper .group-box .group-item-wrapper .group-item-box .options p { 176 | height: 100%; 177 | text-align: center; } 178 | .group-wrapper .group-box .group-item-wrapper .group-item-box .options ul { 179 | display: none; 180 | position: absolute; 181 | top: 36px; 182 | left: 0; 183 | width: 100%; 184 | border: 1px solid #e0e0e0; 185 | border-top: none; 186 | border-bottom: none; 187 | background: #fff; } 188 | .group-wrapper .group-box .group-item-wrapper .group-item-box .options ul li { 189 | height: 30px; 190 | line-height: 30px; 191 | text-align: center; 192 | border-bottom: 1px solid #e0e0e0; 193 | cursor: pointer; } 194 | .group-wrapper .group-box .group-item-wrapper .group-item-box .options ul li:hover { 195 | color: #40a0ff; 196 | font-weight: bold; } 197 | 198 | /*# sourceMappingURL=index.css.map */ 199 | -------------------------------------------------------------------------------- /demo/scss/css/index.css.map: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "mappings": "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", 4 | "sources": ["../index.scss"], 5 | "names": [], 6 | "file": "index.css" 7 | } -------------------------------------------------------------------------------- /demo/scss/css/reset.css: -------------------------------------------------------------------------------- 1 | html{ 2 | color:#000; 3 | background:#FFF; 4 | } 5 | body, div, dl, dt, dd, ul, ol, li, h1, h2, h3, h4, h5, h6, pre, code, form, fieldset, legend, input, textarea, p, blockquote, th, td { 6 | margin:0; 7 | padding:0; 8 | box-sizing:border-box; 9 | } 10 | button{ 11 | border:none; 12 | background:none; 13 | outline:none; 14 | } 15 | table { 16 | border-collapse:collapse; 17 | border-spacing:0; 18 | } 19 | fieldset, img { 20 | border:0; 21 | } 22 | address, caption, cite, code, dfn, em, th, var { 23 | font-style:normal; 24 | font-weight:normal; 25 | } 26 | li { 27 | list-style:none; 28 | } 29 | caption, th { 30 | text-align:left; 31 | } 32 | h1, h2, h3, h4, h5, h6 { 33 | font-size:100%; 34 | font-weight:normal; 35 | } 36 | q:before, q:after { 37 | content:''; 38 | } 39 | abbr, acronym { 40 | border:0; 41 | font-variant:normal; 42 | } 43 | sup { 44 | vertical-align:text-top; 45 | } 46 | sub { 47 | vertical-align:text-bottom; 48 | } 49 | input, textarea, select { 50 | font-family:inherit; 51 | font-size:inherit; 52 | font-weight:inherit; 53 | outline: none; 54 | } 55 | input, textarea, select { 56 | *font-size:100%; 57 | } 58 | legend { 59 | color:#000; 60 | } 61 | a{ 62 | text-decoration: none!important; 63 | } 64 | i{ 65 | font-style:normal; 66 | } 67 | 68 | 69 | 70 | .clearfix:after{ 71 | content: "."; /**//*内容为“.”就是一个英文的句号而已。也可以不写。*/ 72 | display: block; /**//*加入的这个元素转换为块级元素。*/ 73 | clear: both; /**//*清除左右两边浮动。*/ 74 | visibility: hidden; /**//*可见度设为隐藏。注意它和display:none;是有区别的。visibility:hidden;仍然占据空间,只是看不到而已;*/ 75 | line-height: 0; /**//*行高为0;*/ 76 | height: 0; /**//*高度为0;*/ 77 | font-size:0; /**//*字体大小为0;*/ 78 | } 79 | 80 | .clearfix{ *zoom:1;} /**//*这是针对于IE6的,因为IE6不支持:after伪类,这个神奇的zoom:1让IE6的元素可以清除浮动来包裹内部元素。*/ 81 | -------------------------------------------------------------------------------- /demo/scss/index.scss: -------------------------------------------------------------------------------- 1 | @import 'reset.css'; 2 | 3 | 4 | html,body{ 5 | width:100%; 6 | height:100%; 7 | } 8 | .container{ 9 | min-width:1200px; 10 | //max-height:800px; 11 | width:100%; 12 | height:100%; 13 | position:relative; 14 | font-family:'微软雅黑'; 15 | >div{ 16 | height: 100%; 17 | position:absolute; 18 | } 19 | // &.list-close{ 20 | // .container-view{ 21 | // width: 86%; 22 | // } 23 | // .container-device-info{ 24 | // display: none; 25 | // } 26 | // } 27 | .container-panel{ 28 | width: 14%; 29 | height: 100%; 30 | box-shadow: 2px 0px 12px 0px rgba(0, 128, 255, 0.2); 31 | z-index: 9; 32 | 33 | .device-library{ 34 | display: flex; 35 | align-items: center; 36 | width: 100%; 37 | height: 4%; 38 | font-size: 16px; 39 | color: #515151; 40 | text-indent: 40px; 41 | background-image:url('../../img/device.png'),url('../../img/title-bg.png'); 42 | background-repeat:no-repeat; 43 | background-position:12px center,0px 0px; 44 | } 45 | #myPaletteDiv{ 46 | height: 96%; 47 | } 48 | } 49 | .container-view{ 50 | left: 14%; 51 | width: 60%; 52 | background: #f1f5f8; 53 | 54 | } 55 | .port-hint{ 56 | display: none; 57 | left: 14%; 58 | bottom: 0; 59 | width: 100px; 60 | height: 100px; 61 | line-height: 100px; 62 | text-align: center; 63 | border:1px solid #ccc; 64 | background:#fff; 65 | color: #82a8c0; 66 | font-size: 28px; 67 | } 68 | .container-device-info{ 69 | display: block; 70 | left: 74%; 71 | width: 26%; 72 | height: 100%; 73 | overflow: auto; 74 | position:relative; 75 | border: 1px solid #e0e0e0; 76 | .param-setting{ 77 | display: flex; 78 | align-items: center; 79 | width: 100%; 80 | height: 4%; 81 | font-size: 16px; 82 | color: #515151; 83 | text-indent: 40px; 84 | background-image:url('../../img/setting.png'),url('../../img/title-bg.png'); 85 | background-repeat:no-repeat; 86 | background-position:12px center,0px 0px; 87 | } 88 | .device-info-wrapper{ 89 | height: auto; 90 | .has-nothing{ 91 | height: 100px; 92 | line-height: 100px; 93 | text-align:center; 94 | font-size: 20px; 95 | color: #ccc; 96 | } 97 | } 98 | .button-group{ 99 | position:absolute; 100 | left: 0; 101 | bottom:0; 102 | width: 100%; 103 | height: 80px; 104 | line-height: 80px; 105 | background:#f5f5f5; 106 | border-top:1px solid #e0e0e0; 107 | text-align: center; 108 | font-size: 0; 109 | button{ 110 | border:1px solid red; 111 | display: inline-block; 112 | width: 100px; 113 | height: 36px; 114 | font-size: 16px; 115 | background:#ebebeb; 116 | border:1px solid #e0e0e0; 117 | border-radius:4px; 118 | cursor: pointer; 119 | } 120 | .save{ 121 | margin-right: 10%; 122 | color: #8f8f8f; 123 | &:hover{ 124 | color: #0d79ce; 125 | border:1px solid #0d79ce; 126 | } 127 | } 128 | .reset{ 129 | color: #f55655; 130 | &:hover{ 131 | border:1px solid #f55655; 132 | } 133 | } 134 | } 135 | } 136 | } 137 | 138 | .group-wrapper{ 139 | width:100%; 140 | height:100%; 141 | padding:20px; 142 | .group-box{ 143 | margin-bottom: 20px; 144 | .group-title{ 145 | display: inline-block; 146 | height: 36px; 147 | line-height: 36px; 148 | color: #6496c1; 149 | text-indent: 10px; 150 | cursor: pointer; 151 | &.active{ 152 | i{ 153 | background:url('../../img/icon.png') no-repeat 0px 0px; 154 | } 155 | } 156 | i{ 157 | box-sizing:border-box; 158 | margin-top: 6px; 159 | float: left; 160 | width: 24px; 161 | height: 24px; 162 | background:url('../../img/icon.png') no-repeat 0px -60px; 163 | cursor: pointer; 164 | } 165 | } 166 | .group-item-wrapper{ 167 | margin-top: 10px; 168 | border:1px solid #e0e0e0; 169 | border-right:none; 170 | border-bottom:none; 171 | .group-item-box{ 172 | height: 36px; 173 | line-height: 36px; 174 | background:#f5f5f5; 175 | .item-title,.item-content,.options{ 176 | float: left; 177 | height: 100%; 178 | border:1px solid #e0e0e0; 179 | border-top:none; 180 | border-left:none; 181 | color: #717171; 182 | p{ 183 | text-overflow: ellipsis; 184 | white-space: nowrap; 185 | overflow: hidden; 186 | } 187 | } 188 | .item-title{ 189 | width: 30%; 190 | text-indent: 20px; 191 | } 192 | .item-content,.options{ 193 | width: 70%; 194 | text-align: center; 195 | &.readonly{ 196 | p{ 197 | color:#8f8f8f; 198 | } 199 | } 200 | &.editable{ 201 | p{ 202 | color:#3f7ba9; 203 | &:hover{ 204 | border:1px solid #2b2b2b; 205 | line-height: 34px; 206 | } 207 | } 208 | } 209 | input{ 210 | width: 100%; 211 | height: 100%; 212 | border:none; 213 | text-align: center; 214 | } 215 | } 216 | .options{ 217 | width: 70%; 218 | position:relative; 219 | &.active{ 220 | ul{ 221 | display: block; 222 | } 223 | } 224 | p{ 225 | height: 100%; 226 | text-align: center; 227 | } 228 | ul{ 229 | display: none; 230 | position:absolute; 231 | top:36px; 232 | left:0; 233 | width: 100%; 234 | border:1px solid #e0e0e0; 235 | border-top:none; 236 | border-bottom:none; 237 | background:#fff; 238 | li{ 239 | height: 30px; 240 | line-height: 30px; 241 | text-align: center; 242 | border-bottom:1px solid #e0e0e0; 243 | cursor: pointer; 244 | &:hover{ 245 | //background:#6b6b6b; 246 | color:#40a0ff; 247 | font-weight: bold; 248 | } 249 | } 250 | } 251 | } 252 | } 253 | } 254 | } 255 | 256 | } -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # 基于go.js 的动态绘制拓扑图插件 2 | 3 | ## 使用方法 4 | 1. 引入go.js 5 | 2. 引入Smt_topology.js 6 | 7 | ````javascript 8 | 初始化界面 9 | ```` 10 | 11 | ````javascript 12 | Smt_topology.init({ 13 | domId: 'domId', 14 | //dom id 15 | // 设备数据 16 | nodeDataArray: [{ 17 | //设备key 18 | key: '1', 19 | //设备名称 20 | name: 'co2', 21 | //设备可接入其他设备的名称集 必须是数组 可不填此选项 22 | to: ['co2', 'co3'] 23 | }, 24 | { 25 | key: '2', 26 | name: 'co3', 27 | to: ['co4', 'co3'] 28 | }], 29 | linkDataArray: [] //线条数据 可填空数组 30 | }, 31 | { 32 | domId: 'id',//控制面板id 33 | model: [{ 34 | name: 'router', 35 | to: [] 36 | }, 37 | { 38 | name: 'gateway', 39 | to: ['router'] 40 | }, 41 | { 42 | type: 'SRC001', 43 | name: 'SRC001', 44 | to: ['网关'] 45 | }, 46 | { 47 | type: 'SRC002', 48 | name: 'SRC002', 49 | to: ['网关'] 50 | }, 51 | { 52 | name: '传感器', 53 | to: ['SRC002'] 54 | }] 55 | }); 56 | ```` 57 | 58 | 添加设备 59 | ````javascript 60 | Smt_topology.createDevice({ 61 | name: 'deviceName',//设备名 62 | to: ['co2']//设备可接入其他设备的名称集 必须是数组 可不填此选项 63 | }); 64 | ```` 65 | 66 | 保存数据 67 | ````javascript 68 | Smt_topology.save();//返回包含设备信息和线条信息的对象 69 | ```` 70 | --------------------------------------------------------------------------------