This commit is contained in:
Ubuntu
2026-07-03 16:58:28 +08:00
commit 32b43f85bd
6240 changed files with 1987678 additions and 0 deletions
+938
View File
@@ -0,0 +1,938 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
<title>🎮 Net-Games 游戏大厅</title>
<script>
// 强制刷新缓存
if (!window.location.search.includes('v=2')) {
window.location.replace(window.location.pathname + '?v=2');
}
</script>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 50%, #0f3460 100%);
min-height: 100vh;
color: #fff;
overflow-x: hidden;
}
/* 头部 */
.header {
text-align: center;
padding: 40px 20px 30px;
background: rgba(0, 0, 0, 0.2);
backdrop-filter: blur(10px);
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
}
.header h1 {
font-size: 2.5rem;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
margin-bottom: 10px;
text-shadow: 0 0 30px rgba(102, 126, 234, 0.3);
}
.header p {
color: #888;
font-size: 1rem;
}
/* 游戏网格容器 */
.games-container {
max-width: 1600px;
margin: 0 auto;
padding: 30px 20px 60px;
}
.games-grid {
display: grid;
gap: 20px;
grid-template-columns: repeat(2, 1fr);
}
@media (min-width: 640px) {
.games-grid { grid-template-columns: repeat(3, 1fr); }
}
@media (min-width: 900px) {
.games-grid { grid-template-columns: repeat(4, 1fr); }
}
@media (min-width: 1200px) {
.games-grid { grid-template-columns: repeat(5, 1fr); }
}
/* 游戏卡片 */
.game-card {
position: relative;
background: linear-gradient(145deg, #1e293b 0%, #0f172a 100%);
border-radius: 16px;
overflow: hidden;
cursor: pointer;
transition: all 0.3s ease;
border: 1px solid rgba(255, 255, 255, 0.1);
aspect-ratio: 3/4;
display: flex;
flex-direction: column;
}
.game-card:hover {
transform: translateY(-8px) scale(1.02);
box-shadow: 0 20px 40px rgba(102, 126, 234, 0.3);
border-color: rgba(102, 126, 234, 0.5);
}
.game-card:active {
transform: translateY(-4px) scale(0.98);
}
.game-icon {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
font-size: 4rem;
background: linear-gradient(135deg, rgba(102, 126, 234, 0.2) 0%, rgba(118, 75, 162, 0.2) 100%);
position: relative;
overflow: hidden;
}
.game-icon::before {
content: '';
position: absolute;
top: -50%;
left: -50%;
width: 200%;
height: 200%;
background: radial-gradient(circle, rgba(255,255,255,0.1) 0%, transparent 60%);
animation: shimmer 3s infinite;
}
@keyframes shimmer {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.game-info {
padding: 15px;
background: rgba(0, 0, 0, 0.3);
}
.game-title {
font-size: 1rem;
font-weight: 600;
margin-bottom: 5px;
color: #fff;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.game-desc {
font-size: 0.75rem;
color: #888;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
line-height: 1.4;
}
.game-tag {
display: inline-block;
padding: 3px 8px;
background: rgba(102, 126, 234, 0.3);
border-radius: 12px;
font-size: 0.7rem;
color: #a5b4fc;
margin-top: 8px;
}
.game-score {
display: inline-block;
padding: 3px 10px;
background: linear-gradient(135deg, #f59e0b 0%, #d97706 100%);
border-radius: 12px;
font-size: 0.75rem;
color: #fff;
margin-top: 8px;
margin-left: 6px;
font-weight: 600;
}
.new-badge {
position: absolute;
top: 10px;
right: 10px;
background: linear-gradient(135deg, #ff6b6b 0%, #ee5a5a 100%);
color: #fff;
padding: 4px 10px;
border-radius: 12px;
font-size: 0.7rem;
font-weight: 600;
z-index: 10;
box-shadow: 0 4px 10px rgba(238, 90, 90, 0.4);
}
.hot-badge {
position: absolute;
top: 10px;
left: 10px;
background: linear-gradient(135deg, #f59e0b 0%, #d97706 100%);
color: #fff;
padding: 4px 10px;
border-radius: 12px;
font-size: 0.7rem;
font-weight: 600;
z-index: 10;
box-shadow: 0 4px 10px rgba(217, 119, 6, 0.4);
}
/* 页脚 */
.footer {
text-align: center;
padding: 30px 20px;
color: #666;
font-size: 0.85rem;
border-top: 1px solid rgba(255, 255, 255, 0.1);
}
/* 分类标题 */
.category-title {
font-size: 1.3rem;
margin: 30px 0 20px;
padding-left: 10px;
border-left: 4px solid #667eea;
color: #fff;
}
/* ========== 登录弹窗 ========== */
.login-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0,0,0,0.85);
z-index: 10000;
display: flex;
align-items: center;
justify-content: center;
backdrop-filter: blur(8px);
}
.login-overlay.hidden {
display: none !important;
}
.login-input.hidden {
display: none !important;
}
.login-box {
background: linear-gradient(145deg, #1e293b 0%, #0f172a 100%);
padding: 30px;
border-radius: 16px;
width: 90%;
max-width: 360px;
box-shadow: 0 20px 60px rgba(0,0,0,0.5);
border: 1px solid rgba(255, 255, 255, 0.1);
}
.login-title {
text-align: center;
font-size: 22px;
font-weight: 600;
margin-bottom: 8px;
color: #fff;
}
.login-subtitle {
text-align: center;
font-size: 14px;
color: #888;
margin-bottom: 24px;
}
.login-input {
width: 100%;
padding: 14px 16px;
margin-bottom: 12px;
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 10px;
font-size: 16px;
background: rgba(0, 0, 0, 0.3);
color: #fff;
transition: border-color 0.2s;
}
.login-input:focus {
outline: none;
border-color: #667eea;
}
.login-input::placeholder {
color: #666;
}
.login-btn {
width: 100%;
padding: 14px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border: none;
border-radius: 10px;
font-size: 16px;
font-weight: 600;
cursor: pointer;
transition: opacity 0.2s;
margin-top: 8px;
}
.login-btn:hover { opacity: 0.9; }
.login-btn:disabled {
background: #444;
cursor: not-allowed;
}
.login-error {
color: #ff6b6b;
font-size: 14px;
margin-top: 12px;
text-align: center;
display: none;
}
.login-error.show { display: block; }
.login-link {
text-align: center;
margin-top: 16px;
font-size: 14px;
color: #888;
}
.login-link a {
color: #667eea;
cursor: pointer;
text-decoration: none;
}
.login-link a:hover {
text-decoration: underline;
}
/* ========== 用户信息栏 ========== */
.user-bar {
position: fixed;
top: 10px;
right: 10px;
display: none;
align-items: center;
gap: 10px;
background: rgba(0, 0, 0, 0.5);
padding: 8px 16px;
border-radius: 20px;
backdrop-filter: blur(10px);
border: 1px solid rgba(255, 255, 255, 0.1);
z-index: 1000;
}
.user-bar.visible { display: flex; }
.user-info { font-size: 13px; color: #ccc; }
.user-name { color: #fff; font-weight: 600; }
.logout-btn {
padding: 4px 12px;
background: rgba(255, 59, 48, 0.8);
color: white;
border: none;
border-radius: 12px;
font-size: 12px;
cursor: pointer;
}
.logout-btn:hover { background: rgba(255, 59, 48, 1); }
.settings-btn {
padding: 4px 12px;
background: rgba(102, 126, 234, 0.8);
color: white;
border: none;
border-radius: 12px;
font-size: 12px;
cursor: pointer;
}
.settings-btn:hover { background: rgba(102, 126, 234, 1); }
@keyframes shake {
0%, 100% { transform: translateX(0); }
25% { transform: translateX(-5px); }
75% { transform: translateX(5px); }
}
</style>
</head>
<body>
<!-- 登录/注册弹窗 - 默认隐藏,由JS控制显示 -->
<div class="login-overlay hidden" id="loginOverlay" style="display: none;">
<div class="login-box">
<div class="login-title" id="authTitle">🔒 Net-Games</div>
<div class="login-subtitle" id="authSubtitle">请先登录后进入游戏大厅</div>
<input type="text" class="login-input" id="username" placeholder="用户名" autocomplete="username">
<input type="password" class="login-input" id="password" placeholder="密码" autocomplete="current-password">
<input type="password" class="login-input hidden" id="password2" placeholder="确认密码" autocomplete="new-password">
<button class="login-btn" id="authBtn" onclick="doAuth()">登录</button>
<div class="login-error" id="loginError"></div>
<div class="login-link">
<span id="authSwitchText">还没有账号?</span>
<a id="authSwitchLink" onclick="toggleAuthMode()">立即注册</a>
</div>
</div>
</div>
<!-- 用户信息栏 -->
<div class="user-bar" id="userBar">
<div class="user-info">
👤 <span class="user-name" id="userName"></span>
<span id="adminBadge" style="display: none; background: #ff6b6b; color: white; padding: 2px 6px; border-radius: 4px; font-size: 11px; margin-left: 6px;">管理员</span>
</div>
<button class="settings-btn" id="adminBtn" style="display: none; background: #ff6b6b;" onclick="showAdminPanel()">管理</button>
<button class="settings-btn" onclick="showSettingsModal()">设置</button>
<button class="logout-btn" onclick="doLogout()">退出</button>
</div>
<!-- 个人设置弹窗 -->
<div class="login-overlay hidden" id="settingsOverlay" style="display: none;">
<div class="login-box">
<div class="login-title">⚙️ 个人设置</div>
<div class="login-subtitle">修改昵称和密码</div>
<div style="margin-bottom: 20px; padding: 12px; background: rgba(102, 126, 234, 0.1); border-radius: 8px;">
<div style="font-size: 13px; color: #888;">用户名(不可修改)</div>
<div id="settingsUsername" style="font-size: 16px; color: #fff; font-weight: 600;"></div>
</div>
<input type="text" class="login-input" id="settingNickname" placeholder="昵称" maxlength="20">
<div style="margin: 20px 0; border-top: 1px solid rgba(255,255,255,0.1); padding-top: 20px;">
<div style="font-size: 14px; color: #888; margin-bottom: 12px;">🔒 修改密码(可选)</div>
<input type="password" class="login-input" id="settingOldPassword" placeholder="当前密码">
<input type="password" class="login-input" id="settingNewPassword" placeholder="新密码(至少6位)">
</div>
<button class="login-btn" onclick="saveSettings()">保存设置</button>
<div class="login-error" id="settingsError"></div>
<div class="login-link">
<a onclick="hideSettingsModal()">取消</a>
</div>
</div>
</div>
<header class="header">
<h1>🎮 Net-Games 游戏大厅</h1>
<p>Socket.IO 实时对战游戏平台</p>
<p style="font-size: 0.8rem; margin-top: 5px; opacity: 0.7;">支持人机对战、AI对战、多人联机</p>
</header>
<main class="games-container">
<h2 class="category-title">🔥 热门对战</h2>
<div class="games-grid" id="gamesGrid">
<!-- 游戏卡片动态生成 -->
</div>
<h2 class="category-title">🆕 即将上线</h2>
<div class="games-grid" id="comingSoonGrid">
<!-- 即将上线 -->
</div>
</main>
<footer class="footer">
<p>🎮 Net-Games 游戏大厅 | 实时对战,随时开局</p>
</footer>
<script>
// 游戏配置 - 添加新游戏只需修改这里
const games = [
{
id: 'doudizhu',
title: '🃏 斗地主',
desc: '经典三人扑克,支持人类 vs AI、AI vs AI 多种对战模式',
icon: '🃏',
url: '/doudizhu/',
tag: '扑克棋牌',
isNew: true,
isHot: true,
category: 'hot'
}
// 后续添加更多游戏...
// {
// id: 'chinese-chess',
// title: '♟️ 中国象棋',
// desc: '人机对弈,Minimax + DeepSeek AI',
// icon: '♟️',
// url: '/chinese-chess/',
// tag: '棋牌策略',
// isNew: false,
// isHot: true
// }
];
const comingSoonGames = [
{
id: 'chinese-chess',
title: '♟️ 中国象棋',
desc: '人机对弈,Minimax + DeepSeek AI(开发中)',
icon: '♟️',
url: '#',
tag: '即将上线',
disabled: true
},
{
id: 'guandan',
title: '🃏 掼蛋',
desc: '4人2v2对家组队(开发中)',
icon: '🃏',
url: '#',
tag: '即将上线',
disabled: true
}
];
let isAuthenticated = false;
// 用户各游戏积分
let userGameScores = {};
// 创建游戏卡片
function createGameCard(game) {
const badges = [];
if (game.isHot) badges.push('<span class="hot-badge">🔥 HOT</span>');
if (game.isNew) badges.push('<span class="new-badge">NEW</span>');
// 显示该游戏积分(如果已获取)
const score = userGameScores[game.id];
const scoreHtml = score !== undefined && !game.disabled
? `<div class="game-score">🏆 积分: ${score}</div>`
: '';
return `
<div class="game-card" data-id="${game.id}" data-url="${game.url}"
${game.disabled ? '' : 'onclick="handleGameClick(this)"'}
style="${game.disabled ? 'opacity: 0.6; cursor: not-allowed;' : ''}">
${badges.join('')}
<div class="game-icon">${game.icon}</div>
<div class="game-info">
<div class="game-title">${game.title}</div>
<div class="game-desc">${game.desc}</div>
<span class="game-tag">${game.tag}</span>
${scoreHtml}
</div>
</div>
`;
}
// 获取用户各游戏积分
async function fetchUserScores() {
if (!isAuthenticated) return;
try {
const response = await fetch('/api/user/scores', {
credentials: 'include'
});
const data = await response.json();
if (data.success) {
userGameScores = data.scores || {};
renderGames(); // 重新渲染以显示积分
}
} catch (error) {
console.error('获取积分失败:', error);
}
}
// 处理游戏点击
function handleGameClick(element) {
if (!isAuthenticated) {
element.style.animation = 'shake 0.3s';
setTimeout(() => element.style.animation = '', 300);
showLoginOverlay();
return;
}
const url = element.dataset.url;
window.location.href = url;
}
// 渲染游戏列表
function renderGames() {
const hotGames = games.filter(g => g.category === 'hot' || g.isHot);
document.getElementById('gamesGrid').innerHTML = hotGames.map(createGameCard).join('');
document.getElementById('comingSoonGrid').innerHTML = comingSoonGames.map(createGameCard).join('');
}
// ========== 登录相关 ==========
function showLoginOverlay() {
const overlay = document.getElementById('loginOverlay');
overlay.style.display = '';
overlay.classList.remove('hidden');
}
function hideLoginOverlay() {
const overlay = document.getElementById('loginOverlay');
overlay.classList.add('hidden');
// 保留内联样式确保隐藏
overlay.style.display = 'none';
}
function showUserBar(user) {
currentUser = user;
document.getElementById('userName').textContent = user.nickname || user.username;
document.getElementById('userBar').classList.add('visible');
// 显示管理员标记和按钮
if (user.is_admin || user.role === 'admin') {
document.getElementById('adminBadge').style.display = 'inline-block';
document.getElementById('adminBtn').style.display = 'inline-block';
} else {
document.getElementById('adminBadge').style.display = 'none';
document.getElementById('adminBtn').style.display = 'none';
}
}
function hideUserBar() {
document.getElementById('userBar').classList.remove('visible');
}
// ========== 管理员面板 ==========
function showAdminPanel() {
alert('🛠️ 管理员功能开发中...\n\n计划功能:\n• 查看在线统计\n• 管理房间列表\n• 强制关闭房间\n• 用户管理');
}
// ========== 个人设置 ==========
let currentUser = null;
function showSettingsModal() {
if (!currentUser) return;
document.getElementById('settingsUsername').textContent = currentUser.username;
document.getElementById('settingNickname').value = currentUser.nickname || currentUser.username;
document.getElementById('settingOldPassword').value = '';
document.getElementById('settingNewPassword').value = '';
document.getElementById('settingsError').classList.remove('show');
const overlay = document.getElementById('settingsOverlay');
overlay.style.display = '';
overlay.classList.remove('hidden');
}
function hideSettingsModal() {
const overlay = document.getElementById('settingsOverlay');
overlay.classList.add('hidden');
overlay.style.display = 'none';
}
async function saveSettings() {
const nickname = document.getElementById('settingNickname').value.trim();
const oldPassword = document.getElementById('settingOldPassword').value;
const newPassword = document.getElementById('settingNewPassword').value;
const errorDiv = document.getElementById('settingsError');
const btn = document.querySelector('#settingsOverlay .login-btn');
// 验证昵称
if (!nickname || nickname.length < 2 || nickname.length > 20) {
errorDiv.textContent = '昵称长度需在2-20个字符之间';
errorDiv.classList.add('show');
return;
}
// 验证密码(如果填写了)
if (newPassword && newPassword.length < 6) {
errorDiv.textContent = '新密码长度至少6位';
errorDiv.classList.add('show');
return;
}
if (newPassword && !oldPassword) {
errorDiv.textContent = '修改密码需要填写当前密码';
errorDiv.classList.add('show');
return;
}
btn.disabled = true;
btn.textContent = '保存中...';
try {
// 更新昵称
const profileRes = await fetch('/api/user/profile', {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
credentials: 'include',
body: JSON.stringify({ nickname })
});
const profileData = await profileRes.json();
if (!profileData.success) {
errorDiv.textContent = profileData.error || '更新昵称失败';
errorDiv.classList.add('show');
return;
}
// 更新密码(如果填写了)
if (newPassword) {
const pwdRes = await fetch('/api/user/password', {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
credentials: 'include',
body: JSON.stringify({ old_password: oldPassword, new_password: newPassword })
});
const pwdData = await pwdRes.json();
if (!pwdData.success) {
errorDiv.textContent = pwdData.error || '修改密码失败';
errorDiv.classList.add('show');
return;
}
// 密码修改成功,需要重新登录
alert('密码修改成功,请重新登录');
doLogout();
hideSettingsModal();
return;
}
// 更新本地用户信息
currentUser = profileData.user;
document.getElementById('userName').textContent = currentUser.nickname || currentUser.username;
hideSettingsModal();
} catch (error) {
errorDiv.textContent = '网络错误,请重试';
errorDiv.classList.add('show');
} finally {
btn.disabled = false;
btn.textContent = '保存设置';
}
}
async function checkAuth() {
try {
const response = await fetch('/api/auth/status', {
credentials: 'include',
cache: 'no-cache'
});
const data = await response.json();
isAuthenticated = data.authenticated;
console.log('登录状态:', isAuthenticated);
if (isAuthenticated) {
hideLoginOverlay();
showUserBar(data.user);
// 获取用户各游戏积分
await fetchUserScores();
} else {
// 未登录也可以浏览,但点击游戏需要登录
hideLoginOverlay();
}
} catch (error) {
console.error('检查登录状态失败:', error);
hideLoginOverlay();
}
}
let isRegisterMode = false;
function toggleAuthMode() {
isRegisterMode = !isRegisterMode;
const title = document.getElementById('authTitle');
const subtitle = document.getElementById('authSubtitle');
const btn = document.getElementById('authBtn');
const switchText = document.getElementById('authSwitchText');
const switchLink = document.getElementById('authSwitchLink');
const password2 = document.getElementById('password2');
const errorDiv = document.getElementById('loginError');
errorDiv.classList.remove('show');
if (isRegisterMode) {
title.textContent = '📝 注册账号';
subtitle.textContent = '创建新账号开始游戏';
btn.textContent = '注册';
switchText.textContent = '已有账号?';
switchLink.textContent = '立即登录';
password2.classList.remove('hidden');
} else {
title.textContent = '🔒 Net-Games';
subtitle.textContent = '请先登录后进入游戏大厅';
btn.textContent = '登录';
switchText.textContent = '还没有账号?';
switchLink.textContent = '立即注册';
password2.classList.add('hidden');
}
}
async function doAuth() {
if (isRegisterMode) {
await doRegister();
} else {
await doLogin();
}
}
async function doLogin() {
const username = document.getElementById('username').value.trim();
const password = document.getElementById('password').value.trim();
const btn = document.getElementById('authBtn');
const errorDiv = document.getElementById('loginError');
if (!username || !password) {
errorDiv.textContent = '请输入用户名和密码';
errorDiv.classList.add('show');
return;
}
btn.disabled = true;
btn.textContent = '登录中...';
try {
const response = await fetch('/api/auth/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
credentials: 'include',
body: JSON.stringify({ username, password })
});
const data = await response.json();
if (data.success) {
isAuthenticated = true;
hideLoginOverlay();
showUserBar(data.user);
document.getElementById('username').value = '';
document.getElementById('password').value = '';
// 获取用户各游戏积分
await fetchUserScores();
} else {
errorDiv.textContent = data.error || '登录失败';
errorDiv.classList.add('show');
}
} catch (error) {
errorDiv.textContent = '网络错误,请重试';
errorDiv.classList.add('show');
} finally {
btn.disabled = false;
btn.textContent = '登录';
}
}
async function doRegister() {
const username = document.getElementById('username').value.trim();
const password = document.getElementById('password').value.trim();
const password2 = document.getElementById('password2').value.trim();
const btn = document.getElementById('authBtn');
const errorDiv = document.getElementById('loginError');
if (!username || !password) {
errorDiv.textContent = '请输入用户名和密码';
errorDiv.classList.add('show');
return;
}
if (username.length < 3) {
errorDiv.textContent = '用户名至少3个字符';
errorDiv.classList.add('show');
return;
}
if (password.length < 6) {
errorDiv.textContent = '密码至少6位';
errorDiv.classList.add('show');
return;
}
if (password !== password2) {
errorDiv.textContent = '两次输入的密码不一致';
errorDiv.classList.add('show');
return;
}
btn.disabled = true;
btn.textContent = '注册中...';
try {
const response = await fetch('/api/auth/register', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
credentials: 'include',
body: JSON.stringify({ username, password })
});
const data = await response.json();
if (data.success) {
isAuthenticated = true;
hideLoginOverlay();
showUserBar(data.user);
document.getElementById('username').value = '';
document.getElementById('password').value = '';
document.getElementById('password2').value = '';
// 切换回登录模式
toggleAuthMode();
alert('注册成功!欢迎加入 Net-Games!');
} else {
errorDiv.textContent = data.error || '注册失败';
errorDiv.classList.add('show');
}
} catch (error) {
errorDiv.textContent = '网络错误,请重试';
errorDiv.classList.add('show');
} finally {
btn.disabled = false;
btn.textContent = '注册';
}
}
async function doLogout() {
try {
await fetch('/api/auth/logout', {
method: 'POST',
credentials: 'include'
});
isAuthenticated = false;
hideUserBar();
showLoginOverlay();
} catch (error) {
console.error('登出失败:', error);
}
}
// 初始化
document.addEventListener('DOMContentLoaded', async () => {
await checkAuth();
renderGames();
// 回车登录
document.getElementById('password').addEventListener('keypress', (e) => {
if (e.key === 'Enter') doLogin();
});
});
</script>
</body>
</html>