refactor(report): 优化报告样式和数据加载逻辑
- 调整了报告页面的CSS样式,包括颜色、阴影和布局,以提供更专业、现代的视觉效果。 - 改进了从后端向前端JavaScript传递图表数据的方式。现在通过一个独立的`<script>`标签注入JSON字符串,而不是直接嵌入到JS代码中,这增强了鲁棒性并避免了特殊字符导致的解析错误。 - 在JavaScript中增加了对JSON解析和图表数据有效性的检查,以防止因数据格式错误或缺失导致页面渲染失败。 - 将统计模块中的耗时相关键名统一为大写格式,以提高代码一致性。
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
@@ -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, [])
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -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>
|
||||
@@ -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: {
|
||||
|
||||
Reference in New Issue
Block a user