news 2026/10/5 5:04:34

用C# Winform调用百度AI OCR,打造你的截图文字提取小工具

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
用C# Winform调用百度AI OCR,打造你的截图文字提取小工具

最近经常要在文档、网页和聊天记录之间搬运文字,截图里的一段报错、一段代码、一个表格,手动敲一遍效率太低,用QQ截图自带的识别功能又总觉得差点意思,识别结果偶尔还会自动带上表情或者多出换行。后来我索性用C# Winform调百度AI的OCR接口,自己写了个文字提取小工具,按快捷键框选屏幕区域,松开鼠标文字就进剪贴板了,整个过程不到一秒。这项目技术上不复杂,但非常实用,适合刚学完C#基础想找个完整练手项目的朋友,也适合想解决日常办公痛点又不想用臃肿商业软件的人。

这篇博文我会把整个工具的设计思路、百度AI接入流程、核心代码和踩坑记录都摊开讲,完全按我实际开发的过程来写,你照着敲一遍就能跑起来。

1. 为什么要自己写一个文字提取工具

1.1 现有截图工具到底缺了什么

我不是说QQ截图和微信截图不好用,它们胜在“随手就有”,但真到了高频提取文字的场合,几个问题就藏不住了:

第一是识别入口太深。QQ截图默认要按Ctrl+Alt+A,截完图之后还要点一下“屏幕识文”按钮,识别结果再弹一个小窗,你再选中复制。这一套流程在偶尔用一次的时候能接受,但在连续处理几十张截图的时候就显得特别啰嗦。

第二是结果不可控。系统自带的OCR经常会智能(有时候是自作聪明)地把识别结果处理成“方便你直接发消息”的样子,比如每一行自动加换行、繁体字切换、符号替换,但我想拿到的是原始排版、原始字符,尤其是代码片段和日志信息,多一个空格都影响调试。

第三是批量处理能力为零。截图工具的OCR一次只能处理当前截的这一张,如果手头有几十张图片要统一提取文字,那就得一张一张截、一张一张识,没有任何扩展空间。

想明白这几点之后,我的目标就很清晰了:做一个按快捷键就能框选屏幕、松开鼠标就完成OCR识别、结果自动进剪贴板的轻量工具。配合C# Winform做前端界面和交互,百度AI开放平台做OCR识别引擎,两个搭配起来,正好覆盖我所有的需求。

1.2 方案选型:为什么是C# Winform加百度AI

选C# Winform这个技术栈,我自己权衡过两个备选方案。一个是WPF,界面表现力确实比Winform强,动画和自定义样式都方便,但问题是很多老电脑、服务器环境跑WPF反而容易出现兼容问题,Winform在Windows平台上是“万年稳”的存在。另一个是Python加PyQt,写脚本确实快,OCR这块Python生态也成熟,但最终要打包成给同事用的exe,PyQt打包体积大、启动速度也一般,而且很多公司内网机器的Python环境不全。C# Winform编译出来一个小体积exe,拷过去就能跑,不依赖任何运行时(用.NET Framework的话),对办公场景是最省心的。

百度AI相比其他OCR方案的优势也很明显。本地OCR方案(比如Tesseract)识别中文效果一般,尤其是手写体和带背景的字体,而且配置引擎、训练字库的门槛不低。在线OCR方案里,百度AI通用文字识别标准版每天有500次免费额度,个人日常使用根本用不完,识别速度和准确率在中文场景下实测是第一梯队。接口文档也做得清楚,C#调用也就是几个HTTP请求的事。

整体思路可以概括成一句话:快捷键唤起截图遮罩层,鼠标框选区域后截取屏幕,图片交给百度AI识别,返回的文本自动写入剪贴板并弹出结果窗口。接下来每一步我都会讲细节。

2. 开发前准备:环境和百度AI开放平台接入

2.1 开发环境搭建与项目创建

开发工具我用的是Visual Studio 2019,你装2019或者2022都行,社区版就够用。项目类型选择“Windows窗体应用(.NET Framework)”,目标框架我选.NET Framework 4.7.2,主要是考虑兼容性,Win7及以上系统都能跑。你如果只想在Win10/11上用,选.NET 6或.NET 8的“Windows窗体应用”也没问题,代码逻辑基本一致,就是包管理方式和入口函数略有不同。

项目创建好之后,先用NuGet装一个包:Newtonsoft.Json,后面解析百度返回的JSON数据要用的。操作很简单:右键项目 → 管理NuGet程序包 → 搜索Newtonsoft.Json → 安装。如果你用的是.NET 5以上的版本,也可以用System.Text.Json替代,但Newtonsoft.Json在Windows老项目里更通用,我习惯用它。

界面设计上我保持极简:主窗体只放一排按钮,包括“开始识别”(按钮绑定快捷键)、一个文本框用于手工粘贴图片路径,再加一个历史记录列表。真正的主角是一个全屏遮罩窗口,这个窗口不显示标题栏,背景半透明,鼠标拖拽画一个矩形框,这就是截图区域。界面美化的部分我建议第二版再做,第一版先保证功能跑通。

2.2 百度智能云OCR服务开通与凭证获取

这一步是唯一需要联网操作的环节,其实很简单。你先在浏览器打开百度智能云的控制台,用百度账号登录,然后按下面步骤操作:

  1. 在产品列表里找到“文字识别OCR”,进入后选择“创建应用”。
  2. 应用名称随便填,比如“MyOCR”,类型选“个人”,接口选择默认(通用文字识别/标准版)。
  3. 创建完成后,在“应用列表”里能看到三个关键信息:API Key、Secret Key、应用ID。这三个东西就是后续调接口的凭证。

接下来要做的是“开通”OCR服务。百度的OCR是按接口单独开通的,我们去“文字识别”页面,找到“通用文字识别标准版”,点“领取免费额度”或者“开通服务”即可。新用户一般有几百到几千次免费调用,个人用完全足够。

有个细节必须提醒一下:API Key和Secret Key等同于你账户的钥匙,千万别写死在代码里之后又随意把代码传到公开仓库。我见过有人把密钥提交到GitHub然后被刷爆额度的。可以把密钥放到一个单独的配置文件中,并在.gitignore里忽略掉这个文件。

2.3 认识AccessToken的获取逻辑

百度OCR接口的鉴权方式不是直接拿API Key调识别接口,而是要用API Key和Secret Key去换一个AccessToken,然后每次识别请求都带上这个AccessToken。AccessToken有效期是30天,过期之后需要重新获取。

换取AccessToken的HTTP请求是标准的OAuth 2.0,可以用浏览器直接验证:

https://aip.baidubce.com/oauth/2.0/token?grant_type=client_credentials&client_id=你的APIKey&client_secret=你的SecretKey

正常的话会返回一段JSON,其中access_token字段就是我们要的东西。我在程序里封装了一个静态方法,每次启动时检查缓存的AccessToken,不存在或者过期就重新请求,代码如下:

public static class BaiduAuth { private static string _accessToken = ""; private static DateTime _expireTime = DateTime.MinValue; public static async Task<string> GetAccessTokenAsync(string apiKey, string secretKey) { if (!string.IsNullOrEmpty(_accessToken) && DateTime.Now < _expireTime) { return _accessToken; } string url = $"https://aip.baidubce.com/oauth/2.0/token?grant_type=client_credentials&client_id={apiKey}&client_secret={secretKey}"; using (HttpClient client = new HttpClient()) { var resp = await client.GetStringAsync(url); var json = JObject.Parse(resp); _accessToken = json["access_token"]?.ToString(); int expiresIn = json["expires_in"]?.ToObject<int>() ?? 2592000; _expireTime = DateTime.Now.AddSeconds(expiresIn - 600); // 提前10分钟过期,避免临界点 } return _accessToken; } }

这里“提前10分钟过期”是我在实际使用中踩过坑之后加的逻辑。因为如果正好卡在过期时间点调用接口,容易偶发401,提前重置时间可以避开这个临界问题。

3. 核心功能拆解与实现过程

3.1 截图功能:全屏遮罩与鼠标框选

整个工具最核心的交互就是框选屏幕区域,这一块用Winform实现其实很直白。

第一步,按快捷键唤起截图窗口。我使用的是全局快捷键,这样即使工具主窗体在后台、最小化,按下快捷键也能立刻响应。Winform里实现全局快捷键最简单的办法是调用Win32的RegisterHotKey函数。先在主窗体加载时注册一个组合键,比如Ctrl+Shift+Z:

[DllImport("user32.dll")] private static extern bool RegisterHotKey(IntPtr hWnd, int id, uint fsModifiers, uint vk); const int HOTKEY_ID = 0x0001; const uint MOD_CONTROL = 0x0002; const uint MOD_SHIFT = 0x0004; const uint VK_Z = 0x5A; protected override void WndProc(ref Message m) { base.WndProc(ref m); if (m.Msg == 0x0312 && m.WParam.ToInt32() == HOTKEY_ID) // WM_HOTKEY { StartCapture(); } }

这里0x0312是Windows系统定义的WM_HOTKEY消息,当用户按下注册的快捷键时,这个消息会发送到注册窗口的消息循环中。StartCapture方法就是创建并显示截图遮罩窗体。

第二步,截图遮罩窗体。其实是一个全屏无边框窗口,背景色为黑色,透明度设为0.3左右,鼠标在上面按下并拖拽,动态计算一个矩形区域,并在窗体上把这个矩形画出来。为了让用户看清楚选中区域,一般做法是把选中区域用白色边框框起来,同时把选中区域之外的暗色加深。

具体实现上,我重写了遮罩窗体的OnMouseDown、OnMouseMove和OnMouseUp事件。拖拽过程中,记录起点坐标和当前坐标,通过Invalidate触发重绘,在OnPaint事件里用Pen画矩形边框:

private Point _startPoint; private Rectangle _selectRect; protected override void OnMouseDown(MouseEventArgs e) { base.OnMouseDown(e); if (e.Button == MouseButtons.Left) { _startPoint = e.Location; } } protected override void OnMouseMove(MouseEventArgs e) { base.OnMouseMove(e); if (e.Button == MouseButtons.Left) { _selectRect = GetRectangle(_startPoint, e.Location); Invalidate(); // 触发重绘 } } private Rectangle GetRectangle(Point p1, Point p2) { int x = Math.Min(p1.X, p2.X); int y = Math.Min(p1.Y, p2.Y); int width = Math.Abs(p1.X - p2.X); int height = Math.Abs(p1.Y - p2.Y); return new Rectangle(x, y, width, height); } protected override void OnPaint(PaintEventArgs e) { base.OnPaint(e); using (Pen pen = new Pen(Color.White, 2f)) { e.Graphics.DrawRectangle(pen, _selectRect); } }

鼠标松开时,根据_selectRect区域调用截图方法,把屏幕上对应矩形区域的内容抓成Bitmap,然后关闭遮罩窗体。

第三步,抓取屏幕区域。这里用的是Graphics.CopyFromScreen方法:

private Bitmap CaptureScreen(Rectangle rect) { Bitmap bmp = new Bitmap(rect.Width, rect.Height); using (Graphics g = Graphics.FromImage(bmp)) { g.CopyFromScreen(rect.Location, Point.Empty, rect.Size); } return bmp; }

这段代码在普通屏幕上没问题,但在高分屏(DPI缩放不是100%)的系统上会有一个经典坑,我放在后面的“避坑实录”里专门讲。

3.2 图片预处理:直接送识别还是先做加工

截图拿到的Bitmap我建议先做一些预处理再送百度AI,原因很简单:识别率能明显提升,尤其是屏幕上小字号字体和抗锯齿渲染出来的文字。

我自己实验下来最优的预处理顺序是:

  1. 放大。如果框选的区域文字偏小(比如代码编辑器里11号字体),直接把原图送识别,百度有时候会漏字。把图片放大2倍再识别,准确率能上去不少。放大我直接用C#的Graphics类:
private Bitmap ScaleImage(Bitmap src, int scale) { int newWidth = src.Width * scale; int newHeight = src.Height * scale; Bitmap newBmp = new Bitmap(newWidth, newHeight); using (Graphics g = Graphics.FromImage(newBmp)) { g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic; g.DrawImage(src, 0, 0, newWidth, newHeight); } return newBmp; }
  1. 灰度化。屏幕截图的背景如果有颜色渐变、阴影、暗色主题,直接识别容易干扰。转成灰度图可以强化文字和背景的对比度。灰度化就是遍历像素,用标准公式 R0.299 + G0.587 + B*0.114 计算灰度值。

  2. 二值化(可选)。对于白底黑字的截图,二值化效果很好,但遇到深色主题的IDE截图,二值化反而会让文字断裂。我的做法是让用户自己在设置里选“增强模式”和“原图模式”,默认走灰度+放大,不强制二值化。

这一步大家不要过度设计,OCR识别的本质是图像理解和特征匹配,太重的图像处理可能帮倒忙。实测下来对屏幕截图最有效的是“放大”,其次是“灰度”,其他边缘锐化、去噪这些对UI截图的收益很小。

3.3 调用百度AI文字识别接口

百度AI通用文字识别有几种接口:通用文字识别(标准版)、通用文字识别(高精度版)、通用文字识别(含位置信息版)等。标准版免费额度多,识别速度也快,我就用它。接口地址:

https://aip.baidubce.com/rest/2.0/ocr/v1/general_basic?access_token=你的AccessToken

请求方式为POST,Content-Type为application/x-www-form-urlencoded,body里带上image参数,值是图片的Base64编码字符串。如果图片过大,需要先压缩到一定大小,百度要求Base64编码后的图片大小不超过4M,我实际建议控制在1M以内,速度更快。

调用代码封装如下:

public static async Task<string> RecognizeTextAsync(Bitmap bmp, string accessToken) { using (MemoryStream ms = new MemoryStream()) { bmp.Save(ms, System.Drawing.Imaging.ImageFormat.Png); byte[] imageBytes = ms.ToArray(); string base64Image = Convert.ToBase64String(imageBytes); string url = $"https://aip.baidubce.com/rest/2.0/ocr/v1/general_basic?access_token={accessToken}"; using (HttpClient client = new HttpClient()) { var content = new FormUrlEncodedContent(new Dictionary<string, string> { { "image", base64Image }, { "detect_direction", "false" }, { "paragraph", "true" }, { "probability", "false" } }); var resp = await client.PostAsync(url, content); string respBody = await resp.Content.ReadAsStringAsync(); return respBody; } } }

这里有几个参数值得说明一下:

  • detect_direction:是否检测图像朝向,默认false,如果截的是手机竖屏长图或者扫描件,建议设true让百度自动纠正方向。
  • paragraph:是否输出段落信息。默认false时只返回每行文字的坐标和内容,设true后返回结果里多一个paragraphResult字段,可以方便地把多行文字按段落合并。
  • probability:是否返回每行识别的置信度,调试时开着方便定位问题,正常使用建议关闭,能省一点响应时间。

3.4 解析返回结果并联动剪贴板

百度OCR接口返回的JSON结构长这样:

{ "words_result": [ { "words": "你好,世界" }, { "words": "第二行文字" } ], "words_result_num": 2, "log_id": 1234567890 }

用Newtonsoft.Json解析很简单:

var json = JObject.Parse(respBody); var wordsList = json["words_result"]? .Select(t => t["words"]?.ToString()) .ToList(); string resultText = string.Join(Environment.NewLine, wordsList);

解析拿到纯文本后,我做三件事:

  1. 把结果同步到剪贴板,这样用户直接Ctrl+V就能粘贴到任何地方。
  2. 弹出一个非模态的结果预览窗口,里面放一个多行TextBox展示识别内容,方便用户检查识别错误、手动编辑后再复制。
  3. 把结果追加到历史记录列表里,带时间戳,方便回溯。

剪贴板操作在Winform里有一个很需要注意的点:Clipboard.SetText方法在个别环境下会因为剪贴板被其他程序占用而抛异常,所以通常我会在调用时加上重试机制:

public static void SetClipboardText(string text) { for (int i = 0; i < 3; i++) { try { Clipboard.SetText(text); return; } catch (Exception) { Thread.Sleep(100); } } }

4. 高频问题与避坑实录

4.1 高分屏截图偏移问题

我在前文留了一个坑,就是Graphics.CopyFromScreen在高分屏下截图会偏移、尺寸不对。这个问题非常典型,尤其是在笔记本(很多是150%缩放)上跑这个工具,截出来的图片往往只是屏幕左上角的一部分。

原因在于,Windows为了兼容老程序,默认会对显示内容做缩放,而GDI+的CopyFromScreen使用的是物理像素坐标,如果你的Winform进程不是DPI感知(DPI Aware)的,系统会把坐标“虚拟化”,导致获取到的屏幕区域和实际物理显示区域不一致。

解决办法是在程序入口处、任何窗口创建之前,调用SetProcessDPIAware:

[DllImport("user32.dll")] private static extern bool SetProcessDPIAware(); [STAThread] static void Main() { SetProcessDPIAware(); Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); Application.Run(new MainForm()); }

这样进程就按真实DPI来布局,截图坐标和物理像素对齐,问题就消失了。如果你用的是.NET Core 3.0以上版本,还可以通过运行时配置application.manifest来声明DPI感知,效果一样。

4.2 识别结果中英文和数字混排不准

百度AI标准版对纯中文和纯英文的识别都还不错,但屏幕截图中经常出现源代码、日志信息这种“英文关键词+中文注释+数字”混排的情况,偶尔识别结果里会出现把全角逗号识别成半角,或者把0识别成O这类问题。

我的经验是:如果是代码块识别,可以在调用时加参数language_type=CHN_ENG,强制使用中英文混合模型,比默认模型的准确率高一截。另外,如果屏幕上的英文、数字特别多,可以在设置里把“增强模式”打开,也就是先做灰度放大再识别,实测对代码类截图的误识别率能降低不少。

4.3 网络请求超时和AccessToken过期

在线接口不可避免会遇到网络波动和超时的场景。我开发的第一个版本没有做超时控制,偶尔在弱网环境下界面就卡住了,用户体验很差。后来我做了三个调整:

一是给HttpClient设置超时时间,默认15秒,超过就提示“识别超时,请重试”并自动关闭截图等待状态。注意HttpClient的Timeout属性是针对整个请求周期的,如果OCR响应慢,这个值太短反而容易误判。

二是增加简单的重试机制。对网络超时这类的偶发错误,我会自动重试一次;但如果是返回错误码(比如110或111表示AccessToken无效),就不重试,而是提示用户重新获取。

三是设计Token管理机制。每30天AccessToken过期一次,很多用户当天用完第二天再用,发现忽然不好使了,其实就是Token过期了。我建议在启动时强制刷新一次Token,或者把Token和过期时间保存到本地文件里,启动时读取并判断是否过期,这样能省一次HTTP请求,也避免400错误的尴尬。

4.4 全局快捷键被其他软件占用

我用Ctrl+Shift+Z作为默认快捷键,但不少截图软件、输入法也会占用类似的组合,注册失败时RegisterHotKey会返回false。所以初始化时如果发现注册失败,我会弹一个提示,并提供设置窗口让用户改成别的组合。比较稳妥的做法是使用F1到F12组合,或者像QQ截图一样用“Ctrl+Alt+字母”,降低冲突的概率。

另外,全局快捷键消息是在程序的主窗口消息循环里接收的,如果你的主窗体做了最小化到托盘的操作,不要忘记在消息循环里依然处理WM_HOTKEY,否则最小化后就唤不醒了。我最初踩过这个坑,最小化到托盘后怎么按快捷键都没反应,后来确认就是因为托盘化之后消息处理没有走WndProc的重载。

5. 工具还能怎么扩展

这个项目的框架搭好之后,扩展方向非常多,我目前加了几个比较实用的功能:

批量识别。在窗体上支持拖入多张图片,自动批量调用接口识别,把所有结果合并保存到txt文件,适合处理拍照的文档或者PPT幻灯片截图。

剪贴板监听。监听系统剪贴板,当复制了图片时自动弹出提示“检测到剪贴板图片,是否需要识别”,这样连快捷键都不用按,复制图片的动作直接就导向识别。

表格识别。偶尔开会时要拍白板上的表格,我用百度的表格文字识别接口替换通用接口,返回的JSON里带表格结构,可以在程序里渲染出表格预览图,虽然格式复杂一点,但确实能解决实际问题。

识别结果翻译。识别完文字之后接一个翻译接口,对英文文档、英文软件界面非常实用,相当于截图即翻译,省去切换软件的时间。

离线识别兜底。因为在线接口受网络影响,我也试过用PaddleOCR本地模型做离线识别。C#调用PaddleOCR需要用Python环境跑本地服务再通过HTTP调用,架构上多一层,但效果做个兜底方案还凑合。如果你对完全离线有刚性需求,可以考虑这个方向。乐观点说,这属于“平时用在线接口,断网时降级离线”的双通道设计。

我在实际使用中发现,最顺手的工作流反而是“快捷键截图 → 自动识别 → 自动复制 → Ctrl+V黏贴”,整个过程不需要看结果窗口,识别错了再手动改一下。所以我强烈建议第一版把“自动复制到剪贴板”这个功能做扎实,这才是提升效率的关键。

写在最后的个人体会

做这个工具最大的体会是:技术难度真的不高,但做完之后的成就感很实在。从“每次截图识别要点三次鼠标”变成“按一个快捷键文字就进剪贴板”,这个效率提升是肉眼可见的。整个项目代码量不到500行,核心逻辑就三块:截图、HTTP调用、JSON解析,对C#初学者来说正好是一次完整的综合练习——你会接触到Win32 API、GDI+绘图、异步编程、HttpClient、JSON处理这些实际开发中躲不掉的技术点。如果你照着这篇文章实现了第一版,我建议你写完之后给自己提两个问题:第一,识别结果怎么处理才能让用户少改一个字;第二,如果每天要识别几百张图,程序的稳定性和容错要怎么提升。把这两个问题想清楚,你就不只是抄了一套代码,而是真正理解了一个工具类软件的设计逻辑。

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

SDD规范驱动的AI开发:Harness如何实现可控化代码生成

1. 这不是又一个“AI写代码”噱头&#xff1a;SDD规范驱动 Harness工程化&#xff0c;到底在解决什么真问题&#xff1f;最近两周&#xff0c;我连续被三个不同行业的技术负责人拉进会议室&#xff0c;问的都是同一个问题&#xff1a;“你们团队用的DeepSeek Harness&#xff…

作者头像 李华
网站建设 2026/10/5 5:02:44

2026年AI辅助论文初稿实操流程与避坑指南

2026年了&#xff0c;关于“论文初稿能不能用AI”这件事&#xff0c;我觉得已经没什么可争论的了——答案显然是能&#xff0c;而且身边不少人在用。真正值得聊的问题变成了&#xff1a;为什么同样用AI辅助&#xff0c;有人三周写出初稿&#xff0c;导师还夸思路清楚&#xff1…

作者头像 李华
网站建设 2026/10/5 5:02:30

AI助力量化交易:从因子挖掘到模型量化的实战指南

这几年做量化&#xff0c;我最大的感受就是&#xff1a;手里工具的变化&#xff0c;比市场行情的波动还猛。以前写一个策略&#xff0c;要从因子定义到回测框架全部手动撸&#xff0c;一个晚上能憋出半个函数就不错了。现在AI助手直接帮我把策略骨架搭好&#xff0c;我只需要负…

作者头像 李华
网站建设 2026/10/5 5:02:26

电信工单智能路由与闭环处置系统设计

1. 这不是“又一个AI客服”&#xff0c;而是工单系统里的“神经中枢”你有没有见过这样的场景&#xff1a;某省电信运营商一天涌入2.3万张工单——宽带故障报修占47%&#xff0c;5G信号投诉占21%&#xff0c;套餐变更咨询占18%&#xff0c;还有零散的物联网卡异常、政企专线中断…

作者头像 李华
网站建设 2026/10/5 5:02:18

WorkBuddy对接Ollama:本地大模型配置与70 tok/s性能调优全记录

1. 先说结论&#xff1a;WorkBuddy 接 Ollama&#xff0c;到底值不值得折腾WorkBuddy 这个工具&#xff0c;我最早是在折腾 AI 工作台自动化的时候接触到的。简单说&#xff0c;它就是一个把大模型能力编排进日常工作的智能代理平台&#xff0c;支持按任务搭"工作台"…

作者头像 李华
网站建设 2026/10/5 5:01:56

AMD SEV机密计算实战:从内存加密原理到SEV-SNP与宿主机隔离实践

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

作者头像 李华