官网开发中 0731
This commit is contained in:
@@ -1,47 +1,256 @@
|
||||
<script setup lang="ts">
|
||||
const { t, locale, locales } = useI18n();
|
||||
const switchLocalePath = useSwitchLocalePath();
|
||||
import type { NavItem } from '~/types/site';
|
||||
|
||||
const navItems = computed(() => [
|
||||
{ label: t('nav.home'), path: '/' },
|
||||
{ label: t('nav.products'), path: '/products' },
|
||||
{ label: t('nav.news'), path: '/news' },
|
||||
{ label: t('nav.about'), path: '/about' }
|
||||
]);
|
||||
const { t, locale, locales } = useI18n();
|
||||
const localePath = useLocalePath();
|
||||
const switchLocalePath = useSwitchLocalePath();
|
||||
const route = useRoute();
|
||||
|
||||
const siteStore = useSiteStore();
|
||||
const appStore = useAppStore();
|
||||
|
||||
const { navItems, navAppearance, productSeries, site } = storeToRefs(siteStore);
|
||||
|
||||
// 滚动状态:滚动后强制毛玻璃背景 + 深色文字
|
||||
const { y: scrollY } = useWindowScroll();
|
||||
const isScrolled = computed(() => scrollY.value > 24);
|
||||
|
||||
// 是否首页
|
||||
const isHome = computed(() => route.path === localePath('/') || route.path === '/');
|
||||
|
||||
// 导航栏是否使用透明风格(未滚动时)
|
||||
const useTransparent = computed(() => {
|
||||
if (isScrolled.value) return false;
|
||||
const style = isHome.value ? navAppearance.value?.homepage_style : navAppearance.value?.non_homepage_style;
|
||||
return style === 'transparent';
|
||||
});
|
||||
|
||||
// 文字/Logo 颜色:透明风格下用浅色(适配深色首屏),否则用深色
|
||||
const lightText = computed(() => useTransparent.value);
|
||||
|
||||
const logoUrl = computed(() => {
|
||||
if (!site.value) return '';
|
||||
return lightText.value ? site.value.logo_light_url : site.value.logo_dark_url;
|
||||
});
|
||||
|
||||
// 判断是否为「产品」入口(悬浮展开 Mega Menu)
|
||||
function isProductsEntry(item: NavItem): boolean {
|
||||
return item.link.includes('/products');
|
||||
}
|
||||
|
||||
// 解析导航链接:外部 URL 原样返回,内部路由加语言前缀
|
||||
function resolveLink(link: string): string {
|
||||
if (/^https?:\/\//.test(link)) return link;
|
||||
return localePath(link);
|
||||
}
|
||||
|
||||
function isExternal(link: string): boolean {
|
||||
return /^https?:\/\//.test(link);
|
||||
}
|
||||
|
||||
// Mega Menu 悬浮状态
|
||||
const megaOpen = ref(false);
|
||||
let megaTimer: ReturnType<typeof setTimeout> | null = null;
|
||||
|
||||
function openMega() {
|
||||
if (megaTimer) clearTimeout(megaTimer);
|
||||
megaOpen.value = true;
|
||||
}
|
||||
|
||||
function closeMega() {
|
||||
megaTimer = setTimeout(() => {
|
||||
megaOpen.value = false;
|
||||
}, 120);
|
||||
}
|
||||
|
||||
// 移动端:关闭抽屉
|
||||
function closeMobile() {
|
||||
appStore.toggleMobileMenu(false);
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<header class="sticky top-0 z-50 bg-white/80 backdrop-blur-md border-b border-gray-100 dark:bg-dark-900/80 dark:border-dark-700">
|
||||
<header
|
||||
class="fixed top-0 left-0 right-0 z-50 transition-all duration-300"
|
||||
:class="useTransparent ? 'bg-transparent' : 'bg-white/80 backdrop-blur-md border-b border-gray-100 dark:bg-dark-900/80 dark:border-dark-700'"
|
||||
>
|
||||
<div class="section-container h-64px flex items-center justify-between">
|
||||
<!-- Logo -->
|
||||
<NuxtLink :to="switchLocalePath(locale)" class="flex items-center gap-8px text-xl font-bold text-primary">
|
||||
{{ t('app.name') }}
|
||||
<NuxtLink :to="switchLocalePath(locale)" class="flex items-center gap-8px shrink-0" @click="closeMobile">
|
||||
<img v-if="logoUrl" :src="logoUrl" :alt="site?.site_title || 'Luxsin'" class="h-32px w-auto" />
|
||||
<span
|
||||
v-else
|
||||
class="text-xl font-bold transition-colors"
|
||||
:class="lightText ? 'text-white' : 'text-primary'"
|
||||
>
|
||||
{{ site?.site_title || t('app.name') }}
|
||||
</span>
|
||||
</NuxtLink>
|
||||
|
||||
<!-- 导航 -->
|
||||
<nav class="hidden md:flex items-center gap-24px">
|
||||
<NuxtLink
|
||||
v-for="item in navItems"
|
||||
:key="item.path"
|
||||
:to="switchLocalePath(locale) === '/' ? item.path : switchLocalePath(locale) + item.path"
|
||||
class="text-sm text-gray-600 hover:text-primary transition-colors dark:text-gray-300"
|
||||
>
|
||||
{{ item.label }}
|
||||
</NuxtLink>
|
||||
<!-- 桌面端导航 -->
|
||||
<nav class="hidden md:flex items-center gap-32px">
|
||||
<template v-for="item in navItems" :key="item.id">
|
||||
<!-- 产品入口:悬浮 Mega Menu -->
|
||||
<div
|
||||
v-if="isProductsEntry(item)"
|
||||
class="relative h-64px flex items-center"
|
||||
@mouseenter="openMega"
|
||||
@mouseleave="closeMega"
|
||||
>
|
||||
<NuxtLink
|
||||
:to="resolveLink(item.link)"
|
||||
class="text-sm font-medium transition-colors flex items-center gap-4px"
|
||||
:class="lightText ? 'text-white/90 hover:text-white' : 'text-gray-700 hover:text-primary dark:text-gray-200'"
|
||||
>
|
||||
{{ siteStore.navLabel(item) }}
|
||||
<span class="text-10px opacity-60">▾</span>
|
||||
</NuxtLink>
|
||||
|
||||
<!-- Mega Menu 面板 -->
|
||||
<transition name="mega">
|
||||
<div
|
||||
v-if="megaOpen"
|
||||
class="absolute top-64px left-50% -translate-x-50% min-w-640px bg-white/95 backdrop-blur-xl rounded-12px shadow-xl border border-gray-100 p-24px dark:bg-dark-900/95 dark:border-dark-700"
|
||||
>
|
||||
<div class="flex gap-40px">
|
||||
<div v-for="series in productSeries" :key="series.id" class="min-w-200px">
|
||||
<p class="text-12px font-semibold text-gray-400 mb-12px uppercase tracking-wide">
|
||||
{{ locale === 'en' ? series.name_en : series.name_zh }}
|
||||
</p>
|
||||
<div class="flex flex-col gap-8px">
|
||||
<NuxtLink
|
||||
v-for="product in series.products"
|
||||
:key="product.id"
|
||||
:to="localePath(`/products/${product.slug}`)"
|
||||
class="flex items-center gap-12px p-8px rounded-8px hover:bg-gray-50 transition-colors dark:hover:bg-dark-800"
|
||||
@click="megaOpen = false"
|
||||
>
|
||||
<img
|
||||
v-if="product.cover_url"
|
||||
:src="product.cover_url"
|
||||
:alt="product.name_zh"
|
||||
class="w-48px h-36px object-cover rounded-6px bg-gray-100"
|
||||
/>
|
||||
<span class="text-sm text-gray-800 dark:text-gray-100">
|
||||
{{ locale === 'en' ? product.name_en : product.name_zh }}
|
||||
</span>
|
||||
</NuxtLink>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
</div>
|
||||
|
||||
<!-- 普通入口:外部链接 -->
|
||||
<a
|
||||
v-else-if="isExternal(item.link)"
|
||||
:href="item.link"
|
||||
:target="item.open_new_tab ? '_blank' : undefined"
|
||||
class="text-sm font-medium transition-colors"
|
||||
:class="lightText ? 'text-white/90 hover:text-white' : 'text-gray-700 hover:text-primary dark:text-gray-200'"
|
||||
>
|
||||
{{ siteStore.navLabel(item) }}
|
||||
</a>
|
||||
|
||||
<!-- 普通入口:站内路由 -->
|
||||
<NuxtLink
|
||||
v-else
|
||||
:to="resolveLink(item.link)"
|
||||
:target="item.open_new_tab ? '_blank' : undefined"
|
||||
class="text-sm font-medium transition-colors"
|
||||
:class="lightText ? 'text-white/90 hover:text-white' : 'text-gray-700 hover:text-primary dark:text-gray-200'"
|
||||
>
|
||||
{{ siteStore.navLabel(item) }}
|
||||
</NuxtLink>
|
||||
</template>
|
||||
</nav>
|
||||
|
||||
<!-- 语言切换 -->
|
||||
<!-- 右侧:语言切换 + 移动端汉堡 -->
|
||||
<div class="flex items-center gap-12px">
|
||||
<NuxtLink
|
||||
v-for="loc in locales"
|
||||
:key="loc.code"
|
||||
:to="switchLocalePath(loc.code)"
|
||||
class="px-8px py-4px rounded text-sm transition-colors"
|
||||
:class="locale === loc.code ? 'bg-primary text-white' : 'text-gray-500 hover:text-primary'"
|
||||
<div class="hidden md:flex items-center gap-4px">
|
||||
<NuxtLink
|
||||
v-for="loc in locales"
|
||||
:key="loc.code"
|
||||
:to="switchLocalePath(loc.code)"
|
||||
class="px-8px py-4px rounded text-sm transition-colors"
|
||||
:class="
|
||||
locale === loc.code
|
||||
? lightText ? 'bg-white/20 text-white' : 'bg-primary text-white'
|
||||
: lightText ? 'text-white/70 hover:text-white' : 'text-gray-500 hover:text-primary'
|
||||
"
|
||||
>
|
||||
{{ loc.code === 'zh' ? '中' : 'EN' }}
|
||||
</NuxtLink>
|
||||
</div>
|
||||
|
||||
<!-- 汉堡按钮 -->
|
||||
<button
|
||||
class="md:hidden w-40px h-40px flex-center rounded-8px transition-colors"
|
||||
:class="lightText ? 'text-white hover:bg-white/10' : 'text-gray-700 hover:bg-gray-100 dark:text-gray-200'"
|
||||
aria-label="menu"
|
||||
@click="appStore.toggleMobileMenu()"
|
||||
>
|
||||
{{ loc.code === 'zh' ? '中' : 'EN' }}
|
||||
</NuxtLink>
|
||||
<span class="text-xl">{{ appStore.mobileMenuOpen ? '✕' : '☰' }}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 移动端抽屉 -->
|
||||
<transition name="drawer">
|
||||
<div
|
||||
v-if="appStore.mobileMenuOpen"
|
||||
class="md:hidden bg-white dark:bg-dark-900 border-t border-gray-100 dark:border-dark-700 px-16px py-16px flex flex-col gap-4px"
|
||||
>
|
||||
<NuxtLink
|
||||
v-for="item in navItems"
|
||||
:key="item.id"
|
||||
:to="resolveLink(item.link)"
|
||||
class="px-12px py-12px rounded-8px text-base text-gray-800 hover:bg-gray-50 dark:text-gray-100 dark:hover:bg-dark-800"
|
||||
@click="closeMobile"
|
||||
>
|
||||
{{ siteStore.navLabel(item) }}
|
||||
</NuxtLink>
|
||||
|
||||
<div class="flex items-center gap-8px mt-12px px-12px">
|
||||
<NuxtLink
|
||||
v-for="loc in locales"
|
||||
:key="loc.code"
|
||||
:to="switchLocalePath(loc.code)"
|
||||
class="px-12px py-6px rounded text-sm transition-colors"
|
||||
:class="locale === loc.code ? 'bg-primary text-white' : 'text-gray-500 hover:text-primary'"
|
||||
@click="closeMobile"
|
||||
>
|
||||
{{ loc.code === 'zh' ? '中文' : 'English' }}
|
||||
</NuxtLink>
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
</header>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.mega-enter-active,
|
||||
.mega-leave-active {
|
||||
transition: opacity 0.18s ease, transform 0.18s ease;
|
||||
}
|
||||
.mega-enter-from,
|
||||
.mega-leave-to {
|
||||
opacity: 0;
|
||||
transform: translate(-50%, -8px);
|
||||
}
|
||||
.mega-enter-to,
|
||||
.mega-leave-from {
|
||||
opacity: 1;
|
||||
transform: translate(-50%, 0);
|
||||
}
|
||||
|
||||
.drawer-enter-active,
|
||||
.drawer-leave-active {
|
||||
transition: opacity 0.2s ease;
|
||||
}
|
||||
.drawer-enter-from,
|
||||
.drawer-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user