mirror of
https://github.com/Wei-Shaw/claude-relay-service.git
synced 2026-01-22 16:43:35 +00:00
Merge branch 'pr-485' into dev
This commit is contained in:
@@ -492,11 +492,11 @@
|
||||
<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 创建后立即生效,按设定时间过期
|
||||
固定时间模式:Key 创建后立即生效,按设定时间过期(支持小时和天数)
|
||||
</span>
|
||||
<span v-else>
|
||||
<i class="fas fa-info-circle mr-1" />
|
||||
激活模式:Key 首次使用时激活,激活后按设定天数过期(适合批量销售)
|
||||
激活模式:Key 首次使用时激活,激活后按设定时间过期(支持小时和天数,适合批量销售)
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
@@ -509,6 +509,10 @@
|
||||
@change="updateExpireAt"
|
||||
>
|
||||
<option value="">永不过期</option>
|
||||
<option value="1h">1 小时</option>
|
||||
<option value="3h">3 小时</option>
|
||||
<option value="6h">6 小时</option>
|
||||
<option value="12h">12 小时</option>
|
||||
<option value="1d">1 天</option>
|
||||
<option value="7d">7 天</option>
|
||||
<option value="30d">30 天</option>
|
||||
@@ -537,27 +541,36 @@
|
||||
<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"
|
||||
:max="form.activationUnit === 'hours' ? 8760 : 3650"
|
||||
min="1"
|
||||
placeholder="输入天数"
|
||||
:placeholder="form.activationUnit === 'hours' ? '输入小时数' : '输入天数'"
|
||||
type="number"
|
||||
/>
|
||||
<span class="text-sm text-gray-600 dark:text-gray-400">天</span>
|
||||
<select
|
||||
v-model="form.activationUnit"
|
||||
class="form-input w-20 border-gray-300 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-200"
|
||||
@change="updateActivationValue"
|
||||
>
|
||||
<option value="hours">小时</option>
|
||||
<option value="days">天</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="mt-2 flex flex-wrap gap-2">
|
||||
<button
|
||||
v-for="days in [30, 90, 180, 365]"
|
||||
:key="days"
|
||||
v-for="value in getQuickTimeOptions()"
|
||||
:key="value.value"
|
||||
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"
|
||||
@click="form.activationDays = value.value"
|
||||
>
|
||||
{{ days }}天
|
||||
{{ value.label }}
|
||||
</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 }} 天过期
|
||||
Key 将在首次使用后激活,激活后
|
||||
{{ form.activationDays || (form.activationUnit === 'hours' ? 24 : 30) }}
|
||||
{{ form.activationUnit === 'hours' ? '小时' : '天' }}过期
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -917,6 +930,7 @@ const form = reactive({
|
||||
expiresAt: null,
|
||||
expirationMode: 'fixed', // 过期模式:fixed(固定) 或 activation(激活)
|
||||
activationDays: 30, // 激活后有效天数
|
||||
activationUnit: 'days', // 激活时间单位:hours 或 days
|
||||
permissions: 'all',
|
||||
claudeAccountId: '',
|
||||
geminiAccountId: '',
|
||||
@@ -1185,6 +1199,40 @@ const removeTag = (index) => {
|
||||
form.tags.splice(index, 1)
|
||||
}
|
||||
|
||||
// 获取快捷时间选项
|
||||
const getQuickTimeOptions = () => {
|
||||
if (form.activationUnit === 'hours') {
|
||||
return [
|
||||
{ value: 1, label: '1小时' },
|
||||
{ value: 3, label: '3小时' },
|
||||
{ value: 6, label: '6小时' },
|
||||
{ value: 12, label: '12小时' }
|
||||
]
|
||||
} else {
|
||||
return [
|
||||
{ value: 30, label: '30天' },
|
||||
{ value: 90, label: '90天' },
|
||||
{ value: 180, label: '180天' },
|
||||
{ value: 365, label: '365天' }
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
// 单位变化时更新数值
|
||||
const updateActivationValue = () => {
|
||||
if (form.activationUnit === 'hours') {
|
||||
// 从天切换到小时,设置一个合理的默认值
|
||||
if (form.activationDays > 24) {
|
||||
form.activationDays = 24
|
||||
}
|
||||
} else {
|
||||
// 从小时切换到天,设置一个合理的默认值
|
||||
if (form.activationDays < 1) {
|
||||
form.activationDays = 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 创建 API Key
|
||||
const createApiKey = async () => {
|
||||
// 验证表单
|
||||
@@ -1260,6 +1308,7 @@ const createApiKey = async () => {
|
||||
expiresAt: form.expirationMode === 'fixed' ? form.expiresAt || undefined : undefined,
|
||||
expirationMode: form.expirationMode,
|
||||
activationDays: form.expirationMode === 'activation' ? form.activationDays : undefined,
|
||||
activationUnit: form.expirationMode === 'activation' ? form.activationUnit : undefined,
|
||||
permissions: form.permissions,
|
||||
tags: form.tags.length > 0 ? form.tags : undefined,
|
||||
enableModelRestriction: form.enableModelRestriction,
|
||||
|
||||
@@ -46,7 +46,9 @@
|
||||
<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 }} 天过期)
|
||||
(激活后
|
||||
{{ apiKey.activationDays || (apiKey.activationUnit === 'hours' ? 24 : 30) }}
|
||||
{{ apiKey.activationUnit === 'hours' ? '小时' : '天' }}过期)
|
||||
</span>
|
||||
</template>
|
||||
<!-- 已设置过期时间 -->
|
||||
@@ -89,11 +91,15 @@
|
||||
@click="handleActivateNow"
|
||||
>
|
||||
<i class="fas fa-rocket mr-2" />
|
||||
立即激活 (激活后 {{ apiKey.activationDays || 30 }} 天过期)
|
||||
立即激活 (激活后
|
||||
{{ apiKey.activationDays || (apiKey.activationUnit === 'hours' ? 24 : 30) }}
|
||||
{{ apiKey.activationUnit === 'hours' ? '小时' : '天' }}过期)
|
||||
</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 }} 天后过期
|
||||
点击立即激活此 API Key,激活后将在
|
||||
{{ apiKey.activationDays || (apiKey.activationUnit === 'hours' ? 24 : 30) }}
|
||||
{{ apiKey.activationUnit === 'hours' ? '小时' : '天' }}后过期
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -400,14 +406,14 @@ const handleActivateNow = async () => {
|
||||
if (window.showConfirm) {
|
||||
confirmed = await window.showConfirm(
|
||||
'激活 API Key',
|
||||
`确定要立即激活此 API Key 吗?激活后将在 ${props.apiKey.activationDays || 30} 天后自动过期。`,
|
||||
`确定要立即激活此 API Key 吗?激活后将在 ${props.apiKey.activationDays || (props.apiKey.activationUnit === 'hours' ? 24 : 30)} ${props.apiKey.activationUnit === 'hours' ? '小时' : '天'}后自动过期。`,
|
||||
'确定激活',
|
||||
'取消'
|
||||
)
|
||||
} else {
|
||||
// 降级方案
|
||||
confirmed = confirm(
|
||||
`确定要立即激活此 API Key 吗?激活后将在 ${props.apiKey.activationDays || 30} 天后自动过期。`
|
||||
`确定要立即激活此 API Key 吗?激活后将在 ${props.apiKey.activationDays || (props.apiKey.activationUnit === 'hours' ? 24 : 30)} ${props.apiKey.activationUnit === 'hours' ? '小时' : '天'}后自动过期。`
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -123,7 +123,9 @@
|
||||
<i class="fas fa-pause-circle mr-1 text-xs md:text-sm" />
|
||||
未激活
|
||||
<span class="ml-1 text-xs text-gray-500 dark:text-gray-400"
|
||||
>(首次使用后{{ statsData.activationDays || 30 }}天过期)</span
|
||||
>(首次使用后
|
||||
{{ statsData.activationDays || (statsData.activationUnit === 'hours' ? 24 : 30)
|
||||
}}{{ statsData.activationUnit === 'hours' ? '小时' : '天' }}过期)</span
|
||||
>
|
||||
</div>
|
||||
<!-- 已设置过期时间 -->
|
||||
|
||||
@@ -660,7 +660,9 @@
|
||||
style="font-size: 13px"
|
||||
>
|
||||
<i class="fas fa-pause-circle mr-1 text-xs" />
|
||||
未激活 ({{ key.activationDays || 30 }}天)
|
||||
未激活 (
|
||||
{{ key.activationDays || (key.activationUnit === 'hours' ? 24 : 30)
|
||||
}}{{ key.activationUnit === 'hours' ? '小时' : '天' }})
|
||||
</span>
|
||||
<!-- 已设置过期时间 -->
|
||||
<span v-else-if="key.expiresAt">
|
||||
@@ -3523,7 +3525,86 @@ const exportToExcel = () => {
|
||||
|
||||
// 基础数据
|
||||
const baseData = {
|
||||
ID: key.id || '',
|
||||
名称: key.name || '',
|
||||
描述: key.description || '',
|
||||
状态: key.isActive ? '启用' : '禁用',
|
||||
API密钥: key.apiKey || '',
|
||||
|
||||
// 过期配置
|
||||
过期模式:
|
||||
key.expirationMode === 'activation'
|
||||
? '首次使用后激活'
|
||||
: key.expirationMode === 'fixed'
|
||||
? '固定时间'
|
||||
: '无',
|
||||
激活期限: key.activationDays || '',
|
||||
激活单位:
|
||||
key.activationUnit === 'hours' ? '小时' : key.activationUnit === 'days' ? '天' : '',
|
||||
已激活: key.isActivated ? '是' : '否',
|
||||
激活时间: key.activatedAt ? formatDate(key.activatedAt) : '',
|
||||
过期时间: key.expiresAt ? formatDate(key.expiresAt) : '',
|
||||
|
||||
// 权限配置
|
||||
服务权限:
|
||||
key.permissions === 'all'
|
||||
? '全部服务'
|
||||
: key.permissions === 'claude'
|
||||
? '仅Claude'
|
||||
: key.permissions === 'gemini'
|
||||
? '仅Gemini'
|
||||
: key.permissions === 'openai'
|
||||
? '仅OpenAI'
|
||||
: key.permissions || '',
|
||||
|
||||
// 限制配置
|
||||
令牌限制: key.tokenLimit === '0' || key.tokenLimit === 0 ? '无限制' : key.tokenLimit || '',
|
||||
并发限制:
|
||||
key.concurrencyLimit === '0' || key.concurrencyLimit === 0
|
||||
? '无限制'
|
||||
: key.concurrencyLimit || '',
|
||||
'速率窗口(分钟)':
|
||||
key.rateLimitWindow === '0' || key.rateLimitWindow === 0
|
||||
? '无限制'
|
||||
: key.rateLimitWindow || '',
|
||||
速率请求限制:
|
||||
key.rateLimitRequests === '0' || key.rateLimitRequests === 0
|
||||
? '无限制'
|
||||
: key.rateLimitRequests || '',
|
||||
'日费用限制($)':
|
||||
key.dailyCostLimit === '0' || key.dailyCostLimit === 0
|
||||
? '无限制'
|
||||
: `$${key.dailyCostLimit}` || '',
|
||||
'总费用限制($)':
|
||||
key.totalCostLimit === '0' || key.totalCostLimit === 0
|
||||
? '无限制'
|
||||
: `$${key.totalCostLimit}` || '',
|
||||
|
||||
// 账户绑定
|
||||
Claude专属账户: key.claudeAccountId || '',
|
||||
Claude控制台账户: key.claudeConsoleAccountId || '',
|
||||
Gemini专属账户: key.geminiAccountId || '',
|
||||
OpenAI专属账户: key.openaiAccountId || '',
|
||||
'Azure OpenAI专属账户': key.azureOpenaiAccountId || '',
|
||||
Bedrock专属账户: key.bedrockAccountId || '',
|
||||
|
||||
// 模型和客户端限制
|
||||
启用模型限制: key.enableModelRestriction ? '是' : '否',
|
||||
限制的模型:
|
||||
key.restrictedModels && key.restrictedModels.length > 0
|
||||
? key.restrictedModels.join('; ')
|
||||
: '',
|
||||
启用客户端限制: key.enableClientRestriction ? '是' : '否',
|
||||
允许的客户端:
|
||||
key.allowedClients && key.allowedClients.length > 0 ? key.allowedClients.join('; ') : '',
|
||||
|
||||
// 创建信息
|
||||
创建时间: key.createdAt ? formatDate(key.createdAt) : '',
|
||||
创建者: key.createdBy || '',
|
||||
用户ID: key.userId || '',
|
||||
用户名: key.userUsername || '',
|
||||
|
||||
// 使用统计
|
||||
标签: key.tags && key.tags.length > 0 ? key.tags.join(', ') : '无',
|
||||
请求总数: periodRequests,
|
||||
'总费用($)': periodCost.toFixed(2),
|
||||
@@ -3580,12 +3661,33 @@ const exportToExcel = () => {
|
||||
// 设置列宽
|
||||
const headers = Object.keys(exportData[0] || {})
|
||||
const columnWidths = headers.map((header) => {
|
||||
// 基本信息字段
|
||||
if (header === 'ID') return { wch: 40 }
|
||||
if (header === '名称') return { wch: 25 }
|
||||
if (header === '描述') return { wch: 30 }
|
||||
if (header === 'API密钥') return { wch: 45 }
|
||||
if (header === '标签') return { wch: 20 }
|
||||
if (header === '最后使用时间') return { wch: 20 }
|
||||
|
||||
// 时间字段
|
||||
if (header.includes('时间')) return { wch: 20 }
|
||||
|
||||
// 限制字段
|
||||
if (header.includes('限制')) return { wch: 15 }
|
||||
if (header.includes('费用')) return { wch: 15 }
|
||||
if (header.includes('Token')) return { wch: 15 }
|
||||
if (header.includes('请求')) return { wch: 12 }
|
||||
|
||||
// 账户绑定字段
|
||||
if (header.includes('账户')) return { wch: 30 }
|
||||
|
||||
// 权限配置字段
|
||||
if (header.includes('权限') || header.includes('模型') || header.includes('客户端'))
|
||||
return { wch: 20 }
|
||||
|
||||
// 激活配置字段
|
||||
if (header.includes('激活') || header.includes('过期')) return { wch: 18 }
|
||||
|
||||
// 默认宽度
|
||||
return { wch: 15 }
|
||||
})
|
||||
ws['!cols'] = columnWidths
|
||||
|
||||
Reference in New Issue
Block a user