Flutter for OpenHarmony 家具购买记录App实战:首页仪表盘实现
家具管理App首页开发摘要 本文介绍了基于Flutter for OpenHarmony开发的家具管理App首页开发过程。该App旨在帮助用户记录家具购买信息、保修期限等数据。首页采用StatelessWidget构建,包含数据统计卡片、快捷操作入口、近期购买记录和保修提醒等模块。设计上选用暖色调(米白背景+棕色导航栏)营造家居氛围,使用flutter_screenutil进行屏幕适配,get管理

最近在做一个家具购买记录的App,主要是想把家里买的家具都记录下来,方便查看保修期、购买价格这些信息。今天先从首页仪表盘开始,这个页面算是整个App的门面,需要展示一些关键数据和常用入口。
做这个App的初衷其实很简单,家里陆陆续续买了不少家具,有的是网上买的,有的是实体店买的,时间一长就记不清哪个家具是什么时候买的、保修期到什么时候。之前试过用备忘录记,但是太乱了,所以干脆自己写一个专门的App来管理。
开发环境说明
这个项目是基于 Flutter for OpenHarmony 开发的,可以同时运行在鸿蒙设备和其他平台上。用到的主要依赖包括:
- flutter_screenutil:屏幕适配,让UI在不同尺寸设备上都能正常显示
- get:路由管理和状态管理,用起来比较轻量
- convex_bottom_bar:底部导航栏组件,样式比较好看
这些依赖都是经过鸿蒙适配的版本,可以直接在 OpenHarmony 设备上运行。
整体页面结构
首页仪表盘我用的是 StatelessWidget,因为这个页面主要是展示数据,暂时不需要复杂的状态管理。页面整体采用 SingleChildScrollView 包裹,这样内容多了也能滚动。
class DashboardView extends StatelessWidget {
const DashboardView({super.key});
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: const Color(0xFFFAF8F5),
appBar: AppBar(
title: const Text('家具购买记录'),
backgroundColor: const Color(0xFF8B4513),
foregroundColor: Colors.white,
actions: [
IconButton(icon: const Icon(Icons.search), onPressed: () => Get.toNamed(AppRoutes.search)),
IconButton(icon: const Icon(Icons.notifications_outlined), onPressed: () => Get.toNamed(AppRoutes.reminderList)),
],
),
这里背景色用了 0xFFFAF8F5,是一种很淡的米白色,看起来比较温馨,符合家居类App的调性。选这个颜色是因为纯白色看久了眼睛会累,而这种带一点暖色调的白色会舒服很多。
AppBar 的颜色选了 0xFF8B4513,这是一种棕色,和家具的木质感比较搭。其实一开始试过蓝色和绿色,但总觉得和家具主题不太协调,最后还是选了这个棕色系。
右上角放了搜索和通知两个按钮,用 GetX 的路由跳转。搜索功能可以快速找到某个家具,通知按钮点进去是提醒列表,会显示保修即将到期之类的提醒。
页面主体布局
页面主体用 Column 垂直排列各个模块,每个模块之间用 SizedBox 隔开。这里用了 flutter_screenutil 做屏幕适配,.w 和 .h 后缀会根据设计稿自动计算实际尺寸。
body: SingleChildScrollView(
padding: EdgeInsets.all(16.w),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildStatsCard(),
SizedBox(height: 20.h),
_buildQuickActions(),
SizedBox(height: 20.h),
_buildRecentPurchases(),
SizedBox(height: 20.h),
_buildWarrantyReminders(),
],
),
),
crossAxisAlignment: CrossAxisAlignment.start 让子组件都靠左对齐,这样标题文字不会居中显示。整体 padding 设置为 16,给内容留出呼吸空间,不会显得太拥挤。
为什么用 SingleChildScrollView 而不是 ListView?因为这个页面的内容是固定的几个模块,不是动态列表,用 SingleChildScrollView 更合适。如果内容超出屏幕高度,用户可以滚动查看。
数据统计卡片
首页最显眼的就是顶部的统计卡片,用渐变背景让它更有层次感。这个卡片展示三个核心数据:家具总数、房间数、总花费。
Widget _buildStatsCard() {
return Container(
padding: EdgeInsets.all(20.w),
decoration: BoxDecoration(
gradient: const LinearGradient(colors: [Color(0xFF8B4513), Color(0xFFA0522D)]),
borderRadius: BorderRadius.circular(16.r),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
_buildStatItem('家具总数', '48', Icons.chair),
_buildStatItem('房间数', '6', Icons.room),
_buildStatItem('总花费', '¥86,500', Icons.payment),
],
),
);
}
LinearGradient 从深棕色渐变到浅棕色,视觉上更有质感。如果只用单一颜色,看起来会比较平,加上渐变之后立体感就出来了。
borderRadius 用 .r 后缀做圆角适配,16 的圆角大小刚好,不会太圆也不会太方。圆角太大会显得幼稚,太小又没有现代感,16是个比较平衡的数值。
三个统计项用 Row 横向排列,spaceAround 让它们均匀分布。这样不管屏幕多宽,三个数据都能保持合适的间距。
单个统计项组件
每个统计项是一个小的 Column,从上到下依次是图标、数值、标签。把它抽成独立方法,代码更清晰,也方便复用。
Widget _buildStatItem(String label, String value, IconData icon) {
return Column(
children: [
Icon(icon, color: Colors.white70, size: 26.sp),
SizedBox(height: 8.h),
Text(value, style: TextStyle(color: Colors.white, fontSize: 18.sp, fontWeight: FontWeight.bold)),
Text(label, style: TextStyle(color: Colors.white70, fontSize: 12.sp)),
],
);
}
图标用 Colors.white70 稍微透明一点,这样不会太抢眼。如果图标和数值都是纯白色,视觉上会有点乱,分不清主次。
数值用白色加粗显示,是整个卡片的视觉焦点。用户一眼看过去,最先注意到的应该是这些数字。
标签字号小一些,颜色也淡一些,形成主次分明的层次。这种处理方式在很多App里都能看到,是比较成熟的设计模式。
快捷操作区域
快捷操作是用户最常用的几个功能入口,我放了添加家具、购买记录、日历、收藏夹四个。用一个 List 存储配置数据,然后 map 遍历生成按钮。
Widget _buildQuickActions() {
final actions = [
{'icon': Icons.add_circle, 'label': '添加家具', 'route': AppRoutes.addFurniture},
{'icon': Icons.receipt_long, 'label': '购买记录', 'route': AppRoutes.purchaseRecord},
{'icon': Icons.calendar_month, 'label': '日历', 'route': AppRoutes.calendar},
{'icon': Icons.favorite, 'label': '收藏夹', 'route': AppRoutes.favorites},
];
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('快捷操作', style: TextStyle(fontSize: 18.sp, fontWeight: FontWeight.bold, color: const Color(0xFF5D4037))),
SizedBox(height: 12.h),
这种数据驱动的写法好处是,以后要加减按钮只需要改 actions 数组就行,不用动 UI 代码。比如以后想加一个"扫码录入"的功能,直接在数组里加一项就行了。
标题用深棕色 0xFF5D4037,和主题色呼应但又有区分。这个颜色比 AppBar 的棕色稍微深一点,用在文字上刚好。
为什么选这四个功能作为快捷入口?添加家具是最常用的操作,购买记录可以查看历史,日历可以看保修到期时间,收藏夹放一些重要的家具。这四个基本覆盖了日常使用场景。
快捷按钮样式
每个快捷按钮是一个圆角方块加底部文字的组合,点击后跳转到对应页面。
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: actions.map((a) => GestureDetector(
onTap: () => Get.toNamed(a['route'] as String),
child: Column(
children: [
Container(
padding: EdgeInsets.all(14.w),
decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(12.r), boxShadow: [BoxShadow(color: Colors.grey.withOpacity(0.1), blurRadius: 4)]),
child: Icon(a['icon'] as IconData, color: const Color(0xFF8B4513), size: 28.sp),
),
SizedBox(height: 8.h),
Text(a['label'] as String, style: TextStyle(fontSize: 12.sp, color: const Color(0xFF5D4037))),
],
),
)).toList(),
),
按钮背景是白色,加了一点淡淡的阴影,让它有浮起来的感觉。阴影的透明度设为 0.1,模糊半径 4,这样阴影很柔和,不会太突兀。
GestureDetector 包裹整个按钮区域,点击范围更大,用户体验更好。如果只在图标上加点击事件,用户可能会点不准。
图标颜色用主题棕色,保持整体风格统一。图标大小 28,不会太大也不会太小,和下面的文字搭配刚好。
最近购买列表
这个模块展示最近购买的家具,方便用户快速查看。标题栏右边有个"查看全部"按钮,点击跳转到完整的购买记录页面。
Widget _buildRecentPurchases() {
final records = [
{'name': '北欧实木沙发', 'room': '客厅', 'date': '2024-01-15', 'price': '¥12,800'},
{'name': '智能升降书桌', 'room': '书房', 'date': '2024-01-10', 'price': '¥3,200'},
];
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text('最近购买', style: TextStyle(fontSize: 18.sp, fontWeight: FontWeight.bold, color: const Color(0xFF5D4037))),
TextButton(onPressed: () => Get.toNamed(AppRoutes.purchaseRecord), child: const Text('查看全部')),
],
),
标题和按钮用 Row 包裹,spaceBetween 让它们分别靠左和靠右。这是很常见的列表头部布局方式。
TextButton 自带点击效果,不用额外处理。而且它的默认样式就是蓝色文字,和整体设计也比较搭。
这里的数据是写死的,实际项目中应该从数据库读取。不过现在先把 UI 做出来,数据层后面再接入。
购买记录卡片
每条购买记录是一个白色卡片,左边是图标,中间是名称和信息,右边是价格。
...records.map((r) => Container(
margin: EdgeInsets.only(bottom: 10.h),
padding: EdgeInsets.all(14.w),
decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(12.r)),
child: Row(
children: [
Container(
padding: EdgeInsets.all(10.w),
decoration: BoxDecoration(color: const Color(0xFF8B4513).withOpacity(0.1), borderRadius: BorderRadius.circular(10.r)),
child: Icon(Icons.chair, color: const Color(0xFF8B4513), size: 24.sp),
),
SizedBox(width: 12.w),
Expanded(child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
Text(r['name']!, style: TextStyle(fontWeight: FontWeight.w600, fontSize: 15.sp)),
Text('${r['room']} · ${r['date']}', style: TextStyle(color: Colors.grey[600], fontSize: 12.sp)),
])),
Text(r['price']!, style: TextStyle(color: const Color(0xFF8B4513), fontWeight: FontWeight.bold)),
],
),
)).toList(),
图标外面套了一个浅棕色背景的容器,和纯白卡片形成对比。这个浅棕色是主题色加 0.1 的透明度,颜色很淡但能起到区分作用。
中间部分用 Expanded 包裹,这样文字太长会自动省略,不会把价格挤出去。这个细节很重要,不然遇到名字很长的家具,布局就会乱掉。
价格用主题色加粗显示,一眼就能看到。毕竟对于购买记录来说,价格是很重要的信息。
卡片之间用 margin: EdgeInsets.only(bottom: 10.h) 隔开,只设置底部间距,这样最后一个卡片下面不会有多余的空白。
保修提醒模块
最后是保修提醒,用橙色系来表示警告状态,提醒用户注意即将到期的保修。
Widget _buildWarrantyReminders() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('保修提醒', style: TextStyle(fontSize: 18.sp, fontWeight: FontWeight.bold, color: const Color(0xFF5D4037))),
SizedBox(height: 12.h),
Container(
padding: EdgeInsets.all(14.w),
decoration: BoxDecoration(color: Colors.orange[50], borderRadius: BorderRadius.circular(12.r), border: Border.all(color: Colors.orange[200]!)),
child: Row(
children: [
Icon(Icons.alarm, color: Colors.orange[700], size: 24.sp),
SizedBox(width: 12.w),
Expanded(child: Text('智能冰箱保修即将到期', style: TextStyle(fontWeight: FontWeight.w600))),
Text('15天后', style: TextStyle(color: Colors.orange[800], fontSize: 12.sp)),
],
),
),
],
);
}
背景用 Colors.orange[50] 很淡的橙色,边框用 Colors.orange[200] 稍深一点,形成柔和的警告效果。这种配色不会太刺眼,但又能引起用户注意。
闹钟图标用 Colors.orange[700],右边的天数用 Colors.orange[800],整体色调统一但又有层次变化。橙色系在这里表示"注意"而不是"危险",如果用红色会显得太严重。
为什么要单独做一个保修提醒模块?因为很多人买了家具之后就忘了保修这回事,等出问题了才发现保修已经过期了。有了这个提醒,用户可以提前知道哪些家具快过保了,该修的赶紧修。
导入依赖说明
文件开头需要导入必要的依赖:
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart';
import '../../app/routes/app_routes.dart';
flutter_screenutil 提供屏幕适配功能,get 提供路由跳转功能,app_routes.dart 是我们自己定义的路由常量文件。
把路由定义成常量的好处是,如果以后要改路由路径,只需要改一个地方就行了,不用到处找。
配色方案总结
整个首页用到的颜色不多,主要是这几个:
- 主题棕色
0xFF8B4513:AppBar、图标、价格文字 - 深棕色
0xFF5D4037:标题文字 - 米白背景
0xFFFAF8F5:页面背景 - 纯白色:卡片背景
- 橙色系:保修提醒
颜色不要用太多,不然会显得很乱。选定一个主题色,然后围绕它搭配就行了。
响应式适配要点
用 flutter_screenutil 做适配时,有几个要注意的地方:
- 宽度相关的用
.w,比如 padding、margin、width - 高度相关的用
.h,比如 SizedBox 的 height - 字号用
.sp,会根据系统字体设置缩放 - 圆角用
.r,保持比例协调
在 main.dart 里要初始化 ScreenUtil,设置设计稿尺寸。我用的是 375x812,这是 iPhone X 的尺寸,也是很多设计师常用的尺寸。
小结
首页仪表盘的实现其实不复杂,主要是把各个模块拆分清楚,每个模块负责自己的展示逻辑。配色上保持统一的棕色系,符合家具App的定位。用 flutter_screenutil 做适配,在不同尺寸的设备上都能正常显示。
代码组织上,把每个模块抽成独立的方法,这样主 build 方法就很清晰,一眼就能看出页面结构。以后要修改某个模块,直接找到对应的方法就行了。
下一篇会讲家具列表页面的实现,涉及到列表渲染和筛选功能,敬请期待。
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
更多推荐

所有评论(0)