🔥 关键词:鸿蒙实战项目、待办清单App、完整案例、HarmonyOS开发

🔥 专属服务

【关于我】

  • CSDN技术分享者
  • 专注[各种技术]分享
  • 已帮助10000+开发者

【能帮你】

  • 🎯 技术答疑
  • 📚 学习规划
  • 💼 项目指导

**📱 添加微信:最下方的微信名片(备注"CSDN")

前言

经过前面五篇文章的学习,你已经掌握了HarmonyOS开发的核心技能。本文将带你从零开始,开发一个功能完整的待办清单App,涵盖UI设计、状态管理、数据持久化等完整开发流程。


一、项目需求分析

待办清单App的核心功能:

  • ✅ 添加待办事项
  • ✅ 标记完成/未完成
  • ✅ 删除待办事项
  • ✅ 本地数据持久化
  • ✅ 筛选显示(全部/未完成/已完成)

二、项目结构

entry/src/main/ets/
├── entryability/          // EntryAbility
├── model/                 // 数据模型
│   └── Todo.ets           // 待办数据模型
├── database/              // 数据库
│   └── TodoDatabase.ets   // 数据库操作
├── components/            // 组件
│   ├── TodoItem.ets       // 待办项组件
│   └── AddTodoDialog.ets  // 添加对话框
└── pages/                 // 页面
    └── Index.ets          // 主页面

三、数据模型定义

// model/Todo.ets
@Observed
export class Todo {
  id: number
  title: string
  completed: boolean
  createTime: number

  constructor(title: string, id?: number) {
    this.id = id || Date.now()
    this.title = title
    this.completed = false
    this.createTime = Date.now()
  }
}

export enum FilterType {
  ALL = 'all',
  ACTIVE = 'active',
  COMPLETED = 'completed'
}

四、数据库操作

// database/TodoDatabase.ets
import relationalStore from '@ohos.data.relationalStore'
import { Todo } from '../model/Todo'

export class TodoDatabase {
  private static store: relationalStore.RdbStore | null = null

  static async init(context: Context) {
    const config = {
      name: 'TodoDB',
      securityLevel: relationalStore.SecurityLevel.S1
    }
    this.store = await relationalStore.getRdbStore(context, config)
    
    await this.store.executeSql(`
      CREATE TABLE IF NOT EXISTS todos (
        id INTEGER PRIMARY KEY,
        title TEXT,
        completed INTEGER,
        createTime INTEGER
      )
    `)
  }

  static async add(todo: Todo): Promise<void> {
    if (!this.store) return
    await this.store.insert('todos', {
      id: todo.id,
      title: todo.title,
      completed: todo.completed ? 1 : 0,
      createTime: todo.createTime
    })
  }

  static async update(todo: Todo): Promise<void> {
    if (!this.store) return
    const predicates = new relationalStore.RdbPredicates('todos')
    predicates.equalTo('id', todo.id)
    await this.store.update({
      title: todo.title,
      completed: todo.completed ? 1 : 0
    }, predicates)
  }

  static async delete(id: number): Promise<void> {
    if (!this.store) return
    const predicates = new relationalStore.RdbPredicates('todos')
    predicates.equalTo('id', id)
    await this.store.delete(predicates)
  }

  static async getAll(): Promise<Todo[]> {
    if (!this.store) return []
    const predicates = new relationalStore.RdbPredicates('todos')
    predicates.orderByDesc('createTime')
    const result = await this.store.query(predicates)
    
    let todos: Todo[] = []
    while (result.goToNextRow()) {
      const todo = new Todo('')
      todo.id = result.getLong(0)
      todo.title = result.getString(1)
      todo.completed = result.getLong(2) === 1
      todo.createTime = result.getLong(3)
      todos.push(todo)
    }
    result.close()
    return todos
  }
}

五、待办项组件

// components/TodoItem.ets
import { Todo } from '../model/Todo'

@Component
export struct TodoItem {
  @ObjectLink todo: Todo
  onDelete: () => void = () => {}

  build() {
    Row() {
      // 复选框
      Toggle({ type: ToggleType.Checkbox, isOn: this.todo.completed })
        .selectedColor('#3B82F6')
        .onChange((isOn) => {
          this.todo.completed = isOn
        })
      
      // 标题
      Text(this.todo.title)
        .fontSize(16)
        .layoutWeight(1)
        .margin({ left: 12 })
        .decoration(
          this.todo.completed ? TextDecorationType.LineThrough : TextDecorationType.None
        )
        .fontColor(this.todo.completed ? '#94A3B8' : '#1E293B')
      
      // 删除按钮
      Button() {
        Image($r('app.media.ic_delete'))
          .width(20)
          .height(20)
          .fillColor('#EF4444')
      }
      .type(ButtonType.Circle)
      .backgroundColor(Color.Transparent)
      .onClick(() => this.onDelete())
    }
    .width('100%')
    .padding(16)
    .backgroundColor(Color.White)
    .borderRadius(12)
    .shadow({ radius: 4, color: '#00000010', offsetY: 2 })
  }
}

六、主页面

// pages/Index.ets
import { Todo, FilterType } from '../model/Todo'
import { TodoDatabase } from '../database/TodoDatabase'
import { TodoItem } from '../components/TodoItem'

@Entry
@Component
struct TodoPage {
  @State todos: Todo[] = []
  @State filterType: FilterType = FilterType.ALL
  @State newTodoTitle: string = ''
  @State isDialogVisible: boolean = false

  aboutToAppear() {
    // 初始化数据库并加载数据
    TodoDatabase.init(getContext(this)).then(() => {
      this.loadTodos()
    })
  }

  async loadTodos() {
    this.todos = await TodoDatabase.getAll()
  }

  async addTodo() {
    if (this.newTodoTitle.trim() === '') return
    const todo = new Todo(this.newTodoTitle.trim())
    await TodoDatabase.add(todo)
    this.todos.unshift(todo)
    this.newTodoTitle = ''
    this.isDialogVisible = false
  }

  async deleteTodo(index: number) {
    const todo = this.filteredTodos[index]
    await TodoDatabase.delete(todo.id)
    const realIndex = this.todos.findIndex(t => t.id === todo.id)
    if (realIndex > -1) {
      this.todos.splice(realIndex, 1)
    }
  }

  get filteredTodos(): Todo[] {
    switch (this.filterType) {
      case FilterType.ACTIVE:
        return this.todos.filter(t => !t.completed)
      case FilterType.COMPLETED:
        return this.todos.filter(t => t.completed)
      default:
        return this.todos
    }
  }

  get activeCount(): number {
    return this.todos.filter(t => !t.completed).length
  }

  build() {
    Column({ space: 16 }) {
      // 标题栏
      Row() {
        Text('待办清单')
          .fontSize(28)
          .fontWeight(FontWeight.Bold)
          .fontColor('#1E293B')
        
        Button() {
          Image($r('app.media.ic_add'))
            .width(24)
            .height(24)
            .fillColor(Color.White)
        }
        .type(ButtonType.Circle)
        .backgroundColor('#3B82F6')
        .onClick(() => this.isDialogVisible = true)
      }
      .width('100%')
      .justifyContent(FlexAlign.SpaceBetween)
      .padding({ top: 20, bottom: 10 })

      // 筛选标签
      Row({ space: 12 }) {
        this.FilterButton('全部', FilterType.ALL)
        this.FilterButton('未完成', FilterType.ACTIVE)
        this.FilterButton('已完成', FilterType.COMPLETED)
      }
      .width('100%')

      // 待办列表
      List({ space: 12 }) {
        ForEach(this.filteredTodos, (todo: Todo, index) => {
          ListItem() {
            TodoItem({
              todo: todo,
              onDelete: () => this.deleteTodo(index)
            })
          }
        })
      }
      .width('100%')
      .layoutWeight(1)

      // 底部统计
      Text(`${this.activeCount} 个待办事项`)
        .fontSize(14)
        .fontColor('#64748B')
        .margin({ top: 10 })
    }
    .width('100%')
    .height('100%')
    .padding(20)
    .backgroundColor('#F8FAFC')
    // 添加对话框...
  }

  @Builder
  FilterButton(label: string, type: FilterType) {
    Button(label)
      .fontSize(14)
      .fontColor(this.filterType === type ? Color.White : '#64748B')
      .backgroundColor(this.filterType === type ? '#3B82F6' : '#E2E8F0')
      .height(32)
      .onClick(() => this.filterType = type)
  }
}

七、效果展示

完成后的App具有以下特点:

  • 🎨 简洁美观的界面设计
  • ⚡ 流畅的交互动画
  • 💾 数据持久化存储
  • 🔍 多种筛选方式

八、进阶优化方向

可以进一步完善的特性:

  • 📝 添加待办事项编辑功能
  • ☁️ 实现云端数据同步
  • 🔔 添加待办提醒通知
  • 🏷️ 支持待办分类标签

总结

通过本实战项目,我们综合运用ArkTS、ArkUI、状态管理和数据持久化等知识,完成了一个功能完整的待办清单App。这个项目涵盖了鸿蒙应用开发的核心技能,是你进阶鸿蒙开发的重要里程碑。

希望这个系列教程能帮助你顺利开启鸿蒙开发之旅!


结语

🚀 HarmonyOS NEXT 开发指南
CSDN技术博客系列文章合集

本系列教程共6篇文章,从环境搭建到实战项目,系统讲解了原生鸿蒙ArkTS开发的核心知识:

  1. ✅ 开发环境搭建
  2. ✅ ArkTS语言基础
  3. ✅ 声明式UI开发
  4. ✅ 状态管理
  5. ✅ 网络与存储
  6. ✅ 完整项目实战

如果觉得有帮助,欢迎点赞、收藏、转发!


Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐