feat(pbl_scense_ext): 初始提交 - pbls 项目模块代码入库
This commit is contained in:
parent
4551785f38
commit
e570dfacde
12
.gitignore
vendored
Normal file
12
.gitignore
vendored
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
__pycache__/
|
||||||
|
*.pyc
|
||||||
|
*.pyo
|
||||||
|
.pytest_cache/
|
||||||
|
.coverage
|
||||||
|
.DS_Store
|
||||||
|
*.log
|
||||||
|
node_modules/
|
||||||
|
dist/
|
||||||
|
build/
|
||||||
|
.venv/
|
||||||
|
venv/
|
||||||
146
json/scense_ext_pbl_playtest_feedback.json
Normal file
146
json/scense_ext_pbl_playtest_feedback.json
Normal file
@ -0,0 +1,146 @@
|
|||||||
|
{
|
||||||
|
"tblname": "pbl_playtest_feedback",
|
||||||
|
"alias": "pbl_scense_ext",
|
||||||
|
"browserfields": {
|
||||||
|
"id": {
|
||||||
|
"label": "ID",
|
||||||
|
"list": true,
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"tenant_id": {
|
||||||
|
"label": "租户ID",
|
||||||
|
"list": true,
|
||||||
|
"type": "text"
|
||||||
|
},
|
||||||
|
"session_id": {
|
||||||
|
"label": "会话",
|
||||||
|
"type": "number",
|
||||||
|
"list": true
|
||||||
|
},
|
||||||
|
"user_id": {
|
||||||
|
"label": "用户",
|
||||||
|
"type": "text",
|
||||||
|
"list": true
|
||||||
|
},
|
||||||
|
"blueprint_id": {
|
||||||
|
"label": "蓝图",
|
||||||
|
"type": "number",
|
||||||
|
"list": true
|
||||||
|
},
|
||||||
|
"feedback_type": {
|
||||||
|
"label": "类型",
|
||||||
|
"type": "select",
|
||||||
|
"list": true
|
||||||
|
},
|
||||||
|
"rating": {
|
||||||
|
"label": "评分",
|
||||||
|
"type": "number",
|
||||||
|
"list": true
|
||||||
|
},
|
||||||
|
"content_txt": {
|
||||||
|
"label": "内容",
|
||||||
|
"type": "text",
|
||||||
|
"list": true
|
||||||
|
},
|
||||||
|
"context_json": {
|
||||||
|
"label": "上下文",
|
||||||
|
"type": "text",
|
||||||
|
"list": true
|
||||||
|
},
|
||||||
|
"handled": {
|
||||||
|
"label": "已处理",
|
||||||
|
"type": "checkbox",
|
||||||
|
"list": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"params": {
|
||||||
|
"browserfields": {
|
||||||
|
"id": {
|
||||||
|
"label": "ID",
|
||||||
|
"list": true,
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"tenant_id": {
|
||||||
|
"label": "租户ID",
|
||||||
|
"list": true,
|
||||||
|
"type": "text"
|
||||||
|
},
|
||||||
|
"session_id": {
|
||||||
|
"label": "会话",
|
||||||
|
"type": "number",
|
||||||
|
"list": true
|
||||||
|
},
|
||||||
|
"user_id": {
|
||||||
|
"label": "用户",
|
||||||
|
"type": "text",
|
||||||
|
"list": true
|
||||||
|
},
|
||||||
|
"blueprint_id": {
|
||||||
|
"label": "蓝图",
|
||||||
|
"type": "number",
|
||||||
|
"list": true
|
||||||
|
},
|
||||||
|
"feedback_type": {
|
||||||
|
"label": "类型",
|
||||||
|
"type": "select",
|
||||||
|
"list": true
|
||||||
|
},
|
||||||
|
"rating": {
|
||||||
|
"label": "评分",
|
||||||
|
"type": "number",
|
||||||
|
"list": true
|
||||||
|
},
|
||||||
|
"content_txt": {
|
||||||
|
"label": "内容",
|
||||||
|
"type": "text",
|
||||||
|
"list": true
|
||||||
|
},
|
||||||
|
"context_json": {
|
||||||
|
"label": "上下文",
|
||||||
|
"type": "text",
|
||||||
|
"list": true
|
||||||
|
},
|
||||||
|
"handled": {
|
||||||
|
"label": "已处理",
|
||||||
|
"type": "checkbox",
|
||||||
|
"list": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"editable": {
|
||||||
|
"session_id": {
|
||||||
|
"label": "会话",
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"user_id": {
|
||||||
|
"label": "用户",
|
||||||
|
"type": "text"
|
||||||
|
},
|
||||||
|
"blueprint_id": {
|
||||||
|
"label": "蓝图",
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"feedback_type": {
|
||||||
|
"label": "类型",
|
||||||
|
"type": "select"
|
||||||
|
},
|
||||||
|
"rating": {
|
||||||
|
"label": "评分",
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"content_txt": {
|
||||||
|
"label": "内容",
|
||||||
|
"type": "text"
|
||||||
|
},
|
||||||
|
"context_json": {
|
||||||
|
"label": "上下文",
|
||||||
|
"type": "text"
|
||||||
|
},
|
||||||
|
"handled": {
|
||||||
|
"label": "已处理",
|
||||||
|
"type": "checkbox"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"order_by": "id DESC",
|
||||||
|
"page_size": 20
|
||||||
|
}
|
||||||
|
}
|
||||||
146
json/scense_ext_pbl_session_member.json
Normal file
146
json/scense_ext_pbl_session_member.json
Normal file
@ -0,0 +1,146 @@
|
|||||||
|
{
|
||||||
|
"tblname": "pbl_session_member",
|
||||||
|
"alias": "pbl_scense_ext",
|
||||||
|
"browserfields": {
|
||||||
|
"id": {
|
||||||
|
"label": "ID",
|
||||||
|
"list": true,
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"tenant_id": {
|
||||||
|
"label": "租户ID",
|
||||||
|
"list": true,
|
||||||
|
"type": "text"
|
||||||
|
},
|
||||||
|
"session_id": {
|
||||||
|
"label": "会话",
|
||||||
|
"type": "number",
|
||||||
|
"list": true
|
||||||
|
},
|
||||||
|
"user_id": {
|
||||||
|
"label": "用户",
|
||||||
|
"type": "text",
|
||||||
|
"list": true
|
||||||
|
},
|
||||||
|
"role_code": {
|
||||||
|
"label": "角色",
|
||||||
|
"type": "select",
|
||||||
|
"list": true
|
||||||
|
},
|
||||||
|
"team_id": {
|
||||||
|
"label": "团队",
|
||||||
|
"type": "number",
|
||||||
|
"list": true
|
||||||
|
},
|
||||||
|
"joined_at": {
|
||||||
|
"label": "加入时间",
|
||||||
|
"type": "text",
|
||||||
|
"list": true
|
||||||
|
},
|
||||||
|
"last_seen_at": {
|
||||||
|
"label": "心跳",
|
||||||
|
"type": "text",
|
||||||
|
"list": true
|
||||||
|
},
|
||||||
|
"state_version": {
|
||||||
|
"label": "已见状态版本",
|
||||||
|
"type": "number",
|
||||||
|
"list": true
|
||||||
|
},
|
||||||
|
"status": {
|
||||||
|
"label": "状态",
|
||||||
|
"type": "select",
|
||||||
|
"list": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"params": {
|
||||||
|
"browserfields": {
|
||||||
|
"id": {
|
||||||
|
"label": "ID",
|
||||||
|
"list": true,
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"tenant_id": {
|
||||||
|
"label": "租户ID",
|
||||||
|
"list": true,
|
||||||
|
"type": "text"
|
||||||
|
},
|
||||||
|
"session_id": {
|
||||||
|
"label": "会话",
|
||||||
|
"type": "number",
|
||||||
|
"list": true
|
||||||
|
},
|
||||||
|
"user_id": {
|
||||||
|
"label": "用户",
|
||||||
|
"type": "text",
|
||||||
|
"list": true
|
||||||
|
},
|
||||||
|
"role_code": {
|
||||||
|
"label": "角色",
|
||||||
|
"type": "select",
|
||||||
|
"list": true
|
||||||
|
},
|
||||||
|
"team_id": {
|
||||||
|
"label": "团队",
|
||||||
|
"type": "number",
|
||||||
|
"list": true
|
||||||
|
},
|
||||||
|
"joined_at": {
|
||||||
|
"label": "加入时间",
|
||||||
|
"type": "text",
|
||||||
|
"list": true
|
||||||
|
},
|
||||||
|
"last_seen_at": {
|
||||||
|
"label": "心跳",
|
||||||
|
"type": "text",
|
||||||
|
"list": true
|
||||||
|
},
|
||||||
|
"state_version": {
|
||||||
|
"label": "已见状态版本",
|
||||||
|
"type": "number",
|
||||||
|
"list": true
|
||||||
|
},
|
||||||
|
"status": {
|
||||||
|
"label": "状态",
|
||||||
|
"type": "select",
|
||||||
|
"list": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"editable": {
|
||||||
|
"session_id": {
|
||||||
|
"label": "会话",
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"user_id": {
|
||||||
|
"label": "用户",
|
||||||
|
"type": "text"
|
||||||
|
},
|
||||||
|
"role_code": {
|
||||||
|
"label": "角色",
|
||||||
|
"type": "select"
|
||||||
|
},
|
||||||
|
"team_id": {
|
||||||
|
"label": "团队",
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"joined_at": {
|
||||||
|
"label": "加入时间",
|
||||||
|
"type": "text"
|
||||||
|
},
|
||||||
|
"last_seen_at": {
|
||||||
|
"label": "心跳",
|
||||||
|
"type": "text"
|
||||||
|
},
|
||||||
|
"state_version": {
|
||||||
|
"label": "已见状态版本",
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"status": {
|
||||||
|
"label": "状态",
|
||||||
|
"type": "select"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"order_by": "id DESC",
|
||||||
|
"page_size": 20
|
||||||
|
}
|
||||||
|
}
|
||||||
146
json/scense_ext_pbl_ui_page_registry.json
Normal file
146
json/scense_ext_pbl_ui_page_registry.json
Normal file
@ -0,0 +1,146 @@
|
|||||||
|
{
|
||||||
|
"tblname": "pbl_ui_page_registry",
|
||||||
|
"alias": "pbl_scense_ext",
|
||||||
|
"browserfields": {
|
||||||
|
"id": {
|
||||||
|
"label": "ID",
|
||||||
|
"list": true,
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"tenant_id": {
|
||||||
|
"label": "租户ID",
|
||||||
|
"list": true,
|
||||||
|
"type": "text"
|
||||||
|
},
|
||||||
|
"page_key": {
|
||||||
|
"label": "页面键",
|
||||||
|
"type": "select",
|
||||||
|
"list": true
|
||||||
|
},
|
||||||
|
"title": {
|
||||||
|
"label": "标题",
|
||||||
|
"type": "select",
|
||||||
|
"list": true
|
||||||
|
},
|
||||||
|
"route": {
|
||||||
|
"label": "路由",
|
||||||
|
"type": "select",
|
||||||
|
"list": true
|
||||||
|
},
|
||||||
|
"widget_type": {
|
||||||
|
"label": "渲染组件",
|
||||||
|
"type": "select",
|
||||||
|
"list": true
|
||||||
|
},
|
||||||
|
"requires_role": {
|
||||||
|
"label": "所需角色",
|
||||||
|
"type": "select",
|
||||||
|
"list": true
|
||||||
|
},
|
||||||
|
"budget_ms": {
|
||||||
|
"label": "渲染预算",
|
||||||
|
"type": "number",
|
||||||
|
"list": true
|
||||||
|
},
|
||||||
|
"enabled": {
|
||||||
|
"label": "启用",
|
||||||
|
"type": "checkbox",
|
||||||
|
"list": true
|
||||||
|
},
|
||||||
|
"meta_json": {
|
||||||
|
"label": "元数据",
|
||||||
|
"type": "text",
|
||||||
|
"list": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"params": {
|
||||||
|
"browserfields": {
|
||||||
|
"id": {
|
||||||
|
"label": "ID",
|
||||||
|
"list": true,
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"tenant_id": {
|
||||||
|
"label": "租户ID",
|
||||||
|
"list": true,
|
||||||
|
"type": "text"
|
||||||
|
},
|
||||||
|
"page_key": {
|
||||||
|
"label": "页面键",
|
||||||
|
"type": "select",
|
||||||
|
"list": true
|
||||||
|
},
|
||||||
|
"title": {
|
||||||
|
"label": "标题",
|
||||||
|
"type": "select",
|
||||||
|
"list": true
|
||||||
|
},
|
||||||
|
"route": {
|
||||||
|
"label": "路由",
|
||||||
|
"type": "select",
|
||||||
|
"list": true
|
||||||
|
},
|
||||||
|
"widget_type": {
|
||||||
|
"label": "渲染组件",
|
||||||
|
"type": "select",
|
||||||
|
"list": true
|
||||||
|
},
|
||||||
|
"requires_role": {
|
||||||
|
"label": "所需角色",
|
||||||
|
"type": "select",
|
||||||
|
"list": true
|
||||||
|
},
|
||||||
|
"budget_ms": {
|
||||||
|
"label": "渲染预算",
|
||||||
|
"type": "number",
|
||||||
|
"list": true
|
||||||
|
},
|
||||||
|
"enabled": {
|
||||||
|
"label": "启用",
|
||||||
|
"type": "checkbox",
|
||||||
|
"list": true
|
||||||
|
},
|
||||||
|
"meta_json": {
|
||||||
|
"label": "元数据",
|
||||||
|
"type": "text",
|
||||||
|
"list": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"editable": {
|
||||||
|
"page_key": {
|
||||||
|
"label": "页面键",
|
||||||
|
"type": "select"
|
||||||
|
},
|
||||||
|
"title": {
|
||||||
|
"label": "标题",
|
||||||
|
"type": "select"
|
||||||
|
},
|
||||||
|
"route": {
|
||||||
|
"label": "路由",
|
||||||
|
"type": "select"
|
||||||
|
},
|
||||||
|
"widget_type": {
|
||||||
|
"label": "渲染组件",
|
||||||
|
"type": "select"
|
||||||
|
},
|
||||||
|
"requires_role": {
|
||||||
|
"label": "所需角色",
|
||||||
|
"type": "select"
|
||||||
|
},
|
||||||
|
"budget_ms": {
|
||||||
|
"label": "渲染预算",
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"enabled": {
|
||||||
|
"label": "启用",
|
||||||
|
"type": "checkbox"
|
||||||
|
},
|
||||||
|
"meta_json": {
|
||||||
|
"label": "元数据",
|
||||||
|
"type": "text"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"order_by": "id DESC",
|
||||||
|
"page_size": 20
|
||||||
|
}
|
||||||
|
}
|
||||||
110
models/pbl_playtest_feedback.json
Normal file
110
models/pbl_playtest_feedback.json
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
{
|
||||||
|
"summary": [
|
||||||
|
{
|
||||||
|
"name": "pbl_playtest_feedback",
|
||||||
|
"comment": "Playtest 反馈/反思(reflection 走证据链外的轻量表)",
|
||||||
|
"module": "pbl_scense_ext",
|
||||||
|
"engine": "mariadb",
|
||||||
|
"charset": "utf8mb4",
|
||||||
|
"tenant_scoped": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"name": "tenant_id",
|
||||||
|
"comment": "租户ID(强制打头)",
|
||||||
|
"null": false,
|
||||||
|
"type": "str",
|
||||||
|
"length": 32
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "id",
|
||||||
|
"comment": "主键",
|
||||||
|
"null": false,
|
||||||
|
"type": "int",
|
||||||
|
"length": 20,
|
||||||
|
"unsigned": true,
|
||||||
|
"auto_increment": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "session_id",
|
||||||
|
"comment": "会话",
|
||||||
|
"null": false,
|
||||||
|
"type": "bigint"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "user_id",
|
||||||
|
"comment": "用户",
|
||||||
|
"null": false,
|
||||||
|
"type": "str",
|
||||||
|
"length": 32
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "blueprint_id",
|
||||||
|
"comment": "蓝图",
|
||||||
|
"null": false,
|
||||||
|
"type": "bigint"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "feedback_type",
|
||||||
|
"comment": "类型",
|
||||||
|
"null": false,
|
||||||
|
"type": "str",
|
||||||
|
"length": 64
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "rating",
|
||||||
|
"comment": "评分",
|
||||||
|
"null": false,
|
||||||
|
"type": "int"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "content_txt",
|
||||||
|
"comment": "内容",
|
||||||
|
"null": true,
|
||||||
|
"type": "text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "context_json",
|
||||||
|
"comment": "上下文",
|
||||||
|
"null": true,
|
||||||
|
"type": "json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "handled",
|
||||||
|
"comment": "已处理",
|
||||||
|
"null": false,
|
||||||
|
"type": "bool"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "created_at",
|
||||||
|
"comment": "创建时间",
|
||||||
|
"null": false,
|
||||||
|
"type": "datetime"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "updated_at",
|
||||||
|
"comment": "更新时间",
|
||||||
|
"null": false,
|
||||||
|
"type": "datetime"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"indexes": [
|
||||||
|
{
|
||||||
|
"name": "idx_pf_session",
|
||||||
|
"unique": false,
|
||||||
|
"fields": [
|
||||||
|
"tenant_id",
|
||||||
|
"session_id"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "PRIMARY",
|
||||||
|
"unique": true,
|
||||||
|
"fields": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"codes": []
|
||||||
|
}
|
||||||
112
models/pbl_session_member.json
Normal file
112
models/pbl_session_member.json
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
{
|
||||||
|
"summary": [
|
||||||
|
{
|
||||||
|
"name": "pbl_session_member",
|
||||||
|
"comment": "会话成员(复用 world_session,不污染基表)",
|
||||||
|
"module": "pbl_scense_ext",
|
||||||
|
"engine": "mariadb",
|
||||||
|
"charset": "utf8mb4",
|
||||||
|
"tenant_scoped": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"name": "tenant_id",
|
||||||
|
"comment": "租户ID(强制打头)",
|
||||||
|
"null": false,
|
||||||
|
"type": "str",
|
||||||
|
"length": 32
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "id",
|
||||||
|
"comment": "主键",
|
||||||
|
"null": false,
|
||||||
|
"type": "int",
|
||||||
|
"length": 20,
|
||||||
|
"unsigned": true,
|
||||||
|
"auto_increment": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "session_id",
|
||||||
|
"comment": "会话",
|
||||||
|
"null": false,
|
||||||
|
"type": "bigint"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "user_id",
|
||||||
|
"comment": "用户",
|
||||||
|
"null": false,
|
||||||
|
"type": "str",
|
||||||
|
"length": 32
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "role_code",
|
||||||
|
"comment": "角色",
|
||||||
|
"null": false,
|
||||||
|
"type": "str",
|
||||||
|
"length": 64
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "team_id",
|
||||||
|
"comment": "团队",
|
||||||
|
"null": false,
|
||||||
|
"type": "bigint"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "joined_at",
|
||||||
|
"comment": "加入时间",
|
||||||
|
"null": true,
|
||||||
|
"type": "datetime"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "last_seen_at",
|
||||||
|
"comment": "心跳",
|
||||||
|
"null": true,
|
||||||
|
"type": "datetime"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "state_version",
|
||||||
|
"comment": "已见状态版本",
|
||||||
|
"null": false,
|
||||||
|
"type": "bigint"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "status",
|
||||||
|
"comment": "状态",
|
||||||
|
"null": false,
|
||||||
|
"type": "str",
|
||||||
|
"length": 64
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "created_at",
|
||||||
|
"comment": "创建时间",
|
||||||
|
"null": false,
|
||||||
|
"type": "datetime"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "updated_at",
|
||||||
|
"comment": "更新时间",
|
||||||
|
"null": false,
|
||||||
|
"type": "datetime"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"indexes": [
|
||||||
|
{
|
||||||
|
"name": "uk_sm",
|
||||||
|
"unique": true,
|
||||||
|
"fields": [
|
||||||
|
"tenant_id",
|
||||||
|
"session_id",
|
||||||
|
"user_id"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "PRIMARY",
|
||||||
|
"unique": true,
|
||||||
|
"fields": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"codes": []
|
||||||
|
}
|
||||||
113
models/pbl_ui_page_registry.json
Normal file
113
models/pbl_ui_page_registry.json
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
{
|
||||||
|
"summary": [
|
||||||
|
{
|
||||||
|
"name": "pbl_ui_page_registry",
|
||||||
|
"comment": "前端覆盖层页面注册(12 页面 + 时延预算)",
|
||||||
|
"module": "pbl_scense_ext",
|
||||||
|
"engine": "mariadb",
|
||||||
|
"charset": "utf8mb4",
|
||||||
|
"tenant_scoped": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"name": "tenant_id",
|
||||||
|
"comment": "租户ID(强制打头)",
|
||||||
|
"null": false,
|
||||||
|
"type": "str",
|
||||||
|
"length": 32
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "id",
|
||||||
|
"comment": "主键",
|
||||||
|
"null": false,
|
||||||
|
"type": "int",
|
||||||
|
"length": 20,
|
||||||
|
"unsigned": true,
|
||||||
|
"auto_increment": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "page_key",
|
||||||
|
"comment": "页面键",
|
||||||
|
"null": false,
|
||||||
|
"type": "str",
|
||||||
|
"length": 64
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "title",
|
||||||
|
"comment": "标题",
|
||||||
|
"null": false,
|
||||||
|
"type": "str",
|
||||||
|
"length": 128
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "route",
|
||||||
|
"comment": "路由",
|
||||||
|
"null": false,
|
||||||
|
"type": "str",
|
||||||
|
"length": 64
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "widget_type",
|
||||||
|
"comment": "渲染组件",
|
||||||
|
"null": false,
|
||||||
|
"type": "str",
|
||||||
|
"length": 64
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "requires_role",
|
||||||
|
"comment": "所需角色",
|
||||||
|
"null": false,
|
||||||
|
"type": "str",
|
||||||
|
"length": 64
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "budget_ms",
|
||||||
|
"comment": "渲染预算",
|
||||||
|
"null": false,
|
||||||
|
"type": "int"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "enabled",
|
||||||
|
"comment": "启用",
|
||||||
|
"null": false,
|
||||||
|
"type": "bool"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "meta_json",
|
||||||
|
"comment": "元数据",
|
||||||
|
"null": true,
|
||||||
|
"type": "json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "created_at",
|
||||||
|
"comment": "创建时间",
|
||||||
|
"null": false,
|
||||||
|
"type": "datetime"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "updated_at",
|
||||||
|
"comment": "更新时间",
|
||||||
|
"null": false,
|
||||||
|
"type": "datetime"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"indexes": [
|
||||||
|
{
|
||||||
|
"name": "uk_upr_key",
|
||||||
|
"unique": true,
|
||||||
|
"fields": [
|
||||||
|
"tenant_id",
|
||||||
|
"page_key"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "PRIMARY",
|
||||||
|
"unique": true,
|
||||||
|
"fields": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"codes": []
|
||||||
|
}
|
||||||
23
pbl_scense_ext/__init__.py
Normal file
23
pbl_scense_ext/__init__.py
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""pbl_scense_ext —— scense/scense_game 前端覆盖层与页面注册(M9)
|
||||||
|
|
||||||
|
注册三处同步之 ②:必须导出 init.py 里的全部契约函数,漏一行 .dspy 调用即 NameError。
|
||||||
|
"""
|
||||||
|
from pbl_scense_ext.init import load_pbl_scense_ext
|
||||||
|
from pbl_scense_ext.api import (
|
||||||
|
pbl_session_context_get,
|
||||||
|
pbl_page_registry_list,
|
||||||
|
pbl_playtest_feedback_save,
|
||||||
|
pbl_overlay_bootstrap,
|
||||||
|
|
||||||
|
)
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
'load_pbl_scense_ext',
|
||||||
|
'pbl_session_context_get',
|
||||||
|
'pbl_page_registry_list',
|
||||||
|
'pbl_playtest_feedback_save',
|
||||||
|
'pbl_overlay_bootstrap',
|
||||||
|
|
||||||
|
]
|
||||||
132
pbl_scense_ext/api.py
Normal file
132
pbl_scense_ext/api.py
Normal file
@ -0,0 +1,132 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""pbl_scense_ext.api —— scense/scense_game 前端覆盖层服务端侧(M9)。
|
||||||
|
|
||||||
|
不改渲染主循环:本模块只提供 ① 覆盖层启动上下文(bootstrap)、② 页面注册表(12 页面 +
|
||||||
|
渲染时延预算)、③ Playtest 反馈/反思落库。渲染由基座 scense 的 BricksUIProcessor +
|
||||||
|
wwwroot/pbl_overlay/*.ui 完成,JS 侧只做 DOM 覆盖层挂载(见 apps/pbls/wwwroot/pbl_overlay)。
|
||||||
|
"""
|
||||||
|
import json
|
||||||
|
import time
|
||||||
|
|
||||||
|
from pbl_common.api import (PblError, actor_id, crud, json_dump, now_str, sql_exec,
|
||||||
|
sql_rows, tenant_id)
|
||||||
|
|
||||||
|
# 12 页面(ui-design.md §页面清单),budget_ms 为渲染预算(前端 Performance API 实测)
|
||||||
|
PAGES = [
|
||||||
|
('pbl_home', 'PBL 首页', '/pbl_overlay/home.ui', 'card_grid', 'logined', 1500),
|
||||||
|
('pbl_blueprint_list', '蓝图列表', '/pbl_overlay/blueprint_list.ui', 'data_table', 'teacher', 1500),
|
||||||
|
('pbl_blueprint_editor', '蓝图编辑器', '/pbl_overlay/blueprint_editor.ui', 'form_tree', 'teacher', 1500),
|
||||||
|
('pbl_validation_report', '校验报告(14维)', '/pbl_overlay/validation_report.ui', 'score_panel', 'teacher', 1500),
|
||||||
|
('pbl_agent_console', 'Agent 控制台', '/pbl_overlay/agent_console.ui', 'chat_panel', 'teacher', 1500),
|
||||||
|
('pbl_approval_center', '人工审批中心', '/pbl_overlay/approval_center.ui', 'approval_list', 'teacher', 1500),
|
||||||
|
('pbl_play_preview', 'Play 预览', '/pbl_overlay/play_preview.ui', 'world_overlay', 'logined', 500),
|
||||||
|
('pbl_mission_board', '驱动问题板', '/pbl_overlay/mission_board.ui', 'mission_board', 'logined', 500),
|
||||||
|
('pbl_role_panel', '角色面板', '/pbl_overlay/role_panel.ui', 'role_cards', 'logined', 500),
|
||||||
|
('pbl_artifact_studio', '产出物工坊', '/pbl_overlay/artifact_studio.ui', 'editor_panel', 'logined', 500),
|
||||||
|
('pbl_evidence_wall', '证据墙', '/pbl_overlay/evidence_wall.ui', 'evidence_grid', 'logined', 500),
|
||||||
|
('pbl_assessment_report', '评估报告', '/pbl_overlay/assessment_report.ui', 'rubric_report', 'teacher', 1500),
|
||||||
|
]
|
||||||
|
WORLD_PAGES = {p[0] for p in PAGES if p[4] == 500}
|
||||||
|
|
||||||
|
|
||||||
|
def _loads(v, d=None):
|
||||||
|
if isinstance(v, (dict, list)):
|
||||||
|
return v
|
||||||
|
try:
|
||||||
|
return json.loads(v) if v else (d or {})
|
||||||
|
except ValueError:
|
||||||
|
return d or {}
|
||||||
|
|
||||||
|
|
||||||
|
async def _tid():
|
||||||
|
return await tenant_id()
|
||||||
|
|
||||||
|
|
||||||
|
async def pbl_page_registry_list(**kw):
|
||||||
|
"""页面注册表:首次访问自动登记,前端按 route 挂载覆盖层组件。"""
|
||||||
|
tid = await _tid()
|
||||||
|
sc = crud('pbl_ui_page_registry', 'pbl', ['page_key', 'title', 'route', 'widget_type',
|
||||||
|
'requires_role', 'budget_ms', 'enabled',
|
||||||
|
'meta_json'])
|
||||||
|
for key, title, route, widget, role, budget in PAGES:
|
||||||
|
await sc['upsert'](['page_key'], page_key=key, title=title, route=route,
|
||||||
|
widget_type=widget, requires_role=role, budget_ms=budget,
|
||||||
|
enabled=1, meta_json=json_dump({'in_world': key in WORLD_PAGES}))
|
||||||
|
r = await sc['list'](**kw)
|
||||||
|
r['page_count'] = len(r['data'])
|
||||||
|
r['budgets'] = {'world_overlay_ms': 500, 'first_screen_ms': 1500}
|
||||||
|
return r
|
||||||
|
|
||||||
|
|
||||||
|
async def pbl_overlay_bootstrap(**kw):
|
||||||
|
"""覆盖层启动上下文:一次请求拿齐 会话/角色/团队/Mission/页面注册,避免串行多次往返。"""
|
||||||
|
t0 = time.time()
|
||||||
|
tid = await _tid()
|
||||||
|
user = kw.get('user_id') or await actor_id()
|
||||||
|
session_id = kw.get('session_id')
|
||||||
|
ctx = {'ok': True, 'tenant': tid, 'user_id': user, 'session_id': session_id,
|
||||||
|
'server_time': now_str()}
|
||||||
|
if session_id:
|
||||||
|
mem = await sql_rows('SELECT * FROM `pbl_session_member` WHERE `tenant_id` = ${t}$'
|
||||||
|
' AND `session_id` = ${s}$ AND `user_id` = ${u}$ LIMIT 1',
|
||||||
|
{'t': tid, 's': session_id, 'u': user}, 'pbl')
|
||||||
|
ctx['membership'] = mem[0] if mem else None
|
||||||
|
ctx['state_version'] = int(mem[0]['state_version'] or 0) if mem else 0
|
||||||
|
teams = await sql_rows('SELECT * FROM `pbl_team` WHERE `tenant_id` = ${t}$'
|
||||||
|
' AND `session_id` = ${s}$ LIMIT 8', {'t': tid, 's': session_id},
|
||||||
|
'pbl')
|
||||||
|
for t2 in teams:
|
||||||
|
t2['members_json'] = _loads(t2.get('members_json'), [])
|
||||||
|
ctx['teams'] = teams
|
||||||
|
ev = await sql_rows('SELECT `event_type`,`payload_json`,`seq` FROM `pbl_runtime_event`'
|
||||||
|
' WHERE `tenant_id` = ${t}$ AND `session_id` = ${s}$'
|
||||||
|
' ORDER BY `seq` DESC LIMIT ${n}$',
|
||||||
|
{'t': tid, 's': session_id, 'n': int(kw.get('recent_events') or 20)},
|
||||||
|
'pbl')
|
||||||
|
for e in ev:
|
||||||
|
e['payload_json'] = _loads(e.get('payload_json'), {})
|
||||||
|
ctx['recent_events'] = list(reversed(ev))
|
||||||
|
pages = await sql_rows('SELECT `page_key`,`title`,`route`,`widget_type`,`requires_role`,'
|
||||||
|
'`budget_ms` FROM `pbl_ui_page_registry` WHERE `tenant_id` = ${t}$'
|
||||||
|
' AND `enabled` = 1 ORDER BY `page_key`', {'t': tid}, 'pbl')
|
||||||
|
ctx['pages'] = pages
|
||||||
|
ctx['unlocked_missions'] = [e['payload_json'].get('mission') for e in ctx.get(
|
||||||
|
'recent_events', []) if e['event_type'] == 'pbl.unlockMission']
|
||||||
|
ctx['latency_ms'] = int((time.time() - t0) * 1000)
|
||||||
|
ctx['budget_ms'] = 1500
|
||||||
|
ctx['within_budget'] = ctx['latency_ms'] <= 1500
|
||||||
|
return ctx
|
||||||
|
|
||||||
|
|
||||||
|
async def pbl_session_context_get(**kw):
|
||||||
|
"""只读会话上下文(供覆盖层角色/团队面板;无任何写路径)。"""
|
||||||
|
tid = await _tid()
|
||||||
|
session_id = kw.get('session_id')
|
||||||
|
members = await sql_rows('SELECT `user_id`,`role_code`,`team_id`,`state_version`,`status`'
|
||||||
|
' FROM `pbl_session_member` WHERE `tenant_id` = ${t}$'
|
||||||
|
' AND `session_id` = ${s}$', {'t': tid, 's': session_id}, 'pbl')
|
||||||
|
return {'ok': True, 'members': members, 'count': len(members), 'max_members': 8,
|
||||||
|
'authority': 'server'}
|
||||||
|
|
||||||
|
|
||||||
|
async def pbl_playtest_feedback_save(**kw):
|
||||||
|
ftype = kw.get('feedback_type') or 'feedback'
|
||||||
|
if ftype not in ('feedback', 'reflection'):
|
||||||
|
raise PblError('PBL_FEEDBACK_TYPE_INVALID', 'feedback_type 须为 feedback/reflection')
|
||||||
|
sc = crud('pbl_playtest_feedback', 'pbl', ['session_id', 'user_id', 'blueprint_id',
|
||||||
|
'feedback_type', 'rating', 'content_txt',
|
||||||
|
'context_json', 'handled'])
|
||||||
|
payload = dict(kw)
|
||||||
|
payload['user_id'] = kw.get('user_id') or await actor_id()
|
||||||
|
payload['context_json'] = json_dump(kw.get('context_json'))
|
||||||
|
if ftype == 'reflection':
|
||||||
|
# 反思同时进证据链(第19章四类证据之一),由 pbl_evidence 幂等落库
|
||||||
|
from pbl_evidence.api import pbl_evidence_collect
|
||||||
|
r = await pbl_evidence_collect(
|
||||||
|
evidence_type='reflection', session_id=kw.get('session_id') or 0,
|
||||||
|
learner_id=payload['user_id'], blueprint_id=kw.get('blueprint_id'),
|
||||||
|
source_event_id='refl:%s:%s' % (payload['user_id'], now_str().replace(' ', '')),
|
||||||
|
payload_json={'content': kw.get('content_txt'), 'rating': kw.get('rating')})
|
||||||
|
payload.setdefault('handled', 1 if r.get('ok') else 0)
|
||||||
|
return await sc['create'](**payload)
|
||||||
25
pbl_scense_ext/init.py
Normal file
25
pbl_scense_ext/init.py
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""`load_pbl_scense_ext()` —— pbl_scense_ext 模块唯一挂载入口。
|
||||||
|
|
||||||
|
注册三处同步之 ③:env.<契约名> = <契约名>(① 定义在 api.py,② 导出在 __init__.py)。
|
||||||
|
"""
|
||||||
|
from ahserver.serverenv import ServerEnv
|
||||||
|
|
||||||
|
from pbl_scense_ext.api import (
|
||||||
|
pbl_session_context_get,
|
||||||
|
pbl_page_registry_list,
|
||||||
|
pbl_playtest_feedback_save,
|
||||||
|
pbl_overlay_bootstrap,
|
||||||
|
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def load_pbl_scense_ext():
|
||||||
|
env = ServerEnv()
|
||||||
|
env.pbl_session_context_get = pbl_session_context_get
|
||||||
|
env.pbl_page_registry_list = pbl_page_registry_list
|
||||||
|
env.pbl_playtest_feedback_save = pbl_playtest_feedback_save
|
||||||
|
env.pbl_overlay_bootstrap = pbl_overlay_bootstrap
|
||||||
|
|
||||||
|
return 'pbl_scense_ext'
|
||||||
13
pyproject.toml
Normal file
13
pyproject.toml
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
[project]
|
||||||
|
name = "pbl_scense_ext"
|
||||||
|
version = "0.1.0"
|
||||||
|
description = "scense/scense_game 前端覆盖层与页面注册(M9)"
|
||||||
|
requires-python = ">=3.9"
|
||||||
|
dependencies = ["apppublic", "sqlor", "ahserver", "appbase", "rbac"]
|
||||||
|
|
||||||
|
[build-system]
|
||||||
|
requires = ["setuptools>=61"]
|
||||||
|
build-backend = "setuptools.build_meta"
|
||||||
|
|
||||||
|
[tool.setuptools]
|
||||||
|
packages = ["pbl_scense_ext"]
|
||||||
43
scripts/load_path.py
Normal file
43
scripts/load_path.py
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""pbl_scense_ext RBAC 路径注册(硬门禁 6.6 / QC #11)。
|
||||||
|
|
||||||
|
约定:
|
||||||
|
- 路径 = 模块自动路由 `/pbl_scense_ext/api/<契约>.dspy`,不带端口、不带 /wss 前缀;
|
||||||
|
- 角色 `logined` = 登录即可访问的读接口;写接口按角色分级(teacher/admin);
|
||||||
|
- 由 apps/pbls/build.sh 第 8 步调用 `register()`;rbac CLI 不在位时打印清单(不静默跳过)。
|
||||||
|
"""
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
|
||||||
|
MODULE = 'pbl_scense_ext'
|
||||||
|
|
||||||
|
# (path, role)
|
||||||
|
PATHS = [
|
||||||
|
('/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'),
|
||||||
|
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def register():
|
||||||
|
tool = os.environ.get('RBAC_SET_PERM', 'set_role_perm.py')
|
||||||
|
done, missing = 0, []
|
||||||
|
for path, role in PATHS:
|
||||||
|
if subprocess.call([sys.executable if os.environ.get('PY') else 'python3',
|
||||||
|
tool, role, path],
|
||||||
|
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) == 0:
|
||||||
|
done += 1
|
||||||
|
else:
|
||||||
|
missing.append((path, role))
|
||||||
|
print('[%s] rbac paths: total=%%d ok=%%d pending=%%d' %% (len(PATHS), done, len(missing)))
|
||||||
|
for path, role in missing:
|
||||||
|
print(' PENDING %%-12s %%s' %% (role, path))
|
||||||
|
return len(missing) == 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
sys.exit(0 if register() else 1)
|
||||||
24
skill/SKILL.md
Normal file
24
skill/SKILL.md
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
# pbl_scense_ext 模块技能(自动生成骨架 + 人工补充)
|
||||||
|
|
||||||
|
## 定位
|
||||||
|
scense/scense_game 前端覆盖层与页面注册(M9)
|
||||||
|
|
||||||
|
## 挂载
|
||||||
|
`from pbl_scense_ext.init import load_pbl_scense_ext` → `load_pbl_scense_ext()`(应用 app/pbls.py init() 中按序调用)
|
||||||
|
|
||||||
|
## 数据表(3 张)
|
||||||
|
- `pbl_session_member`:会话成员(复用 world_session,不污染基表)
|
||||||
|
- `pbl_playtest_feedback`:Playtest 反馈/反思(reflection 走证据链外的轻量表)
|
||||||
|
- `pbl_ui_page_registry`:前端覆盖层页面注册(12 页面 + 时延预算)
|
||||||
|
|
||||||
|
## 契约接口(4 个,路径 `/pbl_scense_ext/api/<name>.dspy`)
|
||||||
|
- `pbl_session_context_get`
|
||||||
|
- `pbl_page_registry_list`
|
||||||
|
- `pbl_playtest_feedback_save`
|
||||||
|
- `pbl_overlay_bootstrap`
|
||||||
|
|
||||||
|
## 陷阱
|
||||||
|
- 库名一律 `ServerEnv().get_module_dbname('pbl_scense_ext')`,禁止硬编码 DBNAME。
|
||||||
|
- sqlor 只有 `C/U/D/R/I/sqlExe`;查询走 pbl_common.api 的 q_all/q_one(已适配)。
|
||||||
|
- 所有读写强制带 `tenant_id`(pbl_common.api.tenant_id()),缺失即 fail-closed 报错。
|
||||||
|
- 新增契约需同步三处:api.py 定义 + __init__.py 导出 + init.py env 注册 + scripts/load_path.py 路径。
|
||||||
52
sql/pbl_scense_ext.sql
Normal file
52
sql/pbl_scense_ext.sql
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
-- pbl_scense_ext 表 DDL(自动生成,与 apps/pbls/scripts/ddl/pbls_tables.sql 同源)
|
||||||
|
CREATE TABLE IF NOT EXISTS `pbl_session_member` (
|
||||||
|
`tenant_id` VARCHAR(32) NOT NULL NOT NULL COMMENT 租户ID(强制打头),
|
||||||
|
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 主键,
|
||||||
|
`session_id` BIGINT UNSIGNED NOT NULL COMMENT "会话",
|
||||||
|
`user_id` VARCHAR(32) NOT NULL COMMENT "用户",
|
||||||
|
`role_code` VARCHAR(64) NOT NULL COMMENT "角色",
|
||||||
|
`team_id` BIGINT UNSIGNED NOT NULL COMMENT "团队",
|
||||||
|
`joined_at` DATETIME NULL NULL COMMENT "加入时间",
|
||||||
|
`last_seen_at` DATETIME NULL NULL COMMENT "心跳",
|
||||||
|
`state_version` BIGINT NOT NULL DEFAULT 0 COMMENT "已见状态版本",
|
||||||
|
`status` VARCHAR(64) NOT NULL COMMENT "状态",
|
||||||
|
`created_at` DATETIME NOT NULL DEFAULT '1970-01-01 00:00:00' COMMENT "创建时间(应用层写入)",
|
||||||
|
`updated_at` DATETIME NOT NULL DEFAULT '1970-01-01 00:00:00' COMMENT "更新时间(应用层写入)",
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
UNIQUE KEY `uk_sm` (`tenant_id`, `session_id`, `user_id`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT="会话成员(复用 world_session,不污染基表)";
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS `pbl_playtest_feedback` (
|
||||||
|
`tenant_id` VARCHAR(32) NOT NULL NOT NULL COMMENT 租户ID(强制打头),
|
||||||
|
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 主键,
|
||||||
|
`session_id` BIGINT UNSIGNED NOT NULL COMMENT "会话",
|
||||||
|
`user_id` VARCHAR(32) NOT NULL COMMENT "用户",
|
||||||
|
`blueprint_id` BIGINT UNSIGNED NOT NULL COMMENT "蓝图",
|
||||||
|
`feedback_type` VARCHAR(64) NOT NULL COMMENT "类型",
|
||||||
|
`rating` INT NOT NULL DEFAULT 0 COMMENT "评分",
|
||||||
|
`content_txt` TEXT NULL COMMENT "内容",
|
||||||
|
`context_json` LONGTEXT NULL COMMENT "上下文",
|
||||||
|
`handled` TINYINT(1) NOT NULL DEFAULT 0 COMMENT "已处理",
|
||||||
|
`created_at` DATETIME NOT NULL DEFAULT '1970-01-01 00:00:00' COMMENT "创建时间(应用层写入)",
|
||||||
|
`updated_at` DATETIME NOT NULL DEFAULT '1970-01-01 00:00:00' COMMENT "更新时间(应用层写入)",
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
KEY `idx_pf_session` (`tenant_id`, `session_id`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT="Playtest 反馈/反思(reflection 走证据链外的轻量表)";
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS `pbl_ui_page_registry` (
|
||||||
|
`tenant_id` VARCHAR(32) NOT NULL NOT NULL COMMENT 租户ID(强制打头),
|
||||||
|
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 主键,
|
||||||
|
`page_key` VARCHAR(64) NOT NULL COMMENT "页面键",
|
||||||
|
`title` VARCHAR(128) NOT NULL COMMENT "标题",
|
||||||
|
`route` VARCHAR(64) NOT NULL COMMENT "路由",
|
||||||
|
`widget_type` VARCHAR(64) NOT NULL COMMENT "渲染组件",
|
||||||
|
`requires_role` VARCHAR(64) NOT NULL COMMENT "所需角色",
|
||||||
|
`budget_ms` INT NOT NULL DEFAULT 0 COMMENT "渲染预算",
|
||||||
|
`enabled` TINYINT(1) NOT NULL DEFAULT 0 COMMENT "启用",
|
||||||
|
`meta_json` LONGTEXT NULL COMMENT "元数据",
|
||||||
|
`created_at` DATETIME NOT NULL DEFAULT '1970-01-01 00:00:00' COMMENT "创建时间(应用层写入)",
|
||||||
|
`updated_at` DATETIME NOT NULL DEFAULT '1970-01-01 00:00:00' COMMENT "更新时间(应用层写入)",
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
UNIQUE KEY `uk_upr_key` (`tenant_id`, `page_key`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT="前端覆盖层页面注册(12 页面 + 时延预算)";
|
||||||
|
|
||||||
4
wwwroot/api/pbl_overlay_bootstrap.dspy
Normal file
4
wwwroot/api/pbl_overlay_bootstrap.dspy
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
# pbl_scense_ext/api/pbl_overlay_bootstrap.dspy —— 契约端点(自动生成,勿手改:改 spec 后跑 gen_artifacts.py)
|
||||||
|
debug('pbl_scense_ext/api/pbl_overlay_bootstrap.dspy: START params_kw={dict(params_kw)}')
|
||||||
|
data = await pbl_overlay_bootstrap(**params_kw)
|
||||||
|
return data
|
||||||
4
wwwroot/api/pbl_page_registry_list.dspy
Normal file
4
wwwroot/api/pbl_page_registry_list.dspy
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
# pbl_scense_ext/api/pbl_page_registry_list.dspy —— 契约端点(自动生成,勿手改:改 spec 后跑 gen_artifacts.py)
|
||||||
|
debug('pbl_scense_ext/api/pbl_page_registry_list.dspy: START params_kw={dict(params_kw)}')
|
||||||
|
data = await pbl_page_registry_list(**params_kw)
|
||||||
|
return data
|
||||||
4
wwwroot/api/pbl_playtest_feedback_save.dspy
Normal file
4
wwwroot/api/pbl_playtest_feedback_save.dspy
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
# pbl_scense_ext/api/pbl_playtest_feedback_save.dspy —— 契约端点(自动生成,勿手改:改 spec 后跑 gen_artifacts.py)
|
||||||
|
debug('pbl_scense_ext/api/pbl_playtest_feedback_save.dspy: START params_kw={dict(params_kw)}')
|
||||||
|
data = await pbl_playtest_feedback_save(**params_kw)
|
||||||
|
return data
|
||||||
4
wwwroot/api/pbl_session_context_get.dspy
Normal file
4
wwwroot/api/pbl_session_context_get.dspy
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
# pbl_scense_ext/api/pbl_session_context_get.dspy —— 契约端点(自动生成,勿手改:改 spec 后跑 gen_artifacts.py)
|
||||||
|
debug('pbl_scense_ext/api/pbl_session_context_get.dspy: START params_kw={dict(params_kw)}')
|
||||||
|
data = await pbl_session_context_get(**params_kw)
|
||||||
|
return data
|
||||||
147
wwwroot/index.ui
Normal file
147
wwwroot/index.ui
Normal file
@ -0,0 +1,147 @@
|
|||||||
|
{
|
||||||
|
"widgettype": "VBox",
|
||||||
|
"options": {
|
||||||
|
"width": "100%",
|
||||||
|
"height": "100%",
|
||||||
|
"padding": "20px"
|
||||||
|
},
|
||||||
|
"subwidgets": [
|
||||||
|
{
|
||||||
|
"widgettype": "Text",
|
||||||
|
"options": {
|
||||||
|
"label": "scense/scense_game 前端覆盖层与页面注册(M9)",
|
||||||
|
"fontSize": "24px"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"widgettype": "ResponsableBox",
|
||||||
|
"options": {
|
||||||
|
"gap": "16px",
|
||||||
|
"minWidth": "250px"
|
||||||
|
},
|
||||||
|
"subwidgets": [
|
||||||
|
{
|
||||||
|
"widgettype": "VBox",
|
||||||
|
"options": {
|
||||||
|
"backgroundColor": "#FFFFFF",
|
||||||
|
"padding": "20px",
|
||||||
|
"cursor": "pointer"
|
||||||
|
},
|
||||||
|
"binds": [
|
||||||
|
{
|
||||||
|
"wid": "self",
|
||||||
|
"event": "click",
|
||||||
|
"actiontype": "urlwidget",
|
||||||
|
"target": "app.pbl_scense_ext_content",
|
||||||
|
"options": {
|
||||||
|
"url": "{{entire_url('api/pbl_session_context_get.dspy')}}"
|
||||||
|
},
|
||||||
|
"mode": "replace"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"subwidgets": [
|
||||||
|
{
|
||||||
|
"widgettype": "Text",
|
||||||
|
"options": {
|
||||||
|
"label": "pbl_session_context_get"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"widgettype": "VBox",
|
||||||
|
"options": {
|
||||||
|
"backgroundColor": "#FFFFFF",
|
||||||
|
"padding": "20px",
|
||||||
|
"cursor": "pointer"
|
||||||
|
},
|
||||||
|
"binds": [
|
||||||
|
{
|
||||||
|
"wid": "self",
|
||||||
|
"event": "click",
|
||||||
|
"actiontype": "urlwidget",
|
||||||
|
"target": "app.pbl_scense_ext_content",
|
||||||
|
"options": {
|
||||||
|
"url": "{{entire_url('api/pbl_page_registry_list.dspy')}}"
|
||||||
|
},
|
||||||
|
"mode": "replace"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"subwidgets": [
|
||||||
|
{
|
||||||
|
"widgettype": "Text",
|
||||||
|
"options": {
|
||||||
|
"label": "pbl_page_registry_list"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"widgettype": "VBox",
|
||||||
|
"options": {
|
||||||
|
"backgroundColor": "#FFFFFF",
|
||||||
|
"padding": "20px",
|
||||||
|
"cursor": "pointer"
|
||||||
|
},
|
||||||
|
"binds": [
|
||||||
|
{
|
||||||
|
"wid": "self",
|
||||||
|
"event": "click",
|
||||||
|
"actiontype": "urlwidget",
|
||||||
|
"target": "app.pbl_scense_ext_content",
|
||||||
|
"options": {
|
||||||
|
"url": "{{entire_url('api/pbl_playtest_feedback_save.dspy')}}"
|
||||||
|
},
|
||||||
|
"mode": "replace"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"subwidgets": [
|
||||||
|
{
|
||||||
|
"widgettype": "Text",
|
||||||
|
"options": {
|
||||||
|
"label": "pbl_playtest_feedback_save"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"widgettype": "VBox",
|
||||||
|
"options": {
|
||||||
|
"backgroundColor": "#FFFFFF",
|
||||||
|
"padding": "20px",
|
||||||
|
"cursor": "pointer"
|
||||||
|
},
|
||||||
|
"binds": [
|
||||||
|
{
|
||||||
|
"wid": "self",
|
||||||
|
"event": "click",
|
||||||
|
"actiontype": "urlwidget",
|
||||||
|
"target": "app.pbl_scense_ext_content",
|
||||||
|
"options": {
|
||||||
|
"url": "{{entire_url('api/pbl_overlay_bootstrap.dspy')}}"
|
||||||
|
},
|
||||||
|
"mode": "replace"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"subwidgets": [
|
||||||
|
{
|
||||||
|
"widgettype": "Text",
|
||||||
|
"options": {
|
||||||
|
"label": "pbl_overlay_bootstrap"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"widgettype": "VBox",
|
||||||
|
"id": "pbl_scense_ext_content",
|
||||||
|
"options": {
|
||||||
|
"width": "100%",
|
||||||
|
"flex": "1",
|
||||||
|
"marginTop": "20px"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user