ERP/frontend/js/components/initial-stock.js
2026-04-08 11:25:01 +08:00

783 lines
31 KiB
JavaScript
Executable File
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 期初库存管理
(() => {
let stockList = [];
let currentPage = 1;
const pageSize = 20;
let editingId = null;
let currentFactory = '';
Router.register('/plan-mgmt/initial-stock', async () => {
const html = `
<style>
#initial-stock-page {
padding: 20px;
background: var(--bg);
}
#initial-stock-page .page-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
}
#initial-stock-page h1 {
margin: 0;
font-size: 24px;
}
#initial-stock-page .content-area {
background: white;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.05);
}
#initial-stock-page .filter-section {
padding: 20px;
display: flex;
gap: 15px;
align-items: center;
justify-content: space-between;
border-bottom: 1px solid var(--border);
}
#initial-stock-page .table-wrapper {
overflow-x: auto;
}
#initial-stock-page table {
width: 100%;
border-collapse: collapse;
margin: 0;
}
#initial-stock-page thead {
background: var(--border);
position: sticky;
top: 0;
z-index: 10;
}
#initial-stock-page th {
padding: 8px 12px;
text-align: left;
font-weight: 600;
color: var(--text);
border-bottom: 2px solid var(--border);
font-size: 14px;
white-space: nowrap;
}
#initial-stock-page td {
padding: 8px 12px;
border-bottom: 1px solid var(--border);
color: var(--text);
font-size: 14px;
}
#initial-stock-page tbody tr:hover {
background: var(--hover);
}
#initial-stock-page .text-center {
text-align: center;
}
</style>
<div id="initial-stock-page">
<div class="page-header">
<h1 style="margin: 0; font-size: 24px;">期初库存管理</h1>
<div class="page-actions" style="margin-top: 16px;">
<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>
<div class="content-area">
<div class="filter-section" style="padding: 20px; display: flex; gap: 15px; align-items: center; justify-content: flex-start;">
<select id="factory-filter" class="input" style="width: 200px;" onchange="InitialStock.filterByFactory()">
<option value="">全部工厂</option>
</select>
<input type="text" id="search-keyword" class="input" placeholder="搜索物料编码、名称或供应商" style="flex: 1; max-width: 400px;" />
<button class="btn btn-secondary" onclick="InitialStock.search()">搜索</button>
<button class="btn btn-secondary" onclick="InitialStock.resetSearch()">重置</button>
</div>
<div class="table-wrapper">
<table>
<thead>
<tr>
<th style="width: 40px;"><label class="custom-checkbox"><input type="checkbox" id="select-all" onchange="InitialStock.toggleSelectAll(this)" /><span class="checkmark"></span></label></th>
<th>物料编码</th>
<th>物料名称</th>
<th>库存数量</th>
<th>单位</th>
<th>最小包装</th>
<th>供应商</th>
<th>工厂</th>
<th>备注</th>
<th>更新时间</th>
<th>操作</th>
</tr>
</thead>
<tbody id="stock-list">
<tr><td colspan="10" class="text-center">加载中...</td></tr>
</tbody>
</table>
</div>
<div class="pagination" id="pagination" style="padding: 20px; display: flex; justify-content: center; gap: 8px;"></div>
</div>
</div>
<!-- Excel导入弹窗 -->
<div id="import-excel-modal" class="modal" style="display:none;">
<div class="modal-content" style="max-width: 600px;">
<div class="modal-header">
<h2>导入期初库存清单</h2>
<button class="modal-close" onclick="InitialStock.closeImportModal()">&times;</button>
</div>
<div class="modal-body">
<div class="field">
<label>默认工厂(可选)</label>
<div style="display: flex; gap: 5px;">
<input type="text" id="default-factory" class="input" placeholder="不填写则使用Excel中的工厂值" style="flex: 1;" />
<select id="default-factory-select" onchange="document.getElementById('default-factory').value = this.value;" style="width: 120px;">
<option value="">快速选择</option>
</select>
</div>
<div style="margin-top: 4px; font-size: 12px; color: var(--text-3);">
如果Excel中工厂列为空将使用此默认值如果都未填写则标记为"未指定"
</div>
</div>
<div class="field">
<label>选择Excel文件 <span style="color: var(--danger);">*</span></label>
<input type="file" id="excel-file" class="input" accept=".xlsx,.xls,.csv" />
<div style="margin-top: 8px; font-size: 12px; color: var(--text-3);">
支持 .xlsx、.xls 和 .csv 格式,文件大小不超过 10MB
</div>
</div>
<div style="margin-top: 16px; padding: 12px; background: var(--surface); border-radius: 8px; border: 1px solid var(--border);">
<div style="font-size: 14px; color: var(--text-2); margin-bottom: 12px;">
<strong>Excel文件格式要求</strong>
</div>
<table style="width: 100%; font-size: 13px; border-collapse: collapse;">
<thead>
<tr style="background: var(--surface-2);">
<th style="padding: 8px; text-align: left; border: 1px solid var(--border);">列名</th>
<th style="padding: 8px; text-align: left; border: 1px solid var(--border);">说明</th>
<th style="padding: 8px; text-align: left; border: 1px solid var(--border);">必填</th>
</tr>
</thead>
<tbody>
<tr>
<td style="padding: 6px; border: 1px solid var(--border); font-family: monospace;">物料编码</td>
<td style="padding: 6px; border: 1px solid var(--border;">物料编码</td>
<td style="padding: 6px; border: 1px solid var(--border); text-align: center;">✓</td>
</tr>
<tr>
<td style="padding: 6px; border: 1px solid var(--border); font-family: monospace;">物料名称</td>
<td style="padding: 6px; border: 1px solid var(--border;">物料名称</td>
<td style="padding: 6px; border: 1px solid var(--border); text-align: center;">✓</td>
</tr>
<tr>
<td style="padding: 6px; border: 1px solid var(--border); font-family: monospace;">库存数量</td>
<td style="padding: 6px; border: 1px solid var(--border;">当前库存数量</td>
<td style="padding: 6px; border: 1px solid var(--border); text-align: center;">✓</td>
</tr>
<tr>
<td style="padding: 6px; border: 1px solid var(--border); font-family: monospace;">单位</td>
<td style="padding: 6px; border: 1px solid var(--border;">单位pcs</td>
<td style="padding: 6px; border: 1px solid var(--border); text-align: center;">✓</td>
</tr>
<tr>
<td style="padding: 6px; border: 1px solid var(--border); font-family: monospace;">最小包装</td>
<td style="padding: 6px; border: 1px solid var(--border;">最小包装数量</td>
<td style="padding: 6px; border: 1px solid var(--border); text-align: center;">✓</td>
</tr>
<tr>
<td style="padding: 6px; border: 1px solid var(--border); font-family: monospace;">供应商</td>
<td style="padding: 6px; border: 1px solid var(--border;">供应商名称</td>
<td style="padding: 6px; border: 1px solid var(--border); text-align: center;">✓</td>
</tr>
<tr>
<td style="padding: 6px; border: 1px solid var(--border); font-family: monospace;">工厂</td>
<td style="padding: 6px; border: 1px solid var(--border;">工厂名称(可输入自定义名称,如:友辉、铨宝)</td>
<td style="padding: 6px; border: 1px solid var(--border); text-align: center;">✗</td>
</tr>
<tr>
<td style="padding: 6px; border: 1px solid var(--border); font-family: monospace;">备注</td>
<td style="padding: 6px; border: 1px solid var(--border;">备注信息</td>
<td style="padding: 6px; border: 1px solid var(--border); text-align: center;">✗</td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="modal-footer">
<button class="btn btn-secondary" onclick="InitialStock.closeImportModal()">取消</button>
<button class="btn btn-primary" onclick="InitialStock.importExcel()">导入</button>
</div>
</div>
</div>
<!-- 新增/编辑库存弹窗 -->
<div id="stock-modal" class="modal" style="display:none;">
<div class="modal-content" style="max-width: 500px;">
<div class="modal-header">
<h2 id="modal-title">新增期初库存</h2>
<button class="modal-close" onclick="InitialStock.closeModal()">&times;</button>
</div>
<div class="modal-body">
<form id="stock-form">
<div class="field">
<label>物料编码 <span style="color: var(--danger);">*</span></label>
<input type="text" id="material-code" class="input" required />
</div>
<div class="field">
<label>物料名称 <span style="color: var(--danger);">*</span></label>
<input type="text" id="material-name" class="input" required />
</div>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 16px;">
<div class="field">
<label>库存数量 <span style="color: var(--danger);">*</span></label>
<input type="number" id="stock-qty" class="input" min="0" value="0" required />
</div>
<div class="field">
<label>单位</label>
<input type="text" id="unit" class="input" value="pcs" />
</div>
</div>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 16px;">
<div class="field">
<label>最小包装</label>
<input type="number" id="min-package" class="input" min="1" value="1" />
</div>
<div class="field">
<label>供应商</label>
<input type="text" id="supplier" class="input" />
</div>
</div>
<div class="field">
<label>工厂</label>
<div style="display: flex; gap: 5px;">
<input type="text" id="factory" class="input" placeholder="请输入工厂名称" style="flex: 1;" />
<select id="factory-select" onchange="document.getElementById('factory').value = this.value;" style="width: 120px;">
<option value="">快速选择</option>
</select>
</div>
</div>
<div class="field">
<label>备注</label>
<textarea id="remark" class="input" rows="2"></textarea>
</div>
</form>
</div>
<div class="modal-footer">
<button class="btn btn-secondary" onclick="InitialStock.closeModal()">取消</button>
<button class="btn btn-primary" onclick="InitialStock.save()">保存</button>
</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();
});
loadList();
loadFactories();
}, 100);
return html;
});
async function showImportDialog() {
document.getElementById('excel-file').value = '';
document.getElementById('default-factory').value = '';
document.getElementById('import-excel-modal').style.display = 'flex';
// 动态加载工厂列表
await loadFactoriesForSelect('default-factory-select');
}
function closeImportModal() {
document.getElementById('import-excel-modal').style.display = 'none';
}
async function importExcel() {
const fileInput = document.getElementById('excel-file');
const file = fileInput.files[0];
const defaultFactory = document.getElementById('default-factory').value.trim();
if (!file) {
alert('请选择要导入的文件');
return;
}
const formData = new FormData();
formData.append('file', file);
if (defaultFactory) {
formData.append('default_factory', defaultFactory);
}
try {
const overlay = document.getElementById('overlay');
overlay.classList.remove('hidden');
const res = await fetch('/api/initial-stock/import', {
method: 'POST',
body: formData,
credentials: 'include'
});
overlay.classList.add('hidden');
const data = await res.json();
if (data.ok) {
alert(data.message || '导入成功');
closeImportModal();
loadList();
loadFactories(); // 重新加载工厂列表
} else {
alert(data.error || '导入失败');
}
} catch (err) {
document.getElementById('overlay').classList.add('hidden');
alert('导入失败: ' + err.message);
}
}
async function loadFactories() {
try {
const res = await API.get('/api/factories');
const factories = res.factories || [];
const select = document.getElementById('factory-filter');
if (select) {
// 保留"全部工厂"选项,清空其他选项
select.innerHTML = '<option value="">全部工厂</option>';
factories.forEach(factory => {
const option = document.createElement('option');
option.value = factory;
option.textContent = factory;
select.appendChild(option);
});
}
} catch (e) {
console.error('加载工厂列表失败:', e);
}
}
async function loadFactoriesForSelect(selectId) {
try {
const res = await API.get('/api/factories');
const factories = res.factories || [];
const select = document.getElementById(selectId);
if (select) {
const currentValue = select.value;
const firstOption = select.options[0] ? select.options[0].outerHTML : '<option value="">快速选择</option>';
select.innerHTML = firstOption;
factories.forEach(factory => {
const option = document.createElement('option');
option.value = factory;
option.textContent = factory;
select.appendChild(option);
});
if (currentValue) {
select.value = currentValue;
}
}
} catch (e) {
console.error('加载工厂列表失败:', e);
}
}
function downloadTemplate() {
const headers = ['物料编码', '物料名称', '库存数量', '单位', '最小包装', '供应商', '工厂', '备注'];
const example1 = ['PCB-001', '主控PCB板', '50', 'pcs', '10', '深圳电子', '友辉', ''];
const example2 = ['PCB-002', '电源板', '100', 'pcs', '20', '东莞电子', '铨宝', ''];
const example3 = ['PCB-003', '显示板', '75', 'pcs', '15', '苏州电子', '新工厂', ''];
const csvContent = [headers.join(','), example1.join(','), example2.join(','), example3.join(',')].join('\n');
const blob = new Blob(['\ufeff' + csvContent], { type: 'text/csv;charset=utf-8;' });
const link = document.createElement('a');
link.href = URL.createObjectURL(blob);
link.download = '期初库存导入模板.csv';
link.click();
}
async function loadList() {
try {
let url = '/api/initial-stock';
if (currentFactory) {
url += `?factory=${encodeURIComponent(currentFactory)}`;
}
const res = await API.get(url);
stockList = res.list || [];
renderList();
} catch (e) {
console.error('加载库存列表失败:', e);
document.getElementById('stock-list').innerHTML = '<tr><td colspan="11" class="text-center" style="color: var(--danger);">加载失败</td></tr>';
}
}
function renderList() {
const tbody = document.getElementById('stock-list');
const keyword = (document.getElementById('search-keyword')?.value || '').toLowerCase();
let filtered = stockList;
if (keyword) {
filtered = stockList.filter(item =>
(item.material_code || '').toLowerCase().includes(keyword) ||
(item.material_name || '').toLowerCase().includes(keyword) ||
(item.supplier || '').toLowerCase().includes(keyword)
);
}
const totalPages = Math.ceil(filtered.length / pageSize);
const start = (currentPage - 1) * pageSize;
const pageData = filtered.slice(start, start + pageSize);
if (pageData.length === 0) {
tbody.innerHTML = '<tr><td colspan="11" class="text-center">暂无数据</td></tr>';
} else {
tbody.innerHTML = pageData.map(item => `
<tr>
<td><label class="custom-checkbox"><input type="checkbox" class="row-checkbox" data-id="${item.id}" onchange="InitialStock.updateSelectAll()" /><span class="checkmark"></span></label></td>
<td>${escapeHtml(item.material_code || '')}</td>
<td>${escapeHtml(item.material_name || '')}</td>
<td style="font-weight: 600; color: var(--primary);">${item.stock_qty || 0}</td>
<td>${escapeHtml(item.unit || 'pcs')}</td>
<td>${item.min_package || 1}</td>
<td>${escapeHtml(item.supplier || '-')}</td>
<td>${escapeHtml(item.factory || '-')}</td>
<td>${escapeHtml(item.remark || '-')}</td>
<td>${formatTime(item.updated_at)}</td>
<td>
<button class="btn btn-sm btn-secondary" onclick="InitialStock.edit(${item.id})">编辑</button>
<button class="btn btn-sm btn-danger" onclick="InitialStock.delete(${item.id})">删除</button>
</td>
</tr>
`).join('');
}
renderPagination(totalPages);
}
function renderPagination(totalPages) {
const container = document.getElementById('pagination');
if (totalPages <= 1) {
container.innerHTML = '';
return;
}
let html = '';
html += `<button class="btn btn-sm btn-secondary" ${currentPage <= 1 ? 'disabled' : ''} onclick="InitialStock.goPage(${currentPage - 1})">上一页</button>`;
html += `<span style="padding: 0 12px; line-height: 32px;">第 ${currentPage} / ${totalPages} 页</span>`;
html += `<button class="btn btn-sm btn-secondary" ${currentPage >= totalPages ? 'disabled' : ''} onclick="InitialStock.goPage(${currentPage + 1})">下一页</button>`;
container.innerHTML = html;
}
async function openModal(item = null) {
editingId = item?.id || null;
document.getElementById('modal-title').textContent = item ? '编辑期初库存' : '新增期初库存';
document.getElementById('material-code').value = item?.material_code || '';
document.getElementById('material-name').value = item?.material_name || '';
document.getElementById('stock-qty').value = item?.stock_qty || 0;
document.getElementById('unit').value = item?.unit || 'pcs';
document.getElementById('min-package').value = item?.min_package || 1;
document.getElementById('supplier').value = item?.supplier || '';
document.getElementById('factory').value = item?.factory || '';
document.getElementById('remark').value = item?.remark || '';
document.getElementById('stock-modal').style.display = 'flex';
// 动态加载工厂列表
await loadFactoriesForSelect('factory-select');
}
function closeModal() {
document.getElementById('stock-modal').style.display = 'none';
editingId = null;
}
async function save() {
const data = {
material_code: document.getElementById('material-code').value.trim(),
material_name: document.getElementById('material-name').value.trim(),
stock_qty: parseInt(document.getElementById('stock-qty').value) || 0,
unit: document.getElementById('unit').value.trim() || 'pcs',
min_package: parseInt(document.getElementById('min-package').value) || 1,
supplier: document.getElementById('supplier').value.trim(),
factory: document.getElementById('factory').value.trim(),
remark: document.getElementById('remark').value.trim()
};
if (!data.material_code || !data.material_name) {
alert('请填写物料编码和物料名称');
return;
}
try {
if (editingId) {
await API.put(`/api/initial-stock/${editingId}`, data);
alert('更新成功');
} else {
await API.post('/api/initial-stock', data);
alert('创建成功');
}
closeModal();
loadList();
} catch (e) {
alert(e.message || '操作失败');
}
}
async function edit(id) {
const item = stockList.find(x => x.id === id);
if (item) openModal(item);
}
async function deleteStock(id) {
if (!confirm('确定要删除这条库存记录吗?')) return;
try {
await API.delete(`/api/initial-stock/${id}`);
alert('删除成功');
loadList();
} catch (e) {
alert(e.message || '删除失败');
}
}
async function batchDelete() {
const checked = document.querySelectorAll('.row-checkbox:checked');
if (checked.length === 0) {
alert('请先选择要删除的项');
return;
}
if (!confirm(`确定要删除选中的 ${checked.length} 条库存记录吗?`)) return;
const ids = Array.from(checked).map(cb => parseInt(cb.dataset.id));
try {
await API.post('/api/initial-stock/batch-delete', { ids });
alert('批量删除成功');
loadList();
} catch (e) {
alert(e.message || '批量删除失败');
}
}
function toggleSelectAll(checkbox) {
document.querySelectorAll('.row-checkbox').forEach(cb => cb.checked = checkbox.checked);
}
function updateSelectAll() {
const selectAllCheckbox = document.getElementById('select-all');
const rowCheckboxes = document.querySelectorAll('.row-checkbox');
selectAllCheckbox.checked = rowCheckboxes.length > 0 && Array.from(rowCheckboxes).every(cb => cb.checked);
}
function search() {
currentPage = 1;
renderList();
}
function resetSearch() {
document.getElementById('search-keyword').value = '';
document.getElementById('factory-filter').value = '';
currentFactory = '';
currentPage = 1;
loadList();
}
function filterByFactory() {
const factory = document.getElementById('factory-filter')?.value || '';
currentFactory = factory;
currentPage = 1;
loadList();
}
function goPage(page) {
currentPage = page;
renderList();
}
function formatTime(ts) {
if (!ts) return '-';
try {
const d = new Date(ts);
return d.toLocaleString('zh-CN', { hour12: false });
} catch {
return ts;
}
}
function escapeHtml(str) {
if (!str) return '';
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,
filterByFactory,
edit,
delete: deleteStock,
closeModal,
save,
toggleSelectAll,
updateSelectAll,
goPage,
closeImportModal,
importExcel,
closeRestoreModal,
restoreBackup,
deleteBackup
};
})();