368 lines
17 KiB
PHP
Executable File
368 lines
17 KiB
PHP
Executable File
<?php
|
||
require_once __DIR__ . '/includes/auth.php';
|
||
require_once __DIR__ . '/includes/functions.php';
|
||
requireLogin();
|
||
|
||
// 导出 CSV
|
||
if (isset($_GET['export'])) {
|
||
$isAdmin = isAdmin();
|
||
$pid = getCurrentProductId();
|
||
$where = 'WHERE r.product_id = ?';
|
||
$params = [$pid];
|
||
if (!$isAdmin) {
|
||
$where .= ' AND r.user_id = ?';
|
||
$params[] = getCurrentUserId();
|
||
}
|
||
if (!empty($_GET['search'])) {
|
||
$s = '%' . $_GET['search'] . '%';
|
||
$where .= ' AND (r.code LIKE ? OR r.batch_no LIKE ? OR r.code_name LIKE ?)';
|
||
$params = array_merge($params, [$s, $s, $s]);
|
||
}
|
||
$stmt = $pdo->prepare("SELECT r.id, r.code_name, r.batch_no, r.code, r.value, r.status, r.expired_at FROM claim_records r $where ORDER BY r.id DESC");
|
||
$stmt->execute($params);
|
||
$rows = $stmt->fetchAll();
|
||
|
||
header('Content-Type: text/csv; charset=utf-8');
|
||
header('Content-Disposition: attachment; filename=claim_records.csv');
|
||
echo "\xEF\xBB\xBF";
|
||
$f = fopen('php://output', 'w');
|
||
fputcsv($f, ['ID', '兑换码名称', '批次号', '兑换码', '面值', '状态', '过期时间']);
|
||
foreach ($rows as $r) {
|
||
$statusMap = [1 => '未使用', 2 => '已使用', 3 => '已过期'];
|
||
fputcsv($f, [
|
||
$r['id'],
|
||
$r['code_name'],
|
||
$r['batch_no'],
|
||
(int)$r['status'] === 3 ? maskCode($r['code']) : $r['code'],
|
||
$r['value'],
|
||
$statusMap[(int)$r['status']] ?? '未知',
|
||
$r['expired_at'] ? date('Y-m-d H:i:s', strtotime($r['expired_at'])) : '',
|
||
]);
|
||
}
|
||
fclose($f);
|
||
exit;
|
||
}
|
||
|
||
$pageTitle = '兑换码领取记录';
|
||
require __DIR__ . '/includes/header.php';
|
||
|
||
$isAdmin = isAdmin();
|
||
$msg = '';
|
||
|
||
// 管理员:修改状态
|
||
if ($isAdmin && $_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action'])) {
|
||
if (!verifyCsrf($_POST['csrf_token'] ?? '')) { die('CSRF token无效'); }
|
||
$id = (int)($_POST['id'] ?? 0);
|
||
if ($_POST['action'] === 'edit_status') {
|
||
$newStatus = (int)($_POST['status'] ?? 0);
|
||
$usedAt = ($_POST['used_at'] ?? '') !== '' ? str_replace('T', ' ', $_POST['used_at']) . ':00' : '';
|
||
$userId = (int)($_POST['user_id'] ?? 0);
|
||
$usedUserId = trim($_POST['used_user_id'] ?? '');
|
||
if ($newStatus < 1 || $newStatus > 3) {
|
||
$msg = '<div class="alert alert-danger">无效的状态值</div>';
|
||
} elseif ($newStatus === 2 && $usedUserId === '') {
|
||
$msg = '<div class="alert alert-danger">状态设为已使用时,必须填写会员ID</div>';
|
||
} else {
|
||
$update = ['status = ?'];
|
||
$params = [$newStatus];
|
||
if ($usedAt !== '') { $update[] = 'used_at = ?'; $params[] = $usedAt; }
|
||
if ($userId > 0) { $update[] = 'user_id = ?'; $params[] = $userId; }
|
||
if ($usedUserId !== '') { $update[] = 'used_user_id = ?'; $params[] = $usedUserId; }
|
||
$params[] = $id;
|
||
$stmt = $pdo->prepare('UPDATE claim_records SET ' . implode(', ', $update) . ' WHERE id = ?');
|
||
$stmt->execute($params);
|
||
$_SESSION['flash_msg'] = '状态已更新';
|
||
$_SESSION['flash_type'] = 'success';
|
||
header('Location: claim_records.php');
|
||
exit;
|
||
}
|
||
} elseif ($_POST['action'] === 'manual_add') {
|
||
$codeName = trim($_POST['code_name'] ?? '');
|
||
$batchNo = trim($_POST['batch_no'] ?? '');
|
||
$codeType = (int)($_POST['code_type'] ?? 1);
|
||
$code = trim($_POST['code'] ?? '');
|
||
$value = trim($_POST['value'] ?? '');
|
||
$expiredAt = $_POST['expired_at'] ?: null;
|
||
$userId = (int)($_POST['user_id'] ?? 0);
|
||
$claimedAt = ($_POST['claimed_at'] ?? '') !== '' ? str_replace('T', ' ', $_POST['claimed_at']) . ':00' : null;
|
||
$price1 = $_POST['price_tier1'] !== '' ? (float)$_POST['price_tier1'] : null;
|
||
$price2 = $_POST['price_tier2'] !== '' ? (float)$_POST['price_tier2'] : null;
|
||
$recStatus = (int)($_POST['rec_status'] ?? 1);
|
||
if (!in_array($recStatus, [1, 2, 3])) $recStatus = 1;
|
||
$usedUserId = trim($_POST['used_user_id'] ?? '');
|
||
$usedAt = ($_POST['used_at'] ?? '') !== '' ? str_replace('T', ' ', $_POST['used_at']) . ':00' : null;
|
||
if (!$codeName || !$batchNo || !$code) {
|
||
$msg = '<div class="alert alert-danger">兑换码名称、批次号、兑换码为必填项</div>';
|
||
} else {
|
||
$stmt = $pdo->prepare('INSERT INTO claim_records (product_id, created_at, user_id, code_name, batch_no, code_type, code, value, status, expired_at, price_tier1, price_tier2, claimed_at, used_user_id, used_at, remark) VALUES (?, NOW(), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)');
|
||
$stmt->execute([getCurrentProductId(), $userId ?: null, $codeName, $batchNo, $codeType, $code, $value, $recStatus, $expiredAt, $price1, $price2, $claimedAt, $usedUserId ?: null, $usedAt, '手动添加']);
|
||
$_SESSION['flash_msg'] = '手动添加成功';
|
||
$_SESSION['flash_type'] = 'success';
|
||
header('Location: claim_records.php');
|
||
exit;
|
||
}
|
||
}
|
||
}
|
||
|
||
// 分页 + 条件
|
||
$page = max(1, (int)($_GET['p'] ?? 1));
|
||
$perPage = 20;
|
||
$offset = ($page - 1) * $perPage;
|
||
|
||
$pid = getCurrentProductId();
|
||
$where = 'WHERE r.product_id = ?';
|
||
$searchParams = [$pid];
|
||
|
||
if (!$isAdmin) {
|
||
$where .= ' AND r.user_id = ?';
|
||
$searchParams[] = getCurrentUserId();
|
||
}
|
||
|
||
if (!empty($_GET['search'])) {
|
||
$search = '%' . $_GET['search'] . '%';
|
||
$where .= ' AND (r.code LIKE ? OR r.batch_no LIKE ? OR r.code_name LIKE ?)';
|
||
$searchParams = array_merge($searchParams, [$search, $search, $search]);
|
||
}
|
||
|
||
$stmt = $pdo->prepare("SELECT COUNT(*) FROM claim_records r $where");
|
||
$stmt->execute($searchParams);
|
||
$total = (int)$stmt->fetchColumn();
|
||
$totalPages = max(1, ceil($total / $perPage));
|
||
|
||
$stmt = $pdo->prepare("SELECT r.*, u.username FROM claim_records r LEFT JOIN users u ON r.user_id = u.id $where ORDER BY r.id DESC LIMIT ? OFFSET ?");
|
||
$stmt->execute(array_merge($searchParams, [$perPage, $offset]));
|
||
$records = $stmt->fetchAll();
|
||
?>
|
||
<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 ?>
|
||
<div style="margin-bottom:16px;display:flex;flex-wrap:wrap;gap:8px;">
|
||
<a href="claim_code.php" class="btn btn-info btn-sm">兑换码领取</a>
|
||
<?php if ($isAdmin): ?>
|
||
<a href="javascript:void(0)" class="btn btn-success btn-sm" onclick="showModal('addModal')">+ 手动添加</a>
|
||
<?php endif; ?>
|
||
<a href="?export=1<?= !empty($_GET['search']) ? '&search=' . urlencode($_GET['search']) : '' ?>" class="btn btn-primary btn-sm">下载 CSV</a>
|
||
<form method="get" style="display:flex;gap:8px;margin-left:auto;">
|
||
<input type="text" name="search" class="form-control" placeholder="搜索兑换码/批次号/名称" value="<?= h($_GET['search'] ?? '') ?>" style="max-width:300px;">
|
||
<button type="submit" class="btn btn-primary btn-sm">搜索</button>
|
||
<?php if (!empty($_GET['search'])): ?>
|
||
<a href="claim_records.php" class="btn btn-sm" style="background:#999;color:#fff;">清空</a>
|
||
<?php endif; ?>
|
||
</form>
|
||
</div>
|
||
<div class="table-wrapper">
|
||
<table>
|
||
<thead>
|
||
<tr>
|
||
<th>ID</th>
|
||
<th>创建时间</th>
|
||
<th>兑换码名称</th>
|
||
<th>批次号</th>
|
||
<th>类型</th>
|
||
<th>兑换码</th>
|
||
<th>面值</th>
|
||
<th>状态</th>
|
||
<th>过期时间</th>
|
||
<th>使用时间</th>
|
||
<th>会员ID</th>
|
||
<th>用户</th>
|
||
<th>领取时间</th>
|
||
<?php if ($isAdmin): ?>
|
||
<th>出货价一档</th>
|
||
<th>出货价二挡</th>
|
||
<?php endif; ?>
|
||
<th>备注</th>
|
||
<?php if ($isAdmin): ?><th>操作</th><?php endif; ?>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<?php foreach ($records as $r): ?>
|
||
<tr>
|
||
<td><?= $r['id'] ?></td>
|
||
<td><?= formatDateTime($r['created_at']) ?></td>
|
||
<td><?= h($r['code_name']) ?></td>
|
||
<td><?= h($r['batch_no']) ?></td>
|
||
<td><?= h($r['code_type']) ?></td>
|
||
<td><?php if ((int)$r['status'] === 3): ?><code><?= h(maskCode($r['code'])) ?></code><?php else: ?><?= h($r['code']) ?><?php endif; ?></td>
|
||
<td><?= h($r['value']) ?></td>
|
||
<td><?= statusBadge((int)$r['status']) ?></td>
|
||
<td><?= formatDateTime($r['expired_at']) ?></td>
|
||
<td><?= formatDateTime($r['used_at']) ?></td>
|
||
<td><?= h($r['used_user_id'] ?? '-') ?></td>
|
||
<td><?= h($r['username'] ?? $r['user_id'] ?? '-') ?></td>
|
||
<td><?= formatDateTime($r['claimed_at']) ?></td>
|
||
<?php if ($isAdmin): ?>
|
||
<td><?= $r['price_tier1'] !== null ? h($r['price_tier1']) : '-' ?></td>
|
||
<td><?= $r['price_tier2'] !== null ? h($r['price_tier2']) : '-' ?></td>
|
||
<?php endif; ?>
|
||
<td><?= h($r['remark'] ?? '') ?></td>
|
||
<?php if ($isAdmin): ?>
|
||
<td>
|
||
<a href="javascript:void(0)" class="btn btn-warning btn-sm" onclick="editStatus(<?= $r['id'] ?>, <?= $r['status'] ?>, '<?= h($r['used_at'] ?? '') ?>', <?= (int)($r['user_id'] ?? 0) ?>, '<?= h($r['used_user_id'] ?? '') ?>')">修改状态</a>
|
||
</td>
|
||
<?php endif; ?>
|
||
</tr>
|
||
<?php endforeach; ?>
|
||
<?php if (empty($records)): ?>
|
||
<tr><td colspan="<?= $isAdmin ? 17 : 14 ?>" style="text-align:center;color:#999;">暂无数据</td></tr>
|
||
<?php endif; ?>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
<?php renderPagination($page, $totalPages, !empty($_GET['search']) ? ['search' => $_GET['search']] : []); ?>
|
||
</div>
|
||
|
||
<?php
|
||
$allUsers = $pdo->query('SELECT id, username FROM users ORDER BY id ASC')->fetchAll();
|
||
if ($isAdmin):
|
||
?>
|
||
<!-- 修改状态弹窗 -->
|
||
<div class="modal" id="editModal">
|
||
<div class="modal-content">
|
||
<span class="modal-close" onclick="hideModal('editModal')">×</span>
|
||
<h3>修改状态</h3>
|
||
<form method="post">
|
||
<input type="hidden" name="action" value="edit_status">
|
||
<input type="hidden" name="csrf_token" value="<?= csrfToken() ?>">
|
||
<input type="hidden" name="id" id="edit_id">
|
||
<div class="form-group">
|
||
<label>状态</label>
|
||
<select name="status" class="form-control" id="edit_status">
|
||
<option value="1">未使用</option>
|
||
<option value="2">已使用</option>
|
||
<option value="3">已过期</option>
|
||
</select>
|
||
</div>
|
||
<div class="form-group">
|
||
<label>使用时间</label>
|
||
<input type="datetime-local" name="used_at" class="form-control" id="edit_used_at">
|
||
</div>
|
||
<div class="form-group">
|
||
<label>用户</label>
|
||
<select name="user_id" class="form-control" id="edit_user_id">
|
||
<option value="">-- 不关联 --</option>
|
||
<?php foreach ($allUsers as $u): ?>
|
||
<option value="<?= $u['id'] ?>"><?= h($u['username']) ?> (ID: <?= $u['id'] ?>)</option>
|
||
<?php endforeach; ?>
|
||
</select>
|
||
</div>
|
||
<div class="form-group" id="edit_used_user_group">
|
||
<label>会员ID <span id="edit_used_user_required" style="color:red;display:none;">*</span></label>
|
||
<input type="text" name="used_user_id" class="form-control" id="edit_used_user_id" placeholder="使用该兑换码的用户标识">
|
||
</div>
|
||
<button type="submit" class="btn btn-primary">保存</button>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
<!-- 手动添加弹窗 -->
|
||
<div class="modal" id="addModal">
|
||
<div class="modal-content">
|
||
<span class="modal-close" onclick="hideModal('addModal')">×</span>
|
||
<h3>手动添加记录</h3>
|
||
<form method="post">
|
||
<input type="hidden" name="action" value="manual_add">
|
||
<input type="hidden" name="csrf_token" value="<?= csrfToken() ?>">
|
||
<div class="form-group">
|
||
<label>兑换码名称 <span style="color:red">*</span></label>
|
||
<input type="text" name="code_name" class="form-control" required>
|
||
</div>
|
||
<div class="form-group">
|
||
<label>批次号 <span style="color:red">*</span></label>
|
||
<input type="text" name="batch_no" class="form-control" required>
|
||
</div>
|
||
<div class="form-group">
|
||
<label>兑换码类型</label>
|
||
<input type="number" name="code_type" class="form-control" value="1" readonly>
|
||
</div>
|
||
<div class="form-group">
|
||
<label>兑换码 <span style="color:red">*</span></label>
|
||
<input type="text" name="code" class="form-control" required>
|
||
</div>
|
||
<div class="form-group">
|
||
<label>面值</label>
|
||
<input type="text" name="value" class="form-control" placeholder="如: 100元">
|
||
</div>
|
||
<div class="form-group">
|
||
<label>状态</label>
|
||
<select name="rec_status" class="form-control">
|
||
<option value="1">未使用</option>
|
||
<option value="2">已使用</option>
|
||
<option value="3">已过期</option>
|
||
</select>
|
||
</div>
|
||
<div class="form-group">
|
||
<label>使用时间</label>
|
||
<input type="datetime-local" name="used_at" class="form-control">
|
||
</div>
|
||
<div class="form-group">
|
||
<label>过期时间</label>
|
||
<input type="text" name="expired_at" class="form-control" placeholder="2027-06-01 12:26:53">
|
||
</div>
|
||
<div class="form-group">
|
||
<label>会员ID</label>
|
||
<input type="text" name="used_user_id" class="form-control" placeholder="使用该兑换码的用户标识">
|
||
</div>
|
||
<div class="form-group">
|
||
<label>用户</label>
|
||
<select name="user_id" class="form-control">
|
||
<option value="">-- 不关联 --</option>
|
||
<?php foreach ($allUsers as $u): ?>
|
||
<option value="<?= $u['id'] ?>"><?= h($u['username']) ?> (ID: <?= $u['id'] ?>)</option>
|
||
<?php endforeach; ?>
|
||
</select>
|
||
</div>
|
||
<div class="form-group">
|
||
<label>领取时间</label>
|
||
<input type="datetime-local" name="claimed_at" class="form-control">
|
||
</div>
|
||
<div class="form-group">
|
||
<label>出货价一档</label>
|
||
<input type="number" step="0.01" name="price_tier1" class="form-control">
|
||
</div>
|
||
<div class="form-group">
|
||
<label>出货价二挡</label>
|
||
<input type="number" step="0.01" name="price_tier2" class="form-control">
|
||
</div>
|
||
<button type="submit" class="btn btn-success">添加</button>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
<script>
|
||
function showModal(id) { document.getElementById(id).classList.add('active'); }
|
||
function hideModal(id) { document.getElementById(id).classList.remove('active'); }
|
||
function editStatus(id, status, usedAt, userId, usedUserId) {
|
||
document.getElementById('edit_id').value = id;
|
||
document.getElementById('edit_status').value = status;
|
||
document.getElementById('edit_used_at').value = usedAt ? usedAt.replace(' ', 'T').substring(0, 16) : '';
|
||
document.getElementById('edit_user_id').value = userId;
|
||
document.getElementById('edit_used_user_id').value = usedUserId;
|
||
toggleUsedUserRequired();
|
||
showModal('editModal');
|
||
}
|
||
|
||
function toggleUsedUserRequired() {
|
||
var status = document.getElementById('edit_status').value;
|
||
var indicator = document.getElementById('edit_used_user_required');
|
||
var input = document.getElementById('edit_used_user_id');
|
||
if (status === '2') {
|
||
indicator.style.display = 'inline';
|
||
input.required = true;
|
||
} else {
|
||
indicator.style.display = 'none';
|
||
input.required = false;
|
||
}
|
||
}
|
||
|
||
document.getElementById('edit_status').addEventListener('change', toggleUsedUserRequired);
|
||
|
||
document.querySelectorAll('.modal').forEach(function(m) {
|
||
m.addEventListener('click', function(e) { if (e.target === this) this.classList.remove('active'); });
|
||
});
|
||
</script>
|
||
<?php endif; ?>
|
||
<?php require __DIR__ . '/includes/footer.php'; ?>
|