Parcourir la source

Merge branch 'master' of http://192.168.1.220:10080/esb/esb

huangzhiyong il y a 8 ans
Parent
commit
b044e02d56
23 fichiers modifiés avec 1776 ajouts et 2022 suppressions
  1. 1 1
      hos-arbiter/src/main/java/com/yihu/hos/arbiter/routers/SerivceHealthRouter.java
  2. 1 1
      hos-arbiter/src/main/java/com/yihu/hos/arbiter/services/BrokerServerService.java
  3. 5 0
      hos-arbiter/src/main/java/com/yihu/hos/arbiter/services/EndpointService.java
  4. 1 1
      hos-arbiter/src/main/java/com/yihu/hos/arbiter/services/ServiceFlowService.java
  5. 10 2
      hos-arbiter/src/main/resources/application.yml
  6. 1 1
      hos-broker/src/main/resources/application.yml
  7. 4 4
      hos-camel/src/main/java/crawler/route/CrawlerRouteBulider.java
  8. 1 1
      hos-camel/src/main/java/crawler/route/QuartzRoute.java
  9. 1 1
      hos-camel/src/main/java/crawler/route/RouteBulider1.java
  10. 9 9
      src/main/java/com/yihu/hos/common/graph/DGraphImpl.java
  11. 24 9
      src/main/java/com/yihu/hos/system/controller/ProcessController.java
  12. 8 0
      src/main/java/com/yihu/hos/system/dao/FlowProcessDao.java
  13. 18 0
      src/main/java/com/yihu/hos/system/model/ProcessResultModel.java
  14. 14 5
      src/main/java/com/yihu/hos/system/model/SystemServiceFlowProcess.java
  15. 25 26
      src/main/java/com/yihu/hos/system/service/FlowManager.java
  16. 78 45
      src/main/java/com/yihu/hos/system/service/ProcessEditor.java
  17. 87 18
      src/main/java/com/yihu/hos/system/service/ProcessManager.java
  18. 8 3
      src/main/resources/resource/SystemServiceFlowProcess.hbm.xml
  19. 4 1
      src/main/webapp/WEB-INF/ehr/jsp/system/flow/flowJs.jsp
  20. 1 1
      src/main/webapp/WEB-INF/ehr/jsp/system/process/process.jsp
  21. 10 230
      src/main/webapp/WEB-INF/ehr/jsp/system/process/processJs.jsp
  22. 22 0
      src/main/webapp/develop/lib/gooflow/css/GooFlow.css
  23. 1443 1663
      src/main/webapp/develop/lib/gooflow/js/GooFlow.js

+ 1 - 1
hos-arbiter/src/main/java/com/yihu/hos/arbiter/routers/SerivceHealthRouter.java

@ -6,7 +6,7 @@ import org.springframework.stereotype.Component;
/**
 * @created Airhead 2016/8/1.
 */
@Component
//@Component
public class SerivceHealthRouter extends RouteBuilder {
    @Override
    public void configure() throws Exception {

+ 1 - 1
hos-arbiter/src/main/java/com/yihu/hos/arbiter/services/BrokerServerService.java

@ -64,7 +64,7 @@ public class BrokerServerService {
        WriteResult writeResult = mongoOperations.upsert(query, update, BrokerServer.class);
        if (writeResult.isUpdateOfExisting()) {
            //避免Broker重启的情况
            HTTPResponse response = HttpClientKit.post(brokerServer.getURL() + "/esb/heartbeat");
            HTTPResponse response = HttpClientKit.get(brokerServer.getURL() + "/esb/heartbeat");
            if (response.getStatusCode() == 200 && brokerServer.isRegistered()) {
                return;
            }

+ 5 - 0
hos-arbiter/src/main/java/com/yihu/hos/arbiter/services/EndpointService.java

@ -1,6 +1,7 @@
package com.yihu.hos.arbiter.services;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.yihu.hos.core.datatype.StringUtil;
import com.yihu.hos.core.http.HTTPResponse;
import com.yihu.hos.core.http.HttpClientKit;
import com.yihu.hos.web.framework.model.bo.Endpoint;
@ -102,6 +103,10 @@ public class EndpointService {
     */
    private void remoteCheck(Endpoint endpoint) {
        String url = endpoint.getHealthCheckURL();
        if (StringUtil.isEmpty(url)) {
            return;
        }
        HTTPResponse response = HttpClientKit.get(url);
        if (response.getStatusCode() == 200) {
            return;

+ 1 - 1
hos-arbiter/src/main/java/com/yihu/hos/arbiter/services/ServiceFlowService.java

@ -321,7 +321,7 @@ public class ServiceFlowService {
                BrokerServer brokerServer = objectMapper.readValue(msg, BrokerServer.class);
                String serviceFlowMsg = objectMapper.writeValueAsString(serviceFlow);
                boolean result = sendMessage(brokerServer, "post", "/esb/serviceFlow/serverServiceFlow", serviceFlowMsg);
                boolean result = sendMessage(brokerServer, "post", "/esb/serviceFlow/start", serviceFlowMsg);
                if (!result) {
                    logger.error("sendMessage to broker start failed, broker:" + brokerServer.getURL() + ", msg:" + serviceFlowMsg);

+ 10 - 2
hos-arbiter/src/main/resources/application.yml

@ -22,7 +22,9 @@ arbiter:
  timer:
    period: 10000
  central:
    url: 192.168.131.105:15555
    url: localhost:15555
  terminal:
    url: localhost:15555
  tenant:
    name: jkzl
@ -46,6 +48,8 @@ arbiter:
    period: 10000
  central:
    url: 172.17.110.202:15555
  terminal:
    url: 172.17.110.202:15555
  tenant:
    name: jkzl
  mycat:
@ -69,7 +73,9 @@ arbiter:
  timer:
      period: 10000
  central:
    url:  192.168.131.38:15555
    url:
  terminal:
    url: 192.168.131.38:15555
  tenant:
    name: yichang
---
@ -92,6 +98,8 @@ arbiter:
      period: 10000
  central:
    url: 192.168.131.38:15555
  terminal:
    url: 192.168.131.38:15555
  tenant:
    name: jkzl
  mycat:

+ 1 - 1
hos-broker/src/main/resources/application.yml

@ -48,7 +48,7 @@ spring:
    port: 8066
hos:
  esb:
    rest-url: http://192.168.131.119:8080/esb
    rest-url: http://localhost:8080/esb
  arbiter:
    enable: true
    url: http://localhost:10135

+ 4 - 4
hos-camel/src/main/java/crawler/route/CrawlerRouteBulider.java

@ -13,19 +13,19 @@ public class CrawlerRouteBulider extends RouteBuilder {
    public void configure() throws Exception {
        from("jetty:http://192.168.131.96:8066/crawlerPull").routeId("crawlerPull")
                .process(new DefaultHttpProcessor()).setHeader(Exchange.HTTP_METHOD, constant("POST"))
                .process(new DefaultHttpProcessor("{\"jobId\":\"5ad5c11655d443c30155d477a6b10000\"}")).setHeader(Exchange.HTTP_METHOD, constant("POST"))
                .to("http://192.168.131.96:8088/crawler/patientList");
        from("jetty:http://192.168.131.96:8066/crawlerPush").routeId("crawlerPush")
                .process(new DefaultHttpProcessor()).setHeader(Exchange.HTTP_METHOD, constant("POST"))
                .process(new DefaultHttpProcessor("{\"jobId\":\"5ad5c11655d443c30155d477a6b10000\"}")).setHeader(Exchange.HTTP_METHOD, constant("POST"))
                .to("http://192.168.131.96:8088/crawler/patient");
        from("jetty:http://192.168.131.96:8066/crawlerFlowPull").routeId("crawlerFlowPull")
                .process(new DefaultHttpProcessor()).setHeader(Exchange.HTTP_METHOD, constant("POST"))
                .process(new DefaultHttpProcessor("{\"jobId\":\"5ad5c11655d443c30155d477a6b10000\"}")).setHeader(Exchange.HTTP_METHOD, constant("POST"))
                .to("http://192.168.131.96:8088/crawler/patientListFlow");
        from("jetty:http://192.168.131.96:8066/crawlerFlowPush").routeId("crawlerFlowPush")
                .process(new DefaultHttpProcessor()).setHeader(Exchange.HTTP_METHOD, constant("POST"))
                .process(new DefaultHttpProcessor("{\"jobId\":\"5ad5c11655d443c30155d477a6b10000\"}")).setHeader(Exchange.HTTP_METHOD, constant("POST"))
                .to("http://192.168.131.96:8088/crawler/patientFlow");
    }
}

+ 1 - 1
hos-camel/src/main/java/crawler/route/QuartzRoute.java

@ -15,7 +15,7 @@ public class QuartzRoute extends RouteBuilder {
        from("quartz://myGroupName/myTimerName?cron=0/5+*+*+*+*+?")
                .process(new DefaultHttpProcessor() {
                .process(new DefaultHttpProcessor("{\"jobId\":\"5ad5c11655d443c30155d477a6b10000\"}") {
                    @Override
                    public void process(Exchange exchange) throws Exception {
                        System.out.println("I'm running every 5 sec...Change by XXXX===========");

+ 1 - 1
hos-camel/src/main/java/crawler/route/RouteBulider1.java

@ -15,7 +15,7 @@ public class RouteBulider1 extends RouteBuilder {
        from("quartz://myGroup/myTimerName?cron=0/5 * * * * ? ").routeId("list")
                .to("http://localhost:9999/list")
                .split().method(Split.class, "splitJsonArray")
                .process(new HttpProcessor()).to("http://localhost:9999/str")
                .process(new HttpProcessor("str")).to("http://localhost:9999/str")
                .process(new AggregateProcessor("key1"))
                .aggregate(header("test_correlation_key"), new Aggregate()).completionSize(3).to("stream:out");

+ 9 - 9
src/main/java/com/yihu/hos/common/graph/DGraphImpl.java

@ -92,12 +92,12 @@ public class DGraphImpl<V> implements IDGraph<V> {
    /**
     * 广度优先的迭代器
     */
    private class BFSIterator implements Iterator<V> {
    public class BFSIterator implements Iterator<V> {
        /**已访问过的顶点列表*/
        private List<V> mVisitList = null;
        /**待访问的顶点队列*/
        private Queue<V> mVQueue = null;
        
        /**
         * 构造广度优先迭代器
         * @param root
@ -105,11 +105,11 @@ public class DGraphImpl<V> implements IDGraph<V> {
        public BFSIterator(V root) {
            mVisitList = new LinkedList<V>();
            mVQueue = new LinkedList<V>();
            
            //将初始节点入队列
            mVQueue.offer(root);
        }
        
        @Override
        public boolean hasNext() {
            if(mVQueue.size() > 0) {
@ -123,7 +123,7 @@ public class DGraphImpl<V> implements IDGraph<V> {
        public V next() {
            //1.取队列元素
            V v = mVQueue.poll();
            
            if(v != null) {
                //2.将此元素的邻接边中对应顶点入队列,这些顶点需要符合以下条件:
                //1)没访问过;
@ -139,11 +139,11 @@ public class DGraphImpl<V> implements IDGraph<V> {
                        }
                    }
                }
                
                //3.将此顶点添加到已访问过的顶点列表中
                mVisitList.add(v);
            }
            
            //4.返回出队列的元素
            return v;
        }
@ -152,9 +152,9 @@ public class DGraphImpl<V> implements IDGraph<V> {
        public void remove() {
            // 暂时不实现
        }
        
    }
    
    /**顶点列表,由于会经常进行插入删除,使用链表队列*/
    private LinkedList<VE> mVEList;
    /**

+ 24 - 9
src/main/java/com/yihu/hos/system/controller/ProcessController.java

@ -1,6 +1,5 @@
package com.yihu.hos.system.controller;
import com.yihu.hos.core.datatype.StringUtil;
import com.yihu.hos.system.service.ProcessManager;
import com.yihu.hos.web.framework.model.Result;
import com.yihu.hos.web.framework.util.controller.BaseController;
@ -24,16 +23,17 @@ public class ProcessController  extends BaseController {
     * @return
     */
    @RequestMapping("/initial")
    public String processInitial(Model model) {
    public String processInitial(Model model, Integer flowId) {
        model.addAttribute("flowId", flowId);
        model.addAttribute("contentPage", "system/process/process");
        return "partView";
    }
    @RequestMapping(value = "/getAllEndpoints", method = RequestMethod.GET)
    @ResponseBody
    public Result getEndpoints(String endpointName) {
    public Result getAllEndpoints(String endpointName) {
        try {
            return processManager.getEndpoints(endpointName);
            return processManager.getAllEndpoints(endpointName);
        } catch (Exception e) {
            return Result.error("查找端点失败");
        }
@ -49,17 +49,32 @@ public class ProcessController  extends BaseController {
        }
    }
    @RequestMapping(value = "/chart", method = RequestMethod.GET)
    @ResponseBody
    public Result getFlowchart(Integer flowId) {
        try {
            if (flowId != null) {
                String flowchart = processManager.getFlowchart(flowId);
                return Result.success(flowchart);
            } else {
                return Result.success("无初始化数据");
            }
        } catch (Exception e) {
            return Result.error("生成业务流程失败");
        }
    }
    @RequestMapping(value = "/json", method = RequestMethod.POST)
    @ResponseBody
    public Result formatJson(String code, String name, String positionJson, String flowJson) {
        try {
            String fileName = processManager.generateFile(code, name, flowJson);
            if (StringUtil.isEmpty(fileName)) {
                return Result.error("生成业务流程失败");
            Integer flowId = processManager.generateFlow(code, name, flowJson);
            if (flowId == null) {
                return Result.error("生成业务流程失败,请确认流程图是否编辑错误");
            }
            processManager.saveProcess(code, name, fileName, positionJson);
            processManager.saveProcess(code, name, flowId, positionJson, flowJson);
            return Result.error("生成业务流程成功");
            return Result.success("生成业务流程成功");
        } catch (Exception e) {
            return Result.error("生成业务流程失败");
        }

+ 8 - 0
src/main/java/com/yihu/hos/system/dao/FlowProcessDao.java

@ -23,4 +23,12 @@ public class FlowProcessDao extends SQLGeneralDAO {
        }
        return null;
    }
    public SystemServiceFlowProcess getFlowProcessByFlowId(Integer flowId) throws Exception {
        List<SystemServiceFlowProcess> list = (List<SystemServiceFlowProcess>) super.hibernateTemplate.find("from SystemServiceFlowProcess s where flowId=? ", flowId);
        if (!CollectionUtil.isEmpty(list)) {
            return list.get(0);
        }
        return null;
    }
}

+ 18 - 0
src/main/java/com/yihu/hos/system/model/ProcessResultModel.java

@ -8,6 +8,8 @@ public class ProcessResultModel implements java.io.Serializable {
	private String name;
	private String value;
	private String config;
	private String nodeType;
	public String getName() {
		return name;
@ -24,4 +26,20 @@ public class ProcessResultModel implements java.io.Serializable {
	public void setValue(String value) {
		this.value = value;
	}
	public String getConfig() {
		return config;
	}
	public void setConfig(String config) {
		this.config = config;
	}
	public String getNodeType() {
		return nodeType;
	}
	public void setNodeType(String nodeType) {
		this.nodeType = nodeType;
	}
}

+ 14 - 5
src/main/java/com/yihu/hos/system/model/SystemServiceFlowProcess.java

@ -4,8 +4,9 @@ public class SystemServiceFlowProcess implements java.io.Serializable {
    private Integer id;
    private String code;
    private String name;
    private String fileName;
    private Integer flowId;
    private String result;
    private String flowResult;
    public Integer getId() {
        return id;
@ -39,11 +40,19 @@ public class SystemServiceFlowProcess implements java.io.Serializable {
        this.result = result;
    }
    public String getFileName() {
        return fileName;
    public String getFlowResult() {
        return flowResult;
    }
    public void setFileName(String fileName) {
        this.fileName = fileName;
    public void setFlowResult(String flowResult) {
        this.flowResult = flowResult;
    }
    public Integer getFlowId() {
        return flowId;
    }
    public void setFlowId(Integer flowId) {
        this.flowId = flowId;
    }
}

+ 25 - 26
src/main/java/com/yihu/hos/system/service/FlowManager.java

@ -644,7 +644,7 @@ public class FlowManager {
        return null;
    }
    public Boolean sendAddRoute(String code, String name, String javaName, String packageName, String fileInfo) throws Exception {
    public Integer sendAddRoute(String code, String name, String javaName, String packageName, String fileInfo) throws Exception {
        String fileName = javaName + ".java";
        String filePath = this.getClass().getProtectionDomain().getClassLoader().getResource("").getPath() + "temp/" + fileName;
@ -652,20 +652,20 @@ public class FlowManager {
        if (succ) {
            fileName = GridFSUtil.uploadFile(filePath, fileName, null);
        } else {
            return false;
            return null;
        }
        SystemServiceFlow systemServiceFlow = new SystemServiceFlow();
        systemServiceFlow.setName(name);
        systemServiceFlow.setCode(code);
        systemServiceFlow.setCreateDate(new Date());
        systemServiceFlow.setValid(1);
        systemServiceFlow.setFileType(ServiceFlowConstant.JAVA);
        flowDao.saveEntity(systemServiceFlow);
        if (fileName != null) {
            SystemServiceFlow systemServiceFlow = new SystemServiceFlow();
            systemServiceFlow.setName(name);
            systemServiceFlow.setCode(code);
            systemServiceFlow.setCreateDate(new Date());
            systemServiceFlow.setValid(1);
            systemServiceFlow.setFileType(ServiceFlowConstant.JAVA);
            flowDao.saveEntity(systemServiceFlow);
        String enFileName = DES.encrypt(fileName, DES.COMMON_PASSWORD);//加密文件名
            String enFileName = DES.encrypt(fileName, DES.COMMON_PASSWORD);//加密文件名
        if (fileName != null) {
            ServiceFlow serviceFlow = new ServiceFlow();
            serviceFlow.setRouteCode(code);
            serviceFlow.setFlowType(ServiceFlowConstant.CLASS);
@ -679,24 +679,23 @@ public class FlowManager {
            ArrayList<ServiceFlow.HandleFile> handleFiles = new ArrayList<>();
            handleFiles.add(handleFile);
            serviceFlow.setHandleFiles(handleFiles);
            serviceFlowEventService.serviceFlowModifiedAdd(serviceFlow);
            serviceFlowEventService.serviceFlowAdded(serviceFlow);
            String enClassName = DES.encrypt(javaName + ".class", DES.COMMON_PASSWORD);//生成加密过的classPath
            SystemServiceFlowClass flowClass = new SystemServiceFlowClass();
            flowClass.setPackageName(packageName);
            flowClass.setClassName(javaName);
            flowClass.setClassPath(enClassName);
            flowClass.setFlowId(systemServiceFlow.getId());
            flowClass.setType(ServiceFlowConstant.FLOW_TYPE_ROUTE);
            flowClass.setIsUpdate("1");
            flowClassDao.saveEntity(flowClass);
            return systemServiceFlow.getId();
        } else {
            System.out.println("生成route的java文件过程出错");
            return false;
            return null;
        }
        String enClassName = DES.encrypt(javaName + ".class", DES.COMMON_PASSWORD);//生成加密过的classPath
        SystemServiceFlowClass flowClass = new SystemServiceFlowClass();
        flowClass.setPackageName(packageName);
        flowClass.setClassName(javaName);
        flowClass.setClassPath(enClassName);
        flowClass.setFlowId(systemServiceFlow.getId());
        flowClass.setType(ServiceFlowConstant.FLOW_TYPE_ROUTE);
        flowClass.setIsUpdate("1");
        flowClassDao.saveEntity(flowClass);
        return true;
    }
    public Integer sendAddProcessor(String jobId, Integer flowId, Long timestamp) throws Exception {

+ 78 - 45
src/main/java/com/yihu/hos/system/service/ProcessEditor.java

@ -1,8 +1,9 @@
package com.yihu.hos.system.service;
import com.fasterxml.jackson.databind.JsonNode;
import com.yihu.hos.common.graph.IDGraph;
import com.yihu.hos.common.graph.Edge;
import com.yihu.hos.common.graph.IDGraph;
import com.yihu.hos.core.datatype.CollectionUtil;
import com.yihu.hos.core.datatype.StringUtil;
import java.io.IOException;
@ -19,19 +20,19 @@ public class ProcessEditor {
    private Iterator<String> nodeIt;
    private IDGraph<String> mDG;
    private StringBuilder bodyBuilder = new StringBuilder();
    private StringBuilder packageBuilder = new StringBuilder();
    private List<String> packageList = new ArrayList<>();
    private StringBuilder otherRouteBuilder = new StringBuilder();
    private StringBuilder preferBuilder = new StringBuilder();
    public String generate(String javaName, String packageName) throws IOException {
        Boolean isFirstNodeFlg = true;
        StringBuilder allBuilder = new StringBuilder();
        packageBuilder.append("package " + packageName + ";\n\n");
//        packageBuilder.append("import org.apache.camel.Exchange;\n");
        packageBuilder.append("import org.apache.camel.builder.RouteBuilder;\n");
        addPackageList("import org.apache.camel.builder.RouteBuilder;\n");
        bodyBuilder.append("public class "+javaName+" extends RouteBuilder {\n");
        bodyBuilder.append("public void configure() throws Exception {\n");
        preferBuilder.append("public class "+javaName+" extends RouteBuilder {\n");
        preferBuilder.append("public void configure() throws Exception {\n");
        String nodeName = "";
        while(nodeIt.hasNext()) {
            if (isFirstNodeFlg) {
@ -40,6 +41,9 @@ public class ProcessEditor {
                bodyBuilder.append(montage(nodeName));
            } else {
                List<Edge<String>> edgeList = mDG.getEdgeList(nodeName);
                if (CollectionUtil.isEmpty(edgeList)) {
                    nodeIt.next();
                }
                for (Edge<String> edge : edgeList) {
                    nodeIt.next();
                    nodeName = edge.getDest();
@ -50,7 +54,10 @@ public class ProcessEditor {
        bodyBuilder.append(";");
        bodyBuilder.append(otherRouteBuilder);
        bodyBuilder.append("\n}\n}");
        allBuilder.append(packageBuilder).append(bodyBuilder);
        StringBuilder packageBuilder = new StringBuilder();
        packageBuilder.append("package " + packageName + ";\n\n");
        packageList.forEach(packageBuilder::append);
        allBuilder.append(packageBuilder).append(preferBuilder).append(bodyBuilder);
        System.out.println(allBuilder.toString());
        return allBuilder.toString();
    }
@ -58,44 +65,57 @@ public class ProcessEditor {
    public StringBuilder montage(String nodeName) {
        JsonNode node = nodeMap.get(nodeName);
        String nodeType = node.get("nodeType").asText();
        String value = node.get("value").asText();
        String name = node.get("name").asText();
        String config = node.get("config").asText();
//        String name = node.get("name").asText();
        StringBuilder bodyBuilderTemp = new StringBuilder();
        if (nodeType.equals("default")) {
        if (nodeType.equals("start")) {
            bodyBuilder.append("from(\"");
            bodyBuilder.append(value + "\")");
            bodyBuilder.append(config + "\")");
            if (!StringUtil.isEmpty(code)) {
                bodyBuilder.append(".routeId(\""+code+"\")");
            }
        } else if (nodeType.equals("processor")) {
            String value = node.get("value").asText();
            String className = value.substring(value.lastIndexOf(".") + 1, value.length());
            packageBuilder.append("import " + value + ";\n");
            JsonNode param = node.get("param");
            if (param == null) {
                bodyBuilderTemp.append("\n.process(new "+className+"())");
            addPackageList("import " + value + ";\n");
            if (config == null) {
                bodyBuilderTemp.append("\n.process(new "+className+"(\"\"))");
            } else {
                bodyBuilderTemp.append("\n.process(new "+className+"("+param+"))");
                bodyBuilderTemp.append("\n.process(new "+className+"("+config+"))");
            }
        } else if (nodeType.equals("judgement")) {
            judgement(value, nodeName, bodyBuilderTemp);
            judgement(config, nodeName, bodyBuilderTemp);
        } else if (nodeType.equals("circle")) {
            split(value, bodyBuilderTemp);
            split(config, bodyBuilderTemp);
        } else if (nodeType.equals("aggregate")) {
            aggregate(value, bodyBuilderTemp);
            aggregate(config, bodyBuilderTemp);
        } else if (nodeType.equals("multicast")) {
            multicast(value, nodeName, bodyBuilderTemp);
            multicast(config, nodeName, bodyBuilderTemp);
        } else if (nodeType.equals("format")) {
            format(config, nodeName, bodyBuilderTemp);
        } else {
//                    bodyBuilder.append("\n.setHeader(Exchange.HTTP_METHOD, constant(\"POST\"))");
            bodyBuilderTemp.append("\n.to(\"");
            bodyBuilderTemp.append(value + "\")");
            bodyBuilderTemp.append(config + "\")");
        }
        while (nodeIt.hasNext()) {
            List<Edge<String>> edgeList = mDG.getEdgeList(nodeName);
            if (CollectionUtil.isEmpty(edgeList)) {
                return bodyBuilderTemp;
            }
            for (Edge<String> edge : edgeList) {
                nodeIt.next();
                nodeName = edge.getDest();
                bodyBuilderTemp.append(montage(nodeName));
            }
        }
        return bodyBuilderTemp;
    }
    public void judgement(String value, String nodeName, StringBuilder bodyBuilderTemp) {
        bodyBuilderTemp.append("\n.choice()");
        bodyBuilderTemp.append("\n.when("+value+")");
    public void judgement(String config, String nodeName, StringBuilder builder) {
        builder.append("\n.choice()");
        builder.append("\n.when("+config+")");
        String trueNodeName = "";
        String falseNodeName = "";
        for (Edge<String> edge : mDG.getEdgeList(nodeName)) {
@ -103,36 +123,36 @@ public class ProcessEditor {
            String nextNodeName = edge.getDest();
            String nextLineName = edge.getName();
            JsonNode nextLine = lineMap.get(nextLineName);
            if (nextLine.get("value") != null && nextLine.get("value").asText().equals("correct")) {
            if (nextLine.get("config") != null && nextLine.get("config").asText().equals("true")) {
                trueNodeName = nextNodeName;
            } else {
                falseNodeName = nextNodeName;
            }
        }
        JsonNode node1 = nodeMap.get(trueNodeName);
        JsonNode node2 = nodeMap.get(falseNodeName);
        String firstValue = node1.get("value").asText();
        String secondValue =  node2.get("value").asText();
        bodyBuilderTemp.append("\n.to(\"");
        bodyBuilderTemp.append(firstValue + "\")");
        bodyBuilderTemp.append(".otherwise()");
        bodyBuilderTemp.append("\n.to(\"");
        bodyBuilderTemp.append(secondValue + "\")");
        bodyBuilderTemp.append("\n.end()");
        builder.append("\n.to(\"direct:ja\")");
        builder.append("\n.otherwise()");
        builder.append("\n.to(\"direct:jb\")");
        builder.append("\n.end()");
        StringBuilder trueBuilder = montage(trueNodeName);
        StringBuilder falseBuilder = montage(falseNodeName);
        otherRouteBuilder.append("\nfrom(\"direct:ja\")").append(trueBuilder).append(";");
        otherRouteBuilder.append("\nfrom(\"direct:jb\")").append(falseBuilder).append(";");
    }
    public void split(String value, StringBuilder bodyBuilderTemp) {
        packageBuilder.append("import com.yihu.hos.broker.util.Split;\n");
        bodyBuilderTemp.append("\n.split().method(Split.class, \""+value+"\")");
    public void split(String config, StringBuilder builder) {
        addPackageList("import com.yihu.hos.broker.util.Split;\n");
        builder.append("\n.split().method(Split.class, \""+config+"\")");
    }
    public void aggregate(String value, StringBuilder bodyBuilderTemp) {
        packageBuilder.append("import com.yihu.hos.broker.util.Aggregate;\n");
        bodyBuilderTemp.append("\n.aggregate(header(\""+value+"\"), new Aggregate()).completionSize(3)");
    public void aggregate(String config, StringBuilder builder) {
        addPackageList("import com.yihu.hos.broker.util.Aggregate;\n");
        builder.append("\n.aggregate(header(\""+config+"\"), new Aggregate()).completionSize(3)");
    }
    public void multicast(String value, String nodeName, StringBuilder bodyBuilderTemp) {
    public void multicast(String config, String nodeName, StringBuilder builder) {
        String directs = "";
        int i = 'a';
@ -140,7 +160,7 @@ public class ProcessEditor {
        List<Edge<String>> edgeList = mDG.getEdgeList(nodeName);
        for (Edge<String> edge : edgeList) {
            nodeIt.next();
            String direct = "direct:" + (char)i++;
            String direct = "direct:m" + (char)i++;
            directList.add(direct);
            directs += "\"" + direct + "\",";
            StringBuilder directBuilder = new StringBuilder();
@ -152,7 +172,20 @@ public class ProcessEditor {
        directs = StringUtil.substring(directs, 0, directs.length() - 1);
        bodyBuilderTemp.append("\n.multicast().stopOnException().to("+directs+").end()");
        builder.append("\n.multicast().stopOnException().to("+directs+").end()");
    }
    public void format(String config, String nodeName, StringBuilder builder) {
        addPackageList("import org.apache.camel.model.dataformat.XmlJsonDataFormat;\n");
        preferBuilder.append("XmlJsonDataFormat xmlJsonFormat = new XmlJsonDataFormat();\n");
        builder.append("\n."+config + "(xmlJsonFormat)");
    }
    public void addPackageList(String pack) {
        if (!packageList.contains(pack)) {
            packageList.add(pack);
        }
    }
    public void setCode(String code) {

+ 87 - 18
src/main/java/com/yihu/hos/system/service/ProcessManager.java

@ -37,7 +37,7 @@ public class ProcessManager {
    private ObjectMapper objectMapper = new ObjectMapper();
    public Result getEndpoints(String endpointName) throws Exception {
    public Result getAllEndpoints(String endpointName) throws Exception {
        List<SystemServiceEndpoint> serviceEndpointList = appServiceDao.getListByName(endpointName);
        List<String> appIdList = new ArrayList<>();
@ -46,7 +46,10 @@ public class ProcessManager {
            ProcessResultModel resultModel = new ProcessResultModel();
            resultModel.setName(endpoint.getName());
            resultModel.setValue(endpoint.getEndpoint());
            resultModel.setConfig(endpoint.getEndpoint());
            resultModel.setNodeType("service");
            appIdList.add(endpoint.getAppId());
            List<ProcessResultModel> endpointList;
            if (appServiceMap.get(endpoint.getAppId()) != null) {
                endpointList = appServiceMap.get(endpoint.getAppId());
@ -61,10 +64,30 @@ public class ProcessManager {
        for (SystemApp app : appList) {
            JSONObject jsonObject = new JSONObject();
            jsonObject.put("title", app.getName());
            jsonObject.put("list", appServiceMap.get(app.getId()));
            jsonArray.add(jsonObject);
        }
            List<ProcessResultModel> resultModelList = appServiceMap.get(app.getId());
            List<ProcessResultModel> resultModelListListTemp = new ArrayList<>();
            if (app.getName().equals("起始端点")) {
                for (ProcessResultModel resultModel : resultModelList) {
                    resultModel.setNodeType("start");
                    resultModelListListTemp.add(resultModel);
                }
                jsonObject.put("list", resultModelListListTemp);
                jsonArray.add(0, jsonObject);
            } else if (app.getName().equals("结束端点")) {
                for (ProcessResultModel resultModel : resultModelList) {
                    resultModel.setNodeType("end");
                    resultModelListListTemp.add(resultModel);
                }
                jsonObject.put("list", resultModelListListTemp);
                jsonArray.add(1, jsonObject);
            } else {
                resultModelListListTemp = resultModelList;
                jsonObject.put("list", resultModelListListTemp);
                jsonArray.add(jsonObject);
            }
        }
        return Result.success(jsonArray.toString());
    }
@ -75,38 +98,66 @@ public class ProcessManager {
//        return Result.success(result);
//    }
    public String getFlowchart(Integer flowId) throws Exception {
        SystemServiceFlowProcess serviceFlowProcess = processDao.getFlowProcessByFlowId(flowId);
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("code", serviceFlowProcess.getCode());
        jsonObject.put("name", serviceFlowProcess.getName());
        jsonObject.put("flowId", serviceFlowProcess.getFlowId());
        jsonObject.put("result", serviceFlowProcess.getResult());
        return jsonObject.toString();
    }
    public Result getAllProcessor(String processorName) throws Exception {
        List<SystemServiceFlowProcessor> processorList = processorDao.getListByName(processorName);
        List<ProcessResultModel> resultModelList = new ArrayList<>();
        ProcessResultModel json2xml = new ProcessResultModel();
        json2xml.setName("json转xml格式转换器");
        json2xml.setValue("unmarshal");
        json2xml.setConfig("unmarshal");
        json2xml.setNodeType("format");
        ProcessResultModel xml2json = new ProcessResultModel();
        xml2json.setName("xml转json格式转换器");
        xml2json.setValue("marshal");
        xml2json.setConfig("marshal");
        xml2json.setNodeType("format");
        resultModelList.add(json2xml);
        resultModelList.add(xml2json);
        for (SystemServiceFlowProcessor processor : processorList) {
            ProcessResultModel resultModel = new ProcessResultModel();
            resultModel.setName(processor.getName());
            resultModel.setValue(processor.getPackageName() + "." + processor.getClassName());
            resultModel.setConfig("");
            resultModel.setNodeType("processor");
            resultModelList.add(resultModel);
        }
        String result = objectMapper.writeValueAsString(resultModelList);
        return Result.success(result);
    }
    public void saveProcess(String code, String name, String fileName, String positionJsonStr) throws Exception {
    public void saveProcess(String code, String name, Integer flowId, String positionJsonStr, String flowJsonStr) throws Exception {
        SystemServiceFlowProcess process = new SystemServiceFlowProcess();
        process.setCode(code);
        process.setName(name);
        process.setResult(positionJsonStr);
        process.setFileName(fileName);
        process.setFlowId(flowId);
        process.setFlowResult(flowJsonStr);
        processDao.saveEntity(process);
    }
    public static void main(String[] args) throws Exception {
    public void main(String[] args) throws Exception {
        String flowJsonStr = "{\n" +
                "    \"nodes\": {\n" +
                "        \"node_1\": {\n" +
                "            \"name\": \"Quartz\",\n" +
                "            \"value\": \"quartz://myGroup/myTimerName?cron=0/3 * * * * ?\",\n" +
                "            \"nodeType\": \"default\"\n" +
                "            \"nodeType\": \"start\"\n" +
                "        },\n" +
                "        \"node_2\": {\n" +
                "           \"name\": \"常用HTTP转换器\",\n" +
                "            \"name\": \"DefaultHttpProcessor0\",\n" +
                "            \"value\": \"com.yihu.hos.broker.common.processor.DefaultHttpProcessor\",\n" +
                "\t\t\t\"param\": \"{\\\"jobId\\\":\\\"5ad5c11655d443c30155d477a6b10000\\\"}\",\n" +
                "            \"nodeType\": \"processor\"\n" +
@ -115,6 +166,16 @@ public class ProcessManager {
                "            \"name\": \"Crawler0\",\n" +
                "            \"value\": \"http4://localhost:8088/crawler/patientList?method=post\",\n" +
                "            \"nodeType\": \"service\"\n" +
                "        },\n" +
                "\t\t\"node_4\": {\n" +
                "            \"name\": \"xml2json\",\n" +
                "            \"value\": \"marshal\",\n" +
                "            \"nodeType\": \"format\"\n" +
                "        },\n" +
                "\t\t\"node_5\": {\n" +
                "            \"name\": \"Stream\",\n" +
                "            \"value\": \"stream:out\",\n" +
                "            \"nodeType\": \"end\"\n" +
                "        }\n" +
                "    },\n" +
                "    \"lines\": {\n" +
@ -125,13 +186,25 @@ public class ProcessManager {
                "        \"line_2\": {\n" +
                "            \"from\": \"node_2\",\n" +
                "            \"to\": \"node_3\"\n" +
                "        },\n" +
                "        \"line3\": {\n" +
                "            \"from\": \"node_3\",\n" +
                "            \"to\": \"node_4\"\n" +
                "        },\n" +
                "\t\t\"line4\": {\n" +
                "            \"from\": \"node_4\",\n" +
                "            \"to\": \"node_5\"\n" +
                "        }\n" +
                "    }\n" +
                "}";
//        generateFile("crawler", "业务流程图", flowJsonStr);
//        generateFlow("crawler", "业务流程图", flowJsonStr);
    }
    public String generateFile(String code, String name, String flowJsonStr) throws Exception {
    public Integer generateFlow(String code, String name, String flowJsonStr) throws Exception {
        SystemServiceFlowProcess process = processDao.getFlowProcessByCode(code);
        if (process != null) {
            return null;
        }
        String root = "";
        ObjectMapper objectMapper = new ObjectMapper();
        JsonNode flowJson = objectMapper.readValue(flowJsonStr, JsonNode.class);
@ -150,7 +223,7 @@ public class ProcessManager {
            Map.Entry<String, JsonNode> map = nodeIterator.next();
            nodeMap.put(map.getKey(), map.getValue());
            String type = map.getValue().get("nodeType").asText();
            if (StringUtil.isEmpty(root) && type.equals("default")) {
            if (StringUtil.isEmpty(root) && type.equals("start")) {
                root = map.getKey();
            }
            mDG.add(map.getKey());
@ -176,12 +249,8 @@ public class ProcessManager {
        String fileInfo = editor.generate(javaName, packageName);
        Boolean flag = flowManager.sendAddRoute(code, name, javaName, packageName, fileInfo);
        if (flag) {
            return javaName;
        } else {
            return null;
        }
//        Integer flowId = flowManager.sendAddRoute(code, name, javaName, packageName, fileInfo);
        return null;
    }
    //首字母转大写

+ 8 - 3
src/main/resources/resource/SystemServiceFlowProcess.hbm.xml

@ -24,9 +24,14 @@
                <comment>模板编辑结果</comment>
            </column>
        </property>
        <property name="fileName" type="java.lang.String">
            <column name="file_name">
                <comment>生成文件存储名称</comment>
        <property name="flowResult" type="java.lang.String">
            <column name="flow_result">
                <comment>流程编辑结果</comment>
            </column>
        </property>
        <property name="flowId" type="java.lang.Integer">
            <column name="flow_id" length="11">
                <comment>流程ID</comment>
            </column>
        </property>
    </class>

+ 4 - 1
src/main/webapp/WEB-INF/ehr/jsp/system/flow/flowJs.jsp

@ -45,7 +45,7 @@
                        display: '操作', name: 'operator', width: '40%', render: function (row) {
                        var html = '<div class="m-inline-buttons" style="width:350px;vertical-align:middle;">';
                        html += "<a class=\"m-btn\" style=\"padding-right:10px\" onclick=\"flow.showImage('${contextRoot}/app/read/"+row.chart+"')\">查看流程图</a>";
                        html += "<a class=\"m-btn\" style=\"padding-right:10px\" onclick=\"flow.showFlow('"+row.id+"')\">查看流程图</a>";
                        html += "<a class=\"m-btn-edit\" onclick=\"flow.editorDialog('"+row.id+"')\"></a>";
                        html += "<a class=\"m-btn-delete\" onclick=\"flow.delete('"+row.id+"')\"></a>";
                        html += '</div>';
@ -59,6 +59,9 @@
            });
        },
	    showFlow:function (id) {
		    indexPage.openChildPage("",'${contextRoot}/process/initial?flowId='+id);
	    },
        bindEvents: function () {
            var me = this;
            var flag = false;

+ 1 - 1
src/main/webapp/WEB-INF/ehr/jsp/system/process/process.jsp

@ -13,7 +13,7 @@
</style>
<div style="position:relative">
	<div class="back-box">
		<div class="back-box-btn l-button">返回</div>
		<div id="div_back" class="back-box-btn l-button">返回</div>
		返回上一级目录
	</div>
	<div id="esb" class="GooFlow"></div>

+ 10 - 230
src/main/webapp/WEB-INF/ehr/jsp/system/process/processJs.jsp

@ -1,237 +1,17 @@
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="utf-8" %>
<%@include file="/WEB-INF/ehr/commons/jsp/commonInclude.jsp" %>
<link rel="stylesheet" type="text/css" href="${contextRoot}/develop/lib/gooflow/css/GooFlow.css"/>
<script type="text/javascript" src="${contextRoot}/develop/lib/gooflow/js/jquery.min.js"></script>
<script type="text/javascript" src="${contextRoot}/develop/lib/ligerui/ligerui.min.js"></script>
<%--<script type="text/javascript" src="${contextRoot}/develop/lib/gooflow/js/jquery.min.js"></script>--%>
<%--<script type="text/javascript" src="${contextRoot}/develop/lib/ligerui/ligerui.min.js"></script>--%>
<script type="text/javascript" src="${contextRoot}/develop/lib/gooflow/js/GooFlow.js"></script>
<script type="text/javascript">
 	$(function () {
		var demo1 = $('#esb').createGooFlow();
		$('#demo1Export').click(function () {
			console.log(demo1.exportData());
		});
        var data = {
        "nodes": {
        "esb_node_0": {
        "name": "SDf",
        "left": 64,
        "top": 71,
        "type": "task",
        "value": "sd",
        "sType": "service",
        "width": 140,
        "height": 50
        },
        "esb_node_1": {
        "name": "crawler转换器",
        "left": 283,
        "top": 71,
        "type": "plug",
        "value": "CrawlerProcessor0",
        "sType": "processor",
        "width": 140,
        "height": 50
        },
        "esb_node_2": {
        "name": "判断",
        "left": 75,
        "top": 213,
        "type": "state",
        "sType": "judgement",
        "width": 120,
        "height": 70
        },
        "esb_node_3": {
        "name": "循环",
        "left": 100,
        "top": 363,
        "type": "complex",
        "sType": "circle",
        "width": 70,
        "height": 70
        },
        "esb_node_4": {
        "name": "全流程-用户信息",
        "left": 65,
        "top": 560,
        "type": "task",
        "value": "全流程-用户信息",
        "sType": "service",
        "width": 140,
        "height": 50
        },
        "esb_node_5": {
        "name": "全流程-就诊信息",
        "left": 319,
        "top": 435,
        "type": "task",
        "value": "jetty:http://localhost:8080/clinicInfo",
        "sType": "service",
        "width": 140,
        "height": 50
        },
        "esb_node_6": {
        "name": "分流",
        "left": 333,
        "top": 223,
        "type": "fork",
        "sType": "multicast",
        "width": 120,
        "height": 50
        },
        "esb_node_7": {
        "name": "crawler转换器",
        "left": 536,
        "top": 132,
        "type": "plug",
        "value": "CrawlerProcessor0",
        "sType": "processor",
        "width": 140,
        "height": 50
        },
        "esb_node_8": {
        "name": "crawler转换器",
        "left": 540,
        "top": 223,
        "type": "plug",
        "value": "CrawlerProcessor0",
        "sType": "processor",
        "width": 140,
        "height": 50
        },
        "esb_node_9": {
        "name": "crawler转换器",
        "left": 537,
        "top": 311,
        "type": "plug",
        "value": "CrawlerProcessor0",
        "sType": "processor",
        "width": 140,
        "height": 50
        },
        "esb_node_10": {
        "name": "聚合",
        "left": 830,
        "top": 305,
        "type": "join",
        "sType": "aggregate",
        "width": 90,
        "height": 60
        },
        "esb_node_11": {
        "name": "全流程-患者信息",
        "left": 687,
        "top": 453,
        "type": "task",
        "value": "全流程-患者信息",
        "sType": "service",
        "width": 140,
        "height": 50
        },
        "esb_node_12": {
        "name": "全流程-就诊信息",
        "left": 928,
        "top": 452,
        "type": "task",
        "value": "jetty:http://localhost:8080/clinicInfo",
        "sType": "service",
        "width": 140,
        "height": 50
        }
        },
        "lines": {
        "esb_line_0": {
        "type": "sl",
        "from": "esb_node_0",
        "to": "esb_node_1",
        "name": "",
        "marked": false
        },
        "esb_line_1": {
        "type": "tb",
        "M": 171.5,
        "from": "esb_node_1",
        "to": "esb_node_2",
        "name": "",
        "marked": false
        },
        "esb_line_2": {
        "type": "sl",
        "from": "esb_node_2",
        "to": "esb_node_3",
        "name": "自定义内容",
        "marked": false
        },
        "esb_line_3": {
        "type": "tb",
        "from": "esb_node_3",
        "to": "esb_node_5",
        "name": "自定义内容",
        "marked": false,
        "M": 390
        },
        "esb_line_4": {
        "type": "sl",
        "from": "esb_node_3",
        "to": "esb_node_4",
        "name": "自定义内容",
        "marked": false
        },
        "esb_line_5": {
        "type": "sl",
        "from": "esb_node_2",
        "to": "esb_node_6",
        "name": "自定义内容",
        "marked": false
        },
        "esb_line_6": {
        "type": "sl",
        "from": "esb_node_6",
        "to": "esb_node_8",
        "name": "",
        "marked": false
        },
        "esb_line_7": {
        "type": "lr",
        "M": 499.5,
        "from": "esb_node_6",
        "to": "esb_node_7",
        "name": "",
        "marked": false
        },
        "esb_line_8": {
        "type": "lr",
        "M": 500,
        "from": "esb_node_6",
        "to": "esb_node_9",
        "name": "",
        "marked": false
        },
        "esb_line_9": {
        "type": "sl",
        "from": "esb_node_9",
        "to": "esb_node_10",
        "name": "",
        "marked": false
        },
        "esb_line_10": {
        "type": "tb",
        "M": 406.5,
        "from": "esb_node_11",
        "to": "esb_node_10",
        "name": "",
        "marked": false
        },
        "esb_line_11": {
        "type": "tb",
        "M": 406,
        "from": "esb_node_12",
        "to": "esb_node_10",
        "name": "",
        "marked": false
        }
        }
        }
		demo1.loadData(data);
	});
		var esb = $('#esb').createGooFlow({},'${contextRoot}');
	    var id = "${flowId}";
	    var dataGetURL = "/esb/process/chart?flowId="+ id;
	    esb.loadData(dataGetURL);
	    $('#div_back').click(function () {
		    indexPage.loadPage('${contextRoot}/flow/initial');
	    })
    });
</script>

+ 22 - 0
src/main/webapp/develop/lib/gooflow/css/GooFlow.css

@ -709,4 +709,26 @@ v\:group,v\:rect,v\:imagedata,v\:oval,v\:line,v\:polyline,v\:stroke,v\:textbox {
}
.line-error{
    stroke:#f00
}
.file-box{
    position: absolute;
    top: 20px;
    padding-left: 30px;
    left: 50px;
    z-index: 9999;
}
.file-box.error{
    border:2px dashed #f00;
}
.file-box input{
    margin: 20px;
    border: none;
    background: none;
    padding: 5px;
    border-bottom: 1px solid #333;
    
}
.special-error{
    border:2px dashed #f00;
}

+ 1443 - 1663
src/main/webapp/develop/lib/gooflow/js/GooFlow.js

@ -1,31 +1,45 @@
//公共方法
var GooFunc = {
    getElCoordinate: function (dom) {
        var t = dom.offsetTop;
        var l = dom.offsetLeft;
        dom = dom.offsetParent;
        while (dom) {
            t += dom.offsetTop;
            l += dom.offsetLeft;
            dom = dom.offsetParent;
        }
        ;
        return {top: t, left: l};
    },
    mousePosition: function (ev) {
        var dom = document.getElementById('divIndexContent');
        var domX = dom.scrollLeft;
        var domY = dom.scrollTop;
        //兼容各种浏览器的,获取鼠标真实位置
        if (!ev) ev = window.event;
        if (ev.pageX || ev.pageY) {
            return {x: ev.pageX + domX, y: ev.pageY + domY};
        }
        return {
            x: ev.clientX + document.documentElement.scrollLeft - document.body.clientLeft,
            y: ev.clientY + document.documentElement.scrollTop - document.body.clientTop
        };
    }
	getElCoordinate: function (dom) {
		var t = dom.offsetTop;
		var l = dom.offsetLeft;
		dom = dom.offsetParent;
		while (dom) {
			t += dom.offsetTop;
			l += dom.offsetLeft;
			dom = dom.offsetParent;
		};
		return {top: t, left: l};
	},
	mousePosition: function (ev) {
		var dom = document.getElementById('divIndexContent');
		var domX = dom.scrollLeft;
		var domY = dom.scrollTop;
		//兼容各种浏览器的,获取鼠标真实位置
		if (!ev) ev = window.event;
		if (ev.pageX || ev.pageY) {
			return {x: ev.pageX + domX, y: ev.pageY + domY};
		}
		return {
			x: ev.clientX + document.documentElement.scrollLeft - document.body.clientLeft,
			y: ev.clientY + document.documentElement.scrollTop - document.body.clientTop
		};
	},
	getAjaxData: function (url, successFn, completeFn) {
		$.ajax({
			url: url,
			type: 'GET',
			async: true,
			dataType: 'json',
			success: successFn,
			error: function (data, xhr, textStatus) {
				console.log('错误')
				console.log(xhr)
				console.log(textStatus)
			},
			complete: completeFn
		})
	}
}
/*
@ -34,57 +48,74 @@ var GooFunc = {
 * Date: 2017/2/15
 * time: 14:59
 * */
function GooFlow(bgDiv, property) {
function GooFlow(bgDiv, property,url) {
	/*
	 * Todo: 初始化属性设置 【初始化】
	 * Author: LE
	 * Date: 2017/2/15
	 * time: 15:17
	 * */
    // *************************************************** 【UI相关】
	// *************************************************** 【UI相关】
	this.$id = property.id || bgDiv.attr("id"); // 容器id
    this.$bgDiv = bgDiv;                        // 容器对象
    this.$headBox = null;                       // 头部容器对象
	this.$bgDiv = bgDiv;                        // 容器对象
	this.$headBox = null;                       // 头部容器对象
	this.$toolBar = null;                       // 工具栏操作
	this.$flowChart = null;                      // 流程图工具
    this.$contBox = null;                       // 主内容区
	this.$flowChart = null;                     // 流程图工具
	this.$contBox = null;                       // 主内容容器
	this.$contBoxLeft = null;                   // 端点内容
	this.$contBoxRight = null;                  // 转换器内容
	this.$workAreaWrap = null;                  // 流程图区域容器
	this.$workArea = null;                      // 流程图区域
	this.$draw = null;                          // SVG绘制区
	this.$fileCode = '';                        // 流程图文件唯一标识
	this.$fileName = '';                        // 流程图文件名称
	// *************************************************** 【线条相关】
    this.$lineData = {};                        // 数据
	this.$lineData = {};                        // 数据
	this.$lineType = '';                        // 类型
	this.$lineCount = 0;                        // 数量
	this.$lineDom = {};                         // DOM
	this.$lineMoveing = false;                  // 是否在移动
	this.$deteLineId = '';                      // 删除指定ID
	this.$lineMove = null;                      // 线条移动镜像
	this.$canDelLine = false;                   // 是否使用Delete删除线条
	// *************************************************** 【结点相关】
    this.$nodeData = {};                        // 数据
    this.$nodeCount = 0;                        // 数量
	this.$nodeData = {};                        // 数据
	this.$nodeCount = 0;                        // 数量
	this.$nodeDom = {};                         // DOM
	this.$newNodeValue = '';                    // 值
	this.$nodeConfig = '';                    // 配置项
	this.$nodeType = '';                        // 结点类型
	this.$deteNodeId = '';                      // 删除指定ID
	this.$ghost = null;                         // 结点镜像
	// *************************************************** 【当前选中】
	this.$nowType = "cursor";                  // 当前操作类型
    this.$focus = "";                          // SVG操作区中选中对象
	this.$nowType = "cursor";                   // 当前操作类型
	this.$focus = "";                           // SVG操作区中选中对象
	// *************************************************** 【其他】
	this.$MainContener = property.MainContener; // 重要内容
    this.$editable = property.editable;         // 是否可编辑
    this.$max = property.initNum;               // 操作顺序
	this.$editable = property.editable;         // 是否可编辑
	// *************************************************** 【公用变量】
	var that = this;
	// *************************************************** 【请求地址】
	var getAllEndpointsURL = url + "/process/getAllEndpoints",
		getAllProcessorsURL = url + "/process/getAllProcessors",
		dataPostURL = URL + "/process/json";
	/*
	 * Todo: 初始化布局 【初始化】
	 * Author: LE
	 * Date: 2017/2/15
	 * time: 15:17
	 * */
	this.$headBox = $("<div class='GooFlow_top_box'></div>");
	this.$contBox = $("<div class='GooFlow_cont_box'></div>");
	this.$headBox = $("<div class='GooFlow_top_box'></div>");  // 头部
	this.$contBox = $("<div class='GooFlow_cont_box'></div>"); // 中间主体内容
	this.$bgDiv.append(this.$headBox).append(this.$contBox);
	/*
@ -93,276 +124,712 @@ function GooFlow(bgDiv, property) {
	 * Date: 2017/2/15
	 * time: 15:17
	 * */
    if (property.haveTool) {
	    var
		     HtmlStr = "",
		     i = 0,
	         length = property.toolBtns.length;
	    // 工具栏UI构建
        HtmlStr = "<div class='GooFlow_tool'>";
        for (; i < length; ++i) {
            HtmlStr += "<i class='ico_" + property.toolBtns[i] + "'></i>";
        }
        HtmlStr += "</div>";
        this.$toolBar = $(HtmlStr);
        this.$headBox.append(this.$toolBar);
	    // 工具栏功能绑定
        this.$toolBar.children().on("click",function () {
            var icoName = $(this).attr("class").match(/ico_\w+/g)[0];
            switch (icoName) {
                case "ico_save":
                    that.exportData();
                    break;
                case "ico_undo":
                    that.undo();
                    break;
                case "ico_redo":
                    that.redo();
                    break;
            }
	if (property.haveTool) {
		var HtmlStr = "",
			i = 0,
			length = property.toolBtns.length;
		// 工具栏UI构建
		HtmlStr = "<div class='GooFlow_tool'>";
		for (; i < length; ++i) {
			HtmlStr += "<i class='ico_" + property.toolBtns[i] + "'></i>";
		}
		HtmlStr += "</div>";
		this.$toolBar = $(HtmlStr);
		this.$headBox.append(this.$toolBar);
		// 工具栏功能绑定
		this.$toolBar.children().on("click", function () {
			var icoName = $(this).attr("class").match(/ico_\w+/g)[0];
			switch (icoName) {
				case "ico_save":
					that.exportData();
					break;
				case "ico_undo":
					that.undo();
					break;
				case "ico_redo":
					that.redo();
					break;
			}
		});
	}
	/*
	 * Todo: 流程图结点构建 【UI相关--HEAD】
	 * Author: LE
	 * Date: 2017/2/15
	 * time: 15:17
	 * */
	if (property.flowChart) {
		var HtmlStr = "",
			i = 0,
			length = property.flowBtns.length;
		// 流程图结点UI构建
		HtmlStr += "<div class='GooFlow_flowChart'>"
		// 加入用户自定义工具栏
		for (; i < length; ++i) {
			HtmlStr += "<i id='" + this.$id + "_btn_" + property.flowBtns[i] + "' class='ico_" + property.flowBtns[i] + "'/></i>";
		}
		HtmlStr += "</div>"
		this.$flowChart = $(HtmlStr);
		this.$headBox.append(this.$flowChart);
	}
	/*
	 * Todo: 端点及转换器公共筛选方法
	 * Author: LE
	 * Date: 2017/2/20
	 * time: 10:09
	 * */
	function itemSearchFn(obj) {
		var $list = obj.find($('.GooFlow_app_list_item'));
		var $input = obj.find($('.GooFlow_app_search_input'));
		var $item = obj.find($('.GooFlow_app_item'));
		$input.on('keyup', {inthis: that}, function () {
			var input = $input.val()
			if (input.length > 0) {
				$item.hide();
				var filterObj = $item.filter(function (index) {
					return $(this).text().indexOf(input) >= 0;
				})
				if (filterObj.length > 0) {
					filterObj.css("display", "block").parent().parent().parent().addClass('selected');
				} else {
					$item.show();
					$list.removeClass('selected');
				}
			}
			else {
				$item.show();
				$list.removeClass('selected');
			}
		})
	}
	/*
	 * Todo: 端点及转换器公共HTML结构搭建 【UI相关】
	 * Author: LE
	 * Date: 2017/2/20
	 * time: 10:09
	 * */
	function renderHtml(title, className, htmlStr) {
		var appBox = $("<div class='GooFlow_app'></div>");
		var appHead = $("<div class='GooFlow_app_head'><h1 class='GooFlow_app_tit'></h1><div class='GooFlow_app_search'><input class='GooFlow_app_search_input' type='text' placeholder='快速搜索'><div class='GooFlow_search_btn'></div></div></div>");
		var appCont = $("<div class='GooFlow_app_cont'><ul class='GooFlow_app_list'></ul></div>");
		appHead.children('.GooFlow_app_tit').text(title);
		appCont.children('.GooFlow_app_list').append(htmlStr);
		appBox.prepend(appHead).append(appCont).addClass(className);
		return appBox;
	}
	/*
	 * Todo: 端点数据渲染及事件添加 【UI相关】
	 * Author: LE
	 * Date: 2017/2/20
	 * time: 10:09
	 * */
	function getAllEndpointsFn(data) {
		var list = $.parseJSON(data.message),
			htmlStr = '';
		for (var i = 0; i < list.length; i++) {
			htmlStr += "<li class='GooFlow_app_list_item'>";
			htmlStr += "<div class='GooFlow_app_list_tit'>" + list[i].title + "</div>";
			htmlStr += "<div class='GooFlow_app_list_box'><ul>";
			for (var j = 0; j < list[i].list.length; j++) {
				htmlStr += "<li class='GooFlow_app_item' data-type='" + list[i].list[j].nodeType +
					"' data-value ='" + list[i].list[j].value + "' data-config='"  + list[i].list[j].config +
					"'><div class='app_icon'></div> <div class='app_name'>" + list[i].list[j].name + "</div></li>"
			}
			htmlStr += "</ul></li>";
		}
		if (htmlStr) {
			that.$contBoxLeft = renderHtml('端点', 'GooFlow_left', htmlStr);
			that.$contBox.prepend(that.$contBoxLeft)
			// tab标签页
			$('.GooFlow_app_list_tit', that.$contBoxLeft).on("click", function () {
				$('.GooFlow_app_item', that.$contBoxLeft).show();
				$(this).parent().toggleClass('selected').siblings().removeClass('selected');
			});
			// 拖拽
			addNodeByDrag($('.GooFlow_app_item', that.$contBoxLeft), 'task');
			// 筛选
			itemSearchFn(that.$contBoxLeft);
		}
	}
	/*
	 * Todo: 转换器数据渲染及事件添加 【UI相关】
	 * Author: LE
	 * Date: 2017/2/20
	 * time: 10:09
	 * */
	function getAllProcessorsFn(data) {
		var list = $.parseJSON(data.message),
			htmlStr = '';
		htmlStr += "<li class='GooFlow_app_list_item'><div class='GooFlow_app_list_box'><ul>";
		for (var i = 0; i < list.length; i++) {
			htmlStr += "<li class='GooFlow_app_item' data-type='" + list[i].nodeType +
				"' data-value='" + list[i].value + "' data-config='" + list[i].config +
				"'><div class='app_icon'></div> <div class='app_name'>" + list[i].name + "</div></li>"
		}
		htmlStr += "</ul></li>";
		if (htmlStr) {
			that.$contBoxRight = renderHtml('转换器', 'GooFlow_right', htmlStr);
			that.$contBox.prepend(that.$contBoxRight)
			// 拖拽
			addNodeByDrag($('.GooFlow_app_item', that.$contBoxRight), 'plug');
			// 筛选
			itemSearchFn(that.$contBoxRight);
		}
	}
	/*
	 * Todo: 端点及转换数据加载 【ajax相关】
	 * Author: LE
	 * Date: 2017/2/20
	 * time: 10:09
	 * */
	GooFunc.getAjaxData(getAllEndpointsURL, getAllEndpointsFn);
	GooFunc.getAjaxData(getAllProcessorsURL, getAllProcessorsFn);
	/*
	 * Todo: 画布区域HTML结构搭建 【UI相关】
	 * Author: LE
	 * Date: 2017/2/20
	 * time: 10:09
	 * */
	this.initDraw("draw_" + this.$id);
	// 结点镜像 & 文本框
	this.$ghost = $("<div class='rs_ghost GooFlow_item'></div>").attr({
		"unselectable": "on",
		"onselectstart": 'return false',
		"onselect": 'document.selection.empty()'
	});
	this.$textArea = $("<textarea class='text_input'></textarea>");
	this.$bgDiv.append(this.$ghost).append(this.$textArea);
	// 操作折线时的移动框 & 线条增删改操作框
	this.$lineMove = $("<div class='GooFlow_line_move' style='display:none'></div>");
	this.$lineOper = $("<div class='GooFlow_line_oper' style='display:none'><b class='b_l1'></b><b class='b_l2'></b><b class='b_l3'></b><b class='b_x'></b></div>");
	this.$workArea.append(this.$lineMove).append(this.$lineOper);
	/*
	 * Todo: 结点拖拽 【增加结点触发方法】
	 * Author: LE
	 * Date: 2017/2/20
	 * time: 10:03
	 * */
	var onDrag = false;
	function addNodeByDrag(obj, type) {
		obj.mousedown(function () {
			that.$nowType = type;
			if (!type) {
				var thisType = $(this).attr("id").split("btn_")[1];
				that.$nowType = thisType;
			}
			onDrag = true;
			that.$newNodeTxt = $(this).children('.app_name').text();
			that.$newNodeValue = $(this).attr('data-value');
			that.$nodeType = $(this).attr('data-type');
			that.$nodeConfig = $(this).attr('data-config');
		});
		$(document).mouseup(function () {
			onDrag = false;
		});
		that.$workArea.mousemove(function () {
			if (onDrag) {
				var X, Y;
				var ev = GooFunc.mousePosition();
				X = ev.x - 433 + this.parentNode.scrollLeft;
				Y = ev.y - 262 + this.parentNode.scrollTop;
				that.$workArea.children('#node-ghost').attr({class: 'node-' + that.$nowType});
				that.$workArea.css({cursor: 'move'}).children('#node-ghost').css({
					display: "block",
					top: Y + "px",
					left: X + "px",
					cursor: "move",
				});
			}
		})
		that.$workArea.mouseup(function () {
			if (onDrag) {
				addNodeOnArea();
				onDrag = false;
				that.$workArea.css({cursor: 'pointer'}).children('#node-ghost').remove();
			}
		});
		that.$workArea.mouseenter(function () {
			that.$workArea.append("<div id='node-ghost'></div>");
		})
		that.$workArea.mouseleave(function () {
			that.$workArea.css({cursor: 'pointer'}).children('#node-ghost').remove();
		})
		function addNodeOnArea() {
			var X, Y;
			var ev = GooFunc.mousePosition();
			X = ev.x - 433 + that.$workArea[0].parentNode.scrollLeft;
			Y = ev.y - 262 + that.$workArea[0].parentNode.scrollTop;
			that.addNode(that.$id + "_node_" + that.$nodeCount, {
				name: "node_" + that.$nodeCount,
				type: that.$nowType,
				left: X,
				top: Y
			});
		}
	}
	addNodeByDrag(this.$flowChart.children());
	/*
	 * Todo: ### 画布区域功能设置 ******************************************* //
	 * Author: LE
	 * Date: 2017/2/15
	 * time: 15:15
	 * */
	if (this.$editable) {
		// 事件添加
		this.initEvents();
		/*
		 * Todo: 取消&重做函数 【堆栈相关】
		 * Author: LE
		 * Date: 2017/2/15
		 * time: 15:12
		 * */
		if (property.useOperStack) {
			this.$undoStack = [];
			this.$redoStack = [];
			this.$isUndo = 0;
			///////////////以下是构造撤销操作/重做操作的方法
			//为了节省浏览器内存空间,undo/redo中的操作缓存栈,最多只可放40步操作;超过40步时,将自动删掉最旧的一个缓存
			this.pushOper = function (funcName, paras) {
				var len = this.$undoStack.length;
				if (this.$isUndo == 1) {
					this.$redoStack.push([funcName, paras]);
					this.$isUndo = false;
					if (this.$redoStack.length > 40) this.$redoStack.shift();
				} else {
					this.$undoStack.push([funcName, paras]);
					if (this.$undoStack.length > 40) this.$undoStack.shift();
					if (this.$isUndo == 0) {
						this.$redoStack.splice(0, this.$redoStack.length);
					}
					this.$isUndo = 0;
				}
			};
			this.pushExternalOper = function (func, jsonPara) {
				this.pushOper("externalFunc", [func, jsonPara]);
			};
			//撤销上一步操作
			this.undo = function () {
				if (this.$undoStack.length == 0)   return;
				var tmp = this.$undoStack.pop();
				this.$isUndo = 1;
				if (tmp[0] == "externalFunc") {
					tmp[1][0](tmp[1][1]);
				}
				else {
					//传参的数量,最多支持5个.
					switch (tmp[1].length) {
						case 0:
							this[tmp[0]]();
							break;
						case 1:
							this[tmp[0]](tmp[1][0]);
							break;
						case 2:
							this[tmp[0]](tmp[1][0], tmp[1][1]);
							break;
						case 3:
							this[tmp[0]](tmp[1][0], tmp[1][1], tmp[1][2]);
							break;
						case 4:
							this[tmp[0]](tmp[1][0], tmp[1][1], tmp[1][2], tmp[1][3]);
							break;
						case 5:
							this[tmp[0]](tmp[1][0], tmp[1][1], tmp[1][2], tmp[1][3], tmp[1][4]);
							break;
					}
				}
			};
			//重做最近一次被撤销的操作
			this.redo = function () {
				if (this.$redoStack.length == 0)   return;
				var tmp = this.$redoStack.pop();
				this.$isUndo = 2;
				if (tmp[0] == "externalFunc") {
					tmp[1][0](tmp[1][1]);
				}
				else {
					//传参的数量,最多支持5个.
					switch (tmp[1].length) {
						case 0:
							this[tmp[0]]();
							break;
						case 1:
							this[tmp[0]](tmp[1][0]);
							break;
						case 2:
							this[tmp[0]](tmp[1][0], tmp[1][1]);
							break;
						case 3:
							this[tmp[0]](tmp[1][0], tmp[1][1], tmp[1][2]);
							break;
						case 4:
							this[tmp[0]](tmp[1][0], tmp[1][1], tmp[1][2], tmp[1][3]);
							break;
						case 5:
							this[tmp[0]](tmp[1][0], tmp[1][1], tmp[1][2], tmp[1][3], tmp[1][4]);
							break;
					}
				}
			};
		}
	}
}
/*
 * Todo:  ## 原型方法 ***************************************************************************************
 * Author: LE
 * Date: 2017/2/15
 * time: 14:59
 * */
GooFlow.prototype = {
    useSVG: true,
	/*
	 * Todo: 初始化画布 【SVG & 箭头 UI相关】
	 * Author: LE
	 * Date: 2017/2/20
	 * time: 9:51
	 * */
    initDraw: function (id) {
        this.$workAreaWrap = $("<div class='GooFlow_work'></div>");
        this.$workArea = $("<div class='GooFlow_work_inner'></div>").attr({
            "unselectable": "on",
            "onselectstart": 'return false',
            "onselect": 'document.selection.empty()'
        });
    }
    /*
     * Todo: 流程图结点构建 【UI相关--HEAD】
     * Author: LE
     * Date: 2017/2/15
     * time: 15:17
     * */
    if (property.flowChart) {
	    var
		    HtmlStr = "",
		    i = 0,
		    length = property.flowBtns.length;
        // 流程图结点UI构建
        HtmlStr += "<div class='GooFlow_flowChart'>"
        // 加入用户自定义工具栏
        for (; i < length; ++i) {
            HtmlStr += "<i id='" + this.$id + "_btn_" + property.flowBtns[i] + "' class='ico_" + property.flowBtns[i] + "'/></i>";
        this.$workAreaWrap.append(this.$workArea);
        this.$contBox.append(this.$workAreaWrap);
        this.$draw = document.createElementNS("http://www.w3.org/2000/svg", "svg");
        this.$workArea.prepend(this.$draw);
        this.$workArea.prepend("<div class='file-box'>编码:<input id='fileCode' value='" + this.$fileCode +
			"'>流程名称:<input id='fileName' value='" + this.$fileName +
			"'></div>")
        // 设置画布属性
        this.$draw.id = id;
        this.$draw.style.width = '100%';
        this.$draw.style.height = '100%';
        // 定义可以重复利用的元素
        var defs = document.createElementNS("http://www.w3.org/2000/svg", "defs");
        this.$draw.appendChild(defs);
        defs.appendChild(getSvgMarker("arrow1", "#323232"));
        defs.appendChild(getSvgMarker("arrow2", "#00b7ee"));
        defs.appendChild(getSvgMarker("arrow3", "#f00"));
        function getSvgMarker(id, color) {
            var m = document.createElementNS("http://www.w3.org/2000/svg", "marker");
            m.setAttribute("id", id);
            m.setAttribute("viewBox", "0 0 6 6");
            m.setAttribute("refX", 5);
            m.setAttribute("refY", 3);
            m.setAttribute("markerUnits", "strokeWidth");
            m.setAttribute("markerWidth", 8);
            m.setAttribute("markerHeight", 8);
            m.setAttribute("orient", "auto");
            var path = document.createElementNS("http://www.w3.org/2000/svg", "path");
            path.setAttribute("d", "M 0 0 L 6 3 L 0 6  L 2 3 z");
            path.setAttribute("fill", color);
            path.setAttribute("stroke-width", 2);
            m.appendChild(path);
            return m;
        }
	    HtmlStr += "</div>"
	    this.$flowChart = $(HtmlStr);
	    this.$headBox.append(this.$flowChart);
    }
    /*
     * Todo: 初始化工作区 【UI相关】
     * Author: LE
     * Date: 2017/2/15
     * time: 15:18
     * */
    var width = ( property.width == 'auto' ) ? (this.$bgDiv.parent().width()) : property.width;
    var height = property.height;
    var workWidth = workWidth ? property.workWidth : width;
    var workHeight = workHeight ? property.workHeight : height;
    // 创建工作区JQ对象
    /*
     * Todo: 端点及转换数据加载 【ajax相关】
     * Author: LE
     * Date: 2017/2/20
     * time: 10:09
     * */
    function getAppData(url, className, title) {
        $.ajax({
            url: url,
            type: 'GET',
            async: true,
            dataType: 'json',
            success: function (data) {
                var contData = $.parseJSON(data.message);
                appHtml(contData, className, title);
            },
            error: function (data, xhr, textStatus) {
                console.log('错误')
                console.log(xhr)
                console.log(textStatus)
            },
            complete: function () {
                // 数据加载完成后添加端点或转换器点击函数
                completeFn();
    },
	/*
	 * Todo: ### 初始化事件绑定********************************--开始
	 * Author: LE
	 * Date: 2017/2/15
	 * time: 15:09
	 * */
    initEvents: function () {
        // 防止回调函数里this偏移
        var that = this;
        var timeId = null;
		/*
		 * Todo: 空白区域点击事件 【单击事件】
		 * Author: LE
		 * Date: 2017/2/20
		 * time: 10:05
		 * */
        $(this.$draw).on("click", function (e) {
            that.$canDelLine = false;
            var n = $(e.target).prop("tagName");
            if (n == "svg") {
                that.blurItem();
            }
        })
    }
        });
    getAppData('http://192.168.131.110:8080/esb/process/getAllEndpoints', 'GooFlow_left', '端点');
    getAppData('http://192.168.131.110:8080/esb/process/getAllProcessors', 'GooFlow_right', '转换器');
		/*
		 * Todo: 结点事件绑定 【@@】
		 * Author: LE
		 * Date: 2017/2/20
		 * time: 10:22
		 * */
        this.$workArea.on({
            // 选中
            click: function () {
                clearTimeout(timeId);
                var Dom = $(this);
                var id = Dom.attr("id");
                that.focusItem(id, true);
            },
            // 编辑
            dblclick: function () {
                var id = $(this).attr('id'),
                    objPosition = $(this).position(),
                    objLeft = objPosition.left,
                    objTop = objPosition.top,
                    oldTxt = that.$nodeData[id].config;
	                console.log(that.$nodeData[id]);
    /*
     * Todo: 端点及转换器事件添加 【端点&转换器--事件】
     * Author: LE
     * Date: 2017/2/20
     * time: 10:09
     * */
                // 编辑的时候禁止delete键删除
                that.$canDelLine = false;
                that.$deteNodeId = '';
                that.$deteLineId = '';
    function completeFn() {
                clearTimeout(timeId);
                that.$textArea.val(oldTxt).css({
                    display: "block",
                    left: objLeft + 170,
                    top: objTop + 50
                }).data("id", that.$focus).focus();
        addNodeByDrag($('.GooFlow_left .GooFlow_app_item'), 'task');
        addNodeByDrag($('.GooFlow_right .GooFlow_app_item'), 'plug');
                that.$workArea.parent().one("mousedown", function () {
                    that.setName(that.$textArea.data("id"), that.$textArea.val(), "node");
                    that.$textArea.val("").removeData("id").hide();
                });
        $('.GooFlow_left .GooFlow_app_list_tit').on("click", {inthis: that}, function () {
            $('.GooFlow_left .GooFlow_app_item').show();
            $(this).parent().toggleClass('selected').siblings().removeClass('selected');
        });
            },
            // 移动
            mousedown: function (e) {
                if (!e) e = window.event;
                var Dom = $(this);
                var id = Dom.attr("id");
                clearTimeout(timeId);
                timeId = setTimeout(function () {
	                that.focusItem(id, true);
	                var ev = GooFunc.mousePosition(e),
                        t = GooFunc.getElCoordinate(that.$workArea[0]),
                        c = GooFunc.getElCoordinate(that.$MainContener);
                    that.$ghost.css({
                        display: "block",
                        top: that.$nodeData[id].top + 133 - that.$workArea[0].parentNode.scrollTop + "px",
                        left: that.$nodeData[id].left + 200 - that.$workArea[0].parentNode.scrollLeft + "px",
                        cursor: "move"
                    });
        function itemSearchFn(obj) {
            var $list = obj.find($('.GooFlow_app_list_item'));
            var $input = obj.find($('.GooFlow_app_search_input'));
            var $item = obj.find($('.GooFlow_app_item'));
            $input.on('keyup', {inthis: that}, function (e) {
                var input = $input.val()
                if (input.length > 0) {
                    $item.hide();
                    var filterObj = $item.filter(function (index) {
                        return $(this).text().indexOf(input) >= 0;
                    })
                    if (filterObj.length > 0) {
                        filterObj.css("display", "block").parent().parent().parent().addClass('selected');
                    } else {
                        $item.show();
                        $list.removeClass('selected');
                    var X, Y;
                    X = ev.x - t.left + that.$workArea[0].parentNode.scrollLeft;
                    Y = ev.y - t.top + that.$workArea[0].parentNode.scrollTop;
                    var vX = X - that.$nodeData[id].left, vY = Y - that.$nodeData[id].top;
                    var isMove = false;
                    document.onmousemove = function (e) {
                        if (!e) {
                            e = window.event;
                        }
                        var ev = GooFunc.mousePosition(e);
                        X = ev.x - vX - c.left;
                        Y = ev.y - vY - c.top;
                        if (X < t.left - c.left - that.$workArea[0].parentNode.scrollLeft) {
                            X = t.left - c.left - that.$workArea[0].parentNode.scrollLeft;
                        }
                        else if (X + that.$workArea[0].parentNode.scrollLeft + that.$nodeData[id].width > t.left - c.left + that.$workArea.width()) {
                            X = t.left - c.left + that.$workArea.width() - that.$workArea[0].parentNode.scrollLeft - that.$nodeData[id].width;
                        }
                        if (Y < t.top - c.top - that.$workArea[0].parentNode.scrollTop) {
                            Y = t.top - c.top - that.$workArea[0].parentNode.scrollTop;
                        }
                        else if (Y + that.$workArea[0].parentNode.scrollTop + that.$nodeData[id].height > t.top - c.top + that.$workArea.height()) {
                            Y = t.top - c.top + that.$workArea.height() - that.$workArea[0].parentNode.scrollTop - that.$nodeData[id].height;
                        }
                        that.$ghost.css({left: X - 230 + "px", top: Y - 133 + "px"});
                        isMove = true;
                    }
                    document.onmouseup = function () {
                        if (isMove) that.moveNode(id, X + that.$workArea[0].parentNode.scrollLeft - t.left + c.left, Y + that.$workArea[0].parentNode.scrollTop - t.top + c.top);
                        {
                            that.$ghost.empty().hide();
                        }
                        document.onmousemove = null;
                        document.onmouseup = null;
                    }
                }, 150);
            },
            // 显示连线图标
            mouseenter: function () {
                var moving = that.$lineMoveing;
                if (!moving) {
                    var w = $(this).width() + 60 + 'px';
                    var h = $(this).height() + 30 + 'px';
                    $(this).append("<div class='line-begin' style='width:" + w + ";height:" + h + "'><div class='line-begin-icon top'></div><div class='line-begin-icon left'></div><div class='line-begin-icon right'></div><div class='line-begin-icon bottom'></div></div>")
                }
                else {
                    $item.show();
                    $list.removeClass('selected');
                if (moving) {
                    $(this).addClass('item-mark');
                }
            })
        }
            },
            mouseleave: function () {
                $(this).children('.line-begin').remove();
                $(this).removeClass('item-mark');
            },
            // 结束连线
            mouseup: function () {
                clearTimeout(timeId);
                if (that.$lineMoveing) {
                    var type = $(this).children('.GooFlow_item_cont').children('.GooFlow_item_txt').attr("id").split("type-")[1]
                    var obj = $(this);
                    if (that.$nowType != "cursor") {
                        return;
                    }
        itemSearchFn($('.GooFlow_left'));
        itemSearchFn($('.GooFlow_right'));
    }
                    var lineT, lineM;
                    var lineType = that.$lineType;
                    switch (lineType) {
                        case 'top':
                        case 'bottom':
                            lineT = 'tb';
                            break;
                        case 'left':
                        case 'right':
                            lineT = 'lr';
                            break;
                    }
    /*
     * Todo: 端点及转换器HTML结构搭建 【UI相关】
     * Author: LE
     * Date: 2017/2/20
     * time: 10:09
     * */
    function appHtml(data, className, title) {
        var list = data, htmlStr;
        htmlStr = "<div class='GooFlow_app " +
            className +
            "'><div class='GooFlow_app_head'><h1 class='GooFlow_app_tit'>"
            + title +
            "</h1><div class='GooFlow_app_search'><input class='GooFlow_app_search_input' type='text' placeholder='搜索"
            + title +
            "'><div class='GooFlow_search_btn'></div></div></div><div class='GooFlow_app_cont'><ul" +
            " class='GooFlow_app_list'>"
        if (title == '端点') {
            for (var i = 0; i < list.length; i++) {
                htmlStr += "<li class='GooFlow_app_list_item'>";
                htmlStr += "<div class='GooFlow_app_list_tit'>" + list[i].title + "</div>";
                htmlStr += "<div class='GooFlow_app_list_box'><ul>";
                for (var j = 0; j < list[i].list.length; j++) {
                    htmlStr += "<li class='GooFlow_app_item'data ='" + list[i].list[j].value +
                        "'><div class='app_icon'></div> <div class='app_name'>" + list[i].list[j].name + "</div></li>"
                    var lineStart = that.$workArea.data("lineStart");
                    console.log("lineStart" + lineStart);
                    var n1 = that.$nodeData[lineStart.id], n2 = that.$nodeData[obj[0].id];
                    //To结点中心点位置判断
                    var sLeft = n1.left;
                    var sRight = sLeft + n1.width;
                    var sTop = n1.top;
                    var sBottom = sTop + n1.height;
                    var EXcenter = n2.left + n2.width / 2;
                    var EYcenter = n2.top + n2.height / 2;
                    // 当To结点中心点在From结点内时,用直线
                    if ((EXcenter >= sLeft && EXcenter <= sRight) || (EYcenter >= sTop && EYcenter <= sBottom)) {
                        lineT = 'sl';
                        lineM = '';
                    }
                    var lineM = GooFlow.prototype.getMValue(n1, n2, lineT);
                    if (lineStart) {
                        that.addLine(that.$id + "_line_" + that.$lineCount, {
                            from: lineStart.id,
                            to: obj[0].id,
                            name: "",
                            x: lineStart.x,
                            y: lineStart.y,
                            typeFrom: lineStart.typeFrom,
                            typeTo: type,
                            type: lineT,
                            M: lineM
                        });
                    }
                }
                htmlStr += "</ul></li>";
            }
        } else if (title == '转换器'){
            htmlStr += "<li class='GooFlow_app_list_item'><div class='GooFlow_app_list_box'><ul>";
            for (var i = 0; i < list.length; i++) {
                htmlStr += "<li class='GooFlow_app_item' data='" + list[i].value +
                    "'><div class='app_icon'></div> <div class='app_name'>" + list[i].name + "</div></li>"
            }
            htmlStr += "</ul></li></ul></div></div>";
        }
        that.$contBox.prepend(htmlStr);
    }
	/*
	 * Todo: 画布区域 【UI相关】
	 * Author: LE
	 * Date: 2017/2/20
	 * time: 10:09
	 * */
    this.$contBox.append("<div class='GooFlow_work'></div>");
    this.$workArea = $("<div class='GooFlow_work_inner'></div>").attr({
        "unselectable": "on",
        "onselectstart": 'return false',
        "onselect": 'document.selection.empty()'
    });
    this.$contBox.children(".GooFlow_work").append(this.$workArea);
    this.$draw = null;
    this.initDraw("draw_" + this.$id, width, height, workWidth, workHeight);
    /*
     * Todo: ### 画布区域功能设置 ******************************************* //
     * Author: LE
     * Date: 2017/2/15
     * time: 15:15
     * */
    if (this.$editable) {
        /*
         * Todo: 空白区域点击事件 【画布--click事件】
         * Author: LE
         * Date: 2017/2/20
         * time: 10:05
         * */
        this.$workArea.on("click", {inthis: this}, function (e) {
            // 获取事件对象
            var This = e.data.inthis;
	        This.$canDelLine = false;
            if (!e) {
                e = window.event;
            }
            if (!e.data.inthis.$editable) {
                return;
            }
            // 获取当前工具类型
            var type = e.data.inthis.$nowType;
            // 如果当前所选工具类型为指针时
            if (type == "cursor") {
                var t = $(e.target);
                var n = t.prop("tagName");
                if (n == "svg" || (n == "DIV" && t.prop("class").indexOf("GooFlow_work") > -1) || n == "LABEL") {
                    // 点击空白处取消所有结点/连线被选定的状态
                    e.data.inthis.blurItem();
                }
                return;
        }, ".GooFlow_item").on({
            // 删除
            mousedown: function () {
                that.delNode(that.$focus);
                return false;
            }
        });
        }, ".rs_close")
        // 对结点进行移动或者RESIZE时用来显示的遮罩层
        this.$ghost = $("<div class='rs_ghost GooFlow_item'></div>").attr({
            "unselectable": "on",
            "onselectstart": 'return false',
            "onselect": 'document.selection.empty()'
        });
        this.$bgDiv.append(this.$ghost);
        /*
         * Todo: 线条绘制 【线条--mousemove & mouseup事件】
         * Author: LE
         * Date: 2017/2/20
         * time: 10:04
         * */
        this.$workArea.mousemove({inthis: this}, function (e) {
            var This = e.data.inthis;
            if (This.$nowType == "cursor") {
                var lineStart = $(this).data("lineStart");
                if (!lineStart)return;
                var ev = GooFunc.mousePosition(e), t = GooFunc.getElCoordinate(this);
                var X, Y;
                X = ev.x - t.left + this.parentNode.scrollLeft;
                Y = ev.y - t.top + this.parentNode.scrollTop;
                var line = document.getElementById("GooFlow_tmp_line");
	    $('.file-box input').click(function () {
		    // 编辑的时候禁止delete键删除
		    that.blurItem();
	    })
		/*
		 * Todo: 连线链接 【 $$$】
		 * Author: LE
		 * Date: 2017/2/20
		 * time: 10:23
		 * */
        this.$workArea.on({
            // 连线开始
            mousedown: function () {
                var cs = $(this).attr("class").split(/\s+/);
                that.$lineMoveing = true;
                that.$lineType = cs[1];
                var obj = $(this).parent().parent();
                that.$nowType = "cursor";
                var type = obj.children('.GooFlow_item_cont').children('.GooFlow_item_txt').attr("id").split("type-")[1]
                // 将划线开始点定为元素中心点
                var X, Y;
                var objPosition = obj.position();
                var objLeft = objPosition.left;
                var objTop = objPosition.top;
                var w = (obj.width()) / 2;
                var h = (obj.height()) / 2;
                X = objLeft + w;
                Y = objTop + h;
                that.$workArea.data("lineStart", {
                    "x": X,
                    "y": Y,
                    "id": obj[0].id,
                    "typeFrom": type
                }).css("cursor", "crosshair");
                var line = GooFlow.prototype.drawLine("GooFlow_tmp_line", [X, Y], [X, Y], true, true);
                that.$draw.appendChild(line);
                $(document).mouseup(function () {
                    $('.GooFlow_item').removeClass('item-mark');
                    that.$lineMoveing = false;
                })
                return false;
            }
        }, ".GooFlow_item .line-begin > *").on({
            // 连线中
            mousemove: function (e) {
                if (that.$nowType == "cursor") {
                    var lineStart = $(this).data("lineStart");
                    if (!lineStart)return;
                    var ev = GooFunc.mousePosition(e), t = GooFunc.getElCoordinate(this);
                    var X, Y;
                    X = ev.x - t.left + this.parentNode.scrollLeft;
                    Y = ev.y - t.top + this.parentNode.scrollTop;
                if (GooFlow.prototype.useSVG != "") {
                    var line = document.getElementById("GooFlow_tmp_line");
                    line.childNodes[0].setAttribute("d", "M " + lineStart.x + " " + lineStart.y + " L " + X + " " + Y);
                    line.childNodes[1].setAttribute("d", "M " + lineStart.x + " " + lineStart.y + " L " + X + " " + Y);
@ -373,701 +840,274 @@ function GooFlow(bgDiv, property) {
                        line.childNodes[1].setAttribute("marker-end", "url(#arrow2)");
                    }
                }
                else {
                    line.points.value = lineStart.x + "," + lineStart.y + " " + X + "," + Y;
            },
            // 连线结束
            mouseup: function () {
                if (that.$nowType != "cursor") {
                    return;
                }
                $(this).css("cursor", "auto").removeData("lineStart");
                var HtmlStr = document.getElementById("GooFlow_tmp_line");
                if (HtmlStr) {
                    that.$draw.removeChild(HtmlStr);
                }
            }
        });
        this.$workArea.mouseup({inthis: this}, function (e) {
            if (e.data.inthis.$nowType != "cursor") {
                return;
            }
            $(this).css("cursor", "auto").removeData("lineStart");
            var HtmlStr = document.getElementById("GooFlow_tmp_line");
            if (HtmlStr) {
                e.data.inthis.$draw.removeChild(HtmlStr);
            }
        });
        
        /*
         * Todo: 结点拖拽 【结点- document.mousemove@@】
         * Author: LE
         * Date: 2017/2/20
         * time: 10:03
         * */
        var onDrag = false;
        function addNodeByDrag(obj, type) {
            obj.mousedown(function () {
                that.$nowType = type;
                if (!type) {
                    var thisType = $(this).attr("id").split("btn_")[1];
                    that.$nowType = thisType;
        })
		/*
		 * Todo: 线条文字编辑 【 $$$】
		 * Author: LE
		 * Date: 2017/2/20
		 * time: 10:23
		 * */
        $(this.$draw).on({
            click: function () {
                that.focusItem(this.id, true);
            },
            dblclick: function () {
                var oldTxt, x, y, from, to;
                // 编辑的时候禁止delete键删除
                that.$canDelLine = false;
                that.$deteNodeId = '';
                that.$deteLineId = '';
                if (GooFlow.prototype.useSVG != "") {
                    oldTxt = this.childNodes[2].textContent;
                    from = this.getAttribute("from").split(",");
                    to = this.getAttribute("to").split(",");
                }
                onDrag = true;
                that.$newNodeTxt = $(this).children('.app_name').text();
                that.$newNodeValue = $(this).attr('data');
                console.log( $(this));
            });
            $(document).mouseup(function () {
                onDrag = false;
            });
            that.$workArea.mousemove(function () {
                if (onDrag) {
                    var X, Y;
                    var ev = GooFunc.mousePosition();
                    X = ev.x - 433 + this.parentNode.scrollLeft;
                    Y = ev.y - 262  + this.parentNode.scrollTop;
                    that.$workArea.children('#node-ghost').attr({class: 'node-' + that.$nowType});
                    that.$workArea.css({cursor: 'move'}).children('#node-ghost').css({
                        display: "block",
                        top: Y + "px",
                        left: X + "px",
                        cursor: "move",
                    });
                if (that.$lineData[this.id].type == "lr") {
                    from[0] = that.$lineData[this.id].M;
                    to[0] = from[0];
                }
            })
            that.$workArea.mouseup(function () {
                if (onDrag) {
                    addNodeOnArea();
                    onDrag = false;
                    that.$workArea.css({cursor: 'pointer'}).children('#node-ghost').remove();
                else if (that.$lineData[this.id].type == "tb") {
                    from[1] = that.$lineData[this.id].M;
                    to[1] = from[1];
                }
            });
            that.$workArea.mouseenter(function () {
                that.$workArea.append("<div id='node-ghost'></div>");
            })
            that.$workArea.mouseleave(function () {
                that.$workArea.css({cursor: 'pointer'}).children('#node-ghost').remove();
            })
            function addNodeOnArea() {
                var X, Y;
                var ev = GooFunc.mousePosition();
                X = ev.x - 433 +  that.$workArea[0].parentNode.scrollLeft;
                Y = ev.y - 262 +  that.$workArea[0].parentNode.scrollTop;
                that.addNode(that.$id + "_node_" + that.$nodeCount, {
                    name: "node_" + that.$nodeCount,
                    left: X,
                    top: Y,
                    type: that.$nowType
                x = (parseInt(from[0], 10) + parseInt(to[0], 10)) / 2 + 230;
                y = (parseInt(from[1], 10) + parseInt(to[1], 10)) / 2 + 60;
                that.$textArea.val(oldTxt).css({
                    display: "block",
                    left: x,
                    top: y
                }).data("id", that.$focus).focus();
                that.$workArea.parent().one("mousedown", function () {
                    that.setName(that.$textArea.data("id"), that.$textArea.val(), "line");
                    that.$textArea.val("").removeData("id").hide();
                });
                that.$max++;
            }
        }
        addNodeByDrag(that.$flowChart.children());
        // 为了结点而增加的一些集体delegate绑定
        this.initWorkForNode();
        // 文本框
        this.$textArea = $("<textarea class='text_input'></textarea>");
        this.$bgDiv.append(this.$textArea);
        // 操作折线时的移动框
        this.$lineMove = $("<div class='GooFlow_line_move' style='display:none'></div>");
        this.$workArea.append(this.$lineMove);
        /*
         * Todo: 线条移动事件 【线条编辑相关 $$$】
         * Author: LE
         * Date: 2017/2/20
         * time: 10:33
         * */
        this.$lineMove.on("mousedown", {inthis: this}, function (e) {
        }, 'g');
		/*
		 * Todo: 线条移动 【 $$$】
		 * Author: LE
		 * Date: 2017/2/20
		 * time: 10:33
		 * */
        this.$lineMove.on("mousedown", function (e) {
            var lm = $(this);
            lm.css({"background-color": "#333"});
            var This = e.data.inthis;
            var ev = GooFunc.mousePosition(e), t = GooFunc.getElCoordinate(This.$workArea[0]);
            var ev = GooFunc.mousePosition(e), t = GooFunc.getElCoordinate(that.$workArea[0]);
            var X, Y;
            X = ev.x - t.left + This.$workArea[0].parentNode.scrollLeft;
            Y = ev.y - t.top + This.$workArea[0].parentNode.scrollTop;
            var p = This.$lineMove.position();
            X = ev.x - t.left + that.$workArea[0].parentNode.scrollLeft;
            Y = ev.y - t.top + that.$workArea[0].parentNode.scrollTop;
            var p = that.$lineMove.position();
            var vX = X - p.left, vY = Y - p.top;
            var isMove = false;
            document.onmousemove = function (e) {
                if (!e) e = window.event;
                var ev = GooFunc.mousePosition(e);
                var ps = This.$lineMove.position();
                X = ev.x - t.left + This.$workArea[0].parentNode.scrollLeft;
                Y = ev.y - t.top + This.$workArea[0].parentNode.scrollTop;
                if (This.$lineMove.data("type") == "lr") {
                var ps = that.$lineMove.position();
                X = ev.x - t.left + that.$workArea[0].parentNode.scrollLeft;
                Y = ev.y - t.top + that.$workArea[0].parentNode.scrollTop;
                if (that.$lineMove.data("type") == "lr") {
                    X = X - vX;
                    if (X < 0) X = 0;
                    else if (X > This.$workArea.width())
                        X = This.$workArea.width();
                    This.$lineMove.css({left: X + "px"});
                    else if (X > that.$workArea.width())
                        X = that.$workArea.width();
                    that.$lineMove.css({left: X + "px"});
                }
                else if (This.$lineMove.data("type") == "tb") {
                else if (that.$lineMove.data("type") == "tb") {
                    Y = Y - vY;
                    if (Y < 0) Y = 0;
                    else if (Y > This.$workArea.height())
                        Y = This.$workArea.height();
                    This.$lineMove.css({top: Y + "px"});
                    else if (Y > that.$workArea.height())
                        Y = that.$workArea.height();
                    that.$lineMove.css({top: Y + "px"});
                }
                isMove = true;
            }
            document.onmouseup = function (e) {
                if (isMove) {
                    var p = This.$lineMove.position();
                    if (This.$lineMove.data("type") == "lr")
                        This.setLineM(This.$lineMove.data("tid"), p.left + 3);
                    else if (This.$lineMove.data("type") == "tb")
                        This.setLineM(This.$lineMove.data("tid"), p.top + 3);
                    var p = that.$lineMove.position();
                    if (that.$lineMove.data("type") == "lr")
                        that.setLineM(that.$lineMove.data("tid"), p.left + 3);
                    else if (that.$lineMove.data("type") == "tb")
                        that.setLineM(that.$lineMove.data("tid"), p.top + 3);
                }
                This.$lineMove.css({"background-color": "transparent"});
                if (This.$focus == This.$lineMove.data("tid")) {
                    This.focusItem(This.$lineMove.data("tid"));
                that.$lineMove.css({"background-color": "transparent"});
                if (that.$focus == that.$lineMove.data("tid")) {
                    that.focusItem(that.$lineMove.data("tid"));
                }
                document.onmousemove = null;
                document.onmouseup = null;
            }
        });
        /*
         * Todo: 生成线条修改框 【线条编辑UI相关 $$$】
         * Author: LE
         * Date: 2017/2/20
         * time: 10:02
         * */
        this.$lineOper = $("<div class='GooFlow_line_oper' style='display:none'><b class='b_l1'></b><b class='b_l2'></b><b class='b_l3'></b><b class='b_x'></b></div>");
        this.$workArea.append(this.$lineOper);
        this.$lineOper.on("click", {inthis: this}, function (e) {
            if (!e) e = window.event;
        this.$lineOper.on("click", function (e) {
            if (e.target.tagName != "B")   return;
            var This = e.data.inthis;
            var id = $(this).data("tid");
            switch ($(e.target).attr("class")) {
                case "b_x":
                    This.delLine(id);
                    that.delLine(id);
                    this.style.display = "none";
                    break;
                case "b_l1":
                    This.setLineType(id, "lr");
                    that.setLineType(id, "lr");
                    break;
                case "b_l2":
                    This.setLineType(id, "tb");
                    that.setLineType(id, "tb");
                    break;
                case "b_l3":
                    This.setLineType(id, "sl");
                    that.setLineType(id, "sl");
                    break;
            }
        });
        //下面绑定当结点/线/分组块的一些操作事件,这些事件可直接通过this访问对象本身
        //当操作某个单元(结点/线/分组块)被添加时, 触发的方法, 返回FALSE可阻止添加事件的发生
        //格式function(id, type,json):id是单元的唯一标识ID,type是单元的种类,有"node","line","area"三种取值,json即addNode,addLine或addArea方法的第二个传参json.
        this.onItemAdd = property.onItemAdd;
        //当操作某个单元(结点/线/分组块)被删除时, 触发的方法, 返回FALSE可阻止删除事件的发生
        //格式function(id, type):id是单元的唯一标识ID,type是单元的种类,有"node","line","area"三种取值
        this.onItemDel = property.onItemDel;
        //当操作某个单元(结点/分组块)被移动时, 触发的方法, 返回FALSE可阻止移动事件的发生
        //格式function(id, type,left,top):id是单元的唯一标识ID,type是单元的种类,有"node","area"两种取值, 线line不支持移动,left是新的左边距坐标, top是新的顶边距坐标
        this.onItemMove = property.onItemMove;
        //当操作某个单元(结点/线/分组块)被重命名时, 触发的方法, 返回FALSE可阻止重命名事件的发生
        //格式function(id,name,type):id是单元的唯一标识ID,type是单元的种类,有"node","line","area"三种取值,name是新的名称
        this.onItemRename = property.onItemRename;
        //当操作某个单元(结点/线)被由不选中变成选中时, 触发的方法, 返回FALSE可阻止选中事件的发生
        //格式function(id,type):id是单元的唯一标识ID,type是单元的种类,有"node","line"两种取值,"area"不支持被选中
        this.onItemFocus = property.onItemFocus;
        //当操作某个单元(结点/线)被由选中变成不选中时, 触发的方法, 返回FALSE可阻止取消选中事件的发生
        //格式function(id, type):id是单元的唯一标识ID,type是单元的种类,有"node","line"两种取值,"area"不支持被取消选中
        this.onItemBlur = property.onItemBlur;
        //当操作某个单元(结点/分组块)被重定义大小或造型时, 触发的方法, 返回FALSE可阻止重定大小/造型事件的发生
        //格式function(id, type,width,height):id是单元的唯一标识ID,type是单元的种类,有"node","line","area"三种取值;width是新的宽度,height是新的高度
        this.onItemResize = property.onItemResize;
        //当移动某条折线中段的位置, 触发的方法, 返回FALSE可阻止重定大小/造型事件的发生
        //格式function(id, M):id是单元的唯一标识ID,M是中段的新X(或Y)的坐标
        this.onLineMove = property.onLineMove;
        //当变换某条连接线的类型, 触发的方法, 返回FALSE可阻止重定大小/造型事件的发生
        //格式function(id, type):id是单元的唯一标识ID,type是连接线的新类型,"sl":直线,"lr":中段可左右移动的折线,"tb":中段可上下移动的折线
        this.onLineSetType = property.onLineSetType;
        //当用重色标注某个结点/转换线时触发的方法, 返回FALSE可阻止重定大小/造型事件的发生
        //格式function(id, type, mark):id是单元的唯一标识ID,type是单元类型("node"结点,"line"转换线), mark为布尔值,表示是要标注TRUE还是取消标注FALSE
        this.onItemMark = property.onItemMark;
        /*
         * Todo: 取消&重做函数 【堆栈相关】
         * Author: LE
         * Date: 2017/2/15
         * time: 15:12
         * */
        //如果要使用堆栈记录操作并提供“撤销/重做”的功能
        if (property.useOperStack) {
            this.$undoStack = [];
            this.$redoStack = [];
            this.$isUndo = 0;
            ///////////////以下是构造撤销操作/重做操作的方法
            //为了节省浏览器内存空间,undo/redo中的操作缓存栈,最多只可放40步操作;超过40步时,将自动删掉最旧的一个缓存
            this.pushOper = function (funcName, paras) {
                var len = this.$undoStack.length;
                if (this.$isUndo == 1) {
                    this.$redoStack.push([funcName, paras]);
                    this.$isUndo = false;
                    if (this.$redoStack.length > 40) this.$redoStack.shift();
                } else {
                    this.$undoStack.push([funcName, paras]);
                    if (this.$undoStack.length > 40) this.$undoStack.shift();
                    if (this.$isUndo == 0) {
                        this.$redoStack.splice(0, this.$redoStack.length);
                    }
                    this.$isUndo = 0;
                }
            };
            //将外部的方法加入到GooFlow对象的事务操作堆栈中,在过后的undo/redo操作中可以进行控制, 一般用于对流程图以外的附加信息进行编辑的事务撤销/重做控制;
            //传参func为要执行方法对象,jsonPara为外部方法仅有的一个面向字面的JSON传参,由JSON对象带入所有要传的信息;
            //提示:为了让外部方法能够被UNDO/REDO,需要在编写这些外部方法实现时,加入对该方法执行后效果回退的另一个执行方法的pushExternalOper
            this.pushExternalOper = function (func, jsonPara) {
                this.pushOper("externalFunc", [func, jsonPara]);
            };
            //撤销上一步操作
            this.undo = function () {
                if (this.$undoStack.length == 0)   return;
                var tmp = this.$undoStack.pop();
                this.$isUndo = 1;
                if (tmp[0] == "externalFunc") {
                    tmp[1][0](tmp[1][1]);
                }
                else {
                    //传参的数量,最多支持5个.
                    switch (tmp[1].length) {
                        case 0:
                            this[tmp[0]]();
                            break;
                        case 1:
                            this[tmp[0]](tmp[1][0]);
                            break;
                        case 2:
                            this[tmp[0]](tmp[1][0], tmp[1][1]);
                            break;
                        case 3:
                            this[tmp[0]](tmp[1][0], tmp[1][1], tmp[1][2]);
                            break;
                        case 4:
                            this[tmp[0]](tmp[1][0], tmp[1][1], tmp[1][2], tmp[1][3]);
                            break;
                        case 5:
                            this[tmp[0]](tmp[1][0], tmp[1][1], tmp[1][2], tmp[1][3], tmp[1][4]);
                            break;
    },
	/*
	 * Todo: ### 初始化事件绑定******************************--结束
	 * */
	/*
	 * Todo: 取消选定状态 【取消聚焦公共方法】
	 * Author: LE
	 * Date: 2017/2/20
	 * time: 9:53
	 * */
    blurItem: function () {
	    this.$canDelLine = false;
	    this.$deteNodeId = '';
	    this.$deteLineId = '';
        if (this.$focus != "") {
            // 获取被选中对象
            var jq = $("#" + this.$focus);
            if (jq.prop("tagName") == "DIV") {
                if (this.onItemBlur != null && !this.onItemBlur(id, "node"))  return false;
                jq.removeClass("item_focus").children(".rs-box").css("display", "none");
            }
            else {
                if (this.onItemBlur != null && !this.onItemBlur(id, "line"))  return false;
                if (GooFlow.prototype.useSVG != "") {
                    if (!this.$lineData[this.$focus].marked) {
                        jq[0].childNodes[1].setAttribute("stroke", "#323232");
                        jq[0].childNodes[1].setAttribute("marker-end", "url(#arrow1)");
                    }
                }
            };
            //重做最近一次被撤销的操作
            this.redo = function () {
                if (this.$redoStack.length == 0)   return;
                var tmp = this.$redoStack.pop();
                this.$isUndo = 2;
                if (tmp[0] == "externalFunc") {
                    tmp[1][0](tmp[1][1]);
                }
                else {
                    //传参的数量,最多支持5个.
                    switch (tmp[1].length) {
                        case 0:
                            this[tmp[0]]();
                            break;
                        case 1:
                            this[tmp[0]](tmp[1][0]);
                            break;
                        case 2:
                            this[tmp[0]](tmp[1][0], tmp[1][1]);
                            break;
                        case 3:
                            this[tmp[0]](tmp[1][0], tmp[1][1], tmp[1][2]);
                            break;
                        case 4:
                            this[tmp[0]](tmp[1][0], tmp[1][1], tmp[1][2], tmp[1][3]);
                            break;
                        case 5:
                            this[tmp[0]](tmp[1][0], tmp[1][1], tmp[1][2], tmp[1][3], tmp[1][4]);
                            break;
                    }
                    if (!this.$lineData[this.$focus].marked) jq[0].strokeColor = "#323232";
                }
            };
                this.$lineMove.hide().removeData("type").removeData("tid");
                if (this.$editable) this.$lineOper.hide().removeData("tid");
            }
        }
        this.$focus = "";
        return true;
    },
    }
	/*
	 * Todo: 选定状态 【聚焦公共方法】
	 * Author: LE
	 * Date: 2017/2/20
	 * time: 9:53
	 * */
    focusItem: function (id, bool) {
}
/*
 * Todo:  ## 原型方法 ***************************************************************************************
 * Author: LE
 * Date: 2017/2/15
 * time: 14:59
 * */
GooFlow.prototype = {
    useSVG: true,
    /*
     * Todo: SVG箭头相关【UI相关】
     * Author: LE
     * Date: 2017/2/15
     * time: 15:11
     * */
    getSvgMarker: function (id, color) {
        var m = document.createElementNS("http://www.w3.org/2000/svg", "marker");
        m.setAttribute("id", id);
        m.setAttribute("viewBox", "0 0 6 6");
        m.setAttribute("refX", 5);
        m.setAttribute("refY", 3);
        m.setAttribute("markerUnits", "strokeWidth");
        m.setAttribute("markerWidth", 8);
        m.setAttribute("markerHeight", 8);
        m.setAttribute("orient", "auto");
        var path = document.createElementNS("http://www.w3.org/2000/svg", "path");
        path.setAttribute("d", "M 0 0 L 6 3 L 0 6  L 2 3 z");
        path.setAttribute("fill", color);
        path.setAttribute("stroke-width", 2);
        m.appendChild(path);
        return m;
    },
    
    /*
     * Todo: 初始化画布 【SVG & 箭头 UI相关】
     * Author: LE
     * Date: 2017/2/20
     * time: 9:51
     * */
    initDraw: function (id, width, height, workWidth, workHeight) {
        if (GooFlow.prototype.useSVG != "") {
            this.$draw = document.createElementNS("http://www.w3.org/2000/svg", "svg");
            //可创建带有指定命名空间的元素节点
            this.$workArea.prepend(this.$draw);
            // 定义可以重复利用的元素
            var defs = document.createElementNS("http://www.w3.org/2000/svg", "defs");
            this.$draw.appendChild(defs);
            defs.appendChild(GooFlow.prototype.getSvgMarker("arrow1", "#323232"));
            defs.appendChild(GooFlow.prototype.getSvgMarker("arrow2", "#00b7ee"));
            defs.appendChild(GooFlow.prototype.getSvgMarker("arrow3", "#f00"));
        }
        else {
            this.$draw = document.createElement("v:group");
            this.$draw.coordsize = workWidth + "," + workHeight;
            this.$workArea.prepend("<div class='GooFlow_work_vml'" +
                " style='position:relative;width:100%;height:100%'></div>");
            this.$workArea.children("div")[0].insertBefore(this.$draw, null);
        }
        // 设置画布属性
        this.$draw.id = id;
        this.$draw.style.width = '100%';
        this.$draw.style.height = '100%';
        //绑定连线的点击选中以及双击编辑事件
        var tmpClk = (GooFlow.prototype.useSVG != "") ? 'g' : 'PolyLine';
        // 对连线绑定单击事件
        $(this.$draw).delegate(tmpClk, "click", {inthis: this}, function (e) {
            e.data.inthis.focusItem(this.id, true);
        });
        /*
         * Todo: 双击线条编辑事件 【SVG- 双击事件 $$$】
         * Author: LE
         * Date: 2017/2/20
         * time: 9:50
         * */
        if (this.$editable) {
            $(this.$draw).delegate(tmpClk, "dblclick", {inthis: this}, function (e) {
                var oldTxt, x, y, from, to;
                var This = e.data.inthis;
                // 编辑的时候禁止delete键删除
	            This.$canDelLine = false;
	            This.$deteNodeId = '';
	            This.$deteLineId = '';
                if (GooFlow.prototype.useSVG != "") {
                    oldTxt = this.childNodes[2].textContent;
                    from = this.getAttribute("from").split(",");
                    to = this.getAttribute("to").split(",");
                } else {
                    oldTxt = this.childNodes[1].innerHTML;
                    var n = this.getAttribute("fromTo").split(",");
                    from = [n[0], n[1]];
                    to = [n[2], n[3]];
                }
                if (This.$lineData[this.id].type == "lr") {
                    from[0] = This.$lineData[this.id].M;
                    to[0] = from[0];
                }
                else if (This.$lineData[this.id].type == "tb") {
                    from[1] = This.$lineData[this.id].M;
                    to[1] = from[1];
        var jq = $("#" + id);
        var that = this;
        this.$ghost.attr({'class': 'rs_ghost ' + jq.attr('class')});
        if (jq.length == 0)    return;
        if (!this.blurItem())    return;//先执行"取消选中",如果返回FLASE,则也会阻止选定事件继续进行.
        if (jq.prop("tagName") == "DIV") {
            if (bool && this.onItemFocus != null && !this.onItemFocus(id, "node"))  return;
            jq.addClass("item_focus");
            if (this.$editable) jq.children(".rs-box").css("display", "block");
            this.$workArea.append(jq);
            that.$canDelLine = false;
            that.$deteLineId = '';
            that.$deteNodeId = id;
            $(document).keydown(function (e) {
                if (e.keyCode != 8 && that.$deteNodeId) {
                    return
                }
                x = (parseInt(from[0], 10) + parseInt(to[0], 10)) / 2 + 230;
                y = (parseInt(from[1], 10) + parseInt(to[1], 10)) / 2 + 60;
                This.$textArea.val(oldTxt).css({
                    display: "block",
                    left: x,
                    top: y
                }).data("id", This.$focus).focus();
                This.$workArea.parent().one("mousedown", function (e) {
                    This.setName(This.$textArea.data("id"), This.$textArea.val(), "line");
                    This.$textArea.val("").removeData("id").hide();
                });
                that.delNode(that.$deteNodeId);
            });
        }
    },
    /*
     * Todo: ### 工作区事件绑定 ***************************************************************
     * Author: LE
     * Date: 2017/2/15
     * time: 15:09
     * */
    initWorkForNode: function () {
        /*
         * Todo: 结点选中事件 【结点--click 事件 @@】
         * Author: LE
         * Date: 2017/2/20
         * time: 10:21
         * */
        this.$workArea.delegate(".GooFlow_item", "click", {inthis: this}, function (e) {
            e.data.inthis.focusItem(this.id, true);
        });
        /*
         * Todo: 结点删除事件 【删除按钮-mousedown事件 @@】
         * Author: LE
         * Date: 2017/2/20
         * time: 10:22
         * */
        this.$workArea.on("mousedown", ".rs_close", {inthis: this}, function (e) {
            if (!e) e = window.event;
            e.data.inthis.delNode(e.data.inthis.$focus);
            return false;
        });
        /*
         * Todo: 结点移动事件 【结点--mousedown 事件 @@】
         * Author: LE
         * Date: 2017/2/20
         * time: 10:22
         * */
        this.$workArea.on("mousedown", ".GooFlow_item", {inthis: this}, function (e) {
            if (!e) e = window.event;
            var This = e.data.inthis;
            var Dom = $(this);
            var id = Dom.attr("id");
            This.focusItem(id, true);
            var ev = GooFunc.mousePosition(e),
                t = GooFunc.getElCoordinate(This.$workArea[0]),
                c = GooFunc.getElCoordinate(This.$MainContener);
            This.$ghost.css({
		        display: "block",
		        top: This.$nodeData[id].top + 133 - This.$workArea[0].parentNode.scrollTop + "px",
		        left: This.$nodeData[id].left + 200 - This.$workArea[0].parentNode.scrollLeft + "px",
		        cursor: "move"
	        });
            var X, Y;
            X = ev.x - t.left + This.$workArea[0].parentNode.scrollLeft;
            Y = ev.y - t.top + This.$workArea[0].parentNode.scrollTop;
            var vX = X - This.$nodeData[id].left, vY = Y - This.$nodeData[id].top;
            var isMove = false;
            document.onmousemove = function (e) {
                if (!e) {
                    e = window.event;
                }
                var ev = GooFunc.mousePosition(e);
                X = ev.x - vX - c.left;
                Y = ev.y - vY - c.top;
                if (X < t.left - c.left - This.$workArea[0].parentNode.scrollLeft) {
                    X = t.left - c.left - This.$workArea[0].parentNode.scrollLeft;
                }
                else if (X + This.$workArea[0].parentNode.scrollLeft + This.$nodeData[id].width > t.left - c.left + This.$workArea.width()) {
                    X = t.left - c.left + This.$workArea.width() - This.$workArea[0].parentNode.scrollLeft - This.$nodeData[id].width;
                }
                if (Y < t.top - c.top - This.$workArea[0].parentNode.scrollTop) {
                    Y = t.top - c.top - This.$workArea[0].parentNode.scrollTop;
                }
                else if (Y + This.$workArea[0].parentNode.scrollTop + This.$nodeData[id].height > t.top - c.top + This.$workArea.height()) {
                    Y = t.top - c.top + This.$workArea.height() - This.$workArea[0].parentNode.scrollTop - This.$nodeData[id].height;
                }
                This.$ghost.css({left: X - 230 + "px", top: Y - 133 + "px"});
                isMove = true;
            }
            document.onmouseup = function (e) {
                if (isMove) This.moveNode(id, X + This.$workArea[0].parentNode.scrollLeft - t.left + c.left, Y + This.$workArea[0].parentNode.scrollTop - t.top + c.top);
                {
                    This.$ghost.empty().hide();
        else {//如果是连接线
            if (this.onItemFocus != null && !this.onItemFocus(id, "line"))    return;
            that.$canDelLine = true;
            that.$deteNodeId = '';
            that.$deteLineId = id;
            $(document).keydown(function (e) {
                if (e.keyCode == 8 && that.$deteLineId) {
                    that.delLine(that.$deteLineId);
                    $('.GooFlow_line_oper').hide();
                }
                document.onmousemove = null;
                document.onmouseup = null;
            }
        });
        /*
         * Todo: 鼠标移入结点 【结点--mouseenter 事件 @@】
         * Author: LE
         * Date: 2017/2/20
         * time: 10:22
         * */
        this.$workArea.delegate(".GooFlow_item", "mouseenter", {inthis: this}, function (e) {
            var This = e.data.inthis;
            var moving = This.$lineMoveing;
            if (!moving) {
                var w = $(this).width() + 60 + 'px';
                var h = $(this).height() + 30 + 'px';
                $(this).append("<div class='line-begin' style='width:" + w + ";height:" + h + "'><div class='line-begin-icon top'></div><div class='line-begin-icon left'></div><div class='line-begin-icon right'></div><div class='line-begin-icon bottom'></div></div>")
            }
            if (moving) {
                $(this).addClass('item-mark');
            }
        });
        /*
         * Todo: 鼠标移出结点 【结点--mouseleave 事件 @@】
         * Author: LE
         * Date: 2017/2/20
         * time: 10:22
         * */
        this.$workArea.delegate(".GooFlow_item", "mouseleave", {inthis: this}, function (e) {
            $(this).children('.line-begin').remove();
            $(this).removeClass('item-mark');
        });
        /*
         * Todo: 连线事件 【连线按钮--mousedown 事件  $$$】
         * Author: LE
         * Date: 2017/2/20
         * time: 10:23
         * */
        this.$workArea.delegate(".GooFlow_item .line-begin > *", "mousedown", {inthis: this}, function (e) {
            var cs = $(this).attr("class").split(/\s+/);
            var This = e.data.inthis;
            This.$lineMoveing = true;
            This.$lineType = cs[1];
            var obj = $(this).parent().parent();
            This.$nowType = "cursor";
            var type = obj.children('.GooFlow_item_cont').children('.GooFlow_item_txt').attr("id").split("type-")[1]
            // 将划线开始点定为元素中心点
            var X, Y;
            var objPosition = obj.position();
            var objLeft = objPosition.left;
            var objTop = objPosition.top;
            var w = (obj.width()) / 2;
            var h = (obj.height()) / 2;
            X = objLeft + w;
            Y = objTop + h;
            This.$workArea.data("lineStart", {
                "x": X,
                "y": Y,
                "id": obj[0].id,
                "typeFrom": type
            }).css("cursor", "crosshair");
            var line = GooFlow.prototype.drawLine("GooFlow_tmp_line", [X, Y], [X, Y], true, true);
            This.$draw.appendChild(line);
            $(document).mouseup(function () {
                $('.GooFlow_item').removeClass('item-mark');
                This.$lineMoveing = false;
            })
            return false;
        });
        /*
         * Todo: 连线结束事件 【连线按钮--mousedown 事件  $$$】
         * Author: LE
         * Date: 2017/2/20
         * time: 10:24
         * */
        this.$workArea.delegate(".GooFlow_item", "mouseup", {inthis: this}, function (e) {
            var type = $(this).children('.GooFlow_item_cont').children('.GooFlow_item_txt').attr("id").split("type-")[1]
            var This = e.data.inthis;
            var obj = $(this);
            if (This.$nowType != "cursor") {
                return;
            if (GooFlow.prototype.useSVG != "") {
                jq[0].childNodes[1].setAttribute("stroke", "#00b7ee");
                jq[0].childNodes[1].setAttribute("marker-end", "url(#arrow2)");
            }
            var lineT,lineM;
            var lineType = This.$lineType;
            switch (lineType){
                case 'top':
                case 'bottom':
                    lineT = 'tb';
                    break;
                case 'left':
                case 'right':
                    lineT = 'lr';
                    break;
            else    jq[0].strokeColor = "#00b7ee";
            if (!this.$editable) return;
            var x, y, from, to;
            if (GooFlow.prototype.useSVG != "") {
                from = jq.attr("from").split(",");
                to = jq.attr("to").split(",");
            } else {
                var n = jq[0].getAttribute("fromTo").split(",");
                from = [n[0], n[1]];
                to = [n[2], n[3]];
            }
            from[0] = parseInt(from[0], 10);
            from[1] = parseInt(from[1], 10);
            to[0] = parseInt(to[0], 10);
            to[1] = parseInt(to[1], 10);
            var lineStart = This.$workArea.data("lineStart");
            console.log("lineStart"+lineStart);
            var n1 = This.$nodeData[lineStart.id], n2 = This.$nodeData[obj[0].id];
            //To结点中心点位置判断
            var sLeft = n1.left;
            var sRight = sLeft + n1.width;
            var sTop =  n1.top;
            var sBottom = sTop + n1.height;
            var EXcenter = n2.left+n2.width/2;
            var EYcenter = n2.top+n2.height/2;
            if (this.$lineData[id].type == "lr") {
                from[0] = this.$lineData[id].M;
                to[0] = from[0];
            // 当To结点中心点在From结点内时,用直线
            if ((EXcenter >= sLeft && EXcenter <= sRight)||(EYcenter >= sTop && EYcenter <= sBottom)) {
                lineT = 'sl';
                lineM = '';
                this.$lineMove.css({
                    width: "5px", height: (to[1] - from[1]) * (to[1] > from[1] ? 1 : -1) + "px",
                    left: from[0] - 3 + "px",
                    top: (to[1] > from[1] ? from[1] : to[1]) + 1 + "px",
                    cursor: "e-resize", display: "block"
                }).data({"type": "lr", "tid": id});
            }
            
            var lineM = GooFlow.prototype.getMValue(n1,n2,lineT);
            if (lineStart) {
                This.addLine(This.$id + "_line_" + This.$lineCount, {
                    from: lineStart.id,
                    to: obj[0].id,
                    name: "",
                    x: lineStart.x,
                    y: lineStart.y,
                    typeFrom: lineStart.typeFrom,
                    typeTo: type,
                    type: lineT,
                    M: lineM
                });
            else if (this.$lineData[id].type == "tb") {
                from[1] = this.$lineData[id].M;
                to[1] = from[1];
                this.$lineMove.css({
                    width: (to[0] - from[0]) * (to[0] > from[0] ? 1 : -1) + "px", height: "5px",
                    left: (to[0] > from[0] ? from[0] : to[0]) + 1 + "px",
                    top: from[1] - 3 + "px",
                    cursor: "s-resize", display: "block"
                }).data({"type": "tb", "tid": id});
            }
        });
            x = (from[0] + to[0]) / 2 - 35;
            y = (from[1] + to[1]) / 2 + 6;
            this.$lineOper.css({display: "block", left: x + "px", top: y + "px"}).data("tid", id);
        }
        this.$focus = id;
        this.$nowType = "cursor";
    },
    /*
     * Todo: ### 工作区事件绑定结束 ************************************************
     * */
	/*
	 * Todo: 增加结点 【结点公共方法 @@】
@ -1075,7 +1115,7 @@ GooFlow.prototype = {
	 * Date: 2017/2/20
	 * time: 9:53
	 * */
    addNode: function (id, json,isDataLoad) {
    addNode: function (id, json) {
        if (this.onItemAdd != null && !this.onItemAdd(id, "node", json))return;
        if (this.$undoStack) {
            this.pushOper("delNode", [id]);
@ -1084,45 +1124,46 @@ GooFlow.prototype = {
        if (json.type != "start" && json.type != "end") {
            if (json.top < 0) json.top = 0;
            if (json.left < 0) json.left = 0;
            var hack = 0;
            if (navigator.userAgent.indexOf("8.0") != -1) hack = 2;
            switch (json.type) {
                case "state":
                    json.name = '判断';
                    json.config = '';
                    json.nodeType = 'judgement'
                    break;
                case "complex":
                    json.name = '循环';
                    json.config = 'splitJsonArray';
                    json.nodeType = 'circle'
                    break;
                case "join":
                    json.name = '聚合';
                    json.config = '';
                    json.nodeType = 'aggregate'
                    break;
                case "fork":
                    json.name = '分流';
                    json.config = '';
                    json.nodeType = 'multicast'
                    break;
                case "task":
                    json.name = this.$newNodeTxt || json.name;
                    json.value = this.$newNodeValue || json.value;
                    json.nodeType = 'service'
                    json.config = this.$nodeConfig || json.config;
                    json.nodeType = this.$nodeType || json.nodeType;
                    break;
                case "plug":
                    json.name = this.$newNodeTxt || json.name;
                    json.value = this.$newNodeValue || json.value;
                    json.nodeType = 'processor'
                    json.config = this.$nodeConfig || json.config;
                    json.nodeType = this.$nodeType || json.nodeType;
                    break;
            }
            this.$nodeDom[id] = $(" <div class='GooFlow_item node-" + json.type + "' id='" + id + "' style='top:" + json.top + "px;left:" + json.left + "px'> <div class='GooFlow_item_cont'> <i class='fa ico_" + json.type + "'></i>   <span class='GooFlow_item_txt' id='type-" + json.type + "'>" + json.name + "</span></div> <div class='rs-box' style='display:none'> <div class='rs_close'><i class='fa fa-close' aria-hidden='true'></i></div> </div> </div>");
            this.$nodeDom[id] = $("<div class='GooFlow_item node-" + json.type + "' id='" + id + "' style='top:" + json.top + "px;left:" + json.left + "px'><div class='GooFlow_item_cont'> <i class='fa ico_" + json.type + "'></i><span class='GooFlow_item_txt' id='type-" + json.type + "'>" + json.name + "</span></div> <div class='rs-box' style='display:none'><div class='rs_close'><i class='fa fa-close' aria-hidden='true'></i></div></div></div>");
        }
        var ua = navigator.userAgent.toLowerCase();
        if (ua.indexOf('msie') != -1 && ua.indexOf('8.0') != -1) {
            this.$nodeDom[id].css("filter", "progid:DXImageTransform.Microsoft.Shadow(color=#94AAC2,direction=135,strength=2)");
        }
        this.$workArea.append(this.$nodeDom[id]);
        json.width = this.$nodeDom[id].width();
        json.height = this.$nodeDom[id].height();
@ -1130,252 +1171,240 @@ GooFlow.prototype = {
        ++this.$nodeCount;
    },
    /*
     * Todo: 取消选定状态 【取消聚焦公共方法】
     * Author: LE
     * Date: 2017/2/20
     * time: 9:53
     * */
    blurItem: function () {
        this.$deteLineId = '';
        this.$deteNodeId = '';
        if (this.$focus != "") {
            // 获取被选中对象
            var jq = $("#" + this.$focus);
            if (jq.prop("tagName") == "DIV") {
                if (this.onItemBlur != null && !this.onItemBlur(id, "node"))  return false;
                jq.removeClass("item_focus").children(".rs-box").css("display", "none");
            }
            else {
                if (this.onItemBlur != null && !this.onItemBlur(id, "line"))  return false;
                if (GooFlow.prototype.useSVG != "") {
                    if (!this.$lineData[this.$focus].marked) {
                        jq[0].childNodes[1].setAttribute("stroke", "#323232");
                        jq[0].childNodes[1].setAttribute("marker-end", "url(#arrow1)");
                    }
                }
                else {
                    if (!this.$lineData[this.$focus].marked) jq[0].strokeColor = "#323232";
                }
                this.$lineMove.hide().removeData("type").removeData("tid");
                if (this.$editable) this.$lineOper.hide().removeData("tid");
	/*
	 * Todo: 删除结点 【结点公共方法 @@】
	 * Author: LE
	 * Date: 2017/2/20
	 * time: 9:55
	 * */
    delNode: function (id) {
        if (!this.$nodeData[id]) return;
        if (this.$undoStack) {
            var paras = [id, this.$nodeData[id]];
            this.pushOper("addNode", paras);
        }
        if (this.onItemDel != null && !this.onItemDel(id, "node"))    return;
        delete this.$nodeData[id];
        this.$nodeDom[id].remove();
        delete this.$nodeDom[id];
        if (this.$focus == id) this.$focus = "";
        for (var k in this.$lineData) {
            if (this.$lineData[k].from == id || this.$lineData[k].to == id) {
                this.$draw.removeChild(this.$lineDom[k]);
                delete this.$lineData[k];
                delete this.$lineDom[k];
            }
        }
        this.$focus = "";
        return true;
    },
    /*
     * Todo: 结点或线条选中事件 【聚焦公共方法】
     * Author: LE
     * Date: 2017/2/20
     * time: 9:53
     * */
    focusItem: function (id, bool) {
	/*
	 * Todo: 结点移动 【结点移动方法 @@】
	 * Author: LE
	 * Date: 2017/2/20
	 * time: 9:54
	 * */
    moveNode: function (id, left, top) {
        if (!this.$nodeData[id]) return;
        if (this.onItemMove != null && !this.onItemMove(id, "node", left, top)) return;
        if (this.$undoStack) {
            var paras = [id, this.$nodeData[id].left, this.$nodeData[id].top];
            this.pushOper("moveNode", paras);
        }
        if (left < 0) left = 0;
        if (top < 0) top = 0;
        $("#" + id).css({left: left + "px", top: top + "px"});
        this.$nodeData[id].left = left;
        this.$nodeData[id].top = top;
        //重画转换线
        this.resetLines(id, this.$nodeData[id]);
    },
        var jq = $("#" + id);
        var that = this;
        this.$ghost.attr({'class': 'rs_ghost ' + jq.attr('class')});
        if (jq.length == 0)    return;
        if (!this.blurItem())    return;//先执行"取消选中",如果返回FLASE,则也会阻止选定事件继续进行.
        if (jq.prop("tagName") == "DIV") {
            if (bool && this.onItemFocus != null && !this.onItemFocus(id, "node"))  return;
            jq.addClass("item_focus");
            if (this.$editable) jq.children(".rs-box").css("display", "block");
            this.$workArea.append(jq);
	        that.$canDelLine = false;
	        that.$deteLineId = '';
	        that.$deteNodeId = id;
	        $(document).keydown(function (e) {
		        if (e.keyCode != 8 && that.$deteNodeId) {return}
		        that.delNode(that.$deteNodeId);
	        });
	/*
	 * Todo: 设置结点线等内容 【内容设置公共方法 @@ $$】
	 * Author: LE
	 * Date: 2017/2/20
	 * time: 9:54
	 * */
    setName: function (id, config, type) {
        var oldName;
        if (type == "node") {//如果是结点
            if (!this.$nodeData[id]) return;
            this.$nodeData[id].config = config;
        }
        else if (type == "line") {//如果是线
            if (!this.$lineData[id]) return;
            if (this.$lineData[id].config == config)   return;
            oldName = this.$lineData[id].value;
            this.$lineData[id].config = config;
            this.$lineDom[id].childNodes[2].textContent = config;
        }
        else {//如果是连接线
            if (this.onItemFocus != null && !this.onItemFocus(id, "line"))    return;
	        that.$canDelLine = true;
	        that.$deteNodeId = '';
	        that.$deteLineId = id;
            $(document).keydown(function (e) {
                if (e.keyCode == 8 && that.$deteLineId){
	                that.delLine(that.$deteLineId);
	                $('.GooFlow_line_oper').hide();
                }
            })
    },
            if (GooFlow.prototype.useSVG != "") {
                jq[0].childNodes[1].setAttribute("stroke", "#00b7ee");
                jq[0].childNodes[1].setAttribute("marker-end", "url(#arrow2)");
	/*
	 * Todo: 增加线条 【$$】
	 * Author: LE
	 * Date: 2017/2/15
	 * time: 16:46
	 * */
    addLine: function (id, json, isDataLoad) {
        if (!isDataLoad) {
            if (this.$undoStack) {
                this.pushOper("delLine", [id]);
            }
            else    jq[0].strokeColor = "#00b7ee";
            if (!this.$editable) return;
            var x, y, from, to;
            if (GooFlow.prototype.useSVG != "") {
                from = jq.attr("from").split(",");
                to = jq.attr("to").split(",");
            } else {
                var n = jq[0].getAttribute("fromTo").split(",");
                from = [n[0], n[1]];
                to = [n[2], n[3]];
            if (json.from == json.to) {
                return;
            }
            from[0] = parseInt(from[0], 10);
            from[1] = parseInt(from[1], 10);
            to[0] = parseInt(to[0], 10);
            to[1] = parseInt(to[1], 10);
            if (this.$lineData[id].type == "lr") {
                from[0] = this.$lineData[id].M;
                to[0] = from[0];
            var typeF = json.typeFrom || json.type;
            var typeT = json.typeTo || json.type;
            for (var k in this.$lineData) {
                // 单出
                var onlyOut = (json.from == this.$lineData[k].from);
                // 单进
                var onlyIn = (json.to == this.$lineData[k].to);
                // 来源等于去向
                var formEto = (json.from == this.$lineData[k].to);
                // 去向等于来源
                var tofrom = (json.to == this.$lineData[k].from);
                // 不能反向
                if (tofrom && formEto) {
                    return;
                }
                // 设置除了聚合其余都默认单进
                if (typeT != 'join') {
                    if (onlyIn) {
                        return;
                    }
                }
                switch (typeF) {
                    case 'task':
                    case 'plug':
                        if (onlyOut) {
                            return;
                        }
                        break;
                    case 'state':
                    case 'complex':
                        if (json.from == this.$lineData[k].from) {
                            var i = 0;
                            for (var j in this.$lineData) {
                                if (json.from == this.$lineData[j].from) {
                                    i++
                                }
                            }
                            if (i >= 2) {
                                return;
                            }
                        }
                        break;
                }
                this.$lineMove.css({
                    width: "5px", height: (to[1] - from[1]) * (to[1] > from[1] ? 1 : -1) + "px",
                    left: from[0] - 3 + "px",
                    top: (to[1] > from[1] ? from[1] : to[1]) + 1 + "px",
                    cursor: "e-resize", display: "block"
                }).data({"type": "lr", "tid": id});
            }
            else if (this.$lineData[id].type == "tb") {
                from[1] = this.$lineData[id].M;
                to[1] = from[1];
                this.$lineMove.css({
                    width: (to[0] - from[0]) * (to[0] > from[0] ? 1 : -1) + "px", height: "5px",
                    left: (to[0] > from[0] ? from[0] : to[0]) + 1 + "px",
                    top: from[1] - 3 + "px",
                    cursor: "s-resize", display: "block"
                }).data({"type": "tb", "tid": id});
            }
            x = (from[0] + to[0]) / 2 - 35;
            y = (from[1] + to[1]) / 2 + 6;
            this.$lineOper.css({display: "block", left: x + "px", top: y + "px"}).data("tid", id);
        }
        this.$focus = id;
        this.$nowType = "cursor";
    },
    /*
     * Todo: 结点移动 【结点移动方法 @@】
     * Author: LE
     * Date: 2017/2/20
     * time: 9:54
     * */
    moveNode: function (id, left, top) {
        if (!this.$nodeData[id]) return;
        if (this.onItemMove != null && !this.onItemMove(id, "node", left, top)) return;
        if (this.$undoStack) {
            var paras = [id, this.$nodeData[id].left, this.$nodeData[id].top];
            this.pushOper("moveNode", paras);
        //获取开始/结束结点的数据
        var n1 = this.$nodeData[json.from], n2 = this.$nodeData[json.to];
        if (!n1 || !n2) {
            return;
        }
        // 划线函数以及线条类型处理
        var res;
        if (json.type && json.type != "sl") {
            res = GooFlow.prototype.calcPolyPoints(n1, n2, json.type, json.M);
        }
        else {
            res = GooFlow.prototype.calcStartEnd(n1, n2);// 刚开始连线时,默认为直线
        }
        if (!res) {
            return;
        }
        // 存储线条
        this.$lineData[id] = {};
        if (json.type) {
            this.$lineData[id].type = json.type;
            this.$lineData[id].M = json.M;
        }
        else {
            this.$lineData[id].type = "sl";//默认为直线
        }
        this.$lineData[id].from = json.from;
        this.$lineData[id].to = json.to;
        this.$lineData[id].config = json.name;
        if (json.mark) {
            this.$lineData[id].marked = json.mark;
        }
        else {
            this.$lineData[id].marked = false;
        }
        if (left < 0) left = 0;
        if (top < 0) top = 0;
        $("#" + id).css({left: left + "px", top: top + "px"});
        this.$nodeData[id].left = left;
        this.$nodeData[id].top = top;
        //重画转换线
        this.resetLines(id, this.$nodeData[id]);
    },
    /*
     * Todo: 设置结点线等内容 【内容设置公共方法 @@ $$$】
     * Author: LE
     * Date: 2017/2/20
     * time: 9:54
     * */
    setName: function (id, name, type) {
        var oldName;
        if (type == "node") {//如果是结点
            if (!this.$nodeData[id]) return;
            if (this.$nodeData[id].name == name)   return;
            if (this.onItemRename != null && !this.onItemRename(id, name, "node")) return;
            oldName = this.$nodeData[id].name;
            this.$nodeData[id].name = name;
            if (this.$nodeData[id].type == "start" || this.$nodeData[id].type == "end") {
                this.$nodeDom[id].children(".span").text(name);
            }
            else {
                this.$nodeDom[id].find("td:eq(1)").text(name);
                var hack = 0;
                if (navigator.userAgent.indexOf("8.0") != -1) hack = 2;
                var width = this.$nodeDom[id].outerWidth();
                var height = this.$nodeDom[id].outerHeight();
                this.$nodeDom[id].children("table").css({width: width - 2 + "px", height: height - 2 + "px"});
                this.$nodeData[id].width = width;
                this.$nodeData[id].height = height;
            }
            //重画转换线
            this.resetLines(id, this.$nodeData[id]);
        if (this.$lineData[id].type == "sl") {
            this.$lineDom[id] = GooFlow.prototype.drawLine(id, res.start, res.end, json.mark);
        }
        else if (type == "line") {//如果是线
            if (!this.$lineData[id]) return;
            if (this.$lineData[id].name == name)   return;
            if (this.onItemRename != null && !this.onItemRename(id, name, "line")) return;
            oldName = this.$lineData[id].name;
            this.$lineData[id].name = name;
            if (GooFlow.prototype.useSVG != "") {
                this.$lineDom[id].childNodes[2].textContent = name;
            }
            else {
                this.$lineDom[id].childNodes[1].innerHTML = name;
                var n = this.$lineDom[id].getAttribute("fromTo").split(",");
                var x;
                if (this.$lineData[id].type != "lr") {
                    x = (n[2] - n[0]) / 2;
                }
                else {
                    var Min = n[2] > n[0] ? n[0] : n[2];
                    if (Min > this.$lineData[id].M) Min = this.$lineData[id].M;
                    x = this.$lineData[id].M - Min;
                }
                if (x < 0) x = x * -1;
                this.$lineDom[id].childNodes[1].style.left = x - this.$lineDom[id].childNodes[1].offsetWidth / 2 + 4 + "px";
            }
        else {
            this.$lineDom[id] = GooFlow.prototype.drawPoly(id, res.start, res.m1, res.m2, res.end, json.mark);
        }
        else if (type == "area") {//如果是分组区域
            if (!this.$areaData[id]) return;
            if (this.$areaData[id].name == name)   return;
            if (this.onItemRename != null && !this.onItemRename(id, name, "area")) return;
            oldName = this.$areaData[id].name;
            this.$areaData[id].name = name;
            this.$areaDom[id].children("label").text(name);
        this.$draw.appendChild(this.$lineDom[id]);
        if (GooFlow.prototype.useSVG == "") {
            this.$lineDom[id].childNodes[1].innerHTML = json.name;
            if (this.$lineData[id].type != "sl") {
                var Min = (res.start[0] > res.end[0] ? res.end[0] : res.start[0]);
                if (Min > res.m2[0]) Min = res.m2[0];
                if (Min > res.m1[0]) Min = res.m1[0];
                this.$lineDom[i].childNodes[1].style.left = (res.m2[0] + res.m1[0]) / 2 - Min - this.$lineDom[id].childNodes[1].offsetWidth / 2 + 4;
                Min = (res.start[1] > res.end[1] ? res.end[1] : res.start[1]);
                if (Min > res.m2[1]) Min = res.m2[1];
                if (Min > res.m1[1]) Min = res.m1[1];
                this.$lineDom[id].childNodes[1].style.top = (res.m2[1] + res.m1[1]) / 2 - Min - this.$lineDom[id].childNodes[1].offsetHeight / 2;
            } else
                this.$lineDom[id].childNodes[1].style.left =
                    ((res.end[0] - res.start[0]) * (res.end[0] > res.start[0] ? 1 : -1) - this.$lineDom[id].childNodes[1].offsetWidth) / 2 + 4;
        }
        if (this.$undoStack) {
            var paras = [id, oldName, type];
            this.pushOper("setName", paras);
        else {
            this.$lineDom[id].childNodes[2].textContent = json.name;
        }
        ++this.$lineCount;
    },
    /*
     * Todo: 删除结点 【结点公共方法 @@】
     * Author: LE
     * Date: 2017/2/20
     * time: 9:55
     * */
    delNode: function (id) {
        if (!this.$nodeData[id]) return;
	/*
	 * Todo: 线条删除  【$$】
	 * Author: LE
	 * Date: 2017/2/20
	 * time: 9:57
	 * */
    delLine: function (id) {
        if (!this.$lineData[id]) return;
        if (this.$undoStack) {
            var paras = [id, this.$nodeData[id]];
            this.pushOper("addNode", paras);
            var paras = [id, this.$lineData[id]];
            this.pushOper("addLine", paras);
        }
        if (this.onItemDel != null && !this.onItemDel(id, "node"))    return;
        delete this.$nodeData[id];
        this.$nodeDom[id].remove();
        delete this.$nodeDom[id];
        this.$draw.removeChild(this.$lineDom[id]);
        delete this.$lineData[id];
        delete this.$lineDom[id];
        if (this.$focus == id) this.$focus = "";
        for (var k in this.$lineData) {
            if (this.$lineData[k].from == id || this.$lineData[k].to == id) {
                this.$draw.removeChild(this.$lineDom[k]);
                delete this.$lineData[k];
                delete this.$lineDom[k];
            }
        }
    },
    /*
     * Todo: 直线绘制 【$$】
     * Author: LE
     * Date: 2017/2/15
     * time: 14:57
     * */
	/*
	 * Todo: 直线生成 【$$】
	 * Author: LE
	 * Date: 2017/2/15
	 * time: 14:57
	 * */
    drawLine: function (id, sp, ep, mark, dash) {
        var line;
        if (GooFlow.prototype.useSVG != "") {
@ -1448,12 +1477,12 @@ GooFlow.prototype = {
        return line;
    },
    /*
     * Todo: 折线绘制 【$$】
     * Author: LE
     * Date: 2017/2/20
     * time: 10:26
     * */
	/*
	 * Todo: 折线生成 【$$】
	 * Author: LE
	 * Date: 2017/2/20
	 * time: 10:26
	 * */
    drawPoly: function (id, sp, m1, m2, ep, mark) {
        var poly, strPath;
        if (GooFlow.prototype.useSVG != "") {
@ -1530,12 +1559,12 @@ GooFlow.prototype = {
        return poly;
    },
    /*
     * Todo: 直线坐标计算  【$$】
     * Author: LE
     * Date: 2017/2/20
     * time: 10:26
     * */
	/*
	 * Todo: 直线坐标计算  【$$】
	 * Author: LE
	 * Date: 2017/2/20
	 * time: 10:26
	 * */
    calcStartEnd: function (n1, n2) {
        if (!n1 || !n2) {
            return {"start": [], "end": []};
@ -1584,12 +1613,12 @@ GooFlow.prototype = {
    },
    /*
     * Todo: 折线坐标计算  【$$】
     * Author: LE
     * Date: 2017/2/20
     * time: 10:27
     * */
	/*
	 * Todo: 折线坐标计算  【$$】
	 * Author: LE
	 * Date: 2017/2/20
	 * time: 10:27
	 * */
    calcPolyPoints: function (n1, n2, type, M) {
        //开始/结束两个结点的中心
@ -1651,12 +1680,12 @@ GooFlow.prototype = {
        return {start: sp, m1: m1, m2: m2, end: ep};
    },
    /*
     * Todo: 折线固定中线计算  【$$】
     * Author: LE
     * Date: 2017/2/20
     * time: 10:27
     * */
	/*
	 * Todo: 折线固定中线计算  【$$】
	 * Author: LE
	 * Date: 2017/2/20
	 * time: 10:27
	 * */
    getMValue: function (n1, n2, mType) {
        if (mType == "lr") {
            return (n1.left + n1.width / 2 + n2.left + n2.width / 2) / 2;
@ -1666,154 +1695,12 @@ GooFlow.prototype = {
        }
    },
    /*
     * Todo: 增加线条 【$$】
     * Author: LE
     * Date: 2017/2/15
     * time: 16:46
     * */
    addLine: function (id, json,isDataLoad) {
        if (!isDataLoad) {
	        if (this.onItemAdd != null && !this.onItemAdd(id, "line", json)) {
		        return;
	        }
	        if (this.$undoStack) {
		        this.pushOper("delLine", [id]);
	        }
	        if (json.from == json.to) {
		        return;
	        }
	        var typeF = json.typeFrom || json.type;
	        var typeT = json.typeTo || json.type;
	        for (var k in this.$lineData) {
		        // 单出
		        var onlyOut = (json.from == this.$lineData[k].from);
		        // 单进
		        var onlyIn = (json.to == this.$lineData[k].to);
		        // 来源等于去向
		        var formEto = (json.from == this.$lineData[k].to);
		        // 去向等于来源
		        var tofrom = (json.to == this.$lineData[k].from);
		        // 不能反向
		        if (tofrom && formEto) {
			        return;
		        }
		        // 设置除了聚合其余都默认单进
		        if (typeT != 'join') {
			        if (onlyIn) {
				        return;
			        }
		        }
		        switch (typeF) {
			        case 'task':
			        case 'plug':
				        if (onlyOut) {
					        return;
				        }
				        break;
			        case 'state':
			        case 'complex':
				        if (json.from == this.$lineData[k].from) {
					        var i = 0;
					        for (var j in this.$lineData) {
						        if (json.from == this.$lineData[j].from) {
							        i++
						        }
					        }
					        if (i >= 2) {
						        return;
					        }
				        }
				        break;
		        }
	        }
        }
        //获取开始/结束结点的数据
        var n1 = this.$nodeData[json.from], n2 = this.$nodeData[json.to];
        if (!n1 || !n2) {
            return;
        }
        // 划线函数以及线条类型处理
        var res;
        if (json.type && json.type != "sl") {
            res = GooFlow.prototype.calcPolyPoints(n1, n2, json.type,json.M);
        }
        else {
            res = GooFlow.prototype.calcStartEnd(n1, n2);// 刚开始连线时,默认为直线
        }
        if (!res) {
            return;
        }
        // 存储线条
        this.$lineData[id] = {};
        if (json.type) {
            this.$lineData[id].type = json.type;
            this.$lineData[id].M = json.M;
        }
        else {
            this.$lineData[id].type = "sl";//默认为直线
        }
        this.$lineData[id].from = json.from;
        this.$lineData[id].to = json.to;
        this.$lineData[id].name = json.name;
        if (json.mark) {
            this.$lineData[id].marked = json.mark;
        }
        else {
            this.$lineData[id].marked = false;
        }
        if (this.$lineData[id].type == "sl") {
            this.$lineDom[id] = GooFlow.prototype.drawLine(id, res.start, res.end, json.mark);
        }
        else {
            this.$lineDom[id] = GooFlow.prototype.drawPoly(id, res.start, res.m1, res.m2, res.end, json.mark);
        }
        this.$draw.appendChild(this.$lineDom[id]);
        if (GooFlow.prototype.useSVG == "") {
            this.$lineDom[id].childNodes[1].innerHTML = json.name;
            if (this.$lineData[id].type != "sl") {
                var Min = (res.start[0] > res.end[0] ? res.end[0] : res.start[0]);
                if (Min > res.m2[0]) Min = res.m2[0];
                if (Min > res.m1[0]) Min = res.m1[0];
                this.$lineDom[i].childNodes[1].style.left = (res.m2[0] + res.m1[0]) / 2 - Min - this.$lineDom[id].childNodes[1].offsetWidth / 2 + 4;
                Min = (res.start[1] > res.end[1] ? res.end[1] : res.start[1]);
                if (Min > res.m2[1]) Min = res.m2[1];
                if (Min > res.m1[1]) Min = res.m1[1];
                this.$lineDom[id].childNodes[1].style.top = (res.m2[1] + res.m1[1]) / 2 - Min - this.$lineDom[id].childNodes[1].offsetHeight / 2;
            } else
                this.$lineDom[id].childNodes[1].style.left =
                    ((res.end[0] - res.start[0]) * (res.end[0] > res.start[0] ? 1 : -1) - this.$lineDom[id].childNodes[1].offsetWidth) / 2 + 4;
        }
        else {
            this.$lineDom[id].childNodes[2].textContent = json.name;
        }
        ++this.$lineCount;
    },
    /*
     * Todo: 重置线条  【$$】
     * Author: LE
     * Date: 2017/2/20
     * time: 9:56
     * */
	/*
	 * Todo: 重置线条  【$$】
	 * Author: LE
	 * Date: 2017/2/20
	 * time: 9:56
	 * */
    resetLines: function (id, node) {
        for (var i in this.$lineData) {
            var other = null;//获取结束/开始结点的数据
@ -1864,15 +1751,14 @@ GooFlow.prototype = {
        }
    },
    /*
     * Todo: 重置线条类型  【$$】
     * Author: LE
     * Date: 2017/2/20
     * time: 9:56
     * */
	/*
	 * Todo: 重置线条类型  【$$】
	 * Author: LE
	 * Date: 2017/2/20
	 * time: 9:56
	 * */
    setLineType: function (id, newType) {
        if (!newType || newType == null || newType == "" || newType == this.$lineData[id].type)  return false;
        if (this.onLineSetType != null && !this.onLineSetType(id, newType))   return;
        if (this.$undoStack) {
            var paras = [id, this.$lineData[id].type];
            this.pushOper("setLineType", paras);
@ -1912,15 +1798,14 @@ GooFlow.prototype = {
        }
    },
    /*
     * Todo: 重置折线中点  【$$】
     * Author: LE
     * Date: 2017/2/20
     * time: 9:57
     * */
	/*
	 * Todo: 重置折线中点  【$$】
	 * Author: LE
	 * Date: 2017/2/20
	 * time: 9:57
	 * */
    setLineM: function (id, M, noStack) {
        if (!this.$lineData[id] || M < 0 || !this.$lineData[id].type || this.$lineData[id].type == "sl")   return false;
        if (this.onLineMove != null && !this.onLineMove(id, M))   return false;
        if (this.$undoStack && !noStack) {
            var paras = [id, this.$lineData[id].M];
            this.pushOper("setLineM", paras);
@ -1947,282 +1832,228 @@ GooFlow.prototype = {
    },
    /*
     * Todo: 线条删除  【$$】
     * Author: LE
     * Date: 2017/2/20
     * time: 9:57
     * */
    delLine: function (id) {
        if (!this.$lineData[id]) return;
        if (this.onItemDel != null && !this.onItemDel(id, "line"))    return;
        if (this.$undoStack) {
            var paras = [id, this.$lineData[id]];
            this.pushOper("addLine", paras);
        }
        this.$draw.removeChild(this.$lineDom[id]);
        delete this.$lineData[id];
        delete this.$lineDom[id];
        if (this.$focus == id) this.$focus = "";
    },
    /*
     * Todo: 初始化数据加载 【AJAX相关】
     * Author: LE
     * Date: 2017/2/15
     * time: 15:02
     * */
    loadData: function (data) {
        for (var i in data.nodes) {
            this.addNode(i, data.nodes[i]);
        }
	    for (var j in data.lines) {
            this.addLine(j, data.lines[j],true);
        }
	/*
	 * Todo: 初始化数据加载 【AJAX相关】
	 * Author: LE
	 * Date: 2017/2/15
	 * time: 15:02
	 * */
    loadData: function (url) {
    	var that = this;
	    $.ajax({
		    url: url,
		    type: 'GET',
		    async: true,
		    dataType: 'json',
		    success: function (data) {
		    	var data = $.parseJSON(data.message);
			    that.fileMsg(data.code,data.name);
			    for (var i in data.result.nodes) {
				    that.addNode(i, data.result.nodes[i]);
			    }
			    for (var j in data.result.lines) {
				    that.addLine(j, data.result.lines[j], true);
			    }
		    },
		    error: function (data, xhr, textStatus) {
			    console.log('错误')
			    console.log(xhr)
			    console.log(textStatus)
		    },
	    })
    },
    /*
     * Todo: AJAX数据加载  【AJAX相关】
     * Author: LE
     * Date: 2017/2/15
     * time: 15:02
     * */
    loadDataAjax: function (para) {
        var This = this;
        $.ajax({
            type: para.type,
            url: para.url,
            dataType: "json",
            data: para.data,
            success: function (msg) {
                if (para.dataFilter) para.dataFilter(msg, "json");
                This.loadData(msg);
                if (para.success) para.success(msg);
            },
            error: function (XMLHttpRequest, textStatus, errorThrown) {
                if (para.error) para.error(textStatus, errorThrown);
            }
        })
	fileMsg:function (code,name) {
		$('#fileCode').val(code);
		$('#fileName').val(name);
    },
    /*
     * Todo: 导出数据  【AJAX相关】
     * Author: LE
     * Date: 2017/2/20
     * time: 9:58
     * */
	/*
	 * Todo: 导出数据  【AJAX相关】
	 * Author: LE
	 * Date: 2017/2/20
	 * time: 9:58
	 * */
    exportData: function () {
        var that = this;
	    var data = {
            nodes:{},
            lines:{}
	    }
        this.$fileCode = $('#fileCode').val();
        this.$fileName = $('#fileName').val();
        var data = {
            nodes: {},
            lines: {}
        }
        var nodes = data.nodes;
        var lines = data.lines;
	    var nodes = data.nodes;
	    var lines = data.lines;
		function keyJson(obj, target, args) {
			for (var k in obj) {
				var jsonstr = JSON.stringify(obj[k], args);
				$.parseJSON(jsonstr);
				target[k] = $.parseJSON(jsonstr)
			}
		}
        function keyJson(obj,target,args) {
            for (var k in obj) {
                var jsonstr  = JSON.stringify(obj[k],args);
                $.parseJSON(jsonstr);
	            target[k] = $.parseJSON(jsonstr)
            }
        }
		keyJson(this.$nodeData, nodes, ['name', 'value', 'nodeType', 'config']);
		keyJson(this.$lineData, lines, ['from', 'to', 'config']);
	    keyJson(this.$nodeData,nodes,['name','value','nodeType']);
	    keyJson(this.$lineData,lines,['from','to','name']);
		console.log(this.$lineData);
        var positionData = {
            nodes: this.$nodeData,
            lines: this.$lineData
        }
        var nodeArr = [];
        var specialArr = [];
        for (var i in nodes) {
            nodeArr.push(i);
            if (nodes[i].nodeType === 'judgement' || nodes[i].nodeType === 'circle'){
	            specialArr.push(i)
            }
        postData = {
            code: this.$fileCode,
            name: this.$fileName,
            positionJson: JSON.stringify(positionData),
            flowJson: JSON.stringify(data)
        }
        console.log(postData);
        var fromArr = [];
        var toArr = [];
	    for (var j in lines) {
		    fromArr.push(lines[j].from);
            toArr.push(lines[j].to);
	    }
		var nodeArr = [];
		var specialArr = [];
		var fromArr = [];
		var toArr = [];
		var noError = true;
	    // 如果存在没有连线的结点则报错
        var noError = true;
	    nodeArr.forEach(function (elem) {
		    var nodeWithoutLines = (fromArr.indexOf(elem) === -1) && (toArr.indexOf(elem)===-1)
            if (nodeWithoutLines) {
	            noError = false;
	            $('#'+elem).addClass('node-error');
            }else{
			    $('#'+elem).removeClass('node-error');
		    }
	    });
		// 存储所有结点名,并将判断和循环结点单独存储
		for (var i in nodes) {
			nodeArr.push(i);
			if (nodes[i].nodeType === 'judgement') {
				specialArr.push(i)
			}
		}
        // 判断和循环必须有两条线
	    specialArr.forEach(function (elem) {
            var i = 0;
		    fromArr.forEach(function(el){
			    if (elem === el){
			        i++
			    }
		    })
		    if (i<2) {
			    noError = false;
			    $('#'+elem).addClass('node-error');
		    }else{
			    $('#'+elem).removeClass('node-error');
			    for (var e in lines) {
			        console.log('lines[e].name:'+lines[e].name);
				   if (lines[e].from === elem){
					   if (lines[e].name == '') {
						   noError = false;
						   $('#' + e + ' path').addClass('line-error').attr("marker-end", "url(#arrow3)");
						   $('#'+elem).addClass('node-error');
					   }else{
						   $('#' + e + ' path').removeClass('line-error').attr("marker-end", "url(#arrow1)");
					   }
				   }
			    }
		    }
	    })
		// 存储所有的线条的开始结点和结束结点
	    if (noError){
		    saveConfirm();
	    for (var j in lines) {
			fromArr.push(lines[j].from);
			toArr.push(lines[j].to);
		}
		// 如果存在没有连线的结点则报错
		nodeArr.forEach(function (elem) {
			var nodeWithoutLines = (fromArr.indexOf(elem) === -1) && (toArr.indexOf(elem) === -1) || (nodes[elem].config === '')
			if (nodeWithoutLines) {
				noError = false;
				$('#' + elem).addClass('node-error');
			} else {
				$('#' + elem).removeClass('node-error');
			}
		});
		// 判断和循环必须有两条线
	    // 每个节点都必须有配置项
	    specialArr.forEach(function (elem) {
			var i = 0;
			fromArr.forEach(function (el) {
				if (elem === el) {
					i++
				}
			})
			if (i < 2) {
				noError = false;
				$('#' + elem).addClass('special-error');
			} else {
				$('#' + elem).removeClass('special-error');
				for (var e in lines) {
					if (lines[e].from === elem) {
						console.log(lines[e]);
						if (lines[e].config === '') {
							noError = false;
							$('#' + e + ' path').attr({"marker-end":"url(#arrow3)","stroke":"#f00"});
							$('#' + elem).addClass('special-error');
						} else {
							$('#' + e + ' path').attr({"marker-end":"url(#arrow1)","stroke":"#323232"});
						}
					}
				}
			}
		})
	    // 标题code和name不能为空
	    if (this.$fileCode === '' || this.$fileName === ''){
		    noError = false;
		    $('.file-box').addClass('node-error');
	    }else{
		    $.ligerDialog.alert('<p style="padding: 10px;">您还有未编排完成的对象,无法保存。</p>')
	    }
	    function saveConfirm () {
		    var callback = function  () {
		    }
		    $.ligerDialog.confirm2 = function (content, title, callback)
		    {
			    if (typeof (title) == "function")
			    {
				    callback = title;
				    type = null;
			    }
			    var btnclick = function (item, Dialog)
			    {
				    Dialog.close();
				    if (callback)
				    {
					    callback(item.type == 'ok');
				    }
			    };
			    p = {
				    type: 'question',
				    content: content,
				    buttons: [
					    {
						    text: $.ligerDefaults.DialogString.yes, onclick: btnclick, type: 'ok', cls: 'l-dialog-btn-ok'
					    }, {
						    text: $.ligerDefaults.DialogString.no, onclick: btnclick, type: 'no', cls: 'l-dialog-btn-no'
					    }
				    ]
			    };
			    if (typeof (title) == "string" && title != "") p.title = title;
			    $.extend(p, {
				    width:350,
				    showMax: false,
				    showToggle: false,
				    showMin: false
			    });
			    return $.ligerDialog(p);
		    };
		    $.ligerDialog.confirm2('<p style="font-size: 15px;font-weight: bold;text-align: center;line-height: 20px">是否确认保存为正式流程,确认后流程将立即生效?</p><p style="margin-top: 10px;color: #999;text-align: center;line-height: 20px;">如果是请按确认,如果不是请按取消</p>', '保存流程', callback)
		    $('.l-dialog-content').css({ "padding": "0 40px 20px 40px"});
		    $('.file-box').removeClass('node-error');
	    }
	    console.log(data);
    },
    /*
     * Todo: 清空数据
     * Author: LE
     * Date: 2017/2/20
     * time: 10:28
     * */
    clearData: function () {
        for (var key in this.$nodeData) {
            this.delNode(key);
        }
        for (var key in this.$lineData) {
            this.delLine(key);
        }
    },
    /*
     * Todo: 工具函数
     * Author: LE
     * Date: 2017/2/15
     * time: 15:02
     * */
    destrory: function () {
        this.$bgDiv.empty();
        this.$lineData = null;
        this.$nodeData = null;
        this.$lineDom = null;
        this.$nodeDom = null;
        this.$areaDom = null;
        this.$areaData = null;
        this.$nodeCount = 0;
    },
    //获取结点/连线/分组区域的详细信息
    getItemInfo: function (id, type) {
        switch (type) {
            case "node":
                return this.$nodeData[id] || null;
            case "line":
                return this.$lineData[id] || null;
        }
    },
    //用颜色标注/取消标注一个结点或转换线, 常用于显示重点或流程的进度。
    //这是一个在编辑模式中无用,但是在纯浏览模式中非常有用的方法, 实际运用中可用于跟踪流程的进度。
    markItem: function (id, type, mark) {
        if (type == "node") {
            if (!this.$nodeData[id]) return;
            if (this.onItemMark != null && !this.onItemMark(id, "node", mark)) return;
            this.$nodeData[id].marked = mark || false;
            if (mark) this.$nodeDom[id].addClass("item_mark");
            else    this.$nodeDom[id].removeClass("item_mark");
		// 如果没有错误则弹出保存菜单
		if (noError) {
			saveConfirm();
		} else {
			$.ligerDialog.alert('<p style="padding: 10px;">您还有未编排完成的对象,无法保存。</p>')
		}
		/*
		 * Todo: 保存发送请求
		 * Author: LE
		 * Date: 2017/2/22
		 * time: 18:59
		 * */
		function saveConfirm() {
			var callback = function (yes) {
				if (yes) {
					$.ajax({
						type: "POST",
						url: dataPostURL,
						data: postData,
						success: function () {
						}
					});
				}
			}
			$.ligerDialog.confirm2 = function (content, title, callback) {
				if (typeof (title) == "function") {
					callback = title;
					type = null;
				}
				var btnclick = function (item, Dialog) {
					Dialog.close();
					if (callback) {
						callback(item.type == 'ok');
					}
				};
				p = {
					type: 'question',
					content: content,
					buttons: [
						{
							text: $.ligerDefaults.DialogString.yes,
							onclick: btnclick,
							type: 'ok',
							cls: 'l-dialog-btn-ok'
						}, {
							text: $.ligerDefaults.DialogString.no,
							onclick: btnclick,
							type: 'no',
							cls: 'l-dialog-btn-no'
						}
					]
				};
				if (typeof (title) == "string" && title != "") p.title = title;
				$.extend(p, {
					width: 350,
					showMax: false,
					showToggle: false,
					showMin: false
				});
				return $.ligerDialog(p);
			};
			$.ligerDialog.confirm2('<p style="font-size: 15px;font-weight: bold;text-align: center;line-height: 20px">是否确认保存为正式流程,确认后流程将立即生效?</p><p style="margin-top: 10px;color: #999;text-align: center;line-height: 20px;">如果是请按确认,如果不是请按取消</p>', '保存流程', callback)
			$('.l-dialog-content').css({"padding": "0 40px 20px 40px"});
		}
        } else if (type == "line") {
            if (!this.$lineData[id]) return;
            if (this.onItemMark != null && !this.onItemMark(id, "line", mark)) return;
            this.$lineData[id].marked = mark || false;
            if (GooFlow.prototype.useSVG != "") {
                if (mark) {
                    this.$nodeDom[id].childNodes[1].setAttribute("stroke", "#00b7ee");
                    this.$nodeDom[id].childNodes[1].setAttribute("marker-end", "url(#arrow2)");
                } else {
                    this.$nodeDom[id].childNodes[1].setAttribute("stroke", "#323232");
                    this.$nodeDom[id].childNodes[1].setAttribute("marker-end", "url(#arrow1)");
                }
            } else {
                if (mark) this.$nodeDom[id].strokeColor = "#00b7ee";
                else    this.$nodeDom[id].strokeColor = "#323232"
            }
        }
        if (this.$undoStatck) {
            var paras = [id, type, !mark];
            this.pushOper("markItem", paras);
        }
    },
    }
}
};
/*
 * Todo:  ## 注册JQ插件 ****************************************************************************
@ -2231,84 +2062,33 @@ GooFlow.prototype = {
 * time: 14:59
 * */
$.fn.extend({
    createGooFlow: function (property) {
        /*
         * Todo: 初始化配置参数
         * Author: LE
         * Date: 2017/2/20
         * time: 9:59
         * */
        var defaultOption = {
	        // ******************************************************* UI相关
            id: '',
            MainContener: $('body')[0],
            width: 1400,
            height: 600,
            workWidth: null,
            workHeight: null,
            initNum: 1,
	        // ******************************************************* 工具栏
	        haveTool: true,
	        toolBtns: ["save", "undo", "redo"],
	        toolBtnRemarks: ['保存', '撤销', '重做'],
	        // ******************************************************* 流程图工具
            flowChart: true,
	        flowBtns: ["state", "complex", "join", "fork"],
            useOperStack: true,  // 是否启用回滚栈, 如否, 头部工具栏的回滚和重做按钮都将失效
            editable: true,      // 是否可编辑
            // 工具栏回调函数:
            onBtnNewClick: null,     // 新建流程图按钮被点中
            onBtnOpenClick: null,    // 打开流程图按钮定义
            onBtnSaveClick: null,    // 保存流程图按钮定义
            onFreshClick: null,      // 重载流程图按钮定义
            onExpandClick: null,     // 扩大画布按钮定义
            //当操作某个单元(结点/线/分组块)被添加时, 触发的方法, 返回FALSE可阻止添加事件的发生
            //格式function(id, type, json):id是单元的唯一标识ID,type是单元的种类,有"node","line","area"三种取值,json即addNode,addLine或addArea方法的第二个传参json.
            onItemAdd: null,
            //当操作某个单元(结点/线/分组块)被删除时, 触发的方法, 返回FALSE可阻止删除事件的发生
            //格式function(id, type):id是单元的唯一标识ID,type是单元的种类,有"node","line","area"三种取值
            onItemDel: null,
            //当操作某个单元(结点/分组块)被移动时, 触发的方法, 返回FALSE可阻止移动事件的发生
            //格式function(id, type, left, top):id是单元的唯一标识ID,type是单元的种类,有"node","area"两种取值, 线line不支持移动,left是新的左边距坐标, top是新的顶边距坐标
            onItemMove: null,
            //当操作某个单元(结点/线/分组块)被重命名时, 触发的方法, 返回FALSE可阻止重命名事件的发生
            //格式function(id, name, type):id是单元的唯一标识ID,type是单元的种类,有"node","line","area"三种取值,name是新的名称
            onItemRename: null,
            //当操作某个单元(结点/线)被由不选中变成选中时, 触发的方法, 返回FALSE可阻止选中事件的发生
            //格式function(id, type):id是单元的唯一标识ID,type是单元的种类,有"node","line"两种取值,"area"不支持被选中
            onItemFocus: null,
            //当操作某个单元(结点/线)被由选中变成不选中时, 触发的方法, 返回FALSE可阻止取消选中事件的发生
            //格式function(id, type):id是单元的唯一标识ID,type是单元的种类,有"node","line"两种取值,"area"不支持被取消选中
            onItemBlur: null,
            //当操作某个单元(结点/分组块)被重定义大小或造型时, 触发的方法, 返回FALSE可阻止重定大小/造型事件的发生
            //格式function(id, type, width, height):id是单元的唯一标识ID,type是单元的种类,有"node","line","area"三种取值,width是新的宽度,height是新的高度
            onItemResize: null,
            //当移动某条折线中段的位置, 触发的方法, 返回FALSE可阻止重定大小/造型事件的发生
            //格式function(id, M):id是单元的唯一标识ID,M是中段的新X(或Y)的坐标
            onLineMove: null,
            //当变换某条连接线的类型, 触发的方法, 返回FALSE可阻止重定大小/造型事件的发生
            //格式function(id, type):id是单元的唯一标识ID,type是连接线的新类型,"sl":直线,"lr":中段可左右移动的折线,"tb":中段可上下移动的折线
            onLineSetType: null,
            //当用重色标注某个结点/转换线时触发的方法, 返回FALSE可阻止重定大小/造型事件的发生
            //格式function(id, type, mark):id是单元的唯一标识ID,type是单元类型("node"结点,"line"转换线), mark为布尔值,表示是要标注TRUE还是取消标注FALSE
            onItemMark: null,
        };
        var options = $.extend({}, defaultOption, property);
        return new GooFlow(this, options);
    }
	createGooFlow: function (property,url) {
		/*
		 * Todo: 初始化配置参数
		 * Author: LE
		 * Date: 2017/2/20
		 * time: 9:59
		 * */
		var defaultOption = {
			// ******************************************************* UI相关
			id: '',
			MainContener: $('body')[0],
			width: '100%',
			height: '100%',
			// ******************************************************* 工具栏
			haveTool: true,
			toolBtns: ["save", "undo", "redo"],
			toolBtnRemarks: ['保存', '撤销', '重做'],
			// ******************************************************* 流程图工具
			flowChart: true,
			flowBtns: ["state", "complex", "join", "fork"],
			useOperStack: true,  // 是否启用回滚栈, 如否, 头部工具栏的回滚和重做按钮都将失效
			editable: true,      // 是否可编辑
		};
		var options = $.extend({}, defaultOption, property);
		return new GooFlow(this, options,url);
	}
});