|
|
<!doctype html>
|
|
|
<html lang="zh-CN" data-prototype-scale="off">
|
|
|
<head>
|
|
|
<meta charset="UTF-8" />
|
|
|
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
|
|
|
<title>手机扫码核销</title>
|
|
|
<link rel="stylesheet" href="../assets/bootstrap.min.css" />
|
|
|
<link rel="stylesheet" href="../assets/prototype.css" />
|
|
|
</head>
|
|
|
<body class="prototype-page mobile-checkin-page">
|
|
|
<main class="mobile-checkin-shell">
|
|
|
<header class="mobile-checkin-header">
|
|
|
<a href="./admin-audience.html" aria-label="返回观众管理">‹</a>
|
|
|
<div><h1>入场核销</h1><p id="mobileSessionName">上海预赛路演</p></div>
|
|
|
<button type="button" id="flashBtn" aria-pressed="false">闪光灯</button>
|
|
|
</header>
|
|
|
|
|
|
<section class="mobile-scanner" aria-label="二维码扫描区">
|
|
|
<div class="mobile-scanner-shade">
|
|
|
<div class="mobile-scan-frame"><span></span></div>
|
|
|
<div class="mobile-scan-symbol">◎</div>
|
|
|
<p>将观众电子票二维码放入框内</p>
|
|
|
</div>
|
|
|
</section>
|
|
|
|
|
|
<section class="mobile-checkin-content">
|
|
|
<div class="mobile-live-stats">
|
|
|
<div><span>已核销</span><strong id="mobileChecked">168</strong></div>
|
|
|
<i></i>
|
|
|
<div><span>已报名</span><strong id="mobileRegistered">214</strong></div>
|
|
|
<i></i>
|
|
|
<div><span>到场率</span><strong id="mobileRate">78.5%</strong></div>
|
|
|
</div>
|
|
|
|
|
|
<div class="mobile-manual-entry">
|
|
|
<label for="mobileTicketInput">无法扫码?手动输入票号</label>
|
|
|
<div><input id="mobileTicketInput" type="text" value="AUD-SH-2026-0214" autocomplete="off" /><button id="mobileCheckBtn" type="button">核销</button></div>
|
|
|
</div>
|
|
|
|
|
|
<div id="mobileResult" class="mobile-checkin-result d-none" role="status" aria-live="polite">
|
|
|
<div class="mobile-result-icon"></div>
|
|
|
<div><strong id="mobileResultTitle"></strong><p id="mobileResultText"></p></div>
|
|
|
<button id="closeResult" type="button" aria-label="关闭结果">×</button>
|
|
|
</div>
|
|
|
|
|
|
<div class="mobile-recent-head"><h2>最近核销</h2><span>实时更新</span></div>
|
|
|
<div id="recentCheckins" class="mobile-recent-list">
|
|
|
<article><span>李可欣<small>18624685031</small></span><time>13:32</time></article>
|
|
|
<article><span>陈阳<small>13935767612</small></span><time>13:28</time></article>
|
|
|
<article><span>赵一帆<small>13568243948</small></span><time>13:19</time></article>
|
|
|
</div>
|
|
|
</section>
|
|
|
</main>
|
|
|
|
|
|
<script>
|
|
|
const sessionPresets = {
|
|
|
shanghai: { name:'上海预赛路演', date:'2026-09-18', registered:214, checked:168 },
|
|
|
suzhou: { name:'苏州预赛路演', date:'2026-10-30', registered:198, checked:142 },
|
|
|
shenzhen: { name:'深圳预赛路演', date:'2026-12-25', registered:126, checked:81 },
|
|
|
final: { name:'苏州总决赛', date:'2027-03-19', registered:347, checked:77 }
|
|
|
};
|
|
|
const activeSession = sessionPresets[new URLSearchParams(location.search).get('session')] || sessionPresets.shanghai;
|
|
|
const today = new Date(); today.setHours(0,0,0,0);
|
|
|
if (new Date(activeSession.date + 'T23:59:59') < today) {
|
|
|
document.body.innerHTML = '<main class="mobile-checkin-shell mobile-expired-shell"><section><span>核销通道已关闭</span><h1>该场次已过期</h1><p>' + activeSession.name + ' · ' + activeSession.date + '</p><a href="./admin-audience.html">返回核销管理</a></section></main>';
|
|
|
throw new Error('Expired check-in session');
|
|
|
}
|
|
|
document.getElementById('mobileSessionName').textContent = activeSession.name;
|
|
|
document.getElementById('mobileChecked').textContent = activeSession.checked;
|
|
|
document.getElementById('mobileRegistered').textContent = activeSession.registered;
|
|
|
document.getElementById('mobileRate').textContent = (activeSession.checked / activeSession.registered * 100).toFixed(1) + '%';
|
|
|
const tickets = {
|
|
|
'AUD-SH-2026-0214': { name:'王晓宁', phone:'13812342187', used:false },
|
|
|
'AUD-SH-2026-0213': { name:'李可欣', phone:'18624685031', used:true, time:'13:32' }
|
|
|
};
|
|
|
Object.keys(localStorage).filter(key => key.startsWith('audienceRegistrations:')).forEach(function (key) {
|
|
|
try {
|
|
|
(JSON.parse(localStorage.getItem(key)) || []).forEach(function (registration) {
|
|
|
if (!registration.ticketCode) return;
|
|
|
tickets[registration.ticketCode] = {
|
|
|
name: registration.holderName || '观众',
|
|
|
phone: registration.phone || key.split(':')[1],
|
|
|
used: localStorage.getItem('audienceTicketChecked:' + registration.ticketCode) === 'true'
|
|
|
};
|
|
|
});
|
|
|
} catch (error) {}
|
|
|
});
|
|
|
const result = document.getElementById('mobileResult');
|
|
|
function showResult(type, title, text) {
|
|
|
result.className = 'mobile-checkin-result is-' + type;
|
|
|
document.getElementById('mobileResultTitle').textContent = title;
|
|
|
document.getElementById('mobileResultText').textContent = text;
|
|
|
}
|
|
|
document.getElementById('mobileCheckBtn').addEventListener('click', function () {
|
|
|
const code = document.getElementById('mobileTicketInput').value.trim().toUpperCase();
|
|
|
const ticket = tickets[code];
|
|
|
if (!ticket) return showResult('error', '票码无效', '未查询到该电子票,请核对后重试');
|
|
|
if (ticket.used) return showResult('warning', '请勿重复入场', ticket.name + ' 的电子票已于 ' + ticket.time + ' 核销');
|
|
|
ticket.used = true;
|
|
|
localStorage.setItem('audienceTicketChecked:' + code, 'true');
|
|
|
const now = new Date().toLocaleTimeString('zh-CN',{hour:'2-digit',minute:'2-digit',hour12:false});
|
|
|
ticket.time = now;
|
|
|
const checked = document.getElementById('mobileChecked'); checked.textContent = Number(checked.textContent) + 1;
|
|
|
document.getElementById('mobileRate').textContent = (Number(checked.textContent) / activeSession.registered * 100).toFixed(1) + '%';
|
|
|
document.getElementById('recentCheckins').insertAdjacentHTML('afterbegin','<article><span>'+ticket.name+'<small>'+ticket.phone+'</small></span><time>'+now+'</time></article>');
|
|
|
showResult('success', '核销成功', ticket.name + ' · ' + ticket.phone + ',可入场');
|
|
|
});
|
|
|
document.getElementById('closeResult').addEventListener('click', () => result.classList.add('d-none'));
|
|
|
document.getElementById('flashBtn').addEventListener('click', function(){ const on=this.getAttribute('aria-pressed')!=='true'; this.setAttribute('aria-pressed',String(on)); this.classList.toggle('is-on',on); this.textContent=on?'已开灯':'闪光灯'; });
|
|
|
</script>
|
|
|
</body>
|
|
|
</html>
|