fix: 优化移动端适配布局
This commit is contained in:
@@ -2,6 +2,7 @@
|
|||||||
import { RouterLink, RouterView, useRoute } from 'vue-router';
|
import { RouterLink, RouterView, useRoute } from 'vue-router';
|
||||||
import { ref, onMounted, onUnmounted, watch, nextTick, computed } from 'vue';
|
import { ref, onMounted, onUnmounted, watch, nextTick, computed } from 'vue';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
|
import { useBreakpoints, breakpointsTailwind } from '@vueuse/core'; // +++ Import useBreakpoints +++
|
||||||
import { useAuthStore } from './stores/auth.store';
|
import { useAuthStore } from './stores/auth.store';
|
||||||
import { useSettingsStore } from './stores/settings.store';
|
import { useSettingsStore } from './stores/settings.store';
|
||||||
import { useAppearanceStore } from './stores/appearance.store';
|
import { useAppearanceStore } from './stores/appearance.store';
|
||||||
@@ -33,6 +34,8 @@ const { isStyleCustomizerVisible } = storeToRefs(appearanceStore);
|
|||||||
const { isLayoutVisible, isHeaderVisible } = storeToRefs(layoutStore); // 添加 isHeaderVisible
|
const { isLayoutVisible, isHeaderVisible } = storeToRefs(layoutStore); // 添加 isHeaderVisible
|
||||||
const { isConfiguratorVisible: isFocusSwitcherVisible } = storeToRefs(focusSwitcherStore);
|
const { isConfiguratorVisible: isFocusSwitcherVisible } = storeToRefs(focusSwitcherStore);
|
||||||
const { isRdpModalOpen, rdpConnectionInfo } = storeToRefs(sessionStore); // +++ 获取 RDP 状态 +++
|
const { isRdpModalOpen, rdpConnectionInfo } = storeToRefs(sessionStore); // +++ 获取 RDP 状态 +++
|
||||||
|
const breakpoints = useBreakpoints(breakpointsTailwind); // +++ Initialize Breakpoints +++
|
||||||
|
const isMobile = breakpoints.smaller('md'); // +++ Define isMobile +++
|
||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const navRef = ref<HTMLElement | null>(null);
|
const navRef = ref<HTMLElement | null>(null);
|
||||||
@@ -269,14 +272,15 @@ const isElementVisibleAndFocusable = (element: HTMLElement): boolean => {
|
|||||||
</div>
|
</div>
|
||||||
<!-- Right navigation links with Tailwind classes using theme variables -->
|
<!-- Right navigation links with Tailwind classes using theme variables -->
|
||||||
<div class="flex items-center space-x-1">
|
<div class="flex items-center space-x-1">
|
||||||
<a href="https://github.com/Heavrnl/nexus-terminal" target="_blank" rel="noopener noreferrer" title="Heavrnl/nexus-terminal" class="px-2 py-2 rounded-md text-lg text-icon hover:text-icon-hover hover:bg-nav-active-bg hover:no-underline transition duration-150 ease-in-out">
|
<!-- GitHub Icon (Hide on mobile) -->
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" viewBox="0 0 16 16">
|
<a v-if="!isMobile" href="https://github.com/Heavrnl/nexus-terminal" target="_blank" rel="noopener noreferrer" title="Heavrnl/nexus-terminal" class="px-2 py-2 rounded-md text-lg text-icon hover:text-icon-hover hover:bg-nav-active-bg hover:no-underline transition duration-150 ease-in-out">
|
||||||
<path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27s1.36.09 2 .27c1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.01 8.01 0 0 0 16 8c0-4.42-3.58-8-8-8"/>
|
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" viewBox="0 0 16 16">
|
||||||
</svg>
|
<path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27s1.36.09 2 .27c1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.01 8.01 0 0 0 16 8c0-4.42-3.58-8-8-8"/>
|
||||||
</a>
|
</svg>
|
||||||
<a href="#" @click.prevent="openStyleCustomizer" :title="t('nav.customizeStyle')" class="px-2 py-2 rounded-md text-lg text-icon hover:text-icon-hover hover:bg-nav-active-bg hover:no-underline transition duration-150 ease-in-out"><i class="fas fa-paint-brush"></i></a>
|
</a>
|
||||||
<RouterLink v-if="!isAuthenticated" to="/login" class="px-3 py-2 rounded-md text-sm font-medium text-secondary hover:text-link-hover hover:bg-nav-active-bg hover:no-underline transition duration-150 ease-in-out whitespace-nowrap">{{ t('nav.login') }}</RouterLink>
|
<a href="#" @click.prevent="openStyleCustomizer" :title="t('nav.customizeStyle')" class="px-2 py-2 rounded-md text-lg text-icon hover:text-icon-hover hover:bg-nav-active-bg hover:no-underline transition duration-150 ease-in-out"><i class="fas fa-paint-brush"></i></a>
|
||||||
<a href="#" v-if="isAuthenticated" @click.prevent="handleLogout" class="px-3 py-2 rounded-md text-sm font-medium text-secondary hover:text-link-hover hover:bg-nav-active-bg hover:no-underline transition duration-150 ease-in-out whitespace-nowrap">{{ t('nav.logout') }}</a>
|
<RouterLink v-if="!isAuthenticated" to="/login" class="px-3 py-2 rounded-md text-sm font-medium text-secondary hover:text-link-hover hover:bg-nav-active-bg hover:no-underline transition duration-150 ease-in-out whitespace-nowrap">{{ t('nav.login') }}</RouterLink>
|
||||||
|
<a href="#" v-if="isAuthenticated" @click.prevent="handleLogout" class="px-3 py-2 rounded-md text-sm font-medium text-secondary hover:text-link-hover hover:bg-nav-active-bg hover:no-underline transition duration-150 ease-in-out whitespace-nowrap">{{ t('nav.logout') }}</a>
|
||||||
</div>
|
</div>
|
||||||
<!-- Sliding underline element with Tailwind classes using theme variables (JS still controls positioning) -->
|
<!-- Sliding underline element with Tailwind classes using theme variables (JS still controls positioning) -->
|
||||||
<div ref="underlineRef" class="absolute bottom-0 h-0.5 bg-link-active rounded transition-all duration-300 ease-in-out pointer-events-none opacity-0 transform translate-y-1.5"></div> <!-- Changed translate-y-1 to translate-y-1.5 -->
|
<div ref="underlineRef" class="absolute bottom-0 h-0.5 bg-link-active rounded transition-all duration-300 ease-in-out pointer-events-none opacity-0 transform translate-y-1.5"></div> <!-- Changed translate-y-1 to translate-y-1.5 -->
|
||||||
|
|||||||
@@ -12,7 +12,15 @@ import QuickCommandsModal from './QuickCommandsModal.vue'; // +++ Import the mod
|
|||||||
// Disable attribute inheritance as this component has multiple root nodes (div + modal)
|
// Disable attribute inheritance as this component has multiple root nodes (div + modal)
|
||||||
defineOptions({ inheritAttrs: false });
|
defineOptions({ inheritAttrs: false });
|
||||||
|
|
||||||
const emit = defineEmits(['send-command', 'search', 'find-next', 'find-previous', 'close-search', 'clear-terminal']); // 添加 clear-terminal 事件
|
const emit = defineEmits([
|
||||||
|
'send-command',
|
||||||
|
'search',
|
||||||
|
'find-next',
|
||||||
|
'find-previous',
|
||||||
|
'close-search',
|
||||||
|
'clear-terminal',
|
||||||
|
'toggle-virtual-keyboard' // +++ Add new emit +++
|
||||||
|
]);
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const focusSwitcherStore = useFocusSwitcherStore();
|
const focusSwitcherStore = useFocusSwitcherStore();
|
||||||
const settingsStore = useSettingsStore();
|
const settingsStore = useSettingsStore();
|
||||||
@@ -35,8 +43,8 @@ const { updateSessionCommandInput } = sessionStore;
|
|||||||
// Props definition is now empty as search results are no longer handled here
|
// Props definition is now empty as search results are no longer handled here
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
// No props defined here currently
|
// No props defined here currently
|
||||||
// +++ Add isMobile prop +++
|
|
||||||
isMobile?: boolean;
|
isMobile?: boolean;
|
||||||
|
isVirtualKeyboardVisible?: boolean; // +++ Add prop to receive state +++
|
||||||
}>();
|
}>();
|
||||||
// --- 移除本地 commandInput ref ---
|
// --- 移除本地 commandInput ref ---
|
||||||
// const commandInput = ref('');
|
// const commandInput = ref('');
|
||||||
@@ -305,7 +313,11 @@ const handleQuickCommandExecute = (command: string) => {
|
|||||||
v-model="currentSessionCommandInput"
|
v-model="currentSessionCommandInput"
|
||||||
:placeholder="t('commandInputBar.placeholder')"
|
:placeholder="t('commandInputBar.placeholder')"
|
||||||
class="flex-grow min-w-0 px-4 py-1.5 border border-border/50 rounded-lg bg-input text-foreground text-sm shadow-sm focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-primary transition-all duration-300 ease-in-out"
|
class="flex-grow min-w-0 px-4 py-1.5 border border-border/50 rounded-lg bg-input text-foreground text-sm shadow-sm focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-primary transition-all duration-300 ease-in-out"
|
||||||
:class="{ 'basis-3/4': !props.isMobile && isSearching, 'basis-full': !isSearching }"
|
:class="{
|
||||||
|
'basis-3/4': !props.isMobile && isSearching, // Desktop searching: 3/4 width
|
||||||
|
'basis-full': !props.isMobile && !isSearching // Desktop non-searching: full width
|
||||||
|
// Mobile non-searching: No basis class, rely on flex-grow
|
||||||
|
}"
|
||||||
ref="commandInputRef"
|
ref="commandInputRef"
|
||||||
data-focus-id="commandInput"
|
data-focus-id="commandInput"
|
||||||
@keydown="handleCommandInputKeydown"
|
@keydown="handleCommandInputKeydown"
|
||||||
@@ -329,7 +341,17 @@ const handleQuickCommandExecute = (command: string) => {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<!-- Search Controls -->
|
<!-- Search Controls -->
|
||||||
<div class="flex items-center gap-1 flex-shrink-0"> <!-- Adjusted gap -->
|
<div class="flex items-center gap-1 flex-shrink-0">
|
||||||
|
<!-- +++ Toggle Virtual Keyboard Button (Moved here, Mobile only) +++ -->
|
||||||
|
<button
|
||||||
|
v-if="props.isMobile"
|
||||||
|
@click="emit('toggle-virtual-keyboard')"
|
||||||
|
class="flex-shrink-0 flex items-center justify-center w-8 h-8 border border-border/50 rounded-lg text-text-secondary transition-colors duration-200 hover:bg-border hover:text-foreground"
|
||||||
|
:title="props.isVirtualKeyboardVisible ? t('commandInputBar.hideKeyboard', '隐藏虚拟键盘') : t('commandInputBar.showKeyboard', '显示虚拟键盘')"
|
||||||
|
>
|
||||||
|
<i class="fas fa-keyboard text-base" :class="{ 'opacity-50': !props.isVirtualKeyboardVisible }"></i>
|
||||||
|
</button>
|
||||||
|
<!-- Search Toggle Button -->
|
||||||
<button
|
<button
|
||||||
@click="toggleSearch"
|
@click="toggleSearch"
|
||||||
class="flex items-center justify-center w-8 h-8 border border-border/50 rounded-lg text-text-secondary transition-colors duration-200 hover:bg-border hover:text-foreground"
|
class="flex items-center justify-center w-8 h-8 border border-border/50 rounded-lg text-text-secondary transition-colors duration-200 hover:bg-border hover:text-foreground"
|
||||||
|
|||||||
@@ -264,11 +264,12 @@ const toggleButtonTitle = computed(() => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<!-- +++ 使用 :class 绑定来条件化样式 +++ -->
|
<!-- +++ 使用 :class 绑定来条件化样式,包括高度 (修正 props 引用) +++ -->
|
||||||
<div :class="['flex bg-header border border-border overflow-hidden h-10',
|
<div :class="['flex bg-header border border-border overflow-hidden',
|
||||||
{ 'rounded-t-md mx-2 mt-2': !isMobile } // 只在非移动端应用这些类
|
{ 'rounded-t-md mx-2 mt-2': !props.isMobile }, // Desktop margins/rounding - Use props.isMobile
|
||||||
|
props.isMobile ? 'h-8' : 'h-10' // Mobile height h-8, Desktop h-10 - Use props.isMobile
|
||||||
]">
|
]">
|
||||||
<div class="flex items-center overflow-x-auto flex-shrink min-w-0">
|
<div class="flex items-center overflow-x-auto flex-shrink min-w-0 h-full"> <!-- Ensure inner div has h-full -->
|
||||||
<ul class="flex list-none p-0 m-0 h-full flex-shrink-0">
|
<ul class="flex list-none p-0 m-0 h-full flex-shrink-0">
|
||||||
<li
|
<li
|
||||||
v-for="session in sessions"
|
v-for="session in sessions"
|
||||||
|
|||||||
@@ -82,13 +82,17 @@ const keys: KeyDefinition[] = [
|
|||||||
{ label: 'End', sequence: '\x1b[4~', type: 'navigation' }, // +++ End +++
|
{ label: 'End', sequence: '\x1b[4~', type: 'navigation' }, // +++ End +++
|
||||||
{ label: 'PgUp', sequence: '\x1b[5~', type: 'navigation' }, // +++ PageUp +++
|
{ label: 'PgUp', sequence: '\x1b[5~', type: 'navigation' }, // +++ PageUp +++
|
||||||
{ label: 'PgDn', sequence: '\x1b[6~', type: 'navigation' }, // +++ PageDown +++
|
{ label: 'PgDn', sequence: '\x1b[6~', type: 'navigation' }, // +++ PageDown +++
|
||||||
// Row 3: Example character keys for combinations
|
// Row 3: Alphabet Keys (A-Z)
|
||||||
{ label: 'A', type: 'char' },
|
{ label: 'A', type: 'char' }, { label: 'B', type: 'char' }, { label: 'C', type: 'char' },
|
||||||
{ label: 'B', type: 'char' },
|
{ label: 'D', type: 'char' }, { label: 'E', type: 'char' }, { label: 'F', type: 'char' },
|
||||||
{ label: 'C', type: 'char' },
|
{ label: 'G', type: 'char' }, { label: 'H', type: 'char' }, { label: 'I', type: 'char' },
|
||||||
{ label: 'D', type: 'char' },
|
{ label: 'J', type: 'char' }, { label: 'K', type: 'char' }, { label: 'L', type: 'char' },
|
||||||
{ label: 'F', type: 'char' },
|
{ label: 'M', type: 'char' }, { label: 'N', type: 'char' }, { label: 'O', type: 'char' },
|
||||||
// Add more letters, numbers, or symbols as needed
|
{ label: 'P', type: 'char' }, { label: 'Q', type: 'char' }, { label: 'R', type: 'char' },
|
||||||
|
{ label: 'S', type: 'char' }, { label: 'T', type: 'char' }, { label: 'U', type: 'char' },
|
||||||
|
{ label: 'V', type: 'char' }, { label: 'W', type: 'char' }, { label: 'X', type: 'char' },
|
||||||
|
{ label: 'Y', type: 'char' }, { label: 'Z', type: 'char' },
|
||||||
|
// Add numbers or other symbols if needed
|
||||||
];
|
];
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -66,6 +66,7 @@ const showLayoutConfigurator = ref(false); // 控制布局配置器可见性
|
|||||||
// --- 搜索状态 ---
|
// --- 搜索状态 ---
|
||||||
const currentSearchTerm = ref(''); // 当前搜索的关键词
|
const currentSearchTerm = ref(''); // 当前搜索的关键词
|
||||||
const mobileTerminalRef = ref<InstanceType<typeof Terminal> | null>(null); // +++ 添加 mobileTerminalRef +++
|
const mobileTerminalRef = ref<InstanceType<typeof Terminal> | null>(null); // +++ 添加 mobileTerminalRef +++
|
||||||
|
const isVirtualKeyboardVisible = ref(true); // +++ State for virtual keyboard visibility +++
|
||||||
|
|
||||||
// --- 新增:处理全局键盘事件 ---
|
// --- 新增:处理全局键盘事件 ---
|
||||||
const handleGlobalKeyDown = (event: KeyboardEvent) => {
|
const handleGlobalKeyDown = (event: KeyboardEvent) => {
|
||||||
@@ -475,6 +476,11 @@ const handleVirtualKeyPress = (keySequence: string) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// +++ Function to toggle virtual keyboard visibility +++
|
||||||
|
const toggleVirtualKeyboard = () => {
|
||||||
|
isVirtualKeyboardVisible.value = !isVirtualKeyboardVisible.value;
|
||||||
|
};
|
||||||
|
|
||||||
// RDP 事件处理方法已被移除
|
// RDP 事件处理方法已被移除
|
||||||
|
|
||||||
// --- 标签页关闭操作处理 ---
|
// --- 标签页关闭操作处理 ---
|
||||||
@@ -614,9 +620,12 @@ const handleVirtualKeyPress = (keySequence: string) => {
|
|||||||
@find-previous="handleFindPrevious"
|
@find-previous="handleFindPrevious"
|
||||||
@close-search="handleCloseSearch"
|
@close-search="handleCloseSearch"
|
||||||
@clear-terminal="handleClearTerminal"
|
@clear-terminal="handleClearTerminal"
|
||||||
|
:is-virtual-keyboard-visible="isVirtualKeyboardVisible"
|
||||||
|
@toggle-virtual-keyboard="toggleVirtualKeyboard"
|
||||||
/>
|
/>
|
||||||
<!-- +++ 添加虚拟键盘,监听事件 +++ -->
|
<!-- +++ Use v-show for VirtualKeyboard and bind visibility +++ -->
|
||||||
<VirtualKeyboard
|
<VirtualKeyboard
|
||||||
|
v-show="isVirtualKeyboardVisible"
|
||||||
class="mobile-virtual-keyboard"
|
class="mobile-virtual-keyboard"
|
||||||
@send-key="handleVirtualKeyPress"
|
@send-key="handleVirtualKeyPress"
|
||||||
/>
|
/>
|
||||||
|
|||||||
Reference in New Issue
Block a user