CloudBridge/components/AppHeader.vue

142 lines
4.8 KiB
Vue
Raw Normal View History

2025-09-04 18:01:27 +08:00
<template>
<header class="bg-white shadow-sm border-b border-gray-200">
<div class="container-custom">
<div class="flex items-center justify-between h-16">
<!-- Logo -->
<NuxtLink to="/" class="flex items-center space-x-2">
<div class="w-8 h-8 bg-aws-orange rounded-lg flex items-center justify-center">
<span class="text-white font-bold text-lg">C</span>
</div>
<span class="text-xl font-bold text-gray-900">CloudBridge</span>
</NuxtLink>
<!-- Desktop Navigation -->
<nav class="hidden md:flex items-center space-x-8">
<NuxtLink
v-for="item in navItems"
:key="item.key"
:to="item.to"
class="text-gray-700 hover:text-aws-orange font-medium transition-colors duration-200"
>
{{ item.label }}
</NuxtLink>
</nav>
<!-- Right side actions -->
<div class="flex items-center space-x-4">
<!-- Language Switcher -->
<div class="relative">
<select
v-model="currentLocale"
@change="switchLanguage"
class="appearance-none bg-white border border-gray-300 rounded-md px-3 py-1 text-sm focus:outline-none focus:ring-2 focus:ring-aws-orange focus:border-transparent"
>
<option v-for="locale in locales" :key="locale.code" :value="locale.code">
{{ locale.name }}
</option>
</select>
</div>
<!-- Social Links -->
<div class="flex items-center space-x-3">
<a
href="https://t.me/pinnovatecloud"
target="_blank"
rel="noopener noreferrer"
class="text-gray-600 hover:text-aws-orange transition-colors duration-200"
:title="$t('contact.telegram')"
>
<MessageCircle class="w-5 h-5" />
</a>
<a
href="https://wa.me/19174029875"
target="_blank"
rel="noopener noreferrer"
class="text-gray-600 hover:text-aws-orange transition-colors duration-200"
:title="$t('contact.whatsapp')"
>
<Phone class="w-5 h-5" />
</a>
<span class="text-sm text-gray-500">+1 9174029875</span>
</div>
<!-- Mobile menu button -->
<button
@click="mobileMenuOpen = !mobileMenuOpen"
class="md:hidden p-2 rounded-md text-gray-600 hover:text-aws-orange hover:bg-gray-100"
>
<Menu v-if="!mobileMenuOpen" class="w-6 h-6" />
<X v-else class="w-6 h-6" />
</button>
</div>
</div>
<!-- Mobile Navigation -->
<div v-if="mobileMenuOpen" class="md:hidden border-t border-gray-200 py-4">
<nav class="flex flex-col space-y-4">
<NuxtLink
v-for="item in navItems"
:key="item.key"
:to="item.to"
@click="mobileMenuOpen = false"
class="text-gray-700 hover:text-aws-orange font-medium py-2"
>
{{ item.label }}
</NuxtLink>
</nav>
</div>
</div>
</header>
</template>
<script setup>
import { MessageCircle, Phone, Menu, X } from 'lucide-vue-next'
const { t, locale, locales, setLocale } = useI18n()
const localePath = useLocalePath()
const switchLocalePath = useSwitchLocalePath()
const router = useRouter()
const mobileMenuOpen = ref(false)
const currentLocale = ref(locale.value)
const navItems = computed(() => [
{ key: 'home', label: t('nav.home'), to: localePath('/') },
{ key: 'services', label: t('nav.services'), to: localePath('/services') },
{ key: 'pricing', label: t('nav.pricing'), to: localePath('/pricing') },
{ key: 'blog', label: t('nav.blog'), to: localePath('/blog') },
{ key: 'contact', label: t('nav.contact'), to: localePath('/contact') }
])
const route = useRoute()
const switchLanguage = async () => {
const target = currentLocale.value
let path = switchLocalePath(target)
// 强力回退:若未返回路径,则拼接目标语言前缀到当前非语言前缀路径
if (!path) {
const withoutPrefix = route.fullPath.replace(/^\/(en|zh|zh-hant)(?=\/|$)/, '') || '/'
path = target === 'en' ? withoutPrefix : `/${target}${withoutPrefix}`
}
// 规范化默认语言en不应包含 /en 前缀strategy: prefix_except_default
if (path && /^\/en(\/|$)/.test(path)) {
path = path.replace(/^\/en(\/|$)/, '/').replace(/\/$/, '') || '/'
}
await router.push(path)
mobileMenuOpen.value = false
}
// Keep select value in sync when locale changes externally (e.g., via links)
watch(locale, (val) => {
currentLocale.value = val
})
// Close mobile menu when route changes
watch(() => useRoute().path, () => {
mobileMenuOpen.value = false
})
</script>