fix(web): center button loading indicators

Co-authored-by: Codex <noreply@openai.com>
This commit is contained in:
Zyy
2026-08-06 14:53:53 +08:00
co-authored by Codex
parent 98652d4c68
commit e7036a616f
6 changed files with 51 additions and 38 deletions
+16 -7
View File
@@ -70,7 +70,7 @@ const loadingIconSize: Record<ButtonSize, number> = {
}
const classes = computed(() => [
'inline-flex items-center justify-center border-0 font-bold leading-[1.15] transition-[background-color,box-shadow,color,opacity,transform] duration-150 cursor-pointer outline-none motion-safe:active:translate-y-px motion-safe:active:scale-[0.985]',
'relative inline-flex items-center justify-center border-0 font-bold leading-[1.15] transition-[background-color,box-shadow,color,opacity,transform] duration-150 cursor-pointer outline-none motion-safe:active:translate-y-px motion-safe:active:scale-[0.985]',
props.iconOnly ? iconOnlySizeClasses[props.size] : sizeClasses[props.size],
props.iconOnly ? '' : 'rounded-(--cp-button-radius-base)',
'focus-visible:ring-2 focus-visible:ring-(--cp-info-border) focus-visible:ring-offset-2 focus-visible:ring-offset-(--cp-bg-surface)',
@@ -110,23 +110,32 @@ const labelClasses = computed(() => [
>
<span
v-if="loading"
class="inline-flex shrink-0 items-center justify-center leading-none"
class="pointer-events-none absolute inset-0 inline-flex items-center justify-center leading-none"
aria-hidden="true"
>
<LoaderCircle
:size="loadingIconSize[size]"
class="block animate-spin origin-center will-change-transform transform-fill"
class="block animate-spin origin-center [transform-box:view-box] will-change-transform"
/>
</span>
<span v-if="$slots.icon && !loading" class="inline-flex shrink-0">
<span
v-if="$slots.icon"
class="inline-flex shrink-0 transition-opacity duration-150"
:class="loading ? 'opacity-0' : undefined"
>
<slot name="icon" />
</span>
<span v-if="!iconOnly && $slots.default" :class="labelClasses">
<span
v-if="!iconOnly && $slots.default"
class="transition-opacity duration-150"
:class="[labelClasses, loading ? 'opacity-0' : undefined]"
>
<slot />
</span>
<span
v-if="iconOnly && !$slots.icon && !loading"
class="inline-flex items-center justify-center"
v-if="iconOnly && !$slots.icon"
class="inline-flex items-center justify-center transition-opacity duration-150"
:class="loading ? 'opacity-0' : undefined"
>
<slot />
</span>
@@ -45,10 +45,11 @@ const quotaWindows = computed(() => orderedPanelQuotaWindows(props.account.quota
variant="ghost"
size="sm"
title="刷新额度"
:loading="refreshing"
:disabled="refreshing"
@click="emit('refreshQuota', account.id)"
>
<RefreshCw class="size-3.5" :class="refreshing ? 'animate-spin' : undefined" />
<RefreshCw class="size-3.5" />
</BaseButton>
</div>
@@ -1,6 +1,6 @@
<script setup lang="ts">
import type { AccountRow } from '../constants'
import { KeyRound, LoaderCircle, MoreHorizontal, Power, RefreshCw, Trash2, Wifi } from '@lucide/vue'
import { KeyRound, MoreHorizontal, Power, RefreshCw, Trash2, Wifi } from '@lucide/vue'
import { computed } from 'vue'
import BaseButton from '@/components/base/BaseButton.vue'
@@ -52,51 +52,51 @@ const canReauthorize = computed(() => props.account.provider === 'openai')
</template>
<template #default="{ close }">
<button
type="button"
class="flex h-8.5 w-full items-center gap-2 rounded-(--cp-input-radius-small) border-0 bg-transparent px-3 text-left text-[13px] leading-none font-emphasis text-(--cp-text-primary) transition-colors hover:bg-(--cp-default-bg-hover) disabled:cursor-not-allowed disabled:text-(--cp-disabled-text)"
<BaseButton
variant="ghost"
size="sm"
class="h-8.5! w-full justify-start! gap-2! rounded-(--cp-input-radius-small)! px-3! text-left text-[13px] leading-none! font-emphasis! text-(--cp-text-primary)!"
:loading="testing"
:disabled="testing"
@click.stop="(close(), emit('test', account))"
>
<LoaderCircle v-if="testing" class="size-3.5 animate-spin text-(--cp-text-muted)" />
<Wifi v-else class="size-3.5 text-(--cp-text-muted)" />
<Wifi class="size-3.5 text-(--cp-text-muted)" />
测试连接
</button>
<button
</BaseButton>
<BaseButton
v-if="canRefreshToken"
type="button"
class="flex h-8.5 w-full items-center gap-2 rounded-(--cp-input-radius-small) border-0 bg-transparent px-3 text-left text-[13px] leading-none font-emphasis text-(--cp-text-primary) transition-colors hover:bg-(--cp-default-bg-hover) disabled:cursor-not-allowed disabled:text-(--cp-disabled-text)"
variant="ghost"
size="sm"
class="h-8.5! w-full justify-start! gap-2! rounded-(--cp-input-radius-small)! px-3! text-left text-[13px] leading-none! font-emphasis! text-(--cp-text-primary)!"
:loading="refreshing"
:disabled="refreshing"
@click.stop="(close(), emit('refresh', account.id))"
>
<RefreshCw
class="size-3.5 text-(--cp-text-muted)"
:class="refreshing ? 'animate-spin' : undefined"
/>
<RefreshCw class="size-3.5 text-(--cp-text-muted)" />
刷新 token
</button>
<button
</BaseButton>
<BaseButton
v-if="canReauthorize"
type="button"
class="flex h-8.5 w-full items-center gap-2 rounded-(--cp-input-radius-small) border-0 bg-transparent px-3 text-left text-[13px] leading-none font-emphasis text-(--cp-text-primary) transition-colors hover:bg-(--cp-default-bg-hover)"
variant="ghost"
size="sm"
class="h-8.5! w-full justify-start! gap-2! rounded-(--cp-input-radius-small)! px-3! text-left text-[13px] leading-none! font-emphasis! text-(--cp-text-primary)!"
@click.stop="(close(), emit('reauthorize', account))"
>
<KeyRound class="size-3.5 text-(--cp-text-muted)" />
重新授权
</button>
<button
</BaseButton>
<BaseButton
variant="ghost"
size="sm"
class="h-8.5! w-full justify-start! gap-2! rounded-(--cp-input-radius-small)! px-3! text-left text-[13px] leading-none! font-emphasis! text-(--cp-text-primary)!"
:loading="updatingStatus"
type="button"
class="flex h-8.5 w-full items-center gap-2 rounded-(--cp-input-radius-small) border-0 bg-transparent px-3 text-left text-[13px] leading-none font-emphasis text-(--cp-text-primary) transition-colors hover:bg-(--cp-default-bg-hover) disabled:cursor-not-allowed disabled:text-(--cp-disabled-text)"
:disabled="updatingStatus"
@click.stop="(close(), emit('toggleSchedule', account))"
>
<LoaderCircle
v-if="updatingStatus"
class="size-3.5 animate-spin text-(--cp-text-muted)"
/>
<Power v-else class="size-3.5 text-(--cp-text-muted)" />
<Power class="size-3.5 text-(--cp-text-muted)" />
{{ scheduleLabel }}
</button>
</BaseButton>
</template>
</BasePopover>
</div>
+2 -1
View File
@@ -46,10 +46,11 @@ const {
class="text-(--cp-normal)"
size="md"
label="刷新概览"
:loading="loading || refreshing"
:disabled="loading || refreshing"
@click="refresh"
>
<RefreshCw :size="19" :class="loading || refreshing ? 'animate-spin' : undefined" />
<RefreshCw :size="19" />
</BaseButton>
</template>
</BasePageHeader>
@@ -69,10 +69,11 @@ function showDetail(record: Awaited<ReturnType<typeof getOpsErrors>>['items'][nu
variant="ghost"
size="md"
label="刷新错误明细"
:loading="refreshing"
:disabled="loading || refreshing"
@click="refresh"
>
<RefreshCw class="size-4.5" :class="refreshing ? 'animate-spin' : undefined" />
<RefreshCw class="size-4.5" />
</BaseButton>
</div>
</div>
@@ -32,10 +32,11 @@ const search = defineModel<string>('search', { required: true })
variant="ghost"
size="md"
label="刷新使用记录"
:loading="refreshing"
:disabled="loading || refreshing"
@click="emit('refresh')"
>
<RefreshCw class="size-4.5" :class="refreshing ? 'animate-spin' : undefined" />
<RefreshCw class="size-4.5" />
</BaseButton>
</div>
</div>