Files
huihuiSquare/frontend/src/layouts/MainLayout.vue
T

229 lines
5.8 KiB
Vue

<template>
<div class="layout">
<!-- Sidebar -->
<aside class="sidebar">
<div class="sidebar-logo">
<span class="logo-icon">🤖</span>
<span class="logo-text">AI互动系统</span>
</div>
<el-menu :default-active="activeMenu" router class="sidebar-menu">
<el-menu-item index="/dashboard">
<el-icon><DataAnalysis /></el-icon>
<span>数据看板</span>
</el-menu-item>
<el-menu-item index="/avatars">
<el-icon><UserFilled /></el-icon>
<span>数字分身管理</span>
</el-menu-item>
<el-menu-item index="/finance">
<el-icon><WalletFilled /></el-icon>
<span>财务管理</span>
</el-menu-item>
<el-menu-item index="/users">
<el-icon><User /></el-icon>
<span>虚拟用户</span>
</el-menu-item>
<el-menu-item index="/interactions">
<el-icon><ChatDotRound /></el-icon>
<span>互动记录</span>
</el-menu-item>
<el-menu-item index="/ai-models">
<el-icon><Cpu /></el-icon>
<span>AI模型配置</span>
</el-menu-item>
<el-menu-item index="/scheduler">
<el-icon><Setting /></el-icon>
<span>调度设置</span>
</el-menu-item>
<el-menu-item index="/logs">
<el-icon><Document /></el-icon>
<span>日志管理</span>
</el-menu-item>
</el-menu>
<div class="sidebar-footer">
<div class="system-status">
<span class="status-dot" :class="schedulerEnabled ? 'active' : 'paused'"></span>
<span>{{ schedulerEnabled ? '调度运行中' : '调度已暂停' }}</span>
</div>
<div class="version">v1.0.0</div>
</div>
</aside>
<!-- Main content -->
<div class="main-wrapper">
<header class="topbar">
<div class="topbar-left">
<span class="page-title-bar">{{ currentTitle }}</span>
</div>
<div class="topbar-right">
<!-- 右上角消息通知组件暂时注释 -->
<div class="online-badge">
<span class="pulse"></span>
<span>{{ onlineUsers }} 在线</span>
</div>
<div class="token-badge">
<el-icon><Coin /></el-icon>
<span>今日剩余 {{ tokenRemaining.toLocaleString() }}</span>
</div>
<el-button size="small" circle @click="refreshAll">
<el-icon><Refresh /></el-icon>
</el-button>
</div>
</header>
<main class="main-content">
<router-view />
</main>
</div>
</div>
</template>
<script setup>
import { ref, computed, onMounted, onUnmounted } from 'vue'
import { useRoute } from 'vue-router'
import { getDashboard } from '@/api'
const route = useRoute()
const activeMenu = computed(() => route.path)
const currentTitle = computed(() => route.meta?.title || 'AI虚拟用户新闻互动系统')
const onlineUsers = ref(0)
const tokenRemaining = ref(0)
const schedulerEnabled = ref(true)
let timer = null
async function fetchStatus() {
try {
const res = await getDashboard()
onlineUsers.value = res.data?.online_users || 0
tokenRemaining.value = res.data?.token_stats?.remaining || 0
schedulerEnabled.value = res.data?.system_status?.scheduler_enabled ?? true
} catch {}
}
function refreshAll() {
fetchStatus()
window.dispatchEvent(new CustomEvent('page-refresh'))
}
onMounted(() => {
fetchStatus()
timer = setInterval(fetchStatus, 300000) // 5min
})
onUnmounted(() => clearInterval(timer))
</script>
<style scoped>
.layout {
display: flex;
height: 100vh;
background: var(--color-bg);
}
.sidebar {
width: var(--sidebar-width);
background: var(--color-bg-secondary);
border-right: 1px solid var(--color-border);
display: flex;
flex-direction: column;
flex-shrink: 0;
}
.sidebar-logo {
display: flex;
align-items: center;
gap: 10px;
padding: 20px 16px;
border-bottom: 1px solid var(--color-border);
}
.logo-icon { font-size: 24px; }
.logo-text {
font-size: 15px;
font-weight: 700;
color: var(--color-text);
letter-spacing: 0.5px;
}
.sidebar-menu {
flex: 1;
padding: 12px 0;
overflow-y: auto;
}
:deep(.el-menu-item) {
height: 44px;
line-height: 44px;
font-size: 14px;
}
.sidebar-footer {
padding: 16px;
border-top: 1px solid var(--color-border);
}
.system-status {
display: flex;
align-items: center;
gap: 6px;
font-size: 12px;
color: var(--color-text-muted);
margin-bottom: 6px;
}
.status-dot {
width: 7px; height: 7px;
border-radius: 50%;
}
.status-dot.active {
background: var(--color-accent-green);
box-shadow: 0 0 6px var(--color-accent-green);
animation: blink 2s infinite;
}
.status-dot.paused { background: var(--color-accent-orange); }
@keyframes blink { 0%,100%{opacity:1} 50%{opacity:0.4} }
.version { font-size: 11px; color: var(--color-text-muted); opacity: 0.5; }
.main-wrapper {
flex: 1;
display: flex;
flex-direction: column;
overflow: hidden;
}
.topbar {
height: 56px;
background: var(--color-bg-secondary);
border-bottom: 1px solid var(--color-border);
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 24px;
flex-shrink: 0;
}
.page-title-bar { font-size: 15px; font-weight: 600; color: var(--color-text); }
.topbar-right {
display: flex;
align-items: center;
gap: 16px;
}
.online-badge, .token-badge {
display: flex;
align-items: center;
gap: 5px;
font-size: 12px;
color: var(--color-text-muted);
background: var(--color-bg-card);
border: 1px solid var(--color-border);
padding: 4px 10px;
border-radius: 20px;
}
.pulse {
width: 7px; height: 7px;
border-radius: 50%;
background: var(--color-accent-green);
animation: blink 1.5s infinite;
}
.main-content {
flex: 1;
overflow: hidden;
}
</style>