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

192 行
3.8 KiB
Vue

<template>
<section
class="page-hero"
:class="{
'page-hero--compact': compact,
'page-hero--has-bg': backgroundImage,
}"
>
<!-- 背景图模式 -->
<div v-if="backgroundImage" class="page-hero-bg">
<img :src="backgroundImage" :alt="title" class="page-hero-bg-img" />
<div class="page-hero-overlay" />
</div>
<!-- 无背景图时的装饰 -->
<div v-else class="page-hero-decor">
<div class="decor-circle decor-circle--1" />
<div class="decor-circle decor-circle--2" />
<div class="decor-line" />
</div>
<div class="container page-hero-content">
<CommonBreadcrumbNav :items="breadcrumbs" />
<h1 class="page-hero-title">{{ title }}</h1>
<p v-if="subtitle" class="page-hero-subtitle">{{ subtitle }}</p>
</div>
<div class="page-hero-curve">
<svg viewBox="0 0 1440 56" fill="none" preserveAspectRatio="none" aria-hidden="true">
<path d="M0 56h1440V28C1200 0 240 0 0 28v28z" fill="currentColor" />
</svg>
</div>
</section>
</template>
<script setup>
defineProps({
title: { type: String, required: true },
subtitle: { type: String, default: '' },
compact: { type: Boolean, default: false },
breadcrumbs: { type: Array, default: () => [] },
backgroundImage: { type: String, default: '' },
})
</script>
<style lang="less" scoped>
.page-hero {
background: @gradient-cool;
padding: @space-2xl 0 @space-3xl;
position: relative;
overflow: hidden;
.respond-md({
padding: @space-3xl 0 @space-4xl;
});
&--compact {
padding: @space-xl 0 @space-2xl;
}
&--has-bg {
padding: @space-2xl 0 @space-3xl;
min-height: 220px;
.respond-md({
padding: @space-4xl 0 100px;
min-height: 360px;
});
.page-hero-title {
color: #fff;
text-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
}
.page-hero-subtitle {
color: rgba(255, 255, 255, 0.9);
}
// 背景图模式下面包屑使用白色
:deep(.breadcrumb-link),
:deep(.breadcrumb-sep),
:deep(.breadcrumb-current) {
color: rgba(255, 255, 255, 0.85);
}
:deep(.breadcrumb-link:hover) {
color: #fff;
}
}
}
// 背景图
.page-hero-bg {
position: absolute;
inset: 0;
z-index: 0;
}
.page-hero-bg-img {
width: 100%;
height: 100%;
object-fit: cover;
}
.page-hero-overlay {
position: absolute;
inset: 0;
background: linear-gradient(
to bottom,
rgba(27, 67, 50, 0.55) 0%,
rgba(27, 67, 50, 0.7) 100%
);
}
// 装饰元素(无背景图时)
.page-hero-decor {
position: absolute;
inset: 0;
pointer-events: none;
overflow: hidden;
}
.decor-circle {
position: absolute;
border-radius: 50%;
border: 1px solid rgba(45, 106, 79, 0.08);
&--1 {
width: 400px;
height: 400px;
top: -200px;
right: -100px;
background: radial-gradient(circle, rgba(45, 106, 79, 0.04) 0%, transparent 70%);
}
&--2 {
width: 200px;
height: 200px;
bottom: -60px;
left: 10%;
background: radial-gradient(circle, rgba(212, 136, 58, 0.05) 0%, transparent 70%);
}
}
.decor-line {
position: absolute;
top: 50%;
right: 5%;
width: 120px;
height: 1px;
background: linear-gradient(90deg, transparent, rgba(45, 106, 79, 0.15), transparent);
transform: rotate(-30deg);
}
// 内容
.page-hero-content {
position: relative;
z-index: 1;
}
.page-hero-title {
margin-top: @space-md;
color: @color-text-primary;
}
.page-hero-subtitle {
margin-top: @space-sm;
font-size: @font-size-md;
color: @color-text-secondary;
max-width: 600px;
line-height: @line-height-relaxed;
}
.page-hero-curve {
position: absolute;
bottom: -1px;
left: 0;
right: 0;
line-height: 0;
color: #FAFAF8;
z-index: 1;
svg {
width: 100%;
height: 36px;
.respond-md({
height: 56px;
});
}
}
</style>