审批等待格式化与阈值沉 utils/time 正典 相对时间阶梯三套收敛 formatRelative 参数化 灵感状态表沉 constants,透传通道退役 aggText/clearOwn 等命名对齐,IPC 参数名统一 conversation_id
244 lines
11 KiB
TypeScript
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
|
|
}
|