procdef-diagram.html 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595
  1. <!doctype html>
  2. <html>
  3. <head>
  4. <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
  5. <title>流程图</title>
  6. <!--#include file="common/_header.html"-->
  7. <link rel="stylesheet" type="text/css" href="scripts/workflow/tipsy.css" />
  8. <link rel="stylesheet" type="text/css" href="lib/select2/select2.min.css" />
  9. <style>
  10. body {
  11. position: fixed;
  12. top: 0;
  13. bottom: 0;
  14. left: 0;
  15. right: 0;
  16. margin: 0;
  17. padding: 0;
  18. font-family:tahoma "microsoft yahei";
  19. }
  20. .live.map {
  21. width: 100%;
  22. height: 100%;
  23. }
  24. .live.map text {
  25. font-weight: 300;
  26. font-size: 14px;
  27. }
  28. .live.map .node rect {
  29. stroke-width: 1.5px;
  30. stroke: #bbb;
  31. fill: #666;
  32. }
  33. svg {
  34. width: 100%;
  35. height: 100%;
  36. overflow: hidden;
  37. }
  38. .live.map .status {
  39. height: 100%;
  40. width: 15px;
  41. display: block;
  42. float: left;
  43. border-top-left-radius: 5px;
  44. border-bottom-left-radius: 5px;
  45. margin-right: 4px;
  46. }
  47. .live.map .start .status {
  48. background-color: green;
  49. }
  50. .live.map .blank .status {
  51. background-color: white;
  52. }
  53. .live.map .ignore .status {
  54. background-color: #aaaaaa;
  55. }
  56. .live.map .end .status {
  57. background-color: red;
  58. }
  59. .live.map .node g div {
  60. width: 200px;
  61. height: 40px;
  62. color: #fff;
  63. }
  64. .live.map .userTask{
  65. cursor:pointer;
  66. }
  67. .live.map .taskName {
  68. display: inline-block;
  69. width: 200px;
  70. padding:2px;
  71. }
  72. .live.map .assiginee {
  73. display: block;
  74. float: left;
  75. width: 130px;
  76. height: 20px;
  77. font-size: 12px;
  78. margin-top: 2px;
  79. color:cornsilk;
  80. }
  81. .live.map .edgeLabel text {
  82. width: 50px;
  83. fill: #fff;
  84. }
  85. .live.map .edgePath path {
  86. stroke: #999;
  87. stroke-width: 1.5px;
  88. fill: #999;
  89. }
  90. .live.map .node g .gateway{
  91. width : 50px;
  92. height : 50px;
  93. border-radius: 100%;
  94. }
  95. .flex-container{
  96. display: flex;
  97. flex-direction: row;
  98. height:100%;
  99. width:100%;
  100. }
  101. #diagram{
  102. flex:1;
  103. }
  104. .right-side{
  105. display: flex;
  106. flex-direction: column;
  107. border-left:1px dotted black;
  108. }
  109. #propertyPanel{
  110. position:relative;
  111. width:400px;
  112. padding:5px;
  113. overflow: auto;
  114. flex:1;
  115. }
  116. .input-text{
  117. height:30px;
  118. border-radius: 4px;
  119. border: 1px solid #aaa;
  120. width:100%;
  121. }
  122. dt{
  123. font-weight: bold;
  124. height:30px;
  125. line-height:30px;
  126. }
  127. dd {
  128. margin-inline-start:0px;
  129. }
  130. </style>
  131. </head>
  132. <body>
  133. <div class="flex-container">
  134. <div id="diagram" class="live map">
  135. <svg style="width:100%;height:100%;"></svg>
  136. </div>
  137. <div class="right-side">
  138. <div id="propertyPanel">
  139. <h3>属性设置</h3>
  140. <dl>
  141. <dt>当前步骤:</dt>
  142. <dd>
  143. <input type="text" v-model="step.title" readonly class="input-text"/>
  144. </dd>
  145. <dt>当前序号:</dt>
  146. <dd>
  147. <input type="number" v-model="step.no" class="input-text"/>
  148. </dd>
  149. <dt>是否忽略:</dt>
  150. <dd>
  151. <select class="select" v-model="step.ignore">
  152. <option value="1">是</option>
  153. <option value="0" selected>否</option>
  154. </select>
  155. </dd>
  156. <dt>待办人设置:</dt>
  157. <dd>
  158. <select id="assigneeSelect" style="width:100%;" multiple>
  159. </select>
  160. </dd>
  161. <dt>待办人角色设置:</dt>
  162. <dd>
  163. <select id="assigneeRoleSelect" style="width:100%;" multiple>
  164. </select>
  165. <!-- </dd>
  166. <dt>完成后通知人:</dt>
  167. <dd>
  168. <select id="noticeSelect" style="width:100%;" multiple>
  169. </select>
  170. </dd>
  171. <dt>完成后通知角色设置:</dt>
  172. <dd>
  173. <select id="noticeRoleSelect" style="width:100%;" multiple>
  174. </select>
  175. </dd> -->
  176. <dt>预估完成人天设置:</dt>
  177. <dd><input type="number" class="input-text" v-model="step.day"/>人天</dd>
  178. <dt>待办事项:</dt>
  179. <dd>
  180. <table width="100%" class="table table-border table-bordered radius">
  181. <thead>
  182. <tr>
  183. <th>序号</th>
  184. <th>事项名称</th>
  185. <th>操作</th>
  186. </tr>
  187. </thead>
  188. <tr v-for="(todo,index) in todoListFilter(todoList)">
  189. <td>{{index+1}}</td>
  190. <td><input type="text" v-model="todo.detail" class="input-text"/></td>
  191. <td><button @click="removeTodo(index)" class="btn btn-default radius">删除</button></td>
  192. </tr>
  193. </table>
  194. <button @click="addTodo()" class="btn btn-default radius">新增</button>
  195. </dd>
  196. </dl>
  197. </div>
  198. <div style="text-align:center;height:50px;">
  199. <button onclick="submit()" class="btn btn-primary radius size-L">保存</button>
  200. </div>
  201. </div>
  202. </div>
  203. <script id="taskTmpl" type="text/html">
  204. <div id="task_{{id}}" class="userTask {{className}}" onclick="configTask('{{id}}','{{name}}')">
  205. <span class="status"></span>
  206. <span class="taskName" id="{{id}}">{{name}}</span>
  207. <span class="assiginee">{{remark}}</span>
  208. </div>
  209. </script>
  210. <script id="gatewayTmpl" type="text/html">
  211. <p class="gateway"></p>
  212. </script>
  213. <!--#include file="common/_footer.html"-->
  214. <script type="text/javascript" src="scripts/workflow/d3.v3.min.js" charset="utf-8"></script>
  215. <script type="text/javascript" src="scripts/workflow/dagre-d3.min.js" ></script>
  216. <script type="text/javascript" src="scripts/workflow/tipsy.js" ></script>
  217. <script type="text/javascript" src="scripts/global.js"></script>
  218. <script type="text/javascript" src="lib/select2/select2.full.js"></script>
  219. <script type="text/javascript" src="lib/select2/select2.zh-CN.js"></script>
  220. <script type="text/javascript" src="lib/vue/vue.js"></script>
  221. <script type="text/javascript" src="lib/vue/directive/vue-select2.js"></script>
  222. <script type="text/javascript">
  223. var procDefKey = getQueryString("procDefKey");
  224. var templateId = getQueryString("templateId");
  225. </script>
  226. <script type="text/javascript">
  227. $(document).ready(function(){
  228. $.get(global_backend_url + "/procDef/loadDiagram",
  229. {
  230. procDefKey : procDefKey,
  231. templateId : templateId
  232. },function(rs){
  233. var data = rs.data;
  234. createDiagram(data);
  235. },"json");
  236. });
  237. function createDiagram(data){
  238. // Create a new directed graph
  239. var g = new dagreD3.graphlib.Graph();
  240. g.setGraph({
  241. nodesep: 70,
  242. ranksep: 50,
  243. rankdir: "TB",
  244. marginx: 20,
  245. marginy: 20
  246. });
  247. //start
  248. for(var i=0;i<data.startNodeList.length;i++){
  249. var node = data.startNodeList[i];
  250. g.setNode(node.id, {
  251. labelType: "html",
  252. label:template("taskTmpl",{
  253. id:"start",
  254. name:node.name,
  255. className:"start"
  256. }),
  257. rx: 5,
  258. ry: 5,
  259. padding: 0
  260. });
  261. }
  262. //end
  263. for(var i=0;i<data.endNodeList.length;i++){
  264. var node = data.endNodeList[i];
  265. g.setNode(node.id, {
  266. labelType: "html",
  267. label:template("taskTmpl",{
  268. id:"end",
  269. name:node.name,
  270. className:"end"
  271. }),
  272. rx: 5,
  273. ry: 5,
  274. padding: 0
  275. });
  276. }
  277. //用户任务
  278. for(var i=0;i<data.taskList.length;i++){
  279. var task = data.taskList[i];
  280. task.className = "blank";
  281. if(task.step!=null) {
  282. if(!!task.step.ignore){
  283. task.remark = "已设置(自动忽略)";
  284. task.className = "ignore";
  285. }
  286. else{
  287. task.remark = "已设置(正常)";
  288. }
  289. }
  290. else{
  291. task.remark = "未设置";
  292. }
  293. g.setNode(task.id, {
  294. labelType: "html",
  295. label:template("taskTmpl",task),
  296. rx: 5,
  297. ry: 5,
  298. padding: 1
  299. });
  300. }
  301. //独占网关
  302. for(var i=0;i<data.exclusiveGatewayList.length;i++){
  303. var gateway = data.exclusiveGatewayList[i];
  304. g.setNode(gateway.id, {
  305. shape: 'diamond',
  306. style: "fill: #fff; stroke: #000"
  307. });
  308. }
  309. //并行网关
  310. for(var i=0;i<data.parallelGatewayList.length;i++){
  311. var gateway = data.parallelGatewayList[i];
  312. g.setNode(gateway.id, {
  313. label:'',
  314. shape: 'diamond',
  315. style: "fill: #fff; stroke: #000"
  316. });
  317. }
  318. //连接线
  319. for(var i=0;i<data.seqFlowList.length;i++){
  320. var seqFlow = data.seqFlowList[i];
  321. g.setEdge(seqFlow.sourceRef, seqFlow.targetRef,{
  322. label: seqFlow.name
  323. });
  324. }
  325. // Create the renderer
  326. var render = new dagreD3.render();
  327. // Set up an SVG group so that we can translate the final graph.
  328. var svg = d3.select("svg"),
  329. inner = svg.append("g");
  330. // Set up zoom support
  331. var zoom = d3.behavior.zoom().on("zoom", function() {
  332. inner.attr("transform", "translate(" + d3.event.translate + ")" + "scale(" + d3.event.scale + ")");
  333. });
  334. svg.call(zoom);
  335. // Run the renderer. This is what draws the final graph.
  336. render(inner, g);
  337. // Zoom and scale to fit
  338. // Zoom and scale to fit
  339. var graphWidth = g.graph().width + 80;
  340. var graphHeight = g.graph().height + 40;
  341. var width = parseInt(svg.style("width").replace(/px/, ""));
  342. var height = parseInt(svg.style("height").replace(/px/, ""));
  343. // var zoomScale = Math.min(width / graphWidth, height / graphHeight);
  344. var zoomScale = 1;
  345. // var translate = [(width/2) - ((graphWidth*zoomScale)/2), (height/2) - ((graphHeight*zoomScale)/2)];
  346. var translate = [(width/2) - ((graphWidth*zoomScale)/2), 0];
  347. zoom.translate(translate);
  348. zoom.scale(zoomScale);
  349. zoom.event(svg);
  350. }
  351. </script>
  352. <script type="text/javascript">
  353. var vm = new Vue({
  354. el : "#propertyPanel",
  355. data : {
  356. step: {
  357. title:'',
  358. ignore:0,
  359. day:0
  360. },
  361. assigneeList: [],
  362. noticeList: [],
  363. todoList: []
  364. },
  365. methods: {
  366. addTodo : function(){
  367. this.todoList.push({
  368. detail: "",
  369. delFlag: false
  370. });
  371. },
  372. removeTodo : function(index){
  373. // this.todoList.splice(index,1);
  374. this.todoList[index].delFlag = true;
  375. },
  376. todoListFilter:function(todoList){
  377. return todoList.filter(function (todo) {
  378. return !todo.delFlag;
  379. })
  380. },
  381. }
  382. });
  383. var userOptions = {
  384. ajax: {
  385. url: global_backend_url + "/jpAdmin/selectUser",
  386. dataType: 'json',
  387. delay: 250,
  388. method:'post',
  389. allowClear: false,
  390. processResults: function (data) {
  391. var results = data.map(function(item){
  392. return {
  393. id : item.userId,
  394. text : item.realName,
  395. userName: item.userName,
  396. orgName : item.orgName
  397. }
  398. });
  399. return {
  400. results: results
  401. }
  402. }
  403. },
  404. language: "zh-CN",
  405. templateResult: function (state) {
  406. var html = "<div>";
  407. html += "<strong>姓名:" + state.text + "</strong>(" + state.userName + ")<br/>";
  408. html += "单位:" + state.orgName;
  409. html += "</div>";
  410. return $(html);
  411. }
  412. };
  413. var roleOptions = {
  414. ajax: {
  415. url: global_backend_url + "/role/selectRole",
  416. dataType: 'json',
  417. delay: 250,
  418. method:'post',
  419. processResults: function (data) {
  420. console.log(data);
  421. var results = data.map(function(item){
  422. return {
  423. id : item.roleId,
  424. text : item.roleDesc
  425. }
  426. });
  427. return {
  428. results: results
  429. }
  430. }
  431. },
  432. language: "zh-CN",
  433. templateResult: function (state) {
  434. var html = "<div>";
  435. html += state.text;
  436. html += "</div>";
  437. return $(html);
  438. },
  439. allowClear: false
  440. };
  441. function select2_init(el,options){
  442. $(el).select2(options).on('select2:select', function(){
  443. setTimeout(function(){
  444. $(".select2-search__field", $(el).parent()).focus();
  445. });
  446. });
  447. }
  448. select2_init($("#assigneeSelect"),userOptions);
  449. select2_init($("#assigneeRoleSelect"),roleOptions);
  450. function initSelectedUsers(el,userList,value){
  451. $('option', el).remove();
  452. userList.forEach(function(user){
  453. var newOption = new Option(user.realName, user.userId, false, false);
  454. $(el).append(newOption);
  455. });
  456. $(el).val(value).trigger('change');
  457. }
  458. function initSelectedRoles(el,roleList,value){
  459. $('option', el).remove();
  460. roleList.forEach(function(role){
  461. var newOption = new Option(role.roleDesc, role.roleId, false, false);
  462. $(el).append(newOption);
  463. });
  464. $(el).val(value).trigger('change');
  465. }
  466. function configTask(taskDefKey,taskName){
  467. if(taskDefKey.length==0 || taskDefKey=="start" || taskDefKey=="end"){
  468. return;
  469. }
  470. var loadingIndex = layer.load(1, {shade: [0.2,'#fff']});
  471. $.get(global_backend_url + "/procDefTmpl/step/detail",{
  472. taskDefKey : taskDefKey,
  473. templateId : templateId
  474. },function(resp){
  475. layer.close(loadingIndex);
  476. var data = resp.data;
  477. vm.step = data.dto.step;
  478. vm.step.title = taskName;
  479. initSelectedUsers($("#assigneeSelect"),data.assignedList,data.dto.assigneeList);
  480. initSelectedRoles($("#assigneeRoleSelect"),data.assignedRoleList,data.dto.assigneeRoleList);
  481. vm.todoList = data.dto.todoList;
  482. },"json");
  483. }
  484. function submit(){
  485. if(vm.step.no==null || vm.step.no.length==0){
  486. layer.msg("请先设置序号!",{icon:2});
  487. return;
  488. }
  489. var loadingIndex = layer.load(0, {shade: [0.4,'#fff']});
  490. $.ajax({
  491. url: global_backend_url + "/procDefTmpl/step/save",
  492. type: "POST", //请求类型
  493. data: JSON.stringify({
  494. step: vm.step,
  495. assigneeList: $('#assigneeSelect').val(),
  496. assigneeRoleList: $('#assigneeRoleSelect').val(),
  497. todoList: vm.todoList
  498. }),
  499. dataType:"json",
  500. contentType:"application/json",
  501. success: function(resp){
  502. layer.close(loadingIndex);
  503. if(resp.result){
  504. layer.msg("保存成功!",{icon:1,time:2000});
  505. var task = {};
  506. task.remark = "已设置(" + (vm.step.ignore==1 ? "自动忽略" : "正常") + ")";
  507. task.step = vm.step;
  508. task.id = vm.step.taskDefKey;
  509. task.name = vm.step.title;
  510. $("#task_" + task.id).html(template("taskTmpl",task));
  511. }
  512. else{
  513. layer.msg("保存失败!" + resp.message,{icon:1});
  514. }
  515. }
  516. });
  517. }
  518. </script>
  519. </body>
  520. </html>