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.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190
  1. <template>
  2. <footer
  3. class="text-white py-8 w-full relative bg-stone-950 border-t border-zinc-800 overflow-hidden"
  4. >
  5. <div class="max-w-screen-2xl mx-auto px-4 sm:px-6 lg:px-10">
  6. <div
  7. class="grid grid-cols-1 md:grid-cols-2 sm:grid-cols-1 lg:grid-cols-4 gap-8 lg:py-14"
  8. >
  9. <!-- Logo & 描述 -->
  10. <div
  11. class="flex flex-col lg:items-start :lg:justify-start sm:items-center text-center lg:text-left col-span-2 lg:col-span-1"
  12. >
  13. <h3 class="mb-4">
  14. <i class="icon-brand text-white text-2xl"></i>
  15. </h3>
  16. <LanguageSwitcher class="mb-8 hidden lg:block" />
  17. <p class="text-white opacity-60 text-xs">
  18. &copy; {{ new Date().getFullYear() }} Hanye. All rights reserved.
  19. </p>
  20. </div>
  21. <!-- 产品分类链接 -->
  22. <div class="hidden lg:block">
  23. <h3
  24. class="justify-start text-zinc-300 text-xl font-normal leading-snug mb-4"
  25. >
  26. {{ t("common.footer.productsLinks.title") }}
  27. </h3>
  28. <ul class="space-y-4">
  29. <li v-for="item in menuProductsItems" :key="item.path">
  30. <NuxtLink
  31. :to="item.path"
  32. class="text-zinc-500 text-sm font-normal hover:text-white transition"
  33. >
  34. {{ t(item.label) }}
  35. </NuxtLink>
  36. </li>
  37. </ul>
  38. </div>
  39. <!-- 网站快捷链接 -->
  40. <div class="hidden lg:block">
  41. <h3
  42. class="justify-start text-zinc-300 text-xl font-normal leading-snug mb-4"
  43. >
  44. {{ t("common.footer.websiteLinks.title") }}
  45. </h3>
  46. <ul class="space-y-4">
  47. <li v-for="item in menuWebsiteItems" :key="item.path">
  48. <NuxtLink
  49. :to="item.path"
  50. class="text-zinc-500 text-sm font-normal hover:text-white transition"
  51. >
  52. {{ t(item.label) }}
  53. </NuxtLink>
  54. </li>
  55. </ul>
  56. </div>
  57. <!-- 法律与支持 -->
  58. <div class="hidden lg:block">
  59. <h3
  60. class="justify-start text-zinc-300 text-xl font-normal leading-snug mb-4"
  61. >
  62. {{ t("common.footer.legalAndSupport.title") }}
  63. </h3>
  64. <ul class="space-y-4">
  65. <li v-for="item in menuLegalAndSupportItems" :key="item.path">
  66. <NuxtLink
  67. :to="item.path"
  68. class="text-zinc-500 text-sm font-normal hover:text-white transition"
  69. >
  70. {{ t(item.label) }}
  71. </NuxtLink>
  72. </li>
  73. </ul>
  74. </div>
  75. </div>
  76. </div>
  77. </footer>
  78. </template>
  79. <script setup lang="ts">
  80. /**
  81. * 页脚组件
  82. * 包含网站导航、联系信息和版权信息
  83. */
  84. import { useI18n } from "vue-i18n";
  85. const { locale, t } = useI18n();
  86. const config = useRuntimeConfig();
  87. const defaultLocale = config.public.i18n?.defaultLocale || "zh";
  88. // 获取产品分类数据
  89. const { data: categoryResponse } = await useAsyncData(
  90. `footer-categories-${locale.value}`,
  91. async () => {
  92. try {
  93. // 使用queryCollection从content目录获取数据
  94. const content = await queryCollection("content")
  95. .where("path", "LIKE", `/categories/${locale.value}/%`)
  96. .all();
  97. if (!content || !Array.isArray(content)) {
  98. console.log("No category content found for footer");
  99. return [];
  100. }
  101. // 转换为需要的格式
  102. return content
  103. .map((item: any) => {
  104. // 从路径中提取ID - 文件名就是ID
  105. const pathParts = item.path?.split("/");
  106. const idFile = pathParts?.[pathParts.length - 1] || "";
  107. // 如果ID在元数据中有提供,优先使用元数据中的ID
  108. const id = item.id
  109. ? parseInt(item.id)
  110. : parseInt(idFile.replace(".md", "")) || 0;
  111. return {
  112. title: item.title || "",
  113. id: id,
  114. };
  115. })
  116. .sort((a, b) => (a.id || 0) - (b.id || 0));
  117. } catch (error) {
  118. console.error("Error loading category data for footer:", error);
  119. return [];
  120. }
  121. }
  122. );
  123. // 使用计算属性处理产品分类数据
  124. const productCategories = computed(() => {
  125. return categoryResponse.value || [];
  126. });
  127. // 导航菜单项
  128. const menuProductsItems = computed(() => {
  129. // 构建路径前缀
  130. const prefix = locale.value === defaultLocale ? "" : `/${locale.value}`;
  131. // 使用修改后的产品分类数据
  132. return productCategories.value.map((category: any) => ({
  133. label: category.title,
  134. path: `${prefix}/products?category=${encodeURIComponent(category.title)}`,
  135. }));
  136. });
  137. const menuWebsiteItems = computed(() => [
  138. {
  139. label: "common.footer.websiteLinks.home",
  140. path: locale.value === defaultLocale ? "/" : `/${locale.value}`,
  141. },
  142. {
  143. label: "common.footer.websiteLinks.products",
  144. path:
  145. locale.value === defaultLocale
  146. ? "/products"
  147. : `/${locale.value}/products`,
  148. },
  149. {
  150. label: "common.footer.websiteLinks.faq",
  151. path: locale.value === defaultLocale ? "/faq" : `/${locale.value}/faq`,
  152. },
  153. {
  154. label: "common.footer.websiteLinks.about",
  155. path: locale.value === defaultLocale ? "/about" : `/${locale.value}/about`,
  156. },
  157. {
  158. label: "common.footer.websiteLinks.contact",
  159. path:
  160. locale.value === defaultLocale ? "/contact" : `/${locale.value}/contact`,
  161. },
  162. ]);
  163. const menuLegalAndSupportItems = computed(() => [
  164. {
  165. label: "common.footer.legalAndSupport.support",
  166. path:
  167. locale.value === defaultLocale ? "/support" : `/${locale.value}/support`,
  168. },
  169. {
  170. label: "common.footer.legalAndSupport.privacy",
  171. path:
  172. locale.value === defaultLocale ? "/privacy" : `/${locale.value}/privacy`,
  173. },
  174. {
  175. label: "common.footer.legalAndSupport.terms",
  176. path: locale.value === defaultLocale ? "/terms" : `/${locale.value}/terms`,
  177. },
  178. ]);
  179. </script>