token用量添加计费单位

This commit is contained in:
hrx 2026-06-22 14:17:22 +08:00
parent 80c131445f
commit 8ea251b144
5 changed files with 86 additions and 20 deletions

View File

@ -12,6 +12,8 @@
size="small"
prefix-icon="el-icon-search"
placeholder="请输入模型名称"
@keyup.enter.native="$emit('search')"
@clear="$emit('search')"
></el-input>
</el-form-item>
<el-form-item label="模型类型">
@ -21,6 +23,8 @@
filterable
size="small"
placeholder="请选择模型类型"
@change="$emit('search')"
@clear="$emit('search')"
>
<el-option
v-for="item in modelTypeOptions"
@ -37,6 +41,8 @@
filterable
size="small"
placeholder="请选择供应商"
@change="$emit('search')"
@clear="$emit('search')"
>
<el-option
v-for="item in providerOptions"

View File

@ -232,7 +232,24 @@
computed: {
filteredModelList() {
const selectedStatus = this.activeStatus === 'pending' ? 0 : 1
return this.modelList.filter(model => Number(model.listing_status) === selectedStatus)
const modelName = this.normalizeSearchText(this.searchForm.name)
const modelType = this.normalizeSearchText(this.searchForm.type)
const provider = this.normalizeSearchText(this.searchForm.provider)
return this.modelList.filter(model => {
if (Number(model.listing_status) !== selectedStatus) return false
const displayName = this.normalizeSearchText(this.getModelDisplayName(model))
const id = this.normalizeSearchText(this.getModelId(model))
const type = this.normalizeSearchText(this.getModelType(model))
const modelProvider = this.normalizeSearchText(this.getProvider(model))
const nameMatched = !modelName || displayName.includes(modelName) || id.includes(modelName)
const typeMatched = !modelType || type === modelType || type.includes(modelType)
const providerMatched = !provider || modelProvider === provider || modelProvider.includes(provider)
return nameMatched && typeMatched && providerMatched
})
},
pagedModelList() {
const start = (this.currentPage - 1) * this.pageSize
@ -282,6 +299,9 @@
if (this.searchForm.provider) params.provider = this.searchForm.provider
return params
},
normalizeSearchText(value) {
return String(value || '').trim().toLowerCase()
},
extractModelData(res) {
const data = res?.data ?? res
if (data?.model_list) return data

View File

@ -24,7 +24,10 @@
<div class="stat-title">{{ item.label }}</div>
<i :class="item.icon"></i>
</div>
<div class="stat-value">{{ item.value }}</div>
<div class="stat-value">
{{ item.value }}
<span v-if="item.unit" class="stat-unit">{{ item.unit }}</span>
</div>
<div class="stat-desc">{{ item.desc }}</div>
</div>
</div>
@ -91,18 +94,35 @@
<el-tag size="mini" type="info">{{ scope.row.model || '-' }}</el-tag>
</template>
</el-table-column>
<el-table-column prop="request_count" label="请求次数" width="100"></el-table-column>
<el-table-column prop="prompt_tokens" label="输入Token" min-width="110">
<template slot-scope="scope">{{ formatNumber(scope.row.prompt_tokens) }}</template>
<el-table-column prop="request_count" label="请求次数" width="110">
<template slot-scope="scope">
<span class="usage-number">{{ formatNumber(scope.row.request_count) }}</span>
<span class="usage-unit"></span>
</template>
</el-table-column>
<el-table-column prop="completion_tokens" label="输出Token" min-width="110">
<template slot-scope="scope">{{ formatNumber(scope.row.completion_tokens) }}</template>
<el-table-column prop="prompt_tokens" label="输入Token" min-width="130">
<template slot-scope="scope">
<span class="usage-number">{{ formatNumber(scope.row.prompt_tokens) }}</span>
<span class="usage-unit">Token</span>
</template>
</el-table-column>
<el-table-column prop="total_tokens" label="总Token" min-width="110">
<template slot-scope="scope">{{ formatNumber(scope.row.total_tokens) }}</template>
<el-table-column prop="completion_tokens" label="输出Token" min-width="130">
<template slot-scope="scope">
<span class="usage-number">{{ formatNumber(scope.row.completion_tokens) }}</span>
<span class="usage-unit">Token</span>
</template>
</el-table-column>
<el-table-column prop="amount" label="费用(元)" min-width="110">
<template slot-scope="scope">¥{{ formatAmount(scope.row.amount) }}</template>
<el-table-column prop="total_tokens" label="总Token" min-width="130">
<template slot-scope="scope">
<span class="usage-number">{{ formatNumber(scope.row.total_tokens) }}</span>
<span class="usage-unit">Token</span>
</template>
</el-table-column>
<el-table-column prop="amount" label="费用" min-width="110">
<template slot-scope="scope">
<span class="usage-amount">¥{{ formatAmount(scope.row.amount) }}</span>
<span class="usage-unit"></span>
</template>
</el-table-column>
<el-table-column prop="last_usage_time" label="最近使用时间" min-width="160" show-overflow-tooltip></el-table-column>
@ -158,9 +178,9 @@ export default {
computed: {
statCards() {
return [
{ label: '请求次数', value: this.formatNumber(this.summary.request_count), desc: '当前筛选范围', type: 'purple', icon: 'el-icon-s-promotion' },
{ label: 'Token消耗', value: this.formatNumber(this.summary.total_tokens), desc: `输入 ${this.formatNumber(this.summary.prompt_tokens)} / 输出 ${this.formatNumber(this.summary.completion_tokens)}`, type: 'green', icon: 'el-icon-coin' },
{ label: 'Token总费用', value: `¥${this.formatAmount(this.summary.amount)}`, desc: '按调用记录汇总', type: 'orange', icon: 'el-icon-wallet' }
{ label: '请求次数', value: this.formatNumber(this.summary.request_count), unit: '次', desc: '当前筛选范围', type: 'purple', icon: 'el-icon-s-promotion' },
{ label: 'Token消耗', value: this.formatNumber(this.summary.total_tokens), unit: 'Token', desc: `输入 ${this.formatNumber(this.summary.prompt_tokens)} Token / 输出 ${this.formatNumber(this.summary.completion_tokens)} Token`, type: 'green', icon: 'el-icon-coin' },
{ label: 'Token总费用', value: `¥${this.formatAmount(this.summary.amount)}`, unit: '元', desc: '按调用记录汇总', type: 'orange', icon: 'el-icon-wallet' }
]
},
filterTimeText() {
@ -245,7 +265,7 @@ export default {
return Number(value || 0).toLocaleString()
},
formatAmount(value) {
return Number(value || 0).toFixed(6).replace(/0+$/, '').replace(/\.$/, '.00')
return Number(value || 0).toFixed(2)
}
}
}
@ -400,6 +420,13 @@ export default {
line-height: 1;
}
.stat-unit {
margin-left: 6px;
color: #667085;
font-size: 14px;
font-weight: 500;
}
.stat-desc {
margin-top: 10px;
color: #98a2b3;
@ -505,6 +532,19 @@ export default {
}
}
.usage-number,
.usage-amount {
color: #344054;
font-weight: 600;
}
.usage-unit {
margin-left: 4px;
color: #98a2b3;
font-size: 12px;
font-weight: 400;
}
@media (max-width: 900px) {
.stat-grid {
grid-template-columns: 1fr;

View File

@ -191,12 +191,12 @@ export default {
try {
const res = await reqApikeyList(params)
if (res && res.status === false) {
throw new Error(res.msg || '获取 API Key 列表失败')
// throw new Error(res.msg || ' API Key ')
}
this.tokenList = this.normalizeApiKeyList(res)
} catch (error) {
this.tokenList = []
this.$message.error(error && error.message ? error.message : '获取 API Key 列表失败')
// this.$message.error(error && error.message ? error.message : ' API Key ')
} finally {
this.tableLoading = false
}

View File

@ -281,7 +281,7 @@ export default {
return Number(value || 0).toLocaleString()
},
formatAmount(value) {
return Number(value || 0).toFixed(6).replace(/0+$/, '').replace(/\.$/, '.00')
return Number(value || 0).toFixed(2)
},
initCharts() {
if (this.$refs.tokenRatioChart && !this.tokenRatioChart) {
@ -373,8 +373,8 @@ export default {
return [
item.name,
`${this.formatNumber(item.value)} Token`,
`调用次数:${this.formatNumber(rankItem.request_count)}`,
`预估费用:¥ ${this.formatAmount(rankItem.amount)}`
`调用次数:${this.formatNumber(rankItem.request_count)}`,
`预估费用:¥ ${this.formatAmount(rankItem.amount)}`
].join('<br/>')
}
},