Skip to content

Commit 460c697

Browse files
feat(project): add archive functionality
Users can now archive inactive projects to keep the main list focused: - Archive/unarchive via right-click menu (Archive/Unarchive action) - Archived projects hidden from default view, shown only when 'Archived' status filter is selected - Project stats and counts exclude archived projects - Archive state persists in projects.json (omitted when false) - Updated LocalProject type, projectsStore with activeProjects/ archivedProjects computed, toggleProjectArchived action - i18n: added 'archived', 'archive', 'unarchive' translations (zh-CN, en) Implementation details: - filteredProjects switches data source based on statusFilter - archived projects still participate in scan deduplication (via allProjects) to prevent re-import - updateProject preserves isArchived when caller omits it Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 17fcf07 commit 460c697

5 files changed

Lines changed: 60 additions & 12 deletions

File tree

src/constants/localProject.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,8 @@ export interface LocalProject {
5858
isTemporary: boolean
5959
// 标识该项目是否置顶
6060
isPinned?: boolean
61+
// 标识该项目是否已归档(归档项目不出现在默认列表中)
62+
isArchived?: boolean
6163
// 标识该项目是否仍然存在于文件系统中
6264
isExists: boolean
6365
}

src/locales/en.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ app:
7474
available: Available
7575
missing_path: Missing path
7676
temporary: Temporary
77+
archived: Archived
7778
all_languages: All languages
7879
all_groups: All groups
7980
no_group: No group
@@ -119,6 +120,8 @@ app:
119120
copy_failed: Copy failed
120121
pin: Pin
121122
unpin: Unpin
123+
archive: Archive
124+
unarchive: Unarchive
122125
more: More actions
123126
edit: Edit
124127
remove: Remove

src/locales/zh-CN.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ app:
7474
available: 可用
7575
missing_path: 路径缺失
7676
temporary: 临时
77+
archived: 已归档
7778
all_languages: 全部语言
7879
all_groups: 全部分组
7980
no_group: 无分组
@@ -119,6 +120,8 @@ app:
119120
copy_failed: 复制失败
120121
pin: 置顶
121122
unpin: 取消置顶
123+
archive: 归档
124+
unarchive: 取消归档
122125
more: 更多操作
123126
edit: 编辑
124127
remove: 移除

src/stores/projectsStore.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ const projectsPersistence = createPersistence<LocalProject[]>({
1515
return undefined
1616
if (key === 'isPinned' && value !== true)
1717
return undefined
18+
if (key === 'isArchived' && value !== true)
19+
return undefined
1820
if (key === 'license' && value === 'None')
1921
return undefined
2022
return value
@@ -51,6 +53,7 @@ function normalizeProject(project: LocalProject): LocalProject {
5153
&& typeof project.group === 'string'
5254
&& typeof project.isTemporary === 'boolean'
5355
&& typeof project.isPinned === 'boolean'
56+
&& typeof project.isArchived === 'boolean'
5457
&& typeof project.isExists === 'boolean'
5558
&& Array.isArray(project.langGroup)
5659
) {
@@ -64,6 +67,7 @@ function normalizeProject(project: LocalProject): LocalProject {
6467
group: project.group || '',
6568
isTemporary: !!project.isTemporary,
6669
isPinned: !!project.isPinned,
70+
isArchived: !!project.isArchived,
6771
isExists: project.isExists ?? true,
6872
langGroup: project.langGroup || [],
6973
}
@@ -75,6 +79,8 @@ export const useProjectsStore = defineStore('projects', () => {
7579
let loadingPromise: Promise<void> | null = null
7680

7781
const allProjects = computed(() => projects.value)
82+
const activeProjects = computed(() => projects.value.filter(project => !project.isArchived))
83+
const archivedProjects = computed(() => projects.value.filter(project => project.isArchived))
7884
const tempProjects = computed(() => projects.value.filter(project => project.isTemporary))
7985

8086
const mainLangSummary = computed(() => {
@@ -142,6 +148,7 @@ export const useProjectsStore = defineStore('projects', () => {
142148
const project = normalizeProject({
143149
...updatedProject,
144150
isPinned: updatedProject.isPinned ?? projects.value[index].isPinned,
151+
isArchived: updatedProject.isArchived ?? projects.value[index].isArchived,
145152
lastOpenedAt: updatedProject.lastOpenedAt ?? projects.value[index].lastOpenedAt,
146153
})
147154
project.isExists = await checkProjectExistence(project)
@@ -178,6 +185,16 @@ export const useProjectsStore = defineStore('projects', () => {
178185
return true
179186
}
180187

188+
async function toggleProjectArchived(appendTime: LocalProject['appendTime']) {
189+
const project = getProjectByAppendTime(appendTime)
190+
if (!project)
191+
return false
192+
193+
project.isArchived = !project.isArchived
194+
await saveProjects()
195+
return true
196+
}
197+
181198
async function setProjectDefaultOpen(
182199
appendTime: LocalProject['appendTime'],
183200
defaultOpen: LocalProject['defaultOpen'],
@@ -320,6 +337,8 @@ export const useProjectsStore = defineStore('projects', () => {
320337
projects,
321338
loaded,
322339
allProjects,
340+
activeProjects,
341+
archivedProjects,
323342
tempProjects,
324343
mainLangSummary,
325344
allGroups,
@@ -330,6 +349,7 @@ export const useProjectsStore = defineStore('projects', () => {
330349
updateProject,
331350
removeProject,
332351
toggleProjectPinned,
352+
toggleProjectArchived,
333353
setProjectDefaultOpen,
334354
clearProjects,
335355
saveProjects,

src/views/HomeView/HomeView.vue

Lines changed: 32 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ defineOptions({
3535
})
3636
3737
type KindFilter = ProjectKind | 'all'
38-
type StatusFilter = 'all' | 'available' | 'missing' | 'temporary'
38+
type StatusFilter = 'all' | 'available' | 'missing' | 'temporary' | 'archived'
3939
type SortKey = 'recent' | 'name' | 'language' | 'group'
4040
type LayoutMode = 'list' | 'grid'
4141
type ProjectActionKey = 'open' | 'explorer' | 'terminal' | 'copy' | 'more'
@@ -137,7 +137,8 @@ function clearSearchDebounceTimer() {
137137
searchDebounceTimer = null
138138
}
139139
140-
const totalProjects = computed(() => projectsStore.allProjects.length)
140+
const totalProjects = computed(() => projectsStore.activeProjects.length)
141+
const archivedProjectsCount = computed(() => projectsStore.archivedProjects.length)
141142
142143
const projectStats = computed(() => {
143144
const byKind = new Map<ProjectKind, number>()
@@ -147,7 +148,7 @@ const projectStats = computed(() => {
147148
let temporary = 0
148149
let ungrouped = 0
149150
150-
for (const project of projectsStore.allProjects) {
151+
for (const project of projectsStore.activeProjects) {
151152
byKind.set(project.kind, (byKind.get(project.kind) || 0) + 1)
152153
153154
if (project.isExists)
@@ -193,6 +194,7 @@ const statusOptions = computed<Array<FilterOption<StatusFilter>>>(() => [
193194
{ value: 'available', label: t('app.home.filters.available'), count: availableProjects.value },
194195
{ value: 'missing', label: t('app.home.filters.missing_path'), count: missingProjects.value },
195196
{ value: 'temporary', label: t('app.home.filters.temporary'), count: temporaryProjects.value },
197+
{ value: 'archived', label: t('app.home.filters.archived'), count: archivedProjectsCount.value },
196198
])
197199
198200
const sortOptions = computed<Array<FilterOption<SortKey>>>(() => [
@@ -256,19 +258,25 @@ watch(projectSearchSignature, () => {
256258
}, { immediate: true })
257259
258260
const filteredProjects = computed(() => {
259-
let result = projectsStore.allProjects
261+
// 归档视图 vs 活跃视图的基础数据源分离
262+
let result = statusFilter.value === 'archived'
263+
? projectsStore.archivedProjects
264+
: projectsStore.activeProjects
260265
261266
if (kindFilter.value !== 'all') {
262267
result = result.filter(project => project.kind === kindFilter.value)
263268
}
264-
if (statusFilter.value === 'available') {
265-
result = result.filter(project => project.isExists)
266-
}
267-
if (statusFilter.value === 'missing') {
268-
result = result.filter(project => !project.isExists)
269-
}
270-
if (statusFilter.value === 'temporary') {
271-
result = result.filter(project => project.isTemporary)
269+
// 归档视图下忽略其他 status 细分过滤
270+
if (statusFilter.value !== 'archived') {
271+
if (statusFilter.value === 'available') {
272+
result = result.filter(project => project.isExists)
273+
}
274+
if (statusFilter.value === 'missing') {
275+
result = result.filter(project => !project.isExists)
276+
}
277+
if (statusFilter.value === 'temporary') {
278+
result = result.filter(project => project.isTemporary)
279+
}
272280
}
273281
if (languageFilter.value !== 'all') {
274282
result = result.filter(project => project.mainLang === languageFilter.value)
@@ -409,6 +417,11 @@ function projectMenuItems(project: LocalProject): UiActionMenuItem[] {
409417
label: pinButtonTitle(project),
410418
icon: pinButtonIcon(project),
411419
},
420+
{
421+
id: 'archive',
422+
label: project.isArchived ? t('app.home.actions.unarchive') : t('app.home.actions.archive'),
423+
icon: project.isArchived ? 'i-lucide:archive-restore' : 'i-lucide:archive',
424+
},
412425
...(sourceUrl
413426
? [{
414427
id: 'source',
@@ -1347,6 +1360,10 @@ async function toggleProjectPinned(project: LocalProject) {
13471360
await projectsStore.toggleProjectPinned(project.appendTime)
13481361
}
13491362
1363+
async function toggleProjectArchived(project: LocalProject) {
1364+
await projectsStore.toggleProjectArchived(project.appendTime)
1365+
}
1366+
13501367
async function setProjectDefaultEditor(project: LocalProject, editor: CodeEditorEnum) {
13511368
await projectsStore.setProjectDefaultOpen(project.appendTime, editor)
13521369
}
@@ -1379,6 +1396,9 @@ function handleProjectMenuSelect(project: LocalProject, action: string) {
13791396
case 'pin':
13801397
void toggleProjectPinned(project)
13811398
break
1399+
case 'archive':
1400+
void toggleProjectArchived(project)
1401+
break
13821402
case 'source':
13831403
openProjectSource(project)
13841404
break

0 commit comments

Comments
 (0)