| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463 |
- 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: <div class="text-2xl font-black text-primary font-mono leading-none">120</div>
- // 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 = `<div class="w-8 flex justify-center shrink-0 mr-1"><i class="fas ${icon} ${colors[index]} 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>`;
- }
-
- let avatarHtml = '';
- if (state.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 {
- const img = item.avatar || (index % 10 + 1);
- avatarHtml = `<img src="https://i.pravatar.cc/100?img=${img}" class="w-9 h-9 rounded-full mr-3 border-2 ${isTop3 ? 'border-yellow-400' : 'border-transparent'} shrink-0">`;
- }
- 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 += `
- <div class="${containerClass}">
- ${isMe ? '<div class="absolute right-0 top-0 bg-primary text-white text-[8px] px-1.5 py-0.5 rounded-bl-lg">我</div>' : ''}
- ${rankIconHtml}
- ${avatarHtml}
- <div class="flex-1 min-w-0">
- <h4 class="${nameClass} truncate">${itemName}</h4>
- <p class="text-[10px] text-gray-400">${item.coiName || ''}</p>
- </div>
- <div class="text-right">
- <div class="${valClass}">${val}</div>
- <div class="text-[10px] text-gray-400">${unit}</div>
- </div>
- </div>
- `;
- });
-
- container.innerHTML = html || '<div class="text-center text-gray-400 py-10">暂无数据</div>';
- }
- // 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 = `<i class="fas fa-user-friends text-primary"></i> ${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 += `<li onclick="selectEditOption('${team.coiId}', '${team.coiName}')" class="px-4 py-3 hover:bg-blue-50 cursor-pointer flex items-center justify-between border-b border-gray-50"><span class="font-bold flex items-center gap-2"><i class="fas fa-user-friends text-blue-200"></i> ${team.coiName}</span></li>`;
- });
- 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 = `<i class="fas fa-user-friends text-primary"></i> ${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();
- });
|