Files
wangfq 322563dab0 feat: 用户登录/管理 + 操作日志模块
- tb_user 用户表、tb_log 日志表
- Flask-Login 认证(login/logout/权限装饰器)
- 用户管理页(admin 专有):增删改查、改密、角色设置
- 操作日志页:分页查询、按用户/类型筛选
- 测试操作区指令自动记录日志
- 所有页面加 @login_required 保护
- 默认管理员 admin/admin123(首次启动自动创建)
2026-05-28 13:58:19 +08:00

93 lines
3.0 KiB
HTML

{% extends "base.html" %}
{% block title %}操作日志 - EDC 工装管理系统{% endblock %}
{% block content %}
<h2>操作日志</h2>
<div class="search-bar">
<label>用户名:<input type="text" id="search-username" placeholder="筛选用户..."></label>
<label>操作类型:
<select id="search-action">
<option value="">全部</option>
<option value="login">登录</option>
<option value="logout">登出</option>
<option value="command">指令操作</option>
</select>
</label>
<button onclick="searchLogs(1)" class="btn-search">查询</button>
</div>
<table>
<thead>
<tr>
<th>ID</th>
<th>用户</th>
<th>操作类型</th>
<th>对象</th>
<th>详情</th>
<th>结果</th>
<th>IP</th>
<th>时间</th>
</tr>
</thead>
<tbody id="log-tbody"></tbody>
</table>
<div class="pagination" id="pagination"></div>
{% endblock %}
{% block scripts %}
<script>
let currentPage = 1, totalPages = 1;
async function searchLogs(page = 1) {
currentPage = page;
const username = document.getElementById("search-username").value;
const action_type = document.getElementById("search-action").value;
const params = new URLSearchParams({page, per_page: 30});
if (username) params.set("username", username);
if (action_type) params.set("action_type", action_type);
const resp = await fetch(`/logs/api/logs?${params}`);
const data = await resp.json();
renderTable(data.records);
totalPages = data.pages;
renderPagination();
}
function renderTable(records) {
const tbody = document.getElementById("log-tbody");
if (!records.length) {
tbody.innerHTML = '<tr><td colspan="8" style="text-align:center;color:#999;">暂无记录</td></tr>';
return;
}
tbody.innerHTML = records.map(r => `
<tr>
<td>${r.id}</td>
<td>${r.username || '-'}</td>
<td>${r.action_type}</td>
<td>${r.target || '-'}</td>
<td style="max-width:250px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;" title="${esc(r.detail)}">${r.detail || '-'}</td>
<td style="color:${r.result==='ok'?'#27ae60':'#e74c3c'}">${r.result}</td>
<td>${r.ip || '-'}</td>
<td>${r.create_time || '-'}</td>
</tr>
`).join("");
}
function renderPagination() {
const div = document.getElementById("pagination");
let html = `<button onclick="searchLogs(${currentPage-1})" ${currentPage<=1?'disabled':''}>上一页</button>`;
for (let i = 1; i <= totalPages; i++) {
html += `<button onclick="searchLogs(${i})" class="${i===currentPage?'active':''}">${i}</button>`;
}
html += `<button onclick="searchLogs(${currentPage+1})" ${currentPage>=totalPages?'disabled':''}>下一页</button>`;
div.innerHTML = html;
}
function esc(s) { return s.replace(/"/g, '&quot;'); }
searchLogs(1);
</script>
{% endblock %}