arrow.vue 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304
  1. <!-- 剪头渲染组件 -->
  2. <template>
  3. <g v-if="this.DataAll">
  4. <path
  5. @mouseover="pathHover"
  6. @mouseout="pathOut"
  7. :class="(isHover || r_click_menu) ? 'connector-hl' : each.type && each.type == 'active' ? 'connector-active' : each.type && each.type == 'success' ? 'connector' : 'defaultArrow'"
  8. :d="computedLink()"
  9. :style="each.style"
  10. @contextmenu="r_click($event)"
  11. ></path>
  12. <text ref="edgeText" v-if="each.edgesText" :style="computedText()">{{each.edgesText}}</text>
  13. <polyline class="only-watch-el" :points="computedArrow()"
  14. style="stroke:#006600;"/>
  15. <circle class="only-watch-el" :cx="computedCx()" :cy="computedCy()" r="5"
  16. style="stroke:#006600;
  17. stroke-width: 2;
  18. fill:#FFFFFF"/>
  19. <g v-if="r_click_menu">
  20. <foreignObject width="100%" height="100%" style="position: relative" @click="click_menu_cover($event)">
  21. <body xmlns="http://www.w3.org/1999/xhtml" :style="menu_style" style="width:150px;height: auto;border-radius: 5px;">
  22. <div class="menu_contain">
  23. <span @click="delEdges" style="width: 100%;display: block;">删除</span>
  24. </div>
  25. </body>
  26. </foreignObject>
  27. </g>
  28. </g>
  29. </template>
  30. <script>
  31. export default {
  32. props: {
  33. DataAll: {
  34. type: Object
  35. },
  36. each: {
  37. type: Object
  38. },
  39. index: {
  40. type: Number
  41. }
  42. },
  43. computed: {
  44. svgScale() { return sessionStorage['svgScale'] || 1 }
  45. },
  46. data() {
  47. return {
  48. isHover: false,
  49. r_click_menu: false,
  50. menu_style: {
  51. position: "absolute",
  52. left: `${358}px`,
  53. top: `${264}px`
  54. }
  55. };
  56. },
  57. methods: {
  58. pathHover() {
  59. this.isHover = true;
  60. },
  61. pathOut() {
  62. this.isHover = false;
  63. },
  64. click_menu_cover(e) {
  65. // 点击遮罩
  66. this.r_click_menu = false;
  67. e.stopPropagation();
  68. e.preventDefault();
  69. e.cancelBubble = true;
  70. },
  71. delEdges() { // 删除此条连线
  72. let params = {
  73. id: this.each.id
  74. }
  75. let _this = this
  76. this.$confirm('此操作将永久删除连线, 是否继续?', '提示', {
  77. confirmButtonText: '确定',
  78. cancelButtonText: '取消',
  79. type: 'warning'
  80. }).then(() => {
  81. _this.$emit('delEdge', params)
  82. _this.$message({
  83. type: 'success',
  84. message: '删除成功!'
  85. });
  86. }).catch(() => {
  87. _this.$message({
  88. type: 'info',
  89. message: '已取消删除'
  90. });
  91. });
  92. },
  93. r_click(e) {
  94. console.log(this.svgScale)
  95. const x = e.offsetX;
  96. const y = e.offsetY;
  97. this.menu_style = Object.assign({}, this.menu_style, { left: `${(x - (sessionStorage['svg_left'] || 0)) / this.svgScale}px`, top: `${(y - (sessionStorage['svg_top'] || 0)) / this.svgScale}px` })
  98. this.r_click_menu = true;
  99. e.stopPropagation();
  100. e.preventDefault();
  101. e.cancelBubble = true;
  102. },
  103. computedLink() {
  104. // 计算起始点坐标
  105. let f_X, f_Y, t_X, t_Y
  106. if (!this.DataAll) {
  107. return `M 0 0 T 0 0`;
  108. } else {
  109. const {
  110. dst_input_idx, // 目标
  111. dst_node_id, // 目标id
  112. src_node_id, // 来源id
  113. src_output_idx // 来源
  114. } = this.each;
  115. const f_Pos = this.DataAll.nodes.find(item => item.id === src_node_id);
  116. const t_Pos = this.DataAll.nodes.find(item => item.id === dst_node_id);
  117. if (!f_Pos || !t_Pos) { alert(src_node_id) }
  118. if (this.isVertical()) {
  119. f_X = f_Pos.pos_x + (180 / (f_Pos.out_ports.length + 1)) * (src_output_idx + 1);
  120. f_Y = f_Pos.pos_y + 30;
  121. t_X = t_Pos.pos_x + (180 / (t_Pos.in_ports.length + 1)) * (dst_input_idx + 1);
  122. t_Y = t_Pos.pos_y;
  123. return `M ${f_X} ${f_Y} Q ${f_X} ${f_Y + 50} ${(t_X + f_X) / 2} ${(t_Y + f_Y) / 2} T ${t_X} ${t_Y}`;
  124. } else {
  125. f_X = f_Pos.pos_x + 180
  126. f_Y = f_Pos.pos_y + 15
  127. t_X = t_Pos.pos_x
  128. t_Y = t_Pos.pos_y + 15
  129. return `M ${f_X} ${f_Y} Q ${f_X + 30} ${f_Y} ${(t_X + f_X) / 2} ${(t_Y + f_Y) / 2} T ${t_X} ${t_Y}`;
  130. }
  131. }
  132. },
  133. computedText() { // 计算文字坐标
  134. if (!this.DataAll) {
  135. return `M 0 0 T 0 0`;
  136. } else {
  137. const {
  138. dst_input_idx, // 目标
  139. dst_node_id, // 目标id
  140. src_node_id, // 来源id
  141. src_output_idx // 来源
  142. } = this.each;
  143. const f_Pos = this.DataAll.nodes.find(item => item.id === src_node_id);
  144. const t_Pos = this.DataAll.nodes.find(item => item.id === dst_node_id);
  145. if (!f_Pos) {
  146. alert(src_node_id)
  147. }
  148. if (!t_Pos) {
  149. alert(dst_node_id)
  150. }
  151. const f_X =
  152. f_Pos.pos_x +
  153. (180 / (f_Pos.out_ports.length + 1)) * (src_output_idx + 1);
  154. const f_Y = f_Pos.pos_y + 30;
  155. const t_X =
  156. t_Pos.pos_x +
  157. (180 / (t_Pos.in_ports.length + 1)) * (dst_input_idx + 1);
  158. const t_Y = t_Pos.pos_y;
  159. return {
  160. transform: `translate(${(f_X + t_X) / 2}px, ${(f_Y + t_Y) / 2}px)`,
  161. stroke: '#fff',
  162. ...this.each.textStyle
  163. };
  164. }
  165. },
  166. computedArrow() {
  167. // 计算箭头坐标
  168. if (!this.DataAll) {
  169. return `0,0 0,0 0,0`;
  170. } else {
  171. const {
  172. dst_input_idx,
  173. dst_node_id,
  174. src_node_id,
  175. src_output_idx
  176. } = this.each;
  177. const t_Pos = this.DataAll.nodes.find(item => item.id === dst_node_id);
  178. let t_X = t_Pos.pos_x + (180 / (t_Pos.in_ports.length + 1)) * (dst_input_idx + 1);
  179. let t_Y = t_Pos.pos_y;
  180. if (this.isVertical()) {
  181. return `${t_X} ${t_Y + 3} ${t_X - 3} ${t_Y - 3} ${t_X + 3} ${t_Y - 3}`;
  182. } else {
  183. let t_X = t_Pos.pos_x
  184. let t_Y = t_Pos.pos_y + 15
  185. return `${t_X - 2} ${t_Y + 3} ${t_X - 3} ${t_Y - 3} ${t_X + 3} ${t_Y - 1}`
  186. }
  187. }
  188. },
  189. computedCx() {
  190. const { src_node_id, src_output_idx } = this.each;
  191. const f_Pos = this.DataAll.nodes.find(item => item.id === src_node_id);
  192. let f_X = 0
  193. if (this.isVertical()) {
  194. f_X = f_Pos.pos_x + (180 / (f_Pos.out_ports.length + 1)) * (src_output_idx + 1);
  195. } else {
  196. f_X = f_Pos.pos_x + 180
  197. }
  198. return `${f_X}`;
  199. },
  200. computedCy() {
  201. let f_Y = 0
  202. const { src_node_id } = this.each;
  203. const f_Pos = this.DataAll.nodes.find(item => item.id === src_node_id);
  204. if (this.isVertical()) {
  205. f_Y = f_Pos.pos_y + 30;
  206. } else {
  207. f_Y = f_Pos.pos_y + 15
  208. }
  209. return `${f_Y}`;
  210. },
  211. isVertical() {
  212. let GlobalConfig = { isVertical: true }
  213. let _GlobalConfig = localStorage.getItem('GlobalConfig')
  214. if (_GlobalConfig && _GlobalConfig.length > 0) {
  215. GlobalConfig = Object.assign(GlobalConfig, JSON.parse(_GlobalConfig))
  216. }
  217. return GlobalConfig.isVertical
  218. }
  219. },
  220. mounted() {
  221. this.$nextTick(() => {
  222. if (this.$refs.edgeText) {
  223. console.log(this.$refs.edgeText.style.width)
  224. }
  225. })
  226. }
  227. };
  228. </script>
  229. <style scoped>
  230. .only-watch-el {
  231. pointer-events: none;
  232. }
  233. .connector {
  234. stroke: #00c0ff;
  235. stroke-width: 2px;
  236. fill: none;
  237. cursor: pointer;
  238. stroke-dasharray: 100%;
  239. animation: line_success 2s;
  240. }
  241. @keyframes line_success {
  242. 0% {
  243. stroke-dashoffset: 100%;
  244. }
  245. 100% {
  246. stroke-dashoffset: 0%;
  247. }
  248. }
  249. .connector-active {
  250. stroke: rgba(91, 230, 20, 0.6);
  251. fill: none;
  252. cursor: pointer;
  253. stroke-width: 2px;
  254. stroke-dasharray: 5px;
  255. stroke-dashoffset: 1000px;
  256. animation: grown 40s infinite linear;
  257. }
  258. .defaultArrow {
  259. stroke: #00c0ff;
  260. stroke-width: 2px;
  261. fill: none;
  262. cursor: pointer;
  263. stroke-dasharray: 5px;
  264. stroke-dashoffset: 1000px;
  265. animation: grown 40s infinite linear;
  266. }
  267. @keyframes grown {
  268. to{
  269. stroke-dashoffset: 0px;
  270. }
  271. }
  272. .menu_cover {
  273. position: fixed;
  274. left: 0;
  275. top: 0;
  276. right: 0;
  277. bottom: 0;
  278. }
  279. .connector-hl {
  280. stroke: #00c0ff;
  281. stroke-width: 5px;
  282. fill: none;
  283. cursor: pointer;
  284. }
  285. .menu_contain {
  286. width: 150px;
  287. border: 1px solid rgba(1, 1, 1, 0.3);
  288. background: #ffffff;
  289. border-radius: 5px;
  290. padding: 3px;
  291. }
  292. .meun_contain>span {
  293. width: 100%;
  294. display: block;
  295. }
  296. .menu_contain span:hover {
  297. background-color: rgba(40,157,233, .3);
  298. cursor: pointer;
  299. }
  300. </style>