Hanye官网
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198
  1. <template>
  2. <header class="fixed top-0 z-50 w-full bg-slate-900/70 backdrop-blur-[50px]">
  3. <div class="max-w-screen-2xl mx-auto px-4 sm:px-6 lg:px-10">
  4. <div class="h-[55px] flex justify-between items-center sm:h-[72px]">
  5. <div class="flex justify-start items-center gap-12 lg:gap-24">
  6. <nuxt-link :to="homePath" class="mt-[5px] flex-shrink-0">
  7. <i class="icon-brand text-white text-1xl sm:text-2xl"></i>
  8. </nuxt-link>
  9. <!-- Desktop Menu -->
  10. <nav class="hidden md:flex justify-start items-start gap-7 lg:gap-14">
  11. <nuxt-link
  12. v-for="item in menuItems"
  13. :key="item.path"
  14. class="justify-start text-white text-sm opacity-80 hover:opacity-100 transition-opacity"
  15. :to="item.path"
  16. :class="{
  17. 'text-white font-bold opacity-100': $route.path === item.path,
  18. }"
  19. >
  20. {{ $t(item.label) }}
  21. </nuxt-link>
  22. </nav>
  23. </div>
  24. <div class="flex justify-start items-center gap-4 md:gap-6">
  25. <!-- Search -->
  26. <div
  27. 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"
  28. >
  29. <button
  30. class="flex items-center justify-center w-8 h-8 opacity-80 hover:opacity-100 text-white"
  31. >
  32. <i class="icon-search text-sm"></i>
  33. </button>
  34. <span
  35. class="hidden lg:inline-block ml-1 text-white text-sm opacity-80"
  36. >
  37. {{ $t("common.search") }}
  38. </span>
  39. <!-- Input overlay could go here if implementing search -->
  40. </div>
  41. <!-- Language -->
  42. <LanguageSwitcher />
  43. <!-- Mobile Menu Button -->
  44. <div class="md:hidden">
  45. <button
  46. @click="toggleMobileMenu"
  47. 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"
  48. >
  49. <span class="sr-only">Open main menu</span>
  50. <!-- Icon when menu is closed. -->
  51. <svg
  52. v-if="!mobileMenuOpen"
  53. class="block h-6 w-6"
  54. xmlns="http://www.w3.org/2000/svg"
  55. fill="none"
  56. viewBox="0 0 24 24"
  57. stroke="currentColor"
  58. aria-hidden="true"
  59. >
  60. <path
  61. stroke-linecap="round"
  62. stroke-linejoin="round"
  63. stroke-width="2"
  64. d="M4 6h16M4 12h16M4 18h16"
  65. />
  66. </svg>
  67. <!-- Icon when menu is open. -->
  68. <svg
  69. v-else
  70. class="block h-6 w-6"
  71. xmlns="http://www.w3.org/2000/svg"
  72. fill="none"
  73. viewBox="0 0 24 24"
  74. stroke="currentColor"
  75. aria-hidden="true"
  76. >
  77. <path
  78. stroke-linecap="round"
  79. stroke-linejoin="round"
  80. stroke-width="2"
  81. d="M6 18L18 6M6 6l12 12"
  82. />
  83. </svg>
  84. </button>
  85. </div>
  86. </div>
  87. </div>
  88. </div>
  89. <!-- Mobile menu, show/hide based on menu state. -->
  90. <transition name="slide-fade">
  91. <div
  92. v-if="mobileMenuOpen"
  93. class="md:hidden bg-slate-800/90"
  94. id="mobile-menu"
  95. >
  96. <div class="px-2 pt-2 pb-3 space-y-1 sm:px-3">
  97. <nuxt-link
  98. v-for="item in menuItems"
  99. :key="item.path"
  100. :to="item.path"
  101. @click="closeMobileMenu"
  102. class="block px-3 py-2 rounded-md text-base font-medium"
  103. :class="[
  104. $route.path === item.path
  105. ? 'bg-gray-900 text-white'
  106. : 'text-gray-300 hover:bg-gray-700 hover:text-white',
  107. ]"
  108. >{{ $t(item.label) }}</nuxt-link
  109. >
  110. </div>
  111. </div>
  112. </transition>
  113. </header>
  114. </template>
  115. <script setup lang="ts">
  116. import { ref, computed } from "vue";
  117. import { useI18n } from "vue-i18n";
  118. import { useRuntimeConfig } from '#app'; // 导入 useRuntimeConfig
  119. /**
  120. * 页面头部组件
  121. * 包含导航菜单、语言切换和移动端响应式设计
  122. */
  123. const { t, locale } = useI18n();
  124. const config = useRuntimeConfig();
  125. // 从运行时配置获取默认语言,如果未配置则默认为 'en'
  126. const defaultLocale = config.public.i18n?.defaultLocale || 'en';
  127. const mobileMenuOpen = ref(false);
  128. // 使用 computed 来定义 homePath,根据是否为默认语言调整路径
  129. const homePath = computed(() => {
  130. // 如果是默认语言,路径为根路径 '/'
  131. // 否则,路径为 '/<locale>/'
  132. return locale.value === defaultLocale ? '/' : `/${locale.value}/`;
  133. });
  134. // 使用 computed 来定义 menuItems,根据是否为默认语言调整路径
  135. const menuItems = computed(() => {
  136. // 判断当前是否为默认语言
  137. const isDefaultLocale = locale.value === defaultLocale;
  138. // 如果是默认语言,路径前缀为空字符串,否则为 '/<locale>'
  139. const prefix = isDefaultLocale ? '' : `/${locale.value}`;
  140. return [
  141. // 首页路径特殊处理:默认语言为 '/', 其他语言为 '/<locale>/'
  142. { label: "common.home", path: isDefaultLocale ? '/' : `${prefix}/` },
  143. { label: "common.products", path: `${prefix}/products` },
  144. { label: "common.faq", path: `${prefix}/faq` },
  145. { label: "common.about", path: `${prefix}/about` },
  146. { label: "common.contact", path: `${prefix}/contact` },
  147. ];
  148. });
  149. /**
  150. * 切换移动端菜单显示状态
  151. */
  152. function toggleMobileMenu() {
  153. mobileMenuOpen.value = !mobileMenuOpen.value;
  154. }
  155. /**
  156. * 关闭移动端菜单
  157. */
  158. function closeMobileMenu() {
  159. mobileMenuOpen.value = false;
  160. }
  161. </script>
  162. <style lang="scss" scoped>
  163. header {
  164. user-select: none;
  165. }
  166. /* Transition for mobile menu */
  167. .slide-fade-enter-active {
  168. transition: all 0.3s ease-out;
  169. }
  170. .slide-fade-leave-active {
  171. transition: all 0.3s cubic-bezier(1, 0.5, 0.8, 1);
  172. }
  173. .slide-fade-enter-from,
  174. .slide-fade-leave-to {
  175. transform: translateY(-20px);
  176. opacity: 0;
  177. }
  178. /* Keep the sticky header consistent */
  179. .sticky {
  180. position: sticky;
  181. }
  182. </style>