@@ -0,0 +1,27 @@
|
||||
import { Image, Text, View } from '@tarojs/components';
|
||||
import iconStoreBenefit from '../assets/icons/store-benefit.png';
|
||||
|
||||
type BenefitFigureSize = 'sm' | 'md' | 'lg' | 'xl';
|
||||
|
||||
type BenefitFigureProps = {
|
||||
value: string;
|
||||
size?: BenefitFigureSize;
|
||||
prefix?: string;
|
||||
className?: string;
|
||||
};
|
||||
|
||||
/** 好客权益金额:门店核销图标 + 数字,替代人民币符号 */
|
||||
export default function BenefitFigure({
|
||||
value,
|
||||
size = 'md',
|
||||
prefix = '',
|
||||
className = '',
|
||||
}: BenefitFigureProps) {
|
||||
return (
|
||||
<View className={`benefit-figure benefit-figure--${size} ${className}`.trim()}>
|
||||
{prefix ? <Text className="benefit-figure-prefix">{prefix}</Text> : null}
|
||||
<Image className="benefit-figure-icon" src={iconStoreBenefit} mode="aspectFit" />
|
||||
<Text className="benefit-figure-value">{value}</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Text } from '@tarojs/components';
|
||||
import { Text, View } from '@tarojs/components';
|
||||
import BenefitFigure from './BenefitFigure';
|
||||
|
||||
type CouponBadgeProps = {
|
||||
amount: number | string;
|
||||
@@ -10,8 +11,9 @@ export default function CouponBadge({ amount, label = '好客权益' }: CouponBa
|
||||
const n = Number(amount);
|
||||
const display = Number.isFinite(n) ? (Number.isInteger(n) ? String(n) : n.toFixed(0)) : String(amount);
|
||||
return (
|
||||
<Text className="coupon-badge">
|
||||
享 ¥{display} {label}
|
||||
</Text>
|
||||
<View className="coupon-badge">
|
||||
<Text>享</Text>
|
||||
<BenefitFigure value={`${display} ${label}`} size="sm" />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { CoverView, View } from '@tarojs/components';
|
||||
import { registerFloatingToastListener } from '../lib/floating-toast';
|
||||
|
||||
const TOAST_MS = 2600;
|
||||
|
||||
export default function FloatingToastHost() {
|
||||
const [text, setText] = useState('');
|
||||
const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
return registerFloatingToastListener((message) => {
|
||||
if (timerRef.current) clearTimeout(timerRef.current);
|
||||
setText(message);
|
||||
timerRef.current = setTimeout(() => {
|
||||
setText('');
|
||||
timerRef.current = null;
|
||||
}, TOAST_MS);
|
||||
});
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
if (timerRef.current) clearTimeout(timerRef.current);
|
||||
};
|
||||
}, []);
|
||||
|
||||
if (!text) return null;
|
||||
|
||||
const Box = process.env.TARO_ENV === 'weapp' ? CoverView : View;
|
||||
return <Box className="mu-float-toast">{text}</Box>;
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { PropsWithChildren, ReactNode } from 'react';
|
||||
import { View } from '@tarojs/components';
|
||||
import FloatingToastHost from './FloatingToastHost';
|
||||
import { pageShellCssVars, useNavBarMetrics } from '../lib/nav-bar';
|
||||
|
||||
type PageShellVariant = 'tab' | 'scroll' | 'sub' | 'plain';
|
||||
@@ -35,6 +36,7 @@ export default function PageShell({
|
||||
return (
|
||||
<View className={classes} style={pageShellCssVars(metrics)}>
|
||||
{children as ReactNode}
|
||||
<FloatingToastHost />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -14,6 +14,8 @@ type ProductCarouselProps = {
|
||||
* 依赖 swiper 原生 auto-height:海报有多高,轮播就有多高,无裁切。
|
||||
*/
|
||||
imageFit?: 'cover' | 'contain' | 'adaptive';
|
||||
/** 预览相册(默认等于 images);门店详情可传入封面+环境图合并列表 */
|
||||
previewUrls?: string[];
|
||||
};
|
||||
|
||||
/** 商品/门店轮播(对齐 h5 ProductCarousel 三变体) */
|
||||
@@ -23,6 +25,7 @@ export default function ProductCarousel({
|
||||
variant = 'detail',
|
||||
previewable = false,
|
||||
imageFit = 'cover',
|
||||
previewUrls,
|
||||
}: ProductCarouselProps) {
|
||||
const slides = images.length > 0 ? images : [''];
|
||||
const [activeIndex, setActiveIndex] = useState(0);
|
||||
@@ -33,10 +36,10 @@ export default function ProductCarousel({
|
||||
const wrapClass = `${prefix}-wrap${isContain ? ` ${prefix}-wrap--contain` : ''}${isAdaptive ? ` ${prefix}-wrap--adaptive` : ''}`;
|
||||
|
||||
function previewAt(index: number) {
|
||||
const urls = slides.filter(Boolean);
|
||||
if (!urls.length) return;
|
||||
const current = slides[index] || urls[0];
|
||||
Taro.previewImage({ current, urls }).catch(() => undefined);
|
||||
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 (
|
||||
|
||||
Reference in New Issue
Block a user