<script setup lang="ts">
|
|
import { useI18n } from 'vue-i18n';
|
|
import { ref, computed, onMounted, watch } from 'vue';
|
|
import { Icon } from '@iconify/vue';
|
|
import { queryQuestionList, type QuestionItem } from '@/api/modules';
|
|
|
|
// 声明全局变量
|
|
declare global {
|
|
interface Window {
|
|
Typed: any;
|
|
}
|
|
}
|
|
|
|
const { t } = useI18n();
|
|
|
|
// 搜索关键词
|
|
const searchQuery = ref('');
|
|
|
|
// 问题列表
|
|
const questions = ref<QuestionItem[]>([]);
|
|
const loading = ref(true);
|
|
|
|
// 监听搜索关键词变化
|
|
let searchTimeout: number | null = null;
|
|
watch(searchQuery, (newValue: string) => {
|
|
// 清除之前的定时器
|
|
if (searchTimeout) {
|
|
clearTimeout(searchTimeout);
|
|
}
|
|
|
|
// 设置新的定时器,实现防抖
|
|
searchTimeout = setTimeout(() => {
|
|
loadQuestions();
|
|
}, 500) as unknown as number;
|
|
});
|
|
|
|
// 处理搜索按钮点击
|
|
const handleSearch = () => {
|
|
if (searchTimeout) {
|
|
clearTimeout(searchTimeout);
|
|
}
|
|
loadQuestions();
|
|
};
|
|
|
|
// 加载问题数据
|
|
const loadQuestions = async () => {
|
|
try {
|
|
loading.value = true;
|
|
const data = await queryQuestionList({
|
|
pageSize: 50,
|
|
pageNo: 1,
|
|
title: searchQuery.value || undefined // 添加title参数
|
|
});
|
|
questions.value = data;
|
|
} catch (error) {
|
|
console.error('加载常见问题数据失败:', error);
|
|
} finally {
|
|
loading.value = false;
|
|
}
|
|
};
|
|
|
|
// 过滤后的问题列表
|
|
const filteredQuestions = computed(() => {
|
|
let result = questions.value;
|
|
|
|
// 按搜索关键词过滤
|
|
if (searchQuery.value) {
|
|
const query = searchQuery.value.toLowerCase();
|
|
result = result.filter(q =>
|
|
q.question.toLowerCase().includes(query) ||
|
|
q.answer.toLowerCase().includes(query)
|
|
);
|
|
}
|
|
|
|
return result;
|
|
});
|
|
|
|
// 展开/折叠问题
|
|
const expandedQuestions = ref<string[]>([]);
|
|
|
|
const toggleQuestion = (id: string) => {
|
|
const index = expandedQuestions.value.indexOf(id);
|
|
if (index === -1) {
|
|
expandedQuestions.value.push(id);
|
|
} else {
|
|
expandedQuestions.value.splice(index, 1);
|
|
}
|
|
};
|
|
|
|
const isExpanded = (id: string) => {
|
|
return expandedQuestions.value.includes(id);
|
|
};
|
|
|
|
// 打字机效果
|
|
const typedElement = ref<HTMLElement | null>(null);
|
|
let typed: any = null;
|
|
|
|
onMounted(() => {
|
|
loadQuestions();
|
|
|
|
// 初始化打字机效果
|
|
if (typedElement.value && window.Typed) {
|
|
typed = new window.Typed(typedElement.value, {
|
|
strings: [
|
|
t('faq.hero.subtitle'),
|
|
'有任何疑问?我们随时为您解答',
|
|
'探索 MOSE 的常见问题',
|
|
'快速找到您需要的答案'
|
|
],
|
|
typeSpeed: 50,
|
|
backSpeed: 30,
|
|
backDelay: 1500,
|
|
loop: true
|
|
});
|
|
}
|
|
});
|
|
</script>
|
|
|
|
<template>
|
|
<div class="bg-background min-h-screen">
|
|
<!-- Hero Section -->
|
|
<section class="relative py-32 px-6 md:px-12 lg:px-24 bg-background-dark overflow-hidden">
|
|
<div class="container mx-auto relative z-10">
|
|
<div class="max-w-3xl mx-auto text-center">
|
|
<h1 class="text-4xl md:text-5xl lg:text-6xl font-bold text-text mb-6 wow animate__animated animate__fadeInDown animate__duration-fast">
|
|
{{ t('faq.hero.title') }}
|
|
</h1>
|
|
<p class="text-lg md:text-xl text-text-secondary mb-8">
|
|
<span ref="typedElement"></span>
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Background Decoration -->
|
|
<div class="absolute top-0 left-0 w-full h-full overflow-hidden opacity-10">
|
|
<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>
|
|
<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>
|
|
<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>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- Search and Filter Section -->
|
|
<section class="py-12 px-6 md:px-12 lg:px-24">
|
|
<div class="container mx-auto">
|
|
<div class="max-w-3xl mx-auto mb-12">
|
|
|
|
<div class="relative mb-8 search-container">
|
|
<input
|
|
type="text"
|
|
v-model="searchQuery"
|
|
:placeholder="t('faq.search.placeholder')"
|
|
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"
|
|
@keyup.enter="handleSearch"
|
|
/>
|
|
<div
|
|
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"
|
|
@click="handleSearch"
|
|
>
|
|
<Icon icon="carbon:search" width="24" height="24" />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 加载中状态 -->
|
|
<div v-if="loading" class="flex justify-center items-center py-16 max-w-3xl mx-auto">
|
|
<div class="animate-spin rounded-full h-12 w-12 border-t-2 border-b-2 border-primary"></div>
|
|
</div>
|
|
|
|
<!-- FAQ Questions -->
|
|
<div v-else class="max-w-3xl mx-auto">
|
|
<div v-if="filteredQuestions.length === 0" class="text-center py-8 text-text-secondary wow animate__animated animate__fadeIn">
|
|
{{ t('faq.search.noresults') }}
|
|
</div>
|
|
|
|
<div v-else class="space-y-4">
|
|
<div
|
|
v-for="question in filteredQuestions"
|
|
:key="question.id"
|
|
class="bg-background-light rounded-xl overflow-hidden wow animate__animated animate__fadeIn"
|
|
>
|
|
<button
|
|
@click="toggleQuestion(question.id)"
|
|
class="w-full px-6 py-4 flex justify-between items-center text-left hover:bg-background-light/80 transition-colors"
|
|
>
|
|
<h3 class="text-lg font-medium text-text">{{ question.question }}</h3>
|
|
<Icon
|
|
:icon="isExpanded(question.id) ? 'carbon:chevron-up' : 'carbon:chevron-down'"
|
|
class="h-5 w-5 text-text-secondary transition-transform duration-300"
|
|
width="20"
|
|
height="20"
|
|
/>
|
|
</button>
|
|
|
|
<div
|
|
v-show="isExpanded(question.id)"
|
|
class="px-6 py-4 border-t border-background"
|
|
>
|
|
<p class="text-text-secondary whitespace-pre-line" v-html="question.answer"></p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- More Questions Section -->
|
|
<section class="py-16 px-6 md:px-12 lg:px-24 bg-background-light">
|
|
<div class="container mx-auto">
|
|
<div class="max-w-3xl mx-auto text-center">
|
|
<h2 class="text-2xl font-bold text-text mb-4">
|
|
{{ t('faq.more.title') || '还有更多问题?' }}
|
|
</h2>
|
|
<div class="flex flex-col sm:flex-row justify-center gap-4 mt-8">
|
|
<router-link
|
|
to="/contact"
|
|
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"
|
|
>
|
|
<Icon icon="carbon:email" width="20" height="20" />
|
|
{{ t('faq.more.contact') || '联系我们' }}
|
|
</router-link>
|
|
<router-link
|
|
to="/community"
|
|
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"
|
|
>
|
|
<Icon icon="carbon:group" width="20" height="20" />
|
|
{{ t('faq.more.community') || '加入社区' }}
|
|
</router-link>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
</div>
|
|
</template>
|
|
|
|
<style scoped>
|
|
button {
|
|
outline: none;
|
|
}
|
|
|
|
.btn-hover-float {
|
|
transition: transform 0.3s ease;
|
|
}
|
|
|
|
.btn-hover-float:hover {
|
|
transform: translateY(-3px);
|
|
}
|
|
|
|
.btn-hover-glow {
|
|
position: relative;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.btn-hover-glow::after {
|
|
content: '';
|
|
position: absolute;
|
|
top: -50%;
|
|
left: -50%;
|
|
width: 200%;
|
|
height: 200%;
|
|
background: radial-gradient(circle, rgba(255,255,255,0.2) 0%, rgba(255,255,255,0) 70%);
|
|
opacity: 0;
|
|
transform: scale(0.5);
|
|
transition: opacity 0.3s ease, transform 0.3s ease;
|
|
}
|
|
|
|
.btn-hover-glow:hover::after {
|
|
opacity: 1;
|
|
transform: scale(1);
|
|
}
|
|
|
|
.btn-hover-shadow {
|
|
transition: box-shadow 0.3s ease;
|
|
}
|
|
|
|
.btn-hover-shadow:hover {
|
|
box-shadow: 0 4px 12px rgba(var(--primary-light-rgb), 0.15);
|
|
}
|
|
</style>
|