wx ac90205395 feat: 官网代码全量更新
覆盖最新版本代码,包含:
- 新增品牌百科、公司百科页面及CMS管理
- 新增微信引导组件(WechatCTA)
- 新增品牌/公司图片资源
- 全站组件/页面/数据/样式更新

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 13:21:11 +08:00

246 行
6.0 KiB
Vue

此文件含有模棱两可的 Unicode 字符

此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。

<template>
<main class="product-detail" v-if="product">
<section class="product-hero">
<div class="container">
<nav class="breadcrumb" aria-label="面包屑导航">
<NuxtLink to="/">首页</NuxtLink> / <NuxtLink to="/products">产品线</NuxtLink> / <span>{{ product.name }}</span>
</nav>
<div class="hero-content">
<span class="product-tag">{{ product.tag }}</span>
<h1>{{ seo.h1 || product.name }}</h1>
<p class="hero-audience">适合:{{ product.audience }}</p>
<p class="hero-duration">{{ product.days }}天{{ product.nights }}晚 · 专车专属·独立成团</p>
</div>
</div>
</section>
<section class="product-description">
<div class="container">
<p>{{ product.description }}</p>
</div>
</section>
<section class="product-highlights" v-if="product.highlights?.length">
<div class="container">
<h2>行程亮点</h2>
<ul>
<li v-for="(h, i) in product.highlights" :key="i">{{ h }}</li>
</ul>
</div>
</section>
<section class="product-faq" v-if="relatedFaq.length">
<div class="container">
<h2>常见问题</h2>
<div class="faq-list">
<details v-for="q in relatedFaq" :key="q.id" class="faq-item">
<summary>{{ q.question }}</summary>
<p>{{ q.answer }}</p>
</details>
</div>
</div>
</section>
<section class="product-cta">
<div class="container">
<h2>开始定制您的行程</h2>
<p>微信添加 hulai1814定制师一对一为您规划</p>
<NuxtLink to="/contact" class="cta-button">联系定制师</NuxtLink>
</div>
</section>
</main>
</template>
<script setup>
import useSEO from '~/composables/useSEO'
import { useTouristTripSchema, useFAQPageSchema, useBreadcrumbSchema } from '~/composables/useJsonLd'
const route = useRoute()
const productId = route.params.id
const { data: productsData } = await usePublicApi('products')
const { data: faqData } = await usePublicApi('faq')
const product = computed(() => productsData.value?.versions?.find(v => v.id === productId))
if (!product.value) {
throw createError({ statusCode: 404, statusMessage: '产品不存在' })
}
// SEO - 每个产品独立的 meta
const seoOverrides = {
title: `${product.value.name} - 呼伦贝尔${product.value.days}${product.value.nights}晚定制游 - 呼籁旅行`,
description: product.value.description?.substring(0, 160),
h1: product.value.name,
}
const seo = useSEO('products', seoOverrides)
// 产品页专属 keywords
useHead({
meta: [
{ name: 'keywords', content: `${product.value.name},呼伦贝尔${product.value.days}${product.value.nights}晚,呼伦贝尔定制游,呼伦贝尔亲子游,${product.value.tag}` },
],
})
// JSON-LD: 单个产品的 TouristTrip
useTouristTripSchema([product.value])
// 面包屑
useBreadcrumbSchema([
{ text: '首页', to: '/' },
{ text: '产品线', to: '/products' },
{ text: product.value.name },
])
// 相关 FAQ - 从 FAQ 数据中匹配与天数/产品相关的问题
const allQuestions = computed(() => faqData.value?.categories?.flatMap(cat => cat.questions) || [])
const relatedFaq = computed(() => allQuestions.value.filter(q =>
q.question.includes(String(product.value?.days)) ||
q.question.includes('几天') ||
q.question.includes('亲子') ||
q.question.includes('区别') ||
q.question.includes('多少钱')
).slice(0, 5))
// 产品页也注入 FAQ schemaSEO精选摘要加分项
if (relatedFaq.value.length) {
useFAQPageSchema([{ questions: relatedFaq.value }])
}
</script>
<style scoped>
.container {
max-width: 800px;
margin: 0 auto;
padding: 0 20px;
}
.product-hero {
background: linear-gradient(135deg, #f0f5f1, #e8ede9);
padding: 40px 0 48px;
}
.breadcrumb {
font-size: 14px;
color: #7a857c;
margin-bottom: 24px;
}
.breadcrumb a {
color: #3a7d44;
text-decoration: none;
}
.product-tag {
display: inline-block;
background: #3a7d44;
color: #fff;
padding: 4px 12px;
border-radius: 4px;
font-size: 13px;
font-weight: 600;
margin-bottom: 12px;
}
h1 {
font-size: 32px;
font-weight: 800;
color: #1a1f1c;
line-height: 1.3;
margin-bottom: 12px;
}
.hero-audience, .hero-duration {
font-size: 16px;
color: #4a5e4d;
margin-bottom: 4px;
}
.product-description {
padding: 40px 0;
}
.product-description p {
font-size: 17px;
line-height: 1.8;
color: #3d4a40;
}
.product-highlights {
padding: 40px 0;
background: #fafbfa;
}
.product-highlights h2 {
font-size: 24px;
font-weight: 700;
margin-bottom: 20px;
color: #1a1f1c;
}
.product-highlights ul {
list-style: none;
padding: 0;
}
.product-highlights li {
padding: 14px 0;
border-bottom: 1px solid #eef0ef;
font-size: 16px;
line-height: 1.6;
color: #3d4a40;
padding-left: 24px;
position: relative;
}
.product-highlights li::before {
content: '✓';
position: absolute;
left: 0;
color: #3a7d44;
font-weight: 700;
}
.product-faq {
padding: 40px 0;
}
.product-faq h2 {
font-size: 24px;
font-weight: 700;
margin-bottom: 20px;
color: #1a1f1c;
}
.faq-item {
border-bottom: 1px solid #eef0ef;
padding: 16px 0;
}
.faq-item summary {
font-size: 16px;
font-weight: 600;
color: #1a1f1c;
cursor: pointer;
list-style: none;
}
.faq-item summary::marker { display: none; }
.faq-item summary::-webkit-details-marker { display: none; }
.faq-item p {
margin-top: 12px;
font-size: 15px;
line-height: 1.7;
color: #4a5e4d;
}
.product-cta {
padding: 48px 0;
text-align: center;
background: linear-gradient(135deg, #f0f5f1, #e8ede9);
}
.product-cta h2 {
font-size: 24px;
font-weight: 700;
margin-bottom: 8px;
}
.product-cta p {
color: #4a5e4d;
margin-bottom: 20px;
}
.cta-button {
display: inline-block;
background: #3a7d44;
color: #fff;
padding: 12px 32px;
border-radius: 8px;
text-decoration: none;
font-weight: 600;
font-size: 16px;
}
.cta-button:hover {
background: #2e6435;
}
</style>