Files
ai-app-database/app/templates/admin/users.html
T
huty f103148ebf feat: 初始化个人资料库 Web 应用
基于 Flask + MySQL + Bootstrap 5 的全栈个人资料库管理系统。

主要功能:
- 管理员/普通用户双角色权限体系,全站登录保护
- 资源管理:文本、图片、音频、视频四类资源
- 三种添加方式:本地上传(拖拽)、URL 后台下载、磁力下载(aria2c)
- 在线预览:文本、图片、HTML5 音视频播放器
- 安全:bcrypt 加盐密码哈希、CSRF 防护、SQLAlchemy ORM 防注入

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-23 00:16:59 +09:00

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 %}