Merge pull request #36 from yli000/feat/sidebar-collapse
feat: 添加目录栏收起/展开功能
This commit is contained in:
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user