Files
contraband_manager/templates/index.html
2026-02-06 20:40:41 +00:00

535 lines
15 KiB
HTML
Raw Permalink 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>
:root {
--main-red: #ff4d4f;
--gold: #faad14;
--silver: #76838b;
--bronze: #d48806;
}
body {
font-family: "PingFang SC", -apple-system, BlinkMacSystemFont, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
margin: 0;
padding: 20px;
min-height: 100vh;
}
.hot-list-card {
max-width: 600px;
margin: 0 auto;
background: #fff;
border-radius: 20px;
box-shadow: 0 20px 60px rgba(0,0,0,0.3);
overflow: hidden;
}
/* 头部样式 */
.header {
padding: 25px 20px;
background: linear-gradient(135deg, #1a1a1a 0%, #2d2d2d 100%);
color: #fff;
text-align: center;
}
.header h1 {
margin: 0;
font-size: 22px;
font-weight: 700;
letter-spacing: 1px;
}
.header .subtitle {
margin-top: 5px;
font-size: 13px;
color: #8c8c8c;
}
/* Tabs 样式 */
.tabs {
display: flex;
background: #262626;
padding: 8px;
gap: 5px;
}
.tab-btn {
flex: 1;
padding: 12px 10px;
color: #8c8c8c;
border: none;
background: none;
cursor: pointer;
transition: all 0.3s ease;
font-size: 15px;
font-weight: 600;
border-radius: 10px;
position: relative;
}
.tab-btn:hover {
color: #bfbfbf;
background: rgba(255,255,255,0.05);
}
.tab-btn.active {
color: #fff;
background: var(--main-red);
box-shadow: 0 4px 15px rgba(255, 77, 79, 0.4);
}
.tab-btn.active::after {
content: '';
position: absolute;
bottom: 2px;
left: 50%;
transform: translateX(-50%);
width: 20px;
height: 3px;
background: #fff;
border-radius: 2px;
}
/* 列表容器 */
.list-container {
min-height: 400px;
padding: 10px 0;
background: #fafafa;
}
.empty-state {
text-align: center;
padding: 60px 20px;
color: #999;
}
.empty-state .icon {
font-size: 48px;
margin-bottom: 10px;
}
/* 列表项样式 */
.item {
display: flex;
align-items: center;
padding: 16px 20px;
border-bottom: 1px solid #f0f0f0;
cursor: pointer;
transition: all 0.3s ease;
animation: fadeInUp 0.5s ease backwards;
position: relative;
}
.item:hover {
background: #f8f9fa;
transform: translateX(5px);
}
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
/* 排名标签 */
.rank-tag {
width: 45px;
height: 45px;
display: flex;
align-items: center;
justify-content: center;
font-size: 20px;
font-weight: 900;
font-family: Arial, sans-serif;
border-radius: 50%;
background: linear-gradient(135deg, #e0e0e0 0%, #bdbdbd 100%);
color: #666;
flex-shrink: 0;
}
.rank-1 {
background: linear-gradient(135deg, #ffd700 0%, #ffb800 100%);
color: #fff;
box-shadow: 0 4px 15px rgba(250, 173, 20, 0.5);
}
.rank-2 {
background: linear-gradient(135deg, #c0c0c0 0%, #a0a0a0 100%);
color: #fff;
box-shadow: 0 4px 15px rgba(118, 131, 139, 0.5);
}
.rank-3 {
background: linear-gradient(135deg, #cd7f32 0%, #b87333 100%);
color: #fff;
box-shadow: 0 4px 15px rgba(212, 136, 6, 0.5);
}
/* 信息区域 */
.info {
flex: 1;
margin-left: 15px;
}
.name {
font-size: 17px;
font-weight: 600;
color: #262626;
margin-bottom: 4px;
}
.meta {
font-size: 12px;
color: #8c8c8c;
}
/* 分数区域 */
.score-area {
text-align: right;
flex-shrink: 0;
}
.count-num {
font-size: 24px;
font-weight: 800;
color: var(--main-red);
line-height: 1;
}
.count-label {
font-size: 11px;
color: #999;
margin-top: 2px;
}
/* 弹窗样式 */
.modal {
display: none;
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0,0,0,0.6);
z-index: 1000;
align-items: center;
justify-content: center;
animation: fadeIn 0.3s ease;
}
.modal.active {
display: flex;
}
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
.modal-content {
background: #fff;
border-radius: 20px;
max-width: 400px;
width: 90%;
max-height: 70vh;
overflow: hidden;
animation: slideUp 0.3s ease;
box-shadow: 0 20px 60px rgba(0,0,0,0.4);
}
@keyframes slideUp {
from {
opacity: 0;
transform: translateY(50px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.modal-header {
padding: 20px;
background: linear-gradient(135deg, var(--main-red) 0%, #ff7a45 100%);
color: #fff;
display: flex;
justify-content: space-between;
align-items: center;
}
.modal-header h2 {
margin: 0;
font-size: 18px;
}
.close-btn {
background: none;
border: none;
color: #fff;
font-size: 28px;
cursor: pointer;
padding: 0;
width: 32px;
height: 32px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
transition: background 0.3s;
}
.close-btn:hover {
background: rgba(255,255,255,0.2);
}
.modal-body {
padding: 20px;
overflow-y: auto;
max-height: 50vh;
}
.detail-item {
padding: 12px 0;
border-bottom: 1px solid #f0f0f0;
animation: fadeInDetail 0.3s ease backwards;
}
@keyframes fadeInDetail {
from { opacity: 0; transform: translateX(-10px); }
to { opacity: 1; transform: translateX(0); }
}
.detail-time {
font-size: 12px;
color: #8c8c8c;
margin-bottom: 4px;
}
.detail-content {
font-size: 14px;
color: #262626;
line-height: 1.5;
}
.highlight {
color: #fff;
background: var(--main-red);
padding: 2px 6px;
border-radius: 4px;
font-weight: 600;
}
/* 数字滚动动画 */
.count-animate {
animation: countPop 0.5s cubic-bezier(0.68, -0.55, 0.265, 1.55);
}
@keyframes countPop {
0% { transform: scale(0.5); opacity: 0; }
50% { transform: scale(1.2); }
100% { transform: scale(1); opacity: 1; }
}
/* 响应式 */
@media (max-width: 480px) {
.hot-list-card {
margin: 0;
border-radius: 0;
}
.rank-tag {
width: 38px;
height: 38px;
font-size: 17px;
}
.name {
font-size: 16px;
}
.count-num {
font-size: 20px;
}
}
</style>
</head>
<body>
<div class="hot-list-card">
<div class="header">
<h1>🏆 违禁品查获排行榜</h1>
<div class="subtitle">实时统计 · 动态展示</div>
</div>
<div class="tabs">
<button class="tab-btn active" data-tab="month">本月</button>
<button class="tab-btn" data-tab="all">总榜</button>
</div>
<div class="list-container" id="listContainer">
<!-- 列表将通过 JS 动态生成 -->
</div>
</div>
<!-- 详情弹窗 -->
<div class="modal" id="detailModal">
<div class="modal-content">
<div class="modal-header">
<h2 id="modalTitle">姓名</h2>
<button class="close-btn" onclick="closeModal()">×</button>
</div>
<div class="modal-body" id="modalBody">
<!-- 详情将通过 JS 动态生成 -->
</div>
</div>
</div>
<script id="data-json" type="application/json">{{ data_json|safe }}</script>
<script>
// 全局数据
let allData = {};
let currentTab = 'month';
// DOM加载完成后初始化
document.addEventListener('DOMContentLoaded', function() {
// 加载数据
const dataJsonElement = document.getElementById('data-json');
if (dataJsonElement) {
allData = JSON.parse(dataJsonElement.textContent);
// 初始渲染
renderList('month');
}
});
// Tab 切换
document.querySelectorAll('.tab-btn').forEach(btn => {
btn.addEventListener('click', function() {
const tab = this.dataset.tab;
// 更新按钮状态
document.querySelectorAll('.tab-btn').forEach(b => b.classList.remove('active'));
this.classList.add('active');
// 切换数据
currentTab = tab;
renderList(tab);
});
});
// 渲染列表
function renderList(tab) {
const container = document.getElementById('listContainer');
const data = allData[tab] || [];
if (data.length === 0) {
container.innerHTML = `
<div class="empty-state">
<div class="icon">📭</div>
<div>暂无数据</div>
</div>
`;
return;
}
let html = '';
data.forEach((item, index) => {
const rank = index + 1;
const rankClass = rank <= 3 ? `rank-${rank}` : '';
html += `
<div class="item" style="animation-delay: ${index * 0.05}s" onclick="showDetail('${item.name}')">
<div class="rank-tag ${rankClass}">${rank}</div>
<div class="info">
<div class="name">${item.name}</div>
<div class="meta">${item.shift}</div>
</div>
<div class="score-area">
<div class="count-num${index === 0 ? ' count-animate' : ''}">${item.score}</div>
<div class="count-label">票</div>
</div>
</div>
`;
});
container.innerHTML = html;
}
// 显示详情
function showDetail(name) {
const data = allData[currentTab] || [];
const user = data.find(u => u.name === name);
if (!user) return;
document.getElementById('modalTitle').textContent = user.name;
const body = document.getElementById('modalBody');
if (user.details && user.details.length > 0) {
let html = '';
user.details.forEach((detail, index) => {
const time = new Date(detail.time).toLocaleString('zh-CN', {
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit'
});
// 高亮关键词
let content = detail.content
.replace(/(打火机|火苗|压力罐|仿真枪|管制刀具|不明药丸|不明液体|精神药品|流通货币)/g,
'<span class="highlight">$1</span>');
html += `
<div class="detail-item" style="animation-delay: ${index * 0.05}s">
<div class="detail-time">${time}</div>
<div class="detail-content">${content}</div>
</div>
`;
});
body.innerHTML = html;
} else {
body.innerHTML = '<div class="detail-item">暂无详情记录</div>';
}
document.getElementById('detailModal').classList.add('active');
}
// 关闭弹窗
function closeModal() {
document.getElementById('detailModal').classList.remove('active');
}
// 点击弹窗外部关闭
document.getElementById('detailModal').addEventListener('click', function(e) {
if (e.target === this) {
closeModal();
}
});
// 定时刷新每15秒
setInterval(() => {
fetch('/api/rankings')
.then(res => res.json())
.then(data => {
allData = data;
renderList(currentTab);
})
.catch(err => console.error('刷新失败:', err));
}, 15000);
</script>
<!-- 数据传递到 JS -->
<script id="data-json" type="application/json">{{ data_json|safe }}</script>
</body>
</html>