feat: 优化网页终端样式

This commit is contained in:
mofeng-git
2026-07-08 00:04:19 +08:00
parent b719be3e38
commit fc3f1d6aac
3 changed files with 112 additions and 74 deletions

View File

@@ -0,0 +1,108 @@
<script setup lang="ts">
import { useI18n } from 'vue-i18n'
import { ExternalLink, Terminal, X } from 'lucide-vue-next'
import { Button } from '@/components/ui/button'
import {
Dialog,
DialogClose,
DialogContent,
DialogHeader,
DialogTitle,
} from '@/components/ui/dialog'
const props = defineProps<{
open: boolean
}>()
const emit = defineEmits<{
(e: 'update:open', value: boolean): void
}>()
const { t } = useI18n()
function openTerminalInNewTab() {
window.open('/api/terminal/', '_blank')
}
function hideFrameScrollbars(event: Event) {
const frame = event.currentTarget as HTMLIFrameElement | null
try {
const doc = frame?.contentDocument
if (!doc) return
const styleId = 'one-kvm-terminal-scrollbar-style'
let style = doc.getElementById(styleId) as HTMLStyleElement | null
if (!style) {
style = doc.createElement('style')
style.id = styleId
const parent = doc.head || doc.documentElement
parent.appendChild(style)
}
style.textContent = `
html,
body,
.xterm-viewport {
-ms-overflow-style: none !important;
scrollbar-width: none !important;
}
html::-webkit-scrollbar,
body::-webkit-scrollbar,
.xterm-viewport::-webkit-scrollbar {
width: 0 !important;
height: 0 !important;
display: none !important;
}
`
} catch {
// The terminal is served from the same origin; keep the dialog usable if a browser blocks access.
}
}
</script>
<template>
<Dialog :open="props.open" @update:open="emit('update:open', $event)">
<DialogContent
:show-close-button="false"
class="w-[98vw] sm:w-[95vw] max-w-5xl h-[90dvh] sm:h-[85dvh] max-h-[720px] p-0 flex flex-col gap-0 overflow-hidden"
>
<DialogHeader class="px-3 sm:px-4 py-1 border-b shrink-0">
<DialogTitle class="flex h-8 items-center justify-between w-full">
<div class="flex items-center gap-2 min-w-0">
<Terminal class="h-4 w-4 shrink-0" />
<span class="truncate text-sm font-semibold">{{ t('extensions.ttyd.title') }}</span>
</div>
<div class="flex items-center gap-1 shrink-0">
<Button
variant="ghost"
size="icon"
class="h-7 w-7"
@click="openTerminalInNewTab"
:aria-label="t('extensions.ttyd.openInNewTab')"
:title="t('extensions.ttyd.openInNewTab')"
>
<ExternalLink class="h-3.5 w-3.5" />
</Button>
<DialogClose as-child>
<Button variant="ghost" size="icon" class="h-7 w-7">
<X class="h-3.5 w-3.5" />
<span class="sr-only">Close</span>
</Button>
</DialogClose>
</div>
</DialogTitle>
</DialogHeader>
<div class="flex-1 min-h-0">
<iframe
v-if="props.open"
src="/api/terminal/"
class="block w-full h-full border-0"
allow="clipboard-read; clipboard-write"
scrolling="no"
@load="hideFrameScrollbars"
/>
</div>
</DialogContent>
</Dialog>
</template>

View File

@@ -33,6 +33,7 @@ import InfoBar from '@/components/InfoBar.vue'
import VirtualKeyboard from '@/components/VirtualKeyboard.vue' import VirtualKeyboard from '@/components/VirtualKeyboard.vue'
import StatsSheet from '@/components/StatsSheet.vue' import StatsSheet from '@/components/StatsSheet.vue'
import ComputerUseSheet from '@/components/ComputerUseSheet.vue' import ComputerUseSheet from '@/components/ComputerUseSheet.vue'
import TerminalDialog from '@/components/TerminalDialog.vue'
import type { ComputerUseTimelineItem, NewComputerUseTimelineItem } from '@/types/computerUseTimeline' import type { ComputerUseTimelineItem, NewComputerUseTimelineItem } from '@/types/computerUseTimeline'
import LanguageToggleButton from '@/components/LanguageToggleButton.vue' import LanguageToggleButton from '@/components/LanguageToggleButton.vue'
import BrandMark from '@/components/BrandMark.vue' import BrandMark from '@/components/BrandMark.vue'
@@ -61,8 +62,6 @@ import {
Sun, Sun,
Moon, Moon,
ChevronDown, ChevronDown,
Terminal,
ExternalLink,
KeyRound, KeyRound,
Loader2, Loader2,
} from 'lucide-vue-next' } from 'lucide-vue-next'
@@ -2023,12 +2022,6 @@ function openTerminal() {
showTerminalDialog.value = true showTerminalDialog.value = true
} }
function openTerminalInNewTab() {
if (!showTerminal.value) return
if (!ttydStatus.value?.running) return
window.open('/api/terminal/', '_blank')
}
async function handlePowerShort() { async function handlePowerShort() {
try { try {
await atxApi.power('short') await atxApi.power('short')
@@ -3103,37 +3096,7 @@ onUnmounted(() => {
v-model:open="statsSheetOpen" v-model:open="statsSheetOpen"
:webrtc-stats="webrtc.stats.value" :webrtc-stats="webrtc.stats.value"
/> />
<Dialog v-if="showTerminal" v-model:open="showTerminalDialog"> <TerminalDialog v-if="showTerminal" v-model:open="showTerminalDialog" />
<DialogContent class="w-[98vw] sm:w-[95vw] max-w-5xl h-[90dvh] sm:h-[85dvh] max-h-[720px] p-0 flex flex-col overflow-hidden">
<DialogHeader class="px-3 sm:px-4 py-2 sm:py-3 border-b shrink-0">
<DialogTitle class="flex items-center justify-between w-full">
<div class="flex items-center gap-2">
<Terminal class="h-4 w-4 sm:h-5 sm:w-5" />
<span class="text-sm sm:text-base">{{ t('extensions.ttyd.title') }}</span>
</div>
<Button
variant="ghost"
size="icon"
class="h-7 w-7 sm:h-8 sm:w-8 mr-6 sm:mr-8"
@click="openTerminalInNewTab"
:aria-label="t('extensions.ttyd.openInNewTab')"
:title="t('extensions.ttyd.openInNewTab')"
>
<ExternalLink class="h-3.5 w-3.5 sm:h-4 sm:w-4" />
</Button>
</DialogTitle>
</DialogHeader>
<div class="flex-1 min-h-0">
<iframe
v-if="showTerminalDialog"
src="/api/terminal/"
class="w-full h-full border-0"
allow="clipboard-read; clipboard-write"
scrolling="no"
/>
</div>
</DialogContent>
</Dialog>
<Dialog v-model:open="changePasswordDialogOpen"> <Dialog v-model:open="changePasswordDialogOpen">
<DialogContent class="w-[95vw] max-w-md"> <DialogContent class="w-[95vw] max-w-md">
<DialogHeader> <DialogHeader>

View File

@@ -56,6 +56,7 @@ import { getVideoFormatState } from '@/lib/video-format-support'
import { formatVideoDeviceLabel } from '@/lib/video-device-label' import { formatVideoDeviceLabel } from '@/lib/video-device-label'
import AppLayout from '@/components/AppLayout.vue' import AppLayout from '@/components/AppLayout.vue'
import LanguageToggleButton from '@/components/LanguageToggleButton.vue' import LanguageToggleButton from '@/components/LanguageToggleButton.vue'
import TerminalDialog from '@/components/TerminalDialog.vue'
import { Button } from '@/components/ui/button' import { Button } from '@/components/ui/button'
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@/components/ui/card' import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@/components/ui/card'
import { Input } from '@/components/ui/input' import { Input } from '@/components/ui/input'
@@ -1696,10 +1697,6 @@ function openTerminal() {
showTerminalDialog.value = true showTerminalDialog.value = true
} }
function openTerminalInNewTab() {
window.open('/api/terminal/', '_blank')
}
function getExtStatusText(status: ExtensionStatus | undefined): string { function getExtStatusText(status: ExtensionStatus | undefined): string {
if (!status) return t('extensions.stopped') if (!status) return t('extensions.stopped')
switch (status.state) { switch (status.state) {
@@ -5354,37 +5351,7 @@ watch(isWindows, () => {
</div> </div>
<!-- Terminal Dialog --> <!-- Terminal Dialog -->
<Dialog v-model:open="showTerminalDialog"> <TerminalDialog v-model:open="showTerminalDialog" />
<DialogContent class="w-[98vw] sm:w-[95vw] max-w-5xl h-[90dvh] sm:h-[85dvh] max-h-[720px] p-0 flex flex-col overflow-hidden">
<DialogHeader class="px-3 sm:px-4 py-2 sm:py-3 border-b shrink-0">
<DialogTitle class="flex items-center justify-between w-full">
<div class="flex items-center gap-2">
<Terminal class="h-4 w-4 sm:h-5 sm:w-5" />
<span class="text-sm sm:text-base">{{ t('extensions.ttyd.title') }}</span>
</div>
<Button
variant="ghost"
size="icon"
class="h-7 w-7 sm:h-8 sm:w-8 mr-6 sm:mr-8"
@click="openTerminalInNewTab"
:aria-label="t('extensions.ttyd.openInNewTab')"
:title="t('extensions.ttyd.openInNewTab')"
>
<ExternalLink class="h-4 w-4" />
</Button>
</DialogTitle>
</DialogHeader>
<div class="flex-1 min-h-0">
<iframe
v-if="showTerminalDialog"
src="/api/terminal/"
class="w-full h-full border-0"
allow="clipboard-read; clipboard-write"
scrolling="no"
/>
</div>
</DialogContent>
</Dialog>
<!-- Restart Confirmation Dialog --> <!-- Restart Confirmation Dialog -->
<Dialog v-model:open="showRestartDialog"> <Dialog v-model:open="showRestartDialog">