必须用而非+flex,因其提供语义化导航结构,支持屏幕阅读器读出层级顺序,first:hidden和last:font-medium等伪类才生效;分隔符须为独立并加sm:hidden flex-shrink-0,避免焦点错乱与折行。

面包屑必须用 <ol></ol>,别用 <div> + <code>flex
语义错误会导致屏幕阅读器读不出层级、Tab 焦点顺序错乱、first:hidden 和 last:font-medium 失效。很多项目后期加无障碍支持时才发现,当初用 <div class="flex"> 套一堆 <code>aria-label 根本没用——浏览器不认模拟的列表结构。
正确做法是直接用 <ol></ol>,再配 list-none 抹掉默认序号:
<nav aria-label="面包屑导航"><ol class="flex flex-wrap items-center gap-1 sm:gap-2 list-none">
<li><a href="/">首页</a></li>
<li class="sm:hidden text-gray-400 flex-shrink-0">/</li>
<li><a href="/docs">文档</a></li>
<li class="sm:hidden text-gray-400 flex-shrink-0">/</li>
<li class="font-medium text-gray-900">配置指南</li>
</ol></nav>
- 每个分隔符(如
/)必须是独立<li>,和链接同级,不能塞进<a></a>里 -
flex-shrink-0必须加在分隔符上,否则窄屏下会被挤到下一行 -
sm:hidden控制桌面显示、移动端隐藏;若想小屏显示→,可用max-sm:before:content-['→'] max-sm:pl-1
中文路径截断要防折行,不能只靠 truncate
中文无空格,truncate 在 flex 容器里经常失效,尤其父容器没设宽度限制时。结果就是文字撑开整行、移动端溢出不可见,或者突然换行打乱结构。
- 中间项(非首尾)统一加
max-w-[120px] truncate inline-block,强制单行+省略 - 首项(如“首页”)和末项(当前页)不加
truncate,避免关键信息被截断 - 整个
<ol></ol>必须带flex-wrap,否则窄屏下内容会被强行压缩或溢出 - 绝对不要给整行加
whitespace-nowrap——这是移动端布局崩溃的常见源头
动态路由场景下,matched 属性比 currentRoute 更可靠
Vue Router 的 route.matched 返回完整匹配记录数组,按嵌套深度升序排列,每项含 meta.title 和 params。相比只取 route.name 或 route.path 拼接,它天然支持嵌套路由、重定向、命名视图等复杂情况。
- 遍历
route.matched时,跳过meta.hiddenInBreadcrumb: true的项(比如登录页) - 动态参数需从
route.params中提取并替换进meta.title模板,例如meta.title = "订单详情 #${id}" - 避免直接用
route.fullPath解析路径字符串——正则易错、不兼容命名路由、无法处理 alias
AdminLTE 类 CMS 后台要注意菜单高亮与面包屑同步
AdminLTE 默认用 data-widget="tree" 驱动侧边栏高亮,但面包屑常被写成静态 HTML,导致点击菜单后路径显示滞后或错位。
- 面包屑逻辑应与侧边栏共用同一份导航配置(如 JSON 文件或 Vuex store),而非各自维护一套
- 当路由变化时,优先通过
router.beforeEach更新面包屑状态,而不是依赖mounted一次性计算 - 注意 AdminLTE 的
.sidebar-menu内部有多个.treeview嵌套,高亮类名(active)可能需递归向上设置,否则面包屑层级和菜单展开状态不一致
/ 被写进 <a></a> 标签内部,整个语义链就断了——焦点管理、缩放行为、响应式折叠都会不可预测。











