世界杯预测系统 v1.0.0 - 初始版本

This commit is contained in:
2026-06-28 15:57:43 +08:00
commit 7a290ef4f7
11 changed files with 2530 additions and 0 deletions

513
frontend/js/admin.js Normal file
View File

@@ -0,0 +1,513 @@
// API基础路径
const API_BASE = '';
// 页面加载完成后执行
document.addEventListener('DOMContentLoaded', () => {
loadConfig();
loadAlgorithms();
loadMatches();
loadPredictions();
initTabs();
initForms();
});
// 初始化标签切换
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');
});
});
}
// 初始化表单
function initForms() {
// 算法表单
document.getElementById('algorithm-form').addEventListener('submit', async (e) => {
e.preventDefault();
const id = document.getElementById('algorithm-id').value;
const data = {
name: document.getElementById('algorithm-name').value,
description: document.getElementById('algorithm-desc').value,
is_active: parseInt(document.getElementById('algorithm-active').value)
};
try {
const url = id ? `${API_BASE}/api/algorithms/${id}` : `${API_BASE}/api/algorithms`;
const method = id ? 'PUT' : 'POST';
const response = await fetch(url, {
method,
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
});
const result = await response.json();
if (result.success) {
closeModal('algorithm-modal');
loadAlgorithms();
alert(id ? '算法更新成功' : '算法添加成功');
} else {
alert(result.message || '操作失败');
}
} catch (error) {
console.error('保存算法失败:', error);
alert('操作失败,请重试');
}
});
// 比赛表单
document.getElementById('match-form').addEventListener('submit', async (e) => {
e.preventDefault();
const id = document.getElementById('match-id').value;
const data = {
match_code: document.getElementById('match-code').value,
home_team: document.getElementById('home-team').value,
away_team: document.getElementById('away-team').value,
match_time: document.getElementById('match-time').value || null,
round_name: document.getElementById('match-round').value,
status: document.getElementById('match-status').value,
home_score: document.getElementById('home-score').value || null,
away_score: document.getElementById('away-score').value || null
};
try {
const url = id ? `${API_BASE}/api/matches/${id}` : `${API_BASE}/api/matches`;
const method = id ? 'PUT' : 'POST';
const response = await fetch(url, {
method,
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
});
const result = await response.json();
if (result.success) {
closeModal('match-modal');
loadMatches();
loadPredictions();
alert(id ? '比赛更新成功' : '比赛添加成功');
} else {
alert(result.message || '操作失败');
}
} catch (error) {
console.error('保存比赛失败:', error);
alert('操作失败,请重试');
}
});
// 预测表单
document.getElementById('prediction-form').addEventListener('submit', async (e) => {
e.preventDefault();
const data = {
match_id: document.getElementById('prediction-match').value,
algorithm_id: document.getElementById('prediction-algorithm').value,
predicted_home_score: document.getElementById('pred-home-score').value || null,
predicted_away_score: document.getElementById('pred-away-score').value || null,
predicted_winner: document.getElementById('pred-winner').value || null,
confidence: document.getElementById('pred-confidence').value / 100
};
try {
const response = await fetch(`${API_BASE}/api/predictions`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
});
const result = await response.json();
if (result.success) {
closeModal('prediction-modal');
loadPredictions();
alert('预测添加成功');
} else {
alert(result.message || '操作失败');
}
} catch (error) {
console.error('保存预测失败:', error);
alert('操作失败,请重试');
}
});
// 置信度滑块
document.getElementById('pred-confidence').addEventListener('input', (e) => {
document.getElementById('confidence-value').textContent = e.target.value + '%';
});
}
// 加载配置
async function loadConfig() {
try {
const response = await fetch(`${API_BASE}/api/config`);
const result = await response.json();
if (result.success) {
document.getElementById('prediction-cycle').value = result.data.prediction_cycle_hours || 24;
document.getElementById('last-prediction-time').value = result.data.last_prediction_time || '';
document.getElementById('world-cup-year').value = result.data.world_cup_year || '2026';
}
} catch (error) {
console.error('加载配置失败:', error);
}
}
// 保存配置
async function saveConfig() {
const data = {
prediction_cycle_hours: document.getElementById('prediction-cycle').value,
last_prediction_time: document.getElementById('last-prediction-time').value,
world_cup_year: document.getElementById('world-cup-year').value
};
try {
const response = await fetch(`${API_BASE}/api/config`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
});
const result = await response.json();
if (result.success) {
alert('配置保存成功');
} else {
alert('保存失败');
}
} catch (error) {
console.error('保存配置失败:', error);
alert('保存失败,请重试');
}
}
// 加载算法列表
async function loadAlgorithms() {
try {
const response = await fetch(`${API_BASE}/api/algorithms`);
const result = await response.json();
if (result.success) {
const tbody = document.getElementById('algorithms-table');
if (result.data.length === 0) {
tbody.innerHTML = '<tr><td colspan="8" style="text-align: center; color: #a0a0a0;">暂无算法数据</td></tr>';
return;
}
tbody.innerHTML = result.data.map(algo => `
<tr>
<td>${algo.id}</td>
<td><strong>${algo.name}</strong></td>
<td>${algo.description || '-'}</td>
<td><span style="color: #00cec9;">${(algo.accuracy || 0).toFixed(1)}%</span></td>
<td>${algo.total_predictions || 0}</td>
<td>${algo.correct_predictions || 0}</td>
<td>
<span class="status-badge ${algo.is_active ? 'status-active' : 'status-inactive'}">
${algo.is_active ? '启用' : '禁用'}
</span>
</td>
<td>
<button onclick="editAlgorithm(${algo.id})" class="btn-secondary btn-small">编辑</button>
<button onclick="deleteAlgorithm(${algo.id})" class="btn-danger btn-small" style="margin-left: 5px;">删除</button>
</td>
</tr>
`).join('');
}
} catch (error) {
console.error('加载算法失败:', error);
}
}
// 加载比赛列表
async function loadMatches() {
try {
const response = await fetch(`${API_BASE}/api/matches`);
const result = await response.json();
if (result.success) {
const tbody = document.getElementById('matches-table');
if (result.data.length === 0) {
tbody.innerHTML = '<tr><td colspan="8" style="text-align: center; color: #a0a0a0;">暂无比赛数据</td></tr>';
return;
}
tbody.innerHTML = result.data.map(match => `
<tr>
<td>${match.match_code}</td>
<td>${match.home_team}</td>
<td>${match.away_team}</td>
<td>${match.match_time ? new Date(match.match_time).toLocaleString('zh-CN') : '待定'}</td>
<td>${match.home_score !== null ? `${match.home_score} : ${match.away_score}` : '-'}</td>
<td>${match.round_name || '小组赛'}</td>
<td>
<span class="status-badge ${match.status === 'finished' ? 'status-finished' : 'status-upcoming'}">
${match.status === 'finished' ? '已完成' : '即将进行'}
</span>
</td>
<td>
<button onclick="editMatch(${match.id})" class="btn-secondary btn-small">编辑</button>
<button onclick="deleteMatch(${match.id})" class="btn-danger btn-small" style="margin-left: 5px;">删除</button>
</td>
</tr>
`).join('');
// 更新预测管理的比赛筛选下拉框
const filterSelect = document.getElementById('prediction-match-filter');
const currentValue = filterSelect.value;
filterSelect.innerHTML = '<option value="">全部比赛</option>' +
result.data.map(m => `<option value="${m.id}">${m.home_team} vs ${m.away_team}</option>`).join('');
filterSelect.value = currentValue;
// 更新预测表单的比赛下拉框
const predMatchSelect = document.getElementById('prediction-match');
predMatchSelect.innerHTML = '<option value="">-- 选择比赛 --</option>' +
result.data.map(m => `<option value="${m.id}">${m.home_team} vs ${m.away_team}</option>`).join('');
}
} catch (error) {
console.error('加载比赛失败:', error);
}
}
// 加载预测列表
async function loadPredictions() {
try {
const matchFilter = document.getElementById('prediction-match-filter').value;
// 先获取所有比赛
const matchesResponse = await fetch(`${API_BASE}/api/matches`);
const matchesResult = await matchesResponse.json();
if (!matchesResult.success) return;
// 获取所有算法
const algorithmsResponse = await fetch(`${API_BASE}/api/algorithms`);
const algorithmsResult = await algorithmsResponse.json();
if (!algorithmsResult.success) return;
const algorithms = algorithmsResult.data;
// 获取每场比赛的预测
let allPredictions = [];
for (const match of matchesResult.data) {
if (matchFilter && match.id != matchFilter) continue;
const predResponse = await fetch(`${API_BASE}/api/matches/${match.id}`);
const predResult = await predResponse.json();
if (predResult.success && predResult.data.predictions.length > 0) {
predResult.data.predictions.forEach(pred => {
allPredictions.push({
...pred,
match: match
});
});
}
}
const tbody = document.getElementById('predictions-table');
if (allPredictions.length === 0) {
tbody.innerHTML = '<tr><td colspan="8" style="text-align: center; color: #a0a0a0;">暂无预测数据</td></tr>';
return;
}
tbody.innerHTML = allPredictions.map(pred => `
<tr>
<td>${pred.match.home_team} vs ${pred.match.away_team}</td>
<td>${pred.algorithm_name}</td>
<td>${pred.predicted_home_score !== null ? `${pred.predicted_home_score} : ${pred.predicted_away_score}` : '-'}</td>
<td>${pred.predicted_winner || '-'}</td>
<td>${pred.confidence ? (pred.confidence * 100).toFixed(0) + '%' : '-'}</td>
<td>${pred.match.home_score !== null ? `${pred.match.home_score} : ${pred.match.away_score}` : '-'}</td>
<td>
${pred.is_correct !== null ?
`<span class="status-badge ${pred.is_correct ? 'status-active' : 'status-inactive'}">
${pred.is_correct ? '正确' : '错误'}
</span>` :
'<span style="color: #a0a0a0;">待验证</span>'
}
</td>
<td>
<button onclick="addPredictionForMatch(${pred.match.id})" class="btn-secondary btn-small">修改</button>
</td>
</tr>
`).join('');
// 更新预测表单的算法下拉框
const predAlgoSelect = document.getElementById('prediction-algorithm');
predAlgoSelect.innerHTML = '<option value="">-- 选择算法 --</option>' +
algorithms.map(a => `<option value="${a.id}">${a.name}</option>`).join('');
} catch (error) {
console.error('加载预测失败:', error);
}
}
// 显示添加算法弹窗
function showAddAlgorithmModal() {
document.getElementById('algorithm-modal-title').textContent = '添加算法';
document.getElementById('algorithm-form').reset();
document.getElementById('algorithm-id').value = '';
document.getElementById('algorithm-modal').style.display = 'block';
}
// 编辑算法
async function editAlgorithm(id) {
try {
const response = await fetch(`${API_BASE}/api/algorithms/${id}`);
const result = await response.json();
if (result.success) {
const algo = result.data.algorithm;
document.getElementById('algorithm-modal-title').textContent = '编辑算法';
document.getElementById('algorithm-id').value = algo.id;
document.getElementById('algorithm-name').value = algo.name;
document.getElementById('algorithm-desc').value = algo.description || '';
document.getElementById('algorithm-active').value = algo.is_active ? '1' : '0';
document.getElementById('algorithm-modal').style.display = 'block';
}
} catch (error) {
console.error('加载算法失败:', error);
}
}
// 删除算法
async function deleteAlgorithm(id) {
if (!confirm('确定要删除这个算法吗?相关的预测数据也会被删除。')) {
return;
}
try {
const response = await fetch(`${API_BASE}/api/algorithms/${id}`, {
method: 'DELETE'
});
const result = await response.json();
if (result.success) {
loadAlgorithms();
loadPredictions();
alert('删除成功');
} else {
alert('删除失败');
}
} catch (error) {
console.error('删除算法失败:', error);
alert('删除失败,请重试');
}
}
// 显示添加比赛弹窗
function showAddMatchModal() {
document.getElementById('match-modal-title').textContent = '添加比赛';
document.getElementById('match-form').reset();
document.getElementById('match-id').value = '';
document.getElementById('match-modal').style.display = 'block';
}
// 编辑比赛
async function editMatch(id) {
try {
const response = await fetch(`${API_BASE}/api/matches/${id}`);
const result = await response.json();
if (result.success) {
const match = result.data.match;
document.getElementById('match-modal-title').textContent = '编辑比赛';
document.getElementById('match-id').value = match.id;
document.getElementById('match-code').value = match.match_code;
document.getElementById('home-team').value = match.home_team;
document.getElementById('away-team').value = match.away_team;
document.getElementById('match-time').value = match.match_time || '';
document.getElementById('match-round').value = match.round_name || '小组赛';
document.getElementById('match-status').value = match.status || 'upcoming';
document.getElementById('home-score').value = match.home_score !== null ? match.home_score : '';
document.getElementById('away-score').value = match.away_score !== null ? match.away_score : '';
document.getElementById('match-modal').style.display = 'block';
}
} catch (error) {
console.error('加载比赛失败:', error);
}
}
// 删除比赛
async function deleteMatch(id) {
if (!confirm('确定要删除这场比赛吗?相关的预测数据也会被删除。')) {
return;
}
try {
const response = await fetch(`${API_BASE}/api/matches/${id}`, {
method: 'DELETE'
});
const result = await response.json();
if (result.success) {
loadMatches();
loadPredictions();
alert('删除成功');
} else {
alert('删除失败');
}
} catch (error) {
console.error('删除比赛失败:', error);
alert('删除失败,请重试');
}
}
// 为比赛添加预测
function addPredictionForMatch(matchId) {
document.getElementById('prediction-match').value = matchId;
document.getElementById('prediction-modal').style.display = 'block';
}
// 评估预测结果
async function evaluatePredictions() {
if (!confirm('确定要评估所有已完成比赛的预测结果吗?')) {
return;
}
try {
const response = await fetch(`${API_BASE}/api/predictions/evaluate`, {
method: 'POST'
});
const result = await response.json();
if (result.success) {
alert(`评估完成,共评估了 ${result.evaluated} 条预测`);
loadPredictions();
loadAlgorithms();
} else {
alert('评估失败');
}
} catch (error) {
console.error('评估预测失败:', error);
alert('评估失败,请重试');
}
}
// 关闭弹窗
function closeModal(modalId) {
document.getElementById(modalId).style.display = 'none';
}
// 点击弹窗外部关闭
window.addEventListener('click', (e) => {
if (e.target.classList.contains('modal')) {
e.target.style.display = 'none';
}
});