FlowChart.js 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561
  1. //处理表现层
  2. var nodeList = Array(); //所有新生成的节点全部存储在这里,并且有唯一索引号,所有的定位均通过index进行,即将图保存下来了
  3. var root = {
  4. index: 0, //在nodeList中的索引号
  5. id: 0,
  6. parentId: 0,
  7. type: -1,
  8. option: 0,
  9. title: "root",
  10. sequence: [],
  11. parameters: {
  12. history: 1,
  13. tabIndex: 0,
  14. useLoop: false, //是否使用循环中的元素
  15. xpath: "", //xpath
  16. wait: 0,
  17. },
  18. isInLoop: false, //是否处于循环内
  19. };
  20. nodeList.push(root);
  21. var queue = new Array();
  22. var actionSequence = new Array(); //存储图结构,每个元素为在nodelist里面的索引值,下面的id和pid根据此数组进行索引,然后再在nodelist里找
  23. var nowNode = null; //存储现在所在的节点
  24. var vueData = { nowNodeIndex: 0 }; //存储目前所在节点的索引号,不能直接使用变量而需要用对象包起来
  25. var option = 0; //工具箱选项
  26. var title = "";
  27. var parameterNum = 1; //记录目前的参数个数
  28. //处理逻辑层
  29. var app = new Vue({
  30. el: '#app',
  31. data: {
  32. list: { nl: nodeList },
  33. index: vueData,
  34. nodeType: 0, // 当前元素的类型
  35. nowNode: null, // 用来临时存储元素的节点
  36. loopType: -1, //点击循环时候用来循环选项
  37. useLoop: false, //记录是否使用循环内元素
  38. nowArrow: { "position": -1, "pId": 0, "num": 0 },
  39. paras: { "parameters": [] }, //提取数据的参数列表
  40. TClass: -1, //条件分支的条件类别
  41. paraIndex: 0, //当前参数的index
  42. },
  43. watch: {
  44. nowArrow: { //变量发生变化的时候进行一些操作
  45. deep: true,
  46. handler: function(newVal, oldVal) {
  47. let arrlist = document.getElementsByClassName("arrow");
  48. if (oldVal != null) {
  49. for (let i = 0; i < arrlist.length; i++) {
  50. if (arrlist[i].getAttribute("position") == oldVal["position"] &&
  51. arrlist[i].getAttribute("pid") == oldVal["pId"]) {
  52. arrlist[i].style.backgroundColor = ""; // 时刻指示现在应该插入的节点的位置
  53. break;
  54. }
  55. }
  56. }
  57. for (let i = 0; i < arrlist.length; i++) {
  58. if (arrlist[i].getAttribute("position") == newVal["position"] &&
  59. arrlist[i].getAttribute("pid") == newVal["pId"]) {
  60. arrlist[i].style.backgroundColor = "lavender"; // 时刻指示现在应该插入的节点的位置
  61. break;
  62. }
  63. }
  64. }
  65. },
  66. loopType: {
  67. handler: function(newVal, oldVal) {
  68. this.nowNode["parameters"]["loopType"] = newVal;
  69. }
  70. },
  71. TClass: {
  72. handler: function(newVal, oldVal) {
  73. this.nowNode["parameters"]["class"] = newVal;
  74. }
  75. },
  76. useLoop: {
  77. handler: function(newVal, oldVal) {
  78. this.nowNode["parameters"]["useLoop"] = newVal;
  79. }
  80. },
  81. paras: {
  82. handler: function(newVal, oldVal) {
  83. this.nowNode["parameters"]["paras"] = newVal["parameters"];
  84. }
  85. },
  86. },
  87. methods: {
  88. modifyParas: function(i) { //修改第i个参数
  89. this.paraIndex = i;
  90. },
  91. deleteParas: function(i) { //删除第i个参数
  92. this.nowNode["parameters"]["paras"].splice(i, 1);
  93. //如果参数删除完了,就把提取数据也删掉
  94. if (this.nowNode["parameters"]["paras"].length == 0) {
  95. deleteElement();
  96. }
  97. },
  98. upParas: function(i) { //上移第i个参数
  99. if (i != 0) {
  100. let t = this.nowNode["parameters"]["paras"].splice(i, 1)[0];
  101. this.nowNode["parameters"]["paras"].splice(i - 1, 0, t);
  102. }
  103. },
  104. downParas: function(i) { //下移第i个参数
  105. if (i != this.nowNode["parameters"]["paras"].length - 1) {
  106. let t = this.nowNode["parameters"]["paras"].splice(i, 1)[0];
  107. this.nowNode["parameters"]["paras"].splice(i + 1, 0, t);
  108. }
  109. },
  110. getType: function(nodeType, contentType) { //根据类型得到字段名称
  111. if (contentType == 2) {
  112. return "InnerHTML";
  113. } else if (contentType == 3) {
  114. return "OuterHTML";
  115. }
  116. if (nodeType == 2) {
  117. return "Link Address";
  118. } else if (nodeType == 1) {
  119. return "Link Text";
  120. } else if (nodeType == 4) {
  121. return "Image Address";
  122. } else {
  123. return "Text";
  124. }
  125. }
  126. }
  127. })
  128. //深复制
  129. function DeepClone(obj) {
  130. if (obj === null || typeof obj !== 'object') return obj;
  131. var cpObj = obj instanceof Array ? [] : {};
  132. for (var key in obj) cpObj[key] = DeepClone(obj[key]);
  133. return cpObj;
  134. }
  135. // 根据元素类型返回不同元素的样式
  136. function newNode(node) {
  137. id = node["id"];
  138. title = node["title"];
  139. type = node["type"];
  140. if (type == 0) //顺序
  141. {
  142. return `<div class="sequence"><div class="node clk" data="${id}" dataType=${type} id = "${id}" position=${node["position"]} pId=${node["parentId"]}>
  143. <div >
  144. <p>${title}</p>
  145. </div>
  146. </div>
  147. <p class="arrow" position=${node["position"]} data = "${id}" pId=${node["parentId"]}>↓</p></div>`;
  148. } else if (type == 1) //循环
  149. {
  150. return `<div class="loop clk" data="${id}" dataType=${type} id = "${id}" position=${node["position"]} pId=${node["parentId"]}>
  151. <p style="background:#d6d6d6;text-align:left;padding:2px">${title}</p>
  152. <p class="arrow" position=-1 data = "${id}" pId=${id}>↓</p>
  153. </div>
  154. <p class="arrow" data = "${id}" position=${node["position"]} pId=${node["parentId"]}>↓</p></div>`;
  155. } else if (type == 2) //判断
  156. {
  157. return `<div class="loop clk" dataType=${type} data="${id}" position=${node["position"]} pId=${node["parentId"]}>
  158. <p style="background:#d6d6d6;text-align:left;padding:2px">${title}</p>
  159. <p class="branchAdd" data="${id}">Click here to add a new condition to the left most</p>
  160. <div class="judge" id = "${id}">
  161. </div></div>
  162. <p class="arrow" data = "${id}" position=${node["position"]} pId=${node["parentId"]}>↓</p></div>`;
  163. } else //判断分支
  164. {
  165. return `<div class="branch clk" dataType=${type} data="${id}" position=${node["position"]} pId=${node["parentId"]}>
  166. <p style="background:#d6d6d6;text-align:left;padding:2px">${title}</p>
  167. <p data = "${id}" class="arrow" position=-1 pId=${id}>↓</p>
  168. <div id = "${id}">
  169. </div></div>`;
  170. }
  171. }
  172. function elementMousedown(e) {
  173. if (e.button == 2) //右键点击
  174. {
  175. if (nowNode != null) {
  176. nowNode.style.borderColor = "skyblue";
  177. }
  178. nowNode = this;
  179. vueData.nowNodeIndex = actionSequence[this.getAttribute("data")];
  180. this.style.borderColor = "blue";
  181. handleElement(); //处理元素
  182. }
  183. e.stopPropagation(); //防止冒泡
  184. }
  185. function branchMouseDown(e) {
  186. if (e.button == 2) //右键点击
  187. {
  188. let judgeId = this.getAttribute('data');
  189. var l = nodeList.length;
  190. var t = {
  191. index: l,
  192. id: 0,
  193. parentId: 0,
  194. type: 3,
  195. option: 10,
  196. title: "Condition",
  197. sequence: [],
  198. isInLoop: false,
  199. };
  200. addParameters(t)
  201. nodeList.push(t);
  202. nodeList[actionSequence[judgeId]]["sequence"].splice(0, 0, t.index);
  203. refresh();
  204. app._data.nowArrow = { "position": -1, "pId": t["id"], "num": 0 };
  205. $("#" + t["id"]).click();
  206. }
  207. e.stopPropagation(); //防止冒泡
  208. }
  209. function arrowMouseDown(e) {
  210. if (e.button == 2) //右键点击
  211. {
  212. if (option != 0) {
  213. app._data.nowArrow = { "position": this.getAttribute('position'), "pId": this.getAttribute('pId'), "num": 0 };
  214. }
  215. toolBoxKernel(e);
  216. }
  217. }
  218. //增加分支点击事件
  219. function branchClick(e) {
  220. let judgeId = this.getAttribute('data');
  221. var l = nodeList.length;
  222. var t = {
  223. index: l,
  224. id: 0,
  225. parentId: 0,
  226. type: 3,
  227. option: 10,
  228. title: "Condition",
  229. sequence: [],
  230. isInLoop: false,
  231. };
  232. addParameters(t);
  233. nodeList.push(t);
  234. nodeList[actionSequence[judgeId]]["sequence"].splice(0, 0, t.index);
  235. refresh();
  236. app._data.nowArrow = { "position": -1, "pId": t["id"], "num": 0 };
  237. $("#" + t["id"]).click();
  238. e.stopPropagation(); //防止冒泡
  239. }
  240. //元素点击事件
  241. function elementClick(e) {
  242. if (nowNode != null) {
  243. nowNode.style.borderColor = "skyblue";
  244. }
  245. nowNode = this;
  246. vueData.nowNodeIndex = actionSequence[this.getAttribute("data")];
  247. this.style.borderColor = "blue";
  248. handleElement(); //处理元素
  249. e.stopPropagation(); //防止冒泡
  250. }
  251. //箭头点击事件
  252. function arrowClick(e) {
  253. if (option != 0) {
  254. app._data.nowArrow = { "position": this.getAttribute('position'), "pId": this.getAttribute('pId'), "num": 0 };
  255. }
  256. toolBoxKernel(e);
  257. }
  258. //增加元素函数
  259. function addElement(op, para) {
  260. option = op;
  261. if (option == 1) { //打开网页选项
  262. title = "Open Page";
  263. } else {
  264. title = $(".options")[option - 1].innerHTML; //获取新增操作名称
  265. }
  266. toolBoxKernel(null, para);
  267. }
  268. // 工具箱操作函数
  269. function toolBoxKernel(e, para = null) {
  270. if (option == 13) { //调整锚点
  271. // let tarrow = DeepClone(app.$data.nowArrow);
  272. // refresh();
  273. // app._data.nowArrow =tarrow;
  274. } else if (option == 11) { //复制操作
  275. if (nowNode == null) {
  276. e.stopPropagation(); //防止冒泡
  277. } else if (nowNode.getAttribute("dataType") > 0) {
  278. alert("Cannot copy loop, if and condition!");
  279. e.stopPropagation(); //防止冒泡
  280. } else {
  281. let position = parseInt(nowNode.getAttribute('position'));
  282. let pId = nowNode.getAttribute('pId');
  283. var tt = nodeList[nodeList[actionSequence[pId]]["sequence"][position]]; //在相应位置添加新元素
  284. t = DeepClone(tt); //浅复制元素
  285. var l = nodeList.length;
  286. t.index = l;
  287. nodeList.push(t);
  288. var position2 = parseInt(app._data.nowArrow['position']);
  289. var pId2 = app._data.nowArrow['pId'];
  290. nodeList[actionSequence[pId2]]["sequence"].splice(position2 + 1, 0, t.index); //在相应位置添加新元素
  291. refresh(); //重新渲染页面
  292. app._data.nowArrow = { "position": t["position"], "pId": t["parentId"], "num": 0 };
  293. $("#" + t["id"]).click(); //复制后点击复制后的元素
  294. e.stopPropagation(); //防止冒泡
  295. }
  296. } else if (option == 10) { //剪切操作
  297. if (nowNode == null) {
  298. e.stopPropagation(); //防止冒泡
  299. } else if ($(nowNode).is(".branch")) {
  300. alert("Cannot move condition branch!");
  301. e.stopPropagation(); //防止冒泡
  302. } else {
  303. let position = parseInt(nowNode.getAttribute('position'));
  304. let pId = nowNode.getAttribute('pId');
  305. var position2 = parseInt(app._data.nowArrow['position']);
  306. var pId2 = app._data.nowArrow['pId'];
  307. var id = nowNode.getAttribute('data');
  308. var pidt = pId2;
  309. var move = true;
  310. console.log(pidt, id);
  311. while (pidt != 0) {
  312. if (pidt == id) {
  313. move = false;
  314. break;
  315. }
  316. pidt = nodeList[actionSequence[pidt]]["parentId"];
  317. }
  318. if (move) //如果自己要移动到自己节点里就不允许移动
  319. {
  320. let element = nodeList[actionSequence[pId]]["sequence"].splice(position, 1); //在相应位置删除元素
  321. if (pId == pId2 && position < position2) //如果要移动的位置属于同一层并且是从前往后移动,注意需要控制数组插入位置向前错位
  322. {
  323. position2--;
  324. }
  325. console.log(element);
  326. nodeList[actionSequence[pId2]]["sequence"].splice(position2 + 1, 0, element[0]); //在相应位置添加新元素
  327. refresh(); //重新渲染页面
  328. console.log(nodeList[element[0]]);
  329. app._data.nowArrow = { "position": nodeList[element[0]]["position"], "pId": nodeList[element[0]]["parentId"], "num": 0 };
  330. $("#" + nodeList[element[0]]["id"]).click();
  331. } else {
  332. alert("Cannot move inside self!");
  333. }
  334. e.stopPropagation(); //防止冒泡
  335. }
  336. } else if (option > 0) { //新增操作
  337. var l = nodeList.length;
  338. var t = {
  339. id: 0,
  340. index: l,
  341. parentId: 0,
  342. type: 0,
  343. option: option,
  344. title: title,
  345. sequence: [],
  346. isInLoop: false,
  347. };
  348. nodeList.push(t);
  349. if (option == 8) //循环
  350. {
  351. t["type"] = 1;
  352. } else if (option == 9) //判断
  353. {
  354. t["type"] = 2;
  355. // 增加两个分支
  356. var nt = {
  357. id: 0,
  358. parentId: 0,
  359. index: l + 1,
  360. type: 3,
  361. option: 10,
  362. title: "Condition",
  363. sequence: [],
  364. isInLoop: false,
  365. };
  366. var nt2 = {
  367. id: 0,
  368. parentId: 0,
  369. index: l + 2,
  370. type: 3,
  371. option: 10,
  372. title: "Condition",
  373. sequence: [],
  374. isInLoop: false,
  375. };
  376. t["sequence"].push(nt.index);
  377. t["sequence"].push(nt2.index);
  378. nodeList.push(nt)
  379. nodeList.push(nt2);
  380. addParameters(nt); //增加选项的默认参数
  381. addParameters(nt2); //增加选项的默认参数
  382. }
  383. let position = parseInt(app._data.nowArrow['position']);
  384. let pId = app._data.nowArrow['pId'];
  385. nodeList[actionSequence[pId]]["sequence"].splice(position + 1, 0, t.index); //在相应位置添加新元素
  386. refresh(); //重新渲染页面
  387. //下面是确定添加元素之后下一个要插入的节点的位置
  388. app._data.nowArrow = { "position": t["position"], "pId": t["parentId"], "num": 0 };
  389. addParameters(t); //增加选项的默认参数
  390. if (para != null) {
  391. modifyParameters(t, para);
  392. }
  393. if (option == 8) //循环情况下应插入在循环里面
  394. {
  395. app._data.nowArrow = { "position": -1, "pId": t["id"], "num": 0 };
  396. $("#" + t["id"]).click();
  397. } else if (option == 9) //判断插入到第一个判断条件中
  398. {
  399. app._data.nowArrow = { "position": -1, "pId": nt["id"], "num": 0 };
  400. $("#" + nt["id"]).click();
  401. } else {
  402. $("#" + t["id"]).click();
  403. }
  404. if (e != null)
  405. e.stopPropagation(); //防止冒泡
  406. option = 0;
  407. return t;
  408. }
  409. option = 0;
  410. }
  411. $(".options").mousedown(function() {
  412. option = parseInt(this.getAttribute("data"));
  413. title = this.innerHTML;
  414. if (option >= 10 && option <= 12 && (nowNode == null || nowNode.getAttribute("id") == 0)) {
  415. alert("No element is selected now!");
  416. } else if (option == 12) {
  417. deleteElement();
  418. $(".options")[12].click();
  419. }
  420. });
  421. function bindEvents() {
  422. // 清空原来的listener然后再添加新的listener
  423. //以下绑定了左右键的行为
  424. let rect = document.getElementsByClassName('clk');
  425. for (let i = 0, rule; rule = rect[i++];) {
  426. rule.removeEventListener('mousedown', elementMousedown);
  427. rule.addEventListener('mousedown', elementMousedown);
  428. rule.removeEventListener('click', elementClick);
  429. rule.addEventListener('click', elementClick);
  430. }
  431. let arr = document.getElementsByClassName('arrow');
  432. for (let i = 0, rule; rule = arr[i++];) {
  433. rule.removeEventListener('click', arrowClick);
  434. rule.addEventListener('click', arrowClick);
  435. rule.removeEventListener('mousedown', arrowMouseDown);
  436. rule.addEventListener('mousedown', arrowMouseDown);
  437. }
  438. let branch = document.getElementsByClassName('branchAdd');
  439. for (let i = 0, rule; rule = branch[i++];) {
  440. rule.removeEventListener('click', branchClick);
  441. rule.addEventListener('click', branchClick);
  442. rule.removeEventListener('mousedown', branchMouseDown);
  443. rule.addEventListener('mousedown', branchMouseDown);
  444. }
  445. }
  446. //重新画图
  447. function refresh(nowArrowReset = true) {
  448. $("#0").empty();
  449. $("#0").append(`<div style="border-radius: 50%;width: 40px;height: 40px;border:solid;border-color:seagreen;margin:5px auto;background-color:lightcyan;margin-top:20px">
  450. <p style="font-size: 24px!important;text-align: center;margin-left: 6px;font-family:'Times New Roman'">▶</p>
  451. </div>
  452. <p id="firstArrow" class="arrow" position=-1 pId=0>↓</p>`);
  453. actionSequence.splice(0);
  454. queue.splice(0);
  455. var idd = 1;
  456. queue.push(0);
  457. actionSequence.push(0);
  458. while (queue.length != 0) {
  459. var nd = queue.shift(); //取出父元素并建立对子元素的链接
  460. for (i = 0; i < nodeList[nd].sequence.length; i++) {
  461. nodeList[nodeList[nd].sequence[i]].parentId = nodeList[nd].id;
  462. nodeList[nodeList[nd].sequence[i]]["position"] = i;
  463. nodeList[nodeList[nd].sequence[i]].id = idd++;
  464. //检测元素是否位于循环内
  465. if (nodeList[nd].option == 8 || nodeList[nd].isInLoop) {
  466. nodeList[nodeList[nd].sequence[i]].isInLoop = true;
  467. } else {
  468. nodeList[nodeList[nd].sequence[i]].isInLoop = false;
  469. }
  470. queue.push(nodeList[nd].sequence[i]);
  471. actionSequence.push(nodeList[nd].sequence[i]);
  472. }
  473. }
  474. if (nowArrowReset) //如果要重置锚点位置
  475. {
  476. app._data.nowArrow = { "position": -1, "pId": 0, "num": 0 }; //设置默认要添加的位置是元素流程最开头处
  477. }
  478. //第一个元素不渲染
  479. for (i = 1; i < actionSequence.length; i++) {
  480. parentId = nodeList[actionSequence[i]]["parentId"];
  481. $("#" + parentId).append(newNode(nodeList[actionSequence[i]]));
  482. }
  483. bindEvents();
  484. }
  485. function deleteElement() {
  486. if (nowNode.getAttribute("id") == 0) {
  487. alert("No element is selected now!"); //root
  488. return;
  489. }
  490. // if (nodeList[actionSequence[nowNode.getAttribute("data")]]["option"] == 1) {
  491. // alert("Cannot delete the element of Open Page!");
  492. // return;
  493. // }
  494. let position = parseInt(nowNode.getAttribute('position'));
  495. let pId = nowNode.getAttribute('pId');
  496. let tnode = nodeList[actionSequence[pId]]["sequence"].splice(position, 1); //在相应位置删除元素
  497. //循环的标记已经被删除的元素,因为删除循环后,循环内的元素也会
  498. let queue = new Array();
  499. queue.push(tnode[0]);
  500. while (queue.length > 0) {
  501. let index = queue.shift();
  502. nodeList[index]["id"] = -1; //标记服务已被删除
  503. for (let i = 0; i < nodeList[index]["sequence"].length; i++) {
  504. queue.push(nodeList[index]["sequence"][i]);
  505. }
  506. }
  507. app._data["nowNode"] = null;
  508. app._data["nodeType"] = 0;
  509. vueData.nowNodeIndex = 0;
  510. if (nowNode.getAttribute("datatype") == 3) { //如果删掉的是条件分支的话
  511. pId = nowNode.parentNode.parentNode.getAttribute('pId');
  512. position = nowNode.parentNode.parentNode.getAttribute('position');
  513. }
  514. app.$data.nowArrow = { position: position - 1, "pId": pId, "num": 0 }; //删除元素后锚点跳转到当前元素的上一个节点
  515. refresh(false); //重新渲染页面
  516. nowNode = null; //取消选择
  517. }
  518. document.oncontextmenu = function() {
  519. return false;
  520. } //屏蔽右键菜单
  521. //删除元素
  522. document.onkeydown = function(e) {
  523. if (nowNode != null && e.keyCode == 46) {
  524. if (confirm("Do you really want to delete the selected operation?")) {
  525. deleteElement();
  526. }
  527. } else { //ctrl+s保存服务
  528. var currKey = 0,
  529. e = e || event || window.event;
  530. currKey = e.keyCode || e.which || e.charCode;
  531. if (currKey == 83 && (e.ctrlKey || e.metaKey)) {
  532. $('#save').click();
  533. return false;
  534. }
  535. }
  536. }
  537. function inputDelete(e) {
  538. if (e.keyCode == 46) {
  539. e.stopPropagation(); //输入框按delete应该正常运行
  540. }
  541. }