feat(frontend): 搭建桌面端基础界面与 Workspace

- App Shell 壳层:主导航、次导航、Title Bar、状态栏
- Vault 入口页:最近 Vault、打开/创建 Vault、AI Core 状态
- Workspace 与文件树:浏览、打开、创建笔记/文件夹
- Design Token:浅色/深色主题 CSS Variables
- Contracts / Service / Store / Router 基础架构
- 统一 ApiClient 与 SseClient,对接后端 API 契约
This commit is contained in:
2026-08-29 10:36:16 +08:00
parent 694c1b27c1
commit f9efc4f4a2
42 changed files with 8044 additions and 167 deletions
+100
View File
@@ -0,0 +1,100 @@
import { createRouter, createWebHashHistory } from 'vue-router'
import { useWorkspaceStore } from '@/stores/workspace'
const routes = [
{
path: '/',
name: 'vault-entry',
component: () => import('@/features/vault/VaultEntry.vue'),
meta: { title: '选择知识库' },
},
{
path: '/workspace',
name: 'workspace',
component: () => import('@/features/workspace/WorkspaceView.vue'),
meta: { title: '工作区', requiresVault: true },
},
{
path: '/search',
name: 'search',
component: () => import('@/features/search/SearchView.vue'),
meta: { title: '搜索', requiresVault: true },
},
{
path: '/chat',
name: 'chat',
component: () => import('@/features/chat/ChatView.vue'),
meta: { title: 'AI 对话', requiresVault: true },
},
{
path: '/agent/runs/:runId?',
name: 'agent',
component: () => import('@/features/agent/AgentView.vue'),
meta: { title: 'Agent Trace', requiresVault: true },
},
{
path: '/tasks',
name: 'tasks',
component: () => import('@/features/tasks/TasksView.vue'),
meta: { title: '任务', requiresVault: true },
},
{
path: '/extensions/skills',
name: 'skills',
component: () => import('@/features/skills/SkillsView.vue'),
meta: { title: 'Skill 管理', requiresVault: true },
},
{
path: '/extensions/plugins',
name: 'plugins',
component: () => import('@/features/plugins/PluginsView.vue'),
meta: { title: 'Plugin 管理', requiresVault: true },
},
{
path: '/themes',
name: 'themes',
component: () => import('@/features/themes/ThemesView.vue'),
meta: { title: '主题管理', requiresVault: true },
},
{
path: '/settings',
name: 'settings',
component: () => import('@/features/settings/SettingsView.vue'),
meta: { title: '设置', requiresVault: true },
children: [
{ path: '', redirect: '/settings/general' },
{ path: 'general', component: () => import('@/features/settings/sections/GeneralSection.vue') },
{ path: 'editor', component: () => import('@/features/settings/sections/EditorSection.vue') },
{ path: 'providers', component: () => import('@/features/settings/sections/ProvidersSection.vue') },
{ path: 'index', component: () => import('@/features/settings/sections/IndexSection.vue') },
{ path: 'permissions', component: () => import('@/features/settings/sections/PermissionsSection.vue') },
{ path: 'ai-core', component: () => import('@/features/settings/sections/AiCoreSection.vue') },
],
},
]
const router = createRouter({
history: createWebHashHistory(),
routes,
})
router.beforeEach((to, _from, next) => {
const workspaceStore = useWorkspaceStore()
if (to.meta.requiresVault && !workspaceStore.hasVault) {
next({ path: '/' })
return
}
if (to.path === '/' && workspaceStore.hasVault) {
next({ path: '/workspace' })
return
}
next()
})
router.afterEach((to) => {
const baseTitle = '知笔知己'
const title = to.meta.title as string | undefined
document.title = title ? `${title} · ${baseTitle}` : baseTitle
})
export default router