diff --git a/frontend/js/components/initial-stock.js b/frontend/js/components/initial-stock.js index 4e98007..84c4e76 100755 --- a/frontend/js/components/initial-stock.js +++ b/frontend/js/components/initial-stock.js @@ -91,6 +91,7 @@ + @@ -282,12 +283,44 @@ + + + `; setTimeout(() => { document.getElementById('add-stock-btn')?.addEventListener('click', () => openModal()); document.getElementById('import-stock-btn')?.addEventListener('click', () => showImportDialog()); document.getElementById('download-template-btn')?.addEventListener('click', () => downloadTemplate()); + document.getElementById('restore-backup-btn')?.addEventListener('click', () => showRestoreDialog()); document.getElementById('batch-delete-btn')?.addEventListener('click', () => batchDelete()); document.getElementById('search-keyword')?.addEventListener('keypress', (e) => { if (e.key === 'Enter') InitialStock.search(); @@ -623,6 +656,112 @@ return String(str).replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"'); } + async function showRestoreDialog() { + document.getElementById('restore-modal').style.display = 'flex'; + await loadBackupList(); + } + + function closeRestoreModal() { + document.getElementById('restore-modal').style.display = 'none'; + } + + async function loadBackupList() { + const container = document.getElementById('backup-list'); + try { + const res = await API.get('/api/backups?table_name=initial_stock'); + const backups = res.list || []; + + if (backups.length === 0) { + container.innerHTML = '
暂无备份记录
'; + return; + } + + container.innerHTML = backups.map(backup => ` +
+
+
+
+ ${getOperationText(backup.operation)} - ${backup.factory || '全部工厂'} +
+
+ 📦 记录数量: ${backup.record_count} 条 +
+
+ 👤 操作人: ${backup.created_by || '系统'} +
+
+ 🕐 备份时间: ${formatTime(backup.created_at)} +
+ ${backup.description ? `
+ 💬 ${escapeHtml(backup.description)} +
` : ''} +
+
+ + +
+
+
+ `).join(''); + } catch (e) { + container.innerHTML = `
加载失败: ${e.message}
`; + } + } + + function getOperationText(operation) { + const map = { + 'import': '📥 导入前备份', + 'batch_delete': '🗑️ 批量删除前备份', + 'restore': '🔄 恢复前备份', + 'manual': '💾 手动备份' + }; + return map[operation] || operation; + } + + async function restoreBackup(backupId) { + if (!confirm('确定要恢复此备份吗?\n\n当前数据将被完全替换,但会自动备份当前数据。')) { + return; + } + + try { + const overlay = document.getElementById('overlay'); + overlay.classList.remove('hidden'); + + const res = await API.post(`/api/backups/${backupId}/restore`, {}); + + overlay.classList.add('hidden'); + + if (res.ok) { + alert(res.message || '数据恢复成功'); + closeRestoreModal(); + loadList(); + } else { + alert(res.error || '恢复失败'); + } + } catch (e) { + document.getElementById('overlay').classList.add('hidden'); + alert('恢复失败: ' + e.message); + } + } + + async function deleteBackup(backupId) { + if (!confirm('确定要删除此备份吗?此操作不可恢复。')) { + return; + } + + try { + await API.delete(`/api/backups/${backupId}`); + alert('备份已删除'); + await loadBackupList(); + } catch (e) { + alert('删除失败: ' + e.message); + } + } + window.InitialStock = { search, resetSearch, @@ -635,6 +774,9 @@ updateSelectAll, goPage, closeImportModal, - importExcel + importExcel, + closeRestoreModal, + restoreBackup, + deleteBackup }; })(); diff --git a/server/data.db.backup_20260331_093801 b/server/data.db.backup_20260331_093801 new file mode 100644 index 0000000..17be9a8 Binary files /dev/null and b/server/data.db.backup_20260331_093801 differ diff --git a/server/uploads/repair_images/1775180119350_2026-04-03_092039_140.png b/server/uploads/repair_images/1775180119350_2026-04-03_092039_140.png new file mode 100644 index 0000000..f5f60e8 Binary files /dev/null and b/server/uploads/repair_images/1775180119350_2026-04-03_092039_140.png differ