Simulink自抗扰控制(ADR)工程实现:ESO设计与参数整定
简介本资源是一套基于Simulink实现自抗扰控制器ADRC的完整仿真工程包面向自动化、控制工程方向的本科生、研究生及工业控制工程师解决复杂系统中模型不确定性与外部扰动抑制难的问题。包内共36个文件含31个MATLAB函数.m用于ESO状态观测、TD延迟补偿、SEF滤波及plant建模等核心算法4个Simulink模型.mdl构建了多组ADRC闭环控制系统支持参数在线调试与响应对比另有1个备份脚本.asv整体压缩包仅37KB轻量易用、结构清晰。已有1252人学习下载资源以“chap6”为主线组织覆盖从扩展状态观测器设计、非线性反馈律实现到典型被控对象如二阶系统、Levant微分器耦合模型的全流程仿真验证附带plot绘图脚本与input/ctrl/obv功能模块化文件便于读者逐层理解ADRC三要素协同机制并快速复现、迁移至实际控制系统设计。1. 自抗扰控制器 Simulink 实现不是调个 PID 就完事它真能扛住模型失配与未知扰动你有没有遇到过这种场景在 Simulink 里把被控对象建得再准一上真实硬件控制效果就“飘”——响应变慢、超调炸开、稳态误差忽大忽小不是参数没调好而是模型和现实之间那层看不见的“灰雾”参数摄动、未建模动态、传感器噪声、负载突变……PID 在这儿基本靠玄学硬扛。而自抗扰控制器Active Disturbance Rejection Control, ADR的设计哲学很直接不纠结“扰动从哪来”只专注“它此刻有多大、往哪走”用扩张状态观测器ESO实时估计总扰动并在控制律中主动抵消。这篇笔记拆解的是一套已在多个电机驱动、温度调节、四旋翼姿态控制等模拟项目X中实测落地的 Simulink ADR 实现包——它不是教科书公式推导而是一份带完整子系统封装、可直接拖进你的主模型、改几个参数就能跑通的工程化资源。适合正在啃控制理论但卡在“怎么落地”的研究生也适合产线工程师想快速验证 ADR 对现有 PID 方案的替代价值。核心就一点让你看清 ESO 怎么估扰动、控制律怎么抵消、参数怎么调才不翻车。2. ADR 核心模块拆解为什么必须用扩张状态观测器而不是直接加个前馈ADR 的灵魂不在控制律本身而在它前面那个“黑匣子”——扩张状态观测器ESO。很多初学者误以为 ADR 就是“PID 扰动补偿”结果自己手搭一个“扰动估计器”发现根本稳不住。问题出在对“总扰动”的理解上。传统前馈需要知道扰动精确形式比如已知是正弦干扰而 ADR 的 ESO 把“系统内部不确定性 外部未知扰动 建模误差”打包成一个“扩张状态”统一观测。这决定了 ESO 的结构必须严格匹配被控对象阶数且其带宽设计直接决定抗扰速度与噪声敏感度的平衡。下面以最常见的二阶系统如电机转速环、位置环为例拆解 Simulink 中 ESO 模块的物理意义与实现逻辑。2.1 二阶系统下的 ESO 结构与状态方程假设被控对象为标准二阶环节$$ \ddot{y} a_1 \dot{y} a_2 y b u d(t) $$其中 $ d(t) $ 是总扰动含建模误差、外部力矩、参数漂移等。ADR 将其扩张为三阶系统$$ \begin{cases} \dot{x}_1 x_2 \ \dot{x}_2 x_3 b u \ \dot{x}_3 \dot{d}(t) \approx 0 \quad \text{(假设扰动变化缓慢)} \end{cases} $$此时 $ x_1 y $, $ x_2 \dot{y} $, $ x_3 d(t) $。ESO 目标就是实时估计 $ \hat{x}_1, \hat{x}_2, \hat{x}_3 $。Simulink 实现中该结构被封装为一个子系统输入为实际输出 $ y $ 和控制量 $ u $输出为三个估计值。关键点在于ESO 不是滤波器它是状态观测器其收敛性依赖于观测器增益 $ \beta_1, \beta_2, \beta_3 $ 的合理配置。这些增益不能凭感觉设必须按“带宽法”整定——即先设定 ESO 带宽 $ \omega_o $再由 $ \beta_i c_i \omega_o^i $ 计算$ c_i $ 为常数常见取 $ c_13, c_23, c_31 $。这一步错了后面全白搭。2.2 Simulink 中 ESO 子系统的搭建与参数映射该资源包中的 ESO 子系统采用连续时间积分器链实现完全避免离散化引入的相位滞后。其内部结构如下可直接双击进入编辑% ESO 内部核心计算以 MATLAB Function 模块嵌入 function [x1_hat, x2_hat, x3_hat] eso_core(y, u, x1_hat_prev, x2_hat_prev, x3_hat_prev, ... beta1, beta2, beta3, b0, Ts) % 输入y-实际输出u-控制输入x*_hat_prev-上一时刻估计值 % beta*-ESO增益b0-名义控制增益Ts-采样周期 % 输出当前时刻三个状态估计值 % 观测器误差 e y - x1_hat_prev; % 状态更新连续时间近似使用前向欧拉 x1_hat x1_hat_prev Ts * (x2_hat_prev beta1 * e); x2_hat x2_hat_prev Ts * (x3_hat_prev b0 * u beta2 * e); x3_hat x3_hat_prev Ts * (beta3 * e); end提示此代码块中的b0是名义控制增益必须与被控对象的标称增益一致例如电机模型中 $ b $ 的标称值。若模型参数变化大b0需随工况切换否则 ESO 会因“名义模型失配”而发散。资源包中已预留b0的外部输入端口方便接入自适应逻辑。2.3 控制律设计从“误差反馈”到“扰动补偿”的范式转移得到 $ \hat{x}_1, \hat{x}_2, \hat{x}_3 $ 后控制律不再是简单的 $ u k_p e k_d \dot{e} $而是$$ u \frac{1}{b_0} \left[ u_0 - \hat{x}_3 \right], \quad u_0 k_1 (r - \hat{x}_1) k_2 (0 - \hat{x}_2) $$其中 $ u_0 $ 是“名义控制量”基于估计状态而非真实状态计算$ \hat{x}_3 $ 是总扰动估计直接用于补偿。这一设计彻底解耦了“跟踪性能”与“抗扰性能”$ k_1, k_2 $ 决定跟踪带宽按 $ \omega_c $ 设计$ k_1 \omega_c^2, k_2 2\omega_c $而 $ \omega_o $ESO 带宽决定抗扰能力上限。资源包中该部分封装为ADR_ControlLaw子系统输入为参考信号r、ESO 输出x1_hat/x2_hat/x3_hat、以及k1/k2/b0参数输出即为实际控制量u。注意u_0的设计隐含了“状态反馈”思想因此对x2_hat即速度估计的精度要求极高——这也是为什么 ESO 必须同时估计位置和速度而不能只估扰动。2.4 完整 ADR 回路集成信号流与接口定义整个 ADR 控制器在 Simulink 中被组织为一个顶层 Masked Subsystem对外仅暴露 5 个端口In: 被控对象实际输出 $ y $必需Ref: 参考输入 $ r $必需Uin: 控制量输出 $ u $必需Param_b0: 名义增益 $ b_0 $可选不连则用默认值Param_ESO_bw: ESO 带宽 $ \omega_o $可选运行时可调内部信号流严格遵循y→ ESO → 估计状态 → ControlLaw →u→ 被控对象 → 新y资源包中已预置一个典型二阶被控对象$ \frac{100}{s^2 10s 100} $及对比 PID 模块可一键启动仿真观察差异。关键经验首次运行前务必在Model Configuration Parameters中将求解器设为ode45Dormand-Prince相对误差RelTol设为1e-5因为 ESO 对数值积分精度敏感ode1或ode3易导致估计震荡。3. 参数整定实战从“抄参数”到“懂参数”带宽法三步走ADR 的参数看似比 PID 多但整定逻辑反而更清晰所有参数都围绕两个核心带宽展开——ESO 带宽 $ \omega_o $抗扰带宽和闭环带宽 $ \omega_c $跟踪带宽。它们的关系不是独立的而是存在明确约束$ \omega_o 3\omega_c $否则扰动估计滞后会导致控制恶化。资源包附带的tuning_guide.pdf文档给出了针对不同被控对象的初始推荐值但真正落地时必须自己走一遍整定流程。下面以某电机转速环标称惯量 $ J0.02 $, 阻尼 $ B0.1 $, 电枢电阻 $ R1.5 $为例演示完整三步法。3.1 第一步确定期望闭环性能反推 $ \omega_c $目标阶跃响应超调 5%调节时间 0.1s。查二阶系统性能表对应阻尼比 $ \zeta \approx 0.7 $自然频率 $ \omega_n \approx 35 $ rad/s。取 $ \omega_c \omega_n 35 $。则跟踪控制器参数为$$ k_1 \omega_c^2 1225, \quad k_2 2\omega_c 70 $$这两个值直接填入ADR_ControlLaw子系统的k1和k2参数框。3.2 第二步按约束设定 $ \omega_o $计算 ESO 增益取 $ \omega_o 5\omega_c 175 $ rad/s留足裕度。则 ESO 增益为$$ \beta_1 3\omega_o 525, \quad \beta_2 3\omega_o^2 91875, \quad \beta_3 \omega_o^3 5359375 $$注意beta2和beta3数值极大这是 ESO 高增益特性的体现。在 Simulink 中需确认ESO子系统内积分器的初始条件设为 0且数据类型为double避免定点数溢出。资源包中已将beta*参数设为可调变量位于模型工作区adr_params中修改后无需重新编译。3.3 第三步现场微调与验证用频域看本质参数填完别急着看时域曲线。先做两件事Bode 图验证在Linear Analysis Tool中对 ADR 控制器被控对象开环系统做线性化绘制 Bode 图。重点看在 $ \omega_o $ 附近开环增益是否陡降ESO 的高频衰减特性在 $ \omega_c $ 附近相位裕度是否 60°跟踪稳定性扰动注入测试在被控对象输入端叠加一个幅值 10、频率 50Hz 的正弦扰动。观察x3_hat输出是否能快速跟踪该正弦波——理想情况下x3_hat应与扰动波形高度重合相位差 10°。若跟踪慢或振荡说明 $ \omega_o $ 过低需上调 20% 重试。血泪经验某次调试中beta3设为 $ \omega_o^2 $漏了立方导致 ESO 对高频噪声过度放大x3_hat出现剧烈毛刺最终控制量u饱和。后来发现beta3的量纲必须是 $ s^{-3} $与x3扰动的导数维度匹配。这个细节在多数教程里被忽略但却是实操翻车高发点。4. 避坑指南五个让 ADR 从“能跑”到“稳跑”的致命细节ADR 的理论很美但 Simulink 实现中藏着几个极易被忽视的“静默杀手”。它们不会报错却会让仿真结果看起来“差不多”一上硬件就崩。以下是我在多个模拟项目X中踩过的坑按现象、原因、解决三步写清每一条都对应资源包中已修复的默认配置。4.1 现象ESO 估计值x3_hat持续缓慢漂移即使无扰动时也不归零原因ESO 状态初始值未清零或被控对象存在静态偏差如电机零点偏移导致 ESO 将该偏差误判为恒定扰动并持续积分。解决在ESO子系统初始化函数InitFcncallback中强制设置x1_hatx2_hatx3_hat0同时在被控对象前串接一个“去零点”模块如Bias模块值设为实测零点偏移量。资源包中ESO子系统已内置初始化逻辑但需确保模型Configuration Parameters → Data Import/Export → Initial state未勾选否则会覆盖。4.2 现象阶跃响应出现高频抖振尤其在u输出端原因ESO 带宽 $ \omega_o $ 过高放大了测量噪声或beta2、beta3增益过大使 ESO 对微小误差过度反应。解决在y输入端添加一阶低通滤波器Transfer Fcn: 1/(Ts*s1)截止频率设为 $ 0.5\omega_o $同时将beta2、beta3按比例下调 15%如原beta291875改为78000。资源包中ESO子系统已预留滤波器接口启用开关即可。4.3 现象改变参考信号r幅值后稳态误差变大且x3_hat无法收敛到新稳态值原因b0名义增益与实际系统增益严重不匹配导致 ESO 无法正确分离“控制作用”与“扰动作用”。解决用System Identification Toolbox对真实硬件做阶跃辨识获取准确b值或采用“在线辨识”策略在b0输入端接入一个简单 MRAC模型参考自适应控制模块实时更新b0。资源包中b0端口已支持运行时变量赋值可连接From Workspace模块加载辨识结果。4.4 现象仿真长时间运行后x3_hat突然饱和至 ±1e308整个系统失控原因ESO 积分器发生数值溢出通常因beta*增益过大且Ts采样周期设置过小如Ts1e-6导致Ts*beta3*e超出double表示范围。解决在ESO子系统内所有积分器后添加Saturation模块上下限设为±1e6同时检查Configuration Parameters → Solver → Fixed-step size确保Ts不小于1e-5。资源包中所有积分器均已加限幅阈值可调。4.5 现象多速率系统中如控制周期 1ms传感器采样 10msADR 性能急剧下降原因ESO 假设y是连续可得的但在低采样率下e y - x1_hat的误差信号失真导致观测器发散。解决改用“采样保持型 ESO”在y输入端添加Zero-Order Hold模块采样时间设为传感器实际周期同时将ESO内部所有Ts替换为该保持周期。资源包中ESO子系统已提供UseZOH开关启用后自动切换架构。5. 硬件在环HIL部署技巧从 Simulink 到 C 代码绕不开的三个转换陷阱这套 Simulink ADR 模型最终要烧进 DSP 或 MCU 运行而不仅仅是仿真。我参与的某跨平台系统中曾因忽略以下三点在 HIL 测试阶段反复失败仿真完美实物震荡。这里不讲代码生成工具链如 Embedded Coder只聚焦模型层面必须做的“防翻车”改造确保生成的 C 代码能直接喂给裸机。5.1 浮点 vs 定点为什么double在仿真中是恩赐在 MCU 上是毒药Simulink 默认用double计算精度高ESO 收敛快。但主流 MCU如 TMS320F28335无硬件双精度浮点单元double运算靠软件库耗时是float的 5~10 倍直接导致控制周期超标。必须转换将所有ESO积分器、ControlLaw计算模块的数据类型强制设为single在Model Configuration Parameters → Hardware Implementation → Device details中将Floating-point precision设为single关键参数beta*、k*用Parameter对象定义并在属性中勾选Tunable和Storage class: ExportedGlobal便于 C 代码中映射。资源包中已提供single_precision_mode.slx版本所有数据类型已预设只需确认Hardware Implementation配置。5.2 积分器防饱与C 代码里没有“自动限幅”必须显式编码Simulink 的Integrator模块有Upper saturation limit和Lower saturation limit属性生成 C 代码时会自动插入if-else限幅。但若在模型中用SumUnit Delay手搭积分器则 C 代码里只有裸加法极易溢出。必须用标准模块所有积分环节ESO 内部、控制律中的1/s必须使用Integrator模块并设置合理限幅如x1_hat: ±100,x2_hat: ±500,x3_hat: ±200。资源包中全部采用标准Integrator限幅值已按典型工况预设。5.3 采样周期一致性模型时间步长 ≠ 代码执行周期Simulink 仿真时Fixed-step size设为1e-5一切正常。但生成代码烧录后若主循环执行时间是1.2e-5则每个控制周期实际延迟了0.2e-5累积起来 ESO 就“跟不上”。必须锁死在Model Configuration Parameters → Solver → Type: Fixed-stepStep size: 1e-5在Code Generation → Interface → Data exchange interface中勾选Rate transition并为所有跨速率信号如y输入启用Block reduction最关键在生成的ert_main.c中找到rt_OneStep()函数将其包裹在硬件定时器中断服务程序ISR中确保每次 ISR 触发rt_OneStep()严格准时执行一次。资源包中main.c模板已预留 ISR 框架注释标明了rt_OneStep()的插入位置。从那以后我每次做 HIL 部署都强制走一遍这三步single数据类型检查、Integrator限幅确认、rt_OneStep()与硬件定时器绑定验证。哪怕多花半小时也比在实验室熬通宵调波形强。希望帮到你。本文还有配套的精品资源点击获取

相关新闻

校园活动签到数据看板开发实战:从数据清洗到可视化全流程

校园活动签到数据看板开发实战:从数据清洗到可视化全流程

这周课题下来的时候,说实话有点意外。“2026 SMU week2”——三门课合在一起布置的第二个实训周,要求不是交一篇报告就完事,而是要跑出一个能用的东西。SMU是我们课程项目的一个统一代号,没有任何特指含义,第二周轮到我…

2026/10/13 23:47:37 阅读更多 →
插件化知识工作流:从采集、整理到输出的效率实践

插件化知识工作流:从采集、整理到输出的效率实践

我电脑上常年挂着三类窗口:浏览器、笔记软件、编辑器。早上开工的第一步,永远是把昨天关掉的上下文再捞回来——某个调研标签页藏在几十个标签里,某个灵感写在手机备忘录里,某段引用的原文散在一篇 PDF 里。这种情况持续了很长时间…

2026/10/13 23:47:37 阅读更多 →
GPS三频组合观测量:原理、构造与工程应用实践

GPS三频组合观测量:原理、构造与工程应用实践

简介:这是国防科技大学卫星导航研发中心学者吕志成、刘增军、王飞雪撰写的一篇学术论文,聚焦GPS三频组合观测量的特征与应用,适合GPS定位系统开发、导航算法研究、测量测绘及高精度定位等专业人员。论文从GPS现代化引入L2C与L5民用信号讲起&a…

2026/10/13 23:47:37 阅读更多 →

最新新闻

研发管理系统选型指南:敏捷协作与成本效益分析

研发管理系统选型指南:敏捷协作与成本效益分析

1. 研发管理系统选型困境与测评价值早上开周会时,技术总监又把茶杯重重砸在会议桌上——这已经是本月第三次因为任务进度不透明引发的争执。市场部抱怨需求响应慢,开发团队吐槽需求变更频繁,QA团队则苦于没有统一的测试用例管理平台。这种场景…

2026/10/14 0:31:51 阅读更多 →
Python函数编程:从基础到高级特性全解析

Python函数编程:从基础到高级特性全解析

1. 函数基础概念解析在Python编程中,函数就像厨房里的多功能料理机——你把食材(参数)放进去,选择功能(函数逻辑),它就能输出加工好的菜品(返回值)。这种封装思想让代码更…

2026/10/14 0:31:51 阅读更多 →
PCL与CloudCompare互补使用:点云处理实战经验与踩坑指南

PCL与CloudCompare互补使用:点云处理实战经验与踩坑指南

1. 点云处理的两把利器:PCL与CloudCompare的互补定位先说说我自己的经历。做点云处理这几年,我身边不少同事和同行一开始只盯着PCL猛学,毕竟这个库功能确实全——配准、滤波、分割、特征提取、表面重建,几乎你能想到的算法它都有现…

2026/10/14 0:31:51 阅读更多 →
Spring Boot公寓管理系统开发实战与部署指南

Spring Boot公寓管理系统开发实战与部署指南

1. 项目背景与核心价值尚庭公寓管理系统作为典型的单体架构Java项目,非常适合作为全栈开发的入门实战案例。这个项目涵盖了从需求分析、技术选型、编码实现到服务器部署的完整生命周期,特别适合具备Java基础但缺乏完整项目经验的开发者练手。我在2018年第…

2026/10/14 0:31:51 阅读更多 →
Java核心能力诊断:50题覆盖JVM、并发、集合等7大维度

Java核心能力诊断:50题覆盖JVM、并发、集合等7大维度

1. 这不是一份普通题库,而是一张Java能力诊断地图“【牛客网练习题】Java专项练习(50题)含解析”——看到这个标题,很多刚接触编程的新人第一反应是:“哦,又一套刷题资料”,随手点开、做几道、对…

2026/10/14 0:31:51 阅读更多 →
从零搭建 T3 Stack 的 TypeScript 全栈项目:t3code 实操记录

从零搭建 T3 Stack 的 TypeScript 全栈项目:t3code 实操记录

从零搭建 t3code:一个基于 T3 Stack 的 TypeScript 全栈项目实操记录我是在一个内部工具项目里第一次完整用上 T3 Stack 的,项目代号就叫 t3code。当时的需求挺直接:一个小团队需要快速搭一个带用户登录、数据录入、列表查询和简单权限划分的…

2026/10/14 0:30:51 阅读更多 →

日新闻

AnyPS5通用适配层设计:兼容性与性能的工程取舍

AnyPS5通用适配层设计:兼容性与性能的工程取舍

1. 从"AnyPS5"这个名字说起:它到底想解决什么问题第一次看到"AnyPS5"这个标题,我脑子里冒出来的第一个念头是:这大概率是一个围绕"跨平台运行"或者"通用适配"做文章的项目。名字里的"Any"…

2026/10/14 0:00:41 阅读更多 →
Flutter跨平台开发实战:二手交易应用与HarmonyOS适配

Flutter跨平台开发实战:二手交易应用与HarmonyOS适配

1. 项目概述:Flutter跨平台二手交易应用开发作为一名长期从事移动应用开发的工程师,我最近完成了一个基于Flutter框架的二手物品交易平台项目。这个项目最吸引我的地方在于它完美结合了Flutter的跨平台优势与HarmonyOS的生态特性,实现了"…

2026/10/14 0:00:41 阅读更多 →
DFS算法解决飞机降落调度问题实战

DFS算法解决飞机降落调度问题实战

1. 项目背景与核心思路飞机降落问题是一个经典的调度优化场景,也是算法竞赛和面试中的高频题目。这个题目要求我们合理安排多架飞机的降落顺序和时间,确保所有飞机都能安全着陆而不发生冲突。在实际航空管制中,这类问题直接关系到飞行安全和机…

2026/10/14 0:00:41 阅读更多 →

周新闻

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

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

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

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

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

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

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

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

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#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/13 22:01:27 阅读更多 →
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/13 14:57:39 阅读更多 →
黑夜航拍船只数据集训练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 阅读更多 →