| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380 |
- <!DOCTYPE html>
- <html lang="zh-CN">
- <head>
- <meta charset="UTF-8">
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- <title>S3 赛事总览</title>
- <script src="./js/tailwindcss.min.js"></script>
- <link rel="stylesheet" href="./css/all.min.css">
- <style>
- body { font-family: 'Noto Sans SC', 'Roboto', 'PingFang SC', 'Microsoft YaHei', sans-serif; }
- .page-top {
- background-image: url('./static/backgroud/top_bg_egg2.png');
- background-repeat: no-repeat;
- background-position: center;
- background-size: cover;
- min-height: 270px;
- }
- .logo-bg {
- background-image: url('./static/logo/sddx.png');
- background-repeat: no-repeat;
- background-position: center;
- background-size: contain;
- width: 80px; height: 80px; margin-top: 10px;
- }
- .mid-card {
- width: 90%;
- background: #ffffff;
- border-radius: 9px;
- box-shadow: 0px 4px 4px rgba(0, 0, 0, 0.13);
- position: relative;
- z-index: 20;
- margin-left: auto;
- margin-right: auto;
- }
- .mid-line { width: 1px; height: 40px; background-color: #e6e6e6; }
- .path-item { display: flex; align-items: center; justify-content: space-between; background: white; padding: 15px; margin-bottom: 10px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.05); cursor: pointer; border: 2px solid transparent; }
- .path-item.selected { border-color: #81cd00; background-color: #f9fff0; }
- </style>
- </head>
- <body class="bg-gray-50 min-h-screen flex flex-col relative pb-20">
- <!-- Top Section -->
- <div class="page-top w-full flex flex-col items-center pt-8 pb-12">
- <!-- Navbar -->
- <div class="w-full flex justify-between items-center px-4">
- <button onclick="handleBack()" class="bg-black/20 backdrop-blur-sm p-2 rounded-full w-9 h-9 flex items-center justify-center text-white active:scale-90 transition">
- <i class="fas fa-chevron-left"></i>
- </button>
- <h1 id="mc-name" class="text-gray-800 text-lg font-bold">赛事名称</h1>
- <button onclick="handleInfo()" class="bg-black/20 backdrop-blur-sm px-3 py-1.5 rounded-full text-xs font-semibold flex items-center gap-1 text-white active:scale-90 transition">
- <i class="fas fa-question-circle"></i> 说明
- </button>
- </div>
- <div class="flex flex-col items-center gap-2 mt-4">
- <div class="logo-bg"></div>
- <p id="sub-title" class="text-yellow-400 text-xl font-bold drop-shadow-md">活动时间</p>
- </div>
- </div>
- <!-- Mid Section -->
- <div id="mid-type-0" class="mid-card -mt-10 flex flex-col p-4">
- <div class="flex justify-center items-center mb-3 relative">
- <select id="map-select-0" onchange="handleMapChange(this.value)" class="bg-transparent text-gray-500 font-medium text-sm outline-none cursor-pointer">
- </select>
- <button onclick="handleHelp()" class="absolute right-0 text-red-800 text-xs font-medium">帮助</button>
- </div>
- <div class="flex justify-around items-center mb-4 text-xs font-medium text-red-900">
- <div class="flex items-center gap-1"><span id="nick-name-0">昵称</span></div>
- <span id="coi-name-0">组织</span>
- <span id="regroup-btn-0" class="text-gray-400 cursor-pointer hidden" onclick="handleRegroup()">修改</span>
- </div>
- <div class="flex justify-around items-center text-center">
- <div><div class="text-xl font-black" id="stat-num-0">--</div><div class="text-gray-400 text-xs">场次</div></div>
- <div class="mid-line"></div>
- <div><div class="text-xl font-black" id="stat-cp-0">--</div><div class="text-gray-400 text-xs">打点数</div></div>
- <div class="mid-line"></div>
- <div><div class="text-xl font-black" id="stat-rank-0">--</div><div class="text-gray-400 text-xs">个人排名</div></div>
- </div>
- </div>
- <div id="mid-type-1" class="mid-card -mt-16 flex flex-col p-4 hidden">
- <div class="flex justify-center items-center mb-3 relative">
- <select id="map-select-1" onchange="handleMapChange(this.value)" class="bg-transparent text-gray-500 font-medium text-sm outline-none cursor-pointer"></select>
- <div class="absolute right-0 flex gap-3 text-xs font-medium text-red-800">
- <button id="regroup-btn-1" class="hidden" onclick="handleRegroup()">修改</button>
- <button onclick="handleHelp()">帮助</button>
- </div>
- </div>
- <div class="flex justify-between items-center mb-4 text-sm font-medium text-gray-500 px-2">
- <div class="flex items-center gap-1"><span id="nick-name-1" class="text-red-900">昵称</span></div>
- <span id="coi-name-1" class="text-red-900 truncate max-w-[100px]">组织</span>
- <span>场次:<span id="stat-num-1">--</span></span>
- </div>
- <div class="flex justify-around items-center text-center">
- <div><div class="text-xl font-black text-pink-600" id="stat-point-1">--</div><div class="text-gray-400 text-xs">百味豆</div></div>
- <div class="mid-line"></div>
- <div><div class="text-xl font-black" id="stat-dist-1">--</div><div class="text-gray-400 text-xs">里程 km</div></div>
- <div class="mid-line"></div>
- <div><div class="text-xl font-black" id="stat-cp-1">--</div><div class="text-gray-400 text-xs">打点数</div></div>
- <div class="mid-line"></div>
- <div><div class="text-xl font-black" id="stat-pace-1">--</div><div class="text-gray-400 text-xs">最快配速</div></div>
- </div>
- </div>
- <!-- Main Content: Path List -->
- <div class="w-full px-4 mt-6">
- <h3 class="font-bold text-gray-800 mb-3">选择比赛路线</h3>
- <div id="path-list-container" class="flex flex-col gap-3"></div>
- </div>
- <!-- Bottom Action Bar -->
- <div class="fixed bottom-0 w-full bg-white border-t border-gray-100 p-4 z-40">
- <button onclick="handleStartGame()" class="w-full bg-[#81cd00] text-white font-bold py-3 rounded-full shadow-lg active:scale-95 transition-transform">
- 开始比赛
- </button>
- </div>
- <!-- Info Modal -->
- <div id="infoModal" class="fixed inset-0 z-50 hidden transition-opacity duration-300">
- <div class="absolute inset-0 bg-slate-900/70 backdrop-blur-sm" onclick="closeModal('infoModal')"></div>
- <div class="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 w-[90%] bg-white rounded-2xl p-6 shadow-2xl max-h-[80vh] overflow-y-auto">
- <button onclick="closeModal('infoModal')" class="absolute top-2 right-2 text-gray-400 hover:text-gray-600"><i class="fas fa-times"></i></button>
- <div id="info-modal-content" class="text-sm"></div>
- </div>
- </div>
- <script src="./js/utils.js"></script>
- <script src="./js/bridge.js"></script>
- <script src="./js/api.js"></script>
- <script>
- const STATE = {
- ecId: 0,
- token: '',
- mcId: 0,
- mcType: 0,
- mcName: '',
- coiName: '',
- beginSecond: 0,
- endSecond: 0,
- ocaId: 0,
- nickName: '',
- mapList: [],
- pathList: [],
- configParam: { subTitle: '', midType: 0 },
- mapKey: 'rank-tpl-style3-map',
- mcState: 0,
- allowMcSignUp: false,
- stats: {
- regionTotalNum: 0,
- regionTotalCp: 0,
- regionTotalCpRankNum: 0,
- regionTotalSysPoint: 0,
- regionTotalDistance: 0,
- regionFastPace: 0
- }
- };
- function injectCss(css) {
- if (!css) return;
- const style = document.createElement('style');
- style.innerHTML = css;
- document.head.appendChild(style);
- }
- window.onload = function() {
- STATE.token = Tools.getQueryParam('token') || '';
- STATE.ecId = Tools.getQueryParam('id') || 0;
- STATE.mapKey = `${STATE.mapKey}-${STATE.ecId}`;
- const cachedMap = localStorage.getItem(STATE.mapKey);
- if (cachedMap) STATE.ocaId = normalizeOcaId(cachedMap);
- if (window.API) {
- API.setToken(STATE.token);
- if (Tools.getQueryParam('env') === 'mock') {
- API.init({ useMock: true });
- if (!STATE.ecId) STATE.ecId = 'mock_id';
- }
- }
- loadCardConfig();
- matchRsDetailQuery();
- };
- function normalizeOcaId(val) {
- if (val === null || val === undefined) return 0;
- if (typeof val === 'number') return val;
- if (typeof val === 'string') {
- const trimmed = val.trim();
- if (trimmed.startsWith('{') || trimmed.startsWith('[')) {
- try {
- const parsed = JSON.parse(trimmed);
- if (parsed && typeof parsed === 'object' && parsed.ocaId) return parsed.ocaId;
- } catch (e) {}
- }
- const num = Number(trimmed);
- return Number.isNaN(num) ? 0 : num;
- }
- if (typeof val === 'object' && val.ocaId) return val.ocaId;
- return 0;
- }
- function loadCardConfig() {
- if (!window.API) return;
- API.getCardConfig(STATE.ecId, 'rankOverview').then(res => {
- let cfg = res;
- if (res && res.configJson) {
- try { cfg = JSON.parse(res.configJson); } catch (e) { console.warn('config parse fail', e); }
- }
- if (!cfg) return;
- if (cfg.common && cfg.common.css) injectCss(cfg.common.css);
- const pageCfg = cfg.rankOverview || cfg.rank_overview || cfg;
- if (pageCfg && pageCfg.css) injectCss(pageCfg.css);
- if (pageCfg && pageCfg.pathList) STATE.pathList = pageCfg.pathList;
- if (pageCfg && pageCfg.pathListStyle && pageCfg.pathListStyle.showLine === false) {
- // 保留占位,样式已在 pathList 中体现
- }
- if (pageCfg && pageCfg.param) STATE.configParam = Object.assign(STATE.configParam, pageCfg.param);
- document.getElementById('sub-title').innerText = STATE.configParam.subTitle || '';
- });
- }
- function matchRsDetailQuery() {
- if (!window.API) return;
- const payload = { ecId: STATE.ecId, ocaId: STATE.ocaId };
- if (STATE.ocaId) payload.ocaId = STATE.ocaId; // 仅在有值时传入
- if (!payload.ocaId) payload.ocaId = 0;
- API.getMatchRsDetail(payload.ecId, payload.ocaId).then(res => {
- if (!res) return;
- STATE.mcType = res.mcType;
- STATE.mcId = res.mcId;
- STATE.mcName = res.mcName;
- STATE.coiName = res.coiName;
- STATE.beginSecond = res.beginSecond;
- STATE.endSecond = res.endSecond;
- STATE.nickName = res.nickName;
- STATE.stats.regionTotalNum = res.regionTotalNum;
- STATE.stats.regionTotalCp = res.regionTotalCp;
- STATE.stats.regionTotalCpRankNum = res.regionTotalCpRankNum;
- STATE.stats.regionTotalSysPoint = res.regionTotalSysPoint;
- STATE.stats.regionTotalDistance = res.regionTotalDictance;
- STATE.stats.regionFastPace = res.regionFastPace;
- STATE.mcState = Tools.checkMcState(STATE.beginSecond, STATE.endSecond);
- document.getElementById('mc-name').innerText = STATE.mcName || '赛事';
- document.getElementById('sub-title').innerText = STATE.configParam.subTitle || Tools.fmtMcTime2(STATE.beginSecond, STATE.endSecond);
- updateMidStats();
- isAllowMcSignUp();
- mapListQuery();
- });
- }
- function isAllowMcSignUp() {
- if (!window.API) return;
- API.isAllowMcSignUp(STATE.ecId).then(res => {
- if (res) STATE.allowMcSignUp = res.allowSignUp;
- updateMidStats();
- });
- }
- function mapListQuery() {
- if (!window.API || !STATE.mcId) return;
- API.getMapList(STATE.mcId).then(res => {
- if (res && res.length > 0) {
- STATE.mapList = res;
- if (!STATE.ocaId) STATE.ocaId = res[0].ocaId || res[0].mapId || 0;
- renderMapSelect();
- renderPathList();
- localStorage.setItem(STATE.mapKey, STATE.ocaId);
- }
- });
- }
- function renderMapSelect() {
- const midType = STATE.configParam.midType || 0;
- const select = document.getElementById(`map-select-${midType}`);
- select.innerHTML = '';
- STATE.mapList.forEach(map => {
- const opt = document.createElement('option');
- opt.value = map.ocaId;
- opt.innerText = map.mapName || '地图';
- if (map.ocaId == STATE.ocaId) opt.selected = true;
- select.appendChild(opt);
- });
- }
- function renderPathList() {
- const container = document.getElementById('path-list-container');
- container.innerHTML = '';
- const list = STATE.pathList && Object.keys(STATE.pathList).length > 0
- ? Object.values(STATE.pathList).flat()
- : STATE.mapList.map(m => ({ path: { ocaId: m.ocaId, mcType: STATE.mcType }, pathImg: m.mapPic, navImg: '', type: 3, text: m.mapName }));
- list.forEach(item => {
- const ocaId = item.path?.ocaId || item.ocaId || 0;
- const name = item.text || item.pathName || item.mapName || '路线';
- const div = document.createElement('div');
- div.className = `path-item ${ocaId == STATE.ocaId ? 'selected' : ''}`;
- div.innerHTML = `
- <div class="flex items-center gap-3">
- <div class="w-10 h-10 bg-blue-100 rounded-full flex items-center justify-center text-blue-500 font-bold">
- <i class="fas fa-map-marker-alt"></i>
- </div>
- <div>
- <div class="font-bold text-gray-800">${name}</div>
- <div class="text-xs text-gray-400">点击选择此路线</div>
- </div>
- </div>
- ${ocaId == STATE.ocaId ? '<i class="fas fa-check-circle text-green-500 text-xl"></i>' : ''}
- `;
- div.onclick = () => handleMapChange(ocaId);
- container.appendChild(div);
- });
- }
- function updateMidStats() {
- const midType = STATE.configParam.midType || 0;
- document.getElementById('mid-type-0').classList.toggle('hidden', midType !== 0);
- document.getElementById('mid-type-1').classList.toggle('hidden', midType !== 1);
- document.getElementById(`nick-name-${midType}`).innerText = STATE.nickName || '昵称';
- document.getElementById(`coi-name-${midType}`).innerText = STATE.coiName || '组织';
- if (midType === 0) {
- document.getElementById('stat-num-0').innerText = STATE.stats.regionTotalNum ?? '--';
- document.getElementById('stat-cp-0').innerText = STATE.stats.regionTotalCp ?? '--';
- document.getElementById('stat-rank-0').innerText = STATE.stats.regionTotalCpRankNum ?? '--';
- const btn = document.getElementById('regroup-btn-0');
- if (STATE.mcState === 1 && STATE.allowMcSignUp) btn.classList.remove('hidden'); else btn.classList.add('hidden');
- } else {
- document.getElementById('stat-num-1').innerText = STATE.stats.regionTotalNum ?? '--';
- document.getElementById('stat-point-1').innerText = STATE.stats.regionTotalSysPoint ?? '--';
- document.getElementById('stat-dist-1').innerText = Tools.fmtDistance(STATE.stats.regionTotalDistance) ?? '--';
- document.getElementById('stat-cp-1').innerText = STATE.stats.regionTotalCp ?? '--';
- document.getElementById('stat-pace-1').innerText = Tools.convertSecondsToHMS(STATE.stats.regionFastPace, 2);
- const btn = document.getElementById('regroup-btn-1');
- if (STATE.mcState === 1 && STATE.allowMcSignUp) btn.classList.remove('hidden'); else btn.classList.add('hidden');
- }
- }
- function handleMapChange(newOcaId) {
- if (newOcaId != STATE.ocaId) {
- STATE.ocaId = newOcaId;
- localStorage.setItem(STATE.mapKey, STATE.ocaId);
- matchRsDetailQuery();
- renderPathList();
- }
- }
- function handleBack() {
- const qs = window.location.search || `?id=${STATE.ecId}&token=${STATE.token}`;
- Bridge.appAction(`./ranklist.html${qs}`);
- }
- function handleInfo() {
- document.getElementById('info-modal-content').innerHTML = `<p>${STATE.configParam.subTitle || '暂无说明'}</p>`;
- document.getElementById('infoModal').classList.remove('hidden');
- }
- function handleHelp() {
- handleInfo();
- }
- function handleRegroup() {
- const qs = window.location.search || `?id=${STATE.ecId}&token=${STATE.token}`;
- Bridge.appAction(`./signup.html${qs}&from=rankOverview`);
- }
- function handleStartGame() {
- if (STATE.mcState === 1) {
- Bridge.appAction(`action://to_detail/?id=${STATE.ocaId}&matchType=${STATE.mcType}`);
- } else if (STATE.mcState === 0) {
- Tools.showToast('比赛尚未开始');
- } else {
- Tools.showToast('比赛已结束');
- }
- }
- function closeModal(id) {
- document.getElementById(id).classList.add('hidden');
- }
- </script>
- </body>
- </html>
|