HarmonyOS7 商品卡片用 Column 和 Row 写清楚:别让一个小卡片拖垮页面结构

文章目录
前言
商品卡片看起来很小,但它经常是电商页面里最容易变复杂的组件。
一开始只有图片、标题、价格。过几天运营说要加标签,产品说要加库存,设计又补一个收藏按钮。要是 Column 和 Row 一开始没有分工,后面每加一个字段,布局都要跟着抖一下。
商品卡片不是把内容塞进去就完事,重点是先把信息层级定住。
这篇用 HarmonyOS7 的 ArkUI 写一个商品卡片。代码不追求花哨,重点看三件事:图片区怎么固定,信息区怎么伸缩,操作区怎么跟状态绑定。
为什么这个问题经常被写乱
商品卡片用 Column 和 Row 写清楚 这类内容很容易被写成“代码能跑就算讲完了”,但对初学者来说,这恰恰是最不够的地方。真正让人卡住的,往往不是某个组件名记不住,而是不知道这段代码为什么要这样拆、状态为什么要这样放、以后需求变化时应该从哪里改。
所以这篇文章不只想给你一个能跑的例子,更想把背后的判断过程讲清楚。你只要把这个判断过程吃透,后面自己改页面、补需求、查问题时,心里会稳很多。
先把卡片拆成三块

我通常会把商品卡片拆成这三层:
| 区域 | 负责内容 | 写代码时要注意 |
|---|---|---|
| 图片区 | 商品图、角标、售罄遮罩 | 尺寸稳定,不跟文案一起变化 |
| 信息区 | 标题、副标题、标签、价格 | 长文案要截断,不挤压按钮 |
| 操作区 | 收藏、加购、库存提示 | 状态和按钮可用性要来自同一份数据 |
Row 适合处理图片和文字区域的横向关系,Column 适合处理右侧信息的纵向节奏。不要一上来就嵌很多层,先让结构读起来像页面本身。
我的经验是:商品卡片后期难维护,通常不是因为样式复杂,而是因为每块信息的责任没有分清楚。
先把页面目标想清楚
在真正写代码之前,先别急着盯着 API。更有用的做法是先想清楚:这个页面到底想解决什么问题,用户最在意的反馈是什么,哪些状态必须一直保持一致。
当你先把这条主线想明白,再回头看组件和状态设计,很多选择都会顺理成章。对小白来说,这一步尤其重要,因为它能帮你从“照着抄”慢慢过渡到“看得懂、改得动”。
完整 ArkUI 示例
下面这个例子模拟一个商品卡片:左边是商品图占位和角标,右边是标题、卖点、标签、价格和收藏按钮,底部还有一个加购按钮。
interface ProductCardItem {
id: number
title: string
subTitle: string
price: number
marketPrice: number
stock: number
tags: string[]
favorite: boolean
}
@Entry
@Component
struct ProductCardPage {
@State product: ProductCardItem = {
id: 1001,
title: 'HarmonyOS7 开发者双肩包',
subTitle: '防泼水面料,适合通勤和短途出差',
price: 199,
marketPrice: 269,
stock: 8,
tags: ['限时直降', '包邮'],
favorite: false
}
private formatPrice(value: number): string {
return `¥${value.toFixed(2)}`
}
build() {
Column() {
Row({ space: 12 }) {
Stack({ alignContent: Alignment.TopStart }) {
Rect()
.width(104)
.height(104)
.fill('#EAF2FF')
.radius(10)
Text('新品')
.fontSize(11)
.fontColor(Color.White)
.backgroundColor('#E53935')
.padding({ left: 6, right: 6, top: 3, bottom: 3 })
.borderRadius({ topLeft: 10, bottomRight: 8 })
}
.width(104)
.height(104)
Column({ space: 8 }) {
Text(this.product.title)
.fontSize(16)
.fontWeight(FontWeight.Medium)
.maxLines(2)
.textOverflow({ overflow: TextOverflow.Ellipsis })
Text(this.product.subTitle)
.fontSize(12)
.fontColor('#666666')
.maxLines(1)
.textOverflow({ overflow: TextOverflow.Ellipsis })
Row({ space: 6 }) {
ForEach(this.product.tags, (tag: string) => {
Text(tag)
.fontSize(11)
.fontColor('#C62828')
.padding({ left: 6, right: 6, top: 2, bottom: 2 })
.backgroundColor('#FFF0F0')
.borderRadius(4)
}, (tag: string) => tag)
}
Row() {
Text(this.formatPrice(this.product.price))
.fontSize(20)
.fontWeight(FontWeight.Bold)
.fontColor('#D32F2F')
Text(this.formatPrice(this.product.marketPrice))
.fontSize(12)
.fontColor('#999999')
.decoration({ type: TextDecorationType.LineThrough, color: '#999999' })
Blank()
Button(this.product.favorite ? '已收藏' : '收藏')
.height(30)
.fontSize(12)
.type(ButtonType.Capsule)
.onClick(() => {
this.product.favorite = !this.product.favorite
})
}
.width('100%')
}
.layoutWeight(1)
.alignItems(HorizontalAlign.Start)
}
.padding(14)
.backgroundColor(Color.White)
.borderRadius(12)
.shadow({ radius: 10, color: '#14000000', offsetY: 4 })
Button(this.product.stock > 0 ? `加入购物车 · 剩余 ${this.product.stock}` : '暂时缺货')
.width('100%')
.margin({ top: 12 })
.enabled(this.product.stock > 0)
.onClick(() => {
if (this.product.stock > 0) {
this.product.stock -= 1
}
})
}
.padding(16)
.backgroundColor('#F5F7FA')
.width('100%')
}
}

把关键代码一段段拆开
ProductCardItem 是这篇代码的起点。标题、价格、库存、标签、收藏状态都放在这里,页面就不用从按钮文字里反推业务状态。后面接接口时,也更容易和后端字段对齐。
Row({ space: 12 }) 负责卡片主体的横向布局。左边图片固定 104vp,右边信息区使用 layoutWeight(1) 吃掉剩余空间。这样标题再长,也只会在右侧区域内换行或省略,不会把卡片撑坏。
Column({ space: 8 }) 管右侧文案的纵向节奏。标题、副标题、标签、价格各占一层,代码读起来和视觉层级是一致的。
收藏按钮直接修改 this.product.favorite,加购按钮直接依赖 this.product.stock。一个状态字段最好同时决定文案、样式和交互可用性,这样页面不会出现“显示缺货但还能点击”的尴尬问题。
放到真实项目里,我会补这几件事
商品图这里用 Rect 做占位,是为了把布局讲清楚。真实项目里可以换成 Image,但一定要给图片区域稳定尺寸,避免图片加载前后卡片高度跳动。
标签数量也要控制。两个标签还好,如果运营一次塞五六个,价格行会被挤下去。比较稳的做法是最多展示两个,剩下的在详情页展示。
还有一个容易忽略的点:加购不应该只扣本地库存。真实业务里要先走接口,接口成功后再更新库存;失败时给出明确提示。这个例子为了聚焦 UI 状态,只保留了本地演示逻辑。
新手最容易踩的坑
这一类示例最容易让人产生错觉:界面出来了,就以为已经掌握了。其实真正容易出问题的地方,通常都在效果之外,比如状态有没有收拢、失败后怎么兜底、以后要扩展时会不会牵一发动全身。
所以你练这篇内容时,别只看“现在能不能跑”,还要继续看“以后好不好改”。能把这个习惯养起来,你写出来的页面会比单纯照着示例拼出来的页面稳很多。
放进真实项目还要补什么
示例代码的重点是把核心思路讲明白,所以很多工程化细节会故意省掉。真正落到项目里时,你通常还要继续补接口联动、异常处理、边界保护、资源抽离,以及和其他页面状态之间的配合。
写在最后
比较稳的做法是分三步走:先把结构和职责立住,再把真实业务接进去,最后再优化视觉和交互体验。这样改出来的页面不只是“能演示”,而是真的更接近可以长期维护的业务代码。
更多推荐



所有评论(0)