Files
jinshen-website/app/pages/productions/[...slug].vue
R2m1liA e0c16c6391 refactor(production): 重构产品详情页代码
- 将问题列表调整为单独的Vue组件
2025-09-03 14:25:18 +08:00

254 lines
6.3 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.

<template>
<div class="page-container">
<div v-if="production">
<!-- 面包屑导航 -->
<el-breadcrumb class="breadcrumb" separator="/">
<el-breadcrumb-item class="text-md opacity-50">
<NuxtLink :to="$localePath('/')">{{ $t('navigation.home') }}</NuxtLink>
</el-breadcrumb-item>
<el-breadcrumb-item class="text-md opacity-50">
<NuxtLink :to="$localePath('/productions')">{{ $t('navigation.productions') }}</NuxtLink>
</el-breadcrumb-item>
<el-breadcrumb-item class="text-md opactiy-50">{{ production.title }}</el-breadcrumb-item>
</el-breadcrumb>
<!-- 产品详情内容 -->
<div class="production-header">
<div class="production-image">
<el-image :src="useStrapiMedia(production?.cover?.url || '')" :alt="production.title" fit="contain" />
</div>
<div class="production-info">
<h1>{{ production.title }}</h1>
<p class="summary">{{ production.summary }}</p>
</div>
</div>
<!-- 产品详细描述 -->
<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">
<div class="document-list">
<el-card v-for="(doc, index) in production.documents" :key="index" class="document-card">
<div class="document-info">
<h3>{{ doc.caption || doc.name }}</h3>
<div class="document-content">
<span v-if="doc.size" class="document-meta">大小: {{ formatFileSize(doc.size) }}</span>
<span v-if="doc.ext" class="document-meta">格式: {{ formatFileExtension(doc.ext) }}</span>
<el-button
class="download-button" type="primary"
@click="downloadFile(doc.name, useStrapiMedia(doc.url || ''))">
下载
</el-button>
</div>
</div>
</el-card>
</div>
</el-tab-pane>
</el-tabs>
</div>
</div>
<!-- 加载状态 -->
<div v-else-if="pending" class="loading">
<el-skeleton style="--el-skeleton-circle-size: 400px">
<template #template>
<el-skeleton-item variant="circle" />
</template>
</el-skeleton>
<el-skeleton :rows="5" animated />
</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>
</template>
<script setup lang="ts">
const route = useRoute()
const { findOne } = useStrapi()
const { getStrapiLocale } = useLocalizations()
const strapiLocale = getStrapiLocale()
const production = ref<Production | null>(null)
const pending = ref(true)
const activeName = ref('details') // 默认选中概览标签
// 获取路由参数slug 或 id
const documentId = computed(() => route.params.slug as string)
const downloadFile = async (filename: string, url: string) => {
const response = await fetch(url)
const blob = await response.blob()
const fileUrl = window.URL.createObjectURL(blob)
const link = document.createElement('a')
link.href = fileUrl
link.download = filename
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
window.URL.revokeObjectURL(fileUrl)
}
onMounted(async () => {
try {
const response = await findOne<Production>('productions', documentId.value, {
populate: {
production_specs: {
populate: '*',
},
production_images: {
populate: '*',
},
cover: {
populate: '*',
},
questions: {
populate: '*',
},
documents: {
populate: '*',
},
},
locale: strapiLocale,
})
if (response.data) {
const item = response.data
production.value = {
...item,
}
}
} catch (error) {
console.error('Failed to fetch production:', error)
} finally {
pending.value = false
}
})
// SEO
useHead({
title: computed(() => production.value?.title || 'Product Detail'),
meta: [
{
name: 'description',
content: computed(() => production.value?.summary || '')
}
]
})
</script>
<style scoped>
.page-container {
min-height: 60vh;
}
.breadcrumb {
padding: 2rem;
}
.production-header {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 3rem;
}
.production-image .el-image {
width: 100%;
height: 400px;
border-radius: 8px;
}
.production-info h1 {
margin-top: 2rem;
margin-bottom: 1rem;
font-size: 2rem;
}
.summary {
color: var(--el-color-info);
font-size: 1rem;
line-height: 1.6;
margin-bottom: 2rem;
}
.production-tabs ::v-deep(.el-tabs__nav) {
min-width: 30%;
float: right;
}
.production-tabs ::v-deep(.el-tabs__content) {
padding: 10px;
}
.production-content h2 {
color: var(--el-text-color-primary);
margin: 0;
}
.document-list {
display: flex;
flex-direction: column;
gap: 1rem;
margin-top: 1rem;
}
.document-meta {
color: var(--el-text-color-secondary);
font-size: 0.8rem;
}
.download-button {
margin-left: auto;
}
.document-content {
display: flex;
align-items: center;
gap: 1rem;
}
.loading {
display: flex;
justify-content: center;
align-items: center;
margin-top: 1rem;
}
.not-found {
display: flex;
justify-content: center;
align-items: center;
min-height: 400px;
}
/* 响应式设计 */
@media (max-width: 768px) {
.production-header {
grid-template-columns: 1fr;
gap: 2rem;
}
.production-info h1 {
font-size: 2rem;
}
}
</style>