This commit is contained in:
oneao committed 2026-10-09 17:32:14 +08:00
1 parent e99a9fb274
commit 0be0b0767a
788 files changed
+112023 -14941

No files matched your search

@@ -0,0 +1,134 @@
<script setup lang="ts">
/**
* 侧边栏条目 —— DOM 与类名照抄 Docus / @nuxt/ui 的 ContentNavigation。
*
* 结构对照(取自 docus.dev 实测 DOM):
* 有子项: li.itemWithChildren > button.link.trigger + div.content > ul.listWithChildren
* 无子项: li.item > a.link
*
* 实测样式(docus.dev,1440px,light):
* ul.list margin: -6px -10px 0 (-mt-1.5 -mx-2.5)
* li.itemWithChildren display:flex; flex-direction:column
* button/a.link padding:6px 10px; gap:6px; font-size:14px; border-radius:0
* 高度 32px(py-1.5*2 + 行高 20)
* 圆角 6px 在 ::before 上(before:rounded-md)
* 分组标题(trigger) font-weight:600; color: text-highlighted
* 普通项 font-weight:400; color: text-muted
* 激活项 font-weight:500; color: text-primary;无背景
* ::after 竖线 w=1px h=28px left=-6px top/bottom=2px bg=primary
* 图标 width/height:16px(size-4);margin-inline:2px(mx-0.5)
* ul.listWithChildren margin-inline-start:20px; border-inline-start:1px;
* padding-inline-start:0
*/
import { computed, ref } from 'vue'
import { useData } from 'vitepress'
import { isActive } from 'vitepress/dist/client/shared.js'
import { sidebarIcons } from './sidebarIcons'
interface G3SidebarItem {
text?: string
link?: string
/** 图标名(对应 sidebarIcons 的键) */
icon?: string
rel?: string
target?: string
collapsed?: boolean
items?: G3SidebarItem[]
}
const props = defineProps<{
item: G3SidebarItem
level: number
}>()
const { page } = useData()
/** 该条目自身是否就是当前页 */
const isActiveLink = computed(() =>
props.item.link ? isActive(page.value.relativePath, props.item.link) : false,
)
function treeHasActive(item: G3SidebarItem): boolean {
if (item.link && isActive(page.value.relativePath, item.link)) return true
return (item.items ?? []).some(treeHasActive)
}
const hasActiveLink = computed(() => treeHasActive(props.item))
const hasChildren = computed(() => !!(props.item.items && props.item.items.length))
/** 折叠状态:local 覆盖 + 子项含当前页时强制展开 */
const overridden = ref<boolean | null>(null)
const collapsed = computed(() => {
if (overridden.value !== null) return overridden.value
if (hasActiveLink.value) return false
return !!props.item.collapsed
})
function toggle() {
overridden.value = !collapsed.value
}
const iconComponent = computed(() =>
props.item.icon ? sidebarIcons[props.item.icon] ?? null : null,
)
const linkClass = computed(() => [
'g3-nav-link',
// trigger(分组标题):font-semibold
hasChildren.value ? 'g3-nav-trigger' : '',
isActiveLink.value ? 'is-active' : '',
{ 'g3-nav-disabled': false },
])
</script>
<template>
<!-- 有子项:li.itemWithChildren > button.link + div.content > ul.listWithChildren -->
<li v-if="hasChildren" class="g3-item-with-children">
<button
type="button"
:class="linkClass"
:aria-expanded="!collapsed"
@click="toggle"
>
<component
:is="iconComponent"
v-if="iconComponent"
class="g3-nav-icon"
:size="16"
aria-hidden="true"
/>
<span class="g3-nav-title">{{ item.text }}</span>
</button>
<div v-show="!collapsed" class="g3-nav-content">
<ul class="g3-list-with-children">
<G3SidebarItem
v-for="(child, i) in item.items"
:key="`${i}-${child.text}`"
:item="child"
:level="level + 1"
/>
</ul>
</div>
</li>
<!-- 无子项:li.item > a.link -->
<li v-else class="g3-item">
<a
:class="linkClass"
:href="item.link"
:rel="item.rel"
:target="item.target"
:aria-current="isActiveLink ? 'page' : undefined"
>
<component
:is="iconComponent"
v-if="iconComponent"
class="g3-nav-icon"
:size="16"
aria-hidden="true"
/>
<span class="g3-nav-title">{{ item.text }}</span>
</a>
</li>
</template>