轮播图可以预览
This commit is contained in:
@@ -1,4 +1,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { computed, ref } from 'vue'
|
||||||
|
|
||||||
interface HomeBannerSlide {
|
interface HomeBannerSlide {
|
||||||
title?: string
|
title?: string
|
||||||
eyebrow?: string
|
eyebrow?: string
|
||||||
@@ -12,14 +14,43 @@ interface Props {
|
|||||||
banners: HomeBannerSlide[]
|
banners: HomeBannerSlide[]
|
||||||
}
|
}
|
||||||
|
|
||||||
defineProps<Props>()
|
const props = defineProps<Props>()
|
||||||
|
|
||||||
|
const previewVisible = ref(false)
|
||||||
|
const previewIndex = ref(0)
|
||||||
|
|
||||||
|
const previewUrls = computed(() =>
|
||||||
|
props.banners
|
||||||
|
.map(slide => slide.image_url?.trim() || '')
|
||||||
|
.filter((url): url is string => Boolean(url))
|
||||||
|
)
|
||||||
|
|
||||||
|
function openPreview(slide: HomeBannerSlide) {
|
||||||
|
const url = slide.image_url?.trim()
|
||||||
|
if (!url) return
|
||||||
|
const index = previewUrls.value.indexOf(url)
|
||||||
|
if (index < 0) return
|
||||||
|
previewIndex.value = index
|
||||||
|
previewVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
function closePreview() {
|
||||||
|
previewVisible.value = false
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<section class="hero-board">
|
<section class="hero-board">
|
||||||
<el-carousel height="240px" indicator-position="outside" :interval="3600">
|
<el-carousel height="240px" indicator-position="outside" :interval="3600">
|
||||||
<el-carousel-item v-for="slide in banners" :key="slide.title || slide.image_url">
|
<el-carousel-item v-for="slide in banners" :key="slide.title || slide.image_url">
|
||||||
<div class="hero-slide" :class="[`tone-${slide.tone}`, { 'has-image': slide.image_url }]">
|
<div
|
||||||
|
class="hero-slide"
|
||||||
|
:class="[
|
||||||
|
`tone-${slide.tone}`,
|
||||||
|
{ 'has-image': slide.image_url, 'is-previewable': !!slide.image_url },
|
||||||
|
]"
|
||||||
|
@click="openPreview(slide)"
|
||||||
|
>
|
||||||
<img
|
<img
|
||||||
v-if="slide.image_url"
|
v-if="slide.image_url"
|
||||||
:src="slide.image_url"
|
:src="slide.image_url"
|
||||||
@@ -34,6 +65,15 @@ defineProps<Props>()
|
|||||||
</div>
|
</div>
|
||||||
</el-carousel-item>
|
</el-carousel-item>
|
||||||
</el-carousel>
|
</el-carousel>
|
||||||
|
|
||||||
|
<el-image-viewer
|
||||||
|
v-if="previewVisible"
|
||||||
|
:url-list="previewUrls"
|
||||||
|
:initial-index="previewIndex"
|
||||||
|
teleported
|
||||||
|
hide-on-click-modal
|
||||||
|
@close="closePreview"
|
||||||
|
/>
|
||||||
</section>
|
</section>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -69,6 +109,10 @@ defineProps<Props>()
|
|||||||
background: linear-gradient(135deg, #eff6ff 0%, #ffffff 58%, #fff7ed 100%);
|
background: linear-gradient(135deg, #eff6ff 0%, #ffffff 58%, #fff7ed 100%);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.hero-slide.is-previewable {
|
||||||
|
cursor: zoom-in;
|
||||||
|
}
|
||||||
|
|
||||||
.hero-slide.tone-blue {
|
.hero-slide.tone-blue {
|
||||||
background: linear-gradient(135deg, #eff6ff 0%, #dbeafe 56%, #fff7ed 100%);
|
background: linear-gradient(135deg, #eff6ff 0%, #dbeafe 56%, #fff7ed 100%);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -250,6 +250,10 @@
|
|||||||
color: #fff;
|
color: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mobile-banner.is-previewable {
|
||||||
|
cursor: zoom-in;
|
||||||
|
}
|
||||||
|
|
||||||
.mobile-banner.tone-warm {
|
.mobile-banner.tone-warm {
|
||||||
background: #9a3412;
|
background: #9a3412;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ import {
|
|||||||
type AWRecycleConfig,
|
type AWRecycleConfig,
|
||||||
type HomeBannerSlide,
|
type HomeBannerSlide,
|
||||||
} from '@/features/listings/api/homeConfig'
|
} from '@/features/listings/api/homeConfig'
|
||||||
import { showToast } from 'vant'
|
import { showImagePreview, showToast } from 'vant'
|
||||||
import AuthImage from '@/shared/components/business/AuthImage.vue'
|
import AuthImage from '@/shared/components/business/AuthImage.vue'
|
||||||
import MobileHomeFilterSheet, { type FilterSection } from './MobileHomeFilterSheet.vue'
|
import MobileHomeFilterSheet, { type FilterSection } from './MobileHomeFilterSheet.vue'
|
||||||
import {
|
import {
|
||||||
@@ -485,6 +485,21 @@ function openAWRecycle() {
|
|||||||
awRecyclePreviewVisible.value = true
|
awRecyclePreviewVisible.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function openBannerPreview(slide: HomeBannerSlide) {
|
||||||
|
const url = slide.image_url?.trim()
|
||||||
|
if (!url) return
|
||||||
|
const images = bannerSlides.value
|
||||||
|
.map(item => item.image_url?.trim() || '')
|
||||||
|
.filter((item): item is string => Boolean(item))
|
||||||
|
const startPosition = images.indexOf(url)
|
||||||
|
if (startPosition < 0) return
|
||||||
|
showImagePreview({
|
||||||
|
images,
|
||||||
|
startPosition,
|
||||||
|
closeable: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
async function onRefresh() {
|
async function onRefresh() {
|
||||||
refreshing.value = true
|
refreshing.value = true
|
||||||
try {
|
try {
|
||||||
@@ -830,7 +845,11 @@ syncMobileHomeQuery()
|
|||||||
<van-swipe-item v-for="slide in bannerSlides" :key="slide.title || slide.image_url">
|
<van-swipe-item v-for="slide in bannerSlides" :key="slide.title || slide.image_url">
|
||||||
<div
|
<div
|
||||||
class="mobile-banner"
|
class="mobile-banner"
|
||||||
:class="[`tone-${slide.tone}`, { 'has-image': slide.image_url }]"
|
:class="[
|
||||||
|
`tone-${slide.tone}`,
|
||||||
|
{ 'has-image': slide.image_url, 'is-previewable': !!slide.image_url },
|
||||||
|
]"
|
||||||
|
@click="openBannerPreview(slide)"
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
v-if="slide.image_url"
|
v-if="slide.image_url"
|
||||||
|
|||||||
Reference in New Issue
Block a user