2026-08-05 18:36:01 +08:00
|
|
|
|
<script setup lang="ts">
|
|
|
|
|
|
import type { SectionBlock } from '~/types/site';
|
|
|
|
|
|
import { resolveAssetUrl } from '~/utils/resolveAssetUrl';
|
|
|
|
|
|
|
|
|
|
|
|
const props = defineProps<{ section: SectionBlock }>();
|
|
|
|
|
|
|
|
|
|
|
|
const { overline, title, subtitle, content, mediaUrl, isDark, overlineStyle, titleStyle, bodyStyle } = useSectionContent(props.section);
|
|
|
|
|
|
|
|
|
|
|
|
// 媒体视频(video_url 有值时优先渲染视频,支持 /uploads/... 相对路径)
|
|
|
|
|
|
const videoUrl = computed(() => resolveAssetUrl(props.section.video_url));
|
|
|
|
|
|
|
|
|
|
|
|
// 文案水平对齐(config.text_position 九宫格仅取水平方向):left 左对齐(默认)/ center 居中 / right 右对齐
|
|
|
|
|
|
const hAlign = computed(() => {
|
|
|
|
|
|
const col = (props.section.config as Record<string, string> | null)?.text_position?.split('_')[1] ?? 'left';
|
|
|
|
|
|
return ({
|
|
|
|
|
|
left: { wrap: 'items-start', text: 'text-left' },
|
|
|
|
|
|
center: { wrap: 'items-center', text: 'text-center' },
|
|
|
|
|
|
right: { wrap: 'items-end', text: 'text-right' }
|
|
|
|
|
|
} as Record<string, { wrap: string; text: string }>)[col] ?? { wrap: 'items-start', text: 'text-left' };
|
|
|
|
|
|
});
|
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
<template>
|
|
|
|
|
|
<div class="section-container py-[var(--sec-py,96px)] flex flex-col gap-48px" :class="hAlign.wrap">
|
|
|
|
|
|
<!-- 标题区 -->
|
|
|
|
|
|
<div class="flex flex-col gap-16px max-w-800px" :class="hAlign.text">
|
|
|
|
|
|
<p v-if="overline" class="text-14px font-semibold tracking-widest uppercase" :class="isDark ? 'text-primary-300' : 'text-primary'" :style="overlineStyle">
|
|
|
|
|
|
{{ overline }}
|
|
|
|
|
|
</p>
|
|
|
|
|
|
<h2 class="font-bold leading-tight" :class="isDark ? 'text-white' : 'text-gray-900 dark:text-white'" :style="titleStyle">
|
|
|
|
|
|
{{ title }}
|
|
|
|
|
|
</h2>
|
|
|
|
|
|
<p v-if="subtitle" class="text-lg md:text-xl" :class="isDark ? 'text-gray-300' : 'text-gray-600 dark:text-gray-300'" :style="bodyStyle">
|
|
|
|
|
|
{{ subtitle }}
|
|
|
|
|
|
</p>
|
2026-08-13 16:48:05 +08:00
|
|
|
|
<div v-if="content" class="rich-text text-16px leading-relaxed" :class="isDark ? 'text-gray-400' : 'text-gray-500 dark:text-gray-400'" :style="bodyStyle" v-html="content" />
|
2026-08-05 18:36:01 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 媒体:视频优先,其次图片;填满内容容器(宽度跟随「内容区宽度」配置),高度按素材比例自适应 -->
|
|
|
|
|
|
<div v-if="videoUrl || mediaUrl" class="w-full">
|
|
|
|
|
|
<video
|
|
|
|
|
|
v-if="videoUrl"
|
|
|
|
|
|
:src="videoUrl"
|
|
|
|
|
|
class="w-full rounded-16px object-cover shadow-lg"
|
|
|
|
|
|
autoplay
|
|
|
|
|
|
muted
|
|
|
|
|
|
loop
|
|
|
|
|
|
playsinline
|
|
|
|
|
|
/>
|
|
|
|
|
|
<img v-else :src="mediaUrl" :alt="title" class="w-full rounded-16px object-cover shadow-lg" />
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</template>
|