2026-07-31 19:02:00 +08:00
|
|
|
|
<script setup lang="ts">
|
|
|
|
|
|
import type { SectionBlock } from '~/types/site';
|
|
|
|
|
|
|
|
|
|
|
|
const props = defineProps<{ section: SectionBlock }>();
|
|
|
|
|
|
|
2026-08-03 19:09:03 +08:00
|
|
|
|
const { overline, title, subtitle, content, mediaUrl, isDark, overlineStyle, titleStyle, bodyStyle } = useSectionContent(props.section);
|
|
|
|
|
|
|
|
|
|
|
|
// 九宫格文案位置(config.text_position,默认左中):top/middle/bottom + left/center/right
|
|
|
|
|
|
const textPosition = computed(() => (props.section.config as Record<string, string> | null)?.text_position ?? 'middle_left');
|
|
|
|
|
|
|
|
|
|
|
|
// 垂直对齐(flex-col 主轴):top->start / middle->center / bottom->end
|
|
|
|
|
|
const vAlign = computed(() => ({
|
|
|
|
|
|
top: 'justify-start',
|
|
|
|
|
|
middle: 'justify-center',
|
|
|
|
|
|
bottom: 'justify-end'
|
|
|
|
|
|
} as Record<string, string>)[textPosition.value.split('_')[0] ?? ''] ?? 'justify-center');
|
|
|
|
|
|
|
|
|
|
|
|
// 水平对齐(flex-col 交叉轴):left->start / center->center / right->end
|
|
|
|
|
|
const hAlign = computed(() => ({
|
|
|
|
|
|
left: 'items-start',
|
|
|
|
|
|
center: 'items-center',
|
|
|
|
|
|
right: 'items-end'
|
|
|
|
|
|
} as Record<string, string>)[textPosition.value.split('_')[1] ?? ''] ?? 'items-start');
|
|
|
|
|
|
|
|
|
|
|
|
// 水平内边距:左列贴左、右列贴右、中列居中留白
|
|
|
|
|
|
const hPad = computed(() => ({
|
|
|
|
|
|
left: 'md:pl-128px md:pr-96px',
|
|
|
|
|
|
center: 'md:px-64px',
|
|
|
|
|
|
right: 'md:pl-96px md:pr-128px'
|
|
|
|
|
|
} as Record<string, string>)[textPosition.value.split('_')[1] ?? ''] ?? 'md:px-64px');
|
2026-08-05 18:36:01 +08:00
|
|
|
|
|
|
|
|
|
|
// 区块最小高度(config.min_height):未设置 = 沿用首屏高度规约(移动端 2/3 比例 + 桌面视口公式);
|
|
|
|
|
|
// 0 = 完全自适应内容(去上下内边距,高度由媒体素材擑开);>0 = 自定义最小高度 px
|
|
|
|
|
|
const minHeight = computed<number | null>(() => {
|
|
|
|
|
|
const v = (props.section.config as Record<string, unknown> | null)?.min_height;
|
|
|
|
|
|
return typeof v === 'number' && v >= 0 ? v : null;
|
|
|
|
|
|
});
|
|
|
|
|
|
const isAutoFit = computed(() => minHeight.value === 0);
|
|
|
|
|
|
|
|
|
|
|
|
const wrapClass = computed(() => {
|
|
|
|
|
|
const base = 'relative grid gap-48px px-16px md:px-0';
|
|
|
|
|
|
// 默认首屏高度:完整字面量供 UnoCSS 提取
|
|
|
|
|
|
return minHeight.value === null ? `aspect-[2/3] md:aspect-auto md:min-h-[max(680px,min(calc(92vh-64px),54vw))] ${base}` : base;
|
|
|
|
|
|
});
|
|
|
|
|
|
const wrapStyle = computed(() => (!isAutoFit.value && minHeight.value ? { minHeight: `${minHeight.value}px` } : {}));
|
|
|
|
|
|
|
|
|
|
|
|
// 自适应模式下文案区去掉上下内边距,区块高度严格等于内容高度;
|
|
|
|
|
|
// 其余模式支持 config.padding_y 覆盖(默认 64px)
|
|
|
|
|
|
const textPadClass = computed(() => (isAutoFit.value ? 'py-0' : 'py-[var(--sec-py,64px)]'));
|
|
|
|
|
|
|
|
|
|
|
|
// 文案块定位:默认桌面端绝对定位覆盖;自适应模式无前景媒体时改为文档流擑高(否则区块会塔缩),有媒体时保持覆盖
|
|
|
|
|
|
const textBlockClass = computed(() =>
|
|
|
|
|
|
isAutoFit.value && !mediaUrl.value ? 'py-0' : `${textPadClass.value} md:absolute md:inset-0`
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
// 自适应模式下媒体改为文档流(擑开容器高度,桌面端右半对齐);其余模式保持绝对定位铺右半
|
|
|
|
|
|
const mediaClass = computed(() =>
|
|
|
|
|
|
isAutoFit.value ? 'flex-center md:w-1/2 md:ml-auto' : 'flex-center md:absolute md:top-0 md:right-0 md:bottom-0 md:w-1/2'
|
|
|
|
|
|
);
|
2026-07-31 19:02:00 +08:00
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
<template>
|
2026-08-05 18:36:01 +08:00
|
|
|
|
<div :class="wrapClass" :style="wrapStyle">
|
|
|
|
|
|
<!-- 文案块:移动端文档流;桌面端全宽绝对定位 + 九宫格对齐(自适应无媒体时文档流擑高) -->
|
2026-08-03 19:09:03 +08:00
|
|
|
|
<div
|
2026-08-05 18:36:01 +08:00
|
|
|
|
class="relative z-10 flex flex-col gap-20px"
|
|
|
|
|
|
:class="[textBlockClass, vAlign, hAlign, hPad]"
|
2026-08-03 19:09:03 +08:00
|
|
|
|
>
|
|
|
|
|
|
<div class="flex flex-col gap-20px max-w-560px">
|
2026-08-04 19:18:35 +08:00
|
|
|
|
<p v-if="overline" class="text-14px font-semibold tracking-widest uppercase" :class="isDark ? 'text-primary-300' : 'text-primary'" :style="overlineStyle">
|
2026-08-03 19:09:03 +08:00
|
|
|
|
{{ overline }}
|
|
|
|
|
|
</p>
|
2026-08-05 18:36:01 +08:00
|
|
|
|
<h2 class="font-bold leading-tight" :class="isDark ? 'text-white' : 'text-gray-900 dark:text-white'" :style="titleStyle">
|
2026-08-03 19:09:03 +08:00
|
|
|
|
{{ 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-03 19:09:03 +08:00
|
|
|
|
<SectionCta :section="section" class="mt-8px" />
|
|
|
|
|
|
</div>
|
2026-07-31 19:02:00 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
|
2026-08-05 18:36:01 +08:00
|
|
|
|
<!-- 图片:移动端文档流;桌面端占右侧一半(自适应模式为文档流擑高) -->
|
|
|
|
|
|
<div v-if="mediaUrl" :class="mediaClass">
|
2026-07-31 19:02:00 +08:00
|
|
|
|
<img :src="mediaUrl" :alt="title" class="w-full max-w-560px rounded-16px object-cover shadow-lg" />
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</template>
|