refactor(report): 优化报告样式和数据加载逻辑

- 调整了报告页面的CSS样式,包括颜色、阴影和布局,以提供更专业、现代的视觉效果。
- 改进了从后端向前端JavaScript传递图表数据的方式。现在通过一个独立的`<script>`标签注入JSON字符串,而不是直接嵌入到JS代码中,这增强了鲁棒性并避免了特殊字符导致的解析错误。
- 在JavaScript中增加了对JSON解析和图表数据有效性的检查,以防止因数据格式错误或缺失导致页面渲染失败。
- 将统计模块中的耗时相关键名统一为大写格式,以提高代码一致性。
This commit is contained in:
minecraft1024a
2025-11-13 14:31:54 +08:00
parent a9f4c422b1
commit 992a1129ce
5 changed files with 50 additions and 35 deletions

View File

@@ -2,12 +2,13 @@
该模块用于生成HTML格式的统计报告。
"""
from datetime import datetime, timedelta
from typing import Any
import json
import os
from jinja2 import Environment, FileSystemLoader
from datetime import datetime, timedelta
from typing import Any
import aiofiles
from jinja2 import Environment, FileSystemLoader
from .statistic_keys import * # noqa: F403

View File

@@ -412,9 +412,9 @@ class StatisticOutputTask(AsyncTask):
(REQ_CNT_BY_MODEL, "model"),
(REQ_CNT_BY_MODULE, "module"),
]:
time_cost_key = f"time_costs_by_{items}"
avg_key = f"avg_time_costs_by_{items}"
std_key = f"std_time_costs_by_{items}"
time_cost_key = f"TIME_COST_BY_{items.upper()}"
avg_key = f"AVG_TIME_COST_BY_{items.upper()}"
std_key = f"STD_TIME_COST_BY_{items.upper()}"
for item_name in period_stats[category_key]:
time_costs = period_stats[time_cost_key].get(item_name, [])

View File

@@ -3,22 +3,20 @@ body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
margin: 0;
padding: 20px;
background-color: #F8F9FA; /* Light grey background */
color: #495057; /* Softer text color */
background-color: #f0f4f8; /* Light blue-gray background */
color: #333; /* Darker text for better contrast */
line-height: 1.6;
}
/* Main Container */
.container {
max-width: 1200px;
max-width: 95%; /* Make container almost full-width */
margin: 20px auto;
background-color: #FFFFFF; /* Pure white background */
padding: 30px;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0,0,0,0.05);
border: 1px solid #EAEAEA;
border-radius: 12px; /* Slightly more rounded corners */
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.07); /* Softer, deeper shadow */
}
/* Dashboard Layout */
.dashboard-layout {
display: flex;
@@ -56,28 +54,28 @@ h1 {
text-align: center;
font-size: 2.2em;
margin-bottom: 20px;
color: #4A90E2; /* Main blue for title */
color: #2A6CB5; /* A deeper, more professional blue */
}
h2 {
font-size: 1.5em;
margin-top: 40px;
margin-bottom: 15px;
border-bottom: 2px solid #EAEAEA;
border-bottom: 2px solid #DDE6ED; /* Lighter border color */
}
/* Info Banners */
.info-item {
background-color: #E9ECEF;
padding: 10px 15px;
border-radius: 6px;
background-color: #E9F2FA; /* Light blue background */
padding: 12px 18px;
border-radius: 8px;
margin-bottom: 20px;
font-size: 0.95em;
border: 1px solid #DEE2E6;
border: 1px solid #D1E3F4; /* Light blue border */
}
.info-item strong {
color: #4A90E2;
color: #2A6CB5; /* Deeper blue for emphasis */
}
/* Tabs */
@@ -101,12 +99,13 @@ h2 {
}
.tabs button:hover {
color: #212529;
color: #2A6CB5;
background-color: #f0f4f8; /* Subtle hover background */
}
.tabs button.active {
color: #4A90E2;
border-bottom-color: #4A90E2;
color: #2A6CB5; /* Active tab color */
border-bottom-color: #2A6CB5; /* Active tab border color */
}
.tab-content {
@@ -131,7 +130,7 @@ h2 {
padding: 20px;
border-radius: 8px;
text-align: center;
border: 1px solid #EAEAEA;
border: 1px solid #DDE6ED; /* Lighter border */
transition: all 0.3s ease;
}
@@ -166,9 +165,8 @@ th, td {
text-align: left;
border-bottom: 1px solid #EAEAEA;
}
th {
background-color: #4A90E2;
background-color: #4A90E2; /* Main theme blue */
color: white;
font-weight: bold;
font-size: 0.95em;
@@ -177,11 +175,11 @@ th {
}
tr:nth-child(even) {
background-color: #F8F9FA;
background-color: #F7FAFC; /* Very light blue for alternate rows */
}
tr:hover {
background-color: #E9ECEF;
background-color: #E9F2FA; /* Light blue for hover */
}
/* Chart Container in Sidebar */

View File

@@ -14,6 +14,10 @@
<div class="tabs">{{ tab_list }}</div>
{{ tab_content }}
</div>
<script>
const all_chart_data_json_string = `{{ all_chart_data|safe }}`;
const static_chart_data_json_string = `{{ static_chart_data|safe }}`;
</script>
<script>{{ report_js }}</script>
</body>
</html>

View File

@@ -11,9 +11,15 @@ function showTab(evt, tabName) {
}
document.addEventListener('DOMContentLoaded', function () {
// This is a placeholder for chart data which will be injected by python.
const allChartData = JSON.parse('{{ all_chart_data }}')
;
// Chart data is injected by python via the HTML template.
let allChartData = {};
try {
allChartData = JSON.parse(all_chart_data_json_string);
} catch (e) {
console.error("Failed to parse all_chart_data:", e);
console.error("Problematic all_chart_data string:", all_chart_data_json_string);
}
let currentCharts = {};
const chartConfigs = {
totalCost: { id: 'totalCostChart', title: '总花费', yAxisLabel: '花费 (¥)', dataKey: 'total_cost_data', fill: true },
@@ -73,8 +79,14 @@ document.addEventListener('DOMContentLoaded', function () {
}
// Static charts
const staticChartData = JSON.parse('{{ static_chart_data }}')
;
let staticChartData = {};
try {
staticChartData = JSON.parse(static_chart_data_json_string);
} catch (e) {
console.error("Failed to parse static_chart_data:", e);
console.error("Problematic static_chart_data string:", static_chart_data_json_string);
}
Object.keys(staticChartData).forEach(period_id => {
const providerCostData = staticChartData[period_id].provider_cost_data;
const modelCostData = staticChartData[period_id].model_cost_data;
@@ -82,7 +94,7 @@ document.addEventListener('DOMContentLoaded', function () {
// Provider Cost Pie Chart
const providerCtx = document.getElementById(`providerCostPieChart_${period_id}`);
if (providerCtx && providerCostData && providerCostData.data.length > 0) {
if (providerCtx && providerCostData && providerCostData.data && providerCostData.data.length > 0) {
new Chart(providerCtx, {
type: 'pie',
data: {
@@ -106,7 +118,7 @@ document.addEventListener('DOMContentLoaded', function () {
// Model Cost Bar Chart
const modelCtx = document.getElementById(`modelCostBarChart_${period_id}`);
if (modelCtx && modelCostData && modelCostData.data.length > 0) {
if (modelCtx && modelCostData && modelCostData.data && modelCostData.data.length > 0) {
new Chart(modelCtx, {
type: 'bar',
data: {