
我做通行证识别入口时最开始只关心“进入系统识别页”这个按钮能不能点。后来把两个卡种按钮补上才发现这个目标本身就有问题如果卡种选错、样本没有确认授权或者设备根本不支持对应系统能力页面不该试着启动任何识别。这里的“不能启动”不是体验上的保守提示而是入口的职责。页面现有状态明确显示“识别未启动”“回调码未返回”“返回卡种未返回”“字段数量 0”。这些字段不是等待被填满的占位结果而是当前边界的真实描述。没有满足条件时生成一个看似像结果的卡号、姓名或字段数量反而会掩盖门禁是否正确工作。本篇只讲卡种选择、授权样本和系统能力这三个前置条件。不会讨论识别字段也不会把页面跳转当成系统识别已经完成。我一开始把“选中一个卡种”当成了可运行页面支持港澳居民来往内地通行证和台湾居民来往大陆通行证。它们在状态中分别用HK_MO、TW表示对应的枚举值是 6 和 7。刚开始我觉得只要用户点了一张卡按钮就应该放行。这个想法漏掉了两个事实操作者必须确认样本可用于本次验证设备还必须声明 CardRecognition 相关系统能力。类型值的计算仅仅把当前选择映射成枚举值。它没有调用识别也没有说明设备能处理该值。private selectedCardTypeValue(): number { return this.selectedType HK_MO ? 6 : 7; } private selectedTypeText(): string { return this.selectedType HK_MO ? 港澳居民来往内地通行证 : 台湾居民来往大陆通行证; }当我把这两段代码单独读出来问题就很清楚了枚举存在不等于本机可运行选中状态不等于样本已经获准。若把这两层跳过去即使入口看似顺畅后面也只能把不该发生的调用推给运行时处理。否是否是选择卡种 HK_MO 或 TW显示枚举 6 或 7样本授权已人工确认门禁无授权样本识别未启动设备声明系统能力门禁能力不支持识别未启动允许进入系统识别页当前入口页仍不产生字段结果我把图中的 H 特意写得很直白。入口的放行只意味着可以进入后续页面不意味着已经获取了任何证件信息。把“可以尝试”写成“已经识别”会让测试记录失去价值。卡种错误时最该做的是停下来两个卡种按钮的实现只是调用selectType。选择后页面根据系统能力更新gateMessage有能力时提示“卡种已选择仍需确认样本授权”没有能力时提示设备未声明能力、识别未启动。这让用户即使操作错误也能在进入前看到剩余条件而不是等到后面才面对一个没有上下文的失败。private selectType(selectedType: PermitTypeKey): void { this.selectedType selectedType; this.gateMessage this.systemCapabilitySupported ? 卡种已选择仍需确认样本授权 : 当前设备未声明 CardRecognition 系统能力识别未启动; }我以前会认为“卡种点错”应该让系统后面自行纠正。现在我不这么处理了。入口页知道用户选择的类型也知道是否取得授权和运行环境是否支持因此它最适合负责阻断。若这里还把按钮点亮再把类型错误留给原生模块用户既得不到明确原因页面也无法说明究竟有没有触发能力调用。授权确认不是一个装饰性复选框最容易被低估的是authorizationConfirmed。它初始为 false取消勾选时页面回到“无授权样本识别未启动”即使系统能力支持也不例外。勾选后提示会变为“授权已确认可以进入系统识别页”但这只是准入状态不是结果。private updateAuthorization(isOn: boolean): void { this.authorizationConfirmed isOn; if (!isOn) { this.gateMessage this.systemCapabilitySupported ? 无授权样本识别未启动 : 当前设备未声明 CardRecognition 系统能力识别未启动; return; } this.gateMessage this.systemCapabilitySupported ? 授权已确认可以进入系统识别页 : 授权已确认但当前设备不支持该系统能力; }我没有把这个开关写成“用户已同意就可识别”的泛化授权。代码里的文字更窄样本已获授权可用于本次验证。这个限定提醒我页面保存的是操作者对当前样本用途的确认而不是一个能脱离上下文反复使用的万能凭据。入口按钮系统能力授权状态卡种状态操作者入口按钮系统能力授权状态卡种状态操作者仅 A 与 S 同时满足时可用选择港澳或台湾卡种确认本次样本授权返回支持或不支持提供当前类型提供确认状态允许进入后续页面或保持禁用这也解释了为什么我不把卡种当成第三个按钮条件。页面始终有一个默认类型真正的风险不是“没有值”而是选择是否与本次样本相符。这个判断不能由代码猜测应在人工确认环节完成代码负责保留选择并把它带到下一页。系统能力门禁不能靠版本字符串代替页面aboutToAppear()会调用getVisionRuntimeAvailability()并记录supported、系统版本、SDK API 版本和提示。版本信息可以帮助排查环境但不是授权调用的替代品。真正控制入口的是systemCapabilitySupported。aboutToAppear(): void { const availability getVisionRuntimeAvailability(); this.systemCapabilitySupported availability.supported; this.systemVersion availability.systemVersion; this.sdkApiVersion availability.sdkApiVersion; this.gateMessage availability.supported ? 无授权样本识别未启动 : availability.message; }我遇到过一种很诱人的简化只要 API 版本看起来够高就让按钮可点。它忽略了运行设备的实际声明。现有页面把不支持时的状态写成清晰的阻断信息且说明不加载 Vision Kit 原生模块。这样做比“点进去再报错”更容易定位也避免在没有必要时触发后续能力。最终入口如何保持克制startRecognition()的实现很短。它首先检查授权确认与系统能力只要任一条件不满足就直接返回。都满足时仅把selectedType写入AppStorage然后路由进入运行时页面。这不是对识别结果的处理而是把已经确认的类型交给下一步。private startRecognition(): void { if (!this.authorizationConfirmed || !this.systemCapabilitySupported) { return; } AppStorage.setOrCreatestring(permitTypeKey, this.selectedType); router.pushUrl({ url: pages/article10/PermitRecognitionRuntimePage }); }我会在复测中重点看“禁用是否可靠”。默认无授权时按钮必须不可用选择类型后如果仍没有授权按钮仍不可用确认授权但设备不支持时仍不可用只有两个门禁满足时才能进入下一页。每一次状态变化都应伴随准确的gateMessage。当前摘要区固定展示识别未启动、回调码未返回、返回卡种未返回、字段数量 0。这不是缺少实现后的空洞页面而是入口层诚实表达了它尚未拥有的内容。本篇没有测试真实卡片也没有启动任何实际识别所以不会补写字段也不会给成功与否下结论。默认选中并不等于已经核对类型页面默认的selectedType是HK_MO。我一开始看到默认值存在就觉得“类型选择”这个问题已经被解决。后来才意识到默认值只是让页面有稳定初始状态避免界面一开始没有任何类型它不是对手中样本的判断。这一区别在用户快速点击或重新进入页面时很重要。用户可能先看到港澳类型默认被选中再按实际样本切到台湾类型也可能根本没有检查默认值就继续操作。代码不应该猜用户拿的是什么样本因此它保留选择和文字提示让人工在门禁前完成核对。默认状态服务 UI一次明确的选择服务本次操作二者不能混写。我因此没有增加“根据画面自动判断类型”的按钮也没有在选中后立即执行任何能力调用。当前页面的职责范围很窄展示两个可选值记录当前值告诉用户还缺授权或能力。把未知交给人工确认比在入口页假装智能更可控。门禁顺序影响错误是否容易理解逻辑上授权和能力两个条件都必须为真它们的顺序不改变最终按钮能否点击。但用户看到的提示顺序会影响排查。我把样本授权放在更靠近操作的一层因为它是当前操作者可以立刻补齐的信息系统能力由页面加载时读取属于环境事实。因此设备能力不支持时即使操作者勾选授权提示仍然明确说“授权已确认但当前设备不支持该系统能力”。它没有把授权丢掉也没有写成泛化的“失败”。反过来设备支持而授权未确认时提示就是“无授权样本识别未启动”。两种情况都拒绝进入下一页但解释不同。这也是我不建议只用一个布尔变量canStart的原因。按钮状态可以由合并条件得出但排错需要保留条件的来源。若页面只显示“不可用”用户无法判断自己该改卡种、补确认还是换设备。现有的gateMessage虽然简单却让每个停止点可见。在能力不支持时什么都不做本身是结果排查中最容易被误认为“页面没反应”的场景是设备不支持相关系统能力。按钮灰色摘要区不返回字段工作区也不打开相机、相册或识别组件。以前我会试图让页面继续跳转然后从错误回调里寻找解释。现在我把这种停住看成正确输出能力门禁已经生效后面的原生模块没有被加载。这种做法还有一个好处截图和日志不会出现不必要的敏感输入痕迹。当前入口页没有加载识别组件就没有必要出现卡片画面更不用讨论返回字段。测试记录可以只包含设备支持状态、系统版本、SDK API 版本和门禁提示足以说明为何当前测试没有进入下一步。我如何测试类型与门禁而不触碰结果我的复测表有四行。第一行切换到HK_MO确认文本是港澳居民来往内地通行证、枚举为 6第二行切换到TW确认文本是台湾居民来往大陆通行证、枚举为 7。两行都不勾选授权按钮应该持续禁用。第三行在设备支持状态下勾选授权检查按钮才可用、门禁消息变为可以进入系统识别页。这里我只验证入口放行没有点进运行时页去写任何结果。第四行在不支持的环境中切换任意卡种并勾选授权按钮仍应禁用消息指出能力不支持。这个测试能覆盖卡种、确认、环境三种状态组合却不会触发或声称任何识别字段。我也会在每张截图上避开样本图像和个人信息。当前页面本身没有展示这些内容保留这个状态比为了“丰富报告”引入无关素材更合适。入口测试的目标是验证阻断和放行不是收集证件识别样例。错误类型在入口停住后续页才不会背锅过去如果我允许错误类型直接进入后续页面运行时出现的问题会很难回溯是枚举传错、样本未经确认、设备不支持还是后面的能力本身失败入口页已经知道前三项就应让它们在入口停住。这样后续页只面对经过确认的调用意图问题边界也更清楚。不过我仍然不把“放行”称为成功。它只表示此时的选择、授权和系统能力满足入口条件。后面能发生什么取决于另一个页面和另一个运行时环境。把前置判断做好正是为了不把未知伪装成已知。我没有让类型按钮自行修正用户选择有人会问两个卡种只有两个选项是否可以在用户选择后再自动替他确认我的结论是不可以。代码可以显示HK_MO对应 6、TW对应 7但它看不到当前样本也不知道操作者这次想验证的业务类型。让页面自动把一种选择改回另一种表面上减少了点击实际上会让最终传递到下一页的值失去来源。因此类型按钮只做显式选择摘要区也直接显示当前键值。这样当操作人员从港澳切到台湾时可以同时看到名称和枚举已改变若他发现选错也可以在调用前切回。页面的提示只说“卡种已选择仍需确认样本授权”不暗示选择已被外部信息核实。人为选择、人工授权、系统能力分别保留是为了让每个决定都能追溯到对应的状态。这个克制还有一个实际好处测试者可以对两个枚举各跑一次门禁组合而不必猜测页面会不会暗中改值。先在 HK_MO 下测试未确认、已确认、能力不支持再在 TW 下重复相同条件。两组测试的预期相同唯一应变化的是卡种文本与枚举。只要门禁行为因为类型切换出现意外放行或意外阻断就能在入口页面直接发现。按钮禁用与早返回需要同时存在我没有只依靠按钮的.enabled(...)属性。界面禁用可以防止正常点击但startRecognition()内仍然保留条件判断。两层判断分别处理界面表现和方法入口前者让用户知道当前不能继续后者确保即便调用路径在将来调整也不会在缺少确认或能力时保存类型并跳转。这不是为了给当前页面制造复杂逻辑。相反它让规则在两个必要位置都能被读到。看到按钮的人知道两个状态缺一个就不可用阅读方法的人知道调用前还会再次拒绝。两处条件保持同一表达式也让后续改动更容易审查如果有人只改了按钮样式却忘了方法判断或者反过来只需比较两个位置就能发现偏差。我在复测时还会尝试先切换类型、再反复勾选和取消授权然后重新加载页面。重新加载后类型会回到代码定义的默认状态授权确认也回到 false这符合入口页不持久化本次确认的意图。它避免一次页面会话里的临时确认在下一次操作中被误当作仍然有效。这里测试的是初始状态和门禁重置不涉及任何识别调用。入口页最终呈现的“未返回”和“0”是这个设计的一部分。页面还能说明下一步条件却不越过当前信息范围。对我来说这比在测试阶段把字段区域填得很满更可靠当条件不足时停住本身就是正确的页面行为。防回归检查卡种切换后只验证selectedType、枚举值和门禁文案不把选择写成识别开始。样本授权或系统能力任一不满足时入口按钮必须保持禁用且不进入下一页。入口摘要继续保留“未启动、未返回、字段数量 0”不生成任何看似真实的证件字段。