398 行
9.3 KiB
Vue
398 行
9.3 KiB
Vue
<template>
|
||
<main class="destination-detail" v-if="dest">
|
||
<section class="dest-hero">
|
||
<div class="container">
|
||
<nav class="breadcrumb" aria-label="面包屑导航">
|
||
<NuxtLink to="/">首页</NuxtLink> / <NuxtLink to="/destinations">景区攻略</NuxtLink> / <span>{{ dest.name }}</span>
|
||
</nav>
|
||
<div class="hero-content">
|
||
<span class="dest-tag">{{ dest.tag }}</span>
|
||
<h1>{{ dest.name }}</h1>
|
||
<p class="hero-subtitle">{{ dest.subtitle }}</p>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- TL;DR:告诉 AI/搜索引擎这个页面回答了什么 -->
|
||
<section class="page-tldr" aria-label="目的地摘要">
|
||
<div class="container">
|
||
<div class="page-tldr-inner">
|
||
<p>{{ dest.description }}</p>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<section class="dest-description">
|
||
<div class="container">
|
||
<p>{{ dest.description }}</p>
|
||
</div>
|
||
</section>
|
||
|
||
<section class="dest-highlights" v-if="dest.highlights?.length">
|
||
<div class="container">
|
||
<h2>景点亮点</h2>
|
||
<ul>
|
||
<li v-for="(h, i) in dest.highlights" :key="i">{{ h }}</li>
|
||
</ul>
|
||
</div>
|
||
</section>
|
||
|
||
<section class="dest-season">
|
||
<div class="container">
|
||
<h2>最佳游览季节</h2>
|
||
<div class="season-card">
|
||
<div class="season-primary">
|
||
<span class="season-label">最佳时节</span>
|
||
<span class="season-value">{{ dest.bestSeason.primary }}</span>
|
||
</div>
|
||
<div class="season-secondary" v-if="dest.bestSeason.secondary">
|
||
<span class="season-label">次佳时节</span>
|
||
<span class="season-value">{{ dest.bestSeason.secondary }}</span>
|
||
</div>
|
||
<p class="season-desc">{{ dest.bestSeason.description }}</p>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<section class="dest-tips" v-if="dest.tips?.length">
|
||
<div class="container">
|
||
<h2>实用旅行贴士</h2>
|
||
<ul>
|
||
<li v-for="(tip, i) in dest.tips" :key="i">{{ tip }}</li>
|
||
</ul>
|
||
</div>
|
||
</section>
|
||
|
||
<section class="dest-products" v-if="relatedProducts.length">
|
||
<div class="container">
|
||
<h2>包含此景点的行程</h2>
|
||
<p class="products-intro">以下呼籁定制游产品涵盖{{ dest.name }},由专属领队带队深度游览。</p>
|
||
<div class="product-links">
|
||
<NuxtLink
|
||
v-for="p in relatedProducts"
|
||
:key="p.id"
|
||
:to="`/products/${p.id}`"
|
||
class="product-link"
|
||
>
|
||
<span class="product-name">{{ p.name }}</span>
|
||
<span class="product-meta">{{ p.days }}天{{ p.nights }}晚 · {{ p.tag }}</span>
|
||
</NuxtLink>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<section class="dest-cta">
|
||
<div class="container">
|
||
<h2>想去{{ dest.name }}?让定制师为你规划</h2>
|
||
<p>扫码添加微信,定制师一对一为你安排深度游览方案</p>
|
||
<NuxtLink to="/contact" class="cta-button">联系定制师</NuxtLink>
|
||
</div>
|
||
</section>
|
||
</main>
|
||
</template>
|
||
|
||
<script setup>
|
||
import destinationsData from '~/data/destinations-detail.json'
|
||
import productsData from '~/data/products.json'
|
||
import winterProductsData from '~/data/winter-products.json'
|
||
import autumnProductsData from '~/data/autumn-products.json'
|
||
import useSEO from '~/composables/useSEO'
|
||
import { useBreadcrumbSchema } from '~/composables/useJsonLd'
|
||
|
||
const route = useRoute()
|
||
const destId = route.params.id
|
||
|
||
const dest = destinationsData.destinations.find(d => d.id === destId)
|
||
|
||
if (!dest) {
|
||
throw createError({ statusCode: 404, statusMessage: '目的地不存在' })
|
||
}
|
||
|
||
// SEO
|
||
const seoOverrides = {
|
||
title: dest.seo?.title,
|
||
description: dest.seo?.description,
|
||
}
|
||
useSEO('destinations', seoOverrides)
|
||
|
||
useHead({
|
||
meta: [
|
||
{ name: 'keywords', content: dest.seo?.keywords },
|
||
],
|
||
})
|
||
|
||
// JSON-LD: TouristAttraction
|
||
useHead({
|
||
script: [
|
||
{
|
||
type: 'application/ld+json',
|
||
innerHTML: JSON.stringify({
|
||
'@context': 'https://schema.org',
|
||
'@type': 'TouristAttraction',
|
||
name: dest.name,
|
||
description: dest.description,
|
||
url: `https://1814.love/destinations/${dest.id}`,
|
||
touristType: '家庭游客,亲子游,摄影爱好者',
|
||
isAccessibleForFree: false,
|
||
photo: dest.heroImage ? { '@type': 'ImageObject', url: `https://1814.love${dest.heroImage}` } : undefined,
|
||
containedInPlace: {
|
||
'@type': 'AdministrativeArea',
|
||
name: '呼伦贝尔',
|
||
address: {
|
||
'@type': 'PostalAddress',
|
||
addressLocality: '呼伦贝尔市',
|
||
addressRegion: '内蒙古自治区',
|
||
addressCountry: 'CN',
|
||
},
|
||
},
|
||
publicAccess: true,
|
||
availableLanguage: 'Chinese',
|
||
}),
|
||
},
|
||
{
|
||
type: 'application/ld+json',
|
||
innerHTML: JSON.stringify({
|
||
'@context': 'https://schema.org',
|
||
'@type': 'Article',
|
||
headline: dest.seo?.title || dest.name,
|
||
description: dest.seo?.description || dest.description,
|
||
url: `https://1814.love/destinations/${dest.id}`,
|
||
author: { '@id': 'https://1814.love/#organization' },
|
||
publisher: { '@id': 'https://1814.love/#organization' },
|
||
dateModified: new Date().toISOString().split('T')[0],
|
||
about: {
|
||
'@type': 'TouristAttraction',
|
||
name: dest.name,
|
||
},
|
||
keywords: dest.seo?.keywords,
|
||
}),
|
||
},
|
||
],
|
||
})
|
||
|
||
// 面包屑
|
||
useBreadcrumbSchema([
|
||
{ text: '首页', to: '/' },
|
||
{ text: '景区攻略', to: '/destinations' },
|
||
{ text: dest.name },
|
||
])
|
||
|
||
// 关联产品详情——从三个产品线数据中查找
|
||
const allVersions = [
|
||
...(productsData.versions || []),
|
||
...(winterProductsData.versions || []),
|
||
...(autumnProductsData.versions || []),
|
||
]
|
||
const relatedProducts = (dest.relatedProducts || [])
|
||
.map(pid => allVersions.find(v => v.id === pid))
|
||
.filter(Boolean)
|
||
</script>
|
||
|
||
<style scoped>
|
||
.container {
|
||
max-width: 800px;
|
||
margin: 0 auto;
|
||
padding: 0 20px;
|
||
}
|
||
.dest-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;
|
||
}
|
||
.dest-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: 10px;
|
||
}
|
||
.hero-subtitle {
|
||
font-size: 17px;
|
||
color: #4a5e4d;
|
||
}
|
||
.dest-description {
|
||
padding: 40px 0;
|
||
}
|
||
.dest-description p {
|
||
font-size: 17px;
|
||
line-height: 1.9;
|
||
color: #3d4a40;
|
||
}
|
||
.dest-highlights {
|
||
padding: 40px 0;
|
||
background: #fafbfa;
|
||
}
|
||
.dest-highlights h2,
|
||
.dest-season h2,
|
||
.dest-tips h2,
|
||
.dest-products h2 {
|
||
font-size: 24px;
|
||
font-weight: 700;
|
||
margin-bottom: 20px;
|
||
color: #1a1f1c;
|
||
}
|
||
.dest-highlights ul {
|
||
list-style: none;
|
||
padding: 0;
|
||
}
|
||
.dest-highlights li {
|
||
padding: 14px 0;
|
||
border-bottom: 1px solid #eef0ef;
|
||
font-size: 16px;
|
||
line-height: 1.6;
|
||
color: #3d4a40;
|
||
padding-left: 24px;
|
||
position: relative;
|
||
}
|
||
.dest-highlights li::before {
|
||
content: '✓';
|
||
position: absolute;
|
||
left: 0;
|
||
color: #3a7d44;
|
||
font-weight: 700;
|
||
}
|
||
.dest-season {
|
||
padding: 40px 0;
|
||
}
|
||
.season-card {
|
||
background: #f0f5f1;
|
||
border-radius: 10px;
|
||
padding: 24px;
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 20px;
|
||
align-items: flex-start;
|
||
}
|
||
.season-primary,
|
||
.season-secondary {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 4px;
|
||
min-width: 120px;
|
||
}
|
||
.season-label {
|
||
font-size: 12px;
|
||
color: #7a857c;
|
||
font-weight: 600;
|
||
text-transform: uppercase;
|
||
letter-spacing: 0.05em;
|
||
}
|
||
.season-value {
|
||
font-size: 18px;
|
||
font-weight: 700;
|
||
color: #3a7d44;
|
||
}
|
||
.season-desc {
|
||
flex: 1 1 100%;
|
||
font-size: 15px;
|
||
line-height: 1.7;
|
||
color: #4a5e4d;
|
||
margin: 0;
|
||
}
|
||
.dest-tips {
|
||
padding: 40px 0;
|
||
background: #fafbfa;
|
||
}
|
||
.dest-tips ul {
|
||
list-style: none;
|
||
padding: 0;
|
||
}
|
||
.dest-tips li {
|
||
padding: 14px 0;
|
||
border-bottom: 1px solid #eef0ef;
|
||
font-size: 16px;
|
||
line-height: 1.6;
|
||
color: #3d4a40;
|
||
padding-left: 28px;
|
||
position: relative;
|
||
}
|
||
.dest-tips li::before {
|
||
content: '💡';
|
||
position: absolute;
|
||
left: 0;
|
||
font-size: 14px;
|
||
}
|
||
.dest-products {
|
||
padding: 40px 0;
|
||
}
|
||
.products-intro {
|
||
font-size: 15px;
|
||
color: #4a5e4d;
|
||
margin-bottom: 20px;
|
||
}
|
||
.product-links {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 12px;
|
||
}
|
||
.product-link {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
padding: 16px 20px;
|
||
border: 1px solid #d8e4da;
|
||
border-radius: 8px;
|
||
text-decoration: none;
|
||
transition: border-color 0.2s, background 0.2s;
|
||
}
|
||
.product-link:hover {
|
||
border-color: #3a7d44;
|
||
background: #f0f5f1;
|
||
}
|
||
.product-name {
|
||
font-size: 16px;
|
||
font-weight: 600;
|
||
color: #1a1f1c;
|
||
}
|
||
.product-meta {
|
||
font-size: 14px;
|
||
color: #7a857c;
|
||
}
|
||
.dest-cta {
|
||
padding: 48px 0;
|
||
text-align: center;
|
||
background: linear-gradient(135deg, #f0f5f1, #e8ede9);
|
||
}
|
||
.dest-cta h2 {
|
||
font-size: 24px;
|
||
font-weight: 700;
|
||
margin-bottom: 8px;
|
||
color: #1a1f1c;
|
||
}
|
||
.dest-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>
|