feat: 兼容claude格式

This commit is contained in:
chenchun
2026-01-05 15:54:14 +08:00
parent b4a97e8b09
commit 29c1768ded
11 changed files with 645 additions and 0 deletions

View File

@@ -0,0 +1,17 @@
import { post } from '@/utils/request';
import type {
ProfitStatisticsInput,
ProfitStatisticsOutput,
TokenStatisticsInput,
TokenStatisticsOutput,
} from './types';
// 获取利润统计数据
export function getProfitStatistics(data: ProfitStatisticsInput) {
return post<ProfitStatisticsOutput>('/system-statistics/profit', data).json();
}
// 获取指定日期各模型Token统计
export function getTokenStatistics(data: TokenStatisticsInput) {
return post<TokenStatisticsOutput>('/system-statistics/token', data).json();
}

View File

@@ -0,0 +1,41 @@
// 利润统计输入
export interface ProfitStatisticsInput {
currentCost: number;
}
// 利润统计输出
export interface ProfitStatisticsOutput {
date: string;
totalUsedTokens: number;
totalUsedTokensInHundredMillion: number;
totalRemainingTokens: number;
totalRemainingTokensInHundredMillion: number;
currentCost: number;
costPerHundredMillion: number;
totalCost: number;
totalRevenue: number;
profitRate: number;
costAt200Price: number;
}
// Token统计输入
export interface TokenStatisticsInput {
date: string;
}
// 模型Token统计DTO
export interface ModelTokenStatisticsDto {
modelId: string;
modelName: string;
tokens: number;
tokensInWan: number;
count: number;
cost: number;
costPerHundredMillion: number;
}
// Token统计输出
export interface TokenStatisticsOutput {
date: string;
modelStatistics: ModelTokenStatisticsDto[];
}

View File

@@ -0,0 +1,219 @@
<script setup lang="ts">
import { onMounted, ref } from 'vue';
import { ElMessage } from 'element-plus';
import { Refresh } from '@element-plus/icons-vue';
import { getProfitStatistics, getTokenStatistics } from '@/api/systemStatistics';
import type { ProfitStatisticsOutput, TokenStatisticsOutput } from '@/api/systemStatistics/types';
// ==================== 利润统计 ====================
const profitLoading = ref(false);
const profitData = ref<ProfitStatisticsOutput | null>(null);
const currentCost = ref<number>(0);
// 获取利润统计
async function fetchProfitStatistics() {
if (!currentCost.value || currentCost.value <= 0) {
ElMessage.warning('请输入当前成本');
return;
}
profitLoading.value = true;
try {
const res = await getProfitStatistics({
currentCost: currentCost.value,
});
profitData.value = res.data;
}
catch (error: any) {
ElMessage.error(error.message || '获取利润统计失败');
}
finally {
profitLoading.value = false;
}
}
// ==================== Token统计 ====================
const tokenLoading = ref(false);
const tokenData = ref<TokenStatisticsOutput | null>(null);
const selectedDate = ref<string>(new Date().toISOString().split('T')[0]);
// 获取Token统计
async function fetchTokenStatistics() {
if (!selectedDate.value) {
ElMessage.warning('请选择日期');
return;
}
tokenLoading.value = true;
try {
const res = await getTokenStatistics({
date: selectedDate.value,
});
tokenData.value = res.data;
}
catch (error: any) {
ElMessage.error(error.message || '获取Token统计失败');
}
finally {
tokenLoading.value = false;
}
}
// 初始化
onMounted(() => {
// 页面加载时不自动获取数据,等待用户输入参数后手动获取
});
</script>
<template>
<div class="system-statistics">
<div class="statistics-container">
<!-- 利润统计卡片 -->
<el-card class="statistics-card">
<template #header>
<div class="card-header">
<h3>利润统计</h3>
<el-button type="primary" size="small" :icon="Refresh" :loading="profitLoading" @click="fetchProfitStatistics">
查询
</el-button>
</div>
</template>
<div class="input-section">
<el-form :inline="true">
<el-form-item label="当前成本(RMB)">
<el-input-number
v-model="currentCost"
:min="0"
:precision="2"
placeholder="请输入当前成本"
style="width: 200px"
/>
</el-form-item>
</el-form>
</div>
<div v-if="profitData" v-loading="profitLoading" class="data-display">
<el-descriptions :column="2" border>
<el-descriptions-item label="日期">{{ profitData.date }}</el-descriptions-item>
<el-descriptions-item label="当前成本">{{ profitData.currentCost.toFixed(2) }} RMB</el-descriptions-item>
<el-descriptions-item label="尊享包已消耗">
{{ profitData.totalUsedTokensInHundredMillion.toFixed(2) }}亿 ({{ profitData.totalUsedTokens }})
</el-descriptions-item>
<el-descriptions-item label="尊享包剩余库存">
{{ profitData.totalRemainingTokensInHundredMillion.toFixed(2) }}亿 ({{ profitData.totalRemainingTokens }})
</el-descriptions-item>
<el-descriptions-item label="1亿Token成本">
{{ profitData.costPerHundredMillion.toFixed(2) }} RMB
</el-descriptions-item>
<el-descriptions-item label="总成本">
{{ profitData.totalCost.toFixed(2) }} RMB
</el-descriptions-item>
<el-descriptions-item label="总收益">
{{ profitData.totalRevenue.toFixed(2) }} RMB
</el-descriptions-item>
<el-descriptions-item label="利润率">
<el-tag :type="profitData.profitRate > 0 ? 'success' : 'danger'">
{{ profitData.profitRate.toFixed(1) }}%
</el-tag>
</el-descriptions-item>
<el-descriptions-item label="按200售价计算成本" :span="2">
{{ profitData.costAt200Price.toFixed(2) }} RMB
</el-descriptions-item>
</el-descriptions>
</div>
</el-card>
<!-- Token统计卡片 -->
<el-card class="statistics-card">
<template #header>
<div class="card-header">
<h3>Token统计</h3>
<el-button type="primary" size="small" :icon="Refresh" :loading="tokenLoading" @click="fetchTokenStatistics">
查询
</el-button>
</div>
</template>
<div class="input-section">
<el-form :inline="true">
<el-form-item label="选择日期">
<el-date-picker
v-model="selectedDate"
type="date"
placeholder="选择日期"
format="YYYY-MM-DD"
value-format="YYYY-MM-DD"
style="width: 200px"
/>
</el-form-item>
</el-form>
</div>
<div v-if="tokenData" v-loading="tokenLoading" class="data-display">
<div class="date-info">
<h4>{{ tokenData.date }}</h4>
</div>
<el-table :data="tokenData.modelStatistics" border stripe>
<el-table-column prop="modelName" label="模型名称" min-width="150" />
<el-table-column prop="modelId" label="模型ID" min-width="200" show-overflow-tooltip />
<el-table-column label="Token消耗" min-width="120">
<template #default="{ row }">
{{ row.tokensInWan.toFixed(0) }}w
</template>
</el-table-column>
<el-table-column prop="count" label="使用次数" width="100" />
</el-table>
</div>
</el-card>
</div>
</div>
</template>
<style scoped lang="scss">
.system-statistics {
padding: 20px;
height: 100vh;
overflow-y: auto;
.statistics-container {
max-width: 1400px;
margin: 0 auto;
}
.statistics-card {
margin-bottom: 20px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
h3 {
margin: 0;
font-size: 16px;
font-weight: 600;
}
}
.input-section {
margin-bottom: 20px;
}
.data-display {
.date-info {
margin-bottom: 16px;
h4 {
margin: 0;
font-size: 14px;
color: #606266;
}
}
}
}
}
</style>