Hanye官网
Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.

faq.vue 7.9KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220
  1. <template>
  2. <div class="w-full h-[55px] sm:h-[72px]"></div>
  3. <div
  4. class="max-w-full px-4 py-16 md:px-8 lg:px-10 bg-gradient-to-br from-gray-900 via-gray-900 to-black text-gray-300 min-h-screen"
  5. >
  6. <div class="max-w-screen-xl mx-auto">
  7. <h1 class="text-4xl md:text-6xl mb-12 text-center font-normal text-white">
  8. {{ $t("faq.title") }}
  9. </h1>
  10. <!-- Search Bar -->
  11. <div class="mb-12 max-w-xl mx-auto">
  12. <div class="relative">
  13. <input
  14. type="search"
  15. v-model="searchTerm"
  16. :placeholder="$t('faq.searchPlaceholder')"
  17. class="block w-full appearance-none rounded-lg border border-gray-600 bg-gray-700/50 px-4 py-3 pl-10 pr-4 text-base text-gray-100 placeholder-gray-400 shadow-inner transition duration-200 ease-in-out focus:border-blue-500 focus:outline-none focus:ring-2 focus:ring-blue-500/50 focus:ring-offset-2 focus:ring-offset-gray-900"
  18. />
  19. <div
  20. class="pointer-events-none absolute inset-y-0 left-0 flex items-center pl-3"
  21. >
  22. <svg
  23. xmlns="http://www.w3.org/2000/svg"
  24. class="h-5 w-5 text-gray-400"
  25. viewBox="0 0 20 20"
  26. fill="currentColor"
  27. >
  28. <path
  29. fill-rule="evenodd"
  30. d="M8 4a4 4 0 100 8 4 4 0 000-8zM2 8a6 6 0 1110.89 3.476l4.817 4.817a1 1 0 01-1.414 1.414l-4.816-4.816A6 6 0 012 8z"
  31. clip-rule="evenodd"
  32. />
  33. </svg>
  34. </div>
  35. </div>
  36. </div>
  37. <!-- FAQ Accordion -->
  38. <div class="space-y-5">
  39. <div
  40. v-for="faq in filteredFaqs"
  41. :key="faq.id"
  42. class="relative border rounded-xl overflow-hidden shadow-lg backdrop-blur-sm transition-all duration-300 ease-in-out group"
  43. :class="[
  44. openAccordionIds.has(faq.id)
  45. ? 'bg-gray-750/70 border-blue-500/60 shadow-blue-500/20'
  46. : 'bg-gray-800/60 border-gray-700 hover:border-blue-500/40 hover:bg-gray-750/70',
  47. ]"
  48. >
  49. <button
  50. @click="toggleAccordion(faq.id)"
  51. class="flex w-full items-center justify-between px-6 py-5 text-left text-lg font-medium text-gray-100 focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2 focus-visible:ring-offset-gray-800 transition-colors duration-200"
  52. :class="{ 'bg-gray-700/50': openAccordionIds.has(faq.id) }"
  53. :aria-expanded="openAccordionIds.has(faq.id)"
  54. :aria-controls="`faq-answer-${faq.id}`"
  55. >
  56. <span class="pr-4">{{ faq.question }}</span>
  57. <!-- Plus/Minus Icon -->
  58. <div
  59. class="relative h-6 w-6 flex-shrink-0 text-blue-400 group-hover:text-blue-300"
  60. >
  61. <svg
  62. xmlns="http://www.w3.org/2000/svg"
  63. class="absolute h-6 w-6 transition-opacity duration-300 ease-in-out"
  64. :class="
  65. openAccordionIds.has(faq.id) ? 'opacity-0' : 'opacity-100'
  66. "
  67. fill="none"
  68. viewBox="0 0 24 24"
  69. stroke="currentColor"
  70. stroke-width="2"
  71. >
  72. <path
  73. stroke-linecap="round"
  74. stroke-linejoin="round"
  75. d="M12 4v16m8-8H4"
  76. />
  77. </svg>
  78. <svg
  79. xmlns="http://www.w3.org/2000/svg"
  80. class="absolute h-6 w-6 transition-opacity duration-300 ease-in-out"
  81. :class="
  82. openAccordionIds.has(faq.id) ? 'opacity-100' : 'opacity-0'
  83. "
  84. fill="none"
  85. viewBox="0 0 24 24"
  86. stroke="currentColor"
  87. stroke-width="2"
  88. >
  89. <path
  90. stroke-linecap="round"
  91. stroke-linejoin="round"
  92. d="M20 12H4"
  93. />
  94. </svg>
  95. </div>
  96. </button>
  97. <transition
  98. enter-active-class="transition-[grid-template-rows,opacity] ease-in-out duration-300"
  99. enter-from-class="grid-template-rows-[0fr] opacity-0"
  100. enter-to-class="grid-template-rows-[1fr] opacity-100"
  101. leave-active-class="transition-[grid-template-rows,opacity] ease-in-out duration-300"
  102. leave-from-class="grid-template-rows-[1fr] opacity-100"
  103. leave-to-class="grid-template-rows-[0fr] opacity-0"
  104. >
  105. <div
  106. v-show="openAccordionIds.has(faq.id)"
  107. :id="`faq-answer-${faq.id}`"
  108. class="grid overflow-hidden"
  109. role="region"
  110. >
  111. <div class="overflow-hidden">
  112. <div
  113. class="px-6 pt-4 pb-8 text-base text-gray-300 leading-relaxed"
  114. >
  115. <div
  116. class="prose prose-invert max-w-none prose-p:text-gray-300 prose-a:text-blue-400 hover:prose-a:text-blue-300"
  117. >
  118. <p v-html="faq.answer"></p>
  119. </div>
  120. </div>
  121. </div>
  122. </div>
  123. </transition>
  124. </div>
  125. <div
  126. v-if="filteredFaqs.length === 0"
  127. class="text-center text-gray-400 py-8"
  128. >
  129. {{ $t("faq.noResults") }}
  130. </div>
  131. </div>
  132. </div>
  133. </div>
  134. </template>
  135. <script setup lang="ts">
  136. import { ref, computed } from "vue";
  137. import { useI18n } from "vue-i18n";
  138. const { t } = useI18n();
  139. const searchTerm = ref("");
  140. const openAccordionIds = ref<Set<string>>(new Set());
  141. // Define FAQ item type with actual strings
  142. interface FaqItem {
  143. id: string;
  144. question: string;
  145. answer: string;
  146. }
  147. // Placeholder FAQ data with actual strings (replace with your real data)
  148. const faqs = ref<FaqItem[]>([
  149. {
  150. id: "faq-1",
  151. question: "如何购买 Hanye 产品?", // Example Question 1
  152. answer:
  153. "您可以通过我们的官方在线商店或授权的零售商处购买 Hanye 产品。我们建议您在官方渠道购买以确保正品和售后服务。", // Example Answer 1
  154. },
  155. {
  156. id: "faq-2",
  157. question: "产品保修期是多久?",
  158. answer:
  159. "不同产品的保修期可能不同,请参考具体产品的说明页面或联系我们的客服获取详细信息。通常固态硬盘提供3-5年保修,内存条提供终身保固。",
  160. },
  161. {
  162. id: "faq-3",
  163. question: "如何申请售后服务?",
  164. answer:
  165. "如果您需要售后服务,请准备好您的购买凭证,并通过我们的官方网站提交售后申请或直接联系客服中心。",
  166. },
  167. {
  168. id: "faq-4",
  169. question: "Hanye SSD 是否兼容我的电脑?",
  170. answer:
  171. "Hanye SSD 兼容大多数台式机和笔记本电脑。请确认您的设备支持相应的接口(如 SATA 或 NVMe)和规格。具体兼容性列表请参考产品页面。",
  172. },
  173. {
  174. id: "faq-5",
  175. question: "忘记密码怎么办?",
  176. answer:
  177. '如果是指 Hanye 相关的在线服务账户密码,请使用"忘记密码"功能进行重置。如果是加密 U 盘或 SSD 的密码,很抱歉,为了数据安全,我们无法提供密码破解服务。',
  178. },
  179. ]);
  180. // Filter FAQs based on actual string content
  181. const filteredFaqs = computed(() => {
  182. if (!searchTerm.value) {
  183. return faqs.value;
  184. }
  185. const lowerSearchTerm = searchTerm.value.toLowerCase();
  186. return faqs.value.filter(
  187. (faq: FaqItem) =>
  188. faq.question.toLowerCase().includes(lowerSearchTerm) ||
  189. faq.answer.toLowerCase().includes(lowerSearchTerm)
  190. );
  191. });
  192. // Toggle accordion item (modified for Set)
  193. const toggleAccordion = (id: string) => {
  194. if (openAccordionIds.value.has(id)) {
  195. openAccordionIds.value.delete(id);
  196. } else {
  197. openAccordionIds.value.add(id);
  198. }
  199. };
  200. // SEO (Still uses i18n)
  201. useHead({
  202. title: t("faq.meta.title"),
  203. meta: [
  204. {
  205. name: "description",
  206. content: t("faq.meta.description"),
  207. },
  208. ],
  209. });
  210. </script>