【OpenHarmony/HarmonyOs 】最多选择 5 个:ArkUI 快捷入口管理弹窗完整实现

前言

快捷入口的产品目标是让用户从几十个收藏中挑出最常用的少数站点。它看似是多选列表,实际包含选择上限、取消选择、失效数据过滤、持久化和模态层交互。本文拆解 LinkOS 链界首页的快捷入口管理功能。⚡

一、用 ID 集合保存选择

@State showQuickEntrySheet: boolean = false;
@State quickEntrySelectedIds: string[] = [];

private isInQuickEntry(id: string): boolean {
  return this.quickEntrySelectedIds.includes(id);
}

只保存 ID,而不是复制完整 UrlItem。网址标题或图标修改后,快捷入口仍能从最新数据源解析,不会出现两份对象内容不一致。

二、打开模态层

Button('快捷入口')
  .onClick(() => this.showQuickEntrySheet = true)

if (this.showQuickEntrySheet) {
  this.QuickEntrySheet()
}

页面根节点使用 Stack,Sheet 位于内容上方。遮罩层覆盖全屏并支持点击关闭:

Stack() {
  Rect()
    .width('100%')
    .height('100%')
    .fill('rgba(0,0,0,0.35)')
    .onClick(() => this.showQuickEntrySheet = false)

  Column() { /* 选择面板 */ }
    .backgroundColor(Color.White)
    .borderRadius(18)
}
.zIndex(200)

点击面板内部不能冒泡关闭;正式组件还要处理系统返回键、焦点锁定和屏幕旋转。

三、选择与取消选择

.onClick(() => {
  const id = item.id;
  if (this.isInQuickEntry(id)) {
    this.quickEntrySelectedIds =
      this.quickEntrySelectedIds.filter(value => value !== id);
  } else if (this.quickEntrySelectedIds.length < 5) {
    this.quickEntrySelectedIds = [...this.quickEntrySelectedIds, id];
  }
})

取消时使用 filter,新选中时使用展开运算符创建新数组,从而可靠触发 @State 更新。业务规则很清楚:已选项目始终可以取消,未选项目只有在数量小于 5 时才能加入。

四、按钮禁用逻辑

.enabled(
  this.isInQuickEntry(item.id) ||
  this.quickEntrySelectedIds.length < 5
)

达到上限后,未选按钮禁用,但已选按钮仍可点击取消。若简单写成 length < 5,第五个项目选中后连取消按钮也会全部失效,这是多选上限中常见的逻辑错误。

界面还应显示当前进度:

Text(`已选择 ${this.quickEntrySelectedIds.length}/5`)

五、确认后持久化

await storage.put(
  StorageKeys.QUICK_ENTRY_URL_IDS,
  JSON.stringify(this.quickEntrySelectedIds)
);
this.showQuickEntrySheet = false;

Preferences 不直接支持字符串数组,因此使用 JSON。读取时不能直接信任解析结果:

const raw = await storage.get(StorageKeys.QUICK_ENTRY_URL_IDS, '[]') as string;
try {
  const parsed = JSON.parse(raw) as Object;
  if (Array.isArray(parsed)) {
    this.quickEntrySelectedIds = parsed.filter(
      value => typeof value === 'string'
    ).slice(0, 5) as string[];
  }
} catch {
  this.quickEntrySelectedIds = [];
}

slice(0, 5) 可以防御旧版本或异常数据超过上限。

六、保存和取消的语义

如果用户在 Sheet 中点了几次选择,然后点击遮罩“取消”,当前实现已经修改了真实 @State,更改仍可能保留。更严格的交互应维护草稿:

@State quickEntryDraftIds: string[] = [];

private openQuickEntrySheet(): void {
  this.quickEntryDraftIds = [...this.quickEntrySelectedIds];
  this.showQuickEntrySheet = true;
}

所有操作修改 Draft;点击保存才覆盖正式状态,取消则直接丢弃草稿。这样“取消”才真正没有副作用。

七、处理已经删除的网站

用户删除收藏后,Preferences 中可能仍保存对应 ID。恢复时应与当前站点集合取交集:

const validIds = new Set(this.getAllSites().map(item => item.id));
this.quickEntrySelectedIds = storedIds
  .filter(id => validIds.has(id))
  .slice(0, 5);

否则首页可能出现空白入口,计数也会显示错误。删除网址时也可以主动同步清理快捷 ID。

八、顺序问题

数组天然保存选择顺序,但用户可能希望拖动排序。可将选择结果定义为有序 ID 列表,渲染时按 ID 顺序查找对象。未来加入拖拽只需调整数组顺序,不需要修改数据模型。

九、完整状态清单

  • 尚未选择:保存按钮禁用还是允许清空?需要产品明确;
  • 选择 1 至 4 个:可继续选择;
  • 已选 5 个:未选项禁用,已选项可取消;
  • 收藏为空:展示空状态和添加入口;
  • 某条已删除:自动清除失效 ID;
  • JSON 损坏:回退为空;
  • 保存失败:面板不应直接关闭,应允许重试。

十、总结

快捷入口管理的核心是“有序 ID 集合 + 上限规则 + 草稿提交 + 数据清洗”。特别要注意达到上限后仍允许取消、关闭弹窗不应意外保存,以及删除收藏后清除失效 ID。把这些边界处理完整,一个普通多选面板才会变成可靠的产品功能。✅

img

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐