177 lines
7.1 KiB
Vue
177 lines
7.1 KiB
Vue
<script setup lang="ts">
|
||
import type { SupportConfig, FaqCategory, FaqItem, SupportDownload, SupportContactItem } from '~/types/site';
|
||
import { parseJson } from '~/utils/parseJson';
|
||
|
||
const { t, locale } = useI18n();
|
||
const localePath = useLocalePath();
|
||
const { getPageConfig } = useSiteApi();
|
||
|
||
// SEO:技术支持
|
||
await usePageSeo('support', undefined, `${t('app.name')} - ${t('nav.support')}`);
|
||
|
||
const config = ref<SupportConfig | null>(null);
|
||
const loading = ref(true);
|
||
|
||
onMounted(async () => {
|
||
try {
|
||
config.value = await getPageConfig<SupportConfig>('support');
|
||
} catch {
|
||
config.value = null;
|
||
} finally {
|
||
loading.value = false;
|
||
}
|
||
});
|
||
|
||
// ===== JSON 字段解析 =====
|
||
const faqCategories = computed<FaqCategory[]>(() =>
|
||
parseJson<FaqCategory[]>(config.value?.s1_faq_categories, [])
|
||
);
|
||
const downloads = computed<SupportDownload[]>(() =>
|
||
parseJson<SupportDownload[]>(config.value?.s2_downloads, [])
|
||
);
|
||
const contacts = computed<SupportContactItem[]>(() =>
|
||
parseJson<SupportContactItem[]>(config.value?.s3_contact, [])
|
||
);
|
||
|
||
// ===== 按语言取值 =====
|
||
const heroTitle = computed(() => (locale.value === 'en' ? config.value?.hero_title_en : config.value?.hero_title_zh) || (locale.value === 'en' ? 'Support' : '技术支持'));
|
||
const heroSubtitle = computed(() => (locale.value === 'en' ? config.value?.hero_subtitle_en : config.value?.hero_subtitle_zh) || '');
|
||
const s4Cta = computed(() => (locale.value === 'en' ? config.value?.s4_cta_en : config.value?.s4_cta_zh) || '');
|
||
|
||
function catTitle(c: FaqCategory): string {
|
||
return locale.value === 'en' ? c.title_en : c.title_zh;
|
||
}
|
||
function qText(q: FaqItem): string {
|
||
return locale.value === 'en' ? q.question_en : q.question_zh;
|
||
}
|
||
function aText(q: FaqItem): string {
|
||
return locale.value === 'en' ? q.answer_en : q.answer_zh;
|
||
}
|
||
function downloadName(d: SupportDownload): string {
|
||
return locale.value === 'en' ? d.name_en : d.name_zh;
|
||
}
|
||
function contactLabel(c: SupportContactItem): string {
|
||
return locale.value === 'en' ? c.label_en : c.label_zh;
|
||
}
|
||
function contactValue(c: SupportContactItem): string {
|
||
return locale.value === 'en' ? c.value_en : c.value_zh;
|
||
}
|
||
|
||
// ===== FAQ 手风琴展开状态(分类 -> 展开的问答 index) =====
|
||
const openFaq = ref<Record<number, number>>({});
|
||
function toggleFaq(catIdx: number, itemIdx: number) {
|
||
if (openFaq.value[catIdx] === itemIdx) {
|
||
delete openFaq.value[catIdx];
|
||
} else {
|
||
openFaq.value[catIdx] = itemIdx;
|
||
}
|
||
}
|
||
|
||
const hasFaq = computed(() => faqCategories.value.length > 0);
|
||
const hasDownloads = computed(() => downloads.value.length > 0);
|
||
const hasContacts = computed(() => contacts.value.length > 0);
|
||
</script>
|
||
|
||
<template>
|
||
<!-- 内容容器加宽:--sec-cw 可继承,页内所有 section-container 生效(全局默认 1200px 偏窄) -->
|
||
<div style="--sec-cw: 1400px">
|
||
<PageHero
|
||
:title="heroTitle"
|
||
:subtitle="heroSubtitle || undefined"
|
||
:bg-url="config?.hero_bg_url || undefined"
|
||
/>
|
||
|
||
<div v-if="loading" class="py-80px text-center text-sm text-gray-400">
|
||
{{ t('common.loading') }}
|
||
</div>
|
||
|
||
<div v-else class="section-container py-64px md:py-80px flex flex-col gap-64px md:gap-80px">
|
||
<!-- S1:常见问题 -->
|
||
<section v-if="hasFaq">
|
||
<h2 class="text-2xl md:text-3xl font-bold text-gray-900 dark:text-white mb-32px">{{ t('support.faq') }}</h2>
|
||
<div class="flex flex-col gap-24px">
|
||
<div v-for="(cat, catIdx) in faqCategories" :key="catIdx">
|
||
<h3 class="text-lg font-semibold text-gray-700 dark:text-gray-200 mb-16px">{{ catTitle(cat) }}</h3>
|
||
<div class="flex flex-col gap-12px">
|
||
<div
|
||
v-for="(item, itemIdx) in cat.items"
|
||
:key="itemIdx"
|
||
class="rounded-12px border border-gray-100 dark:border-dark-700 overflow-hidden bg-white dark:bg-dark-800"
|
||
>
|
||
<button
|
||
class="w-full flex items-center justify-between gap-16px px-20px py-16px text-left hover:bg-gray-50 dark:hover:bg-dark-700 transition-colors"
|
||
@click="toggleFaq(catIdx, itemIdx)"
|
||
>
|
||
<span class="font-medium text-gray-900 dark:text-white">{{ qText(item) }}</span>
|
||
<span
|
||
class="text-gray-400 transition-transform duration-200 shrink-0"
|
||
:class="openFaq[catIdx] === itemIdx ? 'rotate-180' : ''"
|
||
>▾</span>
|
||
</button>
|
||
<div v-if="openFaq[catIdx] === itemIdx" class="px-20px pb-16px text-sm text-gray-600 dark:text-gray-300 leading-relaxed whitespace-pre-line">
|
||
{{ aText(item) }}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- S2:资料下载 -->
|
||
<section v-if="hasDownloads">
|
||
<h2 class="text-2xl md:text-3xl font-bold text-gray-900 dark:text-white mb-32px">{{ t('support.downloads') }}</h2>
|
||
<div class="grid gap-16px sm:grid-cols-2 lg:grid-cols-3">
|
||
<a
|
||
v-for="(d, idx) in downloads"
|
||
:key="idx"
|
||
:href="d.file_url"
|
||
target="_blank"
|
||
rel="noopener noreferrer"
|
||
class="flex items-center gap-16px p-20px rounded-12px border border-gray-100 dark:border-dark-700 bg-white dark:bg-dark-800 hover:border-primary hover:shadow-md transition-all"
|
||
>
|
||
<span class="text-2xl">📄</span>
|
||
<div class="flex flex-col gap-4px min-w-0 flex-1">
|
||
<span class="font-medium text-gray-900 dark:text-white truncate">{{ downloadName(d) }}</span>
|
||
<span v-if="d.file_size" class="text-xs text-gray-400">{{ d.file_size }}</span>
|
||
</div>
|
||
<span class="text-primary text-lg shrink-0">↓</span>
|
||
</a>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- S3:联系方式 -->
|
||
<section v-if="hasContacts">
|
||
<h2 class="text-2xl md:text-3xl font-bold text-gray-900 dark:text-white mb-32px">{{ t('support.contact') }}</h2>
|
||
<div class="grid gap-16px sm:grid-cols-2 lg:grid-cols-3">
|
||
<div
|
||
v-for="(c, idx) in contacts"
|
||
:key="idx"
|
||
class="p-20px rounded-12px border border-gray-100 dark:border-dark-700 bg-white dark:bg-dark-800"
|
||
>
|
||
<p class="text-xs text-gray-400 mb-8px">{{ contactLabel(c) }}</p>
|
||
<p class="font-medium text-gray-900 dark:text-white break-all">{{ contactValue(c) }}</p>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- S-FB:问题反馈(提交 + 查询) -->
|
||
<SupportFeedback />
|
||
|
||
<!-- S4:CTA -->
|
||
<section v-if="s4Cta" class="text-center py-32px">
|
||
<NuxtLink
|
||
:to="localePath(config?.s4_cta_url || '/contact')"
|
||
class="inline-block px-36px py-14px rounded-8px bg-primary text-white font-medium hover:opacity-90 transition-opacity"
|
||
>
|
||
{{ s4Cta }}
|
||
</NuxtLink>
|
||
</section>
|
||
|
||
<!-- 空配置提示 -->
|
||
<div v-if="!hasFaq && !hasDownloads && !hasContacts && !s4Cta" class="py-80px text-center">
|
||
<p class="text-gray-400">{{ t('common.empty') }}</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</template>
|