🚀 第10章 综合实战
用 ArkTS 构建一个功能完整的 Todo 待办事项应用
11.1 项目概览
本章将把前9章所学知识综合运用,从零开始构建一个功能完整的 待办事项(Todo) 应用。
✅
增删改查
添加、删除、完成待办事项
🔍
筛选过滤
全部/进行中/已完成三种视图
💾
状态管理
@State/@Link 响应式驱动 UI
🎨
自定义样式
动画过渡,美观界面
11.2 10.1 应用架构设计
📐 三层架构
🎨 UI层
TodoItem组件 · FilterBar组件 · InputBar组件 · StatBar组件
⬇ 数据绑定 / 事件回调
⚙️ 逻辑层
TodoViewModel · 过滤逻辑 · 统计计算 · 持久化调用
⬇ 读写
💿 数据层
TodoModel · AppStorage · LocalStorage · 接口定义
11.3 10.2 数据模型定义
📌 接口 & 类型定义
// model/TodoModel.ets
export interface TodoItem {
id: string;
text: string;
done: boolean;
createdAt: number;
}
export type FilterType = 'all' | 'active' | 'done';
// 生成唯一ID
export function genId(): string {
return Date.now().toString(36) + Math.random().toString(36).slice(2);
}
export interface TodoItem {
id: string;
text: string;
done: boolean;
createdAt: number;
}
export type FilterType = 'all' | 'active' | 'done';
// 生成唯一ID
export function genId(): string {
return Date.now().toString(36) + Math.random().toString(36).slice(2);
}
11.4 10.3 ViewModel 实现
// viewmodel/TodoViewModel.ets
import { TodoItem, FilterType, genId } from '../model/TodoModel';
@Observed
export class TodoViewModel {
@Track items: TodoItem[] = [];
@Track filter: FilterType = 'all';
// 添加待办
add(text: string): void {
if (!text.trim()) return;
this.items.unshift({ id: genId(), text: text.trim(), done: false, createdAt: Date.now() });
}
// 切换完成状态
toggle(id: string): void {
const item = this.items.find(i => i.id === id);
if (item) item.done = !item.done;
}
// 删除待办
remove(id: string): void {
this.items = this.items.filter(i => i.id !== id);
}
// 过滤列表
get filtered(): TodoItem[] {
return this.items.filter(i => {
if (this.filter === 'active') return !i.done;
if (this.filter === 'done') return i.done;
return true;
});
}
get activeCount(): number { return this.items.filter(i => !i.done).length; }
get doneCount(): number { return this.items.filter(i => i.done).length; }
}
import { TodoItem, FilterType, genId } from '../model/TodoModel';
@Observed
export class TodoViewModel {
@Track items: TodoItem[] = [];
@Track filter: FilterType = 'all';
// 添加待办
add(text: string): void {
if (!text.trim()) return;
this.items.unshift({ id: genId(), text: text.trim(), done: false, createdAt: Date.now() });
}
// 切换完成状态
toggle(id: string): void {
const item = this.items.find(i => i.id === id);
if (item) item.done = !item.done;
}
// 删除待办
remove(id: string): void {
this.items = this.items.filter(i => i.id !== id);
}
// 过滤列表
get filtered(): TodoItem[] {
return this.items.filter(i => {
if (this.filter === 'active') return !i.done;
if (this.filter === 'done') return i.done;
return true;
});
}
get activeCount(): number { return this.items.filter(i => !i.done).length; }
get doneCount(): number { return this.items.filter(i => i.done).length; }
}
11.5 10.4 子组件实现
11.5.1 10.4.1 输入栏组件
// components/InputBar.ets
@Component
export struct InputBar {
@Link inputText: string;
onAdd?: () => void;
build() {
Row() {
TextInput({ placeholder: '输入待办事项…', text: this.inputText })
.layoutWeight(1)
.height(44)
.borderRadius(8)
.onChange(v => { this.inputText = v; })
.onSubmit(() => { this.onAdd?.(); })
Button('添加')
.height(44)
.backgroundColor('#C02020')
.onClick(() => { this.onAdd?.(); })
}
.width('100%')
.gap(8)
}
}
@Component
export struct InputBar {
@Link inputText: string;
onAdd?: () => void;
build() {
Row() {
TextInput({ placeholder: '输入待办事项…', text: this.inputText })
.layoutWeight(1)
.height(44)
.borderRadius(8)
.onChange(v => { this.inputText = v; })
.onSubmit(() => { this.onAdd?.(); })
Button('添加')
.height(44)
.backgroundColor('#C02020')
.onClick(() => { this.onAdd?.(); })
}
.width('100%')
.gap(8)
}
}
11.5.2 10.4.2 待办项组件
// components/TodoItemComp.ets
@Component
export struct TodoItemComp {
@ObjectLink item: TodoItem;
onToggle?: (id: string) => void;
onDelete?: (id: string) => void;
build() {
Row() {
Checkbox()
.select(this.item.done)
.onChange(() => { this.onToggle?.(this.item.id); })
Text(this.item.text)
.layoutWeight(1)
.decoration({ type: this.item.done ? TextDecorationType.LineThrough : TextDecorationType.None })
.fontColor(this.item.done ? '#aaa' : '#333')
Button('🗑', { type: ButtonType.Normal })
.fontColor('#e57373')
.backgroundColor(Color.Transparent)
.onClick(() => { this.onDelete?.(this.item.id); })
}
.width('100%')
.padding(12)
.backgroundColor(Color.White)
.borderRadius(8)
.margin({ bottom: 4 })
.transition(TransitionEffect.OPACITY)
}
}
@Component
export struct TodoItemComp {
@ObjectLink item: TodoItem;
onToggle?: (id: string) => void;
onDelete?: (id: string) => void;
build() {
Row() {
Checkbox()
.select(this.item.done)
.onChange(() => { this.onToggle?.(this.item.id); })
Text(this.item.text)
.layoutWeight(1)
.decoration({ type: this.item.done ? TextDecorationType.LineThrough : TextDecorationType.None })
.fontColor(this.item.done ? '#aaa' : '#333')
Button('🗑', { type: ButtonType.Normal })
.fontColor('#e57373')
.backgroundColor(Color.Transparent)
.onClick(() => { this.onDelete?.(this.item.id); })
}
.width('100%')
.padding(12)
.backgroundColor(Color.White)
.borderRadius(8)
.margin({ bottom: 4 })
.transition(TransitionEffect.OPACITY)
}
}
11.6 10.5 主页面组装
// pages/Index.ets
import { TodoViewModel } from '../viewmodel/TodoViewModel';
import { InputBar } from '../components/InputBar';
import { TodoItemComp } from '../components/TodoItemComp';
@Entry
@Component
struct Index {
@State vm: TodoViewModel = new TodoViewModel();
@State inputText: string = '';
build() {
Column() {
// 标题栏
Text('📋 我的待办')
.fontSize(24).fontWeight(FontWeight.Bold)
.fontColor('#C02020').margin({ bottom: 16 })
// 输入栏
InputBar({ inputText: $inputText, onAdd: () => {
this.vm.add(this.inputText);
this.inputText = '';
}})
// 过滤标签
Row() {
ForEach(['all', 'active', 'done'],
(f: string) => Button(f === 'all' ? '全部' : f === 'active' ? '进行中' : '已完成')
.backgroundColor(this.vm.filter === f ? '#C02020' : '#eee')
.fontColor(this.vm.filter === f ? Color.White : '#555')
.layoutWeight(1)
.onClick(() => { this.vm.filter = f as any; })
)
}.width('100%').gap(8).margin({ top: 12, bottom: 8 })
// 待办列表
List({ space: 4 }) {
ForEach(this.vm.filtered, (item: TodoItem) =>
ListItem() {
TodoItemComp({ item,
onToggle: id => this.vm.toggle(id),
onDelete: id => this.vm.remove(id)
})
}
, item => item.id)
}.layoutWeight(1)
// 统计栏
Row() {
Text(`待完成 ${this.vm.activeCount} 项`).fontColor('#777')
Text(`已完成 ${this.vm.doneCount} 项`).fontColor('#4caf50')
}.justifyContent(FlexAlign.SpaceBetween).width('100%').margin({ top: 12 })
}
.padding(20)
.backgroundColor('#f5f5f5')
.height('100%')
}
}
import { TodoViewModel } from '../viewmodel/TodoViewModel';
import { InputBar } from '../components/InputBar';
import { TodoItemComp } from '../components/TodoItemComp';
@Entry
@Component
struct Index {
@State vm: TodoViewModel = new TodoViewModel();
@State inputText: string = '';
build() {
Column() {
// 标题栏
Text('📋 我的待办')
.fontSize(24).fontWeight(FontWeight.Bold)
.fontColor('#C02020').margin({ bottom: 16 })
// 输入栏
InputBar({ inputText: $inputText, onAdd: () => {
this.vm.add(this.inputText);
this.inputText = '';
}})
// 过滤标签
Row() {
ForEach(['all', 'active', 'done'],
(f: string) => Button(f === 'all' ? '全部' : f === 'active' ? '进行中' : '已完成')
.backgroundColor(this.vm.filter === f ? '#C02020' : '#eee')
.fontColor(this.vm.filter === f ? Color.White : '#555')
.layoutWeight(1)
.onClick(() => { this.vm.filter = f as any; })
)
}.width('100%').gap(8).margin({ top: 12, bottom: 8 })
// 待办列表
List({ space: 4 }) {
ForEach(this.vm.filtered, (item: TodoItem) =>
ListItem() {
TodoItemComp({ item,
onToggle: id => this.vm.toggle(id),
onDelete: id => this.vm.remove(id)
})
}
, item => item.id)
}.layoutWeight(1)
// 统计栏
Row() {
Text(`待完成 ${this.vm.activeCount} 项`).fontColor('#777')
Text(`已完成 ${this.vm.doneCount} 项`).fontColor('#4caf50')
}.justifyContent(FlexAlign.SpaceBetween).width('100%').margin({ top: 12 })
}
.padding(20)
.backgroundColor('#f5f5f5')
.height('100%')
}
}
11.7 10.6 交互演示
📋 Todo App 演示
待完成 0 项
已完成 0 项
👆 实时可用! 在上方演示中添加待办事项,切换筛选标签,体验与真实 ArkTS 应用相同的交互逻辑。
11.8 10.7 知识点回顾
🔤
第2-3章
变量声明、类型、控制流
🔧
第4章
函数、箭头函数、可选参数
📦
第5章
数组 filter / find / map
🏗️
第6章
类、接口、getter
🎨
第7-8章
@Component @State 装饰器 + UI组件
🔄
第9章
@Link @ObjectLink 跨组件状态
💡 下一步:将数据持久化到
AppStorage 或 Preferences,让 Todo 列表在应用重启后依然存在!
11.9 10.8 项目结构总览
todo-app/
├── entry/src/main/ets/
│ ├── entryability/
│ │ └── EntryAbility.ets
│ ├── model/
│ │ └── TodoModel.ets ← 数据接口定义
│ ├── viewmodel/
│ │ └── TodoViewModel.ets ← 业务逻辑
│ ├── components/
│ │ ├── InputBar.ets ← 输入栏组件
│ │ └── TodoItemComp.ets ← 待办项组件
│ └── pages/
│ └── Index.ets ← 主页面(@Entry)
└── entry/src/main/resources/
└── base/profile/
└── main_pages.json
🎉 恭喜完成 ArkTS 互动教程!
你已掌握 ArkTS 的核心知识体系,可以开始构建真实的 HarmonyOS 应用了!