Files
gmTouringMiniApp/src/components/poi/PoiHeroGallery.vue
T

128 lines
2.9 KiB
Vue

<script setup lang="ts">
import type { PoiImageAsset } from '@/domain/poi'
import { computed, ref, watch } from 'vue'
import PoiImage from './PoiImage.vue'
interface Props {
images: ReadonlyArray<PoiImageAsset>
name: string
initialIndex?: number
height?: string | number
fallbackSrc?: string
showIndicator?: boolean
}
const props = withDefaults(defineProps<Props>(), {
initialIndex: 0,
height: '422rpx',
fallbackSrc: '/static/poi/placeholder.png',
showIndicator: true,
})
const emit = defineEmits<{
change: [index: number]
imageError: [image: PoiImageAsset, index: number, event: unknown]
}>()
const currentIndex = ref(0)
const galleryHeight = computed(() => (
typeof props.height === 'number' ? `${props.height}rpx` : props.height
))
const hasImages = computed(() => props.images.length > 0)
function clampIndex(index: number): number {
if (!hasImages.value)
return 0
return Math.min(Math.max(0, Math.floor(index)), props.images.length - 1)
}
function handleChange(event: { detail: { current: number } }): void {
currentIndex.value = clampIndex(Number(event.detail.current))
emit('change', currentIndex.value)
}
function handleImageError(image: PoiImageAsset, index: number, event: unknown): void {
emit('imageError', image, index, event)
}
watch(
() => [props.initialIndex, props.images.length],
() => {
currentIndex.value = clampIndex(props.initialIndex)
},
{ immediate: true },
)
</script>
<template>
<view class="poi-hero-gallery" :style="{ height: galleryHeight }">
<swiper
v-if="hasImages"
class="poi-hero-gallery__swiper"
:current="currentIndex"
:circular="images.length > 1"
:duration="300"
@change="handleChange"
>
<swiper-item v-for="(image, index) in images" :key="image.id">
<PoiImage
:src="image.url"
:alt="image.alt || `${name}图片${index + 1}`"
:fallback-src="fallbackSrc"
width="100%"
height="100%"
@error="handleImageError(image, index, $event)"
/>
</swiper-item>
</swiper>
<PoiImage
v-else
src=""
:alt="`${name}图片暂不可用`"
:fallback-src="fallbackSrc"
width="100%"
height="100%"
/>
<view
v-if="showIndicator && images.length > 1"
class="poi-hero-gallery__indicator"
aria-live="polite"
>
{{ currentIndex + 1 }} / {{ images.length }}
</view>
</view>
</template>
<style scoped lang="scss">
.poi-hero-gallery {
position: relative;
width: 100%;
overflow: hidden;
background: #e8eeeb;
}
.poi-hero-gallery__swiper {
width: 100%;
height: 100%;
}
.poi-hero-gallery__indicator {
position: absolute;
right: 28rpx;
bottom: 24rpx;
box-sizing: border-box;
min-width: 80rpx;
padding: 8rpx 18rpx;
font-size: 22rpx;
font-weight: 600;
line-height: 32rpx;
color: #fff;
text-align: center;
background: rgb(0 0 0 / 58%);
border-radius: 24rpx;
}
</style>