/* * 模块名称:UI 动作确认页(操作页共享实现,ui_page_action) * 模块功能:可复用的"确认 -> 二次确认 -> 执行回报"操作页**钩子三件套**—— * 强制复归 / 手动录波等破坏性叶子共用同一份确认链实现,各自持一份 * 实例(标题 / 问句 / 结果行 / 执行回调 / 可选观测行); * 执行回调只投递请求不阻塞(跨任务交接由执行侧落定), * 成功经 done_text 回报后再 ENTER 返回,done_text=NULL 成功即关页; * 投递失败显示 ERR,下一次 ENTER 重新走二次确认; * 只读权限(查看密码进入)可整页拒执行并显式说"没权限"(need_modify) * 适用范围:Drivers/BSP/UI(表单页形态:无浅 V 装饰、无页脚——用户密钥页 * 跟随实机先例;ESC 恒关页 = 取消,LEFT 亦关页 = 取消) * 说明:本组件**不建描述符也不开页**(v1.1 起的形态):内容页框架按描述符 * **指针**开页,而描述符只能由"一叶一模块"的页面文件导出(真源 = * ui_menu_tree.txt 的 #page 注记 + 生成物 ui_page_registry.c)—— * 旧形态"组件自己 open()"决定了它永远不可能被注册表绑定(构建器不是 * 描述符,全仓零调用者即其化石证据)。改成 init/draw/key 三个收实例的 * 钩子后:确认链仍只有一份实现,而页面跳板薄到只剩"一份实例 + 一枚 * 描述符"(见 ui_page_reset.c),多个操作页各自持实例、互不串态 * 作者:王建锋 * 创建日期:2026-10-25 * 修改记录: * v1.0 2026-10-25 王建锋 创建(道B task-5 三组件之一;规格 = * 页面扩充详细方案.md §3.4 装置维护操作页 + * §4.2 ui_page_action bind(title, prompt, ok_cb) * 二次确认、ok_cb 只投递不阻塞) * v1.1 2026-10-26 DTU 项目组 道F task-3(装置维护 3 页):改成钩子三件套 * 形态(init/draw/key 收实例指针),删掉模块单例 * 与 ui_page_action_open()——构建器开不了注册表 * 绑定的页(框架按描述符指针开页),旧形态全仓 * 零调用者;新增 need_modify(只读拒绝执行并显 * LOCK)与可选观测行(watch_label + watch_cb, * 复归页用它把"动作位复归"画在同一屏上); * 错误通道由 bsp_err_t 改为无返回 + uint8_t 分发标志 */ #ifndef UI_PAGE_ACTION_H #define UI_PAGE_ACTION_H #ifdef __cplusplus extern "C" { #endif #include #include #include "lvgl.h" /* lv_key_t / lv_layer_t:钩子三件套的形参类型(本组件不建对象) */ /* 执行回调:只投递请求(事件位/消息队列),立即返回; * 返回值:true = 投递被接受(页面转成功回报),false = 拒收(页面显示 ERR) */ typedef bool (*ui_page_action_ok_fn)(void); /* 操作页实例(配置字段 = Flash 指针与只读回调,运行态 = 本模块写的两个字节); * 用法:页面跳板 static 一份、花括号初始化配置,把 init/draw/key 三个钩子直接 * 转进本组件(v1.1 起本组件不再自己开页——内容页框架按描述符指针开页, * 跳板就是那枚描述符的属主,见 ui_page_reset.c) */ typedef struct { /* 页标题不在本结构里:描述符(连 title)由页面跳板 own —— 见 ui_page_reset.c; * 两处各留一份标题就是两个真源,改一处屏上另一处不变那种 */ const char *prompt; /* Flash:确认问句(如"确认复归保护?"—— 问句用字须已入字体子集,否则显示空白方块) */ const char *done_text; /* Flash:成功后结果行(如"已请求复归"); NULL = 成功后直接关页回菜单 */ ui_page_action_ok_fn ok_cb; /* 必填:执行投递(只投递,不等待执行结果) */ uint8_t need_modify; /* 1 = 只读权限(查看密码进入)时拒绝执行: 两次 ENTER 都不投递、态标位改显 LOCK。 破坏性动作必须置 1——只靠"屏上看着像能按" 拦不住人,必须落在键路上 */ const char *watch_label; /* Flash:可选观测行的前缀(NULL = 不画该行), 如"保护动作"。配合 watch_cb 让"复归前 N>0、 复归后 N=0"在同一屏上看得见 */ uint32_t (*watch_cb)(void); /* 可选:观测行取值(NULL = 不画该行;与 watch_label 要么都给要么都不给) */ uint8_t stage; /* 运行态:0 一次确认 / 1 二次确认 / 2 完成回报 */ uint8_t err; /* 运行态:1 = 上轮投递被拒(显示 ERR) */ } ui_page_action_t; /* * 函数功能:开页复位确认链(接框架的 init_cb;v1.1 起本组件不再自己开页) * 入口参数:inst - 操作页实例(ok_cb 不得为 NULL;配置字段由跳板在编译期填好) * ui_page_action_t * * 返回值:无 * 限定条件:内容页框架开页链内调用(uiTask);实例为 NULL 时空操作(防御) * 函数说明:每次进入固定从一次确认开始——"上一次按到一半"跨次保留下来就是 * 第二次进页一按 ENTER 直接执行,那是操作票事故的形状,不是效率优化 */ void ui_page_action_init(ui_page_action_t *a_inst); /* * 函数功能:自绘(接框架的 draw_cb:问句 / 态标 / 可选观测行 / 键提示,四行居中) * 入口参数:inst - 操作页实例 ui_page_action_t * * layer - 绘制层 lv_layer_t * * clip - 重绘区 const lv_area_t * * 返回值:无 * 限定条件:框架绘制链末尾调用(本组件只画自己那几行,标题/装饰/页脚归框架); * 实例或 prompt 为 NULL 时空操作 * 函数说明:态标行优先级——只读且需修改权限 > 二次确认 > 投递失败 ERR; * 只读那条排最前是刻意的:现场最常见的误读是"我按了没反应", * 把"没权限"显出来才不会让人反复按 */ void ui_page_action_draw(ui_page_action_t *a_inst, lv_layer_t *a_layer, const lv_area_t *a_clip); /* * 函数功能:按键处理(接框架的 key_cb:确认链状态机) * 入口参数:inst - 操作页实例 ui_page_action_t * * key - LVGL 键值 lv_key_t * 返回值:1 = 已消费(框架跳过内置语义);0 = 未消费(交回框架) * (键分发标志非错误通道,取 uint8_t 不用 bool—— * bsp-err/err-type 口径,同 ui_w_editor_key) * 限定条件:经描述符 key_cb 挂接;LEFT/ESC 不进本函数(框架恒关页 = 取消) * 函数说明:ENTER——stage0 转二次确认;stage1 调 ok_cb 投递:受理且有 done_text * 转 stage2 回报、受理且无 done_text 直接关页、拒收置 err 留在 stage1; * stage2 关页返回。只读且 need_modify 时任何 ENTER 都不推进也不投递 */ uint8_t ui_page_action_key(ui_page_action_t *a_inst, lv_key_t a_key); #ifdef __cplusplus } #endif #endif /* UI_PAGE_ACTION_H */