panel.vue 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884
  1. <template>
  2. <div>
  3. <div style="padding: 40px 45px 10px 50px" :hidden="end">
  4. <el-steps :active="active" finish-status="success">
  5. <el-step title="基础信息" />
  6. <el-step title="选择表单" />
  7. <el-step title="流程设计" />
  8. </el-steps>
  9. </div>
  10. <div :hidden="active != 0 || isEdit">
  11. <el-form ref="form" :rules="rules" label-width="90px" :model="form">
  12. <el-row>
  13. <el-col :md="12">
  14. <el-form-item label="标题" prop="title">
  15. <el-input v-model="form.title" />
  16. </el-form-item>
  17. </el-col>
  18. <el-col :md="12">
  19. <el-form-item label="模板编号" prop="code">
  20. <el-input v-model="form.code" />
  21. </el-form-item>
  22. </el-col>
  23. </el-row>
  24. <el-row>
  25. <el-col :md="12">
  26. <el-form-item label="发布时间" prop="useDate">
  27. <el-date-picker
  28. v-model="form.useDate"
  29. value-format="yyyy-MM-dd"
  30. type="date"
  31. />
  32. </el-form-item>
  33. </el-col>
  34. <el-col :md="12">
  35. <el-form-item label="重要级" prop="level">
  36. <el-rate v-model="form.level" style="margin-top: 8px" />
  37. </el-form-item>
  38. </el-col>
  39. </el-row>
  40. <el-form-item label="备注" prop="remark">
  41. <el-input type="textarea" v-model="form.remark" />
  42. </el-form-item>
  43. </el-form>
  44. </div>
  45. <div style="padding: 0px 45px 20px 20px" :hidden="active != 1 || isEdit">
  46. <el-tabs type="border-card">
  47. <el-table
  48. ref="table"
  49. v-loading="listLoading"
  50. :data="formList"
  51. size="small"
  52. style="width: 90%"
  53. @row-click="handleRowClick"
  54. >
  55. <el-table-column width="30px" align="center">
  56. <template v-slot="props">
  57. <el-radio
  58. v-model="form.formId"
  59. :label="props.row.id"
  60. @change="handleRowClick(props.row)"
  61. >{{ "" }}</el-radio
  62. >
  63. </template>
  64. </el-table-column>
  65. <el-table-column
  66. label="表单名称"
  67. prop="formName"
  68. align="center"
  69. width="150px"
  70. />
  71. <el-table-column label="api接口" prop="api" align="center" />
  72. <el-table-column label="描述" prop="description" align="center" />
  73. <el-table-column label="禁用" prop="disabled" align="center">
  74. <template slot-scope="scope">
  75. <span>{{ scope.row.disabled | displayStatus }}</span>
  76. </template>
  77. </el-table-column>
  78. </el-table>
  79. <pagination
  80. style="margin-top:0px"
  81. v-show="totalCount > 0"
  82. :total="totalCount"
  83. :page.sync="page"
  84. :limit.sync="listQuery.MaxResultCount"
  85. @pagination="getFormList"
  86. />
  87. </el-tabs>
  88. </div>
  89. <div
  90. v-if="easyFlowVisible"
  91. style="height: calc(100vh)"
  92. :hidden="active != 2"
  93. >
  94. <el-row>
  95. <!--顶部工具菜单-->
  96. <el-col :span="24">
  97. <div class="ef-tooltar">
  98. <el-link type="primary" :underline="false">{{
  99. form.title
  100. }}</el-link>
  101. <el-divider direction="vertical"></el-divider>
  102. <el-button
  103. type="text"
  104. icon="el-icon-download"
  105. size="large"
  106. @click="downloadData"
  107. ></el-button>
  108. <el-divider direction="vertical"></el-divider>
  109. <el-button
  110. type="text"
  111. icon="el-icon-remove"
  112. size="large"
  113. @click="deleteElement"
  114. :disabled="!this.activeElement.type"
  115. >移除</el-button
  116. >
  117. <el-divider direction="vertical"></el-divider>
  118. <el-button
  119. type="text"
  120. size="large"
  121. @click="save"
  122. v-loading.fullscreen.lock="fullscreenLoading"
  123. ><svg-icon icon-class="save" /> 保存</el-button
  124. >
  125. <el-divider direction="vertical"></el-divider>
  126. <el-button
  127. type="text"
  128. icon="el-icon-delete-solid"
  129. size="large"
  130. style="color: #f56c6c"
  131. @click="clear"
  132. >清空</el-button
  133. >
  134. <el-divider direction="vertical"></el-divider>
  135. <el-button
  136. v-if="active != 0 && !isEdit"
  137. type="text"
  138. icon="el-icon-top"
  139. size="large"
  140. @click="back"
  141. >上一步</el-button
  142. >
  143. <div style="float: right; margin-right: 5px">
  144. <el-button
  145. type="info"
  146. plain
  147. round
  148. icon="el-icon-document"
  149. @click="dataInfo"
  150. size="mini"
  151. >流程信息</el-button
  152. >
  153. <el-button
  154. type="warning"
  155. plain
  156. round
  157. icon="el-icon-document"
  158. @click="openHelp"
  159. size="mini"
  160. >帮助</el-button
  161. >
  162. </div>
  163. </div>
  164. </el-col>
  165. </el-row>
  166. <div style="display: flex; height: calc(100% - 47px)">
  167. <div style="width: 230px; border-right: 1px solid #dce3e8">
  168. <node-menu @addNode="addNode" ref="nodeMenu"></node-menu>
  169. </div>
  170. <div id="efContainer" ref="efContainer" class="container" v-flowDrag>
  171. <template v-for="node in data.nodeList">
  172. <flow-node
  173. :id="node.id"
  174. :key="node.id"
  175. :node="node"
  176. :activeElement="activeElement"
  177. @changeNodeSite="changeNodeSite"
  178. @nodeRightMenu="nodeRightMenu"
  179. @clickNode="clickNode"
  180. >
  181. </flow-node>
  182. </template>
  183. <!-- 给画布一个默认的宽度和高度 -->
  184. <div style="position: absolute; top: 2000px; left: 2000px">
  185. &nbsp;
  186. </div>
  187. </div>
  188. <!-- 右侧表单 -->
  189. <div
  190. style="
  191. width: 300px;
  192. border-left: 1px solid #dce3e8;
  193. background-color: #fbfbfb;
  194. "
  195. >
  196. <flow-node-form
  197. ref="nodeForm"
  198. @setLineLabel="setLineLabel"
  199. @repaintEverything="repaintEverything"
  200. ></flow-node-form>
  201. </div>
  202. </div>
  203. <!-- 流程数据详情 -->
  204. <flow-info v-if="flowInfoVisible" ref="flowInfo" :data="data"></flow-info>
  205. <flow-help v-if="flowHelpVisible" ref="flowHelp"></flow-help>
  206. </div>
  207. <div align="center">
  208. <el-button
  209. v-if="active != 0 && !isEdit && !end"
  210. type="primary"
  211. size="small"
  212. @click="back"
  213. >上一步</el-button
  214. >
  215. <el-button v-if="!end" type="primary" size="small" @click="next"
  216. >下一步</el-button
  217. >
  218. </div>
  219. </div>
  220. </template>
  221. <script>
  222. import draggable from "vuedraggable";
  223. import lodash from "lodash";
  224. import "@/components/Flow/jsplumb";
  225. import { easyFlowMixin } from "@/components/Flow/mixins";
  226. import flowNode from "./node";
  227. import nodeMenu from "./node_menu";
  228. import FlowNodeForm from "./node_form";
  229. import FlowInfo from "@/components/Flow/info";
  230. import FlowHelp from "@/components/Flow/help";
  231. import { getDefaultData } from "@/components/Flow/default_data";
  232. import { getDataA } from "@/components/Flow/data_A";
  233. import { getDataB } from "@/components/Flow/data_B";
  234. import { getDataC } from "@/components/Flow/data_C";
  235. import { getDataD } from "@/components/Flow/data_D";
  236. import { getDataE } from "@/components/Flow/data_E";
  237. import { ForceDirected } from "@/components/Flow/force-directed";
  238. import "@/components/Flow/index.css";
  239. import Pagination from "@/components/Pagination";
  240. const defaultForm = {
  241. id: null,
  242. flowId: null,
  243. formId: null,
  244. title: "",
  245. code: "",
  246. useDate: "",
  247. level: 0,
  248. remark: "",
  249. nodeList: [],
  250. lineList: [],
  251. };
  252. export default {
  253. name: "EasyFlow",
  254. props: {
  255. isEdit: {
  256. type: Boolean,
  257. default: false,
  258. },
  259. },
  260. filters: {
  261. displayStatus(status) {
  262. const statusMap = {
  263. true: "是",
  264. false: "否",
  265. };
  266. return statusMap[status];
  267. },
  268. },
  269. data() {
  270. return {
  271. rules: {
  272. title: [{ required: true, message: "请输入标题", trigger: "blur" }],
  273. code: [{ required: true, message: "请输入编号", trigger: "blur" }],
  274. },
  275. form: Object.assign({}, defaultForm),
  276. formList: null,
  277. page: 1,
  278. totalCount: 0,
  279. listLoading: true,
  280. listQuery: {
  281. Filter: "",
  282. Sorting: "",
  283. SkipCount: 0,
  284. MaxResultCount: 10,
  285. },
  286. fullscreenLoading: false,
  287. active: 0,
  288. end: false,
  289. // jsPlumb 实例
  290. jsPlumb: null,
  291. // 控制画布销毁
  292. easyFlowVisible: true,
  293. // 控制流程数据显示与隐藏
  294. flowInfoVisible: false,
  295. // 是否加载完毕标志位
  296. loadEasyFlowFinish: false,
  297. flowHelpVisible: false,
  298. // 数据
  299. data: {},
  300. // 激活的元素、可能是节点、可能是连线
  301. activeElement: {
  302. // 可选值 node 、line
  303. type: undefined,
  304. // 节点ID
  305. nodeId: undefined,
  306. // 连线ID
  307. sourceId: undefined,
  308. targetId: undefined,
  309. },
  310. zoom: 0.5,
  311. };
  312. },
  313. // 一些基础配置移动该文件中
  314. mixins: [easyFlowMixin],
  315. components: {
  316. draggable,
  317. flowNode,
  318. nodeMenu,
  319. FlowInfo,
  320. FlowNodeForm,
  321. FlowHelp,
  322. Pagination
  323. },
  324. directives: {
  325. flowDrag: {
  326. bind(el, binding, vnode, oldNode) {
  327. if (!binding) {
  328. return;
  329. }
  330. el.onmousedown = (e) => {
  331. if (e.button == 2) {
  332. // 右键不管
  333. return;
  334. }
  335. // 鼠标按下,计算当前原始距离可视区的高度
  336. let disX = e.clientX;
  337. let disY = e.clientY;
  338. el.style.cursor = "move";
  339. document.onmousemove = function (e) {
  340. // 移动时禁止默认事件
  341. e.preventDefault();
  342. const left = e.clientX - disX;
  343. disX = e.clientX;
  344. el.scrollLeft += -left;
  345. const top = e.clientY - disY;
  346. disY = e.clientY;
  347. el.scrollTop += -top;
  348. };
  349. document.onmouseup = function (e) {
  350. el.style.cursor = "auto";
  351. document.onmousemove = null;
  352. document.onmouseup = null;
  353. };
  354. };
  355. },
  356. },
  357. },
  358. mounted() {
  359. this.jsPlumb = jsPlumb.getInstance();
  360. this.$nextTick(() => {
  361. //this.dataReload(getDataB());
  362. //this.dataReload(getDefaultData());
  363. });
  364. },
  365. created() {
  366. if (this.isEdit) {
  367. this.active = 2;
  368. this.end = true;
  369. const id = this.$route.params && this.$route.params.id;
  370. //this.dataReload(getDataB());
  371. this.fetchData(id);
  372. } else {
  373. this.getFormList();
  374. this.dataReload(getDefaultData());
  375. }
  376. },
  377. methods: {
  378. fetchData(id) {
  379. this.$axios.gets("/api/business/flow/" + id).then((response) => {
  380. this.form = response;
  381. let data = {
  382. nodeList: response.nodeList,
  383. lineList: response.lineList,
  384. };
  385. this.dataReload(data);
  386. });
  387. },
  388. getFormList() {
  389. this.listLoading = true;
  390. this.listQuery.SkipCount =
  391. (this.page - 1) * this.listQuery.MaxResultCount;
  392. this.$axios
  393. .gets("/api/business/form", this.listQuery)
  394. .then((response) => {
  395. this.formList = response.items;
  396. this.totalCount = response.totalCount;
  397. this.listLoading = false;
  398. });
  399. },
  400. jump() {
  401. this.$store.dispatch("tagsView/delView", this.$route);
  402. this.$router.push({ path: "/tool/flow" });
  403. },
  404. next() {
  405. if (this.active++ == 1) {
  406. this.end = true;
  407. }
  408. },
  409. back() {
  410. this.active--;
  411. this.end = false;
  412. },
  413. handleRowClick(row) {
  414. this.form.formId = row.id;
  415. },
  416. save() {
  417. this.$refs.form.validate((valid) => {
  418. if (valid) {
  419. this.fullscreenLoading = true;
  420. this.form.nodeList = this.data.nodeList;
  421. this.form.lineList = this.data.lineList;
  422. if (!this.isEdit) {
  423. this.$axios
  424. .posts("/api/business/flow", this.form)
  425. .then((response) => {
  426. this.fullscreenLoading = false;
  427. this.$notify({
  428. title: "成功",
  429. message: "新增成功",
  430. type: "success",
  431. duration: 2000,
  432. });
  433. this.jump();
  434. })
  435. .catch(() => {
  436. this.fullscreenLoading = false;
  437. });
  438. } else {
  439. this.$axios
  440. .puts("/api/business/flow/" + this.form.id, this.form)
  441. .then((response) => {
  442. this.fullscreenLoading = false;
  443. this.$notify({
  444. title: "成功",
  445. message: "更新成功",
  446. type: "success",
  447. duration: 2000,
  448. });
  449. this.jump();
  450. })
  451. .catch(() => {
  452. this.fullscreenLoading = false;
  453. });
  454. }
  455. } else {
  456. this.$message({
  457. message: "请完善基础信息",
  458. type: "warning",
  459. });
  460. }
  461. });
  462. },
  463. // 返回唯一标识
  464. getUUID() {
  465. return Math.random().toString(36).substr(3, 10);
  466. },
  467. jsPlumbInit() {
  468. this.jsPlumb.ready(() => {
  469. // 导入默认配置
  470. this.jsPlumb.importDefaults(this.jsplumbSetting);
  471. // 会使整个jsPlumb立即重绘。
  472. this.jsPlumb.setSuspendDrawing(false, true);
  473. // 初始化节点
  474. this.loadEasyFlow();
  475. // 单点击了连接线
  476. this.jsPlumb.bind("click", (conn, originalEvent) => {
  477. this.activeElement.type = "line";
  478. this.activeElement.sourceId = conn.sourceId;
  479. this.activeElement.targetId = conn.targetId;
  480. this.$refs.nodeForm.lineInit(
  481. {
  482. from: conn.sourceId,
  483. to: conn.targetId,
  484. label: conn.getLabel(),
  485. },
  486. this.data,
  487. this.form.formId
  488. );
  489. });
  490. // 连线
  491. this.jsPlumb.bind("connection", (evt) => {
  492. let from = evt.source.id;
  493. let to = evt.target.id;
  494. if (this.loadEasyFlowFinish) {
  495. this.data.lineList.push({
  496. id: "line-" + this.getUUID(),
  497. from: from,
  498. to: to,
  499. });
  500. }
  501. });
  502. // 删除连线回调
  503. this.jsPlumb.bind("connectionDetached", (evt) => {
  504. this.deleteLine(evt.sourceId, evt.targetId);
  505. });
  506. // 改变线的连接节点
  507. this.jsPlumb.bind("connectionMoved", (evt) => {
  508. this.changeLine(evt.originalSourceId, evt.originalTargetId);
  509. });
  510. // 连线右击
  511. this.jsPlumb.bind("contextmenu", (evt) => {
  512. console.log("contextmenu", evt);
  513. });
  514. // 连线
  515. this.jsPlumb.bind("beforeDrop", (evt) => {
  516. let from = evt.sourceId;
  517. let to = evt.targetId;
  518. if (from === to) {
  519. this.$message.error("节点不支持连接自己");
  520. return false;
  521. }
  522. if (this.hasLine(from, to)) {
  523. this.$message.error("该关系已存在,不允许重复创建");
  524. return false;
  525. }
  526. if (this.hashOppositeLine(from, to)) {
  527. this.$message.error("不支持两个节点之间连线回环");
  528. return false;
  529. }
  530. this.$message.success("连接成功");
  531. return true;
  532. });
  533. // beforeDetach
  534. this.jsPlumb.bind("beforeDetach", (evt) => {
  535. console.log("beforeDetach", evt);
  536. });
  537. this.jsPlumb.setContainer(this.$refs.efContainer);
  538. });
  539. },
  540. loadEasyFlow() {
  541. // 初始化节点
  542. for (var i = 0; i < this.data.nodeList.length; i++) {
  543. let node = this.data.nodeList[i];
  544. // 设置源点,可以拖出线连接其他节点
  545. this.jsPlumb.makeSource(
  546. node.id,
  547. lodash.merge(this.jsplumbSourceOptions, {})
  548. );
  549. // // 设置目标点,其他源点拖出的线可以连接该节点
  550. this.jsPlumb.makeTarget(node.id, this.jsplumbTargetOptions);
  551. if (!node.viewOnly) {
  552. this.jsPlumb.draggable(node.id, {
  553. containment: "parent",
  554. stop: function (el) {
  555. // 拖拽节点结束后的对调
  556. console.log("拖拽结束: ", el);
  557. },
  558. });
  559. }
  560. }
  561. for (var i = 0; i < this.data.lineList.length; i++) {
  562. let line = this.data.lineList[i];
  563. var connParam = {
  564. source: line.from,
  565. target: line.to,
  566. label: line.label ? line.label : "",
  567. connector: line.connector ? line.connector : "",
  568. anchors: line.anchors ? line.anchors : undefined,
  569. paintStyle: line.paintStyle ? line.paintStyle : undefined,
  570. };
  571. this.jsPlumb.connect(connParam, this.jsplumbConnectOptions);
  572. }
  573. this.$nextTick(function () {
  574. this.loadEasyFlowFinish = true;
  575. });
  576. },
  577. // 设置连线条件
  578. setLineLabel(from, to, label, formField) {
  579. var conn = this.jsPlumb.getConnections({
  580. source: from,
  581. target: to,
  582. })[0];
  583. if (!label || label === "") {
  584. conn.removeClass("flowLabel");
  585. conn.addClass("emptyFlowLabel");
  586. } else {
  587. conn.addClass("flowLabel");
  588. }
  589. conn.setLabel({
  590. label: label,
  591. });
  592. this.data.lineList.forEach(function (line) {
  593. if (line.from == from && line.to == to) {
  594. line.label = label;
  595. line.formField = formField;
  596. }
  597. });
  598. },
  599. // 删除激活的元素
  600. deleteElement() {
  601. if (this.activeElement.type === "node") {
  602. this.deleteNode(this.activeElement.nodeId);
  603. } else if (this.activeElement.type === "line") {
  604. this.$confirm("确定删除所点击的线吗?", "提示", {
  605. confirmButtonText: "确定",
  606. cancelButtonText: "取消",
  607. type: "warning",
  608. })
  609. .then(() => {
  610. var conn = this.jsPlumb.getConnections({
  611. source: this.activeElement.sourceId,
  612. target: this.activeElement.targetId,
  613. })[0];
  614. this.jsPlumb.deleteConnection(conn);
  615. })
  616. .catch(() => {});
  617. }
  618. },
  619. clear() {
  620. this.$confirm("确定清空流程图吗?", "提示", {
  621. confirmButtonText: "确定",
  622. cancelButtonText: "取消",
  623. type: "warning",
  624. })
  625. .then(() => {
  626. this.dataReload(getDefaultData());
  627. })
  628. .catch(() => {});
  629. },
  630. // 删除线
  631. deleteLine(from, to) {
  632. this.data.lineList = this.data.lineList.filter(function (line) {
  633. if (line.from == from && line.to == to) {
  634. return false;
  635. }
  636. return true;
  637. });
  638. },
  639. // 改变连线
  640. changeLine(oldFrom, oldTo) {
  641. this.deleteLine(oldFrom, oldTo);
  642. },
  643. // 改变节点的位置
  644. changeNodeSite(data) {
  645. for (var i = 0; i < this.data.nodeList.length; i++) {
  646. let node = this.data.nodeList[i];
  647. if (node.id === data.nodeId) {
  648. node.left = data.left;
  649. node.top = data.top;
  650. }
  651. }
  652. },
  653. addNode(evt, nodeMenu, mousePosition) {
  654. var screenX = evt.originalEvent.clientX,
  655. screenY = evt.originalEvent.clientY;
  656. let efContainer = this.$refs.efContainer;
  657. var containerRect = efContainer.getBoundingClientRect();
  658. var left = screenX,
  659. top = screenY;
  660. // 计算是否拖入到容器中
  661. if (
  662. left < containerRect.x ||
  663. left > containerRect.width + containerRect.x ||
  664. top < containerRect.y ||
  665. containerRect.y > containerRect.y + containerRect.height
  666. ) {
  667. this.$message.error("请把节点拖入到画布中");
  668. return;
  669. }
  670. left = left - containerRect.x + efContainer.scrollLeft;
  671. top = top - containerRect.y + efContainer.scrollTop;
  672. // 居中
  673. left -= 85;
  674. top -= 16;
  675. var nodeId = "node-" + this.getUUID();
  676. // 动态生成名字
  677. var origName = nodeMenu.name;
  678. var nodeName = origName;
  679. var index = 1;
  680. while (index < 10000) {
  681. var repeat = false;
  682. for (var i = 0; i < this.data.nodeList.length; i++) {
  683. let node = this.data.nodeList[i];
  684. if (node.name === nodeName) {
  685. nodeName = origName + index;
  686. repeat = true;
  687. }
  688. }
  689. if (repeat) {
  690. index++;
  691. continue;
  692. }
  693. break;
  694. }
  695. var node = {
  696. id: nodeId,
  697. name: nodeName,
  698. type: nodeMenu.type,
  699. left: left + "px",
  700. top: top + "px",
  701. ico: nodeMenu.ico,
  702. state: "success",
  703. };
  704. /**
  705. * 这里可以进行业务判断、是否能够添加该节点
  706. */
  707. this.data.nodeList.push(node);
  708. this.$nextTick(function () {
  709. this.jsPlumb.makeSource(nodeId, this.jsplumbSourceOptions);
  710. this.jsPlumb.makeTarget(nodeId, this.jsplumbTargetOptions);
  711. this.jsPlumb.draggable(nodeId, {
  712. containment: "parent",
  713. stop: function (el) {
  714. // 拖拽节点结束后的对调
  715. console.log("拖拽结束: ", el);
  716. },
  717. });
  718. });
  719. },
  720. /**
  721. * 删除节点
  722. * @param nodeId 被删除节点的ID
  723. */
  724. deleteNode(nodeId) {
  725. this.$confirm("确定要删除节点" + nodeId + "?", "提示", {
  726. confirmButtonText: "确定",
  727. cancelButtonText: "取消",
  728. type: "warning",
  729. closeOnClickModal: false,
  730. })
  731. .then(() => {
  732. /**
  733. * 这里需要进行业务判断,是否可以删除
  734. */
  735. this.data.nodeList = this.data.nodeList.filter(function (node) {
  736. if (node.id === nodeId) {
  737. // 伪删除,将节点隐藏,否则会导致位置错位
  738. // node.show = false
  739. return false;
  740. }
  741. return true;
  742. });
  743. this.$nextTick(function () {
  744. this.jsPlumb.removeAllEndpoints(nodeId);
  745. });
  746. })
  747. .catch(() => {});
  748. return true;
  749. },
  750. clickNode(nodeId) {
  751. this.activeElement.type = "node";
  752. this.activeElement.nodeId = nodeId;
  753. this.$refs.nodeForm.nodeInit(this.data, nodeId);
  754. },
  755. // 是否具有该线
  756. hasLine(from, to) {
  757. for (var i = 0; i < this.data.lineList.length; i++) {
  758. var line = this.data.lineList[i];
  759. if (line.from === from && line.to === to) {
  760. return true;
  761. }
  762. }
  763. return false;
  764. },
  765. // 是否含有相反的线
  766. hashOppositeLine(from, to) {
  767. return this.hasLine(to, from);
  768. },
  769. nodeRightMenu(nodeId, evt) {
  770. this.menu.show = true;
  771. this.menu.curNodeId = nodeId;
  772. this.menu.left = evt.x + "px";
  773. this.menu.top = evt.y + "px";
  774. },
  775. repaintEverything() {
  776. this.jsPlumb.repaint();
  777. },
  778. // 流程数据信息
  779. dataInfo() {
  780. this.flowInfoVisible = true;
  781. this.$nextTick(function () {
  782. this.$refs.flowInfo.init();
  783. });
  784. },
  785. // 加载流程图
  786. dataReload(data) {
  787. this.easyFlowVisible = false;
  788. this.data.nodeList = [];
  789. this.data.lineList = [];
  790. this.$nextTick(() => {
  791. data = lodash.cloneDeep(data);
  792. this.easyFlowVisible = true;
  793. this.data = data;
  794. this.$nextTick(() => {
  795. this.jsPlumb = jsPlumb.getInstance();
  796. this.$nextTick(() => {
  797. this.jsPlumbInit();
  798. });
  799. });
  800. });
  801. },
  802. // 模拟载入数据dataA
  803. dataReloadA() {
  804. this.dataReload(getDataA());
  805. },
  806. // 模拟载入数据dataB
  807. dataReloadB() {
  808. this.dataReload(getDataB());
  809. },
  810. // 模拟载入数据dataC
  811. dataReloadC() {
  812. this.dataReload(getDataC());
  813. },
  814. // 模拟载入数据dataD
  815. dataReloadD() {
  816. this.dataReload(getDataD());
  817. },
  818. // 模拟加载数据dataE,自适应创建坐标
  819. dataReloadE() {
  820. let dataE = getDataE();
  821. let tempData = lodash.cloneDeep(dataE);
  822. let data = ForceDirected(tempData);
  823. this.dataReload(data);
  824. this.$message({
  825. message: "力导图每次产生的布局是不一样的",
  826. type: "warning",
  827. });
  828. },
  829. zoomAdd() {
  830. if (this.zoom >= 1) {
  831. return;
  832. }
  833. this.zoom = this.zoom + 0.1;
  834. this.$refs.efContainer.style.transform = `scale(${this.zoom})`;
  835. this.jsPlumb.setZoom(this.zoom);
  836. },
  837. zoomSub() {
  838. if (this.zoom <= 0) {
  839. return;
  840. }
  841. this.zoom = this.zoom - 0.1;
  842. this.$refs.efContainer.style.transform = `scale(${this.zoom})`;
  843. this.jsPlumb.setZoom(this.zoom);
  844. },
  845. // 下载数据
  846. downloadData() {
  847. this.$confirm("确定要下载该流程数据吗?", "提示", {
  848. confirmButtonText: "确定",
  849. cancelButtonText: "取消",
  850. type: "warning",
  851. closeOnClickModal: false,
  852. })
  853. .then(() => {
  854. var datastr =
  855. "data:text/json;charset=utf-8," +
  856. encodeURIComponent(JSON.stringify(this.data, null, "\t"));
  857. var downloadAnchorNode = document.createElement("a");
  858. downloadAnchorNode.setAttribute("href", datastr);
  859. downloadAnchorNode.setAttribute("download", "data.json");
  860. downloadAnchorNode.click();
  861. downloadAnchorNode.remove();
  862. this.$message.success("正在下载中,请稍后...");
  863. })
  864. .catch(() => {});
  865. },
  866. openHelp() {
  867. this.flowHelpVisible = true;
  868. this.$nextTick(function () {
  869. this.$refs.flowHelp.init();
  870. });
  871. },
  872. },
  873. };
  874. </script>