Files
admin/www/app/pages/support.vue
T

177 lines
7.1 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<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 />
<!-- S4CTA -->
<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>