deliver: 交付收口(引擎代为提交)

This commit is contained in:
agent.develop 2026-09-21 12:20:49 +08:00
parent 22b9d575e9
commit eb9cd56b6b
15 changed files with 1208 additions and 14 deletions

View File

@ -0,0 +1,135 @@
#!/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()

View File

@ -1,10 +1,14 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""pbl_scense_ext RBAC 路径注册(硬门禁 6.6 / QC #11)。
"""pbl_scense_ext RBAC 路径注册(硬门禁 6.6 / QC #11 / M9 重做 QC #1)。
约定:
- 路径 = 模块自动路由 `/pbl_scense_ext/api/<契约>.dspy`,不带端口、不带 /wss 前缀;
- 角色 `logined` = 登录即可访问的读接口;写接口按角色分级(teacher/admin);
- 路径 = 模块自动路由 `/pbl_scense_ext/...`,不带端口、不带 /wss 前缀;
- 角色 `any` = 免登录可取(覆盖层引导脚本 loader.js、覆盖层主题 CSS——须在
鉴权完成前即可注入,否则预鉴权页壳拿不到 loader);
- 角色 `logined` = 登录后可访问(契约 .dspy 接口、覆盖层业务 JS、12 个页面壳 .ui);
- 禁止 `%` / `*` 通配符(module-development-spec「load_path.py Wildcards are FORBIDDEN」),
每个文件一条显式条目;新增/删除静态资源必须同步本表;
- 由 apps/pbls/build.sh 第 8 步调用 `register()`;rbac CLI 不在位时打印清单(不静默跳过)。
"""
import os
@ -13,15 +17,43 @@ import sys
MODULE = 'pbl_scense_ext'
# 覆盖层 12 页面壳(wwwroot/pbl_overlay/*.ui)——与 wwwroot/overlay/pbl_overlay_core.js
# PAGES 注册表 route 同源(route '/pbl_overlay/<key>.ui' → 本模块目录 pbl_overlay/<key>.ui)
OVERLAY_PAGES = [
'home', 'blueprint_list', 'blueprint_editor', 'validation_report',
'agent_console', 'approval_center', 'play_preview', 'mission_board',
'role_panel', 'artifact_studio', 'evidence_wall', 'assessment_report',
]
# (path, role)
PATHS = [
# ── 契约接口(4 个 .dspy,均需登录) ──
('/pbl_scense_ext/api/pbl_session_context_get.dspy', 'logined'),
('/pbl_scense_ext/api/pbl_page_registry_list.dspy', 'logined'),
('/pbl_scense_ext/api/pbl_playtest_feedback_save.dspy', 'logined'),
('/pbl_scense_ext/api/pbl_overlay_bootstrap.dspy', 'logined'),
# ── 模块入口页 ──
('/pbl_scense_ext', 'logined'),
('/pbl_scense_ext/index.ui', 'logined'),
# ── 覆盖层静态资源(QC #1 硬门禁:ahserver 自动托管 .css/.js 但仍受 RBAC 约束,
# 未注册即 403,M9 前端全废) ──
# loader.js 是页壳引导注入器,须在预鉴权页壳里就能加载 → any
('/pbl_scense_ext/overlay/pbl_overlay_loader.js', 'any'),
# 主题 CSS 同理(页壳首屏样式,不含业务数据)→ any
('/pbl_scense_ext/overlay/pbl_overlay.css', 'any'),
# 业务 JS 只在登录后由 loader 注入 → logined
('/pbl_scense_ext/overlay/pbl_overlay_core.js', 'logined'),
('/pbl_scense_ext/overlay/pbl_overlay_transport.js', 'logined'),
('/pbl_scense_ext/overlay/pbl_overlay_play.js', 'logined'),
]
# 覆盖层页面壳 .ui:目录 + 每个文件显式注册(登录后可见,含世界内覆盖层页)
for _key in OVERLAY_PAGES:
PATHS.append(('/pbl_scense_ext/pbl_overlay/%s.ui' % _key, 'logined'))
PATHS.append(('/pbl_scense_ext/pbl_overlay', 'logined'))
def register():
tool = os.environ.get('RBAC_SET_PERM', 'set_role_perm.py')
@ -33,11 +65,47 @@ def register():
done += 1
else:
missing.append((path, role))
print('[%s] rbac paths: total=%d ok=%d pending=%d' %(len(PATHS), done, len(missing)))
print('[%s] rbac paths: total=%d ok=%d pending=%d' % (MODULE, len(PATHS), done, len(missing)))
for path, role in missing:
print(' PENDING %%-12s %s' %(role, path))
print(' PENDING %-12s %s' % (role, path))
return len(missing) == 0
def audit():
"""自检:PATHS 中的 overlay/pbl_overlay 条目与磁盘文件一一对应(无悬空、无漏注册)。"""
root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
problems = []
# 1) 磁盘上有但未注册
for sub in ('overlay', 'pbl_overlay'):
d = os.path.join(root, 'wwwroot', sub)
if not os.path.isdir(d):
continue
for fn in sorted(os.listdir(d)):
if not fn.endswith(('.js', '.css', '.ui')):
continue
p = '/%s/%s/%s' % (MODULE, sub, fn)
if p not in [x[0] for x in PATHS]:
problems.append('UNREGISTERED %s' % p)
# 2) 注册了但磁盘缺文件(页面壳条目)
have = set('/%s/pbl_overlay/%s.ui' % (MODULE, k) for k in OVERLAY_PAGES)
for path, _role in PATHS:
if '/pbl_overlay/' in path and path not in have:
problems.append('BAD ENTRY %s' % path)
for path in sorted(have):
f = os.path.join(root, 'wwwroot', 'pbl_overlay', os.path.basename(path))
if not os.path.isfile(f):
problems.append('MISSING FILE %s' % path)
# 3) 通配符零容忍
for path, _role in PATHS:
if '%' in path or '*' in path:
problems.append('WILDCARD FORBIDDEN %s' % path)
return problems
if __name__ == '__main__':
bad = audit()
for b in bad:
print('AUDIT %s' % b)
if '--audit' in sys.argv:
sys.exit(1 if bad else 0)
sys.exit(0 if register() else 1)

View File

@ -2,26 +2,61 @@
* pbl_overlay_loader.js —— 覆盖层脚本/CSS 按需注入器(M9)
*
* 为什么需要它:bricks .ui 页面禁止硬写 <script>/<link> 标签(会绕过 RBAC 或
* 触发 403)。本 loader 由页面壳的 bricks `script` actiontype 在用户点击
* 「启用 PBL 覆盖层」时调用,动态注入同模块 wwwroot/overlay 下的 JS/CSS
* (这些路径已在 scripts/load_path.py 显式注册为 any/logined),注入完成后
* 调用 PblOverlayPlay.boot() 完成装配。
* 触发 403)。本 loader 由页面壳(wwwroot/pbl_overlay/*.ui)的 bricks
* `script` actiontype 在用户点击「启用 PBL 覆盖层」时调用,动态注入同模块
* wwwroot/overlay 下的 JS/CSS,注入完成后调用 PblOverlayPlay.boot() 完成装配。
*
* RBAC 事实(QC #1 已补齐,与 scripts/load_path.py PATHS 表一一对应,禁止只改一边):
* - /pbl_scense_ext/overlay/pbl_overlay_loader.js → any (预鉴权页壳引导)
* - /pbl_scense_ext/overlay/pbl_overlay.css → any (页壳首屏样式)
* - /pbl_scense_ext/overlay/pbl_overlay_core.js → logined (业务内核)
* - /pbl_scense_ext/overlay/pbl_overlay_transport.js→ logined (广播/意图通道)
* - /pbl_scense_ext/overlay/pbl_overlay_play.js → logined (覆盖层装配)
* - /pbl_scense_ext/pbl_overlay/<page>.ui × 12 → logined (页面壳)
*
* 铁律:只注入本模块静态资源,不改写宿主(scense/scense_game)渲染层任何脚本。
* ==========================================================================*/
(function (global) {
'use strict';
var BASE = String(global.__PBL_API_BASE__ || '').replace(/\/$/, '');
var DIR = BASE + '/pbl_scense_ext/overlay/';
var MODULE = 'pbl_scense_ext';
// 依赖顺序:core → transport → play(play 内部依赖前两者)
function apiBase() {
// 宿主显式注入优先;否则从当前 pathname 去掉模块路由尾部推应用根
var inj = String(global.__PBL_API_BASE__ || '');
if (inj) { return inj.replace(/\/$/, ''); }
return String(global.location && global.location.pathname || '')
.replace(/\/(pbl_overlay|overlay|game|scense_game|scense)\/.*$/, '')
.replace(/\/$/, '');
}
var DIR = apiBase() + '/' + MODULE + '/overlay/';
// 依赖顺序:core → transport → play(play 内部硬依赖前两者)
var FILES = ['pbl_overlay_core.js', 'pbl_overlay_transport.js', 'pbl_overlay_play.js'];
var CSS = 'pbl_overlay.css';
var injected = {};
var loading = null;
/* 注入失败时的最小可见降级(不依赖 core,避免「白屏无提示」)。
* 页面壳的 script 里也引用本函数:window.__pblOverlayFail(code, message) */
function degrade(code, message) {
try {
var host = document.querySelector('[data-pbl-overlay-root]') || document.body;
var box = document.getElementById('pbl-loader-degrade');
if (!box) {
box = document.createElement('div');
box.id = 'pbl-loader-degrade';
box.setAttribute('class', 'pbl-banner pbl-banner--error');
host.appendChild(box);
}
box.textContent = '[' + (code || 'PBL_E_NETWORK') + '] ' + (message || '覆盖层加载失败');
box.setAttribute('data-pbl-degrade', '1');
} catch (e) { /* DOM 不可用时静默,不抛出打断宿主 */ }
return { code: code || 'PBL_E_NETWORK', message: message || 'overlay load failed' };
}
function loadOne(src) {
return new Promise(function (resolve, reject) {
if (injected[src]) { return resolve(true); }
@ -29,7 +64,7 @@
s.src = src;
s.async = false;
s.onload = function () { injected[src] = true; resolve(true); };
s.onerror = function () { reject({ code: 'PBL_E_NETWORK', message: '资源加载失败 ' + src }); };
s.onerror = function () { reject({ code: 'PBL_E_NETWORK', message: '资源加载失败(403/404?) ' + src }); };
document.head.appendChild(s);
});
}
@ -59,13 +94,21 @@
if (!global.PblOverlayPlay) { throw { code: 'PBL_E_NOT_FOUND', message: 'PblOverlayPlay 未注册' }; }
if (global.__PBL_OVERLAY__ && global.__PBL_OVERLAY__.mounted) { return global.__PBL_OVERLAY__; }
return global.PblOverlayPlay.boot(opts || {}) || global.__PBL_OVERLAY__;
}).catch(function (err) {
degrade((err && err.code) || 'PBL_E_LOAD_FAIL', (err && err.message) || String(err));
throw err;
});
}
global.PblOverlayLoader = {
MODULE: MODULE,
DIR: DIR,
FILES: FILES,
CSS: CSS,
apiBase: apiBase,
load: load,
start: start
start: start,
degrade: degrade
};
global.__pblOverlayFail = degrade;
})(window);

View File

@ -0,0 +1,79 @@
{
"widgettype": "VBox",
"options": {
"width": "100%",
"height": "100%",
"padding": "12px",
"backgroundColor": "#0B1020"
},
"subwidgets": [
{
"widgettype": "Text",
"options": {
"label": "PBL 覆盖层 · Agent 控制台",
"fontSize": "18px",
"fontWeight": "bold",
"color": "#E5E7EB"
}
},
{
"widgettype": "Html",
"id": "pbl_overlay_shell_agent_console",
"options": {
"html": "<div data-pbl-page=\"agent_console\" data-pbl-overlay-root=\"1\" class=\"pbl-shell\"><div class=\"pbl-shell-hint\">Agent 控制台(agent_console)· 覆盖层未启用:点击下方「启用 PBL 覆盖层」装配 PBL 组件;本页为教师/学生侧页面(首屏预算 ≤1.5s)。</div></div>"
}
},
{
"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": "window.PblOverlayLoader?PblOverlayLoader.start({page:'agent_console'}):(function(){var b=String(window.__PBL_API_BASE__||'').replace(/\\/$/,'');var s=document.createElement('script');s.src=b+'/pbl_scense_ext/overlay/pbl_overlay_loader.js';s.async=false;s.onload=function(){window.PblOverlayLoader.start({page:'agent_console'})};s.onerror=function(){window.__pblOverlayFail&&window.__pblOverlayFail('PBL_E_NETWORK','覆盖层引导脚本加载失败')};document.head.appendChild(s);})()"
}
}
]
},
{
"widgettype": "Button",
"options": {
"label": "PBL 首页"
},
"binds": [
{
"wid": "self",
"event": "click",
"actiontype": "url",
"options": {
"url": "{{entire_url('/pbl_scense_ext/pbl_overlay/home.ui')}}"
}
}
]
}
]
},
{
"widgettype": "Text",
"options": {
"label": "上下文:URL 携带 session_id / blueprint_id / world_id;覆盖层仅渲染服务端权威状态,不本地预测(Q4)。",
"fontSize": "12px",
"color": "#6B7280",
"marginTop": "10px"
}
}
]
}

View File

@ -0,0 +1,79 @@
{
"widgettype": "VBox",
"options": {
"width": "100%",
"height": "100%",
"padding": "12px",
"backgroundColor": "#0B1020"
},
"subwidgets": [
{
"widgettype": "Text",
"options": {
"label": "PBL 覆盖层 · 人工审批中心",
"fontSize": "18px",
"fontWeight": "bold",
"color": "#E5E7EB"
}
},
{
"widgettype": "Html",
"id": "pbl_overlay_shell_approval_center",
"options": {
"html": "<div data-pbl-page=\"approval_center\" data-pbl-overlay-root=\"1\" class=\"pbl-shell\"><div class=\"pbl-shell-hint\">人工审批中心(approval_center)· 覆盖层未启用:点击下方「启用 PBL 覆盖层」装配 PBL 组件;本页为教师/学生侧页面(首屏预算 ≤1.5s)。</div></div>"
}
},
{
"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": "window.PblOverlayLoader?PblOverlayLoader.start({page:'approval_center'}):(function(){var b=String(window.__PBL_API_BASE__||'').replace(/\\/$/,'');var s=document.createElement('script');s.src=b+'/pbl_scense_ext/overlay/pbl_overlay_loader.js';s.async=false;s.onload=function(){window.PblOverlayLoader.start({page:'approval_center'})};s.onerror=function(){window.__pblOverlayFail&&window.__pblOverlayFail('PBL_E_NETWORK','覆盖层引导脚本加载失败')};document.head.appendChild(s);})()"
}
}
]
},
{
"widgettype": "Button",
"options": {
"label": "PBL 首页"
},
"binds": [
{
"wid": "self",
"event": "click",
"actiontype": "url",
"options": {
"url": "{{entire_url('/pbl_scense_ext/pbl_overlay/home.ui')}}"
}
}
]
}
]
},
{
"widgettype": "Text",
"options": {
"label": "上下文:URL 携带 session_id / blueprint_id / world_id;覆盖层仅渲染服务端权威状态,不本地预测(Q4)。",
"fontSize": "12px",
"color": "#6B7280",
"marginTop": "10px"
}
}
]
}

View File

@ -0,0 +1,79 @@
{
"widgettype": "VBox",
"options": {
"width": "100%",
"height": "100%",
"padding": "12px",
"backgroundColor": "#0B1020"
},
"subwidgets": [
{
"widgettype": "Text",
"options": {
"label": "PBL 覆盖层 · 产出物工坊",
"fontSize": "18px",
"fontWeight": "bold",
"color": "#E5E7EB"
}
},
{
"widgettype": "Html",
"id": "pbl_overlay_shell_artifact_studio",
"options": {
"html": "<div data-pbl-page=\"artifact_studio\" data-pbl-overlay-root=\"1\" class=\"pbl-shell\"><div class=\"pbl-shell-hint\">产出物工坊(artifact_studio)· 覆盖层未启用:点击下方「启用 PBL 覆盖层」装配 PBL 组件;本页为世界内覆盖层(广播渲染预算 ≤500ms)。</div></div>"
}
},
{
"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": "window.PblOverlayLoader?PblOverlayLoader.start({page:'artifact_studio'}):(function(){var b=String(window.__PBL_API_BASE__||'').replace(/\\/$/,'');var s=document.createElement('script');s.src=b+'/pbl_scense_ext/overlay/pbl_overlay_loader.js';s.async=false;s.onload=function(){window.PblOverlayLoader.start({page:'artifact_studio'})};s.onerror=function(){window.__pblOverlayFail&&window.__pblOverlayFail('PBL_E_NETWORK','覆盖层引导脚本加载失败')};document.head.appendChild(s);})()"
}
}
]
},
{
"widgettype": "Button",
"options": {
"label": "PBL 首页"
},
"binds": [
{
"wid": "self",
"event": "click",
"actiontype": "url",
"options": {
"url": "{{entire_url('/pbl_scense_ext/pbl_overlay/home.ui')}}"
}
}
]
}
]
},
{
"widgettype": "Text",
"options": {
"label": "上下文:URL 携带 session_id / blueprint_id / world_id;覆盖层仅渲染服务端权威状态,不本地预测(Q4)。",
"fontSize": "12px",
"color": "#6B7280",
"marginTop": "10px"
}
}
]
}

View File

@ -0,0 +1,79 @@
{
"widgettype": "VBox",
"options": {
"width": "100%",
"height": "100%",
"padding": "12px",
"backgroundColor": "#0B1020"
},
"subwidgets": [
{
"widgettype": "Text",
"options": {
"label": "PBL 覆盖层 · 评估报告",
"fontSize": "18px",
"fontWeight": "bold",
"color": "#E5E7EB"
}
},
{
"widgettype": "Html",
"id": "pbl_overlay_shell_assessment_report",
"options": {
"html": "<div data-pbl-page=\"assessment_report\" data-pbl-overlay-root=\"1\" class=\"pbl-shell\"><div class=\"pbl-shell-hint\">评估报告(assessment_report)· 覆盖层未启用:点击下方「启用 PBL 覆盖层」装配 PBL 组件;本页为教师/学生侧页面(首屏预算 ≤1.5s)。</div></div>"
}
},
{
"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": "window.PblOverlayLoader?PblOverlayLoader.start({page:'assessment_report'}):(function(){var b=String(window.__PBL_API_BASE__||'').replace(/\\/$/,'');var s=document.createElement('script');s.src=b+'/pbl_scense_ext/overlay/pbl_overlay_loader.js';s.async=false;s.onload=function(){window.PblOverlayLoader.start({page:'assessment_report'})};s.onerror=function(){window.__pblOverlayFail&&window.__pblOverlayFail('PBL_E_NETWORK','覆盖层引导脚本加载失败')};document.head.appendChild(s);})()"
}
}
]
},
{
"widgettype": "Button",
"options": {
"label": "PBL 首页"
},
"binds": [
{
"wid": "self",
"event": "click",
"actiontype": "url",
"options": {
"url": "{{entire_url('/pbl_scense_ext/pbl_overlay/home.ui')}}"
}
}
]
}
]
},
{
"widgettype": "Text",
"options": {
"label": "上下文:URL 携带 session_id / blueprint_id / world_id;覆盖层仅渲染服务端权威状态,不本地预测(Q4)。",
"fontSize": "12px",
"color": "#6B7280",
"marginTop": "10px"
}
}
]
}

View File

@ -0,0 +1,79 @@
{
"widgettype": "VBox",
"options": {
"width": "100%",
"height": "100%",
"padding": "12px",
"backgroundColor": "#0B1020"
},
"subwidgets": [
{
"widgettype": "Text",
"options": {
"label": "PBL 覆盖层 · 蓝图结构视图",
"fontSize": "18px",
"fontWeight": "bold",
"color": "#E5E7EB"
}
},
{
"widgettype": "Html",
"id": "pbl_overlay_shell_blueprint_editor",
"options": {
"html": "<div data-pbl-page=\"blueprint_editor\" data-pbl-overlay-root=\"1\" class=\"pbl-shell\"><div class=\"pbl-shell-hint\">蓝图结构视图(blueprint_editor)· 覆盖层未启用:点击下方「启用 PBL 覆盖层」装配 PBL 组件;本页为教师/学生侧页面(首屏预算 ≤1.5s)。</div></div>"
}
},
{
"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": "window.PblOverlayLoader?PblOverlayLoader.start({page:'blueprint_editor'}):(function(){var b=String(window.__PBL_API_BASE__||'').replace(/\\/$/,'');var s=document.createElement('script');s.src=b+'/pbl_scense_ext/overlay/pbl_overlay_loader.js';s.async=false;s.onload=function(){window.PblOverlayLoader.start({page:'blueprint_editor'})};s.onerror=function(){window.__pblOverlayFail&&window.__pblOverlayFail('PBL_E_NETWORK','覆盖层引导脚本加载失败')};document.head.appendChild(s);})()"
}
}
]
},
{
"widgettype": "Button",
"options": {
"label": "PBL 首页"
},
"binds": [
{
"wid": "self",
"event": "click",
"actiontype": "url",
"options": {
"url": "{{entire_url('/pbl_scense_ext/pbl_overlay/home.ui')}}"
}
}
]
}
]
},
{
"widgettype": "Text",
"options": {
"label": "上下文:URL 携带 session_id / blueprint_id / world_id;覆盖层仅渲染服务端权威状态,不本地预测(Q4)。",
"fontSize": "12px",
"color": "#6B7280",
"marginTop": "10px"
}
}
]
}

View File

@ -0,0 +1,79 @@
{
"widgettype": "VBox",
"options": {
"width": "100%",
"height": "100%",
"padding": "12px",
"backgroundColor": "#0B1020"
},
"subwidgets": [
{
"widgettype": "Text",
"options": {
"label": "PBL 覆盖层 · 蓝图列表",
"fontSize": "18px",
"fontWeight": "bold",
"color": "#E5E7EB"
}
},
{
"widgettype": "Html",
"id": "pbl_overlay_shell_blueprint_list",
"options": {
"html": "<div data-pbl-page=\"blueprint_list\" data-pbl-overlay-root=\"1\" class=\"pbl-shell\"><div class=\"pbl-shell-hint\">蓝图列表(blueprint_list)· 覆盖层未启用:点击下方「启用 PBL 覆盖层」装配 PBL 组件;本页为教师/学生侧页面(首屏预算 ≤1.5s)。</div></div>"
}
},
{
"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": "window.PblOverlayLoader?PblOverlayLoader.start({page:'blueprint_list'}):(function(){var b=String(window.__PBL_API_BASE__||'').replace(/\\/$/,'');var s=document.createElement('script');s.src=b+'/pbl_scense_ext/overlay/pbl_overlay_loader.js';s.async=false;s.onload=function(){window.PblOverlayLoader.start({page:'blueprint_list'})};s.onerror=function(){window.__pblOverlayFail&&window.__pblOverlayFail('PBL_E_NETWORK','覆盖层引导脚本加载失败')};document.head.appendChild(s);})()"
}
}
]
},
{
"widgettype": "Button",
"options": {
"label": "PBL 首页"
},
"binds": [
{
"wid": "self",
"event": "click",
"actiontype": "url",
"options": {
"url": "{{entire_url('/pbl_scense_ext/pbl_overlay/home.ui')}}"
}
}
]
}
]
},
{
"widgettype": "Text",
"options": {
"label": "上下文:URL 携带 session_id / blueprint_id / world_id;覆盖层仅渲染服务端权威状态,不本地预测(Q4)。",
"fontSize": "12px",
"color": "#6B7280",
"marginTop": "10px"
}
}
]
}

View File

@ -0,0 +1,79 @@
{
"widgettype": "VBox",
"options": {
"width": "100%",
"height": "100%",
"padding": "12px",
"backgroundColor": "#0B1020"
},
"subwidgets": [
{
"widgettype": "Text",
"options": {
"label": "PBL 覆盖层 · 证据墙",
"fontSize": "18px",
"fontWeight": "bold",
"color": "#E5E7EB"
}
},
{
"widgettype": "Html",
"id": "pbl_overlay_shell_evidence_wall",
"options": {
"html": "<div data-pbl-page=\"evidence_wall\" data-pbl-overlay-root=\"1\" class=\"pbl-shell\"><div class=\"pbl-shell-hint\">证据墙(evidence_wall)· 覆盖层未启用:点击下方「启用 PBL 覆盖层」装配 PBL 组件;本页为世界内覆盖层(广播渲染预算 ≤500ms)。</div></div>"
}
},
{
"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": "window.PblOverlayLoader?PblOverlayLoader.start({page:'evidence_wall'}):(function(){var b=String(window.__PBL_API_BASE__||'').replace(/\\/$/,'');var s=document.createElement('script');s.src=b+'/pbl_scense_ext/overlay/pbl_overlay_loader.js';s.async=false;s.onload=function(){window.PblOverlayLoader.start({page:'evidence_wall'})};s.onerror=function(){window.__pblOverlayFail&&window.__pblOverlayFail('PBL_E_NETWORK','覆盖层引导脚本加载失败')};document.head.appendChild(s);})()"
}
}
]
},
{
"widgettype": "Button",
"options": {
"label": "PBL 首页"
},
"binds": [
{
"wid": "self",
"event": "click",
"actiontype": "url",
"options": {
"url": "{{entire_url('/pbl_scense_ext/pbl_overlay/home.ui')}}"
}
}
]
}
]
},
{
"widgettype": "Text",
"options": {
"label": "上下文:URL 携带 session_id / blueprint_id / world_id;覆盖层仅渲染服务端权威状态,不本地预测(Q4)。",
"fontSize": "12px",
"color": "#6B7280",
"marginTop": "10px"
}
}
]
}

View File

@ -0,0 +1,79 @@
{
"widgettype": "VBox",
"options": {
"width": "100%",
"height": "100%",
"padding": "12px",
"backgroundColor": "#0B1020"
},
"subwidgets": [
{
"widgettype": "Text",
"options": {
"label": "PBL 覆盖层 · PBL 首页",
"fontSize": "18px",
"fontWeight": "bold",
"color": "#E5E7EB"
}
},
{
"widgettype": "Html",
"id": "pbl_overlay_shell_home",
"options": {
"html": "<div data-pbl-page=\"home\" data-pbl-overlay-root=\"1\" class=\"pbl-shell\"><div class=\"pbl-shell-hint\">PBL 首页(home)· 覆盖层未启用:点击下方「启用 PBL 覆盖层」装配 PBL 组件;本页为教师/学生侧页面(首屏预算 ≤1.5s)。</div></div>"
}
},
{
"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": "window.PblOverlayLoader?PblOverlayLoader.start({page:'home'}):(function(){var b=String(window.__PBL_API_BASE__||'').replace(/\\/$/,'');var s=document.createElement('script');s.src=b+'/pbl_scense_ext/overlay/pbl_overlay_loader.js';s.async=false;s.onload=function(){window.PblOverlayLoader.start({page:'home'})};s.onerror=function(){window.__pblOverlayFail&&window.__pblOverlayFail('PBL_E_NETWORK','覆盖层引导脚本加载失败')};document.head.appendChild(s);})()"
}
}
]
},
{
"widgettype": "Button",
"options": {
"label": "PBL 首页"
},
"binds": [
{
"wid": "self",
"event": "click",
"actiontype": "url",
"options": {
"url": "{{entire_url('/pbl_scense_ext/pbl_overlay/home.ui')}}"
}
}
]
}
]
},
{
"widgettype": "Text",
"options": {
"label": "上下文:URL 携带 session_id / blueprint_id / world_id;覆盖层仅渲染服务端权威状态,不本地预测(Q4)。",
"fontSize": "12px",
"color": "#6B7280",
"marginTop": "10px"
}
}
]
}

View File

@ -0,0 +1,79 @@
{
"widgettype": "VBox",
"options": {
"width": "100%",
"height": "100%",
"padding": "12px",
"backgroundColor": "#0B1020"
},
"subwidgets": [
{
"widgettype": "Text",
"options": {
"label": "PBL 覆盖层 · 驱动问题板",
"fontSize": "18px",
"fontWeight": "bold",
"color": "#E5E7EB"
}
},
{
"widgettype": "Html",
"id": "pbl_overlay_shell_mission_board",
"options": {
"html": "<div data-pbl-page=\"mission_board\" data-pbl-overlay-root=\"1\" class=\"pbl-shell\"><div class=\"pbl-shell-hint\">驱动问题板(mission_board)· 覆盖层未启用:点击下方「启用 PBL 覆盖层」装配 PBL 组件;本页为世界内覆盖层(广播渲染预算 ≤500ms)。</div></div>"
}
},
{
"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": "window.PblOverlayLoader?PblOverlayLoader.start({page:'mission_board'}):(function(){var b=String(window.__PBL_API_BASE__||'').replace(/\\/$/,'');var s=document.createElement('script');s.src=b+'/pbl_scense_ext/overlay/pbl_overlay_loader.js';s.async=false;s.onload=function(){window.PblOverlayLoader.start({page:'mission_board'})};s.onerror=function(){window.__pblOverlayFail&&window.__pblOverlayFail('PBL_E_NETWORK','覆盖层引导脚本加载失败')};document.head.appendChild(s);})()"
}
}
]
},
{
"widgettype": "Button",
"options": {
"label": "PBL 首页"
},
"binds": [
{
"wid": "self",
"event": "click",
"actiontype": "url",
"options": {
"url": "{{entire_url('/pbl_scense_ext/pbl_overlay/home.ui')}}"
}
}
]
}
]
},
{
"widgettype": "Text",
"options": {
"label": "上下文:URL 携带 session_id / blueprint_id / world_id;覆盖层仅渲染服务端权威状态,不本地预测(Q4)。",
"fontSize": "12px",
"color": "#6B7280",
"marginTop": "10px"
}
}
]
}

View File

@ -0,0 +1,79 @@
{
"widgettype": "VBox",
"options": {
"width": "100%",
"height": "100%",
"padding": "12px",
"backgroundColor": "#0B1020"
},
"subwidgets": [
{
"widgettype": "Text",
"options": {
"label": "PBL 覆盖层 · Play 预览",
"fontSize": "18px",
"fontWeight": "bold",
"color": "#E5E7EB"
}
},
{
"widgettype": "Html",
"id": "pbl_overlay_shell_play_preview",
"options": {
"html": "<div data-pbl-page=\"play_preview\" data-pbl-overlay-root=\"1\" class=\"pbl-shell\"><div class=\"pbl-shell-hint\">Play 预览(play_preview)· 覆盖层未启用:点击下方「启用 PBL 覆盖层」装配 PBL 组件;本页为世界内覆盖层(广播渲染预算 ≤500ms)。</div></div>"
}
},
{
"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": "window.PblOverlayLoader?PblOverlayLoader.start({page:'play_preview'}):(function(){var b=String(window.__PBL_API_BASE__||'').replace(/\\/$/,'');var s=document.createElement('script');s.src=b+'/pbl_scense_ext/overlay/pbl_overlay_loader.js';s.async=false;s.onload=function(){window.PblOverlayLoader.start({page:'play_preview'})};s.onerror=function(){window.__pblOverlayFail&&window.__pblOverlayFail('PBL_E_NETWORK','覆盖层引导脚本加载失败')};document.head.appendChild(s);})()"
}
}
]
},
{
"widgettype": "Button",
"options": {
"label": "PBL 首页"
},
"binds": [
{
"wid": "self",
"event": "click",
"actiontype": "url",
"options": {
"url": "{{entire_url('/pbl_scense_ext/pbl_overlay/home.ui')}}"
}
}
]
}
]
},
{
"widgettype": "Text",
"options": {
"label": "上下文:URL 携带 session_id / blueprint_id / world_id;覆盖层仅渲染服务端权威状态,不本地预测(Q4)。",
"fontSize": "12px",
"color": "#6B7280",
"marginTop": "10px"
}
}
]
}

View File

@ -0,0 +1,79 @@
{
"widgettype": "VBox",
"options": {
"width": "100%",
"height": "100%",
"padding": "12px",
"backgroundColor": "#0B1020"
},
"subwidgets": [
{
"widgettype": "Text",
"options": {
"label": "PBL 覆盖层 · 角色面板",
"fontSize": "18px",
"fontWeight": "bold",
"color": "#E5E7EB"
}
},
{
"widgettype": "Html",
"id": "pbl_overlay_shell_role_panel",
"options": {
"html": "<div data-pbl-page=\"role_panel\" data-pbl-overlay-root=\"1\" class=\"pbl-shell\"><div class=\"pbl-shell-hint\">角色面板(role_panel)· 覆盖层未启用:点击下方「启用 PBL 覆盖层」装配 PBL 组件;本页为世界内覆盖层(广播渲染预算 ≤500ms)。</div></div>"
}
},
{
"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": "window.PblOverlayLoader?PblOverlayLoader.start({page:'role_panel'}):(function(){var b=String(window.__PBL_API_BASE__||'').replace(/\\/$/,'');var s=document.createElement('script');s.src=b+'/pbl_scense_ext/overlay/pbl_overlay_loader.js';s.async=false;s.onload=function(){window.PblOverlayLoader.start({page:'role_panel'})};s.onerror=function(){window.__pblOverlayFail&&window.__pblOverlayFail('PBL_E_NETWORK','覆盖层引导脚本加载失败')};document.head.appendChild(s);})()"
}
}
]
},
{
"widgettype": "Button",
"options": {
"label": "PBL 首页"
},
"binds": [
{
"wid": "self",
"event": "click",
"actiontype": "url",
"options": {
"url": "{{entire_url('/pbl_scense_ext/pbl_overlay/home.ui')}}"
}
}
]
}
]
},
{
"widgettype": "Text",
"options": {
"label": "上下文:URL 携带 session_id / blueprint_id / world_id;覆盖层仅渲染服务端权威状态,不本地预测(Q4)。",
"fontSize": "12px",
"color": "#6B7280",
"marginTop": "10px"
}
}
]
}

View File

@ -0,0 +1,79 @@
{
"widgettype": "VBox",
"options": {
"width": "100%",
"height": "100%",
"padding": "12px",
"backgroundColor": "#0B1020"
},
"subwidgets": [
{
"widgettype": "Text",
"options": {
"label": "PBL 覆盖层 · 校验报告(14维)",
"fontSize": "18px",
"fontWeight": "bold",
"color": "#E5E7EB"
}
},
{
"widgettype": "Html",
"id": "pbl_overlay_shell_validation_report",
"options": {
"html": "<div data-pbl-page=\"validation_report\" data-pbl-overlay-root=\"1\" class=\"pbl-shell\"><div class=\"pbl-shell-hint\">校验报告(14维)(validation_report)· 覆盖层未启用:点击下方「启用 PBL 覆盖层」装配 PBL 组件;本页为教师/学生侧页面(首屏预算 ≤1.5s)。</div></div>"
}
},
{
"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": "window.PblOverlayLoader?PblOverlayLoader.start({page:'validation_report'}):(function(){var b=String(window.__PBL_API_BASE__||'').replace(/\\/$/,'');var s=document.createElement('script');s.src=b+'/pbl_scense_ext/overlay/pbl_overlay_loader.js';s.async=false;s.onload=function(){window.PblOverlayLoader.start({page:'validation_report'})};s.onerror=function(){window.__pblOverlayFail&&window.__pblOverlayFail('PBL_E_NETWORK','覆盖层引导脚本加载失败')};document.head.appendChild(s);})()"
}
}
]
},
{
"widgettype": "Button",
"options": {
"label": "PBL 首页"
},
"binds": [
{
"wid": "self",
"event": "click",
"actiontype": "url",
"options": {
"url": "{{entire_url('/pbl_scense_ext/pbl_overlay/home.ui')}}"
}
}
]
}
]
},
{
"widgettype": "Text",
"options": {
"label": "上下文:URL 携带 session_id / blueprint_id / world_id;覆盖层仅渲染服务端权威状态,不本地预测(Q4)。",
"fontSize": "12px",
"color": "#6B7280",
"marginTop": "10px"
}
}
]
}