1. 三级导航到底在解决什么问题做前端或者客户端开发的人几乎都绕不开导航菜单这个活儿。一级导航横在顶部二级导航从侧边滑出三级导航再往下钻一层——听起来简单但真动手写的时候你会发现坑比想象中多得多。我见过不少项目一级菜单写得挺利索加到第二级就开始出现定位偏移等到第三级的时候整个交互逻辑直接崩掉鼠标稍微抖一下菜单就没了或者点开三级菜单后二级菜单反而收起来了用户一脸懵。所谓三级导航说白了就是菜单的层级结构有三层第一层是主分类第二层是子分类第三层是具体条目。它要解决的问题很明确——当信息架构超过两层时如何让用户在不迷路的前提下快速定位到目标内容。典型场景包括电商后台的品类管理、文档站点的目录树、企业级应用的功能菜单、以及各类管理系统的侧边栏。适合阅读这篇内容的人包括刚入门前端不久、需要独立完成导航组件的新手也包括做过一二级菜单但三级交互总是调不顺、想搞清楚背后逻辑的中级开发者。我会从结构设计、交互逻辑、定位计算、状态管理几个角度把这件事拆透代码以原生HTML/CSS/JavaScript为主不依赖任何框架方便你迁移到Vue、React或者小程序里。先说说为什么三级导航值得单独拿出来讲。一级导航是“平铺”的所有选项地位平等用户扫一眼就能选二级导航是“展开”的鼠标悬停或点击后出现一个面板逻辑上还是线性的到了三级问题就变成了“嵌套展开”——第三级菜单的触发区域在第二级菜单项内部而第二级菜单本身又是从第一级展开出来的。这就带来了三个核心难点定位基准的切换、展开状态的互斥与保持、鼠标移动路径的容错。这三个点没处理好菜单就会出现“闪退”“错位”“点不到”的经典毛病。我个人的经验是很多人一上来就写CSS的:hover嵌套觉得三层ul li ul li ul套下去就完事了。这种做法在纯展示场景下勉强能用但一旦涉及点击展开、键盘操作、移动端适配立刻捉襟见肘。所以我的建议是结构用嵌套列表打底交互用JavaScript接管CSS只负责视觉呈现和过渡动画。这样职责清晰后续加功能也不至于推倒重来。2. 结构设计与方案选型背后的考量2.1 为什么用嵌套列表而不是扁平数组渲染导航的HTML结构最自然的表达就是嵌套的无序列表。一级ul下面挂若干li每个带子菜单的li内部再嵌一个ul以此类推到第三级。这种结构的优势在于语义清晰、屏幕阅读器能正确识别层级关系、SEO友好而且不需要额外的容器包裹就能表达父子关系。有人喜欢用扁平数组加>nav classnav-root ul classnav-level-1 li classnav-item has-children a href# classnav-link产品中心/a ul classnav-level-2 li classnav-item has-children a href# classnav-link软件产品/a ul classnav-level-3 li classnav-itema href# classnav-link项目管理工具/a/li li classnav-itema href# classnav-link协作平台/a/li /ul /li li classnav-itema href# classnav-link硬件产品/a/li /ul /li li classnav-itema href# classnav-link关于我们/a/li /ul /nav注意我给带子菜单的li加了has-children类这是后面JavaScript判断“这个节点要不要绑定展开逻辑”的钩子。没有子菜单的项就是纯链接不参与展开收起。这个区分很重要否则你会给所有菜单项都绑上事件白白增加复杂度。2.2 定位方案绝对定位加相对定位的父子配合三级导航最容易出问题的地方就是定位。第二级菜单要相对第一级菜单项定位第三级菜单要相对第二级菜单项定位。实现方式很直接给每一级的li设置position: relative给子菜单ul设置position: absolute然后用top和left把它挪到父项旁边。这里有个细节值得说清楚。第二级菜单通常出现在一级菜单项的正下方水平导航或者右侧垂直导航第三级菜单则出现在第二级菜单项的右侧。以垂直侧边栏导航为例第二级菜单的定位是left: 100%; top: 0;意思是贴着父项的右边缘、顶部对齐。第三级同理相对第二级菜单项再往右挪一个身位。.nav-item { position: relative; } .nav-level-2, .nav-level-3 { position: absolute; top: 0; left: 100%; min-width: 160px; display: none; } .nav-item.is-open .nav-level-2, .nav-item.is-open .nav-level-3 { display: block; }为什么用left: 100%而不是写死像素值因为菜单项的宽度可能随内容变化写死像素在字体缩放或者多语言环境下必然错位。100%是相对父元素宽度计算的父项多宽它就偏移多远天然自适应。这是我在实际项目里踩过坑之后固定下来的写法——早期用固定left: 180px结果中文菜单和英文菜单宽度不一样英文短了菜单叠在一起长了又留出大空隙非常难看。2.3 交互触发方式的选择hover还是click这是三级导航设计里争议最大的一个点。桌面端传统做法是hover展开鼠标移上去菜单出现移走消失。但三级嵌套下hover的“移动路径容错”问题会被放大用户从一级移到二级再斜着移向三级鼠标中途可能短暂离开菜单区域导致二级菜单瞬间收起三级自然也就没了。我的方案是桌面端用hover加延迟收起移动端用click切换。延迟收起的意思是鼠标离开菜单后不立即隐藏而是等个150到200毫秒如果这段时间内鼠标又回到了菜单区域包括子菜单就取消隐藏。这个延迟窗口给了用户“斜着移动鼠标”的容错空间。实现上用一个定时器变量控制let closeTimer null; function scheduleClose(item) { clearTimeout(closeTimer); closeTimer setTimeout(() { item.classList.remove(is-open); }, 180); } function cancelClose() { clearTimeout(closeTimer); }180毫秒这个值不是拍脑袋定的。太短了容错不够用户手一抖菜单就没了太长了菜单收得拖泥带水感觉迟钝。我实测下来150到200毫秒是比较舒服的区间具体可以按团队习惯微调。另外要注意取消收起的逻辑不仅要绑在子菜单上还要绑在父项本身因为鼠标可能在父子之间来回移动。3. 核心交互逻辑的逐层拆解3.1 展开状态的互斥与保持规则三级导航的状态管理核心就两条规则同级互斥跨级保持。同级互斥是指同一个父菜单下的多个子菜单同一时间只能展开一个——你点开“软件产品”再点“硬件产品”“软件产品”的第三级菜单要收起来。跨级保持是指展开第三级菜单时它的父级第二级和祖父级第一级必须保持展开状态不能因为操作了深层节点就把上层收掉。这两条规则听起来理所当然但实现时很容易顾此失彼。我见过一种写法是每次点击都先把所有is-open清掉再给当前项加上结果就是点第三级的时候把第一级也清了菜单整个消失。正确的做法是收起操作只作用于“当前节点的兄弟节点及其后代”不碰祖先链。function toggleItem(item) { const parentList item.parentElement; // 同级互斥收起兄弟节点 Array.from(parentList.children).forEach(sibling { if (sibling ! item) { sibling.classList.remove(is-open); } }); // 切换自身 item.classList.toggle(is-open); }这段代码的关键在于parentList.children只遍历直接兄弟不会向上波及。祖先节点的is-open类原封不动跨级保持自然就成立了。如果你用框架开发思路一样只是把DOM操作换成状态更新——维护一个“当前展开路径”的数组点击时替换路径中对应层级的那一项其余层级保持不变。3.2 鼠标移动路径的容错处理前面提到hover延迟收起这里展开说说完整的容错逻辑。用户的操作路径通常是鼠标进入一级项 → 二级展开 → 鼠标移向二级项 → 三级展开 → 鼠标移向三级项。问题出在“移向”的过程中鼠标可能经过两个菜单之间的空隙或者因为手速快短暂划出菜单边界。处理办法是给每一级菜单容器包括li和它内部的ul都绑定mouseenter和mouseleave。mouseenter时取消收起定时器mouseleave时启动定时器。由于ul是li的子元素鼠标从li移到ul上时li的mouseleave会触发但紧接着ul的mouseenter会触发并取消定时器所以只要延迟窗口足够菜单就不会闪退。document.querySelectorAll(.has-children).forEach(item { item.addEventListener(mouseenter, () { cancelClose(); item.classList.add(is-open); }); item.addEventListener(mouseleave, () { scheduleClose(item); }); });这里有个容易忽略的点mouseleave和mouseout的区别。mouseout在鼠标移到子元素上时也会触发因为子元素“冒泡”了移出事件会导致误收起mouseleave只在真正离开元素边界时触发不冒泡正是我们要的。这个细节很多新手会搞混用错了事件菜单怎么调都不对劲。3.3 点击展开与链接跳转的冲突化解带子菜单的菜单项它的文字本身往往也是个链接。用户点它是想展开子菜单还是想跳转到分类页这个歧义必须处理掉否则体验很割裂。我的做法是带子菜单的项点击只负责展开收起不跳转跳转入口放在子菜单的第一项标注为“全部XX”或“查看全部”。这样职责单一用户也不会误操作。如果产品需求坚持要“点击文字跳转、点击箭头展开”那就得在菜单项内部拆出两个可点击区域箭头单独绑事件并阻止冒泡。这种做法能用但点击热区变小了移动端手指点起来费劲我个人不太推荐。真要做的话箭头区域至少给到44×44像素的触控尺寸这是移动端可点击元素的下限经验值。item.querySelector(.nav-link).addEventListener(click, (e) { if (item.classList.contains(has-children)) { e.preventDefault(); // 阻止链接默认跳转 toggleItem(item); } });preventDefault在这里是必须的不然点击会触发href跳转页面刷新后菜单状态全丢。另外记得在移动端把hover逻辑关掉统一走click否则触摸设备上会出现“第一次点击触发hover、第二次才触发click”的诡异行为。4. 完整实操流程与关键代码落地4.1 从零搭建的步骤拆解我把整个实现拆成五步按顺序做下来基本不会乱。第一步写HTML骨架。按前面的嵌套列表结构把三级层级搭出来给带子菜单的li加has-children类。这一步不用管样式先把结构写对。第二步写基础CSS。设置菜单的宽高、字体、背景色给li加position: relative给子菜单ul加position: absolute和初始的display: none。同时把is-open状态下的display: block写好。第三步写展开收起的JavaScript。绑定mouseenter/mouseleave桌面端和click移动端实现toggleItem和延迟收起逻辑。这一步做完菜单应该已经能正常展开了。第四步加过渡动画。用opacity和transform: translateX做淡入滑入效果配合transition让展开收起不那么生硬。注意display: none和display: block之间是没法直接过渡的要么用visibility配合opacity要么用requestAnimationFrame在切换display后加类。我倾向后者控制更精细。第五步做响应式适配。用媒体查询在窄屏下把hover逻辑禁用菜单改为点击展开同时调整子菜单的定位方式——窄屏下三级菜单可能不适合再往右展开改成在父项下方内联展开更稳妥。4.2 过渡动画的实现细节与参数选择动画这块值得单独说因为做不好会显得很廉价。我的方案是子菜单展开时从opacity: 0过渡到1同时transform从translateX(-8px)过渡到0时长200毫秒缓动函数用ease-out。为什么是-8px而不是-20px位移太大显得浮夸太小又看不出动效8px是个克制的值配合透明度变化刚好有“轻轻滑出”的感觉。.nav-level-2, .nav-level-3 { opacity: 0; transform: translateX(-8px); transition: opacity 0.2s ease-out, transform 0.2s ease-out; pointer-events: none; } .nav-item.is-open .nav-level-2, .nav-item.is-open .nav-level-3 { opacity: 1; transform: translateX(0); pointer-events: auto; }注意pointer-events的配合。菜单隐藏时设none防止透明状态下误触展开时设auto恢复交互。这个属性在动画场景里很实用比单纯靠display切换要平滑得多。另外transition只写在基础状态上is-open状态不重复声明这样展开和收起都有动画不会出现“展开有动画、收起瞬间消失”的不对称。4.3 键盘可访问性的补全导航组件如果只考虑鼠标键盘用户就没法用了。补全键盘操作其实不复杂核心是Tab键聚焦、Enter/空格展开、Esc收起、方向键在菜单项间移动。最低限度要把前三个做了。item.addEventListener(keydown, (e) { if (e.key Enter || e.key ) { e.preventDefault(); toggleItem(item); } if (e.key Escape) { item.classList.remove(is-open); item.querySelector(.nav-link).focus(); } });Escape收起后要把焦点还给触发它的菜单项不然焦点会丢到页面某个角落键盘用户直接迷失。这个细节在无障碍规范里是明确要求的做的时候顺手加上成本很低但体验提升明显。另外给菜单项加tabindex0确保可聚焦给子菜单容器加rolemenu、菜单项加rolemenuitem屏幕阅读器就能正确播报层级关系。5. 常见问题排查与避坑经验实录5.1 菜单闪退与错位的排查思路菜单闪退是三级导航最高频的问题表现是鼠标还没移到子菜单上菜单就消失了。排查顺序我总结成三步先看事件绑定用的是mouseleave还是mouseout用错了必闪退再看延迟收起的定时器有没有被子菜单的mouseenter正确取消最后检查子菜单和父项之间有没有物理空隙有的话鼠标经过空隙会触发父项的mouseleave。错位问题通常是定位基准搞错了。检查子菜单的position: absolute的参照物是不是最近的position: relative祖先。如果某一级li忘了设relative子菜单会一路往上找参照物跑到意想不到的位置。用浏览器开发者工具选中子菜单看它的偏移是相对谁计算的一目了然。问题现象可能原因排查动作解决方式菜单闪退用了mouseout而非mouseleave检查事件绑定代码换成mouseleave菜单闪退延迟定时器未取消在子菜单mouseenter打断点补上cancelClose调用菜单错位父级li缺position: relative检查计算样式的定位参照给每级li补relative三级菜单被裁切祖先容器有overflow: hidden逐层检查overflow属性移除或改用overflow: visible移动端点不开hover逻辑未禁用检查媒体查询条件窄屏下只绑click5.2 容器裁切与层级遮挡的处理三级菜单往右展开很容易被父容器的overflow: hidden裁掉或者被其他元素盖住。裁切问题只能靠调整祖先容器的overflow解决没有别的捷径——position: absolute的元素超出overflow: hidden的祖先边界就是会被切。如果布局上必须保留overflow: hidden比如做圆角裁切那就得把菜单用position: fixed挂到body下用JavaScript动态计算位置代价是滚动时要同步更新坐标复杂度上一个台阶。我的建议是能改overflow就改别为了一个圆角把整个定位逻辑搞复杂。层级遮挡用z-index解决但要注意z-index只在同一层叠上下文内比较。如果父级创建了新的层叠上下文比如设了transform或opacity小于1子菜单的z-index再大也跳不出父级的层级。排查时看子菜单是不是被某个兄弟容器盖住如果是要么调整父级顺序要么把菜单提升到更高层级的容器里。5.3 我踩过的几个真实坑第一个坑是过渡动画和display切换打架。早期我直接在is-open里写display: block结果动画完全不生效因为display从none到block是瞬间切换没有过渡可言。后来改成用visibility配合opacity或者用requestAnimationFrame延迟加类才把动画跑起来。第二个坑是移动端300毫秒点击延迟。老式移动浏览器为了判断双击缩放点击事件会延迟300毫秒触发菜单响应明显迟钝。解决办法是在meta里声明viewport的user-scalableno视产品需求而定或者用touchstart替代click。现在现代浏览器基本默认消除了这个延迟但如果你要兼容老设备还是得留意。第三个坑是动态渲染的菜单事件丢失。如果菜单项是异步请求后渲染的初始化时绑事件的那批元素里根本没有它们自然点不动。解决办法是用事件委托把监听绑在根容器上通过e.target.closest(.has-children)找到实际触发的菜单项。这样后续动态插入的节点也能正常工作不用每次渲染完重新绑一遍。提示事件委托不仅能解决动态节点的问题还能减少事件监听器的数量。三级菜单如果每项都单独绑监听器数量是节点数的两三倍用委托只绑一个内存和性能都更友好。6. 从能用走向好用几个提升体验的细节菜单能展开收起只是及格线真正拉开差距的是那些不起眼的细节。比如展开方向的智能判断三级菜单默认往右展开但如果菜单本身已经靠近屏幕右边缘往右展开就出屏了。这时候应该自动翻转到左侧展开。实现上在展开前用getBoundingClientRect算一下子菜单的右边界超过视口宽度就加一个flip-left类CSS里把left: 100%改成right: 100%。再比如当前选中项的视觉标记。用户点进某个三级条目后从一级到三级的整条路径都应该有高亮状态让用户知道自己处在信息架构的哪个位置。这个用一条“激活路径”的数据就能驱动渲染时给路径上的每个节点加is-active类样式上做左侧色条或者文字加粗。还有菜单展开时的滚动处理。如果某一级菜单项特别多超出视口高度得让子菜单内部可滚动同时滚动不能带动页面整体滚动。给子菜单设max-height和overflow-y: auto配合overscroll-behavior: contain阻止滚动链传递到页面。这个属性在移动端尤其重要不然用户滑菜单滑到底页面跟着一起滚体验很糟。最后分享一个我在多个项目里验证过的小技巧给菜单的展开收起加一个极短的“防抖”。快速划过菜单时如果每次mouseenter都立即展开菜单会疯狂闪烁。加个50毫秒的展开延迟鼠标停留超过50毫秒才真正展开快速划过就不会触发。这个延迟和收起延迟配合一进一出都有缓冲菜单的“呼吸感”会自然很多。数值不用太精确50到80毫秒之间都行关键是让快速扫过的鼠标不产生视觉噪音。这套三级导航的实现代码量不大但把定位、状态、容错、动画、可访问性这几块都照顾到之后你会发现它其实是个微缩的交互系统。后续如果要扩展成多级四级以上核心逻辑不用改把嵌套结构和递归遍历的思路套上去就行——不过说实话超过三级的导航在信息架构上就该反思了用户很难在四层菜单里快速找到东西与其把菜单做深不如把分类做扁平。
企业数字化 ERP 产品动态
相关推荐
2026武汉工业展:智能制造与数字化转型新趋势 1. 武汉工业展的时代背景与战略意义2026武汉工业展作为中国制造业转型升级的重要展示窗口,其举办恰逢"十四五"规划收官与"十五五"规划启动的关键节点。从全球视野来看,这将是后疫情时代全球供应链重构背景下,中国向世界展… · 2026/11/4 12:57:57
AI Agent开发实战:技术架构与优化策略 1. AI Agent生态爆发背后的技术驱动力 最近两年AI Agent技术呈现爆发式增长,这背后是三个关键要素的成熟:首先是算力成本的持续下降,使得复杂模型的训练和推理变得经济可行;其次是模型即服务(MaaS)模式的普及,降低了AI… · 2026/11/3 17:25:39
ERP-MES文件型接口设计:制造指图抽出与实绩计上落地要点 简介:《MES 接口设计说明书》面向SAP R/3与制造执行系统(MES)集成项目的开发、实施及接口设计人员,围绕MELEBUS-BMAS场景中的系统间接口集成提供设计参考。文档重点说明制造指图数据抽取、实绩数据计上、R/3与MES间排他控制等接口… · 2026/11/3 17:27:03
PostgreSQL数据类型选择指南与性能优化实践 1. PostgreSQL数据类型概述PostgreSQL作为一款功能强大的开源关系型数据库,其数据类型系统之丰富远超大多数同类产品。我在实际项目中最常遇到的问题是:面对十几种数字类型、多种字符串类型以及各种特殊类型时,开发团队往往凭直觉选择&#x… · 2026/11/5 11:36:58
Mantle系统架构:云存储局部性与扩展性的突破 1. 项目概述:Mantle系统架构演进的技术突围在云存储技术高度成熟的今天,百度沧海存储团队完成的Mantle系统设计堪称一次教科书级别的技术突围。这个被SOSP25收录的存储系统创新案例,完美诠释了如何在看似饱和的技术领域实现突破——不是通过堆… · 2026/11/5 11:34:56
SEATA AT模式原理与实践:分布式事务解决方案 1. SEATA AT模式核心原理剖析 分布式事务一直是微服务架构中的难点问题,SEATA作为开源的分布式事务解决方案,其AT模式(Auto Transaction)通过二阶段提交实现了对业务代码的低侵入性支持。这种模式最大的特点是完全不需要改造业务S… · 2026/11/5 11:32:51
Simulink S-function执行时序与状态管理原理详解 简介:本资源是面向MATLAB/Simulink初学者与控制系统建模学习者的S-Function入门实践包,聚焦自定义系统函数开发这一核心能力,解决标准库无法覆盖的特定数学运算与动态逻辑建模需求。压缩包共4个文件(2个M文件实现核心算法逻辑&… · 2026/11/5 11:32:51
低空经济三维智能感知与实时调度技术解析 1. 低空经济三维空间智能感知与管控体系概述低空经济作为新兴经济形态,正在全球范围内快速发展。根据国际航空运输协会(IATA)预测,到2030年,全球城市空中交通市场规模将达到1.5万亿美元。在这一背景下,《低… · 2026/11/5 11:32:51
链表数据结构与PTA解题技巧详解 1. 链表基础与数据结构课程实践 链表作为线性表的链式存储结构,是数据结构课程中承前启后的关键内容。在山东理工大学(SDUT)的数据结构与算法课程体系中,链表章节通常安排在顺序表之后、树结构之前,这种教学顺序设计体… · 2026/11/5 11:32:51
双极步进电机控制实战: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/5 10:27:59
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/5 10:28:03
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/5 10:28:07
多智能体协作系统架构设计与实践优化 1. 多智能体协作系统的时代背景2017年Transformer架构的诞生彻底改变了人工智能的发展轨迹。当行业还沉浸在单一大模型带来的性能突破时,Anthropic的研究团队已经将目光投向了更前沿的领域——如何让多个智能体通过协作产生超越单体能力的系统智能。这就像交响乐团中… · 2026/11/5 0:00:39
大模型系统性入门实战指南:从原理到部署微调的全链路路线 提到大模型的系统性入门,我先说个结论:这事真不难,难的是资料太散、路线太多,今天这个框架明天那个工具,新人很容易在“收藏夹吃灰”和“教程看到一半放弃”之间反复横跳。市面上关于大模型的内容少说有几十万篇&#… · 2026/11/5 0:00:39
鸿蒙文件系统沙箱机制解析与开发实践 1. 鸿蒙文件系统沙箱机制深度解析作为一名从Android转向鸿蒙开发的工程师,我深刻体会到文件系统访问是迁移过程中最先遇到的"拦路虎"。在Android上运行良好的文件读写代码,在鸿蒙真机环境却频频出现文件写入后神秘消失或直接抛出权限拒绝错误的… · 2026/11/5 0:00:39
双极步进电机控制实战: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/5 10:27:59
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/5 10:28:03
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/5 10:28:07