Files
test-repo/docs/.vitepress/theme/Layout.vue
T
2026-05-06 23:39:35 +08:00

953 lines
26 KiB
Vue

<script setup>
import DefaultTheme from 'vitepress/theme'
import { useData, useRoute, withBase } from 'vitepress'
import TextType from './components/TextType.vue'
import GitHubStars from './components/GitHubStars.vue'
import PageSlidesButton from './components/PageSlidesButton.vue'
import { onMounted, onBeforeUnmount, ref, watch, computed } from 'vue'
import ReadingProgress from './components/ReadingProgress.vue'
import { Setting } from '@element-plus/icons-vue'
import easyVibePaths from './data/easyVibePaths.json'
const { frontmatter } = useData()
const route = useRoute()
const openWelcomeFromWordmark = () => {
const currentPath = window.location.pathname
window.location.href = withBase(
`/welcome/?next=${encodeURIComponent(currentPath)}`
)
}
const homeTaglineTyping = {
typingSpeed: 45,
initialDelay: 0,
pauseDuration: 2500,
postDeletingDelay: 500,
deletingSpeed: 18
}
const FONT_SIZE_STORAGE_KEY = 'ev-doc-font-size'
const LINE_HEIGHT_STORAGE_KEY = 'ev-doc-line-height'
const MIN_FONT_SIZE = 12
const MAX_FONT_SIZE = 18
const DEFAULT_FONT_SIZE = 14
const MIN_LINE_HEIGHT = 1.25
const MAX_LINE_HEIGHT = 1.8
const DEFAULT_LINE_HEIGHT = 1.65
const fontSize = ref(DEFAULT_FONT_SIZE)
const lineHeight = ref(DEFAULT_LINE_HEIGHT)
const isHydrated = ref(false)
const clampFontSize = (value) => {
if (value === null || value === undefined || value === '')
return DEFAULT_FONT_SIZE
const numeric = Number(value)
if (!Number.isFinite(numeric)) return DEFAULT_FONT_SIZE
return Math.min(MAX_FONT_SIZE, Math.max(MIN_FONT_SIZE, numeric))
}
const clampLineHeight = (value) => {
if (value === null || value === undefined || value === '')
return DEFAULT_LINE_HEIGHT
const numeric = Number(value)
if (!Number.isFinite(numeric)) return DEFAULT_LINE_HEIGHT
return Math.min(MAX_LINE_HEIGHT, Math.max(MIN_LINE_HEIGHT, numeric))
}
const applyFontSize = (size) => {
if (typeof document === 'undefined') return
document.documentElement.style.setProperty('--ev-doc-font-size', `${size}px`)
}
const applyLineHeight = (value) => {
if (typeof document === 'undefined') return
document.documentElement.style.setProperty(
'--ev-doc-line-height',
String(value)
)
}
const decreaseFontSize = () => {
fontSize.value = clampFontSize(fontSize.value - 1)
}
const increaseFontSize = () => {
fontSize.value = clampFontSize(fontSize.value + 1)
}
const resetFontSize = () => {
fontSize.value = DEFAULT_FONT_SIZE
}
const resetLineHeight = () => {
lineHeight.value = DEFAULT_LINE_HEIGHT
}
// ============================================
// 目录栏(左侧 VPSidebar)收起/展开功能
// ============================================
const SIDEBAR_COLLAPSED_KEY = 'ev-sidebar-collapsed'
const SIDEBAR_WIDTH_KEY = 'ev-sidebar-width'
const DEFAULT_SIDEBAR_WIDTH = 272
const MIN_SIDEBAR_WIDTH = 160
const MAX_SIDEBAR_WIDTH = 560
const sidebarCollapsed = ref(false)
const sidebarWidth = ref(DEFAULT_SIDEBAR_WIDTH)
const sidebarResizing = ref(false)
let sidebarResizeLeft = 0
const toggleSidebar = () => {
sidebarCollapsed.value = !sidebarCollapsed.value
}
const getSidebarWidthBounds = () => {
if (typeof window === 'undefined') {
return {
min: MIN_SIDEBAR_WIDTH,
max: MAX_SIDEBAR_WIDTH
}
}
const viewportMax = window.innerWidth - 240
return {
min: MIN_SIDEBAR_WIDTH,
max: Math.min(MAX_SIDEBAR_WIDTH, Math.max(MIN_SIDEBAR_WIDTH, viewportMax))
}
}
const clampSidebarWidth = (value) => {
const numeric = Number(value)
if (!Number.isFinite(numeric)) return DEFAULT_SIDEBAR_WIDTH
const bounds = getSidebarWidthBounds()
return Math.min(bounds.max, Math.max(bounds.min, numeric))
}
const applySidebarWidth = (width) => {
if (typeof document === 'undefined') return
document.documentElement.style.setProperty('--vp-sidebar-width', `${width}px`)
}
const setSidebarWidth = (value, shouldPersist = true) => {
const normalized = clampSidebarWidth(value)
sidebarWidth.value = normalized
applySidebarWidth(normalized)
if (shouldPersist) {
localStorage.setItem(SIDEBAR_WIDTH_KEY, String(normalized))
}
}
const getSidebarLeftBoundary = () => {
const sidebar = document.querySelector('.VPSidebar')
if (sidebar) {
return sidebar.getBoundingClientRect().left
}
return 0
}
const updateSidebarWidthFromPointer = (clientX) => {
const nextWidth = clientX - sidebarResizeLeft
setSidebarWidth(nextWidth, false)
}
const handleSidebarResizeMove = (event) => {
if (!sidebarResizing.value) return
updateSidebarWidthFromPointer(event.clientX)
}
const stopSidebarResize = () => {
if (!sidebarResizing.value) return
sidebarResizing.value = false
document.body.classList.remove('ev-sidebar-resizing')
localStorage.setItem(SIDEBAR_WIDTH_KEY, String(sidebarWidth.value))
window.removeEventListener('pointermove', handleSidebarResizeMove)
window.removeEventListener('pointerup', stopSidebarResize)
window.removeEventListener('pointercancel', stopSidebarResize)
}
const startSidebarResize = (event) => {
if (typeof window === 'undefined') return
if (window.innerWidth < 960 || sidebarCollapsed.value) return
event.preventDefault()
sidebarResizeLeft = getSidebarLeftBoundary()
sidebarResizing.value = true
document.body.classList.add('ev-sidebar-resizing')
updateSidebarWidthFromPointer(event.clientX)
window.addEventListener('pointermove', handleSidebarResizeMove)
window.addEventListener('pointerup', stopSidebarResize)
window.addEventListener('pointercancel', stopSidebarResize)
}
const handleViewportResize = () => {
setSidebarWidth(sidebarWidth.value, false)
}
const isHomePage = computed(() => frontmatter.value.layout === 'home')
const isWelcomePage = computed(() =>
route.path === '/welcome/' ||
route.path.endsWith('/welcome/') ||
route.path.endsWith('/welcome.html')
)
onMounted(() => {
const saved = clampFontSize(localStorage.getItem(FONT_SIZE_STORAGE_KEY))
const savedLineHeight = clampLineHeight(
localStorage.getItem(LINE_HEIGHT_STORAGE_KEY)
)
fontSize.value = saved
lineHeight.value = savedLineHeight
applyFontSize(saved)
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')
}
const savedSidebarWidth = localStorage.getItem(SIDEBAR_WIDTH_KEY)
if (savedSidebarWidth) {
setSidebarWidth(savedSidebarWidth, false)
} else {
setSidebarWidth(DEFAULT_SIDEBAR_WIDTH, false)
}
window.addEventListener('resize', handleViewportResize)
initOutlineAutoScroll()
})
onBeforeUnmount(() => {
window.removeEventListener('resize', handleViewportResize)
stopSidebarResize()
})
// ============================================
// Outline 侧边栏自动滚动跟随功能
// 当页面滚动时,自动滚动 outline 让当前激活项保持在可视区域
// ============================================
function initOutlineAutoScroll() {
const outlineSelectors = [
'.VPDocAsideOutline',
'.VPTableOfContents',
'.vitepress-doc-sidebar',
'.sidebar-outline',
'aside'
]
const sidebarSelectors = [
'.VPSidebar',
'.VPDocSidebar',
'.vitepress-doc-sidebar'
]
let outlineContainer = null
for (const selector of outlineSelectors) {
outlineContainer = document.querySelector(selector)
if (outlineContainer) break
}
if (!outlineContainer) return
let sidebarContainer = null
for (const selector of sidebarSelectors) {
sidebarContainer = document.querySelector(selector)
if (sidebarContainer) break
}
const observer = new MutationObserver((mutations) => {
for (const mutation of mutations) {
if (mutation.type === 'attributes' && mutation.attributeName === 'class') {
const target = mutation.target
if (target.classList.contains('active') && target.tagName === 'A') {
scrollOutlineToActiveItem(target)
}
}
}
})
const sidebarObserver = new MutationObserver((mutations) => {
for (const mutation of mutations) {
if (mutation.type === 'attributes' && mutation.attributeName === 'class') {
const target = mutation.target
if (target.classList.contains('is-active')) {
scrollSidebarToActiveItem(target)
}
}
}
})
const startObserving = () => {
const outlineContainer = document.querySelector('.VPDocAsideOutline')
if (outlineContainer) {
observer.observe(outlineContainer, {
attributes: true,
subtree: true,
attributeFilter: ['class']
})
const existingActive = outlineContainer.querySelector('.active')
if (existingActive) {
scrollOutlineToActiveItem(existingActive)
}
}
if (sidebarContainer) {
sidebarObserver.observe(sidebarContainer, {
attributes: true,
subtree: true,
attributeFilter: ['class']
})
const existingSidebarActive = sidebarContainer.querySelector('.is-active')
if (existingSidebarActive) {
scrollSidebarToActiveItem(existingSidebarActive)
}
}
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', startObserving)
} else {
startObserving()
}
const originalPushState = history.pushState
const originalReplaceState = history.replaceState
history.pushState = function (...args) {
originalPushState.apply(this, args)
setTimeout(startObserving, 300)
}
history.replaceState = function (...args) {
originalReplaceState.apply(this, args)
setTimeout(startObserving, 300)
}
window.addEventListener('popstate', () => {
setTimeout(startObserving, 300)
})
}
// 滚动 outline 让当前激活项保持在可视区域中心
function scrollOutlineToActiveItem(activeLink) {
const outlineContainer = document.querySelector('.VPDocAsideOutline')
if (!outlineContainer || !activeLink) return
const containerRect = outlineContainer.getBoundingClientRect()
const linkRect = activeLink.getBoundingClientRect()
// 计算链接相对于容器的位置
const linkTop = linkRect.top - containerRect.top + outlineContainer.scrollTop
const linkHeight = linkRect.height
const containerHeight = containerRect.height
// 判断链接是否在可视区域外
const isAbove = linkRect.top < containerRect.top + 20
const isBelow = linkRect.bottom > containerRect.bottom - 20
if (isAbove || isBelow) {
// 将激活项滚动到容器中间位置
const targetScrollTop = linkTop - containerHeight / 2 + linkHeight / 2
outlineContainer.scrollTo({
top: targetScrollTop,
behavior: 'smooth'
})
}
}
// 滚动侧边栏让当前激活项保持在可视区域中心
function scrollSidebarToActiveItem(activeItem) {
const sidebarContainer = document.querySelector('.VPSidebar') || document.querySelector('.VPDocSidebar')
if (!sidebarContainer || !activeItem) return
const targetElement = activeItem.querySelector('.item') || activeItem.querySelector('a') || activeItem
const containerRect = sidebarContainer.getBoundingClientRect()
const targetRect = targetElement.getBoundingClientRect()
const targetTop = targetRect.top - containerRect.top + sidebarContainer.scrollTop
const targetHeight = targetRect.height
const targetCenterY = targetTop + targetHeight / 2
const isInside = targetRect.top >= containerRect.top - 20 &&
targetRect.bottom <= containerRect.bottom + 20
if (!isInside) {
const targetScrollTop = targetCenterY - containerRect.height / 2
sidebarContainer.scrollTo({
top: targetScrollTop,
behavior: 'smooth'
})
}
}
watch(fontSize, (next) => {
if (!isHydrated.value) return
const normalized = clampFontSize(next)
applyFontSize(normalized)
localStorage.setItem(FONT_SIZE_STORAGE_KEY, String(normalized))
})
watch(lineHeight, (next) => {
if (!isHydrated.value) return
const normalized = clampLineHeight(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 && !isWelcomePage" #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 #doc-before>
<CopyOrDownloadAsMarkdownButtons />
</template>
<template #nav-bar-content-after>
<GitHubStars />
<ClientOnly>
<PageSlidesButton />
</ClientOnly>
<ClientOnly>
<el-popover
placement="bottom-end"
trigger="click"
:width="260"
>
<template #reference>
<button
class="ev-fontsize-button"
type="button"
aria-label="阅读设置"
style="margin-left: 16px; padding: 0; width: 32px"
>
<el-icon :size="16">
<Setting />
</el-icon>
</button>
</template>
<div class="ev-fontsize-panel">
<div class="ev-setting-group">
<div class="ev-setting-header">
<div class="ev-setting-title">
字号
</div>
<div class="ev-setting-value">
{{ fontSize }}px
</div>
</div>
<div class="ev-fontsize-actions">
<button
class="ev-fontsize-action"
type="button"
@click="decreaseFontSize"
>
A-
</button>
<button
class="ev-fontsize-action"
type="button"
@click="resetFontSize"
>
默认
</button>
<button
class="ev-fontsize-action"
type="button"
@click="increaseFontSize"
>
A+
</button>
</div>
<el-slider
v-model="fontSize"
:min="MIN_FONT_SIZE"
:max="MAX_FONT_SIZE"
:step="1"
/>
</div>
<div class="ev-setting-group">
<div class="ev-setting-header">
<div class="ev-setting-title">
行距
</div>
<div class="ev-setting-value">
{{ lineHeight.toFixed(2) }}
</div>
</div>
<div class="ev-fontsize-actions">
<button
class="ev-fontsize-action"
type="button"
@click="resetLineHeight"
>
默认
</button>
<button
class="ev-fontsize-action"
type="button"
@click="lineHeight = clampLineHeight(lineHeight - 0.05)"
>
更紧
</button>
<button
class="ev-fontsize-action"
type="button"
@click="lineHeight = clampLineHeight(lineHeight + 0.05)"
>
更松
</button>
</div>
<el-slider
v-model="lineHeight"
:min="MIN_LINE_HEIGHT"
:max="MAX_LINE_HEIGHT"
:step="0.05"
/>
</div>
</div>
</el-popover>
</ClientOnly>
</template>
<template #home-hero-info-before>
<button
v-if="frontmatter.layout === 'home'"
class="vp-home-wordmark"
type="button"
aria-label="打开欢迎页"
@click="openWelcomeFromWordmark"
>
<svg
viewBox="0 0 460 220"
class="vp-home-wordmark-svg"
>
<defs>
<linearGradient
id="home-hero-ocean"
x1="0"
y1="0"
x2="460"
y2="0"
gradientUnits="userSpaceOnUse"
>
<stop offset="0%" stop-color="#06b6d4" />
<stop offset="50%" stop-color="#0ea5e9" />
<stop offset="100%" stop-color="#3b82f6" />
</linearGradient>
</defs>
<path
v-for="(path, index) in easyVibePaths"
:key="index"
:d="path"
class="vp-home-wordmark-path"
/>
</svg>
</button>
</template>
<template #home-hero-info-after>
<div
v-if="
frontmatter.layout === 'home' &&
(frontmatter.hero?.tagline || frontmatter.hero?.typingTagline)
"
class="vp-typed-tagline"
>
<ClientOnly>
<TextType
:text="frontmatter.hero.typingTagline || frontmatter.hero.tagline"
v-bind="homeTaglineTyping"
:loop="true"
/>
</ClientOnly>
</div>
</template>
</DefaultTheme.Layout>
<ClientOnly>
<div
v-if="!isHomePage && !isWelcomePage"
class="ev-sidebar-hover-area"
:class="{ collapsed: sidebarCollapsed, resizing: sidebarResizing }"
>
<div
v-if="!sidebarCollapsed"
class="ev-sidebar-resizer"
role="separator"
aria-orientation="vertical"
@pointerdown="startSidebarResize"
/>
<button
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>
</div>
</ClientOnly>
<ClientOnly>
<ReadingProgress v-if="!isHomePage && !isWelcomePage" />
</ClientOnly>
</template>
<style>
.VPNavBarTitle .VPImage.logo,
.VPNavBarTitle .logo {
width: 84px !important;
height: 40px !important;
max-width: 84px !important;
max-height: 40px !important;
object-fit: contain;
display: block;
}
/* 隐藏默认的 tagline,因为我们用打字机效果替代了它 */
.VPHomeHero .tagline {
display: none !important;
}
/* 调整打字机容器的样式,使其看起来像原来的 tagline */
.vp-typed-tagline {
padding-top: 0;
margin-top: 8px;
line-height: 28px;
font-size: 18px;
font-weight: 500;
white-space: pre-wrap;
color: var(--vp-c-text-2);
min-height: 28px;
display: flex;
/* 居中对齐 */
text-align: center;
justify-content: center;
}
/* 强制 HomeHero 内容居中 */
.VPHomeHero .container {
text-align: center;
}
.VPHomeHero .main {
margin: -18px auto 0;
}
.VPHomeHero .name,
.VPHomeHero .text {
text-align: center;
margin-left: auto;
margin-right: auto;
}
.VPHomeHero .name {
display: none !important;
}
.VPHomeHero .text {
color: var(--vp-c-text-1) !important;
}
.VPHomeHero .actions {
justify-content: center;
margin-top: 20px;
}
.vp-home-wordmark {
display: flex;
justify-content: center;
margin-top: -12px;
margin-bottom: 18px;
width: 100%;
padding: 0;
border: none;
background: transparent;
cursor: pointer;
}
.vp-home-wordmark-svg {
width: min(380px, 52vw);
height: auto;
filter: none;
}
.vp-home-wordmark-path {
fill: url(#home-hero-ocean);
stroke: none;
}
@media (min-width: 640px) {
.vp-typed-tagline {
line-height: 32px;
font-size: 20px;
}
}
@media (min-width: 960px) {
.vp-typed-tagline {
line-height: 36px;
font-size: 24px;
}
}
.ev-fontsize-button {
display: inline-flex;
align-items: center;
justify-content: center;
height: 32px;
min-width: 32px;
padding: 0 10px;
border: 1px solid var(--vp-c-divider);
border-radius: 999px;
background: var(--vp-c-bg-soft);
color: var(--vp-c-text-1);
font-size: 13px;
font-weight: 600;
line-height: 1;
cursor: pointer;
}
.ev-fontsize-button:hover {
border-color: var(--vp-c-brand);
color: var(--vp-c-brand);
}
.ev-fontsize-panel {
display: grid;
gap: 12px;
}
.ev-setting-group {
display: grid;
gap: 8px;
}
.ev-setting-header {
display: flex;
align-items: baseline;
justify-content: space-between;
gap: 12px;
}
.ev-setting-title {
font-size: 13px;
font-weight: 600;
color: var(--vp-c-text-1);
}
.ev-setting-value {
font-size: 12px;
color: var(--vp-c-text-2);
}
.ev-fontsize-actions {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 8px;
}
.ev-fontsize-action {
height: 32px;
border: 1px solid var(--vp-c-divider);
border-radius: 10px;
background: var(--vp-c-bg-soft);
color: var(--vp-c-text-1);
font-size: 13px;
cursor: pointer;
}
.ev-fontsize-action:hover {
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-hover-area {
display: none;
position: fixed;
top: 0;
--ev-sidebar-divider-offset: 16px;
left: calc(var(--vp-sidebar-width, 272px) - var(--ev-sidebar-divider-offset));
width: 24px;
height: 100vh;
z-index: 30;
}
.ev-sidebar-hover-area.collapsed {
left: 0;
}
.ev-sidebar-resizer {
position: absolute;
left: var(--ev-sidebar-divider-offset);
top: 0;
width: 2px;
height: 100%;
background: var(--vp-c-divider);
opacity: 0;
cursor: col-resize;
transition: opacity 0.2s ease, background-color 0.2s ease;
}
.ev-sidebar-hover-area:hover .ev-sidebar-resizer,
.ev-sidebar-hover-area.resizing .ev-sidebar-resizer {
opacity: 1;
background: var(--vp-c-brand-1);
}
/* 分界线上的收起按钮 */
.ev-sidebar-toggle-btn {
display: flex;
position: absolute;
top: 50%;
transform: translateY(-50%);
left: calc(var(--ev-sidebar-divider-offset) - 4px);
width: 18px;
height: 36px;
border: 1px solid var(--vp-c-divider);
border-radius: 0 4px 4px 0;
background: var(--vp-c-bg-soft);
color: var(--vp-c-text-3);
cursor: pointer;
align-items: center;
justify-content: center;
transition: opacity 0.5s ease;
opacity: 0;
animation: ev-sidebar-btn-flash 2.5s ease-out 0.5s;
}
@keyframes ev-sidebar-btn-flash {
0% { opacity: 0; }
20% { opacity: 0.7; }
60% { opacity: 0.7; }
100% { opacity: 0; }
}
.ev-sidebar-toggle-btn:hover {
color: var(--vp-c-text-1);
background: var(--vp-c-bg);
opacity: 1;
animation: none;
}
.ev-sidebar-hover-area:hover .ev-sidebar-toggle-btn {
opacity: 0.7;
animation: none;
}
.ev-sidebar-hover-area.resizing .ev-sidebar-toggle-btn {
opacity: 1;
}
/* 桌面端才显示按钮 */
@media (min-width: 960px) {
.ev-sidebar-nav-btn {
display: inline-flex;
}
.ev-sidebar-hover-area {
display: block;
}
}
/* @1440px 时分界线按钮跟随侧边栏实际宽度 */
@media (min-width: 1440px) {
.ev-sidebar-hover-area:not(.collapsed) {
left: calc((100% - (var(--vp-layout-max-width, 1440px) - 64px)) / 2 + var(--vp-sidebar-width, 272px) - var(--ev-sidebar-divider-offset));
}
}
/* ---- 收起状态下的 CSS 覆盖 ---- */
/* 隐藏侧边栏 — 仅桌面端,避免覆盖移动端的汉堡菜单 */
@media (min-width: 960px) {
.ev-sidebar-collapsed .VPSidebar {
display: none !important;
}
}
/* 修复侧边栏收起后导航栏标题 border-bottom 重叠问题 */
.ev-sidebar-collapsed .VPNavBar.has-sidebar .VPNavBarTitle .title {
border-bottom-color: transparent !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;
}
.ev-sidebar-resizing,
.ev-sidebar-resizing * {
cursor: col-resize !important;
user-select: none;
}
.ev-sidebar-resizing .VPSidebar,
.ev-sidebar-resizing .VPContent.has-sidebar,
.ev-sidebar-resizing .VPNavBar.has-sidebar .content,
.ev-sidebar-resizing .VPNavBar.has-sidebar .divider {
transition: none !important;
}
</style>