feat: 官网代码全量更新
覆盖最新版本代码,包含: - 新增品牌百科、公司百科页面及CMS管理 - 新增微信引导组件(WechatCTA) - 新增品牌/公司图片资源 - 全站组件/页面/数据/样式更新 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
这个提交包含在:
+6
-5
@@ -17,7 +17,7 @@
|
||||
</section>
|
||||
|
||||
<!-- 1. 品牌故事:为什么只做呼伦贝尔 -->
|
||||
<AboutBrandStory v-if="about" :story="about.story" class="reveal" />
|
||||
<AboutBrandStory v-if="about" :story="about.story" :images="images" class="reveal" />
|
||||
|
||||
<!-- 2. 服务旅程:跟呼籁出行是什么体验 -->
|
||||
<section v-if="about" class="section section--gray reveal" aria-label="服务旅程">
|
||||
@@ -36,10 +36,10 @@
|
||||
</section>
|
||||
|
||||
<!-- 3. 团队介绍:谁在照顾你 -->
|
||||
<AboutTeamIntro v-if="about" :team="about.team" class="reveal" />
|
||||
<AboutTeamIntro v-if="about" :team="about.team" :images="images" class="reveal" />
|
||||
|
||||
<!-- 4. 小红书 -->
|
||||
<AboutXiaohongshuShowcase v-if="about" :xhs="about.xiaohongshu" class="reveal" />
|
||||
<AboutXiaohongshuShowcase v-if="about" :xhs="about.xiaohongshu" :images="images" class="reveal" />
|
||||
|
||||
<!-- 6. 品牌发展历程 -->
|
||||
<section v-if="about" class="section reveal" aria-label="品牌发展历程">
|
||||
@@ -64,7 +64,7 @@
|
||||
|
||||
<!-- 8. 品牌资产:知识产权 + 吉祥物 -->
|
||||
<AboutCopyrightList v-if="about" :copyrights="about.copyrights" :trademark="about.trademark" class="reveal" />
|
||||
<AboutMascotShowcase class="reveal" />
|
||||
<AboutMascotShowcase :images="images" class="reveal" />
|
||||
|
||||
<!-- 9. 企业文化 -->
|
||||
<section v-if="about" class="section reveal" aria-label="企业文化">
|
||||
@@ -103,7 +103,7 @@
|
||||
<!-- CTA -->
|
||||
<CommonSectionCTA
|
||||
title="想了解我们的产品?"
|
||||
description="查看额吉的故乡V9系列和小蒙马夏令营"
|
||||
description="查看额吉的故乡V9系列和小蒙马亲子游学营"
|
||||
:links="[
|
||||
{ to: '/products', text: '查看旅行产品' },
|
||||
{ to: '/contact', text: '联系定制师' },
|
||||
@@ -118,6 +118,7 @@ import { useOrganizationSchema, useBreadcrumbSchema, usePersonSchema } from '~/c
|
||||
const { data: about } = await usePublicApi('about')
|
||||
const { data: brand } = await usePublicApi('brand')
|
||||
const { data: contact } = await usePublicApi('contact')
|
||||
const { data: images } = await usePublicApi('images')
|
||||
|
||||
const seo = useSEO('about')
|
||||
useScrollReveal()
|
||||
|
||||
@@ -0,0 +1,257 @@
|
||||
<template>
|
||||
<div style="max-width: 1100px">
|
||||
<n-space justify="space-between" align="center" style="margin-bottom: 20px">
|
||||
<div>
|
||||
<h1 style="margin: 0 0 4px; font-size: 20px; font-weight: 600; color: #111827">呼籁旅行百科</h1>
|
||||
<p style="margin: 0; font-size: 13px; color: #9ca3af">管理"呼籁旅行"品牌百科页面内容</p>
|
||||
</div>
|
||||
<n-space>
|
||||
<n-button tag="a" href="/brand" target="_blank" secondary>查看前台 ↗</n-button>
|
||||
<n-button type="primary" :loading="saving" @click="save">保存</n-button>
|
||||
</n-space>
|
||||
</n-space>
|
||||
|
||||
<n-spin :show="!loaded">
|
||||
<n-collapse :default-expanded-names="['basic']">
|
||||
<!-- 基本信息 -->
|
||||
<n-collapse-item title="基本信息" name="basic">
|
||||
<n-card>
|
||||
<n-form label-placement="top">
|
||||
<n-grid :cols="2" :x-gap="12">
|
||||
<n-gi><n-form-item label="标题"><n-input v-model:value="data.title" /></n-form-item></n-gi>
|
||||
<n-gi><n-form-item label="Logo 路径"><n-input v-model:value="data.logo" /></n-form-item></n-gi>
|
||||
</n-grid>
|
||||
<n-form-item label="副标题">
|
||||
<n-input v-model:value="data.subtitle" />
|
||||
</n-form-item>
|
||||
<n-form-item label="摘要">
|
||||
<n-input v-model:value="data.summary" type="textarea" :autosize="{ minRows: 3, maxRows: 8 }" />
|
||||
</n-form-item>
|
||||
</n-form>
|
||||
</n-card>
|
||||
</n-collapse-item>
|
||||
|
||||
<!-- 信息框 -->
|
||||
<n-collapse-item title="信息框" name="infoBox">
|
||||
<n-card>
|
||||
<div style="width: 100%">
|
||||
<div v-for="(_, key) in data.infoBox" :key="key" style="display: flex; gap: 8px; align-items: center; margin-bottom: 8px">
|
||||
<n-input :value="key" placeholder="字段名" style="width: 160px" @update:value="v => renameKey(data.infoBox, key, v)" />
|
||||
<n-input v-model:value="data.infoBox[key]" placeholder="值" style="flex: 1" />
|
||||
<n-button size="tiny" type="error" quaternary @click="delete data.infoBox[key]">删</n-button>
|
||||
</div>
|
||||
<n-button size="small" type="primary" dashed @click="addInfoBoxItem">添加字段</n-button>
|
||||
</div>
|
||||
</n-card>
|
||||
</n-collapse-item>
|
||||
|
||||
<!-- 内容章节 -->
|
||||
<n-collapse-item title="内容章节" name="sections">
|
||||
<n-card>
|
||||
<div style="width: 100%">
|
||||
<n-card v-for="(sec, i) in data.sections" :key="i" size="small" style="margin-bottom: 16px">
|
||||
<template #header>
|
||||
<n-space align="center">
|
||||
<span style="font-size: 14px; font-weight: 500">{{ sec.title || '新章节' }}</span>
|
||||
<n-tag :bordered="false" size="small" type="info">{{ getSectionType(sec) }}</n-tag>
|
||||
<n-button size="tiny" type="error" quaternary @click="data.sections.splice(i, 1)">删除</n-button>
|
||||
</n-space>
|
||||
</template>
|
||||
<n-form label-placement="top">
|
||||
<n-form-item label="章节标题">
|
||||
<n-input v-model:value="sec.title" />
|
||||
</n-form-item>
|
||||
|
||||
<!-- 纯文本型 -->
|
||||
<template v-if="getSectionType(sec) === '文本'">
|
||||
<n-form-item label="内容">
|
||||
<n-input v-model:value="sec.content" type="textarea" :autosize="{ minRows: 3, maxRows: 10 }" />
|
||||
</n-form-item>
|
||||
<n-form-item v-if="sec.image !== undefined" label="图片路径">
|
||||
<n-input v-model:value="sec.image" />
|
||||
</n-form-item>
|
||||
</template>
|
||||
|
||||
<!-- 子章节型(品牌历史) -->
|
||||
<template v-if="getSectionType(sec) === '子章节'">
|
||||
<n-form-item label="子章节">
|
||||
<div style="width: 100%">
|
||||
<n-card v-for="(sub, j) in sec.subsections" :key="j" size="small" style="margin-bottom: 8px">
|
||||
<template #header>
|
||||
<n-space align="center">
|
||||
<span style="font-size: 13px; color: #6b7280">{{ j + 1 }}.</span>
|
||||
<n-button size="tiny" type="error" quaternary @click="sec.subsections.splice(j, 1)">删</n-button>
|
||||
</n-space>
|
||||
</template>
|
||||
<n-form-item label="标题"><n-input v-model:value="sub.title" /></n-form-item>
|
||||
<n-form-item label="内容"><n-input v-model:value="sub.content" type="textarea" :autosize="{ minRows: 2, maxRows: 6 }" /></n-form-item>
|
||||
</n-card>
|
||||
<n-button size="small" type="primary" dashed @click="sec.subsections.push({ title: '', content: '' })">添加子章节</n-button>
|
||||
</div>
|
||||
</n-form-item>
|
||||
</template>
|
||||
|
||||
<!-- 产品型 -->
|
||||
<template v-if="getSectionType(sec) === '产品'">
|
||||
<n-form-item label="简介">
|
||||
<n-input v-model:value="sec.intro" type="textarea" :autosize="{ minRows: 2, maxRows: 5 }" />
|
||||
</n-form-item>
|
||||
<n-form-item label="产品列表">
|
||||
<div style="width: 100%">
|
||||
<n-card v-for="(prod, j) in sec.products" :key="j" size="small" style="margin-bottom: 8px">
|
||||
<template #header>
|
||||
<n-space align="center">
|
||||
<span style="font-size: 13px; color: #6b7280">{{ prod.name || '新产品' }}</span>
|
||||
<n-button size="tiny" type="error" quaternary @click="sec.products.splice(j, 1)">删</n-button>
|
||||
</n-space>
|
||||
</template>
|
||||
<n-form-item label="名称"><n-input v-model:value="prod.name" /></n-form-item>
|
||||
<n-form-item label="描述"><n-input v-model:value="prod.description" type="textarea" :autosize="{ minRows: 2, maxRows: 6 }" /></n-form-item>
|
||||
</n-card>
|
||||
<n-button size="small" type="primary" dashed @click="sec.products.push({ name: '', description: '' })">添加产品</n-button>
|
||||
</div>
|
||||
</n-form-item>
|
||||
</template>
|
||||
|
||||
<!-- 步骤型(服务流程) -->
|
||||
<template v-if="getSectionType(sec) === '步骤'">
|
||||
<n-form-item label="步骤">
|
||||
<div style="width: 100%">
|
||||
<div v-for="(step, j) in sec.steps" :key="j" style="display: flex; gap: 8px; align-items: flex-start; margin-bottom: 8px">
|
||||
<n-input-number v-model:value="step.step" placeholder="序号" style="width: 80px" :min="1" />
|
||||
<n-input v-model:value="step.name" placeholder="名称" style="width: 140px" />
|
||||
<n-input v-model:value="step.detail" placeholder="详情" style="flex: 1" />
|
||||
<n-button size="tiny" type="error" quaternary @click="sec.steps.splice(j, 1)">删</n-button>
|
||||
</div>
|
||||
<n-button size="small" type="primary" dashed @click="sec.steps.push({ step: sec.steps.length + 1, name: '', detail: '' })">添加步骤</n-button>
|
||||
</div>
|
||||
</n-form-item>
|
||||
</template>
|
||||
|
||||
<!-- 键值对型(文化理念) -->
|
||||
<template v-if="getSectionType(sec) === '键值对'">
|
||||
<n-form-item label="条目">
|
||||
<div style="width: 100%">
|
||||
<div v-for="(item, j) in sec.items" :key="j" style="display: flex; gap: 8px; align-items: center; margin-bottom: 8px">
|
||||
<n-input v-model:value="item.label" placeholder="标签" style="width: 160px" />
|
||||
<n-input v-model:value="item.value" placeholder="内容" style="flex: 1" />
|
||||
<n-button size="tiny" type="error" quaternary @click="sec.items.splice(j, 1)">删</n-button>
|
||||
</div>
|
||||
<n-button size="small" type="primary" dashed @click="sec.items.push({ label: '', value: '' })">添加条目</n-button>
|
||||
</div>
|
||||
</n-form-item>
|
||||
</template>
|
||||
</n-form>
|
||||
</n-card>
|
||||
|
||||
<n-space style="margin-top: 12px">
|
||||
<n-button size="small" type="primary" dashed @click="addSection('text')">+ 文本章节</n-button>
|
||||
<n-button size="small" type="primary" dashed @click="addSection('subsections')">+ 子章节</n-button>
|
||||
<n-button size="small" type="primary" dashed @click="addSection('products')">+ 产品章节</n-button>
|
||||
<n-button size="small" type="primary" dashed @click="addSection('steps')">+ 步骤章节</n-button>
|
||||
<n-button size="small" type="primary" dashed @click="addSection('items')">+ 键值对章节</n-button>
|
||||
</n-space>
|
||||
</div>
|
||||
</n-card>
|
||||
</n-collapse-item>
|
||||
|
||||
<!-- 图库 -->
|
||||
<n-collapse-item title="图库" name="gallery">
|
||||
<n-card>
|
||||
<n-dynamic-input v-model:value="data.gallery" :min="0" placeholder="图片路径,如 /images/brand/xxx.jpg" />
|
||||
</n-card>
|
||||
</n-collapse-item>
|
||||
|
||||
<!-- SEO -->
|
||||
<n-collapse-item title="SEO" name="seo">
|
||||
<n-card>
|
||||
<n-form label-placement="top">
|
||||
<n-form-item label="页面标题">
|
||||
<n-input v-model:value="data.seo.title" />
|
||||
</n-form-item>
|
||||
<n-form-item label="描述">
|
||||
<n-input v-model:value="data.seo.description" type="textarea" :autosize="{ minRows: 2, maxRows: 4 }" />
|
||||
</n-form-item>
|
||||
<n-form-item label="关键词">
|
||||
<n-input v-model:value="data.seo.keywords" />
|
||||
</n-form-item>
|
||||
</n-form>
|
||||
</n-card>
|
||||
</n-collapse-item>
|
||||
</n-collapse>
|
||||
</n-spin>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { NButton, NCard, NSpace, NInput, NForm, NFormItem, NSpin, NGrid, NGi, NCollapse, NCollapseItem, NDynamicInput, NInputNumber, NTag } from 'naive-ui'
|
||||
import { useMessage } from 'naive-ui'
|
||||
|
||||
definePageMeta({ layout: 'admin', middleware: 'admin' })
|
||||
const { adminFetch } = useAdmin()
|
||||
const message = useMessage()
|
||||
|
||||
const data = ref({
|
||||
title: '', subtitle: '', summary: '', logo: '',
|
||||
infoBox: {},
|
||||
sections: [],
|
||||
gallery: [],
|
||||
seo: { title: '', description: '', keywords: '' }
|
||||
})
|
||||
const loaded = ref(false)
|
||||
const saving = ref(false)
|
||||
|
||||
function getSectionType(sec) {
|
||||
if (sec.subsections) return '子章节'
|
||||
if (sec.products) return '产品'
|
||||
if (sec.steps) return '步骤'
|
||||
if (sec.items) return '键值对'
|
||||
return '文本'
|
||||
}
|
||||
|
||||
function addSection(type) {
|
||||
const templates = {
|
||||
text: { title: '', content: '' },
|
||||
subsections: { title: '', subsections: [{ title: '', content: '' }] },
|
||||
products: { title: '', intro: '', products: [{ name: '', description: '' }] },
|
||||
steps: { title: '', steps: [{ step: 1, name: '', detail: '' }] },
|
||||
items: { title: '', items: [{ label: '', value: '' }] },
|
||||
}
|
||||
data.value.sections.push(templates[type])
|
||||
}
|
||||
|
||||
function addInfoBoxItem() {
|
||||
const key = `新字段${Object.keys(data.value.infoBox).length + 1}`
|
||||
data.value.infoBox[key] = ''
|
||||
}
|
||||
|
||||
function renameKey(obj, oldKey, newKey) {
|
||||
if (newKey && newKey !== oldKey && !(newKey in obj)) {
|
||||
const val = obj[oldKey]
|
||||
delete obj[oldKey]
|
||||
obj[newKey] = val
|
||||
}
|
||||
}
|
||||
|
||||
async function load() {
|
||||
const res = await adminFetch('/api/admin/site-content?key=brand-wiki')
|
||||
data.value = res.data
|
||||
if (!data.value.seo) data.value.seo = { title: '', description: '', keywords: '' }
|
||||
if (!data.value.gallery) data.value.gallery = []
|
||||
loaded.value = true
|
||||
}
|
||||
|
||||
async function save() {
|
||||
saving.value = true
|
||||
try {
|
||||
await adminFetch('/api/admin/site-content', { method: 'PUT', body: { key: 'brand-wiki', data: data.value } })
|
||||
message.success('已保存')
|
||||
} catch (e) {
|
||||
message.error('保存失败')
|
||||
} finally {
|
||||
saving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(load)
|
||||
</script>
|
||||
@@ -0,0 +1,261 @@
|
||||
<template>
|
||||
<div style="max-width: 1100px">
|
||||
<n-space justify="space-between" align="center" style="margin-bottom: 20px">
|
||||
<div>
|
||||
<h1 style="margin: 0 0 4px; font-size: 20px; font-weight: 600; color: #111827">呼籁文旅集团百科</h1>
|
||||
<p style="margin: 0; font-size: 13px; color: #9ca3af">管理"呼籁文旅集团"公司百科页面内容</p>
|
||||
</div>
|
||||
<n-space>
|
||||
<n-button tag="a" href="/company" target="_blank" secondary>查看前台 ↗</n-button>
|
||||
<n-button type="primary" :loading="saving" @click="save">保存</n-button>
|
||||
</n-space>
|
||||
</n-space>
|
||||
|
||||
<n-spin :show="!loaded">
|
||||
<n-collapse :default-expanded-names="['basic']">
|
||||
<!-- 基本信息 -->
|
||||
<n-collapse-item title="基本信息" name="basic">
|
||||
<n-card>
|
||||
<n-form label-placement="top">
|
||||
<n-grid :cols="2" :x-gap="12">
|
||||
<n-gi><n-form-item label="公司名称"><n-input v-model:value="data.title" /></n-form-item></n-gi>
|
||||
<n-gi><n-form-item label="Logo 路径"><n-input v-model:value="data.logo" /></n-form-item></n-gi>
|
||||
</n-grid>
|
||||
<n-form-item label="副标题">
|
||||
<n-input v-model:value="data.subtitle" />
|
||||
</n-form-item>
|
||||
<n-form-item label="摘要">
|
||||
<n-input v-model:value="data.summary" type="textarea" :autosize="{ minRows: 3, maxRows: 8 }" />
|
||||
</n-form-item>
|
||||
</n-form>
|
||||
</n-card>
|
||||
</n-collapse-item>
|
||||
|
||||
<!-- 信息框 -->
|
||||
<n-collapse-item title="信息框" name="infoBox">
|
||||
<n-card>
|
||||
<div style="width: 100%">
|
||||
<div v-for="(_, key) in data.infoBox" :key="key" style="display: flex; gap: 8px; align-items: center; margin-bottom: 8px">
|
||||
<n-input :value="key" placeholder="字段名" style="width: 160px" @update:value="v => renameKey(data.infoBox, key, v)" />
|
||||
<n-input v-model:value="data.infoBox[key]" placeholder="值" style="flex: 1" />
|
||||
<n-button size="tiny" type="error" quaternary @click="delete data.infoBox[key]">删</n-button>
|
||||
</div>
|
||||
<n-button size="small" type="primary" dashed @click="addInfoBoxItem">添加字段</n-button>
|
||||
</div>
|
||||
</n-card>
|
||||
</n-collapse-item>
|
||||
|
||||
<!-- 内容章节 -->
|
||||
<n-collapse-item title="内容章节" name="sections">
|
||||
<n-card>
|
||||
<div style="width: 100%">
|
||||
<n-card v-for="(sec, i) in data.sections" :key="i" size="small" style="margin-bottom: 16px">
|
||||
<template #header>
|
||||
<n-space align="center">
|
||||
<span style="font-size: 14px; font-weight: 500">{{ sec.title || '新章节' }}</span>
|
||||
<n-tag :bordered="false" size="small" type="info">{{ getSectionType(sec) }}</n-tag>
|
||||
<n-button size="tiny" type="error" quaternary @click="data.sections.splice(i, 1)">删除</n-button>
|
||||
</n-space>
|
||||
</template>
|
||||
<n-form label-placement="top">
|
||||
<n-form-item label="章节标题">
|
||||
<n-input v-model:value="sec.title" />
|
||||
</n-form-item>
|
||||
|
||||
<!-- 纯文本型 -->
|
||||
<template v-if="getSectionType(sec) === '文本'">
|
||||
<n-form-item label="内容">
|
||||
<n-input v-model:value="sec.content" type="textarea" :autosize="{ minRows: 3, maxRows: 10 }" />
|
||||
</n-form-item>
|
||||
</template>
|
||||
|
||||
<!-- 时间线型(发展历程) -->
|
||||
<template v-if="getSectionType(sec) === '时间线'">
|
||||
<n-form-item label="时间线">
|
||||
<div style="width: 100%">
|
||||
<div v-for="(item, j) in sec.timeline" :key="j" style="display: flex; gap: 8px; align-items: flex-start; margin-bottom: 8px">
|
||||
<n-input v-model:value="item.year" placeholder="年份" style="width: 120px" />
|
||||
<n-input v-model:value="item.content" placeholder="事件" type="textarea" :autosize="{ minRows: 1, maxRows: 4 }" style="flex: 1" />
|
||||
<n-button size="tiny" type="error" quaternary @click="sec.timeline.splice(j, 1)">删</n-button>
|
||||
</div>
|
||||
<n-button size="small" type="primary" dashed @click="sec.timeline.push({ year: '', content: '' })">添加事件</n-button>
|
||||
</div>
|
||||
</n-form-item>
|
||||
</template>
|
||||
|
||||
<!-- 子公司型 -->
|
||||
<template v-if="getSectionType(sec) === '子公司'">
|
||||
<n-form-item label="简介">
|
||||
<n-input v-model:value="sec.intro" type="textarea" :autosize="{ minRows: 2, maxRows: 5 }" />
|
||||
</n-form-item>
|
||||
<n-form-item label="子公司列表">
|
||||
<div style="width: 100%">
|
||||
<div v-for="(sub, j) in sec.subsidiaries" :key="j" style="display: flex; gap: 8px; align-items: center; margin-bottom: 8px">
|
||||
<n-input :value="sub" placeholder="子公司描述" style="flex: 1" @update:value="v => sec.subsidiaries[j] = v" />
|
||||
<n-button size="tiny" type="error" quaternary @click="sec.subsidiaries.splice(j, 1)">删</n-button>
|
||||
</div>
|
||||
<n-button size="small" type="primary" dashed @click="sec.subsidiaries.push('')">添加子公司</n-button>
|
||||
</div>
|
||||
</n-form-item>
|
||||
</template>
|
||||
|
||||
<!-- 业务线型 -->
|
||||
<template v-if="getSectionType(sec) === '业务线'">
|
||||
<n-form-item label="业务板块">
|
||||
<div style="width: 100%">
|
||||
<n-card v-for="(biz, j) in sec.businesses" :key="j" size="small" style="margin-bottom: 8px">
|
||||
<template #header>
|
||||
<n-space align="center">
|
||||
<span style="font-size: 13px; color: #6b7280">{{ biz.name || '新业务' }}</span>
|
||||
<n-button size="tiny" type="error" quaternary @click="sec.businesses.splice(j, 1)">删</n-button>
|
||||
</n-space>
|
||||
</template>
|
||||
<n-form-item label="名称"><n-input v-model:value="biz.name" /></n-form-item>
|
||||
<n-form-item label="描述"><n-input v-model:value="biz.description" type="textarea" :autosize="{ minRows: 1, maxRows: 4 }" /></n-form-item>
|
||||
<n-form-item v-if="biz.extra !== undefined" label="备注"><n-input v-model:value="biz.extra" /></n-form-item>
|
||||
</n-card>
|
||||
<n-button size="small" type="primary" dashed @click="sec.businesses.push({ name: '', description: '' })">添加业务</n-button>
|
||||
</div>
|
||||
</n-form-item>
|
||||
</template>
|
||||
|
||||
<!-- 键值对型(文化理念) -->
|
||||
<template v-if="getSectionType(sec) === '键值对'">
|
||||
<n-form-item label="条目">
|
||||
<div style="width: 100%">
|
||||
<div v-for="(item, j) in sec.items" :key="j" style="display: flex; gap: 8px; align-items: center; margin-bottom: 8px">
|
||||
<n-input v-model:value="item.label" placeholder="标签" style="width: 160px" />
|
||||
<n-input v-model:value="item.value" placeholder="内容" style="flex: 1" />
|
||||
<n-button size="tiny" type="error" quaternary @click="sec.items.splice(j, 1)">删</n-button>
|
||||
</div>
|
||||
<n-button size="small" type="primary" dashed @click="sec.items.push({ label: '', value: '' })">添加条目</n-button>
|
||||
</div>
|
||||
</n-form-item>
|
||||
</template>
|
||||
|
||||
<!-- 带图片的文本型(团队) -->
|
||||
<template v-if="getSectionType(sec) === '图文'">
|
||||
<n-form-item label="内容">
|
||||
<n-input v-model:value="sec.content" type="textarea" :autosize="{ minRows: 3, maxRows: 10 }" />
|
||||
</n-form-item>
|
||||
<n-form-item label="图片">
|
||||
<n-dynamic-input v-model:value="sec.images" :min="0" placeholder="图片路径" />
|
||||
</n-form-item>
|
||||
</template>
|
||||
</n-form>
|
||||
</n-card>
|
||||
|
||||
<n-space style="margin-top: 12px">
|
||||
<n-button size="small" type="primary" dashed @click="addSection('text')">+ 文本章节</n-button>
|
||||
<n-button size="small" type="primary" dashed @click="addSection('timeline')">+ 时间线</n-button>
|
||||
<n-button size="small" type="primary" dashed @click="addSection('subsidiaries')">+ 子公司</n-button>
|
||||
<n-button size="small" type="primary" dashed @click="addSection('businesses')">+ 业务线</n-button>
|
||||
<n-button size="small" type="primary" dashed @click="addSection('items')">+ 键值对</n-button>
|
||||
<n-button size="small" type="primary" dashed @click="addSection('images')">+ 图文</n-button>
|
||||
</n-space>
|
||||
</div>
|
||||
</n-card>
|
||||
</n-collapse-item>
|
||||
|
||||
<!-- 图库 -->
|
||||
<n-collapse-item title="图库" name="gallery">
|
||||
<n-card>
|
||||
<n-dynamic-input v-model:value="data.gallery" :min="0" placeholder="图片路径,如 /images/company/xxx.jpg" />
|
||||
</n-card>
|
||||
</n-collapse-item>
|
||||
|
||||
<!-- SEO -->
|
||||
<n-collapse-item title="SEO" name="seo">
|
||||
<n-card>
|
||||
<n-form label-placement="top">
|
||||
<n-form-item label="页面标题">
|
||||
<n-input v-model:value="data.seo.title" />
|
||||
</n-form-item>
|
||||
<n-form-item label="描述">
|
||||
<n-input v-model:value="data.seo.description" type="textarea" :autosize="{ minRows: 2, maxRows: 4 }" />
|
||||
</n-form-item>
|
||||
<n-form-item label="关键词">
|
||||
<n-input v-model:value="data.seo.keywords" />
|
||||
</n-form-item>
|
||||
</n-form>
|
||||
</n-card>
|
||||
</n-collapse-item>
|
||||
</n-collapse>
|
||||
</n-spin>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { NButton, NCard, NSpace, NInput, NForm, NFormItem, NSpin, NGrid, NGi, NCollapse, NCollapseItem, NDynamicInput, NTag } from 'naive-ui'
|
||||
import { useMessage } from 'naive-ui'
|
||||
|
||||
definePageMeta({ layout: 'admin', middleware: 'admin' })
|
||||
const { adminFetch } = useAdmin()
|
||||
const message = useMessage()
|
||||
|
||||
const data = ref({
|
||||
title: '', subtitle: '', summary: '', logo: '',
|
||||
infoBox: {},
|
||||
sections: [],
|
||||
gallery: [],
|
||||
seo: { title: '', description: '', keywords: '' }
|
||||
})
|
||||
const loaded = ref(false)
|
||||
const saving = ref(false)
|
||||
|
||||
function getSectionType(sec) {
|
||||
if (sec.timeline) return '时间线'
|
||||
if (sec.subsidiaries) return '子公司'
|
||||
if (sec.businesses) return '业务线'
|
||||
if (sec.items) return '键值对'
|
||||
if (sec.images) return '图文'
|
||||
return '文本'
|
||||
}
|
||||
|
||||
function addSection(type) {
|
||||
const templates = {
|
||||
text: { title: '', content: '' },
|
||||
timeline: { title: '', timeline: [{ year: '', content: '' }] },
|
||||
subsidiaries: { title: '', intro: '', subsidiaries: [''] },
|
||||
businesses: { title: '', businesses: [{ name: '', description: '' }] },
|
||||
items: { title: '', items: [{ label: '', value: '' }] },
|
||||
images: { title: '', content: '', images: [] },
|
||||
}
|
||||
data.value.sections.push(templates[type])
|
||||
}
|
||||
|
||||
function addInfoBoxItem() {
|
||||
const key = `新字段${Object.keys(data.value.infoBox).length + 1}`
|
||||
data.value.infoBox[key] = ''
|
||||
}
|
||||
|
||||
function renameKey(obj, oldKey, newKey) {
|
||||
if (newKey && newKey !== oldKey && !(newKey in obj)) {
|
||||
const val = obj[oldKey]
|
||||
delete obj[oldKey]
|
||||
obj[newKey] = val
|
||||
}
|
||||
}
|
||||
|
||||
async function load() {
|
||||
const res = await adminFetch('/api/admin/site-content?key=company-wiki')
|
||||
data.value = res.data
|
||||
if (!data.value.seo) data.value.seo = { title: '', description: '', keywords: '' }
|
||||
if (!data.value.gallery) data.value.gallery = []
|
||||
loaded.value = true
|
||||
}
|
||||
|
||||
async function save() {
|
||||
saving.value = true
|
||||
try {
|
||||
await adminFetch('/api/admin/site-content', { method: 'PUT', body: { key: 'company-wiki', data: data.value } })
|
||||
message.success('已保存')
|
||||
} catch (e) {
|
||||
message.error('保存失败')
|
||||
} finally {
|
||||
saving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(load)
|
||||
</script>
|
||||
@@ -2,7 +2,7 @@
|
||||
<div>
|
||||
<n-space justify="space-between" align="center" style="margin-bottom: 20px;">
|
||||
<div>
|
||||
<h1 style="margin: 0 0 4px; font-size: 22px; color: #1a1a2e;">小蒙马夏令营</h1>
|
||||
<h1 style="margin: 0 0 4px; font-size: 22px; color: #1a1a2e;">小蒙马亲子游学营</h1>
|
||||
<p style="margin: 0; font-size: 13px; color: #888;">管理研学课程体系,对应官网「夏令营」页面</p>
|
||||
</div>
|
||||
<n-space>
|
||||
|
||||
+35
-10
@@ -78,8 +78,10 @@
|
||||
<!-- 联系咨询 -->
|
||||
<div class="sidebar-card sidebar-card--cta">
|
||||
<h3 class="sidebar-card__title">免费获取定制方案</h3>
|
||||
<p class="sidebar-card__desc">看完攻略有疑问?联系呼籁旅行定制师,一对一解答。</p>
|
||||
<CommonInternalLink to="/contact" text="立即咨询" />
|
||||
<p class="sidebar-card__desc">看完攻略有疑问?扫码添加定制师微信,一对一解答。</p>
|
||||
<img src="/images/contact/qrcode-wechat.jpg" alt="定制师微信二维码" class="sidebar-qrcode" />
|
||||
<span class="sidebar-qrcode-label">扫码添加定制师微信</span>
|
||||
<CommonInternalLink to="/contact" text="其他联系方式" />
|
||||
</div>
|
||||
|
||||
<!-- 全部文章 -->
|
||||
@@ -142,17 +144,17 @@ if (!article.value) {
|
||||
|
||||
// SEO
|
||||
useHead({
|
||||
title: article.value?.seo?.title || article.value?.title,
|
||||
title: article.value?.seoTitle || article.value?.title,
|
||||
meta: [
|
||||
{ name: 'description', content: article.value?.seo?.description || article.value?.summary },
|
||||
{ name: 'keywords', content: article.value?.seo?.keywords || (article.value?.tags || []).join(',') },
|
||||
{ name: 'description', content: article.value?.seoDescription || article.value?.summary },
|
||||
{ name: 'keywords', content: article.value?.seoKeywords || (article.value?.tags || []).join(',') },
|
||||
{ name: 'author', content: article.value?.author },
|
||||
{ name: 'article:published_time', content: (article.value?.date || '') + 'T00:00:00+08:00' },
|
||||
{ name: 'article:modified_time', content: (article.value?.date || '') + 'T00:00:00+08:00' },
|
||||
{ name: 'geo.region', content: 'CN-NM' },
|
||||
{ name: 'geo.placename', content: '呼伦贝尔' },
|
||||
{ property: 'og:title', content: article.value?.seo?.title || article.value?.title },
|
||||
{ property: 'og:description', content: article.value?.seo?.description || article.value?.summary },
|
||||
{ property: 'og:title', content: article.value?.seoTitle || article.value?.title },
|
||||
{ property: 'og:description', content: article.value?.seoDescription || article.value?.summary },
|
||||
{ property: 'og:type', content: 'article' },
|
||||
{ property: 'og:url', content: `https://1814.love/blog/${article.value?.id}` },
|
||||
{ property: 'og:site_name', content: '呼籁旅行' },
|
||||
@@ -277,13 +279,12 @@ function formatDate(dateStr) {
|
||||
margin-bottom: @space-xl;
|
||||
border-radius: @border-radius-lg;
|
||||
overflow: hidden;
|
||||
aspect-ratio: 16 / 7;
|
||||
background: @color-primary-bg;
|
||||
|
||||
&__img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
height: auto;
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -347,6 +348,14 @@ function formatDate(dateStr) {
|
||||
font-weight: @font-weight-bold;
|
||||
}
|
||||
|
||||
:deep(img) {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
border-radius: @border-radius-lg;
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
:deep(br) {
|
||||
display: block;
|
||||
content: '';
|
||||
@@ -518,6 +527,22 @@ function formatDate(dateStr) {
|
||||
}
|
||||
}
|
||||
|
||||
.sidebar-qrcode {
|
||||
display: block;
|
||||
width: 120px;
|
||||
height: 120px;
|
||||
border-radius: @border-radius-sm;
|
||||
object-fit: contain;
|
||||
margin-bottom: @space-xs;
|
||||
}
|
||||
|
||||
.sidebar-qrcode-label {
|
||||
display: block;
|
||||
font-size: @font-size-xs;
|
||||
color: @color-text-muted;
|
||||
margin-bottom: @space-md;
|
||||
}
|
||||
|
||||
.sidebar-articles {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
|
||||
+6
-10
@@ -37,7 +37,7 @@
|
||||
<article
|
||||
v-for="article in filteredArticles"
|
||||
:key="article.id"
|
||||
class="blog-card reveal"
|
||||
class="blog-card"
|
||||
>
|
||||
<NuxtLink :to="`/blog/${article.id}`" class="blog-card__link">
|
||||
<div class="blog-card__cover">
|
||||
@@ -72,15 +72,11 @@
|
||||
</div>
|
||||
|
||||
<!-- CTA -->
|
||||
<div class="blog-cta">
|
||||
<h2>看完攻略,准备出发了吗?</h2>
|
||||
<p>呼籁旅行为每个家庭提供专属定制方案,告诉我们你的需求,我们帮你把行程安排得妥妥的。</p>
|
||||
<div class="blog-cta-links">
|
||||
<CommonInternalLink to="/products" text="查看定制游产品" />
|
||||
<CommonInternalLink to="/contact" text="咨询定制师" />
|
||||
<CommonInternalLink to="/faq" text="常见问答" />
|
||||
</div>
|
||||
</div>
|
||||
<CommonWechatCTA
|
||||
title="看完攻略,准备出发了吗?"
|
||||
description="扫码添加定制师微信,根据你的家庭情况和假期安排,推荐合适的行程方案。"
|
||||
:links="[{ to: '/products', text: '查看定制游产品' }, { to: '/faq', text: '常见问答' }]"
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
+422
@@ -0,0 +1,422 @@
|
||||
<template>
|
||||
<div class="page-wiki">
|
||||
<LayoutPageHero
|
||||
:title="data?.title || '呼籁旅行'"
|
||||
:subtitle="data?.subtitle"
|
||||
:breadcrumbs="[{ text: '关于我们', to: '/about' }, { text: '呼籁旅行' }]"
|
||||
background-image="/images/brand/photo-01.jpeg"
|
||||
/>
|
||||
|
||||
<!-- 摘要 + 信息卡 -->
|
||||
<section class="section" aria-label="品牌概述">
|
||||
<div class="container">
|
||||
<div class="wiki-header">
|
||||
<div class="wiki-summary">
|
||||
<div class="wiki-summary-inner">
|
||||
<h2 class="wiki-summary-heading">品牌简介</h2>
|
||||
<p class="wiki-summary-text">{{ data?.summary }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="data?.infoBox" class="wiki-infobox">
|
||||
<div class="wiki-infobox-logo">
|
||||
<img :src="data.logo" :alt="data.title" />
|
||||
</div>
|
||||
<div class="wiki-infobox-body">
|
||||
<div v-for="(value, key) in data.infoBox" :key="key" class="wiki-infobox-row">
|
||||
<span class="wiki-infobox-label">{{ key }}</span>
|
||||
<span class="wiki-infobox-value">{{ value }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 各段落 -->
|
||||
<template v-for="(section, idx) in data?.sections" :key="idx">
|
||||
<!-- 品牌历史(子段落) -->
|
||||
<section v-if="section.subsections" class="section" :class="{ 'section--gray': idx % 2 === 1 }">
|
||||
<div class="container">
|
||||
<h2 class="wiki-section-title">{{ section.title }}</h2>
|
||||
<div v-for="(sub, si) in section.subsections" :key="si" class="wiki-subsection">
|
||||
<h3 class="wiki-subsection-title">{{ sub.title }}</h3>
|
||||
<p class="wiki-text">{{ sub.content }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 产品及服务 -->
|
||||
<section v-if="section.products" class="section" :class="{ 'section--gray': idx % 2 === 1 }">
|
||||
<div class="container">
|
||||
<h2 class="wiki-section-title">{{ section.title }}</h2>
|
||||
<p v-if="section.intro" class="wiki-text">{{ section.intro }}</p>
|
||||
<div class="wiki-products">
|
||||
<div v-for="(prod, pi) in section.products" :key="pi" class="wiki-product-card">
|
||||
<h3 class="wiki-product-name">{{ prod.name }}</h3>
|
||||
<p class="wiki-product-desc">{{ prod.description }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 服务流程 -->
|
||||
<section v-if="section.steps" class="section" :class="{ 'section--gray': idx % 2 === 1 }">
|
||||
<div class="container">
|
||||
<h2 class="wiki-section-title">{{ section.title }}</h2>
|
||||
<div class="wiki-steps">
|
||||
<div v-for="(s, si) in section.steps" :key="si" class="wiki-step">
|
||||
<div class="wiki-step-number">{{ s.step }}</div>
|
||||
<div class="wiki-step-body">
|
||||
<h4 class="wiki-step-name">{{ s.name }}</h4>
|
||||
<p class="wiki-step-detail">{{ s.detail }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 品牌文化(items列表) -->
|
||||
<section v-if="section.items" class="section" :class="{ 'section--gray': idx % 2 === 1 }">
|
||||
<div class="container">
|
||||
<h2 class="wiki-section-title">{{ section.title }}</h2>
|
||||
<div class="wiki-culture-list">
|
||||
<div v-for="(item, ii) in section.items" :key="ii" class="wiki-culture-item">
|
||||
<span class="wiki-culture-label">{{ item.label }}</span>
|
||||
<p class="wiki-culture-value">{{ item.value }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 普通文本段落(含可选图片) -->
|
||||
<section v-if="section.content && !section.subsections && !section.products && !section.steps && !section.items" class="section" :class="{ 'section--gray': idx % 2 === 1 }">
|
||||
<div class="container">
|
||||
<h2 class="wiki-section-title">{{ section.title }}</h2>
|
||||
<p class="wiki-text">{{ section.content }}</p>
|
||||
<div v-if="section.image" class="wiki-logo-img">
|
||||
<img :src="section.image" :alt="section.title" />
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<!-- 图库 -->
|
||||
<section v-if="data?.gallery?.length" class="section section--gray" aria-label="品牌图库">
|
||||
<div class="container">
|
||||
<h2 class="wiki-section-title">品牌图库</h2>
|
||||
<div class="wiki-gallery">
|
||||
<div v-for="(img, gi) in data.gallery" :key="gi" class="wiki-gallery-item">
|
||||
<img :src="img" :alt="`${data.title} 图片 ${gi + 1}`" loading="lazy" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- CTA -->
|
||||
<CommonSectionCTA
|
||||
title="想了解更多?"
|
||||
description="查看我们的旅行产品或直接联系定制师"
|
||||
:links="[
|
||||
{ to: '/products', text: '查看旅行产品' },
|
||||
{ to: '/contact', text: '联系定制师' },
|
||||
]"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { useBreadcrumbSchema } from '~/composables/useJsonLd'
|
||||
|
||||
const { data } = await usePublicApi('brand-wiki')
|
||||
|
||||
useSEO('brand-wiki', {
|
||||
title: data.value?.seo?.title || '呼籁旅行 - 专注呼伦贝尔的家庭旅行品牌',
|
||||
description: data.value?.seo?.description || '',
|
||||
})
|
||||
|
||||
useBreadcrumbSchema([
|
||||
{ text: '首页', to: '/' },
|
||||
{ text: '关于我们', to: '/about' },
|
||||
{ text: '呼籁旅行' },
|
||||
])
|
||||
|
||||
useScrollReveal()
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.wiki-header {
|
||||
display: flex;
|
||||
gap: @space-2xl;
|
||||
align-items: flex-start;
|
||||
|
||||
@media (max-width: 768px) {
|
||||
flex-direction: column;
|
||||
gap: @space-xl;
|
||||
}
|
||||
}
|
||||
|
||||
.wiki-summary {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.wiki-summary-inner {
|
||||
padding: @space-xl @space-2xl;
|
||||
background: @color-bg-warm;
|
||||
border-radius: @border-radius-lg;
|
||||
border-left: 4px solid @color-primary;
|
||||
}
|
||||
|
||||
.wiki-summary-heading {
|
||||
font-size: @font-size-xl;
|
||||
font-weight: @font-weight-bold;
|
||||
color: @color-primary;
|
||||
margin-bottom: @space-md;
|
||||
}
|
||||
|
||||
.wiki-summary-text {
|
||||
font-size: @font-size-base;
|
||||
line-height: 2;
|
||||
color: @color-text-primary;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.wiki-infobox {
|
||||
flex-shrink: 0;
|
||||
width: 360px;
|
||||
background: @color-bg-white;
|
||||
border-radius: @border-radius-lg;
|
||||
overflow: hidden;
|
||||
box-shadow: @shadow-card;
|
||||
|
||||
@media (max-width: 768px) {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.wiki-infobox-logo {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: @space-xl @space-lg;
|
||||
background: @color-primary-bg;
|
||||
border-bottom: 1px solid @color-divider;
|
||||
|
||||
img {
|
||||
max-width: 160px;
|
||||
height: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.wiki-infobox-body {
|
||||
padding: @space-sm 0;
|
||||
}
|
||||
|
||||
.wiki-infobox-row {
|
||||
display: flex;
|
||||
padding: @space-sm @space-lg;
|
||||
border-bottom: 1px solid @color-divider;
|
||||
gap: @space-md;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
|
||||
.wiki-infobox-label {
|
||||
flex-shrink: 0;
|
||||
width: 80px;
|
||||
font-size: @font-size-sm;
|
||||
font-weight: @font-weight-medium;
|
||||
color: @color-text-muted;
|
||||
padding-top: 1px;
|
||||
}
|
||||
|
||||
.wiki-infobox-value {
|
||||
flex: 1;
|
||||
font-size: @font-size-sm;
|
||||
line-height: 1.6;
|
||||
color: @color-text-primary;
|
||||
}
|
||||
|
||||
.wiki-section-title {
|
||||
font-size: @font-size-2xl;
|
||||
font-weight: @font-weight-bold;
|
||||
color: @color-text-primary;
|
||||
margin-bottom: @space-lg;
|
||||
padding-bottom: @space-sm;
|
||||
border-bottom: 2px solid @color-primary;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.wiki-text {
|
||||
font-size: @font-size-base;
|
||||
line-height: 1.8;
|
||||
color: @color-text-primary;
|
||||
max-width: 800px;
|
||||
}
|
||||
|
||||
.wiki-logo-img {
|
||||
margin-top: @space-lg;
|
||||
text-align: center;
|
||||
|
||||
img {
|
||||
max-width: 160px;
|
||||
height: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.wiki-subsection {
|
||||
margin-bottom: @space-lg;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.wiki-subsection-title {
|
||||
font-size: @font-size-lg;
|
||||
font-weight: @font-weight-bold;
|
||||
color: @color-primary;
|
||||
margin-bottom: @space-sm;
|
||||
}
|
||||
|
||||
.wiki-products {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
gap: @space-md;
|
||||
margin-top: @space-lg;
|
||||
|
||||
.respond-md({
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
});
|
||||
}
|
||||
|
||||
.wiki-product-card {
|
||||
padding: @space-lg;
|
||||
background: @color-bg-white;
|
||||
border: 1px solid @color-border;
|
||||
border-radius: @border-radius-lg;
|
||||
}
|
||||
|
||||
.wiki-product-name {
|
||||
font-size: @font-size-lg;
|
||||
font-weight: @font-weight-bold;
|
||||
color: @color-primary;
|
||||
margin-bottom: @space-sm;
|
||||
}
|
||||
|
||||
.wiki-product-desc {
|
||||
font-size: @font-size-sm;
|
||||
line-height: 1.7;
|
||||
color: @color-text-secondary;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.wiki-steps {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: @space-md;
|
||||
margin-top: @space-lg;
|
||||
}
|
||||
|
||||
.wiki-step {
|
||||
display: flex;
|
||||
gap: @space-md;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.wiki-step-number {
|
||||
flex-shrink: 0;
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
background: @color-primary;
|
||||
color: #fff;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-weight: @font-weight-bold;
|
||||
font-size: @font-size-sm;
|
||||
}
|
||||
|
||||
.wiki-step-body {
|
||||
flex: 1;
|
||||
padding-top: 4px;
|
||||
}
|
||||
|
||||
.wiki-step-name {
|
||||
font-size: @font-size-base;
|
||||
font-weight: @font-weight-bold;
|
||||
color: @color-text-primary;
|
||||
margin-bottom: @space-xs;
|
||||
}
|
||||
|
||||
.wiki-step-detail {
|
||||
font-size: @font-size-sm;
|
||||
line-height: 1.7;
|
||||
color: @color-text-secondary;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.wiki-culture-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: @space-lg;
|
||||
}
|
||||
|
||||
.wiki-culture-item {
|
||||
padding: @space-lg;
|
||||
background: @color-bg-white;
|
||||
border-left: 4px solid @color-primary;
|
||||
border-radius: 0 @border-radius-lg @border-radius-lg 0;
|
||||
box-shadow: @shadow-sm;
|
||||
}
|
||||
|
||||
.wiki-culture-label {
|
||||
display: block;
|
||||
font-size: @font-size-sm;
|
||||
font-weight: @font-weight-bold;
|
||||
color: @color-primary;
|
||||
margin-bottom: @space-xs;
|
||||
}
|
||||
|
||||
.wiki-culture-value {
|
||||
font-size: @font-size-base;
|
||||
line-height: 1.8;
|
||||
color: @color-text-primary;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.wiki-gallery {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: @space-md;
|
||||
margin-top: @space-lg;
|
||||
|
||||
.respond-md({
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
});
|
||||
|
||||
.respond-lg({
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
});
|
||||
}
|
||||
|
||||
.wiki-gallery-item {
|
||||
border-radius: @border-radius-lg;
|
||||
overflow: hidden;
|
||||
aspect-ratio: 16 / 10;
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
&:hover img {
|
||||
transform: scale(1.05);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
+7
-1
@@ -131,6 +131,12 @@
|
||||
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<CommonWechatCTA
|
||||
title="确定了出行时间?扫码找定制师"
|
||||
description="告诉定制师你的出行日期和家庭情况,帮你匹配最合适的行程方案。"
|
||||
:links="[{ to: '/products', text: '查看产品线' }]"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -186,7 +192,7 @@ const seasonalEvents = [
|
||||
{
|
||||
key: 'event-summer',
|
||||
name: '呼伦贝尔夏季旅游最佳期(6-8月)',
|
||||
description: '呼伦贝尔草原夏季旅游高峰,绿草如茵,那达慕传统节庆,骑马、露营、亲子出行最佳时段。呼籁旅行提供额吉的故乡V9系列定制游及小蒙马亲子夏令营。',
|
||||
description: '呼伦贝尔草原夏季旅游高峰,绿草如茵,那达慕传统节庆,骑马、露营、亲子出行最佳时段。呼籁旅行提供额吉的故乡V9系列定制游及小蒙马亲子游学营。',
|
||||
startDate: '2026-06-01',
|
||||
endDate: '2026-08-31',
|
||||
},
|
||||
|
||||
+484
@@ -0,0 +1,484 @@
|
||||
<template>
|
||||
<div class="page-wiki">
|
||||
<LayoutPageHero
|
||||
:title="data?.title || '呼籁文旅集团'"
|
||||
:subtitle="data?.subtitle"
|
||||
:breadcrumbs="[{ text: '关于我们', to: '/about' }, { text: '呼籁文旅集团' }]"
|
||||
background-image="/images/company/photo-03.jpeg"
|
||||
/>
|
||||
|
||||
<!-- 摘要 + 信息卡 -->
|
||||
<section class="section" aria-label="集团概述">
|
||||
<div class="container">
|
||||
<div class="wiki-header">
|
||||
<div class="wiki-summary">
|
||||
<div class="wiki-summary-inner">
|
||||
<h2 class="wiki-summary-heading">集团简介</h2>
|
||||
<p class="wiki-summary-text">{{ data?.summary }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="data?.infoBox" class="wiki-infobox">
|
||||
<div class="wiki-infobox-logo">
|
||||
<img :src="data.logo" :alt="data.title" />
|
||||
</div>
|
||||
<div class="wiki-infobox-body">
|
||||
<div v-for="(value, key) in data.infoBox" :key="key" class="wiki-infobox-row">
|
||||
<span class="wiki-infobox-label">{{ key }}</span>
|
||||
<span class="wiki-infobox-value">{{ value }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 各段落 -->
|
||||
<template v-for="(section, idx) in data?.sections" :key="idx">
|
||||
<!-- 发展历程(时间线) -->
|
||||
<section v-if="section.timeline" class="section" :class="{ 'section--gray': idx % 2 === 1 }">
|
||||
<div class="container">
|
||||
<h2 class="wiki-section-title">{{ section.title }}</h2>
|
||||
<div class="wiki-timeline">
|
||||
<div v-for="(item, ti) in section.timeline" :key="ti" class="wiki-timeline-item">
|
||||
<div class="wiki-timeline-marker">
|
||||
<div class="wiki-timeline-dot" />
|
||||
</div>
|
||||
<div class="wiki-timeline-card">
|
||||
<h4 class="wiki-timeline-year">{{ item.year }}</h4>
|
||||
<p class="wiki-timeline-text">{{ item.content }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 普通文本段落 -->
|
||||
<section v-if="section.content && !section.timeline && !section.subsidiaries && !section.businesses && !section.items && !section.images" class="section" :class="{ 'section--gray': idx % 2 === 1 }">
|
||||
<div class="container">
|
||||
<h2 class="wiki-section-title">{{ section.title }}</h2>
|
||||
<p class="wiki-text">{{ section.content }}</p>
|
||||
<div v-if="section.image" class="wiki-logo-img">
|
||||
<img :src="section.image" :alt="section.title" />
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 组织架构(子公司列表) -->
|
||||
<section v-if="section.subsidiaries" class="section" :class="{ 'section--gray': idx % 2 === 1 }">
|
||||
<div class="container">
|
||||
<h2 class="wiki-section-title">{{ section.title }}</h2>
|
||||
<p v-if="section.intro" class="wiki-text">{{ section.intro }}</p>
|
||||
<ul class="wiki-subsidiary-list">
|
||||
<li v-for="(sub, si) in section.subsidiaries" :key="si" class="wiki-subsidiary-item">
|
||||
{{ sub }}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 管理团队(带图片) -->
|
||||
<section v-if="section.content && section.images" class="section" :class="{ 'section--gray': idx % 2 === 1 }">
|
||||
<div class="container">
|
||||
<h2 class="wiki-section-title">{{ section.title }}</h2>
|
||||
<p class="wiki-text">{{ section.content }}</p>
|
||||
<div v-if="section.images?.length" class="wiki-team-images">
|
||||
<img v-for="(img, ii) in section.images" :key="ii" :src="img" :alt="`${section.title} ${ii + 1}`" loading="lazy" />
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 主要业务 -->
|
||||
<section v-if="section.businesses" class="section" :class="{ 'section--gray': idx % 2 === 1 }">
|
||||
<div class="container">
|
||||
<h2 class="wiki-section-title">{{ section.title }}</h2>
|
||||
<div class="wiki-businesses">
|
||||
<div v-for="(biz, bi) in section.businesses" :key="bi" class="wiki-business-card">
|
||||
<h3 class="wiki-business-name">{{ biz.name }}</h3>
|
||||
<p class="wiki-business-desc">{{ biz.description }}</p>
|
||||
<p v-if="biz.extra" class="wiki-business-extra">{{ biz.extra }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 企业文化(items列表) -->
|
||||
<section v-if="section.items" class="section" :class="{ 'section--gray': idx % 2 === 1 }">
|
||||
<div class="container">
|
||||
<h2 class="wiki-section-title">{{ section.title }}</h2>
|
||||
<div class="wiki-culture-list">
|
||||
<div v-for="(item, ii) in section.items" :key="ii" class="wiki-culture-item">
|
||||
<span class="wiki-culture-label">{{ item.label }}</span>
|
||||
<p class="wiki-culture-value">{{ item.value }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<!-- 图库 -->
|
||||
<section v-if="data?.gallery?.length" class="section section--gray" aria-label="集团图库">
|
||||
<div class="container">
|
||||
<h2 class="wiki-section-title">集团图库</h2>
|
||||
<div class="wiki-gallery">
|
||||
<div v-for="(img, gi) in data.gallery" :key="gi" class="wiki-gallery-item">
|
||||
<img :src="img" :alt="`${data.title} 图片 ${gi + 1}`" loading="lazy" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- CTA -->
|
||||
<CommonSectionCTA
|
||||
title="想了解更多?"
|
||||
description="了解呼籁旅行品牌或直接联系我们"
|
||||
:links="[
|
||||
{ to: '/brand', text: '了解呼籁旅行' },
|
||||
{ to: '/contact', text: '联系我们' },
|
||||
]"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { useBreadcrumbSchema } from '~/composables/useJsonLd'
|
||||
|
||||
const { data } = await usePublicApi('company-wiki')
|
||||
|
||||
useSEO('company-wiki', {
|
||||
title: data.value?.seo?.title || '内蒙古呼籁文旅投资开发集团有限公司',
|
||||
description: data.value?.seo?.description || '',
|
||||
})
|
||||
|
||||
useBreadcrumbSchema([
|
||||
{ text: '首页', to: '/' },
|
||||
{ text: '关于我们', to: '/about' },
|
||||
{ text: '呼籁文旅集团' },
|
||||
])
|
||||
|
||||
useScrollReveal()
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.wiki-header {
|
||||
display: flex;
|
||||
gap: @space-2xl;
|
||||
align-items: flex-start;
|
||||
|
||||
@media (max-width: 768px) {
|
||||
flex-direction: column;
|
||||
gap: @space-xl;
|
||||
}
|
||||
}
|
||||
|
||||
.wiki-summary {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.wiki-summary-inner {
|
||||
padding: @space-xl @space-2xl;
|
||||
background: @color-bg-warm;
|
||||
border-radius: @border-radius-lg;
|
||||
border-left: 4px solid @color-primary;
|
||||
}
|
||||
|
||||
.wiki-summary-heading {
|
||||
font-size: @font-size-xl;
|
||||
font-weight: @font-weight-bold;
|
||||
color: @color-primary;
|
||||
margin-bottom: @space-md;
|
||||
}
|
||||
|
||||
.wiki-summary-text {
|
||||
font-size: @font-size-base;
|
||||
line-height: 2;
|
||||
color: @color-text-primary;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.wiki-infobox {
|
||||
flex-shrink: 0;
|
||||
width: 360px;
|
||||
background: @color-bg-white;
|
||||
border-radius: @border-radius-lg;
|
||||
overflow: hidden;
|
||||
box-shadow: @shadow-card;
|
||||
|
||||
@media (max-width: 768px) {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.wiki-infobox-logo {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: @space-xl @space-lg;
|
||||
background: @color-primary-bg;
|
||||
border-bottom: 1px solid @color-divider;
|
||||
|
||||
img {
|
||||
max-width: 160px;
|
||||
height: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.wiki-infobox-body {
|
||||
padding: @space-sm 0;
|
||||
}
|
||||
|
||||
.wiki-infobox-row {
|
||||
display: flex;
|
||||
padding: @space-sm @space-lg;
|
||||
border-bottom: 1px solid @color-divider;
|
||||
gap: @space-md;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
|
||||
.wiki-infobox-label {
|
||||
flex-shrink: 0;
|
||||
width: 110px;
|
||||
font-size: @font-size-xs;
|
||||
font-weight: @font-weight-medium;
|
||||
color: @color-text-muted;
|
||||
padding-top: 2px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.wiki-infobox-value {
|
||||
flex: 1;
|
||||
font-size: @font-size-sm;
|
||||
line-height: 1.6;
|
||||
color: @color-text-primary;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.wiki-section-title {
|
||||
font-size: @font-size-2xl;
|
||||
font-weight: @font-weight-bold;
|
||||
color: @color-text-primary;
|
||||
margin-bottom: @space-lg;
|
||||
padding-bottom: @space-sm;
|
||||
border-bottom: 2px solid @color-primary;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.wiki-text {
|
||||
font-size: @font-size-base;
|
||||
line-height: 1.8;
|
||||
color: @color-text-primary;
|
||||
max-width: 800px;
|
||||
}
|
||||
|
||||
.wiki-logo-img {
|
||||
margin-top: @space-lg;
|
||||
text-align: center;
|
||||
|
||||
img {
|
||||
max-width: 160px;
|
||||
height: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.wiki-timeline {
|
||||
position: relative;
|
||||
max-width: 800px;
|
||||
margin: @space-lg 0 0;
|
||||
padding-left: @space-xl;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 5px;
|
||||
top: 8px;
|
||||
bottom: 0;
|
||||
width: 2px;
|
||||
background: @color-primary-lighter;
|
||||
}
|
||||
}
|
||||
|
||||
.wiki-timeline-item {
|
||||
position: relative;
|
||||
padding-bottom: @space-lg;
|
||||
|
||||
&:last-child {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.wiki-timeline-marker {
|
||||
position: absolute;
|
||||
left: ~"calc(-@{space-xl} + 1px)";
|
||||
top: 6px;
|
||||
}
|
||||
|
||||
.wiki-timeline-dot {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
background: @color-primary;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.wiki-timeline-card {
|
||||
background: @color-bg-white;
|
||||
border: 1px solid @color-border;
|
||||
border-radius: @border-radius-lg;
|
||||
padding: @space-md @space-lg;
|
||||
}
|
||||
|
||||
.wiki-timeline-year {
|
||||
font-size: @font-size-base;
|
||||
font-weight: @font-weight-bold;
|
||||
color: @color-primary;
|
||||
margin-bottom: @space-xs;
|
||||
}
|
||||
|
||||
.wiki-timeline-text {
|
||||
font-size: @font-size-sm;
|
||||
line-height: 1.7;
|
||||
color: @color-text-secondary;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.wiki-subsidiary-list {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: @space-lg 0 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: @space-sm;
|
||||
}
|
||||
|
||||
.wiki-subsidiary-item {
|
||||
padding: @space-md @space-lg;
|
||||
background: @color-bg-white;
|
||||
border: 1px solid @color-border;
|
||||
border-left: 3px solid @color-primary;
|
||||
border-radius: 0 @border-radius-md @border-radius-md 0;
|
||||
font-size: @font-size-sm;
|
||||
line-height: 1.6;
|
||||
color: @color-text-primary;
|
||||
}
|
||||
|
||||
.wiki-team-images {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
gap: @space-md;
|
||||
margin-top: @space-lg;
|
||||
|
||||
.respond-md({
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
});
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
border-radius: @border-radius-lg;
|
||||
object-fit: cover;
|
||||
}
|
||||
}
|
||||
|
||||
.wiki-businesses {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
gap: @space-md;
|
||||
margin-top: @space-lg;
|
||||
|
||||
.respond-md({
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
});
|
||||
|
||||
.respond-lg({
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
});
|
||||
}
|
||||
|
||||
.wiki-business-card {
|
||||
padding: @space-lg;
|
||||
background: @color-bg-white;
|
||||
border: 1px solid @color-border;
|
||||
border-radius: @border-radius-lg;
|
||||
}
|
||||
|
||||
.wiki-business-name {
|
||||
font-size: @font-size-lg;
|
||||
font-weight: @font-weight-bold;
|
||||
color: @color-primary;
|
||||
margin-bottom: @space-sm;
|
||||
}
|
||||
|
||||
.wiki-business-desc,
|
||||
.wiki-business-extra {
|
||||
font-size: @font-size-sm;
|
||||
line-height: 1.7;
|
||||
color: @color-text-secondary;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.wiki-business-extra {
|
||||
margin-top: @space-xs;
|
||||
}
|
||||
|
||||
.wiki-culture-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: @space-lg;
|
||||
}
|
||||
|
||||
.wiki-culture-item {
|
||||
padding: @space-lg;
|
||||
background: @color-bg-white;
|
||||
border-left: 4px solid @color-primary;
|
||||
border-radius: 0 @border-radius-lg @border-radius-lg 0;
|
||||
box-shadow: @shadow-sm;
|
||||
}
|
||||
|
||||
.wiki-culture-label {
|
||||
display: block;
|
||||
font-size: @font-size-sm;
|
||||
font-weight: @font-weight-bold;
|
||||
color: @color-primary;
|
||||
margin-bottom: @space-xs;
|
||||
}
|
||||
|
||||
.wiki-culture-value {
|
||||
font-size: @font-size-base;
|
||||
line-height: 1.8;
|
||||
color: @color-text-primary;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.wiki-gallery {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: @space-md;
|
||||
margin-top: @space-lg;
|
||||
|
||||
.respond-md({
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
});
|
||||
|
||||
.respond-lg({
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
});
|
||||
}
|
||||
|
||||
.wiki-gallery-item {
|
||||
border-radius: @border-radius-lg;
|
||||
overflow: hidden;
|
||||
aspect-ratio: 16 / 10;
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
&:hover img {
|
||||
transform: scale(1.05);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
+6
-6
@@ -3,7 +3,7 @@
|
||||
<LayoutPageHero
|
||||
:title="seo.h1"
|
||||
subtitle="小蒙马亲子研学·6大课程模块·3-12岁分龄设计"
|
||||
:breadcrumbs="[{ text: '小蒙马夏令营', to: '/summer-camp' }, { text: '研学课程体系' }]"
|
||||
:breadcrumbs="[{ text: '小蒙马亲子游学营', to: '/summer-camp' }, { text: '研学课程体系' }]"
|
||||
background-image="/images/summer-camp/birch-group.jpg"
|
||||
/>
|
||||
|
||||
@@ -93,10 +93,10 @@
|
||||
|
||||
<!-- 中间 CTA -->
|
||||
<CommonSectionCTA
|
||||
title="想了解小蒙马夏令营详情?"
|
||||
title="想了解小蒙马亲子游学营详情?"
|
||||
description="7天6晚亲子研学,每期限8组家庭,现在咨询了解营期安排"
|
||||
:links="[
|
||||
{ to: '/summer-camp', text: '查看小蒙马夏令营' },
|
||||
{ to: '/summer-camp', text: '查看小蒙马亲子游学营' },
|
||||
{ to: '/contact', text: '咨询报名' },
|
||||
]"
|
||||
/>
|
||||
@@ -204,9 +204,9 @@
|
||||
<!-- 底部 CTA -->
|
||||
<CommonSectionCTA
|
||||
title="准备好让孩子在草原上上课了吗?"
|
||||
description="报名小蒙马夏令营,或联系我们了解定制研学方案"
|
||||
description="报名小蒙马亲子游学营,或联系我们了解定制研学方案"
|
||||
:links="[
|
||||
{ to: '/summer-camp', text: '小蒙马夏令营详情' },
|
||||
{ to: '/summer-camp', text: '小蒙马亲子游学营详情' },
|
||||
{ to: '/customize', text: '定制研学方案' },
|
||||
]"
|
||||
/>
|
||||
@@ -223,7 +223,7 @@ useScrollReveal()
|
||||
|
||||
useBreadcrumbSchema([
|
||||
{ text: '首页', to: '/' },
|
||||
{ text: '小蒙马夏令营', to: '/summer-camp' },
|
||||
{ text: '小蒙马亲子游学营', to: '/summer-camp' },
|
||||
{ text: '研学课程体系' },
|
||||
])
|
||||
|
||||
|
||||
@@ -157,6 +157,12 @@
|
||||
{ to: '/contact', text: '咨询旅拍详情' },
|
||||
]"
|
||||
/>
|
||||
|
||||
<CommonWechatCTA
|
||||
title="想拥有同款旅拍?扫码聊聊"
|
||||
description="告诉定制师你喜欢的风格,旅拍随行程一并安排,提前备好服饰和取景方案。"
|
||||
bg="white"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
+1
-1
@@ -7,7 +7,7 @@
|
||||
<section class="page-tldr" aria-label="品牌身份摘要">
|
||||
<div class="container">
|
||||
<div class="page-tldr-inner">
|
||||
<p>呼籁旅行是深耕呼伦贝尔11年的专属旅行品牌,创立于2015年,已服务超10000个家庭。不做拼团、不走马观花;提供额吉的故乡V9系列定制游(4-7天)、小蒙马亲子夏令营(6天5晚)及秋冬季专属产品,全程一单一车、独立成团。</p>
|
||||
<p>呼籁旅行是深耕呼伦贝尔11年的专属旅行品牌,创立于2015年,已服务近万组家庭。不做拼团、不走马观花;提供额吉的故乡V9系列定制游(4-7天)、小蒙马亲子游学营(6天5晚)及秋冬季专属产品,全程专车专属、独立成团。</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
+2
-2
@@ -53,7 +53,7 @@
|
||||
<p>草原的风,已经在等您了。</p>
|
||||
<div class="news-cta-links">
|
||||
<CommonInternalLink to="/products" text="查看2026旅行产品" />
|
||||
<CommonInternalLink to="/summer-camp" text="小蒙马夏令营报名" />
|
||||
<CommonInternalLink to="/summer-camp" text="小蒙马亲子游学营报名" />
|
||||
<CommonInternalLink to="/contact" text="联系定制师" />
|
||||
</div>
|
||||
</div>
|
||||
@@ -95,7 +95,7 @@ useBreadcrumbSchema([
|
||||
author: { '@type': 'Organization', name: '呼籁旅行', url: 'https://1814.love' },
|
||||
publisher: { '@id': 'https://1814.love/#organization' },
|
||||
about: { '@type': 'TravelAgency', name: '呼籁旅行', url: 'https://1814.love' },
|
||||
keywords: '呼籁旅行,呼伦贝尔旅游,额吉的故乡,小蒙马夏令营',
|
||||
keywords: '呼籁旅行,呼伦贝尔旅游,额吉的故乡,小蒙马亲子游学营',
|
||||
}),
|
||||
}],
|
||||
})
|
||||
|
||||
+8
-3
@@ -36,7 +36,7 @@
|
||||
<div class="container">
|
||||
<CommonSectionTitle
|
||||
title="产品线价格参考"
|
||||
subtitle="6条定制游线路 + 1个亲子夏令营 + 秋季产品"
|
||||
subtitle="6条定制游线路 + 1个亲子游学营 + 秋季产品"
|
||||
/>
|
||||
<div class="pricing-grid">
|
||||
<div
|
||||
@@ -96,7 +96,7 @@
|
||||
</section>
|
||||
|
||||
<!-- 价格透明原则 -->
|
||||
<section v-if="pricing" class="section section-philosophy reveal">
|
||||
<section v-if="pricing?.philosophy" class="section section-philosophy reveal">
|
||||
<div class="container">
|
||||
<div class="philosophy-box">
|
||||
<h2 class="philosophy-title">{{ pricing.philosophy.title }}</h2>
|
||||
@@ -108,11 +108,16 @@
|
||||
|
||||
<!-- CTA -->
|
||||
<CommonSectionCTA
|
||||
v-if="pricing"
|
||||
v-if="pricing?.cta"
|
||||
:title="pricing.cta.title"
|
||||
:description="pricing.cta.description"
|
||||
:links="[{ to: pricing.cta.to, text: pricing.cta.button }]"
|
||||
/>
|
||||
|
||||
<CommonWechatCTA
|
||||
title="想了解具体报价?扫码问定制师"
|
||||
description="每个家庭的行程不同,精确报价需要根据出行时间、人数和偏好来定。扫码添加定制师微信,2小时内给你方案和报价。"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
+1
-1
@@ -9,7 +9,7 @@
|
||||
<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>
|
||||
<p class="hero-duration">{{ product.days }}天{{ product.nights }}晚 · 专车专属·独立成团</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<section class="page-tldr" aria-label="产品页摘要">
|
||||
<div class="container">
|
||||
<div class="page-tldr-inner">
|
||||
<p>游牧的森林V3系列提供4-7天6个版本的呼伦贝尔秋季定制游,涵盖南线(呼伦贝尔+阿尔山)和北线(呼伦贝尔+大兴安岭)两条主线。所有版本均为独立成团、一家一单一车,不与陌生人拼团。不确定选哪条线,可<NuxtLink to="/contact">联系定制师</NuxtLink>获取推荐。</p>
|
||||
<p>游牧的森林V3系列提供4-7天6个版本的呼伦贝尔秋季定制游,涵盖南线(呼伦贝尔+阿尔山)和北线(呼伦贝尔+大兴安岭)两条主线。所有版本均为独立成团、私家专车,不与陌生人同行。不确定选哪条线,可<NuxtLink to="/contact">联系定制师</NuxtLink>获取推荐。</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@
|
||||
<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>
|
||||
<p>额吉的故乡V9系列提供5-7天6个版本的呼伦贝尔亲子游,涵盖森林版、草原版、亲子版、探索版、旷野版等不同需求。所有版本均为独立成团、专车不拼,不与陌生人同车。不确定选哪个版本,可使用<NuxtLink to="/selector">选版工具</NuxtLink>进行1分钟匹配,或参考<NuxtLink to="/pricing">价格参考页</NuxtLink>了解费用区间。</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
+1
-1
@@ -95,7 +95,7 @@
|
||||
<!-- CTA -->
|
||||
<CommonSectionCTA
|
||||
title="想了解我们的产品?"
|
||||
description="查看额吉的故乡V9系列和小蒙马夏令营"
|
||||
description="查看额吉的故乡V9系列和小蒙马亲子游学营"
|
||||
:links="[
|
||||
{ to: '/products', text: '查看旅行产品' },
|
||||
{ to: '/contact', text: '联系定制师' },
|
||||
|
||||
+1
-1
@@ -11,7 +11,7 @@
|
||||
<section class="page-tldr" aria-label="客户评价摘要">
|
||||
<div class="container">
|
||||
<div class="page-tldr-inner">
|
||||
<p>呼籁旅行已服务超10000个家庭,本页收录来自真实客户的旅行评价,均为未经修改的原始反馈。可按出行场景(亲子、摄影、蜜月等)和关注维度(安全性、性价比、服务质量等)筛选,找到与自己情况最相似的参考评价。</p>
|
||||
<p>呼籁旅行已服务近万组家庭,本页收录来自真实客户的旅行评价,均为未经修改的原始反馈。可按出行场景(亲子、摄影、蜜月等)和关注维度(安全性、性价比、服务质量等)筛选,找到与自己情况最相似的参考评价。</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
+11
-12
@@ -70,7 +70,7 @@
|
||||
<div class="story-nav">
|
||||
<NuxtLink
|
||||
v-if="prevStory"
|
||||
:to="`/stories/${prevStory.id}`"
|
||||
:to="`/stories/${prevStory.slug}`"
|
||||
class="story-nav__item story-nav__item--prev"
|
||||
>
|
||||
<span class="story-nav__label">上一个故事</span>
|
||||
@@ -78,7 +78,7 @@
|
||||
</NuxtLink>
|
||||
<NuxtLink
|
||||
v-if="nextStory"
|
||||
:to="`/stories/${nextStory.id}`"
|
||||
:to="`/stories/${nextStory.slug}`"
|
||||
class="story-nav__item story-nav__item--next"
|
||||
>
|
||||
<span class="story-nav__label">下一个故事</span>
|
||||
@@ -106,7 +106,7 @@
|
||||
class="sidebar-story"
|
||||
:class="{ 'sidebar-story--active': s.id === story.id }"
|
||||
>
|
||||
<NuxtLink :to="`/stories/${s.id}`" class="sidebar-story__link">
|
||||
<NuxtLink :to="`/stories/${s.slug}`" class="sidebar-story__link">
|
||||
<span class="sidebar-story__family">{{ s.familyType }}</span>
|
||||
<span class="sidebar-story__title">{{ s.title }}</span>
|
||||
</NuxtLink>
|
||||
@@ -146,7 +146,7 @@ const { data: storiesData } = await usePublicApi('stories')
|
||||
|
||||
const route = useRoute()
|
||||
const allStories = computed(() => storiesData.value?.stories || [])
|
||||
const story = computed(() => allStories.value.find(s => s.id === route.params.id))
|
||||
const story = computed(() => allStories.value.find(s => s.slug === route.params.id))
|
||||
|
||||
if (!story.value) {
|
||||
throw createError({ statusCode: 404, statusMessage: '故事不存在' })
|
||||
@@ -154,23 +154,22 @@ if (!story.value) {
|
||||
|
||||
// SEO
|
||||
useHead({
|
||||
title: story.value.seo.title,
|
||||
title: story.value.seoTitle || story.value.title,
|
||||
meta: [
|
||||
{ name: 'description', content: story.value.seo.description },
|
||||
{ name: 'keywords', content: story.value.seo.keywords },
|
||||
{ name: 'author', content: story.value.authorName },
|
||||
{ name: 'description', content: story.value.seoDesc || story.value.subtitle },
|
||||
{ name: 'keywords', content: `${story.value.familyType},呼伦贝尔亲子游,呼籁旅行,客户故事` },
|
||||
{ name: 'article:published_time', content: story.value.travelDate + '-01T00:00:00+08:00' },
|
||||
{ name: 'geo.region', content: 'CN-NM' },
|
||||
{ name: 'geo.placename', content: '呼伦贝尔' },
|
||||
{ property: 'og:title', content: story.value.seo.title },
|
||||
{ property: 'og:description', content: story.value.seo.description },
|
||||
{ property: 'og:title', content: story.value.seoTitle || story.value.title },
|
||||
{ property: 'og:description', content: story.value.seoDesc || story.value.subtitle },
|
||||
{ property: 'og:type', content: 'article' },
|
||||
{ property: 'og:url', content: `https://1814.love/stories/${story.value.id}` },
|
||||
{ property: 'og:url', content: `https://1814.love/stories/${story.value.slug}` },
|
||||
{ property: 'og:site_name', content: '呼籁旅行' },
|
||||
{ property: 'og:locale', content: 'zh_CN' },
|
||||
],
|
||||
link: [
|
||||
{ rel: 'canonical', href: `https://1814.love/stories/${story.value.id}` },
|
||||
{ rel: 'canonical', href: `https://1814.love/stories/${story.value.slug}` },
|
||||
],
|
||||
})
|
||||
|
||||
|
||||
+1
-1
@@ -125,7 +125,7 @@ function getCardProps(story) {
|
||||
rel: 'noopener nofollow',
|
||||
}
|
||||
}
|
||||
return { to: `/stories/${story.id}` }
|
||||
return { to: `/stories/${story.slug}` }
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
+51
-30
@@ -2,8 +2,8 @@
|
||||
<div class="page-summer-camp">
|
||||
<LayoutPageHero
|
||||
:title="seo.h1"
|
||||
subtitle="呼伦贝尔草原亲子夏令营·6天5晚·每期8组家庭"
|
||||
:breadcrumbs="[{ text: '小蒙马夏令营' }]"
|
||||
subtitle="呼伦贝尔草原亲子游学营·6天5晚·每期7-9组家庭·5-12岁"
|
||||
:breadcrumbs="[{ text: '小蒙马亲子游学营' }]"
|
||||
background-image="/images/summer-camp/river-banner.jpg"
|
||||
/>
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
<section class="page-tldr" aria-label="夏令营摘要">
|
||||
<div class="container">
|
||||
<div class="page-tldr-inner">
|
||||
<p>小蒙马夏令营:呼伦贝尔草原亲子夏令营,6天5晚,每期限8组家庭,6-12岁儿童适合。亲子同行,非托管模式,草原骑马、篝火晚会、营地课程、牧民拜访等体验。7月旺季需提前2-3个月报名。</p>
|
||||
<p>小蒙马亲子游学营:呼伦贝尔草原亲子游学营2.0,6天5晚,每期7-9组家庭,5-12岁亲子同行。草原音乐会、写生创作、白桦林非遗研学、亲子马术、做列巴、1200米滑草畅玩,双领队+全程摄影师+餐食全含。2025年11期期期满员,2026年开设30期。</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -23,7 +23,7 @@
|
||||
<div class="camp-mascot">
|
||||
<img
|
||||
:src="images.mascot.xiaomengma"
|
||||
alt="小蒙马 - 小蒙马夏令营吉祥物,一只可爱的蒙古小马形象"
|
||||
alt="小蒙马 - 小蒙马亲子游学营吉祥物,一只可爱的蒙古小马形象"
|
||||
width="200"
|
||||
height="200"
|
||||
loading="lazy"
|
||||
@@ -34,7 +34,7 @@
|
||||
<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">每期7-9组家庭</span>
|
||||
<span class="camp-meta-item">亲子同行</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -49,7 +49,7 @@
|
||||
<div class="camp-photo">
|
||||
<img
|
||||
:src="images.summerCamp.river"
|
||||
alt="小蒙马亲子夏令营在莫日格勒河旁举旗合影,背景是蜿蜒的草原河流"
|
||||
alt="小蒙马亲子游学营在莫日格勒河旁举旗合影,背景是蜿蜒的草原河流"
|
||||
width="1600"
|
||||
height="1067"
|
||||
loading="lazy"
|
||||
@@ -59,7 +59,7 @@
|
||||
<div class="camp-photo">
|
||||
<img
|
||||
:src="images.summerCamp.grassland"
|
||||
alt="小蒙马夏令营亲子家庭在呼伦贝尔草原上合影,手持小蒙马标志牌"
|
||||
alt="小蒙马亲子游学营亲子家庭在呼伦贝尔草原上合影,手持小蒙马标志牌"
|
||||
width="1600"
|
||||
height="1067"
|
||||
loading="lazy"
|
||||
@@ -69,7 +69,7 @@
|
||||
<div class="camp-photo">
|
||||
<img
|
||||
:src="images.summerCamp.birch"
|
||||
alt="小蒙马夏令营亲子家庭在白桦林合影,家长和孩子们一起开心比耶"
|
||||
alt="小蒙马亲子游学营亲子家庭在白桦林合影,家长和孩子们一起开心比耶"
|
||||
width="1600"
|
||||
height="1067"
|
||||
loading="lazy"
|
||||
@@ -79,7 +79,7 @@
|
||||
<div class="camp-photo">
|
||||
<img
|
||||
:src="images.summerCamp.graduation"
|
||||
alt="小蒙马夏令营结营仪式,家长和孩子们手持结营证书在营地合影"
|
||||
alt="小蒙马亲子游学营结营仪式,家长和孩子们手持结营证书在营地合影"
|
||||
width="1600"
|
||||
height="1067"
|
||||
loading="lazy"
|
||||
@@ -93,12 +93,12 @@
|
||||
<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-value">{{ camp.sessions['2026'] }}</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>
|
||||
<span class="camp-stat-value">7-9</span>
|
||||
<span class="camp-stat-label">每期家庭数</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -108,7 +108,7 @@
|
||||
<CommonSectionCTA
|
||||
title="想了解营期安排和费用?"
|
||||
description="添加微信客服,获取最新营期时间和报名方式"
|
||||
:links="[{ to: '/contact', text: '咨询小蒙马夏令营' }]"
|
||||
:links="[{ to: '/contact', text: '咨询小蒙马亲子游学营' }]"
|
||||
/>
|
||||
|
||||
<!-- 四大原则 -->
|
||||
@@ -131,7 +131,11 @@
|
||||
<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 class="itinerary-content">
|
||||
<p class="itinerary-title">{{ day.title }}</p>
|
||||
<p v-if="day.summary" class="itinerary-text">{{ day.summary }}</p>
|
||||
<p v-if="day.distance" class="itinerary-distance">{{ day.distance }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -153,27 +157,27 @@
|
||||
<!-- 与V9的区别 -->
|
||||
<section v-if="camp" class="section reveal">
|
||||
<div class="container">
|
||||
<CommonSectionTitle title="小蒙马 vs 额吉的故乡" subtitle="两种产品,两种精彩" />
|
||||
<CommonSectionTitle title="小蒙马亲子游学营 vs 额吉的故乡" subtitle="两种产品,两种精彩" />
|
||||
<div class="compare-cards">
|
||||
<div class="compare-card compare-card--camp">
|
||||
<h3>小蒙马夏令营</h3>
|
||||
<h3>小蒙马亲子游学营</h3>
|
||||
<ul>
|
||||
<li>亲子同行,家长和孩子一起出发</li>
|
||||
<li>每期8组家庭结伴同行,孩子有玩伴</li>
|
||||
<li>草原写生+非遗研学+民俗体验</li>
|
||||
<li>设有「爸妈托管」时段,家长也能放松</li>
|
||||
<li>孩子有伙伴一起学、一起玩</li>
|
||||
<li>7-9组家庭拼团出发,孩子有同龄玩伴</li>
|
||||
<li>两位亲子领队全程带队,家长可以当甩手掌柜</li>
|
||||
<li>每天都有设计好的研学课程和亲子活动</li>
|
||||
<li>全程跟拍摄影师,每天精修照片发群</li>
|
||||
<li>餐食全含,不用操心吃什么</li>
|
||||
<li>6天5晚,节奏舒缓不赶路</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="compare-card compare-card--v9">
|
||||
<h3>额吉的故乡V9</h3>
|
||||
<h3>额吉的故乡·亲子版</h3>
|
||||
<ul>
|
||||
<li>全家一起出行,行程灵活自由</li>
|
||||
<li>一家一单一车,专属私家定制</li>
|
||||
<li>专属领队一对一服务</li>
|
||||
<li>行程可根据需求随时调整</li>
|
||||
<li>享受亲密家庭时光</li>
|
||||
<li>一单一团,你的家庭独享一辆车、一位领队</li>
|
||||
<li>路线更远,含满洲里,覆盖草原+森林+边境+城市</li>
|
||||
<li>正餐不含,可以自己探索当地美食</li>
|
||||
<li>累计10000+家庭选择,国家版权局版权认证产品</li>
|
||||
<li>行程灵活自由,按自己节奏走</li>
|
||||
<li>4-7天多版本可选</li>
|
||||
</ul>
|
||||
<NuxtLink to="/products" class="compare-link">查看V9系列 →</NuxtLink>
|
||||
@@ -204,7 +208,7 @@
|
||||
title="想了解家庭一起出行的产品?"
|
||||
description="额吉的故乡V9系列,一家一单一车,全家共享草原时光"
|
||||
:links="[
|
||||
{ to: '/contact', text: '咨询小蒙马夏令营' },
|
||||
{ to: '/contact', text: '咨询小蒙马亲子游学营' },
|
||||
{ to: '/products', text: '查看额吉的故乡V9系列' },
|
||||
]"
|
||||
/>
|
||||
@@ -222,7 +226,7 @@ useScrollReveal()
|
||||
|
||||
useBreadcrumbSchema([
|
||||
{ text: '首页', to: '/' },
|
||||
{ text: '小蒙马夏令营' },
|
||||
{ text: '小蒙马亲子游学营' },
|
||||
])
|
||||
useSummerCampSchema(camp.value)
|
||||
|
||||
@@ -427,13 +431,30 @@ const campFAQ = computed(() => camp.value?.faq || [])
|
||||
min-width: 32px;
|
||||
}
|
||||
|
||||
.itinerary-text {
|
||||
.itinerary-content {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.itinerary-title {
|
||||
font-size: @font-size-base;
|
||||
font-weight: @font-weight-medium;
|
||||
color: @color-text-primary;
|
||||
margin: 0 0 @space-xs;
|
||||
}
|
||||
|
||||
.itinerary-text {
|
||||
font-size: @font-size-sm;
|
||||
color: @color-text-secondary;
|
||||
line-height: @line-height-base;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.itinerary-distance {
|
||||
font-size: @font-size-xs;
|
||||
color: @color-text-muted;
|
||||
margin: @space-xs 0 0;
|
||||
}
|
||||
|
||||
// 核心活动
|
||||
.activities-grid {
|
||||
.grid-responsive();
|
||||
|
||||
在新工单中引用
屏蔽一个用户