474 lines
17 KiB
JavaScript
474 lines
17 KiB
JavaScript
let historyStartDate = '';
|
||
let historyEndDate = '';
|
||
let historyStatus = '';
|
||
let historyConfigName = '';
|
||
let historyPageSize = 10;
|
||
|
||
// 每页显示条数变化
|
||
function onHistoryPageSizeChange() {
|
||
historyPageSize = parseInt(document.getElementById('history-page-size').value) || 10;
|
||
loadHistoryWithDate(1);
|
||
}
|
||
|
||
// 状态筛选变化
|
||
function onHistoryStatusChange() {
|
||
historyStatus = document.getElementById('history-status-filter').value;
|
||
loadHistoryWithDate(1);
|
||
}
|
||
|
||
// 统一的批量下载:勾选时下载选中记录,未勾选时按筛选条件下载全部匹配记录
|
||
async function batchDownloadHistory() {
|
||
// 同步当前筛选条件
|
||
const configNameInput = document.getElementById('history-config-name');
|
||
if (configNameInput) {
|
||
historyConfigName = configNameInput.value.trim();
|
||
}
|
||
historyStatus = document.getElementById('history-status-filter').value;
|
||
historyStartDate = document.getElementById('history-start-date').value;
|
||
historyEndDate = document.getElementById('history-end-date').value;
|
||
|
||
const checkboxes = document.querySelectorAll('.history-checkbox:checked');
|
||
const hasSelection = checkboxes.length > 0;
|
||
|
||
if (hasSelection) {
|
||
// 模式1:下载勾选的记录
|
||
const ids = Array.from(checkboxes).map(cb => parseInt(cb.value));
|
||
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;
|
||
}
|
||
} else {
|
||
if (!confirm(`确定要下载选中的 ${successCount} 条报表吗?`)) {
|
||
return;
|
||
}
|
||
}
|
||
|
||
try {
|
||
const response = await fetch('/api/download/batch', {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({ ids: ids })
|
||
});
|
||
await handleDownloadResponse(response);
|
||
} catch (error) {
|
||
alert('下载失败: ' + error.message);
|
||
}
|
||
} else {
|
||
// 模式2:按筛选条件下载全部匹配的成功记录
|
||
const filterDescParts = [];
|
||
if (historyConfigName) filterDescParts.push(`配置名称含"${historyConfigName}"`);
|
||
if (historyStartDate) filterDescParts.push(`开始日期 ${historyStartDate}`);
|
||
if (historyEndDate) filterDescParts.push(`结束日期 ${historyEndDate}`);
|
||
const filterDesc = filterDescParts.length > 0 ? filterDescParts.join(',') : '全部记录';
|
||
|
||
// 查询匹配数量
|
||
let matchCount = 0;
|
||
let dateRange = '';
|
||
try {
|
||
const countResponse = await fetch('/api/download/batch-filter-count', {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({
|
||
config_name: historyConfigName,
|
||
start_date: historyStartDate,
|
||
end_date: historyEndDate
|
||
})
|
||
});
|
||
const countResult = await countResponse.json();
|
||
if (countResult.success) {
|
||
matchCount = countResult.data.total || 0;
|
||
if (countResult.data.min_date && countResult.data.max_date) {
|
||
dateRange = `\n报表日期范围: ${countResult.data.min_date} 至 ${countResult.data.max_date}`;
|
||
}
|
||
}
|
||
} catch (e) {
|
||
console.error('查询导出数量失败:', e);
|
||
}
|
||
|
||
if (matchCount === 0) {
|
||
alert(`没有符合条件的成功记录可导出。\n筛选条件: ${filterDesc}`);
|
||
return;
|
||
}
|
||
|
||
if (!confirm(`未选中任何记录,将按当前筛选条件导出全部匹配的成功记录。\n\n筛选条件: ${filterDesc}\n匹配记录数: ${matchCount} 条${dateRange}\n\n注意: 仅导出生成成功的报表。\n是否继续?`)) {
|
||
return;
|
||
}
|
||
|
||
try {
|
||
const response = await fetch('/api/download/batch-filter', {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({
|
||
config_name: historyConfigName,
|
||
start_date: historyStartDate,
|
||
end_date: historyEndDate,
|
||
status: historyStatus
|
||
})
|
||
});
|
||
await handleDownloadResponse(response);
|
||
} catch (error) {
|
||
alert('下载失败: ' + error.message);
|
||
}
|
||
}
|
||
}
|
||
|
||
// 处理下载响应(两种模式共用)
|
||
async function handleDownloadResponse(response) {
|
||
if (!response.ok) {
|
||
try {
|
||
const result = await response.json();
|
||
alert('下载失败: ' + (result.message || '未知错误'));
|
||
} catch (e) {
|
||
alert('下载失败: 服务器返回错误');
|
||
}
|
||
return;
|
||
}
|
||
|
||
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);
|
||
|
||
// 显示统计信息(如果有)
|
||
const totalMatched = response.headers.get('X-Total-Matched');
|
||
const filesZipped = response.headers.get('X-Files-Zipped');
|
||
const filesMissing = response.headers.get('X-Files-Missing');
|
||
const skippedFailed = response.headers.get('X-Skipped-Failed');
|
||
if (totalMatched && filesZipped) {
|
||
const missing = filesMissing ? parseInt(filesMissing) : 0;
|
||
const skipped = skippedFailed ? parseInt(skippedFailed) : 0;
|
||
let msg = `下载完成!\n`;
|
||
msg += `匹配记录: ${totalMatched} 条\n`;
|
||
msg += `成功打包: ${filesZipped} 个文件`;
|
||
if (missing > 0) {
|
||
msg += `\n缺失文件: ${filesMissing} 个(历史文件可能已被清理)`;
|
||
}
|
||
if (skipped > 0) {
|
||
msg += `\n跳过失败记录: ${skipped} 个`;
|
||
}
|
||
if (missing > 0 || skipped > 0) {
|
||
alert(msg);
|
||
}
|
||
}
|
||
}
|
||
|
||
// 按日期查询
|
||
function queryHistoryByDate() {
|
||
const startDate = document.getElementById('history-start-date').value;
|
||
const endDate = document.getElementById('history-end-date').value;
|
||
const configNameInput = document.getElementById('history-config-name');
|
||
historyStartDate = startDate;
|
||
historyEndDate = endDate;
|
||
historyConfigName = configNameInput ? configNameInput.value.trim() : '';
|
||
loadHistoryWithDate(1);
|
||
}
|
||
|
||
// 清除筛选
|
||
function clearHistoryFilter() {
|
||
document.getElementById('history-status-filter').value = '';
|
||
document.getElementById('history-start-date').value = '';
|
||
document.getElementById('history-end-date').value = '';
|
||
const configNameInput = document.getElementById('history-config-name');
|
||
if (configNameInput) configNameInput.value = '';
|
||
historyStatus = '';
|
||
historyStartDate = '';
|
||
historyEndDate = '';
|
||
historyConfigName = '';
|
||
loadHistory(1);
|
||
}
|
||
|
||
// 带日期参数加载历史记录
|
||
// 渲染历史记录列表
|
||
function renderHistory(list) {
|
||
const tbody = document.getElementById('history-list');
|
||
if (!list || list.length === 0) {
|
||
tbody.innerHTML = '<tr><td colspan="9" class="empty">暂无历史记录</td></tr>';
|
||
return;
|
||
}
|
||
|
||
// 解析日期字符串(支持多种格式)
|
||
const parseDate = (dateStr) => {
|
||
if (!dateStr) return null;
|
||
|
||
// 尝试匹配 "2026-07-12" 格式
|
||
let match = dateStr.match(/(\d{4})-(\d{1,2})-(\d{1,2})/);
|
||
if (match) {
|
||
return {
|
||
year: parseInt(match[1]),
|
||
month: parseInt(match[2]),
|
||
day: parseInt(match[3])
|
||
};
|
||
}
|
||
|
||
// 尝试匹配 "Sun, 12 Jul 2026 00:00:00 GMT" 格式
|
||
match = dateStr.match(/(\d{1,2})\s+(\w+)\s+(\d{4})/);
|
||
if (match) {
|
||
const months = {
|
||
'Jan': 1, 'Feb': 2, 'Mar': 3, 'Apr': 4, 'May': 5, 'Jun': 6,
|
||
'Jul': 7, 'Aug': 8, 'Sep': 9, 'Oct': 10, 'Nov': 11, 'Dec': 12
|
||
};
|
||
return {
|
||
year: parseInt(match[3]),
|
||
month: months[match[2]] || 1,
|
||
day: parseInt(match[1])
|
||
};
|
||
}
|
||
|
||
return null;
|
||
};
|
||
|
||
// 解析时间字符串(支持多种格式)
|
||
const parseTime = (timeStr) => {
|
||
if (!timeStr) return null;
|
||
|
||
// 尝试匹配 "2026-07-12 08:00:00" 格式
|
||
let match = timeStr.match(/(\d{4})-(\d{1,2})-(\d{1,2})\s+(\d{1,2}):(\d{1,2}):(\d{1,2})/);
|
||
if (match) {
|
||
return {
|
||
year: match[1],
|
||
month: match[2].padStart(2, '0'),
|
||
day: match[3].padStart(2, '0'),
|
||
hour: match[4].padStart(2, '0'),
|
||
minute: match[5].padStart(2, '0'),
|
||
second: match[6].padStart(2, '0')
|
||
};
|
||
}
|
||
|
||
// 尝试匹配 "Sun, 12 Jul 2026 08:00:00 GMT" 格式
|
||
match = timeStr.match(/(\d{1,2})\s+(\w+)\s+(\d{4})\s+(\d{1,2}):(\d{1,2}):(\d{1,2})/);
|
||
if (match) {
|
||
const months = {
|
||
'Jan': '01', 'Feb': '02', 'Mar': '03', 'Apr': '04', 'May': '05', 'Jun': '06',
|
||
'Jul': '07', 'Aug': '08', 'Sep': '09', 'Oct': '10', 'Nov': '11', 'Dec': '12'
|
||
};
|
||
return {
|
||
year: match[3],
|
||
month: months[match[2]] || '01',
|
||
day: match[1].padStart(2, '0'),
|
||
hour: match[4].padStart(2, '0'),
|
||
minute: match[5].padStart(2, '0'),
|
||
second: match[6].padStart(2, '0')
|
||
};
|
||
}
|
||
|
||
return null;
|
||
};
|
||
|
||
tbody.innerHTML = list.map(item => {
|
||
const statusText = item.status === 1 ? '成功' : '失败';
|
||
const statusClass = item.status === 1 ? 'status-success' : 'status-failed';
|
||
|
||
// 格式化报表日期为中文
|
||
let reportDate = '-';
|
||
const dateObj = parseDate(item.report_date);
|
||
if (dateObj) {
|
||
reportDate = `${dateObj.year}年${dateObj.month}月${dateObj.day}日`;
|
||
} else if (item.report_date) {
|
||
reportDate = item.report_date;
|
||
}
|
||
|
||
// 格式化时间范围
|
||
let timeRange = '-';
|
||
const startObj = parseTime(item.start_time);
|
||
const endObj = parseTime(item.end_time);
|
||
if (startObj && endObj) {
|
||
const formatTimeObj = (obj) => `${obj.year}-${obj.month}-${obj.day} ${obj.hour}:${obj.minute}:${obj.second}`;
|
||
timeRange = `${formatTimeObj(startObj)} 至 ${formatTimeObj(endObj)}`;
|
||
} else if (item.start_time && item.end_time) {
|
||
timeRange = `${item.start_time} 至 ${item.end_time}`;
|
||
}
|
||
|
||
const configName = item.config_name || '-';
|
||
const splitValue = item.split_name || '-';
|
||
const totalOrders = item.total_orders || 0;
|
||
const totalDegree = item.total_degree ? parseFloat(item.total_degree).toFixed(3) : '0.000';
|
||
|
||
return `
|
||
<tr>
|
||
<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>
|
||
<td>${splitValue}</td>
|
||
<td>${totalOrders}</td>
|
||
<td>${totalDegree}</td>
|
||
<td><span class="${statusClass}">${statusText}</span></td>
|
||
<td>
|
||
${item.status === 1 && item.file_path ?
|
||
`<a href="/api/download?path=${encodeURIComponent(item.file_path)}&token=${encodeURIComponent(getToken())}" class="btn btn-sm btn-success">下载</a>` :
|
||
'-'}
|
||
<button class="btn btn-sm btn-danger" onclick="deleteHistory(${item.id})">删除</button>
|
||
</td>
|
||
</tr>
|
||
`;
|
||
}).join('');
|
||
}
|
||
|
||
// 渲染分页控件
|
||
function renderPagination(page, total, pageSize) {
|
||
const container = document.getElementById('history-pagination');
|
||
if (!container) return;
|
||
|
||
const totalPages = Math.ceil(total / pageSize);
|
||
if (totalPages <= 1) {
|
||
container.innerHTML = '';
|
||
return;
|
||
}
|
||
|
||
let html = '<div class="pagination">';
|
||
|
||
// 上一页
|
||
if (page > 1) {
|
||
html += `<button onclick="loadHistoryWithDate(${page - 1})">上一页</button>`;
|
||
}
|
||
|
||
// 页码(最多显示7个页码)
|
||
let startPage = Math.max(1, page - 3);
|
||
let endPage = Math.min(totalPages, startPage + 6);
|
||
if (endPage - startPage < 6) {
|
||
startPage = Math.max(1, endPage - 6);
|
||
}
|
||
|
||
if (startPage > 1) {
|
||
html += `<button onclick="loadHistoryWithDate(1)">1</button>`;
|
||
if (startPage > 2) {
|
||
html += `<span style="padding: 0 5px;">...</span>`;
|
||
}
|
||
}
|
||
|
||
for (let i = startPage; i <= endPage; i++) {
|
||
if (i === page) {
|
||
html += `<button class="active">${i}</button>`;
|
||
} else {
|
||
html += `<button onclick="loadHistoryWithDate(${i})">${i}</button>`;
|
||
}
|
||
}
|
||
|
||
if (endPage < totalPages) {
|
||
if (endPage < totalPages - 1) {
|
||
html += `<span style="padding: 0 5px;">...</span>`;
|
||
}
|
||
html += `<button onclick="loadHistoryWithDate(${totalPages})">${totalPages}</button>`;
|
||
}
|
||
|
||
// 下一页
|
||
if (page < totalPages) {
|
||
html += `<button onclick="loadHistoryWithDate(${page + 1})">下一页</button>`;
|
||
}
|
||
|
||
html += '</div>';
|
||
container.innerHTML = html;
|
||
}
|
||
|
||
// 加载历史记录(无日期筛选)
|
||
async function loadHistory(page = 1) {
|
||
loadHistoryWithDate(page);
|
||
}
|
||
|
||
async function loadHistoryWithDate(page) {
|
||
try {
|
||
// 同步配置名称搜索框的值(翻页时保留筛选条件)
|
||
const configNameInput = document.getElementById('history-config-name');
|
||
if (configNameInput) {
|
||
historyConfigName = configNameInput.value.trim();
|
||
}
|
||
let url = `/api/report/history?page=${page}&page_size=${historyPageSize}`;
|
||
if (historyStatus) url += `&status=${historyStatus}`;
|
||
if (historyStartDate) url += `&start_date=${historyStartDate}`;
|
||
if (historyEndDate) url += `&end_date=${historyEndDate}`;
|
||
if (historyConfigName) url += `&config_name=${encodeURIComponent(historyConfigName)}`;
|
||
const response = await fetch(url);
|
||
const result = await response.json();
|
||
if (result.success) {
|
||
renderHistory(result.data.list);
|
||
renderPagination(result.data.page, result.data.total, result.data.page_size);
|
||
} else {
|
||
alert('加载失败: ' + (result.error || result.message || '未知错误'));
|
||
}
|
||
} catch (error) {
|
||
alert('加载失败: ' + error.message);
|
||
}
|
||
}
|
||
|
||
// 全选/取消全选历史记录
|
||
function toggleSelectAllHistory() {
|
||
const selectAllCheckbox = document.getElementById('select-all-history');
|
||
const checkboxes = document.querySelectorAll('#history-list input[type="checkbox"]');
|
||
checkboxes.forEach(cb => {
|
||
cb.checked = selectAllCheckbox.checked;
|
||
});
|
||
}
|
||
|
||
// 删除单条历史记录
|
||
async function deleteHistory(id) {
|
||
if (!confirm('确定要删除这条历史记录吗?对应的报表文件也会被删除。')) {
|
||
return;
|
||
}
|
||
|
||
try {
|
||
const response = await fetch('/api/report/history/batch-delete', {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({ ids: [id] })
|
||
});
|
||
const result = await response.json();
|
||
if (result.success) {
|
||
alert('删除成功');
|
||
loadHistoryWithDate();
|
||
} else {
|
||
alert('删除失败: ' + result.message);
|
||
}
|
||
} catch (error) {
|
||
alert('删除失败: ' + error.message);
|
||
}
|
||
}
|
||
|
||
// 批量删除历史记录
|
||
async function batchDeleteHistory() {
|
||
const checkboxes = document.querySelectorAll('.history-checkbox:checked');
|
||
if (checkboxes.length === 0) {
|
||
alert('请至少选择一条记录');
|
||
return;
|
||
}
|
||
|
||
if (!confirm(`确定要删除选中的 ${checkboxes.length} 条历史记录吗?对应的报表文件也会被删除。`)) {
|
||
return;
|
||
}
|
||
|
||
const ids = Array.from(checkboxes).map(cb => parseInt(cb.value));
|
||
|
||
try {
|
||
const response = await fetch('/api/report/history/batch-delete', {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({ ids: ids })
|
||
});
|
||
const result = await response.json();
|
||
if (result.success) {
|
||
alert(result.message);
|
||
loadHistoryWithDate();
|
||
} else {
|
||
alert('删除失败: ' + result.message);
|
||
}
|
||
} catch (error) {
|
||
alert('删除失败: ' + error.message);
|
||
}
|
||
}
|
||
|
||
// 批量导出当前筛选条件下的所有记录 - 已合并到 batchDownloadHistory 函数
|
||
// 该函数已被移除,功能整合到统一的 batchDownloadHistory 中
|