【开源鸿蒙跨平台开发先锋训练营】DAY8~DAY13 底部选项卡&美食功能实现

【开源鸿蒙跨平台开发先锋训练营】DAY8~DAY13 底部选项卡&美食功能实现

目录

[【开源鸿蒙跨平台开发先锋训练营】DAY8~DAY13 底部选项卡&美食功能实现](#【开源鸿蒙跨平台开发先锋训练营】DAY8~DAY13 底部选项卡&美食功能实现)

[摘 要](#摘 要)

[1 概述](#1 概述)

[1.1 开发背景](#1.1 开发背景)

[1.2 开发目标](#1.2 开发目标)

[1.3 核心技术栈](#1.3 核心技术栈)

[2 开发环境准备](#2 开发环境准备)

[2.1 环境配置](#2.1 环境配置)

[2.2 pubspec.yaml 配置(依赖 + 资源)](#2.2 pubspec.yaml 配置(依赖 + 资源))

[2.3 项目目录结构](#2.3 项目目录结构)

[3 底部选项卡功能实现](#3 底部选项卡功能实现)

[3.1 底部选项卡设计](#3.1 底部选项卡设计)

[3.2 底部导航组件实现](#3.2 底部导航组件实现)

[3.3 项目入口配置](#3.3 项目入口配置)

[4 美食页面核心开发](#4 美食页面核心开发)

[4.1 美食主页面(Tab 容器)](#4.1 美食主页面(Tab 容器))

[4.1.1 功能说明](#4.1.1 功能说明)

[4.1.2 核心代码实现](#4.1.2 核心代码实现)

[4.2 清单页(FoodShowTab)](#4.2 清单页(FoodShowTab))

[4.2.1 功能说明](#4.2.1 功能说明)

[4.2.2 核心交互代码(下拉刷新 + 上拉加载)](#4.2.2 核心交互代码(下拉刷新 + 上拉加载))

[4.2.3 美食动态卡片组件(DynamicCard)](#4.2.3 美食动态卡片组件(DynamicCard))

[4.3 排行页(FoodStarTab)](#4.3 排行页(FoodStarTab))

[4.3.1 功能说明](#4.3.1 功能说明)

[4.3.2 核心代码实现(日榜 / 月榜切换)](#4.3.2 核心代码实现(日榜 / 月榜切换))

[4.3.3 达人排行卡片组件(StarCard)](#4.3.3 达人排行卡片组件(StarCard))

[4.4 任务中心页(TaskCenterTab)](#4.4 任务中心页(TaskCenterTab))

[4.4.1 功能说明](#4.4.1 功能说明)

[4.4.2 核心代码实现(静态布局)](#4.4.2 核心代码实现(静态布局))

[4.4.3 任务卡片组件(TaskCard)](#4.4.3 任务卡片组件(TaskCard))

[5 数据模型与接口模拟](#5 数据模型与接口模拟)

[5.1 美食卡片模型(dynamic_model.dart)](#5.1 美食卡片模型(dynamic_model.dart))

[5.2 排行达人模型(star_model.dart)](#5.2 排行达人模型(star_model.dart))

[5.3 任务模型(task_model.dart)](#5.3 任务模型(task_model.dart))

[6 运行验证与效果展示](#6 运行验证与效果展示)

[6.1 鸿蒙设备运行](#6.1 鸿蒙设备运行)

[6.2 功能测试效果](#6.2 功能测试效果)

[7 代码提交](#7 代码提交)

[8 总结与拓展](#8 总结与拓展)

[8.1 总结](#8.1 总结)

[8.2 拓展方向](#8.2 拓展方向)

[9 参考资料](#9 参考资料)


摘 要

本次开源鸿蒙跨平台开发先锋训练营DAY8~DAY13阶段,以Flutter为核心跨平台技术栈,基于开源鸿蒙设备适配要求,聚焦底部选项卡实现与美食模块核心功能开发。通过 Flutter 跨平台技术,在之前已经完成底部导航栏(首页 / 美食 / 动态 / 推荐 / 我的)搭建,本次将重点实现美食页面的清单、排行、任务中心三级Tab功能,包含清单页下拉刷新 + 上拉加载、排行页日榜 / 月榜切换、任务中心静态任务列表展示,同时完成项目组件化拆分与数据模型设计,为后续功能迭代奠定基础。

1 概述

1.1 开发背景

在鸿蒙跨平台开发体系中,Flutter凭借一套代码多端运行的优势,成为鸿蒙应用跨端开发的重要技术选型。本阶段承接前期基础开发,聚焦底部导航交互与美食核心业务模块,实现从页面框架搭建到业务功能落地的过渡,同时遵循组件化、模块化开发规范,提升代码可维护性。

1.2 开发目标

✅ 实现底部选项卡(首页、美食、动态、推荐、我的)的页面切换与状态管理;

✅ 完成美食页面清单、排行、任务中心三级Tab布局与静态展示,各Tab基础交互功能;

✅ 美食清单页:支持下拉刷新、上拉加载更多的动态数据加载与展示,静态展示美食动态卡片;

✅ 美食排行页:实现日榜 / 月榜切换,静态展示达人排行列表(含头像、浏览量、关联美食);

✅ 任务中心页:完成静态任务列表界面(上传头像、关注好友、收藏菜谱等),预留任务状态更新、奖励领取等功能拓展入口;

✅ 适配鸿蒙终端,保证跨平台 UI 一致性与交互流畅性。

1.3 核心技术栈

⚡ 1. 跨平台框架:Flutter(兼容OpenHarmony)

🔧 2. UI 组件:Flutter原生组件+鸿蒙兼容三方库(pull_to_refresh、infinite_scroll_pagination等)

📊 3. 数据处理:dio(网络请求)、json_annotation(数据解析)、shared_preferences(本地存储)

💻 4. 开发工具:VS Code、DevEco Studio 6.0.0 Release

2 开发环境准备

2.1 环境配置

🚀 1. Flutter环境:安装 Flutter 3.27.5版本,配置flutter-OHOS-1.0.1(鸿蒙适配分支)分支(兼容 OpenHarmony);

📱 2. 鸿蒙环境:安装DevEco Studio 6.0.0 Release,配置 OpenHarmony SDK(API Version 20(6.0.0.47)),创建鸿蒙模拟器;

📂 3. 项目初始化:基于现有flutter_harmonyos工程,调整目录结构,安装依赖。

2.2 pubspec.yaml 配置(依赖 + 资源)

复制代码
dependencies:
  flutter:
    sdk: flutter
  cupertino_icons: ^1.0.8
  http: ^1.6.0                         # 网络请求核心库(鸿蒙兼容)
  cached_network_image: ^3.4.1         # 图片缓存(解决鸿蒙设备图片重复加载卡顿)
  pull_to_refresh: ^2.0.0              # 下拉刷新(适配鸿蒙触控交互)
  flutter_rating_bar: ^4.0.1           # 美食评分组件(鸿蒙UI适配)
  dio: 5.0.0                           # 网络请求封装
  json_annotation: ^4.9.0              # 数据解析
  infinite_scroll_pagination: 4.0.0    # 专注上拉分页(适配鸿蒙异步逻辑)
  easy_refresh: ^3.4.0                 # 轻量化刷新(适配鸿蒙触控交互)
  carousel_slider: ^5.1.1              # 轮播图组件
  shared_preferences: ^2.5.3           # 用于本地存储历史记录

dev_dependencies:
  flutter_test:
    sdk: flutter
  flutter_lints: ^5.0.0
  json_serializable: ^6.9.5
  build_runner: ^2.4.15



flutter:
  uses-material-design: true

  assets:
    - assets/images/
    - assets/images/foods/               # 美食卡片图片
    - assets/images/food_show/           # 美食页相关图片
    - assets/images/food_show/dynamic/   # 清单页动态图
    - assets/images/food_show/star/      # 排行页达人头像
    - assets/images/food_show/task/      # 任务中心图标

2.3 项目目录结构

复制代码
lib/
├── core/                # 核心工具(网络、响应式)
├── models/              # 数据模型
│   ├── dynamic_model.dart  # 清单页美食卡片模型
│   ├── star_model.dart     # 排行页达人模型
│   └── task_model.dart     # 任务中心模型
├── pages/               # 页面
│   ├── food_show/        # 美食页面主模块
│   │   ├── components/   # 美食页子组件
│   │   │   ├── dynamic_card.dart # 清单页美食卡片
│   │   │   ├── star_card.dart    # 排行页达人卡片
│   │   │   └── task_card.dart    # 任务中心卡片
│   │   ├── food_list_tab.dart    # 清单页
│   │   ├── food_rank_tab.dart    # 排行页
│   │   ├── task_center_tab.dart  # 任务中心页
│   │   └── food_show_page.dart   # 美食页主页面
│   ├── home/             # 首页
│   ├── mine/             # 我的页
│   └── ...              # 其他底部选项卡页面
├── utils/               # 工具类(日期、格式化)
└── main.dart            # 项目入口

3 底部选项卡功能实现

3.1 底部选项卡设计

✅ 1. 选项卡数量:5 个(首页、美食、动态、推荐、我的),覆盖核心服务场景;

✅ 2. 交互状态:默认状态(灰色图标 + 文字)、选中状态(蓝色高亮图标 + 文字),视觉区分明确;

✅ 3. 切换逻辑:平滑切换,保留各页面状态(如列表滚动位置、输入框内容),避免重复请求数据。

3.2 底部导航组件实现

底部导航组件实现(lib/components/bottom_nav_bar.dart)。

复制代码
// components/              # 公共组件
// 底部导航栏:实现底部导航栏 bottom_nav_bar.dart

import 'package:flutter/material.dart';
// 1. 修复导入路径:去掉空格,用下划线,确保文件名匹配
import '../pages/home/home_page.dart';
import '../pages/food_show/food_show_page.dart';
import '../pages/food_note/food_note_page.dart';
import '../pages/eat_what/eat_what_page.dart';
import '../pages/mine/mine_page.dart';
// import 'package:flutter_harmonyos/models/food_model.dart';

class BottomNavBar extends StatefulWidget {
  const BottomNavBar({super.key});

  @override
  State<BottomNavBar> createState() => _BottomNavBarState();
}

class _BottomNavBarState extends State<BottomNavBar> {
  int _currentIndex = 0; // 默认选中首页
  // 2. 去掉const,避免StatefulWidget构造函数不匹配
  final List<Widget> _pages = [
    const HomePage(), // 如果HomePage构造函数是const,加const;否则去掉
    const FoodShowPage(), // 同理
    const FoodNotePage(),
    const EatWhatPage(),
    const MinePage(),
  ];
  // 3. 优化标签文字:标题+图标+样式全优化
  final List<BottomNavigationBarItem> _navItems = const [
    BottomNavigationBarItem(icon: Icon(Icons.home), label: '首页'),         // 🏠 房子图标
    BottomNavigationBarItem(icon: Icon(Icons.food_bank), label: '美食'),    // 🍽️ 食物图标
    BottomNavigationBarItem(icon: Icon(Icons.note_alt), label: '动态'),     // 📝 笔记图标  美食笔记
    BottomNavigationBarItem(icon: Icon(Icons.restaurant), label: '推荐'),   // 🍴 餐厅图标
    BottomNavigationBarItem(icon: Icon(Icons.person), label: '我的'),       // 👤 人物图标
  ];
  void _onTabTapped(int index) {
    setState(() => _currentIndex = index);
  }
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: _pages[_currentIndex],
      bottomNavigationBar: BottomNavigationBar(
        items: _navItems,
        currentIndex: _currentIndex,
        onTap: _onTabTapped,
        type: BottomNavigationBarType.fixed,
        selectedItemColor: Colors.blue,
        unselectedItemColor: Colors.grey,
      ),
    );
  }
}

3.3 项目入口配置

项目入口配置(lib/main.dart)

将底部导航作为应用根页面,替换原有入口:

复制代码
// 项目入口(重构)
import 'package:flutter/material.dart';
import 'components/bottom_nav_bar.dart';
void main() {
  runApp(const MyApp());
}

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: '本地美食清单',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        // visualDensity 是可选的适配优化,保留/删除都可以,不影响核心功能
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: const BottomNavBar(), // 底部导航作为根页面
      debugShowCheckedModeBanner: false,
    );
  }
}

4 美食页面核心开发

美食页面是项目核心模块之一,美食页(FoodShowPage)通过 TabController 实现顶部:清单、排行、任务中心 三级 Tab 切换,对应三个子页面。

4.1 美食主页面(Tab 容器)

4.1.1 功能说明

作为美食模块的根页面,核心功能是:

✅ 初始化 TabController 实现 3 个子页面(清单 / 排行 / 任务中心)的切换;

✅ 渲染顶部 TabBar 导航栏,定义选中 / 未选中样式;

✅ 挂载 3 个子页面容器(TabBarView),实现 "点击 Tab 切换页面" 的核心联动逻辑。

4.1.2 核心代码实现

复制代码
// 美食主页面:lib/pages/food_show/food_show_page.dart
// 作用:作为美食模块根页面,实现顶部 TabBar + TabBarView,切换 3 个子页面。

import 'package:flutter/material.dart';
import 'tab/food_show_tab.dart';
import 'tab/food_star_tab.dart';
import 'tab/task_center_tab.dart';

class FoodShowPage extends StatefulWidget {
  const FoodShowPage({super.key});
  @override
  State<FoodShowPage> createState() => _FoodShowPageState();
}

class _FoodShowPageState extends State<FoodShowPage> with SingleTickerProviderStateMixin {
  late TabController _tabController;
  // 初始化Tab控制器(绑定3个Tab)
  @override
  void initState() {
    super.initState();
    _tabController = TabController(length: 3, vsync: this); // 3个Tab
  }
  // 销毁控制器,避免内存泄漏 
  @override
  void dispose() {
    _tabController.dispose();
    super.dispose();
  }
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('美食'),
        bottom: TabBar(    // 顶部TabBar导航栏
          controller: _tabController,
          tabs: const [
            Tab(text: '清单'),
            Tab(text: '排行'),
            Tab(text: '任务中心'),
          ],
          indicatorColor: Colors.red, // 选中指示器颜色
          labelColor: Colors.red,      // 选中文字颜色
          unselectedLabelColor: Colors.grey, // 未选中文字颜色
        ),
      ),
      // 子页面容器(与TabBar联动)
      body: TabBarView(
        controller: _tabController,
        children: const [
          FoodShowTab(),   // 美食-清单页
          FoodStarTab(),   // 美食-排行页
          TaskCenterTab(), // 美食-任务中心页
        ],
      ),
    );
  }
}

4.2 清单页(FoodShowTab)

4.2.1 功能说明

清单页是美食模块核心展示页,核心功能:

✅ 模拟网络请求加载美食动态列表数据;

✅ 实现下拉刷新、上拉加载更多的交互逻辑;

✅ 通过 DynamicCard 组件渲染美食动态卡片(图片、标题、作者、时间)。

4.2.2 核心交互代码(下拉刷新 + 上拉加载)

复制代码
// 美食 - 动态列表页:food_show_tab.dart
// 作用:展示美食动态列表,支持下拉刷新、上拉加载,用dynamic_card组件渲染。

import 'package:flutter/material.dart';
import 'package:pull_to_refresh/pull_to_refresh.dart';
import '../components/dynamic_card.dart';
import '../models/dynamic_model.dart';
import 'package:flutter_harmonyos/api/food_show_api.dart';
import '../../../components/loading_widget.dart';
import '../../../components/empty_widget.dart';

class FoodShowTab extends StatefulWidget {
  const FoodShowTab({super.key});

  @override
  State<FoodShowTab> createState() => _FoodShowTabState();
}

class _FoodShowTabState extends State<FoodShowTab> {
  final RefreshController _refreshController = RefreshController();
  List<DynamicModel> _dynamicList = [];
  bool _isLoading = true;      // 加载状态
  int _page = 1;     // 当前页码
  // int _page = 1;          // 当前页码
  // int _pageSize = 6;      // 每页条数
  // List<DynamicModel> _dynamicList = []; // 动态列表数据
  // bool _isLoading = false; // 加载状态

  @override
  void initState() {
    super.initState();
    _loadDynamicData();
  }

  // 下拉刷新
  Future<void> _onRefresh() async {
    _page = 1;
    await _loadDynamicData();
    _refreshController.refreshCompleted();
  }

  // 上拉加载
  Future<void> _onLoading() async {
    _page++;
    await _loadDynamicData();
    _refreshController.loadComplete();
  }

  // 加载动态数据
  Future<void> _loadDynamicData() async {
    setState(() => _isLoading = true);
    try {
      final data = await FoodShowApi.getDynamicList(page: _page, pageSize: 10);
      setState(() {
        if (_page == 1) _dynamicList = data;
        else _dynamicList.addAll(data);
        _isLoading = false;
      });
    } catch (e) {
      setState(() => _isLoading = false);
      ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('加载失败:$e')));
    }
  }

  @override
  Widget build(BuildContext context) {
    return _isLoading
        ? const LoadingWidget()
        : _dynamicList.isEmpty
            ? const EmptyWidget(text: '暂无美食动态')
            : SmartRefresher(
                controller: _refreshController,
                onRefresh: _onRefresh,
                onLoading: _onLoading,
                enablePullUp: true,
                child: GridView.builder(
                  padding: const EdgeInsets.all(12),
                  gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
                    crossAxisCount: 2,
                    crossAxisSpacing: 12,
                    mainAxisSpacing: 12,
                    childAspectRatio: 0.8,
                  ),
                  itemCount: _dynamicList.length,
                  itemBuilder: (_, index) => DynamicCard(dynamic: _dynamicList[index]),
                ),
              );
  }
}

4.2.3 美食动态卡片组件(DynamicCard)

复制代码
// dynamic_card.dart      # 动态卡片(美食列表项)
// 动态卡片:dynamic_card.dart
// 作用:渲染美食动态列表项(图片、标题、作者、时间)。

import 'package:flutter/material.dart';
import '../models/dynamic_model.dart';
import '../../../utils/date_util.dart';

class DynamicCard extends StatelessWidget {
  final DynamicModel dynamic;

  const DynamicCard({super.key, required this.dynamic});

  @override
  Widget build(BuildContext context) {
    return Container(
      decoration: BoxDecoration(
        color: Colors.white,
        borderRadius: BorderRadius.circular(12),
        boxShadow: [BoxShadow(color: Colors.grey[200]!, blurRadius: 4, offset: const Offset(0, 2))],
      ),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          // 动态图片:Image.network → Image.asset
          ClipRRect(
            borderRadius: const BorderRadius.vertical(top: Radius.circular(12)),
            child: Image.asset( // 改为本地图片加载
              dynamic.imageUrl,
              height: 120,
              width: double.infinity,
              fit: BoxFit.cover,
              // 可选:添加错误占位图,避免本地图片缺失报错
              errorBuilder: (_, __, ___) => const Icon(Icons.broken_image, color: Colors.grey, size: 40),
            ),
          ),
          // 动态信息(不变)
          Padding(
            padding: const EdgeInsets.all(8),
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Text(dynamic.title, style: const TextStyle(fontSize: 14, fontWeight: FontWeight.bold), maxLines: 1, overflow: TextOverflow.ellipsis),
                const SizedBox(height: 4),
                Row(
                  children: [
                    Text(dynamic.author, style: const TextStyle(fontSize: 12, color: Colors.grey)),
                    const Spacer(),
                    Text(DateUtil.formatDate(dynamic.time), style: const TextStyle(fontSize: 12, color: Colors.grey)),
                  ],
                ),
              ],
            ),
          ),
        ],
      ),
    );
  }
}

实现效果:

4.3 排行页(FoodStarTab)

4.3.1 功能说明

排行页面聚焦美食达人展示,核心功能:

✅ 模拟加载达人排行榜数据(支持日榜 / 月榜切换);

✅ 通过 StarCard 组件渲染达人卡片(头像、名称、浏览量、作品图)。

4.3.2 核心代码实现(日榜 / 月榜切换)

复制代码
// 美食 - 达人排行榜页:food_star_tab.dart
// 作用:展示美食达人日榜 / 月榜,用star_card组件渲染。

import 'package:flutter/material.dart';
import '../components/star_card.dart';
import '../models/star_model.dart';
import 'package:flutter_harmonyos/api/food_show_api.dart';
import '../../../components/loading_widget.dart';
import '../../../components/empty_widget.dart';

class FoodStarTab extends StatefulWidget {
  const FoodStarTab({super.key});

  @override
  State<FoodStarTab> createState() => _FoodStarTabState();
}

class _FoodStarTabState extends State<FoodStarTab> {
  bool _isLoading = true;
  List<StarModel> _starList = [];
  String _currentRank = '日榜'; // 默认日榜

  @override
  void initState() {
    super.initState();
    _loadStarData();
  }

  // 加载达人榜单数据
  Future<void> _loadStarData() async {
    setState(() => _isLoading = true);
    try {
      final data = await FoodShowApi.getStarList(rankType: _currentRank);
      setState(() {
        _starList = data;
        _isLoading = false;
      });
    } catch (e) {
      setState(() => _isLoading = false);
      ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('加载失败:$e')));
    }
  }

  // 切换日榜/月榜
  void _changeRank(String rankType) {
    setState(() => _currentRank = rankType);
    _loadStarData();
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        // 日榜/月榜切换
        Padding(
          padding: const EdgeInsets.all(12),
          child: Row(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              ElevatedButton(
                onPressed: () => _changeRank('日榜'),
                style: ElevatedButton.styleFrom(
                  backgroundColor: _currentRank == '日榜' ? Colors.red : Colors.grey[200],
                ),
                child: Text('日榜', style: TextStyle(color: _currentRank == '日榜' ? Colors.white : Colors.grey)),
              ),
              const SizedBox(width: 20),
              ElevatedButton(
                onPressed: () => _changeRank('月榜'),
                style: ElevatedButton.styleFrom(
                  backgroundColor: _currentRank == '月榜' ? Colors.red : Colors.grey[200],
                ),
                child: Text('月榜', style: TextStyle(color: _currentRank == '月榜' ? Colors.white : Colors.grey)),
              ),
            ],
          ),
        ),
        // 达人列表
        Expanded(
          child: _isLoading
              ? const LoadingWidget()
              : _starList.isEmpty
                  ? const EmptyWidget(text: '暂无达人数据')
                  : ListView.builder(
                      padding: const EdgeInsets.all(12),
                      itemCount: _starList.length,
                      itemBuilder: (_, index) => StarCard(star: _starList[index]),
                    ),
        ),
      ],
    );
  }
}

4.3.3 达人排行卡片组件(StarCard)

复制代码
// star_card.dart         # 达人卡片(排行榜项)
// 达人卡片:star_card.dart
// 作用:渲染达人排行榜项(头像、名称、排名、作品)。
import 'package:flutter/material.dart';
import '../models/star_model.dart';

class StarCard extends StatelessWidget {
  final StarModel star;

  const StarCard({super.key, required this.star});

  @override
  Widget build(BuildContext context) {
    return Container(
      padding: const EdgeInsets.all(8),
      decoration: BoxDecoration(
        color: Colors.white,
        borderRadius: BorderRadius.circular(12),
        boxShadow: [BoxShadow(color: Colors.grey[200]!, blurRadius: 4, offset: const Offset(0, 2))],
      ),
      child: Row(
        children: [
          // 达人头像:NetworkImage → AssetImage
          CircleAvatar(
            backgroundImage: AssetImage(star.avatar), // 改为本地头像
            radius: 20,
            // 错误占位
            onBackgroundImageError: (_, __) => const Icon(Icons.person, color: Colors.grey),
          ),
          const SizedBox(width: 12),
          Expanded(
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Text(star.name, style: const TextStyle(fontSize: 14, fontWeight: FontWeight.bold)),
                const SizedBox(height: 4),
                Text('浏览 ${star.viewCount}k', style: const TextStyle(fontSize: 12, color: Colors.grey)),
              ],
            ),
          ),
          // 达人作品图:Image.network → Image.asset
          Image.asset( // 改为本地作品图
            star.worksImage,
            height: 60,
            width: 60,
            fit: BoxFit.cover,
            errorBuilder: (_, __, ___) => const Icon(Icons.broken_image, color: Colors.grey),
          ),
        ],
      ),
    );
  }
}

实现效果:

4.4 任务中心页(TaskCenterTab)

4.4.1 功能说明

任务中心页聚焦用户成长任务展示,核心功能:

✅ 模拟加载任务列表数据(上传头像、关注好友、收藏菜谱等);

✅ 通过 TaskCard 组件渲染任务卡片(标题、描述、奖励、状态);

✅ 完成静态布局搭建,预留任务状态更新、奖励领取等拓展入口。

4.4.2 核心代码实现(静态布局)

复制代码
// task_center_tab.dart   # 美食-任务中心页
// 作用:展示任务列表,用task_card组件渲染,处理任务状态。

import 'package:flutter/material.dart';
import '../components/task_card.dart';
import '../models/task_model.dart';
import 'package:flutter_harmonyos/api/food_show_api.dart';
import '../../../components/loading_widget.dart';
import '../../../components/empty_widget.dart';

class TaskCenterTab extends StatefulWidget {
  const TaskCenterTab({super.key});

  @override
  State<TaskCenterTab> createState() => _TaskCenterTabState();
}

class _TaskCenterTabState extends State<TaskCenterTab> {
  bool _isLoading = true;
  List<TaskModel> _taskList = [];

  @override
  void initState() {
    super.initState();
    _loadTaskData();
  }

  // 加载任务数据
  Future<void> _loadTaskData() async {
    setState(() => _isLoading = true);
    try {
      final data = await FoodShowApi.getTaskList();
      setState(() {
        _taskList = data;
        _isLoading = false;
      });
    } catch (e) {
      setState(() => _isLoading = false);
      ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('加载失败:$e')));
    }
  }

  @override
  Widget build(BuildContext context) {
    return _isLoading
        ? const LoadingWidget()
        : _taskList.isEmpty
            ? const EmptyWidget(text: '暂无任务')
            : ListView.builder(
                padding: const EdgeInsets.all(12),
                itemCount: _taskList.length,
                itemBuilder: (_, index) => TaskCard(task: _taskList[index]),
              );
  }
}

4.4.3 任务卡片组件(TaskCard)

复制代码
// task_card.dart         # 任务卡片(任务中心项)
// 作用:渲染任务中心项(任务标题、奖励、状态、描述)。
import 'package:flutter/material.dart';
import '../models/task_model.dart';

class TaskCard extends StatelessWidget {
  final TaskModel task;

  const TaskCard({super.key, required this.task});

  @override
  Widget build(BuildContext context) {
    return Container(
      padding: const EdgeInsets.all(8),
      decoration: BoxDecoration(
        color: Colors.white,
        borderRadius: BorderRadius.circular(12),
        boxShadow: [BoxShadow(color: Colors.grey[200]!, blurRadius: 4, offset: const Offset(0, 2))],
      ),
      child: Row(
        children: [
          // 任务图:Image.network → Image.asset
          Image.asset( // 改为本地任务图
            task.imageUrl,
            height: 80,
            width: 80,
            fit: BoxFit.cover,
            errorBuilder: (_, __, ___) => const Icon(Icons.broken_image, color: Colors.grey),
          ),
          const SizedBox(width: 12),
          // 任务信息
          Expanded(
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Text(task.title, style: const TextStyle(fontSize: 14, fontWeight: FontWeight.bold)),
                const SizedBox(height: 4),
                Text(task.desc, style: const TextStyle(fontSize: 12, color: Colors.grey), maxLines: 2, overflow: TextOverflow.ellipsis),
                const SizedBox(height: 4),
                Text('奖励 ${task.reward}', style: const TextStyle(fontSize: 12, color: Colors.orange)),
              ],
            ),
          ),
          // 任务状态(如"进行中")
          Container(
            padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
            decoration: BoxDecoration(
              color: Colors.orange[100],
              borderRadius: BorderRadius.circular(4),
            ),
            child: Text(task.status, style: const TextStyle(fontSize: 12, color: Colors.orange)),
          ),
        ],
      ),
    );
  }
}

实现效果:

5 数据模型与接口模拟

5.1 美食卡片模型(dynamic_model.dart)

复制代码
// 数据模型(lib/pages/food_show/models/ 下)
// 动态模型:dynamic_model.dart

class DynamicModel {
  final String id;
  final String title;
  final String imageUrl;
  final String author;
  final DateTime time; // 时间戳

  DynamicModel({
    required this.id,
    required this.title,
    required this.imageUrl,
    required this.author,
    required this.time,
  });
}

5.2 排行达人模型(star_model.dart)

复制代码
// 达人模型:star_model.dart

class StarModel {
  final String id;
  final String name;
  final String avatar;
  final int rank;
  final int viewCount;
  final String worksImage;

  StarModel({
    required this.id,
    required this.name,
    required this.avatar,
    required this.rank,
    required this.viewCount,
    required this.worksImage,
  });
}

5.3 任务模型(task_model.dart)

复制代码
// 任务模型:task_model.dart

class TaskModel {
  final String id;
  final String title;
  final String desc;
  final int reward;
  final String status; // 进行中/未领取
  final String imageUrl;

  TaskModel({
    required this.id,
    required this.title,
    required this.desc,
    required this.reward,
    required this.status,
    required this.imageUrl,
  });
}

6 运行验证与效果展示

6.1 鸿蒙设备运行

🔧 模拟器运行:

打开 DevEco Studio,启动鸿蒙OpenHarmony SDK(API Version 20(6.0.0.47))、模拟器,点击右上角运行按钮;

或者通过VS Code / DevEco Studio 终端执行 flutter run 命令运行模拟器;

💻 验证底部选项卡切换、美食页 Tab 切换、清单页刷新 / 加载、排行页切换、任务中心界面展示。

6.2 功能测试效果

✅ 底部选项卡:5 个选项卡切换流畅,页面状态保留;

✅ 美食页 - 清单:下拉刷新重置数据,上拉加载更多卡片,图片加载正常;

✅ 美食页 - 排行:日榜 / 月榜切换生效,达人排名展示完整;

✅ 美食页 - 任务中心:静态任务列表展示,样式匹配设计稿;

✅ 兼容性:鸿蒙设备界面适配正常,无布局错乱。

美食页 - 清单:

美食页 - 排行:日榜 / 月榜

美食页 - 任务中心:

7 代码提交

  1. 提交粒度:按功能模块拆分提交(如feat: 底部选项卡实现、feat: 首页轮播+搜索栏开发、fix: 页面状态保留优化);

  2. Commit Message 规范:

⚡ feat: 新增功能(如底部选项卡、首页+美食组件)

🔧 fix: 修复bug(如页面重复加载、布局错乱)

📊 docs: 文档更新(如 README、注释)

💻 refactor: 代码重构(如目录结构优化、组件复用,没有新增功能或修复Bug)

  1. 提交步骤

所需全部命令:

复制代码
# 1. 查看修改
git status
# 2. 添加修改文件
git add .
# 3. 提交(规范Commit Message)
git commit -m "feat: 完成底部选项卡+美食功能实现"
# 4. 推送到AtomGit远程仓库
git push origin main

完成后可打开 AtomGit 仓库页面(在 AtomGit 创建的个人公开仓库),刷新页面:

确认仓库的"提交记录"中出现刚写的commit message;

刷新后的页面也能看到最新的提交记录和更新时间。

  1. 仓库要求:确保工程包含pubspec.yamllib/源码、assets/资源、调试日志,可直接拉取复现运行效果。

8 总结与拓展

8.1 总结

已实现功能总结:

✅ 实现底部选项卡(首页、美食、动态、推荐、我的)的页面切换与状态管理;

✅ 完成美食页面清单、排行、任务中心三级Tab布局与静态展示,各Tab基础交互功能;

✅ 美食清单页:支持下拉刷新、上拉加载更多的动态数据加载与展示,静态展示美食动态卡片;

✅ 美食排行页:实现日榜 / 月榜切换,静态展示达人排行列表(含头像、浏览量、关联美食);

✅ 任务中心页:完成静态任务列表界面(上传头像、关注好友、收藏菜谱等),预留任务状态更新、奖励领取等功能拓展入口。

8.2 拓展方向

说明:当前项目已完成首页、美食模块核心功能与其他选项卡基础框架,后续将继续完善其他页面功能、补充完整模块与异常处理,持续优化项目体验。

后续将继续完善项目:

🔧 完善其他选项卡页面功能(动态、推荐、我的页面);

📊 补充完整功能模块与异常处理,优化整体体验。

9 参考资料

参考前文:

https://blog.csdn.net/m0_74451345/article/details/156915775?spm=1001.2014.3001.5501

https://blog.csdn.net/m0_74451345/article/details/157024056?spm=1001.2014.3001.5501

https://blog.csdn.net/m0_74451345/article/details/157032531?spm=1001.2014.3001.5501

https://blog.csdn.net/m0_74451345/article/details/157464927?spm=1001.2014.3001.5501

参考三方库:

OpenHarmony兼容的三方库:https://gitcode.com/openharmony-tpc/flutter_packages

博客说明:本文为训练营实战记录,代码可在我的AtomGit个人公开仓库克隆到本地配置后直接运行(部分资源需要本地配置,比如需替换本地图片资源),后续将持续更新任务中心交互、其他底部选项卡模块等功能,文中也有许多待优化点,欢迎大家关注交流~

最后,

欢迎加入开源鸿蒙跨平台社区:

https://openharmonycrossplatform.csdn.net

相关推荐
子春一2 小时前
Flutter for OpenHarmony:构建一个沉浸式 Flutter 掷骰子游戏,深入解析动画控制器、CustomPaint 自定义绘制与状态同步
flutter·游戏
摘星编程2 小时前
React Native鸿蒙:自定义useTheme主题切换
react native·react.js·harmonyos
Xxtaoaooo2 小时前
React Native 跨平台鸿蒙开发实战:调试与真机测试全流程
android·react native·harmonyos
哈__2 小时前
ReactNative for Harmony 项目鸿蒙化三方库集成实战:react-native-linear-gradient
react native·react.js·harmonyos
摘星编程2 小时前
React Native鸿蒙版:自定义useCurrency货币格式化
react native·react.js·harmonyos
熊猫钓鱼>_>5 小时前
【开源鸿蒙跨平台开发先锋训练营】Day 7:开源鸿蒙开发第一阶段复盘与技术深度总结
react native·华为·开源·harmonyos·arkts·openharmony·rnoh
程序员清洒10 小时前
Flutter for OpenHarmony:GridView — 网格布局实现
android·前端·学习·flutter·华为
嘴贱欠吻!10 小时前
Flutter鸿蒙开发指南(七):轮播图搜索框和导航栏
算法·flutter·图搜索算法
Miguo94well10 小时前
Flutter框架跨平台鸿蒙开发——地理知识速记APP的开发流程
flutter·华为·harmonyos·鸿蒙