在这里插入图片描述

引言

从咖啡店到餐厅再到书店,我们看到了经营模拟游戏的自然演进路径:提供的商品从「标准化饮品」到「定制化菜品」再到「可选择图书」,顾客行为从「即时消费」到「限时消费」再到「浏览-选择-购买」,商家策略从「管产能」到「管效率」再到「管选品和营销」。书店经营模拟器代表了模拟经营类游戏的一个新类型——零售/内容型经营。

在这个模拟器中,玩家扮演一位独立书店的老板,需要管理多达 10 本不同类型的图书(涵盖文学、科幻、历史、科普、计算机、商业六大类别),应对随机到来的顾客,满足他们对特定类别图书的阅读需求。新的游戏机制包括:

  • 图书分类浏览与筛选:顾客会明确寻找特定类别的图书,玩家需要合理配置书架上的品类。
  • 热度趋势系统:每本书有 1-5 星的热度评分,每天会随机波动,影响顾客的偏好分布。
  • VIP 会员机制:部分顾客是 VIP,享受 85 折优惠,但能带来更高的声望加成。
  • 三维升级体系:员工等级影响服务速度(缩短顾客耐心衰减)、广告等级影响客流量、装修等级影响顾客耐心。
  • 员工日薪成本:每天结束时扣除员工工资,增加了持续经营的成本压力。

在技术实现上,本示例延续了前两个模拟器的核心架构(@State 驱动、定时器循环、数组整体替换),同时引入了 Scroll 嵌套滚动、分类筛选逻辑、VIP 折扣计算、每日数据更新(dailyTrendUpdate)等新特性,让我们看到了 ArkTS 在处理更复杂业务逻辑时的灵活性和表达力。

这篇文章将继续按照系列文章的结构,全面拆解书店模拟器的实现细节,帮助你掌握分类筛选、动态定价、每日数据更新等进阶技术,进一步深化对 ArkTS 状态驱动型应用开发模式的理解。

1. 应用概述与功能

「书店经营模拟器」是一个以「图书品类匹配」为核心的零售经营模拟游戏。玩家需要根据顾客的类别偏好合理管理库存、调整进货节奏,通过升级员工、广告和装修三个维度提升经营效益。

页面自上而下分为五个主要区域:顶部导航栏(返回按钮 + 标题 + 营业状态切换)、资产信息卡片(金币/声望/天数/营业状态)、顾客等待区(顾客列表 + 耐心进度条 + 服务按钮)、分类浏览与书架(分类标签 + 图书列表 + 进货按钮)、底部操作栏(员工升级/广告升级/装修升级/营业状态)。

1.1 核心功能清单

  • 六大类图书管理:10 本图书分布在文学、科幻、历史、科普、计算机、商业六个类别,每本书有独立的定价、成本、库存和热度。
  • 分类筛选浏览:点击分类标签可以筛选该类别下的图书,便于快速管理特定品类的库存。
  • 顾客分类匹配:每个顾客随机选择一个类别,并可能指定该类别下的特定图书。如果指定图书有库存则完成交易,没有则无法服务。
  • VIP 会员系统:15% 的顾客是 VIP,享受 85 折优惠,但带来双倍声望加成(+2 vs +1)。
  • 每日热度更新:每天营业结束后,所有图书的热度随机波动 ±1(范围 1-5),模拟市场需求的变化。
  • 三维升级体系:员工等级(降低耐心衰减速度)、广告等级(增加客流量)、装修等级(增加顾客耐心上限),每项最高 5 级。
  • 进货成本管理:以图书成本价的 10 倍进货,每次 10 本,金币不足或库存已满时拒绝。
  • 日志系统:所有经营事件实时记录,支持查看最近的操作历史。

1.2 技术要点一览

本示例涉及的关键 ArkTS 技术点包括:

  • @State 数组与对象的深层嵌套bookListBook[] 数组,customerListCustomer103[] 数组,两者都需要通过整体替换触发 UI 刷新。
  • 分类筛选逻辑getFilteredBooks() 方法根据当前选中分类返回对应图书列表,用于条件渲染。
  • VIP 折扣计算:基于顾客 isVIP 状态动态计算实际售价。
  • 每日数据更新dailyTrendUpdate() 在每天结束时随机调整所有图书的热度值。
  • Scroll 嵌套滚动:分类标签使用横向 Scroll,书架区域使用纵向 Scroll 包裹。
  • 进度条的双重用途:日进度条和顾客耐心条,颜色根据状态动态变化。

2. 核心知识点

2.1 Book 接口:图书数据模型

Book 接口定义了书店中每本书的完整数据结构:

interface Book {
  id: number;
  title: string;
  author: string;
  category: string;
  price: number;
  cost: number;
  stock: number;
  maxStock: number;
  trend: number;
  icon: string;
}
  • id:唯一标识,用于库存扣减和顾客匹配。
  • title/author/category:图书基本信息,category 字段是分类筛选的关键。
  • price/cost:售价和进货成本,进货时使用 cost 计算价格。
  • stock/maxStock:当前库存和最大库存上限,用于库存管理和视觉反馈。
  • trend:热度评分(1-5 星),每天随机波动,影响顾客偏好。
  • icon:emoji 图标,增加视觉丰富度。

2.2 BookCategory 接口:分类数据模型

interface BookCategory {
  name: string;
  icon: string;
  books: number[];
}

每个分类包含名称、图标和该分类下所有图书的 ID 列表。books 数组存储图书 ID 而非引用,这样分类数据与图书数据完全解耦,便于维护。

2.3 Customer103 接口:顾客数据模型

interface Customer103 {
  id: number;
  wantCategory: string;
  wantBookId: number;
  patience: number;
  maxPatience: number;
  state: string;
  isVIP: boolean;
}
  • wantCategory:顾客想浏览的图书类别,决定了顾客会对哪些图书感兴趣。
  • wantBookId:顾客最终想买的具体图书 ID,服务时需要匹配。
  • patience/maxPatience:耐心值和最大耐心,耐心耗尽顾客离开。
  • state:顾客状态,目前只用 'browsing'(浏览中)一种状态。
  • isVIP:是否为 VIP 顾客,影响折扣和声望。

2.4 三维升级体系的协同效应

书店模拟器的三维升级各有侧重,需要协同发展:

升级维度 影响参数 公式 效果
员工等级 服务速度(耐心衰减) 400 - staffLevel * 50 ms/tick 每级减少 50ms 耐心衰减
广告等级 顾客生成间隔 max(1500, 4000 - adLevel * 400) ms 每级缩短 400ms 生成间隔
装修等级 顾客耐心上限 5000 + decorLevel * 1500 每级增加 1500 耐心值

协同效应分析

  • 广告等级越高 → 顾客来得越频繁 → 需要更高的员工等级来快速服务。
  • 装修等级越高 → 顾客耐心越长 → 给玩家更多反应时间 → 可以应对更多顾客。
  • 三个维度相互支撑,缺一不可。单一点某一项的边际效益会递减。

2.5 VIP 折扣与声望计算

VIP 顾客的购买逻辑:

const discount: number = c.isVIP ? 0.85 : 1;
const price: number = Math.round(book.price * discount);
this.reputation = Math.min(100, this.reputation + (c.isVIP ? 2 : 1));
  • VIP 享受 85 折优惠(price * 0.85)。
  • VIP 顾客带来双倍声望加成(+2 vs +1)。
  • Math.round 确保最终价格为整数,避免小数带来的复杂性。
  • VIP 的 15% 折扣似乎会降低单笔利润,但双倍声望加成和更高的复购率(VIP 更可能再次光顾)弥补了这一损失。

2.6 每日热度更新机制

每天营业结束后调用 dailyTrendUpdate()

private dailyTrendUpdate(): void {
  const newBooks: Book[] = this.bookList.slice();
  for (let i = 0; i < newBooks.length; i++) {
    let t: number = newBooks[i].trend;
    t += Math.floor(Math.random() * 3) - 1;
    t = Math.max(1, Math.min(5, t));
    newBooks[i].trend = t;
  }
  this.bookList = newBooks;
}

每本书的热度随机波动 -1、0 或 +1,然后限制在 [1, 5] 范围内。这个机制模拟了市场需求的不确定性——今天热门的书明天可能降温,冷门的书可能突然升温。玩家需要根据热度变化调整进货策略:热度高的书多进货,热度低的书少进货甚至清仓。

2.7 分类筛选的实现

分类筛选通过 activeCategory 状态变量和 getFilteredBooks() 方法实现:

private selectCategory(name: string): void {
  this.activeCategory = name;
}

private getFilteredBooks(): Book[] {
  if (this.activeCategory === '全部') {
    return this.bookList;
  }
  const result: Book[] = [];
  for (let i = 0; i < this.bookList.length; i++) {
    if (this.bookList[i].category === this.activeCategory) {
      result.push(this.bookList[i]);
    }
  }
  return result;
}
  • activeCategory 初始为 '全部',显示所有图书。
  • 点击分类标签时调用 selectCategory 更新选中分类。
  • getFilteredBooks 返回匹配当前分类的图书列表。
  • ForEach(this.getFilteredBooks(), ...) 动态渲染筛选后的图书列表。

注意getFilteredBooks() 是一个普通方法而非状态变量,因此每次 activeCategorybookList 变化时,ForEach 会自动重新计算并渲染。这种「方法返回动态数据」的模式在 ArkUI 中完全支持——只要方法内部使用了 @State 变量,ArkUI 的响应式系统会追踪依赖关系。

2.8 顾客生成的「类别匹配」策略

private spawnCustomer(): void {
  const cats: BookCategory[] = this.categoryList;
  const catIdx: number = Math.floor(Math.random() * (cats.length - 1)) + 1;
  catName = cats[catIdx].name;
  const cat: BookCategory = cats[catIdx];
  let bookId: number = 0;
  if (cat.books.length > 0) {
    const bIdx: number = Math.floor(Math.random() * cat.books.length);
    bookId = cat.books[bIdx];
  }
  const isVIP: boolean = Math.random() < 0.15;
  // ... 创建顾客
}

顾客生成的随机性体现在三个层面:

  1. 类别随机:从分类列表(不含「全部」)中随机选一个类别。
  2. 图书随机:从该类别的图书 ID 列表中随机选一本具体图书。
  3. VIP 随机:15% 概率成为 VIP 顾客。

这种分层随机策略确保了顾客需求的多样性,同时保持了合理性——顾客只选已知的类别和图书。

2.9 顾客耐心衰减与服务速度

顾客耐心的衰减由 tickTimer 驱动(400ms 间隔),每 tick 减少 400。初始耐心为 5000 + decorLevel * 1500

  • 装修等级 1:耐心 6500,约 16 秒
  • 装修等级 3:耐心 9500,约 24 秒
  • 装修等级 5:耐心 12500,约 31 秒

相比咖啡店和餐厅,书店的顾客耐心更高,因为「浏览和选择」本身是书店的重要体验。但如果玩家不及时服务(没有及时点击「服务」按钮),顾客仍然会离开。

2.10 横向滚动分类标签

Scroll() {
  Row() {
    ForEach(this.categoryList, (item: BookCategory) => {
      Text(item.icon + item.name)
        .onClick(() => {
          this.selectCategory(item.name);
        })
    }, (item: BookCategory) => item.name)
  }
}
.scrollBar(BarState.Off)

分类标签使用横向 Scroll 包裹 Row,当分类数量超过屏幕宽度时可以左右滑动查看。.scrollBar(BarState.Off) 隐藏滚动条,提供更干净的视觉体验。选中的分类会高亮显示(深色背景 + 白色文字)。

3. 源码逐段解析

3.1 接口定义与数据模型

源码顶部定义了三个接口(Book、BookCategory、Customer103),分别对应图书、分类和顾客实体。设计上值得注意的是:

  • Book 接口中的 category 字段是字符串,用于与 BookCategory 的 name 关联。这种「字符串关联」比 ID 关联更直观,但也要求分类名称保持唯一。
  • BookCategory 的 books 字段存储图书 ID 数组,形成了「分类 → 图书」的聚合关系。
  • Customer103 的 wantBookId 为 0 时表示顾客没有具体的图书偏好(只浏览类别),但当前代码生成顾客时总是会分配一个具体的图书 ID。

3.2 状态变量分组

@State 变量按功能分为六组:

资产与进度组gold=1000reputation=50day=1dayLength=150isOpen=truedayProgress=0。初始金币 ¥1000 比前两个模拟器多,因为图书的品类扩展需要更多启动资金。

业务组bookList=[]categoryList=[]customerList=[]。三个核心业务列表。

筛选组activeCategory='全部'。控制书架区域的分类筛选。

升级组staffLevel=1adLevel=1decorLevel=1。三维升级等级。

统计组totalEarnedtotalSpentbooksSoldcustomersLost

其他logTextshowBookDetailcurrentBookshowRestock。预留了弹窗功能的状态变量。

3.3 initData:图书与分类初始化

initData 方法完成两项关键数据初始化:

  1. 初始化 10 本图书:覆盖六大类别,价格从 ¥38(小王子)到 ¥128(算法导论),成本从 ¥12 到 ¥50,初始库存从 8 到 25 不等。
  2. 初始化 7 个分类:包括「全部」(聚合所有图书)和 6 个具体类别,每个类别指定其包含的图书 ID 列表。

图书选择覆盖了真实的畅销书单:三体、活着、百年孤独、人类简史、小王子等经典作品,以及技术类图书(Node.js、算法导论)和商业类图书(原则)。这种混合品类设计给玩家提供了丰富的选品策略空间。

3.4 生命周期方法

aboutToAppear 依次调用 initData()startDay()aboutToDisappear 调用 stopTimers() 清理三组定时器。

3.5 startDay:启动营业

startDay 重置日进度、设置营业状态、记录日志,并启动三组定时器:

  1. dayTimer(400ms):推进日进度,到 150 时结束营业。
  2. customerTimer(动态间隔):按广告等级调整频率。
  3. tickTimer(400ms):更新顾客耐心。

3.6 endDay:结束营业

endDay 完成以下操作:

  1. 设置 isOpen = false
  2. 清理三组定时器。
  3. 扣除员工日薪:salaries = 30 * staffLevel,等级越高工资越多。
  4. 记录日志。
  5. 天数 +1。
  6. 调用 dailyTrendUpdate() 更新所有图书的热度。

3.7 nextDay:新一天

nextDay 清空顾客列表(所有等待中的顾客在闭店时自动离开),然后调用 startDay() 开始新的一天。与餐厅模拟器不同,书店不需要重建商品数据——图书库存保留。

3.8 serveCustomer:服务顾客

服务顾客是书店模拟器的核心交互,逻辑如下:

  1. 检查顾客是否为 browsing 状态(目前所有顾客都是这个状态)。
  2. 通过 getBookById 获取顾客想要的图书。
  3. 如果图书不存在或库存为 0,提示售罄。
  4. 计算 VIP 折扣价并扣除库存。
  5. 更新财务数据、销售统计和声望。
  6. 记录详细日志。
  7. 从顾客列表中移除该顾客。

3.9 restock:进货

private restock(bookId: number): void {
  const book: Book | null = this.getBookById(bookId);
  const amount: number = 10;
  const cost: number = book.cost * amount;
  if (this.gold < cost) {
    this.log('金币不足!');
    return;
  }
  if (book.stock + amount > book.maxStock) {
    this.log('库存已满');
    return;
  }
  // 扣金币、加库存、记录日志
}

进货以 10 本为单位,每本按成本价计算。如果金币不足或加上 10 本会超过最大容量,则拒绝进货。这种「数量固定、成本固定」的设计简化了交互逻辑,玩家不需要输入购买数量。

3.10 升级方法:三维成长

三个升级方法结构相似:

  • upgradeStaff:费用 150 * staffLevel,最高 5 级。
  • upgradeAd:费用 200 * adLevel,最高 5 级。
  • upgradeDecor:费用 180 * decorLevel,最高 5 级。

每项升级都检查金币是否充足和等级是否已满,成功后更新等级并记录日志。升级费用随等级线性递增(1 级 → 2 级的费用是 当前等级 × 基础费用)。

3.11 build():完整 UI 布局

build() 方法的 UI 结构按自上而下的顺序:

顶部导航栏:深蓝背景,三元素布局(返回按钮 + 居中标题 + 营业状态按钮)。营业状态按钮的 enabled 属性绑定 !this.isOpen——营业中时禁用(因为已经在营业中,不需要再点击「营业中」按钮),歇业后启用(可以点击开始新一天)。

资产信息卡片:三列布局,金币(金色)、声望(浅蓝色)、天数与营业状态(白色+状态色)。

统计信息栏:一行文本展示售出数、流失数、员工等级和广告等级。

日进度条:全宽进度条,蓝色填充。

顾客等待区:条件渲染——空时显示「暂无顾客」占位;有顾客时渲染顾客列表。每个顾客卡片显示 VIP 标识、需求类别、服务按钮和耐心进度条(耐心 < 2000 时变为红色)。

分类浏览:横向滚动的分类标签,当前选中的分类高亮显示。

书架:渲染 getFilteredBooks() 返回的图书列表。每本书展示图标、标题、作者、类别、库存、价格、热度星级和进货按钮。库存低于 5 时库存数字变为红色,提醒玩家补货。

日志区:显示日志文本,限制 4 行。

底部操作栏:四个按钮等宽分布,最后一个按钮在「营业中」/「新一天」之间切换。

4. 关键实现细节分析

4.1 分类筛选与 ForEach 的动态响应

ForEach(this.getFilteredBooks(), ...) 是一个有趣的实现——它不是直接绑定一个 @State 数组,而是调用一个方法返回结果。这意味着:

  1. activeCategory 变化时,getFilteredBooks() 返回不同的数组,ForEach 重新渲染。
  2. bookList 更新时(如库存变化),getFilteredBooks() 遍历最新数据,ForEach 也会重新渲染。
  3. 这种模式比绑定一个单独的 filteredBooks 状态变量更简洁——不需要手动维护筛选后的列表。

4.2 顾客类别匹配的局限性

当前实现中,顾客生成时随机分配一个类别和该类别下的一本具体图书。但在服务时,只要顾客想要的图书有库存就可以售出,不会检查图书是否属于顾客最初的类别。这种简化的设计在大多数情况下不会产生问题——因为顾客被分配的图书确实属于他被分配的类别。但如果未来需要更真实的「顾客可能逛到其他类别的书」,可以在服务时增加类别匹配检查。

4.3 VIP 顾客的概率与价值

VIP 顾客以 15% 的概率生成,带来以下影响:

  • 收入:单价降低 15%,但不影响销售数量。
  • 声望:双倍声望加成(+2 vs +1)。
  • 顾客体验:VIP 的耐心衰减与普通顾客相同,但成功服务后的声望反馈更好。

假设每天服务 20 位顾客,其中 3 位 VIP:

  • 普通顾客:17 × 1 = 17 声望
  • VIP 顾客:3 × 2 = 6 声望
  • 总计:23 声望

如果没有 VIP 机制,20 位顾客只能获得 20 声望。VIP 机制在不增加服务数量的前提下提升了声望获取效率,让玩家有动力服务好每一位 VIP 顾客。

4.4 图书热度的「软影响」设计

当前热度值仅作为展示信息(★★★★★),并没有直接影响游戏逻辑。这是一种「软影响」设计——热度可以帮助玩家判断进货优先级,但不会强制影响顾客行为。如果希望让热度影响游戏玩法,可以扩展为:

  • 热度 5 星的图书顾客更可能指定购买。
  • 热度 1 星的图书打折销售。
  • 热度变化与顾客生成概率挂钩。

4.5 员工日薪与成本控制

员工日薪 salaries = 30 * staffLevel

  • Lv.1:¥30/天
  • Lv.2:¥60/天
  • Lv.3:¥90/天
  • Lv.4:¥120/天
  • Lv.5:¥150/天

升级员工等级的费用为 150 * staffLevel

  • 1 → 2:¥150
  • 2 → 3:¥300
  • 3 → 4:¥450
  • 4 → 5:¥600

总投入 ¥1500 获得 Lv.5 员工,日薪 ¥150。如果 Lv.5 能带来显著的服务效率提升(更多顾客被快速服务),那么额外的日薪成本是值得的。但如果顾客流量不大,升级员工可能导致亏损——这就是经营策略的核心挑战。

4.6 关于 Scroll 嵌套的注意事项

源码中存在 Scroll 嵌套:外层是纵向 Scroll(包裹整个内容区),内层是横向 Scroll(分类标签)。在 ArkUI 中,嵌套滚动需要注意:

  1. 内外层滚动方向不同(纵向 vs 横向),不会产生冲突。
  2. 如果是同方向嵌套(如纵向套纵向),需要使用 NestedScrollMode 配置滚动接管行为。
  3. 当前实现的嵌套是安全的,横向滚动区域和纵向滚动区域互不干扰。

4.7 进度条颜色的动态切换

顾客耐心进度条使用了动态颜色:

Progress({ value: item.patience, total: item.maxPatience, type: ProgressType.Linear })
  .color(item.patience < 2000 ? '#F44336' : '#4CAF50')

耐心值低于 2000 时(约 5 秒内耗尽)变为红色(#F44336),提醒玩家紧急服务。这种「视觉预警」是良好 UX 设计的体现。

4.8 图书库存的视觉反馈

图书列表中的库存数字会根据库存水平变色:

Text('库存:' + item.stock + '/' + item.maxStock)
  .fontColor(item.stock < 5 ? '#F44336' : '#BBDEFB')

库存低于 5 时变为红色,提示玩家及时补货。这种「动态颜色反馈」在库存管理中非常有效——玩家可以快速扫描列表找到需要补货的图书。

4.9 日志系统的限制与改进方向

日志系统仍然使用 500 字符截断的策略。对于书店模拟器来说,日志截断可能导致重要的进货和销售记录丢失。可以考虑改进为:

  • 使用滚动列表展示最近 N 条日志。
  • 区分日志等级(重要事件高亮)。
  • 支持日志导出或持久化。

4.10 代码模式的一致性

三个模拟器在以下方面保持了一致的代码模式:

  1. 生命周期管理aboutToAppear 初始化数据并开始营业,aboutToDisappear 清理定时器。
  2. 定时器模式:三组定时器(日进度、顾客生成、更新),ID 存储在私有变量中。
  3. 数组更新:构建新数组 → 整体替换引用。
  4. 日志系统log() 方法统一格式 [天数] 消息,限制 500 字符。
  5. UI 结构:顶部导航栏 + 资产卡片 + 进度条 + 业务区域 + 底部操作栏。

这种一致性为后续扩展更多模拟器奠定了良好的基础。

5. 运行效果与操作指南

5.1 运行步骤

在 DevEco Studio 中打开工程,确认 main_pages.json 已注册 Index103 页面,从入口页导航进入即可体验书店模拟器。

建议按以下步骤体验:

  1. 初始观察:页面打开后立即开始营业,等待顾客到来。初始金币 ¥1000,声望 50。
  2. 快速服务:顾客到来后点击「服务」按钮。如果图书有库存,交易成功。注意耐心条颜色变化——变红前优先服务。
  3. 分类管理:点击不同分类标签查看该类别下的图书库存。如果某类图书库存不足,及时进货。
  4. VIP 识别:注意顾客的 VIP 标识(👑),VIP 顾客服务后获得双倍声望。
  5. 升级决策:根据经营状况选择升级方向。如果顾客来得太快(应付不过来),升级员工或广告。如果顾客耐心太短(来不及服务),升级装修。
  6. 每日检查:每天结束后查看日志,了解当天的经营情况。次日图书热度会变化,关注高热图书的库存。

5.2 典型经营策略

以下是三种典型的经营策略:

均衡发展策略:三项升级交替进行,每天投入 ¥150-200 用于升级。优点是全面发展,缺点是每项升级进度较慢。

流量优先策略:优先升级广告等级(Lv.3+),快速增加客流量。然后升级员工提升服务能力。优点是营业额增长快,缺点是初期投入大。

体验优先策略:优先升级装修等级(Lv.3+),增加顾客耐心。适合操作不熟练的玩家,有更多反应时间。缺点是客流量没有显著提升。

5.3 进货管理技巧

  • 关注库存低于 5 的图书:红色数字提示需要补货。
  • 热度高的图书优先进货:5 星热度的图书更容易被顾客购买。
  • 保持畅销书库存:三体、活着、小王子等畅销书建议保持 15+ 库存。
  • 计算机类图书备货:算法导论等高价图书虽然销量低,但利润高,建议保持 8+ 库存。

5.4 异常情况处理

  • 图书售罄:顾客指定的图书没有库存时,日志提示「已售罄」,顾客不会离开(仍然等待)。
  • 金币不足进货:日志提示「金币不足!」,进货被拒绝。
  • 库存已满:进货会超出 maxStock 上限时,日志提示「库存已满」。
  • 顾客耐心耗尽:日志提示「顾客因失去耐心离开」,声望 -1。

6. 可扩展方向

6.1 引入更丰富的顾客行为

当前顾客只会「指定图书购买」。可以扩展为:

  • 浏览型顾客:只浏览不购买,增加人气但不直接产生收入。
  • 犹豫型顾客:需要多次点击「服务」才能完成交易。
  • 团购顾客:一次购买多本书,需要批量库存管理。
  • 会员顾客:办理月卡/年卡,享受折扣但需要预付费用。

6.2 引入图书热度的动态影响

让热度真正影响游戏玩法:

  • 热度 5 星的图书,顾客购买概率翻倍。
  • 热度 1 星的图书,自动打折促销。
  • 热度变化触发「市场事件」:如「三体获雨果奖,科幻类图书热度 +2」。

6.3 增加促销与定价策略

  • 打折促销:选择图书设置折扣价,吸引更多顾客。
  • 组合套装:将多本书打包销售,提供套装价。
  • 动态定价:根据热度和库存自动调整价格。
  • 限时特价:每天随机选一本书做特价。

6.4 引入员工管理系统

与餐厅模拟器的员工系统类似:

  • 雇佣额外员工,每人负责一个分类区域。
  • 员工有技能加成(如某员工擅长文学类,该类图书销售更快)。
  • 员工满意度系统:长时间工作需要休假或加薪。

6.5 扩展图书品类

在现有 10 本图书的基础上:

  • 扩展到 20-30 本图书,覆盖更多类别(如艺术、旅游、心理等)。
  • 引入「新书上架」机制:每周更新图书列表。
  • 引入「绝版图书」机制:某些图书卖完后不再补货,增值保值。

6.6 引入成就系统

定义成就目标,如:

  • 「单日销售 100 本」
  • 「声望达到 100」
  • 「集齐所有图书类别」
  • 「连续 7 天无顾客流失」

6.7 持久化与多存档

将经营数据持久化到本地存储:

  • 自动存档:每天结束时保存。
  • 多存档位:支持 3-5 个独立存档。
  • 读档/存档界面:独立页面管理存档。

7. 常见问题与调试技巧

7.1 顾客不出现

检查 customerTimer 是否正常运行。确认广告等级对应的生成间隔(初始 4000ms = 4 秒)。确认当前是否在营业中。

7.2 分类筛选不刷新

检查 activeCategory 是否被正确更新。确认 getFilteredBooks() 返回了正确的结果。如果 ForEach 不刷新,检查是否使用了 ForEach(this.getFilteredBooks(), ...) 的正确写法。

7.3 服务顾客失败

确认顾客想要的图书存在且有库存。检查顾客状态是否为 browsing。检查 serveCustomer 的参数 idx 是否有效。

7.4 进货按钮无反应

确认图书库存没有超过 maxStock 上限。确认金币足够(图书成本 × 10)。检查按钮的 onClick 是否正确绑定了 restock(item.id)

7.5 升级后看不到效果

确认升级是否成功(检查日志和等级显示)。广告升级后需要等待新的顾客生成周期才能感受到频率变化。装修升级后顾客耐心增加,需要观察耐心条。

7.6 日志显示不全

日志使用 maxLines(4) 限制行数。如果需要显示更多日志,可以增加 maxLines 值或改用滚动列表。

7.7 UI 布局错乱

检查 Scroll 嵌套是否正确。确认所有组件的 widthpaddingmargin 设置合理。在不同屏幕尺寸上测试布局。

7.8 定时器泄漏

如果页面切换回来后出现异常行为,检查 aboutToDisappear 是否正确调用了 stopTimers()。确认所有定时器 ID 都被正确清理。

8. 总结

示例 103「书店经营模拟器」用约 660 行代码,构建了一个完整的图书零售经营模拟系统。回顾本文重点:

  • 分类筛选系统activeCategory + getFilteredBooks() + ForEach 实现了动态图书筛选,代码简洁高效。
  • VIP 与普通顾客的差异化处理:VIP 折扣(85 折)和声望加成(×2)为游戏增加了策略层次。
  • 每日数据更新dailyTrendUpdate() 让图书热度每天随机波动,模拟市场需求变化,增加了游戏的不确定性和可玩性。
  • 三维升级体系:员工等级、广告等级、装修等级各有侧重,需要协同发展,体现了经营管理的多维度平衡。
  • 横向滚动分类标签Scroll + Row + ForEach 的组合实现了优雅的横向滚动选择器。
  • 可扩展的数据模型:Book、BookCategory、Customer103 三个接口的设计为后续扩展留下了充足空间。

从咖啡店到餐厅再到书店,我们见证了模拟经营系列的演进:从标准化服务到定制化服务再到内容型零售,从简单顾客队列到多状态餐桌管理再到分类浏览与 VIP 机制。每一个新模拟器都在前一个的基础上引入新的机制和技术点,同时保持了核心架构的一致性。

下一篇文章将介绍「服装店经营模拟器」,它将引入尺码管理、季节性服装和动态定价机制,为模拟经营系列继续拓展边界。希望这篇博客能帮你掌握分类筛选、VIP 系统、每日数据更新等进阶 ArkTS 技能,并在自己的项目中灵活应用。

Logo

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

更多推荐