hulai-website/pages/summer-camp.vue
Mimingguang ee078b61bc 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>
2026-03-24 17:48:02 +08:00

561 行
14 KiB
Vue

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

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

<template>
<div class="page-summer-camp">
<LayoutPageHero
:title="seo.h1"
subtitle="呼伦贝尔草原亲子夏令营·6天5晚·每期8组家庭"
:breadcrumbs="[{ text: '小蒙马夏令营' }]"
background-image="/images/summer-camp/river-banner.jpg"
/>
<!-- TL;DR告诉 AI/搜索引擎这个页面回答了什么 -->
<section class="page-tldr" aria-label="夏令营摘要">
<div class="container">
<div class="page-tldr-inner">
<p>小蒙马夏令营呼伦贝尔草原亲子夏令营6天5晚每期限8组家庭6-12岁儿童适合亲子同行非托管模式草原骑马篝火晚会营地课程牧民拜访等体验7月旺季需提前2-3个月报名</p>
</div>
</div>
</section>
<!-- 核心定位 -->
<section v-if="camp" class="section">
<div class="container">
<div class="camp-hero">
<div class="camp-mascot">
<img
:src="images.mascot.xiaomengma"
alt="小蒙马 - 小蒙马夏令营吉祥物,一只可爱的蒙古小马形象"
width="200"
height="200"
loading="lazy"
/>
</div>
<div class="camp-intro">
<h2 class="camp-slogan">孩子在探索世界你也在找回自己</h2>
<p class="camp-positioning">{{ camp.positioning }}</p>
<div class="camp-meta">
<span class="camp-meta-item">{{ camp.days }}{{ camp.nights }}</span>
<span class="camp-meta-item">每期8组家庭</span>
<span class="camp-meta-item">亲子同行</span>
</div>
</div>
</div>
</div>
</section>
<!-- 实拍照片 + 期数数据 -->
<section v-if="camp && images" class="section section--warm reveal">
<div class="container">
<div class="camp-gallery">
<div class="camp-photo">
<img
:src="images.summerCamp.river"
alt="小蒙马亲子夏令营在莫日格勒河旁举旗合影,背景是蜿蜒的草原河流"
width="1600"
height="1067"
loading="lazy"
/>
<span class="camp-photo-tag">莫日格勒河</span>
</div>
<div class="camp-photo">
<img
:src="images.summerCamp.grassland"
alt="小蒙马夏令营亲子家庭在呼伦贝尔草原上合影,手持小蒙马标志牌"
width="1600"
height="1067"
loading="lazy"
/>
<span class="camp-photo-tag">草原合影</span>
</div>
<div class="camp-photo">
<img
:src="images.summerCamp.birch"
alt="小蒙马夏令营亲子家庭在白桦林合影,家长和孩子们一起开心比耶"
width="1600"
height="1067"
loading="lazy"
/>
<span class="camp-photo-tag">白桦林研学</span>
</div>
<div class="camp-photo">
<img
:src="images.summerCamp.graduation"
alt="小蒙马夏令营结营仪式,家长和孩子们手持结营证书在营地合影"
width="1600"
height="1067"
loading="lazy"
/>
<span class="camp-photo-tag">结营仪式</span>
</div>
</div>
<div class="camp-stats">
<div class="camp-stat-item">
<span class="camp-stat-value">{{ camp.sessions['2025'] }}</span>
<span class="camp-stat-label">2025年成功举办期数</span>
</div>
<div class="camp-stat-item">
<span class="camp-stat-value">{{ camp.sessions['2026plan'] }}</span>
<span class="camp-stat-label">2026年计划开营期数</span>
</div>
<div class="camp-stat-item">
<span class="camp-stat-value">8</span>
<span class="camp-stat-label">每期限定家庭数</span>
</div>
</div>
</div>
</section>
<!-- 中间 CTA看完照片后的转化入口 -->
<CommonSectionCTA
title="想了解营期安排和费用?"
description="添加微信客服,获取最新营期时间和报名方式"
:links="[{ to: '/contact', text: '咨询小蒙马夏令营' }]"
/>
<!-- 四大原则 -->
<section v-if="camp" class="section reveal">
<div class="container">
<CommonSectionTitle title="行程设计四大原则" subtitle="每一处安排都经过精心考量" />
<div class="principles-grid">
<div v-for="(p, i) in camp.principles" :key="i" class="principle-card">
<div class="principle-index">{{ String(i + 1).padStart(2, '0') }}</div>
<h3 class="principle-name">{{ p }}</h3>
</div>
</div>
</div>
</section>
<!-- 行程概览 -->
<section v-if="camp" class="section reveal">
<div class="container">
<CommonSectionTitle title="6天行程概览" subtitle="不赶路、不折腾,每一天都有记忆点" />
<div class="itinerary-list">
<div v-for="(day, i) in camp.itinerary" :key="i" class="itinerary-item">
<div class="itinerary-day">D{{ day.day || i + 1 }}</div>
<p class="itinerary-text">{{ day.title }}{{ day.summary ? ' ' + day.summary : '' }}</p>
</div>
</div>
</div>
</section>
<!-- 核心体验 -->
<section v-if="camp" class="section section--warm reveal">
<div class="container">
<CommonSectionTitle title="行程核心体验" subtitle="草原写生·非遗研学·民俗体验,每一天都有收获" />
<div class="activities-grid">
<div v-for="(activity, i) in camp.coreActivities" :key="i" class="activity-card">
<div class="activity-index">{{ String(i + 1).padStart(2, '0') }}</div>
<p class="activity-text">{{ activity }}</p>
</div>
</div>
</div>
</section>
<!-- 与V9的区别 -->
<section v-if="camp" class="section reveal">
<div class="container">
<CommonSectionTitle title="小蒙马 vs 额吉的故乡" subtitle="两种产品,两种精彩" />
<div class="compare-cards">
<div class="compare-card compare-card--camp">
<h3>小蒙马夏令营</h3>
<ul>
<li>亲子同行家长和孩子一起出发</li>
<li>每期8组家庭结伴同行孩子有玩伴</li>
<li>草原写生+非遗研学+民俗体验</li>
<li>设有爸妈托管时段家长也能放松</li>
<li>孩子有伙伴一起学一起玩</li>
<li>6天5晚节奏舒缓不赶路</li>
</ul>
</div>
<div class="compare-card compare-card--v9">
<h3>额吉的故乡V9</h3>
<ul>
<li>全家一起出行行程灵活自由</li>
<li>一家一单一车专属私家定制</li>
<li>专属领队一对一服务</li>
<li>行程可根据需求随时调整</li>
<li>享受亲密家庭时光</li>
<li>4-7天多版本可选</li>
</ul>
<NuxtLink to="/products" class="compare-link">查看V9系列 </NuxtLink>
</div>
</div>
<p class="compare-detail">{{ camp.differenceFromV9 }}</p>
</div>
</section>
<!-- 家长常见问题可折叠 -->
<section class="section section--gray">
<div class="container">
<CommonSectionTitle title="家长常见问题" />
<div class="camp-faq">
<FaqItem
v-for="(item, i) in campFAQ"
:key="item.question || item.q"
:question="item.question || item.q"
:answer="item.answer || item.a"
:initial-open="i === 0"
/>
</div>
</div>
</section>
<!-- 底部 CTA双入口 -->
<CommonSectionCTA
title="想了解家庭一起出行的产品?"
description="额吉的故乡V9系列,一家一单一车,全家共享草原时光"
:links="[
{ to: '/contact', text: '咨询小蒙马夏令营' },
{ to: '/products', text: '查看额吉的故乡V9系列' },
]"
/>
</div>
</template>
<script setup>
import { useBreadcrumbSchema, useSummerCampSchema } from '~/composables/useJsonLd'
const { data: camp } = await usePublicApi('summer-camp')
const { data: images } = await usePublicApi('images')
const seo = useSEO('summer-camp')
useScrollReveal()
useBreadcrumbSchema([
{ text: '首页', to: '/' },
{ text: '小蒙马夏令营' },
])
useSummerCampSchema(camp.value)
// 家长关心的问题(从数据文件读取)
const campFAQ = computed(() => camp.value?.faq || [])
</script>
<style lang="less" scoped>
.camp-hero {
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
gap: @space-xl;
max-width: 800px;
margin: 0 auto;
.respond-md({
flex-direction: row;
text-align: left;
gap: @space-2xl;
});
}
.camp-mascot {
flex-shrink: 0;
img {
width: 160px;
height: auto;
filter: drop-shadow(0 6px 20px rgba(0, 0, 0, 0.08));
}
}
.camp-intro {
flex: 1;
}
.camp-slogan {
font-size: @font-size-2xl;
color: @color-primary;
font-weight: @font-weight-bold;
margin-bottom: @space-md;
}
.camp-positioning {
font-size: @font-size-base;
color: @color-text-secondary;
line-height: @line-height-loose;
margin-bottom: @space-lg;
}
.camp-meta {
display: flex;
flex-wrap: wrap;
gap: @space-sm;
}
.camp-meta-item {
font-size: @font-size-sm;
color: @color-primary;
background: @color-primary-bg;
padding: @space-xs @space-md;
border-radius: @border-radius-full;
font-weight: @font-weight-medium;
}
// 实拍照片
.camp-gallery {
display: grid;
grid-template-columns: 1fr;
gap: @space-lg;
margin-bottom: @space-xl;
.respond-md({
grid-template-columns: repeat(2, 1fr);
});
}
.camp-photo {
position: relative;
border-radius: @border-radius-lg;
overflow: hidden;
img {
width: 100%;
height: auto;
display: block;
transition: transform @transition-slow;
}
&:hover img {
transform: scale(1.03);
}
}
.camp-photo-tag {
position: absolute;
bottom: @space-sm;
left: @space-sm;
font-size: @font-size-xs;
color: #fff;
background: rgba(0, 0, 0, 0.5);
padding: @space-xs @space-sm;
border-radius: @border-radius-full;
}
.camp-stats {
display: flex;
justify-content: center;
gap: @space-xl;
flex-wrap: wrap;
}
.camp-stat-item {
text-align: center;
}
.camp-stat-value {
display: block;
font-size: @font-size-3xl;
font-weight: @font-weight-bold;
color: @color-primary;
line-height: 1.2;
}
.camp-stat-label {
display: block;
font-size: @font-size-sm;
color: @color-text-secondary;
margin-top: @space-xs;
}
// 四大原则
.principles-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: @space-md;
.respond-md({
grid-template-columns: repeat(4, 1fr);
});
}
.principle-card {
padding: @space-lg;
background: @color-bg-white;
border-radius: @border-radius-lg;
border: 1px solid @color-border;
text-align: center;
transition: border-color @transition-fast, box-shadow @transition-fast;
&:hover {
border-color: @color-primary-lighter;
box-shadow: @shadow-md;
}
}
.principle-index {
font-size: 28px;
font-weight: @font-weight-bold;
color: @color-primary-lighter;
line-height: 1;
margin-bottom: @space-sm;
}
.principle-name {
font-size: @font-size-md;
color: @color-text-primary;
font-weight: @font-weight-medium;
}
// 行程概览
.itinerary-list {
max-width: 800px;
margin: 0 auto;
display: flex;
flex-direction: column;
gap: @space-md;
}
.itinerary-item {
display: flex;
gap: @space-md;
align-items: flex-start;
padding: @space-md @space-lg;
background: @color-bg-gray;
border-radius: @border-radius-md;
border-left: 3px solid @color-primary;
transition: background @transition-fast, border-color @transition-fast;
&:hover {
background: @color-primary-bg;
}
}
.itinerary-day {
flex-shrink: 0;
font-size: @font-size-lg;
font-weight: @font-weight-bold;
color: @color-primary;
min-width: 32px;
}
.itinerary-text {
font-size: @font-size-base;
color: @color-text-secondary;
line-height: @line-height-base;
margin: 0;
}
// 核心活动
.activities-grid {
.grid-responsive();
}
.activity-card {
padding: @space-xl;
background: @color-bg-white;
border-radius: @border-radius-lg;
border: 1px solid @color-border;
transition: border-color @transition-fast, box-shadow @transition-fast;
&:hover {
border-color: @color-primary-lighter;
box-shadow: @shadow-md;
}
}
.activity-index {
font-size: @font-size-3xl;
font-weight: @font-weight-bold;
margin-bottom: @space-sm;
line-height: 1;
color: @color-primary-lighter;
}
.activity-text {
font-size: @font-size-base;
color: @color-text-secondary;
line-height: @line-height-loose;
margin: 0;
}
// 对比卡片
.compare-cards {
display: grid;
grid-template-columns: 1fr;
gap: @space-lg;
max-width: 800px;
margin: 0 auto @space-xl;
.respond-md({
grid-template-columns: 1fr 1fr;
});
}
.compare-card {
padding: @space-xl;
border-radius: @border-radius-lg;
border: 1px solid @color-border;
h3 {
font-size: @font-size-lg;
font-weight: @font-weight-bold;
margin-bottom: @space-md;
}
ul {
display: flex;
flex-direction: column;
gap: @space-sm;
}
li {
font-size: @font-size-sm;
color: @color-text-secondary;
padding-left: @space-md;
position: relative;
&::before {
content: '·';
position: absolute;
left: 0;
color: @color-primary;
font-weight: bold;
}
}
}
.compare-card--camp {
background: @color-primary-bg;
border-color: fadeout(@color-primary, 80%);
h3 {
color: @color-primary;
}
}
.compare-card--v9 {
background: @color-bg-white;
}
.compare-link {
display: inline-block;
margin-top: @space-md;
font-size: @font-size-sm;
color: @color-primary;
text-decoration: none;
&:hover {
text-decoration: underline;
}
}
.compare-detail {
max-width: 800px;
margin: 0 auto;
font-size: @font-size-base;
color: @color-text-secondary;
line-height: @line-height-loose;
padding: @space-lg;
background: @color-bg-gray;
border-radius: @border-radius-md;
}
// 家长FAQ
.camp-faq {
max-width: 800px;
margin: 0 auto;
display: flex;
flex-direction: column;
gap: @space-md;
}
</style>