最稳妥结构是用 包裹 +,每个菜单项为 文本,二级菜单嵌套 并配 aria-haspopup="true" 和 aria-expanded="false"。

左侧菜单用什么 HTML 结构最稳妥
直接用 常见错误是把菜单项写成一堆独立的 纯 CSS 实现折叠(比如用 后端渲染页面时,通常靠服务端模板(如 Thymeleaf、Jinja)在生成 HTML 时就写死 必须在菜单数据**渲染前**过滤。渲染后再用 JS 隐藏元素,不仅浪费 DOM 节点,还可能因权限逻辑缺陷暴露未授权入口(比如按钮隐藏但 API 仍可调用)。 菜单看着简单,但路由匹配精度、权限过滤时机、DOM 更新性能这三点,任何一个没踩准,上线后就会反复修 bug。尤其权限和路由联动的部分,很容易测试覆盖不到。 包裹 + 是最通用、语义正确且无障碍友好的选择。别用 @#@#@#@#@#@#@#@#@#@0,不推荐用 模拟跳转(会破坏原生链接语义和 SEO)
,并给父级 加 aria-haspopup="true" 和 aria-expanded="false"
怎么让菜单支持展开/收起又不卡顿
:hover 或 details/summary)看似简单,但实际项目里几乎不可用:移动端没 hover、无法记忆展开状态、没法配合路由高亮。必须用 JS 控制,但关键是要避免每次点击都重绘整个菜单树。
切换 aria-expanded 和对应子菜单的 display 或 max-height,别用 innerHTML 重写整块 DOMmax-height: 0 + overflow: hidden + transition: max-height 0.2s ease 实现平滑动画,比 height 更可靠(height: auto 无法过渡)菜单高亮怎么和当前路由真正对齐
class="active";但前后端分离项目里,JS 必须根据当前 URL 主动匹配并添加 class,否则点击跳转后菜单还是旧状态。
window.location.pathname 是否包含 /user ——这样 /user/edit 也会激活 /user/list,造成误亮pathname.startsWith('/user/list'),或者用正则 ^/user/list(/|$)
currentRoute.value.path 或 useLocation().pathname,比操作 window.location 更可靠#/user/list)下要解析 location.hash,不是 pathname
权限控制该在菜单渲染前还是渲染后做
permissionCode 字段(如 "user:read"),前端初始化时用当前用户权限列表做一次 filter
v-if 或 {% if %} 里写复杂权限表达式(如 user.role === 'admin' || hasPerm('order:export')),提取成独立函数复用,避免模板臃肿
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











