virtualnformation.vue 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627
  1. <!-- 虚端子关系 -->
  2. <template>
  3. <div class="main">
  4. <!-- 关联图 -->
  5. <div class="main-cont" id="treedom3">
  6. <div class="main-left">
  7. <div v-for="(item, index) in leftList" :key="index" class="conts">
  8. <div class="cont-title">
  9. <img :src="devicePng" alt="" class="img-item" />
  10. <div class="cont-item">
  11. <div>{{ item.descName }}</div>
  12. <div>{{ item.ied_name }}</div>
  13. </div>
  14. </div>
  15. <div
  16. v-for="(cItem, index2) in item.titleItems"
  17. :key="index2"
  18. :ref="(el) => setdomLeftChild3(el, cItem)"
  19. >
  20. <div class="text-midle">
  21. <div>
  22. {{
  23. `${cItem.attr_ld_inst}/${cItem.attr_prefix}${cItem.attr_ln_class}.${cItem.attr_do_name}.${cItem.attr_da_name}`
  24. }}
  25. </div>
  26. <div>{{ cItem.do_source_desc }}</div>
  27. </div>
  28. </div>
  29. </div>
  30. </div>
  31. <!-- 中间部分 -->
  32. <div class="main-middle" ref="middleHeight" id="end">
  33. <div class="cont-title">
  34. <img :src="devicePng" alt="" class="img-item" />
  35. <div class="middle-title" v-if="tagList">
  36. <div>{{ middleTitelDesc }}</div>
  37. <div>{{ tagList.ied_name }}</div>
  38. </div>
  39. </div>
  40. <div class="middle-item">
  41. <div
  42. class="midle-cont"
  43. v-for="(item, index) in svInfo"
  44. :key="index"
  45. :ref="(el) => setdomMiddle3(el, item)"
  46. >
  47. <div
  48. style="margin: 0 4px"
  49. v-if="!item.isDecollate"
  50. :class="{
  51. 'del-middle': item.opt == 'd',
  52. 'upt-middle': item.opt == 'u',
  53. }"
  54. >
  55. {{ item.no }}
  56. </div>
  57. <div
  58. class="midlestyle"
  59. v-if="!item.isDecollate"
  60. :class="{
  61. 'add-middle': item.opt == 'i',
  62. 'del-middle': item.opt == 'd',
  63. 'upt-middle': item.opt == 'u',
  64. }"
  65. >
  66. <div class="middle-cont">
  67. <div v-if="item.inout_type == 'out'">
  68. <span v-if="item.attr_da_name">
  69. {{
  70. `${item.attr_ld_inst}/${item.attr_prefix}${item.attr_ln_class}${item.attr_ln_inst}.${item.attr_do_name}.${item.attr_da_name}`
  71. }}
  72. </span>
  73. <span v-else>
  74. {{
  75. `${item.attr_ld_inst}/${item.attr_prefix}${item.attr_ln_class}${item.attr_ln_inst}.${item.attr_do_name}`
  76. }}
  77. </span>
  78. </div>
  79. <div v-else-if="item.inout_type == 'in'">
  80. {{ item.attr_int_addr }}
  81. </div>
  82. <div v-if="item.inout_type == 'out'">
  83. {{ item.do_source_desc }}
  84. </div>
  85. <div v-else-if="item.inout_type == 'in'">
  86. {{ item.do_target_desc }}
  87. </div>
  88. </div>
  89. <div style="flex: 1">
  90. <img
  91. :src="addMiddlePng"
  92. alt=""
  93. class="img-item-middle"
  94. v-if="item.opt == 'i'"
  95. />
  96. <img
  97. :src="delMiddlePng"
  98. alt=""
  99. class="img-item-middle"
  100. v-else-if="item.opt == 'd'"
  101. />
  102. <img
  103. :src="uptMiddlePng"
  104. alt=""
  105. class="img-item-middle"
  106. v-else-if="item.opt == 'u'"
  107. />
  108. </div>
  109. </div>
  110. <!-- 省略号 -->
  111. <div class="midlestyle omit" v-if="item.isDecollate">
  112. {{ item.do_source_desc }}
  113. </div>
  114. </div>
  115. </div>
  116. </div>
  117. <!-- 右侧 -->
  118. <div class="main-right">
  119. <div v-for="(item, index) in rightList" :key="index" class="conts">
  120. <div class="cont-title">
  121. <img :src="devicePng" alt="" class="img-item" />
  122. <div class="cont-item">
  123. <div>{{ item.descName }}</div>
  124. <div>{{ item.ied_name }}</div>
  125. </div>
  126. </div>
  127. <div
  128. v-for="(cItem, index2) in item.titleItems"
  129. :key="index2"
  130. :ref="(el) => setdomRightChild3(el, cItem)"
  131. >
  132. <div class="text-midle">
  133. <div>{{ `${cItem.attr_int_addr}` }}</div>
  134. <div>{{ cItem.do_target_desc }}</div>
  135. </div>
  136. </div>
  137. </div>
  138. </div>
  139. <div id="wrapper"></div>
  140. </div>
  141. </div>
  142. </template>
  143. <script setup>
  144. import { onMounted, watch, ref, nextTick, defineEmits, inject } from "vue";
  145. import devicePng from "@/assets/image/instruct/device.png";
  146. import addMiddlePng from "@/assets/image/CID/add_middle.png";
  147. import delMiddlePng from "@/assets/image/CID/del_middle.png";
  148. import uptMiddlePng from "@/assets/image/CID/upt_middle.png";
  149. import LeaderLine from "../../../../public/leader-line.min.js";
  150. import AnimEvent from "../../../../public/anim-event.min.js";
  151. import { compResult } from "@/api/scdCheck/scdCheck2";
  152. import { scdIedRelation } from "@/api/iedNetwork";
  153. import { useRoute } from "vue-router";
  154. const route = useRoute();
  155. const props = defineProps({
  156. //对比结果scd所传参数
  157. clickRowDatas: {
  158. type: Object,
  159. default: () => {},
  160. },
  161. clickList: {
  162. type: Object,
  163. default: () => {},
  164. },
  165. clickCodeValue: {
  166. type: String,
  167. default: "",
  168. },
  169. OpensclTrue: {
  170. type: Boolean,
  171. default: false,
  172. },
  173. recordDelIedVer: {
  174. //删除的列表数据,从sclUpdate中获取的
  175. type: Array,
  176. default: () => [],
  177. },
  178. });
  179. const svInfo = ref(null);
  180. //处理两边的数据
  181. const processBoth = (list, svResInfo, inoutType) => {
  182. const iedNames = {};
  183. list.forEach(async (item, index) => {
  184. item.titleItems = [];
  185. svResInfo.data.forEach((key) => {
  186. if (key.ied_name == item.ied_name) {
  187. if (key.inout_type == inoutType) {
  188. item.titleItems.push(key);
  189. }
  190. }
  191. });
  192. let iedNames = {};
  193. //为了得到每个小板块的标题描述
  194. if (!iedNames[item.ied_name]) {
  195. iedNames[item.ied_name] = true;
  196. if (!item.isdel) {
  197. const iedRes = await scdIedRelation({
  198. scd_id: scdIdValue,
  199. ied_name: item.ied_name,
  200. reset: 1,
  201. });
  202. item.descName = iedRes.data[item.ied_name].desc;
  203. } else {
  204. const iedRes = await scdIedRelation({
  205. scd_id: props.clickRowDatas.target_id,
  206. ied_name: item.ied_name,
  207. reset: 1,
  208. });
  209. item.descName = iedRes.data[item.ied_name].desc;
  210. }
  211. }
  212. });
  213. };
  214. let leaderLines3 = ref([]); //控制线条显示
  215. const leftList = ref([]);
  216. const rightList = ref([]);
  217. const domListMiddle3 = ref(new Map()); //获取中间所有的ref
  218. const domListRightChild3 = ref(new Map()); //获取右侧所有子的ref
  219. const domListLeftChild3 = ref(new Map()); //获取中间所有子的ref
  220. const emit = defineEmits(["result"]); //如果不加这个再次点击左侧会没有反应
  221. const setdomMiddle3 = (el, item) => {
  222. // 中间dom
  223. if (el) {
  224. domListMiddle3.value.set(item, el);
  225. }
  226. };
  227. //左侧子Dom
  228. const setdomLeftChild3 = (el, item) => {
  229. if (el) {
  230. domListLeftChild3.value.set(item, el);
  231. }
  232. };
  233. //右侧子Dom
  234. const setdomRightChild3 = (el, item) => {
  235. if (el) {
  236. domListRightChild3.value.set(item, el);
  237. }
  238. };
  239. let tagList = ref(props.clickList); //左侧更改的设备列表
  240. // //scd一致性对比===
  241. const clickRow = ref(props.clickRowDatas);
  242. // //对比文件:头部对比的单个数据
  243. watch(
  244. () => props.clickRowDatas,
  245. (newValue) => {
  246. if (newValue) {
  247. clickRow.value = newValue;
  248. }
  249. }
  250. );
  251. //点击装置列表的数据变化
  252. watch(
  253. () => props.clickList,
  254. (newValue) => {
  255. if (newValue) {
  256. tagList.value = newValue;
  257. clickResetLine3()
  258. getNetworkInfo3();
  259. }
  260. }
  261. );
  262. const cClickCode = ref(props.clickCodeValue); //点击侧边栏差异项的code
  263. watch(
  264. () => props.clickCodeValue,
  265. (newValue) => {
  266. if (newValue) {
  267. cClickCode.value = newValue;
  268. clickResetLine3();
  269. getNetworkInfo3();
  270. }
  271. }
  272. );
  273. // //scd一致性对比====
  274. const bothdataList = ref([]); //处理得到总的两边的数据
  275. const middleTitelDesc = ref('');//中间数据的标题
  276. //得到中间的子版块数据
  277. const getNetworkInfo3 = async () => {
  278. bothdataList.value = [];
  279. leftList.value = [];
  280. rightList.value = [];
  281. let svResInfo;
  282. //scd一致性对比====
  283. const ids = clickRow.value ? clickRow.value.id : "";
  284. const names = tagList.value ? tagList.value.ied_name : "";
  285. svResInfo = await compResult({
  286. comp_id: ids,
  287. ied_name: names,
  288. comptype: "u",
  289. itemcode: cClickCode.value,
  290. });
  291. const iedRes = await scdIedRelation({
  292. scd_id: scdIdValue,
  293. ied_name: names,
  294. reset: 1,
  295. });
  296. middleTitelDesc.value = iedRes.data[names].desc
  297. //省略号===
  298. const data = {
  299. attr_ld_inst: "",
  300. attr_ln_class: "",
  301. attr_ln_inst: "",
  302. attr_do_name: "",
  303. attr_da_name: "",
  304. do_source_desc: "...",
  305. no: "",
  306. isDecollate: true,
  307. };
  308. //省略号===
  309. svResInfo.data.sort(function (a, b) {
  310. return a.no - b.no; // 从小到大排序
  311. });
  312. //得到两边总的每个版块的ied_name名称
  313. const maps = new Map();
  314. svResInfo.data.forEach((item) => {
  315. const key = item.ied_name + item.inout_type; //输入输出都是需要的
  316. if (!maps.has(key)) {
  317. maps.set(key, true);
  318. if (item.inout_type == "in") {
  319. leftList.value.push(item);
  320. } else if (item.inout_type == "out") {
  321. rightList.value.push(item);
  322. }
  323. }
  324. });
  325. // recordDelIedVer 删除的全部数据
  326. props.recordDelIedVer.forEach((itemdel) => {
  327. svResInfo.data.forEach((item) => {
  328. if (itemdel.ied_name == item.ied_name) {
  329. item.isdel = true;
  330. }
  331. });
  332. });
  333. //处理两边的数据
  334. if (svResInfo.data.length > 0) {
  335. processBoth(leftList.value, svResInfo, "in");
  336. processBoth(rightList.value, svResInfo, "out");
  337. }
  338. //处理中间的数据有省略号的
  339. let newData = [];
  340. for (let i = 0; i < svResInfo.data.length; i++) {
  341. newData.push(svResInfo.data[i]);
  342. if (
  343. i < svResInfo.data.length - 1 &&
  344. svResInfo.data[i].ied_name != svResInfo.data[i + 1].ied_name
  345. ) {
  346. newData.push(data);
  347. }
  348. }
  349. svInfo.value = newData;
  350. };
  351. //点击后重置数据和线条
  352. const clickResetLine3 = () => {
  353. domListMiddle3.value.clear();
  354. domListLeftChild3.value.clear();
  355. domListRightChild3.value.clear();
  356. leaderLines3.value = [];
  357. removeLine3();
  358. setLine();
  359. };
  360. const setLeaderline = () => {
  361. //左侧子组件
  362. for (let [key, value] of domListMiddle3.value) {
  363. for (const [key2, value2] of domListLeftChild3.value) {
  364. const endDom = value2;
  365. if (key.node_id == key2.node_id) {
  366. const line = new LeaderLine(endDom, value, {
  367. color: "#7484AB",
  368. size: 2,
  369. path: "straight",
  370. startSocket: "right",
  371. endSocket: "left",
  372. y: 50,
  373. startPlug: "disc",
  374. endPlug: "arrow1",
  375. });
  376. leaderLines3.value.push(line);
  377. }
  378. }
  379. }
  380. for (let [key, value] of domListMiddle3.value) {
  381. //右侧子组件
  382. for (const [key2, value2] of domListRightChild3.value) {
  383. const endDom = value2;
  384. if (key.node_id == key2.node_id) {
  385. const line2 = new LeaderLine(value, endDom, {
  386. color: "#7484AB",
  387. size: 2,
  388. path: "straight",
  389. startSocket: "right",
  390. endSocket: "left",
  391. startPlug: "disc",
  392. endPlug: "arrow1",
  393. });
  394. leaderLines3.value.push(line2);
  395. }
  396. }
  397. }
  398. hiddenLine();
  399. };
  400. //滚动时重定位线条
  401. const newPositionLine = () => {
  402. document.getElementById("treedom3").addEventListener(
  403. "scroll",
  404. AnimEvent.add(() => {
  405. leaderLines3.value.forEach((line) => {
  406. if (line) {
  407. hiddenLine();
  408. line.position();
  409. line.positionByWindowResize = false;
  410. }
  411. });
  412. //中间展示图片的
  413. }),
  414. false
  415. );
  416. document.getElementById("treedom3").addEventListener(
  417. "resize",
  418. AnimEvent.add(function () {
  419. diffline.forEach((line) => {
  420. hiddenLine();
  421. line.position();
  422. line.positionByWindowResize = false;
  423. });
  424. }),
  425. false
  426. );
  427. };
  428. //弹窗打开后使得线条在指定区域中
  429. const hiddenLine = () => {
  430. const elmWrapper = document.getElementById("wrapper");
  431. // 移动 line
  432. document.body.querySelectorAll("body .leader-line").forEach((node) => {
  433. elmWrapper.appendChild(node);
  434. });
  435. elmWrapper.style.transform = "none";
  436. var rectWrapper = elmWrapper.getBoundingClientRect();
  437. // Move to the origin of coordinates as the document
  438. elmWrapper.style.transform = `translate(${
  439. (rectWrapper.left + window.scrollY) * -1
  440. }px, ${(rectWrapper.top + window.scrollX) * -1}px)`;
  441. };
  442. const setLine = () => {
  443. setTimeout(() => {
  444. setLeaderline();
  445. newPositionLine();
  446. }, 500);
  447. };
  448. const removeLine3 = () => {
  449. leaderLines3.value = [];
  450. const elmWrapper = document.getElementById("wrapper");
  451. if (elmWrapper) {
  452. document.body.querySelectorAll("#wrapper .leader-line").forEach((node) => {
  453. elmWrapper.removeChild(node);
  454. });
  455. }
  456. };
  457. let scdIdValue = "";
  458. onMounted(() => {
  459. scdIdValue = route.query.id;
  460. getNetworkInfo3();
  461. //不加条件切换下方tab时会出现bug
  462. nextTick(() => {
  463. setLine();
  464. });
  465. });
  466. watch(
  467. () => props.OpensclTrue,
  468. (newValue) => {
  469. if (newValue) {
  470. domListMiddle3.value.clear();
  471. domListLeftChild3.value.clear();
  472. domListRightChild3.value.clear();
  473. leaderLines3.value = [];
  474. }
  475. nextTick(() => {
  476. removeLine3();
  477. });
  478. }
  479. );
  480. </script>
  481. <style lang="scss" scoped>
  482. @mixin img-size {
  483. width: 48px;
  484. height: 48px;
  485. }
  486. @mixin left-and-right {
  487. display: flex;
  488. flex-direction: column;
  489. }
  490. .main-cont {
  491. display: flex;
  492. justify-content: space-evenly;
  493. margin-top: 60px;
  494. overflow-y: auto;
  495. }
  496. .leader-line {
  497. z-index: 3000;
  498. }
  499. .main-left {
  500. display: flex;
  501. @include left-and-right;
  502. }
  503. .main-middle {
  504. box-sizing: border-box;
  505. border: 2px dashed #98a8ff;
  506. background: #edf3ff;
  507. margin: 0 60px;
  508. img {
  509. @include img-size;
  510. }
  511. .middle-item {
  512. @include left-and-right;
  513. align-items: center;
  514. cursor: pointer;
  515. }
  516. .cont-title {
  517. display: flex;
  518. align-items: center;
  519. margin: 12px 14px 5px 14px;
  520. border-bottom: 1px solid #a3ade0;
  521. }
  522. .middle-title {
  523. color: #ffcb11;
  524. margin-left: 8px;
  525. }
  526. }
  527. .main-right,
  528. .main-left {
  529. display: flex;
  530. @include left-and-right;
  531. .img-item {
  532. @include img-size;
  533. }
  534. }
  535. .conts {
  536. @include left-and-right;
  537. margin-bottom: 24px;
  538. border: 2px dashed #98a8ff;
  539. cursor: pointer;
  540. background: #f7f8fb;
  541. padding: 12px;
  542. .cont-title {
  543. display: flex;
  544. align-items: center;
  545. margin: 12px 14px 5px 14px;
  546. padding: 12px;
  547. border-bottom: 1px solid #a3ade0;
  548. }
  549. .cont-item {
  550. color: #1a2447;
  551. margin-left: 6px;
  552. vertical-align: middle;
  553. }
  554. .ied-desc {
  555. color: #255ce7;
  556. }
  557. .ied-desc-title {
  558. color: #134bea;
  559. }
  560. .ied-desc-child-title {
  561. color: #5182ff;
  562. margin-left: 14px;
  563. display: block;
  564. }
  565. }
  566. .midle-cont {
  567. display: flex;
  568. border: 1px solid #7484ab;
  569. align-items: center;
  570. width: 94%;
  571. margin-bottom: 8px;
  572. color: #1a2447;
  573. }
  574. .ied-desc-child,
  575. .midlestyle {
  576. @include left-and-right;
  577. flex-direction: row;
  578. justify-content: center;
  579. align-items: center;
  580. border-radius: 2px;
  581. padding: 5px;
  582. color: #1a2447;
  583. flex: 1;
  584. .img-item-middle {
  585. width: 22px;
  586. height: 22px;
  587. }
  588. .middle-cont {
  589. display: flex;
  590. flex-direction: column;
  591. align-items: center;
  592. width: 90%;
  593. }
  594. }
  595. .del-middle {
  596. color: #e50505;
  597. }
  598. .upt-middle {
  599. color: #ffa011;
  600. }
  601. .omit {
  602. font-weight: bold;
  603. letter-spacing: 8px;
  604. font-size: 15px;
  605. }
  606. #wrapper {
  607. width: 0;
  608. height: 0;
  609. position: relative; /* Origin of coordinates for lines, and scrolled content (i.e. not `absolute`) */
  610. }
  611. .text-midle {
  612. text-align: center;
  613. width: 94%;
  614. border: 1px solid #7484ab;
  615. margin-bottom: 8px;
  616. border-radius: 2px;
  617. padding: 5px;
  618. }
  619. </style>