From 7a03fd3ebdfaa3dc31670c57c9d8c8efcf9e1a64 Mon Sep 17 00:00:00 2001 From: AskaEth Date: Mon, 27 Jul 2026 11:36:25 +0800 Subject: [PATCH] refactor: scenes now folder-based with preview image support --- models/scene.py | 64 +++++++++++++++++++++++++++++----------- static/js/pages/scene.js | 4 ++- 2 files changed, 50 insertions(+), 18 deletions(-) diff --git a/models/scene.py b/models/scene.py index 9af2e32..7d7ee7d 100644 --- a/models/scene.py +++ b/models/scene.py @@ -84,34 +84,51 @@ class SceneManager: def list_all(self, game_id: str | None = None) -> list[dict[str, Any]]: scenes = [] - for f in SCENES_DIR.glob("*.json"): + for item in SCENES_DIR.iterdir(): + if not item.is_dir(): + continue + f = item / "scene.json" + if not f.exists(): + continue try: with open(f, "r", encoding="utf-8") as fp: data = json.load(fp) scene = Scene.from_dict(data) if game_id is None or scene.game_id == game_id: - scenes.append(scene.to_dict()) + d = scene.to_dict() + for ext in ["jpg","jpeg","png","webp","gif"]: + if (item / f"preview.{ext}").exists(): + d["preview_ext"] = ext + break + scenes.append(d) except Exception as e: - logger.error("Failed to load scene %s: %s", f.name, e) + logger.error("Failed to load scene %s: %s", item.name, e) return scenes def get(self, scene_id: str) -> dict[str, Any] | None: - filepath = SCENES_DIR / f"{scene_id}.json" - if not filepath.exists(): + item = SCENES_DIR / scene_id + f = item / "scene.json" + if not f.exists(): return None try: - with open(filepath, "r", encoding="utf-8") as f: - data = json.load(f) - return Scene.from_dict(data).to_dict() + with open(f, "r", encoding="utf-8") as fp: + data = json.load(fp) + d = Scene.from_dict(data).to_dict() + for ext in ["jpg","jpeg","png","webp","gif"]: + if (item / f"preview.{ext}").exists(): + d["preview_ext"] = ext + break + return d except Exception as e: logger.error("Failed to read scene %s: %s", scene_id, e) return None def save(self, scene: Scene) -> bool: scene.updated_at = datetime.now().isoformat() - filepath = SCENES_DIR / f"{scene.id}.json" + item = SCENES_DIR / scene.id + item.mkdir(parents=True, exist_ok=True) try: - with open(filepath, "w", encoding="utf-8") as f: + with open(item / "scene.json", "w", encoding="utf-8") as f: json.dump(scene.to_dict(), f, indent=2, ensure_ascii=False) logger.info("Scene saved: %s", scene.id) return True @@ -120,20 +137,23 @@ class SceneManager: return False def delete(self, scene_id: str) -> bool: - filepath = SCENES_DIR / f"{scene_id}.json" - if filepath.exists(): - filepath.unlink() + import shutil + item = SCENES_DIR / scene_id + if item.exists(): + shutil.rmtree(item) logger.info("Scene deleted: %s", scene_id) return True return False def export_scene_zip(self, scene_id: str) -> bytes | None: - filepath = SCENES_DIR / f"{scene_id}.json" - if not filepath.exists(): + item = SCENES_DIR / scene_id + if not item.exists(): return None buf = io.BytesIO() with zipfile.ZipFile(buf, 'w', zipfile.ZIP_DEFLATED) as zf: - zf.write(filepath, "scene.json") + for f in sorted(item.rglob("*")): + if f.is_file(): + zf.write(f, f.relative_to(item)) logger.info("Scene exported as zip: %s", scene_id) return buf.getvalue() @@ -145,7 +165,17 @@ class SceneManager: return False data = json.loads(zf.read("scene.json").decode('utf-8')) scene = Scene.from_dict(data) - return self.save(scene) + item = SCENES_DIR / scene.id + if item.exists(): + import shutil; shutil.rmtree(item) + item.mkdir(parents=True) + for name in zf.namelist(): + if name.endswith('/'): continue + dest = item / name + dest.parent.mkdir(parents=True, exist_ok=True) + dest.write_bytes(zf.read(name)) + logger.info("Scene imported from zip: %s", scene.id) + return True except Exception as e: logger.error("Failed to import scene zip: %s", e) return False diff --git a/static/js/pages/scene.js b/static/js/pages/scene.js index 9596bdf..e1d0e38 100644 --- a/static/js/pages/scene.js +++ b/static/js/pages/scene.js @@ -215,8 +215,10 @@ const PageScene = { _sceneCardHtml(scene) { const canvasCount = (scene.canvases || []).length; + const preview = scene.preview_ext ? `/scene/${scene.id}/preview.${scene.preview_ext}` : ''; return `
-
${scene.name}
+ ${preview ? `
` : ''} +
${scene.name}
${this._currentGameName || '未关联游戏'}
${canvasCount} 画布${scene.updated_at ? new Date(scene.updated_at).toLocaleDateString() : ''}
${(scene.canvases||[]).map(c => `${c.label||c.width+'x'+c.height}`).join('')}