2026-08-03 19:09:03 +08:00
|
|
|
|
<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>
|
2026-08-10 18:50:14 +08:00
|
|
|
|
<!-- S1:图文介绍(上文下图) -->
|
2026-08-03 19:09:03 +08:00
|
|
|
|
<section v-if="hasS1" class="section-container py-64px md:py-96px">
|
2026-08-10 18:50:14 +08:00
|
|
|
|
<div class="flex flex-col gap-40px md:gap-48px">
|
|
|
|
|
|
<div class="mx-auto max-w-800px flex flex-col gap-20px">
|
2026-08-03 19:09:03 +08:00
|
|
|
|
<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>
|
2026-08-10 18:50:14 +08:00
|
|
|
|
<div v-if="config?.s1_image_url">
|
2026-08-03 19:09:03 +08:00
|
|
|
|
<img
|
|
|
|
|
|
:src="config.s1_image_url"
|
|
|
|
|
|
:alt="s1Title"
|
2026-08-10 18:50:14 +08:00
|
|
|
|
class="w-full rounded-16px shadow-lg object-cover aspect-[16/9]"
|
2026-08-03 19:09:03 +08:00
|
|
|
|
/>
|
|
|
|
|
|
</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>
|