feat: 优化token用量查看

This commit is contained in:
Gsh
2025-11-29 23:44:38 +08:00
parent 55469708f0
commit 755cb6f509
4 changed files with 73 additions and 57 deletions

View File

@@ -62,8 +62,8 @@ async function fetchTokenList() {
}
}
catch (error) {
console.error('获取Token列表失败:', error);
ElMessage.error('获取Token列表失败');
console.error('获取API密钥列表失败:', error);
ElMessage.error('获取API密钥列表失败');
}
finally {
loading.value = false;
@@ -142,11 +142,11 @@ async function handleFormSubmit(data: TokenFormData) {
if (formMode.value === 'create') {
await createToken(submitData);
ElMessage.success('Token创建成功');
ElMessage.success('API密钥创建成功');
}
else {
await editToken(submitData);
ElMessage.success('Token更新成功');
ElMessage.success('API密钥更新成功');
}
showFormDialog.value = false;
@@ -154,7 +154,7 @@ async function handleFormSubmit(data: TokenFormData) {
}
catch (error) {
console.error('操作失败:', error);
ElMessage.error(formMode.value === 'create' ? '创建Token失败' : '编辑Token失败');
ElMessage.error(formMode.value === 'create' ? '创建API密钥失败' : '编辑API密钥失败');
}
finally {
loading.value = false;
@@ -168,7 +168,7 @@ async function handleDelete(row: TokenItem) {
try {
await ElMessageBox.confirm(
`确定要删除 Token "${row.name}" 吗?删除后将无法恢复`,
`确定要删除 API密钥 "${row.name}" 吗?删除后将无法恢复`,
'删除确认',
{
confirmButtonText: '确定删除',
@@ -180,7 +180,7 @@ async function handleDelete(row: TokenItem) {
operatingTokenId.value = row.id;
await deleteToken(row.id);
ElMessage.success('Token已删除');
ElMessage.success('API密钥已删除');
await fetchTokenList();
}
catch (error) {
@@ -202,11 +202,11 @@ async function handleToggle(row: TokenItem) {
operatingTokenId.value = row.id;
if (row.isDisabled) {
await enableToken(row.id);
ElMessage.success(`Token "${row.name}" 已启用`);
ElMessage.success(`API密钥 "${row.name}" 已启用`);
}
else {
await disableToken(row.id);
ElMessage.success(`Token "${row.name}" 已禁用`);
ElMessage.success(`API密钥 "${row.name}" 已禁用`);
}
await fetchTokenList();
}
@@ -307,8 +307,8 @@ onMounted(async () => {
</el-icon>
</div>
<div class="header-text">
<span class="header-title">API Token 管理中心</span>
<span class="header-subtitle">管理您的 API 访问密钥每个 Token 拥有独立的配额和使用统计</span>
<span class="header-title">API密钥管理中心</span>
<span class="header-subtitle">管理您的 API 访问密钥每个 API密钥 拥有独立的配额和使用统计</span>
</div>
</div>
<div class="header-right">
@@ -324,7 +324,7 @@ onMounted(async () => {
<div class="toolbar">
<div class="toolbar-left">
<el-button type="primary" :icon="Plus" size="default" @click="showCreateDialog">
新增 Token
新增 API密钥
</el-button>
</div>
<div class="toolbar-right">
@@ -347,7 +347,7 @@ onMounted(async () => {
>
<el-table-column
prop="name"
label="Token 名称"
label="API密钥 名称"
min-width="180"
sortable
show-overflow-tooltip
@@ -544,9 +544,9 @@ onMounted(async () => {
<!-- 空状态 -->
<div v-else-if="!loading" class="empty-container">
<el-empty description="暂无Token,点击下方按钮创建您的第一个 API Token">
<el-empty description="暂无API密钥,点击下方按钮创建您的第一个 API密钥">
<el-button type="primary" size="large" :icon="Plus" @click="showCreateDialog">
创建第一个 Token
创建第一个 API密钥
</el-button>
</el-empty>
</div>

View File

@@ -12,6 +12,15 @@ import { CanvasRenderer } from 'echarts/renderers';
import { getPremiumPackageTokenUsage } from '@/api';
import { showProductPackage } from '@/utils/product-package.ts';
const props = withDefaults(defineProps<Props>(), {
loading: false,
});
// Emits
const emit = defineEmits<{
refresh: [];
}>();
// 注册必要的组件
echarts.use([
EPieChart,
@@ -35,15 +44,6 @@ interface Props {
loading?: boolean;
}
const props = withDefaults(defineProps<Props>(), {
loading: false,
});
// Emits
const emit = defineEmits<{
refresh: [];
}>();
// 饼图相关
const tokenPieChart = ref(null);
let tokenPieChartInstance: any = null;
@@ -454,8 +454,8 @@ onBeforeUnmount(() => {
<i-ep-pie-chart />
</el-icon>
<div class="header-text">
<span class="header-title">Token用量占比</span>
<span class="header-subtitle">Premium Token Usage Distribution</span>
<span class="header-title">API密钥用量占比</span>
<span class="header-subtitle">Premium APIKEY Usage Distribution</span>
</div>
</div>
</div>
@@ -509,7 +509,9 @@ onBeforeUnmount(() => {
>
<template #image>
<div class="custom-empty-image">
<el-icon class="empty-main-icon"><i-ep-pie-chart /></el-icon>
<el-icon class="empty-main-icon">
<i-ep-pie-chart />
</el-icon>
<div class="empty-decoration">
<div class="decoration-circle" />
<div class="decoration-circle" />
@@ -519,8 +521,12 @@ onBeforeUnmount(() => {
</template>
<template #description>
<div class="empty-description">
<h3 class="empty-title">暂无Token使用数据</h3>
<p class="empty-text">当您开始使用Token后这里将展示各Token的用量占比统计</p>
<h3 class="empty-title">
暂无Token使用数据
</h3>
<p class="empty-text">
当您开始使用Token后这里将展示各Token的用量占比统计
</p>
<div class="empty-tips">
<el-icon><i-ep-info-filled /></el-icon>
<span>创建并使用Token后即可查看详细的用量分析</span>

View File

@@ -1,6 +1,6 @@
<script lang="ts" setup>
import { ref, watch, computed } from 'vue';
import { ElMessage } from 'element-plus';
import { computed, ref, watch } from 'vue';
interface TokenFormData {
id?: string;
@@ -23,8 +23,8 @@ const props = withDefaults(defineProps<Props>(), {
name: '',
expireTime: '',
premiumQuotaLimit: 0,
quotaUnit: '万'
})
quotaUnit: '万',
}),
});
const emit = defineEmits<{
@@ -36,7 +36,7 @@ const localFormData = ref<TokenFormData>({
name: '',
expireTime: '',
premiumQuotaLimit: 0,
quotaUnit: '万'
quotaUnit: '万',
});
const submitting = ref(false);
@@ -49,7 +49,7 @@ const quotaUnitOptions = [
{ label: '百', value: '百', multiplier: 100 },
{ label: '千', value: '千', multiplier: 1000 },
{ label: '万', value: '万', multiplier: 10000 },
{ label: '亿', value: '亿', multiplier: 100000000 }
{ label: '亿', value: '亿', multiplier: 100000000 },
];
// 监听visible变化重置表单
@@ -69,16 +69,20 @@ watch(() => props.visible, (newVal) => {
if (quota >= 100000000 && quota % 100000000 === 0) {
displayValue = quota / 100000000;
unit = '亿';
} else if (quota >= 10000 && quota % 10000 === 0) {
}
else if (quota >= 10000 && quota % 10000 === 0) {
displayValue = quota / 10000;
unit = '万';
} else if (quota >= 1000 && quota % 1000 === 0) {
}
else if (quota >= 1000 && quota % 1000 === 0) {
displayValue = quota / 1000;
unit = '千';
} else if (quota >= 100 && quota % 100 === 0) {
}
else if (quota >= 100 && quota % 100 === 0) {
displayValue = quota / 100;
unit = '百';
} else if (quota >= 10 && quota % 10 === 0) {
}
else if (quota >= 10 && quota % 10 === 0) {
displayValue = quota / 10;
unit = '十';
}
@@ -90,15 +94,16 @@ watch(() => props.visible, (newVal) => {
localFormData.value = {
...props.formData,
premiumQuotaLimit: displayValue,
quotaUnit: unit
quotaUnit: unit,
};
} else {
}
else {
// 新增模式:重置表单
localFormData.value = {
name: '',
expireTime: '',
premiumQuotaLimit: 1,
quotaUnit: '万'
quotaUnit: '万',
};
neverExpire.value = false;
unlimitedQuota.value = false;
@@ -123,14 +128,15 @@ watch(unlimitedQuota, (newVal) => {
// 关闭对话框
function handleClose() {
if (submitting.value) return;
if (submitting.value)
return;
emit('update:visible', false);
}
// 确认提交
async function handleConfirm() {
if (!localFormData.value.name.trim()) {
ElMessage.warning('请输入Token名称');
ElMessage.warning('请输入API密钥名称');
return;
}
@@ -157,17 +163,18 @@ async function handleConfirm() {
const submitData: TokenFormData = {
...localFormData.value,
expireTime: neverExpire.value ? '' : localFormData.value.expireTime,
premiumQuotaLimit: actualQuota
premiumQuotaLimit: actualQuota,
};
emit('confirm', submitData);
} finally {
}
finally {
// 注意:这里不设置 submitting.value = false
// 因为父组件会关闭对话框watch会重置状态
}
}
const dialogTitle = computed(() => props.mode === 'create' ? '新增 Token' : '编辑 Token');
const dialogTitle = computed(() => props.mode === 'create' ? '新增 API密钥' : '编辑 API密钥');
</script>
<template>
@@ -180,7 +187,7 @@ const dialogTitle = computed(() => props.mode === 'create' ? '新增 Token' : '
@close="handleClose"
>
<el-form :model="localFormData" label-width="110px" label-position="right">
<el-form-item label="Token名称" required>
<el-form-item label="API密钥名称" required>
<el-input
v-model="localFormData.name"
placeholder="例如:生产环境、测试环境、开发环境"
@@ -222,7 +229,7 @@ const dialogTitle = computed(() => props.mode === 'create' ? '新增 Token' : '
</div>
<div v-if="!neverExpire" class="form-hint">
<el-icon><i-ep-warning /></el-icon>
Token将在过期时间后自动失效
API密钥将在过期时间后自动失效
</div>
</el-form-item>
@@ -262,21 +269,21 @@ const dialogTitle = computed(() => props.mode === 'create' ? '新增 Token' : '
</div>
<div v-if="!unlimitedQuota" class="form-hint">
<el-icon><i-ep-info-filled /></el-icon>
超出配额后Token将无法继续使用
超出配额后API密钥将无法继续使用
</div>
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button @click="handleClose" :disabled="submitting">
<el-button :disabled="submitting" @click="handleClose">
取消
</el-button>
<el-button
type="primary"
@click="handleConfirm"
:loading="submitting"
:disabled="submitting"
@click="handleConfirm"
>
{{ mode === 'create' ? '创建' : '保存' }}
</el-button>

View File

@@ -58,9 +58,10 @@ const hasModelData = computed(() => modelUsageData.value.length > 0);
// 计算属性当前选择的token名称
const selectedTokenName = computed(() => {
if (!selectedTokenId.value) return '全部Token';
if (!selectedTokenId.value)
return '全部API密钥';
const token = tokenOptions.value.find(t => t.tokenId === selectedTokenId.value);
return token?.name || '未知Token';
return token?.name || '未知API密钥';
});
// 获取可选择的Token列表
@@ -74,8 +75,8 @@ async function fetchTokenOptions() {
}
}
catch (error) {
console.error('获取Token列表失败:', error);
ElMessage.error('获取Token列表失败');
console.error('获取API密钥列表失败:', error);
ElMessage.error('获取TAPI密钥列表失败');
}
finally {
tokenOptionsLoading.value = false;
@@ -514,7 +515,7 @@ onBeforeUnmount(() => {
<div class="header-actions">
<el-select
v-model="selectedTokenId"
placeholder="选择Token"
placeholder="选择API密钥"
clearable
filterable
:loading="tokenOptionsLoading"
@@ -523,7 +524,9 @@ onBeforeUnmount(() => {
>
<el-option label="全部Token" value="">
<div class="token-option">
<el-icon class="option-icon all-icon"><i-ep-folder-opened /></el-icon>
<el-icon class="option-icon all-icon">
<i-ep-folder-opened />
</el-icon>
<span class="option-label">全部Token</span>
</div>
</el-option>