feat: 添加目录栏收起/展开功能
This commit is contained in:
@@ -3,7 +3,7 @@ import DefaultTheme from 'vitepress/theme'
|
||||
import { useData } from 'vitepress'
|
||||
import TextType from './components/TextType.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 { Setting } from '@element-plus/icons-vue'
|
||||
|
||||
@@ -75,6 +75,18 @@ const resetLineHeight = () => {
|
||||
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(() => {
|
||||
const saved = clampFontSize(localStorage.getItem(FONT_SIZE_STORAGE_KEY))
|
||||
const savedLineHeight = clampLineHeight(
|
||||
@@ -86,6 +98,13 @@ onMounted(() => {
|
||||
applyLineHeight(savedLineHeight)
|
||||
isHydrated.value = true
|
||||
|
||||
// 恢复目录栏收起状态
|
||||
const savedCollapsed = localStorage.getItem(SIDEBAR_COLLAPSED_KEY)
|
||||
if (savedCollapsed === 'true') {
|
||||
sidebarCollapsed.value = true
|
||||
document.body.classList.add('ev-sidebar-collapsed')
|
||||
}
|
||||
|
||||
initOutlineAutoScroll()
|
||||
})
|
||||
|
||||
@@ -263,10 +282,30 @@ watch(lineHeight, (next) => {
|
||||
applyLineHeight(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>
|
||||
|
||||
<template>
|
||||
<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>
|
||||
<GitHubStars />
|
||||
<ClientOnly>
|
||||
@@ -389,6 +428,21 @@ watch(lineHeight, (next) => {
|
||||
</div>
|
||||
</template>
|
||||
</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>
|
||||
<ReadingProgress />
|
||||
</ClientOnly>
|
||||
@@ -519,4 +573,113 @@ watch(lineHeight, (next) => {
|
||||
border-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>
|
||||
|
||||
Reference in New Issue
Block a user