从零构建「时光织」:HarmonyOS 个人日程与时间规划管理工具全栈实践



从零构建「时光织」:HarmonyOS 个人日程与时间规划管理工具全栈实践
作者:duluo
平台:HarmonyOS Next (API 24) · ArkTS · DevEco Studio
应用名称:时光织 / TimeWeave
定位:融合 GTD + 时间块 + 番茄钟三大方法论的个人日程与时间规划管理工具
目录
- 项目背景与设计理念
- 技术选型与架构设计
- UI 组件体系与视觉语言
- 数据类型系统与模拟数据设计
- 路由系统与页面导航
- 核心模块实现详解
- 6.1 首页四 Tab 导航架构
- 6.2 日程 Tab:周日期选择器 + 时间线 + 习惯打卡
- 6.3 任务 Tab:GTD 四象限 + 快速添加
- 6.4 专注 Tab:番茄钟计时器 + setInterval 实时更新
- 6.5 任务管理详情页:收件箱 / 项目 / 四象限 / 标签
- 6.6 专注统计页:Canvas 柱状图 + 高效时段分析
- 6.7 周报复盘页:Canvas 饼图 + 完成率 + AI 建议
- ArkTS 语法陷阱与踩坑记录
- 性能优化与最佳实践
- 未来 roadmap
- 总结
1. 项目背景与设计理念
1.1 为什么选择时间管理方向
在信息过载的时代,时间管理成为了现代人最大的痛点之一。根据 2025 年的一项调研,超过 73% 的知识工作者表示「每天忙得不可开交,但回顾一天却想不起做了什么」。我们发现了市面上时间管理工具的三大缺口:
- 方法论割裂:GTD 工具只管任务,番茄钟只管计时,日历只管安排——用户需要在三个 App 之间来回切换
- 复盘缺失:大多数工具只关注「规划」,不关注「复盘」。用户不知道自己的时间花在了哪里,更不知道如何优化
- 学习成本高:Notion、Obsidian 功能强大但配置复杂,普通用户难以坚持
「时光织」(TimeWeave)的定位正是将 GTD + 时间块 + 番茄钟三大方法论融合到一个应用中,让用户从「规划 → 执行 → 复盘」形成一个完整闭环。名字「时光织」寓意将碎片化的时间编织成有序的锦缎。
1.2 设计哲学
我们的设计围绕四个核心原则展开:
- 闭环设计(Closed Loop):规划 → 执行 → 记录 → 复盘 → 再规划,形成完整的时间管理闭环
- 渐进式复杂度(Progressive Complexity):新用户打开 App 就能用番茄钟和任务列表;进阶用户可以使用 GTD 四象限和时间块规划;高级用户可以利用复盘数据优化时间分配
- 沉浸式专注(Immersive Focus):进入专注模式时,界面切换到深色沉浸视图,减少干扰
- 数据驱动复盘(Data-Driven Review):自动记录专注数据、任务完成率,每周生成可视化周报
1.3 目标用户画像
- 职场人士:需要高效规划工作日,管理多个项目并行
- 学生群体:需要管理课程/自习/考试安排
- 自由职业者:需要自主安排项目时间,保持自律
- 时间管理初学者:希望养成规划习惯,从番茄钟开始
2. 技术选型与架构设计
2.1 为什么选择 HarmonyOS Next (API 24)
| 特性 | 描述 | 在时光织中的应用 |
|---|---|---|
| ArkTS 声明式 UI | 基于 TypeScript 的声明式 UI 框架 | 全部页面采用 @Component + @State + @Builder |
| Canvas 2D API | 完整 Canvas 绘制能力 | 专注柱状图、周报时间饼图 |
| Progress 组件 | 原生进度条组件 | 任务完成率、时间分布、番茄钟进度 |
| Toggle 组件 | 原生开关组件 | 任务标记完成/未完成 |
| bindSheet | 底部 Sheet 弹窗 | 快速添加任务(预留接口) |
| setInterval | 定时器 API | 番茄钟倒计时实时更新 |
2.2 整体架构
┌────────────────────────────────────────────────┐
│ UI Layer │
│ ┌──────────┐ ┌──────────┐ ┌────────────────┐ │
│ │MainPage │ │TaskDetail│ │FocusStats │ │
│ │(4Tab Hub)│ │Page │ │Page │ │
│ └────┬─────┘ └────┬─────┘ └───────┬────────┘ │
│ ┌────┴────┐ │ │ │
│ │WeeklyRep│ │ │ │
│ │ortPage │ │ │ │
│ └─────────┘ │ │ │
├────────────────────┼───────────────┼───────────┤
│ Router (pushUrl / back) │
├────────────────────┼───────────────┼───────────┤
│ Data Layer │
│ ┌──────────┐ ┌──────────┐ ┌────────────────┐ │
│ │Interface │ │ Mock │ │ Utility │ │
│ │ Types │ │ Data │ │ Functions │ │
│ └──────────┘ └──────────┘ └────────────────┘ │
├────────────────────────────────────────────────┤
│ HarmonyOS API 24 Runtime │
└────────────────────────────────────────────────┘
2.3 核心架构决策
4Tab 单页面架构:与财富管理 App 类似,时光织也采用 4Tab 导航(日程/任务/专注/统计)作为首页。但有一个关键差异——专注中视图覆盖全局:当用户开始番茄钟后,无论当前在哪个 Tab,都会切换到全屏的沉浸式专注视图。这是通过一个 @State isFocusing: boolean 状态来实现的:
// 核心状态切换逻辑
build() {
Column() {
// Header
// Tab 栏
Scroll() {
Column() {
if (!this.isFocusing) {
if (this.tabIdx === 0) { this.TimelineTab() }
else if (this.tabIdx === 1) { this.TaskTab() }
else if (this.tabIdx === 2) { this.FocusTab() }
else { this.StatsTab() }
} else {
this.FocusingView() // 全屏沉浸式专注视图
}
}
}
}
}
这个设计决策让番茄钟体验更加沉浸——用户开始专注后,所有的 Tab 和任务列表都会隐藏,只剩下计时器和当前专注任务。
3. UI 组件体系与视觉语言
3.1 色彩系统
时光织采用「沉稳蓝 + 活力青」的主色调,传达冷静、专注、高效的品牌感受。
const COLORS = {
primary: '#1ABC9C', // 活力青 - 强调、CTA按钮、焦点色
darkBg: '#2C3E50', // 沉稳蓝 - 顶部 Header
darkCard: '#34495E', // 深色卡片
pageBg: '#F0F2F5', // 页面背景
cardBg: '#FFF', // 卡片背景
textPrimary: '#333', // 主文字
textSecondary: '#888', // 次要文字
// 时间块类型色
focus: '#3498DB', // 专注块 - 蓝色
break: '#2ECC71', // 休息块 - 绿色
buffer: '#95A5A6', // 缓冲块 - 灰色
meeting: '#E67E22', // 会议块 - 橙色
personal: '#9B59B6', // 个人块 - 紫色
};
色彩心理学考量:
- 沉稳蓝
#2C3E50作为背景色,营造「深海般」的冷静氛围,帮助用户进入专注状态 - 活力青
#1ABC9C作为强调色,在深色背景上格外醒目,指引用户的操作焦点 - 每种时间块类型有独立的色彩标识,用户在时间线上一眼就能区分不同类型的块
3.2 组件体系
原子组件 (Atoms)
├── Text(字号:9/10/11/12/13/14/15/16/18/20/22/24/48/56/64)
├── Progress(进度条)
├── Button(圆角按钮)
├── Toggle(开关 / 勾选框)
├── Circle(圆形指示器)
├── Divider(分割线)
├── Canvas(画布)
└── TextInput(文本输入框)
分子组件 (Molecules)
├── TabBtn(Tab 切换按钮)
├── QuadrantCard(四象限卡片)
├── PriorityBadge(优先级标签)
├── FocusModeBtn(专注模式按钮)
├── FocusStatCard(专注统计卡片)
├── StatCard(通用统计卡片)
├── DistBar(时间分布条)
├── OverviewCard(总览卡片)
├── RangeBtn(时间范围按钮)
├── HourBar(时段热度柱)
├── TagGroup(标签组)
└── TaskRow(任务行 + 展开详情)
组织组件 (Organisms)
├── 周日期选择器(7 天动态计算)
├── 时间线列表(时间块 + 类型标识)
├── 习惯打卡网格(5 个习惯)
├── 任务列表(带优先级和项目标签)
├── 番茄钟计时器(倒计时 + 进度环)
├── 四象限矩阵(2×2 网格)
├── 项目分组列表(带进度条)
├── 本周专注柱状图(Canvas 自绘)
└── 时间分布饼图(Canvas 自绘,甜甜圈效果)
页面 (Pages)
├── MainPage(首页 4Tab)
├── TaskDetailPage(任务管理详情)
├── FocusStatsPage(专注统计)
└── WeeklyReportPage(周报复盘)
3.3 卡片式布局
沿用与 WealthWise 一致的卡片式布局体系,白色圆角卡片承载独立信息模块:
Column() {
Row() {
Text('📈 本周专注趋势').fontSize(16).fontColor('#333').fontWeight(FontWeight.Bold)
Blank()
Text('周报 >').fontSize(12).fontColor('#1ABC9C')
}.width('100%')
// 卡片内容...
}
.width('94%').padding(14).backgroundColor('#FFF').borderRadius(14).margin({ top: 10 })
3.4 微交互设计
- 任务勾选:点击 ⬜ 切换为 ✅,文字自动添加删除线,给予完成任务的成就感
- 专注状态切换:开始专注 → 全屏沉浸深色视图;结束专注 → 恢复 Tab 导航
- Tab 切换:选中 Tab 图标放大、颜色变亮(活力青),未选中保持灰色
- 周日期选择:选中日期高亮为圆形绿色背景,当天自动定位
- 展开详情:点击任务行右侧
>展开描述和子任务,折叠状态保持界面清爽 - 优先级 Badge:高优先级红色、中优先级橙色、低优先级灰色,视觉层级清晰
4. 数据类型系统与模拟数据设计
4.1 接口定义体系
时光织定义了 7 个核心数据接口:
// 时间块
interface TimeBlock {
time: string; // 'HH:mm'
title: string; // 事件名称
type: string; // 'focus' | 'break' | 'buffer' | 'meeting' | 'personal'
duration: string; // '30min' | '2h'
color: string; // 类型对应色
}
// 任务
interface TaskItem {
id: number; title: string; priority: string; due: string;
done: boolean; project: string; estMin: number;
}
// 习惯
interface HabitItem {
icon: string; name: string; done: boolean; streak: number; color: string;
}
// 专注记录
interface FocusSession {
date: string; // '07/03'
minutes: number; // 专注时长
completed: number; // 完成番茄数
}
// 任务详情(GTD 完整字段)
interface TaskDetail {
id: number; title: string; priority: string; status: string;
project: string; estMin: number; due: string; tags: string[];
desc: string; subtasks: string[];
}
// 专注记录详情
interface FocusRecord {
time: string; task: string; minutes: number; completed: boolean;
}
// 周报数据
interface TimeDistItem {
label: string; icon: string; minutes: number; pct: number; color: string;
}
4.2 模拟数据策略
与 WealthWise 一样,我们使用静态模拟数据进行快速原型开发。数据覆盖了完整的「一天」使用场景:
时间块:12 个(7:00-20:00,涵盖专注/休息/缓冲/会议/个人)
任务:6 个(高/中/低三种优先级,4 个项目)
习惯:5 个(阅读/跑步/喝水/冥想/写日记)
专注记录:7 天(日均 3-5h 专注)
周报数据:6 个时间分类
今日专注记录:5 条
4.3 工具函数
// 分钟格式化:180 → '3h', 90 → '1h30min'
function fmtMin(m: number): string {
if (m >= 60) {
return Math.floor(m / 60) + 'h' + (m % 60 > 0 ? (m % 60) + 'min' : '');
}
return m + 'min';
}
// 获取今日日期(中文格式)
function getToday(): string {
const d = new Date();
return d.getFullYear() + '年' + (d.getMonth() + 1) + '月' +
d.getDate() + '日 周' + ['日','一','二','三','四','五','六'][d.getDay()];
}
5. 路由系统与页面导航
5.1 路由配置
{
"src": [
"pages/Index",
"pages/schedule_planner/MainPage",
"pages/schedule_planner/TaskDetailPage",
"pages/schedule_planner/FocusStatsPage",
"pages/schedule_planner/WeeklyReportPage"
]
}
5.2 导航图
Index.ets(应用中心)
└── 📅 时光织 → schedule_planner/MainPage
├── 任务 Tab「查看全部」 → TaskDetailPage
├── 专注 Tab「查看统计」 → FocusStatsPage
└── 统计 Tab「周报」 → WeeklyReportPage
5.3 返回导航
每个子页面顶栏都有 ← 返回 按钮,调用 router.back() 返回首页:
Text('← 返回').fontSize(14).fontColor('#1ABC9C')
.onClick(() => router.back())
6. 核心模块实现详解
6.1 首页四 Tab 导航架构
首页是时光织的核心,采用 4Tab 导航结构。与传统的底部 Tab 不同,我们采用了顶部 Tab 栏的设计,紧跟在 Header 下方,让用户的视线自然地从品牌信息转移到功能切换:
Row() {
this.TabBtn(0, '📅', '日程')
this.TabBtn(1, '✅', '任务')
this.TabBtn(2, '🍅', '专注')
this.TabBtn(3, '📊', '统计')
}
.width('94%').padding(6)
.backgroundColor('#34495E').borderRadius(14)
.margin({ top: 2 })
当用户开始番茄钟后,通过 @State isFocusing 状态切换视图:
if (!this.isFocusing) {
if (this.tabIdx === 0) { this.TimelineTab() }
// ...
} else {
this.FocusingView()
}
6.2 日程 Tab:周日期选择器 + 时间线 + 习惯打卡
6.2.1 周日期选择器
这是一个动态计算本周日期的组件。我们使用 weekDayNumbers getter 来计算当前周的 7 天日期:
get weekDayNumbers(): number[] {
const today = new Date().getDay();
return WEEKDAYS.map((_, idx) => this.selectedDate - (today - idx));
}
然后通过 ForEach 渲染 7 个日期按钮,选中的日期高亮为绿色:
ForEach(this.weekDayNumbers, (dayNum: number, idx: number) => {
Column() {
Text(WEEKDAYS[idx]).fontSize(10).fontColor('#888')
Text(dayNum.toString()).fontSize(14)
.fontColor(dayNum === this.selectedDate ? '#FFF' : '#333')
.fontWeight(dayNum === this.selectedDate ? FontWeight.Bold : FontWeight.Regular)
}
.layoutWeight(1).padding({ top: 4, bottom: 4 })
.backgroundColor(dayNum === this.selectedDate ? '#1ABC9C' : 'transparent')
.borderRadius(8).alignItems(HorizontalAlign.Center)
.onClick(() => { this.selectedDate = dayNum })
}, (d: number) => d.toString())
技术要点:这个组件展示了 ArkTS 中如何通过 getter 将动态计算逻辑从 UI 构建区域中分离出来。一开始我们把 const dayNum = this.selectedDate - (new Date().getDay() - idx) 写在了 ForEach 的回调中,触发了编译错误 Only UI component syntax can be written here。将计算逻辑抽取到 getter 后完美解决。
6.2.2 时间线
时间线是日程 Tab 的核心内容,展示一天的完整时间块规划。每个时间块行包含时间、类型色条、标题、时长:
ForEach(TIME_BLOCKS, (b: TimeBlock) => {
Row() {
Text(b.time).fontSize(11).fontColor('#888').width(44)
Column().width(3).height(48).backgroundColor(b.color).borderRadius(2)
Column({ space: 2 }) {
Text(b.title).fontSize(13).fontColor('#333').fontWeight(FontWeight.Medium)
Text(this.blockTypeLabel(b.type)).fontSize(10).fontColor(b.color)
}.margin({ left: 8 }).layoutWeight(1)
Text(b.duration).fontSize(11).fontColor('#AAA')
Text('>').fontSize(12).fontColor('#DDD').margin({ left: 4 })
}
.width('100%').padding({ top: 5, bottom: 5 })
.border({ width: { bottom: 1 }, color: '#F5F5F5' })
}, (b: TimeBlock) => b.time + b.title)
blockTypeLabel 方法将类型枚举转换为用户友好的标签:
blockTypeLabel(type: string): string {
const map: Record<string, string> = {
'focus': '🔵 专注', 'break': '🟢 休息',
'buffer': '⚪ 缓冲', 'meeting': '🟠 会议', 'personal': '🟣 个人'
};
return map[type] ?? type;
}
6.2.3 习惯打卡
习惯打卡采用 5 列网格布局,每个习惯显示图标、完成状态、名称和连续天数:
Row({ space: 8 }) {
ForEach(HABITS, (h: HabitItem) => {
Column() {
Text(h.icon).fontSize(24)
Text(h.done ? '✅' : '⬜').fontSize(14).margin({ top: 4 })
Text(h.name).fontSize(9).fontColor('#555').margin({ top: 2 })
.maxLines(1).textOverflow({ overflow: TextOverflow.Ellipsis })
Text('🔥' + h.streak + '天').fontSize(8).fontColor('#F59E0B').margin({ top: 1 })
}
.layoutWeight(1).padding(6).backgroundColor('#FFF').borderRadius(10)
.alignItems(HorizontalAlign.Center)
}, (h: HabitItem) => h.name)
}.width('100%').margin({ top: 8 })
6.3 任务 Tab:GTD 四象限 + 快速添加
任务 Tab 是时光织中 GTD 方法论的体现,包含三个核心组件:
6.3.1 快速添加输入框
顶部提供一个 TextInput 和 Button 的组合,方便用户快速捕获想法:
Row({ space: 6 }) {
TextInput({ placeholder: '📝 快速添加任务...' })
.width(0).layoutWeight(1).height(40).fontSize(14)
.placeholderColor('#AAA').backgroundColor('#FFF')
.borderRadius(20).padding({ left: 16 })
Button('+').width(40).height(40).backgroundColor('#1ABC9C')
.fontColor('#FFF').fontSize(22).borderRadius(20)
}
.width('94%').margin({ top: 10 })
6.3.2 四象限矩阵
这是 GTD 中最经典的可视化工具。我们将 2×2 矩阵拆分为 4 个 QuadrantCard:
this.QuadrantCard('重要紧急', '3项', '#E74C3C')
this.QuadrantCard('重要不紧急', '2项', '#F39C12')
this.QuadrantCard('紧急不重要', '1项', '#3498DB')
this.QuadrantCard('不紧急不重要', '0项', '#95A5A6')
每个卡片是一个带颜色的 Column,显示分类名称和任务数量。
6.3.3 任务列表
任务列表展示今日所有任务,支持勾选完成(文字自动添加删除线):
Text(t.done ? '✅' : '⬜').fontSize(16)
.onClick(() => { t.done = !t.done })
// ...
Text(t.title).fontSize(13).fontColor(t.done ? '#AAA' : '#333')
.decoration({
type: t.done ? TextDecorationType.LineThrough : TextDecorationType.None
})
每个任务还展示了优先级 Badge、项目名称、预估时间和截止日期。
6.4 专注 Tab:番茄钟计时器 + setInterval 实时更新
专注模块是时光织的核心功能之一,包含番茄钟计时器和当日专注汇总。
6.4.1 番茄钟计时器
计时器的核心是一个 25 分钟的倒计时,使用 setInterval 每秒更新:
startFocus(): void {
this.isFocusing = true;
this.focusMinutes = 25;
this.focusSeconds = 0;
this.timerId = setInterval(() => {
if (this.focusSeconds === 0) {
if (this.focusMinutes === 0) {
clearInterval(this.timerId);
this.isFocusing = false;
return;
}
this.focusMinutes--;
this.focusSeconds = 59;
} else {
this.focusSeconds--;
}
}, 1000);
}
stopFocus(): void {
clearInterval(this.timerId);
this.isFocusing = false;
this.focusMinutes = 25;
this.focusSeconds = 0;
}
关键设计决策:
- 使用
setInterval而非setTimeout递归——代码更简洁,易于管理 timerId存储在类的私有字段中,方便stopFocus()清理- 倒计时结束时自动退出专注模式,触发 UI 切换回正常视图
6.4.2 计时显示格式化
get focusDisplay(): string {
const m = (this.focusMinutes < 10 ? '0' : '') + this.focusMinutes;
const s = (this.focusSeconds < 10 ? '0' : '') + this.focusSeconds;
return m + ':' + s;
}
6.4.3 专注进度环
使用 Progress 组件实现视觉化的进度跟踪:
get focusProgress(): number {
return ((25 * 60 - (this.focusMinutes * 60 + this.focusSeconds)) / (25 * 60)) * 100;
}
Progress({ value: this.focusProgress, total: 100 })
.width(240).height(6).color('#1ABC9C')
.backgroundColor('rgba(26,188,156,0.2)')
.margin({ top: 12 })
6.4.4 沉浸式专注视图
当番茄钟开始后,整个 UI 切换到沉浸式深色视图:
@Builder
FocusingView() {
Column() {
Column() {
Text('🎯 保持专注').fontSize(20).fontColor('#FFF').fontWeight(FontWeight.Bold)
Text(this.focusDisplay).fontSize(64).fontColor('#1ABC9C').fontWeight(FontWeight.Bold)
.margin({ top: 16 })
Progress({ value: this.focusProgress, total: 100 })
.width(240).height(6).color('#1ABC9C')
.backgroundColor('rgba(26,188,156,0.2)')
.margin({ top: 12 })
Text('🔵 ' + this.focusTaskTitle).fontSize(14).fontColor('rgba(255,255,255,0.7)')
.margin({ top: 12 })
Row({ space: 16 }) {
Button('⏹ 结束').width(120).height(44)
.backgroundColor('rgba(231,76,60,0.8)').fontColor('#FFF').borderRadius(22).fontSize(14)
.onClick(() => this.stopFocus())
Button('⏸ 暂停').width(100).height(44)
.backgroundColor('rgba(255,255,255,0.15)').fontColor('#FFF').borderRadius(22).fontSize(14)
}.margin({ top: 20 })
}
.width('88%').padding(30)
.backgroundColor('rgba(255,255,255,0.08)').borderRadius(24)
.alignItems(HorizontalAlign.Center).margin({ top: 40 })
}
.width('100%').alignItems(HorizontalAlign.Center)
}
6.4.5 专注模式预设
提供三种专注/休息模式快速切换:
Row({ space: 8 }) {
this.FocusModeBtn('🔵 专注', '25min', true)
this.FocusModeBtn('🟢 短休息', '5min', false)
this.FocusModeBtn('🟡 长休息', '15min', false)
}.width('100%').margin({ top: 10 })
6.5 任务管理详情页:收件箱 / 项目 / 四象限 / 标签
TaskDetailPage 是 GTD 方法论的完整实现,包含 4 个子 Tab:
6.5.1 收件箱 Tab
GTD 的第一步是「捕获」——把所有想法放进收件箱。收件箱 Tab 展示所有 status === 'inbox' 的任务:
get inboxTasks(): TaskDetail[] {
return ALL_TASKS.filter(t => t.status === 'inbox');
}
收件箱上方还提供了「📷 拍照」和「🎤 语音」入口(预留接口),方便快速捕获。
6.5.2 项目 Tab
按项目分组展示任务,每个项目带进度条:
ForEach(PROJECTS, (p: ProjectGroup) => {
Column() {
Row() {
Text(p.name).fontSize(16).fontColor(p.color).fontWeight(FontWeight.Bold)
Blank()
Text(p.done + '/' + p.count).fontSize(13).fontColor('#888')
}.width('100%')
Progress({ value: p.done, total: p.count }).width('100%').height(6)
.color(p.color).backgroundColor('#EEE').margin({ top: 4 })
// 项目下的任务列表
ForEach(ALL_TASKS.filter(t => t.project === p.name), ...)
}
})
6.5.3 四象限 Tab
通过 getQuadrant() 方法动态计算每个象限的任务:
getQuadrant(urgentImp: boolean, imp: boolean): TaskDetail[] {
return ALL_TASKS.filter(t => {
if (t.status === 'done') return false;
const hasUrgent = t.tags.includes('紧急') || t.priority === '高';
const hasImportant = t.tags.includes('重要') || t.priority === '高' || t.priority === '中';
if (urgentImp && imp) return hasUrgent && hasImportant;
if (urgentImp && !imp) return hasUrgent && !hasImportant;
if (!urgentImp && imp) return !hasUrgent && hasImportant;
return !hasUrgent && !hasImportant;
});
}
四个象限各有独立的颜色:
- 重要·紧急(红色
#E74C3C):立即处理 - 重要·不紧急(橙色
#F39C12):计划处理 - 不重要·紧急(蓝色
#3498DB):委派他人 - 不重要·不紧急(灰色
#95A5A6):考虑放弃
6.5.4 标签 Tab
按标签组织任务,每个标签组显示对应任务列表:
this.TagGroup('🔥 重要', '#E74C3C', ALL_TASKS.filter(t => t.tags.includes('重要')))
this.TagGroup('⚡ 紧急', '#F39C12', ALL_TASKS.filter(t => t.tags.includes('紧急')))
this.TagGroup('🐛 Bug', '#3498DB', ALL_TASKS.filter(t => t.tags.includes('Bug')))
// ...
6.5.5 任务行展开详情
点击任务行右侧的 > 可以展开查看详情,包括描述和子任务列表:
if (this.showDetailId === t.id) {
Column() {
if (t.desc) { Text('📝 ' + t.desc).fontSize(11).fontColor('#888') }
if (t.subtasks.length > 0) {
ForEach(t.subtasks, (st: string) => {
Row() { Text('☐ ' + st).fontSize(11).fontColor('#555') }.width('100%')
}, (st: string) => st)
}
Row({ space: 6 }) {
Button('✏️ 编辑').height(28).fontSize(11).backgroundColor('#EEF0FF').fontColor('#4A6CF7')
Button('✅ 完成').height(28).fontSize(11).backgroundColor('#F0FFF4').fontColor('#22C55E')
Text(t.due).fontSize(10).fontColor('#AAA')
}.width('100%').margin({ top: 6 })
}
.width('100%').padding(8).backgroundColor('#F9FAFB').borderRadius(8).margin({ top: 4 })
}
6.6 专注统计页:Canvas 柱状图 + 高效时段分析
FocusStatsPage 提供三个时间维度的专注数据(今日/本周/本月),核心是 Canvas 自绘的柱状图。
6.6.1 Canvas 柱状图实现
private barCtx: CanvasRenderingContext2D = new CanvasRenderingContext2D();
drawBar(): void {
const ctx = this.barCtx;
const w = 320, h = 160;
ctx.clearRect(0, 0, w, h);
const maxVal = Math.max(...WEEK_DATA.map(d => d.minutes), 1);
const barW = 30, padL = 20, padB = 24;
for (let i = 0; i < WEEK_DATA.length; i++) {
const d = WEEK_DATA[i];
const x = padL + i * 42;
const bh = (d.minutes / maxVal) * (h - padB - 10);
// 绘制柱体
ctx.fillStyle = '#3498DB';
ctx.fillRect(x, h - padB - bh, barW, bh);
// 顶部数值标签
ctx.font = '9px HarmonyOS Sans';
ctx.fillStyle = '#888';
ctx.textAlign = 'center';
ctx.fillText(d.minutes + 'min', x + barW / 2, h - padB - bh - 4);
// 底部星期标签
ctx.fillText(d.label, x + barW / 2, h - 4);
}
}
Canvas 绘制要点:
clearRect清除画布,避免绘制叠加- 柱体高度通过
(当前值 / 最大值) × (总高度 - 留白)计算 - 顶部数值标签与柱体上边缘保持 4px 间距
- 底部星期标签与画布底部保持 4px 间距
6.6.2 总览统计卡片
六张统计卡片覆盖专注的各个维度:
Row({ space: 8 }) {
this.OverviewCard('总专注时长', this.totalMinutes + 'min', '#3498DB')
this.OverviewCard('完成番茄', this.totalSessions + '个', '#1ABC9C')
this.OverviewCard('日均专注', this.avgMinutes + 'min', '#F39C12')
}.width('100%')
Row({ space: 8 }) {
this.OverviewCard('最长连续日', '5天', '#9B59B6')
this.OverviewCard('效率评分', '88分', '#E67E22')
this.OverviewCard('中断次数', '3次', '#E74C3C')
}.width('100%').margin({ top: 6 })
6.6.3 高效时间段分析
通过 HourBar 组件展示 8 个时间段的热度:
Row() {
this.HourBar('6-8', 0.3)
this.HourBar('8-10', 0.8)
this.HourBar('10-12', 0.6)
this.HourBar('12-14', 0.2)
this.HourBar('14-16', 0.7)
this.HourBar('16-18', 0.4)
this.HourBar('18-20', 0.3)
this.HourBar('20-22', 0.5)
}.width('100%').margin({ top: 6 })
Text('💡 你的高效时段是 8:00-10:00 和 14:00-16:00')
.fontSize(11).fontColor('#1ABC9C').width('100%').margin({ top: 6 })
每个 HourBar 是一个从底部向上的柱体,高度由 pct 参数控制(0-1 范围),让用户直观地看到自己一天中哪些时间段最专注。
6.7 周报复盘页:Canvas 饼图 + 完成率 + AI 建议
WeeklyReportPage 是时间管理闭环的最后一环——复盘。包含周评分、时间分布饼图、完成率和 AI 建议。
6.7.1 周评分
综合专注时长、任务完成率和习惯打卡率三个维度给出周评分(85 分),并与上一周对比:
Row() { Text('专注时长').fontSize(11).fontColor('#555'); Blank(); Text('28h ↑12%').fontSize(11).fontColor('#22C55E') }
Row() { Text('任务完成').fontSize(11).fontColor('#555'); Blank(); Text('75% ↑5%').fontSize(11).fontColor('#22C55E') }
Row() { Text('习惯打卡').fontSize(11).fontColor('#555'); Blank(); Text('68% ↓3%').fontSize(11).fontColor('#E74C3C') }
绿色箭头表示提升,红色箭头表示下降,用户一眼就能看到哪些方面做得好、哪些需要改进。
6.7.2 Canvas 甜甜圈饼图
与 WealthWise 的投资饼图类似,但展示的是时间分类分布:
drawPie(): void {
const ctx = this.pieCtx;
const cx = 80, cy = 80, r = 55;
ctx.clearRect(0, 0, 160, 160);
let startAngle = -Math.PI / 2;
for (const d of TIME_DIST) {
const angle = (d.pct / 100) * 2 * Math.PI;
ctx.beginPath();
ctx.moveTo(cx, cy);
ctx.arc(cx, cy, r, startAngle, startAngle + angle);
ctx.closePath();
ctx.fillStyle = d.color;
ctx.fill();
startAngle += angle;
}
// 中心甜甜圈
ctx.beginPath();
ctx.arc(cx, cy, 28, 0, 2 * Math.PI);
ctx.fillStyle = '#FFF';
ctx.fill();
ctx.font = '12px HarmonyOS Sans';
ctx.fillStyle = '#333';
ctx.textAlign = 'center';
ctx.fillText('本周', cx, cy - 4);
ctx.fillText('37h', cx, cy + 10);
}
6.7.3 完成率统计
三个维度的完成率进度条:
ForEach(COMPLETIONS, (c: CompletionItem) => {
Row() {
Text(c.label).fontSize(13).fontColor('#333').width(100)
Progress({ value: c.done, total: c.total }).width(0).layoutWeight(1).height(8)
.color(c.color).backgroundColor('#EEE')
Text(c.done + '/' + c.total).fontSize(12).fontColor('#888').width(48).textAlign(TextAlign.End)
}.width('100%').padding({ top: 4, bottom: 4 })
}, (c: CompletionItem) => c.label)
6.7.4 AI 复盘建议
四条 AI 生成的复盘建议,涵盖正面反馈、问题预警和优化建议:
Column({ space: 6 }) {
Row() { Text('✅ '); Text('本周专注时长相比上周增加 12%,继续保持').fontSize(12).fontColor('#555') }
Row() { Text('⚠️ '); Text('习惯打卡率下降 3%,建议设定每日固定提醒').fontSize(12).fontColor('#F39C12') }
Row() { Text('💡 '); Text('会议时间占比 12%,可考虑缩短为 45min').fontSize(12).fontColor('#3498DB') }
Row() { Text('🎯 '); Text('下周一建议安排 1h 深度阅读,优先级设为高').fontSize(12).fontColor('#1ABC9C') }
}.width('100%').padding(10).backgroundColor('#F9FAFB').borderRadius(10).margin({ top: 8 })
6.7.5 下周规划
底部的「下周关键事项」为下周的规划提供了起点:
Column({ space: 4 }) {
Row() { Text('📌').fontSize(12); Text('完成Q3产品路线图终稿(高优先级)').fontSize(12).fontColor('#555') }
Row() { Text('📌').fontSize(12); Text('准备周五 VP 演示 PPT').fontSize(12).fontColor('#555') }
Row() { Text('📌').fontSize(12); Text('跑步至少 4 次,恢复习惯打卡').fontSize(12).fontColor('#555') }
}.width('100%').padding(10).backgroundColor('#FFF8E1').borderRadius(10).margin({ top: 8 })
7. ArkTS 语法陷阱与踩坑记录
7.1 不能在 UI 构建区域使用 const/let
问题:在 @Builder 或 build() 方法的 UI 区域中,不能使用 const、let、var 声明。
// ❌ 错误
ForEach(WEEKDAYS, (wd: string, idx: number) => {
const dayNum = this.selectedDate - (new Date().getDay() - idx); // 编译错误
Column() { /* ... */ }
}, (wd: string) => wd)
// ✅ 正确:将计算逻辑抽取到 getter
get weekDayNumbers(): number[] {
const today = new Date().getDay();
return WEEKDAYS.map((_, idx) => this.selectedDate - (today - idx));
}
ForEach(this.weekDayNumbers, (dayNum: number, idx: number) => {
Column() { /* ... */ }
}, (d: number) => d.toString())
7.2 @Builder 函数中的 this 引用
问题:在 @Builder 函数中引用 this 需要特别注意——如果 Builder 函数是类的成员方法(使用 this.BuilderName() 调用),this 是合法的。但如果 Builder 是独立的 function,则不能使用 this。
在时光织中,所有 @Builder 都是类的成员方法(通过 this.XXX() 调用),因此使用 this.tabIdx、this.selectedDate 等状态是合法的。
7.3 Canvas 绘制时机
问题:Canvas 绘制必须在 onReady 回调中执行,不能在其他生命周期方法中调用。
// ❌ 错误
aboutToAppear(): void {
this.drawPie(); // Canvas 可能还没准备好
}
// ✅ 正确
Canvas(this.pieCtx)
.width(160).height(160)
.onReady(() => this.drawPie())
7.4 Progress 组件的动态颜色
问题:Progress 组件的 color 属性虽然接受动态值,但需要在条件渲染场景中注意:
// ❌ 错误用法(假设在 ForEach 中)
Progress({ value: cat.spent, total: cat.budget })
.color(dynamicCondition ? '#E74C3C' : '#3498DB') // 可能不生效
// ✅ 正确:在 Progress 外部计算颜色
const progressColor = cat.spent > cat.budget * 0.9 ? '#E74C3C' :
cat.spent > cat.budget * 0.7 ? '#F39C12' : cat.color;
Progress({ value: cat.spent, total: cat.budget })
.color(progressColor)
虽然 ArkTS 支持表达式链式调用,但建议将复杂条件计算提前赋值给常量。
7.5 setInterval 与组件生命周期
问题:使用 setInterval 实现的番茄钟计时器,在组件销毁时需要清理定时器,否则可能导致内存泄漏和奇怪的 bug。
startFocus(): void {
this.timerId = setInterval(() => {
// 倒计时逻辑
}, 1000);
}
stopFocus(): void {
clearInterval(this.timerId); // 必须清理
this.isFocusing = false;
}
虽然 ArkTS 当前不支持标准的 aboutToDisappear 生命周期清理,但在 stopFocus() 中显式清理是最安全的方式。
8. 性能优化与最佳实践
8.1 ForEach 的 key 生成策略
// 数字 ID 作为 key
ForEach(items, (item) => { ... }, (item: ItemType) => item.id.toString())
// 组合 key(当没有单字段唯一标识时)
ForEach(items, (item) => { ... }, (item: ItemType) => item.time + item.title)
唯一 key 帮助 ArkTS 框架准确追踪列表元素的变化,避免不必要的重渲染。
8.2 状态管理最小化
// ✅ 静态数据用 const(不触发重渲染)
const TIME_BLOCKS: TimeBlock[] = [ ... ];
const TASKS: TaskItem[] = [ ... ];
// ✅ 只在需要驱动 UI 更新时用 @State
@State tabIdx: number = 0;
@State isFocusing: boolean = false;
@State focusMinutes: number = 25;
@State focusSeconds: number = 0;
@State selectedDate: number = new Date().getDate();
8.3 计算属性(getter)替代方法调用
使用 getter 来计算派生状态,而不是在 build 方法中调用函数:
// ✅ 正确:getter 缓存计算结果
get focusDisplay(): string { ... }
get focusProgress(): number { ... }
get weekDayNumbers(): number[] { ... }
// ❌ 错误:每次渲染都重新计算
build() {
Text(this.calcFocusDisplay()) // 每次渲染都调用方法
}
虽然 ArkTS 的 getter 并非严格「缓存」(每次访问都会重新计算),但相比在 build 方法中混合计算逻辑,getter 使代码更清晰、更易于维护。
8.4 布局性能
- 使用
layoutWeight(1)替代硬编码百分比,让布局计算交给框架 - 使用
Blank()组件填充剩余空间,替代空白的 Text 组件 - 列表使用
Scroll包裹,确保内容超出屏幕高度时可滚动 - 使用
width('94%')+padding保持卡片间一致性间距 - Flex 组件替代 Row + flexWrap(In ArkTS,
flexWrap属性在Row上不存在,必须使用Flex组件)
9. 未来 Roadmap
9.1 短期计划(1-2 个月)
- 番茄钟完整功能:暂停/继续、白噪音、专注统计持久化
- 任务 CRUD:添加、编辑、删除、状态流转(收件箱→待办→完成)
- 日历订阅:导入系统日历和第三方日历(ICS 格式)
- 数据持久化:RelationalStore 替换模拟数据
9.2 中期计划(3-6 个月)
- 拖拽调整时间块:在时间线上拖拽调整时间块长度和位置
- AI 智能排程:输入任务列表,AI 自动分配到合适的时间块
- 习惯追踪统计:连续天数、完成率趋势、习惯分类管理
- 主屏幕 Widget:显示今日时间线和专注状态
- 通知提醒:@ohos.notification + 前台常驻服务
9.3 长期愿景(6-12 个月)
- 复盘对话:每周日 AI 和你对话式复盘,给出下周优化建议
- 时间借贷:某任务超时从后续缓冲块「借」时间
- 勿扰模式:专注期间自动开启系统勿扰 + 通知折叠
- 华为手表协同:在手表端快捷查看时间线和专注控制
- 团队协作:共享日历、分配任务、团队专注排行榜
10. 总结
10.1 项目数据统计
| 指标 | 数值 |
|---|---|
| 页面数量 | 4 个 |
| 代码行数 | ~1,260 行 (Ets) |
| Interface 定义 | 7 个 |
| Canvas 自绘图表 | 2 个(饼图、柱状图) |
| 模拟数据量 | 6 类、40+ 条记录 |
| 编译耗时 | ~300ms(增量) |
| 主要编译错误修复 | 3 类(UI 区域限制、重复代码、Canvas 时机) |
10.2 与 WealthWise 的架构对比
两个 App 虽同属一个项目,但在架构设计和用户体验上有显著的差异。通过对比可以更清晰地理解 ArkTS 在不同场景下的应用模式:
| 维度 | WealthWise(财智管家) | TimeWeave(时光织) |
|---|---|---|
| 主色调 | 财富金 #F59E0B + 深海蓝 #0F172A |
沉稳蓝 #2C3E50 + 活力青 #1ABC9C |
| 品牌感觉 | 稳重、信任、增值 | 冷静、专注、高效 |
| Tab 结构 | 4Tab(资产/投资/预算/规划) | 4Tab(日程/任务/专注/统计) |
| 特色交互 | 金额隐私保护(👁️切换) | 沉浸式专注视图(覆盖全局) |
| 核心算法 | 复利计算 FV = PMT × ((1+r)^n - 1)/r |
番茄钟倒计时 setInterval |
| Canvas 图表 | 投资配置甜甜圈饼图 | 时间分布甜甜圈饼图 |
| 数据过滤 | 交易记录按日期分组 + 类型筛选 | 任务按 GTD 四象限动态分类 |
| 页面数量 | 9 个页面 | 4 个页面 |
| 模拟数据量 | 30+ 条记录 | 40+ 条记录 |
| Header 背景 | 深海蓝渐变 | 沉稳蓝纯色 |
| 卡片圆角 | 14px | 14px(一致) |
| 共用组件模式 | @Builder 成员方法 |
@Builder 成员方法 |
架构决策差异分析:
1. 状态管理的复杂度
WealthWise 的核心状态是显隐(showAmount)和切换(tabIdx、selectedAccount),状态之间相对独立。而 TimeWeave 的状态之间存在复杂联动——isFocusing 为 true 时会覆盖所有 Tab 内容,而 focusMinutes 和 focusSeconds 每秒都在变化。这意味着 TimeWeave 的 UI 更新频率远高于 WealthWise。
2. 数据流向模式
WealthWise 的数据流向是「读取 → 显示」,用户交互主要是页面跳转和条件显隐,不涉及数据变更。TimeWeave 的数据流向引入了「用户操作 → 状态变更 → UI 重绘」的闭环——点击任务勾选框修改 t.done,点击专注按钮启动定时器,这些都直接驱动 UI 变化。
3. 页面间耦合度
WealthWise 的页面间耦合度较低——AssetDetail 不依赖 InvestmentDetail 的状态,每个页面独立运作。TimeWeave 的页面间存在一定的逻辑关联——MainPage 的专注统计数据与 FocusStatsPage 共享同一份数据源(虽然当前版本使用独立的模拟数据)。
4. 沉浸式体验设计
TimeWeave 的沉浸式专注视图是一个独特的设计决策。当用户开始番茄钟后,所有 Tab 内容和导航栏都被隐藏,取而代之的是一张全屏的深色专注卡片。这个设计模拟了「进入心流状态」的心理体验——外界干扰被屏蔽,只剩下计时器和当前任务。这种设计在 ArkTS 中通过一个简单的 if/else 状态判断即可实现,展示了声明式 UI 在状态驱动视图切换方面的天然优势。
10.3 从模拟数据到真实数据的演进路径
两个 App 当前都使用静态模拟数据。在实际产品化过程中,需要逐步替换为真实数据源。以下是推荐的演进路径:
第一阶段:LocalStorage + AppStorage
利用 HarmonyOS 的 LocalStorage 和 AppStorage 实现跨页面的状态共享。用户的偏好设置(如 showAmount、faceIdEnabled)可以用 AppStorage 持久化。
第二阶段:RelationalStore 数据库
使用 @ohos.data.relationalStore 创建本地关系型数据库,建立 task、time_block、focus_session、habit_record 等表。模拟数据作为数据库的初始种子数据。
第三阶段:端到端加密云同步
通过华为云或 WebDAV 实现多设备数据同步。同步前对敏感数据进行端到端加密,确保数据安全。
第四阶段:AI 能力接入
接入华为 AI 能力(ML Kit、预测服务),实现智能排程、语音添加任务、图片识别日程等功能。
10.4 核心收获
通过「时光织」TimeWeave 的开发,我们进一步加深了对 HarmonyOS Next (API 24) ArkTS 开发范式的理解,并总结了以下关键经验:
-
状态驱动的 UI 架构:通过
@State驱动的if/else条件渲染,实现了从正常视图到沉浸式专注视图的无缝切换,这是 ArkTS 声明式 UI 相较于传统命令式 UI 的核心优势。只需修改一个布尔值isFocusing,整个页面的布局和样式就随之改变,无需手动操作 DOM。 -
Canvas 自绘图表复用:将 WealthWise 中的 Canvas 饼图绘制经验成功迁移到 TimeWeave 的时间分布图中。两个饼图的绘制算法完全一致,只有数据源和中心文字不同。这验证了 Canvas 组件的高可复用性,为未来建立统一的图表组件库奠定了基础。
-
数据层与 UI 层分离:通过在 getter 中预处理数据(如
weekDayNumbers、focusDisplay、focusProgress),保持build()方法的纯净和可读性。这是一个在 ArkTS 中非常重要的编程范式——由于在 UI 构建区域不允许使用const/let,所有的数据处理逻辑都必须通过 getter 或方法前置。 -
setInterval 计时器管理:番茄钟的实现展示了在 ArkTS 中管理定时器的标准模式——开始(
startFocus)、更新(setInterval回调)、清理(stopFocus中的clearInterval)。这是 App 中为数不多的涉及「实时更新」的场景,为未来开发计时器、秒表、动画等实时功能提供了参考。 -
GTD 方法论的数字化实践:将 GTD 的「收集→整理→组织→执行→复盘」五个步骤转化为数字化的 Tab 和页面。收件箱对应「收集」,项目分组和标签对应「整理→组织」,任务列表和番茄钟对应「执行」,周报复盘对应「复盘」。这让 GTD 不再是纸上谈兵,而是真正可执行的时间管理系统。
-
错误恢复的经验教训:在开发过程中,Index.ets 文件意外被清空(0 字节),这是一个严重的问题。通过及时创建新的导航首页并重建文件内容,成功恢复了应用入口。这一经历提醒我们:在编辑文件时要格外小心
edit_file操作的 old_string 唯一性,以及在进行批量文件操作时做好备份。
更多推荐



所有评论(0)