Files
001code-html--cocos/scratch-gui/src/playground/competition.js
2026-09-17 11:53:14 +08:00

3453 lines
130 KiB
JavaScript
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.
// 导入配置信息
import CONFIG from './config.js';
import { initDisableDevtool } from './devtool-control.js';
// 获取 URL 参数的函数
function getUrlParameter(name) {
// 获取当前 URL 的查询字符串部分
const queryString = window.location.search;
// 创建 URLSearchParams 对象
const urlParams = new URLSearchParams(queryString);
// 返回指定参数的值
return urlParams.get(name);
}
// Parse codetype from URL. Only allow 1/2/3; default to all when missing/invalid.
function getTypeListFromUrl() {
const validTypes = ['1', '2', '3'];
const typeParam = getUrlParameter('type');
if (!typeParam) {
return validTypes.slice();
}
const types = typeParam
.split(',')
.map(t => t.trim())
.filter(t => validTypes.includes(t));
if (types.length === 0) {
return validTypes.slice();
}
return Array.from(new Set(types));
}
/**
* 将UTC时间转换为北京时间(UTC+8)
* @param {string|Date} utcTime - UTC时间字符串或Date对象
* @returns {Date} 转换后的北京时间Date对象
*/
function convertUTCToBeijingTime(utcTime) {
const date = utcTime instanceof Date ? new Date(utcTime) : new Date(utcTime);
// 添加8小时的毫秒数(8小时 = 8 * 60 * 60 * 1000毫秒)
date.setTime(date.getTime() + 8 * 60 * 60 * 1000);
return date;
}
// 添加登录失败计数和锁定时间的变量
let loginFailCount = 0;
let loginLockUntil = 0;
// 存储比赛信息和服务器时间
let competitionInfo = null;
let serverTime = null;
// 添加平台选择引导函数
function showPlatformGuide() {
// 开发测试用:取消下面这行注释可以强制每次都显示引导提示
localStorage.removeItem('platformGuideShown');
// 检查本地存储,看是否已经显示过引导
if (localStorage.getItem('platformGuideShown') === 'true') {
console.log('已经显示过平台选择引导,不再显示');
return; // 如果已经显示过,就不再显示
}
// 获取自定义下拉菜单元素(而非原始下拉菜单)
const customSelectElement = document.querySelector('.custom-select-selected');
if (!customSelectElement) {
// 如果找不到自定义下拉菜单,尝试延迟执行
console.log('找不到自定义下拉菜单,延迟再次尝试');
setTimeout(showPlatformGuide, 500);
return;
}
console.log('找到自定义下拉菜单,显示引导提示');
// 创建提示框元素
const tooltip = document.createElement('div');
tooltip.className = 'platform-guide-tooltip';
// 检查屏幕宽度,在小屏幕上调整位置和文本
const isSmallScreen = window.innerWidth < 768;
// 根据屏幕大小设置不同的提示文本
if (isSmallScreen) {
tooltip.innerHTML = `
<span class="platform-guide-close">&times;</span>
<p><strong>平台模式选择</strong></p>
<p>👆 点击上方下拉菜单选择:</p>
<p>🚀 <strong>训练平台</strong> · 日常练习模式</p>
<p>🏆 <strong>考试平台</strong> · 正式比赛模式</p>
`;
} else {
tooltip.innerHTML = `
<span class="platform-guide-close">&times;</span>
<p><strong>平台模式选择</strong></p>
<p>👉 点击右侧下拉菜单选择:</p>
<p>🚀 <strong>训练平台</strong> · 日常练习模式</p>
<p>🏆 <strong>考试平台</strong> · 正式比赛模式</p>
`;
}
// 添加到页面(先添加才能获取正确的尺寸)
document.body.appendChild(tooltip);
// 计算位置 - 在下拉框左侧
const selectRect = customSelectElement.getBoundingClientRect();
const tooltipRect = tooltip.getBoundingClientRect();
// 检查屏幕宽度,在小屏幕上调整位置
// const isSmallScreen = window.innerWidth < 768; // 已在上面声明过,这里不需要重复声明
if (isSmallScreen) {
// 在小屏幕上显示在下拉框下方
tooltip.style.position = 'fixed';
tooltip.style.top = (selectRect.bottom + 20) + 'px';
tooltip.style.left = (selectRect.left + (selectRect.width - tooltipRect.width) / 2) + 'px';
// 给提示框添加特殊类,使用不同的箭头样式
tooltip.classList.add('position-bottom');
} else {
// 在大屏幕上显示在左侧
tooltip.style.position = 'fixed';
tooltip.style.top = (selectRect.top + selectRect.height / 2 - tooltipRect.height / 2) + 'px';
tooltip.style.left = (selectRect.left - tooltipRect.width - 20) + 'px';
}
// 确保提示框不会超出屏幕边界
if (parseFloat(tooltip.style.top) < 10) {
tooltip.style.top = '10px';
}
if (parseFloat(tooltip.style.left) < 10) {
tooltip.style.left = '10px';
}
console.log('提示框位置:', tooltip.style.top, tooltip.style.left);
// 添加高亮样式到选择框
customSelectElement.classList.add('highlight-select');
// 添加关闭按钮功能
const closeBtn = tooltip.querySelector('.platform-guide-close');
closeBtn.addEventListener('click', function () {
document.body.removeChild(tooltip);
customSelectElement.classList.remove('highlight-select');
localStorage.setItem('platformGuideShown', 'true');
});
// 点击下拉菜单也会关闭提示
customSelectElement.addEventListener('click', function () {
if (document.body.contains(tooltip)) {
document.body.removeChild(tooltip);
customSelectElement.classList.remove('highlight-select');
localStorage.setItem('platformGuideShown', 'true');
}
});
// 5秒后自动消失
setTimeout(function () {
if (document.body.contains(tooltip)) {
document.body.removeChild(tooltip);
customSelectElement.classList.remove('highlight-select');
localStorage.setItem('platformGuideShown', 'true');
}
}, 8000); // 增加到8秒给用户更多阅读时间
}
document.addEventListener('DOMContentLoaded', function () {
if (CONFIG.disabledev) {
initDisableDevtool({
ondevtoolopen: () => {
// window.location.href = '/index.html';
// window.location.href = localStorage.getItem('returnUrl');
},
interval: 500,
clearLog: true,
disableMenu: true,
});
}
});
// 页面加载完成后执行
document.addEventListener('DOMContentLoaded', function () {
// 初始化时不进行隐藏操作,直接显示 body 背景
// const sectionElement = document.querySelector('.u-section-1');
// if (sectionElement) {
// sectionElement.style.visibility = 'hidden';
// }
// 从localStorage恢复登录锁定状态
const savedLockUntil = localStorage.getItem('loginLockUntil');
const savedFailCount = localStorage.getItem('loginFailCount');
if (savedLockUntil) {
loginLockUntil = parseInt(savedLockUntil, 10);
}
if (savedFailCount) {
loginFailCount = parseInt(savedFailCount, 10);
}
// 检查是否处于锁定状态,如果是,显示提示
const currentTime = new Date().getTime();
if (currentTime < loginLockUntil) {
const remainingSeconds = Math.ceil((loginLockUntil - currentTime) / 1000);
showToast(`登录失败次数过多,请在${remainingSeconds}秒后再试`, 'error');
}
// 获取 competition_id 参数
const competitionId = getUrlParameter('competition_id');
// 如果存在 competition_id 参数,则进行相应处理
if (competitionId) {
console.log('比赛ID:', competitionId);
// 更新页面标题
document.title = '比赛 #' + competitionId;
// 先查询比赛信息,等待完成后再处理背景和欢迎图片
fetchCompetitionInfo(competitionId).then(() => {
// 比赛信息加载完成后,如果没有欢迎图片显示,则设置背景图片
// 注意:欢迎图片的显示已经在 fetchCompetitionInfo 内部的 updateCompetitionInfo 函数中处理了
if (!document.querySelector('.welcome-overlay')) {
setBackgroundImage();
}
}).catch(() => {
// 如果获取比赛信息失败,使用默认处理
const welcomeImageUrl = sessionStorage.getItem('competition_welcome_image_url');
if (welcomeImageUrl) {
showWelcomeImageOverlay(welcomeImageUrl);
} else {
setBackgroundImage();
}
});
// 获取服务器时间
getServerTime();
} else {
console.log('未提供比赛ID');
// 处理未提供 competition_id 的情况 - 没有比赛ID时直接处理
const welcomeImageUrl = sessionStorage.getItem('competition_welcome_image_url');
if (welcomeImageUrl) {
showWelcomeImageOverlay(welcomeImageUrl);
} else {
setBackgroundImage();
}
}
// 将切换密码可见性函数添加到全局作用域
window.togglePasswordVisibility = togglePasswordVisibility;
// 为注册表单的密码输入框添加显示/隐藏功能
setupPasswordToggle();
// 直接为提交按钮添加点击事件
const submitButton = document.querySelector('.u-btn-submit');
if (submitButton) {
submitButton.addEventListener('click', function (event) {
event.preventDefault();
// 获取表单数据
const username = document.getElementById('name-b064').value;
const password = document.getElementById('email-b064').value;
const platformType = document.getElementById('select-58f1').value;
// 验证表单数据
if (!username || !password || !platformType) {
showToast('用户名、密码和平台类型不能为空', 'error');
return;
}
// 验证时间是否允许选择该平台
if (!validateTimeForPlatform(platformType)) {
return; // 验证失败,不继续执行
}
// 检查 type 参数
const types = getTypeListFromUrl();
if (types.length === 1) {
// 只有一个类型,直接登录并传入 codetype
loginUser(username, password, platformType, types[0]);
} else {
// 多个类型或无类型,正常登录
loginUser(username, password, platformType);
}
});
}
// 为模态框中的提交按钮添加点击事件
const modalSubmitButton = document.querySelector('.u-btn-step');
if (modalSubmitButton) {
modalSubmitButton.addEventListener('click', function (event) {
event.preventDefault();
// 获取用户选择的编码类型
const codeTypeSelect = document.getElementById('select-8c9b');
const codeType = codeTypeSelect ? codeTypeSelect.value : '1';
// 获取比赛ID
const competitionId = getUrlParameter('competition_id');
console.error(competitionId);
if (competitionId) {
// 调用加入比赛API
joinCompetition(competitionId, codeType);
} else {
showToast('未找到比赛信息', 'error');
}
});
}
// 为注册账号按钮添加点击事件
const registerButton = document.querySelector('.u-btn-3');
if (registerButton) {
registerButton.addEventListener('click', function () {
// 加载省份、年级等数据
loadProvinces();
loadGrades();
});
}
// 为省份选择框添加变更事件
const provinceSelect = document.getElementById('select-c2b7');
if (provinceSelect) {
provinceSelect.addEventListener('change', function () {
const provinceId = this.value;
if (provinceId && provinceId !== '省') {
loadCities(provinceId);
}
});
}
// 为城市选择框添加变更事件
const citySelect = document.getElementById('select-36ac');
if (citySelect) {
citySelect.addEventListener('change', function () {
const cityId = this.value;
if (cityId && cityId !== '市') {
loadDistricts(cityId);
}
});
}
// 为区县选择框添加变更事件 - 加载学校数据
const districtSelect = document.getElementById('select-4860');
if (districtSelect) {
districtSelect.addEventListener('change', function () {
const districtId = this.value;
if (districtId && districtId !== '区/县') {
// 重置学校搜索数据
if (typeof window.populateSchoolsWithSearchComp === 'function') {
window.populateSchoolsWithSearchComp([]);
}
// 加载学校数据
loadSchoolsByDistrict(districtId);
} else {
// 如果选择默认选项,清空学校数据
if (typeof window.populateSchoolsWithSearchComp === 'function') {
window.populateSchoolsWithSearchComp([]);
}
}
});
}
// 为注册表单提交按钮添加点击事件
const registerSubmitButton = document.querySelector('.u-dialog-section-6 .u-btn-step');
if (registerSubmitButton) {
registerSubmitButton.addEventListener('click', function (event) {
event.preventDefault();
// 调用注册函数
registerUser();
});
}
// 自定义平台选择下拉菜单
setupCustomPlatformSelect();
// 1) 阻止所有 form 的默认提交(包括按 Enter 触发的隐式提交)
document.querySelectorAll('form').forEach(form => {
form.addEventListener('submit', function (e) {
e.preventDefault();
e.stopPropagation();
});
});
// 2) 在输入框内按 Enter 时,阻止默认行为(不影响 textarea 换行)
document.addEventListener('keydown', function (e) {
if (e.key === 'Enter') {
const target = e.target;
if (target && target.tagName === 'INPUT') {
e.preventDefault();
}
}
});
});
/**
* 获取服务器时间
*/
function getServerTime() {
// 构建请求数据
const requestData = {
key: "abc123xyz456" // 访问密钥
};
// 发送POST请求到服务器
fetch(`${CONFIG.DataServerBaseUrl}/api/competition/get_server_time/`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(requestData)
})
.then(response => {
if (!response.ok) {
throw new Error('网络请求失败');
}
return response.json();
})
.then(data => {
console.log('服务器时间:', data);
if (data.success && data.data) {
// 服务器返回的timestamp对应UTC时间但实际使用时应该按北京时间处理
// 直接使用服务器返回的timestamp不需要额外转换
serverTime = {
time: data.data.server_time, // 保留原始时间字符串
timestamp: data.data.timestamp // 直接使用服务器时间戳
};
console.log('使用的时间戳:', data.data.timestamp, new Date(data.data.timestamp * 1000).toLocaleString('zh-CN', { timeZone: 'Asia/Shanghai' }));
// 更新平台选择下拉框状态
updatePlatformSelectStatus();
} else {
console.error('获取服务器时间失败:', data.message);
}
})
.catch(error => {
console.error('获取服务器时间请求失败:', error);
});
}
/**
* 根据比赛ID查询比赛信息
* @param {string|number} competitionId - 比赛ID
*/
function fetchCompetitionInfo(competitionId) {
// 获取显示比赛信息的元素
const competitionInfoElement = document.getElementById('competition-info');
if (competitionInfoElement) {
competitionInfoElement.textContent = '正在加载比赛 #' + competitionId + ' 的信息...';
}
// 获取登录按钮
const submitButton = document.querySelector('.u-btn-submit');
// 构建请求数据
const requestData = {
id: parseInt(competitionId, 10) // 确保ID是整数
};
// 发送POST请求到服务器并返回 Promise
return fetch(`${CONFIG.DataServerBaseUrl}/api/competition/query_competitions/`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(requestData)
})
.then(response => {
if (!response.ok) {
throw new Error('网络请求失败');
}
return response.json();
})
.then(data => {
console.log('比赛信息:', data);
if (data.success && data.data && data.data.competitions && data.data.competitions.length > 0) {
// 获取比赛信息
const competition = data.data.competitions[0];
// 存储比赛信息
competitionInfo = competition;
// 更新页面标题为比赛名称
document.title = competition.name;
// 更新比赛信息显示
updateCompetitionInfo(competition);
// 更新平台选择下拉框状态
updatePlatformSelectStatus();
// 启用登录按钮
if (submitButton) {
submitButton.disabled = false;
submitButton.classList.remove('btn-disabled');
submitButton.style.opacity = '1';
submitButton.style.cursor = 'pointer';
// 移除之前可能添加的点击事件阻止器
submitButton.removeEventListener('click', preventButtonClick);
}
} else {
// 处理未找到比赛的情况
console.error('未找到比赛信息或请求失败:', data.message);
// 更新比赛信息标题
const competitionInfoTitleElement = document.getElementById('competition-info-title');
if (competitionInfoTitleElement) {
competitionInfoTitleElement.textContent = '错误';
}
// 更新比赛公告信息
if (competitionInfoElement) {
competitionInfoElement.textContent = '链接不正确,请输入正确的比赛链接';
competitionInfoElement.style.color = 'red';
competitionInfoElement.classList.add('error');
}
// 禁用登录按钮
if (submitButton) {
submitButton.disabled = true;
submitButton.classList.add('btn-disabled');
submitButton.style.opacity = '0.5';
submitButton.style.cursor = 'not-allowed';
// 添加点击事件阻止器
submitButton.addEventListener('click', preventButtonClick);
}
// 清空比赛时间显示
clearCompetitionTimeDisplay();
}
})
.catch(error => {
console.error('获取比赛信息失败:', error);
// 更新比赛信息标题
const competitionInfoTitleElement = document.getElementById('competition-info-title');
if (competitionInfoTitleElement) {
competitionInfoTitleElement.textContent = '错误';
}
// 更新比赛公告信息
if (competitionInfoElement) {
competitionInfoElement.textContent = '获取比赛信息失败: ' + error.message;
competitionInfoElement.style.color = 'red';
competitionInfoElement.classList.add('error');
}
// 禁用登录按钮
if (submitButton) {
submitButton.disabled = true;
submitButton.classList.add('btn-disabled');
submitButton.style.opacity = '0.5';
submitButton.style.cursor = 'not-allowed';
// 添加点击事件阻止器
submitButton.addEventListener('click', preventButtonClick);
}
// 清空比赛时间显示
clearCompetitionTimeDisplay();
// 重新抛出错误,让调用者可以处理
throw error;
});
}
/**
* 阻止按钮点击事件
* @param {Event} event - 点击事件对象
*/
function preventButtonClick(event) {
event.preventDefault();
event.stopPropagation();
showToast('链接不正确,请输入正确的比赛链接', 'error');
return false;
}
/**
* 清空比赛时间显示
*/
function clearCompetitionTimeDisplay() {
// 清空练习时间
const practiceStartDateElement = document.getElementById('practice-start-date');
const practiceEndDateElement = document.getElementById('practice-end-date');
if (practiceStartDateElement) {
practiceStartDateElement.textContent = '--';
}
if (practiceEndDateElement) {
practiceEndDateElement.textContent = '--';
}
// 清空比赛时间
const competitionDateElement = document.getElementById('competition-date');
const competitionDurationElement = document.getElementById('competition-duration');
if (competitionDateElement) {
competitionDateElement.textContent = '--';
}
if (competitionDurationElement) {
competitionDurationElement.textContent = '--';
}
}
/**
* 更新比赛信息显示
* @param {Object} competition - 比赛信息对象
*/
function updateCompetitionInfo(competition) {
// 将比赛数据保存到全局变量,以便其他函数使用
window.competitionData = competition;
// 将比赛和练习时间存入 localStorage
localStorage.setItem('competition_start_time', competition.competition_start_time);
// 计算比赛结束时间时先将UTC时间转换为北京时间再加上持续时间
const beijingStartTime = convertUTCToBeijingTime(competition.competition_start_time);
localStorage.setItem('competition_end_time', new Date(beijingStartTime.getTime() + competition.competition_duration * 60000).toISOString());
localStorage.setItem('competition_duration', competition.competition_duration);
localStorage.setItem('practice_start_time', competition.practice_start_time);
localStorage.setItem('practice_end_time', competition.practice_end_time);
localStorage.setItem('competition_name', competition.name);
localStorage.setItem('competition_id', competition.id);
// 存储关卡包信息
if (competition.practice_level_package) {
localStorage.setItem('practice_level_package_id', competition.practice_level_package.id);
localStorage.setItem('practice_level_package_name', competition.practice_level_package.name);
}
if (competition.competition_level_package) {
localStorage.setItem('competition_level_package_id', competition.competition_level_package.id);
localStorage.setItem('competition_level_package_name', competition.competition_level_package.name);
}
// 存储其他比赛相关信息
localStorage.setItem('competition_level', competition.competition_level);
localStorage.setItem('competition_status', competition.status);
localStorage.setItem('competition_is_authorized', competition.is_authorized);
localStorage.setItem('competition_show_competition_time', competition.show_competition_time);
localStorage.setItem('competition_announcement', competition.announcement);
localStorage.setItem('competition_participant_count', competition.participant_count);
localStorage.setItem('competition_max_participants', competition.max_participants);
// 存储背景图片URL和组委会二维码URL如果存在
if (competition.background_image_url) {
sessionStorage.setItem('competition_background_image_url', competition.background_image_url);
// 如果没有欢迎图片在显示,则在这里更新背景图
if (!sessionStorage.getItem('competition_welcome_image_url')) {
setBackgroundImage();
}
} else {
// 如果背景图片不存在则清除背景图片URL
sessionStorage.removeItem('competition_background_image_url');
}
if (competition.committee_qrcode_url) {
sessionStorage.setItem('competition_committee_qrcode_url', competition.committee_qrcode_url);
}
// 存储欢迎图片URL如果存在
if (competition.welcome_image_url) {
// 存储欢迎图片,并立即显示欢迎蒙层(如果当前没有显示)
sessionStorage.setItem('competition_welcome_image_url', competition.welcome_image_url);
// 检查当前是否已显示欢迎蒙层,并增加标记防止重复创建
if (!document.querySelector('.welcome-overlay') && !window.welcomeOverlayCreating) {
window.welcomeOverlayCreating = true; // 设置创建标记
showWelcomeImageOverlay(competition.welcome_image_url);
}
} else {
// 如果欢迎图片不存在则清除欢迎图片URL
sessionStorage.removeItem('competition_welcome_image_url');
}
if (competition.mobile_welcome_image_url) {
sessionStorage.setItem('mobile_welcome_image_url', competition.mobile_welcome_image_url);
} else {
sessionStorage.removeItem('mobile_welcome_image_url');
}
// 更新比赛信息标题
const competitionInfoTitleElement = document.getElementById('competition-info-title');
if (competitionInfoTitleElement) {
competitionInfoTitleElement.textContent = competition.name || '比赛/训练 信息';
}
// 更新比赛公告信息
const announcementElement = document.getElementById('competition-info');
if (announcementElement) {
const announcementText = (competition.announcement || '暂无公告信息').replace(/\n/g, '<br>');
announcementElement.innerHTML = announcementText;
}
// 更新练习时间 - 使用转换后的北京时间
const practiceStartDateElement = document.getElementById('practice-start-date');
const practiceEndDateElement = document.getElementById('practice-end-date');
if (practiceStartDateElement && competition.practice_start_time) {
const practiceStartDate = convertUTCToBeijingTime(competition.practice_start_time);
practiceStartDateElement.textContent = formatDate(practiceStartDate);
}
if (practiceEndDateElement && competition.practice_end_time) {
const practiceEndDate = convertUTCToBeijingTime(competition.practice_end_time);
practiceEndDateElement.textContent = formatDate(practiceEndDate);
}
// 更新比赛时间 - 使用转换后的北京时间
const competitionDateElement = document.getElementById('competition-date');
const competitionDurationElement = document.getElementById('competition-duration');
const competitionDateTextElement = document.querySelector('.competition-date-text');
// 检查是否允许显示比赛时间
if (competition.show_competition_time === false) {
// 只有特殊地方才屏蔽这个
if(CONFIG.HIDE_COMPETITION_TIME_ID.includes(competitionInfo.id)){
// 如果当前比赛ID在配置的隐藏列表中则隐藏比赛时间
competitionDateElement.style.display = 'none';
competitionDateTextElement.style.display = 'none';
}
// 如果不显示比赛时间,显示"待定"
if (competitionDateElement) {
competitionDateElement.textContent = '待定';
}
if (competitionDurationElement) {
competitionDurationElement.textContent = '';
}
} else {
// 正常显示比赛时间
if (competitionDateElement && competition.competition_start_time) {
const competitionDate = convertUTCToBeijingTime(competition.competition_start_time);
competitionDateElement.textContent = formatDate(competitionDate);
}
if (competitionDurationElement && competition.competition_duration) {
competitionDurationElement.textContent = `${competition.competition_duration}分钟`;
}
}
// 如果还需要保留原来的详细信息显示,可以继续使用下面的代码
const competitionInfoElement = document.querySelector('.u-text-2');
if (competitionInfoElement) {
// 格式化比赛时间用于详细显示 - 使用转换后的北京时间
const practiceStartTime = convertUTCToBeijingTime(competition.practice_start_time).toLocaleString();
const practiceEndTime = convertUTCToBeijingTime(competition.practice_end_time).toLocaleString();
const competitionStartTime = convertUTCToBeijingTime(competition.competition_start_time).toLocaleString();
// 构建比赛详细信息HTML
const announcementWithBreaks = (competition.announcement || '暂无公告信息').replace(/\n/g, '<br>');
let infoHTML = `
<div class="competition-info-details">
<p><strong>赛练信息:</strong> ${announcementWithBreaks}</p>
<p>
<strong>赛练状态:</strong> <span>${getStatusText(competition.status)}</span>
<strong style="margin-left: 20px;">赛练区域:</strong> <span>${getLevelText(competition.competition_level)}</span>
</p>
</div>
`;
localStorage.setItem('competition_announcement', competition.announcement);
// 更新元素内容
competitionInfoElement.innerHTML = infoHTML;
}
// 将所有比赛信息作为一个对象存储,方便其他页面使用
// const competitionFullInfo = {
// id: competition.id,
// name: competition.name,
// announcement: competition.announcement,
// status: competition.status,
// competition_level: competition.competition_level,
// is_authorized: competition.is_authorized,
// practice_start_time: competition.practice_start_time,
// practice_end_time: competition.practice_end_time,
// competition_start_time: competition.competition_start_time,
// competition_duration: competition.competition_duration,
// practice_level_package: competition.practice_level_package,
// competition_level_package: competition.competition_level_package,
// participant_count: competition.participant_count,
// max_participants: competition.max_participants,
// created_at: competition.created_at
// };
// localStorage.setItem('competition_full_info', JSON.stringify(competitionFullInfo));
}
/**
* 格式化日期为YYYY-MM-DD HH:mm格式
* @param {Date} date - 日期对象
* @returns {string} 格式化后的日期字符串
*/
function formatDate(date) {
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
const hours = String(date.getHours()).padStart(2, '0');
const minutes = String(date.getMinutes()).padStart(2, '0');
return `${year}-${month}-${day} ${hours}:${minutes}`;
}
/**
* 获取比赛状态的中文描述
* @param {string} status - 比赛状态
* @returns {string} 状态的中文描述
*/
function getStatusText(status) {
const statusMap = {
'draft': '草稿',
'published': '已发布',
'ongoing': '进行中',
'ended': '已结束',
'cancelled': '已取消'
};
return statusMap[status] || status;
}
/**
* 获取比赛级别的中文描述
* @param {string} level - 比赛级别
* @returns {string} 级别的中文描述
*/
function getLevelText(level) {
const levelMap = {
'national': '全国级',
'provincial': '全省',
'city': '市级',
'district': '区级',
'school': '校级'
};
return levelMap[level] || level;
}
/**
* 切换密码可见性
*/
function togglePasswordVisibility() {
const passwordInput = document.getElementById('email-b064');
const eyeSlash = document.getElementById('eye-slash');
if (passwordInput.type === 'password') {
passwordInput.type = 'text';
eyeSlash.style.display = 'none';
} else {
passwordInput.type = 'password';
eyeSlash.style.display = 'block';
}
}
/**
* 调用登录API验证用户
* @param {string} username - 用户名
* @param {string} password - 密码
* @param {string} platformType - 平台类型 (practice/competition)
* @param {string} codetype - 编程语言类型 (可选)
*/
function loginUser(username, password, platformType, codetype = null) {
// 检查是否处于锁定状态
const currentTime = new Date().getTime();
if (currentTime < loginLockUntil) {
const remainingSeconds = Math.ceil((loginLockUntil - currentTime) / 1000);
showToast(`登录失败次数过多,请在${remainingSeconds}秒后再试`, 'error');
return;
}
// 获取提交按钮
const submitButton = document.querySelector('.u-btn-submit');
// 禁用按钮并添加加载状态
if (submitButton) {
submitButton.classList.add('btn-loading');
submitButton.setAttribute('disabled', 'disabled');
submitButton.style.pointerEvents = 'none';
}
// 显示加载状态
showToast('正在验证账号信息...', 'info');
// 构建请求数据
const requestData = {
username: username,
password: password,
tryuse: true // 允许试用
};
// 发送POST请求到登录API
fetch(`${CONFIG.DataServerBaseUrl}/api/login/`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(requestData)
})
.then(response => response.json())
.then(data => {
console.log('登录响应:', data);
if (data.success) {
// 登录成功,重置失败计数
loginFailCount = 0;
loginLockUntil = 0;
// 更新localStorage
localStorage.removeItem('loginFailCount');
localStorage.removeItem('loginLockUntil');
// 登录成功
showToast('登录成功', 'success');
// 获取比赛ID
const competitionId = getUrlParameter('competition_id');
localStorage.setItem('competition_id', competitionId);
// 保存用户信息到localStorage
localStorage.setItem('access_token', data.access);
localStorage.setItem('player_id', data.player_id);
localStorage.setItem('username', username);
localStorage.setItem('full_name', data.full_name);
localStorage.setItem('qrid', data.qrid);
localStorage.setItem('permission', data.permission);
localStorage.setItem('cityid', data.cityid);
localStorage.setItem('districtid', data.districtid);
localStorage.setItem('institutionid', data.institutionid);
// localStorage.setItem('competitionId', competitionId);
localStorage.setItem('platformType', platformType);
//TODO 将比赛练习时间放到localStorage中
// 保存当前页面URL到localStorage
// sessionStorage.setItem('lastpage', window.location.href);
// 存储当前页面的相对路径和参数
const currentPath = window.location.pathname + window.location.search;
sessionStorage.setItem('lastpage', currentPath.substring(1)); // 去掉开头的斜杠
if (competitionId) {
// 显示正在核验赛练信息的提示
showToast('正在核验赛练信息...', 'info');
// 检查用户是否在比赛中
checkUserInCompetition(data.access, competitionId, platformType, codetype);
} else {
// 没有比赛ID直接根据平台类型跳转
// redirectBasedOnPlatform(platformType);
showToast('此比赛链接错误', 'error');
}
} else {
// 登录失败,增加失败计数
loginFailCount++;
// 更新localStorage
localStorage.setItem('loginFailCount', loginFailCount);
// 检查是否达到最大失败次数
if (loginFailCount >= 5) {
// 设置锁定时间为当前时间 + 1分钟
loginLockUntil = new Date().getTime() + (60 * 1000);
localStorage.setItem('loginLockUntil', loginLockUntil);
showToast('登录失败次数过多请在1分钟后再试', 'error');
} else {
// 登录失败,显示错误信息
let errorMessage = data.message || '登录失败';
// 根据错误ID显示不同的错误信息
switch (data.id) {
case 2:
errorMessage = `用户名或密码错误<br>(第${loginFailCount}次失败5次后将暂时锁定)`;
break;
case 3:
errorMessage = '您没有权限登录或权限已过期';
break;
case 4:
errorMessage = '无效的请求方式';
break;
case 5:
errorMessage = '无效的请求格式';
break;
case 6:
errorMessage = '用户名和密码不能为空';
break;
default:
errorMessage = data.message || '登录失败';
}
showToast(errorMessage, 'error');
}
// 恢复按钮状态
if (submitButton) {
submitButton.classList.remove('btn-loading');
submitButton.removeAttribute('disabled');
submitButton.style.pointerEvents = 'auto';
}
}
})
.catch(error => {
console.error('登录请求失败:', error);
// 显示错误信息
showToast('网络错误,请稍后重试', 'error');
// 恢复按钮状态
if (submitButton) {
submitButton.classList.remove('btn-loading');
submitButton.removeAttribute('disabled');
submitButton.style.pointerEvents = 'auto';
}
});
}
/**
* 检查用户是否在比赛中
* @param {string} token - 用户访问令牌
* @param {string|number} competitionId - 比赛ID
* @param {string} platformType - 平台类型
* @param {string} codetype - 编程语言类型 (可选)
*/
function checkUserInCompetition(token, competitionId, platformType, codetype = null) {
// 如果是活动比赛,先检查绑定状态
const competitionIdInt = parseInt(competitionId, 10);
const isSSNCompetition = Array.isArray(CONFIG.SSN_COMPETITION_ID)
? CONFIG.SSN_COMPETITION_ID.includes(competitionIdInt)
: competitionIdInt === CONFIG.SSN_COMPETITION_ID;
if (isSSNCompetition) {
//调用第三方接口绑定数据
showToast('正在检查活动报名信息...', 'info');
// 先查询用户是否已经绑定
checkUserSSNBinding();
return; // 停止后续流程,等待绑定检查完成
}
// 构建请求数据
const requestData = {
competition_id: parseInt(competitionId, 10),
page: 1,
page_size: 10,
username: localStorage.getItem('username')
};
// 发送请求检查用户是否在比赛中
fetch(`${CONFIG.DataServerBaseUrl}/api/competition/query_competition_participants/`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
},
body: JSON.stringify(requestData)
})
.then(response => response.json())
.then(data => {
console.log('比赛参与者查询响应:', data);
// 检查比赛是否需要授权
const competition = window.competitionData;
const isAuthorized = competition && competition.is_authorized;
if (data.success && data.data && data.data.participants && data.data.participants.length > 0) {
// 用户在比赛中,跳转到比赛页面
localStorage.setItem("codetype", parseInt(data.data.participants[0].codetype, 10))
showToast('验证成功,正在进入赛练...', 'success');
window.location.href = 'competition_sum.html?competition_id=' + competitionId;
} else if (isAuthorized) {
// 比赛需要授权但用户未被授权
showToast('您尚无法参与此活动,请联系管理员', 'error');
// 恢复按钮状态
const submitButton = document.querySelector('.u-btn-submit');
if (submitButton) {
submitButton.classList.remove('btn-loading');
submitButton.removeAttribute('disabled');
submitButton.style.pointerEvents = 'auto';
}
} else {
// 用户不在比赛中,根据 codetype 或 type 参数处理
if (codetype) {
// 已经有 codetype直接加入比赛
joinCompetition(competitionId, codetype);
} else {
// 没有 codetype检查 URL 中的 type 参数
const types = getTypeListFromUrl();
if (types.length === 1) {
// 只有一个类型,直接加入比赛
joinCompetition(competitionId, types[0]);
} else {
// 多个类型,弹出选择模态框
showCodetypeModal(types);
showToast('请选择参赛类型', 'info');
// 恢复按钮状态
const submitButton = document.querySelector('.u-btn-submit');
if (submitButton) {
submitButton.classList.remove('btn-loading');
submitButton.removeAttribute('disabled');
submitButton.style.pointerEvents = 'auto';
}
}
}
}
})
.catch(error => {
console.error('检查用户是否在比赛中失败:', error);
showToast('验证赛练信息失败,请稍后重试', 'error');
// 恢复按钮状态
const submitButton = document.querySelector('.u-btn-submit');
if (submitButton) {
submitButton.classList.remove('btn-loading');
submitButton.removeAttribute('disabled');
submitButton.style.pointerEvents = 'auto';
}
});
}
/**
* 根据平台类型重定向到相应页面
* @param {string} platformType - 平台类型
*/
function redirectBasedOnPlatform(platformType) {
if (platformType === 'practice') {
// 跳转到训练平台
window.location.href = 'practice.html';
} else if (platformType === 'competition') {
// 跳转到比赛平台
window.location.href = 'exam.html';
}
}
/**
* 显示Toast消息
* @param {string} message - 消息内容
* @param {string} type - 消息类型 (success/error/info)
* @param {number} duration - 显示时间毫秒默认3000ms
*/
function showToast(message, type = 'info', duration = 3000) {
// 创建 Toast 容器(如果不存在)
let toastContainer = document.getElementById('toast-container');
if (!toastContainer) {
toastContainer = document.createElement('div');
toastContainer.id = 'toast-container';
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);
}
// 创建新的 Toast 元素
const toast = document.createElement('div');
toast.className = `toast ${type}`;
toast.innerHTML = message; // 使用innerHTML而不是textContent以支持HTML标签
// 添加到容器
toastContainer.appendChild(toast);
// 显示 Toast添加延迟以确保 CSS 过渡效果正常工作)
setTimeout(() => {
toast.style.opacity = '0.9';
toast.style.transform = 'translateY(0)';
}, 10);
// 指定时间后隐藏 Toast
setTimeout(() => {
toast.classList.add('fade-out');
// 动画完成后移除元素
setTimeout(() => {
if (toast.parentNode) {
toast.parentNode.removeChild(toast);
}
}, 500);
}, duration);
}
/**
* 加入比赛
* @param {string|number} competitionId - 比赛ID
* @param {string|number} codeType - 编码类型
*/
function joinCompetition(competitionId, codeType) {
// 获取提交按钮
const submitButton = document.querySelector('.u-btn-step');
// 禁用按钮并添加加载状态
if (submitButton) {
submitButton.classList.add('btn-loading');
submitButton.setAttribute('disabled', 'disabled');
submitButton.style.pointerEvents = 'none';
}
// 显示加载状态
showToast('正在加入比赛...', 'info');
// 获取访问令牌
const token = localStorage.getItem('access_token');
// 如果是活动比赛,需要更新活动编程语言
const competitionIdInt = parseInt(competitionId, 10);
const isSSNCompetition = Array.isArray(CONFIG.SSN_COMPETITION_ID)
? CONFIG.SSN_COMPETITION_ID.includes(competitionIdInt)
: competitionIdInt === CONFIG.SSN_COMPETITION_ID;
console.error(isSSNCompetition)
if (isSSNCompetition) {
// 等待更新完成,如果失败则不继续执行
updateSSNCodingLanguage(parseInt(codeType, 10), token)
.then(() => {
// 更新成功,继续执行加入比赛流程
executeJoinCompetition(competitionId, codeType, token);
})
.catch((error) => {
// 更新失败,结束流程
console.error('活动编程语言更新失败,终止加入比赛流程:', error);
// 恢复按钮状态
const submitButton = document.querySelector('.u-btn-step');
if (submitButton) {
submitButton.classList.remove('btn-loading');
submitButton.removeAttribute('disabled');
submitButton.style.pointerEvents = 'auto';
}
});
return; // 提前返回,不执行后续的加入比赛流程
}
// 如果不是比赛ID为11直接执行加入比赛流程
executeJoinCompetition(competitionId, codeType, token);
}
/**
* 执行加入比赛的核心逻辑
* @param {string|number} competitionId - 比赛ID
* @param {string|number} codeType - 编码类型
* @param {string} token - 访问令牌
*/
function executeJoinCompetition(competitionId, codeType, token) {
// 构建请求数据
const requestData = {
competition_id: parseInt(competitionId, 10),
has_practice_permission: true,
codetype: parseInt(codeType, 10)
};
// 发送请求加入比赛
fetch(`${CONFIG.DataServerBaseUrl}/api/competition/join_competition/`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
},
body: JSON.stringify(requestData)
})
.then(response => response.json())
.then(data => {
console.error('加入比赛响应:', data);
// 关闭模态框
const modal = document.getElementById('carousel_cd2f');
if (modal) {
modal.style.display = 'none';
}
if (data.success) {
// 加入比赛成功
showToast('加入比赛成功', 'success');
// 保存比赛信息到localStorage
if (data.data) {
localStorage.setItem('competition_id', data.data.competition.id);
localStorage.setItem('competition_name', data.data.competition.name);
localStorage.setItem('codetype', data.data.codetype);
localStorage.setItem('registration_time', data.data.registration_time);
}
// 延迟跳转到比赛页面
setTimeout(() => {
// 关闭模态框
closeModal();
window.location.href = 'competition_sum.html?competition_id=' + competitionId;
}, 1000);
} else {
// 加入比赛失败
showToast(data.message || '加入比赛失败', 'error');
// 恢复按钮状态
const submitButton = document.querySelector('.u-btn-step');
if (submitButton) {
submitButton.classList.remove('btn-loading');
submitButton.removeAttribute('disabled');
submitButton.style.pointerEvents = 'auto';
}
}
})
.catch(error => {
console.error('加入比赛请求失败:', error);
showToast('网络错误,请稍后重试', 'error');
// 恢复按钮状态
if (submitButton) {
submitButton.classList.remove('btn-loading');
submitButton.removeAttribute('disabled');
submitButton.style.pointerEvents = 'auto';
}
});
}
/**
* 关闭模态框
*/
function closeModal() {
// 尝试找到模态框的关闭按钮并点击它
const closeButton = document.getElementById('close-codetype-modal-button');
closeButton.click();
}
/**
* 加载省份数据
*/
function loadProvinces() {
fetch(`${CONFIG.DataServerBaseUrl}/api/provinces/`)
.then(response => response.json())
.then(data => {
const provinceSelect = document.getElementById('select-c2b7');
if (provinceSelect) {
// 清空现有选项,保留默认选项
provinceSelect.innerHTML = '<option value="省" data-calc="" selected="selected">省</option>';
// 添加新的省份选项
data.forEach(province => {
const option = document.createElement('option');
option.value = province.id;
option.textContent = province.name;
provinceSelect.appendChild(option);
});
}
})
.catch(error => {
console.error('加载省份数据失败:', error);
showToast('加载省份数据失败', 'error');
});
}
/**
* 加载城市数据
* @param {number} provinceId - 省份ID
*/
function loadCities(provinceId) {
fetch(`${CONFIG.DataServerBaseUrl}/api/cities/${provinceId}/`)
.then(response => response.json())
.then(data => {
const citySelect = document.getElementById('select-36ac');
if (citySelect) {
// 清空现有选项,保留默认选项
citySelect.innerHTML = '<option value="市" data-calc="" selected="selected">市</option>';
// 添加新的城市选项
data.forEach(city => {
const option = document.createElement('option');
option.value = city.id;
option.textContent = city.name;
citySelect.appendChild(option);
});
// 清空区县选择框
const districtSelect = document.getElementById('select-4860');
if (districtSelect) {
districtSelect.innerHTML = '<option value="区/县" data-calc="" selected="selected">区/县</option>';
}
}
})
.catch(error => {
console.error('加载城市数据失败:', error);
showToast('加载城市数据失败', 'error');
});
}
/**
* 加载区县数据
* @param {number} cityId - 城市ID
*/
function loadDistricts(cityId) {
fetch(`${CONFIG.DataServerBaseUrl}/api/districts/${cityId}/`)
.then(response => response.json())
.then(data => {
const districtSelect = document.getElementById('select-4860');
if (districtSelect) {
// 清空现有选项,保留默认选项
districtSelect.innerHTML = '<option value="区/县" data-calc="" selected="selected">区/县</option>';
// 添加新的区县选项
data.forEach(district => {
const option = document.createElement('option');
option.value = district.id;
option.textContent = district.name;
districtSelect.appendChild(option);
});
}
})
.catch(error => {
console.error('加载区县数据失败:', error);
showToast('加载区县数据失败', 'error');
});
}
/**
* 根据区县ID加载学校数据
* @param {string} districtId - 区县ID
*/
async function loadSchoolsByDistrict(districtId) {
try {
const response = await fetch(`${CONFIG.DataServerBaseUrl}/api/institutions/by-district-without-permission/`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
credentials: 'include',
body: JSON.stringify({
district_id: districtId,
page: 1,
page_size: 100 // 获取足够多的学校
})
});
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}
const result = await response.json();
console.log('Competition schools response:', result);
if (!result.success) {
throw new Error(result.message || '获取学校列表失败');
}
// 使用Competition页面的学校数据设置函数
if (result.data && result.data.institutions && result.data.institutions.length > 0) {
if (typeof window.populateSchoolsWithSearchComp === 'function') {
window.populateSchoolsWithSearchComp(result.data.institutions);
}
} else {
// 如果没有学校数据,清空搜索数据
if (typeof window.populateSchoolsWithSearchComp === 'function') {
window.populateSchoolsWithSearchComp([]);
}
}
console.log(`Competition page loaded ${result.data.institutions ? result.data.institutions.length : 0} schools.`);
} catch (error) {
console.error('Error loading schools for competition:', error);
showToast(`无法加载学校数据,请稍后再试。${error.message}`, 'error');
}
}
/**
* 加载年级数据
*/
function loadGrades() {
fetch(`${CONFIG.DataServerBaseUrl}/api/grades/`)
.then(response => response.json())
.then(data => {
const gradeSelect = document.getElementById('select-2088');
if (gradeSelect) {
// 清空现有选项,保留默认选项
gradeSelect.innerHTML = '<option value="年级" data-calc="" selected="selected">年级</option>';
// 添加新的年级选项
data.forEach(grade => {
const option = document.createElement('option');
option.value = grade.id;
option.textContent = grade.name;
gradeSelect.appendChild(option);
});
}
})
.catch(error => {
console.error('加载年级数据失败:', error);
showToast('加载年级数据失败', 'error');
});
}
/**
* 搜索学校 - 已废弃,现在使用弹窗搜索
* @param {string} keyword - 搜索关键词
* @param {HTMLElement} container - 显示结果的容器
*/
/*
function searchSchools(keyword, container) {
fetch(`${CONFIG.DataServerBaseUrl}/api/institutions/search/${encodeURIComponent(keyword)}/`)
.then(response => response.json())
.then(data => {
// 清空容器
container.innerHTML = '';
if (data && data.length > 0) {
// 创建学校列表
const ul = document.createElement('ul');
ul.className = 'school-list';
// 添加学校选项
data.forEach(school => {
const li = document.createElement('li');
li.className = 'school-item';
li.textContent = school.name;
li.dataset.id = school.id;
// 添加点击事件
li.addEventListener('click', function() {
const schoolInput = document.getElementById('text-21f2');
if (schoolInput) {
schoolInput.value = school.name;
// 存储学校ID用于提交表单
schoolInput.dataset.schoolId = school.id;
// 隐藏候选列表
container.style.display = 'none';
}
});
ul.appendChild(li);
});
// 添加到容器
container.appendChild(ul);
container.style.display = 'block';
} else {
container.style.display = 'none';
}
})
.catch(error => {
console.error('搜索学校失败:', error);
container.style.display = 'none';
});
}
*/
/**
* 验证注册表单
* @returns {boolean} 表单是否验证通过
*/
function validateRegisterForm() {
// 获取表单字段
const username = document.getElementById('phone-23d9').value.trim();
const password = document.getElementById('text-b46f').value.trim();
const fullName = document.getElementById('text-bf16').value.trim();
const phoneNumber = document.getElementById('text-8d13').value.trim();
const email = document.getElementById('email-594e').value.trim();
const province = document.getElementById('select-c2b7').value;
const city = document.getElementById('select-36ac').value;
const district = document.getElementById('select-4860').value;
// 获取选中的学校ID和名称
const selectedSchoolIdInputComp = document.getElementById('selected-school-id-comp');
const schoolSelectComp = document.getElementById('school-select-comp');
const selectedSchoolId = selectedSchoolIdInputComp ? selectedSchoolIdInputComp.value : '-1';
const school = schoolSelectComp && schoolSelectComp.options[0] ? schoolSelectComp.options[0].textContent : '';
const grade = document.getElementById('select-2088').value;
// 验证用户名
if (!username) {
showToast('请输入用户名', 'error');
return false;
}
if (username.length > 20) {
showToast('用户名不能超过20个字符', 'error');
return false;
}
// 用户名只能包含英文字母和数字,不允许中文和特殊符号
if (!/^[a-zA-Z0-9]+$/.test(username)) {
showToast('用户名只能包含英文字母和数字', 'error');
return false;
}
// 验证密码
if (!password) {
showToast('请输入密码', 'error');
return false;
}
if (password.length < 6) {
showToast('密码长度不能少于6个字符', 'error');
return false;
}
// 验证姓名
if (!fullName) {
showToast('请输入姓名', 'error');
return false;
}
if (fullName.length > 20) {
showToast('姓名不能超过20个字符', 'error');
return false;
}
// 姓名只能包含中英文字符
if (!/^[\u4e00-\u9fa5a-zA-Z]+$/.test(fullName)) {
showToast('姓名只能包含中英文字符', 'error');
return false;
}
// 验证电话号码
if (!phoneNumber) {
showToast('请输入电话号码', 'error');
return false;
}
// 电话号码必须是11位数字
if (!/^\d{11}$/.test(phoneNumber)) {
showToast('电话号码必须是11位数字', 'error');
return false;
}
// 验证邮箱
if (!email) {
showToast('请输入邮箱地址', 'error');
return false;
}
// 验证邮箱格式
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
showToast('请输入有效的邮箱地址', 'error');
return false;
}
// 验证省市区
if (province === '省') {
showToast('请选择省份', 'error');
return false;
}
if (city === '市') {
showToast('请选择城市', 'error');
return false;
}
if (district === '区/县') {
showToast('请选择区/县', 'error');
return false;
}
// 验证学校
if (!school || school === '请选择学校') {
showToast('请选择学校', 'error');
return false;
}
// 验证年级
if (grade === '年级') {
showToast('请选择年级', 'error');
return false;
}
// 所有验证通过
return true;
}
/**
* 设置密码输入框的显示/隐藏功能
*/
function setupPasswordToggle() {
// 登录表单密码框已经在HTML中设置好了
// 为注册表单的密码框添加显示/隐藏功能
const registerPasswordInput = document.getElementById('text-b46f');
if (registerPasswordInput) {
// 创建密码容器
const passwordContainer = document.createElement('div');
passwordContainer.className = 'password-container';
// 将密码输入框放入容器中
registerPasswordInput.parentNode.insertBefore(passwordContainer, registerPasswordInput);
passwordContainer.appendChild(registerPasswordInput);
// 确保密码框文本居中
registerPasswordInput.style.textAlign = 'center';
registerPasswordInput.style.paddingLeft = '40px';
registerPasswordInput.style.paddingRight = '40px';
registerPasswordInput.style.boxSizing = 'border-box';
// 创建切换按钮
const toggleButton = document.createElement('span');
toggleButton.className = 'password-toggle';
toggleButton.innerHTML = `
<svg id="register-password-toggle-icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"></path>
<circle cx="12" cy="12" r="3"></circle>
<line id="register-eye-slash" x1="3" y1="21" x2="21" y2="3" style="display: none;"></line>
</svg>
`;
// 添加点击事件
toggleButton.addEventListener('click', function () {
toggleRegisterPasswordVisibility();
});
// 将切换按钮添加到容器中
passwordContainer.appendChild(toggleButton);
}
}
/**
* 切换注册表单密码的可见性
*/
function toggleRegisterPasswordVisibility() {
const passwordInput = document.getElementById('text-b46f');
const eyeSlash = document.getElementById('register-eye-slash');
if (passwordInput && eyeSlash) {
if (passwordInput.type === 'password') {
passwordInput.type = 'text';
eyeSlash.style.display = 'block';
} else {
passwordInput.type = 'password';
eyeSlash.style.display = 'none';
}
}
}
/**
* 设置自定义平台选择下拉菜单
*/
function setupCustomPlatformSelect() {
const platformSelect = document.getElementById('select-58f1');
if (!platformSelect) return;
// 获取原始选项
const options = Array.from(platformSelect.options).map(option => ({
value: option.value,
text: option.textContent,
disabled: option.disabled
}));
// 创建自定义下拉菜单容器
const customSelectContainer = document.createElement('div');
customSelectContainer.className = 'custom-select-container';
// 创建显示选中值的元素
const selectedDisplay = document.createElement('div');
selectedDisplay.className = 'custom-select-selected';
selectedDisplay.textContent = options.find(opt => opt.value === platformSelect.value)?.text || '请选择平台';
selectedDisplay.setAttribute('data-value', platformSelect.value);
// 创建下拉选项容器
const optionsContainer = document.createElement('div');
optionsContainer.className = 'custom-select-options';
optionsContainer.style.display = 'none';
// 添加选项
options.forEach(option => {
const optionElement = document.createElement('div');
optionElement.className = 'custom-select-option';
optionElement.textContent = option.text;
optionElement.setAttribute('data-value', option.value);
if (option.disabled) {
optionElement.classList.add('disabled');
} else {
// 为可选选项添加点击事件
optionElement.addEventListener('click', function () {
const value = this.getAttribute('data-value');
platformSelect.value = value;
selectedDisplay.textContent = this.textContent;
selectedDisplay.setAttribute('data-value', value);
optionsContainer.style.display = 'none';
// 触发原始select的change事件
const event = new Event('change', { bubbles: true });
platformSelect.dispatchEvent(event);
});
}
optionsContainer.appendChild(optionElement);
});
// 点击显示/隐藏下拉选项
selectedDisplay.addEventListener('click', function (event) {
event.stopPropagation();
const isVisible = optionsContainer.style.display === 'block';
optionsContainer.style.display = isVisible ? 'none' : 'block';
});
// 点击页面其他地方关闭下拉菜单
document.addEventListener('click', function () {
optionsContainer.style.display = 'none';
});
// 组装自定义下拉菜单
customSelectContainer.appendChild(selectedDisplay);
customSelectContainer.appendChild(optionsContainer);
// 隐藏原始select元素及其父容器中的SVG箭头
platformSelect.style.display = 'none';
// 查找并隐藏原生下拉箭头
const parentWrapper = platformSelect.closest('.u-form-select-wrapper');
if (parentWrapper) {
const svgArrow = parentWrapper.querySelector('.u-caret');
if (svgArrow) {
svgArrow.style.display = 'none';
}
}
// 将自定义下拉菜单添加到DOM
if (parentWrapper) {
parentWrapper.appendChild(customSelectContainer);
} else {
platformSelect.parentNode.insertBefore(customSelectContainer, platformSelect.nextSibling);
}
// 监听原始select的变化更新自定义下拉菜单
const observer = new MutationObserver(function (mutations) {
mutations.forEach(function (mutation) {
if (mutation.type === 'attributes' && mutation.attributeName === 'disabled') {
updateCustomSelectOptions();
}
});
});
// 监听原始select的子元素变化
observer.observe(platformSelect, {
attributes: true,
childList: true,
subtree: true
});
// 更新自定义下拉菜单选项
function updateCustomSelectOptions() {
// 清空现有选项
optionsContainer.innerHTML = '';
// 获取最新选项
const updatedOptions = Array.from(platformSelect.options).map(option => ({
value: option.value,
text: option.textContent,
disabled: option.disabled
}));
// 重新添加选项
updatedOptions.forEach(option => {
const optionElement = document.createElement('div');
optionElement.className = 'custom-select-option';
optionElement.textContent = option.text;
optionElement.setAttribute('data-value', option.value);
if (option.disabled) {
optionElement.classList.add('disabled');
} else {
// 为可选选项添加点击事件
optionElement.addEventListener('click', function () {
const value = this.getAttribute('data-value');
platformSelect.value = value;
selectedDisplay.textContent = this.textContent;
selectedDisplay.setAttribute('data-value', value);
optionsContainer.style.display = 'none';
// 触发原始select的change事件
const event = new Event('change', { bubbles: true });
platformSelect.dispatchEvent(event);
});
}
optionsContainer.appendChild(optionElement);
});
// 更新选中显示
const selectedOption = updatedOptions.find(opt => opt.value === platformSelect.value);
if (selectedOption) {
selectedDisplay.textContent = selectedOption.text;
selectedDisplay.setAttribute('data-value', selectedOption.value);
}
}
console.log('自定义下拉菜单设置完成,准备显示引导提示');
// 在菜单设置完成后显示引导提示,确保元素已存在
// 稍微延迟确保DOM已更新
setTimeout(() => showPlatformGuide(), 500);
}
/**
* 更新平台选择下拉框状态
*/
function updatePlatformSelectStatus() {
// 如果比赛信息和服务器时间都已获取
if (competitionInfo && serverTime) {
// 判断训练时间模块的显示和隐藏
const practiceEndTimestamp = convertUTCToBeijingTime(competitionInfo.practice_end_time).getTime() / 1000
if(serverTime.timestamp > practiceEndTimestamp){
const practiceTimeSection = document.querySelector('.meta-line');
practiceTimeSection.style.display = 'none';
}
const platformSelect = document.getElementById('select-58f1');
if (!platformSelect) return;
const practiceOption = platformSelect.querySelector('option[value="practice"]');
const competitionOption = platformSelect.querySelector('option[value="competition"]');
if (!practiceOption || !competitionOption) return;
// 获取当前时间戳(秒)
const currentTimestamp = serverTime.timestamp;
// 获取练习时间范围 - 使用转换后的北京时间
const practiceStartTime = convertUTCToBeijingTime(competitionInfo.practice_start_time).getTime() / 1000;
const practiceEndTime = convertUTCToBeijingTime(competitionInfo.practice_end_time).getTime() / 1000;
// 获取比赛日期(不考虑具体时间) - 使用转换后的北京时间
const competitionDate = convertUTCToBeijingTime(competitionInfo.competition_start_time);
competitionDate.setHours(0, 0, 0, 0); // 设置为当天的开始时间
const competitionDateStart = competitionDate.getTime() / 1000;
// 比赛日结束时间当天的23:59:59
const competitionDateEnd = competitionDateStart + (24 * 60 * 60) - 1;
console.log('当前时间戳:', currentTimestamp, new Date(currentTimestamp * 1000).toLocaleString());
console.log('练习开始时间:', practiceStartTime, new Date(practiceStartTime * 1000).toLocaleString());
console.log('练习结束时间:', practiceEndTime, new Date(practiceEndTime * 1000).toLocaleString());
console.log('比赛日开始时间:', competitionDateStart, new Date(competitionDateStart * 1000).toLocaleString());
console.log('比赛日结束时间:', competitionDateEnd, new Date(competitionDateEnd * 1000).toLocaleString());
// 检查是否在练习时间内
const isPracticeTime = currentTimestamp >= practiceStartTime && currentTimestamp <= practiceEndTime;
// 检查是否在比赛日当天
const isCompetitionDay = currentTimestamp >= competitionDateStart && currentTimestamp <= competitionDateEnd;
console.log('是否在练习时间内:', isPracticeTime);
console.log('是否在比赛日当天:', isCompetitionDay);
// 更新自定义下拉菜单
const customSelectContainer = document.querySelector('.custom-select-container');
if (customSelectContainer) {
const optionsContainer = customSelectContainer.querySelector('.custom-select-options');
if (optionsContainer) {
const practiceCustomOption = optionsContainer.querySelector('[data-value="practice"]');
const competitionCustomOption = optionsContainer.querySelector('[data-value="competition"]');
if (practiceCustomOption) {
if (!isPracticeTime) {
practiceCustomOption.classList.add('disabled');
practiceCustomOption.textContent = "训练平台 (不在训练时间内)";
} else {
practiceCustomOption.classList.remove('disabled');
practiceCustomOption.textContent = "训练平台";
}
}
if (competitionCustomOption) {
if (!isCompetitionDay) {
competitionCustomOption.classList.add('disabled');
competitionCustomOption.textContent = "比赛平台 (不在比赛日期)";
} else {
competitionCustomOption.classList.remove('disabled');
competitionCustomOption.textContent = "比赛平台";
}
}
}
}
}
}
/**
* 验证当前时间是否允许选择指定的平台
* @param {string} platformType - 平台类型 ('practice' 或 'competition')
* @returns {boolean} 是否允许选择
*/
function validateTimeForPlatform(platformType) {
// 如果比赛信息或服务器时间未获取,允许操作
if (!competitionInfo || !serverTime) return true;
// 获取当前时间戳(秒)
const currentTimestamp = serverTime.timestamp;
if (platformType === 'practice') {
// 获取练习时间范围 - 使用转换后的北京时间
const practiceStartTime = convertUTCToBeijingTime(competitionInfo.practice_start_time).getTime() / 1000;
const practiceEndTime = convertUTCToBeijingTime(competitionInfo.practice_end_time).getTime() / 1000;
// 检查是否在练习时间内
if (currentTimestamp < practiceStartTime) {
showToast('训练尚未开始,请在训练开始时间后再试', 'error');
return false;
} else if (currentTimestamp > practiceEndTime) {
showToast('训练已结束,无法进入训练平台', 'error');
return false;
}
} else if (platformType === 'competition') {
// 获取比赛日期(不考虑具体时间) - 使用转换后的北京时间
const competitionDate = convertUTCToBeijingTime(competitionInfo.competition_start_time);
let competitionDateStart, competitionDateEnd;
// 检查是否为模拟比赛
if (CONFIG.SIMULATE_COMPETITION_ID.includes(competitionInfo.id)) {
// 模拟比赛:按具体开始时间和时长计算
competitionDateStart = competitionDate.getTime() / 1000;
competitionDateEnd = competitionDateStart + Number(competitionInfo.competition_duration) * 60;
} else {
// 正式比赛:当天全天有效 (00:00:00 - 23:59:59)
competitionDate.setHours(0, 0, 0, 0);
competitionDateStart = competitionDate.getTime() / 1000;
competitionDateEnd = competitionDateStart + (24 * 60 * 60) - 1;
}
// 检查是否在比赛日当天
if (currentTimestamp < competitionDateStart) {
showToast('比赛尚未开始,请在比赛日期再试', 'error');
return false;
} else if (currentTimestamp > competitionDateEnd) {
showToast('比赛已结束,无法进入比赛平台', 'error');
return false;
}
}
return true;
}
/**
* 检查用户是否已经绑定活动信息
*/
function checkUserSSNBinding() {
// 获取用户信息
const playerId = localStorage.getItem('player_id');
const token = localStorage.getItem('access_token');
if (!playerId || !token) {
showToast('用户信息获取失败', 'error');
// 恢复登录按钮状态
const submitButton = document.querySelector('.u-btn-submit');
if (submitButton) {
submitButton.classList.remove('btn-loading');
submitButton.removeAttribute('disabled');
submitButton.style.pointerEvents = 'auto';
}
return;
}
// 构建请求数据
const requestData = {
user_id: playerId
};
// 发送请求查询用户绑定状态
fetch(`${CONFIG.DataServerBaseUrl}/api/v1/third-party/get-user-ssn/`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
},
body: JSON.stringify(requestData)
})
.then(response => response.json())
.then(data => {
console.log('用户SSN绑定状态查询响应:', data);
if (data.success) {
// 检查是否有绑定记录
if (data.data && data.data.ssn_records && data.data.ssn_records.length > 0) {
// 用户已经绑定过,存储绑定信息并继续流程
showToast('检测到已绑定活动报名信息', 'success');
// 存储绑定信息(使用最新的一条记录)
const latestRecord = data.data.ssn_records[0];
localStorage.setItem('ssn_register_info', JSON.stringify(latestRecord));
// 存储SSN用户信息到sessionStorage用于在competition_sum页面显示
const ssnUserInfo = {
ssn_register_id: latestRecord.ssn_register_id,
name: latestRecord.name,
id_card: latestRecord.id_card,
org: latestRecord.org
};
sessionStorage.setItem('ssn_user_info', JSON.stringify(ssnUserInfo));
// 延迟后继续原来的流程
setTimeout(() => {
continueCheckUserInCompetition();
}, 1000);
} else {
// 用户尚未绑定,显示绑定弹窗
showToast('请绑定活动报名信息', 'info');
showSSNBindingModal();
}
} else {
// 查询失败,显示绑定弹窗(保险起见)
showToast('无法检查绑定状态,请手动绑定', 'info');
showSSNBindingModal();
}
})
.catch(error => {
console.error('查询用户SSN绑定状态失败:', error);
// 网络错误,显示绑定弹窗(保险起见)
showToast('网络错误,请手动绑定活动报名信息', 'info');
showSSNBindingModal();
});
}
/**
* 显示活动身份证绑定弹窗
*/
function showSSNBindingModal() {
// 创建弹窗蒙层
const overlay = document.createElement('div');
overlay.id = 'ssn-binding-overlay';
overlay.style.cssText = `
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.7);
display: flex;
align-items: center;
justify-content: center;
z-index: 99999;
backdrop-filter: blur(5px);
`;
// 创建弹窗内容
const modal = document.createElement('div');
modal.style.cssText = `
background: #FFFFFF;
padding: 32px;
border-radius: 10px;
box-shadow: 0px 34px 44px -20px rgba(185, 206, 234, 0.25);
text-align: center;
max-width: 450px;
width: 90%;
position: relative;
display: flex;
flex-direction: column;
gap: 24px;
`;
modal.innerHTML = `
<div style="width: 100%; display: flex; justify-content: space-between; align-items: center;">
<div style="width: 32px;"></div>
<h2 style="flex: 1; font-family: Nunito, system-ui, sans-serif; font-weight: 800;
font-size: 1.5rem; text-align: center; color: #020F30; margin: 0;">
活动报名信息绑定
</h2>
<button id="ssn-close-btn" style="width: 32px; height: 32px; border: 0;
background: transparent; cursor: pointer;
padding: 0; display: flex; align-items: center;
justify-content: center;">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#64748b"
stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<line x1="18" y1="6" x2="6" y2="18"></line>
<line x1="6" y1="6" x2="18" y2="18"></line>
</svg>
</button>
</div>
<div style="width: 100%;">
<p style="color: #475569; margin: 0 0 20px 0; line-height: 1.6; font-size: 14px;">
请输入您在活动平台报名时使用的身份证号码,系统将自动获取并绑定您的报名信息。
</p>
<div style="margin-bottom: 0;">
<input type="text" id="ssn-id-card" placeholder="请输入18位身份证号码"
style="width: 100%; height: 48px; border-radius: 8px;
border: 2px solid #e2e8f0; background: #f5f6f9;
padding: 0 16px; font-size: 14px; text-align: center;
box-sizing: border-box; outline: none; transition: all 0.2s ease;
font-family: 'PingFang SC', Inter, system-ui, sans-serif;
color: #1e1a4c;"
maxlength="18">
</div>
</div>
<div style="display: flex; gap: 12px; justify-content: center; width: 100%;">
<button id="ssn-cancel-btn" style="flex: 1; height: 48px; border-radius: 32px;
border: 0; background: #94a3b8; color: #ffffff;
cursor: pointer; font-size: 14px; font-weight: 400;
transition: all 0.3s ease; font-family: 'Noto Sans SC', Inter, system-ui, sans-serif;
box-shadow: 0 2px 8px rgba(148, 163, 184, 0.3);">
取 消
</button>
<button id="ssn-bind-btn" style="flex: 1; height: 48px; border-radius: 32px;
border: 0; background: linear-gradient(90deg, rgba(3, 161, 234, 1) 0%, rgba(32, 91, 175, 1) 100%);
color: #ffffff; cursor: pointer; font-size: 14px;
font-weight: 400; transition: all 0.3s ease;
font-family: 'Noto Sans SC', Inter, system-ui, sans-serif;
box-shadow: 0 2px 8px rgba(3, 161, 234, 0.3);">
确认绑定
</button>
</div>
`;
overlay.appendChild(modal);
document.body.appendChild(overlay);
// 获取元素
const idCardInput = document.getElementById('ssn-id-card');
const bindBtn = document.getElementById('ssn-bind-btn');
const cancelBtn = document.getElementById('ssn-cancel-btn');
const closeBtn = document.getElementById('ssn-close-btn');
// 输入框聚焦效果
idCardInput.addEventListener('focus', function () {
this.style.borderColor = '#5380ea';
this.style.background = '#ffffff';
this.style.boxShadow = '0 0 0 3px rgba(83, 128, 234, 0.1)';
});
idCardInput.addEventListener('blur', function () {
this.style.borderColor = '#e2e8f0';
this.style.background = '#f5f6f9';
this.style.boxShadow = 'none';
});
// 关闭按钮事件
closeBtn.addEventListener('click', function () {
document.body.removeChild(overlay);
});
// 身份证号码格式验证
function validateIdCard(idCard) {
const idCardRegex = /^[1-9]\d{5}(18|19|20)\d{2}((0[1-9])|(1[0-2]))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$/;
return idCardRegex.test(idCard);
}
// 输入框实时验证
idCardInput.addEventListener('input', function () {
const idCard = this.value.trim();
if (idCard.length === 18) {
if (validateIdCard(idCard)) {
bindBtn.disabled = false;
bindBtn.style.opacity = '1';
bindBtn.style.cursor = 'pointer';
} else {
bindBtn.disabled = true;
bindBtn.style.opacity = '0.5';
bindBtn.style.cursor = 'not-allowed';
}
} else {
bindBtn.disabled = true;
bindBtn.style.opacity = '0.5';
bindBtn.style.cursor = 'not-allowed';
}
});
// 为绑定按钮添加悬停效果
bindBtn.addEventListener('mouseenter', function () {
this.style.transform = 'scale(1.02)';
this.style.boxShadow = '7px 7px 25px 0 rgba(0,0,0,0.5)';
});
bindBtn.addEventListener('mouseleave', function () {
this.style.transform = 'scale(1)';
this.style.boxShadow = '5px 5px 20px 0 rgba(0,0,0,0.4)';
});
// 为取消按钮添加悬停效果
cancelBtn.addEventListener('mouseenter', function () {
this.style.transform = 'scale(1.02)';
this.style.boxShadow = '7px 7px 25px 0 rgba(0,0,0,0.5)';
});
cancelBtn.addEventListener('mouseleave', function () {
this.style.transform = 'scale(1)';
this.style.boxShadow = '5px 5px 20px 0 rgba(0,0,0,0.4)';
});
// 绑定按钮点击事件
bindBtn.addEventListener('click', function () {
const idCard = idCardInput.value.trim();
if (!validateIdCard(idCard)) {
showToast('请输入有效的身份证号码', 'error');
return;
}
// 调用绑定接口
bindSSNRegisterInfo(idCard, overlay);
});
// 取消按钮点击事件
cancelBtn.addEventListener('click', function () {
document.body.removeChild(overlay);
// 恢复登录按钮状态
const submitButton = document.querySelector('.u-btn-submit');
if (submitButton) {
submitButton.classList.remove('btn-loading');
submitButton.removeAttribute('disabled');
submitButton.style.pointerEvents = 'auto';
}
showToast('已取消绑定', 'info');
});
// 点击蒙层关闭弹窗
overlay.addEventListener('click', function (e) {
if (e.target === overlay) {
cancelBtn.click();
}
});
// 回车键提交
idCardInput.addEventListener('keypress', function (e) {
if (e.key === 'Enter' && !bindBtn.disabled) {
//bindBtn.click();
}
});
// 自动聚焦到输入框
setTimeout(() => {
idCardInput.focus();
}, 100);
}
/**
* 调用活动绑定接口
* @param {string} idCard - 身份证号码
* @param {HTMLElement} overlay - 弹窗蒙层元素
*/
function bindSSNRegisterInfo(idCard, overlay) {
const bindBtn = document.getElementById('ssn-bind-btn');
const cancelBtn = document.getElementById('ssn-cancel-btn');
// 禁用按钮,显示加载状态
bindBtn.disabled = true;
bindBtn.style.opacity = '0.7';
bindBtn.style.cursor = 'not-allowed';
bindBtn.textContent = '绑定中...';
cancelBtn.disabled = true;
cancelBtn.style.opacity = '0.5';
// 显示加载提示
showToast('正在绑定活动报名信息...', 'info');
// 获取用户信息
const playerId = localStorage.getItem('player_id');
const token = localStorage.getItem('access_token');
// 构建请求数据
const requestData = {
id_card: idCard,
user_id: playerId
};
// 发送请求到绑定接口
fetch(`${CONFIG.DataServerBaseUrl}/api/v1/third-party/ssn-register/`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
},
body: JSON.stringify(requestData)
})
.then(response => response.json())
.then(data => {
console.log('活动绑定响应:', data);
if (data.success) {
// 绑定成功
showToast('活动报名信息绑定成功', 'success');
// 存储绑定信息
if (data.data && data.data.ssn_register_info) {
localStorage.setItem('ssn_register_info', JSON.stringify(data.data.ssn_register_info));
// 存储SSN用户信息到sessionStorage用于在competition_sum页面显示
const ssnUserInfo = {
ssn_register_id: data.data.ssn_register_info.ssn_register_id,
name: data.data.ssn_register_info.name,
id_card: data.data.ssn_register_info.id_card,
org: data.data.ssn_register_info.org
};
sessionStorage.setItem('ssn_user_info', JSON.stringify(ssnUserInfo));
}
// 关闭弹窗
document.body.removeChild(overlay);
// 延迟后继续原来的流程
setTimeout(() => {
// 继续原来的checkUserInCompetition流程
continueCheckUserInCompetition();
}, 1000);
} else {
// 绑定失败
let errorMessage = '绑定失败';
if (data.message) {
errorMessage = data.message;
} else if (data.data && data.data.third_party_error_code) {
errorMessage = `绑定失败:第三方接口错误码 ${data.data.third_party_error_code}`;
}
showToast(errorMessage, 'error');
// 恢复按钮状态
bindBtn.disabled = false;
bindBtn.style.opacity = '1';
bindBtn.style.cursor = 'pointer';
bindBtn.textContent = '确认绑定';
cancelBtn.disabled = false;
cancelBtn.style.opacity = '1';
// 关闭弹窗并恢复登录按钮状态
setTimeout(() => {
document.body.removeChild(overlay);
const submitButton = document.querySelector('.u-btn-submit');
if (submitButton) {
submitButton.classList.remove('btn-loading');
submitButton.removeAttribute('disabled');
submitButton.style.pointerEvents = 'auto';
}
}, 2000);
}
})
.catch(error => {
console.error('活动绑定请求失败:', error);
showToast('网络错误,绑定失败', 'error');
// 恢复按钮状态
bindBtn.disabled = false;
bindBtn.style.opacity = '1';
bindBtn.style.cursor = 'pointer';
bindBtn.textContent = '确认绑定';
cancelBtn.disabled = false;
cancelBtn.style.opacity = '1';
// 关闭弹窗并恢复登录按钮状态
setTimeout(() => {
document.body.removeChild(overlay);
const submitButton = document.querySelector('.u-btn-submit');
if (submitButton) {
submitButton.classList.remove('btn-loading');
submitButton.removeAttribute('disabled');
submitButton.style.pointerEvents = 'auto';
}
}, 2000);
});
}
/**
* 更新活动编程语言
* @param {number} codeType - 编程语言类型 (1-图形化, 2-Python, 3-C++)
* @param {string} token - 访问令牌
* @returns {Promise} - 返回Promise成功时resolve失败时reject
*/
function updateSSNCodingLanguage(codeType, token) {
return new Promise((resolve, reject) => {
// 获取用户ID
const playerId = localStorage.getItem('player_id');
if (!playerId) {
const error = '无法获取用户ID跳过活动编程语言更新';
console.error(error);
showToast(error, 'error');
reject(new Error(error));
return;
}
// 显示更新提示
showToast('正在更新活动编程语言...', 'info');
// 构建请求数据
const requestData = {
user_id: playerId,
coding_language_type: codeType
};
// 发送请求更新编程语言
fetch(`${CONFIG.DataServerBaseUrl}/api/v1/third-party/update-coding-language/`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
},
body: JSON.stringify(requestData)
})
.then(response => response.json())
.then(data => {
console.log('更新活动编程语言响应:', data);
if (data.success) {
// 更新成功
const languageNames = {
1: '图形化',
2: 'Python',
3: 'C++'
};
const languageName = languageNames[codeType] || '未知';
showToast(`活动编程语言已更新为:${languageName}`, 'success');
// 更新本地存储的SSN信息
if (data.data && data.data.updated_record) {
const ssnInfo = JSON.parse(localStorage.getItem('ssn_register_info') || '{}');
ssnInfo.coding_language = data.data.new_coding_language;
localStorage.setItem('ssn_register_info', JSON.stringify(ssnInfo));
}
// 成功时resolve
resolve(data);
} else {
// 更新失败
let errorMessage = '活动编程语言更新失败';
if (data.message) {
errorMessage = data.message;
} else if (data.data && data.data.third_party_error_code) {
errorMessage = `更新失败:第三方接口错误码 ${data.data.third_party_error_code}`;
}
showToast(errorMessage, 'error');
console.error('活动编程语言更新失败:', data);
// 失败时reject
reject(new Error(errorMessage));
}
})
.catch(error => {
const errorMessage = '网络错误,活动编程语言更新失败';
console.error('更新活动编程语言请求失败:', error);
showToast(errorMessage, 'error');
// 网络错误时reject
reject(error);
});
});
}
/**
* 继续执行checkUserInCompetition流程绑定成功后
*/
function continueCheckUserInCompetition() {
// 绑定成功后,需要执行完整的参与者查询逻辑
// 获取必要的参数
const token = localStorage.getItem('access_token');
const competitionId = getUrlParameter('competition_id');
const platformType = localStorage.getItem('platformType');
if (!token || !competitionId) {
showToast('用户信息获取失败', 'error');
// 恢复按钮状态
const submitButton = document.querySelector('.u-btn-submit');
if (submitButton) {
submitButton.classList.remove('btn-loading');
submitButton.removeAttribute('disabled');
submitButton.style.pointerEvents = 'auto';
}
return;
}
// 构建请求数据
const requestData = {
competition_id: parseInt(competitionId, 10),
page: 1,
page_size: 10,
username: localStorage.getItem('username')
};
// 发送请求检查用户是否在比赛中
fetch(`${CONFIG.DataServerBaseUrl}/api/competition/query_competition_participants/`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
},
body: JSON.stringify(requestData)
})
.then(response => response.json())
.then(data => {
console.log('绑定后的比赛参与者查询响应:', data);
// 检查比赛是否需要授权
const competition = window.competitionData;
const isAuthorized = competition && competition.is_authorized;
if (data.success && data.data && data.data.participants && data.data.participants.length > 0) {
// 用户在比赛中,跳转到比赛页面
localStorage.setItem("codetype", parseInt(data.data.participants[0].codetype, 10))
showToast('验证成功,正在进入赛练...', 'success');
window.location.href = 'competition_sum.html?competition_id=' + competitionId;
} else if (isAuthorized) {
// 比赛需要授权但用户未被授权
showToast('您尚无法参与此活动,请联系管理员', 'error');
// 恢复按钮状态
const submitButton = document.querySelector('.u-btn-submit');
if (submitButton) {
submitButton.classList.remove('btn-loading');
submitButton.removeAttribute('disabled');
submitButton.style.pointerEvents = 'auto';
}
} else {
// 用户不在比赛中,需要检查是否为活动比赛以及语言选择状态
const competitionIdInt = parseInt(competitionId, 10);
const isSSNCompetition = Array.isArray(CONFIG.SSN_COMPETITION_ID)
? CONFIG.SSN_COMPETITION_ID.includes(competitionIdInt)
: competitionIdInt === CONFIG.SSN_COMPETITION_ID;
if (isSSNCompetition) {
// 活动比赛,检查语言选择状态
const ssnInfo = JSON.parse(localStorage.getItem('ssn_register_info') || '{}');
const hasChangedLanguage = ssnInfo.has_changed_language;
const codingLanguage = ssnInfo.coding_language;
if (hasChangedLanguage) {
// 已经修改过语言,显示选择框但用户不可修改
showToast('请确认参赛类型(您已选择过编程语言)', 'info');
// 弹出选择codetype的modal
const competitionInfoLink = document.getElementById('competition-info-link');
if (competitionInfoLink) {
competitionInfoLink.click();
// 延迟设置选择框状态,确保模态框已打开
setTimeout(() => {
setCodetypeModalForSSN(codingLanguage, true); // true表示不可修改
}, 500);
}
} else {
// 未修改过语言,用户可以选择
showToast('请选择参赛类型', 'info');
// 弹出选择codetype的modal
const competitionInfoLink = document.getElementById('competition-info-link');
if (competitionInfoLink) {
competitionInfoLink.click();
// 延迟设置选择框状态,确保模态框已打开
setTimeout(() => {
setCodetypeModalForSSN(codingLanguage, false); // false表示可以修改
}, 500);
}
}
} else {
// 非活动比赛,根据 type 参数处理
const types = getTypeListFromUrl();
if (types.length === 1) {
// 只有一个类型,直接加入比赛
joinCompetition(competitionId, types[0]);
} else {
// 多个类型,弹出选择模态框
showCodetypeModal(types);
showToast('请选择参赛类型', 'info');
}
}
// 恢复按钮状态
const submitButton = document.querySelector('.u-btn-submit');
if (submitButton) {
submitButton.classList.remove('btn-loading');
submitButton.removeAttribute('disabled');
submitButton.style.pointerEvents = 'auto';
}
}
})
.catch(error => {
console.error('绑定后检查用户是否在比赛中失败:', error);
showToast('验证赛练信息失败,请稍后重试', 'error');
// 恢复按钮状态
const submitButton = document.querySelector('.u-btn-submit');
if (submitButton) {
submitButton.classList.remove('btn-loading');
submitButton.removeAttribute('disabled');
submitButton.style.pointerEvents = 'auto';
}
});
}
/**
* 设置活动比赛的编程语言选择框状态
* @param {string} codingLanguage - 编程语言
* @param {boolean} isReadOnly - 是否为只读状态
*/
function setCodetypeModalForSSN(codingLanguage, isReadOnly) {
const codeTypeSelect = document.getElementById('select-8c9b');
if (!codeTypeSelect) {
console.error('未找到编程语言选择框');
return;
}
// 编程语言映射API返回的字符串 -> 选择框的value
const languageMapping = {
'图形化': '1',
'Python': '2',
'C++': '3'
};
// 设置选择框的值
const selectValue = languageMapping[codingLanguage] || '1'; // 默认为图形化
codeTypeSelect.value = selectValue;
if (isReadOnly) {
// 设置为只读状态
codeTypeSelect.disabled = true;
codeTypeSelect.style.opacity = '0.6';
codeTypeSelect.style.cursor = 'not-allowed';
// 为父容器添加样式指示这是只读的
const selectWrapper = codeTypeSelect.closest('.u-form-select-wrapper');
if (selectWrapper) {
selectWrapper.style.opacity = '0.6';
selectWrapper.style.pointerEvents = 'none';
}
// 添加提示文本
const modalContainer = codeTypeSelect.closest('.u-dialog');
if (modalContainer) {
let readOnlyHint = modalContainer.querySelector('.ssn-readonly-hint');
if (!readOnlyHint) {
readOnlyHint = document.createElement('p');
readOnlyHint.className = 'ssn-readonly-hint';
readOnlyHint.style.cssText = `
color: rgb(0, 0, 0);
font-size: 0.75rem;
text-align: center;
margin: 5px 0;
font-weight: normal;
width: 100%;
max-width: 100%;
box-sizing: border-box;
word-wrap: break-word;
white-space: normal;
line-height: 1.3;
padding: 0;
`;
readOnlyHint.textContent = '您已选择过编程语言,无法修改';
// 插入到提交按钮的上方
const submitContainer = modalContainer.querySelector('.u-form-submit');
if (submitContainer) {
submitContainer.parentNode.insertBefore(readOnlyHint, submitContainer);
} else {
// 如果找不到提交按钮容器,则插入到选择框后面
const selectContainer = codeTypeSelect.closest('.u-form-group');
if (selectContainer) {
selectContainer.parentNode.insertBefore(readOnlyHint, selectContainer.nextSibling);
}
}
}
}
} else {
// 设置为可编辑状态
codeTypeSelect.disabled = false;
codeTypeSelect.style.opacity = '1';
codeTypeSelect.style.cursor = 'pointer';
// 恢复父容器样式
const selectWrapper = codeTypeSelect.closest('.u-form-select-wrapper');
if (selectWrapper) {
selectWrapper.style.opacity = '1';
selectWrapper.style.pointerEvents = 'auto';
}
// 移除只读提示文本
const modalContainer = codeTypeSelect.closest('.u-dialog');
if (modalContainer) {
const readOnlyHint = modalContainer.querySelector('.ssn-readonly-hint');
if (readOnlyHint) {
readOnlyHint.remove();
}
}
// 添加可选择提示文本
const modalContainer2 = codeTypeSelect.closest('.u-dialog');
if (modalContainer2) {
let editableHint = modalContainer2.querySelector('.ssn-editable-hint');
if (!editableHint) {
editableHint = document.createElement('p');
editableHint.className = 'ssn-editable-hint';
editableHint.style.cssText = `
color: rgb(0, 0, 0);
font-size: 0.75rem;
text-align: center;
margin: 5px 0;
font-weight: normal;
width: 100%;
max-width: 100%;
box-sizing: border-box;
word-wrap: break-word;
white-space: normal;
line-height: 1.3;
padding: 0;
`;
editableHint.textContent = '请选择编程语言(选择后无法修改)';
// 插入到提交按钮的上方
const submitContainer = modalContainer2.querySelector('.u-form-submit');
if (submitContainer) {
submitContainer.parentNode.insertBefore(editableHint, submitContainer);
} else {
// 如果找不到提交按钮容器,则插入到选择框后面
const selectContainer = codeTypeSelect.closest('.u-form-group');
if (selectContainer) {
selectContainer.parentNode.insertBefore(editableHint, selectContainer.nextSibling);
}
}
}
}
}
}
/**
* 注册新用户
*/
function registerUser() {
// 验证表单
if (!validateRegisterForm()) {
return;
}
// 获取表单数据
const username = document.getElementById('phone-23d9').value.trim();
const password = document.getElementById('text-b46f').value.trim();
const fullName = document.getElementById('text-bf16').value.trim();
const phoneNumber = document.getElementById('text-8d13').value.trim();
const email = document.getElementById('email-594e').value.trim();
const province = document.getElementById('select-c2b7').value;
const city = document.getElementById('select-36ac').value;
const district = document.getElementById('select-4860').value;
// 获取选中的学校ID和名称
const selectedSchoolIdInputComp = document.getElementById('selected-school-id-comp');
const schoolSelectComp = document.getElementById('school-select-comp');
const selectedSchoolId = selectedSchoolIdInputComp ? selectedSchoolIdInputComp.value : '-1';
const school = schoolSelectComp && schoolSelectComp.options[0] ? schoolSelectComp.options[0].textContent : '';
const grade = document.getElementById('select-2088').value;
// 获取注册按钮并禁用
const registerButton = document.querySelector('.u-dialog-section-6 .u-btn-step');
if (registerButton) {
registerButton.disabled = true;
registerButton.classList.add('btn-loading');
registerButton.textContent = '注册中...';
}
// 显示注册中的提示
showToast('正在注册,请稍候...', 'info');
// 使用已选择的学校ID
const institutionId = selectedSchoolId && selectedSchoolId !== '-1' ? parseInt(selectedSchoolId) : -1;
// 构建请求数据
const requestData = {
username: username,
password: password,
full_name: fullName,
phone_number: phoneNumber,
email: email,
province: province,
city: city,
district: district,
school_name: school,
grade: grade,
institution: institutionId // 使用选择的学校ID
};
console.log('注册数据:', requestData);
// 发送注册请求
fetch(`${CONFIG.DataServerBaseUrl}/api/register/`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(requestData)
})
.then(response => {
// 解析响应
return response.json().then(data => {
// 将响应数据和状态码一起返回
return { data, status: response.status };
});
})
.then(({ data, status }) => {
// 恢复按钮状态
if (registerButton) {
registerButton.disabled = false;
registerButton.classList.remove('btn-loading');
registerButton.textContent = '注册';
}
// 处理响应
if (status === 200 || status === 201) {
// 注册成功
showToast('注册成功!', 'success');
// 关闭注册模态框
setTimeout(() => {
// 使用正确的关闭按钮ID
const closeButton = document.querySelector('.u-dialog-section-6 .u-dialog-close-button');
if (closeButton) {
closeButton.click();
}
// 自动填充登录表单
document.getElementById('name-b064').value = username;
document.getElementById('email-b064').value = password;
// 提示用户选择练习或比赛模式
setTimeout(() => {
showToast('请选择练习或比赛模式登录此比赛', 'info', 5000);
}, 500);
}, 1500);
} else {
// 注册失败,显示错误信息
let errorMessage = '注册失败,请检查您的信息并重试';
if (data.username) {
errorMessage = `用户名错误: ${data.username.join(', ')}`;
} else if (data.email) {
errorMessage = `邮箱错误: ${data.email.join(', ')}`;
} else if (data.phone_number) {
errorMessage = `电话号码错误: ${data.phone_number.join(', ')}`;
} else if (data.password) {
errorMessage = `密码错误: ${data.password.join(', ')}`;
} else if (data.non_field_errors) {
errorMessage = data.non_field_errors.join(', ');
} else if (typeof data.message === 'string') {
errorMessage = data.message;
}
showToast(errorMessage, 'error');
}
})
.catch(error => {
// 恢复按钮状态
if (registerButton) {
registerButton.disabled = false;
registerButton.classList.remove('btn-loading');
registerButton.textContent = '注册';
}
// 显示错误信息
console.error('注册请求失败:', error);
showToast('网络错误,请稍后重试', 'error');
});
}
/**
* 查询学校ID
* @param {string} schoolName - 学校名称
* @returns {Promise<number>} - 返回学校ID或-1
*/
function searchInstitutionId(schoolName) {
if (!schoolName || schoolName.trim().length < 2) {
return Promise.resolve(-1); // 如果学校名称为空或太短,直接返回-1
}
// 发送请求查询学校ID
return fetch(`${CONFIG.DataServerBaseUrl}/api/institutions/search/${encodeURIComponent(schoolName)}/`, {
method: 'GET',
headers: {
'Content-Type': 'application/json'
}
})
.then(response => {
if (!response.ok) {
throw new Error('网络请求失败');
}
return response.json();
})
.then(institutions => {
console.log('查询到的学校:', institutions);
// 如果找到完全匹配的学校返回其ID
const exactMatch = institutions.find(inst => inst.name.toLowerCase() === schoolName.toLowerCase());
if (exactMatch) {
console.log('找到完全匹配的学校:', exactMatch);
return exactMatch.id;
}
// 如果没有找到完全匹配的学校,返回-1创建新学校
console.log('没有找到完全匹配的学校,将创建新学校');
return -1;
})
.catch(error => {
console.error('查询学校ID失败:', error);
return -1; // 出错时返回-1
});
}
/**
* 设置背景图片,优先使用比赛自定义背景
*/
function setBackgroundImage() {
// 获取背景图片URL从sessionStorage中读取
const bgImageUrl = sessionStorage.getItem('competition_background_image_url');
// 获取需要设置背景的元素 - 修改为body以确保背景全屏显示
const sectionElement = document.body;
if (sectionElement) {
// 设置背景样式以确保美观
sectionElement.style.backgroundSize = 'cover';
sectionElement.style.backgroundPosition = 'center';
sectionElement.style.backgroundRepeat = 'no-repeat';
sectionElement.style.backgroundAttachment = 'fixed';
if (bgImageUrl) {
// 创建一个临时Image对象来验证URL的有效性
const img = new Image();
img.onload = function () {
// 图片加载成功,设置为背景
sectionElement.style.backgroundImage = `url("${bgImageUrl}")`;
console.log('已使用自定义背景图片:', bgImageUrl);
};
img.onerror = function () {
// 图片加载失败,使用默认背景
// 尝试使用相对路径,或者根据实际部署路径调整
sectionElement.style.backgroundImage = 'url("../images/bg.png")';
console.log('自定义背景图片加载失败,使用默认背景');
};
// 设置图片源以触发加载
img.src = bgImageUrl;
} else {
// 没有自定义背景,使用默认背景
sectionElement.style.backgroundImage = 'url("../images/bg.png")';
console.log('未设置自定义背景图片,使用默认背景');
}
}
}
/**
* 显示欢迎图片全屏蒙层
* @param {string} imageUrl - 欢迎图片URL
*/
function showWelcomeImageOverlay(imageUrl) {
// 清除创建标记,表示已开始创建
window.welcomeOverlayCreating = false;
// 创建一个全屏蒙层
const overlay = document.createElement('div');
overlay.className = 'welcome-overlay';
overlay.style.position = 'fixed';
overlay.style.top = '0';
overlay.style.left = '0';
overlay.style.width = '100%';
overlay.style.height = '100%';
overlay.style.backgroundColor = 'rgba(255, 255, 255, 1)'; // 白色背景,不透明
overlay.style.display = 'flex';
overlay.style.alignItems = 'center';
overlay.style.justifyContent = 'center';
overlay.style.flexDirection = 'column';
overlay.style.zIndex = '99999';
overlay.style.transition = 'opacity 0.5s ease';
// 添加加载动画
const loadingSpinner = document.createElement('div');
loadingSpinner.style.width = '50px';
loadingSpinner.style.height = '50px';
loadingSpinner.style.border = '5px solid rgba(0, 0, 0, 0.1)';
loadingSpinner.style.borderRadius = '50%';
loadingSpinner.style.borderTop = '5px solid #fcc75a';
loadingSpinner.style.animation = 'spin 1s linear infinite';
// 添加加载动画的@keyframes
const style = document.createElement('style');
style.textContent = `
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
`;
document.head.appendChild(style);
// 添加到DOM
overlay.appendChild(loadingSpinner);
document.body.appendChild(overlay);
// 创建欢迎图片元素
const img = document.createElement('img');
img.style.maxWidth = '100%';
img.style.maxHeight = '100%';
img.style.objectFit = 'contain';
img.style.position = 'absolute';
img.style.top = '0';
img.style.left = '0';
img.style.width = '100%';
img.style.height = '100%';
img.style.zIndex = '1';
// 创建按钮容器
const buttonContainer = document.createElement('div');
buttonContainer.style.position = 'absolute';
buttonContainer.style.bottom = '10%';
buttonContainer.style.left = '0';
buttonContainer.style.width = '100%';
buttonContainer.style.display = 'flex';
buttonContainer.style.alignItems = 'center';
buttonContainer.style.justifyContent = 'center';
buttonContainer.style.zIndex = '2';
// 判断是否为移动设备
const isMobile = window.innerWidth <= 768;
// 根据设备类型选择图片URL
let selectedImageUrl = imageUrl;
if (isMobile) {
// 如果有移动版图片,则使用移动版
const mobileImageUrl = sessionStorage.getItem('mobile_welcome_image_url');
if (mobileImageUrl) {
selectedImageUrl = mobileImageUrl;
} else {
// 如果没有配置移动版图片,则使用默认图片
selectedImageUrl = '../images/login-bg.png';
}
}
// 电脑模式下保持使用原始的competition_welcome_image_url
// 因为imageUrl参数已经是competition_welcome_image_url所以不需要额外处理
// 图片加载完成后显示
img.onload = function () {
// 移除加载动画
overlay.removeChild(loadingSpinner);
// 添加图片到DOM作为背景
img.className = 'welcome-image';
overlay.appendChild(img);
// 创建进入按钮
const button = document.createElement('button');
button.className = 'enter-button';
button.textContent = '点击进入活动';
// 根据设备类型设置样式
if (isMobile) {
button.style.padding = '12px 30px';
button.style.fontSize = '16px';
} else {
button.style.padding = '15px 50px';
button.style.fontSize = '18px';
}
button.style.fontWeight = 'bold';
button.style.borderRadius = '50px';
button.style.border = 'none';
button.style.outline = 'none';
button.style.cursor = 'pointer';
button.style.transition = 'all 0.3s ease';
// 使用指定的渐变色背景
button.style.backgroundImage = 'linear-gradient(to bottom, #fcc75a, #ee6622)';
button.style.color = '#ffffff';
button.style.boxShadow = '5px 5px 20px 0 rgba(0,0,0,0.4)';
// 鼠标悬停效果
button.onmouseover = function () {
this.style.transform = 'scale(1.05)';
this.style.boxShadow = '7px 7px 25px 0 rgba(0,0,0,0.5)';
};
button.onmouseout = function () {
this.style.transform = 'scale(1)';
this.style.boxShadow = '5px 5px 20px 0 rgba(0,0,0,0.4)';
};
// 点击按钮关闭蒙层
button.onclick = function () {
// 判断是否为移动设备
if (isMobile) {
// 手机模式下显示提示,不关闭欢迎页
showToast('手机端体验不佳,请在电脑端参与', 'info', 5000); // 显示5秒
} else {
// 电脑模式下正常关闭蒙层
overlay.style.opacity = '0';
setTimeout(() => {
document.body.removeChild(overlay);
// 从sessionStorage中移除欢迎图片URL防止再次刷新时显示
sessionStorage.removeItem('competition_welcome_image_url');
// 清除创建标记
window.welcomeOverlayCreating = false;
setBackgroundImage(); // 设置背景图片
}, 500);
}
};
// 创建查看排行榜按钮
const leaderboardButton = document.createElement('button');
leaderboardButton.className = 'enter-button';
leaderboardButton.textContent = '查看排行榜';
// 根据设备类型设置样式
if (isMobile) {
leaderboardButton.style.padding = '12px 30px';
leaderboardButton.style.fontSize = '16px';
leaderboardButton.style.marginLeft = '10px'; // 移动设备上减小边距
} else {
leaderboardButton.style.padding = '15px 50px';
leaderboardButton.style.fontSize = '18px';
leaderboardButton.style.marginLeft = '20px';
}
leaderboardButton.style.fontWeight = 'bold';
leaderboardButton.style.borderRadius = '50px';
leaderboardButton.style.border = 'none';
leaderboardButton.style.outline = 'none';
leaderboardButton.style.cursor = 'pointer';
leaderboardButton.style.transition = 'all 0.3s ease';
// 使用相同的渐变色背景
leaderboardButton.style.backgroundImage = 'linear-gradient(to bottom, #fcc75a, #ee6622)';
leaderboardButton.style.color = '#ffffff';
leaderboardButton.style.boxShadow = '5px 5px 20px 0 rgba(0,0,0,0.4)';
// 鼠标悬停效果
leaderboardButton.onmouseover = function () {
this.style.transform = 'scale(1.05)';
this.style.boxShadow = '7px 7px 25px 0 rgba(0,0,0,0.5)';
};
leaderboardButton.onmouseout = function () {
this.style.transform = 'scale(1)';
this.style.boxShadow = '5px 5px 20px 0 rgba(0,0,0,0.4)';
};
// 点击按钮跳转到排行榜页面
leaderboardButton.onclick = function () {
const competitionId = getUrlParameter('competition_id');
if (competitionId) {
// 无论是手机还是电脑,都可以查看排行榜
window.location.href = `/leaderboard.html?competition_id=${competitionId}`;
} else {
showToast('未找到比赛信息', 'error');
}
};
// 如果是移动设备,调整按钮容器为垂直排列
if (isMobile && window.innerWidth < 480) {
buttonContainer.style.flexDirection = 'column';
buttonContainer.style.bottom = '15%';
leaderboardButton.style.marginLeft = '0';
leaderboardButton.style.marginTop = '15px';
}
// 添加按钮到按钮容器
buttonContainer.appendChild(button);
buttonContainer.appendChild(leaderboardButton);
overlay.appendChild(buttonContainer);
};
// 图片加载失败时的处理
img.onerror = function () {
document.body.removeChild(overlay);
// 从sessionStorage中移除欢迎图片URL防止再次刷新时显示
sessionStorage.removeItem('competition_welcome_image_url');
// 清除创建标记
window.welcomeOverlayCreating = false;
setBackgroundImage(); // 设置背景图片
console.error('欢迎图片加载失败');
};
// 设置图片源以触发加载
img.src = selectedImageUrl;
// 添加窗口大小变化事件,自动适应屏幕尺寸
window.addEventListener('resize', function () {
const isMobileNow = window.innerWidth <= 768;
const buttonContainer = overlay.querySelector('div');
if (buttonContainer) {
const buttons = buttonContainer.querySelectorAll('button');
if (isMobileNow && window.innerWidth < 480) {
// 切换到移动模式
buttonContainer.style.flexDirection = 'column';
buttonContainer.style.bottom = '15%';
if (buttons.length > 1) {
buttons[0].style.padding = '12px 30px';
buttons[0].style.fontSize = '16px';
buttons[1].style.marginLeft = '0';
buttons[1].style.marginTop = '15px';
buttons[1].style.padding = '12px 30px';
buttons[1].style.fontSize = '16px';
}
} else {
// 切换到桌面模式
buttonContainer.style.flexDirection = 'row';
buttonContainer.style.bottom = '10%';
if (buttons.length > 1) {
buttons[0].style.padding = '15px 50px';
buttons[0].style.fontSize = '18px';
buttons[1].style.marginLeft = isMobileNow ? '10px' : '20px';
buttons[1].style.marginTop = '0';
buttons[1].style.padding = '15px 50px';
buttons[1].style.fontSize = '18px';
}
}
}
});
}
/**
* 从图片中提取主色调
* @param {HTMLImageElement} img - 图片元素
* @param {Function} callback - 回调函数,参数为提取的颜色
*/
function extractDominantColor(img, callback) {
try {
// 创建画布
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
// 设置画布大小(使用较小的尺寸以提高性能)
const size = 50;
canvas.width = size;
canvas.height = size;
// 在画布上绘制图片
ctx.drawImage(img, 0, 0, size, size);
// 获取像素数据
const imageData = ctx.getImageData(0, 0, size, size).data;
// 用于存储RGB值的计数
const colorCounts = {};
// 分析每个像素
for (let i = 0; i < imageData.length; i += 4) {
const r = imageData[i];
const g = imageData[i + 1];
const b = imageData[i + 2];
const a = imageData[i + 3];
// 跳过透明像素
if (a < 128) continue;
// 为了减少颜色数量对RGB值进行量化
const quantizedR = Math.round(r / 32) * 32;
const quantizedG = Math.round(g / 32) * 32;
const quantizedB = Math.round(b / 32) * 32;
const colorKey = `${quantizedR},${quantizedG},${quantizedB}`;
// 增加这个颜色的计数
if (colorCounts[colorKey]) {
colorCounts[colorKey]++;
} else {
colorCounts[colorKey] = 1;
}
}
// 找出出现次数最多的颜色
let maxCount = 0;
let dominantColor = null;
for (const colorKey in colorCounts) {
if (colorCounts[colorKey] > maxCount) {
maxCount = colorCounts[colorKey];
dominantColor = colorKey;
}
}
if (dominantColor) {
const [r, g, b] = dominantColor.split(',').map(Number);
// 将RGB转换为HEX格式
const hexColor = `#${((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1)}`;
callback(hexColor);
} else {
callback(null); // 无法提取颜色
}
} catch (error) {
console.error('提取颜色失败:', error);
callback(null); // 出错时返回null
}
}
/**
* 根据背景色获取对比色(黑色或白色文本)
* @param {string} hexColor - 十六进制颜色
* @returns {string} - 返回#000000或#ffffff
*/
function getContrastColor(hexColor) {
// 去掉#前缀
const hex = hexColor.replace('#', '');
// 将十六进制转换为RGB
const r = parseInt(hex.substr(0, 2), 16);
const g = parseInt(hex.substr(2, 2), 16);
const b = parseInt(hex.substr(4, 2), 16);
// 计算亮度使用YIQ公式
const yiq = ((r * 299) + (g * 587) + (b * 114)) / 1000;
// 如果背景色较亮,返回黑色文本;否则返回白色文本
return (yiq >= 128) ? '#000000' : '#ffffff';
}
/**
* 显示编程语言选择模态框
* @param {Array} types - 允许的类型数组
*/
function showCodetypeModal(types) {
const select = document.getElementById('select-8c9b');
if (select) {
select.innerHTML = '';
types.forEach(t => {
const option = document.createElement('option');
option.value = t;
option.textContent = getLanguageName(t);
select.appendChild(option);
});
}
const modal = document.getElementById('carousel_cd2f');
if (modal) {
modal.style.display = 'block';
}
}
/**
* 获取编程语言的中文名称
* @param {string} t - 类型值
* @returns {string} 中文名称
*/
function getLanguageName(t) {
switch(t) {
case '1': return '图形化编程';
case '2': return 'Python编程';
case '3': return 'C++编程';
default: return '';
}
}
// 导出函数,以便其他模块可以使用
export { getUrlParameter, fetchCompetitionInfo };