重构: ARC-05 project.ts 上帝 store 拆分(4 子 store+barrel 兼容零改动)

拆 stores/project/{state,projects,tasks,ideas,workflow}.ts 四领域子 store(共享 state 单例 reactive 引用一致);越层 invoke approve_human_approval/cancel_workflow_node 下沉 src/api/workflow.ts;project.ts 改 barrel 组合器(实例化四子 store+re-export 所有成员,useProjectStore 单例+ProjectStore 类型保留)。所有 view(App/Dashboard/Ideas/ProjectDetail/Projects/Tasks/ToolCard)import '../stores/project' 零改动。行为零变化(含 CR-11⑪ approveHumanApproval 签名+B-34 snake_case)。批6,vue-tsc 0
This commit is contained in:
2026-06-15 05:48:47 +08:00
parent d809cf4b18
commit 8710d6cfd1
7 changed files with 456 additions and 322 deletions

View File

@@ -26,4 +26,30 @@ export const workflowApi = {
callback(event.payload) callback(event.payload)
}) })
}, },
/**
* 发送人工审批响应。
*
* 统一以 decisions 数组表达选择,按 select_type 判定单/多选:
* - 单选:decision 取 decisions[0];
* - 多选:decisions 透传全量,decision 取首项作占位(后端按 decisions 走)。
* options 透传闭环后端校验(R-PD-5)。
* B-260615-34:Tauri 2 IPC 不转 camelCase,snake_case 对齐后端 workflow.rs。
*/
approveHumanApproval(params: {
execution_id: string
node_id: string
decision: string
comment?: string
options: string[]
decisions: string[]
select_type: 'single' | 'multiple'
}): Promise<void> {
return invoke('approve_human_approval', params)
},
/** 取消当前待审批节点(置节点 Cancelled) */
cancelHumanApproval(params: { execution_id: string; node_id: string }): Promise<void> {
return invoke('cancel_workflow_node', params)
},
} }

View File

@@ -1,322 +1,26 @@
import { reactive, computed } from 'vue' import { reactive, computed } from 'vue'
import { invoke } from '@tauri-apps/api/core' import { createProjectsStore } from './project/projects'
import { projectApi, taskApi, ideaApi, workflowApi } from '../api' import { createTasksStore } from './project/tasks'
import type { ImportProjectInput } from '../api/project' import { createIdeasStore } from './project/ideas'
import type { ProjectRecord, TaskRecord, IdeaRecord, WorkflowRecord, WorkflowEventPayload } from '../api/types' import { createWorkflowStore } from './project/workflow'
import { state, clearError } from './project/state'
// ── 全局响应式状态(单例) ──
const state = reactive({
projects: [] as ProjectRecord[],
deletedProjects: [] as ProjectRecord[],
tasks: [] as TaskRecord[],
ideas: [] as IdeaRecord[],
workflowExecutions: [] as WorkflowRecord[],
liveEvents: [] as Array<WorkflowEventPayload & { _ts: number }>,
pendingApproval: null as {
execution_id: string
node_id: string
title: string
description: string
options: string[]
/** F-260615-01: single(缺省)/multiple */
select_type?: 'single' | 'multiple'
} | null,
loading: false,
error: null as string | null,
})
let _eventUnlisten: (() => void) | null = null
// ── barrel 组合器(零行为变更,纯重构) ──
//
// 原 project.ts 是上帝 store(项目/任务/灵感/工作流多领域 + 越层 invoke 审批 IPC)。
// 已拆为 src/stores/project/{projects,tasks,ideas,workflow}.ts 四子 store,
// 共享 src/stores/project/state.ts 单例 state。
//
// 本文件改为组合器:内部实例化四子 store + re-export 所有成员,
// 使现有 import { useProjectStore } 的 view 零改动。
// 越层 invoke(approve_human_approval / cancel_human_node) 已下沉到 src/api/workflow.ts。
function createStore() { function createStore() {
// ── 项目 CRUD ── const projectsStore = createProjectsStore()
async function loadProjects() { const tasksStore = createTasksStore()
state.loading = true const ideasStore = createIdeasStore()
state.error = null const workflowStore = createWorkflowStore()
try {
state.projects = await projectApi.list()
} catch (e: any) {
state.error = e?.toString() ?? '加载项目失败'
} finally {
state.loading = false
}
}
// 清除错误状态(供 toast 显示后重置,允许连续同值错误再次触发 watch) // ── 统计(跨领域,留在组合器) ──
function clearError() { state.error = null }
async function createProject(name: string, description = '', ideaId?: string, path?: string, stack?: string) {
try {
const record = await projectApi.create({ name, description, idea_id: ideaId, path, stack })
state.projects.push(record)
return record
} catch (e: any) {
state.error = e?.toString() ?? '创建项目失败'
return null
}
}
/** 导入历史项目(选已存在目录,后端创建+绑定+探测栈+读 README 首段一步完成) */
async function importProject(input: ImportProjectInput) {
try {
const record = await projectApi.importProject(input)
await loadProjects() // 刷新列表(后端已 insert,统一走 load 避免本地数组与后端不一致)
return record
} catch (e: any) {
state.error = e?.toString() ?? '导入项目失败'
return null
}
}
async function updateProject(id: string, field: string, value: string) {
await projectApi.update(id, field, value)
const idx = state.projects.findIndex(p => p.id === id)
if (idx >= 0) {
(state.projects[idx] as any)[field] = value
}
}
/** 重定位项目目录(后端重探测 stack,返回最新记录并更新本地状态) */
async function relocateProjectPath(id: string, newPath: string) {
const record = await projectApi.relocatePath(id, newPath)
const idx = state.projects.findIndex(p => p.id === id)
if (idx >= 0) state.projects[idx] = record
return record
}
async function deleteProject(id: string) {
try {
await projectApi.delete(id) // 软删 → 回收站(可恢复)
state.projects = state.projects.filter(p => p.id !== id)
} catch (e: any) {
state.error = e?.toString() ?? '删除项目失败'
}
}
async function loadDeletedProjects() {
try {
state.deletedProjects = await projectApi.listDeleted()
} catch (e: any) {
state.error = e?.toString() ?? '加载回收站失败'
}
}
async function restoreProject(id: string) {
try {
await projectApi.restore(id)
state.deletedProjects = state.deletedProjects.filter(p => p.id !== id)
await loadProjects() // 恢复后刷新活跃列表
} catch (e: any) {
state.error = e?.toString() ?? '恢复项目失败'
}
}
async function purgeProject(id: string) {
try {
await projectApi.purge(id) // 彻底删(级联物理删,不可恢复)
state.deletedProjects = state.deletedProjects.filter(p => p.id !== id)
} catch (e: any) {
state.error = e?.toString() ?? '彻底删除失败'
}
}
// ── 任务 CRUD ──
async function loadTasks(projectId?: string) {
try {
state.tasks = await taskApi.list(projectId)
} catch (e: any) {
state.error = e?.toString() ?? '加载任务失败'
}
}
async function createTask(input: { project_id: string; title: string; description?: string; priority?: number; branch_name?: string; assignee?: string }) {
try {
const record = await taskApi.create(input)
state.tasks.push(record)
return record
} catch (e: any) {
state.error = e?.toString() ?? '创建任务失败'
return null
}
}
async function updateTask(id: string, field: string, value: string) {
await taskApi.update(id, field, value)
const idx = state.tasks.findIndex(t => t.id === id)
if (idx >= 0) {
(state.tasks[idx] as any)[field] = value
}
}
async function deleteTask(id: string) {
try {
await taskApi.delete(id)
state.tasks = state.tasks.filter(t => t.id !== id)
} catch (e: any) {
state.error = e?.toString() ?? '删除任务失败'
}
}
// ── 灵感 CRUD ──
async function loadIdeas() {
try {
state.ideas = await ideaApi.list()
} catch (e: any) {
state.error = e?.toString() ?? '加载灵感失败'
}
}
async function createIdea(input: { title: string; description?: string; priority?: number; tags?: string; source?: string }) {
try {
const record = await ideaApi.create(input)
state.ideas.push(record)
return record
} catch (e: any) {
state.error = e?.toString() ?? '创建灵感失败'
return null
}
}
async function updateIdea(id: string, field: string, value: string) {
await ideaApi.update(id, field, value)
const idx = state.ideas.findIndex(i => i.id === id)
if (idx >= 0) {
(state.ideas[idx] as any)[field] = value
}
}
async function deleteIdea(id: string) {
try {
await ideaApi.delete(id)
state.ideas = state.ideas.filter(i => i.id !== id)
} catch (e: any) {
state.error = e?.toString() ?? '删除灵感失败'
}
}
async function evaluateIdea(id: string) {
const record = await ideaApi.evaluate(id)
const idx = state.ideas.findIndex(i => i.id === id)
if (idx >= 0) state.ideas[idx] = record
return record
}
async function promoteIdea(id: string) {
const res = await ideaApi.promote(id)
await loadIdeas() // 后端已回写 status=promoted/promoted_to刷新列表
return res
}
// ── 工作流 ──
async function runWorkflow(name: string, dag: unknown, config?: Record<string, unknown>) {
return await workflowApi.run(name, dag, config)
}
async function loadWorkflowExecutions() {
try {
state.workflowExecutions = await workflowApi.listExecutions()
} catch (e: any) {
state.error = e?.toString() ?? '加载工作流记录失败'
}
}
async function startEventListener() {
if (_eventUnlisten) return _eventUnlisten
_eventUnlisten = await workflowApi.onEvent((payload) => {
try {
// 固时间戳(FR-C1):formattedEvents computed 重算时用事件发生时刻,非当前时刻
state.liveEvents.push({ ...payload, _ts: Date.now() })
// 限长(FR-R3):防 liveEvents 无限增长撑爆内存,保留最近 200 条
if (state.liveEvents.length > 200) {
state.liveEvents.splice(0, state.liveEvents.length - 200)
}
// 处理人工审批请求
// WorkflowEvent serde tag=type rename_all=snake_case → type='human_approval_request',字段扁平(无 data 包装)(B-03b-R7)
if (payload.event?.type === 'human_approval_request') {
state.pendingApproval = payload.event as unknown as typeof state.pendingApproval
}
} catch (e) {
console.error('处理工作流事件失败:', e, payload)
}
})
return _eventUnlisten
}
function clearLiveEvents() {
state.liveEvents = []
}
/**
* 发送审批响应。
*
* 统一以 decisions 数组表达选择,按 pendingApproval.select_type 判定单/多选:
* - 单选(select_type='single' 或缺省):取 decisions[0] 作为后端 decision 单值;
* - 多选(select_type='multiple'):decisions 透传全量。
*
* 后端 IPC 需同时收 decision(单值,向后兼容)与 decisions(数组),此处按 select_type
* 自动分派,消除调用方"该传 decision 还是 decisions"的歧义。
* F-260615-01: decisions/select_type 透传,与后端 IPC 签名对齐。
*/
async function approveHumanApproval(
decisions: string[],
comment?: string,
) {
if (!state.pendingApproval) return
const selectType = state.pendingApproval.select_type ?? 'single'
// 单选取首项(后端 decision 为单值);多选 decision 取首项作占位(后端按 decisions 走)
const decision = selectType === 'multiple'
? (decisions[0] ?? '')
: (decisions[0] ?? '')
try {
// 调用 IPC 命令发送审批响应
// R-PD-5: 透传 options 闭环后端校验(options 非空时 decision 必须 ∈ options否则 IPC 报错)。
// options 来自已收到的 HumanApprovalRequest 事件 payload(IPC 无法访问节点 config)。
await invoke('approve_human_approval', {
execution_id: state.pendingApproval.execution_id,
node_id: state.pendingApproval.node_id,
decision,
comment,
options: state.pendingApproval.options ?? [],
decisions,
select_type: selectType, // B-260615-34:Tauri 2 IPC 不转 camelCase,须 snake_case 对齐后端 workflow.rs:211
})
// 清除待审批状态
state.pendingApproval = null
} catch (error) {
console.error('审批失败:', error)
}
}
/** 取消当前待审批节点(调 cancel_workflow_node IPC 置节点 Cancelled */
async function cancelHumanApproval() {
if (!state.pendingApproval) return
try {
await invoke('cancel_workflow_node', {
execution_id: state.pendingApproval.execution_id,
node_id: state.pendingApproval.node_id,
})
// 清除待审批状态
state.pendingApproval = null
} catch (error) {
console.error('取消审批失败:', error)
}
}
function stopEventListener() {
if (_eventUnlisten) {
try {
_eventUnlisten()
} catch (e) {
console.error('停止事件监听失败:', e)
}
_eventUnlisten = null
}
}
// ── 统计 ──
const stats = computed(() => ({ const stats = computed(() => ({
ideas: state.ideas.length, ideas: state.ideas.length,
projects: state.projects.length, projects: state.projects.length,
@@ -334,18 +38,39 @@ function createStore() {
get liveEvents() { return state.liveEvents }, get liveEvents() { return state.liveEvents },
get loading() { return state.loading }, get loading() { return state.loading },
get error() { return state.error }, get error() { return state.error },
clearError,
get deletedProjects() { return state.deletedProjects }, get deletedProjects() { return state.deletedProjects },
clearError,
// project actions // project actions
loadProjects, createProject, importProject, updateProject, deleteProject, relocateProjectPath, loadProjects: projectsStore.loadProjects,
loadDeletedProjects, restoreProject, purgeProject, createProject: projectsStore.createProject,
importProject: projectsStore.importProject,
updateProject: projectsStore.updateProject,
deleteProject: projectsStore.deleteProject,
relocateProjectPath: projectsStore.relocateProjectPath,
loadDeletedProjects: projectsStore.loadDeletedProjects,
restoreProject: projectsStore.restoreProject,
purgeProject: projectsStore.purgeProject,
// task actions // task actions
loadTasks, createTask, updateTask, deleteTask, loadTasks: tasksStore.loadTasks,
createTask: tasksStore.createTask,
updateTask: tasksStore.updateTask,
deleteTask: tasksStore.deleteTask,
// idea actions // idea actions
loadIdeas, createIdea, updateIdea, deleteIdea, evaluateIdea, promoteIdea, loadIdeas: ideasStore.loadIdeas,
createIdea: ideasStore.createIdea,
updateIdea: ideasStore.updateIdea,
deleteIdea: ideasStore.deleteIdea,
evaluateIdea: ideasStore.evaluateIdea,
promoteIdea: ideasStore.promoteIdea,
// workflow actions // workflow actions
runWorkflow, loadWorkflowExecutions, startEventListener, stopEventListener, clearLiveEvents, runWorkflow: workflowStore.runWorkflow,
approveHumanApproval, cancelHumanApproval, pendingApproval: computed(() => state.pendingApproval), loadWorkflowExecutions: workflowStore.loadWorkflowExecutions,
startEventListener: workflowStore.startEventListener,
stopEventListener: workflowStore.stopEventListener,
clearLiveEvents: workflowStore.clearLiveEvents,
approveHumanApproval: workflowStore.approveHumanApproval,
cancelHumanApproval: workflowStore.cancelHumanApproval,
pendingApproval: computed(() => state.pendingApproval),
// computed // computed
stats, stats,
}) })

View File

@@ -0,0 +1,63 @@
import { ideaApi } from '../../api'
import { state } from './state'
/** 灵感 CRUD 子 store(共享全局 state) */
export function createIdeasStore() {
async function loadIdeas() {
try {
state.ideas = await ideaApi.list()
} catch (e: any) {
state.error = e?.toString() ?? '加载灵感失败'
}
}
async function createIdea(input: { title: string; description?: string; priority?: number; tags?: string; source?: string }) {
try {
const record = await ideaApi.create(input)
state.ideas.push(record)
return record
} catch (e: any) {
state.error = e?.toString() ?? '创建灵感失败'
return null
}
}
async function updateIdea(id: string, field: string, value: string) {
await ideaApi.update(id, field, value)
const idx = state.ideas.findIndex(i => i.id === id)
if (idx >= 0) {
(state.ideas[idx] as any)[field] = value
}
}
async function deleteIdea(id: string) {
try {
await ideaApi.delete(id)
state.ideas = state.ideas.filter(i => i.id !== id)
} catch (e: any) {
state.error = e?.toString() ?? '删除灵感失败'
}
}
async function evaluateIdea(id: string) {
const record = await ideaApi.evaluate(id)
const idx = state.ideas.findIndex(i => i.id === id)
if (idx >= 0) state.ideas[idx] = record
return record
}
async function promoteIdea(id: string) {
const res = await ideaApi.promote(id)
await loadIdeas() // 后端已回写 status=promoted/promoted_to刷新列表
return res
}
return {
loadIdeas,
createIdea,
updateIdea,
deleteIdea,
evaluateIdea,
promoteIdea,
}
}

View File

@@ -0,0 +1,107 @@
import { projectApi } from '../../api'
import type { ImportProjectInput } from '../../api/project'
import { state, clearError } from './state'
/** 项目 CRUD 子 store(共享全局 state) */
export function createProjectsStore() {
async function loadProjects() {
state.loading = true
state.error = null
try {
state.projects = await projectApi.list()
} catch (e: any) {
state.error = e?.toString() ?? '加载项目失败'
} finally {
state.loading = false
}
}
async function createProject(name: string, description = '', ideaId?: string, path?: string, stack?: string) {
try {
const record = await projectApi.create({ name, description, idea_id: ideaId, path, stack })
state.projects.push(record)
return record
} catch (e: any) {
state.error = e?.toString() ?? '创建项目失败'
return null
}
}
/** 导入历史项目(选已存在目录,后端创建+绑定+探测栈+读 README 首段一步完成) */
async function importProject(input: ImportProjectInput) {
try {
const record = await projectApi.importProject(input)
await loadProjects() // 刷新列表(后端已 insert,统一走 load 避免本地数组与后端不一致)
return record
} catch (e: any) {
state.error = e?.toString() ?? '导入项目失败'
return null
}
}
async function updateProject(id: string, field: string, value: string) {
await projectApi.update(id, field, value)
const idx = state.projects.findIndex(p => p.id === id)
if (idx >= 0) {
(state.projects[idx] as any)[field] = value
}
}
/** 重定位项目目录(后端重探测 stack,返回最新记录并更新本地状态) */
async function relocateProjectPath(id: string, newPath: string) {
const record = await projectApi.relocatePath(id, newPath)
const idx = state.projects.findIndex(p => p.id === id)
if (idx >= 0) state.projects[idx] = record
return record
}
async function deleteProject(id: string) {
try {
await projectApi.delete(id) // 软删 → 回收站(可恢复)
state.projects = state.projects.filter(p => p.id !== id)
} catch (e: any) {
state.error = e?.toString() ?? '删除项目失败'
}
}
async function loadDeletedProjects() {
try {
state.deletedProjects = await projectApi.listDeleted()
} catch (e: any) {
state.error = e?.toString() ?? '加载回收站失败'
}
}
async function restoreProject(id: string) {
try {
await projectApi.restore(id)
state.deletedProjects = state.deletedProjects.filter(p => p.id !== id)
await loadProjects() // 恢复后刷新活跃列表
} catch (e: any) {
state.error = e?.toString() ?? '恢复项目失败'
}
}
async function purgeProject(id: string) {
try {
await projectApi.purge(id) // 彻底删(级联物理删,不可恢复)
state.deletedProjects = state.deletedProjects.filter(p => p.id !== id)
} catch (e: any) {
state.error = e?.toString() ?? '彻底删除失败'
}
}
return {
loadProjects,
createProject,
importProject,
updateProject,
deleteProject,
relocateProjectPath,
loadDeletedProjects,
restoreProject,
purgeProject,
// clearError 来自 state(供 barrel re-export)
clearError,
}
}

View File

@@ -0,0 +1,37 @@
import { reactive } from 'vue'
import type { ProjectRecord, TaskRecord, IdeaRecord, WorkflowRecord, WorkflowEventPayload } from '../../api/types'
/** 待审批请求(由 human_approval_request 事件填充) */
export interface PendingApproval {
execution_id: string
node_id: string
title: string
description: string
options: string[]
/** F-260615-01: single(缺省)/multiple */
select_type?: 'single' | 'multiple'
}
// ── 全局响应式状态(单例,四子 store 共享同一份) ──
export const state = reactive({
projects: [] as ProjectRecord[],
deletedProjects: [] as ProjectRecord[],
tasks: [] as TaskRecord[],
ideas: [] as IdeaRecord[],
workflowExecutions: [] as WorkflowRecord[],
liveEvents: [] as Array<WorkflowEventPayload & { _ts: number }>,
pendingApproval: null as PendingApproval | null,
loading: false,
error: null as string | null,
})
/** 清除错误状态(供 toast 显示后重置,允许连续同值错误再次触发 watch) */
export function clearError() { state.error = null }
/** 工作流事件监听 unlisten 句柄(单例,workflow 子 store 读写) */
export let _eventUnlisten: (() => void) | null = null
/** 内部重置 unlisten 句柄(workflow 子 store stopEventListener/startEventListener 用) */
export function setEventUnlisten(fn: (() => void) | null) {
_eventUnlisten = fn
}

View File

@@ -0,0 +1,48 @@
import { taskApi } from '../../api'
import { state } from './state'
/** 任务 CRUD 子 store(共享全局 state) */
export function createTasksStore() {
async function loadTasks(projectId?: string) {
try {
state.tasks = await taskApi.list(projectId)
} catch (e: any) {
state.error = e?.toString() ?? '加载任务失败'
}
}
async function createTask(input: { project_id: string; title: string; description?: string; priority?: number; branch_name?: string; assignee?: string }) {
try {
const record = await taskApi.create(input)
state.tasks.push(record)
return record
} catch (e: any) {
state.error = e?.toString() ?? '创建任务失败'
return null
}
}
async function updateTask(id: string, field: string, value: string) {
await taskApi.update(id, field, value)
const idx = state.tasks.findIndex(t => t.id === id)
if (idx >= 0) {
(state.tasks[idx] as any)[field] = value
}
}
async function deleteTask(id: string) {
try {
await taskApi.delete(id)
state.tasks = state.tasks.filter(t => t.id !== id)
} catch (e: any) {
state.error = e?.toString() ?? '删除任务失败'
}
}
return {
loadTasks,
createTask,
updateTask,
deleteTask,
}
}

View File

@@ -0,0 +1,128 @@
import { workflowApi } from '../../api'
import { state, _eventUnlisten, setEventUnlisten } from './state'
/**
* 工作流 + 审批子 store(共享全局 state)。
*
* 越层 invoke(approve_human_approval / cancel_workflow_node) 已下沉到
* src/api/workflow.ts,本子 store 不再直接 invoke IPC。
*/
export function createWorkflowStore() {
async function runWorkflow(name: string, dag: unknown, config?: Record<string, unknown>) {
return await workflowApi.run(name, dag, config)
}
async function loadWorkflowExecutions() {
try {
state.workflowExecutions = await workflowApi.listExecutions()
} catch (e: any) {
state.error = e?.toString() ?? '加载工作流记录失败'
}
}
async function startEventListener() {
if (_eventUnlisten) return _eventUnlisten
const unlisten = await workflowApi.onEvent((payload) => {
try {
// 固时间戳(FR-C1):formattedEvents computed 重算时用事件发生时刻,非当前时刻
state.liveEvents.push({ ...payload, _ts: Date.now() })
// 限长(FR-R3):防 liveEvents 无限增长撑爆内存,保留最近 200 条
if (state.liveEvents.length > 200) {
state.liveEvents.splice(0, state.liveEvents.length - 200)
}
// 处理人工审批请求
// WorkflowEvent serde tag=type rename_all=snake_case → type='human_approval_request',字段扁平(无 data 包装)(B-03b-R7)
if (payload.event?.type === 'human_approval_request') {
state.pendingApproval = payload.event as unknown as typeof state.pendingApproval
}
} catch (e) {
console.error('处理工作流事件失败:', e, payload)
}
})
setEventUnlisten(unlisten)
return unlisten
}
function clearLiveEvents() {
state.liveEvents = []
}
/**
* 发送审批响应。
*
* 统一以 decisions 数组表达选择,按 pendingApproval.select_type 判定单/多选:
* - 单选(select_type='single' 或缺省):取 decisions[0] 作为后端 decision 单值;
* - 多选(select_type='multiple'):decisions 透传全量。
*
* CR-11⑪:签名保持 approveHumanApproval(decisions: string[], comment?)。
* F-260615-01: decisions/select_type 透传,与后端 IPC 签名对齐。
*/
async function approveHumanApproval(
decisions: string[],
comment?: string,
) {
if (!state.pendingApproval) return
const selectType = state.pendingApproval.select_type ?? 'single'
// 单选取首项(后端 decision 为单值);多选 decision 取首项作占位(后端按 decisions 走)
const decision = selectType === 'multiple'
? (decisions[0] ?? '')
: (decisions[0] ?? '')
try {
// R-PD-5: 透传 options 闭环后端校验(options 非空时 decision 必须 ∈ options,否则 IPC 报错)。
// options 来自已收到的 HumanApprovalRequest 事件 payload(IPC 无法访问节点 config)。
// B-260615-34:snake_case 由 api 层对齐后端 workflow.rs:211
await workflowApi.approveHumanApproval({
execution_id: state.pendingApproval.execution_id,
node_id: state.pendingApproval.node_id,
decision,
comment,
options: state.pendingApproval.options ?? [],
decisions,
select_type: selectType,
})
// 清除待审批状态
state.pendingApproval = null
} catch (error) {
console.error('审批失败:', error)
}
}
/** 取消当前待审批节点(调 cancel_workflow_node IPC 置节点 Cancelled */
async function cancelHumanApproval() {
if (!state.pendingApproval) return
try {
await workflowApi.cancelHumanApproval({
execution_id: state.pendingApproval.execution_id,
node_id: state.pendingApproval.node_id,
})
// 清除待审批状态
state.pendingApproval = null
} catch (error) {
console.error('取消审批失败:', error)
}
}
function stopEventListener() {
if (_eventUnlisten) {
try {
_eventUnlisten()
} catch (e) {
console.error('停止事件监听失败:', e)
}
setEventUnlisten(null)
}
}
return {
runWorkflow,
loadWorkflowExecutions,
startEventListener,
stopEventListener,
clearLiveEvents,
approveHumanApproval,
cancelHumanApproval,
}
}