国外MOSE官网
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.

277 lines
8.6 KiB

3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
  1. <script setup lang="ts">
  2. import { useI18n } from 'vue-i18n';
  3. import { ref, computed, onMounted, watch } from 'vue';
  4. import { Icon } from '@iconify/vue';
  5. import { queryQuestionList, type QuestionItem } from '@/api/modules';
  6. // 声明全局变量
  7. declare global {
  8. interface Window {
  9. Typed: any;
  10. }
  11. }
  12. const { t } = useI18n();
  13. // 搜索关键词
  14. const searchQuery = ref('');
  15. // 问题列表
  16. const questions = ref<QuestionItem[]>([]);
  17. const loading = ref(true);
  18. // 监听搜索关键词变化
  19. let searchTimeout: number | null = null;
  20. watch(searchQuery, (newValue: string) => {
  21. // 清除之前的定时器
  22. if (searchTimeout) {
  23. clearTimeout(searchTimeout);
  24. }
  25. // 设置新的定时器,实现防抖
  26. searchTimeout = setTimeout(() => {
  27. loadQuestions();
  28. }, 500) as unknown as number;
  29. });
  30. // 处理搜索按钮点击
  31. const handleSearch = () => {
  32. if (searchTimeout) {
  33. clearTimeout(searchTimeout);
  34. }
  35. loadQuestions();
  36. };
  37. // 加载问题数据
  38. const loadQuestions = async () => {
  39. try {
  40. loading.value = true;
  41. const data = await queryQuestionList({
  42. pageSize: 50,
  43. pageNo: 1,
  44. title: searchQuery.value || undefined // 添加title参数
  45. });
  46. questions.value = data;
  47. } catch (error) {
  48. console.error('加载常见问题数据失败:', error);
  49. } finally {
  50. loading.value = false;
  51. }
  52. };
  53. // 过滤后的问题列表
  54. const filteredQuestions = computed(() => {
  55. let result = questions.value;
  56. // 按搜索关键词过滤
  57. if (searchQuery.value) {
  58. const query = searchQuery.value.toLowerCase();
  59. result = result.filter(q =>
  60. q.question.toLowerCase().includes(query) ||
  61. q.answer.toLowerCase().includes(query)
  62. );
  63. }
  64. return result;
  65. });
  66. // 展开/折叠问题
  67. const expandedQuestions = ref<string[]>([]);
  68. const toggleQuestion = (id: string) => {
  69. const index = expandedQuestions.value.indexOf(id);
  70. if (index === -1) {
  71. expandedQuestions.value.push(id);
  72. } else {
  73. expandedQuestions.value.splice(index, 1);
  74. }
  75. };
  76. const isExpanded = (id: string) => {
  77. return expandedQuestions.value.includes(id);
  78. };
  79. // 打字机效果
  80. const typedElement = ref<HTMLElement | null>(null);
  81. let typed: any = null;
  82. onMounted(() => {
  83. loadQuestions();
  84. // 初始化打字机效果
  85. if (typedElement.value && window.Typed) {
  86. typed = new window.Typed(typedElement.value, {
  87. strings: [
  88. t('faq.hero.subtitle'),
  89. '有任何疑问?我们随时为您解答',
  90. '探索 MOSE 的常见问题',
  91. '快速找到您需要的答案'
  92. ],
  93. typeSpeed: 50,
  94. backSpeed: 30,
  95. backDelay: 1500,
  96. loop: true
  97. });
  98. }
  99. });
  100. </script>
  101. <template>
  102. <div class="bg-background min-h-screen">
  103. <!-- Hero Section -->
  104. <section class="relative py-32 px-6 md:px-12 lg:px-24 bg-background-dark overflow-hidden">
  105. <div class="container mx-auto relative z-10">
  106. <div class="max-w-3xl mx-auto text-center">
  107. <h1 class="text-4xl md:text-5xl lg:text-6xl font-bold text-text mb-6 wow animate__animated animate__fadeInDown animate__duration-fast">
  108. {{ t('faq.hero.title') }}
  109. </h1>
  110. <p class="text-lg md:text-xl text-text-secondary mb-8">
  111. <span ref="typedElement"></span>
  112. </p>
  113. </div>
  114. </div>
  115. <!-- Background Decoration -->
  116. <div class="absolute top-0 left-0 w-full h-full overflow-hidden opacity-10">
  117. <div class="absolute -top-24 -left-24 w-64 h-64 rounded-full bg-accent blur-3xl wow animate__animated animate__pulse animate__infinite"></div>
  118. <div class="absolute top-1/2 right-0 w-80 h-80 rounded-full bg-primary blur-3xl wow animate__animated animate__pulse animate__infinite animate__delay-sm"></div>
  119. <div class="absolute -bottom-24 left-1/3 w-72 h-72 rounded-full bg-secondary blur-3xl wow animate__animated animate__pulse animate__infinite animate__delay-md"></div>
  120. </div>
  121. </section>
  122. <!-- Search and Filter Section -->
  123. <section class="py-12 px-6 md:px-12 lg:px-24">
  124. <div class="container mx-auto">
  125. <div class="max-w-3xl mx-auto mb-12">
  126. <div class="relative mb-8 search-container">
  127. <input
  128. type="text"
  129. v-model="searchQuery"
  130. :placeholder="t('faq.search.placeholder')"
  131. class="w-full py-4 px-6 pr-12 bg-background-light text-text rounded-xl focus:outline-none focus:ring-2 focus:ring-primary"
  132. @keyup.enter="handleSearch"
  133. />
  134. <div
  135. class="absolute right-4 top-1/2 transform -translate-y-1/2 text-text-secondary hover:text-primary transition-colors btn-hover-scale cursor-pointer"
  136. @click="handleSearch"
  137. >
  138. <Icon icon="carbon:search" width="24" height="24" />
  139. </div>
  140. </div>
  141. </div>
  142. <!-- 加载中状态 -->
  143. <div v-if="loading" class="flex justify-center items-center py-16 max-w-3xl mx-auto">
  144. <div class="animate-spin rounded-full h-12 w-12 border-t-2 border-b-2 border-primary"></div>
  145. </div>
  146. <!-- FAQ Questions -->
  147. <div v-else class="max-w-3xl mx-auto">
  148. <div v-if="filteredQuestions.length === 0" class="text-center py-8 text-text-secondary wow animate__animated animate__fadeIn">
  149. {{ t('faq.search.noresults') }}
  150. </div>
  151. <div v-else class="space-y-4">
  152. <div
  153. v-for="question in filteredQuestions"
  154. :key="question.id"
  155. class="bg-background-light rounded-xl overflow-hidden wow animate__animated animate__fadeIn"
  156. >
  157. <button
  158. @click="toggleQuestion(question.id)"
  159. class="w-full px-6 py-4 flex justify-between items-center text-left hover:bg-background-light/80 transition-colors"
  160. >
  161. <h3 class="text-lg font-medium text-text">{{ question.question }}</h3>
  162. <Icon
  163. :icon="isExpanded(question.id) ? 'carbon:chevron-up' : 'carbon:chevron-down'"
  164. class="h-5 w-5 text-text-secondary transition-transform duration-300"
  165. width="20"
  166. height="20"
  167. />
  168. </button>
  169. <div
  170. v-show="isExpanded(question.id)"
  171. class="px-6 py-4 border-t border-background"
  172. >
  173. <p class="text-text-secondary whitespace-pre-line" v-html="question.answer"></p>
  174. </div>
  175. </div>
  176. </div>
  177. </div>
  178. </div>
  179. </section>
  180. <!-- More Questions Section -->
  181. <section class="py-16 px-6 md:px-12 lg:px-24 bg-background-light">
  182. <div class="container mx-auto">
  183. <div class="max-w-3xl mx-auto text-center">
  184. <h2 class="text-2xl font-bold text-text mb-4">
  185. {{ t('faq.more.title') || '还有更多问题?' }}
  186. </h2>
  187. <div class="flex flex-col sm:flex-row justify-center gap-4 mt-8">
  188. <router-link
  189. to="/contact"
  190. class="px-8 py-3 bg-primary text-text rounded-lg hover:bg-primary-dark transition-colors duration-300 shadow-button btn-hover-glow flex items-center justify-center gap-2"
  191. >
  192. <Icon icon="carbon:email" width="20" height="20" />
  193. {{ t('faq.more.contact') || '联系我们' }}
  194. </router-link>
  195. <router-link
  196. to="/community"
  197. class="px-8 py-3 bg-transparent border border-primary-light text-primary-light rounded-lg hover:bg-primary-light hover:bg-opacity-10 transition-colors duration-300 btn-hover-shadow flex items-center justify-center gap-2"
  198. >
  199. <Icon icon="carbon:group" width="20" height="20" />
  200. {{ t('faq.more.community') || '加入社区' }}
  201. </router-link>
  202. </div>
  203. </div>
  204. </div>
  205. </section>
  206. </div>
  207. </template>
  208. <style scoped>
  209. button {
  210. outline: none;
  211. }
  212. .btn-hover-float {
  213. transition: transform 0.3s ease;
  214. }
  215. .btn-hover-float:hover {
  216. transform: translateY(-3px);
  217. }
  218. .btn-hover-glow {
  219. position: relative;
  220. overflow: hidden;
  221. }
  222. .btn-hover-glow::after {
  223. content: '';
  224. position: absolute;
  225. top: -50%;
  226. left: -50%;
  227. width: 200%;
  228. height: 200%;
  229. background: radial-gradient(circle, rgba(255,255,255,0.2) 0%, rgba(255,255,255,0) 70%);
  230. opacity: 0;
  231. transform: scale(0.5);
  232. transition: opacity 0.3s ease, transform 0.3s ease;
  233. }
  234. .btn-hover-glow:hover::after {
  235. opacity: 1;
  236. transform: scale(1);
  237. }
  238. .btn-hover-shadow {
  239. transition: box-shadow 0.3s ease;
  240. }
  241. .btn-hover-shadow:hover {
  242. box-shadow: 0 4px 12px rgba(var(--primary-light-rgb), 0.15);
  243. }
  244. </style>