跳转至

案例5:Flutter电商App完整实现

📋 案例概述

项目简介

构建一个功能完整的跨平台电商应用,展示Flutter在电商场景下的最佳实践。本案例涵盖商品展示、购物车、订单管理、支付集成等核心电商功能。

预计开发时间: 10-12小时 难度等级: ⭐⭐⭐⭐⭐ 涉及知识点: Flutter状态管理、路由导航、网络请求、本地存储、支付集成

功能特性

  • 🏠 首页轮播图与商品分类
  • 🔍 商品搜索与筛选
  • 🛒 购物车管理
  • 💳 订单创建与支付
  • 📦 订单追踪
  • 👤 用户中心与地址管理
  • ❤️ 商品收藏
  • 🌙 深色模式支持

🏗️ 项目架构

Text Only
lib/
├── main.dart
├── app.dart
├── config/
│   ├── routes.dart
│   ├── theme.dart
│   └── constants.dart
├── data/
│   ├── models/
│   │   ├── product.dart
│   │   ├── category.dart
│   │   ├── cart_item.dart
│   │   ├── order.dart
│   │   └── user.dart
│   ├── repositories/
│   │   ├── product_repository.dart
│   │   ├── cart_repository.dart
│   │   ├── order_repository.dart
│   │   └── user_repository.dart
│   └── providers/
│       ├── api_provider.dart
│       └── storage_provider.dart
├── services/
│   ├── api_service.dart
│   ├── auth_service.dart
│   └── payment_service.dart
├── state/
│   ├── cart_provider.dart
│   ├── product_provider.dart
│   ├── order_provider.dart
│   └── theme_provider.dart
├── ui/
│   ├── screens/
│   │   ├── home/
│   │   │   ├── home_screen.dart
│   │   │   └── widgets/
│   │   ├── product/
│   │   │   ├── product_list_screen.dart
│   │   │   ├── product_detail_screen.dart
│   │   │   └── widgets/
│   │   ├── cart/
│   │   │   ├── cart_screen.dart
│   │   │   └── widgets/
│   │   ├── order/
│   │   │   ├── order_list_screen.dart
│   │   │   ├── order_detail_screen.dart
│   │   │   └── checkout_screen.dart
│   │   ├── profile/
│   │   │   ├── profile_screen.dart
│   │   │   ├── address_screen.dart
│   │   │   └── settings_screen.dart
│   │   └── auth/
│   │       ├── login_screen.dart
│   │       └── register_screen.dart
│   ├── widgets/
│   │   ├── common/
│   │   │   ├── loading_widget.dart
│   │   │   ├── error_widget.dart
│   │   │   └── empty_widget.dart
│   │   └── product/
│   │       ├── product_card.dart
│   │       ├── product_grid.dart
│   │       └── quantity_selector.dart
│   └── theme/
│       ├── app_theme.dart
│       └── app_colors.dart
└── utils/
    ├── extensions.dart
    ├── helpers.dart
    └── validators.dart

🛠️ 技术实现

1. 数据模型

product.dart

Dart
import 'package:freezed_annotation/freezed_annotation.dart';

// freezed代码生成文件,运行 dart run build_runner build 自动生成
part 'product.freezed.dart';
part 'product.g.dart';

// 使用freezed创建不可变数据模型,自动生成copyWith/==/toString等方法
@freezed
class Product with _$Product {
  // 工厂构造函数定义商品属性,required表示必填字段
  const factory Product({
    required String id,
    required String name,
    required String description,
    required double price,        // 当前售价
    required double originalPrice, // 原价,用于计算折扣
    required List<String> images,
    required String categoryId,
    required String categoryName,
    required int stock,
    required double rating,
    required int reviewCount,
    required List<String> tags,
    @Default(false) bool isFavorite, // @Default设置默认值,JSON缺失时使用
    DateTime? createdAt,             // 可空类型,表示可选字段
  }) = _Product;

  // 从JSON Map反序列化,实现由json_serializable自动生成
  factory Product.fromJson(Map<String, dynamic> json) =>
      _$ProductFromJson(json);
}

// 商品分类模型,支持嵌套子分类
@freezed
class Category with _$Category {
  const factory Category({
    required String id,
    required String name,
    required String icon,
    required String image,
    @Default([]) List<Category> subCategories, // 子分类列表,默认空
  }) = _Category;

  factory Category.fromJson(Map<String, dynamic> json) =>
      _$CategoryFromJson(json);
}

cart_item.dart

Dart
import 'package:freezed_annotation/freezed_annotation.dart';
import 'product.dart';

part 'cart_item.freezed.dart';
part 'cart_item.g.dart';

// 购物车单项模型
@freezed
class CartItem with _$CartItem {
  // 私有构造函数,允许在freezed类中定义自定义getter方法
  const CartItem._();

  const factory CartItem({
    required String id,
    required Product product,
    required int quantity,
    String? selectedVariant, // 已选规格(如颜色、尺码)
    DateTime? addedAt,
  }) = _CartItem;

  factory CartItem.fromJson(Map<String, dynamic> json) =>
      _$CartItemFromJson(json);

  // 计算属性:单项总价 = 单价 × 数量
  double get totalPrice => product.price * quantity;
}

// 购物车模型,包含商品列表和优惠信息
@freezed
class Cart with _$Cart {
  const Cart._(); // 私有构造函数,启用自定义getter

  const factory Cart({
    @Default([]) List<CartItem> items, // 购物车商品列表
    String? couponCode,  // 优惠券码
    double? discount,    // 折扣金额
  }) = _Cart;

  factory Cart.fromJson(Map<String, dynamic> json) => _$CartFromJson(json);

  // 以下为计算属性,基于购物车内容动态计算
  double get subtotal => items.fold(0, (sum, item) => sum + item.totalPrice); // 商品小计
  double get total => subtotal - (discount ?? 0); // 实付金额(??为空合并运算符)
  int get itemCount => items.fold(0, (sum, item) => sum + item.quantity); // 总商品数
  bool get isEmpty => items.isEmpty;
}

order.dart

Dart
import 'package:freezed_annotation/freezed_annotation.dart';
import 'cart_item.dart';
import 'user.dart';

part 'order.freezed.dart';
part 'order.g.dart';

// 订单状态枚举,表示订单生命周期的各个阶段
enum OrderStatus {
  pending,    // 待支付
  paid,       // 已支付
  processing, // 处理中
  shipped,    // 已发货
  delivered,  // 已送达
  cancelled,  // 已取消
  refunded,   // 已退款
}

// 支付方式枚举
enum PaymentMethod {
  alipay,     // 支付宝
  wechatPay,  // 微信支付
  creditCard, // 信用卡
}

// 订单模型,包含订单完整信息
@freezed
class Order with _$Order {
  const factory Order({
    required String id,
    required String orderNumber,        // 用户可见的订单编号
    required String userId,
    required List<CartItem> items,      // 订单商品列表
    required Address shippingAddress,   // 收货地址
    required double subtotal,           // 商品小计
    required double shippingCost,       // 运费
    required double discount,           // 折扣金额
    required double total,              // 实付总额
    required OrderStatus status,
    required PaymentMethod paymentMethod,
    String? paymentId,       // 第三方支付流水号
    String? trackingNumber,  // 物流追踪号
    DateTime? paidAt,        // 支付时间
    DateTime? shippedAt,     // 发货时间
    DateTime? deliveredAt,   // 签收时间
    required DateTime createdAt,
    DateTime? updatedAt,
  }) = _Order;

  factory Order.fromJson(Map<String, dynamic> json) => _$OrderFromJson(json);
}

2. 状态管理 (Riverpod)

cart_provider.dart

Dart
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../data/models/cart_item.dart';
import '../data/models/product.dart';
import '../data/repositories/cart_repository.dart';

// Provider: 提供CartRepository单例,注入本地存储依赖
final cartRepositoryProvider = Provider<CartRepository>((ref) {
  return CartRepository(ref.read(storageProvider));
});

// StateNotifierProvider: 管理购物车状态,暴露CartNotifier和Cart
final cartProvider = StateNotifierProvider<CartNotifier, Cart>((ref) {
  return CartNotifier(ref.read(cartRepositoryProvider));
});

// StateNotifier: 购物车业务逻辑,修改state自动通知所有监听者
class CartNotifier extends StateNotifier<Cart> {
  final CartRepository _repository;

  // 初始化空购物车,然后异步加载本地持久化数据
  CartNotifier(this._repository) : super(const Cart()) {
    _loadCart();
  }

  // 从本地存储恢复购物车数据
  Future<void> _loadCart() async {
    final cart = await _repository.getCart();
    state = cart;
  }

  // 添加商品到购物车,已存在则累加数量
  Future<void> addToCart(Product product, {int quantity = 1}) async {
    // 查找购物车中是否已有该商品
    final existingItemIndex = state.items.indexWhere(
      (item) => item.product.id == product.id,
    );

    if (existingItemIndex >= 0) {
      // 已存在:用copyWith创建更新后的不可变对象
      final updatedItems = [...state.items];
      final existingItem = updatedItems[existingItemIndex];
      updatedItems[existingItemIndex] = existingItem.copyWith(
        quantity: existingItem.quantity + quantity,
      );
      state = state.copyWith(items: updatedItems);
    } else {
      // 添加新商品
      final newItem = CartItem(
        id: DateTime.now().millisecondsSinceEpoch.toString(),
        product: product,
        quantity: quantity,
        addedAt: DateTime.now(),
      );
      state = state.copyWith(items: [...state.items, newItem]);
    }

    await _repository.saveCart(state);
  }

  // 更新商品数量,数量<=0时自动移除
  Future<void> updateQuantity(String itemId, int quantity) async {
    if (quantity <= 0) {
      await removeFromCart(itemId);
      return;
    }

    // 使用map遍历并更新匹配项,保持不可变性
    final updatedItems = state.items.map((item) {
      if (item.id == itemId) {
        return item.copyWith(quantity: quantity);
      }
      return item;
    }).toList();

    state = state.copyWith(items: updatedItems);
    await _repository.saveCart(state); // 同步持久化到本地存储
  }

  // 从购物车移除指定商品
  Future<void> removeFromCart(String itemId) async {
    final updatedItems = state.items.where((item) => item.id != itemId).toList();
    state = state.copyWith(items: updatedItems);
    await _repository.saveCart(state);
  }

  // 清空购物车
  Future<void> clearCart() async {
    state = const Cart(); // 重置为空购物车常量
    await _repository.clearCart();
  }

  // 应用优惠券,验证通过后更新折扣金额
  Future<void> applyCoupon(String code) async {
    final discount = await _repository.validateCoupon(code);
    if (discount != null) {
      state = state.copyWith(
        couponCode: code,
        discount: discount,
      );
    }
  }
}

product_provider.dart

Dart
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../data/models/product.dart';
import '../data/repositories/product_repository.dart';

// Provider注入API依赖,提供商品仓库实例
final productRepositoryProvider = Provider<ProductRepository>((ref) {
  return ProductRepository(ref.read(apiProvider));
});

// 商品列表 - FutureProvider.family支持传参(筛选条件),自动管理异步状态
final productsProvider = FutureProvider.family<List<Product>, ProductFilter>(
  (ref, filter) async {
    final repository = ref.read(productRepositoryProvider);
    return repository.getProducts(filter);
  },
);

// 商品详情 - family参数为商品ID,每个ID对应独立的缓存
final productDetailProvider = FutureProvider.family<Product, String>(
  (ref, productId) async {
    final repository = ref.read(productRepositoryProvider);
    return repository.getProductDetail(productId);
  },
);

// 分类列表 - 无参FutureProvider,全局共享缓存
final categoriesProvider = FutureProvider<List<Category>>((ref) async {
  final repository = ref.read(productRepositoryProvider);
  return repository.getCategories();
});

// 搜索商品 - StateNotifierProvider管理搜索状态,支持手动触发搜索
final productSearchProvider = StateNotifierProvider<
    ProductSearchNotifier, AsyncValue<List<Product>>>(
  (ref) => ProductSearchNotifier(ref.read(productRepositoryProvider)),
);

// 商品搜索状态管理,AsyncValue封装 加载/成功/错误 三种状态
class ProductSearchNotifier extends StateNotifier<AsyncValue<List<Product>>> {
  final ProductRepository _repository;

  ProductSearchNotifier(this._repository) : super(const AsyncValue.loading());

  // 执行搜索,自动管理加载状态和错误处理
  Future<void> search(String query, {String? categoryId}) async {
    state = const AsyncValue.loading(); // 切换到加载状态
    try {
      final products = await _repository.searchProducts(
        query: query,
        categoryId: categoryId,
      );
      state = AsyncValue.data(products); // 成功:包装数据
    } catch (e, stack) {
      state = AsyncValue.error(e, stack); // 失败:捕获错误和堆栈
    }
  }
}

// 商品筛选条件,作为FutureProvider.family的参数
class ProductFilter {
  final String? categoryId; // 分类ID
  final String? sortBy;     // 排序方式
  final double? minPrice;   // 最低价格
  final double? maxPrice;   // 最高价格
  final double? minRating;  // 最低评分
  final int page;           // 分页页码
  final int pageSize;       // 每页数量

  const ProductFilter({
    this.categoryId,
    this.sortBy,
    this.minPrice,
    this.maxPrice,
    this.minRating,
    this.page = 1,
    this.pageSize = 20,
  });
}

3. UI层实现

home_screen.dart

Dart
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

// ConsumerWidget: Riverpod版StatelessWidget,通过ref访问Provider
class HomeScreen extends ConsumerWidget {
  const HomeScreen({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    // ref.watch监听Provider变化,数据更新时自动重建Widget
    final categoriesAsync = ref.watch(categoriesProvider);
    final featuredProductsAsync = ref.watch(
      productsProvider(const ProductFilter(pageSize: 10)),
    );

    return Scaffold(
      appBar: AppBar(
        title: const Text('Flutter商城'),
        actions: [
          IconButton(
            icon: const Icon(Icons.search),
            onPressed: () => Navigator.pushNamed(context, '/search'),
          ),
          IconButton(
            icon: const Icon(Icons.shopping_cart),
            onPressed: () => Navigator.pushNamed(context, '/cart'),
          ),
        ],
      ),
      // 下拉刷新:ref.refresh强制Provider重新获取数据
      body: RefreshIndicator(
        onRefresh: () async {
          ref.refresh(categoriesProvider);
          ref.refresh(productsProvider(const ProductFilter(pageSize: 10)));
        },
        // CustomScrollView + Sliver组合实现多种布局混合滚动
        child: CustomScrollView(
          slivers: [
            // 轮播图
            const SliverToBoxAdapter(
              child: BannerCarousel(),
            ),

            // 分类 - when模式匹配AsyncValue的三种状态
            SliverToBoxAdapter(
              child: categoriesAsync.when(
                data: (categories) => CategoryGrid(categories: categories),
                loading: () => const SizedBox.shrink(), // 加载中不显示
                error: (_, __) => const SizedBox.shrink(), // 出错时静默处理
              ),
            ),

            // 特色商品标题
            const SliverToBoxAdapter(
              child: Padding(
                padding: EdgeInsets.all(16),
                child: Text(
                  '热门商品',
                  style: TextStyle(
                    fontSize: 20,
                    fontWeight: FontWeight.bold,
                  ),
                ),
              ),
            ),

            // 商品网格 - Sliver懒加载,仅构建可见区域的Widget
            featuredProductsAsync.when(
              data: (products) => SliverPadding(
                padding: const EdgeInsets.symmetric(horizontal: 16),
                sliver: SliverGrid(
                  // 固定2列网格,宽高比0.7(卡片高度大于宽度)
                  gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
                    crossAxisCount: 2,
                    childAspectRatio: 0.7,
                    crossAxisSpacing: 12,
                    mainAxisSpacing: 12,
                  ),
                  delegate: SliverChildBuilderDelegate(
                    (context, index) => ProductCard(product: products[index]),
                    childCount: products.length,
                  ),
                ),
              ),
              loading: () => const SliverFillRemaining(
                child: Center(child: CircularProgressIndicator()),
              ),
              error: (error, _) => SliverFillRemaining(
                child: Center(child: Text('Error: $error')),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

product_card.dart

Dart
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../data/models/product.dart';
import '../../state/cart_provider.dart';

// 商品卡片组件,使用ConsumerWidget以访问Riverpod状态
class ProductCard extends ConsumerWidget {
  final Product product;

  const ProductCard({
    Key? key,
    required this.product,
  }) : super(key: key);

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    return Card(
      clipBehavior: Clip.antiAlias, // 裁剪子Widget溢出部分,实现圆角效果
      child: InkWell(
        onTap: () => Navigator.pushNamed( // 点击跳转商品详情页
          context,
          '/product-detail',
          arguments: product.id,
        ),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            // 商品图片
            AspectRatio(
              aspectRatio: 1,
              child: Stack(
                fit: StackFit.expand,
                children: [
                  Image.network(
                    product.images.first,
                    fit: BoxFit.cover,
                    loadingBuilder: (context, child, loadingProgress) {
                      if (loadingProgress == null) return child;
                      return const Center(child: CircularProgressIndicator());
                    },
                  ),
                  // 折扣标签
                  if (product.price < product.originalPrice)
                    Positioned(
                      top: 8,
                      left: 8,
                      child: Container(
                        padding: const EdgeInsets.symmetric(
                          horizontal: 8,
                          vertical: 4,
                        ),
                        decoration: BoxDecoration(
                          color: Colors.red,
                          borderRadius: BorderRadius.circular(4),
                        ),
                        child: Text(
                          '-${((1 - product.price / product.originalPrice) * 100).toInt()}%',
                          style: const TextStyle(
                            color: Colors.white,
                            fontSize: 12,
                            fontWeight: FontWeight.bold,
                          ),
                        ),
                      ),
                    ),
                ],
              ),
            ),

            // 商品信息
            Padding(
              padding: const EdgeInsets.all(12),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  Text(
                    product.name,
                    maxLines: 2,
                    overflow: TextOverflow.ellipsis,
                    style: const TextStyle(
                      fontSize: 14,
                      fontWeight: FontWeight.w500,
                    ),
                  ),
                  const SizedBox(height: 4),

                  // 评分
                  Row(
                    children: [
                      Icon(
                        Icons.star,
                        size: 14,
                        color: Colors.amber[600],
                      ),
                      const SizedBox(width: 4),
                      Text(
                        '${product.rating}',
                        style: TextStyle(
                          fontSize: 12,
                          color: Colors.grey[600],
                        ),
                      ),
                      const SizedBox(width: 4),
                      Text(
                        '(${product.reviewCount})',
                        style: TextStyle(
                          fontSize: 12,
                          color: Colors.grey[400],
                        ),
                      ),
                    ],
                  ),
                  const SizedBox(height: 8),

                  // 价格
                  Row(
                    children: [
                      Text(
                        ${product.price.toStringAsFixed(2)}',
                        style: TextStyle(
                          fontSize: 16,
                          fontWeight: FontWeight.bold,
                          color: Theme.of(context).primaryColor,
                        ),
                      ),
                      if (product.price < product.originalPrice) ...[
                        const SizedBox(width: 8),
                        Text(
                          ${product.originalPrice.toStringAsFixed(2)}',
                          style: TextStyle(
                            fontSize: 12,
                            color: Colors.grey[400],
                            decoration: TextDecoration.lineThrough,
                          ),
                        ),
                      ],
                      const Spacer(),
                      // 加入购物车按钮
                      InkWell(
                        onTap: () {
                          // ref.read用于一次性操作(非监听),.notifier获取StateNotifier实例
                          ref.read(cartProvider.notifier).addToCart(product);
                          ScaffoldMessenger.of(context).showSnackBar(
                            const SnackBar(
                              content: Text('已添加到购物车'),
                              duration: Duration(seconds: 1),
                            ),
                          );
                        },
                        child: Container(
                          padding: const EdgeInsets.all(6),
                          decoration: BoxDecoration(
                            color: Theme.of(context).primaryColor,
                            borderRadius: BorderRadius.circular(4),
                          ),
                          child: const Icon(
                            Icons.add_shopping_cart,
                            size: 16,
                            color: Colors.white,
                          ),
                        ),
                      ),
                    ],
                  ),
                ],
              ),
            ),
          ],
        ),
      ),
    );
  }
}

cart_screen.dart

Dart
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../state/cart_provider.dart';

class CartScreen extends ConsumerWidget {
  const CartScreen({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    // ref.watch监听购物车状态,任何变更自动触发UI重建
    final cart = ref.watch(cartProvider);

    return Scaffold(
      appBar: AppBar(
        title: const Text('购物车'),
        actions: [
          if (!cart.isEmpty)
            TextButton(
              onPressed: () {
                ref.read(cartProvider.notifier).clearCart();
              },
              child: const Text('清空'),
            ),
        ],
      ),
      body: cart.isEmpty
          ? const EmptyCartWidget()
          : Column(
              children: [
                Expanded(
                  child: ListView.builder(
                    itemCount: cart.items.length,
                    itemBuilder: (context, index) {
                      final item = cart.items[index];
                      return CartItemTile(
                        item: item,
                        onQuantityChanged: (quantity) {
                          ref
                              .read(cartProvider.notifier)
                              .updateQuantity(item.id, quantity);
                        },
                        onRemove: () {
                          ref
                              .read(cartProvider.notifier)
                              .removeFromCart(item.id);
                        },
                      );
                    },
                  ),
                ),
                CartSummary(cart: cart),
              ],
            ),
    );
  }
}

class CartItemTile extends StatelessWidget {
  final CartItem item;
  final ValueChanged<int> onQuantityChanged;
  final VoidCallback onRemove;

  const CartItemTile({
    Key? key,
    required this.item,
    required this.onQuantityChanged,
    required this.onRemove,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    // Dismissible实现左滑删除手势交互
    return Dismissible(
      key: Key(item.id), // 唯一Key用于动画和状态识别
      direction: DismissDirection.endToStart, // 仅支持从右向左滑动
      background: Container(
        color: Colors.red,
        alignment: Alignment.centerRight,
        padding: const EdgeInsets.only(right: 16),
        child: const Icon(Icons.delete, color: Colors.white),
      ),
      onDismissed: (_) => onRemove(), // 滑动完成后执行删除回调
      child: Card(
        margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
        child: Padding(
          padding: const EdgeInsets.all(12),
          child: Row(
            children: [
              // 商品图片
              ClipRRect(
                borderRadius: BorderRadius.circular(8),
                child: Image.network(
                  item.product.images.first,
                  width: 80,
                  height: 80,
                  fit: BoxFit.cover,
                ),
              ),
              const SizedBox(width: 12),

              // 商品信息
              Expanded(
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Text(
                      item.product.name,
                      maxLines: 2,
                      overflow: TextOverflow.ellipsis,
                      style: const TextStyle(fontWeight: FontWeight.w500),
                    ),
                    const SizedBox(height: 4),
                    Text(
                      ${item.product.price.toStringAsFixed(2)}',
                      style: TextStyle(
                        color: Theme.of(context).primaryColor,
                        fontWeight: FontWeight.bold,
                      ),
                    ),
                  ],
                ),
              ),

              // 数量选择器
              QuantitySelector(
                quantity: item.quantity,
                onChanged: onQuantityChanged,
              ),
            ],
          ),
        ),
      ),
    );
  }
}

class CartSummary extends StatelessWidget {
  final Cart cart;

  const CartSummary({Key? key, required this.cart}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    // 底部结算栏,使用阴影与列表区域视觉分离
    return Container(
      padding: const EdgeInsets.all(16),
      decoration: BoxDecoration(
        color: Colors.white,
        boxShadow: [
          BoxShadow(
            color: Colors.black.withOpacity(0.05),
            blurRadius: 10,
            offset: const Offset(0, -5), // 向上投射阴影
          ),
        ],
      ),
      child: SafeArea( // 避免底部被系统导航栏遮挡
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            // 小计
            Row(
              mainAxisAlignment: MainAxisAlignment.spaceBetween,
              children: [
                Text('共 ${cart.itemCount} 件商品'),
                Text(
                  '合计: ¥${cart.total.toStringAsFixed(2)}',
                  style: const TextStyle(
                    fontSize: 18,
                    fontWeight: FontWeight.bold,
                  ),
                ),
              ],
            ),
            const SizedBox(height: 16),

            // 结算按钮 - 购物车为空时禁用(onPressed传null)
            SizedBox(
              width: double.infinity,
              child: ElevatedButton(
                onPressed: cart.isEmpty
                    ? null // 禁用状态,按钮变灰
                    : () => Navigator.pushNamed(context, '/checkout'),
                style: ElevatedButton.styleFrom(
                  padding: const EdgeInsets.symmetric(vertical: 16),
                  shape: RoundedRectangleBorder(
                    borderRadius: BorderRadius.circular(8),
                  ),
                ),
                child: const Text(
                  '去结算',
                  style: TextStyle(fontSize: 16),
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

4. 路由配置

Dart
// routes.dart - 集中管理应用所有路由配置
import 'package:flutter/material.dart';
import '../ui/screens/home/home_screen.dart';
import '../ui/screens/product/product_detail_screen.dart';
import '../ui/screens/cart/cart_screen.dart';
import '../ui/screens/order/checkout_screen.dart';
import '../ui/screens/profile/profile_screen.dart';

// 路由管理类,定义路径常量和页面映射
class AppRoutes {
  // 路由路径常量,避免硬编码字符串
  static const String home = '/';
  static const String productList = '/products';
  static const String productDetail = '/product-detail';
  static const String cart = '/cart';
  static const String checkout = '/checkout';
  static const String orders = '/orders';
  static const String orderDetail = '/order-detail';
  static const String profile = '/profile';
  static const String addresses = '/addresses';
  static const String login = '/login';
  static const String register = '/register';
  static const String search = '/search';
  static const String favorites = '/favorites';
  static const String settings = '/settings';

  // 静态路由表:无参数的简单页面使用命名路由映射
  static Map<String, WidgetBuilder> get routes => {
    home: (context) => const HomeScreen(),
    cart: (context) => const CartScreen(),
    checkout: (context) => const CheckoutScreen(),
    profile: (context) => const ProfileScreen(),
    // ... 其他路由
  };

  // 动态路由生成:处理需要传参的页面(如商品详情需要productId)
  static Route<dynamic>? onGenerateRoute(RouteSettings settings) {
    switch (settings.name) {
      case productDetail:
        // 从路由参数中提取商品ID
        final productId = settings.arguments as String;
        return MaterialPageRoute(
          builder: (_) => ProductDetailScreen(productId: productId),
        );
      case orderDetail:
        final orderId = settings.arguments as String;
        return MaterialPageRoute(
          builder: (_) => OrderDetailScreen(orderId: orderId),
        );
      default:
        return null; // 未匹配的路由返回null,交由系统处理
    }
  }
}

🔧 依赖配置

YAML
# pubspec.yaml
name: flutter_shop
description: A Flutter e-commerce app

environment:
  sdk: '>=3.0.0 <4.0.0'

dependencies:
  flutter:
    sdk: flutter

  # 状态管理
  flutter_riverpod: ^2.4.9
  riverpod_annotation: ^2.3.3

  # 网络请求
  dio: ^5.4.0
  retrofit: ^4.0.3

  # 本地存储
  shared_preferences: ^2.2.2
  hive: ^2.2.3
  hive_flutter: ^1.1.0

  # 路由
  go_router: ^13.0.1

  # UI组件
  cached_network_image: ^3.3.0
  flutter_staggered_grid_view: ^0.7.0
  carousel_slider: ^4.2.1
  shimmer: ^3.0.0
  flutter_slidable: ^3.0.1

  # 支付
  flutter_stripe: ^10.0.0
  alipay_kit: ^4.0.0

  # 其他
  freezed_annotation: ^2.4.1
  json_annotation: ^4.8.1
  intl: ^0.18.1
  logger: ^2.0.2
  url_launcher: ^6.2.2

dev_dependencies:
  flutter_test:
    sdk: flutter
  flutter_lints: ^3.0.1
  build_runner: ^2.4.7
  freezed: ^2.4.5
  json_serializable: ^6.7.1
  retrofit_generator: ^8.0.6
  riverpod_generator: ^2.3.9
  hive_generator: ^2.0.1

🎯 练习任务

基础任务

  1. ✅ 完成商品列表和详情页
  2. ✅ 实现购物车功能
  3. ✅ 添加商品分类浏览

进阶任务

  1. 🔄 集成支付功能(支付宝/微信支付)
  2. 🔄 实现订单追踪功能
  3. 🔄 添加商品评价功能

挑战任务

  1. 🎯 实现直播带货功能
  2. 🎯 添加AR试穿功能
  3. 🎯 实现秒杀活动功能

📚 学习要点

  1. Flutter状态管理: Riverpod的最佳实践
  2. 跨平台开发: 一套代码多端运行
  3. 性能优化: 列表优化、图片缓存
  4. 支付集成: 第三方支付SDK集成
  5. 用户体验: 动画、过渡效果

🔗 相关章节