@jari-ace/element-plus-component 0.6.2 → 0.6.3

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.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@jari-ace/element-plus-component",
3
3
  "private": false,
4
- "version": "0.6.2",
4
+ "version": "0.6.3",
5
5
  "main": "lib/index.umd.cjs",
6
6
  "module": "lib/index.js",
7
7
  "types": "dist/index.d.ts",
@@ -27,7 +27,7 @@
27
27
  "pretty-bytes": "^7.1.0",
28
28
  "vue-pdf-embed": "^2.1.3",
29
29
  "vue-router": "^5.0.1",
30
- "@jari-ace/app-bolts": "0.7.10"
30
+ "@jari-ace/app-bolts": "0.7.11"
31
31
  },
32
32
  "devDependencies": {
33
33
  "@types/lodash-es": "^4.17.12",
@@ -52,12 +52,9 @@
52
52
  </template>
53
53
  </el-result>
54
54
  </div>
55
- <div v-else-if="flowFormParam">
55
+ <div v-else>
56
56
  <slot name="default" :flowParam="flowFormParam"></slot>
57
57
  </div>
58
- <div class="no-form-tip" v-else>
59
- <el-empty description="暂无表单内容" />
60
- </div>
61
58
  </div>
62
59
  </section>
63
60
 
@@ -107,19 +104,53 @@
107
104
  <ja-button @click="handleForward" type="danger" shortcut="Ctrl+F" v-if="showForwardButton"
108
105
  :tooltip="'保存并' + (flowFormParam?.taskInstance ? '结束当前工作步骤办理' : '发起工作')" :loading="saving"
109
106
  :disabled="saving">{{
110
- flowFormParam?.taskInstance ? "办理结束" : "发起工作" }}</ja-button>
107
+ flowFormParam?.taskInstance ? "办理结束" : "发起流程" }}</ja-button>
111
108
  <slot name="footer" :flowParam="flowFormParam"></slot>
112
109
  </footer>
113
110
  </div>
111
+
112
+ <!-- 流程选择对话框 -->
113
+ <el-dialog
114
+ v-model="flowSelectionDialogVisible"
115
+ title="选择流程"
116
+ width="500px"
117
+ :close-on-click-modal="false"
118
+ :show-close="false"
119
+ align-center
120
+ append-to-body
121
+ >
122
+ <el-scrollbar max-height="50vh">
123
+ <div class="flow-selection-list">
124
+ <el-card
125
+ v-for="flow in selectableFlows"
126
+ :key="flow.flowKey"
127
+ class="flow-selection-card"
128
+ shadow="hover"
129
+ @click="handleSelectFlow(flow)"
130
+ >
131
+ <div class="flow-card-content">
132
+ <div class="flow-caption">{{ flow.flowCaption }}</div>
133
+ <div class="flow-key">{{ flow.flowKey }}</div>
134
+ </div>
135
+ <el-icon><ArrowRight /></el-icon>
136
+ </el-card>
137
+ </div>
138
+ </el-scrollbar>
139
+ <template #footer>
140
+ <span class="dialog-footer">
141
+ <el-button @click="handleCancelFlowSelection">取消</el-button>
142
+ </span>
143
+ </template>
144
+ </el-dialog>
114
145
  </div>
115
146
  </template>
116
147
 
117
148
  <script setup lang="ts" generic="T extends Record<string, any>">
118
149
  import { ref, watch, computed, nextTick } from 'vue'
119
- import { useTaskQueryApi, useFlowDefinitionApi, type FlowFormParamDto } from '@jari-ace/app-bolts'
150
+ import { useTaskQueryApi, useFlowDefinitionApi, type FlowFormParamDto, type ProjectedFlowDefinitionDto } from '@jari-ace/app-bolts'
120
151
  import type { FlowProcessRequest, TaskInstanceDto } from '@jari-ace/app-bolts'
121
152
  import { createAxiosWithoutCache, useLoading } from '@jari-ace/app-bolts'
122
- import { ElMessage, ElTag, ElCard, ElButton, ElTimeline, ElTimelineItem, ElEmpty, ElIcon, ElResult } from 'element-plus'
153
+ import { ElMessage, ElTag, ElCard, ElButton, ElTimeline, ElTimelineItem, ElEmpty, ElIcon, ElResult, ElDialog, ElScrollbar } from 'element-plus'
123
154
  import { JaButton } from '../button'
124
155
  import { JaUserInfoTag } from '../userTag'
125
156
  import { ArrowLeft, ArrowRight } from '@element-plus/icons-vue'
@@ -141,6 +172,10 @@ const props = withDefaults(defineProps<{
141
172
  * 流程定义Key
142
173
  */
143
174
  flowKey?: string
175
+ /**
176
+ * 业务标签,当 flowKey 为空时,根据 bizTag 查找流程
177
+ */
178
+ bizTag?: string
144
179
  /**
145
180
  * 开始节点Key
146
181
  */
@@ -169,14 +204,20 @@ const props = withDefaults(defineProps<{
169
204
  * 侧边栏默认是否收起
170
205
  */
171
206
  defaultCollapsed?: boolean
207
+ /**
208
+ * 是否是新表单,如果为 true,则不等待 formId,直接加载流程定义。如果为 false,且没有 taskId,则会等待 formData.flowFormId 有值后再加载。
209
+ */
210
+ isNewForm: boolean
172
211
  }>(), {
173
212
  appName: undefined,
174
213
  flowKey: undefined,
214
+ bizTag: undefined,
175
215
  startNodeKey: undefined,
176
216
  taskInstanceId: undefined,
177
217
  width: undefined,
178
218
  height: undefined,
179
- defaultCollapsed: false
219
+ defaultCollapsed: false,
220
+ isNewForm: false
180
221
  })
181
222
 
182
223
  // 内部状态
@@ -308,26 +349,59 @@ const getTaskStatus = (state?: number) => {
308
349
  }
309
350
  }
310
351
 
352
+ const actualFlowKey = ref(props.flowKey)
353
+ const flowSelectionDialogVisible = ref(false)
354
+ const selectableFlows = ref<ProjectedFlowDefinitionDto[]>([])
355
+ const selectableFlowParams = ref<FlowFormParamDto[]>([])
356
+
311
357
  // 监听dialogVisible变化
312
358
  watch(
313
359
  () => dialogVisible.value,
314
360
  async (newValue) => {
315
361
  resetState()
316
- if (newValue) {
317
- emits('open')
318
- // 等待下一个事件循环,确保父组件的open事件处理完成(可能包含异步loadData)
362
+ actualFlowKey.value = props.flowKey
363
+ if (newValue) {
364
+ emits('open')
365
+ //等待状态同步
319
366
  await nextTick()
320
- loadFlowFormParam()
367
+ if (props.taskId) {
368
+ // 场景一:通过 taskId 打开,直接加载
369
+ loadFlowFormParam()
370
+ } else if (props.isNewForm) {
371
+ // 场景二:明确是新表单发起流程,不等待 formId,直接加载流程定义
372
+ loadFlowFormParam()
373
+ } else if (props.formData?.flowFormId) {
374
+ // 场景三:非新表单,且一开始就已经有 formId,直接加载
375
+ loadFlowFormParam()
376
+ }
377
+ // 场景四:非新表单且没有 formId,什么都不做,等待 formData.flowFormId 的 watcher 触发
321
378
  } else {
322
379
  emits('closed')
323
380
  }
324
381
  }
325
382
  )
326
383
 
384
+ // 监听 formData 的 flowFormId 变化,用于处理异步加载表单数据的场景
385
+ watch(
386
+ () => props.formData?.flowFormId,
387
+ (newFormId, oldFormId) => {
388
+ // 只有在 dialog 打开的情况下,且从无到有,或者发生了变化,且没有 taskId 的时候,才重新加载流程参数
389
+ if (dialogVisible.value && !props.taskId && !props.isNewForm && newFormId && newFormId !== oldFormId) {
390
+ // 避免重复加载,可以先重置
391
+ resetState()
392
+ actualFlowKey.value = props.flowKey
393
+ loadFlowFormParam()
394
+ }
395
+ }
396
+ )
397
+
327
398
  // 重置状态
328
399
  const resetState = () => {
329
400
  error.value = ''
330
401
  flowFormParam.value = undefined
402
+ selectableFlows.value = []
403
+ selectableFlowParams.value = []
404
+ actualFlowKey.value = undefined
331
405
  }
332
406
 
333
407
  // 加载流程表单参数
@@ -335,28 +409,45 @@ const loadFlowFormParam = async () => {
335
409
  resetState()
336
410
  const formId = props.formData?.flowFormId;
337
411
  // 检查参数
338
- // 必须提供 taskId,或者 appName 和 flowKey
339
- if (!props.taskId && !(props.appName && props.flowKey)) {
340
- ElMessage.error('参数错误: 必须提供taskId或appName、flowKey和flowFormId')
412
+ // 必须提供 taskId,或者 appName 和 (flowKey 或 bizTag)
413
+ if (!props.taskId && !(props.appName && (props.flowKey || props.bizTag))) {
414
+ ElMessage.error('参数错误: 必须提供taskId或appName、flowKey/bizTag和flowFormId')
341
415
  return
342
416
  }
343
-
344
- try {
417
+ try {
345
418
  if (props.taskId) {
346
419
  // 优先使用Id
347
420
  flowFormParam.value = await taskQueryApi.getTaskInstanceById(props.taskId)
348
- } else if (formId) {
421
+ } else if (formId && props.flowKey) {
349
422
  // 使用appName、flowKey、formId
350
- flowFormParam.value = await taskQueryApi.getTaskByFormIdAndAppAndFlowKey(formId, props.appName!, props.flowKey!)
423
+ const param = await taskQueryApi.getTaskByFormIdAndAppAndFlowKey(formId, props.appName!, props.flowKey!)
424
+ if (param && param.flowDefinition) {
425
+ flowFormParam.value = param
426
+ } else {
427
+ // 兜底:如果没查到流程参数或者只有表单数据没有流程实例,按照新建模式加载流程定义
428
+ const flowDefinition = await flowDefinitionApi.getEffectiveDefinition(props.appName!, props.flowKey!)
429
+ flowFormParam.value = {
430
+ flowDefinition: flowDefinition,
431
+ } as FlowFormParamDto
432
+ }
433
+ } else if (formId && props.bizTag) {
434
+ // formId 存在,但没有 flowKey,有 bizTag,查询可能存在的多个流程参数
435
+ const params = await taskQueryApi.getTasksByFormIdAndAppAndBizTag(formId, props.appName!, props.bizTag)
436
+ if (!params || params.length === 0 || !params.some(p => p.flowDefinition)) {
437
+ // 兜底:如果没查到,或者查出来的都没有流程定义,直接按照没有formId走新建模式处理
438
+ await loadNewFlowParamWithBizTag()
439
+ } else if (params.length === 1) {
440
+ flowFormParam.value = params[0]
441
+ actualFlowKey.value = params[0].flowDefinition?.flowKey
442
+ } else {
443
+ // 多于1个,不立即弹出选择框,先存起来,留到点击"发起工作"时再选
444
+ selectableFlowParams.value = params
445
+ selectableFlows.value = params.map(p => p.flowDefinition as ProjectedFlowDefinitionDto)
446
+ return
447
+ }
351
448
  } else {
352
- // 新建模式:只有 appName 和 flowKey,没有 formId
353
- // 获取流程定义
354
- const flowDefinition = await flowDefinitionApi.getEffectiveDefinition(props.appName!, props.flowKey!)
355
- // 构造部分 flowFormParam
356
- flowFormParam.value = {
357
- flowDefinition: flowDefinition,
358
- // 其他字段为空
359
- } as FlowFormParamDto
449
+ // 新建模式
450
+ await loadNewFlowParamWithBizTag()
360
451
  }
361
452
  } catch (e: any) {
362
453
  ElMessage.error(e.message || '加载流程信息失败')
@@ -364,6 +455,68 @@ const loadFlowFormParam = async () => {
364
455
  }
365
456
  }
366
457
 
458
+ const loadNewFlowParamWithBizTag = async () => {
459
+ let keyToUse = props.flowKey
460
+ if (!keyToUse && props.bizTag) {
461
+ // 根据 bizTag 查询当前生效的可用流程
462
+ const res = await flowDefinitionApi.getEffectiveDefinitionsByBizTag(props.appName!, props.bizTag)
463
+
464
+ if (!res || res.length === 0) {
465
+ throw new Error(`未找到业务标签为 ${props.bizTag} 的生效可用流程定义`)
466
+ } else if (res.length === 1) {
467
+ keyToUse = res[0].flowKey
468
+ } else {
469
+ // 弹出选择对话框的动作延迟到点击发起工作
470
+ selectableFlows.value = res
471
+ return
472
+ }
473
+ }
474
+
475
+ actualFlowKey.value = keyToUse
476
+
477
+ // 获取流程定义
478
+ const flowDefinition = await flowDefinitionApi.getEffectiveDefinition(props.appName!, keyToUse!)
479
+ // 构造部分 flowFormParam
480
+ flowFormParam.value = {
481
+ flowDefinition: flowDefinition,
482
+ // 其他字段为空
483
+ } as FlowFormParamDto
484
+ }
485
+
486
+ const handleSelectFlow = async (flow: ProjectedFlowDefinitionDto) => {
487
+ flowSelectionDialogVisible.value = false
488
+ actualFlowKey.value = flow.flowKey
489
+
490
+ try {
491
+ if (selectableFlowParams.value.length > 0) {
492
+ // 已经有完整的流程参数了,直接使用
493
+ const param = selectableFlowParams.value.find(p => p.flowDefinition?.flowKey === flow.flowKey)
494
+ if (param) {
495
+ flowFormParam.value = param
496
+ // 选择了之后,直接执行原先因为需要选择而暂停的 发起/提交流程 操作
497
+ await executeForward()
498
+ return
499
+ }
500
+ }
501
+
502
+ // 否则作为新建模式加载最新流程定义
503
+ const flowDefinition = await flowDefinitionApi.getEffectiveDefinition(props.appName!, flow.flowKey!)
504
+ flowFormParam.value = {
505
+ flowDefinition: flowDefinition,
506
+ } as FlowFormParamDto
507
+ // 选择了之后,直接执行原先因为需要选择而暂停的 发起/提交流程 操作
508
+ await executeForward()
509
+ } catch (e: any) {
510
+ ElMessage.error(e.message || '加载流程信息失败')
511
+ error.value = e.message
512
+ }
513
+ }
514
+
515
+ const handleCancelFlowSelection = () => {
516
+ flowSelectionDialogVisible.value = false
517
+ // 取消选择不再关闭整个表单,只是取消本次的发起动作
518
+ }
519
+
367
520
  // 底部按钮处理
368
521
  const handleSave = async () => {
369
522
  saving.value = true
@@ -373,7 +526,7 @@ const handleSave = async () => {
373
526
  taskId: flowFormParam.value?.taskInstance?.id || '',
374
527
  processRequestType: "SAVE_FORM",
375
528
  appName: props.appName || '',
376
- flowKey: props.flowKey || '',
529
+ flowKey: actualFlowKey.value || '',
377
530
  startNodeKey: props.startNodeKey || '',
378
531
  forwardTo: []
379
532
  },
@@ -384,7 +537,8 @@ const handleSave = async () => {
384
537
  }
385
538
  }
386
539
 
387
- const handleForward = async () => {
540
+ // 实际执行保存并转发的核心逻辑
541
+ const executeForward = async () => {
388
542
  saving.value = true
389
543
  try {
390
544
  const p = flowFormParam.value
@@ -393,7 +547,7 @@ const handleForward = async () => {
393
547
  taskId: p?.taskInstance?.id || '',
394
548
  processRequestType: p?.taskInstance ? "FORWARD" : "INITIATE",
395
549
  appName: props.appName || '',
396
- flowKey: props.flowKey || '',
550
+ flowKey: actualFlowKey.value || '',
397
551
  startNodeKey: props.startNodeKey || '',
398
552
  forwardTo: []
399
553
  },
@@ -406,6 +560,16 @@ const handleForward = async () => {
406
560
  }
407
561
  }
408
562
 
563
+ const handleForward = async () => {
564
+ // 如果需要选择流程,并且还没有确定实际使用的 flowKey,则弹出选择对话框
565
+ if (selectableFlows.value.length > 1 && !actualFlowKey.value) {
566
+ flowSelectionDialogVisible.value = true
567
+ return
568
+ }
569
+
570
+ await executeForward()
571
+ }
572
+
409
573
  </script>
410
574
 
411
575
  <style>
@@ -715,4 +879,48 @@ const handleForward = async () => {
715
879
  box-shadow: 0 -1px 4px rgba(0, 0, 0, 0.05);
716
880
  z-index: 100;
717
881
  }
882
+
883
+ /* 流程选择弹窗样式 */
884
+ .flow-selection-list {
885
+ display: flex;
886
+ flex-direction: column;
887
+ gap: 12px;
888
+ padding: 4px;
889
+ }
890
+
891
+ .flow-selection-card {
892
+ cursor: pointer;
893
+ transition: all 0.3s;
894
+ border: 1px solid #e4e7ed;
895
+ }
896
+
897
+ .flow-selection-card:hover {
898
+ border-color: var(--el-color-primary);
899
+ transform: translateY(-2px);
900
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
901
+ }
902
+
903
+ .flow-selection-card :deep(.el-card__body) {
904
+ display: flex;
905
+ justify-content: space-between;
906
+ align-items: center;
907
+ padding: 16px;
908
+ }
909
+
910
+ .flow-card-content {
911
+ display: flex;
912
+ flex-direction: column;
913
+ gap: 4px;
914
+ }
915
+
916
+ .flow-caption {
917
+ font-size: 16px;
918
+ font-weight: 500;
919
+ color: #303133;
920
+ }
921
+
922
+ .flow-key {
923
+ font-size: 13px;
924
+ color: #909399;
925
+ }
718
926
  </style>