Files
LabelChange-server/metrics-dashboard.html
2026-06-01 16:30:29 +08:00

833 lines
31 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>
<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>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/xlsx@0.18.5/dist/xlsx.full.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/chart.js@3.9.1/dist/chart.min.js"></script>
<style>
:root {
--primary: #2563eb;
--primary-dark: #1d4ed8;
--primary-light: #3b82f6;
--success: #10b981;
--warning: #f59e0b;
--danger: #ef4444;
--bg-primary: #f8fafc;
--bg-secondary: #ffffff;
--text-primary: #1e293b;
--text-secondary: #475569;
--border: #e2e8f0;
--shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
}
* {
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: 40px;
padding: 32px 0;
background: linear-gradient(135deg, var(--primary) 0%, var(--primary-dark) 100%);
border-radius: 12px;
box-shadow: var(--shadow-md);
color: white;
}
.page-header h1 {
font-size: 2rem;
font-weight: 600;
margin: 0;
}
.page-header p {
color: rgba(255,255,255,0.85);
margin: 8px 0 0 0;
}
.card-custom {
border: none;
border-radius: 8px;
box-shadow: var(--shadow-md);
margin-bottom: 24px;
overflow: hidden;
}
.card-header-custom {
background: linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%);
border-bottom: 1px solid var(--border);
padding: 20px 24px;
}
.card-header-custom h3 {
font-size: 1.1rem;
font-weight: 600;
margin: 0;
color: var(--text-primary);
}
.card-body-custom {
padding: 28px;
}
.form-group-row {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 16px;
margin-bottom: 16px;
}
.form-group {
margin-bottom: 0;
}
.form-label {
font-weight: 500;
margin-bottom: 8px;
color: var(--text-primary);
}
.form-control, .form-select {
padding: 10px 12px;
border: 1px solid var(--border);
border-radius: 6px;
font-size: 0.95rem;
transition: all 150ms ease;
}
.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-custom {
padding: 10px 24px;
border-radius: 6px;
font-weight: 500;
transition: all 150ms ease;
border: none;
}
.btn-primary-custom {
background: var(--primary);
color: white;
}
.btn-primary-custom:hover {
background: var(--primary-dark);
transform: translateY(-2px);
box-shadow: var(--shadow-md);
}
.btn-secondary-custom {
background: #e2e8f0;
color: var(--text-primary);
}
.btn-secondary-custom:hover {
background: #cbd5e1;
}
.result-container {
margin-top: 24px;
}
.metric-card {
background: white;
border: 1px solid var(--border);
border-radius: 8px;
padding: 20px;
margin-bottom: 16px;
display: flex;
justify-content: space-between;
align-items: center;
}
.metric-label {
font-size: 0.9rem;
color: var(--text-secondary);
margin-bottom: 8px;
}
.metric-value {
font-size: 2rem;
font-weight: 700;
color: var(--primary);
}
.metric-unit {
font-size: 0.85rem;
color: var(--text-secondary);
margin-left: 8px;
}
.metrics-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 16px;
margin-bottom: 24px;
}
.alert-custom {
padding: 16px;
border-radius: 6px;
margin-bottom: 16px;
display: flex;
align-items: flex-start;
gap: 12px;
}
.alert-success-custom {
background: #ecfdf5;
color: #065f46;
border-left: 4px solid var(--success);
}
.alert-error-custom {
background: #fef2f2;
color: #7f1d1d;
border-left: 4px solid var(--danger);
}
.alert-warning-custom {
background: #fffbeb;
color: #78350f;
border-left: 4px solid var(--warning);
}
.table-custom {
margin-top: 16px;
}
.table-custom thead th {
background: var(--bg-primary);
border-color: var(--border);
font-weight: 600;
color: var(--text-primary);
}
.table-custom tbody tr:hover {
background: var(--bg-primary);
}
.badge-custom {
padding: 6px 12px;
border-radius: 20px;
font-size: 0.85rem;
font-weight: 500;
}
.badge-success-custom {
background: #d1fae5;
color: #065f46;
}
.badge-warning-custom {
background: #fef3c7;
color: #92400e;
}
.badge-danger-custom {
background: #fee2e2;
color: #7f1d1d;
}
.loading-spinner {
display: none;
text-align: center;
padding: 20px;
}
.spinner {
border: 4px solid var(--bg-primary);
border-top: 4px solid var(--primary);
border-radius: 50%;
width: 40px;
height: 40px;
animation: spin 1s linear infinite;
margin: 0 auto;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.chart-container {
position: relative;
height: 300px;
margin-top: 24px;
}
@media (max-width: 768px) {
.form-group-row {
grid-template-columns: 1fr;
}
.metric-card {
flex-direction: column;
align-items: flex-start;
}
.metrics-grid {
grid-template-columns: 1fr;
}
}
</style>
</head>
<body>
<div class="container-main">
<div class="page-header">
<h1>📊 订单指标查询系统</h1>
<p>实时查看订单处理、换单完成率和其他关键指标</p>
</div>
<!-- 环境选择 -->
<div class="form-group-row" style="margin-bottom: 24px;">
<div class="form-group">
<label class="form-label">🌍 环境选择</label>
<select id="environment" class="form-select">
<option value="local">本地环境 (http://localhost:5002)</option>
<option value="test" selected>测试环境 (http://172.232.21.79:5002)</option>
<option value="production">生产环境 (https://lr.tooexp.com)</option>
</select>
</div>
</div>
<!-- 标签率查询 -->
<div class="card card-custom">
<div class="card-header card-header-custom">
<h3>🏷️ 交接单标签率查询</h3>
</div>
<div class="card-body card-body-custom">
<div class="form-group-row">
<div class="form-group">
<label class="form-label">交接单号</label>
<input type="text" id="handoverNumber" class="form-control" placeholder="输入交接单号...">
</div>
<div class="form-group" style="align-self: flex-end;">
<button class="btn btn-custom btn-primary-custom" onclick="queryLabelRate()">查询标签率</button>
</div>
</div>
<div id="labelRateResult" class="result-container"></div>
</div>
</div>
<!-- 订单考核指标查询 -->
<div class="card card-custom">
<div class="card-header card-header-custom">
<h3>📋 订单考核指标查询</h3>
</div>
<div class="card-body card-body-custom">
<div class="form-group-row">
<div class="form-group">
<label class="form-label">中性面单号</label>
<input type="text" id="waybillNumber" class="form-control" placeholder="输入中性面单号...">
</div>
<div class="form-group" style="align-self: flex-end;">
<button class="btn btn-custom btn-primary-custom" onclick="queryOrderAssessment()">查询订单指标</button>
</div>
</div>
<div id="orderAssessmentResult" class="result-container"></div>
</div>
</div>
<!-- 每日汇总查询 -->
<div class="card card-custom">
<div class="card-header card-header-custom">
<h3>📊 每日汇总统计</h3>
</div>
<div class="card-body card-body-custom">
<div class="form-group-row">
<div class="form-group">
<label class="form-label">日期 (格式: yyyy-MM-dd)</label>
<input type="date" id="summaryDate" class="form-control">
</div>
<div class="form-group" style="align-self: flex-end;">
<button class="btn btn-custom btn-primary-custom" onclick="queryDailySummary()">查询每日汇总</button>
</div>
</div>
<div id="dailySummaryResult" class="result-container"></div>
</div>
</div>
<!-- 日期范围汇总查询 -->
<div class="card card-custom">
<div class="card-header card-header-custom">
<h3>📈 日期范围统计</h3>
</div>
<div class="card-body card-body-custom">
<div class="form-group-row">
<div class="form-group">
<label class="form-label">开始日期</label>
<input type="date" id="startDate" class="form-control">
</div>
<div class="form-group">
<label class="form-label">结束日期</label>
<input type="date" id="endDate" class="form-control">
</div>
<div class="form-group" style="align-self: flex-end;">
<button class="btn btn-custom btn-primary-custom" onclick="queryDateRangeSummaries()">查询统计</button>
</div>
</div>
<div id="dateRangeSummariesResult" class="result-container"></div>
</div>
</div>
<!-- 完成率查询 -->
<div class="card card-custom">
<div class="card-header card-header-custom">
<h3>⚡ 完成率统计</h3>
</div>
<div class="card-body card-body-custom">
<div class="form-group-row">
<div class="form-group">
<label class="form-label">日期 (格式: yyyy-MM-dd)</label>
<input type="date" id="rateDate" class="form-control">
</div>
<div class="form-group" style="align-self: flex-end;">
<button class="btn btn-custom btn-primary-custom" onclick="query24HRate()">查询24小时率</button>
<button class="btn btn-custom btn-secondary-custom" onclick="queryDailyRate()" style="margin-left: 8px;">查询每日率</button>
</div>
</div>
<div id="rateResult" class="result-container"></div>
</div>
</div>
</div>
<script>
const proxyUrl = 'metrics-proxy.jsp';
function getEnvironment() {
return document.getElementById('environment').value;
}
function showLoading(targetId) {
$(targetId).html(`
<div class="loading-spinner" style="display: block;">
<div class="spinner"></div>
<p style="margin-top: 12px; color: var(--text-secondary);">正在加载数据...</p>
</div>
`);
}
function showError(targetId, message) {
$(targetId).html(`
<div class="alert-custom alert-error-custom">
<span>❌</span>
<div>
<strong>错误</strong><br/>
${escapeHtml(message)}
</div>
</div>
`);
}
function showSuccess(targetId, message) {
$(targetId).html(`
<div class="alert-custom alert-success-custom">
<span>✅</span>
<div>
<strong>成功</strong><br/>
${escapeHtml(message)}
</div>
</div>
`);
}
function escapeHtml(text) {
const map = {
'&': '&amp;',
'<': '&lt;',
'>': '&gt;',
'"': '&quot;',
"'": '&#039;'
};
return text.replace(/[&<>"']/g, m => map[m]);
}
function queryLabelRate() {
const handoverNumber = document.getElementById('handoverNumber').value;
if (!handoverNumber) {
showError('#labelRateResult', '请输入交接单号');
return;
}
showLoading('#labelRateResult');
$.ajax({
url: proxyUrl,
type: 'GET',
data: {
action: 'getLabelRate',
handoverNumber: handoverNumber,
env: getEnvironment()
},
success: function(response) {
const result = JSON.parse(response);
if (result.success) {
const data = result.data;
$('#labelRateResult').html(`
<div class="metrics-grid">
<div class="metric-card">
<div>
<div class="metric-label">交接单号</div>
<div class="metric-value">${data.handoverNumber}</div>
</div>
</div>
<div class="metric-card">
<div>
<div class="metric-label">总订单数</div>
<div class="metric-value">${data.totalOrderCount}</div>
</div>
</div>
<div class="metric-card">
<div>
<div class="metric-label">有标签订单</div>
<div class="metric-value">${data.labeledOrderCount}</div>
</div>
</div>
<div class="metric-card">
<div>
<div class="metric-label">标签率</div>
<div class="metric-value">${(data.labelRate * 100).toFixed(2)}<span class="metric-unit">%</span></div>
</div>
</div>
</div>
`);
} else {
showError('#labelRateResult', result.error || '查询失败');
}
},
error: function(err) {
showError('#labelRateResult', '请求失败,请检查网络连接');
}
});
}
function queryOrderAssessment() {
const waybillNumber = document.getElementById('waybillNumber').value;
if (!waybillNumber) {
showError('#orderAssessmentResult', '请输入中性面单号');
return;
}
showLoading('#orderAssessmentResult');
$.ajax({
url: proxyUrl,
type: 'GET',
data: {
action: 'getOrderAssessment',
neutralWaybillNumber: waybillNumber,
env: getEnvironment()
},
success: function(response) {
const result = JSON.parse(response);
if (result.success) {
const data = result.data;
const isCompleted = data.completedOnTime ? '✅ 已完成' : '❌ 未完成';
$('#orderAssessmentResult').html(`
<div class="metrics-grid">
<div class="metric-card">
<div>
<div class="metric-label">中性面单号</div>
<div class="metric-value">${data.neutralWaybillNumber}</div>
</div>
</div>
<div class="metric-card">
<div>
<div class="metric-label">标签率</div>
<div class="metric-value">${(data.labelRate * 100).toFixed(2)}<span class="metric-unit">%</span></div>
</div>
</div>
<div class="metric-card">
<div>
<div class="metric-label">考核时间</div>
<div class="metric-value" style="font-size: 1.2rem;">${new Date(data.assessmentTime).toLocaleString('zh-CN')}</div>
</div>
</div>
<div class="metric-card">
<div>
<div class="metric-label">完成状态</div>
<div class="metric-value" style="font-size: 1.5rem;">${isCompleted}</div>
</div>
</div>
</div>
`);
} else {
showError('#orderAssessmentResult', result.error || '查询失败');
}
},
error: function(err) {
showError('#orderAssessmentResult', '请求失败,请检查网络连接');
}
});
}
function queryDailySummary() {
const date = document.getElementById('summaryDate').value;
if (!date) {
showError('#dailySummaryResult', '请选择日期');
return;
}
showLoading('#dailySummaryResult');
$.ajax({
url: proxyUrl,
type: 'GET',
data: {
action: 'getDailySummary',
date: date,
env: getEnvironment()
},
success: function(response) {
const result = JSON.parse(response);
if (result.success) {
const data = result.data;
$('#dailySummaryResult').html(`
<div class="metrics-grid">
<div class="metric-card">
<div>
<div class="metric-label">当天新增换单</div>
<div class="metric-value">${data.dailyNewReplaceCount}</div>
</div>
</div>
<div class="metric-card">
<div>
<div class="metric-label">当日完成</div>
<div class="metric-value">${data.dailySuccessCount}</div>
</div>
</div>
<div class="metric-card">
<div>
<div class="metric-label">当日应该换单</div>
<div class="metric-value">${data.dailyShouldReplaceCount}</div>
</div>
</div>
<div class="metric-card">
<div>
<div class="metric-label">当日扫描数</div>
<div class="metric-value">${data.dailyScanCount}</div>
</div>
</div>
<div class="metric-card">
<div>
<div class="metric-label">当日STOP数</div>
<div class="metric-value">${data.dailyStopCount}</div>
</div>
</div>
<div class="metric-card">
<div>
<div class="metric-label">当日标签推送</div>
<div class="metric-value">${data.dailyLabelPushCount}</div>
</div>
</div>
<div class="metric-card">
<div>
<div class="metric-label">当日完成率</div>
<div class="metric-value">${data.dailyCompletionRate}</div>
</div>
</div>
<div class="metric-card">
<div>
<div class="metric-label">16点前到仓</div>
<div class="metric-value">${data.beforeNoonArrivedCount}</div>
</div>
</div>
</div>
`);
} else {
showError('#dailySummaryResult', result.error || '查询失败');
}
},
error: function(err) {
showError('#dailySummaryResult', '请求失败,请检查网络连接');
}
});
}
function queryDateRangeSummaries() {
const startDate = document.getElementById('startDate').value;
const endDate = document.getElementById('endDate').value;
if (!startDate || !endDate) {
showError('#dateRangeSummariesResult', '请选择开始和结束日期');
return;
}
showLoading('#dateRangeSummariesResult');
$.ajax({
url: proxyUrl,
type: 'GET',
data: {
action: 'getDailySummaries',
startDate: startDate,
endDate: endDate,
env: getEnvironment()
},
success: function(response) {
const result = JSON.parse(response);
if (result.success && result.data && result.data.length > 0) {
const data = result.data;
let html = `
<div class="alert-custom alert-success-custom">
<span>✅</span>
<div>共查询到 <strong>${data.length}</strong> 天的数据</div>
</div>
<table class="table table-custom table-striped">
<thead>
<tr>
<th>日期</th>
<th>新增换单</th>
<th>完成数</th>
<th>应换单数</th>
<th>完成率</th>
<th>扫描数</th>
<th>标签推送</th>
</tr>
</thead>
<tbody>
`;
data.forEach(item => {
html += `
<tr>
<td>${item.date.substring(0, 10)}</td>
<td>${item.dailyNewReplaceCount}</td>
<td>${item.dailySuccessCount}</td>
<td>${item.dailyShouldReplaceCount}</td>
<td><span class="badge badge-custom badge-success-custom">${item.dailyCompletionRate}</span></td>
<td>${item.dailyScanCount}</td>
<td>${item.dailyLabelPushCount}</td>
</tr>
`;
});
html += `
</tbody>
</table>
`;
$('#dateRangeSummariesResult').html(html);
} else {
showError('#dateRangeSummariesResult', '未查询到数据');
}
},
error: function(err) {
showError('#dateRangeSummariesResult', '请求失败,请检查网络连接');
}
});
}
function query24HRate() {
const date = document.getElementById('rateDate').value;
if (!date) {
showError('#rateResult', '请选择日期');
return;
}
showLoading('#rateResult');
$.ajax({
url: proxyUrl,
type: 'GET',
data: {
action: 'get24HCompletionRate',
date: date,
env: getEnvironment()
},
success: function(response) {
const result = JSON.parse(response);
if (result.success) {
const data = result.data;
$('#rateResult').html(`
<div class="alert-custom alert-success-custom">
<span>✅</span>
<div><strong>24小时完成率</strong>: ${data.rate}</div>
</div>
`);
} else {
showError('#rateResult', result.error || '查询失败');
}
},
error: function(err) {
showError('#rateResult', '请求失败,请检查网络连接');
}
});
}
function queryDailyRate() {
const date = document.getElementById('rateDate').value;
if (!date) {
showError('#rateResult', '请选择日期');
return;
}
showLoading('#rateResult');
$.ajax({
url: proxyUrl,
type: 'GET',
data: {
action: 'getDailyCompletionRate',
date: date,
env: getEnvironment()
},
success: function(response) {
const result = JSON.parse(response);
if (result.success) {
const data = result.data;
$('#rateResult').html(`
<div class="alert-custom alert-success-custom">
<span>✅</span>
<div><strong>每日完成率</strong>: ${data.rate}</div>
</div>
`);
} else {
showError('#rateResult', result.error || '查询失败');
}
},
error: function(err) {
showError('#rateResult', '请求失败,请检查网络连接');
}
});
}
// 页面加载时设置日期默认值
$(document).ready(function() {
const today = new Date().toISOString().split('T')[0];
document.getElementById('summaryDate').value = today;
document.getElementById('rateDate').value = today;
// 设置日期范围默认值
const sevenDaysAgo = new Date();
sevenDaysAgo.setDate(sevenDaysAgo.getDate() - 7);
document.getElementById('startDate').value = sevenDaysAgo.toISOString().split('T')[0];
document.getElementById('endDate').value = today;
});
</script>
</body>
</html>