diff --git a/src/api/idea.ts b/src/api/idea.ts index 1e015e2..535412e 100644 --- a/src/api/idea.ts +++ b/src/api/idea.ts @@ -49,4 +49,14 @@ export const ideaApi = { listEvaluations(ideaId: string): Promise { return invoke('list_idea_evaluations', { ideaId }) }, + + /** 归档/取消归档灵感(归档功能 P1,V47)。返回是否命中(灵感不存在/已软删为 false)。 */ + archive(id: string, archived: boolean): Promise { + return invoke('archive_idea', { id, archived }) + }, + + /** 列出归档灵感(archived = 1 且未删,updated_at DESC)。 */ + listArchived(): Promise { + return invoke('list_archived_ideas') + }, } diff --git a/src/api/task.ts b/src/api/task.ts index d071e64..e3a4787 100644 --- a/src/api/task.ts +++ b/src/api/task.ts @@ -59,4 +59,14 @@ export const taskApi = { advance(id: string, targetStatus: string): Promise { return invoke('advance_task', { id, targetStatus }) }, + + /** 归档/取消归档任务(归档功能 P1,V46)。返回是否命中(任务不存在/已软删为 false)。 */ + archive(id: string, archived: boolean): Promise { + return invoke('archive_task', { id, archived }) + }, + + /** 列出归档任务(archived = 1 且未删,updated_at DESC)。projectId 可选过滤。 */ + listArchived(projectId?: string): Promise { + return invoke('list_archived_tasks', { projectId: projectId ?? null }) + }, } diff --git a/src/api/types.ts b/src/api/types.ts index f6ac207..d5b171d 100644 --- a/src/api/types.ts +++ b/src/api/types.ts @@ -37,6 +37,8 @@ export interface IdeaRecord { scores: string | null /** 关联灵感 id JSON 数组字符串(对齐后端 related_ids Option),null/空串=无关联 */ related_ids: string | null + /** 归档标记(归档功能 P1,V47 列)。归档灵感退出主视图,仅在归档箱可见。 */ + archived: boolean created_at: string updated_at: string } @@ -189,6 +191,12 @@ export interface TaskRecord { * 老任务无结构化规格时为 undefined。 */ content_json?: string + /** + * 归档标记(归档功能 P1,V46 列)。与 status/queue 正交的纯元数据: + * archived=true 的任务退出主视图(后端活跃查询恒过滤),仅在归档视图可见。 + * 老任务后端 DEFAULT 0 保证该字段始终存在。 + */ + archived: boolean created_at: string updated_at: string } diff --git a/src/components/ideas/IdeaDetail.vue b/src/components/ideas/IdeaDetail.vue index 36f54b1..ebc0176 100644 --- a/src/components/ideas/IdeaDetail.vue +++ b/src/components/ideas/IdeaDetail.vue @@ -286,10 +286,14 @@ - +
- +
@@ -315,12 +319,15 @@ const props = defineProps<{ evalError: string promoting: boolean deleting: boolean + /** 归档功能 P1:父视图是否处于归档箱模式(决定操作按钮语义:归档↔取消归档) */ + inArchiveBox?: boolean }>() const emit = defineEmits<{ (e: 'evaluate'): void (e: 'promote'): void (e: 'delete'): void + (e: 'archive', archived: boolean): void (e: 'status-change', value: IdeaStatus): void // 返 Promise 让子组件 await 知成功/失败。 // true=落库成功(关编辑态);false=失败(保持编辑态,toast 由父组件处理)。 diff --git a/src/i18n/en/ideas.ts b/src/i18n/en/ideas.ts index cbf039e..bc474fe 100644 --- a/src/i18n/en/ideas.ts +++ b/src/i18n/en/ideas.ts @@ -28,6 +28,7 @@ export default { createFailed: 'Failed to create idea', deleteFailed: 'Failed to delete idea', relateFailed: 'Failed to update relations', + archiveFailed: 'Archive operation failed', }, // Action feedback toasts (WK-16/18: success/failure light toast, aligns Knowledge.createdToast) @@ -147,6 +148,14 @@ export default { // ⑥ Status change confirm (terminal promoted/rejected/archived or leaving approved) confirmStatus: 'Change idea "{title}" status from {from} to {to}?', promoteFailed: 'Promotion failed', + // Archive P1: idea archive box + filterArchived: '📦 Archive', + archiveBoxEmpty: 'No archived ideas', + quickArchive: '📦 Archive', + quickUnarchive: '📤 Unarchive', + confirmArchive: 'Archive idea "{title}"? It will leave the main view; find it in the Archive box.', + archivedToast: 'Archived', + unarchivedToast: 'Unarchived', evalFailed: 'Evaluation failed', }, } diff --git a/src/i18n/en/tasks.ts b/src/i18n/en/tasks.ts index 6e07465..ef709d5 100644 --- a/src/i18n/en/tasks.ts +++ b/src/i18n/en/tasks.ts @@ -53,6 +53,14 @@ export default { quickStatus: 'Status', quickPriority: 'Priority', quickDelete: '🗑 Delete', + // Archive P1: archive moves out of main view, keep searchable; delete moves to trash + quickArchive: '📦 Archive', + quickUnarchive: '📤 Unarchive', + confirmArchive: 'Archive task "{title}"? It will leave the main view; find it in the Archive box.', + archivedToast: 'Archived', + unarchivedToast: 'Unarchived', + archiveBox: '📦 Archive', + archiveBoxEmpty: 'No archived tasks', // Batch action bar (shared by Tasks / ProjectDetail / TaskDetail subtasks) batch: { @@ -129,6 +137,7 @@ export default { createFailed: 'Failed to create task', deleteFailed: 'Failed to delete task', updateFailed: 'Failed to update task', + archiveFailed: 'Archive operation failed', selectProject: 'Please select a project', }, }, diff --git a/src/i18n/zh-CN/ideas.ts b/src/i18n/zh-CN/ideas.ts index 6e9ed4e..f90ddea 100644 --- a/src/i18n/zh-CN/ideas.ts +++ b/src/i18n/zh-CN/ideas.ts @@ -28,6 +28,7 @@ export default { createFailed: '创建灵感失败', deleteFailed: '删除灵感失败', relateFailed: '更新关联关系失败', + archiveFailed: '归档操作失败', }, // 操作反馈 toast(WK-16/18:成功/失败轻提示,对齐 Knowledge.createdToast) @@ -147,6 +148,14 @@ export default { // ⑥ 状态切换确认(终态 promoted/rejected/archived 或离开 approved 失去立项入口) confirmStatus: '将灵感「{title}」状态从 {from} 改为 {to}?', promoteFailed: '立项失败', + // 归档功能 P1(灵感归档箱) + filterArchived: '📦 归档箱', + archiveBoxEmpty: '暂无归档灵感', + quickArchive: '📦 归档到归档箱', + quickUnarchive: '📤 取消归档', + confirmArchive: '归档灵感「{title}」?归档后移出主视图,可在归档箱查看。', + archivedToast: '已归档', + unarchivedToast: '已取消归档', evalFailed: '评估失败', }, } diff --git a/src/i18n/zh-CN/tasks.ts b/src/i18n/zh-CN/tasks.ts index d14207a..3432bec 100644 --- a/src/i18n/zh-CN/tasks.ts +++ b/src/i18n/zh-CN/tasks.ts @@ -53,6 +53,14 @@ export default { quickStatus: '状态', quickPriority: '优先级', quickDelete: '🗑 删除', + // 归档功能 P1(归档=移出主视图保留检索,区别于删除=进回收站) + quickArchive: '📦 归档', + quickUnarchive: '📤 取消归档', + confirmArchive: '归档任务「{title}」?归档后移出主视图,可在归档箱查看。', + archivedToast: '已归档', + unarchivedToast: '已取消归档', + archiveBox: '📦 归档箱', + archiveBoxEmpty: '暂无归档任务', // 批量操作栏(三处任务列表共用:Tasks / ProjectDetail / TaskDetail 子任务) batch: { @@ -129,6 +137,7 @@ export default { createFailed: '创建任务失败', deleteFailed: '删除任务失败', updateFailed: '更新任务失败', + archiveFailed: '归档操作失败', selectProject: '请选择项目', }, }, diff --git a/src/stores/project.ts b/src/stores/project.ts index 96c7327..aa2621f 100644 --- a/src/stores/project.ts +++ b/src/stores/project.ts @@ -179,6 +179,7 @@ function createStore() { createTask: tasksStore.createTask, updateTask: tasksStore.updateTask, deleteTask: tasksStore.deleteTask, + archiveTask: tasksStore.archiveTask, // P1-b A0:统计独立通道(Dashboard 卡片 + ActiveProjectsPanel 各项目任务数) loadStats, // idea actions @@ -186,6 +187,7 @@ function createStore() { createIdea: ideasStore.createIdea, updateIdea: ideasStore.updateIdea, deleteIdea: ideasStore.deleteIdea, + archiveIdea: ideasStore.archiveIdea, evaluateIdea: ideasStore.evaluateIdea, promoteIdea: ideasStore.promoteIdea, relateIdeas: ideasStore.relateIdeas, diff --git a/src/stores/project/ideas.ts b/src/stores/project/ideas.ts index fa02439..afb3026 100644 --- a/src/stores/project/ideas.ts +++ b/src/stores/project/ideas.ts @@ -70,6 +70,19 @@ export function createIdeasStore() { }) } + /** + * 归档/取消归档灵感(归档功能 P1)。归档=移出主视图(list_by_query 恒带 archived=0), + * 本地同步从列表移除(取消归档时调 loadIdeas 重拉)。返回后端命中布尔。 + */ + async function archiveIdea(id: string, archived: boolean): Promise { + const ok = await runWithCatch(state, t('ideas.err.archiveFailed'), async () => { + const hit = await ideaApi.archive(id, archived) + if (hit && archived) state.ideas = state.ideas.filter(x => x.id !== id) + return hit + }) + return ok === true + } + return { loadIdeas, createIdea, @@ -78,5 +91,6 @@ export function createIdeasStore() { evaluateIdea, promoteIdea, relateIdeas, + archiveIdea, } } diff --git a/src/stores/project/tasks.ts b/src/stores/project/tasks.ts index 8183281..3257cd0 100644 --- a/src/stores/project/tasks.ts +++ b/src/stores/project/tasks.ts @@ -69,10 +69,24 @@ export function createTasksStore() { return ok === true } + /** + * 归档/取消归档任务(归档功能 P1)。归档=移出主视图(后端活跃查询恒过滤), + * 本地同步从列表移除(取消归档时调 loadTasks 重拉)。返回后端命中布尔。 + */ + async function archiveTask(id: string, archived: boolean): Promise { + const ok = await runWithCatch(state, t('tasks.err.archiveFailed'), async () => { + const hit = await taskApi.archive(id, archived) + if (hit && archived) state.tasks = state.tasks.filter(x => x.id !== id) + return hit + }) + return ok === true + } + return { loadTasks, createTask, updateTask, deleteTask, + archiveTask, } } diff --git a/src/views/Ideas.vue b/src/views/Ideas.vue index 32b740e..b56b71f 100644 --- a/src/views/Ideas.vue +++ b/src/views/Ideas.vue @@ -54,7 +54,7 @@
{{ $t('common.loading') }}
-
{{ $t('ideas.listEmpty') }}
+
{{ activeFilter === 'archived' ? $t('ideas.archiveBoxEmpty') : $t('ideas.listEmpty') }}