mirror of
https://github.com/Wei-Shaw/claude-relay-service.git
synced 2026-01-23 09:38:02 +00:00
feat: Codex账号管理优化与API Key激活机制
✨ 新功能 - 支持通过refreshToken新增Codex账号,创建时立即验证token有效性 - API Key新增首次使用自动激活机制,支持activation模式设置有效期 - 前端账号表单增加token验证功能,确保账号创建成功 🐛 修复 - 修复Codex token刷新失败问题,增加分布式锁防止并发刷新 - 优化token刷新错误处理,提供更详细的错误信息和建议 - 修复OpenAI账号token过期检测和自动刷新逻辑 📝 文档更新 - 更新README中Codex使用说明,改为config.toml配置方式 - 优化Cherry Studio等第三方工具接入文档 - 添加详细的配置示例和账号类型说明 🎨 界面优化 - 改进账号创建表单UI,支持手动和OAuth两种模式 - 优化API Key过期时间编辑弹窗,支持激活操作 - 调整教程页面布局,提升移动端响应式体验 💡 代码改进 - 重构token刷新服务,增强错误处理和重试机制 - 优化代理配置处理,确保OAuth请求正确使用代理 - 改进webhook通知,增加token刷新失败告警
This commit is contained in:
@@ -127,7 +127,7 @@
|
||||
<div class="flex gap-2">
|
||||
<input
|
||||
v-model="newTag"
|
||||
class="form-input flex-1 dark:border-gray-600 dark:bg-gray-800 dark:text-gray-200"
|
||||
class="form-input flex-1 border-gray-300 dark:border-gray-600 dark:bg-gray-800 dark:text-gray-200"
|
||||
placeholder="输入新标签名称"
|
||||
type="text"
|
||||
@keypress.enter.prevent="addTag"
|
||||
@@ -166,7 +166,7 @@
|
||||
</label>
|
||||
<input
|
||||
v-model="form.rateLimitWindow"
|
||||
class="form-input w-full text-sm dark:border-gray-600 dark:bg-gray-800 dark:text-gray-200"
|
||||
class="form-input w-full border-gray-300 text-sm dark:border-gray-600 dark:bg-gray-800 dark:text-gray-200"
|
||||
min="1"
|
||||
placeholder="不修改"
|
||||
type="number"
|
||||
@@ -179,7 +179,7 @@
|
||||
>
|
||||
<input
|
||||
v-model="form.rateLimitRequests"
|
||||
class="form-input w-full text-sm dark:border-gray-600 dark:bg-gray-800 dark:text-gray-200"
|
||||
class="form-input w-full border-gray-300 text-sm dark:border-gray-600 dark:bg-gray-800 dark:text-gray-200"
|
||||
min="1"
|
||||
placeholder="不修改"
|
||||
type="number"
|
||||
@@ -192,7 +192,7 @@
|
||||
>
|
||||
<input
|
||||
v-model="form.rateLimitCost"
|
||||
class="form-input w-full text-sm dark:border-gray-600 dark:bg-gray-800 dark:text-gray-200"
|
||||
class="form-input w-full border-gray-300 text-sm dark:border-gray-600 dark:bg-gray-800 dark:text-gray-200"
|
||||
min="0"
|
||||
placeholder="不修改"
|
||||
step="0.01"
|
||||
@@ -210,7 +210,7 @@
|
||||
</label>
|
||||
<input
|
||||
v-model="form.dailyCostLimit"
|
||||
class="form-input w-full dark:border-gray-600 dark:bg-gray-800 dark:text-gray-200"
|
||||
class="form-input w-full border-gray-300 dark:border-gray-600 dark:bg-gray-800 dark:text-gray-200"
|
||||
min="0"
|
||||
placeholder="不修改 (0 表示无限制)"
|
||||
step="0.01"
|
||||
@@ -225,7 +225,7 @@
|
||||
</label>
|
||||
<input
|
||||
v-model="form.weeklyOpusCostLimit"
|
||||
class="form-input w-full dark:border-gray-600 dark:bg-gray-800 dark:text-gray-200"
|
||||
class="form-input w-full border-gray-300 dark:border-gray-600 dark:bg-gray-800 dark:text-gray-200"
|
||||
min="0"
|
||||
placeholder="不修改 (0 表示无限制)"
|
||||
step="0.01"
|
||||
@@ -243,7 +243,7 @@
|
||||
>
|
||||
<input
|
||||
v-model="form.concurrencyLimit"
|
||||
class="form-input w-full dark:border-gray-600 dark:bg-gray-800 dark:text-gray-200"
|
||||
class="form-input w-full border-gray-300 dark:border-gray-600 dark:bg-gray-800 dark:text-gray-200"
|
||||
min="0"
|
||||
placeholder="不修改 (0 表示无限制)"
|
||||
type="number"
|
||||
@@ -330,7 +330,7 @@
|
||||
>
|
||||
<select
|
||||
v-model="form.claudeAccountId"
|
||||
class="form-input w-full dark:border-gray-600 dark:bg-gray-800 dark:text-gray-200"
|
||||
class="form-input w-full border-gray-300 dark:border-gray-600 dark:bg-gray-800 dark:text-gray-200"
|
||||
:disabled="form.permissions === 'gemini' || form.permissions === 'openai'"
|
||||
>
|
||||
<option value="">不修改</option>
|
||||
@@ -365,7 +365,7 @@
|
||||
>
|
||||
<select
|
||||
v-model="form.geminiAccountId"
|
||||
class="form-input w-full dark:border-gray-600 dark:bg-gray-800 dark:text-gray-200"
|
||||
class="form-input w-full border-gray-300 dark:border-gray-600 dark:bg-gray-800 dark:text-gray-200"
|
||||
:disabled="form.permissions === 'claude' || form.permissions === 'openai'"
|
||||
>
|
||||
<option value="">不修改</option>
|
||||
@@ -396,7 +396,7 @@
|
||||
>
|
||||
<select
|
||||
v-model="form.openaiAccountId"
|
||||
class="form-input w-full dark:border-gray-600 dark:bg-gray-800 dark:text-gray-200"
|
||||
class="form-input w-full border-gray-300 dark:border-gray-600 dark:bg-gray-800 dark:text-gray-200"
|
||||
:disabled="form.permissions === 'claude' || form.permissions === 'gemini'"
|
||||
>
|
||||
<option value="">不修改</option>
|
||||
@@ -427,7 +427,7 @@
|
||||
>
|
||||
<select
|
||||
v-model="form.bedrockAccountId"
|
||||
class="form-input w-full dark:border-gray-600 dark:bg-gray-800 dark:text-gray-200"
|
||||
class="form-input w-full border-gray-300 dark:border-gray-600 dark:bg-gray-800 dark:text-gray-200"
|
||||
:disabled="form.permissions === 'gemini' || form.permissions === 'openai'"
|
||||
>
|
||||
<option value="">不修改</option>
|
||||
|
||||
@@ -81,7 +81,7 @@
|
||||
<div class="flex items-center gap-2">
|
||||
<input
|
||||
v-model.number="form.batchCount"
|
||||
class="form-input w-full text-sm dark:border-gray-600 dark:bg-gray-700 dark:text-gray-200 dark:placeholder-gray-400"
|
||||
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"
|
||||
max="500"
|
||||
min="2"
|
||||
placeholder="输入数量 (2-500)"
|
||||
@@ -112,7 +112,7 @@
|
||||
>
|
||||
<input
|
||||
v-model="form.name"
|
||||
class="form-input w-full text-sm dark:border-gray-600 dark:bg-gray-700 dark:text-gray-200 dark:placeholder-gray-400"
|
||||
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'
|
||||
@@ -184,7 +184,7 @@
|
||||
<div class="flex gap-2">
|
||||
<input
|
||||
v-model="newTag"
|
||||
class="form-input flex-1 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-200 dark:placeholder-gray-400"
|
||||
class="form-input flex-1 border-gray-300 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-200 dark:placeholder-gray-400"
|
||||
placeholder="输入新标签名称"
|
||||
type="text"
|
||||
@keypress.enter.prevent="addTag"
|
||||
@@ -228,7 +228,7 @@
|
||||
>
|
||||
<input
|
||||
v-model="form.rateLimitWindow"
|
||||
class="form-input w-full text-sm dark:border-gray-600 dark:bg-gray-700 dark:text-gray-200 dark:placeholder-gray-400"
|
||||
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"
|
||||
min="1"
|
||||
placeholder="无限制"
|
||||
type="number"
|
||||
@@ -242,7 +242,7 @@
|
||||
>
|
||||
<input
|
||||
v-model="form.rateLimitRequests"
|
||||
class="form-input w-full text-sm dark:border-gray-600 dark:bg-gray-700 dark:text-gray-200 dark:placeholder-gray-400"
|
||||
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"
|
||||
min="1"
|
||||
placeholder="无限制"
|
||||
type="number"
|
||||
@@ -256,7 +256,7 @@
|
||||
>
|
||||
<input
|
||||
v-model="form.rateLimitCost"
|
||||
class="form-input w-full text-sm dark:border-gray-600 dark:bg-gray-700 dark:text-gray-200 dark:placeholder-gray-400"
|
||||
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"
|
||||
min="0"
|
||||
placeholder="无限制"
|
||||
step="0.01"
|
||||
@@ -321,7 +321,7 @@
|
||||
</div>
|
||||
<input
|
||||
v-model="form.dailyCostLimit"
|
||||
class="form-input w-full dark:border-gray-600 dark:bg-gray-700 dark:text-gray-200 dark:placeholder-gray-400"
|
||||
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"
|
||||
@@ -370,7 +370,7 @@
|
||||
</div>
|
||||
<input
|
||||
v-model="form.weeklyOpusCostLimit"
|
||||
class="form-input w-full dark:border-gray-600 dark:bg-gray-700 dark:text-gray-200 dark:placeholder-gray-400"
|
||||
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"
|
||||
@@ -388,7 +388,7 @@
|
||||
>
|
||||
<input
|
||||
v-model="form.concurrencyLimit"
|
||||
class="form-input w-full dark:border-gray-600 dark:bg-gray-700 dark:text-gray-200 dark:placeholder-gray-400"
|
||||
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 表示无限制"
|
||||
type="number"
|
||||
@@ -404,7 +404,7 @@
|
||||
>
|
||||
<textarea
|
||||
v-model="form.description"
|
||||
class="form-input w-full resize-none text-sm dark:border-gray-600 dark:bg-gray-700 dark:text-gray-200 dark:placeholder-gray-400"
|
||||
class="form-input w-full resize-none border-gray-300 text-sm dark:border-gray-600 dark:bg-gray-700 dark:text-gray-200 dark:placeholder-gray-400"
|
||||
placeholder="描述此 API Key 的用途..."
|
||||
rows="2"
|
||||
/>
|
||||
@@ -412,34 +412,103 @@
|
||||
|
||||
<div>
|
||||
<label class="mb-2 block text-sm font-semibold text-gray-700 dark:text-gray-300"
|
||||
>有效期限</label
|
||||
>过期设置</label
|
||||
>
|
||||
<select
|
||||
v-model="form.expireDuration"
|
||||
class="form-input w-full dark:border-gray-600 dark:bg-gray-700 dark:text-gray-200"
|
||||
@change="updateExpireAt"
|
||||
<!-- 过期模式选择 -->
|
||||
<div
|
||||
class="mb-3 rounded-lg border border-gray-200 bg-gray-50 p-3 dark:border-gray-700 dark:bg-gray-800"
|
||||
>
|
||||
<option value="">永不过期</option>
|
||||
<option value="1d">1 天</option>
|
||||
<option value="7d">7 天</option>
|
||||
<option value="30d">30 天</option>
|
||||
<option value="90d">90 天</option>
|
||||
<option value="180d">180 天</option>
|
||||
<option value="365d">365 天</option>
|
||||
<option value="custom">自定义日期</option>
|
||||
</select>
|
||||
<div v-if="form.expireDuration === 'custom'" class="mt-3">
|
||||
<input
|
||||
v-model="form.customExpireDate"
|
||||
class="form-input w-full dark:border-gray-600 dark:bg-gray-700 dark:text-gray-200"
|
||||
:min="minDateTime"
|
||||
type="datetime-local"
|
||||
@change="updateCustomExpireAt"
|
||||
/>
|
||||
<div class="flex items-center gap-4">
|
||||
<label class="flex cursor-pointer items-center">
|
||||
<input
|
||||
v-model="form.expirationMode"
|
||||
class="mr-2 text-blue-600 focus:ring-blue-500 dark:border-gray-600 dark:bg-gray-700"
|
||||
type="radio"
|
||||
value="fixed"
|
||||
/>
|
||||
<span class="text-sm text-gray-700 dark:text-gray-300">固定时间过期</span>
|
||||
</label>
|
||||
<label class="flex cursor-pointer items-center">
|
||||
<input
|
||||
v-model="form.expirationMode"
|
||||
class="mr-2 text-blue-600 focus:ring-blue-500 dark:border-gray-600 dark:bg-gray-700"
|
||||
type="radio"
|
||||
value="activation"
|
||||
/>
|
||||
<span class="text-sm text-gray-700 dark:text-gray-300">首次使用后激活</span>
|
||||
</label>
|
||||
</div>
|
||||
<p class="mt-2 text-xs text-gray-500 dark:text-gray-400">
|
||||
<span v-if="form.expirationMode === 'fixed'">
|
||||
<i class="fas fa-info-circle mr-1" />
|
||||
固定时间模式:Key 创建后立即生效,按设定时间过期
|
||||
</span>
|
||||
<span v-else>
|
||||
<i class="fas fa-info-circle mr-1" />
|
||||
激活模式:Key 首次使用时激活,激活后按设定天数过期(适合批量销售)
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- 固定时间模式 -->
|
||||
<div v-if="form.expirationMode === 'fixed'">
|
||||
<select
|
||||
v-model="form.expireDuration"
|
||||
class="form-input w-full border-gray-300 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-200"
|
||||
@change="updateExpireAt"
|
||||
>
|
||||
<option value="">永不过期</option>
|
||||
<option value="1d">1 天</option>
|
||||
<option value="7d">7 天</option>
|
||||
<option value="30d">30 天</option>
|
||||
<option value="90d">90 天</option>
|
||||
<option value="180d">180 天</option>
|
||||
<option value="365d">365 天</option>
|
||||
<option value="custom">自定义日期</option>
|
||||
</select>
|
||||
<div v-if="form.expireDuration === 'custom'" class="mt-3">
|
||||
<input
|
||||
v-model="form.customExpireDate"
|
||||
class="form-input w-full border-gray-300 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-200"
|
||||
:min="minDateTime"
|
||||
type="datetime-local"
|
||||
@change="updateCustomExpireAt"
|
||||
/>
|
||||
</div>
|
||||
<p v-if="form.expiresAt" class="mt-2 text-xs text-gray-500 dark:text-gray-400">
|
||||
将于 {{ formatExpireDate(form.expiresAt) }} 过期
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- 激活模式 -->
|
||||
<div v-else>
|
||||
<div class="flex items-center gap-2">
|
||||
<input
|
||||
v-model.number="form.activationDays"
|
||||
class="form-input flex-1 border-gray-300 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-200"
|
||||
max="3650"
|
||||
min="1"
|
||||
placeholder="输入天数"
|
||||
type="number"
|
||||
/>
|
||||
<span class="text-sm text-gray-600 dark:text-gray-400">天</span>
|
||||
</div>
|
||||
<div class="mt-2 flex flex-wrap gap-2">
|
||||
<button
|
||||
v-for="days in [30, 90, 180, 365]"
|
||||
:key="days"
|
||||
class="rounded-md border border-gray-300 px-3 py-1 text-xs hover:bg-gray-100 dark:border-gray-600 dark:hover:bg-gray-700"
|
||||
type="button"
|
||||
@click="form.activationDays = days"
|
||||
>
|
||||
{{ days }}天
|
||||
</button>
|
||||
</div>
|
||||
<p class="mt-2 text-xs text-gray-500 dark:text-gray-400">
|
||||
<i class="fas fa-clock mr-1" />
|
||||
Key 将在首次使用后激活,激活后 {{ form.activationDays || 30 }} 天过期
|
||||
</p>
|
||||
</div>
|
||||
<p v-if="form.expiresAt" class="mt-2 text-xs text-gray-500 dark:text-gray-400">
|
||||
将于 {{ formatExpireDate(form.expiresAt) }} 过期
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
@@ -794,6 +863,8 @@ const form = reactive({
|
||||
expireDuration: '',
|
||||
customExpireDate: '',
|
||||
expiresAt: null,
|
||||
expirationMode: 'fixed', // 过期模式:fixed(固定) 或 activation(激活)
|
||||
activationDays: 30, // 激活后有效天数
|
||||
permissions: 'all',
|
||||
claudeAccountId: '',
|
||||
geminiAccountId: '',
|
||||
@@ -1082,7 +1153,9 @@ const createApiKey = async () => {
|
||||
form.weeklyOpusCostLimit !== '' && form.weeklyOpusCostLimit !== null
|
||||
? parseFloat(form.weeklyOpusCostLimit)
|
||||
: 0,
|
||||
expiresAt: form.expiresAt || undefined,
|
||||
expiresAt: form.expirationMode === 'fixed' ? form.expiresAt || undefined : undefined,
|
||||
expirationMode: form.expirationMode,
|
||||
activationDays: form.expirationMode === 'activation' ? form.activationDays : undefined,
|
||||
permissions: form.permissions,
|
||||
tags: form.tags.length > 0 ? form.tags : undefined,
|
||||
enableModelRestriction: form.enableModelRestriction,
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
>
|
||||
<input
|
||||
v-model="form.name"
|
||||
class="form-input w-full text-sm dark:border-gray-600 dark:bg-gray-700 dark:text-gray-200 dark:placeholder-gray-400"
|
||||
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
|
||||
@@ -53,7 +53,7 @@
|
||||
>
|
||||
<select
|
||||
v-model="form.ownerId"
|
||||
class="form-input w-full dark:border-gray-600 dark:bg-gray-700 dark:text-gray-200"
|
||||
class="form-input w-full border-gray-300 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-200"
|
||||
>
|
||||
<option v-for="user in availableUsers" :key="user.id" :value="user.id">
|
||||
{{ user.displayName }} ({{ user.username }})
|
||||
@@ -122,7 +122,7 @@
|
||||
<div class="flex gap-2">
|
||||
<input
|
||||
v-model="newTag"
|
||||
class="form-input flex-1 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-200 dark:placeholder-gray-400"
|
||||
class="form-input flex-1 border-gray-300 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-200 dark:placeholder-gray-400"
|
||||
placeholder="输入新标签名称"
|
||||
type="text"
|
||||
@keypress.enter.prevent="addTag"
|
||||
@@ -166,7 +166,7 @@
|
||||
>
|
||||
<input
|
||||
v-model="form.rateLimitWindow"
|
||||
class="form-input w-full text-sm dark:border-gray-600 dark:bg-gray-700 dark:text-gray-200 dark:placeholder-gray-400"
|
||||
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"
|
||||
min="1"
|
||||
placeholder="无限制"
|
||||
type="number"
|
||||
@@ -180,7 +180,7 @@
|
||||
>
|
||||
<input
|
||||
v-model="form.rateLimitRequests"
|
||||
class="form-input w-full text-sm dark:border-gray-600 dark:bg-gray-700 dark:text-gray-200 dark:placeholder-gray-400"
|
||||
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"
|
||||
min="1"
|
||||
placeholder="无限制"
|
||||
type="number"
|
||||
@@ -194,7 +194,7 @@
|
||||
>
|
||||
<input
|
||||
v-model="form.rateLimitCost"
|
||||
class="form-input w-full text-sm dark:border-gray-600 dark:bg-gray-700 dark:text-gray-200 dark:placeholder-gray-400"
|
||||
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"
|
||||
min="0"
|
||||
placeholder="无限制"
|
||||
step="0.01"
|
||||
@@ -259,7 +259,7 @@
|
||||
</div>
|
||||
<input
|
||||
v-model="form.dailyCostLimit"
|
||||
class="form-input w-full dark:border-gray-600 dark:bg-gray-700 dark:text-gray-200 dark:placeholder-gray-400"
|
||||
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"
|
||||
@@ -308,7 +308,7 @@
|
||||
</div>
|
||||
<input
|
||||
v-model="form.weeklyOpusCostLimit"
|
||||
class="form-input w-full dark:border-gray-600 dark:bg-gray-700 dark:text-gray-200 dark:placeholder-gray-400"
|
||||
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"
|
||||
@@ -326,7 +326,7 @@
|
||||
>
|
||||
<input
|
||||
v-model="form.concurrencyLimit"
|
||||
class="form-input w-full dark:border-gray-600 dark:bg-gray-700 dark:text-gray-200 dark:placeholder-gray-400"
|
||||
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 表示无限制"
|
||||
type="number"
|
||||
@@ -558,7 +558,7 @@
|
||||
<div class="flex gap-2">
|
||||
<input
|
||||
v-model="form.modelInput"
|
||||
class="form-input flex-1 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-200 dark:placeholder-gray-400"
|
||||
class="form-input flex-1 border-gray-300 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-200 dark:placeholder-gray-400"
|
||||
placeholder="输入模型名称,按回车添加"
|
||||
type="text"
|
||||
@keydown.enter.prevent="addRestrictedModel"
|
||||
|
||||
@@ -39,11 +39,18 @@
|
||||
>
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<p class="mb-1 text-xs font-medium text-gray-600 dark:text-gray-400">
|
||||
当前过期时间
|
||||
</p>
|
||||
<p class="mb-1 text-xs font-medium text-gray-600 dark:text-gray-400">当前状态</p>
|
||||
<p class="text-sm font-semibold text-gray-800 dark:text-gray-200">
|
||||
<template v-if="apiKey.expiresAt">
|
||||
<!-- 未激活状态 -->
|
||||
<template v-if="apiKey.expirationMode === 'activation' && !apiKey.isActivated">
|
||||
<i class="fas fa-pause-circle mr-1 text-blue-500" />
|
||||
未激活
|
||||
<span class="ml-2 text-xs font-normal text-gray-600">
|
||||
(激活后 {{ apiKey.activationDays || 30 }} 天过期)
|
||||
</span>
|
||||
</template>
|
||||
<!-- 已设置过期时间 -->
|
||||
<template v-else-if="apiKey.expiresAt">
|
||||
{{ formatExpireDate(apiKey.expiresAt) }}
|
||||
<span
|
||||
v-if="getExpiryStatus(apiKey.expiresAt)"
|
||||
@@ -53,6 +60,7 @@
|
||||
({{ getExpiryStatus(apiKey.expiresAt).text }})
|
||||
</span>
|
||||
</template>
|
||||
<!-- 永不过期 -->
|
||||
<template v-else>
|
||||
<i class="fas fa-infinity mr-1 text-gray-500" />
|
||||
永不过期
|
||||
@@ -74,6 +82,21 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 激活按钮(仅在未激活状态显示) -->
|
||||
<div v-if="apiKey.expirationMode === 'activation' && !apiKey.isActivated" class="mb-4">
|
||||
<button
|
||||
class="w-full rounded-lg bg-gradient-to-r from-blue-500 to-blue-600 px-4 py-3 font-semibold text-white transition-all hover:from-blue-600 hover:to-blue-700 hover:shadow-lg"
|
||||
@click="handleActivateNow"
|
||||
>
|
||||
<i class="fas fa-rocket mr-2" />
|
||||
立即激活 (激活后 {{ apiKey.activationDays || 30 }} 天过期)
|
||||
</button>
|
||||
<p class="mt-2 text-xs text-gray-500 dark:text-gray-400">
|
||||
<i class="fas fa-info-circle mr-1" />
|
||||
点击立即激活此 API Key,激活后将在 {{ apiKey.activationDays || 30 }} 天后过期
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- 快捷选项 -->
|
||||
<div>
|
||||
<label class="mb-3 block text-sm font-semibold text-gray-700 dark:text-gray-300"
|
||||
@@ -115,7 +138,7 @@
|
||||
>
|
||||
<input
|
||||
v-model="localForm.customExpireDate"
|
||||
class="form-input w-full dark:border-gray-600 dark:bg-gray-700 dark:text-gray-200"
|
||||
class="form-input w-full border-gray-300 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-200"
|
||||
:min="minDateTime"
|
||||
type="datetime-local"
|
||||
@change="updateCustomExpiryPreview"
|
||||
@@ -370,6 +393,35 @@ const handleSave = () => {
|
||||
})
|
||||
}
|
||||
|
||||
// 立即激活
|
||||
const handleActivateNow = async () => {
|
||||
// 使用确认弹窗
|
||||
let confirmed = true
|
||||
if (window.showConfirm) {
|
||||
confirmed = await window.showConfirm(
|
||||
'激活 API Key',
|
||||
`确定要立即激活此 API Key 吗?激活后将在 ${props.apiKey.activationDays || 30} 天后自动过期。`,
|
||||
'确定激活',
|
||||
'取消'
|
||||
)
|
||||
} else {
|
||||
// 降级方案
|
||||
confirmed = confirm(
|
||||
`确定要立即激活此 API Key 吗?激活后将在 ${props.apiKey.activationDays || 30} 天后自动过期。`
|
||||
)
|
||||
}
|
||||
|
||||
if (!confirmed) {
|
||||
return
|
||||
}
|
||||
|
||||
saving.value = true
|
||||
emit('save', {
|
||||
keyId: props.apiKey.id,
|
||||
activateNow: true
|
||||
})
|
||||
}
|
||||
|
||||
// 重置保存状态
|
||||
const resetSaving = () => {
|
||||
saving.value = false
|
||||
|
||||
Reference in New Issue
Block a user