diff --git a/src/server/main.py b/src/server/main.py index ec572d9..24c7412 100644 --- a/src/server/main.py +++ b/src/server/main.py @@ -1697,6 +1697,9 @@ def validate_save_name(name: str) -> bool: class SaveGameRequest(BaseModel): custom_name: Optional[str] = None # 自定义存档名称 +class DeleteSaveRequest(BaseModel): + filename: str + class LoadGameRequest(BaseModel): filename: str @@ -1752,6 +1755,37 @@ def api_save_game(req: SaveGameRequest): else: raise HTTPException(status_code=500, detail="Save failed") +@app.post("/api/game/delete") +def api_delete_game(req: DeleteSaveRequest): + """删除存档及其关联文件""" + # 安全检查 + if ".." in req.filename or "/" in req.filename or "\\" in req.filename: + raise HTTPException(status_code=400, detail="Invalid filename") + + try: + saves_dir = CONFIG.paths.saves + target_path = saves_dir / req.filename + + # 1. 删除 JSON 存档文件 + if target_path.exists(): + os.remove(target_path) + + # 2. 删除对应的 SQL 数据库文件 + events_db_path = get_events_db_path(target_path) + if os.path.exists(events_db_path): + try: + os.remove(events_db_path) + except Exception as e: + print(f"[Warning] Failed to delete db file {events_db_path}: {e}") + + # 3. 删除可能存在的其他关联文件(如果有) + + return {"status": "ok", "message": "Save deleted"} + except Exception as e: + import traceback + traceback.print_exc() + raise HTTPException(status_code=500, detail=f"Delete failed: {str(e)}") + @app.post("/api/game/load") async def api_load_game(req: LoadGameRequest): """加载游戏(异步,支持进度更新)。""" diff --git a/web/src/api/modules/system.ts b/web/src/api/modules/system.ts index 222fcd5..0cfb78c 100644 --- a/web/src/api/modules/system.ts +++ b/web/src/api/modules/system.ts @@ -26,6 +26,10 @@ export const systemApi = { ); }, + deleteSave(filename: string) { + return httpClient.post<{ status: string; message: string }>('/api/game/delete', { filename }); + }, + loadGame(filename: string) { return httpClient.post<{ status: string; message: string }>('/api/game/load', { filename }); }, diff --git a/web/src/components/game/panels/system/SaveLoadPanel.vue b/web/src/components/game/panels/system/SaveLoadPanel.vue index a3c615b..8402910 100644 --- a/web/src/components/game/panels/system/SaveLoadPanel.vue +++ b/web/src/components/game/panels/system/SaveLoadPanel.vue @@ -112,6 +112,21 @@ async function handleLoad(filename: string) { } } +async function handleDelete(filename: string) { + if (!confirm(t('save_load.delete_confirm', { filename }))) return + + loading.value = true + try { + await systemApi.deleteSave(filename) + message.success(t('save_load.delete_success')) + await fetchSaves() + } catch (e) { + message.error(t('save_load.delete_failed')) + } finally { + loading.value = false + } +} + // 格式化保存时间 function formatSaveTime(isoTime: string): string { if (!isoTime) return '' @@ -199,7 +214,22 @@ onMounted(() => { v{{ save.version }} -
{{ t('save_load.load') }}
+
+ + {{ t('save_load.delete') }} + + + {{ t('save_load.load') }} + +
@@ -250,7 +280,7 @@ onMounted(() => { flex-direction: column; } -.save-panel { +.save-panel, .load-panel { align-items: center; padding-top: 2em; } @@ -392,19 +422,10 @@ onMounted(() => { font-family: monospace; } -.load-btn { - background: #333; - color: #ddd; - border: 1px solid #444; - padding: 0.4em 1em; - border-radius: 0.3em; - font-size: 0.9em; - transition: all 0.2s; -} - -.load-btn:hover { - background: #444; - border-color: #555; +.load-actions { + display: flex; + gap: 1em; + align-items: center; } .loading { diff --git a/web/src/locales/en-US.json b/web/src/locales/en-US.json index cb20400..bfaa40e 100644 --- a/web/src/locales/en-US.json +++ b/web/src/locales/en-US.json @@ -49,7 +49,11 @@ "name_placeholder": "Enter save name...", "name_tip": "Leave empty to use auto-generated name", "name_too_long": "Name cannot exceed 50 characters", - "name_invalid_chars": "Name can only contain letters, numbers, Chinese characters and underscores" + "name_invalid_chars": "Name can only contain letters, numbers, Chinese characters and underscores", + "delete": "Delete", + "delete_confirm": "Are you sure you want to permanently delete save {filename}? This action cannot be undone.", + "delete_success": "Save deleted", + "delete_failed": "Failed to delete" }, "llm": { "loading": "Loading...", diff --git a/web/src/locales/zh-CN.json b/web/src/locales/zh-CN.json index 18a5ab7..371f317 100644 --- a/web/src/locales/zh-CN.json +++ b/web/src/locales/zh-CN.json @@ -49,7 +49,11 @@ "name_placeholder": "输入存档名称...", "name_tip": "留空将使用自动生成的名称", "name_too_long": "名称不能超过50个字符", - "name_invalid_chars": "名称只能包含中文、字母、数字和下划线" + "name_invalid_chars": "名称只能包含中文、字母、数字和下划线", + "delete": "删除", + "delete_confirm": "确定要彻底删除存档 {filename} 及其所有数据吗?此操作无法撤销。", + "delete_success": "存档已删除", + "delete_failed": "删除失败" }, "llm": { "loading": "加载中...", diff --git a/web/src/locales/zh-TW.json b/web/src/locales/zh-TW.json index d094a29..ec61b0d 100644 --- a/web/src/locales/zh-TW.json +++ b/web/src/locales/zh-TW.json @@ -49,7 +49,11 @@ "name_placeholder": "輸入存檔名稱...", "name_tip": "留空將使用自動生成的名稱", "name_too_long": "名稱不能超過50個字元", - "name_invalid_chars": "名稱只能包含中文、字母、數字和底線" + "name_invalid_chars": "名稱只能包含中文、字母、數字和底線", + "delete": "刪除", + "delete_confirm": "確定要徹底刪除存檔 {filename} 及其所有資料嗎?此操作無法撤銷。", + "delete_success": "存檔已刪除", + "delete_failed": "刪除失敗" }, "llm": { "loading": "載入中...",