HarmonyOS ArkTS 实战:实现一个待办清单与日程管理应用

HarmonyOS ArkTS 实战:从零实现待办清单与日程管理应用

目录

  • 一、项目背景与效果预览
  • 二、技术栈与开发环境
  • 三、需求分析与功能架构
  • 四、数据结构与服务层设计
  • 五、核心功能实现(完整代码)
    • [5.1 页面状态与数据加载](#5.1 页面状态与数据加载)
    • [5.2 任务管理(添加/编辑/删除)](#5.2 任务管理(添加/编辑/删除))
    • [5.3 四象限分类与优先级标记](#5.3 四象限分类与优先级标记)
    • [5.4 完成状态切换与进度统计](#5.4 完成状态切换与进度统计)
    • [5.5 子任务管理(清单嵌套)](#5.5 子任务管理(清单嵌套))
    • [5.6 标签分类与搜索](#5.6 标签分类与搜索)
    • [5.7 日历视图(模拟)](#5.7 日历视图(模拟))
    • [5.8 重复任务设置](#5.8 重复任务设置)
    • [5.9 专注模式(番茄钟)](#5.9 专注模式(番茄钟))
    • [5.10 数据备份与恢复(导入导出)](#5.10 数据备份与恢复(导入导出))
    • [5.11 数据持久化(Preferences)](#5.11 数据持久化(Preferences))
  • [六、UI 界面设计与实现(完整组件)](#六、UI 界面设计与实现(完整组件))
    • [6.1 顶部标题与今日进度](#6.1 顶部标题与今日进度)
    • [6.2 任务卡片(含优先级颜色、象限标签、子任务展开)](#6.2 任务卡片(含优先级颜色、象限标签、子任务展开))
    • [6.3 添加任务弹窗(含表单)](#6.3 添加任务弹窗(含表单))
    • [6.4 四象限视图(Grid布局)](#6.4 四象限视图(Grid布局))
    • [6.5 日历视图(日期网格)](#6.5 日历视图(日期网格))
    • [6.6 专注模式计时器](#6.6 专注模式计时器)
  • 七、完整主页面代码(Index.ets)及子页面
  • 八、运行与调试
  • 九、项目总结与扩展思路

一、项目背景与效果预览

1.1 痛点场景

大学生活中,课程作业、社团事务、考试复习、生活琐事交织,时间管理成为难题。本应用基于四象限法则(重要紧急/重要不紧急/紧急不重要/不重要不紧急),结合优先级、子任务、标签、日历、番茄钟等工具,打造一站式的效率管理工具,帮助用户告别拖延、提升执行力。

1.2 运行效果(模拟器预览)

  • 主界面(清单):顶部标题 + 今日完成率进度环;中部为任务列表(卡片式,含完成勾选、标题、截止日期、优先级颜色、象限标签);底部添加按钮。
  • 底部Tabs:"清单"、"四象限"、"日历"、"统计"。
  • 四象限:2×2 Grid,每个象限显示对应任务,直观查看分布。
  • 日历:按月份显示日期网格,有任务的日子打点标记,点击可查看当日任务。
  • 统计:完成率饼图、各优先级数量、每日完成趋势。
  • 交互反馈:添加任务弹窗(含标题、描述、优先级、象限、截止日期、标签、重复选项、子任务预填)、任务左滑删除、子任务折叠/展开、番茄钟倒计时。

主题色采用紫色(#7C3AED → #A78BFA),象征效率与创造力。


二、技术栈与开发环境

技术项 说明
开发语言 ArkTS
UI 框架 ArkUI 声明式开发
状态管理 @State / @Provide / @Consume
布局方式 List + Grid + Tabs + Stack
数据持久化 @ohos.data.preferences
图表组件 @ohos.arkui.advanced.Chart
弹窗/提示 @ohos.prompt / @ohos.dialog
路由管理 @ohos.router
定时器 setInterval / clearInterval
开发工具 DevEco Studio 5.0+
SDK 版本 API 24 及以上

三、需求分析与功能架构

3.1 核心功能清单

  1. 任务管理:添加(标题、描述、优先级、象限、截止日期、标签、重复类型、子任务)、编辑、删除(左滑)。
  2. 四象限分类:重要紧急/重要不紧急/紧急不重要/不重要不紧急,独立视图展示。
  3. 优先级标记:高(红)、中(黄)、低(绿),颜色视觉区分。
  4. 完成状态:勾选切换完成,完成率自动统计(今日总完成率)。
  5. 子任务清单:每个任务可包含多个子任务,独立勾选,主任务完成进度受子任务影响。
  6. 标签分类:自由添加标签,支持按标签筛选。
  7. 搜索:按标题/描述/标签搜索任务。
  8. 日历视图:按月显示,有任务的日期带标记,点击查看当日任务列表。
  9. 重复任务:支持每日/每周/每月重复,到期自动生成新任务(模拟)。
  10. 专注模式(番茄钟):设定25分钟倒计时,开始后聚焦当前任务。
  11. 数据备份:导出为JSON,导入恢复。

3.2 数据流

复制代码
添加 → 存储 → 列表更新 → 统计重新计算 → 图表刷新

四、数据结构与服务层设计

4.1 数据模型(完整定义)

typescript 复制代码
// model/Todo.ets
export interface SubTask {
  id: number;
  title: string;
  isCompleted: boolean;
}

export interface Todo {
  id: number;
  title: string;
  description: string;
  priority: 'high' | 'medium' | 'low';
  quadrant: 1 | 2 | 3 | 4;   // 1:重要紧急, 2:重要不紧急, 3:紧急不重要, 4:不重要不紧急
  dueDate: string;           // "YYYY-MM-DD"
  isCompleted: boolean;
  tags: string[];
  createTime: number;        // timestamp
  subtasks: SubTask[];
  repeatType?: 'none' | 'daily' | 'weekly' | 'monthly';
  repeatEndDate?: string;    // 重复截止日期
  completedDate?: string;    // 完成日期(用于统计)
}

// 用于统计的辅助类型
export interface DailyStats {
  date: string;
  completed: number;
  total: number;
}

4.2 服务层(Service)

沿用 BaseService,实现 TodoService

typescript 复制代码
// service/BaseService.ets(与前文相同,略)
// service/TodoService.ets
import { BaseService } from './BaseService';
import { Todo } from '../model/Todo';

class TodoService extends BaseService<Todo> {
  constructor() { super('TodoPrefs', 'todos'); }

  async fetch(): Promise<Todo[]> {
    const data = await this.loadData();
    if (data.length === 0) {
      const mock = this.getMockData();
      await this.saveData(mock);
      return mock;
    }
    return data;
  }

  async add(item: Todo): Promise<Todo[]> {
    const list = await this.loadData();
    list.unshift(item);
    await this.saveData(list);
    return list;
  }

  async update(id: number, newItem: Todo): Promise<Todo[]> {
    const list = await this.loadData();
    const idx = list.findIndex(t => t.id === id);
    if (idx !== -1) {
      list[idx] = newItem;
      await this.saveData(list);
    }
    return list;
  }

  async delete(id: number): Promise<Todo[]> {
    const list = await this.loadData();
    const filtered = list.filter(t => t.id !== id);
    await this.saveData(filtered);
    return filtered;
  }

  // 导出所有数据(备份)
  async exportAll(): Promise<string> {
    const list = await this.loadData();
    return JSON.stringify(list);
  }

  // 导入数据(恢复)
  async importAll(json: string): Promise<Todo[]> {
    try {
      const list = JSON.parse(json) as Todo[];
      await this.saveData(list);
      return list;
    } catch (e) {
      throw new Error('导入数据格式错误');
    }
  }

  private getMockData(): Todo[] {
    const now = Date.now();
    const today = new Date().toISOString().slice(0,10);
    const tomorrow = new Date(Date.now() + 86400000).toISOString().slice(0,10);
    return [
      {
        id: 1,
        title: '完成项目答辩PPT',
        description: '准备国赛答辩材料',
        priority: 'high',
        quadrant: 1,
        dueDate: tomorrow,
        isCompleted: false,
        tags: ['比赛', '重要'],
        createTime: now - 86400000,
        subtasks: [
          { id: 101, title: '收集数据图表', isCompleted: false },
          { id: 102, title: '撰写讲稿', isCompleted: false },
        ],
        repeatType: 'none',
        repeatEndDate: undefined,
        completedDate: undefined
      },
      {
        id: 2,
        title: '复习期末考试',
        description: '操作系统复习',
        priority: 'medium',
        quadrant: 2,
        dueDate: '2026-07-28',
        isCompleted: false,
        tags: ['学习'],
        createTime: now - 172800000,
        subtasks: [],
        repeatType: 'daily',
        repeatEndDate: '2026-08-01',
        completedDate: undefined
      },
      {
        id: 3,
        title: '取快递',
        description: '菜鸟驿站',
        priority: 'low',
        quadrant: 4,
        dueDate: today,
        isCompleted: true,
        tags: ['生活'],
        createTime: now - 259200000,
        subtasks: [],
        repeatType: 'none',
        repeatEndDate: undefined,
        completedDate: today
      },
    ];
  }
}
export const todoService = new TodoService();

五、核心功能实现(完整代码)

5.1 页面状态与数据加载(主页面 Index.ets)

使用 Tabs 实现清单、四象限、日历、统计四个页面。主页面为清单列表。

typescript 复制代码
// pages/Index.ets
import { Todo, SubTask } from '../model/Todo';
import { todoService } from '../service/TodoService';
import prompt from '@ohos.prompt';
import router from '@ohos.router';
import { Chart, ChartType } from '@ohos.arkui.advanced';

@Entry
@Component
struct Index {
  @State todos: Todo[] = [];
  @State currentTab: number = 0;
  @State isLoading: boolean = true;
  @State searchKeyword: string = '';
  @State filterTag: string = '全部';

  // 添加任务弹窗
  @State isAddDialogVisible: boolean = false;
  @State newTitle: string = '';
  @State newDesc: string = '';
  @State newPriority: string = 'medium';
  @State newQuadrant: number = 2;
  @State newDueDate: string = '';
  @State newTags: string = '';
  @State newRepeat: string = 'none';
  @State newSubtasks: string = ''; // 子任务用换行分隔

  // 编辑状态
  @State editingTodoId: number = -1;

  // 统计
  @State completionRate: number = 0;
  @State totalTasks: number = 0;
  @State completedTasks: number = 0;

  // 专注模式
  @State focusTodoId: number = -1;
  @State focusMinutes: number = 25;
  @State focusSeconds: number = 0;
  @State isFocusing: boolean = false;
  private focusTimer: number = -1;

  aboutToAppear() {
    this.loadData();
  }

  async loadData() {
    this.isLoading = true;
    try {
      this.todos = await todoService.fetch();
      this.calcStats();
      // 处理重复任务(每次加载时检查)
      this.processRepeats();
    } catch (e) {
      prompt.showToast({ message: '加载失败' });
    } finally {
      this.isLoading = false;
    }
  }

  // 计算统计
  private calcStats() {
    this.totalTasks = this.todos.length;
    this.completedTasks = this.todos.filter(t => t.isCompleted).length;
    this.completionRate = this.totalTasks ? Math.round((this.completedTasks / this.totalTasks) * 100) : 0;
  }

  // 处理重复任务:若今日已过截止日期且未完成,自动生成新任务(简单实现)
  private processRepeats() {
    const today = new Date().toISOString().slice(0,10);
    let needUpdate = false;
    this.todos = this.todos.map(t => {
      if (t.repeatType !== 'none' && t.repeatEndDate && t.dueDate < today && t.repeatEndDate >= today) {
        // 复制任务,更新截止日期
        const newDue = this.getNextDueDate(t.dueDate, t.repeatType as any);
        if (newDue && newDue <= t.repeatEndDate!) {
          const newTodo = { ...t, id: Date.now() + Math.random(), dueDate: newDue, isCompleted: false, completedDate: undefined };
          // 需要添加新任务,但不能在map中直接push,用标识
          needUpdate = true;
          // 这里采用简单方法:返回原任务(不再处理),在外部添加
          return t;
        }
      }
      return t;
    });
    // 实际实现中,可在加载后单独添加新任务,此处简化
  }

  private getNextDueDate(current: string, repeat: 'daily'|'weekly'|'monthly'): string | null {
    const d = new Date(current);
    if (repeat === 'daily') d.setDate(d.getDate() + 1);
    else if (repeat === 'weekly') d.setDate(d.getDate() + 7);
    else if (repeat === 'monthly') d.setMonth(d.getMonth() + 1);
    else return null;
    return d.toISOString().slice(0,10);
  }

  // 获取过滤后的任务
  private getFilteredTodos(): Todo[] {
    let list = this.todos;
    if (this.searchKeyword.trim()) {
      const kw = this.searchKeyword.trim().toLowerCase();
      list = list.filter(t =>
        t.title.toLowerCase().includes(kw) ||
        t.description.toLowerCase().includes(kw) ||
        t.tags.some(tag => tag.toLowerCase().includes(kw))
      );
    }
    if (this.filterTag !== '全部') {
      list = list.filter(t => t.tags.includes(this.filterTag));
    }
    return list;
  }

  // 获取所有标签
  private getAllTags(): string[] {
    const tagSet = new Set<string>();
    this.todos.forEach(t => t.tags.forEach(tag => tagSet.add(tag)));
    return ['全部', ...Array.from(tagSet)];
  }

  // ... 后续方法
}

5.2 任务管理(添加/编辑/删除)

添加任务弹窗(完整表单),编辑复用。

typescript 复制代码
private openAddDialog() {
  this.newTitle = '';
  this.newDesc = '';
  this.newPriority = 'medium';
  this.newQuadrant = 2;
  this.newDueDate = new Date().toISOString().slice(0,10);
  this.newTags = '';
  this.newRepeat = 'none';
  this.newSubtasks = '';
  this.editingTodoId = -1;
  this.isAddDialogVisible = true;
}

private openEditDialog(todo: Todo) {
  this.newTitle = todo.title;
  this.newDesc = todo.description;
  this.newPriority = todo.priority;
  this.newQuadrant = todo.quadrant;
  this.newDueDate = todo.dueDate;
  this.newTags = todo.tags.join(',');
  this.newRepeat = todo.repeatType || 'none';
  this.newSubtasks = todo.subtasks.map(s => s.title).join('\n');
  this.editingTodoId = todo.id;
  this.isAddDialogVisible = true;
}

private async saveTodo() {
  if (!this.newTitle.trim()) {
    prompt.showToast({ message: '请输入标题' });
    return;
  }
  const tags = this.newTags.split(',').map(s => s.trim()).filter(s => s);
  const subtasks: SubTask[] = this.newSubtasks.split('\n')
    .filter(s => s.trim())
    .map((s, idx) => ({ id: Date.now() + idx, title: s.trim(), isCompleted: false }));

  const todo: Todo = {
    id: this.editingTodoId > 0 ? this.editingTodoId : Date.now(),
    title: this.newTitle.trim(),
    description: this.newDesc.trim(),
    priority: this.newPriority as any,
    quadrant: this.newQuadrant as any,
    dueDate: this.newDueDate || new Date().toISOString().slice(0,10),
    isCompleted: false,
    tags: tags,
    createTime: Date.now(),
    subtasks: subtasks,
    repeatType: this.newRepeat as any,
    repeatEndDate: this.newRepeat !== 'none' ? '2026-12-31' : undefined,
    completedDate: undefined
  };

  try {
    if (this.editingTodoId > 0) {
      // 编辑,保留原完成状态和完成日期
      const old = this.todos.find(t => t.id === this.editingTodoId);
      if (old) {
        todo.isCompleted = old.isCompleted;
        todo.completedDate = old.completedDate;
      }
      this.todos = await todoService.update(this.editingTodoId, todo);
    } else {
      this.todos = await todoService.add(todo);
    }
    this.calcStats();
    prompt.showToast({ message: '保存成功' });
    this.isAddDialogVisible = false;
  } catch (e) {
    prompt.showToast({ message: '保存失败' });
  }
}

private async deleteTodo(id: number) {
  prompt.showDialog({
    title: '删除任务',
    message: '确定删除吗?',
    buttons: [{ text: '取消' }, { text: '删除', color: '#EF4444' }]
  }).then(async (res) => {
    if (res.index === 1) {
      try {
        this.todos = await todoService.delete(id);
        this.calcStats();
        prompt.showToast({ message: '已删除' });
      } catch (e) { /* */ }
    }
  });
}

5.3 四象限分类与优先级标记

在"四象限"Tab中,使用 2×2 Grid,每个象限显示对应任务列表。

typescript 复制代码
// 获取象限任务
private getQuadrantTodos(q: number): Todo[] {
  return this.todos.filter(t => t.quadrant === q && !t.isCompleted);
}

// 在 TabContent 中
Grid() {
  ForEach([1,2,3,4], (q: number) => {
    GridItem() {
      Column() {
        Text(
          q === 1 ? '🔥 重要紧急' :
          q === 2 ? '📌 重要不紧急' :
          q === 3 ? '⚡ 紧急不重要' : '📎 不重要不紧急'
        ).fontSize(14).fontWeight(FontWeight.Bold).fontColor('#1F2937').margin({ bottom: 4 })
        ForEach(this.getQuadrantTodos(q), (t: Todo) => {
          Row() {
            Text(t.title).fontSize(12).maxLines(1)
            Text(t.priority === 'high' ? '🔴' : t.priority === 'medium' ? '🟡' : '🟢').fontSize(10)
          }
          .width('100%')
          .padding(6)
          .backgroundColor('#FFF')
          .borderRadius(4)
          .margin({ bottom: 4 })
          .onClick(() => this.openEditDialog(t))
        })
        if (this.getQuadrantTodos(q).length === 0) {
          Text('空').fontSize(11).fontColor('#999').margin(8)
        }
      }
      .padding(8)
      .backgroundColor('#F3F4F6')
      .borderRadius(8)
      .width('100%')
      .height('100%')
    }
  })
}
.columnsTemplate('1fr 1fr')
.rowsTemplate('1fr 1fr')
.width('100%')
.height('100%')
.padding(10)

5.4 完成状态切换与进度统计

任务卡片中点击勾选框切换完成状态,并实时更新统计。

typescript 复制代码
private async toggleComplete(todoId: number) {
  const todo = this.todos.find(t => t.id === todoId);
  if (!todo) return;
  const updated = {
    ...todo,
    isCompleted: !todo.isCompleted,
    completedDate: !todo.isCompleted ? new Date().toISOString().slice(0,10) : undefined
  };
  try {
    this.todos = await todoService.update(todoId, updated);
    this.calcStats();
  } catch (e) { /* */ }
}

进度显示在顶部(环形进度条):

typescript 复制代码
// 使用 Progress 组件
Progress({ value: this.completionRate, total: 100, type: ProgressType.Ring })
  .width(48).height(48)

5.5 子任务管理(清单嵌套)

在任务卡片中,点击展开子任务列表,每个子任务可单独勾选。

typescript 复制代码
@State expandedTaskId: number = -1;

// 在任务卡片中
if (todo.subtasks.length > 0) {
  Text(`子任务 ${todo.subtasks.filter(s => s.isCompleted).length}/${todo.subtasks.length}`)
    .fontSize(12).fontColor('#999')
    .onClick(() => {
      this.expandedTaskId = this.expandedTaskId === todo.id ? -1 : todo.id;
    })
}
if (this.expandedTaskId === todo.id) {
  Column() {
    ForEach(todo.subtasks, (sub: SubTask) => {
      Row() {
        Text(sub.isCompleted ? '✅' : '⬜')
          .onClick(() => this.toggleSubTask(todo.id, sub.id))
        Text(sub.title).fontSize(13).decoration({ type: sub.isCompleted ? TextDecorationType.LineThrough : TextDecorationType.None })
      }
      .padding(4)
    })
  }
  .margin({ top: 6 })
}

切换子任务方法:

typescript 复制代码
private async toggleSubTask(todoId: number, subId: number) {
  const todo = this.todos.find(t => t.id === todoId);
  if (!todo) return;
  const updatedSubtasks = todo.subtasks.map(s =>
    s.id === subId ? { ...s, isCompleted: !s.isCompleted } : s
  );
  const updated = { ...todo, subtasks: updatedSubtasks };
  // 如果所有子任务完成,自动完成主任务?可选,这里不自动完成
  try {
    this.todos = await todoService.update(todoId, updated);
    this.calcStats();
  } catch (e) { /* */ }
}

5.6 标签分类与搜索

顶部筛选栏包含标签下拉和搜索框。

typescript 复制代码
// 在 build 中
Row() {
  Select(this.getAllTags().map(t => ({ value: t })))
    .selected(0)
    .onSelect((idx) => {
      const tag = this.getAllTags()[idx];
      this.filterTag = tag;
    })
    .width(120)
    .height(32)
  Blank()
  TextInput({ placeholder: '搜索任务', text: this.searchKeyword })
    .onChange(v => this.searchKeyword = v)
    .width(150)
    .height(32)
    .backgroundColor('#FFF')
    .borderRadius(16)
    .padding({ left: 10 })
}

5.7 日历视图(模拟)

在"日历"Tab中,显示当前月份的日历网格,有任务的日子标记小点,点击可查看当日任务列表。

typescript 复制代码
@State currentDate: Date = new Date();
@State selectedDate: string = '';

// 获取某天的任务
private getTasksForDate(dateStr: string): Todo[] {
  return this.todos.filter(t => t.dueDate === dateStr);
}

// 构建日历网格(6行7列)
// 此处简化,用一个Grid展示
Grid() {
  // 星期头
  ForEach(['日','一','二','三','四','五','六'], (day) => {
    GridItem() { Text(day).fontSize(12).fontColor('#999') }
  })
  // 日期填充(略,需要计算当月第一天星期几)
  // 使用循环生成42个格子
}

5.8 重复任务设置

在添加弹窗中,选择重复类型(每日/每周/每月),并设置截止日期。在 loadData 时调用 processRepeats 生成新任务(简化版)。

5.9 专注模式(番茄钟)

点击任务卡片上的"专注"按钮,进入专注模式,显示倒计时。

typescript 复制代码
private startFocus(todoId: number) {
  this.focusTodoId = todoId;
  this.focusMinutes = 25;
  this.focusSeconds = 0;
  this.isFocusing = true;
  if (this.focusTimer !== -1) clearInterval(this.focusTimer);
  this.focusTimer = setInterval(() => {
    if (this.focusSeconds === 0) {
      if (this.focusMinutes === 0) {
        clearInterval(this.focusTimer);
        this.focusTimer = -1;
        this.isFocusing = false;
        prompt.showToast({ message: '专注结束!🎉' });
        return;
      }
      this.focusMinutes--;
      this.focusSeconds = 59;
    } else {
      this.focusSeconds--;
    }
  }, 1000);
}

private stopFocus() {
  if (this.focusTimer !== -1) {
    clearInterval(this.focusTimer);
    this.focusTimer = -1;
  }
  this.isFocusing = false;
}

专注模式UI(可在单独页面或覆盖层):

typescript 复制代码
if (this.isFocusing) {
  Column() {
    Text('⏳ 专注中').fontSize(20)
    Text(`${this.focusMinutes}:${this.focusSeconds.toString().padStart(2,'0')}`).fontSize(48)
    Text(this.todos.find(t => t.id === this.focusTodoId)?.title || '').fontSize(14)
    Button('停止').onClick(() => this.stopFocus()).backgroundColor('#EF4444')
  }
  .position({ x: 0, y: 0 })
  .width('100%')
  .height('100%')
  .backgroundColor('rgba(0,0,0,0.8)')
  .justifyContent(FlexAlign.Center)
}

5.10 数据备份与恢复(导入导出)

在设置页(可增加Tab)中添加导出和导入按钮。

typescript 复制代码
private async exportData() {
  try {
    const json = await todoService.exportAll();
    // 通过文件保存或分享,这里用提示显示
    prompt.showDialog({
      title: '导出数据',
      message: json.substring(0, 200) + '...',
      buttons: [{ text: '复制' }]
    }).then(res => {
      if (res.index === 0) {
        // 复制到剪贴板(需权限,此处简化)
        prompt.showToast({ message: '已复制(模拟)' });
      }
    });
  } catch (e) { /* */ }
}

private async importData() {
  // 模拟导入,可弹窗输入JSON
  prompt.showDialog({
    title: '导入数据',
    message: '粘贴JSON数据',
    buttons: [{ text: '取消' }, { text: '导入' }]
  }).then(async (res) => {
    if (res.index === 1) {
      // 实际需输入框,此处简化
      prompt.showToast({ message: '导入成功(模拟)' });
    }
  });
}

5.11 数据持久化(Preferences)

已在 TodoService 中实现,所有操作自动保存。


六、UI 界面设计与实现(完整组件)

6.1 顶部标题与今日进度

typescript 复制代码
@Builder TopBar() {
  Row() {
    Text('📋 待办清单')
      .fontSize(24)
      .fontWeight(FontWeight.Bold)
      .fontColor('#1F2937')
    Blank()
    Progress({ value: this.completionRate, total: 100, type: ProgressType.Ring })
      .width(40).height(40)
    Text(`${this.completionRate}%`).fontSize(12).margin({ left: 4 })
  }
  .width('100%')
  .padding(16)
}

6.2 任务卡片(含优先级颜色、象限标签、子任务展开)

typescript 复制代码
@Builder TaskCard(todo: Todo) {
  Column() {
    Row({ space: 10 }) {
      Text(todo.isCompleted ? '✅' : '⬜')
        .fontSize(22)
        .onClick(() => this.toggleComplete(todo.id))
      Column({ space: 4 }) {
        Text(todo.title)
          .fontSize(15)
          .fontColor(todo.isCompleted ? '#9CA3AF' : '#1F2937')
          .decoration({ type: todo.isCompleted ? TextDecorationType.LineThrough : TextDecorationType.None })
        Text(`📅 ${todo.dueDate}  ${todo.description}`)
          .fontSize(12)
          .fontColor('#9CA3AF')
      }
      .alignItems(HorizontalAlign.Start)
      .layoutWeight(1)
      Row() {
        Text(todo.priority === 'high' ? '🔴' : todo.priority === 'medium' ? '🟡' : '🟢')
        Text(this.getQuadrantLabel(todo.quadrant)).fontSize(10)
      }
    }
    // 子任务折叠
    if (todo.subtasks.length > 0) {
      Row() {
        Text(`子任务 ${todo.subtasks.filter(s => s.isCompleted).length}/${todo.subtasks.length}`)
          .fontSize(12).fontColor('#999')
          .onClick(() => {
            this.expandedTaskId = this.expandedTaskId === todo.id ? -1 : todo.id;
          })
        Blank()
        Button('专注').onClick(() => this.startFocus(todo.id)).height(24).fontSize(11).backgroundColor('#7C3AED')
        Button('编辑').onClick(() => this.openEditDialog(todo)).height(24).fontSize(11).backgroundColor('#A78BFA')
      }
    } else {
      Row() {
        Button('专注').onClick(() => this.startFocus(todo.id)).height(24).fontSize(11).backgroundColor('#7C3AED')
        Button('编辑').onClick(() => this.openEditDialog(todo)).height(24).fontSize(11).backgroundColor('#A78BFA')
      }
      .width('100%')
      .justifyContent(FlexAlign.End)
    }
    // 展开子任务
    if (this.expandedTaskId === todo.id) {
      Column() {
        ForEach(todo.subtasks, (sub) => { /* 子任务行 */ })
      }
    }
  }
  .width('100%')
  .padding(14)
  .backgroundColor('#FFF')
  .borderRadius(12)
  .shadow({ radius: 2, color: '#00000010' })
  .margin({ bottom: 10 })
}

private getQuadrantLabel(q: number): string {
  return ['', '重要紧急', '重要不紧急', '紧急不重要', '不重要不紧急'][q] || '';
}

6.3 添加任务弹窗(含表单)

弹窗包含所有字段,使用 dialog 展示。

typescript 复制代码
@Builder AddTodoDialog() {
  Column() {
    Text(this.editingTodoId > 0 ? '编辑任务' : '添加任务').fontSize(18).fontWeight(FontWeight.Bold).margin(12);
    TextInput({ placeholder: '标题*', text: this.newTitle }).onChange(v => this.newTitle = v).margin(6);
    TextInput({ placeholder: '描述', text: this.newDesc }).onChange(v => this.newDesc = v).margin(6);
    Row() {
      Text('优先级').width(60);
      Select([{ value: '高' }, { value: '中' }, { value: '低' }])
        .selected(['high','medium','low'].indexOf(this.newPriority))
        .onSelect((idx) => {
          this.newPriority = ['high','medium','low'][idx];
        })
        .width(120);
    }.margin(6);
    Row() {
      Text('象限').width(60);
      Select([{ value: '重要紧急' }, { value: '重要不紧急' }, { value: '紧急不重要' }, { value: '不重要不紧急' }])
        .selected(this.newQuadrant - 1)
        .onSelect((idx) => {
          this.newQuadrant = (idx + 1) as any;
        })
        .width(140);
    }.margin(6);
    Row() {
      Text('截止日期').width(60);
      DatePicker({ selected: new Date(this.newDueDate) })
        .onChange((val) => {
          this.newDueDate = val.year + '-' + String(val.month+1).padStart(2,'0') + '-' + String(val.day).padStart(2,'0');
        });
    }.margin(6);
    TextInput({ placeholder: '标签(逗号分隔)', text: this.newTags }).onChange(v => this.newTags = v).margin(6);
    Row() {
      Text('重复').width(60);
      Select([{ value: '不重复' }, { value: '每日' }, { value: '每周' }, { value: '每月' }])
        .selected(['none','daily','weekly','monthly'].indexOf(this.newRepeat))
        .onSelect((idx) => {
          this.newRepeat = ['none','daily','weekly','monthly'][idx];
        })
        .width(120);
    }.margin(6);
    TextArea({ placeholder: '子任务(每行一个)', text: this.newSubtasks })
      .onChange(v => this.newSubtasks = v)
      .height(60)
      .margin(6);
    Row() {
      Button('取消').onClick(() => this.isAddDialogVisible = false).backgroundColor('#999');
      Button('保存').onClick(() => this.saveTodo()).backgroundColor('#7C3AED').margin({ left: 20 });
    }.margin(16);
  }
  .padding(20)
  .width('90%')
  .backgroundColor('#FFF')
  .borderRadius(16);
}

6.4 四象限视图

见 5.3。

6.5 日历视图

见 5.7(需完整实现日历网格,此处略细,但提供思路)。

6.6 专注模式计时器

见 5.9。


七、完整主页面代码(Index.ets)及子页面

Index.ets 采用 Tabs + 各 TabContent 组合所有视图,由于篇幅,提供完整结构框架:

typescript 复制代码
// Index.ets
@Entry
@Component
struct Index {
  // 所有状态
  // 所有方法

  build() {
    Column() {
      // 顶部栏(在首页显示)
      if (this.currentTab === 0) {
        this.TopBar();
      }

      Tabs({ barPosition: BarPosition.End }) {
        TabContent() {
          // 清单列表
          Column() {
            // 筛选栏
            Row() { /* 标签选择 + 搜索框 */ }
            .padding(8)

            List() {
              ForEach(this.getFilteredTodos(), (t: Todo) => {
                ListItem() { this.TaskCard(t) }
              })
            }
            .padding(16)
            .layoutWeight(1)
          }
        }
        .tabBar('📋 清单')

        TabContent() {
          // 四象限
          this.QuadrantView()
        }
        .tabBar('🎯 象限')

        TabContent() {
          // 日历
          this.CalendarView()
        }
        .tabBar('📅 日历')

        TabContent() {
          // 统计
          this.StatsView()
        }
        .tabBar('📊 统计')
      }
      .width('100%')
      .layoutWeight(1)

      // 添加按钮(仅在清单页显示)
      if (this.currentTab === 0) {
        Button('+ 添加任务')
          .width('90%')
          .height(52)
          .backgroundColor('#7C3AED')
          .borderRadius(26)
          .margin({ bottom: 20 })
          .onClick(() => this.openAddDialog())
      }

      // 专注模式覆盖层
      if (this.isFocusing) { /* 专注UI */ }
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#F5F3FF')
    .dialog($$this.isAddDialogVisible, this.AddTodoDialog())
  }
}

其他子页面(如统计视图)可在同一文件中实现。


八、运行与调试

8.1 环境

  • DevEco Studio 5.0+,API 24。
  • 无需额外权限。

8.2 运行

  1. 导入完整项目,含所有 model/service。
  2. 运行模拟器,测试添加任务、切换完成、四象限、日历、专注模式等。

8.3 调试

  • 使用 HiLog 查看持久化操作。
  • 测试重复任务生成逻辑。

九、项目总结与扩展思路

9.1 项目总结

  • 功能完整:涵盖四象限、优先级、子任务、标签、搜索、日历、统计、专注、备份。
  • 工程化:服务层+持久化,便于扩展。
  • 交互丰富:弹窗、动画、卡片、进度环。
  • 效率导向:番茄钟、重复任务等生产力工具。

9.2 扩展方向

  1. 云端同步:接入云存储,多设备同步。
  2. 推送提醒:任务到期前通知。
  3. 团队协作:共享任务清单。
  4. 语音添加:语音转文字创建任务。
  5. 桌面小组件:显示今日待办。
  6. 暗黑模式:自适应深色主题。

运行效果

相关推荐
不肥嘟嘟右卫门2 小时前
鸿蒙原生ArkTS布局方式之RelativeContainer+百分比多设备布局深度解析
华为·harmonyos
●VON3 小时前
鸿蒙 PC Markdown 编辑器原生右键弹层兼容
安全·华为·编辑器·harmonyos·鸿蒙
达子6663 小时前
第9章_HarmonyOs图解 用Java开发UI
java·ui·harmonyos
网络工程小王5 小时前
【HCIE-AI】10.pytorch模型迁移分析
人工智能·学习·华为·llama
ZENERGY-众壹5 小时前
跨品牌逆变器升级:华为锦浪德业 API 对接的 7 个坑
分布式·华为·数据归一化·光伏运维·逆变器api
●VON6 小时前
鸿蒙 PC Markdown 编辑器系统浏览器与图片查看器独立验收
华为·编辑器·harmonyos·鸿蒙
红烧大青虫6 小时前
HarmonyOS开发实战:小分享-WaterFlow 瀑布流布局实现模板墙
华为·harmonyos·鸿蒙
程序员黑豆6 小时前
鸿蒙应用开发:Stack堆叠组件实战——实现微信消息角标效果
前端·harmonyos
Catrice06 小时前
HarmonyOS ArkTS 实战:实现一个心情日记与情绪追踪应用
华为·harmonyos