feat: 优化token用量查看
This commit is contained in:
@@ -62,8 +62,8 @@ async function fetchTokenList() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (error) {
|
catch (error) {
|
||||||
console.error('获取Token列表失败:', error);
|
console.error('获取API密钥列表失败:', error);
|
||||||
ElMessage.error('获取Token列表失败');
|
ElMessage.error('获取API密钥列表失败');
|
||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
loading.value = false;
|
loading.value = false;
|
||||||
@@ -142,11 +142,11 @@ async function handleFormSubmit(data: TokenFormData) {
|
|||||||
|
|
||||||
if (formMode.value === 'create') {
|
if (formMode.value === 'create') {
|
||||||
await createToken(submitData);
|
await createToken(submitData);
|
||||||
ElMessage.success('Token创建成功');
|
ElMessage.success('API密钥创建成功');
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
await editToken(submitData);
|
await editToken(submitData);
|
||||||
ElMessage.success('Token更新成功');
|
ElMessage.success('API密钥更新成功');
|
||||||
}
|
}
|
||||||
|
|
||||||
showFormDialog.value = false;
|
showFormDialog.value = false;
|
||||||
@@ -154,7 +154,7 @@ async function handleFormSubmit(data: TokenFormData) {
|
|||||||
}
|
}
|
||||||
catch (error) {
|
catch (error) {
|
||||||
console.error('操作失败:', error);
|
console.error('操作失败:', error);
|
||||||
ElMessage.error(formMode.value === 'create' ? '创建Token失败' : '编辑Token失败');
|
ElMessage.error(formMode.value === 'create' ? '创建API密钥失败' : '编辑API密钥失败');
|
||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
loading.value = false;
|
loading.value = false;
|
||||||
@@ -168,7 +168,7 @@ async function handleDelete(row: TokenItem) {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
await ElMessageBox.confirm(
|
await ElMessageBox.confirm(
|
||||||
`确定要删除 Token "${row.name}" 吗?删除后将无法恢复`,
|
`确定要删除 API密钥 "${row.name}" 吗?删除后将无法恢复`,
|
||||||
'删除确认',
|
'删除确认',
|
||||||
{
|
{
|
||||||
confirmButtonText: '确定删除',
|
confirmButtonText: '确定删除',
|
||||||
@@ -180,7 +180,7 @@ async function handleDelete(row: TokenItem) {
|
|||||||
|
|
||||||
operatingTokenId.value = row.id;
|
operatingTokenId.value = row.id;
|
||||||
await deleteToken(row.id);
|
await deleteToken(row.id);
|
||||||
ElMessage.success('Token已删除');
|
ElMessage.success('API密钥已删除');
|
||||||
await fetchTokenList();
|
await fetchTokenList();
|
||||||
}
|
}
|
||||||
catch (error) {
|
catch (error) {
|
||||||
@@ -202,11 +202,11 @@ async function handleToggle(row: TokenItem) {
|
|||||||
operatingTokenId.value = row.id;
|
operatingTokenId.value = row.id;
|
||||||
if (row.isDisabled) {
|
if (row.isDisabled) {
|
||||||
await enableToken(row.id);
|
await enableToken(row.id);
|
||||||
ElMessage.success(`Token "${row.name}" 已启用`);
|
ElMessage.success(`API密钥 "${row.name}" 已启用`);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
await disableToken(row.id);
|
await disableToken(row.id);
|
||||||
ElMessage.success(`Token "${row.name}" 已禁用`);
|
ElMessage.success(`API密钥 "${row.name}" 已禁用`);
|
||||||
}
|
}
|
||||||
await fetchTokenList();
|
await fetchTokenList();
|
||||||
}
|
}
|
||||||
@@ -307,8 +307,8 @@ onMounted(async () => {
|
|||||||
</el-icon>
|
</el-icon>
|
||||||
</div>
|
</div>
|
||||||
<div class="header-text">
|
<div class="header-text">
|
||||||
<span class="header-title">API Token 管理中心</span>
|
<span class="header-title">API密钥管理中心</span>
|
||||||
<span class="header-subtitle">管理您的 API 访问密钥,每个 Token 拥有独立的配额和使用统计</span>
|
<span class="header-subtitle">管理您的 API 访问密钥,每个 API密钥 拥有独立的配额和使用统计</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="header-right">
|
<div class="header-right">
|
||||||
@@ -324,7 +324,7 @@ onMounted(async () => {
|
|||||||
<div class="toolbar">
|
<div class="toolbar">
|
||||||
<div class="toolbar-left">
|
<div class="toolbar-left">
|
||||||
<el-button type="primary" :icon="Plus" size="default" @click="showCreateDialog">
|
<el-button type="primary" :icon="Plus" size="default" @click="showCreateDialog">
|
||||||
新增 Token
|
新增 API密钥
|
||||||
</el-button>
|
</el-button>
|
||||||
</div>
|
</div>
|
||||||
<div class="toolbar-right">
|
<div class="toolbar-right">
|
||||||
@@ -347,7 +347,7 @@ onMounted(async () => {
|
|||||||
>
|
>
|
||||||
<el-table-column
|
<el-table-column
|
||||||
prop="name"
|
prop="name"
|
||||||
label="Token 名称"
|
label="API密钥 名称"
|
||||||
min-width="180"
|
min-width="180"
|
||||||
sortable
|
sortable
|
||||||
show-overflow-tooltip
|
show-overflow-tooltip
|
||||||
@@ -544,9 +544,9 @@ onMounted(async () => {
|
|||||||
|
|
||||||
<!-- 空状态 -->
|
<!-- 空状态 -->
|
||||||
<div v-else-if="!loading" class="empty-container">
|
<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">
|
<el-button type="primary" size="large" :icon="Plus" @click="showCreateDialog">
|
||||||
创建第一个 Token
|
创建第一个 API密钥
|
||||||
</el-button>
|
</el-button>
|
||||||
</el-empty>
|
</el-empty>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -12,6 +12,15 @@ import { CanvasRenderer } from 'echarts/renderers';
|
|||||||
import { getPremiumPackageTokenUsage } from '@/api';
|
import { getPremiumPackageTokenUsage } from '@/api';
|
||||||
import { showProductPackage } from '@/utils/product-package.ts';
|
import { showProductPackage } from '@/utils/product-package.ts';
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
|
loading: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Emits
|
||||||
|
const emit = defineEmits<{
|
||||||
|
refresh: [];
|
||||||
|
}>();
|
||||||
|
|
||||||
// 注册必要的组件
|
// 注册必要的组件
|
||||||
echarts.use([
|
echarts.use([
|
||||||
EPieChart,
|
EPieChart,
|
||||||
@@ -35,15 +44,6 @@ interface Props {
|
|||||||
loading?: boolean;
|
loading?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = withDefaults(defineProps<Props>(), {
|
|
||||||
loading: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Emits
|
|
||||||
const emit = defineEmits<{
|
|
||||||
refresh: [];
|
|
||||||
}>();
|
|
||||||
|
|
||||||
// 饼图相关
|
// 饼图相关
|
||||||
const tokenPieChart = ref(null);
|
const tokenPieChart = ref(null);
|
||||||
let tokenPieChartInstance: any = null;
|
let tokenPieChartInstance: any = null;
|
||||||
@@ -454,8 +454,8 @@ onBeforeUnmount(() => {
|
|||||||
<i-ep-pie-chart />
|
<i-ep-pie-chart />
|
||||||
</el-icon>
|
</el-icon>
|
||||||
<div class="header-text">
|
<div class="header-text">
|
||||||
<span class="header-title">各Token用量占比</span>
|
<span class="header-title">各API密钥用量占比</span>
|
||||||
<span class="header-subtitle">Premium Token Usage Distribution</span>
|
<span class="header-subtitle">Premium APIKEY Usage Distribution</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -509,7 +509,9 @@ onBeforeUnmount(() => {
|
|||||||
>
|
>
|
||||||
<template #image>
|
<template #image>
|
||||||
<div class="custom-empty-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="empty-decoration">
|
||||||
<div class="decoration-circle" />
|
<div class="decoration-circle" />
|
||||||
<div class="decoration-circle" />
|
<div class="decoration-circle" />
|
||||||
@@ -519,8 +521,12 @@ onBeforeUnmount(() => {
|
|||||||
</template>
|
</template>
|
||||||
<template #description>
|
<template #description>
|
||||||
<div class="empty-description">
|
<div class="empty-description">
|
||||||
<h3 class="empty-title">暂无Token使用数据</h3>
|
<h3 class="empty-title">
|
||||||
<p class="empty-text">当您开始使用Token后,这里将展示各Token的用量占比统计</p>
|
暂无Token使用数据
|
||||||
|
</h3>
|
||||||
|
<p class="empty-text">
|
||||||
|
当您开始使用Token后,这里将展示各Token的用量占比统计
|
||||||
|
</p>
|
||||||
<div class="empty-tips">
|
<div class="empty-tips">
|
||||||
<el-icon><i-ep-info-filled /></el-icon>
|
<el-icon><i-ep-info-filled /></el-icon>
|
||||||
<span>创建并使用Token后即可查看详细的用量分析</span>
|
<span>创建并使用Token后即可查看详细的用量分析</span>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref, watch, computed } from 'vue';
|
|
||||||
import { ElMessage } from 'element-plus';
|
import { ElMessage } from 'element-plus';
|
||||||
|
import { computed, ref, watch } from 'vue';
|
||||||
|
|
||||||
interface TokenFormData {
|
interface TokenFormData {
|
||||||
id?: string;
|
id?: string;
|
||||||
@@ -23,8 +23,8 @@ const props = withDefaults(defineProps<Props>(), {
|
|||||||
name: '',
|
name: '',
|
||||||
expireTime: '',
|
expireTime: '',
|
||||||
premiumQuotaLimit: 0,
|
premiumQuotaLimit: 0,
|
||||||
quotaUnit: '万'
|
quotaUnit: '万',
|
||||||
})
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
@@ -36,7 +36,7 @@ const localFormData = ref<TokenFormData>({
|
|||||||
name: '',
|
name: '',
|
||||||
expireTime: '',
|
expireTime: '',
|
||||||
premiumQuotaLimit: 0,
|
premiumQuotaLimit: 0,
|
||||||
quotaUnit: '万'
|
quotaUnit: '万',
|
||||||
});
|
});
|
||||||
|
|
||||||
const submitting = ref(false);
|
const submitting = ref(false);
|
||||||
@@ -49,7 +49,7 @@ const quotaUnitOptions = [
|
|||||||
{ label: '百', value: '百', multiplier: 100 },
|
{ label: '百', value: '百', multiplier: 100 },
|
||||||
{ label: '千', value: '千', multiplier: 1000 },
|
{ label: '千', value: '千', multiplier: 1000 },
|
||||||
{ label: '万', value: '万', multiplier: 10000 },
|
{ label: '万', value: '万', multiplier: 10000 },
|
||||||
{ label: '亿', value: '亿', multiplier: 100000000 }
|
{ label: '亿', value: '亿', multiplier: 100000000 },
|
||||||
];
|
];
|
||||||
|
|
||||||
// 监听visible变化,重置表单
|
// 监听visible变化,重置表单
|
||||||
@@ -69,16 +69,20 @@ watch(() => props.visible, (newVal) => {
|
|||||||
if (quota >= 100000000 && quota % 100000000 === 0) {
|
if (quota >= 100000000 && quota % 100000000 === 0) {
|
||||||
displayValue = quota / 100000000;
|
displayValue = quota / 100000000;
|
||||||
unit = '亿';
|
unit = '亿';
|
||||||
} else if (quota >= 10000 && quota % 10000 === 0) {
|
}
|
||||||
|
else if (quota >= 10000 && quota % 10000 === 0) {
|
||||||
displayValue = quota / 10000;
|
displayValue = quota / 10000;
|
||||||
unit = '万';
|
unit = '万';
|
||||||
} else if (quota >= 1000 && quota % 1000 === 0) {
|
}
|
||||||
|
else if (quota >= 1000 && quota % 1000 === 0) {
|
||||||
displayValue = quota / 1000;
|
displayValue = quota / 1000;
|
||||||
unit = '千';
|
unit = '千';
|
||||||
} else if (quota >= 100 && quota % 100 === 0) {
|
}
|
||||||
|
else if (quota >= 100 && quota % 100 === 0) {
|
||||||
displayValue = quota / 100;
|
displayValue = quota / 100;
|
||||||
unit = '百';
|
unit = '百';
|
||||||
} else if (quota >= 10 && quota % 10 === 0) {
|
}
|
||||||
|
else if (quota >= 10 && quota % 10 === 0) {
|
||||||
displayValue = quota / 10;
|
displayValue = quota / 10;
|
||||||
unit = '十';
|
unit = '十';
|
||||||
}
|
}
|
||||||
@@ -90,15 +94,16 @@ watch(() => props.visible, (newVal) => {
|
|||||||
localFormData.value = {
|
localFormData.value = {
|
||||||
...props.formData,
|
...props.formData,
|
||||||
premiumQuotaLimit: displayValue,
|
premiumQuotaLimit: displayValue,
|
||||||
quotaUnit: unit
|
quotaUnit: unit,
|
||||||
};
|
};
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
// 新增模式:重置表单
|
// 新增模式:重置表单
|
||||||
localFormData.value = {
|
localFormData.value = {
|
||||||
name: '',
|
name: '',
|
||||||
expireTime: '',
|
expireTime: '',
|
||||||
premiumQuotaLimit: 1,
|
premiumQuotaLimit: 1,
|
||||||
quotaUnit: '万'
|
quotaUnit: '万',
|
||||||
};
|
};
|
||||||
neverExpire.value = false;
|
neverExpire.value = false;
|
||||||
unlimitedQuota.value = false;
|
unlimitedQuota.value = false;
|
||||||
@@ -123,14 +128,15 @@ watch(unlimitedQuota, (newVal) => {
|
|||||||
|
|
||||||
// 关闭对话框
|
// 关闭对话框
|
||||||
function handleClose() {
|
function handleClose() {
|
||||||
if (submitting.value) return;
|
if (submitting.value)
|
||||||
|
return;
|
||||||
emit('update:visible', false);
|
emit('update:visible', false);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 确认提交
|
// 确认提交
|
||||||
async function handleConfirm() {
|
async function handleConfirm() {
|
||||||
if (!localFormData.value.name.trim()) {
|
if (!localFormData.value.name.trim()) {
|
||||||
ElMessage.warning('请输入Token名称');
|
ElMessage.warning('请输入API密钥名称');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -157,17 +163,18 @@ async function handleConfirm() {
|
|||||||
const submitData: TokenFormData = {
|
const submitData: TokenFormData = {
|
||||||
...localFormData.value,
|
...localFormData.value,
|
||||||
expireTime: neverExpire.value ? '' : localFormData.value.expireTime,
|
expireTime: neverExpire.value ? '' : localFormData.value.expireTime,
|
||||||
premiumQuotaLimit: actualQuota
|
premiumQuotaLimit: actualQuota,
|
||||||
};
|
};
|
||||||
|
|
||||||
emit('confirm', submitData);
|
emit('confirm', submitData);
|
||||||
} finally {
|
}
|
||||||
|
finally {
|
||||||
// 注意:这里不设置 submitting.value = false
|
// 注意:这里不设置 submitting.value = false
|
||||||
// 因为父组件会关闭对话框,watch会重置状态
|
// 因为父组件会关闭对话框,watch会重置状态
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const dialogTitle = computed(() => props.mode === 'create' ? '新增 Token' : '编辑 Token');
|
const dialogTitle = computed(() => props.mode === 'create' ? '新增 API密钥' : '编辑 API密钥');
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -180,7 +187,7 @@ const dialogTitle = computed(() => props.mode === 'create' ? '新增 Token' : '
|
|||||||
@close="handleClose"
|
@close="handleClose"
|
||||||
>
|
>
|
||||||
<el-form :model="localFormData" label-width="110px" label-position="right">
|
<el-form :model="localFormData" label-width="110px" label-position="right">
|
||||||
<el-form-item label="Token名称" required>
|
<el-form-item label="API密钥名称" required>
|
||||||
<el-input
|
<el-input
|
||||||
v-model="localFormData.name"
|
v-model="localFormData.name"
|
||||||
placeholder="例如:生产环境、测试环境、开发环境"
|
placeholder="例如:生产环境、测试环境、开发环境"
|
||||||
@@ -222,7 +229,7 @@ const dialogTitle = computed(() => props.mode === 'create' ? '新增 Token' : '
|
|||||||
</div>
|
</div>
|
||||||
<div v-if="!neverExpire" class="form-hint">
|
<div v-if="!neverExpire" class="form-hint">
|
||||||
<el-icon><i-ep-warning /></el-icon>
|
<el-icon><i-ep-warning /></el-icon>
|
||||||
Token将在过期时间后自动失效
|
API密钥将在过期时间后自动失效
|
||||||
</div>
|
</div>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
@@ -262,21 +269,21 @@ const dialogTitle = computed(() => props.mode === 'create' ? '新增 Token' : '
|
|||||||
</div>
|
</div>
|
||||||
<div v-if="!unlimitedQuota" class="form-hint">
|
<div v-if="!unlimitedQuota" class="form-hint">
|
||||||
<el-icon><i-ep-info-filled /></el-icon>
|
<el-icon><i-ep-info-filled /></el-icon>
|
||||||
超出配额后Token将无法继续使用
|
超出配额后API密钥将无法继续使用
|
||||||
</div>
|
</div>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-form>
|
</el-form>
|
||||||
|
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<div class="dialog-footer">
|
<div class="dialog-footer">
|
||||||
<el-button @click="handleClose" :disabled="submitting">
|
<el-button :disabled="submitting" @click="handleClose">
|
||||||
取消
|
取消
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-button
|
<el-button
|
||||||
type="primary"
|
type="primary"
|
||||||
@click="handleConfirm"
|
|
||||||
:loading="submitting"
|
:loading="submitting"
|
||||||
:disabled="submitting"
|
:disabled="submitting"
|
||||||
|
@click="handleConfirm"
|
||||||
>
|
>
|
||||||
{{ mode === 'create' ? '创建' : '保存' }}
|
{{ mode === 'create' ? '创建' : '保存' }}
|
||||||
</el-button>
|
</el-button>
|
||||||
|
|||||||
@@ -58,9 +58,10 @@ const hasModelData = computed(() => modelUsageData.value.length > 0);
|
|||||||
|
|
||||||
// 计算属性:当前选择的token名称
|
// 计算属性:当前选择的token名称
|
||||||
const selectedTokenName = computed(() => {
|
const selectedTokenName = computed(() => {
|
||||||
if (!selectedTokenId.value) return '全部Token';
|
if (!selectedTokenId.value)
|
||||||
|
return '全部API密钥';
|
||||||
const token = tokenOptions.value.find(t => t.tokenId === selectedTokenId.value);
|
const token = tokenOptions.value.find(t => t.tokenId === selectedTokenId.value);
|
||||||
return token?.name || '未知Token';
|
return token?.name || '未知API密钥';
|
||||||
});
|
});
|
||||||
|
|
||||||
// 获取可选择的Token列表
|
// 获取可选择的Token列表
|
||||||
@@ -74,8 +75,8 @@ async function fetchTokenOptions() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (error) {
|
catch (error) {
|
||||||
console.error('获取Token列表失败:', error);
|
console.error('获取API密钥列表失败:', error);
|
||||||
ElMessage.error('获取Token列表失败');
|
ElMessage.error('获取TAPI密钥列表失败');
|
||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
tokenOptionsLoading.value = false;
|
tokenOptionsLoading.value = false;
|
||||||
@@ -514,7 +515,7 @@ onBeforeUnmount(() => {
|
|||||||
<div class="header-actions">
|
<div class="header-actions">
|
||||||
<el-select
|
<el-select
|
||||||
v-model="selectedTokenId"
|
v-model="selectedTokenId"
|
||||||
placeholder="选择Token"
|
placeholder="选择API密钥"
|
||||||
clearable
|
clearable
|
||||||
filterable
|
filterable
|
||||||
:loading="tokenOptionsLoading"
|
:loading="tokenOptionsLoading"
|
||||||
@@ -523,7 +524,9 @@ onBeforeUnmount(() => {
|
|||||||
>
|
>
|
||||||
<el-option label="全部Token" value="">
|
<el-option label="全部Token" value="">
|
||||||
<div class="token-option">
|
<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>
|
<span class="option-label">全部Token</span>
|
||||||
</div>
|
</div>
|
||||||
</el-option>
|
</el-option>
|
||||||
|
|||||||
Reference in New Issue
Block a user