Files
admin/www/app/pages/about.vue
T
2026-08-10 18:50:14 +08:00

128 lines
5.4 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 { 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_featuresJSON 字符串或数组
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>
<!-- S3CTA -->
<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>