问题:日期时间显示为中文格式(如"2026年7月9日 上午10:55:11") 修复: - 添加 formatDateTimeDisplay 函数,将日期时间转换为纯数字格式 - 在历史记录渲染中使用该函数 - 格式:YYYY-MM-DD HH:MM:SS(如"2026-07-09 10:55:11") 现在日期时间会显示为纯数字格式。 Coze-Commit-Type: user Coze-User-ID: 3722323274763196 Coze-Conversation-ID: 9894087
1058 lines
38 KiB
HTML
1058 lines
38 KiB
HTML
<!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">
|
||
<button class="btn btn-primary" onclick="refreshHistory()">刷新</button>
|
||
</div>
|
||
|
||
<div class="card">
|
||
<table>
|
||
<thead>
|
||
<tr>
|
||
<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="8" 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()">×</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>
|
||
</select>
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label>选择企业/用户 *</label>
|
||
<select id="split-value" class="form-control">
|
||
<option value="">请先选择拆分方式</option>
|
||
</select>
|
||
</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-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('');
|
||
}
|
||
|
||
// 加载企业/用户列表
|
||
async function loadEntities() {
|
||
const splitType = document.getElementById('split-type').value;
|
||
const select = document.getElementById('split-value');
|
||
|
||
if (!splitType) {
|
||
select.innerHTML = '<option value="">请先选择拆分方式</option>';
|
||
return;
|
||
}
|
||
|
||
select.innerHTML = '<option value="">加载中...</option>';
|
||
|
||
try {
|
||
const response = await fetch(`/api/entities?type=${splitType === 'company_id' ? 'company' : 'user'}`);
|
||
const result = await response.json();
|
||
|
||
if (result.success) {
|
||
const entities = result.data;
|
||
select.innerHTML = '<option value="">请选择</option>' +
|
||
entities.map(e => {
|
||
const value = splitType === 'company_id' ? e.company_id : e.user_id;
|
||
const name = splitType === 'company_id' ? e.company_name : e.user_name;
|
||
const type = e.order_type === 3 ? '(企业用户)' : '(普通用户)';
|
||
return `<option value="${value}" data-name="${name}">${name} ${splitType === 'user_id' ? type : ''}</option>`;
|
||
}).join('');
|
||
}
|
||
} catch (error) {
|
||
console.error('加载列表失败:', error);
|
||
}
|
||
}
|
||
|
||
// 保存配置
|
||
async function saveConfig() {
|
||
const configName = document.getElementById('config-name').value;
|
||
const splitType = document.getElementById('split-type').value;
|
||
const splitValueSelect = document.getElementById('split-value');
|
||
const splitValue = splitValueSelect.value;
|
||
const splitName = splitValueSelect.options[splitValueSelect.selectedIndex].dataset.name || '';
|
||
|
||
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;
|
||
|
||
await loadEntities();
|
||
document.getElementById('split-value').value = config.split_value;
|
||
|
||
await loadFields();
|
||
renderFields(config.selected_fields, config.sum_fields);
|
||
|
||
document.getElementById('config-modal').classList.add('active');
|
||
}
|
||
}
|
||
} catch (error) {
|
||
alert('加载配置失败: ' + error.message);
|
||
}
|
||
}
|
||
|
||
// 删除配置
|
||
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);
|
||
}
|
||
|
||
// 生成日报
|
||
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>${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>`}
|
||
</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);
|
||
}
|
||
|
||
// 初始化
|
||
loadConfigs();
|
||
loadFields();
|
||
</script>
|
||
</body>
|
||
</html> |