修复: 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:
@@ -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)
|
||||
|
||||
+31
-1
@@ -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,
|
||||
|
||||
@@ -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 }>,
|
||||
|
||||
@@ -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')
|
||||
|
||||
Reference in New Issue
Block a user