Browse Source

scd网络结构图弹窗,装置关联关系,输入输出控制块--除开关联图

“yueshang” 1 year ago
parent
commit
2910015223

+ 3 - 0
public/anim-event.min.js

@@ -0,0 +1,3 @@
+/*! AnimEvent v1.0.17 (c) anseki https://github.com/anseki/anim-event */
+var AnimEvent=function(n){var e={};function t(r){if(e[r])return e[r].exports;var o=e[r]={i:r,l:!1,exports:{}};return n[r].call(o.exports,o,o.exports,t),o.l=!0,o.exports}return t.m=n,t.c=e,t.d=function(n,e,r){t.o(n,e)||Object.defineProperty(n,e,{enumerable:!0,get:r})},t.r=function(n){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(n,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(n,"__esModule",{value:!0})},t.t=function(n,e){if(1&e&&(n=t(n)),8&e)return n;if(4&e&&"object"==typeof n&&n&&n.__esModule)return n;var r=Object.create(null);if(t.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:n}),2&e&&"string"!=typeof n)for(var o in n)t.d(r,o,function(e){return n[e]}.bind(null,o));return r},t.n=function(n){var e=n&&n.__esModule?function(){return n.default}:function(){return n};return t.d(e,"a",e),e},t.o=function(n,e){return Object.prototype.hasOwnProperty.call(n,e)},t.p="",t(t.s=0)}([function(n,e,t){"use strict";t.r(e);var r,o=[],i=window.requestAnimationFrame||window.mozRequestAnimationFrame||window.webkitRequestAnimationFrame||window.msRequestAnimationFrame||function(n){return setTimeout(n,1e3/60)},u=window.cancelAnimationFrame||window.mozCancelAnimationFrame||window.webkitCancelAnimationFrame||window.msCancelAnimationFrame||function(n){return clearTimeout(n)},a=Date.now();function l(){var n,e;r&&(u.call(window,r),r=null),o.forEach((function(e){var t;(t=e.event)&&(e.event=null,e.listener(t),n=!0)})),n?(a=Date.now(),e=!0):Date.now()-a<500&&(e=!0),e&&(r=i.call(window,l))}function c(n){var e=-1;return o.some((function(t,r){return t.listener===n&&(e=r,!0)})),e}var f={add:function(n){var e;return-1===c(n)?(o.push(e={listener:n}),function(n){e.event=n,r||l()}):null},remove:function(n){var e;(e=c(n))>-1&&(o.splice(e,1),!o.length&&r&&(u.call(window,r),r=null))}};e.default=f}]).default;
+export default AnimEvent;

+ 2 - 1
public/index.html

@@ -22,7 +22,8 @@
   <div id="app"></div>
   <!-- built files will be auto injected -->
   <script src="https://unpkg.com/bpmn-js@6.0.2/dist/bpmn-viewer.development.js"></script>
-  <script src="./leader-line.min.js"></script>
+  <!-- <script src="./leader-line.min.js" type="module"></script> -->
+  <!-- <script src="./anim-event.min.js" type="module"></script> -->
 </body>
 
 </html>

+ 55 - 1
src/api/iedNetwork/index.js

@@ -54,4 +54,58 @@ export function areaIedList(query) {
     method: 'get',
     params: query
   })
-}
+}
+//输入输出控制块send发送
+export function iedSVSendCtrlblock(query) {
+  return request({
+    url: '/screen/scd/ied/ctrlblock',
+    method: 'get',
+    params: query
+  })
+}
+//输入输出控制块send发送
+export function iedSVSendfcdalist(query) {
+  return request({
+    url: 'screen/scd/ied/smv_ctrlblock/sendfcdalist',
+    method: 'get',
+    params: query
+  })
+}
+export function iednetworkInfo(query) {
+  return request({
+    url: '/screen/scd/ied/network/info',
+    method: 'get',
+    params: query
+  })
+}
+export function smvCtrlblockReceive(query) {
+  return request({
+    url: '/screen/scd/ied/smv_ctrlblock/receive',
+    method: 'get',
+    params: query
+  })
+}
+export function gooseSendctrlblock(query) {
+  return request({
+    url: '/screen/scd/ied/ref/ctrlblock',
+    method: 'get',
+    params: query
+  })
+}
+export function gooseSendfcdalist(query) {
+  return request({
+    url: '/screen/scd/ied/goose_ctrlblock/sendfcdalist',
+    method: 'get',
+    params: query
+  })
+}
+export function gooseReceivefcdalist(query) {
+  return request({
+    url: 'screen/scd/ied/goose_ctrlblock/receive',
+    method: 'get',
+    params: query
+  })
+}
+
+
+

+ 124 - 14
src/pages/netStructPicture/components/dialogIndex.vue

@@ -11,27 +11,28 @@
   >
     <template #header>
       <div class="my-header">
-        <div class="title" v-if="checkDialogData">
-          {{ checkDialogData.desc }}
+        <div class="title" v-if="checkData">
+          {{ checkData.desc }}
         </div>
       </div>
     </template>
     <div class="main">
       <div class="main-left">
         <el-input
-          v-model="input1"
+          v-model="searchInput"
           class="w-50 m-2"
           size="large"
           placeholder="快捷搜索"
           :suffix-icon="Search"
+          @change="searchClick"
           style="width: 90%; margin-left: 5%; margin-top: 10px"
         />
         <el-scrollbar height="72vh">
           <p
-            v-for="(item, index) in iedRelationData"
+            v-for="(item, index) in iedRelation"
             :key="index"
             class="scrollbar-demo-item"
-            @click="clickLeft(index)"
+            @click="clickLeft(item, index)"
             :class="{ 'left-item-active': activeLeft == index }"
           >
             <span class="ied-name">{{ item.ied_name }}</span
@@ -52,10 +53,29 @@
             {{ item.name }}
           </div>
         </div>
-        <div class="main-right-item">
+        <div v-if="inoutName.__name == 'inoutControl'" class="nav">
+          <div
+            v-for="(item, index) in inoutNav"
+            :key="index"
+            @click="clickInoutNav(item, index)"
+            class="inout"
+          >
+            <span
+              class="inout-item"
+              :class="{ 'inout-item-active': inoutItemNavIndex == index }"
+              >{{ item.name }}</span
+            >
+          </div>
+        </div>
+        <div class="main-right-item" :class="{ 'inout-right-item': inoutName.__name == 'inoutControl' }">
+          <!-- @result前的参数是装置关联关系的, -->
           <component
             :is="activeNavName"
-            :checkDialogData="checkDialogData"
+            :checkData="checkData"
+            :isOpen="isOpen"
+            :iedRelation="iedRelation"
+            @result="result"
+            :isPhoto="isPhoto"
           ></component>
         </div>
       </div>
@@ -63,8 +83,15 @@
   </el-dialog>
 </template>
   <script setup>
-import { onMounted, watch, ref, computed,nextTick } from "vue";
-import { defineEmits } from "vue";
+import {
+  onMounted,
+  watch,
+  ref,
+  computed,
+  nextTick,
+  shallowRef,
+  defineEmits,
+} from "vue";
 import devicePng from "@/assets/image/instruct/device.png";
 import { Search } from "@element-plus/icons-vue";
 import relationShip from "./relationShip.vue";
@@ -88,16 +115,58 @@ const props = defineProps({
     default: () => {},
   },
 });
-
 const isOpen = ref(props.openBig);
+const inoutNav = ref([
+  { name: "关联图", code: "photo" },
+  { name: "SV发送", code: "SVsend" },
+  { name: "SV接收", code: "SVreceive" },
+  { name: "GOOSE发送", code: "GOOSEsend" },
+  { name: "GOOSE接收", code: "GOOSEreceive" },
+]);
 watch(
   () => props.openBig,
   (newValue) => {
     isOpen.value = newValue;
   }
 );
+const iedRelation = ref(null);
+watch(
+  () => props.iedRelationData,
+  (newValue) => {
+    iedRelation.value = newValue;
+  }
+);
+const checkData = ref(null); //选中的数据
+watch(
+  () => props.checkDialogData,
+  (newValue) => {
+    checkData.value = newValue;
+  }
+);
+const result = (newData) => {
+  checkData.value = newData;
+};
+const searchInput = ref("");
+//搜索ied编码或名称
+const searchIedList = ref([]);
+const searchClick = (value) => {
+  if (value) {
+    iedRelation.value = Object.values(iedRelation.value).filter((item) => {
+      const lowercaseValue = value.toLowerCase(); //不区分大小写
+      const iedNameLower = item.ied_name ? item.ied_name.toLowerCase() : null;
+      const descLower = item.desc ? item.desc.toLowerCase() : null;
+      return (
+        (iedNameLower !== null && iedNameLower.includes(value)) ||
+        (descLower !== null && descLower.includes(value))
+      );
+    });
+  } else {
+    iedRelation.value = props.iedRelationData;
+  }
+};
+
 //头部
-const navtopData = ref([
+const navtopData = shallowRef([
   { name: "装置关联关系", code: relationShip },
   { name: "输入输出控制块", code: inoutControl },
   { name: "虚端子关系", code: virtualRelation },
@@ -110,24 +179,36 @@ const emit = defineEmits(["done"]);
 const cancelClick = () => {
   isOpen.value = false;
   activeLeft.value = null;
+  checkData.value = null;
   emit("done", false);
 };
 const activeNav = ref(0);
-const activeNavName = ref(relationShip);
+const activeNavName = shallowRef(relationShip);
+const inoutName = ref("");
 const clickNav = (navIndex, name) => {
   //点击导航栏事件
+  inoutName.value = name;
+  console.log("name", name);
   activeNavName.value = name;
   activeNav.value = navIndex;
 };
 const activeLeft = ref(null);
-const clickLeft = (navIndex) => {
-  //点击导航栏事件
+const clickLeft = (item, navIndex) => {
+  //点击侧边栏事件
   activeLeft.value = navIndex;
+  checkData.value = item;
 };
 onMounted(() => {
   // const height = ref(0);
   // height.value = document.documentElement.clientHeight - 400 + "px;";
 });
+//点击头部
+const inoutItemNavIndex = ref("");
+const isPhoto = ref("photo");
+const clickInoutNav = (item, index) => {
+  isPhoto.value = item.code;
+  inoutItemNavIndex.value = index;
+};
 </script>
   <style scoped lang="scss">
 @mixin mid-center {
@@ -161,6 +242,7 @@ $height: 40px;
     width: 90%;
     margin-left: 5%;
     display: flex;
+    padding: 4px 0;
     .ied-name {
       width: 25%;
     }
@@ -199,4 +281,32 @@ $height: 40px;
 .left-item-active {
   color: #255ce7;
 }
+.inout {
+  padding: 0 16px;
+  border-right: 1px solid #7484ab;
+  margin-top: 24px;
+}
+.inout-item {
+  display: inline-block;
+  width: 92px;
+  height: 26px;
+  cursor: pointer;
+  text-align: center;
+  color: #000;
+  line-height: 26px;
+}
+.inout-item-active {
+  background: #f6f9ff;
+  border-radius: 2px 2px 2px 2px;
+  opacity: 1;
+  text-align: center;
+  line-height: 26px;
+  color: #255ce7;
+  border: 1px solid #255ce7;
+}
+.inout-right-item{
+  margin-top: 27px;
+  margin-left: 16px;
+  overflow-y: hidden !important;
+}
 </style>

+ 49 - 4
src/pages/netStructPicture/components/inoutControl.vue

@@ -1,15 +1,60 @@
 <!-- 输入输出控制块 -->
 <template>
-  <div>2</div>
+  <div>
+    <div v-if="isPhoto == 'photo'">关联图</div>
+    <div v-if="isPhoto != 'photo'">
+      <inoutSendOrRv
+        :isPhoto="isPhoto"
+        :checkData="checkData"
+        :svInfo="svInfo"
+      ></inoutSendOrRv>
+    </div>
+  </div>
 </template>
 <script setup>
-import { onMounted, watch, ref,nextTick,defineEmits } from "vue";
+import { onMounted, watch, ref, nextTick, defineEmits, inject } from "vue";
+import inoutSendOrRv from "./inoutSendOrRv";
+import {
+  //send发送
+  iednetworkInfo,
+} from "@/api/iedNetwork";
 const props = defineProps({
-  checkDialogData: {
+  checkData: {
     type: Object,
     default: () => {},
   },
+  isPhoto: {
+    type: String,
+    default: "",
+  },
+});
+const svInfo = ref(null);
+const scdIdValue = inject("scdId");
+const getNetworkInfo = async () => {
+  const svResInfo = await iednetworkInfo({
+    scd_id: scdIdValue,
+    ied_name: props.checkData.ied_name,
+  });
+  svInfo.value = svResInfo.data;
+};
+watch(
+  () => props.checkData,
+  (newValue) => {
+    getNetworkInfo();
+  }
+);
+onMounted(() => {
+  getNetworkInfo();
 });
 </script>
-<style scoped lang="scss">
+<style lang="scss">
+@mixin img-size {
+  width: 150px;
+  height: 90px;
+  margin-bottom: 10px;
+}
+@mixin left-and-right {
+  display: flex;
+  flex-direction: column;
+}
 </style>

+ 400 - 0
src/pages/netStructPicture/components/inoutSendOrRv.vue

@@ -0,0 +1,400 @@
+<!-- 输入输出控制块的发送和接收 -->
+<template>
+  <div>
+    <div v-if="isPhoto == 'SVsend'" class="cont-table">
+      <el-table
+        :data="svTableCtrl"
+        stripe
+        style="width: 100%"
+        :cell-style="{ color: '#000' }"
+        @row-click="svSendRowClick"
+        :highlight-current-row="true"
+        ref="myTable"
+      >
+        <el-table-column type="index" label="序号" width="80" />
+        <el-table-column prop="attr_name" label="控制块名称" width="180" />
+        <el-table-column prop="attr_dat_set" label="数据集">
+          <template #default="scope">
+            {{ `[${scope.row.datset_desc}]${scope.row.attr_dat_set}` }}
+          </template>
+        </el-table-column>
+        <el-table-column prop="address.APPID" label="APPID" />
+        <el-table-column prop="address.MAC_Address" label="MAC地址" />
+        <el-table-column prop="address.VLAN_ID" label="VLAN-ID" />
+      </el-table>
+      <div class="title">
+        SMV端子列表详情(共<span v-if="svTableFcd">{{ svTableFcd.length }}</span
+        >条)
+      </div>
+      <el-table
+        :data="svTableFcd"
+        style="width: 100%"
+        stripe
+        :cell-style="{ color: '#000' }"
+      >
+        <el-table-column type="index" label="序号" width="80" />
+        <el-table-column prop="do_desc" label="内部端子描述" width="180" />
+        <el-table-column label="内部端子地址">
+          <template #default="scope">
+            {{
+              `${scope.row.ld_inst}/${scope.row.prefix}${scope.row.ld_inst}${scope.row.ln_class}${scope.row.ln_inst}.${scope.row.do_name}`
+            }}
+          </template>
+        </el-table-column>
+        <el-table-column label="外部IED名称">
+          <template #default="scope">
+            {{ `${scope.row.out_ied_name}.${scope.row.out_ied_desc}` }}
+          </template>
+        </el-table-column>
+        <el-table-column prop="out_do_desc" label="外部端子描述" />
+      </el-table>
+    </div>
+    <div v-else-if="isPhoto == 'SVreceive'" class="cont-table">
+      <el-table
+        :data="svReceive"
+        stripe
+        style="width: 100%"
+        :cell-style="{ color: '#000' }"
+        @row-click="svSendRowClick2"
+        :highlight-current-row="true"
+        ref="myTable2"
+      >
+        <el-table-column type="index" label="序号" width="80" />
+        <el-table-column prop="address.APPID" label="输入IED">
+          <template #default="scope">
+            {{ `[${scope.row.out_ied_name}]${scope.row.out_ied_desc}` }}
+          </template>
+        </el-table-column>
+        <el-table-column
+          prop="out_smv_ctrl_name"
+          label="控制块名称"
+          width="180"
+        />
+        <el-table-column prop="out_dataset_name" label="数据集" />
+        <el-table-column prop="out_smv_ctrl_address.APPID" label="APPID" />
+      </el-table>
+      <div class="title">
+        SMV端子列表详情(共<span v-if="svReceiveTableCtrl">{{
+          svReceiveTableCtrl.length
+        }}</span
+        >条)
+      </div>
+      <el-table
+        :data="svReceiveTableCtrl"
+        style="width: 100%"
+        stripe
+        :cell-style="{ color: '#000' }"
+      >
+        <el-table-column type="index" label="序号" width="80" />
+        <el-table-column prop="do_desc" label="内部端子描述" width="180" />
+        <el-table-column label="内部端子地址">
+          <template #default="scope">
+            {{ scope.row.int_addr }}
+          </template>
+        </el-table-column>
+        <el-table-column label="外部IED名称">
+          <template #default="scope">
+            {{ `${scope.row.out_ied_name}.${scope.row.out_ied_desc}` }}
+          </template>
+        </el-table-column>
+        <el-table-column prop="out_do_desc" label="外部端子描述" />
+      </el-table>
+    </div>
+    <div v-else-if="isPhoto == 'GOOSEsend'" class="cont-table">
+      <el-table
+        :data="gooseCtrl"
+        stripe
+        style="width: 100%"
+        :cell-style="{ color: '#000' }"
+        @row-click="svSendRowClick3"
+        :highlight-current-row="true"
+        ref="myTable3"
+      >
+        <el-table-column type="index" label="序号" width="80" />
+        <el-table-column prop="attr_name" label="控制块名称" width="180" />
+        <el-table-column prop="address.APPID" label="数据集">
+          <template #default="scope">
+            {{ `[${scope.row.attr_dat_set}]${scope.row.datset_desc}` }}
+          </template>
+        </el-table-column>
+        <el-table-column prop="address.APPID" label="APPID" />
+        <el-table-column prop="address.MAC_Address" label="MAC地址" />
+        <el-table-column prop="address.VLAN_ID" label="VLAN-ID" />
+      </el-table>
+      <div class="title">
+        GOOSE端子列表详情(共<span v-if="gooseList">{{ gooseList.length }}</span
+        >条)
+      </div>
+      <el-table
+        :data="gooseList"
+        style="width: 100%"
+        stripe
+        :cell-style="{ color: '#000' }"
+      >
+        <el-table-column type="index" label="序号" width="80" />
+        <el-table-column prop="do_desc" label="内部端子描述" width="180" />
+        <el-table-column label="内部端子地址">
+          <template #default="scope">
+            {{
+              `${scope.row.ld_inst}/${scope.row.prefix}${scope.row.ld_inst}${scope.row.ln_class}${scope.row.ln_inst}.${scope.row.do_name}`
+            }}
+          </template>
+        </el-table-column>
+        <el-table-column label="外部IED名称">
+          <template #default="scope">
+            {{ `${scope.row.out_ied_name}.${scope.row.out_ied_desc}` }}
+          </template>
+        </el-table-column>
+        <el-table-column prop="out_do_desc" label="外部端子描述" />
+      </el-table>
+    </div>
+    <div v-else class="cont-table">
+      <el-table
+        :data="gooseReCtrl"
+        stripe
+        style="width: 100%"
+        :cell-style="{ color: '#000' }"
+        @row-click="svSendRowClick3"
+        :highlight-current-row="true"
+        ref="myTable4"
+      >
+        <el-table-column type="index" label="序号" width="80" />
+        <el-table-column prop="address.APPID" label="输入IED">
+          <template #default="scope">
+            {{ `[${scope.row.out_ied_name}]${scope.row.out_ied_desc}` }}
+          </template>
+        </el-table-column>
+        <el-table-column
+          prop="out_gse_ctrl_name"
+          label="控制块名称"
+          width="180"
+        />
+        <el-table-column prop="out_dataset_name" label="数据集" />
+        <el-table-column prop="out_gse_ctrl_address.APPID" label="APPID" />
+      </el-table>
+      <div class="title">
+        GOOSE端子列表详情(共<span v-if="svReceiveTableCtrl">{{
+          svReceiveTableCtrl.length
+        }}</span
+        >条)
+      </div>
+      <el-table
+        :data="gooseList"
+        style="width: 100%"
+        stripe
+        :cell-style="{ color: '#000' }"
+      >
+        <el-table-column type="index" label="序号" width="80" />
+        <el-table-column prop="do_desc" label="内部端子描述" width="180" />
+        <el-table-column label="内部端子地址">
+          <template #default="scope">
+            {{
+              `${scope.row.ld_inst}/${scope.row.prefix}${scope.row.ld_inst}${scope.row.ln_class}${scope.row.ln_inst}.${scope.row.do_name}`
+            }}
+          </template>
+        </el-table-column>
+        <el-table-column label="外部IED名称">
+          <template #default="scope">
+            {{ `${scope.row.out_ied_name}.${scope.row.out_ied_desc}` }}
+          </template>
+        </el-table-column>
+        <el-table-column prop="out_do_desc" label="外部端子描述" />
+      </el-table>
+    </div>
+  </div>
+</template>
+  <script setup>
+import { onMounted, watch, ref, nextTick, defineEmits, inject } from "vue";
+import {
+  //send发送
+  iedSVSendCtrlblock,
+  iedSVSendfcdalist,
+  smvCtrlblockReceive,
+  gooseSendctrlblock,
+  gooseSendfcdalist,
+  gooseReceivefcdalist,
+} from "@/api/iedNetwork";
+const props = defineProps({
+  checkData: {
+    type: Object,
+    default: () => {},
+  },
+  isPhoto: {
+    type: String,
+    default: "",
+  },
+  svInfo: {
+    type: Array,
+    default: [],
+  },
+});
+const scdIdValue = inject("scdId");
+const myTable = ref(null);
+const myTable2 = ref(null);
+const myTable3 = ref(null);
+const myTable4 = ref(null);
+//获取send发送数据
+const svTableCtrl = ref(null);
+const svTableFcd = ref(null);
+const ctrlId = ref(null);
+const goodCtrlId = ref(null);
+const gooseCtrl = ref(null);
+const getSvSendCtrl = async () => {
+  const svResCtrl = await iedSVSendCtrlblock({
+    scd_id: scdIdValue,
+    ied_name: props.checkData.ied_name,
+  });
+  svTableCtrl.value = svResCtrl.data ? svResCtrl.data.SampledValueControl : [];
+  ctrlId.value =
+    svTableCtrl.value.length > 0 ? svTableCtrl.value[0].node_id : null;
+  gooseCtrl.value = svResCtrl.data ? svResCtrl.data.GSEControl : [];
+  goodCtrlId.value =
+    gooseCtrl.value.length > 0 ? gooseCtrl.value[0].node_id : null;
+  if (ctrlId.value) {
+    getSvSendFcd();
+  }
+  if (goodCtrlId.value) {
+    getGooseList();
+  }
+};
+const getSvSendFcd = async () => {
+  svTableFcd.value = [];
+  const svResFcd = await iedSVSendfcdalist({
+    scd_id: scdIdValue,
+    ied_name: props.checkData.ied_name,
+    ctrlid: ctrlId.value,
+  });
+  svTableFcd.value = svResFcd.data;
+};
+const svSendRowClick = (row, column) => {
+  ctrlId.value = row.node_id;
+  getSvSendFcd();
+};
+//sv接收
+// const svSendCtrlId = ref(null);
+const svReceiveTableCtrl = ref([]);
+const svReceive = ref([]);
+const smvCtrlblockRe = async () => {
+  const svRecResCtrl = await smvCtrlblockReceive({
+    scd_id: scdIdValue,
+    ied_name: props.checkData.ied_name,
+  });
+  svReceiveTableCtrl.value = svRecResCtrl.data;
+  svReceive.value = [];
+  if (svRecResCtrl.data.length > 0) {
+    svReceive.value.push(svRecResCtrl.data[0]);
+  }
+};
+const svSendRowClick2 = (row, column) => {
+  // svSendCtrlId.value = row.node_id;
+  // smvCtrlblockRe();
+};
+//GOOSE发送
+const gooseList = ref(null);
+const getGooseList = async () => {
+  const gooseRes = await gooseSendfcdalist({
+    scd_id: scdIdValue,
+    ied_name: props.checkData.ied_name,
+    ctrlid: goodCtrlId.value,
+  });
+  gooseList.value = gooseRes.data;
+};
+const svSendRowClick3 = (row, column) => {
+  goodCtrlId.value = row.node_id;
+  getGooseList();
+};
+//GOOSE接收
+const gooseReList = ref(null);
+const gooseReCtrl = ref([]);
+const getGooseReList = async () => {
+  gooseReCtrl.value = [];
+  const gooseRes = await gooseReceivefcdalist({
+    scd_id: scdIdValue,
+    ied_name: props.checkData.ied_name,
+  });
+  gooseRes.data.forEach((item) => {
+    const alreadyExists = gooseReCtrl.value.some((arrItem) => {
+      return (
+        item.out_dataset_name == arrItem.out_dataset_name &&
+        item.out_gse_ctrl_name == arrItem.out_gse_ctrl_name &&
+        item.out_ied_desc == arrItem.out_ied_desc &&
+        item.out_ied_name == arrItem.out_ied_name
+      );
+    });
+    if (!alreadyExists) {
+      gooseReCtrl.value.push(item);
+    }
+  });
+  gooseReList.value = gooseRes.data.map((item) => {
+    if (gooseRes.data.length > 0) {
+      return (
+        item.out_dataset_name == gooseRes.data[0].out_dataset_name &&
+        item.out_gse_ctrl_name == gooseRes.data[0].out_gse_ctrl_name &&
+        item.out_ied_desc == gooseRes.data[0].out_ied_desc &&
+        item.out_ied_name == gooseRes.data[0].out_ied_name
+      );
+    }
+  });
+  console.log("gooseReList.value", gooseReList.value);
+};
+watch(
+  () => props.svInfo,
+  (newValue) => {}
+);
+watch(
+  () => props.checkData,
+  (newValue) => {
+    svTableCtrl.value = [];
+    svReceiveTableCtrl.value = [];
+    svTableFcd.value = [];
+    gooseList.value = [];
+    gooseReList.value = [];
+    getSvSendCtrl();
+    smvCtrlblockRe();
+    getGooseReList();
+  }
+);
+watch(
+  () => props.isPhoto,
+  (newValue) => {
+    nextTick(() => {
+      switch (newValue) {
+        case "SVsend":
+          myTable.value.setCurrentRow(svTableCtrl.value[0]);
+          break;
+        case "SVreceive":
+          myTable2.value.setCurrentRow(svReceive.value[0]);
+          break;
+        case "GOOSEsend":
+          myTable3.value.setCurrentRow(gooseCtrl.value[0]);
+          break;
+        case "GOOSEreceive":
+          myTable4.value.setCurrentRow(gooseReCtrl.value[0]);
+          break;
+        default:
+          break;
+      }
+    });
+  }
+);
+onMounted(() => {
+  getSvSendCtrl();
+  smvCtrlblockRe();
+  getGooseReList();
+});
+</script>
+<style scoped lang="scss">
+.cont-table {
+  height: 65vh;
+  display: flex;
+  flex-direction: column;
+  justify-content: space-between;
+}
+:deep(.el-table) {
+  flex-basis: 45%;
+}
+.title {
+  margin: 16px;
+  color: #51637f;
+}
+</style>

+ 7 - 6
src/pages/netStructPicture/components/netWork.vue

@@ -96,7 +96,7 @@
     
   
 <script setup>
-import { computed, nextTick, onMounted, reactive, ref} from "vue";
+import { computed, nextTick, onMounted, provide, reactive, ref} from "vue";
 import { nodeList, iedNetaddr, scdIedRelation } from "@/api/iedNetwork";
 import { useRoute } from "vue-router";
 import dialogIndex from "./dialogIndex.vue";
@@ -151,7 +151,7 @@ const loading = ref(true);
 //获取网络图的顶部列表
 const getNetWork = async () => {
   const infoRes = await nodeList({
-    scd_id: 356000096,
+    scd_id: 272000075,
     pagesize: 10000,
     name: "SubNetwork",
   });
@@ -170,7 +170,7 @@ const allApData = ref({});
 const initLoad = async () => {
   allApData.value = {};
   const allAP = await nodeList({
-    scd_id: 356000096,
+    scd_id: 272000075,
     pagesize: 10000,
     name: "ConnectedAP",
   });
@@ -198,7 +198,7 @@ const initLoad = async () => {
 };
 //处理重复的ip
 const ipNetaddr = async () => {
-  const ipRes = await iedNetaddr({ scd_id: 356000096 });
+  const ipRes = await iedNetaddr({ scd_id: 272000075 });
   if (ipRes.code == 1) {
     return;
   }
@@ -487,7 +487,7 @@ const onChangeline = (val) => {
 const checkDialogData = ref(null);
 const done = (emits) => {
   openBig.value = emits;
-  checkDialogData.value = []
+  checkDialogData.value = null
 };
 const clickNetworkInfo = (value) => {
   openBig.value = true;
@@ -500,10 +500,11 @@ const clickNetworkInfo = (value) => {
 
 const iedRelationData = ref([]);
 const iedRelation = async () => {
-  const iedRes = await scdIedRelation({ scd_id: 356000096 });
+  const iedRes = await scdIedRelation({ scd_id: 272000075 });
   iedRelationData.value = iedRes.data;
 };
 //弹窗=============
+provide('scdId',272000075)
 </script>
   
 <style scoped lang="scss">

+ 323 - 38
src/pages/netStructPicture/components/relationShip.vue

@@ -1,65 +1,323 @@
+<!-- 装置关联关系 -->
 <template>
-  <!-- <div class="main-cont" ref="myElement">
+<div>
+  <div class="main-cont" ref="myElement" id="treedom">
     <div class="main-left">
-      <img :src="devicePng" alt="" id="start" />
-      <img :src="devicePng" alt="" />
-      <img :src="devicePng" alt="" />
+      <div
+        v-for="(item, index) in leftList"
+        :key="index"
+        class="cont"
+        @click="clickImg(item)"
+      >
+        <img
+          :src="devicePng"
+          alt=""
+          class="img-item"
+          :ref="(el) => setdom(el, item)"
+        />
+        <div>{{ item.ref_ied_name }}</div>
+        <div class="ied-desc">{{ item.ref_ied_desc }}</div>
+      </div>
     </div>
-    <div class="main-middle" ref="middleElement">
-      <img :src="devicePng" alt="" id="end" />
+    <div class="main-middle">
+      <div class="middle-item" ref="middleElement">
+        <img :src="devicePng" alt="" id="end" />
+        <div v-if="listData">{{ listData.ied_name }}</div>
+        <div v-if="listData">{{ listData.desc }}</div>
+      </div>
     </div>
     <div class="main-right">
-      <img :src="devicePng" alt="" /> <img :src="devicePng" alt="" /><img
-        :src="devicePng"
-        alt=""
-      />
+      <div
+        v-for="(item, index) in rightList"
+        :key="index"
+        class="cont"
+        @click="clickImg(item)"
+      >
+        <img
+          :src="devicePng"
+          alt=""
+          class="img-item"
+          :ref="(el) => setdomRight(el, item)"
+        />
+        <div>{{ item.ref_ied_name }}</div>
+        <div class="ied-desc">{{ item.ref_ied_desc }}</div>
+      </div>
     </div>
-  </div> -->
-<div>
-  <div id="start">start</div>
-<div id="end" style="margin-left:200px">end</div>
+  </div>
+  <div id="wrapper"></div>
 </div>
 </template>
 <script setup>
 import { onMounted, watch, ref, nextTick, defineEmits } from "vue";
 import devicePng from "@/assets/image/instruct/device.png";
-import LeaderLine from "../../../../public/leader-line.min.js"
+import LeaderLine from "../../../../public/leader-line.min.js";
+import AnimEvent from "../../../../public/anim-event.min.js";
 const props = defineProps({
-  checkDialogData: {
+  checkData: {
+    type: Object,
+    default: () => {},
+  },
+  isOpen: {
+    type: Boolean,
+    default: false,
+  },
+  iedRelation: {
     type: Object,
     default: () => {},
   },
 });
 const middleElement = ref(null);
 const myElement = ref(null);
-onMounted(() => {
-  const startElement = document.getElementById('start');  
-  const endElement = document.getElementById('end');  
-  // new LeaderLine(startElement, endElement);  
-  new LeaderLine(startElement, endElement, { color: 'gray', size: 2, path: "grid" })
+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 = 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 == 1) {
+      line = new LeaderLine(endDom, startDom, lineStyle1);
+    } else if (key.ref_type == 2) {
+      line = new LeaderLine(endDom, startDom, lineStyle2);
+    }
+    //  保存进数组,方便进行遍历删除
+    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 == 1) {
+      line2 = new LeaderLine(endDom, startDom, lineStyleRight1);
+    } else if (key.ref_type == 2) {
+      line2 = new LeaderLine(endDom, startDom, lineStyleRight2);
+    }
+    //  保存进数组,方便进行遍历删除
+    leaderLines.value.push(line2);
+  }
+  hiddenLine();
+};
+//设置中间盒子的所在位置
+const middleLinePosition = () => {
+  setTimeout(() => {
+    const heights = myElement.value.scrollHeight;
+    if (leftList.value.length > 3 || rightList.value.length > 3) {
+      middleElement.value.style.marginTop = `${(heights - 60) / 2}px`; // 设置元素的垂直位置
+    } else {
+      middleElement.value.style.marginTop = "150px"; // 设置元素的垂直位置
+    }
+  }, 0);
+};
+onMounted(() => {
   nextTick(() => {
-  
-
-
-    // setTimeout(() => {
-    //   const heights = myElement.value.scrollHeight;
-    //   const eysHeigth = myElement.value.clientHeight;
-    //   if (heights > 550) {
-    //     console.log("heights", heights);
-    //     middleElement.value.style.marginTop = `${(heights - 160) / 2}px`; // 设置元素的垂直位置
-    //   } else {
-    //     middleElement.value.style.marginTop = `${heights / 3}px`; // 设置元素的垂直位置
-    //   }
-    // }, 0);
+    setLine();
+    middleLinePosition();
+    nextTick(() => {
+      newPositionLine();
+    });
   });
 });
+//滚动时重定位线条
+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);
+  });
+};
 </script>
-<style scoped lang="scss">
+<style lang="scss">
 @mixin img-size {
   width: 150px;
   height: 90px;
-  margin-bottom: 40px;
+  margin-bottom: 10px;
 }
 @mixin left-and-right {
   display: flex;
@@ -69,21 +327,48 @@ onMounted(() => {
   display: flex;
   justify-content: space-evenly;
 }
+
+.leader-line {
+  z-index: 3000;
+}
 .main-left {
   display: flex;
   @include left-and-right;
-  & > img {
+  .img-item {
     @include img-size;
   }
 }
 .main-middle {
   box-sizing: border-box;
+  img {
+    margin-bottom: 10px;
+  }
+  .middle-item {
+    @include left-and-right;
+    align-items: center;
+    color: #ffcb11;
+  }
 }
 .main-right {
   display: flex;
   @include left-and-right;
-  & > img {
+  .img-item {
     @include img-size;
   }
 }
+
+.cont {
+  @include left-and-right;
+  align-items: center;
+  margin-bottom: 10px;
+  .ied-desc {
+    color: #255ce7;
+  }
+}
+
+#wrapper {
+  width: 0;
+  height: 0;
+  position: relative; /* Origin of coordinates for lines, and scrolled content (i.e. not `absolute`) */
+}
 </style>

+ 6 - 6
src/pages/netStructPicture/components/scdVisual.vue

@@ -161,7 +161,7 @@ import scdDialogIndex from "./scdDialogIndex";
 const userStoreCode = useDataStore();
 const data = reactive({
   queryParams: {
-    scd_id: 356000096,
+    scd_id: 272000075,
   },
 });
 const loading = ref(true);
@@ -171,7 +171,7 @@ const { queryParams } = toRefs(data);
 // 表单重置
 const reset = () => {
   queryParams.value = {
-    scd_id: 356000096,
+    scd_id: 272000075,
     voltage_level_id: null, //电压等级
     area_id: null, //间隔
     device_type_id: null, //装置类型
@@ -185,7 +185,7 @@ const allIedType = [{ name: "全部", code: "alls" }];
 const voltageLevel = ref([{ name: "全部", id: "alls" }]); //电压等级
 const areaType = ref([]);
 const getArea = async () => {
-  const areaRes = await areaList({ scd_id: 356000096 });
+  const areaRes = await areaList({ scd_id: 272000075 });
   if (!areaRes.data) {
     voltageLevel.value = [];
     loading.value = false;
@@ -223,7 +223,7 @@ const getArea = async () => {
 //设备类型
 const iedTypeData = ref([]);
 const getTypelist = async () => {
-  const typeRes = await iedTypelist({ scd_id: 356000096 });
+  const typeRes = await iedTypelist({ scd_id: 272000075 });
   iedTypeData.value = typeRes.data ? [...allIedType, ...typeRes.data] : [];
 };
 const iedName = ref([]);
@@ -254,7 +254,7 @@ const searchInput = (value) => {
 const count = ref(0);
 const iedNameData = async () => {
   //IED编码或名称
-  const iedRes = await scdIedRelation({ scd_id: 356000096 });
+  const iedRes = await scdIedRelation({ scd_id: 272000075 });
   iedName.value = iedRes.data;
   count.value = iedRes.count;
 };
@@ -282,7 +282,7 @@ const changeLevel = async (value, mainValue) => {
     open.value = true;
     dialogData.value = iedName.value;
   } else if (value) {
-    const mainClick = { scd_id: 356000096, area_id: value };
+    const mainClick = { scd_id: 272000075, area_id: value };
     const forms = mainValue ? mainClick : queryParams.value;
     open.value = true;
     dialogData.value = [];

+ 3 - 3
src/utils/request.js

@@ -6,7 +6,7 @@ import router from '@/router'
 // 请求url
 const service = axios.create({
     // baseURL: window.STATIC_CONFIG.proxyUrl, // url = base url + request url
-    baseURL: "http://127.0.0.1:9527/api",
+    baseURL: "http://8.142.173.95:9527/api",
     timeout: 15000, // request timeout
     headers:{
         "Content-Type":"application/x-www-form-urlencoded"
@@ -63,7 +63,7 @@ service.interceptors.request.use(
     },
     error => {
         // do something with request error
-        console.log('请求前拦截error=', error) // for debug
+        // console.log('请求前拦截error=', error) // for debug
         // return Promise.reject(error)
     }
 )
@@ -76,7 +76,7 @@ service.interceptors.response.use(
         return res
     },
     error => {
-        console.log(error,'相应拦截器error');
+        // console.log(error,'相应拦截器error');
         if (error.response.status === 401) {
             Message({
                 message: "登陆已过期,请重新登录",