世界杯预测系统 v1.0.0 - 初始版本
This commit is contained in:
513
frontend/js/admin.js
Normal file
513
frontend/js/admin.js
Normal 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';
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user