【开源鸿蒙跨平台开发先锋训练营】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 代码提交
-
提交粒度:按功能模块拆分提交(如feat: 底部选项卡实现、feat: 首页轮播+搜索栏开发、fix: 页面状态保留优化);
-
Commit Message 规范:
⚡ feat: 新增功能(如底部选项卡、首页+美食组件)
🔧 fix: 修复bug(如页面重复加载、布局错乱)
📊 docs: 文档更新(如 README、注释)
💻 refactor: 代码重构(如目录结构优化、组件复用,没有新增功能或修复Bug)
- 提交步骤
所需全部命令:
# 1. 查看修改
git status
# 2. 添加修改文件
git add .
# 3. 提交(规范Commit Message)
git commit -m "feat: 完成底部选项卡+美食功能实现"
# 4. 推送到AtomGit远程仓库
git push origin main




完成后可打开 AtomGit 仓库页面(在 AtomGit 创建的个人公开仓库),刷新页面:
确认仓库的"提交记录"中出现刚写的commit message;
刷新后的页面也能看到最新的提交记录和更新时间。

- 仓库要求:确保工程包含
pubspec.yaml、lib/源码、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个人公开仓库克隆到本地配置后直接运行(部分资源需要本地配置,比如需替换本地图片资源),后续将持续更新任务中心交互、其他底部选项卡模块等功能,文中也有许多待优化点,欢迎大家关注交流~
最后,
欢迎加入开源鸿蒙跨平台社区: