【鸿蒙心迹】HarmonyOS笔记28:数据库列表展示与更新
🔥 个人主页: 杨利杰YJlio
❄️ 个人专栏: 《Windows 疑难杂症与工单复盘案例库》 《Sysinternals实战教程》
《WINDOWS教程》 《Windows PowerShell 实战》 《IOS插件分析测试》
🌟 让复杂的事情更简单,让重复的工作自动化


文章目录
大家好,我是杨利杰 YJlio。
上一节整理了工单表的基础读写。这一节继续使用TicketLearn.db和TICKET表,让查询结果进入列表,再编辑一条记录的标题,保存后重新查询。
本篇编号028,计划日期2026年10月18日。原稿保留。原稿的字符串数组只是布局数据,不是数据库查询结果。下面补充同一页面文件中配套使用的学习代码,尚未在本机编译或设备运行;图片均为概念示意。
一、从静态字符串转为业务记录
原稿中的网络无法连接、打印机驱动安装可用于看布局,但标题可能重复,不适合作为业务记录的唯一身份。本篇沿用上一节的三个字段:
interface Ticket {
id: string;
title: string;
status: string;
}
数据库列ID、TITLE、STATUS分别映射到id、title、status。列表数据来自查询,不在页面初始化时伪造两条“数据库记录”。本篇也不自动插入或删除样例数据。

二、查询完成后整体替换列表状态
以下函数与后面的页面放在同一个Index.ets学习文件中。已有工程可拆成模块,但不要重复声明同名函数。
import { relationalStore } from '@kit.ArkData';
import { common } from '@kit.AbilityKit';
import { BusinessError } from '@kit.BasicServicesKit';
async function openStore(context: common.UIAbilityContext): Promise<relationalStore.RdbStore> {
const config: relationalStore.StoreConfig = {
name: 'TicketLearn.db',
securityLevel: relationalStore.SecurityLevel.S3
};
const store = await relationalStore.getRdbStore(context, config);
await store.executeSql('CREATE TABLE IF NOT EXISTS TICKET (' +
'ID TEXT PRIMARY KEY NOT NULL, TITLE TEXT NOT NULL, STATUS TEXT NOT NULL)');
return store;
}
async function loadTickets(store: relationalStore.RdbStore): Promise<Ticket[]> {
const predicates = new relationalStore.RdbPredicates('TICKET');
const result = await store.query(predicates, ['ID', 'TITLE', 'STATUS']);
try {
const rows: Ticket[] = [];
const idIndex = result.getColumnIndex('ID');
const titleIndex = result.getColumnIndex('TITLE');
const statusIndex = result.getColumnIndex('STATUS');
while (result.goToNextRow()) {
rows.push({
id: result.getString(idIndex),
title: result.getString(titleIndex),
status: result.getString(statusIndex)
});
}
return rows;
} finally {
result.close();
}
}
查询返回新数组后,再赋给页面的@State tickets。这里使用整体赋值,不直接修改数组内对象的深层属性。状态观察边界可参考官方@State文档。
表结构沿用027;IF NOT EXISTS不负责旧表迁移。首次打开的空库可能没有记录,这是正常的空表情形。

三、列表只展示查询到的字段
每行显示业务ID、标题和状态,编辑按钮只负责选择这一行的ID与标题。读取失败单独显示错误,不用空数组掩盖异常。
加载中、正常空表与加载失败要分开。已有列表刷新失败时保留旧内容,同时显示错误,让用户知道当前内容可能尚未更新。

四、本例行键包含ID与展示内容
ForEach的键用来决定子项复用。本例没有在行内保留输入框状态,编辑框放在列表外,因此选择将id、title、status组合序列化作为行键:
(ticket: Ticket) => JSON.stringify([ticket.id, ticket.title, ticket.status])
未变化的记录保持相同键;显示内容变化后,该行使用新键重建。这是本例处理简单展示行的明确选择,不能当成所有列表的通用键策略。若行内有输入、焦点或复杂组件状态,应另做稳定业务ID与子组件状态传递设计。
业务身份仍是ID;本例的行键还包含展示内容,用于决定这一行何时重建。
不要只拿标题做唯一键。两条工单可以同名,编辑标题也不应改变它们的业务ID。更多规则见官方ForEach文档。

五、编辑草稿与已加载列表分开
selectedId保存当前编辑的业务ID,draftTitle保存输入框中的标题草稿。点击编辑时复制当前标题;输入变化只修改草稿,不立即写库,也不直接改列表里的ticket.title。
这样可以区分“输入框已经修改”与“数据库已经保存”。本例保存前仍做必填和80长度限制,计数使用UTF-16码元的string.length,与024的限制口径一致。

六、按ID保存标题,再重新查询
async function saveTitle(
store: relationalStore.RdbStore,
id: string,
input: string
): Promise<number> {
const title = input.trim();
if (id.trim().length === 0 || title.length === 0 || title.length > 80) {
throw new Error('请选择记录并填写1至80长度的标题');
}
const predicates = new relationalStore.RdbPredicates('TICKET');
predicates.equalTo('ID', id);
const values: relationalStore.ValuesBucket = { TITLE: title };
return await store.update(values, predicates);
}
保存只修改匹配业务ID的TITLE,不改ID和STATUS。返回影响行数后,页面核对是否为1,再调用loadTickets读取当前记录。
本例没有把“输入框改了”当成“保存完成”,也没有用本地猜测的标题替代更新后的查询结果。

七、区分更新失败与更新后的刷新失败
页面使用busy串行处理本页面操作,按钮与输入框在处理期间禁用。它不是数据库全局锁,也没有解决其他页面或进程的并发写入。
@Entry
@Component
struct Index {
@State tickets: Ticket[] = [];
@State busy: boolean = false;
@State message: string = '';
@State selectedId: string = '';
@State draftTitle: string = '';
aboutToAppear(): void {
this.refresh();
}
private async getStore(): Promise<relationalStore.RdbStore> {
const context = this.getUIContext().getHostContext() as common.UIAbilityContext;
if (!context) {
throw new Error('无法取得页面宿主上下文');
}
return await openStore(context);
}
private async refresh(): Promise<void> {
if (this.busy) return;
this.busy = true;
this.message = '';
try {
const store = await this.getStore();
this.tickets = await loadTickets(store);
} catch (error) {
const err = error as BusinessError;
console.error(`refresh failed: ${err.code}, ${err.message}`);
this.message = '加载失败,保留原列表';
} finally {
this.busy = false;
}
}
private async save(): Promise<void> {
if (this.busy) return;
this.busy = true;
this.message = '';
let updated = false;
try {
const store = await this.getStore();
const changed = await saveTitle(store, this.selectedId, this.draftTitle);
if (changed !== 1) {
throw new Error(`影响行数异常:${changed}`);
}
updated = true;
this.tickets = await loadTickets(store);
this.draftTitle = this.draftTitle.trim();
this.message = '更新和刷新完成';
} catch (error) {
const err = error as BusinessError;
console.error(`save failed: ${err.code}, ${err.message}`);
this.message = updated ? '更新已完成,但刷新失败,请重新加载' : `更新失败:${err.message}`;
} finally {
this.busy = false;
}
}
build() {
Column({ space: 12 }) {
Button('重新加载').enabled(!this.busy).onClick(() => {
this.refresh()
})
Text(this.busy ? '处理中' : this.message)
if (!this.busy && this.message.length === 0 && this.tickets.length === 0) {
Text('暂无记录')
}
List() {
ForEach(this.tickets, (ticket: Ticket) => {
ListItem() {
Column({ space: 6 }) {
Text(`${ticket.id}|${ticket.title}|${ticket.status}`)
Button('编辑标题').enabled(!this.busy).onClick(() => {
this.selectedId = ticket.id;
this.draftTitle = ticket.title;
})
}.padding(12)
}
}, (ticket: Ticket) => JSON.stringify([ticket.id, ticket.title, ticket.status]))
}.layoutWeight(1)
if (this.selectedId.length > 0) {
Text(`编辑记录:${this.selectedId}`)
TextInput({ text: this.draftTitle, placeholder: '输入新标题' })
.enabled(!this.busy)
.onChange((value: string) => {
this.draftTitle = value
})
Button('保存标题').enabled(!this.busy).onClick(() => {
this.save()
})
}
}
.width('100%')
.height('100%')
.padding(16)
}
}
updated只在影响1行后设为true。之后查询失败,提示更新已完成但刷新失败,而不是笼统提示“保存失败”让用户反复提交。
普通Error不一定有平台错误码,要同时查看message。这里未提供持久化层的完整生命周期管理、冲突检测或事务方案,RdbStore的使用范围仍需在实际工程中规划。
写入已经完成但列表刷新失败时,要保留这个区别,不能让提示误导下一次操作。

八、按加载、编辑、保存与重启路径核验
| 场景 | 本例预期或需观察内容 | 当前状态 |
|---|---|---|
| 首次加载 | 查询后整体赋值tickets | 待验证 |
| 正常空表 | 显示暂无记录 | 待验证 |
| 查询失败 | 显示错误,保留旧列表 | 待验证 |
| 选择一条业务ID | 草稿显示该条标题,列表不立即改变 | 待验证 |
| 空白或超长输入 | 更新失败提示,不执行有效更新 | 待验证 |
| 保存合法标题 | 核对影响1行,重新查询并显示 | 待验证 |
| 匹配不到ID | 影响行数异常,不宣称成功 | 待验证 |
| 更新后查询失败 | 提示更新已完成但刷新失败 | 待验证 |
| 正常重启后只加载 | 核对保存的标题仍能查询 | 待验证 |
本篇没有自动插入数据。可在独立练习库中沿027建立自己的测试记录,再进行编辑。保留实际DevEco Studio、SDK/API、设备与完整日志,不把概念图当作运行截图。
以数据库读回值更新列表,并分别记录加载、写入和刷新阶段的结果。

写在最后
这一节我把数据库操作接到页面上:查询得到数组,数组进入状态,编辑草稿独立保存,更新按业务ID执行,成功后再查询。
下一篇学习异步函数与Promise基础,继续解释这些等待、返回与异常传播为什么影响页面结果。
参考资料
- OpenHarmony官方文档:@State装饰器。
- OpenHarmony官方文档:ForEach循环渲染。
- OpenHarmony官方文档:RdbStore接口。实际HarmonyOS工程以对应SDK资料为准。
- 本系列027数据库基础与028原稿《数据库记录列表展示与更新》,本篇计划日期2026-10-18。
声明:本文为个人学习笔记。代码与配图未经过SDK编译或设备实测,未操作真实业务数据库,运行结果需在独立练习工程核验。

更多推荐

所有评论(0)