""" 移动端侧栏增强插件入口 背景 ---- Pear Admin 的侧栏折叠涉及三处状态,必须同步变化: 1. body / .pear-admin 上的 `pear-mini` —— 控制整页布局(CSS) 2. #side 上的 `pear-nav-mini` —— 控制菜单自身宽度与子菜单呈现方式 3. PearAdmin.instances.menu.isCollapse —— 框架内部状态机 框架只暴露了一个能同时同步这三者的入口:admin.js 里的私有函数 `collapse()`, 它由 `body.on("click", ".collapse,.pear-cover", ...)` 驱动。**外面拿不到这个函数**, 所以任何"手动 addClass('pear-mini')"的写法都必然造成状态错位(详见 README)。 本插件的做法 ------------ - 不修改 framework 任何文件; - 通过 after_request 向后台主页注入 `static/mobile.js`; - 脚本里一律用「触发原生 .collapse 点击」的方式折叠,绝不手写 class; - 额外补两处移动端体验:触屏点空白处收起子菜单浮层、点菜单后自动收起侧栏。 """ from flask import Blueprint, Flask, request, url_for bp = Blueprint( "mobileUI", __name__, url_prefix="/plugin/mobileUI", static_folder="static", ) def _script_tag() -> bytes: """脚本标签的 URL 用 url_for 生成,避免手拼蓝图静态路径出错。""" tag = f'' return tag.encode("utf-8") def event_init(app: Flask): """注册静态资源蓝图(提供 /plugin/mobileUI/static/mobile.js)。""" app.register_blueprint(bp) print(" * mobileUI: registered /plugin/mobileUI/static/* blueprint") def event_finish(app: Flask): """在后台主页响应里注入移动端脚本。""" @app.after_request def _inject_mobile_script(response): try: # 只处理成功的 HTML 页面 if response.status_code != 200: return response if not response.content_type.startswith("text/html"): return response if response.direct_passthrough: return response # 只注入后台主框架页(含侧栏容器 #side),不碰 iframe 子页面与前台页 if request.path not in ("/", "/index", "/system/index"): return response body = response.get_data() if b'id="side"' not in body or b"pear-admin" not in body: return response script_tag = _script_tag() if script_tag in body: # 幂等,避免重复注入 return response response.set_data(body.replace(b"", script_tag + b"")) except Exception: # 注入失败绝不能影响正常业务响应 app.logger.exception("mobileUI: inject failed") return response print(" * mobileUI: registered after_request hook (inject mobile.js into admin index)")