【openHarmony】开源鸿蒙跨平台DAY11:HarmonyOS + Flutter 电商实战:商品详情页开发与轮播图跳转实现
前言
本文将详细介绍如何从零开发Flutter电商应用的商品详情页。我们将参考开源鸿蒙的水果详情页设计,并针对电商场景进行优化实现。
一、项目背景
本文基于一个采用Flutter 3.x框架开发的跨平台电商应用,主要技术架构如下:
核心框架:Flutter 3.x版本
应用结构:采用底部导航栏设计,包含四个核心页面:
首页
商品分类页
购物车页面
个人中心页
已实现功能模块:
轮播图展示
商品分类列表
特惠商品推荐板块
二、开发流程
需求梳理 → 数据建模 → 接口开发 → 页面实现 → 联调测试 → 问题优化
2.1、需求设计
根据CSDN文章中的水果详情页面设计,我们需要实现:
模块功能表格
| 模块 | 功能说明 |
|---|---|
| 顶部导航栏 | 返回按钮、标题、分享按钮 |
| 商品基本信息 | 图片、名称、英文名、分类、价格 |
| 商品卖点 | 营养价值/功效描述 |
| 商品特性 | 规格特性列表(卡片展示) |
| 商品参数 | 网格形式展示参数数据 |
| 底部操作栏 | 客服、收藏、加入购物车、立即购买 |
2.2 数据架构规划
参考水果详情的数据结构,设计商品详情数据模型:
// 商品参数项
class ProductParam {
final String name; // 参数名称
final String value; // 参数值
}
// 商品规格特性
class ProductFeature {
final String short; // 简短描述
final String long; // 详细描述
}
// 商品详情
class ProductDetail {
final String id; // 商品ID
final String name; // 商品名称
final String englishName; // 英文名称
final List<ProductParam> params; // 商品参数列表
final String category; // 分类
final String picture; // 商品图片
final String benefits; // 商品功效/卖点
final List<String> detailImages; // 详情图片列表
final String detailDescription; // 详细描述
final String mainColor; // 主题色
final String mainBg; // 背景色
final List<ProductFeature> features; // 规格特性列表
final String price; // 价格
final String originalPrice; // 原价
}
三、代码实现方案
3.1 创建数据模型文件
文件路径:lib/viewmodels/product_detail.dart
/**
* 商品详情数据模型
*
* 参考CSDN文章: https://blog.csdn.net/qq_33247427/article/details/157554060
* 来源: 【开源鸿蒙跨平台开发先锋训练营】DAY15~DAY19为开源鸿蒙跨平台应用全面集成添加核心场景-水果详情页面
*/
// 商品参数项
class ProductParam {
final String name; // 参数名称
final String value; // 参数值
ProductParam({required this.name, required this.value});
factory ProductParam.fromJSON(List<dynamic> json) {
return ProductParam(
name: json[0] ?? '',
value: json[1] ?? '',
);
}
}
// 商品规格特性
class ProductFeature {
final String short; // 简短描述
final String long; // 详细描述
ProductFeature({required this.short, required this.long});
factory ProductFeature.fromJSON(Map<String, dynamic> json) {
return ProductFeature(
short: json['short'] ?? '',
long: json['long'] ?? '',
);
}
}
// 商品详情
class ProductDetail {
final String id; // 商品ID
final String name; // 商品名称
final String englishName; // 英文名称
final List<ProductParam> params; // 商品参数列表
final String category; // 分类
final String picture; // 商品图片
final String benefits; // 商品功效/卖点
final List<String> detailImages; // 详情图片列表
final String detailDescription; // 详细描述
final String mainColor; // 主题色
final String mainBg; // 背景色
final List<ProductFeature> features; // 规格特性列表
final String price; // 价格
final String originalPrice; // 原价
ProductDetail({
required this.id,
required this.name,
required this.englishName,
required this.params,
required this.category,
required this.picture,
required this.benefits,
required this.detailImages,
required this.detailDescription,
required this.mainColor,
required this.mainBg,
required this.features,
required this.price,
required this.originalPrice,
});
factory ProductDetail.fromJSON(Map<String, dynamic> json) {
// 解析参数列表
List<ProductParam> paramList = [];
if (json['params'] != null) {
paramList = (json['params'] as List)
.map((e) => ProductParam.fromJSON(e as List<dynamic>))
.toList();
}
// 解析特性列表
List<ProductFeature> featureList = [];
if (json['features'] != null) {
featureList = (json['features'] as List)
.map((e) => ProductFeature.fromJSON(e as Map<String, dynamic>))
.toList();
}
// 解析详情图片
List<String> images = [];
if (json['detailImages'] != null) {
images = List<String>.from(json['detailImages']);
}
return ProductDetail(
id: json['id'] ?? '',
name: json['name'] ?? '',
englishName: json['englishName'] ?? '',
params: paramList,
category: json['category'] ?? '默认分类',
picture: json['picture'] ?? '',
benefits: json['benefits'] ?? '',
detailImages: images,
detailDescription: json['detailDescription'] ?? '',
mainColor: json['mainColor'] ?? '#FF6B00',
mainBg: json['mainBg'] ?? '#FFF3E0',
features: featureList,
price: json['price'] ?? '0.00',
originalPrice: json['originalPrice'] ?? '0.00',
);
}
// 创建模拟数据(用于演示)
static ProductDetail createMock() {
return ProductDetail(
id: '1',
name: '精选有机红富士苹果',
englishName: 'Organic Fuji Apple',
params: [
ProductParam(name: '产地', value: '陕西烟台'),
ProductParam(name: '规格', value: '500g/个'),
ProductParam(name: '保质期', value: '30天'),
ProductParam(name: '储存方式', value: '冷藏保存'),
ProductParam(name: '净含量', value: '2.5kg'),
ProductParam(name: '品牌', value: '果园直供'),
],
category: '新鲜水果',
picture: 'https://images.unsplash.com/photo-1560806887-1e4cd0b6cbd6?w=400',
benefits: '富含维生素C、膳食纤维和多种矿物质,口感清脆香甜,是日常健康饮食的理想选择。',
detailImages: [
'https://images.unsplash.com/photo-1560806887-1e4cd0b6cbd6?w=400',
'https://images.unsplash.com/photo-1567306226416-28f0efdc88ce?w=400',
],
detailDescription: '我们的红富士苹果来自优质果园,采用有机种植方式,不使用任何化学农药和化肥。每一颗苹果都经过精心挑选,确保最佳口感和品质。',
mainColor: '#FF6B00',
mainBg: '#FFF3E0',
features: [
ProductFeature(
short: '新鲜直达',
long: '果园直采,从枝头到舌尖不超过48小时,确保新鲜度。',
),
ProductFeature(
short: '有机认证',
long: '通过国家有机食品认证,无农药残留,吃得放心。',
),
ProductFeature(
short: '营养丰富',
long: '富含维生素C、果胶、膳食纤维等营养元素,有益健康。',
),
],
price: '39.90',
originalPrice: '59.90',
);
}
}
3.2 设计API接口文档
文件路径:lib/api/product_detail.dart
/**
* 商品详情API接口
*/
import 'package:harmonyos_day_four/utils/DioRequest.dart';
import 'package:harmonyos_day_four/viewmodels/product_detail.dart';
/// 获取商品详情数据
/// 商品详情接口(实际项目中需要替换为真实的接口地址)
Future<ProductDetail> getProductDetailAPI(String productId) async {
// 模拟API请求 - 实际项目中应该请求真实接口
// final result = await dioRequest.get('/product/detail/$productId');
// return ProductDetail.fromJSON(result);
// 当前返回模拟数据用于演示
return ProductDetail.createMock();
}
3.3 优化商品详情页
文件路径:lib/pages/product/detail.dart
Scaffold
├── AppBar (顶部导航栏)
├── SingleChildScrollView (可滚动内容)
│ ├── 商品基本信息卡片
│ ├── 商品卖点卡片
│ ├── 商品特性卡片
│ ├── 商品参数网格
│ └── 商品详情描述卡片
└── BottomNavigationBar (底部操作栏)
关键代码:
// 商品基本信息卡片
Widget _buildProductHeader() {
return Container(
width: double.infinity,
padding: const EdgeInsets.all(16),
decoration: const BoxDecoration(color: Colors.white),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 商品图片
ClipRRect(
borderRadius: BorderRadius.circular(12),
child: Image.network(_productDetail!.picture, ...),
),
// 商品名称和价格
Text(_productDetail!.name, ...),
// 价格信息(含原价删除线)
Row(
children: [
Text('¥${_productDetail!.price}', style: 主题色),
Text('¥${_productDetail!.originalPrice}',
style: 删除线样式),
],
),
],
),
);
}
// 商品参数网格(2列布局)
GridView.builder(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
mainAxisSpacing: 10,
crossAxisSpacing: 10,
childAspectRatio: 2.0,
),
itemBuilder: (context, index) {
return _buildParamCard(_productDetail!.params[index]);
},
)
**3.4 增强轮播图组件交互体验,实现点击跳转功能
**
文件路径:lib/components/Home/HmSlider.dart
// 添加导入
import 'package:harmonyos_day_four/pages/product/detail.dart';
// 修改PageView.builder的itemBuilder
itemBuilder: (context, index) {
return GestureDetector(
onTap: () {
// 点击轮播图跳转到商品详情页
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ProductDetailPage(
productId: widget.bannerList[index].id,
),
),
);
},
child: Image.network(
widget.bannerList[index].imgUrl,
fit: BoxFit.cover,
width: screenWidth,
// ... loadingBuilder 和 errorBuilder
),
);
},
四、遇到的问题及解决方法问题
问题描述
轮播图点击无响应,无法跳转至商品详情页。
原因分析
Stack布局中,搜索框组件通过Padding(padding: const EdgeInsets.all(10))覆盖了整个轮播图区域,导致点击事件被拦截。
Widget _getSearch() {
return Positioned(
top: 10,
left: 0,
right: 0,
child: Padding(
padding: const EdgeInsets.all(10), // 导致覆盖问题
child: Container(...),
),
);
}
修复方案
移除Padding改用直接边距控制:
Widget _getSearch() {
return Positioned(
top: 10,
left: 10, // 直接控制边距
right: 10,
child: Container(...), // 移除Padding
);
}
修复效果
搜索框仅占据顶部70px区域
轮播图其他区域可正常响应点击事件
图片加载失败处理方案
问题描述
网络图片加载失败时无友好提示。
解决方案
使用errorBuilder构建错误状态UI:
Image.network(
_productDetail!.picture,
errorBuilder: (context, error, stackTrace) {
return Container(
decoration: BoxDecoration(
color: bgColor,
borderRadius: BorderRadius.circular(12),
),
child: Center(
child: Icon(Icons.image_not_supported,
size: 50, color: mainColor.withOpacity(0.5)),
),
);
},
)
主题色动态解析方案
问题描述
需将服务端返回的十六进制颜色值(如#FF6B00)转换为Color对象。
解决方案
Color _parseColor(String hex) {
return Color(int.parse(hex.substring(1, 7), radix: 16) + 0xFF000000);
}
使用示例
Color primaryColor = _parseColor('#FF6B00');
五、页面配色方案
参考水果详情页面的配色,电商详情页采用橙色系:
以下是整理后的表格形式:
| 用途 | 颜色值 | 说明 |
|---|---|---|
| 主题色 | #FF6B00 | 橙色,用于标题、价格、按钮 |
| 浅橙背景 | #FFF3E0 | 半透明橙色,用于卡片背景 |
| 页面背景 | #F5F5F5 | 浅灰色 |
| 主文本 | #1F2937 | 深灰色 |
| 次要文本 | #9CA3AF | 中灰色 |
| 标签文本 | #6B7280 | 灰色 |
六、项目结构
lib/
├── api/
│ └── product_detail.dart # 商品详情API
├── components/
│ └── Home/
│ └── HmSlider.dart # 轮播图组件(已修改)
├── pages/
│ └── product/
│ └── detail.dart # 商品详情页面
└── viewmodels/
└── product_detail.dart # 商品详情数据模型
七、总结
数据模型设计
参考开源鸿蒙的水果详情页面数据结构,设计电商商品详情的数据模型,确保数据字段覆盖商品基本属性、规格参数和展示信息。
页面UI开发
采用卡片式布局风格实现商品详情页,完整展示商品主图、名称、价格、促销信息、规格参数和详情描述等内容区域。通过视觉层级划分提升信息可读性。
交互功能实现
完成从首页轮播图到商品详情页的点击跳转功能联调,确保路由跳转参数传递正确。针对Stack布局中存在的点击事件拦截问题,通过调整组件层级和事件冒泡机制进行修复。
关键技术应用
GridView.builder组件实现商品参数网格化展示,支持动态数据加载和自适应列数。Positioned组件在Stack布局中精确定位悬浮按钮等子元素。通过合理设置Padding避免组件重叠导致的界面异常。
异常处理机制
为网络图片组件配置errorBuilder回调,处理加载失败情况并显示占位图。对可能出现的空数据状态进行防御性编程,保证页面容错能力。
该实现方案采用Flutter框架构建跨平台电商商品详情页,在保持原生性能的同时提供丰富的交互体验。通过组件化开发思路,各功能模块可独立维护和复用。
更多推荐



所有评论(0)