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 || ''}
`;
});
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();
});