fix: 增加对话token显示,token消耗统计
This commit is contained in:
@@ -17,7 +17,7 @@ interface Props {
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
title: '弹窗标题',
|
||||
width: '800px',
|
||||
width: '1000px',
|
||||
defaultActive: '',
|
||||
});
|
||||
|
||||
@@ -55,6 +55,7 @@ function handleConfirm() {
|
||||
|
||||
<template>
|
||||
<el-dialog
|
||||
|
||||
v-model="visible"
|
||||
:title="title"
|
||||
:width="width"
|
||||
|
||||
@@ -0,0 +1,426 @@
|
||||
<script lang="ts" setup>
|
||||
import { PieChart } from '@element-plus/icons-vue';
|
||||
import { BarChart, PieChart as EPieChart, LineChart } from 'echarts/charts';
|
||||
import {
|
||||
GraphicComponent,
|
||||
GridComponent,
|
||||
LegendComponent,
|
||||
TitleComponent,
|
||||
TooltipComponent,
|
||||
} from 'echarts/components';
|
||||
// 按需引入 ECharts
|
||||
import * as echarts from 'echarts/core';
|
||||
import { CanvasRenderer } from 'echarts/renderers';
|
||||
import { getLast7DaysTokenUsage, getModelTokenUsage } from '@/api';
|
||||
|
||||
// 注册必要的组件
|
||||
echarts.use([
|
||||
LineChart,
|
||||
EPieChart,
|
||||
BarChart,
|
||||
TitleComponent,
|
||||
TooltipComponent,
|
||||
GridComponent,
|
||||
LegendComponent,
|
||||
GraphicComponent,
|
||||
CanvasRenderer,
|
||||
]);
|
||||
|
||||
// 图表引用
|
||||
const lineChart = ref(null);
|
||||
const pieChart = ref(null);
|
||||
const barChart = ref(null);
|
||||
let lineChartInstance: any = null;
|
||||
let pieChartInstance: any = null;
|
||||
let barChartInstance: any = null;
|
||||
|
||||
// 数据
|
||||
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;
|
||||
try {
|
||||
const [res, res2] = await Promise.all([
|
||||
getLast7DaysTokenUsage(),
|
||||
getModelTokenUsage(),
|
||||
]);
|
||||
|
||||
usageData.value = res.data || [];
|
||||
modelUsageData.value = res2.data || [];
|
||||
totalTokens.value = usageData.value.reduce((sum, item) => sum + item.tokens, 0);
|
||||
|
||||
updateCharts();
|
||||
}
|
||||
catch (error) {
|
||||
console.error('获取用量数据失败:', error);
|
||||
ElMessage.error('获取用量数据失败');
|
||||
}
|
||||
finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化图表
|
||||
function initCharts() {
|
||||
if (lineChart.value) {
|
||||
lineChartInstance = echarts.init(lineChart.value);
|
||||
}
|
||||
if (pieChart.value) {
|
||||
pieChartInstance = echarts.init(pieChart.value);
|
||||
}
|
||||
if (barChart.value) {
|
||||
barChartInstance = echarts.init(barChart.value);
|
||||
}
|
||||
|
||||
window.addEventListener('resize', resizeCharts);
|
||||
}
|
||||
|
||||
// 更新图表数据
|
||||
function updateCharts() {
|
||||
updateLineChart();
|
||||
updatePieChart();
|
||||
updateBarChart();
|
||||
}
|
||||
|
||||
// 更新折线图
|
||||
function updateLineChart() {
|
||||
if (!lineChartInstance)
|
||||
return;
|
||||
|
||||
const dates = usageData.value.map((item) => {
|
||||
const date = new Date(item.date);
|
||||
return `${date.getMonth() + 1}月${date.getDate()}日`;
|
||||
});
|
||||
|
||||
const tokens = usageData.value.map(item => item.tokens);
|
||||
|
||||
const option = {
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
formatter: '{b}<br/>用量: {c} ',
|
||||
},
|
||||
grid: {
|
||||
left: '3%',
|
||||
right: '4%',
|
||||
bottom: '3%',
|
||||
containLabel: true,
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: dates,
|
||||
axisLabel: {
|
||||
rotate: 45,
|
||||
},
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
name: 'Token用量',
|
||||
axisLine: {
|
||||
show: true,
|
||||
},
|
||||
},
|
||||
series: [{
|
||||
data: tokens,
|
||||
type: 'line',
|
||||
smooth: true,
|
||||
areaStyle: {
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||
{ offset: 0, color: 'rgba(58, 77, 233, 0.8)' },
|
||||
{ offset: 1, color: 'rgba(58, 77, 233, 0.1)' },
|
||||
]),
|
||||
},
|
||||
itemStyle: {
|
||||
color: '#3a4de9',
|
||||
},
|
||||
lineStyle: {
|
||||
width: 3,
|
||||
},
|
||||
symbolSize: 8,
|
||||
}],
|
||||
};
|
||||
|
||||
lineChartInstance.setOption(option);
|
||||
}
|
||||
|
||||
// 更新饼图
|
||||
function updatePieChart() {
|
||||
if (!pieChartInstance || modelUsageData.value.length === 0)
|
||||
return;
|
||||
|
||||
const data = modelUsageData.value.map(item => ({
|
||||
name: item.model,
|
||||
value: item.tokens,
|
||||
}));
|
||||
|
||||
const option = {
|
||||
tooltip: {
|
||||
trigger: 'item',
|
||||
formatter: '{a} <br/>{b}: {c} tokens ({d}%)',
|
||||
},
|
||||
legend: {
|
||||
orient: 'vertical',
|
||||
right: 10,
|
||||
top: 'center',
|
||||
data: data.map(item => item.name),
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '模型用量',
|
||||
type: 'pie',
|
||||
radius: ['50%', '70%'],
|
||||
avoidLabelOverlap: false,
|
||||
itemStyle: {
|
||||
borderRadius: 10,
|
||||
borderColor: '#fff',
|
||||
borderWidth: 2,
|
||||
},
|
||||
label: {
|
||||
show: false,
|
||||
position: 'center',
|
||||
},
|
||||
emphasis: {
|
||||
label: {
|
||||
show: true,
|
||||
fontSize: '18',
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
},
|
||||
labelLine: {
|
||||
show: false,
|
||||
},
|
||||
data,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
pieChartInstance.setOption(option);
|
||||
}
|
||||
|
||||
// 更新柱状图
|
||||
function updateBarChart() {
|
||||
if (!barChartInstance || modelUsageData.value.length === 0)
|
||||
return;
|
||||
|
||||
const models = modelUsageData.value.map(item => item.model);
|
||||
const tokens = modelUsageData.value.map(item => item.tokens);
|
||||
|
||||
const option = {
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
type: 'shadow',
|
||||
},
|
||||
formatter: '{b}<br/>用量: {c} tokens',
|
||||
},
|
||||
grid: {
|
||||
left: '3%',
|
||||
right: '4%',
|
||||
bottom: '3%',
|
||||
containLabel: true,
|
||||
},
|
||||
xAxis: {
|
||||
type: 'value',
|
||||
name: 'Token用量',
|
||||
},
|
||||
yAxis: {
|
||||
type: 'category',
|
||||
data: models,
|
||||
axisLabel: {
|
||||
interval: 0,
|
||||
},
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '用量',
|
||||
type: 'bar',
|
||||
data: tokens,
|
||||
itemStyle: {
|
||||
color(params: any) {
|
||||
const colorList = [
|
||||
'#3a4de9',
|
||||
'#6a5acd',
|
||||
'#9370db',
|
||||
'#8a2be2',
|
||||
'#9932cc',
|
||||
'#ba55d3',
|
||||
'#da70d6',
|
||||
'#ee82ee',
|
||||
'#dda0dd',
|
||||
'#ff00ff',
|
||||
];
|
||||
return colorList[params.dataIndex % colorList.length];
|
||||
},
|
||||
borderRadius: [0, 4, 4, 0],
|
||||
},
|
||||
label: {
|
||||
show: true,
|
||||
position: 'right',
|
||||
formatter: '{c} ',
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
barChartInstance.setOption(option);
|
||||
}
|
||||
|
||||
// 调整图表大小
|
||||
function resizeCharts() {
|
||||
lineChartInstance?.resize();
|
||||
pieChartInstance?.resize();
|
||||
barChartInstance?.resize();
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
initCharts();
|
||||
fetchUsageData();
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
window.removeEventListener('resize', resizeCharts);
|
||||
lineChartInstance?.dispose();
|
||||
pieChartInstance?.dispose();
|
||||
barChartInstance?.dispose();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="usage-statistics">
|
||||
<div class="header">
|
||||
<h2>
|
||||
<el-icon><PieChart /></el-icon>
|
||||
Token用量统计
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<el-card v-loading="loading" class="chart-card">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>近七天每日Token消耗量</span>
|
||||
<el-tag type="primary">
|
||||
总计: {{ totalTokens }} tokens
|
||||
</el-tag>
|
||||
</div>
|
||||
</template>
|
||||
<div class="chart-container">
|
||||
<div ref="lineChart" class="chart" style="height: 400px;" />
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<el-card v-loading="loading" class="chart-card">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>各模型Token消耗占比</span>
|
||||
</div>
|
||||
</template>
|
||||
<div class="chart-container">
|
||||
<div ref="pieChart" class="chart" style="height: 400px;" />
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<el-card v-loading="loading" class="chart-card">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>各模型Token消耗量</span>
|
||||
</div>
|
||||
</template>
|
||||
<div class="chart-container">
|
||||
<div ref="barChart" class="chart" style="height: 400px;" />
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
/* 样式保持不变 */
|
||||
.usage-statistics {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.header h2 {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin: 0;
|
||||
font-size: 20px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.header .el-icon {
|
||||
margin-right: 8px;
|
||||
color: #3a4de9;
|
||||
}
|
||||
|
||||
.chart-card {
|
||||
margin-bottom: 20px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.chart-container {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.chart {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.header {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user