增加操作日志,修改图标
This commit is contained in:
parent
31ce256864
commit
2152b13e97
@ -1 +1,4 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1763817367778" class="icon" viewBox="0 0 1292 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="28036" xmlns:xlink="http://www.w3.org/1999/xlink" width="252.34375" height="200"><path d="M1192.333763 0H99.918598A99.917961 99.917961 0 0 0 0.000636 100.236172V795.525171a99.917961 99.917961 0 0 0 99.917962 100.236171h1092.415165A100.236172 100.236172 0 0 0 1292.569934 795.525171V100.236172A100.236172 100.236172 0 0 0 1192.333763 0z m-122.829087 299.753884l-345.257924 275.25171-190.926041-203.654444-273.660659 266.660037a27.684276 27.684276 0 0 1-19.410814 7.637042 27.047856 27.047856 0 0 1-20.047234-9.228092 27.684276 27.684276 0 0 1 0-39.139839l313.436917-305.799875 195.062772 207.154754 305.481666-243.430702a28.320696 28.320696 0 0 1 39.139838 4.454941 28.002486 28.002486 0 0 1-3.818521 40.094468z" fill="#efb336" p-id="28037"></path><path d="M277.479816 964.176507m25.775016 0l686.060907 0q25.775016 0 25.775016 25.775016l0 8.273461q0 25.775016-25.775016 25.775016l-686.060907 0q-25.775016 0-25.775016-25.775016l0-8.273461q0-25.775016 25.775016-25.775016Z" fill="#efb336" p-id="28038"></path><path d="M613.509648 896.079553h64.914854v81.461777h-64.914854z" fill="#efb336" p-id="28039"></path></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 4091.27 4091.73" width="100" height="100">
|
||||
<path fill="#F7931A" d="M4030.06 2540.77c-273.24,1096.01 -1383.32,1763.02 -2479.46,1489.71 -1095.68,-273.24 -1762.69,-1383.39 -1489.33,-2479.31 273.12,-1096.13 1383.2,-1763.19 2479,-1489.95 1096.06,273.24 1763.03,1383.51 1489.76,2479.57l0.02 -0.02z"/>
|
||||
<path fill="white" d="M2947.77 1754.38c40.72,-272.26 -166.56,-418.61 -450,-516.24l91.95 -368.8 -224.5 -55.94 -89.51 359.09c-59.02,-14.72 -119.63,-28.59 -179.87,-42.34l90.16 -361.46 -224.36 -55.94 -92 368.68c-48.84,-11.12 -96.81,-22.11 -143.35,-33.69l0.26 -1.16 -309.59 -77.31 -59.72 239.78c0,0 166.56,38.18 163.05,40.53 90.91,22.69 107.35,82.87 104.62,130.57l-104.74 420.15c6.26,1.59 14.38,3.89 23.34,7.49 -7.49,-1.86 -15.46,-3.89 -23.73,-5.87l-146.81 588.57c-11.11,27.62 -39.31,69.07 -102.87,53.33 2.25,3.26 -163.17,-40.72 -163.17,-40.72l-111.46 256.98 292.15 72.83c54.35,13.63 107.61,27.89 160.06,41.3l-92.9 373.03 224.24 55.94 92 -369.07c61.26,16.63 120.71,31.97 178.91,46.43l-91.69 367.33 224.51 55.94 92.89 -372.33c382.82,72.45 670.67,43.24 791.83,-303.02 97.63,-278.78 -4.86,-439.58 -206.26,-544.44 146.69,-33.83 257.18,-130.31 286.64,-329.61l-0.07 -0.05zm-512.93 719.26c-69.38,278.78 -538.76,128.08 -690.94,90.29l123.28 -494.2c152.17,37.99 640.17,113.17 567.67,403.91zm69.43 -723.3c-63.29,253.58 -453.96,124.75 -580.69,93.16l111.77 -448.21c126.73,31.59 534.85,90.55 468.94,355.05l-0.02 0z"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.4 KiB |
@ -134,6 +134,48 @@ body {
|
||||
filter: drop-shadow(0 4px 8px rgba(59, 130, 246, 0.3));
|
||||
}
|
||||
|
||||
/* 比特币旋转硬币 */
|
||||
.logo-icon.bitcoin-coin {
|
||||
perspective: 500px;
|
||||
animation: none;
|
||||
}
|
||||
|
||||
.logo-icon.bitcoin-coin .coin-face {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 50%;
|
||||
backface-visibility: hidden;
|
||||
}
|
||||
|
||||
.logo-icon.bitcoin-coin .front {
|
||||
transform: translateZ(2px);
|
||||
animation: rotate-bitcoin 7s infinite linear;
|
||||
transform-style: preserve-3d;
|
||||
}
|
||||
|
||||
.logo-icon.bitcoin-coin .back {
|
||||
transform: rotateY(180deg) translateZ(2px);
|
||||
animation: rotate-bitcoin 7s infinite linear;
|
||||
transform-style: preserve-3d;
|
||||
}
|
||||
|
||||
.logo-icon.bitcoin-coin::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, #b36a00, #faa504, #b36a00);
|
||||
border-radius: 50%;
|
||||
animation: rotate-bitcoin 7s infinite linear;
|
||||
transform-style: preserve-3d;
|
||||
}
|
||||
|
||||
@keyframes rotate-bitcoin {
|
||||
0% { transform: rotateY(0deg); }
|
||||
100% { transform: rotateY(360deg); }
|
||||
}
|
||||
|
||||
@keyframes float-icon {
|
||||
0%, 100% {
|
||||
transform: translateY(0) scale(1);
|
||||
|
||||
@ -12,6 +12,15 @@ body{margin:0;background:var(--bg);color:var(--text);font-family:Inter,system-ui
|
||||
.brand-logo svg{width:18px;height:18px;color:#fff}
|
||||
.brand-logo.dollar-spinner{border-top:2px solid #eab308;background:#fde047;animation:spin 1s linear infinite;border-radius:50%;color:#a16207;font-weight:700;font-size:16px}
|
||||
@keyframes spin{0%{transform:rotate(0deg)}100%{transform:rotate(360deg)}}
|
||||
.coin-wrapper{width:32px;height:32px;perspective:500px;flex-shrink:0}
|
||||
.coin{width:32px;height:32px;position:relative;animation:rotate_coin 7s infinite linear;transform-style:preserve-3d}
|
||||
.coin .side{position:absolute;width:100%;height:100%;border-radius:50%;backface-visibility:hidden}
|
||||
.coin .heads{transform:translateZ(1.5px)}
|
||||
.coin .tails{transform:rotateY(180deg) translateZ(1.5px)}
|
||||
.coin::before{content:'';position:absolute;width:100%;height:100%;background:linear-gradient(90deg,#b36a00,#faa504,#b36a00);border-radius:50%;transform:translateZ(0px)}
|
||||
.coin::after{content:'';position:absolute;width:100%;height:100%;border-radius:50%;box-shadow:inset 0 0 0 1.5px #b36a00,0 0 2px rgba(0,0,0,0.3);transform:translateZ(1.5px)}
|
||||
@keyframes rotate_coin{100%{transform:rotateY(360deg)}}
|
||||
.coin .svg_back{transform:scaleX(-1)}
|
||||
.brand-text{display:flex;flex-direction:column;line-height:1.2;overflow:hidden}
|
||||
.brand-title{font-size:15px;font-weight:700;color:var(--text);letter-spacing:0.5px;white-space:nowrap}
|
||||
.brand-subtitle{font-size:10px;color:var(--text-2);letter-spacing:1px;text-transform:uppercase;font-weight:500;white-space:nowrap}
|
||||
@ -23,6 +32,8 @@ body{margin:0;background:var(--bg);color:var(--text);font-family:Inter,system-ui
|
||||
.sidebar.collapsed .brand-logo{width:40px;height:40px;border-radius:10px}
|
||||
.sidebar.collapsed .brand-logo svg{width:22px;height:22px}
|
||||
.sidebar.collapsed .brand-logo.dollar-spinner{border-radius:50%;font-size:20px}
|
||||
.sidebar.collapsed .coin-wrapper{width:36px;height:36px}
|
||||
.sidebar.collapsed .coin{width:36px;height:36px}
|
||||
.sidebar.collapsed .sidebar-toggle{width:40px;height:32px;background:rgba(79,140,255,0.08);border-radius:8px}
|
||||
.sidebar-toggle{width:36px;height:36px;border:none;background:transparent;color:var(--text-2);cursor:pointer;border-radius:8px;display:flex;align-items:center;justify-content:center;transition:all 0.2s ease;flex-shrink:0}
|
||||
.sidebar-toggle:hover{background:rgba(79,140,255,.12);color:var(--text)}
|
||||
|
||||
@ -16,7 +16,26 @@
|
||||
<aside class="sidebar" id="sidebar">
|
||||
<div class="brand-container">
|
||||
<div class="loader-wrapper">
|
||||
<div class="brand-logo dollar-spinner">$</div>
|
||||
<div class="coin-wrapper">
|
||||
<div class="coin">
|
||||
<div class="side heads">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" width="100%" height="100%" version="1.1" shape-rendering="geometricPrecision" text-rendering="geometricPrecision" image-rendering="optimizeQuality" fill-rule="evenodd" clip-rule="evenodd" viewBox="0 0 4091.27 4091.73">
|
||||
<g id="Layer_x0020_1">
|
||||
<path fill="#F7931A" fill-rule="nonzero" d="M4030.06 2540.77c-273.24,1096.01 -1383.32,1763.02 -2479.46,1489.71 -1095.68,-273.24 -1762.69,-1383.39 -1489.33,-2479.31 273.12,-1096.13 1383.2,-1763.19 2479,-1489.95 1096.06,273.24 1763.03,1383.51 1489.76,2479.57l0.02 -0.02z"></path>
|
||||
<path fill="white" fill-rule="nonzero" d="M2947.77 1754.38c40.72,-272.26 -166.56,-418.61 -450,-516.24l91.95 -368.8 -224.5 -55.94 -89.51 359.09c-59.02,-14.72 -119.63,-28.59 -179.87,-42.34l90.16 -361.46 -224.36 -55.94 -92 368.68c-48.84,-11.12 -96.81,-22.11 -143.35,-33.69l0.26 -1.16 -309.59 -77.31 -59.72 239.78c0,0 166.56,38.18 163.05,40.53 90.91,22.69 107.35,82.87 104.62,130.57l-104.74 420.15c6.26,1.59 14.38,3.89 23.34,7.49 -7.49,-1.86 -15.46,-3.89 -23.73,-5.87l-146.81 588.57c-11.11,27.62 -39.31,69.07 -102.87,53.33 2.25,3.26 -163.17,-40.72 -163.17,-40.72l-111.46 256.98 292.15 72.83c54.35,13.63 107.61,27.89 160.06,41.3l-92.9 373.03 224.24 55.94 92 -369.07c61.26,16.63 120.71,31.97 178.91,46.43l-91.69 367.33 224.51 55.94 92.89 -372.33c382.82,72.45 670.67,43.24 791.83,-303.02 97.63,-278.78 -4.86,-439.58 -206.26,-544.44 146.69,-33.83 257.18,-130.31 286.64,-329.61l-0.07 -0.05zm-512.93 719.26c-69.38,278.78 -538.76,128.08 -690.94,90.29l123.28 -494.2c152.17,37.99 640.17,113.17 567.67,403.91zm69.43 -723.3c-63.29,253.58 -453.96,124.75 -580.69,93.16l111.77 -448.21c126.73,31.59 534.85,90.55 468.94,355.05l-0.02 0z"></path>
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="side tails">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="svg_back" xml:space="preserve" width="100%" height="100%" version="1.1" shape-rendering="geometricPrecision" text-rendering="geometricPrecision" image-rendering="optimizeQuality" fill-rule="evenodd" clip-rule="evenodd" viewBox="0 0 4091.27 4091.73">
|
||||
<g id="Layer_x0020_1">
|
||||
<path fill="#F7931A" fill-rule="nonzero" d="M4030.06 2540.77c-273.24,1096.01 -1383.32,1763.02 -2479.46,1489.71 -1095.68,-273.24 -1762.69,-1383.39 -1489.33,-2479.31 273.12,-1096.13 1383.2,-1763.19 2479,-1489.95 1096.06,273.24 1763.03,1383.51 1489.76,2479.57l0.02 -0.02z"></path>
|
||||
<path fill="white" fill-rule="nonzero" d="M2947.77 1754.38c40.72,-272.26 -166.56,-418.61 -450,-516.24l91.95 -368.8 -224.5 -55.94 -89.51 359.09c-59.02,-14.72 -119.63,-28.59 -179.87,-42.34l90.16 -361.46 -224.36 -55.94 -92 368.68c-48.84,-11.12 -96.81,-22.11 -143.35,-33.69l0.26 -1.16 -309.59 -77.31 -59.72 239.78c0,0 166.56,38.18 163.05,40.53 90.91,22.69 107.35,82.87 104.62,130.57l-104.74 420.15c6.26,1.59 14.38,3.89 23.34,7.49 -7.49,-1.86 -15.46,-3.89 -23.73,-5.87l-146.81 588.57c-11.11,27.62 -39.31,69.07 -102.87,53.33 2.25,3.26 -163.17,-40.72 -163.17,-40.72l-111.46 256.98 292.15 72.83c54.35,13.63 107.61,27.89 160.06,41.3l-92.9 373.03 224.24 55.94 92 -369.07c61.26,16.63 120.71,31.97 178.91,46.43l-91.69 367.33 224.51 55.94 92.89 -372.33c382.82,72.45 670.67,43.24 791.83,-303.02 97.63,-278.78 -4.86,-439.58 -206.26,-544.44 146.69,-33.83 257.18,-130.31 286.64,-329.61l-0.07 -0.05zm-512.93 719.26c-69.38,278.78 -538.76,128.08 -690.94,90.29l123.28 -494.2c152.17,37.99 640.17,113.17 567.67,403.91zm69.43 -723.3c-63.29,253.58 -453.96,124.75 -580.69,93.16l111.77 -448.21c126.73,31.59 534.85,90.55 468.94,355.05l-0.02 0z"></path>
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="brand-text">
|
||||
<span class="brand-title">韬智生产管理</span>
|
||||
<span class="brand-subtitle">Production System</span>
|
||||
@ -180,6 +199,13 @@
|
||||
<span class="text">设置</span>
|
||||
</a>
|
||||
</div>
|
||||
<div class="nav-group superadmin-only" style="display:none;">
|
||||
<div class="nav-group-title">系统管理</div>
|
||||
<a href="#/system/operations-log" class="nav-item" data-route="system-operations-log">
|
||||
<span class="icon">📜</span>
|
||||
<span class="text">操作日志</span>
|
||||
</a>
|
||||
</div>
|
||||
</nav>
|
||||
<div class="theme-toggle-container">
|
||||
<label class="bb8-toggle">
|
||||
@ -289,6 +315,7 @@
|
||||
<script src="./js/components/export.js"></script>
|
||||
<script src="./js/components/settings.js"></script>
|
||||
<script src="./js/components/notifications.js"></script>
|
||||
<script src="./js/components/operations-log.js"></script>
|
||||
<script src="./js/app.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@ -51,6 +51,16 @@
|
||||
planMgmtGroup.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
// 超级管理员专属菜单(系统管理)
|
||||
const superadminMenus = document.querySelectorAll('.superadmin-only');
|
||||
superadminMenus.forEach(menu => {
|
||||
if (user && user.role === 'superadmin') {
|
||||
menu.style.display = '';
|
||||
} else {
|
||||
menu.style.display = 'none';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 创建水印
|
||||
|
||||
@ -124,7 +124,7 @@
|
||||
<td>${order.order_date || '—'}</td>
|
||||
<td>${order.order_no || '—'}</td>
|
||||
<td>${order.customer_name || '—'}</td>
|
||||
<td style="white-space: pre-wrap;">${order.material || '—'}</td>
|
||||
<td style="white-space: pre-line; word-break: break-word;">${order.material || '—'}</td>
|
||||
<td>${order.quantity || 0}</td>
|
||||
<td>${order.unit_price || 0}</td>
|
||||
<td>
|
||||
|
||||
234
frontend/js/components/operations-log.js
Normal file
234
frontend/js/components/operations-log.js
Normal file
@ -0,0 +1,234 @@
|
||||
// 操作日志管理
|
||||
(() => {
|
||||
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');
|
||||
}
|
||||
}
|
||||
})();
|
||||
@ -16,64 +16,19 @@
|
||||
<div class="login-card">
|
||||
<div class="login-header">
|
||||
<div class="logo-container">
|
||||
<div class="logo-icon">
|
||||
<svg viewBox="0 0 64 64" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<!-- 工厂主体 -->
|
||||
<path d="M8 52h48v8H8z" fill="url(#grad1)"/>
|
||||
<!-- 左侧建筑 -->
|
||||
<path d="M8 28h16v24H8z" fill="url(#grad2)"/>
|
||||
<!-- 中间建筑 -->
|
||||
<path d="M24 20h16v32H24z" fill="url(#grad3)"/>
|
||||
<!-- 右侧建筑 -->
|
||||
<path d="M40 24h16v28H40z" fill="url(#grad4)"/>
|
||||
<!-- 烟囱 -->
|
||||
<rect x="14" y="16" width="4" height="12" rx="1" fill="#60a5fa"/>
|
||||
<rect x="30" y="10" width="4" height="10" rx="1" fill="#60a5fa"/>
|
||||
<rect x="46" y="14" width="4" height="10" rx="1" fill="#60a5fa"/>
|
||||
<!-- 烟雾 -->
|
||||
<circle cx="16" cy="14" r="2" fill="#93c5fd" opacity="0.6"/>
|
||||
<circle cx="18" cy="12" r="2.5" fill="#93c5fd" opacity="0.5"/>
|
||||
<circle cx="32" cy="8" r="2" fill="#93c5fd" opacity="0.6"/>
|
||||
<circle cx="34" cy="6" r="2.5" fill="#93c5fd" opacity="0.5"/>
|
||||
<circle cx="48" cy="12" r="2" fill="#93c5fd" opacity="0.6"/>
|
||||
<circle cx="50" cy="10" r="2.5" fill="#93c5fd" opacity="0.5"/>
|
||||
<!-- 窗户 -->
|
||||
<rect x="12" y="34" width="4" height="4" rx="0.5" fill="#dbeafe"/>
|
||||
<rect x="18" y="34" width="4" height="4" rx="0.5" fill="#dbeafe"/>
|
||||
<rect x="12" y="42" width="4" height="4" rx="0.5" fill="#dbeafe"/>
|
||||
<rect x="18" y="42" width="4" height="4" rx="0.5" fill="#dbeafe"/>
|
||||
<rect x="28" y="26" width="4" height="4" rx="0.5" fill="#dbeafe"/>
|
||||
<rect x="34" y="26" width="4" height="4" rx="0.5" fill="#dbeafe"/>
|
||||
<rect x="28" y="34" width="4" height="4" rx="0.5" fill="#dbeafe"/>
|
||||
<rect x="34" y="34" width="4" height="4" rx="0.5" fill="#dbeafe"/>
|
||||
<rect x="28" y="42" width="4" height="4" rx="0.5" fill="#dbeafe"/>
|
||||
<rect x="34" y="42" width="4" height="4" rx="0.5" fill="#dbeafe"/>
|
||||
<rect x="44" y="30" width="4" height="4" rx="0.5" fill="#dbeafe"/>
|
||||
<rect x="50" y="30" width="4" height="4" rx="0.5" fill="#dbeafe"/>
|
||||
<rect x="44" y="38" width="4" height="4" rx="0.5" fill="#dbeafe"/>
|
||||
<rect x="50" y="38" width="4" height="4" rx="0.5" fill="#dbeafe"/>
|
||||
<!-- 大门 -->
|
||||
<rect x="28" y="48" width="8" height="4" rx="0.5" fill="#1e40af"/>
|
||||
<!-- 渐变定义 -->
|
||||
<defs>
|
||||
<linearGradient id="grad1" x1="0%" y1="0%" x2="0%" y2="100%">
|
||||
<stop offset="0%" style="stop-color:#1e3a8a;stop-opacity:1" />
|
||||
<stop offset="100%" style="stop-color:#1e40af;stop-opacity:1" />
|
||||
</linearGradient>
|
||||
<linearGradient id="grad2" x1="0%" y1="0%" x2="0%" y2="100%">
|
||||
<stop offset="0%" style="stop-color:#2563eb;stop-opacity:1" />
|
||||
<stop offset="100%" style="stop-color:#1e40af;stop-opacity:1" />
|
||||
</linearGradient>
|
||||
<linearGradient id="grad3" x1="0%" y1="0%" x2="0%" y2="100%">
|
||||
<stop offset="0%" style="stop-color:#3b82f6;stop-opacity:1" />
|
||||
<stop offset="100%" style="stop-color:#2563eb;stop-opacity:1" />
|
||||
</linearGradient>
|
||||
<linearGradient id="grad4" x1="0%" y1="0%" x2="0%" y2="100%">
|
||||
<stop offset="0%" style="stop-color:#60a5fa;stop-opacity:1" />
|
||||
<stop offset="100%" style="stop-color:#3b82f6;stop-opacity:1" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
<div class="logo-icon bitcoin-coin">
|
||||
<div class="coin-face front">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 4091.27 4091.73" width="100%" height="100%">
|
||||
<path fill="#F7931A" d="M4030.06 2540.77c-273.24,1096.01 -1383.32,1763.02 -2479.46,1489.71 -1095.68,-273.24 -1762.69,-1383.39 -1489.33,-2479.31 273.12,-1096.13 1383.2,-1763.19 2479,-1489.95 1096.06,273.24 1763.03,1383.51 1489.76,2479.57l0.02 -0.02z"/>
|
||||
<path fill="white" d="M2947.77 1754.38c40.72,-272.26 -166.56,-418.61 -450,-516.24l91.95 -368.8 -224.5 -55.94 -89.51 359.09c-59.02,-14.72 -119.63,-28.59 -179.87,-42.34l90.16 -361.46 -224.36 -55.94 -92 368.68c-48.84,-11.12 -96.81,-22.11 -143.35,-33.69l0.26 -1.16 -309.59 -77.31 -59.72 239.78c0,0 166.56,38.18 163.05,40.53 90.91,22.69 107.35,82.87 104.62,130.57l-104.74 420.15c6.26,1.59 14.38,3.89 23.34,7.49 -7.49,-1.86 -15.46,-3.89 -23.73,-5.87l-146.81 588.57c-11.11,27.62 -39.31,69.07 -102.87,53.33 2.25,3.26 -163.17,-40.72 -163.17,-40.72l-111.46 256.98 292.15 72.83c54.35,13.63 107.61,27.89 160.06,41.3l-92.9 373.03 224.24 55.94 92 -369.07c61.26,16.63 120.71,31.97 178.91,46.43l-91.69 367.33 224.51 55.94 92.89 -372.33c382.82,72.45 670.67,43.24 791.83,-303.02 97.63,-278.78 -4.86,-439.58 -206.26,-544.44 146.69,-33.83 257.18,-130.31 286.64,-329.61l-0.07 -0.05zm-512.93 719.26c-69.38,278.78 -538.76,128.08 -690.94,90.29l123.28 -494.2c152.17,37.99 640.17,113.17 567.67,403.91zm69.43 -723.3c-63.29,253.58 -453.96,124.75 -580.69,93.16l111.77 -448.21c126.73,31.59 534.85,90.55 468.94,355.05l-0.02 0z"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="coin-face back">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 4091.27 4091.73" width="100%" height="100%" style="transform:scaleX(-1)">
|
||||
<path fill="#F7931A" d="M4030.06 2540.77c-273.24,1096.01 -1383.32,1763.02 -2479.46,1489.71 -1095.68,-273.24 -1762.69,-1383.39 -1489.33,-2479.31 273.12,-1096.13 1383.2,-1763.19 2479,-1489.95 1096.06,273.24 1763.03,1383.51 1489.76,2479.57l0.02 -0.02z"/>
|
||||
<path fill="white" d="M2947.77 1754.38c40.72,-272.26 -166.56,-418.61 -450,-516.24l91.95 -368.8 -224.5 -55.94 -89.51 359.09c-59.02,-14.72 -119.63,-28.59 -179.87,-42.34l90.16 -361.46 -224.36 -55.94 -92 368.68c-48.84,-11.12 -96.81,-22.11 -143.35,-33.69l0.26 -1.16 -309.59 -77.31 -59.72 239.78c0,0 166.56,38.18 163.05,40.53 90.91,22.69 107.35,82.87 104.62,130.57l-104.74 420.15c6.26,1.59 14.38,3.89 23.34,7.49 -7.49,-1.86 -15.46,-3.89 -23.73,-5.87l-146.81 588.57c-11.11,27.62 -39.31,69.07 -102.87,53.33 2.25,3.26 -163.17,-40.72 -163.17,-40.72l-111.46 256.98 292.15 72.83c54.35,13.63 107.61,27.89 160.06,41.3l-92.9 373.03 224.24 55.94 92 -369.07c61.26,16.63 120.71,31.97 178.91,46.43l-91.69 367.33 224.51 55.94 92.89 -372.33c382.82,72.45 670.67,43.24 791.83,-303.02 97.63,-278.78 -4.86,-439.58 -206.26,-544.44 146.69,-33.83 257.18,-130.31 286.64,-329.61l-0.07 -0.05zm-512.93 719.26c-69.38,278.78 -538.76,128.08 -690.94,90.29l123.28 -494.2c152.17,37.99 640.17,113.17 567.67,403.91zm69.43 -723.3c-63.29,253.58 -453.96,124.75 -580.69,93.16l111.77 -448.21c126.73,31.59 534.85,90.55 468.94,355.05l-0.02 0z"/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<h1 class="system-title">韬智生产管理系统</h1>
|
||||
</div>
|
||||
|
||||
@ -305,6 +305,9 @@ def init_db():
|
||||
|
||||
|
||||
def log(action, detail=''):
|
||||
# 过滤掉审计相关的操作,不记录到日志
|
||||
if action.startswith('audit_'):
|
||||
return
|
||||
try:
|
||||
conn = get_db()
|
||||
c = conn.cursor()
|
||||
@ -3702,7 +3705,7 @@ def create_customer_order():
|
||||
order_date = data.get('order_date', '').strip()
|
||||
order_no = data.get('order_no', '').strip()
|
||||
customer_name = data.get('customer_name', '').strip()
|
||||
material = data.get('material', '').strip()
|
||||
material = data.get('material', '').strip().replace(' ', '\n') # 空格转换行
|
||||
quantity = data.get('quantity', 0)
|
||||
unit_price = data.get('unit_price', 0)
|
||||
|
||||
@ -3748,7 +3751,7 @@ def update_customer_order(order_id):
|
||||
order_date = data.get('order_date', '').strip()
|
||||
order_no = data.get('order_no', '').strip()
|
||||
customer_name = data.get('customer_name', '').strip()
|
||||
material = data.get('material', '').strip()
|
||||
material = data.get('material', '').strip().replace(' ', '\n') # 空格转换行
|
||||
quantity = data.get('quantity')
|
||||
unit_price = data.get('unit_price')
|
||||
|
||||
@ -3777,13 +3780,13 @@ def update_customer_order(order_id):
|
||||
return jsonify({'error': '订单不存在'}), 404
|
||||
|
||||
# 更新订单
|
||||
now = get_beijing_time()
|
||||
c.execute('''UPDATE customer_orders
|
||||
SET order_date=?, order_no=?, customer_name=?, material=?,
|
||||
quantity=?, unit_price=?, updated_at=?, updated_by=?
|
||||
quantity=?, unit_price=?, updated_at=?
|
||||
WHERE id=?''',
|
||||
(order_date, order_no, customer_name, material, quantity, unit_price,
|
||||
datetime.now(timezone(timedelta(hours=8))).isoformat(),
|
||||
session.get('username', 'unknown'), order_id))
|
||||
now, order_id))
|
||||
|
||||
conn.commit()
|
||||
conn.close()
|
||||
@ -5276,6 +5279,89 @@ def not_found(e):
|
||||
return jsonify({'error': 'not found'}), 404
|
||||
|
||||
|
||||
# ==================== 操作日志管理 ====================
|
||||
|
||||
@app.get('/api/operations-log')
|
||||
@require_login
|
||||
@require_any_role('superadmin')
|
||||
def get_operations_log():
|
||||
"""获取操作日志列表"""
|
||||
page = request.args.get('page', 1, type=int)
|
||||
page_size = request.args.get('page_size', 50, type=int)
|
||||
keyword = request.args.get('keyword', '')
|
||||
|
||||
conn = get_db()
|
||||
c = conn.cursor()
|
||||
|
||||
# 构建查询 - 按操作详情搜索
|
||||
where_clause = ''
|
||||
params = []
|
||||
if keyword:
|
||||
where_clause = 'WHERE o.detail LIKE ?'
|
||||
params.append(f'%{keyword}%')
|
||||
|
||||
# 获取总数
|
||||
c.execute(f'SELECT COUNT(*) FROM operations_log o {where_clause}', params)
|
||||
total = c.fetchone()[0]
|
||||
|
||||
# 分页查询,关联用户表获取用户名
|
||||
offset = (page - 1) * page_size
|
||||
c.execute(f'''
|
||||
SELECT o.id, o.user_id, o.action, o.detail, o.ts, u.username
|
||||
FROM operations_log o
|
||||
LEFT JOIN users u ON o.user_id = u.id
|
||||
{where_clause}
|
||||
ORDER BY o.id DESC
|
||||
LIMIT ? OFFSET ?
|
||||
''', params + [page_size, offset])
|
||||
|
||||
rows = c.fetchall()
|
||||
conn.close()
|
||||
|
||||
logs = []
|
||||
for row in rows:
|
||||
logs.append({
|
||||
'id': row['id'],
|
||||
'user_id': row['user_id'],
|
||||
'username': row['username'] or '系统',
|
||||
'action': row['action'],
|
||||
'detail': row['detail'],
|
||||
'ts': row['ts']
|
||||
})
|
||||
|
||||
return jsonify({
|
||||
'list': logs,
|
||||
'total': total,
|
||||
'page': page,
|
||||
'page_size': page_size
|
||||
})
|
||||
|
||||
|
||||
@app.delete('/api/operations-log/cleanup')
|
||||
@require_login
|
||||
@require_any_role('superadmin')
|
||||
def cleanup_operations_log():
|
||||
"""清理3个月前的操作日志"""
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
# 计算3个月前的日期
|
||||
three_months_ago = (datetime.now() - timedelta(days=90)).isoformat()
|
||||
|
||||
conn = get_db()
|
||||
c = conn.cursor()
|
||||
|
||||
# 删除旧日志
|
||||
c.execute('DELETE FROM operations_log WHERE ts < ?', (three_months_ago,))
|
||||
deleted_count = c.rowcount
|
||||
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
log('cleanup_operations_log', f'清理了 {deleted_count} 条旧日志')
|
||||
|
||||
return jsonify({'ok': True, 'deleted': deleted_count, 'message': f'已清理 {deleted_count} 条旧日志'})
|
||||
|
||||
|
||||
init_db()
|
||||
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user