workflow-bpmn-modeler-andtv-vue3 10.1.1 → 10.2.0

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 (52) hide show
  1. package/dist/fonts/bpmn.5d33bee4.eot +0 -0
  2. package/dist/fonts/bpmn.67058807.woff2 +0 -0
  3. package/dist/fonts/bpmn.b5c9250d.ttf +0 -0
  4. package/dist/fonts/bpmn.e9e7d076.woff +0 -0
  5. package/dist/img/bpmn.74eea12b.svg +224 -0
  6. package/dist/workflow-bpmn-modeler-andtv-vue3.common.js +199 -5
  7. package/dist/workflow-bpmn-modeler-andtv-vue3.umd.js +199 -5
  8. package/dist/workflow-bpmn-modeler-andtv-vue3.umd.min.js +199 -5
  9. package/package/CHANGELOG.md +6 -0
  10. package/package/LICENSE +21 -0
  11. package/package/README.md +208 -0
  12. package/package/components/nodePanel/process.vue +9 -2
  13. package/package/components/nodePanel/startEnd.vue +6 -2
  14. package/package/dist/demo.html +9 -0
  15. package/package/dist/fonts/bpmn.5d33bee4.eot +0 -0
  16. package/package/dist/fonts/bpmn.67058807.woff2 +0 -0
  17. package/package/dist/fonts/bpmn.b5c9250d.ttf +0 -0
  18. package/package/dist/fonts/bpmn.e9e7d076.woff +0 -0
  19. package/package/dist/img/bpmn.74eea12b.svg +224 -0
  20. package/package/dist/workflow-bpmn-modeler-andtv-vue3.common.js +5617 -0
  21. package/package/dist/workflow-bpmn-modeler-andtv-vue3.umd.js +5617 -0
  22. package/package/dist/workflow-bpmn-modeler-andtv-vue3.umd.min.js +5617 -0
  23. package/package/index.vue +91 -427
  24. package/package/package/BpmData.js +68 -0
  25. package/package/package/PropertyPanel.vue +342 -0
  26. package/package/package/common/customTranslate.js +20 -0
  27. package/package/package/common/mixinExecutionListener.js +24 -0
  28. package/package/package/common/mixinPanel.js +70 -0
  29. package/package/package/common/mixinXcrud.js +22 -0
  30. package/package/package/common/parseElement.js +53 -0
  31. package/package/package/components/custom/customContextPad.vue +24 -0
  32. package/package/package/components/nodePanel/gateway.vue +165 -0
  33. package/package/package/components/nodePanel/process.vue +201 -0
  34. package/package/package/components/nodePanel/property/executionListener.vue +240 -0
  35. package/package/package/components/nodePanel/property/listenerParam.vue +137 -0
  36. package/package/package/components/nodePanel/property/multiInstance.vue +177 -0
  37. package/package/package/components/nodePanel/property/signal.vue +178 -0
  38. package/package/package/components/nodePanel/property/taskListener.vue +242 -0
  39. package/package/package/components/nodePanel/sequenceFlow.vue +180 -0
  40. package/package/package/components/nodePanel/startEnd.vue +174 -0
  41. package/package/package/components/nodePanel/task.vue +452 -0
  42. package/package/package/flowable/flowable.json +1218 -0
  43. package/package/package/flowable/init.js +24 -0
  44. package/package/package/flowable/showConfig.js +51 -0
  45. package/package/package/index.js +9 -0
  46. package/package/package/index.ts +10 -0
  47. package/package/package/index.vue +730 -0
  48. package/package/package/lang/zh.js +227 -0
  49. package/package/package/types/components.d.ts +35 -0
  50. package/package/package.json +89 -0
  51. package/package.json +12 -13
  52. package/package/bpmn-styles.css +0 -40
@@ -0,0 +1,730 @@
1
+ <template>
2
+ <div v-loading="isView" class="flow-containers" :class="{ 'view-mode': isView }">
3
+ <a-layout class="main-layout">
4
+ <!-- 顶部工具栏 -->
5
+ <a-layout-header class="toolbar-header">
6
+ <div class="toolbar-container">
7
+ <!-- 左侧工具组 -->
8
+ <div class="toolbar-left">
9
+ <div class="toolbar-group">
10
+ <span class="group-label">文件操作</span>
11
+ <a-upload :before-upload="openBpmn" class="toolbar-item">
12
+ <a-tooltip title="加载XML文件" placement="bottom">
13
+ <a-button class="toolbar-btn" size="small">
14
+ <template #icon><FolderOpenOutlined /></template>
15
+ </a-button>
16
+ </a-tooltip>
17
+ </a-upload>
18
+ <a-tooltip title="新建流程图" placement="bottom">
19
+ <a-button class="toolbar-btn" size="small" @click="newDiagram">
20
+ <template #icon><PlusCircleOutlined /></template>
21
+ </a-button>
22
+ </a-tooltip>
23
+ </div>
24
+
25
+ <div class="toolbar-group">
26
+ <span class="group-label">视图控制</span>
27
+ <a-tooltip title="自适应屏幕" placement="bottom">
28
+ <a-button class="toolbar-btn" size="small" @click="fitViewport">
29
+ <template #icon><FullscreenOutlined /></template>
30
+ </a-button>
31
+ </a-tooltip>
32
+ <a-tooltip title="放大视图" placement="bottom">
33
+ <a-button class="toolbar-btn" size="small" @click="zoomViewport(true)">
34
+ <template #icon><ZoomInOutlined /></template>
35
+ </a-button>
36
+ </a-tooltip>
37
+ <a-tooltip title="缩小视图" placement="bottom">
38
+ <a-button class="toolbar-btn" size="small" @click="zoomViewport(false)">
39
+ <template #icon><ZoomOutOutlined /></template>
40
+ </a-button>
41
+ </a-tooltip>
42
+ </div>
43
+
44
+ <div class="toolbar-group">
45
+ <span class="group-label">历史操作</span>
46
+ <a-tooltip title="撤销操作" placement="bottom">
47
+ <a-button class="toolbar-btn" size="small" @click="() => modeler?.get('commandStack').undo()">
48
+ <template #icon><UndoOutlined /></template>
49
+ </a-button>
50
+ </a-tooltip>
51
+ <a-tooltip title="重做操作" placement="bottom">
52
+ <a-button class="toolbar-btn" size="small" @click="() => modeler?.get('commandStack').redo()">
53
+ <template #icon><RedoOutlined /></template>
54
+ </a-button>
55
+ </a-tooltip>
56
+ </div>
57
+ </div>
58
+
59
+ <!-- 右侧操作组 -->
60
+ <div class="toolbar-right">
61
+ <div class="toolbar-group">
62
+ <span class="group-label">导出</span>
63
+ <a-button class="toolbar-btn export-btn" size="small" @click="saveXML(true)">
64
+ <template #icon><DownloadOutlined /></template>
65
+ 下载XML
66
+ </a-button>
67
+ <a-button class="toolbar-btn export-btn" size="small" @click="saveImg('svg', true)">
68
+ <template #icon><PictureOutlined /></template>
69
+ 下载SVG
70
+ </a-button>
71
+ </div>
72
+ <a-button class="save-btn" type="primary" @click="save">
73
+ <template #icon><DownloadOutlined /></template>
74
+ 保存模型
75
+ </a-button>
76
+ </div>
77
+ </div>
78
+ </a-layout-header>
79
+
80
+ <!-- 主内容区域 -->
81
+ <a-layout class="content-layout">
82
+ <a-layout-content class="canvas-container">
83
+ <div ref="canvas" class="canvas" />
84
+ </a-layout-content>
85
+ <a-layout-sider class="property-panel">
86
+ <PropertyPanel v-if="modeler" :modeler="modeler" :users="users" :groups="groups" :categorys="categorys" />
87
+ </a-layout-sider>
88
+ </a-layout>
89
+ </a-layout>
90
+ </div>
91
+ </template>
92
+
93
+ <script setup lang="ts">
94
+ import { ref, watch, onMounted, nextTick } from 'vue'
95
+
96
+ defineOptions({
97
+ name: 'WorkflowBpmnModeler'
98
+ })
99
+ // 汉化
100
+ import customTranslate from './common/customTranslate'
101
+ import Modeler from 'bpmn-js/lib/Modeler'
102
+ import PropertyPanel from './PropertyPanel.vue'
103
+ import BpmData from './BpmData'
104
+ import getInitStr from './flowable/init'
105
+ // 引入flowable的节点文件
106
+ import flowableModdle from './flowable/flowable.json'
107
+ // 引入图标
108
+ import {
109
+ FolderOpenOutlined,
110
+ PlusCircleOutlined,
111
+ FullscreenOutlined,
112
+ ZoomInOutlined,
113
+ ZoomOutOutlined,
114
+ UndoOutlined,
115
+ RedoOutlined,
116
+ DownloadOutlined,
117
+ PictureOutlined
118
+ } from '@ant-design/icons-vue'
119
+
120
+ interface User {
121
+ name: string
122
+ id: string
123
+ }
124
+
125
+ interface Group {
126
+ name: string
127
+ id: string
128
+ }
129
+
130
+ interface Category {
131
+ name: string
132
+ id: string
133
+ }
134
+
135
+ interface SaveData {
136
+ process: any
137
+ xml: string | undefined
138
+ svg: string | undefined
139
+ }
140
+
141
+ interface Props {
142
+ xml?: string
143
+ users?: User[]
144
+ groups?: Group[]
145
+ categorys?: Category[]
146
+ isView?: boolean
147
+ }
148
+
149
+ const props = withDefaults(defineProps<Props>(), {
150
+ xml: '',
151
+ users: () => [],
152
+ groups: () => [],
153
+ categorys: () => [],
154
+ isView: false
155
+ })
156
+
157
+ const emit = defineEmits<{
158
+ save: [data: SaveData]
159
+ }>()
160
+ const canvas = ref<HTMLElement | null>(null)
161
+ const modeler = ref<Modeler | null>(null)
162
+ const taskList = ref<any[]>([])
163
+ const zoom = ref<number>(1)
164
+
165
+ watch(() => props.xml, (val: string) => {
166
+ if (val) {
167
+ createNewDiagram(val)
168
+ }
169
+ })
170
+
171
+ onMounted(() => {
172
+ // 生成实例
173
+ modeler.value = new Modeler({
174
+ container: canvas.value,
175
+ additionalModules: [
176
+ {
177
+ translate: ['value', customTranslate]
178
+ }
179
+ ],
180
+ moddleExtensions: {
181
+ flowable: flowableModdle
182
+ }
183
+ })
184
+ // 新增流程定义
185
+ if (!props.xml) {
186
+ newDiagram()
187
+ } else {
188
+ createNewDiagram(props.xml)
189
+ }
190
+ })
191
+
192
+ const newDiagram = (): void => {
193
+ createNewDiagram(getInitStr())
194
+ }
195
+
196
+ // 让图能自适应屏幕
197
+ const fitViewport = (): void => {
198
+ if (!modeler.value) return
199
+ zoom.value = modeler.value.get('canvas').zoom('fit-viewport')
200
+ const viewport = document.querySelector('.flow-containers .viewport') as SVGSVGElement
201
+ const bbox = viewport?.getBBox()
202
+ if (!bbox) return
203
+ const currentViewbox = modeler.value.get('canvas').viewbox()
204
+ const elementMid = {
205
+ x: bbox.x + bbox.width / 2 - 65,
206
+ y: bbox.y + bbox.height / 2
207
+ }
208
+ modeler.value.get('canvas').viewbox({
209
+ x: elementMid.x - currentViewbox.width / 2,
210
+ y: elementMid.y - currentViewbox.height / 2,
211
+ width: currentViewbox.width,
212
+ height: currentViewbox.height
213
+ })
214
+ zoom.value = bbox.width / currentViewbox.width * 1.8
215
+ }
216
+
217
+ // 放大缩小
218
+ const zoomViewport = (zoomIn: boolean = true): void => {
219
+ if (!modeler.value) return
220
+ zoom.value = modeler.value.get('canvas').zoom()
221
+ zoom.value += (zoomIn ? 0.1 : -0.1)
222
+ modeler.value.get('canvas').zoom(zoom.value)
223
+ }
224
+
225
+ const createNewDiagram = async (data: string): Promise<void> => {
226
+ // 将字符串转换成图显示出来
227
+ // data = data.replace(/<!\[CDATA\[(.+?)]]>/g, '&lt;![CDATA[$1]]&gt;')
228
+ data = data.replace(/<!\[CDATA\[(.+?)]]>/g, function(match: string, str: string) {
229
+ return str.replace(/</g, '&lt;')
230
+ })
231
+ try {
232
+ if (!modeler.value) return
233
+ await modeler.value.importXML(data)
234
+ adjustPalette()
235
+ fitViewport()
236
+ // fillColor()
237
+ } catch (err: any) {
238
+ console.error(err.message, err.warnings)
239
+ }
240
+ }
241
+
242
+ // 调整左侧工具栏排版
243
+ const adjustPalette = (): void => {
244
+ try {
245
+ // 获取 bpmn 设计器实例
246
+ const canvasElement = canvas.value
247
+ if (!canvasElement) return
248
+ const djsPalette = canvasElement.children[0].children[1].children[4] as HTMLElement
249
+ const djsPalStyle: Record<string, string> = {
250
+ width: '130px',
251
+ padding: '5px',
252
+ background: 'white',
253
+ left: '20px',
254
+ borderRadius: '0'
255
+ }
256
+ for (const key in djsPalStyle) {
257
+ (djsPalette.style as any)[key] = djsPalStyle[key]
258
+ }
259
+ const palette = djsPalette.children[0] as HTMLElement
260
+ const allGroups = palette.children
261
+ ;(allGroups[0] as HTMLElement).style.display = 'none'
262
+ // 修改控件样式
263
+ for (let gKey = 0; gKey < allGroups.length; gKey++) {
264
+ const group = allGroups[gKey] as HTMLElement
265
+ for (let cKey = 0; cKey < group.children.length; cKey++) {
266
+ const control = group.children[cKey] as HTMLElement
267
+ const controlStyle: Record<string, string> = {
268
+ display: 'flex',
269
+ justifyContent: 'flex-start',
270
+ alignItems: 'center',
271
+ width: '100%',
272
+ padding: '5px'
273
+ }
274
+ if (
275
+ control.className &&
276
+ control.dataset &&
277
+ control.className.indexOf('entry') !== -1
278
+ ) {
279
+ const controlProps = new BpmData().getControl(
280
+ control.dataset.action
281
+ )
282
+ control.innerHTML = `<div style='font-size: 14px;font-weight:500;margin-left:15px;'>${
283
+ controlProps['title']
284
+ }</div>`
285
+ for (const csKey in controlStyle) {
286
+ (control.style as any)[csKey] = controlStyle[csKey]
287
+ }
288
+ }
289
+ }
290
+ }
291
+ } catch (e) {
292
+ console.log(e)
293
+ }
294
+ }
295
+ const fillColor = (): void => {
296
+ if (!modeler.value) return
297
+ const canvasElement = modeler.value.get('canvas')
298
+ (modeler.value as any)._definitions.rootElements[0].flowElements.forEach((n: any) => {
299
+ if (n.$type === 'bpmn:UserTask') {
300
+ const completeTask = taskList.value.find(m => m.key === n.id) || { completed: true }
301
+ const todoTask = taskList.value.find(m => !m.completed)
302
+ const endTask = taskList.value[taskList.value.length - 1]
303
+ if (completeTask) {
304
+ canvasElement.addMarker(n.id, completeTask.completed ? 'highlight' : 'highlight-todo')
305
+ n.outgoing?.forEach((nn: any) => {
306
+ const targetTask = taskList.value.find(m => m.key === nn.targetRef.id)
307
+ if (targetTask) {
308
+ canvasElement.addMarker(nn.id, targetTask.completed ? 'highlight' : 'highlight-todo')
309
+ } else if (nn.targetRef.$type === 'bpmn:ExclusiveGateway') {
310
+ // canvasElement.addMarker(nn.id, 'highlight');
311
+ canvasElement.addMarker(nn.id, completeTask.completed ? 'highlight' : 'highlight-todo')
312
+ canvasElement.addMarker(nn.targetRef.id, completeTask.completed ? 'highlight' : 'highlight-todo')
313
+ } else if (nn.targetRef.$type === 'bpmn:EndEvent') {
314
+ if (!todoTask && endTask.key === n.id) {
315
+ canvasElement.addMarker(nn.id, 'highlight')
316
+ canvasElement.addMarker(nn.targetRef.id, 'highlight')
317
+ }
318
+ if (!completeTask.completed) {
319
+ canvasElement.addMarker(nn.id, 'highlight-todo')
320
+ canvasElement.addMarker(nn.targetRef.id, 'highlight-todo')
321
+ }
322
+ }
323
+ })
324
+ }
325
+ } else if (n.$type === 'bpmn:ExclusiveGateway') {
326
+ n.outgoing.forEach((nn: any) => {
327
+ const targetTask = taskList.value.find(m => m.key === nn.targetRef.id)
328
+ if (targetTask) {
329
+ canvasElement.addMarker(nn.id, targetTask.completed ? 'highlight' : 'highlight-todo')
330
+ }
331
+ })
332
+ }
333
+ if (n.$type === 'bpmn:StartEvent') {
334
+ n.outgoing.forEach((nn: any) => {
335
+ const completeTask = taskList.value.find(m => m.key === nn.targetRef.id)
336
+ if (completeTask) {
337
+ canvasElement.addMarker(nn.id, 'highlight')
338
+ canvasElement.addMarker(n.id, 'highlight')
339
+ return
340
+ }
341
+ })
342
+ }
343
+ })
344
+ }
345
+
346
+ // 对外 api
347
+ const getProcess = (): any => {
348
+ const element = getProcessElement()
349
+ return {
350
+ id: element.id,
351
+ name: element.name,
352
+ category: element.$attrs['flowable:processCategory']
353
+ }
354
+ }
355
+
356
+ const getProcessElement = (): any => {
357
+ if (!modeler.value) return null
358
+ const rootElements = modeler.value.getDefinitions().rootElements
359
+ for (let i = 0; i < rootElements.length; i++) {
360
+ if (rootElements[i].$type === 'bpmn:Process') return rootElements[i]
361
+ }
362
+ }
363
+
364
+ const saveXML = async (download: boolean = false): Promise<string | undefined> => {
365
+ try {
366
+ if (!modeler.value) return
367
+ const { xml } = await modeler.value.saveXML({ format: true })
368
+ if (download) {
369
+ const processElement = getProcessElement()
370
+ if (processElement) {
371
+ downloadFile(`${processElement.name}.bpmn20.xml`, xml, 'application/xml')
372
+ }
373
+ }
374
+ return xml
375
+ } catch (err) {
376
+ console.log(err)
377
+ }
378
+ }
379
+
380
+ const saveImg = async (type: string = 'svg', download: boolean = false): Promise<string | undefined> => {
381
+ try {
382
+ if (!modeler.value) return
383
+ const { svg } = await modeler.value.saveSVG({ format: true })
384
+ if (download) {
385
+ const processElement = getProcessElement()
386
+ if (processElement) {
387
+ downloadFile(processElement.name, svg, 'image/svg+xml')
388
+ }
389
+ }
390
+ return svg
391
+ } catch (err) {
392
+ console.log(err)
393
+ }
394
+ }
395
+
396
+ const save = async (): Promise<void> => {
397
+ const process = getProcess()
398
+ const xml = await saveXML()
399
+ const svg = await saveImg()
400
+ const result = { process, xml, svg }
401
+ emit('save', result)
402
+ window.parent.postMessage(result, '*')
403
+ }
404
+
405
+ const openBpmn = (file: File): boolean => {
406
+ const reader = new FileReader()
407
+ reader.readAsText(file, 'utf-8')
408
+ reader.onload = () => {
409
+ createNewDiagram(reader.result as string)
410
+ }
411
+ return false
412
+ }
413
+
414
+ const downloadFile = (filename: string, data: string, type: string): void => {
415
+ const a = document.createElement('a')
416
+ const url = window.URL.createObjectURL(new Blob([data], { type: type }))
417
+ a.href = url
418
+ a.download = filename
419
+ a.click()
420
+ window.URL.revokeObjectURL(url)
421
+ }
422
+
423
+ </script>
424
+
425
+ <style lang="scss">
426
+ /* BPMN.js 样式导入 */
427
+ @import "~bpmn-js/dist/assets/diagram-js.css";
428
+ @import "~bpmn-js/dist/assets/bpmn-font/css/bpmn.css";
429
+ @import "~bpmn-js/dist/assets/bpmn-font/css/bpmn-codes.css";
430
+ @import "~bpmn-js/dist/assets/bpmn-font/css/bpmn-embedded.css";
431
+
432
+ /* 视图模式样式 */
433
+ .view-mode {
434
+ .ant-layout-header,
435
+ .ant-layout-sider,
436
+ .djs-palette,
437
+ .bjs-powered-by {
438
+ display: none;
439
+ }
440
+ .ant-spin-mask {
441
+ background-color: initial;
442
+ }
443
+ .ant-spin-spinning {
444
+ display: none;
445
+ }
446
+ }
447
+
448
+ /* 主容器样式 */
449
+ .flow-containers {
450
+ width: 100%;
451
+ height: 100vh;
452
+ background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
453
+ overflow: hidden;
454
+
455
+ /* 主布局 */
456
+ .main-layout {
457
+ height: 100%;
458
+ background: transparent;
459
+ }
460
+
461
+ /* 工具栏头部 */
462
+ .toolbar-header {
463
+ background: #f8f9fa;
464
+ border-bottom: 1px solid #e9ecef;
465
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
466
+ padding: 0;
467
+ height: 64px;
468
+ line-height: 64px;
469
+
470
+ .toolbar-container {
471
+ display: flex;
472
+ justify-content: space-between;
473
+ align-items: center;
474
+ padding: 0 32px;
475
+ height: 100%;
476
+ }
477
+
478
+ .toolbar-left {
479
+ display: flex;
480
+ align-items: center;
481
+ gap: 20px;
482
+ }
483
+
484
+ .toolbar-right {
485
+ display: flex;
486
+ align-items: center;
487
+ gap: 16px;
488
+ }
489
+
490
+ .toolbar-group {
491
+ display: flex;
492
+ align-items: center;
493
+ gap: 8px;
494
+ padding: 0;
495
+ min-width: fit-content;
496
+
497
+ .group-label {
498
+ color: #6c757d;
499
+ font-size: 12px;
500
+ font-weight: 600;
501
+ margin-right: 8px;
502
+ white-space: nowrap;
503
+ }
504
+ }
505
+
506
+ .toolbar-btn {
507
+ background: #ffffff;
508
+ border: 1px solid #e9ecef;
509
+ color: #495057;
510
+ border-radius: 6px;
511
+ transition: all 0.3s ease;
512
+ height: 36px;
513
+ padding: 0 12px;
514
+ font-size: 13px;
515
+ font-weight: 500;
516
+ min-width: 36px;
517
+ display: flex;
518
+ align-items: center;
519
+ justify-content: center;
520
+
521
+ &:hover {
522
+ background: #f8f9fa;
523
+ border-color: #dee2e6;
524
+ color: #212529;
525
+ transform: translateY(-1px);
526
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
527
+ }
528
+
529
+ &:active {
530
+ transform: translateY(0);
531
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
532
+ }
533
+ }
534
+
535
+ .export-btn {
536
+ background: #f8f9fa;
537
+ border: 1px solid #e9ecef;
538
+ color: #6c757d;
539
+
540
+ &:hover {
541
+ background: #e9ecef;
542
+ border-color: #dee2e6;
543
+ color: #495057;
544
+ }
545
+ }
546
+
547
+ .save-btn {
548
+ background: #007bff;
549
+ border: 1px solid #007bff;
550
+ border-radius: 6px;
551
+ font-weight: 600;
552
+ height: 36px;
553
+ padding: 0 20px;
554
+ font-size: 13px;
555
+ color: white;
556
+ min-width: 120px;
557
+
558
+ &:hover {
559
+ background: #0056b3;
560
+ border-color: #0056b3;
561
+ transform: translateY(-1px);
562
+ box-shadow: 0 2px 8px rgba(0, 123, 255, 0.3);
563
+ }
564
+
565
+ &:active {
566
+ transform: translateY(0);
567
+ box-shadow: 0 1px 3px rgba(0, 123, 255, 0.3);
568
+ }
569
+ }
570
+ }
571
+
572
+ /* 内容布局 */
573
+ .content-layout {
574
+ height: calc(100vh - 64px);
575
+ background: transparent;
576
+ }
577
+
578
+ /* 画布容器 */
579
+ .canvas-container {
580
+ background: white;
581
+ border-radius: 8px;
582
+ margin: 16px;
583
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
584
+ overflow: hidden;
585
+ position: relative;
586
+
587
+ &::before {
588
+ content: '';
589
+ position: absolute;
590
+ top: 0;
591
+ left: 0;
592
+ right: 0;
593
+ bottom: 0;
594
+ background: linear-gradient(45deg, #f8f9fa 25%, transparent 25%),
595
+ linear-gradient(-45deg, #f8f9fa 25%, transparent 25%),
596
+ linear-gradient(45deg, transparent 75%, #f8f9fa 75%),
597
+ linear-gradient(-45deg, transparent 75%, #f8f9fa 75%);
598
+ background-size: 20px 20px;
599
+ background-position: 0 0, 0 10px, 10px -10px, -10px 0px;
600
+ opacity: 0.3;
601
+ pointer-events: none;
602
+ }
603
+ }
604
+
605
+ .canvas {
606
+ width: 100%;
607
+ height: 100%;
608
+ position: relative;
609
+ z-index: 1;
610
+ }
611
+
612
+ /* 属性面板 */
613
+ .property-panel {
614
+ width: 380px !important;
615
+ min-width: 380px !important;
616
+ max-width: 380px !important;
617
+ background: white;
618
+ border-radius: 8px;
619
+ margin: 16px 20px 16px 0;
620
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
621
+ overflow: hidden;
622
+ display: flex;
623
+ flex-direction: column;
624
+ height: calc(100vh - 96px);
625
+
626
+ :deep(.property-panel) {
627
+ height: 100%;
628
+ overflow-y: auto;
629
+ overflow-x: hidden;
630
+ }
631
+ }
632
+
633
+ /* BPMN 工具栏样式优化 */
634
+ .djs-palette {
635
+ left: 20px !important;
636
+ top: 20px !important;
637
+ border: none !important;
638
+ border-radius: 12px !important;
639
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15) !important;
640
+ background: white !important;
641
+ padding: 16px !important;
642
+ width: 160px !important;
643
+ min-width: 160px !important;
644
+
645
+ .djs-palette-entries {
646
+ .djs-palette-entry {
647
+ border-radius: 8px !important;
648
+ margin: 6px 0 !important;
649
+ transition: all 0.3s ease !important;
650
+ width: 100% !important;
651
+ min-height: 40px !important;
652
+ display: flex !important;
653
+ align-items: center !important;
654
+
655
+ &:hover {
656
+ background: linear-gradient(135deg, #e6f7ff 0%, #bae7ff 100%) !important;
657
+ transform: translateY(-1px) !important;
658
+ box-shadow: 0 4px 12px rgba(24, 144, 255, 0.2) !important;
659
+ }
660
+
661
+ .entry {
662
+ padding: 10px 12px !important;
663
+ border-radius: 8px !important;
664
+ font-size: 13px !important;
665
+ font-weight: 500 !important;
666
+ color: #262626 !important;
667
+ width: 100% !important;
668
+ white-space: nowrap !important;
669
+ overflow: hidden !important;
670
+ text-overflow: ellipsis !important;
671
+ display: flex !important;
672
+ align-items: center !important;
673
+ gap: 8px !important;
674
+ }
675
+ }
676
+ }
677
+ }
678
+
679
+ /* BPMN 画布样式 */
680
+ .djs-container svg {
681
+ min-height: 100%;
682
+ background: transparent;
683
+ }
684
+
685
+ /* 表单样式优化 */
686
+ .ant-form-item-label > label {
687
+ font-size: 13px;
688
+ font-weight: 500;
689
+ color: #262626;
690
+ }
691
+
692
+ /* 响应式设计 */
693
+ @media (max-width: 1200px) {
694
+ .property-panel {
695
+ width: 320px !important;
696
+ min-width: 320px !important;
697
+ max-width: 320px !important;
698
+ margin: 16px 16px 16px 0;
699
+ }
700
+
701
+ .djs-palette {
702
+ width: 140px !important;
703
+ min-width: 140px !important;
704
+ }
705
+ }
706
+
707
+ @media (max-width: 768px) {
708
+ .toolbar-container {
709
+ padding: 0 16px;
710
+ }
711
+
712
+ .toolbar-group {
713
+ padding: 6px 12px;
714
+ gap: 6px;
715
+
716
+ .group-label {
717
+ display: none;
718
+ }
719
+ }
720
+
721
+ .property-panel {
722
+ width: 100% !important;
723
+ min-width: 100% !important;
724
+ max-width: 100% !important;
725
+ margin: 0;
726
+ border-radius: 0;
727
+ }
728
+ }
729
+ }
730
+ </style>