add 导入旧数据

This commit is contained in:
wess09 2026-05-13 18:24:47 +08:00
parent 78a39f6d98
commit 4e8741d1a2
2 changed files with 197 additions and 0 deletions

View File

@ -321,11 +321,96 @@ async def api_notify_stream(request):
)
async def api_import_legacy_upload(request):
"""
接收浏览器上传的旧 ALAS 文件夹内容写入本项目对应位置
前端使用 webkitdirectory 选择文件夹后上传
"""
from pathlib import Path
try:
form = await request.form()
current_root = Path(os.getcwd()).resolve()
result = {
"config": 0,
"db": 0,
"cl1": 0,
"azurstat": 0,
"skipped": 0,
"errors": 0,
}
for file in form.getlist('file'):
if not hasattr(file, 'filename') or not file.filename:
continue
relative_path = file.filename.replace("\\", "/")
filename = Path(relative_path).name
# 提取根级相对路径:跳过前导 / 和可能的文件夹名前缀
parts = relative_path.split("/")
# parts[0]='' (前导 /), parts[1]可能是文件夹名或 config/log
start_idx = 1
if len(parts) >= 3 and parts[1] not in ("config", "log"):
start_idx = 2 # parts[1] 是文件夹名,跳过
sub_path = "/".join(parts[start_idx:])
# 判断是否需要处理该文件
rel_target = None
# 只匹配 根目录/config/ 下的 .json/.db排除 template*
if sub_path.startswith("config/"):
ext = Path(filename).suffix.lower()
if ext in (".json", ".db") and not filename.lower().startswith("template"):
rel_target = sub_path
# 只匹配 根目录/log/cl1/ 下的所有文件
if sub_path.startswith("log/cl1/"):
rel_target = sub_path
# 只匹配 根目录/log/azurstat_meowofficer_farming.csv
if sub_path == "log/azurstat_meowofficer_farming.csv":
rel_target = sub_path
if rel_target is None:
result["skipped"] += 1
continue
target = current_root / rel_target
try:
target.parent.mkdir(parents=True, exist_ok=True)
content = await file.read()
target.write_bytes(content)
if rel_target.startswith("config/"):
ext = Path(filename).suffix.lower()
if ext == ".json":
result["config"] += 1
else:
result["db"] += 1
elif rel_target.startswith("log/cl1/"):
result["cl1"] += 1
elif "azurstat" in rel_target:
result["azurstat"] += 1
except Exception as e:
logger.error(f"写入失败 {target}: {e}")
result["errors"] += 1
logger.info(f"导入完成: {result}")
return JSONResponse({"success": True, "data": result})
except Exception as e:
logger.error(f"导入API错误: {e}")
return JSONResponse({"success": False, "error": str(e)}, status_code=500)
api_routes = [
Route("/api/cl1_stats", api_cl1_stats),
Route("/api/ap_timeline", api_ap_timeline),
Route("/api/notify", api_notify, methods=["POST"]),
Route("/api/notify_stream", api_notify_stream),
Route("/api/import_legacy_upload", api_import_legacy_upload, methods=["POST"]),
Route("/obs", serve_obs_overlay),
WebSocketRoute("/ws/live_screenshot", ws_live_screenshot),
]

View File

@ -2563,6 +2563,12 @@ class AlasGUI(Frame):
color="menu",
).style(f"--menu-HomePage--")
put_button(
label="导入旧数据",
onclick=self.ui_import_legacy,
color="menu",
).style(f"--menu-Import--")
# put_button(
# label=t("Gui.MenuDevelop.Translate"),
# onclick=self.dev_translate,
@ -3050,6 +3056,112 @@ class AlasGUI(Frame):
put()
@use_scope("content", clear=True)
def ui_import_legacy(self) -> None:
"""Develop 菜单:导入旧 AzurPilot 数据"""
self.init_menu(name="Import")
self.set_title("导入旧数据")
from pywebio.output import put_markdown, put_html, put_buttons, put_scope
import json
# 检查上一轮导入的结果(通过 sessionStorage 跨刷新传递)
try:
raw = eval_js("(function(){var r=sessionStorage.getItem('import_msg');if(r){sessionStorage.removeItem('import_msg');return r;}return null;})()")
if raw:
info = json.loads(raw)
if info.get("ok"):
d = info["data"]
parts = []
toast("导入成功", color="success", duration=10)
else:
toast("导入失败:" + info.get("error", "未知错误"), color="error", duration=10)
except Exception:
pass
def import_legacy_upload():
toast("请在弹出的窗口中选择旧 AzurPilot/ALAS 根目录", color="info", duration=0)
run_js("""
(function(){
var input = document.createElement('input');
input.type = 'file';
input.setAttribute('webkitdirectory', '');
input.setAttribute('multiple', '');
input.style.display = 'none';
input.addEventListener('change', async function(e) {
var files = e.target.files;
document.body.removeChild(input);
if (!files || files.length === 0) return;
var formData = new FormData();
var matched = 0, skipped = 0, total = files.length;
for (var i = 0; i < total; i++) {
var file = files[i];
var relPath = '/' + file.webkitRelativePath.replace(/\\\\/g, '/');
var name = relPath.split('/').pop().toLowerCase();
var pp = relPath.split('/');
var si = 1;
if (pp.length >= 3 && pp[1] !== 'config' && pp[1] !== 'log') si = 2;
var subPath = pp.slice(si).join('/');
var ok = false;
if (subPath.startsWith('config/')) {
if ((name.endsWith('.json') || name.endsWith('.db')) && !name.startsWith('template')) ok = true;
} else if (subPath.startsWith('log/cl1/')) {
ok = true;
} else if (subPath === 'log/azurstat_meowofficer_farming.csv') {
ok = true;
}
if (!ok) { skipped++; continue; }
matched++;
formData.append('file', file, relPath);
}
if (matched === 0) {
sessionStorage.setItem('import_msg', JSON.stringify({ok:false, error:'所选文件夹中没有找到 config/ 或 log/cl1/ 下的匹配文件'}));
location.reload();
return;
}
try {
var resp = await fetch('/api/import_legacy_upload', { method: 'POST', body: formData });
var result = await resp.json();
if (result.success) {
result.data.total = total;
sessionStorage.setItem('import_msg', JSON.stringify({ok:true, data:result.data, total:total}));
} else {
sessionStorage.setItem('import_msg', JSON.stringify({ok:false, error:result.error || '未知错误'}));
}
} catch (err) {
sessionStorage.setItem('import_msg', JSON.stringify({ok:false, error:'上传请求失败: ' + err.message}));
}
location.reload();
});
document.body.appendChild(input);
input.click();
})();
""")
put_html(build_title_block("导入旧 AzurPilot/ALAS 数据", margin_top=12, margin_bottom=8))
put_markdown(
"选择旧 AzurPilot/ALAS 根目录后,自动将以下数据导入到当前项目:\n\n"
"**配置 数据文件等**\n\n"
"> 同名文件将被覆盖,建议先备份当前项目。"
)
put_scope("import_btn")
with use_scope("import_btn"):
put_buttons(
[
{"label": "选择旧 AzurPilot/ALAS 文件夹", "value": "upload", "color": "primary"},
],
onclick=[import_legacy_upload],
)
def show(self) -> None:
self._show()
self.load_home = True