diff --git a/src/i18n/en/dashboard.ts b/src/i18n/en/dashboard.ts index a06f144..0eec5a4 100644 --- a/src/i18n/en/dashboard.ts +++ b/src/i18n/en/dashboard.ts @@ -35,9 +35,7 @@ export default { stage: { planning: 'Planning', coding: 'Coding', - paused: 'Paused', done: 'Done', - cancelled: 'Cancelled', }, ideaStatus: { draft: 'Draft', diff --git a/src/i18n/en/ideas.ts b/src/i18n/en/ideas.ts index 7de8799..f21afe8 100644 --- a/src/i18n/en/ideas.ts +++ b/src/i18n/en/ideas.ts @@ -72,6 +72,9 @@ export default { titlePlaceholder: 'Describe your idea in one sentence...', descPlaceholder: 'More details (optional)...', + // Deep link not found + notFound: 'Idea not found or deleted', + // Native dialog text (confirm / alert) confirmDelete: 'Delete idea "{title}"? This cannot be undone.', promoteFailed: 'Promotion failed', diff --git a/src/i18n/en/projects.ts b/src/i18n/en/projects.ts index 92af1a7..b9c1ca8 100644 --- a/src/i18n/en/projects.ts +++ b/src/i18n/en/projects.ts @@ -39,11 +39,8 @@ export default { // Status labels (PROJECT_STATUS_LABELS values in constants/project.ts use these keys) status: { planning: '📐 Planning', - in_progress: '💻 In Progress', - paused: '⏸ Paused', - completed: '✅ Completed', - cancelled: '❌ Cancelled', active: '🚀 In Progress', + completed: '✅ Completed', }, // Error fallbacks (user-visible via state.error) err: { @@ -55,6 +52,8 @@ export default { restoreFailed: 'Failed to restore project', purgeFailed: 'Failed to delete permanently', loadWorkflowFailed: 'Failed to load workflow records', + approveFailed: 'Approval operation failed', + cancelFailed: 'Cancel approval failed', }, }, } diff --git a/src/i18n/zh-CN/dashboard.ts b/src/i18n/zh-CN/dashboard.ts index 48beaf7..c951384 100644 --- a/src/i18n/zh-CN/dashboard.ts +++ b/src/i18n/zh-CN/dashboard.ts @@ -35,9 +35,7 @@ export default { stage: { planning: '规划中', coding: '编码中', - paused: '已暂停', done: '已完成', - cancelled: '已取消', }, ideaStatus: { draft: '草稿', diff --git a/src/i18n/zh-CN/ideas.ts b/src/i18n/zh-CN/ideas.ts index 684d013..af1dae1 100644 --- a/src/i18n/zh-CN/ideas.ts +++ b/src/i18n/zh-CN/ideas.ts @@ -72,6 +72,9 @@ export default { titlePlaceholder: '一句话描述你的灵感...', descPlaceholder: '详细说明(可选)...', + // 深链不存在提示 + notFound: '灵感不存在或已删除', + // 原生对话框文案(confirm / alert) confirmDelete: '确定删除灵感「{title}」?此操作不可撤销。', promoteFailed: '立项失败', diff --git a/src/i18n/zh-CN/projects.ts b/src/i18n/zh-CN/projects.ts index 119d5a0..b878924 100644 --- a/src/i18n/zh-CN/projects.ts +++ b/src/i18n/zh-CN/projects.ts @@ -38,11 +38,8 @@ export default { // 状态文案(constants/project.ts 的 PROJECT_STATUS_LABELS 值走此 key) status: { planning: '📐 规划中', - in_progress: '💻 进行中', - paused: '⏸ 已暂停', - completed: '✅ 已完成', - cancelled: '❌ 已取消', active: '🚀 进行中', + completed: '✅ 已完成', }, // 错误回退(state.error 用户可见) err: { @@ -54,6 +51,8 @@ export default { restoreFailed: '恢复项目失败', purgeFailed: '彻底删除失败', loadWorkflowFailed: '加载工作流记录失败', + approveFailed: '审批操作失败', + cancelFailed: '取消审批失败', }, }, } diff --git a/src/router/index.ts b/src/router/index.ts index 9fb66f0..3c288d9 100644 --- a/src/router/index.ts +++ b/src/router/index.ts @@ -47,6 +47,12 @@ const routes = [ component: () => import('../views/Tasks.vue'), meta: { title: '任务', icon: 'icon-thunder' }, }, + { + path: '/tasks/:id', + name: 'TaskDetail', + component: () => import('../views/TaskDetail.vue'), + meta: { title: '任务详情', icon: 'icon-thunder' }, + }, { path: '/knowledge', name: 'Knowledge', diff --git a/src/stores/project/tasks.ts b/src/stores/project/tasks.ts index cb49460..cb4b6e6 100644 --- a/src/stores/project/tasks.ts +++ b/src/stores/project/tasks.ts @@ -27,10 +27,14 @@ export function createTasksStore() { } async function updateTask(id: string, field: string, value: string) { - await taskApi.update(id, field, value) - const idx = state.tasks.findIndex(t => t.id === id) - if (idx >= 0) { - (state.tasks[idx] as any)[field] = value + try { + await taskApi.update(id, field, value) + const idx = state.tasks.findIndex(t => t.id === id) + if (idx >= 0) { + (state.tasks[idx] as any)[field] = value + } + } catch (e: any) { + state.error = e?.toString() ?? t('tasks.err.updateFailed') } } diff --git a/src/stores/project/workflow.ts b/src/stores/project/workflow.ts index 14621f9..88b5eae 100644 --- a/src/stores/project/workflow.ts +++ b/src/stores/project/workflow.ts @@ -89,6 +89,7 @@ export function createWorkflowStore() { state.pendingApproval = null } catch (error) { console.error('审批失败:', error) + state.error = error?.toString() ?? t('projects.err.approveFailed') } } @@ -105,6 +106,7 @@ export function createWorkflowStore() { state.pendingApproval = null } catch (error) { console.error('取消审批失败:', error) + state.error = error?.toString() ?? t('projects.err.cancelFailed') } } diff --git a/src/views/Dashboard.vue b/src/views/Dashboard.vue index a3d5dd7..ee9a305 100644 --- a/src/views/Dashboard.vue +++ b/src/views/Dashboard.vue @@ -126,15 +126,13 @@ const stats = computed(() => [ ]) function getProjectStage(status: string): { stage: string; stageLabelKey: string; progress: number } { - // stage(颜色档)与 stageLabelKey(文案)语义必须一致: - // planning/cancelled 不再借用 coding/testing 颜色(否则颜色撒谎),改用专属颜色档。 + // F-09 对齐:project status 死键(in_progress/paused/cancelled)已从后端映射移除, + // 此处 switch 同步收敛,与 constants/project.ts 的 PROJECT_STAGE_INFO 语义一致。 switch (status) { case 'planning': return { stage: 'planning', stageLabelKey: 'planning', progress: 20 } - case 'in_progress': return { stage: 'coding', stageLabelKey: 'coding', progress: 55 } - case 'paused': return { stage: 'testing', stageLabelKey: 'paused', progress: 40 } + case 'active': return { stage: 'coding', stageLabelKey: 'coding', progress: 55 } case 'completed': return { stage: 'release', stageLabelKey: 'done', progress: 100 } - case 'cancelled': return { stage: 'cancelled', stageLabelKey: 'cancelled', progress: 0 } - default: return { stage: 'coding', stageLabelKey: 'coding', progress: 30 } + default: return { stage: 'planning', stageLabelKey: 'planning', progress: 20 } } } diff --git a/src/views/Ideas.vue b/src/views/Ideas.vue index 0990692..b1af5b9 100644 --- a/src/views/Ideas.vue +++ b/src/views/Ideas.vue @@ -206,8 +206,8 @@ @@ -811,62 +833,8 @@ onMounted(async () => { margin-bottom: var(--df-gap-page); } -/* ===== 灵感描述 Markdown 渲染(B-260615-25,样式同 TaskDetail B-24 .ai-md 收敛) ===== */ +/* ===== 灵感描述 Markdown 渲染(B-260615-25,基础样式收敛至全局 ai-md.css) ===== */ .detail-desc.ai-md { white-space: normal; color: var(--df-text-secondary); } -.detail-desc.ai-md :deep(p) { margin: 0 0 6px; } -.detail-desc.ai-md :deep(p:last-child) { margin-bottom: 0; } -.detail-desc.ai-md :deep(ul), .detail-desc.ai-md :deep(ol) { margin: 4px 0; padding-left: 20px; } -.detail-desc.ai-md :deep(li) { margin: 2px 0; line-height: 1.5; } -.detail-desc.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); -} -.detail-desc.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; -} -.detail-desc.ai-md :deep(pre code) { - padding: 0; - background: transparent; - border-radius: 0; - color: var(--df-text-secondary); - font-size: 12px; - line-height: 1.5; -} -.detail-desc.ai-md :deep(blockquote) { - margin: 6px 0; - padding: 4px 12px; - border-left: 2px solid var(--df-accent); - color: var(--df-text-secondary); -} -.detail-desc.ai-md :deep(h1), .detail-desc.ai-md :deep(h2), .detail-desc.ai-md :deep(h3) { - font-weight: 500; - color: var(--df-text); - margin: 8px 0 4px; -} -.detail-desc.ai-md :deep(h1) { font-size: 16px; } -.detail-desc.ai-md :deep(h2) { font-size: 14px; } -.detail-desc.ai-md :deep(h3) { font-size: 13px; } -.detail-desc.ai-md :deep(a) { color: var(--df-accent); text-decoration: none; } -.detail-desc.ai-md :deep(a:hover) { text-decoration: underline; } -.detail-desc.ai-md :deep(strong) { font-weight: 500; color: var(--df-text); } -.detail-desc.ai-md :deep(hr) { border: none; border-top: 0.5px solid var(--df-border); margin: 8px 0; } -.detail-desc.ai-md :deep(table) { - width: 100%; border-collapse: collapse; margin: 6px 0; - font-size: 12px; overflow-x: auto; display: block; -} -.detail-desc.ai-md :deep(th), .detail-desc.ai-md :deep(td) { - padding: 4px 8px; border: 0.5px solid var(--df-border); text-align: left; -} -.detail-desc.ai-md :deep(th) { font-weight: 500; background: var(--df-bg); } .detail-section { margin-bottom: var(--df-gap-page); diff --git a/src/views/Knowledge.vue b/src/views/Knowledge.vue index b3feafc..ceaa447 100644 --- a/src/views/Knowledge.vue +++ b/src/views/Knowledge.vue @@ -564,7 +564,7 @@ onMounted(() => { /* ===== 左右分栏 ===== */ .kn-layout { - display: grid; grid-template-columns: 360px 1fr; gap: var(--df-gap-page); + display: grid; grid-template-columns: minmax(240px, 360px) 1fr; gap: var(--df-gap-page); flex: 1; min-height: 0; } .kn-list-panel, .kn-detail-panel { @@ -573,7 +573,7 @@ onMounted(() => { border-radius: var(--df-radius-lg); overflow: hidden; display: flex; flex-direction: column; } -.kn-detail-panel { overflow-y: auto; padding: 20px; } +.kn-detail-panel { overflow-y: auto; padding: 20px; min-width: 0; } /* 左侧搜索 + 分类 */ .search-bar { padding: 12px 12px 0; } @@ -631,62 +631,8 @@ onMounted(() => { .detail-field label { display: block; font-size: 12px; color: var(--df-text-secondary); margin-bottom: 4px; } .detail-content { font-size: 13px; color: var(--df-text); line-height: 1.6; white-space: pre-wrap; } -/* ===== 知识内容 Markdown 渲染(B-260615-25,样式同 TaskDetail B-24 .ai-md 收敛) ===== */ +/* ===== 知识内容 Markdown 渲染(B-260615-25,基础样式收敛至全局 ai-md.css) ===== */ .detail-content.ai-md { white-space: normal; } -.detail-content.ai-md :deep(p) { margin: 0 0 6px; } -.detail-content.ai-md :deep(p:last-child) { margin-bottom: 0; } -.detail-content.ai-md :deep(ul), .detail-content.ai-md :deep(ol) { margin: 4px 0; padding-left: 20px; } -.detail-content.ai-md :deep(li) { margin: 2px 0; line-height: 1.5; } -.detail-content.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); -} -.detail-content.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; -} -.detail-content.ai-md :deep(pre code) { - padding: 0; - background: transparent; - border-radius: 0; - color: var(--df-text-secondary); - font-size: 12px; - line-height: 1.5; -} -.detail-content.ai-md :deep(blockquote) { - margin: 6px 0; - padding: 4px 12px; - border-left: 2px solid var(--df-accent); - color: var(--df-text-secondary); -} -.detail-content.ai-md :deep(h1), .detail-content.ai-md :deep(h2), .detail-content.ai-md :deep(h3) { - font-weight: 500; - color: var(--df-text); - margin: 8px 0 4px; -} -.detail-content.ai-md :deep(h1) { font-size: 16px; } -.detail-content.ai-md :deep(h2) { font-size: 14px; } -.detail-content.ai-md :deep(h3) { font-size: 13px; } -.detail-content.ai-md :deep(a) { color: var(--df-accent); text-decoration: none; } -.detail-content.ai-md :deep(a:hover) { text-decoration: underline; } -.detail-content.ai-md :deep(strong) { font-weight: 500; color: var(--df-text); } -.detail-content.ai-md :deep(hr) { border: none; border-top: 0.5px solid var(--df-border); margin: 8px 0; } -.detail-content.ai-md :deep(table) { - width: 100%; border-collapse: collapse; margin: 6px 0; - font-size: 12px; overflow-x: auto; display: block; -} -.detail-content.ai-md :deep(th), .detail-content.ai-md :deep(td) { - padding: 4px 8px; border: 0.5px solid var(--df-border); text-align: left; -} -.detail-content.ai-md :deep(th) { font-weight: 500; background: var(--df-bg); } .detail-tags { display: flex; gap: 6px; flex-wrap: wrap; } .tag { font-size: 11px; padding: 2px 8px; border-radius: var(--df-radius-xs); background: rgba(108,99,255,0.1); color: var(--df-accent); } .muted { color: var(--df-text-dim); font-size: 12px; } @@ -778,4 +724,10 @@ onMounted(() => { .form-input:focus, .form-select:focus { border-color: var(--df-accent); } .form-textarea { min-height: 80px; resize: vertical; font-family: inherit; } .modal-actions { display: flex; justify-content: flex-end; gap: 8px; margin-top: 16px; } + +/* ===== 响应式:窄屏详情标题防竖线化(B-260616-19) ===== */ +@media (max-width: 760px) { + .kn-layout { grid-template-columns: 1fr; } + .detail-title { font-size: 15px; } +} diff --git a/src/views/ProjectDetail.vue b/src/views/ProjectDetail.vue index 04753a5..7ce2a72 100644 --- a/src/views/ProjectDetail.vue +++ b/src/views/ProjectDetail.vue @@ -272,7 +272,7 @@ const currentProject = computed(() => ) // B-260615-25:项目描述 Markdown 渲染(复用 AiChat/TaskDetail 同款渲染器,模块级单例), // useRendered 封装 computed(读 mdReady 触发响应式 + renderMd)+ ensureLoaded(幂等预热) -const { rendered: renderedDesc, ensureLoaded: ensureMdLoaded } = useRendered( +const { rendered: renderedDesc, ensureLoaded } = useRendered( () => currentProject.value?.description ?? '', ) const stageKey = computed(() => currentProject.value?.status ?? 'planning') @@ -378,7 +378,7 @@ async function relocateDir() { await checkPath() } catch (e: any) { console.error('重定位失败:', e) - Message.error(t('projectDetail.relocateFailed', { msg: e?.toString() ?? '未知错误' })) + Message.error(t('projectDetail.relocateFailed', { msg: e?.toString() ?? t('common.unknownError') })) } } @@ -456,7 +456,7 @@ watch(() => store.pendingApproval, (newApproval) => { // ── 生命周期 ── onMounted(async () => { - ensureMdLoaded() // 后台预热 Markdown 渲染器(模块单例,与 AiChat/TaskDetail 共享),不阻塞 + ensureLoaded() // 后台预热 Markdown 渲染器(模块单例,与 AiChat/TaskDetail 共享),不阻塞 await store.loadProjects() await store.loadTasks() // 全量加载,详情页用 computed 过滤本项目(避免覆盖全局 tasks 单例) unlisten = await store.startEventListener() @@ -544,62 +544,8 @@ onUnmounted(() => { border: 0.5px solid var(--df-border); } -/* ===== 项目描述 Markdown 渲染(B-260615-25,样式同 TaskDetail B-24 .ai-md 收敛) ===== */ +/* ===== 项目描述 Markdown 渲染(B-260615-25,基础样式收敛至全局 ai-md.css) ===== */ .description.ai-md { font-size: 14px; color: var(--df-text); line-height: 1.6; } -.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); }