Files
pear-admin-flask/plugins/mobileUI/__init__.py
T
bwstudio b0865ea18d fix(admin): 修复移动端后台侧栏错位与子菜单不收起
根因:此前为移动端手写的一段折叠逻辑直接给 body 加 pear-mini,
绕过了 admin.js 里唯一的同步入口 collapse(),导致三处状态错位——
布局层认为已折叠(侧栏宽 0、菜单文字被 CSS 隐藏),
菜单 DOM 仍是展开态(#side 没有 pear-nav-mini),于是"文字看不见、
但子菜单又在原地铺开"。

处理:
- templates/system/index.html 还原为框架原始版本,删除手写的
  foldSidebar() IIFE 与首屏强制加类逻辑
- 删除无引用的死文件 templates/system/common/{head,side}.html
- 新增插件 plugins/mobileUI,以运行时注入 mobile.js 的方式实现移动端增强:
  * 首屏折叠只通过框架的 li.collapse 点击完成,不手写 class
  * 轮询等待菜单异步渲染就绪后再折叠
  * 补齐 framework 内部状态 isCollapse(后端 /rights/configs 把键拼成了
    "collaspe",admin.js 读 collapse 恒为 undefined,窄屏 resize 会反向展开)
  * 触屏下点击菜单外区域收起 mini 浮层(触屏没有可靠的 mouseleave)
  * 展开态点击叶子菜单后自动收起侧栏
- applications/config.py 的 PLUGIN_ENABLE_FOLDERS 追加 cookieManager、mobileUI

framework 业务逻辑零改动,仅 config.py 一行配置。
2026-09-09 19:20:03 +08:00

76 lines
2.9 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
移动端侧栏增强插件入口
背景
----
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'<script src="{url_for("mobileUI.static", filename="mobile.js")}"></script>'
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"</body>", script_tag + b"</body>"))
except Exception: # 注入失败绝不能影响正常业务响应
app.logger.exception("mobileUI: inject failed")
return response
print(" * mobileUI: registered after_request hook (inject mobile.js into admin index)")