// ============ 全局变量 ============
let currentDbName = null;
let pendingImport = null;
let allBackups = [];
// ============ 初始化 ============
async function init() {
try {
await window.electronAPI.initDatabase();
loadDatabases();
setupDropZone();
setupEventListeners();
} catch (err) {
console.error("初始化失败:", err);
showToast("初始化失败: " + err.message, "error");
}
}
// ============ 工具函数 ============
const formatFileSize = (b) => window.electronAPI.formatFileSize(b);
const formatDateDisplay = (d) => window.electronAPI.formatDateDisplay(d);
// ============ 数据库管理 ============
function loadDatabases() {
const databases = window.electronAPI.listDatabases();
const listEl = document.getElementById("databaseList");
if (databases.length === 0) {
listEl.innerHTML = "
暂无数据库
";
return;
}
listEl.innerHTML = databases.map(db => `
${db.name}
${db.count} 备份 · ${formatFileSize(db.totalSize)}
`).join("");
listEl.querySelectorAll(".database-item").forEach(item => {
item.addEventListener("click", () => selectDatabase(item.dataset.name));
});
}
function selectDatabase(dbName) {
try {
window.electronAPI.openDatabase(dbName);
currentDbName = dbName;
document.getElementById("noDatabase").style.display = "none";
document.getElementById("databaseContent").style.display = "flex";
document.getElementById("searchInput").value = "";
document.querySelectorAll(".database-item").forEach(item => {
item.classList.toggle("active", item.dataset.name === dbName);
});
loadBackups();
} catch (err) {
showToast(err.message, "error");
}
}
function loadBackups() {
allBackups = window.electronAPI.getBackups(); // 已按 date_modified 降序排序
renderBackups(allBackups);
}
function renderBackups(backups) {
const listEl = document.getElementById("backupList");
document.getElementById("backupCount").textContent = `${backups.length} 个备份`;
if (backups.length === 0) {
listEl.innerHTML = "";
return;
}
const icons = { "zip": "fa-file-zipper", "rar": "fa-file-zipper", "7z": "fa-file-zipper", "tar": "fa-file-archive", "gz": "fa-file-archive" };
listEl.innerHTML = backups.map(b => `
${b.filename}
${formatDateDisplay(b.date_modified)}
${formatFileSize(b.file_size)}
${b.tag || "标签"}
`).join("");
}
function filterBackups() {
const query = document.getElementById("searchInput").value.toLowerCase().trim();
if (!query) {
renderBackups(allBackups);
return;
}
const filtered = allBackups.filter(b =>
b.filename.toLowerCase().includes(query) ||
(b.tag && b.tag.toLowerCase().includes(query)) ||
b.date_modified.includes(query)
);
renderBackups(filtered);
}
function escapeHtml(str) {
return str ? str.replace(/'/g, "\\'").replace(/"/g, "\\\"") : "";
}
// ============ 拖拽处理 ============
function setupDropZone() {
const dropZone = document.getElementById("dropZone");
["dragenter", "dragover", "dragleave", "drop"].forEach(e => {
dropZone.addEventListener(e, ev => { ev.preventDefault(); ev.stopPropagation(); });
document.body.addEventListener(e, ev => { ev.preventDefault(); ev.stopPropagation(); });
});
["dragenter", "dragover"].forEach(e => dropZone.addEventListener(e, () => dropZone.classList.add("drag-over")));
["dragleave", "drop"].forEach(e => dropZone.addEventListener(e, () => dropZone.classList.remove("drag-over")));
dropZone.addEventListener("drop", handleDrop);
}
async function handleDrop(e) {
if (!currentDbName) { showToast("请先选择数据库", "warning"); return; }
const files = e.dataTransfer.files;
if (files.length === 0) return;
await processImport(files[0].path);
}
async function processImport(filePath) {
showLoading("处理中...");
try {
const stats = window.electronAPI.getFileStats(filePath);
if (!stats) throw new Error("无法读取文件");
const originalName = window.electronAPI.pathBasename(filePath);
let dateModified, archiveType, sourcePath = filePath, tempFile = null;
if (stats.isDirectory) {
showLoading("压缩文件夹...");
const latestTime = window.electronAPI.getLatestModifiedTime(filePath);
dateModified = window.electronAPI.formatDateModified(latestTime);
archiveType = "zip";
const dataPath = await window.electronAPI.getDataPath();
const tempDir = window.electronAPI.pathJoin(dataPath, "temp");
window.electronAPI.ensureDir(tempDir);
tempFile = window.electronAPI.pathJoin(tempDir, `temp_${Date.now()}.zip`);
await window.electronAPI.compressFolder(filePath, tempFile);
sourcePath = tempFile;
} else {
if (!window.electronAPI.isSupportedArchive(filePath)) {
throw new Error("不支持的格式");
}
dateModified = window.electronAPI.formatDateModified(new Date(stats.mtime));
archiveType = window.electronAPI.getArchiveType(filePath);
}
const targetFilename = window.electronAPI.generateBackupFilename(currentDbName, dateModified, archiveType);
if (!window.electronAPI.isNameMatching(originalName, currentDbName)) {
hideLoading();
pendingImport = { sourcePath, originalName, dateModified, archiveType, targetFilename, tempFile };
showNameMismatchModal(originalName, currentDbName, targetFilename);
return;
}
await performImport(sourcePath, originalName, dateModified, archiveType, targetFilename, tempFile);
} catch (err) {
hideLoading();
showToast(err.message, "error");
}
}
async function performImport(sourcePath, originalName, dateModified, archiveType, targetFilename, tempFile) {
showLoading("导入中...");
try {
const storagePath = window.electronAPI.getCurrentStoragePath();
const targetPath = window.electronAPI.pathJoin(storagePath, targetFilename);
const existing = window.electronAPI.findByDateModified(dateModified);
if (existing) {
window.electronAPI.deleteFile(window.electronAPI.pathJoin(storagePath, existing.filename));
}
await window.electronAPI.copyArchive(sourcePath, targetPath);
const targetStats = window.electronAPI.getFileStats(targetPath);
window.electronAPI.upsertBackup({
filename: targetFilename,
original_name: originalName,
date_modified: dateModified,
archive_type: archiveType,
file_size: targetStats.size
});
if (tempFile) window.electronAPI.deleteFile(tempFile);
hideLoading();
showToast("导入成功", "success");
loadBackups();
loadDatabases();
} catch {
hideLoading();
showToast("导入失败", "error");
}
}
// ============ 模态框 ============
function showModal(id) { document.getElementById(id).classList.add("show"); }
function closeModal(id) {
document.getElementById(id).classList.remove("show");
if (id === "nameMismatchModal" && pendingImport?.tempFile) {
window.electronAPI.deleteFile(pendingImport.tempFile);
pendingImport = null;
}
}
function showNewDbModal() {
document.getElementById("dbModalTitle").textContent = "新建数据库";
document.getElementById("dbName").value = "";
document.getElementById("btnConfirmDb").onclick = confirmCreateDb;
showModal("dbModal");
document.getElementById("dbName").focus();
}
function confirmCreateDb() {
const name = document.getElementById("dbName").value.trim();
if (!name) { showToast("请输入名称", "warning"); return; }
if (!/^[\w\u4e00-\u9fa5]+$/.test(name)) { showToast("名称包含非法字符", "warning"); return; }
try {
window.electronAPI.createDatabase(name);
closeModal("dbModal");
showToast("创建成功", "success");
loadDatabases();
selectDatabase(name);
} catch (err) {
showToast(err.message, "error");
}
}
function showRenameDb(dbName) {
document.getElementById("dbModalTitle").textContent = "重命名";
document.getElementById("dbName").value = dbName;
document.getElementById("btnConfirmDb").onclick = () => confirmRenameDb(dbName);
showModal("dbModal");
document.getElementById("dbName").focus();
document.getElementById("dbName").select();
}
function confirmRenameDb(oldName) {
const newName = document.getElementById("dbName").value.trim();
if (!newName) { showToast("请输入名称", "warning"); return; }
if (newName === oldName) { closeModal("dbModal"); return; }
if (!/^[\w\u4e00-\u9fa5]+$/.test(newName)) { showToast("名称包含非法字符", "warning"); return; }
try {
showLoading("重命名中...");
window.electronAPI.renameDatabase(oldName, newName);
if (currentDbName === oldName) {
currentDbName = newName;
window.electronAPI.openDatabase(newName);
}
hideLoading();
closeModal("dbModal");
showToast("重命名成功", "success");
loadDatabases();
loadBackups();
} catch (err) {
hideLoading();
showToast(err.message, "error");
}
}
function confirmDeleteDb(dbName) {
showConfirm("删除数据库", `确定删除 "${dbName}"?所有备份将被删除!`, () => {
try {
window.electronAPI.deleteDatabase(dbName);
if (currentDbName === dbName) {
currentDbName = null;
document.getElementById("noDatabase").style.display = "flex";
document.getElementById("databaseContent").style.display = "none";
}
showToast("已删除", "success");
loadDatabases();
} catch (err) {
showToast(err.message, "error");
}
});
}
function showConfirm(title, message, onConfirm) {
document.getElementById("confirmTitle").textContent = title;
document.getElementById("confirmMessage").textContent = message;
document.getElementById("btnConfirmAction").onclick = () => { closeModal("confirmModal"); onConfirm(); };
showModal("confirmModal");
}
function showNameMismatchModal(sourceName, dbName, newFileName) {
document.getElementById("sourceFileName").textContent = sourceName;
document.getElementById("targetDbName").textContent = dbName;
document.getElementById("newFileName").textContent = newFileName;
showModal("nameMismatchModal");
}
async function confirmMismatchImport() {
closeModal("nameMismatchModal");
if (pendingImport) {
const { sourcePath, originalName, dateModified, archiveType, targetFilename, tempFile } = pendingImport;
pendingImport = null;
await performImport(sourcePath, originalName, dateModified, archiveType, targetFilename, tempFile);
}
}
// ============ 标签编辑 ============
let editingTagId = null;
function showEditTag(id, currentTag) {
editingTagId = id;
document.getElementById("tagInput").value = currentTag || "";
showModal("tagModal");
document.getElementById("tagInput").focus();
}
function saveTag() {
const tag = document.getElementById("tagInput").value.trim();
try {
window.electronAPI.updateTag(editingTagId, tag);
closeModal("tagModal");
showToast("已保存", "success");
loadBackups();
} catch (err) {
showToast(err.message, "error");
}
}
function clearTag() {
try {
window.electronAPI.updateTag(editingTagId, null);
closeModal("tagModal");
showToast("已清除", "success");
loadBackups();
} catch (err) {
showToast(err.message, "error");
}
}
// ============ 导出和删除 ============
async function exportBackup(id) {
const backup = allBackups.find(b => b.id === id);
if (!backup) { showToast("不存在", "error"); return; }
const exportDir = await window.electronAPI.selectExportDir();
if (!exportDir) return;
const storagePath = window.electronAPI.getCurrentStoragePath();
const sourcePath = window.electronAPI.pathJoin(storagePath, backup.filename);
const targetPath = window.electronAPI.pathJoin(exportDir, backup.filename);
try {
showLoading("导出中...");
await window.electronAPI.copyArchive(sourcePath, targetPath);
hideLoading();
showToast("导出成功", "success");
window.electronAPI.showInFolder(targetPath);
} catch {
hideLoading();
showToast("导出失败", "error");
}
}
function confirmDeleteBackup(id) {
showConfirm("删除备份", "确定删除?不可恢复!", () => {
try {
window.electronAPI.deleteBackup(id);
showToast("已删除", "success");
loadBackups();
loadDatabases();
} catch (err) {
showToast(err.message, "error");
}
});
}
// ============ 工具函数 ============
function showToast(message, type = "success") {
const toast = document.getElementById("toast");
const icons = { success: "fa-check", error: "fa-xmark", warning: "fa-exclamation" };
toast.className = "toast " + type;
document.getElementById("toastIcon").className = "toast-icon fa-solid " + (icons[type] || "fa-check");
document.getElementById("toastMessage").textContent = message;
toast.classList.add("show");
setTimeout(() => toast.classList.remove("show"), 2500);
}
function showLoading(text = "处理中...") {
document.getElementById("loadingText").textContent = text;
document.getElementById("loadingOverlay").classList.add("show");
}
function hideLoading() {
document.getElementById("loadingOverlay").classList.remove("show");
}
// ============ 事件监听 ============
function setupEventListeners() {
document.getElementById("btnConfirmImport").addEventListener("click", confirmMismatchImport);
document.getElementById("btnConfirmTag").addEventListener("click", saveTag);
document.getElementById("btnClearTag").addEventListener("click", clearTag);
document.querySelectorAll(".modal-overlay").forEach(overlay => {
overlay.addEventListener("click", e => {
const modal = e.target.closest(".modal");
if (modal) closeModal(modal.id);
});
});
document.addEventListener("keydown", e => {
if (e.key === "Escape") {
const openModal = document.querySelector(".modal.show");
if (openModal) closeModal(openModal.id);
}
});
document.getElementById("dbName").addEventListener("keydown", e => {
if (e.key === "Enter") document.getElementById("btnConfirmDb").click();
});
document.getElementById("tagInput").addEventListener("keydown", e => {
if (e.key === "Enter") saveTag();
});
}
// 初始化
document.addEventListener("DOMContentLoaded", init);
// 暴露全局函数
window.showNewDbModal = showNewDbModal;
window.showRenameDb = showRenameDb;
window.confirmDeleteDb = confirmDeleteDb;
window.showEditTag = showEditTag;
window.exportBackup = exportBackup;
window.confirmDeleteBackup = confirmDeleteBackup;
window.closeModal = closeModal;
window.filterBackups = filterBackups;