ERP/frontend/js/components/operations-log.js

235 lines
7.6 KiB
JavaScript
Raw Normal View History

2025-12-10 06:42:48 +00:00
// 操作日志管理
(() => {
let currentPage = 1;
const pageSize = 50;
Router.register('/system/operations-log', async () => {
const html = `
<div class="page-header">
<h1>操作日志</h1>
<div class="page-actions">
<input type="text" id="keyword-filter" class="input" placeholder="搜索操作详情..." style="width: 200px; margin-right: 10px;" />
<button id="search-btn" class="btn btn-secondary">搜索</button>
<button id="cleanup-btn" class="btn btn-danger" style="margin-left: 10px;">清理旧日志</button>
</div>
</div>
<div class="card">
<div class="card-body">
<div class="table-container">
<table class="data-table">
<thead>
<tr>
<th style="width: 60px;">ID</th>
<th style="width: 100px;">用户</th>
<th style="width: 150px;">操作类型</th>
<th>操作详情</th>
<th style="width: 180px;">操作时间</th>
</tr>
</thead>
<tbody id="log-list">
<tr>
<td colspan="5" class="text-center">加载中...</td>
</tr>
</tbody>
</table>
</div>
<div id="pagination" style="margin-top: 20px; display: flex; justify-content: space-between; align-items: center;">
<div id="page-info"></div>
<div>
<button id="prev-btn" class="btn btn-secondary" disabled>上一页</button>
<button id="next-btn" class="btn btn-secondary" style="margin-left: 10px;">下一页</button>
</div>
</div>
</div>
</div>
`;
setTimeout(() => {
loadLogs();
document.getElementById('search-btn')?.addEventListener('click', () => {
currentPage = 1;
loadLogs();
});
document.getElementById('keyword-filter')?.addEventListener('keypress', (e) => {
if (e.key === 'Enter') {
currentPage = 1;
loadLogs();
}
});
document.getElementById('cleanup-btn')?.addEventListener('click', cleanupLogs);
document.getElementById('prev-btn')?.addEventListener('click', () => {
if (currentPage > 1) {
currentPage--;
loadLogs();
}
});
document.getElementById('next-btn')?.addEventListener('click', () => {
currentPage++;
loadLogs();
});
}, 100);
return html;
});
// 操作类型中文映射
const actionLabels = {
'login': '登录',
'logout': '登出',
'create_customer_order': '创建客户订单',
'update_customer_order': '更新客户订单',
'delete_customer_order': '删除客户订单',
'create_reconciliation': '创建对账单',
'update_reconciliation': '更新对账单',
'delete_reconciliation': '删除对账单',
'batch_delete_reconciliation': '批量删除对账单',
'batch_delete_reconciliations': '批量删除对账单',
'create_work_order': '创建工单',
'update_work_order': '更新工单',
'delete_work_order': '删除工单',
'confirm_work_order': '确认工单',
'create_bom': '创建BOM',
'update_bom': '更新BOM',
'delete_bom': '删除BOM',
'import_bom': '导入BOM',
'create_initial_stock': '创建期初库存',
'update_initial_stock': '更新期初库存',
'delete_initial_stock': '删除期初库存',
'import_initial_stock': '导入期初库存',
'create_purchase_demand': '创建采购需求',
'update_purchase_demand': '更新采购需求',
'delete_purchase_demand': '删除采购需求',
'create_material_purchase': '创建物料采购',
'update_material_purchase': '更新物料采购',
'delete_material_purchase': '删除物料采购',
'batch_delete_material_purchase': '批量删除物料采购',
'import_material_purchase': '导入物料采购',
'cleanup_operations_log': '清理操作日志',
'add_personnel': '添加人员',
'upload_sop': '上传SOP',
'delete_sop': '删除SOP',
'upload_mac': '上传MAC',
'upload_stats': '上传统计',
'upload_repairs': '上传返修',
'upload_shipments': '上传发货',
'create_shipment': '创建发货',
'delete_shipment': '删除发货',
'clear_redis': '清空缓存',
'audit_yt_cost': '审核易泰勒成本',
'audit_yt_probe': '审核易泰勒探针',
'audit_pdd_cost': '审核拼多多成本',
'audit_pdd_probe': '审核拼多多探针',
'upload_shipment': '上传发货记录'
};
function getActionLabel(action) {
return actionLabels[action] || action;
}
async function loadLogs() {
try {
const keyword = document.getElementById('keyword-filter')?.value || '';
const res = await fetch(`/api/operations-log?page=${currentPage}&page_size=${pageSize}&keyword=${encodeURIComponent(keyword)}`);
const data = await res.json();
const tbody = document.getElementById('log-list');
if (!tbody) return;
if (!data.list || data.list.length === 0) {
tbody.innerHTML = '<tr><td colspan="5" class="text-center">暂无数据</td></tr>';
updatePagination(0);
return;
}
tbody.innerHTML = data.list.map(log => `
<tr>
<td>${log.id}</td>
<td>${log.username || '—'}</td>
<td><span class="badge">${getActionLabel(log.action)}</span></td>
<td style="max-width: 400px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;" title="${escapeHtml(log.detail || '')}">${log.detail || '—'}</td>
<td>${formatTime(log.ts)}</td>
</tr>
`).join('');
updatePagination(data.total);
} catch (err) {
console.error('加载操作日志失败:', err);
API.toast('加载失败', 'error');
}
}
function updatePagination(total) {
const pageInfo = document.getElementById('page-info');
const prevBtn = document.getElementById('prev-btn');
const nextBtn = document.getElementById('next-btn');
const totalPages = Math.ceil(total / pageSize);
if (pageInfo) {
pageInfo.textContent = `${total} 条记录,第 ${currentPage}/${totalPages || 1}`;
}
if (prevBtn) {
prevBtn.disabled = currentPage <= 1;
}
if (nextBtn) {
nextBtn.disabled = currentPage >= totalPages;
}
}
function formatTime(ts) {
if (!ts) return '—';
try {
const date = new Date(ts);
return date.toLocaleString('zh-CN', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit'
});
} catch {
return ts;
}
}
function escapeHtml(str) {
if (!str) return '';
return str.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;');
}
async function cleanupLogs() {
if (!confirm('确定要清理3个月前的旧日志吗此操作不可恢复。')) {
return;
}
try {
const res = await fetch('/api/operations-log/cleanup', {
method: 'DELETE'
});
const data = await res.json();
if (res.ok && data.ok) {
API.toast(data.message, 'success');
loadLogs();
} else {
API.toast(data.error || '清理失败', 'error');
}
} catch (err) {
console.error('清理日志失败:', err);
API.toast('清理失败', 'error');
}
}
})();