diff --git a/frontend/src/components/base/BaseButton.vue b/frontend/src/components/base/BaseButton.vue
index 3a6e15dfb..d18ea25de 100644
--- a/frontend/src/components/base/BaseButton.vue
+++ b/frontend/src/components/base/BaseButton.vue
@@ -70,11 +70,11 @@ const loadingIconSize: Record = {
}
const classes = computed(() => [
- 'inline-flex items-center justify-center border-0 font-[720] leading-[1.15] transition-[background-color,box-shadow,color,opacity,transform] duration-150 cursor-pointer outline-none',
+ 'inline-flex items-center justify-center border-0 font-[720] 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)',
- 'disabled:cursor-not-allowed disabled:bg-(--cp-disabled-bg) disabled:text-(--cp-disabled-text) disabled:shadow-none',
+ 'disabled:cursor-not-allowed disabled:transform-none disabled:bg-(--cp-disabled-bg) disabled:text-(--cp-disabled-text) disabled:shadow-none',
props.iconOnly && (props.variant === 'default' || props.variant === 'primary')
? iconOnlyDefaultClasses
: variantClasses[props.variant],
diff --git a/frontend/src/components/base/BaseMotionIcon.vue b/frontend/src/components/base/BaseMotionIcon.vue
new file mode 100644
index 000000000..3898a3ce3
--- /dev/null
+++ b/frontend/src/components/base/BaseMotionIcon.vue
@@ -0,0 +1,302 @@
+
+
+
+
+
+
+
+
+
diff --git a/frontend/src/layout/components/AppSidebar.vue b/frontend/src/layout/components/AppSidebar.vue
index 3f0443050..7d841c5d8 100644
--- a/frontend/src/layout/components/AppSidebar.vue
+++ b/frontend/src/layout/components/AppSidebar.vue
@@ -1,7 +1,16 @@
@@ -266,11 +348,13 @@ watch(
class="mt-6 grid h-12 grid-cols-[44px_minmax(0,1fr)] items-center"
:class="isCollapsed ? 'w-11 justify-start' : 'w-full gap-3'"
>
-
-
+
-
+
+
diff --git a/frontend/src/views/accounts/components/AccountOverviewCards.vue b/frontend/src/views/accounts/components/AccountOverviewCards.vue
index e664265a5..a52956a3b 100644
--- a/frontend/src/views/accounts/components/AccountOverviewCards.vue
+++ b/frontend/src/views/accounts/components/AccountOverviewCards.vue
@@ -3,6 +3,7 @@ import { computed } from 'vue'
import { AlertTriangle, Gauge, ShieldCheck, Users } from '@lucide/vue'
import BaseCard from '@/components/base/BaseCard.vue'
+import BaseMotionIcon from '@/components/base/BaseMotionIcon.vue'
const props = defineProps<{
summary: any
@@ -80,12 +81,13 @@ function overviewIconClass(tone: string) {
{{ item.caption }}
-
-
+
diff --git a/frontend/src/views/dashboard/components/AccountOverviewCard.vue b/frontend/src/views/dashboard/components/AccountOverviewCard.vue
index 28f68765f..8d616581e 100644
--- a/frontend/src/views/dashboard/components/AccountOverviewCard.vue
+++ b/frontend/src/views/dashboard/components/AccountOverviewCard.vue
@@ -3,9 +3,9 @@ import { clamp } from 'es-toolkit'
import { computed } from 'vue'
import { CircleCheck, RefreshCw, ShieldAlert, TriangleAlert } from '@lucide/vue'
-import BaseCard from '../../../components/base/BaseCard.vue'
-import BaseEmpty from '../../../components/base/BaseEmpty.vue'
-import AccountIdentityCell from '../../accounts/components/AccountIdentityCell.vue'
+import BaseCard from '@/components/base/BaseCard.vue'
+import BaseEmpty from '@/components/base/BaseEmpty.vue'
+import AccountIdentityCell from '@/views/accounts/components/AccountIdentityCell.vue'
const props = defineProps<{
accounts: any[]
diff --git a/frontend/src/views/dashboard/components/DashboardHeartbeat.vue b/frontend/src/views/dashboard/components/DashboardHeartbeat.vue
new file mode 100644
index 000000000..b732a5984
--- /dev/null
+++ b/frontend/src/views/dashboard/components/DashboardHeartbeat.vue
@@ -0,0 +1,77 @@
+
+
+
+
+
+
+
+
+ 自动刷新 30s
+
+
diff --git a/frontend/src/views/dashboard/components/MetricCard.vue b/frontend/src/views/dashboard/components/MetricCard.vue
index 1f9abc208..7ff932f7f 100644
--- a/frontend/src/views/dashboard/components/MetricCard.vue
+++ b/frontend/src/views/dashboard/components/MetricCard.vue
@@ -3,8 +3,9 @@ import type { EChartsOption } from 'echarts'
import type { Component } from 'vue'
import { computed } from 'vue'
-import BaseCard from '../../../components/base/BaseCard.vue'
-import BaseChart from '../../../components/charts/BaseChart.vue'
+import BaseCard from '@/components/base/BaseCard.vue'
+import BaseMotionIcon from '@/components/base/BaseMotionIcon.vue'
+import BaseChart from '@/components/charts/BaseChart.vue'
import AnimatedMetricValue from './AnimatedMetricValue.vue'
@@ -110,12 +111,13 @@ const sparklineOption = computed(() => {
-
-
+
{{
metric.title
}}
diff --git a/frontend/src/views/dashboard/components/RequestHealthTimelineCard.vue b/frontend/src/views/dashboard/components/RequestHealthTimelineCard.vue
index efe191a0e..7e7983dd7 100644
--- a/frontend/src/views/dashboard/components/RequestHealthTimelineCard.vue
+++ b/frontend/src/views/dashboard/components/RequestHealthTimelineCard.vue
@@ -3,8 +3,8 @@ import { usePreferredReducedMotion } from '@vueuse/core'
import { gsap } from 'gsap'
import { computed, onBeforeUnmount, shallowRef, useTemplateRef, watch } from 'vue'
-import BaseCard from '../../../components/base/BaseCard.vue'
-import BasePopover from '../../../components/base/BasePopover.vue'
+import BaseCard from '@/components/base/BaseCard.vue'
+import BasePopover from '@/components/base/BasePopover.vue'
import HealthTimelinePointPopover from './HealthTimelinePointPopover.vue'
import {
formatHealthCount,
diff --git a/frontend/src/views/dashboard/components/RequestTrendCard.vue b/frontend/src/views/dashboard/components/RequestTrendCard.vue
index f4f3ea16b..3cad70921 100644
--- a/frontend/src/views/dashboard/components/RequestTrendCard.vue
+++ b/frontend/src/views/dashboard/components/RequestTrendCard.vue
@@ -1,12 +1,13 @@
@@ -449,21 +492,33 @@ function handleTrendChange(value: string) {
-
@@ -476,7 +531,7 @@ function handleTrendChange(value: string) {
{{ item.value }}
-
+
diff --git a/frontend/src/views/dashboard/composables/useDashboard.ts b/frontend/src/views/dashboard/composables/useDashboard.ts
index efca6297d..38ac4fe7a 100644
--- a/frontend/src/views/dashboard/composables/useDashboard.ts
+++ b/frontend/src/views/dashboard/composables/useDashboard.ts
@@ -10,6 +10,8 @@ import { formatDateTime } from '@/utils/date'
type DashboardTrendKind = 'usage' | 'latency' | 'errors'
+const METRIC_SPARKLINE_BUCKETS = 12
+
export function useDashboard(): any {
const activeTrendKind = ref
('usage')
const metrics = ref(metricCards(emptyDashboardSummary()))
@@ -284,7 +286,10 @@ export function useDashboard(): any {
}
if (lastActiveIndex < 0) return []
- return points.slice(Math.max(0, lastActiveIndex - 8), lastActiveIndex + 1)
+ return points.slice(
+ Math.max(0, lastActiveIndex - (METRIC_SPARKLINE_BUCKETS - 1)),
+ lastActiveIndex + 1,
+ )
}
function formatDashboardCompactNumber(value: number) {
diff --git a/frontend/src/views/dashboard/index.vue b/frontend/src/views/dashboard/index.vue
index fd3100ea9..d061b8fba 100644
--- a/frontend/src/views/dashboard/index.vue
+++ b/frontend/src/views/dashboard/index.vue
@@ -4,6 +4,7 @@ import { RefreshCw } from '@lucide/vue'
import BaseButton from '@/components/base/BaseButton.vue'
import AccountOverviewCard from './components/AccountOverviewCard.vue'
+import DashboardHeartbeat from './components/DashboardHeartbeat.vue'
import UsageRecordCard from './components/UsageRecordCard.vue'
import MetricCard from './components/MetricCard.vue'
import RequestHealthTimelineCard from './components/RequestHealthTimelineCard.vue'
@@ -26,6 +27,7 @@ const {
poolSummary,
capacityInfo,
rotationStrategy,
+ lastRefreshedAt,
refresh,
loadTrend,
} = useDashboard()
@@ -38,8 +40,11 @@ const {
-
- 当日统计 · 自动刷新 30s
+
+ 当日统计
+
diff --git a/frontend/src/views/login/components/LoginPanel.vue b/frontend/src/views/login/components/LoginPanel.vue
index 1f409aa18..83891839a 100644
--- a/frontend/src/views/login/components/LoginPanel.vue
+++ b/frontend/src/views/login/components/LoginPanel.vue
@@ -5,6 +5,7 @@ import { computed, shallowRef } from 'vue'
import BaseButton from '@/components/base/BaseButton.vue'
import BaseCard from '@/components/base/BaseCard.vue'
import BaseInput from '@/components/base/BaseInput.vue'
+import BaseMotionIcon from '@/components/base/BaseMotionIcon.vue'
type ThemeName = 'light' | 'dark'
type PasswordInputType = 'password' | 'text'
@@ -56,9 +57,9 @@ function togglePasswordVisible(): void {
-
+
-
+
-
-
+
{{ status.exists ? '已启用' : '未生成' }}
diff --git a/frontend/src/views/usage/components/UsageSummaryCards.vue b/frontend/src/views/usage/components/UsageSummaryCards.vue
index ec685aaba..953bb29d8 100644
--- a/frontend/src/views/usage/components/UsageSummaryCards.vue
+++ b/frontend/src/views/usage/components/UsageSummaryCards.vue
@@ -2,6 +2,8 @@
import { Activity, Database, FileText, Timer } from '@lucide/vue'
import { computed } from 'vue'
+import BaseMotionIcon from '@/components/base/BaseMotionIcon.vue'
+
const props = defineProps<{
summary: any
}>()
@@ -52,12 +54,13 @@ const items = computed(() => [
:key="item.key"
class="grid min-h-23 grid-cols-[36px_minmax(0,1fr)] items-stretch gap-3 rounded-(--cp-card-radius) bg-(--cp-bg-surface) px-4 py-3 shadow-(--cp-shadow-card)"
>
-
-
+
{{ item.label }}