news 2026/9/6 5:51:55

LVGL选项卡视图在ESP32屏幕开发中的完整实践指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
LVGL选项卡视图在ESP32屏幕开发中的完整实践指南

LVGL 选项卡视图应该是嵌入式 ESP32 屏幕开发里最实用的控件之一,早期很多人的仪表盘、菜单主页、配网页面都用它搭出来。这次我们把lv_tabview从创建、添加页面、切换事件到内存占用完整过一遍,并结合小屏幕适配和实际项目场景给出一套可以直接用的代码模板。

很多刚接触 ESP32 + LVGL 的开发者会遇到一个问题:官方示例能跑,但一到自己的项目里就出现花屏、内存不足、切换卡顿,甚至编译报错。这些大部分不是芯片问题,而是lv_conf.h配置、屏幕驱动和选项卡视图使用方式没对上。这篇文章会先讲清楚 LVGL 选项卡视图的核心能力和适用场景,再给出本地部署(模拟器调试 + 真机烧录)的完整流程,最后补充排错清单和工程化建议。

1. 核心能力速览

能力项说明
项目类型嵌入式 GUI 控件应用示例,基于 LVGL 图形库
控件名称lv_tabview(选项卡视图 / 标签页控件)
核心功能顶部/底部选项卡按钮 + 多个内容页面切换
典型搭配控件lv_obj容器、lv_labellv_btnlv_listlv_group
适合芯片ESP32、ESP32-S3(推荐带 PSRAM)、STM32 等可运行 LVGL 的 MCU
屏幕接口SPI 屏(常见 ILI9341、ST7789、ST7735)或 RGB 屏,取决于移植的显示驱动
显示分辨率320x240、480x320、240x240 等,需按屏幕实际分辨率配置
输入方式触摸屏、按键、编码器、遥控器(通过lv_group接入)
内存要求与色深、分辨率、缓冲区和控件数量有关,建议 ESP32 使用 PSRAM 或降低色深
启动方式LVGL 模拟器(PC)/ 真机编译烧录
支持 API是,LVGL 提供原生 C API,可封装事件回调、页面切换接口
批量任务不直接涉及,但可为多个传感器/设备页面做统一布局管理
适合场景菜单设置、设备状态页、多模式切换、数据监控页、配网页面

从材料看,这个项目是嵌入式 ESP32 系列的 LVGL 实践内容,重点落在“选项卡视图”上。选项卡视图不是某个独立软件,而是 LVGL 内置的标准控件,开发者可以基于它快速搭出多页面 UI,再配合不同的内容容器承载实时数据、控制按钮或状态展示。

2. 适用场景与使用边界

2.1 适合谁

  • 正在做 ESP32 屏幕项目,需要多页面切换功能的开发者;
  • 想从基础控件开始搭建 LVGL 界面的嵌入式初学者;
  • 需要在 MCU 上实现“仪表盘 + 设置页 + 数据页”这类常见结构的开发者;
  • 正在学习 LVGL 源码和对象模型的开发者。

2.2 能解决什么问题

选项卡视图解决的是“如何在有限的屏幕空间内组织多块内容”。如果不用选项卡,你会面临两个常见麻烦:

  • 页面切换逻辑自己写,状态管理混乱;
  • 页面堆叠显示,内存占用高,屏幕还容易乱。

使用lv_tabview后,每个选项卡对应一个独立容器,切换时 LVGL 自动处理显示隐藏关系,开发者只需要往每个容器里塞内容。

2.3 不适合什么场景

  • 需要高度自定义交互的页面流,比如多个页面之间的复杂转场动画,选项卡视图的切换效果比较基础;
  • 内容量极大的列表类应用,更适合用lv_listlv_menu
  • 屏幕空间极小(如 128x64)的设备,选项卡按钮会挤压内容区域,建议改用滑块式或翻页式布局。

2.4 使用边界与合规提醒

本示例属于嵌入式 UI 开发技术实践。如果在真实项目中接入传感器数据、摄像头画面、联网信息或用户数据,请务必遵守设备所在地区的数据保护规定,只在合法授权的数据和设备范围内进行处理。涉及第三方图标、字体、素材时,注意版权合规;涉及用户个人信息展示时,做好脱敏和权限控制。

3. 环境准备与前置条件

3.1 硬件选择

硬件用途建议
ESP32 / ESP32-S3 开发板运行 LVGL 的主控首选带 PSRAM 的型号,复杂页面更从容
SPI 屏幕显示 UI常见 2.4 寸、2.8 寸、3.5 寸,分辨率 320x240 居多
触摸/按键模块切换选项卡可选,键盘输入通过lv_group也能切换
USB 转 TTL 或开发板自带串口烧录和日志输出必备

实际选用哪个型号,取决于你的屏幕驱动 IC 和引脚定义。ILI9341、ST7789、ST7735 在 LVGL 项目中都很常见。

3.2 软件环境

软件用途说明
ESP-IDF 或 Arduino ESP32 环境编译、烧录、运行二选一即可,本项目通常用 ESP-IDF 更顺手
LVGL 源码GUI 图形库版本建议用当前主流稳定版,如 8.3.x 系列
屏幕驱动显示底层TFT_eSPI(Arduino 平台)或 ESP-IDF 下的显示驱动封装
PC 模拟器(可选)先在电脑上调 UIlv_sim_eclipse_sdllv_sim_vscode_sdl都可
串口工具查看日志idf.py monitor或 Arduino 串口监视器

3.3 通用检查清单

在开始敲代码前,先确认以下内容:

  • 开发板能否正常烧录基础例程(比如点灯程序);
  • 屏幕接线是否和驱动代码里的引脚定义一致;
  • lv_conf.h是否已经复制到项目并开启LV_USE_TABVIEW
  • 是否根据屏幕分辨率正确设置LV_HOR_RES_MAXLV_VER_RES_MAX
  • 是否有足够的 FLASH 空间存放中文字库或图片资源。

如果没有现成的屏幕驱动,建议先用 LVGL PC 模拟器跑通选项卡视图,再迁移到真机。这样 UI 逻辑和硬件问题可以分开排查。

4. LVGL 选项卡视图本地部署与启动

4.1 在 PC 模拟器上验证

先不碰硬件,在 PC 上创建一个 LVGL 模拟器工程(以 SDL 模拟器为例),核心思路是初始化 LVGL 后创建一个选项卡视图并加几个页面。

#include "lvgl.h" #include <stdio.h> void tabview_demo(void) { lv_obj_t *tabview = lv_tabview_create(lv_scr_act(), LV_DIR_TOP, 50); /* 第 1 个选项卡页面 */ lv_obj_t *tab1 = lv_tabview_add_tab(tabview, "首页"); lv_obj_t *label1 = lv_label_create(tab1); lv_label_set_text(label1, "这是首页内容"); lv_obj_center(label1); /* 第 2 个选项卡页面 */ lv_obj_t *tab2 = lv_tabview_add_tab(tabview, "设置"); lv_obj_t *label2 = lv_label_create(tab2); lv_label_set_text(label2, "设置页内容"); lv_obj_center(label2); /* 第 3 个选项卡页面 */ lv_obj_t *tab3 = lv_tabview_add_tab(tabview, "关于"); lv_obj_t *label3 = lv_label_create(tab3); lv_label_set_text(label3, "版本 v1.0.0"); lv_obj_center(label3); }

这段代码就是在屏幕顶部创建一个选项卡栏,高度 50 像素,然后添加“首页”“设置”“关于”三个页面。每个页面都是一个独立的容器对象,后续可以在里面继续添加按钮、滑块、标签、图表等控件。

模拟器里运行的效果是:点击顶部按钮,下方内容区域会切换到对应页面。

4.2 在 ESP32 真机上跑

真机部署的流程和模拟器类似,但要注意屏幕驱动初始化必须放在 LVGL 初始化之前。

以 ESP-IDF 为例,整体步骤如下:

  1. 配置好 ESP-IDF 环境;
  2. 将 LVGL 源码放进项目组件目录(components/lvgl);
  3. 将屏幕驱动初始化代码和 LVGL 的lv_port_displv_port_indev结合起来;
  4. app_main里按顺序:屏幕初始化 -> LVGL 初始化 -> 显示驱动注册 -> 输入驱动注册 -> 创建选项卡视图界面。
#include <stdio.h> #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "esp_log.h" #include "lvgl.h" #include "disp_driver.h" /* 你的屏幕驱动头文件 */ #include "lv_port_disp.h" #include "lv_port_indev.h" void lv_tabview_demo(void) { lv_obj_t *tabview = lv_tabview_create(lv_scr_act(), LV_DIR_TOP, 45); lv_obj_t *tab1 = lv_tabview_add_tab(tabview, "温湿度"); lv_obj_t *tab2 = lv_tabview_add_tab(tabview, "控制"); lv_obj_t *tab3 = lv_tabview_add_tab(tabview, "日志"); lv_obj_t *label1 = lv_label_create(tab1); lv_label_set_text(label1, "温度: 26.5 C\n湿度: 48.0 %"); lv_obj_center(label1); lv_obj_t *sw = lv_switch_create(tab2); lv_obj_center(sw); lv_obj_t *label3 = lv_label_create(tab3); lv_label_set_text(label3, "2025-01-01 10:00:00 boot ok\n"); lv_obj_center(label3); } void app_main(void) { /* 1. 屏幕硬件初始化 */ disp_driver_init(); /* 2. LVGL 初始化 */ lv_init(); /* 3. 注册显示驱动 */ lv_port_disp_init(); /* 4. 注册输入驱动(触摸或按键) */ lv_port_indev_init(); /* 5. 创建选项卡视图界面 */ lv_tabview_demo(); /* 6. LVGL 任务循环 */ while (1) { lv_timer_handler(); vTaskDelay(pdMS_TO_TICKS(5)); } }

真机移植时最常见的错误就是忘记在lv_conf.h里开启LV_USE_TABVIEW 1。LVGL 有些控件默认是关闭的,如果没启用,编译时会报“函数未定义”或控件创建失败。

4.3 配置示例:lv_conf.h 关键项

#define LV_USE_TABVIEW 1 #define LV_COLOR_DEPTH 16 #define LV_MEM_SIZE (48U * 1024U) #define LV_HOR_RES_MAX 320 #define LV_VER_RES_MAX 240

色深和缓冲区大小需要根据屏幕模组规格调整。16 位色在 320x240 屏幕上比较常见,48KB 的 LVGL 堆内存对选项卡视图来说属于基础设置,页面控件数量多时还需要继续加大,或者改用 PSRAM 支持的外部堆内存。

5. 选项卡视图功能测试与效果验证

5.1 基础切换测试

测试项操作预期结果
选项卡创建调用lv_tabview_create顶部或底部出现选项卡按钮,下方出现内容容器
添加选项卡调用lv_tabview_add_tab3 次出现 3 个可点击的按钮
页面切换点击不同选项卡内容区域显示对应页面,无残留、无重叠
默认页不设置当前页默认显示第 1 个页面

验证代码:

void set_tab_test(lv_obj_t *tabview) { /* 切换到第 2 个页面,索引从 0 开始 */ lv_tabview_set_act(tabview, 1, LV_ANIM_ON); }

lv_tabview_set_act的第三个参数控制切换动画,LV_ANIM_ON会有滑动过渡,LV_ANIM_OFF则直接切换。在低端 MCU 上如果觉得动画掉帧,建议关闭。

5.2 事件回调测试

选项卡视图本身会处理按钮点击事件,但如果你需要在切换页面时更新页面数据,就需要监听选项卡的切换事件。方式是在选项卡内容容器上添加事件回调。

static void tab_event_cb(lv_event_t *e) { lv_event_code_t code = lv_event_get_code(e); if (code == LV_EVENT_VALUE_CHANGED) { lv_obj_t *tabview = lv_event_get_target(e); uint16_t act_id = lv_tabview_get_tab_act(tabview); printf("[LVGL] 当前激活选项卡: %d\n", act_id); } } void tabview_with_event(lv_obj_t *parent) { lv_obj_t *tabview = lv_tabview_create(parent, LV_DIR_TOP, 45); lv_obj_add_event_cb(tabview, tab_event_cb, LV_EVENT_ALL, NULL); lv_tabview_add_tab(tabview, "A"); lv_tabview_add_tab(tabview, "B"); lv_tabview_add_tab(tabview, "C"); }

在这里要注意:LV_EVENT_VALUE_CHANGED是选项卡视图切换时最常用的事件。调试时可以把当前激活的选项卡索引打出来,确认事件触发是否正常。

5.3 内容添加测试

每个新增的 tab 都是一个lv_obj_t *容器,你可以像操作普通屏幕一样操作它。

void add_widgets_to_tab(lv_obj_t *tab) { /* 添加一个滑块 */ lv_obj_t *slider = lv_slider_create(tab); lv_obj_set_pos(slider, 10, 10); /* 添加一个开关 */ lv_obj_t *sw = lv_switch_create(tab); lv_obj_set_pos(sw, 10, 50); /* 添加一个文本区 */ lv_obj_t *ta = lv_textarea_create(tab); lv_obj_set_pos(ta, 10, 90); lv_obj_set_size(ta, 200, 60); }

判断成功标准:每个 widget 都出现在对应 tab 内,切换到其他 tab 时隐藏,切回来时状态保留。

5.4 中文字体显示测试

很多人的选项卡按钮需要显示中文,比如“设置”“主页”“数据”。LVGL 默认字体lv_font_montserrat_14不支持中文,需更换支持中文的字体,或者在lv_conf.h中开启并载入自定义字体。

/* 示例:使用系统已有中文字体,需在你的工程中实际引入 */ lv_obj_set_style_text_font(tabview, &my_cn_font_14, LV_STATE_DEFAULT);

如果不做字体处理,中文会显示成方框或空白。这是 LVGL 中文项目最常见的问题之一。

5.5 键盘/遥控器导航测试

如果你的设备没有触摸屏,而是用红外遥控器、按键或旋钮控制,可以借助lv_group实现选项卡切换。

lv_group_t *g = lv_group_create(); lv_group_add_obj(g, tabview); lv_group_focus_obj(tabview_enable_key); /* 在 lv_port_indev 的按键回调中调用 */ void key_event_handler(uint8_t key) { if (key == KEY_PREV) { lv_group_focus_prev(g); } else if (key == KEY_NEXT) { lv_group_focus_next(g); } else if (key == KEY_ENTER) { /* 确认,触发当前控件点击事件 */ lv_event_send(lv_group_get_focused(g), LV_EVENT_CLICKED, NULL); } }

lv_tabview默认支持按键导航,因为它在内部使用了一个按钮矩阵。把选项卡视图加入lv_group后,左右键就能在选项卡之间移动。

6. 选项卡视图 API 与事务管理

LVGL 本身是 C 语言库,所谓“接口”就是它提供的一组 API。本节给出项目和 UI 逻辑中比较常用的一组函数,适合在开发中直接查阅。

API 函数功能说明
lv_tabview_create(parent, pos, tab_size)创建选项卡视图,pos控制选项卡在顶部还是底部
lv_tabview_add_tab(tabview, name)添加一个选项卡页面,返回内容容器对象
lv_tabview_set_act(tabview, id, anim)切换到指定索引的选项卡
lv_tabview_get_tab_act(tabview)获取当前激活的选项卡索引
lv_tabview_get_tab_count(tabview)获取选项卡总数
lv_tabview_get_tab(tabview, id)获取指定索引的选项卡容器

6.1 批量创建页面

如果页面结构相似(比如多路传感器监控),建议用一个数组加循环来批量创建选项卡。

static const char *pages[] = {"传感器1", "传感器2", "传感器3", "传感器4"}; void create_multi_pages(lv_obj_t *parent) { lv_obj_t *tabview = lv_tabview_create(parent, LV_DIR_TOP, 40); lv_obj_t *tabs[4]; for (int i = 0; i < 4; i++) { tabs[i] = lv_tabview_add_tab(tabview, pages[i]); char buf[32]; snprintf(buf, sizeof(buf), "通道 %d\nValue: %d", i, i * 10); lv_obj_t *label = lv_label_create(tabs[i]); lv_label_set_text(label, buf); lv_obj_center(label); } }

这样做的优势很明显:后期新增一个传感器页面,只需要在pages数组里加一个字符串,不需要重复写创建代码。

6.2 页面数据刷新实践

选项卡页面里的数据通常要定时刷新。建议给每个 tab 建一个独立的更新函数,避免在全局lv_timer_handler里堆大量刷新逻辑。

void update_tab1_data(lv_timer_t *timer) { /* 更新页面上的标签文本 */ lv_label_set_text_fmt(sensor_label1, "温度: %.1f C", read_temp()); }

然后在初始化时创建定时器:

lv_timer_create(update_tab1_data, 1000, NULL);

这种“每个页面一个定时器”的方式,在页面较少时很清爽。页面很多时,可以改为一个定时器遍历刷新。

6.3 事件冲突处理

如果某个页面里还有滑块或开关,它们也会产生LV_EVENT_VALUE_CHANGED。在事件回调中最好加一层判断,确认事件来源。

static void tab_event_cb(lv_event_t *e) { lv_event_code_t code = lv_event_get_code(e); lv_obj_t *target = lv_event_get_target(e); lv_obj_t *tabview = lv_event_get_user_data(e); if (code == LV_EVENT_VALUE_CHANGED && target == tabview) { uint16_t act = lv_tabview_get_tab_act(tabview); printf("switch to %d\n", act); } }

这样可以避免页内控件的事件误触发页面切换逻辑。

7. 资源占用与性能观察

7.1 显存/内存观察方法

LVGL 不是直接操作 GPU 的框架,它的内存消耗主要分为三块:

  • LVGL 内部堆内存LV_MEM_SIZE,用于所有控件对象;
  • 显示缓冲区,一般是一个或两个LV_HOR_RES_MAX * LV_VER_RES_MAX * LV_COLOR_DEPTH / 8大小的像素缓冲区;
  • 字体和图片资源占用的 FLASH 或 RAM,大量中文文字和图片会显著增加占用。

没有实际编译运行之前,很难给出准确的占用数字。比较稳妥的做法是:

  1. 第一次先跑最小选项卡示例,观察内存余量;
  2. 不断添加控件,观察内存变化;
  3. 打开 LVGL 自带的内存监控函数lv_mem_monitor打印内存快照。
void print_mem_info(void) { lv_mem_monitor_t mon; lv_mem_monitor(&mon); printf("total: %lu, free: %lu, used: %lu, frag: %u%%\n", mon.total_size, mon.free_size, mon.used_size, mon.frag_pct); }

7.2 降低内存占用的常用手段

手段说明
降低色深从 16 位色降到 8 位色或 1 位色,适合内容简单的 UI
缩小缓冲区使用单缓冲或局部刷新,牺牲一定帧率
开启 PSRAM把大缓冲区或字体图片放到外部 RAM
减少同时显示的控件选项卡页内控件尽量按需创建,或使用lv_obj_set_hidden
使用图片代替大量绘制复杂背景可以预渲染成图片,避免运行时绘制
关闭不需要的动画切换页关闭动画,减少 CPU 负载

7.3 性能观察重点

  • 启动耗时:从芯片上电到界面显示完成,是否在可接受范围;
  • 切换延迟:点击选项卡到内容刷新的时间是否明显;
  • 帧率:lv_timer_handler执行一次的时间是否小于帧间隔;
  • CPU 占用:FreeRTOS 下可以通过vTaskListesp_get_free_heap_size辅助判断。

一般来说,选项卡比较少、每个页面内容简单时,MCU 负载很低;页面多且每个页面有大量图形控件时,需要重点观察纹理刷新是否拖动卡顿。

如果出现明显的切换卡顿,优先检查显示缓冲区是否太小、动画是否开启、是否有其他任务长期占用 CPU。

8. 常见问题与排查方法

问题现象可能原因排查方式解决方案
编译报错:LV_USE_TABVIEW未定义lv_conf.h中没有启用选项卡控件检查lv_conf.h设置#define LV_USE_TABVIEW 1
中文显示成方框使用了默认英文字体检查字体加载代码引入支持中文的字体,并设置到控件样式
选项卡按钮不显示控件被放置到超出屏幕范围或父容器尺寸异常检查lv_tabview_create的父对象和尺寸确保父容器尺寸合理,选项卡视图尺寸覆盖屏幕内容区
点击选项卡没有反应输入驱动未注册或触摸坐标不准先跑触摸测试例程校准触摸,或者改用按键/编码器测试
切换页面时内容重叠缓冲区或刷新区域设置不对检查lv_port_disp的缓冲区配置改用全屏刷新或正确绘制脏矩形
运行后重启或白屏内存不足或 SPI 通信异常查看串口日志、检查接线缩小控件数量、加大内存、检查屏幕初始化时序
API 调用段错误对象句柄无效或提前释放检查对象生命周期在创建和销毁时统一管理句柄
页面较多时内存溢出每个 tab 创建了太多控件打印内存快照按需创建页面,或减少每页控件数量

8.1 启动后屏幕一直闪烁

常见原因是显示缓冲区刷新过于频繁或刷新区域计算异常。可以先关闭动画,将刷新方式改为全屏刷新,如果不再闪烁,说明脏矩形重绘逻辑没有适配好。

8.2 无法切换到预期页面

先检查调用lv_tabview_set_act时传入的索引是否越界。索引从 0 开始,最大值为lv_tabview_get_tab_count(tabview) - 1。另外确认调用发生在 LVGL 初始化完成之后。

8.3 真机运行正常但模拟器异常

模拟器和真机的分辨率、字体路径、输入方式不同,建议优先保证模拟器上的 UI 效果,再针对真机调整尺寸和字体。

9. 最佳实践与使用建议

9.1 小参数先验证

第一次移植选项卡视图,不要一上来就设计复杂界面。先跑通 2 个 tab,内容只放一个标签。确认选项卡切换正常后,再逐步增加页面和控件。

9.2 保持一套最小可运行配置

把选项卡视图的最小可运行代码单独保存为一个工程模板,包含正确的lv_conf.h、屏幕驱动初始化和输入驱动注册。后续做新项目时直接基于这个模板改,能省很多时间。

9.3 目录管理

建议按下面的结构组织 LVGL 项目:

project/ ├── main/ │ ├── app_main.c │ ├── ui_tabview.c │ ├── ui_tabview.h │ └── lv_conf.h ├── components/ │ ├── lvgl/ │ └── display_driver/ └── build/

UI 代码和业务逻辑分开,ui_tabview.c只负责界面创建和事件响应,不要在 UI 文件里直接读写传感器或网络数据,通过回调函数或接口透传。

9.4 批量任务与页面状态管理

选项卡视图页面多后,容易出现“所有页面同时刷新”的问题。建议用“当前激活页优先刷新”的策略,切到哪个页面就刷新哪个页面的数据,未激活页面降低刷新频率或完全停止。

void update_active_tab_only(lv_timer_t *timer) { uint16_t act = lv_tabview_get_tab_act(tabview_global); if (act == 0) { /* 刷新温湿度页 */ } else if (act == 1) { /* 刷新控制页 */ } else if (act == 2) { /* 刷新日志页 */ } }

这样虽然代码多一点,但能明显降低 MCU 负担。

9.5 日志和错误处理

嵌入式 UI 出问题时最难排查的就是“不知道当前走到哪一步”。建议在页面切换、数据刷新、内存告警三个位置都加上调试日志。内存告警可以使用自定义回调或定期检查空闲堆内存,提前报警总比白屏好处理。

10. 总结与下一步

这个项目最值得尝试的地方在于:它把 LVGL 里最常用的多页面组织方式拆成了一个可复制、可扩展的模板。你不用在一开始就掌握 LVGL 的全部对象模型,只需要理解“选项卡视图是一个容器,每个 tab 是子容器”这个核心概念,就能把温湿度页、控制页、日志页、设置页全部组织起来。

建议先做的事:

  1. 在 PC 模拟器上跑通 3 个 tab 的选项卡视图;
  2. 把其中一个 tab 替换为真正的业务内容,比如一个开关或一个实时数据标签;
  3. 迁移到 ESP32 真机,重点观察内存余量和切换流畅度;
  4. 把常见中文显示、字体加载、事件回调问题记录到自己的笔记里。

最容易踩的坑有三个:LV_USE_TABVIEW没开启、中文字体没准备、屏幕驱动初始化顺序不对。先把这三个问题解决,选项卡视图的开发会顺利很多。

后续扩展方向:

  • 使用lv_tabview做复杂设备控制面板,每个 tab 对应一个功能模块;
  • 将选项卡视图与lv_chart结合,做实时曲线监控页面;
  • 把页面切换状态通过 MQTT 或串口上报到上位机;
  • 结合 FreeRTOS 任务,把 UI 刷新和数据采集解耦。

LVGL 的选项卡视图在嵌入式小屏设备上属于“花小钱办大事”的控件,花少量内存就能换来清晰的界面层级。建议收藏备用,实际项目里可以直接套用上面的代码框架。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/6 5:50:27

做过会计的店长:把验证码做成报表科目

做过会计的店长&#xff1a;把验证码做成报表科目 一位财务背景店长的算法&#xff1a; 「干了八年会计&#xff0c;职业病是啥都能入账。我给自己的人肉验证算过一笔账&#xff1a;上月过验证四百一十次&#xff0c;平均四十秒一次&#xff0c;合计四千六百多分钟——七十六个…

作者头像 李华
网站建设 2026/9/6 5:48:27

全店商品去重:同一批货多店分发的正确姿势

全店商品去重&#xff1a;同一批货多店分发的正确姿势 一次关联判定的复盘现场&#xff1a; 「八个店分发同一批三百个品&#xff0c;我做了差异化&#xff1a;标题改了、主图换了。但还是被判了关联降权——排查发现漏了两个细节&#xff1a;详情页的文案一字没动&#xff0c…

作者头像 李华
网站建设 2026/9/6 5:48:17

jemter+ant+jeknis(二)

安装ant&#xff1a; 1.官方下载地址&#xff1a;https://ant.apache.org/bindownload.cgi 下载完成后解压文件。 2.配置环境变量 新建变量ANT_HOME&#xff1b;然后在path变量中添加lib和bin文件的路径%ANT_HOME%\lib&#xff1b;%ANT_HOME%\bin。 cmd窗口输入ant -version…

作者头像 李华
网站建设 2026/9/6 5:45:19

Cline接入GLM-5.3-Flash实战:免费API配置与踩坑指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/6 5:43:39

​郑州GEO优化服务商怎么选?本地企业避坑的4条硬核标准

导语&#xff1a;在郑州找GEO优化服务商&#xff0c;核心是看其是否具备本地化服务能力、技术自研实力与权威渠道资源。河南云骁智能科技有限公司&#xff08;简称“云骁智能”&#xff09;作为河南最早入局生成式引擎优化&#xff08;GEO&#xff09;的商业化服务商&#xff0…

作者头像 李华
网站建设 2026/9/6 5:37:17

新能源整车台架测试中的CAN转WIFI无线化改造实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华