229 lines
5.8 KiB
Vue
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>
|