CefSharp 多标签分页实战:进程模型、资源释放与避坑指南
简介这份资源面向.NET桌面开发者与CefSharp初学者聚焦在WinForms应用中嵌入Chromium内核并实现类浏览器交互。内容围绕Tab分页管理、页面刷新、标签删除与新窗口创建四条主线展开涉及ChromiumWebBrowser多实例动态增删、Reload强制忽略缓存、CreateBrowser弹出窗口处理以及icudt.dll、libcef.dll等依赖库的部署排错思路适合需要自建定制浏览器或内嵌网页模块的中级开发者参考。压缩包共154个文件约19.34MB以106个pak资源包、8个dll动态库、6个exe可执行文件为主另含6个cs源码、5个config配置、sln与csproj工程文件及少量js、html、resx等工程结构完整可直接编译调试。目前已有4059人学习下载可帮助读者快速理解CefSharp分页与窗口管理的实现方式并掌握依赖库缺失时的排查方法。1. 从一次多标签翻车说起CefSharp 分页到底难在哪去年帮一个做数据看板的朋友改客户端需求听起来特别简单把内嵌的 Chromium 浏览器做成带标签页的样子能新建、能关闭、能刷新、还能拖出新窗口。他一开始觉得这不就是套个 TabControl 吗结果上线第一天就翻车——关掉一个标签页整个进程直接闪退新建三个标签页内存飙到 1.2G点刷新按钮页面白屏三秒才回来。问题出在 CefSharp 的浏览器实例生命周期和 WinForms/WPF 的控件树根本不是一回事。CefSharp 每个 ChromiumWebBrowser 背后是一个独立的渲染进程加一堆非托管资源你把它当普通 UserControl 塞进 TabPage 里关闭时释放顺序一乱非托管那边就炸了。这篇笔记就是围绕「cef分页」这个场景把标签页的创建、切换、刷新、关闭、拖出新窗口这几件事拆开讲清楚顺带把资源释放和进程模型这两个最容易踩的坑说明白。适合已经能在项目里跑起一个 CefSharp 浏览器、但一上多标签就出问题的 .NET 桌面开发者。2. 先搞懂 CefSharp 的进程模型再谈分页2.1 为什么不能把 ChromiumWebBrowser 当普通控件塞进 TabPageCefSharp 的架构里你的 .NET 进程只是「宿主」真正的页面渲染、JS 执行、网络请求跑在独立的渲染进程和 GPU 进程里。ChromiumWebBrowser 这个控件本质上是一个托管层的「把手」它通过 CEF 的 IPC 通道跟非托管侧通信。当你把它放进 TabPageWinForms 的控件树会认为它跟一个 Button 没区别关闭 TabPage 时调用 Dispose但 CEF 的非托管对象不一定跟着走。常见做法是每个标签页对应一个独立的 ChromiumWebBrowser 实例但这个实例的创建和销毁必须走 CEF 的生命周期回调而不是靠控件树的 Dispose 触发。我一般会用一个BrowserTabItem类把「标签页元数据」和「浏览器实例」绑在一起元数据包括标题、URL、加载状态浏览器实例单独管理。这样切换标签时只是切换显示层不销毁实例关闭标签时才真正走销毁流程。下面这个结构是我在几个项目里反复用过的public class BrowserTabItem { public string Id { get; } Guid.NewGuid().ToString(N); public string Title { get; set; } 新标签页; public string CurrentUrl { get; set; } about:blank; public ChromiumWebBrowser Browser { get; set; } public bool IsLoading { get; set; } }逻辑说明Id 用来在字典里做唯一索引避免用 TabPage 的 Handle 做 keyHandle 会变Title 和 CurrentUrl 是给 UI 层绑定的跟浏览器实例解耦Browser 字段持有真正的 CefSharp 实例。参数上没什么可调的关键是别把 Browser 直接暴露给 UI 层去 Dispose。2.2 初始化 CEF 时那几个必须写死的参数CefSharp 初始化有一堆设置多标签场景下最影响稳定性的是CefSettings里的几个开关。下面这段是我现在项目里的默认配置注释里写了每个参数为什么这么设var settings new CefSettings { // 多标签下缓存目录必须独立否则多个实例抢同一个 Cache 会锁死 CachePath Path.Combine(Environment.GetFolderPath( Environment.SpecialFolder.LocalApplicationData), MyApp, Cache), // 关闭 GPU 加速在部分老显卡上能避免白屏但会牺牲滚动流畅度 // 如果目标机器显卡较新可以设为 true // WindowlessRenderingEnabled false, // 日志级别设 Error不然多标签下日志文件涨得飞快 LogSeverity LogSeverity.Error, // 中文环境必须设否则部分页面字体渲染发虚 Locale zh-CN, // 多标签下建议开启减少渲染进程数量 // 但注意这个参数在部分 CefSharp 版本里行为不一致 // MultiThreadedMessageLoop true, }; Cef.Initialize(settings);逻辑说明CachePath 一定要指向一个可写目录默认路径在 Program Files 下会因为没有写权限导致初始化失败LogSeverity 设成 Error 是因为多标签场景下每个页面都会产生大量 Info 日志磁盘 IO 会成为瓶颈Locale 影响字体和日期格式中文项目不设会出各种显示问题。MultiThreadedMessageLoop 这个参数我踩过坑某些版本下开了之后关闭标签会卡死建议先保持默认遇到消息循环阻塞再针对性调整。2.3 标签页的创建流程从点击「」到页面加载完成新建标签页不是new ChromiumWebBrowser()就完事。正确的顺序是先在数据层创建一个 BrowserTabItem再创建 ChromiumWebBrowser 实例并挂到隐藏的容器上等IsBrowserInitializedChanged触发后再导航到目标 URL最后把 TabPage 显示出来。下面是一个简化版的创建方法private async TaskBrowserTabItem CreateTabAsync(string url) { var item new BrowserTabItem { CurrentUrl url }; var browser new ChromiumWebBrowser(); item.Browser browser; // 先挂到隐藏面板避免创建过程中闪烁 _hiddenPanel.Controls.Add(browser); browser.Dock DockStyle.Fill; // 等待浏览器内核初始化完成 var tcs new TaskCompletionSourcebool(); browser.IsBrowserInitializedChanged (s, e) { if (browser.IsBrowserInitialized) tcs.TrySetResult(true); }; await tcs.Task; // 初始化完成后再导航 browser.Load(url); // 订阅标题变化同步到标签页文字 browser.TitleChanged (s, e) { item.Title e.Title; // 这里触发 UI 更新事件不要直接跨线程操作控件 }; return item; }逻辑说明先挂到隐藏面板是为了让 CEF 完成句柄创建直接 new 出来不挂载就 Load 会偶发白屏IsBrowserInitializedChanged是 CefSharp 提供的初始化完成信号必须等它触发后再 Load否则导航请求会被丢弃TitleChanged 里更新 UI 要走 Invoke因为 CEF 的回调在非 UI 线程。参数上url 传about:blank可以创建一个空白页传具体地址就直接加载。3. 刷新、关闭、新窗口三个动作的实操细节3.1 刷新不是调 Refresh而是走 Reload 还是 LoadWinForms 控件的 Refresh 只是重绘对 CefSharp 没用。真正刷新页面要用browser.Reload()但这里有个分支如果页面是通过 POST 提交后到达的Reload 会弹出一个「是否重新提交表单」的对话框用户体验很差。我一般会判断一下当前 URL 和原始 URL 是否一致不一致就用Load(originalUrl)重新走 GET。下面这个封装方法处理了大部分场景public void RefreshTab(BrowserTabItem item, bool forceGet false) { if (item.Browser null || !item.Browser.IsBrowserInitialized) return; if (forceGet) { // 强制走 GET避免 POST 重提交弹窗 item.Browser.Load(item.CurrentUrl); } else { item.Browser.Reload(); } }逻辑说明forceGet 参数给调用方一个选择比如工具栏上的刷新按钮可以传 false而「重新加载」菜单项传 trueCurrentUrl 要在每次导航完成后更新否则 Load 会加载旧地址。参数上没什么玄学关键是别在浏览器未初始化时调用会抛异常。3.2 关闭标签页时的释放顺序错一步就闪退关闭标签页是整个分页逻辑里最容易翻车的地方。我见过最多的错误写法是直接tabPage.Dispose()然后 ChromiumWebBrowser 的非托管资源没释放下次创建新标签时 CEF 内部状态错乱直接闪退。正确的顺序是先从 UI 容器移除 TabPage再调用browser.Dispose()最后从数据字典里移除 BrowserTabItem。下面这段代码我用了两年多没再出现过关闭闪退public void CloseTab(string tabId) { if (!_tabs.TryGetValue(tabId, out var item)) return; // 1. 先从 UI 移除避免 Dispose 过程中控件还在渲染 if (item.Browser ! null item.Browser.Parent ! null) { item.Browser.Parent.Controls.Remove(item.Browser); } // 2. 取消所有事件订阅防止 Dispose 后回调还在触发 item.Browser.TitleChanged - OnTitleChanged; item.Browser.LoadingStateChanged - OnLoadingStateChanged; // 3. 释放浏览器实例 item.Browser.Dispose(); item.Browser null; // 4. 从数据层移除 _tabs.Remove(tabId); // 5. 如果关闭的是当前选中标签切到相邻标签 if (_activeTabId tabId) { var next _tabs.Values.FirstOrDefault(); if (next ! null) SwitchToTab(next.Id); } }逻辑说明第 1 步先移除控件是为了让 CEF 知道这个浏览器不再显示触发内部清理第 2 步取消事件订阅很关键Dispose 之后 CEF 可能还有异步回调进来不取消会访问已释放对象第 3 步 Dispose 是同步的但内部会等待渲染进程退出如果页面有大量 JS 定时器这里可能卡几百毫秒建议放到后台线程或者加个超时提示。参数上tabId 用 Guid 字符串别用 TabPage 的索引索引会变。3.3 拖出新窗口用 CefLifeSpanHandler 接管弹窗「新窗口」这个需求有两种理解一种是用户点页面里的target_blank链接另一种是拖拽标签页到窗口外。前者要靠CefLifeSpanHandler的OnBeforePopup回调拦截后者是 UI 层的拖拽逻辑。先讲前者这是 CefSharp 分页里必须处理的public class PopupHandler : CefLifeSpanHandler { private readonly Actionstring _onNewWindow; public PopupHandler(Actionstring onNewWindow) { _onNewWindow onNewWindow; } protected override bool OnBeforePopup( IWebBrowser chromiumWebBrowser, IBrowser browser, IFrame frame, string targetUrl, string targetFrameName, WindowOpenDisposition targetDisposition, bool userGesture, IPopupFeatures popupFeatures, IWindowInfo windowInfo, IBrowserSettings browserSettings, ref bool noJavascriptAccess, out IWebBrowser newBrowser) { // 返回 true 表示自己处理不创建 CEF 默认弹窗 newBrowser null; _onNewWindow?.Invoke(targetUrl); return true; } }逻辑说明OnBeforePopup返回 true 表示拦截返回 false 表示让 CEF 自己弹窗。这里我们拦截后调用外部传入的回调在回调里创建一个新的标签页。参数里 targetUrl 是目标地址targetDisposition 表示打开方式新窗口、新标签、当前窗口等可以根据这个值决定是新建标签还是当前标签导航。注意newBrowser必须设为 null否则 CEF 会尝试用你返回的实例容易出问题。拖拽标签出窗口的逻辑属于 WinForms 的 DragDrop 范畴核心是监听 TabControl 的 MouseDown 和 MouseMove判断拖拽距离超过阈值后创建一个新的 Form把对应的 ChromiumWebBrowser 从原容器移除再挂到新 Form 上。这里有个坑ChromiumWebBrowser 不能同时属于两个父容器必须先 Remove 再 Add而且 Remove 之后要等一个消息循环周期再 Add否则句柄会失效。4. 避坑与排查多标签场景下最常见的五个问题4.1 关闭标签后进程残留任务管理器里一堆 CefSharp.BrowserSubprocess现象关闭所有标签页后任务管理器里还有多个 CefSharp.BrowserSubprocess.exe 进程不退出。原因CEF 的渲染进程是独立进程正常情况下会在浏览器实例 Dispose 后几秒内退出但如果页面里有未清理的 JS 定时器、WebSocket 连接或者 Service Worker渲染进程会保持存活。解决在关闭标签前先执行一段清理脚本把定时器和连接断掉然后调用browser.Dispose()再在应用退出时调用Cef.Shutdown()。我一般会在 CloseTab 里加一步// 关闭前先执行清理脚本 await item.Browser.EvaluateScriptAsync( window.clearInterval window.clearInterval(); if (window.socket) window.socket.close(); );4.2 新建标签页时白屏等几秒才显示内容现象点「」新建标签后标签页是空白的过几秒才加载出来。原因ChromiumWebBrowser 创建后需要时间初始化渲染进程如果直接 Load 会丢请求。解决等IsBrowserInitializedChanged触发后再 Load前面 2.3 节的代码已经处理了。如果还是白屏检查是不是把浏览器实例挂到了一个不可见的容器上CEF 在控件不可见时可能不渲染。4.3 切换标签时页面滚动位置丢失现象从标签 A 切到标签 B 再切回来A 的滚动位置回到了顶部。原因切换标签时如果用了Controls.Clear()或者把浏览器实例从容器移除再添加CEF 会认为页面重新可见触发重新布局。解决不要移除浏览器实例用多个 Panel 叠放通过Visible属性切换或者用BringToFront()。这样浏览器实例始终在控件树里滚动位置会保留。4.4 刷新时弹出「是否重新提交表单」现象在 POST 提交后的页面上点刷新弹出一个对话框问是否重新提交。原因Reload()会重放最后一次导航请求如果是 POST 就会触发浏览器默认行为。解决在导航完成后记录原始 URL 和请求方法刷新时判断如果是 POST 就改用Load(originalUrl)走 GET。CefSharp 的RequestHandler可以拿到请求方法在OnBeforeBrowse里记录。4.5 多标签下内存暴涨开十个标签就上 G现象每开一个新标签内存涨 80-150M开十个就上 G。原因每个 ChromiumWebBrowser 默认会创建独立的渲染进程进程间不共享缓存和 JS 堆。解决在 CefSettings 里开启MultiThreadedMessageLoop和CachePath共享同时限制同时打开的标签数量超过阈值时关闭最久未使用的标签。另外可以在OnBeforePopup里判断如果是同域链接直接在当前标签导航而不是新建。5. 进阶技巧用请求拦截给分页加一层「后悔药」多标签场景下还有一个隐藏需求用户误关了标签想恢复。这个功能靠 CefSharp 本身没有直接支持但可以用RequestHandler拦截导航请求把每个标签的 URL 历史存下来关闭时不清除提供一个「最近关闭」列表。下面是我在项目里实现的一个简化版public class NavigationRecorder : RequestHandler { private readonly Actionstring, string _onNavigated; public NavigationRecorder(Actionstring, string onNavigated) { _onNavigated onNavigated; } public override bool OnBeforeBrowse( IWebBrowser browser, IRequest request, bool isRedirect, bool isMainFrame) { if (isMainFrame) { // 记录主框架的导航子框架忽略 _onNavigated?.Invoke( browser.GetHost().GetBrowser().Identifier.ToString(), request.Url); } return false; // 不拦截继续导航 } }逻辑说明OnBeforeBrowse在主框架导航前触发request.Url是目标地址browser.Identifier可以关联到具体的标签页。返回 false 表示不拦截让导航继续。参数上isMainFrame 用来过滤 iframe 的导航不然一个页面里的广告 iframe 会污染历史记录。把记录下来的 URL 存到一个栈里关闭标签时不清栈提供一个快捷键比如 CtrlShiftT弹出最近关闭列表选中后重新创建标签并 Load 对应 URL。这个「后悔药」机制我建议配合一个上限比如只保留最近 20 条不然长时间运行内存会慢慢涨。另外要注意如果用户关闭标签时页面还在加载记录的 URL 可能是中间状态最好在LoadingStateChanged里等加载完成再记录最终 URL。从那以后我每次做 CefSharp 分页都会先把关闭流程和进程退出检查跑一遍确认没有残留进程再继续写业务逻辑。希望帮到你。本文还有配套的精品资源点击获取

相关新闻

2026年网络安全转行全解析:岗位供需、薪资真相与实战路径

2026年网络安全转行全解析:岗位供需、薪资真相与实战路径

“现在转网安还来得及吗?”“听说2026年缺口300万,是真的吗?”“35岁转行会不会太晚?”——2026年了,这些问题依然在各大社区被反复刷屏。我做了近十年安全工作,从乙方驻场干到甲方安全负责人,也…

2026/10/13 3:47:55 阅读更多 →
Windows文件夹被占用无法删除?用句柄机制和Process Explorer五分钟定位真凶

Windows文件夹被占用无法删除?用句柄机制和Process Explorer五分钟定位真凶

遇到这种问题真的烦:右键删除一个文件夹,Windows 弹出一句“操作无法完成,因为文件夹已在另一个程序中打开”,英文环境就是 Unable to delete / Unable to rename。文件夹就那么杵在屏幕上,删不掉也改不了名&#xff0…

2026/10/13 3:47:55 阅读更多 →
从命令行到图形界面:VS Code CMake Tools配置C++项目全攻略

从命令行到图形界面:VS Code CMake Tools配置C++项目全攻略

每次要在命令行里敲cmake -S . -B build -DCMAKE_BUILD_TYPEDebug,再敲cmake --build build --config Debug,然后才敢打开VS Code里写代码——这种状态我持续了挺长时间。直到我把配置切到VS Code的CMake Tools扩展上,情况才彻底变了。这篇指…

2026/10/13 3:47:55 阅读更多 →

最新新闻

西门子PLC实战:锂电浆料线程序开发与通讯调试全流程

西门子PLC实战:锂电浆料线程序开发与通讯调试全流程

很多人找我聊西门子PLC程序开发怎么入门,我一般不会直接甩教程,而是先反问一句:你见过新能源浆料线吗?去年我接手了一条锂电正极浆料产线的整套PLC程序开发,从需求梳理到带料调试,前后折腾了小半年。说句实…

2026/10/13 5:56:39 阅读更多 →
逻辑回归原理与实战:从线性回归到分类算法的完整指南

逻辑回归原理与实战:从线性回归到分类算法的完整指南

第一次接触逻辑回归的人,十有八九会被它的名字带偏——明明叫“回归”,翻开文档才发现它是个分类算法。我刚入行那年就踩过这个认知坑,一度以为它是用来预测房价的。后来在好几个分类项目里把它用熟了,才意识到这个“名字有歧义”…

2026/10/13 5:56:39 阅读更多 →
表格解析新思路:先看懂结构再生成答案,复杂表格不再答错

表格解析新思路:先看懂结构再生成答案,复杂表格不再答错

表格解析这个方向,做文档智能的人应该都不陌生。以前大家的习惯做法很简单:直接把一整张表格图像和问题一起丢给多模态模型,让它端到端输出答案。看着挺省事,但一落到真实场景——带合并单元格的财务报表、跨页的长规格书、行列爆…

2026/10/13 5:56:39 阅读更多 →
已完成研究,现在撰写文章

已完成研究,现在撰写文章

桌面应用 【免费下载链接】QOwnNotes QOwnNotes is a plain-text file notepad and todo-list manager with Markdown support and Nextcloud / ownCloud integration. 项目地址&#xff1a; https://gitcode.com/gh_mirrors/qo/QOwnNotes 点击查看 免费下载 <output_artic…

2026/10/13 5:56:39 阅读更多 →
快速去掉 Windows 与 Office 未激活水印:MAS 3.12 免费激活四步指南

快速去掉 Windows 与 Office 未激活水印:MAS 3.12 免费激活四步指南

快速去掉 Windows 与 Office 未激活水印&#xff1a;MAS 3.12 免费激活四步指南 【免费下载链接】Microsoft-Activation-Scripts Open-source Windows and Office activator featuring HWID, Ohook, TSforge, and Online KMS activation methods, along with advanced troubles…

2026/10/13 5:56:39 阅读更多 →
ZMK 电源管理行为实战:用 `ext_power` 控制外部电源(VCC)输出以节省电量

ZMK 电源管理行为实战:用 `ext_power` 控制外部电源(VCC)输出以节省电量

固件嵌入式智能硬件蓝牙 【免费下载链接】zmk ZMK Firmware Repository 项目地址&#xff1a; https://gitcode.com/gh_mirrors/zm/zmk 点击查看 免费下载 本文以 ZMK 固件仓库中的电源管理行为文档&#xff08;docs/docs/keymaps/behaviors/power.md&#xff09;为核心&#…

2026/10/13 5:55:39 阅读更多 →

日新闻

物联网系统集成避坑指南:从选型到验收的关键要点

物联网系统集成避坑指南:从选型到验收的关键要点

做了这么多年物联网系统集成的项目&#xff0c;我最大的感受是&#xff0c;大部分坑不是出在技术前沿&#xff0c;而是出在一些看起来很平常的地方。设备选了便宜的、协议没问清楚、现场没摸透、验收标准写得含糊——这些细枝末节&#xff0c;最后都会变成延期、超支和扯皮的大…

2026/10/13 0:00:57 阅读更多 →
ESP32舵机控制全攻略:从PWM原理到高级玩法与常见故障排查

ESP32舵机控制全攻略:从PWM原理到高级玩法与常见故障排查

如果你玩ESP32有一阵子了&#xff0c;很难不碰到舵机控制这道坎。做智能小车要转方向&#xff0c;做机械臂要抬关节&#xff0c;做云台要偏航俯仰&#xff0c;背后都是同一个东西在干活&#xff1a;舵机。而我见过太多人卡在“代码能编译、舵机就是不听话”这个阶段&#xff0c…

2026/10/13 0:00:57 阅读更多 →
CAN总线原理与STM32实战:抗干扰、仲裁、错误处理全解析

CAN总线原理与STM32实战:抗干扰、仲裁、错误处理全解析

1. 为什么CAN总线至今仍是工业现场的“硬通货”&#xff1f;——从汽车底盘到智能温室的真实需求你有没有拆过一辆老款燃油车的中控台&#xff1f;或者调试过某高校实验室里那台跑着Modbus协议却频频丢帧的PLC控制柜&#xff1f;又或者&#xff0c;在某个农业物联网项目里&…

2026/10/13 0:00:57 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介&#xff1a;基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码&#xff0c;面向计算机相关专业课程设计与期末大作业学生&#xff0c;以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程&#xff0c;…

2026/10/13 2:54:56 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程&#xff1a;键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化&#xff0c;十个新手有八个栽在"往输入框里填东西"这件事上&#xff1a;要么填不进去&#xff0c;要么填了一半&#xff0c;要么直接把原来内容追加在后面。这背后的根因&…

2026/10/12 0:16:38 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程&#xff1a;阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀&#xff1a;什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面&#xff0c;跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/13 1:58:08 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

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

2026/10/12 21:05:09 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

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

2026/10/12 14:48:49 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

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

2026/10/12 18:11:17 阅读更多 →