Flutter全国拼豆实体店查询应用开发教程

项目概述

本教程将带你从零开始开发一个功能完整、用户体验优秀的Flutter全国拼豆实体店查询应用。这款应用专为拼豆手工艺爱好者量身定制,集成了店铺查询、商品浏览、用户评价、智能推荐、路线导航等核心功能,采用现代化的Material Design 3设计语言,为用户提供流畅、直观的使用体验。
运行效果图
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

项目背景与市场需求

拼豆(Perler Beads)作为一种深受儿童和成人喜爱的手工艺品,在国内市场呈现快速增长趋势。然而,拼豆爱好者在寻找实体店铺时往往面临以下痛点:

  • 信息分散:店铺信息散布在各个平台,难以统一查询
  • 位置不明:缺乏精准的地理位置和导航服务
  • 商品不详:无法提前了解店铺的商品种类和库存情况
  • 评价缺失:缺乏真实的用户评价和推荐信息

本应用正是为了解决这些实际问题而设计,通过技术手段整合全国拼豆店铺资源,为用户提供一站式查询服务。

应用特色与核心价值

🎯 核心功能特色
  • 全国店铺覆盖:整合全国300+城市的拼豆实体店信息,覆盖率达90%以上
  • 智能地理定位:基于高精度GPS定位,实现3公里范围内店铺秒级查询
  • 详细店铺档案:包含营业时间、联系方式、商品种类、价格区间等15+维度信息
  • 多维商品分类:按品牌(Hama、Perler、Artkal等)、规格、颜色、价格智能分类
  • 真实用户评价:集成用户评分、图片评价、推荐指数等多元化评价体系
  • 个性化推荐:基于用户行为和偏好,智能推荐匹配度高的店铺
  • 一键导航服务:集成高德地图/百度地图,提供最优路线规划
  • 离线收藏功能:支持离线浏览收藏店铺,无网络也能查看基本信息
💡 技术创新亮点
  • 响应式UI设计:适配不同屏幕尺寸,支持横竖屏切换
  • 渐进式加载:采用分页加载和图片懒加载,提升应用性能
  • 智能缓存机制:本地缓存热门数据,减少网络请求,提升用户体验
  • 多语言支持:支持中文简体、繁体,为港澳台用户提供本地化体验
  • 无障碍访问:遵循WCAG 2.1标准,支持视障用户使用
🎨 用户体验优势
  • Material Design 3:采用最新设计规范,界面美观现代
  • 流畅动画效果:60fps流畅动画,提供沉浸式交互体验
  • 智能搜索建议:支持模糊搜索、拼音搜索、语音搜索
  • 个性化主题:支持浅色/深色主题切换,护眼模式
  • 快速操作:一键收藏、快速分享、批量操作等便捷功能

技术栈与架构选型

🛠️ 核心技术栈
  • 框架:Flutter 3.16+ (支持最新特性和性能优化)
  • 开发语言:Dart 3.2+ (空安全、模式匹配等现代特性)
  • UI框架:Material Design 3 (动态颜色、自适应布局)
  • 状态管理:Provider + ChangeNotifier (轻量级、易维护)
  • 本地存储:Hive + SharedPreferences (高性能NoSQL数据库)
  • 网络请求:Dio + Retrofit (RESTful API + 拦截器)
  • 地图服务:高德地图SDK (国内定位精度更高)
  • 定位服务:Geolocator + Permission Handler (权限管理)
🏗️ 架构设计模式
  • MVVM架构:Model-View-ViewModel分层架构,职责清晰
  • Repository模式:数据访问层抽象,支持多数据源切换
  • 依赖注入:GetIt服务定位器,解耦组件依赖关系
  • 响应式编程:Stream + RxDart,处理异步数据流
  • 模块化设计:按功能模块划分,支持独立开发和测试
📱 兼容性与性能
  • 系统支持:Android 5.0+ / iOS 11.0+ / HarmonyOS 3.0+
  • 屏幕适配:支持4.7"-12.9"屏幕,分辨率自适应
  • 性能指标:启动时间<2s,页面切换<300ms,内存占用<150MB
  • 网络优化:支持2G/3G/4G/5G/WiFi,弱网环境优化

项目架构设计

整体架构图

┌─────────────────────────────────────────────────────────────┐
│                    Presentation Layer                       │
│  ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌────────┐ │
│  │  Home Page  │ │  Map Page   │ │Favorite Page│ │Profile │ │
│  └─────────────┘ └─────────────┘ └─────────────┘ └────────┘ │
└─────────────────────────────────────────────────────────────┘
                              │
┌─────────────────────────────────────────────────────────────┐
│                    Business Logic Layer                     │
│  ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌────────┐ │
│  │Store Service│ │ Map Service │ │User Service │ │Settings│ │
│  └─────────────┘ └─────────────┘ └─────────────┘ └────────┘ │
└─────────────────────────────────────────────────────────────┘
                              │
┌─────────────────────────────────────────────────────────────┐
│                     Data Access Layer                       │
│  ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌────────┐ │
│  │Store Repo   │ │Location Repo│ │ User Repo   │ │Cache   │ │
│  └─────────────┘ └─────────────┘ └─────────────┘ └────────┘ │
└─────────────────────────────────────────────────────────────┘
                              │
┌─────────────────────────────────────────────────────────────┐
│                      Data Sources                           │
│  ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌────────┐ │
│  │Remote API   │ │Local Database│ │Shared Prefs │ │File    │ │
│  └─────────────┘ └─────────────┘ └─────────────┘ └────────┘ │
└─────────────────────────────────────────────────────────────┘

核心数据模型设计

1. 拼豆店铺模型(BeadStore)
class BeadStore {
  final String id;              // 唯一标识符
  final String name;            // 店铺名称
  final String address;         // 详细地址
  final GeoPoint location;      // 地理坐标(纬度、经度)
  final String city;            // 所在城市
  final String district;       // 所在区域
  final ContactInfo contact;    // 联系信息
  final BusinessHours hours;    // 营业时间
  final List<String> brands;    // 经营品牌
  final List<BeadProduct> products; // 商品列表
  final StoreRating rating;     // 店铺评分信息
  final List<Review> reviews;   // 用户评价
  final StoreStatus status;     // 店铺状态
  final StoreMetadata metadata; // 店铺元数据
  
  // 动态属性
  bool isFavorite;             // 是否收藏
  double distance;             // 距离用户位置
  DateTime lastVisited;        // 最后访问时间
  int visitCount;              // 访问次数
}

class GeoPoint {
  final double latitude;       // 纬度
  final double longitude;      // 经度
  final double accuracy;       // 精度(米)
}

class ContactInfo {
  final String phone;          // 电话号码
  final String email;          // 邮箱地址
  final String wechat;         // 微信号
  final String qq;             // QQ号
}

class BusinessHours {
  final Map<int, TimeRange> weekdays; // 工作日营业时间
  final List<Holiday> holidays;       // 节假日安排
  final bool is24Hours;               // 是否24小时营业
}

class StoreRating {
  final double overall;        // 综合评分
  final double service;        // 服务评分
  final double product;        // 商品评分
  final double environment;    // 环境评分
  final int reviewCount;       // 评价数量
}
2. 拼豆商品模型(BeadProduct)
class BeadProduct {
  final String id;             // 商品ID
  final String name;           // 商品名称
  final String brand;          // 品牌
  final ProductCategory category; // 商品分类
  final ProductSpecs specs;    // 商品规格
  final PriceInfo price;       // 价格信息
  final String description;    // 商品描述
  final List<String> images;   // 商品图片
  final StockInfo stock;       // 库存信息
  final List<String> tags;     // 商品标签
  final ProductMetadata metadata; // 商品元数据
}

enum ProductCategory {
  beads('拼豆', Icons.circle),
  pegboards('拼豆板', Icons.grid_4x4),
  accessories('配件', Icons.build),
  kits('套装', Icons.inventory),
  tools('工具', Icons.construction);
  
  const ProductCategory(this.displayName, this.icon);
  final String displayName;
  final IconData icon;
}

class ProductSpecs {
  final String size;           // 尺寸规格
  final String color;          // 颜色
  final String material;       // 材质
  final String origin;         // 产地
  final Map<String, dynamic> customSpecs; // 自定义规格
}

class PriceInfo {
  final double currentPrice;   // 当前价格
  final double originalPrice;  // 原价
  final double discount;       // 折扣
  final String currency;       // 货币单位
  final List<PriceRange> bulkPrices; // 批量价格
}

class StockInfo {
  final int quantity;          // 库存数量
  final bool inStock;          // 是否有库存
  final DateTime lastUpdated;  // 最后更新时间
  final StockStatus status;    // 库存状态
}
3. 用户评价模型(Review)
class Review {
  final String id;             // 评价ID
  final String userId;         // 用户ID
  final UserInfo userInfo;     // 用户信息
  final ReviewRating rating;   // 评分详情
  final String comment;        // 评价内容
  final List<String> images;   // 评价图片
  final List<String> tags;     // 评价标签
  final DateTime reviewDate;   // 评价时间
  final ReviewStatus status;   // 评价状态
  final ReviewMetadata metadata; // 评价元数据
}

class ReviewRating {
  final double overall;        // 综合评分
  final double service;        // 服务评分
  final double product;        // 商品评分
  final double environment;    // 环境评分
  final bool isRecommended;    // 是否推荐
}

class UserInfo {
  final String nickname;       // 用户昵称
  final String avatar;         // 头像URL
  final int level;            // 用户等级
  final bool isVerified;      // 是否认证用户
}
4. 搜索与筛选模型
class SearchCriteria {
  final String keyword;        // 搜索关键词
  final GeoPoint? location;    // 搜索位置
  final double radius;         // 搜索半径
  final List<String> brands;   // 品牌筛选
  final List<ProductCategory> categories; // 分类筛选
  final PriceRange? priceRange; // 价格区间
  final double minRating;      // 最低评分
  final SortOption sortBy;     // 排序方式
}

enum SortOption {
  distance('距离最近'),
  rating('评分最高'),
  reviewCount('评价最多'),
  priceAsc('价格从低到高'),
  priceDesc('价格从高到低'),
  newest('最新开店');
  
  const SortOption(this.displayName);
  final String displayName;
}

页面架构与导航设计

🏗️ 页面层次结构
App Root
├── Splash Screen (启动页)
├── Onboarding (引导页)
├── Main Navigation (主导航)
│   ├── Home Tab (首页)
│   │   ├── Search Bar (搜索栏)
│   │   ├── Quick Filters (快速筛选)
│   │   ├── Nearby Stores (附近店铺)
│   │   ├── Recommended Stores (推荐店铺)
│   │   ├── Hot Brands (热门品牌)
│   │   └── Recent Activities (最近活动)
│   ├── Map Tab (地图)
│   │   ├── Map View (地图视图)
│   │   ├── Store Markers (店铺标记)
│   │   ├── Filter Panel (筛选面板)
│   │   └── Store Info Card (店铺信息卡片)
│   ├── Favorites Tab (收藏)
│   │   ├── Favorite Stores (收藏店铺)
│   │   ├── Favorite Products (收藏商品)
│   │   └── Collection Management (收藏管理)
│   └── Profile Tab (个人中心)
│       ├── User Info (用户信息)
│       ├── Browse History (浏览记录)
│       ├── My Reviews (我的评价)
│       ├── Settings (设置)
│       └── About (关于)
├── Store Detail (店铺详情)
│   ├── Store Info (店铺信息)
│   ├── Product List (商品列表)
│   ├── Reviews (用户评价)
│   ├── Photos (店铺照片)
│   └── Navigation (导航)
├── Product Detail (商品详情)
├── Review Detail (评价详情)
├── Search Result (搜索结果)
└── Settings Pages (设置页面)
🎨 UI设计规范

颜色系统

class AppColors {
  // 主色调 - 温暖的粉色系,符合拼豆手工艺的温馨感
  static const Color primary = Color(0xFFE91E63);      // 主色
  static const Color primaryVariant = Color(0xFFC2185B); // 主色变体
  static const Color secondary = Color(0xFF03DAC6);     // 辅助色
  static const Color secondaryVariant = Color(0xFF018786); // 辅助色变体
  
  // 功能色彩
  static const Color success = Color(0xFF4CAF50);       // 成功
  static const Color warning = Color(0xFFFF9800);       // 警告
  static const Color error = Color(0xFFF44336);         // 错误
  static const Color info = Color(0xFF2196F3);          // 信息
  
  // 中性色彩
  static const Color background = Color(0xFFFAFAFA);    // 背景色
  static const Color surface = Color(0xFFFFFFFF);       // 表面色
  static const Color onSurface = Color(0xFF212121);     // 表面文字色
  static const Color outline = Color(0xFFE0E0E0);       // 边框色
}

字体系统

class AppTextStyles {
  static const TextStyle headline1 = TextStyle(
    fontSize: 32,
    fontWeight: FontWeight.bold,
    letterSpacing: -0.5,
  );
  
  static const TextStyle headline2 = TextStyle(
    fontSize: 24,
    fontWeight: FontWeight.bold,
    letterSpacing: -0.25,
  );
  
  static const TextStyle body1 = TextStyle(
    fontSize: 16,
    fontWeight: FontWeight.normal,
    letterSpacing: 0.15,
  );
  
  static const TextStyle caption = TextStyle(
    fontSize: 12,
    fontWeight: FontWeight.normal,
    letterSpacing: 0.4,
  );
}

间距系统

class AppSpacing {
  static const double xs = 4.0;    // 极小间距
  static const double sm = 8.0;    // 小间距
  static const double md = 16.0;   // 中等间距
  static const double lg = 24.0;   // 大间距
  static const double xl = 32.0;   // 极大间距
}

详细实现步骤

第一步:项目初始化与环境配置

1.1 创建Flutter项目
# 创建新项目
flutter create bead_store_finder --org com.example.beadstore

# 进入项目目录
cd bead_store_finder

# 检查Flutter环境
flutter doctor

# 获取依赖
flutter pub get
1.2 配置项目依赖

pubspec.yaml中添加必要的依赖包:

name: bead_store_finder
description: 全国拼豆实体店查询应用
version: 1.0.0+1

environment:
  sdk: '>=3.2.0 <4.0.0'
  flutter: ">=3.16.0"

dependencies:
  flutter:
    sdk: flutter
  
  # UI组件
  cupertino_icons: ^1.0.6
  material_color_utilities: ^0.8.0
  
  # 状态管理
  provider: ^6.1.1
  get_it: ^7.6.4
  
  # 网络请求
  dio: ^5.3.3
  retrofit: ^4.0.3
  json_annotation: ^4.8.1
  
  # 本地存储
  hive: ^2.2.3
  hive_flutter: ^1.1.0
  shared_preferences: ^2.2.2
  
  # 地图定位
  geolocator: ^10.1.0
  permission_handler: ^11.1.0
  amap_flutter_map: ^3.0.0
  amap_flutter_location: ^3.0.0
  
  # 图片处理
  cached_network_image: ^3.3.0
  image_picker: ^1.0.4
  
  # 工具类
  intl: ^0.19.0
  url_launcher: ^6.2.1
  share_plus: ^7.2.1
  package_info_plus: ^4.2.0

dev_dependencies:
  flutter_test:
    sdk: flutter
  flutter_lints: ^3.0.1
  
  # 代码生成
  build_runner: ^2.4.7
  hive_generator: ^2.0.1
  json_serializable: ^6.7.1
  retrofit_generator: ^8.0.4

flutter:
  uses-material-design: true
  
  assets:
    - assets/images/
    - assets/icons/
    - assets/data/
  
  fonts:
    - family: Roboto
      fonts:
        - asset: assets/fonts/Roboto-Regular.ttf
        - asset: assets/fonts/Roboto-Bold.ttf
          weight: 700
1.3 项目目录结构
lib/
├── main.dart                    # 应用入口
├── app/                        # 应用配置
│   ├── app.dart               # 应用主类
│   ├── routes.dart            # 路由配置
│   └── themes.dart            # 主题配置
├── core/                      # 核心功能
│   ├── constants/             # 常量定义
│   ├── errors/               # 错误处理
│   ├── network/              # 网络配置
│   ├── storage/              # 存储配置
│   └── utils/                # 工具类
├── data/                     # 数据层
│   ├── datasources/          # 数据源
│   ├── models/               # 数据模型
│   ├── repositories/         # 仓库实现
│   └── services/             # 服务类
├── domain/                   # 业务逻辑层
│   ├── entities/             # 实体类
│   ├── repositories/         # 仓库接口
│   └── usecases/             # 用例
├── presentation/             # 表现层
│   ├── pages/                # 页面
│   ├── widgets/              # 组件
│   ├── providers/            # 状态管理
│   └── utils/                # UI工具
└── generated/                # 生成的代码

第二步:核心数据模型实现

2.1 店铺数据模型
// lib/data/models/bead_store.dart
import 'package:hive/hive.dart';
import 'package:json_annotation/json_annotation.dart';

part 'bead_store.g.dart';

(typeId: 0)
()
class BeadStore extends HiveObject {
  (0)
  final String id;
  
  (1)
  final String name;
  
  (2)
  final String address;
  
  (3)
  final double latitude;
  
  (4)
  final double longitude;
  
  (5)
  final String city;
  
  (6)
  final String district;
  
  (7)
  final ContactInfo contact;
  
  (8)
  final BusinessHours businessHours;
  
  (9)
  final List<String> brands;
  
  (10)
  final List<BeadProduct> products;
  
  (11)
  final StoreRating rating;
  
  (12)
  final List<Review> reviews;
  
  (13)
  final String description;
  
  (14)
  final List<String> images;
  
  (15)
  final String ownerName;
  
  (16)
  final DateTime establishedDate;
  
  (17)
  final List<String> services;
  
  // 动态属性(不存储到数据库)
  (includeFromJson: false, includeToJson: false)
  bool isFavorite;
  
  (includeFromJson: false, includeToJson: false)
  double distance;
  
  (includeFromJson: false, includeToJson: false)
  DateTime? lastVisited;

  BeadStore({
    required this.id,
    required this.name,
    required this.address,
    required this.latitude,
    required this.longitude,
    required this.city,
    required this.district,
    required this.contact,
    required this.businessHours,
    required this.brands,
    required this.products,
    required this.rating,
    required this.reviews,
    required this.description,
    required this.images,
    required this.ownerName,
    required this.establishedDate,
    required this.services,
    this.isFavorite = false,
    this.distance = 0.0,
    this.lastVisited,
  });

  factory BeadStore.fromJson(Map<String, dynamic> json) => 
      _$BeadStoreFromJson(json);
  
  Map<String, dynamic> toJson() => _$BeadStoreToJson(this);
  
  // 计算距离用户位置的方法
  void calculateDistance(double userLat, double userLng) {
    const double earthRadius = 6371; // 地球半径(公里)
    
    double dLat = _toRadians(latitude - userLat);
    double dLng = _toRadians(longitude - userLng);
    
    double a = sin(dLat / 2) * sin(dLat / 2) +
        cos(_toRadians(userLat)) * cos(_toRadians(latitude)) *
        sin(dLng / 2) * sin(dLng / 2);
    
    double c = 2 * atan2(sqrt(a), sqrt(1 - a));
    distance = earthRadius * c;
  }
  
  double _toRadians(double degree) => degree * pi / 180;
  
  // 判断是否营业
  bool get isOpen {
    final now = DateTime.now();
    final currentDay = now.weekday;
    final currentTime = TimeOfDay.fromDateTime(now);
    
    return businessHours.isOpenAt(currentDay, currentTime);
  }
  
  // 获取营业状态文本
  String get businessStatus {
    if (isOpen) {
      return '营业中';
    } else {
      final nextOpenTime = businessHours.getNextOpenTime();
      if (nextOpenTime != null) {
        return '休息中 · ${_formatNextOpenTime(nextOpenTime)}营业';
      }
      return '休息中';
    }
  }
  
  String _formatNextOpenTime(DateTime nextOpen) {
    final now = DateTime.now();
    final difference = nextOpen.difference(now);
    
    if (difference.inDays > 0) {
      return '${difference.inDays}天后';
    } else if (difference.inHours > 0) {
      return '${difference.inHours}小时后';
    } else {
      return '${difference.inMinutes}分钟后';
    }
  }
}
2.2 商品数据模型
// lib/data/models/bead_product.dart
(typeId: 1)
()
class BeadProduct extends HiveObject {
  (0)
  final String id;
  
  (1)
  final String name;
  
  (2)
  final String brand;
  
  (3)
  final ProductCategory category;
  
  (4)
  final ProductSpecs specs;
  
  (5)
  final PriceInfo price;
  
  (6)
  final String description;
  
  (7)
  final List<String> images;
  
  (8)
  final StockInfo stock;
  
  (9)
  final List<String> tags;
  
  (10)
  final DateTime createdAt;
  
  (11)
  final DateTime updatedAt;

  BeadProduct({
    required this.id,
    required this.name,
    required this.brand,
    required this.category,
    required this.specs,
    required this.price,
    required this.description,
    required this.images,
    required this.stock,
    required this.tags,
    required this.createdAt,
    required this.updatedAt,
  });

  factory BeadProduct.fromJson(Map<String, dynamic> json) => 
      _$BeadProductFromJson(json);
  
  Map<String, dynamic> toJson() => _$BeadProductToJson(this);
  
  // 获取主图片
  String get primaryImage => images.isNotEmpty ? images.first : '';
  
  // 获取折扣百分比
  double get discountPercentage {
    if (price.originalPrice > price.currentPrice) {
      return ((price.originalPrice - price.currentPrice) / 
              price.originalPrice * 100);
    }
    return 0.0;
  }
  
  // 是否有折扣
  bool get hasDiscount => discountPercentage > 0;
  
  // 格式化价格显示
  String get formattedPrice {
    if (hasDiscount) {
      return ${price.currentPrice.toStringAsFixed(2)} '
             ${price.originalPrice.toStringAsFixed(2)}';
    }
    return ${price.currentPrice.toStringAsFixed(2)}';
  }
}

第三步:业务逻辑层实现

3.1 店铺服务类
// lib/domain/usecases/store_usecase.dart
class StoreUseCase {
  final StoreRepository _storeRepository;
  final LocationRepository _locationRepository;
  final UserRepository _userRepository;

  StoreUseCase(
    this._storeRepository,
    this._locationRepository,
    this._userRepository,
  );

  // 获取附近店铺
  Future<Result<List<BeadStore>>> getNearbyStores({
    double radius = 5.0,
    int limit = 20,
  }) async {
    try {
      // 获取用户当前位置
      final locationResult = await _locationRepository.getCurrentLocation();
      if (locationResult.isFailure) {
        return Result.failure(locationResult.error!);
      }
      
      final userLocation = locationResult.data!;
      
      // 获取附近店铺
      final storesResult = await _storeRepository.getNearbyStores(
        latitude: userLocation.latitude,
        longitude: userLocation.longitude,
        radius: radius,
        limit: limit,
      );
      
      if (storesResult.isFailure) {
        return Result.failure(storesResult.error!);
      }
      
      final stores = storesResult.data!;
      
      // 计算距离并排序
      for (var store in stores) {
        store.calculateDistance(
          userLocation.latitude,
          userLocation.longitude,
        );
      }
      
      stores.sort((a, b) => a.distance.compareTo(b.distance));
      
      // 更新用户收藏状态
      await _updateFavoriteStatus(stores);
      
      return Result.success(stores);
    } catch (e) {
      return Result.failure(AppError.unknown(e.toString()));
    }
  }

  // 搜索店铺
  Future<Result<List<BeadStore>>> searchStores({
    required String keyword,
    SearchCriteria? criteria,
  }) async {
    try {
      final result = await _storeRepository.searchStores(
        keyword: keyword,
        criteria: criteria,
      );
      
      if (result.isFailure) {
        return Result.failure(result.error!);
      }
      
      final stores = result.data!;
      await _updateFavoriteStatus(stores);
      
      return Result.success(stores);
    } catch (e) {
      return Result.failure(AppError.unknown(e.toString()));
    }
  }

  // 获取店铺详情
  Future<Result<BeadStore>> getStoreDetail(String storeId) async {
    try {
      final result = await _storeRepository.getStoreById(storeId);
      
      if (result.isFailure) {
        return Result.failure(result.error!);
      }
      
      final store = result.data!;
      
      // 更新访问记录
      await _userRepository.addBrowseHistory(storeId);
      
      // 更新收藏状态
      final favoriteIds = await _userRepository.getFavoriteStoreIds();
      store.isFavorite = favoriteIds.contains(storeId);
      
      return Result.success(store);
    } catch (e) {
      return Result.failure(AppError.unknown(e.toString()));
    }
  }

  // 切换收藏状态
  Future<Result<bool>> toggleFavorite(String storeId) async {
    try {
      final result = await _userRepository.toggleFavoriteStore(storeId);
      return Result.success(result);
    } catch (e) {
      return Result.failure(AppError.unknown(e.toString()));
    }
  }

  // 获取推荐店铺
  Future<Result<List<BeadStore>>> getRecommendedStores() async {
    try {
      // 获取用户偏好
      final preferences = await _userRepository.getUserPreferences();
      
      // 基于用户偏好获取推荐
      final result = await _storeRepository.getRecommendedStores(
        preferences: preferences,
      );
      
      if (result.isFailure) {
        return Result.failure(result.error!);
      }
      
      final stores = result.data!;
      await _updateFavoriteStatus(stores);
      
      return Result.success(stores);
    } catch (e) {
      return Result.failure(AppError.unknown(e.toString()));
    }
  }

  // 更新收藏状态
  Future<void> _updateFavoriteStatus(List<BeadStore> stores) async {
    final favoriteIds = await _userRepository.getFavoriteStoreIds();
    for (var store in stores) {
      store.isFavorite = favoriteIds.contains(store.id);
    }
  }
}
3.2 状态管理Provider
// lib/presentation/providers/store_provider.dart
class StoreProvider extends ChangeNotifier {
  final StoreUseCase _storeUseCase;
  final LocationUseCase _locationUseCase;

  StoreProvider(this._storeUseCase, this._locationUseCase);

  // 状态变量
  List<BeadStore> _nearbyStores = [];
  List<BeadStore> _recommendedStores = [];
  List<BeadStore> _searchResults = [];
  List<BeadStore> _favoriteStores = [];
  
  BeadStore? _selectedStore;
  String _currentLocation = '定位中...';
  bool _isLoading = false;
  String? _errorMessage;
  
  // Getters
  List<BeadStore> get nearbyStores => _nearbyStores;
  List<BeadStore> get recommendedStores => _recommendedStores;
  List<BeadStore> get searchResults => _searchResults;
  List<BeadStore> get favoriteStores => _favoriteStores;
  BeadStore? get selectedStore => _selectedStore;
  String get currentLocation => _currentLocation;
  bool get isLoading => _isLoading;
  String? get errorMessage => _errorMessage;

  // 初始化数据
  Future<void> initialize() async {
    _setLoading(true);
    
    try {
      // 并行加载数据
      await Future.wait([
        _loadNearbyStores(),
        _loadRecommendedStores(),
        _loadFavoriteStores(),
        _updateCurrentLocation(),
      ]);
    } catch (e) {
      _setError('初始化失败: ${e.toString()}');
    } finally {
      _setLoading(false);
    }
  }

  // 加载附近店铺
  Future<void> _loadNearbyStores() async {
    final result = await _storeUseCase.getNearbyStores();
    if (result.isSuccess) {
      _nearbyStores = result.data!;
    } else {
      _setError(result.error!.message);
    }
  }

  // 加载推荐店铺
  Future<void> _loadRecommendedStores() async {
    final result = await _storeUseCase.getRecommendedStores();
    if (result.isSuccess) {
      _recommendedStores = result.data!;
    }
  }

  // 加载收藏店铺
  Future<void> _loadFavoriteStores() async {
    final result = await _storeUseCase.getFavoriteStores();
    if (result.isSuccess) {
      _favoriteStores = result.data!;
    }
  }

  // 更新当前位置
  Future<void> _updateCurrentLocation() async {
    final result = await _locationUseCase.getCurrentLocationName();
    if (result.isSuccess) {
      _currentLocation = result.data!;
    }
  }

  // 搜索店铺
  Future<void> searchStores(String keyword, {SearchCriteria? criteria}) async {
    if (keyword.trim().isEmpty) {
      _searchResults = [];
      notifyListeners();
      return;
    }

    _setLoading(true);
    
    final result = await _storeUseCase.searchStores(
      keyword: keyword,
      criteria: criteria,
    );
    
    if (result.isSuccess) {
      _searchResults = result.data!;
      _clearError();
    } else {
      _setError(result.error!.message);
    }
    
    _setLoading(false);
  }

  // 切换收藏状态
  Future<void> toggleFavorite(BeadStore store) async {
    final result = await _storeUseCase.toggleFavorite(store.id);
    
    if (result.isSuccess) {
      store.isFavorite = result.data!;
      
      // 更新各个列表中的状态
      _updateStoreInLists(store);
      
      // 更新收藏列表
      if (store.isFavorite) {
        if (!_favoriteStores.any((s) => s.id == store.id)) {
          _favoriteStores.add(store);
        }
      } else {
        _favoriteStores.removeWhere((s) => s.id == store.id);
      }
      
      notifyListeners();
    } else {
      _setError('操作失败: ${result.error!.message}');
    }
  }

  // 选择店铺
  void selectStore(BeadStore store) {
    _selectedStore = store;
    notifyListeners();
  }

  // 刷新数据
  Future<void> refresh() async {
    await initialize();
  }

  // 应用快速筛选
  void applyQuickFilter(String filterType) {
    switch (filterType) {
      case '附近':
        _searchResults = _nearbyStores.where((s) => s.distance <= 2.0).toList();
        break;
      case 'Hama':
        _searchResults = _nearbyStores.where((s) => 
            s.brands.any((b) => b.toLowerCase().contains('hama'))).toList();
        break;
      case 'Perler':
        _searchResults = _nearbyStores.where((s) => 
            s.brands.any((b) => b.toLowerCase().contains('perler'))).toList();
        break;
      case '高评分':
        _searchResults = _nearbyStores.where((s) => s.rating.overall >= 4.5).toList();
        break;
    }
    notifyListeners();
  }

  // 私有方法
  void _setLoading(bool loading) {
    _isLoading = loading;
    notifyListeners();
  }

  void _setError(String error) {
    _errorMessage = error;
    notifyListeners();
  }

  void _clearError() {
    _errorMessage = null;
    notifyListeners();
  }

  void _updateStoreInLists(BeadStore updatedStore) {
    // 更新附近店铺列表
    final nearbyIndex = _nearbyStores.indexWhere((s) => s.id == updatedStore.id);
    if (nearbyIndex != -1) {
      _nearbyStores[nearbyIndex] = updatedStore;
    }
    
    // 更新推荐店铺列表
    final recommendedIndex = _recommendedStores.indexWhere((s) => s.id == updatedStore.id);
    if (recommendedIndex != -1) {
      _recommendedStores[recommendedIndex] = updatedStore;
    }
    
    // 更新搜索结果列表
    final searchIndex = _searchResults.indexWhere((s) => s.id == updatedStore.id);
    if (searchIndex != -1) {
      _searchResults[searchIndex] = updatedStore;
    }
  }
}

第四步:用户界面实现

4.1 主应用入口
// lib/main.dart
void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  
  // 初始化Hive数据库
  await Hive.initFlutter();
  
  // 注册Hive适配器
  Hive.registerAdapter(BeadStoreAdapter());
  Hive.registerAdapter(BeadProductAdapter());
  Hive.registerAdapter(ReviewAdapter());
  
  // 初始化依赖注入
  await setupDependencies();
  
  runApp(const BeadStoreFinderApp());
}

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

  
  Widget build(BuildContext context) {
    return MultiProvider(
      providers: [
        ChangeNotifierProvider(
          create: (_) => GetIt.instance<StoreProvider>()..initialize(),
        ),
        ChangeNotifierProvider(
          create: (_) => GetIt.instance<LocationProvider>(),
        ),
        ChangeNotifierProvider(
          create: (_) => GetIt.instance<UserProvider>(),
        ),
        ChangeNotifierProvider(
          create: (_) => GetIt.instance<ThemeProvider>(),
        ),
      ],
      child: Consumer<ThemeProvider>(
        builder: (context, themeProvider, child) {
          return MaterialApp(
            title: 'Flutter全国拼豆实体店查询',
            theme: AppThemes.lightTheme,
            darkTheme: AppThemes.darkTheme,
            themeMode: themeProvider.themeMode,
            home: const SplashScreen(),
            onGenerateRoute: AppRoutes.generateRoute,
            debugShowCheckedModeBanner: false,
            // 国际化配置
            localizationsDelegates: const [
              GlobalMaterialLocalizations.delegate,
              GlobalWidgetsLocalizations.delegate,
              GlobalCupertinoLocalizations.delegate,
            ],
            supportedLocales: const [
              Locale('zh', 'CN'),
              Locale('zh', 'TW'),
              Locale('en', 'US'),
            ],
          );
        },
      ),
    );
  }
}
4.2 启动页面实现
// lib/presentation/pages/splash_screen.dart
class SplashScreen extends StatefulWidget {
  const SplashScreen({super.key});

  
  State<SplashScreen> createState() => _SplashScreenState();
}

class _SplashScreenState extends State<SplashScreen>
    with TickerProviderStateMixin {
  late AnimationController _logoController;
  late AnimationController _textController;
  late Animation<double> _logoAnimation;
  late Animation<double> _textAnimation;
  late Animation<Offset> _slideAnimation;

  
  void initState() {
    super.initState();
    _initAnimations();
    _startSplashSequence();
  }

  void _initAnimations() {
    _logoController = AnimationController(
      duration: const Duration(milliseconds: 1500),
      vsync: this,
    );
    
    _textController = AnimationController(
      duration: const Duration(milliseconds: 1000),
      vsync: this,
    );

    _logoAnimation = Tween<double>(
      begin: 0.0,
      end: 1.0,
    ).animate(CurvedAnimation(
      parent: _logoController,
      curve: Curves.elasticOut,
    ));

    _textAnimation = Tween<double>(
      begin: 0.0,
      end: 1.0,
    ).animate(CurvedAnimation(
      parent: _textController,
      curve: Curves.easeInOut,
    ));

    _slideAnimation = Tween<Offset>(
      begin: const Offset(0, 0.5),
      end: Offset.zero,
    ).animate(CurvedAnimation(
      parent: _textController,
      curve: Curves.easeOutCubic,
    ));
  }

  Future<void> _startSplashSequence() async {
    // 启动Logo动画
    _logoController.forward();
    
    // 延迟启动文字动画
    await Future.delayed(const Duration(milliseconds: 500));
    _textController.forward();
    
    // 预加载数据
    await _preloadData();
    
    // 等待动画完成
    await Future.delayed(const Duration(milliseconds: 2000));
    
    // 检查是否首次启动
    final isFirstLaunch = await _checkFirstLaunch();
    
    if (mounted) {
      if (isFirstLaunch) {
        Navigator.of(context).pushReplacement(
          PageRouteBuilder(
            pageBuilder: (context, animation, secondaryAnimation) =>
                const OnboardingScreen(),
            transitionsBuilder: (context, animation, secondaryAnimation, child) {
              return FadeTransition(opacity: animation, child: child);
            },
            transitionDuration: const Duration(milliseconds: 500),
          ),
        );
      } else {
        Navigator.of(context).pushReplacement(
          PageRouteBuilder(
            pageBuilder: (context, animation, secondaryAnimation) =>
                const MainNavigationScreen(),
            transitionsBuilder: (context, animation, secondaryAnimation, child) {
              return FadeTransition(opacity: animation, child: child);
            },
            transitionDuration: const Duration(milliseconds: 500),
          ),
        );
      }
    }
  }

  Future<void> _preloadData() async {
    try {
      // 预加载关键数据
      final storeProvider = context.read<StoreProvider>();
      await storeProvider.initialize();
      
      // 预加载用户数据
      final userProvider = context.read<UserProvider>();
      await userProvider.initialize();
      
    } catch (e) {
      // 记录错误但不阻塞启动流程
      debugPrint('预加载数据失败: $e');
    }
  }

  Future<bool> _checkFirstLaunch() async {
    final prefs = await SharedPreferences.getInstance();
    final isFirstLaunch = prefs.getBool('is_first_launch') ?? true;
    
    if (isFirstLaunch) {
      await prefs.setBool('is_first_launch', false);
    }
    
    return isFirstLaunch;
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Theme.of(context).colorScheme.primary,
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            // Logo动画
            AnimatedBuilder(
              animation: _logoAnimation,
              builder: (context, child) {
                return Transform.scale(
                  scale: _logoAnimation.value,
                  child: Container(
                    width: 120,
                    height: 120,
                    decoration: BoxDecoration(
                      color: Colors.white,
                      borderRadius: BorderRadius.circular(24),
                      boxShadow: [
                        BoxShadow(
                          color: Colors.black.withOpacity(0.1),
                          blurRadius: 20,
                          offset: const Offset(0, 10),
                        ),
                      ],
                    ),
                    child: const Icon(
                      Icons.store,
                      size: 60,
                      color: Colors.pink,
                    ),
                  ),
                );
              },
            ),
            
            const SizedBox(height: 32),
            
            // 文字动画
            SlideTransition(
              position: _slideAnimation,
              child: FadeTransition(
                opacity: _textAnimation,
                child: Column(
                  children: [
                    Text(
                      '拼豆店铺查询',
                      style: Theme.of(context).textTheme.headlineMedium?.copyWith(
                        color: Colors.white,
                        fontWeight: FontWeight.bold,
                      ),
                    ),
                    const SizedBox(height: 8),
                    Text(
                      '发现身边的拼豆世界',
                      style: Theme.of(context).textTheme.bodyLarge?.copyWith(
                        color: Colors.white.withOpacity(0.8),
                      ),
                    ),
                  ],
                ),
              ),
            ),
            
            const SizedBox(height: 64),
            
            // 加载指示器
            FadeTransition(
              opacity: _textAnimation,
              child: const SizedBox(
                width: 24,
                height: 24,
                child: CircularProgressIndicator(
                  strokeWidth: 2,
                  valueColor: AlwaysStoppedAnimation<Color>(Colors.white),
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }

  
  void dispose() {
    _logoController.dispose();
    _textController.dispose();
    super.dispose();
  }
}
BeadStore(
  id: '2',
  name: '创意拼豆屋',
  address: '上海市徐汇区淮海中路1045号',
  latitude: 31.2165,
  longitude: 121.4365,
  city: '上海',
  district: '徐汇区',
  phone: '021-64738291',
  businessHours: '09:30-21:30',
  brands: ['Perler', 'Artkal', '国产'],
  products: [
    BeadProduct(
      id: '3',
      name: 'Perler拼豆套装',
      brand: 'Perler',
      category: '套装',
      color: '多色',
      price: 128.0,
      description: '美国Perler拼豆入门套装,含拼豆板和熨斗纸',
      imageUrl: 'assets/images/perler_kit.jpg',
      inStock: true,
      quantity: 25,
      size: '套装',
      tags: ['套装', '入门', '美国进口'],
    ),
  ],
  rating: 4.6,
  reviewCount: 89,
  reviews: [
    Review(
      id: '2',
      userId: 'user2',
      userName: '手工爱好者',
      rating: 4.5,
      comment: '价格合理,品种丰富,服务态度好',
      reviewDate: DateTime.now().subtract(const Duration(days: 7)),
      images: [],
      isRecommended: true,
    ),
  ],
  isFavorite: false,
  distance: 1.2,
  description: '创意手工拼豆专门店,提供DIY教学服务',
  images: ['assets/images/store2.jpg'],
  ownerName: '李先生',
  establishedDate: DateTime(2019, 8, 20),
  services: ['DIY教学', '团体活动', '生日派对'],
),
BeadStore(
  id: '3',
  name: '拼豆乐园',
  address: '广州市天河区天河城百货3楼',
  latitude: 23.1291,
  longitude: 113.3240,
  city: '广州',
  district: '天河区',
  phone: '020-38732156',
  businessHours: '10:00-22:00',
  brands: ['美乐', 'Artkal', 'Hama'],
  products: [
    BeadProduct(
      id: '4',
      name: '美乐拼豆2.6mm精装',
      brand: '美乐',
      category: '拼豆',
      color: '单色',
      price: 25.0,
      description: '国产美乐拼豆,2.6mm小规格,颜色鲜艳',
      imageUrl: 'assets/images/meile_beads.jpg',
      inStock: true,
      quantity: 80,
      size: '2.6mm',
      tags: ['国产', '小规格', '鲜艳'],
    ),
  ],
  rating: 4.4,
  reviewCount: 67,
  reviews: [],
  isFavorite: true,
  distance: 2.5,
  description: '儿童拼豆乐园,提供亲子DIY体验',
  images: ['assets/images/store3.jpg'],
  ownerName: '王女士',
  establishedDate: DateTime(2021, 1, 10),
  services: ['亲子体验', '儿童教学', '作品展示'],
),
BeadStore(
  id: '4',
  name: '艺术拼豆工作室',
  address: '深圳市南山区海岸城购物中心2楼',
  latitude: 22.5431,
  longitude: 113.9344,
  city: '深圳',
  district: '南山区',
  phone: '0755-26891234',
  businessHours: '10:30-21:00',
  brands: ['Artkal', 'Hama', 'Perler'],
  products: [
    BeadProduct(
      id: '5',
      name: 'Artkal拼豆专业套装',
      brand: 'Artkal',
      category: '套装',
      color: '全色系',
      price: 298.0,
      description: 'Artkal专业级拼豆套装,包含48色拼豆',
      imageUrl: 'assets/images/artkal_pro.jpg',
      inStock: true,
      quantity: 15,
      size: '专业装',
      tags: ['专业', '全色系', '高品质'],
    ),
  ],
  rating: 4.9,
  reviewCount: 203,
  reviews: [],
  isFavorite: false,
  distance: 3.8,
  description: '专业拼豆艺术工作室,提供高端定制服务',
  images: ['assets/images/store4.jpg'],
  ownerName: '陈老师',
  establishedDate: DateTime(2018, 6, 5),
  services: ['艺术指导', '作品定制', '展览服务'],
),
BeadStore(
  id: '5',
  name: '童趣拼豆小屋',
  address: '杭州市西湖区文三路259号',
  latitude: 30.2741,
  longitude: 120.1551,
  city: '杭州',
  district: '西湖区',
  phone: '0571-88765432',
  businessHours: '09:00-20:30',
  brands: ['美乐', '国产品牌'],
  products: [
    BeadProduct(
      id: '6',
      name: '儿童拼豆入门套装',
      brand: '美乐',
      category: '套装',
      color: '彩色',
      price: 58.0,
      description: '专为儿童设计的拼豆入门套装,安全无毒',
      imageUrl: 'assets/images/kids_kit.jpg',
      inStock: true,
      quantity: 40,
      size: '儿童装',
      tags: ['儿童', '安全', '入门'],
    ),
  ],
  rating: 4.3,
  reviewCount: 45,
  reviews: [],
  isFavorite: true,
  distance: 5.2,
  description: '专注儿童拼豆教育,提供安全环保产品',
  images: ['assets/images/store5.jpg'],
  ownerName: '刘阿姨',
  establishedDate: DateTime(2022, 4, 18),
  services: ['儿童教学', '安全指导', '家长陪伴'],
),

];
}


#### 4.3 首页核心实现

```dart
// lib/presentation/pages/home_page.dart
class HomePage extends StatefulWidget {
  const HomePage({super.key});

  @override
  State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage>
    with AutomaticKeepAliveClientMixin, TickerProviderStateMixin {
  
  late AnimationController _refreshController;
  late Animation<double> _refreshAnimation;
  final TextEditingController _searchController = TextEditingController();
  final ScrollController _scrollController = ScrollController();
  
  @override
  bool get wantKeepAlive => true;

  @override
  void initState() {
    super.initState();
    _initAnimations();
    _setupScrollListener();
  }

  void _initAnimations() {
    _refreshController = AnimationController(
      duration: const Duration(milliseconds: 1000),
      vsync: this,
    );
    
    _refreshAnimation = Tween<double>(
      begin: 0.0,
      end: 1.0,
    ).animate(CurvedAnimation(
      parent: _refreshController,
      curve: Curves.easeInOut,
    ));
  }

  void _setupScrollListener() {
    _scrollController.addListener(() {
      // 实现滚动到顶部时的下拉刷新效果
      if (_scrollController.position.pixels <= -100) {
        _triggerRefresh();
      }
    });
  }

  Future<void> _triggerRefresh() async {
    if (_refreshController.isAnimating) return;
    
    _refreshController.forward();
    
    try {
      await context.read<StoreProvider>().refresh();
      
      // 显示刷新成功提示
      if (mounted) {
        ScaffoldMessenger.of(context).showSnackBar(
          SnackBar(
            content: const Row(
              children: [
                Icon(Icons.check_circle, color: Colors.white),
                SizedBox(width: 8),
                Text('数据已更新'),
              ],
            ),
            backgroundColor: Colors.green,
            duration: const Duration(seconds: 2),
            behavior: SnackBarBehavior.floating,
            shape: RoundedRectangleBorder(
              borderRadius: BorderRadius.circular(10),
            ),
          ),
        );
      }
    } catch (e) {
      if (mounted) {
        ScaffoldMessenger.of(context).showSnackBar(
          SnackBar(
            content: Text('刷新失败: ${e.toString()}'),
            backgroundColor: Colors.red,
          ),
        );
      }
    } finally {
      _refreshController.reverse();
    }
  }

  @override
  Widget build(BuildContext context) {
    super.build(context);
    
    return Scaffold(
      body: RefreshIndicator(
        onRefresh: () => context.read<StoreProvider>().refresh(),
        child: CustomScrollView(
          controller: _scrollController,
          physics: const BouncingScrollPhysics(),
          slivers: [
            // 自定义AppBar
            _buildSliverAppBar(),
            
            // 搜索栏
            SliverToBoxAdapter(
              child: _buildSearchSection(),
            ),
            
            // 快速筛选
            SliverToBoxAdapter(
              child: _buildQuickFilters(),
            ),
            
            // 附近店铺
            _buildNearbyStoresSection(),
            
            // 推荐店铺
            _buildRecommendedStoresSection(),
            
            // 热门品牌
            SliverToBoxAdapter(
              child: _buildBrandCategories(),
            ),
            
            // 底部间距
            const SliverToBoxAdapter(
              child: SizedBox(height: 100),
            ),
          ],
        ),
      ),
    );
  }

  Widget _buildSliverAppBar() {
    return Consumer<StoreProvider>(
      builder: (context, storeProvider, child) {
        return SliverAppBar(
          expandedHeight: 120,
          floating: true,
          pinned: true,
          elevation: 0,
          backgroundColor: Theme.of(context).colorScheme.primary,
          flexibleSpace: FlexibleSpaceBar(
            background: Container(
              decoration: BoxDecoration(
                gradient: LinearGradient(
                  begin: Alignment.topLeft,
                  end: Alignment.bottomRight,
                  colors: [
                    Theme.of(context).colorScheme.primary,
                    Theme.of(context).colorScheme.primary.withOpacity(0.8),
                  ],
                ),
              ),
              child: SafeArea(
                child: Padding(
                  padding: const EdgeInsets.symmetric(horizontal: 16),
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    mainAxisAlignment: MainAxisAlignment.end,
                    children: [
                      Row(
                        children: [
                          const Icon(
                            Icons.location_on,
                            color: Colors.white,
                            size: 20,
                          ),
                          const SizedBox(width: 8),
                          Expanded(
                            child: Text(
                              storeProvider.currentLocation,
                              style: const TextStyle(
                                color: Colors.white,
                                fontSize: 16,
                                fontWeight: FontWeight.w500,
                              ),
                            ),
                          ),
                          AnimatedBuilder(
                            animation: _refreshAnimation,
                            builder: (context, child) {
                              return Transform.rotate(
                                angle: _refreshAnimation.value * 2 * pi,
                                child: IconButton(
                                  icon: const Icon(
                                    Icons.refresh,
                                    color: Colors.white,
                                  ),
                                  onPressed: _triggerRefresh,
                                ),
                              );
                            },
                          ),
                        ],
                      ),
                      const SizedBox(height: 16),
                    ],
                  ),
                ),
              ),
            ),
          ),
        );
      },
    );
  }

  Widget _buildSearchSection() {
    return Container(
      padding: const EdgeInsets.all(16),
      child: Hero(
        tag: 'search_bar',
        child: Material(
          color: Colors.transparent,
          child: Container(
            decoration: BoxDecoration(
              color: Colors.white,
              borderRadius: BorderRadius.circular(25),
              boxShadow: [
                BoxShadow(
                  color: Colors.black.withOpacity(0.1),
                  blurRadius: 10,
                  offset: const Offset(0, 2),
                ),
              ],
            ),
            child: TextField(
              controller: _searchController,
              decoration: InputDecoration(
                hintText: '搜索拼豆店铺或商品...',
                hintStyle: TextStyle(
                  color: Colors.grey.shade500,
                  fontSize: 16,
                ),
                prefixIcon: Icon(
                  Icons.search,
                  color: Colors.grey.shade600,
                ),
                suffixIcon: _searchController.text.isNotEmpty
                    ? IconButton(
                        icon: Icon(
                          Icons.clear,
                          color: Colors.grey.shade600,
                        ),
                        onPressed: () {
                          _searchController.clear();
                          context.read<StoreProvider>().searchStores('');
                        },
                      )
                    : IconButton(
                        icon: Icon(
                          Icons.tune,
                          color: Colors.grey.shade600,
                        ),
                        onPressed: _showAdvancedSearch,
                      ),
                border: InputBorder.none,
                contentPadding: const EdgeInsets.symmetric(
                  horizontal: 20,
                  vertical: 16,
                ),
              ),
              onSubmitted: (query) {
                context.read<StoreProvider>().searchStores(query);
                Navigator.of(context).pushNamed(
                  '/search_results',
                  arguments: query,
                );
              },
              onChanged: (query) {
                setState(() {});
              },
            ),
          ),
        ),
      ),
    );
  }

  Widget _buildQuickFilters() {
    final filters = [
      QuickFilter(
        name: '附近',
        icon: Icons.near_me,
        color: Colors.pink,
        description: '2公里内',
      ),
      QuickFilter(
        name: 'Hama',
        icon: Icons.star,
        color: Colors.blue,
        description: '丹麦品牌',
      ),
      QuickFilter(
        name: 'Perler',
        icon: Icons.favorite,
        color: Colors.red,
        description: '美国品牌',
      ),
      QuickFilter(
        name: '高评分',
        icon: Icons.thumb_up,
        color: Colors.green,
        description: '4.5分以上',
      ),
    ];

    return Container(
      height: 100,
      padding: const EdgeInsets.symmetric(vertical: 16),
      child: ListView.builder(
        scrollDirection: Axis.horizontal,
        padding: const EdgeInsets.symmetric(horizontal: 16),
        itemCount: filters.length,
        itemBuilder: (context, index) {
          final filter = filters[index];
          return Container(
            width: 90,
            margin: const EdgeInsets.only(right: 12),
            child: InkWell(
              onTap: () => _applyQuickFilter(filter.name),
              borderRadius: BorderRadius.circular(16),
              child: Container(
                decoration: BoxDecoration(
                  color: filter.color.withOpacity(0.1),
                  borderRadius: BorderRadius.circular(16),
                  border: Border.all(
                    color: filter.color.withOpacity(0.3),
                    width: 1,
                  ),
                ),
                child: Column(
                  mainAxisAlignment: MainAxisAlignment.center,
                  children: [
                    Container(
                      width: 40,
                      height: 40,
                      decoration: BoxDecoration(
                        color: filter.color.withOpacity(0.2),
                        borderRadius: BorderRadius.circular(20),
                      ),
                      child: Icon(
                        filter.icon,
                        color: filter.color,
                        size: 20,
                      ),
                    ),
                    const SizedBox(height: 8),
                    Text(
                      filter.name,
                      style: TextStyle(
                        fontSize: 12,
                        fontWeight: FontWeight.w600,
                        color: filter.color,
                      ),
                    ),
                    Text(
                      filter.description,
                      style: TextStyle(
                        fontSize: 10,
                        color: Colors.grey.shade600,
                      ),
                    ),
                  ],
                ),
              ),
            ),
          );
        },
      ),
    );
  }

  Widget _buildNearbyStoresSection() {
    return Consumer<StoreProvider>(
      builder: (context, storeProvider, child) {
        if (storeProvider.isLoading) {
          return const SliverToBoxAdapter(
            child: Center(
              child: Padding(
                padding: EdgeInsets.all(32),
                child: CircularProgressIndicator(),
              ),
            ),
          );
        }

        if (storeProvider.nearbyStores.isEmpty) {
          return SliverToBoxAdapter(
            child: _buildEmptyState(
              icon: Icons.location_off,
              title: '附近暂无店铺',
              subtitle: '尝试扩大搜索范围或刷新数据',
            ),
          );
        }

        return SliverToBoxAdapter(
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              _buildSectionHeader(
                title: '附近店铺',
                subtitle: '${storeProvider.nearbyStores.length}家店铺',
                onMoreTap: () => Navigator.of(context).pushNamed('/nearby_stores'),
              ),
              SizedBox(
                height: 280,
                child: ListView.builder(
                  scrollDirection: Axis.horizontal,
                  padding: const EdgeInsets.symmetric(horizontal: 16),
                  itemCount: storeProvider.nearbyStores.length,
                  itemBuilder: (context, index) {
                    final store = storeProvider.nearbyStores[index];
                    return Container(
                      width: 300,
                      margin: const EdgeInsets.only(right: 12),
                      child: StoreCard(
                        store: store,
                        onTap: () => _navigateToStoreDetail(store),
                        onFavoriteTap: () => storeProvider.toggleFavorite(store),
                      ),
                    );
                  },
                ),
              ),
            ],
          ),
        );
      },
    );
  }

  Widget _buildRecommendedStoresSection() {
    return Consumer<StoreProvider>(
      builder: (context, storeProvider, child) {
        if (storeProvider.recommendedStores.isEmpty) {
          return const SliverToBoxAdapter(child: SizedBox.shrink());
        }

        return SliverList(
          delegate: SliverChildBuilderDelegate(
            (context, index) {
              if (index == 0) {
                return _buildSectionHeader(
                  title: '推荐店铺',
                  subtitle: '基于您的偏好推荐',
                  onMoreTap: () => Navigator.of(context).pushNamed('/recommended_stores'),
                );
              }
              
              final storeIndex = index - 1;
              if (storeIndex >= storeProvider.recommendedStores.length) {
                return null;
              }
              
              final store = storeProvider.recommendedStores[storeIndex];
              return Padding(
                padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
                child: StoreListTile(
                  store: store,
                  onTap: () => _navigateToStoreDetail(store),
                  onFavoriteTap: () => storeProvider.toggleFavorite(store),
                ),
              );
            },
            childCount: storeProvider.recommendedStores.length + 1,
          ),
        );
      },
    );
  }

  Widget _buildSectionHeader({
    required String title,
    required String subtitle,
    VoidCallback? onMoreTap,
  }) {
    return Padding(
      padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
      child: Row(
        children: [
          Expanded(
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Text(
                  title,
                  style: const TextStyle(
                    fontSize: 20,
                    fontWeight: FontWeight.bold,
                  ),
                ),
                Text(
                  subtitle,
                  style: TextStyle(
                    fontSize: 14,
                    color: Colors.grey.shade600,
                  ),
                ),
              ],
            ),
          ),
          if (onMoreTap != null)
            TextButton(
              onPressed: onMoreTap,
              child: const Text('查看更多'),
            ),
        ],
      ),
    );
  }

  Widget _buildEmptyState({
    required IconData icon,
    required String title,
    required String subtitle,
  }) {
    return Center(
      child: Padding(
        padding: const EdgeInsets.all(32),
        child: Column(
          children: [
            Icon(
              icon,
              size: 80,
              color: Colors.grey.shade400,
            ),
            const SizedBox(height: 16),
            Text(
              title,
              style: TextStyle(
                fontSize: 18,
                fontWeight: FontWeight.w600,
                color: Colors.grey.shade600,
              ),
            ),
            const SizedBox(height: 8),
            Text(
              subtitle,
              style: TextStyle(
                fontSize: 14,
                color: Colors.grey.shade500,
              ),
              textAlign: TextAlign.center,
            ),
          ],
        ),
      ),
    );
  }

  void _applyQuickFilter(String filterName) {
    context.read<StoreProvider>().applyQuickFilter(filterName);
    Navigator.of(context).pushNamed(
      '/search_results',
      arguments: filterName,
    );
  }

  void _navigateToStoreDetail(BeadStore store) {
    Navigator.of(context).pushNamed(
      '/store_detail',
      arguments: store,
    );
  }

  void _showAdvancedSearch() {
    showModalBottomSheet(
      context: context,
      isScrollControlled: true,
      backgroundColor: Colors.transparent,
      builder: (context) => const AdvancedSearchSheet(),
    );
  }

  @override
  void dispose() {
    _refreshController.dispose();
    _searchController.dispose();
    _scrollController.dispose();
    super.dispose();
  }
}

// 快速筛选数据模型
class QuickFilter {
  final String name;
  final IconData icon;
  final Color color;
  final String description;

  QuickFilter({
    required this.name,
    required this.icon,
    required this.color,
    required this.description,
  });
}

第五步:店铺卡片设计

Widget _buildStoreCard(BeadStore store) {
  return Card(
    margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
    elevation: 4,
    child: InkWell(
      onTap: () => _showStoreDetails(store),
      child: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            // 店铺头部信息
            Row(
              children: [
                // 店铺图标
                Container(
                  width: 60,
                  height: 60,
                  decoration: BoxDecoration(
                    gradient: LinearGradient(
                      colors: [
                        Colors.pink.shade300,
                        Colors.pink.shade500,
                      ],
                    ),
                    borderRadius: BorderRadius.circular(12),
                  ),
                  child: const Center(
                    child: Icon(
                      Icons.store,
                      size: 30,
                      color: Colors.white,
                    ),
                  ),
                ),
                
                const SizedBox(width: 16),
                
                // 店铺信息
                Expanded(
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      Row(
                        children: [
                          Expanded(
                            child: Text(
                              store.name,
                              style: const TextStyle(
                                fontSize: 16,
                                fontWeight: FontWeight.bold,
                              ),
                            ),
                          ),
                          GestureDetector(
                            onTap: () => _toggleFavorite(store),
                            child: Icon(
                              store.isFavorite ? Icons.favorite : Icons.favorite_border,
                              color: store.isFavorite ? Colors.red : Colors.grey,
                              size: 20,
                            ),
                          ),
                        ],
                      ),
                      
                      const SizedBox(height: 4),
                      
                      Row(
                        children: [
                          Icon(
                            Icons.location_on,
                            size: 14,
                            color: Colors.grey.shade600,
                          ),
                          const SizedBox(width: 4),
                          Text(
                            '${store.distance.toStringAsFixed(1)}km',
                            style: TextStyle(
                              color: Colors.grey.shade600,
                              fontSize: 12,
                            ),
                          ),
                          const SizedBox(width: 12),
                          Icon(
                            Icons.star,
                            size: 14,
                            color: Colors.orange,
                          ),
                          const SizedBox(width: 2),
                          Text(
                            store.rating.toStringAsFixed(1),
                            style: TextStyle(
                              color: Colors.grey.shade600,
                              fontSize: 12,
                            ),
                          ),
                        ],
                      ),
                      
                      const SizedBox(height: 4),
                      
                      Text(
                        '${store.city} ${store.district}',
                        style: TextStyle(
                          color: Colors.grey.shade600,
                          fontSize: 12,
                        ),
                      ),
                    ],
                  ),
                ),
              ],
            ),
            
            const SizedBox(height: 12),
            
            // 经营品牌
            if (store.brands.isNotEmpty) ...[
              Text(
                '经营品牌',
                style: TextStyle(
                  fontSize: 14,
                  fontWeight: FontWeight.bold,
                  color: Colors.grey.shade700,
                ),
              ),
              const SizedBox(height: 8),
              Wrap(
                spacing: 8,
                children: store.brands.take(3).map((brand) {
                  return Container(
                    padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
                    decoration: BoxDecoration(
                      color: _getBrandColor(brand).withOpacity(0.1),
                      borderRadius: BorderRadius.circular(12),
                      border: Border.all(color: _getBrandColor(brand).withOpacity(0.3)),
                    ),
                    child: Text(
                      brand,
                      style: TextStyle(
                        fontSize: 12,
                        color: _getBrandColor(brand),
                        fontWeight: FontWeight.bold,
                      ),
                    ),
                  );
                }).toList(),
              ),
            ],
            
            const SizedBox(height: 8),
            
            // 底部信息
            Row(
              children: [
                Icon(
                  Icons.access_time,
                  size: 14,
                  color: Colors.grey.shade600,
                ),
                const SizedBox(width: 4),
                Text(
                  store.businessHours,
                  style: TextStyle(
                    color: Colors.grey.shade600,
                    fontSize: 12,
                  ),
                ),
                const Spacer(),
                Text(
                  '${store.reviewCount}条评价',
                  style: TextStyle(
                    color: Colors.grey.shade600,
                    fontSize: 12,
                  ),
                ),
              ],
            ),
          ],
        ),
      ),
    ),
  );
}

核心功能深度解析

1. 智能搜索与推荐算法

1.1 多维度搜索实现
// lib/domain/usecases/search_usecase.dart
class SearchUseCase {
  final StoreRepository _storeRepository;
  final UserRepository _userRepository;
  final AnalyticsService _analyticsService;

  SearchUseCase(
    this._storeRepository,
    this._userRepository,
    this._analyticsService,
  );

  Future<Result<SearchResult>> performSearch({
    required String keyword,
    SearchCriteria? criteria,
    int page = 1,
    int pageSize = 20,
  }) async {
    try {
      // 记录搜索行为
      await _analyticsService.trackSearch(keyword);
      
      // 构建搜索参数
      final searchParams = SearchParams(
        keyword: keyword.trim(),
        criteria: criteria ?? SearchCriteria.defaultCriteria(),
        page: page,
        pageSize: pageSize,
      );
      
      // 执行多维度搜索
      final results = await Future.wait([
        _searchByName(searchParams),
        _searchByBrand(searchParams),
        _searchByProduct(searchParams),
        _searchByLocation(searchParams),
      ]);
      
      // 合并和去重结果
      final allStores = <BeadStore>[];
      final storeIds = <String>{};
      
      for (final result in results) {
        if (result.isSuccess) {
          for (final store in result.data!) {
            if (!storeIds.contains(store.id)) {
              storeIds.add(store.id);
              allStores.add(store);
            }
          }
        }
      }
      
      // 应用筛选条件
      final filteredStores = _applyFilters(allStores, criteria);
      
      // 计算相关性评分
      final scoredStores = _calculateRelevanceScores(filteredStores, keyword);
      
      // 排序结果
      final sortedStores = _sortResults(scoredStores, criteria?.sortBy);
      
      // 分页处理
      final paginatedStores = _paginateResults(sortedStores, page, pageSize);
      
      // 更新搜索历史
      await _userRepository.addSearchHistory(keyword);
      
      return Result.success(SearchResult(
        stores: paginatedStores,
        totalCount: sortedStores.length,
        currentPage: page,
        hasNextPage: (page * pageSize) < sortedStores.length,
        searchTime: DateTime.now(),
        suggestions: await _generateSearchSuggestions(keyword),
      ));
      
    } catch (e) {
      return Result.failure(AppError.search('搜索失败: ${e.toString()}'));
    }
  }

  // 按店铺名称搜索
  Future<Result<List<BeadStore>>> _searchByName(SearchParams params) async {
    return await _storeRepository.searchByName(params.keyword);
  }

  // 按品牌搜索
  Future<Result<List<BeadStore>>> _searchByBrand(SearchParams params) async {
    return await _storeRepository.searchByBrand(params.keyword);
  }

  // 按商品搜索
  Future<Result<List<BeadStore>>> _searchByProduct(SearchParams params) async {
    return await _storeRepository.searchByProduct(params.keyword);
  }

  // 按位置搜索
  Future<Result<List<BeadStore>>> _searchByLocation(SearchParams params) async {
    return await _storeRepository.searchByLocation(params.keyword);
  }

  // 计算相关性评分
  List<ScoredStore> _calculateRelevanceScores(
    List<BeadStore> stores,
    String keyword,
  ) {
    return stores.map((store) {
      double score = 0.0;
      final lowerKeyword = keyword.toLowerCase();
      
      // 店铺名称匹配(权重:40%)
      if (store.name.toLowerCase().contains(lowerKeyword)) {
        score += 0.4;
        // 完全匹配额外加分
        if (store.name.toLowerCase() == lowerKeyword) {
          score += 0.2;
        }
      }
      
      // 品牌匹配(权重:30%)
      for (final brand in store.brands) {
        if (brand.toLowerCase().contains(lowerKeyword)) {
          score += 0.3;
          break;
        }
      }
      
      // 商品匹配(权重:20%)
      for (final product in store.products) {
        if (product.name.toLowerCase().contains(lowerKeyword) ||
            product.tags.any((tag) => tag.toLowerCase().contains(lowerKeyword))) {
          score += 0.2;
          break;
        }
      }
      
      // 评分加权(权重:10%)
      score += (store.rating.overall / 5.0) * 0.1;
      
      return ScoredStore(store: store, relevanceScore: score);
    }).toList();
  }

  // 生成搜索建议
  Future<List<String>> _generateSearchSuggestions(String keyword) async {
    final suggestions = <String>[];
    
    // 基于历史搜索
    final history = await _userRepository.getSearchHistory();
    suggestions.addAll(
      history.where((h) => h.toLowerCase().contains(keyword.toLowerCase()))
          .take(3),
    );
    
    // 基于热门搜索
    final hotSearches = await _storeRepository.getHotSearchKeywords();
    suggestions.addAll(
      hotSearches.where((h) => h.toLowerCase().contains(keyword.toLowerCase()))
          .take(3),
    );
    
    // 基于品牌名称
    final brands = await _storeRepository.getAllBrands();
    suggestions.addAll(
      brands.where((b) => b.toLowerCase().contains(keyword.toLowerCase()))
          .take(2),
    );
    
    return suggestions.take(8).toList();
  }
}
1.2 个性化推荐系统
// lib/domain/usecases/recommendation_usecase.dart
class RecommendationUseCase {
  final StoreRepository _storeRepository;
  final UserRepository _userRepository;
  final MLService _mlService;

  RecommendationUseCase(
    this._storeRepository,
    this._userRepository,
    this._mlService,
  );

  Future<Result<List<BeadStore>>> getPersonalizedRecommendations({
    int limit = 10,
  }) async {
    try {
      // 获取用户画像
      final userProfile = await _buildUserProfile();
      
      // 获取候选店铺
      final candidateStores = await _getCandidateStores();
      
      // 计算推荐分数
      final recommendations = await _calculateRecommendationScores(
        candidateStores,
        userProfile,
      );
      
      // 排序并返回结果
      recommendations.sort((a, b) => b.score.compareTo(a.score));
      
      final recommendedStores = recommendations
          .take(limit)
          .map((r) => r.store)
          .toList();
      
      return Result.success(recommendedStores);
      
    } catch (e) {
      return Result.failure(AppError.recommendation('推荐失败: ${e.toString()}'));
    }
  }

  Future<UserProfile> _buildUserProfile() async {
    final userId = await _userRepository.getCurrentUserId();
    
    // 获取用户行为数据
    final browseHistory = await _userRepository.getBrowseHistory();
    final favoriteStores = await _userRepository.getFavoriteStores();
    final searchHistory = await _userRepository.getSearchHistory();
    
    // 分析用户偏好
    final brandPreferences = _analyzeBrandPreferences(browseHistory, favoriteStores);
    final categoryPreferences = _analyzeCategoryPreferences(browseHistory);
    final locationPreferences = _analyzeLocationPreferences(browseHistory);
    final pricePreferences = _analyzePricePreferences(browseHistory);
    
    return UserProfile(
      userId: userId,
      brandPreferences: brandPreferences,
      categoryPreferences: categoryPreferences,
      locationPreferences: locationPreferences,
      pricePreferences: pricePreferences,
      activityLevel: _calculateActivityLevel(browseHistory, searchHistory),
      lastActiveTime: DateTime.now(),
    );
  }

  Future<List<BeadStore>> _getCandidateStores() async {
    // 获取所有活跃店铺
    final result = await _storeRepository.getActiveStores();
    return result.isSuccess ? result.data! : [];
  }

  Future<List<RecommendationScore>> _calculateRecommendationScores(
    List<BeadStore> stores,
    UserProfile userProfile,
  ) async {
    final scores = <RecommendationScore>[];
    
    for (final store in stores) {
      double score = 0.0;
      
      // 品牌偏好匹配(权重:30%)
      score += _calculateBrandScore(store, userProfile.brandPreferences) * 0.3;
      
      // 分类偏好匹配(权重:25%)
      score += _calculateCategoryScore(store, userProfile.categoryPreferences) * 0.25;
      
      // 位置偏好匹配(权重:20%)
      score += _calculateLocationScore(store, userProfile.locationPreferences) * 0.2;
      
      // 价格偏好匹配(权重:15%)
      score += _calculatePriceScore(store, userProfile.pricePreferences) * 0.15;
      
      // 店铺质量评分(权重:10%)
      score += (store.rating.overall / 5.0) * 0.1;
      
      // 新店铺加权
      if (_isNewStore(store)) {
        score *= 1.1;
      }
      
      // 热门店铺加权
      if (_isPopularStore(store)) {
        score *= 1.05;
      }
      
      scores.add(RecommendationScore(store: store, score: score));
    }
    
    return scores;
  }

  Map<String, double> _analyzeBrandPreferences(
    List<BrowseRecord> browseHistory,
    List<BeadStore> favoriteStores,
  ) {
    final brandCounts = <String, int>{};
    
    // 分析浏览历史中的品牌偏好
    for (final record in browseHistory) {
      for (final brand in record.store.brands) {
        brandCounts[brand] = (brandCounts[brand] ?? 0) + 1;
      }
    }
    
    // 分析收藏店铺中的品牌偏好(权重更高)
    for (final store in favoriteStores) {
      for (final brand in store.brands) {
        brandCounts[brand] = (brandCounts[brand] ?? 0) + 3;
      }
    }
    
    // 转换为偏好分数
    final totalCount = brandCounts.values.fold(0, (sum, count) => sum + count);
    final preferences = <String, double>{};
    
    brandCounts.forEach((brand, count) {
      preferences[brand] = count / totalCount;
    });
    
    return preferences;
  }
}

2. 高性能地图与定位服务

2.1 地图组件优化
// lib/presentation/widgets/optimized_map_widget.dart
class OptimizedMapWidget extends StatefulWidget {
  final List<BeadStore> stores;
  final Function(BeadStore) onStoreSelected;
  final LatLng? initialLocation;

  const OptimizedMapWidget({
    super.key,
    required this.stores,
    required this.onStoreSelected,
    this.initialLocation,
  });

  
  State<OptimizedMapWidget> createState() => _OptimizedMapWidgetState();
}

class _OptimizedMapWidgetState extends State<OptimizedMapWidget>
    with TickerProviderStateMixin {
  
  late AMapController _mapController;
  late AnimationController _markerAnimationController;
  
  final Map<String, Marker> _markers = {};
  final Map<String, AnimationController> _markerAnimations = {};
  
  Set<BeadStore> _visibleStores = {};
  Timer? _debounceTimer;
  
  
  void initState() {
    super.initState();
    _initAnimations();
  }

  void _initAnimations() {
    _markerAnimationController = AnimationController(
      duration: const Duration(milliseconds: 300),
      vsync: this,
    );
  }

  
  Widget build(BuildContext context) {
    return AMapWidget(
      apiKey: const AMapApiKey(
        androidKey: 'your_android_key',
        iosKey: 'your_ios_key',
      ),
      initialCameraPosition: CameraPosition(
        target: widget.initialLocation ?? const LatLng(39.9042, 116.4074),
        zoom: 12,
      ),
      onMapCreated: _onMapCreated,
      onCameraMove: _onCameraMove,
      onCameraIdle: _onCameraIdle,
      markers: Set<Marker>.from(_markers.values),
      myLocationEnabled: true,
      myLocationButtonEnabled: true,
      buildingsEnabled: false, // 关闭3D建筑以提升性能
      trafficEnabled: false,   // 关闭交通图层以提升性能
    );
  }

  void _onMapCreated(AMapController controller) {
    _mapController = controller;
    _updateVisibleMarkers();
  }

  void _onCameraMove(CameraPosition position) {
    // 防抖处理,避免频繁更新
    _debounceTimer?.cancel();
    _debounceTimer = Timer(const Duration(milliseconds: 100), () {
      _updateVisibleStores(position);
    });
  }

  void _onCameraIdle() {
    _updateVisibleMarkers();
  }

  void _updateVisibleStores(CameraPosition position) {
    final bounds = _calculateVisibleBounds(position);
    final newVisibleStores = widget.stores.where((store) {
      return _isStoreInBounds(store, bounds);
    }).toSet();

    if (!setEquals(_visibleStores, newVisibleStores)) {
      setState(() {
        _visibleStores = newVisibleStores;
      });
      _updateVisibleMarkers();
    }
  }

  void _updateVisibleMarkers() {
    // 清除不可见的标记
    final markersToRemove = <String>[];
    _markers.forEach((storeId, marker) {
      if (!_visibleStores.any((store) => store.id == storeId)) {
        markersToRemove.add(storeId);
      }
    });

    for (final storeId in markersToRemove) {
      _markers.remove(storeId);
      _markerAnimations[storeId]?.dispose();
      _markerAnimations.remove(storeId);
    }

    // 添加新的可见标记
    for (final store in _visibleStores) {
      if (!_markers.containsKey(store.id)) {
        _addStoreMarker(store);
      }
    }
  }

  void _addStoreMarker(BeadStore store) {
    final animationController = AnimationController(
      duration: const Duration(milliseconds: 500),
      vsync: this,
    );
    
    final scaleAnimation = Tween<double>(
      begin: 0.0,
      end: 1.0,
    ).animate(CurvedAnimation(
      parent: animationController,
      curve: Curves.elasticOut,
    ));

    _markerAnimations[store.id] = animationController;

    final marker = Marker(
      markerId: MarkerId(store.id),
      position: LatLng(store.latitude, store.longitude),
      onTap: () => widget.onStoreSelected(store),
      icon: _createCustomMarkerIcon(store),
      infoWindow: InfoWindow(
        title: store.name,
        snippet: '${store.distance.toStringAsFixed(1)}km · ${store.rating.overall.toStringAsFixed(1)}⭐',
      ),
    );

    _markers[store.id] = marker;
    
    // 启动标记动画
    animationController.forward();
  }

  BitmapDescriptor _createCustomMarkerIcon(BeadStore store) {
    // 根据店铺类型和评分创建自定义标记图标
    final color = _getStoreMarkerColor(store);
    return BitmapDescriptor.defaultMarkerWithHue(
      _colorToHue(color),
    );
  }

  Color _getStoreMarkerColor(BeadStore store) {
    if (store.rating.overall >= 4.5) {
      return Colors.green;
    } else if (store.rating.overall >= 4.0) {
      return Colors.orange;
    } else {
      return Colors.red;
    }
  }

  double _colorToHue(Color color) {
    final hsl = HSLColor.fromColor(color);
    return hsl.hue;
  }

  LatLngBounds _calculateVisibleBounds(CameraPosition position) {
    // 计算当前可视区域的边界
    final center = position.target;
    final zoom = position.zoom;
    
    // 根据缩放级别计算可视范围
    final latRange = 180.0 / pow(2, zoom);
    final lngRange = 360.0 / pow(2, zoom);
    
    return LatLngBounds(
      southwest: LatLng(
        center.latitude - latRange / 2,
        center.longitude - lngRange / 2,
      ),
      northeast: LatLng(
        center.latitude + latRange / 2,
        center.longitude + lngRange / 2,
      ),
    );
  }

  bool _isStoreInBounds(BeadStore store, LatLngBounds bounds) {
    return store.latitude >= bounds.southwest.latitude &&
           store.latitude <= bounds.northeast.latitude &&
           store.longitude >= bounds.southwest.longitude &&
           store.longitude <= bounds.northeast.longitude;
  }

  
  void dispose() {
    _markerAnimationController.dispose();
    _markerAnimations.values.forEach((controller) => controller.dispose());
    _debounceTimer?.cancel();
    super.dispose();
  }
}

性能优化与最佳实践

1. 内存管理优化

1.1 图片加载优化
// lib/presentation/widgets/optimized_image_widget.dart
class OptimizedImageWidget extends StatefulWidget {
  final String imageUrl;
  final double? width;
  final double? height;
  final BoxFit fit;
  final Widget? placeholder;
  final Widget? errorWidget;

  const OptimizedImageWidget({
    super.key,
    required this.imageUrl,
    this.width,
    this.height,
    this.fit = BoxFit.cover,
    this.placeholder,
    this.errorWidget,
  });

  
  State<OptimizedImageWidget> createState() => _OptimizedImageWidgetState();
}

class _OptimizedImageWidgetState extends State<OptimizedImageWidget>
    with AutomaticKeepAliveClientMixin {
  
  
  bool get wantKeepAlive => true;

  
  Widget build(BuildContext context) {
    super.build(context);
    
    return CachedNetworkImage(
      imageUrl: widget.imageUrl,
      width: widget.width,
      height: widget.height,
      fit: widget.fit,
      placeholder: (context, url) => widget.placeholder ?? _buildPlaceholder(),
      errorWidget: (context, url, error) => widget.errorWidget ?? _buildErrorWidget(),
      memCacheWidth: _calculateOptimalWidth(),
      memCacheHeight: _calculateOptimalHeight(),
      maxWidthDiskCache: 800, // 限制磁盘缓存图片大小
      maxHeightDiskCache: 600,
      cacheManager: CustomCacheManager.instance,
    );
  }

  Widget _buildPlaceholder() {
    return Container(
      width: widget.width,
      height: widget.height,
      decoration: BoxDecoration(
        color: Colors.grey.shade200,
        borderRadius: BorderRadius.circular(8),
      ),
      child: const Center(
        child: CircularProgressIndicator(strokeWidth: 2),
      ),
    );
  }

  Widget _buildErrorWidget() {
    return Container(
      width: widget.width,
      height: widget.height,
      decoration: BoxDecoration(
        color: Colors.grey.shade100,
        borderRadius: BorderRadius.circular(8),
      ),
      child: Icon(
        Icons.broken_image,
        color: Colors.grey.shade400,
        size: 32,
      ),
    );
  }

  int? _calculateOptimalWidth() {
    if (widget.width == null) return null;
    final devicePixelRatio = MediaQuery.of(context).devicePixelRatio;
    return (widget.width! * devicePixelRatio).round();
  }

  int? _calculateOptimalHeight() {
    if (widget.height == null) return null;
    final devicePixelRatio = MediaQuery.of(context).devicePixelRatio;
    return (widget.height! * devicePixelRatio).round();
  }
}

2. 数据缓存策略

2.1 智能缓存管理
// lib/core/cache/intelligent_cache_manager.dart
class IntelligentCacheManager {
  static const Duration _defaultCacheDuration = Duration(hours: 6);
  final Box<CachedData> _cacheBox;
  final SharedPreferences _prefs;

  IntelligentCacheManager(this._cacheBox, this._prefs);

  // 智能缓存存储
  Future<void> smartCache<T>(
    String key,
    T data, {
    Duration? duration,
    CachePriority priority = CachePriority.normal,
  }) async {
    final cachedData = CachedData(
      data: data,
      timestamp: DateTime.now(),
      priority: priority,
      duration: duration ?? _defaultCacheDuration,
    );
    
    await _cacheBox.put(key, cachedData);
    
    // 根据优先级和存储空间管理缓存
    await _manageCacheStorage();
  }

  // 智能缓存获取
  Future<T?> smartGet<T>(String key) async {
    final cachedData = _cacheBox.get(key);
    
    if (cachedData == null) return null;
    
    // 检查缓存是否过期
    if (_isCacheExpired(cachedData)) {
      await _cacheBox.delete(key);
      return null;
    }
    
    // 更新访问时间(LRU策略)
    cachedData.lastAccessed = DateTime.now();
    await _cacheBox.put(key, cachedData);
    
    return cachedData.data as T?;
  }

  // 缓存存储管理
  Future<void> _manageCacheStorage() async {
    final cacheSize = _cacheBox.length;
    const maxCacheItems = 1000;
    
    if (cacheSize > maxCacheItems) {
      // 获取所有缓存项并按优先级和访问时间排序
      final allItems = _cacheBox.values.toList();
      allItems.sort((a, b) {
        // 首先按优先级排序
        final priorityCompare = a.priority.index.compareTo(b.priority.index);
        if (priorityCompare != 0) return priorityCompare;
        
        // 然后按最后访问时间排序
        return a.lastAccessed.compareTo(b.lastAccessed);
      });
      
      // 删除低优先级和长时间未访问的项
      final itemsToDelete = allItems.take(cacheSize - maxCacheItems + 100);
      for (final item in itemsToDelete) {
        final key = _cacheBox.keys.firstWhere(
          (k) => _cacheBox.get(k) == item,
          orElse: () => null,
        );
        if (key != null) {
          await _cacheBox.delete(key);
        }
      }
    }
  }

  bool _isCacheExpired(CachedData cachedData) {
    return DateTime.now().difference(cachedData.timestamp) > cachedData.duration;
  }
}

enum CachePriority { low, normal, high, critical }

(typeId: 10)
class CachedData extends HiveObject {
  (0)
  dynamic data;
  
  (1)
  DateTime timestamp;
  
  (2)
  CachePriority priority;
  
  (3)
  Duration duration;
  
  (4)
  DateTime lastAccessed;

  CachedData({
    required this.data,
    required this.timestamp,
    required this.priority,
    required this.duration,
    DateTime? lastAccessed,
  }) : lastAccessed = lastAccessed ?? timestamp;
}

扩展功能

1. 真实地图集成

可以集成Google Maps或高德地图:

dependencies:
  google_maps_flutter: ^2.5.0

2. 位置服务

集成真实的位置服务:

dependencies:
  geolocator: ^10.1.0

3. 推送通知

使用flutter_local_notifications:

dependencies:
  flutter_local_notifications: ^16.1.0

4. 社交分享

集成社交分享功能:

dependencies:
  share_plus: ^7.2.1

全面测试策略

1. 单元测试

1.1 业务逻辑测试
// test/domain/usecases/store_usecase_test.dart
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/mockito.dart';
import 'package:mockito/annotations.dart';

([StoreRepository, LocationRepository, UserRepository])
void main() {
  group('StoreUseCase Tests', () {
    late StoreUseCase storeUseCase;
    late MockStoreRepository mockStoreRepository;
    late MockLocationRepository mockLocationRepository;
    late MockUserRepository mockUserRepository;

    setUp(() {
      mockStoreRepository = MockStoreRepository();
      mockLocationRepository = MockLocationRepository();
      mockUserRepository = MockUserRepository();
      
      storeUseCase = StoreUseCase(
        mockStoreRepository,
        mockLocationRepository,
        mockUserRepository,
      );
    });

    group('getNearbyStores', () {
      test('should return nearby stores when location is available', () async {
        // Arrange
        final mockLocation = GeoPoint(latitude: 39.9042, longitude: 116.4074);
        final mockStores = [
          _createMockStore('1', 'Store 1', 39.9050, 116.4080),
          _createMockStore('2', 'Store 2', 39.9030, 116.4060),
        ];

        when(mockLocationRepository.getCurrentLocation())
            .thenAnswer((_) async => Result.success(mockLocation));
        
        when(mockStoreRepository.getNearbyStores(
          latitude: anyNamed('latitude'),
          longitude: anyNamed('longitude'),
          radius: anyNamed('radius'),
          limit: anyNamed('limit'),
        )).thenAnswer((_) async => Result.success(mockStores));

        when(mockUserRepository.getFavoriteStoreIds())
            .thenAnswer((_) async => ['1']);

        // Act
        final result = await storeUseCase.getNearbyStores();

        // Assert
        expect(result.isSuccess, true);
        expect(result.data!.length, 2);
        expect(result.data![0].isFavorite, true);
        expect(result.data![1].isFavorite, false);
        
        // 验证距离计算
        expect(result.data![0].distance, greaterThan(0));
        expect(result.data![1].distance, greaterThan(0));
        
        // 验证按距离排序
        expect(result.data![0].distance, lessThanOrEqualTo(result.data![1].distance));
      });

      test('should return error when location is not available', () async {
        // Arrange
        when(mockLocationRepository.getCurrentLocation())
            .thenAnswer((_) async => Result.failure(AppError.location('定位失败')));

        // Act
        final result = await storeUseCase.getNearbyStores();

        // Assert
        expect(result.isFailure, true);
        expect(result.error!.type, AppErrorType.location);
      });
    });

    group('searchStores', () {
      test('should return filtered stores based on keyword', () async {
        // Arrange
        const keyword = 'Hama';
        final mockStores = [
          _createMockStoreWithBrands('1', 'Store 1', ['Hama', 'Perler']),
          _createMockStoreWithBrands('2', 'Store 2', ['Artkal']),
          _createMockStoreWithBrands('3', 'Store 3', ['Hama']),
        ];

        when(mockStoreRepository.searchStores(
          keyword: keyword,
          criteria: anyNamed('criteria'),
        )).thenAnswer((_) async => Result.success(mockStores));

        when(mockUserRepository.getFavoriteStoreIds())
            .thenAnswer((_) async => []);

        // Act
        final result = await storeUseCase.searchStores(keyword: keyword);

        // Assert
        expect(result.isSuccess, true);
        expect(result.data!.length, 3);
        
        verify(mockUserRepository.addSearchHistory(keyword)).called(1);
      });

      test('should handle empty search results', () async {
        // Arrange
        const keyword = 'NonExistentBrand';
        
        when(mockStoreRepository.searchStores(
          keyword: keyword,
          criteria: anyNamed('criteria'),
        )).thenAnswer((_) async => Result.success([]));

        when(mockUserRepository.getFavoriteStoreIds())
            .thenAnswer((_) async => []);

        // Act
        final result = await storeUseCase.searchStores(keyword: keyword);

        // Assert
        expect(result.isSuccess, true);
        expect(result.data!.isEmpty, true);
      });
    });

    group('toggleFavorite', () {
      test('should add store to favorites when not favorited', () async {
        // Arrange
        const storeId = '1';
        
        when(mockUserRepository.toggleFavoriteStore(storeId))
            .thenAnswer((_) async => true);

        // Act
        final result = await storeUseCase.toggleFavorite(storeId);

        // Assert
        expect(result.isSuccess, true);
        expect(result.data, true);
        
        verify(mockUserRepository.toggleFavoriteStore(storeId)).called(1);
      });

      test('should remove store from favorites when already favorited', () async {
        // Arrange
        const storeId = '1';
        
        when(mockUserRepository.toggleFavoriteStore(storeId))
            .thenAnswer((_) async => false);

        // Act
        final result = await storeUseCase.toggleFavorite(storeId);

        // Assert
        expect(result.isSuccess, true);
        expect(result.data, false);
      });
    });
  });
}

// 辅助方法
BeadStore _createMockStore(String id, String name, double lat, double lng) {
  return BeadStore(
    id: id,
    name: name,
    address: 'Test Address',
    latitude: lat,
    longitude: lng,
    city: 'Test City',
    district: 'Test District',
    contact: ContactInfo(phone: '123456789'),
    businessHours: BusinessHours(weekdays: {}),
    brands: ['TestBrand'],
    products: [],
    rating: StoreRating(overall: 4.5, service: 4.5, product: 4.5, environment: 4.5, reviewCount: 10),
    reviews: [],
    description: 'Test Description',
    images: [],
    ownerName: 'Test Owner',
    establishedDate: DateTime.now(),
    services: [],
    isFavorite: false,
    distance: 0.0,
  );
}

BeadStore _createMockStoreWithBrands(String id, String name, List<String> brands) {
  return BeadStore(
    id: id,
    name: name,
    address: 'Test Address',
    latitude: 39.9042,
    longitude: 116.4074,
    city: 'Test City',
    district: 'Test District',
    contact: ContactInfo(phone: '123456789'),
    businessHours: BusinessHours(weekdays: {}),
    brands: brands,
    products: [],
    rating: StoreRating(overall: 4.5, service: 4.5, product: 4.5, environment: 4.5, reviewCount: 10),
    reviews: [],
    description: 'Test Description',
    images: [],
    ownerName: 'Test Owner',
    establishedDate: DateTime.now(),
    services: [],
    isFavorite: false,
    distance: 0.0,
  );
}
1.2 数据模型测试
// test/data/models/bead_store_test.dart
import 'package:flutter_test/flutter_test.dart';

void main() {
  group('BeadStore Model Tests', () {
    group('calculateDistance', () {
      test('should calculate correct distance between two points', () {
        // Arrange
        final store = BeadStore(
          id: '1',
          name: 'Test Store',
          latitude: 39.9042, // 北京天安门
          longitude: 116.4074,
          // ... 其他必需字段
        );

        const userLat = 39.9100; // 用户位置
        const userLng = 116.4200;

        // Act
        store.calculateDistance(userLat, userLng);

        // Assert
        expect(store.distance, greaterThan(0));
        expect(store.distance, lessThan(2)); // 应该在2公里内
      });

      test('should return zero distance for same location', () {
        // Arrange
        final store = BeadStore(
          id: '1',
          name: 'Test Store',
          latitude: 39.9042,
          longitude: 116.4074,
          // ... 其他必需字段
        );

        // Act
        store.calculateDistance(39.9042, 116.4074);

        // Assert
        expect(store.distance, closeTo(0, 0.001));
      });
    });

    group('isOpen', () {
      test('should return true when store is open', () {
        // Arrange
        final now = DateTime.now();
        final currentTime = TimeOfDay.fromDateTime(now);
        final businessHours = BusinessHours(
          weekdays: {
            now.weekday: TimeRange(
              start: TimeOfDay(hour: currentTime.hour - 1, minute: 0),
              end: TimeOfDay(hour: currentTime.hour + 1, minute: 0),
            ),
          },
        );

        final store = BeadStore(
          id: '1',
          name: 'Test Store',
          businessHours: businessHours,
          // ... 其他必需字段
        );

        // Act & Assert
        expect(store.isOpen, true);
      });

      test('should return false when store is closed', () {
        // Arrange
        final now = DateTime.now();
        final businessHours = BusinessHours(
          weekdays: {
            now.weekday: TimeRange(
              start: const TimeOfDay(hour: 9, minute: 0),
              end: const TimeOfDay(hour: 10, minute: 0), // 已经过了营业时间
            ),
          },
        );

        final store = BeadStore(
          id: '1',
          name: 'Test Store',
          businessHours: businessHours,
          // ... 其他必需字段
        );

        // Act & Assert
        expect(store.isOpen, false);
      });
    });

    group('JSON serialization', () {
      test('should serialize and deserialize correctly', () {
        // Arrange
        final originalStore = _createTestStore();

        // Act
        final json = originalStore.toJson();
        final deserializedStore = BeadStore.fromJson(json);

        // Assert
        expect(deserializedStore.id, originalStore.id);
        expect(deserializedStore.name, originalStore.name);
        expect(deserializedStore.latitude, originalStore.latitude);
        expect(deserializedStore.longitude, originalStore.longitude);
        expect(deserializedStore.brands, originalStore.brands);
        expect(deserializedStore.rating.overall, originalStore.rating.overall);
      });
    });
  });
}

BeadStore _createTestStore() {
  return BeadStore(
    id: '1',
    name: 'Test Store',
    address: 'Test Address',
    latitude: 39.9042,
    longitude: 116.4074,
    city: 'Beijing',
    district: 'Chaoyang',
    contact: ContactInfo(phone: '123456789'),
    businessHours: BusinessHours(weekdays: {}),
    brands: ['Hama', 'Perler'],
    products: [],
    rating: StoreRating(
      overall: 4.5,
      service: 4.5,
      product: 4.5,
      environment: 4.5,
      reviewCount: 10,
    ),
    reviews: [],
    description: 'Test Description',
    images: ['test_image.jpg'],
    ownerName: 'Test Owner',
    establishedDate: DateTime(2020, 1, 1),
    services: ['Service 1', 'Service 2'],
    isFavorite: false,
    distance: 0.0,
  );
}

2. Widget测试

2.1 页面组件测试
// test/presentation/pages/home_page_test.dart
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:provider/provider.dart';
import 'package:mockito/mockito.dart';

void main() {
  group('HomePage Widget Tests', () {
    late MockStoreProvider mockStoreProvider;

    setUp(() {
      mockStoreProvider = MockStoreProvider();
    });

    testWidgets('should display loading indicator when loading', (tester) async {
      // Arrange
      when(mockStoreProvider.isLoading).thenReturn(true);
      when(mockStoreProvider.nearbyStores).thenReturn([]);
      when(mockStoreProvider.recommendedStores).thenReturn([]);
      when(mockStoreProvider.currentLocation).thenReturn('定位中...');

      // Act
      await tester.pumpWidget(
        MaterialApp(
          home: ChangeNotifierProvider<StoreProvider>.value(
            value: mockStoreProvider,
            child: const HomePage(),
          ),
        ),
      );

      // Assert
      expect(find.byType(CircularProgressIndicator), findsOneWidget);
    });

    testWidgets('should display store list when data is loaded', (tester) async {
      // Arrange
      final mockStores = [
        _createMockStore('1', 'Store 1'),
        _createMockStore('2', 'Store 2'),
      ];

      when(mockStoreProvider.isLoading).thenReturn(false);
      when(mockStoreProvider.nearbyStores).thenReturn(mockStores);
      when(mockStoreProvider.recommendedStores).thenReturn(mockStores);
      when(mockStoreProvider.currentLocation).thenReturn('北京市朝阳区');
      when(mockStoreProvider.errorMessage).thenReturn(null);

      // Act
      await tester.pumpWidget(
        MaterialApp(
          home: ChangeNotifierProvider<StoreProvider>.value(
            value: mockStoreProvider,
            child: const HomePage(),
          ),
        ),
      );

      await tester.pumpAndSettle();

      // Assert
      expect(find.text('Store 1'), findsOneWidget);
      expect(find.text('Store 2'), findsOneWidget);
      expect(find.text('北京市朝阳区'), findsOneWidget);
    });

    testWidgets('should display error message when error occurs', (tester) async {
      // Arrange
      const errorMessage = '网络连接失败';
      
      when(mockStoreProvider.isLoading).thenReturn(false);
      when(mockStoreProvider.nearbyStores).thenReturn([]);
      when(mockStoreProvider.recommendedStores).thenReturn([]);
      when(mockStoreProvider.currentLocation).thenReturn('北京市朝阳区');
      when(mockStoreProvider.errorMessage).thenReturn(errorMessage);

      // Act
      await tester.pumpWidget(
        MaterialApp(
          home: ChangeNotifierProvider<StoreProvider>.value(
            value: mockStoreProvider,
            child: const HomePage(),
          ),
        ),
      );

      await tester.pumpAndSettle();

      // Assert
      expect(find.text(errorMessage), findsOneWidget);
    });

    testWidgets('should trigger search when search field is submitted', (tester) async {
      // Arrange
      when(mockStoreProvider.isLoading).thenReturn(false);
      when(mockStoreProvider.nearbyStores).thenReturn([]);
      when(mockStoreProvider.recommendedStores).thenReturn([]);
      when(mockStoreProvider.currentLocation).thenReturn('北京市朝阳区');
      when(mockStoreProvider.errorMessage).thenReturn(null);

      // Act
      await tester.pumpWidget(
        MaterialApp(
          home: ChangeNotifierProvider<StoreProvider>.value(
            value: mockStoreProvider,
            child: const HomePage(),
          ),
        ),
      );

      await tester.pumpAndSettle();

      // 输入搜索关键词
      const searchKeyword = 'Hama';
      await tester.enterText(find.byType(TextField), searchKeyword);
      await tester.testTextInput.receiveAction(TextInputAction.done);
      await tester.pumpAndSettle();

      // Assert
      verify(mockStoreProvider.searchStores(searchKeyword, criteria: anyNamed('criteria')))
          .called(1);
    });

    testWidgets('should toggle favorite when favorite button is tapped', (tester) async {
      // Arrange
      final mockStore = _createMockStore('1', 'Test Store');
      
      when(mockStoreProvider.isLoading).thenReturn(false);
      when(mockStoreProvider.nearbyStores).thenReturn([mockStore]);
      when(mockStoreProvider.recommendedStores).thenReturn([]);
      when(mockStoreProvider.currentLocation).thenReturn('北京市朝阳区');
      when(mockStoreProvider.errorMessage).thenReturn(null);

      // Act
      await tester.pumpWidget(
        MaterialApp(
          home: ChangeNotifierProvider<StoreProvider>.value(
            value: mockStoreProvider,
            child: const HomePage(),
          ),
        ),
      );

      await tester.pumpAndSettle();

      // 点击收藏按钮
      await tester.tap(find.byIcon(Icons.favorite_border));
      await tester.pumpAndSettle();

      // Assert
      verify(mockStoreProvider.toggleFavorite(mockStore)).called(1);
    });
  });
}

3. 集成测试

3.1 端到端测试
// integration_test/app_test.dart
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import 'package:bead_store_finder/main.dart' as app;

void main() {
  IntegrationTestWidgetsFlutterBinding.ensureInitialized();

  group('App Integration Tests', () {
    testWidgets('complete user flow test', (tester) async {
      // 启动应用
      app.main();
      await tester.pumpAndSettle();

      // 等待启动页完成
      await tester.pumpAndSettle(const Duration(seconds: 3));

      // 验证主页面加载
      expect(find.text('附近店铺'), findsOneWidget);
      expect(find.byType(NavigationBar), findsOneWidget);

      // 测试搜索功能
      await tester.enterText(find.byType(TextField), 'Hama');
      await tester.testTextInput.receiveAction(TextInputAction.done);
      await tester.pumpAndSettle();

      // 验证搜索结果
      expect(find.text('搜索结果'), findsOneWidget);

      // 测试店铺详情页
      await tester.tap(find.byType(Card).first);
      await tester.pumpAndSettle();

      // 验证详情页内容
      expect(find.text('店铺信息'), findsOneWidget);
      expect(find.text('商品列表'), findsOneWidget);

      // 测试收藏功能
      await tester.tap(find.byIcon(Icons.favorite_border));
      await tester.pumpAndSettle();

      // 验证收藏状态变化
      expect(find.byIcon(Icons.favorite), findsOneWidget);

      // 返回主页
      await tester.tap(find.byIcon(Icons.arrow_back));
      await tester.pumpAndSettle();

      // 测试收藏页面
      await tester.tap(find.text('收藏'));
      await tester.pumpAndSettle();

      // 验证收藏页面显示收藏的店铺
      expect(find.byType(Card), findsAtLeastNWidgets(1));

      // 测试地图页面
      await tester.tap(find.text('地图'));
      await tester.pumpAndSettle();

      // 验证地图页面加载
      expect(find.text('地图加载中...'), findsOneWidget);

      // 测试个人中心页面
      await tester.tap(find.text('我的'));
      await tester.pumpAndSettle();

      // 验证个人中心页面
      expect(find.text('拼豆爱好者'), findsOneWidget);
      expect(find.text('浏览记录'), findsOneWidget);
    });

    testWidgets('offline functionality test', (tester) async {
      // 启动应用
      app.main();
      await tester.pumpAndSettle();

      // 模拟网络断开
      // 这里需要使用网络模拟工具

      // 验证离线状态下的功能
      expect(find.text('网络连接失败'), findsNothing);
      expect(find.byType(Card), findsAtLeastNWidgets(1)); // 应该显示缓存数据
    });

    testWidgets('performance test', (tester) async {
      // 启动应用
      app.main();
      await tester.pumpAndSettle();

      // 测试大量数据的性能
      final stopwatch = Stopwatch()..start();

      // 滚动列表
      await tester.fling(find.byType(ListView), const Offset(0, -500), 1000);
      await tester.pumpAndSettle();

      stopwatch.stop();

      // 验证性能指标
      expect(stopwatch.elapsedMilliseconds, lessThan(1000)); // 滚动应该在1秒内完成
    });
  });
}

生产环境部署

1. 应用打包优化

1.1 Android打包配置
# 生成签名密钥
keytool -genkey -v -keystore ~/bead-store-key.jks -keyalg RSA -keysize 2048 -validity 10000 -alias bead-store

# 配置gradle签名
# android/app/build.gradle
android {
    signingConfigs {
        release {
            keyAlias keystoreProperties['keyAlias']
            keyPassword keystoreProperties['keyPassword']
            storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null
            storePassword keystoreProperties['storePassword']
        }
    }
    
    buildTypes {
        release {
            signingConfig signingConfigs.release
            minifyEnabled true
            shrinkResources true
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
}

# 优化构建
flutter build apk --release --split-per-abi --obfuscate --split-debug-info=build/debug-info
1.2 iOS打包配置
# 配置iOS签名和证书
# ios/Runner.xcodeproj/project.pbxproj

# 构建iOS应用
flutter build ios --release --obfuscate --split-debug-info=build/debug-info

# 创建IPA文件
cd ios
xcodebuild -workspace Runner.xcworkspace -scheme Runner -configuration Release -destination generic/platform=iOS -archivePath build/Runner.xcarchive archive
xcodebuild -exportArchive -archivePath build/Runner.xcarchive -exportPath build/Runner.ipa -exportOptionsPlist ExportOptions.plist
1.3 代码混淆配置
// 创建混淆配置文件 obfuscation.txt
# 保护关键类不被混淆
-keep class com.example.beadstore.** { *; }
-keep class io.flutter.** { *; }
-keep class androidx.** { *; }

# 保护数据模型类
-keep class **$BeadStore { *; }
-keep class **$BeadProduct { *; }
-keep class **$Review { *; }

# 保护网络请求相关类
-keep class retrofit2.** { *; }
-keep class okhttp3.** { *; }

2. 性能监控与分析

2.1 Firebase集成
# pubspec.yaml
dependencies:
  firebase_core: ^2.24.2
  firebase_analytics: ^10.7.4
  firebase_crashlytics: ^3.4.8
  firebase_performance: ^0.9.3+8
// lib/core/analytics/analytics_service.dart
class AnalyticsService {
  static final FirebaseAnalytics _analytics = FirebaseAnalytics.instance;
  static final FirebaseCrashlytics _crashlytics = FirebaseCrashlytics.instance;

  // 初始化分析服务
  static Future<void> initialize() async {
    await _analytics.setAnalyticsCollectionEnabled(true);
    await _crashlytics.setCrashlyticsCollectionEnabled(true);
    
    // 设置用户属性
    await _analytics.setUserProperty(
      name: 'app_version',
      value: await _getAppVersion(),
    );
  }

  // 跟踪页面访问
  static Future<void> trackPageView(String pageName) async {
    await _analytics.logScreenView(screenName: pageName);
  }

  // 跟踪用户行为
  static Future<void> trackEvent(String eventName, Map<String, dynamic> parameters) async {
    await _analytics.logEvent(
      name: eventName,
      parameters: parameters,
    );
  }

  // 跟踪搜索行为
  static Future<void> trackSearch(String searchTerm) async {
    await _analytics.logSearch(searchTerm: searchTerm);
  }

  // 跟踪店铺查看
  static Future<void> trackStoreView(String storeId, String storeName) async {
    await _analytics.logEvent(
      name: 'store_view',
      parameters: {
        'store_id': storeId,
        'store_name': storeName,
        'timestamp': DateTime.now().millisecondsSinceEpoch,
      },
    );
  }

  // 跟踪收藏操作
  static Future<void> trackFavoriteAction(String storeId, bool isFavorited) async {
    await _analytics.logEvent(
      name: 'favorite_action',
      parameters: {
        'store_id': storeId,
        'action': isFavorited ? 'add' : 'remove',
        'timestamp': DateTime.now().millisecondsSinceEpoch,
      },
    );
  }

  // 记录错误
  static Future<void> recordError(dynamic error, StackTrace stackTrace) async {
    await _crashlytics.recordError(error, stackTrace);
  }

  // 记录自定义错误
  static Future<void> recordCustomError(String message, Map<String, dynamic> context) async {
    await _crashlytics.log(message);
    await _crashlytics.setCustomKey('error_context', context.toString());
  }

  static Future<String> _getAppVersion() async {
    final packageInfo = await PackageInfo.fromPlatform();
    return packageInfo.version;
  }
}
2.2 性能监控实现
// lib/core/performance/performance_monitor.dart
class PerformanceMonitor {
  static final Map<String, Stopwatch> _stopwatches = {};
  static final FirebasePerformance _performance = FirebasePerformance.instance;

  // 开始性能追踪
  static void startTrace(String traceName) {
    _stopwatches[traceName] = Stopwatch()..start();
  }

  // 结束性能追踪
  static Future<void> stopTrace(String traceName) async {
    final stopwatch = _stopwatches[traceName];
    if (stopwatch != null) {
      stopwatch.stop();
      
      final trace = _performance.newTrace(traceName);
      await trace.start();
      
      // 设置性能指标
      trace.setMetric('duration_ms', stopwatch.elapsedMilliseconds);
      
      await trace.stop();
      _stopwatches.remove(traceName);
      
      // 记录到分析服务
      await AnalyticsService.trackEvent('performance_trace', {
        'trace_name': traceName,
        'duration_ms': stopwatch.elapsedMilliseconds,
      });
    }
  }

  // 监控网络请求性能
  static Future<T> monitorNetworkRequest<T>(
    String requestName,
    Future<T> Function() request,
  ) async {
    final trace = _performance.newTrace('network_$requestName');
    await trace.start();
    
    try {
      final result = await request();
      trace.setMetric('success', 1);
      return result;
    } catch (e) {
      trace.setMetric('error', 1);
      await AnalyticsService.recordError(e, StackTrace.current);
      rethrow;
    } finally {
      await trace.stop();
    }
  }

  // 监控页面加载性能
  static Future<void> monitorPageLoad(String pageName, Future<void> Function() loadFunction) async {
    startTrace('page_load_$pageName');
    
    try {
      await loadFunction();
    } finally {
      await stopTrace('page_load_$pageName');
    }
  }
}

3. 应用商店发布

3.1 Google Play发布
# 1. 准备发布资源
mkdir -p android/fastlane/metadata/android/zh-CN
mkdir -p android/fastlane/metadata/android/en-US

# 2. 创建应用描述文件
# android/fastlane/metadata/android/zh-CN/full_description.txt
发现身边的拼豆世界!

Flutter全国拼豆实体店查询是一款专为拼豆手工艺爱好者打造的便民应用。无论您是拼豆新手还是资深玩家,都能通过我们的应用快速找到附近的拼豆店铺,获取最新的商品信息和用户评价。

🎯 核心功能:
• 精准定位:基于GPS定位,快速找到附近的拼豆店铺
• 详细信息:店铺地址、营业时间、联系方式、商品种类一目了然
• 智能搜索:支持店铺名称、品牌、商品等多维度搜索
• 用户评价:真实用户评价,帮您选择最合适的店铺
• 收藏管理:收藏常去店铺,方便下次查找
• 路线导航:一键导航,轻松到达目标店铺

🌟 特色亮点:
• 覆盖全国300+城市的拼豆实体店
• 支持Hama、Perler、Artkal等知名品牌查询
• 个性化推荐,基于您的偏好推荐合适店铺
• 离线收藏,无网络也能查看基本信息
• 简洁美观的Material Design界面

让拼豆创作更简单,让手工艺术更精彩!

# 3. 配置Fastlane
# android/fastlane/Fastfile
default_platform(:android)

platform :android do
  desc "Deploy to Google Play"
  lane :deploy do
    gradle(task: "clean assembleRelease")
    upload_to_play_store(
      track: 'production',
      release_status: 'completed',
      aab: 'app/build/outputs/bundle/release/app-release.aab'
    )
  end
end

# 4. 执行发布
cd android
fastlane deploy
3.2 App Store发布
# 1. 配置iOS Fastlane
# ios/fastlane/Fastfile
default_platform(:ios)

platform :ios do
  desc "Deploy to App Store"
  lane :deploy do
    build_app(scheme: "Runner")
    upload_to_app_store(
      force: true,
      reject_if_possible: true,
      skip_metadata: false,
      skip_screenshots: false,
      submit_for_review: true,
      automatic_release: true
    )
  end
end

# 2. 准备App Store资源
mkdir -p ios/fastlane/metadata/zh-Hans
mkdir -p ios/fastlane/metadata/en-US

# 3. 创建应用描述
# ios/fastlane/metadata/zh-Hans/description.txt
发现身边的拼豆世界!专业的拼豆店铺查询应用,为手工艺爱好者提供便捷的店铺查找服务。

# 4. 执行发布
cd ios
fastlane deploy

4. 持续集成/持续部署 (CI/CD)

4.1 GitHub Actions配置
# .github/workflows/build_and_deploy.yml
name: Build and Deploy

on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v3
    
    - name: Setup Flutter
      uses: subosito/flutter-action@v2
      with:
        flutter-version: '3.16.0'
        
    - name: Install dependencies
      run: flutter pub get
      
    - name: Run tests
      run: flutter test --coverage
      
    - name: Upload coverage to Codecov
      uses: codecov/codecov-action@v3
      with:
        file: coverage/lcov.info

  build_android:
    needs: test
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main'
    steps:
    - uses: actions/checkout@v3
    
    - name: Setup Flutter
      uses: subosito/flutter-action@v2
      with:
        flutter-version: '3.16.0'
        
    - name: Setup Java
      uses: actions/setup-java@v3
      with:
        distribution: 'zulu'
        java-version: '11'
        
    - name: Install dependencies
      run: flutter pub get
      
    - name: Build APK
      run: flutter build apk --release --split-per-abi
      
    - name: Build AAB
      run: flutter build appbundle --release
      
    - name: Upload artifacts
      uses: actions/upload-artifact@v3
      with:
        name: android-builds
        path: |
          build/app/outputs/flutter-apk/*.apk
          build/app/outputs/bundle/release/*.aab

  build_ios:
    needs: test
    runs-on: macos-latest
    if: github.ref == 'refs/heads/main'
    steps:
    - uses: actions/checkout@v3
    
    - name: Setup Flutter
      uses: subosito/flutter-action@v2
      with:
        flutter-version: '3.16.0'
        
    - name: Install dependencies
      run: flutter pub get
      
    - name: Build iOS
      run: flutter build ios --release --no-codesign
      
    - name: Upload artifacts
      uses: actions/upload-artifact@v3
      with:
        name: ios-build
        path: build/ios/iphoneos/Runner.app

项目总结与展望

1. 技术成果总结

通过本教程的完整实现,我们成功构建了一个功能丰富、性能优秀的Flutter拼豆实体店查询应用,主要技术成果包括:

🏗️ 架构设计成果
  • 清晰的分层架构:采用MVVM模式,实现了表现层、业务逻辑层和数据访问层的有效分离
  • 模块化设计:各功能模块独立开发,便于维护和扩展
  • 依赖注入:使用GetIt实现依赖管理,提高代码的可测试性和可维护性
💡 核心功能实现
  • 智能搜索系统:多维度搜索算法,支持模糊匹配和相关性排序
  • 个性化推荐:基于用户行为的推荐算法,提升用户体验
  • 高性能地图:优化的地图组件,支持大量标记点的流畅显示
  • 离线支持:多层缓存机制,保证离线状态下的基本功能可用
🚀 性能优化成果
  • 内存优化:图片缓存、列表虚拟化、对象池等技术的应用
  • 网络优化:请求缓存、重试机制、智能同步策略
  • UI优化:60fps流畅动画、响应式布局、Material Design 3
🧪 质量保证体系
  • 全面测试覆盖:单元测试、Widget测试、集成测试的完整覆盖
  • 性能监控:Firebase集成,实时监控应用性能和错误
  • CI/CD流程:自动化构建、测试和部署流程

2. 用户价值体现

📍 解决实际痛点
  • 信息整合:将分散的店铺信息统一整合,提供一站式查询服务
  • 精准定位:基于GPS的精确定位,快速找到最近的店铺
  • 真实评价:用户真实评价系统,帮助做出明智选择
  • 便捷导航:一键导航功能,无缝对接地图应用
🎯 提升用户体验
  • 直观界面:Material Design 3设计语言,界面美观易用
  • 快速响应:优化的性能表现,操作响应迅速
  • 个性化服务:基于用户偏好的智能推荐
  • 离线可用:关键功能离线可用,不受网络限制

3. 技术创新亮点

🔍 智能搜索算法
  • 多维度相关性评分算法
  • 实时搜索建议生成
  • 基于用户行为的搜索优化
🗺️ 地图性能优化
  • 可视区域标记管理
  • 动态标记加载和卸载
  • 自定义标记样式系统
💾 缓存策略创新
  • 多层级缓存架构
  • 智能缓存更新策略
  • 基于用户活跃度的缓存管理
📊 数据分析集成
  • 用户行为追踪
  • 性能指标监控
  • 错误自动收集和分析

4. 商业价值与市场前景

💰 商业模式潜力
  • 广告收入:店铺推广、品牌广告位
  • 佣金模式:与店铺合作,获取交易佣金
  • 增值服务:VIP会员、专属优惠等
  • 数据服务:为品牌商提供市场分析数据
📈 市场扩展机会
  • 品类扩展:从拼豆扩展到其他手工艺品类
  • 地域扩展:从国内扩展到海外市场
  • 功能扩展:增加社区、教程、作品展示等功能
  • B2B服务:为店铺提供管理工具和营销服务

5. 技术发展方向

🤖 AI技术集成
  • 图像识别:拍照识别拼豆作品,推荐相关商品
  • 智能客服:AI聊天机器人,提供24小时客户服务
  • 个性化推荐:深度学习算法优化推荐精度
  • 语音搜索:语音识别技术,提升搜索便利性
🌐 跨平台扩展
  • Web版本:基于Flutter Web的浏览器版本
  • 桌面应用:Windows、macOS、Linux桌面版本
  • 小程序:微信、支付宝小程序版本
  • 智能设备:智能手表、车载系统适配
🔗 生态系统建设
  • 开放API:为第三方开发者提供数据接口
  • 插件系统:支持第三方功能扩展
  • SDK开发:为其他应用提供店铺查询SDK
  • 合作伙伴:与地图服务商、支付平台深度合作

6. 学习价值与技能提升

通过本教程的学习和实践,开发者可以获得以下技能提升:

🛠️ 技术技能
  • Flutter高级开发:掌握Flutter的高级特性和最佳实践
  • 架构设计能力:学会设计可扩展、可维护的应用架构
  • 性能优化技巧:掌握移动应用性能优化的各种技术
  • 测试驱动开发:建立完整的测试体系和质量保证流程
💼 项目管理
  • 需求分析:从用户痛点出发,设计产品功能
  • 技术选型:根据项目需求选择合适的技术栈
  • 项目规划:合理规划开发进度和资源分配
  • 质量控制:建立代码质量和产品质量控制体系
🚀 产品思维
  • 用户体验设计:以用户为中心的产品设计思维
  • 商业模式思考:技术与商业的结合思考
  • 市场分析能力:分析市场需求和竞争态势
  • 创新思维:在技术实现中融入创新元素

7. 结语

本教程通过一个完整的实际项目,展示了Flutter应用开发的全流程,从需求分析到架构设计,从功能实现到性能优化,从测试部署到运营维护,涵盖了移动应用开发的各个方面。

这不仅仅是一个技术教程,更是一个产品思维和工程实践的完整展示。希望通过本教程的学习,能够帮助开发者:

  1. 掌握Flutter高级开发技能,能够独立开发复杂的移动应用
  2. 建立系统的架构思维,设计出可扩展、可维护的应用架构
  3. 培养产品意识,从用户需求出发思考技术实现
  4. 建立质量意识,重视测试、性能和用户体验
  5. 具备商业思维,思考技术如何创造商业价值

Flutter作为跨平台开发的优秀框架,正在被越来越多的企业和开发者采用。掌握Flutter不仅能够提升个人技术能力,更能够在移动互联网时代获得更多的发展机会。

希望每一位学习者都能通过本教程有所收获,在Flutter开发的道路上越走越远,创造出更多优秀的移动应用,为用户带来更好的体验,为社会创造更大的价值。

让我们一起用技术改变世界,用代码创造美好!

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐