- 后端
【免费下载链接】flask-admin
Simple and extensible administrative interface framework for Flask
本文围绕 flask-admin 仓库内置的第三方组件
multi-level-dropdowns-bootstrap展开,讲解如何在不修改 Bootstrap 4 官方 CSS/JS 的前提下,让下拉菜单支持无限层级展开。文章既覆盖组件的完整用法(基础引入、Hover 触发、事件处理),也结合 flask-admin 源码说明其在后台导航菜单中的真实集成方式与工作原理,读完即可在自己的 Bootstrap 4 项目中复刻同款多级菜单。
背景:Bootstrap 4 原生下拉为何做不到多级
Bootstrap 4 的官方下拉组件(dropdown.js)在设计上只支持“一层下拉”:
- 点击/悬停
[data-toggle="dropdown"]元素后,dropdown.js只会切换当前菜单的show类; - 当二级菜单里再嵌套一个
.dropdown时,官方组件既不会在点击时阻止事件冒泡,也不会在关闭时区分“子菜单是否仍处于打开状态”; - 结果就是:子菜单一点开,父菜单就被关闭,深层菜单永远无法稳定显示。
multi-level-dropdowns-bootstrap正是针对这一缺口设计的 hack:它复用 Bootstrap 官方的dropdown()方法、事件与数据结构,只通过一层轻量包装就能实现“无限级”下拉,且不引入任何额外 CSS 类来覆盖官方样式——正如其 README 所说“Using official HTML without adding extra CSS styles and classes, it's just like native support”。
该组件对应的核心文件位于仓库 flask_admin/static/vendor/multi-level-dropdowns-bootstrap/ 目录:
| 文件 | 作用 |
|---|---|
bootstrap4-dropdown-ml-hack.js | 核心多级下拉 hack(点击触发) |
bootstrap4-dropdown-ml-hack-hover.js | 带 Hover 触发版本(包含 hack + 鼠标悬停逻辑) |
bootstrap4-dropdown-ml-hack-hover.css | 配合 Hover 使用的间距修正样式 |
README.md | 官方使用说明(本文分析主体) |
基础用法:一行<script>开启多级下拉
根据 README 的 “Base” 部分,多级下拉的开启方式极其简单:在 jQuery 与 Bootstrap 的 JS 文件之后引入 hack 脚本即可:
<script src="https://raw.githubusercontent.com/dallaslu/bootstrap-4-multi-level-dropdown/master/bootstrap4-dropdown-ml-hack.js"></script>注意事项:
- 加载顺序必须为
jquery → bootstrap(含 dropdown.js)→ 本 hack 脚本,因为 hack 内部会读取并包装$.fn.dropdown,依赖官方组件已就绪; - 引入后无需修改任何 HTML 结构,官方文档 getbootstrap.com/docs/4.4/components/dropdowns 中列出的所有功能不受影响——CSS 类、JS 方法、事件与选项均可照常使用;
- 在 flask-admin 中,该脚本并非由用户手工引入,而是由后台基础模板统一加载(详见下文“flask-admin 的集成方式”)。
原理剖析:hack 到底改了什么
阅读仓库内的 bootstrap4-dropdown-ml-hack.js 源码,可以发现它的全部逻辑只有 26 行,核心是“包装官方方法 + 接管事件”:
$.fn.dropdown = (function() { var $bsDropdown = $.fn.dropdown; // 1. 保存官方 $.fn.dropdown return function(config) { if (typeof config === 'string' && config === 'toggle') { // 2. 拦截 toggle 调用 $('.has-child-dropdown-show').removeClass('has-child-dropdown-show'); $(this).closest('.dropdown').parents('.dropdown').addClass('has-child-dropdown-show'); } var ret = $bsDropdown.call($(this), config); // 3. 调用官方方法完成实际开关 $(this).off('click.bs.dropdown'); // 4. 关闭官方内置的 click 绑定,防止重复 toggle return ret; } })(); $(function() { $('.dropdown [data-toggle="dropdown"]').on('click', function(e) { $(this).dropdown('toggle'); e.stopPropagation(); // 5. 阻止冒泡,避免事件传给父级 dropdown }); $('.dropdown').on('hide.bs.dropdown', function(e) { if ($(this).is('.has-child-dropdown-show')) { // 6. 子菜单打开时,阻止父菜单被隐藏 $(this).removeClass('has-child-dropdown-show'); e.preventDefault(); } e.stopPropagation(); // 7. 多级模式下不再向上传播 hide 事件 }); });逐段拆解其工作机制:
- 包装
$.fn.dropdown:先把官方实现保存为$bsDropdown,再返回新函数。这样外部调用$(...).dropdown('toggle')时仍会执行官方逻辑,hack 只是在其前后追加行为。 - 标记祖先菜单:触发
toggle时,先清除全局的has-child-dropdown-show标记,再给当前.dropdown的所有祖先.dropdown加上该标记,用于记录“哪些父菜单下有正在打开的子菜单”。 - 移除官方 click 绑定:Bootstrap 官方
dropdown.js会给[data-toggle="dropdown"]绑定click.bs.dropdown,内部会再次调用toggle();hack 用$(this).off('click.bs.dropdown')将其关闭,避免一次点击触发两次开关导致菜单闪动。 - 点击事件自行接管:在 document ready 后统一为所有
.dropdown [data-toggle="dropdown"]绑定 click,显式调用dropdown('toggle')并e.stopPropagation()——这是多级菜单最关键的一步:点击子菜单按钮时事件不会冒泡到父级 dropdown 的处理器,父菜单因此不会被意外关闭。 - 拦截
hide.bs.dropdown:当某个 dropdown 触发隐藏事件时,如果它带有has-child-dropdown-show标记(说明它的子菜单还开着),就preventDefault()阻止其关闭,并移除标记;同时stopPropagation()阻止隐藏事件继续向上传播,保证深层菜单关闭时不会连坐关闭上层菜单。
这套设计保证了两个核心体验:点开深层子菜单时父级保持打开,关闭深层子菜单时父级不受影响。
Hover 触发:鼠标悬停即展开
README 的 “Hover” 部分提供了两种实现方式。
方式一:手动加样式 + 事件
先加一行 CSS,把菜单与触发元素之间的间距归零,避免鼠标从按钮移向菜单时触发mouseleave而被误关:
.dropdown-hover-all .dropdown-menu, .dropdown-hover > .dropdown-menu { margin:0 }再为指定容器绑定mouseenter/mouseleave事件,用dropdown('toggle')(README 建议使用'toggle'以获得最佳体验)控制开关:
$('.dropdown-hover').on('mouseenter',function() { if(!$(this).hasClass('show')){ $('>[data-toggle="dropdown"]', this).dropdown('toggle'); } }); $('.dropdown-hover').on('mouseleave',function() { if($(this).hasClass('show')){ $('>[data-toggle="dropdown"]', this).dropdown('toggle'); } }); $('.dropdown-hover-all').on('mouseenter', '.dropdown', function() { if(!$(this).hasClass('show')){ $('>[data-toggle="dropdown"]', this).dropdown('toggle'); } }); $('.dropdown-hover-all').on('mouseleave', '.dropdown', function() { if($(this).hasClass('show')){ $('>[data-toggle="dropdown"]', this).dropdown('toggle'); } });两类选择器的区别:
.dropdown-hover:针对单个dropdown 元素,只监听其自身的鼠标进出;.dropdown-hover-all:针对容器内所有.dropdown,利用事件委托(on('mouseenter', '.dropdown', ...))一次绑定、全局生效,适合整条导航栏整体启用 Hover。
方式二:直接引入配套文件
如果不想手写事件,仓库已经提供了现成的组合文件,README 给出的完整用法如下:
<link rel="stylesheet" href="https://raw.githubusercontent.com/dallaslu/bootstrap-4-multi-level-dropdown/master/bootstrap4-dropdown-ml-hack-hover.css" /> ... <div class="dropdown-hover-all"> <!-- .dropdown elements --> </div> <div class="dropdown dropdown-hover"> <!-- toggle and menu elements --> </div> ... <script src="https://raw.githubusercontent.com/dallaslu/bootstrap-4-multi-level-dropdown/master/bootstrap4-dropdown-ml-hack-hover.js"></script>对照仓库文件可以确认:
- bootstrap4-dropdown-ml-hack-hover.css 内容与 README 中的样式完全一致(
margin:0一条规则); - bootstrap4-dropdown-ml-hack-hover.js 前 26 行与
bootstrap4-dropdown-ml-hack.js完全相同,其后追加了.dropdown-hover/.dropdown-hover-all的鼠标事件绑定。
因此,若只需点击触发,引入bootstrap4-dropdown-ml-hack.js一个文件即可;需要 Hover 时则换成-hover后缀的 JS + CSS 组合。
flask-admin 的集成方式:后台导航栏的多级菜单
flask-admin 使用 Bootstrap 4 渲染整个管理后台界面,其左侧顶部导航由Admin.menu()生成的菜单树驱动。为了让分类(category)可以嵌套为多级下拉,仓库直接内置了该 hack 组件。
1. 模板层:统一加载 hack 脚本
所有 Bootstrap 4 后台页面共用的基础模板 flask_admin/templates/bootstrap4/admin/base.html 在tail_js块中按顺序加载脚本(见 base.html 第 81-98 行):
<script src="{{ admin_static.url(filename='vendor/jquery.min.js', v='3.5.1') }}" ...></script> <script src="{{ admin_static.url(filename='bootstrap/bootstrap4/js/bootstrap.min.js', v='4.6.2') }}" ...></script> <script src="{{ admin_static.url(filename='vendor/bootstrap4/util.js', v='4.3.1') }}" ...></script> <script src="{{ admin_static.url(filename='vendor/bootstrap4/dropdown.js', v='4.3.1') }}" ...></script> <script src="{{ admin_static.url(filename='vendor/multi-level-dropdowns-bootstrap/bootstrap4-dropdown-ml-hack.js') }}" ...></script>可以观察到 flask-admin 在加载顺序上的刻意安排:
jquery.min.js(v3.5.1)与bootstrap.min.js(v4.6.2)在前;- 之后是官方
util.js与dropdown.js(v4.3.1)——dropdown.js是 hack 包装的目标对象,必须先就位; - 最后才是
bootstrap4-dropdown-ml-hack.js,且未启用 Hover 版本,即后台菜单默认使用“点击展开”交互。
这正好印证了 README 中“Just add js after jquery and bootstrap js files”的要求:hack 脚本必须后于官方组件加载。
2. 菜单渲染:递归生成嵌套.dropdown
后台菜单由 flask_admin/templates/bootstrap4/admin/layout.html 中的menu()宏渲染(见 layout.html 第 21-83 行)。其关键逻辑是递归:
- 遇到
is_category()的菜单项时,渲染一个<li class="dropdown">,内部<a>{%- if child.is_category() -%} {{ menu(menu_root=[child]) }} {% else %} ...渲染普通菜单项... {%- endif -%}最终 DOM 形如:
<li class="dropdown"> <a class="dropdown-toggle nav-link">class SubMenuCategory(MenuCategory): def __init__(self, *args: str, **kwargs: t.Any) -> None: super().__init__(*args, **kwargs) self.class_name += " dropdown-submenu dropright"MenuDivider(menu.py 第 186-203 行):渲染为dropdown-divider分隔线,get_url()返回None,用于在菜单中插入视觉分隔。
在 layout.html 第 37-41 行 中,渲染宏还会依据
class_name是否包含dropdown-submenu来决定显示“右箭头”还是“下箭头”图标,说明SubMenuCategory的dropright类正是多级菜单“向右弹出”视觉语义的来源。与 Bootstrap 官方功能的兼容性
README 特别强调:官方文档中列出的所有dropdown 能力均不受 hack 影响,可以放心混用:
- CSS 类:
.dropdown-menu-right、.dropdown-header、.dropdown-divider、.disabled等官方类照常生效; - JS 方法:
toggle、show、hide、update等方法仍由官方实现执行,hack 只是在toggle前后附加了祖先标记逻辑; - 事件:
show.bs.dropdown、shown.bs.dropdown、hide.bs.dropdown、hidden.bs.dropdown等官方事件照常触发,hack 仅额外监听hide.bs.dropdown做保护性拦截; - 选项:如
offset、flip、boundary、reference、display等 Popper.js 相关选项不受影响。
其设计哲学是“只增加行为、不改写行为”——从 bootstrap4-dropdown-ml-hack.js 源码可以看到,hack 全程保留了对官方
$bsDropdown的调用,未替换官方实现,也未新增任何覆盖官方样式的 CSS。兼容性验证与使用建议
- README 给出的官方 Demo 基于Bootstrap v4.4.1验证可用;flask-admin 在 base.html 中同时加载了
bootstrap.min.js(v4.6.2)与dropdown.js(v4.3.1),从源码结构与官方 README 声明可以推断,该 hack 与 Bootstrap 4 系列版本均能协同工作; - 点击版 vs Hover 版:桌面端管理后台若追求效率可考虑 Hover 版(引入
-hover后缀的两个文件),但需注意 README 中“减少间距以避免误触发 mouseleave”的 CSS 提示;移动端触屏场景下点击版更稳妥; - 加载顺序:务必确保 hack 脚本在 jQuery 与官方 dropdown 组件之后加载,否则包装
$.fn.dropdown时会拿到未定义的实现; - 若在 flask-admin 之外的项目中独立使用,直接参照上文“基础用法”与“Hover 触发”两节引入仓库 flask_admin/static/vendor/multi-level-dropdowns-bootstrap/ 中的对应文件即可,无需依赖 flask-admin 本身。
小结
multi-level-dropdowns-bootstrap用不到 30 行的轻量 hack,为 Bootstrap 4 补上了官方缺失的“无限级下拉”能力,且保持了对官方组件的高度兼容。在 flask-admin 中,它通过与SubMenuCategory/MenuDivider菜单模型、layout.html递归渲染宏的配合,让后台导航得以支持多级分类组织。理解其“包装$.fn.dropdown、接管 click、拦截hide.bs.dropdown”的三段式原理后,你既可以直接复用仓库文件,也能在遇到类似交互需求时举一反三。赞- 后端
点击查看免费下载【免费下载链接】flask-admin
Simple and extensible administrative interface framework for Flask
项目地址:https://gitcode.com/gh_mirrors/fl/flask-admin相关推荐
Ant Design Blazor Dropdown 多级菜单实现指南:Dropdown 与 SubMenu 联动的级联下拉菜单
Ant Design Blazor Dropdown 多级菜单实现指南:Dropdown 与 SubMenu 联动的级联下拉菜单 在 Ant Design Bl
前端UI组件设计系统Ant Design Dropdown 多级菜单(SubMenu)实战指南:在 React 下拉菜单中实现层级嵌套
Ant Design Dropdown 多级菜单(SubMenu)实战指南:在 React 下拉菜单中实现层级嵌套 本篇技术指南以 Ant Design 仓库中
UI组件前端设计系统Brython 导航菜单实战指南:用 browser.widgets.menu 构建横向导航栏与多级下拉子菜单
Brython 导航菜单实战指南:用 browser.widgets.menu 构建横向导航栏与多级下拉子菜单 本文是 Brython(运行在浏览器中的 Pyt
编程语言语言运行时编译器前端
上一篇:5分钟快速上手Arduino ESP32:物联网开发的终极解决方案下一篇:dijo视图模式完全指南:Day模式和Week模式的数据可视化技巧
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考