及时做APP开发实战(十九)-数据统计与图表展示

及时做APP开发实战(十九):数据统计与图表展示功能实现

本文将详细介绍如何在"及时做"APP中实现专注数据统计和可视化图表展示功能。通过本文,您将学习到:数据模型设计、统计计算逻辑、图表组件实现以及数据查询优化等核心内容,帮助用户更好地了解自己的专注习惯。

一、功能需求分析

1.1 统计页面布局设计

图1:统计页面整体布局设计

复制代码
┌─────────────────────────────────────┐
│  📊 专注统计                          │
├─────────────────────────────────────┤
│  ┌─────────────────────────────┐   │
│  │ 今日专注                     │   │
│  │                              │   │
│  │   🍅 8个番茄钟               │   │
│  │   ⏱️ 200分钟                 │   │
│  │   📝 完成5个待办             │   │
│  └─────────────────────────────┘   │
│                                     │
│  ┌─────────────────────────────┐   │
│  │ 本周统计                     │   │
│  │                              │   │
│  │  一  二  三  四  五  六  日   │   │
│  │  ▓▓  ▓▓▓ ▓▓ ▓▓▓▓ ▓▓ ▓  ▓▓▓  │   │
│  │  2   3   2   4    2   1   3   │   │
│  └─────────────────────────────┘   │
│                                     │
│  ┌─────────────────────────────┐   │
│  │ 累计统计                     │   │
│  │                              │   │
│  │   总专注时长: 50小时         │   │
│  │   总番茄钟: 100个            │   │
│  │   总待办: 80个               │   │
│  └─────────────────────────────┘   │
└─────────────────────────────────────┘

1.2 统计维度

维度 说明 展示方式
今日统计 今日专注数据 数字卡片
本周统计 近7天专注趋势 柱状图
本月统计 近30天专注趋势 折线图
累计统计 历史总数据 数字汇总

二、数据模型

2.1 统计数据模型

【图2:统计图表效果】

typescript 复制代码
// src/main/ets/model/StatisticsModel.ets

export class DailyStatistics {
  date: string = '';              // 日期 YYYY-MM-DD
  pomodoroCount: number = 0;      // 番茄钟数
  focusMinutes: number = 0;       // 专注分钟数
  completedTodos: number = 0;     // 完成待办数
}

export class StatisticsSummary {
  // 今日统计
  todayPomodoros: number = 0;
  todayMinutes: number = 0;
  todayTodos: number = 0;
  
  // 本周统计
  weekPomodoros: number = 0;
  weekMinutes: number = 0;
  weekData: DailyStatistics[] = [];
  
  // 累计统计
  totalPomodoros: number = 0;
  totalMinutes: number = 0;
  totalTodos: number = 0;
}

export class ChartData {
  label: string = '';      // 标签(日期)
  value: number = 0;       // 数值
  color: string = '';      // 颜色
}

2.2 统计计算逻辑

typescript 复制代码
// src/main/ets/viewmodel/StatisticsViewModel.ets

export class StatisticsViewModel {
  private focusRepository: FocusRepository;
  private todoRepository: TodoRepository;
  
  /**
   * 获取今日统计
   */
  async getTodayStatistics(): Promise<DailyStatistics> {
    const today = DateTimeUtil.formatDate(new Date());
    const records = await this.focusRepository.getRecordsByDate(today);
    
    const statistics = new DailyStatistics();
    statistics.date = today;
    statistics.pomodoroCount = records.length;
    statistics.focusMinutes = records.reduce((sum, r) => sum + r.duration, 0);
    
    const todos = await this.todoRepository.getCompletedByDate(today);
    statistics.completedTodos = todos.length;
    
    return statistics;
  }
  
  /**
   * 获取本周统计
   */
  async getWeekStatistics(): Promise<DailyStatistics[]> {
    const weekData: DailyStatistics[] = [];
    const today = new Date();
    
    for (let i = 6; i >= 0; i--) {
      const date = new Date(today);
      date.setDate(date.getDate() - i);
      
      const dateStr = DateTimeUtil.formatDate(date);
      const dayStats = await this.getDayStatistics(dateStr);
      weekData.push(dayStats);
    }
    
    return weekData;
  }
  
  /**
   * 获取累计统计
   */
  async getTotalStatistics(): Promise<StatisticsSummary> {
    const summary = new StatisticsSummary();
    
    const allRecords = await this.focusRepository.getAllRecords();
    summary.totalPomodoros = allRecords.length;
    summary.totalMinutes = allRecords.reduce((sum, r) => sum + r.duration, 0);
    
    const allTodos = await this.todoRepository.getAllTodos();
    summary.totalTodos = allTodos.filter(t => t.completed).length;
    
    return summary;
  }
}

三、统计页面实现

3.1 页面结构

【图3:统计卡片效果】

typescript 复制代码
// src/main/ets/pages/StatisticsPage.ets

@Entry
@Component
struct StatisticsPage {
  @State todayStats: DailyStatistics = new DailyStatistics();
  @State weekData: DailyStatistics[] = [];
  @State totalStats: StatisticsSummary = new StatisticsSummary();
  @State isLoading: boolean = true;
  
  async aboutToAppear() {
    await this.loadStatistics();
  }
  
  async loadStatistics() {
    const viewModel = new StatisticsViewModel();
    
    this.todayStats = await viewModel.getTodayStatistics();
    this.weekData = await viewModel.getWeekStatistics();
    this.totalStats = await viewModel.getTotalStatistics();
    
    this.isLoading = false;
  }
  
  build() {
    Column() {
      this.TitleBar()
      
      if (this.isLoading) {
        this.LoadingView()
      } else {
        this.StatisticsContent()
      }
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#F5F5F5')
  }
}

3.2 今日统计卡片

typescript 复制代码
@Builder
TodayStatisticsCard() {
  Column() {
    Text('今日专注')
      .fontSize(16)
      .fontWeight(FontWeight.Bold)
      .margin({ bottom: 15 })
    
    Row() {
      // 番茄钟数
      Column() {
        Text('🍅')
          .fontSize(24)
        Text(`${this.todayStats.pomodoroCount}`)
          .fontSize(28)
          .fontWeight(FontWeight.Bold)
          .fontColor('#FF6B6B')
        Text('个番茄钟')
          .fontSize(12)
          .fontColor('#999999')
      }
      .layoutWeight(1)
      
      // 专注时长
      Column() {
        Text('⏱️')
          .fontSize(24)
        Text(`${this.todayStats.focusMinutes}`)
          .fontSize(28)
          .fontWeight(FontWeight.Bold)
          .fontColor('#4CAF50')
        Text('分钟')
          .fontSize(12)
          .fontColor('#999999')
      }
      .layoutWeight(1)
      
      // 完成待办
      Column() {
        Text('📝')
          .fontSize(24)
        Text(`${this.todayStats.completedTodos}`)
          .fontSize(28)
          .fontWeight(FontWeight.Bold)
          .fontColor('#2196F3')
        Text('个待办')
          .fontSize(12)
          .fontColor('#999999')
      }
      .layoutWeight(1)
    }
    .width('100%')
  }
  .width('100%')
  .padding(20)
  .backgroundColor('#FFFFFF')
  .borderRadius(12)
  .margin({ top: 15, left: 15, right: 15 })
}

3.3 本周统计图表

typescript 复制代码
@Builder
WeekChartCard() {
  Column() {
    Text('本周统计')
      .fontSize(16)
      .fontWeight(FontWeight.Bold)
      .margin({ bottom: 15 })
    
    // 柱状图
    Row() {
      ForEach(this.weekData, (day: DailyStatistics, index: number) => {
        Column() {
          // 柱子
          Column()
            .width(20)
            .height(this.getBarHeight(day.pomodoroCount))
            .backgroundColor('#FF6B6B')
            .borderRadius({ topLeft: 4, topRight: 4 })
          
          // 日期标签
          Text(this.getWeekDayLabel(index))
            .fontSize(10)
            .fontColor('#999999')
            .margin({ top: 5 })
        }
        .layoutWeight(1)
        .justifyContent(FlexAlign.End)
      })
    }
    .width('100%')
    .height(120)
  }
  .width('100%')
  .padding(20)
  .backgroundColor('#FFFFFF')
  .borderRadius(12)
  .margin({ top: 15, left: 15, right: 15 })
}

// 计算柱子高度
private getBarHeight(count: number): number {
  const maxHeight = 80;
  const maxCount = Math.max(...this.weekData.map(d => d.pomodoroCount), 1);
  return (count / maxCount) * maxHeight;
}

// 获取星期标签
private getWeekDayLabel(index: number): string {
  const labels = ['一', '二', '三', '四', '五', '六', '日'];
  return labels[index];
}

3.4 累计统计卡片

typescript 复制代码
@Builder
TotalStatisticsCard() {
  Column() {
    Text('累计统计')
      .fontSize(16)
      .fontWeight(FontWeight.Bold)
      .margin({ bottom: 15 })
    
    Column() {
      // 总专注时长
      Row() {
        Text('总专注时长')
          .fontSize(14)
          .fontColor('#666666')
        
        Blank()
        
        Text(`${Math.floor(this.totalStats.totalMinutes / 60)}小时${this.totalStats.totalMinutes % 60}分钟`)
          .fontSize(14)
          .fontWeight(FontWeight.Bold)
          .fontColor('#FF6B6B')
      }
      .width('100%')
      .margin({ bottom: 10 })
      
      // 总番茄钟
      Row() {
        Text('总番茄钟')
          .fontSize(14)
          .fontColor('#666666')
        
        Blank()
        
        Text(`${this.totalStats.totalPomodoros}个`)
          .fontSize(14)
          .fontWeight(FontWeight.Bold)
          .fontColor('#4CAF50')
      }
      .width('100%')
      .margin({ bottom: 10 })
      
      // 总完成待办
      Row() {
        Text('总完成待办')
          .fontSize(14)
          .fontColor('#666666')
        
        Blank()
        
        Text(`${this.totalStats.totalTodos}个`)
          .fontSize(14)
          .fontWeight(FontWeight.Bold)
          .fontColor('#2196F3')
      }
      .width('100%')
    }
  }
  .width('100%')
  .padding(20)
  .backgroundColor('#FFFFFF')
  .borderRadius(12)
  .margin({ top: 15, left: 15, right: 15, bottom: 15 })
}

四、数据查询实现

4.1 按日期查询

typescript 复制代码
// src/main/ets/repository/FocusRepository.ets

export class FocusRepository {
  /**
   * 获取指定日期的专注记录
   */
  async getRecordsByDate(date: string): Promise<FocusRecord[]> {
    const allRecords = await this.getAllRecords();
    return allRecords.filter(record => {
      const recordDate = DateTimeUtil.formatDate(new Date(record.startTime));
      return recordDate === date;
    });
  }
  
  /**
   * 获取日期范围内的专注记录
   */
  async getRecordsByDateRange(startDate: string, endDate: string): Promise<FocusRecord[]> {
    const allRecords = await this.getAllRecords();
    return allRecords.filter(record => {
      const recordDate = DateTimeUtil.formatDate(new Date(record.startTime));
      return recordDate >= startDate && recordDate <= endDate;
    });
  }
}

4.2 待办统计查询

typescript 复制代码
// src/main/ets/repository/TodoRepository.ets

export class TodoRepository {
  /**
   * 获取指定日期完成的待办
   */
  async getCompletedByDate(date: string): Promise<TodoItem[]> {
    const allTodos = await this.getAllTodos();
    return allTodos.filter(todo => {
      if (!todo.completed) return false;
      const completedDate = DateTimeUtil.formatDate(new Date(todo.completedAt || todo.createdAt));
      return completedDate === date;
    });
  }
}

五、总结

本文实现了专注数据统计和图表展示功能,核心要点:

  1. 数据模型:DailyStatistics、StatisticsSummary
  2. 统计计算:今日、本周、累计统计
  3. 图表展示:柱状图、数字卡片
  4. 数据查询:按日期范围查询

相关推荐
YM52e11 小时前
鸿蒙Flutter Card组件:Material设计风格卡片
android·学习·flutter·华为·harmonyos·鸿蒙
世人万千丶21 小时前
鸿蒙Flutter TextStyle样式配置
学习·flutter·harmonyos·鸿蒙
xd1855785551 天前
道歉话术生成:基于鸿蒙生态的智能情感沟通助手
人工智能·华为·harmonyos·鸿蒙
绝世番茄1 天前
鸿蒙原生 ArkTS 布局方式之 Button+Shake 抖动按钮实战全解
华为·harmonyos·鸿蒙
●VON1 天前
鸿蒙 PC Markdown 编辑器换行兼容:LF、CRLF 与混合换行归一化
华为·架构·编辑器·harmonyos·鸿蒙
千逐681 天前
鸿蒙新特性 | 页面路由——router 怎么跳怎么传参
华为·harmonyos·鸿蒙
●VON1 天前
鸿蒙 PC Markdown 编辑器第二阶段工程复盘:从可用原型到 Alpha 基线
华为·编辑器·harmonyos·鸿蒙
熊猫钓鱼>_>1 天前
ArkTS 方舟编程语言 · 原创快速入门教程
运维·架构·ts·harmonyos·arkts·鸿蒙·js
●VON1 天前
鸿蒙 PC Markdown 编辑器查找系统:大小写、整词与循环定位
华为·单元测试·编辑器·harmonyos·鸿蒙