更改总计逻辑
This commit is contained in:
@@ -872,3 +872,137 @@ color: #666;
|
||||
color: white;
|
||||
border-color: var(--primary);
|
||||
}
|
||||
|
||||
/* 生成中动态效果 */
|
||||
.generate-loading {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.generate-loading-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.spinner {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border: 3px solid #bee5eb;
|
||||
border-top-color: #17a2b8;
|
||||
border-radius: 50%;
|
||||
animation: spin 0.8s linear infinite;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
.generate-loading-title {
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
color: #0c5460;
|
||||
}
|
||||
|
||||
.generate-loading-progress {
|
||||
font-size: 13px;
|
||||
color: #31708f;
|
||||
margin-left: auto;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.generate-progress-bar {
|
||||
width: 100%;
|
||||
height: 6px;
|
||||
background: #bee5eb;
|
||||
border-radius: 3px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.generate-progress-fill {
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, #17a2b8, #3498db);
|
||||
border-radius: 3px;
|
||||
transition: width 0.3s ease;
|
||||
}
|
||||
|
||||
.generate-item-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.generate-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 10px 12px;
|
||||
background: white;
|
||||
border-radius: 6px;
|
||||
font-size: 13px;
|
||||
border: 1px solid #d1ecf1;
|
||||
}
|
||||
|
||||
.generate-item-icon {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 12px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.generate-item-icon.pending {
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.generate-item-icon.running {
|
||||
animation: pulse 1s ease-in-out infinite;
|
||||
color: #17a2b8;
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0%, 100% { opacity: 1; }
|
||||
50% { opacity: 0.4; }
|
||||
}
|
||||
|
||||
.generate-item-icon.success {
|
||||
color: #27ae60;
|
||||
}
|
||||
|
||||
.generate-item-icon.fail {
|
||||
color: #e74c3c;
|
||||
}
|
||||
|
||||
.generate-item-name {
|
||||
flex: 1;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.generate-item.running .generate-item-name {
|
||||
font-weight: 600;
|
||||
color: #17a2b8;
|
||||
}
|
||||
|
||||
.generate-item-status {
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.generate-item.running .generate-item-status {
|
||||
color: #17a2b8;
|
||||
}
|
||||
|
||||
.generate-item.success .generate-item-status {
|
||||
color: #27ae60;
|
||||
}
|
||||
|
||||
.generate-item.fail .generate-item-status {
|
||||
color: #e74c3c;
|
||||
}
|
||||
|
||||
@@ -25,10 +25,50 @@ async function batchDownloadHistory() {
|
||||
|
||||
const ids = Array.from(checkboxes).map(cb => parseInt(cb.value));
|
||||
|
||||
// 逐个下载
|
||||
ids.forEach(id => {
|
||||
window.open(`/api/download?id=${id}`, '_blank');
|
||||
});
|
||||
// 检查是否有生成失败的记录
|
||||
const failedCount = Array.from(checkboxes).filter(cb => cb.dataset.status !== '1').length;
|
||||
const successCount = checkboxes.length - failedCount;
|
||||
|
||||
if (successCount === 0) {
|
||||
alert('选中的记录都是生成失败的,无法下载');
|
||||
return;
|
||||
}
|
||||
|
||||
if (failedCount > 0) {
|
||||
if (!confirm(`选中的 ${checkboxes.length} 条记录中有 ${failedCount} 条是生成失败的,无法下载。\n是否打包下载成功的 ${successCount} 条?`)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/download/batch', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ ids: ids })
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
const blob = await response.blob();
|
||||
const url = window.URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
const timestamp = new Date().toISOString().slice(0, 19).replace(/[-T:]/g, '');
|
||||
a.download = `批量下载_${timestamp}.zip`;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
window.URL.revokeObjectURL(url);
|
||||
} else {
|
||||
try {
|
||||
const result = await response.json();
|
||||
alert('下载失败: ' + (result.message || '未知错误'));
|
||||
} catch (e) {
|
||||
alert('下载失败: 服务器返回错误');
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
alert('下载失败: ' + error.message);
|
||||
}
|
||||
}
|
||||
|
||||
// 按日期查询
|
||||
@@ -159,7 +199,7 @@ function renderHistory(list) {
|
||||
|
||||
return `
|
||||
<tr>
|
||||
<td><input type="checkbox" class="history-checkbox" value="${item.id}"></td>
|
||||
<td><input type="checkbox" class="history-checkbox" value="${item.id}" data-status="${item.status}" data-name="${configName}"></td>
|
||||
<td>${reportDate}</td>
|
||||
<td>${timeRange}</td>
|
||||
<td>${configName}</td>
|
||||
|
||||
@@ -193,7 +193,7 @@ function stopGeneratePolling() {
|
||||
function initGenerateState() {
|
||||
const state = getGenerateState();
|
||||
if (state) {
|
||||
const { startTime } = state;
|
||||
const { startTime, configIds } = state;
|
||||
const now = Date.now();
|
||||
const elapsed = now - startTime;
|
||||
|
||||
@@ -208,11 +208,73 @@ function initGenerateState() {
|
||||
|
||||
const resultDiv = document.getElementById('generate-result');
|
||||
if (resultDiv) {
|
||||
resultDiv.innerHTML = '<div class="alert alert-info">检测到有正在生成的报表,请稍候...</div>';
|
||||
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) {
|
||||
@@ -237,20 +299,23 @@ async function generateReport() {
|
||||
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();
|
||||
|
||||
const resultDiv = document.getElementById('generate-result');
|
||||
resultDiv.innerHTML = '<div class="alert alert-info">正在批量生成日报,请稍候...</div>';
|
||||
|
||||
renderGenerateLoading(configNames, 0);
|
||||
|
||||
let successCount = 0;
|
||||
let failCount = 0;
|
||||
let results = [];
|
||||
|
||||
for (const cb of checkboxes) {
|
||||
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', {
|
||||
@@ -298,7 +363,10 @@ async function generateReport() {
|
||||
});
|
||||
|
||||
html += '</div>';
|
||||
resultDiv.innerHTML = html;
|
||||
const resultDiv = document.getElementById('generate-result');
|
||||
if (resultDiv) {
|
||||
resultDiv.innerHTML = html;
|
||||
}
|
||||
|
||||
isGenerating = false;
|
||||
setGenerateControlsDisabled(false);
|
||||
|
||||
@@ -182,6 +182,11 @@ async function loadSumData(page = 1) {
|
||||
const configName = document.getElementById('sum-data-config-name').value;
|
||||
const dataSource = document.getElementById('sum-data-source').value;
|
||||
|
||||
const listTbody = document.getElementById('sum-data-list');
|
||||
if (listTbody) {
|
||||
listTbody.innerHTML = '<tr><td colspan="12" class="loading">加载中...</td></tr>';
|
||||
}
|
||||
|
||||
const params = new URLSearchParams();
|
||||
params.append('page', '1');
|
||||
params.append('page_size', '5000');
|
||||
@@ -196,10 +201,14 @@ async function loadSumData(page = 1) {
|
||||
if (result.success) {
|
||||
renderSumDataList(result.data.list || []);
|
||||
} else {
|
||||
document.getElementById('sum-data-list').innerHTML = '<tr><td colspan="12" class="empty">加载失败: ' + (result.message || '未知错误') + '</td></tr>';
|
||||
if (listTbody) {
|
||||
listTbody.innerHTML = '<tr><td colspan="12" class="empty">加载失败: ' + (result.message || '未知错误') + '</td></tr>';
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
document.getElementById('sum-data-list').innerHTML = '<tr><td colspan="12" class="empty">加载失败: ' + error.message + '</td></tr>';
|
||||
if (listTbody) {
|
||||
listTbody.innerHTML = '<tr><td colspan="12" class="empty">加载失败: ' + error.message + '</td></tr>';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -224,10 +233,14 @@ function toggleSelectAllSumData() {
|
||||
async function collectSumDataFromHistory() {
|
||||
if (!confirm('确定要从所有成功的历史记录中重新采集求和数据吗?\n(会先清除旧的自动采集数据)')) return;
|
||||
|
||||
const resultDiv = document.getElementById('sum-data-list');
|
||||
resultDiv.innerHTML = '<tr><td colspan="12" class="empty">正在采集中,请稍候...</td></tr>';
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/sum-data/collect', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({})
|
||||
});
|
||||
const result = await response.json();
|
||||
if (result.success) {
|
||||
@@ -235,9 +248,11 @@ async function collectSumDataFromHistory() {
|
||||
loadSumData(1);
|
||||
} else {
|
||||
alert('采集失败: ' + (result.message || '未知错误'));
|
||||
loadSumData(1);
|
||||
}
|
||||
} catch (error) {
|
||||
alert('采集失败: ' + error.message);
|
||||
loadSumData(1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -558,7 +573,13 @@ async function exportSumData() {
|
||||
alert('没有可导出的数据');
|
||||
return;
|
||||
}
|
||||
window.open(result.file_path, '_blank');
|
||||
const a = document.createElement('a');
|
||||
a.href = result.file_path;
|
||||
a.download = result.filename || '求和数据导出.xlsx';
|
||||
a.target = '_blank';
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
} else {
|
||||
alert('导出失败: ' + (result.message || '未知错误'));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user