一、引言

购物模拟器是系列中第一个引入多页分类(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');

这行代码是价格解析的核心,包含三个关键部分:

  1. item.match(/\d+/):使用正则表达式 \d+ 匹配字符串中的数字序列
  2. ?.[0]:可选链操作符,取第一个匹配结果
  3. || '0':如果未匹配到,默认为’0’

例如,'🍎 苹果 5元' 经过解析:

  1. match(/\d+/)['5']
  2. [0]'5'
  3. 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 数组中的商品逐个 pushbag 数组,然后清空 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在"电商类模拟"场景下的开发模式:

  1. 多页分类:使用 page 索引 + 二维数组实现Tab切换
  2. 正则解析item.match(/\d+/)?.[0] 从字符串中提取价格
  3. 购物车与背包:临时状态与永久状态的分离管理
  4. 守卫逻辑:余额不足时阻止购买
  5. 空状态处理:购物车为空的校验

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐