fix: 优化token图表,增加全屏显示

This commit is contained in:
Gsh
2025-08-10 15:34:53 +08:00
parent 6b31536de5
commit bbe5b01872

View File

@@ -1,5 +1,6 @@
<script lang="ts" setup>
import { PieChart } from '@element-plus/icons-vue';
import { FullScreen, PieChart } from '@element-plus/icons-vue';
import { useElementSize } from '@vueuse/core';
import { BarChart, PieChart as EPieChart, LineChart } from 'echarts/charts';
import {
GraphicComponent,
@@ -8,7 +9,6 @@ import {
TitleComponent,
TooltipComponent,
} from 'echarts/components';
// 按需引入 ECharts
import * as echarts from 'echarts/core';
import { CanvasRenderer } from 'echarts/renderers';
import { getLast7DaysTokenUsage, getModelTokenUsage } from '@/api';
@@ -34,48 +34,20 @@ let lineChartInstance: any = null;
let pieChartInstance: any = null;
let barChartInstance: any = null;
// 全屏状态
const isFullscreen = ref(false);
const usageStatisticsRef = ref<HTMLElement>();
// 容器尺寸监听
const pieContainerSize = useElementSize(pieChart);
const barContainerSize = useElementSize(barChart);
// 数据
const dateRange = ref([
new Date(new Date().setDate(new Date().getDate() - 30)),
new Date(),
]);
const loading = ref(false);
const totalTokens = ref(0);
const usageData = ref<any[]>([]);
const modelUsageData = ref<any[]>([]);
// 日期选择器选项
const pickerOptions = {
disabledDate(time: Date) {
return time > new Date();
},
shortcuts: [{
text: '最近一周',
onClick(picker: any) {
const end = new Date();
const start = new Date();
start.setDate(start.getDate() - 7);
picker.$emit('pick', [start, end]);
},
}, {
text: '最近一个月',
onClick(picker: any) {
const end = new Date();
const start = new Date();
start.setMonth(start.getMonth() - 1);
picker.$emit('pick', [start, end]);
},
}, {
text: '最近三个月',
onClick(picker: any) {
const end = new Date();
const start = new Date();
start.setMonth(start.getMonth() - 3);
picker.$emit('pick', [start, end]);
},
}],
};
// 获取用量数据
async function fetchUsageData() {
loading.value = true;
@@ -149,7 +121,7 @@ function updateLineChart() {
type: 'category',
data: dates,
axisLabel: {
rotate: 45,
// rotate: 45,
},
},
yAxis: {
@@ -182,7 +154,7 @@ function updateLineChart() {
lineChartInstance.setOption(option);
}
// 更新饼图
// 更新饼图 - 动态适应数据量
function updatePieChart() {
if (!pieChartInstance || modelUsageData.value.length === 0)
return;
@@ -192,15 +164,31 @@ function updatePieChart() {
value: item.tokens,
}));
const isManyItems = data.length > 8;
const isSmallContainer = pieContainerSize.width.value < 600;
const option = {
tooltip: {
trigger: 'item',
formatter: '{a} <br/>{b}: {c} tokens ({d}%)',
},
legend: {
orient: 'vertical',
right: 10,
top: 'center',
orient: isManyItems ? 'vertical' : 'horizontal',
right: isManyItems ? 10 : 'auto',
bottom: isManyItems ? 0 : 10,
type: isManyItems ? 'scroll' : 'plain',
pageIconColor: '#3a4de9',
pageIconInactiveColor: '#ccc',
pageTextStyle: { color: '#333' },
itemGap: isSmallContainer ? 5 : 10,
itemWidth: isSmallContainer ? 15 : 25,
itemHeight: isSmallContainer ? 10 : 14,
textStyle: {
fontSize: isSmallContainer ? 10 : 12,
},
formatter(name: string) {
return name.length > 15 ? `${name.substring(0, 12)}...` : name;
},
data: data.map(item => item.name),
},
series: [
@@ -208,6 +196,7 @@ function updatePieChart() {
name: '模型用量',
type: 'pie',
radius: ['50%', '70%'],
center: isManyItems ? ['40%', '50%'] : ['50%', '50%'],
avoidLabelOverlap: false,
itemStyle: {
borderRadius: 10,
@@ -236,7 +225,7 @@ function updatePieChart() {
pieChartInstance.setOption(option);
}
// 更新柱状图
// 更新柱状图 - 动态适应数据量
function updateBarChart() {
if (!barChartInstance || modelUsageData.value.length === 0)
return;
@@ -244,6 +233,9 @@ function updateBarChart() {
const models = modelUsageData.value.map(item => item.model);
const tokens = modelUsageData.value.map(item => item.tokens);
const isManyItems = models.length > 10;
const isSmallContainer = barContainerSize.width.value < 600;
const option = {
tooltip: {
trigger: 'axis',
@@ -256,6 +248,7 @@ function updateBarChart() {
left: '3%',
right: '4%',
bottom: '3%',
top: '3%',
containLabel: true,
},
xAxis: {
@@ -267,12 +260,20 @@ function updateBarChart() {
data: models,
axisLabel: {
interval: 0,
fontSize: Math.max(10, 14 - Math.floor(models.length / 10)),
formatter(value: string) {
if (isSmallContainer && value.length > 10) {
return `${value.substring(0, 8)}...`;
}
return value;
},
},
},
series: [
{
name: '用量',
type: 'bar',
barWidth: isManyItems ? '60%' : '80%',
data: tokens,
itemStyle: {
color(params: any) {
@@ -311,6 +312,20 @@ function resizeCharts() {
barChartInstance?.resize();
}
// 切换全屏
function toggleFullscreen() {
isFullscreen.value = !isFullscreen.value;
nextTick(() => {
resizeCharts();
});
}
// 监听容器大小变化
watch([pieContainerSize.width, barContainerSize.width], () => {
updatePieChart();
updateBarChart();
});
onMounted(() => {
initCharts();
fetchUsageData();
@@ -325,12 +340,23 @@ onBeforeUnmount(() => {
</script>
<template>
<div class="usage-statistics">
<div
ref="usageStatisticsRef"
class="usage-statistics"
:class="{ 'fullscreen-mode': isFullscreen }"
>
<div class="header">
<h2>
<el-icon><PieChart /></el-icon>
Token用量统计
</h2>
<el-button
:icon="FullScreen"
circle
plain
size="small"
@click="toggleFullscreen"
/>
</div>
<el-card v-loading="loading" class="chart-card">
@@ -354,7 +380,7 @@ onBeforeUnmount(() => {
</div>
</template>
<div class="chart-container">
<div ref="pieChart" class="chart" style="height: 400px;" />
<div ref="pieChart" class="chart" style="height: 450px;" />
</div>
</el-card>
@@ -365,16 +391,29 @@ onBeforeUnmount(() => {
</div>
</template>
<div class="chart-container">
<div ref="barChart" class="chart" style="height: 400px;" />
<div ref="barChart" class="chart" style="height: 500px;" />
</div>
</el-card>
</div>
</template>
<style scoped>
/* 样式保持不变 */
.usage-statistics {
padding: 20px;
position: relative;
transition: all 0.3s ease;
}
.usage-statistics.fullscreen-mode {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 2000;
background: #fff;
padding: 20px;
overflow-y: auto;
}
.header {
@@ -421,6 +460,19 @@ onBeforeUnmount(() => {
.header {
flex-direction: column;
align-items: flex-start;
gap: 10px;
}
.chart-card {
margin-bottom: 15px;
}
.chart {
height: 350px !important;
}
.usage-statistics.fullscreen-mode {
padding: 10px;
}
}
</style>