From c97831c7ddca9fc470572da8a21cb12945a38b4a Mon Sep 17 00:00:00 2001 From: Baobhan Sith <80159437+Heavrnl@users.noreply.github.com> Date: Tue, 13 May 2025 11:44:48 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E9=80=82=E9=85=8D=E7=A7=BB=E5=8A=A8?= =?UTF-8?q?=E7=AB=AF=E7=95=8C=E9=9D=A2=E4=BB=A5=E4=BD=BF=E7=94=A8=E6=96=87?= =?UTF-8?q?=E4=BB=B6=E7=AE=A1=E7=90=86=E5=99=A8=E5=92=8C=E6=96=87=E4=BB=B6?= =?UTF-8?q?=E7=BC=96=E8=BE=91=E5=99=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/frontend/src/App.vue | 7 +- .../src/components/FileEditorOverlay.vue | 78 ++++++++++++++++--- .../src/composables/useDeviceDetection.ts | 9 +++ packages/frontend/src/views/WorkspaceView.vue | 5 +- 4 files changed, 82 insertions(+), 17 deletions(-) create mode 100644 packages/frontend/src/composables/useDeviceDetection.ts diff --git a/packages/frontend/src/App.vue b/packages/frontend/src/App.vue index 7d1c4fe..9e539b5 100644 --- a/packages/frontend/src/App.vue +++ b/packages/frontend/src/App.vue @@ -2,8 +2,8 @@ import { RouterLink, RouterView, useRoute } from 'vue-router'; import { ref, onMounted, onUnmounted, watch, nextTick, computed } from 'vue'; import { useI18n } from 'vue-i18n'; -import { useBreakpoints, breakpointsTailwind } from '@vueuse/core'; // +++ Import useBreakpoints +++ import { useAuthStore } from './stores/auth.store'; +import { useDeviceDetection } from './composables/useDeviceDetection'; import { useSettingsStore } from './stores/settings.store'; import { useAppearanceStore } from './stores/appearance.store'; import { useLayoutStore } from './stores/layout.store'; @@ -36,8 +36,7 @@ const { isStyleCustomizerVisible } = storeToRefs(appearanceStore); const { isLayoutVisible, isHeaderVisible } = storeToRefs(layoutStore); // 添加 isHeaderVisible const { isConfiguratorVisible: isFocusSwitcherVisible } = storeToRefs(focusSwitcherStore); const { isRdpModalOpen, rdpConnectionInfo, isVncModalOpen, vncConnectionInfo } = storeToRefs(sessionStore); // +++ 获取 RDP 和 VNC 状态 +++ -const breakpoints = useBreakpoints(breakpointsTailwind); // +++ Initialize Breakpoints +++ -const isMobile = breakpoints.smaller('md'); // +++ Define isMobile +++ +const { isMobile } = useDeviceDetection(); const route = useRoute(); const navRef = ref(null); @@ -302,7 +301,7 @@ const isElementVisibleAndFocusable = (element: HTMLElement): boolean => { - + diff --git a/packages/frontend/src/components/FileEditorOverlay.vue b/packages/frontend/src/components/FileEditorOverlay.vue index 3dce05c..d406fb2 100644 --- a/packages/frontend/src/components/FileEditorOverlay.vue +++ b/packages/frontend/src/components/FileEditorOverlay.vue @@ -4,19 +4,24 @@ import { useI18n } from 'vue-i18n'; import { storeToRefs } from 'pinia'; import MonacoEditor from './MonacoEditor.vue'; import FileEditorTabs from './FileEditorTabs.vue'; -import { useFileEditorStore, type FileTab } from '../stores/fileEditor.store'; +import { useFileEditorStore, type FileTab } from '../stores/fileEditor.store'; import { useSettingsStore } from '../stores/settings.store'; -import { useSessionStore } from '../stores/session.store'; +import { useSessionStore } from '../stores/session.store'; const { t } = useI18n(); const fileEditorStore = useFileEditorStore(); const settingsStore = useSettingsStore(); -const sessionStore = useSessionStore(); +const sessionStore = useSessionStore(); // --- 本地状态控制弹窗显示 --- const isVisible = ref(false); +// 接收 isMobile 属性 +const props = defineProps<{ + isMobile?: boolean; +}>(); + // --- 从 Store 获取状态 --- // 全局 Store (用于共享模式和触发器) const { @@ -83,10 +88,22 @@ const minHeight = 300; // 最小高度 const encodingSelectRef = ref(null); // +++ Ref for the select element +++ // --- 计算属性,用于模板绑定 --- -const popupStyle = computed(() => ({ - width: `${popupWidthPx.value}px`, - height: `${popupHeightPx.value}px`, -})); +const popupStyle = computed(() => { + if (props.isMobile) { + return { + width: '100vw', + height: '100vh', + maxWidth: '100%', + maxHeight: '100%', + borderRadius: '0', + }; + } else { + return { + width: `${popupWidthPx.value}px`, + height: `${popupHeightPx.value}px`, + }; + } +}); // --- 动态计算属性 (根据模式选择数据源) --- @@ -580,22 +597,55 @@ onBeforeUnmount(() => { position: relative; /* 为拖拽手柄定位 */ } +/* 移动设备上布满屏幕 */ +@media (max-width: 768px) { + .editor-popup { + width: 100vw; + height: 100vh; + max-width: 100%; + max-height: 100%; + border-radius: 0; + } + .editor-header { + padding: 1.5rem 2.5rem 0.5rem 1rem; /* 增加顶部内边距以确保关闭按钮可见 */ + } + .editor-actions { + flex-wrap: wrap; + max-width: 100%; + margin-top: 1rem; + } +} + +/* 适配横向旋转 */ +@media (orientation: landscape) and (max-width: 1024px) { + .editor-popup { + width: 100vw; + height: 100vh; + max-width: 100%; + max-height: 100%; + border-radius: 0; + } +} + /* 标签栏区域 (FileEditorTabs 组件将放在这里) */ /* .file-tabs-container { ... } */ .editor-header { display: flex; - justify-content: space-between; - align-items: center; + flex-direction: column; + align-items: flex-start; padding: 0.5rem 1rem; background-color: #333; border-bottom: 1px solid #555; font-size: 0.9em; flex-shrink: 0; + position: relative; } .editor-header-placeholder { - justify-content: space-between; /* 保持关闭按钮在右侧 */ + flex-direction: column; + align-items: flex-start; color: #888; + position: relative; } .modified-indicator { @@ -611,6 +661,10 @@ onBeforeUnmount(() => { font-size: 1.2em; cursor: pointer; padding: 0.2rem 0.5rem; + position: absolute; + right: 0.5rem; + top: 0.5rem; + z-index: 10; } .close-editor-btn:hover { color: white; @@ -647,6 +701,10 @@ onBeforeUnmount(() => { display: flex; align-items: center; gap: 0.8rem; /* 稍微减小间距以容纳下拉菜单 */ + flex-wrap: wrap; + max-width: 100%; + justify-content: flex-start; + margin-top: 0.5rem; } .save-btn { diff --git a/packages/frontend/src/composables/useDeviceDetection.ts b/packages/frontend/src/composables/useDeviceDetection.ts new file mode 100644 index 0000000..234eae5 --- /dev/null +++ b/packages/frontend/src/composables/useDeviceDetection.ts @@ -0,0 +1,9 @@ +import { computed } from 'vue'; + +export function useDeviceDetection() { + const isMobile = computed(() => { + return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent); + }); + + return { isMobile }; +} \ No newline at end of file diff --git a/packages/frontend/src/views/WorkspaceView.vue b/packages/frontend/src/views/WorkspaceView.vue index d43af78..0b7648e 100644 --- a/packages/frontend/src/views/WorkspaceView.vue +++ b/packages/frontend/src/views/WorkspaceView.vue @@ -2,8 +2,8 @@ import { onMounted, onBeforeUnmount, computed, ref, shallowRef, type PropType } from 'vue'; import { useI18n } from 'vue-i18n'; import { storeToRefs } from 'pinia'; -import { useBreakpoints, breakpointsTailwind } from '@vueuse/core'; import { useLayoutStore } from '../stores/layout.store'; +import { useDeviceDetection } from '../composables/useDeviceDetection'; import { useConnectionsStore, type ConnectionInfo } from '../stores/connections.store'; import AddConnectionFormComponent from '../components/AddConnectionForm.vue'; import TerminalTabBar from '../components/TerminalTabBar.vue'; @@ -36,8 +36,7 @@ const layoutStore = useLayoutStore(); const commandHistoryStore = useCommandHistoryStore(); const connectionsStore = useConnectionsStore(); const { isHeaderVisible } = storeToRefs(layoutStore); -const breakpoints = useBreakpoints(breakpointsTailwind); // +++ 初始化 Breakpoints +++ -const isMobile = breakpoints.smaller('md'); // +++ 定义 isMobile (小于 md 断点) +++ +const { isMobile } = useDeviceDetection(); // --- 从 Store 获取响应式状态和 Getters --- const { sessionTabsWithStatus, activeSessionId, activeSession, isRdpModalOpen, rdpConnectionInfo, isVncModalOpen, vncConnectionInfo } = storeToRefs(sessionStore); // 使用 storeToRefs 获取 RDP 和 VNC 状态