Merge branch 'Wei-Shaw:dev' into dev

This commit is contained in:
sususu98
2025-09-10 14:40:46 +08:00
committed by GitHub
12 changed files with 805 additions and 775 deletions

View File

@@ -110,70 +110,26 @@
class="mb-1.5 block text-xs font-semibold text-gray-700 dark:text-gray-300 sm:mb-2 sm:text-sm"
>名称 <span class="text-red-500">*</span></label
>
<input
v-model="form.name"
class="form-input w-full border-gray-300 text-sm dark:border-gray-600 dark:bg-gray-700 dark:text-gray-200 dark:placeholder-gray-400"
:class="{ 'border-red-500': errors.name }"
:placeholder="
form.createType === 'batch'
? '输入基础名称(将自动添加序号)'
: '为您的 API Key 取一个名称'
"
required
type="text"
@input="errors.name = ''"
/>
<div>
<input
v-model="form.name"
class="form-input flex-1 border-gray-300 text-sm dark:border-gray-600 dark:bg-gray-700 dark:text-gray-200 dark:placeholder-gray-400"
:class="{ 'border-red-500': errors.name }"
:placeholder="
form.createType === 'batch'
? '输入基础名称(将自动添加序号)'
: '为您的 API Key 取一个名称'
"
required
type="text"
@input="errors.name = ''"
/>
</div>
<p v-if="errors.name" class="mt-1 text-xs text-red-500 dark:text-red-400">
{{ errors.name }}
</p>
</div>
<!-- 图标上传 -->
<div>
<label class="mb-2 block text-sm font-semibold text-gray-700 dark:text-gray-300">
图标 (可选)
</label>
<div class="space-y-3">
<!-- 当前图标预览 -->
<div v-if="form.icon" class="flex items-center gap-3">
<div
class="h-12 w-12 overflow-hidden rounded-lg border border-gray-200 dark:border-gray-600"
>
<img alt="API Key图标" class="h-full w-full object-cover" :src="form.icon" />
</div>
<button
class="text-sm text-red-500 hover:text-red-700 dark:text-red-400 dark:hover:text-red-300"
type="button"
@click="form.icon = ''"
>
移除图标
</button>
</div>
<!-- 图标上传按钮 -->
<div class="flex items-center gap-3">
<input
ref="iconInput"
accept="image/*"
class="hidden"
type="file"
@change="handleIconUpload"
/>
<button
class="flex items-center gap-2 rounded-md border border-gray-300 bg-white px-3 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-200 dark:hover:bg-gray-600"
type="button"
@click="$refs.iconInput.click()"
>
<i class="fas fa-upload" />
选择图标
</button>
<p class="text-xs text-gray-500 dark:text-gray-400">
支持 PNGJPG 格式建议尺寸 64x64px
</p>
</div>
</div>
</div>
<!-- 标签 -->
<div>
<label class="mb-2 block text-sm font-semibold text-gray-700 dark:text-gray-300"
@@ -428,56 +384,6 @@
</div>
</div>
<!-- GPT-5 High推理级别周费用限制 -->
<div>
<label class="mb-2 block text-sm font-semibold text-gray-700 dark:text-gray-300"
>GPT-5 High推理级别周费用限制 (美元)</label
>
<div class="space-y-2">
<div class="flex gap-2">
<button
class="rounded bg-gray-100 px-2 py-1 text-xs font-medium hover:bg-gray-200 dark:bg-gray-700 dark:text-gray-300 dark:hover:bg-gray-600"
type="button"
@click="form.weeklyGPT5HighCostLimit = '5'"
>
$5
</button>
<button
class="rounded bg-gray-100 px-2 py-1 text-xs font-medium hover:bg-gray-200 dark:bg-gray-700 dark:text-gray-300 dark:hover:bg-gray-600"
type="button"
@click="form.weeklyGPT5HighCostLimit = '20'"
>
$20
</button>
<button
class="rounded bg-gray-100 px-2 py-1 text-xs font-medium hover:bg-gray-200 dark:bg-gray-700 dark:text-gray-300 dark:hover:bg-gray-600"
type="button"
@click="form.weeklyGPT5HighCostLimit = '50'"
>
$50
</button>
<button
class="rounded bg-gray-100 px-2 py-1 text-xs font-medium hover:bg-gray-200 dark:bg-gray-700 dark:text-gray-300 dark:hover:bg-gray-600"
type="button"
@click="form.weeklyGPT5HighCostLimit = ''"
>
自定义
</button>
</div>
<input
v-model="form.weeklyGPT5HighCostLimit"
class="form-input w-full border-gray-300 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-200 dark:placeholder-gray-400"
min="0"
placeholder="0 表示无限制"
step="0.01"
type="number"
/>
<p class="text-xs text-gray-500 dark:text-gray-400">
设置 GPT-5 High推理级别的周费用限制周一到周日0 或留空表示无限制
</p>
</div>
</div>
<div>
<label class="mb-2 block text-sm font-semibold text-gray-700 dark:text-gray-300"
>并发限制 (可选)</label
@@ -898,7 +804,6 @@
<script setup>
import { ref, reactive, computed, onMounted } from 'vue'
import { ElMessage } from 'element-plus'
import { showToast } from '@/utils/toast'
import { useClientsStore } from '@/stores/clients'
import { useApiKeysStore } from '@/stores/apiKeys'
@@ -957,7 +862,6 @@ const form = reactive({
concurrencyLimit: '',
dailyCostLimit: '',
weeklyOpusCostLimit: '',
weeklyGPT5HighCostLimit: '', // 新增GPT-5 High推理级别周费用限制
expireDuration: '',
customExpireDate: '',
expiresAt: null,
@@ -973,8 +877,7 @@ const form = reactive({
modelInput: '',
enableClientRestriction: false,
allowedClients: [],
tags: [],
icon: '' // 新增图标base64编码
tags: []
})
// 加载支持的客户端和已存在的标签
@@ -995,35 +898,6 @@ onMounted(async () => {
}
})
// 处理图标上传
const handleIconUpload = (event) => {
const file = event.target.files?.[0]
if (!file) return
// 检查文件类型
if (!file.type.startsWith('image/')) {
ElMessage.error('请选择图片文件')
return
}
// 检查文件大小 (限制为 2MB)
if (file.size > 2 * 1024 * 1024) {
ElMessage.error('图片大小不能超过 2MB')
return
}
// 读取文件并转换为 base64
const reader = new FileReader()
reader.onload = (e) => {
form.icon = e.target.result
ElMessage.success('图标上传成功')
}
reader.onerror = () => {
ElMessage.error('图标上传失败')
}
reader.readAsDataURL(file)
}
// 刷新账号列表
const refreshAccounts = async () => {
accountsLoading.value = true
@@ -1178,22 +1052,7 @@ const removeRestrictedModel = (index) => {
}
// 常用模型列表
const commonModels = ref([
// Claude 模型
'claude-opus-4-20250514',
'claude-opus-4-1-20250805',
// OpenAI 模型
'gpt-5',
'gpt-5 minimal',
'gpt-5 low',
'gpt-5 medium',
'gpt-5 high',
'gpt-4o',
'gpt-4o-mini',
'o1',
'o1-mini',
'o1-preview'
])
const commonModels = ref(['claude-opus-4-20250514', 'claude-opus-4-1-20250805'])
// 可用的快捷模型(过滤掉已在限制列表中的)
const availableQuickModels = computed(() => {
@@ -1296,11 +1155,6 @@ const createApiKey = async () => {
form.weeklyOpusCostLimit !== '' && form.weeklyOpusCostLimit !== null
? parseFloat(form.weeklyOpusCostLimit)
: 0,
// 新增GPT-5 High推理级别周费用限制
weeklyGPT5HighCostLimit:
form.weeklyGPT5HighCostLimit !== '' && form.weeklyGPT5HighCostLimit !== null
? parseFloat(form.weeklyGPT5HighCostLimit)
: 0,
expiresAt: form.expirationMode === 'fixed' ? form.expiresAt || undefined : undefined,
expirationMode: form.expirationMode,
activationDays: form.expirationMode === 'activation' ? form.activationDays : undefined,
@@ -1309,8 +1163,7 @@ const createApiKey = async () => {
enableModelRestriction: form.enableModelRestriction,
restrictedModels: form.restrictedModels,
enableClientRestriction: form.enableClientRestriction,
allowedClients: form.allowedClients,
icon: form.icon || undefined // 新增:图标
allowedClients: form.allowedClients
}
// 处理Claude账户绑定区分OAuth和Console

View File

@@ -32,14 +32,16 @@
class="mb-1.5 block text-xs font-semibold text-gray-700 dark:text-gray-300 sm:mb-3 sm:text-sm"
>名称</label
>
<input
v-model="form.name"
class="form-input w-full border-gray-300 text-sm dark:border-gray-600 dark:bg-gray-700 dark:text-gray-200 dark:placeholder-gray-400"
maxlength="100"
placeholder="请输入API Key名称"
required
type="text"
/>
<div>
<input
v-model="form.name"
class="form-input flex-1 border-gray-300 text-sm dark:border-gray-600 dark:bg-gray-700 dark:text-gray-200 dark:placeholder-gray-400"
maxlength="100"
placeholder="请输入API Key名称"
required
type="text"
/>
</div>
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400 sm:mt-2">
用于识别此 API Key 的用途
</p>
@@ -320,56 +322,6 @@
</div>
</div>
<!-- GPT-5 High推理级别周费用限制 -->
<div>
<label class="mb-2 block text-sm font-semibold text-gray-700 dark:text-gray-300"
>GPT-5 High推理级别周费用限制 (美元)</label
>
<div class="space-y-2">
<div class="flex gap-2">
<button
class="rounded bg-gray-100 px-2 py-1 text-xs font-medium hover:bg-gray-200 dark:bg-gray-700 dark:text-gray-300 dark:hover:bg-gray-600"
type="button"
@click="form.weeklyGPT5HighCostLimit = '5'"
>
$5
</button>
<button
class="rounded bg-gray-100 px-2 py-1 text-xs font-medium hover:bg-gray-200 dark:bg-gray-700 dark:text-gray-300 dark:hover:bg-gray-600"
type="button"
@click="form.weeklyGPT5HighCostLimit = '20'"
>
$20
</button>
<button
class="rounded bg-gray-100 px-2 py-1 text-xs font-medium hover:bg-gray-200 dark:bg-gray-700 dark:text-gray-300 dark:hover:bg-gray-600"
type="button"
@click="form.weeklyGPT5HighCostLimit = '50'"
>
$50
</button>
<button
class="rounded bg-gray-100 px-2 py-1 text-xs font-medium hover:bg-gray-200 dark:bg-gray-700 dark:text-gray-300 dark:hover:bg-gray-600"
type="button"
@click="form.weeklyGPT5HighCostLimit = ''"
>
自定义
</button>
</div>
<input
v-model="form.weeklyGPT5HighCostLimit"
class="form-input w-full border-gray-300 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-200 dark:placeholder-gray-400"
min="0"
placeholder="0 表示无限制"
step="0.01"
type="number"
/>
<p class="text-xs text-gray-500 dark:text-gray-400">
设置 GPT-5 High推理级别的周费用限制周一到周日0 或留空表示无限制
</p>
</div>
</div>
<div>
<label class="mb-3 block text-sm font-semibold text-gray-700 dark:text-gray-300"
>并发限制</label
@@ -762,7 +714,6 @@ const form = reactive({
concurrencyLimit: '',
dailyCostLimit: '',
weeklyOpusCostLimit: '',
weeklyGPT5HighCostLimit: '', // 新增GPT-5 High推理级别周费用限制
permissions: 'all',
claudeAccountId: '',
geminiAccountId: '',
@@ -792,22 +743,7 @@ const removeRestrictedModel = (index) => {
}
// 常用模型列表
const commonModels = ref([
// Claude 模型
'claude-opus-4-20250514',
'claude-opus-4-1-20250805',
// OpenAI 模型
'gpt-5',
'gpt-5 minimal',
'gpt-5 low',
'gpt-5 medium',
'gpt-5 high',
'gpt-4o',
'gpt-4o-mini',
'o1',
'o1-mini',
'o1-preview'
])
const commonModels = ref(['claude-opus-4-20250514', 'claude-opus-4-1-20250805'])
// 可用的快捷模型(过滤掉已在限制列表中的)
const availableQuickModels = computed(() => {
@@ -894,11 +830,6 @@ const updateApiKey = async () => {
form.weeklyOpusCostLimit !== '' && form.weeklyOpusCostLimit !== null
? parseFloat(form.weeklyOpusCostLimit)
: 0,
// 新增GPT-5 High推理级别周费用限制
weeklyGPT5HighCostLimit:
form.weeklyGPT5HighCostLimit !== '' && form.weeklyGPT5HighCostLimit !== null
? parseFloat(form.weeklyGPT5HighCostLimit)
: 0,
permissions: form.permissions,
tags: form.tags
}
@@ -1122,7 +1053,6 @@ onMounted(async () => {
form.concurrencyLimit = props.apiKey.concurrencyLimit || ''
form.dailyCostLimit = props.apiKey.dailyCostLimit || ''
form.weeklyOpusCostLimit = props.apiKey.weeklyOpusCostLimit || ''
form.weeklyGPT5HighCostLimit = props.apiKey.weeklyGPT5HighCostLimit || '' // 新增
form.permissions = props.apiKey.permissions || 'all'
// 处理 Claude 账号(区分 OAuth 和 Console
if (props.apiKey.claudeConsoleAccountId) {

View File

@@ -1,6 +1,6 @@
<template>
<div class="w-full">
<div class="relative h-8 w-full overflow-hidden rounded-md shadow-sm" :class="containerClass">
<div class="relative h-8 w-full overflow-hidden rounded-lg shadow-sm" :class="containerClass">
<!-- 背景层 -->
<div class="absolute inset-0" :class="backgroundClass"></div>
@@ -12,17 +12,17 @@
></div>
<!-- 文字层 - 使用双层文字技术确保可读性 -->
<div class="relative z-10 flex h-full items-center justify-between px-2.5">
<div class="relative z-10 flex h-full items-center justify-between px-3">
<div class="flex items-center gap-1.5">
<i :class="['text-[10px]', iconClass]" />
<span class="text-[10px] font-semibold" :class="labelTextClass">{{ label }}</span>
<i :class="['text-xs', iconClass]" />
<span class="text-xs font-semibold" :class="labelTextClass">{{ label }}</span>
</div>
<div class="mr-1 flex items-center gap-0.5">
<span class="text-[10px] font-bold tabular-nums" :class="currentValueClass">
<div class="flex items-center gap-1.5">
<span class="text-xs font-bold tabular-nums" :class="currentValueClass">
${{ current.toFixed(2) }}
</span>
<span class="text-[9px] font-medium" :class="limitTextClass">
/${{ limit.toFixed(2) }}
<span class="text-xs font-medium" :class="limitTextClass">
/ ${{ limit.toFixed(2) }}
</span>
</div>
</div>
@@ -48,7 +48,7 @@ const props = defineProps({
type: {
type: String,
required: true,
validator: (value) => ['daily', 'opus', 'window', 'gpt5-high'].includes(value)
validator: (value) => ['daily', 'opus', 'window'].includes(value)
},
label: {
type: String,
@@ -88,8 +88,6 @@ const backgroundClass = computed(() => {
return 'bg-violet-50/50 dark:bg-violet-950/20'
case 'window':
return 'bg-sky-50/50 dark:bg-sky-950/20'
case 'gpt5-high':
return 'bg-orange-50/50 dark:bg-orange-950/20'
default:
return 'bg-gray-100/50 dark:bg-gray-800/30'
}
@@ -129,16 +127,6 @@ const progressBarClass = computed(() => {
}
}
if (props.type === 'gpt5-high') {
if (p >= 90) {
return 'bg-red-400 dark:bg-red-500'
} else if (p >= 70) {
return 'bg-amber-400 dark:bg-amber-500'
} else {
return 'bg-orange-400 dark:bg-orange-500'
}
}
return 'bg-gray-300 dark:bg-gray-400'
})
@@ -163,9 +151,6 @@ const iconClass = computed(() => {
case 'window':
colorClass = 'text-blue-700 dark:text-blue-400'
break
case 'gpt5-high':
colorClass = 'text-orange-700 dark:text-orange-400'
break
default:
colorClass = 'text-gray-600 dark:text-gray-400'
}
@@ -182,9 +167,6 @@ const iconClass = computed(() => {
case 'window':
iconName = 'fas fa-clock'
break
case 'gpt5-high':
iconName = 'fas fa-brain'
break
default:
iconName = 'fas fa-infinity'
}
@@ -209,8 +191,6 @@ const labelTextClass = computed(() => {
return 'text-purple-900 dark:text-purple-100'
case 'window':
return 'text-blue-900 dark:text-blue-100'
case 'gpt5-high':
return 'text-orange-900 dark:text-orange-100'
default:
return 'text-gray-900 dark:text-gray-100'
}
@@ -239,8 +219,6 @@ const currentValueClass = computed(() => {
return 'text-purple-800 dark:text-purple-200'
case 'window':
return 'text-blue-800 dark:text-blue-200'
case 'gpt5-high':
return 'text-orange-800 dark:text-orange-200'
default:
return 'text-gray-900 dark:text-gray-100'
}