鸿蒙Next零基础实战:ArkTS开发运动数据记录与统计小程序 原创不易,码字不易!本篇为鸿蒙新手高质量实战项目,适配课程作业、期末实训、练手入门,代码完整可直接运行,无第三方依赖、无报错,适配Har
原创不易,码字不易!本篇为鸿蒙新手高质量实战项目,适配课程作业、期末实训、练手入门,代码完整可直接运行,无第三方依赖、无报错,适配HarmonyOS NEXT最新版本。
文章标签:#鸿蒙开发 #HarmonyOS NEXT #ArkTS #移动端实战 #课程作业
🔥 适合人群:鸿蒙零基础初学者、高校移动端开发实训学生、需要期末作业/练手项目的开发者
🔥 项目优势:纯原生ArkTS开发、单页面轻量化实现、功能完整、代码规范、注释详细、可直接提交作业
一、项目前言
随着智能穿戴、全民运动的普及,轻量化运动数据记录工具成为日常高频应用场景。相比于复杂的大型健康App,极简的本地运动记录工具更加轻便、无广告、无需联网,能够快速记录用户每一次运动信息,量化运动成果。
本文基于HarmonyOS NEXT、API20+,使用原生ArkTS声明式UI开发一款运动数据记录与统计小程序。项目摒弃冗余复杂逻辑,聚焦核心业务,完整实现运动录入、数据统计、历史管理、表单校验、智能日期适配等功能。
全程零第三方插件、零额外权限,所有逻辑单页面完成,完美覆盖鸿蒙入门核心考点:状态管理、列表渲染、数组高阶运算、自定义弹窗、表单校验、工具方法封装,是非常优质的期末作业与实战练手项目。
二、项目整体介绍
2.1 项目开发背景
日常健身、跑步、球类运动已成为大众主流生活方式,但多数用户缺乏轻量化的数据统计工具,无法直观查看每周运动次数、运动总时长、热量消耗,难以量化自身健身效果,无法针对性制定运动计划。
针对以上痛点,本项目开发一款本地运动记录工具,专注个人运动数据轻量化管理,自动统计近一周运动数据,可视化展示运动成果,帮助用户清晰掌握运动状态,培养规律健身习惯。
2.2 核心功能清单
本项目功能完整、贴合教学需求,全部为自主原生实现:
•多品类运动选择:内置跑步、游泳、健身、球类等十余种主流运动类型,适配日常运动场景
•个性化数据录入:支持自定义运动时长、消耗卡路里、运动备注信息录入
•一周数据智能统计:自动筛选近7日运动数据,统计运动次数、总时长、总消耗热量
•可视化图标适配:不同运动类型自动匹配专属Emoji图标,界面简洁美观、辨识度高
•人性化日期展示:智能区分今天、昨天、历史日期,告别生硬时间戳展示
•完整历史记录管理:实时渲染全部运动记录,支持单条记录一键删除
•精细化数据校验:拦截负数、空值、超限数值、非法字符,保障数据规范性
2.3 开发环境与技术栈
•开发工具:DevEco Studio 最新稳定版
•适配系统:HarmonyOS NEXT
•最低API版本:API 20及以上
•开发语言:ArkTS
•核心技术:声明式UI、@State响应式状态管理、数组filter/reduce高阶运算、自定义Builder弹窗、日期格式化封装、表单合法性校验
三、项目架构与设计思路
3.1 页面分层设计
项目采用高内聚、低耦合的分层模块化设计,页面结构清晰,可读性极强,符合工业级开发规范:
1.顶部导航层:展示项目标题、新增记录功能入口,界面简洁整洁
2.数据统计层:卡片式展示本周核心运动数据,实现数据可视化
3.弹窗表单层:自定义弹窗实现数据录入,包含类型选择、数值输入、备注填写
4.历史列表层:循环渲染所有运动记录,支持删除交互,空数据友好提示
3.2 数据结构设计
自定义标准化运动记录实体结构体,统一所有数据字段规范,方便后续筛选、统计、删除、渲染操作,从根源保证代码整洁、易于拓展。
四、核心技术知识点精讲
4.1 响应式状态管理机制
项目全程采用 @State 装饰器管理页面所有动态状态,包含弹窗显示状态、表单输入数据、运动记录列表等。依托ArkTS数据驱动视图的核心特性,数据发生变更时,页面自动精准刷新,无需手动操作DOM,极大简化开发逻辑,也是鸿蒙开发最核心的基础知识点。
4.2 数组高阶方法实现数据统计
本项目核心亮点即为原生数组运算统计数据,通过 filter 方法精准筛选近7日的有效运动记录,再通过 reduce 方法累加运动时长与卡路里数据。相比于传统for循环遍历,高阶方法代码更精简、逻辑更清晰、执行效率更高,是鸿蒙数据类项目的高频考点。
4.3 键值对映射实现图标自动匹配
通过自定义键值对映射对象,建立运动类型与Emoji图标的一一对应关系。页面渲染时根据记录的运动类型,自动匹配展示对应图标,代码扩展性极强。后续需要新增运动类型,仅需在映射表中添加配置即可,无需修改核心业务逻辑。
4.4 封装工具方法统一处理日期逻辑
统一封装日期格式化、日期比对、相对日期转换工具函数,实现日期标准化存储、人性化展示。解决了新手开发中常见的日期筛选不准、时间格式混乱、展示生硬等问题,代码复用性高。
4.5 表单输入合法性校验
针对运动时长、卡路里两大核心输入项设置合理数值区间,自动拦截空值、负数、超大超限数值、非数字字符,有效规避非法数据导致的统计错乱、页面异常问题,保障项目运行稳定性。
五、完整可运行源码(Index.ets)
✅ 使用说明:新建鸿蒙空白项目,项目名 ExerciseRecordTracker,替换 pages/Index.ets 全部代码,直接编译运行,零报错、零兼容问题。
在这里插入代码片
/**
* 项目名称:ExerciseRecordTracker 运动数据记录统计小程序
* 适配版本:HarmonyOS NEXT API20+
* 功能:运动数据录入、本周智能统计、历史记录管理、表单校验、智能日期适配
* 适用场景:鸿蒙课程作业、期末实训、新手实战练手
*/
// 自定义运动记录数据实体类
interface ExerciseItem {
id: number; // 唯一标识ID
sportType: string; // 运动类型
duration: number; // 运动时长(分钟)
calorie: number; // 消耗卡路里
recordDate: string; // 记录日期
remark: string; // 运动备注
}
@Entry
@Component
struct Index {
// 新增弹窗显示状态
@State isShowDialog: boolean = false;
// 表单录入数据
@State selectSportType: string = "跑步";
@State inputDuration: string = "30";
@State inputCalorie: string = "200";
@State inputRemark: string = "";
// 运动记录全局列表
@State exerciseList: ExerciseItem[] = [];
// 全部可选运动类型
private sportTypeArray: string[] = [
"跑步", "游泳", "骑行", "健身", "瑜伽",
"跳绳", "篮球", "羽毛球", "足球", "网球"
];
// 运动类型与图标映射表
private sportIconMap: Record<string, string> = {
"跑步": "🏃",
"游泳": "🏊",
"骑行": "🚴",
"健身": "💪",
"瑜伽": "🧘",
"跳绳": "⚡",
"篮球": "🏀",
"羽毛球": "🏸",
"足球": "⚽",
"网球": "🎾"
};
// 根据运动类型获取对应图标
private getSportIcon(type: string): string {
return this.sportIconMap[type] || "🏃";
}
// 获取今日标准日期
private getStandardTodayDate(): string {
const now = new Date();
const year = now.getFullYear();
const month = (now.getMonth() + 1).toString().padStart(2, "0");
const day = now.getDate().toString().padStart(2, "0");
return `${year}-${month}-${day}`;
}
// 日期格式化 转为月日格式
private formatMonthDay(dateStr: string): string {
const date = new Date(dateStr);
const month = (date.getMonth() + 1).toString().padStart(2, "0");
const day = date.getDate().toString().padStart(2, "0");
return `${month}月${day}日`;
}
// Date对象转标准日期字符串
private dateToStandardStr(date: Date): string {
const year = date.getFullYear();
const month = (date.getMonth() + 1).toString().padStart(2, "0");
const day = date.getDate().toString().padStart(2, "0");
return `${year}-${month}-${day}`;
}
// 智能相对日期展示
private getSmartDateText(dateStr: string): string {
const today = this.getStandardTodayDate();
if (dateStr === today) return "今天";
const yesterday = new Date();
yesterday.setDate(yesterday.getDate() - 1);
if (dateStr === this.dateToStandardStr(yesterday)) return "昨天";
return this.formatMonthDay(dateStr);
}
// 校验运动时长合法性
private verifyDuration(val: string): boolean {
const num = parseInt(val);
return !isNaN(num) && num > 0 && num <= 480;
}
// 校验卡路里合法性
private verifyCalorie(val: string): boolean {
const num = parseInt(val);
return !isNaN(num) && num > 0 && num <= 5000;
}
// 计算本周运动统计数据
private calcWeeklyStatistic() {
const nowTime = new Date();
// 计算7天前时间节点
const weekBeforeTime = new Date(nowTime.getTime() - 7 * 24 * 60 * 60 * 1000);
// 筛选本周所有运动记录
const weekDataList = this.exerciseList.filter(item => {
const itemTime = new Date(item.recordDate);
return itemTime >= weekBeforeTime && itemTime <= nowTime;
})
// 聚合统计数据
return {
totalCount: weekDataList.length,
totalTime: weekDataList.reduce((sum, item) => sum + item.duration, 0),
totalCalorie: weekDataList.reduce((sum, item) => sum + item.calorie, 0)
}
}
// 重置表单数据
private resetFormData() {
this.selectSportType = "跑步";
this.inputDuration = "30";
this.inputCalorie = "200";
this.inputRemark = "";
}
// 新增运动记录
private addNewRecord() {
// 数据校验拦截
if (!this.verifyDuration(this.inputDuration) || !this.verifyCalorie(this.inputCalorie)) {
return;
}
// 组装新数据
const newRecord: ExerciseItem = {
id: Date.now(),
sportType: this.selectSportType,
duration: parseInt(this.inputDuration),
calorie: parseInt(this.inputCalorie),
recordDate: this.getStandardTodayDate(),
remark: this.inputRemark.trim()
}
// 头部插入新记录,最新数据置顶
this.exerciseList.unshift(newRecord);
// 重置表单、关闭弹窗
this.resetFormData();
this.isShowDialog = false;
}
// 删除单条运动记录
private deleteSingleRecord(id: number) {
this.exerciseList = this.exerciseList.filter(item => item.id !== id);
}
// 自定义新增记录弹窗
@Builder
AddExerciseDialog() {
Column() {
Text("新增运动记录")
.fontSize(22)
.fontWeight(FontWeight.Bold)
.margin({ bottom: 24 })
Text("选择运动类型")
.fontSize(14)
.fontColor("#333333")
.width("100%")
Select(this.sportTypeArray.map(item => ({ value: item })))
.value(this.selectSportType)
.width("100%")
.height(48)
.margin({ bottom: 16 })
.onSelect((index: number) => {
this.selectSportType = this.sportTypeArray[index];
})
Text("运动时长(分钟)")
.fontSize(14)
.fontColor("#333333")
.width("100%")
TextInput({ text: this.inputDuration, placeholder: "请输入1-480有效数值" })
.width("100%")
.height(48)
.inputType(InputType.Number)
.onChange(val => this.inputDuration = val)
.margin({ bottom: 16 })
Text("消耗卡路里")
.fontSize(14)
.fontColor("#333333")
.width("100%")
TextInput({ text: this.inputCalorie, placeholder: "请输入1-5000有效数值" })
.width("100%")
.height(48)
.inputType(InputType.Number)
.onChange(val => this.inputCalorie = val)
.margin({ bottom: 16 })
Text("运动备注(选填)")
.fontSize(14)
.fontColor("#333333")
.width("100%")
TextInput({ text: this.inputRemark, placeholder: "记录运动场景、心得等信息" })
.width("100%")
.height(48)
.onChange(val => this.inputRemark = val)
.margin({ bottom: 24 })
Row({ space: 20 }) {
Button("取消")
.layoutWeight(1)
.height(44)
.backgroundColor("#EEEEEE")
.fontColor("#666666")
.onClick(() => {
this.isShowDialog = false;
this.resetFormData();
})
Button("保存记录")
.layoutWeight(1)
.height(44)
.backgroundColor("#1677ff")
.onClick(() => this.addNewRecord())
}
}
.width("92%")
.padding(24)
.backgroundColor(Color.White)
.borderRadius(20)
}
build() {
Column() {
// 顶部导航栏
Row() {
Text("个人运动记录中心")
.fontSize(24)
.fontWeight(FontWeight.Bold)
.fontColor("#1F2937")
Blank()
Button("+ 新增记录")
.backgroundColor("#1677ff")
.fontSize(14)
.borderRadius(20)
.onClick(() => this.isShowDialog = true)
}
.width("100%")
.padding({ left: 20, right: 20, top: 24, bottom: 16 })
// 本周数据统计卡片
Column() {
Text("本周运动统计数据")
.fontSize(18)
.fontColor("#6B7280")
.width("100%")
.margin({ bottom: 16 })
Row({ space: 10 }) {
Column() {
Text(`${this.calcWeeklyStatistic().totalCount}`)
.fontSize(30)
.fontWeight(FontWeight.Bold)
.fontColor("#1677ff")
Text("运动次数")
.fontSize(13)
.fontColor("#9CA3AF")
.margin({ top: 4 })
}
.layoutWeight(1)
.alignItems(HorizontalAlign.Center)
Column() {
Text(`${this.calcWeeklyStatistic().totalTime}`)
.fontSize(30)
.fontWeight(FontWeight.Bold)
.fontColor("#1677ff")
Text("总时长(分钟)")
.fontSize(13)
.fontColor("#9CA3AF")
.margin({ top: 4 })
}
.layoutWeight(1)
.alignItems(HorizontalAlign.Center)
Column() {
Text(`${this.calcWeeklyStatistic().totalCalorie}`)
.fontSize(30)
.fontWeight(FontWeight.Bold)
.fontColor("#1677ff")
Text("总卡路里")
.fontSize(13)
.fontColor("#9CA3AF")
.margin({ top: 4 })
}
.layoutWeight(1)
.alignItems(HorizontalAlign.Center)
}
}
.width("92%")
.padding(24)
.backgroundColor(Color.White)
.borderRadius(20)
.margin({ bottom: 20 })
// 运动记录列表区域
if (this.exerciseList.length > 0) {
List() {
ForEach(this.exerciseList, (item: ExerciseItem) => {
ListItem() {
Row() {
Text(this.getSportIcon(item.sportType))
.fontSize(36)
.margin({ right: 16 })
Column() {
Text(item.sportType)
.fontSize(17)
.fontWeight(FontWeight.Medium)
.fontColor("#1F2937")
Text(`时长${item.duration}分钟 · 消耗${item.calorie}千卡`)
.fontSize(13)
.fontColor("#6B7280")
.margin({ top: 4 })
if (item.remark) {
Text(item.remark)
.fontSize(12)
.fontColor("#9CA3AF")
.maxLines(1)
.textOverflow({ overflow: TextOverflow.Ellipsis })
.margin({ top: 2 })
}
}
.layoutWeight(1)
Column() {
Text(this.getSmartDateText(item.recordDate))
.fontSize(12)
.fontColor("#6B7280")
Button("删除")
.fontSize(12)
.height(26)
.backgroundColor("#F53F3F")
.margin({ top: 8 })
.onClick(() => this.deleteSingleRecord(item.id))
}
.alignItems(HorizontalAlign.End)
}
.width("100%")
.padding(18)
.backgroundColor(Color.White)
.borderRadius(16)
.margin({ bottom: 10 })
}
})
}
.width("92%")
.layoutWeight(1)
} else {
Column() {
Text("暂无运动记录,点击上方按钮添加你的第一条运动记录")
.fontSize(14)
.fontColor("#9CA3AF")
.textAlign(TextAlign.Center)
}
.layoutWeight(1)
.width("100%")
.justifyContent(FlexAlign.Center)
}
// 弹窗遮罩层
if (this.isShowDialog) {
Stack() {
Rect().width("100%").height("100%").fillColor(0x88000000)
this.AddExerciseDialog()
}
.width("100%")
.height("100%")
.position({ x: 0, y: 0 })
}
}
.width("100%")
.height("100%")
.backgroundColor("#F5F7FA")
}
}

六、项目运行流程详解
- 项目初始化:应用启动后,运动记录列表为空,统计数据默认归零,页面展示空数据友好提示,整体界面简洁干净。
- 新增运动记录:点击页面右上角新增按钮唤起自定义弹窗,选择对应运动类型,填写合规的时长、卡路里数据与备注,点击保存即可完成新增,新记录自动置顶展示。
- 实时数据统计更新:页面每次渲染都会重新执行统计方法,新增、删除记录后,本周运动次数、总时长、总卡路里数据会自动实时刷新,无需手动干预。
- 智能时间展示:系统自动识别记录日期,当天记录展示「今天」、昨日记录展示「昨天」,更早的记录展示具体月日,大幅优化用户体验。
- 历史记录删除:点击单条记录的删除按钮,可即时移除对应数据,列表视图与统计数据同步更新,数据实时联动。
七、开发常见问题与解决方案
问题1:输入非法数值导致统计数据错乱
解决方案:针对性封装双重校验方法,严格限制运动时长、卡路里的数值区间,自动拦截空值、负数、超大超限数值,从源头规避异常数据,保证统计结果精准无误。
问题2:数据增减后统计卡片不刷新
解决方案:统计方法不做静态缓存,采用实时计算逻辑,页面每次渲染都会重新遍历计算最新数据,确保视图与数据完全同步。
问题3:弹窗关闭后残留上次填写数据
解决方案:监听弹窗关闭事件,每次关闭弹窗自动重置表单为默认初始值,彻底解决数据残留问题。
问题4:本周日期筛选范围不准确
解决方案:通过时间戳精准计算7天时间区间,使用Date对象时间比对筛选数据,规避字符串匹配带来的误差问题,筛选结果精准可靠。
八、项目进阶拓展方向
本项目基础功能完善,可基于此持续迭代进阶功能,适合二次开发学习:
•新增本地数据持久化功能,应用重启保留所有历史记录
•接入图表组件,实现运动数据趋势可视化展示
•新增运动目标打卡、数据成就体系
•实现记录编辑、批量删除、运动类型筛选功能
•适配深色模式,优化多场景视觉体验
九、项目总结
本项目是一款高性价比、高适配度的鸿蒙Next零基础实战项目,完整覆盖鸿蒙ArkTS开发的核心基础知识点,包含UI布局、响应式状态管理、自定义弹窗、表单校验、数组数据统计、日期工具封装、列表渲染与交互等高频核心技能。
项目代码结构规范、逻辑清晰、注释完善、无冗余报错,界面美观适配移动端,功能完整贴合课程实训与期末作业要求,上手简单、运行稳定,非常适合鸿蒙新手巩固基础、积累实战经验。同时可作为健康类原生App的基础模板,进行个性化二次开发。
项目名称:ExerciseRecordTracker 运动数据记录统计小程序
适配版本:HarmonyOS NEXT API20+
更多推荐



所有评论(0)