2026-03-12 11:23:10 +08:00
|
|
|
// 导出功能
|
|
|
|
|
function showExportDialog() {
|
|
|
|
|
document.getElementById('exportModal').classList.add('show');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function closeExportModal() {
|
|
|
|
|
document.getElementById('exportModal').classList.remove('show');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function doExport(format) {
|
|
|
|
|
closeExportModal();
|
|
|
|
|
showToast('正在导出...');
|
|
|
|
|
try {
|
2026-07-01 16:15:23 +08:00
|
|
|
const res = await apiFetch(API + '/api/export?format=' + format);
|
2026-03-12 11:23:10 +08:00
|
|
|
if (!res.ok) {
|
|
|
|
|
const err = await res.json();
|
|
|
|
|
showToast(err.error || '导出失败');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
const disposition = res.headers.get('Content-Disposition');
|
|
|
|
|
let filename = 'gamer-export.' + format;
|
|
|
|
|
if (disposition) {
|
|
|
|
|
const match = disposition.match(/filename="?([^"]+)"?/);
|
|
|
|
|
if (match) filename = match[1];
|
|
|
|
|
}
|
|
|
|
|
const blob = await res.blob();
|
|
|
|
|
const url = URL.createObjectURL(blob);
|
|
|
|
|
const a = document.createElement('a');
|
|
|
|
|
a.href = url;
|
|
|
|
|
a.download = filename;
|
|
|
|
|
document.body.appendChild(a);
|
|
|
|
|
a.click();
|
|
|
|
|
document.body.removeChild(a);
|
|
|
|
|
URL.revokeObjectURL(url);
|
|
|
|
|
showToast('导出成功');
|
|
|
|
|
} catch (e) {
|
|
|
|
|
showToast('导出失败: 网络错误');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 导入功能
|
|
|
|
|
let pendingImportContent = null;
|
|
|
|
|
|
|
|
|
|
function triggerImport() {
|
|
|
|
|
document.getElementById('importFileInput').value = '';
|
|
|
|
|
document.getElementById('importFileInput').click();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function handleImportFile(event) {
|
|
|
|
|
const file = event.target.files[0];
|
|
|
|
|
if (!file) return;
|
|
|
|
|
const ext = file.name.split('.').pop().toLowerCase();
|
|
|
|
|
if (ext !== 'json' && ext !== 'csv') {
|
|
|
|
|
showToast('请选择 JSON 或 CSV 文件');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
const reader = new FileReader();
|
|
|
|
|
reader.onload = function(e) {
|
|
|
|
|
const content = e.target.result;
|
|
|
|
|
pendingImportContent = content;
|
|
|
|
|
let count = 0;
|
|
|
|
|
try {
|
|
|
|
|
const trimmed = content.replace(/^\uFEFF/, '').trim();
|
|
|
|
|
if (trimmed.startsWith('{')) {
|
|
|
|
|
const data = JSON.parse(trimmed);
|
|
|
|
|
count = data.recordCount || (data.records ? data.records.length : 0);
|
|
|
|
|
} else {
|
|
|
|
|
count = Math.max(0, trimmed.split('\n').filter(l => l.trim()).length - 1);
|
|
|
|
|
}
|
|
|
|
|
} catch (err) {
|
|
|
|
|
showToast('文件格式无法识别');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
document.getElementById('importConfirmMsg').textContent =
|
|
|
|
|
'文件: ' + file.name + '\n检测到 ' + count + ' 条记录,确认导入?\n(重复记录将自动跳过)';
|
|
|
|
|
document.getElementById('importConfirmModal').classList.add('show');
|
|
|
|
|
};
|
|
|
|
|
reader.readAsText(file, 'UTF-8');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function closeImportConfirm() {
|
|
|
|
|
document.getElementById('importConfirmModal').classList.remove('show');
|
|
|
|
|
pendingImportContent = null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function confirmImport() {
|
|
|
|
|
document.getElementById('importConfirmModal').classList.remove('show');
|
|
|
|
|
if (!pendingImportContent) return;
|
|
|
|
|
showToast('正在导入...');
|
|
|
|
|
try {
|
2026-07-01 16:15:23 +08:00
|
|
|
const res = await apiFetch(API + '/api/import', {
|
2026-03-12 11:23:10 +08:00
|
|
|
method: 'POST',
|
2026-07-01 16:15:23 +08:00
|
|
|
headers: { 'Content-Type': 'text/plain; charset=utf-8' },
|
2026-03-12 11:23:10 +08:00
|
|
|
body: pendingImportContent
|
|
|
|
|
});
|
|
|
|
|
const data = await res.json();
|
|
|
|
|
if (!res.ok) {
|
|
|
|
|
showToast(data.error || '导入失败');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
showToast('导入完成: ' + data.imported + '条新增, ' + data.skipped + '条跳过');
|
|
|
|
|
pendingImportContent = null;
|
|
|
|
|
loadStats();
|
|
|
|
|
loadTotalStats();
|
|
|
|
|
} catch (e) {
|
|
|
|
|
showToast('导入失败: 网络错误');
|
|
|
|
|
}
|
|
|
|
|
}
|