235 lines
7.6 KiB
JavaScript
235 lines
7.6 KiB
JavaScript
// 操作日志管理
|
||
(() => {
|
||
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, '&')
|
||
.replace(/</g, '<')
|
||
.replace(/>/g, '>')
|
||
.replace(/"/g, '"');
|
||
}
|
||
|
||
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');
|
||
}
|
||
}
|
||
})();
|