新增: 工具工作流补全(diff_files工具 + 技能清单注入 + 工作流进度共享 + human端到端测试 + 知识库MCP工具)
This commit is contained in:
@@ -288,6 +288,9 @@ export interface WorkflowEventPayload {
|
||||
}
|
||||
}
|
||||
|
||||
/** 工作流 DAG 节点实时状态(WorkflowDagDisplay 高亮 + workflow store 共享推导) */
|
||||
export type NodeStatus = 'pending' | 'running' | 'completed' | 'failed' | 'skipped'
|
||||
|
||||
// ============================================================
|
||||
// 通用
|
||||
// ============================================================
|
||||
|
||||
@@ -65,6 +65,8 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, nextTick } from 'vue'
|
||||
import type { NodeStatus } from '@/api/types'
|
||||
export type { NodeStatus }
|
||||
|
||||
interface DagNode {
|
||||
id: string
|
||||
@@ -76,7 +78,6 @@ interface EdgeDef {
|
||||
target: string
|
||||
condition?: string | null
|
||||
}
|
||||
export type NodeStatus = 'pending' | 'running' | 'completed' | 'failed' | 'skipped'
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
dagJson: string
|
||||
|
||||
@@ -35,6 +35,8 @@ export default {
|
||||
emptyTasks: 'No tasks yet',
|
||||
// Workflow log panel
|
||||
workflowLogTitle: '📋 Workflow Log',
|
||||
// B-41 workflow progress panel
|
||||
workflowProgressTitle: '📈 Workflow Progress',
|
||||
runDemoWorkflow: '▶ Run Demo Workflow',
|
||||
emptyWorkflowLog: 'Click "Run Demo Workflow" to view live logs',
|
||||
// Approval dialog
|
||||
|
||||
@@ -35,6 +35,8 @@ export default {
|
||||
emptyTasks: '暂无任务',
|
||||
// 工作流日志面板
|
||||
workflowLogTitle: '📋 工作流日志',
|
||||
// B-41 工作流实时进度面板
|
||||
workflowProgressTitle: '📈 工作流实时进度',
|
||||
runDemoWorkflow: '▶ 运行测试工作流',
|
||||
emptyWorkflowLog: '点击"运行测试工作流"查看实时日志',
|
||||
// 审批对话框
|
||||
|
||||
@@ -168,6 +168,8 @@ function createStore() {
|
||||
startEventListener: workflowStore.startEventListener,
|
||||
stopEventListener: workflowStore.stopEventListener,
|
||||
clearLiveEvents: workflowStore.clearLiveEvents,
|
||||
// B-41: 工作流进度共享推导(按 execId 派生节点状态/进度)
|
||||
workflowProgress: workflowStore.workflowProgress,
|
||||
approveHumanApproval: workflowStore.approveHumanApproval,
|
||||
cancelHumanApproval: workflowStore.cancelHumanApproval,
|
||||
// AR-11(方案A):数据变更联动刷新监听(后端工具执行后 emit df-data-changed)
|
||||
|
||||
@@ -1,7 +1,79 @@
|
||||
import { workflowApi } from '@/api'
|
||||
import { t } from '@/i18n/i18n-helpers'
|
||||
import type { NodeStatus, WorkflowEventPayload } from '@/api/types'
|
||||
import { state, _eventUnlisten, setEventUnlisten } from './state'
|
||||
|
||||
// B-41: 工作流进度共享推导。liveEvents 已全局收集所有 workflow-event,
|
||||
// 此处按 execution_id 重放派生节点状态/进度,供 TaskDetail/ProjectDetail 同源使用,
|
||||
// 替代各自维护一份 onEvent 累加状态(单事实源,并发工作流互不干扰)。
|
||||
export interface WorkflowProgress {
|
||||
nodeStatuses: Record<string, NodeStatus>
|
||||
runningNode: string
|
||||
doneCount: number
|
||||
totalNodes: number
|
||||
result: 'completed' | 'failed' | null
|
||||
}
|
||||
|
||||
function deriveWorkflowProgress(
|
||||
execId: string | null,
|
||||
events: WorkflowEventPayload[],
|
||||
): WorkflowProgress {
|
||||
const empty: WorkflowProgress = {
|
||||
nodeStatuses: {},
|
||||
runningNode: '',
|
||||
doneCount: 0,
|
||||
totalNodes: 0,
|
||||
result: null,
|
||||
}
|
||||
if (!execId) return empty
|
||||
const nodeStatuses: Record<string, NodeStatus> = {}
|
||||
let runningNode = ''
|
||||
let doneCount = 0
|
||||
let totalNodes = 0
|
||||
let result: 'completed' | 'failed' | null = null
|
||||
for (const payload of events) {
|
||||
if (payload.execution_id !== execId) continue
|
||||
const evt = payload.event
|
||||
switch (evt.type) {
|
||||
case 'node_started': {
|
||||
const node = String(evt.node_id ?? '')
|
||||
if (node) {
|
||||
nodeStatuses[node] = 'running'
|
||||
runningNode = node
|
||||
}
|
||||
// 后端模板节点数前端无法预知,用「已启动 + 已完成」近似 total
|
||||
totalNodes = Math.max(totalNodes, doneCount + 1)
|
||||
break
|
||||
}
|
||||
case 'node_completed': {
|
||||
if (runningNode) nodeStatuses[runningNode] = 'completed'
|
||||
runningNode = ''
|
||||
doneCount += 1
|
||||
break
|
||||
}
|
||||
case 'node_failed': {
|
||||
const node = String(evt.node_id ?? '')
|
||||
if (node) nodeStatuses[node] = 'failed'
|
||||
break
|
||||
}
|
||||
case 'node_cancelled': {
|
||||
const node = String(evt.node_id ?? '')
|
||||
if (node) nodeStatuses[node] = 'skipped'
|
||||
break
|
||||
}
|
||||
case 'workflow_completed':
|
||||
result = 'completed'
|
||||
break
|
||||
case 'workflow_failed':
|
||||
result = 'failed'
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
return { nodeStatuses, runningNode, doneCount, totalNodes, result }
|
||||
}
|
||||
|
||||
// B-03b-R10(⑦ 互斥): 审批响应/取消 IPC 进行中标记(模块级单例,createWorkflowStore 每会话只跑一次)。
|
||||
// 防双击/连点在同一轮 IPC 未返回前发起第二次(后端无幂等键,第二次会重复消费 Request 触发
|
||||
// "node already completed" 类报错)。store 层守卫优于按钮 disabled: 共享单例 state,跨调用方统一。
|
||||
@@ -77,6 +149,14 @@ export function createWorkflowStore() {
|
||||
state.liveEvents = []
|
||||
}
|
||||
|
||||
/**
|
||||
* B-41: 按 execution_id 派生工作流进度(读全局 liveEvents,响应式)。
|
||||
* 供 TaskDetail(自身推进的 execId)/ ProjectDetail(最近一次执行)共用同源状态。
|
||||
*/
|
||||
function workflowProgress(execId: string | null): WorkflowProgress {
|
||||
return deriveWorkflowProgress(execId, state.liveEvents)
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送审批响应。
|
||||
*
|
||||
@@ -165,6 +245,7 @@ export function createWorkflowStore() {
|
||||
startEventListener,
|
||||
stopEventListener,
|
||||
clearLiveEvents,
|
||||
workflowProgress,
|
||||
approveHumanApproval,
|
||||
cancelHumanApproval,
|
||||
}
|
||||
|
||||
@@ -310,6 +310,22 @@
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<!-- B-41 工作流实时进度:最近一次执行的 DAG + 节点状态高亮(共享 workflow store 派生) -->
|
||||
<section v-if="hasWfProgress" class="panel wf-progress-panel">
|
||||
<div class="panel-header">
|
||||
<h2>{{ $t('projectDetail.workflowProgressTitle') }}</h2>
|
||||
</div>
|
||||
<div class="wf-progress">
|
||||
<span v-if="wfProgress.runningNode">{{ $t('taskDetail.workflowStepRunning', { node: wfProgress.runningNode }) }}</span>
|
||||
<span v-else-if="wfProgress.doneCount > 0" class="wf-progress-count">
|
||||
{{ $t('taskDetail.workflowStepsProgress', { done: wfProgress.doneCount, total: wfProgress.totalNodes }) }}
|
||||
</span>
|
||||
<span v-if="wfProgress.result === 'completed'" class="wf-progress-hint">{{ $t('taskDetail.workflowCompletedHint') }}</span>
|
||||
<span v-if="wfProgress.result === 'failed'" class="wf-progress-hint wf-progress-hint-fail">{{ $t('taskDetail.workflowFailedHint') }}</span>
|
||||
</div>
|
||||
<WorkflowDagDisplay v-if="wfDagJson" :dag-json="wfDagJson" :node-statuses="wfProgress.nodeStatuses" />
|
||||
</section>
|
||||
|
||||
<!-- 工作流日志:无记录时折叠为摘要(标题 + 计数),有记录才展开列表 -->
|
||||
<section v-if="formattedEvents.length > 0 || !workflowLogCollapsed" class="panel workflow-log-panel">
|
||||
<div class="panel-header">
|
||||
@@ -396,7 +412,7 @@ import { useI18n } from 'vue-i18n'
|
||||
import { Message } from '@arco-design/web-vue'
|
||||
import { open } from '@tauri-apps/plugin-dialog'
|
||||
import { useProjectStore } from '@/stores/project'
|
||||
import { projectApi, taskApi } from '@/api'
|
||||
import { projectApi, taskApi, workflowApi } from '@/api'
|
||||
import { moduleApi, type ProjectModuleRecord } from '@/api/module'
|
||||
import { formatDate } from '@/utils/time'
|
||||
import { parseStack } from '@/utils/project'
|
||||
@@ -406,6 +422,7 @@ import ConfirmDialog from '@/components/ConfirmDialog.vue'
|
||||
import ApprovalDialog from '@/components/project/ApprovalDialog.vue'
|
||||
import FileExplorer from '@/components/project/FileExplorer.vue'
|
||||
import DependencyGraph from '@/components/project/DependencyGraph.vue'
|
||||
import WorkflowDagDisplay from '@/components/workflow/WorkflowDagDisplay.vue'
|
||||
import { useConfirm } from '@/composables/useConfirm'
|
||||
import { useRendered } from '@/composables/useMarkdown'
|
||||
import type { ProjectId, TaskRecord } from '@/api/types'
|
||||
@@ -416,7 +433,6 @@ const store = useProjectStore()
|
||||
const { t, locale } = useI18n()
|
||||
const logListRef = ref<HTMLElement | null>(null)
|
||||
const showApprovalDialog = ref(false)
|
||||
let unlisten: (() => void) | null = null
|
||||
|
||||
// Tab 导航:概览(项目信息/任务/工作流日志) vs 文件浏览器(Batch 10)。
|
||||
// P1-g+ 问题10④:activeTab 持久化 localStorage(按项目隔离,切换项目不串扰)。
|
||||
@@ -672,6 +688,26 @@ const formattedEvents = computed<LogEntry[]>(() => {
|
||||
})
|
||||
})
|
||||
|
||||
// ── B-41 工作流实时进度 ──
|
||||
// ProjectDetail 无工作流推进入口,但审批弹窗/日志面板已接 liveEvents;此处展示最近一次
|
||||
// 工作流的 DAG + 节点状态高亮(共享 workflow store 派生,与 TaskDetail 同源),
|
||||
// 手动跑工作流时也能看到节点实时进度。无事件(本次会话没跑过)时整个面板不渲染。
|
||||
const wfDagJson = ref('')
|
||||
const latestWfExecId = computed(() => {
|
||||
const evs = store.liveEvents
|
||||
return evs.length > 0 ? evs[evs.length - 1].execution_id : null
|
||||
})
|
||||
const wfProgress = computed(() => store.workflowProgress(latestWfExecId.value))
|
||||
const hasWfProgress = computed(() => Object.keys(wfProgress.value.nodeStatuses).length > 0)
|
||||
watch(latestWfExecId, async (id) => {
|
||||
wfDagJson.value = ''
|
||||
if (!id) return
|
||||
try {
|
||||
const record = await workflowApi.getExecution(id)
|
||||
if (record?.dag_json) wfDagJson.value = record.dag_json
|
||||
} catch { /* 静默 */ }
|
||||
}, { immediate: true })
|
||||
|
||||
// ── 新建任务 ──
|
||||
const showNewTaskModal = ref(false)
|
||||
const newTaskTitle = ref('')
|
||||
@@ -822,7 +858,7 @@ onMounted(async () => {
|
||||
// 来源灵感回溯:项目有 idea_id 但 store.ideas 为空(本页未 load 过)时补拉,
|
||||
// 否则 sourceIdea computed 永远找不到对应灵感记录
|
||||
await store.loadIdeas()
|
||||
unlisten = await store.startEventListener()
|
||||
await store.startEventListener()
|
||||
await checkPath()
|
||||
})
|
||||
|
||||
@@ -838,10 +874,9 @@ watch(projectId, async (newId) => {
|
||||
watch(() => currentProject.value?.path, () => { checkPath() })
|
||||
|
||||
onUnmounted(() => {
|
||||
if (unlisten) {
|
||||
unlisten()
|
||||
unlisten = null
|
||||
}
|
||||
// 用 store.stopEventListener 统一停全局监听并复位句柄(直接调 unlisten() 停监听但
|
||||
// 不复位 _eventUnlisten,后续 startEventListener 幂等会误返回已死句柄,共享化后必查)
|
||||
store.stopEventListener()
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -1205,6 +1240,20 @@ onUnmounted(() => {
|
||||
}
|
||||
.workflow-log-panel { flex: 0 0 auto; }
|
||||
|
||||
/* B-41 工作流实时进度面板(与 TaskDetail wf-progress 同款视觉) */
|
||||
.wf-progress-panel { flex: 0 0 auto; }
|
||||
.wf-progress {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
margin-top: 6px;
|
||||
font-size: 12px;
|
||||
color: var(--df-text-secondary);
|
||||
}
|
||||
.wf-progress-count { color: var(--df-text-dim); }
|
||||
.wf-progress-hint { color: var(--df-success); }
|
||||
.wf-progress-hint-fail { color: var(--df-danger); }
|
||||
|
||||
/* ===== 面板 ===== */
|
||||
/* .panel / .panel-header 基础样式已收敛至全局 components.css(DRY 收口 B-260619),
|
||||
此处仅保留本组件特有 .task-count。 */
|
||||
|
||||
+33
-78
@@ -237,6 +237,7 @@ import { useRoute, useRouter } from 'vue-router'
|
||||
import { listen } from '@tauri-apps/api/event'
|
||||
import { taskApi, projectApi, ideaApi } from '@/api'
|
||||
import { workflowApi } from '@/api'
|
||||
import { useProjectStore } from '@/stores/project'
|
||||
import { formatDate } from '@/utils/time'
|
||||
import { useRendered } from '@/composables/useMarkdown'
|
||||
import {
|
||||
@@ -245,14 +246,15 @@ import {
|
||||
priorityLabel,
|
||||
priorityClass,
|
||||
} from '../constants/project'
|
||||
import type { TaskRecord, ProjectRecord, IdeaRecord, DfDataChangedPayload, WorkflowEventPayload } from '@/api/types'
|
||||
import type { TaskRecord, ProjectRecord, IdeaRecord, DfDataChangedPayload } from '@/api/types'
|
||||
import TaskOutputCard from '@/components/task/TaskOutputCard.vue'
|
||||
import WorkflowDagDisplay from '@/components/workflow/WorkflowDagDisplay.vue'
|
||||
import type { NodeStatus } from '@/components/workflow/WorkflowDagDisplay.vue'
|
||||
|
||||
const { t } = useI18n()
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
// B-41: 共享 workflow store(liveEvents 全局收集 + workflowProgress 派生进度)
|
||||
const store = useProjectStore()
|
||||
|
||||
const loading = ref(true)
|
||||
const errorMsg = ref('')
|
||||
@@ -386,32 +388,47 @@ async function confirmSubtask() {
|
||||
// F-260616-06 ①-1 / B-41 工作流推进状态(与手动 advance 的 advancing 独立,互不干扰)
|
||||
// ------------------------------------------------------------
|
||||
// wfAdvancing:推进中态;wfExecId:当前监听的工作流执行 ID(过滤 workflow-event);
|
||||
// wfTotalNodes/wfDoneCount/wfRunningNode:轻量进度(听 NodeStarted/NodeCompleted);
|
||||
// wfTotalNodes/wfDoneCount/wfRunningNode:轻量进度(由共享 store 按 execId 派生);
|
||||
// wfResult: 'completed' | 'failed' | null —— 终态提示,完成/失败后自动清空(由新一次推进重置)。
|
||||
const wfAdvancing = ref(false)
|
||||
const wfExecId = ref<string | null>(null)
|
||||
const wfTotalNodes = ref(0)
|
||||
const wfDoneCount = ref(0)
|
||||
const wfRunningNode = ref<string>('')
|
||||
const wfResult = ref<'completed' | 'failed' | null>(null)
|
||||
// SW-260618-21: 终态提示 timer 引用,卸载时清理防写已销毁 ref(对齐 Projects.vue _toastTimer 模式)
|
||||
let _wfResultTimer: ReturnType<typeof setTimeout> | null = null
|
||||
|
||||
// B-41: 共享工作流进度推导。workflow store 已全局收集 liveEvents,此处按 execId
|
||||
// 派生节点状态/进度,与 ProjectDetail 同源(store.workflowProgress),替代原本地 onEvent 逐事件累加。
|
||||
const wfProgress = computed(() => store.workflowProgress(wfExecId.value))
|
||||
const wfNodeStatuses = computed(() => wfProgress.value.nodeStatuses)
|
||||
const wfDoneCount = computed(() => wfProgress.value.doneCount)
|
||||
const wfTotalNodes = computed(() => wfProgress.value.totalNodes)
|
||||
const wfRunningNode = computed(() => wfProgress.value.runningNode)
|
||||
const wfDoneTotal = computed(() => wfTotalNodes.value)
|
||||
|
||||
const wfProgressHint = computed(() => wfResult.value !== null)
|
||||
const wfCompletedHint = computed(() => wfResult.value === 'completed')
|
||||
const wfFailedHint = computed(() => wfResult.value === 'failed')
|
||||
|
||||
// 工作流 DAG 结构展示
|
||||
// 终态 → 瞬态提示:派生 result 置位时收 wfAdvancing + 3s 后清 wfResult(仅 UI hint,数据不改)
|
||||
watch(() => wfProgress.value.result, (r) => {
|
||||
if (!r) return
|
||||
wfResult.value = r
|
||||
wfAdvancing.value = false
|
||||
if (_wfResultTimer) clearTimeout(_wfResultTimer)
|
||||
_wfResultTimer = setTimeout(() => {
|
||||
if (wfResult.value === r) wfResult.value = null
|
||||
_wfResultTimer = null
|
||||
}, 3000)
|
||||
})
|
||||
|
||||
// 工作流 DAG 结构展示(执行记录读回,节点状态由共享派生)
|
||||
const wfDagJson = ref('')
|
||||
const wfNodeStatuses = ref<Record<string, NodeStatus>>({})
|
||||
async function refreshWorkflowDag(execId: string) {
|
||||
wfNodeStatuses.value = {}
|
||||
try {
|
||||
const record = await workflowApi.getExecution(execId)
|
||||
if (record?.dag_json) wfDagJson.value = record.dag_json
|
||||
} catch { /* 静默 */ }
|
||||
}
|
||||
const wfDoneTotal = computed(() => wfTotalNodes.value)
|
||||
|
||||
const taskId = computed(() => route.params.id as string)
|
||||
|
||||
@@ -568,9 +585,6 @@ async function handleWorkflowAdvance(target: string) {
|
||||
if (!task.value || wfAdvancing.value || advancing.value) return
|
||||
wfAdvancing.value = true
|
||||
wfResult.value = null
|
||||
wfRunningNode.value = ''
|
||||
wfDoneCount.value = 0
|
||||
wfTotalNodes.value = 0
|
||||
try {
|
||||
const execId = await workflowApi.run(
|
||||
`task-advance:${target}`,
|
||||
@@ -589,64 +603,8 @@ async function handleWorkflowAdvance(target: string) {
|
||||
}
|
||||
}
|
||||
|
||||
// B-41: 处理单个工作流事件(由 onEvent 回调 dispatch)。按 execution_id 过滤当前推进。
|
||||
function handleWorkflowEvent(payload: WorkflowEventPayload) {
|
||||
if (!wfExecId.value || payload.execution_id !== wfExecId.value) return
|
||||
const evt = payload.event
|
||||
switch (evt?.type) {
|
||||
case 'node_started': {
|
||||
// 第一次 NodeStarted 时初始化 total(后端模板节点数,无法前端预知,
|
||||
// 用「已启动 + 已完成」近似 total,显示已完成/已启动进度)
|
||||
const node = String(evt.node_id ?? '')
|
||||
wfRunningNode.value = node
|
||||
wfNodeStatuses.value = { ...wfNodeStatuses.value, [node]: 'running' }
|
||||
if (wfTotalNodes.value === 0) wfTotalNodes.value = 1
|
||||
else wfTotalNodes.value = Math.max(wfTotalNodes.value, wfDoneCount.value + 1)
|
||||
break
|
||||
}
|
||||
case 'node_completed': {
|
||||
wfDoneCount.value += 1
|
||||
if (wfRunningNode.value) {
|
||||
wfNodeStatuses.value = { ...wfNodeStatuses.value, [wfRunningNode.value]: 'completed' }
|
||||
}
|
||||
wfRunningNode.value = ''
|
||||
break
|
||||
}
|
||||
case 'workflow_completed': {
|
||||
wfAdvancing.value = false
|
||||
wfRunningNode.value = ''
|
||||
wfResult.value = 'completed'
|
||||
// 终态提示保留 3s 后清空(任务本体由 df-data-changed 刷新,提示不影响数据)
|
||||
if (_wfResultTimer) clearTimeout(_wfResultTimer)
|
||||
_wfResultTimer = setTimeout(() => {
|
||||
if (wfResult.value === 'completed') wfResult.value = null
|
||||
_wfResultTimer = null
|
||||
}, 3000)
|
||||
break
|
||||
}
|
||||
case 'workflow_failed':
|
||||
case 'node_failed': {
|
||||
if (evt?.node_id) {
|
||||
const node = String(evt.node_id)
|
||||
wfNodeStatuses.value = { ...wfNodeStatuses.value, [node]: 'failed' }
|
||||
}
|
||||
if (evt?.type === 'workflow_failed') {
|
||||
wfAdvancing.value = false
|
||||
wfRunningNode.value = ''
|
||||
wfResult.value = 'failed'
|
||||
if (_wfResultTimer) clearTimeout(_wfResultTimer)
|
||||
_wfResultTimer = setTimeout(() => {
|
||||
if (wfResult.value === 'failed') wfResult.value = null
|
||||
_wfResultTimer = null
|
||||
}, 3000)
|
||||
}
|
||||
// node_failed: 单节点失败,工作流后续会发 workflow_failed,此处不提前改终态
|
||||
break
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
// B-41: 进度状态已由共享 store 按 execId 派生(store.workflowProgress),不再本地逐事件累加。
|
||||
// 原有 handleWorkflowEvent 已移除 —— 事件仍由 workflow store 全局收集,TaskDetail 只读派生结果。
|
||||
|
||||
async function load() {
|
||||
loading.value = true
|
||||
@@ -689,8 +647,6 @@ watch(renderedDesc, () => { measureDescHeight() })
|
||||
// 不享受 store 全局 df-data-changed 监听(该监听只刷 store.tasks 列表,不含本视图的当前 task 单体),
|
||||
// 故此本地监听 entity=task/project 时重载当前 task。
|
||||
let _unlistenDataChanged: (() => void) | null = null
|
||||
// B-41: 工作流推进事件 unlistener(听 NodeStarted/NodeCompleted/WorkflowFailed 显示轻量进度)
|
||||
let _unlistenWorkflowEvent: (() => void) | null = null
|
||||
|
||||
onMounted(async () => {
|
||||
ensureLoaded() // 后台预热 Markdown 渲染器(模块单例,与 AiChat 共享),不阻塞 load
|
||||
@@ -709,18 +665,17 @@ onMounted(async () => {
|
||||
} catch (e) {
|
||||
console.error('[TaskDetail] 启动 df-data-changed 监听失败:', e)
|
||||
}
|
||||
// B-41: 工作流推进事件监听(复用 workflowApi.onEvent, 与 workflow store 的全局监听并存无冲突 —
|
||||
// 多个 listen 各自接收全量事件,本视图按 execution_id 过滤只处理自己发起的推进)
|
||||
// B-41: 工作流进度由 workflow store 全局 liveEvents 推导(store.workflowProgress),
|
||||
// 此处只需确保 store 事件监听已启动(幂等,共享单例;按 execution_id 过滤由派生层完成)。
|
||||
try {
|
||||
_unlistenWorkflowEvent = await workflowApi.onEvent(handleWorkflowEvent)
|
||||
await store.startEventListener()
|
||||
} catch (e) {
|
||||
console.error('[TaskDetail] 启动 workflow-event 监听失败:', e)
|
||||
console.error('[TaskDetail] 启动 workflow 事件监听失败:', e)
|
||||
}
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
if (_unlistenDataChanged) { _unlistenDataChanged(); _unlistenDataChanged = null }
|
||||
if (_unlistenWorkflowEvent) { _unlistenWorkflowEvent(); _unlistenWorkflowEvent = null }
|
||||
// F-260805:移除子任务快捷菜单关闭监听
|
||||
document.removeEventListener('click', closeChildMenu)
|
||||
// SW-260618-21: 清终态提示 timer 防卸载后写已销毁 ref
|
||||
|
||||
Reference in New Issue
Block a user