代码画架构图:Diagrams库选型与自动化维护实战
diagram-design 这个词字面意思就是“图的设计”。但在我这里它有个更具体的身份一套把系统架构图、云上拓扑图、甚至数据流转图从画布拖拽变成几段可维护代码的完整打法。你拿着它可以把团队里“画图两小时、改图一下午”的事情压缩成一条命令也可以让架构图跟着仓库的每一次 diff 走再也不是某个人电脑里的私有文件。如果你正在写技术方案、做云架构评审或者要给新来的伙伴讲清楚系统长什么样这套玩法能直接帮你省掉最烦的那部分重复劳动。这篇文章不会写太多虚的我把从选型到落地、从样式微调到踩坑修复的过程按真实顺序过一遍。适合被画布工具折磨过的人看也适合那些不想让图烂尾的团队参考。就算你暂时不打算引入新工具了解背后的图模型原理对怎么把图画清楚也有帮助。1. 图表设计这个事为什么值得用代码来做1.1 手动画图的三个老大难先说手动画图的问题。这里不是要完全否定画布工具Visio、draw.io 这些在临场开会时确实很好用但一旦图要长期维护问题就全冒出来了。第一个问题是版本管理。手工画出的图本质上是一张位图。这周拓扑改了两个节点你保存为 v8 副本下周同事又改一处另存为 v9两个版本之间到底有什么区别没人说得清。图片文件很难像代码一样做 diff也难做分支合入谁的保存顺序靠后谁说了算最后总会有人在群里喊一句“这张图是最新的吗”第二个问题是复用性差。我经常在一个项目里见五种长得几乎一样的图区别只是某些节点颜色不同、一些服务名不一样。想做成模板画布工具的模板能力有限结果通常是复制一份再改导致同一个系统里图越来越多但一致度越来越低。改图的人还得小心翼翼地避开那些“只属于这个视图”的特殊元素稍不注意就动错地方。第三个问题是自动化断层。手工图很难跟真实环境校验。线上挂了十台机器图里还标着三台数据库换了内核版本图里的数据库图标还是老样子。这些漂移在日常维护里太常见了而图的作者往往是最先离开项目的那个人。维护一张手工图本质上是在维护一份“有人记得才有用”的记忆可这种记忆在项目里恰恰最脆弱。1.2 代码画图解决的核心问题把图变成代码后事情开始变得有趣。首先是版本可追踪。你在 Git 里改一行给某个服务节点重命名提交之后 diff 里能明确看到哪个节点变了。下次谁再问“图准不准”直接把仓库和最新提交丢过去就行不用再解释“我这版是改过的你手上那版是上周的”。其次是可组装复用。定义一个“标准 Web 服务”组件负载均衡在后计算节点在中间数据层在最底下这些函数可以在不同项目里 import不用反复从零开始拖。我甚至会在团队里把这些组件定义抽成一个公共模块谁要画新项目架构图直接引用基础组件再补少量自定义节点半小时内就能出一张规范图。然后是结构化验证。图不只是给人看的画背后是有数据结构的。节点有哪些、边怎么连、哪些服务依赖数据库这些都可以写测试去断言。我加过一个很简单的检查所有写操作必须经过服务层不允许业务节点直接连存储节点一旦图里违反了规则流水线就直接标红。这在手工图里基本不可能实现。代码画图的终极形态是从配置或者真实资源定义里去自动生成图。集群里跑着一个服务清单脚本读取之后生成拓扑图每天定时更新。图不再是人工维护的宝贝而是资产文件的附属品。只要上游信息准确图永远不会过期。1.3 不是所有图都适合代码画一件事值得做不代表所有图都该往代码里塞。我自己的判断标准很简单图的内容是否跟着代码结构变化是否经常需要重新发布是否有明确的节点和边结构三个回答都是“是”那就适合代码画。反过来那种给运营小伙伴看的用户路径宣传图、各种视觉冲击力强的营销一图流或者需要大量手绘风格创意的海报图还是交给设计师和画布工具更合适。还有那种临时头脑风暴画的草稿直接拿白板画就行别打开编辑器。不要用代码画画的冲动去对抗创意工作工具各有所长硬套反而拖慢节奏。2. 工具选型解析Graphviz、PlantUML、Diagrams库怎么选2.1 三种主流方案的横向对比选型之前需要认识目前比较主流的三种代码画图路线。第一种是 Graphviz最老牌用所谓的 dot 语言描述图结构然后由 dot 工具自动排版输出成图片。第二种是 PlantUML语法更口语化尤其适合画 UML 类的图。第三种是 Diagrams 库一个 Python 库用对象建模的方式定义节点和边底层还是会去调用 Graphviz 完成排版。维度GraphvizPlantUMLDiagrams 库描述方式dot 语法偏底层类 UML 文本Python 对象贴近代码思维上手成本中等需理解布局概念低几行就能出图低会写 Python 就行图标生态基本全靠文本标签自带部分 UML/云图标云服务商组件齐全二次开发/自动化一般靠命令行一般可以直接嵌入 Python 生态适合画什么通用图、流程、拓扑时序图、类图、状态图架构图、云上拓扑这个表格不是想选出谁最强而是告诉你不同场景的舒适区。我见过有人用 Graphviz 硬画非常复杂的业务流程图节点和边多到根本没法读也见过有人用 Diagrams 库去画严格意义上的 UML 类图结果绕了很多路。工具本身没有高低只有匹配度。2.2 底层不变量图结构与自动布局无论你选哪条路线底下都逃不开同一个概念图。一张架构图拆到最小就是一堆节点和连接它们的边。画图是个视觉行为但设计图本质是建模行为。代码画图帮你把“建模”这一步提出来把坐标、颜色、排版这些视觉杂事交给布局引擎。你可以理解为你只告诉导航软件起点、终点和途经点路线长什么样由导航自动规划。很多新人第一次看到 Diagrams 库里几行代码就能生成一张干净图都觉得神奇其实就是这个原理。节点之间的“关系”才是你真正要维护的信息而坐标和线形只是关系的投影。这个思路对人的要求反而更高了。因为当你用鼠标拖图时区间划分、前后层级这些你潜意识里会想清楚用代码画图时如果关系定义得模棱两可渲染出来的图一定模棱两可。所以代码画图不是降低思考要求而是把思考落成了可检查的文本。2.3 我的选型结论为什么主推 Diagrams 库如果以架构图为目标我个人最常用的其实是 Diagrams 库理由有三点。第一Python 生态能跟日常脚本无缝衔接。第二它封装好的云服务图标已经相当全AWS、Azure、阿里云这些都有画云拓扑的时候很省事。第三当你需要做更复杂的自动化时可以拿 Python 直接生成图定义天然适合动态拼装。但这不代表 PlantUML 不好。团队如果全员用 PlantUML 做文档生态那就别折腾跟着生态走。而如果你只是偶尔画一张简单的数据流图Graphviz 的轻量写法已经够了。工具选型不是站队是为了省事。这也是“diagram-design”这件事的核心逻辑先理解你的图是什么类型再决定怎么设计它最后才轮到选工具。3. 从零实现一套可维护的架构图工程3.1 初始化环境与最小编码下面进入实操。我最常用的环境是 Diagrams 库加 Graphviz先说安装。Diagrams 库本身的安装很简单一条pip install diagrams就够。但它渲染依赖 Graphviz 这个底层的布局和渲染引擎需要提前装好。macOS 可以直接brew install graphvizDebian/Ubuntu 用apt-get install -y graphvizWindows 则下载安装包后把 bin 目录加进 PATH。很多同学报错找不到 dot 命令基本就是这一步没做全。安装完可以先跑一个最简示例from diagrams import Diagram from diagrams.aws.general import User with Diagram(访客示意, showFalse): User(访客)showFalse是让代码渲染后不要自动弹出图片对自动化场景很友好。保存为 demo.py执行python demo.py当前目录就会生成一张访客示意.png。你可以用任意图片查看器打开看到单个节点居中显示。虽然只有一个节点但跑通了整条链路。看到文件生成就说明你的 Graphviz 和 Diagrams 库已经打通了。这个最小示例看起来没什么用但它是后面一切的基础。我一般会把它作为环境是否正常的冒烟测试换了新电脑第一件事就是跑这个。3.2 设计一个“负载均衡两类服务数据库存储”的拓扑图现在演示一个小型应用拓扑更贴近真实场景。这个结构模拟了一个很常见的局面流量入口经过负载均衡一路进入 API 服务另一路进入异步任务二者都需要访问主数据库数据最终沉淀到对象存储。集群在这里把服务层和数据层包起来视觉上一下子清楚很多比所有节点全部平铺要好读。from diagrams import Diagram, Cluster, Edge from diagrams.aws.compute import EC2 from diagrams.aws.database import RDS from diagrams.aws.storage import S3 from diagrams.aws.network import ELB with Diagram(小型应用拓扑, showFalse, directionLR): lb ELB(负载均衡) with Cluster(服务层): api EC2(API 实例) worker EC2(异步任务) with Cluster(数据层): db RDS(主数据库) bucket S3(对象存储) lb Edge(label请求入口) api db bucket lb Edge(label写入任务处理) worker db api Edge(label数据读写) db不要小看这一层 Cluster它对应着真实架构里“逻辑边界”的表达。服务层和数据层在物理上可能跨机器、跨网络但在架构图上你希望读者第一眼就能理解这两个逻辑区域之间的关系。Cluster 就是用来承载这种逻辑分区的。我在实际项目中会发现很多新手画图时只关注节点本身忘了画“边”的语义。其实边才是架构设计里真正需要花心思的地方。哪条链路是同步调用哪条是异步哪条是低频请求这些信息在文字文档里要用大段话说明在图上其实用一条带标签的边就能表达清楚。3.3 布局方向、边标签和自定义样式布局方向我用directionLR控制也就是从左到右。默认是 TB也就是从上到下。数据库的架构图常喜欢用 TB长链路的调用用 LR 看着更顺这个取决于项目习惯。边是可以加语义的给负载均衡到异步任务的边加 label图里的阅读者就明白这条链路是干嘛的不用靠猜。样式方面Edge 可以设置 color 与 style比如把关键链路标红、把异步流程画成虚线。Diagrams 库也支持自定义节点图标可以用Custom(节点名, 图片路径)这样即使没有官方图标也能用本地图片素材撑住场子。比如有些私有协议组件官方图标里没有我一般会把内部系统截图抠成小图然后用 Custom 节点放进去图就不会出现“这个框是什么为什么没有图标”的疑问。还有一个容易忽略的功能是outformat。默认输出 PNG但如果你要把图嵌进文档SVG 更适合。改成with Diagram(示例, outformatsvg)输出就是矢量图放大不糊很适合技术文档印刷或投屏。3.4 把图接进文档和 CI 流水线手动执行 demo.py 没问题但真要长期用必须接入工程流程。我通常会把图生成到docs/architecture/目录然后文档里用静态资源方式引用。如果团队用了 MkDocs 或 Sphinx还可以写一个小脚本在构建文档前批量渲染所有架构图文档站自动更新。更进一步可以在 CI 流水线里加一个任务每次主分支有变更就执行渲染脚本再把生成的图片和之前的版本做对比。如果业务代码改了而架构图没有更新测试直接标红从流程上逼着大家同步维护图。这一步看着有点严但对一个长期项目来说特别实用。截图神器没了图就不会再腐烂了。我举个例子。某个团队之前有个约定每次服务扩容或下线都要更新架构文档。这个约定靠口头提醒坚持了两周就崩了。后来我把架构图渲染脚本挂进 CI凡是涉及集群配置变更的目录有改动就自动触发渲染并把“架构图已自动更新请人工确认”打印在流水线输出里。从那以后架构图基本没再过期过。4. 常见问题与排查技巧实录4.1 布局变乱、节点重叠怎么办很多人第一次生成的图都会遇到一个问题节点一多就挤成一团线乱飞标签互相重叠。这不是你笨而是布局引擎默认的算法有时候不够理想。解决办法有几个。第一用小集群拆分。把强相关的节点包进 Cluster布局引擎会优先考虑它们之间的紧密程度。第二控制整体方向。一个图里不要一会儿横一会儿竖选定 direction 后保持统一。第三把大图拆成多张。我从一次教训里学到当一张图有超过四五个集群、二十多个节点时阅读成本已经明显上升不如拆成系统全景图和各模块子图全景图用粗粒度节点子图做细粒度连线。最后还可以通过 graph_attr 直接改 Graphviz 底层的布局参数。with Diagram(调整布局, showFalse, directionTB, graph_attr{ranksep: 0.6, nodesep: 0.5}):ranksep和nodesep控制的是层级间距和同层节点间距数值调大一些节点就松开了。这个参数组合我经常用来处理节点过密的问题比手动回去调坐标省事得多。4.2 中文乱码与字体问题第二个高频问题是中文乱码。Graphviz 在渲染文本时需要一个能覆盖中文字符的字体系统没装对应字体或者不认识就画成一个个方块。解决办法不难第一系统层面装上中文字体比如 Noto Sans CJK第二在 Diagram 里告诉 Graphviz 用哪个字体名。with Diagram(中文架构图, showFalse, graph_attr{fontname: PingFang SC}):Windows 上可以写Microsoft YaHeimacOS 上写PingFang SCLinux 上写Noto Sans CJK SC。我这里强烈建议标签优先用英文结构清晰还不挑环境实在要用中文就把字体配好否则换一台机器渲染就容易出问题。字体问题不止影响标题节点内的文本也会受影响。如果一张图里既有中文又有英文最好统一字体族避免某个字体缺字导致个别标签变成方块那比整体乱码还难排查。4.3 渲染成功但节点消失或连线错误排查还有一种看起来很诡异的情况代码运行成功png 也生成了但有节点就是不在图上。我先说一个最常见的罪魁祸首重复的变量名。Diagrams 库在 with 块里如果你把两个不同节点赋给同一个变量名后面那个会把前面那个覆盖掉图中就会少一个节点。这个坑很隐蔽代码不报错图就是缺东西。另外节点必须定义在 with 块内部。有些同学把节点构造放在 with 外面备用结果会发现它们根本不会出现在图里。还有边尽量按真实依赖走不要为了视觉效果重复连接同一个节点这会导致布局引擎多绕路图越画越乱。当节点真正缺失时排查顺序建议是先检查变量名是否重复再检查节点是否在 with 块内最后检查该组件图标在当前库版本里是否存在。升级 diagrams 后偶尔会有路径变动报 ImportError 就换一个等价组件。这套顺序能解决九成以上问题。4.4 高频问题速查表现象可能原因处理方式中文渲染成方框缺少中文字体安装字体并配置 fontname节点消失变量名重复或节点放错位置检查 with 块和变量布局拥挤重叠节点过多或未分组用 Cluster 拆分边乱飞重复连线或方向混乱简化边并固定 direction找不到输出文件未指定路径或目录不对确认工作目录和文件名渲染报 dot 命令错误Graphviz 未安装或未加入 PATH安装 Graphviz 并配置环境变量4.5 长期维护的经验沉淀最后分享几个维护层面的体会。第一图的命名要像代码命名一样讲究别叫“最终版图1”直接按模块名或系统名命名比如“前端网关-拓扑”。第二每次架构调整顺手更新图定义这个习惯比任何工具都重要我自己甚至会把图渲染步骤写进项目 README防止换人之后无人维护。第三如果想画多个阶段的演进图用多个 Diagram 文件分开画然后把它们引用到一篇概览文档里不要试图在一张图里塞下过去、现在和未来。图上只画当前真实状态这是让它长期可信的唯一标准。我见过太多人试图在架构图里做“规划”把计划中的服务也画进去结果图变成立体迷宫谁也不知道哪部分已经上线哪部分还在设计稿里。代码画的图保存的是结构关系当它变成团队共识之后就只保留真相就好。我在实际维护这套图的过程中最明显的感受是它把“画图”这个动作从一次性劳动变成了持续的小成本投入。每次改完配置顺手改几行图定义提交时看一眼 diff架构就已经跟着更新了。这个习惯一旦养成你不再害怕别人问“图还准吗”因为你随时能拉出最新渲染结果。如果你还在为手工图的版本和滞后问题头疼不妨从今天这个最小示例开始把第一张图变成代码试试。

相关新闻

OpenAI API工程实践:提示词调优、RAG与错误排查全指南

OpenAI API工程实践:提示词调优、RAG与错误排查全指南

算起来,我接触OpenAI生态差不多三年了。从最早拿API写一些Demo脚本,到后来正经做企业级的文档问答、内容生成系统,中间踩过的坑、翻过的车,攒下来的经验足够写一篇长文了。这个标题就叫“openai”,看起来简单&#xff…

2026/10/20 13:58:47 阅读更多 →
基于OpenAI API的知识问答助手开发实践与调优指南

基于OpenAI API的知识问答助手开发实践与调优指南

1. 项目整体设计与思路拆解说句实话,这两年接触AI相关的技术开发,绕不开OpenAI生态。身边不少同行在问,到底怎么把一个基于OpenAI API的智能助手真正落到自己的业务里,而不是仅仅停留在网页对话框里玩一玩。这篇文章我就结合这段时…

2026/10/20 13:58:57 阅读更多 →
DeepSeek技术人实战指南:代码生成、Debug、日志分析与提示词模板

DeepSeek技术人实战指南:代码生成、Debug、日志分析与提示词模板

这会儿刚用 DeepSeek 把拖了大半周的一段重构收尾,关掉编辑器的时候忍不住想:这玩意儿现在在我的工作流里,地位已经从"尝鲜玩具"变成真老伙计了。前阵子和几个同行聊起来,发现不少人还在把它当聊天机器人用——问两句生…

2026/10/19 7:49:25 阅读更多 →

最新新闻

链表环与相交的底层原理:从内存地址到工程可靠性

链表环与相交的底层原理:从内存地址到工程可靠性

1. 这不是一道“刷题”题,而是数据结构底层思维的试金石“链表是否有环,是否相交”——看到这八个字,很多人第一反应是:哦,又是LeetCode第141、142、160题。翻出快慢指针模板,背下相遇点公式,提…

2026/10/20 13:57:57 阅读更多 →
AI辅助视频剪辑实操:把3小时剪片压缩到45分钟的时间账单

AI辅助视频剪辑实操:把3小时剪片压缩到45分钟的时间账单

经常有人问我:“为什么我剪一条视频要两三个小时,别人半小时就搞定了?”我一般不会直接回答,而是反过来问一句:“你剪视频的时候,是不是一直在手动做字幕?”对方往往一愣,然后陷进沉…

2026/10/20 13:57:57 阅读更多 →
DDS协议实战:分布式仿真系统实时数据分发与调优指南

DDS协议实战:分布式仿真系统实时数据分发与调优指南

做分布式仿真和实时系统这一行,通信协议往往是决定项目成败的“隐形变量”。我真正开始系统研究DDS(Data Distribution Service,数据分发服务)这个协议,是因为一个分布式仿真项目——多个异构节点要在局域网内实时同步…

2026/10/20 13:57:57 阅读更多 →
Python数据分析项目实战:从源码拆解到二次开发

Python数据分析项目实战:从源码拆解到二次开发

简介:这份资源面向希望系统掌握Python数据分析与可视化的学习者,覆盖从编程基础到实战项目的完整技能链路,适合高校学生、转行者及需要处理结构化数据的从业者。压缩包共162个文件,约31.27MB,以67个ipynb实验代码、11个…

2026/10/20 13:57:57 阅读更多 →
AI写教材如何避免重复?低查重工具嵌入创作流程的实战指南

AI写教材如何避免重复?低查重工具嵌入创作流程的实战指南

如果有人觉得“AI写教材”就是输入标题、点一下生成、再复制粘贴,那就大错特错了。真正做过教材或讲义的人都知道,教材创作最耗时的不是“写”,而是让内容准确、成体系、可教学,同时还要保证表达足够原创、不跟已有资料大面积重合…

2026/10/20 13:57:57 阅读更多 →
AO环境光遮蔽合成指南:黑线与白底之间的数值差异与混合策略

AO环境光遮蔽合成指南:黑线与白底之间的数值差异与混合策略

/* 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 13:56:50 阅读更多 →

日新闻

CLRC663芯片Pin-to-Pin替代实战:从评估到量产全记录

CLRC663芯片Pin-to-Pin替代实战:从评估到量产全记录

/* 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 1:01:30 阅读更多 →
ESP32舵机控制指南:原理、多路驱动与避坑实战

ESP32舵机控制指南:原理、多路驱动与避坑实战

/* 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 1:01:30 阅读更多 →
位移法实现一阶低通滤波:单片机ADC采样降噪实战

位移法实现一阶低通滤波:单片机ADC采样降噪实战

/* 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 1:01:30 阅读更多 →

周新闻

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

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

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

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

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

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

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

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

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

2026/10/20 2:50:50 阅读更多 →

月新闻

我发现了一个新思路:用 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 阅读更多 →