新增: 任务推进链(7态状态机+advance_task CAS原子写)+软删除+前后端7态对齐
This commit is contained in:
@@ -21,4 +21,13 @@ export const taskApi = {
|
||||
delete(id: string): Promise<boolean> {
|
||||
return invoke('delete_task', { id })
|
||||
},
|
||||
|
||||
/**
|
||||
* F-05 推进任务状态(走后端 advance_task 状态机:df-nodes task_advance_node)。
|
||||
* 后端校验 from→to 合法性(can_transition)+ review_rounds 自动累加(退回时),
|
||||
* 前端只传 target_status,不自行判状态机(单一真相源在后端)。
|
||||
*/
|
||||
advance(id: string, targetStatus: string): Promise<TaskRecord> {
|
||||
return invoke('advance_task', { id, targetStatus })
|
||||
},
|
||||
}
|
||||
|
||||
@@ -91,6 +91,8 @@ export interface TaskRecord {
|
||||
assignee: string | null
|
||||
workflow_def_id: string | null
|
||||
base_branch: string | null
|
||||
/** review 退回累计轮数(in_review→in_progress / testing→in_review 各 +1) */
|
||||
review_rounds: number
|
||||
created_at: string
|
||||
updated_at: string
|
||||
}
|
||||
|
||||
@@ -6,22 +6,19 @@
|
||||
// ── 项目状态 ──
|
||||
|
||||
// 值为 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 单独处理)。
|
||||
export const PROJECT_STATUS_LABELS: Record<string, string> = {
|
||||
planning: 'projects.status.planning',
|
||||
in_progress: 'projects.status.in_progress',
|
||||
paused: 'projects.status.paused',
|
||||
completed: 'projects.status.completed',
|
||||
cancelled: 'projects.status.cancelled',
|
||||
active: 'projects.status.active',
|
||||
completed: 'projects.status.completed',
|
||||
}
|
||||
|
||||
/** 项目状态 → 卡片 badge 样式 class */
|
||||
export const PROJECT_STATUS_BADGE_CLASS: Record<string, string> = {
|
||||
planning: 'stage-design',
|
||||
in_progress: 'stage-coding',
|
||||
paused: 'stage-testing',
|
||||
active: 'stage-coding',
|
||||
completed: 'stage-release',
|
||||
cancelled: 'stage-design',
|
||||
}
|
||||
|
||||
/** 项目状态 → 阶段/进度信息(详情页 pipeline + Dashboard 进度条共用) */
|
||||
@@ -35,11 +32,9 @@ export interface ProjectStageInfo {
|
||||
}
|
||||
|
||||
export const PROJECT_STAGE_INFO: Record<string, ProjectStageInfo> = {
|
||||
planning: { stepIndex: 0, progress: 20, stage: 'coding' },
|
||||
in_progress: { stepIndex: 2, progress: 55, stage: 'coding' },
|
||||
paused: { stepIndex: 2, progress: 40, stage: 'testing' },
|
||||
planning: { stepIndex: 0, progress: 20, stage: 'planning' },
|
||||
active: { stepIndex: 2, progress: 55, stage: 'coding' },
|
||||
completed: { stepIndex: 4, progress: 100, stage: 'release' },
|
||||
cancelled: { stepIndex: 0, progress: 0, stage: 'testing' },
|
||||
}
|
||||
|
||||
export function projectStatusLabel(status: string): string {
|
||||
@@ -51,26 +46,41 @@ export function projectBadgeClass(status: string): string {
|
||||
}
|
||||
|
||||
export function projectStageInfo(status: string): ProjectStageInfo {
|
||||
return PROJECT_STAGE_INFO[status] ?? PROJECT_STAGE_INFO.planning
|
||||
// F-09:兜底用字面量而非 PROJECT_STAGE_INFO.planning,避免已删键的隐式依赖
|
||||
return PROJECT_STAGE_INFO[status] ?? { stepIndex: 0, progress: 0, stage: 'planning' }
|
||||
}
|
||||
|
||||
// ── 任务状态 ──
|
||||
|
||||
// D-260616-01:前端对齐后端 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',
|
||||
review_ready: 'tasks.status.review_ready',
|
||||
merged: 'tasks.status.merged',
|
||||
abandoned: 'tasks.status.abandoned',
|
||||
in_review: 'tasks.status.in_review',
|
||||
testing: 'tasks.status.testing',
|
||||
done: 'tasks.status.done',
|
||||
blocked: 'tasks.status.blocked',
|
||||
cancelled: 'tasks.status.cancelled',
|
||||
}
|
||||
|
||||
// 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',
|
||||
review_ready: 'status-review',
|
||||
merged: 'status-done',
|
||||
abandoned: 'status-abandoned',
|
||||
in_review: 'status-review',
|
||||
testing: 'status-testing',
|
||||
done: 'status-done',
|
||||
blocked: 'status-blocked',
|
||||
cancelled: 'status-abandoned',
|
||||
}
|
||||
|
||||
export function taskStatusLabel(status: string): string {
|
||||
|
||||
@@ -18,7 +18,24 @@ export default {
|
||||
assignee: 'Assignee',
|
||||
baseBranch: 'Base Branch',
|
||||
workflowDef: 'Workflow Definition',
|
||||
reviewRounds: 'Review Round {n}',
|
||||
createdAt: 'Created At',
|
||||
updatedAt: 'Updated At',
|
||||
// F-05 advance buttons (shown by state-machine-legal next states, validated by advance_task)
|
||||
advanceTitle: 'Advance Task',
|
||||
// Button labels (legal transitions per task_state_machine.rs can_transition)
|
||||
advance: {
|
||||
toInProgress: 'Start',
|
||||
toInReview: 'Submit for Review',
|
||||
toTesting: 'Approve to Testing',
|
||||
toDone: 'Done',
|
||||
resume: 'Resume',
|
||||
sendBack: 'Send Back',
|
||||
sendBackToReview: 'Send Back to Review',
|
||||
block: 'Block',
|
||||
cancel: 'Cancel Task',
|
||||
},
|
||||
advancing: 'Advancing...',
|
||||
advanceFailed: 'Failed to advance task',
|
||||
},
|
||||
}
|
||||
|
||||
@@ -11,14 +11,16 @@ export default {
|
||||
status: 'Status: ',
|
||||
all: 'All',
|
||||
},
|
||||
// Status filters (with icons)
|
||||
// Status filters (with icons) — D-260616-01 aligned to backend 7 states
|
||||
statusFilter: {
|
||||
all: 'All',
|
||||
todo: 'Todo',
|
||||
in_progress: 'In Progress',
|
||||
review_ready: 'Review Ready',
|
||||
merged: 'Merged',
|
||||
abandoned: 'Abandoned',
|
||||
in_review: 'In Review',
|
||||
testing: 'Testing',
|
||||
done: 'Done',
|
||||
blocked: 'Blocked',
|
||||
cancelled: 'Cancelled',
|
||||
},
|
||||
|
||||
// Groups
|
||||
@@ -44,19 +46,22 @@ export default {
|
||||
priorityMedium: 'P2 Medium',
|
||||
priorityLow: 'P3 Low',
|
||||
},
|
||||
// Status labels (TASK_STATUS_LABELS values in constants/project.ts use these keys)
|
||||
// Status labels (TASK_STATUS_LABELS values in constants/project.ts use these keys) — D-260616-01 aligned to backend 7 states
|
||||
status: {
|
||||
todo: '📋 Todo',
|
||||
in_progress: '🔄 In Progress',
|
||||
review_ready: '👀 Review Ready',
|
||||
merged: '✅ Merged',
|
||||
abandoned: '🗑️ Abandoned',
|
||||
in_review: '👀 In Review',
|
||||
testing: '🧪 Testing',
|
||||
done: '✅ Done',
|
||||
blocked: '🚫 Blocked',
|
||||
cancelled: '🗑️ Cancelled',
|
||||
},
|
||||
// Error fallbacks (user-visible via state.error)
|
||||
err: {
|
||||
loadFailed: 'Failed to load tasks',
|
||||
createFailed: 'Failed to create task',
|
||||
deleteFailed: 'Failed to delete task',
|
||||
updateFailed: 'Failed to update task',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -18,7 +18,24 @@ export default {
|
||||
assignee: '负责人',
|
||||
baseBranch: '基础分支',
|
||||
workflowDef: '工作流定义',
|
||||
reviewRounds: '第 {n} 轮 Review',
|
||||
createdAt: '创建时间',
|
||||
updatedAt: '更新时间',
|
||||
// F-05 推进按钮(按状态机合法下一态显示,advance_task 状态机校验)
|
||||
advanceTitle: '推进任务',
|
||||
// 按钮文案(状态机合法转换,见 task_state_machine.rs can_transition)
|
||||
advance: {
|
||||
toInProgress: '开始', // todo/blocked → in_progress
|
||||
toInReview: '提交审查', // in_progress → in_review
|
||||
toTesting: '通过进测试', // in_review → testing
|
||||
toDone: '完成', // testing → done
|
||||
resume: '恢复', // blocked → in_progress(与 toInProgress 同向,blocked 专用文案)
|
||||
sendBack: '退回修改', // in_review → in_progress
|
||||
sendBackToReview: '退回重审', // testing → in_review
|
||||
block: '阻塞', // * → blocked
|
||||
cancel: '取消任务', // * → cancelled
|
||||
},
|
||||
advancing: '推进中...',
|
||||
advanceFailed: '推进任务失败',
|
||||
},
|
||||
}
|
||||
|
||||
@@ -11,14 +11,16 @@ export default {
|
||||
status: '状态:',
|
||||
all: '全部',
|
||||
},
|
||||
// 状态筛选(含图标)
|
||||
// 状态筛选(含图标) — D-260616-01 对齐后端 7 态
|
||||
statusFilter: {
|
||||
all: '全部',
|
||||
todo: '待开始',
|
||||
in_progress: '进行中',
|
||||
review_ready: '待审查',
|
||||
merged: '已合并',
|
||||
abandoned: '已放弃',
|
||||
in_review: '审查中',
|
||||
testing: '测试中',
|
||||
done: '已完成',
|
||||
blocked: '已阻塞',
|
||||
cancelled: '已取消',
|
||||
},
|
||||
|
||||
// 分组
|
||||
@@ -44,19 +46,22 @@ export default {
|
||||
priorityMedium: 'P2 中',
|
||||
priorityLow: 'P3 低',
|
||||
},
|
||||
// 状态文案(constants/project.ts 的 TASK_STATUS_LABELS 值走此 key)
|
||||
// 状态文案(constants/project.ts 的 TASK_STATUS_LABELS 值走此 key) — D-260616-01 对齐后端 7 态
|
||||
status: {
|
||||
todo: '📋 待开始',
|
||||
in_progress: '🔄 进行中',
|
||||
review_ready: '👀 待审查',
|
||||
merged: '✅ 已合并',
|
||||
abandoned: '🗑️ 已放弃',
|
||||
in_review: '👀 审查中',
|
||||
testing: '🧪 测试中',
|
||||
done: '✅ 已完成',
|
||||
blocked: '🚫 已阻塞',
|
||||
cancelled: '🗑️ 已取消',
|
||||
},
|
||||
// 错误回退(state.error 用户可见)
|
||||
err: {
|
||||
loadFailed: '加载任务失败',
|
||||
createFailed: '创建任务失败',
|
||||
deleteFailed: '删除任务失败',
|
||||
updateFailed: '更新任务失败',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
<span
|
||||
class="value description ai-md"
|
||||
v-if="task.description"
|
||||
v-html="renderedDescription"
|
||||
v-html="renderedDesc"
|
||||
></span>
|
||||
<span v-else class="value description">—</span>
|
||||
</div>
|
||||
@@ -42,8 +42,27 @@
|
||||
<span class="label">{{ $t('taskDetail.status') }}</span>
|
||||
<span class="value">
|
||||
<span class="status-tag" :class="taskStatusClass(task.status)">{{ $t(taskStatusLabel(task.status)) }}</span>
|
||||
<!-- F-04 review 轮次显示(>0 才显示,in_review→in_progress / testing→in_review 退回时后端 +1) -->
|
||||
<span v-if="task.review_rounds > 0" class="review-rounds-badge">
|
||||
{{ $t('taskDetail.reviewRounds', { n: task.review_rounds }) }}
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
<!-- F-05 推进按钮:按当前 status 显示状态机合法下一态(todo/done/cancelled 终态无按钮) -->
|
||||
<div v-if="advanceActions.length" class="info-item info-block advance-row">
|
||||
<span class="label">{{ $t('taskDetail.advanceTitle') }}</span>
|
||||
<div class="advance-actions">
|
||||
<button
|
||||
v-for="act in advanceActions"
|
||||
:key="act.target"
|
||||
type="button"
|
||||
class="btn btn-sm"
|
||||
:class="act.variant"
|
||||
:disabled="advancing"
|
||||
@click="handleAdvance(act.target)"
|
||||
>{{ $t(act.label) }}</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="label">{{ $t('taskDetail.priority') }}</span>
|
||||
<span class="value">
|
||||
@@ -95,9 +114,10 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted, watch } from 'vue'
|
||||
import { ref, computed, onMounted, onBeforeUnmount, watch } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { listen } from '@tauri-apps/api/event'
|
||||
import { taskApi, projectApi } from '@/api'
|
||||
import { formatDate } from '@/utils/time'
|
||||
import { useRendered } from '@/composables/useMarkdown'
|
||||
@@ -107,7 +127,7 @@ import {
|
||||
priorityLabel,
|
||||
priorityClass,
|
||||
} from '../constants/project'
|
||||
import type { TaskRecord, ProjectRecord } from '@/api/types'
|
||||
import type { TaskRecord, ProjectRecord, DfDataChangedPayload } from '@/api/types'
|
||||
|
||||
const { t } = useI18n()
|
||||
const route = useRoute()
|
||||
@@ -116,12 +136,13 @@ const loading = ref(true)
|
||||
const errorMsg = ref('')
|
||||
const task = ref<TaskRecord | null>(null)
|
||||
const projects = ref<ProjectRecord[]>([])
|
||||
const advancing = ref(false)
|
||||
|
||||
const taskId = computed(() => route.params.id as string)
|
||||
|
||||
// B-24:任务描述 Markdown 渲染(复用 AiChat 同款渲染器,模块级单例),useRendered 封装
|
||||
// computed(读 mdReady 触发响应式 + renderMd)+ ensureLoaded(幂等预热)
|
||||
const { rendered: renderedDescription, ensureLoaded } = useRendered(
|
||||
const { rendered: renderedDesc, ensureLoaded } = useRendered(
|
||||
() => task.value?.description ?? '',
|
||||
)
|
||||
|
||||
@@ -130,6 +151,76 @@ const projectName = computed(() => {
|
||||
return p?.name ?? task.value?.project_id ?? '—'
|
||||
})
|
||||
|
||||
// ============================================================
|
||||
// F-05 推进按钮 — 状态机合法下一态映射
|
||||
// ------------------------------------------------------------
|
||||
// 真相源:后端 df-nodes task_state_machine.rs can_transition(详见该文件注释矩阵)。
|
||||
// 前端只做「按当前 status 显示哪些合法目标」的渲染决策,不做合法性判定;
|
||||
// 点击调 advance_task,后端二次校验(can_transition)+ 原子写 + review_rounds 累加。
|
||||
//
|
||||
// 矩阵(行=from):
|
||||
// todo → in_progress(开始), cancelled(取消)
|
||||
// in_progress → in_review(提交审查), blocked(阻塞), cancelled
|
||||
// in_review → testing(通过进测试), in_progress(退回修改), blocked, cancelled
|
||||
// testing → done(完成), in_review(退回重审), blocked, cancelled
|
||||
// blocked → in_progress(恢复), cancelled
|
||||
// done/cancelled → 终态,无按钮
|
||||
// variant: primary=前向推进主操作(每态至多一个),danger=取消,ghost=其余(阻塞/退回/恢复)
|
||||
interface AdvanceAction {
|
||||
target: string
|
||||
label: string // i18n key(走 taskDetail.advance.*)
|
||||
variant: string // btn-primary | btn-danger | btn-ghost
|
||||
}
|
||||
const ADVANCE_MAP: Record<string, AdvanceAction[]> = {
|
||||
todo: [
|
||||
{ target: 'in_progress', label: 'taskDetail.advance.toInProgress', variant: 'btn-primary' },
|
||||
{ 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: '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: '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: 'cancelled', label: 'taskDetail.advance.cancel', variant: 'btn-danger' },
|
||||
],
|
||||
blocked: [
|
||||
{ target: 'in_progress', label: 'taskDetail.advance.resume', variant: 'btn-primary' },
|
||||
{ target: 'cancelled', label: 'taskDetail.advance.cancel', variant: 'btn-danger' },
|
||||
],
|
||||
// done / cancelled:终态,无推进按钮
|
||||
}
|
||||
|
||||
const advanceActions = computed<AdvanceAction[]>(() => {
|
||||
const s = task.value?.status
|
||||
if (!s) return []
|
||||
return ADVANCE_MAP[s] ?? []
|
||||
})
|
||||
|
||||
async function handleAdvance(target: string) {
|
||||
if (!task.value || advancing.value) return
|
||||
advancing.value = true
|
||||
try {
|
||||
// advance_task 返回更新后的 TaskRecord(含新 status / review_rounds)
|
||||
const updated = await taskApi.advance(task.value.id, target)
|
||||
task.value = updated
|
||||
errorMsg.value = ''
|
||||
} catch (e: any) {
|
||||
errorMsg.value = e?.toString() ?? t('taskDetail.advanceFailed')
|
||||
} finally {
|
||||
advancing.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function load() {
|
||||
loading.value = true
|
||||
errorMsg.value = ''
|
||||
@@ -156,9 +247,31 @@ function refresh() {
|
||||
// 路由参数变化(id 变化)时重新加载
|
||||
watch(taskId, () => { load() })
|
||||
|
||||
onMounted(() => {
|
||||
// B-260616-18: 数据变更联动刷新 unlistener(onMounted 注册,onBeforeUnmount 释放)
|
||||
// 对齐 AiChat _unlistenToolSlow 生命周期模式。本视图绕 store 直调 taskApi.get/projectApi.list,
|
||||
// 不享受 store 全局 df-data-changed 监听(该监听只刷 store.tasks 列表,不含本视图的当前 task 单体),
|
||||
// 故此本地监听 entity=task/project 时重载当前 task。
|
||||
let _unlistenDataChanged: (() => void) | null = null
|
||||
|
||||
onMounted(async () => {
|
||||
ensureLoaded() // 后台预热 Markdown 渲染器(模块单例,与 AiChat 共享),不阻塞 load
|
||||
load()
|
||||
// B-260616-18: 后端 AI 工具(create/update/delete 等)emit df-data-changed → 本视图重载当前 task
|
||||
// entity=task:任务本体字段(标题/状态/描述/分支…)被改时刷新;entity=project:项目名变更影响 projectName 解析时刷新
|
||||
try {
|
||||
_unlistenDataChanged = await listen<DfDataChangedPayload>('df-data-changed', (event) => {
|
||||
const { entity } = event.payload
|
||||
if (entity === 'task' || entity === 'project') {
|
||||
load()
|
||||
}
|
||||
})
|
||||
} catch (e) {
|
||||
console.error('[TaskDetail] 启动 df-data-changed 监听失败:', e)
|
||||
}
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
if (_unlistenDataChanged) { _unlistenDataChanged(); _unlistenDataChanged = null }
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -189,6 +302,31 @@ onMounted(() => {
|
||||
}
|
||||
.btn-ghost { background: transparent; color: var(--df-text-secondary); border: 0.5px solid var(--df-border); }
|
||||
.btn-ghost:hover { background: var(--df-bg-card); color: var(--df-text); }
|
||||
/* F-05 推进按钮变体(样式跟随 Projects/ProjectDetail/Knowledge 同款 btn-primary/btn-danger/btn-sm) */
|
||||
.btn-primary { background: var(--df-accent); color: #fff; }
|
||||
.btn-primary:hover { background: var(--df-accent-hover); }
|
||||
.btn-danger { background: transparent; color: var(--df-danger); border: 0.5px solid rgba(240,101,101,0.4); }
|
||||
.btn-danger:hover { background: rgba(240,101,101,0.12); }
|
||||
.btn-sm { padding: 5px 12px; font-size: 12px; }
|
||||
.btn:disabled { opacity: 0.5; cursor: not-allowed; }
|
||||
|
||||
/* F-05 推进操作区 */
|
||||
.advance-row .advance-actions {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
/* F-04 review 轮次徽章 */
|
||||
.review-rounds-badge {
|
||||
font-size: 11px;
|
||||
color: var(--df-warning);
|
||||
background: rgba(255,217,61,0.12);
|
||||
padding: 3px 8px;
|
||||
border-radius: var(--df-radius-xs);
|
||||
margin-left: 8px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* ===== 状态/优先级标签 ===== */
|
||||
.status-tag {
|
||||
@@ -258,77 +396,7 @@ onMounted(() => {
|
||||
/* B-24 漏 white-space 覆盖致 v-html 后 HTML 标签间 \n 被 pre-wrap 渲染为空行间距——移除 pre-wrap,描述由 .ai-md 接管(line-height 1.5 对齐 AiChat li) */
|
||||
.info-item .value.description { line-height: 1.5; }
|
||||
|
||||
/* ===== 任务描述 Markdown 渲染(B-24,样式同 AiChat .ai-md 收敛) ===== */
|
||||
.description.ai-md :deep(p) { margin: 0 0 6px; }
|
||||
.description.ai-md :deep(p:last-child) { margin-bottom: 0; }
|
||||
.description.ai-md :deep(ul), .description.ai-md :deep(ol) { margin: 4px 0; padding-left: 20px; }
|
||||
.description.ai-md :deep(li) { margin: 2px 0; line-height: 1.5; }
|
||||
.description.ai-md :deep(code) {
|
||||
font-family: var(--df-font-mono);
|
||||
font-size: 12px;
|
||||
padding: 1px 5px;
|
||||
background: rgba(255,255,255,0.06);
|
||||
border-radius: var(--df-radius-sm);
|
||||
color: var(--df-accent);
|
||||
}
|
||||
.description.ai-md :deep(pre) {
|
||||
margin: 8px 0;
|
||||
padding: 10px 12px;
|
||||
background: var(--df-bg);
|
||||
border: 0.5px solid var(--df-border);
|
||||
border-radius: var(--df-radius);
|
||||
overflow-x: auto;
|
||||
}
|
||||
.description.ai-md :deep(pre code) {
|
||||
padding: 0;
|
||||
background: transparent;
|
||||
border-radius: 0;
|
||||
color: var(--df-text-secondary);
|
||||
font-size: 12px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
.description.ai-md :deep(blockquote) {
|
||||
margin: 6px 0;
|
||||
padding: 4px 12px;
|
||||
border-left: 2px solid var(--df-accent);
|
||||
color: var(--df-text-secondary);
|
||||
}
|
||||
.description.ai-md :deep(h1), .description.ai-md :deep(h2), .description.ai-md :deep(h3) {
|
||||
font-weight: 500;
|
||||
color: var(--df-text);
|
||||
margin: 8px 0 4px;
|
||||
}
|
||||
.description.ai-md :deep(h1) { font-size: 16px; }
|
||||
.description.ai-md :deep(h2) { font-size: 14px; }
|
||||
.description.ai-md :deep(h3) { font-size: 13px; }
|
||||
.description.ai-md :deep(a) {
|
||||
color: var(--df-accent);
|
||||
text-decoration: none;
|
||||
}
|
||||
.description.ai-md :deep(a:hover) { text-decoration: underline; }
|
||||
.description.ai-md :deep(strong) { font-weight: 500; color: var(--df-text); }
|
||||
.description.ai-md :deep(hr) {
|
||||
border: none;
|
||||
border-top: 0.5px solid var(--df-border);
|
||||
margin: 8px 0;
|
||||
}
|
||||
.description.ai-md :deep(table) {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin: 6px 0;
|
||||
font-size: 12px;
|
||||
overflow-x: auto;
|
||||
display: block;
|
||||
}
|
||||
.description.ai-md :deep(th), .description.ai-md :deep(td) {
|
||||
padding: 4px 8px;
|
||||
border: 0.5px solid var(--df-border);
|
||||
text-align: left;
|
||||
}
|
||||
.description.ai-md :deep(th) {
|
||||
font-weight: 500;
|
||||
background: var(--df-bg);
|
||||
}
|
||||
/* ===== 任务描述 Markdown 渲染(B-24,基础样式收敛至全局 ai-md.css) ===== */
|
||||
|
||||
.project-link {
|
||||
color: var(--df-accent);
|
||||
|
||||
@@ -143,13 +143,16 @@ const projectFilters = computed(() => [
|
||||
...store.projects.map(p => ({ key: p.id, label: p.name })),
|
||||
])
|
||||
|
||||
// D-260616-01:筛选器对齐后端 7 态(删 review_ready/merged/abandoned,加 in_review/testing/done/blocked/cancelled)
|
||||
const statusFilters = computed<{ key: string; label: string; icon: string }[]>(() => [
|
||||
{ key: 'all', label: t('tasks.statusFilter.all'), icon: '📋' },
|
||||
{ key: 'todo', label: t('tasks.statusFilter.todo'), icon: '📝' },
|
||||
{ key: 'in_progress', label: t('tasks.statusFilter.in_progress'), icon: '🔨' },
|
||||
{ key: 'review_ready', label: t('tasks.statusFilter.review_ready'), icon: '👀' },
|
||||
{ key: 'merged', label: t('tasks.statusFilter.merged'), icon: '✅' },
|
||||
{ key: 'abandoned', label: t('tasks.statusFilter.abandoned'), icon: '🗑️' },
|
||||
{ key: 'in_review', label: t('tasks.statusFilter.in_review'), icon: '👀' },
|
||||
{ key: 'testing', label: t('tasks.statusFilter.testing'), icon: '🧪' },
|
||||
{ key: 'done', label: t('tasks.statusFilter.done'), icon: '✅' },
|
||||
{ key: 'blocked', label: t('tasks.statusFilter.blocked'), icon: '🚫' },
|
||||
{ key: 'cancelled', label: t('tasks.statusFilter.cancelled'), icon: '🗑️' },
|
||||
])
|
||||
|
||||
function getProjectName(projectId: string): string {
|
||||
@@ -406,6 +409,9 @@ onMounted(async () => {
|
||||
.status-review { background: rgba(255,217,61,0.2); color: var(--df-warning); }
|
||||
.status-done { background: rgba(100,255,218,0.15); color: var(--df-success); }
|
||||
.status-abandoned { background: rgba(255,107,107,0.2); color: var(--df-danger); }
|
||||
/* D-260616-01:7 态新增 — testing(橙警告,近 review 区分)/blocked(红 danger,与 cancelled 区分用实心边框) */
|
||||
.status-testing { background: rgba(255,152,0,0.2); color: #ff9800; }
|
||||
.status-blocked { background: rgba(255,107,107,0.12); color: var(--df-danger); border: 0.5px solid var(--df-danger); }
|
||||
|
||||
/* ===== 空状态(Y-2: loading/error/empty 兜底,样式参考 Projects.vue) ===== */
|
||||
.empty-state {
|
||||
|
||||
Reference in New Issue
Block a user