3453 lines
130 KiB
JavaScript
3453 lines
130 KiB
JavaScript
// 导入配置信息
|
||
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">×</span>
|
||
<p><strong>平台模式选择</strong></p>
|
||
<p>👆 点击上方下拉菜单选择:</p>
|
||
<p>🚀 <strong>训练平台</strong> · 日常练习模式</p>
|
||
<p>🏆 <strong>考试平台</strong> · 正式比赛模式</p>
|
||
`;
|
||
} else {
|
||
tooltip.innerHTML = `
|
||
<span class="platform-guide-close">×</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 };
|