- Created backend structure with Go, including main application logic and API endpoints. - Added Docker support for both development and production environments. - Introduced frontend using Nuxt 3 with Tailwind CSS for styling. - Included configuration files for Docker and environment variables. - Established basic documentation for contributing, development, and deployment processes. - Set up .gitignore and .dockerignore files to manage ignored files in the repository.
104 lines
3.0 KiB
Vue
104 lines
3.0 KiB
Vue
<template>
|
||
<div class="h-screen flex flex-col bg-base-100 overflow-hidden">
|
||
<header class="navbar bg-base-200/80 backdrop-blur px-4 gap-2 shrink-0">
|
||
<NuxtLink to="/" class="text-lg font-semibold hover:opacity-80">{{ title }}</NuxtLink>
|
||
<div class="flex-1" />
|
||
<NuxtLink v-if="!isLogin" to="/" class="btn btn-ghost btn-sm">Map</NuxtLink>
|
||
<NuxtLink v-if="!isLogin" to="/profile" class="btn btn-ghost btn-sm">Profile</NuxtLink>
|
||
<NuxtLink v-if="!isLogin && isAdmin" to="/admin" class="btn btn-ghost btn-sm">Admin</NuxtLink>
|
||
<button
|
||
v-if="!isLogin && me"
|
||
type="button"
|
||
class="btn btn-ghost btn-sm btn-outline"
|
||
@click="doLogout"
|
||
>
|
||
Logout
|
||
</button>
|
||
<label class="swap swap-rotate btn btn-ghost btn-sm">
|
||
<input type="checkbox" v-model="dark" @change="toggleTheme" />
|
||
<span class="swap-off">☀️</span>
|
||
<span class="swap-on">🌙</span>
|
||
</label>
|
||
<span v-if="live" class="badge badge-success badge-sm">Live</span>
|
||
</header>
|
||
<main class="flex-1 min-h-0 relative">
|
||
<slot />
|
||
</main>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
const route = useRoute()
|
||
const router = useRouter()
|
||
const THEME_KEY = 'hnh-map-theme'
|
||
|
||
function getInitialDark(): boolean {
|
||
if (import.meta.client) {
|
||
const stored = localStorage.getItem(THEME_KEY)
|
||
if (stored === 'dark') return true
|
||
if (stored === 'light') return false
|
||
return window.matchMedia('(prefers-color-scheme: dark)').matches
|
||
}
|
||
return false
|
||
}
|
||
|
||
const title = ref('HnH Map')
|
||
const dark = ref(false)
|
||
const live = ref(false)
|
||
const me = ref<{ username?: string; auths?: string[] } | null>(null)
|
||
|
||
const { isLoginPath } = useAppPaths()
|
||
const isLogin = computed(() => isLoginPath(route.path))
|
||
const isAdmin = computed(() => !!me.value?.auths?.includes('admin'))
|
||
|
||
async function loadMe() {
|
||
if (isLogin.value) return
|
||
try {
|
||
me.value = await useMapApi().me()
|
||
} catch {
|
||
me.value = null
|
||
}
|
||
}
|
||
|
||
async function loadConfig() {
|
||
if (isLogin.value) return
|
||
try {
|
||
const config = await useMapApi().getConfig()
|
||
if (config?.title) title.value = config.title
|
||
} catch (_) {}
|
||
}
|
||
|
||
onMounted(() => {
|
||
dark.value = getInitialDark()
|
||
const html = document.documentElement
|
||
html.setAttribute('data-theme', dark.value ? 'dark' : 'light')
|
||
})
|
||
|
||
watch(
|
||
() => route.path,
|
||
(path) => {
|
||
if (!isLoginPath(path)) loadMe().then(loadConfig)
|
||
},
|
||
{ immediate: true }
|
||
)
|
||
|
||
function toggleTheme() {
|
||
const html = document.documentElement
|
||
if (dark.value) {
|
||
html.setAttribute('data-theme', 'dark')
|
||
localStorage.setItem(THEME_KEY, 'dark')
|
||
} else {
|
||
html.setAttribute('data-theme', 'light')
|
||
localStorage.setItem(THEME_KEY, 'light')
|
||
}
|
||
}
|
||
|
||
async function doLogout() {
|
||
await useMapApi().logout()
|
||
await router.push('/login')
|
||
me.value = null
|
||
}
|
||
|
||
defineExpose({ setLive: (v: boolean) => { live.value = v } })
|
||
</script>
|