feat: redesign manager admin layout

This commit is contained in:
ngfchl
2026-07-07 13:53:59 +08:00
parent 5c11b44dbb
commit 23269d261a
9 changed files with 1093 additions and 17 deletions
+39 -15
View File
@@ -318,25 +318,48 @@ onBeforeUnmount(() => {
<template>
<SConfigProvider :theme="{ theme: { primary: 'indigo' }, size: 'sm' }">
<main class="page">
<header class="topbar">
<div class="brand">
<main class="admin-shell">
<aside class="sidebar">
<div class="side-brand">
<img class="brand-icon" src="/icon.svg" alt="TG Spam Guard" />
<div>
<h1>TG Spam Guard</h1>
<p>Telegram 群组安全管理面板</p>
<p>群组安全后台</p>
</div>
</div>
<div class="top-actions">
<select v-model="selectedChatId" class="form-select" @change="reloadByGroup"><option :value="null">全部群组</option><option v-for="g in groups" :key="g.chat_id" :value="g.chat_id">{{ g.label || g.name }}</option></select>
<span class="conn" :class="sseConnected ? 'on' : 'off'">{{ sseConnected ? '实时连接' : '连接中' }}</span>
<SButton size="xs" variant="outline" @click="activeModal='shop'; loadShopItems(); loadShopTransactions()">商城</SButton>
<SButton size="xs" variant="outline" @click="activeModal='lottery'; loadLotteries()">抽奖</SButton>
<SButton size="xs" variant="outline" @click="activeModal='bans'; loadBans()">封禁</SButton>
<SButton size="xs" variant="outline" color="primary" :disabled="restartingBot" @click="restartBot">重启Bot</SButton>
<SButton size="xs" variant="soft" color="warning" :disabled="restarting" @click="restartService">重启服务</SButton>
<div class="side-section">
<span class="side-label">当前群组</span>
<select v-model="selectedChatId" class="form-select side-select" @change="reloadByGroup"><option :value="null">全部群组</option><option v-for="g in groups" :key="g.chat_id" :value="g.chat_id">{{ g.label || g.name }}</option></select>
</div>
</header>
<nav class="side-nav">
<a class="active" href="#dashboard"><span>总览</span><em>Dashboard</em></a>
<a href="#chat"><span>聊天审计</span><em>Messages</em></a>
<a href="#logs"><span>运行日志</span><em>Logs</em></a>
<button @click="activeModal='shop'; loadShopItems(); loadShopTransactions()"><span>商城管理</span><em>Shop</em></button>
<button @click="activeModal='lottery'; loadLotteries()"><span>抽奖管理</span><em>Lottery</em></button>
<button @click="activeModal='bans'; loadBans()"><span>封禁记录</span><em>Bans</em></button>
</nav>
<div class="side-footer">
<span class="conn" :class="sseConnected ? 'on' : 'off'">{{ sseConnected ? '实时连接' : '连接中' }}</span>
<small>Bot 管理控制台</small>
</div>
</aside>
<section class="admin-main">
<header class="admin-header" id="dashboard">
<div>
<span class="eyebrow">CONTROL CENTER</span>
<h2>群组安全总览</h2>
<p>实时审计聊天封禁日志和运营活动</p>
</div>
<div class="top-actions">
<SButton size="xs" variant="outline" @click="activeModal='shop'; loadShopItems(); loadShopTransactions()">商城管理</SButton>
<SButton size="xs" variant="outline" @click="activeModal='lottery'; loadLotteries()">抽奖管理</SButton>
<SButton size="xs" variant="outline" @click="activeModal='bans'; loadBans()">封禁记录</SButton>
<SButton size="xs" variant="outline" color="primary" :disabled="restartingBot" @click="restartBot">重启 Bot</SButton>
<SButton size="xs" variant="soft" color="warning" :disabled="restarting" @click="restartService">重启服务</SButton>
</div>
</header>
<section class="stats-grid">
<SCard class="stat-card blue"><span>今日封禁</span><strong>{{ stats.today_bans || 0 }}</strong></SCard>
@@ -346,7 +369,7 @@ onBeforeUnmount(() => {
</section>
<section class="main-grid">
<SCard class="panel chat-panel" title="💬 聊天记录" :description="`${chatCount} 条`">
<SCard id="chat" class="panel chat-panel" title="聊天记录" :description="`${chatCount} 条`">
<div class="scroll-area chat-scroll">
<div v-if="!chatLogs.length" class="empty">等待消息...</div>
<article v-for="(item, i) in chatLogs" :key="item.id || i" :class="chatItemClass(item)">
@@ -398,7 +421,7 @@ onBeforeUnmount(() => {
<div v-if="activeModal" class="modal-mask" @click.self="activeModal=null">
<div class="modal-card">
<div class="modal-head">
<h2>{{ activeModal === 'shop' ? '🛒 商城管理' : activeModal === 'lottery' ? '🎁 抽奖管理' : '🚫 封禁记录' }}</h2>
<h2>{{ activeModal === 'shop' ? '商城管理' : activeModal === 'lottery' ? '抽奖管理' : '封禁记录' }}</h2>
<SButton size="xs" variant="outline" @click="activeModal=null">关闭</SButton>
</div>
@@ -469,6 +492,7 @@ onBeforeUnmount(() => {
</div>
</div>
</div>
</section>
</main>
</SConfigProvider>
</template>
+65
View File
@@ -132,3 +132,68 @@ button { font-family: inherit; }
.manager-layout { grid-template-columns: 1fr; }
.form-two { grid-template-columns: 1fr; }
}
/* Admin template layout */
body { background: #f3f6fb; }
.admin-shell { min-height: 100vh; display: grid; grid-template-columns: 264px minmax(0, 1fr); background: #f3f6fb; color: #0f172a; }
.sidebar { position: sticky; top: 0; height: 100vh; display: flex; flex-direction: column; gap: 18px; padding: 18px 14px; background: #0b1220; color: #e5e7eb; border-right: 1px solid rgba(148,163,184,.16); }
.side-brand { display: flex; align-items: center; gap: 12px; padding: 8px 8px 14px; border-bottom: 1px solid rgba(148,163,184,.16); }
.side-brand h1 { margin: 0; font-size: 17px; line-height: 1.1; color: #fff; }
.side-brand p { margin: 4px 0 0; color: #94a3b8; font-size: 12px; }
.sidebar .brand-icon { width: 40px; height: 40px; border-radius: 12px; box-shadow: 0 12px 30px rgba(0,0,0,.32); }
.side-section { display: grid; gap: 8px; padding: 0 4px; }
.side-label { color: #64748b; font-size: 11px; font-weight: 800; letter-spacing: .08em; text-transform: uppercase; }
.side-select { width: 100%; background: rgba(15,23,42,.9) !important; color: #e5e7eb !important; border-color: rgba(148,163,184,.26) !important; }
.side-nav { display: grid; gap: 6px; }
.side-nav a, .side-nav button { display: flex; align-items: center; justify-content: space-between; width: 100%; border: 0; border-radius: 12px; padding: 11px 12px; color: #cbd5e1; background: transparent; text-align: left; text-decoration: none; cursor: pointer; transition: background .16s, color .16s, transform .16s; }
.side-nav a:hover, .side-nav button:hover, .side-nav .active { background: rgba(99,102,241,.16); color: #fff; transform: translateX(2px); }
.side-nav span { font-size: 14px; font-weight: 750; }
.side-nav em { font-style: normal; color: #64748b; font-size: 11px; }
.side-footer { margin-top: auto; padding: 12px; border-radius: 14px; background: rgba(15,23,42,.82); border: 1px solid rgba(148,163,184,.14); display: grid; gap: 8px; }
.side-footer small { color: #64748b; }
.admin-main { min-width: 0; padding: 18px 20px 88px; }
.admin-header { display: flex; align-items: center; justify-content: space-between; gap: 18px; min-height: 88px; padding: 18px 20px; margin-bottom: 16px; border: 1px solid #e2e8f0; border-radius: 20px; background: linear-gradient(135deg, rgba(255,255,255,.98), rgba(248,250,252,.94)); box-shadow: 0 18px 45px rgba(15,23,42,.07); }
.eyebrow { color: #4f46e5; font-size: 11px; font-weight: 900; letter-spacing: .12em; }
.admin-header h2 { margin: 3px 0; font-size: 25px; letter-spacing: -0.03em; }
.admin-header p { margin: 0; color: #64748b; font-size: 13px; }
.stats-grid { gap: 14px; margin-bottom: 14px; }
.stat-card { min-height: 96px; border-left: 0; border-radius: 18px !important; background: #fff !important; box-shadow: 0 12px 30px rgba(15,23,42,.06); border: 1px solid #e2e8f0 !important; position: relative; overflow: hidden; }
.stat-card::after { content: ''; position: absolute; right: -18px; top: -18px; width: 72px; height: 72px; border-radius: 999px; background: rgba(59,130,246,.12); }
.stat-card.red::after { background: rgba(239,68,68,.12); }
.stat-card.orange::after { background: rgba(245,158,11,.14); }
.stat-card.green::after { background: rgba(16,185,129,.14); }
.stat-card strong { font-size: 32px; letter-spacing: -0.04em; font-variant-numeric: tabular-nums; }
.main-grid { grid-template-columns: minmax(0, 1.25fr) minmax(420px, .75fr); gap: 14px; height: calc(100vh - 252px); min-height: 560px; }
.panel, .ban-panel { border-radius: 18px !important; border: 1px solid #e2e8f0 !important; box-shadow: 0 12px 30px rgba(15,23,42,.05); background: #fff !important; }
.chat-item { border-radius: 10px; margin-bottom: 8px; }
.send-box { left: 284px; right: 20px; bottom: 18px; border: 1px solid #e2e8f0; border-radius: 18px; box-shadow: 0 18px 50px rgba(15,23,42,.12); background: rgba(255,255,255,.94); }
.modal-card { border-radius: 22px; }
.modal-head { padding: 18px 20px; background: #f8fafc; }
.modal-head h2 { font-size: 20px; letter-spacing: -0.02em; }
@media (max-width: 1100px) {
.admin-shell { grid-template-columns: 1fr; }
.sidebar { position: relative; height: auto; flex-direction: row; align-items: center; flex-wrap: wrap; }
.side-brand { border-bottom: 0; padding-bottom: 8px; }
.side-section { min-width: 240px; }
.side-nav { grid-template-columns: repeat(3, minmax(120px, 1fr)); flex: 1; }
.side-footer { display: none; }
.send-box { left: 16px; right: 16px; }
.main-grid { grid-template-columns: 1fr; height: auto; }
}
@media (max-width: 720px) {
.admin-main { padding: 12px 12px 92px; }
.admin-header { align-items: flex-start; flex-direction: column; }
.side-nav { grid-template-columns: 1fr 1fr; }
.stats-grid { grid-template-columns: 1fr 1fr; }
}
/* Admin layout send box override */
.admin-shell .send-box { left: 284px; right: 20px; bottom: 18px; width: auto; transform: none; max-width: none; }
@media (max-width: 1100px) { .admin-shell .send-box { left: 16px; right: 16px; width: auto; transform: none; } }
@media (max-width: 520px) { .admin-shell .send-box { left: 8px; right: 8px; width: auto; transform: none; } }
/* Static admin composer */
.admin-shell .send-box { position: static; left: auto; right: auto; bottom: auto; width: 100%; transform: none; max-width: none; margin-top: 14px; }
.admin-main { padding-bottom: 20px; }
@media (max-width: 1100px) { .admin-shell .send-box { position: static; left: auto; right: auto; width: 100%; transform: none; } }
@media (max-width: 520px) { .admin-shell .send-box { position: static; left: auto; right: auto; width: 100%; transform: none; } }
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+2 -2
View File
@@ -5,8 +5,8 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="icon" type="image/svg+xml" href="/static/icon.svg" />
<title>TG Spam Guard</title>
<script type="module" crossorigin src="/static/assets/index-kYxHYLUo.js"></script>
<link rel="stylesheet" crossorigin href="/static/assets/index-zJhQHWIR.css">
<script type="module" crossorigin src="/static/assets/index-BIlnBDgU.js"></script>
<link rel="stylesheet" crossorigin href="/static/assets/index-Dbs1frnk.css">
</head>
<body>
<div id="app"></div>