1063 lines
44 KiB
Plaintext
1063 lines
44 KiB
Plaintext
<!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;">
|
||
“新华书店科学教育” 品牌唯一编程产品
|
||
</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> |