Files
ylt_diy/templates/index.html
user9994793890 0ebc35398d fix: 修复JavaScript变量名冲突导致页面无法加载的问题
Coze-Commit-Type: user
Coze-User-ID: 3722323274763196
Coze-Conversation-ID: 9894087
2026-07-10 13:07:58 +08:00

1339 lines
51 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>订单日报系统</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', sans-serif;
background: #f5f7fa;
color: #333;
}
.container {
max-width: 1400px;
margin: 0 auto;
padding: 20px;
}
h1 {
text-align: center;
margin-bottom: 30px;
color: #2c3e50;
}
.tabs {
display: flex;
border-bottom: 2px solid #e1e8ed;
margin-bottom: 20px;
}
.tab {
padding: 12px 24px;
cursor: pointer;
border: none;
background: none;
font-size: 16px;
color: #666;
transition: all 0.3s;
}
.tab:hover {
color: #3498db;
}
.tab.active {
color: #3498db;
border-bottom: 2px solid #3498db;
margin-bottom: -2px;
}
.tab-content {
display: none;
}
.tab-content.active {
display: block;
}
.card {
background: white;
border-radius: 8px;
padding: 20px;
margin-bottom: 20px;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}
.form-group {
margin-bottom: 15px;
}
.form-group label {
display: block;
margin-bottom: 5px;
font-weight: 500;
color: #555;
}
.form-control {
width: 100%;
padding: 8px 12px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 14px;
}
.form-control:focus {
outline: none;
border-color: #3498db;
}
select {
background: white;
}
.btn {
padding: 8px 16px;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
transition: all 0.3s;
}
.btn-primary {
background: #3498db;
color: white;
}
.btn-primary:hover {
background: #2980b9;
}
.btn-success {
background: #27ae60;
color: white;
}
.btn-success:hover {
background: #229954;
}
.btn-danger {
background: #e74c3c;
color: white;
}
.btn-danger:hover {
background: #c0392b;
}
.btn-warning {
background: #f39c12;
color: white;
}
.btn-warning:hover {
background: #d35400;
}
.btn-sm {
padding: 4px 8px;
font-size: 12px;
}
table {
width: 100%;
border-collapse: collapse;
}
th, td {
padding: 12px;
text-align: left;
border-bottom: 1px solid #e1e8ed;
}
th {
background: #f8f9fa;
font-weight: 600;
color: #555;
}
tr:hover {
background: #f8f9fa;
}
.checkbox-group {
display: flex;
flex-wrap: wrap;
gap: 10px;
max-height: 300px;
overflow-y: auto;
padding: 10px;
border: 1px solid #ddd;
border-radius: 4px;
}
.checkbox-item {
display: flex;
align-items: center;
gap: 5px;
min-width: 200px;
}
.status-badge {
display: inline-block;
padding: 4px 8px;
border-radius: 4px;
font-size: 12px;
font-weight: 500;
}
.status-active {
background: #d4edda;
color: #155724;
}
.status-inactive {
background: #f8d7da;
color: #721c24;
}
.alert {
padding: 12px 16px;
border-radius: 4px;
margin-bottom: 15px;
}
.alert-success {
background: #d4edda;
color: #155724;
border: 1px solid #c3e6cb;
}
.alert-error {
background: #f8d7da;
color: #721c24;
border: 1px solid #f5c6cb;
}
.alert-info {
background: #d1ecf1;
color: #0c5460;
border: 1px solid #bee5eb;
}
.loading {
text-align: center;
padding: 20px;
color: #666;
}
.empty {
text-align: center;
padding: 40px;
color: #999;
}
.action-buttons {
display: flex;
gap: 5px;
}
.modal {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0,0,0,0.5);
z-index: 1000;
}
.modal.active {
display: flex;
align-items: center;
justify-content: center;
}
.modal-content {
background: white;
border-radius: 8px;
padding: 30px;
max-width: 800px;
width: 90%;
max-height: 90vh;
overflow-y: auto;
}
.modal-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
}
.modal-title {
font-size: 20px;
font-weight: 600;
}
.close-btn {
background: none;
border: none;
font-size: 24px;
cursor: pointer;
color: #999;
}
.close-btn:hover {
color: #333;
}
.pagination {
display: flex;
justify-content: center;
align-items: center;
gap: 10px;
margin-top: 20px;
}
.pagination button {
padding: 6px 12px;
border: 1px solid #ddd;
background: white;
cursor: pointer;
border-radius: 4px;
}
.pagination button:hover:not(:disabled) {
background: #f0f0f0;
}
.pagination button:disabled {
cursor: not-allowed;
opacity: 0.5;
}
.pagination span {
color: #666;
}
</style>
</head>
<body>
<div class="container">
<h1>订单日报系统</h1>
<div class="tabs">
<button class="tab active" onclick="switchTab('config')">配置管理</button>
<button class="tab" onclick="switchTab('generate')">日报生成</button>
<button class="tab" onclick="switchTab('history')">历史记录</button>
</div>
<!-- 配置管理 -->
<div id="config-tab" class="tab-content active">
<div class="card">
<button class="btn btn-primary" onclick="showCreateModal()">创建配置</button>
</div>
<div class="card">
<table>
<thead>
<tr>
<th>配置名称</th>
<th>拆分方式</th>
<th>拆分值</th>
<th>字段数</th>
<th>状态</th>
<th>操作</th>
</tr>
</thead>
<tbody id="config-list">
<tr><td colspan="6" class="loading">加载中...</td></tr>
</tbody>
</table>
</div>
</div>
<!-- 日报生成 -->
<div id="generate-tab" class="tab-content">
<div class="card">
<div class="alert alert-info" style="margin-bottom: 20px;">
<strong>💡 使用说明:</strong><br>
1. 选择已创建的配置<br>
2. 选择时间范围(可点击预设按钮快速选择)<br>
3. 点击"生成日报"按钮<br>
4. 生成成功后,点击"立即下载Excel文件"按钮下载,或切换到"历史记录"标签页查看和下载
</div>
<h3 style="margin-bottom: 20px;">生成日报</h3>
<div class="form-group">
<label>选择配置 *</label>
<select id="generate-config" class="form-control">
<option value="">请选择配置</option>
</select>
</div>
<div class="form-group">
<label>时间范围</label>
<div style="display: flex; gap: 10px; margin-bottom: 10px;">
<button class="btn btn-sm btn-primary" onclick="setPresetTime('yesterday')">昨天</button>
<button class="btn btn-sm btn-primary" onclick="setPresetTime('last3days')">最近3天</button>
<button class="btn btn-sm btn-primary" onclick="setPresetTime('last7days')">最近7天</button>
</div>
<div style="display: flex; gap: 10px;">
<div style="flex: 1;">
<input type="datetime-local" id="start-time" class="form-control">
</div>
<div style="flex: 1;">
<input type="datetime-local" id="end-time" class="form-control">
</div>
</div>
</div>
<button class="btn btn-success" onclick="generateReport()">生成日报</button>
<div id="generate-result" style="margin-top: 20px;"></div>
</div>
</div>
<!-- 历史记录 -->
<div id="history-tab" class="tab-content">
<div class="card">
<div style="display: flex; gap: 10px; align-items: center; flex-wrap: wrap;">
<button class="btn btn-primary" onclick="refreshHistory()">刷新</button>
<button class="btn btn-danger" onclick="batchDeleteHistory()">批量删除</button>
<button class="btn btn-success" onclick="batchDownloadHistory()">批量下载</button>
<span style="margin-left: auto;">日期查询:</span>
<input type="date" id="query-start-date" class="form-control" style="width: auto;">
<span></span>
<input type="date" id="query-end-date" class="form-control" style="width: auto;">
<button class="btn btn-primary" onclick="queryHistoryByDate()">查询</button>
<button class="btn btn-secondary" onclick="clearDateQuery()">清除</button>
</div>
</div>
<div class="card">
<table>
<thead>
<tr>
<th><input type="checkbox" id="select-all" onchange="toggleSelectAll()"></th>
<th>报表日期</th>
<th>时间范围</th>
<th>配置名称</th>
<th>拆分值</th>
<th>订单数</th>
<th>总金额</th>
<th>状态</th>
<th>操作</th>
</tr>
</thead>
<tbody id="history-list">
<tr><td colspan="9" class="loading">加载中...</td></tr>
</tbody>
</table>
<div class="pagination" id="pagination"></div>
</div>
</div>
</div>
<!-- 创建/编辑配置模态框 -->
<div id="config-modal" class="modal">
<div class="modal-content">
<div class="modal-header">
<h3 class="modal-title" id="modal-title">创建配置</h3>
<button class="close-btn" onclick="closeModal()">&times;</button>
</div>
<div class="form-group">
<label>配置名称 *</label>
<input type="text" id="config-name" class="form-control" placeholder="请输入配置名称">
</div>
<div class="form-group">
<label>拆分方式 *</label>
<select id="split-type" class="form-control" onchange="loadEntities()">
<option value="">请选择拆分方式</option>
<option value="company_id">按企业ID</option>
<option value="user_id">按用户ID</option>
<option value="station_id">按场站ID</option>
</select>
</div>
<div class="form-group">
<label>选择企业/用户/场站(可多选) *</label>
<div style="display: flex; gap: 8px; margin-bottom: 8px;">
<input type="text" id="entity-search" class="form-control" placeholder="输入名称搜索..." style="flex: 1;">
<button type="button" class="btn btn-secondary" onclick="searchEntities()">搜索</button>
</div>
<select id="split-value" class="form-control" multiple size="8">
<option value="">请先选择拆分方式</option>
</select>
<div style="display: flex; justify-content: space-between; align-items: center; margin-top: 8px;">
<small class="form-text text-muted">按住 Ctrl 或 Shift 键可多选</small>
<div id="entity-pagination" style="display: flex; gap: 4px; align-items: center;"></div>
</div>
</div>
<div class="form-group">
<label>选择字段 *</label>
<div class="checkbox-group" id="fields-list">
<div class="loading">加载中...</div>
</div>
</div>
<div class="form-group">
<label>求和字段(可选)</label>
<div class="checkbox-group" id="sum-fields-list">
<div class="loading">加载中...</div>
</div>
</div>
<div style="display: flex; gap: 10px; justify-content: flex-end;">
<button class="btn" onclick="closeModal()">取消</button>
<button class="btn btn-primary" onclick="saveConfig()">保存</button>
</div>
</div>
</div>
<script>
// 全局错误处理防止JavaScript错误中断执行
window.onerror = function(msg, url, line, col, error) {
console.error('JavaScript错误:', msg, 'at', url, 'line:', line);
return true; // 阻止错误传播
};
let editingConfigId = null;
let allFields = [];
let currentPage = 1;
const pageSize = 20;
// 切换标签页
function switchTab(tabName, event) {
document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
document.querySelectorAll('.tab-content').forEach(t => t.classList.remove('active'));
if (event && event.target) {
event.target.classList.add('active');
}
document.getElementById(`${tabName}-tab`).classList.add('active');
if (tabName === 'config') {
loadConfigs();
} else if (tabName === 'generate') {
loadGenerateConfigs();
} else if (tabName === 'history') {
loadHistory();
}
}
// 加载配置列表
async function loadConfigs() {
try {
const response = await fetch('/api/config');
const result = await response.json();
if (result.success) {
renderConfigList(result.data);
} else {
alert('加载配置失败: ' + result.message);
}
} catch (error) {
alert('加载配置失败: ' + error.message);
}
}
// 渲染配置列表
function renderConfigList(configs) {
const tbody = document.getElementById('config-list');
if (configs.length === 0) {
tbody.innerHTML = '<tr><td colspan="6" class="empty">暂无配置,请点击"创建配置"按钮</td></tr>';
return;
}
tbody.innerHTML = configs.map(config => `
<tr>
<td>${config.config_name}</td>
<td>${config.split_type === 'company_id' ? '按企业ID' : '按用户ID'}</td>
<td>${config.split_name || config.split_value}</td>
<td>${config.selected_fields.length}</td>
<td>
<span class="status-badge ${config.is_active ? 'status-active' : 'status-inactive'}">
${config.is_active ? '已启用' : '已禁用'}
</span>
</td>
<td>
<div class="action-buttons">
<button class="btn btn-sm btn-warning" onclick="toggleConfig(${config.id})">
${config.is_active ? '禁用' : '启用'}
</button>
<button class="btn btn-sm btn-primary" onclick="editConfig(${config.id})">编辑</button>
<button class="btn btn-sm btn-success" onclick="copyConfig(${config.id})">复制</button>
<button class="btn btn-sm btn-danger" onclick="deleteConfig(${config.id})">删除</button>
<button class="btn btn-sm" onclick="moveConfig(${config.id}, 'up')">↑</button>
<button class="btn btn-sm" onclick="moveConfig(${config.id}, 'down')">↓</button>
</div>
</td>
</tr>
`).join('');
}
// 显示创建模态框
function showCreateModal() {
editingConfigId = null;
document.getElementById('modal-title').textContent = '创建配置';
document.getElementById('config-name').value = '';
document.getElementById('split-type').value = '';
document.getElementById('split-value').innerHTML = '<option value="">请先选择拆分方式</option>';
loadFields();
document.getElementById('config-modal').classList.add('active');
}
// 加载字段列表
async function loadFields() {
try {
const response = await fetch('/api/fields');
const result = await response.json();
if (result.success) {
allFields = result.data;
renderFields();
}
} catch (error) {
console.error('加载字段失败:', error);
}
}
// 渲染字段列表
function renderFields(selectedFields = [], sumFields = []) {
const fieldsList = document.getElementById('fields-list');
const sumFieldsList = document.getElementById('sum-fields-list');
fieldsList.innerHTML = allFields.map(field => `
<label class="checkbox-item">
<input type="checkbox" value="${field.field}"
${selectedFields.includes(field.field) ? 'checked' : ''}>
${field.display}
</label>
`).join('');
sumFieldsList.innerHTML = allFields.map(field => `
<label class="checkbox-item">
<input type="checkbox" value="${field.field}"
${sumFields.includes(field.field) ? 'checked' : ''}>
${field.display}
</label>
`).join('');
}
// 当前实体加载状态
let currentEntityType = '';
let entityPage = 1;
let currentSearch = '';
const entityPageSize = 100;
// 加载企业/用户列表(支持分页和搜索)
async function loadEntities(page = 1, search = '') {
const splitType = document.getElementById('split-type').value;
const select = document.getElementById('split-value');
if (!splitType) {
select.innerHTML = '<option value="">请先选择拆分方式</option>';
return;
}
// 保存当前状态
currentEntityType = splitType === 'company_id' ? 'company' :
splitType === 'user_id' ? 'user' :
splitType === 'station_id' ? 'station' : '';
entityPage = page;
currentSearch = search;
select.innerHTML = '<option value="">加载中...</option>';
try {
const response = await fetch(`/api/entities?type=${currentEntityType}&page=${page}&page_size=${entityPageSize}&search=${encodeURIComponent(search)}`);
const result = await response.json();
if (result.success) {
const entities = result.data;
const total = result.total || 0;
const totalPages = Math.ceil(total / entityPageSize);
if (entities.length === 0) {
select.innerHTML = '<option value="">无数据</option>';
} else {
select.innerHTML = entities.map(e => {
let value = '';
let name = '';
let typeLabel = '';
if (splitType === 'company_id') {
value = e.company_id;
name = e.company_name;
} else if (splitType === 'user_id') {
value = e.user_id;
name = e.user_name;
typeLabel = e.order_type === 3 ? '(企业用户)' : '(普通用户)';
} else if (splitType === 'station_id') {
value = e.station_id;
name = e.station_name;
}
return `<option value="${value}" data-name="${name}">${name} ${typeLabel}</option>`;
}).join('');
}
// 更新分页UI
updatePagination(totalPages, total);
}
} catch (error) {
console.error('加载列表失败:', error);
select.innerHTML = '<option value="">加载失败</option>';
}
}
// 更新分页UI
function updatePagination(totalPages, total) {
const paginationDiv = document.getElementById('entity-pagination');
if (totalPages <= 1) {
paginationDiv.innerHTML = `<small class="text-muted">共 ${total} 条</small>`;
return;
}
let html = `<small class="text-muted">共 ${total} 条</small>`;
html += `<button type="button" class="btn btn-sm btn-secondary" onclick="loadEntities(${entityPage - 1}, currentSearch)" ${entityPage <= 1 ? 'disabled' : ''}>上一页</button>`;
html += `<span>${entityPage}/${totalPages}</span>`;
html += `<button type="button" class="btn btn-sm btn-secondary" onclick="loadEntities(${entityPage + 1}, currentSearch)" ${entityPage >= totalPages ? 'disabled' : ''}>下一页</button>`;
paginationDiv.innerHTML = html;
}
// 搜索实体
function searchEntities() {
const search = document.getElementById('entity-search').value.trim();
loadEntities(1, search);
}
// 回车搜索
document.addEventListener('DOMContentLoaded', function() {
const searchInput = document.getElementById('entity-search');
if (searchInput) {
searchInput.addEventListener('keypress', function(e) {
if (e.key === 'Enter') {
searchEntities();
}
});
}
});
// 保存配置
async function saveConfig() {
const configName = document.getElementById('config-name').value;
const splitType = document.getElementById('split-type').value;
const splitValueSelect = document.getElementById('split-value');
// 处理多选:获取所有选中的选项
const selectedOptions = Array.from(splitValueSelect.selectedOptions).filter(opt => opt.value);
const splitValue = selectedOptions.map(opt => opt.value).join(',');
const splitName = selectedOptions.map(opt => opt.dataset.name || '').join(',');
const selectedFields = Array.from(document.querySelectorAll('#fields-list input:checked')).map(cb => cb.value);
const sumFields = Array.from(document.querySelectorAll('#sum-fields-list input:checked')).map(cb => cb.value);
if (!configName || !splitType || !splitValue) {
alert('请填写所有必填字段');
return;
}
if (selectedFields.length === 0) {
alert('请至少选择一个字段');
return;
}
const data = {
config_name: configName,
split_type: splitType,
split_value: splitValue,
split_name: splitName,
selected_fields: selectedFields,
sum_fields: sumFields
};
try {
let response;
if (editingConfigId) {
response = await fetch(`/api/config/${editingConfigId}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
});
} else {
response = await fetch('/api/config', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
});
}
const result = await response.json();
if (result.success) {
alert(editingConfigId ? '配置更新成功' : '配置创建成功');
closeModal();
loadConfigs();
} else {
alert('保存失败: ' + result.message);
}
} catch (error) {
alert('保存失败: ' + error.message);
}
}
// 编辑配置
async function editConfig(configId) {
try {
const response = await fetch('/api/config');
const result = await response.json();
if (result.success) {
const config = result.data.find(c => c.id === configId);
if (config) {
editingConfigId = configId;
document.getElementById('modal-title').textContent = '编辑配置';
document.getElementById('config-name').value = config.config_name;
document.getElementById('split-type').value = config.split_type;
// 获取已选择的值
const splitValues = config.split_value.split(',');
// 加载实体并回显
await loadEntitiesForEdit(config.split_type, splitValues);
await loadFields();
renderFields(config.selected_fields, config.sum_fields);
document.getElementById('config-modal').classList.add('active');
}
}
} catch (error) {
alert('加载配置失败: ' + error.message);
}
}
// 加载实体用于编辑回显
async function loadEntitiesForEdit(splitType, selectedValues) {
const select = document.getElementById('split-value');
if (!splitType) {
select.innerHTML = '<option value="">请先选择拆分方式</option>';
return;
}
const entityType = splitType === 'company_id' ? 'company' :
splitType === 'user_id' ? 'user' :
splitType === 'station_id' ? 'station' : '';
select.innerHTML = '<option value="">加载中...</option>';
try {
// 先加载第一页数据
const response = await fetch(`/api/entities?type=${entityType}&page=1&page_size=${entityPageSize}`);
const result = await response.json();
if (result.success) {
const entities = result.data;
const total = result.total || 0;
const totalPages = Math.ceil(total / entityPageSize);
select.innerHTML = entities.map(e => {
let value = '';
let name = '';
let typeLabel = '';
if (splitType === 'company_id') {
value = e.company_id;
name = e.company_name;
} else if (splitType === 'user_id') {
value = e.user_id;
name = e.user_name;
typeLabel = e.order_type === 3 ? '(企业用户)' : '(普通用户)';
} else if (splitType === 'station_id') {
value = e.station_id;
name = e.station_name;
}
const isSelected = selectedValues.includes(String(value));
return `<option value="${value}" data-name="${name}" ${isSelected ? 'selected' : ''}>${name} ${typeLabel}</option>`;
}).join('');
// 更新分页UI
currentEntityType = entityType;
entityPage = 1;
updatePagination(totalPages, total);
}
} catch (error) {
console.error('加载列表失败:', error);
select.innerHTML = '<option value="">加载失败</option>';
}
}
// 复制配置
async function copyConfig(configId) {
try {
const response = await fetch(`/api/config/${configId}/copy`, {
method: 'POST'
});
const result = await response.json();
if (result.success) {
alert('配置复制成功!');
loadConfigs();
} else {
alert('复制失败: ' + result.error);
}
} catch (error) {
console.error('复制配置失败:', error);
alert('复制失败,请稍后重试');
}
}
// 删除配置
async function deleteConfig(configId) {
if (!confirm('确定要删除这个配置吗?')) {
return;
}
try {
const response = await fetch(`/api/config/${configId}`, {
method: 'DELETE'
});
const result = await response.json();
if (result.success) {
alert('配置删除成功');
loadConfigs();
} else {
alert('删除失败: ' + result.message);
}
} catch (error) {
alert('删除失败: ' + error.message);
}
}
// 启用/禁用配置
async function toggleConfig(configId) {
try {
const response = await fetch(`/api/config/${configId}/toggle`, {
method: 'POST'
});
const result = await response.json();
if (result.success) {
loadConfigs();
} else {
alert('操作失败: ' + result.message);
}
} catch (error) {
alert('操作失败: ' + error.message);
}
}
// 移动配置
async function moveConfig(configId, direction) {
try {
const response = await fetch(`/api/config/${configId}/move`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ direction })
});
const result = await response.json();
if (result.success) {
loadConfigs();
} else {
alert('移动失败: ' + result.message);
}
} catch (error) {
alert('移动失败: ' + error.message);
}
}
// 关闭模态框
function closeModal() {
document.getElementById('config-modal').classList.remove('active');
}
// 加载生成配置列表
async function loadGenerateConfigs() {
try {
const response = await fetch('/api/config');
const result = await response.json();
if (result.success) {
const select = document.getElementById('generate-config');
select.innerHTML = '<option value="">请选择配置</option>' +
result.data.map(c => `<option value="${c.id}">${c.config_name}</option>`).join('');
}
} catch (error) {
console.error('加载配置失败:', error);
}
}
// 设置预设时间
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);
}
// 生成日报
async function generateReport() {
const configId = document.getElementById('generate-config').value;
const startTime = document.getElementById('start-time').value;
const endTime = document.getElementById('end-time').value;
if (!configId) {
alert('请选择配置');
return;
}
if (!startTime || !endTime) {
alert('请选择时间范围');
return;
}
const resultDiv = document.getElementById('generate-result');
resultDiv.innerHTML = '<div class="alert alert-info">正在生成日报,请稍候...</div>';
try {
const response = await fetch('/api/report/generate', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
config_id: parseInt(configId),
start_time: startTime.replace('T', ' ') + ':00',
end_time: endTime.replace('T', ' ') + ':00'
})
});
const result = await response.json();
if (result.success) {
resultDiv.innerHTML = `
<div class="alert alert-success">
<strong>✓ 日报生成成功!</strong><br><br>
<div style="margin-left: 20px;">
<strong>统计信息:</strong><br>
• 订单数量: ${result.total_orders} 条<br>
• 总金额: ¥${result.total_amount.toFixed(2)}<br>
${result.sum_results && Object.keys(result.sum_results).length > 0 ?
'<strong>求和结果:</strong><br>' +
Object.entries(result.sum_results).map(([field, value]) =>
`${field}: ${typeof value === 'number' ? value.toFixed(2) : value}`
).join('<br>')
: ''}
</div>
<br>
<div style="display: flex; gap: 10px; margin-top: 15px;">
<a href="/api/download?path=${result.file_path}" target="_blank" class="btn btn-success">
📥 立即下载Excel文件
</a>
<button class="btn btn-primary" onclick="switchTab('history'); document.querySelectorAll('.tab')[2].click();">
📋 查看历史记录
</button>
</div>
<br>
<div style="font-size: 12px; color: #666; margin-top: 10px;">
💡 提示:生成的日报也会保存在"历史记录"标签页中,可以随时下载。
</div>
</div>
`;
} else {
resultDiv.innerHTML = `
<div class="alert alert-error">
<strong>✗ 生成失败</strong><br><br>
<strong>错误信息:</strong><br>
${result.message}<br><br>
<strong>可能的原因:</strong><br>
• 选择的时间范围内没有符合条件的订单<br>
• 数据库连接失败<br>
• 配置信息有误<br><br>
<strong>建议:</strong><br>
• 检查时间范围是否正确<br>
• 确认配置中的企业/用户是否有订单数据<br>
• 查看服务器日志获取详细错误信息
</div>
`;
}
} catch (error) {
resultDiv.innerHTML = `<div class="alert alert-error">生成失败: ${error.message}</div>`;
}
}
// 加载历史记录
async function loadHistory(page = 1) {
console.log('[loadHistory] 开始加载page:', page);
currentPage = page;
const tbody = document.getElementById('history-list');
if (tbody) {
tbody.innerHTML = '<tr><td colspan="8" class="loading">加载中...</td></tr>';
}
try {
console.log('[loadHistory] 发送请求...');
const response = await fetch(`/api/report/history?page=${page}&page_size=${pageSize}`);
console.log('[loadHistory] 响应状态:', response.status);
const result = await response.json();
console.log('[loadHistory] 响应数据:', result);
if (result.success) {
console.log('[loadHistory] 渲染历史记录...');
renderHistory(result.data.list);
renderPagination(result.data.total, page);
} else {
console.error('[loadHistory] API返回失败:', result.error);
if (tbody) {
tbody.innerHTML = '<tr><td colspan="8" class="empty">加载失败: ' + (result.error || '未知错误') + '</td></tr>';
}
}
} catch (error) {
console.error('[loadHistory] 错误:', error);
if (tbody) {
tbody.innerHTML = '<tr><td colspan="8" class="empty">加载失败: ' + error.message + '</td></tr>';
}
}
}
// 渲染历史记录
function renderHistory(history) {
const tbody = document.getElementById('history-list');
if (history.length === 0) {
tbody.innerHTML = '<tr><td colspan="8" class="empty">暂无历史记录<br><br>生成日报后,记录会显示在这里</td></tr>';
return;
}
tbody.innerHTML = history.map(item => {
// 调试信息
console.log('历史记录项:', item);
console.log(' status:', item.status, '类型:', typeof item.status);
console.log(' file_path:', item.file_path, '类型:', typeof item.file_path);
console.log(' 是否有文件:', !!(item.status === 1 && item.file_path));
const hasFile = item.status === 1 && item.file_path && item.file_path.trim() !== '';
return `
<tr>
<td><input type="checkbox" class="history-checkbox" value="${item.id}"></td>
<td>${formatDateDisplay(item.report_date)}</td>
<td style="font-size: 12px;">${formatDateTimeDisplay(item.start_time)}<br>至<br>${formatDateTimeDisplay(item.end_time)}</td>
<td>${item.config_name || '-'}</td>
<td>${item.split_name || item.split_value}</td>
<td><strong>${item.total_orders}</strong></td>
<td><strong>¥${parseFloat(item.total_amount || 0).toFixed(2)}</strong></td>
<td>
<span class="status-badge ${item.status === 1 ? 'status-active' : 'status-inactive'}">
${item.status === 1 ? '✓ 成功' : '✗ 失败'}
</span>
</td>
<td>
${hasFile ?
`<a href="/api/download?path=${encodeURIComponent(item.file_path)}" target="_blank" class="btn btn-sm btn-success">📥 下载</a>` :
`<span style="color: #999;" title="文件路径: ${item.file_path || '无'}">-</span>`}
<button onclick="deleteHistory(${item.id})" class="btn btn-sm btn-danger" style="margin-left: 5px;">🗑️ 删除</button>
</td>
</tr>
`}).join('');
}
// 渲染分页
function renderPagination(total, currentPage) {
const totalPages = Math.ceil(total / pageSize);
const pagination = document.getElementById('pagination');
if (totalPages <= 1) {
pagination.innerHTML = '';
return;
}
pagination.innerHTML = `
<button ${currentPage === 1 ? 'disabled' : ''} onclick="loadHistory(${currentPage - 1})">上一页</button>
<span>第 ${currentPage} / ${totalPages} 页,共 ${total} 条</span>
<button ${currentPage === totalPages ? 'disabled' : ''} onclick="loadHistory(${currentPage + 1})">下一页</button>
`;
}
// 刷新历史记录
function refreshHistory() {
loadHistory(currentPage);
}
// 全选/取消全选
function toggleSelectAll() {
const selectAll = document.getElementById('select-all');
const checkboxes = document.querySelectorAll('.history-checkbox');
checkboxes.forEach(cb => cb.checked = selectAll.checked);
}
// 获取选中的ID列表
function getSelectedIds() {
const checkboxes = document.querySelectorAll('.history-checkbox:checked');
return Array.from(checkboxes).map(cb => parseInt(cb.value));
}
// 批量删除
async function batchDeleteHistory() {
const ids = getSelectedIds();
if (ids.length === 0) {
alert('请先选择要删除的记录');
return;
}
if (!confirm(`确定要删除选中的 ${ids.length} 条记录吗?`)) {
return;
}
try {
const response = await fetch('/api/report/history/batch-delete', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ ids })
});
const result = await response.json();
if (result.success) {
alert(`成功删除 ${result.deleted_count} 条记录`);
loadHistory(currentPage);
} else {
alert('批量删除失败: ' + (result.error || '未知错误'));
}
} catch (error) {
alert('批量删除失败: ' + error.message);
}
}
// 批量下载
function batchDownloadHistory() {
const ids = getSelectedIds();
if (ids.length === 0) {
alert('请先选择要下载的记录');
return;
}
// 逐个下载
ids.forEach(id => {
window.open(`/api/download?id=${id}`, '_blank');
});
}
// 按日期查询
function queryHistoryByDate() {
const startDate = document.getElementById('query-start-date').value;
const endDate = document.getElementById('query-end-date').value;
if (!startDate && !endDate) {
alert('请至少选择一个日期');
return;
}
// 重新加载历史记录,带上日期参数
loadHistoryWithDate(1, startDate, endDate);
}
// 清除日期查询
function clearDateQuery() {
document.getElementById('query-start-date').value = '';
document.getElementById('query-end-date').value = '';
loadHistory(1);
}
// 带日期参数加载历史记录
async function loadHistoryWithDate(page, startDate, endDate) {
try {
let url = `/api/report/history?page=${page}&page_size=10`;
if (startDate) url += `&start_date=${startDate}`;
if (endDate) url += `&end_date=${endDate}`;
const response = await fetch(url);
const result = await response.json();
if (result.success) {
renderHistory(result.data.list);
renderPagination(result.data.total, page);
} else {
alert('加载失败: ' + (result.error || '未知错误'));
}
} catch (error) {
alert('加载失败: ' + error.message);
}
}
// 初始化
loadConfigs();
loadFields();
</script>
</body>
</html>