big update

This commit is contained in:
stardrophere
2026-03-11 20:52:58 +08:00
parent 8ed819a580
commit 966bcfbba4
44 changed files with 7124 additions and 650 deletions
+366
View File
@@ -0,0 +1,366 @@
<script setup lang="ts">
import { computed, ref } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import BrandLogo from '@/components/BrandLogo.vue'
import ThemeToggle from '@/components/ThemeToggle.vue'
import { useAuthStore } from '@/stores/auth'
const authStore = useAuthStore()
const route = useRoute()
const router = useRouter()
const sidebarOpen = ref(false)
const displayName = computed(() => authStore.user?.nickname || authStore.user?.email?.split('@')[0] || '用户')
const avatarUrl = computed(
() =>
authStore.user?.avatar_url ||
`https://ui-avatars.com/api/?name=${encodeURIComponent(displayName.value)}&background=6366f1&color=fff`,
)
const navItems = [
{ name: '全局热点池', icon: 'fa-solid fa-fire', route: '/' },
{ name: '公关修改追踪', icon: 'fa-solid fa-mask', route: '/revisions' },
{ name: '我的泛订阅', icon: 'fa-solid fa-rss', route: '/topics' },
{ name: 'AI 简报设置', icon: 'fa-solid fa-paper-plane', route: '/delivery' },
]
function isActive(path: string) {
return route.path === path
}
async function handleLogout() {
authStore.logout()
await router.replace('/login')
}
function toggleSidebar() {
sidebarOpen.value = !sidebarOpen.value
}
</script>
<template>
<div class="dashboard-shell">
<!-- 移动端侧边栏遮罩 -->
<div v-if="sidebarOpen" class="sidebar-overlay" @click="sidebarOpen = false"></div>
<!-- 侧边栏 -->
<aside class="sidebar" :class="{ open: sidebarOpen }">
<div class="sidebar-inner">
<!-- Logo -->
<div class="sidebar-logo">
<BrandLogo />
<span class="logo-text">InsightRadar<span class="logo-dot">.AI</span></span>
</div>
<!-- 导航菜单 -->
<nav class="sidebar-nav">
<RouterLink
v-for="item in navItems"
:key="item.route"
:to="item.route"
class="nav-item"
:class="{ active: isActive(item.route) }"
@click="sidebarOpen = false"
>
<i :class="item.icon" class="nav-icon"></i>
<span>{{ item.name }}</span>
</RouterLink>
</nav>
</div>
<!-- 用户信息 -->
<div class="sidebar-user">
<img :src="avatarUrl" class="user-avatar" alt="头像" />
<div class="user-info">
<p class="user-name">{{ displayName }}</p>
<p class="user-status">
<span class="status-dot"></span>
已登录
</p>
</div>
<button class="logout-btn" title="退出登录" @click="handleLogout">
<i class="fa-solid fa-right-from-bracket"></i>
</button>
</div>
</aside>
<!-- 主内容区 -->
<main class="main-area">
<!-- 顶部通栏 -->
<header class="top-header">
<button class="menu-toggle" @click="toggleSidebar">
<i class="fa-solid fa-bars"></i>
</button>
<div class="header-right">
<ThemeToggle />
</div>
</header>
<!-- 页面内容插槽 -->
<div class="page-content">
<RouterView v-slot="{ Component }">
<transition name="page-fade" mode="out-in">
<component :is="Component" />
</transition>
</RouterView>
</div>
</main>
</div>
</template>
<style scoped>
.dashboard-shell {
display: flex;
height: 100vh;
overflow: hidden;
}
/* ==========================================
侧边栏
========================================== */
.sidebar {
width: 260px;
min-width: 260px;
/* 增加侧边栏的毛玻璃高级感 */
background: var(--bg-surface);
backdrop-filter: var(--backdrop-blur);
-webkit-backdrop-filter: var(--backdrop-blur);
border-right: 1px solid var(--border-subtle);
display: flex;
flex-direction: column;
justify-content: space-between;
z-index: 40;
transition: transform 0.3s ease;
}
.sidebar-inner {
flex: 1;
overflow-y: auto;
}
.sidebar-logo {
height: 64px;
display: flex;
align-items: center;
padding: 0 20px;
gap: 12px;
border-bottom: 1px solid var(--border-subtle);
}
.logo-text {
font-size: 20px;
font-weight: 700;
letter-spacing: 0.02em;
}
.logo-dot {
color: var(--brand-primary);
}
/* 导航 */
.sidebar-nav {
padding: 16px 12px;
display: flex;
flex-direction: column;
gap: 4px;
}
.nav-item {
display: flex;
align-items: center;
gap: 12px;
padding: 12px 16px;
border-radius: var(--radius-md);
font-size: 14px;
font-weight: 500;
color: var(--text-secondary);
text-decoration: none;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
.nav-item:hover {
color: var(--text-primary);
background: var(--bg-hover);
transform: translateX(4px);
}
.nav-item.active {
color: var(--brand-primary);
background: var(--brand-primary-alpha);
border-left: 3px solid var(--brand-primary);
padding-left: 13px; /* 减去 border 的 3px 保持布局不跳动 */
font-weight: 600;
}
.nav-icon {
width: 18px;
text-align: center;
font-size: 15px;
}
/* 用户区 */
.sidebar-user {
padding: 16px 20px;
border-top: 1px solid var(--border-subtle);
display: flex;
align-items: center;
gap: 12px;
}
.user-avatar {
width: 36px;
height: 36px;
border-radius: 50%;
flex-shrink: 0;
}
.user-info {
flex: 1;
min-width: 0;
}
.user-name {
font-size: 13px;
font-weight: 600;
margin: 0;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.user-status {
font-size: 11px;
color: var(--status-success);
margin: 2px 0 0;
display: flex;
align-items: center;
gap: 4px;
}
.status-dot {
width: 6px;
height: 6px;
background: var(--status-success);
border-radius: 50%;
display: inline-block;
animation: pulse-dot 2s infinite;
}
@keyframes pulse-dot {
0%, 100% { opacity: 1; }
50% { opacity: 0.4; }
}
.logout-btn {
color: var(--text-secondary);
padding: 8px;
border-radius: var(--radius-md);
font-size: 14px;
transition: all 0.2s;
}
.logout-btn:hover {
color: var(--status-error);
background: rgba(239, 68, 68, 0.1);
}
/* ==========================================
主内容区
========================================== */
.main-area {
flex: 1;
display: flex;
flex-direction: column;
overflow: hidden;
}
.top-header {
height: 60px;
min-height: 60px;
/* 顶部导航毛玻璃 */
background: var(--bg-surface);
backdrop-filter: var(--backdrop-blur);
-webkit-backdrop-filter: var(--backdrop-blur);
border-bottom: 1px solid var(--border-subtle);
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 24px;
z-index: 10;
}
.menu-toggle {
display: none;
font-size: 18px;
color: var(--text-secondary);
padding: 8px;
border-radius: var(--radius-md);
}
.menu-toggle:hover {
background: var(--bg-input);
color: var(--text-primary);
}
.header-right {
display: flex;
align-items: center;
gap: 16px;
margin-left: auto;
}
.page-content {
flex: 1;
overflow-y: auto;
padding: 24px;
}
/* ==========================================
移动端适配
========================================== */
.sidebar-overlay {
display: none;
}
@media (max-width: 768px) {
.sidebar {
position: fixed;
top: 0;
left: 0;
bottom: 0;
transform: translateX(-100%);
}
.sidebar.open {
transform: translateX(0);
}
.sidebar-overlay {
display: block;
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.5);
z-index: 30;
}
.menu-toggle {
display: block;
}
.page-content {
padding: 16px;
}
}
/* 页面过渡动画 */
.page-fade-enter-active,
.page-fade-leave-active {
transition: opacity 0.2s ease;
}
.page-fade-enter-from,
.page-fade-leave-to {
opacity: 0;
}
</style>