305 lines
12 KiB
Vue
305 lines
12 KiB
Vue
<template>
|
||
<nav :class="['fixed w-full z-50 transition-all duration-300',
|
||
'bg-white dark:bg-gray-900 shadow-sm border-b border-gray-100 dark:border-gray-800']">
|
||
<div class="container mx-auto px-4">
|
||
<div class="flex items-center justify-between h-16">
|
||
<!-- 网站Logo -->
|
||
<NuxtLink to="/" class="flex items-center pl-2">
|
||
<img src="/logo.svg" alt="PinnovateCloud" class="h-8 mr-2" />
|
||
</NuxtLink>
|
||
|
||
<!-- 桌面端导航 -->
|
||
<div class="hidden md:flex items-center">
|
||
<div class="flex items-center space-x-6">
|
||
<NuxtLink
|
||
v-for="(item, index) in navItems"
|
||
:key="index"
|
||
:to="item.path"
|
||
class="transition-colors duration-300 whitespace-nowrap font-medium text-blue-500 hover:text-blue-700 dark:text-blue-400 dark:hover:text-blue-300 text-center px-2"
|
||
:class="{ 'text-blue-700 dark:text-blue-300 font-semibold': route.path === item.path }"
|
||
>
|
||
{{ item.name }}
|
||
</NuxtLink>
|
||
</div>
|
||
|
||
<!-- 社交媒体图标 -->
|
||
<div class="flex items-center space-x-4 ml-8 nav-social-icons">
|
||
<!-- Facebook图标 -->
|
||
<a :href="contact.facebook" target="_blank" class="text-gray-500 dark:text-gray-400 hover:text-blue-500 dark:hover:text-blue-300 transition-colors duration-300 p-1">
|
||
<i class="fab fa-facebook text-xl"></i>
|
||
</a>
|
||
|
||
<!-- Instagram图标 -->
|
||
<a :href="contact.instagram" target="_blank" class="text-gray-500 dark:text-gray-400 hover:text-blue-500 dark:hover:text-blue-300 transition-colors duration-300 p-1">
|
||
<i class="fab fa-instagram text-xl"></i>
|
||
</a>
|
||
|
||
<!-- Telegram图标 -->
|
||
<a :href="contact.telegram" target="_blank" class="text-gray-500 dark:text-gray-400 hover:text-blue-500 dark:hover:text-blue-300 transition-colors duration-300 p-1">
|
||
<i class="fab fa-telegram text-xl"></i>
|
||
</a>
|
||
|
||
<!-- Twitter图标 -->
|
||
<a :href="contact.twitter" target="_blank" class="text-gray-500 dark:text-gray-400 hover:text-blue-500 dark:hover:text-blue-300 transition-colors duration-300 p-1">
|
||
<i class="fab fa-twitter text-xl"></i>
|
||
</a>
|
||
|
||
<!-- WhatsApp图标 -->
|
||
<a :href="contact.whatsapp" target="_blank" class="text-gray-500 dark:text-gray-400 hover:text-blue-500 dark:hover:text-blue-300 transition-colors duration-300 p-1">
|
||
<i class="fab fa-whatsapp text-xl"></i>
|
||
</a>
|
||
|
||
<!-- 语言切换图标 -->
|
||
<div class="language-switcher relative">
|
||
<button
|
||
@click="toggleLanguageDropdown"
|
||
class="text-gray-500 dark:text-gray-400 hover:text-blue-500 dark:hover:text-blue-300 transition-colors duration-300 p-1 flex items-center justify-center"
|
||
>
|
||
<i class="fas fa-globe text-xl"></i>
|
||
</button>
|
||
|
||
<div
|
||
v-if="languageDropdownOpen"
|
||
class="absolute mt-2 right-0 bg-white dark:bg-gray-800 rounded-md shadow-xl py-1 z-10 border border-gray-200 dark:border-gray-700 w-32 overflow-hidden"
|
||
>
|
||
<button
|
||
v-for="loc in availableLocales"
|
||
:key="loc"
|
||
@click="switchLanguage(loc)"
|
||
class="w-full text-left px-4 py-2 text-sm text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 hover:text-blue-500 dark:hover:text-blue-300 transition-colors duration-150 flex items-center"
|
||
:class="{ 'bg-gray-100 dark:bg-gray-700 text-blue-500 dark:text-blue-300 font-medium': loc === locale }"
|
||
>
|
||
<span class="w-5 h-5 inline-flex items-center justify-center mr-2">
|
||
<span v-if="loc === locale" class="w-2 h-2 rounded-full bg-blue-500 dark:bg-blue-400"></span>
|
||
</span>
|
||
{{ getLanguageName(loc) }}
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 移动端菜单按钮 -->
|
||
<div class="md:hidden flex items-center">
|
||
<!-- 语言切换图标 -->
|
||
<div class="language-switcher relative mr-4">
|
||
<button
|
||
@click="toggleLanguageDropdown"
|
||
class="text-gray-500 dark:text-gray-400 hover:text-blue-500 dark:hover:text-blue-300 transition-colors duration-300 p-1 flex items-center justify-center"
|
||
>
|
||
<i class="fas fa-globe text-xl"></i>
|
||
</button>
|
||
|
||
<div
|
||
v-if="languageDropdownOpen"
|
||
class="absolute mt-2 right-0 bg-white dark:bg-gray-800 rounded-md shadow-xl py-1 z-10 border border-gray-200 dark:border-gray-700 w-32 overflow-hidden"
|
||
>
|
||
<button
|
||
v-for="loc in availableLocales"
|
||
:key="loc"
|
||
@click="switchLanguage(loc)"
|
||
class="w-full text-left px-4 py-2 text-sm text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 hover:text-blue-500 dark:hover:text-blue-300 transition-colors duration-150 flex items-center"
|
||
:class="{ 'bg-gray-100 dark:bg-gray-700 text-blue-500 dark:text-blue-300 font-medium': loc === locale }"
|
||
>
|
||
<span class="w-5 h-5 inline-flex items-center justify-center mr-2">
|
||
<span v-if="loc === locale" class="w-2 h-2 rounded-full bg-blue-500 dark:bg-blue-400"></span>
|
||
</span>
|
||
{{ getLanguageName(loc) }}
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<button
|
||
class="p-2 rounded-lg transition-colors duration-300 text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700"
|
||
aria-label="打开菜单"
|
||
@click="toggleMobileMenu"
|
||
>
|
||
<i class="fas fa-bars text-xl"></i>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 移动端菜单 -->
|
||
<div :class="['md:hidden absolute top-16 left-0 right-0 shadow-lg bg-white dark:bg-gray-900', { 'hidden': !mobileMenuOpen }]">
|
||
<div class="container py-4">
|
||
<!-- 移动端社交媒体图标 -->
|
||
<div class="social-icons-mobile flex flex-wrap justify-center mb-4 gap-6 py-3 border-b border-gray-100 dark:border-gray-800 bg-gray-50 dark:bg-gray-800 rounded-lg">
|
||
<!-- Facebook图标 -->
|
||
<a :href="contact.facebook" target="_blank" class="text-gray-500 dark:text-gray-400 hover:text-blue-500 dark:hover:text-blue-300 transition-colors duration-300 p-1">
|
||
<i class="fab fa-facebook text-2xl"></i>
|
||
</a>
|
||
|
||
<!-- Instagram图标 -->
|
||
<a :href="contact.instagram" target="_blank" class="text-gray-500 dark:text-gray-400 hover:text-blue-500 dark:hover:text-blue-300 transition-colors duration-300 p-1">
|
||
<i class="fab fa-instagram text-2xl"></i>
|
||
</a>
|
||
|
||
<!-- Telegram图标 -->
|
||
<a :href="contact.telegram" target="_blank" class="text-gray-500 dark:text-gray-400 hover:text-blue-500 dark:hover:text-blue-300 transition-colors duration-300 p-1">
|
||
<i class="fab fa-telegram text-2xl"></i>
|
||
</a>
|
||
|
||
<!-- Twitter图标 -->
|
||
<a :href="contact.twitter" target="_blank" class="text-gray-500 dark:text-gray-400 hover:text-blue-500 dark:hover:text-blue-300 transition-colors duration-300 p-1">
|
||
<i class="fab fa-twitter text-2xl"></i>
|
||
</a>
|
||
|
||
<!-- WhatsApp图标 -->
|
||
<a :href="contact.whatsapp" target="_blank" class="text-gray-500 dark:text-gray-400 hover:text-blue-500 dark:hover:text-blue-300 transition-colors duration-300 p-1">
|
||
<i class="fab fa-whatsapp text-2xl"></i>
|
||
</a>
|
||
</div>
|
||
|
||
<div class="flex flex-col space-y-4">
|
||
<NuxtLink
|
||
v-for="(item, index) in navItems"
|
||
:key="index"
|
||
:to="item.path"
|
||
class="text-blue-500 dark:text-blue-400 hover:text-blue-700 dark:hover:text-blue-300 transition-colors duration-300 py-2 whitespace-nowrap font-medium"
|
||
:class="{ 'text-blue-700 dark:text-blue-300 font-semibold': route.path === item.path }"
|
||
@click="mobileMenuOpen = false"
|
||
>
|
||
{{ item.name }}
|
||
</NuxtLink>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</nav>
|
||
|
||
<!-- 添加导航栏占位,防止内容被导航栏遮挡 -->
|
||
<div class="h-16"></div>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import { ref, onMounted, onUnmounted, computed } from 'vue';
|
||
import { useRoute, useRouter } from 'vue-router';
|
||
import { useI18n } from 'vue-i18n';
|
||
import { useLanguageTransition } from '~/composables/useLanguageTransition';
|
||
import { contact } from '~/public/js/contact.js';
|
||
|
||
const route = useRoute();
|
||
const router = useRouter();
|
||
const { t, locale, availableLocales } = useI18n();
|
||
const { smoothSwitchLanguage, isChangingLanguage } = useLanguageTransition();
|
||
const mobileMenuOpen = ref(false);
|
||
const scrolled = ref(false);
|
||
const languageDropdownOpen = ref(false);
|
||
|
||
// 计算带有当前语言前缀的路由路径
|
||
const localizedPath = (path: string) => {
|
||
// 如果路径已经是以语言代码开头,则不添加
|
||
if (path.startsWith(`/${locale.value}`)) {
|
||
return path;
|
||
}
|
||
// 如果是根路径,则只返回语言代码
|
||
if (path === '/') {
|
||
return `/${locale.value}`;
|
||
}
|
||
// 其他情况,添加语言前缀
|
||
return `/${locale.value}${path}`;
|
||
};
|
||
|
||
const navItems = computed(() => [
|
||
{ name: t('nav.home'), path: localizedPath('/') },
|
||
{ name: t('nav.products'), path: localizedPath('/products') },
|
||
{ name: t('nav.solutions'), path: localizedPath('/solutions') },
|
||
{ name: t('nav.cases'), path: localizedPath('/cases') },
|
||
{ name: t('nav.news'), path: localizedPath('/awsnews') },
|
||
{ name: t('nav.about'), path: localizedPath('/about') },
|
||
{ name: t('nav.contact'), path: localizedPath('/contact') }
|
||
]);
|
||
|
||
const toggleMobileMenu = () => {
|
||
mobileMenuOpen.value = !mobileMenuOpen.value;
|
||
};
|
||
|
||
const toggleLanguageDropdown = () => {
|
||
if (isChangingLanguage.value) return;
|
||
languageDropdownOpen.value = !languageDropdownOpen.value;
|
||
};
|
||
|
||
// 切换语言函数
|
||
function getLanguageName(localeCode: string) {
|
||
const languageNames = {
|
||
'zh': '简体中文',
|
||
'en': 'English',
|
||
'zh-TW': '繁體中文'
|
||
};
|
||
return languageNames[localeCode as keyof typeof languageNames] || localeCode;
|
||
}
|
||
|
||
function switchLanguage(newLocale: string) {
|
||
if (isChangingLanguage.value || locale.value === newLocale) return;
|
||
|
||
// 获取当前路径,去掉语言前缀
|
||
const currentPath = route.path;
|
||
const parts = currentPath.split('/');
|
||
|
||
// 如果当前URL已经包含语言代码,则去掉它
|
||
let pathWithoutLocale = currentPath;
|
||
if (availableLocales.includes(parts[1])) {
|
||
pathWithoutLocale = currentPath.substring(parts[1].length + 1) || '/';
|
||
}
|
||
|
||
// 构建带有新语言代码的路径
|
||
const newPath = newLocale === 'zh'
|
||
? `/${newLocale}${pathWithoutLocale === '/' ? '' : pathWithoutLocale}`
|
||
: `/${newLocale}${pathWithoutLocale === '/' ? '' : pathWithoutLocale}`;
|
||
|
||
// 使用平滑过渡方法切换语言并导航到新路径
|
||
smoothSwitchLanguage(newLocale);
|
||
|
||
// 导航到新路径
|
||
router.push(newPath);
|
||
|
||
languageDropdownOpen.value = false;
|
||
}
|
||
|
||
// 点击外部关闭下拉菜单
|
||
function closeDropdowns(e: MouseEvent) {
|
||
if (!e.target || !(e.target as HTMLElement).closest('.language-switcher')) {
|
||
languageDropdownOpen.value = false;
|
||
}
|
||
}
|
||
|
||
const handleScroll = () => {
|
||
if (window.scrollY > 100) {
|
||
scrolled.value = true;
|
||
} else {
|
||
scrolled.value = false;
|
||
}
|
||
};
|
||
|
||
onMounted(() => {
|
||
window.addEventListener('scroll', handleScroll);
|
||
document.addEventListener('click', closeDropdowns);
|
||
});
|
||
|
||
onUnmounted(() => {
|
||
window.removeEventListener('scroll', handleScroll);
|
||
document.removeEventListener('click', closeDropdowns);
|
||
});
|
||
</script>
|
||
|
||
<style scoped>
|
||
.container {
|
||
max-width: 1280px;
|
||
margin: 0 auto;
|
||
}
|
||
|
||
.nav-social-icons a, .language-switcher button {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
@keyframes fadeIn {
|
||
from { opacity: 0; transform: translateY(-10px); }
|
||
to { opacity: 1; transform: translateY(0); }
|
||
}
|
||
|
||
.language-switcher .absolute {
|
||
animation: fadeIn 0.2s ease-out;
|
||
}
|
||
</style> |