550 lines
26 KiB
PHP
Executable File
550 lines
26 KiB
PHP
Executable File
<?php
|
||
require_once __DIR__ . '/includes/auth.php';
|
||
require_once __DIR__ . '/includes/functions.php';
|
||
requireAdmin();
|
||
|
||
$pageTitle = '后台设置';
|
||
require __DIR__ . '/includes/header.php';
|
||
|
||
$msg = '';
|
||
|
||
// 生成邀请码
|
||
if (isset($_POST['gen_invite'])) {
|
||
if (!verifyCsrf($_POST['csrf_token'] ?? '')) { die('CSRF token无效'); }
|
||
$count = max(1, min(100, (int)($_POST['count'] ?? 1)));
|
||
$maxUses = max(0, (int)($_POST['max_uses'] ?? 1));
|
||
$inserted = 0;
|
||
$stmt = $pdo->prepare('INSERT INTO invite_codes (code, max_uses) VALUES (?, ?)');
|
||
for ($i = 0; $i < $count; $i++) {
|
||
$code = strtoupper(bin2hex(random_bytes(4)));
|
||
try {
|
||
$stmt->execute([$code, $maxUses]);
|
||
$inserted++;
|
||
} catch (PDOException $e) {
|
||
}
|
||
}
|
||
$_SESSION['flash_msg'] = '成功生成 ' . $inserted . ' 个邀请码(最大使用次数: ' . ($maxUses === 0 ? '不限' : $maxUses) . ')';
|
||
$_SESSION['flash_type'] = 'success';
|
||
header('Location: admin_settings.php');
|
||
exit;
|
||
}
|
||
|
||
// 修改邀请码次数
|
||
if (isset($_POST['edit_max_uses'])) {
|
||
if (!verifyCsrf($_POST['csrf_token'] ?? '')) { die('CSRF token无效'); }
|
||
$id = (int)($_POST['id'] ?? 0);
|
||
$maxUses = max(0, (int)($_POST['max_uses'] ?? 1));
|
||
$stmt = $pdo->prepare('UPDATE invite_codes SET max_uses = ? WHERE id = ?');
|
||
$stmt->execute([$maxUses, $id]);
|
||
$_SESSION['flash_msg'] = '已更新最大使用次数';
|
||
$_SESSION['flash_type'] = 'success';
|
||
header('Location: admin_settings.php');
|
||
exit;
|
||
}
|
||
|
||
// 删除邀请码(仅可删未使用的)
|
||
if (isset($_GET['del_invite']) && is_numeric($_GET['del_invite'])) {
|
||
if (!verifyCsrf($_GET['csrf_token'] ?? '')) { $msg = '<div class="alert alert-danger">CSRF token无效</div>'; }
|
||
else {
|
||
$id = (int)$_GET['del_invite'];
|
||
$stmt = $pdo->prepare('DELETE FROM invite_codes WHERE id = ? AND used_count = 0');
|
||
$stmt->execute([$id]);
|
||
$msg = $stmt->rowCount() ? '<div class="alert alert-success">邀请码已删除</div>' : '<div class="alert alert-danger">无法删除:已被使用或不存在</div>';
|
||
}
|
||
}
|
||
|
||
// 新增用户
|
||
if (isset($_POST['add_user'])) {
|
||
if (!verifyCsrf($_POST['csrf_token'] ?? '')) { die('CSRF token无效'); }
|
||
$username = trim($_POST['username'] ?? '');
|
||
$password = $_POST['password'] ?? '';
|
||
$role = $_POST['role'] ?? 'user';
|
||
$remark = trim($_POST['remark'] ?? '');
|
||
if (!in_array($role, ['admin', 'user'])) $role = 'user';
|
||
if ($username === '' || $password === '') {
|
||
$msg = '<div class="alert alert-danger">用户名和密码不能为空</div>';
|
||
} elseif (strlen($username) < 3 || strlen($username) > 20) {
|
||
$msg = '<div class="alert alert-danger">用户名长度3-20个字符</div>';
|
||
} elseif (strlen($password) < 6) {
|
||
$msg = '<div class="alert alert-danger">密码长度至少6位</div>';
|
||
} else {
|
||
$stmt = $pdo->prepare('SELECT id FROM users WHERE username = ?');
|
||
$stmt->execute([$username]);
|
||
if ($stmt->fetch()) {
|
||
$msg = '<div class="alert alert-danger">用户名已存在</div>';
|
||
} else {
|
||
$productIds = array_map('intval', $_POST['product_ids'] ?? []);
|
||
$productIds = array_filter($productIds, fn($v) => $v > 0);
|
||
if (empty($productIds)) {
|
||
$msg = '<div class="alert alert-danger">至少选择一个产品</div>';
|
||
} else {
|
||
$hash = password_hash($password, PASSWORD_DEFAULT);
|
||
$stmt = $pdo->prepare('INSERT INTO users (username, password, role, remark) VALUES (?, ?, ?, ?)');
|
||
$stmt->execute([$username, $hash, $role, $remark ?: null]);
|
||
$userId = (int)$pdo->lastInsertId();
|
||
$stmt = $pdo->prepare('INSERT INTO user_products (user_id, product_id) VALUES (?, ?)');
|
||
foreach ($productIds as $pid) {
|
||
$stmt->execute([$userId, $pid]);
|
||
}
|
||
$_SESSION['flash_msg'] = '用户 ' . h($username) . ' 添加成功';
|
||
$_SESSION['flash_type'] = 'success';
|
||
header('Location: admin_settings.php');
|
||
exit;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// 修改备注
|
||
if (isset($_POST['edit_remark'])) {
|
||
if (!verifyCsrf($_POST['csrf_token'] ?? '')) { die('CSRF token无效'); }
|
||
$id = (int)($_POST['user_id'] ?? 0);
|
||
$remark = trim($_POST['remark'] ?? '');
|
||
$stmt = $pdo->prepare('UPDATE users SET remark = ? WHERE id = ?');
|
||
$stmt->execute([$remark ?: null, $id]);
|
||
$_SESSION['flash_msg'] = '备注已更新';
|
||
$_SESSION['flash_type'] = 'success';
|
||
header('Location: admin_settings.php');
|
||
exit;
|
||
}
|
||
|
||
// 分配产品
|
||
if (isset($_POST['edit_user_products'])) {
|
||
if (!verifyCsrf($_POST['csrf_token'] ?? '')) { die('CSRF token无效'); }
|
||
$id = (int)($_POST['user_id'] ?? 0);
|
||
$productIds = array_map('intval', $_POST['product_ids'] ?? []);
|
||
$productIds = array_filter($productIds, fn($v) => $v > 0);
|
||
if (empty($productIds)) {
|
||
$msg = '<div class="alert alert-danger">至少选择一个产品</div>';
|
||
} else {
|
||
$stmt = $pdo->prepare('DELETE FROM user_products WHERE user_id = ?');
|
||
$stmt->execute([$id]);
|
||
$stmt = $pdo->prepare('INSERT INTO user_products (user_id, product_id) VALUES (?, ?)');
|
||
foreach ($productIds as $pid) {
|
||
$stmt->execute([$id, $pid]);
|
||
}
|
||
$_SESSION['flash_msg'] = '产品分配已更新';
|
||
$_SESSION['flash_type'] = 'success';
|
||
header('Location: admin_settings.php');
|
||
exit;
|
||
}
|
||
}
|
||
|
||
// 禁用/启用用户
|
||
if (isset($_GET['toggle_disable']) && is_numeric($_GET['toggle_disable'])) {
|
||
if (!verifyCsrf($_GET['csrf_token'] ?? '')) {
|
||
$_SESSION['flash_msg'] = 'CSRF token无效';
|
||
$_SESSION['flash_type'] = 'danger';
|
||
} else {
|
||
$id = (int)$_GET['toggle_disable'];
|
||
if ($id === getCurrentUserId()) {
|
||
$_SESSION['flash_msg'] = '不能禁用自己';
|
||
$_SESSION['flash_type'] = 'danger';
|
||
} else {
|
||
$stmt = $pdo->prepare('SELECT disabled FROM users WHERE id = ?');
|
||
$stmt->execute([$id]);
|
||
$current = (int)$stmt->fetchColumn();
|
||
$newDisabled = $current ? 0 : 1;
|
||
$stmt = $pdo->prepare('UPDATE users SET disabled = ? WHERE id = ?');
|
||
$stmt->execute([$newDisabled, $id]);
|
||
$_SESSION['flash_msg'] = $newDisabled ? '用户已禁用' : '用户已启用';
|
||
$_SESSION['flash_type'] = 'success';
|
||
}
|
||
}
|
||
header('Location: admin_settings.php');
|
||
exit;
|
||
}
|
||
|
||
// 修改用户角色
|
||
if (isset($_GET['set_role']) && is_numeric($_GET['set_role'])) {
|
||
$id = (int)$_GET['set_role'];
|
||
$role = $_GET['role'] ?? '';
|
||
if ($id === getCurrentUserId()) {
|
||
$msg = '<div class="alert alert-danger">不能修改自己的角色</div>';
|
||
} elseif (!in_array($role, ['admin', 'user'])) {
|
||
$msg = '<div class="alert alert-danger">无效的角色</div>';
|
||
} else {
|
||
$stmt = $pdo->query("SELECT COUNT(*) FROM users WHERE role = 'admin'");
|
||
$adminCount = (int)$stmt->fetchColumn();
|
||
$stmt = $pdo->prepare('SELECT role FROM users WHERE id = ?');
|
||
$stmt->execute([$id]);
|
||
$targetRole = $stmt->fetchColumn();
|
||
if ($role === 'user' && $targetRole === 'admin' && $adminCount <= 1) {
|
||
$msg = '<div class="alert alert-danger">至少保留一名管理员</div>';
|
||
} else {
|
||
$stmt = $pdo->prepare('UPDATE users SET role = ? WHERE id = ?');
|
||
$stmt->execute([$role, $id]);
|
||
$msg = '<div class="alert alert-success">角色已更新</div>';
|
||
}
|
||
}
|
||
}
|
||
|
||
// 分页 - 邀请码
|
||
$page = max(1, (int)($_GET['p'] ?? 1));
|
||
$perPage = 20;
|
||
$offset = ($page - 1) * $perPage;
|
||
|
||
$stmt = $pdo->query('SELECT COUNT(*) FROM invite_codes');
|
||
$inviteTotal = (int)$stmt->fetchColumn();
|
||
$invitePages = max(1, ceil($inviteTotal / $perPage));
|
||
|
||
$stmt = $pdo->prepare('SELECT * FROM invite_codes ORDER BY id DESC LIMIT ? OFFSET ?');
|
||
$stmt->execute([$perPage, $offset]);
|
||
$invites = $stmt->fetchAll();
|
||
|
||
// 用户列表
|
||
$stmt = $pdo->query('SELECT * FROM users ORDER BY id ASC');
|
||
$users = $stmt->fetchAll();
|
||
|
||
// 所有启用产品
|
||
$stmt = $pdo->query('SELECT id, name, code FROM products WHERE status = 1 ORDER BY id ASC');
|
||
$allProducts = $stmt->fetchAll();
|
||
|
||
// 用户-产品关联
|
||
$userProducts = [];
|
||
$stmt = $pdo->query('SELECT user_id, product_id FROM user_products');
|
||
while ($row = $stmt->fetch()) {
|
||
$userProducts[$row['user_id']][] = (int)$row['product_id'];
|
||
}
|
||
?>
|
||
<div class="card">
|
||
<h2>邀请码管理</h2>
|
||
<?php if (isset($_SESSION['flash_msg'])): ?>
|
||
<div class="alert alert-<?= h($_SESSION['flash_type'] ?? 'success') ?>"><?= h($_SESSION['flash_msg']) ?></div>
|
||
<?php unset($_SESSION['flash_msg'], $_SESSION['flash_type']); ?>
|
||
<?php endif; ?>
|
||
<?= $msg ?>
|
||
<form method="post" style="display:flex;gap:8px;align-items:flex-end;flex-wrap:wrap;margin-bottom:16px;">
|
||
<input type="hidden" name="csrf_token" value="<?= csrfToken() ?>">
|
||
<div class="form-group" style="margin-bottom:0;">
|
||
<label>生成数量</label>
|
||
<input type="number" name="count" class="form-control" value="5" min="1" max="100" style="width:100px;">
|
||
</div>
|
||
<div class="form-group" style="margin-bottom:0;">
|
||
<label>最大使用次数</label>
|
||
<input type="number" name="max_uses" class="form-control" value="1" min="0" style="width:120px;" title="0=不限次数">
|
||
</div>
|
||
<button type="submit" name="gen_invite" class="btn btn-success">生成邀请码</button>
|
||
</form>
|
||
<div class="table-wrapper">
|
||
<table>
|
||
<thead>
|
||
<tr>
|
||
<th>ID</th>
|
||
<th>邀请码</th>
|
||
<th>使用次数</th>
|
||
<th>最大次数</th>
|
||
<th>状态</th>
|
||
<th>创建时间</th>
|
||
<th>操作</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<?php foreach ($invites as $i): ?>
|
||
<?php
|
||
$expired = $i['max_uses'] > 0 && $i['used_count'] >= $i['max_uses'];
|
||
?>
|
||
<tr>
|
||
<td><?= $i['id'] ?></td>
|
||
<td><code><?= h($i['code']) ?></code></td>
|
||
<td><?= $i['used_count'] ?></td>
|
||
<td><?= $i['max_uses'] === 0 ? '不限' : $i['max_uses'] ?></td>
|
||
<td>
|
||
<?php if ($expired): ?>
|
||
<span class="badge badge-danger">已用完</span>
|
||
<?php elseif ($i['used_count'] > 0): ?>
|
||
<span class="badge badge-warning">使用中</span>
|
||
<?php else: ?>
|
||
<span class="badge badge-success">未使用</span>
|
||
<?php endif; ?>
|
||
</td>
|
||
<td><?= formatDateTime($i['created_at']) ?></td>
|
||
<td>
|
||
<div class="action-group">
|
||
<a href="javascript:void(0)" class="btn btn-primary btn-sm" onclick="editMaxUses(<?= $i['id'] ?>, <?= $i['max_uses'] ?>)">修改次数</a>
|
||
<?php if ($i['used_count'] === 0): ?>
|
||
<a href="?del_invite=<?= $i['id'] ?>&csrf_token=<?= csrfToken() ?>" class="btn btn-danger btn-sm" onclick="return confirm('确定删除?')">删除</a>
|
||
<?php endif; ?>
|
||
</div>
|
||
</td>
|
||
</tr>
|
||
<?php endforeach; ?>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
<?php renderPagination($page, $invitePages); ?>
|
||
</div>
|
||
|
||
<div class="card">
|
||
<h2 style="display:flex;justify-content:space-between;align-items:center;flex-wrap:wrap;">
|
||
用户管理
|
||
<a href="javascript:void(0)" class="btn btn-success btn-sm" onclick="showModal('addUserModal')">+ 新增用户</a>
|
||
</h2>
|
||
<div class="table-wrapper">
|
||
<table>
|
||
<thead>
|
||
<tr>
|
||
<th>ID</th>
|
||
<th>用户名</th>
|
||
<th>角色</th>
|
||
<th>状态</th>
|
||
<th>可管理产品</th>
|
||
<th>备注</th>
|
||
<th>注册时间</th>
|
||
<th>操作</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<?php foreach ($users as $u): ?>
|
||
<tr>
|
||
<td><?= $u['id'] ?></td>
|
||
<td><?= h($u['username']) ?></td>
|
||
<td>
|
||
<?php if ($u['role'] === 'admin'): ?>
|
||
<span class="badge badge-danger">管理员</span>
|
||
<?php else: ?>
|
||
<span class="badge badge-info">普通用户</span>
|
||
<?php endif; ?>
|
||
</td>
|
||
<td>
|
||
<?php if (!empty($u['disabled'])): ?>
|
||
<span class="badge badge-secondary">已禁用</span>
|
||
<?php else: ?>
|
||
<span class="badge badge-success">正常</span>
|
||
<?php endif; ?>
|
||
</td>
|
||
<td style="max-width:180px;">
|
||
<?php
|
||
$upIds = $userProducts[(int)$u['id']] ?? [];
|
||
$upNames = array_map(fn($p) => $p['name'], array_filter($allProducts, fn($p) => in_array((int)$p['id'], $upIds)));
|
||
echo $upNames ? h(implode(', ', $upNames)) : '<span style="color:#ccc;">-</span>';
|
||
?>
|
||
</td>
|
||
<td>
|
||
<?php if ($u['remark']): ?>
|
||
<span title="<?= h($u['remark']) ?>"><?= h(mb_substr($u['remark'], 0, 20)) ?><?= mb_strlen($u['remark']) > 20 ? '...' : '' ?></span>
|
||
<?php else: ?>
|
||
<span style="color:#ccc;">-</span>
|
||
<?php endif; ?>
|
||
<a href="javascript:void(0)" class="btn btn-sm" style="padding:2px 6px;font-size:11px;" onclick="editRemark(<?= $u['id'] ?>, '<?= h($u['remark'] ?? '') ?>')">✏️</a>
|
||
</td>
|
||
<td><?= formatDateTime($u['created_at']) ?></td>
|
||
<td>
|
||
<div class="action-group">
|
||
<a href="javascript:void(0)" class="btn btn-info btn-sm" onclick="editUserProducts(<?= $u['id'] ?>, '<?= h($u['username']) ?>')">分配产品</a>
|
||
<?php if ((int)$u['id'] !== getCurrentUserId()): ?>
|
||
<?php if ($u['role'] === 'user'): ?>
|
||
<a href="?set_role=<?= $u['id'] ?>&role=admin&csrf_token=<?= csrfToken() ?>" class="btn btn-primary btn-sm">设为管理员</a>
|
||
<?php else: ?>
|
||
<a href="?set_role=<?= $u['id'] ?>&role=user&csrf_token=<?= csrfToken() ?>" class="btn btn-warning btn-sm">设为普通用户</a>
|
||
<?php endif; ?>
|
||
<?php if (!empty($u['disabled'])): ?>
|
||
<a href="?toggle_disable=<?= $u['id'] ?>&csrf_token=<?= csrfToken() ?>" class="btn btn-success btn-sm">启用</a>
|
||
<?php else: ?>
|
||
<a href="?toggle_disable=<?= $u['id'] ?>&csrf_token=<?= csrfToken() ?>" class="btn btn-danger btn-sm" onclick="return confirm('确定禁用用户 <?= h($u['username']) ?>?')">禁用</a>
|
||
<?php endif; ?>
|
||
<?php else: ?>
|
||
<span style="color:#999;">当前用户</span>
|
||
<?php endif; ?>
|
||
</div>
|
||
</td>
|
||
</tr>
|
||
<?php endforeach; ?>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 修改邀请码次数弹窗 -->
|
||
<div class="modal" id="maxUsesModal">
|
||
<div class="modal-content">
|
||
<span class="modal-close" onclick="hideModal('maxUsesModal')">×</span>
|
||
<h3>修改最大使用次数</h3>
|
||
<form method="post">
|
||
<input type="hidden" name="csrf_token" value="<?= csrfToken() ?>">
|
||
<input type="hidden" name="edit_max_uses" value="1">
|
||
<input type="hidden" name="id" id="max_uses_id">
|
||
<div class="form-group">
|
||
<label>最大使用次数(0=不限次数)</label>
|
||
<input type="number" name="max_uses" class="form-control" id="max_uses_value" min="0" required>
|
||
</div>
|
||
<button type="submit" class="btn btn-primary">保存</button>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="card" style="margin-bottom:20px;">
|
||
<h2>兑换码状态同步</h2>
|
||
<?php
|
||
require_once __DIR__ . '/includes/coupon_sync.php';
|
||
$syncResult = '';
|
||
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['sync_coupon'])) {
|
||
if (!verifyCsrf($_POST['csrf_token'] ?? '')) { die('CSRF token无效'); }
|
||
$startTime = (int)($_POST['start_time'] ?? 0);
|
||
$endTime = (int)($_POST['end_time'] ?? 0);
|
||
if ($startTime <= 0 || $endTime <= 0 || $startTime > $endTime) {
|
||
$syncResult = '<div class="alert alert-danger">请选择有效的时间范围</div>';
|
||
} else {
|
||
$stmt = $pdo->query("SELECT id, code, name, api_url, token FROM products WHERE status = 1 AND api_url IS NOT NULL AND api_url != ''");
|
||
$products = $stmt->fetchAll();
|
||
if (empty($products)) {
|
||
$syncResult = '<div class="alert alert-warning">没有已配置接口地址的产品</div>';
|
||
} else {
|
||
$output = '';
|
||
foreach ($products as $p) {
|
||
$ret = syncCouponStatus($pdo, $p, $startTime, $endTime);
|
||
if ($ret['success']) {
|
||
$output .= "<strong>{$p['name']}</strong>: API返回{$ret['total']}条, 匹配{$ret['matched']}条, 更新{$ret['updated']}条";
|
||
if (!empty($ret['matched_codes'])) {
|
||
$output .= '<br><span style="font-size:12px;color:#166534;">✓ 已匹配 (' . count($ret['matched_codes']) . '个): ' . h(implode(', ', $ret['matched_codes'])) . '</span>';
|
||
}
|
||
if (!empty($ret['unmatched_codes'])) {
|
||
$output .= '<br><span style="font-size:12px;color:#991b1b;">✗ 未匹配 (' . count($ret['unmatched_codes']) . '个): ' . h(implode(', ', array_slice($ret['unmatched_codes'], 0, 50))) . (count($ret['unmatched_codes']) > 50 ? '...' : '') . '</span>';
|
||
}
|
||
$output .= '<br>';
|
||
} else {
|
||
$output .= "<strong>{$p['name']}</strong>: 失败 - {$ret['message']}<br>";
|
||
}
|
||
}
|
||
$syncResult = '<div class="alert alert-success" style="font-size:13px;word-break:break-all;">' . $output . '</div>';
|
||
}
|
||
}
|
||
}
|
||
?>
|
||
<?= $syncResult ?>
|
||
<p style="font-size:14px;color:#666;margin-bottom:12px;">通过远程接口查询兑换码的使用状态,并将已使用的兑换码更新到本地。</p>
|
||
<form method="post" style="display:flex;gap:8px;align-items:flex-end;flex-wrap:wrap;" onsubmit="document.getElementById('sync_start').value=Math.floor(new Date(document.getElementById('sync_start_input').value).getTime()/1000);document.getElementById('sync_end').value=Math.floor(new Date(document.getElementById('sync_end_input').value).getTime()/1000);">
|
||
<input type="hidden" name="csrf_token" value="<?= csrfToken() ?>">
|
||
<div class="form-group" style="margin-bottom:0;">
|
||
<label>开始时间</label>
|
||
<input type="datetime-local" id="sync_start_input" class="form-control" required style="width:200px;">
|
||
<input type="hidden" name="start_time" id="sync_start" value="">
|
||
</div>
|
||
<div class="form-group" style="margin-bottom:0;">
|
||
<label>结束时间</label>
|
||
<input type="datetime-local" id="sync_end_input" class="form-control" required style="width:200px;">
|
||
<input type="hidden" name="end_time" id="sync_end" value="">
|
||
</div>
|
||
<button type="submit" name="sync_coupon" class="btn btn-primary">立即同步</button>
|
||
</form>
|
||
</div>
|
||
|
||
<!-- 新增用户弹窗 -->
|
||
<div class="modal" id="addUserModal">
|
||
<div class="modal-content">
|
||
<span class="modal-close" onclick="hideModal('addUserModal')">×</span>
|
||
<h3>新增用户</h3>
|
||
<form method="post">
|
||
<input type="hidden" name="csrf_token" value="<?= csrfToken() ?>">
|
||
<div class="form-group">
|
||
<label>用户名 <span style="color:red">*</span></label>
|
||
<input type="text" name="username" class="form-control" required minlength="3" maxlength="20">
|
||
</div>
|
||
<div class="form-group">
|
||
<label>密码 <span style="color:red">*</span></label>
|
||
<input type="password" name="password" class="form-control" required minlength="6">
|
||
</div>
|
||
<div class="form-group">
|
||
<label>角色</label>
|
||
<select name="role" class="form-control">
|
||
<option value="user">普通用户</option>
|
||
<option value="admin">管理员</option>
|
||
</select>
|
||
</div>
|
||
<div class="form-group">
|
||
<label>可管理产品 <span style="color:red">*</span></label>
|
||
<div style="max-height:150px;overflow-y:auto;border:1px solid #ddd;border-radius:4px;padding:8px;">
|
||
<?php foreach ($allProducts as $p): ?>
|
||
<label style="display:block;font-weight:normal;margin-bottom:4px;">
|
||
<input type="checkbox" name="product_ids[]" value="<?= $p['id'] ?>"> <?= h($p['name']) ?>
|
||
</label>
|
||
<?php endforeach; ?>
|
||
</div>
|
||
<div style="font-size:12px;color:#999;margin-top:4px;">至少选择一个产品</div>
|
||
</div>
|
||
<div class="form-group">
|
||
<label>备注</label>
|
||
<input type="text" name="remark" class="form-control" placeholder="选填">
|
||
</div>
|
||
<button type="submit" name="add_user" class="btn btn-success">添加用户</button>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 编辑备注弹窗 -->
|
||
<div class="modal" id="remarkModal">
|
||
<div class="modal-content">
|
||
<span class="modal-close" onclick="hideModal('remarkModal')">×</span>
|
||
<h3>编辑备注</h3>
|
||
<form method="post">
|
||
<input type="hidden" name="csrf_token" value="<?= csrfToken() ?>">
|
||
<input type="hidden" name="edit_remark" value="1">
|
||
<input type="hidden" name="user_id" id="remark_user_id">
|
||
<div class="form-group">
|
||
<label>备注</label>
|
||
<input type="text" name="remark" class="form-control" id="remark_content" maxlength="255">
|
||
</div>
|
||
<button type="submit" class="btn btn-primary">保存</button>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 分配产品弹窗 -->
|
||
<div class="modal" id="productModal">
|
||
<div class="modal-content">
|
||
<span class="modal-close" onclick="hideModal('productModal')">×</span>
|
||
<h3>分配产品 - <span id="product_user_name"></span></h3>
|
||
<form method="post">
|
||
<input type="hidden" name="csrf_token" value="<?= csrfToken() ?>">
|
||
<input type="hidden" name="edit_user_products" value="1">
|
||
<input type="hidden" name="user_id" id="product_user_id">
|
||
<div class="form-group">
|
||
<label>可管理产品 <span style="color:red">*</span></label>
|
||
<div id="product_checkbox_list" style="max-height:200px;overflow-y:auto;border:1px solid #ddd;border-radius:4px;padding:8px;">
|
||
<?php foreach ($allProducts as $p): ?>
|
||
<label style="display:block;font-weight:normal;margin-bottom:4px;">
|
||
<input type="checkbox" name="product_ids[]" value="<?= $p['id'] ?>" class="product-checkbox"> <?= h($p['name']) ?>
|
||
</label>
|
||
<?php endforeach; ?>
|
||
</div>
|
||
<div style="font-size:12px;color:#999;margin-top:4px;">至少选择一个产品</div>
|
||
</div>
|
||
<button type="submit" class="btn btn-primary">保存</button>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
function showModal(id) { document.getElementById(id).classList.add('active'); }
|
||
function hideModal(id) { document.getElementById(id).classList.remove('active'); }
|
||
function editRemark(id, content) {
|
||
document.getElementById('remark_user_id').value = id;
|
||
document.getElementById('remark_content').value = content;
|
||
showModal('remarkModal');
|
||
}
|
||
function editMaxUses(id, maxUses) {
|
||
document.getElementById('max_uses_id').value = id;
|
||
document.getElementById('max_uses_value').value = maxUses;
|
||
showModal('maxUsesModal');
|
||
}
|
||
function editUserProducts(id, name) {
|
||
document.getElementById('product_user_id').value = id;
|
||
document.getElementById('product_user_name').textContent = name;
|
||
// 取消所有选中
|
||
document.querySelectorAll('.product-checkbox').forEach(function(cb) { cb.checked = false; });
|
||
// 选中当前用户已有的产品
|
||
var data = <?= json_encode($userProducts) ?>;
|
||
var ids = data[id] || [];
|
||
document.querySelectorAll('.product-checkbox').forEach(function(cb) {
|
||
if (ids.indexOf(parseInt(cb.value)) !== -1) {
|
||
cb.checked = true;
|
||
}
|
||
});
|
||
showModal('productModal');
|
||
}
|
||
document.querySelectorAll('.modal').forEach(function(m) {
|
||
m.addEventListener('click', function(e) { if (e.target === this) this.classList.remove('active'); });
|
||
});
|
||
</script>
|
||
<?php require __DIR__ . '/includes/footer.php'; ?>
|