111 lines
4.7 KiB
Vue
111 lines
4.7 KiB
Vue
<script setup lang="ts">
|
||
import type { SectionBlock } from '~/types/site';
|
||
import { resolveAssetUrl } from '~/utils/resolveAssetUrl';
|
||
|
||
const props = defineProps<{ section: SectionBlock }>();
|
||
|
||
const { overline, title, subtitle, config, isEn, isDark, overlineStyle, titleStyle, bodyStyle } = useSectionContent(props.section);
|
||
|
||
type MosaicPattern = 'big_3' | 'half_2' | 'big_4' | 'big_2';
|
||
|
||
// 拼贴版式(config.mosaic_pattern):big_3 一大三小(默认)/ half_2 左右等分 / big_4 一大四小 / big_2 一大二小上下
|
||
const pattern = computed<MosaicPattern>(() => {
|
||
const p = config.value.mosaic_pattern;
|
||
return p === 'half_2' || p === 'big_4' || p === 'big_2' ? p : 'big_3';
|
||
});
|
||
|
||
// 各版式槽位数量
|
||
const slotCount: Record<MosaicPattern, number> = { big_3: 4, half_2: 2, big_4: 5, big_2: 3 };
|
||
|
||
// 槽位素材(config.mosaic_items):media_zh/_en 以语言后缀结尾,不命中数据层 resolveAssetUrls,需手动解析相对路径
|
||
const items = computed(() => {
|
||
const raw = (config.value.mosaic_items ?? []) as Array<{ type?: string; media_zh?: string; media_en?: string }>;
|
||
return Array.from({ length: slotCount[pattern.value] }, (_, i) => {
|
||
const it = raw[i] ?? {};
|
||
const src = (isEn.value ? it.media_en || it.media_zh : it.media_zh || it.media_en) || '';
|
||
return { type: it.type === 'video' ? 'video' : 'image', url: resolveAssetUrl(src) };
|
||
});
|
||
});
|
||
|
||
// 网格高度(config.min_height,与主视觉分屏共用字段):仅桌面端生效;
|
||
// 未设置 = 默认 16/9 宽高比;>0 = 固定高度 px(格子媒体 object-cover 裁剪填充),移动端保持单列 4/3 堆叠
|
||
const gridHeight = computed(() => {
|
||
const v = (props.section.config as Record<string, unknown> | null)?.min_height;
|
||
return typeof v === 'number' && v > 0 ? `${v}px` : '';
|
||
});
|
||
|
||
// 容器网格 class(完整字面量供 UnoCSS 提取;容器定高后行轨道均分,格子内媒体 object-cover 填充)
|
||
const gridClass = computed(() => {
|
||
const base = (
|
||
{
|
||
big_3: 'md:grid-cols-4 md:grid-rows-2',
|
||
half_2: 'md:grid-cols-2',
|
||
big_4: 'md:grid-cols-4 md:grid-rows-2',
|
||
big_2: 'md:grid-cols-2 md:grid-rows-2'
|
||
} as Record<MosaicPattern, string>
|
||
)[pattern.value];
|
||
// 自定义高度时改用固定高(md:h-[var(--mosaic-h)]),否则保持默认宽高比
|
||
const sizing = gridHeight.value
|
||
? 'md:h-[var(--mosaic-h)]'
|
||
: (
|
||
{
|
||
big_3: 'md:aspect-16/9',
|
||
half_2: '',
|
||
big_4: 'md:aspect-16/9',
|
||
big_2: 'md:aspect-16/9'
|
||
} as Record<MosaicPattern, string>
|
||
)[pattern.value];
|
||
return `${base} ${sizing}`;
|
||
});
|
||
|
||
// 格子跨行跨列 class(按槽位下标;移动端统一单列 4/3 堆叠)
|
||
function cellClass(idx: number) {
|
||
switch (pattern.value) {
|
||
case 'big_3':
|
||
if (idx === 0) return 'aspect-4/3 md:aspect-auto md:col-span-2 md:row-span-2';
|
||
if (idx === 3) return 'aspect-4/3 md:aspect-auto md:col-span-2';
|
||
return 'aspect-4/3 md:aspect-auto';
|
||
case 'half_2':
|
||
return 'aspect-4/3';
|
||
case 'big_4':
|
||
if (idx === 0) return 'aspect-4/3 md:aspect-auto md:col-span-2 md:row-span-2';
|
||
return 'aspect-4/3 md:aspect-auto';
|
||
case 'big_2':
|
||
if (idx === 0) return 'aspect-4/3 md:aspect-auto md:row-span-2';
|
||
return 'aspect-4/3 md:aspect-auto';
|
||
default:
|
||
return 'aspect-4/3';
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<template>
|
||
<div class="section-container py-[var(--sec-py,96px)]">
|
||
<!-- 标题区 -->
|
||
<div v-if="overline || title || subtitle" class="text-center max-w-640px mx-auto mb-56px">
|
||
<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="mt-12px font-bold" :class="isDark ? 'text-white' : 'text-gray-900 dark:text-white'" :style="titleStyle">
|
||
{{ title }}
|
||
</h2>
|
||
<p v-if="subtitle" class="mt-16px text-base" :class="isDark ? 'text-gray-400' : 'text-gray-500 dark:text-gray-400'" :style="bodyStyle">
|
||
{{ subtitle }}
|
||
</p>
|
||
</div>
|
||
|
||
<!-- 拼贴网格:自定义高度时通过 --mosaic-h 下发(仅桌面端) -->
|
||
<div class="grid gap-16px" :class="gridClass" :style="gridHeight ? { '--mosaic-h': gridHeight } : {}">
|
||
<div
|
||
v-for="(item, idx) in items"
|
||
:key="idx"
|
||
class="overflow-hidden rounded-16px bg-gray-100 dark:bg-gray-800"
|
||
:class="cellClass(idx)"
|
||
>
|
||
<video v-if="item.type === 'video' && item.url" :src="item.url" class="w-full h-full object-cover" autoplay muted loop playsinline preload="metadata" />
|
||
<img v-else-if="item.url" :src="item.url" :alt="title" class="w-full h-full object-cover" loading="lazy" />
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</template>
|