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); }