初始化鸿蒙应用展示平台项目 - 前后端分离架构

This commit is contained in:
Nvex
2025-10-25 11:45:17 +08:00
commit c0f81dbbe2
92 changed files with 40210 additions and 0 deletions

363
templates/user_management.html Executable file
View File

@@ -0,0 +1,363 @@
{% 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 %}