feat(scene): scene 初始化提交(元景项目-初始迭代,开发产线产出)

This commit is contained in:
agent.develop 2026-08-28 13:34:47 +08:00
commit 02323863a0
24 changed files with 683 additions and 0 deletions

8
.gitignore vendored Normal file
View File

@ -0,0 +1,8 @@
build/
*.egg-info/
__pycache__/
*.pyc
*.swp
*.swo
wwwroot/scene/
wwwroot/scene_import/

33
README.md Normal file
View File

@ -0,0 +1,33 @@
# scene 模块
场景管理,含场景导入。
## 功能
- 场景表 `scene` 的 CRUDlist/get/create/update/delete
- 场景导入:`api/scene_import.dspy`(文件上传 → 解析 JSON/JSON Lines/CSV → 批量落库 → 写导入记录)
- 枚举字段 appcodes 字典scene_type / scene_status / import_statuscond parentid=
- 世界下拉(依赖 world 模块)
## 数据表
- `scene`models/scene.json场景表
- 主键 `id`str32
- 唯一索引 `idx_scene_code`code、索引 `idx_scene_world`world_id
- 字典world_id → world、scene_type/status → appcodes_kv
- `scene_import`models/scene_import.json场景导入记录表只读
- 主键 `id`str32、索引 `idx_scene_import_world`world_id
- 字典world_id → world、status → appcodes_kv(parentid='import_status')
## 目录结构
- `scene/`Python 包init.py + __init__.py
- `models/`:表定义 scene.json、scene_import.json
- `json/`CRUD 定义 scene.json、scene_import.json只读
- `wwwroot/`index.ui + import_page.ui + api/*.dspy
- `init/data.json`字典种子数据scene_type / scene_status / import_status
- `scripts/load_path.py`RBAC 路径注册
## 集成方式
宿主应用在入口 `init()` 中调用 `load_scene()` 挂载模块函数。
库名统一通过 `ServerEnv().get_module_dbname("scene")` 获取,禁止硬编码。
## 开发顺序
2依赖 world

15
__init__.py Normal file
View File

@ -0,0 +1,15 @@
from sqlor import sor
def get_dbname():
try:
from ahserver import ServerEnv
return ServerEnv().get_module_dbname("scene")
except Exception:
return "scense"
def list_scenes(params): return sor.R("scene", params)
def get_scene(params): return sor.R("scene", {"id": params.get("id")})
def create_scene(params): return sor.C("scene", params)
def update_scene(params): return sor.U("scene", params)
def delete_scene(params): return sor.D("scene", params)
def load_scene(): return {"module": "scene", "dbname": get_dbname()}

30
init/data.json Normal file
View File

@ -0,0 +1,30 @@
{
"appcodes": [
{
"parentid": "scene_type",
"parentname": "场景类型",
"items": [
{"k": "0", "v": "默认"},
{"k": "1", "v": "室内"},
{"k": "2", "v": "室外"}
]
},
{
"parentid": "scene_status",
"parentname": "场景状态",
"items": [
{"k": "0", "v": "启用"},
{"k": "1", "v": "停用"}
]
},
{
"parentid": "import_status",
"parentname": "导入状态",
"items": [
{"k": "0", "v": "成功"},
{"k": "1", "v": "部分成功"},
{"k": "2", "v": "失败"}
]
}
]
}

27
json/scene.json Normal file
View File

@ -0,0 +1,27 @@
{
"tblname": "scene",
"title": "场景管理",
"params": {
"sortby": ["created_at desc"],
"data_filter": {
"AND": [
{"field": "name", "op": "LIKE", "var": "name"},
{"field": "world_id", "op": "=", "var": "world_id"}
]
},
"browserfields": {
"exclouded": ["id", "description", "config_json"],
"alters": {
"world_id": {"uitype": "code", "dataurl": "{{entire_url('../api/get_search_world_id.dspy')}}"},
"scene_type": {"uitype": "code", "dataurl": "{{entire_url('../api/get_search_scene_type.dspy')}}"},
"status": {"uitype": "code", "dataurl": "{{entire_url('../api/get_search_status.dspy')}}"}
}
},
"editexclouded": ["created_at", "updated_at"],
"editable": {
"new_data_url": "{{entire_url('../api/create_scene.dspy')}}",
"update_data_url": "{{entire_url('../api/scene_update.dspy')}}",
"delete_data_url": "{{entire_url('../api/scene_delete.dspy')}}"
}
}
}

10
json/scene_import.json Normal file
View File

@ -0,0 +1,10 @@
{
"tblname": "scene_import",
"title": "场景导入记录",
"params": {
"sortby": ["created_at desc"],
"browserfields": {
"exclouded": ["id"]
}
}
}

31
models/scene.json Normal file
View File

@ -0,0 +1,31 @@
{
"summary": [
{
"name": "scene",
"title": "场景表",
"primary": ["id"],
"catelog": "entity"
}
],
"fields": [
{"name": "id", "title": "主键ID", "type": "str", "length": 32, "nullable": "no"},
{"name": "world_id", "title": "所属世界", "type": "str", "length": 32, "nullable": "no"},
{"name": "name", "title": "场景名称", "type": "str", "length": 255, "nullable": "no"},
{"name": "code", "title": "场景编码", "type": "str", "length": 64, "nullable": "no"},
{"name": "description", "title": "描述", "type": "text", "nullable": "yes"},
{"name": "scene_type", "title": "场景类型", "type": "str", "length": 16, "nullable": "no", "default": "0"},
{"name": "status", "title": "状态", "type": "str", "length": 16, "nullable": "no", "default": "0"},
{"name": "config_json", "title": "场景配置JSON", "type": "text", "nullable": "yes"},
{"name": "created_at", "title": "创建时间", "type": "timestamp", "nullable": "no"},
{"name": "updated_at", "title": "更新时间", "type": "timestamp", "nullable": "yes"}
],
"indexes": [
{"name": "idx_scene_code", "idxtype": "unique", "idxfields": ["code"]},
{"name": "idx_scene_world", "idxtype": "index", "idxfields": ["world_id"]}
],
"codes": [
{"field": "world_id", "table": "world", "valuefield": "id", "textfield": "name"},
{"field": "scene_type", "table": "appcodes_kv", "valuefield": "k", "textfield": "v", "cond": "parentid='scene_type'"},
{"field": "status", "table": "appcodes_kv", "valuefield": "k", "textfield": "v", "cond": "parentid='scene_status'"}
]
}

27
models/scene_import.json Normal file
View File

@ -0,0 +1,27 @@
{
"summary": [
{
"name": "scene_import",
"title": "场景导入记录表",
"primary": ["id"],
"catelog": "entity"
}
],
"fields": [
{"name": "id", "title": "主键ID", "type": "str", "length": 32, "nullable": "no"},
{"name": "world_id", "title": "目标世界", "type": "str", "length": 32, "nullable": "no"},
{"name": "file_name", "title": "导入文件名", "type": "str", "length": 255, "nullable": "no"},
{"name": "total", "title": "总条数", "type": "int", "nullable": "no", "default": "0"},
{"name": "success", "title": "成功条数", "type": "int", "nullable": "no", "default": "0"},
{"name": "fail", "title": "失败条数", "type": "int", "nullable": "no", "default": "0"},
{"name": "status", "title": "导入状态", "type": "str", "length": 16, "nullable": "no", "default": "0"},
{"name": "created_at", "title": "导入时间", "type": "timestamp", "nullable": "no"}
],
"indexes": [
{"name": "idx_scene_import_world", "idxtype": "index", "idxfields": ["world_id"]}
],
"codes": [
{"field": "world_id", "table": "world", "valuefield": "id", "textfield": "name"},
{"field": "status", "table": "appcodes_kv", "valuefield": "k", "textfield": "v", "cond": "parentid='import_status'"}
]
}

View File

@ -0,0 +1,20 @@
{
"summary": [{"name": "scene_import_log", "title": "场景导入日志表", "primary": ["id"], "catelog": "relation"}],
"fields": [
{"name": "id", "title": "主键ID", "type": "str", "length": 32, "nullable": "no"},
{"name": "world_id", "title": "所属世界ID", "type": "str", "length": 32, "nullable": "no"},
{"name": "file_name", "title": "导入文件名", "type": "str", "length": 255, "nullable": "no"},
{"name": "total", "title": "总记录数", "type": "int", "nullable": "no", "default": "0"},
{"name": "success", "title": "成功数", "type": "int", "nullable": "no", "default": "0"},
{"name": "failed", "title": "失败数", "type": "int", "nullable": "no", "default": "0"},
{"name": "status", "title": "导入状态", "type": "str", "length": 16, "nullable": "no", "default": "processing"},
{"name": "detail", "title": "失败明细", "type": "text", "nullable": "yes"},
{"name": "created_by", "title": "导入人ID", "type": "str", "length": 32, "nullable": "yes"},
{"name": "created_at", "title": "导入时间", "type": "timestamp", "nullable": "no"}
],
"indexes": [{"name": "idx_scene_ilog_world", "idxtype": "index", "idxfields": ["world_id"]}],
"codes": [
{"field": "world_id", "table": "world", "valuefield": "id", "textfield": "name"},
{"field": "status", "table": "appcodes_kv", "valuefield": "k", "textfield": "v", "cond": "parentid='import_status'"}
]
}

13
pyproject.toml Normal file
View File

@ -0,0 +1,13 @@
[build-system]
requires = ["setuptools>=45", "wheel"]
build-backend = "setuptools.build_meta"
[project]
name = "scene"
version = "1.0.0"
requires-python = ">=3.8"
dependencies = ["sqlor", "bricks_for_python"]
[tool.setuptools.packages.find]
where = ["."]
include = ["scene*"]

29
scene/__init__.py Normal file
View File

@ -0,0 +1,29 @@
# -*- coding: utf-8 -*-
"""scene 模块包入口。"""
from .init import (
load_scene,
list_scenes,
get_scene,
create_scene,
update_scene,
delete_scene,
import_scenes,
parse_scene_file,
get_world_options,
get_scene_type_options,
get_scene_status_options,
)
__all__ = [
"load_scene",
"list_scenes",
"get_scene",
"create_scene",
"update_scene",
"delete_scene",
"import_scenes",
"parse_scene_file",
"get_world_options",
"get_scene_type_options",
"get_scene_status_options",
]

235
scene/init.py Normal file
View File

@ -0,0 +1,235 @@
# -*- coding: utf-8 -*-
"""scene 模块初始化与业务函数。
通过 load_scene() 将函数注册到 ServerEnv .ui/.dspy 直接调用
库名统一从宿主应用获取禁止硬编码 DBNAME
依赖 world 模块world_id 外键
"""
import io
import json
import csv
from appPublic.uniqueID import getID
from appPublic.timeUtils import curDateString, timestampstr
from sqlor.dbpools import DBPools
# 场景表允许落库的字段(导入时过滤非法字段)
_SCENE_FIELDS = {
"id", "world_id", "name", "code", "description",
"scene_type", "status", "config_json", "created_at", "updated_at",
}
def _get_dbname():
"""取库名(禁止硬编码 DBNAME"""
from ahserver import ServerEnv
return ServerEnv().get_module_dbname("scene")
def _gen_code():
"""自动生成场景编码。"""
return "S" + getID()[:10]
async def list_scenes(params_kw=None):
"""场景列表查询(供下拉/菜单)。返回 [{value:id, text:name}]。"""
params = params_kw or {}
dbname = _get_dbname()
async with DBPools().sqlorContext(dbname) as sor:
recs = await sor.R("scene", params)
return [{"value": r.id, "text": r.name} for r in recs]
async def get_scene(params_kw=None):
"""按 id 查询场景。"""
params = params_kw or {}
dbname = _get_dbname()
async with DBPools().sqlorContext(dbname) as sor:
return await sor.R("scene", {"id": params.get("id")})
async def create_scene(params_kw=None):
"""创建场景(自动 id/code/created_at"""
ns = dict(params_kw or {})
for k in list(ns.keys()):
if k.endswith("_text"):
ns.pop(k, None)
ns["id"] = ns.get("id") or getID()
ns["code"] = ns.get("code") or _gen_code()
ns["created_at"] = ns.get("created_at") or curDateString()
dbname = _get_dbname()
async with DBPools().sqlorContext(dbname) as sor:
await sor.C("scene", ns)
return {"success": True, "id": ns["id"]}
async def update_scene(params_kw=None):
"""更新场景(写 updated_at"""
ns = dict(params_kw or {})
for k in list(ns.keys()):
if k.endswith("_text"):
ns.pop(k, None)
ns["updated_at"] = curDateString()
dbname = _get_dbname()
async with DBPools().sqlorContext(dbname) as sor:
await sor.U("scene", ns)
return {"success": True}
async def delete_scene(params_kw=None):
"""删除场景。"""
params = params_kw or {}
dbname = _get_dbname()
async with DBPools().sqlorContext(dbname) as sor:
await sor.D("scene", {"id": params.get("id")})
return {"success": True}
async def get_world_options(params_kw=None):
"""世界下拉选项world 表)。返回 [{value:id, text:name}]。"""
from ahserver import ServerEnv
dbname = ServerEnv().get_module_dbname("world")
async with DBPools().sqlorContext(dbname) as sor:
recs = await sor.R("world", {"page": 1, "rows": 1000})
return [{"value": r.id, "text": r.name} for r in recs]
async def get_scene_type_options(params_kw=None):
"""场景类型下拉appcodes_kv parentid='scene_type')。"""
dbname = _get_dbname()
async with DBPools().sqlorContext(dbname) as sor:
recs = await sor.R("appcodes_kv", {"parentid": "scene_type", "page": 1, "rows": 1000})
return [{"value": r.k, "text": r.v} for r in recs]
async def get_scene_status_options(params_kw=None):
"""场景状态下拉appcodes_kv parentid='scene_status')。"""
dbname = _get_dbname()
async with DBPools().sqlorContext(dbname) as sor:
recs = await sor.R("appcodes_kv", {"parentid": "scene_status", "page": 1, "rows": 1000})
return [{"value": r.k, "text": r.v} for r in recs]
def parse_scene_file(content, filename=""):
"""解析上传文件内容为 [{...}] 场景记录列表。
支持JSON 数组 / JSON Lines / CSV首行表头
解析失败返回 None
"""
if content is None:
return None
if isinstance(content, bytes):
try:
content = content.decode("utf-8")
except UnicodeDecodeError:
try:
content = content.decode("gbk", errors="ignore")
except Exception:
return None
if not isinstance(content, str):
return None
text = content.strip()
if not text:
return []
# JSON 数组 / 单对象
try:
data = json.loads(text)
if isinstance(data, list):
return [d for d in data if isinstance(d, dict)]
if isinstance(data, dict):
return [data]
except Exception:
pass
# JSON Lines
lines = [ln for ln in text.splitlines() if ln.strip()]
if lines and lines[0].strip().startswith("{"):
rows = []
for ln in lines:
try:
obj = json.loads(ln)
if isinstance(obj, dict):
rows.append(obj)
except Exception:
rows.append(None)
if any(r is not None for r in rows):
return [r for r in rows if r is not None]
# CSV
try:
reader = csv.DictReader(io.StringIO(text))
rows = [dict(r) for r in reader]
if rows:
return rows
except Exception:
pass
return None
async def import_scenes(params_kw=None):
"""场景导入:逐条校验 → sor.C("scene") → 统计 → 写导入记录。
输入world_idfile_namerowsparse_scene_file 解析后的记录列表
输出{success, total, success_count, fail}
"""
params = params_kw or {}
world_id = params.get("world_id") or "0"
file_name = params.get("file_name") or ""
rows = params.get("rows") or []
total = len(rows)
n_success = 0
n_fail = 0
dbname = _get_dbname()
async with DBPools().sqlorContext(dbname) as sor:
for row in rows:
try:
if not isinstance(row, dict):
raise ValueError("非法记录")
name = str(row.get("name") or "").strip()
code = str(row.get("code") or "").strip()
if not name or not code:
raise ValueError("name/code 不能为空")
ns = {k: v for k, v in row.items() if k in _SCENE_FIELDS}
ns["id"] = getID()
ns["world_id"] = world_id if world_id != "0" else (row.get("world_id") or "0")
ns["name"] = name
ns["code"] = code
ns["scene_type"] = ns.get("scene_type") or "0"
ns["status"] = ns.get("status") or "0"
ns["created_at"] = curDateString()
ns.pop("updated_at", None)
await sor.C("scene", ns)
n_success += 1
except Exception:
n_fail += 1
import_ns = {
"id": getID(),
"world_id": world_id,
"file_name": file_name,
"total": total,
"success": n_success,
"fail": n_fail,
"status": "0",
"created_at": curDateString(),
}
await sor.C("scene_import", import_ns)
return {"success": True, "total": total, "success_count": n_success, "fail": n_fail}
def load_scene(env=None):
"""挂载 scene 模块函数到 ServerEnv。"""
if env is None:
from ahserver import ServerEnv
env = ServerEnv()
env.list_scenes = list_scenes
env.get_scene = get_scene
env.create_scene = create_scene
env.create_scenes = create_scene
env.update_scene = update_scene
env.update_scenes = update_scene
env.delete_scene = delete_scene
env.delete_scenes = delete_scene
env.import_scenes = import_scenes
env.parse_scene_file = parse_scene_file
env.get_world_options = get_world_options
env.get_scene_type_options = get_scene_type_options
env.get_scene_status_options = get_scene_status_options
return env

64
scripts/load_path.py Normal file
View File

@ -0,0 +1,64 @@
# -*- coding: utf-8 -*-
"""scene 模块 RBAC 路径注册(显式路径,禁止通配符)。"""
import os
import sys
def find_sage_root():
candidates = [
os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "..")),
os.path.expanduser("~/repos/sage"),
os.path.expanduser("~/sage"),
]
for c in candidates:
if os.path.isdir(os.path.join(c, "wwwroot")) and os.path.isdir(os.path.join(c, "py3", "bin")):
return c
return None
SAGE_ROOT = find_sage_root()
PATHS_ANY = [
]
PATHS_LOGINED = [
"/scene",
"/scene/index.ui",
"/scene/import_page.ui",
"/scene/scene",
"/scene/scene/index.ui",
"/scene/scene/get_scene.dspy",
"/scene/scene/add_scene.dspy",
"/scene/scene/update_scene.dspy",
"/scene/scene/delete_scene.dspy",
"/scene/scene_import",
"/scene/scene_import/index.ui",
"/scene/scene_import/get_scene_import.dspy",
"/scene/api/list_scenes.dspy",
"/scene/api/create_scene.dspy",
"/scene/api/scene_update.dspy",
"/scene/api/scene_delete.dspy",
"/scene/api/get_search_world_id.dspy",
"/scene/api/get_search_scene_type.dspy",
"/scene/api/get_search_status.dspy",
"/scene/api/scene_import.dspy",
]
def main():
if SAGE_ROOT is None:
print("Sage root not found, skip RBAC registration.")
return
sys.path.insert(0, SAGE_ROOT)
try:
from set_role_perm import set_role_perm
except ImportError:
print("set_role_perm not found, skip RBAC registration.")
return
for p in PATHS_ANY:
set_role_perm(p, "any")
for p in PATHS_LOGINED:
set_role_perm(p, "logined")
if __name__ == "__main__":
main()

72
skill/SKILL.md Normal file
View File

@ -0,0 +1,72 @@
---
name: scene
description: scene 模块技能文档——场景管理(场景表 CRUD + 场景导入)。提供 scene/scene_import 两张表、CRUD、列表查询接口、文件导入api/scene_import.dspy通过 load_scene() 挂载。枚举字段用 appcodes 字典cond parentid=)。
---
# scene 模块
## 概述
场景管理,含场景导入。依赖 worldworld_id 外键、appbaseappcodes 字典、rbac权限
## 数据模型
### 表 `scene`models/scene.json
| 字段 | 类型 | 说明 |
|------|------|------|
| id | str(32) | 主键 |
| world_id | str(32) | 所属世界→world.id |
| name | str(255) | 场景名称 |
| code | str(64) | 场景编码(唯一) |
| description | text | 描述 |
| scene_type | str(16) | 场景类型appcodes scene_type默认 '0' |
| status | str(16) | 状态appcodes scene_status默认 '0' |
| config_json | text | 场景配置 JSON |
| created_at | timestamp | 创建时间 |
| updated_at | timestamp | 更新时间 |
- 主键 `["id"]`;唯一索引 `idx_scene_code(code)`;索引 `idx_scene_world(world_id)`
- 字典world_id → world(id/name)、scene_type → appcodes_kv(parentid='scene_type')、status → appcodes_kv(parentid='scene_status')
### 表 `scene_import`models/scene_import.json
| 字段 | 类型 | 说明 |
|------|------|------|
| id | str(32) | 主键 |
| world_id | str(32) | 目标世界 |
| file_name | str(255) | 导入文件名 |
| total | int | 总条数(默认 0 |
| success | int | 成功条数(默认 0 |
| fail | int | 失败条数(默认 0 |
| status | str(16) | 导入状态appcodes import_status默认 '0' |
| created_at | timestamp | 导入时间 |
- 主键 `["id"]`;索引 `idx_scene_import_world(world_id)`
- 字典world_id → world、status → appcodes_kv(parentid='import_status')
## 关键接口
### ServerEnv 注册函数load_scene() 挂载)
- `list_scenes(params)``[{value:id, text:name}]`
- `get_scene({id})` → 单条
- `create_scene(ns)` → 自动生成 id/code、写 created_at
- `update_scene(ns)` → 写 updated_at、剔除 `_text` 后缀
- `delete_scene({id})`
- `import_scenes({world_id, file_name, rows})``{success, total, success_count, fail}`
- `parse_scene_file(content, filename)``[{...}]`JSON 数组 / JSON Lines / CSV
- `get_world_options()` / `get_scene_type_options()` / `get_scene_status_options()``[{value, text}]`
### wwwroot/api/*.dspy
- `list_scenes.dspy``create_scene.dspy``scene_update.dspy``scene_delete.dspy`
- `get_search_world_id.dspy` / `get_search_scene_type.dspy` / `get_search_status.dspy`:字典下拉
- `scene_import.dspy`场景导入world_id + file → parse_scene_file → import_scenes
## 陷阱
- 库名禁止硬编码:.py 用 `ServerEnv().get_module_dbname("scene")`.dspy 用 `get_module_dbname("scene")`(全局)
- world 下拉用 `ServerEnv().get_module_dbname("world")`world 表在 world 模块库)
- dspy 无 importjson/get_sor_context/debug/params_kw 均为预加载全局)
- `create_scene` 必须设置 `created_at = curDateString()`,否则 sor.C 静默丢记录
- 导入时 `scene_import` 记录必须设置 `created_at`,字段 total/success/fail 为 int
- `import_scenes` 返回用 `success_count` 表示成功条数(避免与布尔 `success` 键冲突)
- CRUD json `new_data_url` 指向自定义 `api/create_scene.dspy`scene_import 为只读列表,无 editable
## 依赖
- worldworld_id 外键 + 世界下拉、appbaseappcodes 字典、rbac权限

View File

@ -0,0 +1 @@
return await create_scene(params_kw)

View File

@ -0,0 +1 @@
return await get_scene_type_options(params_kw)

View File

@ -0,0 +1 @@
return await get_scene_status_options(params_kw)

View File

@ -0,0 +1 @@
return await get_world_options(params_kw)

View File

@ -0,0 +1 @@
return await list_scenes(params_kw)

View File

@ -0,0 +1 @@
return await delete_scene(params_kw)

View File

@ -0,0 +1,26 @@
# scene_import.dspy: 场景导入(文件上传 → 解析 → 批量落库 → 写导入记录)
world_id = params_kw.get('world_id')
file = params_kw.get('file')
file_name = params_kw.get('file_name') or ''
content = None
if file is not None:
if hasattr(file, 'file'):
file_name = file_name or getattr(file, 'filename', '') or ''
try:
content = file.file.read()
except Exception:
content = None
elif hasattr(file, 'read'):
try:
content = file.read()
except Exception:
content = None
else:
content = file
if content is None:
return {'success': False, 'message': '未接收到上传文件'}
rows = parse_scene_file(content, file_name)
if rows is None:
return {'success': False, 'message': '文件解析失败,仅支持 JSON 数组 / JSON Lines / CSV'}
result = await import_scenes({'world_id': world_id, 'file_name': file_name, 'rows': rows})
return result

View File

@ -0,0 +1 @@
return await update_scene(params_kw)

18
wwwroot/import_page.ui Normal file
View File

@ -0,0 +1,18 @@
{
"widgettype": "VBox",
"options": {"width": "100%", "height": "100%", "padding": "20px"},
"subwidgets": [
{"widgettype": "Text", "options": {"label": "场景导入", "fontSize": "20px"}},
{"widgettype": "Text", "options": {"label": "支持 JSON 数组 / JSON Lines / CSV表头name,code,description,scene_type,status,config_json", "fontSize": "12px", "color": "#888888"}},
{"widgettype": "Form", "options": {
"submit_url": "{{entire_url('/scene/api/scene_import.dspy')}}",
"method": "POST"
}, "subwidgets": [
{"widgettype": "Select", "options": {"name": "world_id", "label": "目标世界", "required": "true",
"dataurl": "{{entire_url('/scene/api/get_search_world_id.dspy')}}", "valueField": "value", "textField": "text"}},
{"widgettype": "UiFile", "options": {"name": "file", "label": "导入文件", "required": "true"}},
{"widgettype": "Button", "options": {"label": "开始导入"}}
]},
{"widgettype": "VBox", "id": "app.scene_import_content", "options": {"width": "100%", "flex": "1", "marginTop": "20px"}}
]
}

18
wwwroot/index.ui Normal file
View File

@ -0,0 +1,18 @@
{
"widgettype": "VBox",
"options": {"width": "100%", "height": "100%", "padding": "20px"},
"subwidgets": [
{"widgettype": "Text", "options": {"label": "场景管理", "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.scene_content",
"options": {"url": "{{entire_url('/scene/scene')}}"}, "mode": "replace"}],
"subwidgets": [{"widgettype": "Text", "options": {"label": "场景列表"}}]},
{"widgettype": "VBox", "options": {"backgroundColor": "#FFFFFF", "padding": "20px", "cursor": "pointer"},
"binds": [{"wid": "self", "event": "click", "actiontype": "urlwidget", "target": "app.scene_content",
"options": {"url": "{{entire_url('/scene/import_page.ui')}}"}, "mode": "replace"}],
"subwidgets": [{"widgettype": "Text", "options": {"label": "场景导入"}}]}
]},
{"widgettype": "VBox", "id": "app.scene_content", "options": {"width": "100%", "flex": "1", "marginTop": "20px"}}
]
}