- 后台全面引入 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>
120 行
2.3 KiB
Vue
120 行
2.3 KiB
Vue
<template>
|
||
<section class="section section--warm">
|
||
<div class="container">
|
||
<div class="story-layout">
|
||
<!-- 左侧:配图 -->
|
||
<div class="story-photo">
|
||
<div class="story-photo-card">
|
||
<img
|
||
:src="images.brandStory.photo"
|
||
alt="呼籁旅行创始人在呼伦贝尔草原"
|
||
width="600"
|
||
height="800"
|
||
loading="lazy"
|
||
/>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 右侧:文字 -->
|
||
<div class="story-text">
|
||
<h2 class="story-title">{{ story.title }}</h2>
|
||
<div class="story-title-line" />
|
||
<div class="story-content">
|
||
<template v-if="story.paragraphs">
|
||
<p v-for="(para, i) in story.paragraphs" :key="i" class="story-para">{{ para }}</p>
|
||
</template>
|
||
<p v-else>{{ story.content }}</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
</template>
|
||
|
||
<script setup>
|
||
import images from '~/data/images.json'
|
||
defineProps({ story: { type: Object, required: true } })
|
||
</script>
|
||
|
||
<style lang="less" scoped>
|
||
.story-layout {
|
||
display: grid;
|
||
grid-template-columns: 1fr;
|
||
gap: @space-2xl;
|
||
|
||
.respond-lg({
|
||
grid-template-columns: 5fr 7fr;
|
||
gap: @space-3xl;
|
||
align-items: start;
|
||
});
|
||
}
|
||
|
||
// 配图
|
||
.story-photo {
|
||
.respond-lg({
|
||
position: sticky;
|
||
top: 100px;
|
||
});
|
||
}
|
||
|
||
.story-photo-card {
|
||
border-radius: @border-radius-lg;
|
||
overflow: hidden;
|
||
box-shadow: @shadow-lg;
|
||
|
||
img {
|
||
width: 100%;
|
||
display: block;
|
||
aspect-ratio: 3 / 4;
|
||
object-fit: cover;
|
||
object-position: center 30%;
|
||
|
||
.respond-lg({
|
||
aspect-ratio: 4 / 5;
|
||
});
|
||
}
|
||
}
|
||
|
||
// 文字
|
||
.story-text {
|
||
padding-top: @space-md;
|
||
}
|
||
|
||
.story-title {
|
||
font-size: @font-size-2xl;
|
||
font-weight: @font-weight-bold;
|
||
color: @color-text-primary;
|
||
margin: 0 0 @space-md;
|
||
line-height: @line-height-tight;
|
||
|
||
.respond-md({
|
||
font-size: @font-size-3xl;
|
||
});
|
||
}
|
||
|
||
.story-title-line {
|
||
width: 48px;
|
||
height: 3px;
|
||
background: @gradient-accent;
|
||
border-radius: 3px;
|
||
margin-bottom: @space-xl;
|
||
}
|
||
|
||
.story-content {
|
||
p {
|
||
font-size: @font-size-base;
|
||
line-height: 2;
|
||
color: @color-text-secondary;
|
||
}
|
||
|
||
.story-para {
|
||
text-indent: 2em;
|
||
margin-bottom: @space-lg;
|
||
|
||
&:last-child {
|
||
margin-bottom: 0;
|
||
}
|
||
}
|
||
}
|
||
</style>
|