Flutter × OpenHarmony:电商首页完整开发指南(组件化设计 + 数据驱动 + 最佳实践)

前言

本文将带你从零开始,使用 Flutter 构建一个生产级的电商应用首页。我们将深入探讨组件化设计、数据驱动开发、状态管理、以及在实际开发中遇到的各种问题和解决方案。

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

项目环境:

  • Flutter SDK 3.24.0+
  • Dart 3.5.0+
  • DevEco Studio 5.0+
  • HarmonyOS API 9+

你将学到:

  • ✅ 完整的组件化设计模式
  • ✅ 数据驱动的开发思维
  • ✅ MVVM 架构在 Flutter 中的应用
  • ✅ CustomScrollView 高级用法
  • ✅ 真实场景的踩坑与解决方案

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

一、项目架构设计

1.1 整体架构

采用 MVVM(Model-View-ViewModel) 架构:

┌─────────────────────────────────────────────────────┐
│                     View (页面)                     │
│  ┌──────────┐  ┌──────────┐  ┌──────────┐         │
│  │ 首页视图  │  │ 分类视图  │  │购物车视图 │  ...   │
│  └────┬─────┘  └────┬─────┘  └────┬─────┘         │
│       │             │             │                │
├───────┴─────────────┴─────────────┴────────────────┤
│                  ViewModel (视图模型)               │
│  ┌──────────┐  ┌──────────┐  ┌──────────┐         │
│  │首页VM    │  │数据状态  │  │业务逻辑  │         │
│  └────┬─────┘  └────┬─────┘  └────┬─────┘         │
├───────┴─────────────┴─────────────┴────────────────┤
│                   Model (数据模型)                  │
│  ┌──────────┐  ┌──────────┐  ┌──────────┐         │
│  │BannerItem│  │Category  │  │GoodsItem │         │
│  └──────────┘  └──────────┘  └──────────┘         │
├─────────────────────────────────────────────────────┤
│                    API (网络层)                     │
│  ┌──────────┐  ┌──────────┐  ┌──────────┐         │
│  │ Dio封装  │  │ 接口定义  │  │ 错误处理  │         │
│  └──────────┘  └──────────┘  └──────────┘         │
└─────────────────────────────────────────────────────┘

1.2 项目目录结构

lib/
├── main.dart                    # 应用入口
├── constants/                   # 常量定义
│   └── index.dart              # 全局常量、接口地址
├── utils/                       # 工具类
│   └── DioRequest.dart         # 网络请求封装
├── viewmodels/                  # 视图模型(数据模型)
│   └── home.dart               # 首页数据模型
├── api/                         # API接口
│   └── home.dart               # 首页接口
├── components/                  # 可复用组件
│   └── Home/                   # 首页组件
│       ├── HmSlider.dart       # 轮播图组件
│       ├── HmCategory.dart     # 分类组件
│       ├── HmSuggestion.dart   # 推荐组件
│       ├── HmHot.dart          # 爆款推荐组件
│       └── HmMoreList.dart     # 商品列表组件
└── pages/                       # 页面
    ├── home/
    │   └── index.dart          # 首页
    ├── category/
    │   └── index.dart          # 分类页面
    ├── cart/
    │   └── index.dart          # 购物车页面
    ├── profile/
    │   └── index.dart          # 我的页面
    └── main/
        └── index.dart          # 底部导航主框架

二、核心数据模型设计

2.1 数据模型定义

文件:lib/viewmodels/home.dart

/// 轮播图数据模型
class BannerItem {
  final String id;
  final String imgUrl;
  final String? linkUrl;  // 跳转链接

  BannerItem({
    required this.id,
    required this.imgUrl,
    this.linkUrl,
  });

  factory BannerItem.fromJSON(Map<String, dynamic> json) {
    return BannerItem(
      id: json['id']?.toString() ?? '',
      imgUrl: json['imgUrl'] ?? '',
      linkUrl: json['linkUrl'],
    );
  }
}

/// 分类数据模型
class CategoryItem {
  final String id;
  final String name;
  final String picture;
  final List<CategoryItem>? children;

  CategoryItem({
    required this.id,
    required this.name,
    required this.picture,
    this.children,
  });

  factory CategoryItem.fromJSON(Map<String, dynamic> json) {
    return CategoryItem(
      id: json['id']?.toString() ?? '',
      name: json['name'] ?? '',
      picture: json['picture'] ?? '',
      children: json['children'] == null
          ? null
          : (json['children'] as List)
              .map((e) => CategoryItem.fromJSON(e as Map<String, dynamic>))
              .toList(),
    );
  }
}

/// 商品数据模型
class GoodsItem {
  final String id;
  final String name;
  final String? desc;
  final String price;
  final String picture;
  final int orderNum;

  GoodsItem({
    required this.id,
    required this.name,
    this.desc,
    required this.price,
    required this.picture,
    required this.orderNum,
  });

  factory GoodsItem.fromJSON(Map<String, dynamic> json) {
    return GoodsItem(
      id: json['id']?.toString() ?? '',
      name: json['name'] ?? '',
      desc: json['desc'],
      price: json['price']?.toString() ?? '0',
      picture: json['picture'] ?? '',
      orderNum: json['orderNum'] ?? 0,
    );
  }
}

/// 商品列表模型
class GoodsItems {
  final int counts;
  final int pageSize;
  final int pages;
  final int page;
  final List<GoodsItem> items;

  GoodsItems({
    required this.counts,
    required this.pageSize,
    required this.pages,
    required this.page,
    required this.items,
  });

  factory GoodsItems.fromJSON(Map<String, dynamic> json) {
    return GoodsItems(
      counts: json['counts'] ?? 0,
      pageSize: json['pageSize'] ?? 0,
      pages: json['pages'] ?? 0,
      page: json['page'] ?? 0,
      items: (json['items'] as List?)
          ?.map((e) => GoodsItem.fromJSON(e as Map<String, dynamic>))
          .toList() ?? [],
    );
  }
}

/// 特惠推荐结果模型
class SpecialOfferResult {
  final String id;
  final String title;
  final List<SubType> subTypes;

  SpecialOfferResult({
    required this.id,
    required this.title,
    required this.subTypes,
  });

  factory SpecialOfferResult.fromJSON(Map<String, dynamic> json) {
    return SpecialOfferResult(
      id: json['id']?.toString() ?? '',
      title: json['title'] ?? '',
      subTypes: (json['subTypes'] as List?)
          ?.map((e) => SubType.fromJSON(e as Map<String, dynamic>))
          .toList() ?? [],
    );
  }
}

/// 子类型模型
class SubType {
  final String id;
  final String title;
  final GoodsItems goodsItems;

  SubType({
    required this.id,
    required this.title,
    required this.goodsItems,
  });

  factory SubType.fromJSON(Map<String, dynamic> json) {
    return SubType(
      id: json['id']?.toString() ?? '',
      title: json['title'] ?? '',
      goodsItems: GoodsItems.fromJSON(json['goodsItems'] ?? {}),
    );
  }
}

2.2 常量定义

文件:lib/constants/index.dart

/// 全局状态常量
class GlobalConstants {
  /// 基础地址
  static const String BASE_URL = "https://meikou-api.itheima.net/";

  /// 超时时间(秒)
  static const int TIME_OUT = 10;

  /// 成功状态码
  static const String SUCCESS_CODE = "1";
}

/// 接口地址常量
class HttpConstants {
  /// 轮播图接口
  static const String BANNER_LIST = "/home/banner";

  /// 分类列表接口
  static const String CATEGORY_LIST = "/home/category/head";

  /// 特惠推荐接口
  static const String PRODUCT_LIST = "/hot/preference";
}

三、网络层封装

3.1 Dio 请求封装

文件:lib/utils/DioRequest.dart

import 'package:dio/dio.dart';
import 'package:harmonyos_day_four/constants/index.dart';

/// Dio 单例
final dioRequest = DioRequest._internal();

class DioRequest {
  late final Dio _dio;

  // 私有构造函数
  DioRequest._internal() {
    _dio = Dio(BaseOptions(
      baseUrl: GlobalConstants.BASE_URL,
      connectTimeout: Duration(seconds: GlobalConstants.TIME_OUT),
      receiveTimeout: Duration(seconds: GlobalConstants.TIME_OUT),
      headers: {
        'Content-Type': 'application/json',
      },
    ));

    // 添加拦截器
    _dio.interceptors.add(InterceptorsWrapper(
      onRequest: (options, handler) {
        print('请求: ${options.method} ${options.uri}');
        return handler.next(options);
      },
      onResponse: (response, handler) {
        print('响应: ${response.statusCode} ${response.data}');
        return handler.next(response);
      },
      onError: (DioException error, handler) {
        print('错误: ${error.message}');
        return handler.next(error);
      },
    ));
  }

  /// GET 请求
  Future get(String path, {Map<String, dynamic>? queryParameters}) async {
    try {
      final response = await _dio.get(
        path,
        queryParameters: queryParameters,
      );
      return response.data['data'] ?? response.data;
    } catch (e) {
      rethrow;
    }
  }

  /// POST 请求
  Future post(String path, {dynamic data}) async {
    try {
      final response = await _dio.post(path, data: data);
      return response.data['data'] ?? response.data;
    } catch (e) {
      rethrow;
    }
  }
}

3.2 API 接口定义

文件:lib/api/home.dart

import 'package:harmonyos_day_four/utils/DioRequest.dart';
import 'package:harmonyos_day_four/viewmodels/home.dart';
import 'package:harmonyos_day_four/constants/index.dart';

/// 获取轮播图列表
Future<List<BannerItem>> getBannerListAPI() async {
  final result = await dioRequest.get(HttpConstants.BANNER_LIST) as List;
  return result.map((e) => BannerItem.fromJSON(e as Map<String, dynamic>)).toList();
}

/// 获取分类列表
Future<List<CategoryItem>> getCategoryListAPI() async {
  final result = await dioRequest.get(HttpConstants.CATEGORY_LIST) as List;
  return result.map((e) => CategoryItem.fromJSON(e as Map<String, dynamic>)).toList();
}

/// 获取特惠推荐
Future<SpecialOfferResult> getProductListAPI() async {
  final result = await dioRequest.get(HttpConstants.PRODUCT_LIST);
  return SpecialOfferResult.fromJSON(result);
}

四、首页组件实现(数据驱动)

4.1 轮播图组件(支持自动播放)

文件:lib/components/Home/HmSlider.dart

import 'dart:async';
import 'package:flutter/material.dart';
import 'package:harmonyos_day_four/viewmodels/home.dart';

/// 轮播图组件
class HmSlider extends StatefulWidget {
  final List<BannerItem> bannerList;

  const HmSlider({super.key, required this.bannerList});

  
  State<HmSlider> createState() => _HmSliderState();
}

class _HmSliderState extends State<HmSlider> {
  final PageController _pageController = PageController();
  int _currentIndex = 0;
  Timer? _timer;

  
  void initState() {
    super.initState();
    _startAutoPlay();
  }

  
  void didUpdateWidget(HmSlider oldWidget) {
    super.didUpdateWidget(oldWidget);
    // 数据更新时重置轮播
    if (widget.bannerList != oldWidget.bannerList) {
      setState(() {
        _currentIndex = 0;
      });
      _pageController.jumpToPage(0);
    }
  }

  /// 开始自动播放
  void _startAutoPlay() {
    _timer?.cancel();
    if (widget.bannerList.length <= 1) return;

    _timer = Timer.periodic(const Duration(seconds: 3), (timer) {
      if (_currentIndex < widget.bannerList.length - 1) {
        _currentIndex++;
      } else {
        _currentIndex = 0;
      }
      if (_pageController.hasClients) {
        _pageController.animateToPage(
          _currentIndex,
          duration: const Duration(milliseconds: 300),
          curve: Curves.easeInOut,
        );
      }
    });
  }

  
  void dispose() {
    _pageController.dispose();
    _timer?.cancel();
    super.dispose();
  }

  
  Widget build(BuildContext context) {
    if (widget.bannerList.isEmpty) {
      return _buildPlaceholder();
    }

    final screenWidth = MediaQuery.of(context).size.width;

    return SizedBox(
      height: 180,
      child: Stack(
        children: [
          // 轮播图
          PageView.builder(
            controller: _pageController,
            onPageChanged: (index) {
              setState(() {
                _currentIndex = index;
              });
            },
            itemCount: widget.bannerList.length,
            itemBuilder: (context, index) {
              return _buildBannerItem(widget.bannerList[index], screenWidth);
            },
          ),
          // 指示器
          _buildIndicator(),
        ],
      ),
    );
  }

  /// 构建单个轮播图
  Widget _buildBannerItem(BannerItem item, double screenWidth) {
    return GestureDetector(
      onTap: () {
        if (item.linkUrl != null) {
          // TODO: 处理跳转
          print('点击轮播图: ${item.linkUrl}');
        }
      },
      child: Image.network(
        item.imgUrl,
        width: screenWidth,
        fit: BoxFit.cover,
        errorBuilder: (context, error, stackTrace) {
          return Container(
            color: Colors.grey[200],
            child: const Center(
              child: Icon(Icons.broken_image, size: 50, color: Colors.grey),
            ),
          );
        },
      ),
    );
  }

  /// 构建指示器
  Widget _buildIndicator() {
    return Positioned(
      left: 0,
      right: 0,
      bottom: 10,
      child: Row(
        mainAxisAlignment: MainAxisAlignment.center,
        children: List.generate(widget.bannerList.length, (index) {
          final isActive = index == _currentIndex;
          return AnimatedContainer(
            duration: const Duration(milliseconds: 300),
            margin: const EdgeInsets.symmetric(horizontal: 4),
            width: isActive ? 20 : 8,
            height: 6,
            decoration: BoxDecoration(
              color: isActive ? Colors.orange : Colors.white,
              borderRadius: BorderRadius.circular(3),
            ),
          );
        }),
      ),
    );
  }

  /// 占位图
  Widget _buildPlaceholder() {
    return SizedBox(
      height: 180,
      child: Container(
        color: Colors.grey[200],
        child: const Center(
          child: CircularProgressIndicator(),
        ),
      ),
    );
  }
}

4.2 分类组件(横向滚动)

文件:lib/components/Home/HmCategory.dart

import 'package:flutter/material.dart';
import 'package:harmonyos_day_four/viewmodels/home.dart';

/// 分类组件
class HmCategory extends StatelessWidget {
  final List<CategoryItem> categoryList;

  const HmCategory({super.key, required this.categoryList});

  
  Widget build(BuildContext context) {
    if (categoryList.isEmpty) {
      return _buildPlaceholder();
    }

    return SizedBox(
      height: 100,
      child: ListView.builder(
        scrollDirection: Axis.horizontal,
        padding: const EdgeInsets.symmetric(horizontal: 10),
        itemCount: categoryList.length,
        itemBuilder: (context, index) {
          return _buildCategoryItem(categoryList[index]);
        },
      ),
    );
  }

  /// 构建分类项
  Widget _buildCategoryItem(CategoryItem item) {
    return Container(
      width: 70,
      margin: const EdgeInsets.only(right: 15),
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          // 图标
          Container(
            width: 50,
            height: 50,
            decoration: BoxDecoration(
              color: Colors.orange[50],
              borderRadius: BorderRadius.circular(25),
            ),
            child: ClipOval(
              child: Image.network(
                item.picture,
                fit: BoxFit.cover,
                errorBuilder: (context, error, stackTrace) {
                  return Icon(Icons.category, color: Colors.orange[300]);
                },
              ),
            ),
          ),
          const SizedBox(height: 8),
          // 名称
          Text(
            item.name,
            style: const TextStyle(
              fontSize: 12,
              color: Color(0xFF333333),
            ),
            maxLines: 1,
            overflow: TextOverflow.ellipsis,
          ),
        ],
      ),
    );
  }

  /// 占位图
  Widget _buildPlaceholder() {
    return SizedBox(
      height: 100,
      child: Center(
        child: Container(
          width: 50,
          height: 50,
          decoration: BoxDecoration(
            color: Colors.grey[200],
            borderRadius: BorderRadius.circular(25),
          ),
          child: const CircularProgressIndicator(strokeWidth: 2),
        ),
      ),
    );
  }
}

4.3 特惠推荐组件

文件:lib/components/Home/HmSuggestion.dart

import 'package:flutter/material.dart';
import 'package:harmonyos_day_four/viewmodels/home.dart';

/// 特惠推荐组件
class HmSuggestion extends StatelessWidget {
  final SpecialOfferResult specialOfferResult;

  const HmSuggestion({super.key, required this.specialOfferResult});

  
  Widget build(BuildContext context) {
    if (specialOfferResult.subTypes.isEmpty) {
      return const SizedBox.shrink();
    }

    return Padding(
      padding: const EdgeInsets.symmetric(horizontal: 10),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          // 标题
          Text(
            specialOfferResult.title,
            style: const TextStyle(
              fontSize: 18,
              fontWeight: FontWeight.bold,
              color: Color(0xFF333333),
            ),
          ),
          const SizedBox(height: 12),
          // 子类型列表
          ...specialOfferResult.subTypes.map((subType) {
            return _buildSubTypeSection(subType);
          }).toList(),
        ],
      ),
    );
  }

  /// 构建子类型区域
  Widget _buildSubTypeSection(SubType subType) {
    final goods = subType.goodsItems;

    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        // 子类型标题
        Padding(
          padding: const EdgeInsets.symmetric(vertical: 8),
          child: Row(
            children: [
              Container(
                width: 4,
                height: 16,
                decoration: BoxDecoration(
                  color: Colors.orange,
                  borderRadius: BorderRadius.circular(2),
                ),
              ),
              const SizedBox(width: 8),
              Text(
                subType.title,
                style: const TextStyle(
                  fontSize: 16,
                  fontWeight: FontWeight.w600,
                  color: Color(0xFF333333),
                ),
              ),
            ],
          ),
        ),
        // 商品横向列表
        SizedBox(
          height: 180,
          child: ListView.builder(
            scrollDirection: Axis.horizontal,
            itemCount: goods.items.length,
            itemBuilder: (context, index) {
              return _buildGoodsItem(goods.items[index]);
            },
          ),
        ),
      ],
    );
  }

  /// 构建商品项
  Widget _buildGoodsItem(GoodsItem item) {
    return Container(
      width: 120,
      margin: const EdgeInsets.only(right: 10),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          // 商品图片
          Expanded(
            child: ClipRRect(
              borderRadius: BorderRadius.circular(8),
              child: Image.network(
                item.picture,
                width: double.infinity,
                fit: BoxFit.cover,
                errorBuilder: (context, error, stackTrace) {
                  return Container(
                    color: Colors.grey[200],
                    child: const Center(
                      child: Icon(Icons.image_not_supported),
                    ),
                  );
                },
              ),
            ),
          ),
          const SizedBox(height: 8),
          // 商品名称
          Text(
            item.name,
            style: const TextStyle(
              fontSize: 13,
              color: Color(0xFF333333),
            ),
            maxLines: 2,
            overflow: TextOverflow.ellipsis,
          ),
          const SizedBox(height: 4),
          // 价格
          Text(
            ${item.price}',
            style: const TextStyle(
              fontSize: 16,
              fontWeight: FontWeight.bold,
              color: Colors.orange,
            ),
          ),
        ],
      ),
    );
  }
}

4.4 爆款推荐组件

文件:lib/components/Home/HmHot.dart

import 'package:flutter/material.dart';

/// 爆款推荐组件
class HmHot extends StatelessWidget {
  const HmHot({super.key});

  
  Widget build(BuildContext context) {
    return Container(
      height: 160,
      decoration: BoxDecoration(
        gradient: LinearGradient(
          colors: [Colors.orange[400]!, Colors.orange[300]!],
          begin: Alignment.topLeft,
          end: Alignment.bottomRight,
        ),
        borderRadius: BorderRadius.circular(12),
      ),
      alignment: Alignment.center,
      child: const Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          Icon(Icons.local_fire_department, color: Colors.white, size: 40),
          SizedBox(height: 8),
          Text(
            '爆款推荐',
            style: TextStyle(
              color: Colors.white,
              fontSize: 18,
              fontWeight: FontWeight.bold,
            ),
          ),
        ],
      ),
    );
  }
}

4.5 商品列表组件(无限滚动)

文件:lib/components/Home/HmMoreList.dart

import 'package:flutter/material.dart';
import 'package:harmonyos_day_four/viewmodels/home.dart';

/// 商品列表组件
class HmMorelist extends StatefulWidget {
  const HmMorelist({super.key});

  
  State<HmMorelist> createState() => _HmMorelistState();
}

class _HmMorelistState extends State<HmMorelist> {
  // 模拟数据
  final List<Map<String, String>> _goods = List.generate(
    20,
    (index) => {
      'name': '商品 ${index + 1}',
      'price': '${(index + 1) * 99}.00',
      'image': 'https://via.placeholder.com/150',
    },
  );

  // 是否正在加载
  bool _isLoading = false;

  
  Widget build(BuildContext context) {
    return SliverGrid.builder(
      gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
        crossAxisCount: 2,
        mainAxisSpacing: 10,
        crossAxisSpacing: 10,
        childAspectRatio: 0.75,
      ),
      itemCount: _goods.length + (_isLoading ? 2 : 0),
      itemBuilder: (context, index) {
        // 加载更多
        if (index == _goods.length) {
          _loadMore();
          return _buildLoadingItem();
        }

        return _buildGoodsItem(_goods[index]);
      },
    );
  }

  /// 构建商品项
  Widget _buildGoodsItem(Map<String, String> goods) {
    return Container(
      decoration: BoxDecoration(
        color: Colors.white,
        borderRadius: BorderRadius.circular(8),
        border: Border.all(color: Colors.grey[200]!),
      ),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          // 商品图片
          Expanded(
            child: ClipRRect(
              borderRadius: const BorderRadius.vertical(top: Radius.circular(8)),
              child: Image.network(
                goods['image']!,
                width: double.infinity,
                fit: BoxFit.cover,
                errorBuilder: (context, error, stackTrace) {
                  return Container(
                    color: Colors.grey[100],
                    child: const Center(
                      child: Icon(Icons.image, color: Colors.grey),
                    ),
                  );
                },
              ),
            ),
          ),
          // 商品信息
          Padding(
            padding: const EdgeInsets.all(8),
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Text(
                  goods['name']!,
                  style: const TextStyle(
                    fontSize: 14,
                    color: Color(0xFF333333),
                  ),
                  maxLines: 2,
                  overflow: TextOverflow.ellipsis,
                ),
                const SizedBox(height: 4),
                Text(
                  '¥${goods['price']}',
                  style: const TextStyle(
                    fontSize: 16,
                    fontWeight: FontWeight.bold,
                    color: Colors.orange,
                  ),
                ),
              ],
            ),
          ),
        ],
      ),
    );
  }

  /// 构建加载项
  Widget _buildLoadingItem() {
    return Container(
      decoration: BoxDecoration(
        color: Colors.grey[100],
        borderRadius: BorderRadius.circular(8),
      ),
      child: const Center(
        child: CircularProgressIndicator(strokeWidth: 2),
      ),
    );
  }

  /// 加载更多
  Future<void> _loadMore() async {
    if (_isLoading) return;

    setState(() {
      _isLoading = true;
    });

    // 模拟网络请求
    await Future.delayed(const Duration(seconds: 1));

    if (!mounted) return;

    setState(() {
      _isLoading = false;
    });
  }
}

五、首页页面组装

文件:lib/pages/home/index.dart

import 'package:flutter/material.dart';
import 'package:harmonyos_day_four/components/Home/HmCategory.dart';
import 'package:harmonyos_day_four/components/Home/HmHot.dart';
import 'package:harmonyos_day_four/components/Home/HmMoreList.dart';
import 'package:harmonyos_day_four/components/Home/HmSlider.dart';
import 'package:harmonyos_day_four/components/Home/HmSuggestion.dart';
import 'package:harmonyos_day_four/viewmodels/home.dart';
import 'package:harmonyos_day_four/api/home.dart';

/// 首页视图
class HomeView extends StatefulWidget {
  const HomeView({super.key});

  
  State<HomeView> createState() => _HomeViewState();
}

class _HomeViewState extends State<HomeView> {
  // 数据状态
  List<BannerItem> _bannerList = [];
  List<CategoryItem> _categoryList = [];
  SpecialOfferResult _specialOfferResult = SpecialOfferResult(
    id: '',
    title: '',
    subTypes: [],
  );

  // 加载状态
  bool _isLoading = true;

  
  void initState() {
    super.initState();
    _loadData();
  }

  /// 加载首页数据
  Future<void> _loadData() async {
    try {
      // 并行请求所有接口
      final results = await Future.wait([
        getBannerListAPI(),
        getCategoryListAPI(),
        getProductListAPI(),
      ]);

      if (!mounted) return;

      setState(() {
        _bannerList = results[0] as List<BannerItem>;
        _categoryList = results[1] as List<CategoryItem>;
        _specialOfferResult = results[2] as SpecialOfferResult;
        _isLoading = false;
      });
    } catch (e) {
      if (!mounted) return;
      setState(() {
        _isLoading = false;
      });
      // 显示错误提示
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text('数据加载失败: $e')),
      );
    }
  }

  /// 获取滚动容器内容
  List<Widget> _getScrollChildren() {
    return [
      // 轮播图
      SliverToBoxAdapter(
        child: _isLoading
            ? _buildLoadingPlaceholder()
            : HmSlider(bannerList: _bannerList),
      ),
      const SliverToBoxAdapter(child: SizedBox(height: 10)),

      // 分类
      SliverToBoxAdapter(
        child: _isLoading
            ? _buildLoadingPlaceholder(height: 100)
            : HmCategory(categoryList: _categoryList),
      ),
      const SliverToBoxAdapter(child: SizedBox(height: 10)),

      // 特惠推荐
      SliverToBoxAdapter(
        child: _isLoading
            ? const SizedBox.shrink()
            : HmSuggestion(specialOfferResult: _specialOfferResult),
      ),
      const SliverToBoxAdapter(child: SizedBox(height: 10)),

      // 爆款推荐(两个)
      SliverToBoxAdapter(
        child: Padding(
          padding: const EdgeInsets.symmetric(horizontal: 10),
          child: Row(
            children: [
              Expanded(child: HmHot()),
              const SizedBox(width: 10),
              Expanded(child: HmHot()),
            ],
          ),
        ),
      ),
      const SliverToBoxAdapter(child: SizedBox(height: 10)),

      // 商品列表
      const HmMorelist(),
    ];
  }

  /// 加载占位符
  Widget _buildLoadingPlaceholder({double height = 180}) {
    return SizedBox(
      height: height,
      child: Container(
        color: Colors.grey[100],
        child: const Center(
          child: CircularProgressIndicator(strokeWidth: 2),
        ),
      ),
    );
  }

  
  Widget build(BuildContext context) {
    return CustomScrollView(slivers: _getScrollChildren());
  }
}

六、底部导航栏实现

文件:lib/pages/main/index.dart

import 'package:flutter/material.dart';
import 'package:harmonyos_day_four/pages/home/index.dart';
import 'package:harmonyos_day_four/pages/category/index.dart';
import 'package:harmonyos_day_four/pages/cart/index.dart';
import 'package:harmonyos_day_four/pages/profile/index.dart';

/// 底部导航主框架
class MainContainer extends StatefulWidget {
  const MainContainer({super.key});

  
  State<MainContainer> createState() => _MainContainerState();
}

class _MainContainerState extends State<MainContainer> {
  // 当前选中的页面索引
  int _currentIndex = 0;

  // 页面列表(使用 const 优化性能)
  static const List<Widget> _pages = [
    HomeView(),
    CategoryView(),
    CartView(),
    ProfileView(),
  ];

  
  Widget build(BuildContext context) {
    return Scaffold(
      body: _pages[_currentIndex],
      bottomNavigationBar: BottomNavigationBar(
        currentIndex: _currentIndex,
        onTap: (index) {
          setState(() {
            _currentIndex = index;
          });
        },
        type: BottomNavigationBarType.fixed,
        selectedItemColor: Colors.orange,
        unselectedItemColor: Colors.grey,
        items: const [
          BottomNavigationBarItem(
            icon: Icon(Icons.home_outlined),
            activeIcon: Icon(Icons.home),
            label: '首页',
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.category_outlined),
            activeIcon: Icon(Icons.category),
            label: '分类',
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.shopping_cart_outlined),
            activeIcon: Icon(Icons.shopping_cart),
            label: '购物车',
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.person_outline),
            activeIcon: Icon(Icons.person),
            label: '我的',
          ),
        ],
      ),
    );
  }
}

七、常见问题与解决方案

问题 1:CustomScrollView 报错 “All children must be Sliver widgets”

错误信息:

FlutterError: All children of a CustomScrollView must be Sliver widgets.
A Container was found, but Slivers are expected.

原因分析:
CustomScrollViewslivers 参数只接受 Sliver 家族组件,不能直接放入普通 Widget。

解决方案:
使用 SliverToBoxAdapter 包裹普通组件:

// ❌ 错误写法
CustomScrollView(
  slivers: [
    Container(...),  // 报错
  ],
)

// ✅ 正确写法
CustomScrollView(
  slivers: [
    SliverToBoxAdapter(child: Container(...)),  // 正确
  ],
)

问题 2:ListView 横向滚动高度失效

问题描述:
ListView.scrollDirection 设置为 Axis.horizontal 后,容器高度变为 0。

原因分析:
ListView 在横向滚动时,无法自动推断高度。

解决方案:
使用 SizedBox 指定高度:

SizedBox(
  height: 100,  // 必须指定高度
  child: ListView.builder(
    scrollDirection: Axis.horizontal,
    itemBuilder: (context, index) => Container(...),
  ),
)

问题 3:底部导航栏图标不显示

问题描述:
超过 3 个 Tab 时,图标显示异常。

解决方案:
设置 type: BottomNavigationBarType.fixed

BottomNavigationBar(
  type: BottomNavigationBarType.fixed,  // 关键
  items: [...],
)

问题 4:轮播图自动播放内存泄漏

问题描述:
退出页面后 Timer 仍在运行。

解决方案:
dispose 中取消 Timer:


void dispose() {
  _timer?.cancel();  // 必须取消
  _pageController.dispose();
  super.dispose();
}

问题 5:网络图片加载失败显示空白

解决方案:
使用 errorBuilder 处理错误:

Image.network(
  url,
  errorBuilder: (context, error, stackTrace) {
    return Container(
      color: Colors.grey[200],
      child: const Icon(Icons.broken_image),
    );
  },
)

八、项目运行

运行步骤

  1. 启动项目

    cd harmonyos_day_four
    flutter run
    
  2. 选择设备

    • Android 模拟器/真机
    • iOS 模拟器/真机
    • HarmonyOS 设备
  3. 查看效果

    • 首页:完整的电商首页布局
    • 底部导航:4 个 Tab 可切换
    • 数据加载:真实的 API 数据

效果展示

页面 说明
首页 轮播图自动播放、分类横向滚动、特惠推荐、商品列表
分类 分类展示页面
购物车 购物车页面
我的 个人中心页面

九、总结

通过本文,我们实现了一个生产级的电商首页:

模块 技术要点
数据层 完整的数据模型 + API 封装
组件层 可复用的组件 + 数据驱动
页面层 MVVM 架构 + 状态管理
网络层 Dio 封装 + 错误处理

关键要点:

  • ✅ 使用 MVVM 架构分离业务逻辑
  • ✅ 组件化设计提高代码复用性
  • ✅ 数据驱动开发,组件接收数据参数
  • ✅ 完整的错误处理和加载状态
  • ✅ 生命周期管理,防止内存泄漏

项目源码:
https://atomgit.com/lbbxmx111/haromyos_day_four

欢迎交流:
如有疑问,欢迎评论区讨论!


Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐