Files
001code-html--cocos/scratch-gui/src/playground/welcome.ejs
2026-09-07 09:55:36 +08:00

1063 lines
44 KiB
Plaintext
Raw 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">
<title>001CODE-首页</title>
<link rel="stylesheet" href="css/nicepage.css?v=<%= htmlWebpackPlugin.options.version %>" media="screen">
<link rel="stylesheet" href="css/welcome-new.css?v=<%= htmlWebpackPlugin.options.version %>">
<script class="u-script" type="text/javascript" src="js/jquery.js?v=<%= htmlWebpackPlugin.options.version %>"
defer></script>
<script class="u-script" type="text/javascript" src="js/nicepage.js?v=<%= htmlWebpackPlugin.options.version %>"
defer></script>
<!-- <script class="u-script" type="text/javascript"
src="rank-badge-generator.js?v=<%= htmlWebpackPlugin.options.version %>" defer></script> -->
<style>
/* 灵丌编程 — 与 ai.html 一致 */
.nav__pill {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 5px 12px;
height: 32px;
border-radius: 3px;
background: #f2f3ff;
text-decoration: none;
flex-shrink: 0;
}
.nav__pillText {
font-family: "PingFang SC", system-ui, -apple-system, "Segoe UI", Arial, sans-serif;
font-weight: 400;
font-size: 14px;
line-height: 1.5714285714;
color: #4F8CFF;
white-space: nowrap;
}
.nav__pillNew {
display: none;
}
/* 灵丌AI 导航胶囊 */
.nav-ai-pill {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 5px 18px 5px 14px;
height: 32px;
border-radius: 20px;
text-decoration: none;
flex-shrink: 0;
position: relative;
transition: transform 0.35s cubic-bezier(0.22, 1, 0.36, 1);
}
.nav-ai-pill:hover {
transform: scale(1.05);
}
.nav-ai-pill:focus {
outline: none;
}
.nav-ai-pill:focus-visible {
outline: 2px solid rgba(0, 3, 255, 0.45);
outline-offset: 3px;
}
.nav-ai-pill span {
font-weight: 600;
font-size: 13px;
font-style: italic;
letter-spacing: 0.5px;
background-image: linear-gradient(
105deg,
rgba(0, 3, 255, 0.55) 0%,
rgba(0, 3, 255, 0.55) 38%,
#7dd3fc 46%,
#ffffff 50%,
#60a5fa 54%,
rgba(0, 3, 255, 0.55) 62%,
rgba(0, 3, 255, 0.55) 100%
);
background-size: 220% 100%;
background-position: 100% 50%;
-webkit-background-clip: text;
background-clip: text;
color: transparent;
transition: letter-spacing 0.35s ease;
}
.nav-ai-pill:hover span {
letter-spacing: 1.2px;
animation: navAIShimmer 1.35s ease-in-out infinite;
}
@keyframes navAIShimmer {
0% { background-position: 100% 50%; }
100% { background-position: -120% 50%; }
}
.nav-ai-pill img {
width: 18px;
height: 18px;
flex-shrink: 0;
filter: drop-shadow(0 0 5px rgba(255, 255, 255, 0.5));
transition: transform 0.4s cubic-bezier(0.22, 1, 0.36, 1), filter 0.35s ease;
}
.nav-ai-pill:hover img {
transform: rotate(18deg) scale(1.18);
filter: brightness(1.35) saturate(1.25);
animation: navAISpark 1.1s ease-in-out infinite;
}
@keyframes navAISpark {
0%, 100% { transform: rotate(18deg) scale(1.18); }
50% { transform: rotate(-8deg) scale(1.28); }
}
</style>
</head>
<body data-path-to-root="./" data-include-products="false" class="u-body u-xl-mode" data-lang="en">
<div class="page">
<header class="navbar-wrapper">
<div class="container navbar">
<div class="navbar-brand">
<div class="navbar-logo" onclick="window.location.href='/home.html'" style="cursor:pointer;"></div>
<!-- <div class="navbar-title">001CODE</div> -->
</div>
<button type="button" class="navbar-toggle" id="navbarToggle" aria-label="打开菜单" aria-expanded="false" aria-controls="navbarCollapse">
<span></span>
<span></span>
<span></span>
</button>
<div class="navbar-collapse" id="navbarCollapse">
<div class="navbar-middle">
<div class="nav-link-group">
<a class="nav-link" href="home.html">首页</a>
</div>
<div class="nav-link-group">
<span class="nav__pill" aria-current="page" style="cursor:default;">
<span class="nav__pillText">灵丌编程</span>
</span>
</div>
<div class="nav-link-group">
<a class="nav-ai-pill" href="/ai.html">
<img src="../images/ai-sparkle.svg" alt="" />
<span>灵丌AI</span>
</a>
</div>
<div class="nav-link-group" id="user-rank-badge" title="Rank Badge">
<span class="nav-link nav-label">当前段位:</span>
<span class="nav-rank-icon"></span>
<span class="nav-link nav-rank-text">加载中...</span>
</div>
</div>
<div class="navbar-right">
<a class="navbar-link" id="open-personal-info" href="javascript:void(0);">个人信息</a>
<a class="navbar-link u-dialog-link" href="#carousel_c286">修改密码</a>
<a class="navbar-link navbar-btn-activate u-dialog-link" href="#carousel_ac25" style="text-decoration:none;">
<div style="display:flex;align-items:center;gap:4px;">
<span>开通权益</span>
<span class="navbar-link-vip" style="display:none;">VIP</span>
</div>
</a>
<div class="navbar-account">
<!-- <div class="navbar-avatar"></div> -->
<div class="navbar-logout" id="logout-button">退出</div>
</div>
<a class="navbar-login" href="/login.html" id="navbarLoginBtn">去登录</a>
</div>
</div>
</div>
<div class="navbar-backdrop" id="navbarBackdrop"></div>
</header>
<script>
(function () {
try {
if (!localStorage.getItem('access_token')) {
var nav = document.querySelector('.navbar-wrapper');
if (nav) nav.classList.add('is-guest');
var loginBtn = document.getElementById('navbarLoginBtn');
if (loginBtn) {
loginBtn.addEventListener('click', function () {
try { localStorage.setItem('returnUrl', '/welcome.html'); } catch (e) {}
});
}
}
} catch (e) {}
})();
</script>
<div class="glass-container">
<main class="main">
<section class="banner">
<div class="banner-frame">
<div class="banner-bg"></div>
<img class="banner-mask" src="../images/banner-mask.png" alt="">
<div class="banner-title">趣味编程学习平台</div>
<div class="banner-feature banner-feature-1">
<img class="banner-feature-icon" src="../images/pacman-2.svg" alt="">
<div class="banner-feature-text">
<div class="banner-feature-title" style="line-height:normal; padding-top: 7px;">
“新华书店科学教育”&nbsp;&nbsp;品牌唯一编程产品
</div>
</div>
</div>
<div class="banner-feature banner-feature-2">
<img class="banner-feature-icon" src="../images/pacman-1.svg" alt="">
<div class="banner-feature-text banner-feature-text-offset" style="padding-left: 2px;">
<div class="banner-feature-title">闯关式趣味编程学习平台</div>
<div class="banner-feature-desc">适合青少年的交互式一站式编程学习平台</div>
</div>
</div>
<!-- Hero Image -->
<img class="banner-hero" src="../images/hero-overlay.png" alt="">
</div>
</section>
<section class="feed" aria-label="课程卡片">
<!-- Card 1: Scratch -->
<article class="card" aria-label="编程闯关">
<div class="card__hero" style="--badge-left: 51.2%; --pill-bg: #5dafe3">
<img class="card__hero-bg" src="../images/card1-bg.png" alt="" />
<!-- <div class="brand-badge">
<div class="brand-badge__part"> -->
<!-- <img class="brand-badge__icon brand-badge__logo"
src="../images/logo-9de92858a1345ac1ceafb017f77d069b.png" alt="" />
<span class="brand-badge__name">001CODE</span> -->
<!-- </div> -->
<!-- <div class="brand-badge__divider"></div> -->
<!-- <div class="brand-badge__part">
<img class="brand-badge__icon" src="../images/icon-scratch-block.svg" alt="" />
<span class="brand-badge__course">图形</span>
</div>
</div> -->
</div>
<div class="card__content">
<header class="card__header">
<h2 class="card__title">图形化编程</h2>
<div class="tags" aria-label="标签">
<!-- <span class="tag tag--primary">Scratch</span> -->
<span class="tag tag--success">图形化</span>
</div>
</header>
<div class="card__actions">
<button id="scratch-programming-1" class="card__actions-btn">
开始闯关
</button>
</div>
</div>
</article>
<!-- Card 2: Python -->
<article class="card" aria-label="Python编程">
<div class="card__hero" style="--badge-left: 49.9%; --pill-bg: #2a8acf">
<img class="card__hero-bg" src="../images/card2-bg.png" alt="" />
<!-- <div class="brand-badge"> -->
<!-- <div class="brand-badge__part">
<img class="brand-badge__icon brand-badge__logo"
src="../images/logo-9de92858a1345ac1ceafb017f77d069b.png" alt="" />
<span class="brand-badge__name">001CODE</span>
</div>
<div class="brand-badge__divider"></div> -->
<!-- <div class="brand-badge__part">
<img class="brand-badge__icon" src="../images/logo-python.png" alt="" />
<span class="brand-badge__course">Python</span>
</div>
</div> -->
</div>
<div class="card__content">
<header class="card__header">
<h2 class="card__title">Python编程</h2>
<div class="tags" aria-label="标签">
<span class="tag tag--primary">Python</span>
<!-- <span class="tag tag--warning">代码实战</span> -->
</div>
</header>
<div class="card__actions">
<button id="python-programming" class="card__actions-btn">
开始闯关
</button>
</div>
</div>
</article>
<!-- Card 3: C++ -->
<article class="card" aria-label="C++编程">
<div class="card__hero" style="--badge-left: 53.3%; --pill-bg: #0e6aae">
<img class="card__hero-bg" src="../images/card3-bg.png" alt="" />
<!-- <div class="brand-badge"> -->
<!-- <div class="brand-badge__part">
<img class="brand-badge__icon brand-badge__logo"
src="../images/logo-9de92858a1345ac1ceafb017f77d069b.png" alt="" />
<span class="brand-badge__name">001CODE</span>
</div>
<div class="brand-badge__divider"></div> -->
<!-- <div class="brand-badge__part">
<img class="brand-badge__icon" src="../images/logo-cpp.png" alt="" />
<span class="brand-badge__course">C++</span>
</div>
</div> -->
</div>
<div class="card__content">
<header class="card__header">
<h2 class="card__title">C++编程</h2>
<div class="tags" aria-label="标签">
<!-- <span class="tag tag--primary">系统思维</span> -->
<span class="tag tag--danger">C++</span>
</div>
</header>
<div class="card__actions">
<button id="cpp-programming" class="card__actions-btn">
开始闯关
</button>
</div>
</div>
</article>
</section>
</main>
<footer class="footer">
<div class="footer-inner">
<div class="footer-text">
ICP备案号<a href="https://beian.miit.gov.cn/" target="_blank" rel="noopener">沪ICP备2026042325号</a>
<!-- 公安备案号:<a href="https://beian.mps.gov.cn/" target="_blank">川公网安备51011202000980号</a> --> © 灵丌编程001CODE.COM
</div>
</div>
</footer>
</div>
</div>
<!-- HIDDEN MODALS FROM ORIGINAL FILE - PRESERVED FOR INTERACTION LOGIC -->
<!-- Personal Info Modal -->
<section class="modal-overlay" id="sec-1cf9" style="display:none;">
<div class="modal-basic-info" role="dialog" aria-modal="true" aria-label="基本信息">
<header class="modal-header">
<div class="modal-title">
<span>基本信息</span>
<span class="modal-subtitle">Personal Information</span>
</div>
<button class="modal-close" type="button" aria-label="关闭">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<line x1="18" y1="6" x2="6" y2="18"></line>
<line x1="6" y1="6" x2="18" y2="18"></line>
</svg>
</button>
</header>
<div class="modal-body profile-body">
<div class="profile-grid">
<!-- Name -->
<div class="profile-item">
<label class="profile-label">姓名</label>
<div class="profile-input-wrap">
<svg class="profile-icon" viewBox="0 0 24 24" fill="none" stroke="#4F46E5" stroke-width="2">
<path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"></path>
<circle cx="12" cy="7" r="4"></circle>
</svg>
<input class="profile-input" type="text" id="edit-name" value="" disabled="disabled" placeholder="未设置">
</div>
</div>
<!-- Phone -->
<div class="profile-item">
<label class="profile-label">手机</label>
<div class="profile-input-wrap">
<svg class="profile-icon" viewBox="0 0 24 24" fill="none" stroke="#10B981" stroke-width="2">
<path
d="M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6 19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72 12.84 12.84 0 0 0 .7 2.81 2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45 12.84 12.84 0 0 0 2.81.7A2 2 0 0 1 22 16.92z">
</path>
</svg>
<input class="profile-input" type="tel" id="edit-phone" value="" disabled="disabled" placeholder="未绑定">
</div>
</div>
<!-- Grade -->
<div class="profile-item">
<label class="profile-label">年级</label>
<div class="profile-input-wrap">
<svg class="profile-icon" viewBox="0 0 24 24" fill="none" stroke="#F59E0B" stroke-width="2">
<path d="M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z"></path>
<path d="M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z"></path>
</svg>
<select id="edit-grade" class="profile-input" disabled="disabled"></select>
</div>
</div>
<!-- School -->
<div class="profile-item">
<label class="profile-label">学校</label>
<div class="profile-input-wrap">
<svg class="profile-icon" viewBox="0 0 24 24" fill="none" stroke="#3B82F6" stroke-width="2">
<path d="M3 21h18M5 21V7l8-4 8 4v14M8 21V8a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v13"></path>
</svg>
<div class="profile-value u-table-cell-4" style="line-height: 44px; text-align: left;"></div>
</div>
</div>
<!-- Location -->
<div class="profile-item">
<label class="profile-label">地域</label>
<div class="profile-input-wrap">
<svg class="profile-icon" viewBox="0 0 24 24" fill="none" stroke="#EC4899" stroke-width="2">
<path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z"></path>
<circle cx="12" cy="10" r="3"></circle>
</svg>
<div class="profile-value u-table-cell-10" style="line-height: 44px; text-align: left;"></div>
</div>
</div>
<!-- Expiry -->
<div class="profile-item">
<label class="profile-label">会员有效期</label>
<div class="profile-input-wrap is-vip">
<svg class="profile-icon" viewBox="0 0 24 24" fill="none" stroke="#D97706" stroke-width="2">
<rect x="3" y="4" width="18" height="18" rx="2" ry="2"></rect>
<line x1="16" y1="2" x2="16" y2="6"></line>
<line x1="8" y1="2" x2="8" y2="6"></line>
<line x1="3" y1="10" x2="21" y2="10"></line>
</svg>
<button id="expiry-button" class="profile-value-btn" disabled="true"></button>
</div>
</div>
</div>
<div class="modal-actions">
<button id="edit-submit-btn" class="modal-primary-btn" type="button">修改个人信息</button>
</div>
</div>
</div>
</section>
<!-- Activation/VIP Modal -->
<section class="modal-overlay" id="carousel_ac25" style="display:none;">
<div class="modal-activate" role="dialog" aria-modal="true" aria-label="激活账号">
<header class="modal-header">
<div class="modal-header-spacer" aria-hidden="true"></div>
<div class="modal-title">激活账号</div>
<button class="modal-close" type="button" aria-label="关闭" id="closeNoPermissionModal">
<img src="../images/modal-activate-close.svg" alt="关闭">
</button>
</header>
<div class="modal-content">
<div class="modal-left">
<div class="modal-left-top">
<div class="modal-greeting">直接使用激活码激活使用</div>
<div class="modal-field">
<div class="field-label">激活码 <sapn style="color: #ff4757;">*</sapn>
</div>
<div class="field-input-wrap">
<input class="field-input" type="text" placeholder="请输入内容" aria-label="激活码" id="permissionCode">
</div>
</div>
</div>
<div class="modal-left-actions">
<button class="modal-submit" type="button" id="permissionCodeSendButton">提交</button>
<!-- <button class="modal-try" type="button" id="tryuse">试用部分功能</button> -->
</div>
</div>
<div class="modal-divider" aria-hidden="true"></div>
<div class="modal-right">
<div class="modal-right-title">扫描下方二维码联系开通</div>
<img class="modal-qr" src="../images/modal-activate-qr.png" alt="联系开通二维码">
</div>
</div>
</div>
</section>
<!-- Password Change Modal -->
<section class="modal-overlay" id="carousel_c286" style="display:none;">
<div class="modal-password" role="dialog" aria-modal="true" aria-label="密码修改">
<header class="modal-header">
<div class="modal-header-spacer" aria-hidden="true"></div>
<div class="modal-title" style="align-items: center; text-align: center;">密码修改</div>
<button class="modal-close u-dialog-close-button" type="button" aria-label="关闭">
<!-- Close SVG -->
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<line x1="18" y1="6" x2="6" y2="18"></line>
<line x1="6" y1="6" x2="18" y2="18"></line>
</svg>
</button>
</header>
<div class="modal-body">
<div class="field">
<div class="field-label">旧密码 <span style="color: #ff4757;">*</span></div>
<div class="input-gradient">
<input class="field-input" type="password" placeholder="请输入内容" id="old-password" aria-label="旧密码">
</div>
</div>
<div class="field">
<div class="field-label">新密码 <span style="color: #ff4757;">*</span></div>
<div class="input-gradient">
<input class="field-input" type="password" placeholder="请输入内容" id="new-password" aria-label="新密码">
</div>
</div>
<div class="field">
<div class="field-label">重复新密码 <span style="color: #ff4757;">*</span></div>
<div class="input-gradient">
<input class="field-input" type="password" placeholder="请输入内容" id="confirm-password" aria-label="重复新密码">
</div>
</div>
</div>
<div class="modal-actions">
<button class="primary-btn" type="button" id="change-password-btn">提交修改</button>
</div>
</div>
</section>
<!-- Rank Info Dialog -->
<section class="modal-overlay" id="rank_info_dialog" style="display: none;">
<div class="modal-rank-intro" role="dialog" aria-modal="true" aria-label="段位介绍">
<header class="modal-header">
<button class="modal-close" type="button" aria-label="关闭" onclick="closeRankInfoDialog()"
style="visibility: hidden;">
<img src="../images/modal-rank-intro-close.svg" alt="关闭">
</button>
<div class="modal-title-container"
style="flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: center;">
<div class="modal-title" style="flex: 0; align-items: center; text-align: center;">段位介绍</div>
<div id="current-rank-display">当前段位:<span id="current_score"></span></div>
</div>
<button class="modal-close" type="button" aria-label="关闭" onclick="closeRankInfoDialog()">
<img src="../images/modal-rank-intro-close.svg" alt="关闭">
</button>
</header>
<div class="modal-body">
<div class="current-card">
<div class="current-card-content"></div>
</div>
<!-- Rank badges will be dynamically inserted here by JavaScript -->
<div id="rank-badges-container" style="width: 100%; display: flex; flex-direction: column; gap: 11px;">
<!-- Dynamic content from rank-badge-generator.js -->
</div>
</div>
<div class="modal-footnote">完成关卡内容获得积分与头衔</div>
</div>
</section>
<!-- Logic Scripts -->
<script>
document.addEventListener('DOMContentLoaded', async function () {
// Global Helper
function showSuccessMessage(msg) {
const d = document.createElement('div');
d.textContent = msg;
Object.assign(d.style, {
position: 'fixed', top: '50%', left: '50%', transform: 'translate(-50%,-50%)',
background: 'rgba(0,200,0,0.8)', color: 'white', padding: '20px', borderRadius: '5px', zIndex: 99999
});
document.body.appendChild(d);
setTimeout(() => d.remove(), 2000);
}
// Rank Info Dialog Logic - Defined EARLY to avoid ReferenceError
window.showRankInfoDialog = function () {
const modal = document.getElementById('rank_info_dialog');
if (!modal) return;
modal.style.display = 'flex';
const container = document.getElementById('rank-badges-container');
if (!container) return;
container.innerHTML = '';
if (window.RankBadgeGenerator || window.rankBadgeGenerator) {
const rankDetails = [
{ id: 1, title: '坚韧黑铁', icon: 'rank-iron.png', colorClass: 'gray9' },
{ id: 2, title: '英勇黄铜', icon: 'rank-bronze.png', colorClass: 'bronze' },
{ id: 3, title: '不屈白银', icon: 'rank-silver.png', colorClass: 'gray6' },
{ id: 4, title: '荣耀黄金', icon: 'rank-gold.png', colorClass: 'gold' },
{ id: 5, title: '华贵铂金', icon: 'rank-platinum.png', colorClass: 'gray8' },
{ id: 6, title: '璀璨钻石', icon: 'rank-diamond.png', colorClass: 'diamond' },
{ id: 7, title: '超凡大师', icon: 'rank-master.png', colorClass: 'master' },
{ id: 8, title: '傲世宗师', icon: 'rank-grandmaster.png', colorClass: 'grandmaster' },
{ id: 9, title: '最强王者', icon: 'rank-king.png', colorClass: 'king' }
];
const generator = window.rankBadgeGenerator || (new window.RankBadgeGenerator());
const rankData = generator.rankData;
// Create two rows
const row1 = document.createElement('div');
row1.className = 'rank-row';
row1.setAttribute('aria-label', '段位列表第一行');
const row2 = document.createElement('div');
row2.className = 'rank-row';
row2.setAttribute('aria-label', '段位列表第二行');
rankDetails.forEach((detail, index) => {
const data = rankData[detail.id] || { scoreRange: '待定' };
const card = document.createElement('div');
card.className = 'rank-card';
card.innerHTML = `
<img class="rank-icon" src="images/${detail.icon}" alt="${detail.title}">
<div class="rank-content">
<div class="rank-title-row">
<div class="rank-title ${detail.colorClass}">${detail.title}</div>
<div class="rank-tag">${data.scoreRange}</div>
</div>
</div>
`;
if (index < 5) {
row1.appendChild(card);
} else {
row2.appendChild(card);
}
});
container.appendChild(row1);
container.appendChild(row2);
} else {
container.innerHTML = '<p style="text-align:center; color:#666;">徽章生成器未加载,请尝试刷新页面。</p>';
}
};
window.closeRankInfoDialog = function () {
document.getElementById('rank_info_dialog').style.display = 'none';
};
// Adapted Advertisement Fetcher for Banner
async function fetchAdvertisements() {
try {
const response = await fetch(`${window.CONFIG.DataServerBaseUrl}/api/advertisement/get_user_advertisements/`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({})
});
const result = await response.json();
if (result.success && result.data && result.data.advertisements && result.data.advertisements.length > 0) {
const ads = result.data.advertisements;
const bannerFrame = document.querySelector('.banner-frame');
const bannerBg = document.querySelector('.banner-bg');
if (bannerBg && bannerFrame) {
// Add transition for smooth switching
bannerBg.style.transition = 'opacity 0.5s ease-in-out';
// Create Indicators Container
let indicatorsContainer = document.querySelector('.banner-indicators');
if (!indicatorsContainer) {
indicatorsContainer = document.createElement('div');
indicatorsContainer.className = 'banner-indicators';
bannerFrame.appendChild(indicatorsContainer);
}
indicatorsContainer.innerHTML = '';
let currentIndex = 0;
let timer = null;
let currentAdLink = null; // Store current ad link
// Add click event for ad navigation
bannerBg.addEventListener('click', () => {
if (currentAdLink) {
window.open(currentAdLink, '_blank');
}
});
const updateDots = (index) => {
const dots = indicatorsContainer.querySelectorAll('.banner-dot');
dots.forEach((dot, i) => {
if (i === index) dot.classList.add('active');
else dot.classList.remove('active');
});
};
const showAd = (index) => {
const ad = ads[index];
console.log('Displaying Ad:', index, ad);
if (ad && ad.background_url) {
// Update current link
currentAdLink = ad.link || null;
// Update cursor style
bannerBg.style.cursor = currentAdLink ? 'pointer' : 'default';
// Fade out
bannerBg.style.opacity = '0';
// Wait for fade out, then change image and fade in
setTimeout(() => {
const img = new Image();
img.onload = () => {
bannerBg.style.backgroundImage = `url("${ad.background_url}")`;
bannerBg.style.opacity = '1';
console.log('Ad image loaded and set:', ad.background_url);
};
img.onerror = (e) => {
console.error('Failed to load ad image:', ad.background_url, e);
};
img.src = ad.background_url;
}, 500);
currentIndex = index;
updateDots(currentIndex);
}
};
// Setup dots
if (ads.length > 1) {
ads.forEach((_, index) => {
const dot = document.createElement('div');
dot.className = 'banner-dot';
if (index === 0) dot.classList.add('active');
dot.addEventListener('click', () => {
if (timer) clearInterval(timer);
showAd(index);
startTimer(); // Restart timer after manual switch
});
indicatorsContainer.appendChild(dot);
});
}
// Initial display
console.log('Initial Ad Setup. Ads count:', ads.length);
if (ads[0].background_url) {
bannerBg.style.backgroundImage = `url("${ads[0].background_url}")`;
bannerBg.style.opacity = '1'; // Ensure opacity is 1
// Set initial link and cursor
currentAdLink = ads[0].link || null;
bannerBg.style.cursor = currentAdLink ? 'pointer' : 'default';
console.log('Initial background set to:', ads[0].background_url);
// Optional: Hide default banner elements if ad is present to prevent obstruction
const bannerHero = document.querySelector('.banner-hero');
const bannerTitle = document.querySelector('.banner-title');
const bannerFeatures = document.querySelectorAll('.banner-feature');
const bannerMask = document.querySelector('.banner-mask');
if (bannerHero) bannerHero.style.display = 'none';
if (bannerTitle) bannerTitle.style.display = 'none';
if (bannerMask) bannerMask.style.display = 'none';
bannerFeatures.forEach(el => el.style.display = 'none');
}
const startTimer = () => {
if (ads.length > 1) {
// Clear previous timer if any to avoid multiples
if (timer) clearInterval(timer);
timer = setInterval(() => {
const nextIndex = (currentIndex + 1) % ads.length;
showAd(nextIndex);
}, 5000);
}
};
startTimer();
} else {
console.error('Banner elements not found. bannerBg:', bannerBg, 'bannerFrame:', bannerFrame);
}
} else {
console.log('No ads found or request failed', result);
}
} catch (error) {
console.error('获取广告数据失败:', error);
}
}
await fetchAdvertisements();
// Original Modal & User Data Logic
const editSubmitBtn = document.getElementById('edit-submit-btn');
const editName = document.getElementById('edit-name');
const editPhone = document.getElementById('edit-phone');
const editGrade = document.getElementById('edit-grade');
async function fetchGrades() {
try {
const response = await fetch(`${window.CONFIG.DataServerBaseUrl}/api/grades/`, {
method: 'GET', headers: { 'Content-Type': 'application/json' }
});
const grades = await response.json();
editGrade.innerHTML = '';
grades.forEach(grade => {
const option = document.createElement('option');
option.value = grade.id;
option.textContent = grade.name;
editGrade.appendChild(option);
});
} catch (error) { console.error(error); }
}
async function fetchUserDetails() {
try {
const username = localStorage.getItem('username');
if (!username) return;
await fetchGrades();
const response = await fetch(`${window.CONFIG.DataServerBaseUrl}/api/get-user-details/`, {
method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ username })
});
const result = await response.json();
if (result.success) {
const data = result.data;
editName.value = data.full_name;
editPhone.value = data.phone_number;
if (data.grade_id) editGrade.value = data.grade_id;
else if (data.grade) {
const options = Array.from(editGrade.options);
const m = options.find(o => o.textContent === data.grade);
if (m) editGrade.value = m.value;
}
document.querySelector('.u-table-cell-4').textContent = data.school_name;
document.querySelector('.u-table-cell-10').textContent = data.location;
const expiryButton = document.getElementById('expiry-button');
if (data.membership_expiry) expiryButton.textContent = data.membership_expiry;
}
} catch (error) { console.error(error); }
}
async function fetchUserRank() {
try {
const accessToken = localStorage.getItem('access_token');
if (!accessToken) return;
const response = await fetch(`${window.CONFIG.DataServerBaseUrl}/api/get-user-rank/`, {
method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${accessToken}` }, body: JSON.stringify({})
});
const result = await response.json();
if (result.success && result.data) {
const rankLevel = result.data.rank_level;
const currentScore = result.data.current_score || 0;
// Wait for Badge Generator
const waitForBadge = () => new Promise(resolve => {
const i = setInterval(() => { if (window.RankBadgeGenerator) { clearInterval(i); resolve(); } }, 100);
setTimeout(() => { clearInterval(i); resolve(); }, 5000);
});
await waitForBadge();
if (window.RankBadgeGenerator) {
const generator = window.rankBadgeGenerator || (new window.RankBadgeGenerator());
const badgeInfo = generator.getBadgeInfo(rankLevel);
const badgeContainer = document.getElementById('user-rank-badge');
// Define Rank Colors mapping (should match rank-badge-generator or CSS)
const rankColorMap = {
1: '#5E5E5E', // Iron - Gray
2: '#AAB41BE6', // Bronze
3: '#A6A6A6', // Silver
4: '#EBB521E6', // Gold
5: '#777777', // Platinum - Light Blue/Cyan
6: '#DBC1399', // Diamond - Purple
7: '#AE2190E6', // Master - Pink
8: '#D54941', // Grandmaster - Red
9: '#E37318' // King - Yellow/Gold
};
const rankColor = rankColorMap[rankLevel] || '#1E293B';
if (badgeContainer && badgeInfo) {
const rankTextEl = badgeContainer.querySelector('.nav-rank-text');
if (rankTextEl) rankTextEl.textContent = badgeInfo.name;
// Update Navbar Icon
const rankIconEl = badgeContainer.querySelector('.nav-rank-icon');
if (rankIconEl) {
const rankIcons = {
1: 'rank-iron.png', 2: 'rank-bronze.png', 3: 'rank-silver.png', 4: 'rank-gold.png',
5: 'rank-platinum.png', 6: 'rank-diamond.png', 7: 'rank-master.png', 8: 'rank-grandmaster.png', 9: 'rank-king.png'
};
const iconName = rankIcons[rankLevel] || 'rank.png';
rankIconEl.style.backgroundImage = `url("../images/${iconName}")`;
}
// Update Modal Header Display
const currentRankDisplay = document.getElementById('current-rank-display');
if (currentRankDisplay) {
// Set HTML: "当前段位:[Colored Rank Name] (积分: [Score])"
// Or just rank name as requested: "这里的当前段 要和nav-link-group当前段位一样" implies text content.
// And user mentioned current_score field.
currentRankDisplay.innerHTML = `当前段位:<span style="color: ${rankColor}; font-weight: bold; margin-right: 8px;">${badgeInfo.name}</span> <span style="color: #64748B; font-size: 14px;">(积分: ${currentScore})</span>`;
}
badgeContainer.addEventListener('click', showRankInfoDialog);
}
}
}
} catch (error) { console.error(error); }
}
const hasAccessToken = !!localStorage.getItem('access_token');
if (hasAccessToken) {
await fetchUserDetails();
await fetchUserRank();
}
// Edit Profile Logic
let isEditing = false;
editSubmitBtn.addEventListener('click', async function () {
if (!isEditing) {
isEditing = true;
editSubmitBtn.textContent = '提交';
editSubmitBtn.style.padding = '10px 50px';
editName.disabled = false; editPhone.disabled = false; editGrade.disabled = false;
editName.focus();
} else {
// Basic validation
const phoneRegex = /^1[3-9]\d{9}$/;
if (!phoneRegex.test(editPhone.value) || editName.value.trim() === '') {
showSuccessMessage('输入有误'); return;
}
try {
const username = localStorage.getItem('username');
const response = await fetch(`${window.CONFIG.DataServerBaseUrl}/api/update-user-info/`, {
method: 'POST', headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username, full_name: editName.value, phone_number: editPhone.value, grade: editGrade.value })
});
const result = await response.json();
if (result.success) showSuccessMessage('修改成功');
else showSuccessMessage('修改失败: ' + result.message);
} catch (e) { showSuccessMessage('Error'); }
isEditing = false;
editSubmitBtn.textContent = '修改个人信息';
editName.disabled = true; editPhone.disabled = true; editGrade.disabled = true;
}
});
// Modals
document.getElementById('open-personal-info').addEventListener('click', (e) => {
e.preventDefault();
document.getElementById('sec-1cf9').style.display = 'flex';
});
document.addEventListener('click', (e) => {
if (e.target.closest('#sec-1cf9 .modal-close')) document.getElementById('sec-1cf9').style.display = 'none';
if (e.target === document.getElementById('sec-1cf9')) document.getElementById('sec-1cf9').style.display = 'none';
});
// Password Change Modal
const passwordChangeLink = document.querySelector('a[href="#carousel_c286"]');
const passwordModal = document.getElementById('carousel_c286');
if (passwordChangeLink) {
passwordChangeLink.addEventListener('click', (e) => {
e.preventDefault();
// Clear inputs before showing modal
const oldP = document.getElementById('old-password');
const newP = document.getElementById('new-password');
const conP = document.getElementById('confirm-password');
if (oldP) oldP.value = '';
if (newP) newP.value = '';
if (conP) conP.value = '';
passwordModal.style.display = 'flex';
});
}
// Close button click
const passwordCloseBtn = passwordModal.querySelector('.modal-close');
if (passwordCloseBtn) {
passwordCloseBtn.addEventListener('click', (e) => {
e.stopPropagation();
passwordModal.style.display = 'none';
});
}
// Click outside modal to close
passwordModal.addEventListener('click', (e) => {
if (e.target === passwordModal) {
passwordModal.style.display = 'none';
}
});
// Password Change Logic
document.getElementById('change-password-btn').addEventListener('click', async function (e) {
e.preventDefault();
const oldP = document.getElementById('old-password').value.trim();
const newP = document.getElementById('new-password').value.trim();
const conP = document.getElementById('confirm-password').value.trim();
if (!oldP || !newP || newP !== conP) { showSuccessMessage('输入有误'); return; }
try {
const res = await fetch(`${window.CONFIG.DataServerBaseUrl}/api/change-password/`, {
method: 'POST', headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ old_password: oldP, new_password: newP })
});
const r = await res.json();
if (r.success) {
showSuccessMessage('密码修改成功');
setTimeout(() => { document.getElementById('carousel_c286').style.display = 'none'; }, 1000);
} else showSuccessMessage(r.message);
} catch (e) { console.error(e); }
});
// Account Activation Modal
const activationLink = document.querySelector('a[href="#carousel_ac25"]');
const activationModal = document.getElementById('carousel_ac25');
if (activationLink) {
activationLink.addEventListener('click', (e) => {
e.preventDefault();
// Clear inputs before showing modal
const pCode = document.getElementById('permissionCode');
if (pCode) pCode.value = '';
activationModal.style.display = 'flex';
});
}
const closeActivationBtn = document.getElementById('closeNoPermissionModal');
if (closeActivationBtn) {
closeActivationBtn.addEventListener('click', (e) => {
e.stopPropagation();
activationModal.style.display = 'none';
});
}
const tryUseBtn = document.getElementById('tryuse');
if (tryUseBtn) {
tryUseBtn.addEventListener('click', (e) => {
e.preventDefault();
activationModal.style.display = 'none';
});
}
activationModal.addEventListener('click', (e) => {
if (e.target === activationModal) {
activationModal.style.display = 'none';
}
});
// Rank Info Dialog - Click outside to close
const rankInfoModal = document.getElementById('rank_info_dialog');
if (rankInfoModal) {
rankInfoModal.addEventListener('click', (e) => {
if (e.target === rankInfoModal) {
rankInfoModal.style.display = 'none';
}
});
}
});
</script>
</body>
</html>