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
@@ -0,0 +1,87 @@
<script setup lang="ts">
import { computed } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { useWorkspaceStore } from '@/stores/workspace'
import { useThemeStore } from '@/stores/theme'
import { useEditorStore } from '@/stores/editor'
import { useSettingsStore } from '@/stores/settings'
import { useAgentStore } from '@/stores/agent'
import PrimarySidebar from './PrimarySidebar.vue'
import SecondarySidebar from './SecondarySidebar.vue'
import StatusBar from './StatusBar.vue'
import TitleBar from './TitleBar.vue'
defineProps<{
showSecondarySidebar?: boolean
}>()
const workspaceStore = useWorkspaceStore()
const themeStore = useThemeStore()
const editorStore = useEditorStore()
const settingsStore = useSettingsStore()
const agentStore = useAgentStore()
const route = useRoute()
const router = useRouter()
const routeName = computed(() => route.name as string)
const secondaryComponent = computed(() => {
switch (routeName) {
case 'workspace': return 'file-tree'
case 'search': return 'search-filters'
case 'chat': return 'conversation-list'
case 'agent': return 'run-list'
case 'tasks': return 'task-filters'
case 'skills':
case 'plugins': return 'extension-list'
default: return null
}
})
function openCitation(noteId: string, blockId: string, filePath: string) {
workspaceStore.openFile(filePath)
editorStore.highlightBlock(blockId)
router.push('/workspace')
}
defineExpose({ openCitation })
</script>
<template>
<div class="app-shell" :class="{ 'theme-dark': themeStore.isDark }">
<TitleBar />
<div class="app-body">
<PrimarySidebar />
<SecondarySidebar v-if="secondaryComponent" :component="secondaryComponent" />
<main class="main-content">
<slot />
</main>
</div>
<StatusBar />
</div>
</template>
<style scoped>
.app-shell {
display: flex;
flex-direction: column;
height: 100vh;
width: 100vw;
background: var(--color-background-primary);
color: var(--color-text-primary);
}
.app-body {
flex: 1;
display: flex;
min-height: 0;
overflow: hidden;
}
.main-content {
flex: 1;
min-width: 0;
overflow: hidden;
background: var(--color-background-primary);
}
</style>