fix: 完善 MSD 错误提示;解决 MSD 文件名称过长问题;优化粘贴文本样式;优化所有下拉框样式;删除“画面静止时会自动降帧”提示

This commit is contained in:
mofeng-git
2026-07-26 23:13:47 +08:00
parent 376dc97134
commit 4f770a1eea
8 changed files with 284 additions and 177 deletions

View File

@@ -71,7 +71,6 @@ import { Label } from '@/components/ui/label'
import { Switch } from '@/components/ui/switch'
import { Separator } from '@/components/ui/separator'
import { Badge } from '@/components/ui/badge'
import { NativeSelect, NativeSelectOption } from '@/components/ui/native-select'
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table'
import { ButtonGroup } from '@/components/ui/button-group'
@@ -151,6 +150,7 @@ const configStore = useConfigStore()
const authStore = useAuthStore()
const featureVisibility = useFeatureVisibility()
const { theme, setTheme } = useTheme()
const EMPTY_SELECT_VALUE = '__one-kvm-empty-select-value__'
const isWindows = computed(() => systemStore.platform?.mode === 'windows')
@@ -2927,38 +2927,57 @@ watch(isWindows, () => {
<CardContent class="space-y-4">
<div class="space-y-2">
<Label for="video-device">{{ t('settings.videoDevice') }}</Label>
<NativeSelect id="video-device" v-model="config.video_device" class="w-full">
<NativeSelectOption value="">{{ t('settings.selectDevice') }}</NativeSelectOption>
<NativeSelectOption v-for="dev in devices.video" :key="dev.path" :value="dev.path">{{ formatVideoDeviceLabel(dev) }}</NativeSelectOption>
</NativeSelect>
<Select
:model-value="config.video_device"
@update:model-value="value => config.video_device = value === EMPTY_SELECT_VALUE ? '' : String(value)"
>
<SelectTrigger id="video-device" class="w-full"><SelectValue :placeholder="t('settings.selectDevice')" /></SelectTrigger>
<SelectContent>
<SelectItem :value="EMPTY_SELECT_VALUE">{{ t('settings.selectDevice') }}</SelectItem>
<SelectItem v-for="dev in devices.video" :key="dev.path" :value="dev.path">{{ formatVideoDeviceLabel(dev) }}</SelectItem>
</SelectContent>
</Select>
</div>
<div class="space-y-2">
<Label for="video-format">{{ t('settings.videoFormat') }}</Label>
<NativeSelect id="video-format" v-model="config.video_format" class="w-full" :disabled="!config.video_device">
<NativeSelectOption value="">{{ t('settings.selectFormat') }}</NativeSelectOption>
<NativeSelectOption
v-for="fmt in availableFormatOptions"
:key="fmt.format"
:value="fmt.format"
:disabled="fmt.disabled"
>
{{ fmt.format }} - {{ fmt.description }}{{ fmt.disabled ? t('common.notSupportedYet') : '' }}
</NativeSelectOption>
</NativeSelect>
<Select
:model-value="config.video_format"
:disabled="!config.video_device"
@update:model-value="value => config.video_format = value === EMPTY_SELECT_VALUE ? '' : String(value)"
>
<SelectTrigger id="video-format" class="w-full"><SelectValue :placeholder="t('settings.selectFormat')" /></SelectTrigger>
<SelectContent>
<SelectItem :value="EMPTY_SELECT_VALUE">{{ t('settings.selectFormat') }}</SelectItem>
<SelectItem
v-for="fmt in availableFormatOptions"
:key="fmt.format"
:value="fmt.format"
:disabled="fmt.disabled"
>
{{ fmt.format }} - {{ fmt.description }}{{ fmt.disabled ? t('common.notSupportedYet') : '' }}
</SelectItem>
</SelectContent>
</Select>
</div>
<div class="grid gap-4 sm:grid-cols-2">
<div class="space-y-2">
<Label for="video-resolution">{{ t('settings.resolution') }}</Label>
<NativeSelect id="video-resolution" :model-value="`${config.video_width}x${config.video_height}`" class="w-full" :disabled="!config.video_format" @update:model-value="value => { const parts = String(value).split('x').map(Number); if (parts[0] && parts[1]) { config.video_width = parts[0]; config.video_height = parts[1]; } }">
<NativeSelectOption v-for="res in availableResolutions" :key="`${res.width}x${res.height}`" :value="`${res.width}x${res.height}`">{{ res.width }}x{{ res.height }}</NativeSelectOption>
</NativeSelect>
<Select :model-value="`${config.video_width}x${config.video_height}`" :disabled="!config.video_format" @update:model-value="value => { const parts = String(value).split('x').map(Number); if (parts[0] && parts[1]) { config.video_width = parts[0]; config.video_height = parts[1]; } }">
<SelectTrigger id="video-resolution" class="w-full"><SelectValue /></SelectTrigger>
<SelectContent>
<SelectItem 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>
</div>
<div class="space-y-2">
<Label for="video-fps">{{ t('settings.frameRate') }}</Label>
<NativeSelect id="video-fps" :model-value="config.video_fps" class="w-full" :disabled="!config.video_format" @update:model-value="value => config.video_fps = Number(value)">
<NativeSelectOption v-for="fps in availableFps" :key="fps" :value="fps">{{ formatFpsLabel(fps) }}</NativeSelectOption>
<NativeSelectOption v-if="!availableFps.includes(config.video_fps)" :value="config.video_fps">{{ formatFpsLabel(config.video_fps) }}</NativeSelectOption>
</NativeSelect>
<Select :model-value="config.video_fps" :disabled="!config.video_format" @update:model-value="value => config.video_fps = Number(value)">
<SelectTrigger id="video-fps" class="w-full"><SelectValue /></SelectTrigger>
<SelectContent>
<SelectItem v-for="fps in availableFps" :key="fps" :value="fps">{{ formatFpsLabel(fps) }}</SelectItem>
<SelectItem v-if="!availableFps.includes(config.video_fps)" :value="config.video_fps">{{ formatFpsLabel(config.video_fps) }}</SelectItem>
</SelectContent>
</Select>
</div>
</div>
</CardContent>
@@ -3077,10 +3096,16 @@ watch(isWindows, () => {
</div>
<div v-if="config.hid_backend === 'ch9329'" class="space-y-2">
<Label for="serial-device">{{ t('settings.serialDevice') }}</Label>
<NativeSelect id="serial-device" v-model="config.hid_serial_device" class="w-full">
<NativeSelectOption value="">{{ t('settings.selectDevice') }}</NativeSelectOption>
<NativeSelectOption v-for="dev in devices.serial" :key="dev.path" :value="dev.path">{{ dev.name }} ({{ dev.path }})</NativeSelectOption>
</NativeSelect>
<Select
:model-value="config.hid_serial_device"
@update:model-value="value => config.hid_serial_device = value === EMPTY_SELECT_VALUE ? '' : String(value)"
>
<SelectTrigger id="serial-device" class="w-full"><SelectValue :placeholder="t('settings.selectDevice')" /></SelectTrigger>
<SelectContent>
<SelectItem :value="EMPTY_SELECT_VALUE">{{ t('settings.selectDevice') }}</SelectItem>
<SelectItem v-for="dev in devices.serial" :key="dev.path" :value="dev.path">{{ dev.name }} ({{ dev.path }})</SelectItem>
</SelectContent>
</Select>
</div>
<div v-if="config.hid_backend === 'ch9329'" class="space-y-2">
<Label for="serial-baudrate">{{ t('settings.baudRate') }}</Label>
@@ -3093,10 +3118,16 @@ watch(isWindows, () => {
</div>
<div v-if="config.hid_backend === 'otg'" class="space-y-2">
<Label for="otg-udc">{{ t('settings.otgUdc') }}</Label>
<NativeSelect id="otg-udc" v-model="config.hid_otg_udc" class="w-full">
<NativeSelectOption value="">{{ t('settings.autoRecommended') }}</NativeSelectOption>
<NativeSelectOption v-for="udc in devices.udc" :key="udc.name" :value="udc.name">{{ udc.name }}</NativeSelectOption>
</NativeSelect>
<Select
:model-value="config.hid_otg_udc"
@update:model-value="value => config.hid_otg_udc = value === EMPTY_SELECT_VALUE ? '' : String(value)"
>
<SelectTrigger id="otg-udc" class="w-full"><SelectValue :placeholder="t('settings.autoRecommended')" /></SelectTrigger>
<SelectContent>
<SelectItem :value="EMPTY_SELECT_VALUE">{{ t('settings.autoRecommended') }}</SelectItem>
<SelectItem v-for="udc in devices.udc" :key="udc.name" :value="udc.name">{{ udc.name }}</SelectItem>
</SelectContent>
</Select>
</div>
<template v-if="config.hid_backend === 'ch9329'">
@@ -3343,30 +3374,37 @@ watch(isWindows, () => {
</div>
<div class="space-y-2">
<Label for="otg-network-interface">{{ t('settings.otgNetworkInterface') }}</Label>
<NativeSelect
id="otg-network-interface"
v-model="config.otg_network_interface"
class="w-full"
<Select
:model-value="config.otg_network_interface"
:disabled="otgNetworkInterfaces.length === 0"
:aria-invalid="Boolean(otgNetworkInterfacesError)"
@update:model-value="value => config.otg_network_interface = value === EMPTY_SELECT_VALUE ? '' : String(value)"
>
<NativeSelectOption
v-if="otgNetworkInterfacesError"
:value="config.otg_network_interface"
<SelectTrigger
id="otg-network-interface"
class="w-full"
:aria-invalid="Boolean(otgNetworkInterfacesError)"
>
{{ otgNetworkInterfacesError }}
</NativeSelectOption>
<NativeSelectOption v-else-if="otgNetworkInterfaces.length === 0" value="">
{{ t('settings.otgNetworkNone') }}
</NativeSelectOption>
<NativeSelectOption
v-for="item in otgNetworkInterfaces"
:key="item.name"
:value="item.name"
>
{{ item.name }} · {{ item.interface_type }}
</NativeSelectOption>
</NativeSelect>
<SelectValue :placeholder="otgNetworkInterfacesError || t('settings.otgNetworkNone')" />
</SelectTrigger>
<SelectContent>
<SelectItem
v-if="otgNetworkInterfacesError"
:value="config.otg_network_interface || EMPTY_SELECT_VALUE"
>
{{ otgNetworkInterfacesError }}
</SelectItem>
<SelectItem v-else-if="otgNetworkInterfaces.length === 0" :value="EMPTY_SELECT_VALUE">
{{ t('settings.otgNetworkNone') }}
</SelectItem>
<SelectItem
v-for="item in otgNetworkInterfaces"
:key="item.name"
:value="item.name"
>
{{ item.name }} · {{ item.interface_type }}
</SelectItem>
</SelectContent>
</Select>
</div>
</div>
</template>
@@ -4058,17 +4096,23 @@ watch(isWindows, () => {
<div v-if="['usbrelay', 'serial'].includes(atxConfig.driver)" class="grid gap-4 sm:grid-cols-2">
<div v-if="['usbrelay', 'serial'].includes(atxConfig.driver)" class="space-y-2">
<Label for="atx-device">{{ t('settings.atxDevice') }}</Label>
<NativeSelect id="atx-device" v-model="atxConfig.device" class="w-full">
<NativeSelectOption value="">{{ t('settings.selectDevice') }}</NativeSelectOption>
<NativeSelectOption
v-for="dev in getAtxDevicesForDriver(atxConfig.driver)"
:key="dev"
:value="dev"
:disabled="atxConfig.driver === 'serial' && isAtxSerialDeviceReserved(dev)"
>
{{ formatAtxDeviceLabel(atxConfig.driver, dev) }}
</NativeSelectOption>
</NativeSelect>
<Select
:model-value="atxConfig.device"
@update:model-value="value => atxConfig.device = value === EMPTY_SELECT_VALUE ? '' : String(value)"
>
<SelectTrigger id="atx-device" class="w-full"><SelectValue :placeholder="t('settings.selectDevice')" /></SelectTrigger>
<SelectContent>
<SelectItem :value="EMPTY_SELECT_VALUE">{{ t('settings.selectDevice') }}</SelectItem>
<SelectItem
v-for="dev in getAtxDevicesForDriver(atxConfig.driver)"
:key="dev"
:value="dev"
:disabled="atxConfig.driver === 'serial' && isAtxSerialDeviceReserved(dev)"
>
{{ formatAtxDeviceLabel(atxConfig.driver, dev) }}
</SelectItem>
</SelectContent>
</Select>
</div>
<div v-if="atxConfig.driver === 'serial'" class="space-y-2">
<Label for="atx-baudrate">{{ t('settings.baudRate') }}</Label>
@@ -4089,10 +4133,16 @@ watch(isWindows, () => {
<div class="grid gap-3 sm:grid-cols-3">
<div class="space-y-2">
<Label for="power-device">{{ t('settings.atxGpioChip') }}</Label>
<NativeSelect id="power-device" v-model="atxConfig.power.device" class="w-full">
<NativeSelectOption value="">{{ t('settings.atxDriverNone') }}</NativeSelectOption>
<NativeSelectOption v-for="dev in atxDevices.gpio_chips" :key="dev" :value="dev">{{ dev }}</NativeSelectOption>
</NativeSelect>
<Select
:model-value="atxConfig.power.device"
@update:model-value="value => atxConfig.power.device = value === EMPTY_SELECT_VALUE ? '' : String(value)"
>
<SelectTrigger id="power-device" class="w-full"><SelectValue :placeholder="t('settings.atxDriverNone')" /></SelectTrigger>
<SelectContent>
<SelectItem :value="EMPTY_SELECT_VALUE">{{ t('settings.atxDriverNone') }}</SelectItem>
<SelectItem v-for="dev in atxDevices.gpio_chips" :key="dev" :value="dev">{{ dev }}</SelectItem>
</SelectContent>
</Select>
</div>
<div class="space-y-2">
<Label for="power-pin">{{ t('settings.atxPin') }}</Label>
@@ -4113,10 +4163,16 @@ watch(isWindows, () => {
<div class="grid gap-3 sm:grid-cols-3">
<div class="space-y-2">
<Label for="reset-device">{{ t('settings.atxGpioChip') }}</Label>
<NativeSelect id="reset-device" v-model="atxConfig.reset.device" class="w-full">
<NativeSelectOption value="">{{ t('settings.atxDriverNone') }}</NativeSelectOption>
<NativeSelectOption v-for="dev in atxDevices.gpio_chips" :key="dev" :value="dev">{{ dev }}</NativeSelectOption>
</NativeSelect>
<Select
:model-value="atxConfig.reset.device"
@update:model-value="value => atxConfig.reset.device = value === EMPTY_SELECT_VALUE ? '' : String(value)"
>
<SelectTrigger id="reset-device" class="w-full"><SelectValue :placeholder="t('settings.atxDriverNone')" /></SelectTrigger>
<SelectContent>
<SelectItem :value="EMPTY_SELECT_VALUE">{{ t('settings.atxDriverNone') }}</SelectItem>
<SelectItem v-for="dev in atxDevices.gpio_chips" :key="dev" :value="dev">{{ dev }}</SelectItem>
</SelectContent>
</Select>
</div>
<div class="space-y-2">
<Label for="reset-pin">{{ t('settings.atxPin') }}</Label>
@@ -4137,10 +4193,16 @@ watch(isWindows, () => {
<div class="grid gap-3 sm:grid-cols-3">
<div class="space-y-2">
<Label for="led-device">{{ t('settings.atxGpioChip') }}</Label>
<NativeSelect id="led-device" v-model="atxConfig.led.device" class="w-full">
<NativeSelectOption value="">{{ t('settings.atxDriverNone') }}</NativeSelectOption>
<NativeSelectOption v-for="dev in atxDevices.gpio_chips" :key="dev" :value="dev">{{ dev }}</NativeSelectOption>
</NativeSelect>
<Select
:model-value="atxConfig.led.device"
@update:model-value="value => atxConfig.led.device = value === EMPTY_SELECT_VALUE ? '' : String(value)"
>
<SelectTrigger id="led-device" class="w-full"><SelectValue :placeholder="t('settings.atxDriverNone')" /></SelectTrigger>
<SelectContent>
<SelectItem :value="EMPTY_SELECT_VALUE">{{ t('settings.atxDriverNone') }}</SelectItem>
<SelectItem v-for="dev in atxDevices.gpio_chips" :key="dev" :value="dev">{{ dev }}</SelectItem>
</SelectContent>
</Select>
</div>
<div class="space-y-2">
<Label for="led-pin">{{ t('settings.atxPin') }}</Label>
@@ -4161,10 +4223,16 @@ watch(isWindows, () => {
<div class="grid gap-3 sm:grid-cols-3">
<div class="space-y-2">
<Label for="hdd-device">{{ t('settings.atxGpioChip') }}</Label>
<NativeSelect id="hdd-device" v-model="atxConfig.hdd.device" class="w-full">
<NativeSelectOption value="">{{ t('settings.atxDriverNone') }}</NativeSelectOption>
<NativeSelectOption v-for="dev in atxDevices.gpio_chips" :key="dev" :value="dev">{{ dev }}</NativeSelectOption>
</NativeSelect>
<Select
:model-value="atxConfig.hdd.device"
@update:model-value="value => atxConfig.hdd.device = value === EMPTY_SELECT_VALUE ? '' : String(value)"
>
<SelectTrigger id="hdd-device" class="w-full"><SelectValue :placeholder="t('settings.atxDriverNone')" /></SelectTrigger>
<SelectContent>
<SelectItem :value="EMPTY_SELECT_VALUE">{{ t('settings.atxDriverNone') }}</SelectItem>
<SelectItem v-for="dev in atxDevices.gpio_chips" :key="dev" :value="dev">{{ dev }}</SelectItem>
</SelectContent>
</Select>
</div>
<div class="space-y-2">
<Label for="hdd-pin">{{ t('settings.atxPin') }}</Label>