You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

113 lines
6.5 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<!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>