feat: AI虚拟用户新闻互动系统 v1.3.0 初始提交
- 虚拟用户管理(昵称/头像/性别/简介/邮箱同步到目标平台) - AI互动调度(点赞/收藏/评论/转发) - 日志时间改为北京时间 - 评论达上限后继续执行点赞收藏转发 - 一键登出全部功能 - 浅色主题UI
This commit is contained in:
@@ -0,0 +1,219 @@
|
||||
<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="/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>
|
||||
Reference in New Issue
Block a user