Flutter校园快递代取记录应用开发教程

项目概述

本教程将带你开发一个功能完整的Flutter校园快递代取记录应用。这款应用专为校园快递代取服务设计,提供快递信息管理、代取订单追踪、费用结算和信用评价等功能,让校园快递代取服务更加规范化和便民化。
运行效果图
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

应用特色

  • 快递信息管理:详细记录快递信息,包括快递公司、单号、收件人、代取费用等
  • 订单状态追踪:实时跟踪代取订单状态,从接单到完成的全流程管理
  • 智能匹配系统:根据位置和时间智能匹配代取员和订单
  • 费用结算功能:自动计算代取费用,支持多种结算方式
  • 信用评价体系:双向评价系统,维护服务质量和用户体验
  • 数据统计分析:收入统计、订单分析、服务评价等数据展示
  • 消息通知提醒:订单状态变更、费用结算等重要信息及时通知

技术栈

  • 框架:Flutter 3.x
  • 语言:Dart
  • UI组件:Material Design 3
  • 状态管理:StatefulWidget
  • 动画:AnimationController + Tween
  • 数据存储:内存存储(可扩展为本地数据库)

项目结构设计

核心数据模型

1. 快递订单模型(ExpressOrder)
class ExpressOrder {
  final String id;              // 唯一标识
  final String trackingNumber;  // 快递单号
  final String company;         // 快递公司
  final String senderName;      // 寄件人
  final String senderPhone;     // 寄件人电话
  final String recipientName;   // 收件人
  final String recipientPhone;  // 收件人电话
  final String pickupLocation;  // 取件地点
  final String deliveryLocation; // 送达地点
  final double weight;          // 重量
  final String description;     // 物品描述
  final double serviceFee;      // 代取费用
  final DateTime createTime;    // 创建时间
  final DateTime? pickupTime;   // 取件时间
  final DateTime? deliveryTime; // 送达时间
  final OrderStatus status;     // 订单状态
  final String? pickupCode;     // 取件码
  final String notes;           // 备注
  final List<String> photos;    // 照片
  bool isUrgent;               // 是否加急
  double rating;               // 评分
  String feedback;             // 反馈
}
2. 代取员模型(DeliveryAgent)
class DeliveryAgent {
  final String id;              // 唯一标识
  final String name;            // 姓名
  final String phone;           // 电话
  final String studentId;       // 学号
  final String dormitory;       // 宿舍
  final String avatar;          // 头像
  final double rating;          // 评分
  final int completedOrders;    // 完成订单数
  final double totalEarnings;   // 总收入
  final bool isAvailable;      // 是否可接单
  final DateTime joinDate;     // 加入时间
  final List<String> serviceAreas; // 服务区域
  final String introduction;    // 个人介绍
  final List<Review> reviews;   // 评价列表
}
3. 评价模型(Review)
class Review {
  final String id;              // 唯一标识
  final String orderId;         // 订单ID
  final String reviewerId;      // 评价人ID
  final String reviewerName;    // 评价人姓名
  final String targetId;        // 被评价人ID
  final double rating;          // 评分
  final String content;         // 评价内容
  final List<String> tags;      // 标签
  final DateTime createTime;    // 创建时间
  final ReviewType type;        // 评价类型
}
4. 状态枚举
enum OrderStatus {
  pending,      // 待接单
  accepted,     // 已接单
  picking,      // 取件中
  picked,       // 已取件
  delivering,   // 配送中
  completed,    // 已完成
  cancelled,    // 已取消
}

enum ReviewType {
  fromCustomer, // 客户评价代取员
  fromAgent,    // 代取员评价客户
}

enum ServiceArea {
  dormA,        // A区宿舍
  dormB,        // B区宿舍
  dormC,        // C区宿舍
  library,      // 图书馆
  canteen,      // 食堂
  teaching,     // 教学楼
  express,      // 快递点
}

enum UrgencyLevel {
  normal,       // 普通
  urgent,       // 加急
  veryUrgent,   // 特急
}

页面架构

应用采用底部导航栏设计,包含四个主要页面:

  1. 订单页面:查看和管理快递代取订单
  2. 代取员页面:代取员信息和服务管理
  3. 统计页面:收入统计和数据分析
  4. 个人页面:个人信息和设置管理

详细实现步骤

第一步:项目初始化

创建新的Flutter项目:

flutter create campus_express_delivery
cd campus_express_delivery

第二步:主应用结构

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  
  Widget build(BuildContext context) {
    return MaterialApp(
      title: '校园快递代取记录',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue),
        useMaterial3: true,
      ),
      home: const ExpressDeliveryHomePage(),
    );
  }
}

第三步:数据初始化

创建示例订单数据:

void _initializeOrders() {
  _orders = [
    ExpressOrder(
      id: '1',
      trackingNumber: 'SF1234567890',
      company: '顺丰速运',
      senderName: '张三',
      senderPhone: '13800138001',
      recipientName: '李四',
      recipientPhone: '13800138002',
      pickupLocation: '南门快递点',
      deliveryLocation: 'A区宿舍楼下',
      weight: 1.5,
      description: '书籍',
      serviceFee: 3.0,
      createTime: DateTime.now().subtract(const Duration(hours: 2)),
      status: OrderStatus.pending,
      pickupCode: '1234',
      notes: '请小心轻放',
      photos: [],
      isUrgent: false,
      rating: 0.0,
      feedback: '',
    ),
    // 更多订单数据...
  ];
}

第四步:订单列表页面

订单卡片组件
Widget _buildOrderCard(ExpressOrder order) {
  final statusColor = _getStatusColor(order.status);
  final statusText = _getStatusText(order.status);

  return Card(
    elevation: 4,
    margin: const EdgeInsets.only(bottom: 16),
    child: InkWell(
      onTap: () => _showOrderDetail(order),
      child: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            // 订单头部信息
            Row(
              children: [
                Container(
                  width: 50,
                  height: 50,
                  decoration: BoxDecoration(
                    color: _getCompanyColor(order.company).withValues(alpha: 0.1),
                    borderRadius: BorderRadius.circular(8),
                  ),
                  child: Icon(
                    _getCompanyIcon(order.company),
                    color: _getCompanyColor(order.company),
                    size: 24,
                  ),
                ),
                const SizedBox(width: 12),
                Expanded(
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      Text(
                        order.trackingNumber,
                        style: const TextStyle(
                          fontSize: 16,
                          fontWeight: FontWeight.bold,
                        ),
                      ),
                      Text(
                        order.company,
                        style: TextStyle(
                          color: Colors.grey.shade600,
                          fontSize: 14,
                        ),
                      ),
                    ],
                  ),
                ),
                Container(
                  padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
                  decoration: BoxDecoration(
                    color: statusColor.withValues(alpha: 0.1),
                    borderRadius: BorderRadius.circular(16),
                    border: Border.all(color: statusColor.withValues(alpha: 0.3)),
                  ),
                  child: Text(
                    statusText,
                    style: TextStyle(
                      color: statusColor,
                      fontSize: 12,
                      fontWeight: FontWeight.bold,
                    ),
                  ),
                ),
              ],
            ),
            
            const SizedBox(height: 16),
            
            // 收发件人信息
            Container(
              padding: const EdgeInsets.all(12),
              decoration: BoxDecoration(
                color: Colors.grey.shade50,
                borderRadius: BorderRadius.circular(8),
              ),
              child: Column(
                children: [
                  Row(
                    children: [
                      const Icon(Icons.person_outline, size: 16, color: Colors.blue),
                      const SizedBox(width: 8),
                      Text('收件人: ${order.recipientName}'),
                      const Spacer(),
                      Text(
                        order.recipientPhone,
                        style: TextStyle(color: Colors.grey.shade600),
                      ),
                    ],
                  ),
                  const SizedBox(height: 8),
                  Row(
                    children: [
                      const Icon(Icons.location_on_outlined, size: 16, color: Colors.green),
                      const SizedBox(width: 8),
                      Expanded(
                        child: Text(
                          '${order.pickupLocation}${order.deliveryLocation}',
                          style: TextStyle(color: Colors.grey.shade700),
                        ),
                      ),
                    ],
                  ),
                ],
              ),
            ),
            
            const SizedBox(height: 12),
            
            // 订单详情
            Row(
              children: [
                _buildInfoChip(Icons.scale, '${order.weight}kg'),
                const SizedBox(width: 8),
                _buildInfoChip(Icons.attach_money, ${order.serviceFee.toStringAsFixed(1)}'),
                const SizedBox(width: 8),
                if (order.isUrgent)
                  _buildInfoChip(Icons.flash_on, '加急', Colors.orange),
                if (order.pickupCode != null)
                  _buildInfoChip(Icons.lock, order.pickupCode!),
              ],
            ),
            
            const SizedBox(height: 12),
            
            // 时间信息
            Row(
              mainAxisAlignment: MainAxisAlignment.spaceBetween,
              children: [
                Text(
                  '下单时间: ${_formatDateTime(order.createTime)}',
                  style: TextStyle(
                    fontSize: 12,
                    color: Colors.grey.shade600,
                  ),
                ),
                if (order.status == OrderStatus.pending)
                  ElevatedButton(
                    onPressed: () => _acceptOrder(order),
                    style: ElevatedButton.styleFrom(
                      backgroundColor: Colors.blue,
                      foregroundColor: Colors.white,
                      padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
                    ),
                    child: const Text('接单', style: TextStyle(fontSize: 12)),
                  ),
              ],
            ),
            
            // 备注信息
            if (order.notes.isNotEmpty) ...[
              const SizedBox(height: 8),
              Container(
                width: double.infinity,
                padding: const EdgeInsets.all(8),
                decoration: BoxDecoration(
                  color: Colors.amber.withValues(alpha: 0.1),
                  borderRadius: BorderRadius.circular(6),
                  border: Border.all(color: Colors.amber.withValues(alpha: 0.3)),
                ),
                child: Row(
                  children: [
                    const Icon(Icons.note, size: 14, color: Colors.amber),
                    const SizedBox(width: 6),
                    Expanded(
                      child: Text(
                        '备注: ${order.notes}',
                        style: const TextStyle(
                          fontSize: 12,
                          color: Colors.amber,
                        ),
                      ),
                    ),
                  ],
                ),
              ),
            ],
          ],
        ),
      ),
    ),
  );
}
订单筛选功能
void _filterOrders() {
  setState(() {
    _filteredOrders = _orders.where((order) {
      bool matchesSearch = _searchQuery.isEmpty ||
          order.trackingNumber.toLowerCase().contains(_searchQuery.toLowerCase()) ||
          order.recipientName.toLowerCase().contains(_searchQuery.toLowerCase()) ||
          order.company.toLowerCase().contains(_searchQuery.toLowerCase());

      bool matchesStatus = _selectedStatus == null || order.status == _selectedStatus;

      bool matchesCompany = _selectedCompany == null || order.company == _selectedCompany;

      bool matchesUrgent = !_showUrgentOnly || order.isUrgent;

      return matchesSearch && matchesStatus && matchesCompany && matchesUrgent;
    }).toList();

    // 排序
    switch (_sortBy) {
      case SortBy.createTime:
        _filteredOrders.sort((a, b) => b.createTime.compareTo(a.createTime));
        break;
      case SortBy.serviceFee:
        _filteredOrders.sort((a, b) => b.serviceFee.compareTo(a.serviceFee));
        break;
      case SortBy.status:
        _filteredOrders.sort((a, b) => a.status.index.compareTo(b.status.index));
        break;
    }
  });
}

第五步:代取员管理功能

代取员信息卡片
Widget _buildAgentCard(DeliveryAgent agent) {
  return Card(
    elevation: 2,
    margin: const EdgeInsets.only(bottom: 16),
    child: InkWell(
      onTap: () => _showAgentDetail(agent),
      child: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          children: [
            // 代取员基本信息
            Row(
              children: [
                CircleAvatar(
                  radius: 30,
                  backgroundColor: Colors.blue.withValues(alpha: 0.1),
                  child: agent.avatar.isNotEmpty
                      ? ClipOval(child: Image.network(agent.avatar, fit: BoxFit.cover))
                      : Text(
                          agent.name.substring(0, 1),
                          style: const TextStyle(
                            fontSize: 24,
                            fontWeight: FontWeight.bold,
                            color: Colors.blue,
                          ),
                        ),
                ),
                const SizedBox(width: 16),
                Expanded(
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      Row(
                        children: [
                          Text(
                            agent.name,
                            style: const TextStyle(
                              fontSize: 18,
                              fontWeight: FontWeight.bold,
                            ),
                          ),
                          const SizedBox(width: 8),
                          Container(
                            padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
                            decoration: BoxDecoration(
                              color: agent.isAvailable ? Colors.green : Colors.grey,
                              borderRadius: BorderRadius.circular(10),
                            ),
                            child: Text(
                              agent.isAvailable ? '在线' : '离线',
                              style: const TextStyle(
                                color: Colors.white,
                                fontSize: 10,
                                fontWeight: FontWeight.bold,
                              ),
                            ),
                          ),
                        ],
                      ),
                      const SizedBox(height: 4),
                      Text(
                        '学号: ${agent.studentId} | ${agent.dormitory}',
                        style: TextStyle(
                          color: Colors.grey.shade600,
                          fontSize: 14,
                        ),
                      ),
                      const SizedBox(height: 4),
                      Row(
                        children: [
                          const Icon(Icons.star, size: 16, color: Colors.amber),
                          const SizedBox(width: 4),
                          Text(
                            agent.rating.toStringAsFixed(1),
                            style: const TextStyle(
                              fontSize: 14,
                              fontWeight: FontWeight.bold,
                            ),
                          ),
                          const SizedBox(width: 16),
                          Text(
                            '完成${agent.completedOrders}单',
                            style: TextStyle(
                              fontSize: 12,
                              color: Colors.grey.shade600,
                            ),
                          ),
                        ],
                      ),
                    ],
                  ),
                ),
                Column(
                  children: [
                    Text(
                      ${agent.totalEarnings.toStringAsFixed(0)}',
                      style: const TextStyle(
                        fontSize: 16,
                        fontWeight: FontWeight.bold,
                        color: Colors.green,
                      ),
                    ),
                    Text(
                      '总收入',
                      style: TextStyle(
                        fontSize: 12,
                        color: Colors.grey.shade600,
                      ),
                    ),
                  ],
                ),
              ],
            ),
            
            const SizedBox(height: 12),
            
            // 服务区域
            if (agent.serviceAreas.isNotEmpty) ...[
              Align(
                alignment: Alignment.centerLeft,
                child: Text(
                  '服务区域:',
                  style: TextStyle(
                    fontSize: 14,
                    fontWeight: FontWeight.w500,
                    color: Colors.grey.shade700,
                  ),
                ),
              ),
              const SizedBox(height: 6),
              Wrap(
                spacing: 6,
                runSpacing: 4,
                children: agent.serviceAreas.map((area) => Container(
                  padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
                  decoration: BoxDecoration(
                    color: Colors.blue.withValues(alpha: 0.1),
                    borderRadius: BorderRadius.circular(12),
                  ),
                  child: Text(
                    area,
                    style: const TextStyle(
                      fontSize: 12,
                      color: Colors.blue,
                    ),
                  ),
                )).toList(),
              ),
            ],
            
            const SizedBox(height: 12),
            
            // 个人介绍
            if (agent.introduction.isNotEmpty) ...[
              Container(
                width: double.infinity,
                padding: const EdgeInsets.all(10),
                decoration: BoxDecoration(
                  color: Colors.grey.shade50,
                  borderRadius: BorderRadius.circular(8),
                ),
                child: Text(
                  agent.introduction,
                  style: TextStyle(
                    fontSize: 13,
                    color: Colors.grey.shade700,
                    height: 1.3,
                  ),
                  maxLines: 2,
                  overflow: TextOverflow.ellipsis,
                ),
              ),
            ],
          ],
        ),
      ),
    ),
  );
}

第六步:订单状态管理

状态更新功能
void _updateOrderStatus(ExpressOrder order, OrderStatus newStatus) {
  setState(() {
    final index = _orders.indexWhere((o) => o.id == order.id);
    if (index != -1) {
      _orders[index] = ExpressOrder(
        id: order.id,
        trackingNumber: order.trackingNumber,
        company: order.company,
        senderName: order.senderName,
        senderPhone: order.senderPhone,
        recipientName: order.recipientName,
        recipientPhone: order.recipientPhone,
        pickupLocation: order.pickupLocation,
        deliveryLocation: order.deliveryLocation,
        weight: order.weight,
        description: order.description,
        serviceFee: order.serviceFee,
        createTime: order.createTime,
        pickupTime: newStatus == OrderStatus.picked ? DateTime.now() : order.pickupTime,
        deliveryTime: newStatus == OrderStatus.completed ? DateTime.now() : order.deliveryTime,
        status: newStatus,
        pickupCode: order.pickupCode,
        notes: order.notes,
        photos: order.photos,
        isUrgent: order.isUrgent,
        rating: order.rating,
        feedback: order.feedback,
      );
    }
  });
  
  _filterOrders();
  _calculateStats();
  
  // 显示状态更新提示
  ScaffoldMessenger.of(context).showSnackBar(
    SnackBar(
      content: Text('订单状态已更新为: ${_getStatusText(newStatus)}'),
      duration: const Duration(seconds: 2),
    ),
  );
}
接单功能
void _acceptOrder(ExpressOrder order) {
  showDialog(
    context: context,
    builder: (context) => AlertDialog(
      title: const Text('确认接单'),
      content: Column(
        mainAxisSize: MainAxisSize.min,
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Text('快递单号: ${order.trackingNumber}'),
          Text('收件人: ${order.recipientName}'),
          Text('取件地点: ${order.pickupLocation}'),
          Text('送达地点: ${order.deliveryLocation}'),
          Text('代取费用: ¥${order.serviceFee.toStringAsFixed(1)}'),
          if (order.isUrgent)
            const Text(
              '⚡ 加急订单',
              style: TextStyle(color: Colors.orange, fontWeight: FontWeight.bold),
            ),
        ],
      ),
      actions: [
        TextButton(
          onPressed: () => Navigator.pop(context),
          child: const Text('取消'),
        ),
        ElevatedButton(
          onPressed: () {
            Navigator.pop(context);
            _updateOrderStatus(order, OrderStatus.accepted);
          },
          child: const Text('确认接单'),
        ),
      ],
    ),
  );
}

第七步:统计分析功能

统计数据计算
class DeliveryStats {
  final int totalOrders;
  final int completedOrders;
  final int pendingOrders;
  final double totalEarnings;
  final double averageRating;
  final Map<String, int> ordersByCompany;
  final Map<OrderStatus, int> ordersByStatus;
  final List<ExpressOrder> recentOrders;
  final double completionRate;

  DeliveryStats({
    required this.totalOrders,
    required this.completedOrders,
    required this.pendingOrders,
    required this.totalEarnings,
    required this.averageRating,
    required this.ordersByCompany,
    required this.ordersByStatus,
    required this.recentOrders,
    required this.completionRate,
  });
}

DeliveryStats _calculateStats() {
  final totalOrders = _orders.length;
  final completedOrders = _orders.where((o) => o.status == OrderStatus.completed).length;
  final pendingOrders = _orders.where((o) => o.status == OrderStatus.pending).length;
  final totalEarnings = _orders
      .where((o) => o.status == OrderStatus.completed)
      .fold<double>(0, (sum, order) => sum + order.serviceFee);
  
  final completedOrdersWithRating = _orders
      .where((o) => o.status == OrderStatus.completed && o.rating > 0)
      .toList();
  final averageRating = completedOrdersWithRating.isNotEmpty
      ? completedOrdersWithRating.fold<double>(0, (sum, order) => sum + order.rating) / completedOrdersWithRating.length
      : 0.0;
  
  final ordersByCompany = <String, int>{};
  final ordersByStatus = <OrderStatus, int>{};
  
  for (final order in _orders) {
    ordersByCompany[order.company] = (ordersByCompany[order.company] ?? 0) + 1;
    ordersByStatus[order.status] = (ordersByStatus[order.status] ?? 0) + 1;
  }
  
  final recentOrders = _orders
      .where((o) => o.createTime.isAfter(DateTime.now().subtract(const Duration(days: 7))))
      .toList()
    ..sort((a, b) => b.createTime.compareTo(a.createTime));
  
  final completionRate = totalOrders > 0 ? (completedOrders / totalOrders * 100) : 0.0;
  
  return DeliveryStats(
    totalOrders: totalOrders,
    completedOrders: completedOrders,
    pendingOrders: pendingOrders,
    totalEarnings: totalEarnings,
    averageRating: averageRating,
    ordersByCompany: ordersByCompany,
    ordersByStatus: ordersByStatus,
    recentOrders: recentOrders,
    completionRate: completionRate,
  );
}
统计页面展示
Widget _buildStatsPage() {
  final stats = _calculateStats();
  
  return SingleChildScrollView(
    padding: const EdgeInsets.all(16),
    child: Column(
      children: [
        // 总览统计卡片
        Card(
          elevation: 4,
          child: Padding(
            padding: const EdgeInsets.all(20),
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                const Row(
                  children: [
                    Icon(Icons.analytics, color: Colors.blue, size: 28),
                    SizedBox(width: 12),
                    Text(
                      '数据统计',
                      style: TextStyle(
                        fontSize: 20,
                        fontWeight: FontWeight.bold,
                      ),
                    ),
                  ],
                ),
                const SizedBox(height: 20),
                Row(
                  children: [
                    Expanded(
                      child: _buildStatItem(
                        '总订单',
                        '${stats.totalOrders}单',
                        Icons.receipt_long,
                        Colors.blue,
                      ),
                    ),
                    Expanded(
                      child: _buildStatItem(
                        '已完成',
                        '${stats.completedOrders}单',
                        Icons.check_circle,
                        Colors.green,
                      ),
                    ),
                  ],
                ),
                const SizedBox(height: 16),
                Row(
                  children: [
                    Expanded(
                      child: _buildStatItem(
                        '总收入',
                        ${stats.totalEarnings.toStringAsFixed(0)}',
                        Icons.account_balance_wallet,
                        Colors.orange,
                      ),
                    ),
                    Expanded(
                      child: _buildStatItem(
                        '平均评分',
                        '${stats.averageRating.toStringAsFixed(1)}分',
                        Icons.star,
                        Colors.amber,
                      ),
                    ),
                  ],
                ),
                const SizedBox(height: 16),
                Container(
                  width: double.infinity,
                  padding: const EdgeInsets.all(16),
                  decoration: BoxDecoration(
                    color: Colors.blue.withValues(alpha: 0.1),
                    borderRadius: BorderRadius.circular(12),
                  ),
                  child: Column(
                    children: [
                      Text(
                        '完成率',
                        style: TextStyle(
                          fontSize: 14,
                          color: Colors.grey.shade600,
                        ),
                      ),
                      const SizedBox(height: 8),
                      Text(
                        '${stats.completionRate.toStringAsFixed(1)}%',
                        style: const TextStyle(
                          fontSize: 32,
                          fontWeight: FontWeight.bold,
                          color: Colors.blue,
                        ),
                      ),
                      const SizedBox(height: 8),
                      LinearProgressIndicator(
                        value: stats.completionRate / 100,
                        backgroundColor: Colors.grey.shade300,
                        valueColor: const AlwaysStoppedAnimation<Color>(Colors.blue),
                      ),
                    ],
                  ),
                ),
              ],
            ),
          ),
        ),
        
        const SizedBox(height: 16),
        
        // 快递公司分布
        Card(
          child: Padding(
            padding: const EdgeInsets.all(16),
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                const Text(
                  '快递公司分布',
                  style: TextStyle(
                    fontSize: 18,
                    fontWeight: FontWeight.bold,
                  ),
                ),
                const SizedBox(height: 16),
                ...stats.ordersByCompany.entries.map((entry) => Padding(
                  padding: const EdgeInsets.only(bottom: 12),
                  child: Row(
                    children: [
                      Container(
                        width: 30,
                        height: 30,
                        decoration: BoxDecoration(
                          color: _getCompanyColor(entry.key).withValues(alpha: 0.1),
                          borderRadius: BorderRadius.circular(6),
                        ),
                        child: Icon(
                          _getCompanyIcon(entry.key),
                          size: 16,
                          color: _getCompanyColor(entry.key),
                        ),
                      ),
                      const SizedBox(width: 12),
                      Expanded(
                        child: Column(
                          crossAxisAlignment: CrossAxisAlignment.start,
                          children: [
                            Text(
                              entry.key,
                              style: const TextStyle(fontWeight: FontWeight.w500),
                            ),
                            const SizedBox(height: 2),
                            LinearProgressIndicator(
                              value: entry.value / stats.totalOrders,
                              backgroundColor: Colors.grey.shade200,
                              valueColor: AlwaysStoppedAnimation<Color>(
                                _getCompanyColor(entry.key),
                              ),
                            ),
                          ],
                        ),
                      ),
                      const SizedBox(width: 12),
                      Text(
                        '${entry.value}单',
                        style: TextStyle(
                          fontSize: 14,
                          fontWeight: FontWeight.bold,
                          color: Colors.grey.shade600,
                        ),
                      ),
                    ],
                  ),
                )).toList(),
              ],
            ),
          ),
        ),
        
        const SizedBox(height: 16),
        
        // 最近订单
        Card(
          child: Padding(
            padding: const EdgeInsets.all(16),
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                const Text(
                  '最近7天订单',
                  style: TextStyle(
                    fontSize: 18,
                    fontWeight: FontWeight.bold,
                  ),
                ),
                const SizedBox(height: 16),
                if (stats.recentOrders.isEmpty)
                  const Center(
                    child: Text(
                      '暂无最近订单',
                      style: TextStyle(color: Colors.grey),
                    ),
                  )
                else
                  ...stats.recentOrders.take(5).map((order) => Padding(
                    padding: const EdgeInsets.only(bottom: 8),
                    child: Row(
                      children: [
                        Container(
                          width: 8,
                          height: 8,
                          decoration: BoxDecoration(
                            color: _getStatusColor(order.status),
                            shape: BoxShape.circle,
                          ),
                        ),
                        const SizedBox(width: 12),
                        Expanded(
                          child: Column(
                            crossAxisAlignment: CrossAxisAlignment.start,
                            children: [
                              Text(
                                order.trackingNumber,
                                style: const TextStyle(fontWeight: FontWeight.w500),
                              ),
                              Text(
                                _formatDateTime(order.createTime),
                                style: TextStyle(
                                  fontSize: 12,
                                  color: Colors.grey.shade600,
                                ),
                              ),
                            ],
                          ),
                        ),
                        Text(
                          _getStatusText(order.status),
                          style: TextStyle(
                            fontSize: 12,
                            color: _getStatusColor(order.status),
                            fontWeight: FontWeight.bold,
                          ),
                        ),
                      ],
                    ),
                  )).toList(),
              ],
            ),
          ),
        ),
      ],
    ),
  );
}

第八步:辅助功能

颜色和状态管理
Color _getStatusColor(OrderStatus status) {
  switch (status) {
    case OrderStatus.pending: return Colors.orange;
    case OrderStatus.accepted: return Colors.blue;
    case OrderStatus.picking: return Colors.purple;
    case OrderStatus.picked: return Colors.indigo;
    case OrderStatus.delivering: return Colors.teal;
    case OrderStatus.completed: return Colors.green;
    case OrderStatus.cancelled: return Colors.red;
  }
}

String _getStatusText(OrderStatus status) {
  switch (status) {
    case OrderStatus.pending: return '待接单';
    case OrderStatus.accepted: return '已接单';
    case OrderStatus.picking: return '取件中';
    case OrderStatus.picked: return '已取件';
    case OrderStatus.delivering: return '配送中';
    case OrderStatus.completed: return '已完成';
    case OrderStatus.cancelled: return '已取消';
  }
}

Color _getCompanyColor(String company) {
  switch (company) {
    case '顺丰速运': return Colors.yellow.shade700;
    case '圆通速递': return Colors.green;
    case '中通快递': return Colors.blue;
    case '申通快递': return Colors.orange;
    case '韵达速递': return Colors.purple;
    case '百世汇通': return Colors.red;
    case '京东物流': return Colors.red.shade700;
    case '菜鸟驿站': return Colors.orange.shade700;
    default: return Colors.grey;
  }
}

IconData _getCompanyIcon(String company) {
  switch (company) {
    case '顺丰速运': return Icons.flight;
    case '圆通速递': return Icons.local_shipping;
    case '中通快递': return Icons.delivery_dining;
    case '申通快递': return Icons.fire_truck;
    case '韵达速递': return Icons.airport_shuttle;
    case '百世汇通': return Icons.local_shipping;
    case '京东物流': return Icons.shopping_cart;
    case '菜鸟驿站': return Icons.store;
    default: return Icons.local_post_office;
  }
}

核心功能详解

1. 实时订单追踪

应用提供完整的订单状态追踪功能:

Widget _buildOrderTimeline(ExpressOrder order) {
  final steps = [
    TimelineStep('下单', order.createTime, true),
    TimelineStep('接单', order.pickupTime, order.status.index >= OrderStatus.accepted.index),
    TimelineStep('取件', order.pickupTime, order.status.index >= OrderStatus.picked.index),
    TimelineStep('配送', null, order.status.index >= OrderStatus.delivering.index),
    TimelineStep('完成', order.deliveryTime, order.status == OrderStatus.completed),
  ];

  return Column(
    children: steps.asMap().entries.map((entry) {
      final index = entry.key;
      final step = entry.value;
      final isLast = index == steps.length - 1;

      return Row(
        children: [
          Column(
            children: [
              Container(
                width: 20,
                height: 20,
                decoration: BoxDecoration(
                  color: step.isCompleted ? Colors.green : Colors.grey.shade300,
                  shape: BoxShape.circle,
                ),
                child: step.isCompleted
                    ? const Icon(Icons.check, size: 12, color: Colors.white)
                    : null,
              ),
              if (!isLast)
                Container(
                  width: 2,
                  height: 30,
                  color: Colors.grey.shade300,
                ),
            ],
          ),
          const SizedBox(width: 12),
          Expanded(
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Text(
                  step.title,
                  style: TextStyle(
                    fontWeight: FontWeight.w500,
                    color: step.isCompleted ? Colors.black : Colors.grey,
                  ),
                ),
                if (step.time != null)
                  Text(
                    _formatDateTime(step.time!),
                    style: TextStyle(
                      fontSize: 12,
                      color: Colors.grey.shade600,
                    ),
                  ),
              ],
            ),
          ),
        ],
      );
    }).toList(),
  );
}

2. 智能费用计算

根据距离、重量、紧急程度自动计算代取费用:

double _calculateServiceFee(ExpressOrder order) {
  double baseFee = 2.0; // 基础费用
  
  // 重量费用
  if (order.weight > 1.0) {
    baseFee += (order.weight - 1.0) * 0.5;
  }
  
  // 距离费用(简化计算)
  final distance = _calculateDistance(order.pickupLocation, order.deliveryLocation);
  if (distance > 500) {
    baseFee += (distance - 500) / 100 * 0.2;
  }
  
  // 加急费用
  if (order.isUrgent) {
    baseFee *= 1.5;
  }
  
  return baseFee;
}

double _calculateDistance(String from, String to) {
  // 简化的距离计算,实际应用中可以使用地图API
  final locations = {
    '南门快递点': [0, 0],
    'A区宿舍楼下': [200, 100],
    'B区宿舍楼下': [400, 200],
    'C区宿舍楼下': [600, 300],
    '图书馆': [300, 400],
    '食堂': [100, 300],
  };
  
  final fromCoord = locations[from] ?? [0, 0];
  final toCoord = locations[to] ?? [0, 0];
  
  final dx = fromCoord[0] - toCoord[0];
  final dy = fromCoord[1] - toCoord[1];
  
  return sqrt(dx * dx + dy * dy).toDouble();
}

3. 评价系统

双向评价机制,维护服务质量:

void _showRatingDialog(ExpressOrder order) {
  double rating = 5.0;
  String feedback = '';
  List<String> selectedTags = [];
  
  final tags = ['服务态度好', '速度快', '包装完好', '联系及时', '位置准确'];

  showDialog(
    context: context,
    builder: (context) => StatefulBuilder(
      builder: (context, setState) => AlertDialog(
        title: const Text('订单评价'),
        content: SingleChildScrollView(
          child: Column(
            mainAxisSize: MainAxisSize.min,
            children: [
              Text('订单: ${order.trackingNumber}'),
              const SizedBox(height: 16),
              
              // 星级评分
              Row(
                mainAxisAlignment: MainAxisAlignment.center,
                children: List.generate(5, (index) => GestureDetector(
                  onTap: () => setState(() => rating = index + 1.0),
                  child: Icon(
                    index < rating ? Icons.star : Icons.star_border,
                    color: Colors.amber,
                    size: 32,
                  ),
                )),
              ),
              
              const SizedBox(height: 16),
              
              // 标签选择
              Wrap(
                spacing: 8,
                runSpacing: 4,
                children: tags.map((tag) => FilterChip(
                  label: Text(tag),
                  selected: selectedTags.contains(tag),
                  onSelected: (selected) {
                    setState(() {
                      if (selected) {
                        selectedTags.add(tag);
                      } else {
                        selectedTags.remove(tag);
                      }
                    });
                  },
                )).toList(),
              ),
              
              const SizedBox(height: 16),
              
              // 文字评价
              TextField(
                decoration: const InputDecoration(
                  labelText: '评价内容',
                  hintText: '请输入您的评价...',
                  border: OutlineInputBorder(),
                ),
                maxLines: 3,
                onChanged: (value) => feedback = value,
              ),
            ],
          ),
        ),
        actions: [
          TextButton(
            onPressed: () => Navigator.pop(context),
            child: const Text('取消'),
          ),
          ElevatedButton(
            onPressed: () {
              _submitRating(order, rating, feedback, selectedTags);
              Navigator.pop(context);
            },
            child: const Text('提交评价'),
          ),
        ],
      ),
    ),
  );
}

性能优化

1. 列表优化

使用ListView.builder实现虚拟滚动:

ListView.builder(
  padding: const EdgeInsets.all(16),
  itemCount: _filteredOrders.length,
  itemBuilder: (context, index) {
    final order = _filteredOrders[index];
    return _buildOrderCard(order);
  },
)

2. 状态管理优化

合理使用setState,避免不必要的重建:

void _updateOrderStatus(ExpressOrder order, OrderStatus newStatus) {
  setState(() {
    final index = _orders.indexWhere((o) => o.id == order.id);
    if (index != -1) {
      // 只更新必要的字段
      _orders[index] = _orders[index].copyWith(
        status: newStatus,
        pickupTime: newStatus == OrderStatus.picked ? DateTime.now() : null,
        deliveryTime: newStatus == OrderStatus.completed ? DateTime.now() : null,
      );
    }
  });
}

3. 数据缓存

实现本地数据缓存:

class CacheManager {
  static final Map<String, dynamic> _cache = {};

  static void setCache(String key, dynamic value) {
    _cache[key] = value;
  }

  static T? getCache<T>(String key) {
    return _cache[key] as T?;
  }

  static void clearCache() {
    _cache.clear();
  }
}

扩展功能

1. 地图集成

可以集成地图服务实现路径规划:

dependencies:
  amap_flutter_map: ^3.0.0
  amap_flutter_location: ^3.0.0

2. 推送通知

集成推送服务实现订单状态通知:

dependencies:
  firebase_messaging: ^14.6.5
  flutter_local_notifications: ^15.1.0+1

3. 支付集成

集成支付功能实现在线结算:

dependencies:
  pay: ^1.1.2
  alipay_kit: ^3.0.0

测试策略

1. 单元测试

测试核心业务逻辑:

test('should calculate service fee correctly', () {
  final order = ExpressOrder(/* 参数 */);
  final fee = _calculateServiceFee(order);
  expect(fee, greaterThan(0));
});

2. Widget测试

测试UI组件:

testWidgets('should display order information', (WidgetTester tester) async {
  final order = ExpressOrder(/* 参数 */);
  
  await tester.pumpWidget(MaterialApp(
    home: Scaffold(body: OrderCard(order: order)),
  ));

  expect(find.text(order.trackingNumber), findsOneWidget);
  expect(find.text(order.recipientName), findsOneWidget);
});

3. 集成测试

测试完整用户流程:

testWidgets('should accept order successfully', (WidgetTester tester) async {
  await tester.pumpWidget(MyApp());
  
  // 点击接单按钮
  await tester.tap(find.text('接单'));
  await tester.pumpAndSettle();
  
  // 确认接单
  await tester.tap(find.text('确认接单'));
  await tester.pumpAndSettle();
  
  // 验证状态更新
  expect(find.text('已接单'), findsOneWidget);
});

部署发布

1. Android打包

flutter build apk --release

2. iOS打包

flutter build ios --release

3. 应用商店发布

准备应用图标、截图和描述,提交到各大应用商店。

总结

本教程详细介绍了Flutter校园快递代取记录应用的完整开发过程,涵盖了:

  • 数据模型设计:订单、代取员、评价等完整的数据结构
  • UI界面开发:Material Design 3风格的现代化界面
  • 功能实现:订单管理、状态追踪、费用计算、评价系统
  • 动画效果:提升用户体验的流畅动画
  • 性能优化:列表优化、状态管理、数据缓存
  • 扩展功能:地图集成、推送通知、支付集成
  • 测试策略:单元测试、Widget测试、集成测试

这款应用不仅功能全面,而且代码结构清晰,易于维护和扩展。通过本教程的学习,你可以掌握Flutter应用开发的核心技能,为后续开发更复杂的应用打下坚实基础。

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

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐