文件
2026-08-16 17:03:10 +08:00

260 行
14 KiB
PHP

此文件含有模棱两可的 Unicode 字符
此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。
<?php
require __DIR__ . '/../includes/init.php';
requireStaff();
require '_common.php';
$isAdmin = isAdminStaff();
$db = db();
$msg = '';
$err = '';
if (!$isAdmin && $_SERVER['REQUEST_METHOD'] === 'POST') {
$err = '客服账号仅可查看,如需操作请联系管理员。';
}
if (isset($_POST['toggle'])) {
verifyCsrf();
$id = (int) ($_POST['toggle'] ?? 0);
$stmt = $db->prepare('SELECT id, is_admin, status FROM ' . tn('users') . ' WHERE id = ?');
$stmt->execute([$id]);
$row = $stmt->fetch();
if (!$row) {
$err = '用户不存在';
} elseif ((int) $row['is_admin'] === 1) {
$err = '不能对管理员执行该操作';
} else {
$next = ((int) $row['status'] === 1) ? 0 : 1;
$db->prepare('UPDATE ' . tn('users') . ' SET status = ? WHERE id = ?')->execute([$next, $id]);
$msg = $next === 0 ? '该用户已被封禁,无法再登录。' : '已解除封禁。';
}
}
if (isset($_POST['delete'])) {
verifyCsrf();
$id = (int) ($_POST['delete'] ?? 0);
$stmt = $db->prepare('SELECT id, is_admin FROM ' . tn('users') . ' WHERE id = ?');
$stmt->execute([$id]);
$row = $stmt->fetch();
if (!$row) {
$err = '用户不存在';
} elseif ((int) $row['is_admin'] === 1) {
$err = '不能删除管理员账户';
} else {
$db->prepare('DELETE FROM ' . tn('users') . ' WHERE id = ? AND is_admin = 0')->execute([$id]);
$msg = '用户已删除';
}
}
if (isset($_POST['add'])) {
verifyCsrf();
$u = trim($_POST['username'] ?? '');
$em = trim($_POST['email'] ?? '');
$pw = $_POST['password'] ?? '';
if (strlen($u) < 3) $err = '用户名至少 3 个字符';
elseif (!filter_var($em, FILTER_VALIDATE_EMAIL)) $err = '请填写有效的邮箱';
elseif (strlen($pw) < 6) $err = '密码至少 6 位';
else {
$chk = $db->prepare('SELECT id FROM ' . tn('users') . ' WHERE username = ?');
$chk->execute([$u]);
if ($chk->fetch()) {
$err = '该用户名已存在';
} else {
$hash = password_hash($pw, PASSWORD_DEFAULT);
$code = genInviteCode();
$db->prepare('INSERT INTO ' . tn('users') . ' (username,password,email,invite_code,created_at) VALUES (?,?,?,?,NOW())')
->execute([$u, $hash, $em, $code]);
$msg = '用户已添加';
}
}
}
if (isset($_POST['adjust'])) {
verifyCsrf();
$target = trim($_POST['target'] ?? '');
$amount = (int) ($_POST['amount'] ?? 0);
$remark = trim($_POST['remark'] ?? '');
if ($target === '') $err = '请填写用户 ID 或用户名';
elseif ($amount === 0) $err = '积分变动不能为 0';
elseif ($remark === '') $err = '请填写调整备注(便于留痕)';
else {
if (is_numeric($target)) {
$stmt = $db->prepare('SELECT id, is_admin FROM ' . tn('users') . ' WHERE id = ?');
$stmt->execute([(int) $target]);
} else {
$stmt = $db->prepare('SELECT id, is_admin FROM ' . tn('users') . ' WHERE username = ?');
$stmt->execute([$target]);
}
$row = $stmt->fetch();
if (!$row) {
$err = '未找到该用户';
} else {
$res = addPoints((int) $row['id'], 'manual', $amount, '后台手动调整:' . $remark);
if ($res['ok']) {
$msg = '已为用户
} else {
$err = '调整失败:' . $res['error'];
}
}
}
}
if (isset($_POST['toggle_verify'])) {
verifyCsrf();
$id = (int) ($_POST['toggle_verify'] ?? 0);
$stmt = $db->prepare('SELECT id, verified FROM ' . tn('users') . ' WHERE id = ?');
$stmt->execute([$id]);
$row = $stmt->fetch();
if (!$row) {
$err = '用户不存在';
} else {
$next = (int) $row['verified'] === 1 ? 0 : 1;
$db->prepare('UPDATE ' . tn('users') . ' SET verified = ? WHERE id = ?')->execute([$next, $id]);
$msg = $next === 1 ? '已标记该用户邮箱为已验证。' : '已将该用户邮箱设为未验证。';
}
}
$viewUser = null;
$viewRealname = null;
$viewLastIp = null;
if (isset($_GET['uid'])) {
$vid = (int) ($_GET['uid'] ?? 0);
$stmt = $db->prepare('SELECT id, username, email, status, points, verified, created_at FROM ' . tn('users') . ' WHERE id = ? AND is_admin = 0');
$stmt->execute([$vid]);
$viewUser = $stmt->fetch();
if ($viewUser) {
$viewRealname = getUserRealname($vid);
$vstmt = $db->prepare('SELECT ip, created_at FROM ' . tn('visit_logs') . ' WHERE user_id = ? ORDER BY id DESC LIMIT 1');
$vstmt->execute([$vid]);
$viewLastIp = $vstmt->fetch();
if ($isAdmin && $viewRealname) {
Logger::info('后台查看用户实名信息', ['admin_id' => ($_SESSION['user_id'] ?? 0), 'user_id' => $vid]);
}
}
}
$lastIpMap = [];
try {
$ipRows = $db->query(
'SELECT v.user_id, v.ip FROM ' . tn('visit_logs') . ' v '
. 'INNER JOIN (SELECT user_id, MAX(id) mid FROM ' . tn('visit_logs') . ' GROUP BY user_id) t '
. 'ON t.user_id = v.user_id AND v.id = t.mid'
)->fetchAll();
foreach ($ipRows as $r) {
$lastIpMap[(int) $r['user_id']] = $r['ip'];
}
} catch (Throwable $e) {
}
$list = $db->query('SELECT id, username, email, status, points, verified, created_at FROM ' . tn('users') . ' WHERE is_admin = 0 ORDER BY created_at DESC')->fetchAll();
$orderCnt = $db->query('SELECT user_id, COUNT(*) c FROM ' . tn('orders') . ' GROUP BY user_id')->fetchAll(PDO::FETCH_KEY_PAIR);
adminHeader('用户管理', 'users');
?>
<h1 class="page-title"><i class="fas fa-users"></i> 用户管理(共 <?= count($list) ?> 人)</h1>
<?php if ($msg): ?><p class="banner-ok"><i class="fas fa-check-circle"></i> <?= h($msg) ?></p><?php endif; ?>
<?php if ($err): ?><p class="form-err"><?= h($err) ?></p><?php endif; ?>
<?php if ($viewUser): ?>
<div class="panel">
<h3>用户详情
<p class="muted">
邮箱:<?= h($viewUser['email']) ?>
状态:<?= (int) $viewUser['status'] === 1 ? '正常' : '已封禁' ?>
积分:<?= (int) ($viewUser['points'] ?? 0) ?>
注册:<?= date('Y-m-d', strtotime($viewUser['created_at'])) ?>
</p>
<p>最近访问 IP<code><?= h($viewLastIp['ip'] ?? '无记录') ?></code><?php if (!empty($viewLastIp['created_at'])): ?> <span class="muted"><?= h(substr($viewLastIp['created_at'], 0, 19)) ?></span><?php endif; ?></p>
<h4 style="margin:16px 0 8px;">实名认证信息</h4>
<?php if (!$viewRealname): ?>
<p class="empty">该用户尚未进行实名认证。</p>
<?php elseif (!$isAdmin): ?>
<p class="muted"><i class="fas fa-lock"></i> 实名信息仅管理员可查看(用于违法违规上报)。</p>
<?php else:
$rnName = rnDecrypt($viewRealname['real_name_enc']);
$rnId = rnDecrypt($viewRealname['idno_enc']);
$rawExists = !empty($viewRealname['real_name_enc']) || !empty($viewRealname['idno_enc']);
$decryptFail = $rawExists && $rnName === '' && $rnId === '';
?>
<?php if ($decryptFail): ?>
<p class="warn-note"><i class="fas fa-triangle-exclamation"></i> 解密失败:密文存在但无法解密,通常是<b>当前 config.php 的 SITE_KEY 与当初提交实名时不一致</b>(被修改过),或数据已损坏。请改用当初加密时的同一份 config.php 再查看。</p>
<?php else: ?>
<div class="realname-box">
<p><strong>姓名:</strong><span class="secret" data-secret="<?= h($rnName) ?>"><?= h(maskName($rnName)) ?></span> <button class="reveal-btn" type="button">显示</button></p>
<p><strong>身份证号:</strong><span class="secret" data-secret="<?= h($rnId) ?>"><?= h(maskIdno($rnId)) ?></span> <button class="reveal-btn" type="button">显示</button></p>
<p class="muted" style="font-size:.82rem">提交时间:<?= h(substr($viewRealname['created_at'], 0, 19)) ?> 核验状态:<?= (int) $viewRealname['status'] === 1 ? '已通过本地校验' : '未通过' ?></p>
<button type="button" class="btn btn-ghost btn-sm" onclick="var t=document.getElementById('rnFull');if(t){t.style.position='fixed';t.style.left='0';t.select();try{document.execCommand('copy');}catch(e){}var b=this;b.textContent='已复制 ';setTimeout(function(){b.textContent='复制完整信息用于上报';},2000);}">复制完整信息用于上报</button>
<textarea id="rnFull" readonly style="position:absolute;left:-9999px;top:-9999px"><?= h("姓名:{$rnName}\n身份证号:{$rnId}\n用户ID{$viewUser['id']}\n用户名:{$viewUser['username']}\n最近IP{$viewLastIp['ip']}") ?></textarea>
</div>
<p class="warn-note"><i class="fas fa-triangle-exclamation"></i> 实名信息属敏感个人数据,仅可在用户涉嫌违法违规、需向有关部门上报时查看与提供,严禁用于其他用途。</p>
<?php endif; ?>
<?php endif; ?>
<p style="margin-top:12px"><a href="users.php" class="btn btn-ghost btn-sm"><i class="fas fa-arrow-left"></i> 返回用户列表</a></p>
</div>
<?php elseif (isset($_GET['uid'])): ?>
<p class="form-err">未找到该用户(或该用户为管理员账户)。</p>
<?php endif; ?>
<div class="panel">
<h3>添加用户</h3>
<form method="post" action="" class="grid-form" style="max-width:680px;">
<?= csrfField() ?>
<label>用户名 *<input type="text" name="username" placeholder="至少3位" required></label>
<label>邮箱 *<input type="email" name="email" required></label>
<label class="span2">密码 *<input type="password" name="password" placeholder="至少6位" required></label>
<div class="form-actions span2"><button type="submit" name="add" class="btn btn-primary"><i class="fas fa-user-plus"></i> 添加用户</button></div>
</form>
</div>
<div class="panel">
<h3>手动调整积分</h3>
<form method="post" action="" class="grid-form" style="max-width:720px;">
<?= csrfField() ?>
<label>用户 ID 或用户名 *<input type="text" name="target" placeholder="如 12 或 xiaoming" required></label>
<label>积分变动 *<input type="number" name="amount" placeholder="正数=增加,负数=扣减" required></label>
<label class="span2">调整备注 *<input type="text" name="remark" placeholder="如「活动奖励」「误扣退回」等" required></label>
<div class="form-actions span2">
<button type="submit" name="adjust" class="btn btn-primary"><i class="fas fa-coins"></i> 提交调整</button>
</div>
</form>
</div>
<div class="panel">
<h3>用户列表</h3>
<?php if (empty($list)): ?>
<p class="empty">还没有普通用户。</p>
<?php else: ?>
<table class="data-table">
<thead><tr><th>ID</th><th>用户名</th><th>邮箱</th><th>积分</th><th>订单数</th><th>验证</th><th>实名</th><th>最近IP</th><th>状态</th><th>注册时间</th><th>操作</th></tr></thead>
<tbody>
<?php foreach ($list as $u): ?>
<tr>
<td><?= (int) $u['id'] ?></td>
<td><?= h($u['username']) ?></td>
<td><?= h($u['email']) ?></td>
<td><?= (int) ($u['points'] ?? 0) ?></td>
<td><?= (int) ($orderCnt[$u['id']] ?? 0) ?></td>
<td>
<?php if ((int) $u['verified'] === 1): ?>
<span class="badge-on">已验证</span>
<?php else: ?>
<span class="badge-off" style="background:var(--warn);color:
<?php endif; ?>
</td>
<td>
<?php if (isRealnameVerified($u['id'])): ?>
<span class="badge-on">已实名</span>
<a href="?uid=<?= (int) $u['id'] ?>" class="mini-link">查看</a>
<?php else: ?>
<span class="badge-off">未实名</span>
<?php endif; ?>
</td>
<td><code style="font-size:.76rem"><?= h($lastIpMap[$u['id']] ?? '') ?></code></td>
<td><?= (int) $u['status'] === 1 ? '<span class="badge-on">正常</span>' : '<span class="badge-off">已封禁</span>' ?></td>
<td><?= date('Y-m-d', strtotime($u['created_at'])) ?></td>
<td class="row-actions">
<form method="post" action="" style="display:inline" onsubmit="return confirm('确定<?= (int)$u['status']===1?'封禁':'解封' ?>该用户?');">
<?= csrfField() ?>
<button type="submit" name="toggle" value="<?= (int) $u['id'] ?>" class="mini-btn <?= (int)$u['status']===1?'warn':'ok' ?>"><i class="fas fa-<?= (int)$u['status']===1?'ban':'check' ?>"></i></button>
</form>
<form method="post" action="" style="display:inline" onsubmit="return confirm('确定<?= (int)$u['verified']===1?'取消验证':'标记已验证' ?>?');">
<?= csrfField() ?>
<button type="submit" name="toggle_verify" value="<?= (int) $u['id'] ?>" class="mini-btn <?= (int)$u['verified']===1?'warn':'ok' ?>" title="切换邮箱验证状态"><i class="fas fa-<?= (int)$u['verified']===1?'envelope':'envelope-circle-check' ?>"></i></button>
</form>
<form method="post" action="" style="display:inline" onsubmit="return confirm('确定删除该用户?此操作不可恢复。');">
<?= csrfField() ?>
<button type="submit" name="delete" value="<?= (int) $u['id'] ?>" class="mini-btn danger"><i class="fas fa-trash"></i></button>
</form>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php endif; ?>
</div>
<?php adminFooter(); ?>