feat: 后台管理系统完整重构 + 前后台数据联通

- 后台全面引入 Naive UI 组件库,统一 UI 规范
- 前台 usePublicApi 切换到 API 调用(GET /api/content/[key])
- 前后台数据源统一(site_content 表)
- 产品线统一管理(tour/camp/course 三套编辑器)
- 产品数据归一化(itinerary/faq/pricing 格式统一)
- 表单提交 API 改写入 submissions 表
- 新增 submissions 标记已读 API
- site-content PUT 支持 upsert
- 修复前台 bug(stories 路由冲突、占位符警告、selector breadcrumb)
- 消除 PageHero 类型警告(useSEO reactive 修复)
- 25 个前台页面全部 HTTP 200,展示效果不变

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
这个提交包含在:
Mimingguang
2026-03-24 17:48:02 +08:00
共同撰写人 Claude Opus 4.6
当前提交 ee078b61bc
共修改 444 个文件,包含 59890 行新增和 0 行删除
+245
查看文件
@@ -0,0 +1,245 @@
<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 schema(SEO精选摘要加分项)
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>
+300
查看文件
@@ -0,0 +1,300 @@
<template>
<div class="page-autumn">
<LayoutPageHero
:title="seo.h1"
:subtitle="autumnData?.style"
:breadcrumbs="[{ text: '产品线', to: '/products' }, { text: '游牧的森林' }]"
/>
<!-- TL;DR -->
<section class="page-tldr" aria-label="产品页摘要">
<div class="container">
<div class="page-tldr-inner">
<p>游牧的森林V3系列提供4-7天6个版本的呼伦贝尔秋季定制游,涵盖南线(呼伦贝尔+阿尔山)和北线(呼伦贝尔+大兴安岭)两条主线。所有版本均为独立成团、一家一单一车,不与陌生人拼团。不确定选哪条线,可<NuxtLink to="/contact">联系定制师</NuxtLink>获取推荐。</p>
</div>
</div>
</section>
<!-- 风景大图 -->
<div class="scenic-banner">
<img src="/images/seasons/autumn-hero.jpg" alt="秋季大兴安岭金色白桦林" loading="eager" />
</div>
<!-- 产品设计理念 -->
<ProductsProductOverview v-if="autumnData" :narrative="autumnData.narrative" class="reveal" />
<!-- 秋季亮点 -->
<section v-if="autumnData" class="section reveal">
<div class="container">
<CommonSectionTitle title="秋季呼伦贝尔 · 不可错过的风景" subtitle="每年9月中旬-10月中旬" />
<div class="highlights-grid">
<div v-for="h in autumnData.highlights" :key="h.title" class="highlight-card">
<h3 class="highlight-title">{{ h.title }}</h3>
<p class="highlight-desc">{{ h.description }}</p>
</div>
</div>
</div>
</section>
<!-- 南线产品卡片 -->
<section class="section reveal">
<div class="container">
<CommonSectionTitle title="南线 · 呼伦贝尔+阿尔山" subtitle="草原秋色与火山天池" />
<div class="products-grid">
<AutumnProductCard v-for="p in southVersions" :key="p.id" :product="p" />
</div>
</div>
</section>
<!-- 南线特色体验 -->
<section v-if="autumnData" class="section section--warm reveal">
<div class="container">
<CommonSectionTitle title="南线独有体验" subtitle="阿尔山专属" />
<div class="highlights-grid highlights-grid--small">
<div v-for="h in autumnData.southHighlights" :key="h.title" class="highlight-card">
<h3 class="highlight-title">{{ h.title }}</h3>
<p class="highlight-desc">{{ h.description }}</p>
</div>
</div>
</div>
</section>
<!-- 中间转化入口 -->
<CommonSectionCTA
title="不确定选南线还是北线?"
description="联系定制师,根据您的假期和偏好推荐最适合的行程"
:links="[{ to: '/contact', text: '联系定制师' }]"
/>
<!-- 北线产品卡片 -->
<section class="section reveal">
<div class="container">
<CommonSectionTitle title="北线 · 呼伦贝尔+大兴安岭" subtitle="深入大兴安岭腹地赏秋" />
<div class="products-grid">
<AutumnProductCard v-for="p in northVersions" :key="p.id" :product="p" />
</div>
</div>
</section>
<!-- 北线特色体验 -->
<section v-if="autumnData" class="section section--warm reveal">
<div class="container">
<CommonSectionTitle title="北线独有体验" subtitle="大兴安岭专属" />
<div class="highlights-grid highlights-grid--small">
<div v-for="h in autumnData.northHighlights" :key="h.title" class="highlight-card">
<h3 class="highlight-title">{{ h.title }}</h3>
<p class="highlight-desc">{{ h.description }}</p>
</div>
</div>
</div>
</section>
<!-- 选择指南 -->
<section v-if="autumnData?.selectionGuide" id="guide" class="section section--gray reveal">
<div class="container">
<CommonSectionTitle title="不知道选哪个?" subtitle="按假期长度推荐" />
<div class="guide-items">
<div v-for="item in autumnData.selectionGuide.byVacationLength" :key="item.condition" class="guide-item">
<div class="guide-item-top">
<span class="guide-condition">{{ item.condition }}</span>
<span class="guide-arrow">→</span>
<span class="guide-rec">{{ getProductName(item.recommendation) }}</span>
</div>
<p class="guide-reason">{{ item.reason }}</p>
</div>
</div>
</div>
</section>
<!-- 版本迭代时间线 -->
<ProductsVersionTimeline
v-if="autumnData"
:timeline="autumnData.timeline"
title="游牧的森林 · 版本迭代"
subtitle="V1到V3的完整迭代记录"
/>
<!-- 底部 CTA -->
<CommonSectionCTA
title="听听其他家庭怎么说"
description="看看真实客户的旅行评价"
:links="[{ to: '/reviews', text: '查看10000+家庭的真实评价' }]"
/>
</div>
</template>
<script setup>
import { useBreadcrumbSchema } from '~/composables/useJsonLd'
const { data: autumnData } = await usePublicApi('autumn-products')
const seo = useSEO('products-autumn', {
title: '游牧的森林V3 - 呼伦贝尔秋季定制游 - 呼籁旅行',
description: '呼籁旅行呼伦贝尔秋季定制游产品「游牧的森林」V3系列,南线呼伦贝尔+阿尔山、北线呼伦贝尔+大兴安岭,4-7天6个版本可选。独立成团不拼团,一家一单一车。',
h1: '游牧的森林 · 呼伦贝尔秋季定制游',
})
useScrollReveal()
useBreadcrumbSchema([
{ text: '首页', to: '/' },
{ text: '产品线', to: '/products' },
{ text: '游牧的森林' },
])
const southVersions = computed(() =>
autumnData.value?.versions?.filter(v => v.line !== '北线') || []
)
const northVersions = computed(() =>
autumnData.value?.versions?.filter(v => v.line === '北线') || []
)
function getProductName(id) {
const v = autumnData.value?.versions?.find(v => v.id === id)
return v ? v.name : id
}
</script>
<style lang="less" scoped>
.products-grid {
display: grid;
grid-template-columns: 1fr;
gap: @space-xl;
.respond-md({
grid-template-columns: repeat(2, 1fr);
});
.respond-lg({
grid-template-columns: repeat(3, 1fr);
});
> * {
display: flex;
}
}
.scenic-banner {
width: 100%;
overflow: hidden;
max-height: 480px;
img {
width: 100%;
height: 480px;
object-fit: cover;
object-position: center 70%;
display: block;
}
}
// 秋季亮点卡片
.highlights-grid {
display: grid;
grid-template-columns: 1fr;
gap: @space-lg;
.respond-md({
grid-template-columns: repeat(2, 1fr);
});
.respond-lg({
grid-template-columns: repeat(3, 1fr);
});
&--small {
max-width: 900px;
margin: 0 auto;
.respond-lg({
grid-template-columns: repeat(3, 1fr);
});
}
}
.highlight-card {
padding: @space-lg;
background: @color-bg-white;
border: 1px solid @color-border;
border-radius: @border-radius-md;
transition: box-shadow @transition-fast, transform @transition-fast;
&:hover {
box-shadow: @shadow-sm;
transform: translateY(-2px);
}
}
.highlight-title {
font-size: @font-size-base;
font-weight: @font-weight-bold;
color: @color-text-primary;
margin-bottom: @space-sm;
padding-left: @space-sm;
border-left: 3px solid #D97706;
}
.highlight-desc {
font-size: @font-size-sm;
color: @color-text-secondary;
line-height: @line-height-relaxed;
margin: 0;
}
// 选择指南
.guide-items {
display: flex;
flex-direction: column;
gap: @space-md;
max-width: 800px;
margin: 0 auto;
}
.guide-item {
padding: @space-lg;
background: @color-bg-white;
border-radius: @border-radius-md;
border: 1px solid @color-border;
transition: border-color @transition-fast, box-shadow @transition-fast;
&:hover {
border-color: #D97706;
box-shadow: @shadow-sm;
}
}
.guide-item-top {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: @space-sm;
margin-bottom: @space-xs;
}
.guide-condition {
font-weight: @font-weight-medium;
color: @color-text-primary;
background: @color-bg-gray;
padding: @space-xs @space-sm;
border-radius: @border-radius-sm;
font-size: @font-size-sm;
}
.guide-arrow {
color: #D97706;
font-weight: @font-weight-bold;
}
.guide-rec {
color: #D97706;
font-weight: @font-weight-bold;
font-size: @font-size-base;
}
.guide-reason {
font-size: @font-size-sm;
color: @color-text-muted;
margin: 0;
line-height: @line-height-base;
}
</style>
+178
查看文件
@@ -0,0 +1,178 @@
<template>
<div class="page-products">
<LayoutPageHero
:title="seo.h1"
subtitle="独立成团不拼团 · 一单一车 · 随走随停 · 不错过每处风景"
:breadcrumbs="[{ text: '产品线', to: '/products' }, { text: '额吉的故乡' }]"
/>
<!-- TL;DR:告诉 AI/搜索引擎这个页面回答了什么 -->
<section class="page-tldr" aria-label="产品页摘要">
<div class="container">
<div class="page-tldr-inner">
<p>额吉的故乡V9系列提供5-7天6个版本的呼伦贝尔亲子游,涵盖森林版、草原版、亲子版、探索版、旷野版等不同需求。所有版本均为独立成团、一家一单一车,不与陌生人拼团。不确定选哪个版本,可使用<NuxtLink to="/selector">选版工具</NuxtLink>进行1分钟匹配,或参考<NuxtLink to="/pricing">价格参考页</NuxtLink>了解费用区间。</p>
</div>
</div>
</section>
<!-- 风景大图 -->
<div class="scenic-banner">
<img src="/images/seasons/summer-hero.jpg" alt="夏季呼伦贝尔莫日格勒河草原全景" loading="eager" />
</div>
<!-- 产品设计理念 -->
<ProductsProductOverview v-if="products" :narrative="products.narrative" class="reveal" />
<!-- 产品卡片 -->
<section v-if="products" class="section reveal">
<div class="container">
<CommonSectionTitle title="额吉的故乡V9系列" subtitle="5-7天多版本行程" />
<div class="products-grid">
<ProductsProductCard v-for="p in products.versions" :key="p.id" :product="p" />
</div>
</div>
</section>
<!-- 夏季风景 -->
<div class="scenic-duo reveal">
<img src="/images/seasons/summer-river.jpg" alt="夏季草原蒙古包莫日格勒河全景" loading="lazy" />
<img src="/images/seasons/summer-car.jpg" alt="呼伦贝尔一单一车草原越野" loading="lazy" />
</div>
<!-- 中间转化入口 -->
<CommonSectionCTA
title="不确定选哪个版本?"
description="5个问题,1分钟,找到最适合你家庭的行程版本"
:links="[{ to: '/selector', text: '使用选版工具' }, { to: '/contact', text: '联系定制师' }]"
/>
<!-- 选择指南 -->
<ProductsSelectionGuide v-if="products" :guide="products.selectionGuide" :versions="products.versions" class="reveal" />
<!-- 产品进化:统计数据 -->
<ProductsEvolutionStats v-if="versions" :stats="versions.stats" class="reveal" />
<!-- 2026核心升级 -->
<ProductsVersionUpgrades v-if="versions" :upgrades="versions.upgrades2026" class="reveal" />
<!-- V9亮点 -->
<ProductsVersionHighlights v-if="versions" :highlights="versions.highlights" class="reveal" />
<!-- V8→V9对比 -->
<ProductsVersionCompare v-if="versions" :compare="versions.compareV8V9" class="reveal" />
<!-- 历史版本时间线 -->
<ProductsVersionTimeline v-if="versions" :timeline="versions.timeline" />
<!-- 产品哲学 -->
<ProductsVersionPhilosophy v-if="versions" :philosophy="versions.philosophy" :quote="versions.quote" />
<!-- 价格透明 -->
<ProductsPricingPhilosophy v-if="products" :content="products.pricingPhilosophy" />
<!-- 爸妈也想来草原 -->
<section class="section section--gray reveal">
<div class="container" style="max-width: 720px; text-align: center;">
<h2 class="section-title" style="font-size: 24px; margin-bottom: 8px;">爸妈也想来草原?</h2>
<p style="color: #5a6b5e; line-height: 1.8; margin-bottom: 24px;">很多家庭带孩子来过呼伦贝尔之后,又给爸妈报了名。自己上班没时间陪,但想让父母也体验一次靠谱的草原旅行——我们的「牵手草原」长辈定制行程,专为父母和长辈设计,节奏更缓、住宿更舒适、服务更细致。</p>
<NuxtLink to="/contact" class="senior-cta">联系定制师了解「牵手草原」</NuxtLink>
</div>
</section>
<!-- 内链 -->
<CommonSectionCTA
title="听听其他家庭怎么说"
description="看看真实客户的旅行评价"
:links="[{ to: '/reviews', text: '查看10000+家庭的真实评价' }]"
/>
</div>
</template>
<script setup>
import { useProductsListSchema, useBreadcrumbSchema } from '~/composables/useJsonLd'
const { data: products } = await usePublicApi('products')
const { data: versions } = await usePublicApi('versions')
const seo = useSEO('products')
useScrollReveal()
useProductsListSchema(products.value?.versions)
useBreadcrumbSchema([
{ text: '首页', to: '/' },
{ text: '产品线', to: '/products' },
{ text: '额吉的故乡' },
])
</script>
<style lang="less" scoped>
.products-grid {
display: grid;
grid-template-columns: 1fr;
gap: @space-xl;
.respond-md({
grid-template-columns: repeat(2, 1fr);
});
.respond-lg({
grid-template-columns: repeat(3, 1fr);
});
// 让卡片等高对齐
> * {
display: flex;
}
}
.scenic-banner {
width: 100%;
overflow: hidden;
max-height: 480px;
img {
width: 100%;
height: 480px;
object-fit: cover;
object-position: center 70%;
display: block;
}
}
.scenic-duo {
display: grid;
grid-template-columns: 1fr;
gap: 4px;
max-height: 360px;
overflow: hidden;
.respond-md({
grid-template-columns: 1fr 1fr;
});
img {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
max-height: 360px;
}
}
.senior-cta {
display: inline-block;
padding: 12px 28px;
background: @color-bg-white;
color: @color-primary;
border: 1px solid @color-primary;
border-radius: @border-radius-md;
text-decoration: none;
font-size: @font-size-sm;
font-weight: @font-weight-medium;
transition: all @transition-fast;
&:hover {
background: @color-primary;
color: #fff;
}
}
</style>
+172
查看文件
@@ -0,0 +1,172 @@
<template>
<div class="page-winter">
<LayoutPageHero
:title="seo.h1"
subtitle="独立成团不拼团 · 一单一车 · 随走随停 · 不错过每处风景"
:breadcrumbs="[{ text: '产品线', to: '/products' }, { text: '嗨冰雪' }]"
background-image="/images/seasons/winter-hero.jpg"
/>
<!-- TL;DR -->
<section class="page-tldr" aria-label="冬季产品摘要">
<div class="container">
<div class="page-tldr-inner">
<p>嗨冰雪V3系列:呼伦贝尔冬季定制游,5-7天南北两线可选。独立成团不拼团,一单一车随走随停。冰雪越野、雪地策马、冬季那达慕。</p>
</div>
</div>
</section>
<!-- 品牌理念 -->
<ProductsProductOverview v-if="winterData" :narrative="winterData.narrative" class="reveal" />
<!-- 产品卡片 -->
<section v-if="winterData" class="section reveal">
<div class="container">
<CommonSectionTitle title="嗨冰雪V3系列" subtitle="5-7天南北两线,专属私家团" />
<div class="products-grid">
<WinterProductCard v-for="p in winterData.versions" :key="p.id" :product="p" />
</div>
</div>
</section>
<!-- 选行程指南 -->
<CommonSectionCTA
title="不确定选南线还是北线?"
description="告诉定制师您的假期和偏好,一分钟帮您选对行程"
:links="[{ to: '/contact', text: '联系定制师选行程' }]"
/>
<!-- 共同亮点 -->
<section v-if="winterData" class="section reveal">
<div class="container">
<CommonSectionTitle title="冬季行程亮点" subtitle="每一条线路都会体验到" />
<div class="highlights-grid">
<div v-for="(h, i) in winterData.highlights" :key="i" class="highlight-card">
<h3 class="highlight-title">{{ h.title }}</h3>
<p class="highlight-desc">{{ h.description }}</p>
</div>
</div>
</div>
</section>
<!-- 南线特色 -->
<section v-if="winterData" class="section section--warm reveal">
<div class="container">
<CommonSectionTitle title="南线特色 · 呼伦贝尔+阿尔山" subtitle="大雪原与童话小城的碰撞" />
<div class="highlights-grid">
<div v-for="(h, i) in winterData.southHighlights" :key="i" class="highlight-card">
<h3 class="highlight-title">{{ h.title }}</h3>
<p class="highlight-desc">{{ h.description }}</p>
</div>
</div>
</div>
</section>
<!-- 北线特色 -->
<section v-if="winterData" class="section reveal">
<div class="container">
<CommonSectionTitle title="北线特色 · 呼伦贝尔+大兴安岭" subtitle="边境风情与驯鹿部落的秘境" />
<div class="highlights-grid">
<div v-for="(h, i) in winterData.northHighlights" :key="i" class="highlight-card">
<h3 class="highlight-title">{{ h.title }}</h3>
<p class="highlight-desc">{{ h.description }}</p>
</div>
</div>
</div>
</section>
<!-- 内链 -->
<CommonSectionCTA
title="听听冬季旅行的家庭怎么说"
description="看看真实客户的冬季旅行评价"
:links="[{ to: '/reviews', text: '查看客户评价' }, { to: '/contact', text: '咨询冬季行程' }]"
/>
</div>
</template>
<script setup>
import { useBreadcrumbSchema } from '~/composables/useJsonLd'
const { data: winterData } = await usePublicApi('winter-products')
const seo = useSEO('products-winter', {
title: '嗨冰雪 - 呼伦贝尔冬季定制游 · 南北两线5-7天 - 呼籁旅行',
description: '呼籁旅行冬季定制游产品「嗨冰雪V3」,南线呼伦贝尔+阿尔山5-6天,北线呼伦贝尔+大兴安岭7天。冰雪那达慕、雪地狼群、白狼峰日出、呼伦湖蓝冰、驯鹿部落。一单一车专属私家团。',
h1: '嗨冰雪 · 呼伦贝尔冬季定制游',
})
useScrollReveal()
useBreadcrumbSchema([
{ text: '首页', to: '/' },
{ text: '产品线', to: '/products' },
{ text: '嗨冰雪' },
])
</script>
<style lang="less" scoped>
.products-grid {
display: grid;
grid-template-columns: 1fr;
gap: @space-xl;
.respond-md({
grid-template-columns: repeat(2, 1fr);
});
.respond-lg({
grid-template-columns: repeat(3, 1fr);
});
> * {
display: flex;
}
}
.highlights-grid {
display: grid;
grid-template-columns: 1fr;
gap: @space-lg;
.respond-md({
grid-template-columns: repeat(2, 1fr);
});
.respond-lg({
grid-template-columns: repeat(3, 1fr);
});
}
.highlight-card {
background: @color-bg-white;
border: 1px solid @color-border;
border-radius: @border-radius-lg;
padding: @space-xl;
transition: box-shadow @transition-base, transform @transition-base;
&:hover {
box-shadow: @shadow-md;
transform: translateY(-2px);
}
}
.highlight-title {
font-size: @font-size-lg;
font-weight: @font-weight-bold;
color: @color-text-primary;
margin-bottom: @space-sm;
&::before {
content: '❄ ';
color: #3B82F6;
}
}
.highlight-desc {
font-size: @font-size-sm;
color: @color-text-secondary;
line-height: @line-height-base;
}
.section--warm {
background: @color-bg-warm;
}
</style>