import { reactive, computed } from 'vue' import { listen } from '@tauri-apps/api/event' import { createProjectsStore } from './project/projects' import { createTasksStore } from './project/tasks' import { createIdeasStore } from './project/ideas' import { createWorkflowStore } from './project/workflow' import { state, clearError } from './project/state' import { taskApi } from '@/api/task' import type { DfDataChangedPayload, TaskQuery, IdeaQuery } from '@/api/types' // ── 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() { const projectsStore = createProjectsStore() const tasksStore = createTasksStore() const ideasStore = createIdeasStore() const workflowStore = createWorkflowStore() // ── AR-11(方案A):数据变更联动刷新 ── // 后端 AI 工具(create/update/delete/restore/purge/bind_directory)执行成功后 emit // "df-data-changed" { entity, action },前端按 entity 调对应 load 刷新列表, // 取代工具执行后需手动刷新。 // // CR-260615-26(R-1): task 分支不再无参 loadTasks(会把 store.tasks 冲成全量,破坏 // B-260615-29「store.tasks 反映当前筛选」契约)。改为读 _activeTaskProject: // - undefined: Tasks 视图未挂载/从未设过筛选 → 不触发 task load(避免破坏筛选, // 让 B-29 watch/handle 主导;此时改动由用户下次进 Tasks 自然刷新) // - 'all' : 全量拉取 // - 具体 id : 按项目拉取 // 由 Tasks.vue 经 setActiveTaskProject 在 onMounted/watch 更新本变量。 // // F-260621-02(P1 status 下沉):新增 _activeTaskStatus 追踪当前 status 筛选,数据变更 // 联动刷新时一并下沉到后端 query(保 B-29「store.tasks 反映当前筛选」契约, // 避免刷新把已选 status 冲掉)。 let _activeTaskProject: string | undefined = undefined let _activeTaskStatus: string | undefined = undefined // WK-10:Tasks 视图搜索关键字登记(数据变更联动刷新时一并下沉 keyword,避免冲掉搜索结果) let _activeTaskKeyword: string | undefined = undefined function setActiveTaskProject(projectKey: string | undefined) { _activeTaskProject = projectKey } function setActiveTaskStatus(statusKey: string | undefined) { _activeTaskStatus = statusKey } function setActiveTaskKeyword(keyword: string | undefined) { _activeTaskKeyword = keyword } // 构造当前 task 视图筛选对应的 query(供数据变更 listener 复用同一筛选拉取) function buildActiveTaskQuery(): TaskQuery | undefined { if (_activeTaskProject === undefined) return undefined // 视图未挂载,不触发 const query: TaskQuery = {} if (_activeTaskProject !== 'all') query.project_id = _activeTaskProject if (_activeTaskStatus && _activeTaskStatus !== 'all') query.status = _activeTaskStatus if (_activeTaskKeyword) query.keyword = _activeTaskKeyword return query } // WK-4:Ideas 视图当前查询登记(数据变更联动刷新按当前筛选重载,不再无参 loadIdeas 全量冲掉筛选)。 // undefined = Ideas 视图未挂载(如 Dashboard)→ 退化为全量刷新,保持面板统计新鲜。 let _activeIdeaQuery: IdeaQuery | undefined = undefined function setActiveIdeaQuery(q: IdeaQuery | undefined) { _activeIdeaQuery = q } let _dataChangedUnlisten: (() => void) | null = null async function startDataChangedListener() { if (_dataChangedUnlisten) return _dataChangedUnlisten _dataChangedUnlisten = await listen('df-data-changed', (event) => { const { entity } = event.payload // 按 entity 调对应 load 刷新(CR-260615-19:action 当前未消费,为未来 delete 本地移除等差异化刷新预留,统一 load 全量拉取) if (entity === 'project') { void projectsStore.loadProjects() } else if (entity === 'task') { // 无筛选状态(_activeTaskProject undefined)时不触发,保 B-29 筛选契约; // 有筛选则按当前 activeProject + activeStatus 构造 query 下沉后端拉取 const q = buildActiveTaskQuery() if (q !== undefined) { void tasksStore.loadTasks(q) } // P1-b A0:统计走独立通道,任务变更(create/update/advance/delete)后刷新 // Dashboard 数字(与 Tasks 视图分页/筛选解耦,不受 q undefined 影响) void loadStats() } else if (entity === 'idea') { // WK-4:按 Ideas 视图当前 query 重载(未挂载时退化为全量刷新 Dashboard 面板) void ideasStore.loadIdeas(_activeIdeaQuery) } }) return _dataChangedUnlisten } function stopDataChangedListener() { if (_dataChangedUnlisten) { try { _dataChangedUnlisten() } catch (e) { console.error('停止数据变更监听失败:', e) } _dataChangedUnlisten = null } } // ── 统计(跨领域,留在组合器) ── // P1-b A0:activeTasks 改读 state.activeTaskCount(后端 COUNT 独立通道), // 不再 filter(state.tasks) —— state.tasks 是 Tasks 视图当前筛选+分页数据, // 直接 filter 会让 Dashboard 数字随 Tasks 翻页/筛选错乱。 const stats = computed(() => ({ ideas: state.ideas.length, projects: state.projects.length, activeTasks: state.activeTaskCount, drafts: state.ideas.filter(i => i.status === 'draft').length, })) // P1-b A0:统计拉取走后端 count_tasks(独立通道),与 tasks 数组解耦。 // activeTaskCount = 全局 in_progress 任务数;projectTaskCounts = 各项目 in_progress 任务数。 // tasks 变更(create/update/delete/advance)后由 df-data-changed listener 调用刷新。 // 注:projectTaskCounts 依赖 state.projects 已加载 —— 调用方需保证先 loadProjects。 // - Dashboard.loadAll:先 Promise.all([loadProjects, loadIdeas]) 完成再 loadStats // - df-data-changed task 分支:projects 由 onMounted 已加载,直接 loadStats 安全 async function loadStats() { try { state.activeTaskCount = await taskApi.count({ status: 'in_progress' } as TaskQuery) const entries = await Promise.all(state.projects.map(p => taskApi.count({ project_id: p.id, status: 'in_progress' } as TaskQuery).then(n => [p.id, n] as const) )) state.projectTaskCounts = Object.fromEntries(entries) } catch (e) { console.error('[loadStats] 统计拉取失败', e) } } return reactive({ // 状态用 getter 实时读 state —— 直接 ideas: state.ideas 会快照引用, // loadIdeas 等重新赋值 state.ideas 时返回对象的 ideas 属性不跟随,刷新后视图为空 get projects() { return state.projects }, get tasks() { return state.tasks }, // P1-b A0:统计缓存读口(供 ActiveProjectsPanel 读 projectTaskCounts) get activeTaskCount() { return state.activeTaskCount }, get projectTaskCounts() { return state.projectTaskCounts }, get ideas() { return state.ideas }, get workflowExecutions() { return state.workflowExecutions }, get liveEvents() { return state.liveEvents }, get loading() { return state.loading }, get error() { return state.error }, get deletedProjects() { return state.deletedProjects }, clearError, // project actions loadProjects: projectsStore.loadProjects, 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 loadTasks: tasksStore.loadTasks, createTask: tasksStore.createTask, updateTask: tasksStore.updateTask, deleteTask: tasksStore.deleteTask, // P1-b A0:统计独立通道(Dashboard 卡片 + ActiveProjectsPanel 各项目任务数) loadStats, // idea actions loadIdeas: ideasStore.loadIdeas, createIdea: ideasStore.createIdea, updateIdea: ideasStore.updateIdea, deleteIdea: ideasStore.deleteIdea, evaluateIdea: ideasStore.evaluateIdea, promoteIdea: ideasStore.promoteIdea, relateIdeas: ideasStore.relateIdeas, // workflow actions runWorkflow: workflowStore.runWorkflow, loadWorkflowExecutions: workflowStore.loadWorkflowExecutions, 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) startDataChangedListener, stopDataChangedListener, // CR-260615-26(R-1): Tasks 视图登记当前 activeProject 筛选,供 listener 按筛选拉取 setActiveTaskProject, // F-260621-02(P1 status 下沉):Tasks 视图登记当前 status 筛选,供 listener 复用同一筛选拉取 setActiveTaskStatus, // WK-10:Tasks 视图登记当前搜索关键字,供 listener 复用同一筛选拉取 setActiveTaskKeyword, // WK-4: Ideas 视图登记当前查询,供 listener 按筛选重载(不冲掉筛选视图) setActiveIdeaQuery, pendingApproval: computed(() => state.pendingApproval), // computed stats, }) } type ProjectStore = ReturnType let _storeInstance: ProjectStore | null = null /** 项目/任务/灵感/工作流 全局状态(单例,多组件复用同一 reactive 包装) */ export function useProjectStore(): ProjectStore { if (_storeInstance) return _storeInstance _storeInstance = createStore() return _storeInstance }