src/renderer/routes/README.zh-CN.md
本项目使用 TanStack Router + Multi MemoryRouter 架构,每个 Tab 拥有独立的路由实例,实现原生 KeepAlive。
在 src/renderer/routes/ 目录下创建文件:
// routes/knowledge.tsx
import { createFileRoute } from '@tanstack/react-router'
export const Route = createFileRoute('/knowledge')({
component: KnowledgePage
})
function KnowledgePage() {
return <div>Knowledge Page</div>
}
运行 yarn dev 后,TanStack Router 会自动更新 routeTree.gen.ts。
// routes/chat/$topicId.tsx
import { createFileRoute } from '@tanstack/react-router'
export const Route = createFileRoute('/chat/$topicId')({
component: ChatPage
})
function ChatPage() {
const { topicId } = Route.useParams()
return <div>Chat: {topicId}</div>
}
routes/
├── settings.tsx # /settings (布局)
├── settings/
│ ├── general.tsx # /settings/general
│ └── provider.tsx # /settings/provider
// routes/settings.tsx
import { createFileRoute, Outlet } from '@tanstack/react-router'
export const Route = createFileRoute('/settings')({
component: SettingsLayout
})
function SettingsLayout() {
return (
<div className="flex">
<aside>Settings Menu</aside>
<main><Outlet /></main>
</div>
)
}
本项目有两种导航方式:
openTab打开新 Tab 或切换到已有 Tab,使用 useTabs hook:
import { useTabs } from '@renderer/hooks/tab'
function MyComponent() {
const { openTab, closeTab } = useTabs()
// 基础用法 - 复用已有 Tab 或新建
openTab('/settings')
// 带标题
openTab('/chat/123', { title: 'Chat with Alice' })
// 强制新开 Tab(即使已有相同 URL)
openTab('/settings', { forceNew: true })
// 打开 Webview Tab
openTab('https://example.com', {
type: 'webview',
title: 'Example Site'
})
// 关闭 Tab
closeTab(tabId)
}
useNavigate在同一个 Tab 内跳转路由(不会新开 Tab),使用 TanStack Router 的 useNavigate:
import { useNavigate } from '@tanstack/react-router'
function SettingsPage() {
const navigate = useNavigate()
// 在当前 Tab 内跳转到子页面
navigate({ to: '/settings/provider' })
// 带参数跳转
navigate({ to: '/chat/$topicId', params: { topicId: '123' } })
}
| 场景 | 使用 | 效果 |
|---|---|---|
| 打开新功能模块 | openTab('/knowledge') | 新建 Tab |
| 设置页内切换子页 | navigate({ to: '/settings/provider' }) | 当前 Tab 内跳转 |
| 从列表打开详情 | openTab('/chat/123', { title: '...' }) | 新建 Tab |
| 返回上一页 | navigate({ to: '..' }) | 当前 Tab 内返回 |
useTabs() 返回值| 属性/方法 | 类型 | 说明 |
|---|---|---|
tabs | Tab[] | 所有 Tab 列表 |
activeTabId | string | 当前激活的 Tab ID |
activeTab | Tab | undefined | 当前激活的 Tab 对象 |
openTab(url, options?) | (url: string, options?: OpenTabOptions) => string | 打开 Tab,返回 Tab ID |
closeTab(id) | (id: string) => void | 关闭指定 Tab |
setActiveTab(id) | (id: string) => void | 切换到指定 Tab |
updateTab(id, updates) | (id: string, updates: Partial<Tab>) => void | 更新 Tab 属性 |
OpenTabOptions| 选项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
forceNew | boolean | false | 强制新开 Tab |
title | string | URL 路径 | Tab 标题 |
type | 'route' | 'webview' | 'route' | Tab 类型 |
id | string | 自动生成 | 自定义 Tab ID |
AppShell
├── Sidebar
├── TabBar
└── Content Area
├── TabRouter #1 (Home)
│ └── Activity(visible) → MemoryRouter → RouterProvider
├── TabRouter #2 (Settings)
│ └── Activity(hidden) → MemoryRouter → RouterProvider
└── WebviewContainer (for webview tabs)
MemoryRouter 实例<Activity> 组件控制可见性| 层级 | 机制 | 作用范围 |
|---|---|---|
| 路由 render 错误 | 每 tab router 的 defaultErrorComponent: RouteErrorFallback(TabRouter.tsx) | 圈禁在抛错 tab 内;带主题的错误卡片,可重试/重载 |
| Provider render 错误 | 各窗口 App 最外层 <ErrorBoundary fallbackComponent={WindowFatalFallback}> | 整窗回退到 context-free 致命错误页,不再白屏 |
errorComponent 路由选项覆盖默认defaultErrorComponent,TanStack 以透传 fragment 包裹 match:路由 render 错误会冒泡到窗口级边界,炸掉整窗src/renderer/
├── routes/ # 路由页面(TanStack Router 文件路由)
│ ├── __root.tsx # 根路由(渲染 Outlet)
│ ├── settings.tsx # /settings
│ ├── settings.index.tsx # /settings/ 索引路由(平铺点记法——禁止裸 index.tsx)
│ └── README.md # 本文档
├── components/layout/
│ ├── AppShell.tsx # 主布局(Sidebar + TabBar + Content)
│ └── TabRouter.tsx # Tab 路由容器(MemoryRouter + Activity)
├── hooks/
│ └── useTabs.ts # Tab 状态管理 Hook
└── routeTree.gen.ts # 自动生成的路由树(勿手动编辑)
routeTree.gen.ts - 它由 TanStack Router 自动生成routes/settings.tsx → /settings$ - routes/chat/$topicId.tsx → /chat/:topicIduseState、滚动位置等