Files
worldcup-predictor/frontend/js/app.js

374 lines
16 KiB
JavaScript

// API基础路径
const API_BASE = '';
// 页面加载完成后执行
document.addEventListener('DOMContentLoaded', () => {
loadOverviewStats();
loadUpcomingMatches();
loadFinishedMatches();
loadAlgorithms();
initTabs();
});
// 初始化标签切换
function initTabs() {
document.querySelectorAll('.tab-btn').forEach(btn => {
btn.addEventListener('click', () => {
// 移除所有活动状态
document.querySelectorAll('.tab-btn').forEach(b => b.classList.remove('active'));
document.querySelectorAll('.tab-content').forEach(c => c.classList.remove('active'));
// 添加当前活动状态
btn.classList.add('active');
const tabId = btn.dataset.tab + '-tab';
document.getElementById(tabId).classList.add('active');
});
});
}
// 加载总体统计
async function loadOverviewStats() {
try {
const response = await fetch(`${API_BASE}/api/stats/overview`);
const result = await response.json();
if (result.success) {
document.getElementById('total-matches').textContent = result.data.total_matches;
document.getElementById('upcoming-matches').textContent = result.data.upcoming_matches;
document.getElementById('active-algorithms').textContent = result.data.active_algorithms;
document.getElementById('overall-accuracy').textContent = result.data.overall_accuracy.toFixed(1) + '%';
}
} catch (error) {
console.error('加载统计数据失败:', error);
}
}
// 加载即将进行的比赛
async function loadUpcomingMatches() {
try {
const response = await fetch(`${API_BASE}/api/matches?status=upcoming`);
const result = await response.json();
if (result.success) {
const container = document.getElementById('upcoming-matches-list');
if (result.data.length === 0) {
container.innerHTML = '<p style="text-align: center; color: #a0a0a0;">暂无即将进行的比赛</p>';
return;
}
// 获取每场比赛的预测
for (const match of result.data) {
const predictions = await getMatchPredictions(match.id);
match.predictions = predictions;
}
container.innerHTML = result.data.map(match => renderMatchCard(match, false)).join('');
}
} catch (error) {
console.error('加载比赛失败:', error);
}
}
// 加载已完成的比赛
async function loadFinishedMatches() {
try {
const response = await fetch(`${API_BASE}/api/matches?status=finished`);
const result = await response.json();
if (result.success) {
const container = document.getElementById('finished-matches-list');
if (result.data.length === 0) {
container.innerHTML = '<p style="text-align: center; color: #a0a0a0;">暂无已完成的比赛</p>';
return;
}
// 获取每场比赛的预测
for (const match of result.data) {
const predictions = await getMatchPredictions(match.id);
match.predictions = predictions;
}
container.innerHTML = result.data.map(match => renderMatchCard(match, true)).join('');
}
} catch (error) {
console.error('加载比赛失败:', error);
}
}
// 获取比赛预测
async function getMatchPredictions(matchId) {
try {
const response = await fetch(`${API_BASE}/api/matches/${matchId}`);
const result = await response.json();
return result.success ? result.data.predictions : [];
} catch (error) {
console.error('获取预测失败:', error);
return [];
}
}
// 渲染比赛卡片
function renderMatchCard(match, isFinished) {
const matchTime = match.match_time ? new Date(match.match_time).toLocaleString('zh-CN') : '待定';
let predictionsHtml = '';
if (match.predictions && match.predictions.length > 0) {
predictionsHtml = `
<div class="match-predictions">
<h4 style="margin-bottom: 10px; color: #a0a0a0; font-size: 13px;">预测结果</h4>
${match.predictions.map(pred => {
let resultText = '';
let resultClass = '';
if (isFinished && pred.is_correct !== null) {
resultText = pred.is_correct ? '✓ 正确' : '✗ 错误';
resultClass = pred.is_correct ? 'correct' : 'wrong';
}
return `
<div class="prediction-item">
<span class="prediction-algorithm">${pred.algorithm_name}</span>
<span>
${pred.predicted_home_score !== null ? `${pred.predicted_home_score} : ${pred.predicted_away_score}` : ''}
${pred.predicted_winner ? `(${getWinnerText(pred.predicted_winner, match.home_team, match.away_team)})` : ''}
</span>
${resultText ? `<span class="prediction-result ${resultClass}">${resultText}</span>` : ''}
</div>
`;
}).join('')}
</div>
`;
}
return `
<div class="match-card" onclick="showMatchDetail(${match.id})">
<div class="match-header">
<span class="match-round">${match.round_name || '小组赛'}</span>
<span class="match-time">${matchTime}</span>
</div>
<div class="match-teams">
<div class="team">
<div class="team-name">${match.home_team}</div>
</div>
<div class="vs">VS</div>
<div class="team">
<div class="team-name">${match.away_team}</div>
</div>
</div>
${isFinished && match.home_score !== null ? `
<div class="match-score">
<span class="score-box">${match.home_score}</span>
<span>:</span>
<span class="score-box">${match.away_score}</span>
</div>
` : ''}
${predictionsHtml}
</div>
`;
}
// 获取胜负文本
function getWinnerText(winner, homeTeam, awayTeam) {
if (winner === 'home') return homeTeam + '胜';
if (winner === 'away') return awayTeam + '胜';
if (winner === 'draw') return '平局';
return winner;
}
// 加载算法排行
async function loadAlgorithms() {
try {
const response = await fetch(`${API_BASE}/api/algorithms`);
const result = await response.json();
if (result.success) {
const container = document.getElementById('algorithms-list');
if (result.data.length === 0) {
container.innerHTML = '<p style="text-align: center; color: #a0a0a0;">暂无算法数据</p>';
return;
}
container.innerHTML = result.data.map(algo => `
<div class="algorithm-card" onclick="showAlgorithmDetail(${algo.id})">
<div class="algorithm-name">${algo.name}</div>
<div class="algorithm-desc">${algo.description || '暂无描述'}</div>
<div class="algorithm-stats">
<div class="algo-stat">
<div class="algo-stat-value">${(algo.accuracy || 0).toFixed(1)}%</div>
<div class="algo-stat-label">准确率</div>
</div>
<div class="algo-stat">
<div class="algo-stat-value">${algo.total_predictions || 0}</div>
<div class="algo-stat-label">预测数</div>
</div>
<div class="algo-stat">
<div class="algo-stat-value">${algo.correct_predictions || 0}</div>
<div class="algo-stat-label">正确数</div>
</div>
</div>
</div>
`).join('');
}
} catch (error) {
console.error('加载算法失败:', error);
}
}
// 显示比赛详情
async function showMatchDetail(matchId) {
try {
const response = await fetch(`${API_BASE}/api/matches/${matchId}`);
const result = await response.json();
if (result.success) {
const match = result.data.match;
const predictions = result.data.predictions;
const matchTime = match.match_time ? new Date(match.match_time).toLocaleString('zh-CN') : '待定';
let predictionsHtml = '';
if (predictions.length > 0) {
predictionsHtml = `
<h3 style="margin-top: 20px; color: #00cec9;">预测详情</h3>
<div style="margin-top: 15px;">
${predictions.map(pred => `
<div style="background: rgba(0,0,0,0.2); padding: 15px; border-radius: 8px; margin-bottom: 10px;">
<div style="display: flex; justify-content: space-between; align-items: center;">
<span style="color: #00cec9; font-weight: bold;">${pred.algorithm_name}</span>
<span style="color: #a0a0a0;">准确率: ${(pred.algorithm_accuracy || 0).toFixed(1)}%</span>
</div>
<div style="margin-top: 10px; color: #e0e0e0;">
${pred.predicted_home_score !== null ?
`预测比分: ${pred.predicted_home_score} : ${pred.predicted_away_score}` :
''}
${pred.predicted_winner ?
`<br>预测胜负: ${getWinnerText(pred.predicted_winner, match.home_team, match.away_team)}` :
''}
${pred.confidence ?
`<br>置信度: ${(pred.confidence * 100).toFixed(0)}%` :
''}
</div>
${match.status === 'finished' && pred.is_correct !== null ? `
<div style="margin-top: 10px; padding: 5px 10px; border-radius: 5px; display: inline-block;
background: ${pred.is_correct ? 'rgba(46, 213, 115, 0.2)' : 'rgba(255, 71, 87, 0.2)'};
color: ${pred.is_correct ? '#2ed573' : '#ff4757'};">
${pred.is_correct ? '✓ 预测正确' : '✗ 预测错误'}
</div>
` : ''}
</div>
`).join('')}
</div>
`;
}
document.getElementById('modal-title').textContent =
`${match.home_team} vs ${match.away_team}`;
document.getElementById('modal-body').innerHTML = `
<div style="text-align: center; margin: 20px 0;">
<span class="status-badge ${match.status === 'finished' ? 'status-finished' : 'status-upcoming'}">
${match.status === 'finished' ? '已完成' : '即将进行'}
</span>
</div>
<div style="text-align: center; color: #a0a0a0; margin-bottom: 15px;">
${match.round_name || '小组赛'} | ${matchTime}
</div>
${match.status === 'finished' && match.home_score !== null ? `
<div style="text-align: center; font-size: 36px; margin: 20px 0;">
<span style="color: #00cec9;">${match.home_score}</span>
<span style="color: #a0a0a0;"> : </span>
<span style="color: #00cec9;">${match.away_score}</span>
</div>
` : ''}
${predictionsHtml}
`;
document.getElementById('match-modal').style.display = 'block';
}
} catch (error) {
console.error('加载比赛详情失败:', error);
}
}
// 显示算法详情
async function showAlgorithmDetail(algoId) {
try {
const response = await fetch(`${API_BASE}/api/algorithms/${algoId}`);
const result = await response.json();
if (result.success) {
const algo = result.data.algorithm;
const history = result.data.history;
let historyHtml = '';
if (history.length > 0) {
historyHtml = `
<h3 style="margin-top: 25px; color: #00cec9;">预测历史</h3>
<div style="max-height: 300px; overflow-y: auto; margin-top: 15px;">
${history.map(h => `
<div style="background: rgba(0,0,0,0.2); padding: 12px; border-radius: 8px; margin-bottom: 10px;">
<div style="display: flex; justify-content: space-between;">
<span>${h.home_team} vs ${h.away_team}</span>
<span style="color: #a0a0a0; font-size: 12px;">
${h.match_time ? new Date(h.match_time).toLocaleDateString('zh-CN') : '待定'}
</span>
</div>
<div style="margin-top: 8px; color: #e0e0e0; font-size: 13px;">
预测: ${h.predicted_home_score} : ${h.predicted_away_score}
${h.home_score !== null ? ` | 实际: ${h.home_score} : ${h.away_score}` : ''}
</div>
${h.is_correct !== null ? `
<span style="margin-top: 5px; padding: 2px 8px; border-radius: 3px; font-size: 12px;
background: ${h.is_correct ? 'rgba(46, 213, 115, 0.2)' : 'rgba(255, 71, 87, 0.2)'};
color: ${h.is_correct ? '#2ed573' : '#ff4757'};">
${h.is_correct ? '正确' : '错误'}
</span>
` : ''}
</div>
`).join('')}
</div>
`;
}
document.getElementById('modal-title').textContent = algo.name;
document.getElementById('modal-body').innerHTML = `
<p style="color: #a0a0a0; margin-bottom: 20px;">${algo.description || '暂无描述'}</p>
<div class="algorithm-stats" style="background: rgba(0,0,0,0.2); padding: 20px; border-radius: 10px;">
<div class="algo-stat">
<div class="algo-stat-value">${(algo.accuracy || 0).toFixed(1)}%</div>
<div class="algo-stat-label">准确率</div>
</div>
<div class="algo-stat">
<div class="algo-stat-value">${algo.total_predictions || 0}</div>
<div class="algo-stat-label">总预测数</div>
</div>
<div class="algo-stat">
<div class="algo-stat-value">${algo.correct_predictions || 0}</div>
<div class="algo-stat-label">正确预测</div>
</div>
</div>
${historyHtml}
`;
document.getElementById('match-modal').style.display = 'block';
}
} catch (error) {
console.error('加载算法详情失败:', error);
}
}
// 关闭弹窗
document.querySelectorAll('.close').forEach(btn => {
btn.addEventListener('click', () => {
btn.parentElement.parentElement.style.display = 'none';
});
});
// 点击弹窗外部关闭
window.addEventListener('click', (e) => {
if (e.target.classList.contains('modal')) {
e.target.style.display = 'none';
}
});