// 「系统」Tab:数据备份导出/导入(依赖 base.js:esc/_csrfHeaders/apiPost/showToast/handleResult)
let _restoreToken = ''; // 预览通过后的暂存标识(用于 apply)
// ================== 导出 ==================
async function doExportBackup(){
const st=document.getElementById('backup-status');
const btn=event&&event.target||null;
if(btn)btn.disabled=true;
const includeApk=document.getElementById('backup-include-apk').checked;
if(st)st.textContent='正在生成备份并打包…';
try{
const r=await fetch('/api/system/backup/export',{
method:'POST',
headers:{'Content-Type':'application/json',..._csrfHeaders()},
body:JSON.stringify({include_apk:includeApk})
});
if(r.status===401){window.location='/login';return;}
if(!r.ok){
let msg='导出失败';
try{const d=await r.json();if(d&&d.error)msg=d.error;}catch(e){}
showToast(msg,'error');
if(st)st.textContent='';
return;
}
const blob=await r.blob();
const cd=r.headers.get('Content-Disposition')||'';
const m=/filename="?([^";]+)"?/i.exec(cd);
const fname=m?m[1]:('auto_control_backup_'+Date.now()+'.zip');
const url=URL.createObjectURL(blob);
const a=document.createElement('a');
a.href=url;a.download=fname;document.body.appendChild(a);
a.click();a.remove();
setTimeout(()=>URL.revokeObjectURL(url),3000);
if(st)st.textContent='已生成 '+fname;
showToast('导出成功','success');
}catch(e){
showToast('导出失败: '+e,'error');
if(st)st.textContent='';
}finally{
if(btn)btn.disabled=false;
}
}
// ================== 导入:上传预览 ==================
async function previewRestore(){
const fileEl=document.getElementById('restore-file');
const f=fileEl&&fileEl.files&&fileEl.files[0];
const box=document.getElementById('restore-preview');
const st=document.getElementById('restore-status');
const applyBtn=document.getElementById('btn-apply-restore');
if(!f){showToast('请先选择备份文件(.zip 或 .db)','error');return;}
_restoreToken='';
if(applyBtn)applyBtn.style.display='none';
if(st)st.textContent='';
box.innerHTML='解析校验中…';
const fd=new FormData();
fd.append('file',f);
try{
const r=await fetch('/api/system/backup/preview',{
method:'POST',
headers:_csrfHeaders(), // FormData 不能手动设 Content-Type
body:fd
});
if(r.status===401){window.location='/login';return;}
const d=await r.json();
box.innerHTML='';
if(!d.ok){showToast(d.error||'解析失败','error');return;}
_restoreToken=d.token;
renderRestorePreview(d.preview);
if(applyBtn)applyBtn.style.display='';
}catch(e){
box.innerHTML='';
showToast('上传失败: '+e,'error');
}
}
function renderRestorePreview(p){
const box=document.getElementById('restore-preview');
const rows=(p.tables||[]).map(t=>
'
| '+esc(t.label||t.table)+' | '+esc(t.table)+' | '+esc(t.rows)+' |
').join('');
const warns=(p.warnings||[]).map(w=>''+esc(w)+'').join('');
const crossEnv=!!(p.source_env&&p.current_env&&p.source_env!==p.current_env);
box.innerHTML=
''+
''+
'| 文件 | '+esc(p.file_name||'')+'('+(p.file_size!=null?fmtSize(p.file_size):'')+') |
'+
'| 库结构版本 | 备份 v'+esc(p.schema_version)+' / 当前 v'+esc(p.current_schema_version)+' |
'+
'| 来源 / 当前环境 | '+(p.source_env?esc(p.source_env):'(旧版备份,未记录)')+' → '+esc(p.current_env||'未登记')+' |
'+
'| 完整性 | '+esc(p.integrity||'')+' |
'+
''+
'
'+
(crossEnv?'':'')+
'表数据行数
'+
''+
(warns?'':'');
}
function fmtSize(n){
if(n==null)return '';
if(n<1024)return n+' B';
if(n<1024*1024)return (n/1024).toFixed(1)+' KB';
return (n/1024/1024).toFixed(1)+' MB';
}
// ================== 导入:确认应用 ==================
async function applyRestore(){
const applyBtn=document.getElementById('btn-apply-restore');
const st=document.getElementById('restore-status');
if(!_restoreToken)return;
const forceEl=document.getElementById('restore-force-env');
const force=!!(forceEl&&forceEl.checked);
if(!confirm('确认应用导入?\n\n系统会先自动备份当前库(data/backups/pre_restore_*.zip),再把待导入数据覆盖现有全部数据。\n导入将在重启 web_server 后生效。\n\n确定继续?'))return;
if(applyBtn)applyBtn.disabled=true;
if(st)st.textContent='正在生成恢复任务…';
const r=await apiPost('/api/system/backup/apply',{token:_restoreToken,force_env_mismatch:force});
if(!r)return;
if(r.ok){
const box=document.getElementById('restore-preview');
box.innerHTML=''+
'✅ 恢复任务已生成。
当前库已自动备份到 '+esc(r.backup_name||'')+'。
'+
'请重启 web_server,启动时将自动应用导入数据(恢复完成前不要再次导入)。
';
if(st)st.textContent='';
if(applyBtn)applyBtn.style.display='none';
document.getElementById('restore-file').value='';
_restoreToken='';
}else{
showToast(r.error||'应用导入失败','error');
if(applyBtn)applyBtn.disabled=false;
}
}