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

235 lines
7.6 KiB
JavaScript
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 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');
}
}
})();