4.8 KiB
Raw Blame History

name description version tags
bricks-menu-dspy-pitfalls Bricks Menu/DSPY/语言切换/CRUD stub 实战教训——PCCS 踩坑总结 1.0.0
bricks
menu
dspy
pitfall
pccs
language
stub

用户菜单(👤)聚合机制: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]