fix: 修复设置页页眉回弹和状态色辨识度 #281

- 在侧栏布局滚动容器统一阻止纵向 overscroll
  - 拆分状态指示色与成功文本色,提升在线状态辨识度
  - 统一连接、运行及键盘 LED 等状态点样式
This commit is contained in:
mofeng-git
2026-07-29 18:50:20 +08:00
parent fbcd95b617
commit d414557d74
8 changed files with 31 additions and 28 deletions

View File

@@ -48,13 +48,13 @@ const keysDisplay = computed(() => {
<div class="flex items-center justify-between text-xs px-2 py-0.5"> <div class="flex items-center justify-between text-xs px-2 py-0.5">
<div v-if="keyboardLedEnabled" class="flex items-center gap-1"> <div v-if="keyboardLedEnabled" class="flex items-center gap-1">
<Badge variant="outline" class="h-4 gap-1 px-1 text-[10px] text-foreground"> <Badge variant="outline" class="h-4 gap-1 px-1 text-[10px] text-foreground">
<span class="size-1.5 rounded-full" :class="capsLock ? 'bg-success' : 'bg-warning'" />C <span class="size-1.5 rounded-full" :class="capsLock ? 'bg-status-active' : 'bg-warning'" />C
</Badge> </Badge>
<Badge variant="outline" class="h-4 gap-1 px-1 text-[10px] text-foreground"> <Badge variant="outline" class="h-4 gap-1 px-1 text-[10px] text-foreground">
<span class="size-1.5 rounded-full" :class="numLock ? 'bg-success' : 'bg-warning'" />N <span class="size-1.5 rounded-full" :class="numLock ? 'bg-status-active' : 'bg-warning'" />N
</Badge> </Badge>
<Badge variant="outline" class="h-4 gap-1 px-1 text-[10px] text-foreground"> <Badge variant="outline" class="h-4 gap-1 px-1 text-[10px] text-foreground">
<span class="size-1.5 rounded-full" :class="scrollLock ? 'bg-success' : 'bg-warning'" />S <span class="size-1.5 rounded-full" :class="scrollLock ? 'bg-status-active' : 'bg-warning'" />S
</Badge> </Badge>
</div> </div>
<div v-else class="text-[10px] text-muted-foreground/60"> <div v-else class="text-[10px] text-muted-foreground/60">
@@ -87,15 +87,15 @@ const keysDisplay = computed(() => {
<Separator orientation="vertical" class="h-5" /> <Separator orientation="vertical" class="h-5" />
<template v-if="keyboardLedEnabled"> <template v-if="keyboardLedEnabled">
<Badge variant="outline" class="mx-1 gap-1.5 text-foreground"> <Badge variant="outline" class="mx-1 gap-1.5 text-foreground">
<span class="size-1.5 rounded-full" :class="capsLock ? 'bg-success' : 'bg-warning'" /> <span class="size-1.5 rounded-full" :class="capsLock ? 'bg-status-active' : 'bg-warning'" />
{{ t('infobar.caps') }} {{ t('infobar.caps') }}
</Badge> </Badge>
<Badge variant="outline" class="mx-1 gap-1.5 text-foreground"> <Badge variant="outline" class="mx-1 gap-1.5 text-foreground">
<span class="size-1.5 rounded-full" :class="numLock ? 'bg-success' : 'bg-warning'" /> <span class="size-1.5 rounded-full" :class="numLock ? 'bg-status-active' : 'bg-warning'" />
{{ t('infobar.num') }} {{ t('infobar.num') }}
</Badge> </Badge>
<Badge variant="outline" class="mx-1 gap-1.5 text-foreground"> <Badge variant="outline" class="mx-1 gap-1.5 text-foreground">
<span class="size-1.5 rounded-full" :class="scrollLock ? 'bg-success' : 'bg-warning'" /> <span class="size-1.5 rounded-full" :class="scrollLock ? 'bg-status-active' : 'bg-warning'" />
{{ t('infobar.scroll') }} {{ t('infobar.scroll') }}
</Badge> </Badge>
</template> </template>

View File

@@ -714,8 +714,8 @@ onUnmounted(() => {
<span class="flex min-w-0 flex-wrap items-center gap-x-2 gap-y-1"> <span class="flex min-w-0 flex-wrap items-center gap-x-2 gap-y-1">
<span :class="msdConnected ? 'text-success' : 'text-muted-foreground'" class="flex items-center gap-1.5"> <span :class="msdConnected ? 'text-success' : 'text-muted-foreground'" class="flex items-center gap-1.5">
<span class="relative flex size-2"> <span class="relative flex size-2">
<span v-if="msdConnected" class="absolute inline-flex h-full w-full animate-ping rounded-full bg-success opacity-75"></span> <span v-if="msdConnected" class="absolute inline-flex size-full animate-ping rounded-full bg-status-active opacity-75"></span>
<span :class="msdConnected ? 'bg-success' : 'bg-muted-foreground'" class="relative inline-flex size-2 rounded-full"></span> <span :class="msdConnected ? 'bg-status-active' : 'bg-muted-foreground'" class="relative inline-flex size-2 rounded-full"></span>
</span> </span>
{{ msdConnected ? t('common.connected') : t('common.disconnected') }} {{ msdConnected ? t('common.connected') : t('common.disconnected') }}
</span> </span>

View File

@@ -529,8 +529,8 @@ onUnmounted(() => {
<Item variant="muted" size="sm"><ItemContent><ItemDescription>{{ t('stats.connectionType') }}</ItemDescription><ItemTitle class="flex items-center gap-1.5"> <Item variant="muted" size="sm"><ItemContent><ItemDescription>{{ t('stats.connectionType') }}</ItemDescription><ItemTitle class="flex items-center gap-1.5">
<span <span
:class="[ :class="[
'inline-block w-2 h-2 rounded-full', 'inline-block size-2 rounded-full',
currentStats.isRelay ? 'bg-warning' : 'bg-success' currentStats.isRelay ? 'bg-warning' : 'bg-status-active'
]" ]"
/> />
{{ currentStats.isRelay ? t('stats.relay') : t('stats.p2p') }} {{ currentStats.isRelay ? t('stats.relay') : t('stats.p2p') }}

View File

@@ -51,7 +51,7 @@ onMounted(() => {
const statusColor = computed(() => { const statusColor = computed(() => {
switch (props.status) { switch (props.status) {
case 'connected': case 'connected':
return 'bg-success' return 'bg-status-active'
case 'connecting': case 'connecting':
return 'bg-warning animate-pulse' return 'bg-warning animate-pulse'
case 'disconnected': case 'disconnected':

View File

@@ -11,7 +11,7 @@ const props = defineProps<{
<div <div
data-slot="sidebar-content" data-slot="sidebar-content"
data-sidebar="content" data-sidebar="content"
:class="cn('flex min-h-0 flex-1 flex-col gap-2 overflow-auto group-data-[collapsible=icon]:overflow-hidden', props.class)" :class="cn('flex min-h-0 flex-1 flex-col gap-2 overflow-auto overscroll-y-none group-data-[collapsible=icon]:overflow-hidden', props.class)"
> >
<slot /> <slot />
</div> </div>

View File

@@ -11,7 +11,7 @@ const props = defineProps<{
<main <main
data-slot="sidebar-inset" data-slot="sidebar-inset"
:class="cn( :class="cn(
'bg-background relative flex w-full flex-1 flex-col', 'bg-background relative flex min-h-0 w-full flex-1 flex-col overflow-y-auto overscroll-y-none',
'md:peer-data-[variant=inset]:m-2 md:peer-data-[variant=inset]:ml-0 md:peer-data-[variant=inset]:rounded-xl md:peer-data-[variant=inset]:shadow-sm md:peer-data-[variant=inset]:peer-data-[state=collapsed]:ml-2', 'md:peer-data-[variant=inset]:m-2 md:peer-data-[variant=inset]:ml-0 md:peer-data-[variant=inset]:rounded-xl md:peer-data-[variant=inset]:shadow-sm md:peer-data-[variant=inset]:peer-data-[state=collapsed]:ml-2',
props.class, props.class,
)" )"

View File

@@ -23,6 +23,8 @@
--destructive-foreground: oklch(98.48% 0 0); --destructive-foreground: oklch(98.48% 0 0);
--success: oklch(52.7% 0.154 150.07); --success: oklch(52.7% 0.154 150.07);
--success-foreground: oklch(98.48% 0 0); --success-foreground: oklch(98.48% 0 0);
/* Compact state indicators need stronger luminance separation than success text. */
--status-active: oklch(72.3% 0.219 149.58);
--warning: oklch(55.4% 0.135 66.44); --warning: oklch(55.4% 0.135 66.44);
--warning-foreground: oklch(98.48% 0 0); --warning-foreground: oklch(98.48% 0 0);
--info: oklch(54.6% 0.245 262.88); --info: oklch(54.6% 0.245 262.88);
@@ -111,6 +113,7 @@
--color-destructive-foreground: var(--destructive-foreground); --color-destructive-foreground: var(--destructive-foreground);
--color-success: var(--success); --color-success: var(--success);
--color-success-foreground: var(--success-foreground); --color-success-foreground: var(--success-foreground);
--color-status-active: var(--status-active);
--color-warning: var(--warning); --color-warning: var(--warning);
--color-warning-foreground: var(--warning-foreground); --color-warning-foreground: var(--warning-foreground);
--color-info: var(--info); --color-info: var(--info);

View File

@@ -368,7 +368,7 @@ function watchdogRequestError(error: unknown, action: 'enable' | 'disable'): str
const watchdogStatusClass = computed(() => { const watchdogStatusClass = computed(() => {
switch (watchdogStatusKey.value) { switch (watchdogStatusKey.value) {
case 'running': return 'bg-success' case 'running': return 'bg-status-active'
case 'error': return 'bg-destructive' case 'error': return 'bg-destructive'
default: return 'bg-muted-foreground' default: return 'bg-muted-foreground'
} }
@@ -819,7 +819,7 @@ function otgCheckStatusText(level: OtgSelfCheckLevel): string {
function otgGroupStatusClass(status: OtgCheckGroupStatus): string { function otgGroupStatusClass(status: OtgCheckGroupStatus): string {
if (status === 'error') return 'bg-destructive' if (status === 'error') return 'bg-destructive'
if (status === 'warn') return 'bg-warning' if (status === 'warn') return 'bg-warning'
if (status === 'ok') return 'bg-success' if (status === 'ok') return 'bg-status-active'
return 'bg-muted-foreground/40' return 'bg-muted-foreground/40'
} }
@@ -1778,7 +1778,7 @@ function getExtStatusClass(status: ExtensionStatus | undefined): string {
switch (status.state) { switch (status.state) {
case 'unavailable': return 'bg-muted-foreground' case 'unavailable': return 'bg-muted-foreground'
case 'stopped': return 'bg-muted-foreground' case 'stopped': return 'bg-muted-foreground'
case 'running': return 'bg-success' case 'running': return 'bg-status-active'
default: return 'bg-muted-foreground' default: return 'bg-muted-foreground'
} }
} }
@@ -2470,7 +2470,7 @@ function getRustdeskStatusClass(status: string | null | undefined): string {
switch (status) { switch (status) {
case 'running': case 'running':
case 'registered': case 'registered':
case 'connected': return 'bg-success' case 'connected': return 'bg-status-active'
case 'starting': case 'starting':
case 'connecting': return 'bg-warning' case 'connecting': return 'bg-warning'
case 'stopped': case 'stopped':
@@ -2648,7 +2648,7 @@ function getVncServiceStatusText(status: string | undefined): string {
function getVncStatusClass(status: string | undefined): string { function getVncStatusClass(status: string | undefined): string {
switch (status) { switch (status) {
case 'running': return 'bg-success' case 'running': return 'bg-status-active'
case 'starting': return 'bg-warning' case 'starting': return 'bg-warning'
case 'stopped': return 'bg-muted-foreground' case 'stopped': return 'bg-muted-foreground'
default: default:
@@ -2671,7 +2671,7 @@ function getRtspServiceStatusText(status: string | undefined): string {
function getRtspStatusClass(status: string | undefined): string { function getRtspStatusClass(status: string | undefined): string {
switch (status) { switch (status) {
case 'running': return 'bg-success' case 'running': return 'bg-status-active'
case 'starting': return 'bg-warning' case 'starting': return 'bg-warning'
case 'stopped': return 'bg-muted-foreground' case 'stopped': return 'bg-muted-foreground'
default: default:
@@ -2761,7 +2761,7 @@ watch(isWindows, () => {
</SidebarContent> </SidebarContent>
</Sidebar> </Sidebar>
<SidebarInset class="min-w-0 overflow-y-auto"> <SidebarInset class="min-w-0">
<!-- Mobile Header --> <!-- Mobile Header -->
<div class="md:hidden sticky top-0 z-20 flex items-center px-3 sm:px-4 py-2 sm:py-3 border-b bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/70"> <div class="md:hidden sticky top-0 z-20 flex items-center px-3 sm:px-4 py-2 sm:py-3 border-b bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/70">
<SidebarTrigger class="mr-1.5 size-8 sm:mr-2 sm:size-9" /> <SidebarTrigger class="mr-1.5 size-8 sm:mr-2 sm:size-9" />
@@ -4362,7 +4362,7 @@ watch(isWindows, () => {
<!-- Status and controls --> <!-- Status and controls -->
<div class="flex items-center justify-between"> <div class="flex items-center justify-between">
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<div :class="['w-2 h-2 rounded-full', getExtStatusClass(extensions?.ttyd?.status)]" /> <div :class="['size-2 rounded-full', getExtStatusClass(extensions?.ttyd?.status)]" />
<span class="text-sm">{{ getExtStatusText(extensions?.ttyd?.status) }}</span> <span class="text-sm">{{ getExtStatusText(extensions?.ttyd?.status) }}</span>
</div> </div>
<div class="flex gap-2"> <div class="flex gap-2">
@@ -4453,7 +4453,7 @@ watch(isWindows, () => {
<!-- Status and controls --> <!-- Status and controls -->
<div class="flex items-center justify-between"> <div class="flex items-center justify-between">
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<div :class="['w-2 h-2 rounded-full', getExtStatusClass(extensions?.gostc?.status)]" /> <div :class="['size-2 rounded-full', getExtStatusClass(extensions?.gostc?.status)]" />
<span class="text-sm">{{ getExtStatusText(extensions?.gostc?.status) }}</span> <span class="text-sm">{{ getExtStatusText(extensions?.gostc?.status) }}</span>
</div> </div>
<div class="flex gap-2"> <div class="flex gap-2">
@@ -4548,7 +4548,7 @@ watch(isWindows, () => {
<!-- Status and controls --> <!-- Status and controls -->
<div class="flex items-center justify-between"> <div class="flex items-center justify-between">
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<div :class="['w-2 h-2 rounded-full', getExtStatusClass(extensions?.easytier?.status)]" /> <div :class="['size-2 rounded-full', getExtStatusClass(extensions?.easytier?.status)]" />
<span class="text-sm">{{ getExtStatusText(extensions?.easytier?.status) }}</span> <span class="text-sm">{{ getExtStatusText(extensions?.easytier?.status) }}</span>
</div> </div>
<div class="flex gap-2"> <div class="flex gap-2">
@@ -4654,7 +4654,7 @@ watch(isWindows, () => {
<template v-else> <template v-else>
<div class="flex items-center justify-between"> <div class="flex items-center justify-between">
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<div :class="['w-2 h-2 rounded-full', getExtStatusClass(extensions?.frpc?.status)]" /> <div :class="['size-2 rounded-full', getExtStatusClass(extensions?.frpc?.status)]" />
<span class="text-sm">{{ getExtStatusText(extensions?.frpc?.status) }}</span> <span class="text-sm">{{ getExtStatusText(extensions?.frpc?.status) }}</span>
</div> </div>
<div class="flex gap-2"> <div class="flex gap-2">
@@ -4828,7 +4828,7 @@ watch(isWindows, () => {
<CardContent class="space-y-4"> <CardContent class="space-y-4">
<div class="flex items-center justify-between"> <div class="flex items-center justify-between">
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<div :class="['w-2 h-2 rounded-full', getRtspStatusClass(rtspStatus?.service_status)]" /> <div :class="['size-2 rounded-full', getRtspStatusClass(rtspStatus?.service_status)]" />
<span class="text-sm">{{ getRtspServiceStatusText(rtspStatus?.service_status) }}</span> <span class="text-sm">{{ getRtspServiceStatusText(rtspStatus?.service_status) }}</span>
</div> </div>
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
@@ -4954,7 +4954,7 @@ watch(isWindows, () => {
<CardContent class="space-y-4"> <CardContent class="space-y-4">
<div class="flex items-center justify-between"> <div class="flex items-center justify-between">
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<div :class="['w-2 h-2 rounded-full', getVncStatusClass(vncStatus?.service_status)]" /> <div :class="['size-2 rounded-full', getVncStatusClass(vncStatus?.service_status)]" />
<span class="text-sm">{{ getVncServiceStatusText(vncStatus?.service_status) }}</span> <span class="text-sm">{{ getVncServiceStatusText(vncStatus?.service_status) }}</span>
<template v-if="vncStatus?.connection_count"> <template v-if="vncStatus?.connection_count">
<span class="text-muted-foreground">|</span> <span class="text-muted-foreground">|</span>
@@ -5077,11 +5077,11 @@ watch(isWindows, () => {
<!-- Status and controls --> <!-- Status and controls -->
<div class="flex items-center justify-between"> <div class="flex items-center justify-between">
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<div :class="['w-2 h-2 rounded-full', getRustdeskStatusClass(rustdeskStatus?.service_status)]" /> <div :class="['size-2 rounded-full', getRustdeskStatusClass(rustdeskStatus?.service_status)]" />
<span class="text-sm">{{ getRustdeskServiceStatusText(rustdeskStatus?.service_status) }}</span> <span class="text-sm">{{ getRustdeskServiceStatusText(rustdeskStatus?.service_status) }}</span>
<template v-if="rustdeskStatus?.rendezvous_status"> <template v-if="rustdeskStatus?.rendezvous_status">
<span class="text-muted-foreground">|</span> <span class="text-muted-foreground">|</span>
<div :class="['w-2 h-2 rounded-full', getRustdeskStatusClass(rustdeskStatus?.rendezvous_status)]" /> <div :class="['size-2 rounded-full', getRustdeskStatusClass(rustdeskStatus?.rendezvous_status)]" />
<span class="text-sm text-muted-foreground">{{ getRustdeskRendezvousStatusText(rustdeskStatus?.rendezvous_status) }}</span> <span class="text-sm text-muted-foreground">{{ getRustdeskRendezvousStatusText(rustdeskStatus?.rendezvous_status) }}</span>
</template> </template>
</div> </div>