// ============ 全局变量 ============ 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;