751 lines
26 KiB
JavaScript
751 lines
26 KiB
JavaScript
// 执行历史页面JavaScript
|
||
|
||
class HistoryManager {
|
||
constructor() {
|
||
this.currentTaskPage = 1;
|
||
this.currentLogsPage = 1;
|
||
this.pageSize = 20;
|
||
this.charts = {};
|
||
this.searchParams = {
|
||
taskType: '',
|
||
taskStatus: '',
|
||
startDate: '',
|
||
endDate: ''
|
||
};
|
||
this.logSearchParams = {
|
||
level: '',
|
||
category: '',
|
||
keyword: ''
|
||
};
|
||
|
||
this.init();
|
||
}
|
||
|
||
async init() {
|
||
console.log('初始化执行历史页面...');
|
||
|
||
// 绑定事件
|
||
this.bindEvents();
|
||
|
||
// 初始化图表
|
||
this.initCharts();
|
||
|
||
// 加载初始数据
|
||
await this.loadTaskHistory();
|
||
await this.loadSystemLogs();
|
||
await this.loadStatistics();
|
||
|
||
console.log('执行历史页面初始化完成');
|
||
}
|
||
|
||
bindEvents() {
|
||
// 任务历史筛选
|
||
document.getElementById('taskFilterForm').addEventListener('submit', (e) => {
|
||
e.preventDefault();
|
||
this.searchParams.taskType = document.getElementById('taskTypeFilter').value;
|
||
this.searchParams.taskStatus = document.getElementById('taskStatusFilter').value;
|
||
this.searchParams.startDate = document.getElementById('startDate').value;
|
||
this.searchParams.endDate = document.getElementById('endDate').value;
|
||
this.currentTaskPage = 1;
|
||
this.loadTaskHistory();
|
||
});
|
||
|
||
// 系统日志筛选
|
||
document.getElementById('logFilterForm').addEventListener('submit', (e) => {
|
||
e.preventDefault();
|
||
this.logSearchParams.level = document.getElementById('logLevelFilter').value;
|
||
this.logSearchParams.category = document.getElementById('logCategoryFilter').value;
|
||
this.logSearchParams.keyword = document.getElementById('logSearchKeyword').value;
|
||
this.currentLogsPage = 1;
|
||
this.loadSystemLogs();
|
||
});
|
||
|
||
// 选项卡切换事件
|
||
document.getElementById('stats-tab').addEventListener('shown.bs.tab', () => {
|
||
this.loadStatistics();
|
||
this.updateCharts();
|
||
});
|
||
}
|
||
|
||
initCharts() {
|
||
// 任务趋势图
|
||
const taskTrendCtx = document.getElementById('taskTrendChart');
|
||
if (taskTrendCtx) {
|
||
this.charts.taskTrend = new Chart(taskTrendCtx, {
|
||
type: 'line',
|
||
data: {
|
||
labels: [],
|
||
datasets: [{
|
||
label: '成功任务',
|
||
data: [],
|
||
borderColor: '#28a745',
|
||
backgroundColor: 'rgba(40, 167, 69, 0.1)',
|
||
tension: 0.4
|
||
}, {
|
||
label: '失败任务',
|
||
data: [],
|
||
borderColor: '#dc3545',
|
||
backgroundColor: 'rgba(220, 53, 69, 0.1)',
|
||
tension: 0.4
|
||
}]
|
||
},
|
||
options: {
|
||
responsive: true,
|
||
maintainAspectRatio: false,
|
||
plugins: {
|
||
legend: {
|
||
position: 'top',
|
||
}
|
||
},
|
||
scales: {
|
||
y: {
|
||
beginAtZero: true,
|
||
ticks: {
|
||
precision: 0
|
||
}
|
||
}
|
||
}
|
||
}
|
||
});
|
||
}
|
||
|
||
// 日志级别分布图
|
||
const logLevelCtx = document.getElementById('logLevelChart');
|
||
if (logLevelCtx) {
|
||
this.charts.logLevel = new Chart(logLevelCtx, {
|
||
type: 'doughnut',
|
||
data: {
|
||
labels: ['错误', '警告', '信息', '调试'],
|
||
datasets: [{
|
||
data: [0, 0, 0, 0],
|
||
backgroundColor: ['#dc3545', '#ffc107', '#17a2b8', '#6c757d']
|
||
}]
|
||
},
|
||
options: {
|
||
responsive: true,
|
||
maintainAspectRatio: false,
|
||
plugins: {
|
||
legend: {
|
||
position: 'bottom',
|
||
}
|
||
}
|
||
}
|
||
});
|
||
}
|
||
}
|
||
|
||
async loadTaskHistory() {
|
||
try {
|
||
this.showTaskLoading(true);
|
||
|
||
// 构建查询参数
|
||
const params = new URLSearchParams({
|
||
limit: this.pageSize,
|
||
offset: (this.currentTaskPage - 1) * this.pageSize
|
||
});
|
||
|
||
if (this.searchParams.taskType) params.append('taskType', this.searchParams.taskType);
|
||
if (this.searchParams.taskStatus) params.append('status', this.searchParams.taskStatus);
|
||
|
||
const response = await fetch(`/api/history?${params}`);
|
||
const result = await response.json();
|
||
|
||
if (result.success) {
|
||
this.renderTaskHistoryTable(result.data);
|
||
this.renderTaskPagination(result.pagination);
|
||
} else {
|
||
this.showAlert('加载任务历史失败: ' + result.error, 'danger');
|
||
}
|
||
} catch (error) {
|
||
console.error('加载任务历史失败:', error);
|
||
this.showAlert('加载任务历史失败: ' + error.message, 'danger');
|
||
} finally {
|
||
this.showTaskLoading(false);
|
||
}
|
||
}
|
||
|
||
async loadSystemLogs() {
|
||
try {
|
||
this.showLogsLoading(true);
|
||
|
||
// 构建查询参数
|
||
const params = new URLSearchParams({
|
||
limit: this.pageSize,
|
||
offset: (this.currentLogsPage - 1) * this.pageSize
|
||
});
|
||
|
||
if (this.logSearchParams.level) params.append('level', this.logSearchParams.level);
|
||
if (this.logSearchParams.category) params.append('category', this.logSearchParams.category);
|
||
|
||
let response;
|
||
if (this.logSearchParams.keyword) {
|
||
response = await fetch(`/api/history/logs/search?keyword=${encodeURIComponent(this.logSearchParams.keyword)}&limit=${this.pageSize}`);
|
||
} else {
|
||
response = await fetch(`/api/history/logs/system?${params}`);
|
||
}
|
||
|
||
const result = await response.json();
|
||
|
||
if (result.success) {
|
||
this.renderSystemLogsTable(result.data);
|
||
this.renderLogsPagination(result.pagination);
|
||
} else {
|
||
this.showAlert('加载系统日志失败: ' + result.error, 'danger');
|
||
}
|
||
} catch (error) {
|
||
console.error('加载系统日志失败:', error);
|
||
this.showAlert('加载系统日志失败: ' + error.message, 'danger');
|
||
} finally {
|
||
this.showLogsLoading(false);
|
||
}
|
||
}
|
||
|
||
async loadStatistics() {
|
||
try {
|
||
// 加载任务统计
|
||
const taskStatsResponse = await fetch('/api/history/stats/summary');
|
||
const taskStatsResult = await taskStatsResponse.json();
|
||
|
||
if (taskStatsResult.success) {
|
||
this.updateTaskStatistics(taskStatsResult.data.summary);
|
||
this.renderTaskStats(taskStatsResult.data);
|
||
}
|
||
|
||
// 加载日志统计
|
||
const logStatsResponse = await fetch('/api/history/logs/stats');
|
||
const logStatsResult = await logStatsResponse.json();
|
||
|
||
if (logStatsResult.success) {
|
||
this.updateLogStatistics(logStatsResult.data.summary);
|
||
this.renderLogStats(logStatsResult.data);
|
||
}
|
||
|
||
} catch (error) {
|
||
console.error('加载统计数据失败:', error);
|
||
this.showAlert('加载统计数据失败: ' + error.message, 'danger');
|
||
}
|
||
}
|
||
|
||
renderTaskHistoryTable(tasks) {
|
||
const tbody = document.getElementById('taskHistoryTableBody');
|
||
|
||
if (tasks.length === 0) {
|
||
tbody.innerHTML = `
|
||
<tr>
|
||
<td colspan="8" class="text-center p-4">
|
||
<i class="bi bi-inbox fs-1 text-muted"></i>
|
||
<p class="text-muted mt-2">暂无任务历史</p>
|
||
</td>
|
||
</tr>
|
||
`;
|
||
return;
|
||
}
|
||
|
||
tbody.innerHTML = tasks.map(task => `
|
||
<tr>
|
||
<td>
|
||
<span class="badge bg-secondary">${this.getTaskTypeLabel(task.task_type)}</span>
|
||
</td>
|
||
<td>${task.task_name}</td>
|
||
<td>
|
||
<span class="badge ${this.getStatusClass(task.status)}">
|
||
<i class="bi bi-${this.getStatusIcon(task.status)}"></i>
|
||
${this.getStatusText(task.status)}
|
||
</span>
|
||
</td>
|
||
<td>
|
||
<small class="text-muted">${this.formatDateTime(task.start_time)}</small>
|
||
</td>
|
||
<td>
|
||
<small class="text-muted">${task.end_time ? this.formatDateTime(task.end_time) : '-'}</small>
|
||
</td>
|
||
<td>
|
||
<small class="text-muted">${task.duration ? this.formatDuration(task.duration) : '-'}</small>
|
||
</td>
|
||
<td>
|
||
<small class="text-muted">${task.result_summary || '-'}</small>
|
||
</td>
|
||
<td>
|
||
<button class="btn btn-sm btn-outline-primary" onclick="historyManager.showTaskDetail(${task.id})">
|
||
<i class="bi bi-eye"></i> 详情
|
||
</button>
|
||
</td>
|
||
</tr>
|
||
`).join('');
|
||
}
|
||
|
||
renderSystemLogsTable(logs) {
|
||
const tbody = document.getElementById('systemLogsTableBody');
|
||
|
||
if (logs.length === 0) {
|
||
tbody.innerHTML = `
|
||
<tr>
|
||
<td colspan="5" class="text-center p-4">
|
||
<i class="bi bi-inbox fs-1 text-muted"></i>
|
||
<p class="text-muted mt-2">暂无系统日志</p>
|
||
</td>
|
||
</tr>
|
||
`;
|
||
return;
|
||
}
|
||
|
||
tbody.innerHTML = logs.map(log => `
|
||
<tr>
|
||
<td>
|
||
<small class="text-muted">${this.formatDateTime(log.timestamp)}</small>
|
||
</td>
|
||
<td>
|
||
<span class="badge ${this.getLogLevelClass(log.level)}">${log.level.toUpperCase()}</span>
|
||
</td>
|
||
<td>
|
||
<small>${log.category || '-'}</small>
|
||
</td>
|
||
<td>
|
||
<small>${log.message}</small>
|
||
</td>
|
||
<td>
|
||
<small class="text-muted">${log.source}</small>
|
||
</td>
|
||
</tr>
|
||
`).join('');
|
||
}
|
||
|
||
renderTaskPagination(pagination) {
|
||
const paginationElement = document.getElementById('taskPagination');
|
||
this.renderPagination(paginationElement, pagination, this.currentTaskPage, (page) => {
|
||
this.currentTaskPage = page;
|
||
this.loadTaskHistory();
|
||
});
|
||
}
|
||
|
||
renderLogsPagination(pagination) {
|
||
const paginationElement = document.getElementById('logsPagination');
|
||
this.renderPagination(paginationElement, pagination, this.currentLogsPage, (page) => {
|
||
this.currentLogsPage = page;
|
||
this.loadSystemLogs();
|
||
});
|
||
}
|
||
|
||
renderPagination(container, pagination, currentPage, onPageChange) {
|
||
const totalPages = Math.ceil(pagination.total / pagination.limit);
|
||
|
||
if (totalPages <= 1) {
|
||
container.innerHTML = '';
|
||
return;
|
||
}
|
||
|
||
let paginationHTML = '';
|
||
|
||
// 上一页
|
||
paginationHTML += `
|
||
<li class="page-item ${currentPage === 1 ? 'disabled' : ''}">
|
||
<a class="page-link" href="#" onclick="historyManager.goToTaskPage(${currentPage - 1}); return false;">
|
||
<i class="bi bi-chevron-left"></i>
|
||
</a>
|
||
</li>
|
||
`;
|
||
|
||
// 页码
|
||
const startPage = Math.max(1, currentPage - 2);
|
||
const endPage = Math.min(totalPages, currentPage + 2);
|
||
|
||
for (let i = startPage; i <= endPage; i++) {
|
||
paginationHTML += `
|
||
<li class="page-item ${i === currentPage ? 'active' : ''}">
|
||
<a class="page-link" href="#" onclick="historyManager.goToTaskPage(${i}); return false;">${i}</a>
|
||
</li>
|
||
`;
|
||
}
|
||
|
||
// 下一页
|
||
paginationHTML += `
|
||
<li class="page-item ${currentPage === totalPages ? 'disabled' : ''}">
|
||
<a class="page-link" href="#" onclick="historyManager.goToTaskPage(${currentPage + 1}); return false;">
|
||
<i class="bi bi-chevron-right"></i>
|
||
</a>
|
||
</li>
|
||
`;
|
||
|
||
container.innerHTML = paginationHTML;
|
||
}
|
||
|
||
updateTaskStatistics(stats) {
|
||
document.getElementById('totalTasks').textContent = stats.total;
|
||
document.getElementById('successTasks').textContent = stats.success;
|
||
document.getElementById('failedTasks').textContent = stats.failed;
|
||
document.getElementById('successRate').textContent = stats.success_rate + '%';
|
||
}
|
||
|
||
updateLogStatistics(stats) {
|
||
// 更新日志级别图表
|
||
if (this.charts.logLevel) {
|
||
this.charts.logLevel.data.datasets[0].data = [
|
||
stats.error,
|
||
stats.warning,
|
||
stats.info,
|
||
stats.debug
|
||
];
|
||
this.charts.logLevel.update();
|
||
}
|
||
}
|
||
|
||
renderTaskStats(data) {
|
||
const content = document.getElementById('taskStatsContent');
|
||
|
||
if (!data.daily || data.daily.length === 0) {
|
||
content.innerHTML = '<p class="text-muted">暂无数据</p>';
|
||
return;
|
||
}
|
||
|
||
let html = '<table class="table table-sm">';
|
||
html += '<thead><tr><th>日期</th><th>总任务</th><th>成功</th><th>失败</th><th>成功率</th></tr></thead><tbody>';
|
||
|
||
data.daily.slice(0, 7).forEach(day => {
|
||
const successRate = day.total > 0 ? ((day.success / day.total) * 100).toFixed(1) : '0';
|
||
html += `
|
||
<tr>
|
||
<td>${this.formatDate(day.date)}</td>
|
||
<td>${day.total}</td>
|
||
<td class="text-success">${day.success}</td>
|
||
<td class="text-danger">${day.failed}</td>
|
||
<td>${successRate}%</td>
|
||
</tr>
|
||
`;
|
||
});
|
||
|
||
html += '</tbody></table>';
|
||
content.innerHTML = html;
|
||
}
|
||
|
||
renderLogStats(data) {
|
||
const content = document.getElementById('logStatsContent');
|
||
|
||
const summary = data.summary;
|
||
let html = '<div class="row">';
|
||
html += '<div class="col-6"><small class="text-muted">总日志数:</small><div><strong>' + summary.total + '</strong></div></div>';
|
||
html += '<div class="col-6"><small class="text-muted">错误:</small><div><strong class="text-danger">' + summary.error + '</strong></div></div>';
|
||
html += '<div class="col-6"><small class="text-muted">警告:</small><div><strong class="text-warning">' + summary.warning + '</strong></div></div>';
|
||
html += '<div class="col-6"><small class="text-muted">信息:</small><div><strong class="text-info">' + summary.info + '</strong></div></div>';
|
||
html += '</div>';
|
||
|
||
content.innerHTML = html;
|
||
}
|
||
|
||
async updateCharts() {
|
||
try {
|
||
// 加载每日任务统计
|
||
const response = await fetch('/api/dashboard/charts/tasks');
|
||
const result = await response.json();
|
||
|
||
if (result.success && this.charts.taskTrend) {
|
||
const labels = result.data.map(item => this.formatDate(item.date));
|
||
const successData = result.data.map(item => item.scrape_success + item.validation_success);
|
||
const failedData = result.data.map(item => item.scrape_failed + item.validation_failed);
|
||
|
||
this.charts.taskTrend.data.labels = labels;
|
||
this.charts.taskTrend.data.datasets[0].data = successData;
|
||
this.charts.taskTrend.data.datasets[1].data = failedData;
|
||
this.charts.taskTrend.update();
|
||
}
|
||
} catch (error) {
|
||
console.error('更新图表失败:', error);
|
||
}
|
||
}
|
||
|
||
async showTaskDetail(taskId) {
|
||
try {
|
||
const response = await fetch(`/api/history/${taskId}`);
|
||
const result = await response.json();
|
||
|
||
if (result.success) {
|
||
const task = result.data;
|
||
const content = document.getElementById('taskDetailContent');
|
||
|
||
content.innerHTML = `
|
||
<div class="row">
|
||
<div class="col-md-6">
|
||
<strong>任务类型:</strong><br>
|
||
<span class="badge bg-secondary">${this.getTaskTypeLabel(task.task_type)}</span>
|
||
</div>
|
||
<div class="col-md-6">
|
||
<strong>任务名称:</strong><br>
|
||
${task.task_name}
|
||
</div>
|
||
</div>
|
||
<hr>
|
||
<div class="row">
|
||
<div class="col-md-6">
|
||
<strong>状态:</strong><br>
|
||
<span class="badge ${this.getStatusClass(task.status)}">
|
||
${this.getStatusText(task.status)}
|
||
</span>
|
||
</div>
|
||
<div class="col-md-6">
|
||
<strong>执行时长:</strong><br>
|
||
${task.duration ? this.formatDuration(task.duration) : '-'}
|
||
</div>
|
||
</div>
|
||
<hr>
|
||
<div class="row">
|
||
<div class="col-md-6">
|
||
<strong>开始时间:</strong><br>
|
||
${this.formatDateTime(task.start_time)}
|
||
</div>
|
||
<div class="col-md-6">
|
||
<strong>结束时间:</strong><br>
|
||
${task.end_time ? this.formatDateTime(task.end_time) : '-'}
|
||
</div>
|
||
</div>
|
||
<hr>
|
||
<div class="row">
|
||
<div class="col-12">
|
||
<strong>结果摘要:</strong><br>
|
||
<p>${task.result_summary || '暂无摘要'}</p>
|
||
</div>
|
||
</div>
|
||
${task.error_message ? `
|
||
<div class="row">
|
||
<div class="col-12">
|
||
<strong>错误信息:</strong><br>
|
||
<div class="alert alert-danger">${task.error_message}</div>
|
||
</div>
|
||
</div>
|
||
` : ''}
|
||
${task.details ? `
|
||
<div class="row">
|
||
<div class="col-12">
|
||
<strong>详细信息:</strong><br>
|
||
<pre class="bg-light p-2 rounded"><code>${JSON.stringify(task.details, null, 2)}</code></pre>
|
||
</div>
|
||
</div>
|
||
` : ''}
|
||
`;
|
||
|
||
// 显示模态框
|
||
const modal = new bootstrap.Modal(document.getElementById('taskDetailModal'));
|
||
modal.show();
|
||
} else {
|
||
this.showAlert('获取任务详情失败: ' + result.error, 'danger');
|
||
}
|
||
} catch (error) {
|
||
console.error('获取任务详情失败:', error);
|
||
this.showAlert('获取任务详情失败: ' + error.message, 'danger');
|
||
}
|
||
}
|
||
|
||
async exportTaskHistory() {
|
||
try {
|
||
const params = new URLSearchParams();
|
||
if (this.searchParams.taskType) params.append('taskType', this.searchParams.taskType);
|
||
if (this.searchParams.taskStatus) params.append('status', this.searchParams.taskStatus);
|
||
|
||
const response = await fetch(`/api/history/export?${params}&format=csv`);
|
||
|
||
if (response.ok) {
|
||
const blob = await response.blob();
|
||
const url = window.URL.createObjectURL(blob);
|
||
const a = document.createElement('a');
|
||
a.href = url;
|
||
a.download = `task_history_${new Date().toISOString().slice(0, 10)}.csv`;
|
||
document.body.appendChild(a);
|
||
a.click();
|
||
window.URL.revokeObjectURL(url);
|
||
document.body.removeChild(a);
|
||
|
||
this.showAlert('任务历史导出成功', 'success');
|
||
} else {
|
||
this.showAlert('导出失败', 'danger');
|
||
}
|
||
} catch (error) {
|
||
console.error('导出任务历史失败:', error);
|
||
this.showAlert('导出任务历史失败: ' + error.message, 'danger');
|
||
}
|
||
}
|
||
|
||
async cleanupLogs() {
|
||
if (!confirm('确定要清理30天前的历史记录吗?此操作不可恢复。')) {
|
||
return;
|
||
}
|
||
|
||
try {
|
||
const response = await fetch('/api/history/cleanup', {
|
||
method: 'DELETE',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
},
|
||
body: JSON.stringify({ days: 30, type: 'all' })
|
||
});
|
||
|
||
const result = await response.json();
|
||
|
||
if (result.success) {
|
||
this.showAlert(result.message, 'success');
|
||
await this.loadSystemLogs();
|
||
await this.loadStatistics();
|
||
} else {
|
||
this.showAlert('清理失败: ' + result.error, 'danger');
|
||
}
|
||
} catch (error) {
|
||
console.error('清理历史记录失败:', error);
|
||
this.showAlert('清理历史记录失败: ' + error.message, 'danger');
|
||
}
|
||
}
|
||
|
||
// 工具函数
|
||
getTaskTypeLabel(type) {
|
||
const labels = {
|
||
'scrape': '抓取任务',
|
||
'validation': '验证任务',
|
||
'health_check': '健康检查'
|
||
};
|
||
return labels[type] || type;
|
||
}
|
||
|
||
getStatusClass(status) {
|
||
const classes = {
|
||
'success': 'bg-success',
|
||
'failed': 'bg-danger',
|
||
'running': 'bg-warning',
|
||
'pending': 'bg-secondary'
|
||
};
|
||
return classes[status] || 'bg-secondary';
|
||
}
|
||
|
||
getStatusIcon(status) {
|
||
const icons = {
|
||
'success': 'check-circle',
|
||
'failed': 'x-circle',
|
||
'running': 'clock',
|
||
'pending': 'pause-circle'
|
||
};
|
||
return icons[status] || 'question-circle';
|
||
}
|
||
|
||
getStatusText(status) {
|
||
const texts = {
|
||
'success': '成功',
|
||
'failed': '失败',
|
||
'running': '运行中',
|
||
'pending': '等待中'
|
||
};
|
||
return texts[status] || status;
|
||
}
|
||
|
||
getLogLevelClass(level) {
|
||
const classes = {
|
||
'error': 'bg-danger',
|
||
'warning': 'bg-warning',
|
||
'info': 'bg-info',
|
||
'debug': 'bg-secondary'
|
||
};
|
||
return classes[level] || 'bg-secondary';
|
||
}
|
||
|
||
formatDateTime(dateString) {
|
||
const date = new Date(dateString);
|
||
return date.toLocaleString('zh-CN');
|
||
}
|
||
|
||
formatDate(dateString) {
|
||
const date = new Date(dateString);
|
||
return date.toLocaleDateString('zh-CN', { month: 'short', day: 'numeric' });
|
||
}
|
||
|
||
formatDuration(ms) {
|
||
if (!ms) return '-';
|
||
|
||
if (ms < 1000) {
|
||
return ms + 'ms';
|
||
} else if (ms < 60000) {
|
||
return (ms / 1000).toFixed(1) + 's';
|
||
} else {
|
||
return (ms / 60000).toFixed(1) + 'min';
|
||
}
|
||
}
|
||
|
||
showTaskLoading(show) {
|
||
const tbody = document.getElementById('taskHistoryTableBody');
|
||
const spinner = tbody.querySelector('.spinner-border');
|
||
if (spinner) {
|
||
spinner.parentElement.style.display = show ? 'block' : 'none';
|
||
}
|
||
}
|
||
|
||
showLogsLoading(show) {
|
||
const tbody = document.getElementById('systemLogsTableBody');
|
||
const spinner = tbody.querySelector('.spinner-border');
|
||
if (spinner) {
|
||
spinner.parentElement.style.display = show ? 'block' : 'none';
|
||
}
|
||
}
|
||
|
||
showAlert(message, type = 'info') {
|
||
const alertContainer = document.getElementById('alertContainer');
|
||
if (!alertContainer) return;
|
||
|
||
const alertId = 'alert-' + Date.now();
|
||
const alertHtml = `
|
||
<div class="alert alert-${type} alert-dismissible fade show" role="alert" id="${alertId}">
|
||
<i class="bi bi-${this.getAlertIcon(type)}"></i>
|
||
${message}
|
||
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
||
</div>
|
||
`;
|
||
|
||
alertContainer.insertAdjacentHTML('beforeend', alertHtml);
|
||
|
||
// 自动移除提示
|
||
setTimeout(() => {
|
||
const alertElement = document.getElementById(alertId);
|
||
if (alertElement) {
|
||
alertElement.remove();
|
||
}
|
||
}, 5000);
|
||
}
|
||
|
||
getAlertIcon(type) {
|
||
const icons = {
|
||
'success': 'check-circle-fill',
|
||
'danger': 'exclamation-triangle-fill',
|
||
'warning': 'exclamation-triangle-fill',
|
||
'info': 'info-circle-fill'
|
||
};
|
||
return icons[type] || 'info-circle-fill';
|
||
}
|
||
|
||
goToTaskPage(page) {
|
||
this.currentTaskPage = page;
|
||
this.loadTaskHistory();
|
||
}
|
||
}
|
||
|
||
// 全局函数(供HTML调用)
|
||
let historyManager;
|
||
|
||
async function refreshHistory() {
|
||
if (historyManager) {
|
||
await Promise.all([
|
||
historyManager.loadTaskHistory(),
|
||
historyManager.loadSystemLogs(),
|
||
historyManager.loadStatistics()
|
||
]);
|
||
historyManager.showAlert('历史数据已刷新', 'info');
|
||
}
|
||
}
|
||
|
||
async function exportTaskHistory() {
|
||
if (historyManager) {
|
||
await historyManager.exportTaskHistory();
|
||
}
|
||
}
|
||
|
||
async function cleanupLogs() {
|
||
if (historyManager) {
|
||
await historyManager.cleanupLogs();
|
||
}
|
||
}
|
||
|
||
// 页面加载完成后初始化
|
||
document.addEventListener('DOMContentLoaded', () => {
|
||
historyManager = new HistoryManager();
|
||
}); |