技术栈:HarmonyOS / ArkTS / ArkUI CheckboxGroup / Checkbox / 业务校验
示例项目:ConsentDesk;页面:ConsentPage;演示任务:RV-924

本文实现的是开发者自建的发布前检查页,不是华为应用市场的审核接口,也不构成任何审核通过承诺。文中的四项核验、勾选比例、模拟日志和手机界面均由演示数据生成;还没有进行真实应用商店提交或 DevEco 真机测试。

一、一键全选很方便,发布前的确认却不能只有全选

把上架前的隐私说明做成一组勾选项,好处很直接:开发者能在同一页看到缺什么、核过什么。麻烦也从这里开始。有人勾选了“第三方 SDK 清单”,但没有核对“数据共享路径”;界面如果只按多数通过给出绿色状态,就会把一个尚未确认的事项隐藏起来。反过来,关闭其中一项后,上一版生成的提交摘要如果仍然保持“全部就绪”,审核资料就与当前页不同步。

这次把目标缩到 ConsentDesk 的 ConsentPage:只检查四个固定的业务条目,分别叫“隐私政策版本确认”“权限用途映射”“第三方 SDK 清单”“数据共享路径核验”。任务 RV-924 的演示状态是前三项已勾选,最后一项未勾选,页面展示 3/4、75% 和“待补充”。“待补充”是我们自己的产品状态,不对应任何官方审核结论。

1. 最容易误读的部分选中

CheckboxGroup 官方定义的是“组内多选框的全选、取消全选以及所选名称集合”。其中 CheckboxGroupResult.name 是当次已经选中的子项名称,status 表示 All、Part 或 None。这几个状态描述的是控件选择情况,不是政策合规结论;就算用户全部勾上,也只能说明页面收到了四个布尔选择,不能证明每条佐证材料完整或者政策判断正确。

因此我不把 CheckboxGroupResult.status 直接写到业务审批字段。页面右上角可以标“部分选中”,但最终按钮必须调用独立的 inspectChecklist。这也给以后接入人工复核留下空间:勾选结果是输入,业务规则给出缺项,真实审查由拥有证据和权限的流程完成。

二、先定义可回放的清单,再接 CheckboxGroup 的事件

1. 校验器只接受稳定 ID

中文文案可能改版,但状态键不该跟着标题改。我们固定四个键 privacy、permission、sdk、sharing。最后一项没有确认时,返回缺失键和提示;传入未知键时,不能因为数组长度恰好等于四就直接放行。先写这个纯业务校验器,而不是在组件的 onChange 里塞十几条判断。

// model/ChecklistPolicy.ets:纯业务判断
export interface Inspection {
  ready: boolean;
  missing: string[];
  unknown: string[];
}

const REQUIRED: string[] = ['privacy', 'permission', 'sdk', 'sharing'];

export function inspectChecklist(keys: string[]): Inspection {
  const chosen: Set<string> = new Set(keys);
  const missing: string[] = REQUIRED.filter((id: string) => !chosen.has(id));
  const unknown: string[] = keys.filter((id: string) => !REQUIRED.includes(id));
  return { ready: missing.length === 0 && unknown.length === 0,
    missing: missing, unknown: unknown };
}

这个函数没有 UI 副作用,重复输入不累加“通过次数”。如果传入三项演示数据,它应返回缺少 sharing;传入五项且多出陌生键则报告 unknown。这种设计也便于以后切换到服务端校验:纯函数负责解释当前输入,服务端返回的佐证结果另外建模,不混淆“勾了选项”和“材料已审核”。真实政策条款需要持续核对官方应用市场规则,不能由这四个示例名称推导出来。

2. 组状态和本地数组只留一个真相源

现在接入 ArkUI。Checkbox({name, group}) 把子项归到同名组,CheckboxGroup({group}) 接收组选中事件。官方说明组的 onChange 在手动改变组内选择时会携带已选名称,因此我们统一在回调中接收一份新数组;不为每个子项再维护一份互相矛盾的布尔字段。ForEach 的第三个参数使用固定 ID,避免文案变化时重建错误行。

// pages/ConsentPage.ets:省略 import 的同文件页面节选
interface CheckRow { id: string; title: string; }
const ITEMS: CheckRow[] = [
  { id: 'privacy', title: '隐私政策版本确认' },
  { id: 'permission', title: '权限用途映射' },
  { id: 'sdk', title: '第三方 SDK 清单' },
  { id: 'sharing', title: '数据共享路径核验' }
];

@Entry
@Component
struct ConsentPage {
  @State selected: string[] = ['privacy', 'permission', 'sdk'];
  @State status: string = '待补充';
  private revision: number = 1;

  private acceptSelection(names: string[]): void {
    this.selected = [...names];
    this.revision += 1;
    this.status = inspectChecklist(this.selected).ready ? '可生成快照' : '待补充';
  }

  build() {
    Column({ space: 16 }) {
      Text('ConsentDesk · RV-924').fontSize(22)
      Text(`已核对 ${this.selected.length}/4 · ${this.status}`)
      CheckboxGroup({ group: 'audit-items' })
        .selectAll(this.selected.length === ITEMS.length)
        .onChange((event: CheckboxGroupResult) => {
          this.acceptSelection(event.name);
        })
      Text('全选 / 取消全选').fontSize(13)
      ForEach(ITEMS, (item: CheckRow) => {
        Row({ space: 12 }) {
          Checkbox({ name: item.id, group: 'audit-items' })
            .select(this.selected.includes(item.id))
          Text(item.title).fontSize(16)
        }.width('100%')
      }, (item: CheckRow) => item.id)
      Button('生成本地核对快照').onClick(() => this.makeSnapshot())
    }.padding(20).width('100%')
  }
}

上面的 makeSnapshot 在下一节补齐。需要留意 selectAll 只是控件状态设置,不会代替清单校验;组件初始化已经指定了三个被选 ID,因此首次进入页面就应该是部分选中。@State 数组每次整体替换,组件才有明确的新快照。未来增加条目时,必须同步修改校验器与界面数据源,不能仅靠 selected.length 判断“全选已完成”。

三、将提交动作定义为一次静态快照,而不是当前按钮颜色

1. 提交前再次校验,回调回来再次比对

业务流程里最大的误区,是按钮点击时验证了一次,随后就把检查结果当作永远有效。假设点击后用户马上取消“隐私政策版本确认”,先前的异步回调晚到;如果它还把页面改成“快照已生成”,当前页面就会留下一个容易误导的状态。

这里把预览操作做成可比较的快照:记录当时的 revision,复制选中数组,调用独立校验器;模拟的异步边界返回后再比较代次。它只创建本地待审核对象,不上传材料、签名、调用应用市场或宣称提交成功。

// 置于 ConsentPage 内:演示性本地快照,不包含平台上传
private async makeSnapshot(): Promise<void> {
  const ticket: number = this.revision;
  const keys: string[] = [...this.selected].sort();
  const verdict: Inspection = inspectChecklist(keys);
  if (!verdict.ready) {
    this.status = '待补充';
    return;
  }
  this.status = '生成快照中';
  try {
    await Promise.resolve(); // 仅模拟异步任务边界
    if (ticket !== this.revision) { return; }
    this.status = '快照已生成';
  } catch (_) {
    if (ticket === this.revision) { this.status = '生成失败'; }
  }
}

如果未来接文件校验或后端服务,请在 try 中替换成真实且经过授权的请求,并添加失败码、超时和网络状态,不要直接复制 Promise.resolve() 当成上传逻辑。页面销毁时也应使代次失效:例如在 aboutToDisappear 增加 this.revision += 1,防止已经退出的页面再接收旧结果。没有监听器、定时器或外部句柄时不必虚构释放动作;真实附件流或文件句柄应由对应服务释放。

2. 四个操作顺序比一个通过百分比更关键

验收时我更愿意看状态序列,而不是盯着那根百分比条。RV-924 进入时是 3/4,尚缺 sharing,状态“待补充”;点击“生成本地核对快照”应停留在缺项提示;补勾第四项后才变成 4/4、“可生成快照”;最后主动点击按钮,才进入“生成快照中”,并在本地模拟边界结束后得到“快照已生成”。如果刚点击又取消一项,旧回调必须失效,不能再次把状态染绿。

还有两个边界要讲明白。一是用户快速点击全选再手动取消一项,最终应由回调实际返回的名称集合决定,而非本地加减计数。二是未知 ID 和重复键:Set 会消除重复 ID,但未知项仍须被报告;如果真实业务需要记录操作次数,就应该另建审计日志,而不是滥用选择集合。本文示意图只呈现初始 3/4 状态,HiLog 是按照预设路径写出的示例文字,并未连接平台服务。

3. 文案改版不能悄悄继承旧勾选

还有一个容易在长期维护中出现的问题:文案变了,ID 没变,旧选择却仍然被当作“确认了新版材料”。如果隐私说明的版本从草稿 A 改成草稿 B,应该主动使与它相关的确认失效,并要求重新核对佐证链接。此时不是把 selected 数组继续保存到本地就算完成,而是给快照附上资料版本,版本不符时清空受影响的键。本文演示没有引入持久化,正是为了不误导读者认为跨版本继承天然安全。

四、真正要守住的是“选择已确认”和“资料已合规”的分界

这类上架核对页适合作为团队提交前的提醒工具,也可以放在持续集成报告的前置说明中:谁负责哪份说明、当前少了哪一项、哪一次快照对应哪个版本。但它不能替代官方审核、隐私合规评估、第三方 SDK 的实际数据流核验或者权限最小化审查。勾上四项不是通过,只是让团队知道四项自查声明已经被主动确认。

从工程角度看,本篇的关键是把 CheckboxGroup 的选择事件转成稳定 ID 数组,再由纯业务规则独立判断缺项,并用修订代次隔离晚到结果。它和前一篇质量图的共同点是都尊重“状态有边界”,但这里聚焦的是人为确认、材料责任与快照可追溯,并非数值的统计含义。真正上线前,还需要补充资料版本号、操作人身份、权限控制和审计留痕,并按最新官方政策人工核对。

官方依据:华为开发者文档《CheckboxGroup》:https://developer.huawei.com/consumer/en/doc/harmonyos-references-V2/ts-basic-components-checkboxgroup-0000001478181397-V2 ;《Checkbox》:https://developer.huawei.com/consumer/cn/doc/doccenter-capabilities/api/ts-basic-components-checkbox 。实际选型以开发者项目对应 SDK 和最新 API 参考为准。

Logo

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

更多推荐