399 lines
14 KiB
JavaScript
399 lines
14 KiB
JavaScript
// 加载生成配置列表
|
|
async function loadGenerateConfigs() {
|
|
try {
|
|
const response = await fetch('/api/config');
|
|
const result = await response.json();
|
|
|
|
if (result.success) {
|
|
const container = document.getElementById('generate-config-list');
|
|
container.innerHTML = result.data.map(c => `
|
|
<label style="display: flex; align-items: center; gap: 8px; padding: 6px 0; cursor: pointer;">
|
|
<input type="checkbox" class="config-checkbox" value="${c.id}" data-name="${c.config_name}">
|
|
<span>${c.config_name}</span>
|
|
</label>
|
|
`).join('');
|
|
|
|
initGenerateState();
|
|
}
|
|
} catch (error) {
|
|
console.error('加载配置失败:', error);
|
|
}
|
|
}
|
|
// 全选/取消全选配置
|
|
function toggleSelectAllConfigs() {
|
|
const checkboxes = document.querySelectorAll('.config-checkbox');
|
|
if (checkboxes.length === 0) return;
|
|
|
|
// 检查是否所有复选框都被选中
|
|
const allChecked = Array.from(checkboxes).every(cb => cb.checked);
|
|
|
|
// 切换所有复选框的状态
|
|
checkboxes.forEach(cb => {
|
|
cb.checked = !allChecked;
|
|
});
|
|
}
|
|
// 设置预设时间
|
|
function setPresetTime(preset) {
|
|
const now = new Date();
|
|
let start, end;
|
|
|
|
if (preset === 'yesterday') {
|
|
end = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 8, 0, 0);
|
|
start = new Date(end.getTime() - 24 * 60 * 60 * 1000);
|
|
} else if (preset === 'last3days') {
|
|
end = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 8, 0, 0);
|
|
start = new Date(end.getTime() - 3 * 24 * 60 * 60 * 1000);
|
|
} else if (preset === 'last7days') {
|
|
end = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 8, 0, 0);
|
|
start = new Date(end.getTime() - 7 * 24 * 60 * 60 * 1000);
|
|
}
|
|
|
|
document.getElementById('start-time').value = formatDateTimeLocal(start);
|
|
document.getElementById('end-time').value = formatDateTimeLocal(end);
|
|
}
|
|
|
|
// 格式化日期时间(用于输入框)
|
|
function formatDateTimeLocal(date) {
|
|
const year = date.getFullYear();
|
|
const month = String(date.getMonth() + 1).padStart(2, '0');
|
|
const day = String(date.getDate()).padStart(2, '0');
|
|
const hours = String(date.getHours()).padStart(2, '0');
|
|
const minutes = String(date.getMinutes()).padStart(2, '0');
|
|
return `${year}-${month}-${day}T${hours}:${minutes}`;
|
|
}
|
|
|
|
// 格式化日期时间为数字格式(用于显示)
|
|
function formatDateTimeDisplay(dateTimeStr) {
|
|
if (!dateTimeStr) return '-';
|
|
// 如果是字符串,直接返回(已经是数字格式)
|
|
if (typeof dateTimeStr === 'string') {
|
|
// 如果是 ISO 格式或其他格式,转换为 YYYY-MM-DD HH:MM:SS
|
|
const date = new Date(dateTimeStr);
|
|
if (!isNaN(date.getTime())) {
|
|
const year = date.getFullYear();
|
|
const month = String(date.getMonth() + 1).padStart(2, '0');
|
|
const day = String(date.getDate()).padStart(2, '0');
|
|
const hours = String(date.getHours()).padStart(2, '0');
|
|
const minutes = String(date.getMinutes()).padStart(2, '0');
|
|
const seconds = String(date.getSeconds()).padStart(2, '0');
|
|
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
|
|
}
|
|
return dateTimeStr;
|
|
}
|
|
return String(dateTimeStr);
|
|
}
|
|
|
|
// 格式化日期为数字格式(只显示日期,用于显示)
|
|
function formatDateDisplay(dateStr) {
|
|
if (!dateStr) return '-';
|
|
const date = new Date(dateStr);
|
|
if (!isNaN(date.getTime())) {
|
|
const year = date.getFullYear();
|
|
const month = String(date.getMonth() + 1).padStart(2, '0');
|
|
const day = String(date.getDate()).padStart(2, '0');
|
|
return `${year}-${month}-${day}`;
|
|
}
|
|
return String(dateStr);
|
|
}
|
|
|
|
let isGenerating = false;
|
|
let generatePollTimer = null;
|
|
|
|
// 保存生成状态到 localStorage
|
|
function saveGenerateState(configIds, startTime) {
|
|
const state = {
|
|
configIds: configIds,
|
|
startTime: startTime
|
|
};
|
|
localStorage.setItem('report_generate_state', JSON.stringify(state));
|
|
}
|
|
|
|
// 从 localStorage 读取生成状态
|
|
function getGenerateState() {
|
|
const stateStr = localStorage.getItem('report_generate_state');
|
|
if (!stateStr) return null;
|
|
try {
|
|
return JSON.parse(stateStr);
|
|
} catch (e) {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
// 清除生成状态
|
|
function clearGenerateState() {
|
|
localStorage.removeItem('report_generate_state');
|
|
}
|
|
|
|
// 检查生成是否完成
|
|
async function checkGenerateComplete() {
|
|
const state = getGenerateState();
|
|
if (!state) {
|
|
stopGeneratePolling();
|
|
setGenerateControlsDisabled(false);
|
|
isGenerating = false;
|
|
return;
|
|
}
|
|
|
|
const { configIds, startTime } = state;
|
|
|
|
try {
|
|
const response = await fetch('/api/report/history?page=1&page_size=50');
|
|
const result = await response.json();
|
|
|
|
if (result.success && result.data && result.data.list) {
|
|
const historyList = result.data.list;
|
|
let allDone = true;
|
|
|
|
for (const configId of configIds) {
|
|
const configHistory = historyList.filter(h => h.config_id === configId);
|
|
if (configHistory.length === 0) {
|
|
allDone = false;
|
|
break;
|
|
}
|
|
const latest = configHistory[0];
|
|
const latestTime = new Date(latest.start_time).getTime();
|
|
if (latestTime < startTime) {
|
|
allDone = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (allDone) {
|
|
stopGeneratePolling();
|
|
clearGenerateState();
|
|
setGenerateControlsDisabled(false);
|
|
isGenerating = false;
|
|
|
|
const resultDiv = document.getElementById('generate-result');
|
|
if (resultDiv && !resultDiv.innerHTML.includes('生成完成')) {
|
|
resultDiv.innerHTML = '<div class="alert alert-info">检测到报表已生成完成,请在历史记录中查看。</div>';
|
|
}
|
|
}
|
|
}
|
|
} catch (e) {
|
|
console.error('检查生成状态失败:', e);
|
|
}
|
|
}
|
|
|
|
// 开始轮询
|
|
function startGeneratePolling() {
|
|
stopGeneratePolling();
|
|
generatePollTimer = setInterval(checkGenerateComplete, 3000);
|
|
}
|
|
|
|
// 停止轮询
|
|
function stopGeneratePolling() {
|
|
if (generatePollTimer) {
|
|
clearInterval(generatePollTimer);
|
|
generatePollTimer = null;
|
|
}
|
|
}
|
|
|
|
// 页面加载时检查生成状态
|
|
function initGenerateState() {
|
|
const state = getGenerateState();
|
|
if (state) {
|
|
const { startTime, configIds } = state;
|
|
const now = Date.now();
|
|
const elapsed = now - startTime;
|
|
|
|
if (elapsed > 30 * 60 * 1000) {
|
|
clearGenerateState();
|
|
return;
|
|
}
|
|
|
|
isGenerating = true;
|
|
setGenerateControlsDisabled(true);
|
|
startGeneratePolling();
|
|
|
|
const resultDiv = document.getElementById('generate-result');
|
|
if (resultDiv) {
|
|
let html = '<div class="generate-loading">';
|
|
html += '<div class="generate-loading-header">';
|
|
html += '<div class="spinner"></div>';
|
|
html += '<span class="generate-loading-title">检测到有正在生成的报表</span>';
|
|
html += `<span class="generate-loading-progress">${configIds.length}个配置</span>`;
|
|
html += '</div>';
|
|
html += '<div class="generate-progress-bar">';
|
|
html += '<div class="generate-progress-fill" style="width: 100%; animation: pulse 1.5s ease-in-out infinite;"></div>';
|
|
html += '</div>';
|
|
html += '<div style="font-size: 13px; color: #31708f; margin-top: 4px;">';
|
|
html += '正在后台生成中,完成后将自动恢复...';
|
|
html += '</div>';
|
|
html += '</div>';
|
|
resultDiv.innerHTML = html;
|
|
}
|
|
}
|
|
}
|
|
|
|
// 渲染生成中动态效果
|
|
function renderGenerateLoading(configNames, currentIndex) {
|
|
const total = configNames.length;
|
|
const progress = Math.round((currentIndex / total) * 100);
|
|
|
|
let html = '<div class="generate-loading">';
|
|
|
|
html += '<div class="generate-loading-header">';
|
|
html += '<div class="spinner"></div>';
|
|
html += '<span class="generate-loading-title">正在生成日报</span>';
|
|
html += `<span class="generate-loading-progress">${currentIndex}/${total}</span>`;
|
|
html += '</div>';
|
|
|
|
html += '<div class="generate-progress-bar">';
|
|
html += `<div class="generate-progress-fill" style="width: ${progress}%"></div>`;
|
|
html += '</div>';
|
|
|
|
html += '<div class="generate-item-list">';
|
|
configNames.forEach((name, index) => {
|
|
let status = 'pending';
|
|
let icon = '○';
|
|
let statusText = '等待中';
|
|
|
|
if (index < currentIndex) {
|
|
status = 'success';
|
|
icon = '✓';
|
|
statusText = '已完成';
|
|
} else if (index === currentIndex) {
|
|
status = 'running';
|
|
icon = '●';
|
|
statusText = '生成中...';
|
|
}
|
|
|
|
html += `<div class="generate-item ${status}">`;
|
|
html += `<span class="generate-item-icon ${status}">${icon}</span>`;
|
|
html += `<span class="generate-item-name">${name}</span>`;
|
|
html += `<span class="generate-item-status">${statusText}</span>`;
|
|
html += '</div>';
|
|
});
|
|
html += '</div>';
|
|
|
|
html += '</div>';
|
|
|
|
const resultDiv = document.getElementById('generate-result');
|
|
if (resultDiv) {
|
|
resultDiv.innerHTML = html;
|
|
}
|
|
}
|
|
|
|
// 生成日报(支持多选)
|
|
async function generateReport() {
|
|
if (isGenerating) {
|
|
return;
|
|
}
|
|
|
|
const checkboxes = document.querySelectorAll('.config-checkbox:checked');
|
|
const startTime = document.getElementById('start-time').value;
|
|
const endTime = document.getElementById('end-time').value;
|
|
|
|
if (checkboxes.length === 0) {
|
|
alert('请至少选择一个配置');
|
|
return;
|
|
}
|
|
|
|
if (!startTime || !endTime) {
|
|
alert('请选择时间范围');
|
|
return;
|
|
}
|
|
|
|
isGenerating = true;
|
|
setGenerateControlsDisabled(true);
|
|
|
|
const configIds = Array.from(checkboxes).map(cb => parseInt(cb.value));
|
|
const configNames = Array.from(checkboxes).map(cb => cb.dataset.name);
|
|
const generateStartTime = Date.now();
|
|
saveGenerateState(configIds, generateStartTime);
|
|
startGeneratePolling();
|
|
|
|
renderGenerateLoading(configNames, 0);
|
|
|
|
let successCount = 0;
|
|
let failCount = 0;
|
|
let results = [];
|
|
|
|
for (let i = 0; i < checkboxes.length; i++) {
|
|
const cb = checkboxes[i];
|
|
const configId = parseInt(cb.value);
|
|
const configName = cb.dataset.name;
|
|
|
|
renderGenerateLoading(configNames, i);
|
|
|
|
try {
|
|
const response = await fetch('/api/report/generate', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({
|
|
config_id: configId,
|
|
start_time: startTime.replace('T', ' ') + ':00',
|
|
end_time: endTime.replace('T', ' ') + ':00'
|
|
})
|
|
});
|
|
|
|
const result = await response.json();
|
|
|
|
if (result.success) {
|
|
successCount++;
|
|
results.push({ name: configName, success: true, ...result });
|
|
} else {
|
|
failCount++;
|
|
results.push({ name: configName, success: false, message: result.message });
|
|
}
|
|
} catch (error) {
|
|
failCount++;
|
|
results.push({ name: configName, success: false, message: error.message });
|
|
}
|
|
}
|
|
|
|
// 显示结果
|
|
let html = '<div class="alert alert-info"><strong>生成完成</strong><br><br>';
|
|
html += `成功:${successCount} 个,失败:${failCount} 个<br><br>`;
|
|
|
|
results.forEach(r => {
|
|
if (r.success) {
|
|
html += `<div style="margin: 10px 0; padding: 10px; background: #f0f8ff; border-radius: 4px;">`;
|
|
html += `<strong>✓ ${r.name}</strong><br>`;
|
|
html += `订单数量:${r.total_orders} 条,总电量:${r.total_degree ? parseFloat(r.total_degree).toFixed(3) : '0.000'} kWh<br>`;
|
|
html += `<a href="/api/download?path=${r.file_path}" target="_blank" class="btn btn-sm btn-success" style="margin-top: 5px;">📥 下载</a>`;
|
|
html += `</div>`;
|
|
} else {
|
|
html += `<div style="margin: 10px 0; padding: 10px; background: #fff0f0; border-radius: 4px;">`;
|
|
html += `<strong>✗ ${r.name}</strong><br>`;
|
|
html += `错误:${r.message}`;
|
|
html += `</div>`;
|
|
}
|
|
});
|
|
|
|
html += '</div>';
|
|
const resultDiv = document.getElementById('generate-result');
|
|
if (resultDiv) {
|
|
resultDiv.innerHTML = html;
|
|
}
|
|
|
|
isGenerating = false;
|
|
setGenerateControlsDisabled(false);
|
|
clearGenerateState();
|
|
stopGeneratePolling();
|
|
}
|
|
|
|
// 设置生成相关控件的禁用状态
|
|
function setGenerateControlsDisabled(disabled) {
|
|
const generateBtn = document.getElementById('generate-btn');
|
|
if (generateBtn) {
|
|
generateBtn.disabled = disabled;
|
|
generateBtn.style.opacity = disabled ? '0.6' : '1';
|
|
generateBtn.style.cursor = disabled ? 'not-allowed' : 'pointer';
|
|
}
|
|
|
|
document.querySelectorAll('.config-checkbox').forEach(cb => {
|
|
cb.disabled = disabled;
|
|
});
|
|
|
|
document.getElementById('start-time').disabled = disabled;
|
|
document.getElementById('end-time').disabled = disabled;
|
|
|
|
document.querySelectorAll('[onclick^="setPresetTime"]').forEach(btn => {
|
|
btn.style.opacity = disabled ? '0.6' : '1';
|
|
btn.style.pointerEvents = disabled ? 'none' : 'auto';
|
|
});
|
|
}
|
|
|