LineDouble.vue 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670
  1. <template>
  2. <div>
  3. <el-dialog v-model="dialogVisible" title="装置端子关系配置" width="60%" @close="handleClose"
  4. :close-on-click-modal="false">
  5. <div class="masBox" v-loading="pastLoading">
  6. <div class="leftBox">
  7. <h1 style="
  8. border-bottom: 1px solid #7484AB;
  9. width: 73%;
  10. margin: 25px auto;
  11. padding-bottom: 10px;
  12. ">{{ startTxt }}</h1>
  13. <div style="height: auto;">
  14. <div style="margin-bottom: 10px;" v-for="(item, index) in curList"
  15. @click="setIndex(item, index)">
  16. <span :class="index == leftIndex ? 'result' : 'anther'">{{ index + 1 }}</span>
  17. <span :class="index == leftIndex ? 'results' : 'anthers'" :id="item.id">{{ item.fcda_name
  18. }}</span>
  19. </div>
  20. </div>
  21. </div>
  22. <div class="middleBox">
  23. <p class="background" :style="{ 'background-image': `url(${setBackground})`, 'color': setColor }">{{
  24. setGooseOrSv }}</p>
  25. </div>
  26. <div class="rightBox" v-loading="loading">
  27. <h1 style="
  28. border-bottom: 1px solid #7484AB;
  29. width: 73%;
  30. margin: 25px auto;
  31. padding-bottom: 10px;
  32. ">{{ endTxt }}</h1>
  33. <div style="height: auto;">
  34. <div v-for="(item, index) in outList">
  35. <el-checkbox-group v-model="checkList" @change="checkChange">
  36. <el-checkbox :id="item.id" :label="item.id" :key="item.id">
  37. {{ item.fcda_name }}
  38. </el-checkbox>
  39. </el-checkbox-group>
  40. </div>
  41. <!-- <div style="margin-bottom: 10px;" v-for="(item, index) in outList" :id="item.id" class="anthers">
  42. <el-radio-group v-if="item.from_ied_type==''" v-model="checkList" @change="checkChange">
  43. <el-radio :label="item.id" :key="item.id"> {{ item.fcda_name }}</el-radio>
  44. </el-radio-group>
  45. <span v-else >{{ item.fcda_name }}</span>
  46. </div> -->
  47. </div>
  48. </div>
  49. <div id="wrapper"></div>
  50. </div>
  51. <template #footer>
  52. <span class="dialog-footer">
  53. <el-button @click="cancels">取消</el-button>
  54. <el-button type="primary" @click="sureClick">确定</el-button>
  55. </span>
  56. </template>
  57. </el-dialog>
  58. </div>
  59. </template>
  60. <script>
  61. import { ref, onMounted, watch, toRefs, computed, onBeforeUnmount } from 'vue';
  62. import flow from '@/api/flow/flow';
  63. import { ElMessage } from 'element-plus';
  64. import LeaderLine from "../../../../public/leader-line.min.js";
  65. export default {
  66. props: {
  67. ldModal: {
  68. type: Boolean,
  69. required: true,
  70. },
  71. startTarget: {
  72. type: Object,
  73. required: true
  74. },//开始数据
  75. endTarget: {
  76. type: Object,
  77. required: true,
  78. },//结尾数据
  79. modelId: {
  80. type: String,
  81. required: true,
  82. },//模型id
  83. numCase: {
  84. type: Number,
  85. required: true
  86. },//用于判断goose还是sv,0为sv,1为goose
  87. startText: {
  88. type: String,
  89. required: true
  90. },//开始节点文本
  91. endText: {
  92. type: String,
  93. required: true
  94. },//结束文本
  95. lineType: {
  96. type: String,
  97. required: true
  98. },//用于判断goose还是sv
  99. },
  100. setup(props, { emit }) {
  101. let dialogVisible = ref(false)//模态框开关
  102. let starts = ref({})//开始装置数据
  103. let ends = ref({})//结束装置数据
  104. let modelIds = ref("")//本组件的模型id
  105. let startTxt = ref("")//本组件开始文本
  106. let endTxt = ref("")//本组件结束文本
  107. let curList = ref([])//箭头开始list
  108. let outList = ref([])//箭头结束list
  109. let numStatus = ref(0)//判断goose还是sv
  110. let visibleItems = ref([])//选择的数组
  111. let fcdaIds = ref("")//需要的fcda_ids
  112. let leftIndex = ref(-1)//左侧选择的输出下标
  113. let leftFcda = ref("")//左侧的fcdaid
  114. let together = ref("")//绑定的id
  115. let checkList = ref([])//select数据
  116. let loading = ref(false)
  117. let arrNew = ref([])//新的存储fdcdids
  118. let gv = ref('')
  119. let pastLoading = ref(false)
  120. let leaderLineList = ref({})//组件leader-line数据,以方便离开组件时清除画线,key:start和end对象的端子id组合,value为line对象
  121. watch(() => props.modelId, (newVal) => {
  122. modelIds.value = newVal
  123. })
  124. watch(() => props.startText, (newVal) => {
  125. startTxt.value = newVal
  126. })
  127. watch(() => props.endText, (newVal) => {
  128. endTxt.value = newVal
  129. })
  130. watch(() => props.numCase, (newVal) => {
  131. numStatus.value = newVal
  132. })
  133. watch(() => props.lineType, (newVal) => {
  134. gv.value = newVal
  135. })
  136. // 初始化函数
  137. async function reload() {
  138. pastLoading.value = true
  139. dialogVisible.value = props.ldModal
  140. modelIds.value = props.modelId
  141. starts.value = props.startTarget
  142. ends.value = props.endTarget
  143. if(props.startText==null || props.startText==''){
  144. startTxt.value = starts.value.ied_type
  145. }else{
  146. startTxt.value = props.startText
  147. }
  148. if(props.endText==null || props.endText==''){
  149. endTxt.value = ends.value.ied_type
  150. }else{
  151. endTxt.value = props.endText
  152. }
  153. numStatus.value = props.numCase
  154. gv.value = props.lineType
  155. await flow.getModelAndIed({
  156. model_id: modelIds.value - 0,
  157. ied_type: starts.value.ied_type,
  158. sv_or_goose: gv.value,
  159. in_or_out: "输出",
  160. ref_ied_type: ends.value.ied_type
  161. }).then(res => {
  162. //if ( res.data) {
  163. //liling:避免提示空错误提示,res.data为空不代表接口有错误,所以先判断接口code值
  164. pastLoading.value = false
  165. if (res.code == 0) {
  166. if (res.data == null || res.data=="") {
  167. curList.value = [];
  168. return;
  169. }
  170. let tmpList = [];
  171. //对结果进行重排排序:将已关联到输出装置的端子排在前面
  172. res.data.forEach((item, index) => {
  173. if (item.to_ied_type == ends.value.ied_type) {
  174. tmpList.unshift(item)
  175. } else {
  176. //未关联的端子按原结果顺序排列
  177. tmpList.push(item)
  178. }
  179. })
  180. curList.value = tmpList
  181. //pastLoading.value = false
  182. } else {
  183. ElMessage({
  184. message: res.msg,
  185. type: "error"
  186. })
  187. }
  188. })
  189. await flow.getModelAndIed({
  190. model_id: modelIds.value - 0,
  191. ied_type: ends.value.ied_type,
  192. sv_or_goose: gv.value,
  193. in_or_out: "接收",
  194. ref_ied_type: starts.value.ied_type
  195. }).then(res => {
  196. console.log(res.data, '!!!!!');
  197. //if ( res.data) {
  198. //liling:避免提示空错误提示,res.data为空不代表接口有错误,所以先判断接口code值
  199. if (res.code == 0) {
  200. if (res.data == null || res.data=="") {
  201. outList.value = [];
  202. return;
  203. }
  204. //对结果进行重排排序:将已关联到输出装置的端子排在前面
  205. let tmpList = [];
  206. res.data.forEach((item, index) => {
  207. if (item.from_ied_type == starts.value.ied_type) {
  208. tmpList.unshift(item)
  209. } else {
  210. //未关联的端子按原结果顺序排列
  211. tmpList.push(item)
  212. }
  213. })
  214. outList.value = tmpList;
  215. // 将 setTimeout 移动到 then 方法中
  216. setTimeout(() => {
  217. res.data.forEach((item, index) => {
  218. let line = {
  219. start: item.from_fcda_id,
  220. end: item.id
  221. };
  222. let starts = document.getElementById(line.start);
  223. let ends = document.getElementById(line.end).parentNode;
  224. // console.log(starts, ends, '??????');
  225. // 检查 starts 和 ends 是否存在
  226. if (starts && ends) {
  227. /*
  228. leaderLineList.value.push();
  229. */
  230. const key=line.start+','+line.end
  231. leaderLineList.value[key] = new LeaderLine(starts, ends, {
  232. color: "#ccc",
  233. size: 2,
  234. path: "straight",
  235. startSocket: "right",
  236. endSocket: "left",
  237. endPlug: 'arrow3',
  238. endPlugSize: 2
  239. })
  240. hiddenLine();
  241. pastLoading.value = false
  242. } else {
  243. console.error(`Element with ID ${line.start} or ${line.end} not found.`);
  244. }
  245. });
  246. }, 100);
  247. } else {
  248. ElMessage({
  249. message: res.msg,
  250. type: "error"
  251. });
  252. }
  253. });
  254. curList.value.sort((obj1, obj2) => {
  255. if (outList.value[0] == null) return 1;
  256. const nameB = outList.value[0].fcda_name;
  257. if (obj1.fcda_name === nameB) return -1; // 将 name 与数组 b 中第一个对象的 name 属性相等的对象移到数组的第一位
  258. if (obj2.fcda_name === nameB) return 1;
  259. return 0;
  260. });
  261. }
  262. // 关闭模态框
  263. function closeModal() {
  264. removeLine3()
  265. dialogVisible.value = false
  266. emit("lineBack", dialogVisible.value)
  267. }
  268. // 确认关闭模态框
  269. function sureClose() {
  270. // if (leftFcda.value == '') {
  271. // leftFcda.value = curList.value[0].id
  272. // }
  273. // flow.saveModelOn({
  274. // model_id: modelIds.value - 0,
  275. // from_ied_type: starts.value.ied_type,
  276. // to_ied_type: ends.value.ied_type,
  277. // from_fcda_id: leftFcda.value - 0,
  278. // to_fcda_ids: fcdaIds.value,
  279. // goosesv: setGooseOrSv.value,
  280. // }).then(res => {
  281. // if (res.code == 0) {
  282. // ElMessage({
  283. // type: "success",
  284. // message: "关联成功!"
  285. // })
  286. dialogVisible.value = false
  287. removeLine3()
  288. // emit("lineBack", dialogVisible.value)
  289. // } else {
  290. // ElMessage({
  291. // type: "error",
  292. // message: res.msg
  293. // })
  294. // }
  295. // })
  296. }
  297. // 返回goose还是sv
  298. const setGooseOrSv = computed(() => {
  299. if (gv.value == "SV") {
  300. return "SV"
  301. } else if (gv.value == "GOOSE") {
  302. return "GOOSE"
  303. }
  304. })
  305. // 返回图片
  306. const setBackground = computed(() => {
  307. if (gv.value == "SV") {
  308. return require("../../../assets/image/sv_orange.png")
  309. } else if (gv.value == "GOOSE") {
  310. return require("../../../assets/image/goose_blue.png")
  311. }
  312. })
  313. // 返回文字颜色
  314. const setColor = computed(() => {
  315. if (gv.value == "SV") {
  316. return 'orange'
  317. } else if (gv.value == "GOOSE") {
  318. return 'blue'
  319. }
  320. })
  321. // 下标选择
  322. function setIndex(row, num) {
  323. console.log(row, 'row');
  324. if (row && num) {
  325. leftFcda.value = row.id
  326. // checkList.value = [row.to_fcda_id]
  327. fcdaIds.value = row.to_fcda_id
  328. leftIndex.value = num
  329. // flow.getModelOn({
  330. // model_id: modelIds.value - 0,
  331. // from_fcda_id: row.id - 0
  332. // }).then(res => {
  333. // if (res.data != null) {
  334. // together.value = res.data
  335. // checkList.value = together.value.map(item => {
  336. // return item.to_fcda_id
  337. // })
  338. // fcdaIds.value = res.data.map(item => {
  339. // return item.to_fcda_id
  340. // }).join(",")
  341. // loading.value = false
  342. // } else {
  343. // together.value = []
  344. // checkList.value = []
  345. // loading.value = false
  346. // }
  347. // })
  348. } else {
  349. if (curList.value.length > 0) {
  350. console.log(curList.value, 'cur');
  351. curList.value.map(item => {
  352. if (item.to_fcda_id != '0' || item.to_fcda_id != '') {
  353. checkList.value.push(item.to_fcda_id)
  354. }
  355. })
  356. leftFcda.value = curList.value[0].id
  357. // checkList.value = [curList.value[0].to_fcda_id]
  358. fcdaIds.value = curList.value[0].to_fcda_id
  359. leftIndex.value = 0
  360. // flow.getModelOn({
  361. // model_id: modelIds.value - 0,
  362. // from_fcda_id: curList.value[0].id - 0
  363. // }).then(res => {
  364. // if (res.data != null) {
  365. // together.value = res.data
  366. // checkList.value = together.value.map(item => {
  367. // return item.to_fcda_id
  368. // })
  369. // fcdaIds.value = res.data.map(item => {
  370. // return item.to_fcda_id
  371. // }).join(",")
  372. // loading.value = false
  373. // } else {
  374. // together.value = []
  375. // checkList.value = []
  376. // loading.value = false
  377. // }
  378. // })
  379. } else {
  380. ElMessage({
  381. type: "info",
  382. message: "您还没有配置装置"
  383. })
  384. loading.value = false
  385. }
  386. }
  387. }
  388. function checkChange(e) {
  389. if (e.length > 1) {
  390. fcdaIds.value = e[0]
  391. ElMessage({
  392. message: "只能选择一个端子,已保存上一次选择的端子",
  393. type: "error"
  394. })
  395. } else {
  396. fcdaIds.value = e.join(",")
  397. if (leftFcda.value == '') {
  398. leftFcda.value = curList.value[0].id
  399. }
  400. flow.saveModelOn({
  401. model_id: modelIds.value - 0,
  402. from_ied_type: starts.value.ied_type,
  403. to_ied_type: ends.value.ied_type,
  404. from_fcda_id: leftFcda.value - 0,
  405. to_fcda_ids: fcdaIds.value,
  406. goosesv: setGooseOrSv.value,
  407. }).then(res => {
  408. if (res.code == 0) {
  409. ElMessage({
  410. type: "success",
  411. message: "关联成功!"
  412. })
  413. reload()
  414. setIndex()
  415. removeLine3()
  416. emit("lineBack", dialogVisible.value)
  417. } else {
  418. ElMessage({
  419. type: "error",
  420. message: res.msg
  421. })
  422. }
  423. })
  424. }
  425. }
  426. //弹窗打开后使得线条在指定区域中
  427. function hiddenLine() {
  428. const elmWrapper = document.getElementById("wrapper");
  429. // 移动 line
  430. document.body.querySelectorAll("body .leader-line").forEach((node) => {
  431. elmWrapper.appendChild(node);
  432. });
  433. elmWrapper.style.transform = "none";
  434. var rectWrapper = elmWrapper.getBoundingClientRect();
  435. // Move to the origin of coordinates as the document
  436. elmWrapper.style.transform = `translate(${(rectWrapper.left + window.scrollY) * -1
  437. }px, ${(rectWrapper.top + window.scrollX) * -1}px)`;
  438. };
  439. // 稳定leader-line函数
  440. function removeLine3() {
  441. leaderLineList.value = {};
  442. const elmWrapper = document.getElementById("wrapper");
  443. if (elmWrapper) {
  444. document.body.querySelectorAll("#wrapper .leader-line").forEach((node) => {
  445. elmWrapper.removeChild(node);
  446. });
  447. }
  448. };
  449. onMounted(async () => {
  450. await reload()
  451. setIndex()
  452. })
  453. onBeforeUnmount(() => {
  454. for(let key in leaderLineList.value){
  455. leaderLineList.value[key].remove();
  456. }
  457. //leaderLineList.value.forEach(line => line.remove());//离开当前组件时清除连线
  458. removeLine3()
  459. })
  460. return {
  461. dialogVisible,//模态框开关
  462. reload,//初始化函数
  463. starts,//开始节点数据
  464. ends,//结束节点数据
  465. handleClose: closeModal,//关闭模态框
  466. cancels: closeModal,//关闭模态框
  467. sureClick: sureClose,//确认关闭模态框
  468. modelIds,//本组件的模型id
  469. startTxt,//本组件开始文本
  470. endTxt,//本组件结束文本
  471. curList,//渲染list
  472. setGooseOrSv,//判断显示goose还是sv
  473. setBackground,//判断显示背景图
  474. setColor,//判断文字颜色
  475. visibleItems,//选择的数组
  476. // toggleVisibility,//模拟多选框方法
  477. fcdaIds,//需要的fcda_ids
  478. leftIndex,//左侧下标选择
  479. setIndex,//下标选择函数
  480. // getClass,//设置多选span的class函数
  481. leftFcda,//左侧单选的fcdaid
  482. outList,//箭头指向端list
  483. together,//绑定的id
  484. // isVisible,
  485. loading,
  486. checkList,
  487. checkChange,
  488. arrNew,//新的存储ids
  489. gv,
  490. pastLoading,//maxbox加载动画
  491. leaderLineList,//组件库leader-line数据,以方便离开组件时清除画线
  492. hiddenLine,//使leader-line显示在模态框内
  493. removeLine3,// 稳定leader-line函数
  494. }
  495. }
  496. }
  497. </script>
  498. <style scoped>
  499. .masBox {
  500. width: 100%;
  501. height: calc(100vh - 400px);
  502. /* border: 1px solid red; */
  503. display: flex;
  504. justify-content: space-around;
  505. align-items: center;
  506. overflow-y: auto;
  507. position: relative;
  508. }
  509. .leftBox {
  510. width: 33%;
  511. /* height: calc(100vh - 420px); */
  512. text-align: center;
  513. background-color: #F7F8FB;
  514. border: 2px dashed #A3ADE0;
  515. position: absolute;
  516. top: 2%;
  517. left: 5%;
  518. }
  519. .middleBox {
  520. width: 22%;
  521. height: calc(100vh - 420px);
  522. display: flex;
  523. justify-content: space-around;
  524. align-items: center;
  525. position: absolute;
  526. top: 5%;
  527. left: 39%;
  528. }
  529. .rightBox {
  530. width: 33%;
  531. /* height: calc(100vh - 420px); */
  532. text-align: center;
  533. background-color: #EDF3FF;
  534. border: 2px dashed #A3ADE0;
  535. position: absolute;
  536. top: 2%;
  537. right: 5%;
  538. }
  539. .result {
  540. display: inline-block;
  541. width: 11%;
  542. height: 40px;
  543. border: 1px solid blue;
  544. text-align: center;
  545. line-height: 40px;
  546. background-color: #D9E6FE;
  547. color: blue;
  548. margin-right: 10px;
  549. }
  550. .anther {
  551. display: inline-block;
  552. width: 11%;
  553. height: 40px;
  554. border: 1px solid black;
  555. text-align: center;
  556. line-height: 40px;
  557. background-color: #F5FAFE;
  558. color: black;
  559. margin-right: 10px;
  560. }
  561. .results {
  562. display: inline-block;
  563. width: 75%;
  564. height: 40px;
  565. border: 1px solid blue;
  566. text-align: left;
  567. line-height: 40px;
  568. background-color: #D9E6FE;
  569. color: blue;
  570. padding-left: 5px;
  571. }
  572. .anthers {
  573. display: inline-block;
  574. width: 75%;
  575. height: 40px;
  576. border: 1px solid black;
  577. text-align: left;
  578. line-height: 40px;
  579. background-color: #F5FAFE;
  580. color: black;
  581. padding-left: 5px;
  582. }
  583. .background {
  584. width: 90%;
  585. height: calc(100vh - 850px);
  586. /* background-position: 100%; */
  587. background-repeat: no-repeat;
  588. background-size: 100%;
  589. text-align: center;
  590. background-position: 100% 50%;
  591. line-height: calc(100vh - 850px);
  592. }
  593. :deep(.el-checkbox__inner) {
  594. width: 40px !important;
  595. height: 40px !important;
  596. background-color: white !important;
  597. }
  598. :deep(.el-checkbox__inner::after) {
  599. height: 30px !important;
  600. left: 11px !important;
  601. width: 15px !important;
  602. top: -2px !important;
  603. border-color: #2B5AE5;
  604. }
  605. :deep(.el-checkbox__input.is-checked .el-checkbox__inner::after) {
  606. border-color: #2B5AE5;
  607. }
  608. :deep(.el-checkbox) {
  609. width: 300px !important;
  610. height: 42px !important;
  611. margin-right: 0 !important;
  612. margin-bottom: 10px !important;
  613. }
  614. :deep(.el-checkbox__label) {
  615. width: 202px !important;
  616. height: 42px !important;
  617. margin-left: 10px !important;
  618. line-height: 42px !important;
  619. text-align: center;
  620. }
  621. .leader-line {
  622. z-index: 3000;
  623. }
  624. #wrapper {
  625. width: 0;
  626. height: 0;
  627. position: relative;
  628. /* Origin of coordinates for lines, and scrolled content (i.e. not `absolute`) */
  629. }
  630. :deep(.el-radio__inner) {
  631. width: 32px;
  632. height: 32px;
  633. }
  634. :deep(.el-radio__input.is-checked .el-radio__inner::after) {
  635. width: 15px;
  636. height: 15px;
  637. }
  638. :deep(.el-radio-group) {
  639. width: 100%;
  640. justify-content: space-between;
  641. margin: 4px;
  642. }
  643. </style>