官网开发中 0803
This commit is contained in:
@@ -0,0 +1,113 @@
|
||||
<script setup lang="ts">
|
||||
import type { ProductListConfig, ProductSeries } from '~/types/site';
|
||||
|
||||
const { t, locale } = useI18n();
|
||||
const localePath = useLocalePath();
|
||||
const siteStore = useSiteStore();
|
||||
const { getPageConfig } = useSiteApi();
|
||||
|
||||
// SEO:产品列表页
|
||||
await usePageSeo('products', undefined, `${t('app.name')} - ${t('nav.products')}`);
|
||||
|
||||
// 页面配置(hero + 列数),未配置时用默认值
|
||||
const config = ref<ProductListConfig | null>(null);
|
||||
const loading = ref(true);
|
||||
|
||||
onMounted(async () => {
|
||||
try {
|
||||
const [cfg] = await Promise.all([getPageConfig<ProductListConfig>('product-list')]);
|
||||
config.value = cfg;
|
||||
} catch {
|
||||
config.value = null;
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
});
|
||||
|
||||
// 产品系列(store 已在 app 启动时加载)
|
||||
const { productSeries } = storeToRefs(siteStore);
|
||||
|
||||
const maxColumns = computed(() => Math.min(Math.max(config.value?.max_columns || 3, 1), 4));
|
||||
|
||||
// 有产品的系列才展示
|
||||
const visibleSeries = computed(() =>
|
||||
(productSeries.value || []).filter((s) => (s.products || []).length > 0)
|
||||
);
|
||||
|
||||
const heroTitle = computed(() => (locale.value === 'en' ? config.value?.hero_title_en : config.value?.hero_title_zh) || (locale.value === 'en' ? 'Products' : '产品中心'));
|
||||
const heroSubtitle = computed(() => (locale.value === 'en' ? config.value?.hero_subtitle_en : config.value?.hero_subtitle_zh) || '');
|
||||
const heroOverline = computed(() => (locale.value === 'en' ? config.value?.hero_overline_en : config.value?.hero_overline_zh) || '');
|
||||
|
||||
function seriesName(s: ProductSeries): string {
|
||||
return locale.value === 'en' ? s.name_en : s.name_zh;
|
||||
}
|
||||
|
||||
function seriesSubtitle(s: ProductSeries): string {
|
||||
return locale.value === 'en' ? s.subtitle_en : s.subtitle_zh;
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<PageHero
|
||||
:title="heroTitle"
|
||||
:subtitle="heroSubtitle || undefined"
|
||||
:overline="heroOverline || 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-if="visibleSeries.length" class="section-container py-64px md:py-80px flex flex-col gap-80px">
|
||||
<section v-for="series in visibleSeries" :key="series.id">
|
||||
<div class="mb-32px flex flex-col gap-8px">
|
||||
<h2 class="text-2xl md:text-3xl font-bold text-gray-900 dark:text-white">{{ seriesName(series) }}</h2>
|
||||
<p v-if="seriesSubtitle(series)" class="text-gray-500 dark:text-gray-400">{{ seriesSubtitle(series) }}</p>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="grid gap-24px"
|
||||
:class="{
|
||||
'grid-cols-1': maxColumns === 1,
|
||||
'sm:grid-cols-2': maxColumns === 2,
|
||||
'sm:grid-cols-2 lg:grid-cols-3': maxColumns === 3,
|
||||
'sm:grid-cols-2 lg:grid-cols-4': maxColumns === 4
|
||||
}"
|
||||
>
|
||||
<NuxtLink
|
||||
v-for="product in series.products"
|
||||
:key="product.id"
|
||||
:to="localePath(`/products/${product.slug}`)"
|
||||
class="group flex flex-col rounded-16px border border-gray-100 dark:border-dark-700 overflow-hidden bg-white dark:bg-dark-800 hover:shadow-xl hover:-translate-y-4px transition-all duration-300"
|
||||
>
|
||||
<div class="aspect-[16/10] bg-gray-50 dark:bg-dark-700 overflow-hidden">
|
||||
<img
|
||||
v-if="product.cover_url"
|
||||
:src="product.cover_url"
|
||||
:alt="locale === 'en' ? product.name_en : product.name_zh"
|
||||
class="w-full h-full object-cover group-hover:scale-105 transition-transform duration-500"
|
||||
/>
|
||||
<div v-else class="w-full h-full flex-center text-gray-300 dark:text-gray-600 text-4xl">🎧</div>
|
||||
</div>
|
||||
<div class="p-20px flex flex-col gap-8px flex-1">
|
||||
<h3 class="text-lg font-semibold text-gray-900 dark:text-white group-hover:text-primary transition-colors">
|
||||
{{ locale === 'en' ? product.name_en : product.name_zh }}
|
||||
</h3>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400 line-clamp-2">
|
||||
{{ locale === 'en' ? product.intro_en : product.intro_zh }}
|
||||
</p>
|
||||
</div>
|
||||
</NuxtLink>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<div v-else class="section-container py-80px text-center">
|
||||
<p class="text-gray-400">{{ t('common.empty') }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
Reference in New Issue
Block a user