HarmonyOS NEXT AI 智能生活助手:AI 待办事项生成
前言
在第14篇中,我们实现了 AI 代码解释。本文将实现AI 待办事项生成------用户用自然语言描述任务,AI 自动提取待办事项、设置优先级、归类。
AI 待办生成 是效率工具的核心能力。只需一句话如"明天上午开会,下午写报告,晚上去健身",AI 就能自动拆解为结构化的待办列表。
本文将实现:
- 自然语言转待办:一句话生成结构化待办
- 四档优先级:紧急/重要/普通/低优
- 自动分类:工作/生活/学习分类
- 排序与过滤:按优先级和分类展示

图1:AI 待办事项生成页面布局
一、功能设计
1.1 页面布局
┌─────────────────────────┐
│ ← 返回 AI 待办 │
├─────────────────────────┤
│ │
│ ┌─────────────────────┐│
│ │ 输入描述任务... ││ ← 自然语言输入
│ │ "明天上午开会 ││
│ │ 下午写报告..." ││
│ └─────────────────────┘│
│ │
│ ⟳ 生成待办 │
│ │
│ ┌─────────────────────┐│
│ │ [文档] 待办列表 (5项) ││
│ │ ││
│ │ [红点] 紧急 ││
│ │ □ 明天上午9点开会 ││ ← 按优先级分组
│ │ ││
│ │ [黄点] 重要 ││
│ │ □ 下午完成报告 ││
│ │ ││
│ │ [绿点] 普通 ││
│ │ □ 晚上健身 ││
│ └─────────────────────┘│
└─────────────────────────┘
1.2 技术架构
typescript
// 待办事项模块架构
/*
┌─────────────────────────────────────────┐
│ TodoPage.ets │
│ (UI 展示 / 输入 / 状态切换) │
└─────────────────┬───────────────────────┘
│
┌─────────────────▼───────────────────────┐
│ TodoManager.ts │
│ (待办生成 / 排序 / 分类 / 统计) │
└─────────────────┬───────────────────────┘
│
┌───────────┼───────────┐
▼ ▼ ▼
┌─────────┐ ┌─────────┐ ┌─────────┐
│AIService│ │CacheMgr │ │relational│
│(多Provider│ │(缓存) │ │Store │
│ 切换) │ │ │ │(待办数据) │
└─────────┘ └─────────┘ └─────────┘
*/
二、数据模型
2.1 待办事项数据结构
typescript
// model/TodoItem.ts
export interface TodoItem {
id: string;
title: string; // 标题
description?: string; // 描述
priority: Priority; // 优先级
category: TodoCategory; // 分类
deadline?: string; // 截止日期
estimatedMinutes?: number; // 预估时长
isCompleted: boolean; // 是否完成
createdTime: number; // 创建时间
tags: string[]; // 标签
}
export type Priority = 'urgent' | 'important' | 'normal' | 'low';
export type TodoCategory = 'work' | 'life' | 'study' | 'health' | 'other';
// 优先级配置
export const PRIORITY_CONFIG: Record<Priority, { label: string; icon: string; color: string; score: number }> = {
urgent: { label: '紧急', icon: 'ic_priority_urgent', color: '#E17055', score: 0 },
important: { label: '重要', icon: 'ic_priority_important', color: '#FDCB6E', score: 1 },
normal: { label: '普通', icon: 'ic_priority_normal', color: '#00B894', score: 2 },
low: { label: '低优', icon: 'ic_priority_low', color: '#B2BEC3', score: 3 }
};
// 分类配置
export const CATEGORY_CONFIG: Record<TodoCategory, { label: string; icon: string; color: string }> = {
work: { label: '工作', icon: 'ic_category_work', color: '#6C5CE7' },
life: { label: '生活', icon: 'ic_category_life', color: '#00B894' },
study: { label: '学习', icon: 'ic_category_study', color: '#0984E3' },
health: { label: '健康', icon: 'ic_category_health', color: '#E17055' },
other: { label: '其他', icon: 'ic_category_other', color: '#B2BEC3' }
};
2.2 待办事项数据库表
typescript
// model/TodoRecord.ts
export interface TodoRecord {
id: string;
title: string;
description?: string;
priority: string;
category: string;
deadline?: string;
estimatedMinutes?: number;
isCompleted: boolean;
createdTime: number;
tags: string;
}
export const TODO_TABLE = {
tableName: 'todos',
columns: [
{ name: 'id', type: 'TEXT PRIMARY KEY' },
{ name: 'title', type: 'TEXT NOT NULL' },
{ name: 'description', type: 'TEXT' },
{ name: 'priority', type: 'TEXT' },
{ name: 'category', type: 'TEXT' },
{ name: 'deadline', type: 'TEXT' },
{ name: 'estimated_minutes', type: 'INTEGER' },
{ name: 'is_completed', type: 'INTEGER' },
{ name: 'created_time', type: 'INTEGER' },
{ name: 'tags', type: 'TEXT' }
]
};
三、TodoManager 实现
3.1 核心代码
typescript
// ai/TodoManager.ts
export class TodoManager {
private static instance: TodoManager;
private aiService = AIService.getInstance();
private promptManager = PromptManager.getInstance();
private cacheManager = CacheManager.getInstance();
private todos: TodoItem[] = [];
static getInstance(): TodoManager {
if (!TodoManager.instance) {
TodoManager.instance = new TodoManager();
}
return TodoManager.instance;
}
// 从自然语言生成待办
async generateTodos(text: string): Promise<TodoItem[]> {
// 检查缓存
const cacheKey = `todo_${this.hashText(text)}`;
const cached = this.cacheManager.get<TodoItem[]>(cacheKey);
if (cached) return cached;
const prompt = this.promptManager.buildPrompt('todo', {});
const response = await this.aiService.chat([
{ role: 'system', content: prompt },
{ role: 'user', content: `从以下文本中提取待办事项:\n\n${text}` }
]);
const todos = this.parseTodos(response.content);
this.todos.push(...todos);
// 写入缓存
this.cacheManager.set(cacheKey, todos, 60 * 60 * 1000);
return todos;
}
// 按优先级排序
sortTodos(todos?: TodoItem[]): TodoItem[] {
const list = todos || this.todos;
const priorityOrder = { urgent: 0, important: 1, normal: 2, low: 3 };
return [...list].sort((a, b) => {
const pa = priorityOrder[a.priority] ?? 2;
const pb = priorityOrder[b.priority] ?? 2;
return pa - pb;
});
}
// 按分类过滤
getByCategory(category: TodoCategory): TodoItem[] {
return this.todos.filter(t => t.category === category);
}
// 切换完成状态
toggleComplete(id: string): void {
const todo = this.todos.find(t => t.id === id);
if (todo) todo.isCompleted = !todo.isCompleted;
}
// 删除待办
deleteTodo(id: string): void {
this.todos = this.todos.filter(t => t.id !== id);
}
// 获取统计数据
getStats(): TodoStats {
return TodoOrganizer.getStats(this.todos);
}
private parseTodos(content: string): TodoItem[] {
try {
const jsonMatch = content.match(/\[[\s\S]*?\]/);
if (jsonMatch) {
return JSON.parse(jsonMatch[0]).map((t: any) => ({
...t,
id: t.id || Date.now().toString(36) + Math.random().toString(36).substr(2, 5),
isCompleted: false,
createdTime: Date.now(),
tags: t.tags || []
}));
}
} catch {}
return [];
}
private hashText(text: string): string {
let hash = 0;
for (let i = 0; i < text.length; i++) {
const char = text.charCodeAt(i);
hash = ((hash << 5) - hash) + char;
hash |= 0;
}
return hash.toString(36);
}
}
3.2 排序与分类
typescript
// ai/TodoOrganizer.ts
export class TodoOrganizer {
// Eisenhower Matrix 分类
static classifyByMatrix(todo: TodoItem): 'do' | 'decide' | 'delegate' | 'delete' {
if (todo.priority === 'urgent' && todo.category === 'work') return 'do';
if (todo.priority === 'important') return 'decide';
if (todo.priority === 'normal') return 'delegate';
return 'delete';
}
// 预估番茄钟数量
static estimatePomodoros(minutes: number): number {
return Math.ceil(minutes / 25);
}
// 时间分组
static groupByTimeframe(todos: TodoItem[]): Record<string, TodoItem[]> {
const groups: Record<string, TodoItem[]> = {
today: [],
tomorrow: [],
thisWeek: [],
future: []
};
for (const todo of todos) {
if (!todo.deadline) { groups.future.push(todo); continue; }
const deadline = new Date(todo.deadline);
const now = new Date();
const diff = deadline.getTime() - now.getTime();
const days = Math.ceil(diff / (1000 * 60 * 60 * 24));
if (days <= 0) groups.today.push(todo);
else if (days <= 1) groups.tomorrow.push(todo);
else if (days <= 7) groups.thisWeek.push(todo);
else groups.future.push(todo);
}
return groups;
}
// 分类统计
static getStats(todos: TodoItem[]): TodoStats {
return {
total: todos.length,
completed: todos.filter(t => t.isCompleted).length,
urgent: todos.filter(t => t.priority === 'urgent').length,
overdue: todos.filter(t => {
if (!t.deadline || t.isCompleted) return false;
return new Date(t.deadline).getTime() < Date.now();
}).length
};
}
}
interface TodoStats {
total: number;
completed: number;
urgent: number;
overdue: number;
}
四象限法则:利用 Eisenhower Matrix 对任务进行"重要-紧急"分类,帮助用户优先处理最重要的事务。
四、Prompt 模板
4.1 todo.md
markdown
---
name: todo
version: 1.1.0
description: AI 待办事项生成
---
你是一个任务管理专家。从用户输入的文本中提取待办事项。
## 提取规则
1. 识别所有明确的任务描述
2. 推断合理的优先级(urgent/important/normal/low)
3. 分类到合适的类别(work/life/study/health/other)
4. 提取时间信息作为截止日期
## 输出格式
返回 JSON 数组:
[
{
"title": "明天上午开会",
"description": "准备季度汇报材料",
"priority": "urgent",
"category": "work",
"deadline": "2025-01-15",
"estimatedMinutes": 120,
"tags": ["会议"]
}
]
五、主界面实现
5.1 TodoPage
typescript
// pages/TodoPage.ets
import { display } from '@kit.ArkUI';
@Entry
@Component
struct TodoPage {
@State inputText: string = '';
@State todos: TodoItem[] = [];
@State isLoading: boolean = false;
@State filterCategory: string = 'all';
@State statusBarHeight: number = 0;
@State navBarHeight: number = 0;
private todoManager = TodoManager.getInstance();
aboutToAppear() {
this.statusBarHeight = AppStorage.get<number>('statusBarHeight') || 0;
this.navBarHeight = AppStorage.get<number>('navBarHeight') || 0;
}
build() {
Column() {
// 状态栏占位
Row().width('100%').height(this.statusBarHeight);
// 导航栏
Row() {
Image($r('app.media.ic_back')).width(24).height(24)
.onClick(() => RouterUtil.back());
Text('AI 待办').fontSize(18).fontWeight(FontWeight.Bold).margin({ left: 12 });
Blank();
Text('统计').fontSize(14).fontColor('#6C5CE7')
.onClick(() => this.showStats());
}
.width('100%').height(56).padding({ left: 16, right: 16 });
// 输入区域
TextArea({
text: this.inputText,
placeholder: '用自然语言描述任务,如"明天上午开会,下午写周报..."'
})
.height(120).backgroundColor(Color.White).borderRadius(12)
.padding(12).fontSize(15).margin(16)
.onChange(v => this.inputText = v);
// 生成按钮
Button() {
if (this.isLoading) {
LoadingView({ text: '生成中...' });
} else {
Row() {
Image($r('app.media.ic_generate')).width(18).height(18);
Text('生成待办').fontSize(16).fontColor(Color.White).margin({ left: 6 });
}
}
}
.width('90%').height(44).backgroundColor('#6C5CE7').borderRadius(22)
.disabled(!this.inputText.trim() || this.isLoading)
.margin({ bottom: 16 })
.onClick(() => this.generateTodos());
// 待办列表
if (this.todos.length > 0) {
Row() {
Text('待办列表').fontSize(16).fontWeight(FontWeight.Bold);
Blank();
Text(`共 ${this.todos.length} 项`).fontSize(13).fontColor(Color.Gray);
}
.padding({ left: 16, right: 16, bottom: 8 });
Scroll() {
Column() {
// 按优先级分组展示
ForEach(['urgent', 'important', 'normal', 'low'] as Priority[], (priority) => {
const items = this.todos.filter(t => t.priority === priority);
if (items.length === 0) return;
Column() {
Row() {
Image($r(`app.media.${PRIORITY_CONFIG[priority].icon}`)).width(14).height(14);
Text(PRIORITY_CONFIG[priority].label).fontSize(14)
.fontWeight(FontWeight.Bold).margin({ left: 4 });
Text(`(${items.length})`).fontSize(12).fontColor(Color.Gray);
}
.width('100%').margin({ bottom: 6 });
ForEach(items, (item: TodoItem) => {
this.todoItem(item);
}, (item: TodoItem) => item.id);
}
.width('100%').padding(16).backgroundColor(Color.White)
.borderRadius(12).margin({ bottom: 8 });
}, (p: string) => p);
}
.padding(16);
}
.layoutWeight(1);
}
// 底部导航栏占位
Row().width('100%').height(this.navBarHeight);
}
.width('100%').height('100%').backgroundColor('#F5F6FA');
}
@Builder
todoItem(item: TodoItem) {
Row() {
// 复选框
Toggle({ type: ToggleType.Checkbox, isOn: item.isCompleted })
.width(22).height(22).margin({ right: 12 })
.onChange(() => this.todoManager.toggleComplete(item.id));
// 内容
Column() {
Text(item.title).fontSize(15)
.decoration({ type: item.isCompleted ? TextDecorationType.LineThrough : TextDecorationType.None })
.fontColor(item.isCompleted ? Color.Gray : '#2D3436');
if (item.deadline) {
Text(`${item.deadline}`).fontSize(12).fontColor(Color.Gray).margin({ top: 2 });
}
Row() {
ForEach(item.tags, (tag: string) => {
Text(tag).fontSize(10).fontColor('#6C5CE7')
.backgroundColor('#F0F0FF').borderRadius(4)
.padding({ left: 4, right: 4, top: 1, bottom: 1 }).margin({ right: 4 });
}, (tag: string) => tag);
}
.margin({ top: 4 });
}
.layoutWeight(1);
// 优先级标识
Text(PRIORITY_CONFIG[item.priority].label).fontSize(11)
.fontColor(Color.White).borderRadius(8).padding({ left: 6, right: 6, top: 2, bottom: 2 })
.backgroundColor(PRIORITY_CONFIG[item.priority].color);
}
.padding(12).border({ bottom: { width: 0.5, color: '#F0F0F0' } });
}
async generateTodos() {
this.isLoading = true;
try {
const newTodos = await this.todoManager.generateTodos(this.inputText);
this.todos = newTodos;
} catch {
ToastUtil.show('生成失败');
} finally {
this.isLoading = false;
}
}
showStats() {
const stats = this.todoManager.getStats();
AlertDialog.show({
title: '待办统计',
message: `总计:${stats.total}\n已完成:${stats.completed}\n紧急:${stats.urgent}\n逾期:${stats.overdue}`
});
}
}
六、数据持久化与缓存
6.1 使用 relationalStore 持久化待办
typescript
// database/TodoDatabase.ts
import { relationalStore } from '@kit.ArkData';
export class TodoDatabase {
private static instance: TodoDatabase;
private rdbStore: relationalStore.RdbStore | null = null;
static getInstance(): TodoDatabase {
if (!TodoDatabase.instance) {
TodoDatabase.instance = new TodoDatabase();
}
return TodoDatabase.instance;
}
async init(context: Context): Promise<void> {
const config: relationalStore.StoreConfig = {
name: 'todo.db',
securityLevel: relationalStore.SecurityLevel.S1
};
this.rdbStore = await relationalStore.getRdbStore(context, config);
await this.rdbStore?.executeSql(`
CREATE TABLE IF NOT EXISTS todos (
id TEXT PRIMARY KEY,
title TEXT NOT NULL,
description TEXT,
priority TEXT,
category TEXT,
deadline TEXT,
estimated_minutes INTEGER,
is_completed INTEGER,
created_time INTEGER,
tags TEXT
)
`);
}
async insert(todo: TodoItem): Promise<number> {
const bucket: relationalStore.ValuesBucket = {
id: todo.id,
title: todo.title,
description: todo.description,
priority: todo.priority,
category: todo.category,
deadline: todo.deadline,
estimated_minutes: todo.estimatedMinutes,
is_completed: todo.isCompleted ? 1 : 0,
created_time: todo.createdTime,
tags: JSON.stringify(todo.tags)
};
return await this.rdbStore?.insert('todos', bucket) || -1;
}
async queryAll(): Promise<TodoItem[]> {
const predicates = new relationalStore.RdbPredicates('todos');
predicates.orderByDesc('created_time');
const resultSet = await this.rdbStore?.query(predicates);
const list: TodoItem[] = [];
while (resultSet?.goToNextRow()) {
list.push({
id: resultSet.getString(resultSet.getColumnIndex('id')),
title: resultSet.getString(resultSet.getColumnIndex('title')),
description: resultSet.getString(resultSet.getColumnIndex('description')),
priority: resultSet.getString(resultSet.getColumnIndex('priority')) as Priority,
category: resultSet.getString(resultSet.getColumnIndex('category')) as TodoCategory,
deadline: resultSet.getString(resultSet.getColumnIndex('deadline')),
estimatedMinutes: resultSet.getLong(resultSet.getColumnIndex('estimated_minutes')),
isCompleted: resultSet.getLong(resultSet.getColumnIndex('is_completed')) === 1,
createdTime: resultSet.getLong(resultSet.getColumnIndex('created_time')),
tags: JSON.parse(resultSet.getString(resultSet.getColumnIndex('tags')) || '[]')
});
}
resultSet?.close();
return list;
}
async updateStatus(id: string, isCompleted: boolean): Promise<void> {
const bucket: relationalStore.ValuesBucket = {
is_completed: isCompleted ? 1 : 0
};
const predicates = new relationalStore.RdbPredicates('todos');
predicates.equalTo('id', id);
await this.rdbStore?.update(bucket, predicates);
}
async deleteById(id: string): Promise<void> {
const predicates = new relationalStore.RdbPredicates('todos');
predicates.equalTo('id', id);
await this.rdbStore?.delete(predicates);
}
}
6.2 CacheManager 缓存策略
typescript
// cache/CacheManager.ts
export class CacheManager {
private static instance: CacheManager;
private memoryCache: Map<string, CacheEntry> = new Map();
static getInstance(): CacheManager {
if (!CacheManager.instance) {
CacheManager.instance = new CacheManager();
}
return CacheManager.instance;
}
set<T>(key: string, value: T, ttl: number = 60 * 60 * 1000): void {
this.memoryCache.set(key, {
data: value,
expireAt: Date.now() + ttl
});
}
get<T>(key: string): T | null {
const entry = this.memoryCache.get(key);
if (!entry) return null;
if (Date.now() > entry.expireAt) {
this.memoryCache.delete(key);
return null;
}
return entry.data as T;
}
async persist<T>(key: string, value: T): Promise<void> {
const pref = await getPreferences(getContext(), 'todo_cache');
await pref.put(key, JSON.stringify(value));
await pref.flush();
}
clear(): void {
this.memoryCache.clear();
}
}
interface CacheEntry {
data: unknown;
expireAt: number;
}
七、待办统计与报表
7.1 统计指标表
| 指标 | 说明 | 计算方式 |
|---|---|---|
| 总计 | 待办总数 | todos.length |
| 已完成 | 已完成数量 | filter(isCompleted).length |
| 完成率 | 完成百分比 | completed / total * 100% |
| 紧急 | 紧急优先级数量 | filter(priority === 'urgent').length |
| 逾期 | 超过截止日期 | filter(deadline < now && !isCompleted).length |
| 平均用时 | 预估时长平均值 | sum(estimatedMinutes) / total |
7.2 分类统计
typescript
export class TodoStatsAnalyzer {
static getCategoryDistribution(todos: TodoItem[]): Record<TodoCategory, number> {
const distribution: Record<string, number> = {
work: 0, life: 0, study: 0, health: 0, other: 0
};
for (const todo of todos) {
distribution[todo.category] = (distribution[todo.category] || 0) + 1;
}
return distribution as Record<TodoCategory, number>;
}
static getPriorityDistribution(todos: TodoItem[]): Record<Priority, number> {
const distribution: Record<string, number> = {
urgent: 0, important: 0, normal: 0, low: 0
};
for (const todo of todos) {
distribution[todo.priority] = (distribution[todo.priority] || 0) + 1;
}
return distribution as Record<Priority, number>;
}
static getWeeklyTrend(todos: TodoItem[]): { day: string; completed: number; created: number }[] {
const days = ['周一', '周二', '周三', '周四', '周五', '周六', '周日'];
return days.map((day, index) => ({
day,
completed: todos.filter(t => {
const d = new Date(t.createdTime);
return d.getDay() === (index + 1) % 7 && t.isCompleted;
}).length,
created: todos.filter(t => {
const d = new Date(t.createdTime);
return d.getDay() === (index + 1) % 7;
}).length
}));
}
}
7.3 完成趋势图表数据
| 日期 | 新增 | 完成 | 累计 |
|---|---|---|---|
| 周一 | 5 | 3 | 2 |
| 周二 | 3 | 4 | 1 |
| 周三 | 4 | 2 | 3 |
| 周四 | 2 | 5 | 0 |
| 周五 | 6 | 4 | 2 |
八、Git 提交
bash
git add .
git commit -m "feat(todo): 完成 AI 待办事项生成
- 自然语言转结构化待办
- 四档优先级 + 四象限分类
- 按时间分组(今天/明天/本周/未来)
- 完成状态切换和删除
- 待办统计功能
- 集成 relationalStore 持久化待办数据
- 实现 CacheManager 缓存策略
- 支持 OpenAI/DeepSeek/Qwen/智谱/豆包 多 Provider
- 实现安全区适配(AppStorage + display)
- 使用 SVG 矢量图替代 emoji
- 实现 PromptManager 版本控制
- 分类统计与趋势分析
Co-Authored-By: AtomCode (deepseek-v4-flash) <noreply@atomgit.com>"
git tag v0.1.4
总结
本文实现了 AI 待办事项生成 功能。核心要点:
- 自然语言理解:一句话自动提取待办事项
- 四档优先级:紧急/重要/普通/低优,Eisenhower Matrix 分类
- 时间分组:今天/明天/本周/未来,自动排期
- 分类统计:总计、完成、逾期一目了然
- 灵活操作:勾选完成、删除、标签管理
- 多 Provider 支持:OpenAI、DeepSeek、Qwen、智谱、豆包
- PromptManager:独立管理 prompt,支持版本控制
- 安全区适配:通过 AppStorage 获取 statusBarHeight 和 navBarHeight
- 数据持久化:使用 relationalStore 保存待办数据
- 双层缓存:CacheManager 内存缓存 + 持久化缓存
- SVG 图标:所有图标使用矢量图,不使用 emoji
- 统计报表:分类分布、优先级分布、周趋势分析
如果这篇文章对你有帮助,欢迎点赞、收藏、关注,你的支持是我持续创作的动力!