relationShip.vue 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454
  1. <!-- 装置关联关系 -->
  2. <template>
  3. <div
  4. v-loading="loading"
  5. element-loading-text="加载数据中"
  6. >
  7. <div class="main-cont" ref="myElement" id="treedom" style="overflow: hidden">
  8. <div class="main-left" ref="leftElement">
  9. <div
  10. v-for="(item, index) in leftList"
  11. :key="index"
  12. class="cont"
  13. @click="clickImg(item)"
  14. >
  15. <img
  16. :src="devicePng"
  17. alt=""
  18. class="img-item"
  19. :ref="(el) => setdom(el, item)"
  20. />
  21. <div>{{ item.ref_ied_name }}</div>
  22. <div class="ied-desc">{{ item.ref_ied_desc }}</div>
  23. </div>
  24. </div>
  25. <div class="main-middle">
  26. <div class="middle-item" ref="middleElement">
  27. <img :src="devicePng" alt="" id="end" />
  28. <div v-if="listData">{{ listData.ied_name }}</div>
  29. <div v-if="listData">{{ listData.desc }}</div>
  30. </div>
  31. </div>
  32. <div class="main-right" ref="rightElement">
  33. <div
  34. v-for="(item, index) in rightList"
  35. :key="index"
  36. class="cont"
  37. @click="clickImg(item)"
  38. >
  39. <img
  40. :src="devicePng"
  41. alt=""
  42. class="img-item"
  43. :ref="(el) => setdomRight(el, item)"
  44. />
  45. <div>{{ item.ref_ied_name }}</div>
  46. <div class="ied-desc">{{ item.ref_ied_desc }}</div>
  47. </div>
  48. </div>
  49. </div>
  50. <div id="wrapper"></div>
  51. </div>
  52. </template>
  53. <script setup>
  54. import { onMounted, watch, ref, nextTick, defineEmits, inject } from "vue";
  55. import devicePng from "@/assets/image/instruct/device.png";
  56. import LeaderLine from "../../../../public/leader-line.min.js";
  57. import AnimEvent from "../../../../public/anim-event.min.js";
  58. import { useRoute } from "vue-router";
  59. import { clickImgEvent } from "@/utils/common.js";
  60. const route = useRoute();
  61. const loading = ref(true);
  62. const props = defineProps({
  63. checkData: {
  64. type: Object,
  65. default: () => {},
  66. },
  67. isOpen: {
  68. type: Boolean,
  69. default: false,
  70. },
  71. iedRelation: {
  72. type: Object,
  73. default: () => {},
  74. },
  75. isScdView: {
  76. type: Boolean,
  77. default: false,
  78. },
  79. });
  80. const middleElement = ref(null);
  81. const rightElement = ref(null);
  82. const leftElement = ref(null);
  83. const myElement = ref(null);
  84. let leaderLines = ref([]); //控制线条显示
  85. const leftList = ref([]);
  86. const rightList = ref([]);
  87. const domList = ref(new Map()); //获取 所有的ref
  88. const domListRight = ref(new Map()); //获取 所有的ref
  89. const listData = ref(props.checkData); //线条左右两侧的数据
  90. const emit = defineEmits(["result"]); //如果不加这个再次点击左侧会没有反应
  91. const setdom = (el, item) => {
  92. //左侧dom
  93. if (el) {
  94. domList.value.set(item, el);
  95. }
  96. };
  97. const setdomRight = (el, item) => {
  98. //右侧dom
  99. if (el) {
  100. domListRight.value.set(item, el);
  101. }
  102. };
  103. const processArray = (arr) => {
  104. // ref_ied_id作为键,obj作为值
  105. const uniqueObjects = new Map();
  106. if (!arr) return;
  107. // 遍历数组
  108. for (const obj of arr) {
  109. const { ref_ied_id } = obj;
  110. // 如果当前对象的 ref_ied_id 属性已经存在于 uniqueObjects 中
  111. if (uniqueObjects.has(ref_ied_id)) {
  112. // 将对应对象的 ref_type 属性设为 2,箭头双向
  113. uniqueObjects.get(ref_ied_id).ref_type = 2;
  114. } else {
  115. // 否则,将当前对象添加到 uniqueObjects 中
  116. uniqueObjects.set(ref_ied_id, obj);
  117. }
  118. }
  119. // 将 uniqueObjects 中的值转为数组并返回
  120. return Array.from(uniqueObjects.values());
  121. };
  122. //点击图片的时候筛选出数据
  123. const clickImg = async (dataItem) => {
  124. loading.value = true;
  125. listData.value = await clickImgEvent(props, dataItem, scdIdValue);
  126. if (!listData.value || !listData.value.length) return (loading.value = false);
  127. };
  128. watch(
  129. () => props.checkData,
  130. (newValue, oldV) => {
  131. loading.value = true;
  132. listData.value = newValue;
  133. if (newValue && leaderLines.value.length > 0) {
  134. // leaderLines.value.forEach((line) => line.remove()); //清除连线
  135. leaderLines.value = [];
  136. }
  137. },
  138. { deep: true }
  139. );
  140. watch(
  141. () => listData.value,
  142. (newValue) => {
  143. emit("result", newValue);
  144. clickResetLine();
  145. }
  146. );
  147. watch(
  148. () => props.isOpen,
  149. (newValue) => {
  150. if (newValue) {
  151. domList.value.clear();
  152. domListRight.value.clear();
  153. leaderLines.value = [];
  154. nextTick(() => {
  155. // middleLinePosition();
  156. removeLine();
  157. });
  158. }
  159. }
  160. );
  161. //点击后重置数据和线条
  162. const clickResetLine = () => {
  163. domList.value.clear();
  164. domListRight.value.clear();
  165. leaderLines.value = [];
  166. removeLine();
  167. middleLinePosition();
  168. setLine();
  169. };
  170. // 将设备列表分成两份
  171. const bothSide = (data) => {
  172. const formatArr = processArray(data);
  173. if (!formatArr && formatArr.length) return;
  174. const arrlenght = formatArr.length;
  175. const long1 = Math.ceil(arrlenght / 2);
  176. leftList.value = formatArr.splice(0, long1);
  177. rightList.value = formatArr.splice(0);
  178. };
  179. const setLeaderline = () => {
  180. //线条样式
  181. const lineStyle1 = {
  182. color: "#51637F",
  183. size: 2,
  184. path: "straight",
  185. endPlug: "arrow1",
  186. startSocket: "right",
  187. endSocket: "left",
  188. };
  189. const lineStyle0 = {
  190. ...lineStyle1,
  191. startPlug: "arrow1",
  192. endPlug: "behind",
  193. };
  194. const lineStyle2 = {
  195. color: "#134BEA",
  196. size: 2,
  197. path: "straight",
  198. startPlug: "arrow1",
  199. endPlug: "arrow1",
  200. startSocket: "right",
  201. endSocket: "left",
  202. };
  203. const lineStyleRight1 = {
  204. ...lineStyle1,
  205. startSocket: "left",
  206. endSocket: "right",
  207. };
  208. const lineStyleRight0 = {
  209. ...lineStyleRight1,
  210. startPlug: "arrow1",
  211. endPlug: "behind",
  212. };
  213. const lineStyleRight2 = {
  214. ...lineStyle2,
  215. startSocket: "left",
  216. endSocket: "right",
  217. };
  218. const startDom = document.getElementById("end");
  219. let count = 56;
  220. //循环画线
  221. for (const [key, value] of domList.value) {
  222. const endDom = value;
  223. let line;
  224. count += 5;
  225. LeaderLine.positionByWindowResize = false;
  226. if (key.ref_type == 0) {
  227. line = new LeaderLine(
  228. endDom,
  229. LeaderLine.pointAnchor(startDom, { x: 0, y: count }),
  230. lineStyle0
  231. );
  232. } else if (key.ref_type == 1) {
  233. line = new LeaderLine(
  234. endDom,
  235. LeaderLine.pointAnchor(startDom, { x: 0, y: count }),
  236. lineStyle1
  237. );
  238. } else if (key.ref_type == 2) {
  239. line = new LeaderLine(
  240. endDom,
  241. LeaderLine.pointAnchor(startDom, { x: 0, y: count }),
  242. lineStyle2
  243. );
  244. }
  245. // 保存进数组,方便进行遍历删除
  246. leaderLines.value.push(line);
  247. }
  248. let count2 = 56;
  249. //循环画线右侧
  250. for (const [key, value] of domListRight.value) {
  251. const endDom = value;
  252. let line2;
  253. count2 += 5;
  254. LeaderLine.positionByWindowResize = false;
  255. if (key.ref_type == 0) {
  256. line2 = new LeaderLine(
  257. endDom,
  258. LeaderLine.pointAnchor(startDom, { x: "100%", y: count2 }),
  259. lineStyleRight0
  260. );
  261. } else if (key.ref_type == 1) {
  262. line2 = new LeaderLine(
  263. endDom,
  264. LeaderLine.pointAnchor(startDom, { x: "100%", y: count2 }),
  265. lineStyleRight1
  266. );
  267. } else if (key.ref_type == 2) {
  268. line2 = new LeaderLine(
  269. endDom,
  270. LeaderLine.pointAnchor(startDom, { x: "100%", y: count2 }),
  271. lineStyleRight2
  272. );
  273. }
  274. // 保存进数组,方便进行遍历删除
  275. leaderLines.value.push(line2);
  276. }
  277. loading.value = false;
  278. hiddenLine();
  279. };
  280. //设置中间盒子的所在位置
  281. const middleLinePosition = () => {
  282. setTimeout(() => {
  283. const heights = myElement.value.scrollHeight;
  284. let leftListLength = leftList.value.length;
  285. let rightListLength = rightList.value.length;
  286. console.log("leftListLength", leftListLength, rightListLength);
  287. const setElementMarginTop = (element, value) => {
  288. element.value.style.marginTop = `${value}px`;
  289. };
  290. switch (`${rightListLength}${leftListLength}`) {
  291. case "11":
  292. setElementMarginTop(rightElement, 165);
  293. setElementMarginTop(leftElement, 165);
  294. setElementMarginTop(middleElement, 150);
  295. break;
  296. case "22":
  297. setElementMarginTop(rightElement, 92);
  298. setElementMarginTop(leftElement, 92);
  299. setElementMarginTop(middleElement, 150);
  300. break;
  301. default:
  302. if (!middleElement.value) return;
  303. if (rightListLength <= 4 && leftListLength <= 4) {
  304. setElementMarginTop(middleElement, 150);
  305. }
  306. if (leftListLength > 4 || rightListLength > 4) {
  307. setElementMarginTop(rightElement, 0);
  308. setElementMarginTop(leftElement, 0);
  309. middleElement.value.style.marginTop = `${(heights - 60) / 2}px`; // 设置元素的垂直位置
  310. }
  311. break;
  312. }
  313. const marginTopValues = { 1: 165, 2: 92, 3: 50 };
  314. setElementMarginTop(rightElement, marginTopValues[rightListLength] || 0);
  315. setElementMarginTop(leftElement, marginTopValues[leftListLength] || 0);
  316. }, 0);
  317. };
  318. let scdIdValue = "";
  319. onMounted(() => {
  320. if (props.delScdId) {
  321. scdIdValue = props.delScdId;
  322. } else {
  323. scdIdValue = route.query.id;
  324. }
  325. nextTick(() => {
  326. setLine();
  327. middleLinePosition();
  328. nextTick(() => {
  329. newPositionLine();
  330. });
  331. });
  332. });
  333. //滚动时重定位线条
  334. const newPositionLine = () => {
  335. if (!document.getElementById("treedom")) return;
  336. document.getElementById("treedom").addEventListener(
  337. "scroll",
  338. AnimEvent.add(() => {
  339. if (!leaderLines.value) return;
  340. leaderLines.value.forEach((line) => {
  341. hiddenLine();
  342. line.position();
  343. line.positionByWindowResize = false;
  344. });
  345. }),
  346. false
  347. );
  348. document.getElementById("treedom").addEventListener(
  349. "resize",
  350. AnimEvent.add(function () {
  351. if (!leaderLines.value) return;
  352. leaderLines.value.forEach((line) => {
  353. hiddenLine();
  354. line.position();
  355. line.positionByWindowResize = true;
  356. });
  357. }),
  358. false
  359. );
  360. };
  361. //弹窗打开后使得线条在指定区域中
  362. const hiddenLine = () => {
  363. const elmWrapper = document.getElementById("wrapper");
  364. if (!elmWrapper) return;
  365. // 移动 line
  366. document.body.querySelectorAll("body .leader-line").forEach((node) => {
  367. elmWrapper.appendChild(node);
  368. });
  369. elmWrapper.style.transform = "none";
  370. var rectWrapper = elmWrapper.getBoundingClientRect();
  371. // Move to the origin of coordinates as the document
  372. elmWrapper.style.transform = `translate(${
  373. (rectWrapper.left + window.scrollY) * -1
  374. }px, ${(rectWrapper.top + window.scrollX) * -1}px)`;
  375. };
  376. const setLine = () => {
  377. if (listData.value) {
  378. bothSide(listData.value.list);
  379. }
  380. setTimeout(() => {
  381. setLeaderline();
  382. }, 300);
  383. };
  384. const removeLine = () => {
  385. const elmWrapper = document.getElementById("wrapper");
  386. if (elmWrapper) {
  387. document.body.querySelectorAll("#wrapper .leader-line").forEach((node) => {
  388. elmWrapper.removeChild(node);
  389. });
  390. }
  391. };
  392. </script>
  393. <style lang="scss">
  394. @mixin img-size {
  395. width: 150px;
  396. height: 90px;
  397. margin-bottom: 10px;
  398. }
  399. @mixin left-and-right {
  400. display: flex;
  401. flex-direction: column;
  402. }
  403. .main-cont {
  404. margin-top: 60px;
  405. display: flex;
  406. justify-content: space-evenly;
  407. }
  408. .leader-line {
  409. z-index: 3000;
  410. }
  411. .main-left {
  412. display: flex;
  413. @include left-and-right;
  414. .img-item {
  415. @include img-size;
  416. }
  417. }
  418. .main-middle {
  419. box-sizing: border-box;
  420. img {
  421. margin-bottom: 10px;
  422. }
  423. .middle-item {
  424. @include left-and-right;
  425. align-items: center;
  426. color: #ffcb11;
  427. }
  428. }
  429. .main-right {
  430. display: flex;
  431. @include left-and-right;
  432. .img-item {
  433. @include img-size;
  434. }
  435. }
  436. .cont {
  437. @include left-and-right;
  438. align-items: center;
  439. margin-bottom: 10px;
  440. .ied-desc {
  441. color: #255ce7;
  442. }
  443. }
  444. #wrapper {
  445. width: 0;
  446. height: 0;
  447. position: relative; /* Origin of coordinates for lines, and scrolled content (i.e. not `absolute`) */
  448. }
  449. </style>