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,165 @@
1
+ <template>
2
+ <div>
3
+ <a-form ref="formRef" :model="formData" layout="horizontal">
4
+ <a-form-item label="节点 id" name="id" :rules="[{ required: true, message: 'Id 不能为空' }]">
5
+ <a-input v-model:value="formData.id" />
6
+ </a-form-item>
7
+
8
+ <a-form-item label="节点名称" name="name">
9
+ <a-input v-model:value="formData.name" />
10
+ </a-form-item>
11
+
12
+ <a-form-item label="节点描述" name="documentation">
13
+ <a-textarea v-model:value="formData.documentation" />
14
+ </a-form-item>
15
+
16
+ <a-form-item label="执行监听器">
17
+ <a-badge :count="executionListenerLength">
18
+ <a-button size="small" @click="dialogName = 'executionListenerDialog'">编辑</a-button>
19
+ </a-badge>
20
+ </a-form-item>
21
+
22
+ <a-form-item label="异步" name="async">
23
+ <a-switch v-model:checked="formData.async" checked-children="是" un-checked-children="否" />
24
+ </a-form-item>
25
+ </a-form>
26
+
27
+ <executionListenerDialog
28
+ v-if="dialogName === 'executionListenerDialog'"
29
+ :element="element"
30
+ :modeler="modeler"
31
+ @close="finishExecutionListener"
32
+ />
33
+ </div>
34
+ </template>
35
+
36
+ <script setup lang="ts">
37
+ import { ref, computed, watch, onMounted, nextTick } from 'vue'
38
+ import { FormInstance } from 'ant-design-vue'
39
+ import executionListenerDialog from './property/executionListener.vue'
40
+ import { commonParse } from '../../common/parseElement'
41
+ import showConfigData from '../../flowable/showConfig'
42
+ import Modeler from 'bpmn-js/lib/Modeler'
43
+
44
+ defineOptions({
45
+ name: 'GatewayPanel'
46
+ })
47
+
48
+ interface Props {
49
+ modeler: Modeler
50
+ element: any
51
+ }
52
+
53
+ const props = defineProps<Props>()
54
+
55
+ const formRef = ref<FormInstance>()
56
+ const dialogName = ref('')
57
+ const executionListenerLength = ref(0)
58
+ const formData = ref<Record<string, any>>({})
59
+
60
+ const elementType = computed(() => {
61
+ const bizObj = props.element.businessObject
62
+ return bizObj.eventDefinitions
63
+ ? bizObj.eventDefinitions[0].$type
64
+ : bizObj.$type
65
+ })
66
+
67
+ const showConfig = computed(() => (showConfigData as any)[elementType.value] || {})
68
+
69
+ const updateProperties = (properties: Record<string, any>) => {
70
+ nextTick(() => {
71
+ try {
72
+ const modeling = props.modeler.get('modeling')
73
+ modeling.updateProperties(props.element, properties)
74
+ } catch (error) {
75
+ console.warn('Update properties error:', error)
76
+ }
77
+ })
78
+ }
79
+
80
+ const computedExecutionListenerLength = () => {
81
+ if (props.element?.businessObject?.extensionElements?.values) {
82
+ executionListenerLength.value = props.element.businessObject.extensionElements.values
83
+ .filter((item: any) => item.$type === 'flowable:ExecutionListener').length
84
+ } else {
85
+ executionListenerLength.value = 0
86
+ }
87
+ }
88
+
89
+ const finishExecutionListener = () => {
90
+ if (dialogName.value === 'executionListenerDialog') {
91
+ computedExecutionListenerLength()
92
+ }
93
+ dialogName.value = ''
94
+ }
95
+
96
+ // Watch for form data changes
97
+ watch(() => formData.value.async, (val) => {
98
+ if (val === '') val = null
99
+ updateProperties({ 'flowable:async': val })
100
+ })
101
+
102
+ watch(() => formData.value.id, (val) => {
103
+ updateProperties({ id: val })
104
+ })
105
+
106
+ watch(() => formData.value.name, (val) => {
107
+ updateProperties({ name: val })
108
+ })
109
+
110
+ watch(() => formData.value.documentation, (val) => {
111
+ if (!val) {
112
+ updateProperties({ documentation: [] })
113
+ return
114
+ }
115
+ if (props.modeler?.get('moddle')) {
116
+ const documentationElement = props.modeler.get('moddle').create('bpmn:Documentation', { text: val })
117
+ updateProperties({ documentation: [documentationElement] })
118
+ }
119
+ })
120
+
121
+ onMounted(() => {
122
+ if (props.element) {
123
+ formData.value = commonParse(props.element)
124
+ computedExecutionListenerLength()
125
+ }
126
+ })
127
+ </script>
128
+
129
+ <style scoped>
130
+ .flow-containers {
131
+ padding: 0;
132
+ background: transparent;
133
+ min-height: 100%;
134
+ }
135
+
136
+ .flow-containers :deep(.ant-form-item) {
137
+ margin-bottom: 16px;
138
+ }
139
+
140
+ .flow-containers :deep(.ant-form-item-label) {
141
+ font-weight: 500;
142
+ color: #262626;
143
+ }
144
+
145
+ .flow-containers :deep(.ant-input),
146
+ .flow-containers :deep(.ant-select),
147
+ .flow-containers :deep(.ant-textarea) {
148
+ border-radius: 6px;
149
+ transition: all 0.3s;
150
+ }
151
+
152
+ .flow-containers :deep(.ant-button) {
153
+ border-radius: 6px;
154
+ font-weight: 500;
155
+ }
156
+
157
+ .flow-containers :deep(.ant-badge) {
158
+ margin-right: 8px;
159
+ }
160
+
161
+ .flow-containers :deep(.ant-badge .ant-badge-count) {
162
+ background: linear-gradient(135deg, #ff4d4f 0%, #ff7875 100%);
163
+ box-shadow: 0 2px 4px rgba(255, 77, 79, 0.3);
164
+ }
165
+ </style>
@@ -0,0 +1,201 @@
1
+ <template>
2
+ <div>
3
+ <a-form ref="formRef" :model="formData" layout="horizontal">
4
+ <a-form-item label="流程分类" name="processCategory">
5
+ <a-select v-model:value="formData.processCategory" :options="categoryOptions" />
6
+ </a-form-item>
7
+
8
+ <a-form-item label="流程标识key" name="id" :rules="[{ required: true, message: 'Id 不能为空' }]">
9
+ <a-input v-model:value="formData.id" />
10
+ </a-form-item>
11
+
12
+ <a-form-item label="流程名称" name="name">
13
+ <a-input v-model:value="formData.name" />
14
+ </a-form-item>
15
+
16
+ <a-form-item label="节点描述" name="documentation">
17
+ <a-textarea v-model:value="formData.documentation" />
18
+ </a-form-item>
19
+
20
+ <a-form-item label="执行监听器">
21
+ <a-badge :count="executionListenerLength">
22
+ <a-button size="small" @click="dialogName = 'executionListenerDialog'">编辑</a-button>
23
+ </a-badge>
24
+ </a-form-item>
25
+
26
+ <a-form-item label="信号定义">
27
+ <a-badge :count="signalLength">
28
+ <a-button size="small" @click="dialogName = 'signalDialog'">编辑</a-button>
29
+ </a-badge>
30
+ </a-form-item>
31
+ </a-form>
32
+
33
+ <executionListenerDialog
34
+ v-if="dialogName === 'executionListenerDialog'"
35
+ :element="element"
36
+ :modeler="modeler"
37
+ @close="finishExecutionListener"
38
+ />
39
+ <signalDialog
40
+ v-if="dialogName === 'signalDialog'"
41
+ :element="element"
42
+ :modeler="modeler"
43
+ @close="finishSignal"
44
+ />
45
+ </div>
46
+ </template>
47
+
48
+ <script setup lang="ts">
49
+ import { ref, computed, watch, onMounted, nextTick } from 'vue'
50
+ import { FormInstance } from 'ant-design-vue'
51
+ import executionListenerDialog from './property/executionListener.vue'
52
+ import signalDialog from './property/signal.vue'
53
+ import { commonParse } from '../../common/parseElement'
54
+ import showConfigData from '../../flowable/showConfig'
55
+ import Modeler from 'bpmn-js/lib/Modeler'
56
+
57
+ defineOptions({
58
+ name: 'ProcessPanel'
59
+ })
60
+
61
+ interface Category {
62
+ name: string
63
+ id: string
64
+ }
65
+
66
+ interface Props {
67
+ categorys: Category[]
68
+ modeler: Modeler
69
+ element: any
70
+ }
71
+
72
+ const props = defineProps<Props>()
73
+
74
+ const formRef = ref<FormInstance>()
75
+ const dialogName = ref('')
76
+ const executionListenerLength = ref(0)
77
+ const signalLength = ref(0)
78
+ const formData = ref<Record<string, any>>({})
79
+
80
+ const elementType = computed(() => {
81
+ const bizObj = props.element.businessObject
82
+ return bizObj.eventDefinitions
83
+ ? bizObj.eventDefinitions[0].$type
84
+ : bizObj.$type
85
+ })
86
+
87
+ const showConfig = computed(() => (showConfigData as any)[elementType.value] || {})
88
+
89
+ const categoryOptions = computed(() =>
90
+ props.categorys.map(category => ({ label: category.name, value: category.id }))
91
+ )
92
+
93
+ const updateProperties = (properties: Record<string, any>) => {
94
+ nextTick(() => {
95
+ try {
96
+ const modeling = props.modeler.get('modeling')
97
+ modeling.updateProperties(props.element, properties)
98
+ } catch (error) {
99
+ console.warn('Update properties error:', error)
100
+ }
101
+ })
102
+ }
103
+
104
+ const computedExecutionListenerLength = () => {
105
+ if (props.element?.businessObject?.extensionElements?.values) {
106
+ executionListenerLength.value = props.element.businessObject.extensionElements.values
107
+ .filter((item: any) => item.$type === 'flowable:ExecutionListener').length
108
+ } else {
109
+ executionListenerLength.value = 0
110
+ }
111
+ }
112
+
113
+ const computedSignalLength = () => {
114
+ signalLength.value = props.element?.businessObject?.extensionElements?.values?.length ?? 0
115
+ }
116
+
117
+ const finishExecutionListener = () => {
118
+ if (dialogName.value === 'executionListenerDialog') {
119
+ computedExecutionListenerLength()
120
+ }
121
+ dialogName.value = ''
122
+ }
123
+
124
+ const finishSignal = () => {
125
+ if (dialogName.value === 'signalDialog') {
126
+ computedSignalLength()
127
+ }
128
+ dialogName.value = ''
129
+ }
130
+
131
+ // Watch for form data changes
132
+ watch(() => formData.value.processCategory, (val) => {
133
+ if (val === '') val = null
134
+ updateProperties({ 'flowable:processCategory': val })
135
+ })
136
+
137
+ watch(() => formData.value.id, (val) => {
138
+ updateProperties({ id: val })
139
+ })
140
+
141
+ watch(() => formData.value.name, (val) => {
142
+ updateProperties({ name: val })
143
+ })
144
+
145
+ watch(() => formData.value.documentation, (val) => {
146
+ if (!val) {
147
+ updateProperties({ documentation: [] })
148
+ return
149
+ }
150
+ if (props.modeler?.get('moddle')) {
151
+ const documentationElement = props.modeler.get('moddle').create('bpmn:Documentation', { text: val })
152
+ updateProperties({ documentation: [documentationElement] })
153
+ }
154
+ })
155
+
156
+ onMounted(() => {
157
+ if (props.element) {
158
+ formData.value = commonParse(props.element)
159
+ computedExecutionListenerLength()
160
+ computedSignalLength()
161
+ }
162
+ })
163
+ </script>
164
+
165
+ <style scoped>
166
+ .flow-containers {
167
+ padding: 0;
168
+ background: transparent;
169
+ min-height: 100%;
170
+ }
171
+
172
+ .flow-containers :deep(.ant-form-item) {
173
+ margin-bottom: 16px;
174
+ }
175
+
176
+ .flow-containers :deep(.ant-form-item-label) {
177
+ font-weight: 500;
178
+ color: #262626;
179
+ }
180
+
181
+ .flow-containers :deep(.ant-input),
182
+ .flow-containers :deep(.ant-select),
183
+ .flow-containers :deep(.ant-textarea) {
184
+ border-radius: 6px;
185
+ transition: all 0.3s;
186
+ }
187
+
188
+ .flow-containers :deep(.ant-button) {
189
+ border-radius: 6px;
190
+ font-weight: 500;
191
+ }
192
+
193
+ .flow-containers :deep(.ant-badge) {
194
+ margin-right: 8px;
195
+ }
196
+
197
+ .flow-containers :deep(.ant-badge .ant-badge-count) {
198
+ background: linear-gradient(135deg, #ff4d4f 0%, #ff7875 100%);
199
+ box-shadow: 0 2px 4px rgba(255, 77, 79, 0.3);
200
+ }
201
+ </style>
@@ -0,0 +1,240 @@
1
+ <template>
2
+ <div>
3
+ <a-modal
4
+ v-model:open="dialogVisible"
5
+ title="执行监听器"
6
+ width="900px"
7
+ :mask-closable="false"
8
+ :keyboard="false"
9
+ :closable="false"
10
+ @cancel="$emit('close')"
11
+ >
12
+ <a-form ref="formRef" :model="formData" layout="vertical">
13
+ <a-tabs>
14
+ <a-tab-pane key="executionListener" tab="执行监听器">
15
+ <div style="margin-bottom: 16px;">
16
+ <a-button type="primary" size="small" @click="addExecutionListener">
17
+ <template #icon><PlusOutlined /></template>
18
+ 添加执行监听器
19
+ </a-button>
20
+ </div>
21
+
22
+ <a-table
23
+ :columns="columns"
24
+ :data-source="formData.executionListener"
25
+ :pagination="false"
26
+ size="small"
27
+ >
28
+ <template #bodyCell="{ column, record, index }">
29
+ <template v-if="column.key === 'event'">
30
+ <a-select v-model:value="record.event" size="small" style="width: 100%">
31
+ <a-select-option value="start">start</a-select-option>
32
+ <a-select-option value="end">end</a-select-option>
33
+ <a-select-option value="take">take</a-select-option>
34
+ </a-select>
35
+ </template>
36
+ <template v-if="column.key === 'type'">
37
+ <a-select v-model:value="record.type" size="small" style="width: 100%">
38
+ <a-select-option value="class">class</a-select-option>
39
+ <a-select-option value="expression">expression</a-select-option>
40
+ <a-select-option value="delegateExpression">delegateExpression</a-select-option>
41
+ </a-select>
42
+ </template>
43
+ <template v-if="column.key === 'className'">
44
+ <a-input v-model:value="record.className" size="small" placeholder="请输入类名" />
45
+ </template>
46
+ <template v-if="column.key === 'params'">
47
+ <a-badge :count="record.params ? record.params.length : 0">
48
+ <a-button size="small" @click="configParam(index)">配置</a-button>
49
+ </a-badge>
50
+ </template>
51
+ <template v-if="column.key === 'action'">
52
+ <a-button type="link" danger size="small" @click="removeExecutionListener(index)">删除</a-button>
53
+ </template>
54
+ </template>
55
+ </a-table>
56
+ </a-tab-pane>
57
+ </a-tabs>
58
+ </a-form>
59
+
60
+ <template #footer>
61
+ <a-button type="primary" @click="closeDialog">确 定</a-button>
62
+ </template>
63
+ </a-modal>
64
+
65
+ <listenerParam v-if="showParamDialog && nowIndex !== null" :value="formData.executionListener[nowIndex]?.params" @close="finishConfigParam" />
66
+ </div>
67
+ </template>
68
+
69
+ <script setup lang="ts">
70
+ import { ref, computed, onMounted, nextTick } from 'vue'
71
+ import { FormInstance } from 'ant-design-vue'
72
+ import { PlusOutlined } from '@ant-design/icons-vue'
73
+ import listenerParam from './listenerParam.vue'
74
+ import Modeler from 'bpmn-js/lib/Modeler'
75
+
76
+ defineOptions({
77
+ name: 'ExecutionListenerDialog'
78
+ })
79
+
80
+ interface Props {
81
+ element: any
82
+ modeler: Modeler
83
+ }
84
+
85
+ const props = defineProps<Props>()
86
+ const emit = defineEmits<{
87
+ close: []
88
+ }>()
89
+
90
+ const formRef = ref<FormInstance>()
91
+ const dialogVisible = ref(true)
92
+ const showParamDialog = ref(false)
93
+ const nowIndex = ref<number | null>(null)
94
+ const formData = ref({
95
+ executionListener: [] as any[]
96
+ })
97
+
98
+ const columns = [
99
+ {
100
+ title: '事件',
101
+ dataIndex: 'event',
102
+ key: 'event',
103
+ width: 120
104
+ },
105
+ {
106
+ title: '类型',
107
+ dataIndex: 'type',
108
+ key: 'type',
109
+ width: 150
110
+ },
111
+ {
112
+ title: 'Java类名',
113
+ dataIndex: 'className',
114
+ key: 'className'
115
+ },
116
+ {
117
+ title: '参数',
118
+ key: 'params',
119
+ width: 100
120
+ },
121
+ {
122
+ title: '操作',
123
+ key: 'action',
124
+ width: 80
125
+ }
126
+ ]
127
+
128
+ const updateProperties = (properties: Record<string, any>) => {
129
+ nextTick(() => {
130
+ try {
131
+ const modeling = props.modeler.get('modeling')
132
+ modeling.updateProperties(props.element, properties)
133
+ } catch (error) {
134
+ }
135
+ })
136
+ }
137
+
138
+ const addExecutionListener = () => {
139
+ formData.value.executionListener.push({
140
+ event: 'start',
141
+ type: 'class',
142
+ className: '',
143
+ params: []
144
+ })
145
+ }
146
+
147
+ const removeExecutionListener = (index: number) => {
148
+ formData.value.executionListener.splice(index, 1)
149
+ }
150
+
151
+ const configParam = (index: number) => {
152
+ nowIndex.value = index
153
+ const nowObj = formData.value.executionListener[index]
154
+ if (!nowObj.params) {
155
+ nowObj.params = []
156
+ }
157
+ showParamDialog.value = true
158
+ }
159
+
160
+ const finishConfigParam = (param: any) => {
161
+ showParamDialog.value = false
162
+ if (nowIndex.value !== null) {
163
+ const cache = formData.value.executionListener[nowIndex.value]
164
+ cache.params = param
165
+ formData.value.executionListener[nowIndex.value] = { ...cache }
166
+ }
167
+ nowIndex.value = null
168
+ }
169
+
170
+ const updateElement = () => {
171
+ if (formData.value.executionListener?.length) {
172
+ let extensionElements = props.element.businessObject.get('extensionElements')
173
+ if (!extensionElements) {
174
+ extensionElements = props.modeler.get('moddle').create('bpmn:ExtensionElements')
175
+ }
176
+ // 清除旧值
177
+ extensionElements.values = extensionElements.values?.filter((item: any) => item.$type !== 'flowable:ExecutionListener') ?? []
178
+ formData.value.executionListener.forEach((item: any) => {
179
+ const executionListener = props.modeler.get('moddle').create('flowable:ExecutionListener')
180
+ executionListener['event'] = item.event
181
+ executionListener[item.type] = item.className
182
+ if (item.params && item.params.length) {
183
+ item.params.forEach((field: any) => {
184
+ const fieldElement = props.modeler.get('moddle').create('flowable:Field')
185
+ fieldElement['name'] = field.name
186
+ fieldElement[field.type] = field.value
187
+ executionListener.get('fields').push(fieldElement)
188
+ })
189
+ }
190
+ extensionElements.get('values').push(executionListener)
191
+ })
192
+ updateProperties({ extensionElements: extensionElements })
193
+ } else {
194
+ const extensionElements = props.element.businessObject[`extensionElements`]
195
+ if (extensionElements) {
196
+ extensionElements.values = extensionElements.values?.filter((item: any) => item.$type !== 'flowable:ExecutionListener') ?? []
197
+ }
198
+ }
199
+ }
200
+
201
+ const closeDialog = () => {
202
+ updateElement()
203
+ dialogVisible.value = false
204
+ emit('close')
205
+ }
206
+
207
+ onMounted(() => {
208
+ if (props.element?.businessObject?.extensionElements?.values) {
209
+ formData.value.executionListener = props.element.businessObject.extensionElements.values
210
+ .filter((item: any) => item.$type === 'flowable:ExecutionListener')
211
+ .map((item: any) => {
212
+ let type
213
+ if ('class' in item) type = 'class'
214
+ if ('expression' in item) type = 'expression'
215
+ if ('delegateExpression' in item) type = 'delegateExpression'
216
+ return {
217
+ event: item.event,
218
+ type: type,
219
+ className: type ? item[type] : '',
220
+ params: item.fields?.map((field: any) => {
221
+ let fieldType
222
+ if ('stringValue' in field) fieldType = 'stringValue'
223
+ if ('expression' in field) fieldType = 'expression'
224
+ return {
225
+ name: field.name,
226
+ type: fieldType,
227
+ value: fieldType ? field[fieldType] : ''
228
+ }
229
+ }) ?? []
230
+ }
231
+ })
232
+ }
233
+ })
234
+ </script>
235
+
236
+ <style scoped>
237
+ .flow-containers .ant-badge {
238
+ margin-right: 8px;
239
+ }
240
+ </style>