鸿蒙原生应用实战(四):愿望单与个人统计 — 数据聚合与可视化

鸿蒙原生应用实战(四):愿望单与个人统计 --- 数据聚合与可视化

一、前言

前文我们完成了首页、游戏库和详情页。本篇将开发最后两个页面------WishPage(愿望单)StatsPage(统计中心)

这两个页面代表了 App 数据维度的两个方向:

  • 愿望单:面向未来的"想玩游戏",帮助用户规划购游决策
  • 统计中心:面向过去的"游戏数据",通过可视化洞察玩家行为

二、愿望单页面 (WishPage)

2.1 页面功能

复制代码
┌─────────────────────────────────┐
│  ←           ⭐ 愿望单     5款  │
├─────────────────────────────────┤
│  🔴 高 3   🟡 中 1   🟢 低 1  │  ← 优先级概览
│  💰 总价估算                    │
├─────────────────────────────────┤
│  排序: 添加时间 ▼    [+ 添加]   │  ← 排序 + 添加按钮
├─────────────────────────────────┤
│ ┌───────────────────────────┐   │
│ │ 🎮  ┌────────────────┐   │   │
│ │     │ 黑神话:悟空  ¥268│   │  │
│ │     │ PC · 动作        │   │  │
│ │     │ 🔴 高优先级 📅待定│   │  │  ← 愿望单卡片
│ │     └────────────────┘   │   │
│ └───────────────────────────┘   │
│ ┌───────────────────────────┐   │
│ │  💡 愿望单小贴士          │   │  ← 小贴士区域
│ │  • 关注游戏折扣           │   │
│ │  • 设置优先级             │   │
│ │  • 查看评测后再购买       │   │
│ └───────────────────────────┘   │
└─────────────────────────────────┘

2.2 数据模型

愿望单数据比游戏库多了价格、优先级等专属字段:

typescript 复制代码
interface WishItem {
  id: number;
  title: string;
  platform: string;
  genre: string;
  price: string;          // 价格,如 "¥268" 或 "待定"
  releaseDate: string;    // 发行日期,如 "2025-08-20" 或 "待定"
  priority: string;       // 优先级:高 / 中 / 低
  addedDate: string;      // 添加日期
  coverColor: string;     // 封面颜色
}

2.3 初始化数据

typescript 复制代码
loadWishlist(): void {
  this.wishItems = [
    { id: 6, title: '黑神话: 悟空', platform: 'PC', genre: '动作',
      price: '¥268', releaseDate: '2025-08-20', priority: '高',
      addedDate: '2025-01-10', coverColor: '#1A1A2E' },
    { id: 7, title: '空洞骑士: 丝之歌', platform: 'PC', genre: '类银河城',
      price: '待定', releaseDate: '待定', priority: '高',
      addedDate: '2025-01-08', coverColor: '#E91E63' },
    { id: 8, title: '歧路旅人2', platform: 'Switch', genre: 'JRPG',
      price: '¥298', releaseDate: '2025-06-01', priority: '中',
      addedDate: '2025-01-05', coverColor: '#FF6B35' },
    { id: 13, title: '女神异闻录6', platform: 'PS5', genre: 'JRPG',
      price: '待定', releaseDate: '待定', priority: '高',
      addedDate: '2024-12-20', coverColor: '#E74C3C' },
    { id: 14, title: '丝之歌', platform: 'PC', genre: '类银河城',
      price: '待定', releaseDate: '待定', priority: '中',
      addedDate: '2024-12-15', coverColor: '#9B59B6' },
    { id: 15, title: '动物森友会 新作', platform: 'Switch', genre: '模拟',
      price: '待定', releaseDate: '待定', priority: '低',
      addedDate: '2024-11-01', coverColor: '#2ECC71' }
  ];
}

2.4 Header --- 带数据反馈

typescript 复制代码
@Builder buildHeader() {
  Row() {
    Text('←').fontSize(20).fontColor('#333333')
      .onClick(() => { router.back(); })
    Blank()
    Text('⭐ 愿望单')
      .fontSize(18).fontWeight(FontWeight.Bold).fontColor('#1A1A2E')
    Blank()
    Text(`${this.wishItems.length}款`)
      .fontSize(14).fontColor('#FF6B35').fontWeight(FontWeight.Medium)
  }
  .width('100%').padding({ left: 16, right: 16, top: 12, bottom: 12 })
  .backgroundColor('#FFFFFF')
}

右侧实时显示「N款」,给用户即时的数据反馈。

2.5 优先级概览卡片

typescript 复制代码
@Builder buildSummary() {
  Row() {
    Column() {
      Text('🔴').fontSize(18)
      Text(`高 ${this.wishItems.filter(w => w.priority === '高').length}`)
        .fontSize(12).fontColor('#E74C3C').margin({ top: 2 })
    }.layoutWeight(1).alignItems(HorizontalAlign.Center)

    Column() {
      Text('🟡').fontSize(18)
      Text(`中 ${this.wishItems.filter(w => w.priority === '中').length}`)
        .fontSize(12).fontColor('#F39C12').margin({ top: 2 })
    }.layoutWeight(1).alignItems(HorizontalAlign.Center)

    Column() {
      Text('🟢').fontSize(18)
      Text(`低 ${this.wishItems.filter(w => w.priority === '低').length}`)
        .fontSize(12).fontColor('#2ECC71').margin({ top: 2 })
    }.layoutWeight(1).alignItems(HorizontalAlign.Center)

    Column() {
      Text('💰').fontSize(18)
      Text('总价估算').fontSize(11).fontColor('#999999').margin({ top: 2 })
    }.layoutWeight(1).alignItems(HorizontalAlign.Center)
  }
  .width('100%').padding(14).backgroundColor('#FFFFFF')
  .margin({ top: 8, left: 16, right: 16 }).borderRadius(10)
}

设计思考 :使用 Emoji 圆点(🔴🟡🟢)配合颜色文字,比纯色块更生动。每个优先级用 filter 动态计数。

2.6 优先级颜色映射

typescript 复制代码
getPriorityColor(priority: string): ResourceStr {
  if (priority === '高') return '#E74C3C';   // 红色 - 紧迫
  if (priority === '中') return '#F39C12';   // 橙色 - 适中
  return '#95A5A6';                           // 灰色 - 观望
}

2.7 排序功能

typescript 复制代码
@Builder buildSortRow() {
  Row() {
    Text('排序:').fontSize(12).fontColor('#999999')
    Row() {
      Text(this.sortMode).fontSize(12).fontColor('#FF6B35')
      Text(' ▼').fontSize(10).fontColor('#FF6B35')
    }
    .padding({ left: 8, right: 10, top: 3, bottom: 3 })
    .backgroundColor('#FFF0E8').borderRadius(10).margin({ left: 6 })
    .onClick(() => {
      const modes: string[] = ['添加时间', '优先级', '发行日期'];
      const idx: number = modes.indexOf(this.sortMode);
      this.sortMode = modes[(idx + 1) % modes.length];
    })
    Blank()
    Text('➕ 添加').fontSize(12).fontColor('#FF6B35')
      .fontWeight(FontWeight.Medium)
  }
  .width('100%').padding({ left: 16, right: 16, top: 8 })
}

与游戏库页面的排序模式一致,保持交互统一性。

2.8 愿望单卡片

typescript 复制代码
@Builder buildWishCard(item: WishItem) {
  Row() {
    Stack() {
      Column().width(60).height(80).borderRadius(8)
        .backgroundColor(item.coverColor)
        .justifyContent(FlexAlign.Center)
      Text('🎮').fontSize(24)
    }.width(60).height(80)

    Column() {
      Row() {
        Text(item.title).fontSize(15).fontWeight(FontWeight.Medium)
          .fontColor('#1A1A2E').layoutWeight(1)
          .maxLines(1).textOverflow({ overflow: TextOverflow.Ellipsis })
        Text(item.price).fontSize(12).fontWeight(FontWeight.Bold)
          .fontColor('#FF6B35')
      }.width('100%')

      Text(`${item.platform} · ${item.genre}`)
        .fontSize(12).fontColor('#999999').margin({ top: 4 })

      Row() {
        Text(item.priority === '高' ? '🔴 高优先级'
           : item.priority === '中' ? '🟡 中优先级'
           : '🟢 低')
          .fontSize(11).fontColor(this.getPriorityColor(item.priority))
        Text(` 📅 ${item.releaseDate}`)
          .fontSize(11).fontColor('#BBBBBB').margin({ left: 6 })
      }.margin({ top: 4 })
    }
    .alignItems(HorizontalAlign.Start).margin({ left: 10 }).layoutWeight(1)
  }
  .width('100%').padding(12).backgroundColor('#FFFFFF')
  .borderRadius(10).margin({ top: 8, left: 16, right: 16 })
  .onClick(() => {
    router.pushUrl({
      url: 'pages/GameDetailPage',
      params: { gameId: item.id }
    })
  })
}

与游戏库卡片的异同

  • 相同:左侧封面色块、右侧信息布局、整体尺寸
  • 不同:展示价格(橙色显眼)、优先级(带emoji)、发行日期
  • 点击同样跳转详情页

2.9 愿望单小贴士

typescript 复制代码
@Builder buildEmptySuggestion() {
  Column() {
    Text('💡 愿望单小贴士')
      .fontSize(14).fontWeight(FontWeight.Bold).fontColor('#1A1A2E')
      .width('100%')

    Column() {
      Row() {
        Text('•').fontSize(12).fontColor('#FF6B35')
        Text(' 关注游戏折扣,愿望单中降价会通知你')
          .fontSize(12).fontColor('#666666').margin({ left: 4 })
      }.width('100%').margin({ top: 6 })
      Row() {
        Text('•').fontSize(12).fontColor('#FF6B35')
        Text(' 设置优先级,帮你决定先入手哪款')
          .fontSize(12).fontColor('#666666').margin({ left: 4 })
      }.width('100%').margin({ top: 4 })
      Row() {
        Text('•').fontSize(12).fontColor('#FF6B35')
        Text(' 查看游戏评测后再决定是否购买')
          .fontSize(12).fontColor('#666666').margin({ left: 4 })
      }.width('100%').margin({ top: 4 })
    }.width('100%').margin({ top: 8 })
  }
  .width('100%').padding(16).backgroundColor('#FFFFFF')
  .borderRadius(10).margin({ top: 12, left: 16, right: 16 })
}

这个小贴士模块既丰富了页面内容,又在 UI 上平衡了列表的视觉密度。

2.10 组装

typescript 复制代码
build(): void {
  Column() {
    this.buildHeader()
    Scroll() {
      Column() {
        this.buildSummary()
        this.buildSortRow()
        ForEach(this.wishItems, (item: WishItem) => {
          this.buildWishCard(item)
        }, (item: WishItem) => item.id.toString())
        this.buildEmptySuggestion()
      }.width('100%').padding({ bottom: 30 })
    }
    .scrollable(ScrollDirection.Vertical).layoutWeight(1).width('100%')
  }
  .width('100%').height('100%').backgroundColor('#F5F5F5')
}

三、统计中心页面 (StatsPage) --- 340行数据可视化

3.1 页面功能

复制代码
┌─────────────────────────────────┐
│  ←          📊 游戏统计     分享 │
├─────────────────────────────────┤
│  🎮 12  🏆 5  ⏱️ 979h  📊 42%│  ← 统计概览网格
├─────────────────────────────────┤
│  🎯 类型分布                    │  ← 横向条形图
│  🟧 开放世界  ████████████  3款  │
│  🟦 动作RPG   ████████      2款  │
│  🟩 JRPG      ████████████  3款  │
│  🟥 动作      ████████      2款  │
│  ⬜ 其他      ████████      2款  │
├─────────────────────────────────┤
│  📅 月度活跃                    │  ← 柱状图
│  1月 2月 3月 4月 5月 6月 ...    │
├─────────────────────────────────┤
│  🏅 游戏之最                    │  ← 游戏之最
│  🏆 巫师3:狂猎  210小时         │
│  平均评分 47/50 | 完成度63%     │
├─────────────────────────────────┤
│  ⏱️ 时间分布                    │  ← 时间排名
│  巫师3  ████████████████  21%   │
│  艾尔登 ███████████████   19%   │
│  ...                            │
├─────────────────────────────────┤
│  🏅 成就进度                    │
│  12 | 6 已解锁 | 50%完成率      │
├─────────────────────────────────┤
│  📈 年度对比                    │
│  2024: 8款 420h | 2025: 4款 180h│
└─────────────────────────────────┘

3.2 数据模型

typescript 复制代码
interface GameStat {
  label: string;    // 标签名
  value: string;    // 数值
  color: ResourceStr;  // 主题色
  icon: string;     // 图标
}

interface GenrePie {
  name: string;     // 类型名
  count: number;    // 数量
  color: ResourceStr;  // 颜色
}

3.3 状态定义

typescript 复制代码
@State stats: GameStat[] = [];
@State genreStats: GenrePie[] = [];
@State topGame: string = '巫师3: 狂猎';
@State topHours: number = 210;
@State avgRating: number = 47;
@State avgCompletion: number = 63;

3.4 统计数据初始化

typescript 复制代码
calcStats(): void {
  this.stats = [
    { label: '游戏总数', value: '12', color: '#FF6B35', icon: '🎮' },
    { label: '通关数',   value: '5',  color: '#2ECC71', icon: '🏆' },
    { label: '总时长',   value: '979h', color: '#3498DB', icon: '⏱️' },
    { label: '完成率',   value: '42%', color: '#9B59B6', icon: '📊' }
  ];

  this.genreStats = [
    { name: '开放世界', count: 3, color: '#FF6B35' },
    { name: '动作RPG',  count: 2, color: '#3498DB' },
    { name: 'JRPG',     count: 3, color: '#2ECC71' },
    { name: '动作',     count: 2, color: '#E74C3C' },
    { name: '其他',     count: 2, color: '#95A5A6' }
  ];
}

3.5 统计概览网格

typescript 复制代码
@Builder buildStatGrid() {
  Column() {
    Row() {
      ForEach(this.stats, (stat: GameStat) => {
        Column() {
          Text(stat.icon).fontSize(22)
          Text(stat.value).fontSize(20).fontWeight(FontWeight.Bold)
            .fontColor(stat.color as string).margin({ top: 4 })
          Text(stat.label).fontSize(11).fontColor('#999999').margin({ top: 2 })
        }.layoutWeight(1).alignItems(HorizontalAlign.Center)
      }, (stat: GameStat) => stat.label)
    }
  }
  .width('100%').padding(14).backgroundColor('#FFFFFF')
  .borderRadius(10).margin({ top: 8, left: 16, right: 16 })
}

使用 ForEach + layoutWeight(1) 实现 4 列等宽布局。

3.6 类型分布 --- 横向条形图 ⭐

这是我们用纯 ArkTS 组件实现的最典型的条形图:

typescript 复制代码
@Builder buildGenreStats() {
  Column() {
    Text('🎯 类型分布')
      .fontSize(15).fontWeight(FontWeight.Bold).fontColor('#1A1A2E')
      .width('100%')

    ForEach(this.genreStats, (genre: GenrePie) => {
      Row() {
        // 颜色圆点
        Column().width(10).height(10).borderRadius(5)
          .backgroundColor(genre.color as string)
        // 类型名
        Text(genre.name).fontSize(13).fontColor('#333333')
          .margin({ left: 8 }).width(64)

        // 条形图(Stack 实现精准宽度)
        Stack() {
          Column().width('100%').height(14)
            .backgroundColor('#F0F0F0').borderRadius(7)
          Column()
            .width(`${(genre.count / 12 * 100).toString()}%`)
            .height(14).backgroundColor(genre.color as string)
            .borderRadius(7).alignSelf(ItemAlign.Start)
        }.layoutWeight(1)

        Text(`${genre.count}款`)
          .fontSize(11).fontColor('#999999').width(30)
          .textAlign(TextAlign.End)
      }.width('100%').margin({ top: 8 })
    }, (genre: GenrePie) => genre.name)
  }
  // ...
}

实现原理

  • 用两层 Column 叠加:底层灰色(#F0F0F0)作为背景槽,上层彩色作为填充
  • 填充宽度 = (count / 总游戏数12) * 100%,使用字符串模板拼接
  • alignSelf(ItemAlign.Start) 确保条形从左开始填充

3.7 月度活跃柱状图

typescript 复制代码
@Builder buildMonthlyActivity() {
  const months: string[][] = [
    ['1月', '3', '在玩3款'], ['2月', '2', '通关1款'],
    ['3月', '1', '新购1款'], ['4月', '0', '放置中'],
    ['5月', '2', '回归2款'], ['6月', '1', '新购1款'],
    ['7月', '0', '出差'],     ['8月', '1', '通关1款'],
    ['9月', '2', '开坑2款'], ['10月', '3', '活跃'],
    ['11月', '1', '摸鱼'],   ['12月', '2', '年终冲量']
  ];

  Column() {
    Text('📅 月度活跃')
      .fontSize(15).fontWeight(FontWeight.Bold).fontColor('#1A1A2E')
      .width('100%')

    Scroll() {
      Row() {
        ForEach(months, (m: string[]) => {
          Column() {
            Text(m[0]).fontSize(10).fontColor('#999999')
            const h: number = parseInt(m[1]) * 18;
            Column().width(20).height(h > 0 ? h : 8)
              .backgroundColor(h > 0 ? '#FF6B35' : '#F0F0F0')
              .borderRadius(4).margin({ top: 6 })
            Text(m[1]).fontSize(10).fontColor('#FF6B35')
              .fontWeight(FontWeight.Medium).margin({ top: 2 })
          }
          .layoutWeight(1).alignItems(HorizontalAlign.Center)
        }, (m: string[]) => m[0])
      }.padding({ left: 8, right: 8 })
    }
    .scrollable(ScrollDirection.Horizontal).height(100).margin({ top: 8 })
  }
  // ...
}

设计亮点

  • 柱高 = 月份活跃数 × 18px,线性映射
  • 活跃度为 0 时,显示 8px 灰色矮柱(表示该月无活动)
  • 活跃度 > 0 时,显示橙色柱,高度随数值变化
  • Scroll(ScrollDirection.Horizontal) 支持横向滚动查看 12 个月

3.8 游戏之最

typescript 复制代码
@Builder buildTopGame() {
  Column() {
    Text('🏅 游戏之最')
      .fontSize(15).fontWeight(FontWeight.Bold).fontColor('#1A1A2E')

    Row() {
      Stack() {
        Column().width(56).height(56).borderRadius(28)
          .backgroundColor('#FFD700').justifyContent(FlexAlign.Center)
        Text('🏆').fontSize(28)
      }
      Column() {
        Text('时长最长的游戏').fontSize(11).fontColor('#999999')
        Text(this.topGame).fontSize(16).fontWeight(FontWeight.Bold)
          .fontColor('#1A1A2E').margin({ top: 2 })
        Text(`${this.topHours}小时`).fontSize(13).fontColor('#FF6B35')
          .margin({ top: 2 })
      }.alignItems(HorizontalAlign.Start).margin({ left: 12 }).layoutWeight(1)
    }.width('100%').margin({ top: 10 })

    // 三个平均指标
    Row() {
      Column() {
        Text('平均评分').fontSize(11).fontColor('#999999')
        Text(this.avgRating.toString()).fontSize(20)
          .fontWeight(FontWeight.Bold).fontColor('#FF6B35')
        Text('/50').fontSize(11).fontColor('#999999')
      }.layoutWeight(1).alignItems(HorizontalAlign.Center)

      Column() {
        Text('平均完成度').fontSize(11).fontColor('#999999')
        Text(`${this.avgCompletion}%`).fontSize(20)
          .fontWeight(FontWeight.Bold).fontColor('#2ECC71')
        Text('已通关游戏').fontSize(11).fontColor('#999999')
      }.layoutWeight(1).alignItems(HorizontalAlign.Center)

      Column() {
        Text('最爱平台').fontSize(11).fontColor('#999999')
        Text('PC').fontSize(20).fontWeight(FontWeight.Bold).fontColor('#3498DB')
        Text('7款游戏').fontSize(11).fontColor('#999999')
      }.layoutWeight(1).alignItems(HorizontalAlign.Center)
    }
    .width('100%').margin({ top: 14 }).padding({ top: 12 })
    .border({ width: { top: 1 }, color: '#F0F0F0' })
  }
  // ...
}

使用 border({ width: { top: 1 }, color: '#F0F0F0' }) 实现顶部分隔线------ArkTS 中 BorderOptions 支持按边设置。

3.9 时间分布排名

typescript 复制代码
@Builder buildTimeDistribution() {
  Column() {
    Text('⏱️ 时间分布(按游戏)')
      .fontSize(15).fontWeight(FontWeight.Bold).fontColor('#1A1A2E')

    const timeData: string[][] = [
      ['巫师3', '210h', '21%', '#FF6B35'],
      ['艾尔登法环', '186h', '19%', '#FFD700'],
      ['荒野大镖客2', '185h', '19%', '#E74C3C'],
      ['赛博朋克2077', '120h', '12%', '#3498DB'],
      ['只狼', '98h', '10%', '#E74C3C'],
      ['塞尔达', '72h', '7%', '#2ECC71'],
      ['其他', '108h', '12%', '#95A5A6']
    ];

    ForEach(timeData, (data: string[]) => {
      Row() {
        Text(data[0]).fontSize(13).fontColor('#333333').width(64)
        Text(data[1]).fontSize(11).fontColor('#999999').width(36)
          .textAlign(TextAlign.Center)
        Stack() {
          Column().width('100%').height(10).backgroundColor('#F0F0F0')
            .borderRadius(5)
          Column().width(data[2]).height(10)
            .backgroundColor(data[3] as string).borderRadius(5)
            .alignSelf(ItemAlign.Start)
        }.layoutWeight(1)
        Text(data[2]).fontSize(11).fontColor('#999999').width(32)
          .textAlign(TextAlign.End)
      }.width('100%').margin({ top: 6 })
    }, (data: string[]) => data[0])
  }
  // ...
}

每条数据包含:游戏名(64px固定宽)、时长(36px)、条形图(弹性宽度)、占比(32px)。

3.10 年度对比

typescript 复制代码
@Builder buildYearComparison() {
  Column() {
    Text('📈 年度对比')
      .fontSize(15).fontWeight(FontWeight.Bold).fontColor('#1A1A2E')

    Row() {
      Column() {
        Text('2024').fontSize(14).fontWeight(FontWeight.Bold)
          .fontColor('#999999')
        Text('8款').fontSize(18).fontWeight(FontWeight.Bold)
          .fontColor('#333333').margin({ top: 4 })
        Text('420小时').fontSize(11).fontColor('#999999').margin({ top: 2 })
      }
      .layoutWeight(1).alignItems(HorizontalAlign.Center)
      .padding(12).backgroundColor('#F5F5F5').borderRadius(8)
      .margin({ right: 6 })

      Column() {
        Text('2025').fontSize(14).fontWeight(FontWeight.Bold)
          .fontColor('#FF6B35')
        Text('4款').fontSize(18).fontWeight(FontWeight.Bold)
          .fontColor('#FF6B35').margin({ top: 4 })
        Text('180小时').fontSize(11).fontColor('#FF6B35').margin({ top: 2 })
      }
      .layoutWeight(1).alignItems(HorizontalAlign.Center)
      .padding(12).backgroundColor('#FFF0E8').borderRadius(8)
      .margin({ left: 6 })
    }.width('100%').margin({ top: 10 })
  }
  // ...
}

通过背景色和文字颜色区分:2024 年为灰色低调,2025 年为主色调橙色高亮。

3.11 组装

typescript 复制代码
build(): void {
  Column() {
    this.buildHeader()
    Scroll() {
      Column() {
        this.buildStatGrid()         // 统计概览
        this.buildGenreStats()       // 类型分布
        this.buildMonthlyActivity()  // 月度活跃
        this.buildTopGame()          // 游戏之最
        this.buildTimeDistribution() // 时间分布
        this.buildAchievementStats() // 成就进度
        this.buildYearComparison()   // 年度对比
      }
      .width('100%').padding({ bottom: 30 })
    }
    .scrollable(ScrollDirection.Vertical).layoutWeight(1).width('100%')
  }
  .width('100%').height('100%').backgroundColor('#F5F5F5')
}

四、从数据到视觉:ArkTS 图表绘制技术总结

本项目所有图表均使用纯 ArkTS 组件实现,无需第三方库:

图表类型 实现方式 核心组件
统计数字 Text + layoutWeight 均分 Column, Row
水平条形图 双层 Column 叠加 Stack, Column
月度柱状图 动态高度 Column Column, parseInt()
进度条 ArkTS 内置 Progress Progress

通用模式

复制代码
Stack {
  Column().width('100%')      // 灰色背景槽
  Column().width('80%')       // 彩色填充条
    .alignSelf(ItemAlign.Start) // 左对齐
}

五、小结

本篇完成了最后两个页面:

愿望单页面:

  1. ✅ 优先级分类(高/中/低)可视化概览
  2. ✅ 排序切换(添加时间/优先级/发行日期)
  3. ✅ 愿望单卡片(含价格、优先级标签)
  4. ✅ 愿望单小贴士模块

统计中心页面:

  1. ✅ 统计概览网格(4 个核心指标)
  2. ✅ 类型分布水平条形图
  3. ✅ 月度活跃柱状图
  4. ✅ 游戏之最 + 平均指标
  5. ✅ 时间分布排名
  6. ✅ 成就进度统计
  7. ✅ 年度对比卡片

下篇将进行 全项目工程优化:路由架构、代码组织、测试实践与构建配置。


系列目录

  • 第一篇:项目搭建与首页开发
  • 第二篇:游戏库列表与筛选排序
  • 第三篇:游戏详情页与交互功能
  • 第四篇:愿望单与个人统计(本文)
  • 第五篇:路由导航与工程优化
相关推荐
木咺吟3 小时前
鸿蒙原生应用实战(二):游戏库列表与筛选排序 — 卡片式UI设计
harmonyos
互联网散修4 小时前
鸿蒙实战:从零实现自定义相机(下)——填平预览拉伸、比例错乱、缩略图消失的六大坑
数码相机·华为·harmonyos
风华圆舞4 小时前
鸿蒙 + Flutter 下 AI 助手为什么要支持流式输出
人工智能·flutter·harmonyos
金启攻5 小时前
【鸿蒙原生应用实战】第四篇:打包清单——勾选交互、进度计算与实用工具
harmonyos
Swift社区5 小时前
鸿蒙 App 卡顿分析:定位方法 + 优化代码实战
华为·harmonyos
坚果派·白晓明6 小时前
鸿蒙 PC 应用集成 libhv 鸿蒙化三方库 —— AtomCode + Skills 驱动的高效集成实践
c语言·c++·ai编程·harmonyos·atomcode
祭曦念7 小时前
【共创季稿事节】HarmonyOS动态任务列表开发实战
华为·harmonyos
祭曦念7 小时前
【共创季稿事节】鸿蒙原生ArkTS动态列表布局实战_State_ForEach完整指南
华为·harmonyos
不羁的木木8 小时前
《HarmonyOS 6.1 新能力实战之智感握姿》第二篇:核心功能——查询与监听握持手状态
华为·harmonyos