f103148ebf
基于 Flask + MySQL + Bootstrap 5 的全栈个人资料库管理系统。 主要功能: - 管理员/普通用户双角色权限体系,全站登录保护 - 资源管理:文本、图片、音频、视频四类资源 - 三种添加方式:本地上传(拖拽)、URL 后台下载、磁力下载(aria2c) - 在线预览:文本、图片、HTML5 音视频播放器 - 安全:bcrypt 加盐密码哈希、CSRF 防护、SQLAlchemy ORM 防注入 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
158 lines
5.9 KiB
HTML
158 lines
5.9 KiB
HTML
{% extends 'base.html' %}
|
|
{% block title %}用户管理{% endblock %}
|
|
|
|
{% block breadcrumb %}
|
|
<ol class="breadcrumb mb-0">
|
|
<li class="breadcrumb-item"><a href="{{ url_for('admin.dashboard') }}">控制台</a></li>
|
|
<li class="breadcrumb-item active">用户管理</li>
|
|
</ol>
|
|
{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
|
<h4 class="mb-0"><i class="bi bi-people me-2"></i>用户管理</h4>
|
|
<a href="{{ url_for('admin.user_create') }}" class="btn btn-primary">
|
|
<i class="bi bi-person-plus me-1"></i>新建用户
|
|
</a>
|
|
</div>
|
|
|
|
<!-- 搜索 -->
|
|
<form method="GET" class="mb-3">
|
|
<div class="input-group" style="max-width:400px">
|
|
<input type="text" name="q" value="{{ q }}" class="form-control"
|
|
placeholder="搜索用户名或邮箱">
|
|
<button class="btn btn-outline-secondary" type="submit">
|
|
<i class="bi bi-search"></i>
|
|
</button>
|
|
{% if q %}
|
|
<a href="{{ url_for('admin.users') }}" class="btn btn-outline-danger">
|
|
<i class="bi bi-x"></i>
|
|
</a>
|
|
{% endif %}
|
|
</div>
|
|
</form>
|
|
|
|
<div class="card shadow-sm">
|
|
<div class="table-responsive">
|
|
<table class="table table-hover mb-0">
|
|
<thead class="table-light">
|
|
<tr>
|
|
<th>ID</th><th>用户名</th><th>邮箱</th><th>角色</th>
|
|
<th>状态</th><th>注册时间</th><th>最后登录</th><th>操作</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for user in pagination.items %}
|
|
<tr class="{{ 'table-secondary' if not user.is_active else '' }}">
|
|
<td class="text-muted small">{{ user.id }}</td>
|
|
<td>
|
|
<div class="d-flex align-items-center gap-2">
|
|
<div class="avatar-circle avatar-sm">{{ user.username[0].upper() }}</div>
|
|
{{ user.username }}
|
|
{% if user.id == current_user.id %}
|
|
<span class="badge bg-info text-dark small">我</span>
|
|
{% endif %}
|
|
</div>
|
|
</td>
|
|
<td class="text-muted small">{{ user.email }}</td>
|
|
<td>
|
|
<span class="badge bg-{{ 'danger' if user.is_admin else 'secondary' }}">
|
|
{{ '管理员' if user.is_admin else '普通用户' }}
|
|
</span>
|
|
</td>
|
|
<td>
|
|
<span class="badge bg-{{ 'success' if user.is_active else 'warning text-dark' }}">
|
|
{{ '正常' if user.is_active else '已禁用' }}
|
|
</span>
|
|
</td>
|
|
<td class="text-muted small">{{ user.created_at | datetime_fmt }}</td>
|
|
<td class="text-muted small">{{ user.last_login | datetime_fmt if user.last_login else '—' }}</td>
|
|
<td>
|
|
<div class="btn-group btn-group-sm">
|
|
<a href="{{ url_for('admin.user_edit', user_id=user.id) }}"
|
|
class="btn btn-outline-primary"><i class="bi bi-pencil"></i></a>
|
|
{% if user.id != current_user.id %}
|
|
<form action="{{ url_for('admin.user_toggle', user_id=user.id) }}"
|
|
method="POST" class="d-inline">
|
|
{{ csrf_token_input() }}
|
|
<button class="btn btn-outline-{{ 'warning' if user.is_active else 'success' }}"
|
|
title="{{ '禁用' if user.is_active else '启用' }}">
|
|
<i class="bi bi-{{ 'slash-circle' if user.is_active else 'check-circle' }}"></i>
|
|
</button>
|
|
</form>
|
|
<button class="btn btn-outline-danger"
|
|
onclick="confirmDelete({{ user.id }}, '{{ user.username }}')"
|
|
title="删除">
|
|
<i class="bi bi-trash"></i>
|
|
</button>
|
|
{% endif %}
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
{% else %}
|
|
<tr><td colspan="8" class="text-center text-muted py-4">暂无用户</td></tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
{% if pagination.pages > 1 %}
|
|
<div class="card-footer d-flex justify-content-center">
|
|
<nav>
|
|
<ul class="pagination pagination-sm mb-0">
|
|
{% if pagination.has_prev %}
|
|
<li class="page-item">
|
|
<a class="page-link" href="?page={{ pagination.prev_num }}&q={{ q }}">«</a>
|
|
</li>
|
|
{% endif %}
|
|
{% for p in pagination.iter_pages() %}
|
|
{% if p %}
|
|
<li class="page-item {{ 'active' if p == pagination.page else '' }}">
|
|
<a class="page-link" href="?page={{ p }}&q={{ q }}">{{ p }}</a>
|
|
</li>
|
|
{% else %}
|
|
<li class="page-item disabled"><span class="page-link">…</span></li>
|
|
{% endif %}
|
|
{% endfor %}
|
|
{% if pagination.has_next %}
|
|
<li class="page-item">
|
|
<a class="page-link" href="?page={{ pagination.next_num }}&q={{ q }}">»</a>
|
|
</li>
|
|
{% endif %}
|
|
</ul>
|
|
</nav>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
|
|
<!-- 删除确认模态框 -->
|
|
<div class="modal fade" id="deleteModal" tabindex="-1">
|
|
<div class="modal-dialog modal-sm">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h6 class="modal-title text-danger"><i class="bi bi-exclamation-triangle me-1"></i>确认删除</h6>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
|
</div>
|
|
<div class="modal-body" id="deleteModalBody"></div>
|
|
<div class="modal-footer">
|
|
<button class="btn btn-secondary btn-sm" data-bs-dismiss="modal">取消</button>
|
|
<form id="deleteForm" method="POST">
|
|
{{ csrf_token_input() }}
|
|
<button type="submit" class="btn btn-danger btn-sm">确认删除</button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endblock %}
|
|
|
|
{% block scripts %}
|
|
<script>
|
|
function confirmDelete(id, name) {
|
|
document.getElementById('deleteModalBody').textContent =
|
|
`确定要删除用户「${name}」及其所有资源吗?此操作不可恢复。`;
|
|
document.getElementById('deleteForm').action = `/admin/users/${id}/delete`;
|
|
new bootstrap.Modal(document.getElementById('deleteModal')).show();
|
|
}
|
|
</script>
|
|
{% endblock %}
|