feat: 平台图标与计费修复
- fix(billing): 修复 OpenAI 兼容 API 缓存 token 重复计费问题 - fix(auth): 隐藏数据库错误详情,返回通用服务不可用错误 - feat(ui): 新增 PlatformIcon 组件,GroupBadge 支持平台颜色区分 - feat(ui): 账号管理新增重置状态按钮,重授权后自动清除错误 - feat(ui): 分组管理新增计费类型列,显示订阅限额信息 - ui: 首页 GPT 状态改为已支持
This commit is contained in:
@@ -139,6 +139,17 @@
|
||||
|
||||
<template #cell-actions="{ row }">
|
||||
<div class="flex items-center gap-1">
|
||||
<!-- Reset Status button for error accounts -->
|
||||
<button
|
||||
v-if="row.status === 'error'"
|
||||
@click="handleResetStatus(row)"
|
||||
class="p-2 rounded-lg hover:bg-red-50 dark:hover:bg-red-900/20 text-red-500 hover:text-red-600 dark:hover:text-red-400 transition-colors"
|
||||
:title="t('admin.accounts.resetStatus')"
|
||||
>
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="1.5">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M9 15L3 9m0 0l6-6M3 9h12a6 6 0 010 12h-3" />
|
||||
</svg>
|
||||
</button>
|
||||
<!-- Clear Rate Limit button -->
|
||||
<button
|
||||
v-if="isRateLimited(row) || isOverloaded(row)"
|
||||
@@ -496,6 +507,23 @@ const handleClearRateLimit = async (account: Account) => {
|
||||
}
|
||||
}
|
||||
|
||||
// Reset account status (clear error and rate limit)
|
||||
const handleResetStatus = async (account: Account) => {
|
||||
try {
|
||||
// Clear error status
|
||||
await adminAPI.accounts.clearError(account.id)
|
||||
// Also clear rate limit if exists
|
||||
if (isRateLimited(account) || isOverloaded(account)) {
|
||||
await adminAPI.accounts.clearRateLimit(account.id)
|
||||
}
|
||||
appStore.showSuccess(t('admin.accounts.statusReset'))
|
||||
loadAccounts()
|
||||
} catch (error: any) {
|
||||
appStore.showError(error.response?.data?.detail || t('admin.accounts.failedToResetStatus'))
|
||||
console.error('Error resetting account status:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// Toggle schedulable
|
||||
const handleToggleSchedulable = async (account: Account) => {
|
||||
togglingSchedulable.value = account.id
|
||||
|
||||
@@ -60,14 +60,46 @@
|
||||
</template>
|
||||
|
||||
<template #cell-platform="{ value }">
|
||||
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-purple-100 text-purple-800 dark:bg-purple-900/30 dark:text-purple-400">
|
||||
<svg class="w-3 h-3 mr-1" fill="currentColor" viewBox="0 0 24 24">
|
||||
<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-1 17.93c-3.95-.49-7-3.85-7-7.93 0-.62.08-1.21.21-1.79L9 15v1c0 1.1.9 2 2 2v1.93zm6.9-2.54c-.26-.81-1-1.39-1.9-1.39h-1v-3c0-.55-.45-1-1-1H8v-2h2c.55 0 1-.45 1-1V7h2c1.1 0 2-.9 2-2v-.41c2.93 1.19 5 4.06 5 7.41 0 2.08-.8 3.97-2.1 5.39z"/>
|
||||
</svg>
|
||||
{{ value.charAt(0).toUpperCase() + value.slice(1) }}
|
||||
<span
|
||||
:class="[
|
||||
'inline-flex items-center gap-1.5 px-2.5 py-0.5 rounded-full text-xs font-medium',
|
||||
value === 'anthropic'
|
||||
? 'bg-orange-100 text-orange-700 dark:bg-orange-900/30 dark:text-orange-400'
|
||||
: 'bg-emerald-100 text-emerald-700 dark:bg-emerald-900/30 dark:text-emerald-400'
|
||||
]"
|
||||
>
|
||||
<PlatformIcon :platform="value" size="xs" />
|
||||
{{ value === 'anthropic' ? 'Anthropic' : 'OpenAI' }}
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<template #cell-billing_type="{ row }">
|
||||
<div class="space-y-1">
|
||||
<!-- Type Badge -->
|
||||
<span
|
||||
:class="[
|
||||
'inline-block px-2 py-0.5 rounded-full text-xs font-medium',
|
||||
row.subscription_type === 'subscription'
|
||||
? 'bg-violet-100 text-violet-700 dark:bg-violet-900/30 dark:text-violet-400'
|
||||
: 'bg-gray-100 text-gray-600 dark:bg-gray-700 dark:text-gray-300'
|
||||
]"
|
||||
>
|
||||
{{ row.subscription_type === 'subscription' ? t('admin.groups.subscription.subscription') : t('admin.groups.subscription.standard') }}
|
||||
</span>
|
||||
<!-- Subscription Limits - compact single line -->
|
||||
<div v-if="row.subscription_type === 'subscription'" class="text-xs text-gray-500 dark:text-gray-400">
|
||||
<template v-if="row.daily_limit_usd || row.weekly_limit_usd || row.monthly_limit_usd">
|
||||
<span v-if="row.daily_limit_usd">${{ row.daily_limit_usd }}/{{ t('admin.groups.limitDay') }}</span>
|
||||
<span v-if="row.daily_limit_usd && (row.weekly_limit_usd || row.monthly_limit_usd)" class="mx-1 text-gray-300 dark:text-gray-600">·</span>
|
||||
<span v-if="row.weekly_limit_usd">${{ row.weekly_limit_usd }}/{{ t('admin.groups.limitWeek') }}</span>
|
||||
<span v-if="row.weekly_limit_usd && row.monthly_limit_usd" class="mx-1 text-gray-300 dark:text-gray-600">·</span>
|
||||
<span v-if="row.monthly_limit_usd">${{ row.monthly_limit_usd }}/{{ t('admin.groups.limitMonth') }}</span>
|
||||
</template>
|
||||
<span v-else class="text-gray-400 dark:text-gray-500">{{ t('admin.groups.subscription.noLimit') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #cell-rate_multiplier="{ value }">
|
||||
<span class="text-sm text-gray-700 dark:text-gray-300">{{ value }}x</span>
|
||||
</template>
|
||||
@@ -186,8 +218,8 @@
|
||||
<input
|
||||
v-model.number="createForm.rate_multiplier"
|
||||
type="number"
|
||||
step="0.1"
|
||||
min="0.1"
|
||||
step="0.001"
|
||||
min="0.001"
|
||||
required
|
||||
class="input"
|
||||
/>
|
||||
@@ -323,15 +355,17 @@
|
||||
<Select
|
||||
v-model="editForm.platform"
|
||||
:options="platformOptions"
|
||||
:disabled="true"
|
||||
/>
|
||||
<p class="input-hint">{{ t('admin.groups.platformNotEditable') }}</p>
|
||||
</div>
|
||||
<div v-if="editForm.subscription_type !== 'subscription'">
|
||||
<label class="input-label">{{ t('admin.groups.form.rateMultiplier') }}</label>
|
||||
<input
|
||||
v-model.number="editForm.rate_multiplier"
|
||||
type="number"
|
||||
step="0.1"
|
||||
min="0.1"
|
||||
step="0.001"
|
||||
min="0.001"
|
||||
required
|
||||
class="input"
|
||||
/>
|
||||
@@ -472,6 +506,7 @@ import Modal from '@/components/common/Modal.vue'
|
||||
import ConfirmDialog from '@/components/common/ConfirmDialog.vue'
|
||||
import EmptyState from '@/components/common/EmptyState.vue'
|
||||
import Select from '@/components/common/Select.vue'
|
||||
import PlatformIcon from '@/components/common/PlatformIcon.vue'
|
||||
|
||||
const { t } = useI18n()
|
||||
const appStore = useAppStore()
|
||||
@@ -479,6 +514,7 @@ const appStore = useAppStore()
|
||||
const columns = computed<Column[]>(() => [
|
||||
{ key: 'name', label: t('admin.groups.columns.name'), sortable: true },
|
||||
{ key: 'platform', label: t('admin.groups.columns.platform'), sortable: true },
|
||||
{ key: 'billing_type', label: t('admin.groups.columns.billingType'), sortable: true },
|
||||
{ key: 'rate_multiplier', label: t('admin.groups.columns.rateMultiplier'), sortable: true },
|
||||
{ key: 'is_exclusive', label: t('admin.groups.columns.type'), sortable: true },
|
||||
{ key: 'account_count', label: t('admin.groups.columns.accounts'), sortable: true },
|
||||
|
||||
Reference in New Issue
Block a user