feat: 用户登录/管理 + 操作日志模块

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

View File

@@ -0,0 +1,112 @@
{% extends "base.html" %}
{% block title %}用户管理 - EDC 工装管理系统{% endblock %}
{% block content %}
<h2>用户管理</h2>
<div style="margin-bottom:16px;">
<button onclick="showAddForm()" class="btn-search">新增用户</button>
<div id="add-form" style="display:none;margin-top:12px;background:#fff;padding:16px;border-radius:8px;">
<label>用户名:<input type="text" id="new-username" style="margin:0 8px;"></label>
<label>密码:<input type="password" id="new-password" style="margin:0 8px;"></label>
<label>角色:
<select id="new-role" style="margin:0 8px;">
<option value="operator">operator</option>
<option value="admin">admin</option>
</select>
</label>
<button onclick="addUser()" class="btn-search">确认</button>
<button onclick="document.getElementById('add-form').style.display='none'" class="btn-export">取消</button>
</div>
</div>
<table>
<thead>
<tr>
<th>ID</th>
<th>用户名</th>
<th>角色</th>
<th>状态</th>
<th>创建时间</th>
<th>操作</th>
</tr>
</thead>
<tbody id="user-tbody"></tbody>
</table>
{% endblock %}
{% block scripts %}
<script>
async function loadUsers() {
const resp = await fetch("/users/api/users");
const users = await resp.json();
const tbody = document.getElementById("user-tbody");
tbody.innerHTML = users.map(u => `
<tr>
<td>${u.id}</td>
<td>${u.username}</td>
<td>${u.role}</td>
<td>${u.is_active ? '启用' : '禁用'}</td>
<td>${u.create_time || '-'}</td>
<td>
<select onchange="updateUser(${u.id}, this, 'role')" data-field="role">
<option value="operator" ${u.role==='operator'?'selected':''}>operator</option>
<option value="admin" ${u.role==='admin'?'selected':''}>admin</option>
</select>
<select onchange="updateUser(${u.id}, this, 'is_active')" data-field="is_active">
<option value="1" ${u.is_active?'selected':''}>启用</option>
<option value="0" ${!u.is_active?'selected':''}>禁用</option>
</select>
<button onclick="resetPwd(${u.id})" class="btn-search" style="font-size:11px;">改密</button>
</td>
</tr>
`).join("");
}
function showAddForm() {
document.getElementById("add-form").style.display = "block";
document.getElementById("new-username").value = "";
document.getElementById("new-password").value = "";
}
async function addUser() {
const username = document.getElementById("new-username").value.trim();
const password = document.getElementById("new-password").value;
const role = document.getElementById("new-role").value;
if (!username || !password) { alert("用户名和密码不能为空"); return; }
const resp = await fetch("/users/api/users", {
method: "POST",
headers: {"Content-Type": "application/json"},
body: JSON.stringify({username, password, role}),
});
const data = await resp.json();
if (data.ok) { document.getElementById("add-form").style.display = "none"; loadUsers(); }
else { alert(data.error || "创建失败"); }
}
async function updateUser(id, el, field) {
const value = el.value;
const body = {};
body[field] = field === 'is_active' ? parseInt(value) : value;
await fetch(`/users/api/users/${id}`, {
method: "PUT",
headers: {"Content-Type": "application/json"},
body: JSON.stringify(body),
});
loadUsers();
}
async function resetPwd(id) {
const pwd = prompt("输入新密码至少6位");
if (!pwd || pwd.length < 6) { alert("密码至少6位"); return; }
await fetch(`/users/api/users/${id}`, {
method: "PUT",
headers: {"Content-Type": "application/json"},
body: JSON.stringify({password: pwd}),
});
alert("密码已更新");
}
loadUsers();
</script>
{% endblock %}