From d1321af880e0f1dc7cf1fb172c4f02ac3f46ce33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=BB=9D=E5=B0=98?= <237809796@qq.com> Date: Sun, 2 Aug 2026 12:29:44 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D:=20P1-b=20Dashboard=E7=BB=9F?= =?UTF-8?q?=E8=AE=A1=E6=BA=90=E8=A7=A3=E8=80=A6(=E6=B2=BB=E7=BF=BB?= =?UTF-8?q?=E9=A1=B5/=E7=AD=9B=E9=80=89=E5=90=8E=E7=BB=9F=E8=AE=A1?= =?UTF-8?q?=E6=95=B0=E5=AD=97=E9=94=99=E4=B9=B1=20H6/H7)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 统计走后端 count_tasks 独立通道,不再 filter(state.tasks) 当前页数据 - state 加 activeTaskCount/projectTaskCounts 缓存,loadStats action 并发 count 填充 + df-data-changed task 分支联动刷新 - Dashboard loadAll 删 loadTasks 改 loadStats(不再消费 state.tasks 全量) - ActiveProjectsPanel getProjectTaskCount 改读 store.projectTaskCounts - 后端零改(复用已注册 count_tasks IPC + count_by_query) --- .../dashboard/ActiveProjectsPanel.vue | 5 ++- src/stores/project.ts | 32 ++++++++++++++++++- src/stores/project/state.ts | 3 ++ src/views/Dashboard.vue | 9 ++++-- 4 files changed, 45 insertions(+), 4 deletions(-) diff --git a/src/components/dashboard/ActiveProjectsPanel.vue b/src/components/dashboard/ActiveProjectsPanel.vue index ffd8d78..cd94856 100644 --- a/src/components/dashboard/ActiveProjectsPanel.vue +++ b/src/components/dashboard/ActiveProjectsPanel.vue @@ -51,8 +51,11 @@ function goDetail(projectId: string) { router.push(`/projects/${projectId}`) } +// P1-b A0:统计走后端 count_tasks 独立通道,不再 filter(store.tasks)。 +// store.tasks 是 Tasks 视图当前筛选+分页数据,filter 会让面板数字随 Tasks 翻页/筛选错乱。 +// store.projectTaskCounts[projectId] = 后端该项目 in_progress 任务数(loadStats 填充)。 function getProjectTaskCount(projectId: string): number { - return store.tasks.filter(t => t.project_id === projectId && t.status === 'in_progress').length + return store.projectTaskCounts[projectId] ?? 0 } // 阶段 label key:projectStageInfo 返回的 stage 值(coding/testing/release/planning) diff --git a/src/stores/project.ts b/src/stores/project.ts index 09d0353..4380943 100644 --- a/src/stores/project.ts +++ b/src/stores/project.ts @@ -5,6 +5,7 @@ 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 } from '@/api/types' // ── barrel 组合器(零行为变更,纯重构) ── @@ -71,6 +72,9 @@ function createStore() { if (q !== undefined) { void tasksStore.loadTasks(q) } + // P1-b A0:统计走独立通道,任务变更(create/update/advance/delete)后刷新 + // Dashboard 数字(与 Tasks 视图分页/筛选解耦,不受 q undefined 影响) + void loadStats() } else if (entity === 'idea') { void ideasStore.loadIdeas() } @@ -90,18 +94,42 @@ function createStore() { } // ── 统计(跨领域,留在组合器) ── + // 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.tasks.filter(t => t.status === 'in_progress').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 }, @@ -124,6 +152,8 @@ function createStore() { createTask: tasksStore.createTask, updateTask: tasksStore.updateTask, deleteTask: tasksStore.deleteTask, + // P1-b A0:统计独立通道(Dashboard 卡片 + ActiveProjectsPanel 各项目任务数) + loadStats, // idea actions loadIdeas: ideasStore.loadIdeas, createIdea: ideasStore.createIdea, diff --git a/src/stores/project/state.ts b/src/stores/project/state.ts index 2a17d90..b44960f 100644 --- a/src/stores/project/state.ts +++ b/src/stores/project/state.ts @@ -17,6 +17,9 @@ export const state = reactive({ projects: [] as ProjectRecord[], deletedProjects: [] as ProjectRecord[], tasks: [] as TaskRecord[], + // 统计缓存,与 tasks 数组解耦,供 Dashboard,不被 Tasks 分页/筛选污染(P1-b A0) + activeTaskCount: 0 as number, + projectTaskCounts: {} as Record, ideas: [] as IdeaRecord[], workflowExecutions: [] as WorkflowRecord[], liveEvents: [] as Array, diff --git a/src/views/Dashboard.vue b/src/views/Dashboard.vue index 715bf7a..82d37e1 100644 --- a/src/views/Dashboard.vue +++ b/src/views/Dashboard.vue @@ -60,14 +60,19 @@ const { t } = useI18n() const loading = ref(false) const errorMsg = ref('') -// 统一加载入口(projects/tasks/ideas 并行),管理 loading + errorMsg +// 统一加载入口(projects/ideas 并行 + 统计独立通道),管理 loading + errorMsg // refresh 与 onMounted 共用,避免两处 catch 逻辑重复 +// P1-b A0:不再调 store.loadTasks() —— Dashboard 模板不消费 state.tasks, +// 统计(activeTaskCount/projectTaskCounts)走 loadStats() 独立通道(后端 count_tasks), +// 不被 Tasks 视图分页/筛选污染。Tasks.vue 自行 loadTasks(onMounted/筛选/变更)。 +// loadStats 依赖 state.projects(算 projectTaskCounts),故 loadProjects 完成后再调。 async function loadAll() { if (loading.value) return // 防重复触发 loading.value = true errorMsg.value = '' try { - await Promise.all([store.loadProjects(), store.loadTasks(), store.loadIdeas()]) + await Promise.all([store.loadProjects(), store.loadIdeas()]) + await store.loadStats() } catch (e: any) { console.error('Dashboard load failed:', e) errorMsg.value = e?.toString() ?? t('dashboard.err.loadFailed')