WinForm分页控件实战:SQL Server OFFSET-FETCH高效分页方案

简介:这是一份面向Windows Forms初学者与中级开发者的实用分页控件集成方案,专为解决WinForm项目中大数据量展示导致的界面卡顿、内存占用高等常见问题而设计。资源提供开箱即用的自定义分页控件(PagerControl.cs及配套设计器文件),完整封装SQL Server分页逻辑(支持OFFSET/FETCH分页查询),并附带可直接运行的演示窗体(Form1、Form2、StartForm)与数据库连接示例,覆盖UI布局、数据绑定、页码导航、总数计算等核心环节。压缩包共65个文件,含20个C#源码(含主控件、窗体、配置类)、8个resx本地化资源、2个sln/suo解决方案工程、2个csproj项目文件,以及升级日志、说明文档和UI图标等辅助文件,整体仅218KB,轻量易集成。目前已有837人学习下载,适合快速嵌入现有WinForm项目、理解分页底层实现或作为教学演示范例使用。

1. WinForm 分页控件不是“加个控件就完事”:它本质是数据库查询+UI状态+内存边界三重协同的落地接口

你有没有试过在 WinForm 里拖一个 DataGridView,绑上几百条数据,再点“下一页”——界面卡死两秒、滚动条跳变、翻到第 50 页突然报OutOfMemoryException?这不是你代码写得烂,而是 WinForm 原生控件压根没提供分页语义:它默认全量加载、全量渲染。所谓“很好用的分页控件”,核心不在 UI 多漂亮,而在于它把SQL 分页查询逻辑(OFFSET-FETCH / ROW_NUMBER)、UI 状态同步(当前页/总页数/每页条数)、以及数据缓存生命周期(查完即弃 or 局部缓存)封装成一个可复用、可配置、不黑匣子的组件。它适合两类人:一是正在维护老系统、不能动数据库结构、又急需改善响应速度的某公司桌面端开发;二是带学生做课程设计的某导师——学生不用啃 T-SQL 分页语法,也能做出带页码跳转、记录数统计、禁用无效按钮的完整界面。这个资源不是“美化控件”,它是 WinForm 生态里少有的、把数据库分页从“手写 SQL + 手算页码 + 手管状态”的玄学操作,拉回工程化轨道的实操包。


2. 分页控件的底层逻辑:为什么必须绕开 DataSet.Fill() 全量加载?

2.1 传统做法的致命缺陷:全量加载是性能毒药

很多开发者习惯用SqlDataAdapter.Fill(DataSet)把整张表读进内存,再用BindingSource.Position做“伪分页”。这在测试数据 100 条时很丝滑,但一旦真实表有 50 万行,问题立刻暴露:

  • 启动慢:首次加载就要执行SELECT * FROM Orders,网络传输+反序列化耗时不可控;
  • 内存炸:DataTable每行约占用 200~500 字节(取决于字段数和长度),50 万行轻松吃掉 200MB+;
  • 状态错乱:用户快速点击“下一页”时,Position可能还没更新完,CurrentPage显示为 3,实际渲染的是第 2 页数据。

提示:这不是 WinForm 的锅,是误把“数据绑定”当“分页方案”。真正的分页必须发生在数据库层,让 SQL Server 自己裁剪结果集。

2.2 正确分页路径:SQL Server 2012+ 的 OFFSET-FETCH 是唯一推荐方案

该资源采用OFFSET @skip ROWS FETCH NEXT @take ROWS ONLY语法,原因明确:

  • 语义清晰:@skip = (currentPage - 1) * pageSize,@take = pageSize,无歧义;
  • 执行计划友好:SQL Server 能基于索引高效跳过前 N 行(尤其主键有序时);
  • 规避 ROW_NUMBER() 的隐式排序陷阱:ROW_NUMBER() OVER (ORDER BY id)强制要求ORDER BY,而 OFFSET-FETCH 允许ORDER BY缺失(虽不推荐,但兼容性更强)。

下面这段是控件内部执行的核心查询模板(已脱敏):

-- 控件生成的实际 SQL(参数化,防注入) SELECT [Id], [Name], [Amount], [CreateTime] FROM [Orders] WHERE [Status] = @status ORDER BY [Id] DESC OFFSET @skip ROWS FETCH NEXT @take ROWS ONLY;
  • @skip和@take由控件根据用户点击的页码自动计算,绝不拼接字符串;
  • ORDER BY必须存在(否则 OFFSET-FETCH 报错),且建议用聚集索引列(如Id),避免排序开销;
  • WHERE条件由调用方传入(如new SqlCondition("Status", "=", 1)),控件只负责分页,不侵入业务逻辑。

2.3 控件如何与 UI 解耦?靠三个契约接口

该资源不是“一个 UserControl 拖进去就用”,而是通过接口定义职责边界:

  • IDataSource<T>:声明Task<(List<T>, int total)> GetPageAsync(int page, int size),返回当前页数据 + 总记录数;
  • IPagingConfig:配置PageSize(默认 20)、MaxPageCount(防用户输 999999)、EnableJump(是否显示跳转框);
  • IPagingView:定义 UI 方法如ShowLoading(),BindData(List<T>),UpdatePagerInfo(int currentPage, int totalPages)。

这样,你可以用SqlDataSource<Order>连 SQL Server,也可以换成ApiDataSource<Order>调 HTTP 接口,UI 层(WinForm Form)只需实现IPagingView,完全隔离数据源变化。我一般会把SqlDataSource封装成单例,避免每次翻页都新建SqlConnection。


3. 集成步骤:从 NuGet 安装到绑定 SQL Server 数据库(含完整代码)

3.1 安装与引用:仅需两个动作

该资源以.nupkg包形式发布,不依赖任何第三方 UI 库(如 DevExpress),纯 .NET Framework 4.6.1+ 可用。安装命令如下:

# 在 Visual Studio 包管理器控制台中执行(项目需为 .NET Framework) Install-Package WinFormPagingControl -Version 2.3.1
  • 安装后,工具箱会自动出现PagingDataGridView和PagingToolBar两个控件;
  • 不需要手动添加using,设计器会自动生成private PagingDataGridView pagingDataGridView1;;
  • 若提示“未找到控件”,右键工具箱 → “选择项” → 勾选WinFormPagingControl.dll。

3.2 数据源准备:建一个可测试的 Orders 表

为验证效果,先在本地 SQL Server 创建测试表(SQL Server Management Studio 执行):

CREATE TABLE [Orders] ( [Id] INT IDENTITY(1,1) PRIMARY KEY, [Name] NVARCHAR(100) NOT NULL, [Amount] DECIMAL(18,2) DEFAULT 0, [Status] TINYINT DEFAULT 0, -- 0:待处理, 1:已完成 [CreateTime] DATETIME2 DEFAULT GETDATE() ); -- 插入 10 万测试数据(用循环,非重点,略)

注意:Id必须是主键或有唯一索引,否则ORDER BY Id DESC无法利用索引加速 OFFSET。

3.3 代码绑定:5 行完成分页初始化

在 WinForm 的Form_Load事件中编写:

private async void Form1_Load(object sender, EventArgs e) { // 1. 创建数据源(连接字符串来自配置文件) var connectionString = ConfigurationManager.ConnectionStrings["Default"].ConnectionString; var dataSource = new SqlDataSource<Order>(connectionString); // 2. 设置查询语句(注意:必须含 ORDER BY) dataSource.SelectCommand = @" SELECT [Id], [Name], [Amount], [Status], [CreateTime] FROM [Orders] WHERE [Status] = @status ORDER BY [Id] DESC"; // 3. 绑定参数(支持多个,按名称匹配) dataSource.Parameters.Add(new SqlParameter("@status", SqlDbType.TinyInt) { Value = 1 }); // 4. 关联控件 pagingDataGridView1.DataSource = dataSource; // 5. 启动加载第 1 页(自动触发) await pagingDataGridView1.LoadFirstPageAsync(); }
  • SqlDataSource<Order>泛型确保编译期类型安全,Order类属性名必须与 SQL 字段名一致(大小写不敏感);
  • SelectCommand中的@status参数会被自动识别并绑定,无需手动AddWithValue;
  • LoadFirstPageAsync()是异步方法,不会阻塞 UI 线程,控件内部已处理await和线程切换。

3.4 配置分页行为:通过属性面板或代码

所有配置均可在设计器属性面板修改,也可代码设置(推荐代码,便于版本控制):

// 设置每页 25 条,最多显示 100 页(防恶意请求) pagingDataGridView1.PageSize = 25; pagingDataGridView1.MaxPageCount = 100; // 隐藏“跳转到”输入框,只留首页/上页/下页/末页按钮 pagingDataGridView1.ShowJumpBox = false; // 自定义页码文本(支持中文) pagingDataGridView1.PageText = "第 {0} 页,共 {1} 页"; pagingDataGridView1.RecordText = "共 {0} 条记录";
  • MaxPageCount是关键防护:若总记录 100 万,PageSize=10,则总页数 10 万,用户输入页码99999时,控件会自动截断为100,避免OFFSET 999990 ROWS导致查询超时;
  • PageText和RecordText支持{0}{1}占位符,{0}是当前页,{1}是总页数,{2}是总记录数(部分版本支持)。

4. 避坑指南:五个血泪经验换来的常见问题排查清单

4.1 现象:点击“下一页”无反应,Network Monitor 显示无 SQL 请求

原因:控件检测到SelectCommand为空或未设置ORDER BY子句,主动拒绝执行(安全机制)。
解决:检查dataSource.SelectCommand是否赋值,且字符串中是否包含ORDER BY(哪怕只是ORDER BY (SELECT NULL)也行,但不推荐)。用 SQL Server Profiler 抓包确认实际发送的 SQL。

4.2 现象:第 1 页正常,翻到第 2 页时抛出SqlException: Invalid column name 'xxx'

原因:SelectCommand中的字段别名与Order类属性名不匹配,且启用了AutoMapColumns=true(默认开启)。例如 SQL 返回SELECT OrderId AS Id,但Order.Id是int,而OrderId是string,类型映射失败。
解决:关闭自动映射dataSource.AutoMapColumns = false,改用手动映射:

dataSource.ColumnMappings.Add("OrderId", "Id"); dataSource.ColumnMappings.Add("OrderName", "Name");

4.3 现象:总记录数始终为 0,页码显示“第 1 页,共 0 页”

原因:控件执行SELECT COUNT(*)获取总数时,WHERE条件中的参数未正确传递。常见于Parameters.Add()时Value为null,而 SQL Server 对@param = null的比较结果为UNKNOWN,导致COUNT为 0。
解决:确保所有参数Value非空,或改用ISNULL(@param, 0):

// 错误写法 dataSource.Parameters.Add(new SqlParameter("@status", SqlDbType.TinyInt) { Value = null }); // 正确写法 dataSource.Parameters.Add(new SqlParameter("@status", SqlDbType.TinyInt) { Value = DBNull.Value }); // 或 SQL 中写 WHERE [Status] = ISNULL(@status, [Status])

4.4 现象:快速连续点击“下一页”,UI 显示错乱(如页码跳到 5,数据却是第 3 页)

原因:异步请求未加锁,第 2 页请求未返回时,第 3 页请求已发出,后发先至。控件默认启用EnableConcurrentRequest = false,但若手动设为true则需自行处理。
解决:保持默认false,并在 UI 层增加防抖:

private bool _isBusy = false; private async void btnNext_Click(object sender, EventArgs e) { if (_isBusy) return; _isBusy = true; try { await pagingDataGridView1.LoadNextPageAsync(); } finally { _isBusy = false; } }

4.5 现象:DataGridView 行高不一致,部分行文字被截断

原因:控件默认启用AutoSizeRowsMode = DataGridViewAutoSizeRowsMode.AllCells,但PagingDataGridView继承自DataGridView,其RowTemplate.Height被重置为默认值 22,而字体变大后内容撑不开。
解决:在设计器中选中控件 → 属性面板 →RowTemplate→Height设为 30;或代码设置:

pagingDataGridView1.RowTemplate.Height = 30; pagingDataGridView1.DefaultCellStyle.WrapMode = DataGridViewTriState.True; // 允许换行

5. 进阶技巧:自定义分页样式、导出当前页、以及应对大数据量的缓存策略

5.1 样式定制:用 CSS-like 方式重绘分页栏

PagingToolBar控件暴露了PagerStyle属性,支持细粒度控制每个元素的外观。例如,让“首页”按钮变蓝、“禁用按钮”变灰:

// 在 Form_Load 中设置 pagingToolBar1.PagerStyle.FirstButtonStyle.BackColor = Color.FromArgb(65, 105, 225); pagingToolBar1.PagerStyle.FirstButtonStyle.ForeColor = Color.White; pagingToolBar1.PagerStyle.DisabledButtonStyle.BackColor = Color.LightGray; pagingToolBar1.PagerStyle.DisabledButtonStyle.ForeColor = Color.DarkGray; // 自定义分隔符(默认是"|",可改为"»") pagingToolBar1.PagerStyle.SeparatorText = " » ";
  • 所有*ButtonStyle都继承自ButtonStyle,包含BackColor、ForeColor、Font、Padding;
  • SeparatorText支持 Unicode 符号,比纯文字更显专业;
  • 若需圆角按钮,需重写OnPaint,但该资源已内置RoundedCorners = true属性,直接设为true即可。

5.2 导出当前页数据:不查库、不重复请求的轻量方案

用户常提需求:“把当前看到的这 20 条导出 Excel”。控件提供ExportCurrentPage()方法,原理是直接序列化BindingSource.List(即当前页的List<T>),而非重新查库:

private void btnExport_Click(object sender, EventArgs e) { // 1. 获取当前页数据(已在内存,毫秒级) var currentPageData = pagingDataGridView1.GetCurrentPageData(); // 2. 使用 EPPlus(需 NuGet 安装)导出 using (var package = new ExcelPackage()) { var worksheet = package.Workbook.Worksheets.Add("Orders"); worksheet.Cells["A1"].LoadFromCollection(currentPageData, true); var stream = new MemoryStream(package.GetAsByteArray()); // 3. 保存文件 SaveFileDialog dialog = new SaveFileDialog { Filter = "Excel files (*.xlsx)|*.xlsx", FileName = $"Orders_Page{pagingDataGridView1.CurrentPage}.xlsx" }; if (dialog.ShowDialog() == DialogResult.OK) { File.WriteAllBytes(dialog.FileName, stream.ToArray()); } } }
  • GetCurrentPageData()返回IList<T>,类型安全,无需反射;
  • 导出内容与界面上显示的完全一致(含格式化后的列,如金额保留两位小数);
  • 不触发新 SQL 查询,对数据库零压力。

5.3 大数据量场景:启用本地缓存,避免重复查询

当用户频繁在相邻页(如第 49、50、51 页)间跳转时,反复查库浪费资源。控件支持LocalCacheSize属性,启用 LRU 缓存:

// 缓存最近 5 页的数据(每页 20 条,约 100 条记录) pagingDataGridView1.LocalCacheSize = 5; // 缓存键规则:"{TableName}_{WhereHash}_{OrderBy}_{PageSize}_{PageNumber}" // 例如:"Orders_7a8b9c_Id_DESC_20_49"
  • 缓存存储在ConcurrentDictionary<string, object>中,线程安全;
  • LocalCacheSize = 0(默认)表示禁用缓存;设为5时,第 50 页被缓存,第 45 页被踢出;
  • 重要限制:缓存仅对SqlDataSource有效,且SelectCommand不能含GETDATE()等运行时函数(否则哈希值总变);
  • 缓存对象是List<T>,内存占用可控,100 条Order对象约 200KB。

5.4 最后一个技巧:调试时快速查看生成的 SQL

开发阶段最头疼的是“我写的条件到底生成了什么 SQL?”。控件内置DebugMode属性,开启后会在 Visual Studio 输出窗口打印完整 SQL:

// 开发时启用,上线前注释掉 pagingDataGridView1.DebugMode = true; // 输出示例: // [PagingControl] Executing SQL: // SELECT [Id], [Name] FROM [Orders] WHERE [Status] = @p0 ORDER BY [Id] DESC OFFSET 20 ROWS FETCH NEXT 20 ROWS ONLY // Parameters: @p0=1
  • DebugMode仅影响输出,不改变执行逻辑;
  • 参数值会脱敏(@p0=1而非@p0='admin'),符合安全规范;
  • 我从那以后每次集成新数据源,都强制走一遍DebugMode = true,看三遍 SQL 再提交代码——省去一半数据库抓包时间。希望帮到你。

本文还有配套的精品资源,点击获取

相关新闻

SpringBoot+Vue企业绩效量化管理系统设计与实现全解析

SpringBoot+Vue企业绩效量化管理系统设计与实现全解析

SpringBootVue 这套组合在 Java Web 毕业设计里&#xff0c;可以说是现象级的存在。但绝大多数同学的问题不是不会写代码&#xff0c;而是不知道一个“完整项目”到底该装进哪些东西&#xff1a;后端怎么写才能让代码看起来不像增删改查练习册&#xff1f;数据库脚本怎么设计才…

2026/10/20 3:08:54 阅读更多 →
量化AI系统稳定性:KCVI稳定性评估框架全解析

量化AI系统稳定性:KCVI稳定性评估框架全解析

在AI系统大面积铺开之后&#xff0c;很多基础设施团队都会遇到同一个尴尬的问题&#xff1a;所有的监控面板都是绿的&#xff0c;可是业务方反馈越来越差&#xff0c;模型效果莫名其妙地波动&#xff0c;系统时不时抽风&#xff0c;但你查不出个所以然。传统那套“可用性”“响…

2026/10/20 3:08:54 阅读更多 →
Databricks 中无法导入 pyspark.pipelines 的原因与解决

Databricks 中无法导入 pyspark.pipelines 的原因与解决

按 Lakeflow Declarative Pipelines 的 Python 开发方式写代码,第一行通常是 from pyspark import pipelines as dp。但把这行原样贴进一个普通 Databricks notebook 里执行,会直接报导入错误。 问题现象 在 notebook 顶部执行: from pyspark import pipelines as dp报错…

2026/10/20 3:08:54 阅读更多 →

最新新闻

从囤积资料到掌握技能:个人技能管理闭环方法论

从囤积资料到掌握技能:个人技能管理闭环方法论

你有没有过这种感觉&#xff1a;收藏夹里躺了几百个教程&#xff0c;网盘里存了一堆学习资料&#xff0c;每天刷各种干货文章&#xff0c;觉得自己一直在"学东西"&#xff0c;可真到了要用的时候&#xff0c;却发现自己什么都不会&#xff1f;我太熟悉这种状态了。前…

2026/10/21 4:32:49 阅读更多 →
OpenClaw实战:让大模型能动手的本地执行引擎

OpenClaw实战:让大模型能动手的本地执行引擎

1. 为什么需要OpenClaw&#xff1a;从聊天到执行的缺口1.1 聊天再聪明&#xff0c;也替你做不了事这两年我用过不少AI助手&#xff0c;从网页版到本地部署的各种开源模型都折腾过。说实话&#xff0c;光论对话能力&#xff0c;现在的开源模型已经相当能打了。写文案、列提纲、解…

2026/10/21 4:32:49 阅读更多 →
REA:面向高频数据场景的前端实时事件聚合器设计与实践

REA:面向高频数据场景的前端实时事件聚合器设计与实践

半年前我在本地新建了一个空目录&#xff0c;名字就叫rea&#xff0c;没有说明文档&#xff0c;没有 package.json&#xff0c;只有三个字母。后来我给它补全了一个含义&#xff1a;REA Real-time Event Aggregator&#xff0c;一个轻量级实时事件聚合器。这算是把项目名从“名…

2026/10/21 4:32:49 阅读更多 →
发布-订阅模式实战:系统解耦、消息中间件与故障排查全解析

发布-订阅模式实战:系统解耦、消息中间件与故障排查全解析

做后端开发的这几年&#xff0c;我遇到最多的设计问题之一就是系统之间怎么通信。早期项目里&#xff0c;A系统的数据一变&#xff0c;就得硬编码调用B系统、C系统的接口&#xff0c;改一个接口就要联动改好几个调用方。中间只要有一个系统升级&#xff0c;整个调用链就跟着紧张…

2026/10/21 4:32:49 阅读更多 →
C#纯托管读取PCD点云:格式解析、避坑指南与二次开发

C#纯托管读取PCD点云:格式解析、避坑指南与二次开发

简介&#xff1a;一套C#读取点云数据的示例工程&#xff0c;面向C#初级开发者与三维视觉学习者&#xff0c;演示如何从文本文件中逐行读取点云数据&#xff0c;并将大量离散的三维坐标点解析为Point3D对象&#xff0c;适用于3D重建、机器视觉、自动驾驶等场景的入门实践。压缩包…

2026/10/21 4:32:49 阅读更多 →
项目标题‘rea’解析:为何模糊缩写无法支撑技术内容创作

项目标题‘rea’解析:为何模糊缩写无法支撑技术内容创作

项目标题是“rea”&#xff0c;但提供的输入内容中&#xff0c;项目正文为空、关键词未列出、摘要描述缺失&#xff0c;且网络搜索部分为纯空行&#xff08;&#xff09;&#xff0c;无任何实际信息支撑。作为一名从业十多年、每天要拆解几十个真实项目标题的资深博主&#xff…

2026/10/21 4:31:49 阅读更多 →

日新闻

统一内存+量化:锐龙AI Max PRO 400如何本地跑320B大模型

统一内存+量化:锐龙AI Max PRO 400如何本地跑320B大模型

这段时间硬件圈和AI圈都在传一个事&#xff1a;AMD锐龙AI Max PRO 400系列把最高192GB统一内存搬进了PC形态的产品里&#xff0c;按宣传口径&#xff0c;本地跑320B参数的大家伙不再是纸面概念。我第一反应倒不是“内存真大”&#xff0c;而是这话背后的含金量——它把过去只能…

2026/10/21 0:00:11 阅读更多 →
AI助手深度使用体验:对话质量、长文本处理与指令遵循如何留住用户

AI助手深度使用体验:对话质量、长文本处理与指令遵循如何留住用户

1. 从“封号重开”这个行为说起&#xff1a;用户到底在追求什么“封号重开”这四个字&#xff0c;放在任何产品语境里都带着一股决绝的味道。一个账号从零开始积累使用记录、调教偏好、沉淀工作流&#xff0c;背后是大量的时间和精力成本。正常情况下&#xff0c;没有人愿意主动…

2026/10/21 0:00:11 阅读更多 →
ROS中YOLOv3工程落地:通信同步、坐标变换与性能调优

ROS中YOLOv3工程落地:通信同步、坐标变换与性能调优

1. 为什么在ROS里硬刚YOLOv3不是“炫技”&#xff0c;而是工程落地的必经之路我第一次在某高校机器人实验室看到那台搭载Jetson TX2的移动底盘原地打转、反复识别错垃圾桶时&#xff0c;就意识到&#xff1a;单纯跑通darknet_ros demo和真正让YOLOv3在ROS里“干活”&#xff0c…

2026/10/21 0:00:11 阅读更多 →

周新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型&#xff08;GMM&#xff09;的语音识别系统&#xff0c;很多人第一反应就是一堆数学公式&#xff0c;觉得门槛很高。但如果你真的在MATLAB里把它落地跑通&#xff0c;会发现核心其实就是三件事&#xff1a;把声音变成特征向量&#xff0c;用GMM给每个类别…

2026/10/20 10:02:13 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人&#xff0c;但落到具体操作上&#xff0c;无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程&#xff1a;对图像做反白、改调色板、彩色转灰度&#xff0c;最后把像素数据导出成txt文件。单看每一…

2026/10/20 20:43:59 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词&#xff0c;你会发现一个很有意思的现象&#xff1a;每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型&#xff0c;有人指的是运动控制系统&#xff0c;还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/21 4:27:17 阅读更多 →

月新闻

我发现了一个新思路:用 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/20 10:02:08 阅读更多 →
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/20 9:50:17 阅读更多 →
黑夜航拍船只数据集训练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/20 9:50:45 阅读更多 →