Skip to content
jtopo-editor.js 53.3 KiB
Newer Older
wangqinghua's avatar
wangqinghua committed
/**
 * 基于jtopo-editor.js的二次封装
 * designed by xwenyuan
 * github: https://github.com/xwenyuan/jtopo_topology.git
 */
/**
 * 提供拓扑图面板相关操作的函数集,编辑器继承其全部功能
 */
wangqinghua's avatar
wangqinghua committed
function TopologyPanel() {
}
wangqinghua's avatar
wangqinghua committed

/**
 * 保存序列化的拓扑图JSON数据到服务器
 */
TopologyPanel.prototype.saveTopology = function (url) {
wangqinghua's avatar
wangqinghua committed
    editor.mainMenu.hide();
    var self = this;
wangqinghua's avatar
wangqinghua committed
    // 保存container状态
wangqinghua's avatar
wangqinghua committed
    var containers = editor.utils.getContainers()
    for (var c = 0; c < containers.length; c++) {
        var temp = []
        var nodes = containers[c].childs
        for (var n = 0; n < nodes.length; n++) {
wangqinghua's avatar
wangqinghua committed
            if (nodes[n] instanceof JTopo.Node) {
                temp.push(nodes[n].nodeId)
            }
        }
        containers[c].childNodes = temp.join(',')
    }
    // 获取json
wangqinghua's avatar
wangqinghua committed
    var topologyJSON = editor.stage.toJson();
wangqinghua's avatar
wangqinghua committed
    return topologyJSON;
wangqinghua's avatar
wangqinghua committed
    // 保存拓扑图数据
}

/**
 * 重置拓扑图
 */
TopologyPanel.prototype.resetTopology = function (url) {
    editor.stageMode = 'normal'
    this.replaceStage(url)
}

/**
 * 加载指定id的拓扑图JSON数据结构
 * @param topologyGuid 拓扑 表记录ID
 * @param backImg 拓扑图的背景图片
 */
wangqinghua's avatar
wangqinghua committed
TopologyPanel.prototype.loadTopology = function (response, topologyGuid, backImg,canvasWidth,canvasHeight) {
wangqinghua's avatar
wangqinghua committed
            // 错误处理
wangqinghua's avatar
wangqinghua committed
          if (!response) {
wangqinghua's avatar
wangqinghua committed
                // 拓扑不存在,创建一个空白的拓扑图
wangqinghua's avatar
wangqinghua committed
                var initTopologyJson = {
wangqinghua's avatar
wangqinghua committed
                    'version': '0.4.8',
                    'wheelZoom': 0.95,
                    'width': 972,
                    'height': 569,
                    'id': 'ST172.19.105.52015100809430700001',
                    'childs': [
                        {
                            'elementType': 'scene',
                            'id': 'S172.19.105.52015100809430700002',
                            'translateX': -121.82,
                            'translateY': 306.72,
                            'scaleX': 1.26,
                            'scaleY': 1.26,
                            'childs': []
                        }
                    ]
                }
wangqinghua's avatar
wangqinghua committed
                editor.init(topologyGuid, backImg, initTopologyJson,canvasWidth,canvasHeight)
wangqinghua's avatar
wangqinghua committed
            } else {
                // 拓扑存在,渲染拓扑图
wangqinghua's avatar
wangqinghua committed
                editor.init(topologyGuid, backImg, response,canvasWidth,canvasHeight)
wangqinghua's avatar
wangqinghua committed
            }
}

/**
 * 传入JSON形式的拓扑图数据,绘制拓扑图。如果数据结构不正确,返回空拓扑图
 * @param topologyJson json形式的拓扑结构数据
 * @param backImg 拓扑图的背景图片
 */
TopologyPanel.prototype.loadTopologyByJson = function (topologyJson, backImg) {
    try {
        JTopo.replaceStageWithJson(topologyJson)
        if (editor.stage && editor.scene && editor.scene.childs && editor.scene.childs.length > 0) {
wangqinghua's avatar
wangqinghua committed
            editor.stage.centerAndZoom();
            var alarmList = editor.utils.getAllNodes();
            for(var i = 0;i < alarmList.length;i++){
                (function (i) {
xiaowenjie's avatar
xiaowenjie committed
                    if (alarmList[i].alarm == '危险'){
                        setInterval(function(){
                            if(alarmList[i].alarm == '危险'){
                                alarmList[i].alarm = '';
                            }else if(alarmList[i].alarm == ''){
                                alarmList[i].alarm  = '危险'
                            }
wangqinghua's avatar
wangqinghua committed
                        }, 1000);
xiaowenjie's avatar
xiaowenjie committed
                    }
                    if (alarmList[i].alarm == '主机被删除,请及时清理'){
                        setInterval(function(){
                            if(alarmList[i].alarm == '主机被删除,请及时清理'){
                                alarmList[i].alarm = '';
                            }else if(alarmList[i].alarm == ''){
                                alarmList[i].alarm  = '主机被删除,请及时清理'
                            }
wangqinghua's avatar
wangqinghua committed
                        }, 1000);
xiaowenjie's avatar
xiaowenjie committed
                    }
                    if (alarmList[i].alarm == '故障'){
                        setInterval(function(){
                            if(alarmList[i].alarm == '故障'){
                                alarmList[i].alarm = '';
                            }else if(alarmList[i].alarm == ''){
                                alarmList[i].alarm  = '故障'
                            }
                        }, 1000);
                    }

wangqinghua's avatar
wangqinghua committed
                })(i);
            }

wangqinghua's avatar
wangqinghua committed
        }
    } catch (e) {
wangqinghua's avatar
wangqinghua committed
        var initTopologyJson = {
wangqinghua's avatar
wangqinghua committed
            'version': '0.4.8',
            'wheelZoom': 0.95,
            'width': 972,
            'height': 569,
            'id': 'ST172.19.105.52015100809430700001',
            'childs': [
                {
                    'elementType': 'scene',
                    'id': 'S172.19.105.52015100809430700002',
                    'translateX': -121.82,
                    'translateY': 306.72,
                    'scaleX': 1.26,
                    'scaleY': 1.26,
                    'childs': []
                }
            ]
        }
        JTopo.replaceStageWithJson(initTopologyJson)
        if (editor.stage && editor.scene && editor.scene.childs && editor.scene.childs.length > 0) {
            editor.stage.centerAndZoom()
        }
    }
}

/**
 * 清空所有节点
 */
TopologyPanel.prototype.deleteAllNodes = function () {
    editor.stage.childs.forEach(function (s) {
        s.clear()
    })
    // 连线重置
    editor.beginNode = null
    editor.link = null
    // alert('已清空拓扑图')
}

/**
 * 编辑器对象,原型继承拓扑图面板对象,提供编辑器的主要功能
 */
function TopologyEditor() {
    // 绘图参数
    this.config = {
        // Stage属性
        stageFrames: 500,                       // 舞台播放的帧数/秒
        defaultScal: 0.95,                      // 鼠标滚轮缩放比例
        eagleEyeVsibleDefault: false,         // 是否显示鹰眼对象
        // Node属性
        nodeAlpha: 1,                           // 节点透明度,取值范围[0-1]
        nodeStrokeColor: '22,124,255',        // 节点描边的颜色
        nodeFillColor: '22,124,255',          // 节点填充颜色
        nodeShadow: false,                     // 节点是否显示阴影
        nodeShadowColor: 'rgba(0,0,0,0.5)',  // 节点阴影的颜色
        nodeFont: '12px Consolas',            // 节点字体
        nodeFontColor: 'black',               // 节点文字颜色,如"255,255,0"
        nodeDefaultWidth: 32,                 // 新建节点默认宽
        nodeDefaultHeight: 32,                // 新建节点默认高
        nodeBorderColor: 'black',            // 节点容器边框颜色,如"255,255,0"
        nodeBorderRadius: 30,                // 节点半径,非圆节点有此属性会变形
        nodeRotateValue: 0.5,                // 节点旋转的角度(弧度)
        nodeScale: 0.2,                       // 节点缩放幅度(此处保证X和Y均等缩放)
        // Link属性
        linkAlpha: 1,                         // 连线透明度,取值范围[0-1]
        linkStrokeColor: '123,165,241',     // 连线的颜色
        linkFillColor: '123,165,241',
        linkShadow: false,                   // 是否显示连线阴影
        linkShadowColor: 'rgba(0,0,0,0.5)',
xiaowenjie's avatar
xiaowenjie committed
        linkFont: '25px Consolas',           // 节点字体
wangqinghua's avatar
wangqinghua committed
        linkFontColor: 'red',              // 连线文字颜色,如"255,255,0"
wangqinghua's avatar
wangqinghua committed
        linkArrowsRadius: 0,                 // 线条箭头半径
xiaowenjie's avatar
xiaowenjie committed
        linkDefaultWidth: 2,                 // 连线宽度
wangqinghua's avatar
wangqinghua committed
        linkOffsetGap: 80,                   // 折线拐角处的长度
wangqinghua's avatar
wangqinghua committed
        linkDirection: 'horizontal',        // 折线的方向
        // Container属性
        containerAlpha: 1,
        containerStrokeColor: '22,124,255',
        containerFillColor: '22,124,255',
        containerShadow: false,
        containerShadowColor: 'rgba(0,0,0,0.5)',
        containerFont: '12px Consolas',
        containerFontColor: 'black',
        containerBorderColor: 'black',
wangqinghua's avatar
wangqinghua committed
        containerBorderRadius: 30,

        //主机属性
        hostId:null,
wangqinghua's avatar
wangqinghua committed
        //监控项属性
        itemId:[]
wangqinghua's avatar
wangqinghua committed
    };
wangqinghua's avatar
wangqinghua committed
    // 布局参数
wangqinghua's avatar
wangqinghua committed
    this.layout = {};
wangqinghua's avatar
wangqinghua committed
    // 绘图区属性
wangqinghua's avatar
wangqinghua committed
    this.stage = null;
wangqinghua's avatar
wangqinghua committed
    this.scene = null
    // 当前模式
    this.stageMode = 'normal'
    // 默认连线类型
    this.lineType = 'line'
    // 当前选择的节点对象
    this.currentNode = null
    // 当前选择的连线对象
    this.currentLink = null
    // 节点右键菜单DOM对象
    this.nodeMenu = $('#node-menu')
    // 连线右键菜单DOM对象
    this.lineMenu = $('#line-menu')
    // 全局右键菜单
    this.mainMenu = $('#main-menu')
    // 布局管理菜单
    this.layoutMenu = $('#layout-menu')
    // 节点文字方向
    this.nodeTextPosMenu = $('#node-text-pos-menu')
    // 节点分组菜单
    this.groupMangeMenu = $('#group-mange-menu')
    // 节点对齐菜单
    this.groupAlignMenu = $('#group-align-menu')
    this.alignGroup = $('#align-group')
    // 分组的容器管理菜单
    this.containerMangeMenu = $('#container-mange-menu')
    // 调用构造函数,继承TopologyPanel类
    TopologyPanel.call(this)
}

// 原型继承
TopologyEditor.prototype = new TopologyPanel()

/**
 * 菜单初始化
 */
TopologyEditor.prototype.initMenus = function () {
wangqinghua's avatar
wangqinghua committed
    var self = this
wangqinghua's avatar
wangqinghua committed

    // 右键菜单事件处理(右键一级菜单)
    self.nodeMenu.on('click', function (event) {
        // 菜单文字对应事件
wangqinghua's avatar
wangqinghua committed
        var text = $.trim($(event.target).text())
wangqinghua's avatar
wangqinghua committed
        if (text === '删除节点(Delete)') {
            editor.utils.deleteSelectedNodes()
        } else if (text === '复制节点(Shift+C)') {
            self.utils.cloneSelectedNodes()
        } else if (text === '撤销(Shift+Z)') {
            self.utils.cancleNodeAction()
        } else if (text === '重做(Shift+R)') {
            self.utils.reMakeNodeAction()
        } else {
            editor.utils.saveNodeInitState()
        }
        switch (text) {
            case '放大(Shift+)':
                self.utils.scalingBig()
                self.utils.saveNodeNewState()
                break
            case '缩小(Shift-)':
                self.utils.scalingSmall()
                self.utils.saveNodeNewState()
                break
            case '顺时针旋转(Shift+U)':
                self.utils.rotateAdd()
                self.utils.saveNodeNewState()
                break
            case '逆时针旋转(Shift+I)':
                self.utils.rotateSub()
                self.utils.saveNodeNewState()
                break
            case '节点文字':
                return
            default :

        }
        // 关闭菜单
        $(this).hide()
    })

    self.nodeMenu.on('mouseover', function (event) {
        // 菜单文字
wangqinghua's avatar
wangqinghua committed
        var text = $.trim($(event.target).text())
        var menuX = parseInt(this.style.left) + $(document.getElementById('change-node-text-pos')).width()
wangqinghua's avatar
wangqinghua committed
        // 边界判断
        if (menuX + self.nodeTextPosMenu.width() * 2 >= self.stage.width) {
            menuX -= (self.nodeTextPosMenu.width() + self.nodeMenu.width())
        }
        if (text === '文字位置') {
            self.layoutMenu.hide()
            self.nodeTextPosMenu.css({
                top: parseInt(this.style.top) + $(document.getElementById('change-node-text-pos')).height(),
                left: menuX
            }).show()
        } else if (text === '应用布局') {
            self.nodeTextPosMenu.hide()
            self.layoutMenu.css({
                top: parseInt(this.style.top),
                left: menuX
            }).show()
        } else {
            self.layoutMenu.hide()
            self.nodeTextPosMenu.hide()
        }
    })

    // 修改节点文字位置菜单
    self.nodeTextPosMenu.on('click', function (event) {
        // 菜单文字
wangqinghua's avatar
wangqinghua committed
        var text = $.trim($(event.target).text())
wangqinghua's avatar
wangqinghua committed
        if (self.currentNode && self.currentNode instanceof JTopo.Node) {
            self.utils.saveNodeInitState()
            switch (text) {
                case '顶部居左':
                    self.currentNode.textPosition = 'Top_Left'
                    self.utils.saveNodeNewState()
                    break
                case '顶部居中':
                    self.currentNode.textPosition = 'Top_Center'
                    self.utils.saveNodeNewState()
                    break
                case '顶部居右':
                    self.currentNode.textPosition = 'Top_Right'
                    self.utils.saveNodeNewState()
                    break
                case '中间居左':
                    self.currentNode.textPosition = 'Middle_Left'
                    self.utils.saveNodeNewState()
                    break
                case '居中':
                    self.currentNode.textPosition = 'Middle_Center'
                    self.utils.saveNodeNewState()
                    break
                case '中间居右':
                    self.currentNode.textPosition = 'Middle_Right'
                    self.utils.saveNodeNewState()
                    break
                case '底部居左':
                    self.currentNode.textPosition = 'Bottom_Left'
                    self.utils.saveNodeNewState()
                    break
                case '底部居中':
                    self.currentNode.textPosition = 'Bottom_Center'
                    self.utils.saveNodeNewState()
                    break
                case '底部居右':
                    self.currentNode.textPosition = 'Bottom_Right'
                    self.utils.saveNodeNewState()
                    break
                default :
            }
            $('div[id$=\'-menu\']').hide()
        }
    })
    // 连线菜单
    self.lineMenu.on('click', function (event) {
        // 关闭菜单
        $(this).hide()
wangqinghua's avatar
wangqinghua committed
        var text = $.trim($(event.target).text())
wangqinghua's avatar
wangqinghua committed
        switch (text) {
            case '连线设置':
                // alert('连线设置')
                break
            case '删除连线':
                editor.utils.deleteLine()
                break
            default :
        }
    })

    // 系统设置菜单
    self.mainMenu.on('click', function (event) {
        // 关闭菜单
        $(this).hide()
    })

    // 节点分组菜单
    self.groupMangeMenu.on('click', function (event) {
        $(this).hide()
wangqinghua's avatar
wangqinghua committed
        var text = $.trim($(event.target).text())
wangqinghua's avatar
wangqinghua committed
        if (text === '新建分组') {
            self.utils.toMerge()
        }
    })
    // 对齐
    self.groupAlignMenu.on('click', function (event) {
wangqinghua's avatar
wangqinghua committed
        var currNode = self.currentNode
        var selectedNodes = self.utils.getSelectedNodes()
wangqinghua's avatar
wangqinghua committed
        if (!currNode || !selectedNodes || selectedNodes.length === 0) return
        $(this).hide()
wangqinghua's avatar
wangqinghua committed
        var text = $.trim($(event.target).text())
wangqinghua's avatar
wangqinghua committed
        selectedNodes.forEach(function (n) {
            if (n.nodeId === currNode.nodeId) return true
            if (text === '水平对齐') {
                n.y = currNode.y
            } else if (text === '垂直对齐') {
                n.x = currNode.x
            } else {

            }
        })
    })
    self.groupMangeMenu.on('mouseover', function (event) {
wangqinghua's avatar
wangqinghua committed
        var text = $.trim($(event.target).text())
wangqinghua's avatar
wangqinghua committed
        if (text === '对齐方式') {
            // 节点对齐
wangqinghua's avatar
wangqinghua committed
            var menuX = parseInt(this.style.left) + $(document.getElementById('align-group')).width()
wangqinghua's avatar
wangqinghua committed
            if (menuX + self.alignGroup.width() * 2 >= self.stage.width) {
                menuX -= (self.alignGroup.width() + self.groupMangeMenu.width())
            }
            self.groupAlignMenu.css({
                top: parseInt(this.style.top) + $(document.getElementById('align-group')).height(),
                left: menuX
            }).show()
        } else {
            self.groupAlignMenu.hide()
        }
    })
    // 容器管理菜单
    self.containerMangeMenu.on('click', function (event) {
wangqinghua's avatar
wangqinghua committed
        var cNode = editor.currentNode
wangqinghua's avatar
wangqinghua committed
        if (!(cNode instanceof JTopo.Container)) {
            return
        }
        $(this).hide()
wangqinghua's avatar
wangqinghua committed
        var text = $.trim($(event.target).text())
wangqinghua's avatar
wangqinghua committed
        if (text === '拆分') {
            self.utils.toSplit()
            self.utils.deleteNode(cNode)
        }
    })

    // 容器管理菜单
    self.layoutMenu.on('click', function (event) {
        editor.currentNode.layout = {}
        $('div[id$=\'-menu\']').hide()
wangqinghua's avatar
wangqinghua committed
        var text = $.trim($(event.target).text())
wangqinghua's avatar
wangqinghua committed
        if (text === '取消布局') {
            editor.currentNode.layout.on = false
        } else if (text === '分组布局') {
            editor.currentNode.layout.on = true
            editor.currentNode.layout.type = 'auto'
        } else if (text === '树形布局') {
            editor.currentNode.layout.on = true
            editor.currentNode.layout.type = 'tree'
            editor.currentNode.layout.direction = 'bottom'
            editor.currentNode.layout.width = 80
            editor.currentNode.layout.height = 100
            JTopo.layout.layoutNode(self.scene, self.currentNode, true)
        } else if (text === '圆形布局') {
            editor.currentNode.layout.on = true
            editor.currentNode.layout.type = 'circle'
            editor.currentNode.layout.radius = 200
            JTopo.layout.layoutNode(self.scene, self.currentNode, true)
        }
    })
}
/**
 * 替换当前舞台,用于编辑保存后重新加载
 * @param topologyGuid
 */
TopologyEditor.prototype.replaceStage = function (url) {
    // var self = this
    $.ajax({
        type: 'GET',
        url: url,
        async: false,
        contentType: 'application/json',
        dataType: 'json',
        error: function () {
            // alert('服务器异常,请稍后重试..')
        },
        success: function (response) {
            // 错误处理
            if (response.code !== 200) {
                console.error(response.msg)
            } else {
wangqinghua's avatar
wangqinghua committed
                var topologyJson = response.data.topology_json
wangqinghua's avatar
wangqinghua committed
                JTopo.replaceStageWithJson(topologyJson)
                if (editor.stage && editor.scene && editor.scene.childs && editor.scene.childs.length > 0) {
                    editor.stage.centerAndZoom()
                }
            }
        }
    })
}
/**
 * 编辑器初始化方法,根据请求返回结果加载空白的或者指定结构的拓扑编辑器
 * @param topologyGuid  拓扑记录ID
 * @param backImg     背景图片
 * @param topologyJson    拓扑JSON结构
 */
wangqinghua's avatar
wangqinghua committed
TopologyEditor.prototype.init = function (topologyGuid, backImg, topologyJson,canvasWidth,canvasHeight) {
wangqinghua's avatar
wangqinghua committed
    if (!topologyJson) {
wangqinghua's avatar
wangqinghua committed
        // alert('加载拓扑编辑器失败!')
wangqinghua's avatar
wangqinghua committed
        return
    }
    this.topologyGuid = topologyGuid
    // 创建jTopo舞台屏幕对象
wangqinghua's avatar
wangqinghua committed
    var canvas = document.getElementById('topology-canvas')
wangqinghua's avatar
wangqinghua committed
    canvas.width = canvasWidth;
    canvas.height = canvasHeight;
wangqinghua's avatar
wangqinghua committed
    // 加载空白的编辑器
    if (topologyJson === '-1') {
        this.stage = new JTopo.Stage(canvas)         // 定义舞台对象
        this.scene = new JTopo.Scene(this.stage)    // 定义场景对象
    } else {
        this.stage = JTopo.createStageFromJson(topologyJson, canvas)    // 根据保存好的jsonStr(拓扑结构)创建舞台对象
        this.scene = this.stage.childs[0]                           // 场景对象列表,childs是舞台的属性
    }
    // 滚轮缩放
    this.stage.frames = this.config.stageFrames       // 设置当前舞台播放的帧数/秒
    this.stage.wheelZoom = this.config.defaultScal    // 鼠标滚轮缩放操作比例
    this.stage.eagleEye.visible = this.config.eagleEyeVsibleDefault    // 是否开启鹰眼
    this.stage.mode = this.stageMode
    // 设置舞台模式
    // 背景由样式指定
    // this.scene.background = backImg;
    // 用来连线的两个节点
    this.tempNodeA = new JTopo.Node('tempA')
    this.tempNodeA.setSize(1, 1)
    this.tempNodeZ = new JTopo.Node('tempZ')
    this.tempNodeZ.setSize(1, 1)
    this.beginNode = null
    this.link = null
wangqinghua's avatar
wangqinghua committed
    var self = this
wangqinghua's avatar
wangqinghua committed

    // 模拟告警
wangqinghua's avatar
wangqinghua committed
    if(editor.utils.getAllNodes().length > 0){
wangqinghua's avatar
wangqinghua committed
        debugger
wangqinghua's avatar
wangqinghua committed
        editor.utils.getAllNodes()[0].alarm = "告警";
    }
wangqinghua's avatar
wangqinghua committed


    // 初始化菜单
    this.initMenus()

    // 鼠标进入事件
    this.scene.mouseover(function (event) {
        Timer.start()
        // 进入某个节点
        if (event.target != null && event.target instanceof JTopo.Node && event.target.nodeTooltip && editor.stageMode !== 'edit') {
            $('.node-tooltip span').html(event.target.nodeTooltip)
            // 记录鼠标触发位置在canvas中的相对位置
wangqinghua's avatar
wangqinghua committed
            var menuY = event.layerY ? event.layerY : event.offsetY
            var menuX = event.layerX ? event.layerX : event.offsetX
wangqinghua's avatar
wangqinghua committed
            // 判断边界出是否能完整显示弹出菜单
            if (menuX + $('.node-tooltip').width() >= self.stage.width) {
                menuX -= $('.node-tooltip').width()
            }
            if (menuY + $('.node-tooltip').height() >= self.stage.height) {
                menuY -= $('.node-tooltip').height()
            }
            $('.link-tooltip').css('display', 'none')
            $('.node-tooltip').css({
                'display': 'block',
                'margin-top': menuY,
                'margin-left': menuX,
                'cursor': 'pointer'
            })
            // 进入某个连线
        } else if (event.target != null && event.target instanceof JTopo.Link && event.target.linkTooltip && editor.stageMode !== 'edit') {
            $('.link-tooltip span').html(event.target.linkTooltip)
            // 记录鼠标触发位置在canvas中的相对位置
wangqinghua's avatar
wangqinghua committed
            var menuY = event.layerY ? event.layerY : event.offsetY
            var menuX = event.layerX ? event.layerX : event.offsetX
wangqinghua's avatar
wangqinghua committed
            // 判断边界出是否能完整显示弹出菜单
            if (menuX + $('.link-tooltip').width() >= self.stage.width) {
                menuX -= $('.link-tooltip').width()
            }
            if (menuY + $('.link-tooltip').height() >= self.stage.height) {
                menuY -= $('.link-tooltip').height()
            }
            $('.node-tooltip').css('display', 'none')
            $('.link-tooltip').css({
                'display': 'block',
                'margin-top': menuY,
                'margin-left': menuX,
                'cursor': 'pointer'
            })
        } else {
            // 鼠标进入别的地方
        }
    })

    // 鼠标离开事件
    this.scene.mouseout(function (event) {
wangqinghua's avatar
wangqinghua committed
        var timeSpan = Timer.pause()
wangqinghua's avatar
wangqinghua committed
        // 消抖
        if (timeSpan > 100) {
            $('.node-tooltip').css('display', 'none')
            $('.link-tooltip').css('display', 'none')
            Timer.stop()
        }
    })

    // 鼠标单击节点事件
    this.scene.click(function (event) {
        if (!event.target) {
            // 单击舞台空白处
            $('.node-tooltip').css('display', 'none')
            return;
        }
        self.currentNode = event.target;

        // 只读模式下单击节点
        if (event.target instanceof JTopo.Node && editor.stageMode === 'normal') {
wangqinghua's avatar
wangqinghua committed

wangqinghua's avatar
wangqinghua committed
        } else if (event.target instanceof JTopo.Node && editor.stageMode === 'edit') {
wangqinghua's avatar
wangqinghua committed

wangqinghua's avatar
wangqinghua committed

        } else if (event.target instanceof JTopo.Link && editor.stageMode === 'normal') {
wangqinghua's avatar
wangqinghua committed

wangqinghua's avatar
wangqinghua committed
        } else if (event.target instanceof JTopo.Link && editor.stageMode === 'edit') {
wangqinghua's avatar
wangqinghua committed

wangqinghua's avatar
wangqinghua committed
        } else {
            // 单击别的地方
            $('.node-tooltip').css('display', 'none')
        }
    })

    // 双击编辑事件
    this.scene.dbclick(function (event) {
        if (!event.target) {
            // 单击舞台空白处
            $('.node-tooltip').css('display', 'none')
            return;
        }
        self.currentNode = event.target;

        // 只处理双击节点事件
        if (event.target instanceof JTopo.Node && editor.stageMode === 'edit') {
wangqinghua's avatar
wangqinghua committed
            const nodeObj = {
                name:event.target.text,
                hostId:event.target.hostId
            }
wangqinghua's avatar
wangqinghua committed
            localStorage.setItem("node",'true');
wangqinghua's avatar
wangqinghua committed
            localStorage.setItem("nodeObj",JSON.stringify(nodeObj));
wangqinghua's avatar
wangqinghua committed
        } else if (event.target instanceof JTopo.Link && editor.stageMode === 'edit') {
wangqinghua's avatar
wangqinghua committed
            const lineObj = {
wangqinghua's avatar
wangqinghua committed
                hostIds:[],
                itemId:event.target.itemId
            }
wangqinghua's avatar
wangqinghua committed
            lineObj.hostIds.push(event.target.nodeA.hostId);
            lineObj.hostIds.push(event.target.nodeZ.hostId);
wangqinghua's avatar
wangqinghua committed
            localStorage.setItem("line",'true');
wangqinghua's avatar
wangqinghua committed
            localStorage.setItem("linkObj",JSON.stringify(lineObj));
wangqinghua's avatar
wangqinghua committed
        }
wangqinghua's avatar
wangqinghua committed
    });
wangqinghua's avatar
wangqinghua committed

    // 清除起始节点不完整的拖放线
    this.scene.mousedown(function (e) {
        if (self.link && !self.isSelectedMode && (e.target == null || e.target === self.beginNode || e.target === self.link)) {
            this.remove(self.link)
        }
    })

    // 监听鼠标松开事件
    // 处理右键菜单、左键连线
    // event.button: 0-左键 1-中键 2-右键
    this.scene.mouseup(function (event) {
        if (event.target && event.target instanceof JTopo.Node) {
            self.currentNode = event.target
        } else if (event.target && event.target instanceof JTopo.Link) {
            self.currentLink = event.target
        }
        if (event.target && event.target instanceof JTopo.Node && event.target.layout && event.target.layout.on && event.target.layout.type && event.target.layout.type !== 'auto') {
            JTopo.layout.layoutNode(this, event.target, true, event)
        }
        if (event.button === 2) {                      // 右键菜单
            $('div[id$=\'-menu\']').hide()
wangqinghua's avatar
wangqinghua committed
            var menuY = event.layerY ? event.layerY : event.offsetY
            var menuX = event.layerX ? event.layerX : event.offsetX
wangqinghua's avatar
wangqinghua committed
            // 记录鼠标触发位置在canvas中的相对位置
            self.xInCanvas = menuX
            self.yInCanvas = menuY
            if (event.target) {
                if (event.target instanceof JTopo.Node) {          // 处理节点右键菜单事件
wangqinghua's avatar
wangqinghua committed
                    var selectedNodes = self.utils.getSelectedNodes()
wangqinghua's avatar
wangqinghua committed
                    // 如果是节点多选状态弹出分组菜单
                    if (selectedNodes && selectedNodes.length > 1) {
                        // 判断边界出是否能完整显示弹出菜单
                        if (menuX + self.groupMangeMenu.width() >= self.stage.width) {
                            menuX -= self.groupMangeMenu.width()
                        }
                        if (menuY + self.groupMangeMenu.height() >= self.stage.height) {
                            menuY -= self.groupMangeMenu.height()
                        }
                        self.groupMangeMenu.css({
                            top: menuY,
                            left: menuX
                        }).show()
                    } else {
                        // 判断边界出是否能完整显示弹出菜单
                        if (menuX + self.nodeMenu.width() >= self.stage.width) {
                            menuX -= self.nodeMenu.width()
                        }
                        if (menuY + self.nodeMenu.height() >= self.stage.height) {
                            menuY -= self.nodeMenu.height()
                        }
                        self.nodeMenu.css({
                            top: menuY,
                            left: menuX
                        }).show()
                    }
                } else if (event.target instanceof JTopo.Link) {     // 连线右键菜单
                    self.lineMenu.css({
                        top: event.layerY ? event.layerY : event.offsetY,
                        left: event.layerX ? event.layerX : event.offsetX
                    }).show()
                } else if (event.target instanceof JTopo.Container) {        // 容器右键菜单
                    self.containerMangeMenu.css({
                        top: event.layerY ? event.layerY : event.offsetY,
                        left: event.layerX ? event.layerX : event.offsetX
                    }).show()
                }
            } else {
                // 判断边界出是否能完整显示弹出菜单
                if (menuX + self.mainMenu.width() >= self.stage.width) {
                    menuX -= self.mainMenu.width()
                }
                if (menuY + self.mainMenu.height() >= self.stage.height) {
                    menuY -= self.mainMenu.height()
                }
                self.mainMenu.css({
                    top: menuY,
                    left: menuX
                }).show()
            }
        } else if (event.button === 1) {          // 中键

        } else if (event.button === 0) {          // 左键松开事件
            if (event.target != null && event.target instanceof JTopo.Node && !self.isSelectedMode && editor.stageMode === 'edit') {
                if (self.beginNode == null) {
                    // 在起始节点处松开鼠标,创建动态的线条(临时节点A-Z)
                    self.beginNode = event.target
                    if (self.lineType === 'line') {
                        // 直线
                        self.link = new JTopo.Link(self.tempNodeA, self.tempNodeZ)
                        self.link.lineType = 'line'
                    } else if (self.lineType === 'foldLine') {
                        // 折线
                        self.link = new JTopo.FoldLink(self.tempNodeA, self.tempNodeZ)
                        self.link.lineType = 'foldLine'
                        self.link.direction = self.config.linkDirection
                    } else if (self.lineType === 'flexLine') {
                        // 二次折线
                        self.link = new JTopo.FlexionalLink(self.tempNodeA, self.tempNodeZ)
                        self.link.direction = self.config.linkDirection
                        self.link.lineType = 'flexLine'
                    } else if (self.lineType === 'curveLine') {
                        // 曲线
                        self.link = new JTopo.CurveLink(self.tempNodeA, self.tempNodeZ)
                        self.link.lineType = 'curveLine'
                    }
                    self.link.dashedPattern = 2
                    self.link.lineWidth = self.config.linkDefaultWidth
                    self.link.shadow = self.config.linkShadow
                    self.link.strokeColor = JTopo.util.randomColor()
                    this.add(self.link)
                    self.tempNodeA.setLocation(event.x, event.y)
                    self.tempNodeZ.setLocation(event.x, event.y)
                } else if (event.target && event.target instanceof JTopo.Node && self.beginNode !== event.target) {
                    // 在终点节点处松开鼠标,则建立连线
wangqinghua's avatar
wangqinghua committed
                    var endNode = event.target
wangqinghua's avatar
wangqinghua committed
                    // 判断两个节点是否有循环引用
                    /** ***************** 我这里允许循环引用 *************************
                     for (var el = 0; el < endNode.outLinks.length; el++) {
                        // 存在循环引用,线条变红
                        if (endNode.outLinks[el].nodeZ === self.beginNode) {
                            if (self.link)
                                this.remove(self.link);
                            self.beginNode = null;
                            return;
                        }
                    }
                     *****************************************************************/
                    // 判断节点间是否有重复连线,即起点到终点有两条以上连线
                    /** ***************** 我这里允许它有两条连线 *************************
                     for (var el2 = 0; el2 < self.beginNode.outLinks.length; el2++) {
                        // 起始节点已经有一条线指向目标节点
                        if (self.beginNode.outLinks[el2].nodeZ === endNode) {
                            if (self.link)
                                this.remove(self.link);
                            self.beginNode = null;
                            return;
                        }
                    }
                     *****************************************************************/
wangqinghua's avatar
wangqinghua committed
                    var link
wangqinghua's avatar
wangqinghua committed
                    if (self.lineType === 'line') {
                        link = new JTopo.Link(self.beginNode, endNode)
                        link.lineType = 'line'
                    } else if (self.lineType === 'foldLine') {
                        link = new JTopo.FoldLink(self.beginNode, endNode)
                        link.direction = self.config.linkDirection
                        link.bundleOffset = self.config.linkOffsetGap    // 折线拐角处的长度
                        link.lineType = 'foldLine'
                    } else if (self.lineType === 'flexLine') {
                        link = new JTopo.FlexionalLink(self.beginNode, endNode)
                        link.direction = self.config.linkDirection
                        link.lineType = 'flexLine'
                        link.offsetGap = self.config.linkOffsetGap
                    } else if (self.lineType === 'curveLine') {
                        link = new JTopo.CurveLink(self.beginNode, endNode)
                        link.lineType = 'curveLine'
                    }
                    // 保存线条所连接的两个节点ID
                    link.nodeSrc = self.beginNode.nodeId
                    link.nodeDst = endNode.nodeId
                    if (self.lineType !== 'curveLine') {
                        link.arrowsRadius = self.config.arrowsRadius
                    }
wangqinghua's avatar
wangqinghua committed
                    link.id = generateUUID();
wangqinghua's avatar
wangqinghua committed
                    link.strokeColor = self.config.linkStrokeColor
                    link.lineWidth = self.config.linkDefaultWidth
                    this.add(link)
                    self.beginNode = null
                    this.remove(self.link)
                    self.link = null
                } else {
                    self.beginNode = null
                }
            } else {
                if (self.link) {
                    this.remove(self.link)
                }
                self.beginNode = null
            }
        }
    })

    // 动态更新连线坐标(创建连线时的临时节点A-Z)
    this.scene.mousemove(function (event) {
        if (!self.isSelectedMode && self.beginNode) {
            self.tempNodeZ.setLocation(event.x, event.y)
        }
    })

    this.scene.mousedrag(function (event) {
        if (!self.isSelectedMode && self.beginNode) {
            self.tempNodeZ.setLocation(event.x, event.y)
        }
    })

    // 单击编辑器隐藏右键菜单
    this.stage.click(function (event) {
        if (event.button === 0) {
            // 关闭弹出菜单(div)
            $('div[id$=\'-menu\']').hide()
        }
    })

    // 鼠标移出舞台
    this.stage.mouseout(function (event) {
        // 删掉节点带出来的连线
        if (self.link && !self.isSelectedMode && (event.target == null || event.target === self.beginNode || event.target === self.link)) {
            self.scene.remove(self.link)
        }
    })

    // 按下ctrl进入多选模式,此时选择节点不能画线
    $(document).keydown(function (e) {
        if (e.shiftKey) {  // 组合键模式
            switch (e.which) {
xiaowenjie's avatar
xiaowenjie committed
                // 放大 shift+=
wangqinghua's avatar
wangqinghua committed
                case 187:
                case 61:
                    // 单个节点可以撤销操作
                    if (editor.currentNode instanceof JTopo.Node) {
                        // 保存初始状态
                        editor.utils.saveNodeInitState()
                        editor.utils.scalingBig()
                        editor.utils.saveNodeNewState()
                    } else {
                        editor.utils.scalingBig()
                    }
                    break
xiaowenjie's avatar
xiaowenjie committed
                // 缩小 shift+-
wangqinghua's avatar
wangqinghua committed
                case 189:
                case 173:
                    if (editor.currentNode instanceof JTopo.Node) {
                        // 保存初始状态
                        editor.utils.saveNodeInitState()
                        editor.utils.scalingSmall()
                        editor.utils.saveNodeNewState()
                    } else {
                        editor.utils.scalingSmall()
                    }
                    break
                case 70:
xiaowenjie's avatar
xiaowenjie committed
                    // shift+f 全屏显示
wangqinghua's avatar
wangqinghua committed
                    editor.utils.showInFullScreen(editor.stage.canvas, 'RequestFullScreen')
                    break
                case 72:
                    // h 帮助
                    // alert('帮助文档')
                    break
                case 71:
xiaowenjie's avatar
xiaowenjie committed
                    // shift+g 居中显示
wangqinghua's avatar
wangqinghua committed
                    editor.utils.showInCenter()
                    break
                case 73:
                    // shif+I 逆时针旋转
                    if (editor.currentNode instanceof JTopo.Node) {
                        editor.utils.saveNodeInitState()
                        editor.utils.rotateSub()
                        editor.utils.saveNodeNewState()
                    }
                    break
                case 67:
                    editor.utils.cloneSelectedNodes()
                    break
                case 80:
xiaowenjie's avatar
xiaowenjie committed
                    // shif + p
wangqinghua's avatar
wangqinghua committed
                    editor.utils.showPic()
                    break
                case 82:
                    // 单个节点重做
                    if (editor.currentNode instanceof JTopo.Node) {
                        editor.utils.reMakeNodeAction()
                    }
                    break
                case 83:
xiaowenjie's avatar
xiaowenjie committed
                    // shif+s 保存
wangqinghua's avatar
wangqinghua committed
                    editor.saveTopology(true)
                    break
                case 85:
                    // shif+U 顺时针旋转
                    if (editor.currentNode instanceof JTopo.Node) {
                        editor.utils.saveNodeInitState()
                        editor.utils.rotateAdd()
                        editor.utils.saveNodeNewState()
                    }
                    break
                case 87:
                    // alert('ctrl + w 另存为')
                    break
                case 89:
                    // ctrl+y
                    editor.utils.clearTopology()
                    break
                case 90:
                    // 单个节点撤销
                    if (editor.currentNode instanceof JTopo.Node) {
                        editor.utils.cancleNodeAction()
                    }
                    break
            }
        } else if (e.which === 46) {              // 单独按下delete
            editor.utils.deleteSelectedNodes()
        } else if (e.which === 17) {              // 单独按下ctrl
            self.isSelectedMode = true
        }
    })
    $(document).keyup(function (e) {
        if (e.which === 17) {
            self.isSelectedMode = false
            return false
        }
    })
    // 第一次进入拓扑编辑器,生成stage和scene对象
    if (topologyJson === '-1') {
        this.saveTopology(false)
    }
}

/**
 * 图元拖放功能实现
 * @param modeDiv 备选列表中的元素(各种样式的节点)
 * @param drawArea 舞台区域
 */
TopologyEditor.prototype.drag = function (modeDiv, drawArea, text) {
    if (!text) text = ''
wangqinghua's avatar
wangqinghua committed
    var self = this
wangqinghua's avatar
wangqinghua committed
    // 拖拽开始,携带必要的参数
    modeDiv.ondragstart = function (event) {
        event = event || window.event
wangqinghua's avatar
wangqinghua committed
        var dragSrc = this
        var backImg = $(dragSrc).find('img').eq(0).attr('data-src')
wangqinghua's avatar
wangqinghua committed
        backImg = backImg.substring(backImg.lastIndexOf('/') + 1)
wangqinghua's avatar
wangqinghua committed
        var nodeType = $(this).attr('topo-nodetype')
wangqinghua's avatar
wangqinghua committed
        try {