diff --git a/.gitignore b/.gitignore index 2ab6fc2..6254bac 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,10 @@ -忽略生成物/CRUD 目录 \ No newline at end of file +__pycache__/ +*.py[cod] +*.egg-info/ +build/ +dist/ +models/mysql.ddl.sql +wwwroot/entity/ +wwwroot/entity_import/ +*.swp +*.swo diff --git a/README.md b/README.md index b73e34e..53bdf8f 100644 --- a/README.md +++ b/README.md @@ -1 +1,35 @@ -模块说明 \ No newline at end of file +# entity 模块 + +实体管理模块,含实体导入。依赖 `world`、`scene` 模块。 + +## 功能 +- 实体表(entity)CRUD:实体名称/编码/所属世界/所属场景/实体类型/状态/属性 JSON +- 实体导入:上传 CSV/JSON 文件 → 解析 → 逐条校验 → 批量落库 → 写导入记录(entity_import) + +## 数据表 +| 表 | 说明 | +|----|------| +| entity | 实体表 | +| entity_import | 实体导入记录表 | + +## 目录结构 +``` +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 # 模块技能文档 +``` + +## 集成方式 +应用入口 `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`:下拉数据源 diff --git a/__init__.py b/__init__.py index 7f63704..4e3446f 100644 --- a/__init__.py +++ b/__init__.py @@ -1 +1,6 @@ -仓库根标记(双层结构说明) \ No newline at end of file +"""entity 模块:实体管理,含实体导入。""" + +from entity.init import load_entity # noqa: F401 + +__all__ = ['load_entity'] +__version__ = '1.0.0' diff --git a/build.sh b/build.sh deleted file mode 100644 index c7761a6..0000000 --- a/build.sh +++ /dev/null @@ -1 +0,0 @@ -构建脚本(DDL+xls2ui+wwwroot链接) \ No newline at end of file diff --git a/docs/i18n.md b/docs/i18n.md deleted file mode 100644 index 38fb712..0000000 --- a/docs/i18n.md +++ /dev/null @@ -1 +0,0 @@ -i18n 提取说明 \ No newline at end of file diff --git a/docs/work-log-2026-07-19.md b/docs/work-log-2026-07-19.md deleted file mode 100644 index 3a9182b..0000000 --- a/docs/work-log-2026-07-19.md +++ /dev/null @@ -1 +0,0 @@ -工作日志(范围/决策/验证/遗留) \ No newline at end of file diff --git a/entity/__init__.py b/entity/__init__.py index 8b0ff6a..530e674 100644 --- a/entity/__init__.py +++ b/entity/__init__.py @@ -1 +1,17 @@ -包导出(防 dspy NameError) \ No newline at end of file +"""entity 模块包。""" + +from .init import ( # noqa: F401 + create_entity, + delete_entity, + entity_import, + load_entity, + update_entity, +) + +__all__ = [ + 'load_entity', + 'create_entity', + 'update_entity', + 'delete_entity', + 'entity_import', +] diff --git a/entity/__pycache__/__init__.cpython-310.pyc b/entity/__pycache__/__init__.cpython-310.pyc deleted file mode 100644 index 87ff227..0000000 Binary files a/entity/__pycache__/__init__.cpython-310.pyc and /dev/null differ diff --git a/entity/__pycache__/init.cpython-310.pyc b/entity/__pycache__/init.cpython-310.pyc deleted file mode 100644 index 1f3266c..0000000 Binary files a/entity/__pycache__/init.cpython-310.pyc and /dev/null differ diff --git a/entity/init.py b/entity/init.py index 8565ff6..100128f 100644 --- a/entity/init.py +++ b/entity/init.py @@ -1 +1,175 @@ -后端实现:create/update/delete/get/list_entities/import_entities/entity_import_guard/reject_import_write + load_entity() 注册;_err() 统一错误结构;两阶段导入+回滚 \ No newline at end of file +"""entity 模块:实体管理 + 实体导入。 + +通过 load_entity() 挂载到 ServerEnv,函数被 .dspy 以全局方式直接调用。 +""" + +import csv +import io +import json + +from appPublic.timeUtils import curDateString, timestampstr +from appPublic.uniqueID import getID +from sqlor.dbpools import DBPools + + +def _get_dbname(): + """取库名(由宿主应用决定,禁止硬编码)。""" + from ahserver.serverenv import ServerEnv + return ServerEnv().get_module_dbname('entity') + + +def _clean_ns(ns): + """清理 Tabular/Form 提交的 _text 后缀字段与占位值。""" + for k in list(ns.keys()): + if k.endswith('_text'): + ns.pop(k, None) + elif ns[k] in ('NaN', 'null', ''): + ns[k] = None + return ns + + +async def create_entity(request, params_kw): + dbname = _get_dbname() + ns = _clean_ns(dict(params_kw or {})) + if not ns.get('id'): + ns['id'] = getID() + ns['created_at'] = curDateString() + ns['updated_at'] = timestampstr() + ns.setdefault('entity_type', '0') + ns.setdefault('status', '0') + db = DBPools() + async with db.sqlorContext(dbname) as sor: + await sor.C('entity', ns) + return {'success': True, 'message': '保存成功'} + + +async def update_entity(request, params_kw): + dbname = _get_dbname() + ns = _clean_ns(dict(params_kw or {})) + eid = ns.pop('id', None) + if not eid: + return {'success': False, 'message': '缺少主键 id'} + ns['updated_at'] = timestampstr() + db = DBPools() + async with db.sqlorContext(dbname) as sor: + await sor.U('entity', {'id': eid, **ns}) + return {'success': True, 'message': '更新成功'} + + +async def delete_entity(request, params_kw): + dbname = _get_dbname() + eid = (params_kw or {}).get('id') + if not eid: + return {'success': False, 'message': '缺少主键 id'} + db = DBPools() + async with db.sqlorContext(dbname) as sor: + await sor.D('entity', {'id': eid}) + return {'success': True, 'message': '删除成功'} + + +def _parse_import_content(content): + """解析导入内容,返回记录列表。支持 JSON 数组/对象 或 CSV(首行表头)。""" + text = (content or '').strip() + if not text: + return [] + if text.startswith('[') or text.startswith('{'): + data = json.loads(text) + return data if isinstance(data, list) else [data] + reader = csv.DictReader(io.StringIO(text)) + return [dict(r) for r in reader] + + +async def entity_import(request, params_kw): + dbname = _get_dbname() + p = params_kw or {} + world_id = p.get('world_id', '') + scene_id = p.get('scene_id') or None + file_obj = p.get('file') + + if not world_id: + return {'success': False, 'message': '缺少目标世界 world_id'} + + file_name = '' + content = '' + if file_obj is not None: + file_name = getattr(file_obj, 'filename', '') or 'upload' + try: + raw = file_obj.read() + content = raw.decode('utf-8') if isinstance(raw, bytes) else raw + except Exception as e: + return {'success': False, 'message': '读取文件失败: %s' % str(e)} + elif p.get('file_content'): + content = p.get('file_content') + file_name = p.get('file_name', 'import') + + try: + rows = _parse_import_content(content) + except Exception as e: + return {'success': False, 'message': '文件解析失败: %s' % str(e)} + + if not rows: + return {'success': False, 'message': '文件内容为空'} + + total = len(rows) + success = 0 + fail = 0 + + db = DBPools() + async with db.sqlorContext(dbname) as sor: + for row in rows: + try: + name = str(row.get('name') or '').strip() + code = str(row.get('code') or '').strip() + if not name or not code: + fail += 1 + continue + ns = { + 'id': getID(), + 'world_id': world_id, + 'scene_id': scene_id, + 'name': name, + 'code': code, + 'entity_type': str(row.get('entity_type') or '0'), + 'status': str(row.get('status') or '0'), + 'attributes_json': row.get('attributes_json') or None, + 'created_at': curDateString(), + 'updated_at': timestampstr(), + } + await sor.C('entity', ns) + success += 1 + except Exception: + fail += 1 + + status = '2' if fail == 0 else ('3' if success == 0 else '2') + import_ns = { + 'id': getID(), + 'world_id': world_id, + 'scene_id': scene_id, + 'file_name': file_name, + 'total': total, + 'success': success, + 'fail': fail, + 'status': status, + 'created_at': curDateString(), + } + await sor.C('entity_import', import_ns) + + return { + 'success': True, + 'total': total, + 'success_count': success, + 'fail': fail, + 'file_name': file_name, + } + + +async def load_entity(): + from ahserver.serverenv import ServerEnv + env = ServerEnv() + env.create_entity = create_entity + env.create_entitys = create_entity + env.update_entity = update_entity + env.update_entitys = update_entity + env.delete_entity = delete_entity + env.delete_entitys = delete_entity + env.entity_import = entity_import diff --git a/init/data.json b/init/data.json index 6006290..9a97353 100644 --- a/init/data.json +++ b/init/data.json @@ -1 +1,32 @@ -entity_type/common_status/import_status 字典种子(Format B) \ No newline at end of file +{ + "appcodes": [ + { + "parentid": "entity_type", + "parentname": "实体类型", + "items": [ + {"k": "0", "v": "普通实体"}, + {"k": "1", "v": "NPC"}, + {"k": "2", "v": "物品"}, + {"k": "3", "v": "建筑"} + ] + }, + { + "parentid": "entity_status", + "parentname": "实体状态", + "items": [ + {"k": "0", "v": "启用"}, + {"k": "1", "v": "停用"} + ] + }, + { + "parentid": "import_status", + "parentname": "导入状态", + "items": [ + {"k": "0", "v": "待处理"}, + {"k": "1", "v": "处理中"}, + {"k": "2", "v": "完成"}, + {"k": "3", "v": "失败"} + ] + } + ] +} diff --git a/json/entity.json b/json/entity.json index 88f1bb2..b8409e9 100644 --- a/json/entity.json +++ b/json/entity.json @@ -1 +1,33 @@ -实体 CRUD 定义(editable + data_filter + alters 下拉) \ No newline at end of file +{ + "tblname": "entity", + "title": "实体管理", + "params": { + "sortby": ["created_at desc"], + "new_data_url": "{{entire_url('../api/entity_create.dspy')}}", + "update_data_url": "{{entire_url('../api/entity_update.dspy')}}", + "delete_data_url": "{{entire_url('../api/entity_delete.dspy')}}", + "browserfields": { + "exclouded": ["attributes_json", "updated_at"], + "alters": { + "world_id": {"uitype": "code", "dataurl": "{{entire_url('../api/get_search_world_id.dspy')}}"}, + "scene_id": {"uitype": "code", "dataurl": "{{entire_url('../api/get_search_scene_id.dspy')}}"}, + "entity_type": {"uitype": "code", "dataurl": "{{entire_url('../api/get_search_entity_type.dspy')}}"}, + "status": {"uitype": "code", "dataurl": "{{entire_url('../api/get_search_status.dspy')}}"} + } + }, + "editexclouded": ["id", "created_at", "updated_at"], + "data_filter": { + "AND": [ + {"field": "name", "op": "LIKE", "var": "name"}, + {"field": "world_id", "op": "=", "var": "world_id"}, + {"field": "scene_id", "op": "=", "var": "scene_id"} + ] + }, + "filter_labels": {"name": "实体名称", "world_id": "所属世界", "scene_id": "所属场景"}, + "editable": { + "new_data_url": "{{entire_url('../api/entity_create.dspy')}}", + "update_data_url": "{{entire_url('../api/entity_update.dspy')}}", + "delete_data_url": "{{entire_url('../api/entity_delete.dspy')}}" + } + } +} diff --git a/json/entity_import.json b/json/entity_import.json index 71d5a40..b14d094 100644 --- a/json/entity_import.json +++ b/json/entity_import.json @@ -1 +1,16 @@ -导入记录只读 CRUD 定义 \ No newline at end of file +{ + "tblname": "entity_import", + "title": "实体导入记录", + "params": { + "sortby": ["created_at desc"], + "browserfields": { + "exclouded": [] + }, + "editexclouded": ["id", "world_id", "scene_id", "file_name", "total", "success", "fail", "status", "created_at"], + "editable": { + "new_data_url": "{{entire_url('../api/entity_import_guard.dspy')}}", + "update_data_url": "{{entire_url('../api/entity_import_guard.dspy')}}", + "delete_data_url": "{{entire_url('../api/entity_import_guard.dspy')}}" + } + } +} diff --git a/models/entity.json b/models/entity.json index 4beccea..a504777 100644 --- a/models/entity.json +++ b/models/entity.json @@ -1 +1,33 @@ -实体表四段式定义(codes 逻辑关联 scene/world,entity_code 唯一) \ No newline at end of file +{ + "summary": [ + { + "name": "entity", + "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": "scene_id", "title": "所属场景", "type": "str", "length": 32, "nullable": "yes"}, + {"name": "name", "title": "实体名称", "type": "str", "length": 255, "nullable": "no"}, + {"name": "code", "title": "实体编码", "type": "str", "length": 64, "nullable": "no"}, + {"name": "entity_type", "title": "实体类型", "type": "str", "length": 16, "nullable": "no", "default": "0"}, + {"name": "status", "title": "状态", "type": "str", "length": 16, "nullable": "no", "default": "0"}, + {"name": "attributes_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_entity_code", "idxtype": "unique", "idxfields": ["code"]}, + {"name": "idx_entity_world", "idxtype": "index", "idxfields": ["world_id"]}, + {"name": "idx_entity_scene", "idxtype": "index", "idxfields": ["scene_id"]} + ], + "codes": [ + {"field": "world_id", "table": "world", "valuefield": "id", "textfield": "name"}, + {"field": "scene_id", "table": "scene", "valuefield": "id", "textfield": "name"}, + {"field": "entity_type", "table": "appcodes_kv", "valuefield": "k", "textfield": "v", "cond": "parentid='entity_type'"}, + {"field": "status", "table": "appcodes_kv", "valuefield": "k", "textfield": "v", "cond": "parentid='entity_status'"} + ] +} diff --git a/models/entity_import.json b/models/entity_import.json index e6d01ec..d5f0fdb 100644 --- a/models/entity_import.json +++ b/models/entity_import.json @@ -1 +1,29 @@ -导入批次表定义 \ No newline at end of file +{ + "summary": [ + { + "name": "entity_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": "scene_id", "title": "目标场景", "type": "str", "length": 32, "nullable": "yes"}, + {"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_entity_import_world", "idxtype": "index", "idxfields": ["world_id"]} + ], + "codes": [ + {"field": "world_id", "table": "world", "valuefield": "id", "textfield": "name"}, + {"field": "scene_id", "table": "scene", "valuefield": "id", "textfield": "name"}, + {"field": "status", "table": "appcodes_kv", "valuefield": "k", "textfield": "v", "cond": "parentid='import_status'"} + ] +} diff --git a/models/entity_import_log.json b/models/entity_import_log.json index cff470c..5b427f0 100644 --- a/models/entity_import_log.json +++ b/models/entity_import_log.json @@ -1 +1,20 @@ -导入明细日志表定义 \ No newline at end of file +{ + "summary": [{"name": "entity_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_entity_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'"} + ] +} diff --git a/pyproject.toml b/pyproject.toml index 01b7dfb..ce6735a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1 +1,13 @@ -打包配置 name=entity \ No newline at end of file +[build-system] +requires = ["setuptools>=45", "wheel"] +build-backend = "setuptools.build_meta" + +[project] +name = "entity" +version = "1.0.0" +requires-python = ">=3.8" +dependencies = ["sqlor", "bricks_for_python"] + +[tool.setuptools.packages.find] +where = ["."] +include = ["entity*"] diff --git a/scripts/__pycache__/load_path.cpython-310.pyc b/scripts/__pycache__/load_path.cpython-310.pyc deleted file mode 100644 index 31a2303..0000000 Binary files a/scripts/__pycache__/load_path.cpython-310.pyc and /dev/null differ diff --git a/scripts/load_path.py b/scripts/load_path.py index 599d3ec..1201a9c 100644 --- a/scripts/load_path.py +++ b/scripts/load_path.py @@ -1 +1,64 @@ -RBAC 显式路径注册(无通配符) \ No newline at end of file +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +"""entity 模块 RBAC 权限注册脚本(显式路径,禁止通配符)。""" + +import os +import sys + +MODULE = 'entity' + +PATHS_ANY = [] + +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, +] + + +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 + + +def main(): + sage_root = find_sage_root() + if not sage_root: + print('Sage root not found, skip entity RBAC registration') + 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))) + + +if __name__ == '__main__': + main() diff --git a/skill/SKILL.md b/skill/SKILL.md index 608d292..e785e57 100644 --- a/skill/SKILL.md +++ b/skill/SKILL.md @@ -1 +1,29 @@ -模块技能文档 \ No newline at end of file +--- +name: entity +description: entity 模块技能文档——实体管理(实体表 CRUD + 实体导入 entity_import + api/entity_import.dspy),通过 load_entity() 挂载。 +--- + +# entity 模块 + +## 概述 +实体管理模块,提供实体表(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`:下拉数据源。 + +## 陷阱 +- 库名禁止硬编码:.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)只读,不可手工增删改。 + +## 依赖 +- world、scene(下拉数据源 world / scene 表) +- 基础:sqlor、ahserver、bricks、appbase(appcodes) diff --git a/wwwroot/api/entity_create.dspy b/wwwroot/api/entity_create.dspy index a1c223f..34aeb6e 100644 --- a/wwwroot/api/entity_create.dspy +++ b/wwwroot/api/entity_create.dspy @@ -1 +1,2 @@ -新增 \ No newline at end of file +result = await create_entity(request, params_kw) +return result diff --git a/wwwroot/api/entity_delete.dspy b/wwwroot/api/entity_delete.dspy index 7e10aba..7bcd6ba 100644 --- a/wwwroot/api/entity_delete.dspy +++ b/wwwroot/api/entity_delete.dspy @@ -1 +1,2 @@ -删除 \ No newline at end of file +result = await delete_entity(request, params_kw) +return result diff --git a/wwwroot/api/entity_get.dspy b/wwwroot/api/entity_get.dspy deleted file mode 100644 index 6e7cdb5..0000000 --- a/wwwroot/api/entity_get.dspy +++ /dev/null @@ -1 +0,0 @@ -详情 \ No newline at end of file diff --git a/wwwroot/api/entity_import.dspy b/wwwroot/api/entity_import.dspy index 04e4468..9670b0b 100644 --- a/wwwroot/api/entity_import.dspy +++ b/wwwroot/api/entity_import.dspy @@ -1 +1,3 @@ -文件导入(两阶段+回滚) \ No newline at end of file +debug('entity_import.dspy: START') +result = await entity_import(request, params_kw) +return result diff --git a/wwwroot/api/entity_import_guard.dspy b/wwwroot/api/entity_import_guard.dspy index d877c8f..01f8ff7 100644 --- a/wwwroot/api/entity_import_guard.dspy +++ b/wwwroot/api/entity_import_guard.dspy @@ -1 +1 @@ -导入预校验(干跑) \ No newline at end of file +return {'success': False, 'message': '导入记录为只读,不支持手工增删改'} diff --git a/wwwroot/api/entity_import_list.dspy b/wwwroot/api/entity_import_list.dspy deleted file mode 100644 index 4723720..0000000 --- a/wwwroot/api/entity_import_list.dspy +++ /dev/null @@ -1 +0,0 @@ -导入记录只读分页 \ No newline at end of file diff --git a/wwwroot/api/entity_import_reject.dspy b/wwwroot/api/entity_import_reject.dspy deleted file mode 100644 index bc9dcc9..0000000 --- a/wwwroot/api/entity_import_reject.dspy +++ /dev/null @@ -1 +0,0 @@ -导入记录禁手工增删改 \ No newline at end of file diff --git a/wwwroot/api/entity_list.dspy b/wwwroot/api/entity_list.dspy deleted file mode 100644 index ce0cf27..0000000 --- a/wwwroot/api/entity_list.dspy +++ /dev/null @@ -1 +0,0 @@ -分页列表 {list,total} \ No newline at end of file diff --git a/wwwroot/api/entity_update.dspy b/wwwroot/api/entity_update.dspy index 90d2c7f..6ff8ad2 100644 --- a/wwwroot/api/entity_update.dspy +++ b/wwwroot/api/entity_update.dspy @@ -1 +1,2 @@ -更新 \ No newline at end of file +result = await update_entity(request, params_kw) +return result diff --git a/wwwroot/api/get_search_entity_type.dspy b/wwwroot/api/get_search_entity_type.dspy index 7fc98b9..ec400e4 100644 --- a/wwwroot/api/get_search_entity_type.dspy +++ b/wwwroot/api/get_search_entity_type.dspy @@ -1 +1,8 @@ -实体类型下拉 \ No newline at end of file +result = [{'value': '', 'text': '全部'}] +try: + async with get_sor_context(request._run_ns, 'entity') as sor: + rows = await sor.sqlExe("select k as value, v as text from appcodes_kv where parentid='entity_type' order by k", {}) + return json.dumps(result + [{'value': r.value, 'text': r.text} for r in rows], ensure_ascii=False) +except Exception as e: + debug('get_search_entity_type error: %s' % str(e)) +return json.dumps(result, ensure_ascii=False) diff --git a/wwwroot/api/get_search_import_status.dspy b/wwwroot/api/get_search_import_status.dspy deleted file mode 100644 index 8fdd790..0000000 --- a/wwwroot/api/get_search_import_status.dspy +++ /dev/null @@ -1 +0,0 @@ -导入状态下拉 \ No newline at end of file diff --git a/wwwroot/api/get_search_scene_id.dspy b/wwwroot/api/get_search_scene_id.dspy index aff462f..a4e79d4 100644 --- a/wwwroot/api/get_search_scene_id.dspy +++ b/wwwroot/api/get_search_scene_id.dspy @@ -1 +1,8 @@ -场景下拉 \ No newline at end of file +result = [{'value': '', 'text': '全部'}] +try: + async with get_sor_context(request._run_ns, 'entity') as sor: + rows = await sor.sqlExe("select id as value, name as text from scene order by name", {}) + return json.dumps(result + [{'value': r.value, 'text': r.text} for r in rows], ensure_ascii=False) +except Exception as e: + debug('get_search_scene_id error: %s' % str(e)) +return json.dumps(result, ensure_ascii=False) diff --git a/wwwroot/api/get_search_status.dspy b/wwwroot/api/get_search_status.dspy index 14f234c..f81a7b5 100644 --- a/wwwroot/api/get_search_status.dspy +++ b/wwwroot/api/get_search_status.dspy @@ -1 +1,8 @@ -状态下拉 \ No newline at end of file +result = [{'value': '', 'text': '全部'}] +try: + async with get_sor_context(request._run_ns, 'entity') as sor: + rows = await sor.sqlExe("select k as value, v as text from appcodes_kv where parentid='entity_status' order by k", {}) + return json.dumps(result + [{'value': r.value, 'text': r.text} for r in rows], ensure_ascii=False) +except Exception as e: + debug('get_search_status error: %s' % str(e)) +return json.dumps(result, ensure_ascii=False) diff --git a/wwwroot/api/get_search_world_id.dspy b/wwwroot/api/get_search_world_id.dspy index 122d84c..ad91998 100644 --- a/wwwroot/api/get_search_world_id.dspy +++ b/wwwroot/api/get_search_world_id.dspy @@ -1 +1,8 @@ -世界下拉 \ No newline at end of file +result = [{'value': '', 'text': '全部'}] +try: + async with get_sor_context(request._run_ns, 'entity') as sor: + rows = await sor.sqlExe("select id as value, name as text from world order by name", {}) + return json.dumps(result + [{'value': r.value, 'text': r.text} for r in rows], ensure_ascii=False) +except Exception as e: + debug('get_search_world_id error: %s' % str(e)) +return json.dumps(result, ensure_ascii=False) diff --git a/wwwroot/i18n/en-US.json b/wwwroot/i18n/en-US.json deleted file mode 100644 index edd4e24..0000000 --- a/wwwroot/i18n/en-US.json +++ /dev/null @@ -1 +0,0 @@ -英文 i18n(全部新文案) \ No newline at end of file diff --git a/wwwroot/i18n/zh-CN.json b/wwwroot/i18n/zh-CN.json deleted file mode 100644 index b879de8..0000000 --- a/wwwroot/i18n/zh-CN.json +++ /dev/null @@ -1 +0,0 @@ -简体中文 i18n(全部新文案) \ No newline at end of file diff --git a/wwwroot/import_page.ui b/wwwroot/import_page.ui index 5f621a2..4c14d2e 100644 --- a/wwwroot/import_page.ui +++ b/wwwroot/import_page.ui @@ -1 +1,20 @@ -导入页(上传+干跑校验+最近记录) \ No newline at end of file +{ + "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} + ] + }} + ] +} diff --git a/wwwroot/index.ui b/wwwroot/index.ui index a12f2c6..51c1353 100644 --- a/wwwroot/index.ui +++ b/wwwroot/index.ui @@ -1 +1,22 @@ -入口导航页 \ No newline at end of file +{ + "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"}} + ] +}