rn_for_openharmony商城项目app实战-优惠券选择实现
本文介绍了优惠券选择页面的实现,主要功能是在结算时筛选和选择适用的优惠券。与单纯展示的优惠券列表不同,该页面会根据订单金额判断优惠券是否可用,并将用户选择结果传回结算页。 关键实现点包括: 从页面参数获取订单金额和选择回调函数 将优惠券分为可用和不可用两组,不可用优惠券仍显示但标注原因 提供"不使用优惠券"选项 通过回调函数将选择结果返回结算页 视觉区分可用/不可用优惠券,并显
案例开源地址:https://atomgit.com/nutpi/rn_openharmony_buy
写在前面
上一篇写了优惠券列表,那是用户查看自己所有优惠券的地方。这篇要写的优惠券选择页,是在结算时选择要使用哪张优惠券。
这两个页面看起来差不多,但逻辑不同。优惠券列表是纯展示,优惠券选择要根据订单金额判断哪些优惠券可用、哪些不可用,用户选择后要把结果传回结算页。
和优惠券列表的区别
优惠券列表页:
- 展示所有优惠券(可用、已用、过期)
- 点击"去使用"跳转首页
- 纯展示,不涉及选择
优惠券选择页:
- 只展示未使用的优惠券
- 根据订单金额判断是否可用
- 点击选中优惠券,返回结算页
引入依赖
import React from 'react';
import {View, Text, FlatList, StyleSheet, TouchableOpacity} from 'react-native';
import {useApp} from '../store/AppContext';
import {Header} from '../components/Header';
import {Empty} from '../components/Empty';
import {Coupon} from '../types';
标准的列表页依赖,没什么特别的。
组件主体
export const CouponSelectScreen = () => {
const {coupons, screenParams, goBack} = useApp();
const minAmount = screenParams?.minAmount || 0;
const onSelect = screenParams?.onSelect;
从页面参数里拿两个东西:
minAmount:订单金额,用来判断优惠券是否可用onSelect:选择回调,选中优惠券后调用这个函数把结果传回去
这两个参数是结算页跳转时传过来的。
为什么用回调传递结果?
也可以用全局状态,但回调更灵活。结算页传一个函数过来,选择页调用这个函数把结果传回去,两个页面的耦合度很低。
优惠券分组
const availableCoupons = coupons.filter(c => !c.used && minAmount >= c.minAmount);
const unavailableCoupons = coupons.filter(c => !c.used && minAmount < c.minAmount);
把优惠券分成两组:
- 可用:未使用且订单金额满足最低消费(
minAmount >= c.minAmount) - 不可用:未使用但订单金额不满足最低消费
注意这里只筛选未使用的优惠券,已使用的不显示。已过期的也应该过滤掉,这里简化了没加过期判断。
为什么要显示不可用的优惠券?
让用户知道自己还有哪些优惠券,只是当前订单金额不够用。这样用户可能会多买点东西凑单,提高客单价。如果不显示,用户可能不知道自己有这些优惠券。
选择处理
const handleSelect = (coupon: Coupon | null) => {
if (onSelect) {
onSelect(coupon);
}
goBack();
};
选择优惠券后,调用回调函数把选中的优惠券传回去,然后返回上一页。
参数类型是 Coupon | null,因为用户也可以选择"不使用优惠券",这时候传 null。
优惠券卡片渲染
const renderCoupon = ({item, disabled}: {item: Coupon; disabled?: boolean}) => (
<TouchableOpacity
style={[styles.couponCard, disabled && styles.disabledCard]}
onPress={() => !disabled && handleSelect(item)}
disabled={disabled}
>
卡片接收 disabled 参数,不可用的优惠券点击无效。disabled 属性会阻止 onPress 触发,但为了保险,onPress 里也加了判断。
左边金额区域
<View style={[styles.leftPart, disabled && styles.disabledLeft]}>
<Text style={[styles.discount, disabled && styles.disabledText]}>${item.discount}</Text>
<Text style={[styles.condition, disabled && styles.disabledText]}>满${item.minAmount}可用</Text>
</View>
和优惠券列表一样,左边显示优惠金额和使用条件。不可用时变灰色。
右边信息区域
<View style={styles.rightPart}>
<Text style={[styles.title, disabled && styles.disabledText]}>{item.title}</Text>
<Text style={[styles.expire, disabled && styles.disabledText]}>有效期至 {item.expireDate}</Text>
{disabled && <Text style={styles.disabledReason}>订单金额不满足</Text>}
</View>
显示优惠券名称和有效期。不可用的优惠券下面显示原因:“订单金额不满足”。
为什么要显示不可用原因?
让用户知道为什么不能用这张优惠券。如果只是变灰不说原因,用户会困惑。告诉用户"订单金额不满足",用户就知道多买点东西就能用了。
选择指示器
{!disabled && <View style={styles.selectCircle}><Text style={styles.selectIcon}>○</Text></View>}
</TouchableOpacity>
);
可用的优惠券右边有个圆圈,表示可以选择。不可用的不显示这个圆圈。
不使用优惠券选项
return (
<View style={styles.container}>
<Header title="选择优惠券" />
<TouchableOpacity style={styles.noCouponBtn} onPress={() => handleSelect(null)}>
<Text style={styles.noCouponText}>不使用优惠券</Text>
</TouchableOpacity>
列表最上面有个"不使用优惠券"的选项。用户可能有优惠券但不想用(比如想留着下次用),点击这个选项传 null 回去。
列表渲染
{availableCoupons.length === 0 && unavailableCoupons.length === 0 ? (
<Empty icon="🎫" title="暂无优惠券" subtitle="去领取优惠券吧" />
) : (
<FlatList
data={[...availableCoupons, ...unavailableCoupons]}
keyExtractor={item => item.id.toString()}
contentContainerStyle={styles.list}
renderItem={({item}) => renderCoupon({item, disabled: minAmount < item.minAmount})}
ListHeaderComponent={
availableCoupons.length > 0 ? (
<Text style={styles.listHeader}>可用优惠券 ({availableCoupons.length})</Text>
) : null
}
/>
)}
</View>
);
};
把可用和不可用的优惠券合并成一个数组,可用的在前面。renderItem 里根据订单金额判断是否 disabled。
ListHeaderComponent 显示"可用优惠券 (X)"的标题,只在有可用优惠券时显示。
为什么不用 SectionList?
也可以用
SectionList把可用和不可用分成两个 section。但这里不可用的优惠券不需要单独的标题,用FlatList更简单。
样式
const styles = StyleSheet.create({
container: {flex: 1, backgroundColor: '#f5f5f5'},
noCouponBtn: {
backgroundColor: '#fff',
padding: 16,
marginBottom: 12,
alignItems: 'center',
},
noCouponText: {fontSize: 16, color: '#3498db'},
"不使用优惠券"按钮用蓝色文字,和普通优惠券卡片区分开。
listHeader: {fontSize: 14, color: '#666', marginBottom: 12},
couponCard: {
flexDirection: 'row',
backgroundColor: '#fff',
borderRadius: 12,
marginBottom: 12,
overflow: 'hidden',
alignItems: 'center',
},
disabledCard: {opacity: 0.6},
不可用的卡片透明度降低,视觉上变暗。
disabledReason: {fontSize: 12, color: '#e74c3c', marginTop: 4},
selectCircle: {paddingRight: 16},
selectIcon: {fontSize: 24, color: '#3498db'},
});
不可用原因用红色小字,选择圆圈用蓝色。
结算页如何调用
看看结算页是怎么跳转到优惠券选择页的:
// CheckoutScreen.tsx
const [selectedCoupon, setSelectedCoupon] = useState<Coupon | null>(null);
const handleSelectCoupon = () => {
navigate('couponSelect', {
minAmount: totalPrice,
onSelect: (coupon: Coupon | null) => {
setSelectedCoupon(coupon);
},
});
};
跳转时传入订单金额和选择回调。用户选择后,回调被调用,selectedCoupon 被更新,结算页显示选中的优惠券。
选中状态显示
当前实现点击就选中并返回,没有显示当前选中的是哪张。可以加个选中状态:
const currentCouponId = screenParams?.currentCouponId;
// 在卡片里
{!disabled && (
<View style={styles.selectCircle}>
<Text style={styles.selectIcon}>
{item.id === currentCouponId ? '●' : '○'}
</Text>
</View>
)}
结算页跳转时把当前选中的优惠券 ID 传过来,选择页显示哪张是选中的。选中的显示实心圆,未选中的显示空心圆。
最优优惠券推荐
可以自动推荐最优惠的优惠券:
const bestCoupon = availableCoupons.reduce((best, current) => {
if (!best) return current;
return current.discount > best.discount ? current : best;
}, null as Coupon | null);
// 在最优优惠券旁边显示标签
{item.id === bestCoupon?.id && (
<View style={styles.bestTag}>
<Text style={styles.bestTagText}>最优</Text>
</View>
)}
找出优惠金额最大的优惠券,在旁边显示"最优"标签,引导用户选择。
写在最后
优惠券选择页和优惠券列表页看起来相似,但职责不同。选择页要根据订单金额判断可用性,要支持选择和取消选择,要把结果传回结算页。
这种"选择并返回"的交互模式在电商 App 里很常见,地址选择也是类似的逻辑。核心是通过页面参数传递回调函数,选择完成后调用回调把结果传回去。
下一篇写消息列表,展示系统通知、订单消息等。
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
更多推荐

所有评论(0)