6.2 KiB
| name | description | tags | ||||||
|---|---|---|---|---|---|---|---|---|
| bricks-dev-patterns | Bricks Menu/target/DOM/语言/主题/DSPY陷阱。改Bricks前必读。 |
|
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不用 emoji(bricks 当作 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 stub:
return {'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_handler是 async:点击后立刻检查 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.js 里 options.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 忽略(按路径 serve,query 不影响)。
教训:诊断前端"改了没生效"时,别拿"浏览器缓存"当未经验证的根因下结论——用户已明确清过缓存仍复现,就该怀疑代码本身,改用 console.log 探针 + ?v= 破除缓存再看实际数据。