|
@@ -1,7 +1,88 @@
|
|
|
<!-- 输入输出控制块 -->
|
|
|
<template>
|
|
|
<div>
|
|
|
- <div v-if="isPhoto == 'photo'">关联图</div>
|
|
|
+ <!-- 关联图 -->
|
|
|
+ <div
|
|
|
+ v-if="isPhoto == 'photo'"
|
|
|
+ class="main-cont"
|
|
|
+ ref="myElement"
|
|
|
+ id="treedom"
|
|
|
+ >
|
|
|
+ <div class="main-left">
|
|
|
+ <div
|
|
|
+ v-for="(item, index) in leftList"
|
|
|
+ :key="index"
|
|
|
+ class="conts"
|
|
|
+ @click="clickImg(item)"
|
|
|
+ :ref="(el) => setdom(el, item)"
|
|
|
+ >
|
|
|
+ <div class="cont-item">
|
|
|
+ <div>{{ item.ref_ied_name }}</div>
|
|
|
+ <div>{{ item.ref_ied_desc }}</div>
|
|
|
+ </div>
|
|
|
+ <div v-for="(cItem, index2) in item.titleItems" :key="index2">
|
|
|
+ <div class="ied-desc-child-title">
|
|
|
+ {{ cItem.ld_inst }} {{ cItem.ld_desc }}
|
|
|
+ </div>
|
|
|
+ <div
|
|
|
+ class="ied-desc-child"
|
|
|
+ v-for="(item3, index3) in cItem.childItem"
|
|
|
+ :key="index3"
|
|
|
+ >
|
|
|
+ <div>{{ item3.ctrl_name }}</div>
|
|
|
+ <div>{{ item3.datset_desc }}</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <!-- 中间部分 -->
|
|
|
+ <div class="main-middle" ref="middleHeight">
|
|
|
+ <div class="middle-title">
|
|
|
+ <div v-if="listData">{{ listData.ied_name }}</div>
|
|
|
+ <div v-if="listData">{{ listData.desc }}</div>
|
|
|
+ </div>
|
|
|
+ <div class="middle-item" ref="middleElement">
|
|
|
+ <div
|
|
|
+ class="ied-desc-child middle-child"
|
|
|
+ v-for="(itemChild, index) in svInfo"
|
|
|
+ :key="index"
|
|
|
+ >
|
|
|
+ <div>{{ `${itemChild.ldinst}/${itemChild.attr_name}` }}</div>
|
|
|
+ <div>{{ itemChild.datset_desc }}</div>
|
|
|
+ <div class="ied-desc">APPID:{{ itemChild.APPID }}</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <!-- 右侧 -->
|
|
|
+ <div class="main-right">
|
|
|
+ <div
|
|
|
+ v-for="(item, index) in rightList"
|
|
|
+ :key="index"
|
|
|
+ class="conts"
|
|
|
+ @click="clickImg(item)"
|
|
|
+ :ref="(el) => setdomRight(el, item)"
|
|
|
+ >
|
|
|
+ <div class="cont-item">
|
|
|
+ <div>{{ item.ref_ied_name }}</div>
|
|
|
+ <div>{{ item.ref_ied_desc }}</div>
|
|
|
+ </div>
|
|
|
+ <div v-for="(cItem, index2) in item.titleItems" :key="index2">
|
|
|
+ <div class="ied-desc-child-title">
|
|
|
+ {{ cItem.ld_inst }} {{ cItem.ld_desc }}
|
|
|
+ </div>
|
|
|
+ <div
|
|
|
+ class="ied-desc-child"
|
|
|
+ v-for="(item3, index3) in cItem.childItem"
|
|
|
+ :key="index3"
|
|
|
+ >
|
|
|
+ <div>{{ item3.ctrl_name }}</div>
|
|
|
+ <div>{{ item3.datset_desc }}</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div id="wrapper"></div>
|
|
|
+ </div>
|
|
|
<div v-if="isPhoto != 'photo'">
|
|
|
<inoutSendOrRv
|
|
|
:isPhoto="isPhoto"
|
|
@@ -13,11 +94,17 @@
|
|
|
</template>
|
|
|
<script setup>
|
|
|
import { onMounted, watch, ref, nextTick, defineEmits, inject } from "vue";
|
|
|
+import devicePng from "@/assets/image/instruct/device.png";
|
|
|
+import LeaderLine from "../../../../public/leader-line.min.js";
|
|
|
+import AnimEvent from "../../../../public/anim-event.min.js";
|
|
|
import inoutSendOrRv from "./inoutSendOrRv";
|
|
|
import {
|
|
|
//send发送
|
|
|
iednetworkInfo,
|
|
|
+ iedSVSendCtrlblock,
|
|
|
+ gooseSendctrlblock,
|
|
|
} from "@/api/iedNetwork";
|
|
|
+import jsPlumb from "jsplumb";
|
|
|
const props = defineProps({
|
|
|
checkData: {
|
|
|
type: Object,
|
|
@@ -27,27 +114,359 @@ const props = defineProps({
|
|
|
type: String,
|
|
|
default: "",
|
|
|
},
|
|
|
+ checkData: {
|
|
|
+ type: Object,
|
|
|
+ default: () => {},
|
|
|
+ },
|
|
|
+ isOpen: {
|
|
|
+ type: Boolean,
|
|
|
+ default: false,
|
|
|
+ },
|
|
|
+ iedRelation: {
|
|
|
+ type: Object,
|
|
|
+ default: () => {},
|
|
|
+ },
|
|
|
});
|
|
|
const svInfo = ref(null);
|
|
|
const scdIdValue = inject("scdId");
|
|
|
+const iedChild = ref(null);
|
|
|
+//得到两边子版块的数据
|
|
|
+const getIedChild = async () => {
|
|
|
+ const childRes = await gooseSendctrlblock({
|
|
|
+ scd_id: scdIdValue,
|
|
|
+ ied_name: props.checkData.ied_name,
|
|
|
+ forcerefresh: 0,
|
|
|
+ });
|
|
|
+ //左右侧内部侧数据组装
|
|
|
+ listData.value.list.forEach((item, index) => {
|
|
|
+ item.titleItems = [];
|
|
|
+ childRes.data.forEach((key) => {
|
|
|
+ if (key.target_ied_name === item.ref_ied_name) {
|
|
|
+ if (
|
|
|
+ item.titleItems.length < 1 ||
|
|
|
+ !item.titleItems.some(
|
|
|
+ (titleKey) =>
|
|
|
+ titleKey.ld_inst === key.ld_inst &&
|
|
|
+ titleKey.ld_desc === key.ld_desc
|
|
|
+ )
|
|
|
+ ) {
|
|
|
+ key.childItem = [];
|
|
|
+ item.titleItems.push(key);
|
|
|
+ }
|
|
|
+ item.titleItems.forEach((itemKey) => {
|
|
|
+ if (
|
|
|
+ itemKey.ld_inst == key.ld_inst &&
|
|
|
+ itemKey.ld_desc == key.ld_desc
|
|
|
+ ) {
|
|
|
+ itemKey.childItem.push(key);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+ });
|
|
|
+ });
|
|
|
+ iedChild.value = childRes.data;
|
|
|
+};
|
|
|
+
|
|
|
+//得到中间的子版块数据
|
|
|
const getNetworkInfo = async () => {
|
|
|
const svResInfo = await iednetworkInfo({
|
|
|
scd_id: scdIdValue,
|
|
|
ied_name: props.checkData.ied_name,
|
|
|
});
|
|
|
- svInfo.value = svResInfo.data;
|
|
|
+ const svResInfoCtrl = await iedSVSendCtrlblock({
|
|
|
+ scd_id: scdIdValue,
|
|
|
+ ied_name: props.checkData.ied_name,
|
|
|
+ });
|
|
|
+ if (svResInfoCtrl.data.GSEControl) {
|
|
|
+ svResInfo.data.forEach((item) => {
|
|
|
+ svResInfoCtrl.data.GSEControl.forEach((key) => {
|
|
|
+ key.APPID =
|
|
|
+ item.cb_name == key.attr_name
|
|
|
+ ? JSON.parse(item.address_json).APPID
|
|
|
+ : "";
|
|
|
+ });
|
|
|
+ });
|
|
|
+ svInfo.value = svResInfoCtrl.data.GSEControl;
|
|
|
+ }
|
|
|
};
|
|
|
watch(
|
|
|
() => props.checkData,
|
|
|
(newValue) => {
|
|
|
- getNetworkInfo();
|
|
|
+ if (newValue != null) {
|
|
|
+ getIedChild();
|
|
|
+ getNetworkInfo();
|
|
|
+ }
|
|
|
+ }
|
|
|
+);
|
|
|
+//线条
|
|
|
+const middleElement = ref(null);
|
|
|
+const myElement = ref(null);
|
|
|
+let leaderLines = ref([]); //控制线条显示
|
|
|
+const leftList = ref([]);
|
|
|
+const rightList = ref([]);
|
|
|
+const domList = ref(new Map()); //获取 所有的ref
|
|
|
+const domListRight = ref(new Map()); //获取 所有的ref
|
|
|
+const listData = ref(props.checkData); //线条左右两侧的数据
|
|
|
+const emit = defineEmits(["result"]); //如果不加这个再次点击左侧会没有反应
|
|
|
+const setdom = (el, item) => {
|
|
|
+ //左侧dom
|
|
|
+ if (el) {
|
|
|
+ domList.value.set(item, el);
|
|
|
+ }
|
|
|
+};
|
|
|
+const setdomRight = (el, item) => {
|
|
|
+ //右侧dom
|
|
|
+ if (el) {
|
|
|
+ domListRight.value.set(item, el);
|
|
|
+ }
|
|
|
+};
|
|
|
+const processArray = (arr) => {
|
|
|
+ // ref_ied_id作为键,obj作为值
|
|
|
+ const uniqueObjects = new Map();
|
|
|
+ // 遍历数组
|
|
|
+ for (const obj of arr) {
|
|
|
+ const { ref_ied_id } = obj;
|
|
|
+ // 如果当前对象的 ref_ied_id 属性已经存在于 uniqueObjects 中
|
|
|
+ if (uniqueObjects.has(ref_ied_id)) {
|
|
|
+ // 将对应对象的 ref_type 属性设为 2,箭头双向
|
|
|
+ uniqueObjects.get(ref_ied_id).ref_type = 2;
|
|
|
+ } else {
|
|
|
+ // 否则,将当前对象添加到 uniqueObjects 中
|
|
|
+ uniqueObjects.set(ref_ied_id, obj);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // 将 uniqueObjects 中的值转为数组并返回
|
|
|
+ return Array.from(uniqueObjects.values());
|
|
|
+};
|
|
|
+//点击图片的时候筛选出数据
|
|
|
+const clickImg = (dataItem) => {
|
|
|
+ Object.values(props.iedRelation).find((item) => {
|
|
|
+ if (item.ied_name == dataItem.ref_ied_name) {
|
|
|
+ listData.value = item;
|
|
|
+ }
|
|
|
+ });
|
|
|
+};
|
|
|
+watch(
|
|
|
+ () => props.checkData,
|
|
|
+ (newValue, oldV) => {
|
|
|
+ listData.value = [];
|
|
|
+ listData.value = newValue;
|
|
|
+ if (newValue && leaderLines.value.length > 0) {
|
|
|
+ // leaderLines.value.forEach((line) => line.remove()); //清除连线
|
|
|
+ leaderLines.value = [];
|
|
|
+ }
|
|
|
+ },
|
|
|
+ { deep: true }
|
|
|
+);
|
|
|
+watch(
|
|
|
+ () => listData.value,
|
|
|
+ (newValue) => {
|
|
|
+ emit("result", newValue);
|
|
|
+ clickResetLine();
|
|
|
}
|
|
|
);
|
|
|
+watch(
|
|
|
+ () => props.isOpen,
|
|
|
+ (newValue) => {
|
|
|
+ if (newValue) {
|
|
|
+ domList.value.clear();
|
|
|
+ domListRight.value.clear();
|
|
|
+ leaderLines.value = [];
|
|
|
+ }
|
|
|
+ nextTick(() => {
|
|
|
+ middleLinePosition();
|
|
|
+ removeLine();
|
|
|
+ });
|
|
|
+ }
|
|
|
+);
|
|
|
+//点击后重置数据和线条
|
|
|
+const clickResetLine = () => {
|
|
|
+ domList.value.clear();
|
|
|
+ domListRight.value.clear();
|
|
|
+ leaderLines.value = [];
|
|
|
+ middleLinePosition();
|
|
|
+ setLine();
|
|
|
+ removeLine();
|
|
|
+};
|
|
|
+// 将设备列表分成两份
|
|
|
+const bothSide = (data) => {
|
|
|
+ const formatArr = processArray(data);
|
|
|
+ const arrlenght = formatArr.length;
|
|
|
+ const long1 = Math.ceil(arrlenght / 2);
|
|
|
+ leftList.value = formatArr.splice(0, long1);
|
|
|
+ rightList.value = formatArr.splice(0);
|
|
|
+};
|
|
|
+
|
|
|
+const setLeaderline = () => {
|
|
|
+ // lineArr.value = [];
|
|
|
+ //线条样式
|
|
|
+ const lineStyle0 = {
|
|
|
+ color: "#51637F",
|
|
|
+ size: 2,
|
|
|
+ path: "straight",
|
|
|
+ startPlug: "arrow1",
|
|
|
+ endPlug: "behind",
|
|
|
+ startSocket: "right",
|
|
|
+ endSocket: "left",
|
|
|
+ };
|
|
|
+ const lineStyle1 = {
|
|
|
+ color: "#51637F",
|
|
|
+ size: 2,
|
|
|
+ path: "straight",
|
|
|
+ endPlug: "arrow1",
|
|
|
+ startSocket: "right",
|
|
|
+ endSocket: "left",
|
|
|
+ };
|
|
|
+ const lineStyle2 = {
|
|
|
+ color: "#134BEA",
|
|
|
+ size: 2,
|
|
|
+ path: "straight",
|
|
|
+ startPlug: "arrow1",
|
|
|
+ endPlug: "arrow1",
|
|
|
+ startSocket: "right",
|
|
|
+ endSocket: "left",
|
|
|
+ };
|
|
|
+ const lineStyleRight0 = {
|
|
|
+ color: "#51637F",
|
|
|
+ size: 2,
|
|
|
+ path: "straight",
|
|
|
+ startPlug: "arrow1",
|
|
|
+ endPlug: "behind",
|
|
|
+ startSocket: "left",
|
|
|
+ endSocket: "right",
|
|
|
+ };
|
|
|
+ const lineStyleRight1 = {
|
|
|
+ color: "#51637F",
|
|
|
+ size: 2,
|
|
|
+ path: "straight",
|
|
|
+ endPlug: "arrow1",
|
|
|
+ startSocket: "left",
|
|
|
+ endSocket: "right",
|
|
|
+ };
|
|
|
+ const lineStyleRight2 = {
|
|
|
+ color: "#134BEA",
|
|
|
+ size: 2,
|
|
|
+ path: "straight",
|
|
|
+ startPlug: "arrow1",
|
|
|
+ endPlug: "arrow1",
|
|
|
+ startSocket: "left",
|
|
|
+ endSocket: "right",
|
|
|
+ };
|
|
|
+ const startDom = document.getElementById("end");
|
|
|
+ //循环画线
|
|
|
+ for (const [key, value] of domList.value) {
|
|
|
+ const endDom = value;
|
|
|
+ let line;
|
|
|
+ if (key.ref_type == 0) {
|
|
|
+ // line = new LeaderLine(endDom, startDom, lineStyle0);
|
|
|
+ } else if (key.ref_type == 2) {
|
|
|
+ // line = new LeaderLine(endDom, startDom, lineStyle0);
|
|
|
+ }
|
|
|
+ // 保存进数组,方便进行遍历删除
|
|
|
+ leaderLines.value.push(line);
|
|
|
+ }
|
|
|
+ //循环画线右侧
|
|
|
+ for (const [key, value] of domListRight.value) {
|
|
|
+ const endDom = value;
|
|
|
+ let line2;
|
|
|
+ if (key.ref_type == 0) {
|
|
|
+ // line2 = new LeaderLine(endDom, startDom, lineStyleRight0);
|
|
|
+ } else if (key.ref_type == 2) {
|
|
|
+ // line2 = new LeaderLine(endDom, startDom, lineStyleRight0);
|
|
|
+ }
|
|
|
+ // 保存进数组,方便进行遍历删除
|
|
|
+ leaderLines.value.push(line2);
|
|
|
+ }
|
|
|
+ hiddenLine();
|
|
|
+};
|
|
|
+const middleHeight = ref(null);
|
|
|
+//设置中间盒子的所在位置
|
|
|
+const middleLinePosition = () => {
|
|
|
+ setTimeout(() => {
|
|
|
+ const heights = myElement.value.scrollHeight;
|
|
|
+ if (leftList.value.length > 2 || rightList.value.length > 2) {
|
|
|
+ middleElement.value.style.marginTop = `${(heights - 60) / 2}px`; // 设置元素的垂直位置
|
|
|
+ middleHeight.value.style.height = heights
|
|
|
+ } else {
|
|
|
+ middleElement.value.style.marginTop = "150px"; // 设置元素的垂直位置
|
|
|
+ }
|
|
|
+ }, 0);
|
|
|
+};
|
|
|
+//滚动时重定位线条
|
|
|
+const newPositionLine = () => {
|
|
|
+ document.getElementById("treedom").addEventListener(
|
|
|
+ "scroll",
|
|
|
+ AnimEvent.add(() => {
|
|
|
+ leaderLines.value.forEach((line) => {
|
|
|
+ hiddenLine();
|
|
|
+ // line.position();
|
|
|
+ // line.positionByWindowResize = false;
|
|
|
+ });
|
|
|
+ //中间展示图片的
|
|
|
+ }),
|
|
|
+ false
|
|
|
+ );
|
|
|
+};
|
|
|
+//弹窗打开后使得线条在指定区域中
|
|
|
+const hiddenLine = () => {
|
|
|
+ const elmWrapper = document.getElementById("wrapper");
|
|
|
+ // 移动 line
|
|
|
+ document.body.querySelectorAll("body .leader-line").forEach((node) => {
|
|
|
+ elmWrapper.appendChild(node);
|
|
|
+ });
|
|
|
+ elmWrapper.style.transform = "none";
|
|
|
+ var rectWrapper = elmWrapper.getBoundingClientRect();
|
|
|
+ // Move to the origin of coordinates as the document
|
|
|
+ elmWrapper.style.transform = `translate(${
|
|
|
+ (rectWrapper.left + window.scrollY) * -1
|
|
|
+ }px, ${(rectWrapper.top + window.scrollX) * -1}px)`;
|
|
|
+};
|
|
|
+const setLine = () => {
|
|
|
+ if (listData.value) {
|
|
|
+ bothSide(listData.value.list);
|
|
|
+ }
|
|
|
+ setTimeout(() => {
|
|
|
+ setLeaderline();
|
|
|
+ }, 30);
|
|
|
+};
|
|
|
+const removeLine = () => {
|
|
|
+ const elmWrapper = document.getElementById("wrapper");
|
|
|
+ document.body.querySelectorAll("body .leader-line").forEach((node) => {
|
|
|
+ elmWrapper.removeChild(node);
|
|
|
+ });
|
|
|
+};
|
|
|
onMounted(() => {
|
|
|
- getNetworkInfo();
|
|
|
+ if (props.checkData != null) {
|
|
|
+ getIedChild();
|
|
|
+ getNetworkInfo();
|
|
|
+ }
|
|
|
+ //不加条件切换下方tab时会出现bug
|
|
|
+ if (props.isPhoto == "photo") {
|
|
|
+ nextTick(() => {
|
|
|
+ setLine();
|
|
|
+ middleLinePosition();
|
|
|
+ nextTick(() => {
|
|
|
+ newPositionLine();
|
|
|
+ });
|
|
|
+ });
|
|
|
+ }
|
|
|
});
|
|
|
+watch(
|
|
|
+ () => props.isPhoto,
|
|
|
+ (newValue) => {
|
|
|
+ if (props.isPhoto == "photo") {
|
|
|
+ nextTick(() => {
|
|
|
+ setLine();
|
|
|
+ middleLinePosition();
|
|
|
+ nextTick(() => {
|
|
|
+ newPositionLine();
|
|
|
+ });
|
|
|
+ });
|
|
|
+ }
|
|
|
+ }
|
|
|
+);
|
|
|
</script>
|
|
|
-<style lang="scss">
|
|
|
+<style lang="scss" scoped>
|
|
|
@mixin img-size {
|
|
|
width: 150px;
|
|
|
height: 90px;
|
|
@@ -57,4 +476,85 @@ onMounted(() => {
|
|
|
display: flex;
|
|
|
flex-direction: column;
|
|
|
}
|
|
|
+.main-cont {
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-evenly;
|
|
|
+ overflow-y: auto;
|
|
|
+ height: 65vh;
|
|
|
+}
|
|
|
+
|
|
|
+.leader-line {
|
|
|
+ z-index: 3000;
|
|
|
+}
|
|
|
+.main-left {
|
|
|
+ display: flex;
|
|
|
+ @include left-and-right;
|
|
|
+}
|
|
|
+.main-middle {
|
|
|
+ box-sizing: border-box;
|
|
|
+ border: 2px dashed #98a8ff;
|
|
|
+ img {
|
|
|
+ margin-bottom: 10px;
|
|
|
+ }
|
|
|
+ .middle-item {
|
|
|
+ @include left-and-right;
|
|
|
+ align-items: center;
|
|
|
+ cursor: pointer;
|
|
|
+ }
|
|
|
+ .middle-title {
|
|
|
+ margin: 12px;
|
|
|
+ text-align: center;
|
|
|
+ color: #ffcb11;
|
|
|
+ border-bottom: 1px solid #a3ade0;
|
|
|
+ }
|
|
|
+ .middel-child {
|
|
|
+ }
|
|
|
+}
|
|
|
+.main-right {
|
|
|
+ display: flex;
|
|
|
+ @include left-and-right;
|
|
|
+ .img-item {
|
|
|
+ @include img-size;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.conts {
|
|
|
+ @include left-and-right;
|
|
|
+ margin-bottom: 24px;
|
|
|
+ border: 2px dashed #98a8ff;
|
|
|
+ cursor: pointer;
|
|
|
+ .cont-item {
|
|
|
+ @include left-and-right;
|
|
|
+ color: #1a2447;
|
|
|
+ align-items: center;
|
|
|
+ margin: 12px 14px 5px 14px;
|
|
|
+ border-bottom: 1px solid #a3ade0;
|
|
|
+ }
|
|
|
+ .ied-desc {
|
|
|
+ color: #255ce7;
|
|
|
+ }
|
|
|
+ .ied-desc-title {
|
|
|
+ color: #134bea;
|
|
|
+ }
|
|
|
+
|
|
|
+ .ied-desc-child-title {
|
|
|
+ color: #5182ff;
|
|
|
+ margin-left: 14px;
|
|
|
+ display: block;
|
|
|
+ }
|
|
|
+}
|
|
|
+.ied-desc-child {
|
|
|
+ @include left-and-right;
|
|
|
+ align-items: center;
|
|
|
+ border: 1px solid #7484ab;
|
|
|
+ border-radius: 2px;
|
|
|
+ margin: 12px 14px;
|
|
|
+ padding: 5px;
|
|
|
+ color: #1a2447;
|
|
|
+}
|
|
|
+#wrapper {
|
|
|
+ width: 0;
|
|
|
+ height: 0;
|
|
|
+ position: relative; /* Origin of coordinates for lines, and scrolled content (i.e. not `absolute`) */
|
|
|
+}
|
|
|
</style>
|