Flutter for OpenHarmony 商城App实战 - 优惠券实现

优惠券是电商应用中重要的营销工具。用户可以获取、查看、使用优惠券来享受购物优惠。一个完整的优惠券系统需要支持多种优惠券类型、有效期管理、使用状态追踪等功能。本文将详细讲解如何在 Flutter for OpenHarmony 项目中实现一个功能完整的优惠券管理系统,包括优惠券展示、分类管理、有效期提示和使用操作等功能。

优惠券类型定义

优惠券支持多种类型,包括折扣、满减和包邮。

dart 复制代码
enum CouponType {
  percentage,    // 折扣券
  fixed,         // 满减券
  freeShipping,  // 包邮券
}

这个枚举定义了三种优惠券类型:

优惠券类型:

  • percentage:折扣券,如9折、8折
  • fixed:满减券,如满100减20
  • freeShipping:包邮券,满足条件包邮

用途:

  • 区分不同的优惠方式
  • 支持不同的优惠计算逻辑
  • 便于UI展示和用户理解

优惠券数据模型

优惠券模型包含优惠信息和有效期管理。

dart 复制代码
class Coupon {
  const Coupon({
    required this.id,
    required this.code,
    required this.type,
    required this.value,
    required this.minOrderAmount,
    required this.expiresAt,
    this.isUsed = false,
    this.description,
  });

  final String id;
  final String code;
  final CouponType type;
  final double value;
  final double minOrderAmount;
  final DateTime expiresAt;
  final bool isUsed;
  final String? description;
}

这个优惠券模型包含了所有必要的优惠信息:

基本信息:

  • id:优惠券唯一标识
  • code:优惠券代码,如"WELCOME10"
  • type:优惠券类型
  • value:优惠值(折扣百分比或减少金额)

使用条件:

  • minOrderAmount:最低订单金额
  • expiresAt:过期时间
  • isUsed:是否已使用
  • description:优惠券描述

优惠券显示值

根据优惠券类型生成显示文本。

dart 复制代码
String get displayValue {
  switch (type) {
    case CouponType.percentage:
      return '${value.toInt()}折';
    case CouponType.fixed:
      return '减¥${value.toStringAsFixed(0)}';
    case CouponType.freeShipping:
      return '包邮';
  }
}

这个方法根据优惠券类型生成用户友好的显示文本:

显示格式:

  • 折扣券:显示为"9折"、"8折"等
  • 满减券:显示为"减¥20"、"减¥50"等
  • 包邮券:显示为"包邮"

用途:

  • 在优惠券卡片上显示
  • 帮助用户快速理解优惠内容
  • 统一的格式展示

优惠券有效期判断

判断优惠券是否过期和是否有效。

dart 复制代码
bool get isExpired => DateTime.now().isAfter(expiresAt);

bool get isValid => !isUsed && !isExpired;

这两个属性用于判断优惠券的状态:

过期判断:

  • isExpired:当前时间是否在过期时间之后
  • 用于判断优惠券是否已过期

有效判断:

  • isValid:优惠券是否可用
  • 需要同时满足:未使用且未过期
  • 用于筛选可用优惠券

应用场景:

  • 显示优惠券状态
  • 筛选可用优惠券
  • 禁用已过期或已使用的优惠券

优惠券列表页面

优惠券列表页面使用Tab分类显示不同状态的优惠券。

dart 复制代码
class CouponsPage extends StatelessWidget {
  const CouponsPage({super.key});

  @override
  Widget build(BuildContext context) {
    final appState = AppStateScope.of(context);

    return DefaultTabController(
      length: 3,
      child: Scaffold(
        appBar: AppBar(
          title: const Text('我的优惠券'),
          bottom: const TabBar(
            tabs: [
              Tab(text: '可用'),
              Tab(text: '已用'),
              Tab(text: '已过期'),
            ],
          ),
        ),
        body: AnimatedBuilder(
          animation: appState,
          builder: (context, _) {
            final coupons = appState.coupons;
            final available = coupons.where((c) => c.isValid).toList();
            final used = coupons.where((c) => c.isUsed).toList();
            final expired = coupons.where((c) => c.isExpired && !c.isUsed).toList();

            return TabBarView(
              children: [
                _CouponList(coupons: available, emptyMessage: '暂无可用优惠券'),
                _CouponList(coupons: used, emptyMessage: '暂无已用优惠券'),
                _CouponList(coupons: expired, emptyMessage: '暂无过期优惠券'),
              ],
            );
          },
        ),
      ),
    );
  }
}

这个优惠券列表页面使用Tab分类管理:

页面结构:

  • AppBar:显示标题"我的优惠券"
  • TabBar:三个Tab分别显示可用、已用、已过期
  • TabBarView:对应的优惠券列表

优惠券分类:

  • 可用:未使用且未过期的优惠券
  • 已用:已使用的优惠券
  • 已过期:已过期且未使用的优惠券

状态管理:

  • 使用 AnimatedBuilder 监听状态变化
  • 自动过滤优惠券列表
  • 实时更新Tab内容

优惠券列表组件

优惠券列表组件显示优惠券列表或空状态。

dart 复制代码
class _CouponList extends StatelessWidget {
  const _CouponList({
    required this.coupons,
    required this.emptyMessage,
  });

  final List<Coupon> coupons;
  final String emptyMessage;

  @override
  Widget build(BuildContext context) {
    if (coupons.isEmpty) {
      return Center(
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            Icon(
              Icons.card_giftcard,
              size: 80,
              color: Colors.grey.shade400,
            ),
            const SizedBox(height: 16),
            Text(emptyMessage),
          ],
        ),
      );
    }

    return ListView.builder(
      padding: const EdgeInsets.all(16),
      itemCount: coupons.length,
      itemBuilder: (context, index) {
        final coupon = coupons[index];
        return Padding(
          padding: const EdgeInsets.only(bottom: 12),
          child: _CouponCard(coupon: coupon),
        );
      },
    );
  }
}

这个列表组件处理优惠券的显示和空状态:

功能特性:

  • 显示优惠券列表
  • 处理空状态显示
  • 使用 ListView.builder 高效渲染

空状态显示:

  • 礼物卡图标表示优惠券
  • 自定义空状态提示文本
  • 居中显示提高视觉效果

列表显示:

  • 每个优惠券占用一行
  • 底部间距12像素
  • 整体内边距16像素

优惠券卡片左侧

显示优惠券的优惠值。

dart 复制代码
Container(
  width: 80,
  height: 80,
  decoration: BoxDecoration(
    color: isValid
        ? Theme.of(context).colorScheme.primaryContainer
        : Colors.grey.shade200,
    borderRadius: BorderRadius.circular(8),
  ),
  child: Center(
    child: Text(
      coupon.displayValue,
      textAlign: TextAlign.center,
      style: TextStyle(
        fontWeight: FontWeight.bold,
        color: isValid
            ? Theme.of(context).colorScheme.primary
            : Colors.grey,
      ),
    ),
  ),
)

这个卡片左侧显示优惠券的优惠值:

设计特点:

  • 固定尺寸:80x80像素的正方形
  • 圆角:8像素圆角边框
  • 背景色:有效优惠券使用主题色,已过期使用灰色

内容显示:

  • 显示 displayValue(如"9折"、"减¥20")
  • 粗体显示便于识别
  • 文本居中对齐

状态区分:

  • 有效优惠券:彩色背景和文字
  • 已过期优惠券:灰色背景和文字
  • 视觉上清晰区分

优惠券卡片右侧信息

显示优惠券的详细信息。

dart 复制代码
Expanded(
  child: Column(
    crossAxisAlignment: CrossAxisAlignment.start,
    children: [
      Text(
        coupon.code,
        style: const TextStyle(fontWeight: FontWeight.bold),
      ),
      if (coupon.description != null) ...[
        const SizedBox(height: 4),
        Text(
          coupon.description!,
          style: Theme.of(context).textTheme.bodySmall,
        ),
      ],
      const SizedBox(height: 4),
      Text(
        '满¥${coupon.minOrderAmount.toStringAsFixed(0)}可用',
        style: Theme.of(context).textTheme.bodySmall,
      ),
      const SizedBox(height: 4),
      Text(
        '有效期至:${coupon.expiresAt.year}/${coupon.expiresAt.month}/${coupon.expiresAt.day}',
        style: TextStyle(
          fontSize: 12,
          color: coupon.isExpired ? Colors.red : Colors.grey,
        ),
      ),
    ],
  ),
)

这个卡片右侧显示优惠券的详细信息:

显示内容:

  • 优惠券代码:如"WELCOME10",粗体显示
  • 描述:如"新用户专享折扣"(可选)
  • 使用条件:最低订单金额
  • 有效期:过期时间

信息排列:

  • 从上到下依次显示
  • 使用 SizedBox 控制间距
  • 灵活的列宽度

状态提示:

  • 过期优惠券的有效期显示为红色
  • 未过期优惠券的有效期显示为灰色
  • 清晰的视觉区分

优惠券卡片使用按钮

显示优惠券的使用按钮。

dart 复制代码
if (isValid)
  ShopButton(
    label: '使用',
    isPrimary: false,
    onPressed: () =>
        Navigator.of(context).pushNamed(AppRoutes.shop),
  )

这个按钮用于使用优惠券:

按钮特性:

  • 条件显示:只在优惠券有效时显示
  • 标签:显示"使用"
  • 样式:非主要按钮样式(次要按钮)

操作流程:

  • 点击"使用"按钮
  • 跳转到商店页面
  • 在结算时应用优惠券

状态处理:

  • 已使用或已过期的优惠券不显示按钮
  • 通过 isValid 判断是否显示
  • 提高用户体验

优惠券卡片完整实现

优惠券卡片的完整实现包括所有元素。

dart 复制代码
class _CouponCard extends StatelessWidget {
  const _CouponCard({required this.coupon});
  final Coupon coupon;

  @override
  Widget build(BuildContext context) {
    final isValid = coupon.isValid;

    return Opacity(
      opacity: isValid ? 1.0 : 0.5,
      child: ShopCard(
        child: Row(
          children: [
            // 左侧优惠值
            Container(
              width: 80,
              height: 80,
              decoration: BoxDecoration(
                color: isValid
                    ? Theme.of(context).colorScheme.primaryContainer
                    : Colors.grey.shade200,
                borderRadius: BorderRadius.circular(8),
              ),
              child: Center(
                child: Text(
                  coupon.displayValue,
                  textAlign: TextAlign.center,
                  style: TextStyle(
                    fontWeight: FontWeight.bold,
                    color: isValid
                        ? Theme.of(context).colorScheme.primary
                        : Colors.grey,
                  ),
                ),
              ),
            ),
            const SizedBox(width: 12),
            // 右侧信息
            Expanded(
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  Text(
                    coupon.code,
                    style: const TextStyle(fontWeight: FontWeight.bold),
                  ),
                  if (coupon.description != null) ...[
                    const SizedBox(height: 4),
                    Text(
                      coupon.description!,
                      style: Theme.of(context).textTheme.bodySmall,
                    ),
                  ],
                  const SizedBox(height: 4),
                  Text(
                    '满¥${coupon.minOrderAmount.toStringAsFixed(0)}可用',
                    style: Theme.of(context).textTheme.bodySmall,
                  ),
                  const SizedBox(height: 4),
                  Text(
                    '有效期至:${coupon.expiresAt.year}/${coupon.expiresAt.month}/${coupon.expiresAt.day}',
                    style: TextStyle(
                      fontSize: 12,
                      color: coupon.isExpired ? Colors.red : Colors.grey,
                    ),
                  ),
                ],
              ),
            ),
            // 使用按钮
            if (isValid)
              ShopButton(
                label: '使用',
                isPrimary: false,
                onPressed: () =>
                    Navigator.of(context).pushNamed(AppRoutes.shop),
              ),
          ],
        ),
      ),
    );
  }
}

这个完整的优惠券卡片整合了所有功能:

卡片结构:

  • 外层 :Opacity 控制透明度
  • 中层 :ShopCard 提供卡片样式
  • 内层 :Row 水平排列三个部分

三个部分:

  • 左侧:优惠值显示(80x80)
  • 中间:优惠券详细信息
  • 右侧:使用按钮(仅有效时显示)

状态管理:

  • 已过期或已使用的优惠券透明度为0.5
  • 有效优惠券透明度为1.0
  • 清晰的视觉区分

优惠券状态管理

应用状态管理优惠券的获取和使用。

dart 复制代码
final List<Coupon> _coupons = [];
List<Coupon> get coupons => List.unmodifiable(_coupons);

List<Coupon> get validCoupons =>
    _coupons.where((c) => c.isValid).toList();

这个状态管理提供了优惠券的访问接口:

优惠券列表:

  • _coupons:内部优惠券列表
  • coupons:返回不可修改的优惠券列表
  • 防止外部直接修改

有效优惠券:

  • validCoupons:获取所有有效优惠券
  • 自动过滤已使用和已过期的
  • 便于结算时选择优惠券

数据保护:

  • 使用 List.unmodifiable() 保护数据
  • 防止意外修改
  • 确保数据一致性

优惠券初始化

应用启动时初始化优惠券数据。

dart 复制代码
_coupons.addAll([
  Coupon(
    id: 'coupon_001',
    code: 'WELCOME10',
    type: CouponType.percentage,
    value: 10,
    minOrderAmount: 50,
    expiresAt: DateTime.now().add(const Duration(days: 30)),
    description: '新用户专享折扣',
  ),
  Coupon(
    id: 'coupon_002',
    code: 'SAVE20',
    type: CouponType.fixed,
    value: 20,
    minOrderAmount: 100,
    expiresAt: DateTime.now().add(const Duration(days: 7)),
    description: '满100减20',
  ),
  Coupon(
    id: 'coupon_003',
    code: 'FREESHIP',
    type: CouponType.freeShipping,
    value: 0,
    minOrderAmount: 30,
    expiresAt: DateTime.now().add(const Duration(days: 14)),
    description: '满30包邮',
  ),
]);

这个初始化代码创建了三个示例优惠券:

优惠券示例:

  • WELCOME10:10折折扣,满50可用,30天有效
  • SAVE20:满减20元,满100可用,7天有效
  • FREESHIP:包邮,满30可用,14天有效

初始化特点:

  • 使用 addAll() 批量添加
  • 设置不同的过期时间
  • 模拟真实的优惠券数据

数据来源:

  • 实际应用中应从服务器获取
  • 这里使用本地模拟数据
  • 便于开发和测试

优惠券过滤和查询

提供灵活的优惠券查询功能。

dart 复制代码
// 按类型过滤优惠券
List<Coupon> getCouponsByType(CouponType type) {
  return _coupons.where((c) => c.type == type).toList();
}

// 获取即将过期的优惠券
List<Coupon> getExpiringCoupons(int days) {
  final cutoffDate = DateTime.now().add(Duration(days: days));
  return _coupons.where((c) {
    return c.isValid && c.expiresAt.isBefore(cutoffDate);
  }).toList();
}

// 按最低金额过滤
List<Coupon> getCouponsByMinAmount(double amount) {
  return validCoupons
      .where((c) => c.minOrderAmount <= amount)
      .toList();
}

这些查询方法提供了灵活的优惠券检索:

按类型查询:

  • 获取特定类型的优惠券
  • 如只获取折扣券或满减券

即将过期查询:

  • 获取即将过期的优惠券
  • 提醒用户及时使用
  • 参数为天数

按金额查询:

  • 获取适用于特定金额的优惠券
  • 结算时自动推荐
  • 提高用户体验

应用场景:

  • 结算页面推荐优惠券
  • 优惠券列表筛选
  • 用户提醒功能

优惠券使用操作

处理优惠券的使用和标记。

dart 复制代码
// 使用优惠券
Future<bool> useCoupon(String couponId) async {
  try {
    final index = _coupons.indexWhere((c) => c.id == couponId);
    if (index >= 0 && _coupons[index].isValid) {
      // 模拟API调用
      await Future.delayed(const Duration(milliseconds: 500));
      
      // 创建新的优惠券对象,标记为已使用
      _coupons[index] = Coupon(
        id: _coupons[index].id,
        code: _coupons[index].code,
        type: _coupons[index].type,
        value: _coupons[index].value,
        minOrderAmount: _coupons[index].minOrderAmount,
        expiresAt: _coupons[index].expiresAt,
        isUsed: true,
        description: _coupons[index].description,
      );
      
      notifyListeners();
      return true;
    }
    return false;
  } catch (e) {
    return false;
  }
}

这个方法处理优惠券的使用:

使用流程:

  • 查找要使用的优惠券
  • 检查优惠券是否有效
  • 模拟API调用
  • 标记为已使用

数据更新:

  • 创建新的优惠券对象
  • 设置 isUsed 为 true
  • 通知所有监听者

错误处理:

  • 检查优惠券是否存在
  • 检查优惠券是否有效
  • 返回操作结果

应用场景:

  • 结算时应用优惠券
  • 用户点击"使用"按钮
  • 订单创建后标记优惠券

优惠券计算工具

计算优惠券的优惠金额。

dart 复制代码
class CouponCalculator {
  // 计算优惠金额
  static double calculateDiscount(
    Coupon coupon,
    double orderAmount,
  ) {
    if (!coupon.isValid || orderAmount < coupon.minOrderAmount) {
      return 0;
    }

    switch (coupon.type) {
      case CouponType.percentage:
        // 折扣券:计算折扣金额
        return orderAmount * (coupon.value / 100);
      case CouponType.fixed:
        // 满减券:直接返回减少金额
        return coupon.value;
      case CouponType.freeShipping:
        // 包邮券:返回运费(这里假设为10元)
        return 10;
    }
  }

  // 计算最终价格
  static double calculateFinalPrice(
    double orderAmount,
    Coupon coupon,
  ) {
    final discount = calculateDiscount(coupon, orderAmount);
    return (orderAmount - discount).clamp(0, double.infinity);
  }
}

这个计算工具处理优惠券的优惠计算:

优惠计算:

  • 折扣券:按百分比计算,如10折则优惠10%
  • 满减券:直接减少固定金额
  • 包邮券:优惠运费(假设10元)

验证条件:

  • 检查优惠券是否有效
  • 检查订单金额是否满足最低要求
  • 不满足条件返回0

最终价格:

  • 订单金额减去优惠金额
  • 使用 clamp() 确保不为负数
  • 返回最终支付金额

应用场景:

  • 结算页面显示优惠金额
  • 计算最终支付价格
  • 订单确认前验证

总结

优惠券系统的实现涉及多个重要的技术点。首先是优惠券数据模型的设计,支持多种优惠券类型和有效期管理。其次是优惠券列表页面的实现,使用Tab分类显示不同状态的优惠券。再次是优惠券卡片的设计,清晰展示优惠信息和使用状态。最后是优惠券的状态管理和计算工具,支持优惠券的查询、使用和优惠计算。

这种设计确保了优惠券系统的功能完整性和用户体验的流畅性。用户可以轻松查看优惠券、了解优惠内容、及时使用优惠券,整个优惠券管理流程自然而直观。


欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net

相关推荐
千里马学框架5 天前
一起学 Android 14:ShellTransition 屏幕旋转过程深度剖析
android·智能手机·性能优化·framework·性能·屏幕旋转·rotation
美狐美颜SDK开放平台5 天前
开发直播APP时如何接入视频美颜SDK?开发流程与注意事项
android·人工智能·计算机视觉·音视频·直播美颜sdk
AFinalStone5 天前
Android7 SystemUI源码解析(七)Keyguard锁屏模块深度解析
android·systemui
致远ccc5 天前
Google Play 上架前如何测试 App?多国家 Android 环境测试
android·app测试·googleplay·多国家应用测试
ttyyttemo5 天前
Kotlin 协程中的 Job 结构化并发与取消
android
sun0077005 天前
tbox 4g/5g切换,导致wan ip 改变,导致车机旧网络不可用。需要重启车机才行
android
其实防守也摸鱼5 天前
内网穿透与反向代理:原理、工具与实战指南
android·大数据·运维·安全·网络安全·自动化·渗透
AFinalStone5 天前
Android7 SystemUI 源码解析(四)NavigationBar 导航栏与 SystemBars
android·systemui
JMchen5 天前
属性动画原理与高级动画实现
android·kotlin·canvas
AFinalStone5 天前
Android7 SystemUI 源码解析(二)启动流程深度解析
android·systemui