windows/app/pages/500.vue

696 lines
14 KiB
Vue
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="error-page" :class="{ 'dark-mode': isDarkMode }">
<!-- macOS Style Background -->
<div class="background-image">
<div class="blur-overlay"></div>
<div class="gradient-overlay"></div>
</div>
<!-- Glass Layer -->
<div class="glass-layer"></div>
<!-- Windows-style Error Window -->
<div class="error-window">
<!-- Title Bar -->
<div class="title-bar">
<div class="title-bar-content">
<div class="title-bar-icon"></div>
<div class="title-bar-text">{{ $t('error.title') }}</div>
</div>
<div class="title-bar-controls">
<button class="control-btn close-btn" @click="goHome">
<span class="control-btn-icon">×</span>
</button>
</div>
</div>
<!-- Window Content -->
<div class="window-content">
<div class="error-content">
<!-- Error Icon -->
<div class="error-icon">
<div class="error-code">500</div>
<div class="error-symbol"></div>
</div>
<!-- Error Message -->
<div class="error-message">
<h1 class="error-title">{{ $t('error.serverError') }}</h1>
<p class="error-description">{{ $t('error.serverErrorDescription') }}</p>
</div>
<!-- Action Button -->
<div class="error-actions">
<button class="action-btn primary" @click="goHome">
<span class="btn-icon">🏠</span>
{{ $t('error.goHome') }}
</button>
</div>
</div>
</div>
</div>
<!-- Status Bar -->
<div class="status-bar">
<div class="status-left">
<!-- Empty for now -->
</div>
<div class="status-center">
<!-- Empty for now -->
</div>
<div class="status-right">
<!-- Language Toggle -->
<div class="language-switcher-wrapper" ref="languageSwitcherWrapper">
<button @click="toggleLanguageMenu" class="language-switcher">
{{ currentLanguageDisplay }}
</button>
<div v-if="isLanguageMenuOpen" class="language-menu">
<ul>
<li v-for="lang in availableLanguages" :key="lang.key" @click="selectLanguage(lang.key as 'en' | 'zh')">
<span class="checkmark" :style="{ visibility: locale === lang.key ? 'visible' : 'hidden' }">✓</span>
<span>{{ lang.label }}</span>
</li>
</ul>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, computed, onMounted, onUnmounted, watch } from 'vue';
import { useRouter } from 'vue-router';
import { useI18n } from 'vue-i18n';
import { useSettingsStore } from '../../stores/settings';
const router = useRouter();
const { t, locale, setLocale } = useI18n();
const settingsStore = useSettingsStore();
// Reactive state
const currentTime = ref('');
const requestId = ref('');
const userAgent = ref('');
// --- Language Switcher Logic ---
const isLanguageMenuOpen = ref(false);
const languageSwitcherWrapper = ref<HTMLElement | null>(null);
const availableLanguages = computed(() => [
{ key: 'en', label: 'English', display: 'EN' },
{ key: 'zh', label: '繁體中文', display: '注' },
]);
const currentLanguageDisplay = computed(() => {
const current = availableLanguages.value.find(lang => lang.key === locale.value);
return current?.display || '注';
});
// Theme state from settings store
const isDarkMode = computed(() => settingsStore.theme === 'dark');
// Computed properties
const goHome = () => {
router.push('/');
};
const refreshPage = () => {
window.location.reload();
};
const reportError = () => {
// In a real application, this would open an error reporting dialog
console.log('Error reported');
alert(t('error.reportSent'));
};
// Language switcher functions
function toggleLanguageMenu() {
isLanguageMenuOpen.value = !isLanguageMenuOpen.value;
}
function selectLanguage(lang: 'en' | 'zh') {
setLocale(lang);
isLanguageMenuOpen.value = false;
}
const handleClickOutside = (event: MouseEvent) => {
if (languageSwitcherWrapper.value && !languageSwitcherWrapper.value.contains(event.target as Node)) {
isLanguageMenuOpen.value = false;
}
};
// Initialize data
onMounted(() => {
currentTime.value = new Date().toLocaleString();
requestId.value = Math.random().toString(36).substr(2, 9);
userAgent.value = navigator.userAgent.substring(0, 50) + '...';
});
onUnmounted(() => {
document.removeEventListener('click', handleClickOutside);
});
// Watch for language menu state
watch(isLanguageMenuOpen, (isOpen) => {
if (isOpen) {
document.addEventListener('click', handleClickOutside);
} else {
document.removeEventListener('click', handleClickOutside);
}
});
// Set page title
useHead({
title: t('error.title')
});
</script>
<style scoped>
.error-page {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
display: flex;
align-items: center;
justify-content: center;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
overflow: hidden;
}
.background-image {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: linear-gradient(135deg,
#f8fafc 0%,
#e2e8f0 25%,
#cbd5e1 50%,
#94a3b8 75%,
#64748b 100%);
background-size: 400% 400%;
animation: gradient-shift 15s ease infinite;
z-index: 0;
}
.glass-layer {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
z-index: 1;
}
@keyframes gradient-shift {
0% { background-position: 0% 50%; }
50% { background-position: 100% 50%; }
100% { background-position: 0% 50%; }
}
.blur-overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
}
.gradient-overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: linear-gradient(
180deg,
rgba(0, 0, 0, 0.1) 0%,
rgba(0, 0, 0, 0.3) 50%,
rgba(0, 0, 0, 0.6) 100%
);
}
.error-window {
position: relative;
z-index: 2;
width: 90%;
max-width: 600px;
background: var(--window-background);
border: 1px solid var(--window-border-color);
border-radius: var(--rounded-window);
box-shadow: var(--shadow-window);
overflow: hidden;
animation: window-appear 0.5s ease-out;
}
@keyframes window-appear {
from {
opacity: 0;
transform: scale(0.9) translateY(20px);
}
to {
opacity: 1;
transform: scale(1) translateY(0);
}
}
.title-bar {
display: flex;
align-items: center;
justify-content: space-between;
background: var(--title-bar-background);
color: var(--title-bar-text-color);
padding: 8px 12px;
border-bottom: 1px solid var(--window-border-color);
}
.title-bar-content {
display: flex;
align-items: center;
gap: 8px;
}
.title-bar-icon {
font-size: 16px;
}
.title-bar-text {
font-weight: 600;
font-size: 14px;
}
.title-bar-controls {
display: flex;
gap: 4px;
}
.control-btn {
width: 20px;
height: 20px;
border: none;
border-radius: var(--rounded-control-btn);
background: var(--control-btn-close-bg);
color: white;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.2s ease;
font-size: 12px;
font-weight: bold;
}
.control-btn:hover {
transform: scale(1.1);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
}
.window-content {
padding: 32px;
color: var(--content-text-color);
}
.error-content {
text-align: center;
}
.error-icon {
margin-bottom: 24px;
position: relative;
}
.error-code {
font-size: 72px;
font-weight: 900;
color: var(--control-btn-close-bg);
text-shadow: 0 4px 8px rgba(0, 0, 0, 0.3);
margin-bottom: 8px;
}
.error-symbol {
font-size: 48px;
position: absolute;
top: -10px;
right: 50%;
transform: translateX(50%);
animation: bounce 2s infinite;
}
@keyframes bounce {
0%, 20%, 50%, 80%, 100% {
transform: translateX(50%) translateY(0);
}
40% {
transform: translateX(50%) translateY(-10px);
}
60% {
transform: translateX(50%) translateY(-5px);
}
}
.error-message {
margin-bottom: 32px;
}
.error-title {
font-size: 28px;
font-weight: 700;
margin-bottom: 12px;
color: var(--content-text-color);
}
.error-description {
font-size: 16px;
line-height: 1.6;
color: var(--content-text-color);
opacity: 0.8;
max-width: 400px;
margin: 0 auto;
}
.error-actions {
display: flex;
justify-content: center;
margin-bottom: 32px;
}
.action-btn {
display: flex;
align-items: center;
gap: 8px;
padding: 12px 20px;
border: none;
border-radius: var(--rounded-button);
font-size: 14px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
text-decoration: none;
min-width: 120px;
justify-content: center;
}
.action-btn.primary {
background: var(--control-btn-maximize-bg);
color: white;
box-shadow: var(--shadow-button);
}
.action-btn.primary:hover {
transform: translateY(-2px);
box-shadow: 0 6px 20px rgba(76, 209, 55, 0.4);
}
.action-btn.secondary {
background: var(--taskbar-item-background);
color: var(--content-text-color);
border: 1px solid var(--window-border-color);
}
.action-btn.secondary:hover {
background: var(--taskbar-item-background-hover);
transform: translateY(-1px);
}
.btn-icon {
font-size: 16px;
}
.status-bar {
position: absolute;
top: 0;
left: 0;
right: 0;
height: 22px;
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 20px;
background: var(--taskbar-background);
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
color: var(--taskbar-item-text-color);
font-size: 12px;
font-weight: 500;
z-index: 2;
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
}
.status-left,
.status-center,
.status-right {
display: flex;
align-items: center;
gap: 8px;
}
.language-switcher-wrapper {
position: relative;
}
.language-switcher {
background: none;
border: none;
color: var(--taskbar-item-text-color);
font-size: 12px;
font-weight: 500;
cursor: pointer;
padding: 0 4px;
width: 24px;
height: 22px;
text-align: center;
line-height: 22px;
display: flex;
align-items: center;
justify-content: center;
}
.language-menu {
position: absolute;
top: calc(100% + 4px);
right: 0;
width: 160px;
background-color: var(--start-menu-background);
border: 1px solid var(--start-menu-border-color);
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
padding: 4px;
z-index: 10001;
}
.language-menu ul {
list-style: none;
padding: 0;
margin: 0;
}
.language-menu li {
display: flex;
align-items: center;
padding: 4px 8px;
border-radius: 4px;
cursor: pointer;
transition: background-color 0.15s ease-in-out;
}
.language-menu li:hover {
background-color: var(--taskbar-item-background-hover);
}
.checkmark {
width: 16px;
text-align: center;
margin-right: 4px;
}
/* Responsive Design */
@media (max-width: 768px) {
.error-window {
width: 95%;
margin: 20px;
}
.window-content {
padding: 24px;
}
.error-code {
font-size: 56px;
}
.error-title {
font-size: 24px;
}
.action-btn {
width: 100%;
max-width: 200px;
}
.status-bar {
padding: 8px 16px;
font-size: 0.8rem;
}
}
/* Light mode (default) */
.background-image {
background: linear-gradient(135deg,
#f8fafc 0%,
#e2e8f0 25%,
#cbd5e1 50%,
#94a3b8 75%,
#64748b 100%);
}
.glass-layer {
background: rgba(255, 255, 255, 0.1);
}
.error-window {
background: rgba(255, 255, 255, 0.95);
border: 1px solid rgba(255, 255, 255, 0.2);
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
}
.title-bar {
background: rgba(255, 255, 255, 0.9);
color: #1f2937;
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
}
.window-content {
color: #1f2937;
}
.error-title {
color: #1f2937;
}
.error-description {
color: #4b5563;
}
.action-btn.primary {
background: #10b981;
color: white;
}
.action-btn.primary:hover {
background: #059669;
box-shadow: 0 6px 20px rgba(16, 185, 129, 0.4);
}
.action-btn.secondary {
background: rgba(0, 0, 0, 0.05);
color: #374151;
border: 1px solid rgba(0, 0, 0, 0.1);
}
.action-btn.secondary:hover {
background: rgba(0, 0, 0, 0.1);
}
.details-toggle {
color: #374151;
}
.details-toggle:hover {
background: rgba(0, 0, 0, 0.05);
}
.details-content {
background: rgba(0, 0, 0, 0.05);
border: 1px solid rgba(0, 0, 0, 0.1);
}
.details-item {
color: #4b5563;
}
/* Dark mode adjustments */
.dark-mode .background-image {
background: linear-gradient(135deg,
#0f172a 0%,
#1e293b 25%,
#334155 50%,
#475569 75%,
#64748b 100%);
}
.dark-mode .glass-layer {
background: rgba(0, 0, 0, 0.1);
}
.dark-mode .error-window {
background: rgba(15, 23, 42, 0.95);
border: 1px solid rgba(255, 255, 255, 0.1);
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
}
.dark-mode .title-bar {
background: rgba(15, 23, 42, 0.9);
color: #f1f5f9;
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
}
.dark-mode .window-content {
color: #f1f5f9;
}
.dark-mode .error-title {
color: #f1f5f9;
}
.dark-mode .error-description {
color: #cbd5e1;
}
.dark-mode .action-btn.primary {
background: #10b981;
color: white;
}
.dark-mode .action-btn.primary:hover {
background: #059669;
box-shadow: 0 6px 20px rgba(16, 185, 129, 0.4);
}
.dark-mode .action-btn.secondary {
background: rgba(255, 255, 255, 0.1);
color: #e2e8f0;
border: 1px solid rgba(255, 255, 255, 0.2);
}
.dark-mode .action-btn.secondary:hover {
background: rgba(255, 255, 255, 0.2);
}
.dark-mode .details-toggle {
color: #e2e8f0;
}
.dark-mode .details-toggle:hover {
background: rgba(255, 255, 255, 0.1);
}
.dark-mode .details-content {
background: rgba(255, 255, 255, 0.1);
border: 1px solid rgba(255, 255, 255, 0.2);
}
.dark-mode .details-item {
color: #cbd5e1;
}
</style>