first commit

This commit is contained in:
dekun
2026-05-22 13:06:42 +08:00
commit af5c249cf8
27 changed files with 1741 additions and 0 deletions
+91
View File
@@ -0,0 +1,91 @@
const REFRESH_MS = 60_000;
function formatPeriod(start, end) {
const fmt = (s) => s.replace("T", " ").slice(0, 16);
return `${fmt(start)} ~ ${fmt(end)}`;
}
function renderTags(row) {
const parts = [];
if (row.is_high_volume) {
parts.push('<span class="tag tag-vol">千万+</span>');
}
if (row.is_high_change) {
parts.push('<span class="tag tag-chg">涨跌5%+</span>');
}
return parts.length ? parts.join("") : "—";
}
function pctClass(pct) {
if (pct > 0) return "pct-up";
if (pct < 0) return "pct-down";
return "";
}
function renderTable(tbody, items) {
if (!items || !items.length) {
tbody.innerHTML = '<tr><td colspan="5" class="loading">暂无数据</td></tr>';
return;
}
tbody.innerHTML = items
.map((row) => {
const highlight =
row.is_high_volume || row.is_high_change ? " row-highlight" : "";
const pct = row.price_change_pct ?? 0;
return `<tr class="${highlight}">
<td class="rank">${row.rank}</td>
<td><strong>${row.symbol}</strong></td>
<td>${row.quote_volume_fmt || row.quote_volume}</td>
<td class="${pctClass(pct)}">${row.price_change_pct_fmt || pct.toFixed(2) + "%"}</td>
<td>${renderTags(row)}</td>
</tr>`;
})
.join("");
}
async function loadYesterday() {
const body = document.getElementById("yesterday-body");
body.innerHTML = '<tr><td colspan="5" class="loading">加载中…</td></tr>';
try {
const res = await fetch("/api/yesterday/top30");
const data = await res.json();
document.getElementById("yesterday-period").textContent = formatPeriod(
data.period_start,
data.period_end
);
document.getElementById("yesterday-updated").textContent =
"更新: " + (data.updated_at || "").replace("T", " ").slice(0, 19);
renderTable(body, data.items);
} catch (e) {
body.innerHTML = `<tr><td colspan="5" class="error">加载失败: ${e.message}</td></tr>`;
}
}
async function loadToday() {
const body = document.getElementById("today-body");
try {
const res = await fetch("/api/today/top30");
const data = await res.json();
document.getElementById("today-period").textContent = formatPeriod(
data.period_start,
data.period_end
);
document.getElementById("today-updated").textContent =
"更新: " + (data.updated_at || "").replace("T", " ").slice(0, 19);
renderTable(body, data.items);
document.getElementById("status").textContent = "今日数据已刷新";
} catch (e) {
body.innerHTML = `<tr><td colspan="5" class="error">加载失败: ${e.message}</td></tr>`;
document.getElementById("status").textContent = e.message;
}
}
document.getElementById("btn-refresh").addEventListener("click", async () => {
document.getElementById("status").textContent = "刷新中…";
await fetch("/api/refresh/today", { method: "POST" });
await loadToday();
});
loadYesterday();
loadToday();
setInterval(loadToday, REFRESH_MS);
+66
View File
@@ -0,0 +1,66 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>币安 U本位 成交额 Top30</title>
<link rel="stylesheet" href="/static/style.css" />
</head>
<body>
<header>
<h1>币安 U本位合约 · 成交额排名</h1>
<p class="subtitle">北京时间 08:00 切日 · Top30 · 高亮:≥1000万 USDT / |涨跌|≥5%</p>
</header>
<section class="panel" id="panel-yesterday">
<div class="panel-head">
<h2>昨日周期</h2>
<span class="period" id="yesterday-period"></span>
<span class="updated" id="yesterday-updated"></span>
</div>
<div class="table-wrap">
<table>
<thead>
<tr>
<th>排名</th>
<th>合约</th>
<th>成交额 (USDT)</th>
<th>涨跌幅</th>
<th>标记</th>
</tr>
</thead>
<tbody id="yesterday-body"></tbody>
</table>
</div>
</section>
<section class="panel" id="panel-today">
<div class="panel-head">
<h2>今日周期 <span class="live">实时</span></h2>
<span class="period" id="today-period"></span>
<span class="updated" id="today-updated"></span>
</div>
<div class="table-wrap">
<table>
<thead>
<tr>
<th>排名</th>
<th>合约</th>
<th>成交额 (USDT)</th>
<th>涨跌幅</th>
<th>标记</th>
</tr>
</thead>
<tbody id="today-body"></tbody>
</table>
</div>
</section>
<footer>
<button type="button" id="btn-refresh">立即刷新今日</button>
<span id="status"></span>
</footer>
<script src="/static/app.js"></script>
</body>
</html>
+170
View File
@@ -0,0 +1,170 @@
:root {
--bg: #0f1419;
--panel: #1a2332;
--border: #2d3a4f;
--text: #e7ecf3;
--muted: #8b9cb3;
--accent: #f0b90b;
--up: #0ecb81;
--down: #f6465d;
--tag-vol: #3d5afe;
--tag-chg: #ff6d00;
}
* {
box-sizing: border-box;
}
body {
margin: 0;
font-family: "Segoe UI", system-ui, sans-serif;
background: var(--bg);
color: var(--text);
line-height: 1.5;
padding: 1.5rem;
max-width: 1100px;
margin-inline: auto;
}
header h1 {
margin: 0 0 0.25rem;
font-size: 1.5rem;
}
.subtitle {
color: var(--muted);
margin: 0 0 1.5rem;
font-size: 0.9rem;
}
.panel {
background: var(--panel);
border: 1px solid var(--border);
border-radius: 10px;
margin-bottom: 1.5rem;
overflow: hidden;
}
.panel-head {
display: flex;
flex-wrap: wrap;
align-items: baseline;
gap: 0.75rem 1.5rem;
padding: 1rem 1.25rem;
border-bottom: 1px solid var(--border);
}
.panel-head h2 {
margin: 0;
font-size: 1.1rem;
}
.live {
font-size: 0.75rem;
color: var(--accent);
font-weight: normal;
}
.period {
color: var(--muted);
font-size: 0.85rem;
}
.updated {
margin-left: auto;
color: var(--muted);
font-size: 0.8rem;
}
.table-wrap {
overflow-x: auto;
}
table {
width: 100%;
border-collapse: collapse;
font-size: 0.9rem;
}
th,
td {
padding: 0.65rem 1rem;
text-align: left;
border-bottom: 1px solid var(--border);
}
th {
color: var(--muted);
font-weight: 600;
font-size: 0.8rem;
}
tr:hover td {
background: rgba(255, 255, 255, 0.03);
}
.rank {
color: var(--accent);
font-weight: 600;
}
.pct-up {
color: var(--up);
}
.pct-down {
color: var(--down);
}
.tag {
display: inline-block;
padding: 0.15rem 0.45rem;
border-radius: 4px;
font-size: 0.72rem;
margin-right: 0.35rem;
}
.tag-vol {
background: rgba(61, 90, 254, 0.25);
color: #8fa8ff;
}
.tag-chg {
background: rgba(255, 109, 0, 0.25);
color: #ffb74d;
}
.row-highlight td {
background: rgba(240, 185, 11, 0.06);
}
footer {
display: flex;
align-items: center;
gap: 1rem;
color: var(--muted);
font-size: 0.85rem;
}
button {
background: var(--accent);
color: #000;
border: none;
padding: 0.5rem 1rem;
border-radius: 6px;
cursor: pointer;
font-weight: 600;
}
button:hover {
filter: brightness(1.05);
}
.loading {
color: var(--muted);
padding: 1rem;
}
.error {
color: var(--down);
}