Python工程化新宠uv:从pip到uv的迁移实战指南
如果你最近在看Python的工程化工具链大概率会注意到一个叫uv的名字。python uv工具使用这件事听起来像是又给pip换了个皮肤但真正上手之后我的感受完全不同它把“在项目里装好依赖、跑起来、复现环境”这一整套动作的体验从“炖汤”变成了“微波炉热菜”。本文不是uv官方文档的翻译我按自己从pip/virtualenv切换过来之后的真实路径把环境初始化、日常工作流、锁文件逻辑、迁移老项目时踩过的坑全部串起来讲一遍。这篇内容适合两类人第一类是已经被pip的解析速度、poetry的依赖冲突折磨过一阵的Python开发者第二类是刚接触现代Python工程化想在新项目里一步到位用上靠谱工具的新手。下面所有命令都在常规终端里可以复现不依赖云服务也不是某个发行版独占功能。1. 为什么偏偏选uv先看它到底赢在哪1.1 与pip、poetry的一次直观对比先做个不严谨但很直观的测试创建全新的虚拟环境安装requests、flask、pandas、numpy这一组典型依赖。同样一台机器网络条件相同结果差异体现在两个层面一个是冷缓存状态一个是热缓存状态。工具 / 场景冷缓存从未下载过热缓存本地已有wheelpip venv通常较慢大量时间花在逐个下载与安装有一定提升但仍要逐个检查元数据poetry install解析阶段常停留数秒到十几秒解析逻辑依然偏重感知提升不明显uv sync冷缓存下明显更快并行化下载节省大头常在一两秒内完成主要操作变成缓存链接我用自己维护的一个内部工具项目做参照它大概有80个直接依赖跑的是一个常见Web框架。旧方案是virtualenv加pip全新环境从零装完大约要一分半钟切换到uv之后同样是全新环境整个同步流程十几秒内跑完。注意这时候网络下载仍然是瓶颈uv真正消灭的是服务器往返过程中的等待时间以及本地解析时的重复计算。你觉得它“快得离谱”是很正常的我第一次用的时候也以为它只是把输出变得精简了实际看监控才发现下载阶段直接并发进行安装阶段也不再是“下载一个、装一个、再下载下一个”的串行队列。1.2 底层逻辑没那么玄乎全局缓存与并行uv会快主要不是因为它把PNG图标换成了更有设计感的Logo也不是用了黑魔法。拆开来看就几点用Rust重写了解析器、安装器和各类环境管理逻辑进程启动和运行开销都极其低默认维护一个全局缓存目录所有项目共用同一份下载产物同一版本同一平台的wheel不会因为换了项目就重新下载依赖解析和下载是并行推进的解决了一部分“先解析完才能判断该下什么”的串行瓶颈能够复用现有元数据和本地索引信息快速判断哪些版本可能满足约束条件而不是傻傻把所有候选都拉下来试。打个比方pip像每次做饭前都得从买菜、择菜、洗菜开始uv像家里有个塞满食材的冰箱你提前备好了菜做饭时只把需要的盒子拿出来加热或者切一下就行。缓存目录就是那个冰箱装过一次的包在另一个新项目里再次用到时几乎不需要重新下载。这也解释了为什么热缓存状态下uv同步可以快到一两秒锁文件没变、包已经躺在缓存里它做的工作就是算一下“这个项目需要哪些文件目录里有没有有就直接引用”所有步骤都是极轻量的本地操作。1.3 安装和升级低门槛但有几个细节安装uv的方式很简单最常规的是pip install uv uv --version如果你不想全局污染Python环境也可以使用官方文档提供的安装脚本它会把uv作为独立二进制放在你本地的工具目录里。这里要提醒一句任何来自网络的管道脚本执行前都建议先打开页面看一眼内容再决定是否真的运行。这不是对uv不信任而是对所有curl管道安装的基本素养。装完之后确认一下终端能不能直接识别到uv命令。用官方安装脚本时可能需要开一个新终端或者刷新一下PATH否则你会以为安装失败了。升级也有两条路如果当初是用pip安装的直接执行pip install --upgrade uv如果是按官方脚本方式管理的执行uv self update它的升级逻辑和pip不同更像在管理一个独立软件本体。要注意的是如果你正在某个虚拟环境里运行uv self update它更新的是那个全局二进制和当前环境无关不要以为它会顺带更新项目依赖。2. 从零跑通uv初始化、环境与首次同步2.1 uv init 初始化项目pyproject与.python-version建立新项目时我现在的动作几乎固定是uv init demo-project cd demo-project这会在当前目录下生成一套标准的Python项目骨架。注意不同版本生成的文件略有差异我手边的版本会包含pyproject.toml、.python-version、README.md以及src目录。最关键的是pyproject.toml你打开后会发现它长得非常干净[project] name demo-project version 0.1.0 requires-python 3.10 dependencies []这个文件就是未来项目依赖声明的唯一权威来源。以后你执行uv add requests它会自动把requests加进dependencies列表不需要你手动编辑也基本不会出现“requirements.txt里写了一份pyproject里又写了一份二者还互相对不上”的老问题。.python-version是另一个容易被忽略的文件。它记录当前项目想要的Python版本uv在执行后续命令时会主动参考它。你把这个文件提交到版本库团队成员拉到项目之后uv会尝试从本机找到匹配的解释器找不到时它甚至可以直接下载指定版本。这一点在缺少统一Python版本管理的团队里尤其有用。如果你已经有了一套文件夹而且里面已经存在老的requirements.txt我不建议直接执行uv init进行“格式化”那样会把本来就脆弱的旧项目结构搅得更乱。先用下一节讲到的方式建环境再把依赖逐步搬进pyproject.toml。2.2 用 uv venv 创建虚拟环境假如你只是需要一个虚拟环境不想要uv init生成的那一堆内容可以单独执行uv venv .venv source .venv/bin/activateWindows环境对应的激活命令是.\\.venv\\Scripts\\activate。这条命令比virtualenv快的原因之一正是上一节提到的全局缓存机制它会直接复用本机已有的解释器和基础组件而不是重新扫描一遍系统路径。你也可以指定解释器版本uv venv --python 3.11 .venv这么做最省心的地方在于如果你本机没有3.11解释器它会尝试自动下载而不是抛出一段“请手动安装对应版本”的提示让你自己去折腾。对比一下pyenv或者手动下载Python安装包这确实是开了即用式的体验。但我的建议是新建项目不要止步于临时虚拟环境。你完全可以只依赖uv init加uv add加uv run这套流程让uv自动创建和管理环境自己连activate这一步都可以省略。手动创建.venv的场景更多出现在你只是想临时验证某个库的功能或者项目结构实在不适合迁移的时候。2.3 uv sync 首次装依赖如果项目已经通过pyproject.toml声明了依赖第一次安装直接uv sync它会做三件事解析pyproject.toml中的依赖约束生成一份uv.lock锁文件然后把所有依赖按锁文件装进虚拟环境。如果你执行过uv init环境默认会放在项目目录下的.venv里位置不用你操心。之后再跑uv sync时只要锁文件没变化它不会去重新解析整棵依赖树只会检查当前环境与锁文件的状态是否一致并把缺少的包补齐。同步这个词用得挺准的它不是重新安装而是让环境对齐uv sync --group dev这条命令会把开发依赖一起装进来。新增开发依赖时先执行uv add --dev pytest把它写进独立的开发依赖组再同步时才自动给测试工具留出位置。有个容易踩的认知误区是以为uv sync只是“装得快点”的pip install。其实它会根据锁文件删掉环境中多余的包保证.venv和uv.lock完全一致。这在多人协作时非常有用因为不会再出现“你本地比锁文件多装了一个包结果测试环境少一个包导致行为不一致”的问题。3. 日常开发中的高频操作不是 pip 翻版3.1 uv add / uv remove 与锁文件的前因后果日常添加依赖时uv add requests uv add --dev pytest uv remove feedparser这组命令会同步维护三处pyproject.toml中的声明、uv.lock中的锁定版本、当前虚拟环境里的实际安装情况。也就是说输入完uv add依赖已经可用了不用再单独跑一步安装。有人刚切换过来时会很自然地想只改pyproject.toml然后丢给uv sync去处理。这样做不是不行但uv add更符合直觉因为它会自动帮你处理版本范围和传递依赖解析还会把选择结果写进锁文件。这里必须强调一下uv.lock的意义。它不是requirements.txt那种粗糙的列表而是把每一个直接依赖、传递依赖、对应版本、来源信息全部锁死的精确快照。建议把它提交到Git里。这样团队成员拉取代码后执行uv sync得到的依赖组合和你在本地开发时用的几乎完全一致这对排查“本地跑得好好的别人拉下来就崩”这类问题价值极大。如果只是想升级某一个依赖而不是把全项目依赖全部升级一遍uv lock --upgrade-package requests uv sync先让uv重新解析requests允许的最新版本并更新锁文件再同步环境。直接执行uv add requests虽然也能更新但它不会针对性地处理该依赖的传递依赖变化长期来看还是用uv lock --upgrade-package更可控。3.2 uv run 替代手动激活环境日常开发中我可以完全告别source activateuv run python script.py uv run pytest uv run ruff check .uv run会自动检测当前目录是否是uv管理的项目必要时自动创建或更新.venv然后在该环境内执行你指定的命令。好处是相当明显的切换项目时不用记着“先退出当前环境再进另一个环境”终端会话也不会被一串环境变量污染。习惯后你会重新思考“激活虚拟环境”这个动作是否真的有必要。至少在我的工作流里除了偶尔需要手动进环境去敲交互式Python其他时候都用uv run代替了。我还在shell配置里加了一个别名alias pruv run直接在项目目录下运行pr python main.py整个操作入口变得非常统一。有个细节值得注意uv run在项目目录外会找不到项目定义因此会退回普通命令执行。如果你有脚本需要读取项目内的配置建议还是先cd进项目目录再调用uv run。3.3 多版本Python管理项目A要支持3.9项目B必须跑3.11在不用pyenv的情况下uv也能把解释器本身管起来uv python install 3.9 uv python install 3.11 uv python pin 3.9uv python pin 3.9会写入.python-version文件让当前目录下的uv命令自动使用Python 3.9。不同目录各pin各的版本互不干扰。这比pyenv少了一层shell hook也少了很多“明明装了却切换不过去”的玄学问题。我在一个测试脚本项目上验证过这个流程项目A用3.9项目B用3.11两边都有各自的.venv切换时只需要cd到对应目录然后继续用uv run做事情即可不需要关心当前shell里有没有某个版本的Python处于激活状态。3.4 全局工具安装也能用uv有一类场景和具体项目无关你要装的是命令行工具本身而不是给某个项目的依赖。比如代码检查工具、格式化工具、发布工具。老派做法是用pip install --user然后忍受一堆工具塞进同一个Python环境互相踩版本。uv提供了一套更干净的方案uv tool install ruff uvx ruff check .uv tool install会给这个工具创建独立环境不会污染当前项目的虚拟环境。uvx是临时调用工具的统一入口本质上是“装到一个临时环境里只为了跑一条命令”。如果你想给团队分享一整套命令工具还可以用类似uv tool list查看当前装过的全局工具管理起来一目了然。这里再分享一个最有“开眼界”感的用法uv run --no-project --with pandas python analyze.py当某个临时脚本需要pandas但你又懒得为它建一个正式项目时这条命令会临时创造一个包含pandas的环境然后在该环境里运行脚本。脚本跑完这个临时环境不被默认保留不会污染你的工作目录。对于写一次性分析脚本、复现第三方示例代码这类场景几乎是我的默认起手式。4. 踩坑与边界用uv过程中遇到的现实问题4.1 旧项目、requirements.txt 和 pip 的兼容uv并不是万能的。如果你的项目还在使用requirements.txt加setup.py的老结构直接执行uv sync大概率不会像预期那样工作因为uv sync需要pyproject.toml作为入口。这时候有两个选择第一临时兼容模式uv pip install -r requirements.txtuv pip是一层兼容接口行为上接近pip但底层仍然享受缓存加速。这种模式适合你想快速把依赖装上、又不打算立刻改造项目结构的过渡期。第二彻底迁移。把requirements.txt里的直接依赖手工整理进pyproject.toml的dependencies列表然后删除旧的requirements.txt让uv sync全面接管。如果项目里有大量通过pip命令安装的历史操作建议迁移完成后跑一次uv sync确认环境一致再考虑删除旧文件。我的真实经历是一个老的Django项目直接混用了pip和uv结果出现过一个现象某些包明明在锁文件里环境里却存在另一个版本。把环境删掉重新uv sync才恢复一致。混用工具不是毒药但它的行为会超出你的直觉预期与其事后排查不如从一开始就选定一个工具作为主管理入口。4.2 uv.lock 锁太牢导致版本升级不生效很多人刚用uv时会遇到一种怪情况同事在pyproject.toml里加了新依赖你这边pull代码后跑uv sync却被告知环境已经是最新的新依赖就是没出现。这通常不是uv装错了而是本地uv.lock还没有同步到同事锁定的状态。解决方式之一uv sync --refresh--refresh会强制重新获取远程元数据并重新核对锁文件然后再同步环境。如果只想重新解析依赖并更新锁文件暂不安装任何东西就执行uv lock这之后再看pyproject.toml中的版本范围、锁文件中的解析结果就能发现大多数升级问题的根因了。还有另一种情况是pyproject.toml里的版本约束本身就限制了升级路径比如写死了requests2.0,3.0uv自然永远不会解析到大版本3.x。这时候要改的是约束本身不是怪uv“怎么锁得这么死”。4.3 缓存目录膨胀与清理策略全局缓存提升了速度代价是磁盘占用。缓存目录的位置可以通过uv cache dir查看。在多项目持续使用几个月之后这个目录很容易悄悄膨胀到几个GB级别。清理手段uv cache clean # 清空全部缓存 uv cache prune # 只清理未使用的缓存个人经验是不要动不动就clean。频繁全量清理会把热缓存优势直接抹掉下次进新项目又得像冷缓存一样从头下载。我自己的节奏是每两到三个月执行一次uv cache prune只清理那些已经不再被引用的条目保留下载量大的基础依赖比如numpy、pandas、PyTorch这一类让缓存带来的加速收益持续保留。4.4 有编译任务的项目别指望uv能省掉编译器uv下载的是预编译wheel但当某个包没有提供当前Python版本对应的wheel必须从源码编译时操作系统依然需要具备编译器工具链比如gcc、rust、go等以及Python头文件。这个行为和pip完全一致uv不会为你变魔术。举个例子某些数据科学常用包在Python新版本刚发布时往往要等几个月才会提供官方wheel。这时候如果你强行用最新Python去sync大概率会看到一堆编译错误。解决办法很朴素先用uv python pin 3.11这类方式切到一个已有较多预编译wheel的Python版本让依赖尽量以二进制方式装好避免在每次部署时都重新编译一遍。CI环境里同样要注意这一点。如果镜像里没有编译器而这次依赖解析又需要从源码构建那么构建阶段就会卡在同一个错误上。建议在CI配置中提前补上编译所需的基础包或者锁定一个成熟的Python版本减少编译类依赖出现的概率。我在实际项目中踩过一次最典型的坑是在新Python版本下同步一个包含多个需要编译链接的第三方库结果报错信息指向缺失的构建工具链。当时第一反应是去怀疑uv的二进制处理逻辑后来才意识到根本原因是那个库对最新Python还没有预编译wheel支持。所以遇到类似问题时先把视角从“这个工具怎么又坏了”切换成“这里到底是下载阶段失败还是构建阶段失败”能省下很多排查时间。从旧项目迁移过来时心理上不用太急。我给自己的过渡策略是新项目直接uv init加uv sync老项目先用uv pip install跑通日常开发等下次有较大的依赖升级需求时再整体迁移到pyproject.toml和uv管理。这样既不会让旧项目陷入马上重写的窘境又能逐步享受新工具带来的效率。最后分享一个我个人用得最多的小技巧临时调试脚本时直接uv run --no-project --with requests python debug.py。不需要建环境、不需要记忆虚拟环境路径一条命令就把临时环境准备妥当。真正高效的工具不是让你学会更多命令而是让你少想一些琐事把注意力挪回代码本身。

相关新闻

字节开源Agent手册:50行代码跑通本地可调试Agent

字节开源Agent手册:50行代码跑通本地可调试Agent

简介:本资源是字节跳动开源的《Agent实践手册》配套可运行源码包,面向AI工程化落地一线的技术团队与开发者,解决企业级AI智能体从设计、开发到规模化部署中的方法论缺失与实操断层问题。手册覆盖飞书会议辅助、抖音电商导购、教育备课等真实业…

2026/10/19 5:09:12 阅读更多 →
C 语言 scanf 函数详解:格式化输入、缓冲区与避坑指南

C 语言 scanf 函数详解:格式化输入、缓冲区与避坑指南

一、前言本文核心内容来自学习笔记,围绕 scanf 的三个关键问题展开:为什么变量必须取地址?为什么会阻塞?缓冲区残留的 \n 怎么处理? 在此基础上补充了常用格式符、返回值、常见错误与输入安全等知识点。二、核心知识点…

2026/10/19 5:09:12 阅读更多 →
artcraft手工艺术创作实战:从设计到批量的完整工艺拆解

artcraft手工艺术创作实战:从设计到批量的完整工艺拆解

artcraft,这个项目名字拆开看,就是 art craft,艺术与手艺的结合。我在这个行业里摸爬滚打了几年,越来越觉得纯艺术和纯手工正在快速融合,而 artcraft 这类项目恰好踩在了这个交叉点上:它不追求博物馆级别的…

2026/10/19 5:09:12 阅读更多 →

最新新闻

CSerialPort串口类2017修正版:稳定嵌入工业控制的Win32封装

CSerialPort串口类2017修正版:稳定嵌入工业控制的Win32封装

简介:这是一份面向C开发者,特别是Windows平台串口通信初学者与中级工程师的轻量级串口类封装资源。CSerialPort类经2017年最新修正,显著降低MFC依赖性,新增_AFX宏适配MFC环境,并提供纯Win32验证程序,使非MF…

2026/10/19 5:54:34 阅读更多 →
STM32+有人4G模块接入阿里云IoT完整实践指南

STM32+有人4G模块接入阿里云IoT完整实践指南

简介:本资源是一套完整的STM32嵌入式设备接入阿里云IoT平台的实战工程,面向具备C语言与STM32基础的物联网开发者及高校电子类专业学生,解决4G远程通信、云平台身份认证与双向数据交互等核心问题,适用于工业监控、智能硬件原型开发…

2026/10/19 5:54:34 阅读更多 →
Python Web应用部署实战:Docker+Nginx实现稳定上线

Python Web应用部署实战:Docker+Nginx实现稳定上线

部署Python Web应用这事儿,看起来简单,实际操作中能绊倒人的地方其实不少。本地用Flask写个Demo,起个服务跑得飞快,一旦要放到服务器上常驻运行,还要面对域名、反向代理、数据持久化、日志、自动重启这些问题&#xff…

2026/10/19 5:54:34 阅读更多 →
artcraft:面向人机协同的高精度创意控制范式

artcraft:面向人机协同的高精度创意控制范式

1. “Artcraft”不是新词,而是被低估的创作范式重启信号最近在几个设计类社区和开源项目讨论区里,“artcraft”这个词突然高频出现,但没人给它下定义。它不像“generative design”那样有明确的技术边界,也不像“digital craft”那…

2026/10/19 5:54:34 阅读更多 →
MEXA-324M汽车尾气测量仪实战全解:从原理到维修诊断

MEXA-324M汽车尾气测量仪实战全解:从原理到维修诊断

上个月,一位客户开着他那辆快十年的老车进店,说年检没过,单子上写着HC超标。他问了好几家店,有人说换火花塞,有人说洗喷油嘴,折腾了一圈回来复检还是不行。我们师傅把MEXA-324M尾气测量仪的探头往排气管里一…

2026/10/19 5:54:34 阅读更多 →
Diagram as Code:用可执行图表构建系统逻辑骨架

Diagram as Code:用可执行图表构建系统逻辑骨架

1. 项目概述:这不是画图,是构建可执行的系统逻辑骨架“diagram-design”这个标题乍看像一个设计类工具操作,但在我过去十年带过的几十个跨领域项目里,它从来不是美术课——而是工程师、产品负责人、运维人员和业务方坐在一起&…

2026/10/19 5:53:33 阅读更多 →

日新闻

频率跟踪型PWM电源设计:臭氧发生器谐振点漂移与软开关实现

频率跟踪型PWM电源设计:臭氧发生器谐振点漂移与软开关实现

简介:本资源为电源技术方向的学术研究文档,聚焦介质阻挡放电型臭氧发生器电源的频率跟踪型PWM控制策略,面向电力电子、高压电源及臭氧应用领域的科研人员与工程技术人员。针对负载呈容性、等效电容随外加电压升高而增大、谐振频率随之漂移的难…

2026/10/19 0:00:47 阅读更多 →
Vibe Coding 遇上 TaoToken:把开发者思维圣殿的灵感流接入统一 Key 通道

Vibe Coding 遇上 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/19 0:00:47 阅读更多 →
HundredCV-Chat话题建模实战教程:利用4,871个内置话题标签构建主题分析系统

HundredCV-Chat话题建模实战教程:利用4,871个内置话题标签构建主题分析系统

【免费下载链接】HundredCV-Chat 项目地址: https://ai.gitcode.com/hf_mirrors/Jax-dan/HundredCV-Chat 点击查看 免费下载 HundredCV-Chat 是一个由 100 位青年简历数据开发而来的中文多轮对话数据集,共包含 24,750 组日常闲聊对话和 4,871 个话题标签…

2026/10/19 0:00:47 阅读更多 →

周新闻

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

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

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

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

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

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

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

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

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

2026/10/19 1:30:52 阅读更多 →

月新闻

我发现了一个新思路:用 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/19 2:54:50 阅读更多 →
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/17 17:22:31 阅读更多 →
黑夜航拍船只数据集训练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/18 12:16:16 阅读更多 →