Files
OCS_service/app/components/layout/AppHeader.vue
T
2026-05-23 13:45:58 +08:00

128 lines
3.5 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
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.
<!-- app/components/layout/AppHeader.vue - 顶部导航和页面切换入口 -->
<script lang="ts" setup>
import {
Activity,
LayoutDashboard,
LogIn,
LogOut,
Moon,
Search,
Sun
} from "lucide-vue-next";
import { Button } from "@/components/ui/button";
import { useAuthStore } from "@/stores/auth";
/** 使用 VueUse 的 useColorMode 切换 dark/lightclass 会挂到 <html> 上 */
const colorMode = useColorMode();
const isDark = computed(() => colorMode.value === "dark");
const toggleColorMode = () => {
colorMode.value = isDark.value ? "light" : "dark";
};
const authStore = useAuthStore();
/** 顶部导航项,icon 使用 lucide,和项目里 shadcn 的图标体系保持一致 */
const navItems = [
{
label: "答题",
to: "/",
icon: Search
},
{
label: "Dashboard",
to: "/dashboard",
icon: LayoutDashboard
}
];
const route = useRoute();
/** 当前路由高亮,Dashboard 的子路径也归到 Dashboard 导航项 */
const isActive = (to: string) => {
if (to === "/") return route.path === "/";
return route.path.startsWith(to);
};
const handleSignOut = async () => {
await authStore.signOut();
await navigateTo("/login");
};
</script>
<template>
<header
class="border-b bg-background/95 backdrop-blur supports-backdrop-filter:bg-background/80"
>
<div
class="mx-auto flex min-h-16 w-full max-w-7xl items-center justify-between gap-4 px-4 md:px-6"
>
<NuxtLink to="/" class="flex min-w-0 items-center gap-3">
<span
class="flex size-9 shrink-0 items-center justify-center rounded-2xl bg-primary text-primary-foreground"
>
<Activity class="size-5" />
</span>
<span class="min-w-0">
<span class="block truncate text-base font-semibold text-foreground">
OCS AI 答题服务
</span>
<span class="block truncate text-xs text-muted-foreground">
Nuxt API Runtime
</span>
</span>
</NuxtLink>
<nav class="flex shrink-0 items-center gap-1 overflow-x-auto">
<Button
v-for="item in navItems"
:key="item.to"
as-child
:variant="isActive(item.to) ? 'secondary' : 'ghost'"
size="sm"
>
<NuxtLink :to="item.to" class="gap-2">
<component :is="item.icon" class="size-4" />
<span>{{ item.label }}</span>
</NuxtLink>
</Button>
<!-- 深色/浅色主题切换按钮 -->
<Button
variant="ghost"
size="icon"
aria-label="切换主题"
@click="toggleColorMode"
>
<Sun v-if="isDark" class="size-4" />
<Moon v-else class="size-4" />
</Button>
<!-- 已登录显示邮箱和退出按钮未登录显示登录链接 -->
<template v-if="authStore.isOnline">
<span class="max-sm:hidden text-xs text-muted-foreground">
{{ authStore.user!.email }}
</span>
<Button
variant="ghost"
size="icon"
aria-label="退出登录"
@click="handleSignOut"
>
<LogOut class="size-4" />
</Button>
</template>
<template v-else>
<Button as-child variant="ghost" size="sm">
<NuxtLink to="/login" class="gap-2">
<LogIn class="size-4" />
<span>登录</span>
</NuxtLink>
</Button>
</template>
</nav>
</div>
</header>
</template>