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

589 lines
22 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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 showBatchAddMatchModal() {
document.getElementById('batch-match-data').value = '';
document.getElementById('batch-match-round').value = '';
document.getElementById('batch-match-modal').style.display = 'block';
}
// 批量添加比赛
async function batchAddMatches() {
const dataText = document.getElementById('batch-match-data').value.trim();
const defaultRound = document.getElementById('batch-match-round').value;
if (!dataText) {
alert('请输入比赛数据');
return;
}
const lines = dataText.split('\n').filter(line => line.trim());
let successCount = 0;
let failCount = 0;
let errors = [];
for (const line of lines) {
const parts = line.split(',').map(p => p.trim());
if (parts.length < 3) {
failCount++;
errors.push(`格式错误: ${line}`);
continue;
}
const matchData = {
match_code: parts[0],
home_team: parts[1],
away_team: parts[2],
match_time: parts[3] || null,
round_name: parts[4] || defaultRound || '小组赛',
status: 'upcoming'
};
try {
const response = await fetch(`${API_BASE}/api/matches`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(matchData)
});
const result = await response.json();
if (result.success) {
successCount++;
} else {
failCount++;
errors.push(`${matchData.match_code}: ${result.message}`);
}
} catch (error) {
failCount++;
errors.push(`${matchData.match_code}: 网络错误`);
}
}
let message = `批量添加完成\n成功: ${successCount}\n失败: ${failCount}`;
if (errors.length > 0 && errors.length <= 5) {
message += '\n\n失败详情:\n' + errors.join('\n');
} else if (errors.length > 5) {
message += '\n\n失败详情前5条:\n' + errors.slice(0, 5).join('\n') + '\n...';
}
alert(message);
if (successCount > 0) {
closeModal('batch-match-modal');
loadMatches();
loadPredictions();
}
}
// 显示添加比赛弹窗
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';
}
});