在这里插入图片描述
在这里插入图片描述

实例:待办事项清单(Todo List)|风格:清新卡片式

一、整体布局结构

页面采用「统计面板 → 输入区 → 状态切换 → 列表 → 底部操作栏」五段式结构,让面板内容丰富但不拥挤:

┌────────────────────────────────────┐
│   ✅ 待办清单          共 15 项      │   ← 标题栏
├────────────────────────────────────┤
│  ▢ 未完成 12   ✓ 已完成 3   ⚡高优 4 │   ← 统计卡(3 个数字块)
├────────────────────────────────────┤
│  [输入框: 输入新待办…]  [分类] [新增] │   ← 输入区
├────────────────────────────────────┤
│  [全部] [未完成] [已完成]            │   ← 状态切换(分段控件)
├────────────────────────────────────┤
│  ┌──────────────────────────────┐  │
│  │ ⬜ 完成周报提交        工作 ⚡  │  │   ← 待办卡片(多张)
│  │    今天 09:30                 │  │
│  │    [改] [完成] [删]           │  │
│  └──────────────────────────────┘  │
│  ...                               │
├────────────────────────────────────┤
│  已完成 3 项  ▏▏▏▏▏▏▏▏▏▏ 20%  清空  │   ← 底部进度条 + 清空已完成
└────────────────────────────────────┘

二、各区块说明

1. 标题栏

  • 左侧:✅ 待办清单 标题 + 副标题「优先级排序 · 未完成在前」
  • 右侧:刷新按钮(),点击重新查询

2. 统计卡(3 个数字块)

数据项 数据来源 展示样式
未完成 statistics().pending 大号蓝色数字
已完成 statistics().done 大号绿色数字
高优先级 statistics().high 大号橙色数字

卡片为圆角白底、左右三块均分,数字下方有小字标签。

3. 输入区

组件 说明
TextInput 待办内容输入,占位符「输入新待办,回车或点新增…」
Select(分类) 下拉选择:工作 / 生活 / 学习 / 日常
优先级选择 三选一按钮:低 · 中 · 高
新增按钮 主色填充圆角按钮「+ 新增」

4. 状态切换分段控件

三个 Tab:全部 / 未完成 / 已完成,切换后调用 queryByCompleted() 刷新列表,同时统计卡联动变化。

5. 待办卡片(列表主体)

每张卡片包含:

元素 样式
完成勾选圈 未完成:空心圆;已完成:绿色实心圆 + 白勾
标题 未完成黑色加粗;已完成灰色 + 删除线
分类标签 小圆角底色标签(工作蓝/生活绿/学习紫/日常灰)
优先级 高优显示 ⚡ 橙色角标
时间 灰色小字「MM-DD HH:mm」
操作按钮 「改」打开编辑弹窗、「完成/取消完成」、「删除」

已完成卡片整体置灰、透明度 0.6,视觉上弱化。

6. 底部进度条

  • 完成率进度条:已完成/总数 百分比
  • 右侧「清空已完成」按钮(红色小字),一键删除全部已完成项

三、UI 风格要素

风格项 取值
背景色 #F5F7FA 浅灰蓝
卡片底色 白色,圆角 12,浅阴影
主色 #3B82F6 蓝(按钮/选中态)
成功色 #22C55E 绿(完成态)
警告色 #F97316 橙(高优先级)
删除色 #EF4444
字体 默认 HarmonyOS Sans

四、弹窗交互

  • 编辑弹窗:点击「改」弹出底部弹窗,可修改内容、分类、优先级、备注,点「保存」调用 update()
  • 删除确认:点「删除」弹出 AlertDialog 二次确认,避免误删
  • 清空确认:清空已完成前同样弹窗确认

五、空态与加载

场景 展示
无任何待办 居中插画表情 + 「暂无待办,输入上方内容开始吧」
切换分类后无数据 「该分类下暂无待办」提示条
加载中 列表顶部显示细进度条

六、文章小结

本篇设计了五段式页面结构:统计面板让顶部信息量充足,分段控件提供三种视图,卡片承载待办内容,底部进度条呈现完成率。整体「清新卡片式」风格统一、信息层级清晰,且每个数字、每个标签都直接对应 DAO 查询结果,为后续文章的数据填充做好了 UI 容器准备。


七、页面信息架构与状态管理详解

1. @Entry 与 @Component:入口与组件的边界

在 ArkTS 中,@Entry 标记的 struct 是页面级入口,可被 router.pushUrl() 跳转加载;@Component 则声明一个可复用的 UI 组件。本页由三层结构组成:

@Entry                       // 页面入口:可被路由加载
@Component
struct TodoPage {            // 顶层页面容器
  @State todoList: TodoItem[] = []            // 列表数据
  @State statistics: Statistics = { pending: 0, done: 0, high: 0 }
  @State currentTab: number = 0               // 0全部 1未完成 2已完成
  @State inputText: string = ''               // 输入框内容
  @State showLoading: boolean = false         // 加载中开关

  aboutToAppear() {
    this.refreshAll()        // 页面首次出现即查库
  }
  ...
}

2. @State 是声明式刷新的「信号源」

装饰器 作用 本页应用
@Entry 标记页面入口,可被 router 跳转 顶层 TodoPage
@Component 声明组件,可独立复用 TodoPage / TodoCard
@State 组件内可变状态,变化即触发 UI 重建 todoList、currentTab 等
@Builder 复用一段 UI 结构 统计数字块、卡片操作按钮

核心思想是只改数据、不碰 DOM:点「完成」后只需 this.todoList[i].completed = 1,ArkUI 会自动对比新旧状态树并重绘对应卡片,开发者无需手动刷新或操作节点。

3. 从 DAO 到 UI 的数据流闭环

用户点击(新增/完成/筛选)
   ↓ 调用
TodoDao.insert() / toggleCompleted() / queryByCompleted()
   ↓ Promise 返回结果
赋值给 @State 变量(todoList / statistics)
   ↓ 状态变化
ArkUI 状态树 diff → 只重建受影响的组件

这条链路里,DAO 负责「取数」,@State 负责「存数」,ArkUI 负责「画数」,三者各司其职,页面代码因此保持简洁。

八、四大区块的 UI 实现与样式拆解

1. 标题栏:Flex 两端对齐

Row() {
  Column() {
    Text('✅ 待办清单').fontSize(22).fontWeight(FontWeight.Bold)
    Text('优先级排序 · 未完成在前').fontSize(12).fontColor('#9CA3AF')
  }.alignItems(HorizontalAlign.Start)
  Blank()                    // 弹性占位:把两侧顶开
  Button('↻').onClick(() => this.refreshAll())
}.width('100%').padding(16)

要点:Blank() 是弹性空白组件,自动占据剩余宽度,实现「左标题、右按钮」的经典顶栏;刷新按钮回调 refreshAll() 重新查库。

2. 统计面板:三等分数字卡

Row() {
  this.statBlock('未完成', this.statistics.pending, '#3B82F6')
  this.statBlock('已完成', this.statistics.done, '#22C55E')
  this.statBlock('高优先级', this.statistics.high, '#F97316')
}.padding(12)
  .backgroundColor(Color.White)
  .borderRadius(12)
  .shadow({ radius: 6, color: 'rgba(0,0,0,0.06)' })

三个数字块用 @Builder 抽成公共方法,保证样式完全一致:

@Builder
statBlock(label: string, value: number, color: string) {
  Column() {
    Text(`${value}`).fontSize(28).fontWeight(FontWeight.Bold).fontColor(color)
    Text(label).fontSize(12).fontColor('#6B7280')
  }.layoutWeight(1).padding({ top: 12, bottom: 12 })
}

layoutWeight(1) 让三块在 Row 内均分宽度,这是「卡片三等分」的关键,也方便日后增加第四块数字。

3. 输入区:TextInput + Select + 主色按钮

控件 关键属性 说明
TextInput placeholder / onChange 待办内容输入,双向绑定 inputText
Select options 数组 分类下拉:工作/生活/学习/日常
Button backgroundColor 主色 「+ 新增」提交并清空输入框
Row({ space: 8 }) {
  TextInput({ placeholder: '输入新待办,回车或点新增…', text: this.inputText })
    .onChange(v => this.inputText = v)
    .layoutWeight(1)
  Button('+ 新增')
    .backgroundColor('#3B82F6')
    .onClick(() => this.addTodo())
}.width('100%').padding(12)

4. 列表卡片:状态驱动的行样式

ForEach(this.todoList, (item: TodoItem) => {
  TodoCard({
    item: item,
    onToggle: () => this.toggleItem(item.id),
    onDelete: () => this.confirmDelete(item.id),
    onEdit: () => this.openEditDialog(item)
  })
}, (item: TodoItem) => `${item.id}`)

已完成卡片的置灰、删除线全部由三元表达式驱动,无需写 if/else 分支去操作样式对象:

.opacity(item.completed === 1 ? 0.6 : 1)
Text(item.title)
  .decoration({ type: item.completed === 1 ? TextDecorationType.LineThrough : TextDecorationType.None })
  .fontColor(item.completed === 1 ? '#9CA3AF' : '#1F2937')

九、状态筛选与交互设计

1. 点击筛选:分段控件联动统计

Tabs({ barPosition: BarPosition.Start }) {
  TabContent() { this.listByState(0) }.tabBar('全部')
  TabContent() { this.listByState(1) }.tabBar('未完成')
  TabContent() { this.listByState(2) }.tabBar('已完成')
}
.onChange(async index => {
  this.currentTab = index
  this.todoList = await TodoDao.queryByCompleted(
    this.context, index === 0 ? -1 : index - 1)   // -1 表示不过滤
  this.statistics = await TodoDao.statistics(this.context)  // 统计联动
})

切换 Tab 触发重新查库,列表与统计卡同步刷新,形成「筛选 → 查询 → 渲染」的完整闭环,保证任何视图下数字都真实可信。

2. 删除确认:AlertDialog 二次确认

AlertDialog.show({
  title: '确认删除',
  message: '删除后不可恢复,确定删除这条待办吗?',
  primaryButton: {
    value: '取消',
    action: () => {}
  },
  secondaryButton: {
    value: '删除',
    fontColor: '#EF4444',
    action: () => this.deleteItem(pendingId)
  }
})

删除是破坏性操作,二次确认可避免误触;「清空已完成」按钮同样复用该弹窗,只是 message 改为「将删除全部 N 条已完成待办」。

十、ArkTS 声明式开发的注意事项

1. ForEach 的 key 必须唯一且稳定

key 生成写法 是否推荐 原因
(item) => item.id ✅ 推荐 主键唯一、不随渲染变化,增删不错位
(item) => item.title ⚠️ 谨慎 内容可重复、可编辑,复用会错乱
(item, index) => index ❌ 避免 增删后 index 漂移,动画与状态全乱

2. 事件绑定传参要用箭头函数

// ❌ 错误:render 时就立即执行了 deleteItem
.onClick(this.deleteItem(item.id))
// ✅ 正确:箭头函数延迟到点击时才执行
.onClick(() => this.deleteItem(item.id))

3. 状态驱动刷新:改「数据」而非改「样式」

  • 数组整体替换:this.todoList = newList,触发全量 diff(推荐)
  • 单项更新:先 splice(i, 1, newItem) 整体替换该下标元素
  • 禁止直接改 @State 数组元素属性后不赋值——ArkUI 无法感知对象内部的细粒度变化

十一、FAQ 常见问题

Q1:统计数字刷新了,列表却没变,为什么?
A:多半是列表用了另一个数组(如临时变量)赋值,或 queryByCompleted 返回新数组后没有整体赋给 todoList。检查 @State 变量是否被真正替换。

Q2:删除卡片后动画错乱、行内容串位?
A:几乎都是 ForEach 的 key 用了 index。改用 item.id 后,ArkUI 才能正确识别「谁被删、谁保留」。

Q3:@State 里存对象,改了对象属性页面不刷新?
A:需要整体替换对象引用(如 this.statistics = { ...this.statistics, done: n }),或将高频变化的字段拆成独立 @State。

Q4:如何让加载进度条只在查询期间显示?
A:用 @State showLoading,在 await 查询前置 true、拿到结果后置 false,配合 if (this.showLoading) { LoadingProgress() } 条件渲染即可。

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐