Files
dukang/apps/mini-user/src/components/ProductCarousel.tsx
T
jacy fc2e5b65de
CI / verify (pull_request) Has been cancelled
v3.5.3版本更新1
2026-08-20 18:54:15 +08:00

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>
);
}