fix: deploy auto exec
This commit is contained in:
commit
66bdc2a2bc
|
@ -0,0 +1,24 @@
|
|||
# Nuxt dev/build outputs
|
||||
.output
|
||||
.data
|
||||
.nuxt
|
||||
.nitro
|
||||
.cache
|
||||
dist
|
||||
|
||||
# Node dependencies
|
||||
node_modules
|
||||
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
|
||||
# Misc
|
||||
.DS_Store
|
||||
.fleet
|
||||
.idea
|
||||
|
||||
# Local env files
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
|
@ -0,0 +1,75 @@
|
|||
# Nuxt Minimal Starter
|
||||
|
||||
Look at the [Nuxt documentation](https://nuxt.com/docs/getting-started/introduction) to learn more.
|
||||
|
||||
## Setup
|
||||
|
||||
Make sure to install dependencies:
|
||||
|
||||
```bash
|
||||
# npm
|
||||
npm install
|
||||
|
||||
# pnpm
|
||||
pnpm install
|
||||
|
||||
# yarn
|
||||
yarn install
|
||||
|
||||
# bun
|
||||
bun install
|
||||
```
|
||||
|
||||
## Development Server
|
||||
|
||||
Start the development server on `http://localhost:3000`:
|
||||
|
||||
```bash
|
||||
# npm
|
||||
npm run dev
|
||||
|
||||
# pnpm
|
||||
pnpm dev
|
||||
|
||||
# yarn
|
||||
yarn dev
|
||||
|
||||
# bun
|
||||
bun run dev
|
||||
```
|
||||
|
||||
## Production
|
||||
|
||||
Build the application for production:
|
||||
|
||||
```bash
|
||||
# npm
|
||||
npm run build
|
||||
|
||||
# pnpm
|
||||
pnpm build
|
||||
|
||||
# yarn
|
||||
yarn build
|
||||
|
||||
# bun
|
||||
bun run build
|
||||
```
|
||||
|
||||
Locally preview production build:
|
||||
|
||||
```bash
|
||||
# npm
|
||||
npm run preview
|
||||
|
||||
# pnpm
|
||||
pnpm preview
|
||||
|
||||
# yarn
|
||||
yarn preview
|
||||
|
||||
# bun
|
||||
bun run preview
|
||||
```
|
||||
|
||||
Check out the [deployment documentation](https://nuxt.com/docs/getting-started/deployment) for more information.
|
|
@ -0,0 +1,201 @@
|
|||
<script setup>
|
||||
import Taskbar from '~/components/Taskbar.vue';
|
||||
import AboutMeWindow from '~/components/AboutMeWindow.vue';
|
||||
import { ref, reactive, onMounted, onUnmounted, computed } from 'vue';
|
||||
|
||||
// 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
|
||||
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);
|
||||
};
|
||||
|
||||
// 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);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Save window states to localStorage whenever windows array changes
|
||||
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 = 400, initialHeight = 300) => {
|
||||
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
|
||||
} 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 initialX = (desktopWidth - initialWidth) / 2;
|
||||
const initialY = (desktopHeight - initialHeight) / 2 + 40; // Add taskbar height
|
||||
|
||||
const newWindow = {
|
||||
id: generateUniqueId(),
|
||||
type,
|
||||
title: titleKey, // Store the translation key here
|
||||
isVisible: true,
|
||||
isMinimized: false,
|
||||
x: initialX,
|
||||
y: initialY,
|
||||
width: initialWidth,
|
||||
height: initialHeight,
|
||||
zIndex: currentZIndex.value,
|
||||
};
|
||||
windows.push(newWindow);
|
||||
saveWindowsToLocalStorage();
|
||||
}
|
||||
};
|
||||
|
||||
// Function to close a window
|
||||
const closeWindow = (id) => {
|
||||
const index = windows.findIndex(w => w.id === id);
|
||||
if (index !== -1) {
|
||||
windows.splice(index, 1);
|
||||
saveWindowsToLocalStorage();
|
||||
}
|
||||
};
|
||||
|
||||
// Function to close all windows
|
||||
const closeAllWindows = () => {
|
||||
windows.splice(0, windows.length); // Clear the array
|
||||
saveWindowsToLocalStorage();
|
||||
};
|
||||
|
||||
// Function to minimize a window
|
||||
const minimizeWindow = (id) => {
|
||||
const windowToMinimize = windows.find(w => w.id === id);
|
||||
if (windowToMinimize) {
|
||||
windowToMinimize.isMinimized = true;
|
||||
saveWindowsToLocalStorage();
|
||||
}
|
||||
};
|
||||
|
||||
// Function to restore a minimized window
|
||||
const restoreWindow = (id) => {
|
||||
const windowToRestore = windows.find(w => w.id === id);
|
||||
if (windowToRestore) {
|
||||
windowToRestore.isMinimized = false;
|
||||
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) {
|
||||
currentZIndex.value++;
|
||||
windowToFront.zIndex = currentZIndex.value;
|
||||
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;
|
||||
saveWindowsToLocalStorage();
|
||||
}
|
||||
};
|
||||
|
||||
// Handle opening About Me window from Taskbar
|
||||
const openAboutMeWindow = () => {
|
||||
openWindow('about-me', 'about_me'); // Pass the translation key
|
||||
};
|
||||
|
||||
// Computed property for minimized windows
|
||||
const minimizedWindows = computed(() => {
|
||||
return windows.filter(win => win.isMinimized);
|
||||
});
|
||||
|
||||
// Clean up localStorage on component unmount (optional, for development)
|
||||
onUnmounted(() => {
|
||||
// localStorage.removeItem('windows');
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<Taskbar
|
||||
@open-about-me="openAboutMeWindow"
|
||||
@close-all-windows="closeAllWindows"
|
||||
:minimized-windows="minimizedWindows"
|
||||
@restore-window="restoreWindow"
|
||||
/>
|
||||
<div class="desktop-content">
|
||||
<NuxtPage />
|
||||
</div>
|
||||
|
||||
<!-- Dynamically render windows -->
|
||||
<template v-for="window in windows" :key="window.id">
|
||||
<AboutMeWindow
|
||||
v-if="window.type === 'about-me' && window.isVisible && !window.isMinimized"
|
||||
:window-data="window"
|
||||
@close="closeWindow(window.id)"
|
||||
@minimize="minimizeWindow(window.id)"
|
||||
@restore="restoreWindow(window.id)"
|
||||
@bring-to-front="bringWindowToFront(window.id)"
|
||||
@update-position="updateWindowPosition(window.id, $event.x, $event.y)"
|
||||
/>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
body {
|
||||
background-color: #008080; /* Windows 95 teal */
|
||||
background-image: url('https://picsum.photos/1920/1080'); /* Placeholder wallpaper */
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
font-family: 'Courier New', Courier, monospace;
|
||||
margin: 0;
|
||||
overflow: hidden; /* Hide scrollbars from the body */
|
||||
}
|
||||
|
||||
/* Adjust Taskbar position */
|
||||
.taskbar {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 40px;
|
||||
z-index: 1000; /* Ensure it's on top of other content */
|
||||
}
|
||||
|
||||
.desktop-content {
|
||||
/* Push content down by the height of the taskbar */
|
||||
margin-top: 40px;
|
||||
height: calc(100vh - 40px); /* Fill remaining height */
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
overflow-y: auto; /* Allow scrolling for desktop content if needed */
|
||||
background-color: transparent; /* Changed to transparent */
|
||||
border: none; /* Removed border */
|
||||
box-shadow: none; /* Removed shadow */
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,180 @@
|
|||
<template>
|
||||
<div
|
||||
v-if="windowData.isVisible && !windowData.isMinimized"
|
||||
class="window"
|
||||
:style="windowStyle"
|
||||
@mousedown="bringToFront"
|
||||
>
|
||||
<div class="title-bar" @mousedown="startDrag">
|
||||
<div class="title-bar-text">{{ t(windowData.title) }}</div>
|
||||
<div class="title-bar-controls">
|
||||
<button aria-label="Minimize" @click.stop="minimizeWindow"></button>
|
||||
<button aria-label="Close" @click.stop="closeWindow"></button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="window-body">
|
||||
<p>{{ t('hello_developer') }}</p>
|
||||
<p>{{ t('demo_ui') }}</p>
|
||||
<p>{{ t('coming_soon') }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { defineProps, defineEmits, ref, computed, watch } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const props = defineProps({
|
||||
windowData: {
|
||||
type: Object,
|
||||
required: true,
|
||||
default: () => ({}),
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits([
|
||||
'close',
|
||||
'minimize',
|
||||
'restore',
|
||||
'bring-to-front',
|
||||
'update-position',
|
||||
]);
|
||||
|
||||
// Local refs for dragging, updated by watch for initial position from props
|
||||
const x = ref(props.windowData.x);
|
||||
const y = ref(props.windowData.y);
|
||||
|
||||
let isDragging = false;
|
||||
let dragOffsetX = 0;
|
||||
let dragOffsetY = 0;
|
||||
let animationFrameId = null; // To store requestAnimationFrame ID
|
||||
|
||||
// Watch for changes in windowData.x and windowData.y (e.g., from localStorage load or app.vue updates)
|
||||
watch(() => props.windowData.x, (newX) => { x.value = newX; });
|
||||
watch(() => props.windowData.y, (newY) => { y.value = newY; });
|
||||
|
||||
const windowStyle = computed(() => ({
|
||||
left: `${x.value}px`,
|
||||
top: `${y.value}px`,
|
||||
width: `${props.windowData.width}px`,
|
||||
height: `${props.windowData.height}px`,
|
||||
zIndex: props.windowData.zIndex,
|
||||
}));
|
||||
|
||||
const startDrag = (event) => {
|
||||
isDragging = true;
|
||||
dragOffsetX = event.clientX - x.value;
|
||||
dragOffsetY = event.clientY - y.value;
|
||||
window.addEventListener('mousemove', doDrag);
|
||||
window.addEventListener('mouseup', stopDrag);
|
||||
bringToFront(); // Bring to front when dragging starts
|
||||
};
|
||||
|
||||
const doDrag = (event) => {
|
||||
if (isDragging) {
|
||||
const newX = event.clientX - dragOffsetX;
|
||||
const newY = event.clientY - dragOffsetY;
|
||||
|
||||
if (animationFrameId) {
|
||||
cancelAnimationFrame(animationFrameId);
|
||||
}
|
||||
animationFrameId = requestAnimationFrame(() => {
|
||||
x.value = newX;
|
||||
y.value = newY;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const stopDrag = () => {
|
||||
isDragging = false;
|
||||
window.removeEventListener('mousemove', doDrag);
|
||||
window.removeEventListener('mouseup', stopDrag);
|
||||
if (animationFrameId) {
|
||||
cancelAnimationFrame(animationFrameId);
|
||||
animationFrameId = null;
|
||||
}
|
||||
// Emit the final updated position
|
||||
emit('update-position', { x: x.value, y: y.value });
|
||||
};
|
||||
|
||||
const closeWindow = () => {
|
||||
emit('close');
|
||||
};
|
||||
|
||||
const minimizeWindow = () => {
|
||||
emit('minimize');
|
||||
};
|
||||
|
||||
const bringToFront = () => {
|
||||
emit('bring-to-front');
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.window {
|
||||
position: absolute;
|
||||
background-color: #c0c0c0; /* Classic Windows gray */
|
||||
border: 2px solid;
|
||||
border-color: #ffffff #808080 #808080 #ffffff; /* 3D effect */
|
||||
box-shadow: 4px 4px 0px #000; /* Simple black shadow */
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
/* z-index, top, left, width, height are set dynamically via style binding */
|
||||
}
|
||||
|
||||
.title-bar {
|
||||
background: linear-gradient(to right, #0a246a, #a6caf0); /* Classic blue gradient */
|
||||
padding: 3px 2px 3px 3px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
font-weight: bold;
|
||||
color: white;
|
||||
font-size: 0.9rem;
|
||||
cursor: grab;
|
||||
}
|
||||
|
||||
.title-bar-text {
|
||||
margin-left: 3px;
|
||||
}
|
||||
|
||||
.title-bar-controls button {
|
||||
background-color: #c0c0c0;
|
||||
border: 2px solid;
|
||||
border-color: #ffffff #000 #000 #ffffff;
|
||||
width: 16px;
|
||||
height: 14px;
|
||||
font-size: 0.8rem;
|
||||
line-height: 1;
|
||||
padding: 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.title-bar-controls button:active {
|
||||
border-color: #000 #ffffff #ffffff #000;
|
||||
}
|
||||
|
||||
.title-bar-controls button[aria-label="Minimize"]::before {
|
||||
content: '_';
|
||||
display: block;
|
||||
position: relative;
|
||||
top: -2px;
|
||||
}
|
||||
|
||||
.title-bar-controls button[aria-label="Close"]::before {
|
||||
content: 'X';
|
||||
display: block;
|
||||
position: relative;
|
||||
top: -2px;
|
||||
}
|
||||
|
||||
.window-body {
|
||||
padding: 1rem;
|
||||
flex-grow: 1;
|
||||
overflow-y: auto;
|
||||
border-top: 2px solid #000;
|
||||
background-color: #c0c0c0; /* Ensure classic gray background for body */
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,216 @@
|
|||
<template>
|
||||
<div class="taskbar">
|
||||
<div class="start-section">
|
||||
<button class="start-button" @click="toggleStartMenu">
|
||||
<span class="start-icon">🪟</span>
|
||||
<span>{{ t('start') }}</span>
|
||||
</button>
|
||||
<div v-if="showStartMenu" class="start-menu">
|
||||
<div class="menu-item" @click="openAboutMe">
|
||||
{{ t('about_me') }}
|
||||
</div>
|
||||
<!-- More menu items can go here -->
|
||||
<div class="menu-item" @click="closeAllWindows">
|
||||
{{ t('close_all_windows') }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="task-buttons">
|
||||
<button
|
||||
v-for="window in minimizedWindows"
|
||||
:key="window.id"
|
||||
class="task-button"
|
||||
@click="restoreMinimizedWindow(window.id)"
|
||||
>
|
||||
{{ t(window.title) }}
|
||||
</button>
|
||||
</div>
|
||||
<div class="right-section">
|
||||
<div class="language-toggle" @click="toggleLanguage">
|
||||
<span>{{ locale === 'zh' ? '注音' : 'ABC' }}</span>
|
||||
</div>
|
||||
<div class="clock">
|
||||
<span>{{ currentTime }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted, onUnmounted } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
const { t, locale } = useI18n();
|
||||
|
||||
const props = defineProps({
|
||||
minimizedWindows: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
});
|
||||
|
||||
const currentTime = ref(new Date().toLocaleTimeString());
|
||||
let timerId = null;
|
||||
const showStartMenu = ref(false);
|
||||
|
||||
const emit = defineEmits(['open-about-me', 'restore-window', 'close-all-windows']);
|
||||
|
||||
const toggleStartMenu = () => {
|
||||
showStartMenu.value = !showStartMenu.value;
|
||||
};
|
||||
|
||||
const openAboutMe = () => {
|
||||
emit('open-about-me');
|
||||
showStartMenu.value = false;
|
||||
};
|
||||
|
||||
const closeAllWindows = () => {
|
||||
emit('close-all-windows');
|
||||
showStartMenu.value = false;
|
||||
};
|
||||
|
||||
const toggleLanguage = () => {
|
||||
locale.value = locale.value === 'zh' ? 'en' : 'zh';
|
||||
};
|
||||
|
||||
const restoreMinimizedWindow = (id) => {
|
||||
emit('restore-window', id);
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
timerId = setInterval(() => {
|
||||
currentTime.value = new Date().toLocaleTimeString();
|
||||
}, 1000);
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
if (timerId) {
|
||||
clearInterval(timerId);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.taskbar {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
background-color: #c0c0c0; /* Classic Windows gray */
|
||||
padding: 4px 6px;
|
||||
border: 2px solid;
|
||||
border-color: #ffffff #808080 #808080 #ffffff; /* 3D effect */
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
height: 40px;
|
||||
position: relative; /* For positioning the start menu */
|
||||
}
|
||||
|
||||
.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 {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 4px 8px;
|
||||
border: 2px solid;
|
||||
border-color: #ffffff #808080 #808080 #ffffff;
|
||||
background-color: #c0c0c0;
|
||||
font-weight: bold;
|
||||
font-size: 1rem;
|
||||
cursor: pointer;
|
||||
color: black;
|
||||
}
|
||||
|
||||
.start-button:active {
|
||||
border-color: #808080 #ffffff #ffffff #808080;
|
||||
}
|
||||
|
||||
.start-icon {
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.start-menu {
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 0;
|
||||
background-color: #c0c0c0;
|
||||
border: 2px solid;
|
||||
border-color: #ffffff #808080 #808080 #ffffff;
|
||||
padding: 4px;
|
||||
min-width: 160px;
|
||||
z-index: 1001;
|
||||
}
|
||||
|
||||
.menu-item {
|
||||
padding: 4px 8px;
|
||||
cursor: pointer;
|
||||
white-space: nowrap;
|
||||
border: 1px solid transparent;
|
||||
color: black;
|
||||
}
|
||||
|
||||
.menu-item:hover {
|
||||
background-color: #000080;
|
||||
color: white;
|
||||
border-color: #000080;
|
||||
}
|
||||
|
||||
.language-toggle {
|
||||
padding: 4px 8px;
|
||||
border: 2px solid;
|
||||
border-color: #808080 #ffffff #ffffff #808080;
|
||||
font-size: 0.9rem;
|
||||
color: black;
|
||||
cursor: pointer;
|
||||
background-color: #c0c0c0;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,65 @@
|
|||
<template>
|
||||
<div class="desktop">
|
||||
<div class="icon">
|
||||
<div class="icon-image">[PC]</div>
|
||||
<div class="icon-label">{{ t('my_computer') }}</div>
|
||||
</div>
|
||||
<div class="icon">
|
||||
<div class="icon-image">[BIN]</div>
|
||||
<div class="icon-label">{{ t('recycle_bin') }}</div>
|
||||
</div>
|
||||
<div class="icon">
|
||||
<div class="icon-image">[>]_</div>
|
||||
<div class="icon-label">{{ t('terminal') }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { useI18n } from 'vue-i18n'; // Import useI18n
|
||||
|
||||
const { t } = useI18n(); // Use useI18n composable
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.desktop {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 2rem;
|
||||
padding: 2rem;
|
||||
align-content: flex-start;
|
||||
background-color: transparent; /* Ensure desktop background is transparent */
|
||||
}
|
||||
|
||||
.icon {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
width: 100px;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.icon:hover .icon-image {
|
||||
background-color: #e0e0e0;
|
||||
}
|
||||
|
||||
.icon-image {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
border: 2px solid black;
|
||||
background-color: white;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
font-size: 1.5rem;
|
||||
font-weight: bold;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.icon-label {
|
||||
background-color: white;
|
||||
padding: 2px 4px;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,6 @@
|
|||
// @ts-check
|
||||
import withNuxt from './.nuxt/eslint.config.mjs'
|
||||
|
||||
export default withNuxt(
|
||||
// Your custom configs here
|
||||
)
|
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"start": "Start",
|
||||
"about_me": "About Me",
|
||||
"hello_developer": "Hello! I'm a developer passionate about creating unique web experiences.",
|
||||
"demo_ui": "This is a Windows 95 inspired UI.",
|
||||
"coming_soon": "More content coming soon!",
|
||||
"my_computer": "My Computer",
|
||||
"recycle_bin": "Recycle Bin",
|
||||
"terminal": "Terminal",
|
||||
"close_all_windows": "Close All Windows"
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"start": "開始",
|
||||
"about_me": "關於我",
|
||||
"hello_developer": "哈囉!我是一位熱衷於創造獨特網路體驗的開發者。",
|
||||
"demo_ui": "這是一個 Windows 95 風格的使用者介面展示。",
|
||||
"coming_soon": "更多內容即將推出!",
|
||||
"my_computer": "我的電腦",
|
||||
"recycle_bin": "資源回收筒",
|
||||
"terminal": "終端機",
|
||||
"close_all_windows": "關閉全視窗"
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
// https://nuxt.com/docs/api/configuration/nuxt-config
|
||||
|
||||
export default defineNuxtConfig({
|
||||
compatibilityDate: '2025-07-15',
|
||||
devtools: { enabled: true },
|
||||
|
||||
modules: [
|
||||
'@nuxt/ui',
|
||||
'@nuxt/test-utils',
|
||||
'@nuxt/scripts',
|
||||
'@nuxt/image',
|
||||
'@nuxt/eslint',
|
||||
'@nuxt/content',
|
||||
'@nuxtjs/i18n',
|
||||
],
|
||||
i18n: {
|
||||
locales: [
|
||||
{ code: 'en', file: 'en.json', name: 'English' },
|
||||
{ code: 'zh', file: 'zh.json', name: '繁體中文' }
|
||||
],
|
||||
langDir: 'locales', // 你的語言文件夾名稱
|
||||
defaultLocale: 'zh', // 預設語言
|
||||
strategy: 'no_prefix', // 或者 'prefix_except_default'
|
||||
},
|
||||
build: {
|
||||
transpile: ['@nuxtjs/i18n']
|
||||
}
|
||||
})
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,27 @@
|
|||
{
|
||||
"name": "nuxt-app",
|
||||
"type": "module",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build": "nuxt build",
|
||||
"dev": "nuxt dev",
|
||||
"generate": "nuxt generate",
|
||||
"preview": "nuxt preview",
|
||||
"postinstall": "nuxt prepare"
|
||||
},
|
||||
"dependencies": {
|
||||
"@nuxt/content": "^2.7.2",
|
||||
"@nuxt/eslint": "^1.9.0",
|
||||
"@nuxt/image": "^1.11.0",
|
||||
"@nuxt/scripts": "^0.11.13",
|
||||
"@nuxt/test-utils": "^3.19.2",
|
||||
"@nuxt/ui": "^3.3.3",
|
||||
"@nuxtjs/i18n": "^10.1.0",
|
||||
"@unhead/vue": "^2.0.14",
|
||||
"eslint": "^9.35.0",
|
||||
"nuxt": "^4.1.1",
|
||||
"typescript": "^5.9.2",
|
||||
"vue": "^3.5.21",
|
||||
"vue-router": "^4.5.1"
|
||||
}
|
||||
}
|
Binary file not shown.
After Width: | Height: | Size: 4.2 KiB |
|
@ -0,0 +1,2 @@
|
|||
User-Agent: *
|
||||
Disallow:
|
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
// https://nuxt.com/docs/guide/concepts/typescript
|
||||
"files": [],
|
||||
"references": [
|
||||
{
|
||||
"path": "./.nuxt/tsconfig.app.json"
|
||||
},
|
||||
{
|
||||
"path": "./.nuxt/tsconfig.server.json"
|
||||
},
|
||||
{
|
||||
"path": "./.nuxt/tsconfig.shared.json"
|
||||
},
|
||||
{
|
||||
"path": "./.nuxt/tsconfig.node.json"
|
||||
}
|
||||
]
|
||||
}
|
Loading…
Reference in New Issue