上传源代码版本

This commit is contained in:
Im-Jenisson
2026-06-01 16:30:29 +08:00
commit b2a9b7d3c2
462 changed files with 104365 additions and 0 deletions

View File

@@ -0,0 +1,653 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>订单指标日汇总 - 系统仪表盘</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=Noto+Sans+SC:wght@300;400;500;600;700&display=swap" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/jquery@3.7.1/dist/jquery.min.js"></script>
<style>
:root {
--primary: #2563eb;
--primary-dark: #1d4ed8;
--success: #10b981;
--warning: #f59e0b;
--danger: #ef4444;
--bg-primary: #f8fafc;
--text-primary: #1e293b;
--text-secondary: #475569;
--border: #e2e8f0;
}
* {
font-family: 'Inter', 'Noto Sans SC', -apple-system, BlinkMacSystemFont, sans-serif;
}
body {
background: linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%);
min-height: 100vh;
color: var(--text-primary);
}
.container-main {
max-width: 1400px;
padding: 32px 24px;
margin: 0 auto;
}
.page-header {
text-align: center;
margin-bottom: 32px;
padding: 28px 0;
background: linear-gradient(135deg, var(--primary) 0%, var(--primary-dark) 100%);
border-radius: 12px;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
color: white;
}
.page-header h1 {
font-size: 1.8rem;
font-weight: 600;
margin: 0;
}
.page-header p {
color: rgba(255,255,255,0.85);
margin: 8px 0 0 0;
font-size: 0.95rem;
}
.query-section {
background: white;
border-radius: 8px;
padding: 24px;
margin-bottom: 32px;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
}
.query-controls {
display: flex;
gap: 16px;
align-items: flex-end;
flex-wrap: wrap;
}
.form-group-inline {
flex: 0 1 auto;
min-width: 200px;
}
.form-label {
font-weight: 500;
margin-bottom: 8px;
color: var(--text-primary);
display: block;
}
.form-control, .form-select {
padding: 10px 12px;
border: 1px solid var(--border);
border-radius: 6px;
font-size: 0.95rem;
transition: all 150ms ease;
width: 100%;
}
.form-control:focus, .form-select:focus {
border-color: var(--primary);
box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.1);
outline: none;
}
.btn-query {
padding: 10px 28px;
background: var(--primary);
color: white;
border: none;
border-radius: 6px;
font-weight: 500;
cursor: pointer;
transition: all 150ms ease;
}
.btn-query:hover {
background: var(--primary-dark);
transform: translateY(-2px);
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
}
.btn-refresh {
padding: 10px 16px;
background: #e2e8f0;
color: var(--text-primary);
border: none;
border-radius: 6px;
font-weight: 500;
cursor: pointer;
transition: all 150ms ease;
}
.btn-refresh:hover {
background: #cbd5e1;
}
.loading-spinner {
text-align: center;
padding: 40px;
}
.spinner {
border: 4px solid var(--bg-primary);
border-top: 4px solid var(--primary);
border-radius: 50%;
width: 50px;
height: 50px;
animation: spin 1s linear infinite;
margin: 0 auto 16px;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.alert-custom {
padding: 16px;
border-radius: 6px;
margin-bottom: 16px;
display: flex;
align-items: flex-start;
gap: 12px;
}
.alert-error {
background: #fef2f2;
color: #7f1d1d;
border-left: 4px solid var(--danger);
}
.header-info {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 24px;
padding: 16px;
background: var(--bg-primary);
border-radius: 6px;
}
.header-date {
font-size: 1.2rem;
font-weight: 600;
color: var(--primary);
}
.header-time {
font-size: 0.9rem;
color: var(--text-secondary);
}
.metrics-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
gap: 16px;
margin-bottom: 24px;
}
.metric-card {
background: white;
border: 1px solid var(--border);
border-radius: 8px;
padding: 20px;
text-align: center;
transition: all 150ms ease;
}
.metric-card:hover {
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
transform: translateY(-4px);
}
.metric-card.large {
grid-column: span 2;
min-width: unset;
}
@media (max-width: 768px) {
.metric-card.large {
grid-column: span 1;
}
}
.metric-label {
font-size: 0.85rem;
color: var(--text-secondary);
margin-bottom: 12px;
}
.metric-value {
font-size: 2.2rem;
font-weight: 700;
color: var(--primary);
margin-bottom: 4px;
}
.metric-card.large .metric-value {
font-size: 2.8rem;
}
.metric-unit {
font-size: 0.8rem;
color: var(--text-secondary);
font-weight: 400;
}
.metric-card.success {
border-color: var(--success);
background: linear-gradient(135deg, rgba(16, 185, 129, 0.05) 0%, rgba(16, 185, 129, 0.02) 100%);
}
.metric-card.warning {
border-color: var(--warning);
background: linear-gradient(135deg, rgba(245, 158, 11, 0.05) 0%, rgba(245, 158, 11, 0.02) 100%);
}
.metric-card.danger {
border-color: var(--danger);
background: linear-gradient(135deg, rgba(239, 68, 68, 0.05) 0%, rgba(239, 68, 68, 0.02) 100%);
}
.section-title {
font-size: 1.1rem;
font-weight: 600;
margin: 28px 0 16px 0;
color: var(--text-primary);
border-bottom: 2px solid var(--primary);
padding-bottom: 8px;
}
.table-container {
background: white;
border-radius: 8px;
overflow: hidden;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
margin-bottom: 24px;
}
.table-custom {
margin: 0;
font-size: 0.95rem;
}
.table-custom thead th {
background: var(--bg-primary);
border-color: var(--border);
font-weight: 600;
color: var(--text-primary);
padding: 16px;
}
.table-custom tbody td {
padding: 16px;
border-color: var(--border);
}
.table-custom tbody tr:hover {
background: var(--bg-primary);
}
.details-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
gap: 12px;
}
.detail-item {
background: white;
border: 1px solid var(--border);
border-radius: 6px;
padding: 12px;
}
.detail-label {
font-size: 0.8rem;
color: var(--text-secondary);
margin-bottom: 4px;
}
.detail-value {
font-size: 1.3rem;
font-weight: 600;
color: var(--primary);
}
.no-data {
text-align: center;
padding: 60px 20px;
color: var(--text-secondary);
}
.no-data-icon {
font-size: 3rem;
margin-bottom: 16px;
}
@media (max-width: 768px) {
.query-controls {
flex-direction: column;
}
.form-group-inline {
width: 100%;
}
.btn-query {
width: 100%;
}
.header-info {
flex-direction: column;
text-align: center;
gap: 8px;
}
.metrics-grid {
grid-template-columns: 1fr;
}
.metric-card.large {
grid-column: span 1;
}
}
</style>
</head>
<body>
<div class="container-main">
<div class="page-header">
<h1>📊 订单处理指标日汇总</h1>
<p>一键查询完整的日统计指标</p>
</div>
<!-- 查询控制区 -->
<div class="query-section">
<div class="query-controls">
<div class="form-group-inline">
<label class="form-label">环境选择</label>
<select id="environment" class="form-select">
<option value="local">本地环境</option>
<option value="test" selected>测试环境</option>
<option value="production">生产环境</option>
</select>
</div>
<div class="form-group-inline">
<label class="form-label">查询日期</label>
<input type="date" id="dashboardDate" class="form-control">
</div>
<div class="form-group-inline">
<button class="btn-query" onclick="queryDailyDashboard()">📊 查询汇总</button>
</div>
<div class="form-group-inline">
<button class="btn-refresh" onclick="refreshCurrentData()" style="display:none;" id="refreshBtn">🔄 刷新</button>
</div>
</div>
</div>
<!-- 结果展示区 -->
<div id="resultContainer"></div>
</div>
<script>
// 配置环境
const environments = {
local: 'http://localhost:5002',
test: 'http://172.232.21.79:5002',
production: 'https://lr.tooexp.com'
};
function getBaseUrl() {
const env = document.getElementById('environment').value;
return environments[env] || environments.test;
}
let currentDate = null;
// 初始化日期
function initDate() {
const today = new Date();
const year = today.getFullYear();
const month = String(today.getMonth() + 1).padStart(2, '0');
const day = String(today.getDate()).padStart(2, '0');
document.getElementById('dashboardDate').value = `${year}-${month}-${day}`;
}
// 显示加载状态
function showLoading() {
console.log('显示加载状态');
document.getElementById('resultContainer').innerHTML = `
<div class="loading-spinner">
<div class="spinner"></div>
<p>正在加载数据...</p>
</div>
`;
}
// 显示错误
function showError(message) {
console.error('显示错误信息:', message);
document.getElementById('resultContainer').innerHTML = `
<div class="alert-custom alert-error">
<span>❌</span>
<div><strong>错误</strong><br/>${message}</div>
</div>
`;
}
// JSONP 请求
function jsonpRequest(url, callbackName) {
$.ajax({
url: url,
type: 'GET',
dataType: 'jsonp',
jsonp: 'callback',
jsonpCallback: callbackName,
cache: false,
timeout: 15000,
success: function(response) {
console.log('AJAX success - 响应接收:', response);
},
error: function(xhr, status, error) {
console.error('AJAX 失败:', status, error);
delete window[callbackName];
showError('请求失败: ' + (error || status));
}
});
}
// 查询日汇总
function queryDailyDashboard() {
const date = document.getElementById('dashboardDate').value;
if (!date) {
showError('请选择日期');
return;
}
currentDate = date;
showLoading();
const baseUrl = getBaseUrl();
const url = baseUrl + '/api/metrics/daily-dashboard?date=' + date;
console.log('查询仪表盘数据', { date, env: document.getElementById('environment').value, url: url });
// 使用JSONP方式避免CORS问题
const callbackName = 'dashboardCallback_' + Date.now();
window[callbackName] = function(response) {
console.log('收到响应:', response);
handleDashboardResponse(response);
delete window[callbackName];
};
$.ajax({
url: url,
type: 'GET',
dataType: 'jsonp',
jsonp: 'callback',
jsonpCallback: callbackName,
cache: false,
timeout: 60000,
success: function(response) {
// JSONP成功处理已在回调中进行
},
error: function(xhr, status, error) {
console.error('请求失败:', status, error);
showError('请求失败: ' + (error || status));
delete window[callbackName];
}
});
}
// 处理响应
function handleDashboardResponse(response) {
if (response && response.success) {
displayDashboard(response.data);
} else {
showError(response?.error || '查询失败');
}
}
// 生成颜色类
function getMetricClass(rate) {
if (typeof rate === 'string') {
const percent = parseFloat(rate);
if (percent >= 95) return 'success';
if (percent >= 85) return 'warning';
return 'danger';
}
return '';
}
// 显示仪表盘
function displayDashboard(data) {
const date = data.date;
const summary = data.summary || {};
const breakdown = data.breakdown || {};
const details = data.details || {};
let html = `
<div class="header-info">
<div>
<span class="header-date">📅 ${date}</span>
</div>
<div class="header-time">
更新于: ${new Date().toLocaleString('zh-CN')}
</div>
</div>
<!-- 核心指标 -->
<h2 class="section-title">🎯 核心指标</h2>
<div class="metrics-grid">
<div class="metric-card large">
<div class="metric-label">当天新增换单数</div>
<div class="metric-value">${summary.dailyNewReplaceCount || 0}</div>
</div>
<div class="metric-card large">
<div class="metric-label">当天应该换单数</div>
<div class="metric-value">${summary.dailyShouldReplaceCount || 0}</div>
</div>
<div class="metric-card large">
<div class="metric-label">当日换单完成数</div>
<div class="metric-value">${summary.dailySuccessCount || 0}</div>
</div>
</div>
<!-- 完成率 -->
<h2 class="section-title">📈 完成率指标</h2>
<div class="metrics-grid">
<div class="metric-card ${getMetricClass(summary.dailyCompletionRate)}">
<div class="metric-label">当日完成率</div>
<div class="metric-value">${summary.dailyCompletionRate || '0%'}</div>
</div>
<div class="metric-card ${getMetricClass(summary.rate24Hour)}">
<div class="metric-label">24小时完成率</div>
<div class="metric-value">${summary.rate24Hour || '0%'}</div>
</div>
</div>
<!-- 分时段统计 -->
<h2 class="section-title">⏰ 分时段统计</h2>
<div class="table-container">
<table class="table table-custom">
<thead>
<tr>
<th style="width: 20%;">时段</th>
<th style="width: 20%;">到仓数</th>
<th style="width: 20%;">完成数</th>
<th style="width: 40%;">完成率</th>
</tr>
</thead>
<tbody>
<tr>
<td>🌅 16点前</td>
<td>${breakdown.beforeNoon?.arrived || 0}</td>
<td>${breakdown.beforeNoon?.passed || 0}</td>
<td><span style="background: ${getMetricClass(breakdown.beforeNoon?.rate) === 'success' ? '#d1fae5' : getMetricClass(breakdown.beforeNoon?.rate) === 'warning' ? '#fef3c7' : '#fee2e2'}; padding: 4px 8px; border-radius: 4px;">${breakdown.beforeNoon?.rate || '0%'}</span></td>
</tr>
<tr>
<td>🌆 16点后</td>
<td>${breakdown.afternoon?.arrived || 0}</td>
<td>${breakdown.afternoon?.passed || 0}</td>
<td><span style="background: ${getMetricClass(breakdown.afternoon?.rate) === 'success' ? '#d1fae5' : getMetricClass(breakdown.afternoon?.rate) === 'warning' ? '#fef3c7' : '#fee2e2'}; padding: 4px 8px; border-radius: 4px;">${breakdown.afternoon?.rate || '0%'}</span></td>
</tr>
</tbody>
</table>
</div>
<!-- 详细指标 -->
<h2 class="section-title">📊 详细指标</h2>
<div class="details-grid">
<div class="detail-item">
<div class="detail-label">当日STOP数</div>
<div class="detail-value">${details.dailyStop || 0}</div>
</div>
<div class="detail-item">
<div class="detail-label">当日标签推送</div>
<div class="detail-value">${details.dailyLabelPush || 0}</div>
</div>
<div class="detail-item">
<div class="detail-label">当日扫描数</div>
<div class="detail-value">${details.dailyScanCount || 0}</div>
</div>
<div class="detail-item">
<div class="detail-label">当日失败数</div>
<div class="detail-value">${details.dailyFailure || 0}</div>
</div>
<div class="detail-item">
<div class="detail-label">累计未完成</div>
<div class="detail-value">${details.cumulativeTotal || 0}</div>
</div>
</div>
`;
document.getElementById('resultContainer').innerHTML = html;
document.getElementById('refreshBtn').style.display = 'inline-block';
}
// 刷新当前数据
function refreshCurrentData() {
if (currentDate) {
queryDailyDashboard();
}
}
// 页面加载
$(document).ready(function() {
initDate();
});
</script>
</body>
</html>