374 lines
16 KiB
JavaScript
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';
|
|
}
|
|
}); |