2025-09-11 10:55:59 +08:00

281 lines
11 KiB
Vue
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<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 space-x-2 flex-shrink-0">
<img src="/logo.svg" alt="www.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">
<!-- Telegram -->
<a :href="contact.telegram" target="_blank" class="flex items-center space-x-2 text-gray-700 dark:text-gray-300 hover:text-blue-500">
<i class="fab fa-telegram" style="color: #2AABEE; font-size: 1.55rem;"></i>
<span class="font-medium">@{{ contact.telegram.split('/').pop() }}</span>
</a>
<!-- WhatsApp -->
<a :href="contact.whatsapp" target="_blank" class="flex items-center space-x-2 text-gray-700 dark:text-gray-300 hover:text-blue-500">
<i class="fab fa-whatsapp" style="color: #25D366; font-size: 1.5rem;"></i>
<span class="font-medium whitespace-nowrap">{{ contact.phone.replace('Tel: ', '') }}</span>
</a>
<!-- Language Switcher -->
<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 px-4">
<!-- 移动端导航链接 -->
<div class="flex flex-col space-y-2">
<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 text-center rounded-md hover:bg-gray-100 dark:hover:bg-gray-800"
:class="{ 'text-blue-700 dark:text-blue-300 font-semibold bg-gray-100 dark:bg-gray-800': route.path === item.path }"
@click="mobileMenuOpen = false"
>
{{ item.name }}
</NuxtLink>
</div>
<!-- 分割线 -->
<hr class="my-4 border-gray-200 dark:border-gray-700">
<!-- 移动端社交媒体图标 -->
<div class="flex flex-col items-center space-y-4">
<!-- Telegram -->
<a :href="contact.telegram" target="_blank" class="flex items-center space-x-2 text-gray-700 dark:text-gray-300 hover:text-blue-500">
<i class="fab fa-telegram" style="color: #2AABEE; font-size: 1.5rem;"></i>
<span class="font-medium">@{{ contact.telegram.split('/').pop() }}</span>
</a>
<!-- WhatsApp -->
<a :href="contact.whatsapp" target="_blank" class="flex items-center space-x-2 text-gray-700 dark:text-gray-300 hover:text-blue-500">
<i class="fab fa-whatsapp" style="color: #25D366; font-size: 1.5rem;"></i>
<span class="font-medium whitespace-nowrap">{{ contact.phone.replace('Tel: ', '') }}</span>
</a>
</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>