mirror of
https://github.com/mofeng-git/One-KVM.git
synced 2026-07-29 06:31:45 +08:00
feat: 优化网页终端样式
This commit is contained in:
108
web/src/components/TerminalDialog.vue
Normal file
108
web/src/components/TerminalDialog.vue
Normal 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>
|
||||
@@ -33,6 +33,7 @@ import InfoBar from '@/components/InfoBar.vue'
|
||||
import VirtualKeyboard from '@/components/VirtualKeyboard.vue'
|
||||
import StatsSheet from '@/components/StatsSheet.vue'
|
||||
import ComputerUseSheet from '@/components/ComputerUseSheet.vue'
|
||||
import TerminalDialog from '@/components/TerminalDialog.vue'
|
||||
import type { ComputerUseTimelineItem, NewComputerUseTimelineItem } from '@/types/computerUseTimeline'
|
||||
import LanguageToggleButton from '@/components/LanguageToggleButton.vue'
|
||||
import BrandMark from '@/components/BrandMark.vue'
|
||||
@@ -61,8 +62,6 @@ import {
|
||||
Sun,
|
||||
Moon,
|
||||
ChevronDown,
|
||||
Terminal,
|
||||
ExternalLink,
|
||||
KeyRound,
|
||||
Loader2,
|
||||
} from 'lucide-vue-next'
|
||||
@@ -2023,12 +2022,6 @@ function openTerminal() {
|
||||
showTerminalDialog.value = true
|
||||
}
|
||||
|
||||
function openTerminalInNewTab() {
|
||||
if (!showTerminal.value) return
|
||||
if (!ttydStatus.value?.running) return
|
||||
window.open('/api/terminal/', '_blank')
|
||||
}
|
||||
|
||||
async function handlePowerShort() {
|
||||
try {
|
||||
await atxApi.power('short')
|
||||
@@ -3103,37 +3096,7 @@ onUnmounted(() => {
|
||||
v-model:open="statsSheetOpen"
|
||||
:webrtc-stats="webrtc.stats.value"
|
||||
/>
|
||||
<Dialog 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>
|
||||
<TerminalDialog v-if="showTerminal" v-model:open="showTerminalDialog" />
|
||||
<Dialog v-model:open="changePasswordDialogOpen">
|
||||
<DialogContent class="w-[95vw] max-w-md">
|
||||
<DialogHeader>
|
||||
|
||||
@@ -56,6 +56,7 @@ import { getVideoFormatState } from '@/lib/video-format-support'
|
||||
import { formatVideoDeviceLabel } from '@/lib/video-device-label'
|
||||
import AppLayout from '@/components/AppLayout.vue'
|
||||
import LanguageToggleButton from '@/components/LanguageToggleButton.vue'
|
||||
import TerminalDialog from '@/components/TerminalDialog.vue'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@/components/ui/card'
|
||||
import { Input } from '@/components/ui/input'
|
||||
@@ -1696,10 +1697,6 @@ function openTerminal() {
|
||||
showTerminalDialog.value = true
|
||||
}
|
||||
|
||||
function openTerminalInNewTab() {
|
||||
window.open('/api/terminal/', '_blank')
|
||||
}
|
||||
|
||||
function getExtStatusText(status: ExtensionStatus | undefined): string {
|
||||
if (!status) return t('extensions.stopped')
|
||||
switch (status.state) {
|
||||
@@ -5354,37 +5351,7 @@ watch(isWindows, () => {
|
||||
</div>
|
||||
|
||||
<!-- Terminal Dialog -->
|
||||
<Dialog 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>
|
||||
<TerminalDialog v-model:open="showTerminalDialog" />
|
||||
|
||||
<!-- Restart Confirmation Dialog -->
|
||||
<Dialog v-model:open="showRestartDialog">
|
||||
|
||||
Reference in New Issue
Block a user