vue-wiring-diagram 1.1.22 → 1.1.24

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. package/README.md +93 -93
  2. package/dist/style.css +1 -1
  3. package/dist/vue-wiring-diagram.es.js +5563 -5234
  4. package/dist/vue-wiring-diagram.umd.js +27 -27
  5. package/package.json +1 -1
  6. package/packages/components/Shortcuts.vue +31 -31
  7. package/packages/components/baseShape.js +62 -62
  8. package/packages/components/common.js +105 -105
  9. package/packages/components/edge-control/arrow-line.vue +292 -292
  10. package/packages/components/edge-control/condition.vue +110 -110
  11. package/packages/components/edge-control/default-line.vue +156 -156
  12. package/packages/components/edge-control/index.vue +94 -94
  13. package/packages/components/edge-control/pipe-line.vue +354 -354
  14. package/packages/components/editor/index.vue +590 -590
  15. package/packages/components/enums.js +80 -80
  16. package/packages/components/graph-control/index.vue +121 -121
  17. package/packages/components/image-control/group-form.vue +114 -114
  18. package/packages/components/image-control/image-condition.vue +117 -0
  19. package/packages/components/image-control/image-form.vue +184 -184
  20. package/packages/components/image-control/image-management.vue +213 -213
  21. package/packages/components/image-control/index.vue +290 -124
  22. package/packages/components/portsOptions.js +21 -21
  23. package/packages/components/preview/index.vue +399 -355
  24. package/packages/components/settings.js +262 -262
  25. package/packages/components/text-control/index.vue +457 -457
  26. package/packages/components/tools.js +256 -256
  27. package/packages/http.js +104 -104
  28. package/packages/index.js +43 -43
  29. package/packages/styles/animation.scss +27 -27
  30. package/packages/styles/dialog.scss +4 -4
  31. package/packages/styles/editor.scss +165 -165
  32. package/packages/styles/elPath.scss +257 -257
@@ -1,590 +1,590 @@
1
- <template>
2
- <div class="page-container">
3
- <!--操作烂-->
4
- <div class="tool-bar">
5
- <div class="action">
6
- <el-dropdown @command="handleCommandSetting">
7
- <span class="el-dropdown-link">
8
- <el-button size="small" text>工具
9
- <el-icon><ArrowDown/></el-icon>
10
- </el-button>
11
- </span>
12
- <template #dropdown>
13
- <el-dropdown-menu>
14
- <el-dropdown-item command="snapLine">对齐线</el-dropdown-item>
15
- <el-dropdown-item command="select">框选</el-dropdown-item>
16
- <el-dropdown-item command="drawLine">绘制直线</el-dropdown-item>
17
- <el-dropdown-item command="adaptive">视图自适应</el-dropdown-item>
18
- <el-dropdown-item command="shortcuts">查看快捷键</el-dropdown-item>
19
- <el-dropdown-item command="image">图片管理</el-dropdown-item>
20
- <el-dropdown-item command="clear">清空画布</el-dropdown-item>
21
- <el-dropdown-item command="export">导出成图片</el-dropdown-item>
22
- </el-dropdown-menu>
23
- </template>
24
- </el-dropdown>
25
- </div>
26
- <div class="action" style="margin-right: 50px">
27
- <el-tooltip content="预览" placement="bottom">
28
- <el-button size="small" :icon="View" @click="viewJson">
29
- </el-button>
30
- </el-tooltip>
31
- <el-tooltip content="保存" placement="bottom">
32
- <el-button size="small" :icon="Document" @click="getJson">
33
- </el-button>
34
- </el-tooltip>
35
- </div>
36
- </div>
37
- <div class="board">
38
- <!--模型列表-->
39
- <div id="stencil"></div>
40
- <!--画板-->
41
- <div class="drawing-board-box">
42
- <div id="drawing-board" ref="drawBoard"></div>
43
- </div>
44
- <!--控制器-->
45
- <div id="controls">
46
- <div class="controls-label" v-if="controls.label">{{ controls.label }}</div>
47
- <el-scrollbar>
48
- <component :is="controls.component" :graph="graph" :payload="payload"
49
- :key="controlsKey" :itemId="props.itemId"></component>
50
- </el-scrollbar>
51
- </div>
52
- </div>
53
- <el-dialog v-model="dialog.show" width="400px" title="快捷键" :close-on-click-modal="false"
54
- :close-on-press-escape="false">
55
- <shortcuts/>
56
- </el-dialog>
57
- <el-dialog v-model="imageManagement.show" width="800px" title="图片管理" :close-on-click-modal="false"
58
- :close-on-press-escape="false">
59
- <image-management v-if="imageManagement.show"/>
60
- </el-dialog>
61
- <el-drawer class="no-border-drawer" v-model="view.show" @close="closeViewBtnClick" title="预览"
62
- :direction="'ttb'" size="100%">
63
- <div style="width: 100%;height: 100%;background: black">
64
- <wiring-diagram-preview v-if="view.show" :background="view.background" :grid="view.grid"
65
- :data="view.data"></wiring-diagram-preview>
66
- </div>
67
- </el-drawer>
68
- </div>
69
- </template>
70
-
71
- <script setup>
72
- import {onMounted, reactive, ref, shallowRef} from "vue";
73
- import moment from 'moment'
74
- import {ElMessage, ElTooltip} from "element-plus";
75
- import {getData, showPorts} from "../common.js";
76
- import {backgroundOptions, gridOptions, lineOptions, stencilOptions, textOptions} from "../settings.js";
77
- import GraphControl from '../graph-control/index.vue'
78
- import textControl from '../text-control/index.vue'
79
- import edgeControl from "../edge-control/index.vue";
80
- import Shortcuts from "../Shortcuts.vue";
81
- import {Graph} from "@antv/x6";
82
- import {Stencil} from '@antv/x6-plugin-stencil'
83
- import {Export} from "@antv/x6-plugin-export";
84
- import imageControl from "../image-control/index.vue";
85
- import {baseShape} from "../baseShape.js";
86
- import {portsOptions} from "../portsOptions.js";
87
- import {Document, View, ArrowDown} from "@element-plus/icons-vue";
88
- import WiringDiagramPreview from "../preview/index.vue";
89
- import ImageManagement from "../image-control/image-management.vue";
90
- import {get} from "packages/http.js";
91
- import {
92
- clipboard,
93
- keyboard,
94
- snapLine,
95
- transform,
96
- history,
97
- selection,
98
- setMathRandom,
99
- deleteCell,
100
- copyCell,
101
- pasteCell, undoCell, redoCell, addLineTools, isLineOrPipe, setSunCloudImageUrl
102
- } from "packages/components/tools.js";
103
- import {isSunOs} from "packages";
104
-
105
- defineOptions({
106
- name: 'editor'
107
- })
108
-
109
- const props = defineProps({
110
- id: {
111
- type: String,
112
- default: ''
113
- },
114
- itemId: {
115
- type: String,
116
- default: ''
117
- }
118
- })
119
-
120
- const graph = ref() // 画布实例
121
- // 控制器
122
- const controls = reactive({
123
- label: '', // 控制器
124
- component: shallowRef(null), // 控制器组件
125
- })
126
- const controlsKey = ref(null) // 控制器key
127
- const payload = ref(null)
128
-
129
- /**
130
- * 初始化画布
131
- */
132
- const initGraph = () => {
133
- window.__x6_instances__ = []
134
- const container = document.getElementById('drawing-board')
135
- graph.value = new Graph({
136
- container: container,
137
- autoResize: true,
138
- panning: true,
139
- mousewheel: true,
140
- connecting: {
141
- snap: true,
142
- allowEdge: true,
143
- highlight: true,
144
- router: 'orth',
145
- connectionPoint: {
146
- name: 'anchor',
147
- },
148
- anchor: 'center',
149
- createEdge() {
150
- return graph.value.createEdge({
151
- inherit: 'edge',
152
- ...lineOptions,
153
- data: {
154
- type: isLineOrPipe(),
155
- },
156
- zIndex: 0,
157
- })
158
- }
159
- },
160
- })
161
- window.__x6_instances__.push(graph.value)
162
- graph.value.use(transform())
163
- graph.value.use(keyboard())
164
- graph.value.use(snapLine())
165
- graph.value.use(clipboard())
166
- graph.value.use(history())
167
- graph.value.use(selection())
168
- graph.value.bindKey(['delete', 'backspace'], () => {
169
- deleteCell(graph.value)
170
- })
171
- graph.value.bindKey(['ctrl+c', 'cmd + c'], () => {
172
- copyCell(graph.value)
173
- })
174
- graph.value.bindKey(['ctrl+v', 'cmd + v'], () => {
175
- pasteCell(graph.value)
176
- })
177
- graph.value.bindKey(['ctrl+z', 'cmd + z'], () => {
178
- undoCell(graph.value)
179
- })
180
- graph.value.bindKey(['ctrl+y', 'cmd + y'], () => {
181
- redoCell(graph.value)
182
- })
183
-
184
- let setZIndexTimer = null
185
-
186
- graph.value.on('edge:click', ({cell}) => {
187
- payload.value = cell
188
- controls.component = edgeControl
189
- controls.label = '线段属性'
190
- controlsKey.value = setMathRandom()
191
- })
192
-
193
- //给线添加拐点
194
- graph.value.on('edge:mouseenter', ({cell}) => {
195
- clearTimeout(setZIndexTimer)
196
- cell.toFront()
197
- addLineTools(cell)
198
- })
199
-
200
- // 隐藏线段的拐点
201
- graph.value.on('edge:mouseleave', ({cell}) => {
202
- setZIndexTimer = setTimeout(() => {
203
- cell.toBack()
204
- cell.removeTools()
205
- }, 300)
206
-
207
- })
208
-
209
- //连接桩显示
210
- graph.value.on('node:mouseenter', () => {
211
- showPorts('#drawing-board', true)
212
- })
213
-
214
- //连接桩隐藏
215
- graph.value.on('node:mouseleave', () => {
216
- showPorts("#drawing-board", false)
217
- })
218
-
219
- // 导出插件
220
- graph.value.use(new Export())
221
-
222
- //选中画布
223
- graph.value.on('blank:click', ({e, x, y}) => {
224
- payload.value = graph
225
- controls.component = GraphControl
226
- controls.label = '画布属性'
227
- controlsKey.value = setMathRandom()
228
- })
229
-
230
-
231
- //选中节点
232
- graph.value.on('cell:click', ({cell}) => {
233
- payload.value = cell
234
- controlsKey.value = setMathRandom()
235
- if (cell.data?.type === 'text') {
236
- controls.component = textControl
237
- controls.label = '文本属性'
238
- return
239
- }
240
- if (cell.data?.type === 'base-shape') {
241
- controls.component = textControl
242
- controls.label = '基础图形属性'
243
- return
244
- }
245
- if (cell?.shape === 'edge') {
246
- controls.component = edgeControl
247
- controls.label = '连线属性'
248
- return
249
- }
250
- if (cell?.shape === 'image') {
251
- controls.component = imageControl
252
- controls.label = '图片属性'
253
- return
254
- }
255
- controls.component = null
256
- controls.label = null
257
- })
258
-
259
- graph.value.on('render:done', () => {
260
- showPorts("#drawing-board", false)
261
- adaptive()
262
- })
263
- }
264
-
265
- const stencil = ref()
266
-
267
- /**
268
- * 初始化模型列表
269
- */
270
- const initStencil = () => {
271
- stencil.value = new Stencil({
272
- target: graph.value,
273
- ...stencilOptions,
274
- groups: [
275
- {
276
- name: '基础组件',
277
- graphPadding: 30
278
- },
279
- ],
280
- search(cell, keyword) {
281
- return cell.label?.indexOf(keyword) !== -1
282
- },
283
- placeholder: '搜索',
284
- notFoundText: '未找到匹配项',
285
- getDragNode: (node) => {
286
- const copyNode = node.clone({keepId: true})
287
- if (copyNode.label !== '文本') {
288
- copyNode.label = ''
289
- }
290
- return copyNode
291
- },
292
- })
293
- const text = graph.value.createNode({
294
- ...textOptions
295
- })
296
- const baseShapes = baseShape.map(item => {
297
- item.ports = portsOptions
298
- return graph.value.createNode(item)
299
- })
300
- stencil.value.load([text, ...baseShapes], '基础组件')
301
- get('/custom/groupList').then(res => {
302
- const data = res?.data || []
303
- data?.forEach(group => {
304
- stencil.value.addGroup({
305
- name: group?.groupName,
306
- graphPadding: 30
307
- })
308
- const image = group?.pictureList.map(item => {
309
- const nodeOption = {
310
- shape: 'image',
311
- imageUrl: isSunOs ? item?.imageUrl : setSunCloudImageUrl(item?.imageUrl),
312
- ports: portsOptions,
313
- width: 64,
314
- height: 64,
315
- label: item.imageName
316
- }
317
- return graph.value.createNode(nodeOption)
318
- })
319
- stencil.value.load(image, group?.groupName)
320
- document.getElementById('stencil').appendChild(stencil.value.container)
321
- })
322
- }).finally(() => {
323
- setTimeout(() => {
324
- const container = stencil.value.container;
325
- const nodes = container.querySelectorAll('.x6-node');
326
- const baseLength = baseShape.length;
327
-
328
- // 使用事件委托优化事件监听
329
- container.addEventListener('mouseenter', handleMouseEnter, true);
330
- container.addEventListener('mouseleave', handleMouseLeave, true);
331
-
332
- nodes.forEach((node, index) => {
333
- const isCustomNode = index > baseLength;
334
- const text = node.querySelector('text');
335
-
336
- if (isCustomNode && text) {
337
- Object.assign(text.style, {
338
- display: 'none',
339
- fontSize: '12px',
340
- fill: '#ffffff'
341
- });
342
- text.setAttribute('x', '0%');
343
- text.setAttribute('y', '40');
344
- node.style.position = 'relative';
345
- node.style.cursor = 'pointer';
346
- }
347
- });
348
-
349
- function handleMouseEnter(e) {
350
- const node = e.target.closest('.x6-node');
351
- if (node) {
352
- const index = Array.from(nodes).indexOf(node);
353
- const text = node.querySelector('text');
354
- if (index > baseLength && text) {
355
- text.style.display = 'block';
356
- }
357
- }
358
- }
359
-
360
- function handleMouseLeave(e) {
361
- const node = e.target.closest('.x6-node');
362
- if (node) {
363
- const index = Array.from(nodes).indexOf(node);
364
- const text = node.querySelector('text');
365
- if (index > baseLength && text) {
366
- text.style.display = 'none';
367
- }
368
- }
369
- }
370
-
371
- // 添加清理事件监听的方法
372
- return () => {
373
- container.removeEventListener('mouseenter', handleMouseEnter, true);
374
- container.removeEventListener('mouseleave', handleMouseLeave, true);
375
- };
376
- }, 200);
377
- })
378
- }
379
-
380
- /**
381
- * 命令
382
- * @param command
383
- */
384
- const handleCommandSetting = (command) => {
385
- switch (command) {
386
- case 'snapLine':
387
- if (graph.value.isSnaplineEnabled()) {
388
- graph.value.disableSnapline()
389
- ElMessage.warning('关闭对齐线')
390
- } else {
391
- graph.value.enableSnapline()
392
- ElMessage.success('开启对齐线')
393
- }
394
- break
395
- case 'select':
396
- if (graph.value.isSelectionEnabled()) {
397
- graph.value.disableSelection()
398
- ElMessage.warning('关闭框选')
399
- } else {
400
- graph.value.enableSelection()
401
- ElMessage.success('开启框选')
402
- }
403
- break
404
- case 'drawLine':
405
- drawLine()
406
- break
407
- case 'adaptive':
408
- adaptive()
409
- break
410
- case 'shortcuts':
411
- showShortcuts()
412
- break
413
- case 'image':
414
- showImageManagement()
415
- break
416
- case 'clear':
417
- clear()
418
- break
419
- case 'export':
420
- exportGraph()
421
- break
422
- }
423
- }
424
-
425
- const drawLineSwitch = ref(false) // 绘制直线开关
426
- const edge = ref(null) // 边
427
-
428
- /**
429
- * 绘制直线
430
- */
431
- const drawLine = () => {
432
- drawLineSwitch.value = true
433
- const time = new Date()
434
- graph.value.startBatch(time)
435
- graph.value.disablePanning()
436
- graph.value.once('blank:mousedown', ({e, x, y}) => {
437
- if (drawLineSwitch.value) {
438
- edge.value = graph.value.addEdge({
439
- source: {x: x, y: y},
440
- target: {x: x, y: y},
441
- ...lineOptions,
442
- data: {
443
- type: isLineOrPipe(),
444
- isConnector: false,
445
- },
446
- zIndex: 0,
447
- })
448
- }
449
- })
450
-
451
- graph.value.on('blank:mousemove', ({e, x, y}) => {
452
- if (drawLineSwitch.value) {
453
- edge.value.setTarget({x: x, y: y})
454
- }
455
- })
456
-
457
- graph.value.on('blank:mouseup', ({e, x, y}) => {
458
- drawLineSwitch.value = false
459
- graph.value.enablePanning()
460
- graph.value.stopBatch(time)
461
- })
462
- }
463
-
464
- /**
465
- * 适配画布
466
- */
467
- const adaptive = () => {
468
- graph.value.zoomToFit({maxScale: 1, padding: 20})
469
- }
470
-
471
- const dialog = reactive({show: false}) // 弹窗
472
-
473
- /**
474
- * 显示快捷键
475
- */
476
- const showShortcuts = () => {
477
- dialog.show = true
478
- }
479
-
480
- // 图片管理
481
- const imageManagement = reactive({
482
- show: false,
483
- })
484
-
485
- /**
486
- * 显示图片管理
487
- */
488
- const showImageManagement = () => {
489
- imageManagement.show = true
490
- }
491
-
492
- /**
493
- * 清空画布
494
- */
495
- const clear = () => {
496
- graph.value.clearCells()
497
- graph.value.drawBackground(backgroundOptions)
498
- graph.value.drawGrid(gridOptions)
499
- ElMessage.success('清空成功')
500
- }
501
-
502
- /**
503
- * 导出画布
504
- */
505
- const exportGraph = () => {
506
- const fileName = moment().format('YYYYMMDDHHmmss')
507
- graph.value.exportPNG(fileName, {
508
- padding: 10,
509
- quality: 1,
510
- backgroundColor: graph.value.options.background.color,
511
- })
512
- }
513
-
514
- // 暴露给父组件
515
- const emits = defineEmits(['getJson'])
516
-
517
- const view = reactive({
518
- show: false,
519
- background: null,
520
- grid: null,
521
- data: null
522
- })
523
-
524
- /**
525
- * 查看画布数据
526
- */
527
- const viewJson = () => {
528
- view.background = graph.value.options.background
529
- view.grid = graph.value.options.grid
530
- view.data = graph.value.toJSON()
531
- view.show = true
532
- }
533
-
534
- const closeViewBtnClick = () => {
535
- view.show = false
536
- }
537
-
538
- /**
539
- * 获取画布数据
540
- */
541
- const getJson = () => {
542
- emits('getJson', {
543
- background: graph.value.options.background,
544
- grid: graph.value.options.grid,
545
- data: graph.value.toJSON()
546
- })
547
- }
548
-
549
- /**
550
- * 渲染数据
551
- */
552
- const renderData = async () => {
553
- if (!props.id) {
554
- graph.value.drawBackground(backgroundOptions)
555
- graph.value.drawGrid(gridOptions)
556
- graph.value.fromJSON({cells: []})
557
- return
558
- }
559
- const data = await getData(props.id)
560
- graph.value.drawBackground(data?.background || backgroundOptions)
561
- graph.value.drawGrid(data?.grid || gridOptions )
562
- graph.value.fromJSON(data?.data || {cells: []} )
563
- }
564
-
565
- const drawBoard = shallowRef(null)
566
-
567
- const watchBrowser = () => {
568
- const observer = new ResizeObserver(() => {
569
- adaptive()
570
- })
571
- observer.observe(drawBoard.value)
572
- }
573
-
574
- onMounted(() => {
575
- initGraph()
576
- initStencil()
577
- renderData()
578
- watchBrowser()
579
-
580
- })
581
-
582
- </script>
583
-
584
- <style lang="scss" scoped>
585
- @use "../../styles/editor";
586
- </style>
587
-
588
- <style lang="scss">
589
- @use "../../styles/animation.scss";
590
- </style>
1
+ <template>
2
+ <div class="page-container">
3
+ <!--操作烂-->
4
+ <div class="tool-bar">
5
+ <div class="action">
6
+ <el-dropdown @command="handleCommandSetting">
7
+ <span class="el-dropdown-link">
8
+ <el-button size="small" text>工具
9
+ <el-icon><ArrowDown/></el-icon>
10
+ </el-button>
11
+ </span>
12
+ <template #dropdown>
13
+ <el-dropdown-menu>
14
+ <el-dropdown-item command="snapLine">对齐线</el-dropdown-item>
15
+ <el-dropdown-item command="select">框选</el-dropdown-item>
16
+ <el-dropdown-item command="drawLine">绘制直线</el-dropdown-item>
17
+ <el-dropdown-item command="adaptive">视图自适应</el-dropdown-item>
18
+ <el-dropdown-item command="shortcuts">查看快捷键</el-dropdown-item>
19
+ <el-dropdown-item command="image">图片管理</el-dropdown-item>
20
+ <el-dropdown-item command="clear">清空画布</el-dropdown-item>
21
+ <el-dropdown-item command="export">导出成图片</el-dropdown-item>
22
+ </el-dropdown-menu>
23
+ </template>
24
+ </el-dropdown>
25
+ </div>
26
+ <div class="action" style="margin-right: 50px">
27
+ <el-tooltip content="预览" placement="bottom">
28
+ <el-button size="small" :icon="View" @click="viewJson">
29
+ </el-button>
30
+ </el-tooltip>
31
+ <el-tooltip content="保存" placement="bottom">
32
+ <el-button size="small" :icon="Document" @click="getJson">
33
+ </el-button>
34
+ </el-tooltip>
35
+ </div>
36
+ </div>
37
+ <div class="board">
38
+ <!--模型列表-->
39
+ <div id="stencil"></div>
40
+ <!--画板-->
41
+ <div class="drawing-board-box">
42
+ <div id="drawing-board" ref="drawBoard"></div>
43
+ </div>
44
+ <!--控制器-->
45
+ <div id="controls">
46
+ <div class="controls-label" v-if="controls.label">{{ controls.label }}</div>
47
+ <el-scrollbar>
48
+ <component :is="controls.component" :graph="graph" :payload="payload"
49
+ :key="controlsKey" :itemId="props.itemId"></component>
50
+ </el-scrollbar>
51
+ </div>
52
+ </div>
53
+ <el-dialog v-model="dialog.show" width="400px" title="快捷键" :close-on-click-modal="false"
54
+ :close-on-press-escape="false">
55
+ <shortcuts/>
56
+ </el-dialog>
57
+ <el-dialog v-model="imageManagement.show" width="800px" title="图片管理" :close-on-click-modal="false"
58
+ :close-on-press-escape="false">
59
+ <image-management v-if="imageManagement.show"/>
60
+ </el-dialog>
61
+ <el-drawer class="no-border-drawer" v-model="view.show" @close="closeViewBtnClick" title="预览"
62
+ :direction="'ttb'" size="100%">
63
+ <div style="width: 100%;height: 100%;background: black">
64
+ <wiring-diagram-preview v-if="view.show" :background="view.background" :grid="view.grid"
65
+ :data="view.data"></wiring-diagram-preview>
66
+ </div>
67
+ </el-drawer>
68
+ </div>
69
+ </template>
70
+
71
+ <script setup>
72
+ import {onMounted, reactive, ref, shallowRef} from "vue";
73
+ import moment from 'moment'
74
+ import {ElMessage, ElTooltip} from "element-plus";
75
+ import {getData, showPorts} from "../common.js";
76
+ import {backgroundOptions, gridOptions, lineOptions, stencilOptions, textOptions} from "../settings.js";
77
+ import GraphControl from '../graph-control/index.vue'
78
+ import textControl from '../text-control/index.vue'
79
+ import edgeControl from "../edge-control/index.vue";
80
+ import Shortcuts from "../Shortcuts.vue";
81
+ import {Graph} from "@antv/x6";
82
+ import {Stencil} from '@antv/x6-plugin-stencil'
83
+ import {Export} from "@antv/x6-plugin-export";
84
+ import imageControl from "../image-control/index.vue";
85
+ import {baseShape} from "../baseShape.js";
86
+ import {portsOptions} from "../portsOptions.js";
87
+ import {Document, View, ArrowDown} from "@element-plus/icons-vue";
88
+ import WiringDiagramPreview from "../preview/index.vue";
89
+ import ImageManagement from "../image-control/image-management.vue";
90
+ import {get} from "packages/http.js";
91
+ import {
92
+ clipboard,
93
+ keyboard,
94
+ snapLine,
95
+ transform,
96
+ history,
97
+ selection,
98
+ setMathRandom,
99
+ deleteCell,
100
+ copyCell,
101
+ pasteCell, undoCell, redoCell, addLineTools, isLineOrPipe, setSunCloudImageUrl
102
+ } from "packages/components/tools.js";
103
+ import {isSunOs} from "packages";
104
+
105
+ defineOptions({
106
+ name: 'editor'
107
+ })
108
+
109
+ const props = defineProps({
110
+ id: {
111
+ type: String,
112
+ default: ''
113
+ },
114
+ itemId: {
115
+ type: String,
116
+ default: ''
117
+ }
118
+ })
119
+
120
+ const graph = ref() // 画布实例
121
+ // 控制器
122
+ const controls = reactive({
123
+ label: '', // 控制器
124
+ component: shallowRef(null), // 控制器组件
125
+ })
126
+ const controlsKey = ref(null) // 控制器key
127
+ const payload = ref(null)
128
+
129
+ /**
130
+ * 初始化画布
131
+ */
132
+ const initGraph = () => {
133
+ window.__x6_instances__ = []
134
+ const container = document.getElementById('drawing-board')
135
+ graph.value = new Graph({
136
+ container: container,
137
+ autoResize: true,
138
+ panning: true,
139
+ mousewheel: true,
140
+ connecting: {
141
+ snap: true,
142
+ allowEdge: true,
143
+ highlight: true,
144
+ router: 'orth',
145
+ connectionPoint: {
146
+ name: 'anchor',
147
+ },
148
+ anchor: 'center',
149
+ createEdge() {
150
+ return graph.value.createEdge({
151
+ inherit: 'edge',
152
+ ...lineOptions,
153
+ data: {
154
+ type: isLineOrPipe(),
155
+ },
156
+ zIndex: 0,
157
+ })
158
+ }
159
+ },
160
+ })
161
+ window.__x6_instances__.push(graph.value)
162
+ graph.value.use(transform())
163
+ graph.value.use(keyboard())
164
+ graph.value.use(snapLine())
165
+ graph.value.use(clipboard())
166
+ graph.value.use(history())
167
+ graph.value.use(selection())
168
+ graph.value.bindKey(['delete', 'backspace'], () => {
169
+ deleteCell(graph.value)
170
+ })
171
+ graph.value.bindKey(['ctrl+c', 'cmd + c'], () => {
172
+ copyCell(graph.value)
173
+ })
174
+ graph.value.bindKey(['ctrl+v', 'cmd + v'], () => {
175
+ pasteCell(graph.value)
176
+ })
177
+ graph.value.bindKey(['ctrl+z', 'cmd + z'], () => {
178
+ undoCell(graph.value)
179
+ })
180
+ graph.value.bindKey(['ctrl+y', 'cmd + y'], () => {
181
+ redoCell(graph.value)
182
+ })
183
+
184
+ let setZIndexTimer = null
185
+
186
+ graph.value.on('edge:click', ({cell}) => {
187
+ payload.value = cell
188
+ controls.component = edgeControl
189
+ controls.label = '线段属性'
190
+ controlsKey.value = setMathRandom()
191
+ })
192
+
193
+ //给线添加拐点
194
+ graph.value.on('edge:mouseenter', ({cell}) => {
195
+ clearTimeout(setZIndexTimer)
196
+ cell.toFront()
197
+ addLineTools(cell)
198
+ })
199
+
200
+ // 隐藏线段的拐点
201
+ graph.value.on('edge:mouseleave', ({cell}) => {
202
+ setZIndexTimer = setTimeout(() => {
203
+ cell.toBack()
204
+ cell.removeTools()
205
+ }, 300)
206
+
207
+ })
208
+
209
+ //连接桩显示
210
+ graph.value.on('node:mouseenter', () => {
211
+ showPorts('#drawing-board', true)
212
+ })
213
+
214
+ //连接桩隐藏
215
+ graph.value.on('node:mouseleave', () => {
216
+ showPorts("#drawing-board", false)
217
+ })
218
+
219
+ // 导出插件
220
+ graph.value.use(new Export())
221
+
222
+ //选中画布
223
+ graph.value.on('blank:click', ({e, x, y}) => {
224
+ payload.value = graph
225
+ controls.component = GraphControl
226
+ controls.label = '画布属性'
227
+ controlsKey.value = setMathRandom()
228
+ })
229
+
230
+
231
+ //选中节点
232
+ graph.value.on('cell:click', ({cell}) => {
233
+ payload.value = cell
234
+ controlsKey.value = setMathRandom()
235
+ if (cell.data?.type === 'text') {
236
+ controls.component = textControl
237
+ controls.label = '文本属性'
238
+ return
239
+ }
240
+ if (cell.data?.type === 'base-shape') {
241
+ controls.component = textControl
242
+ controls.label = '基础图形属性'
243
+ return
244
+ }
245
+ if (cell?.shape === 'edge') {
246
+ controls.component = edgeControl
247
+ controls.label = '连线属性'
248
+ return
249
+ }
250
+ if (cell?.shape === 'image') {
251
+ controls.component = imageControl
252
+ controls.label = '图片属性'
253
+ return
254
+ }
255
+ controls.component = null
256
+ controls.label = null
257
+ })
258
+
259
+ graph.value.on('render:done', () => {
260
+ showPorts("#drawing-board", false)
261
+ adaptive()
262
+ })
263
+ }
264
+
265
+ const stencil = ref()
266
+
267
+ /**
268
+ * 初始化模型列表
269
+ */
270
+ const initStencil = () => {
271
+ stencil.value = new Stencil({
272
+ target: graph.value,
273
+ ...stencilOptions,
274
+ groups: [
275
+ {
276
+ name: '基础组件',
277
+ graphPadding: 30
278
+ },
279
+ ],
280
+ search(cell, keyword) {
281
+ return cell.label?.indexOf(keyword) !== -1
282
+ },
283
+ placeholder: '搜索',
284
+ notFoundText: '未找到匹配项',
285
+ getDragNode: (node) => {
286
+ const copyNode = node.clone({keepId: true})
287
+ if (copyNode.label !== '文本') {
288
+ copyNode.label = ''
289
+ }
290
+ return copyNode
291
+ },
292
+ })
293
+ const text = graph.value.createNode({
294
+ ...textOptions
295
+ })
296
+ const baseShapes = baseShape.map(item => {
297
+ item.ports = portsOptions
298
+ return graph.value.createNode(item)
299
+ })
300
+ stencil.value.load([text, ...baseShapes], '基础组件')
301
+ get('/custom/groupList').then(res => {
302
+ const data = res?.data || []
303
+ data?.forEach(group => {
304
+ stencil.value.addGroup({
305
+ name: group?.groupName,
306
+ graphPadding: 30
307
+ })
308
+ const image = group?.pictureList.map(item => {
309
+ const nodeOption = {
310
+ shape: 'image',
311
+ imageUrl: isSunOs ? item?.imageUrl : setSunCloudImageUrl(item?.imageUrl),
312
+ ports: portsOptions,
313
+ width: 64,
314
+ height: 64,
315
+ label: item.imageName
316
+ }
317
+ return graph.value.createNode(nodeOption)
318
+ })
319
+ stencil.value.load(image, group?.groupName)
320
+ document.getElementById('stencil').appendChild(stencil.value.container)
321
+ })
322
+ }).finally(() => {
323
+ setTimeout(() => {
324
+ const container = stencil.value.container;
325
+ const nodes = container.querySelectorAll('.x6-node');
326
+ const baseLength = baseShape.length;
327
+
328
+ // 使用事件委托优化事件监听
329
+ container.addEventListener('mouseenter', handleMouseEnter, true);
330
+ container.addEventListener('mouseleave', handleMouseLeave, true);
331
+
332
+ nodes.forEach((node, index) => {
333
+ const isCustomNode = index > baseLength;
334
+ const text = node.querySelector('text');
335
+
336
+ if (isCustomNode && text) {
337
+ Object.assign(text.style, {
338
+ display: 'none',
339
+ fontSize: '12px',
340
+ fill: '#ffffff'
341
+ });
342
+ text.setAttribute('x', '0%');
343
+ text.setAttribute('y', '40');
344
+ node.style.position = 'relative';
345
+ node.style.cursor = 'pointer';
346
+ }
347
+ });
348
+
349
+ function handleMouseEnter(e) {
350
+ const node = e.target.closest('.x6-node');
351
+ if (node) {
352
+ const index = Array.from(nodes).indexOf(node);
353
+ const text = node.querySelector('text');
354
+ if (index > baseLength && text) {
355
+ text.style.display = 'block';
356
+ }
357
+ }
358
+ }
359
+
360
+ function handleMouseLeave(e) {
361
+ const node = e.target.closest('.x6-node');
362
+ if (node) {
363
+ const index = Array.from(nodes).indexOf(node);
364
+ const text = node.querySelector('text');
365
+ if (index > baseLength && text) {
366
+ text.style.display = 'none';
367
+ }
368
+ }
369
+ }
370
+
371
+ // 添加清理事件监听的方法
372
+ return () => {
373
+ container.removeEventListener('mouseenter', handleMouseEnter, true);
374
+ container.removeEventListener('mouseleave', handleMouseLeave, true);
375
+ };
376
+ }, 200);
377
+ })
378
+ }
379
+
380
+ /**
381
+ * 命令
382
+ * @param command
383
+ */
384
+ const handleCommandSetting = (command) => {
385
+ switch (command) {
386
+ case 'snapLine':
387
+ if (graph.value.isSnaplineEnabled()) {
388
+ graph.value.disableSnapline()
389
+ ElMessage.warning('关闭对齐线')
390
+ } else {
391
+ graph.value.enableSnapline()
392
+ ElMessage.success('开启对齐线')
393
+ }
394
+ break
395
+ case 'select':
396
+ if (graph.value.isSelectionEnabled()) {
397
+ graph.value.disableSelection()
398
+ ElMessage.warning('关闭框选')
399
+ } else {
400
+ graph.value.enableSelection()
401
+ ElMessage.success('开启框选')
402
+ }
403
+ break
404
+ case 'drawLine':
405
+ drawLine()
406
+ break
407
+ case 'adaptive':
408
+ adaptive()
409
+ break
410
+ case 'shortcuts':
411
+ showShortcuts()
412
+ break
413
+ case 'image':
414
+ showImageManagement()
415
+ break
416
+ case 'clear':
417
+ clear()
418
+ break
419
+ case 'export':
420
+ exportGraph()
421
+ break
422
+ }
423
+ }
424
+
425
+ const drawLineSwitch = ref(false) // 绘制直线开关
426
+ const edge = ref(null) // 边
427
+
428
+ /**
429
+ * 绘制直线
430
+ */
431
+ const drawLine = () => {
432
+ drawLineSwitch.value = true
433
+ const time = new Date()
434
+ graph.value.startBatch(time)
435
+ graph.value.disablePanning()
436
+ graph.value.once('blank:mousedown', ({e, x, y}) => {
437
+ if (drawLineSwitch.value) {
438
+ edge.value = graph.value.addEdge({
439
+ source: {x: x, y: y},
440
+ target: {x: x, y: y},
441
+ ...lineOptions,
442
+ data: {
443
+ type: isLineOrPipe(),
444
+ isConnector: false,
445
+ },
446
+ zIndex: 0,
447
+ })
448
+ }
449
+ })
450
+
451
+ graph.value.on('blank:mousemove', ({e, x, y}) => {
452
+ if (drawLineSwitch.value) {
453
+ edge.value.setTarget({x: x, y: y})
454
+ }
455
+ })
456
+
457
+ graph.value.on('blank:mouseup', ({e, x, y}) => {
458
+ drawLineSwitch.value = false
459
+ graph.value.enablePanning()
460
+ graph.value.stopBatch(time)
461
+ })
462
+ }
463
+
464
+ /**
465
+ * 适配画布
466
+ */
467
+ const adaptive = () => {
468
+ graph.value.zoomToFit({maxScale: 1, padding: 20})
469
+ }
470
+
471
+ const dialog = reactive({show: false}) // 弹窗
472
+
473
+ /**
474
+ * 显示快捷键
475
+ */
476
+ const showShortcuts = () => {
477
+ dialog.show = true
478
+ }
479
+
480
+ // 图片管理
481
+ const imageManagement = reactive({
482
+ show: false,
483
+ })
484
+
485
+ /**
486
+ * 显示图片管理
487
+ */
488
+ const showImageManagement = () => {
489
+ imageManagement.show = true
490
+ }
491
+
492
+ /**
493
+ * 清空画布
494
+ */
495
+ const clear = () => {
496
+ graph.value.clearCells()
497
+ graph.value.drawBackground(backgroundOptions)
498
+ graph.value.drawGrid(gridOptions)
499
+ ElMessage.success('清空成功')
500
+ }
501
+
502
+ /**
503
+ * 导出画布
504
+ */
505
+ const exportGraph = () => {
506
+ const fileName = moment().format('YYYYMMDDHHmmss')
507
+ graph.value.exportPNG(fileName, {
508
+ padding: 10,
509
+ quality: 1,
510
+ backgroundColor: graph.value.options.background.color,
511
+ })
512
+ }
513
+
514
+ // 暴露给父组件
515
+ const emits = defineEmits(['getJson'])
516
+
517
+ const view = reactive({
518
+ show: false,
519
+ background: null,
520
+ grid: null,
521
+ data: null
522
+ })
523
+
524
+ /**
525
+ * 查看画布数据
526
+ */
527
+ const viewJson = () => {
528
+ view.background = graph.value.options.background
529
+ view.grid = graph.value.options.grid
530
+ view.data = graph.value.toJSON()
531
+ view.show = true
532
+ }
533
+
534
+ const closeViewBtnClick = () => {
535
+ view.show = false
536
+ }
537
+
538
+ /**
539
+ * 获取画布数据
540
+ */
541
+ const getJson = () => {
542
+ emits('getJson', {
543
+ background: graph.value.options.background,
544
+ grid: graph.value.options.grid,
545
+ data: graph.value.toJSON()
546
+ })
547
+ }
548
+
549
+ /**
550
+ * 渲染数据
551
+ */
552
+ const renderData = async () => {
553
+ if (!props.id) {
554
+ graph.value.drawBackground(backgroundOptions)
555
+ graph.value.drawGrid(gridOptions)
556
+ graph.value.fromJSON({cells: []})
557
+ return
558
+ }
559
+ const data = await getData(props.id)
560
+ graph.value.drawBackground(data?.background || backgroundOptions)
561
+ graph.value.drawGrid(data?.grid || gridOptions )
562
+ graph.value.fromJSON(data?.data || {cells: []} )
563
+ }
564
+
565
+ const drawBoard = shallowRef(null)
566
+
567
+ const watchBrowser = () => {
568
+ const observer = new ResizeObserver(() => {
569
+ adaptive()
570
+ })
571
+ observer.observe(drawBoard.value)
572
+ }
573
+
574
+ onMounted(() => {
575
+ initGraph()
576
+ initStencil()
577
+ renderData()
578
+ watchBrowser()
579
+
580
+ })
581
+
582
+ </script>
583
+
584
+ <style lang="scss" scoped>
585
+ @use "../../styles/editor";
586
+ </style>
587
+
588
+ <style lang="scss">
589
+ @use "../../styles/animation.scss";
590
+ </style>