# [特殊字符] 购物模拟器 — 鸿蒙ArkTS完整技术解析
·
一、引言
购物模拟器是系列中第一个引入多页分类(Tab切换)和购物车机制的应用。它将模拟购物体验分为三个关键环节:
浏览商品(分类切换)→ 加入购物车 → 结算购买 → 放入背包
从技术角度看,购物模拟器引入了几个新的ArkTS概念:
- 多页切换:通过
page状态变量控制显示哪个分类 - 正则表达式解析:从字符串中提取价格数字
- 购物车临时状态:与背包(永久状态)的分离
- 列表渲染:使用
ForEach渲染商品列表 - 背包管理:已购物品的展示和清理
二、需求分析
2.1 功能需求
| 需求ID | 功能 | 描述 |
|---|---|---|
| F1 | 分类浏览 | 水果/食品/百货三类商品 |
| F2 | 商品列表 | 每类5件商品,显示名称和价格 |
| F3 | 加入购物车 | 扣钱后将商品放入购物车 |
| F4 | 购物车结算 | 将购物车内所有商品放入背包 |
| F5 | 背包管理 | 查看已购物品 |
| F6 | 余额管理 | 动态更新余额 |
| F7 | 余额不足 | 买不起时给出提示 |
2.2 数据模型
private shopItems: string[][] = [
['🍎 苹果 5元', '🍌 香蕉 3元', '🍇 葡萄 8元', '🍊 橙子 4元', '🍉 西瓜 12元'],
['🥛 牛奶 6元', '🍞 面包 4元', '🧀 奶酪 15元', '🥚 鸡蛋 2元', '🧈 黄油 10元'],
['🧸 玩具 30元', '📚 书本 25元', '👕 衣服 50元', '🎧 耳机 80元', '🎮 游戏 100元']
];
使用二维数组管理商品数据:
- 第一维:分类索引(水果/食品/百货)
- 第二维:具体商品(字符串形式,包含名称、emoji和价格)
三、多页分类切换
3.1 分类状态
@State page: number = 0;
一个简单的数字索引控制显示哪个分类的页面。
3.2 分类Tab实现
Row() {
ForEach(this.categoryNames, (name: string, idx: number) => {
Button(name)
.fontSize(13)
.backgroundColor(this.page === idx ? '#FF9800' : '#BDBDBD')
.margin({ right: 5 })
.onClick(() => {
this.page = idx;
})
})
}
Tab切换的核心机制:通过 this.page === idx 条件动态设置按钮颜色。选中时显示橙色(#FF9800),未选中时显示灰色(#BDBDBD)。这在UI设计中称为"活性指示器"(Active Indicator)。
3.3 商品列表渲染
Column() {
ForEach(this.shopItems[this.page], (item: string) => {
Row() {
Text(item)
.fontSize(16)
.width('60%')
Button('➕ 加入购物车')
.fontSize(12)
.backgroundColor('#4CAF50')
.onClick(() => {
// 处理购买逻辑
})
}
.width('90%')
.justifyContent(FlexAlign.SpaceBetween)
.padding(8)
.margin({ bottom: 5 })
.backgroundColor('#FAFAFA')
.borderRadius(8)
})
}
当 page 变化时,this.shopItems[this.page] 获取对应分类的商品数组,ForEach 自动重新渲染列表。
四、价格解析算法
4.1 从字符串提取数字
let price = parseInt(item.match(/\d+/)?.[0] || '0');
这行代码是价格解析的核心,包含三个关键部分:
item.match(/\d+/):使用正则表达式\d+匹配字符串中的数字序列?.[0]:可选链操作符,取第一个匹配结果|| '0':如果未匹配到,默认为’0’
例如,'🍎 苹果 5元' 经过解析:
match(/\d+/)→['5'][0]→'5'parseInt('5')→5
4.2 正则表达式详解
/\d+/:
\d:匹配任何数字字符(0-9)+:匹配一个或多个- 斜杠
/:正则字面量分隔符
4.3 购买逻辑的完整流程
.onClick(() => {
let price = parseInt(item.match(/\d+/)?.[0] || '0');
if (this.money < price) {
this.message = '余额不足,买不起这个了😅';
return;
}
this.money -= price;
this.cart.push(item);
this.message = '加入了购物车:' + item;
})
流程:解析价格→检查余额→扣除余额→加入购物车→显示反馈。
五、购物车与背包系统
5.1 购物车(临时)vs 背包(永久)
@State cart: string[] = []; // 购物车 - 结算前
@State bag: string[] = []; // 背包 - 已购
两个数组的核心区别:
| 维度 | 购物车(cart) | 背包(bag) |
|---|---|---|
| 状态 | 临时 | 永久 |
| 可清空 | 结算后自动清空 | 手动清空 |
| 数据来源 | "加入购物车"操作 | 购物车结算 |
| 展示 | 结算按钮上计数 | 页面底部展示 |
5.2 结算逻辑
Button('🧾 结算购物车 (' + this.cart.length + ' 件)')
.onClick(() => {
if (this.cart.length === 0) {
this.message = '购物车是空的,去买点东西吧!';
return;
}
for (let item of this.cart) {
this.bag.push(item);
}
this.message = '🛍️ 购买成功!共买了 ' + this.cart.length + ' 件商品';
this.cart = []; // 清空购物车
})
结算的实质是将 cart 数组中的商品逐个 push 到 bag 数组,然后清空 cart。这是一个经典的"暂存→提交"模式。
5.3 背包展示
if (this.bag.length > 0) {
Text('已购物品:')
.fontSize(14)
.fontWeight(FontWeight.Bold)
.margin({ top: 10 })
ForEach(this.bag, (item: string) => {
Text(' ✅ ' + item)
.fontSize(13)
.fontColor('#666')
})
Button('🗑️ 清空背包')
.onClick(() => {
this.bag = [];
this.message = '背包已清空!';
})
}
背包展示采用 ForEach 渲染列表,每个商品前加 ✅ 标记。清空背包按钮提供批量删除功能。
六、UI布局与交互
6.1 余额展示
Text('💰 ' + this.money + ' 元')
.fontSize(20)
.fontWeight(FontWeight.Bold)
.fontColor('#FF9800')
余额使用较大字号和橙色强调,使用户时刻关注自己的财务状况。
6.2 购物车计数器
Button('🧾 结算购物车 (' + this.cart.length + ' 件)')
购物车中的商品数量直接显示在按钮文案中,这种"徽标式"设计让用户一目了然地知道待结算商品数量。
6.3 空状态处理
if (this.cart.length === 0) {
this.message = '购物车是空的,去买点东西吧!';
}
对购物车为空的情况进行特殊处理,避免无意义的结算操作。
七、数据流分析
7.1 完整数据流
初始:money=500, cart=[], bag=[]
操作1:加入购物车"苹果5元"
→ price=5, money>=5 ✅
→ money=495, cart=['🍎 苹果 5元']
操作2:加入购物车"面包4元"
→ price=4, money>=4 ✅
→ money=491, cart=['🍎 苹果 5元', '🍞 面包 4元']
操作3:结算
→ cart.length=2, 遍历push到bag
→ bag=['🍎 苹果 5元', '🍞 面包 4元']
→ cart=[]
7.2 边界条件
| 场景 | 结果 |
|---|---|
| 余额刚好够买 | 成功购买 |
| 余额差1元 | 提示"余额不足" |
| 购物车为空结算 | 提示"购物车是空的" |
| 背包为空清空 | 无操作(无按钮) |
八、与打工模拟器的联动
购物模拟器和打工模拟器天然构成了一个完整的经济生态系统:
打工模拟器:赚钱 → 金额积累在money中
购物模拟器:花钱 → 消耗money获取商品
虽然当前两个应用是独立的,但在设计上共享 money 变量含义——这正是未来整合的基础。
九、扩展思路
9.1 折扣系统
@State discount: number = 0.9; // 9折
let finalPrice = Math.floor(price * this.discount);
9.2 搜索功能
@State searchQuery: string = '';
@State filteredItems: string[] = [];
// 在build中
ForEach(this.shopItems[this.page].filter(item =>
item.includes(this.searchQuery)
), (item) => { ... })
9.3 购买记录
使用 PersistentStorage 持久化购买记录:
PersistentStorage.persistProp('purchase_history', []);
@StorageLink('purchase_history') purchaseHistory: string[] = [];
十、总结
购物模拟器展示了ArkTS在"电商类模拟"场景下的开发模式:
- 多页分类:使用
page索引 + 二维数组实现Tab切换 - 正则解析:
item.match(/\d+/)?.[0]从字符串中提取价格 - 购物车与背包:临时状态与永久状态的分离管理
- 守卫逻辑:余额不足时阻止购买
- 空状态处理:购物车为空的校验
更多推荐



所有评论(0)