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> <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 { BarChart, PieChart as EPieChart, LineChart } from 'echarts/charts';
import { import {
GraphicComponent, GraphicComponent,
@@ -8,7 +9,6 @@ import {
TitleComponent, TitleComponent,
TooltipComponent, TooltipComponent,
} from 'echarts/components'; } from 'echarts/components';
// 按需引入 ECharts
import * as echarts from 'echarts/core'; import * as echarts from 'echarts/core';
import { CanvasRenderer } from 'echarts/renderers'; import { CanvasRenderer } from 'echarts/renderers';
import { getLast7DaysTokenUsage, getModelTokenUsage } from '@/api'; import { getLast7DaysTokenUsage, getModelTokenUsage } from '@/api';
@@ -34,48 +34,20 @@ let lineChartInstance: any = null;
let pieChartInstance: any = null; let pieChartInstance: any = null;
let barChartInstance: 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 loading = ref(false);
const totalTokens = ref(0); const totalTokens = ref(0);
const usageData = ref<any[]>([]); const usageData = ref<any[]>([]);
const modelUsageData = 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() { async function fetchUsageData() {
loading.value = true; loading.value = true;
@@ -149,7 +121,7 @@ function updateLineChart() {
type: 'category', type: 'category',
data: dates, data: dates,
axisLabel: { axisLabel: {
rotate: 45, // rotate: 45,
}, },
}, },
yAxis: { yAxis: {
@@ -182,7 +154,7 @@ function updateLineChart() {
lineChartInstance.setOption(option); lineChartInstance.setOption(option);
} }
// 更新饼图 // 更新饼图 - 动态适应数据量
function updatePieChart() { function updatePieChart() {
if (!pieChartInstance || modelUsageData.value.length === 0) if (!pieChartInstance || modelUsageData.value.length === 0)
return; return;
@@ -192,15 +164,31 @@ function updatePieChart() {
value: item.tokens, value: item.tokens,
})); }));
const isManyItems = data.length > 8;
const isSmallContainer = pieContainerSize.width.value < 600;
const option = { const option = {
tooltip: { tooltip: {
trigger: 'item', trigger: 'item',
formatter: '{a} <br/>{b}: {c} tokens ({d}%)', formatter: '{a} <br/>{b}: {c} tokens ({d}%)',
}, },
legend: { legend: {
orient: 'vertical', orient: isManyItems ? 'vertical' : 'horizontal',
right: 10, right: isManyItems ? 10 : 'auto',
top: 'center', 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), data: data.map(item => item.name),
}, },
series: [ series: [
@@ -208,6 +196,7 @@ function updatePieChart() {
name: '模型用量', name: '模型用量',
type: 'pie', type: 'pie',
radius: ['50%', '70%'], radius: ['50%', '70%'],
center: isManyItems ? ['40%', '50%'] : ['50%', '50%'],
avoidLabelOverlap: false, avoidLabelOverlap: false,
itemStyle: { itemStyle: {
borderRadius: 10, borderRadius: 10,
@@ -236,7 +225,7 @@ function updatePieChart() {
pieChartInstance.setOption(option); pieChartInstance.setOption(option);
} }
// 更新柱状图 // 更新柱状图 - 动态适应数据量
function updateBarChart() { function updateBarChart() {
if (!barChartInstance || modelUsageData.value.length === 0) if (!barChartInstance || modelUsageData.value.length === 0)
return; return;
@@ -244,6 +233,9 @@ function updateBarChart() {
const models = modelUsageData.value.map(item => item.model); const models = modelUsageData.value.map(item => item.model);
const tokens = modelUsageData.value.map(item => item.tokens); const tokens = modelUsageData.value.map(item => item.tokens);
const isManyItems = models.length > 10;
const isSmallContainer = barContainerSize.width.value < 600;
const option = { const option = {
tooltip: { tooltip: {
trigger: 'axis', trigger: 'axis',
@@ -256,6 +248,7 @@ function updateBarChart() {
left: '3%', left: '3%',
right: '4%', right: '4%',
bottom: '3%', bottom: '3%',
top: '3%',
containLabel: true, containLabel: true,
}, },
xAxis: { xAxis: {
@@ -267,12 +260,20 @@ function updateBarChart() {
data: models, data: models,
axisLabel: { axisLabel: {
interval: 0, 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: [ series: [
{ {
name: '用量', name: '用量',
type: 'bar', type: 'bar',
barWidth: isManyItems ? '60%' : '80%',
data: tokens, data: tokens,
itemStyle: { itemStyle: {
color(params: any) { color(params: any) {
@@ -311,6 +312,20 @@ function resizeCharts() {
barChartInstance?.resize(); barChartInstance?.resize();
} }
// 切换全屏
function toggleFullscreen() {
isFullscreen.value = !isFullscreen.value;
nextTick(() => {
resizeCharts();
});
}
// 监听容器大小变化
watch([pieContainerSize.width, barContainerSize.width], () => {
updatePieChart();
updateBarChart();
});
onMounted(() => { onMounted(() => {
initCharts(); initCharts();
fetchUsageData(); fetchUsageData();
@@ -325,12 +340,23 @@ onBeforeUnmount(() => {
</script> </script>
<template> <template>
<div class="usage-statistics"> <div
ref="usageStatisticsRef"
class="usage-statistics"
:class="{ 'fullscreen-mode': isFullscreen }"
>
<div class="header"> <div class="header">
<h2> <h2>
<el-icon><PieChart /></el-icon> <el-icon><PieChart /></el-icon>
Token用量统计 Token用量统计
</h2> </h2>
<el-button
:icon="FullScreen"
circle
plain
size="small"
@click="toggleFullscreen"
/>
</div> </div>
<el-card v-loading="loading" class="chart-card"> <el-card v-loading="loading" class="chart-card">
@@ -354,7 +380,7 @@ onBeforeUnmount(() => {
</div> </div>
</template> </template>
<div class="chart-container"> <div class="chart-container">
<div ref="pieChart" class="chart" style="height: 400px;" /> <div ref="pieChart" class="chart" style="height: 450px;" />
</div> </div>
</el-card> </el-card>
@@ -365,16 +391,29 @@ onBeforeUnmount(() => {
</div> </div>
</template> </template>
<div class="chart-container"> <div class="chart-container">
<div ref="barChart" class="chart" style="height: 400px;" /> <div ref="barChart" class="chart" style="height: 500px;" />
</div> </div>
</el-card> </el-card>
</div> </div>
</template> </template>
<style scoped> <style scoped>
/* 样式保持不变 */
.usage-statistics { .usage-statistics {
padding: 20px; 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 { .header {
@@ -421,6 +460,19 @@ onBeforeUnmount(() => {
.header { .header {
flex-direction: column; flex-direction: column;
align-items: flex-start; align-items: flex-start;
gap: 10px;
}
.chart-card {
margin-bottom: 15px;
}
.chart {
height: 350px !important;
}
.usage-statistics.fullscreen-mode {
padding: 10px;
} }
} }
</style> </style>