- notFound/已归档/加载失败三态分离:单查兜底+归档恢复入口+加载失败重试钮 - 反馈一致性六项:同步守卫与完成提示/首屏并行加载/筛选空态文案/计数改过滤口径/路径勾选 seq 守卫/删除确认危险态 - 文件列表滚动条对齐全局范式(scrollbar-width:thin 致 Chromium 忽略定制样式) - 删除失败不再跳转(store 返布尔)/幽灵勾选清理/折叠阈值与 CSS 对齐
137 lines
4.9 KiB
TypeScript
137 lines
4.9 KiB
TypeScript
import { projectApi } from '@/api'
|
|
import type { ImportProjectInput, ProjectQuery } from '@/api/project'
|
|
import { t } from '@/i18n/i18n-helpers'
|
|
import { runWithCatch } from '@/composables/useStoreAction'
|
|
import { state, clearError } from './state'
|
|
|
|
/** 项目 CRUD 子 store(共享全局 state) */
|
|
export function createProjectsStore() {
|
|
/**
|
|
* 加载未删除项目。
|
|
* 可选 query 透传(关键词/排序/分页)。不传 = 当前全量行为(向后兼容)。
|
|
*/
|
|
async function loadProjects(query?: ProjectQuery) {
|
|
state.loading = true
|
|
state.error = null
|
|
await runWithCatch(state, t('projects.err.loadFailed'), async () => {
|
|
state.projects = await projectApi.list(query)
|
|
})
|
|
state.loading = false
|
|
}
|
|
|
|
async function createProject(name: string, description = '', ideaId?: string, path?: string, stack?: string) {
|
|
const record = await runWithCatch(state, t('projects.err.createFailed'), async () => {
|
|
const r = await projectApi.create({ name, description, idea_id: ideaId, path, stack })
|
|
state.projects.push(r)
|
|
return r
|
|
})
|
|
return record ?? null
|
|
}
|
|
|
|
/** 导入历史项目(选已存在目录,后端创建+绑定+探测栈+读 README 首段一步完成) */
|
|
async function importProject(input: ImportProjectInput) {
|
|
const record = await runWithCatch(state, t('projects.err.importFailed'), async () => {
|
|
const r = await projectApi.importProject(input)
|
|
await loadProjects() // 刷新列表(后端已 insert,统一走 load 避免本地数组与后端不一致)
|
|
return r
|
|
})
|
|
return record ?? null
|
|
}
|
|
|
|
async function updateProject(id: string, field: string, value: string) {
|
|
await projectApi.update(id, field, value)
|
|
const idx = state.projects.findIndex(p => p.id === id)
|
|
if (idx >= 0) {
|
|
(state.projects[idx] as any)[field] = value
|
|
}
|
|
}
|
|
|
|
/** 重定位项目目录(后端重探测 stack,返回最新记录并更新本地状态) */
|
|
async function relocateProjectPath(id: string, newPath: string) {
|
|
const record = await projectApi.relocatePath(id, newPath)
|
|
const idx = state.projects.findIndex(p => p.id === id)
|
|
if (idx >= 0) state.projects[idx] = record
|
|
return record
|
|
}
|
|
|
|
async function deleteProject(id: string): Promise<boolean> {
|
|
// 返成功布尔(失败时 runWithCatch 已置 state.error),详情页删除据此阻断失败后的误跳转
|
|
const ok = await runWithCatch(state, t('projects.err.deleteFailed'), async () => {
|
|
await projectApi.delete(id) // 软删 → 回收站(可恢复)
|
|
state.projects = state.projects.filter(p => p.id !== id)
|
|
return true
|
|
})
|
|
return ok === true
|
|
}
|
|
|
|
async function loadDeletedProjects() {
|
|
await runWithCatch(state, t('projects.err.loadTrashFailed'), async () => {
|
|
state.deletedProjects = await projectApi.listDeleted()
|
|
})
|
|
}
|
|
|
|
async function restoreProject(id: string) {
|
|
try {
|
|
await projectApi.restore(id)
|
|
state.deletedProjects = state.deletedProjects.filter(p => p.id !== id)
|
|
await loadProjects() // 恢复后刷新活跃列表
|
|
} catch (e: any) {
|
|
state.error = e?.toString() ?? t('projects.err.restoreFailed')
|
|
}
|
|
}
|
|
|
|
// ── 归档功能 P2(V48):归档箱三动作,对标 tasks/ideas 子 store 同款 ──
|
|
|
|
/** 归档/取消归档。归档=退出主列表;取消归档=从归档箱移回主列表(两端各刷一份) */
|
|
async function archiveProject(id: string, archived: boolean) {
|
|
await runWithCatch(state, t('projects.err.archiveFailed'), async () => {
|
|
await projectApi.archive(id, archived)
|
|
if (archived) {
|
|
// 从主列表移出,归档箱按需懒加载(打开归档箱时 loadArchivedProjects)
|
|
state.projects = state.projects.filter(p => p.id !== id)
|
|
} else {
|
|
state.archivedProjects = state.archivedProjects.filter(p => p.id !== id)
|
|
await loadProjects() // 取消归档回主列表
|
|
}
|
|
})
|
|
}
|
|
|
|
/** 加载归档项目(归档箱 Tab 数据源) */
|
|
async function loadArchivedProjects() {
|
|
await runWithCatch(state, t('projects.err.loadArchivedFailed'), async () => {
|
|
state.archivedProjects = await projectApi.listArchived()
|
|
})
|
|
}
|
|
|
|
/** 归档箱内恢复到主列表(取消归档语义的 UI 别名,动线用词更自然) */
|
|
async function unarchiveProject(id: string) {
|
|
await archiveProject(id, false)
|
|
}
|
|
|
|
async function purgeProject(id: string) {
|
|
try {
|
|
await projectApi.purge(id) // 彻底删(级联物理删,不可恢复)
|
|
state.deletedProjects = state.deletedProjects.filter(p => p.id !== id)
|
|
} catch (e: any) {
|
|
state.error = e?.toString() ?? t('projects.err.purgeFailed')
|
|
}
|
|
}
|
|
|
|
return {
|
|
loadProjects,
|
|
createProject,
|
|
importProject,
|
|
updateProject,
|
|
deleteProject,
|
|
relocateProjectPath,
|
|
loadDeletedProjects,
|
|
restoreProject,
|
|
purgeProject,
|
|
archiveProject,
|
|
loadArchivedProjects,
|
|
unarchiveProject,
|
|
// clearError 来自 state(供 barrel re-export)
|
|
clearError,
|
|
}
|
|
}
|