feat(SSR): 将产品页改为SSR
This commit is contained in:
@ -1,90 +1,108 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="page-container">
|
<div class="page-container">
|
||||||
<div v-if="production">
|
<div v-if="!pending">
|
||||||
<!-- 面包屑导航 -->
|
<div v-if="production">
|
||||||
<el-breadcrumb class="breadcrumb" separator="/">
|
<!-- 面包屑导航 -->
|
||||||
<el-breadcrumb-item class="text-md opacity-50">
|
<el-breadcrumb class="breadcrumb" separator="/">
|
||||||
<NuxtLink :to="$localePath('/')">{{
|
<el-breadcrumb-item class="text-md opacity-50">
|
||||||
$t('navigation.home')
|
<NuxtLink :to="$localePath('/')">{{
|
||||||
}}</NuxtLink>
|
$t('navigation.home')
|
||||||
</el-breadcrumb-item>
|
}}</NuxtLink>
|
||||||
<el-breadcrumb-item class="text-md opacity-50">
|
</el-breadcrumb-item>
|
||||||
<NuxtLink :to="$localePath('/productions')">{{
|
<el-breadcrumb-item class="text-md opacity-50">
|
||||||
$t('navigation.productions')
|
<NuxtLink :to="$localePath('/productions')">{{
|
||||||
}}</NuxtLink>
|
$t('navigation.productions')
|
||||||
</el-breadcrumb-item>
|
}}</NuxtLink>
|
||||||
<el-breadcrumb-item class="text-md opactiy-50">{{
|
</el-breadcrumb-item>
|
||||||
production.title
|
<el-breadcrumb-item class="text-md opactiy-50">{{
|
||||||
}}</el-breadcrumb-item>
|
production.title
|
||||||
</el-breadcrumb>
|
}}</el-breadcrumb-item>
|
||||||
|
</el-breadcrumb>
|
||||||
|
|
||||||
<!-- 产品详情内容 -->
|
<!-- 产品详情内容 -->
|
||||||
<div class="production-header">
|
<div class="production-header">
|
||||||
<div class="production-image">
|
<div class="production-image">
|
||||||
<el-image
|
<el-image
|
||||||
v-if="production.production_images.length <= 1"
|
v-if="production.production_images.length <= 1"
|
||||||
:src="useStrapiMedia(production?.cover?.url || '')"
|
:src="useStrapiMedia(production?.cover?.url || '')"
|
||||||
:alt="production.title"
|
:alt="production.title"
|
||||||
fit="contain"
|
fit="contain"
|
||||||
/>
|
/>
|
||||||
<el-carousel
|
<el-carousel
|
||||||
v-else
|
v-else
|
||||||
class="production-carousel"
|
class="production-carousel"
|
||||||
height="500px"
|
height="500px"
|
||||||
:autoplay="false"
|
:autoplay="false"
|
||||||
:loop="false"
|
:loop="false"
|
||||||
arrow="always"
|
arrow="always"
|
||||||
>
|
|
||||||
<el-carousel-item
|
|
||||||
v-for="(item, index) in production.production_images || []"
|
|
||||||
:key="index"
|
|
||||||
>
|
>
|
||||||
<div class="production-carousel-item">
|
<el-carousel-item
|
||||||
<el-image
|
v-for="(item, index) in production.production_images || []"
|
||||||
:src="useStrapiMedia(item.url || '')"
|
:key="index"
|
||||||
:alt="item.alternativeText || production.title"
|
>
|
||||||
fit="contain"
|
<div class="production-carousel-item">
|
||||||
lazy
|
<el-image
|
||||||
/>
|
:src="useStrapiMedia(item.url || '')"
|
||||||
<p v-if="item.caption" class="production-image-caption">
|
:alt="item.alternativeText || production.title"
|
||||||
{{ item.caption }}
|
fit="contain"
|
||||||
</p>
|
lazy
|
||||||
</div>
|
/>
|
||||||
</el-carousel-item>
|
<p v-if="item.caption" class="production-image-caption">
|
||||||
</el-carousel>
|
{{ item.caption }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</el-carousel-item>
|
||||||
|
</el-carousel>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="production-info">
|
||||||
|
<h1>{{ production.title }}</h1>
|
||||||
|
<p class="summary">{{ production.summary }}</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="production-info">
|
|
||||||
<h1>{{ production.title }}</h1>
|
<!-- 产品详细描述 -->
|
||||||
<p class="summary">{{ production.summary }}</p>
|
<div class="production-content">
|
||||||
|
<el-tabs v-model="activeName" class="production-tabs" stretch>
|
||||||
|
<el-tab-pane label="产品详情" name="details">
|
||||||
|
<markdown-renderer
|
||||||
|
:content="production.production_details || ''"
|
||||||
|
/>
|
||||||
|
</el-tab-pane>
|
||||||
|
<el-tab-pane label="技术规格" name="specs">
|
||||||
|
<spec-table :data="production.production_specs" />
|
||||||
|
</el-tab-pane>
|
||||||
|
<el-tab-pane label="常见问题" name="faq">
|
||||||
|
<question-list :questions="production.questions" />
|
||||||
|
</el-tab-pane>
|
||||||
|
<el-tab-pane label="相关文档" name="documents">
|
||||||
|
<document-list
|
||||||
|
:documents="
|
||||||
|
production.production_documents.map(
|
||||||
|
(item) => item.document
|
||||||
|
) || []
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
</el-tab-pane>
|
||||||
|
</el-tabs>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<!-- 未找到产品 -->
|
||||||
<!-- 产品详细描述 -->
|
<div v-else class="not-found">
|
||||||
<div class="production-content">
|
<el-result
|
||||||
<el-tabs v-model="activeName" class="production-tabs" stretch>
|
icon="warning"
|
||||||
<el-tab-pane label="产品详情" name="details">
|
:title="$t('product-not-found')"
|
||||||
<markdown-renderer :content="production.production_details || ''" />
|
:sub-title="$t('product-not-found-desc')"
|
||||||
</el-tab-pane>
|
>
|
||||||
<el-tab-pane label="技术规格" name="specs">
|
<template #extra>
|
||||||
<spec-table :data="production.production_specs" />
|
<el-button type="primary" @click="$router.push('/productions')">
|
||||||
</el-tab-pane>
|
{{ $t('back-to-productions') }}
|
||||||
<el-tab-pane label="常见问题" name="faq">
|
</el-button>
|
||||||
<question-list :questions="production.questions" />
|
</template>
|
||||||
</el-tab-pane>
|
</el-result>
|
||||||
<el-tab-pane label="相关文档" name="documents">
|
|
||||||
<document-list
|
|
||||||
:documents="
|
|
||||||
production.production_documents.map((item) => item.document) ||
|
|
||||||
[]
|
|
||||||
"
|
|
||||||
/>
|
|
||||||
</el-tab-pane>
|
|
||||||
</el-tabs>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div v-else class="loading">
|
||||||
<!-- 加载状态 -->
|
|
||||||
<div v-else-if="pending" class="loading">
|
|
||||||
<el-skeleton style="--el-skeleton-circle-size: 400px">
|
<el-skeleton style="--el-skeleton-circle-size: 400px">
|
||||||
<template #template>
|
<template #template>
|
||||||
<el-skeleton-item variant="circle" />
|
<el-skeleton-item variant="circle" />
|
||||||
@ -92,21 +110,6 @@
|
|||||||
</el-skeleton>
|
</el-skeleton>
|
||||||
<el-skeleton :rows="5" animated />
|
<el-skeleton :rows="5" animated />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 未找到产品 -->
|
|
||||||
<div v-else class="not-found">
|
|
||||||
<el-result
|
|
||||||
icon="warning"
|
|
||||||
:title="$t('product-not-found')"
|
|
||||||
:sub-title="$t('product-not-found-desc')"
|
|
||||||
>
|
|
||||||
<template #extra>
|
|
||||||
<el-button type="primary" @click="$router.push('/productions')">
|
|
||||||
{{ $t('back-to-productions') }}
|
|
||||||
</el-button>
|
|
||||||
</template>
|
|
||||||
</el-result>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -116,51 +119,46 @@
|
|||||||
const { getStrapiLocale } = useLocalizations();
|
const { getStrapiLocale } = useLocalizations();
|
||||||
const strapiLocale = getStrapiLocale();
|
const strapiLocale = getStrapiLocale();
|
||||||
|
|
||||||
const production = ref<Production | null>(null);
|
|
||||||
const pending = ref(true);
|
|
||||||
|
|
||||||
const activeName = ref('details'); // 默认选中概览标签
|
|
||||||
|
|
||||||
// 获取路由参数(slug 或 id)
|
// 获取路由参数(slug 或 id)
|
||||||
const documentId = computed(() => route.params.slug as string);
|
const documentId = computed(() => route.params.slug as string);
|
||||||
|
|
||||||
onMounted(async () => {
|
const { data, pending, error } = useAsyncData(
|
||||||
try {
|
() => `production-${documentId.value}`,
|
||||||
const response = await findOne<Production>(
|
() =>
|
||||||
'productions',
|
findOne<Production>('productions', documentId.value, {
|
||||||
documentId.value,
|
populate: {
|
||||||
{
|
production_specs: {
|
||||||
populate: {
|
populate: '*',
|
||||||
production_specs: {
|
|
||||||
populate: '*',
|
|
||||||
},
|
|
||||||
production_images: {
|
|
||||||
populate: '*',
|
|
||||||
},
|
|
||||||
cover: {
|
|
||||||
populate: '*',
|
|
||||||
},
|
|
||||||
questions: {
|
|
||||||
populate: '*',
|
|
||||||
},
|
|
||||||
production_documents: {
|
|
||||||
populate: 'document',
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
locale: strapiLocale,
|
production_images: {
|
||||||
}
|
populate: '*',
|
||||||
);
|
},
|
||||||
if (response.data) {
|
cover: {
|
||||||
const item = response.data;
|
populate: '*',
|
||||||
production.value = {
|
},
|
||||||
...item,
|
questions: {
|
||||||
};
|
populate: '*',
|
||||||
console.log('Fetched production:', production.value);
|
},
|
||||||
}
|
production_documents: {
|
||||||
} catch (error) {
|
populate: 'document',
|
||||||
console.error('Failed to fetch production:', error);
|
},
|
||||||
} finally {
|
},
|
||||||
pending.value = false;
|
locale: strapiLocale,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
const production = computed(() => data.value?.data ?? null);
|
||||||
|
|
||||||
|
const activeName = ref('details'); // 默认选中概览标签
|
||||||
|
|
||||||
|
watch(error, (value) => {
|
||||||
|
if (value) {
|
||||||
|
console.error('数据获取失败: ', value);
|
||||||
|
throw createError({
|
||||||
|
statusCode: 500,
|
||||||
|
statusMessage: 'CMS数据获取失败',
|
||||||
|
fatal: true,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -15,7 +15,7 @@
|
|||||||
</el-breadcrumb-item>
|
</el-breadcrumb-item>
|
||||||
</el-breadcrumb>
|
</el-breadcrumb>
|
||||||
</div>
|
</div>
|
||||||
<div class="page-content">
|
<div v-if="!pending" class="page-content">
|
||||||
<div class="productions-container">
|
<div class="productions-container">
|
||||||
<el-collapse v-model="activeNames" class="production-collapse">
|
<el-collapse v-model="activeNames" class="production-collapse">
|
||||||
<el-collapse-item
|
<el-collapse-item
|
||||||
@ -38,18 +38,44 @@
|
|||||||
</el-collapse>
|
</el-collapse>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div v-else>
|
||||||
|
<el-skeleton :rows="6" animated />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
const { find } = useStrapi();
|
const { find } = useStrapi();
|
||||||
const { getStrapiLocale } = useLocalizations();
|
const { getStrapiLocale } = useLocalizations();
|
||||||
|
|
||||||
const strapiLocale = getStrapiLocale();
|
const strapiLocale = getStrapiLocale();
|
||||||
|
|
||||||
|
const { data, pending, error } = useAsyncData(
|
||||||
|
'productions',
|
||||||
|
() =>
|
||||||
|
find<Production>('productions', {
|
||||||
|
populate: {
|
||||||
|
cover: {
|
||||||
|
populate: '*',
|
||||||
|
},
|
||||||
|
production_type: {
|
||||||
|
populate: '*',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
filters: {
|
||||||
|
show_in_production_list: {
|
||||||
|
$eq: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
locale: strapiLocale,
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
lazy: true,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
const activeNames = ref<string[]>([]);
|
const activeNames = ref<string[]>([]);
|
||||||
|
|
||||||
const productions = ref<Production[]>([]);
|
const productions = computed(() => data.value?.data ?? []);
|
||||||
|
|
||||||
// 按类型分组
|
// 按类型分组
|
||||||
// 兼容 production_type 既可能为对象也可能为字符串
|
// 兼容 production_type 既可能为对象也可能为字符串
|
||||||
@ -72,34 +98,32 @@
|
|||||||
return groups;
|
return groups;
|
||||||
});
|
});
|
||||||
|
|
||||||
onMounted(async () => {
|
watch(groupedProductions, () => {
|
||||||
try {
|
if (groupedProductions.value) {
|
||||||
const response = await find<Production>('productions', {
|
activeNames.value = [
|
||||||
populate: {
|
...Object.keys(groupedProductions.value),
|
||||||
cover: {
|
'no-category',
|
||||||
populate: '*',
|
];
|
||||||
},
|
}
|
||||||
production_type: {
|
});
|
||||||
populate: '*',
|
|
||||||
},
|
watch(error, (value) => {
|
||||||
},
|
if (value) {
|
||||||
filters: {
|
console.error('数据获取失败: ', value);
|
||||||
show_in_production_list: {
|
throw createError({
|
||||||
$eq: true, // 只获取在产品列表中显示的产品
|
statusCode: 500,
|
||||||
},
|
statusMessage: 'CMS数据获取失败',
|
||||||
},
|
fatal: true,
|
||||||
locale: strapiLocale,
|
|
||||||
});
|
});
|
||||||
productions.value = response.data.map((item: Production) => ({
|
}
|
||||||
...item,
|
});
|
||||||
// 保持 production_type 原始类型,兼容对象或字符串
|
|
||||||
production_type: item.production_type,
|
onMounted(() => {
|
||||||
}));
|
if (groupedProductions.value) {
|
||||||
// 默认展开所有分组
|
activeNames.value = [
|
||||||
activeNames.value = Object.keys(groupedProductions.value);
|
...Object.keys(groupedProductions.value),
|
||||||
activeNames.value.push('no-category'); // 展开未分类
|
'no-category',
|
||||||
} catch (error) {
|
];
|
||||||
console.error('Failed to fetch productions:', error);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
Reference in New Issue
Block a user