|
|
@@ -260,75 +260,118 @@
|
|
|
// --- 核心状态 ---
|
|
|
let currentTab = 'team';
|
|
|
let currentMetric = 'score';
|
|
|
+
|
|
|
+ // --- 全局数据存储 ---
|
|
|
+ let API_DATA = {}; // 存储 API 返回的完整对象
|
|
|
+ let CURRENT_RANK_LIST = []; // 当前显示的列表
|
|
|
|
|
|
- // --- 模拟数据 ---
|
|
|
- let MOCK_DATA = {
|
|
|
+ // --- 字段映射配置 (参考 rankList.vue) ---
|
|
|
+ // 对应 dispArrStr: "teamCp,teamDistance,teamRightAnswerPer,teamTodayPace,regionCp,regionDistance,regionRightAnswerPer,regionTodayPace"
|
|
|
+ const DATA_MAPPING = {
|
|
|
team: {
|
|
|
- score: [
|
|
|
- { name: '飞虎队', sub: '兴隆山', val: '98,450', unit: '分' },
|
|
|
- { name: '火箭队', sub: '中心校区', val: '85,120', unit: '分' },
|
|
|
- { name: '摸鱼队', sub: '快乐组', val: '76,300', unit: '分' },
|
|
|
- { name: '汪汪队', sub: '教职工', val: '45,000', unit: '分' },
|
|
|
- { name: '喵喵队', sub: '学生会', val: '41,200', unit: '分' },
|
|
|
- ],
|
|
|
+ score: { key: 'teamCpRs', unit: '分', label: '积分' }, // 对应 rankList.vue tab1Current=0, index=0 (rankType=totalScore) -> teamCpRs
|
|
|
+ mileage: { key: 'teamDistanceRs', unit: 'km', label: '里程' }, // index=2 (rankType=totalDistance) -> teamDistanceRs
|
|
|
+ accuracy: { key: 'teamRightAnswerPerRs', unit: '%', label: '正确率' }, // index=3 -> teamRightAnswerPerRs
|
|
|
+ count: { key: 'teamCpRs', unit: '个', label: '打点' }, // 暂复用 teamCpRs 或 teamTodayCpRs? 这里沿用 score 的源,但在 Vue 里是分开的 Tab
|
|
|
+ lap: { key: 'teamTodayPaceRs', unit: '/km', label: '配速' } // index=4 -> teamTodayPaceRs
|
|
|
},
|
|
|
individual: {
|
|
|
- score: [
|
|
|
- { name: '风一样的男子', sub: '火箭队', val: '3,450', unit: '分', avatar: 33 },
|
|
|
- { name: '爱吃西瓜', sub: '摸鱼队', val: '2,980', unit: '分', avatar: 47 },
|
|
|
- { name: '晨跑小将', sub: '汪汪队', val: '2,100', unit: '分', avatar: 11 },
|
|
|
- { name: '路人甲', sub: '飞虎队', val: '1,800', unit: '分', avatar: 5 },
|
|
|
- { name: '学霸李', sub: '火箭队', val: '1,750', unit: '分', avatar: 8 },
|
|
|
- // 我在第 45 名
|
|
|
- { name: '奔跑的蜗牛', sub: '飞虎队', val: '120', unit: '分', avatar: 12, isMe: true, rank: 45 },
|
|
|
- { name: '追风者', sub: '火箭队', val: '110', unit: '分', avatar: 20 },
|
|
|
- ]
|
|
|
+ score: { key: 'regionCpRs', unit: '分', label: '积分' }, // tab1Current=1, index=0 -> regionCpRs
|
|
|
+ mileage: { key: 'regionDistanceRs', unit: 'km', label: '里程' },
|
|
|
+ accuracy: { key: 'regionRightAnswerPerRs', unit: '%', label: '正确率' },
|
|
|
+ count: { key: 'regionCpRs', unit: '个', label: '打点' },
|
|
|
+ lap: { key: 'regionTodayPaceRs', unit: '/km', label: '配速' }
|
|
|
}
|
|
|
};
|
|
|
- // 补全数据结构
|
|
|
- ['mileage', 'accuracy', 'count', 'lap'].forEach(key => {
|
|
|
- MOCK_DATA.team[key] = MOCK_DATA.team.score;
|
|
|
- MOCK_DATA.individual[key] = MOCK_DATA.individual.score;
|
|
|
- });
|
|
|
|
|
|
- // --- 渲染逻辑 (修改了Padding和Avatar大小以变窄) ---
|
|
|
+ // --- 格式化工具 ---
|
|
|
+ const Formatters = {
|
|
|
+ distance: (val) => {
|
|
|
+ if (!val) return '0';
|
|
|
+ if (val < 10000) return (Math.round(val * 100 / 1000) / 100).toFixed(2);
|
|
|
+ return Math.round(val / 1000).toString();
|
|
|
+ },
|
|
|
+ pace: (val) => {
|
|
|
+ if (!val) return "--'--\"";
|
|
|
+ const m = Math.floor(val / 60);
|
|
|
+ const s = Math.floor(val % 60);
|
|
|
+ return `${m}'${s < 10 ? '0' + s : s}"`;
|
|
|
+ },
|
|
|
+ default: (val) => val || '0'
|
|
|
+ };
|
|
|
+
|
|
|
+ // --- 渲染逻辑 ---
|
|
|
function renderLeaderboard() {
|
|
|
const container = document.getElementById('leaderboard-container');
|
|
|
- const fullList = MOCK_DATA[currentTab][currentMetric];
|
|
|
+ container.innerHTML = ''; // Clear existing
|
|
|
+
|
|
|
+ // 1. 获取当前配置
|
|
|
+ const config = DATA_MAPPING[currentTab][currentMetric];
|
|
|
+ if (!config) return;
|
|
|
+
|
|
|
+ // 2. 获取数据列表
|
|
|
+ const rawList = API_DATA[config.key] || [];
|
|
|
+
|
|
|
+ // 3. 转换数据结构
|
|
|
+ // Vue 组件 item: { rankNum, isSelf, userName, inRankNum, headUrl, ... }
|
|
|
+ const fullList = rawList.map(item => {
|
|
|
+ let valDisplay = item.inRankNum;
|
|
|
+
|
|
|
+ // 根据 Metric 类型进行格式化
|
|
|
+ if (currentMetric === 'mileage') valDisplay = Formatters.distance(item.inRankNum);
|
|
|
+ else if (currentMetric === 'lap') valDisplay = Formatters.pace(item.inRankNum);
|
|
|
+
|
|
|
+ return {
|
|
|
+ name: item.userName || item.teamName || '匿名', // 兼容不同字段
|
|
|
+ sub: item.additionalName || '', // 副标题
|
|
|
+ val: valDisplay,
|
|
|
+ unit: config.unit,
|
|
|
+ avatar: item.headUrl, // 个人头像
|
|
|
+ rank: item.rankNum,
|
|
|
+ isMe: item.isSelf === 1 || item.isSelf === true, // 兼容
|
|
|
+ raw: item
|
|
|
+ };
|
|
|
+ });
|
|
|
+
|
|
|
+ if (fullList.length === 0) {
|
|
|
+ container.innerHTML = '<div class="text-center text-gray-400 py-10 text-sm">暂无排名数据</div>';
|
|
|
+ return;
|
|
|
+ }
|
|
|
|
|
|
const renderItem = (item, visualIndex, isRealRank = false) => {
|
|
|
- const rank = (isRealRank && item.rank) ? item.rank : (visualIndex + 1);
|
|
|
- const isTop3 = visualIndex < 3;
|
|
|
+ const rank = (isRealRank && item.rank) ? item.rank : (item.rank || visualIndex + 1);
|
|
|
+ const isTop3 = rank <= 3; // 使用真实排名判断样式
|
|
|
|
|
|
- // 图标 - 稍微调小以适应更窄的行 (w-8)
|
|
|
+ // 图标
|
|
|
let rankIconHtml = '';
|
|
|
- if (isTop3 && currentTab === 'team') {
|
|
|
- const colors = ['text-yellow-400', 'text-gray-400', 'text-orange-600'];
|
|
|
- rankIconHtml = `<div class="w-8 flex justify-center shrink-0 mr-1"><i class="fas fa-trophy ${colors[visualIndex]} text-lg drop-shadow-sm"></i></div>`;
|
|
|
- } else if (isTop3 && currentTab === 'individual') {
|
|
|
+ if (isTop3) {
|
|
|
const colors = ['text-yellow-400', 'text-gray-400', 'text-orange-600'];
|
|
|
- rankIconHtml = `<div class="w-8 flex justify-center shrink-0 mr-1"><i class="fas fa-medal ${colors[visualIndex]} text-xl drop-shadow-sm"></i></div>`;
|
|
|
+ const colorClass = colors[rank - 1] || 'text-gray-400'; // 安全回退
|
|
|
+ const iconClass = currentTab === 'team' ? 'fa-trophy' : 'fa-medal';
|
|
|
+ rankIconHtml = `<div class="w-8 flex justify-center shrink-0 mr-1"><i class="fas ${iconClass} ${colorClass} text-lg drop-shadow-sm"></i></div>`;
|
|
|
} else {
|
|
|
rankIconHtml = `<div class="w-8 text-center font-bold text-gray-400 text-sm mr-1">${rank}</div>`;
|
|
|
}
|
|
|
+
|
|
|
+ // “我”的特殊排位样式
|
|
|
if (item.isMe) {
|
|
|
rankIconHtml = `<div class="w-8 text-center font-black text-primary text-lg italic mr-1">${rank}</div>`;
|
|
|
}
|
|
|
|
|
|
- // 头像 - 调小 (w-9 h-9)
|
|
|
+ // 头像
|
|
|
let avatarHtml = '';
|
|
|
if (currentTab === 'team') {
|
|
|
avatarHtml = `<div class="w-9 h-9 bg-blue-50 rounded-full mr-3 flex items-center justify-center shrink-0 text-primary"><i class="fas fa-user-friends text-base"></i></div>`;
|
|
|
} else {
|
|
|
- avatarHtml = `<img src="https://i.pravatar.cc/100?img=${item.avatar}" class="w-9 h-9 rounded-full mr-3 border-2 ${isTop3 ? 'border-yellow-400' : 'border-transparent'} shrink-0">`;
|
|
|
+ const avatarSrc = item.avatar || 'https://i.pravatar.cc/100?img=1'; // 默认头像
|
|
|
+ avatarHtml = `<img src="${avatarSrc}" class="w-9 h-9 rounded-full mr-3 border-2 ${isTop3 ? 'border-yellow-400' : 'border-transparent'} shrink-0 object-cover">`;
|
|
|
}
|
|
|
|
|
|
- // 容器 - 减少 Padding (py-2 px-3)
|
|
|
+ // 容器样式
|
|
|
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"; // 稍微减小字号适配窄行
|
|
|
+ let valClass = "font-bold text-gray-600 font-mono text-base";
|
|
|
|
|
|
- // “我”的高亮样式
|
|
|
if (item.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";
|
|
|
@@ -339,46 +382,32 @@
|
|
|
|
|
|
const meBadge = item.isMe ? `<div class="absolute right-0 top-0 bg-primary text-white text-[8px] px-1.5 py-0.5 rounded-bl-lg">我</div>` : '';
|
|
|
|
|
|
- return `
|
|
|
- <div class="${containerClass}" style="animation-delay: ${visualIndex * 50}ms">
|
|
|
+ return
|
|
|
+ `<div class="${containerClass}" style="animation-delay: ${visualIndex * 50}ms">
|
|
|
${meBadge}
|
|
|
${rankIconHtml}
|
|
|
${avatarHtml}
|
|
|
<div class="flex-1 min-w-0">
|
|
|
<h4 class="${nameClass} truncate">${item.name}</h4>
|
|
|
- <p class="text-[10px] text-gray-400">${item.sub}</p>
|
|
|
+ <p class="text-[10px] text-gray-400 truncate">${item.sub}</p>
|
|
|
</div>
|
|
|
<div class="text-right">
|
|
|
<div class="${valClass}">${item.val}</div>
|
|
|
<div class="text-[10px] text-gray-400">${item.unit}</div>
|
|
|
</div>
|
|
|
- </div>
|
|
|
- `;
|
|
|
+ </div>`;
|
|
|
};
|
|
|
|
|
|
let html = '';
|
|
|
|
|
|
- // 1. 前三名
|
|
|
- const top3 = fullList.slice(0, 3);
|
|
|
- top3.forEach((item, index) => {
|
|
|
+ // 逻辑:前3名 + (如果我在后面则插队) + 剩余
|
|
|
+ // 这里简化处理,直接渲染列表,如果 "我" 在列表中会自动高亮。
|
|
|
+ // 如果 API 返回的数据没有包含 "我" (比如分页了),则需要单独处理 getUserCurrentRank 接口,这里暂且假设 rankRs 包含了 top N。
|
|
|
+
|
|
|
+ fullList.forEach((item, index) => {
|
|
|
html += renderItem(item, index);
|
|
|
});
|
|
|
|
|
|
- // 2. 如果我在三名以外,插队显示在第四位
|
|
|
- if (currentTab === 'individual') {
|
|
|
- const me = fullList.find(x => x.isMe);
|
|
|
- if (me && (!me.rank || me.rank > 3)) {
|
|
|
- html += renderItem(me, 3, true);
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- // 3. 渲染剩余 (从列表的第4个开始,即索引3)
|
|
|
- const others = fullList.slice(3);
|
|
|
- others.forEach((item, index) => {
|
|
|
- if (item.isMe && currentTab === 'individual') return;
|
|
|
- html += renderItem(item, index + 3);
|
|
|
- });
|
|
|
-
|
|
|
container.innerHTML = html;
|
|
|
}
|
|
|
|
|
|
@@ -402,15 +431,10 @@
|
|
|
|
|
|
function saveProfile() {
|
|
|
const newName = editNameInput.value;
|
|
|
- const newTeam = selectedTeamText.innerText.trim();
|
|
|
+ // 这里的 teamName 逻辑仅仅是 UI 演示,实际修改需要调用 API (e.g., signUpOnline)
|
|
|
+ // 目前仅更新 UI
|
|
|
document.getElementById('profileName').innerText = newName;
|
|
|
- document.getElementById('profileTeam').innerText = newTeam;
|
|
|
-
|
|
|
- ['score', 'mileage', 'accuracy', 'count', 'lap'].forEach(key => {
|
|
|
- const me = MOCK_DATA.individual[key].find(i => i.isMe);
|
|
|
- if(me) { me.name = newName; me.sub = newTeam; }
|
|
|
- });
|
|
|
- renderLeaderboard();
|
|
|
+ // TODO: Call API to update profile if needed
|
|
|
closeEditModal();
|
|
|
}
|
|
|
|
|
|
@@ -465,15 +489,12 @@
|
|
|
function switchMetric(metric) {
|
|
|
currentMetric = metric;
|
|
|
document.querySelectorAll('.metric-btn').forEach(btn => {
|
|
|
- if (btn.id === 'metric-score') {
|
|
|
- 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 relative overflow-visible transition-colors';
|
|
|
+ if (btn.id === `metric-${metric}`) {
|
|
|
+ btn.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';
|
|
|
} else {
|
|
|
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-${metric}`);
|
|
|
- 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';
|
|
|
|
|
|
renderLeaderboard();
|
|
|
}
|
|
|
@@ -494,8 +515,6 @@
|
|
|
}
|
|
|
|
|
|
window.onload = function() {
|
|
|
- renderLeaderboard();
|
|
|
-
|
|
|
// Helper function to get URL query parameters (local to this script)
|
|
|
function getQueryParam(name) {
|
|
|
const params = new URLSearchParams(window.location.search);
|
|
|
@@ -527,7 +546,20 @@
|
|
|
|
|
|
// Get external parameters
|
|
|
const token = getQueryParam('token');
|
|
|
- const ecId = getQueryParam('id'); // Use 'id' from URL as ecId
|
|
|
+ let ecId = getQueryParam('id'); // Use 'id' from URL as ecId
|
|
|
+ const env = getQueryParam('env');
|
|
|
+
|
|
|
+ // Initialize Mock if needed
|
|
|
+ if (env === 'mock' && window.API) {
|
|
|
+ console.log('Initializing API in Mock mode...');
|
|
|
+ window.API.init({ useMock: true });
|
|
|
+
|
|
|
+ // Fallback ecId for mock mode if missing
|
|
|
+ if (!ecId) {
|
|
|
+ ecId = 'mock_default_id';
|
|
|
+ console.warn('No "id" parameter found. Using default mock ID:', ecId);
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
// State for Marquee Data
|
|
|
let marqueeData = {
|
|
|
@@ -550,107 +582,110 @@
|
|
|
window.API.setToken(token);
|
|
|
console.log('API Token set:', token);
|
|
|
} else {
|
|
|
- console.warn('Token not found in URL or API.setToken not available.');
|
|
|
+ if (env !== 'mock') console.warn('Token not found in URL or API.setToken not available.');
|
|
|
}
|
|
|
|
|
|
- // Call API with ecId and ocaId (using a dummy ocaId for now)
|
|
|
+ // Call API with ecId
|
|
|
if (window.API && ecId) {
|
|
|
- // Assuming ocaId is also passed, or a default value for demonstration
|
|
|
- // For demonstration, using a static ocaId=1. In a real scenario, this might also come from URL or other logic.
|
|
|
- // const ocaId = 1; // Not needed for getCardDetail
|
|
|
-
|
|
|
- API.getCardDetail(ecId).then(res => {
|
|
|
- console.log('API Response for ecId=' + ecId + ' (getCardDetail):', res);
|
|
|
-
|
|
|
- // Update nickname and team from API response
|
|
|
- const profileNameElement = document.getElementById('profileName');
|
|
|
- const profileTeamElement = document.getElementById('profileTeam');
|
|
|
-
|
|
|
- if (profileNameElement && res.nickName) { // Use nickName as per user clarification
|
|
|
- profileNameElement.innerText = res.nickName;
|
|
|
- }
|
|
|
- if (profileTeamElement && res.coiName) {
|
|
|
- profileTeamElement.innerText = res.coiName;
|
|
|
+ console.log('Starting Analysis-Based Sequence...');
|
|
|
+
|
|
|
+ // 0. Global State for selections
|
|
|
+ window.CURRENT_SELECTION = {
|
|
|
+ mcId: 0,
|
|
|
+ mcType: 0,
|
|
|
+ ocaId: 0, // Current selected map ID (for individual)
|
|
|
+ mapList: [] // Store map options
|
|
|
+ };
|
|
|
+
|
|
|
+ // 1. Match Details (Core)
|
|
|
+ API.getMatchRsDetail(ecId).then(matchRes => {
|
|
|
+ console.log('API Response (getMatchRsDetail):', matchRes);
|
|
|
+ if (!matchRes) return;
|
|
|
+
|
|
|
+ const mcId = matchRes.mcId;
|
|
|
+ const mcType = matchRes.mcType;
|
|
|
+ window.CURRENT_SELECTION.mcId = mcId;
|
|
|
+ window.CURRENT_SELECTION.mcType = mcType;
|
|
|
+
|
|
|
+ // Update UI Info
|
|
|
+ if (matchRes.nickName) document.getElementById('profileName').innerText = matchRes.nickName;
|
|
|
+ if (matchRes.totalSysPoint !== undefined) document.getElementById('currentScoreDisplay').innerText = matchRes.totalSysPoint;
|
|
|
+ if (matchRes.endSecond !== undefined) {
|
|
|
+ marqueeData.remainingTimeStr = formatRemainingTime(matchRes.endSecond);
|
|
|
+ updateMarquee();
|
|
|
}
|
|
|
|
|
|
- // Store mcId and ocaId in local storage, differentiated by ecId
|
|
|
- if (res.mcId !== undefined && res.ocaId !== undefined && ecId) {
|
|
|
- const storageKey = `cardData_${ecId}`;
|
|
|
- const dataToStore = {
|
|
|
- mcId: res.mcId,
|
|
|
- ocaId: res.ocaId
|
|
|
- };
|
|
|
- try {
|
|
|
- localStorage.setItem(storageKey, JSON.stringify(dataToStore));
|
|
|
- console.log(`Stored mcId and ocaId for ecId ${ecId}:`, dataToStore);
|
|
|
- } catch (e) {
|
|
|
- console.error('Failed to save to localStorage:', e);
|
|
|
+ // 2. Parallel Calls: MapList & Statistics
|
|
|
+ const p1 = API.getMapList(mcId).then(mapRes => {
|
|
|
+ console.log('API Response (getMapList):', mapRes);
|
|
|
+ if (mapRes && mapRes.length > 0) {
|
|
|
+ window.CURRENT_SELECTION.mapList = mapRes;
|
|
|
+ // Default ocaId to the first map
|
|
|
+ window.CURRENT_SELECTION.ocaId = mapRes[0].ocaId;
|
|
|
+
|
|
|
+ // Update Dropdown/Tab UI if needed (Simulated here)
|
|
|
+ // In a real app, you'd populate the 'individual' tab dropdown here
|
|
|
+ console.log('Default ocaId set to:', window.CURRENT_SELECTION.ocaId);
|
|
|
}
|
|
|
- } else {
|
|
|
- console.warn('mcId, ocaId, or ecId missing from API response or URL. Not storing to localStorage.');
|
|
|
- }
|
|
|
-
|
|
|
- // Call API.getMatchRsDetail if mcId and ocaId are available
|
|
|
- if (res.mcId !== undefined && res.ocaId !== undefined) {
|
|
|
-
|
|
|
- API.getMatchRsDetail(ecId, res.ocaId).then(matchRes => {
|
|
|
- console.log('API Response for ecId=' + ecId + ', ocaId=' + res.ocaId + ' (getMatchRsDetail):', matchRes);
|
|
|
-
|
|
|
- const currentScoreDisplayElement = document.getElementById('currentScoreDisplay');
|
|
|
- if (currentScoreDisplayElement && matchRes && matchRes.totalSysPoint !== undefined) {
|
|
|
- currentScoreDisplayElement.innerText = matchRes.totalSysPoint;
|
|
|
- console.log('Updated current score with totalSysPoint:', matchRes.totalSysPoint);
|
|
|
- } else {
|
|
|
- console.warn('totalSysPoint not found in getMatchRsDetail response or element not found.');
|
|
|
- }
|
|
|
-
|
|
|
- // Update endSecond for marquee
|
|
|
- if (matchRes.endSecond !== undefined) {
|
|
|
- marqueeData.remainingTimeStr = formatRemainingTime(matchRes.endSecond);
|
|
|
- updateMarquee();
|
|
|
- }
|
|
|
-
|
|
|
- }).catch(matchErr => {
|
|
|
- console.error('API Error (getMatchRsDetail):', matchErr);
|
|
|
- });
|
|
|
-
|
|
|
- // New: Call API.getCompStatistic
|
|
|
- if (res.mcId !== undefined) { // Check if mcId is available from getCardDetail response
|
|
|
- API.getCompStatistic(res.mcId).then(compStatsRes => {
|
|
|
- console.log('API Response for mcId=' + res.mcId + ' (getCompStatistic):', compStatsRes);
|
|
|
-
|
|
|
- if (compStatsRes) {
|
|
|
- if (compStatsRes.totalDistance !== undefined) {
|
|
|
- marqueeData.totalDistanceKm = Math.round(compStatsRes.totalDistance / 1000);
|
|
|
- }
|
|
|
- if (compStatsRes.totalAnswerNum !== undefined) {
|
|
|
- marqueeData.totalAnswerNum = compStatsRes.totalAnswerNum;
|
|
|
- }
|
|
|
- if (compStatsRes.totalCp !== undefined) {
|
|
|
- marqueeData.totalCp = compStatsRes.totalCp;
|
|
|
- }
|
|
|
-
|
|
|
- updateMarquee();
|
|
|
- console.log('Updated marquee text with CompStatistic data.');
|
|
|
- } else {
|
|
|
- console.warn('CompStatistic data not found.');
|
|
|
- }
|
|
|
- }).catch(compStatsErr => {
|
|
|
- console.error('API Error (getCompStatistic):', compStatsErr);
|
|
|
- });
|
|
|
- } else {
|
|
|
- console.warn('mcId not available for API.getCompStatistic call.');
|
|
|
+ }).catch(e => console.error('MapList Error:', e));
|
|
|
+
|
|
|
+ const p2 = API.getCompStatistic(mcId).then(compStatsRes => {
|
|
|
+ if (compStatsRes) {
|
|
|
+ if (compStatsRes.totalDistance !== undefined) marqueeData.totalDistanceKm = Math.round(compStatsRes.totalDistance / 1000);
|
|
|
+ if (compStatsRes.totalAnswerNum !== undefined) marqueeData.totalAnswerNum = compStatsRes.totalAnswerNum;
|
|
|
+ if (compStatsRes.totalCp !== undefined) marqueeData.totalCp = compStatsRes.totalCp;
|
|
|
+ updateMarquee();
|
|
|
}
|
|
|
- }
|
|
|
+ }).catch(e => console.warn('CompStat Error:', e));
|
|
|
+
|
|
|
+ // 3. Initial Rank Load (Wait for MapList to ensure ocaId is ready for Individual tab if that's default,
|
|
|
+ // but usually Team tab is default which might not need specific ocaId or uses global context)
|
|
|
+ Promise.all([p1, p2]).then(() => {
|
|
|
+ loadRankData();
|
|
|
+ });
|
|
|
+
|
|
|
+ }).catch(err => console.error('MatchRsDetail Error:', err));
|
|
|
|
|
|
- }).catch(err => {
|
|
|
- console.error('API Error:', err);
|
|
|
- });
|
|
|
} else if (!ecId) {
|
|
|
- console.warn('URL parameter "id" (ecId) not found. API call skipped.');
|
|
|
- } else {
|
|
|
- console.warn('API not loaded. API call skipped.');
|
|
|
+ console.warn('URL parameter "id" (ecId) not found.');
|
|
|
}
|
|
|
+
|
|
|
+ // Internal function to load rank data based on current state
|
|
|
+ window.loadRankData = function() {
|
|
|
+ const { mcId, mcType, ocaId } = window.CURRENT_SELECTION;
|
|
|
+ // Determine effective ocaId based on current tab
|
|
|
+ // If Team tab, usually we query global or specific team-level data.
|
|
|
+ // If Individual tab, we MUST use the selected ocaId (mapId).
|
|
|
+ // Note: The Vue component sends 'ocaId' in apiCardRankDetailQuery.
|
|
|
+ // When in Team mode (tab1Current=0), it might still pass the ocaId if the team rank is also segregated by map,
|
|
|
+ // OR it might rely on the backend ignoring it for team ranks.
|
|
|
+ // Based on analysis: "onSelectChange" updates ocaId and calls query.
|
|
|
+
|
|
|
+ let effectiveOcaId = ocaId;
|
|
|
+ // If needed: if (currentTab === 'team') effectiveOcaId = 0; // Uncomment if team rank is global
|
|
|
+
|
|
|
+ const dispArrStr = "teamCp,teamTodayCp,teamDistance,teamRightAnswerPer,teamTodayPace,regionCp,regionTodayCp,regionDistance,regionRightAnswerPer,regionTodayPace";
|
|
|
+
|
|
|
+ console.log(`Fetching Rank: mcId=${mcId}, type=${mcType}, ocaId=${effectiveOcaId}`);
|
|
|
+
|
|
|
+ API.getRankDetail(mcId, mcType, effectiveOcaId, dispArrStr).then(rankRes => {
|
|
|
+ console.log('API Response (getRankDetail):', rankRes);
|
|
|
+ if (rankRes) {
|
|
|
+ API_DATA = rankRes;
|
|
|
+ renderLeaderboard();
|
|
|
+ } else {
|
|
|
+ document.getElementById('leaderboard-container').innerHTML = '<div class="text-center text-gray-400 py-10 text-sm">暂无排名数据</div>';
|
|
|
+ }
|
|
|
+ }).catch(e => console.error('RankDetail Error:', e));
|
|
|
+ };
|
|
|
+
|
|
|
+ // Hook into tab switching
|
|
|
+ const originalSwitchMainTab = window.switchMainTab;
|
|
|
+ window.switchMainTab = function(type) {
|
|
|
+ originalSwitchMainTab(type); // Update UI state
|
|
|
+ // Re-load data (ocaId stays same or logic inside loadRankData handles it)
|
|
|
+ loadRankData();
|
|
|
+ };
|
|
|
};
|
|
|
</script>
|
|
|
</body>
|