648 lines
26 KiB
JavaScript
648 lines
26 KiB
JavaScript
(function () {
|
||
const body = document.body;
|
||
const subtitleEl = document.getElementById('season-subtitle');
|
||
const bannerEl = document.getElementById('season-banner');
|
||
const profileEl = document.getElementById('season-profile');
|
||
const announcementsEl = document.getElementById('season-announcements');
|
||
const cardsEl = document.getElementById('season-cards');
|
||
const tasksEl = document.getElementById('season-tasks');
|
||
const rewardsEl = document.getElementById('season-rewards');
|
||
const decorationsEl = document.getElementById('season-decorations');
|
||
const heroCoverEl = document.getElementById('season-hero-cover');
|
||
const heroTitleEl = document.getElementById('season-hero-title');
|
||
const heroSubEl = document.getElementById('season-hero-sub');
|
||
const dailyCardEl = document.getElementById('season-daily-card');
|
||
const dailyStoryEl = document.getElementById('season-daily-story');
|
||
const dailyStoryHintEl = document.getElementById('season-daily-story-hint');
|
||
const intelEl = document.getElementById('season-intel');
|
||
const intelUnlockBtn = document.getElementById('season-intel-unlock-btn');
|
||
const statusEl = document.getElementById('status');
|
||
const cardRefreshHintEl = document.getElementById('season-card-refresh-hint');
|
||
const passUnlockBtn = document.getElementById('season-pass-unlock-btn');
|
||
const claimAllTasksBtn = document.getElementById('season-claim-all-tasks-btn');
|
||
const chroniclePinEl = document.getElementById('season-chronicle-pin');
|
||
const seasonSwitchEl = document.getElementById('season-switch');
|
||
const seasonSwitchCardsEl = document.getElementById('season-switch-cards');
|
||
const seasonSwitchChronicleEl = document.getElementById('season-switch-chronicle');
|
||
|
||
const tabAllBtn = document.getElementById('season-task-tab-all');
|
||
const tabDailyBtn = document.getElementById('season-task-tab-daily');
|
||
const tabWeeklyBtn = document.getElementById('season-task-tab-weekly');
|
||
const tabSeasonBtn = document.getElementById('season-task-tab-season');
|
||
|
||
let currentTaskFilter = '';
|
||
let cachedPayload = null;
|
||
|
||
const setStatus = (text) => {
|
||
if (statusEl) statusEl.textContent = text || '';
|
||
};
|
||
|
||
const getJson = async (url) => {
|
||
const res = await fetch(url);
|
||
return res.json();
|
||
};
|
||
|
||
const postJson = async (url, payload = {}) => {
|
||
const res = await fetch(url, {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify(payload),
|
||
});
|
||
return res.json();
|
||
};
|
||
|
||
const getCardDetail = async (cardId) => {
|
||
const safeId = String(cardId || '').trim();
|
||
if (!safeId) return null;
|
||
const res = await fetch(`/api/cards/${encodeURIComponent(safeId)}`);
|
||
const data = await res.json();
|
||
if (!data.ok) return null;
|
||
return data.card || null;
|
||
};
|
||
|
||
const levelBadge = (rarity) => {
|
||
const key = String(rarity || '').toLowerCase();
|
||
if (key === 'legendary') return '传说';
|
||
if (key === 'epic') return '史诗';
|
||
if (key === 'rare') return '稀有';
|
||
return '普通';
|
||
};
|
||
|
||
const formatCountdown = (nextRefreshAt) => {
|
||
const text = String(nextRefreshAt || '').trim();
|
||
if (!text) return '下次刷新时间:-';
|
||
const nextTs = Date.parse(text);
|
||
if (!Number.isFinite(nextTs)) return `下次刷新时间:${text}`;
|
||
const diff = Math.max(0, nextTs - Date.now());
|
||
const hour = Math.floor(diff / (1000 * 60 * 60));
|
||
const minute = Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60));
|
||
return `距下次刷新约 ${hour} 小时 ${minute} 分钟(${text})`;
|
||
};
|
||
|
||
const renderBanner = (payload) => {
|
||
if (!bannerEl) return;
|
||
const season = payload.season || {};
|
||
const profile = payload.profile || {};
|
||
bannerEl.textContent = '';
|
||
if (heroCoverEl) {
|
||
const cover = String(season.cover_url || '').trim();
|
||
if (cover) heroCoverEl.style.setProperty('--season-cover', `url('${cover.replace(/'/g, "\\'")}')`);
|
||
}
|
||
const rows = [
|
||
`赛季:${season.name || '-'}`,
|
||
`主题:${season.theme || '-'}`,
|
||
`状态:${season.time_status || '-'}`,
|
||
`时间:${season.start_at || '-'} ~ ${season.end_at || '-'}`,
|
||
season.rest_end_at ? `休整期截止:${season.rest_end_at}` : '',
|
||
`通行证价格:${Number(season.pass_price || 0)} 积分`,
|
||
`赛季等级:Lv.${Number(profile.level || 1)}(总经验 ${Number(profile.xp || 0)})`,
|
||
`赛季币:${Number(profile.season_points || 0)}`,
|
||
].filter(Boolean);
|
||
rows.forEach((text) => {
|
||
const div = document.createElement('div');
|
||
div.textContent = text;
|
||
bannerEl.appendChild(div);
|
||
});
|
||
const titleText = `${season.name || '赛季'} · ${season.theme || '未命名主题'}`;
|
||
if (subtitleEl) subtitleEl.textContent = titleText;
|
||
if (heroTitleEl) heroTitleEl.textContent = season.name || '赛季中心';
|
||
if (heroSubEl) heroSubEl.textContent = titleText;
|
||
if (cardRefreshHintEl) {
|
||
cardRefreshHintEl.textContent = formatCountdown(payload.next_refresh_at || '');
|
||
}
|
||
};
|
||
|
||
const renderSeasonCardCenter = (payload) => {
|
||
const season = payload.season || {};
|
||
const seasonId = String(season.season_id || '').trim();
|
||
if (!seasonId) return;
|
||
const parent = dailyCardEl ? dailyCardEl.parentElement : null;
|
||
if (!parent) return;
|
||
let box = document.getElementById('season-card-center');
|
||
if (!box) {
|
||
box = document.createElement('div');
|
||
box.id = 'season-card-center';
|
||
box.className = 'admin-inline-actions';
|
||
box.style.marginTop = '0.4rem';
|
||
parent.insertBefore(box, dailyCardEl);
|
||
}
|
||
box.textContent = '';
|
||
const link = document.createElement('a');
|
||
link.className = 'link-btn';
|
||
link.href = `/season/${encodeURIComponent(seasonId)}/cards`;
|
||
link.textContent = '进入本赛季游戏卡列表';
|
||
box.appendChild(link);
|
||
};
|
||
|
||
const renderProfile = (payload) => {
|
||
if (!profileEl) return;
|
||
const profile = payload.profile || {};
|
||
profileEl.textContent = '';
|
||
const rows = [
|
||
`通行证:${profile.pass_type === 'premium' ? '高级档' : '免费档'}`,
|
||
`当前等级:${Number(profile.level || 1)}`,
|
||
`本级经验:${Number(profile.xp_in_current_level || 0)} / ${Number(profile.xp_per_level || 100)}`,
|
||
`距离下一级:${Number(profile.xp_to_next_level || 0)}`,
|
||
`已完成任务:${Number(profile.completed_tasks || 0)}`,
|
||
`已领取卡片:${Number(profile.total_claimed_cards || 0)}`,
|
||
];
|
||
rows.forEach((text) => {
|
||
const div = document.createElement('div');
|
||
div.textContent = text;
|
||
profileEl.appendChild(div);
|
||
});
|
||
if (passUnlockBtn) {
|
||
passUnlockBtn.disabled = profile.pass_type === 'premium';
|
||
passUnlockBtn.textContent = profile.pass_type === 'premium' ? '已解锁高级通行证' : '解锁高级通行证';
|
||
}
|
||
};
|
||
|
||
const renderAnnouncements = (rows) => {
|
||
if (!announcementsEl) return;
|
||
announcementsEl.textContent = '';
|
||
const list = Array.isArray(rows) ? rows : [];
|
||
if (!list.length) {
|
||
const li = document.createElement('li');
|
||
li.textContent = '暂无赛季公告';
|
||
announcementsEl.appendChild(li);
|
||
return;
|
||
}
|
||
list.forEach((row) => {
|
||
const li = document.createElement('li');
|
||
li.textContent = `${row.title || '公告'} · ${row.created_at || '-'} · ${row.content || ''}`;
|
||
announcementsEl.appendChild(li);
|
||
});
|
||
};
|
||
|
||
const buildActionBtn = (text, onClick, className = 'secondary-btn') => {
|
||
const btn = document.createElement('button');
|
||
btn.type = 'button';
|
||
btn.className = className;
|
||
btn.textContent = text;
|
||
btn.addEventListener('click', onClick);
|
||
return btn;
|
||
};
|
||
|
||
const renderCards = (rows, payload) => {
|
||
if (!cardsEl) return;
|
||
cardsEl.textContent = '';
|
||
const list = Array.isArray(rows) ? rows : [];
|
||
if (!list.length) {
|
||
const li = document.createElement('li');
|
||
li.textContent = '当前刷新槽位暂无卡片,稍后重试。';
|
||
cardsEl.appendChild(li);
|
||
return;
|
||
}
|
||
list.forEach((row) => {
|
||
const li = document.createElement('li');
|
||
const head = document.createElement('div');
|
||
head.textContent = `【${levelBadge(row.rarity)}】${row.title || '未命名卡片'} · ${row.status || 'active'}`;
|
||
li.appendChild(head);
|
||
|
||
const story = document.createElement('p');
|
||
story.className = 'helper-text';
|
||
story.textContent = row.story_text || '';
|
||
li.appendChild(story);
|
||
|
||
const meta = document.createElement('p');
|
||
meta.className = 'helper-text';
|
||
meta.textContent = `目标:${row.objective || '-'} | 进度 ${Number(row.progress_value || 0)}/${Number(row.target_value || 1)} | 奖励 ${Number(row.reward_points || 0)} 积分 + ${Number(row.reward_xp || 0)} 经验`;
|
||
li.appendChild(meta);
|
||
|
||
const actions = document.createElement('div');
|
||
actions.className = 'admin-inline-actions';
|
||
if (String(row.status || '') === 'completed') {
|
||
actions.appendChild(buildActionBtn('领取奖励', async () => {
|
||
setStatus(`正在领取卡片奖励 #${row.id}...`);
|
||
const data = await postJson(`/api/seasons/active/cards/${row.id}/claim`, {});
|
||
if (!data.ok) {
|
||
setStatus(data.error || '领取失败');
|
||
return;
|
||
}
|
||
setStatus('卡片奖励领取成功。');
|
||
await reload();
|
||
}));
|
||
}
|
||
if (String(row.status || '') === 'active') {
|
||
actions.appendChild(buildActionBtn('手动刷新', async () => {
|
||
setStatus(`正在刷新卡片 #${row.id}...`);
|
||
const data = await postJson(`/api/seasons/active/cards/${row.id}/refresh`, {});
|
||
if (!data.ok) {
|
||
setStatus(data.error || '刷新失败');
|
||
return;
|
||
}
|
||
setStatus('卡片已刷新。');
|
||
await reload();
|
||
}, 'ghost-btn'));
|
||
}
|
||
if (actions.children.length) li.appendChild(actions);
|
||
cardsEl.appendChild(li);
|
||
});
|
||
if (cardRefreshHintEl) {
|
||
cardRefreshHintEl.textContent = formatCountdown(payload.next_refresh_at || '');
|
||
}
|
||
};
|
||
|
||
const renderTasks = (rows) => {
|
||
if (!tasksEl) return;
|
||
tasksEl.textContent = '';
|
||
const list = Array.isArray(rows) ? rows : [];
|
||
const filtered = currentTaskFilter
|
||
? list.filter((row) => String(row.task_type || '') === currentTaskFilter)
|
||
: list;
|
||
if (!filtered.length) {
|
||
const li = document.createElement('li');
|
||
li.textContent = '暂无任务';
|
||
tasksEl.appendChild(li);
|
||
return;
|
||
}
|
||
filtered.forEach((row) => {
|
||
const li = document.createElement('li');
|
||
const title = document.createElement('div');
|
||
title.textContent = `[${row.task_type || '-'}] ${row.title || '未命名任务'} · ${row.status || 'active'}`;
|
||
li.appendChild(title);
|
||
const desc = document.createElement('p');
|
||
desc.className = 'helper-text';
|
||
desc.textContent = `${row.description || ''} | 进度 ${Number(row.progress_value || 0)}/${Number(row.target_value || 1)} | 奖励 ${Number(row.reward_points || 0)} 积分 + ${Number(row.reward_xp || 0)} 经验`;
|
||
li.appendChild(desc);
|
||
if (String(row.status || '') === 'completed') {
|
||
li.appendChild(buildActionBtn('领取任务奖励', async () => {
|
||
setStatus(`正在领取任务 #${row.id}...`);
|
||
const data = await postJson(`/api/seasons/active/tasks/${row.id}/claim`, {});
|
||
if (!data.ok) {
|
||
setStatus(data.error || '领取失败');
|
||
return;
|
||
}
|
||
setStatus('任务奖励领取成功。');
|
||
await reload();
|
||
}));
|
||
}
|
||
tasksEl.appendChild(li);
|
||
});
|
||
};
|
||
|
||
const renderRewards = (rows, claimedIds, profile) => {
|
||
if (!rewardsEl) return;
|
||
rewardsEl.textContent = '';
|
||
const list = Array.isArray(rows) ? rows : [];
|
||
const claimedSet = new Set(Array.isArray(claimedIds) ? claimedIds : []);
|
||
const level = Number(profile?.level || 1);
|
||
if (!list.length) {
|
||
const li = document.createElement('li');
|
||
li.textContent = '暂无通行证奖励配置';
|
||
rewardsEl.appendChild(li);
|
||
return;
|
||
}
|
||
list.slice(0, 120).forEach((row) => {
|
||
const rewardId = String(row.reward_id || '');
|
||
const li = document.createElement('li');
|
||
const locked = level < Number(row.reward_level || 1);
|
||
const claimed = claimedSet.has(rewardId);
|
||
const title = `${row.title || rewardId} · Lv.${Number(row.reward_level || 1)} · ${row.reward_track || 'free'}`;
|
||
li.textContent = `${title} | 奖励 ${Number(row.reward_points || 0)} 积分`;
|
||
if (!locked && !claimed) {
|
||
li.appendChild(buildActionBtn('领取', async () => {
|
||
setStatus(`正在领取赛季奖励 ${rewardId}...`);
|
||
const data = await postJson(`/api/seasons/active/rewards/${encodeURIComponent(rewardId)}/claim`, {});
|
||
if (!data.ok) {
|
||
setStatus(data.error || '领取失败');
|
||
return;
|
||
}
|
||
setStatus('赛季奖励领取成功。');
|
||
await reload();
|
||
}));
|
||
} else {
|
||
const mark = document.createElement('span');
|
||
mark.className = 'helper-text';
|
||
mark.style.marginLeft = '0.5rem';
|
||
mark.textContent = claimed ? '已领取' : '未解锁';
|
||
li.appendChild(mark);
|
||
}
|
||
rewardsEl.appendChild(li);
|
||
});
|
||
};
|
||
|
||
const renderDecorations = (rows, equipped) => {
|
||
if (!decorationsEl) return;
|
||
decorationsEl.textContent = '';
|
||
const list = Array.isArray(rows) ? rows : [];
|
||
if (!list.length) {
|
||
const li = document.createElement('li');
|
||
li.textContent = '暂未获得赛季装扮';
|
||
decorationsEl.appendChild(li);
|
||
return;
|
||
}
|
||
const equippedTitleId = String((equipped?.title || {}).decoration_id || '');
|
||
const equippedFrameId = String((equipped?.avatar_frame || {}).decoration_id || '');
|
||
list.forEach((row) => {
|
||
const type = String(row.decoration_type || '');
|
||
const decorationId = String(row.decoration_id || '');
|
||
const isEquipped = (type === 'title' && decorationId === equippedTitleId)
|
||
|| (type === 'avatar_frame' && decorationId === equippedFrameId)
|
||
|| Boolean(row.is_equipped);
|
||
const li = document.createElement('li');
|
||
li.textContent = `${type === 'title' ? '称号' : '头像框'}:${decorationId}${isEquipped ? '(已佩戴)' : ''}`;
|
||
if (!isEquipped) {
|
||
li.appendChild(buildActionBtn('佩戴', async () => {
|
||
setStatus(`正在佩戴 ${decorationId}...`);
|
||
const data = await postJson('/api/seasons/decorations/equip', {
|
||
decoration_type: type,
|
||
decoration_id: decorationId,
|
||
});
|
||
if (!data.ok) {
|
||
setStatus(data.error || '佩戴失败');
|
||
return;
|
||
}
|
||
setStatus('佩戴成功。');
|
||
await reload();
|
||
}, 'ghost-btn'));
|
||
}
|
||
decorationsEl.appendChild(li);
|
||
});
|
||
};
|
||
|
||
const renderDailyCard = (payload) => {
|
||
if (!dailyCardEl) return;
|
||
dailyCardEl.textContent = '';
|
||
const daily = payload.daily_game_card || {};
|
||
const title = String(daily.title || '').trim() || '今日赛季日卡未生成';
|
||
const summary = String(daily.summary || '').trim();
|
||
const cardId = String(daily.card_id || '').trim();
|
||
const head = document.createElement('div');
|
||
head.className = 'season-daily-head';
|
||
head.textContent = title;
|
||
dailyCardEl.appendChild(head);
|
||
if (summary) {
|
||
const p = document.createElement('p');
|
||
p.className = 'helper-text';
|
||
p.textContent = summary;
|
||
dailyCardEl.appendChild(p);
|
||
}
|
||
if (cardId) {
|
||
const btn = document.createElement('button');
|
||
btn.type = 'button';
|
||
btn.className = 'secondary-btn';
|
||
btn.textContent = '用今日游戏卡开一局';
|
||
btn.addEventListener('click', async () => {
|
||
setStatus('正在创建房间...');
|
||
const data = await postJson('/api/rooms/create', { card_id: cardId });
|
||
if (!data.ok) {
|
||
setStatus(data.error || '创建失败');
|
||
return;
|
||
}
|
||
if (data.player_id) localStorage.setItem(`echo-player-${data.room_id}`, data.player_id);
|
||
if (data.player_name) localStorage.setItem('echo-last-name', data.player_name);
|
||
window.location.href = data.redirect;
|
||
});
|
||
dailyCardEl.appendChild(btn);
|
||
}
|
||
|
||
const hint = document.createElement('p');
|
||
hint.className = 'helper-text';
|
||
hint.textContent = '提示:通关这张日卡后,才能解锁当日资料。';
|
||
dailyCardEl.appendChild(hint);
|
||
};
|
||
|
||
const renderDailyStory = async (payload) => {
|
||
if (!dailyStoryEl) return;
|
||
dailyStoryEl.textContent = '';
|
||
const daily = payload.daily_game_card || {};
|
||
const cardId = String(daily.card_id || '').trim();
|
||
if (!cardId) {
|
||
dailyStoryEl.textContent = '今日故事未生成';
|
||
return;
|
||
}
|
||
const card = await getCardDetail(cardId);
|
||
if (!card) {
|
||
dailyStoryEl.textContent = '今日故事加载失败';
|
||
return;
|
||
}
|
||
|
||
const title = document.createElement('div');
|
||
title.className = 'season-daily-head';
|
||
title.textContent = `“${card.title || '未命名故事'}”`;
|
||
dailyStoryEl.appendChild(title);
|
||
|
||
const summary = document.createElement('p');
|
||
summary.className = 'helper-text';
|
||
summary.textContent = card.summary || '';
|
||
dailyStoryEl.appendChild(summary);
|
||
|
||
const goals = Array.isArray(card.starter_goals) ? card.starter_goals : [];
|
||
const objective = document.createElement('p');
|
||
objective.className = 'helper-text';
|
||
objective.textContent = goals[0] || '探索今日线索并推进剧情';
|
||
dailyStoryEl.appendChild(objective);
|
||
|
||
const reward = document.createElement('div');
|
||
reward.className = 'helper-text';
|
||
reward.textContent = '奖励:赛季经验/积分结算';
|
||
dailyStoryEl.appendChild(reward);
|
||
|
||
const actions = document.createElement('div');
|
||
actions.className = 'admin-inline-actions';
|
||
|
||
const acceptBtn = document.createElement('button');
|
||
acceptBtn.type = 'button';
|
||
acceptBtn.className = 'secondary-btn';
|
||
acceptBtn.textContent = '接受挑战';
|
||
acceptBtn.addEventListener('click', async () => {
|
||
setStatus('正在创建房间...');
|
||
const data = await postJson('/api/rooms/create', { card_id: cardId });
|
||
if (!data.ok) {
|
||
setStatus(data.error || '创建失败');
|
||
return;
|
||
}
|
||
if (data.player_id) localStorage.setItem(`echo-player-${data.room_id}`, data.player_id);
|
||
if (data.player_name) localStorage.setItem('echo-last-name', data.player_name);
|
||
window.location.href = data.redirect;
|
||
});
|
||
|
||
const refreshBtn = document.createElement('button');
|
||
refreshBtn.type = 'button';
|
||
refreshBtn.className = 'ghost-btn';
|
||
refreshBtn.textContent = '刷新';
|
||
refreshBtn.addEventListener('click', async () => {
|
||
setStatus('正在刷新今日故事...');
|
||
await reload();
|
||
setStatus('今日故事已刷新。');
|
||
});
|
||
|
||
actions.appendChild(acceptBtn);
|
||
actions.appendChild(refreshBtn);
|
||
dailyStoryEl.appendChild(actions);
|
||
|
||
if (dailyStoryHintEl) {
|
||
const locked = Array.isArray(daily.locked_intel) ? daily.locked_intel.length : 0;
|
||
const unlocked = Array.isArray(payload.intel)
|
||
? payload.intel.filter((it) => String(it.day_key || '') === String(daily.day_key || '')).length
|
||
: 0;
|
||
const pending = Math.max(0, locked - unlocked);
|
||
dailyStoryHintEl.textContent = pending ? `还有 ${pending} 条线索待查看 →` : '今日线索已全部查看';
|
||
}
|
||
};
|
||
|
||
const renderIntel = (payload) => {
|
||
if (!intelEl) return;
|
||
intelEl.textContent = '';
|
||
const rows = Array.isArray(payload.intel) ? payload.intel : [];
|
||
if (!rows.length) {
|
||
const li = document.createElement('li');
|
||
li.textContent = '暂无资料,先解锁一条。';
|
||
intelEl.appendChild(li);
|
||
return;
|
||
}
|
||
rows.slice(0, 80).forEach((row) => {
|
||
const li = document.createElement('li');
|
||
li.textContent = `${row.intel_title || '资料'} · ${row.day_key || '-'} · ${row.intel_text || ''}`;
|
||
intelEl.appendChild(li);
|
||
});
|
||
};
|
||
|
||
const renderTaskTabs = () => {
|
||
const update = () => {
|
||
[tabAllBtn, tabDailyBtn, tabWeeklyBtn, tabSeasonBtn].forEach((btn) => {
|
||
if (!btn) return;
|
||
btn.classList.remove('secondary-btn');
|
||
});
|
||
if (!currentTaskFilter && tabAllBtn) tabAllBtn.classList.add('secondary-btn');
|
||
if (currentTaskFilter === 'daily' && tabDailyBtn) tabDailyBtn.classList.add('secondary-btn');
|
||
if (currentTaskFilter === 'weekly' && tabWeeklyBtn) tabWeeklyBtn.classList.add('secondary-btn');
|
||
if (currentTaskFilter === 'season' && tabSeasonBtn) tabSeasonBtn.classList.add('secondary-btn');
|
||
if (cachedPayload) renderTasks(cachedPayload.tasks || []);
|
||
};
|
||
if (tabAllBtn) tabAllBtn.addEventListener('click', () => { currentTaskFilter = ''; update(); });
|
||
if (tabDailyBtn) tabDailyBtn.addEventListener('click', () => { currentTaskFilter = 'daily'; update(); });
|
||
if (tabWeeklyBtn) tabWeeklyBtn.addEventListener('click', () => { currentTaskFilter = 'weekly'; update(); });
|
||
if (tabSeasonBtn) tabSeasonBtn.addEventListener('click', () => { currentTaskFilter = 'season'; update(); });
|
||
update();
|
||
};
|
||
|
||
const bindTopActions = () => {
|
||
if (passUnlockBtn) {
|
||
passUnlockBtn.addEventListener('click', async () => {
|
||
setStatus('正在解锁高级通行证...');
|
||
const data = await postJson('/api/seasons/active/pass/unlock', {});
|
||
if (!data.ok) {
|
||
setStatus(data.error || '解锁失败');
|
||
return;
|
||
}
|
||
setStatus('高级通行证已解锁。');
|
||
await reload();
|
||
});
|
||
}
|
||
if (claimAllTasksBtn) {
|
||
claimAllTasksBtn.addEventListener('click', async () => {
|
||
setStatus('正在一键领取任务奖励...');
|
||
const data = await postJson('/api/seasons/active/tasks/claim-all', {});
|
||
if (!data.ok) {
|
||
setStatus(data.error || '领取失败');
|
||
return;
|
||
}
|
||
setStatus(`已领取 ${Number((data.result || {}).claimed_count || 0)} 个任务奖励。`);
|
||
await reload();
|
||
});
|
||
}
|
||
};
|
||
|
||
const bindIntelActions = () => {
|
||
if (!intelUnlockBtn) return;
|
||
intelUnlockBtn.addEventListener('click', async () => {
|
||
const payload = cachedPayload || {};
|
||
const daily = payload.daily_game_card || {};
|
||
const dayKey = String(daily.day_key || '').trim();
|
||
if (!dayKey) {
|
||
setStatus('今日赛季日卡尚未生成,稍后再试。');
|
||
return;
|
||
}
|
||
setStatus('正在解锁资料...');
|
||
const data = await postJson('/api/seasons/active/intel/unlock', { day_key: dayKey });
|
||
if (!data.ok) {
|
||
if (String(data.error || '').includes('通关')) {
|
||
setStatus(data.error);
|
||
} else {
|
||
setStatus(data.error || '解锁失败');
|
||
}
|
||
return;
|
||
}
|
||
setStatus(`已解锁资料:${(data.unlocked || {}).intel_title || ''}`);
|
||
await reload();
|
||
});
|
||
};
|
||
|
||
const renderAll = (payload) => {
|
||
cachedPayload = payload || {};
|
||
renderBanner(payload || {});
|
||
renderProfile(payload || {});
|
||
renderAnnouncements(payload.announcements || []);
|
||
renderSeasonCardCenter(payload || {});
|
||
bindSeasonSwitch(payload || {});
|
||
renderDailyStory(payload || {}).catch((error) => setStatus(`网络错误: ${error}`));
|
||
renderDailyCard(payload || {});
|
||
renderIntel(payload || {});
|
||
renderCards(payload.cards || [], payload || {});
|
||
renderTasks(payload.tasks || []);
|
||
renderRewards(payload.rewards || [], payload.claimed_reward_ids || [], payload.profile || {});
|
||
renderDecorations(payload.decorations || [], payload.equipped_decorations || {});
|
||
const timeStatus = String((payload.season || {}).time_status || '');
|
||
if (chroniclePinEl) {
|
||
chroniclePinEl.style.display = timeStatus === 'rest' ? '' : 'none';
|
||
}
|
||
};
|
||
|
||
const bindSeasonSwitch = (payload) => {
|
||
if (!seasonSwitchEl || !seasonSwitchCardsEl || !seasonSwitchChronicleEl) return;
|
||
if (seasonSwitchEl.dataset.bound === '1') return;
|
||
seasonSwitchEl.dataset.bound = '1';
|
||
|
||
const applyLinks = () => {
|
||
const seasonId = String(seasonSwitchEl.value || '').trim();
|
||
seasonSwitchCardsEl.href = seasonId ? `/season/${encodeURIComponent(seasonId)}/cards` : '#';
|
||
seasonSwitchChronicleEl.href = seasonId ? `/season/${encodeURIComponent(seasonId)}/chronicle` : '#';
|
||
seasonSwitchCardsEl.classList.toggle('disabled', !seasonId);
|
||
seasonSwitchChronicleEl.classList.toggle('disabled', !seasonId);
|
||
};
|
||
|
||
seasonSwitchEl.addEventListener('change', applyLinks);
|
||
applyLinks();
|
||
|
||
(async () => {
|
||
const res = await getJson('/api/seasons/history');
|
||
if (!res.ok) {
|
||
setStatus(res.error || '加载赛季列表失败');
|
||
return;
|
||
}
|
||
const seasons = Array.isArray(res.seasons) ? res.seasons : [];
|
||
const currentId = String((payload.season || {}).season_id || '').trim();
|
||
seasonSwitchEl.textContent = '';
|
||
seasons.slice(0, 60).forEach((s) => {
|
||
const sid = String((s || {}).season_id || '').trim();
|
||
if (!sid) return;
|
||
const opt = document.createElement('option');
|
||
const name = String((s || {}).name || '').trim() || '未命名赛季';
|
||
const theme = String((s || {}).theme || '').trim();
|
||
const status = String((s || {}).time_status || '').trim();
|
||
opt.value = sid;
|
||
opt.textContent = `${name}${theme ? ` · ${theme}` : ''}${status ? ` · ${status}` : ''}`;
|
||
if (sid === currentId) opt.selected = true;
|
||
seasonSwitchEl.appendChild(opt);
|
||
});
|
||
applyLinks();
|
||
})().catch((error) => setStatus(`网络错误: ${error}`));
|
||
};
|
||
|
||
const reload = async () => {
|
||
const data = await getJson('/api/seasons/active');
|
||
if (!data.ok) {
|
||
setStatus(data.error || '加载赛季信息失败');
|
||
return;
|
||
}
|
||
renderAll(data);
|
||
};
|
||
|
||
bindTopActions();
|
||
bindIntelActions();
|
||
renderTaskTabs();
|
||
reload().catch((error) => setStatus(`网络错误: ${error}`));
|
||
})();
|