鸿蒙 ArkTS 实战:Poetry Feihualing 从诗词飞花令到成绩记录完整解析
鸿蒙 ArkTS 实战:Poetry Feihualing 从诗词飞花令到成绩记录完整解析
前言
Poetry Feihualing 是一个面向诗词飞花令的鸿蒙 ArkTS 单页应用。项目代码集中在Index.ets,通过@State管理记录列表、输入内容、声音开关和状态提示,并用ForEach把每一轮记录渲染为可勾选列表。
这篇文章直接基于项目源码展开,不写空泛介绍,重点分析它的数据模型、默认记录、统计函数、表单提交、列表刷新和后续扩展方式。对于学习 ArkTS 单页应用的人来说,这类项目很适合用来理解状态驱动 UI 的基本路径。
轻量小游戏或训练工具不一定先做复杂玩法。先把记录、统计、反馈和列表交互搭起来,后续接入真正的游戏逻辑会更稳。
配图展示的是开发者生态入口,发布时也可以替换成应用运行截图。本文关注的结构是:顶部指标、功能标签、声音开关、录入表单和记录列表。
一、项目定位与源码入口
1.1 应用定位
围绕关键字出题、限时回答、诗词库和对战模式,记录每轮飞花令的诗句产出与得分。
当前页面不是复杂多页应用,而是一个清晰的单页面闭环:用户看到默认记录,输入新的轮次信息,点击按钮保存,再通过列表勾选需要关注的记录。
1.2 源码入口
核心文件位于entry/src/main/ets/pages/Index.ets,入口组件使用@Entry与@Component标记。
@Entry@Componentstruct Index{build(){Scroll(){Column({space:14}){Text('Poetry Feihualing')}}}}| 区域 | 作用 | 对应能力 |
|---|---|---|
| 标题 | 呈现应用名称 | Poetry Feihualing |
| 统计卡片 | 展示Score与 Selected | 汇总与计数 |
| 标签区 | 展示玩法模块 | Keyword prompt, Timed answer, Poem library, Battle mode |
| 表单 | 新增记录 | TextInput、Button |
| 列表 | 管理记录状态 | ForEach、Toggle |
二、数据模型设计
2.1 记录接口
项目使用GameRound描述每一条游戏记录。
interfaceGameRound{title:string;score:number;note:string;active:boolean;}这个接口非常适合成绩类应用。title表示轮次或关卡,score保存诗句得分,note用来保存补充描述,active则控制是否被选中。
2.2 字段语义
| 字段 | 类型 | 在本项目中的含义 |
|---|---|---|
| title | string | 轮次、关卡或分类名称 |
| score | number | 诗句得分 |
| note | string | 结果说明、提示状态或扩展备注 |
| active | boolean | 是否作为重点记录 |
数据模型稳定是这类项目的关键。后续即使增加真实玩法,也可以继续把最终结果写回同一个记录结构。
三、默认数据与业务语义
3.1 默认记录
页面初始化时已经准备两条业务记录。
@Staterounds:GameRound[]=[{title:'Keyword moon',score:7,note:'Timed 60s',active:false},{title:'Battle round',score:11,note:'Poem library used',active:false}];| 默认标题 | 数值 | 备注 | 是否选中 |
|---|---|---|---|
| Keyword moon | 7 | Timed 60s | false |
| Battle round | 11 | Poem library used | false |
3.2 默认数据的价值
默认数据让用户首次进入页面时就能理解这个应用要记录什么。比如Keyword moon和Battle round分别对应不同训练或玩法场景,备注字段则补充了结果细节。
- 空列表会降低理解效率。
- 示例记录可以说明输入格式。
- 顶部统计从首次打开就有可读数值。
四、页面状态管理
4.1 状态字段
页面使用@State保存列表、表单、开关和提示文本。
@Staterounds:GameRound[]=[];@StatetitleText:string='';@StatescoreText:string='';@StatenoteText:string='';@StatesoundOn:boolean=true;@Statestatus:string='Game ready';4.2 状态流转
| 状态 | 触发动作 | 结果 |
|---|---|---|
rounds | 新增或勾选记录 | 列表和统计刷新 |
titleText | 输入轮次名称 | 提交时写入 title |
scoreText | 输入数值 | 转换为 score |
noteText | 输入备注 | 写入 note |
soundOn | 切换声音开关 | 保存声音偏好 |
status | 提交表单 | 展示错误或成功信息 |
ArkTS 的声明式体验重点在于状态变化。只要状态设计清楚,UI 更新就能自然跟随数据变化。
五、统计函数拆解
5.1 累计Score
顶部左侧指标来自totalScore()。
privatetotalScore():number{lettotal=0;this.rounds.forEach((item:GameRound)=>{total+=item.score;});returntotal;}5.2 计算选中数量
右侧 Selected 来自selectedCount()。
privateselectedCount():number{letcount=0;this.rounds.forEach((item:GameRound)=>{if(item.active){count++;}});returncount;}这两个函数都只读列表状态,不修改任何数据,因此适合作为 UI 层的派生计算。
六、顶部指标卡片实现
6.1 卡片代码
顶部区域使用两个Column并排展示核心指标。
Row({space:10}){Column({space:4}){Text('Score').fontSize(13).fontColor('#475569')Text(String(this.totalScore())).fontSize(24).fontWeight(FontWeight.Bold).fontColor('#DC2626')}.layoutWeight(1).padding(14).backgroundColor('#FEE2E2').borderRadius(8)Column({space:4}){Text('Selected').fontSize(13).fontColor('#475569')Text(this.selectedCount()+'/'+this.rounds.length).fontSize(24).fontWeight(FontWeight.Bold).fontColor('#1D4ED8')}.layoutWeight(1).padding(14).backgroundColor('#DBEAFE').borderRadius(8)}6.2 指标解释
| 指标 | 数据来源 | 产品含义 |
|---|---|---|
Score | 所有记录的 score 汇总 | 衡量整体表现 |
| Selected | active 为 true 的记录数量 | 标记当前关注记录 |
| 总数 | rounds.length | 展示记录规模 |
对于诗词飞花令,诗句得分是最重要的结果数据,因此放在左侧主卡片。
七、功能标签区域
7.1 标签渲染
页面用四个短标签概括玩法能力。
Column({space:8}){Row({space:8}){Text('Keyword prompt').layoutWeight(1).fontSize(13).textAlign(TextAlign.Center)Text('Timed answer').layoutWeight(1).fontSize(13).textAlign(TextAlign.Center)}Row({space:8}){Text('Poem library').layoutWeight(1).fontSize(13).textAlign(TextAlign.Center)Text('Battle mode').layoutWeight(1).fontSize(13).textAlign(TextAlign.Center)}}7.2 标签含义
| 序号 | 标签 | 说明 |
|---|---|---|
| 1 | Keyword prompt | 主玩法入口 |
| 2 | Timed answer | 交互过程 |
| 3 | Poem library | 结果反馈 |
| 4 | Battle mode | 沉淀能力 |
功能标签的好处是信息密度高,不需要额外说明文字,也能让用户迅速理解页面能力。
八、声音开关与配置入口
8.1 开关代码
页面提供Sound开关,状态保存在soundOn中。
Row(){Text('Sound').fontSize(16).fontColor('#374151')Blank()Toggle({type:ToggleType.Switch,isOn:this.soundOn}).onChange((checked:boolean)=>this.soundOn=checked)}8.2 扩展方向
在当前版本中,soundOn还没有接入音频系统,但它已经占好了配置位置。后续可以根据它控制按钮音效、答题反馈音、成功提示音或错误提醒音。
九、表单录入与校验
9.1 输入区结构
表单由名称、数值、备注和按钮组成。
Column({space:10}){TextInput({placeholder:'Round name',text:this.titleText}).onChange((value:string)=>this.titleText=value)TextInput({placeholder:'Score',text:this.scoreText}).type(InputType.Number).onChange((value:string)=>this.scoreText=value)TextInput({placeholder:'Note',text:this.noteText}).onChange((value:string)=>this.noteText=value)Button('Add score').height(42).width('100%').onClick(()=>this.addRound())}9.2 校验规则
提交时会先检查名称和数值。名称不能为空,分数必须能转换成有效数字。
constscore=Number(this.scoreText);if(this.titleText.trim().length===0||!Number.isFinite(score)){this.status='Enter round and valid score';return;}这个校验足够直接,能避免空标题和非法分数进入列表。
十、新增记录逻辑
10.1 保存方法
新增记录由addRound()完成。
privateaddRound():void{constscore=Number(this.scoreText);if(this.titleText.trim().length===0||!Number.isFinite(score)){this.status='Enter round and valid score';return;}this.rounds=[{title:this.titleText,score:Math.round(score),note:this.noteText.length>0?this.noteText:'No note yet',active:false},...this.rounds];this.titleText='';this.scoreText='';this.noteText='';this.status='Round saved';}10.2 头部插入
新记录会插入到数组头部。这样用户提交后,最新数据立即出现在第一条,反馈路径很短。
十一、列表渲染与选中态
11.1 ForEach 渲染
列表渲染使用ForEach遍历rounds。
ForEach(this.rounds,(item:GameRound,index:number)=>{Row(){Toggle({type:ToggleType.Checkbox,isOn:item.active}).onChange(()=>this.toggleActive(index))Column({space:6}){Text(item.title).fontSize(18).fontWeight(FontWeight.Bold)Text(item.note).fontSize(14).fontColor('#475569')}.layoutWeight(1).margin({left:8})Text(String(item.score)).fontSize(18).fontWeight(FontWeight.Bold).fontColor('#DC2626')}.padding(14).backgroundColor(item.active?'#DCFCE7':'#FFFFFF').borderRadius(8)})11.2 勾选更新
切换勾选时,先修改当前项,再用展开语法生成新数组引用。
privatetoggleActive(index:number):void{this.rounds[index].active=!this.rounds[index].active;this.rounds=[...this.rounds];}这个写法保证列表 UI 能感知状态变化。选中的记录会使用#DCFCE7背景色,视觉反馈明确。
十二、布局与视觉层次
12.1 页面容器
外层使用Scroll,内部使用Column。
Scroll(){Column({space:14}){Text('Poetry Feihualing')// stats// tags// sound switch// form// list}.padding(18).width('100%')}.height('100%').backgroundColor('#F8FAFC')12.2 配色表
| 颜色 | 使用位置 | 作用 |
|---|---|---|
#F8FAFC | 页面背景 | 保持浅色基底 |
#DC2626 | 主指标数值 | 强调成绩 |
#1D4ED8 | Selected 数值 | 强调选择状态 |
#DCFCE7 | 选中列表项 | 表达活跃状态 |
#F1F5F9 | 表单背景 | 区分输入区域 |
十三、运行验证流程
13.1 手工验证路径
可以按以下流程验证:
- 打开页面,确认标题是
Poetry Feihualing。 - 查看默认两条记录是否出现。
- 点击
Add score且不输入内容,确认提示为Enter round and valid score。 - 输入
Daily round、99和备注,再点击保存。 - 勾选新记录,确认背景变绿且 Selected 数量增加。
this.titleText='Daily round';this.scoreText='99';this.noteText='Manual record';this.addRound();// 预期:status 更新为 Round saved// 预期:新记录位于列表第一项13.2 验证要点
| 验证点 | 预期结果 |
|---|---|
| 空标题提交 | 显示错误提示 |
| 合法提交 | 新记录插入列表顶部 |
| 数值输入 | 保存为整数 score |
| 勾选记录 | active 取反 |
| 顶部统计 | 跟随列表变化 |
十四、面向真实玩法的扩展
14.1 业务扩展
后续可以接入诗词检索、关键字命中校验、对战轮换、倒计时和诗句去重。
14.2 工程扩展
后续扩展时,可以保持当前页面的状态结构不变,只把真实玩法结果接入score和note:
interfacePlayResult{roundTitle:string;finalScore:number;summary:string;}functionmapResultToRound(result:PlayResult):GameRound{return{title:result.roundTitle,score:result.finalScore,note:result.summary,active:false};}这样可以把玩法层和记录层分开,避免页面代码随着功能增加而失控。
十五、工程复盘
15.1 当前实现的优点
- 数据结构简单,字段语义清楚。
- 统计函数只依赖列表数据,逻辑可预测。
- 表单校验直接,错误提示明确。
- 列表更新使用新数组引用,刷新路径稳定。
- 功能标签贴合
诗词飞花令的业务方向。
15.2 可复用经验
这类结构可以复用到答题、训练、学习、聚会游戏、儿童教育和轻量工具中。只要业务结果可以表达为“标题 + 数值 + 备注 + 状态”,就能套入相同的记录管理骨架。
总结
Poetry Feihualing展示了一个完整的鸿蒙 ArkTS 小应用骨架:用GameRound定义记录,用rounds保存列表,用totalScore()和selectedCount()输出统计结果,用addRound()完成表单提交,再用ForEach和Toggle完成记录展示与选中状态更新。
它的代码不复杂,但工程链路完整。对于诗词飞花令这种玩法来说,当前版本已经完成了成绩记录层;后续只需要逐步补齐真实玩法,就能从原型平滑走向可用产品。
补充说明:当前页面把业务状态集中在组件内,适合小型训练工具快速迭代。
补充说明:记录列表采用头部插入,能让用户提交后立即看到最新结果。
补充说明:备注字段保留了足够弹性,可以承载提示、收藏、错误次数或扩展状态。
补充说明:统计卡片和列表项共享同一份数据,减少了重复状态带来的不一致风险。
补充说明:当前页面把业务状态集中在组件内,适合小型训练工具快速迭代。
补充说明:记录列表采用头部插入,能让用户提交后立即看到最新结果。
补充说明:备注字段保留了足够弹性,可以承载提示、收藏、错误次数或扩展状态。
补充说明:统计卡片和列表项共享同一份数据,减少了重复状态带来的不一致风险。
相关资源:
- HarmonyOS 开发者官网
- ArkTS 语言基础
- ArkUI 组件开发
- 状态管理概览
- TextInput 组件
