xmg / public /index.html
Rould-Bot's picture
Update public/index.html
27b31cd verified
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>data fetch</title>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Arial;
background: linear-gradient(135deg, #eef2f7, #f8fafc);
margin: 0;
padding: 30px;
}
/* ===== 标题 ===== */
.title {
text-align: center;
margin-bottom: 10px;
}
/* ===== header ===== */
.header {
position: relative;
display: flex;
justify-content: center;
align-items: center;
margin-bottom: 18px;
min-height: 44px;
}
.btn-group {
display: flex;
gap: 12px;
}
.info-group {
position: absolute;
right: 0;
display: flex;
gap: 10px;
align-items: center;
}
button {
padding: 10px 18px;
border-radius: 10px;
border: none;
cursor: pointer;
font-size: 14px;
transition: 0.2s;
}
.primary {
background: #1677ff;
color: white;
}
.secondary {
background: #e5e7eb;
}
.auto-on {
background: #16a34a;
color: white;
box-shadow: 0 0 0 3px rgba(22,163,74,0.15);
}
button:disabled {
opacity: 0.6;
cursor: not-allowed;
}
#lastTime {
font-size: 13px;
background: #fff;
padding: 6px 10px;
border-radius: 999px;
box-shadow: 0 2px 6px rgba(0,0,0,0.06);
}
#status {
font-size: 13px;
color: #666;
}
/* ===== grid ===== */
.grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 18px;
}
.card {
background: white;
border-radius: 16px;
padding: 16px;
box-shadow: 0 6px 18px rgba(0,0,0,0.08);
display: flex;
flex-direction: column;
gap: 10px;
}
.time {
font-size: 12px;
color: #888;
}
.text {
font-size: 14px;
line-height: 1.6;
word-break: break-all;
}
.badges {
display: flex;
gap: 6px;
flex-wrap: wrap;
}
.badge {
font-size: 11px;
padding: 2px 8px;
border-radius: 999px;
font-weight: 600;
}
.badge.new {
transition: opacity 0.4s;
}
.new {
background: #dcfce7;
color: #166534;
}
.high {
background: #fee2e2;
color: #991b1b;
}
.copy-btn {
align-self: flex-end;
background: #f3f4f6;
font-size: 12px;
padding: 6px 10px;
}
.copy-btn:hover {
background: #e5e7eb;
}
/* ===== toast ===== */
.toast {
position: fixed;
bottom: 30px;
left: 50%;
transform: translateX(-50%);
background: #111;
color: white;
padding: 10px 18px;
border-radius: 999px;
font-size: 13px;
opacity: 0;
transition: 0.25s;
pointer-events: none;
}
.toast.show {
opacity: 1;
}
</style>
</head>
<body>
<h2 class="title">📊 Data Fetch</h2>
<div class="header">
<div class="btn-group">
<button id="loadBtn" class="primary" onclick="manualLoad()">拉取最新列表</button>
<button id="autoBtn" class="auto-on" onclick="toggleAuto()">自动拉取中</button>
</div>
<div class="info-group">
<div id="lastTime">最近拉取:--</div>
<div id="status">自动模式</div>
</div>
</div>
<div id="grid" class="grid"></div>
<div id="toast" class="toast">复制成功</div>
<script>
let cooldown = false;
let autoMode = true;
let autoTimer = null;
let seenSet = new Set();
/* ===== 工具 ===== */
function extractPrice(text) {
const m = text.match(/今天(\d+)块/);
return m ? parseInt(m[1]) : 0;
}
function showToast(msg="复制成功") {
const t = document.getElementById("toast");
t.innerText = msg;
t.classList.add("show");
setTimeout(()=>t.classList.remove("show"), 1200);
}
/* ===== 冷却 ===== */
function setCooldown(btn) {
cooldown = true;
let t = 5;
btn.disabled = true;
btn.innerText = `冷却 ${t}s`;
const timer = setInterval(() => {
t--;
btn.innerText = `冷却 ${t}s`;
if (t <= 0) {
clearInterval(timer);
btn.disabled = false;
btn.innerText = "拉取最新列表";
cooldown = false;
}
}, 1000);
}
/* ===== 请求 ===== */
async function loadData() {
const status = document.getElementById("status");
const last = document.getElementById("lastTime");
status.innerText = autoMode ? "自动拉取中…" : "加载中…";
try {
const res = await fetch("/api/data");
const data = await res.json();
// clearNewBadges();
render(data.posts);
last.innerText = "最近拉取:" + new Date().toLocaleTimeString();
status.innerText = autoMode ? "自动模式" : "完成";
} catch (e) {
status.innerText = "加载失败";
}
}
function clearNewBadges() {
document.querySelectorAll(".badge.new").forEach(b => b.remove());
}
/* ===== 渲染(只插入新数据)===== */
function render(posts) {
const grid = document.getElementById("grid");
// 先反转,保证接口里最新的在最前插入
posts.slice().reverse().forEach(p => {
const key = p.created_at + p.text;
if (seenSet.has(key)) return;
seenSet.add(key);
const price = extractPrice(p.text);
const badges = [];
badges.push(`<span class="badge new">NEW</span>`);
if (price > 850) badges.push(`<span class="badge high">高价</span>`);
const card = document.createElement("div");
card.className = "card";
card.dataset.insertTime = Date.now();
card.innerHTML = `
<div class="time">${p.created_at}</div>
<div class="badges">${badges.join("")}</div>
<div class="text">${p.text}</div>
<button class="copy-btn" onclick='copyText(${JSON.stringify(p.text)})'>复制</button>
`;
grid.prepend(card);
});
}
/* ===== 复制 ===== */
function copyText(t) {
navigator.clipboard.writeText(t);
showToast();
}
/* ===== 手动 ===== */
function manualLoad() {
if (cooldown) return;
const btn = document.getElementById("loadBtn");
setCooldown(btn);
loadData();
}
/* ===== 自动模式 ===== */
function toggleAuto() {
autoMode = !autoMode;
const btn = document.getElementById("autoBtn");
if (autoMode) {
btn.innerText = "自动拉取中";
btn.classList.add("auto-on");
loadData();
autoTimer = setInterval(loadData, 5000);
} else {
btn.innerText = "开启自动拉取";
btn.classList.remove("auto-on");
clearInterval(autoTimer);
}
}
/* ===== 页面加载即启动自动模式 ===== */
loadData();
autoTimer = setInterval(loadData, 5000);
const NEW_KEEP_SECONDS = 15;
setInterval(() => {
const now = Date.now();
document.querySelectorAll(".card").forEach(card => {
const t = parseInt(card.dataset.insertTime || 0);
if (!t) return;
if (now - t > NEW_KEEP_SECONDS * 1000) {
const badge = card.querySelector(".badge.new");
if (badge) {
badge.style.opacity = 0;
setTimeout(()=>badge.remove(), 400);
}
}
});
}, 1000);
</script>
</body>
</html>