更新首页

This commit is contained in:
2026-09-11 12:25:38 +08:00
parent 9e24153eac
commit c29c3be942
326 changed files with 6564 additions and 44307 deletions
+78 -48
View File
@@ -40,46 +40,46 @@
</div>
<!-- 右侧操作区 -->
<div class="flex items-center gap-2 flex-shrink-0 nav-right">
<!-- PC 登录/头像 -->
<div class="hidden md:flex items-center gap-3">
<template v-if="!isAuthed">
<a-button type="primary" @click="navigateTo('/login')">{{ '登录' }}</a-button>
</template>
<template v-else>
<!-- 用户头像 -->
<a-dropdown :trigger="['hover']" placement="bottomRight">
<a-space>
<a-avatar :src="userAvatar" :size="32">
<template v-if="!userAvatar" #icon>
<UserOutlined />
</template>
</a-avatar>
<span class="text-gray-100">{{ userName }}</span>
</a-space>
<template #overlay>
<a-menu @click="onUserMenuClick">
<a-menu-item key="profile"><ProfileOutlined style="margin-right: 8px" />个人信息</a-menu-item>
<a-menu-item key="my-suggestions"><MessageOutlined style="margin-right: 8px" />我的建言</a-menu-item>
<template v-if="isSuperAdmin">
<a-menu-divider />
<a-menu-item key="admin"> 后台管理</a-menu-item>
</template>
<a-menu-divider />
<a-menu-item key="logout">{{ '退出登录' }}</a-menu-item>
</a-menu>
</template>
</a-dropdown>
</template>
</div>
<!-- <div class="flex items-center gap-2 flex-shrink-0 nav-right">-->
<!-- &lt;!&ndash; PC 登录/头像 &ndash;&gt;-->
<!-- <div class="hidden md:flex items-center gap-3">-->
<!-- <template v-if="!isAuthed">-->
<!-- <a-button type="primary" @click="navigateTo('/login')">{{ '登录' }}</a-button>-->
<!-- </template>-->
<!-- <template v-else>-->
<!-- &lt;!&ndash; 用户头像 &ndash;&gt;-->
<!-- <a-dropdown :trigger="['hover']" placement="bottomRight">-->
<!-- <a-space>-->
<!-- <a-avatar :src="userAvatar" :size="32">-->
<!-- <template v-if="!userAvatar" #icon>-->
<!-- <UserOutlined />-->
<!-- </template>-->
<!-- </a-avatar>-->
<!-- <span class="text-gray-100">{{ userName }}</span>-->
<!-- </a-space>-->
<!-- <template #overlay>-->
<!-- <a-menu @click="onUserMenuClick">-->
<!-- <a-menu-item key="profile"><ProfileOutlined style="margin-right: 8px" />个人信息</a-menu-item>-->
<!-- <a-menu-item key="my-suggestions"><MessageOutlined style="margin-right: 8px" />我的建言</a-menu-item>-->
<!-- <template v-if="isSuperAdmin">-->
<!-- <a-menu-divider />-->
<!-- <a-menu-item key="admin"> 后台管理</a-menu-item>-->
<!-- </template>-->
<!-- <a-menu-divider />-->
<!-- <a-menu-item key="logout">{{ '退出登录' }}</a-menu-item>-->
<!-- </a-menu>-->
<!-- </template>-->
<!-- </a-dropdown>-->
<!-- </template>-->
<!-- </div>-->
<!-- 移动端汉堡菜单按钮 -->
<button class="md:hidden flex flex-col justify-center items-center w-10 h-10 gap-1.5 rounded-lg bg-white/10 hover:bg-white/20 border border-white/20 transition-colors" @click="open = true">
<span class="block w-5 h-0.5 bg-white rounded-full"></span>
<span class="block w-5 h-0.5 bg-white rounded-full"></span>
<span class="block w-5 h-0.5 bg-white rounded-full"></span>
</button>
</div>
<!-- &lt;!&ndash; 移动端汉堡菜单按钮 &ndash;&gt;-->
<!-- <button class="md:hidden flex flex-col justify-center items-center w-10 h-10 gap-1.5 rounded-lg bg-white/10 hover:bg-white/20 border border-white/20 transition-colors" @click="open = true">-->
<!-- <span class="block w-5 h-0.5 bg-white rounded-full"></span>-->
<!-- <span class="block w-5 h-0.5 bg-white rounded-full"></span>-->
<!-- <span class="block w-5 h-0.5 bg-white rounded-full"></span>-->
<!-- </button>-->
<!-- </div>-->
</div>
</a-layout-header>
</a-affix>
@@ -102,14 +102,14 @@
</a-menu-item>
</template>
</a-menu>
<div class="mt-4">
<a-button v-if="!isAuthed" block type="primary" @click="onNav('/login')">{{ '登录' || '登录' }}</a-button>
<template v-else>
<a-button block type="primary" class="mb-2" @click="onNav('/profile')">个人中心</a-button>
<a-button v-if="isSuperAdmin" block @click="onNav('/admin')"> 后台管理</a-button>
<a-button block danger class="mt-2" @click="logout">{{ '退出登录' || '退出登录' }}</a-button>
</template>
</div>
<!-- <div class="mt-4">-->
<!-- <a-button v-if="!isAuthed" block type="primary" @click="onNav('/login')">{{ '登录' || '登录' }}</a-button>-->
<!-- <template v-else>-->
<!-- <a-button block type="primary" class="mb-2" @click="onNav('/profile')">个人中心</a-button>-->
<!-- <a-button v-if="isSuperAdmin" block @click="onNav('/admin')"> 后台管理</a-button>-->
<!-- <a-button block danger class="mt-2" @click="logout">{{ '退出登录' || '退出登录' }}</a-button>-->
<!-- </template>-->
<!-- </div>-->
</a-drawer>
</template>
@@ -128,10 +128,26 @@ const open = ref(false)
const selectedKeys = computed(() => {
const currentPath = route.path
const currentType = typeof route.query.type === 'string' ? route.query.type : ''
const exactChildHit = nav.value
.flatMap(item => item.children || [])
.find((item) => item.to === route.fullPath || buildPathWithQuery(item.to) === route.fullPath)
if (exactChildHit) return [exactChildHit.key]
const exactHit = nav.value.find((item) => item.to === currentPath)
if (exactHit) return [exactHit.key]
if (currentType) {
const childTypeHit = nav.value
.flatMap(item => item.children || [])
.find((item) => {
const child = normalizeNavTarget(item.to)
return child.path === currentPath && child.type === currentType
})
if (childTypeHit) return [childTypeHit.key]
}
const prefixHit = nav.value.find((item) => item.to !== '/' && currentPath.startsWith(`${item.to}/`))
if (prefixHit) return [prefixHit.key]
@@ -141,6 +157,20 @@ const selectedKeys = computed(() => {
return ['home']
})
function normalizeNavTarget(to: string) {
const [path, search = ''] = to.split('?')
const params = new URLSearchParams(search)
return {
path,
type: params.get('type') || '',
}
}
function buildPathWithQuery(to: string) {
const normalized = normalizeNavTarget(to)
return normalized.type ? `${normalized.path}?type=${normalized.type}` : normalized.path
}
// 获取 badge 样式类
function getBadgeClass(badge: string) {
const baseClass = 'ml-1.5 px-1.5 py-0.5 text-xs font-medium rounded'
@@ -157,7 +187,7 @@ const token = ref('')
const user = ref<User | null>(null)
const isAuthed = computed(() => !!token.value)
const userName = computed(() => String(user.value?.nickname || user.value?.username || '已登录'))
const isSuperAdmin = computed(() => !!(user.value as any)?.isAdmin)
const isSuperAdmin = computed(() => !!(user.value as User & { isAdmin?: boolean } | null)?.isAdmin)
const userAvatar = computed(() => {
const candidate =
user.value?.avatarUrl ||