排序、筛选、商品卡片——电商列表页的三板斧。

前言

首页是"逛"——用户漫无目的地看,被 Banner 和推荐吸引。列表页是"找"——用户带着明确目的来,需要快速筛选出想要的商品。

商品列表页的设计目标:让用户用最少的操作找到目标商品。排序、筛选、列表三个模块各司其职。
完整效果
在这里插入图片描述

页面结构

从截图可以看到,页面从上到下:

  1. 顶部导航栏:返回按钮 + 标题"全部分类" + 商品数量"16件"
  2. 排序栏:综合 | 价格↑ | 价格↓ | 销量 + 筛选按钮
  3. 商品列表:每个商品一行,左侧图片 + 中间信息 + 右侧加购按钮
    在这里插入图片描述

路由入口

从首页进入

// 搜索
router.pushUrl({ url: 'pages/ProductList',
  params: { 'search': this.searchText } as Record<string,Object> })

// 分类
router.pushUrl({ url: 'pages/ProductList',
  params: { 'category': item.name } as Record<string,Object> })

在这里插入图片描述

列表页接收两种参数:

  • search:搜索关键词
  • category:分类名称

从分类导航进入

// 底部导航的"分类"按钮
.onClick(() => { router.pushUrl({ url: 'pages/ProductList',
  params: { 'category': '全部分类' } as Record<string,Object> }) })

在这里插入图片描述

"全部分类"是一个特殊值——显示所有商品。

排序栏

从截图可以看到四个排序选项:综合、价格↑、价格↓、销量。

// 排序栏的实现思路
Row() {
  Text('综合').fontSize(13).fontColor(T1)  // 当前选中
  Text('价格↑').fontSize(13).fontColor(T2)
  Text('价格↓').fontSize(13).fontColor(T2)
  Text('销量').fontSize(13).fontColor(T2)
  Blank()
  Text('筛选').fontSize(13).fontColor(T2)
}

在这里插入图片描述

排序逻辑:

@State sortBy: string = 'default'  // default | priceAsc | priceDesc | sales

private getSortedProducts(): Product[] {
  let list = this.filteredProducts
  if (this.sortBy === 'priceAsc') {
    list = [...list].sort((a, b) => a.price - b.price)
  } else if (this.sortBy === 'priceDesc') {
    list = [...list].sort((a, b) => b.price - a.price)
  } else if (this.sortBy === 'sales') {
    list = [...list].sort((a, b) => b.sales - a.sales)
  }
  return list
}

为什么要 [...list] 复制数组?

list = [...list].sort(...)

直接 list.sort() 会修改原数组——ArkTS 的 @State 不会检测到原数组内部的变化,UI 不会更新。复制一个新数组再排序,@State 检测到引用变化,才会触发重新渲染。

这个问题和 3D 拆解分析里的 this.pc 刷新是同一个原理。

筛选功能

从截图右上角可以看到"筛选"按钮。点击后应该弹出筛选面板(价格区间、品牌、评分等)。

@State showFilter: boolean = false

// 筛选按钮
Text('筛选').fontSize(13).fontColor(T2)
  .onClick(() => { this.showFilter = true })

// 筛选面板(条件渲染)
if (this.showFilter) {
  // 筛选弹窗内容...
}

筛选是列表页最复杂的模块——需要支持多条件组合(价格区间 + 品牌 + 评分),还要支持重置和应用。

商品卡片

从截图可以看到每个商品的布局:

┌─────────────────────────────────────────┐
│ 📱  iPhone 15 Pro Max         [+]      │
│      深空灰·黑色·全色全覆盖             │
│      ¥8999    ★ 15天                      │
└─────────────────────────────────────────┘

三个区域:左侧图片、中间信息、右侧加购按钮。

加购按钮

从截图可以看到每个商品右侧有一个 [+] 按钮。这个按钮把商品加入购物车。

// 加购按钮的实现
Row() {
  Text('+').fontSize(18).fontColor(Color.White)
}.width(28).height(28).borderRadius(14).backgroundColor(A)
  .onClick(() => {
    addToCart(p)
    this.cartCount = getCartCount()  // 更新徽章
  })

加购后刷新首页徽章:

用户在列表页加购后,回到首页时购物车徽章需要更新。首页的 onPageShow 会重新调用 getCartCount(),所以只要数据源(CartService)更新了,首页徽章会自动刷新。

商品信息的密度

从截图看,每个商品卡片包含:

  1. 图标(emoji)
  2. 名称(加粗)
  3. 描述(灰色小字,比如"深空灰·黑色·全色全覆盖")
  4. 价格(红色加粗)
  5. 其他信息(评分、天数等)

信息密度很高——每个卡片要在有限空间内展示尽可能多的决策信息。名称告诉用户"这是什么",描述告诉用户"有什么特点",价格告诉用户"多少钱",评分告诉用户"别人觉得怎么样"。

跳转到商品详情

.onClick(() => { router.pushUrl({ url: 'pages/ProductDetail',
  params: { 'id': p.id } as Record<string,Object> }) })

点击商品行跳转到详情页,传递商品 ID。详情页根据 ID 加载完整的商品信息。

踩坑记录

1. 筛选和排序的状态管理

列表页有多个状态需要管理:

  • sortBy:排序方式
  • searchText:搜索关键词
  • category:分类
  • showFilter:是否显示筛选面板
  • filteredProducts:筛选后的商品列表

如果把这些状态都放在 @State 里,代码会很乱。建议用一个 @State 对象统一管理:

@State filterState: FilterState = {
  sortBy: 'default',
  category: '',
  priceRange: [0, Infinity],
  brands: []
}

2. 空状态处理

当搜索/筛选没有结果时,需要显示空状态:

if (this.getSortedProducts().length === 0) {
  Column() {
    Text('🔍').fontSize(48).opacity(0.25)
    Text('没有找到相关商品').fontSize(16).fontColor(T1)
    Text('换个关键词试试').fontSize(12).fontColor(T3)
  }
}

3. 列表性能

如果商品数量很多(比如 100+),ForEach 一次性渲染所有商品会影响性能。建议用懒加载:

// 只渲染可见的商品
private visibleProducts: Product[] = []
private pageSize: number = 20

4. 路由参数的类型安全

params: { 'category': item.name } as Record<string,Object>

每次路由跳转都要 as Record<string,Object> 断言,很啰嗦。建议封装一个路由工具函数:

function navigateTo(page: string, params: Record<string, string>): void {
  router.pushUrl({ url: page, params: params as Record<string,Object> })
}

写在最后

商品列表页是电商 App 中使用频率最高的页面——用户每次搜索、每次点分类、每次点"更多"都会进来。它的设计直接影响用户的购物效率。

排序和筛选是列表页的核心能力。排序让用户按价格或销量快速定位,筛选让用户按品牌或价格区间缩小范围。两者配合,用户能在几秒内从 100 个商品中找到想要的那个。

加购按钮的即时反馈很重要——用户点一下就能看到购物车徽章更新。这种"操作→反馈"的闭环让用户知道"我的操作成功了"。

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐