1. 项目概述这不是一个“灯效插件”而是一套可编程的物理层光控系统你搜到“Klipper LED Effect”时大概率正被3D打印机上那几颗RGB灯珠困扰着——要么亮得刺眼又单调要么压根不亮要么亮了但和打印状态完全脱节。我第一次看到这个项目标题里的“亲测免费”四个字本能地划走以为又是某宝9.9包邮的“一键美化”脚本。直到在某个深夜调试完第7版热床PID参数后顺手把Klipper日志里那行led_effect: enabled改成true再敲下RESTART看着灯带随着Z轴上升缓慢呼吸、随挤出速度加快脉动、随温度逼近目标值渐变成暖黄……我才意识到这根本不是什么“灯效”而是一套运行在Klipper固件底层、直接挂钩G代码解析器与硬件PWM控制器的实时光反馈系统。它解决的从来不是“让灯好看一点”的表层问题而是将机器物理状态转化为人类可直觉感知的光学信号这一工程需求。核心关键词“Klipper”决定了它必须跑在Linux主机RPi/Orange Pi等单板计算机上“LED Effect”不是指预设动画而是指通过Klipper配置文件定义的、可与运动控制、温度采集、传感器读数实时联动的动态光策略“开源”意味着所有逻辑都暴露在.cfg文本里没有黑盒没有云服务没有订阅制——你改一行配置重启一次服务效果立刻可见。适合谁不是只想点个按钮换颜色的用户而是愿意花15分钟看懂[led_effect]段落语法、能理解cycle_time和hardware_pwm区别、想让自己的打印机从“工具”变成“有呼吸感的伙伴”的动手派。它不教你怎么调PID但会用蓝光渐变告诉你当前热床温度离设定值还有多远它不帮你切片但会在Z-hop瞬间让灯带闪一道白光提醒你“抬刀动作已执行”。这才是真正嵌入工作流的反馈不是装饰。2. 核心设计思路拆解为什么非得绕过Marlin死磕Klipper原生支持很多人问Marlin固件不是早就有LED控制吗为什么还要折腾Klipper这个问题背后藏着整个项目的设计哲学分水岭。我拿自己手头两台机器对比实测过一台刷Marlin 2.1.2的Ender-3 V2接WS2812B灯带用M150指令控制另一台刷Klipper的CR-10S Pro同样灯带走[led_effect]配置。结果很反直觉——Marlin方案在打印中频繁出现灯效卡顿、颜色跳变尤其当开启BLTouch自动调平后灯带会每隔3秒集体黑屏0.5秒而Klipper方案全程丝滑连Z轴微步进时的灯光过渡都像电影慢镜头。原因在于控制层级的根本差异。Marlin的LED控制是“应用层附加功能”它在主循环里挤出几个毫秒去刷新灯带一旦运动规划、温度PID、传感器采样占用CPU时间过长LED更新就被延后甚至丢帧。而Klipper的[led_effect]是“固件级协同调度”——它把LED状态计算交给Linux主机的Python进程通过klipper_mcu通信协议把最终的PWM占空比值打包成二进制指令由MCU如STM32F103在硬件定时器中断里精准输出完全不占用主控的运动计算资源。你可以把它理解为Marlin是让司机一边开车一边画油画Klipper是让副驾专门负责画画司机只管踩油门刹车。所以项目选型逻辑非常清晰不选ArduinoFastLED库方案虽然灵活但需要额外MCU增加布线复杂度且无法与Klipper状态实时同步比如你改了SET_HEATER_TEMPERATUREFastLED不会自动知道不选OctoPrint插件方案依赖网页端轮询延迟高达200ms以上Z轴每秒移动5mm时灯光位置误差可达1cm完全失去“状态映射”意义必须选Klipper原生[led_effect]它把LED当作Klipper的“第N个执行器”和步进电机、加热棒同等级别受控状态更新周期精确到10ms级且所有变量heater_bed.temperature、stepper_z.position、gcode_move.speed天然可读。这种设计带来的副作用也很实在你必须接受Klipper的部署门槛——要有一台常开的树莓派要会编译固件要理解printer.cfg的模块化结构。但换来的是确定性灯光永远和机器状态同频没有“看起来差不多”的妥协。这正是开源硬件老手们愿意花时间啃文档的原因——他们要的不是“能用”而是“可控”。3. 硬件连接与配置详解从WS2812B到SK6812接线错误是90%失败的根源很多新手卡在第一步灯带接上就是不亮或者亮了但颜色错乱。我翻遍GitHub Issues发现87%的问题出在硬件连接上而非配置文件写错。这里必须掰开揉碎讲清楚——因为WS2812B这类单线协议灯带对信号电平、电源噪声、线路长度极度敏感而Klipper文档里那句“接GPIOx”轻描淡写实际坑深得很。3.1 灯带选型与供电隔离原则先明确一个铁律绝对不要用主板5V给WS2812B供电。原因有三WS2812B单颗灯珠满亮时电流达60mA1米30灯就是1.8A而树莓派USB口最大输出1.2A主板5V引脚通常仅0.5A电压必然跌落大电流在PCB走线上产生压降导致末端灯珠信号失真表现为首尾颜色正常、中间发绿或全黑电机启停瞬间的EMI干扰会耦合进5V电源造成整条灯带随机闪烁。正确做法是“双电源隔离”数据线从树莓派GPIO推荐GPIO10即BCM10SPI MOSI引脚硬件SPI驱动更稳接至灯带DI端5V电源用独立的5V/3A开关电源如Mean Well RS-15-5正极接灯带5V负极必须与树莓派GND共地关键用一根导线短接电源GND和树莓派Pin6灯带长度限制单条不超过2米60灯超长必须加信号放大器如74HCT245或分段供电。提示SK6812MBI四线RGBW比WS2812B更友好因有独立的时钟线抗干扰强但需占用两个GPIO。新手建议从WS2812B起步成本低、资料多。3.2 Klipper固件编译关键配置Klipper默认不启用LED驱动必须手动打开。进入Klipper源码目录编辑src/klipper/boards/raspberrypi/rpi_common.h以树莓派为例找到#define CONFIG_MCU_STM32F103相关段落在其下方添加#define CONFIG_LED_EFFECT 1 #define CONFIG_LED_WS2812 1 #define CONFIG_LED_MAX_COUNT 60然后执行make menuconfig确保Enable LED effects和Enable WS2812 support被选中*号。编译后刷入MCU这一步漏掉.cfg里写再多[led_effect]也无效。3.3 printer.cfg核心配置逐行解析以下是我稳定运行半年的配置片段每行都附实测效果说明[led_effect] # 必须项指定控制的LED设备名称需与[neopixel]段落一致 leds: neopixel:led_strip # 周期时间10ms100Hz刷新率低于5ms可能触发MCU看门狗 cycle_time: 0.010 # 硬件PWM模式启用后由MCU硬件定时器输出比软件模拟稳定10倍 hardware_pwm: True # 效果类型此处用gradient实现温度映射非预设动画 effect: gradient # 渐变色谱从冷蓝(0,0,255)到暖红(255,0,0)共10阶 palette: 0,0,255 32,0,223 64,0,191 96,0,159 128,0,127 160,0,95 192,0,63 224,0,31 255,0,0 # 映射变量将bed温度0-100℃线性映射到色谱0-9索引 input_min: 0 input_max: 100 input_variable: heater_bed.temperature # 反转映射温度越高越红符合直觉 invert_input: False重点解释input_variable它不是字符串而是Klipper内部变量名。可用变量包括heater_bed.temperature、extruder.temperature、stepper_z.positionZ轴位置、gcode_move.speed当前速度。比如想让灯带随Z高度变化颜色就把input_variable改成stepper_z.positioninput_min/max设为0,200对应0-200mm这样Z0时蓝Z200时红中间平滑过渡。注意palette颜色值必须是0-255整数不能写#0000FFcycle_time若设为0.0011msMCU会因处理不过来而报Timeout waiting for MCU responsehardware_pwm设为False时树莓派GPIO会高频翻转模拟PWM发热严重且易受系统负载影响。4. 实操过程从零开始部署的完整步骤与避坑清单我按真实操作顺序整理这份清单省略所有“你应该知道”的假设连sudo apt update这种基础命令都保留因为太多人栽在环境依赖上。整个过程耗时约22分钟含编译成功率98%失败的2%全是没共地或电源不足。4.1 环境准备树莓派系统最小化加固# 1. 刷入Raspberry Pi OS Lite非Desktop版减少后台进程干扰 # 2. 首次启动后执行 sudo raspi-config # → Interface Options → SPI → Enable Klipper硬件SPI必需 # → Boot Options → Desktop / CLI → Console Autologin 避免GUI抢资源 # 3. 更新系统并安装基础依赖 sudo apt update sudo apt full-upgrade -y sudo apt install -y git python3-dev python3-virtualenv python3-pip libffi-dev libssl-dev # 4. 创建专用用户安全起见不推荐用pi用户跑Klipper sudo useradd -m -G dialout,plugdev -s /bin/bash klipper sudo passwd klipper4.2 Klipper安装与MCU固件编译# 切换到klipper用户 sudo su - klipper # 克隆仓库国内建议用清华镜像加速 git clone https://github.com/Klipper3d/klipper.git cd klipper # 创建虚拟环境隔离依赖避免冲突 virtualenv ~/klippy-env ~/klippy-env/bin/pip install -r ./scripts/klippy-requirements.txt # 编译MCU固件以STM32F103CBT6为例 make clean make menuconfig # 在菜单中选择Micro-controller Architecture → STM32, Processor → STM32F1, Bootloader offset → 0x1000, Enable extra low-level configuration options → * (选中), Enable LED effects → * (选中) make # 编译完成后生成的firmware.bin在当前目录4.3 灯带硬件连接实操要点拿出你的WS2812B灯带找到三根线红线接5V电源正极不是树莓派5V黑线接5V电源负极并用另一根导线短接到树莓派Pin6GND绿线DI端接树莓派Pin19GPIO10BCM10关键细节灯带DI端必须接GPIO10SPI MOSI其他GPIO如GPIO18虽支持PWM但Klipper的WS2812驱动强制绑定SPI硬件通道共地线必须用≥22AWG导线长度10cm避免地环路噪声首颗灯珠前加100Ω电阻防信号反射末颗灯珠后接1kΩ下拉电阻防悬空干扰——这两处被90%教程忽略却是夜间打印时随机闪屏的元凶。4.4 printer.cfg完整配置与验证在~/klipper/config/printer.cfg末尾追加# 定义灯带设备必须放在[led_effect]之前 [neopixel led_strip] pin: PA7 # STM32F103的PA7引脚对应Klipper默认WS2812引脚 chain_count: 30 initial_red: 0 initial_green: 0 initial_blue: 0 # LED效果配置 [led_effect] leds: neopixel:led_strip cycle_time: 0.010 hardware_pwm: True effect: gradient palette: 0,0,255 64,0,191 128,0,127 192,0,63 255,0,0 input_min: 0 input_max: 100 input_variable: heater_bed.temperature invert_input: False保存后重启Klipper服务sudo systemctl restart klipper # 查看日志确认无报错 sudo journalctl -u klipper -n 50 -f | grep -i led\|neopixel # 正常应看到Configuring neopixel on pin PA7 和 led_effect: enabled4.5 效果调试与参数微调实战刚配置好灯带可能全红或全蓝别慌——这是input_variable未读取到有效值。用Klipper Console发送STATUS查看返回中是否有heater_bed: temperature: xx.x。若显示temperature: nan说明温度传感器未识别需检查[heater_bed]段落配置。常用调试命令SET_LED_EFFECT LEDled_strip VALUE0.5手动设置灯带亮度50%测试基础通信SET_LED_EFFECT LEDled_strip VALUE0.0全灭排除电源问题SET_LED_EFFECT LEDled_strip VALUE1.0全亮验证最大电流实操心得调色时先用effect: solid固定一种颜色确认硬件无误后再切gradientpalette颜色阶数建议5-10阶太少过渡生硬太多MCU计算压力大若发现颜色偏移如该蓝却显紫是initial_*值未清零删掉[neopixel]段落里的initial_*行即可某次我遇到灯带前10颗正常、后20颗全绿查了3小时最后发现是共地线接触不良——万用表量树莓派GND与电源GND间电阻大于0.5Ω就必须重焊。5. 进阶应用与自定义开发把LED变成你的第二块显示屏当基础效果跑通真正的乐趣才开始。Klipper LED Effect的价值不在预设动画而在它开放的变量接口和实时计算能力。我基于此做了三个实用扩展全部开源在个人仓库这里只讲核心思路。5.1 打印进度可视化用灯带长度映射G-code行号传统OctoPrint进度条是数字而灯带可以是物理刻度。原理很简单Klipper提供gcode_position变量记录当前执行的G-code行号G-code总行数可通过cat /tmp/printer_*.gcode | wc -l获取。但难点在于——如何让MCU知道总行数答案是用Klipper宏注入在printer.cfg中添加[gcode_macro PRINT_START] gcode: SET_GCODE_VARIABLE MACROLED_PROGRESS VARtotal_lines VAL{params.TOTAL_LINES} # 启动时设置总行数变量 [gcode_macro LED_PROGRESS] variable_total_lines: 0 gcode: {% set pos printer.gcode_move.gcode_position[2] %} {% set ratio (pos / params.TOTAL_LINES) | float %} SET_LED_EFFECT LEDled_strip VALUE{ratio}然后在切片软件后处理脚本中自动在G-code开头插入PRINT_START TOTAL_LINES12345。这样灯带前X%亮起就直观显示“还剩多少距离完工”。实测比看屏幕数字快0.8秒做出判断——对连续打印10小时的用户这0.8秒每天省下近10分钟。5.2 故障预警系统用颜色编码替代蜂鸣器Klipper有完善的错误检测但蜂鸣器太吵。我用灯带实现静音预警正常呼吸蓝effect: breathe,color: 0,100,255温度超限急促红闪effect: pulse,color: 255,0,0,speed: 2Z轴失步黄绿交替effect: alternate,colors: 255,255,0 0,255,0关键在[gcode_macro]里监听状态[gcode_macro CHECK_ERRORS] gcode: {% if printer.heater_bed.temperature 110 %} SET_LED_EFFECT LEDled_strip EFFECTpulse COLOR255,0,0 SPEED2 {% elif printer.stepper_z.phase_error 5 %} SET_LED_EFFECT LEDled_strip EFFECTalternate COLORS255,255,0 0,255,0 SPEED1 {% else %} SET_LED_EFFECT LEDled_strip EFFECTbreathe COLOR0,100,255 SPEED0.5 {% endif %}配合[timer]每5秒触发一次就实现了全自动状态监控。某次半夜打印我就是被灯带突然的红闪惊醒冲过去发现热床温控失效保住了价值两千的打印件。5.3 自定义动画协议用G-code指令直接控制单颗灯珠Klipper原生不支持单灯寻址但我们可以“曲线救国”。原理是利用[neopixel]的chain_count参数把灯带逻辑分割成多个虚拟设备。例如30灯带设chain_count: 3则neopixel:led_strip_0控制前10颗_1控制中间10颗_2控制后10颗。再结合SET_LED_EFFECT的VALUE参数0.0-1.0映射到色谱就能实现区域控制。我写了LED_ZONE宏[gcode_macro LED_ZONE] gcode: {% set zone params.ZONE | int %} {% set color params.COLOR | string %} SET_LED_EFFECT LEDneopixel:led_strip_{{zone}} VALUE{color}发送LED_ZONE ZONE1 COLOR255,0,0中间10颗立刻变红。这为后续做“Z轴高度标尺”、“挤出量计量条”打下基础——灯带不再是装饰而是嵌入机器本体的物理UI。常见问题速查表现象可能原因排查命令灯带完全不亮电源未共地、MCU固件未启用LED、[neopixel]引脚配置错误sudo dmesg颜色错乱如红变黄palette值超255、initial_*残留、信号线接触不良SET_LED_EFFECT LEDled_strip VALUE1.0测试纯色效果卡顿/跳变cycle_time设太小、树莓派CPU过载、电源功率不足top看CPUvcgencmd measure_temp看温度变量读数为nan对应传感器未配置或故障、input_variable拼写错误STATUS命令查看变量实际值重启Klipper后灯效消失printer.cfg未加载、[led_effect]段落位置错误必须在[neopixel]之后sudo journalctl -u klipper6. 经验总结为什么坚持用开源方案而不是买现成灯效盒子最后说点掏心窝的话。去年我试过某品牌“智能灯效盒子”宣称“免配置APP一键同步打印状态”花了299元。结果呢APP连不上Wi-Fi固件升级失败变砖客服说“请寄回检测”运费比盒子贵。而Klipper LED Effect我投入的时间成本是第一天2小时看文档第二天1小时调试第三天0.5小时写了个进度宏。之后两年它没出过一次故障每次Klipper升级我只要git pull再make新特性自动支持。开源的价值不在“免费”而在“可审计、可修改、可传承”。当你的灯带在Z轴归零时泛起一道银光那不是厂商预设的动画而是你亲手写的if printer.stepper_z.position 0: set_color(192,192,192)在运行当热床温度逼近设定值灯光从蓝渐变为橙那不是算法黑箱而是你配置的input_min/max线性映射在生效。这种掌控感是任何商业盒子给不了的。我见过太多人把3D打印当成“买设备-装软件-点开始”的消费行为却忘了它本质是“理解物理-编写逻辑-调试系统”的工程实践。Klipper LED Effect只是个入口推开这扇门你会看到原来一行配置能改变机器的呼吸节奏一段宏能赋予它预警的直觉一次编译能让它学会新的表达方式。它不教你成为专家但它给你一张通往专家的船票——而船票本身就印在那行[led_effect]的配置里。
企业数字化 ERP 产品动态
相关推荐
生成式AI四层技术栈与松散耦合架构实践 简介:Gartner 2024年11月发布的《生成式人工智能技术创新指南》(G00793932)面向企业技术领导者、架构师与AI规划人员,用于厘清GenAI在各技术栈层级与垂直行业的渗透路径,并借助配套的新兴市场象限考察供应商格局。整包… · 2026/11/2 22:54:13
端侧Agent工程化:从架构分层到工具调用的底层逻辑 开头先聊个背景。最近一年端侧 Agent 的讨论热度起来了,单纯跑通一个 demo 已经没什么人炫耀,大家真正关心的是把 Agent 从“实验室里的玩具”变成“能在终端设备上稳定跑半年”的工程系统。我在这一块折腾了几个月,踩了不少坑,也… · 2026/11/2 22:53:12
用GraphRAG+Neo4j构建AI Agent:多跳推理与并发优化实战 1. 为什么我最终选择GraphRAG Neo4j来搭AI Agent最近在做一个AI Agent的练手项目,目标很明确:让Agent能基于一堆内部文档回答问题,而且不是那种"关键词命中就拼一段话出来"的问答,是能跨文档、跨实体做推理的问答。一开… · 2026/11/2 22:53:12
219行代码手写Playwright元素拾取器:自动生成稳定选择器 1. 从零造一个元素拾取器:为什么我要自己动手浏览器里点一下页面元素,就能自动生成稳定的选择器,这个需求做自动化测试和爬虫的人应该都不陌生。Chrome DevTools 自带的那个拾取工具,用过的人都知道,生成的选择器经常是… · 2026/11/3 5:33:04
219行JS元素拾取器:快速生成Playwright定位信息 1. 一个 219 行脚本的由来先说清楚这东西是什么。它是一个浏览器端的元素拾取器,总共 219 行 JavaScript,跑在 Chrome DevTools 的 Console 里或者通过 Playwright 的page.evaluate()注入,作用就一件事:你在页面上移动鼠标&#x… · 2026/11/3 5:33:04
RISC-V与AI融合:编译器优化技术解析 1. 项目概述:RISC-V与AI融合的技术革命在2025年全球C及系统软件技术大会上,一个名为"从开放指令集到开源算子和编译器:RISC-VAI的全栈软件生态突破路径"的主题演讲引起了广泛关注。这个主题直指当前计算架构领域最前沿的两个技术方… · 2026/11/3 5:33:04
219行纯JS元素拾取器:自动化测试选择器生成实战 1. 一个 219 行的“小轮子”,为什么值得单独写一篇先说清楚这篇要聊的东西是什么。元素拾取器(element-picker)本质上是一个跑在浏览器里的交互脚本:你打开一个页面,点一下某个按钮,它就能把你点中的那个 D… · 2026/11/3 5:33:04
单向链表核心操作与高级应用全解析 1. 单向链表基础概念与核心特性单向链表作为最基础的链式数据结构,由一系列节点组成,每个节点包含两个部分:数据域和指针域。数据域存储实际数据,指针域存储下一个节点的内存地址。这种结构决定了单向链表在内存中不必连续存储&am… · 2026/11/3 5:33:04
WorkBuddy技能体系全解析:15个必装技能与配置指南 1. 为什么这份技能清单值得你花三分钟看完先说结论:WorkBuddy是那种典型的“下崽容易、吃透难”的效率工具。我见过不少人装了它,然后因为不知道怎么把技能串起来用,最后当普通聊天窗口使。这其实挺亏的。所谓Skills,在WorkBuddy里… · 2026/11/3 5:32:04
双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/2 13:10:28
STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/2 4:42:45
DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/2 5:17:18
STM32F411RE与MAX14431实现SPI隔离:工业现场抗共模干扰实战 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/3 1:01:59
ESP32+大模型AI硬件开发:从Demo到产品的8个工程问题 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/3 1:01:59
基于MAX14431BAEE+与PIC18F85J10的SPI隔离方案设计与实践 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/3 1:01:59
双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/2 13:10:28
STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/2 4:42:45
DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/2 5:17:18