import DefaultTheme from 'vitepress/theme' import type { Theme } from 'vitepress' import { h, watch, nextTick, computed } from 'vue' import { useData, useRoute } from 'vitepress' import ProjectList from './components/ProjectList.vue' import HomePage from './components/HomePage.vue' import CapabilityTabs from './components/CapabilityTabs.vue' import FeatureGrid from './components/FeatureGrid.vue' import ScreenshotPreview from './components/ScreenshotPreview.vue' import PortalFooter from './components/PortalFooter.vue' import { setupNavAnchorHighlight, applyNavAnchorHighlight } from './nav-anchor' import './styles/portal.css' /** * 首页用 layout: page 承载自定义整页排版, * 这里给它套一层 .portal-page,把默认窄栏与 padding 去掉。 * 同时接管导航栏锚点项的高亮(VitePress 默认只认 path,不认 hash)。 */ const PortalLayout = () => { const { frontmatter } = useData() const route = useRoute() const isPortal = frontmatter.value.layout === 'page' /** * 是否隐藏整站页脚。 * * 判定原则:**文档型页面不挂页脚**。 * 这类页面有左侧目录树、右侧本页目录、底部上一页/下一页翻页, * 读者正处在阅读流里,页脚那 450px 的品牌/联系/版权是纯噪音, * 还会和翻页按钮挤在一起。 * * 覆盖三种情况: * 1) 路径命中项目手册 `/projects//manual/...` * 2) frontmatter 写了 `sidebar` / `outline` / `aside`(文档页标记) * 3) frontmatter 显式 `footer: false` * * frontmatter 的 `footer` 可双向覆盖: * footer: false → 强制隐藏(如清单页 /projects/:有 outline 但本质是浏览页) * footer: true → 强制显示 * * 自定义整页(layout: page,如首页/关于/演示/官方文档)不在此列, * 它们有完整的品牌收尾节奏,页脚保留。 */ const isManualPath = computed(() => /^\/projects\/[^/]+\/manual(\/|$)/.test(route.path)) const isDocPage = computed(() => { const fm = frontmatter.value as Record if (fm.layout === 'page' || fm.layout === 'home') return false if (fm.footer === false) return true if (fm.footer === true) return false const hasDocMarks = fm.sidebar !== undefined || fm.outline !== undefined || fm.aside !== undefined return isManualPath.value || hasDocMarks }) /** * Layout 是函数式组件,onMounted 在这里不生效(Vue 会告警), * 因此直接在渲染期间调度:等 DOM 落地后跑一次,再靠 MutationObserver 兜住后续变化。 */ if (typeof window !== 'undefined') { nextTick(() => setupNavAnchorHighlight()) } // SPA 路由切换不触发 hashchange,这里补一次 watch( () => [route.path, route.hash], () => nextTick(() => applyNavAnchorHighlight()) ) return h('div', { class: isPortal ? 'portal-page' : '' }, h(DefaultTheme.Layout, null, { // 自定义整站页脚(品牌 + 多栏链接 + 联系方式 + 版权条) // 文档型页面一律不挂(见 isDocPage 判定) 'layout-bottom': isDocPage.value ? undefined : () => h(PortalFooter), })) } export default { extends: DefaultTheme, Layout: PortalLayout, enhanceApp({ app }) { app.component('ProjectList', ProjectList) app.component('HomePage', HomePage) app.component('CapabilityTabs', CapabilityTabs) app.component('FeatureGrid', FeatureGrid) app.component('ScreenshotPreview', ScreenshotPreview) }, } satisfies Theme