375 lines
16 KiB
HTML
375 lines
16 KiB
HTML
{% extends "base.html" %}
|
||
{% block title %}用户管理{% endblock %}
|
||
|
||
{% block content %}
|
||
<div class="container">
|
||
<!-- 标题和搜索框 -->
|
||
<div class="row mb-3">
|
||
<div class="col-md-6">
|
||
<h1 class="my-4">用户列表</h1>
|
||
</div>
|
||
<div class="col-md-6">
|
||
<div class="input-group mt-4">
|
||
<select class="form-select" id="queryMode">
|
||
<option value="uid">UID</option>
|
||
<option value="phone_number">手机号</option>
|
||
<option value="username">用户名</option>
|
||
<option value="email">邮箱</option>
|
||
</select>
|
||
<input type="text" class="form-control" id="queryValue" placeholder="搜索用户...">
|
||
<button class="btn btn-primary" type="button" onclick="performSearch()">搜索</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<p>当前用户总数:{{ total_users }}</p>
|
||
<table class="table table-striped table-bordered">
|
||
<thead class="thead-light">
|
||
<tr>
|
||
<th>用户ID</th>
|
||
<th>用户名</th>
|
||
<th>积分</th>
|
||
<th>性别</th>
|
||
<th>手机号</th>
|
||
<th>邮箱</th>
|
||
<th>用户类型</th>
|
||
<th>创建时间</th>
|
||
<th>更新时间</th>
|
||
<th>操作</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="userTableBody">
|
||
{% for user in users %}
|
||
<tr>
|
||
<td>{{ user.user_id }}</td>
|
||
<td>{{ user.username }}</td>
|
||
<td>{{ user.points }}</td>
|
||
<td>{{ user.gender or '' }}</td>
|
||
<td>{{ user.phone_number or '' }}</td>
|
||
<td>{{ user.email or '' }}</td>
|
||
<td>{{ user.user_type }}</td>
|
||
<td>{{ user.created_at }}</td>
|
||
<td>{{ user.updated_at }}</td>
|
||
<td>
|
||
<button type="button" class="btn btn-info btn-sm"
|
||
onclick="openPointsModal('{{ user.user_id }}')">
|
||
调整积分
|
||
</button>
|
||
<!-- 更新按钮 -->
|
||
<button type="button" class="btn btn-primary btn-sm"
|
||
onclick="openUpdateModal('{{ user.user_id }}', '{{ user.username }}', '{{ user.email }}', '{{ user.phone_number or '' }}', '{{ user.gender or '' }}', '{{ user.user_type }}')">
|
||
更新
|
||
</button>
|
||
<!-- 重置密码按钮 -->
|
||
<button type="button" class="btn btn-warning btn-sm"
|
||
onclick="openResetPasswordModal('{{ user.user_id }}')">
|
||
重置密码
|
||
</button>
|
||
<button type="button" class="btn btn-success btn-sm"
|
||
onclick="openIssueCouponModal('{{ user.user_id }}')">
|
||
发放优惠券
|
||
</button>
|
||
<!-- 删除按钮 -->
|
||
<form action="{{ url_for('users.delete_user', page=page) }}" method="post" style="display:inline;">
|
||
<input type="hidden" name="uid" value="{{ user.user_id }}">
|
||
<button type="submit" class="btn btn-danger btn-sm" onclick="return confirm('确定删除该用户吗?');">
|
||
删除
|
||
</button>
|
||
</form>
|
||
</td>
|
||
</tr>
|
||
{% endfor %}
|
||
</tbody>
|
||
</table>
|
||
<!-- 分页导航 -->
|
||
<nav aria-label="Page navigation">
|
||
<ul class="pagination">
|
||
{% if page > 1 %}
|
||
<li class="page-item">
|
||
<a class="page-link" href="{{ url_for('users.list_users', page=page - 1) }}">上一页</a>
|
||
</li>
|
||
{% else %}
|
||
<li class="page-item disabled">
|
||
<span class="page-link">上一页</span>
|
||
</li>
|
||
{% endif %}
|
||
|
||
{% for p in range(1, total_pages + 1) %}
|
||
<li class="page-item {% if p == page %}active{% endif %}">
|
||
<a class="page-link" href="{{ url_for('users.list_users', page=p) }}">{{ p }}</a>
|
||
</li>
|
||
{% endfor %}
|
||
|
||
{% if page < total_pages %}
|
||
<li class="page-item">
|
||
<a class="page-link" href="{{ url_for('users.list_users', page=page + 1) }}">下一页</a>
|
||
</li>
|
||
{% else %}
|
||
<li class="page-item disabled">
|
||
<span class="page-link">下一页</span>
|
||
</li>
|
||
{% endif %}
|
||
</ul>
|
||
</nav>
|
||
</div>
|
||
|
||
<!-- 更新用户模态框 -->
|
||
<div class="modal fade" id="updateUserModal" tabindex="-1" role="dialog"
|
||
aria-labelledby="updateUserModalLabel" aria-hidden="true">
|
||
<div class="modal-dialog" role="document">
|
||
<div class="modal-content">
|
||
<form action="{{ url_for('users.update_user', page=page) }}" method="post">
|
||
<div class="modal-header">
|
||
<h5 class="modal-title" id="updateUserModalLabel">更新用户信息</h5>
|
||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||
<span aria-hidden="true">×</span>
|
||
</button>
|
||
</div>
|
||
<div class="modal-body">
|
||
<!-- uid, username, email, phone_number 保持原样 -->
|
||
<input type="hidden" id="update-uid" name="uid">
|
||
|
||
<div class="form-group">
|
||
<label for="update-username">用户名</label>
|
||
<input type="text" class="form-control" id="update-username" name="username">
|
||
</div>
|
||
<div class="form-group">
|
||
<label for="update-email">邮箱</label>
|
||
<input type="email" class="form-control" id="update-email" name="email">
|
||
</div>
|
||
<div class="form-group">
|
||
<label for="update-phone_number">手机号</label>
|
||
<input type="text" class="form-control" id="update-phone_number" name="phone_number">
|
||
</div>
|
||
|
||
<!-- 性别下拉:male, female, other -->
|
||
<div class="form-group">
|
||
<label for="update-gender">性别</label>
|
||
<select class="form-control" id="update-gender" name="gender">
|
||
<option value="">请选择</option>
|
||
<option value="male">male</option>
|
||
<option value="female">female</option>
|
||
<option value="other">other</option>
|
||
</select>
|
||
</div>
|
||
|
||
<!-- 用户类型改为下拉选单:player, admin -->
|
||
<div class="form-group">
|
||
<label for="update-user_type">用户类型</label>
|
||
<select class="form-control" id="update-user_type" name="user_type">
|
||
<option value="">请选择</option>
|
||
<option value="player">player</option>
|
||
<option value="admin">admin</option>
|
||
</select>
|
||
</div>
|
||
</div> <!-- end modal-body -->
|
||
|
||
<div class="modal-footer">
|
||
<button type="button" class="btn btn-secondary" data-dismiss="modal">关闭</button>
|
||
<button type="submit" class="btn btn-primary">保存更改</button>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 重置密码模态框 -->
|
||
<div class="modal fade" id="resetPasswordModal" tabindex="-1" role="dialog"
|
||
aria-labelledby="resetPasswordModalLabel" aria-hidden="true">
|
||
<div class="modal-dialog" role="document">
|
||
<div class="modal-content">
|
||
<!-- 注意:此处提交的路由 'users.reset_password' 只是一个占位,目前没有实际的重置密码逻辑 -->
|
||
<form action="{{ url_for('users.reset_password', page=page) }}" method="post">
|
||
<div class="modal-header">
|
||
<h5 class="modal-title" id="resetPasswordModalLabel">重置密码</h5>
|
||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||
<span aria-hidden="true">×</span>
|
||
</button>
|
||
</div>
|
||
<div class="modal-body">
|
||
<input type="hidden" id="reset-uid" name="uid">
|
||
<div class="form-group">
|
||
<label for="reset-password">新密码</label>
|
||
<input type="password" class="form-control" id="reset-password" name="password">
|
||
</div>
|
||
</div>
|
||
<div class="modal-footer">
|
||
<button type="button" class="btn btn-secondary" data-dismiss="modal">关闭</button>
|
||
<button type="submit" class="btn btn-primary">重置密码</button>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 添加积分调整模态框 -->
|
||
<div class="modal fade" id="pointsModal">
|
||
<div class="modal-dialog">
|
||
<div class="modal-content">
|
||
<form action="{{ url_for('users.adjust_points') }}" method="post">
|
||
<input type="hidden" name="uid" id="points-uid">
|
||
<div class="modal-body">
|
||
<div class="form-group">
|
||
<label>调整数值(正数为增加,负数为扣除)</label>
|
||
<input type="number" name="points" class="form-control" required>
|
||
</div>
|
||
<div class="form-group">
|
||
<label>调整原因</label>
|
||
<input type="text" name="reason" class="form-control" required value="管理员后台更改">
|
||
</div>
|
||
</div>
|
||
<div class="modal-footer">
|
||
<button type="submit" class="btn btn-primary">提交</button>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
|
||
<div class="modal fade" id="issueCouponModal">
|
||
<div class="modal-dialog">
|
||
<div class="modal-content">
|
||
<form method="post" action="{{ url_for('users.issue_coupon') }}">
|
||
<input type="hidden" name="user_id" id="issue-user-id">
|
||
<div class="modal-header">
|
||
<h5 class="modal-title">发放优惠券</h5>
|
||
</div>
|
||
<div class="modal-body">
|
||
<div class="form-group">
|
||
<label>选择优惠券</label>
|
||
<select class="form-control" name="coupon_id" required>
|
||
<option value="">请选择优惠券</option>
|
||
{% for coupon in coupons %}
|
||
<option value="{{ coupon.coupon_id }}">{{ coupon.coupon_code }} - {{ coupon.discount_type }}</option>
|
||
{% endfor %}
|
||
</select>
|
||
</div>
|
||
</div>
|
||
<div class="modal-footer">
|
||
<button type="button" class="btn btn-secondary" data-dismiss="modal">取消</button>
|
||
<button type="submit" class="btn btn-primary">发放</button>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
|
||
<script>
|
||
function performSearch() {
|
||
const queryMode = document.getElementById("queryMode");
|
||
const queryValue = document.getElementById("queryValue").value.trim();
|
||
const tableBody = document.getElementById("userTableBody");
|
||
|
||
// 若搜索框为空,重新加载当前页面,等同于清空搜索
|
||
if (!queryValue) {
|
||
location.reload();
|
||
return;
|
||
}
|
||
|
||
fetch("/users/search", {
|
||
method: "POST",
|
||
headers: {
|
||
"Content-Type": "application/json"
|
||
},
|
||
body: JSON.stringify({
|
||
query_mode: queryMode.value,
|
||
query_value: queryValue
|
||
})
|
||
})
|
||
.then(response => {
|
||
if (!response.ok) {
|
||
throw new Error(`HTTP error! Status: ${response.status}`);
|
||
}
|
||
return response.json();
|
||
})
|
||
.then(data => {
|
||
tableBody.innerHTML = "";
|
||
if (data.length === 0) {
|
||
tableBody.innerHTML =
|
||
`<tr><td colspan="9" class="text-center">未找到匹配的用户</td></tr>`;
|
||
return;
|
||
}
|
||
data.forEach(user => {
|
||
let row = `<tr>
|
||
<td>${user.user_id}</td>
|
||
<td>${user.username}</td>
|
||
<td>${user.points}</td>
|
||
<td>${user.gender || ''}</td>
|
||
<td>${user.phone_number || ''}</td>
|
||
<td>${user.email || ''}</td>
|
||
<td>${user.user_type}</td>
|
||
<td>${user.created_at}</td>
|
||
<td>${user.updated_at}</td>
|
||
<td>
|
||
<button type="button" class="btn btn-info btn-sm"
|
||
onclick="openPointsModal('${user.user_id}')">
|
||
调整积分
|
||
</button>
|
||
<button type="button" class="btn btn-primary btn-sm"
|
||
onclick="openUpdateModal('${user.user_id}', '${user.username}', '${user.email}', '${user.phone_number || ''}', '${user.gender || ''}', '${user.user_type}')">
|
||
更新
|
||
</button>
|
||
<button type="button" class="btn btn-warning btn-sm"
|
||
onclick="openResetPasswordModal('${user.user_id}')">
|
||
重置密码
|
||
</button>
|
||
<form action="{{ url_for('users.delete_user', page=page) }}" method="post" style="display:inline;">
|
||
<input type="hidden" name="uid" value="${user.user_id}">
|
||
<button type="submit" class="btn btn-danger btn-sm"
|
||
onclick="return confirm('确定删除该用户吗?');">
|
||
删除
|
||
</button>
|
||
</form>
|
||
</td>
|
||
</tr>`;
|
||
tableBody.innerHTML += row;
|
||
});
|
||
})
|
||
.catch(error => console.error("搜索用户失败:", error));
|
||
}
|
||
|
||
function openUpdateModal(uid, username, email, phone_number, gender, user_type) {
|
||
document.getElementById('update-uid').value = uid;
|
||
document.getElementById('update-username').value = username;
|
||
document.getElementById('update-email').value = email;
|
||
document.getElementById('update-phone_number').value = phone_number;
|
||
|
||
// 下拉选单初始化
|
||
// gender 可能是 "male", "female" 或 "other";若为空则使用 ""
|
||
let genderSelect = document.getElementById('update-gender');
|
||
genderSelect.value = gender || "";
|
||
|
||
// user_type 可能是 "player", "admin";若为空则使用 ""
|
||
let userTypeSelect = document.getElementById('update-user_type');
|
||
userTypeSelect.value = user_type || "";
|
||
|
||
$('#updateUserModal').modal('show');
|
||
}
|
||
|
||
function openResetPasswordModal(uid) {
|
||
document.getElementById('reset-uid').value = uid;
|
||
$('#resetPasswordModal').modal('show');
|
||
}
|
||
|
||
function openPointsModal(uid) {
|
||
document.getElementById('points-uid').value = uid;
|
||
$('#pointsModal').modal('show');
|
||
}
|
||
|
||
function openIssueCouponModal(userId) {
|
||
$('#issue-user-id').val(userId);
|
||
$('#issueCouponModal').modal('show');
|
||
}
|
||
|
||
// 监听优惠券选择变化
|
||
$('select[name="coupon_id"]').on('change', function() {
|
||
const selected = $(this).find('option:selected');
|
||
const quantity = selected.data('quantity') || 0;
|
||
$('#coupon-quantity-hint').text(`剩余库存: ${quantity}`);
|
||
});
|
||
|
||
|
||
</script>
|
||
{% endblock %}
|