4.8 KiB
| name | description | version | tags | |||||||
|---|---|---|---|---|---|---|---|---|---|---|
| bricks-menu-dspy-pitfalls | Bricks Menu/DSPY/语言切换/CRUD stub 实战教训——PCCS 踩坑总结 | 1.0.0 |
|
用户菜单(👤)聚合机制:user_menu.ui + 模块 usermenu.ui
Sage 平台用户头像菜单不是各模块直接改共享文件,而是聚合:
- 应用级:应用
wwwroot/下放user_menu.ui(带下划线)= 聚合入口。 - 模块级:每个模块要往用户菜单加项,就在自己模块
wwwroot/下放usermenu.ui(无下划线),Sage 自动聚合所有模块的 usermenu.ui。
铁律:绝不往 rbac 的 usermenu.ui 加应用专属菜单项。 rbac 是通用模块(所有应用共用),加"绑定微信"这类产线专属项会让所有应用都出现该菜单。应用专属项放该应用自己模块的 wwwroot/usermenu.ui。
入口链路:Header 👤 → urlwidget 加载 /rbac/user/user_panel.ui → user.ui → userinfo.ui → 点击弹 Popup 加载 /user_menu.ui(应用级聚合入口)。关键:应用 wwwroot/ 必须存在 user_menu.ui 文件,否则 /user_menu.ui 返回 500 invalid path——这正是用户头像菜单打不开的根因,补上应用级 user_menu.ui 后 500→200。user_menu.ui 是 Menu widget,items 里每项 label + submenu: entire_url('/模块/usermenu.ui') 指向各模块(例:我→/rbac/usermenu.ui、充值→/unipay/usermenu.ui)。模块级 /rbac/usermenu.ui 需 permission 表有该路径的 any 权限条目,否则 403。
⚠️ 别把 userinfo.ui 的引用路径改成 /rbac/usermenu.ui——那会绕过应用级聚合,直接加载单个模块菜单。正确是保留 /user_menu.ui 并补应用级 user_menu.ui 文件。
TabPanel 动态 Tab(菜单项点击添加/切换 tab)
bricks.Html 的 <script> 不执行(innerHTML 限制)
bricks.Html 构造用 this.dom_element.innerHTML = opts.html,innerHTML 插入的 <script> 标签不会被浏览器执行。而且 html 字段里的 </script> 会提前终止 HTML shell 的外层 <script> 标签(const opts = {...} 那段),导致整页变成原始 JSON 文本。
正确做法:把 JS 放到独立 .js 文件(如 wwwroot/index_tab.js),靠 ahserver 的 configuredServer.get_js_files(get_filetype_files('.js') 遍历 website.paths 根目录 + 子目录)自动收集加载到 HTML shell 的 <script src=...>,不需要在 .ui 里引用。注意根目录的 .js 需 rp.json any 权限(模块目录的由 /module/** 覆盖)。
TabPanel 原生缺陷(需扩展修复)
add_tab(desc)只创建 toolbar 按钮,不把 desc 加进opts.items→ 点击动态 tab 时show_tabcontent在 items 里找不到,"nothing to do"。show_tabcontent里this.cur_tab_name = name用了未定义变量name(应为tdesc.name)。
扩展 open_tab 模式(在独立 .js 里 prototype 扩展):
bricks.TabPanel.prototype.open_tab = async function(desc){
// 已存在判断用 opts.items(不是 content_buffer)——因 urlwidget 请求 401 时 content 构建失败,content_buffer 存不进去,会误判"不存在"导致重复新增
var existing = null;
for (var i=0;i<this.opts.items.length;i++)
if (this.opts.items[i].name === desc.name){ existing=this.opts.items[i]; break; }
if (existing){
if (desc.name !== this.cur_tab_name){
this.cur_tab_name = desc.name;
var w = this.content_buffer[desc.name];
if (!w){ w = await bricks.widgetBuild(existing.content, this, {}); if (w) this.content_buffer[desc.name]=w; }
if (w) this.switch_content(w);
if (this.toolbar && this.toolbar.click) this.toolbar.click(desc.name);
}
return;
}
desc.content = desc.content || {"widgettype":"urlwidget","options":{"url":desc.url}};
this.opts.items.push(desc);
if (this.add_tab) this.add_tab(desc);
var w = await bricks.widgetBuild(desc.content, this, {});
this.content_buffer[desc.name] = w; // 即使 null 也记录,避免重复新增
this.cur_tab_name = desc.name;
if (w) this.switch_content(w);
};
Menu 项 opts.url 会 clear_widgets 清空 target
menu_clicked 里若菜单项有 opts.url,会 t.clear_widgets(); t.add_widget(w)——若 target 是 TabPanel 会清掉 tab 结构。改用 opts.script(script 里 this = target widget,即 TabPanel)调 this.open_tab({...})。菜单项从 "url":"{{entire_url('/xxx')}}" 改成 "script":"this.open_tab({name:'xxx',label:'标签',url:'/xxx',removable:true})"(url 用相对路径,urlwidget 的 bricks.absurl 自动解析)。
主页不可删除 tab
items 里 "removable": false 即可(Toolbar 的 add_removable 只在 removable 为真时加删除按钮)。...[truncated]