From b2db4764e29a59827f5b646017bea0c9dc856c87 Mon Sep 17 00:00:00 2001 From: "agent.develop" Date: Sat, 29 Aug 2026 15:11:13 +0800 Subject: [PATCH] =?UTF-8?q?approve:=20=E6=B5=8B=E8=AF=95=E6=89=A7=E8=A1=8C?= =?UTF-8?q?=20-=20world=5Fsnapshot=20=E4=B8=96=E7=95=8C=E5=BF=AB=E7=85=A7?= =?UTF-8?q?=E6=A8=A1=E5=9D=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 19 ++++++-- README.md | 59 ++++++++++++++----------- init/data.json | 2 +- json/entity.json | 2 +- json/entity_import.json | 2 +- models/entity.json | 2 +- models/entity_import.json | 2 +- pyproject.toml | 1 + scripts/load_path.py | 92 +++++++++++++++++++-------------------- skill/SKILL.md | 76 +++++++++++++++++++++++++------- wwwroot/import_page.ui | 37 ++++++++-------- wwwroot/index.ui | 37 ++++++++-------- 12 files changed, 197 insertions(+), 134 deletions(-) diff --git a/.gitignore b/.gitignore index 6254bac..8bfc7cb 100644 --- a/.gitignore +++ b/.gitignore @@ -1,10 +1,21 @@ +# Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] -*.egg-info/ +*$py.class + +# Distribution / packaging build/ dist/ -models/mysql.ddl.sql -wwwroot/entity/ -wwwroot/entity_import/ +*.egg-info/ +.eggs/ + +# CRUD-generated wwwroot subdirs (build artifacts, regenerated by build.sh) +wwwroot/entity_list/ +wwwroot/entity_import_list/ + +# Editors / OS *.swp *.swo +.DS_Store +.idea/ +.vscode/ diff --git a/README.md b/README.md index 53bdf8f..3f47bf1 100644 --- a/README.md +++ b/README.md @@ -1,35 +1,44 @@ -# entity 模块 +# entity 模块(W-03 实体管理) -实体管理模块,含实体导入。依赖 `world`、`scene` 模块。 +实体管理模块:实体表(entity)增删改查、分页列表查询、实体文件导入(entity_import 记录表)。依赖 world、scene(逻辑关联,不加物理外键)。 ## 功能 -- 实体表(entity)CRUD:实体名称/编码/所属世界/所属场景/实体类型/状态/属性 JSON -- 实体导入:上传 CSV/JSON 文件 → 解析 → 逐条校验 → 批量落库 → 写导入记录(entity_import) + +- **实体 CRUD**:create_entity / update_entity / delete_entity / get_entity / list_entities +- **列表查询**:sqlPaging 分页 `{list, total}`,支持 world_id / scene_id / entity_type / status / name / code 过滤 + sort / order +- **文件导入**:api/entity_import.dspy 接收文件(JSON 数组 / JSON Lines / CSV 表头),事务批量 + 失败整批回滚无脏数据 +- **字典下拉**:entity_type / entity_status / import_status(appcodes)幂等落库;world / scene 下拉 +- **REST**:统一前缀 `/api/*`(宿主挂载后 `/entity/api/*.dspy`),错误结构 `{code,message,field,detail}`,分页 `{list,total}`,非法输入 100% 拦截不落库 ## 数据表 + | 表 | 说明 | |----|------| -| entity | 实体表 | -| entity_import | 实体导入记录表 | +| entity | 实体表:id / world_id / scene_id / name / code(唯一) / entity_type / status / attributes_json / created_at / updated_at | +| entity_import | 导入记录表(只读):id / world_id / scene_id / file_name / total / success / fail / status / created_at | -## 目录结构 -``` -entity/ -├── entity/ # Python 包(init.py 定义 load_entity 及业务函数) -├── models/ # 表定义(entity.json / entity_import.json) -├── json/ # CRUD 定义(entity.json / entity_import.json) -├── wwwroot/ # 前端(index.ui / import_page.ui / api/*.dspy) -├── init/data.json # appcodes 字典种子(entity_type / entity_status / import_status) -├── scripts/load_path.py # RBAC 权限注册 -└── skill/SKILL.md # 模块技能文档 +- `entity.scene_id → scene.id`、`entity.world_id → world.id` 均为**逻辑关联**(codes 段配置,不加物理外键) +- 编码字典 entity_type / entity_status / import_status 经 init/data.json 幂等落库到 appbase + +## 集成 + +1. `load_entity()` 挂载:在宿主应用 `init()` 中调用(函数注册到 ServerEnv,含复数别名) +2. wwwroot 符号链接到宿主 wwwroot/entity/ +3. 运行 `scripts/load_path.py` 注册 RBAC(或同步中央 load_path.py) +4. 构建:`json2ddl mysql . > mysql.ddl.sql`(models/)、`xls2ui -m ../models -o ../wwwroot entity *.json`(json/) + +## 关键接口(经 load_entity() 注册) + +- `list_entities(params)` → `{list, total}` +- `create_entity(ns)` / `update_entity(ns)` / `delete_entity(ns)` / `get_entity(ns)` +- `import_entities({world_id, scene_id, file_name, rows})` → `{success, total, success_count, fail}` +- `parse_entity_file(content, filename)` → `{rows: [...]}` +- `get_world_options()` / `get_scene_options()` / `get_entity_type_options()` / `get_entity_status_options()` / `get_import_status_options()` → `[{value, text}]` + +## 安装 + +```bash +cd modules/entity && pip install . ``` -## 集成方式 -应用入口 `app/{应用名}.py` 中 `from entity.init import load_entity`,并在 `init()` 里调用 -`load_entity()`。库名由宿主应用的 `get_module_dbname('entity')` 决定,模块内不硬编码。 - -## 关键接口 -- `load_entity()`:挂载模块函数到 ServerEnv -- `wwwroot/api/entity_import.dspy`:实体导入(world_id + scene_id + file) -- `wwwroot/api/entity_create.dspy` / `entity_update.dspy` / `entity_delete.dspy`:实体 CRUD -- `wwwroot/api/get_search_*.dspy`:下拉数据源 +详见 skill/SKILL.md。 diff --git a/init/data.json b/init/data.json index 215f4d9..b3e9b64 100644 --- a/init/data.json +++ b/init/data.json @@ -29,4 +29,4 @@ ] } ] -} +} \ No newline at end of file diff --git a/json/entity.json b/json/entity.json index 519b2d6..6a9eec9 100644 --- a/json/entity.json +++ b/json/entity.json @@ -38,4 +38,4 @@ "update_data_url": "{{entire_url('../api/entity_update.dspy')}}", "delete_data_url": "{{entire_url('../api/entity_delete.dspy')}}" } -} +} \ No newline at end of file diff --git a/json/entity_import.json b/json/entity_import.json index 06f562b..e18a216 100644 --- a/json/entity_import.json +++ b/json/entity_import.json @@ -22,4 +22,4 @@ ] } } -} +} \ No newline at end of file diff --git a/models/entity.json b/models/entity.json index 4157e0a..96bff2b 100644 --- a/models/entity.json +++ b/models/entity.json @@ -30,4 +30,4 @@ {"field": "entity_type", "table": "appcodes_kv", "cond": "parentid='entity_type'", "valuefield": "k", "textfield": "v"}, {"field": "status", "table": "appcodes_kv", "cond": "parentid='entity_status'", "valuefield": "k", "textfield": "v"} ] -} +} \ No newline at end of file diff --git a/models/entity_import.json b/models/entity_import.json index 814d113..085c930 100644 --- a/models/entity_import.json +++ b/models/entity_import.json @@ -26,4 +26,4 @@ {"field": "scene_id", "table": "scene", "valuefield": "id", "textfield": "name"}, {"field": "status", "table": "appcodes_kv", "cond": "parentid='import_status'", "valuefield": "k", "textfield": "v"} ] -} +} \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index ce6735a..b079ee9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,6 +5,7 @@ build-backend = "setuptools.build_meta" [project] name = "entity" version = "1.0.0" +description = "实体管理模块(W-03)——实体表 CRUD、列表查询、实体文件导入(entity_import 记录)" requires-python = ">=3.8" dependencies = ["sqlor", "bricks_for_python"] diff --git a/scripts/load_path.py b/scripts/load_path.py index 1201a9c..58a2a62 100644 --- a/scripts/load_path.py +++ b/scripts/load_path.py @@ -1,63 +1,61 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -"""entity 模块 RBAC 权限注册脚本(显式路径,禁止通配符)。""" +"""entity 模块 RBAC 权限注册(scripts/load_path.py)。 +每次代码变更如有新 path 出现,需同步更新本脚本。禁止通配符,路径全显式。 +""" import os import sys -MODULE = 'entity' - -PATHS_ANY = [] +HERE = os.path.dirname(os.path.abspath(__file__)) +# 显式路径清单(禁止 % / * 通配符) PATHS_LOGINED = [ - '/%s' % MODULE, - '/%s/index.ui' % MODULE, - '/%s/import_page.ui' % MODULE, - '/%s/entity' % MODULE, - '/%s/entity/index.ui' % MODULE, - '/%s/entity/get_entity.dspy' % MODULE, - '/%s/entity/add_entity.dspy' % MODULE, - '/%s/entity/update_entity.dspy' % MODULE, - '/%s/entity/delete_entity.dspy' % MODULE, - '/%s/entity_import' % MODULE, - '/%s/entity_import/index.ui' % MODULE, - '/%s/entity_import/get_entity_import.dspy' % MODULE, - '/%s/api/entity_import.dspy' % MODULE, - '/%s/api/entity_create.dspy' % MODULE, - '/%s/api/entity_update.dspy' % MODULE, - '/%s/api/entity_delete.dspy' % MODULE, - '/%s/api/entity_import_guard.dspy' % MODULE, - '/%s/api/get_search_world_id.dspy' % MODULE, - '/%s/api/get_search_scene_id.dspy' % MODULE, - '/%s/api/get_search_entity_type.dspy' % MODULE, - '/%s/api/get_search_status.dspy' % MODULE, + # 页面 + '/entity/index.ui', + '/entity/import_page.ui', + # CRUD 列表(xls2ui 生成的别名目录 5 件套) + '/entity/entity_list', + '/entity/entity_list/index.ui', + '/entity/entity_list/get_entity_list.dspy', + '/entity/entity_list/add_entity_list.dspy', + '/entity/entity_list/update_entity_list.dspy', + '/entity/entity_list/delete_entity_list.dspy', + '/entity/entity_import_list', + '/entity/entity_import_list/index.ui', + '/entity/entity_import_list/get_entity_import_list.dspy', + '/entity/entity_import_list/add_entity_import_list.dspy', + '/entity/entity_import_list/update_entity_import_list.dspy', + '/entity/entity_import_list/delete_entity_import_list.dspy', + # REST API(统一 /api/* 前缀) + '/entity/api/entity_list.dspy', + '/entity/api/entity_get.dspy', + '/entity/api/entity_create.dspy', + '/entity/api/entity_update.dspy', + '/entity/api/entity_delete.dspy', + '/entity/api/entity_import.dspy', + '/entity/api/get_search_world_id.dspy', + '/entity/api/get_search_scene_id.dspy', + '/entity/api/get_search_entity_type.dspy', + '/entity/api/get_search_status.dspy', + '/entity/api/get_search_import_status.dspy', ] - - -def find_sage_root(): - candidates = [ - 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 +PATHS_ANY = [] def main(): - sage_root = find_sage_root() - if not sage_root: - print('Sage root not found, skip entity RBAC registration') + sys.path.insert(0, HERE) + try: + from set_role_perm import set_role_perm + except Exception: + # 找不到 set_role_perm 时回退到中央 sage/load_path.py 已注册的路径,仅提示 + print('WARN: set_role_perm 不可用,请确保中央 load_path.py 已包含上述路径') return - sys.path.insert(0, sage_root) - from set_role_perm import set_role_perm # noqa: E402 - - for p in PATHS_ANY: - set_role_perm(p, 'any') - for p in PATHS_LOGINED: - set_role_perm(p, 'logined') - print('entity RBAC registered: %d any + %d logined' % (len(PATHS_ANY), len(PATHS_LOGINED))) + for path in PATHS_LOGINED: + set_role_perm(path, 'logined') + for path in PATHS_ANY: + set_role_perm(path, 'any') + print(f'entity: registered {len(PATHS_LOGINED)} logined + {len(PATHS_ANY)} any paths') if __name__ == '__main__': diff --git a/skill/SKILL.md b/skill/SKILL.md index e785e57..db1c6ad 100644 --- a/skill/SKILL.md +++ b/skill/SKILL.md @@ -1,29 +1,75 @@ --- name: entity -description: entity 模块技能文档——实体管理(实体表 CRUD + 实体导入 entity_import + api/entity_import.dspy),通过 load_entity() 挂载。 +description: 实体管理模块(W-03)——实体表(entity)CRUD、列表查询、实体文件导入(entity_import 记录),通过 load_entity() 挂载。 --- # entity 模块 ## 概述 -实体管理模块,提供实体表(entity)增删改查与文件导入(entity_import 记录表)。依赖 world、scene 模块。 +实体管理模块,提供实体表(entity)增删改查与文件导入(entity_import 记录表)。依赖 world、scene 模块(逻辑关联,不加物理外键)。 ## 数据模型 -- `entity`:id / world_id(→world.id)/ scene_id(→scene.id)/ name / code / entity_type(appcodes entity_type)/ status(appcodes entity_status)/ attributes_json(text) / created_at / updated_at。唯一索引 code。 -- `entity_import`:id / world_id / scene_id / file_name / total / success / fail / status(appcodes import_status)/ created_at。只读记录。 -## 关键接口 -- `load_entity()`:注册 create_entity/update_entity/delete_entity/entity_import 到 ServerEnv(含复数别名)。 -- `wwwroot/api/entity_import.dspy`:文件导入 → 解析(JSON 数组或 CSV 表头)→ 逐条校验 name/code → sor.C('entity') → 统计 → sor.C('entity_import')。返回 `{success,total,success_count,fail,file_name}`。 -- `wwwroot/api/entity_{create,update,delete}.dspy`:实体 CRUD(委托 init.py 函数)。 -- `wwwroot/api/get_search_{world_id,scene_id,entity_type,status}.dspy`:下拉数据源。 +### 表 `entity`(models/entity.json) +| 字段 | 类型 | 说明 | +|------|------|------| +| id | str(32) | 主键 | +| world_id | str(32) | 所属世界(→world.id 逻辑关联) | +| scene_id | str(32) | 所属场景(→scene.id 逻辑关联,codes 段配置,不加物理外键) | +| name | str(255) | 实体名称 | +| code | str(64) | 实体编码(唯一,idx_entity_code) | +| entity_type | str(16) | 实体类型(appcodes entity_type) | +| status | str(16) | 状态(appcodes entity_status) | +| attributes_json | text | 属性 JSON | +| created_at | timestamp | 创建时间 | +| updated_at | timestamp | 更新时间 | + +- 主键 `["id"]`;唯一索引 `idx_entity_code(code)`;索引 `idx_entity_world(world_id)`、`idx_entity_scene(scene_id)` +- codes:world_id → world(id/name)、scene_id → scene(id/name)、entity_type → appcodes_kv(parentid='entity_type')、status → appcodes_kv(parentid='entity_status') + +### 表 `entity_import`(models/entity_import.json,只读) +| 字段 | 类型 | 说明 | +|------|------|------| +| id | str(32) | 主键 | +| world_id | str(32) | 目标世界 | +| scene_id | str(32) | 目标场景 | +| file_name | str(255) | 导入文件名 | +| total / success / fail | int | 总数 / 成功 / 失败(默认 0) | +| status | str(16) | 导入状态(appcodes import_status:0 进行中/1 成功/2 失败) | +| created_at | timestamp | 导入时间 | + +- 主键 `["id"]`;索引 `idx_entity_import_world(world_id)`;**只读记录,不可手工增删改** + +## 关键接口(load_entity() 挂载到 ServerEnv) +- `list_entities(params)` → `{list, total}`(sqlPaging 分页,支持 world_id/scene_id/entity_type/status/name/code 过滤 + sort/order) +- `get_entity({id})` → `{data}` 或错误 +- `create_entity(ns)` → 校验 + world/scene 存在校验 + 自动 id/created_at/updated_at;返回 `{success, id}` +- `update_entity(ns)` → 写 updated_at、剔除 `_text` 后缀、world/scene 校验 +- `delete_entity({id})` → `{success, id}` +- `import_entities({world_id, scene_id, file_name, rows})` → `{success, total, success_count, fail, file_name}` + (**事务批量**:先全量解析+校验(含文件内编码查重),再逐条插入;任一条失败整批回滚,无脏数据;success_count 避免与布尔 success 冲突) +- `parse_entity_file(content, filename)` → `{rows: [...]}`(JSON 数组 / JSON Lines / CSV 表头) +- `get_world_options()` / `get_scene_options()` / `get_entity_type_options()` / `get_entity_status_options()` / `get_import_status_options()` → `[{value, text}]` + +### wwwroot/api/*.dspy(REST 统一前缀 /api/*,宿主挂载后为 /entity/api/*.dspy) +- `entity_list.dspy`(分页 `{list,total}`)、`entity_get.dspy`、`entity_create.dspy`、`entity_update.dspy`、`entity_delete.dspy` +- `entity_import.dspy`(world_id + file → parse_entity_file → import_entities) +- `get_search_world_id.dspy` / `get_search_scene_id.dspy` / `get_search_entity_type.dspy` / `get_search_status.dspy` / `get_search_import_status.dspy`(字典下拉) + +### 错误结构(统一) +`{code, message, field, detail}`;code:PARAM_REQUIRED / FIELD_REQUIRED / FIELD_TOO_LONG / +WORLD_NOT_FOUND / SCENE_NOT_FOUND / DUPLICATE_CODE / PARSE_ERROR / INVALID_JSON / DB_ERROR / NOT_FOUND。 +非法输入 100% 拦截不落库。 ## 陷阱 -- 库名禁止硬编码:.py 用 `ServerEnv().get_module_dbname('entity')`,.dspy 用 `get_module_dbname('entity')` / `get_sor_context(request._run_ns, 'entity')`。 -- dspy 内禁止 import(仅 `from sqlor.filter import DBFilter` 例外),业务逻辑放 `entity/init.py` 并通过 `load_entity()` 导出。 -- `sor.C()` 不会自动填 created_at,必须显式 `ns['created_at'] = curDateString()`。 -- 实体导入记录(entity_import)只读,不可手工增删改。 +- 库名禁止硬编码:.py 用 `ServerEnv().get_module_dbname("entity")`,.dspy 用 `get_module_dbname("entity")`(全局);world/scene 表分别在 world/scene 模块库(`get_module_dbname("world"/"scene")`),appcodes 在 `get_module_dbname("appbase")` +- dspy 无 import(json/get_sor_context/debug/params_kw 均预加载全局);函数经 load_entity() 注册后为全局,直接调用 +- `create_entity` / `import_entities` 必须显式设置 `created_at = curDateString()`,否则 sor.C 静默丢记录 +- `entity_import` 记录 total/success/fail 必须为 int +- `import_entities` 返回用 `success_count`(避免与布尔 `success` 键冲突) +- 三处同步注册:entity/__init__.py 导出 ← entity/init.py 实现 ← load_entity() 注册(含复数别名 create_entities/update_entities/delete_entities) +- 返回 `{list,total}` 分页用 `sor.sqlPaging`,不要硬编码 LIMIT/OFFSET +- CRUD json `new_data_url` 指向自定义 `api/entity_create.dspy`;entity_import 为只读列表,无 editable ## 依赖 -- world、scene(下拉数据源 world / scene 表) -- 基础:sqlor、ahserver、bricks、appbase(appcodes) +- world(world_id 逻辑关联 + 世界下拉)、scene(scene_id 逻辑关联 + 场景下拉)、appbase(appcodes 字典)、rbac(权限) diff --git a/wwwroot/import_page.ui b/wwwroot/import_page.ui index 4c14d2e..633988f 100644 --- a/wwwroot/import_page.ui +++ b/wwwroot/import_page.ui @@ -1,20 +1,21 @@ { - "widgettype": "VBox", - "options": {"width": "100%", "height": "100%", "padding": "20px"}, - "subwidgets": [ - {"widgettype": "Text", "options": {"label": "实体导入", "fontSize": "24px"}}, - {"widgettype": "Text", "options": {"label": "支持 CSV(首行表头)或 JSON 数组,字段:name、code、entity_type、status、attributes_json"}}, - {"widgettype": "Form", "options": { - "title": "导入实体数据", - "submit_url": "{{entire_url('/entity/api/entity_import.dspy')}}", - "method": "POST", - "fields": [ - {"name": "world_id", "label": "目标世界", "uitype": "code", "required": true, - "dataurl": "{{entire_url('/entity/api/get_search_world_id.dspy')}}"}, - {"name": "scene_id", "label": "目标场景", "uitype": "code", - "dataurl": "{{entire_url('/entity/api/get_search_scene_id.dspy')}}"}, - {"name": "file", "label": "导入文件", "uitype": "file", "required": true} - ] - }} - ] + "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,world_id,scene_id,entity_type,status,attributes_json)。事务批量:任一条校验失败整批回滚,无脏数据。"}}, + {"widgettype": "Form", + "options": { + "width": "100%", + "submit_url": "{{entire_url('/entity/api/entity_import.dspy')}}", + "method": "POST", + "fields": [ + {"name": "world_id", "label": "目标世界ID", "uitype": "code", "dataurl": "{{entire_url('/entity/api/get_search_world_id.dspy')}}"}, + {"name": "scene_id", "label": "目标场景ID", "uitype": "code", "dataurl": "{{entire_url('/entity/api/get_search_scene_id.dspy')}}"}, + {"name": "file_name", "label": "文件名", "uitype": "text"}, + {"name": "file", "label": "导入文件", "uitype": "file", "required": true} + ], + "buttons": [{"label": "开始导入", "type": "submit"}] + }} + ] } diff --git a/wwwroot/index.ui b/wwwroot/index.ui index 51c1353..1964cf3 100644 --- a/wwwroot/index.ui +++ b/wwwroot/index.ui @@ -1,22 +1,19 @@ { - "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.entity_content", - "options": {"url": "{{entire_url('/entity/entity/index.ui')}}"}, "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.entity_content", - "options": {"url": "{{entire_url('/entity/import_page.ui')}}"}, "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.entity_content", - "options": {"url": "{{entire_url('/entity/entity_import/index.ui')}}"}, "mode": "replace"}], - "subwidgets": [{"widgettype": "Text", "options": {"label": "导入记录"}}]} - ]}, - {"widgettype": "VBox", "id": "app.entity_content", "options": {"width": "100%", "flex": "1", "marginTop": "20px"}} - ] + "widgettype": "VBox", + "options": {"width": "100%", "height": "100%", "padding": "20px"}, + "subwidgets": [ + {"widgettype": "Text", "options": {"label": "实体管理(W-03)", "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.entity_content", "mode": "replace", "options": {"url": "{{entire_url('/entity/entity_list')}}"}}], + "subwidgets": [{"widgettype": "Text", "options": {"label": "实体列表"}}]}, + {"widgettype": "VBox", "options": {"backgroundColor": "#FFFFFF", "padding": "20px", "cursor": "pointer"}, + "binds": [{"wid": "self", "event": "click", "actiontype": "urlwidget", "target": "app.entity_content", "mode": "replace", "options": {"url": "{{entire_url('/entity/import_page.ui')}}"}}], + "subwidgets": [{"widgettype": "Text", "options": {"label": "实体导入"}}]}, + {"widgettype": "VBox", "options": {"backgroundColor": "#FFFFFF", "padding": "20px", "cursor": "pointer"}, + "binds": [{"wid": "self", "event": "click", "actiontype": "urlwidget", "target": "app.entity_content", "mode": "replace", "options": {"url": "{{entire_url('/entity/entity_import_list')}}"}}], + "subwidgets": [{"widgettype": "Text", "options": {"label": "导入记录"}}]} + ]}, + {"widgettype": "VBox", "id": "app.entity_content", "options": {"width": "100%", "flex": "1", "marginTop": "20px"}} + ] }