var state = { token: "", ecId: 0, mcId: 0, mcName: "", ocaId: 0, // Current Map/Group ID // Personal Info userInfo: { nickName: '', coiName: '', totalScore: 0, // Points avatar: 12 // Random or from API if available }, // Global Stats stats: { totalDistance: 0, totalAnswerNum: 0, totalCp: 0, endSecond: 0 }, // Leaderboard Data rankList: {}, // Stores raw data from API // UI State currentTab: 'team', // 'team' or 'individual' currentMetric: 'score', // 'score', 'mileage', 'accuracy', 'count', 'lap' (pace) mapList: [], // For drawer mcState: 0 }; function initRankListPage() { const params = new URLSearchParams(window.location.search); state.token = params.get('token') || ''; state.ecId = params.get('id') || 0; const savedOcaId = uni.getStorageSync(`rank-tpl-style3-map-${state.ecId}`); if(savedOcaId) state.ocaId = savedOcaId; window.cardfunc.init(state.token, state.ecId); window.cardfunc.getCardConfig(onConfigLoaded); document.getElementById('drawer-backdrop').addEventListener('click', closeDrawer); } function onConfigLoaded(config) { matchRsDetailQuery(); compStatisticQuery(); mapListQuery(); window.cardfunc.unReadMessageQuery(); } function matchRsDetailQuery() { uni.request({ url: window.apiMatchRsDetailQuery, header: { "Content-Type": "application/x-www-form-urlencoded", "token": state.token }, method: "POST", data: { ecId: state.ecId, ocaId: state.ocaId }, success: (res) => { if(window.checkResCode(res)) { const data = res.data.data; state.mcId = data.mcId; state.mcName = data.mcName; state.userInfo.nickName = data.nickName; state.userInfo.coiName = data.coiName; state.userInfo.totalScore = data.regionTotalSysPoint || 0; state.mcState = window.tools.checkMcState(data.beginSecond, data.endSecond); state.stats.endSecond = data.endSecond; updateHeaderUI(); fetchCompStats(); fetchMapList(); } } }); } function fetchCompStats() { uni.request({ url: window.apiCompStatisticQuery, header: { "Content-Type": "application/x-www-form-urlencoded", "token": state.token }, method: "POST", data: { mcId: state.mcId }, success: (res) => { if(res.data.code == 0) { const data = res.data.data; state.stats.totalDistance = data.totalDistance; state.stats.totalAnswerNum = data.totalAnswerNum; state.stats.totalCp = data.totalCp; updateMarquee(); } } }); } function fetchMapList() { uni.request({ url: window.apiMapListQuery, header: { "Content-Type": "application/x-www-form-urlencoded", "token": state.token }, method: "POST", data: { mcId: state.mcId }, success: (res) => { if(res.data.code == 0) { state.mapList = res.data.data; if(state.ocaId == 0 && state.mapList.length > 0) { state.ocaId = state.mapList[0].ocaId; uni.setStorageSync(`rank-tpl-style3-map-${state.ecId}`, state.ocaId); } fetchRankDetail(); renderDrawer(); } } }); } function fetchRankDetail() { const dispArrStr = "teamCp,teamTodayCp,teamDistance,teamRightAnswerPer,teamTodayPace,regionCp,regionTodayCp,regionDistance,regionRightAnswerPer,regionTodayPace"; uni.request({ url: window.apiCardRankDetailQuery, header: { "Content-Type": "application/x-www-form-urlencoded", "token": state.token }, method: "POST", data: { mcIdListStr: state.mcId, mcType: 1, ocaId: state.ocaId, dispArrStr: dispArrStr }, success: (res) => { if(res.data.code == 0) { state.rankList = res.data.data; renderLeaderboard(); } } }); } function updateHeaderUI() { const nameEl = document.getElementById('profileName'); if(nameEl) nameEl.innerText = state.userInfo.nickName || '未命名'; const teamEl = document.getElementById('profileTeam'); if(teamEl) teamEl.innerText = state.userInfo.coiName || '未加入战队'; // Score is in the personal card right side. // HTML structure:
120
// I'll look for that div or expect user to add ID. // I'll try to select it by context if ID not present. // "当前积分" is in the next div. const scoreLabel = Array.from(document.querySelectorAll('div')).find(el => el.innerText === '当前积分'); if(scoreLabel) { const valEl = scoreLabel.previousElementSibling; if(valEl) valEl.innerText = state.userInfo.totalScore; } } function updateMarquee() { const marquee = document.querySelector('.animate-marquee'); if(marquee) { const now = Date.now() / 1000; const dif = state.stats.endSecond - now; let timeStr = "已结束"; if(dif > 0) timeStr = window.tools.convertSecondsToDHM(dif); marquee.innerText = `当前总题目: ${state.stats.totalAnswerNum}道 | 总里程: ${window.tools.fmtDistanct(state.stats.totalDistance)}km | 总打点数: ${state.stats.totalCp}个 | 距离比赛结束还有 ${timeStr} | 加油!冲鸭!`; } } const metricMap = { team: { score: 'teamCpRs', mileage: 'teamDistanceRs', accuracy: 'teamRightAnswerPerRs', count: 'teamCpRs', lap: 'teamTodayPaceRs' }, individual: { score: 'regionCpRs', mileage: 'regionDistanceRs', accuracy: 'regionRightAnswerPerRs', count: 'regionCpRs', lap: 'regionTodayPaceRs' } }; function renderLeaderboard() { const container = document.getElementById('leaderboard-container'); const key = metricMap[state.currentTab][state.currentMetric]; const list = state.rankList[key] || []; let html = ''; list.forEach((item, index) => { const rank = index + 1; const isTop3 = index < 3; const isMe = (item.nickName === state.userInfo.nickName); let rankIconHtml = ''; if (isTop3) { const colors = ['text-yellow-400', 'text-gray-400', 'text-orange-600']; const icon = state.currentTab === 'team' ? 'fa-trophy' : 'fa-medal'; rankIconHtml = `
`; } else { rankIconHtml = `
${rank}
`; } let avatarHtml = ''; if (state.currentTab === 'team') { avatarHtml = `
`; } else { const img = item.avatar || (index % 10 + 1); avatarHtml = ``; } let containerClass = "bg-white rounded-xl py-2 px-3 flex items-center shadow-sm border border-gray-100 relative fade-in-up"; let nameClass = "font-bold text-gray-800 text-sm"; let valClass = "font-bold text-gray-600 font-mono text-base"; if (isMe) { containerClass = "bg-blue-50 rounded-xl py-2 px-3 flex items-center shadow-md border-2 border-primary/30 relative overflow-hidden transform scale-[1.02] fade-in-up z-10 my-2"; nameClass = "font-bold text-primary text-sm"; valClass = "font-bold text-primary font-mono text-lg"; } let val = item.val || item.score || item.value || 0; if(item.teamCp !== undefined) val = item.teamCp; // Specifics based on API key // Actually API usually returns numeric values in fields like `value` or `score` or same as key name (e.g. teamDistanceRs array contains objects with teamDistance) // Let's try to find the value based on metric. if(state.currentMetric === 'mileage') { val = item.teamDistance || item.regionDistance || item.value || 0; val = window.tools.fmtDistanct(val); } else if (state.currentMetric === 'score') { val = item.teamCp || item.regionCp || item.value || 0; } else if (state.currentMetric === 'accuracy') { val = item.teamRightAnswerPer || item.regionRightAnswerPer || 0; } else if (state.currentMetric === 'lap') { val = item.teamTodayPace || item.regionTodayPace || 0; val = window.tools.fmtPace(val); } else { val = item.teamCp || item.regionCp || 0; } let unit = ''; if(state.currentMetric === 'mileage') unit = 'km'; else if (state.currentMetric === 'score') unit = '分'; else if (state.currentMetric === 'accuracy') unit = '%'; else if (state.currentMetric === 'count') unit = '个'; let itemName = item.coiName || item.nickName || item.name || '未知'; html += `
${isMe ? '
' : ''} ${rankIconHtml} ${avatarHtml}

${itemName}

${item.coiName || ''}

${val}
${unit}
`; }); container.innerHTML = html || '
暂无数据
'; } // Exposed UI Functions window.switchMainTab = function(type) { state.currentTab = type; updateTabUI(); renderLeaderboard(); }; window.switchMetric = function(metric) { state.currentMetric = metric; updateMetricUI(); renderLeaderboard(); }; function updateTabUI() { const tTeam = document.getElementById('tab-team'); const tInd = document.getElementById('tab-ind'); const active = "flex-1 py-2 rounded-full text-sm font-bold text-center transition-all duration-300 bg-white text-primary shadow-sm"; const inactive = "flex-1 py-2 rounded-full text-sm font-bold text-center transition-all duration-300 text-gray-500 hover:text-gray-700"; if(state.currentTab === 'team') { tTeam.className = active; tInd.className = inactive; } else { tTeam.className = inactive; tInd.className = active; } } function updateMetricUI() { document.querySelectorAll('.metric-btn').forEach(btn => { btn.className = 'metric-btn bg-white text-gray-500 border border-gray-200 px-3 py-1 rounded-full text-xs font-bold shrink-0 active:bg-gray-50 transition-colors'; }); const activeBtn = document.getElementById(`metric-${state.currentMetric}`); if(activeBtn) activeBtn.className = 'metric-btn bg-primary text-white px-3 py-1 rounded-full text-xs shadow-md shadow-blue-100 font-bold shrink-0 relative overflow-visible transition-colors'; } // Drawer window.openDrawer = function() { document.getElementById('drawer-backdrop').classList.remove('hidden'); document.getElementById('drawer').classList.remove('translate-y-full'); }; window.closeDrawer = function() { document.getElementById('drawer-backdrop').classList.add('hidden'); document.getElementById('drawer').classList.add('translate-y-full'); }; function renderDrawer() { const drawerContent = document.querySelector('#drawer .space-y-6'); // We can populate map options here } window.triggerJump = function(target) { const map = state.mapList.find(m => m.mapName === target); if(map) { if(state.ocaId !== map.ocaId) { state.ocaId = map.ocaId; uni.setStorageSync(`rank-tpl-style3-map-${state.ecId}`, state.ocaId); matchRsDetailQuery(); fetchRankDetail(); } } else { // If it's '高德地图' etc. if(target.includes('地图')) { alert("正在打开导航:" + target); } else { // Assume map name const map2 = state.mapList.find(m => m.mapName === target); if(map2) { state.ocaId = map2.ocaId; uni.setStorageSync(`rank-tpl-style3-map-${state.ecId}`, state.ocaId); matchRsDetailQuery(); fetchRankDetail(); } } } closeDrawer(); }; // Info Modal window.openInfoModal = function() { document.getElementById('infoModal').classList.remove('hidden'); } window.closeInfoModal = function() { document.getElementById('infoModal').classList.add('hidden'); } // Edit Modal window.openEditModal = function() { const editNameInput = document.getElementById('editNameInput'); const selectedTeamText = document.getElementById('selectedTeamText'); editNameInput.value = state.userInfo.nickName; selectedTeamText.innerHTML = ` ${state.userInfo.coiName || '选择战队'}`; document.getElementById('editProfileModal').classList.remove('hidden'); // Populate teams if empty (reuse mapList or fetch teams if different API? // Actually teams come from apiOnlineMcSignUpDetail which we might need to call if we want to allow changing teams. // For simplicity, assuming mapList contains teams or we need to fetch cois. // The signup page fetches apiOnlineMcSignUpDetail. Let's do that here too if needed. getOnlineMcSignUpDetail(); }; window.closeEditModal = function() { document.getElementById('editProfileModal').classList.add('hidden'); window.closeDropdown(); }; // Fetch teams for dropdown function getOnlineMcSignUpDetail() { uni.request({ url: window.apiOnlineMcSignUpDetail, header: { "Content-Type": "application/x-www-form-urlencoded", "token": state.token }, method: "POST", data: { mcId: state.mcId }, success: (res) => { const data = res.data.data; if(data) { const teams = data.coiRs; renderEditDropdown(teams); } } }); } function renderEditDropdown(teams) { const ul = document.querySelector('#dropdownMenu ul'); if(!ul) return; let html = ''; teams.forEach(team => { html += `
  • ${team.coiName}
  • `; }); ul.innerHTML = html; } window.toggleDropdown = function(event) { event.stopPropagation(); const dropdownMenu = document.getElementById('dropdownMenu'); const dropdownArrow = document.getElementById('dropdownArrow'); if (dropdownMenu.classList.contains('hidden')) { dropdownMenu.classList.remove('hidden'); setTimeout(() => dropdownMenu.classList.add('dropdown-enter-active'), 10); dropdownArrow.style.transform = 'rotate(180deg)'; } else { window.closeDropdown(); } }; window.closeDropdown = function() { const dropdownMenu = document.getElementById('dropdownMenu'); const dropdownArrow = document.getElementById('dropdownArrow'); dropdownMenu.classList.remove('dropdown-enter-active'); dropdownArrow.style.transform = 'rotate(0deg)'; setTimeout(() => dropdownMenu.classList.add('hidden'), 200); }; window.closeDropdownOnClickOutside = function(event) { if (!document.getElementById('editProfileModal').classList.contains('hidden')) { const btn = document.getElementById('dropdownBtn'); const menu = document.getElementById('dropdownMenu'); if (menu && !menu.contains(event.target) && btn && !btn.contains(event.target)) { window.closeDropdown(); } } }; let editingCoiId = 0; window.selectEditOption = function(id, name) { editingCoiId = id; document.getElementById('selectedTeamText').innerHTML = ` ${name}`; window.closeDropdown(); }; window.saveProfile = function() { const newName = document.getElementById('editNameInput').value; if(!newName) { uni.showToast({title:'请输入昵称',icon:'none'}); return; } // Call API uni.request({ url: window.apiOnlineMcSignUp, header: { "Content-Type": "application/x-www-form-urlencoded", "token": state.token }, method: "POST", data: { mcId: state.mcId, coiId: editingCoiId || 0, // If 0, maybe keep old? API requires valid ID usually. selectTeam: 0, nickName: newName }, success: (res) => { if(window.checkResCode(res)) { uni.showToast({title:'修改成功',icon:'none'}); window.closeEditModal(); matchRsDetailQuery(); // Refresh } } }); }; document.addEventListener('DOMContentLoaded', () => { initRankListPage(); });