init
This commit is contained in:
713
scratch-gui/src/playground/programming-learning.js
Normal file
713
scratch-gui/src/playground/programming-learning.js
Normal file
@@ -0,0 +1,713 @@
|
||||
import CONFIG from './config.js';
|
||||
import { initDisableDevtool } from './devtool-control.js';
|
||||
|
||||
// ============= 业务参数 =============
|
||||
let gamelvid = '1'; // 具体关卡等级,初始为 1
|
||||
let gamelvdata = {};
|
||||
// 添加缓存对象
|
||||
const levelDataCache = {};
|
||||
const permission = localStorage.getItem('permission');
|
||||
let currentProcessedLevelData = []; // Store processed level data for secure checking
|
||||
|
||||
gamelvdata = sessionStorage.getItem('gamelvdata')
|
||||
if (gamelvdata) {
|
||||
gamelvdata = JSON.parse(gamelvdata)
|
||||
} else {
|
||||
gamelvdata = {
|
||||
gamemode: 1,
|
||||
gamelv: 1,
|
||||
gameid: 1,
|
||||
currentlv: 1,
|
||||
ismatch: false,
|
||||
matchid: { cuntry: 1, province: 1, city: 1, district: 1 },
|
||||
gameNumber: 10,
|
||||
};
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
let selectGameDom = document.querySelector('.navbar-middle');
|
||||
selectGameDom.addEventListener('click', function () {
|
||||
localStorage.removeItem('levelHistory')
|
||||
window.location.href = 'welcome.html';
|
||||
})
|
||||
// Update Language Name based on gamemode
|
||||
function updateLanguageDisplay() {
|
||||
const langNameEl = document.querySelector('.lang-name');
|
||||
if (langNameEl && gamelvdata) {
|
||||
const mode = parseInt(gamelvdata.gamemode);
|
||||
let langText = '';
|
||||
const iconSrc = 'images/stat-icon-graphical.svg';
|
||||
if (mode === 1) {
|
||||
langText = '图形化';
|
||||
} else if (mode === 2) {
|
||||
langText = 'Python';
|
||||
} else if (mode === 3) {
|
||||
langText = 'C++';
|
||||
}
|
||||
|
||||
langNameEl.innerHTML = `<img src="${iconSrc}" class="lang-icon" alt=""><span>${langText}</span>`;
|
||||
}
|
||||
}
|
||||
updateLanguageDisplay();
|
||||
|
||||
// 初始化禁用开发者工具
|
||||
initDisableDevtool({
|
||||
ondevtoolopen: () => {
|
||||
window.location.href = localStorage.getItem('returnUrl') || '/welcome.html';
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// 3. 弹窗交互绑定
|
||||
function bindSharedModalEvents() {
|
||||
// 通用关闭逻辑 (增强:支持点击内部图标)
|
||||
document.addEventListener('click', (e) => {
|
||||
const closeBtn = e.target.closest('.u-dialog-close-button') || e.target.closest('.modal-close-btn') || e.target.closest('.modal-close');
|
||||
if (closeBtn) {
|
||||
const modal = closeBtn.closest('.modal-overlay');
|
||||
if (modal) modal.style.display = 'none';
|
||||
}
|
||||
if (e.target.classList.contains('modal-overlay')) {
|
||||
e.target.style.display = 'none';
|
||||
}
|
||||
});
|
||||
|
||||
// 登出按钮事件
|
||||
const logoutBtn = document.getElementById('logout-button');
|
||||
if (logoutBtn) {
|
||||
logoutBtn.addEventListener('click', () => {
|
||||
['access_token', 'refresh_token', 'player_id'].forEach(key => {
|
||||
localStorage.removeItem(key);
|
||||
});
|
||||
window.location.href = '/login.html';
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 执行共有逻辑初始化
|
||||
bindSharedModalEvents();
|
||||
|
||||
// ============== 共有逻辑结束 ==============
|
||||
const listItemsModes = document.querySelectorAll('.level-card-actions .pill');
|
||||
// New UI doesn't have shape toggles, so we just handle the data update.
|
||||
|
||||
listItemsModes.forEach((item, index) => {
|
||||
item.addEventListener('click', function () {
|
||||
if (index !== 0 && (!permission || parseInt(permission) === 0)) {
|
||||
showMessage('error', '您没有权限解锁此等级');
|
||||
return;
|
||||
}
|
||||
|
||||
listItemsModes.forEach(other => other.classList.remove('pill-active'));
|
||||
this.classList.add('pill-active');
|
||||
|
||||
// Update Level Card Text
|
||||
const levelInfo = {
|
||||
1: { title: "Level A <span style='font-size: 0.7em'>丝路青年</span>", sub: "编程基础,循环入门" },
|
||||
2: { title: "Level B <span style='font-size: 0.7em'>红色之旅</span>", sub: "变量入门,循环进阶,变量进阶" },
|
||||
3: { title: "Level C <span style='font-size: 0.7em'>数智时代</span>", sub: "变量进阶,单条件分支入门,双条件分支入门,复杂路线规划" },
|
||||
4: { title: "Level D <span style='font-size: 0.7em'>国宝秘境</span>", sub: "条件分支进阶,循环嵌套入门,循环嵌套进阶,变量、循环与条件综合应用" },
|
||||
5: { title: "Level E <span style='font-size: 0.7em'>雪域征途</span>", sub: "循环嵌套进阶,无参数函数入门,单参数函数入门" },
|
||||
6: { title: "Level F <span style='font-size: 0.7em'>红色之旅</span>", sub: "综合运用与项目实战" }
|
||||
};
|
||||
const lv = index + 1;
|
||||
const titleEl = document.querySelector('.level-card-head');
|
||||
const subEl = document.querySelector('.level-card-sub');
|
||||
if (levelInfo[lv]) {
|
||||
if (titleEl) titleEl.innerHTML = levelInfo[lv].title;
|
||||
if (subEl) subEl.textContent = levelInfo[lv].sub;
|
||||
}
|
||||
|
||||
// Update Cover Image
|
||||
const coverEl = document.querySelector('.level-card-cover');
|
||||
if (coverEl) {
|
||||
coverEl.src = `images/level${lv}_cover.png`;
|
||||
}
|
||||
|
||||
gamelvid = (index + 1).toString();
|
||||
gamelvdata.gamelv = index + 1;
|
||||
// 记住当前 Level,从关卡返回后恢复
|
||||
try {
|
||||
sessionStorage.setItem('gamelvdata', JSON.stringify(gamelvdata));
|
||||
sessionStorage.setItem('programmingLearningSelectedLevel', String(lv));
|
||||
} catch (e) {}
|
||||
console.log('当前 gamelvid:', gamelvid);
|
||||
|
||||
if (levelDataCache[gamelvid]) {
|
||||
console.log('使用缓存数据:', gamelvid);
|
||||
renderGameModeItems(levelDataCache[gamelvid]);
|
||||
} else {
|
||||
fetchAndRenderGameModes();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// 恢复上次选中的 Level(默认 Level A)
|
||||
if (listItemsModes.length > 0) {
|
||||
let savedLv = parseInt(
|
||||
(gamelvdata && gamelvdata.gamelv) ||
|
||||
sessionStorage.getItem('programmingLearningSelectedLevel') ||
|
||||
'1',
|
||||
10
|
||||
);
|
||||
if (!isFinite(savedLv) || savedLv < 1) savedLv = 1;
|
||||
if (savedLv > listItemsModes.length) savedLv = listItemsModes.length;
|
||||
// 无权限时只能回 Level A
|
||||
if (savedLv !== 1 && (!permission || parseInt(permission) === 0)) {
|
||||
savedLv = 1;
|
||||
}
|
||||
listItemsModes[savedLv - 1].click();
|
||||
}
|
||||
|
||||
/********************************************************
|
||||
* 2) Bind Level Item Click
|
||||
********************************************************/
|
||||
addLevelItemClick();
|
||||
});
|
||||
|
||||
/********************************************************
|
||||
* 3) 拉取数据并渲染关卡列表
|
||||
********************************************************/
|
||||
function fetchAndRenderGameModes() {
|
||||
const playerId = localStorage.getItem('player_id');
|
||||
resetLevelItems();
|
||||
const requestGamelvid = gamelvid; // Capture current ID for this request
|
||||
|
||||
fetch(`${CONFIG.DataServerBaseUrl}/api/get-base-game-records/`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${localStorage.getItem('access_token')}`
|
||||
},
|
||||
body: JSON.stringify({
|
||||
"user_id": playerId,
|
||||
"gamemode": gamelvdata.gamemode,
|
||||
"gamelv": gamelvdata.gamelv,
|
||||
})
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
// Must check if user hasn't switched away while waiting
|
||||
if (requestGamelvid !== gamelvid) {
|
||||
console.log(`忽略旧请求结果: req=${requestGamelvid}, curr=${gamelvid}`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (data.success) {
|
||||
// Use the captured ID (requestGamelvid) to get data
|
||||
const levelData = data.data[requestGamelvid];
|
||||
|
||||
if (Array.isArray(levelData)) {
|
||||
levelDataCache[requestGamelvid] = levelData;
|
||||
console.log('缓存关卡数据:', requestGamelvid);
|
||||
renderGameModeItems(levelData);
|
||||
} else {
|
||||
console.error('无效的关卡数据格式:', levelData);
|
||||
showMessage('error', '关卡数据格式错误');
|
||||
}
|
||||
} else {
|
||||
showMessage('error', data.message);
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
console.error('请求出错:', err);
|
||||
// const container = document.getElementById('levels-container');
|
||||
// if (container) container.innerHTML = '<p style="text-align:center; padding: 20px;">加载关卡失败,请稍后重试。</p>';
|
||||
showMessage('error', '加载关卡失败,请稍后重试');
|
||||
})
|
||||
.finally(() => {
|
||||
// if (loadingEl) loadingEl.style.display = 'none';
|
||||
// if (levelsContainer) levelsContainer.style.display = ''; // Restore original display (grid/flex)
|
||||
});
|
||||
}
|
||||
|
||||
function renderGameModeItems(data) {
|
||||
let trialLevelCount = 3;
|
||||
const playerId = localStorage.getItem('player_id');
|
||||
|
||||
const fetchTrialLevels = async () => {
|
||||
if (!playerId || (permission && parseInt(permission) !== 0)) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const response = await fetch(`${CONFIG.DataServerBaseUrl}/api/get-user-trial-levels/`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${localStorage.getItem('access_token')}`
|
||||
},
|
||||
body: JSON.stringify({ user_id: playerId })
|
||||
});
|
||||
const result = await response.json();
|
||||
if (result.success && result.data) {
|
||||
trialLevelCount = Math.floor(result.data.trial_level_count / 10);
|
||||
console.log('用户试用关卡数量:', trialLevelCount);
|
||||
localStorage.setItem('trial_count', result.data.trial_level_count);
|
||||
processItemsWithTrialCount();
|
||||
} else {
|
||||
console.error('获取试用关卡数量失败:', result.message);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('请求试用关卡数量出错:', error);
|
||||
}
|
||||
};
|
||||
|
||||
const processItemsWithTrialCount = () => {
|
||||
const items = data.map((item, index) => {
|
||||
if (!permission || parseInt(permission) === 0) {
|
||||
item.islock = index >= trialLevelCount;
|
||||
}
|
||||
if (!CONFIG.lvlock_normal_learn) {
|
||||
item.islock = false;
|
||||
}
|
||||
return item;
|
||||
});
|
||||
|
||||
currentProcessedLevelData = items;
|
||||
renderItems(items);
|
||||
};
|
||||
|
||||
const renderItems = (items) => {
|
||||
// Target the new list items in .levels-row
|
||||
const listItems = document.querySelectorAll('.levels-row .level-item');
|
||||
|
||||
listItems.forEach((li, index) => {
|
||||
if (index < items.length) {
|
||||
const itemData = items[index];
|
||||
|
||||
// Update data-gamelv-id
|
||||
li.dataset.gamelvId = itemData.gameid;
|
||||
|
||||
// Elements
|
||||
const btn = li.querySelector('.level-btn');
|
||||
const imgEl = li.querySelector('.level-bg');
|
||||
const numEl = li.querySelector('.level-number');
|
||||
const titleEl = li.querySelector('.level-title');
|
||||
|
||||
// Update Text
|
||||
if (numEl) numEl.textContent = itemData.islock ? '' : (itemData.titleNumber || (index + 1));
|
||||
if (titleEl) {
|
||||
const text = itemData.titleText || '';
|
||||
// Add promotion badge for specific levels ONLY if unlocked
|
||||
if (!itemData.islock && (parseInt(itemData.gameid) === 29 || parseInt(itemData.gameid) === 30)) {
|
||||
titleEl.innerHTML = `<div>${text}</div><div style="margin-top: 5px;"><span class="promotion-badge">晋升关卡</span></div>`;
|
||||
} else {
|
||||
titleEl.textContent = text;
|
||||
}
|
||||
}
|
||||
|
||||
// Update Lock State and Image
|
||||
if (itemData.islock) {
|
||||
if (btn) {
|
||||
btn.classList.add('locked');
|
||||
btn.classList.remove('unlocked');
|
||||
}
|
||||
if (imgEl) {
|
||||
imgEl.src = 'images/level_btn_locked.png';
|
||||
imgEl.alt = 'Locked';
|
||||
}
|
||||
// Add custom tooltip logic if needed (borrowed from old code)
|
||||
addLockedTooltip(li, itemData.titleText);
|
||||
} else {
|
||||
if (btn) {
|
||||
btn.classList.remove('locked');
|
||||
btn.classList.add('unlocked');
|
||||
}
|
||||
if (imgEl) {
|
||||
imgEl.src = 'images/level_btn_unlocked.svg';
|
||||
imgEl.alt = itemData.titleText || 'Level';
|
||||
}
|
||||
removeLockedTooltip(li);
|
||||
}
|
||||
|
||||
|
||||
// 5. 进度条逻辑
|
||||
const shapeGray = li.querySelector('.u-grey-5');
|
||||
const shapeProgress = li.querySelector('.u-gradient');
|
||||
if (shapeGray && shapeProgress) {
|
||||
const total = itemData.total || 0;
|
||||
const finish = itemData.finish || 0;
|
||||
if (total > 0 && !itemData.islock) {
|
||||
shapeGray.style.display = 'block';
|
||||
const ratio = total > 0 ? (finish / total) : 0;
|
||||
const progressWidth = (ratio * 100).toFixed(0) + '%';
|
||||
shapeProgress.style.width = progressWidth;
|
||||
let gradient;
|
||||
if (ratio < 0.3) gradient = 'linear-gradient(to right, #ff6474, #e01c4c)';
|
||||
else if (ratio < 1) gradient = 'linear-gradient(to right, #fcc75a, #ee6622)';
|
||||
else gradient = 'linear-gradient(to right, #00ff00, #009245)';
|
||||
shapeProgress.style.backgroundImage = gradient;
|
||||
shapeProgress.setAttribute('title', `${finish}/${total}`);
|
||||
} else {
|
||||
shapeGray.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
li.style.display = 'flex'; // Use flex as per new CSS? .level-item is flex column
|
||||
} else {
|
||||
li.style.display = 'none';
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
if (!permission || parseInt(permission) === 0) {
|
||||
fetchTrialLevels();
|
||||
} else {
|
||||
processItemsWithTrialCount();
|
||||
}
|
||||
}
|
||||
|
||||
/********************************************************
|
||||
* 4) Click Event
|
||||
********************************************************/
|
||||
function addLevelItemClick() {
|
||||
const levelsContainer = document.getElementById('levels-container');
|
||||
if (!levelsContainer) return;
|
||||
|
||||
let startX = 0;
|
||||
let startY = 0;
|
||||
|
||||
levelsContainer.addEventListener('mousedown', function (event) {
|
||||
startX = event.clientX;
|
||||
startY = event.clientY;
|
||||
});
|
||||
|
||||
levelsContainer.addEventListener('click', function (event) {
|
||||
const diffX = Math.abs(event.clientX - startX);
|
||||
const diffY = Math.abs(event.clientY - startY);
|
||||
// 如果移动超过5px,认为是拖拽选择操作,不触发点击
|
||||
if (diffX > 5 || diffY > 5) {
|
||||
return;
|
||||
}
|
||||
|
||||
const li = event.target.closest('.level-item');
|
||||
if (!li) return;
|
||||
|
||||
const gameid = li.dataset.gamelvId;
|
||||
console.log('你点击了关卡 gamelv-id:', gameid);
|
||||
|
||||
const allListItems = document.querySelectorAll('.levels-row .level-item');
|
||||
const index = Array.from(allListItems).indexOf(li);
|
||||
|
||||
if (!currentProcessedLevelData || !currentProcessedLevelData[index]) {
|
||||
console.log('关卡数据尚未加载,阻止点击');
|
||||
return;
|
||||
}
|
||||
|
||||
const isLockedInMemory = currentProcessedLevelData[index].islock;
|
||||
|
||||
if (isLockedInMemory) {
|
||||
console.log('此关卡已锁定,无法进入。');
|
||||
if ((permission && parseInt(permission) !== 0)) {
|
||||
showMessage('error', '请先完成前面的关卡');
|
||||
} else {
|
||||
showMessage('error', '仅向权益用户开放');
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Checking for promotion levels
|
||||
if (gameid === '29' || gameid === '30') {
|
||||
showPromotionDialog(gameid);
|
||||
return;
|
||||
}
|
||||
|
||||
enterGame(gameid);
|
||||
});
|
||||
}
|
||||
|
||||
function enterGame(gameid) {
|
||||
let gamelvdata = sessionStorage.getItem('gamelvdata');
|
||||
if (gamelvdata) {
|
||||
gamelvdata = JSON.parse(gamelvdata);
|
||||
} else {
|
||||
gamelvdata = {
|
||||
gamemode: 1,
|
||||
gamelv: 1,
|
||||
gameid: 1,
|
||||
currentlv: 1,
|
||||
ismatch: false,
|
||||
matchid: { cuntry: 1, province: 1, city: 1, district: 1 },
|
||||
gameNumber: 10,
|
||||
};
|
||||
}
|
||||
|
||||
gamelvdata.gamelv = parseInt(gamelvid);
|
||||
gamelvdata.gameid = parseInt(gameid);
|
||||
sessionStorage.setItem('gamelvdata', JSON.stringify(gamelvdata));
|
||||
sessionStorage.setItem('lastpage', 'programming-learning.html');
|
||||
localStorage.setItem('platformMode', 'normal');
|
||||
window.location.href = 'editor.html';
|
||||
}
|
||||
|
||||
// ========== Permissions & Modals UI Logic ==========
|
||||
|
||||
function initPermissionControl() {
|
||||
const permission = localStorage.getItem('permission');
|
||||
const vipBadge = document.querySelector('.navbar-link-vip');
|
||||
if (vipBadge) {
|
||||
if (permission && parseInt(permission) !== 0) {
|
||||
vipBadge.style.display = 'inline-flex';
|
||||
} else {
|
||||
vipBadge.style.display = 'none';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener('DOMContentLoaded', () => {
|
||||
initPermissionControl();
|
||||
setQRCodeImage();
|
||||
initializePermissionCode();
|
||||
initPromotionDialog();
|
||||
|
||||
// Close buttons for new simple modals
|
||||
document.querySelectorAll('.modal-close').forEach(btn => {
|
||||
btn.addEventListener('click', (e) => {
|
||||
const modal = e.target.closest('.modal-overlay');
|
||||
if (modal) modal.style.display = 'none';
|
||||
});
|
||||
});
|
||||
|
||||
const activationLink = document.querySelector('a[href="#carousel_ac25"]');
|
||||
const activationModal = document.getElementById('carousel_ac25');
|
||||
if (activationLink && activationModal) {
|
||||
activationLink.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
activationModal.style.display = 'flex';
|
||||
});
|
||||
}
|
||||
|
||||
const tryUseBtn = document.getElementById('tryuse');
|
||||
if (tryUseBtn) {
|
||||
tryUseBtn.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
if (activationModal) activationModal.style.display = 'none';
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById('logout-button').addEventListener('click', () => {
|
||||
['access_token', 'refresh_token', 'player_id'].forEach(key => {
|
||||
localStorage.removeItem(key);
|
||||
});
|
||||
window.location.href = '/login.html';
|
||||
});
|
||||
|
||||
function showMessage(type, message) {
|
||||
let toastContainer = document.getElementById('toast-container');
|
||||
if (!toastContainer) {
|
||||
toastContainer = document.createElement('div');
|
||||
toastContainer.id = 'toast-container';
|
||||
// Add basic toast styles since we lost nicepage
|
||||
toastContainer.style.position = 'fixed';
|
||||
toastContainer.style.top = '20px';
|
||||
toastContainer.style.left = '50%';
|
||||
toastContainer.style.transform = 'translateX(-50%)';
|
||||
toastContainer.style.zIndex = '9999';
|
||||
document.body.appendChild(toastContainer);
|
||||
}
|
||||
|
||||
const toast = document.createElement('div');
|
||||
toast.classList.add('toast', type);
|
||||
toast.textContent = message;
|
||||
// Basic Toast Style
|
||||
toast.style.background = type === 'error' ? '#ff4757' : '#2ed573';
|
||||
// toast.style.color = 'white';
|
||||
// toast.style.padding = '10px 20px';
|
||||
// toast.style.borderRadius = '5px';
|
||||
// toast.style.marginBottom = '10px';
|
||||
// toast.style.boxShadow = '0 2px 5px rgba(0,0,0,0.2)';
|
||||
|
||||
toastContainer.appendChild(toast);
|
||||
|
||||
setTimeout(() => {
|
||||
toast.remove();
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
function showPromotionDialog(gameid) {
|
||||
const dialog = document.getElementById('promotion-dialog');
|
||||
if (dialog) {
|
||||
dialog.style.display = 'flex';
|
||||
dialog.dataset.currentGameId = gameid;
|
||||
}
|
||||
}
|
||||
|
||||
function hidePromotionDialog(enterLevel = false) {
|
||||
const dialog = document.getElementById('promotion-dialog');
|
||||
if (dialog) {
|
||||
const gameid = dialog.dataset.currentGameId;
|
||||
dialog.style.display = 'none';
|
||||
if (enterLevel && gameid) {
|
||||
enterGame(gameid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function initPromotionDialog() {
|
||||
const closeButton = document.querySelector('.u-btn-promotion-close');
|
||||
if (closeButton) {
|
||||
closeButton.addEventListener('click', function () {
|
||||
hidePromotionDialog(true);
|
||||
});
|
||||
}
|
||||
|
||||
const closeIcon = document.querySelector('.u-icon-promotion-close');
|
||||
if (closeIcon) {
|
||||
closeIcon.addEventListener('click', function () {
|
||||
hidePromotionDialog(false);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function setQRCodeImage() {
|
||||
const qrcodeUrl = sessionStorage.getItem('qrcode_url');
|
||||
// Target the new modal structure
|
||||
const qrcodeElement = document.querySelector('#carousel_ac25 .modal-qr');
|
||||
if (qrcodeElement) {
|
||||
if (qrcodeElement.tagName === 'IMG') {
|
||||
qrcodeElement.src = qrcodeUrl || '../images/modal-activate-qr.png';
|
||||
} else {
|
||||
qrcodeElement.style.backgroundImage = `url("${qrcodeUrl || '../images/modal-activate-qr.png'}")`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const initializePermissionCode = () => {
|
||||
const permissionCodeSendButton = document.getElementById('permissionCodeSendButton');
|
||||
const closeModal = document.getElementById('closeNoPermissionModal');
|
||||
|
||||
if (!permissionCodeSendButton) return;
|
||||
|
||||
permissionCodeSendButton.addEventListener('click', async (e) => {
|
||||
e.preventDefault();
|
||||
const permissionCode = document.getElementById('permissionCode').value.trim();
|
||||
const username = localStorage.getItem('username');
|
||||
permissionCodeSendButton.disabled = true;
|
||||
|
||||
try {
|
||||
const result = await api.redeemActivationCode(permissionCode, username);
|
||||
if (result.success) {
|
||||
showMessage('success', '您已激活账号,即将刷新页面');
|
||||
const user_permission = result.user_permission;
|
||||
localStorage.setItem('permission', user_permission);
|
||||
if (closeModal) closeModal.click();
|
||||
document.getElementById('permissionCode').value = "";
|
||||
window.location.reload();
|
||||
} else {
|
||||
showMessage('error', result.message);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error activating account:', error);
|
||||
showMessage('error', '服务器错误,请稍后再试');
|
||||
} finally {
|
||||
permissionCodeSendButton.disabled = false;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const api = {
|
||||
async redeemActivationCode(code, username) {
|
||||
const response = await fetch(`${CONFIG.DataServerBaseUrl}/api/redeem_activation_code/`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
credentials: 'include',
|
||||
body: JSON.stringify({ code, username })
|
||||
});
|
||||
return await response.json();
|
||||
}
|
||||
};
|
||||
|
||||
// Tooltip logic adapted for new UI
|
||||
function addLockedTooltip(listItem, titleText) {
|
||||
removeLockedTooltip(listItem);
|
||||
const imageElement = listItem.querySelector('.level-bg'); // Target the image
|
||||
if (!imageElement || !titleText) return;
|
||||
|
||||
const tooltip = document.createElement('div');
|
||||
tooltip.className = 'locked-tooltip'; // Make sure this class is defined in CSS or add inline styles
|
||||
tooltip.textContent = titleText;
|
||||
tooltip.style.position = 'fixed';
|
||||
tooltip.style.background = 'rgba(0,0,0,0.8)';
|
||||
tooltip.style.color = 'white';
|
||||
tooltip.style.padding = '5px 10px';
|
||||
tooltip.style.borderRadius = '4px';
|
||||
tooltip.style.pointerEvents = 'none';
|
||||
tooltip.style.fontSize = '12px';
|
||||
tooltip.style.zIndex = '10000';
|
||||
tooltip.style.display = 'none';
|
||||
|
||||
document.body.appendChild(tooltip);
|
||||
|
||||
const mouseEnterHandler = () => {
|
||||
const rect = listItem.getBoundingClientRect(); // Tooltip relative to ITEM, not just image
|
||||
tooltip.style.left = (rect.left + rect.width / 2) + 'px';
|
||||
tooltip.style.top = (rect.top - 30) + 'px';
|
||||
tooltip.style.transform = 'translateX(-50%)';
|
||||
tooltip.style.display = 'block';
|
||||
};
|
||||
|
||||
const mouseLeaveHandler = () => {
|
||||
tooltip.style.display = 'none';
|
||||
};
|
||||
|
||||
listItem.addEventListener('mouseenter', mouseEnterHandler);
|
||||
listItem.addEventListener('mouseleave', mouseLeaveHandler);
|
||||
|
||||
listItem._tooltipHandlers = {
|
||||
mouseEnter: mouseEnterHandler,
|
||||
mouseLeave: mouseLeaveHandler,
|
||||
tooltip: tooltip
|
||||
};
|
||||
}
|
||||
|
||||
function removeLockedTooltip(listItem) {
|
||||
if (!listItem || !listItem._tooltipHandlers) return;
|
||||
const handlers = listItem._tooltipHandlers;
|
||||
listItem.removeEventListener('mouseenter', handlers.mouseEnter);
|
||||
listItem.removeEventListener('mouseleave', handlers.mouseLeave);
|
||||
if (handlers.tooltip && handlers.tooltip.parentNode) {
|
||||
handlers.tooltip.parentNode.removeChild(handlers.tooltip);
|
||||
}
|
||||
delete listItem._tooltipHandlers;
|
||||
}
|
||||
|
||||
// Helper to reset items to skeleton state
|
||||
function resetLevelItems() {
|
||||
// 必须清空缓存的当前关卡数据,防止在数据未加载完成时,用户点击使用了上一个Level的数据判断
|
||||
currentProcessedLevelData = [];
|
||||
|
||||
const listItems = document.querySelectorAll('.levels-row .level-item');
|
||||
listItems.forEach(li => {
|
||||
// 1. 重置为显示状态,因为我们要显示Skeleton
|
||||
li.style.display = 'flex';
|
||||
|
||||
// 2. 也是最重要的,重置为包含 "locked" 样式
|
||||
const btn = li.querySelector('.level-btn');
|
||||
if (btn) {
|
||||
btn.classList.add('locked');
|
||||
btn.classList.remove('unlocked');
|
||||
}
|
||||
|
||||
// 3. 重置图标
|
||||
const imgEl = li.querySelector('.level-bg');
|
||||
if (imgEl) {
|
||||
imgEl.src = 'images/level_btn_locked.png';
|
||||
}
|
||||
|
||||
// 4. 清空数字
|
||||
const numEl = li.querySelector('.level-number');
|
||||
if (numEl) numEl.textContent = '';
|
||||
|
||||
// 5. 清空标题
|
||||
const titleEl = li.querySelector('.level-title');
|
||||
if (titleEl) titleEl.textContent = '';
|
||||
|
||||
// 6. 隐藏进度条
|
||||
const shapeGray = li.querySelector('.u-grey-5');
|
||||
if (shapeGray) shapeGray.style.display = 'none';
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user