优化: 项目进度改为真实任务完成率
- 进度条不再用阶段写死百分比,改为已完成任务数/总任务数 - 复用现有任务计数通道,零后端改动;无任务项目显示无任务不伪造数字 - 阶段映射表仅保留颜色文案用途
This commit is contained in:
@@ -15,9 +15,10 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="project-bar-wrap">
|
<div class="project-bar-wrap">
|
||||||
<div class="project-bar">
|
<div class="project-bar">
|
||||||
<div class="project-bar-fill" :class="'fill-' + p.stage" :style="{ width: p.progress + '%' }"></div>
|
<div class="project-bar-fill" :class="'fill-' + p.stage" :style="{ width: (p.progress ?? 0) + '%' }"></div>
|
||||||
</div>
|
</div>
|
||||||
<span class="project-pct">{{ p.progress }}%</span>
|
<span v-if="p.progress !== null" class="project-pct">{{ p.progress }}%</span>
|
||||||
|
<span v-else class="project-pct">{{ $t('dashboard.noTasks') }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="project-meta-row">
|
<div class="project-meta-row">
|
||||||
<span class="project-meta-item">
|
<span class="project-meta-item">
|
||||||
@@ -54,10 +55,18 @@ function goDetail(projectId: string) {
|
|||||||
// P1-b A0:统计走后端 count_tasks 独立通道,不再 filter(store.tasks)。
|
// P1-b A0:统计走后端 count_tasks 独立通道,不再 filter(store.tasks)。
|
||||||
// store.tasks 是 Tasks 视图当前筛选+分页数据,filter 会让面板数字随 Tasks 翻页/筛选错乱。
|
// store.tasks 是 Tasks 视图当前筛选+分页数据,filter 会让面板数字随 Tasks 翻页/筛选错乱。
|
||||||
// store.projectTaskCounts[projectId] = 后端该项目 in_progress 任务数(loadStats 填充)。
|
// store.projectTaskCounts[projectId] = 后端该项目 in_progress 任务数(loadStats 填充)。
|
||||||
|
// store.projectTaskProgress[projectId] = 该项目 { done, total } 任务数(loadStats 填充),
|
||||||
|
// 进度条按真实完成率渲染;无任务(total=0)时不伪造百分比,显示「无任务」。
|
||||||
function getProjectTaskCount(projectId: string): number {
|
function getProjectTaskCount(projectId: string): number {
|
||||||
return store.projectTaskCounts[projectId] ?? 0
|
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)
|
// 阶段 label key:projectStageInfo 返回的 stage 值(coding/testing/release/planning/paused/cancelled)
|
||||||
// 直接拼接 i18n key dashboard.stage.<stage>,与 constants/project.ts PROJECT_STAGE_INFO 单一来源。
|
// 直接拼接 i18n key dashboard.stage.<stage>,与 constants/project.ts PROJECT_STAGE_INFO 单一来源。
|
||||||
// 面板名为"活跃项目",按真实生命周期过滤:排除已完结/已取消(后端状态机无 'active',原
|
// 面板名为"活跃项目",按真实生命周期过滤:排除已完结/已取消(后端状态机无 'active',原
|
||||||
@@ -79,7 +88,7 @@ const displayProjects = computed(() =>
|
|||||||
name: p.name,
|
name: p.name,
|
||||||
stage: info.stage,
|
stage: info.stage,
|
||||||
stageLabelKey: info.stage,
|
stageLabelKey: info.stage,
|
||||||
progress: info.progress,
|
progress: getProjectProgress(p.id),
|
||||||
activeTasks: getProjectTaskCount(p.id),
|
activeTasks: getProjectTaskCount(p.id),
|
||||||
lastActivity: formatRelative(p.last_active_at ?? p.updated_at),
|
lastActivity: formatRelative(p.last_active_at ?? p.updated_at),
|
||||||
}
|
}
|
||||||
|
|||||||
+13
-12
@@ -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<string, string> = {
|
|||||||
cancelled: 'stage-dim',
|
cancelled: 'stage-dim',
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 项目状态 → 阶段/进度信息(详情页 pipeline + Dashboard 进度条共用) */
|
/** 项目状态 → 阶段信息(Dashboard 阶段 dot/chip/fill 颜色档与文案共用) */
|
||||||
export interface ProjectStageInfo {
|
export interface ProjectStageInfo {
|
||||||
/** pipeline 步骤索引 0-4: idea/requirement/coding/testing/release */
|
/** pipeline 步骤索引 0-4: idea/requirement/coding/testing/release */
|
||||||
stepIndex: number
|
stepIndex: number
|
||||||
/** 进度百分比 */
|
|
||||||
progress: number
|
|
||||||
/** Dashboard dot/chip 颜色档: coding/testing/release */
|
/** Dashboard dot/chip 颜色档: coding/testing/release */
|
||||||
stage: string
|
stage: string
|
||||||
}
|
}
|
||||||
@@ -49,14 +48,16 @@ export interface ProjectStageInfo {
|
|||||||
// 对齐 7 态。stage 值决定 Dashboard dot/chip/fill 颜色档(planning/coding/testing/release/paused/cancelled),
|
// 对齐 7 态。stage 值决定 Dashboard dot/chip/fill 颜色档(planning/coding/testing/release/paused/cancelled),
|
||||||
// 需与 ActiveProjectsPanel.vue 的 dot-*/chip-*/fill-* 类一一对应(paused 为该组件新增 dim 类)。
|
// 需与 ActiveProjectsPanel.vue 的 dot-*/chip-*/fill-* 类一一对应(paused 为该组件新增 dim 类)。
|
||||||
// 'active' 键已删,老数据兜底走 projectStageInfo 默认 planning。
|
// 'active' 键已删,老数据兜底走 projectStageInfo 默认 planning。
|
||||||
|
// 进度百分比已移出本表:进度改由任务完成率计算(store.projectTaskProgress),
|
||||||
|
// stage 枚举与任务完成度无必然对应,固定映射会给出与实际不符的数字。
|
||||||
export const PROJECT_STAGE_INFO: Record<string, ProjectStageInfo> = {
|
export const PROJECT_STAGE_INFO: Record<string, ProjectStageInfo> = {
|
||||||
planning: { stepIndex: 0, progress: 20, stage: 'planning' },
|
planning: { stepIndex: 0, stage: 'planning' },
|
||||||
in_progress: { stepIndex: 2, progress: 55, stage: 'coding' },
|
in_progress: { stepIndex: 2, stage: 'coding' },
|
||||||
testing: { stepIndex: 3, progress: 80, stage: 'testing' },
|
testing: { stepIndex: 3, stage: 'testing' },
|
||||||
releasing: { stepIndex: 4, progress: 95, stage: 'release' },
|
releasing: { stepIndex: 4, stage: 'release' },
|
||||||
completed: { stepIndex: 4, progress: 100, stage: 'release' },
|
completed: { stepIndex: 4, stage: 'release' },
|
||||||
paused: { stepIndex: 1, progress: 40, stage: 'paused' },
|
paused: { stepIndex: 1, stage: 'paused' },
|
||||||
cancelled: { stepIndex: 0, progress: 0, stage: 'cancelled' },
|
cancelled: { stepIndex: 0, stage: 'cancelled' },
|
||||||
}
|
}
|
||||||
|
|
||||||
export function projectStatusLabel(status: string): string {
|
export function projectStatusLabel(status: string): string {
|
||||||
@@ -69,7 +70,7 @@ export function projectBadgeClass(status: string): string {
|
|||||||
|
|
||||||
export function projectStageInfo(status: string): ProjectStageInfo {
|
export function projectStageInfo(status: string): ProjectStageInfo {
|
||||||
// 兜底用字面量而非 PROJECT_STAGE_INFO.planning,避免已删键的隐式依赖
|
// 兜底用字面量而非 PROJECT_STAGE_INFO.planning,避免已删键的隐式依赖
|
||||||
return PROJECT_STAGE_INFO[status] ?? { stepIndex: 0, progress: 0, stage: 'planning' }
|
return PROJECT_STAGE_INFO[status] ?? { stepIndex: 0, stage: 'planning' }
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── 任务状态 ──
|
// ── 任务状态 ──
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ export default {
|
|||||||
aiBatchProcess: 'AI Batch Process',
|
aiBatchProcess: 'AI Batch Process',
|
||||||
tasks: '{count} tasks',
|
tasks: '{count} tasks',
|
||||||
taskUnit: '{n} tasks',
|
taskUnit: '{n} tasks',
|
||||||
|
noTasks: 'No tasks',
|
||||||
trending: 'Trending',
|
trending: 'Trending',
|
||||||
approved: 'Approved',
|
approved: 'Approved',
|
||||||
pending: 'Pending',
|
pending: 'Pending',
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ export default {
|
|||||||
aiBatchProcess: 'AI 批量处理',
|
aiBatchProcess: 'AI 批量处理',
|
||||||
tasks: '{count} 个任务',
|
tasks: '{count} 个任务',
|
||||||
taskUnit: '{n} 个任务',
|
taskUnit: '{n} 个任务',
|
||||||
|
noTasks: '无任务',
|
||||||
trending: '热门',
|
trending: '热门',
|
||||||
approved: '已立项',
|
approved: '已立项',
|
||||||
pending: '待定',
|
pending: '待定',
|
||||||
|
|||||||
+11
-2
@@ -120,6 +120,8 @@ function createStore() {
|
|||||||
|
|
||||||
// P1-b A0:统计拉取走后端 count_tasks(独立通道),与 tasks 数组解耦。
|
// P1-b A0:统计拉取走后端 count_tasks(独立通道),与 tasks 数组解耦。
|
||||||
// activeTaskCount = 全局 in_progress 任务数;projectTaskCounts = 各项目 in_progress 任务数。
|
// 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 调用刷新。
|
// tasks 变更(create/update/delete/advance)后由 df-data-changed listener 调用刷新。
|
||||||
// 注:projectTaskCounts 依赖 state.projects 已加载 —— 调用方需保证先 loadProjects。
|
// 注:projectTaskCounts 依赖 state.projects 已加载 —— 调用方需保证先 loadProjects。
|
||||||
// - Dashboard.loadAll:先 Promise.all([loadProjects, loadIdeas]) 完成再 loadStats
|
// - Dashboard.loadAll:先 Promise.all([loadProjects, loadIdeas]) 完成再 loadStats
|
||||||
@@ -128,9 +130,14 @@ function createStore() {
|
|||||||
try {
|
try {
|
||||||
state.activeTaskCount = await taskApi.count({ status: 'in_progress' } as TaskQuery)
|
state.activeTaskCount = await taskApi.count({ status: 'in_progress' } as TaskQuery)
|
||||||
const entries = await Promise.all(state.projects.map(p =>
|
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) {
|
} catch (e) {
|
||||||
console.error('[loadStats] 统计拉取失败', e)
|
console.error('[loadStats] 统计拉取失败', e)
|
||||||
}
|
}
|
||||||
@@ -144,6 +151,8 @@ function createStore() {
|
|||||||
// P1-b A0:统计缓存读口(供 ActiveProjectsPanel 读 projectTaskCounts)
|
// P1-b A0:统计缓存读口(供 ActiveProjectsPanel 读 projectTaskCounts)
|
||||||
get activeTaskCount() { return state.activeTaskCount },
|
get activeTaskCount() { return state.activeTaskCount },
|
||||||
get projectTaskCounts() { return state.projectTaskCounts },
|
get projectTaskCounts() { return state.projectTaskCounts },
|
||||||
|
// 各项目任务完成进度(done/total)读口,Dashboard 项目进度条用
|
||||||
|
get projectTaskProgress() { return state.projectTaskProgress },
|
||||||
get ideas() { return state.ideas },
|
get ideas() { return state.ideas },
|
||||||
get workflowExecutions() { return state.workflowExecutions },
|
get workflowExecutions() { return state.workflowExecutions },
|
||||||
get liveEvents() { return state.liveEvents },
|
get liveEvents() { return state.liveEvents },
|
||||||
|
|||||||
@@ -20,6 +20,8 @@ export const state = reactive({
|
|||||||
// 统计缓存,与 tasks 数组解耦,供 Dashboard,不被 Tasks 分页/筛选污染(P1-b A0)
|
// 统计缓存,与 tasks 数组解耦,供 Dashboard,不被 Tasks 分页/筛选污染(P1-b A0)
|
||||||
activeTaskCount: 0 as number,
|
activeTaskCount: 0 as number,
|
||||||
projectTaskCounts: {} as Record<string, number>,
|
projectTaskCounts: {} as Record<string, number>,
|
||||||
|
// 各项目任务完成进度(done/total),Dashboard 进度条完成率的数据源,与 tasks 数组解耦
|
||||||
|
projectTaskProgress: {} as Record<string, { done: number; total: number }>,
|
||||||
ideas: [] as IdeaRecord[],
|
ideas: [] as IdeaRecord[],
|
||||||
workflowExecutions: [] as WorkflowRecord[],
|
workflowExecutions: [] as WorkflowRecord[],
|
||||||
liveEvents: [] as Array<WorkflowEventPayload & { _ts: number }>,
|
liveEvents: [] as Array<WorkflowEventPayload & { _ts: number }>,
|
||||||
|
|||||||
Reference in New Issue
Block a user