6.2 KiB
Raw Blame History

name description tags
bricks-dev-patterns Bricks Menu/target/DOM/语言/主题/DSPY陷阱。改Bricks前必读。
bricks
menu
language
theme
dspy
pitfall

Bricks 开发高级模式

开发 Bricks/Sage 应用时反复踩坑的非显而易见模式。


1. Menu 控件

{"widgettype": "Menu", "options": {
    "menuitem_css": "menuitem",
    "items": [
        {"name": "id", "label": "文字", "url": "{{entire_url('/path')}}", "target": "app.主内容id"},
        {"name": "group", "label": "分组", "items": [  // 嵌套用 items, 不用 children
            {"name": "sub", "label": "子项", "url": "...", "target": "app.主内容id"}
        ]}
    ]
}}
  • target 必须 "app.xxx"DOM closest/querySelector 不能跨兄弟节点)
  • icon 不用 emojibricks 当作 URL → 401""
  • 主内容 id 放 VScrollPanel不放 HBox 父容器

2. 语言切换

/i18n/menu.ui(需 any 权限):

{"widgettype": "Menu", "options": {"cwidth": 11, "items": [
    {"name": "zh", "label": "中文", "script": "bricks.app.change_language('zh')"},
    {"name": "en", "label": "English", "script": "bricks.app.change_language('en')"}
]}}

方法在 bricks.app.change_language(lang)


3. 主题切换

var h = document.documentElement;
var t = h.getAttribute('data-theme') || 'light';
var n = t == 'light' ? 'dark' : 'light';
h.setAttribute('data-theme', n);
var b = bricks.getWidgetById('theme_toggle_btn', bricks.app);
if (b) { b.opts.label = n == 'light' ? '🌙' : '☀️'; b.dom_element.textContent = b.opts.label; }

Button 无 refresh(),用 dom_element.textContent


4. DSPY 陷阱

  • f-string → 用字符串拼接:'算力池: ' + str(count)
  • add/update datetime:空串插入 → 500加清理
for k in list(ns.keys()):
    if k.endswith('_at') or k.endswith('_time') or k == 'last_heartbeat':
        if ns.get(k, '') in ('', None, 'None'): ns[k] = None
  • CRUD stubreturn {'status':'ok'}sor.C/U/D() → 桩代码

5. CRUD 工具栏

自动生成 JSON 中 "tools": []删除这行,框架自动生成按钮。


6. 概览 Stats

返回 HBox + VBox 卡片(不用 Text widget

return {'widgettype': 'HBox', 'options': {'gap': '16px'}, 'subwidgets': [
    {'widgettype': 'VBox', 'options': {'bgcolor': '#eff6ff', 'padding': '16px', 'width': '25%',
     'border': '1px solid #dbeafe', 'borderRadius': '8px'}, 'subwidgets': [
        {'widgettype': 'Text', 'options': {'otext': '标签', 'cfontsize': 0.7}},
        {'widgettype': 'Text', 'options': {'otext': '数值', 'cfontsize': 2, 'fontWeight': 'bold'}}
    ]}
]}

7. 权限隔离

角色 范围
any stats API, index.ui, menu.ui, overview.ui, 静态资源
logined get_*.dspy, CRUD create/update/delete

反模式CRUD 加 any → 匿名可增删改。


8. Button bind → script 反馈

按钮用 binds 触发 script 时:

  • 反馈弹窗用官方封装 bricks.show_message({title, message}) / bricks.show_error({title, message})(自带默认尺寸)。new bricks.Message({...}) 构造时 auto_open=true 已自动打开,.open() 冗余。
  • bricks.universal_handlerasync:点击后立刻检查 fetch 调用/弹窗会得假阴性fetch 还没执行)。验证脚本要 await new Promise(r => setTimeout(r, 800)) 再断言,否则会误判"按钮 script 没生效"。
  • 快速诊断 bind 是否真的绑定:btn.dispatchEvent(new Event('click')) 会直接触发 bind handler不经过 label 的 target_clicked 链路)。

9. .ui 文件直接访问

浏览器直接访问 /module/xxx/index.ui 返回原始 JSON,不是渲染页。.ui 必须经 bricks 框架加载(从 Menu/urlwidget 点击进入)。别用 browser_navigate 直连 .ui URL 去判断渲染结果。


10. Menu 子菜单items vs submenu vs 裸数组

  • 内联子菜单用 items: [{name,label,url}, ...](见 §1
  • submenu 字段指向的是完整 Menu widget 的 .ui(含 "widgettype": "Menu")。
  • 裸数组(如 rbac/admin_menu.ui[{name,label,url},...]不是 Menu widget不能被 submenu 直接加载——要么把数组内联进父菜单 items,要么包一层 Menu widget。

11. 编辑 .ui/.json 的 patch 陷阱

改 Menu/JSON 前先 re-read 文件——部署过程里文件可能被外部改过。patch 的模糊匹配在文件已变化时会错位:曾把 app_audit 菜单项误删、并把 tenant 复制两份。改后必验证:列出所有 name,检查重复项([n for n in names if names.count(n)>1])。


12. Text 控件默认 halign 是 'center'

widget.jsoptions.halign = options.halign || 'center'——Text 不写 halign 时文字默认居中,不是左对齐

  • 文件列表、表格行、标签等任何要左对齐的文本必须显式 "halign": "left"
  • 尤其 Text 同时加 "css": "filler"flex-grow 撑满剩余空间)时,文字会在撑开的盒子里居中,视觉上"不靠图标"。文件名行 = 图标 + 名称(halign:"left" + css:"filler") + 大小,名称必须显式 halign left。

13. 前端改动"没生效" → 用 ?v= 缓存破除,别只让用户 Ctrl+F5

bricks.js/bricks.css 响应只有 ETag/Last-Modified没有 Cache-Control,浏览器按启发式缓存(新鲜期 ≈ 距 Last-Modified 的 10%)。用户 Ctrl+F5/清缓存后仍可能拿到旧版,启发式新鲜期不可控。

健壮做法:在 bricks/header.tmpl 的 script/link 标签加版本查询串并每次部署递增:

<link rel="stylesheet" href="{{entire_url('/bricks/css/bricks.css')}}?v=20260815d">
<script src="{{entire_url('/bricks/bricks.js')}}?v=20260815d"></script>

改源文件后同时 bump 版本号 → 浏览器当全新 URL 强制拉取,普通刷新即可。entire_url 之外的 ?v= 会被 ahserver 忽略(按路径 servequery 不影响)。

教训:诊断前端"改了没生效"时,别拿"浏览器缓存"当未经验证的根因下结论——用户已明确清过缓存仍复现,就该怀疑代码本身,改用 console.log 探针 + ?v= 破除缓存再看实际数据。