136 lines
5.6 KiB
Python
136 lines
5.6 KiB
Python
#!/usr/bin/env python3
|
||
# -*- coding: utf-8 -*-
|
||
"""生成 M9 覆盖层 12 个页面壳 wwwroot/pbl_overlay/<key>.ui(bricks 纯 JSON)。
|
||
|
||
为什么需要:wwwroot/overlay/pbl_overlay_core.js 的 PAGES 注册表 route 指向
|
||
`/pbl_overlay/<key>.ui`,play.js 的 pageUrl() 会拼成
|
||
`/pbl_scense_ext/pbl_overlay/<key>.ui`;页面壳文件不存在则导航即 404(QC #3)。
|
||
|
||
页面壳职责(只做壳,不做业务渲染):
|
||
1. 打 `data-pbl-page="<key>"` + `data-pbl-overlay-root="1"` 标记 —— play.js
|
||
hostPageKey() 据此识别当前页、boot() 据此挂载覆盖层根节点;
|
||
2. 提供 `script` actiontype 按钮调用 `PblOverlayLoader.start({page:...})`,
|
||
由 loader 按需注入 core/transport/play JS 与 CSS(不在 .ui 里硬写 <script>/<link>,
|
||
遵守 module-development-spec「ahserver auto-serves .css/.js」);
|
||
3. 提供返回首页的 `url` actiontype 导航。
|
||
|
||
route 与 RBAC:新增/删除页面壳必须同步 scripts/load_path.py 的 OVERLAY_PAGES。
|
||
"""
|
||
import json
|
||
import os
|
||
|
||
MODULE = 'pbl_scense_ext'
|
||
LOADER_PATH = '/%s/overlay/pbl_overlay_loader.js' % MODULE
|
||
|
||
# (key, title, in_world) —— 与 wwwroot/overlay/pbl_overlay_core.js PAGES 注册表同源
|
||
PAGES = [
|
||
('home', 'PBL 首页', False),
|
||
('blueprint_list', '蓝图列表', False),
|
||
('blueprint_editor', '蓝图结构视图', False),
|
||
('validation_report', '校验报告(14维)', False),
|
||
('agent_console', 'Agent 控制台', False),
|
||
('approval_center', '人工审批中心', False),
|
||
('play_preview', 'Play 预览', True),
|
||
('mission_board', '驱动问题板', True),
|
||
('role_panel', '角色面板', True),
|
||
('artifact_studio', '产出物工坊', True),
|
||
('evidence_wall', '证据墙', True),
|
||
('assessment_report', '评估报告', False),
|
||
]
|
||
|
||
OUT_DIR = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))),
|
||
'wwwroot', 'pbl_overlay')
|
||
|
||
|
||
def enable_script(key):
|
||
"""点击「启用 PBL 覆盖层」:优先用已注入的 loader;未注入则先注入 loader.js(RBAC=any)再启动。"""
|
||
return (
|
||
"window.PblOverlayLoader"
|
||
"?PblOverlayLoader.start({page:'%s'})"
|
||
":(function(){"
|
||
"var b=String(window.__PBL_API_BASE__||'').replace(/\\/$/,'');"
|
||
"var s=document.createElement('script');s.src=b+'%s';s.async=false;"
|
||
"s.onload=function(){window.PblOverlayLoader.start({page:'%s'})};"
|
||
"s.onerror=function(){window.__pblOverlayFail&&window.__pblOverlayFail('PBL_E_NETWORK','覆盖层引导脚本加载失败')};"
|
||
"document.head.appendChild(s);})()"
|
||
) % (key, LOADER_PATH, key)
|
||
|
||
|
||
def build_page(key, title, in_world):
|
||
shell_html = (
|
||
'<div data-pbl-page="{key}" data-pbl-overlay-root="1" class="pbl-shell">'
|
||
'<div class="pbl-shell-hint">'
|
||
'{title}({key})· 覆盖层未启用:点击下方「启用 PBL 覆盖层」装配 PBL 组件;'
|
||
'{hint}'
|
||
'</div></div>'
|
||
).format(
|
||
key=key, title=title,
|
||
hint='本页为世界内覆盖层(广播渲染预算 ≤500ms)。' if in_world
|
||
else '本页为教师/学生侧页面(首屏预算 ≤1.5s)。'
|
||
)
|
||
return {
|
||
'widgettype': 'VBox',
|
||
'options': {
|
||
'width': '100%', 'height': '100%', 'padding': '12px',
|
||
'backgroundColor': '#0B1020',
|
||
},
|
||
'subwidgets': [
|
||
{
|
||
'widgettype': 'Text',
|
||
'options': {'label': 'PBL 覆盖层 · ' + title, 'fontSize': '18px',
|
||
'fontWeight': 'bold', 'color': '#E5E7EB'},
|
||
},
|
||
{
|
||
'widgettype': 'Html',
|
||
'id': 'pbl_overlay_shell_' + key,
|
||
'options': {'html': shell_html},
|
||
},
|
||
{
|
||
'widgettype': 'HBox',
|
||
'options': {'width': '100%', 'gap': '8px', 'marginTop': '10px'},
|
||
'subwidgets': [
|
||
{
|
||
'widgettype': 'Button',
|
||
'options': {'label': '启用 PBL 覆盖层', 'type': 'primary'},
|
||
'binds': [{'wid': 'self', 'event': 'click',
|
||
'actiontype': 'script',
|
||
'options': {'script': enable_script(key)}}],
|
||
},
|
||
{
|
||
'widgettype': 'Button',
|
||
'options': {'label': 'PBL 首页'},
|
||
'binds': [{'wid': 'self', 'event': 'click',
|
||
'actiontype': 'url',
|
||
'options': {'url': "{{entire_url('/%s/pbl_overlay/home.ui')}}" % MODULE}}],
|
||
},
|
||
],
|
||
},
|
||
{
|
||
'widgettype': 'Text',
|
||
'options': {'label': '上下文:URL 携带 session_id / blueprint_id / world_id;'
|
||
'覆盖层仅渲染服务端权威状态,不本地预测(Q4)。',
|
||
'fontSize': '12px', 'color': '#6B7280', 'marginTop': '10px'},
|
||
},
|
||
],
|
||
}
|
||
|
||
|
||
def main():
|
||
if not os.path.isdir(OUT_DIR):
|
||
os.makedirs(OUT_DIR)
|
||
written = []
|
||
for key, title, in_world in PAGES:
|
||
obj = build_page(key, title, in_world)
|
||
path = os.path.join(OUT_DIR, key + '.ui')
|
||
with open(path, 'w', encoding='utf-8') as f:
|
||
json.dump(obj, f, ensure_ascii=False, indent=2)
|
||
f.write('\n')
|
||
written.append(path)
|
||
print('generated %d overlay page shells:' % len(written))
|
||
for p in written:
|
||
print(' ' + os.path.relpath(p, os.path.dirname(OUT_DIR)))
|
||
|
||
|
||
if __name__ == '__main__':
|
||
main()
|