鸿蒙 ArkTS 实战:Customer Followup Book 从业务状态到页面交互完整解析

前言

客户跟进簿 是一个面向 团队协作效率 的鸿蒙 ArkTS 应用。管理客户阶段、跟进日志、今日提醒和阶段推进,适合销售跟进场景。 本文基于项目中的 entry/src/main/ets/pages/Index.ets 展开,拆解它的 数据接口响应式状态业务方法Builder 组件 和页面布局,让读者能从源码层面理解这个小应用的实现路径。

在这里插入图片描述

图示说明:页面以办公场景为背景,通过输入框、筛选按钮、状态卡片和列表卡片完成核心操作闭环。

一、应用定位与业务闭环

1.1 场景定位

Customer Followup Book 的价值在于把原本分散在表格、聊天记录或纸质便签里的信息,收拢到一个可操作的单页应用中。用户打开页面后可以查看统计、录入数据、切换状态,并通过列表看到最新结果。

业务环节 页面表达 ArkTS 实现点
数据录入 输入框和按钮 TextInput、Button
状态展示 顶部统计和标签 @State 派生计算
列表管理 卡片式条目 ForEach + Builder
状态流转 完成、推进、筛选 private 方法
反馈输出 文案、颜色、消息 条件渲染

1.2 文章拆解重点

本文按照源码结构拆解:

  1. 先看工程入口和页面骨架。
  2. 再看 interface 如何约束业务数据。
  3. 接着分析 @State 如何驱动页面刷新。
  4. 最后看方法和 Builder 如何组合成完整交互。

核心观点:单页 ArkTS 应用的质量,主要取决于数据模型是否清晰、状态更新是否可追踪、UI 片段是否有明确边界。

二、工程结构与入口页面

2.1 目录位置

核心代码位于 entry/src/main/ets/pages/Index.ets。这个文件承载页面入口、数据声明、业务方法和 UI 组件。

Customer Followup Book
├── AppScope
├── entry
│   └── src
│       └── main
│           ├── ets
│           │   └── pages
│           │       └── Index.ets
│           └── module.json5
├── hvigor
├── build-profile.json5
└── oh-package.json5

2.2 页面入口结构

ArkTS 页面使用 @Entry 和 @Component 标记入口组件。uild() 方法描述页面树。

@Entry
@Component
struct Index {
  build() {
    Scroll() {
      Column() {
        // header
        // form
        // list
      }
    }
    .height('100%')
    .width('100%')
  }
}

2.3 技术构成

技术点 作用 在本项目中的价值
ArkTS 类型与业务逻辑 定义接口、方法、状态
ArkUI 声明式 UI 构建卡片和列表
@State 响应式状态 驱动页面更新
@Builder UI 片段复用 降低重复布局
Hvigor 构建工具 生成 HAP 包

三、数据接口设计

3.1 接口清单

源码中定义的接口包括:$iface。这些接口是页面业务的基础。

interface CustomerRecord {
  id: number;
  title?: string;
  name?: string;
  status?: string;
  done?: boolean;
}

实际源码会根据业务使用更具体的字段,例如客户阶段、任务优先级、报销金额、模板类别、风险等级等。接口让每条记录的结构稳定,避免列表渲染时字段不一致。

3.2 字段分层

字段类型 常见字段 用途
标识字段 id 作为列表 key 和操作目标
展示字段 itle、
ame 显示主标题
分类字段 status、 ype、category 筛选和标签颜色
数值字段 mount、progress、score 统计和进度展示
布尔字段 done、 avorite、enabled 控制开关状态

3.3 为什么先建模

对于办公工具类应用,数据字段往往比页面样式更重要。先定义接口,可以让新增、过滤、推进、导出等方法都围绕同一个结构展开。

四、响应式状态拆解

4.1 状态清单

项目中的 @State 声明包括:$state。

@State records: CustomerRecord[] = [];
@State draftText: string = '';
@State filter: string = '全部';
@State message: string = '';

这些状态可以分成列表状态、输入状态、筛选状态和反馈状态。页面每次交互,最终都会落到这些状态的变化上。

4.2 状态分类表

状态类别 作用 示例
列表状态 保存业务数据 $primaryState
输入状态 接收用户填写内容 draft、keyword、mountDraft
筛选状态 控制当前视图 ilter、selectedStatus
消息状态 输出操作结果 message、exportMessage
游标状态 标记当前条目 cursor、ctiveCustomerId

4.3 状态更新方式

列表更新通常使用新数组替换旧数组,保持响应式刷新稳定。

private updateRecord(id: number): void {
  this.records = this.records.map((item: CustomerRecord) => {
    if (item.id !== id) {
      return item;
    }
    return {
      ...item,
      done: !item.done
    };
  });
}

五、核心方法解析

5.1 方法清单

源码中的核心方法包括:$method。这些方法决定了页面能做什么。

方法类型 典型职责 结果
统计方法 计算数量、金额、进度、平均值 顶部数据卡刷新
新增方法 从输入状态创建新记录 列表新增
流转方法 改变状态、阶段、进度 卡片文案变化
筛选方法 根据类别或关键词过滤 列表范围变化
输出方法 生成导出消息或预览内容 反馈区变化

5.2 新增记录

新增方法一般先读取输入状态,去除空白,再构造对象并插入列表顶部。

private addRecord(): void {
  const title = this.draftText.trim();
  if (title.length === 0) {
    return;
  }
  const id = this.records.reduce((maxId: number, item: CustomerRecord) => Math.max(maxId, item.id), 0) + 1;
  const record: CustomerRecord = {
    id: id,
    title: title,
    done: false
  };
  this.records = [record, ...this.records];
  this.draftText = '';
}

5.3 状态推进

状态推进类方法通常使用 map 保持不可变更新风格。

private nextStatus(id: number): void {
  this.records = this.records.map((item: CustomerRecord) => {
    if (item.id !== id) {
      return item;
    }
    return {
      ...item,
      status: '下一阶段'
    };
  });
}

5.4 筛选列表

筛选方法把状态与列表结合,形成当前可见数据。

private visibleRecords(): CustomerRecord[] {
  return this.records.filter((item: CustomerRecord) => {
    return this.filter === '全部' || item.status === this.filter;
  });
}

六、Builder 组件设计

6.1 Builder 清单

源码中的 Builder 组件包括:$builder。Builder 用于封装重复 UI,例如标签页、卡片、列表行、栏目区域。

@Builder
private card(item: CustomerRecord) {
  Column() {
    Text(item.title ?? item.name ?? '未命名')
      .fontSize(16)
      .fontWeight(FontWeight.Bold)
  }
  .padding(14)
  .backgroundColor('#FFFFFF')
  .borderRadius(8)
}

6.2 复用边界

UI 片段 适合放入 Builder 的原因
状态标签 选中态、颜色、点击动作重复
列表卡片 每条记录结构一致
统计卡 数值和标题样式一致
输入区块 多字段录入布局稳定

6.3 Builder 与方法分工

Builder 负责渲染,private 方法负责业务变化。这样代码阅读时可以快速区分“页面长什么样”和“点击后发生什么”。

七、页面布局结构

7.1 纵向滚动布局

办公工具通常信息较多,采用 Scroll + Column 更稳妥。

Scroll() {
  Column() {
    this.headerView()
    this.formView()
    ForEach(this.visibleRecords(), (item: CustomerRecord) => {
      this.card(item)
    }, (item: CustomerRecord) => item.id.toString())
  }
  .padding(16)
}
.backgroundColor('#F5F7FA')

7.2 页面分区

区域 功能 用户关注点
顶部概览 标题、说明、统计 当前整体状态
输入区域 新增或编辑信息 快速录入
筛选区域 状态、类别、关键词 缩小范围
列表区域 业务条目 执行操作
反馈区域 导出、提示、预览 操作结果

7.3 列表 key

使用 id.toString() 作为 key,能让列表渲染更稳定。

ForEach(this.records, (item: CustomerRecord) => {
  this.card(item)
}, (item: CustomerRecord) => item.id.toString())

八、交互链路分析

8.1 输入到新增

输入框通过 onChange 写入草稿状态,按钮点击后调用新增方法。

TextInput({ text: this.draftText })
  .onChange((value: string) => {
    this.draftText = value;
  })

Button('添加')
  .onClick(() => {
    this.addRecord();
  })

8.2 卡片到状态流转

卡片按钮把 id 传回方法,方法再更新列表。

Button('推进')
  .onClick(() => {
    this.nextStatus(item.id);
  })

8.3 统计同步

顶部统计不单独保存结果,而是从列表派生。

private activeCount(): number {
  return this.records.filter((item: CustomerRecord) => !item.done).length;
}

关键点:统计值从源数据计算,而不是额外维护一份副本,可以减少状态不同步的问题。

九、办公场景适配细节

9.1 信息密度

Customer Followup Book 属于办公效率工具,页面应优先保证信息清楚,而不是追求复杂视觉效果。卡片中主标题、状态、辅助字段和操作按钮要保持层级明确。

9.2 颜色语义

颜色语义 适用状态
绿色 已完成、通过、安全
橙色 待处理、临近、关注
红色 风险、逾期、异常
蓝色 当前选中、主操作
灰色 辅助文本、禁用状态

9.3 文案原则

按钮文案应直接描述动作,例如添加、推进、导出、复制、预约、完成。状态文案应描述当前结果,例如待处理、已完成、进行中、已导出。

十、运行与调试

10.1 DevEco Studio 运行

# 打开项目目录
# 同步 oh-package 依赖
# 选择 entry 模块
# 连接模拟器或真机运行

10.2 Hvigor 构建

hvigorw clean
hvigorw assembleHap

10.3 调试重点

调试点 判断方式
输入是否生效 输入后状态字段是否变化
列表是否刷新 新增或推进后卡片是否更新
筛选是否正确 切换标签后数量是否变化
统计是否同步 顶部数字是否来自当前列表
按钮是否可达 点击区域是否足够明确

十一、代码可维护性

11.1 文件规模

当前 Index.ets 约 389 行,适合单页示例。后续如果继续扩展,可以把卡片、表单和统计区拆成独立组件。

11.2 命名一致性

$primaryMethod 等方法名都表达具体业务动作。方法名越贴近业务,页面代码越容易维护。

11.3 状态收敛

不要为每个展示值都创建 @State。能从列表计算出来的值,优先使用方法派生。

private summary(): number {
  return this.records.length;
}

十二、可扩展方向

12.1 本地持久化

可以把核心列表序列化到本地存储,进入页面时恢复。

interface StoredRecord {
  id: number;
  payload: string;
  updatedAt: number;
}

12.2 搜索与排序

列表较长后,可以增加关键词搜索、状态排序和时间排序。

12.3 数据导出

办公类应用常见需求是导出表格、复制模板、生成二维码或分享文本。当前项目中的消息状态和导出方法已经具备扩展基础。

十三、同类应用复用模板

13.1 最小结构

interface ItemModel {
  id: number;
  title: string;
  status: string;
}

@State items: ItemModel[] = [];
@State draftTitle: string = '';
@State filter: string = '全部';

13.2 最小交互

private addItem(): void {
  const title = this.draftTitle.trim();
  if (title.length === 0) {
    return;
  }
  this.items = [{ id: Date.now(), title: title, status: '待处理' }, ...this.items];
  this.draftTitle = '';
}

13.3 最小展示

@Builder
private itemRow(item: ItemModel) {
  Row() {
    Text(item.title).layoutWeight(1)
    Text(item.status)
  }
  .padding(12)
  .backgroundColor('#FFFFFF')
}

十四、项目实现亮点

14.1 业务对象清晰

Customer Followup Book 使用 $iface 管理业务数据,接口字段和页面操作对应关系明确。

14.2 状态变化集中

$state 覆盖了页面主要交互状态,状态入口集中在 Index 组件内,便于定位问题。

14.3 组件复用明确

$builder 让列表和筛选区域保持一致样式,减少重复代码。

十五、总结

Customer Followup Book 展示了鸿蒙 ArkTS 办公类单页应用的典型实现方式:先用接口描述业务记录,再用 @State 保存列表、输入和筛选状态,通过 $method 完成新增、筛选、推进和统计,最后用 $builder 把数据渲染为稳定的卡片与操作区。

这类项目的关键不在于代码量,而在于状态链路是否完整。只要保证输入、状态、方法和 UI 四个环节一致,后续扩展本地存储、导出、搜索、通知或多页面跳转都可以建立在现有结构之上。

如果这篇文章对你有帮助,欢迎点赞、收藏、关注,你的支持是我持续创作的动力!


相关资源:

Logo

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

更多推荐