初始化

需要注意的是饥荒不够完善:动作、手持工具、细节交互、系统逻辑
This commit is contained in:
李琦
2026-08-15 07:24:34 +08:00
parent aa37fc4119
commit cd1ab90013
150 changed files with 40801 additions and 271 deletions

290
src/components/NavBar.vue Normal file
View File

@@ -0,0 +1,290 @@
<script setup>
// 顶部导航栏站点名、主导航SVG 图标在上 + 文字在下的竖排布局)、消息入口(带未读角标)、积分、用户菜单
// 小屏≤900px不再收进汉堡抽屉而是隐藏文字仅留图标title 悬浮提示文字)
import { computed } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { useUserStore } from '../stores/user'
import { useThemeStore } from '../stores/theme'
import { useChatStore } from '../stores/chat'
import NavIcon from './NavIcon.vue'
import PixelAvatar from './PixelAvatar.vue'
const route = useRoute()
const router = useRouter()
const userStore = useUserStore()
const themeStore = useThemeStore()
const chatStore = useChatStore()
// 主导航配置icon 为 NavIcon 组件里的 SVG 名称)
const navs = [
{ name: 'lobby', path: '/', label: '大厅', icon: 'lobby' },
{ name: 'center', path: '/center', label: '游戏中心', icon: 'center' },
{ name: 'battle', path: '/battle', label: '联机对战', icon: 'battle' },
{ name: 'signin', path: '/signin', label: '签到中心', icon: 'signin' },
{ name: 'vip', path: '/vip', label: 'VIP', icon: 'vip' },
{ name: 'rank', path: '/rank', label: '排行榜', icon: 'rank' },
]
const points = computed(() => userStore.user?.points ?? 0)
// 未读私聊 + 待处理好友申请合计(角标)
const msgBadge = computed(() => {
const n = chatStore.unread + chatStore.reqBadge
return n > 99 ? '99+' : n
})
// 当前路由是否命中某导航
function isActive(path) {
return route.path === path || (path !== '/' && route.path.startsWith(path))
}
// 退出登录
function logout() {
userStore.logout()
router.push('/login')
}
</script>
<template>
<header class="nav">
<div class="nav-inner">
<router-link to="/" class="brand">
<span class="brand-icon"><NavIcon name="lobby" :size="26" /></span>
<span class="brand-name">{{ themeStore.siteName }}</span>
</router-link>
<!-- 主导航图标在上文字在下小屏自动隐藏文字 -->
<nav class="links">
<router-link
v-for="n in navs"
:key="n.name"
:to="n.path"
class="link"
:class="{ active: isActive(n.path) }"
:title="n.label"
>
<NavIcon :name="n.icon" :size="22" class="link-icon" />
<span class="link-text">{{ n.label }}</span>
</router-link>
<router-link
to="/chat"
class="link msg-link"
:class="{ active: route.path.startsWith('/chat') }"
title="消息"
>
<NavIcon name="chat" :size="22" class="link-icon" />
<span class="link-text">消息</span>
<em v-if="chatStore.unread + chatStore.reqBadge > 0" class="msg-badge">{{ msgBadge }}</em>
</router-link>
</nav>
<div class="right">
<span class="points" title="当前积分"><NavIcon name="coin" :size="17" class="coin-icon" /> {{ points }}</span>
<router-link v-if="userStore.isAdmin" to="/admin" class="link tool-link" :class="{ active: route.path.startsWith('/admin') }" title="后台管理">
<NavIcon name="admin" :size="22" class="link-icon" />
<span class="link-text">后台</span>
</router-link>
<router-link to="/profile" class="user" title="个人中心">
<span class="avatar"><PixelAvatar :code="userStore.user?.avatar || 'px:01'" :size="30" /></span>
<span class="nickname">{{ userStore.user?.nickname }}</span>
</router-link>
<button class="link tool-link logout-link" title="退出登录" @click="logout">
<NavIcon name="logout" :size="22" class="link-icon" />
<span class="link-text">退出</span>
</button>
</div>
</div>
</header>
</template>
<style scoped>
.nav {
position: sticky;
top: 0;
z-index: 100;
background: color-mix(in srgb, var(--bg-panel) 88%, transparent);
backdrop-filter: blur(10px);
border-bottom: 1px solid var(--border);
}
.nav-inner {
max-width: 1200px;
margin: 0 auto;
height: 64px;
display: flex;
align-items: center;
gap: 16px;
padding: 0 16px;
}
.brand {
display: flex;
align-items: center;
gap: 8px;
font-weight: 800;
font-size: 17px;
white-space: nowrap;
}
.brand-icon {
display: flex;
align-items: center;
color: var(--primary);
filter: drop-shadow(var(--glow));
}
.brand-name {
background: linear-gradient(90deg, var(--primary), var(--accent));
-webkit-background-clip: text;
background-clip: text;
color: transparent;
}
.links {
display: flex;
gap: 2px;
flex: 1;
min-width: 0;
}
/* 竖排导航项SVG 图标在上、11px 文字在下 */
.link {
padding: 6px 12px 5px;
border-radius: calc(var(--radius) / 1.6);
color: var(--text-dim);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 3px;
transition: all 0.15s;
white-space: nowrap;
background: transparent;
border: none;
cursor: pointer;
font-family: inherit;
}
.link-icon {
display: block;
flex-shrink: 0;
}
.link-text {
font-size: 11px;
line-height: 1;
}
.link:hover {
color: var(--text);
background: var(--bg-card);
}
.link.active {
color: #fff;
background: linear-gradient(135deg, var(--primary), var(--primary-2));
box-shadow: var(--glow);
}
.msg-link {
position: relative;
}
.msg-badge {
position: absolute;
top: 2px;
right: 4px;
min-width: 16px;
height: 16px;
padding: 0 4px;
border-radius: 999px;
background: #e5484d;
color: #fff;
font-size: 10px;
font-weight: 800;
font-style: normal;
display: flex;
align-items: center;
justify-content: center;
}
.right {
display: flex;
align-items: center;
gap: 10px;
margin-left: auto;
flex-shrink: 0;
}
.points {
font-weight: 700;
color: var(--primary-2);
white-space: nowrap;
display: flex;
align-items: center;
gap: 4px;
}
.coin-icon {
flex-shrink: 0;
}
.tool-link {
padding: 5px 9px 4px;
}
.tool-link.active {
color: #fff;
}
.logout-link:hover {
color: #ff8a8e;
}
.user {
display: flex;
align-items: center;
gap: 6px;
}
.avatar {
width: 32px;
height: 32px;
border-radius: 50%;
background: var(--bg-card);
border: 1px solid var(--border);
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
}
.nickname {
font-size: 13px;
max-width: 90px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
/* 小屏隐藏文字仅留图标title 悬浮提示),不再使用汉堡抽屉 */
@media (max-width: 900px) {
.nav-inner {
gap: 8px;
height: 56px;
}
.links {
gap: 0;
overflow-x: auto;
scrollbar-width: none;
}
.links::-webkit-scrollbar {
display: none;
}
.link {
padding: 8px 9px;
}
.link-text {
display: none;
}
.msg-badge {
top: 1px;
right: 0;
}
.nickname {
display: none;
}
.right {
gap: 6px;
}
}
@media (max-width: 420px) {
.brand-name {
display: none;
}
.points {
font-size: 13px;
}
.avatar {
width: 28px;
height: 28px;
font-size: 15px;
}
}
</style>