一、引言
购物模拟器是系列中第一个引入多页分类(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]从字符串中提取价格 - 购物车与背包:临时状态与永久状态的分离管理
- 守卫逻辑:余额不足时阻止购买
- 空状态处理:购物车为空的校验