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调用
  • 标记为已使用

数据更新:

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

错误处理:

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

应用场景:

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

优惠券计算工具

计算优惠券的优惠金额。

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

相关推荐
百锦再2 小时前
React编程高级主题:测试代码
android·前端·javascript·react.js·前端框架·reactjs
2501_916008893 小时前
全面介绍Fiddler、Wireshark、HttpWatch、SmartSniff和firebug抓包工具功能与使用
android·ios·小程序·https·uni-app·iphone·webview
玉梅小洋3 小时前
Windows 10 Android 构建配置指南
android·windows
不爱吃糖的程序媛4 小时前
Flutter 与 OpenHarmony 通信:Flutter Channel 使用指南
前端·javascript·flutter
Libraeking5 小时前
视觉篇:Canvas 自定义绘图与高级动画的华丽圆舞曲
android·经验分享·android jetpack
Fushize6 小时前
多模块架构下的依赖治理:如何避免 Gradle 依赖地狱
android·架构·kotlin
Jomurphys6 小时前
Kotlin - 类型别名 typealias
android·kotlin
Haha_bj6 小时前
Flutter ——flutter_screenutil 屏幕适配
android·ios
用户66116655296526 小时前
Futter3 仿抖音我的页面or用户详情页
flutter
Haha_bj7 小时前
Flutter ——device_info_plus详解
android·flutter·ios