Files
admin/www/app/components/section/layouts/LayoutHeroSplit.vue
T
2026-08-13 16:48:05 +08:00

90 lines
4.5 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script setup lang="ts">
import type { SectionBlock } from '~/types/site';
const props = defineProps<{ section: SectionBlock }>();
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');
// 区块最小高度(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'
);
</script>
<template>
<div :class="wrapClass" :style="wrapStyle">
<!-- 文案块移动端文档流桌面端全宽绝对定位 + 九宫格对齐自适应无媒体时文档流擑高 -->
<div
class="relative z-10 flex flex-col gap-20px"
:class="[textBlockClass, vAlign, hAlign, hPad]"
>
<div class="flex flex-col gap-20px max-w-560px">
<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>
<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" />
<SectionCta :section="section" class="mt-8px" />
</div>
</div>
<!-- 图片移动端文档流桌面端占右侧一半自适应模式为文档流擑高 -->
<div v-if="mediaUrl" :class="mediaClass">
<img :src="mediaUrl" :alt="title" class="w-full max-w-560px rounded-16px object-cover shadow-lg" />
</div>
</div>
</template>