Files
ns2.0/templates/user_management.html

363 lines
16 KiB
HTML
Executable File
Raw Permalink 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.

{% extends "base.html" %}
{% block content %}
{% include 'admin_nav.html' %}
<div class="admin-container">
<div class="admin-content">
<div class="admin-card">
<div class="card-header">
<div class="header-left">
<i class="fas fa-user-plus"></i>
<h3>添加管理员</h3>
</div>
</div>
<form onsubmit="submitAddAdminForm(event, this)" class="admin-form">
<div class="form-group">
<label for="username">用户名</label>
<input type="text" id="username" name="username" placeholder="请输入用户名" required>
</div>
<div class="form-group">
<label for="password">密码</label>
<div class="password-input-wrapper">
<input type="password" id="password" name="password" placeholder="请输入密码" required>
<button type="button" class="toggle-password" onclick="togglePassword('password')">
<i class="fas fa-eye"></i>
</button>
</div>
</div>
<div class="form-group">
<label for="confirm_password">确认密码</label>
<div class="password-input-wrapper">
<input type="password" id="confirm_password" name="confirm_password" placeholder="请再次输入密码" required>
<button type="button" class="toggle-password" onclick="togglePassword('confirm_password')">
<i class="fas fa-eye"></i>
</button>
</div>
</div>
<button type="submit" class="btn-primary">
<i class="fas fa-plus"></i> 添加管理员
</button>
</form>
</div>
<div class="admin-card full-width">
<div class="card-header">
<div class="header-left">
<i class="fas fa-users"></i>
<h3>管理员列表</h3>
</div>
<form class="search-form header-search" method="GET">
<div class="search-wrapper">
<i class="fas fa-search"></i>
<input type="text" name="search" placeholder="搜索管理员..." value="{{ request.args.get('search', '') }}">
</div>
</form>
</div>
<div class="table-responsive">
<table class="admin-table">
<thead>
<tr>
<th>用户名</th>
<th>角色</th>
<th>创建时间</th>
<th>最后登录</th>
<th>登录次数</th>
<th>最后操作</th>
<th>操作</th>
</tr>
</thead>
<tbody>
{% for admin in admins %}
<tr>
<td>{{ admin.username }}</td>
<td>{{ '超级管理员' if admin.is_superadmin else '管理员' }}</td>
<td>{{ admin.created_at }}</td>
<td>{{ (admin.last_login|default('从未登录', true))|datetime_format }}</td>
<td>{{ admin.login_count }}</td>
<td>{{ admin.last_action or '无' }}</td>
<td>
<div class="action-buttons">
{% if not admin.is_superadmin %}
<a href="#" class="btn-view" onclick="showLogs('{{ admin.id }}'); return false;">
<i class="fas fa-history"></i>
</a>
<a href="{{ url_for('reset_admin_password', admin_id=admin.id) }}"
class="btn-edit"
onclick="return confirm('确定要重置该管理员的密码吗?')">
<i class="fas fa-key"></i>
</a>
<a href="#" class="btn-delete" onclick="deleteAdmin('{{ admin.id }}'); return false;">
<i class="fas fa-trash"></i>
</a>
{% endif %}
</div>
</td>
</tr>
<!-- 管理员日志详情 -->
<tr id="logs-{{ admin.id }}" style="display: none;">
<td colspan="8">
<div class="admin-logs">
<h4>最近操作记录</h4>
<table class="logs-table">
<thead>
<tr>
<th>时间</th>
<th>操作</th>
<th>IP地址</th>
<th>浏览器信息</th>
</tr>
</thead>
<tbody>
{% for log in admin_logs[admin.id] %}
<tr>
<td>{{ log.created_at|datetime_format }}</td>
<td>{{ log.action }}</td>
<td>{{ log.ip_address }}</td>
<td>
{% if 'Mobile' in log.user_agent %}
{% if 'Android' in log.user_agent %}
{% if 'MI' in log.user_agent or 'XiaoMi' in log.user_agent or 'Redmi' in log.user_agent %}
小米{{ log.user_agent.split('MI ')[1].split(' ')[0] if 'MI ' in log.user_agent else log.user_agent.split('Redmi ')[1].split(' ')[0] if 'Redmi ' in log.user_agent else '' }}
{% elif 'HUAWEI' in log.user_agent %}
华为{{ log.user_agent.split('HUAWEI ')[1].split(' ')[0] if 'HUAWEI ' in log.user_agent else '' }}
{% elif 'HONOR' in log.user_agent %}
荣耀{{ log.user_agent.split('HONOR ')[1].split(' ')[0] if 'HONOR ' in log.user_agent else '' }}
{% elif 'OPPO' in log.user_agent %}
OPPO{{ log.user_agent.split('OPPO ')[1].split(' ')[0] if 'OPPO ' in log.user_agent else '' }}
{% elif 'vivo' in log.user_agent %}
vivo{{ log.user_agent.split('vivo ')[1].split(' ')[0] if 'vivo ' in log.user_agent else '' }}
{% elif 'SAMSUNG' in log.user_agent %}
三星{{ log.user_agent.split('SAMSUNG ')[1].split(' ')[0] if 'SAMSUNG ' in log.user_agent else '' }}
{% elif 'Build/' in log.user_agent %}
{% set model = log.user_agent.split('Build/')[0].split(';')[-1].strip() %}
{{ model if model else 'HarmonyOS NEXT' }}
{% else %}
HarmonyOS NEXT
{% endif %}
{% elif 'iPhone' in log.user_agent %}
iPhone{{ log.user_agent.split('iPhone ')[1].split(' ')[0] if 'iPhone ' in log.user_agent else '' }}
{% elif 'iPad' in log.user_agent %}
iPad{{ log.user_agent.split('iPad ')[1].split(' ')[0] if 'iPad ' in log.user_agent else '' }}
{% else %}
HarmonyOS NEXT
{% endif %}
{% else %}
{% if 'Windows' in log.user_agent %}
Windows{% if 'Win64' in log.user_agent %} 64位{% endif %}
{% elif 'Macintosh' in log.user_agent %}
macOS
{% elif 'Linux' in log.user_agent %}
Linux{% if 'Ubuntu' in log.user_agent %} (Ubuntu){% elif 'Fedora' in log.user_agent %} (Fedora){% elif 'Debian' in log.user_agent %} (Debian){% endif %}
{% else %}
HarmonyOS NEXT
{% endif %}
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
</div>
<style>
.password-input-wrapper {
position: relative;
display: flex;
align-items: center;
width: 100%;
}
.password-input-wrapper input {
width: 100%;
padding: 8px 40px 8px 12px;
border: 1px solid #d2d2d7;
border-radius: 6px;
font-size: 14px;
transition: all 0.3s ease;
}
.toggle-password {
position: absolute;
right: 12px;
top: 50%;
transform: translateY(-50%);
background: none;
border: none;
color: #666;
cursor: pointer;
padding: 0;
display: flex;
align-items: center;
justify-content: center;
width: 24px;
height: 24px;
z-index: 2;
}
.toggle-password:hover {
color: #333;
}
.toggle-password:focus {
outline: none;
}
.password-input-wrapper input:hover {
border-color: #b2b2b2;
}
.password-input-wrapper input:focus {
border-color: #0066cc;
outline: none;
}
</style>
<script>
// 添加通知显示函数
function showNotification(message, type = 'success') {
const notification = document.createElement('div');
notification.className = `notification ${type}`;
notification.textContent = message;
notification.style.position = 'fixed';
notification.style.bottom = '20px';
notification.style.right = '20px';
notification.style.padding = '10px 20px';
notification.style.borderRadius = '4px';
notification.style.backgroundColor = type === 'success' ? '#4CAF50' : '#f44336';
notification.style.color = 'white';
notification.style.zIndex = '1000';
notification.style.animation = 'fadeInOut 3s forwards';
document.body.appendChild(notification);
setTimeout(() => {
notification.remove();
}, 3000);
}
// 修改添加管理员函数
function submitAddAdminForm(event, form) {
event.preventDefault();
const formData = new FormData(form);
fetch('{{ url_for("add_admin") }}', {
method: 'POST',
body: formData
})
.then(response => response.json())
.then(data => {
if (data.success) {
showNotification(data.message, 'success');
form.reset();
// 刷新页面以显示新管理员
location.reload();
} else {
showNotification(data.error, 'error');
}
})
.catch(error => {
showNotification('操作失败,请重试', 'error');
console.error('Error:', error);
});
}
// 修改删除管理员函数
function deleteAdmin(adminId) {
if (!confirm('确定要删除这个管理员吗?')) return;
fetch(`/delete_admin/${adminId}`, {
method: 'POST'
})
.then(response => response.json())
.then(data => {
if (data.success) {
showNotification(data.message, 'success');
// 使用更可靠的选择器
const adminRows = document.querySelectorAll('tr');
adminRows.forEach(row => {
// 检查行中是否包含带有特定 onclick 属性的删除按钮
const deleteButton = row.querySelector(`a[onclick*="deleteAdmin(${adminId})"]`);
if (deleteButton) {
row.remove();
// 同时删除对应的日志行
const logsRow = document.getElementById(`logs-${adminId}`);
if (logsRow) {
logsRow.remove();
}
}
});
} else {
showNotification(data.error, 'error');
}
})
.catch(error => {
showNotification('删除失败,请重试', 'error');
console.error('Error:', error);
});
}
// 修改日志查看函数
function showLogs(adminId) {
const logsRow = document.getElementById(`logs-${adminId}`);
if (logsRow.style.display === 'none') {
logsRow.style.display = 'table-row';
// 平滑滚动到日志行
const rect = logsRow.getBoundingClientRect();
const absoluteTop = window.pageYOffset + rect.top - 100; // 减去100px的偏移量让视图更好
window.scrollTo({
top: absoluteTop,
behavior: 'smooth'
});
} else {
logsRow.style.display = 'none';
}
}
// 添加密码显示/隐藏功能
function togglePassword(inputId) {
const input = document.getElementById(inputId);
const button = input.nextElementSibling;
const icon = button.querySelector('i');
if (input.type === 'password') {
input.type = 'text';
icon.className = 'fas fa-eye-slash';
} else {
input.type = 'password';
icon.className = 'fas fa-eye';
}
}
// 添加日期格式化过滤器
function formatDateTime(dateStr) {
if (!dateStr || dateStr === '从未登录') return dateStr;
const date = new Date(dateStr);
return date.toLocaleString('zh-CN', { timeZone: 'Asia/Shanghai' });
}
// 添加动画样式
const style = document.createElement('style');
style.textContent = `
@keyframes fadeInOut {
0% { opacity: 0; transform: translateY(20px); }
10% { opacity: 1; transform: translateY(0); }
90% { opacity: 1; transform: translateY(0); }
100% { opacity: 0; transform: translateY(-20px); }
}
`;
document.head.appendChild(style);
</script>
{% endblock %}