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

鸿蒙 ArkUI 数据采集表格组件实现详解:DataTable 行内编辑、增删行与批量提交

摘要:本文以 HarmonyOS ArkUI 框架为基础,详细阐述如何实现一个具备 DataTable 风格的行内编辑数据采集页面。涵盖从数据模型设计、表格布局搭建、TextInput 行内编辑绑定、动态行增删操作到批量提交与数据校验的完整技术方案,并深入分析 ArkUI 声明式语法、状态管理、组件化 @Builder 模式及路由导航等核心知识点。全文约 10,000 字,适合有一定 ArkTS 基础的开发者阅读。


一、引言

1.1 背景与需求

在企业级移动应用开发中,数据采集 是最常见、最高频的业务场景之一。无论是员工信息登记、客户回访记录、巡检数据填报,还是库存盘点、物流单录入,核心交互模式高度一致:用户需要在一个表格结构中,逐行编辑多个字段,支持灵活地增加或删除行,最后将整批数据一次性提交到后端。

在 Flutter 生态中,DataTable + TextFormField 的组合是解决此类需求的经典方案。而在华为鸿蒙生态中,ArkUI(声明式 UI 框架)同样提供了强有力的基础设施——TextInputButtonForEachScrollAlertDialog 等组件,配合 @State@Builder 装饰器,完全可以实现同等甚至更优的交互体验。

1.2 本文目标

本文将带领读者从零开始,构建一个完整的鸿蒙数据采集页面,核心功能包括:

  1. 表格结构布局——使用 Row + Column 组合模拟 DataTable 的网格结构,包含序号、姓名、年龄、电话、地址、操作六列。
  2. 行内编辑——每个数据单元格使用 TextInput 组件,支持 placeholder 提示、输入类型限制(数字键盘、电话键盘)。
  3. 动态行管理——通过「新增行」按钮追加空行,通过每行末尾的「-」按钮删除当前行,至少保留一行。
  4. 批量提交与校验——过滤全空行,通过 AlertDialog 展示提交数据的 JSON 结果,确认后重置表格。
  5. 路由导航——从首页跳转到数据采集页,并支持返回。

1.3 技术选型与版本

项目 版本 / 规格
开发框架 HarmonyOS ArkUI(声明式)
开发语言 ArkTS(API 26, Stage 模型)
兼容 SDK 6.1.1 (24)
目标 SDK 26.0.0
IDE DevEco Studio
构建工具 hvigor 6.26.1

二、项目结构与准备工作

2.1 项目目录概览

在开始编码前,我们先梳理项目结构。开发环境为 DevEco Studio,项目类型为 HarmonyOS Application(Stage 模型),项目名为 MyApplication65

MyApplication65/
├── AppScope/
│   └── app.json5                 # 应用全局配置
├── entry/
│   ├── build-profile.json5        # 模块构建配置
│   ├── src/
│   │   ├── main/
│   │   │   ├── ets/
│   │   │   │   ├── entryability/
│   │   │   │   │   └── EntryAbility.ets  # 应用入口 Ability
│   │   │   │   └── pages/
│   │   │   │       ├── Index.ets          # 首页(入口页面)
│   │   │   │       └── DataTablePage.ets  # ★ 数据采集页面(核心)
│   │   │   ├── module.json5               # 模块清单文件
│   │   │   └── resources/
│   │   │       └── base/
│   │   │           └── profile/
│   │   │               └── main_pages.json # 页面路由配置
│   ├── oh-package.json5
│   └── ...
├── build-profile.json5           # 全局构建配置
└── hvigorfile.ts                 # 构建脚本入口

2.2 页面路由注册

在 HarmonyOS Stage 模型中,所有可被路由导航的页面必须在 main_pages.json 中注册。我们在已有 pages/Index 的基础上,新增 pages/DataTablePage

{
  "src": [
    "pages/Index",
    "pages/DataTablePage"
  ]
}

这是非常重要的一步——如果页面未在此处注册,运行时调用 router.pushUrl() 将因找不到目标页面而报错。

2.3 首页入口设计

首页 Index.ets 作为整个应用的启动页面,功能简洁:展示项目标题、副标题说明,以及一个跳转到数据采集页的按钮。用户点击按钮后,通过 getUIContext().getRouter().pushUrl() 方法导航到 DataTablePage

import { router } from '@kit.ArkUI';

@Entry
@Component
struct Index {
  build() {
    Column() {
      Text('数据采集示例')
        .fontSize(28).fontWeight(FontWeight.Bold)
        .fontColor('#1a1a2e')
        .margin({ top: '35%', bottom: 8 })

      Text('DataTable + TextInput 行内编辑 · 增删行 · 批量提交')
        .fontSize(14).fontColor('#666')
        .margin({ bottom: 60 })

      Button() {
        Text('📋 打开数据录入表')
          .fontSize(18).fontColor('#fff').fontWeight(FontWeight.Bold)
      }
      .height(52).width(220).backgroundColor('#1890ff')
      .borderRadius(26)
      .onClick(() => {
        this.getUIContext().getRouter().pushUrl({ url: 'pages/DataTablePage' });
      })
    }
    .width('100%').height('100%')
    .alignItems(HorizontalAlign.Center)
    .backgroundColor('#f5f5f5')
  }
}

这里我们采用 Column + alignItems 居中布局替换了原先的 RelativeContainer + alignRules,原因是经过验证,在 SDK 6.1.1(24) 环境下 RelativeContaineralignRules 类型约束与当前 API 版本存在兼容性问题(编译时报类型不匹配错误),而 Column 布局更加简洁可靠,且对初学者更友好。


三、数据模型设计

3.1 定义 DataRow 接口

数据采集的核心是数据行。每个行代表一条采集记录,包含若干个字段。在 ArkTS 中,我们使用 interface 定义数据结构:

interface DataRow {
  id: number;      // 唯一标识,用于 ForEach 的 key
  name: string;    // 姓名
  age: string;     // 年龄(用 string 以便与 TextInput 双向绑定)
  phone: string;   // 电话号码
  address: string; // 地址
}

这里有一个值得注意的设计决策:age 字段虽然是数值含义,但我们在接口中定义为 string 类型。原因在于 ArkUI 的 TextInput 组件的 text 属性和 onChange 回调都基于字符串类型。如果定义为 number,则需要额外的 parseInt / toString 转换处理。对于数据采集这种临时数据录入场景,保持字符串类型更简洁,最终提交时再做类型转换即可。

3.2 状态托管

@Component 中,我们使用 @State 装饰器管理数据行列表和 ID 自增计数器:

@Component
struct DataTablePage {
  @State private dataRows: DataRow[] = [];
  @State private nextId: number = 1;
}

@State 是 ArkUI 声明式编程的核心——当被装饰的变量发生变化时,框架会自动触发组件树的局部重建(Re-render),无需手动操作 DOM。这是 ArkUI 与 React / Flutter 一致的响应式编程范式。

3.3 初始化数据

在组件的 aboutToAppear 生命周期回调中,我们预先加载三行空数据,让用户一进入页面即可开始编辑,避免空白页面的突兀感:

aboutToAppear(): void {
  this.addRow();
  this.addRow();
  this.addRow();
}

aboutToAppear 类似于 Flutter 的 initState 或 Vue 的 created,在组件即将挂载到视图树时触发,是执行初始化逻辑的标准位置。


四、表格布局实现

4.1 整体布局结构

数据采集页面的整体布局从上到下分为四个区域:

┌─────────────────────────────────────┐
│  顶部标题栏                          │  ← Row + Button(返回)
├─────────────────────────────────────┤
│  操作按钮栏:新增行 | 行数统计 | 清空 │  ← Row + Button
├─────────────────────────────────────┤
│  表格区域(可滚动):                 │
│  ┌───┬─────┬────┬──────┬──────┬──┐ │  ← Scroll → Column
│  │ # │姓名 │年龄│ 电话 │ 地址 │操作│ │      → ForEach(dataRows)
│  ├───┼─────┼────┼──────┼──────┼──┤ │
│  │ 1 │[...]│[...]│[...]│[...]│[-]│ │
│  │ 2 │[...]│[...]│[...]│[...]│[-]│ │
│  └───┴─────┴────┴──────┴──────┴──┘ │
├─────────────────────────────────────┤
│  底部提交区:统计信息 | 提交按钮      │  ← Row + Button
└─────────────────────────────────────┘

代码实现中,整个页面是一个垂直 Column,通过 layoutWeight(1) 分配表格区域占据剩余空间:

build() {
  Column() {
    // 1. 顶部标题栏
    this.buildTopBar()

    // 2. 操作按钮栏
    this.buildActionBar()

    // 3. 可滚动表格区域
    Scroll() {
      Column() {
        this.buildTableHeader()
        Divider()
        ForEach(this.dataRows, (row, index) => {
          this.buildDataRow(row, index)
        }, (row) => row.id.toString())
      }
    }
    .layoutWeight(1)  // 占据所有剩余高度

    // 4. 底部提交区
    this.buildBottomBar()
  }
  .width('100%').height('100%')
  .backgroundColor('#f5f5f5')
}

4.2 表头构建 (@Builder)

ArkUI 提供了 @Builder 装饰器,用于封装可复用的 UI 片段。与普通函数不同,@Builder 内部可以编写完整的组件声明代码,并且可以在 build() 方法中直接调用。

我们将表头封装为一个 @Builder 方法:

@Builder
buildTableHeader() {
  Row() {
    Text(' #')      .width(40)  .textAlign(TextAlign.Center)
    Text('姓  名')  .layoutWeight(1.5).textAlign(TextAlign.Center)
    Text('年  龄')  .layoutWeight(1)  .textAlign(TextAlign.Center)
    Text('电  话')  .layoutWeight(2)  .textAlign(TextAlign.Center)
    Text('地  址')  .layoutWeight(2)  .textAlign(TextAlign.Center)
    Text('操作')    .width(50)  .textAlign(TextAlign.Center)
  }
  .width('100%').height(40)
  .backgroundColor('#eef2f7')
  .borderRadius({ topLeft: 8, topRight: 8 })
}

布局策略说明:

分配方式 说明
序号 width(40) 固定宽度,仅显示行号
姓名 layoutWeight(1.5) 相对权重,占用较多空间
年龄 layoutWeight(1) 字段较短,权重较小
电话 layoutWeight(2) 需要容纳 11 位手机号
地址 layoutWeight(2) 字段最长,权重与电话一致
操作 width(50) 固定宽度,容纳删除按钮

layoutWeight 是 ArkUI Row 布局中非常有用的属性——它按权重比例分配行内的剩余空间(即总宽度减去所有固定宽度列后的余量)。这与 CSS Flexbox 的 flex-grow 或 Flutter 的 Expanded / Flexible 原理一致。

4.3 数据行构建

每一行数据使用 @Builder 封装,包含序号显示和四个 TextInput 编辑框:

@Builder
buildDataRow(row: DataRow, index: number) {
  Row() {
    // 序号
    Text(`${index + 1}`)
      .fontSize(13).fontColor('#999')
      .width(40).textAlign(TextAlign.Center)

    // 姓名
    TextInput({ placeholder: '请输入姓名', text: row.name })
      .layoutWeight(1.5).height(38)
      .backgroundColor('#f9f9f9')
      .borderRadius(4)
      .border({ width: 1, color: '#e8e8e8' })
      .onChange((value: string) => { row.name = value; })

    // 年龄(数字键盘)
    TextInput({ placeholder: '年龄', text: row.age })
      .layoutWeight(1).height(38)
      .type(InputType.Number)
      .onChange((value: string) => { row.age = value; })

    // 电话(电话键盘)
    TextInput({ placeholder: '请输入电话', text: row.phone })
      .layoutWeight(2).height(38)
      .type(InputType.PhoneNumber)
      .onChange((value: string) => { row.phone = value; })

    // 地址
    TextInput({ placeholder: '请输入地址', text: row.address })
      .layoutWeight(2).height(38)
      .onChange((value: string) => { row.address = value; })

    // 删除按钮
    Button({ type: ButtonType.Circle }) {
      Text('-').fontSize(18).fontColor('#ff4d4f')
    }
    .width(32).height(32)
    .backgroundColor('#fff0f0')
    .onClick(() => this.removeRow(index))
  }
  .width('100%').height(52)
  .backgroundColor(index % 2 === 0 ? '#ffffff' : '#fafbfc')
}
4.3.1 TextInput 的关键属性
  • placeholder:水印提示文本,告诉用户此行应填写什么内容。
  • text:绑定当前字段的值。由于我们传入的是 row.name(字符串类型),TextInput 会显示该值。
  • type:指定输入键盘类型。InputType.Number 弹出纯数字键盘,InputType.PhoneNumber 弹出带 +/* 的电话键盘。
  • onChange:输入内容变化时的回调函数。这里我们直接将修改写回 row.name 等字段。
4.3.2 斑马纹实现

通过 index % 2 === 0 三元表达式,偶数行白色背景,奇数行浅灰背景,提升长表格的可读性。

4.3.3 删除按钮

使用 ButtonType.Circle 创建圆形按钮,内部显示减号「-」,背景为浅红色 #fff0f0,文字为红色 #ff4d4f,视觉上直观表明删除功能。点击时调用 this.removeRow(index)

4.4 滚动支持

当数据行数超过屏幕可见范围时,我们需要让表格区域支持垂直滚动。ArkUI 提供了 Scroll 容器组件:

Scroll() {
  Column() {
    this.buildTableHeader()
    Divider()
    ForEach(this.dataRows, ..., ...)
  }
}
.layoutWeight(1)  // 占据剩余高度
.width('100%')

Scroll 在高度受限时会自动激活滚动条,用户可以通过滑动查看更多行数据。配合 .layoutWeight(1),表格区域自适应填充顶部和底部固定栏之间的所有可用空间。


五、动态行管理

5.1 新增行

新增行的逻辑非常简单——在 dataRows 数组末尾 push 一个新的 DataRow 对象:

addRow(): void {
  this.dataRows.push({
    id: this.nextId++,
    name: '',
    age: '',
    phone: '',
    address: ''
  });
}

关键点说明:

  1. id: this.nextId++:每个新行获得一个自增的唯一 ID。这个 ID 被用作 ForEach 的 key 生成函数 (row) => row.id.toString(),帮助 ArkUI 框架高效追踪哪个行需要增/删/改,避免全量重建。
  2. 所有字段初始化为空字符串:用户进入空白行后直接输入即可。
  3. @State 自动触发渲染dataRows@State 变量,当 push 操作导致数组引用变化时,ArkUI 自动更新 UI。

5.2 删除行

删除行时,我们使用 splice 方法移除指定索引的元素,并增加保护逻辑——至少保留一行:

removeRow(index: number): void {
  if (this.dataRows.length <= 1) {
    return;  // 不允许删除最后一行
  }
  this.dataRows.splice(index, 1);
}

这个「至少保留一行」的设计是有意为之的。如果允许用户全部删除,页面将呈现一个空白表格,用户需要多一步「新增行」操作才能继续录入,体验不够流畅。保留一行空行可以让用户始终处于"可编辑"状态。

5.3 ForEach 与 key

ForEach 是 ArkUI 中最常用的列表渲染组件。它的三个参数分别是:数据源数组、item 构建函数、key 生成函数:

ForEach(
  this.dataRows,                        // 数据源
  (row: DataRow, index: number) => {    // item 构建
    this.buildDataRow(row, index);
    // 行间分隔线
    if (index < this.dataRows.length - 1) {
      Divider().height(0.5).color('#f0f0f0');
    }
  },
  (row: DataRow) => row.id.toString()   // key 生成
)

为什么 key 很重要?

当列表数据发生变化(增/删行)时,ArkUI 需要确定哪些节点是新增的、哪些是删除的、哪些可以复用。如果没有稳定的 key,框架可能不得不重建整个列表,导致性能下降甚至动画闪烁。使用 row.id(唯一且稳定的标识符)作为 key,框架可以精确地只操作变化的部分。


六、批量提交与数据校验

6.1 提交前的数据过滤

用户可能在某行中只填了部分数据,或者误新增了空行。在提交前,我们需要过滤出至少填写了一个字段的有效行:

submitData(): void {
  const validRows = this.dataRows.filter(row =>
    row.name.trim().length > 0 ||
    row.age.trim().length > 0 ||
    row.phone.trim().length > 0 ||
    row.address.trim().length > 0
  );
  // ...
}

这种按 OR 逻辑的过滤策略较为宽松——只要任何一个字段有内容,就视为有效行。在实际业务中,你可以根据需要调整为 AND 逻辑(所有字段必填),或针对特定字段设置必填规则。

6.2 无数据提示

如果过滤后有效行为空(用户确实一条都没填),弹出提示:

if (validRows.length === 0) {
  AlertDialog.show({
    title: '提示',
    message: '请至少填写一条数据后再提交',
    autoCancel: true,
    confirm: { value: '确定', action: () => {} }
  });
  return;
}

AlertDialog.show 是 ArkUI 内置的对话框 API,提供 titlemessageconfirmcancelautoCancel(点击空白区域是否自动关闭)等配置项。

6.3 提交结果展示

提交时,将有效数据序列化为 JSON 字符串展示在对话框中,方便用户确认:

AlertDialog.show({
  title: '提交成功',
  message: `共提交 ${validRows.length} 条数据\n${JSON.stringify(validRows, null, 2)}`,
  autoCancel: false,
  confirm: {
    value: '确定',
    action: () => {
      // 提交确认后清空并重置
      this.dataRows = [];
      this.nextId = 1;
      this.addRow(); this.addRow(); this.addRow();
    }
  }
});

JSON.stringify(validRows, null, 2) 生成格式化 JSON,缩进 2 个空格,让多条数据一目了然。在实际生产环境中,此处的 action 回调应替换为网络请求(HTTP POST / PUT)或数据库写入逻辑。

6.4 清空确认

为了防止误操作,清空所有数据前也需要二次确认:

clearAll(): void {
  AlertDialog.show({
    title: '确认清空',
    message: '确定要清空所有数据吗?此操作不可撤销。',
    primaryButton: { value: '取消', action: () => {} },
    confirm: { value: '清空', action: () => {
      this.dataRows = [];
      this.nextId = 1;
      this.addRow();
    }}
  });
}

这里使用了 primaryButton + confirm 双按钮配置,primaryButton 作为次要操作(取消)显示在左侧,confirm 作为主要操作(清空)显示在右侧。清空后重置为一行空数据,保持界面的一致性。


七、关键知识点深入解析

7.1 ArkUI 声明式编程范式

ArkUI 采用声明式(Declarative)UI 编程模型,这与传统的命令式(Imperative)UI 编程有本质区别:

特性 命令式 声明式
编程方式 “如何做”——手动操作 DOM “是什么”——描述目标状态
状态管理 手动查找节点、更新属性 框架自动追踪状态变化
代码量 较多(需处理增删改查) 较少(只需声明映射关系)
可预测性 较低(状态分散) 较高(UI 是状态的函数)

在 ArkUI 中,@State 变量是状态与 UI 之间的桥梁。当 @State dataRows 数组变化时,所有依赖它的组件(即 ForEach 中的行)会自动重新构建,开发者无需编写任何额外的 DOM 操作代码。

7.2 @Builder 与组件复用

@Builder 是 ArkUI 实现组件复用的核心工具。与自定义组件(@Component)不同,@Builder 更加轻量——它不需要独立的文件或类定义,直接在当前文件中声明即可使用。

适用场景:

  • 当一段 UI 结构在同一个组件中出现多次(如本文的表头 + 每一行数据)
  • 当 UI 结构依赖外部传入的参数(如 rowindex
  • 当不想为了简单的 UI 片段而创建额外组件文件时

与自定义组件的选择建议:

对比项 @Builder @Component
文件独立性 不需独立文件 通常独立文件
参数传递 函数参数 @Prop / @Link
状态独立性 共享父组件状态 独立状态管理
重用范围 当前组件及子作用域 全局可用
适用场景 简单 UI 片段 复杂的独立功能模块

7.3 ForEach 的优化技巧

ForEach 在处理大型列表时,需要注意以下优化点:

  1. 始终提供稳定的 key:使用数据库主键、UUID 或自增 ID,不要使用数组索引作为 key——索引在增删操作后会变化,导致不必要的重建。
  2. 避免在 ForEach 内使用复杂计算:将计算提前到数据准备阶段,ForEach 内只做 UI 映射。
  3. 配合 LazyForEach:对于超过 100 条的长列表,考虑使用 LazyForEach(按需懒加载),减少一次性渲染开销。LazyForEach 需要实现 IDataSource 接口。

7.4 路由导航机制

在 HarmonyOS Stage 模型中,路由导航通过 UIContext 提供的 RouterController 实现:

// 跳转到目标页面
this.getUIContext().getRouter().pushUrl({
  url: 'pages/DataTablePage'
});

// 返回上一页
this.getUIContext().getRouter().back();

路由栈的管理与 Android 的 Activity 栈类似——pushUrl 将目标页面压入栈顶,back 弹出当前页面回到上一页。每个被路由的页面必须在 main_pages.json 中注册。

关于 router.pushUrlthis.getUIContext().getRouter().pushUrl 的区别:前者是静态导入的方式(import { router } from '@kit.ArkUI'),在较新的 API 版本中已被标记为 deprecated,推荐使用后者(通过 UIContext 获取 Router 实例)以获得更好的类型安全和上下文管理。

7.5 TextInput 输入类型

ArkUI 的 TextInput 组件支持多种输入类型,通过 .type() 方法设置:

InputType 枚举 效果 适用场景
Normal 默认文本键盘 姓名、地址等
Number 纯数字键盘 年龄、数量等
PhoneNumber 电话键盘(含 +、*) 手机号
Email 带 @ 的邮箱键盘 电子邮箱
Url 带 .com 的网址键盘 URL 输入
Password 密码模式(内容隐藏) 密码输入

设置合适的输入类型可以显著提升用户的输入效率和体验,这是移动端开发中容易被忽视但非常重要的细节。


八、UI 样式与交互优化

8.1 颜色体系

页面采用了一套简洁的蓝白色系风格:

// 主色调
'#1890ff'    // 蓝色——主按钮、主操作
'#1a1a2e'    // 深蓝黑——标题文字
'#ff4d4f'    // 红色——删除、危险操作
'#f5f5f5'    // 浅灰——页面背景
'#eef2f7'    // 浅蓝灰——表头背景
'#f9f9f9'    // 白灰——输入框背景
'#e8e8e8'    // 浅灰边框——输入框边框

这种配色方案具有以下优势:

  • 清晰的信息层级:高饱和度的蓝色用于主操作按钮,红色用于破坏性操作,灰色用于辅助信息。
  • 良好的可读性:浅色背景 + 灰色边框的输入框在视觉上柔和,不易产生视觉疲劳。
  • 专业的质感应:表头的浅蓝灰背景与白色数据行形成对比,视觉上区分表头与数据区域。

8.2 阴影与圆角

在底部提交按钮上,我们添加了投影效果以营造立体感:

.shadow({
  radius: 12,
  color: '#401890ff',
  offsetX: 0,
  offsetY: 6
})

参数说明:

  • radius:阴影的模糊半径,越大阴影越柔和。
  • color:阴影颜色,#401890ff 表示带透明度的蓝色(40 = 25% 透明度)。
  • offsetX/Y:阴影偏移量,此处 Y 偏移 6px 使得按钮看起来悬浮在表面之上。

输入框和按钮都设置了 borderRadius 圆角,4px 和 18px 分别对应输入框的小圆角和按钮的胶囊形圆角,符合 Material Design 的设计语言。

8.3 交互反馈

页面中的交互反馈主要通过三方面实现:

  1. AlertDialog 弹窗:清空操作、空数据提交、成功提交都有对应的弹窗提示,让用户清楚当前操作的结果。
  2. 按钮视觉反馈stateEffect: true 属性让按钮在按下时有反馈效果(颜色变深)。
  3. 输入框边框:点击输入框时自动获得焦点,并显示光标,符合用户的编辑预期。

九、扩展与优化建议

9.1 表单字段校验

当前实现的校验仅过滤了全空行。在实际生产场景中,通常需要更精细的校验:

function validateRow(row: DataRow): string | null {
  if (row.name.trim().length === 0) return '姓名不能为空';
  if (row.name.trim().length < 2) return '姓名至少2个字符';
  const age = parseInt(row.age);
  if (isNaN(age) || age < 0 || age > 150) return '年龄不合法(0-150)';
  if (!/^1[3-9]\d{9}$/.test(row.phone.trim())) return '手机号格式不正确';
  if (row.address.trim().length === 0) return '地址不能为空';
  return null; // 校验通过
}

submitData 中逐行校验,如果有任一行校验不通过,则定位到该行并提示具体错误信息。

9.2 大数据量性能优化

当数据行数超过 100 行时,可以考虑以下优化:

  1. 使用 LazyForEach 替代 ForEachLazyForEach 按需创建和销毁节点,大幅降低内存占用。
  2. 虚拟滚动:只渲染可见区域内的行,配合 Scroll 的事件动态调整渲染范围。
  3. 分批提交:对于数百条数据,将提交请求拆分为多个较小的批次,避免单次网络请求超时。

9.3 网络请求集成

将模拟提交替换为实际的网络请求:

import { http } from '@kit.NetworkingKit';

async function submitToServer(rows: DataRow[]): Promise<boolean> {
  const req = http.createHttp();
  const result = await req.request('https://api.example.com/data/batch', {
    method: http.RequestMethod.POST,
    header: { 'Content-Type': 'application/json' },
    extraData: JSON.stringify(rows)
  });
  req.destroy();
  return result.responseCode === 200;
}

使用 @kit.NetworkingKithttp 模块发送 POST 请求。注意在异步回调中处理加载状态和错误提示。

9.4 本地持久化

对于离线场景,可以使用 @kit.StorageKit 或关系型数据库(RDB)实现本地存储:

import { preferences } from '@kit.StorageKit';

async function saveToLocal(rows: DataRow[]): Promise<void> {
  const pref = await preferences.getPreferences(this.context, 'data_store');
  await pref.put('pending_rows', JSON.stringify(rows));
  await pref.flush();
}

将待提交数据缓存到本地,用户下次打开应用时可以继续编辑或提交。

9.5 自定义列配置

让用户或管理员动态配置表格的列字段,而不是硬编码为固定的四列:

interface ColumnConfig {
  key: string;
  label: string;
  inputType: InputType;
  width: number | string; // 固定宽度或 layoutWeight
  required: boolean;
}

通过传入 ColumnConfig[] 数组,可以动态渲染任意数量和类型的列,提升组件的通用性和复用性。

9.6 导入导出功能

为采集的数据提供 Excel / CSV 导出功能,方便用户进行二次分析。HarmonyOS 提供了 @ohos.file.picker 实现文件保存到本地:

import { picker } from '@kit.CoreFileKit';

async function exportToCsv(rows: DataRow[]): Promise<void> {
  const csvContent = rows.map(r =>
    `${r.name},${r.age},${r.phone},${r.address}`
  ).join('\n');
  // 使用文件保存选择器导出
}

十、总结

10.1 核心要点回顾

本文通过一个完整的数据采集表格组件实现,系统性地介绍了以下核心知识点:

  1. ArkUI 声明式编程@State 状态管理 + ForEach 列表渲染 + @Builder 组件复用。
  2. DataTable 风格布局:使用 Row + layoutWeight 实现等比例列分配,配合 Scroll 支持滚动。
  3. 行内编辑TextInput 配合 type() 指定输入类型,onChange 实现数据双向绑定。
  4. 动态行管理:增行(push)、删行(splice)、Key 优化、至少保留一行保护。
  5. 批量提交:空行过滤、JSON 序列化展示、AlertDialog 结果确认。
  6. 路由导航UIContext.getRouter() 实现页面跳转与返回。

10.2 与 Flutter DataTable 的对比

对比维度 Flutter DataTable ArkUI 实现方案
表格组件 DataTable(内置) Row + Column 组合
编辑组件 TextFormField TextInput
状态管理 setState + StatefulWidget @State 装饰器
列表渲染 ListView.builder ForEach / LazyForEach
组件复用 Widget 类 @Builder 装饰器
路由导航 Navigator.push UIContext.getRouter().pushUrl

可以看出,ArkUI 在概念上高度对标 Flutter 的核心抽象,但在实现细节和 API 风格上有其自身特点。对于有 Flutter 开发经验的开发者来说,上手 ArkUI 的迁移成本较低;对于 ArkUI 原生开发者来说,本文的方案完全基于官方 API 实现,无需引入任何第三方库。

10.3 适用场景

该组件方案适用于以下数据采集场景:

  • 员工/人员信息登记:多行人员信息批量录入
  • 设备巡检记录:每个巡检点的多字段数据采集
  • 库存盘点:商品条码、数量、位置等字段的行编辑
  • 调查问卷/反馈收集:多行非标数据录入
  • 订单批量录入:商品名称、数量、价格的表格编辑

10.4 下一步学习路径

如果您希望在此基础上进一步深入学习 HarmonyOS ArkUI 开发,建议按以下路线进行:

  1. 动画与过渡:学习 animateTotransition@Animatable,为增删行添加平滑动画。
  2. 自定义组件封装:将表格组件独立为 @Component,支持通过 @Prop 配置列配置和校验规则。
  3. 数据层集成:学习 @kit.NetworkingKit 的 HTTP 请求和 @kit.StorageKit 的本地持久化。
  4. 状态管理进阶:了解 @Link@Prop@Provide / @Consume 等装饰器,实现跨组件状态共享。
  5. 性能优化:掌握 LazyForEach@Recycle@Monitor 在长列表和复杂场景下的应用。

附录

A. 完整代码

完整代码已存储于项目的以下文件中:

  • entry/src/main/ets/pages/DataTablePage.ets — 数据采集核心页面(约 390 行)
  • entry/src/main/ets/pages/Index.ets — 首页入口(约 44 行)
  • entry/src/main/resources/base/profile/main_pages.json — 路由注册

B. 构建验证

项目已在以下环境中通过编译验证:

BUILD SUCCESSFUL in ~2s
—— CompileArkTS: 0 errors, 6 warnings (deprecated API)
—— PackageHap: 完成无签名 HAP 打包

注意:运行时需配置签名(build-profile.json5 中的 signingConfigs),或在 DevEco Studio 中使用自动签名功能。

C. 参考资源


Logo

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

更多推荐