// 导入配置信息 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 = ` ×

平台模式选择

👆 点击上方下拉菜单选择:

🚀 训练平台 · 日常练习模式

🏆 考试平台 · 正式比赛模式

`; } else { tooltip.innerHTML = ` ×

平台模式选择

👉 点击右侧下拉菜单选择:

🚀 训练平台 · 日常练习模式

🏆 考试平台 · 正式比赛模式

`; } // 添加到页面(先添加才能获取正确的尺寸) 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, '
'); 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, '
'); let infoHTML = `

赛练信息: ${announcementWithBreaks}

赛练状态: ${getStatusText(competition.status)} 赛练区域: ${getLevelText(competition.competition_level)}

`; 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 = `用户名或密码错误
(第${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 = ''; // 添加新的省份选项 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 = ''; // 添加新的城市选项 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 = ''; } } }) .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 = ''; // 添加新的区县选项 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 = ''; // 添加新的年级选项 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 = ` `; // 添加点击事件 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 = `

活动报名信息绑定

请输入您在活动平台报名时使用的身份证号码,系统将自动获取并绑定您的报名信息。

`; 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} - 返回学校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 };