Merge pull request #36 from yli000/feat/sidebar-collapse

feat: 添加目录栏收起/展开功能
This commit is contained in:
Sanbu
2026-02-22 11:25:26 +08:00
committed by GitHub
+164 -1
View File
@@ -3,7 +3,7 @@ import DefaultTheme from 'vitepress/theme'
import { useData } from 'vitepress' import { useData } from 'vitepress'
import TextType from './components/TextType.vue' import TextType from './components/TextType.vue'
import GitHubStars from './components/GitHubStars.vue' import GitHubStars from './components/GitHubStars.vue'
import { onMounted, ref, watch } from 'vue' import { onMounted, ref, watch, computed } from 'vue'
import ReadingProgress from './components/ReadingProgress.vue' import ReadingProgress from './components/ReadingProgress.vue'
import { Setting } from '@element-plus/icons-vue' import { Setting } from '@element-plus/icons-vue'
@@ -75,6 +75,18 @@ const resetLineHeight = () => {
lineHeight.value = DEFAULT_LINE_HEIGHT lineHeight.value = DEFAULT_LINE_HEIGHT
} }
// ============================================
// 目录栏(左侧 VPSidebar)收起/展开功能
// ============================================
const SIDEBAR_COLLAPSED_KEY = 'ev-sidebar-collapsed'
const sidebarCollapsed = ref(false)
const toggleSidebar = () => {
sidebarCollapsed.value = !sidebarCollapsed.value
}
const isHomePage = computed(() => frontmatter.value.layout === 'home')
onMounted(() => { onMounted(() => {
const saved = clampFontSize(localStorage.getItem(FONT_SIZE_STORAGE_KEY)) const saved = clampFontSize(localStorage.getItem(FONT_SIZE_STORAGE_KEY))
const savedLineHeight = clampLineHeight( const savedLineHeight = clampLineHeight(
@@ -86,6 +98,13 @@ onMounted(() => {
applyLineHeight(savedLineHeight) applyLineHeight(savedLineHeight)
isHydrated.value = true isHydrated.value = true
// 恢复目录栏收起状态
const savedCollapsed = localStorage.getItem(SIDEBAR_COLLAPSED_KEY)
if (savedCollapsed === 'true') {
sidebarCollapsed.value = true
document.body.classList.add('ev-sidebar-collapsed')
}
initOutlineAutoScroll() initOutlineAutoScroll()
}) })
@@ -263,10 +282,30 @@ watch(lineHeight, (next) => {
applyLineHeight(normalized) applyLineHeight(normalized)
localStorage.setItem(LINE_HEIGHT_STORAGE_KEY, String(normalized)) localStorage.setItem(LINE_HEIGHT_STORAGE_KEY, String(normalized))
}) })
watch(sidebarCollapsed, (collapsed) => {
if (typeof document === 'undefined') return
document.body.classList.toggle('ev-sidebar-collapsed', collapsed)
localStorage.setItem(SIDEBAR_COLLAPSED_KEY, String(collapsed))
})
</script> </script>
<template> <template>
<DefaultTheme.Layout> <DefaultTheme.Layout>
<template v-if="!isHomePage" #nav-bar-title-before>
<button
class="ev-sidebar-nav-btn"
type="button"
:aria-label="sidebarCollapsed ? '展开目录' : '收起目录'"
@click.stop.prevent="toggleSidebar"
>
<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
<rect x="1" y="2" width="14" height="1.5" rx="0.75" />
<rect x="1" y="7.25" width="14" height="1.5" rx="0.75" />
<rect x="1" y="12.5" width="14" height="1.5" rx="0.75" />
</svg>
</button>
</template>
<template #nav-bar-content-after> <template #nav-bar-content-after>
<GitHubStars /> <GitHubStars />
<ClientOnly> <ClientOnly>
@@ -389,6 +428,21 @@ watch(lineHeight, (next) => {
</div> </div>
</template> </template>
</DefaultTheme.Layout> </DefaultTheme.Layout>
<ClientOnly>
<button
v-if="!isHomePage"
class="ev-sidebar-toggle-btn"
:class="{ collapsed: sidebarCollapsed }"
type="button"
:aria-label="sidebarCollapsed ? '展开目录' : '收起目录'"
@click="toggleSidebar"
>
<svg width="12" height="12" viewBox="0 0 12 12" fill="currentColor">
<path v-if="!sidebarCollapsed" d="M8 1L3 6l5 5" stroke="currentColor" stroke-width="1.5" fill="none" stroke-linecap="round" stroke-linejoin="round" />
<path v-else d="M4 1l5 5-5 5" stroke="currentColor" stroke-width="1.5" fill="none" stroke-linecap="round" stroke-linejoin="round" />
</svg>
</button>
</ClientOnly>
<ClientOnly> <ClientOnly>
<ReadingProgress /> <ReadingProgress />
</ClientOnly> </ClientOnly>
@@ -519,4 +573,113 @@ watch(lineHeight, (next) => {
border-color: var(--vp-c-brand); border-color: var(--vp-c-brand);
color: var(--vp-c-brand); color: var(--vp-c-brand);
} }
/* ============================================
目录栏收起/展开
============================================ */
/* 导航栏左侧的收起按钮 */
.ev-sidebar-nav-btn {
display: none;
align-items: center;
justify-content: center;
width: 32px;
height: 32px;
border: none;
border-radius: 6px;
background: transparent;
color: var(--vp-c-text-2);
cursor: pointer;
margin-right: 4px;
flex-shrink: 0;
}
.ev-sidebar-nav-btn:hover {
color: var(--vp-c-text-1);
background: var(--vp-c-bg-soft);
}
/* 分界线上的收起按钮 */
.ev-sidebar-toggle-btn {
display: none;
position: fixed;
top: 50%;
transform: translateY(-50%);
left: calc(var(--vp-sidebar-width, 272px) - 12px);
z-index: 30;
width: 24px;
height: 48px;
border: 1px solid var(--vp-c-divider);
border-radius: 0 6px 6px 0;
background: var(--vp-c-bg);
color: var(--vp-c-text-3);
cursor: pointer;
align-items: center;
justify-content: center;
transition: left 0.3s ease, opacity 0.2s;
}
.ev-sidebar-toggle-btn:hover {
color: var(--vp-c-text-1);
border-color: var(--vp-c-brand);
}
.ev-sidebar-toggle-btn.collapsed {
left: 0;
border-radius: 0 6px 6px 0;
}
/* 桌面端才显示按钮 */
@media (min-width: 960px) {
.ev-sidebar-nav-btn {
display: inline-flex;
}
.ev-sidebar-toggle-btn {
display: inline-flex;
}
}
/* @1440px 时分界线按钮跟随侧边栏实际宽度 */
@media (min-width: 1440px) {
.ev-sidebar-toggle-btn:not(.collapsed) {
left: calc((100% - (var(--vp-layout-max-width, 1440px) - 64px)) / 2 + var(--vp-sidebar-width, 272px) - 32px - 12px);
}
}
/* ---- 收起状态下的 CSS 覆盖 ---- */
/* 隐藏侧边栏 */
.ev-sidebar-collapsed .VPSidebar {
display: none !important;
}
/* 内容区域填满页面 */
@media (min-width: 960px) {
.ev-sidebar-collapsed .VPContent.has-sidebar {
padding-left: 0 !important;
}
.ev-sidebar-collapsed .VPNavBar.has-sidebar .content {
padding-left: 0 !important;
}
.ev-sidebar-collapsed .VPNavBar.has-sidebar .divider {
padding-left: 0 !important;
}
}
@media (min-width: 1440px) {
.ev-sidebar-collapsed .VPContent.has-sidebar {
padding-left: calc((100% - var(--vp-layout-max-width, 1440px)) / 2) !important;
}
.ev-sidebar-collapsed .VPNavBar.has-sidebar .content {
padding-left: calc((100% - var(--vp-layout-max-width, 1440px)) / 2) !important;
}
.ev-sidebar-collapsed .VPNavBar.has-sidebar .divider {
padding-left: calc((100% - var(--vp-layout-max-width, 1440px)) / 2) !important;
}
}
/* 收起/展开过渡动画 */
.VPSidebar,
.VPContent.has-sidebar,
.VPNavBar.has-sidebar .content,
.VPNavBar.has-sidebar .divider {
transition: padding-left 0.3s ease, transform 0.3s ease;
}
</style> </style>