办理通行证相关核验时,用户很容易把注意力放在“赶紧开始识别”上,却忽略最开始的类型选择。页面若把两个类型收进一个不醒目的下拉框,或者只在运行页里显示一个数字,用户可能带着错误类型一路点到系统组件,再把没有结果、结果不符或组件不可用都理解成“识别不准”。这不是一个靠更醒目的成功提示就能补救的问题:卡种是进入识别之前就该被确认的前置条件。

本文从产品经理观察者的视角,只讨论 HarmonyOS 6.1.1 CardRecognition 对应的两项通行证卡种选择:港澳居民来往内地通行证使用 CardType 6,台湾居民来往大陆通行证使用 CardType 7。重点不是替读者判断任何证件内容,也不讨论普通证件、字段提取或识别准确率;而是把“我选的是哪种卡”“现在能不能进入系统组件”“系统返回后页面能如实告诉我什么”分成可被用户逐步确认的状态。

一、用户急着开始识别时,为什么先要停在卡种选择上

1.1 卡种不是一个技术编号,而是用户接下来要核对的对象

页面左侧把证件范围拆成“港澳居民通行证”和“台湾居民通行证”两个按钮,并在每项下方显示 CardType 6CardType 7。对工程而言,这两个数字最终会作为 supportType 传入识别组件;对操作人员而言,它们首先是一次可见确认:此刻页面准备处理的是哪一类通行证。

这层确认不能只留给懂 API 的人。若页面只显示 67,用户可能知道自己点过一个按钮,却不知道该数字对应哪种对象;若只显示“通行证识别”,又会让两种范围看上去没有差别。把名称和卡种并排显示,才让用户在真正进入识别前有机会说出“我选的是港澳”或“我选的是台湾”,而不是事后从异常里猜。

在这里插入图片描述

1.2 选错类型后,后续的“没结果”不应被包装成技术故障

用户拿着台湾居民来往大陆通行证,却在页面保留了港澳居民通行证的选择;或者反过来选错。此时即使随后没有得到期望结果,也不能直接归因为组件异常、照片质量或系统识别能力。因为进入组件的条件已经与用户实际要完成的核验对象不一致,后续观察失去了可靠参照。

产品上更稳妥的做法不是在失败后补一段长说明,而是在开始前让选择足够明确:显示当前名称、显示对应 CardType、允许重新选择,并在变更后要求重新确认本次样本授权。这样用户看到“需重新确认授权”时,会明白这是卡种变更带来的重新核对,而不是页面无故把他踢出流程。

1.3 “可以点”与“可以识别”是两回事

卡种按钮始终可以切换,说明用户可以修正自己的选择;“进入系统识别”按钮是否可用,则取决于样本授权和系统能力是否同时通过。把这两种可用性分开,可以避免一个常见误解:用户刚选好类型、看到卡种数字,就以为页面已经开始读取证件。

当前工程在未通过双门禁时,把主按钮显示为“保存为待验证”,而不是假装能运行识别。这个差别对用户很重要。它表示页面可以保存“这次准备核验哪种卡”的本地结论,却不会从一条本地记录推导出“系统已经识别到对应证件”。

二、页面怎样把当前卡种说清楚,而不是让用户凭记忆继续操作

2.1 切换操作同时更新名称、数值与本地提示

卡种选择由 selectedType 保存。页面根据它返回当前的数值与可读名称:

private selectedCardTypeValue(): number {
  return this.selectedType === 'HK_MO' ? 6 : 7;
}

private selectedTypeText(): string {
  return this.selectedType === 'HK_MO'
    ? '港澳居民来往内地通行证'
    : '台湾居民来往大陆通行证';
}

这段代码能证明:当页面状态为 HK_MO 时,会准备使用数值 6 并显示港澳居民来往内地通行证;状态为 TW 时,会准备使用数值 7 并显示台湾居民来往大陆通行证。它不能证明眼前样本是哪种证件,不能证明该证件真实有效,也不能证明系统组件已经接受了这个数值。

对于用户,名称与数值共同出现的意义是降低回看成本。接手人不必从按钮颜色猜测,也不必把内部状态名当作业务名称;他可以在同一屏确认当前对象,再决定是继续、退回重选,还是保存为待验证。

在这里插入图片描述

2.2 一旦改卡种,就撤回旧授权的默认前提

选择处理函数不会把原来的授权状态沿用到底。它会更新 selectedType,清除“待验证已保存”标记,并提示当前操作者重新确认样本授权:

private selectType(selectedType: PermitTypeKey): void {
  this.selectedType = selectedType;
  this.pendingSaved = false;
  this.localNote = `已切换为 ${selectedType === 'HK_MO' ? 'CardType 6' : 'CardType 7'},请重新确认本次样本授权。`;
}

这段代码能证明切换卡种后,页面会撤销此前的本地待验证保存标记,并把“重新确认本次样本授权”写入本地提示。它不能证明任何授权凭证真实有效,也不能证明样本已被平台、机构或持有人允许使用;页面只记录当前操作者的确认动作。

这正是产品反馈应该克制的地方。若切换后仍显示“已确认,可识别”,用户容易把对旧类型、旧样本的确认误用于新的判断对象。要求重新确认不是增加无意义步骤,而是在对象改变时,让用户主动意识到自己正在进入另一条核验路径。

2.3 目标卡种应在决定区重复出现,但不要重复制造结论

右侧“运行时决策”区域会展示系统版本、API 与“目标卡种”。把目标卡种再放到即将操作的位置,能防止用户选完后在大页面里忘记自己刚才选了什么;同时,它不能被设计成“类型已验证”的绿灯。这里回显的是当前选择,不是系统确认。

如果运营流程需要二次确认,可以让用户在按钮前看到一句明确文案,例如“当前将按台湾居民来往大陆通行证(CardType 7)进入系统识别,请确认样本类别”。但不应要求用户去理解内部枚举、AppStorage 或组件参数。产品文案要解释当前任务对象,而不是把工程状态直接抛给用户。

三、什么条件满足后,页面才允许带着这个卡种进入系统组件

3.1 双门禁分别回答“能不能用”和“能不能用这份样本”

入口页把样本授权、系统能力和识别组件分开显示。样本授权由当前操作者勾选“样本已获授权,仅用于本次核验”;系统能力则由运行时检查决定。只有两者同时满足,页面才认为可以进入系统识别:

private canStartRecognition(): boolean {
  return this.authorizationConfirmed && this.systemCapabilitySupported;
}

private startRecognition(): void {
  if (!this.canStartRecognition()) {
    this.localNote = '识别未启动:需要样本授权和 CardRecognition 系统能力同时满足。';
    return;
  }
  AppStorage.setOrCreate<string>('permitTypeKey', this.selectedType);
  AppStorage.setOrCreate<boolean>('permitSampleAuthorized', true);
  router.pushUrl({ url: 'pages/article10/PermitRecognitionRuntimePage' });
}

这段代码能证明页面在进入运行页前检查两项本地条件,并把当前选择与授权状态传给下一页。它不能证明识别组件一定可被构造,不能证明设备实际支持识别,也不能证明路由跳转后已出现任何识别结果。

两项门禁承担不同问题。系统能力回答“当前环境是否允许尝试加载这个组件”;授权确认回答“这份样本是否允许当前操作者用于本次核验”。把它们合成一个“已通过”会让用户无法知道卡住的是设备环境还是样本前提,也会让后续补证没有方向。

3.2 模拟器显示 API 24,也不该让用户误以为组件已经可用

工程通过 getVisionRuntimeAvailability() 检查 SystemCapability.AI.Component.CardRecognition、SDK API 版本和设备类型。其中特别把模拟器单独拦下:当设备型号为 emulator 时,页面记录“当前模拟器 API 为 6.1.1(24),但实测 CardRecognition 运行时模块未导出;未实例化组件以避免崩溃”。

这个规则能让页面说清“SDK/API 条件可见”与“运行时组件可构造”的差别。用户在模拟器看到 API 24,最多可以确认环境报告了该版本;不能据此说通行证识别已经被模拟器支持,更不应因为页面没有崩溃就宣布卡种配置可在真实设备上生效。

产品文案应把下一步落在可执行动作上:保留所选卡种和待验证说明,换到支持设备后重新检查能力;不要引导用户反复切换 6/7 来试图绕过一个与卡种无关的环境门禁。

3.3 未放行时保存的是待验证判断,不是识别记录

当门禁未通过,用户可以点“保存为待验证”。工程将 pendingSaved 置为真,并生成类似“本地待验证结论已保存:港澳居民来往内地通行证 · 等待授权确认”的提示。它让用户在等待设备或补齐授权时不丢失当前选择。

这段体验的关键是用词。应说“本地待验证结论已保存”,不该说“识别信息已保存”或“证件已登记”。该页面明确不写文件、不写日志、不进入截图归档,也不展示证件字段值。保存的只是本次会话中的卡种、门禁状态和等待原因,不能作为业务系统记录,更不能替代合规留痕。

四、运行页把卡种传入组件后,用户还能从回调中确认什么

4.1 组件拿到的是当前选择,不是页面替用户做出的识别结论

运行页从 AppStorage 读取 permitTypeKey,再次计算 supportType,再把它传入 CardRecognition

CardRecognition({
  supportType: this.selectedCardTypeValue(),
  callback: (_legacyResult: CallbackParam) => {},
  onResult: (result: CardRecognitionResult) => { this.handleResult(result); },
  cardRecognitionConfig: { isPhotoSelectionSupported: true }
})

这段代码能证明运行页会把当前选择映射为 supportType,并为系统结果准备 onResult 回调和照片选择配置。它不能证明当前环境已经构造组件、照片选择已经完成、用户提供了任何样本,或系统已经返回与所选卡种一致的结果。

从用户角度看,运行页标题里的 CardType 6CardType 7 应理解为“本次请求所带的类型”,而不是“系统判定出的类型”。把请求参数和识别结果混成一个标签,会让用户无法判断自己是选错了类型,还是系统返回了不同类型。

4.2 回调摘要保留 code、返回卡种和字段数量,不展示证件内容

onResult 收到 CardRecognitionResult,页面只记录回调码、系统返回卡种、字段数量与回调时间。字段值会在内存中丢弃:

private handleResult(result: CardRecognitionResult): void {
  const success = result.code === 0;
  this.runtimeState = {
    callbackCode: result.code,
    returnedCardType: result.cardType,
    fieldCount: this.countReturnedFields(result.cardInfo),
    outcome: success ? 'success' : 'component_failed',
    message: success ? '字段值已在内存中丢弃,仅记录字段数量。' : `系统识别组件返回 code=${result.code}`,
    callbackTime: new Date().toISOString()
  };
}

这段代码能证明页面会把回调的 codecardType、字段数量和本地时间组织成脱敏摘要,也能证明字段值不在这个页面展示或持久化。它不能证明字段内容正确,不能证明卡片真实有效,不能证明字段数量满足某个业务要求,更不能把 code=0 扩张成审批、备案或核验完成。

对用户来说,返回卡种 适合用于检查“系统本次返回的类型是否与我选择的类型一致”;一旦不一致,页面应保留当前选择、返回值和回调时间,提示停止继续提交并人工复核。它不应自动替换用户的原始选择,也不应依据一个数字替用户认定证件归属。

4.3 没有回调时,页面应明确说“未返回”而不是用空白掩盖等待

运行页默认将回调码和返回卡种显示为“未返回”,字段数量为 0,最后回调为“尚未触发”。当双门禁未通过,页面还会明确写“当前不构造识别组件”,并提供返回准入页、刷新能力两个动作。

这类等待反馈避免了两种误导:一是用户把空白组件区当作“已经识别但没有内容”;二是用户把字段数量零直接理解成样本不合格。当前状态最多说明页面尚未收到回调。排查顺序应是先确认选中的卡种与样本意图是否一致,再检查授权和设备能力,最后才在支持环境中观察组件是否真的返回结果。

五、卡种、门禁和回调不一致时,怎样让用户还有下一步

5.1 先用页面能确认的事实,描述现在卡在什么位置

产品提示不需要替用户猜原因。若卡种刚被改过,可写“已切换为 CardType 7,请重新确认本次样本授权”;若授权未确认,可写“当前类型已选定,等待样本授权”;若设备不支持,可写“当前设备未声明 CardRecognition 系统能力,未加载识别组件”;若尚无回调,则写“系统尚未返回,请保留当前类型并检查运行条件”。

每句话只覆盖一层事实:选择、授权、能力或回调。这样用户知道自己应该回到哪一步,而不会把所有等待状态都当作“识别失败”。页面越是在敏感样本场景中克制用词,接手人越容易复核到底是对象、环境还是组件环节尚未满足。

5.2 建议的用户操作顺序:确认对象,再确认门禁,最后再看摘要

当用户准备开始时,可以按下面的顺序完成一次最小核对:

  1. 在“证件范围”确认名称是否为本次要处理的港澳居民来往内地通行证或台湾居民来往大陆通行证,同时核对对应的 CardType 6/7
  2. 若刚刚切换过类型,重新确认本次样本授权;不把此前页面上的绿色状态沿用到新的对象上。
  3. 查看系统能力文案。若是模拟器或能力未声明,保存为待验证并换到支持环境,不反复尝试启动。
  4. 双门禁通过后,再进入运行页;在回调摘要中分别看请求所用的目标卡种、系统返回卡种、回调码、字段数量和时间。
  5. 没有回调、返回卡种不一致或组件失败时,保留脱敏摘要,停止把页面状态写成识别结论,转交具备样本授权与设备条件的人员复核。

这份顺序的目的不是让用户多做一次检查,而是让每个决定都有对应信息。卡种选择解决“我准备识别什么”;门禁解决“当前是否允许尝试”;摘要解决“系统实际回了什么”。三者不能倒过来推导。

5.3 交接信息只保留判断所需的摘要,不复制敏感字段

需要转交时,建议记录当前选择的通行证名称与 CardType、样本授权是否由当前操作者确认、系统能力提示、是否构造组件、回调码、返回卡种、字段数量、最后回调时间和下一步动作。不要在页面、截图说明或交接文字中抄录证件号码、姓名、住址、照片或任何系统返回的字段值。

例如可写:“当前选择台湾居民来往大陆通行证(CardType 7);当前设备能力待验证,识别组件未构造,未收到回调;已保存本地待验证说明。请在授权样本与支持设备条件满足后重新检查。”这是一份能帮助下一位操作者继续判断的记录,而不是识别结果、合规结论或业务归档凭据。

必要条件|运行门禁

G1:SDK/API门禁

确认 HarmonyOS 6.1.1 API 24 已安装,Hvigor 能构建 entry 模块。

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

G2:Kit门禁

确认本文页面的 Kit import 可解析,并使用 API 24 支持的接口。

G3:模块/页面门禁

确认 Stage 模块、页面路由、设备类型和相关模块配置完整。

G4:权限门禁

确认静态声明存在;Camera/AI字幕等运行时能力在真正调用前完成动态授权。

在这里插入图片描述

G5:系统能力门禁

确认目标设备声明并实际提供所需摄像头、麦克风、地图、视觉或文件能力。任一门禁未通过,停止进入核心操作。

在这里插入图片描述

MapKit文章在 G5 放行前增加服务配置门禁:AppGallery Connect 中已选择正确项目,应用包名和签名证书与工程一致,MapKit 服务已开通且应用服务凭据/授权配置已完成。不得在文章或源码中暴露服务密钥。

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

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐