feat: 首页板块的轮播实现

This commit is contained in:
zoujing committed 2026-09-17 11:00:36 +08:00
1 parent f23d598328
commit d4790ba82c
4 files changed
+73 -33

No files matched your search

+47 -7
View File
@@ -11,16 +11,36 @@
* → 因此**高度由组件自持**(sizeCls,按 variant 给),不依赖父级;
* **宽度**仍由父级在宿主节点上分配(flex-[1.06] / flex-1)。
*/
import { computed } from "vue";
import { computed, ref } from "vue";
import { goCatalog } from "@/utils/nav";
const props = defineProps({
section: { type: Object, required: true },
variant: { type: String, default: "short" },
/**
* 卡内轮播图集。首页旅居卡传「每个分组一张代表图」(4 张),
* 传空则回落为板块封面单图(右二半高卡就是这种)。
*/
covers: { type: Array, default: () => [] },
});
const isTall = computed(() => props.variant === "tall");
/** 轮播图集:有 covers 用 covers,否则单张封面 —— 统一成一个数组,模板只判长度 */
const images = computed(() => {
if (props.covers?.length) return props.covers;
return props.section?.coverImage ? [props.section.coverImage] : [];
});
/** ≥2 张才起轮播:单张时 swiper 没有意义,还会白白多一层节点 */
const hasSlider = computed(() => images.value.length > 1);
const activeIndex = ref(0);
/** 取模防越界:图集长度变化(如热更新/接口返回变少)时进度点不会全部熄灭 */
const activeDot = computed(() => (images.value.length ? activeIndex.value % images.value.length : 0));
const onSwiperChange = (e) => {
activeIndex.value = Number(e.detail?.current || 0);
};
/**
* ⚠️ 卡片高度**由组件自持**,不吃父级传来的尺寸。
* 原因:小程序自定义组件是「宿主节点 → 内部根节点」两层,父级写在
@@ -66,7 +86,24 @@ const onTap = () => goCatalog(props.section.key);
hover-class="opacity-95"
@click="onTap"
>
<image class="absolute inset-0 w-full h-full" :src="section.coverImage" mode="aspectFill" />
<!-- 图:≥2 张自动轮播(首页旅居卡 = 酒店/民宿/公寓/露营 四个分组各一张),否则单图。
swiper 用 absolute inset-0 填满卡片 —— 根节点高度由 sizeCls 自持(226px),
百分比高度链路在小程序里太脆,靠绝对定位拿确定高度最稳。 -->
<swiper
v-if="hasSlider"
class="absolute inset-0 w-full h-full"
circular
autoplay
:interval="3500"
:duration="500"
:indicator-dots="false"
@change="onSwiperChange"
>
<swiper-item v-for="url in images" :key="url">
<image class="w-full h-full" :src="url" mode="aspectFill" />
</swiper-item>
</swiper>
<image v-else class="absolute inset-0 w-full h-full" :src="section.coverImage" mode="aspectFill" />
<!-- 遮罩:文字锚顶部 → 顶部最重 -->
<view class="absolute inset-0" :class="isTall ? 'scrim-tile-tall' : 'scrim-tile'" />
@@ -86,11 +123,14 @@ const onTap = () => goCatalog(props.section.key);
<AppIcon name="arrow" tone="white" :size="isTall ? 22 : 17" />
</view>
<!-- 通高卡右下角放轮播指示点 -->
<view v-if="isTall" class="absolute left-[15px] bottom-[23px] flex gap-[5px] z-[2]">
<view class="w-[14px] h-[5px] rounded-full bg-white" />
<view class="w-[5px] h-[5px] rounded-full bg-[rgba(255,255,255,0.45)]" />
<view class="w-[5px] h-[5px] rounded-full bg-[rgba(255,255,255,0.45)]" />
<!-- 通高卡左下角轮播指示点:张数 = 图集张数(旅居卡 4 个分组 → 4 个点) -->
<view v-if="isTall && hasSlider" class="absolute left-[15px] bottom-[23px] flex gap-[5px] z-[2]">
<view
v-for="(url, i) in images"
:key="`${url}-dot`"
class="h-[5px] rounded-full transition-all duration-300"
:class="i === activeDot ? 'w-[14px] bg-white' : 'w-[5px] bg-[rgba(255,255,255,0.45)]'"
/>
</view>
</view>
</template>