123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198 |
- <template>
- <header class="fixed top-0 z-50 w-full bg-slate-900/70 backdrop-blur-[50px]">
- <div class="max-w-screen-2xl mx-auto px-4 sm:px-6 lg:px-10">
- <div class="h-[55px] flex justify-between items-center sm:h-[72px]">
- <div class="flex justify-start items-center gap-12 lg:gap-24">
- <nuxt-link :to="homePath" class="mt-[5px] flex-shrink-0">
- <i class="icon-brand text-white text-1xl sm:text-2xl"></i>
- </nuxt-link>
- <!-- Desktop Menu -->
- <nav class="hidden md:flex justify-start items-start gap-7 lg:gap-14">
- <nuxt-link
- v-for="item in menuItems"
- :key="item.path"
- class="justify-start text-white text-sm opacity-80 hover:opacity-100 transition-opacity"
- :to="item.path"
- :class="{
- 'text-white font-bold opacity-100': $route.path === item.path,
- }"
- >
- {{ $t(item.label) }}
- </nuxt-link>
- </nav>
- </div>
-
- <div class="flex justify-start items-center gap-4 md:gap-6">
- <!-- Search -->
- <div
- class="w-auto h-8 relative items-center opacity-40 rounded-2xl border border-color-[rgba(255,255,255,0.4)] pr-4 hover:opacity-100 transition-opacity duration-300 hidden md:flex"
- >
- <button
- class="flex items-center justify-center w-8 h-8 opacity-80 hover:opacity-100 text-white"
- >
- <i class="icon-search text-sm"></i>
- </button>
- <span
- class="hidden lg:inline-block ml-1 text-white text-sm opacity-80"
- >
- {{ $t("common.search") }}
- </span>
- <!-- Input overlay could go here if implementing search -->
- </div>
-
- <!-- Language -->
- <LanguageSwitcher />
-
- <!-- Mobile Menu Button -->
- <div class="md:hidden">
- <button
- @click="toggleMobileMenu"
- class="inline-flex items-center justify-center p-2 rounded-md text-gray-400 bg-white/10 hover:text-white hover:bg-white/20 focus:outline-none focus:ring-2 focus:ring-inset focus:ring-white"
- >
- <span class="sr-only">Open main menu</span>
- <!-- Icon when menu is closed. -->
- <svg
- v-if="!mobileMenuOpen"
- class="block h-6 w-6"
- xmlns="http://www.w3.org/2000/svg"
- fill="none"
- viewBox="0 0 24 24"
- stroke="currentColor"
- aria-hidden="true"
- >
- <path
- stroke-linecap="round"
- stroke-linejoin="round"
- stroke-width="2"
- d="M4 6h16M4 12h16M4 18h16"
- />
- </svg>
- <!-- Icon when menu is open. -->
- <svg
- v-else
- class="block h-6 w-6"
- xmlns="http://www.w3.org/2000/svg"
- fill="none"
- viewBox="0 0 24 24"
- stroke="currentColor"
- aria-hidden="true"
- >
- <path
- stroke-linecap="round"
- stroke-linejoin="round"
- stroke-width="2"
- d="M6 18L18 6M6 6l12 12"
- />
- </svg>
- </button>
- </div>
- </div>
- </div>
- </div>
-
- <!-- Mobile menu, show/hide based on menu state. -->
- <transition name="slide-fade">
- <div
- v-if="mobileMenuOpen"
- class="md:hidden bg-slate-800/90"
- id="mobile-menu"
- >
- <div class="px-2 pt-2 pb-3 space-y-1 sm:px-3">
- <nuxt-link
- v-for="item in menuItems"
- :key="item.path"
- :to="item.path"
- @click="closeMobileMenu"
- class="block px-3 py-2 rounded-md text-base font-medium"
- :class="[
- $route.path === item.path
- ? 'bg-gray-900 text-white'
- : 'text-gray-300 hover:bg-gray-700 hover:text-white',
- ]"
- >{{ $t(item.label) }}</nuxt-link
- >
- </div>
- </div>
- </transition>
- </header>
- </template>
-
- <script setup lang="ts">
- import { ref, computed } from "vue";
- import { useI18n } from "vue-i18n";
- import { useRuntimeConfig } from '#app'; // 导入 useRuntimeConfig
-
- /**
- * 页面头部组件
- * 包含导航菜单、语言切换和移动端响应式设计
- */
- const { t, locale } = useI18n();
- const config = useRuntimeConfig();
- // 从运行时配置获取默认语言,如果未配置则默认为 'en'
- const defaultLocale = config.public.i18n?.defaultLocale || 'en';
- const mobileMenuOpen = ref(false);
-
- // 使用 computed 来定义 homePath,根据是否为默认语言调整路径
- const homePath = computed(() => {
- // 如果是默认语言,路径为根路径 '/'
- // 否则,路径为 '/<locale>/'
- return locale.value === defaultLocale ? '/' : `/${locale.value}/`;
- });
-
-
- // 使用 computed 来定义 menuItems,根据是否为默认语言调整路径
- const menuItems = computed(() => {
- // 判断当前是否为默认语言
- const isDefaultLocale = locale.value === defaultLocale;
- // 如果是默认语言,路径前缀为空字符串,否则为 '/<locale>'
- const prefix = isDefaultLocale ? '' : `/${locale.value}`;
- return [
- // 首页路径特殊处理:默认语言为 '/', 其他语言为 '/<locale>/'
- { label: "common.home", path: isDefaultLocale ? '/' : `${prefix}/` },
- { label: "common.products", path: `${prefix}/products` },
- { label: "common.faq", path: `${prefix}/faq` },
- { label: "common.about", path: `${prefix}/about` },
- { label: "common.contact", path: `${prefix}/contact` },
- ];
- });
-
- /**
- * 切换移动端菜单显示状态
- */
- function toggleMobileMenu() {
- mobileMenuOpen.value = !mobileMenuOpen.value;
- }
-
- /**
- * 关闭移动端菜单
- */
- function closeMobileMenu() {
- mobileMenuOpen.value = false;
- }
- </script>
-
- <style lang="scss" scoped>
- header {
- user-select: none;
- }
-
- /* Transition for mobile menu */
- .slide-fade-enter-active {
- transition: all 0.3s ease-out;
- }
-
- .slide-fade-leave-active {
- transition: all 0.3s cubic-bezier(1, 0.5, 0.8, 1);
- }
-
- .slide-fade-enter-from,
- .slide-fade-leave-to {
- transform: translateY(-20px);
- opacity: 0;
- }
-
- /* Keep the sticky header consistent */
- .sticky {
- position: sticky;
- }
- </style>
|