//! 项目 & 任务状态映射常量 //! //! 统一各视图(Projects/ProjectDetail/Tasks/Dashboard)的 label / badge / 阶段颜色档, //! 根治此前三套状态映射互相矛盾、任务文案两处不一致的问题。 //! 项目进度不在此处(按 stage 拍百分比已废弃),由任务完成率计算,见 store.projectTaskProgress。 import type { IdeaStatus } from '@/api/types' // ── 项目状态 ── // 值为 i18n key,实际文案走 $t('projects.status.')(见 src/i18n/{zh-CN,en}/projects.ts) // F-09 对齐:删 in_progress/paused/cancelled(project 维度无数据产生源、无 UI 入口、无消费方), // 保留与后端真实生命周期一致的最小集(planning 常态 + 软删 deleted_at 由 list_deleted 单独处理)。 // A6[PD-P2-7] 补全:in_progress/testing/releasing/paused/cancelled —— 老 DB 数据或后端可能 // 返回这些状态,projectStatusLabel 兜底逻辑会把未收录的 status 原样透传给 $t(报错/显裸 key), // 补齐映射 + i18n 文案避免渲染裸 key。阶段 badge class(.stage-*) 已存在(CSS 无需动)。 export const PROJECT_STATUS_LABELS: Record = { planning: 'projects.status.planning', active: 'projects.status.active', in_progress: 'projects.status.in_progress', testing: 'projects.status.testing', releasing: 'projects.status.releasing', completed: 'projects.status.completed', paused: 'projects.status.paused', cancelled: 'projects.status.cancelled', } /** 项目状态 → 卡片 badge 样式 class */ // 对齐 7 态真实生命周期(planning/in_progress/testing/releasing/completed/paused/cancelled)。 // 类名对齐 ProjectCard.vue scoped 的 stage-* 色系(design/coding/testing/release),paused/cancelled 走新增 // stage-dim(置灰)。'active' 键删除:后端无此状态,老数据兜底走 stage-design(与 projectStageInfo 兜底一致)。 export const PROJECT_STATUS_BADGE_CLASS: Record = { planning: 'stage-design', in_progress: 'stage-coding', testing: 'stage-testing', releasing: 'stage-release', completed: 'stage-release', paused: 'stage-dim', cancelled: 'stage-dim', } /** 项目状态 → 阶段信息(Dashboard 阶段 dot/chip/fill 颜色档与文案共用) */ export interface ProjectStageInfo { /** pipeline 步骤索引 0-4: idea/requirement/coding/testing/release */ stepIndex: number /** Dashboard dot/chip 颜色档: coding/testing/release */ stage: string } // 对齐 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, 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 { return PROJECT_STATUS_LABELS[status] ?? status } export function projectBadgeClass(status: string): string { return PROJECT_STATUS_BADGE_CLASS[status] ?? 'stage-design' } export function projectStageInfo(status: string): ProjectStageInfo { // 兜底用字面量而非 PROJECT_STAGE_INFO.planning,避免已删键的隐式依赖 return PROJECT_STAGE_INFO[status] ?? { stepIndex: 0, stage: 'planning' } } // ── 任务状态 ── // 前端对齐后端 7 态纯状态机(crates/df-core/src/types.rs TaskStatus) // 删旧 5 态 Git 工作流导向(review_ready/merged/abandoned)。 // 后端 task.rs update_task 写 status 经 TaskStatus::is_valid 拦截,旧 5 态无法落库; // migrations.rs V1 无 seed,create_task 默认 todo → 新 DB 无旧 5 态数据,安全对齐。 // // 值为 i18n key,实际文案走 $t('tasks.status.')(见 src/i18n/{zh-CN,en}/tasks.ts) export const TASK_STATUS_LABELS: Record = { todo: 'tasks.status.todo', in_progress: 'tasks.status.in_progress', in_review: 'tasks.status.in_review', testing: 'tasks.status.testing', done: 'tasks.status.done', blocked: 'tasks.status.blocked', cancelled: 'tasks.status.cancelled', deferred: 'tasks.status.deferred', } // class 名对齐既有 css(Tasks/TaskDetail/ProjectDetail 均有 status-todo/progress/review/done/abandoned): // - todo/in_progress/in_review/done 复用既有 class(保持跨视图样式一致,不破坏指派外文件) // - cancelled 复用 status-abandoned(同为 danger 红色,语义一致) // - testing/blocked 新增(Tasks.vue 自身样式补充;TaskDetail/ProjectDetail 遇此值兜底 status-todo 灰色, // 非破坏——仅样式保守,文案经 $t 正常渲染) export const TASK_STATUS_CLASS: Record = { todo: 'status-todo', in_progress: 'status-progress', in_review: 'status-review', testing: 'status-testing', done: 'status-done', blocked: 'status-blocked', cancelled: 'status-abandoned', deferred: 'status-deferred', } // D-260616-01 删旧 5 态前的老 DB 数据兼容映射(老 DB 可能存 completed/review_ready/merged/abandoned) // 新 DB 经 TaskStatus::is_valid 拦截不会有,但老 DB 未迁移清理时兜底显示而非 $t() 报错 const LEGACY_STATUS_MAP: Record = { completed: 'done', review_ready: 'in_review', merged: 'done', abandoned: 'cancelled', } /** 老DB 旧态映射(completed→done / review_ready→in_review 等),新 DB 无此数据 */ function mapLegacyStatus(status: string): string { return LEGACY_STATUS_MAP[status] ?? status } export function taskStatusLabel(status: string): string { return TASK_STATUS_LABELS[mapLegacyStatus(status)] ?? 'tasks.status.todo' } export function taskStatusClass(status: string): string { return TASK_STATUS_CLASS[mapLegacyStatus(status)] ?? 'status-todo' } // 任务状态机合法流转目标(源状态 → 可达目标态,对齐 TaskDetail.vue ADVANCE_MAP 的 target 集)。 // 列表快捷菜单据此过滤,避免直接置终态/非法跳转(done 为唯一终态,无推进目标;cancelled 可恢复); // 新建/手动改状态仍走后端 TaskStatus::is_valid 兜底校验。 export const TASK_STATUS_TRANSITIONS: Record = { todo: ['in_progress', 'cancelled', 'deferred'], in_progress: ['in_review', 'blocked', 'cancelled', 'deferred'], in_review: ['testing', 'in_progress', 'blocked', 'cancelled', 'deferred'], testing: ['done', 'in_review', 'blocked', 'cancelled', 'deferred'], blocked: ['in_progress', 'cancelled', 'deferred'], deferred: ['todo', 'cancelled'], cancelled: ['todo'], done: [], } // 状态语义排序(推进主链在前 → 暂停态 → 历史旁支),供状态多选 chips / 快捷菜单 / 看板列统一使用。 // 与后端 ALL_STATES / TASK_STATUS_TRANSITIONS 为同源集合,仅决定展示顺序。 export const TASK_STATUS_ORDER = [ 'todo', 'in_progress', 'in_review', 'testing', 'done', 'blocked', 'deferred', 'cancelled', ] as const /** 推进按钮映射(与 TASK_STATUS_TRANSITIONS 同源,附带 label i18n key + variant 样式)。 */ export interface AdvanceAction { target: string label: string variant: string } export const ADVANCE_MAP: Record = { todo: [ { target: 'in_progress', label: 'taskDetail.advance.toInProgress', variant: 'btn-primary' }, { target: 'deferred', label: 'taskDetail.advance.defer', variant: 'btn-ghost' }, { target: 'cancelled', label: 'taskDetail.advance.cancel', variant: 'btn-danger' }, ], in_progress: [ { target: 'in_review', label: 'taskDetail.advance.toInReview', variant: 'btn-primary' }, { target: 'blocked', label: 'taskDetail.advance.block', variant: 'btn-ghost' }, { target: 'deferred', label: 'taskDetail.advance.defer', variant: 'btn-ghost' }, { target: 'cancelled', label: 'taskDetail.advance.cancel', variant: 'btn-danger' }, ], in_review: [ { target: 'testing', label: 'taskDetail.advance.toTesting', variant: 'btn-primary' }, { target: 'in_progress', label: 'taskDetail.advance.sendBack', variant: 'btn-ghost' }, { target: 'blocked', label: 'taskDetail.advance.block', variant: 'btn-ghost' }, { target: 'deferred', label: 'taskDetail.advance.defer', variant: 'btn-ghost' }, { target: 'cancelled', label: 'taskDetail.advance.cancel', variant: 'btn-danger' }, ], testing: [ { target: 'done', label: 'taskDetail.advance.toDone', variant: 'btn-primary' }, { target: 'in_review', label: 'taskDetail.advance.sendBackToReview', variant: 'btn-ghost' }, { target: 'blocked', label: 'taskDetail.advance.block', variant: 'btn-ghost' }, { target: 'deferred', label: 'taskDetail.advance.defer', variant: 'btn-ghost' }, { target: 'cancelled', label: 'taskDetail.advance.cancel', variant: 'btn-danger' }, ], blocked: [ { target: 'in_progress', label: 'taskDetail.advance.resume', variant: 'btn-primary' }, { target: 'deferred', label: 'taskDetail.advance.defer', variant: 'btn-ghost' }, { target: 'cancelled', label: 'taskDetail.advance.cancel', variant: 'btn-danger' }, ], deferred: [ { target: 'todo', label: 'taskDetail.advance.resume', variant: 'btn-primary' }, { target: 'cancelled', label: 'taskDetail.advance.cancel', variant: 'btn-danger' }, ], cancelled: [ { target: 'todo', label: 'taskDetail.advance.resume', variant: 'btn-primary' }, ], } // ── 任务优先级 ── export const PRIORITY_LABELS: Record = { 0: 'P0', 1: 'P1', 2: 'P2', 3: 'P3' } export const PRIORITY_CLASSES: Record = { 0: 'priority-critical', 1: 'priority-high', 2: 'priority-medium', 3: 'priority-low', } export function priorityLabel(p: number): string { return PRIORITY_LABELS[p] ?? `P${p}` } export function priorityClass(p: number): string { return PRIORITY_CLASSES[p] ?? 'priority-low' } // ── 灵感状态 ── // 值为 i18n key,实际文案走 $t(见 src/i18n/{zh-CN,en}/ideas.ts)。 // 单一数据源:Ideas.vue 列表 badge/确认文案 与 IdeaDetail.vue 详情 badge/状态下拉共用 // (原 Ideas.vue 自持 + 经 props 透传 IdeaDetail 双写,已收敛至此;archived 无菜单入口不入表)。 export const IDEA_STATUS_OPTIONS: { value: IdeaStatus; labelKey: string }[] = [ { value: 'draft', labelKey: 'ideas.status.draft' }, { value: 'pending_review', labelKey: 'ideas.status.pending_review' }, { value: 'approved', labelKey: 'ideas.status.approved' }, { value: 'promoted', labelKey: 'ideas.status.promoted' }, { value: 'rejected', labelKey: 'ideas.status.rejected' }, ] /** 任意 status → 对应 i18n key;未知状态(含 archived)回退到原值显示 */ export function ideaStatusLabelKey(status: IdeaStatus): string { return IDEA_STATUS_OPTIONS.find(s => s.value === status)?.labelKey ?? status }