增加网络配置界面

This commit is contained in:
2026-09-10 11:36:33 +08:00
parent 020278d4e3
commit 8e1fd3b2b8
44 changed files with 2488 additions and 332 deletions
+100 -16
View File
@@ -16,6 +16,14 @@
* 2026-09-09 王建锋 拆分为"框架 + 页面插件"模型(内容页架构.md):三页数据移入
* ui_page_xxx 模块,本文件只留机制 + 描述符表;绘制/按键改
* 按描述符驱动(静态表/refresh + 4 钩子),行为零变化
* 2026-09-09 王建锋 密码页(用户密钥):绘制链支持每页 title_y/"浅 V"/页脚开关;
* 按键分发支持每页 ← 拦截(ESC 恒不可拦截);新 API
* ui_content_page_invalidate(表单页触发整页重绘)
* 2026-09-09 王建锋 权限模型:open(id, perm) 记录授予权限(页面经
* ui_content_page_is_editable 查询:edit_capable 且修改权限)
* 2026-09-10 王建锋 网口设置页(node 30):新增 CONTENT_PAGE_NET_CFG 入描述符表;
* 新 API ui_content_page_selected_row(页面钩子感知选中行)+
* 描述符 init_cb(开页后调一次,页面模块重置运行态)
*/
/* 头文件包含区 */
@@ -29,6 +37,12 @@
#include "ui_page_dc.h"
#include "ui_page_password.h"
#include "ui_page_net_cfg.h"
#include "ui_pwd_def.h"
#include "ui_menu_view.h"
#include "ui_deco.h"
@@ -63,10 +77,13 @@ static const content_page_def_t *const s_defs[CONTENT_PAGE_COUNT] = {
[CONTENT_PAGE_DEVICE_INFO] = &ui_page_device_info_desc,
[CONTENT_PAGE_AC] = &ui_page_ac_desc,
[CONTENT_PAGE_DC] = &ui_page_dc_desc,
[CONTENT_PAGE_PASSWORD] = &ui_page_password_desc,
[CONTENT_PAGE_NET_CFG] = &ui_page_net_cfg_desc,
};
static content_page_id_t s_open = CONTENT_PAGE_COUNT; /* 当前打开的页(COUNT = 未打开) */
static uint16_t s_sel; /* 列表页选中行(全局下标,跨页连续) */
static uint8_t s_perm; /* 本次授予权限(ui_perm_t;开页时装载,关页重置) */
static lv_obj_t *s_scr; /* 页面屏幕(进入时建、返回时毁) */
static lv_obj_t *s_cont; /* 页面全屏容器(焦点/键入口 + 自绘载体) */
@@ -121,6 +138,19 @@ static uint16_t cp_row_total(const content_page_def_t *d) {
return d->row_count;
}
/*
* 函数功能:失效当前打开的页面容器(触发整页重绘)
* 入口参数:无
* 返回值:无
* 限定条件:页面打开时(s_cont 非空)
* 函数说明:供页面模块(表单/操作页)自身状态变化后调用;无页打开时空操作
*/
void ui_content_page_invalidate(void) {
if (s_cont != NULL) {
lv_obj_invalidate(s_cont);
}
}
/*
* 函数功能:绘制居中单行文本(标题/副标题)
* 入口参数:layer - 绘制层 lv_layer_t *
@@ -168,19 +198,22 @@ static void cp_draw_cb(lv_event_t *e) {
const content_page_def_t *d = s_defs[s_open];
lv_layer_t *layer = lv_event_get_layer(e);
/* "浅 V"装饰:共享 ui_deco 模块(与菜单根框同几何) */
ui_deco_draw(layer, &layer->_clip_area);
/* "浅 V"装饰:共享 ui_deco 模块(与菜单根框同几何);每页可关(密码页跟随实机:无装饰) */
if (d->hide_deco == 0u) {
ui_deco_draw(layer, &layer->_clip_area);
}
/* 标题(居中) */
/* 标题(居中;放置 y 每页可配,0 = 框架默认) */
{
int32_t ty = (d->title_y != 0) ? d->title_y : CP_TITLE_Y;
lv_area_t t;
t.x1 = 0;
t.x2 = CP_SCREEN_W - 1;
t.y1 = CP_TITLE_Y;
t.y2 = CP_TITLE_Y + 15;
t.y1 = ty;
t.y2 = ty + 15;
if (cp_areas_intersect(&t, &layer->_clip_area)) {
cp_draw_centered_text(layer, d->title, CP_TITLE_Y);
cp_draw_centered_text(layer, d->title, ty);
}
}
@@ -284,7 +317,7 @@ static void cp_draw_cb(lv_event_t *e) {
}
}
/* 页脚"第 X 页,共 Y 页":列表页随选中行现算,信息页按参考图固定值 */
/* 页脚"第 X 页,共 Y 页":列表页随选中行现算,信息页按参考图固定值;每页可关(密码页跟随实机) */
{
char buf[32];
uint8_t cur;
@@ -304,7 +337,7 @@ static void cp_draw_cb(lv_event_t *e) {
t.x2 = CP_SCREEN_W - 1;
t.y1 = CP_PAGE_LINE_Y;
t.y2 = CP_PAGE_LINE_Y + 15;
if (cp_areas_intersect(&t, &layer->_clip_area)) {
if (d->hide_footer == 0u && cp_areas_intersect(&t, &layer->_clip_area)) {
/* lv_snprintf 而非 snprintf:MSVC 对 printf 族做格式串扫描,中文字面量会误报 C4819 */
lv_snprintf(buf, sizeof(buf), "第 %u 页,共 %u 页", (unsigned)cur, (unsigned)pages);
@@ -332,22 +365,33 @@ static void cp_draw_cb(lv_event_t *e) {
* 入口参数:e - 事件指针 lv_event_t *
* 返回值:无
* 限定条件:容器已加入默认输入组并获焦
* 函数说明: 1. ESC/←:恒关页返回菜单(框架保证,页面钩子不可拦截)
* 2. 其余键:key_cb 钩子先调(返回 true = 页已处理,框架跳过内置)
* 3. 内置:↑/↓ 列表页移选(首尾环绕,跨页连续),窗口随页切换后重绘;
* 函数说明: 1. ESC:恒关页返回菜单(框架保证,页面钩子不可拦截)
* 2. ←:页面置拦截标志(key_intercept)时先交 key_cb(true = 页已处理不关页),
* 否则关页返回(密码页光标 ←/→ 即由此处理)
* 3. 其余键:key_cb 钩子先调(返回 true = 页已处理,框架跳过内置)
* 4. 内置:↑/↓ 列表页移选(首尾环绕,跨页连续),窗口随页切换后重绘;
* 信息页不响应;动态行数可能缩小,选中行越界时重置回首行
*/
static void cp_key_cb(lv_event_t *e) {
const content_page_def_t *d = s_defs[s_open];
lv_key_t key = (lv_key_t)lv_event_get_key(e);
/* ESC/←:关闭页面返回菜单屏(焦点/导航状态还原) */
if (key == LV_KEY_ESC || key == LV_KEY_LEFT) {
/* ESC:关闭页面返回菜单屏(焦点/导航状态还原;恒不可拦截) */
if (key == LV_KEY_ESC) {
ui_content_page_close();
return;
}
/* 页面钩子先调:true = 页已处理,框架跳过内置语义 */
/* ←:页面置拦截标志且钩子已处理则不关页(密码页光标 ←/→);否则照旧返回 */
if (key == LV_KEY_LEFT) {
if (d->key_intercept != 0u && d->key_cb != NULL && d->key_cb(key)) {
return;
}
ui_content_page_close();
return;
}
/* 其余键:页面钩子先调:true = 页已处理,框架跳过内置语义 */
if (d->key_cb != NULL && d->key_cb(key)) {
return;
}
@@ -385,14 +429,16 @@ static void cp_key_cb(lv_event_t *e) {
/*
* 函数功能:打开指定内容页(建屏 + 切屏 + 焦点置页面容器)
* 入口参数:id - 内容页 ID content_page_id_t
* 入口参数:id - 内容页 ID content_page_id_t
* perm - 本次进入授予的权限 ui_perm_t(UI_PERM_VIEW/MODIFY)
* 返回值:true - 成功;false - 失败(ID 无效/已有页面打开/无描述符/对象创建失败)
* 限定条件:ui_menu_init() 已完成(默认输入组与菜单屏存在)
* 函数说明:1. lv_obj_create(NULL) 新建独立屏幕 + 全屏容器(自绘载体,无子对象)
* 2. 容器入默认输入组并获焦后再切屏——按键立即由本模块接管
* 3. 导航状态不压层:菜单 s_depth/s_sel 原样保留,ESC 返回即还原
* 4. 记录授予权限(页面经 ui_content_page_is_editable 查询)
*/
bool ui_content_page_open(content_page_id_t id) {
bool ui_content_page_open(content_page_id_t id, uint8_t perm) {
if (id >= CONTENT_PAGE_COUNT || s_open != CONTENT_PAGE_COUNT) {
return false; /* ID 无效或已有页面打开(防御) */
}
@@ -403,6 +449,8 @@ bool ui_content_page_open(content_page_id_t id) {
return false; /* 枚举/描述符表漂移:拒开并留痕,不崩溃 */
}
s_perm = perm; /* 本次授予权限(页面可编辑性 = edit_capable 且修改权限) */
s_scr = lv_obj_create(NULL); /* parent=NULL → 新建屏幕 */
if (s_scr == NULL) {
return false; /* LVGL 池耗尽 */
@@ -424,6 +472,10 @@ bool ui_content_page_open(content_page_id_t id) {
s_open = id;
s_sel = 0u; /* 每次进入从首行开始(页面按需建毁,不跨次保留) */
if (d->init_cb != NULL) {
d->init_cb(); /* 页面模块重置运行态(编辑模式/光标/保存标志等) */
}
/* 焦点/键入口:入默认输入组并获焦(先于切屏,保证按键立即由本模块接管) */
lv_group_t *g = lv_group_get_default();
if (g != NULL) {
@@ -464,6 +516,7 @@ void ui_content_page_close(void) {
s_cont = NULL;
s_open = CONTENT_PAGE_COUNT;
s_sel = 0u;
s_perm = (uint8_t)UI_PERM_VIEW; /* 权限重置(卫生:不留上次进入的授权) */
}
/*
@@ -476,3 +529,34 @@ void ui_content_page_close(void) {
bool ui_content_page_is_open(void) {
return (s_open != CONTENT_PAGE_COUNT);
}
/*
* 函数功能:查询当前打开的页面是否可编辑
* 入口参数:无
* 返回值:true - 页面支持编辑模式(edit_capable)且被授予修改权限;false - 其余
* 限定条件:无
* 函数说明:供页面钩子(key_cb/draw_cb)决定编辑行为;权限由 open 的 perm 实参授予
*/
bool ui_content_page_is_editable(void) {
if (s_open == CONTENT_PAGE_COUNT || s_defs[s_open] == NULL) {
return false; /* 无页打开或无描述符(防御) */
}
return (s_defs[s_open]->edit_capable != 0u && s_perm == (uint8_t)UI_PERM_MODIFY);
}
/*
* 函数功能:查询当前列表页的选中行(全局下标,跨页连续)
* 入口参数:无
* 返回值:选中行下标;无页打开/非列表页 = 0 uint16_t
* 限定条件:无
* 函数说明:供页面钩子(key_cb/draw_cb)感知当前选中位置
*/
uint16_t ui_content_page_selected_row(void) {
if (s_open == CONTENT_PAGE_COUNT || s_defs[s_open] == NULL) {
return 0u;
}
if (s_defs[s_open]->selectable == 0u) {
return 0u; /* 信息页/表单页无行选中概念 */
}
return s_sel;
}