bindbox-game/static/lottery_offline_verifier.html
邹方成 642b3cf7dd
Some checks failed
Build docker and publish / linux (1.24.5) (push) Failing after 50s
build: 更新前端构建产物和资源文件
更新了前端构建产物包括JavaScript、CSS和HTML文件,主要涉及以下变更:

1. 新增了多个组件和工具函数,包括异常页面组件、iframe组件等
2. 更新了活动管理、产品管理、优惠券管理等业务模块
3. 优化了构建配置和依赖管理
4. 修复了一些样式和功能问题
5. 更新了测试相关文件

同时更新了部分后端服务接口和测试用例。这些变更主要是为了支持新功能和改进现有功能的用户体验。
2025-11-21 01:24:13 +08:00

191 lines
7.0 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>离线抽奖验证器</title>
<style>
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', sans-serif; margin: 24px; }
h1 { font-size: 20px; margin-bottom: 8px; }
.card { border: 1px solid #ddd; border-radius: 8px; padding: 16px; margin-bottom: 16px; }
.row { display: flex; gap: 12px; align-items: center; margin: 8px 0; }
input[type="text"], input[type="number"] { padding: 8px; border: 1px solid #ccc; border-radius: 6px; width: 280px; }
button { padding: 8px 12px; border: none; border-radius: 6px; background: #1677ff; color: #fff; cursor: pointer; }
button:disabled { background: #9dbdff; cursor: not-allowed; }
table { width: 100%; border-collapse: collapse; margin-top: 12px; }
th, td { border: 1px solid #eee; padding: 8px; text-align: left; font-size: 13px; }
th { background: #fafafa; }
.muted { color: #666; font-size: 12px; }
.ok { color: #0a7; }
.bad { color: #d33; }
.flex { display: flex; gap: 8px; align-items: center; }
</style>
</head>
<body>
<h1>离线抽奖验证器</h1>
<div class="card">
<div class="row">
<label>参与者文件</label>
<input id="file" type="file" accept="text/plain,.txt,.csv" />
</div>
<div class="row">
<label>随机种子</label>
<input id="seed" type="text" placeholder="粘贴主办方公布的seed" />
</div>
<div class="row">
<label>中奖人数 N</label>
<input id="topN" type="number" min="1" value="1" />
</div>
<div class="row flex">
<button id="run" disabled>计算并显示结果</button>
<button id="export" disabled>导出中奖名单</button>
<span id="status" class="muted"></span>
</div>
</div>
<div class="card">
<div class="row">
<label>查询我的 user_id</label>
<input id="userId" type="text" placeholder="输入报名ID/账号/编号" />
<button id="check" disabled>查询排名</button>
</div>
<div id="checkResult" class="muted"></div>
</div>
<div class="card">
<div class="flex">
<strong>结果列表</strong>
<span id="meta" class="muted"></span>
</div>
<table id="table">
<thead>
<tr><th>#</th><th>user_id</th><th>hash(SHA-256)</th></tr>
</thead>
<tbody></tbody>
</table>
</div>
<script>
const state = { users: [], seed: '', topN: 1, results: [] };
async function sha256Hex(text) {
const encoder = new TextEncoder();
const data = encoder.encode(text);
const hashBuffer = await crypto.subtle.digest('SHA-256', data);
const bytes = new Uint8Array(hashBuffer);
const hex = Array.from(bytes).map(b => b.toString(16).padStart(2, '0')).join('');
return hex;
}
function parseUsers(text) {
const lines = text.split(/\r?\n/).map(s => s.trim()).filter(Boolean);
const users = [];
for (const line of lines) {
const parts = line.split(',');
const id = parts[0].trim();
if (id) users.push(id);
}
return Array.from(new Set(users));
}
function setStatus(msg, ok) {
const el = document.getElementById('status');
el.textContent = msg;
el.className = ok ? 'ok' : 'bad';
}
function refreshButtons() {
const canRun = state.users.length > 0 && state.seed.length > 0 && state.topN > 0;
document.getElementById('run').disabled = !canRun;
document.getElementById('check').disabled = state.results.length === 0;
document.getElementById('export').disabled = state.results.length === 0;
}
function renderTable(rows) {
const tbody = document.querySelector('#table tbody');
tbody.innerHTML = '';
const frag = document.createDocumentFragment();
for (let i = 0; i < rows.length; i++) {
const tr = document.createElement('tr');
const rank = document.createElement('td');
rank.textContent = String(i + 1);
const uid = document.createElement('td');
uid.textContent = rows[i].userId;
const hx = document.createElement('td');
hx.textContent = rows[i].hash;
tr.appendChild(rank);
tr.appendChild(uid);
tr.appendChild(hx);
frag.appendChild(tr);
}
tbody.appendChild(frag);
document.getElementById('meta').textContent = `合计 ${rows.length} 人,前 ${state.topN} 为中奖`;
}
function exportWinners() {
const winners = state.results.slice(0, state.topN).map(r => `${r.userId},${r.hash}`).join('\n');
const blob = new Blob([winners], { type: 'text/plain;charset=utf-8' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'winners.txt';
document.body.appendChild(a);
a.click();
a.remove();
URL.revokeObjectURL(url);
}
document.getElementById('file').addEventListener('change', async (e) => {
const f = e.target.files && e.target.files[0];
if (!f) return;
const text = await f.text();
state.users = parseUsers(text);
setStatus(`已载入参与者 ${state.users.length}`, true);
refreshButtons();
});
document.getElementById('seed').addEventListener('input', (e) => {
state.seed = e.target.value.trim();
refreshButtons();
});
document.getElementById('topN').addEventListener('input', (e) => {
const v = Number(e.target.value);
state.topN = Number.isFinite(v) && v > 0 ? v : 1;
refreshButtons();
});
document.getElementById('run').addEventListener('click', async () => {
setStatus('计算中…', true);
const pairs = [];
for (const userId of state.users) {
const h = await sha256Hex(userId + state.seed);
pairs.push({ userId, hash: h });
}
pairs.sort((a, b) => a.hash.localeCompare(b.hash));
state.results = pairs;
renderTable(pairs);
setStatus('计算完成', true);
refreshButtons();
});
document.getElementById('check').addEventListener('click', () => {
const uid = document.getElementById('userId').value.trim();
if (!uid) return;
const idx = state.results.findIndex(x => x.userId === uid);
if (idx === -1) {
document.getElementById('checkResult').textContent = '该 user_id 不在参与列表中';
document.getElementById('checkResult').className = 'bad';
return;
}
const rank = idx + 1;
const win = rank <= state.topN;
const txt = `排名:${rank}${win ? '中奖' : '未中奖'},哈希:${state.results[idx].hash}`;
document.getElementById('checkResult').textContent = txt;
document.getElementById('checkResult').className = win ? 'ok' : 'muted';
});
document.getElementById('export').addEventListener('click', exportWinners);
</script>
</body>
</html>