修复: P1-b Dashboard统计源解耦(治翻页/筛选后统计数字错乱 H6/H7)

- 统计走后端 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)
This commit is contained in:
lxy
2026-08-02 12:29:44 +08:00
parent 9e85cadb3f
commit d1321af880
4 changed files with 45 additions and 4 deletions
+31 -1
View File
@@ -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,
+3
View File
@@ -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<string, number>,
ideas: [] as IdeaRecord[],
workflowExecutions: [] as WorkflowRecord[],
liveEvents: [] as Array<WorkflowEventPayload & { _ts: number }>,