refactor(frontend): migrate from Vite to Nuxt SPA
Replace Vite and Vue Router with Nuxt framework Update Docker, build scripts, and env vars for Nuxt generate
This commit is contained in:
191
frontend/app.vue
Normal file
191
frontend/app.vue
Normal file
@@ -0,0 +1,191 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, onUnmounted, ref } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import AppShell from './src/components/AppShell.vue';
|
||||
import {
|
||||
iconAction,
|
||||
iconAdmin,
|
||||
iconArtifact,
|
||||
iconAutomation,
|
||||
iconChecklist,
|
||||
iconClothes,
|
||||
iconDish,
|
||||
iconDreamIsland,
|
||||
iconEvent,
|
||||
iconHabitat,
|
||||
iconHome,
|
||||
iconItem,
|
||||
iconLife,
|
||||
iconPokemon,
|
||||
iconRecipe,
|
||||
type AppIcon
|
||||
} from './src/icons';
|
||||
import { getCurrentLocale, loadSystemWordings, onLocaleChange, setCurrentLocale } from './src/i18n';
|
||||
import { api, getAuthToken, onAuthTokenChange, setAuthToken, type AuthUser, type Language } from './src/services/api';
|
||||
|
||||
const { t, locale } = useI18n();
|
||||
const router = useRouter();
|
||||
const currentUser = ref<AuthUser | null>(null);
|
||||
const languages = ref<Language[]>([
|
||||
{ code: 'en', name: 'English', enabled: true, isDefault: true, sortOrder: 10 },
|
||||
{ code: 'zh-CN', name: '简体中文', enabled: true, isDefault: false, sortOrder: 20 }
|
||||
]);
|
||||
let removeAuthListener: (() => void) | null = null;
|
||||
let removeLocaleListener: (() => void) | null = null;
|
||||
|
||||
type NavBadge = {
|
||||
label: string;
|
||||
tone?: 'info' | 'success' | 'warning' | 'danger' | 'neutral';
|
||||
};
|
||||
|
||||
type NavLinkItem = {
|
||||
label: string;
|
||||
to: string;
|
||||
icon?: AppIcon;
|
||||
badge?: NavBadge;
|
||||
};
|
||||
|
||||
type NavGroupItem = {
|
||||
key: string;
|
||||
label: string;
|
||||
icon?: AppIcon;
|
||||
children: NavLinkItem[];
|
||||
};
|
||||
|
||||
type NavItem = NavLinkItem | NavGroupItem;
|
||||
|
||||
function inDevBadge(): NavBadge {
|
||||
return { label: t('common.inDev'), tone: 'info' };
|
||||
}
|
||||
|
||||
function can(permissionKey: string) {
|
||||
return currentUser.value?.permissions.includes(permissionKey) === true;
|
||||
}
|
||||
|
||||
const navItems = computed<NavItem[]>(() => {
|
||||
const items: NavItem[] = [
|
||||
{ label: t('nav.home'), to: '/', icon: iconHome },
|
||||
{
|
||||
key: 'pokedex',
|
||||
label: t('nav.pokedex'),
|
||||
icon: iconPokemon,
|
||||
children: [
|
||||
{ label: t('nav.mainGame'), to: '/pokemon', icon: iconPokemon },
|
||||
{ label: t('nav.event'), to: '/event-pokemon', icon: iconEvent }
|
||||
]
|
||||
},
|
||||
{
|
||||
key: 'habitat-dex',
|
||||
label: t('nav.habitatDex'),
|
||||
icon: iconHabitat,
|
||||
children: [
|
||||
{ label: t('nav.mainGame'), to: '/habitats', icon: iconHabitat },
|
||||
{ label: t('nav.event'), to: '/event-habitats', icon: iconEvent }
|
||||
]
|
||||
},
|
||||
{
|
||||
key: 'collections',
|
||||
label: t('nav.collections'),
|
||||
icon: iconItem,
|
||||
children: [
|
||||
{ label: t('nav.mainGame'), to: '/items', icon: iconItem },
|
||||
{ label: t('nav.event'), to: '/event-items', icon: iconEvent },
|
||||
{ label: t('nav.ancientArtifacts'), to: '/ancient-artifacts', icon: iconArtifact }
|
||||
]
|
||||
},
|
||||
{ label: t('nav.recipes'), to: '/recipes', icon: iconRecipe },
|
||||
{ label: t('nav.automation'), to: '/automation', icon: iconAutomation, badge: inDevBadge() },
|
||||
{ label: t('nav.dish'), to: '/dish', icon: iconDish },
|
||||
{ label: t('nav.events'), to: '/events', icon: iconEvent, badge: inDevBadge() },
|
||||
{ label: t('nav.actions'), to: '/actions', icon: iconAction, badge: inDevBadge() },
|
||||
{ label: t('nav.dreamIsland'), to: '/dream-island', icon: iconDreamIsland, badge: inDevBadge() },
|
||||
{ label: t('nav.clothes'), to: '/clothes', icon: iconClothes, badge: inDevBadge() },
|
||||
{ label: t('nav.checklist'), to: '/checklist', icon: iconChecklist },
|
||||
{ label: t('nav.life'), to: '/life', icon: iconLife }
|
||||
];
|
||||
|
||||
if (can('admin.access')) {
|
||||
items.push({ label: t('nav.admin'), to: '/admin', icon: iconAdmin });
|
||||
}
|
||||
|
||||
return items;
|
||||
});
|
||||
|
||||
async function loadCurrentUser() {
|
||||
if (!getAuthToken()) {
|
||||
currentUser.value = null;
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await api.me();
|
||||
currentUser.value = response.user;
|
||||
} catch {
|
||||
currentUser.value = null;
|
||||
setAuthToken(null);
|
||||
}
|
||||
}
|
||||
|
||||
async function logout() {
|
||||
try {
|
||||
await api.logout();
|
||||
} catch {
|
||||
// The local session is cleared even when the server session is already gone.
|
||||
}
|
||||
|
||||
currentUser.value = null;
|
||||
setAuthToken(null);
|
||||
await router.push('/');
|
||||
}
|
||||
|
||||
async function loadLanguages() {
|
||||
try {
|
||||
const loadedLanguages = await api.languages();
|
||||
if (loadedLanguages.length) {
|
||||
languages.value = loadedLanguages;
|
||||
}
|
||||
|
||||
if (!languages.value.some((language) => language.code === getCurrentLocale() && language.enabled)) {
|
||||
setCurrentLocale('en');
|
||||
}
|
||||
|
||||
await loadSystemWordings(getCurrentLocale());
|
||||
} catch {
|
||||
// Keep the built-in language list when the API is not ready yet.
|
||||
}
|
||||
}
|
||||
|
||||
async function updateLocale(value: string) {
|
||||
await loadSystemWordings(value);
|
||||
setCurrentLocale(value);
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
void loadLanguages();
|
||||
void loadCurrentUser();
|
||||
removeAuthListener = onAuthTokenChange(() => {
|
||||
void loadCurrentUser();
|
||||
});
|
||||
removeLocaleListener = onLocaleChange(() => {
|
||||
void loadLanguages();
|
||||
});
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
removeAuthListener?.();
|
||||
removeLocaleListener?.();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<AppShell
|
||||
:current-user="currentUser"
|
||||
:languages="languages"
|
||||
:locale="locale"
|
||||
:nav-items="navItems"
|
||||
@logout="logout"
|
||||
@update:locale="updateLocale"
|
||||
>
|
||||
<NuxtPage :key="locale" />
|
||||
</AppShell>
|
||||
</template>
|
||||
Reference in New Issue
Block a user