网站服务器维护费用,湛江搜索引擎网站推广,桂林网,wordpress有什么插件一、数据模型#xff1a;构建运动记录的数字骨架
代码通过RunRecord接口定义了跑步数据的核心结构#xff1a;
interface RunRecord {id: string; // 记录唯一标识date: Date; // 跑步日期distance: number; // 距离#xff08;公里#xff09;duratio… 一、数据模型构建运动记录的数字骨架
代码通过RunRecord接口定义了跑步数据的核心结构
interface RunRecord {id: string; // 记录唯一标识date: Date; // 跑步日期distance: number; // 距离公里duration: number; // 时长分钟pace: number; // 配速分钟/公里
}
这一模型以距离-时长-配速为核心维度通过id和date建立时间轴索引。在实际跑步场景中当用户点击结束跑步时系统会基于实时数据生成RunRecord对象并添加到runRecords数组中
const newRecord: RunRecord {id: Date.now().toString(),date: new Date(),distance: this.currentDistance,duration: this.currentDuration,pace: this.currentPace
};
this.runRecords [newRecord, ...this.runRecords];
这种设计遵循了最小必要数据原则既满足基础运动分析需求又降低了数据存储与计算的复杂度。 二、状态管理实现运动数据的实时响应
应用通过State装饰器管理五大核心状态构建起数据流转的中枢系统
isRunning标记跑步状态进行中/已结束控制界面按钮与数据展示逻辑currentDistance/duration/pace实时跑步数据通过定时器模拟GPS更新showHistory控制历史记录列表的展开/收起状态
核心状态更新逻辑体现在startRun与endRun方法中。当用户点击开始跑步时系统启动定时器以100ms为周期更新数据
this.intervalId setInterval(() {this.currentDistance 0.01; // 模拟每100ms增加10米this.currentDuration 0.1; // 模拟每100ms增加0.1秒if (this.currentDistance 0) {this.currentPace this.currentDuration / this.currentDistance / 60;}
}, 100);
而endRun方法则负责清除定时器、保存记录并重置状态形成数据采集-存储-重置的闭环。 三、UI组件打造直观的运动数据可视化体验
应用采用统计头部-数据卡片-历史列表的三层布局通过ArkTS的声明式UI特性实现动态渲染
统计头部组件StatsHeader 采用三栏式布局展示总跑步次数、总距离与平均配速通过reduce方法对历史记录进行聚合计算
Text(${this.runRecords.reduce((sum, r) sum r.distance, 0).toFixed(1)}km)
数据展示遵循数值单位的层级结构大号字体突出核心数字小号字体标注单位与说明符合用户先看结果再辨含义的认知习惯。
跑步数据卡片RunDataCard 通过isRunning状态动态切换显示逻辑跑步中时突出距离与时长数据搭配配速与时长的分栏展示跑步结束后则展示今日汇总数据。按钮设计采用绿色开始与红色结束的高对比度配色强化操作反馈。历史记录列表HistoryList 通过showHistory状态控制显示/隐藏使用ForEach循环渲染历史记录每条记录包含日期、时长、距离与配速信息。当记录为空时显示空状态提示提升用户体验的完整性。 四、核心算法从原始数据到运动洞察
代码中包含三个关键数据处理函数将原始运动数据转化为可解读的运动指标
formatTime将秒数转换为分:秒格式如123秒转为2:03便于用户快速理解运动时长getTodayDistance/duration/pace通过日期筛选与数组聚合计算今日运动数据支持用户查看短期运动趋势formatDate将Date对象转换为月/日格式如6/15简化历史记录的时间展示
以getTodayPace为例其核心逻辑是通过筛选今日记录并计算平均配速
const totalDistance this.getTodayDistance();
const totalDuration this.getTodayDuration();
if (totalDistance 0) return 0;
return totalDuration / totalDistance / 60; 五、附代码
import promptAction from ohos.promptAction;// 跑步记录接口
interface RunRecord {id: string;date: Date;distance: number; // 公里duration: number; // 分钟pace: number; // 配速(分钟/公里)
}
Entry
Component
struct Index {State runRecords: RunRecord[] []; // 跑步记录列表State isRunning: boolean false; // 是否正在跑步State currentDistance: number 0; // 当前跑步距离State currentDuration: number 0; // 当前跑步时长State currentPace: number 0; // 当前配速State showHistory: boolean false; // 是否显示历史记录private intervalId: number -1; // 定时器ID// 开始跑步private startRun() {this.isRunning true;this.currentDistance 0;this.currentDuration 0;this.currentPace 0;// 模拟GPS定位更新this.intervalId setInterval(() {this.currentDistance 0.01; // 模拟每100ms增加10米this.currentDuration 0.1; // 模拟每100ms增加0.1秒// 更新配速if (this.currentDistance 0) {this.currentPace this.currentDuration / this.currentDistance / 60;}}, 100);}// 结束跑步private endRun() {clearInterval(this.intervalId);// 创建新记录const newRecord: RunRecord {id: Date.now().toString(),date: new Date(),distance: this.currentDistance,duration: this.currentDuration,pace: this.currentPace};// 添加到记录列表this.runRecords [newRecord, ...this.runRecords];// 重置状态this.isRunning false;this.currentDistance 0;this.currentDuration 0;this.currentPace 0;promptAction.showToast({ message: 跑步记录已保存 });}// 格式化时间为分:秒private formatTime(seconds: number): string {const minutes Math.floor(seconds / 60);const secs Math.floor(seconds % 60);return ${minutes}:${secs 10 ? 0 : }${secs};}// 获取今日跑步距离private getTodayDistance(): number {const today new Date();today.setHours(0, 0, 0, 0);const todayRuns this.runRecords.filter(record {const recordDate new Date(record.date);recordDate.setHours(0, 0, 0, 0);return recordDate.getTime() today.getTime();});return todayRuns.reduce((sum, record) sum record.distance, 0);}// 获取今日跑步时长private getTodayDuration(): number {const today new Date();today.setHours(0, 0, 0, 0);const todayRuns this.runRecords.filter(record {const recordDate new Date(record.date);recordDate.setHours(0, 0, 0, 0);return recordDate.getTime() today.getTime();});return todayRuns.reduce((sum, record) sum record.duration, 0);}// 获取今日平均配速private getTodayPace(): number {const totalDistance this.getTodayDistance();const totalDuration this.getTodayDuration();if (totalDistance 0) return 0;return totalDuration / totalDistance / 60;}// 格式化日期private formatDate(date: Date): string {return ${date.getMonth() 1}/${date.getDate()};}// 头部统计组件BuilderStatsHeader() {Column() {Text(跑步统计).fontSize(18).fontWeight(FontWeight.Bold).margin({ bottom: 15 })Row() {Column() {Text(${this.runRecords.length}).fontSize(24).fontWeight(FontWeight.Bold)Text(总次数).fontSize(12).fontColor(#888).margin({top: 5})}.width(33%)Column() {Text(${this.runRecords.reduce((sum, r) sum r.distance, 0).toFixed(1)}km).fontSize(24).fontWeight(FontWeight.Bold)Text(总距离).fontSize(12).fontColor(#888).margin({top: 5})}.width(33%)Column() {Text(${(this.runRecords.reduce((sum, r) sum r.pace, 0) / this.runRecords.length || 0).toFixed(2)}min/km).fontSize(24).fontWeight(FontWeight.Bold)Text(平均配速).fontSize(12).fontColor(#888).margin({top: 5})}.width(33%)}.width(100%)}.width(100%).padding(15).backgroundColor(#F8F9FC).borderRadius(12)}// 跑步数据卡片BuilderRunDataCard() {Column() {Text(this.isRunning ? 跑步中 : 今日跑步数据).fontSize(18).fontWeight(FontWeight.Bold).margin({ bottom: 25 })if (this.isRunning) {// 跑步中数据显示Column() {Text(${this.currentDistance.toFixed(2)}km).fontSize(42).fontWeight(FontWeight.Bold).margin({ bottom: 15 })Text(${this.formatTime(this.currentDuration)}).fontSize(24).margin({ bottom: 25 })Row() {Column() {Text(${this.currentPace.toFixed(2)}min/km).fontSize(16).fontWeight(FontWeight.Bold)Text(配速).fontSize(12).fontColor(#888).margin({top: 5})}.width(50%)Column() {Text(${this.formatTime(this.currentDuration)}).fontSize(16).fontWeight(FontWeight.Bold)Text(时长).fontSize(12).fontColor(#888).margin({top: 5})}.width(50%)}.width(100%)}.width(100%).alignItems(HorizontalAlign.Center).margin({ bottom: 25 })} else {// 跑步后数据显示Row() {Column() {Text(${this.getTodayDistance().toFixed(2)}km).fontSize(24).fontWeight(FontWeight.Bold)Text(距离).fontSize(12).fontColor(#888).margin({top: 5})}.width(33%)Column() {Text(${this.formatTime(this.getTodayDuration())}).fontSize(24).fontWeight(FontWeight.Bold)Text(时长).fontSize(12).fontColor(#888).margin({top: 5})}.width(33%)Column() {Text(${this.getTodayPace().toFixed(2)}min/km).fontSize(24).fontWeight(FontWeight.Bold)Text(配速).fontSize(12).fontColor(#888).margin({top: 5})}.width(33%)}.width(100%).margin({ bottom: 25 })}if (this.isRunning) {Button(结束跑步).width(100%).height(45).backgroundColor(#E53935).fontColor(Color.White).fontSize(16).borderRadius(8).onClick(() this.endRun())} else {Button(开始跑步).width(100%).height(45).backgroundColor(#2E7D32).fontColor(Color.White).fontSize(16).borderRadius(8).onClick(() this.startRun())}}.width(100%).padding(15).backgroundColor(Color.White).borderRadius(12).shadow({ radius: 3, color: #0000001A })}// 历史记录列表BuilderHistoryList() {if (this.showHistory) {Column() {Text(跑步历史).fontSize(16).fontWeight(FontWeight.Bold).margin({ bottom: 15 })if (this.runRecords.length 0) {Text(暂无跑步记录).fontSize(14).fontColor(#AAA).margin({ top: 40 })} else {List() {ForEach(this.runRecords, (record: RunRecord) {ListItem() {Row() {Column() {Text(this.formatDate(record.date)).fontSize(14)Text(${this.formatTime(record.duration)}).fontSize(12).fontColor(#888).margin({top: 4})}.width(40%)Column() {Text(${record.distance}km).fontSize(14).fontWeight(FontWeight.Bold)Text(${record.pace.toFixed(2)}min/km).fontSize(12).fontColor(#888).margin({top: 4})}.width(60%)}.width(100%).padding(8)}})}}}.width(100%).padding(15).backgroundColor(#F8F9FC).borderRadius(12).layoutWeight(1)}}build() {Column() {// 统计头部this.StatsHeader()// 跑步数据卡片this.RunDataCard()// 历史记录this.HistoryList()// 底部按钮Button(this.showHistory ? 隐藏历史 : 显示历史).width(100%).margin({ top: 15 }).height(40).fontSize(14).borderRadius(8).backgroundColor(#E0E0E0).fontColor(#333).onClick(() {this.showHistory !this.showHistory;})}.width(100%).height(100%).padding(12).backgroundColor(#FCFDFF)}
}