181.[HarmonyOS NEXT 实战案例九:Grid] 电商网格布局基础篇:打造精美商品展示页面
2025-06-30 23:01:10
104次阅读
0个评论
[HarmonyOS NEXT 实战案例九:Grid] 电商网格布局基础篇:打造精美商品展示页面
项目已开源,开源地址: https://gitcode.com/nutpi/HarmonyosNextCaseStudyTutorial , 欢迎fork & star
效果演示
1. 电商网格布局概述
在移动电商应用中,网格布局是展示商品的最佳选择之一,它能够以规整、美观的方式呈现多个商品项,并支持用户快速浏览和筛选。HarmonyOS NEXT 的 ArkUI 框架提供了强大的 Grid 和 GridItem 组件,使得开发者能够轻松实现各种复杂的电商网格布局。
1.1 电商网格布局的特点
特点 | 描述 |
---|---|
规整的视觉呈现 | 以网格形式展示商品,视觉整齐,便于用户浏览 |
高效的空间利用 | 充分利用屏幕空间,展示更多商品信息 |
灵活的筛选排序 | 支持按类别、价格、销量等多维度筛选和排序 |
丰富的交互体验 | 支持点击查看详情、加入购物车等交互操作 |
响应式布局 | 能够适应不同屏幕尺寸和方向的变化 |
1.2 本案例实现的功能
本案例将实现一个完整的电商商品展示页面,包含以下功能:
- 商品网格展示:使用 Grid 和 GridItem 组件展示商品信息
- 分类筛选:支持按商品分类筛选商品
- 多维度排序:支持按价格、销量、评分等维度排序
- 搜索功能:支持按关键词搜索商品
- 商品详情:点击商品可查看详细信息
- 购物车功能:支持将商品加入购物车
2. 数据模型设计
2.1 商品数据模型
虽然我们无法直接查看 CommonTypes.ts 文件中的 EcommerceProduct 接口定义,但从代码中可以推断出其结构:
// 推断的 EcommerceProduct 接口结构
interface EcommerceProduct {
id: number; // 商品ID
name: string; // 商品名称
description: string; // 商品描述
price: number; // 商品价格
originalPrice?: number; // 原价(可选)
discount?: number; // 折扣(可选)
image: Resource; // 商品主图
images: Resource[]; // 商品图片集
category: string; // 商品分类
brand: string; // 品牌
rating: number; // 评分
reviewCount: number; // 评价数量
salesCount: number; // 销售数量
stock: number; // 库存
tags: string[]; // 标签
isHot: boolean; // 是否热销
isNew: boolean; // 是否新品
isFreeShipping: boolean; // 是否免运费
specifications: { // 规格
color?: string[]; // 颜色选项
size?: string[]; // 尺寸选项
material?: string; // 材质
};
seller: { // 卖家信息
name: string; // 卖家名称
rating: number; // 卖家评分
location: string; // 卖家位置
};
}
2.2 购物车数据模型
interface CateItem {
productId: number; // 商品ID
quantity: number; // 数量
}
2.3 价格范围数据模型
interface PriceRange {
min: number; // 最低价格
max: number; // 最高价格
}
3. 状态管理设计
在电商应用中,有效的状态管理对于实现复杂的交互逻辑至关重要。本案例使用 ArkUI 的 @State
装饰器来管理组件的状态:
@State products: EcommerceProduct[] = [...] // 商品数据
@State cartItems: CateItem[] = [] // 购物车项目
@State selectedCategory: string = '全部' // 选中的分类
@State sortBy: string = '综合' // 排序方式
@State priceRange: PriceRange = { min: 0, max: 20000 } // 价格范围
@State searchKeyword: string = '' // 搜索关键词
@State showProductDetail: boolean = false // 是否显示商品详情
@State selectedProduct: EcommerceProduct = {...} // 选中的商品
@State showCart: boolean = false // 是否显示购物车
这些状态变量使得我们能够:
- 跟踪用户的筛选和排序选择
- 管理购物车中的商品
- 控制商品详情和购物车的显示与隐藏
- 记录当前选中的商品
4. 辅助功能实现
4.1 商品过滤与排序
getFilteredProducts(): EcommerceProduct[] {
let filtered = this.products
// 分类过滤
if (this.selectedCategory !== '全部') {
filtered = filtered.filter(product => product.category === this.selectedCategory)
}
// 价格过滤
filtered = filtered.filter(product =>
product.price >= this.priceRange.min && product.price <= this.priceRange.max
)
// 搜索过滤
if (this.searchKeyword.trim() !== '') {
filtered = filtered.filter(product =>
product.name.includes(this.searchKeyword) ||
product.description.includes(this.searchKeyword) ||
product.brand.includes(this.searchKeyword) ||
product.tags.some(tag => tag.includes(this.searchKeyword))
)
}
// 排序
switch (this.sortBy) {
case '价格升序':
filtered.sort((a, b) => a.price - b.price)
break
case '价格降序':
filtered.sort((a, b) => b.price - a.price)
break
case '销量':
filtered.sort((a, b) => b.salesCount - a.salesCount)
break
case '评分':
filtered.sort((a, b) => b.rating - a.rating)
break
default: // 综合
filtered.sort((a, b) => {
const scoreA = a.salesCount * 0.3 + a.rating * 1000 + (a.isHot ? 500 : 0)
const scoreB = b.salesCount * 0.3 + b.rating * 1000 + (b.isHot ? 500 : 0)
return scoreB - scoreA
})
}
return filtered
}
这个方法实现了多维度的商品过滤和排序:
- 分类过滤:根据用户选择的商品分类进行过滤
- 价格过滤:根据设定的价格范围过滤商品
- 关键词搜索:在商品名称、描述、品牌和标签中搜索关键词
- 多维度排序:支持按价格升序/降序、销量、评分排序,以及综合排序(考虑销量、评分和热销标志)
4.2 购物车功能
// 添加到购物车
addToCart(productId: number, quantity: number = 1) {
const existingItem = this.cartItems.find(item => item.productId === productId)
if (existingItem) {
existingItem.quantity += quantity
} else {
this.cartItems.push({ productId, quantity })
}
}
// 获取购物车商品数量
getCartItemCount(): number {
return this.cartItems.reduce((total, item) => total + item.quantity, 0)
}
// 获取购物车总价
getCartTotal(): number {
return this.cartItems.reduce((total, item) => {
const product = this.products.find(p => p.id === item.productId)
return total + (product ? product.price * item.quantity : 0)
}, 0)
}
这些方法实现了购物车的核心功能:
- 添加商品:将商品添加到购物车,如果已存在则增加数量
- 计算总数量:计算购物车中所有商品的总数量
- 计算总价:计算购物车中所有商品的总价
4.3 数字格式化
// 格式化数字
formatNumber(num: number): string {
if (num >= 10000) {
return `${(num / 10000).toFixed(1)}万`
} else if (num >= 1000) {
return `${(num / 1000).toFixed(1)}k`
}
return num.toString()
}
这个方法将大数字格式化为更易读的形式,例如将 15600 格式化为 "1.6万",提升用户体验。
5. 页面结构设计
5.1 整体布局
电商商品展示页面的整体布局如下:
build() {
Stack() {
Column() {
// 顶部搜索和购物车
Row() { ... }
// 分类和排序
Column() { ... }
// 商品网格
Grid() { ... }
}
.width('100%')
.height('100%')
// 商品详情对话框
if (this.showProductDetail) { ... }
// 购物车浮层
if (this.showCart) { ... }
}
.width('100%')
.height('100%')
}
整体布局采用 Stack 组件作为根容器,包含三个主要部分:
- 主内容区域:包含顶部搜索栏、分类和排序选项、商品网格
- 商品详情对话框:点击商品时显示的详情浮层
- 购物车浮层:点击购物车图标时显示的浮层
5.2 顶部搜索和购物车
// 顶部搜索和购物车
Row() {
Row() {
Image($r('app.media.search_icon'))
.width(20)
.height(20)
.fillColor('#999999')
.margin({ left: 12 })
TextInput({ placeholder: '搜索商品' })
.fontSize(16)
.backgroundColor('transparent')
.border({ width: 0 })
.layoutWeight(1)
.margin({ left: 8, right: 12 })
.onChange((value: string) => {
this.searchKeyword = value
})
}
.width('100%')
.height(44)
.backgroundColor('#F5F5F5')
.borderRadius(22)
.layoutWeight(1)
Button() {
Stack({ alignContent: Alignment.TopEnd }) {
Image($r('app.media.cart_icon'))
.width(24)
.height(24)
.fillColor('#333333')
if (this.getCartItemCount() > 0) {
Text(this.getCartItemCount().toString())
.fontSize(10)
.fontColor('#FFFFFF')
.backgroundColor('#FF3B30')
.width(16)
.height(16)
.borderRadius(8)
.textAlign(TextAlign.Center)
.margin({ top: -4, right: -4 })
}
}
}
.width(44)
.height(44)
.borderRadius(22)
.backgroundColor('#F0F0F0')
.margin({ left: 12 })
.onClick(() => {
this.showCart = true
})
}
.width('100%')
.padding({ left: 16, right: 16, top: 16, bottom: 16 })
.backgroundColor('#FFFFFF')
顶部区域包含:
- 搜索框:用户可以输入关键词搜索商品
- 购物车按钮:显示购物车图标和商品数量,点击后显示购物车浮层
5.3 分类和排序区域
// 分类和排序
Column() {
// 分类标签
Scroll() {
Row() {
ForEach(this.categories, (category:string, index) => {
Button(category)
.fontSize(14)
.fontColor(this.selectedCategory === category ? '#FFFFFF' : '#333333')
.backgroundColor(this.selectedCategory === category ? '#007AFF' : '#F0F0F0')
.borderRadius(16)
.padding({ left: 16, right: 16, top: 8, bottom: 8 })
.margin({ right: index < this.categories.length - 1 ? 8 : 0 })
.onClick(() => {
this.selectedCategory = category
})
})
}
}
.scrollable(ScrollDirection.Horizontal)
.scrollBar(BarState.Off)
.width('100%')
.margin({ bottom: 12 })
// 排序选项
Row() {
Text('排序:')
.fontSize(14)
.fontColor('#666666')
ForEach(this.sortOptions, (option:string, index) => {
Button(option)
.fontSize(12)
.fontColor(this.sortBy === option ? '#007AFF' : '#666666')
.backgroundColor('transparent')
.border({
width: 1,
color: this.sortBy === option ? '#007AFF' : '#E0E0E0'
})
.borderRadius(12)
.padding({ left: 12, right: 12, top: 4, bottom: 4 })
.margin({ left: 8 })
.onClick(() => {
this.sortBy = option
})
})
Blank()
}
.width('100%')
}
.width('100%')
.padding({ left: 16, right: 16, bottom: 16 })
.backgroundColor('#FFFFFF')
分类和排序区域包含:
- 分类标签:水平滚动的分类按钮,用户可以选择不同的商品分类
- 排序选项:多个排序按钮,用户可以选择不同的排序方式
6. 商品网格实现
6.1 Grid 组件配置
// 商品网格
Grid() {
ForEach(this.getFilteredProducts(), (product: EcommerceProduct) => {
GridItem() {
// GridItem 内容...
}
})
}
.columnsTemplate('1fr 1fr')
.rowsGap(12)
.columnsGap(12)
.width('100%')
.layoutWeight(1)
.padding({ left: 16, right: 16, bottom: 16 })
.backgroundColor('#F8F8F8')
Grid 组件的关键配置:
- columnsTemplate:设置为 '1fr 1fr',表示两列等宽布局
- rowsGap:行间距为 12 像素
- columnsGap:列间距为 12 像素
- layoutWeight:设置为 1,使网格区域占据剩余空间
6.2 GridItem 商品卡片实现
GridItem() {
Column() {
// 商品图片
Stack({ alignContent: Alignment.TopStart }) {
Image(product.image)
.width('100%')
.height(160)
.objectFit(ImageFit.Cover)
.borderRadius({ topLeft: 12, topRight: 12 })
// 标签
Column() {
if (product.isHot) {
Text('热销')
.fontSize(10)
.fontColor('#FFFFFF')
.backgroundColor('#FF9500')
.padding({ left: 6, right: 6, top: 2, bottom: 2 })
.borderRadius(4)
.margin({ bottom: 4 })
}
if (product.isNew) {
Text('新品')
.fontSize(10)
.fontColor('#FFFFFF')
.backgroundColor('#34C759')
.padding({ left: 6, right: 6, top: 2, bottom: 2 })
.borderRadius(4)
}
}
.margin({ top: 8, left: 8 })
}
// 商品信息
Column() {
Text(product.name)
.fontSize(14)
.fontWeight(FontWeight.Bold)
.fontColor('#333333')
.maxLines(2)
.textOverflow({ overflow: TextOverflow.Ellipsis })
.width('100%')
.textAlign(TextAlign.Start)
.margin({ bottom: 6 })
// 价格
Row() {
Text(`¥${product.price}`)
.fontSize(16)
.fontWeight(FontWeight.Bold)
.fontColor('#FF3B30')
if (product.originalPrice) {
Text(`¥${product.originalPrice}`)
.fontSize(12)
.fontColor('#999999')
.decoration({ type: TextDecorationType.LineThrough })
.margin({ left: 6 })
}
Blank()
}
.width('100%')
.margin({ bottom: 6 })
// 评分和销量
Row() {
Row() {
ForEach([1,2,3,4,5], (star:number) => {
Image(star <= product.rating ? $r('app.media.heart_filled') : $r('app.media.heart_outline'))
.width(10)
.height(10)
.fillColor(star <= product.rating ? '#FFD700' : '#E0E0E0')
})
Text(`${product.rating}`)
.fontSize(10)
.fontColor('#666666')
.margin({ left: 2 })
}
Blank()
Text(`${this.formatNumber(product.salesCount)}已售`)
.fontSize(10)
.fontColor('#999999')
}
.width('100%')
.margin({ bottom: 8 })
// 添加到购物车按钮
Button('加入购物车')
.fontSize(12)
.fontColor('#FFFFFF')
.backgroundColor('#007AFF')
.borderRadius(16)
.width('100%')
.height(32)
.onClick(() => {
this.addToCart(product.id)
})
}
.padding(12)
.alignItems(HorizontalAlign.Start)
}
.width('100%')
.backgroundColor('#FFFFFF')
.borderRadius(12)
.shadow({
radius: 6,
color: 'rgba(0, 0, 0, 0.1)',
offsetX: 0,
offsetY: 2
})
.onClick(() => {
this.selectedProduct = product
this.showProductDetail = true
})
}
每个 GridItem 包含一个商品卡片,结构如下:
- 商品图片:顶部显示商品主图,左上角显示热销/新品标签
- 商品名称:最多显示两行,超出部分使用省略号
- 价格信息:显示当前价格和原价(如果有折扣)
- 评分和销量:显示星级评分和销售数量
- 加入购物车按钮:点击后将商品添加到购物车
整个卡片添加了圆角和阴影效果,提升了视觉层次感,点击卡片会显示商品详情对话框。
7. 商品详情对话框
@Builder
ProductDetailDialog() {
if (this.selectedProduct) {
Column() {
// 商品图片
Image(this.selectedProduct.image)
.width('100%')
.height(300)
.objectFit(ImageFit.Cover)
.borderRadius({ topLeft: 16, topRight: 16 })
// 商品信息
Column() {
// 价格和标签
Row() { ... }
// 商品名称
Text(this.selectedProduct.name) ...
// 商品描述
Text(this.selectedProduct.description) ...
// 评分和销量
Row() { ... }
// 商品标签
Row() { ... }
// 操作按钮
Row() {
Button('加入购物车') ...
Button('立即购买') ...
}
}
.padding(20)
.alignItems(HorizontalAlign.Start)
}
.width('95%')
.height('85%')
.backgroundColor('#FFFFFF')
.borderRadius(16)
}
}
商品详情对话框以浮层形式展示,包含:
- 商品大图:顶部显示商品主图
- 价格信息:显示当前价格、原价和折扣信息
- 商品名称和描述:详细展示商品信息
- 评分和销量:以星级形式展示评分,并显示评价数量和销售数量
- 商品标签:展示商品的所有标签
- 操作按钮:提供"加入购物车"和"立即购买"两个操作
8. Grid 组件在电商场景中的优势
8.1 Grid 组件的核心优势
优势 | 描述 |
---|---|
灵活的布局控制 | 通过 columnsTemplate 属性可以轻松设置列数和宽度比例 |
间距控制 | rowsGap 和 columnsGap 属性可以精确控制商品卡片之间的间距 |
自动换行 | 自动将超出当前行的商品卡片放置到下一行 |
响应式布局 | 可以根据屏幕尺寸动态调整列数和卡片大小 |
高性能渲染 | 对于大量商品数据,Grid 组件能够保持良好的性能 |
8.2 电商商品卡片设计技巧
- 视觉层次:使用阴影和圆角创造立体感,突出商品卡片
- 信息优先级:将最重要的信息(图片、名称、价格)放在最显眼的位置
- 标签设计:使用醒目的颜色标识热销、新品等特殊属性
- 文本处理:对长文本使用 maxLines 和 textOverflow 属性处理溢出
- 价格展示:使用不同的字体大小和颜色区分当前价格和原价
- 评分可视化:使用星级图标直观展示商品评分
- 操作按钮:提供明确的加入购物车按钮,增强交互性
9. 总结
在本教程中,我们学习了如何使用 HarmonyOS NEXT 的 Grid 和 GridItem 组件创建一个功能完善的电商商品展示页面。我们详细讲解了数据模型设计、状态管理、过滤排序功能、商品网格布局实现以及商品详情对话框的创建。
00
- 0回答
- 4粉丝
- 0关注
相关话题
- 157.[HarmonyOS NEXT 实战案例一:Grid] 基础网格布局:打造精美电商商品列表
- 182.[HarmonyOS NEXT 实战案例九:Grid] 电商网格布局进阶篇:打造高级交互与视觉体验
- [HarmonyOS NEXT 实战案例一] 电商首页商品网格布局(下)
- [HarmonyOS NEXT 实战案例一] 电商首页商品网格布局(上)
- 183.[HarmonyOS NEXT 实战案例九:Grid] 电商网格布局高级篇:复杂场景与性能优化
- 158.[HarmonyOS NEXT 实战案例一:Grid] 基础网格布局进阶篇:电商商品列表的交互与状态管理
- [HarmonyOS NEXT 实战案例:电商应用] 基础篇 - 垂直分割布局打造商品详情页
- [HarmonyOS NEXT 实战案例:电商应用] 基础篇 - 垂直分割布局打造商品详情页
- [HarmonyOS NEXT 实战案例十五] 电商分类导航网格布局
- [HarmonyOS NEXT 实战案例十五] 电商分类导航网格布局(进阶篇)
- 159.[HarmonyOS NEXT 实战案例一:Grid] 基础网格布局高级篇:电商应用的复杂交互与动效实现
- 163.[HarmonyOS NEXT 实战案例三:Grid] 不规则网格布局基础篇:打造新闻应用首页
- 151.[HarmonyOS NEXT 实战案例十二:List系列] 卡片样式列表组件实战:打造精美电商应用 基础篇
- 160.[HarmonyOS NEXT 实战案例二:Grid] 照片相册网格布局:基础篇
- 172.[HarmonyOS NEXT 实战案例六:Grid] 响应式网格布局 - 基础篇