327 lines
13 KiB
Plaintext
327 lines
13 KiB
Plaintext
<!DOCTYPE html>
|
||
<html lang="zh-CN">
|
||
|
||
<head>
|
||
<meta charset="UTF-8" />
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||
<title>001CODE-赛练平台</title>
|
||
<link rel="stylesheet" href="css/competition-v2.css?v=<%= htmlWebpackPlugin.options.version %>" media="screen">
|
||
<link rel="stylesheet" href="css/competition_sum-v2.css?v=<%= htmlWebpackPlugin.options.version %>" media="screen">
|
||
<script class="u-script" type="text/javascript" src="js/jquery.js?v=<%= htmlWebpackPlugin.options.version %>"
|
||
defer></script>
|
||
</head>
|
||
|
||
<body>
|
||
<div class="page">
|
||
|
||
<main class="content">
|
||
<!-- Left Column: Leaderboard -->
|
||
<div class="left-column" style="display:flex; flex-direction:column; gap:24px; min-width: 0; width: 100%;">
|
||
|
||
<section class="card ranking-card">
|
||
<header class="card-header">
|
||
<h1 class="title">
|
||
<svg class="trophy-icon" xmlns="http://www.w3.org/2000/svg" width="28" height="28" viewBox="0 0 24 24"
|
||
fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||
<path d="M6 9H4.5a2.5 2.5 0 0 1 0-5H6"></path>
|
||
<path d="M18 9h1.5a2.5 2.5 0 0 0 0-5H18"></path>
|
||
<path d="M4 22h16"></path>
|
||
<path d="M10 14.66V17c0 .55-.47.98-.97 1.21C7.85 18.75 7 20.24 7 22"></path>
|
||
<path d="M14 14.66V17c0 .55.47.98.97 1.21C16.15 18.75 17 20.24 17 22"></path>
|
||
<path d="M18 2H6v7a6 6 0 0 0 12 0V2Z"></path>
|
||
</svg>
|
||
排行榜
|
||
</h1>
|
||
<div class="refresh-info">
|
||
实时排行榜 <span id="refresh-countdown" class="refresh-timer">30s</span> <span id="time"
|
||
class="refresh-timer"></span>
|
||
</div>
|
||
</header>
|
||
|
||
<div class="table-container">
|
||
<table class="custom-table">
|
||
<thead>
|
||
<tr>
|
||
<th style="width: 60px; text-align: center;">排名</th>
|
||
<th>姓名</th>
|
||
<th>学校</th>
|
||
<th>完成数量</th>
|
||
<th>代码总数</th>
|
||
<th>角色总步数</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="leaderboard-body">
|
||
<!-- JS will populate -->
|
||
<tr>
|
||
<td colspan="6" style="text-align:center; padding: 20px; color: #94a3b8;">正在加载排行榜数据...</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
<!-- Search & Pagination moved inside Leaderboard Card -->
|
||
<div class="table-footer">
|
||
<div class="search-form-inline">
|
||
<label for="input-search">搜索用户:</label>
|
||
<input type="text" id="input-search" name="fullname" class="form-input-small" placeholder="输入姓名">
|
||
<button type="button" id="btn-search" class="form-btn-small">搜索</button>
|
||
<input type="hidden" id="email-2132">
|
||
</div>
|
||
|
||
<div id="page_num" class="pagination-container">
|
||
<!-- JS populates this -->
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- User Rank Table -->
|
||
<section class="card ranking-card">
|
||
<div class="table-container">
|
||
<table class="custom-table">
|
||
<thead>
|
||
<tr>
|
||
<th style="width: 60px; text-align: center;">排名</th>
|
||
<th>姓名</th>
|
||
<th>学校</th>
|
||
<th>完成数量</th>
|
||
<th>代码总数</th>
|
||
<th>角色总步数</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="user-rank-body">
|
||
<tr>
|
||
<td class="rank-cell">-</td>
|
||
<td>-</td>
|
||
<td>-</td>
|
||
<td>-</td>
|
||
<td>-</td>
|
||
<td>-</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</section>
|
||
</div>
|
||
|
||
<!-- Right Column: Sidebar -->
|
||
<div class="right-column" style="display:flex; flex-direction:column; gap:24px;">
|
||
|
||
<!-- Countdown & Actions -->
|
||
<section class="card">
|
||
|
||
<h2 class="widget-title" id="countdown-title">
|
||
剩余时间
|
||
<span class="timer-icon"></span>
|
||
</h2>
|
||
<div id="custom-countdown" class="countdown-container">
|
||
<div class="countdown-item"><span class="countdown-value" id="countdown-days">00</span><span
|
||
class="countdown-label">日</span></div>
|
||
<div class="countdown-separator">:</div>
|
||
<div class="countdown-item"><span class="countdown-value" id="countdown-hours">00</span><span
|
||
class="countdown-label">时</span></div>
|
||
<div class="countdown-separator">:</div>
|
||
<div class="countdown-item"><span class="countdown-value" id="countdown-minutes">00</span><span
|
||
class="countdown-label">分</span></div>
|
||
<div class="countdown-separator">:</div>
|
||
<div class="countdown-item"><span class="countdown-value" id="countdown-seconds">00</span><span
|
||
class="countdown-label">秒</span></div>
|
||
</div>
|
||
|
||
<div class="action-buttons">
|
||
<button id="btn-start" class="full-width-btn u-btn-3">开始活动</button>
|
||
<button id="btn-exit" class="full-width-btn secondary">退出平台</button>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- Announcement -->
|
||
<section class="card">
|
||
<h2 class="widget-title">公告</h2>
|
||
<div class="announcement-box">
|
||
<a id="competition-announcement" style="text-decoration: none; color: inherit; display: block;"></a>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- Camera -->
|
||
<section class="card" style="display: none;">
|
||
<div id="camera-container" class="camera-wrapper">
|
||
<div class="camera-placeholder">摄像头画面</div>
|
||
</div>
|
||
<button id="btn-camera" class="full-width-btn">开启摄像头</button>
|
||
<p
|
||
style="font-size: 12px; color: #64748b; margin-top: 10px; text-align: center; display: flex; align-items: center; justify-content: center; gap: 4px;">
|
||
<span>📷</span> 摄像头在考试过程中将全程监考
|
||
</p>
|
||
</section>
|
||
|
||
</div>
|
||
</main>
|
||
|
||
<!-- Footer -->
|
||
<footer>
|
||
<div class="footer-line"></div>
|
||
<p class="footer-text">ICP备案号:<a href="https://beian.miit.gov.cn/" target="_blank" rel="noopener" style="color:#478ac9 ;">沪ICP备2026042325号</a>
|
||
公安备案号:<a href="https://beian.mps.gov.cn/#/query/webSearch?code=31011502407417" rel="noreferrer" target="_blank" style="color:#478ac9 ;"><img src="../images/gongan-beian.png" alt="" width="16" height="16" style="width:16px;height:16px;vertical-align:-3px;margin-right:4px;" />沪公网安备31011502407417号</a>
|
||
© 灵丌编程(001CODE.COM)</p>
|
||
</footer>
|
||
</div>
|
||
|
||
<!-- Floating Elements -->
|
||
<div id="back-to-main" class="floating-back-button">
|
||
<a href="/login.html" class="back-link">
|
||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
|
||
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||
<path d="m12 19-7-7 7-7" />
|
||
<path d="M19 12H5" />
|
||
</svg>
|
||
<span>返回主站</span>
|
||
</a>
|
||
</div>
|
||
|
||
<div id="floating-user-name" class="floating-user-name">
|
||
<div class="user-chip">
|
||
<span id="current-user-name-text"></span>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Confirm Start Modal -->
|
||
<div id="start-competition-modal" class="modal">
|
||
<div class="modal-content">
|
||
<div class="modal-header">
|
||
<h2>开始比赛确认</h2>
|
||
<span class="close-modal">×</span>
|
||
</div>
|
||
<div class="modal-body">
|
||
<!-- Content populated by JS or simple text -->
|
||
<p>确定要开始比赛吗?</p>
|
||
</div>
|
||
<div class="modal-footer">
|
||
<button id="confirm-start-competition" class="modal-btn confirm-btn">确认开始</button>
|
||
<button id="cancel-start-competition" class="modal-btn cancel-btn">取消</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Competition End / Alert Modal -->
|
||
<div id="competition-end-modal" class="modal">
|
||
<div class="modal-content">
|
||
<div class="modal-header">
|
||
<h2>提示</h2>
|
||
<span class="close-modal" id="competition-end-close">×</span>
|
||
</div>
|
||
<div class="modal-body">
|
||
<p id="competition-end-message">提示信息</p>
|
||
</div>
|
||
<div class="modal-footer">
|
||
<button id="competition-end-ok" class="modal-btn confirm-btn close-modal">确定</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
// Include drag logic for floating buttons (Adapted from original file)
|
||
document.addEventListener('DOMContentLoaded', function () {
|
||
// Logic for user name already in competition_sum.js?
|
||
// check competition_sum.js line 469. It sets 'current-user-name-text' content.
|
||
|
||
const floatingButton = document.getElementById('back-to-main');
|
||
setupDraggable(floatingButton);
|
||
|
||
const floatingUser = document.getElementById('floating-user-name');
|
||
setupDraggable(floatingUser);
|
||
|
||
function setupDraggable(element) {
|
||
if (!element) return;
|
||
let isDragging = false;
|
||
let startX, startY, startLeft, startTop;
|
||
|
||
element.addEventListener('mousedown', function (e) {
|
||
if (e.target.tagName === 'A' || e.target.closest('a')) return;
|
||
isDragging = true;
|
||
startX = e.clientX;
|
||
startY = e.clientY;
|
||
const rect = element.getBoundingClientRect();
|
||
startLeft = rect.left;
|
||
startTop = rect.top;
|
||
element.style.position = 'fixed';
|
||
element.style.left = startLeft + 'px';
|
||
element.style.top = startTop + 'px';
|
||
element.style.right = 'auto'; // Disable right positioning after drag starts
|
||
element.style.bottom = 'auto';
|
||
document.addEventListener('mousemove', handleMouseMove);
|
||
document.addEventListener('mouseup', handleMouseUp);
|
||
e.preventDefault();
|
||
});
|
||
|
||
function handleMouseMove(e) {
|
||
if (!isDragging) return;
|
||
const deltaX = e.clientX - startX;
|
||
const deltaY = e.clientY - startY;
|
||
let newLeft = startLeft + deltaX;
|
||
let newTop = startTop + deltaY;
|
||
|
||
const rect = element.getBoundingClientRect();
|
||
newLeft = Math.max(0, Math.min(newLeft, window.innerWidth - rect.width));
|
||
newTop = Math.max(0, Math.min(newTop, window.innerHeight - rect.height));
|
||
|
||
element.style.left = newLeft + 'px';
|
||
element.style.top = newTop + 'px';
|
||
}
|
||
|
||
function handleMouseUp() {
|
||
isDragging = false;
|
||
document.removeEventListener('mousemove', handleMouseMove);
|
||
document.removeEventListener('mouseup', handleMouseUp);
|
||
}
|
||
|
||
// Touch support (basic)
|
||
element.addEventListener('touchstart', function (e) {
|
||
if (e.target.tagName === 'A' || e.target.closest('a')) return;
|
||
isDragging = true;
|
||
const touch = e.touches[0];
|
||
startX = touch.clientX;
|
||
startY = touch.clientY;
|
||
const rect = element.getBoundingClientRect();
|
||
startLeft = rect.left;
|
||
startTop = rect.top;
|
||
element.style.position = 'fixed';
|
||
element.style.left = startLeft + 'px';
|
||
element.style.top = startTop + 'px';
|
||
element.style.right = 'auto';
|
||
element.style.bottom = 'auto';
|
||
e.preventDefault();
|
||
}, { passive: false });
|
||
|
||
element.addEventListener('touchmove', function (e) {
|
||
if (!isDragging) return;
|
||
const touch = e.touches[0];
|
||
const deltaX = touch.clientX - startX;
|
||
const deltaY = touch.clientY - startY;
|
||
let newLeft = startLeft + deltaX;
|
||
let newTop = startTop + deltaY;
|
||
element.style.left = newLeft + 'px';
|
||
element.style.top = newTop + 'px';
|
||
e.preventDefault();
|
||
}, { passive: false });
|
||
|
||
element.addEventListener('touchend', function () { isDragging = false; });
|
||
}
|
||
|
||
// Modal close logic (competition-v2 style)
|
||
document.querySelectorAll('.close-modal, .cancel-btn').forEach(btn => {
|
||
btn.addEventListener('click', function () {
|
||
const modal = this.closest('.modal');
|
||
if (modal) modal.style.display = 'none';
|
||
});
|
||
});
|
||
|
||
// Close modal on outside click
|
||
window.addEventListener('click', function (e) {
|
||
if (e.target.classList.contains('modal')) {
|
||
e.target.style.display = 'none';
|
||
}
|
||
});
|
||
});
|
||
</script>
|
||
</body>
|
||
|
||
</html> |