LineDouble.vue 25 KB

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