This commit is contained in:
zzh 2026-04-08 11:25:01 +08:00
parent c11a70fdf2
commit 2e2b166e08
3 changed files with 143 additions and 1 deletions

View File

@ -91,6 +91,7 @@
<button id="add-stock-btn" class="btn btn-primary" style="margin-right: 10px;">新增库存</button>
<button id="import-stock-btn" class="btn btn-secondary" style="margin-right: 10px;">导入 Excel</button>
<button id="download-template-btn" class="btn btn-secondary" style="margin-right: 10px;">下载模板</button>
<button id="restore-backup-btn" class="btn btn-secondary" style="margin-right: 10px;">📦 数据恢复</button>
<button id="batch-delete-btn" class="btn btn-danger">批量删除</button>
</div>
</div>
@ -282,12 +283,44 @@
</div>
</div>
</div>
<!-- 数据恢复弹窗 -->
<div id="restore-modal" class="modal" style="display:none;">
<div class="modal-content" style="max-width: 800px;">
<div class="modal-header">
<h2>📦 数据恢复</h2>
<button class="modal-close" onclick="InitialStock.closeRestoreModal()">&times;</button>
</div>
<div class="modal-body">
<div style="margin-bottom: 16px; padding: 12px; background: var(--warning-bg, #fff3cd); border-left: 4px solid var(--warning, #ffc107); border-radius: 4px;">
<strong> 重要提示</strong>
<ul style="margin: 8px 0 0 20px; padding: 0;">
<li>恢复数据将<strong>完全替换</strong></li>
<li>恢复前会自动备份当前数据可以再次恢复</li>
<li>请仔细确认要恢复的备份版本</li>
</ul>
</div>
<div style="margin-bottom: 12px;">
<strong>可用备份列表</strong>
</div>
<div id="backup-list" style="max-height: 400px; overflow-y: auto;">
<div style="text-align: center; padding: 40px; color: var(--text-3);">加载中...</div>
</div>
</div>
<div class="modal-footer">
<button class="btn btn-secondary" onclick="InitialStock.closeRestoreModal()">关闭</button>
</div>
</div>
</div>
`;
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, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
}
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 = '<div style="text-align: center; padding: 40px; color: var(--text-3);">暂无备份记录</div>';
return;
}
container.innerHTML = backups.map(backup => `
<div style="border: 1px solid var(--border); border-radius: 8px; padding: 16px; margin-bottom: 12px; background: white;">
<div style="display: flex; justify-content: space-between; align-items: start; margin-bottom: 12px;">
<div style="flex: 1;">
<div style="font-weight: 600; font-size: 15px; margin-bottom: 8px;">
${getOperationText(backup.operation)} - ${backup.factory || '全部工厂'}
</div>
<div style="font-size: 13px; color: var(--text-2); margin-bottom: 4px;">
📦 记录数量: <strong>${backup.record_count}</strong>
</div>
<div style="font-size: 13px; color: var(--text-2); margin-bottom: 4px;">
👤 操作人: ${backup.created_by || '系统'}
</div>
<div style="font-size: 13px; color: var(--text-2); margin-bottom: 4px;">
🕐 备份时间: ${formatTime(backup.created_at)}
</div>
${backup.description ? `<div style="font-size: 13px; color: var(--text-3); margin-top: 8px; padding: 8px; background: var(--surface); border-radius: 4px;">
💬 ${escapeHtml(backup.description)}
</div>` : ''}
</div>
<div style="display: flex; gap: 8px; margin-left: 16px;">
<button class="btn btn-sm btn-primary" onclick="InitialStock.restoreBackup(${backup.id})">
恢复此版本
</button>
<button class="btn btn-sm btn-danger" onclick="InitialStock.deleteBackup(${backup.id})">
删除
</button>
</div>
</div>
</div>
`).join('');
} catch (e) {
container.innerHTML = `<div style="text-align: center; padding: 40px; color: var(--danger);">加载失败: ${e.message}</div>`;
}
}
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
};
})();

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 602 KiB