From cfa76cab17d6a9f54ca8b3961ee71a2b3952ecf2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E6=80=A7=E9=A9=8A?= Date: Fri, 12 Sep 2025 00:50:28 +0800 Subject: [PATCH] fix: language --- app/app.vue | 318 ++++++++++++++++++++---------- app/components/AboutMeWindow.vue | 177 ++--------------- app/components/BaseWindow.vue | 207 +++++++++++++++++++ app/components/Taskbar.vue | 144 ++++++-------- app/components/TerminalWindow.vue | 118 +++-------- app/components/WinampWindow.vue | 48 +++++ app/components/WindowFrame.vue | 149 ++++++++++++++ app/pages/index.vue | 23 ++- 8 files changed, 743 insertions(+), 441 deletions(-) create mode 100644 app/components/BaseWindow.vue create mode 100644 app/components/WinampWindow.vue create mode 100644 app/components/WindowFrame.vue diff --git a/app/app.vue b/app/app.vue index 5028d70..91affcc 100644 --- a/app/app.vue +++ b/app/app.vue @@ -2,76 +2,76 @@ import Taskbar from '~/components/Taskbar.vue'; import AboutMeWindow from '~/components/AboutMeWindow.vue'; import TerminalWindow from '~/components/TerminalWindow.vue'; -import { ref, reactive, onMounted, onUnmounted, computed } from 'vue'; -import { useI18n } from 'vue-i18n'; +import WinampWindow from '~/components/WinampWindow.vue'; +import { ref, reactive, onMounted, computed, provide, onUnmounted } from 'vue'; +// --- Data Definitions --- // Reactive array to hold all window states const windows = reactive([]); -// Keep track of the highest z-index to ensure new/focused windows are on top +// Keep track of the highest z-index const currentZIndex = ref(100); -// Function to generate a unique ID for each window instance -const generateUniqueId = () => { - return Date.now().toString(36) + Math.random().toString(36).substr(2); -}; +// Desktop icons container position +const iconsContainerX = ref(0); +const iconsContainerY = ref(0); -// Load window states from localStorage on mount -onMounted(() => { - const savedWindows = localStorage.getItem('windows'); - if (savedWindows) { - const parsedWindows = JSON.parse(savedWindows); - parsedWindows.forEach(win => { - // Ensure z-index is properly managed on load - if (win.zIndex > currentZIndex.value) { - currentZIndex.value = win.zIndex; - } - windows.push(win); - }); - } -}); +// Dragging state for desktop icons container +const isIconsContainerDragging = ref(false); +const iconsContainerStartX = ref(0); +const iconsContainerStartY = ref(0); +const initialIconsContainerX = ref(0); +const initialIconsContainerY = ref(0); -// Save window states to localStorage whenever windows array changes -const saveWindowsToLocalStorage = () => { - localStorage.setItem('windows', JSON.stringify(windows)); -}; +// Define Start Menu Items with icons and actions +const startMenuItems = ref([ + { + labelKey: 'terminal', + icon: 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KICAgIDxwYXRoIGQ9Ik0yMiAxNlY4QzIyIDYuODk1NDMgMjEuMTA0NiA2IDIwIDZIMTRMMTIgNEg0QzIuODk1NDMgNCAyIDQuODk1NDMgMiA2VjE4QzIgMTkuMTA0NiAyLjg5NTQzIDIwIDQgMjBIMjBDMjEuMTA0NiAyMCAyMiAxOS4xMDQ2IDIyIDE4VjE2WiIgZmlsbD0iIzAwMDAwMCIvPgogICAgPHBhdGggZD0iTTggMTJMMTAgMTQuNUwxMiAxMkwxMCA5LjVMOCAxMloiIGZpbGw9IndoaXRlIi8+CiAgICA8cGF0aCBkPSJNMTAgMTZIMTRWMThIMTBWMTZaIiBmaWxsPSJ3aGl0ZSIvPgo8L3N2Zz4=', + action: 'openTerminal', + }, + { + labelKey: 'about_me', + icon: 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KICAgIDxjaXJjbGUgY3g9IjEyIiBjeT0iMTIiIHI9IjEwIiBmaWxsPSIjRkZGRjAwIi8+CiAgICA8cGF0aCBkPSJNMTEgN0gxM1Y5SDExVjdaTTExIDExSDEzVjE3SDExVjExWiIgZmlsbD0iYmxhY2siLz4KPC9zdmc+Cg==', + action: 'openAboutMe', + }, + { + labelKey: 'close_all_windows', + icon: 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KICAgIDxwYXRoIGQ9Ik0xOC41IDUuNUw1LjUgMTguNU01LjUgNS41TDE4LjUgMTguNSIgc3Ryb2tlPSJibGFjayIgc3Ryb2tlLXdpZHRoPSIyIi8+Cjwvc3ZnPgo=', + action: 'closeAllWindows', + }, +]); + +// --- Window Management Functions --- + +const generateUniqueId = () => Date.now().toString(36) + Math.random().toString(36).substr(2); + +const saveWindowsToLocalStorage = () => localStorage.setItem('windows', JSON.stringify(windows)); -// Function to open a new window or bring an existing one to front const openWindow = (type, titleKey, initialWidth = 600, initialHeight = 400) => { const existingWindow = windows.find(w => w.type === type); if (existingWindow) { bringWindowToFront(existingWindow.id); - if (existingWindow.isMinimized) { - restoreWindow(existingWindow.id); - } - existingWindow.isVisible = true; // Ensure it's visible if it was closed + if (existingWindow.isMinimized) restoreWindow(existingWindow.id); + existingWindow.isVisible = true; } else { currentZIndex.value++; - // Calculate initial position to center the window within the desktop content area const desktopWidth = window.innerWidth; - const desktopHeight = window.innerHeight - 40; // Minus taskbar height + const desktopHeight = window.innerHeight - 40; const initialX = (desktopWidth - initialWidth) / 2; - const initialY = (desktopHeight - initialHeight) / 2 + 40; // Offset by taskbar height + const initialY = (desktopHeight - initialHeight) / 2 + 40; - const newWindow = { + windows.push({ id: generateUniqueId(), - type, - title: titleKey, // Store the translation key here - isVisible: true, - isMinimized: false, - x: initialX, - y: initialY, - width: initialWidth, - height: initialHeight, + type, title: titleKey, isVisible: true, isMinimized: false, + x: initialX, y: initialY, width: initialWidth, height: initialHeight, zIndex: currentZIndex.value, - }; - windows.push(newWindow); - saveWindowsToLocalStorage(); + }); } + saveWindowsToLocalStorage(); }; -// Function to close a window const closeWindow = (id) => { const index = windows.findIndex(w => w.id === id); if (index !== -1) { @@ -80,138 +80,256 @@ const closeWindow = (id) => { } }; -// Function to close all windows const closeAllWindows = () => { - windows.splice(0, windows.length); // Clear the array + windows.length = 0; saveWindowsToLocalStorage(); }; -// Function to minimize a window const minimizeWindow = (id) => { - const windowToMinimize = windows.find(w => w.id === id); - if (windowToMinimize) { - windowToMinimize.isMinimized = true; + const win = windows.find(w => w.id === id); + if (win) { + console.log(`Minimizing window ${id}. Before: isMinimized=${win.isMinimized}`); + win.isMinimized = true; + console.log(`Minimizing window ${id}. After: isMinimized=${win.isMinimized}`); saveWindowsToLocalStorage(); } }; -// Function to restore a minimized window const restoreWindow = (id) => { - const windowToRestore = windows.find(w => w.id === id); - if (windowToRestore) { - windowToRestore.isMinimized = false; + const win = windows.find(w => w.id === id); + if (win) { + console.log(`Restoring window ${id}. Before: isMinimized=${win.isMinimized}`); + win.isMinimized = false; + console.log(`Restoring window ${id}. After: isMinimized=${win.isMinimized}`); bringWindowToFront(id); saveWindowsToLocalStorage(); } }; -// Function to bring a window to the front (update z-index) const bringWindowToFront = (id) => { - const windowToFront = windows.find(w => w.id === id); - if (windowToFront) { + const win = windows.find(w => w.id === id); + if (win) { currentZIndex.value++; - windowToFront.zIndex = currentZIndex.value; + win.zIndex = currentZIndex.value; + console.log(`Bringing window ${id} to front. New zIndex: ${win.zIndex}`); saveWindowsToLocalStorage(); } }; -// Function to update window position (for dragging) -const updateWindowPosition = (id, newX, newY) => { - const windowToUpdate = windows.find(w => w.id === id); - if (windowToUpdate) { - windowToUpdate.x = newX; - windowToUpdate.y = newY; +const updateWindowPosition = (id, { x, y }) => { + const win = windows.find(w => w.id === id); + if (win) { + win.x = x; + win.y = y; saveWindowsToLocalStorage(); } }; -// Handle opening About Me window from Taskbar -const openAboutMeWindow = () => { - openWindow('about-me', 'about_me'); // Pass the translation key +const updateWindowDimensions = (id, { width, height }) => { + const win = windows.find(w => w.id === id); + if (win) { + win.width = width; + win.height = height; + saveWindowsToLocalStorage(); + } }; -const openTerminalWindow = () => { - openWindow('terminal', 'terminal'); +// --- Action Handlers --- + +const openAboutMeWindow = () => openWindow('about-me', 'about_me'); +const openTerminalWindow = () => openWindow('terminal', 'terminal'); +const openWinampWindow = () => { + console.log('Opening Winamp window via provide/inject.'); + openWindow('winamp', 'Winamp', 400, 200); }; -// Computed property for minimized windows -const minimizedWindows = computed(() => { - return windows.filter(win => win.isMinimized); +// Provide the openWinampWindow function +provide('openWinamp', openWinampWindow); + +const menuActions = { + openTerminal: openTerminalWindow, + openAboutMe: openAboutMeWindow, + closeAllWindows: closeAllWindows, +}; + +const handleMenuAction = (action) => { + const func = menuActions[action]; + if (func) func(); +}; + +// --- Desktop Icons Container Dragging Functions --- +const handleIconsContainerMouseDown = (e) => { + // Only drag if not clicking on a window or taskbar + if (e.target.closest('.window-wrapper') || e.target.closest('.taskbar')) { + console.log('Clicked on a window or taskbar, not dragging desktop icons container.'); + return; + } + isIconsContainerDragging.value = true; + iconsContainerStartX.value = e.clientX; + iconsContainerStartY.value = e.clientY; + initialIconsContainerX.value = iconsContainerX.value; + initialIconsContainerY.value = iconsContainerY.value; + console.log(`Icons container drag started. Initial pos: (${initialIconsContainerX.value}, ${initialIconsContainerY.value})`); +}; + +const handleIconsContainerMouseMove = (e) => { + if (!isIconsContainerDragging.value) return; + const dx = e.clientX - iconsContainerStartX.value; + const dy = e.clientY - iconsContainerStartY.value; + + let newX = initialIconsContainerX.value + dx; + let newY = initialIconsContainerY.value + dy; + + // Get desktop content dimensions + const desktopContent = document.querySelector('.desktop-content'); + const desktopWidth = desktopContent.offsetWidth; + const desktopHeight = desktopContent.offsetHeight; + + // Get NuxtPage (icons container) dimensions + const nuxtPage = document.querySelector('.desktop-icons-container'); + const nuxtPageWidth = nuxtPage.offsetWidth; + const nuxtPageHeight = nuxtPage.offsetHeight; + + // Define boundaries + const minX = -(nuxtPageWidth - desktopWidth); + const minY = -(nuxtPageHeight - desktopHeight); + const maxX = 0; + const maxY = 0; + + // Clamp newX and newY within boundaries + iconsContainerX.value = Math.max(minX, Math.min(maxX, newX)); + iconsContainerY.value = Math.max(minY, Math.min(maxY, newY)); + + console.log(`Icons container dragging. Current pos: (${iconsContainerX.value}, ${iconsContainerY.value})`); +}; + +const handleIconsContainerMouseUp = () => { + if (isIconsContainerDragging.value) { + isIconsContainerDragging.value = false; + console.log(`Icons container drag ended. Final pos: (${iconsContainerX.value}, ${iconsContainerY.value})`); + } +}; + +// --- Lifecycle Hooks --- + +onMounted(() => { + const savedWindows = localStorage.getItem('windows'); + if (savedWindows) { + const parsedWindows = JSON.parse(savedWindows); + parsedWindows.forEach(win => { + if (win.zIndex > currentZIndex.value) currentZIndex.value = win.zIndex; + windows.push(win); + }); + } + window.addEventListener('mousemove', handleIconsContainerMouseMove); + window.addEventListener('mouseup', handleIconsContainerMouseUp); }); -// Clean up localStorage on component unmount (optional, for development) onUnmounted(() => { - // localStorage.removeItem('windows'); + window.removeEventListener('mousemove', handleIconsContainerMouseMove); + window.removeEventListener('mouseup', handleIconsContainerMouseUp); }); + +const minimizedWindows = computed(() => windows.filter(win => win.isMinimized)); + + +.desktop-icons-container { + position: relative; /* Allow positioning of children (NuxtPage) */ + width: 100%; + height: 100%; + cursor: grab; +} + \ No newline at end of file diff --git a/app/components/AboutMeWindow.vue b/app/components/AboutMeWindow.vue index 9e3cc34..e100ce9 100644 --- a/app/components/AboutMeWindow.vue +++ b/app/components/AboutMeWindow.vue @@ -1,180 +1,33 @@ diff --git a/app/components/BaseWindow.vue b/app/components/BaseWindow.vue new file mode 100644 index 0000000..1a9f6c9 --- /dev/null +++ b/app/components/BaseWindow.vue @@ -0,0 +1,207 @@ + + + + + diff --git a/app/components/Taskbar.vue b/app/components/Taskbar.vue index da82e1b..0b82a36 100644 --- a/app/components/Taskbar.vue +++ b/app/components/Taskbar.vue @@ -2,19 +2,18 @@
- - - -
@@ -44,36 +43,25 @@ import { ref, onMounted, onUnmounted, watch } from 'vue'; import { useI18n } from 'vue-i18n'; const { t, locale, setLocale } = useI18n(); + defineProps({ - minimizedWindows: { - type: Array, - default: () => [], - }, + minimizedWindows: { type: Array, default: () => [] }, + menuItems: { type: Array, default: () => [] }, }); +const emit = defineEmits(['restore-window', 'menu-action']); + const currentTime = ref(''); const isMounted = ref(false); let timerId = null; const showStartMenu = ref(false); -const emit = defineEmits(['open-about-me', 'restore-window', 'close-all-windows', 'open-terminal']); - const toggleStartMenu = () => { showStartMenu.value = !showStartMenu.value; }; -const openTerminal = () => { - emit('open-terminal'); - showStartMenu.value = false; -}; - -const openAboutMe = () => { - emit('open-about-me'); - showStartMenu.value = false; -}; - -const closeAllWindows = () => { - emit('close-all-windows'); +const handleMenuClick = (action) => { + emit('menu-action', action); showStartMenu.value = false; }; @@ -88,16 +76,15 @@ const restoreMinimizedWindow = (id) => { }; const updateCurrentTime = () => { - // Map i18n locale to a specific locale for time formatting const timeLocale = locale.value === 'zh-tw' ? 'zh-TW' : 'en-US'; currentTime.value = new Date().toLocaleTimeString(timeLocale); }; -// Watch for locale changes to update the time format immediately + watch(locale, updateCurrentTime); onMounted(() => { isMounted.value = true; - updateCurrentTime(); // Set initial time based on current locale + updateCurrentTime(); timerId = setInterval(updateCurrentTime, 1000); }); @@ -113,53 +100,20 @@ onUnmounted(() => { display: flex; justify-content: space-between; align-items: center; - background-color: #c0c0c0; /* Classic Windows gray */ + background-color: #c0c0c0; padding: 4px 6px; border-top: 2px solid #ffffff; border-bottom: 2px solid #808080; width: 100%; box-sizing: border-box; height: 40px; - position: relative; /* For positioning the start menu */ + position: relative; } .start-section { position: relative; display: flex; align-items: center; - flex-grow: 0; /* Don't let start section grow */ -} - -.right-section { - display: flex; - align-items: center; - gap: 6px; -} - -.task-buttons { - display: flex; - flex-grow: 1; /* Allow task buttons to take available space */ - margin-left: 10px; /* Space between start button and task buttons */ - gap: 6px; - overflow-x: auto; /* Allow horizontal scrolling if many windows */ - padding-bottom: 2px; /* Prevent scrollbar from overlapping border */ -} - -.task-button { - padding: 4px 8px; - border: 2px solid; - border-color: #808080 #ffffff #ffffff #808080; - background-color: #c0c0c0; - font-weight: bold; - font-size: 0.9rem; - cursor: pointer; - color: black; - white-space: nowrap; /* Prevent text wrapping */ - flex-shrink: 0; /* Prevent buttons from shrinking */ -} - -.task-button:active { - border-color: #ffffff #808080 #808080 #ffffff; } .start-button { @@ -181,26 +135,31 @@ onUnmounted(() => { .start-icon { margin-right: 8px; + width: 20px; + height: 20px; } .start-menu { position: absolute; - top: 100%; /* Opens downwards from the taskbar */ + top: 100%; left: 0; background-color: #c0c0c0; border: 2px solid; border-color: #ffffff #808080 #808080 #ffffff; padding: 4px; - min-width: 160px; + min-width: 200px; z-index: 1001; } .menu-item { - padding: 4px 8px; + padding: 8px 12px; cursor: pointer; white-space: nowrap; border: 1px solid transparent; color: black; + display: flex; + align-items: center; + gap: 10px; } .menu-item:hover { @@ -209,7 +168,43 @@ onUnmounted(() => { border-color: #000080; } -.language-toggle { +.menu-icon { + width: 24px; + height: 24px; +} + +.task-buttons { + display: flex; + flex-grow: 1; + margin-left: 10px; + gap: 6px; + overflow-x: auto; +} + +.task-button { + padding: 4px 8px; + border: 2px solid; + border-color: #808080 #ffffff #ffffff #808080; + background-color: #c0c0c0; + font-weight: bold; + font-size: 0.9rem; + cursor: pointer; + color: black; + white-space: nowrap; + flex-shrink: 0; +} + +.task-button:active { + border-color: #ffffff #808080 #808080 #ffffff; +} + +.right-section { + display: flex; + align-items: center; + gap: 6px; +} + +.language-toggle, .clock { padding: 4px 8px; border: 2px solid; border-color: #808080 #ffffff #ffffff #808080; @@ -222,13 +217,4 @@ onUnmounted(() => { .language-toggle:active { border-color: #ffffff #808080 #808080 #ffffff; } - -.clock { - padding: 4px 8px; - border: 2px solid; - border-color: #808080 #ffffff #ffffff #808080; - font-size: 0.9rem; - color: black; - background-color: #c0c0c0; -} diff --git a/app/components/TerminalWindow.vue b/app/components/TerminalWindow.vue index 64ee1a9..ffd5f0c 100644 --- a/app/components/TerminalWindow.vue +++ b/app/components/TerminalWindow.vue @@ -1,115 +1,57 @@ diff --git a/app/components/WindowFrame.vue b/app/components/WindowFrame.vue new file mode 100644 index 0000000..09ed8d0 --- /dev/null +++ b/app/components/WindowFrame.vue @@ -0,0 +1,149 @@ + + + + + \ No newline at end of file diff --git a/app/pages/index.vue b/app/pages/index.vue index e3f30e9..1da9b2a 100644 --- a/app/pages/index.vue +++ b/app/pages/index.vue @@ -1,16 +1,15 @@ + + @@ -56,4 +55,4 @@ padding: 2px 4px; font-size: 0.8rem; } - + \ No newline at end of file