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

146 lines
6.7 KiB
JavaScript

let i, tab_content, tab_links;
tab_content = document.getElementsByClassName("tab-content");
tab_links = document.getElementsByClassName("tab-link");
if (tab_content.length > 0) tab_content[0].classList.add("active");
if (tab_links.length > 0) tab_links[0].classList.add("active");
function showTab(evt, tabName) {
for (i = 0; i < tab_content.length; i++) tab_content[i].classList.remove("active");
for (i = 0; i < tab_links.length; i++) tab_links[i].classList.remove("active");
document.getElementById(tabName).classList.add("active");
evt.currentTarget.classList.add("active");
}
document.addEventListener('DOMContentLoaded', function () {
// 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 },
costByModule: { id: 'costByModuleChart', title: '各模块花费', yAxisLabel: '花费 (¥)', dataKey: 'cost_by_module', fill: false },
costByModel: { id: 'costByModelChart', title: '各模型花费', yAxisLabel: '花费 (¥)', dataKey: 'cost_by_model', fill: false },
messageByChat: { id: 'messageByChatChart', title: '各聊天流消息数', yAxisLabel: '消息数', dataKey: 'message_by_chat', fill: false }
};
window.switchTimeRange = function(timeRange) {
document.querySelectorAll('.time-range-btn').forEach(btn => btn.classList.remove('active'));
event.target.classList.add('active');
updateAllCharts(allChartData[timeRange], timeRange);
}
function updateAllCharts(data, timeRange) {
Object.values(currentCharts).forEach(chart => chart && chart.destroy());
currentCharts = {};
Object.keys(chartConfigs).forEach(type => createChart(type, data, timeRange));
}
function createChart(chartType, data, timeRange) {
const config = chartConfigs[chartType];
if (!data || !data[config.dataKey]) return;
const colors = ['#3498db', '#e74c3c', '#2ecc71', '#f39c12', '#9b59b6', '#1abc9c', '#34495e', '#e67e22', '#95a5a6', '#f1c40f'];
let datasets = [];
if (chartType === 'totalCost') {
datasets = [{ label: config.title, data: data[config.dataKey], borderColor: colors[0], backgroundColor: 'rgba(52, 152, 219, 0.1)', tension: 0.4, fill: config.fill }];
} else {
let i = 0;
Object.entries(data[config.dataKey]).forEach(([name, chartData]) => {
datasets.push({ label: name, data: chartData, borderColor: colors[i % colors.length], backgroundColor: colors[i % colors.length] + '20', tension: 0.4, fill: config.fill });
i++;
});
}
currentCharts[chartType] = new Chart(document.getElementById(config.id), {
type: 'line',
data: { labels: data.time_labels, datasets: datasets },
options: {
responsive: true,
plugins: { title: { display: true, text: `${timeRange}${config.title}趋势`, font: { size: 16 } }, legend: { display: chartType !== 'totalCost', position: 'top' } },
scales: { x: { title: { display: true, text: '时间' }, ticks: { maxTicksLimit: 12 } }, y: { title: { display: true, text: config.yAxisLabel }, beginAtZero: true } },
interaction: { intersect: false, mode: 'index' }
}
});
}
if (allChartData['24h']) {
updateAllCharts(allChartData['24h'], '24h');
// Activate the 24h button by default
document.querySelectorAll('.time-range-btn').forEach(btn => {
if (btn.textContent.includes('24小时')) {
btn.classList.add('active');
} else {
btn.classList.remove('active');
}
});
}
// Static charts
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;
const colors = ['#3498db', '#2ecc71', '#f1c40f', '#e74c3c', '#9b59b6', '#1abc9c', '#34495e', '#e67e22'];
// Provider Cost Pie Chart
const providerCtx = document.getElementById(`providerCostPieChart_${period_id}`);
if (providerCtx && providerCostData && providerCostData.data && providerCostData.data.length > 0) {
new Chart(providerCtx, {
type: 'pie',
data: {
labels: providerCostData.labels,
datasets: [{
label: '按供应商花费',
data: providerCostData.data,
backgroundColor: colors,
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
title: { display: true, text: '按供应商花费分布', font: { size: 16 } },
legend: { position: 'top' }
}
}
});
}
// Model Cost Bar Chart
const modelCtx = document.getElementById(`modelCostBarChart_${period_id}`);
if (modelCtx && modelCostData && modelCostData.data && modelCostData.data.length > 0) {
new Chart(modelCtx, {
type: 'bar',
data: {
labels: modelCostData.labels,
datasets: [{
label: '按模型花费',
data: modelCostData.data,
backgroundColor: colors,
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
title: { display: true, text: '按模型花费排行', font: { size: 16 } },
legend: { display: false }
},
scales: {
y: { beginAtZero: true, title: { display: true, text: '花费 (¥)' } }
}
}
});
}
});
});