Files
DevFlow/src/constants/project.ts
T
lxy 0560137840 优化: 前端DRY收敛与命名对齐
审批等待格式化与阈值沉 utils/time 正典
相对时间阶梯三套收敛 formatRelative 参数化
灵感状态表沉 constants,透传通道退役
aggText/clearOwn 等命名对齐,IPC 参数名统一 conversation_id
2026-10-01 19:35:20 +08:00

244 lines
11 KiB
TypeScript

//! 项目 & 任务状态映射常量
//!
//! 统一各视图(Projects/ProjectDetail/Tasks/Dashboard)的 label / badge / 阶段颜色档,
//! 根治此前三套状态映射互相矛盾、任务文案两处不一致的问题。
//! 项目进度不在此处(按 stage 拍百分比已废弃),由任务完成率计算,见 store.projectTaskProgress。
import type { IdeaStatus } from '@/api/types'
// ── 项目状态 ──
// 值为 i18n key,实际文案走 $t('projects.status.<key>')(见 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<string, string> = {
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<string, string> = {
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<string, ProjectStageInfo> = {
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.<key>')(见 src/i18n/{zh-CN,en}/tasks.ts)
export const TASK_STATUS_LABELS: Record<string, string> = {
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<string, string> = {
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<string, string> = {
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<string, string[]> = {
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<string, AdvanceAction[]> = {
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<number, string> = { 0: 'P0', 1: 'P1', 2: 'P2', 3: 'P3' }
export const PRIORITY_CLASSES: Record<number, string> = {
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
}