mirror of
https://github.com/mofeng-git/One-KVM.git
synced 2026-09-12 18:44:25 +08:00
update: 更新前端依赖;修改前端样式
This commit is contained in:
@@ -25,6 +25,9 @@ import {
|
||||
HoverCardTrigger,
|
||||
} from '@/components/ui/hover-card'
|
||||
import { Switch } from '@/components/ui/switch'
|
||||
import { Stepper, StepperItem, StepperSeparator, StepperTitle, StepperTrigger } from '@/components/ui/stepper'
|
||||
import { Alert, AlertDescription } from '@/components/ui/alert'
|
||||
import { NativeSelect, NativeSelectOption } from '@/components/ui/native-select'
|
||||
import {
|
||||
Eye,
|
||||
EyeOff,
|
||||
@@ -37,6 +40,7 @@ import {
|
||||
HelpCircle,
|
||||
Puzzle,
|
||||
RefreshCw,
|
||||
AlertTriangle,
|
||||
} from 'lucide-vue-next'
|
||||
|
||||
const { t } = useI18n()
|
||||
@@ -157,7 +161,7 @@ const passwordStrengthText = computed(() => {
|
||||
})
|
||||
|
||||
const passwordStrengthColor = computed(() => {
|
||||
const colors = ['bg-muted', 'bg-red-500', 'bg-orange-500', 'bg-yellow-500', 'bg-green-500']
|
||||
const colors = ['bg-muted', 'bg-destructive', 'bg-warning', 'bg-warning', 'bg-success']
|
||||
return colors[passwordStrength.value] || colors[0]
|
||||
})
|
||||
|
||||
@@ -552,37 +556,36 @@ const stepIcons = [User, Video, Keyboard, Puzzle]
|
||||
{{ t('setup.progress', { current: step, total: totalSteps }) }}
|
||||
</p>
|
||||
|
||||
<!-- Step Indicator with Labels -->
|
||||
<div class="flex items-center justify-center gap-1.5 sm:gap-2 mb-5 sm:mb-6">
|
||||
<template v-for="i in totalSteps" :key="i">
|
||||
<div class="flex flex-col items-center gap-1">
|
||||
<div
|
||||
class="flex items-center justify-center w-9 h-9 sm:w-10 sm:h-10 rounded-full border-2 transition-all duration-300"
|
||||
:class="
|
||||
step > i
|
||||
? 'bg-primary border-primary text-primary-foreground scale-100'
|
||||
: step === i
|
||||
? 'border-primary text-primary scale-110'
|
||||
: 'border-muted text-muted-foreground scale-100'
|
||||
"
|
||||
>
|
||||
<Check v-if="step > i" class="w-4 h-4 sm:w-5 sm:h-5" />
|
||||
<component :is="stepIcons[i - 1]" v-else class="w-4 h-4 sm:w-5 sm:h-5" />
|
||||
</div>
|
||||
<span
|
||||
class="text-[10px] sm:text-xs transition-colors duration-300 max-w-14 sm:max-w-16 text-center leading-tight"
|
||||
:class="step >= i ? 'text-foreground font-medium' : 'text-muted-foreground'"
|
||||
>
|
||||
{{ stepLabels[i - 1] }}
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
<Stepper :model-value="step" class="mb-5 flex w-full items-start gap-1 sm:mb-6 sm:gap-2">
|
||||
<StepperItem
|
||||
v-for="i in totalSteps"
|
||||
:key="i"
|
||||
v-slot="{ state }"
|
||||
:step="i"
|
||||
class="relative flex w-full flex-col items-center justify-center"
|
||||
>
|
||||
<StepperSeparator
|
||||
v-if="i < totalSteps"
|
||||
class="w-5 sm:w-8 h-0.5 transition-colors duration-300 mb-5 sm:mb-6"
|
||||
:class="step > i ? 'bg-primary' : 'bg-muted'"
|
||||
class="absolute left-[calc(50%+18px)] right-[calc(-50%+8px)] top-5 h-0.5 rounded-full group-data-[state=completed]:bg-primary"
|
||||
/>
|
||||
</template>
|
||||
</div>
|
||||
<StepperTrigger as-child>
|
||||
<Button
|
||||
type="button"
|
||||
size="icon"
|
||||
:variant="state === 'completed' || state === 'active' ? 'default' : 'outline'"
|
||||
class="z-10 size-9 shrink-0 rounded-full disabled:opacity-100 sm:size-10"
|
||||
:class="state === 'active' && 'ring-2 ring-ring ring-offset-2 ring-offset-background'"
|
||||
disabled
|
||||
>
|
||||
<Check v-if="state === 'completed'" class="size-4 sm:size-5" />
|
||||
<component :is="stepIcons[i - 1]" v-else class="size-4 sm:size-5" />
|
||||
</Button>
|
||||
</StepperTrigger>
|
||||
<StepperTitle class="mt-1 max-w-14 whitespace-normal text-center text-[10px] leading-tight sm:max-w-16 sm:text-xs" :class="state === 'active' ? 'text-foreground' : 'text-muted-foreground'">
|
||||
{{ stepLabels[i - 1] }}
|
||||
</StepperTitle>
|
||||
</StepperItem>
|
||||
</Stepper>
|
||||
|
||||
<!-- Step Content with Animation -->
|
||||
<Transition :name="slideDirection === 'forward' ? 'slide-forward' : 'slide-backward'" mode="out-in">
|
||||
@@ -629,15 +632,17 @@ const stepIcons = [User, Video, Keyboard, Puzzle]
|
||||
@blur="validatePassword"
|
||||
@input="passwordTouched && validatePassword()"
|
||||
/>
|
||||
<button
|
||||
<Button
|
||||
type="button"
|
||||
class="absolute right-3 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground transition-colors"
|
||||
variant="ghost"
|
||||
size="icon-sm"
|
||||
class="absolute right-1 top-1/2 -translate-y-1/2 text-muted-foreground"
|
||||
:aria-label="showPassword ? t('extensions.rustdesk.hidePassword') : t('extensions.rustdesk.showPassword')"
|
||||
@click="showPassword = !showPassword"
|
||||
>
|
||||
<Eye v-if="!showPassword" class="w-4 h-4" />
|
||||
<EyeOff v-else class="w-4 h-4" />
|
||||
</button>
|
||||
</Button>
|
||||
</div>
|
||||
<p v-if="passwordError" class="text-xs text-destructive">{{ passwordError }}</p>
|
||||
|
||||
@@ -684,92 +689,79 @@ const stepIcons = [User, Video, Keyboard, Puzzle]
|
||||
<Label for="videoDevice">{{ t('setup.videoDevice') }}</Label>
|
||||
<HoverCard>
|
||||
<HoverCardTrigger as-child>
|
||||
<button type="button" class="text-muted-foreground hover:text-foreground transition-colors" :aria-label="t('common.info')">
|
||||
<Button type="button" variant="ghost" size="icon-xs" class="text-muted-foreground" :aria-label="t('common.info')">
|
||||
<HelpCircle class="w-4 h-4" />
|
||||
</button>
|
||||
</Button>
|
||||
</HoverCardTrigger>
|
||||
<HoverCardContent class="w-64 text-sm">
|
||||
{{ t('setup.videoDeviceHelp') }}
|
||||
</HoverCardContent>
|
||||
</HoverCard>
|
||||
</div>
|
||||
<Select v-model="videoDevice">
|
||||
<SelectTrigger>
|
||||
<SelectValue :placeholder="t('setup.selectVideoDevice')" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem v-for="dev in devices.video" :key="dev.path" :value="dev.path">
|
||||
<NativeSelect v-model="videoDevice" class="w-full">
|
||||
<NativeSelectOption value="">{{ t('setup.selectVideoDevice') }}</NativeSelectOption>
|
||||
<NativeSelectOption v-for="dev in devices.video" :key="dev.path" :value="dev.path">
|
||||
{{ formatVideoDeviceLabel(dev) }}
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</NativeSelectOption>
|
||||
</NativeSelect>
|
||||
</div>
|
||||
|
||||
<div v-if="videoDevice && !selectedDeviceHasSignal" class="flex items-center gap-3 p-3 rounded-lg border border-orange-500/30 bg-orange-500/5 text-sm text-orange-600 dark:text-orange-400">
|
||||
<Alert v-if="videoDevice && !selectedDeviceHasSignal" variant="warning">
|
||||
<AlertTriangle />
|
||||
<AlertDescription class="flex items-center gap-3">
|
||||
<p class="flex-1">{{ t('setup.noSignalDetected') }}</p>
|
||||
<Button variant="outline" size="sm" :disabled="refreshingDevices" @click="refreshDeviceList">
|
||||
<RefreshCw class="w-4 h-4 mr-1" :class="{ 'animate-spin': refreshingDevices }" />
|
||||
{{ t('setup.refreshDevices') }}
|
||||
</Button>
|
||||
</div>
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
|
||||
<div v-if="videoDevice" class="space-y-2">
|
||||
<div class="flex items-center gap-2">
|
||||
<Label for="videoFormat">{{ t('setup.videoFormat') }}</Label>
|
||||
<HoverCard>
|
||||
<HoverCardTrigger as-child>
|
||||
<button type="button" class="text-muted-foreground hover:text-foreground transition-colors" :aria-label="t('common.info')">
|
||||
<Button type="button" variant="ghost" size="icon-xs" class="text-muted-foreground" :aria-label="t('common.info')">
|
||||
<HelpCircle class="w-4 h-4" />
|
||||
</button>
|
||||
</Button>
|
||||
</HoverCardTrigger>
|
||||
<HoverCardContent class="w-64 text-sm">
|
||||
{{ t('setup.videoFormatHelp') }}
|
||||
</HoverCardContent>
|
||||
</HoverCard>
|
||||
</div>
|
||||
<Select v-model="videoFormat">
|
||||
<SelectTrigger>
|
||||
<SelectValue :placeholder="t('setup.selectFormat')" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem v-for="fmt in availableFormats" :key="fmt.format" :value="fmt.format">
|
||||
<NativeSelect v-model="videoFormat" class="w-full">
|
||||
<NativeSelectOption value="">{{ t('setup.selectFormat') }}</NativeSelectOption>
|
||||
<NativeSelectOption v-for="fmt in availableFormats" :key="fmt.format" :value="fmt.format">
|
||||
{{ fmt.format }} - {{ fmt.description }}
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</NativeSelectOption>
|
||||
</NativeSelect>
|
||||
</div>
|
||||
|
||||
<div v-if="videoFormat" class="grid grid-cols-2 gap-4">
|
||||
<div class="space-y-2">
|
||||
<Label for="videoResolution">{{ t('setup.resolution') }}</Label>
|
||||
<Select v-model="videoResolution">
|
||||
<SelectTrigger>
|
||||
<SelectValue :placeholder="t('setup.selectResolution')" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem
|
||||
<NativeSelect v-model="videoResolution" class="w-full">
|
||||
<NativeSelectOption value="">{{ t('setup.selectResolution') }}</NativeSelectOption>
|
||||
<NativeSelectOption
|
||||
v-for="res in availableResolutions"
|
||||
:key="`${res.width}x${res.height}`"
|
||||
:value="`${res.width}x${res.height}`"
|
||||
>
|
||||
{{ res.width }}x{{ res.height }}
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</NativeSelectOption>
|
||||
</NativeSelect>
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<Label for="videoFps">{{ t('setup.fps') }}</Label>
|
||||
<Select v-model="videoFps">
|
||||
<SelectTrigger>
|
||||
<SelectValue :placeholder="t('setup.selectFps')" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem v-for="fps in availableFps" :key="fps" :value="fps">
|
||||
<NativeSelect :model-value="videoFps" class="w-full" @update:model-value="value => videoFps = value === '' ? null : Number(value)">
|
||||
<NativeSelectOption value="">{{ t('setup.selectFps') }}</NativeSelectOption>
|
||||
<NativeSelectOption v-for="fps in availableFps" :key="fps" :value="fps">
|
||||
{{ formatFpsLabel(fps) }}
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</NativeSelectOption>
|
||||
</NativeSelect>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -783,27 +775,20 @@ const stepIcons = [User, Video, Keyboard, Puzzle]
|
||||
<Label for="audioDevice">{{ t('setup.audioDevice') }}</Label>
|
||||
<HoverCard>
|
||||
<HoverCardTrigger as-child>
|
||||
<button type="button" class="text-muted-foreground hover:text-foreground transition-colors" :aria-label="t('common.info')">
|
||||
<Button type="button" variant="ghost" size="icon-xs" class="text-muted-foreground" :aria-label="t('common.info')">
|
||||
<HelpCircle class="w-4 h-4" />
|
||||
</button>
|
||||
</Button>
|
||||
</HoverCardTrigger>
|
||||
<HoverCardContent class="w-64 text-sm">
|
||||
{{ t('setup.audioDeviceHelp') }}
|
||||
</HoverCardContent>
|
||||
</HoverCard>
|
||||
</div>
|
||||
<Select v-model="audioDevice" :disabled="!audioEnabled">
|
||||
<SelectTrigger>
|
||||
<SelectValue :placeholder="t('setup.selectAudioDevice')" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="__none__">{{ t('setup.noAudio') }}</SelectItem>
|
||||
<SelectItem v-for="dev in devices.audio" :key="dev.name" :value="dev.name">
|
||||
{{ dev.description }}
|
||||
<span v-if="dev.is_hdmi" class="text-xs text-muted-foreground ml-1">(HDMI)</span>
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<NativeSelect v-model="audioDevice" class="w-full" :disabled="!audioEnabled">
|
||||
<NativeSelectOption value="">{{ t('setup.selectAudioDevice') }}</NativeSelectOption>
|
||||
<NativeSelectOption value="__none__">{{ t('setup.noAudio') }}</NativeSelectOption>
|
||||
<NativeSelectOption v-for="dev in devices.audio" :key="dev.name" :value="dev.name">{{ dev.description }}{{ dev.is_hdmi ? ' (HDMI)' : '' }}</NativeSelectOption>
|
||||
</NativeSelect>
|
||||
<p v-if="!devices.audio.length" class="text-xs text-muted-foreground">
|
||||
{{ t('setup.noAudioDevices') }}
|
||||
</p>
|
||||
@@ -811,9 +796,10 @@ const stepIcons = [User, Video, Keyboard, Puzzle]
|
||||
|
||||
<!-- Advanced: Encoder Backend (Collapsible) -->
|
||||
<div class="mt-4 border rounded-lg">
|
||||
<button
|
||||
<Button
|
||||
type="button"
|
||||
class="w-full flex items-center justify-between p-3 text-left hover:bg-muted/50 rounded-lg transition-colors"
|
||||
variant="ghost"
|
||||
class="h-auto w-full justify-between rounded-lg p-3 text-left"
|
||||
:aria-label="t('setup.advancedEncoder')"
|
||||
@click="showAdvancedEncoder = !showAdvancedEncoder"
|
||||
>
|
||||
@@ -824,7 +810,7 @@ const stepIcons = [User, Video, Keyboard, Puzzle]
|
||||
class="h-4 w-4 transition-transform duration-200"
|
||||
:class="{ 'rotate-90': showAdvancedEncoder }"
|
||||
/>
|
||||
</button>
|
||||
</Button>
|
||||
<div v-if="showAdvancedEncoder" class="px-3 pb-3 space-y-3">
|
||||
<p class="text-xs text-muted-foreground">
|
||||
{{ t('setup.encoderHint') }}
|
||||
@@ -873,16 +859,12 @@ const stepIcons = [User, Video, Keyboard, Puzzle]
|
||||
|
||||
<div class="space-y-2">
|
||||
<Label for="ch9329Port">{{ t('setup.serialPort') }}</Label>
|
||||
<Select v-model="ch9329Port">
|
||||
<SelectTrigger>
|
||||
<SelectValue :placeholder="t('setup.selectSerialPort')" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem v-for="port in devices.serial" :key="port.path" :value="port.path">
|
||||
<NativeSelect v-model="ch9329Port" class="w-full">
|
||||
<NativeSelectOption value="">{{ t('setup.selectSerialPort') }}</NativeSelectOption>
|
||||
<NativeSelectOption v-for="port in devices.serial" :key="port.path" :value="port.path">
|
||||
{{ port.name }} ({{ port.path }})
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</NativeSelectOption>
|
||||
</NativeSelect>
|
||||
<p v-if="!devices.serial.length" class="text-xs text-muted-foreground">
|
||||
{{ t('setup.noSerialDevices') }}
|
||||
</p>
|
||||
@@ -890,7 +872,7 @@ const stepIcons = [User, Video, Keyboard, Puzzle]
|
||||
|
||||
<div class="space-y-2">
|
||||
<Label for="ch9329Baudrate">{{ t('setup.baudRate') }}</Label>
|
||||
<Select v-model="ch9329Baudrate">
|
||||
<Select :model-value="ch9329Baudrate" @update:model-value="value => ch9329Baudrate = Number(value)">
|
||||
<SelectTrigger>
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
@@ -912,16 +894,12 @@ const stepIcons = [User, Video, Keyboard, Puzzle]
|
||||
|
||||
<div class="space-y-2">
|
||||
<Label for="otgUdc">{{ t('setup.udc') }}</Label>
|
||||
<Select v-model="otgUdc">
|
||||
<SelectTrigger>
|
||||
<SelectValue :placeholder="t('setup.selectUdc')" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem v-for="udc in devices.udc" :key="udc.name" :value="udc.name">
|
||||
<NativeSelect v-model="otgUdc" class="w-full">
|
||||
<NativeSelectOption value="">{{ t('setup.selectUdc') }}</NativeSelectOption>
|
||||
<NativeSelectOption v-for="udc in devices.udc" :key="udc.name" :value="udc.name">
|
||||
{{ udc.name }}
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</NativeSelectOption>
|
||||
</NativeSelect>
|
||||
<p v-if="!devices.udc.length" class="text-xs text-muted-foreground">
|
||||
{{ t('setup.noUdcDevices') }}
|
||||
</p>
|
||||
@@ -960,7 +938,10 @@ const stepIcons = [User, Video, Keyboard, Puzzle]
|
||||
|
||||
<!-- Error Message -->
|
||||
<Transition name="fade">
|
||||
<p v-if="error" class="text-sm text-destructive text-center">{{ error }}</p>
|
||||
<Alert v-if="error" variant="destructive">
|
||||
<AlertTriangle />
|
||||
<AlertDescription>{{ error }}</AlertDescription>
|
||||
</Alert>
|
||||
</Transition>
|
||||
|
||||
<!-- Navigation Buttons -->
|
||||
|
||||
Reference in New Issue
Block a user