feat: 完成意心ai3.4版本发布
This commit is contained in:
@@ -213,8 +213,12 @@ export function getPremiumPackageTokenUsage() {
|
|||||||
] */
|
] */
|
||||||
|
|
||||||
// 获取当前用户近24小时每小时Token消耗统计
|
// 获取当前用户近24小时每小时Token消耗统计
|
||||||
export function getLast24HoursTokenUsage() {
|
// tokenId: 可选,传入则查询该token的用量,不传则查询全部
|
||||||
return get<any>('/usage-statistics/last24Hours-token-usage').json();
|
export function getLast24HoursTokenUsage(tokenId?: string) {
|
||||||
|
const url = tokenId
|
||||||
|
? `/usage-statistics/last24Hours-token-usage?tokenId=${tokenId}`
|
||||||
|
: '/usage-statistics/last24Hours-token-usage';
|
||||||
|
return get<any>(url).json();
|
||||||
}
|
}
|
||||||
/* 返回数据
|
/* 返回数据
|
||||||
[
|
[
|
||||||
@@ -232,8 +236,12 @@ export function getLast24HoursTokenUsage() {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
// 获取当前用户今日各模型使用量统计
|
// 获取当前用户今日各模型使用量统计
|
||||||
export function getTodayModelUsage() {
|
// tokenId: 可选,传入则查询该token的用量,不传则查询全部
|
||||||
return get<any>('/usage-statistics/today-model-usage').json();
|
export function getTodayModelUsage(tokenId?: string) {
|
||||||
|
const url = tokenId
|
||||||
|
? `/usage-statistics/today-model-usage?tokenId=${tokenId}`
|
||||||
|
: '/usage-statistics/today-model-usage';
|
||||||
|
return get<any>(url).json();
|
||||||
}
|
}
|
||||||
/* 返回数据
|
/* 返回数据
|
||||||
[
|
[
|
||||||
|
|||||||
@@ -50,6 +50,9 @@ const barContainerSize = useElementSize(barChart);
|
|||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
const totalTokens = ref(0);
|
const totalTokens = ref(0);
|
||||||
const usageData = ref<any[]>([]);
|
const usageData = ref<any[]>([]);
|
||||||
|
const showLast7DaysChart = ref(false); // 是否显示近七天图表
|
||||||
|
const loadingLast7Days = ref(false); // 近七天数据加载状态
|
||||||
|
const currentPeriodView = ref('24h'); // 当前显示的时间范围:'24h' 或 '7d'
|
||||||
const modelUsageData = ref<any[]>([]);
|
const modelUsageData = ref<any[]>([]);
|
||||||
const hourlyUsageData = ref<any[]>([]); // 新增:近24小时每小时Token消耗数据
|
const hourlyUsageData = ref<any[]>([]); // 新增:近24小时每小时Token消耗数据
|
||||||
const todayModelUsageData = ref<any[]>([]); // 新增:今日各模型使用量数据
|
const todayModelUsageData = ref<any[]>([]); // 新增:今日各模型使用量数据
|
||||||
@@ -179,26 +182,65 @@ async function fetchTokenOptions() {
|
|||||||
|
|
||||||
// Token选择变化
|
// Token选择变化
|
||||||
function handleTokenChange() {
|
function handleTokenChange() {
|
||||||
|
// 切换 token 时重置近七天数据状态
|
||||||
|
showLast7DaysChart.value = false;
|
||||||
|
usageData.value = [];
|
||||||
|
totalTokens.value = 0;
|
||||||
|
// 如果当前是7天视图,切换回24小时视图
|
||||||
|
if (currentPeriodView.value === '7d') {
|
||||||
|
currentPeriodView.value = '24h';
|
||||||
|
}
|
||||||
fetchUsageData();
|
fetchUsageData();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取用量数据
|
// 切换时间范围视图(24小时/7天)
|
||||||
|
function togglePeriodView() {
|
||||||
|
if (currentPeriodView.value === '24h') {
|
||||||
|
// 切换到7天视图
|
||||||
|
currentPeriodView.value = '7d';
|
||||||
|
if (!showLast7DaysChart.value) {
|
||||||
|
fetchLast7DaysData();
|
||||||
|
} else {
|
||||||
|
// 使用 nextTick 确保 DOM 更新后再渲染图表
|
||||||
|
nextTick(() => {
|
||||||
|
// 确保 lineChart 实例已初始化
|
||||||
|
if (!lineChartInstance && lineChart.value) {
|
||||||
|
lineChartInstance = echarts.init(lineChart.value);
|
||||||
|
}
|
||||||
|
updateLineChart();
|
||||||
|
// 延迟调用 resize 确保容器尺寸正确
|
||||||
|
setTimeout(() => {
|
||||||
|
lineChartInstance?.resize();
|
||||||
|
}, 100);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 切换回24小时视图
|
||||||
|
currentPeriodView.value = '24h';
|
||||||
|
// 使用 nextTick 确保 DOM 更新后再渲染图表
|
||||||
|
nextTick(() => {
|
||||||
|
updateHourlyBarChart();
|
||||||
|
setTimeout(() => {
|
||||||
|
hourlyBarChartInstance?.resize();
|
||||||
|
}, 100);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取用量数据(不包含近七天数据)
|
||||||
async function fetchUsageData() {
|
async function fetchUsageData() {
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
try {
|
try {
|
||||||
const tokenId = selectedTokenId.value || undefined;
|
const tokenId = selectedTokenId.value || undefined;
|
||||||
const [res, res2, res3, res4] = await Promise.all([
|
const [res2, res3, res4] = await Promise.all([
|
||||||
getLast7DaysTokenUsage(tokenId),
|
|
||||||
getModelTokenUsage(tokenId),
|
getModelTokenUsage(tokenId),
|
||||||
getLast24HoursTokenUsage(),
|
getLast24HoursTokenUsage(tokenId),
|
||||||
getTodayModelUsage(),
|
getTodayModelUsage(tokenId),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
usageData.value = res.data || [];
|
|
||||||
modelUsageData.value = res2.data || [];
|
modelUsageData.value = res2.data || [];
|
||||||
hourlyUsageData.value = res3.data || [];
|
hourlyUsageData.value = res3.data || [];
|
||||||
todayModelUsageData.value = res4.data || [];
|
todayModelUsageData.value = res4.data || [];
|
||||||
totalTokens.value = usageData.value.reduce((sum, item) => sum + item.tokens, 0);
|
|
||||||
|
|
||||||
updateCharts();
|
updateCharts();
|
||||||
}
|
}
|
||||||
@@ -211,6 +253,39 @@ async function fetchUsageData() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 单独加载近七天数据
|
||||||
|
async function fetchLast7DaysData() {
|
||||||
|
loadingLast7Days.value = true;
|
||||||
|
try {
|
||||||
|
const tokenId = selectedTokenId.value || undefined;
|
||||||
|
const res = await getLast7DaysTokenUsage(tokenId);
|
||||||
|
|
||||||
|
usageData.value = res.data || [];
|
||||||
|
totalTokens.value = usageData.value.reduce((sum, item) => sum + item.tokens, 0);
|
||||||
|
|
||||||
|
showLast7DaysChart.value = true;
|
||||||
|
// 使用 nextTick 确保 DOM 更新后再渲染图表
|
||||||
|
nextTick(() => {
|
||||||
|
// 确保 lineChart 实例已初始化
|
||||||
|
if (!lineChartInstance && lineChart.value) {
|
||||||
|
lineChartInstance = echarts.init(lineChart.value);
|
||||||
|
}
|
||||||
|
updateLineChart();
|
||||||
|
// 延迟调用 resize 确保容器尺寸正确
|
||||||
|
setTimeout(() => {
|
||||||
|
lineChartInstance?.resize();
|
||||||
|
}, 100);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
console.error('获取近七天数据失败:', error);
|
||||||
|
ElMessage.error('获取近七天数据失败');
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
loadingLast7Days.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 初始化图表
|
// 初始化图表
|
||||||
function initCharts() {
|
function initCharts() {
|
||||||
if (lineChart.value) {
|
if (lineChart.value) {
|
||||||
@@ -614,7 +689,7 @@ function updateBarChart() {
|
|||||||
barChartInstance.setOption(option, true);
|
barChartInstance.setOption(option, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更新近24小时每小时Token消耗柱状图
|
// 更新近24小时每小时Token消耗柱状图(堆叠柱状图)
|
||||||
function updateHourlyBarChart() {
|
function updateHourlyBarChart() {
|
||||||
if (!hourlyBarChartInstance)
|
if (!hourlyBarChartInstance)
|
||||||
return;
|
return;
|
||||||
@@ -689,84 +764,36 @@ function updateHourlyBarChart() {
|
|||||||
|
|
||||||
const isMobile = window.innerWidth < 768;
|
const isMobile = window.innerWidth < 768;
|
||||||
|
|
||||||
// 找出24小时中单小时模型数量最多的值
|
// 堆叠柱状图强调样式
|
||||||
const maxModelsPerHour = hourlyUsageData.value.reduce((max, hour) => {
|
const emphasisStyle = {
|
||||||
const count = hour.modelBreakdown?.length || 0;
|
itemStyle: {
|
||||||
return Math.max(max, count);
|
shadowBlur: 10,
|
||||||
}, 0);
|
shadowOffsetX: 0,
|
||||||
|
shadowColor: 'rgba(0, 0, 0, 0.3)',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
// 智能计算柱子宽度:基于最大模型数量动态计算
|
// 构建每个模型的数据系列(堆叠柱状图)
|
||||||
// 柱子宽度 = 单元格宽度 / 最大模型数量 - 间距占比
|
|
||||||
let barWidth: number;
|
|
||||||
let barGap: number | string;
|
|
||||||
let barCategoryGap: number | string;
|
|
||||||
|
|
||||||
// 估算每个时间段的可用宽度(像素)
|
|
||||||
// 假设图表容器宽度,移动端约320-375px,桌面端约1000-1400px
|
|
||||||
const containerWidth = isMobile ? 350 : 1200;
|
|
||||||
const hourCount = hourlyUsageData.value.length;
|
|
||||||
const categoryWidth = containerWidth / hourCount; // 每个小时类别的宽度
|
|
||||||
|
|
||||||
// 柱子间距配置(像素)
|
|
||||||
const gapBetweenBars = isMobile ? 3 : 6; // 柱子之间的间距
|
|
||||||
const gapBetweenCategories = isMobile ? 8 : 15; // 类别之间的间距
|
|
||||||
|
|
||||||
// 计算柱子宽度:可用宽度除以最大模型数
|
|
||||||
// 可用宽度 = 类别宽度 - 类别间距 - (柱子数-1)*柱子间距
|
|
||||||
const availableWidth = categoryWidth - gapBetweenCategories - ((maxModelsPerHour - 1) * gapBetweenBars);
|
|
||||||
barWidth = Math.max(4, Math.floor(availableWidth / maxModelsPerHour)); // 最小4px
|
|
||||||
|
|
||||||
// 将间距转换为百分比以便ECharts自适应
|
|
||||||
barGap = `${(gapBetweenBars / barWidth) * 100}%`;
|
|
||||||
barCategoryGap = `${(gapBetweenCategories / categoryWidth) * 100}%`;
|
|
||||||
|
|
||||||
// 限制最大柱子宽度,避免太少模型时柱子过粗
|
|
||||||
const maxBarWidth = isMobile ? 25 : 60;
|
|
||||||
barWidth = Math.min(barWidth, maxBarWidth);
|
|
||||||
|
|
||||||
// 构建每个模型的数据系列(并排柱状图)
|
|
||||||
const series = hourlyModels.value.map(({ modelId }, index) => {
|
const series = hourlyModels.value.map(({ modelId }, index) => {
|
||||||
const data = hourlyUsageData.value.map((hour) => {
|
const data = hourlyUsageData.value.map((hour) => {
|
||||||
const modelData = hour.modelBreakdown?.find((m: any) => m.modelId === modelId);
|
const modelData = hour.modelBreakdown?.find((m: any) => m.modelId === modelId);
|
||||||
return modelData?.tokens || 0;
|
return modelData?.tokens || 0;
|
||||||
});
|
});
|
||||||
|
|
||||||
// 根据最大模型数量决定是否显示标签
|
|
||||||
const showLabel = maxModelsPerHour <= 4 && !isMobile;
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
name: modelId,
|
name: modelId,
|
||||||
type: 'bar',
|
type: 'bar',
|
||||||
barWidth,
|
stack: 'total', // 所有系列堆叠在一起
|
||||||
barGap,
|
emphasis: emphasisStyle,
|
||||||
barCategoryGap,
|
|
||||||
data,
|
data,
|
||||||
itemStyle: {
|
itemStyle: {
|
||||||
color: modelColors.value[modelId],
|
color: modelColors.value[modelId],
|
||||||
borderRadius: [4, 4, 0, 0],
|
borderRadius: [0, 0, 0, 0], // 堆叠柱状图不需要圆角
|
||||||
borderWidth: 0,
|
borderWidth: 0,
|
||||||
},
|
},
|
||||||
label: {
|
label: {
|
||||||
show: showLabel,
|
show: false, // 堆叠柱状图不显示标签
|
||||||
position: 'top',
|
|
||||||
formatter: (params: any) => {
|
|
||||||
if (params.value === 0)
|
|
||||||
return '';
|
|
||||||
return params.value >= 1000 ? `${(params.value / 1000).toFixed(1)}k` : params.value.toString();
|
|
||||||
},
|
|
||||||
fontSize: isMobile ? 9 : 11,
|
|
||||||
color: '#666',
|
|
||||||
},
|
},
|
||||||
emphasis: {
|
|
||||||
focus: 'series',
|
|
||||||
itemStyle: {
|
|
||||||
shadowBlur: 8,
|
|
||||||
shadowOffsetX: 0,
|
|
||||||
shadowColor: 'rgba(0, 0, 0, 0.2)',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
// 确保系列按总使用量排序的顺序
|
|
||||||
seriesIndex: index,
|
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -847,21 +874,18 @@ function updateHourlyBarChart() {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
toolbox: {
|
toolbox: {
|
||||||
show: true, // 强制显示工具箱
|
show: true,
|
||||||
feature: {
|
feature: {
|
||||||
dataZoom: {
|
magicType: {
|
||||||
yAxisIndex: 'none',
|
type: ['stack', 'line'],
|
||||||
title: {
|
title: {
|
||||||
zoom: '区域缩放',
|
stack: '切换为堆叠',
|
||||||
back: '还原缩放',
|
line: '切换为折线',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
brush: {
|
dataView: {
|
||||||
type: ['lineX', 'clear'],
|
title: '数据视图',
|
||||||
title: {
|
readOnly: true,
|
||||||
lineX: '横向选择',
|
|
||||||
clear: '清除选择',
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
restore: {
|
restore: {
|
||||||
show: true,
|
show: true,
|
||||||
@@ -896,6 +920,26 @@ function updateHourlyBarChart() {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
brush: {
|
||||||
|
toolbox: ['lineX', 'keep', 'clear'],
|
||||||
|
xAxisIndex: 0,
|
||||||
|
brushLink: 'all',
|
||||||
|
throttleType: 'debounce',
|
||||||
|
throttleDelay: 300,
|
||||||
|
removeOnClick: true,
|
||||||
|
brushType: false,
|
||||||
|
inBrush: {
|
||||||
|
opacity: 1,
|
||||||
|
},
|
||||||
|
outOfBrush: {
|
||||||
|
opacity: 0.15,
|
||||||
|
},
|
||||||
|
brushStyle: {
|
||||||
|
borderWidth: 1,
|
||||||
|
color: 'rgba(102, 126, 234, 0.15)',
|
||||||
|
borderColor: '#667eea',
|
||||||
|
},
|
||||||
|
},
|
||||||
legend: {
|
legend: {
|
||||||
show: true,
|
show: true,
|
||||||
type: hourlyModels.value.length > 8 || isMobile ? 'scroll' : 'plain',
|
type: hourlyModels.value.length > 8 || isMobile ? 'scroll' : 'plain',
|
||||||
@@ -920,89 +964,30 @@ function updateHourlyBarChart() {
|
|||||||
pageButtonItemGap: 5,
|
pageButtonItemGap: 5,
|
||||||
},
|
},
|
||||||
grid: {
|
grid: {
|
||||||
top: maxModelsPerHour > 8 ? '15%' : '12%',
|
top: '12%',
|
||||||
left: '1%',
|
left: '1%',
|
||||||
right: isMobile ? '8%' : '10%',
|
right: isMobile ? '8%' : '3%',
|
||||||
bottom: isMobile ? '15%' : '12%',
|
bottom: '10%',
|
||||||
containLabel: true,
|
containLabel: true,
|
||||||
},
|
},
|
||||||
dataZoom: [
|
|
||||||
{
|
|
||||||
show: true, // 强制显示滑动条
|
|
||||||
start: hourlyUsageData.value.length > 12 ? 100 - Math.round((12 / hourlyUsageData.value.length) * 100) : 80,
|
|
||||||
end: 100,
|
|
||||||
xAxisIndex: [0],
|
|
||||||
bottom: '3%',
|
|
||||||
height: 20,
|
|
||||||
borderColor: 'transparent',
|
|
||||||
fillerColor: 'rgba(102, 126, 234, 0.2)',
|
|
||||||
handleStyle: {
|
|
||||||
color: '#667eea',
|
|
||||||
},
|
|
||||||
textStyle: {
|
|
||||||
color: '#999',
|
|
||||||
fontSize: 11,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'inside',
|
|
||||||
start: hourlyUsageData.value.length > 12 ? 100 - Math.round((12 / hourlyUsageData.value.length) * 100) : 80,
|
|
||||||
end: 100,
|
|
||||||
xAxisIndex: [0],
|
|
||||||
zoomOnMouseWheel: true,
|
|
||||||
moveOnMouseMove: true,
|
|
||||||
moveOnMouseWheel: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
show: true, // 强制显示Y轴缩放条
|
|
||||||
yAxisIndex: [0],
|
|
||||||
filterMode: 'empty',
|
|
||||||
width: 28,
|
|
||||||
height: '70%',
|
|
||||||
showDataShadow: false,
|
|
||||||
left: '96%',
|
|
||||||
borderColor: 'transparent',
|
|
||||||
fillerColor: 'rgba(102, 126, 234, 0.15)',
|
|
||||||
handleStyle: {
|
|
||||||
color: '#667eea',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
// 区域选框缩放配置(配合calculable使用)
|
|
||||||
brush: {
|
|
||||||
id: 'brush',
|
|
||||||
xAxisIndex: 0,
|
|
||||||
link: ['x'],
|
|
||||||
throttleType: 'debounce',
|
|
||||||
throttleDelay: 300,
|
|
||||||
removeOnClick: true,
|
|
||||||
brushLink: 'all',
|
|
||||||
brushType: false, // 默认不启用刷子,通过工具箱激活
|
|
||||||
inBrush: {
|
|
||||||
opacity: 1,
|
|
||||||
},
|
|
||||||
outOfBrush: {
|
|
||||||
opacity: 0.15,
|
|
||||||
},
|
|
||||||
// 选框样式
|
|
||||||
brushStyle: {
|
|
||||||
borderWidth: 1,
|
|
||||||
color: 'rgba(102, 126, 234, 0.15)',
|
|
||||||
borderColor: '#667eea',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
xAxis: {
|
xAxis: {
|
||||||
type: 'category',
|
type: 'category',
|
||||||
data: hours,
|
data: hours,
|
||||||
|
name: '时间',
|
||||||
|
nameTextStyle: {
|
||||||
|
fontSize: isMobile ? 11 : 12,
|
||||||
|
color: '#999',
|
||||||
|
padding: [0, 0, 0, 0],
|
||||||
|
},
|
||||||
axisLine: {
|
axisLine: {
|
||||||
|
show: true,
|
||||||
lineStyle: {
|
lineStyle: {
|
||||||
color: '#e9ecef',
|
color: '#e9ecef',
|
||||||
width: 1,
|
width: 1,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
axisTick: {
|
axisTick: {
|
||||||
alignWithLabel: true,
|
show: false,
|
||||||
length: 4,
|
|
||||||
},
|
},
|
||||||
axisLabel: {
|
axisLabel: {
|
||||||
interval: isMobile ? 3 : 0,
|
interval: isMobile ? 3 : 0,
|
||||||
@@ -1011,6 +996,12 @@ function updateHourlyBarChart() {
|
|||||||
color: '#666',
|
color: '#666',
|
||||||
fontFamily: 'monospace',
|
fontFamily: 'monospace',
|
||||||
},
|
},
|
||||||
|
splitLine: {
|
||||||
|
show: false,
|
||||||
|
},
|
||||||
|
splitArea: {
|
||||||
|
show: false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
yAxis: {
|
yAxis: {
|
||||||
type: 'value',
|
type: 'value',
|
||||||
@@ -1150,23 +1141,56 @@ onBeforeUnmount(() => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 近24小时每小时Token消耗柱状图 -->
|
<!-- 时间范围图表(可切换:24小时/7天) -->
|
||||||
<el-card v-loading="loading" class="chart-card">
|
<el-card v-loading="currentPeriodView === '7d' ? loadingLast7Days : loading" class="chart-card">
|
||||||
<template #header>
|
<template #header>
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<span class="card-title">⏰ 近24小时每小时Token消耗</span>
|
<span class="card-title">
|
||||||
|
{{ currentPeriodView === '24h' ? '⏰ 近24小时每小时Token消耗' : '📊 近七天每日Token消耗量' }}{{ selectedTokenId ? ` (${selectedTokenName})` : '' }}
|
||||||
|
</span>
|
||||||
|
<!-- 切换按钮 -->
|
||||||
|
<div class="period-toggle-group">
|
||||||
|
<el-button
|
||||||
|
:type="currentPeriodView === '24h' ? 'primary' : 'default'"
|
||||||
|
size="small"
|
||||||
|
@click="togglePeriodView"
|
||||||
|
>
|
||||||
|
近24小时
|
||||||
|
</el-button>
|
||||||
|
<el-button
|
||||||
|
:type="currentPeriodView === '7d' ? 'primary' : 'default'"
|
||||||
|
size="small"
|
||||||
|
@click="togglePeriodView"
|
||||||
|
>
|
||||||
|
近7天
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<div class="chart-container">
|
<!-- 24小时视图 -->
|
||||||
|
<div v-show="currentPeriodView === '24h'" class="chart-container">
|
||||||
<div ref="hourlyBarChart" class="chart hourly-bar-chart" />
|
<div ref="hourlyBarChart" class="chart hourly-bar-chart" />
|
||||||
</div>
|
</div>
|
||||||
|
<!-- 7天视图 -->
|
||||||
|
<div v-show="currentPeriodView === '7d'" class="chart-container">
|
||||||
|
<div v-if="!showLast7DaysChart" class="load-chart-inline">
|
||||||
|
<div class="load-inline-content">
|
||||||
|
<div class="load-inline-icon">📊</div>
|
||||||
|
<div class="load-inline-text">该功能查询性能消耗较大</div>
|
||||||
|
<el-button type="primary" @click="fetchLast7DaysData" :loading="loadingLast7Days">
|
||||||
|
加载近七天数据
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-else ref="lineChart" class="chart line-chart" />
|
||||||
|
</div>
|
||||||
</el-card>
|
</el-card>
|
||||||
|
|
||||||
<!-- 今日各模型使用量卡片列表 -->
|
<!-- 今日各模型使用量卡片列表 -->
|
||||||
<el-card v-loading="loading" class="chart-card today-model-card">
|
<el-card v-loading="loading" class="chart-card today-model-card">
|
||||||
<template #header>
|
<template #header>
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<span class="card-title">📋 今日各模型使用量统计(凌晨零点至现在)</span>
|
<span class="card-title">📋 今日各模型使用量统计(凌晨零点至现在){{ selectedTokenId ? ` (${selectedTokenName})` : '' }}</span>
|
||||||
<el-tag v-if="todayModelUsageData.length > 0" type="success" effect="plain">
|
<el-tag v-if="todayModelUsageData.length > 0" type="success" effect="plain">
|
||||||
共 {{ todayModelUsageData.length }} 个模型
|
共 {{ todayModelUsageData.length }} 个模型
|
||||||
</el-tag>
|
</el-tag>
|
||||||
@@ -1236,41 +1260,30 @@ onBeforeUnmount(() => {
|
|||||||
</div>
|
</div>
|
||||||
</el-card>
|
</el-card>
|
||||||
|
|
||||||
<el-card v-loading="loading" class="chart-card">
|
<!-- 各模型Token消耗占比和总Token消耗量并排显示 -->
|
||||||
<template #header>
|
<div class="charts-row">
|
||||||
<div class="card-header">
|
<el-card v-loading="loading" class="chart-card half-width">
|
||||||
<span class="card-title">📊 近七天每日Token消耗量{{ selectedTokenId ? ` (${selectedTokenName})` : '' }}</span>
|
<template #header>
|
||||||
<el-tag type="primary" size="large" effect="dark">
|
<div class="card-header">
|
||||||
近七日总计: {{ totalTokens }} tokens
|
<span class="card-title">🥧 各模型总Token消耗占比{{ selectedTokenId ? ` (${selectedTokenName})` : '' }}</span>
|
||||||
</el-tag>
|
</div>
|
||||||
|
</template>
|
||||||
|
<div class="chart-container">
|
||||||
|
<div ref="pieChart" class="chart pie-chart" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</el-card>
|
||||||
<div class="chart-container">
|
|
||||||
<div ref="lineChart" class="chart line-chart" />
|
|
||||||
</div>
|
|
||||||
</el-card>
|
|
||||||
|
|
||||||
<el-card v-loading="loading" class="chart-card">
|
<el-card v-loading="loading" class="chart-card half-width">
|
||||||
<template #header>
|
<template #header>
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<span class="card-title">🥧 各模型Token消耗占比{{ selectedTokenId ? ` (${selectedTokenName})` : '' }}</span>
|
<span class="card-title">📈 各模型总Token消耗量{{ selectedTokenId ? ` (${selectedTokenName})` : '' }}</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<div class="chart-container">
|
||||||
|
<div ref="barChart" class="chart bar-chart" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</el-card>
|
||||||
<div class="chart-container">
|
</div>
|
||||||
<div ref="pieChart" class="chart pie-chart" />
|
|
||||||
</div>
|
|
||||||
</el-card>
|
|
||||||
|
|
||||||
<el-card v-loading="loading" class="chart-card">
|
|
||||||
<template #header>
|
|
||||||
<div class="card-header">
|
|
||||||
<span class="card-title">📈 各模型总Token消耗量{{ selectedTokenId ? ` (${selectedTokenName})` : '' }}</span>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<div class="chart-container">
|
|
||||||
<div ref="barChart" class="chart bar-chart" />
|
|
||||||
</div>
|
|
||||||
</el-card>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -1384,6 +1397,59 @@ onBeforeUnmount(() => {
|
|||||||
transform: translateY(-4px);
|
transform: translateY(-4px);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 图表行布局:两个图表并排显示 */
|
||||||
|
.charts-row {
|
||||||
|
display: flex;
|
||||||
|
gap: 20px;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.charts-row .chart-card {
|
||||||
|
flex: 1;
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.charts-row .half-width {
|
||||||
|
width: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 时间范围切换按钮组 */
|
||||||
|
.period-toggle-group {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.period-toggle-group .el-button {
|
||||||
|
padding: 6px 16px;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 内联加载提示 */
|
||||||
|
.load-chart-inline {
|
||||||
|
min-height: 400px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.load-inline-content {
|
||||||
|
text-align: center;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.load-inline-icon {
|
||||||
|
font-size: 48px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.load-inline-text {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
.card-header {
|
.card-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
@@ -1643,6 +1709,40 @@ onBeforeUnmount(() => {
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 移动端图表行垂直排列 */
|
||||||
|
.charts-row {
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.charts-row .half-width {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 移动端切换按钮组 */
|
||||||
|
.period-toggle-group {
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.period-toggle-group .el-button {
|
||||||
|
padding: 4px 12px;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 移动端内联加载提示 */
|
||||||
|
.load-chart-inline {
|
||||||
|
min-height: 280px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.load-inline-icon {
|
||||||
|
font-size: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.load-inline-text {
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
/* 移动端图表高度优化 */
|
/* 移动端图表高度优化 */
|
||||||
.line-chart {
|
.line-chart {
|
||||||
height: 280px !important;
|
height: 280px !important;
|
||||||
@@ -1720,6 +1820,26 @@ onBeforeUnmount(() => {
|
|||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 超小屏幕图表行垂直排列 */
|
||||||
|
.charts-row {
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.charts-row .half-width {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 超小屏幕切换按钮组 */
|
||||||
|
.period-toggle-group .el-button {
|
||||||
|
padding: 4px 10px;
|
||||||
|
font-size: 11px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.load-chart-inline {
|
||||||
|
min-height: 240px;
|
||||||
|
}
|
||||||
|
|
||||||
/* 超小屏幕图表高度 */
|
/* 超小屏幕图表高度 */
|
||||||
.line-chart {
|
.line-chart {
|
||||||
height: 250px !important;
|
height: 250px !important;
|
||||||
|
|||||||
Reference in New Issue
Block a user