Flutter三方库适配OpenHarmony【rock_paper_scissors】猜拳小游戏项目完整实战

前言

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net

rock_paper_scissors 是一个经典的石头、剪刀、布 Flutter 小游戏。玩家从 rock、paper、scissors 三个选项中选择一个,电脑通过随机数生成出拳,应用延迟 500ms 后判断胜负,并分别统计玩家胜场、电脑胜场和平局次数。

这个项目虽然规则简单,但很适合练习 Flutter 的 状态管理、随机数、延迟反馈、条件渲染、统计卡片、GestureDetector 点击、Emoji 文本展示和 OpenHarmony 触摸适配。同时,它也暴露了一些真实工程边界,例如 _isPlaying 只被赋值但没有参与防重复点击,延迟回调没有 mounted 保护,连续点击可能叠加多次判定。

在这里插入图片描述

图示说明:本文围绕 Flutter 工程中的 rock_paper_scissors 项目展开,重点分析出拳数据结构、随机电脑选择、胜负判断、比分统计、UI 布局和 OpenHarmony 适配关注点。

猜拳小游戏的关键不是规则本身,而是“玩家选择、电脑选择、延迟揭晓、结果统计”这一轮交互是否清晰稳定。

本文将基于项目真实源码展开,核心内容包括:

  • RockPaperScissorsApp 的应用入口与深紫色 Material 3 主题
  • _choices 如何定义 rock、paper、scissors 三个选项
  • _play() 如何生成电脑随机出拳并进入本轮状态
  • _determineWinner() 如何判断胜负和平局
  • _buildStatCard() 如何复用比分统计卡片
  • _getChoiceIcon() 如何把出拳名称转换成 Emoji
  • UI 如何展示玩家、VS、电脑和结果
  • _isPlaying 未参与点击锁定的真实边界
  • OpenHarmony 适配时需要验证的 Emoji、点击和延迟回调

一、项目背景与目标

1.1 游戏定位

rock_paper_scissors 是一个轻量猜拳游戏。玩家点击底部三个选项之一后,电脑随机选择一个选项,页面展示双方出拳,并在短暂延迟后给出结果。

从玩家视角看,流程是:

  1. 打开应用,看到比分和三个出拳选项。
  2. 点击 rock、paper 或 scissors。
  3. 页面显示玩家选择和电脑选择。
  4. 500ms 后显示本轮结果。
  5. 比分卡片同步更新。
  6. 玩家可以继续下一轮。

从工程视角看,流程是:

  1. 定义三种出拳数据。
  2. 玩家点击触发 _play()
  3. 随机生成电脑选择。
  4. 写入玩家和电脑出拳状态。
  5. 延迟 500ms 调用胜负判断。
  6. 根据结果更新比分。
  7. UI 根据状态重新渲染。

1.2 当前功能概览

功能 当前实现 技术点
应用入口 runApp(const RockPaperScissorsApp()) Flutter 启动流程
应用主题 深紫色 Material 3 ColorScheme.fromSeed
出拳选项 rock / paper / scissors List<Map<String, String>>
电脑出拳 随机 0 到 2 math.Random()
胜负判断 平局、玩家赢、电脑赢 条件分支
延迟揭晓 500ms 后判定 Future.delayed
比分统计 You / Draws / Computer 三个 Card
图标展示 Emoji 文本 rock、paper、scissors 图标
点击交互 GestureDetector 底部三选项

1.3 适合学习的能力

这个项目适合学习:

  • 小游戏状态建模
  • 随机选择逻辑
  • 条件分支胜负判断
  • 延迟反馈设计
  • 统计卡片复用
  • GestureDetector 点击事件
  • Emoji 文本渲染
  • OpenHarmony 下触摸与字体表现验证

二、环境准备与工程结构

2.1 技术栈概览

项目使用 Flutter SDK 和 Dart 标准库能力,没有复杂插件。

类别 当前使用 说明
开发语言 Dart Flutter 应用主语言
UI 框架 Flutter Material 页面、卡片、文本、容器
随机数 dart:math 电脑随机出拳
状态管理 StatefulWidget + setState 游戏状态刷新
点击输入 GestureDetector 玩家出拳
延迟反馈 Future.delayed 延迟判定结果
图标显示 Emoji 文本 出拳视觉表达
目标适配 Flutter / OpenHarmony UI、触摸、字体验证

2.2 pubspec 关键配置

工程配置如下:

environment:
  sdk: ^3.9.2

dependencies:
  flutter:
    sdk: flutter
  cupertino_icons: ^1.0.8

dev_dependencies:
  flutter_test:
    sdk: flutter
  flutter_lints: ^5.0.0

flutter:
  uses-material-design: true

当前项目虽然使用 Emoji 作为出拳图标,但仍然需要 Material 组件和 Material Icons 支持基础页面。

2.3 主源码结构

核心代码集中在 lib/main.dart

import 'package:flutter/material.dart';
import 'dart:math' as math;

void main() {
  runApp(const RockPaperScissorsApp());
}

主要结构如下:

结构 类型 作用
RockPaperScissorsApp StatelessWidget 应用根组件
RockPaperScissorsHomePage StatefulWidget 猜拳首页
_RockPaperScissorsHomePageState State 管理出拳、结果和比分

2.4 常用运行命令

完成 Flutter 环境准备后,可以执行:

flutter pub get
flutter analyze
flutter test
flutter run

OpenHarmony 环境运行时,需要结合本地 Flutter OpenHarmony 发行版、DevEco Studio、设备连接和签名配置。

三、应用入口与主题配置

3.1 main 函数

应用入口如下:

void main() {
  runApp(const RockPaperScissorsApp());
}

runApp 会把根组件挂载到 Flutter 渲染树。

3.2 RockPaperScissorsApp 根组件

根组件代码如下:

class RockPaperScissorsApp extends StatelessWidget {
  const RockPaperScissorsApp({super.key});

  
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Rock Paper Scissors',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: true,
      ),
      home: const RockPaperScissorsHomePage(title: 'Rock Paper Scissors'),
    );
  }
}

它完成了:

  1. 设置应用标题为 Rock Paper Scissors
  2. 使用深紫色作为 Material 3 种子色。
  3. 将首页设置为 RockPaperScissorsHomePage

3.3 深紫色主题的意义

深紫色让小游戏界面更有娱乐感,也和被选中的底部选项颜色一致:

Colors.deepPurple.shade100
Colors.deepPurple

主题色与选中态保持一致,可以让用户更容易理解当前选择。

四、游戏状态设计

4.1 状态字段

页面状态如下:

String _playerChoice = '';
String _computerChoice = '';
String _result = '';
int _playerWins = 0;
int _computerWins = 0;
int _draws = 0;
bool _isPlaying = false;

字段含义如下:

字段 作用
_playerChoice 玩家当前选择
_computerChoice 电脑当前选择
_result 本轮结果文案
_playerWins 玩家胜场
_computerWins 电脑胜场
_draws 平局次数
_isPlaying 当前源码中被设置,但没有实际参与 UI 控制

4.2 结果状态

_result 当前可能是:

含义
空字符串 尚未有结果
Draw! 平局
You Win! 玩家获胜
You Lose! 玩家失败

页面会根据结果文字包含的关键词决定颜色。

4.3 _isPlaying 的真实边界

源码中 _play() 会把 _isPlaying 设置为 true:

_isPlaying = true;

但后续没有任何 UI 或逻辑使用 _isPlaying 来禁用点击,也没有在判定结束后把它重置为 false。因此当前这个字段没有实际控制作用。

如果要防止连续点击,它应该参与按钮禁用逻辑。

五、出拳数据结构

5.1 _choices 列表

三种出拳定义如下:

final List<Map<String, String>> _choices = [
  {'name': 'rock', 'icon': '🪨', 'beats': 'scissors'},
  {'name': 'paper', 'icon': '📄', 'beats': 'rock'},
  {'name': 'scissors', 'icon': '✂️', 'beats': 'paper'},
];

每个选项包含:

字段 作用
name 出拳名称
icon 出拳 Emoji
beats 当前选项能击败的选项

5.2 数据结构优点

这个结构可读性很好:

  • rock 使用石头 Emoji。
  • paper 使用纸张 Emoji。
  • scissors 使用剪刀 Emoji。
  • beats 字段表达规则关系。

5.3 beats 字段的当前边界

虽然 _choices 中定义了 beats,但实际胜负判断没有使用这个字段,而是硬编码条件:

(player == 'rock' && computer == 'scissors')

如果要让规则更易扩展,可以直接使用 beats 字段判断。

5.4 获取图标方法

出拳名称转换图标使用:

String _getChoiceIcon(String choice) {
  return _choices.firstWhere((c) => c['name'] == choice)['icon']!;
}

它会在 _choices 中查找对应选项,并返回 Emoji。

六、玩家出拳与电脑随机选择

6.1 _play 方法

玩家点击选项后调用:

void _play(String choice) {
  final random = math.Random();
  final computerChoiceIndex = random.nextInt(3);
  final computerChoice = _choices[computerChoiceIndex]['name']!;

  setState(() {
    _playerChoice = choice;
    _computerChoice = computerChoice;
    _isPlaying = true;
  });

  Future.delayed(const Duration(milliseconds: 500), () {
    _determineWinner(choice, computerChoice);
  });
}

这段代码做了:

  1. 创建随机数对象。
  2. 生成 0 到 2 的随机索引。
  3. _choices 中取电脑选择。
  4. 更新玩家和电脑选择。
  5. 500ms 后判定胜负。

6.2 随机索引范围

电脑出拳索引来自:

random.nextInt(3)

返回值可能是 0、1、2,对应 rock、paper、scissors。

6.3 延迟反馈设计

延迟 500ms 的代码如下:

Future.delayed(const Duration(milliseconds: 500), () {
  _determineWinner(choice, computerChoice);
});

这让页面先展示双方选择,再出现结果,游戏反馈更自然。

6.4 延迟回调边界

当前延迟回调没有 mounted 判断。如果页面在 500ms 内被销毁,回调里调用 setState 可能出现问题。

更稳妥的写法是:

Future.delayed(const Duration(milliseconds: 500), () {
  if (!mounted) return;
  _determineWinner(choice, computerChoice);
});

这能避免页面销毁后的状态更新。

七、胜负判断逻辑

7.1 _determineWinner 方法

胜负判断如下:

void _determineWinner(String player, String computer) {
  setState(() {
    if (player == computer) {
      _result = 'Draw!';
      _draws++;
    } else if (
      (player == 'rock' && computer == 'scissors') ||
      (player == 'paper' && computer == 'rock') ||
      (player == 'scissors' && computer == 'paper')
    ) {
      _result = 'You Win!';
      _playerWins++;
    } else {
      _result = 'You Lose!';
      _computerWins++;
    }
  });
}

7.2 平局判断

当玩家和电脑选择相同:

if (player == computer)

结果是 Draw!,平局次数加 1。

7.3 玩家胜利判断

玩家胜利条件有三种:

玩家 电脑 结果
rock scissors 玩家赢
paper rock 玩家赢
scissors paper 玩家赢

源码中使用条件组合表达:

(player == 'rock' && computer == 'scissors') ||
(player == 'paper' && computer == 'rock') ||
(player == 'scissors' && computer == 'paper')

7.4 电脑胜利判断

如果不是平局,也不是玩家胜利,就进入电脑胜利分支:

else {
  _result = 'You Lose!';
  _computerWins++;
}

这个逻辑简洁且符合三选项规则。

八、比分统计卡片

8.1 顶部统计区

顶部有三个统计卡片:

_buildStatCard('You', _playerWins, Colors.green)
_buildStatCard('Draws', _draws, Colors.grey)
_buildStatCard('Computer', _computerWins, Colors.red)

分别展示玩家胜场、平局次数和电脑胜场。

8.2 _buildStatCard 方法

统计卡片方法如下:

Widget _buildStatCard(String label, int value, Color color) {
  return Card(
    child: Container(
      padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 12),
      child: Column(
        children: [
          Text(label, style: TextStyle(color: color)),
          const SizedBox(height: 4),
          Text(
            value.toString(),
            style: const TextStyle(
              fontSize: 28,
              fontWeight: FontWeight.bold,
            ),
          ),
        ],
      ),
    ),
  );
}

8.3 颜色语义

卡片 颜色 含义
You 绿色 玩家胜利
Draws 灰色 平局
Computer 红色 电脑胜利

8.4 统计持久化边界

当前比分只存在内存中。应用重启后玩家胜场、电脑胜场和平局都会恢复为 0。

如果需要长期记录,需要引入本地存储。

九、对战展示区

9.1 玩家选择区域

玩家选择区域显示:

Text(
  _playerChoice.isEmpty ? '?' : _getChoiceIcon(_playerChoice),
  style: const TextStyle(fontSize: 48),
)

首次进入时显示问号,玩家选择后显示对应 Emoji。

9.2 电脑选择区域

电脑区域逻辑相同:

Text(
  _computerChoice.isEmpty ? '?' : _getChoiceIcon(_computerChoice),
  style: const TextStyle(fontSize: 48),
)

电脑选择会在玩家点击后立即显示。

9.3 VS 和结果

中间区域展示 VS 和结果:

Text(
  _result.isEmpty ? '' : _result,
  style: TextStyle(
    fontSize: 20,
    fontWeight: FontWeight.bold,
    color: _result.contains('Win')
        ? Colors.green
        : _result.contains('Lose')
            ? Colors.red
            : Colors.grey,
  ),
)

结果颜色根据文本判断:

结果 颜色
You Win! 绿色
You Lose! 红色
Draw! 灰色

9.4 Emoji 字体边界

项目使用了石头、纸张和剪刀 Emoji。如果目标设备字体不完整,可能出现方框、黑白图标或显示差异。

OpenHarmony 设备上需要特别验证 Emoji 渲染效果。

十、底部出拳按钮

10.1 三个选项按钮

底部按钮由 _choices.map() 生成:

children: _choices.map((choice) {
  return GestureDetector(
    onTap: () => _play(choice['name']!),
    child: Container(
      padding: const EdgeInsets.all(16),
      decoration: BoxDecoration(...),
      child: Column(
        children: [
          Text(choice['icon']!, style: const TextStyle(fontSize: 48)),
          const SizedBox(height: 4),
          Text(choice['name']!.toUpperCase()),
        ],
      ),
    ),
  );
}).toList()

这让三个按钮结构一致。

10.2 选中态样式

当前玩家选择会被高亮:

color: _playerChoice == choice['name']
    ? Colors.deepPurple.shade100
    : Colors.grey.shade100

边框也会同步变化:

border: Border.all(
  color: _playerChoice == choice['name']
      ? Colors.deepPurple
      : Colors.transparent,
  width: 2,
)

10.3 点击区域

每个按钮由 GestureDetector 包裹。用户点击整个容器都能触发出拳。

10.4 连续点击边界

当前 _isPlaying 没有阻止连续点击。玩家可以在 500ms 延迟期间快速点击多次,每次都会安排一个 _determineWinner() 回调,最终可能导致比分多次更新。

更稳妥的做法是:

if (_isPlaying) return;

并在判定结束后重置:

_isPlaying = false;

十一、规则结构优化

11.1 使用 beats 字段判断

当前 _choices 已经有 beats 字段:

{'name': 'rock', 'icon': '🪨', 'beats': 'scissors'}

可以用它简化胜负判断。

11.2 查找玩家选择

可以先找到玩家选项:

final playerChoice = _choices.firstWhere((item) => item['name'] == player);

然后判断:

if (player == computer) {
  // draw
} else if (playerChoice['beats'] == computer) {
  // player wins
} else {
  // computer wins
}

11.3 优化价值

这种写法的好处是:

  • 规则集中在数据表。
  • 判断逻辑更短。
  • 后续扩展选项更容易。
  • 减少硬编码条件。

11.4 扩展玩法可能

如果增加 rock-paper-scissors-lizard-spock 之类扩展规则,数据驱动写法会比硬编码分支更容易维护。

十二、OpenHarmony 适配要点

12.1 适配关注范围

rock_paper_scissors 没有平台插件,重点验证 UI、触摸、Emoji 和延迟状态。

适配项 涉及源码 验证重点
MaterialApp 根组件 应用启动和主题
Card 比分统计 布局、阴影
GestureDetector 出拳点击 点击区域和响应
Emoji Text 三种出拳图标 字体兼容
Future.delayed 延迟判定 回调稳定性
Row / Column 页面布局 小屏是否溢出
Container 选中态 背景和边框
Text 结果颜色 Win/Lose/Draw 状态

12.2 Emoji 渲染验证

源码使用:

'🪨'
'📄'
'✂️'

OpenHarmony 设备上需要确认:

  1. Emoji 是否正常显示。
  2. Emoji 是否有彩色字体支持。
  3. 字体大小是否稳定。
  4. 不同设备显示是否差异过大。

12.3 点击体验验证

底部出拳依赖:

GestureDetector(
  onTap: () => _play(choice['name']!),
)

需要验证:

  • 三个按钮都能点击。
  • 选中态是否更新。
  • 快速点击是否会重复计分。
  • 小屏幕上按钮是否挤压。

12.4 延迟回调验证

结果判定有 500ms 延迟:

Future.delayed(const Duration(milliseconds: 500), () {
  _determineWinner(choice, computerChoice);
});

需要验证:

  • 点击后是否先显示选择。
  • 500ms 后是否显示结果。
  • 连续点击时比分是否符合预期。
  • 页面退出时是否避免 setState 风险。

十三、测试与验证

13.1 静态分析

建议执行:

flutter analyze

重点关注:

  • _isPlaying 是否未被有效使用。
  • 延迟回调是否缺少 mounted 判断。
  • Emoji 字符是否正常编码。
  • UI 是否有小屏溢出风险。

13.2 组件测试方向

可以执行:

flutter test

适合覆盖的行为包括:

  1. 初始比分为 0。
  2. 页面显示 rock、paper、scissors 三个选项。
  3. 点击任意选项后玩家区域不再显示问号。
  4. 延迟后结果不为空。
  5. 玩家、电脑或平局统计会增加。

13.3 示例测试代码

下面是一段基础页面测试思路:

testWidgets('shows rock paper scissors options', (tester) async {
  await tester.pumpWidget(const RockPaperScissorsApp());

  expect(find.text('ROCK'), findsOneWidget);
  expect(find.text('PAPER'), findsOneWidget);
  expect(find.text('SCISSORS'), findsOneWidget);
});

这能确认三个出拳选项正常渲染。

13.4 延迟结果测试思路

可以等待 500ms 后检查结果区域:

testWidgets('tap option produces result', (tester) async {
  await tester.pumpWidget(const RockPaperScissorsApp());

  await tester.tap(find.text('ROCK'));
  await tester.pump(const Duration(milliseconds: 600));

  expect(
    find.byWidgetPredicate((widget) {
      return widget is Text &&
          (widget.data == 'You Win!' ||
           widget.data == 'You Lose!' ||
           widget.data == 'Draw!');
    }),
    findsOneWidget,
  );
});

由于电脑出拳随机,测试不应固定期望某一个结果。

13.5 手动验证流程

手动验证可以按如下顺序进行:

  1. 启动应用,确认三个比分均为 0。
  2. 点击 ROCK,确认玩家选择显示石头。
  3. 等待 500ms,确认出现结果。
  4. 连续点击多个选项,观察是否出现重复计分。
  5. 检查 You、Draws、Computer 三个统计是否更新。
  6. 在 OpenHarmony 设备上验证 Emoji 是否正常显示。
  7. 在小屏幕上观察三个出拳按钮是否溢出。

十四、常见问题与优化建议

14.1 为什么 _isPlaying 没有阻止重复点击

因为源码只设置了:

_isPlaying = true;

但没有在 _play() 开头判断它,也没有在判定结束后重置。所以它当前没有实际控制效果。

14.2 为什么连续点击可能多次计分

每次点击都会安排一个延迟回调:

Future.delayed(const Duration(milliseconds: 500), () {
  _determineWinner(choice, computerChoice);
});

连续点击多次,就会有多个回调依次执行,比分也可能增加多次。

14.3 为什么 beats 字段没有被使用

_choices 中定义了:

'beats': 'scissors'

但胜负判断里没有读取这个字段,而是使用硬编码条件。它可以作为后续重构点。

14.4 为什么没有 Reset 按钮

当前源码没有提供清空比分的按钮。应用重启会清零,但运行中不能手动重置。可以在 AppBar 添加 reset action。

14.5 Emoji 显示异常怎么办

如果目标设备 Emoji 显示不稳定,可以改为:

  • 使用 Material Icons。
  • 使用本地图片资源。
  • 使用自定义 SVG。
  • 使用文字标签替代。

十五、工程扩展方向

15.1 增加防重复点击

可以在 _play() 开头加入:

if (_isPlaying) return;

判定结束后:

_isPlaying = false;

这样能确保一轮结束前不会重复计分。

15.2 增加 mounted 判断

延迟回调可以改成:

Future.delayed(const Duration(milliseconds: 500), () {
  if (!mounted) return;
  _determineWinner(choice, computerChoice);
});

这能避免页面销毁后继续更新状态。

15.3 使用 beats 字段重构规则

可以让胜负判断变成数据驱动:

final playerChoice = _choices.firstWhere((item) => item['name'] == player);
final playerBeatsComputer = playerChoice['beats'] == computer;

再根据结果更新比分。

15.4 增加比分重置

可以添加:

void _resetScore() {
  setState(() {
    _playerWins = 0;
    _computerWins = 0;
    _draws = 0;
    _result = '';
    _playerChoice = '';
    _computerChoice = '';
  });
}

然后绑定到 AppBar 的按钮。

15.5 增加对局历史

可以记录每一轮:

class RoundRecord {
  const RoundRecord({
    required this.player,
    required this.computer,
    required this.result,
  });

  final String player;
  final String computer;
  final String result;
}

有了历史记录,就可以展示最近 N 轮对局。

十六、完整核心代码回顾

16.1 应用入口

void main() {
  runApp(const RockPaperScissorsApp());
}

入口负责启动根组件。

16.2 根组件

class RockPaperScissorsApp extends StatelessWidget {
  const RockPaperScissorsApp({super.key});

  
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Rock Paper Scissors',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: true,
      ),
      home: const RockPaperScissorsHomePage(title: 'Rock Paper Scissors'),
    );
  }
}

根组件负责主题和首页绑定。

16.3 出拳数据

final List<Map<String, String>> _choices = [
  {'name': 'rock', 'icon': '🪨', 'beats': 'scissors'},
  {'name': 'paper', 'icon': '📄', 'beats': 'rock'},
  {'name': 'scissors', 'icon': '✂️', 'beats': 'paper'},
];

三种出拳都包含名称、图标和能击败的对象。

16.4 玩家出拳

void _play(String choice) {
  final random = math.Random();
  final computerChoiceIndex = random.nextInt(3);
  final computerChoice = _choices[computerChoiceIndex]['name']!;

  setState(() {
    _playerChoice = choice;
    _computerChoice = computerChoice;
    _isPlaying = true;
  });

  Future.delayed(const Duration(milliseconds: 500), () {
    _determineWinner(choice, computerChoice);
  });
}

这段代码负责玩家出拳、电脑随机出拳和延迟判定。

16.5 胜负判定

if (player == computer) {
  _result = 'Draw!';
  _draws++;
} else if (
  (player == 'rock' && computer == 'scissors') ||
  (player == 'paper' && computer == 'rock') ||
  (player == 'scissors' && computer == 'paper')
) {
  _result = 'You Win!';
  _playerWins++;
} else {
  _result = 'You Lose!';
  _computerWins++;
}

这是游戏规则的核心。

16.6 图标映射

String _getChoiceIcon(String choice) {
  return _choices.firstWhere((c) => c['name'] == choice)['icon']!;
}

这个方法把出拳名称映射为 Emoji。

总结

rock_paper_scissors 用 Flutter 实现了一个完整的猜拳小游戏:它通过 _choices 定义 rock、paper、scissors 三种出拳,通过 _play() 处理玩家点击和电脑随机选择,通过 Future.delayed 做 500ms 延迟反馈,通过 _determineWinner() 判断胜负,并用三个统计卡片展示玩家胜场、平局和电脑胜场。

从 OpenHarmony 适配角度看,这个项目覆盖了 Material 主题、Card 统计、GestureDetector 触摸、Emoji 文本、Row/Column 布局、Container 选中态和延迟回调等基础能力,很适合验证 Flutter 小游戏页面在 OpenHarmony 上的表现。

当前源码也有几个真实边界:_isPlaying 没有真正参与防重复点击;连续点击可能叠加多个延迟判定并多次计分;延迟回调没有 mounted 判断;beats 字段已定义但未用于规则判断;运行中没有比分重置按钮。这些边界不影响项目作为入门实战案例使用,但在继续工程化时需要优先处理防重复点击和延迟回调安全性。

如果这篇文章对你有帮助,欢迎点赞、收藏、关注,你的支持是我持续创作的动力!


相关资源:

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐