Some checks failed
Build docker and publish / linux (1.24.5) (push) Failing after 50s
更新了前端构建产物包括JavaScript、CSS和HTML文件,主要涉及以下变更: 1. 新增了多个组件和工具函数,包括异常页面组件、iframe组件等 2. 更新了活动管理、产品管理、优惠券管理等业务模块 3. 优化了构建配置和依赖管理 4. 修复了一些样式和功能问题 5. 更新了测试相关文件 同时更新了部分后端服务接口和测试用例。这些变更主要是为了支持新功能和改进现有功能的用户体验。
191 lines
7.0 KiB
HTML
191 lines
7.0 KiB
HTML
<!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> |