116 lines
3.5 KiB
Vue
116 lines
3.5 KiB
Vue
<template>
|
|
<!--
|
|
CRITICAL FIX: Removed 'overflow-hidden' from this root div.
|
|
'overflow-hidden' on an ancestor element breaks 'position: sticky'
|
|
in child components (like BlogDetail TOC) because it changes the
|
|
scrolling context.
|
|
-->
|
|
<div class="min-h-screen bg-art-bg text-art-text relative">
|
|
<!-- Star Background (Only for frontend) -->
|
|
<StarBackground v-if="!isAdminOrLogin" class="z-0" />
|
|
|
|
<!-- Global Noise (Only for frontend) -->
|
|
<div v-if="!isAdminOrLogin" class="fixed inset-0 pointer-events-none z-[60] bg-noise opacity-30 mix-blend-overlay"></div>
|
|
|
|
<!-- Toast Container -->
|
|
<div id="toast-container" class="toast-container relative z-[70]"></div>
|
|
|
|
<!-- Header (Hide on Admin & Login) -->
|
|
<Header v-if="!isAdminOrLogin" class="z-50" />
|
|
|
|
<!-- Mobile Menu (Hide on Admin & Login) -->
|
|
<MobileMenu v-if="!isAdminOrLogin" class="z-50" />
|
|
|
|
<!-- Main Content -->
|
|
<!-- Conditional classes: Apply max-w-7xl only for frontend pages -->
|
|
<main :class="[
|
|
isAdminOrLoginOrBlog ? 'w-full h-full relative z-10' : 'pt-32 pb-20 px-6 max-w-7xl mx-auto relative z-10'
|
|
]">
|
|
<router-view />
|
|
</main>
|
|
|
|
<!-- Footer (Hide on Admin & Login) -->
|
|
<Footer v-if="!isAdminOrLogin" class="relative z-10" />
|
|
|
|
<!-- Snippet Modal (Hide on Admin & Login) -->
|
|
<!-- SnippetModal is managed by Snippets.vue page, not here -->
|
|
|
|
<!-- Inquiry Modal (Hide on Admin & Login) -->
|
|
<InquiryModal v-if="!isAdminOrLogin" class="relative z-[80]" />
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { computed, onMounted } from 'vue'
|
|
import { useRoute } from 'vue-router'
|
|
import Header from './components/Header.vue'
|
|
import MobileMenu from './components/MobileMenu.vue'
|
|
import Footer from './components/Footer.vue'
|
|
import InquiryModal from './components/InquiryModal.vue'
|
|
import StarBackground from './components/StarBackground.vue'
|
|
import { getPublicSettings } from './services/api'
|
|
|
|
const route = useRoute()
|
|
|
|
// Check if current route is Admin or Login page
|
|
const isAdminOrLogin = computed(() => {
|
|
const path = route.path
|
|
return path.startsWith('/admin') || path === '/login'
|
|
})
|
|
|
|
// Check if current route is Admin or Login page
|
|
const isAdminOrLoginOrBlog = computed(() => {
|
|
const path = route.path
|
|
return path.startsWith('/admin') || path === '/login' || path.startsWith('/blog/')
|
|
})
|
|
|
|
// 应用网站配置到页面标题和meta标签
|
|
const applySiteSettings = async () => {
|
|
try {
|
|
const settings = await getPublicSettings()
|
|
|
|
// 设置页面标题
|
|
if (settings.site_title) {
|
|
document.title = settings.site_title
|
|
}
|
|
|
|
// 更新meta标签
|
|
const updateMetaTag = (name: string, content: string) => {
|
|
if (!content) return
|
|
|
|
let meta = document.querySelector(`meta[name="${name}"]`)
|
|
if (!meta) {
|
|
meta = document.createElement('meta')
|
|
meta.setAttribute('name', name)
|
|
document.head.appendChild(meta)
|
|
}
|
|
meta.setAttribute('content', content)
|
|
}
|
|
|
|
// 更新description
|
|
if (settings.site_description) {
|
|
updateMetaTag('description', settings.site_description)
|
|
}
|
|
|
|
// 更新keywords
|
|
if (settings.site_keywords) {
|
|
updateMetaTag('keywords', settings.site_keywords)
|
|
}
|
|
|
|
// 更新author
|
|
if (settings.site_author) {
|
|
updateMetaTag('author', settings.site_author)
|
|
}
|
|
} catch (error) {
|
|
console.error('Failed to load site settings:', error)
|
|
// 使用默认标题,不抛出错误
|
|
}
|
|
}
|
|
|
|
onMounted(() => {
|
|
// 只在非管理页面应用设置
|
|
if (!isAdminOrLogin.value) {
|
|
applySiteSettings()
|
|
}
|
|
})
|
|
</script> |