From 0289d8c37cf0273a456efe7ad4a25cc5e5cde266 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=BB=9D=E5=B0=98?= <237809796@qq.com> Date: Wed, 17 Jun 2026 02:36:55 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96:=20B-03b-R10=E5=89=8D?= =?UTF-8?q?=E7=AB=AF=E5=AE=A1=E6=89=B9(=E7=BB=88=E6=80=81=E6=B8=85pending+?= =?UTF-8?q?=E4=BA=92=E6=96=A5=E9=94=81=E9=98=B2=E9=87=8D=E5=A4=8DIPC)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/stores/project/workflow.ts | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/src/stores/project/workflow.ts b/src/stores/project/workflow.ts index 8cc3e8d..db0e1e1 100644 --- a/src/stores/project/workflow.ts +++ b/src/stores/project/workflow.ts @@ -5,6 +5,11 @@ import { state, _eventUnlisten, setEventUnlisten } from './state' // composable 外全局 i18n 实例(非 setup 上下文) const t = ((i18n as any).global.t as (k: string) => string).bind((i18n as any).global) +// B-03b-R10(⑦ 互斥): 审批响应/取消 IPC 进行中标记(模块级单例,createWorkflowStore 每会话只跑一次)。 +// 防双击/连点在同一轮 IPC 未返回前发起第二次(后端无幂等键,第二次会重复消费 Request 触发 +// "node already completed" 类报错)。store 层守卫优于按钮 disabled: 共享单例 state,跨调用方统一。 +let _approvalInFlight = false + /** * 工作流 + 审批子 store(共享全局 state)。 * @@ -49,6 +54,20 @@ export function createWorkflowStore() { if (payload.event?.type === 'human_approval_request') { state.pendingApproval = payload.event as unknown as typeof state.pendingApproval } + // B-03b-R10(⑤ 终态不清): 工作流走到终态(completed/failed)或审批节点被取消时, + // 若 pendingApproval 仍属于本次 execution,说明该审批未经 approve/cancel 正常路径关闭 + // (上游节点失败/工作流被整体取消/用户「稍后」关闭弹窗但 state 仍残留),清掉陈旧态 + // 避免下次进 ProjectDetail 时 watch immediate 误判仍有待审批。 + // 按 execution_id 限定: 并发工作流场景不清掉他人家的 pending(④ 单槽根因另议,见审查报告)。 + else if ( + state.pendingApproval && + state.pendingApproval.execution_id === payload.execution_id && + (payload.event?.type === 'workflow_completed' || + payload.event?.type === 'workflow_failed' || + payload.event?.type === 'node_cancelled') + ) { + state.pendingApproval = null + } } catch (e) { console.error('处理工作流事件失败:', e, payload) } @@ -76,6 +95,9 @@ export function createWorkflowStore() { comment?: string, ) { if (!state.pendingApproval) return + // B-03b-R10(⑦ 互斥): 上一轮 IPC 未返回,忽略重复点击(连点/双击) + if (_approvalInFlight) return + _approvalInFlight = true const selectType = state.pendingApproval.select_type ?? 'single' // CR-260615-18:删死逻辑三元(原 selectType 多/单分支返回值相同) // 单选取首项(后端 decision 单值);多选 decision 取首项占位(后端按 decisions 走) @@ -100,12 +122,18 @@ export function createWorkflowStore() { } catch (error) { console.error('审批失败:', error) state.error = error?.toString() ?? t('projects.err.approveFailed') + } finally { + // B-03b-R10(⑦ 互斥): 无论成功/失败,IPC 已返回,释放重入锁 + _approvalInFlight = false } } /** 取消当前待审批节点(调 cancel_workflow_node IPC 置节点 Cancelled) */ async function cancelHumanApproval() { if (!state.pendingApproval) return + // B-03b-R10(⑦ 互斥): 上一轮 IPC 未返回,忽略重复点击 + if (_approvalInFlight) return + _approvalInFlight = true try { await workflowApi.cancelHumanApproval({ @@ -117,6 +145,9 @@ export function createWorkflowStore() { } catch (error) { console.error('取消审批失败:', error) state.error = error?.toString() ?? t('projects.err.cancelFailed') + } finally { + // B-03b-R10(⑦ 互斥): 无论成功/失败,IPC 已返回,释放重入锁 + _approvalInFlight = false } }