128 lines
5.4 KiB
Vue
128 lines
5.4 KiB
Vue
<script setup lang="ts">
|
||
import type { AboutConfig, AboutFeature } from '~/types/site';
|
||
import { parseJson } from '~/utils/parseJson';
|
||
|
||
const { t, locale } = useI18n();
|
||
const localePath = useLocalePath();
|
||
const { getPageConfig } = useSiteApi();
|
||
|
||
// SEO:关于我们
|
||
await usePageSeo('about', undefined, `${t('app.name')} - ${t('nav.about')}`);
|
||
|
||
const config = ref<AboutConfig | null>(null);
|
||
const loading = ref(true);
|
||
|
||
onMounted(async () => {
|
||
try {
|
||
config.value = await getPageConfig<AboutConfig>('about');
|
||
} catch {
|
||
config.value = null;
|
||
} finally {
|
||
loading.value = false;
|
||
}
|
||
});
|
||
|
||
// ===== 按语言取值 =====
|
||
const heroTitle = computed(() => (locale.value === 'en' ? config.value?.hero_title_en : config.value?.hero_title_zh) || (locale.value === 'en' ? 'About Us' : '关于我们'));
|
||
const heroSubtitle = computed(() => (locale.value === 'en' ? config.value?.hero_subtitle_en : config.value?.hero_subtitle_zh) || '');
|
||
const s1Title = computed(() => (locale.value === 'en' ? config.value?.s1_title_en : config.value?.s1_title_zh) || '');
|
||
const s1Content = computed(() => (locale.value === 'en' ? config.value?.s1_content_en : config.value?.s1_content_zh) || '');
|
||
const s1Cta = computed(() => (locale.value === 'en' ? config.value?.s1_cta_en : config.value?.s1_cta_zh) || '');
|
||
const s2Title = computed(() => (locale.value === 'en' ? config.value?.s2_title_en : config.value?.s2_title_zh) || '');
|
||
const s3Title = computed(() => (locale.value === 'en' ? config.value?.s3_title_en : config.value?.s3_title_zh) || '');
|
||
const s3Cta = computed(() => (locale.value === 'en' ? config.value?.s3_cta_en : config.value?.s3_cta_zh) || '');
|
||
|
||
// s2_features:JSON 字符串或数组
|
||
const features = computed<AboutFeature[]>(() =>
|
||
parseJson<AboutFeature[]>(config.value?.s2_features, [])
|
||
);
|
||
|
||
function featureTitle(f: AboutFeature): string {
|
||
return locale.value === 'en' ? f.title_en : f.title_zh;
|
||
}
|
||
function featureDesc(f: AboutFeature): string {
|
||
return locale.value === 'en' ? f.desc_en : f.desc_zh;
|
||
}
|
||
|
||
// 是否需要展示各区块
|
||
const hasS1 = computed(() => Boolean(s1Title.value || s1Content.value || config.value?.s1_image_url));
|
||
const hasS2 = computed(() => Boolean(s2Title.value && features.value.length));
|
||
const hasS3 = computed(() => Boolean(s3Title.value || s3Cta.value));
|
||
</script>
|
||
|
||
<template>
|
||
<div>
|
||
<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>
|
||
<!-- S1:图文介绍(上文下图) -->
|
||
<section v-if="hasS1" class="section-container py-64px md:py-96px">
|
||
<div class="flex flex-col gap-40px md:gap-48px">
|
||
<div class="mx-auto max-w-800px flex flex-col gap-20px">
|
||
<h2 v-if="s1Title" class="text-2xl md:text-3xl font-bold text-gray-900 dark:text-white">{{ s1Title }}</h2>
|
||
<!-- 富文本(dashboard 端为 HTML 内容) -->
|
||
<div v-if="s1Content" class="text-base md:text-lg text-gray-600 dark:text-gray-300 leading-relaxed" v-html="s1Content" />
|
||
<div v-if="s1Cta">
|
||
<NuxtLink
|
||
:to="localePath(config?.s1_cta_url || '/contact')"
|
||
class="inline-block px-28px py-12px rounded-8px bg-primary text-white text-sm font-medium hover:opacity-90 transition-opacity"
|
||
>
|
||
{{ s1Cta }}
|
||
</NuxtLink>
|
||
</div>
|
||
</div>
|
||
<div v-if="config?.s1_image_url">
|
||
<img
|
||
:src="config.s1_image_url"
|
||
:alt="s1Title"
|
||
class="w-full rounded-16px shadow-lg object-cover aspect-[16/9]"
|
||
/>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- S2:核心优势 -->
|
||
<section v-if="hasS2" class="bg-gray-50 dark:bg-dark-900 border-y border-gray-100 dark:border-dark-700">
|
||
<div class="section-container py-64px md:py-96px">
|
||
<h2 class="text-center text-2xl md:text-3xl font-bold text-gray-900 dark:text-white mb-48px">{{ s2Title }}</h2>
|
||
<div class="grid gap-24px sm:grid-cols-2 lg:grid-cols-3">
|
||
<div
|
||
v-for="(f, idx) in features"
|
||
:key="idx"
|
||
class="p-24px rounded-16px bg-white dark:bg-dark-800 border border-gray-100 dark:border-dark-700 hover:shadow-lg transition-shadow"
|
||
>
|
||
<h3 class="font-semibold text-gray-900 dark:text-white mb-8px">{{ featureTitle(f) }}</h3>
|
||
<p class="text-sm text-gray-500 dark:text-gray-400 leading-relaxed">{{ featureDesc(f) }}</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- S3:CTA -->
|
||
<section v-if="hasS3" class="section-container py-64px md:py-96px text-center">
|
||
<h2 class="text-2xl md:text-3xl font-bold text-gray-900 dark:text-white mb-32px">{{ s3Title }}</h2>
|
||
<NuxtLink
|
||
v-if="s3Cta"
|
||
:to="localePath(config?.s3_cta_url || '/contact')"
|
||
class="inline-block px-32px py-12px rounded-8px bg-primary text-white text-sm font-medium hover:opacity-90 transition-opacity"
|
||
>
|
||
{{ s3Cta }}
|
||
</NuxtLink>
|
||
</section>
|
||
|
||
<!-- 空配置提示 -->
|
||
<div v-if="!hasS1 && !hasS2 && !hasS3" class="section-container py-80px text-center">
|
||
<p class="text-gray-400">{{ t('common.empty') }}</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</template>
|