在这里插入图片描述

一、为什么用一个“小而完整”的待办面板练习鸿蒙 ArkUI

在鸿蒙 PC 场景中,页面不是把手机界面横向拉宽即可。窗口变大后,用户通常希望同时看到分类入口、当前任务和完成进度;如果还依赖多层跳转,操作成本反而会上升。

待办面板正好覆盖一个业务页面最常见的闭环:数据建模 → 页面状态 → 分类筛选 → 列表渲染 → 用户交互 → 状态回写。示例包含以下能力:

  • 左侧显示“全部、进行中、已完成”三个筛选入口;
  • 右侧用任务卡片展示标题与完成状态;
  • 点击分类后只展示符合条件的任务;
  • 勾选复选框后,任务文本颜色和删除线随状态更新;
  • 采用“左导航 + 右内容”的宽屏布局,让页面层级保持清晰。

它不是要替代完整的任务管理产品,而是提供一个可以继续扩展的 ArkUI 页面骨架。后续接入本地持久化、网络数据、搜索和任务创建时,不需要推翻核心状态结构。

二、先确定数据边界:只保存源数据和筛选条件

每条任务只保留三个字段:唯一标识 id、展示用 title 和完成状态 done。页面状态中只维护原始任务数组 todos 与筛选条件 filter

interface TodoItem {
  id: number
  title: string
  done: boolean
}

@State todos: TodoItem[] = [
  { id: 1, title: '整理鸿蒙 PC 文章大纲', done: false },
  { id: 2, title: '运行示例并截取页面', done: false },
  { id: 3, title: '检查发布前质量分', done: true }
]
@State filter: string = '全部'

这里有一个容易被忽略的原则:不要把“全部列表”“进行中列表”“已完成列表”分别存成三份状态。 三份列表一旦同时存在,勾选任务后就必须同步修改多个位置,很容易出现计数不一致或切换分类后显示旧数据的问题。

更稳妥的做法是只保存源数据,再根据当前筛选条件推导出页面需要展示的数据:

private get visibleTodos(): TodoItem[] {
  if (this.filter === '进行中') return this.todos.filter(item => !item.done)
  if (this.filter === '已完成') return this.todos.filter(item => item.done)
  return this.todos
}

visibleTodos 是从 todosfilter 计算出来的结果,不承担额外的数据写入职责。这样无论用户先切换筛选还是先勾选任务,最终界面都从同一个源数组推导,逻辑更容易验证。

三、完整页面代码:导航、列表和交互放在同一个状态闭环里

将下面代码放入 ArkTS Stage 工程首页的 Index.ets 后,可以作为页面原型继续扩展:

interface TodoItem {
  id: number
  title: string
  done: boolean
}

@Entry
@Component
struct Index {
  @State todos: TodoItem[] = [
    { id: 1, title: '整理鸿蒙 PC 文章大纲', done: false },
    { id: 2, title: '运行示例并截取页面', done: false },
    { id: 3, title: '检查发布前质量分', done: true }
  ]
  @State filter: string = '全部'

  private get visibleTodos(): TodoItem[] {
    if (this.filter === '进行中') return this.todos.filter(item => !item.done)
    if (this.filter === '已完成') return this.todos.filter(item => item.done)
    return this.todos
  }

  build() {
    Row() {
      this.Sidebar()
      this.ContentPanel()
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#F6F8FA')
  }

  @Builder Sidebar() {
    Column({ space: 12 }) {
      Text('我的待办')
        .fontSize(24)
        .fontWeight(FontWeight.Bold)
      ForEach(['全部', '进行中', '已完成'], (item: string) => {
        Text(item)
          .width('100%')
          .padding(12)
          .borderRadius(8)
          .backgroundColor(this.filter === item ? '#DCEBFF' : '#FFFFFF')
          .onClick(() => this.filter = item)
      })
    }
    .width(180)
    .height('100%')
    .padding(20)
    .backgroundColor('#FFFFFF')
  }

  @Builder ContentPanel() {
    Column({ space: 16 }) {
      Text(this.filter)
        .fontSize(28)
        .fontWeight(FontWeight.Bold)
      Text(`${this.visibleTodos.length} 条任务`)
        .fontSize(14)
        .fontColor('#667085')
      List({ space: 10 }) {
        ForEach(this.visibleTodos, (item: TodoItem) => {
          ListItem() {
            Row({ space: 12 }) {
              Checkbox({ name: String(item.id), group: 'todos' })
                .select(item.done)
                .onChange((value: boolean) => this.toggleTodo(item.id, value))
              Text(item.title)
                .fontSize(17)
                .fontColor(item.done ? '#98A2B3' : '#1D2939')
                .decoration({ type: item.done ? TextDecorationType.LineThrough : TextDecorationType.None })
            }
            .width('100%')
            .padding(18)
            .borderRadius(12)
            .backgroundColor('#FFFFFF')
          }
        }, (item: TodoItem) => item.id)
      }
      .layoutWeight(1)
    }
    .layoutWeight(1)
    .height('100%')
    .padding({ left: 32, right: 32, top: 28, bottom: 28 })
  }

  private toggleTodo(id: number, done: boolean): void {
    this.todos = this.todos.map(item => item.id === id ? { ...item, done } : item)
  }
}

四、三个关键实现点

1. 用 @State 管理会影响界面的数据

todosfilter 都会直接影响 UI:前者决定任务卡片内容和勾选状态,后者决定展示范围。因此它们适合作为页面状态。点击分类时只需要更新 filter;列表读取 visibleTodos 后就会根据新的条件展示对应任务。

状态粒度要适中。把每一个派生结果也放入状态,不会让页面更“响应式”,反而会让同步关系变复杂。保留最少、最权威的状态,能降低维护成本。

2. 更新任务时返回新数组,而不是隐式修改旧对象

勾选复选框时,示例通过 map 生成新的任务数组:

private toggleTodo(id: number, done: boolean): void {
  this.todos = this.todos.map(item => item.id === id ? { ...item, done } : item)
}

这段代码有两个好处:第一,只有 id 相同的目标任务会被替换;第二,最终会把新的数组赋回 @State 字段,状态变化的入口非常明确。以后如果要记录修改时间、增加撤销操作或写入持久化层,也能在这个方法里集中处理。

3. 宽屏布局先保证信息层级,再考虑视觉细节

左侧导航使用固定宽度,右侧内容区通过 layoutWeight(1) 占据剩余空间。这个组合的目的不是追求复杂布局,而是让用户一眼看清三类信息:

  1. 当前位置:左侧哪个筛选项被选中;
  2. 当前范围:右侧标题与“共 X 条任务”;
  3. 可执行内容:列表中的任务和复选框。

任务卡片采用留白、圆角和弱对比背景,已完成任务降低文字对比度并加删除线。即使任务数量增加,阅读顺序也不会混乱。

五、真实运行前的验证清单

下面步骤用于得到可用于文章的真实运行截图。在未完成这些步骤前,不应把页面描述为“运行成功”。

  1. 在 DevEco Studio 创建或打开 ArkTS Stage 工程;
  2. 用本文代码替换首页 Index.ets,处理工程模板与 SDK 版本可能带来的编译提示;
  3. 将工程运行到可用的鸿蒙 PC 模拟器或真实设备;
  4. 截取完整初始布局:左侧导航、右侧标题、统计文字和三张任务卡都应可见;
  5. 点击“进行中”,确认列表只保留未完成任务,再截取筛选状态;
  6. 勾选一条未完成任务,确认任务从“进行中”列表移除,或在“全部”中呈现完成样式,再截取交互后的状态;
  7. 对照截图与本文任务文案、数量和交互说明,确认一致后再插入 CSDN 正文。

真实运行截图

在这里插入图片描述

六、可以继续扩展什么

当页面原型完成真实验证后,可以按业务优先级继续增加:

  • 创建任务:用输入框或弹窗生成新的 TodoItem
  • 搜索任务:把搜索关键字作为另一个状态,并与 filter 共同推导展示列表;
  • 持久化:把任务数组保存到本地数据层,在页面初始化时恢复;
  • 任务优先级:在数据模型中加入优先级字段,并通过颜色或排序强化信息层级;
  • 跨设备能力:在明确业务需求和数据一致性策略后,再考虑同步机制。

扩展时仍应遵守同一原则:源数据尽量单一,界面结果尽量通过源数据和当前条件推导,而不是在多个组件之间复制状态。

七、小结

这个鸿蒙 ArkUI 待办面板的重点不是功能数量,而是形成一条可复用的页面开发路径:数据建模 → 状态保存 → 派生筛选 → 列表渲染 → 交互更新 → 真实运行验证

只要保持状态来源清晰,后续无论是增加搜索、创建任务还是接入网络数据,都可以在现有结构上继续演进。发布前最后一步也不能省略:必须用真实设备或模拟器运行项目、截取与本文描述一致的画面,并把截图嵌入文章正文。

Logo

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

更多推荐