fix(frontend): 修复 Markdown 目录锚点跳转与 Tauri 路由

- 安装 rehype-slug 插件,自动为 heading 生成 id,解决目录链接无锚点目标的问题
- 自定义 <a> 组件处理内部锚点链接,阻止默认刷新行为并使用 scrollIntoView 平滑滚动
- Tauri 桌面端改用 HashRouter,避免刷新时 404 及错误打开外部浏览器

fixes #xxx

Co-Authored-By: HAPI <noreply@hapi.run>
This commit is contained in:
techotaku39
2026-05-24 02:53:08 +08:00
parent 717df2af7b
commit ebdb254fc6
4 changed files with 98 additions and 77 deletions

View File

@@ -1,6 +1,6 @@
import './App.css'
import { lazy, Suspense, useEffect } from 'react'
import { BrowserRouter, Navigate, Routes, Route } from 'react-router-dom'
import { BrowserRouter, HashRouter, Navigate, Routes, Route } from 'react-router-dom'
import { useTaskPolling } from '@/hooks/useTaskPolling.ts'
import { useCheckBackend } from '@/hooks/useCheckBackend.ts'
import { systemCheck } from '@/services/system.ts'
@@ -57,12 +57,16 @@ function App() {
)
}
// 桌面端使用 HashRouter 避免刷新 404Web 端继续使用 BrowserRouter
const isTauri = typeof window !== 'undefined' && '__TAURI_INTERNALS__' in window
const Router = isTauri ? HashRouter : BrowserRouter
// 后端已初始化,渲染主应用
return (
<>
<StartupBanner />
<BackendHealthIndicator />
<BrowserRouter>
<Router>
<Suspense fallback={<div className="flex h-screen items-center justify-center"></div>}>
<Routes>
<Route path="/onboarding" element={<Onboarding />} />
@@ -86,7 +90,7 @@ function App() {
</Route>
</Routes>
</Suspense>
</BrowserRouter>
</Router>
</>
)
}

View File

@@ -14,6 +14,7 @@ import 'react-medium-image-zoom/dist/styles.css'
import gfm from 'remark-gfm'
import remarkMath from 'remark-math'
import rehypeKatex from 'rehype-katex'
import rehypeSlug from 'rehype-slug'
import 'katex/dist/katex.min.css'
import 'github-markdown-css/github-markdown-light.css'
import { ScrollArea } from '@/components/ui/scroll-area.tsx'
@@ -47,7 +48,7 @@ const steps = [
]
const remarkPlugins = [gfm, remarkMath]
const rehypePlugins = [rehypeKatex]
const rehypePlugins = [rehypeKatex, rehypeSlug]
/**
* 构建 ReactMarkdown components 对象baseURL 用于修正图片路径。
@@ -117,6 +118,31 @@ function createMarkdownComponents(baseURL: string) {
)
}
// 处理笔记内部锚点链接(如目录跳转)
if (href?.startsWith('#')) {
const handleAnchorClick = (e: React.MouseEvent) => {
e.preventDefault()
const id = decodeURIComponent(href.slice(1))
const target = document.getElementById(id)
if (target) {
target.scrollIntoView({ behavior: 'smooth', block: 'start' })
} else {
toast.error('未找到对应章节')
}
}
return (
<a
href={href}
onClick={handleAnchorClick}
className="text-primary hover:text-primary/80 inline-flex items-center gap-0.5 font-medium underline underline-offset-4"
{...props}
>
{children}
</a>
)
}
return (
<a
href={href}