86 lines
3.1 KiB
TypeScript
86 lines
3.1 KiB
TypeScript
import { useState } from 'react';
|
|
import { View, Image, Swiper, SwiperItem } from '@tarojs/components';
|
|
import Taro from '@tarojs/taro';
|
|
|
|
type ProductCarouselProps = {
|
|
images: string[];
|
|
alt: string;
|
|
variant?: 'home' | 'detail' | 'store';
|
|
previewable?: boolean;
|
|
/**
|
|
* cover=aspectFill 裁剪铺满(固定高度,可能裁切)
|
|
* contain=aspectFit 完整显示(固定高度,可能留白)
|
|
* adaptive=widthFix 按图片真实比例自适应高度,完整显示不裁剪(门店套餐详情用)
|
|
* 依赖 swiper 原生 auto-height:海报有多高,轮播就有多高,无裁切。
|
|
*/
|
|
imageFit?: 'cover' | 'contain' | 'adaptive';
|
|
/** 预览相册(默认等于 images);门店详情可传入封面+环境图合并列表 */
|
|
previewUrls?: string[];
|
|
};
|
|
|
|
/** 商品/门店轮播(对齐 h5 ProductCarousel 三变体) */
|
|
export default function ProductCarousel({
|
|
images,
|
|
alt,
|
|
variant = 'detail',
|
|
previewable = false,
|
|
imageFit = 'cover',
|
|
previewUrls,
|
|
}: ProductCarouselProps) {
|
|
const slides = images.length > 0 ? images : [''];
|
|
const [activeIndex, setActiveIndex] = useState(0);
|
|
const prefix =
|
|
variant === 'store' ? 'store-detail-carousel' : variant === 'home' ? 'home-carousel' : 'detail-carousel';
|
|
const isContain = imageFit === 'contain';
|
|
const isAdaptive = imageFit === 'adaptive';
|
|
const wrapClass = `${prefix}-wrap${isContain ? ` ${prefix}-wrap--contain` : ''}${isAdaptive ? ` ${prefix}-wrap--adaptive` : ''}`;
|
|
|
|
function previewAt(index: number) {
|
|
const album = (previewUrls?.length ? previewUrls : slides).filter(Boolean);
|
|
if (!album.length) return;
|
|
const current = slides[index] || album[0];
|
|
Taro.previewImage({ current, urls: album }).catch(() => undefined);
|
|
}
|
|
|
|
return (
|
|
<View className={wrapClass}>
|
|
<Swiper
|
|
className={prefix}
|
|
circular={slides.length > 1}
|
|
autoHeight={isAdaptive}
|
|
{...(variant === 'detail' && slides.length > 1 ? { autoplay: true, interval: 3500 } : {})}
|
|
onChange={(e) => setActiveIndex(e.detail.current)}
|
|
>
|
|
{slides.map((src, index) => (
|
|
<SwiperItem key={`${src}-${index}`} className={`${prefix}-item`}>
|
|
{src ? (
|
|
<Image
|
|
className={`${prefix}-image`}
|
|
src={src}
|
|
mode={isAdaptive ? 'widthFix' : isContain ? 'aspectFit' : 'aspectFill'}
|
|
alt={alt}
|
|
onClick={previewable ? () => previewAt(index) : undefined}
|
|
/>
|
|
) : (
|
|
<View className={`${prefix}-placeholder`} />
|
|
)}
|
|
</SwiperItem>
|
|
))}
|
|
</Swiper>
|
|
{slides.length > 1 ? (
|
|
<View className={`${prefix}-dots`}>
|
|
{slides.map((_, index) => (
|
|
<View
|
|
key={index}
|
|
className={`${prefix}-dot${index === activeIndex ? ` ${prefix}-dot--active` : ''}`}
|
|
/>
|
|
))}
|
|
</View>
|
|
) : null}
|
|
{variant === 'detail' && slides.length > 1 ? (
|
|
<View className={`${prefix}-counter`}>{activeIndex + 1}/{slides.length}</View>
|
|
) : null}
|
|
</View>
|
|
);
|
|
}
|