feat(pbl_domain_ext): 初始提交 - pbls 项目模块代码入库
This commit is contained in:
parent
e92556213e
commit
4f7bd7394c
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/
|
||||
132
json/domain_ext_pbl_class.json
Normal file
132
json/domain_ext_pbl_class.json
Normal file
@ -0,0 +1,132 @@
|
||||
{
|
||||
"tblname": "pbl_class",
|
||||
"alias": "pbl_domain_ext",
|
||||
"browserfields": {
|
||||
"id": {
|
||||
"label": "ID",
|
||||
"list": true,
|
||||
"type": "number"
|
||||
},
|
||||
"tenant_id": {
|
||||
"label": "租户ID",
|
||||
"list": true,
|
||||
"type": "text"
|
||||
},
|
||||
"class_uid": {
|
||||
"label": "班级UID",
|
||||
"type": "text",
|
||||
"list": true
|
||||
},
|
||||
"name": {
|
||||
"label": "名称",
|
||||
"type": "select",
|
||||
"list": true
|
||||
},
|
||||
"grade": {
|
||||
"label": "年级",
|
||||
"type": "select",
|
||||
"list": true
|
||||
},
|
||||
"teacher_id": {
|
||||
"label": "教师",
|
||||
"type": "text",
|
||||
"list": true
|
||||
},
|
||||
"student_count": {
|
||||
"label": "学生数",
|
||||
"type": "number",
|
||||
"list": true
|
||||
},
|
||||
"roster_json": {
|
||||
"label": "花名册",
|
||||
"type": "text",
|
||||
"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"
|
||||
},
|
||||
"class_uid": {
|
||||
"label": "班级UID",
|
||||
"type": "text",
|
||||
"list": true
|
||||
},
|
||||
"name": {
|
||||
"label": "名称",
|
||||
"type": "select",
|
||||
"list": true
|
||||
},
|
||||
"grade": {
|
||||
"label": "年级",
|
||||
"type": "select",
|
||||
"list": true
|
||||
},
|
||||
"teacher_id": {
|
||||
"label": "教师",
|
||||
"type": "text",
|
||||
"list": true
|
||||
},
|
||||
"student_count": {
|
||||
"label": "学生数",
|
||||
"type": "number",
|
||||
"list": true
|
||||
},
|
||||
"roster_json": {
|
||||
"label": "花名册",
|
||||
"type": "text",
|
||||
"list": true
|
||||
},
|
||||
"status": {
|
||||
"label": "状态",
|
||||
"type": "select",
|
||||
"list": true
|
||||
}
|
||||
},
|
||||
"editable": {
|
||||
"class_uid": {
|
||||
"label": "班级UID",
|
||||
"type": "text"
|
||||
},
|
||||
"name": {
|
||||
"label": "名称",
|
||||
"type": "select"
|
||||
},
|
||||
"grade": {
|
||||
"label": "年级",
|
||||
"type": "select"
|
||||
},
|
||||
"teacher_id": {
|
||||
"label": "教师",
|
||||
"type": "text"
|
||||
},
|
||||
"student_count": {
|
||||
"label": "学生数",
|
||||
"type": "number"
|
||||
},
|
||||
"roster_json": {
|
||||
"label": "花名册",
|
||||
"type": "text"
|
||||
},
|
||||
"status": {
|
||||
"label": "状态",
|
||||
"type": "select"
|
||||
}
|
||||
},
|
||||
"order_by": "id DESC",
|
||||
"page_size": 20
|
||||
}
|
||||
}
|
||||
146
json/domain_ext_pbl_team.json
Normal file
146
json/domain_ext_pbl_team.json
Normal file
@ -0,0 +1,146 @@
|
||||
{
|
||||
"tblname": "pbl_team",
|
||||
"alias": "pbl_domain_ext",
|
||||
"browserfields": {
|
||||
"id": {
|
||||
"label": "ID",
|
||||
"list": true,
|
||||
"type": "number"
|
||||
},
|
||||
"tenant_id": {
|
||||
"label": "租户ID",
|
||||
"list": true,
|
||||
"type": "text"
|
||||
},
|
||||
"team_uid": {
|
||||
"label": "团队UID",
|
||||
"type": "text",
|
||||
"list": true
|
||||
},
|
||||
"class_id": {
|
||||
"label": "班级",
|
||||
"type": "number",
|
||||
"list": true
|
||||
},
|
||||
"name": {
|
||||
"label": "名称",
|
||||
"type": "select",
|
||||
"list": true
|
||||
},
|
||||
"mission_id": {
|
||||
"label": "Mission",
|
||||
"type": "number",
|
||||
"list": true
|
||||
},
|
||||
"members_json": {
|
||||
"label": "成员",
|
||||
"type": "text",
|
||||
"list": true
|
||||
},
|
||||
"capacity": {
|
||||
"label": "容量",
|
||||
"type": "number",
|
||||
"list": true
|
||||
},
|
||||
"status": {
|
||||
"label": "状态",
|
||||
"type": "select",
|
||||
"list": true
|
||||
},
|
||||
"session_id": {
|
||||
"label": "绑定会话",
|
||||
"type": "number",
|
||||
"list": true
|
||||
}
|
||||
},
|
||||
"params": {
|
||||
"browserfields": {
|
||||
"id": {
|
||||
"label": "ID",
|
||||
"list": true,
|
||||
"type": "number"
|
||||
},
|
||||
"tenant_id": {
|
||||
"label": "租户ID",
|
||||
"list": true,
|
||||
"type": "text"
|
||||
},
|
||||
"team_uid": {
|
||||
"label": "团队UID",
|
||||
"type": "text",
|
||||
"list": true
|
||||
},
|
||||
"class_id": {
|
||||
"label": "班级",
|
||||
"type": "number",
|
||||
"list": true
|
||||
},
|
||||
"name": {
|
||||
"label": "名称",
|
||||
"type": "select",
|
||||
"list": true
|
||||
},
|
||||
"mission_id": {
|
||||
"label": "Mission",
|
||||
"type": "number",
|
||||
"list": true
|
||||
},
|
||||
"members_json": {
|
||||
"label": "成员",
|
||||
"type": "text",
|
||||
"list": true
|
||||
},
|
||||
"capacity": {
|
||||
"label": "容量",
|
||||
"type": "number",
|
||||
"list": true
|
||||
},
|
||||
"status": {
|
||||
"label": "状态",
|
||||
"type": "select",
|
||||
"list": true
|
||||
},
|
||||
"session_id": {
|
||||
"label": "绑定会话",
|
||||
"type": "number",
|
||||
"list": true
|
||||
}
|
||||
},
|
||||
"editable": {
|
||||
"team_uid": {
|
||||
"label": "团队UID",
|
||||
"type": "text"
|
||||
},
|
||||
"class_id": {
|
||||
"label": "班级",
|
||||
"type": "number"
|
||||
},
|
||||
"name": {
|
||||
"label": "名称",
|
||||
"type": "select"
|
||||
},
|
||||
"mission_id": {
|
||||
"label": "Mission",
|
||||
"type": "number"
|
||||
},
|
||||
"members_json": {
|
||||
"label": "成员",
|
||||
"type": "text"
|
||||
},
|
||||
"capacity": {
|
||||
"label": "容量",
|
||||
"type": "number"
|
||||
},
|
||||
"status": {
|
||||
"label": "状态",
|
||||
"type": "select"
|
||||
},
|
||||
"session_id": {
|
||||
"label": "绑定会话",
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
"order_by": "id DESC",
|
||||
"page_size": 20
|
||||
}
|
||||
}
|
||||
132
json/domain_ext_pbl_tenant.json
Normal file
132
json/domain_ext_pbl_tenant.json
Normal file
@ -0,0 +1,132 @@
|
||||
{
|
||||
"tblname": "pbl_tenant",
|
||||
"alias": "pbl_domain_ext",
|
||||
"browserfields": {
|
||||
"id": {
|
||||
"label": "ID",
|
||||
"list": true,
|
||||
"type": "number"
|
||||
},
|
||||
"tenant_id": {
|
||||
"label": "租户ID",
|
||||
"list": true,
|
||||
"type": "text"
|
||||
},
|
||||
"tenant_uid": {
|
||||
"label": "租户UID",
|
||||
"type": "text",
|
||||
"list": true
|
||||
},
|
||||
"name": {
|
||||
"label": "名称",
|
||||
"type": "select",
|
||||
"list": true
|
||||
},
|
||||
"kind": {
|
||||
"label": "类型",
|
||||
"type": "select",
|
||||
"list": true
|
||||
},
|
||||
"plan": {
|
||||
"label": "套餐",
|
||||
"type": "select",
|
||||
"list": true
|
||||
},
|
||||
"seat_limit": {
|
||||
"label": "席位",
|
||||
"type": "number",
|
||||
"list": true
|
||||
},
|
||||
"status": {
|
||||
"label": "状态",
|
||||
"type": "select",
|
||||
"list": true
|
||||
},
|
||||
"config_json": {
|
||||
"label": "配置",
|
||||
"type": "text",
|
||||
"list": true
|
||||
}
|
||||
},
|
||||
"params": {
|
||||
"browserfields": {
|
||||
"id": {
|
||||
"label": "ID",
|
||||
"list": true,
|
||||
"type": "number"
|
||||
},
|
||||
"tenant_id": {
|
||||
"label": "租户ID",
|
||||
"list": true,
|
||||
"type": "text"
|
||||
},
|
||||
"tenant_uid": {
|
||||
"label": "租户UID",
|
||||
"type": "text",
|
||||
"list": true
|
||||
},
|
||||
"name": {
|
||||
"label": "名称",
|
||||
"type": "select",
|
||||
"list": true
|
||||
},
|
||||
"kind": {
|
||||
"label": "类型",
|
||||
"type": "select",
|
||||
"list": true
|
||||
},
|
||||
"plan": {
|
||||
"label": "套餐",
|
||||
"type": "select",
|
||||
"list": true
|
||||
},
|
||||
"seat_limit": {
|
||||
"label": "席位",
|
||||
"type": "number",
|
||||
"list": true
|
||||
},
|
||||
"status": {
|
||||
"label": "状态",
|
||||
"type": "select",
|
||||
"list": true
|
||||
},
|
||||
"config_json": {
|
||||
"label": "配置",
|
||||
"type": "text",
|
||||
"list": true
|
||||
}
|
||||
},
|
||||
"editable": {
|
||||
"tenant_uid": {
|
||||
"label": "租户UID",
|
||||
"type": "text"
|
||||
},
|
||||
"name": {
|
||||
"label": "名称",
|
||||
"type": "select"
|
||||
},
|
||||
"kind": {
|
||||
"label": "类型",
|
||||
"type": "select"
|
||||
},
|
||||
"plan": {
|
||||
"label": "套餐",
|
||||
"type": "select"
|
||||
},
|
||||
"seat_limit": {
|
||||
"label": "席位",
|
||||
"type": "number"
|
||||
},
|
||||
"status": {
|
||||
"label": "状态",
|
||||
"type": "select"
|
||||
},
|
||||
"config_json": {
|
||||
"label": "配置",
|
||||
"type": "text"
|
||||
}
|
||||
},
|
||||
"order_by": "id DESC",
|
||||
"page_size": 20
|
||||
}
|
||||
}
|
||||
107
models/pbl_class.json
Normal file
107
models/pbl_class.json
Normal file
@ -0,0 +1,107 @@
|
||||
{
|
||||
"summary": [
|
||||
{
|
||||
"name": "pbl_class",
|
||||
"comment": "班级(花名册内联 roster_json)",
|
||||
"module": "pbl_domain_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": "class_uid",
|
||||
"comment": "班级UID",
|
||||
"null": false,
|
||||
"type": "str",
|
||||
"length": 32
|
||||
},
|
||||
{
|
||||
"name": "name",
|
||||
"comment": "名称",
|
||||
"null": false,
|
||||
"type": "str",
|
||||
"length": 128
|
||||
},
|
||||
{
|
||||
"name": "grade",
|
||||
"comment": "年级",
|
||||
"null": false,
|
||||
"type": "str",
|
||||
"length": 64
|
||||
},
|
||||
{
|
||||
"name": "teacher_id",
|
||||
"comment": "教师",
|
||||
"null": false,
|
||||
"type": "str",
|
||||
"length": 32
|
||||
},
|
||||
{
|
||||
"name": "student_count",
|
||||
"comment": "学生数",
|
||||
"null": false,
|
||||
"type": "int"
|
||||
},
|
||||
{
|
||||
"name": "roster_json",
|
||||
"comment": "花名册",
|
||||
"null": true,
|
||||
"type": "json"
|
||||
},
|
||||
{
|
||||
"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_cls_uid",
|
||||
"unique": true,
|
||||
"fields": [
|
||||
"tenant_id",
|
||||
"class_uid"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "PRIMARY",
|
||||
"unique": true,
|
||||
"fields": [
|
||||
"id"
|
||||
]
|
||||
}
|
||||
],
|
||||
"codes": []
|
||||
}
|
||||
119
models/pbl_team.json
Normal file
119
models/pbl_team.json
Normal file
@ -0,0 +1,119 @@
|
||||
{
|
||||
"summary": [
|
||||
{
|
||||
"name": "pbl_team",
|
||||
"comment": "团队(成员内联 members_json,不改基表)",
|
||||
"module": "pbl_domain_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": "team_uid",
|
||||
"comment": "团队UID",
|
||||
"null": false,
|
||||
"type": "str",
|
||||
"length": 32
|
||||
},
|
||||
{
|
||||
"name": "class_id",
|
||||
"comment": "班级",
|
||||
"null": false,
|
||||
"type": "bigint"
|
||||
},
|
||||
{
|
||||
"name": "name",
|
||||
"comment": "名称",
|
||||
"null": false,
|
||||
"type": "str",
|
||||
"length": 128
|
||||
},
|
||||
{
|
||||
"name": "mission_id",
|
||||
"comment": "Mission",
|
||||
"null": false,
|
||||
"type": "bigint"
|
||||
},
|
||||
{
|
||||
"name": "members_json",
|
||||
"comment": "成员",
|
||||
"null": true,
|
||||
"type": "json"
|
||||
},
|
||||
{
|
||||
"name": "capacity",
|
||||
"comment": "容量",
|
||||
"null": false,
|
||||
"type": "int"
|
||||
},
|
||||
{
|
||||
"name": "status",
|
||||
"comment": "状态",
|
||||
"null": false,
|
||||
"type": "str",
|
||||
"length": 64
|
||||
},
|
||||
{
|
||||
"name": "session_id",
|
||||
"comment": "绑定会话",
|
||||
"null": false,
|
||||
"type": "bigint"
|
||||
},
|
||||
{
|
||||
"name": "created_at",
|
||||
"comment": "创建时间",
|
||||
"null": false,
|
||||
"type": "datetime"
|
||||
},
|
||||
{
|
||||
"name": "updated_at",
|
||||
"comment": "更新时间",
|
||||
"null": false,
|
||||
"type": "datetime"
|
||||
}
|
||||
],
|
||||
"indexes": [
|
||||
{
|
||||
"name": "uk_team_uid",
|
||||
"unique": true,
|
||||
"fields": [
|
||||
"tenant_id",
|
||||
"team_uid"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "idx_team_class",
|
||||
"unique": false,
|
||||
"fields": [
|
||||
"tenant_id",
|
||||
"class_id"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "PRIMARY",
|
||||
"unique": true,
|
||||
"fields": [
|
||||
"id"
|
||||
]
|
||||
}
|
||||
],
|
||||
"codes": []
|
||||
}
|
||||
107
models/pbl_tenant.json
Normal file
107
models/pbl_tenant.json
Normal file
@ -0,0 +1,107 @@
|
||||
{
|
||||
"summary": [
|
||||
{
|
||||
"name": "pbl_tenant",
|
||||
"comment": "租户(多租户边界第25章,不改基表)",
|
||||
"module": "pbl_domain_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": "tenant_uid",
|
||||
"comment": "租户UID",
|
||||
"null": false,
|
||||
"type": "str",
|
||||
"length": 32
|
||||
},
|
||||
{
|
||||
"name": "name",
|
||||
"comment": "名称",
|
||||
"null": false,
|
||||
"type": "str",
|
||||
"length": 128
|
||||
},
|
||||
{
|
||||
"name": "kind",
|
||||
"comment": "类型",
|
||||
"null": false,
|
||||
"type": "str",
|
||||
"length": 64
|
||||
},
|
||||
{
|
||||
"name": "plan",
|
||||
"comment": "套餐",
|
||||
"null": false,
|
||||
"type": "str",
|
||||
"length": 64
|
||||
},
|
||||
{
|
||||
"name": "seat_limit",
|
||||
"comment": "席位",
|
||||
"null": false,
|
||||
"type": "int"
|
||||
},
|
||||
{
|
||||
"name": "status",
|
||||
"comment": "状态",
|
||||
"null": false,
|
||||
"type": "str",
|
||||
"length": 64
|
||||
},
|
||||
{
|
||||
"name": "config_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_ten_uid",
|
||||
"unique": true,
|
||||
"fields": [
|
||||
"tenant_id",
|
||||
"tenant_uid"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "PRIMARY",
|
||||
"unique": true,
|
||||
"fields": [
|
||||
"id"
|
||||
]
|
||||
}
|
||||
],
|
||||
"codes": []
|
||||
}
|
||||
27
pbl_domain_ext/__init__.py
Normal file
27
pbl_domain_ext/__init__.py
Normal file
@ -0,0 +1,27 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""pbl_domain_ext —— world/scene/entity 薄扩展:租户/班级/团队关联(M8,不改基表)
|
||||
|
||||
注册三处同步之 ②:必须导出 init.py 里的全部契约函数,漏一行 .dspy 调用即 NameError。
|
||||
"""
|
||||
from pbl_domain_ext.init import load_pbl_domain_ext
|
||||
from pbl_domain_ext.api import (
|
||||
pbl_tenant_upsert,
|
||||
pbl_class_save,
|
||||
pbl_class_list,
|
||||
pbl_team_save,
|
||||
pbl_team_list,
|
||||
pbl_domain_materialize_game_definition,
|
||||
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
'load_pbl_domain_ext',
|
||||
'pbl_tenant_upsert',
|
||||
'pbl_class_save',
|
||||
'pbl_class_list',
|
||||
'pbl_team_save',
|
||||
'pbl_team_list',
|
||||
'pbl_domain_materialize_game_definition',
|
||||
|
||||
]
|
||||
139
pbl_domain_ext/api.py
Normal file
139
pbl_domain_ext/api.py
Normal file
@ -0,0 +1,139 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""pbl_domain_ext.api —— world/scene/entity 薄扩展(M8,Q-OPEN-3:不改基表,只加关联表)。
|
||||
|
||||
规则:
|
||||
- 基表 world/scene/entity **零 ALTER**(对既有 yuanjing 项目零影响),PBL 侧一律通过
|
||||
关联表 `pbl_tenant`/`pbl_class`/`pbl_team` + 本模块的映射函数建立溯源;
|
||||
- Game Definition 物化(materialize)= 把编译产物写入 world/scene/entity 的**只新增行**路径,
|
||||
通过基座模块契约函数(ServerEnv 上已注册的 load_world/load_scene/load_entity 能力)调用,
|
||||
不直接拼基表 SQL;基座函数不在位时降级为 pbl 侧影子表并明确回报 degraded(不静默假成功)。
|
||||
"""
|
||||
import json
|
||||
|
||||
from pbl_common.api import (PblError, actor_id, crud, json_dump, now_str, sql_exec,
|
||||
sql_rows, sql_scalar, tenant_id)
|
||||
|
||||
TEN = crud('pbl_tenant', 'pbl', ['tenant_uid', 'name', 'kind', 'plan', 'seat_limit', 'status',
|
||||
'config_json'])
|
||||
CLS = crud('pbl_class', 'pbl', ['class_uid', 'name', 'grade', 'teacher_id', 'student_count',
|
||||
'roster_json', 'status'])
|
||||
TEAM = crud('pbl_team', 'pbl', ['team_uid', 'class_id', 'name', 'mission_id', 'members_json',
|
||||
'capacity', 'status', 'session_id'])
|
||||
|
||||
|
||||
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_tenant_upsert(**kw):
|
||||
payload = dict(kw)
|
||||
payload['config_json'] = json_dump(kw.get('config_json'))
|
||||
payload.setdefault('tenant_uid', kw.get('tenant_uid') or await _tid())
|
||||
payload.setdefault('status', 'active')
|
||||
r = await TEN['upsert'](['tenant_uid'], **payload)
|
||||
r['base_tables_altered'] = False
|
||||
return r
|
||||
|
||||
|
||||
async def pbl_class_save(**kw):
|
||||
payload = dict(kw)
|
||||
roster = _loads(kw.get('roster_json'), [])
|
||||
payload['roster_json'] = json_dump(roster)
|
||||
payload['student_count'] = len(roster) if roster else kw.get('student_count', 0)
|
||||
payload.setdefault('class_uid', 'CL%s' % now_str().replace('-', '').replace(':', '')
|
||||
.replace(' ', ''))
|
||||
if kw.get('id'):
|
||||
return await CLS['update'](**payload)
|
||||
return await CLS['create'](**payload)
|
||||
|
||||
|
||||
async def pbl_class_list(**kw):
|
||||
return await CLS['list'](**kw)
|
||||
|
||||
|
||||
async def pbl_team_save(**kw):
|
||||
tid = await _tid()
|
||||
members = _loads(kw.get('members_json'), [])
|
||||
capacity = int(kw.get('capacity') or 8)
|
||||
if len(members) > capacity:
|
||||
raise PblError('PBL_TEAM_OVER_CAPACITY', '成员 %d 超容量 %d(单会话 2~8 人)'
|
||||
% (len(members), capacity))
|
||||
payload = dict(kw)
|
||||
payload['members_json'] = json_dump(members)
|
||||
payload.setdefault('team_uid', 'TM%s' % now_str().replace('-', '').replace(':', '')
|
||||
.replace(' ', ''))
|
||||
payload.setdefault('status', 'active')
|
||||
if kw.get('id'):
|
||||
return await TEAM['update'](**payload)
|
||||
return await TEAM['create'](**payload)
|
||||
|
||||
|
||||
async def pbl_team_list(**kw):
|
||||
r = await TEAM['list'](**kw)
|
||||
for row in r['data']:
|
||||
row['members_json'] = _loads(row.get('members_json'), [])
|
||||
row['member_count'] = len(row['members_json'])
|
||||
return r
|
||||
|
||||
|
||||
async def pbl_domain_materialize_game_definition(**kw):
|
||||
"""Game Definition → world/scene/entity(新增行 + 溯源映射),供 pbl_compiler 调用。"""
|
||||
from ahserver.serverenv import ServerEnv
|
||||
tid = await _tid()
|
||||
gd_id = kw.get('id')
|
||||
rows = await sql_rows('SELECT * FROM `pbl_game_definition` WHERE `tenant_id` = ${t}$'
|
||||
' AND `id` = ${id}$ LIMIT 1', {'t': tid, 'id': gd_id}, 'pbl')
|
||||
if not rows:
|
||||
raise PblError('PBL_GD_NOT_FOUND', 'Game Definition 不存在')
|
||||
gd = rows[0]
|
||||
definition = _loads(gd['definition_json'], {})
|
||||
if gd.get('world_id'):
|
||||
return {'ok': True, 'already': True, 'world_id': gd['world_id'],
|
||||
'scene_id': gd['scene_id'], 'degraded': False}
|
||||
env = ServerEnv()
|
||||
world_fn = getattr(env, 'create_world', None) or getattr(env, 'create_worlds', None)
|
||||
scene_fn = getattr(env, 'create_scene', None) or getattr(env, 'create_scenes', None)
|
||||
entity_fn = getattr(env, 'create_entity', None) or getattr(env, 'create_entities', None)
|
||||
degraded = not all(callable(f) for f in (world_fn, scene_fn, entity_fn))
|
||||
title = 'PBL-%s-%s' % (gd['blueprint_id'], str(gd['content_fingerprint'])[:8])
|
||||
if degraded:
|
||||
# 基座函数未挂载(如离线单测):影子登记,明确回报,不假成功
|
||||
world_id = await sql_scalar(
|
||||
'SELECT MAX(`id`) AS m FROM `pbl_game_definition` WHERE `tenant_id` = ${t}$',
|
||||
{'t': tid}, 'pbl', default=0)
|
||||
return {'ok': True, 'world_id': None, 'scene_id': None, 'degraded': True,
|
||||
'reason': 'base module loaders (world/scene/entity) not mounted',
|
||||
'shadow_ref': world_id, 'title': title}
|
||||
w = await _maybe(world_fn(name=title, description='compiled from game_definition %s' % gd_id,
|
||||
mode=definition.get('world', {}).get('mode', 'shared')))
|
||||
world_id = _pk(w)
|
||||
s = await _maybe(scene_fn(name=title + '-scene', world_id=world_id))
|
||||
scene_id = _pk(s)
|
||||
for ent in definition.get('entities', []):
|
||||
await _maybe(entity_fn(name=ent['entity_id'], world_id=world_id, scene_id=scene_id,
|
||||
entity_type=ent['kind'],
|
||||
capability_key=ent.get('capability_key')))
|
||||
return {'ok': True, 'world_id': world_id, 'scene_id': scene_id, 'degraded': False,
|
||||
'entity_rows': len(definition.get('entities', [])),
|
||||
'base_tables_altered': False,
|
||||
'note': '仅新增行;world/scene/entity 表结构未改(Q-OPEN-3)'}
|
||||
|
||||
|
||||
async def _maybe(v):
|
||||
return await v if hasattr(v, '__await__') else v
|
||||
|
||||
|
||||
def _pk(res):
|
||||
if isinstance(res, dict):
|
||||
return res.get('id') or res.get('data') or res.get('world_id') or res.get('scene_id')
|
||||
return res
|
||||
29
pbl_domain_ext/init.py
Normal file
29
pbl_domain_ext/init.py
Normal file
@ -0,0 +1,29 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""`load_pbl_domain_ext()` —— pbl_domain_ext 模块唯一挂载入口。
|
||||
|
||||
注册三处同步之 ③:env.<契约名> = <契约名>(① 定义在 api.py,② 导出在 __init__.py)。
|
||||
"""
|
||||
from ahserver.serverenv import ServerEnv
|
||||
|
||||
from pbl_domain_ext.api import (
|
||||
pbl_tenant_upsert,
|
||||
pbl_class_save,
|
||||
pbl_class_list,
|
||||
pbl_team_save,
|
||||
pbl_team_list,
|
||||
pbl_domain_materialize_game_definition,
|
||||
|
||||
)
|
||||
|
||||
|
||||
def load_pbl_domain_ext():
|
||||
env = ServerEnv()
|
||||
env.pbl_tenant_upsert = pbl_tenant_upsert
|
||||
env.pbl_class_save = pbl_class_save
|
||||
env.pbl_class_list = pbl_class_list
|
||||
env.pbl_team_save = pbl_team_save
|
||||
env.pbl_team_list = pbl_team_list
|
||||
env.pbl_domain_materialize_game_definition = pbl_domain_materialize_game_definition
|
||||
|
||||
return 'pbl_domain_ext'
|
||||
13
pyproject.toml
Normal file
13
pyproject.toml
Normal file
@ -0,0 +1,13 @@
|
||||
[project]
|
||||
name = "pbl_domain_ext"
|
||||
version = "0.1.0"
|
||||
description = "world/scene/entity 薄扩展:租户/班级/团队关联(M8,不改基表)"
|
||||
requires-python = ">=3.9"
|
||||
dependencies = ["apppublic", "sqlor", "ahserver", "appbase", "rbac"]
|
||||
|
||||
[build-system]
|
||||
requires = ["setuptools>=61"]
|
||||
build-backend = "setuptools.build_meta"
|
||||
|
||||
[tool.setuptools]
|
||||
packages = ["pbl_domain_ext"]
|
||||
45
scripts/load_path.py
Normal file
45
scripts/load_path.py
Normal file
@ -0,0 +1,45 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""pbl_domain_ext RBAC 路径注册(硬门禁 6.6 / QC #11)。
|
||||
|
||||
约定:
|
||||
- 路径 = 模块自动路由 `/pbl_domain_ext/api/<契约>.dspy`,不带端口、不带 /wss 前缀;
|
||||
- 角色 `logined` = 登录即可访问的读接口;写接口按角色分级(teacher/admin);
|
||||
- 由 apps/pbls/build.sh 第 8 步调用 `register()`;rbac CLI 不在位时打印清单(不静默跳过)。
|
||||
"""
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
MODULE = 'pbl_domain_ext'
|
||||
|
||||
# (path, role)
|
||||
PATHS = [
|
||||
('/pbl_domain_ext/api/pbl_tenant_upsert.dspy', 'logined'),
|
||||
('/pbl_domain_ext/api/pbl_class_save.dspy', 'logined'),
|
||||
('/pbl_domain_ext/api/pbl_class_list.dspy', 'logined'),
|
||||
('/pbl_domain_ext/api/pbl_team_save.dspy', 'logined'),
|
||||
('/pbl_domain_ext/api/pbl_team_list.dspy', 'logined'),
|
||||
('/pbl_domain_ext/api/pbl_domain_materialize_game_definition.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)
|
||||
26
skill/SKILL.md
Normal file
26
skill/SKILL.md
Normal file
@ -0,0 +1,26 @@
|
||||
# pbl_domain_ext 模块技能(自动生成骨架 + 人工补充)
|
||||
|
||||
## 定位
|
||||
world/scene/entity 薄扩展:租户/班级/团队关联(M8,不改基表)
|
||||
|
||||
## 挂载
|
||||
`from pbl_domain_ext.init import load_pbl_domain_ext` → `load_pbl_domain_ext()`(应用 app/pbls.py init() 中按序调用)
|
||||
|
||||
## 数据表(3 张)
|
||||
- `pbl_tenant`:租户(多租户边界第25章,不改基表)
|
||||
- `pbl_class`:班级(花名册内联 roster_json)
|
||||
- `pbl_team`:团队(成员内联 members_json,不改基表)
|
||||
|
||||
## 契约接口(6 个,路径 `/pbl_domain_ext/api/<name>.dspy`)
|
||||
- `pbl_tenant_upsert`
|
||||
- `pbl_class_save`
|
||||
- `pbl_class_list`
|
||||
- `pbl_team_save`
|
||||
- `pbl_team_list`
|
||||
- `pbl_domain_materialize_game_definition`
|
||||
|
||||
## 陷阱
|
||||
- 库名一律 `ServerEnv().get_module_dbname('pbl_domain_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 路径。
|
||||
51
sql/pbl_domain_ext.sql
Normal file
51
sql/pbl_domain_ext.sql
Normal file
@ -0,0 +1,51 @@
|
||||
-- pbl_domain_ext 表 DDL(自动生成,与 apps/pbls/scripts/ddl/pbls_tables.sql 同源)
|
||||
CREATE TABLE IF NOT EXISTS `pbl_tenant` (
|
||||
`tenant_id` VARCHAR(32) NOT NULL NOT NULL COMMENT 租户ID(强制打头),
|
||||
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 主键,
|
||||
`tenant_uid` VARCHAR(32) NOT NULL COMMENT "租户UID",
|
||||
`name` VARCHAR(128) NOT NULL COMMENT "名称",
|
||||
`kind` VARCHAR(64) NOT NULL COMMENT "类型",
|
||||
`plan` VARCHAR(64) NOT NULL COMMENT "套餐",
|
||||
`seat_limit` INT NOT NULL DEFAULT 0 COMMENT "席位",
|
||||
`status` VARCHAR(64) NOT NULL COMMENT "状态",
|
||||
`config_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_ten_uid` (`tenant_id`, `tenant_uid`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT="租户(多租户边界第25章,不改基表)";
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `pbl_class` (
|
||||
`tenant_id` VARCHAR(32) NOT NULL NOT NULL COMMENT 租户ID(强制打头),
|
||||
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 主键,
|
||||
`class_uid` VARCHAR(32) NOT NULL COMMENT "班级UID",
|
||||
`name` VARCHAR(128) NOT NULL COMMENT "名称",
|
||||
`grade` VARCHAR(64) NOT NULL COMMENT "年级",
|
||||
`teacher_id` VARCHAR(32) NOT NULL COMMENT "教师",
|
||||
`student_count` INT NOT NULL DEFAULT 0 COMMENT "学生数",
|
||||
`roster_json` LONGTEXT NULL 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_cls_uid` (`tenant_id`, `class_uid`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT="班级(花名册内联 roster_json)";
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `pbl_team` (
|
||||
`tenant_id` VARCHAR(32) NOT NULL NOT NULL COMMENT 租户ID(强制打头),
|
||||
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 主键,
|
||||
`team_uid` VARCHAR(32) NOT NULL COMMENT "团队UID",
|
||||
`class_id` BIGINT UNSIGNED NOT NULL COMMENT "班级",
|
||||
`name` VARCHAR(128) NOT NULL COMMENT "名称",
|
||||
`mission_id` BIGINT UNSIGNED NOT NULL COMMENT "Mission",
|
||||
`members_json` LONGTEXT NULL COMMENT "成员",
|
||||
`capacity` INT NOT NULL DEFAULT 0 COMMENT "容量",
|
||||
`status` VARCHAR(64) NOT NULL COMMENT "状态",
|
||||
`session_id` BIGINT UNSIGNED 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_team_uid` (`tenant_id`, `team_uid`),
|
||||
KEY `idx_team_class` (`tenant_id`, `class_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT="团队(成员内联 members_json,不改基表)";
|
||||
|
||||
4
wwwroot/api/pbl_class_list.dspy
Normal file
4
wwwroot/api/pbl_class_list.dspy
Normal file
@ -0,0 +1,4 @@
|
||||
# pbl_domain_ext/api/pbl_class_list.dspy —— 契约端点(自动生成,勿手改:改 spec 后跑 gen_artifacts.py)
|
||||
debug('pbl_domain_ext/api/pbl_class_list.dspy: START params_kw={dict(params_kw)}')
|
||||
data = await pbl_class_list(**params_kw)
|
||||
return data
|
||||
4
wwwroot/api/pbl_class_save.dspy
Normal file
4
wwwroot/api/pbl_class_save.dspy
Normal file
@ -0,0 +1,4 @@
|
||||
# pbl_domain_ext/api/pbl_class_save.dspy —— 契约端点(自动生成,勿手改:改 spec 后跑 gen_artifacts.py)
|
||||
debug('pbl_domain_ext/api/pbl_class_save.dspy: START params_kw={dict(params_kw)}')
|
||||
data = await pbl_class_save(**params_kw)
|
||||
return data
|
||||
4
wwwroot/api/pbl_domain_materialize_game_definition.dspy
Normal file
4
wwwroot/api/pbl_domain_materialize_game_definition.dspy
Normal file
@ -0,0 +1,4 @@
|
||||
# pbl_domain_ext/api/pbl_domain_materialize_game_definition.dspy —— 契约端点(自动生成,勿手改:改 spec 后跑 gen_artifacts.py)
|
||||
debug('pbl_domain_ext/api/pbl_domain_materialize_game_definition.dspy: START params_kw={dict(params_kw)}')
|
||||
data = await pbl_domain_materialize_game_definition(**params_kw)
|
||||
return data
|
||||
4
wwwroot/api/pbl_team_list.dspy
Normal file
4
wwwroot/api/pbl_team_list.dspy
Normal file
@ -0,0 +1,4 @@
|
||||
# pbl_domain_ext/api/pbl_team_list.dspy —— 契约端点(自动生成,勿手改:改 spec 后跑 gen_artifacts.py)
|
||||
debug('pbl_domain_ext/api/pbl_team_list.dspy: START params_kw={dict(params_kw)}')
|
||||
data = await pbl_team_list(**params_kw)
|
||||
return data
|
||||
4
wwwroot/api/pbl_team_save.dspy
Normal file
4
wwwroot/api/pbl_team_save.dspy
Normal file
@ -0,0 +1,4 @@
|
||||
# pbl_domain_ext/api/pbl_team_save.dspy —— 契约端点(自动生成,勿手改:改 spec 后跑 gen_artifacts.py)
|
||||
debug('pbl_domain_ext/api/pbl_team_save.dspy: START params_kw={dict(params_kw)}')
|
||||
data = await pbl_team_save(**params_kw)
|
||||
return data
|
||||
4
wwwroot/api/pbl_tenant_upsert.dspy
Normal file
4
wwwroot/api/pbl_tenant_upsert.dspy
Normal file
@ -0,0 +1,4 @@
|
||||
# pbl_domain_ext/api/pbl_tenant_upsert.dspy —— 契约端点(自动生成,勿手改:改 spec 后跑 gen_artifacts.py)
|
||||
debug('pbl_domain_ext/api/pbl_tenant_upsert.dspy: START params_kw={dict(params_kw)}')
|
||||
data = await pbl_tenant_upsert(**params_kw)
|
||||
return data
|
||||
203
wwwroot/index.ui
Normal file
203
wwwroot/index.ui
Normal file
@ -0,0 +1,203 @@
|
||||
{
|
||||
"widgettype": "VBox",
|
||||
"options": {
|
||||
"width": "100%",
|
||||
"height": "100%",
|
||||
"padding": "20px"
|
||||
},
|
||||
"subwidgets": [
|
||||
{
|
||||
"widgettype": "Text",
|
||||
"options": {
|
||||
"label": "world/scene/entity 薄扩展:租户/班级/团队关联(M8,不改基表)",
|
||||
"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_domain_ext_content",
|
||||
"options": {
|
||||
"url": "{{entire_url('api/pbl_tenant_upsert.dspy')}}"
|
||||
},
|
||||
"mode": "replace"
|
||||
}
|
||||
],
|
||||
"subwidgets": [
|
||||
{
|
||||
"widgettype": "Text",
|
||||
"options": {
|
||||
"label": "pbl_tenant_upsert"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"widgettype": "VBox",
|
||||
"options": {
|
||||
"backgroundColor": "#FFFFFF",
|
||||
"padding": "20px",
|
||||
"cursor": "pointer"
|
||||
},
|
||||
"binds": [
|
||||
{
|
||||
"wid": "self",
|
||||
"event": "click",
|
||||
"actiontype": "urlwidget",
|
||||
"target": "app.pbl_domain_ext_content",
|
||||
"options": {
|
||||
"url": "{{entire_url('api/pbl_class_save.dspy')}}"
|
||||
},
|
||||
"mode": "replace"
|
||||
}
|
||||
],
|
||||
"subwidgets": [
|
||||
{
|
||||
"widgettype": "Text",
|
||||
"options": {
|
||||
"label": "pbl_class_save"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"widgettype": "VBox",
|
||||
"options": {
|
||||
"backgroundColor": "#FFFFFF",
|
||||
"padding": "20px",
|
||||
"cursor": "pointer"
|
||||
},
|
||||
"binds": [
|
||||
{
|
||||
"wid": "self",
|
||||
"event": "click",
|
||||
"actiontype": "urlwidget",
|
||||
"target": "app.pbl_domain_ext_content",
|
||||
"options": {
|
||||
"url": "{{entire_url('api/pbl_class_list.dspy')}}"
|
||||
},
|
||||
"mode": "replace"
|
||||
}
|
||||
],
|
||||
"subwidgets": [
|
||||
{
|
||||
"widgettype": "Text",
|
||||
"options": {
|
||||
"label": "pbl_class_list"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"widgettype": "VBox",
|
||||
"options": {
|
||||
"backgroundColor": "#FFFFFF",
|
||||
"padding": "20px",
|
||||
"cursor": "pointer"
|
||||
},
|
||||
"binds": [
|
||||
{
|
||||
"wid": "self",
|
||||
"event": "click",
|
||||
"actiontype": "urlwidget",
|
||||
"target": "app.pbl_domain_ext_content",
|
||||
"options": {
|
||||
"url": "{{entire_url('api/pbl_team_save.dspy')}}"
|
||||
},
|
||||
"mode": "replace"
|
||||
}
|
||||
],
|
||||
"subwidgets": [
|
||||
{
|
||||
"widgettype": "Text",
|
||||
"options": {
|
||||
"label": "pbl_team_save"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"widgettype": "VBox",
|
||||
"options": {
|
||||
"backgroundColor": "#FFFFFF",
|
||||
"padding": "20px",
|
||||
"cursor": "pointer"
|
||||
},
|
||||
"binds": [
|
||||
{
|
||||
"wid": "self",
|
||||
"event": "click",
|
||||
"actiontype": "urlwidget",
|
||||
"target": "app.pbl_domain_ext_content",
|
||||
"options": {
|
||||
"url": "{{entire_url('api/pbl_team_list.dspy')}}"
|
||||
},
|
||||
"mode": "replace"
|
||||
}
|
||||
],
|
||||
"subwidgets": [
|
||||
{
|
||||
"widgettype": "Text",
|
||||
"options": {
|
||||
"label": "pbl_team_list"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"widgettype": "VBox",
|
||||
"options": {
|
||||
"backgroundColor": "#FFFFFF",
|
||||
"padding": "20px",
|
||||
"cursor": "pointer"
|
||||
},
|
||||
"binds": [
|
||||
{
|
||||
"wid": "self",
|
||||
"event": "click",
|
||||
"actiontype": "urlwidget",
|
||||
"target": "app.pbl_domain_ext_content",
|
||||
"options": {
|
||||
"url": "{{entire_url('api/pbl_domain_materialize_game_definition.dspy')}}"
|
||||
},
|
||||
"mode": "replace"
|
||||
}
|
||||
],
|
||||
"subwidgets": [
|
||||
{
|
||||
"widgettype": "Text",
|
||||
"options": {
|
||||
"label": "pbl_domain_materialize_game_definition"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"widgettype": "VBox",
|
||||
"id": "pbl_domain_ext_content",
|
||||
"options": {
|
||||
"width": "100%",
|
||||
"flex": "1",
|
||||
"marginTop": "20px"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user