rn_for_openharmony商城项目app实战-优惠券列表实现
本文介绍了电商App中优惠券列表页的实现方案。文章首先定义了优惠券的数据结构,包含金额、使用门槛、有效期等关键字段。然后通过Tab组件实现优惠券分类(可使用/已使用/已过期),并详细讲解了筛选逻辑。重点展示了优惠券卡片的设计:左侧金额区域采用红色背景,右侧显示优惠券信息,已使用和已过期的卡片会变灰并加盖状态印章。文章还提供了空状态处理方案,当没有优惠券时显示引导提示。整体实现采用了React Na
案例开源地址:https://atomgit.com/nutpi/rn_openharmony_buy
写在前面
优惠券是电商 App 的常见营销工具,用户领了优惠券下单时可以抵扣一部分金额。优惠券列表页展示用户拥有的所有优惠券,分为可使用、已使用、已过期三种状态。
这个页面的设计有点意思:优惠券卡片要做成那种左边是金额、右边是信息的样式,已使用和已过期的要有明显的视觉区分。这篇文章来实现这些效果。
优惠券数据结构
先看看优惠券数据长什么样:
interface Coupon {
id: number;
title: string; // 优惠券名称,如"新人专享"
discount: number; // 优惠金额
minAmount: number; // 最低消费金额
expireDate: string; // 过期日期
used: boolean; // 是否已使用
}
minAmount 是使用门槛,比如"满 100 减 20"里的 100。expireDate 是过期日期,过了这个日期优惠券就不能用了。
Tab 配置
优惠券列表有三个 Tab:可使用、已使用、已过期。
const tabs = [
{key: 'available', label: '可使用'},
{key: 'used', label: '已使用'},
{key: 'expired', label: '已过期'},
];
和订单列表的 Tab 类似,用数组配置方便遍历渲染。
引入依赖
import React, {useState} 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 CouponListScreen = () => {
const {coupons, navigate} = useApp();
const [activeTab, setActiveTab] = useState('available');
const now = new Date();
从全局状态拿优惠券列表。now 是当前时间,用来判断优惠券是否过期。
优惠券筛选
const filteredCoupons = coupons.filter(c => {
const expireDate = new Date(c.expireDate);
if (activeTab === 'available') return !c.used && expireDate >= now;
if (activeTab === 'used') return c.used;
if (activeTab === 'expired') return !c.used && expireDate < now;
return true;
});
根据当前 Tab 筛选优惠券:
- 可使用:未使用且未过期(
!c.used && expireDate >= now) - 已使用:已使用(
c.used) - 已过期:未使用但已过期(
!c.used && expireDate < now)
为什么已过期要判断未使用?
因为已使用的优惠券不应该出现在"已过期"里。一张优惠券要么是已使用,要么是已过期,不能同时是两种状态。如果用过了,就算过期了也应该显示在"已使用"里。
优惠券卡片渲染
优惠券卡片的渲染逻辑比较复杂,单独抽成函数:
const renderCoupon = ({item}: {item: Coupon}) => {
const isExpired = new Date(item.expireDate) < now;
const isDisabled = item.used || isExpired;
先判断是否过期和是否禁用。已使用或已过期的优惠券都是禁用状态,样式要变灰。
卡片结构
return (
<View style={[styles.couponCard, isDisabled && styles.disabledCard]}>
<View style={[styles.leftPart, isDisabled && styles.disabledLeft]}>
<Text style={[styles.currency, isDisabled && styles.disabledText]}>$</Text>
<Text style={[styles.discount, isDisabled && styles.disabledText]}>{item.discount}</Text>
<Text style={[styles.condition, isDisabled && styles.disabledText]}>满${item.minAmount}可用</Text>
</View>
左边是金额区域,红色背景(禁用时变灰色)。显示货币符号、优惠金额和使用条件。
条件样式的写法
[styles.leftPart, isDisabled && styles.disabledLeft]这种写法,当isDisabled为true时会应用disabledLeft样式,为false时数组里是false,React Native 会忽略它。
<View style={styles.rightPart}>
<Text style={[styles.title, isDisabled && styles.disabledText]}>{item.title}</Text>
<Text style={[styles.expire, isDisabled && styles.disabledText]}>有效期至 {item.expireDate}</Text>
右边是信息区域,显示优惠券名称和有效期。
状态印章
{item.used && (
<View style={styles.usedStamp}>
<Text style={styles.usedStampText}>已使用</Text>
</View>
)}
{isExpired && !item.used && (
<View style={styles.expiredStamp}>
<Text style={styles.expiredStampText}>已过期</Text>
</View>
)}
</View>
已使用和已过期的优惠券右上角有个斜着的印章,像盖了个章一样。用 transform: [{rotate: '-15deg'}] 实现旋转效果。
使用按钮
{!isDisabled && (
<TouchableOpacity style={styles.useBtn} onPress={() => navigate('home')}>
<Text style={styles.useBtnText}>去使用</Text>
</TouchableOpacity>
)}
</View>
);
};
可用的优惠券右边有个"去使用"按钮,点击跳转到首页去购物。禁用的优惠券不显示这个按钮。
页面主体
return (
<View style={styles.container}>
<Header title="我的优惠券" />
<View style={styles.tabs}>
{tabs.map(tab => (
<TouchableOpacity
key={tab.key}
style={[styles.tab, activeTab === tab.key && styles.activeTab]}
onPress={() => setActiveTab(tab.key)}>
<Text style={[styles.tabText, activeTab === tab.key && styles.activeTabText]}>
{tab.label}
</Text>
</TouchableOpacity>
))}
</View>
Tab 栏和订单列表类似,选中的 Tab 有下划线和红色文字。
空状态处理
{filteredCoupons.length === 0 ? (
<Empty
icon="🎫"
title={
activeTab === 'available' ? '暂无可用优惠券' :
activeTab === 'used' ? '暂无已使用优惠券' :
'暂无过期优惠券'
}
subtitle={activeTab === 'available' ? '去领取优惠券吧' : ''}
buttonText={activeTab === 'available' ? '去领券' : undefined}
onPress={activeTab === 'available' ? () => navigate('home') : undefined}
/>
) : (
<FlatList
data={filteredCoupons}
keyExtractor={item => item.id.toString()}
contentContainerStyle={styles.list}
renderItem={renderCoupon}
/>
)}
</View>
);
};
空状态的文案根据当前 Tab 不同而不同。"可使用"Tab 下显示"去领券"按钮,其他 Tab 不显示按钮(已使用和已过期的优惠券没什么可操作的)。
三元表达式嵌套
这里用了嵌套的三元表达式来决定 title。如果逻辑更复杂,建议抽成一个函数,不然可读性会很差。
样式详解
优惠券卡片的核心样式:
couponCard: {
flexDirection: 'row',
backgroundColor: '#fff',
borderRadius: 12,
marginBottom: 12,
overflow: 'hidden',
alignItems: 'center',
},
disabledCard: {opacity: 0.7},
卡片是横向布局,overflow: 'hidden' 让圆角生效(不然左边的红色区域会超出圆角)。禁用时整体透明度降低。
左边金额区域:
leftPart: {
width: 100,
backgroundColor: '#e74c3c',
justifyContent: 'center',
alignItems: 'center',
paddingVertical: 20,
alignSelf: 'stretch',
},
disabledLeft: {backgroundColor: '#ccc'},
discount: {fontSize: 32, fontWeight: 'bold', color: '#fff'},
condition: {fontSize: 11, color: 'rgba(255,255,255,0.8)', marginTop: 4},
固定宽度 100,红色背景。alignSelf: 'stretch' 让高度撑满整个卡片。金额用大号加粗字体,使用条件用小号半透明字体。
印章样式:
usedStamp: {
position: 'absolute',
top: 8,
right: 8,
transform: [{rotate: '-15deg'}],
},
usedStampText: {
fontSize: 12,
color: '#999',
borderWidth: 1,
borderColor: '#999',
paddingHorizontal: 6,
paddingVertical: 2,
borderRadius: 4,
},
绝对定位在右上角,旋转 -15 度。边框样式让它看起来像个印章。
Context 里的优惠券数据
看看全局状态里的优惠券数据:
const mockCoupons: Coupon[] = [
{id: 1, title: '新人专享', discount: 10, minAmount: 50, expireDate: '2025-02-28', used: false},
{id: 2, title: '满减优惠', discount: 20, minAmount: 100, expireDate: '2025-01-31', used: false},
{id: 3, title: '限时折扣', discount: 15, minAmount: 80, expireDate: '2025-01-15', used: true},
{id: 4, title: '会员专享', discount: 30, minAmount: 200, expireDate: '2025-03-15', used: false},
];
const [coupons, setCoupons] = useState<Coupon[]>(mockCoupons);
初始化了一些模拟数据,包括不同状态的优惠券。
使用优惠券的方法:
const useCoupon = (id: number) => {
setCoupons(prev => prev.map(c => (c.id === id ? {...c, used: true} : c)));
};
把指定优惠券的 used 设为 true。这个方法在结算页使用优惠券时调用。
优惠券倒计时
快过期的优惠券可以显示倒计时,提醒用户赶紧使用:
const getDaysLeft = (expireDate: string) => {
const diff = new Date(expireDate).getTime() - Date.now();
return Math.ceil(diff / (1000 * 60 * 60 * 24));
};
// 在卡片里
const daysLeft = getDaysLeft(item.expireDate);
{daysLeft <= 3 && daysLeft > 0 && (
<Text style={styles.urgentText}>还剩 {daysLeft} 天过期</Text>
)}
计算距离过期还有几天,如果小于等于 3 天就显示提醒。用红色文字,制造紧迫感。
领券功能
当前优惠券是写死的,正式项目应该有领券功能:
const claimCoupon = async (couponId: number) => {
// 调用后端接口领取优惠券
const newCoupon = await api.claimCoupon(couponId);
setCoupons(prev => [...prev, newCoupon]);
};
可以做一个领券中心页面,展示可领取的优惠券,用户点击领取后添加到自己的优惠券列表。
优惠券详情
点击优惠券可以查看详情,包括使用规则、适用商品范围等:
<TouchableOpacity onPress={() => navigate('couponDetail', {coupon: item})}>
{/* 卡片内容 */}
</TouchableOpacity>
详情页展示更多信息,比如"仅限指定商品使用"、"不可与其他优惠叠加"等规则说明。
写在最后
优惠券列表页的核心是状态筛选和视觉区分。三种状态(可使用、已使用、已过期)通过 Tab 切换,每种状态的优惠券有不同的样式:可用的是红色,禁用的是灰色,还有印章标记。
优惠券卡片的设计比较有特色,左边金额区域用醒目的颜色,右边是详细信息。这种设计让用户一眼就能看到优惠金额,是电商 App 常见的优惠券样式。
下一篇写优惠券选择页,在结算时选择要使用的优惠券。
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
更多推荐




所有评论(0)