45 lines
1.7 KiB
Vue
45 lines
1.7 KiB
Vue
<script setup lang="ts">
|
|
const route = useRoute()
|
|
const config = useRuntimeConfig()
|
|
const scrollProgress = ref(0)
|
|
let scrollFrame = 0
|
|
const canonical = computed(() => `${String(config.public.siteUrl).replace(/\/$/, '')}${route.path === '/' ? '/' : route.path}`)
|
|
const isDraftPreview = computed(() => route.query.preview === 'draft')
|
|
|
|
useHead(() => route.path.startsWith('/admin') ? {} : {
|
|
link: [{ rel: 'canonical', href: canonical.value }],
|
|
})
|
|
useSeoMeta({ ogUrl: () => canonical.value, twitterCard: 'summary_large_image' })
|
|
|
|
const updateScrollProgress = () => {
|
|
scrollFrame = 0
|
|
const max = document.documentElement.scrollHeight - window.innerHeight
|
|
scrollProgress.value = max > 0 ? Math.min(1, window.scrollY / max) : 0
|
|
}
|
|
const requestProgressUpdate = () => {
|
|
if (!scrollFrame) scrollFrame = requestAnimationFrame(updateScrollProgress)
|
|
}
|
|
|
|
onMounted(() => {
|
|
updateScrollProgress()
|
|
window.addEventListener('scroll', requestProgressUpdate, { passive: true })
|
|
window.addEventListener('resize', requestProgressUpdate, { passive: true })
|
|
})
|
|
|
|
onBeforeUnmount(() => {
|
|
if (import.meta.client) {
|
|
window.removeEventListener('scroll', requestProgressUpdate)
|
|
window.removeEventListener('resize', requestProgressUpdate)
|
|
}
|
|
if (scrollFrame) cancelAnimationFrame(scrollFrame)
|
|
})
|
|
</script>
|
|
|
|
<template>
|
|
<NuxtRouteAnnouncer />
|
|
<a class="skip-link" href="#main-content">跳到正文</a>
|
|
<div v-if="!route.path.startsWith('/admin')" class="page-progress" aria-hidden="true"><span :style="{ transform: `scaleX(${scrollProgress})` }" /></div>
|
|
<div v-if="isDraftPreview" class="draft-preview-ribbon"><strong>草稿预览</strong><span>此页面不会被搜索引擎收录</span><NuxtLink to="/admin">返回后台</NuxtLink></div>
|
|
<NuxtPage />
|
|
</template>
|