diff --git a/src/components/dashboard/ActiveProjectsPanel.vue b/src/components/dashboard/ActiveProjectsPanel.vue
index e74dbd3..a26889a 100644
--- a/src/components/dashboard/ActiveProjectsPanel.vue
+++ b/src/components/dashboard/ActiveProjectsPanel.vue
@@ -15,9 +15,10 @@
-
{{ p.progress }}%
+
{{ p.progress }}%
+
{{ $t('dashboard.noTasks') }}
@@ -54,10 +55,18 @@ function goDetail(projectId: string) {
// P1-b A0:统计走后端 count_tasks 独立通道,不再 filter(store.tasks)。
// store.tasks 是 Tasks 视图当前筛选+分页数据,filter 会让面板数字随 Tasks 翻页/筛选错乱。
// store.projectTaskCounts[projectId] = 后端该项目 in_progress 任务数(loadStats 填充)。
+// store.projectTaskProgress[projectId] = 该项目 { done, total } 任务数(loadStats 填充),
+// 进度条按真实完成率渲染;无任务(total=0)时不伪造百分比,显示「无任务」。
function getProjectTaskCount(projectId: string): number {
return store.projectTaskCounts[projectId] ?? 0
}
+function getProjectProgress(projectId: string): number | null {
+ const prog = store.projectTaskProgress[projectId]
+ if (!prog || prog.total === 0) return null
+ return Math.round((prog.done / prog.total) * 100)
+}
+
// 阶段 label key:projectStageInfo 返回的 stage 值(coding/testing/release/planning/paused/cancelled)
// 直接拼接 i18n key dashboard.stage.,与 constants/project.ts PROJECT_STAGE_INFO 单一来源。
// 面板名为"活跃项目",按真实生命周期过滤:排除已完结/已取消(后端状态机无 'active',原
@@ -79,7 +88,7 @@ const displayProjects = computed(() =>
name: p.name,
stage: info.stage,
stageLabelKey: info.stage,
- progress: info.progress,
+ progress: getProjectProgress(p.id),
activeTasks: getProjectTaskCount(p.id),
lastActivity: formatRelative(p.last_active_at ?? p.updated_at),
}
diff --git a/src/constants/project.ts b/src/constants/project.ts
index b3cfbd0..dbc08b5 100644
--- a/src/constants/project.ts
+++ b/src/constants/project.ts
@@ -1,7 +1,8 @@
//! 项目 & 任务状态映射常量
//!
-//! 统一各视图(Projects/ProjectDetail/Tasks/Dashboard)的 label / badge / 阶段进度,
+//! 统一各视图(Projects/ProjectDetail/Tasks/Dashboard)的 label / badge / 阶段颜色档,
//! 根治此前三套状态映射互相矛盾、任务文案两处不一致的问题。
+//! 项目进度不在此处(按 stage 拍百分比已废弃),由任务完成率计算,见 store.projectTaskProgress。
// ── 项目状态 ──
@@ -36,12 +37,10 @@ export const PROJECT_STATUS_BADGE_CLASS: Record = {
cancelled: 'stage-dim',
}
-/** 项目状态 → 阶段/进度信息(详情页 pipeline + Dashboard 进度条共用) */
+/** 项目状态 → 阶段信息(Dashboard 阶段 dot/chip/fill 颜色档与文案共用) */
export interface ProjectStageInfo {
/** pipeline 步骤索引 0-4: idea/requirement/coding/testing/release */
stepIndex: number
- /** 进度百分比 */
- progress: number
/** Dashboard dot/chip 颜色档: coding/testing/release */
stage: string
}
@@ -49,14 +48,16 @@ export interface ProjectStageInfo {
// 对齐 7 态。stage 值决定 Dashboard dot/chip/fill 颜色档(planning/coding/testing/release/paused/cancelled),
// 需与 ActiveProjectsPanel.vue 的 dot-*/chip-*/fill-* 类一一对应(paused 为该组件新增 dim 类)。
// 'active' 键已删,老数据兜底走 projectStageInfo 默认 planning。
+// 进度百分比已移出本表:进度改由任务完成率计算(store.projectTaskProgress),
+// stage 枚举与任务完成度无必然对应,固定映射会给出与实际不符的数字。
export const PROJECT_STAGE_INFO: Record = {
- planning: { stepIndex: 0, progress: 20, stage: 'planning' },
- in_progress: { stepIndex: 2, progress: 55, stage: 'coding' },
- testing: { stepIndex: 3, progress: 80, stage: 'testing' },
- releasing: { stepIndex: 4, progress: 95, stage: 'release' },
- completed: { stepIndex: 4, progress: 100, stage: 'release' },
- paused: { stepIndex: 1, progress: 40, stage: 'paused' },
- cancelled: { stepIndex: 0, progress: 0, stage: 'cancelled' },
+ planning: { stepIndex: 0, stage: 'planning' },
+ in_progress: { stepIndex: 2, stage: 'coding' },
+ testing: { stepIndex: 3, stage: 'testing' },
+ releasing: { stepIndex: 4, stage: 'release' },
+ completed: { stepIndex: 4, stage: 'release' },
+ paused: { stepIndex: 1, stage: 'paused' },
+ cancelled: { stepIndex: 0, stage: 'cancelled' },
}
export function projectStatusLabel(status: string): string {
@@ -69,7 +70,7 @@ export function projectBadgeClass(status: string): string {
export function projectStageInfo(status: string): ProjectStageInfo {
// 兜底用字面量而非 PROJECT_STAGE_INFO.planning,避免已删键的隐式依赖
- return PROJECT_STAGE_INFO[status] ?? { stepIndex: 0, progress: 0, stage: 'planning' }
+ return PROJECT_STAGE_INFO[status] ?? { stepIndex: 0, stage: 'planning' }
}
// ── 任务状态 ──
diff --git a/src/i18n/en/dashboard.ts b/src/i18n/en/dashboard.ts
index 9c8bce6..178932a 100644
--- a/src/i18n/en/dashboard.ts
+++ b/src/i18n/en/dashboard.ts
@@ -21,6 +21,7 @@ export default {
aiBatchProcess: 'AI Batch Process',
tasks: '{count} tasks',
taskUnit: '{n} tasks',
+ noTasks: 'No tasks',
trending: 'Trending',
approved: 'Approved',
pending: 'Pending',
diff --git a/src/i18n/zh-CN/dashboard.ts b/src/i18n/zh-CN/dashboard.ts
index 680a979..2f8ddd2 100644
--- a/src/i18n/zh-CN/dashboard.ts
+++ b/src/i18n/zh-CN/dashboard.ts
@@ -21,6 +21,7 @@ export default {
aiBatchProcess: 'AI 批量处理',
tasks: '{count} 个任务',
taskUnit: '{n} 个任务',
+ noTasks: '无任务',
trending: '热门',
approved: '已立项',
pending: '待定',
diff --git a/src/stores/project.ts b/src/stores/project.ts
index 8af43e7..64fe9ca 100644
--- a/src/stores/project.ts
+++ b/src/stores/project.ts
@@ -120,6 +120,8 @@ function createStore() {
// P1-b A0:统计拉取走后端 count_tasks(独立通道),与 tasks 数组解耦。
// activeTaskCount = 全局 in_progress 任务数;projectTaskCounts = 各项目 in_progress 任务数。
+ // projectTaskProgress = 各项目 { done, total } 任务数,Dashboard 进度条按真实完成率渲染,
+ // 复用同一 count_tasks 通道(不带 status 计 total,带 status='done' 计完成数)。
// tasks 变更(create/update/delete/advance)后由 df-data-changed listener 调用刷新。
// 注:projectTaskCounts 依赖 state.projects 已加载 —— 调用方需保证先 loadProjects。
// - Dashboard.loadAll:先 Promise.all([loadProjects, loadIdeas]) 完成再 loadStats
@@ -128,9 +130,14 @@ function createStore() {
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)
+ Promise.all([
+ taskApi.count({ project_id: p.id, status: 'in_progress' } as TaskQuery),
+ taskApi.count({ project_id: p.id } as TaskQuery),
+ taskApi.count({ project_id: p.id, status: 'done' } as TaskQuery),
+ ]).then(([active, total, done]) => [p.id, active, { done, total }] as const)
))
- state.projectTaskCounts = Object.fromEntries(entries)
+ state.projectTaskCounts = Object.fromEntries(entries.map(([id, active]) => [id, active]))
+ state.projectTaskProgress = Object.fromEntries(entries.map(([id, , prog]) => [id, prog]))
} catch (e) {
console.error('[loadStats] 统计拉取失败', e)
}
@@ -144,6 +151,8 @@ function createStore() {
// P1-b A0:统计缓存读口(供 ActiveProjectsPanel 读 projectTaskCounts)
get activeTaskCount() { return state.activeTaskCount },
get projectTaskCounts() { return state.projectTaskCounts },
+ // 各项目任务完成进度(done/total)读口,Dashboard 项目进度条用
+ get projectTaskProgress() { return state.projectTaskProgress },
get ideas() { return state.ideas },
get workflowExecutions() { return state.workflowExecutions },
get liveEvents() { return state.liveEvents },
diff --git a/src/stores/project/state.ts b/src/stores/project/state.ts
index b44960f..6b817ae 100644
--- a/src/stores/project/state.ts
+++ b/src/stores/project/state.ts
@@ -20,6 +20,8 @@ export const state = reactive({
// 统计缓存,与 tasks 数组解耦,供 Dashboard,不被 Tasks 分页/筛选污染(P1-b A0)
activeTaskCount: 0 as number,
projectTaskCounts: {} as Record,
+ // 各项目任务完成进度(done/total),Dashboard 进度条完成率的数据源,与 tasks 数组解耦
+ projectTaskProgress: {} as Record,
ideas: [] as IdeaRecord[],
workflowExecutions: [] as WorkflowRecord[],
liveEvents: [] as Array,