mirror of
https://github.com/zyycn/codex-proxy-rs.git
synced 2026-11-06 08:06:19 +00:00
fix(frontend): refine responsive interface states
Co-authored-by: Codex <noreply@openai.com>
This commit is contained in:
@@ -52,7 +52,7 @@ const sizeClasses: Record<InputSize, string> = {
|
||||
}
|
||||
|
||||
const containerClasses = computed(() => [
|
||||
'relative inline-flex w-full min-w-0 items-center overflow-visible rounded-cp border-0 text-cp-text shadow-cp-input transition-[background-color,box-shadow,color] duration-[160ms] [--base-input-autofill-bg:var(--cp-input-bg)] hover:[--base-input-autofill-bg:var(--cp-input-hover-bg)] focus-within:[--base-input-autofill-bg:var(--cp-input-active-bg)] motion-reduce:transition-none',
|
||||
'relative inline-flex w-full min-w-0 items-center overflow-visible rounded-cp border-0 text-cp-text shadow-cp-input transition-[background-color,box-shadow,color] duration-[160ms] motion-reduce:transition-none',
|
||||
sizeClasses[props.size],
|
||||
props.disabled
|
||||
? 'cursor-not-allowed bg-cp-bg-container-disabled text-cp-text-disabled shadow-none'
|
||||
@@ -73,6 +73,11 @@ const iconClasses = computed(() => [
|
||||
function updateModel(event: Event) {
|
||||
model.value = (event.target as HTMLInputElement).value
|
||||
}
|
||||
|
||||
function syncAutofill(event: AnimationEvent) {
|
||||
if (event.animationName === 'base-input-autofill-sync')
|
||||
updateModel(event)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -93,6 +98,8 @@ function updateModel(event: Event) {
|
||||
:aria-describedby="describedBy"
|
||||
:aria-invalid="invalid || undefined"
|
||||
:aria-required="field?.required.value || undefined"
|
||||
@animationstart="syncAutofill"
|
||||
@change="updateModel"
|
||||
@input="updateModel"
|
||||
>
|
||||
<span v-if="$slots.suffix" :class="iconClasses">
|
||||
@@ -107,9 +114,23 @@ function updateModel(event: Event) {
|
||||
.base-input__field:-webkit-autofill:hover,
|
||||
.base-input__field:-webkit-autofill:focus,
|
||||
.base-input__field:autofill {
|
||||
animation-name: base-input-autofill-sync;
|
||||
animation-duration: 1ms;
|
||||
background-clip: text !important;
|
||||
caret-color: var(--cp-color-text);
|
||||
-webkit-background-clip: text !important;
|
||||
-webkit-text-fill-color: var(--cp-color-text) !important;
|
||||
-webkit-box-shadow: 0 0 0 1000px var(--base-input-autofill-bg) inset !important;
|
||||
box-shadow: 0 0 0 1000px var(--base-input-autofill-bg) inset !important;
|
||||
-webkit-box-shadow: none !important;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
@keyframes base-input-autofill-sync {
|
||||
from {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -31,7 +31,7 @@ const emit = defineEmits<{
|
||||
}>()
|
||||
|
||||
const edgeGap = 12
|
||||
const defaultButtonSize = 44
|
||||
const defaultButtonSize = 48
|
||||
const dragClickThreshold = 6
|
||||
const storageKey = 'codex-proxy:mobile-sidebar-toggle-position'
|
||||
|
||||
@@ -220,7 +220,7 @@ onScopeDispose(() => {
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
class="inline-flex size-11.5 items-center justify-center rounded-cp border-0 bg-cp-bg-container text-cp-text shadow-[0_18px_34px_-18px_var(--cp-color-shadow)] outline-none transition-[background-color,box-shadow,color,opacity,transform] duration-150 hover:bg-cp-bg-text-hover hover:text-cp-primary-text active:scale-95 focus-visible:ring-2 focus-visible:ring-cp-control-outline focus-visible:ring-offset-2 focus-visible:ring-offset-cp-bg-layout"
|
||||
class="inline-flex size-12 touch-none select-none items-center justify-center rounded-cp border-0 bg-cp-bg-elevated text-cp-primary-text shadow-cp outline-none transition-[background-color,box-shadow,color,opacity,transform] duration-150 [-webkit-tap-highlight-color:transparent] hover:bg-cp-fill-tertiary hover:text-cp-text active:scale-95 active:bg-cp-control-item-bg-active focus-visible:ring-2 focus-visible:ring-cp-control-outline focus-visible:ring-offset-2 focus-visible:ring-offset-cp-bg-layout"
|
||||
:class="
|
||||
isDragging
|
||||
? 'scale-95 cursor-grabbing shadow-[0_14px_28px_-20px_var(--cp-color-shadow)]'
|
||||
|
||||
@@ -40,15 +40,17 @@ const groupOptions = computed(() => [
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="flex w-full flex-col gap-3 md:flex-row md:flex-wrap md:items-center"
|
||||
class="flex w-full flex-col gap-3 xl:flex-row xl:flex-wrap xl:items-center"
|
||||
role="group"
|
||||
aria-label="账号筛选与操作"
|
||||
>
|
||||
<div class="flex min-w-0 flex-wrap items-center gap-2 md:flex-none md:gap-3">
|
||||
<div
|
||||
class="grid w-full min-w-0 grid-cols-2 items-center gap-2 sm:grid-cols-[minmax(0,1fr)_minmax(0,1fr)_7.75rem] xl:flex xl:w-auto xl:flex-none xl:flex-wrap xl:gap-3"
|
||||
>
|
||||
<BaseInput
|
||||
v-model="search"
|
||||
placeholder="搜索账号"
|
||||
class="min-w-0 flex-1 md:w-80 md:flex-none"
|
||||
class="col-span-2 min-w-0 sm:col-span-3 xl:w-80 xl:flex-none"
|
||||
>
|
||||
<template #prefix>
|
||||
<Search class="size-4.5 text-cp-text-tertiary" />
|
||||
@@ -59,7 +61,7 @@ const groupOptions = computed(() => [
|
||||
v-model="status"
|
||||
:options="accountStatusFilterOptions"
|
||||
aria-label="按账号状态筛选"
|
||||
class="w-34 shrink-0 md:w-40"
|
||||
class="w-full min-w-0 xl:w-40 xl:shrink-0"
|
||||
/>
|
||||
|
||||
<BaseSelect
|
||||
@@ -67,22 +69,22 @@ const groupOptions = computed(() => [
|
||||
:options="groupOptions"
|
||||
:disabled="groupsLoading"
|
||||
aria-label="按账号分组筛选"
|
||||
class="w-34 shrink-0 md:w-40"
|
||||
class="w-full min-w-0 xl:w-40 xl:shrink-0"
|
||||
/>
|
||||
|
||||
<ProviderFilterSegmented
|
||||
v-model="provider"
|
||||
class="w-31 shrink-0"
|
||||
class="col-span-2 w-full sm:col-span-1 xl:w-31 xl:shrink-0"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="grid w-full grid-cols-2 gap-2 md:flex md:w-auto md:flex-wrap md:shrink-0 md:self-end md:items-center md:justify-end md:ml-auto"
|
||||
class="grid w-full grid-cols-2 gap-2 xl:flex xl:w-auto xl:flex-wrap xl:shrink-0 xl:self-end xl:items-center xl:justify-end xl:ml-auto"
|
||||
>
|
||||
<BaseButton
|
||||
v-if="selectedCount > 0"
|
||||
variant="secondary"
|
||||
class="w-full whitespace-nowrap md:w-auto"
|
||||
class="w-full whitespace-nowrap xl:w-auto"
|
||||
@click="emit('editSelected')"
|
||||
>
|
||||
<Pencil class="size-4 text-cp-link" />
|
||||
@@ -91,7 +93,7 @@ const groupOptions = computed(() => [
|
||||
<BaseButton
|
||||
v-if="selectedCount > 0"
|
||||
variant="destructive"
|
||||
class="w-full whitespace-nowrap md:w-auto"
|
||||
class="w-full whitespace-nowrap xl:w-auto"
|
||||
:disabled="batchDeleting"
|
||||
@click="emit('deleteSelected')"
|
||||
>
|
||||
@@ -101,7 +103,7 @@ const groupOptions = computed(() => [
|
||||
<BaseButton
|
||||
v-if="selectedCount > 0"
|
||||
variant="secondary"
|
||||
class="w-full whitespace-nowrap md:w-auto"
|
||||
class="w-full whitespace-nowrap xl:w-auto"
|
||||
:loading="exportingAccounts"
|
||||
@click="emit('exportSelected')"
|
||||
>
|
||||
@@ -112,7 +114,7 @@ const groupOptions = computed(() => [
|
||||
</BaseButton>
|
||||
<BaseButton
|
||||
variant="primary"
|
||||
class="whitespace-nowrap md:w-auto"
|
||||
class="whitespace-nowrap xl:w-auto"
|
||||
:class="selectedCount > 0 ? 'col-span-2 w-full' : 'col-span-2 justify-self-end'"
|
||||
@click="emit('create')"
|
||||
>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { ArrowRight, CircleAlert, Eye, EyeOff, KeyRound, Mail, Moon, Sun } from '@lucide/vue'
|
||||
import { CircleAlert, Eye, EyeOff, KeyRound, Mail, Moon, Sun } from '@lucide/vue'
|
||||
import { computed, shallowRef } from 'vue'
|
||||
|
||||
import AppBrandMark from '@/components/AppBrandMark.vue'
|
||||
@@ -45,7 +45,7 @@ function togglePasswordVisible(): void {
|
||||
<BaseCard
|
||||
as="form"
|
||||
padding="none"
|
||||
class="login-form relative grid min-h-129.25 w-[min(440px,100%)] gap-2.5 rounded-lg px-7.5 pt-6.5 pb-6 max-[560px]:min-h-auto max-[560px]:gap-2.5 max-[560px]:p-5.5"
|
||||
class="login-form relative grid min-h-120 w-[min(440px,100%)] gap-2.5 rounded-lg px-7.5 pt-6.5 pb-6 max-[560px]:min-h-auto max-[560px]:gap-4 max-[560px]:p-5.5"
|
||||
@submit.prevent="emit('submit')"
|
||||
>
|
||||
<div class="login-form-line" />
|
||||
@@ -81,14 +81,14 @@ function togglePasswordVisible(): void {
|
||||
</button>
|
||||
</header>
|
||||
|
||||
<section class="grid min-w-0 gap-1" aria-labelledby="login-title">
|
||||
<section class="grid min-w-0 gap-1 max-[560px]:gap-2" aria-labelledby="login-title">
|
||||
<h1
|
||||
id="login-title"
|
||||
class="m-0 text-[34px] leading-[1.02] font-semibold text-(--cp-login-title-color) max-[560px]:text-[30px]"
|
||||
>
|
||||
控制台登录
|
||||
</h1>
|
||||
<p class="m-0 -ml-2 text-sm leading-[1.45] font-normal text-(--cp-login-description-color)">
|
||||
<p class="m-0 -ml-2 text-sm leading-[1.45] font-normal text-(--cp-login-description-color) max-[560px]:py-2">
|
||||
「 欢迎回来,登录以开始您的数据之旅 」
|
||||
</p>
|
||||
</section>
|
||||
@@ -153,7 +153,6 @@ function togglePasswordVisible(): void {
|
||||
:disabled="props.submitDisabled"
|
||||
>
|
||||
<span>{{ submitLabel }}</span>
|
||||
<ArrowRight v-if="!props.loading" :size="18" />
|
||||
</BaseButton>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -236,7 +236,7 @@ function formatTooltip(params: unknown) {
|
||||
</strong>
|
||||
</div>
|
||||
</div>
|
||||
<BaseChart v-if="hasData" :option="chartOption" :height="210" />
|
||||
<BaseChart v-if="hasData" :option="chartOption" :height="210" class="-mb-2" />
|
||||
<BaseEmpty
|
||||
v-else
|
||||
size="sm"
|
||||
|
||||
@@ -18,7 +18,7 @@ function valueClass(item: { mono?: boolean, wrap?: boolean }) {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<dl class="mt-3 grid grid-cols-1 gap-x-4 gap-y-3 sm:grid-cols-2">
|
||||
<dl class="mt-3 grid min-w-0 grid-cols-1 gap-x-4 gap-y-3 sm:grid-cols-2">
|
||||
<div v-for="item in items" :key="item.label" class="min-w-0">
|
||||
<dt :class="fieldLabelClass">
|
||||
{{ item.label }}
|
||||
|
||||
@@ -176,7 +176,7 @@ function formatTooltip(params: unknown) {
|
||||
</strong>
|
||||
</div>
|
||||
</div>
|
||||
<BaseChart v-if="hasData" :option="chartOption" :height="210" />
|
||||
<BaseChart v-if="hasData" :option="chartOption" :height="210" class="-mb-2" />
|
||||
<BaseEmpty
|
||||
v-else
|
||||
size="sm"
|
||||
|
||||
@@ -221,7 +221,7 @@ function formatThroughput(value: number | null) {
|
||||
</strong>
|
||||
</div>
|
||||
</div>
|
||||
<BaseChart v-if="hasData" :option="chartOption" :height="210" />
|
||||
<BaseChart v-if="hasData" :option="chartOption" :height="210" class="-mb-2" />
|
||||
<BaseEmpty
|
||||
v-else
|
||||
size="sm"
|
||||
|
||||
@@ -47,7 +47,7 @@ const tokenDetails = computed(() => props.record ? usageTokenDetails(props.recor
|
||||
const billing = computed(() => props.record ? usageBilling(props.record) : null)
|
||||
const latencyDetails = computed(() => props.record ? usageLatencyDetails(props.record) : null)
|
||||
|
||||
const panelClass = 'rounded-cp-card bg-cp-fill-quaternary px-4 py-3.5'
|
||||
const panelClass = 'min-w-0 rounded-cp-card bg-cp-fill-quaternary px-4 py-3.5'
|
||||
const panelTitleClass = 'm-0 text-cp-sm leading-none font-heavy text-cp-text-secondary'
|
||||
|
||||
const accountDisplay = computed(() => props.record ? usageAccountText(props.record) : '—')
|
||||
@@ -284,7 +284,7 @@ const tokenDonutOption = computed<EChartsOption>(() => {
|
||||
tone="info"
|
||||
size="xl"
|
||||
>
|
||||
<div v-if="record" class="grid gap-3">
|
||||
<div v-if="record" class="grid min-w-0 gap-3">
|
||||
<section :class="panelClass">
|
||||
<div class="min-w-0">
|
||||
<span :class="fieldLabelClass">账号</span>
|
||||
@@ -324,8 +324,8 @@ const tokenDonutOption = computed<EChartsOption>(() => {
|
||||
</dl>
|
||||
</section>
|
||||
|
||||
<section class="grid gap-3 lg:grid-cols-[minmax(0,0.95fr)_minmax(0,1.05fr)]">
|
||||
<div class="flex min-h-0 flex-col gap-3">
|
||||
<section class="grid min-w-0 gap-3 lg:grid-cols-[minmax(0,0.95fr)_minmax(0,1.05fr)]">
|
||||
<div class="flex min-h-0 min-w-0 flex-col gap-3">
|
||||
<section :class="panelClass">
|
||||
<h3 :class="panelTitleClass">
|
||||
{{ modelRouteGroup.title }}
|
||||
@@ -338,7 +338,7 @@ const tokenDonutOption = computed<EChartsOption>(() => {
|
||||
Token
|
||||
</h3>
|
||||
<div
|
||||
class="mt-3 grid min-h-38 flex-1 grid-cols-1 content-center items-center gap-3 sm:grid-cols-[150px_minmax(0,1fr)]"
|
||||
class="mt-3 grid min-h-38 min-w-0 flex-1 grid-cols-1 content-center items-center gap-3 sm:grid-cols-[150px_minmax(0,1fr)]"
|
||||
>
|
||||
<div class="relative mx-auto w-38 sm:mx-0">
|
||||
<BaseChart :option="tokenDonutOption" :height="152" />
|
||||
@@ -356,7 +356,7 @@ const tokenDonutOption = computed<EChartsOption>(() => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<dl class="grid grid-cols-2 gap-x-4 gap-y-3">
|
||||
<dl class="grid min-w-0 grid-cols-2 gap-x-4 gap-y-3">
|
||||
<div v-for="item in tokenChartItems" :key="item.label" class="min-w-0">
|
||||
<dt class="flex min-w-0 items-center gap-1.5" :class="fieldLabelClass">
|
||||
<i
|
||||
@@ -377,7 +377,7 @@ const tokenDonutOption = computed<EChartsOption>(() => {
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<div class="flex min-h-0 flex-col gap-3">
|
||||
<div class="flex min-h-0 min-w-0 flex-col gap-3">
|
||||
<section class="flex-1" :class="[panelClass]">
|
||||
<h3 :class="panelTitleClass">
|
||||
{{ clientUpstreamGroup.title }}
|
||||
|
||||
Reference in New Issue
Block a user