FatherMap.vue 34 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073
  1. <template>
  2. <div>
  3. <div style="width: 95%;height: calc(100vh - 300px);" id="diagramContainer">
  4. <ul class="box">
  5. <li class="leftMenu">
  6. <!-- <h3>装置列表</h3> -->
  7. <el-collapse>
  8. <el-collapse-item :title="item1.name" v-for="(item1, index) in leftMenuData" :key="index">
  9. <draggable @start="moveStart" @end="moveEnd" v-model="item1.children" :options="options">
  10. <div v-for="(item2, n) in item1.children" class="content" :divOption="JSON.stringify(item2)"
  11. @mousedown="mouseDownFun" :key="n">
  12. {{ item2.config[0].value }}
  13. </div>
  14. </draggable>
  15. </el-collapse-item>
  16. </el-collapse>
  17. </li>
  18. <li class="plumbBox" id="plumbBox">
  19. <!-- <div class="setBox">
  20. </div> -->
  21. <div v-for="(item, index) in info" :key="index" :id="item.id" :style="getStyle(item)"
  22. :class="item.id === activeNode.id ? 'activeNode' : 'normalNode'" @click="sendActive(item)">
  23. <div class="plumbNode" :id="item.id + 'plumbNode'">
  24. <!-- <el-icon :size="20">
  25. <CirclePlusFilled />
  26. </el-icon> -->
  27. </div>
  28. {{ item.config[0].value }}
  29. <el-icon class="is-loading" v-if="item.status === 'loading'" color="blue">
  30. <Loading />
  31. </el-icon>
  32. <el-icon v-else-if="item.status === 'success'" color="green">
  33. <CircleCheckFilled />
  34. </el-icon>
  35. <el-icon v-else-if="item.status === 'error'" color="red">
  36. <CircleCloseFilled />
  37. </el-icon>
  38. </div>
  39. </li>
  40. <li class="rightContent">
  41. <h3>节点操作</h3>
  42. <div style="padding-left: 10px">
  43. <RightForm ref="rightForm" @changeActiveNodeInfo="changeActiveNodeInfo" @deleteLine="deleteLine"
  44. @deleteNode="deleteNode"></RightForm>
  45. </div>
  46. </li>
  47. </ul>
  48. </div>
  49. </div>
  50. </template>
  51. <script setup>
  52. //引入jsPlumb
  53. import { jsPlumb } from "jsplumb";
  54. import { VueDraggableNext } from "vue-draggable-next";
  55. import { ElMessage } from "element-plus";
  56. import lodash from "lodash";
  57. import { v4 as uuidv4 } from "uuid";
  58. import { reactive, ref, onMounted, nextTick } from "vue";
  59. import RightForm from "./SonMap.vue";
  60. const draggable = VueDraggableNext;
  61. let plumbBox = null;
  62. let plumbBoxPositionInfo = reactive({});
  63. //鼠标和节点的内部差距,为了让节点更精准的判断区域
  64. let nodePositionDiff = reactive({});
  65. //后面需要回传给父组件的值
  66. let plumbList = ref([]);
  67. //绘制标识
  68. let renderFlag = ref(undefined);
  69. //动态节点
  70. let activeNode = ref({});
  71. let inputVal = ref("名称1");
  72. let rightForm = ref(null);
  73. /*
  74. ----------------------------------------------
  75. //连线基础配置
  76. let jsPlumbConnectOptions = {
  77. isSource: true,
  78. isTarget: true,
  79. // 动态锚点、提供了4个方向 Continuous、AutoDefault
  80. anchor: ["Continuous",{shape:"Circle"}],
  81. overlays: [['Arrow', { width: 8, length: 8, location: 1 }]] // overlay
  82. }
  83. //画布节点的拖拽连线配置
  84. const jsplumbSourceOptions = {
  85. filter:'.plumbNode',
  86. filterExclude:false,
  87. anchor:'Continuous',
  88. allowLoopback:true,
  89. maxConnections: -1,
  90. onMaxConnections:function (info,e){
  91. console.log(`超过了最大值连线:${info.maxConnections}`)
  92. }
  93. }
  94. ----------------------------------------------
  95. */
  96. //左侧菜单节点的拖拽配置
  97. const options = {
  98. preventOnFilter: false,
  99. sort: false,
  100. disabled: false,
  101. ghostClass: "tt",
  102. // 不使用H5原生的配置
  103. forceFallback: true,
  104. };
  105. //默认配置
  106. let globalConfig = {
  107. Container: "plumbBox",
  108. anchor: ["Bottom", "Top", "Left", "Right"],
  109. connector: "Bezier",
  110. endpoint: "Blank",
  111. paintStyle: {
  112. stroke: "#364249",
  113. strokeWidth: 1,
  114. outlineStroke: "transparent",
  115. outlineWidth: 10,
  116. },
  117. hoverPaintStyle: { stroke: "#000", strokeWidth: 1.3 },
  118. overlays: [["Arrow", { width: 5, length: 5, location: 1 }]],
  119. endpointStyle: {
  120. fill: "lightgray",
  121. outlineStroke: "darkgray",
  122. outlineWidth: 2,
  123. },
  124. };
  125. //左侧
  126. let leftMenuData = ref([
  127. {
  128. name: "装置列表",
  129. children: [
  130. {
  131. to: [],
  132. top: 0,
  133. left: 0,
  134. // status: "loading",
  135. isSource: true,
  136. isTarget: false,
  137. config: [
  138. {
  139. label: "名称",
  140. name: "label",
  141. type: "text",
  142. value: "保护(高压制)",
  143. require: true,
  144. },
  145. {
  146. label: "描述",
  147. name: "description",
  148. type: "textarea",
  149. value: "",
  150. require: false,
  151. },
  152. {
  153. label: "归属",
  154. name: "affiliation",
  155. type: "select",
  156. value: "check",
  157. require: true,
  158. options: [
  159. { label: "审核信息", value: "check" },
  160. { label: "生产经营", value: "manage" },
  161. { label: "结算报销", value: "account" },
  162. ],
  163. },
  164. ],
  165. },
  166. {
  167. to: [],
  168. top: 0,
  169. left: 0,
  170. // status: "loading",
  171. isSource: true,
  172. isTarget: true,
  173. config: [
  174. {
  175. label: "名称",
  176. name: "label",
  177. type: "text",
  178. value: "合并单元(高压制)",
  179. require: true,
  180. },
  181. {
  182. label: "描述",
  183. name: "description",
  184. type: "textarea",
  185. value: "",
  186. require: false,
  187. },
  188. {
  189. label: "归属",
  190. name: "affiliation",
  191. type: "select",
  192. value: "check",
  193. require: true,
  194. options: [
  195. { label: "审核信息", value: "check" },
  196. { label: "生产经营", value: "manage" },
  197. { label: "结算报销", value: "account" },
  198. ],
  199. },
  200. ],
  201. },
  202. {
  203. to: [],
  204. top: 0,
  205. left: 0,
  206. // status: "loading",
  207. isSource: true,
  208. isTarget: false,
  209. config: [
  210. {
  211. label: "名称",
  212. name: "label",
  213. type: "text",
  214. value: "保护(低压制)",
  215. require: true,
  216. },
  217. {
  218. label: "描述",
  219. name: "description",
  220. type: "textarea",
  221. value: "",
  222. require: false,
  223. },
  224. {
  225. label: "归属",
  226. name: "affiliation",
  227. type: "select",
  228. value: "check",
  229. require: true,
  230. options: [
  231. { label: "审核信息", value: "check" },
  232. { label: "生产经营", value: "manage" },
  233. { label: "结算报销", value: "account" },
  234. ],
  235. },
  236. ],
  237. },
  238. {
  239. to: [],
  240. top: 0,
  241. left: 0,
  242. // status: "loading",
  243. isSource: true,
  244. isTarget: false,
  245. config: [
  246. {
  247. label: "名称",
  248. name: "label",
  249. type: "text",
  250. value: "合并单元(低压制)",
  251. require: true,
  252. },
  253. {
  254. label: "描述",
  255. name: "description",
  256. type: "textarea",
  257. value: "",
  258. require: false,
  259. },
  260. {
  261. label: "归属",
  262. name: "affiliation",
  263. type: "select",
  264. value: "check",
  265. require: true,
  266. options: [
  267. { label: "审核信息", value: "check" },
  268. { label: "生产经营", value: "manage" },
  269. { label: "结算报销", value: "account" },
  270. ],
  271. },
  272. ],
  273. },
  274. {
  275. to: [],
  276. top: 0,
  277. left: 0,
  278. // status: "loading",
  279. isSource: true,
  280. isTarget: false,
  281. config: [
  282. {
  283. label: "名称",
  284. name: "label",
  285. type: "text",
  286. value: "本体测控",
  287. require: true,
  288. },
  289. {
  290. label: "描述",
  291. name: "description",
  292. type: "textarea",
  293. value: "",
  294. require: false,
  295. },
  296. {
  297. label: "归属",
  298. name: "affiliation",
  299. type: "select",
  300. value: "check",
  301. require: true,
  302. options: [
  303. { label: "审核信息", value: "check" },
  304. { label: "生产经营", value: "manage" },
  305. { label: "结算报销", value: "account" },
  306. ],
  307. },
  308. ],
  309. },
  310. {
  311. to: [],
  312. top: 0,
  313. left: 0,
  314. // status: "loading",
  315. isSource: true,
  316. isTarget: false,
  317. config: [
  318. {
  319. label: "名称",
  320. name: "label",
  321. type: "text",
  322. value: "母线保护",
  323. require: true,
  324. },
  325. {
  326. label: "描述",
  327. name: "description",
  328. type: "textarea",
  329. value: "",
  330. require: false,
  331. },
  332. {
  333. label: "归属",
  334. name: "affiliation",
  335. type: "select",
  336. value: "check",
  337. require: true,
  338. options: [
  339. { label: "审核信息", value: "check" },
  340. { label: "生产经营", value: "manage" },
  341. { label: "结算报销", value: "account" },
  342. ],
  343. },
  344. ],
  345. },
  346. {
  347. to: [],
  348. top: 0,
  349. left: 0,
  350. // status: "loading",
  351. isSource: true,
  352. isTarget: false,
  353. config: [
  354. {
  355. label: "名称",
  356. name: "label",
  357. type: "text",
  358. value: "高压侧测控",
  359. require: true,
  360. },
  361. {
  362. label: "描述",
  363. name: "description",
  364. type: "textarea",
  365. value: "",
  366. require: false,
  367. },
  368. {
  369. label: "归属",
  370. name: "affiliation",
  371. type: "select",
  372. value: "check",
  373. require: true,
  374. options: [
  375. { label: "审核信息", value: "check" },
  376. { label: "生产经营", value: "manage" },
  377. { label: "结算报销", value: "account" },
  378. ],
  379. },
  380. ],
  381. },
  382. {
  383. to: [],
  384. top: 0,
  385. left: 0,
  386. // status: "loading",
  387. isSource: true,
  388. isTarget: false,
  389. config: [
  390. {
  391. label: "名称",
  392. name: "label",
  393. type: "text",
  394. value: "母联智能终端",
  395. require: true,
  396. },
  397. {
  398. label: "描述",
  399. name: "description",
  400. type: "textarea",
  401. value: "",
  402. require: false,
  403. },
  404. {
  405. label: "归属",
  406. name: "affiliation",
  407. type: "select",
  408. value: "check",
  409. require: true,
  410. options: [
  411. { label: "审核信息", value: "check" },
  412. { label: "生产经营", value: "manage" },
  413. { label: "结算报销", value: "account" },
  414. ],
  415. },
  416. ],
  417. },
  418. {
  419. to: [],
  420. top: 0,
  421. left: 0,
  422. // status: "loading",
  423. isSource: true,
  424. isTarget: false,
  425. config: [
  426. {
  427. label: "名称",
  428. name: "label",
  429. type: "text",
  430. value: "智能终端(高压侧)",
  431. require: true,
  432. },
  433. {
  434. label: "描述",
  435. name: "description",
  436. type: "textarea",
  437. value: "",
  438. require: false,
  439. },
  440. {
  441. label: "归属",
  442. name: "affiliation",
  443. type: "select",
  444. value: "check",
  445. require: true,
  446. options: [
  447. { label: "审核信息", value: "check" },
  448. { label: "生产经营", value: "manage" },
  449. { label: "结算报销", value: "account" },
  450. ],
  451. },
  452. ],
  453. },
  454. {
  455. to: [],
  456. top: 0,
  457. left: 0,
  458. // status: "loading",
  459. isSource: true,
  460. isTarget: false,
  461. config: [
  462. {
  463. label: "名称",
  464. name: "label",
  465. type: "text",
  466. value: "智能终端(低压侧)",
  467. require: true,
  468. },
  469. {
  470. label: "描述",
  471. name: "description",
  472. type: "textarea",
  473. value: "",
  474. require: false,
  475. },
  476. {
  477. label: "归属",
  478. name: "affiliation",
  479. type: "select",
  480. value: "check",
  481. require: true,
  482. options: [
  483. { label: "审核信息", value: "check" },
  484. { label: "生产经营", value: "manage" },
  485. { label: "结算报销", value: "account" },
  486. ],
  487. },
  488. ],
  489. },
  490. {
  491. to: [],
  492. top: 0,
  493. left: 0,
  494. // status: "loading",
  495. isSource: true,
  496. isTarget: false,
  497. config: [
  498. {
  499. label: "名称",
  500. name: "label",
  501. type: "text",
  502. value: "母线合并单元",
  503. require: true,
  504. },
  505. {
  506. label: "描述",
  507. name: "description",
  508. type: "textarea",
  509. value: "",
  510. require: false,
  511. },
  512. {
  513. label: "归属",
  514. name: "affiliation",
  515. type: "select",
  516. value: "check",
  517. require: true,
  518. options: [
  519. { label: "审核信息", value: "check" },
  520. { label: "生产经营", value: "manage" },
  521. { label: "结算报销", value: "account" },
  522. ],
  523. },
  524. ],
  525. },
  526. ],
  527. },
  528. // {
  529. // name: "完结列表",
  530. // children: [
  531. // {
  532. // to: [],
  533. // top: 0,
  534. // left: 0,
  535. // status: "loading",
  536. // type: "target",
  537. // isSource: false,
  538. // isTarget: false,
  539. // config: [
  540. // {
  541. // label: "名称",
  542. // name: "label",
  543. // type: "text",
  544. // value: "完结节点1",
  545. // require: true,
  546. // },
  547. // {
  548. // label: "描述",
  549. // name: "description",
  550. // type: "textarea",
  551. // value: "check",
  552. // require: false,
  553. // },
  554. // {
  555. // label: "归属",
  556. // name: "affiliation",
  557. // type: "select",
  558. // value: "",
  559. // require: true,
  560. // options: [
  561. // { label: "审核信息", value: "check" },
  562. // { label: "生产经营", value: "manage" },
  563. // { label: "结算报销", value: "account" },
  564. // ],
  565. // },
  566. // ],
  567. // },
  568. // {
  569. // to: [],
  570. // top: 0,
  571. // left: 0,
  572. // status: "loading",
  573. // isSource: false,
  574. // isTarget: false,
  575. // config: [
  576. // {
  577. // label: "名称",
  578. // name: "label",
  579. // type: "text",
  580. // value: "完结节点2",
  581. // require: true,
  582. // },
  583. // {
  584. // label: "描述",
  585. // name: "description",
  586. // type: "textarea",
  587. // value: "",
  588. // require: false,
  589. // },
  590. // {
  591. // label: "归属",
  592. // name: "affiliation",
  593. // type: "select",
  594. // value: "check",
  595. // require: true,
  596. // options: [
  597. // { label: "审核信息", value: "check" },
  598. // { label: "生产经营", value: "manage" },
  599. // { label: "结算报销", value: "account" },
  600. // ],
  601. // },
  602. // ],
  603. // },
  604. // ],
  605. // },
  606. ]);
  607. //渲染节点信息(默认是后台传过来的)
  608. let info = ref([]);
  609. //新增一个节点
  610. const addNode = (newInfo) => {
  611. newInfo.id = uuidv4();
  612. newInfo = Object.assign(newInfo, globalConfig);
  613. info.value.push(newInfo);
  614. console.log(newInfo, "???新增节点的信息");
  615. nextTick(() => {
  616. renderFlag.value = "new";
  617. makeFun(newInfo);
  618. });
  619. };
  620. //新增一条连线
  621. const addLine = () => {
  622. info.value[3].to = ["div6"];
  623. renderNode();
  624. };
  625. const mouseDownFun = (event) => {
  626. //具体位置鼠标信息
  627. let mousedownPositionInfo = { x: event.clientX, y: event.clientY };
  628. //被拖拽节点初始的位置信息
  629. let moveBoxBeforePosition = {
  630. x: event.target.getBoundingClientRect().x,
  631. y: event.target.getBoundingClientRect().y,
  632. };
  633. nodePositionDiff = {
  634. leftDiff: mousedownPositionInfo.x - moveBoxBeforePosition.x,
  635. topDiff: mousedownPositionInfo.y - moveBoxBeforePosition.y,
  636. };
  637. };
  638. //开始拖动
  639. const moveStart = (el) => {
  640. console.log(el, "开始拖动");
  641. };
  642. //停止拖动
  643. const moveEnd = (el) => {
  644. refreshPlumbPostionInfo();
  645. let dragNodeInfo = JSON.parse(el.item.attributes.divOption.nodeValue);
  646. judgePosition(
  647. dragNodeInfo,
  648. plumbBoxPositionInfo,
  649. el.originalEvent.x,
  650. el.originalEvent.y
  651. );
  652. };
  653. //判断拖动区域
  654. const judgePosition = (dragNodeInfo, plumbBoxPositionInfo, x, y) => {
  655. //拖拽至画布外部
  656. if (
  657. x - nodePositionDiff.leftDiff < plumbBoxPositionInfo.left ||
  658. x + 180 - nodePositionDiff.leftDiff > plumbBoxPositionInfo.right ||
  659. y - nodePositionDiff.topDiff < plumbBoxPositionInfo.top ||
  660. y + 40 - nodePositionDiff.topDiff > plumbBoxPositionInfo.bottom
  661. ) {
  662. ElMessage({
  663. message: "节点不能拖拽至画布之外",
  664. type: "error",
  665. });
  666. } else {
  667. dragNodeInfo.left =
  668. x - plumbBoxPositionInfo.left - nodePositionDiff.leftDiff;
  669. dragNodeInfo.top = y - plumbBoxPositionInfo.top - nodePositionDiff.topDiff;
  670. addNode(dragNodeInfo);
  671. }
  672. };
  673. //刷新画布区域信息
  674. const refreshPlumbPostionInfo = () => {
  675. plumbBox = document.querySelector(".plumbBox");
  676. let positionInfo = plumbBox.getBoundingClientRect();
  677. plumbBoxPositionInfo = positionInfo;
  678. };
  679. //渲染节点
  680. const renderNode = (flag) => {
  681. //合并节点信息和配置
  682. info.value.map((item) => (item = Object.assign(item, globalConfig)));
  683. //这里需要等所依赖的DOM节点全部渲染完毕,才能进行图形渲染
  684. nextTick(() => {
  685. if (flag === "new") {
  686. renderFlag.value = "once";
  687. }
  688. plumbInit.deleteEveryConnection();
  689. plumbInit.deleteEveryEndpoint();
  690. refreshPlumbPostionInfo();
  691. //渲染画布中的信息节点
  692. let renderList = [];
  693. // if(info.value.length<1){return}
  694. info.value.forEach((item) => {
  695. if (item.to.length > 0) {
  696. item.to.forEach((v) => {
  697. renderList.push({
  698. source: item.id,
  699. target: v,
  700. anchor: item.anchor,
  701. connector: item.connector,
  702. endpoint: item.endpoint,
  703. overlays: item.overlays,
  704. paintStyle: item.paintStyle,
  705. hoverPaintStyle: item.hoverPaintStyle,
  706. endpointStyle: item.endpointStyle,
  707. });
  708. });
  709. }
  710. });
  711. plumbList.value = renderList;
  712. //渲染函数
  713. plumbInit.ready(() => {
  714. renderList.forEach((item) => {
  715. // plumbInit.connect(item,jsPlumbConnectOptions);
  716. plumbInit.connect(item);
  717. });
  718. info.value.forEach((item) => {
  719. makeFun(item);
  720. plumbInit.draggable(item.id, {
  721. containment: "parent",
  722. stop: function (el) {
  723. item.left = el.pos[0];
  724. item.top = el.pos[1];
  725. },
  726. });
  727. });
  728. });
  729. });
  730. };
  731. //设置节点可连接属性
  732. const makeFun = (item) => {
  733. var common = {
  734. isSource: true,
  735. isTarget: true,
  736. connector: ['Flowchart'],
  737. grid: [10, 10],
  738. maxConnections: -1,
  739. overlays: [
  740. ['Arrow', { width: 12, length: 12, location: 0.5 }],
  741. // ["Label", { location: 0.5, label: "标签", cssClass: "endpointTargetLabel", visible: true, id: "label" }]
  742. ],
  743. // anchor: ['Bottom'],
  744. // anchor: "Continuous"
  745. }
  746. plumbInit.addEndpoint(item.id + '', common)
  747. plumbInit.setSourceEnabled(item.id, common);
  748. plumbInit.setTargetEnabled(item.id, common);
  749. plumbInit.setDraggable(item.id, true);
  750. plumbInit.makeSource(item.id, {
  751. filter: ".plumbNode",
  752. filterExclude: false,
  753. allowLoopback: true,
  754. maxConnections: -1,
  755. Container: "plumbBox",
  756. anchor: "Continuous",
  757. connector: ['Flowchart'],
  758. endpoint: item.endpoint,
  759. overlays: item.overlays,
  760. paintStyle: item.paintStyle,
  761. hoverPaintStyle: item.hoverPaintStyle,
  762. endpointStyle: item.endpointStyle,
  763. });
  764. plumbInit.makeTarget(item.id, {
  765. filter: ".plumbNode",
  766. filterExclude: false,
  767. allowLoopback: true,
  768. maxConnections: 1,
  769. Container: "plumbBox",
  770. anchor: "Continuous",
  771. connector: ['Flowchart'],
  772. endpoint: item.endpoint,
  773. overlays: item.overlays,
  774. paintStyle: item.paintStyle,
  775. hoverPaintStyle: item.hoverPaintStyle,
  776. endpointStyle: item.endpointStyle,
  777. });
  778. plumbInit.draggable(item.id, {
  779. containment: "parent",
  780. stop: function (el) {
  781. item.left = el.pos[0];
  782. item.top = el.pos[1];
  783. },
  784. });
  785. console.log(item, 'item555');
  786. };
  787. // 给元素设置渲染样式
  788. const getStyle = function (item) {
  789. return {
  790. position: "absolute",
  791. left: item.left + "px",
  792. top: item.top + "px",
  793. width: "calc(100% - 750px)",
  794. height: "40px",
  795. lineHeight: "40px",
  796. textAlign: "center",
  797. color:' #255CE7',
  798. backgroundColor: '#EDF2FF',
  799. cursor: "pointer",
  800. border:' solid 1px #255CE7'
  801. };
  802. };
  803. //初始化jsplumb实例
  804. let plumbInit = jsPlumb.getInstance();
  805. //
  806. plumbInit.bind("click", (conn, originalEvent) => {
  807. console.log(conn, "点击连线1");
  808. let lineInfo = {};
  809. // console.log(info.value, "整体信息");
  810. let sourceInfo = info.value.find((v) => v.id === conn.sourceId);
  811. let targetInfo = info.value.find((v) => v.id === conn.targetId);
  812. lineInfo = {
  813. sourceInfo,
  814. targetInfo,
  815. };
  816. rightForm.value.getLineInfo(lineInfo);
  817. // console.log("点击了", coon, originalEvent);
  818. // plumbInit.deleteConnection(conn);
  819. });
  820. //连线触发事件
  821. plumbInit.bind("connection", (event) => {
  822. // console.log(event, "新的连线事件触发");
  823. // forceUpdate();
  824. let sourceNode = info.value.find((item) => item.id === event.sourceId);
  825. console.log(sourceNode.to, event.targetId, "???");
  826. if (sourceNode.to.findIndex((v) => v === event.targetId) === -1) {
  827. sourceNode.to.push(event.targetId);
  828. }
  829. plumbInit.repaint();
  830. nextTick(() => {
  831. renderFlag.value = "new";
  832. });
  833. if (renderFlag.value === "new") {
  834. console.log("新的页面刷新");
  835. renderFlag.value = "once";
  836. renderNode("new");
  837. }
  838. // console.log(info.value,'所有节点')
  839. // renderNode()
  840. });
  841. //切换动态节点
  842. function sendActive(node) {
  843. activeNode.value = node;
  844. console.log(activeNode.value, "动态节点");
  845. rightForm.value.changeFormData(activeNode.value.config);
  846. }
  847. onMounted(() => {
  848. setTimeout(() => {
  849. // info.value = [
  850. // {
  851. // name: "div1",
  852. // to: ["div2", "div3"],
  853. // top: 300,
  854. // left: 100,
  855. // color: "red",
  856. // context: "开始运行",
  857. // status: "success",
  858. // isSource: true,
  859. // isTarget: false,
  860. // },
  861. // {
  862. // name: "div2",
  863. // to: ["div4"],
  864. // top: 200,
  865. // left: 500,
  866. // color: "green",
  867. // context: "构建任务1",
  868. // status: "success",
  869. // isSource: true,
  870. // isTarget: true,
  871. // },
  872. // {
  873. // name: "div3",
  874. // to: ["div5"],
  875. // top: 400,
  876. // left: 500,
  877. // color: "green",
  878. // context: "构建任务2",
  879. // status: "error",
  880. // isSource: true,
  881. // isTarget: true,
  882. // },
  883. // {
  884. // name: "div4",
  885. // to: [],
  886. // top: 200,
  887. // left: 900,
  888. // color: "blue",
  889. // context: "完成部署1",
  890. // status: "success",
  891. // isSource: false,
  892. // isTarget: true,
  893. // },
  894. // {
  895. // name: "div5",
  896. // to: [],
  897. // top: 400,
  898. // left: 900,
  899. // color: "blue",
  900. // context: "完成部署2",
  901. // status: "loading",
  902. // isSource: false,
  903. // isTarget: true,
  904. // },
  905. // ]
  906. renderNode();
  907. nextTick(() => {
  908. console.log("页面初次渲染完毕");
  909. renderFlag.value = "render";
  910. });
  911. }, 2000);
  912. jsPlumb.bind('click', function (conn, originalEvent) {
  913. // console.log(conn,'comn');
  914. // if (window.prompt('确定删除所点击的链接吗? 输入1确定') === '1') {
  915. // jsPlumb.detach(conn)
  916. // }
  917. })
  918. });
  919. //右侧保存值
  920. const changeActiveNodeInfo = (info) => {
  921. console.log(info, "保存后的新值");
  922. activeNode.value.config = info;
  923. nextTick(() => {
  924. renderFlag.value = "new";
  925. makeFun(activeNode.value);
  926. });
  927. };
  928. //删除线
  929. const deleteLine = (deleteLineInfo) => {
  930. console.log(deleteLineInfo, "要删除的连线信息");
  931. console.log(info.value, "全量信息");
  932. let sourceIndex = info.value.findIndex(
  933. (item) => item.id === deleteLineInfo.sourceInfo.id
  934. );
  935. let deleteTargetId = deleteLineInfo.targetInfo.id;
  936. let deleteTargetIndex = info.value[sourceIndex].to.findIndex(
  937. (v) => v === deleteTargetId
  938. );
  939. info.value[sourceIndex].to.splice(deleteTargetIndex, 1);
  940. renderNode();
  941. };
  942. //删除节点
  943. const deleteNode = (nodeInfo) => {
  944. console.log(activeNode.value);
  945. let nodeIndex = info.value.findIndex(
  946. (item) => item.id === activeNode.value.id
  947. );
  948. info.value.splice(nodeIndex, 1);
  949. info.value.forEach((item) => {
  950. let flagIndex = item.to.findIndex((v) => v === activeNode.value.id);
  951. if (flagIndex !== -1) {
  952. item.to.splice(flagIndex, 1);
  953. }
  954. });
  955. console.log(info.value, "节点列表");
  956. renderNode();
  957. activeNode.value = {};
  958. rightForm.value.changeFormData([]);
  959. };
  960. //暴露给父组件的值,需要父组件发送请求
  961. defineExpose({
  962. plumbList,
  963. info,
  964. });
  965. </script>
  966. <style lang="less" scoped>
  967. ul {
  968. list-style: none;
  969. margin: 0;
  970. padding: 0;
  971. margin-left: 5px;
  972. }
  973. .box {
  974. width: 100%;
  975. height: calc(100vh - 360px);
  976. display: flex;
  977. margin-top: 10px;
  978. }
  979. .leftMenu {
  980. width: calc(100% - 1080px);
  981. border-right: 1px solid #d3d3d3;
  982. border-bottom: 1px solid #d3d3d3;
  983. h3 {
  984. width: 100%;
  985. height: 30px;
  986. line-height: 30px;
  987. background: #eee;
  988. text-align: center;
  989. }
  990. .content {
  991. width: calc(100% - 20px);
  992. height: 40px;
  993. border: solid 1px #255CE7;
  994. text-align: center;
  995. line-height: 40px;
  996. margin-bottom: 10px;
  997. // margin-right: 10px;
  998. cursor: pointer;
  999. color: #255CE7;
  1000. background-color: #EDF2FF;
  1001. overflow-y: auto;
  1002. }
  1003. }
  1004. .plumbBox {
  1005. overflow: scroll;
  1006. position: relative;
  1007. // margin: 0 20px;
  1008. width: 100%;
  1009. border-right: 1px solid #d3d3d3;
  1010. }
  1011. .rightContent {
  1012. width: 240px;
  1013. border-bottom: 1px solid #d3d3d3;
  1014. h3 {
  1015. width: 200px;
  1016. height: 30px;
  1017. line-height: 30px;
  1018. text-align: center;
  1019. }
  1020. }
  1021. .plumbNode {
  1022. float: left;
  1023. line-height: 45px;
  1024. }
  1025. .activePlumbNode {
  1026. float: left;
  1027. line-height: 45px;
  1028. background: #0bcfe9;
  1029. }
  1030. .normalNode {
  1031. background-color: #fff;
  1032. }
  1033. .activeNode {
  1034. background-color: #80eaf8;
  1035. }
  1036. :deep(.el-collapse-item__content){
  1037. padding: 0;
  1038. }
  1039. .setBox{
  1040. width: 100%;
  1041. height: calc(100% - 500px);
  1042. border: 1px solid blue;
  1043. }
  1044. </style>