Initial commit: gmTouringMiniApp project
This commit is contained in:
@@ -0,0 +1,127 @@
|
||||
<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>
|
||||
Reference in New Issue
Block a user