From f65426f82c43ca8f808bf1b2eea10c68649fe2bc Mon Sep 17 00:00:00 2001 From: ngfchl Date: Mon, 6 Jul 2026 20:33:25 +0800 Subject: [PATCH] style: optimize manager dashboard layout --- web/static/index.html | 304 +++++++++++++++++++----------------------- 1 file changed, 137 insertions(+), 167 deletions(-) diff --git a/web/static/index.html b/web/static/index.html index aebc7fb..d63f43f 100644 --- a/web/static/index.html +++ b/web/static/index.html @@ -6,135 +6,124 @@ 🛡️ TG Spam Guard - 收割机
- -
+

🛡️ TG Spam Guard

- {{ sseConnected ? '已连接' : '连接中...' }} + {{ sseConnected ? '实时连接' : '连接中...' }}
- - - - -
{{ stats.today_bans }}
-
今日封禁
-
+ + + 今日封禁{{ stats.today_bans || 0 }} - - -
{{ stats.total_bans }}
-
累计封禁
-
+ + 累计封禁{{ stats.total_bans || 0 }} - - -
{{ stats.active_bans }}
-
当前封禁中
-
+ + 当前封禁{{ stats.active_bans || 0 }} - - -
{{ chatLogs.length }}
-
聊天记录
-
+ + 聊天记录{{ chatLogs.length || 0 }}
- - - - - + + +
-

💬 实时聊天

- {{ chatLogs.length }} 条 +

💬 聊天记录

+ {{ chatLogs.length }} 条
- + 发送
-
-
- 💬等待消息... -
+
+
💬等待消息...
- @{{ item.username }} ({{ item.user_id }}) + @{{ item.username || item.first_name || 'unknown' }} ({{ item.user_id }}) {{ formatTime(item.time) }}
{{ item.text }}
@@ -144,46 +133,43 @@ - - - + +
-

🚫 封禁列表

- 刷新 -
-
-
- 暂无封禁记录 +

📜 实时日志

+
+ {{ logs.length }} 条 + 刷新
-
-
-
@{{ item.username }} ({{ item.user_id }})
-
📝 {{ item.reason }}
-
⏰ {{ formatTime(item.time) }}
-
- 解封 - 已解封 +
+
+
等待日志...
+
+ [{{ formatTime(item.time) }}] [{{ item.level }}] {{ compactLog(item.message) }}
- +
-

📜 实时日志

- 刷新 +

🚫 封禁记录

+
+ {{ bans.length }} 条 + 刷新 +
-
-
等待日志...
-
- [{{ formatTime(item.time) }}] [{{ item.level }}] {{ item.message }} +
+
暂无封禁记录
+
+
+
@{{ item.username || item.first_name || 'unknown' }} ({{ item.user_id }})
+
📝 {{ item.reason }}
+
⏰ {{ formatTime(item.time) }}
+
+ 解封 + 已解封
@@ -213,47 +199,39 @@ new Vue({ this.connectSSE() setInterval(() => this.loadStats(), 30000) }, + beforeDestroy() { + if (this._es) this._es.close() + }, methods: { - // SSE connectSSE() { if (this._es) this._es.close() const es = new EventSource('/api/stream') this._es = es - - es.addEventListener('connected', () => { - this.sseConnected = true - }) - + es.addEventListener('connected', () => { this.sseConnected = true }) es.addEventListener('chat', (e) => { const data = JSON.parse(e.data) this.chatLogs.unshift(data) - if (this.chatLogs.length > 100) this.chatLogs.pop() + if (this.chatLogs.length > 120) this.chatLogs.pop() }) - es.addEventListener('spam', (e) => { const data = JSON.parse(e.data) const item = this.chatLogs.find(x => x.msg_id === data.msg_id) if (item) Object.assign(item, data) }) - es.addEventListener('log', (e) => { const data = JSON.parse(e.data) this.logs.push(data) if (this.logs.length > 300) this.logs.shift() - this.$nextTick(() => { - if (this.$refs.logScroll) this.$refs.logScroll.scrollTop = this.$refs.logScroll.scrollHeight - }) + this.scrollLogBottom() }) - es.addEventListener('ban', (e) => { const data = JSON.parse(e.data) data._loading = false this.bans.unshift(data) - if (this.bans.length > 50) this.bans.pop() + if (this.bans.length > 80) this.bans.pop() this.loadStats() this.$message({ type: 'error', message: `🚫 封禁 @${data.username} - ${data.reason}`, duration: 5000 }) }) - es.addEventListener('unban', (e) => { const data = JSON.parse(e.data) const ban = this.bans.find(b => b.user_id === data.user_id) @@ -261,42 +239,27 @@ new Vue({ this.loadStats() this.$message({ type: 'success', message: `✅ 已解封 ${data.username || data.user_id}`, duration: 3000 }) }) - - es.addEventListener('heartbeat', () => {}) - es.onerror = () => { this.sseConnected = false setTimeout(() => this.connectSSE(), 3000) } }, - - // API async loadChatHistory() { - try { - const r = await fetch('/api/chat') - this.chatLogs = await r.json() - } catch (e) {} + try { this.chatLogs = await (await fetch('/api/chat')).json() } catch (e) {} }, async loadBans() { try { - const r = await fetch('/api/bans') - const data = await r.json() + const data = await (await fetch('/api/bans')).json() this.bans = data.map(b => ({ ...b, _loading: false })) } catch (e) {} }, async loadStats() { - try { - const r = await fetch('/api/stats') - this.stats = await r.json() - } catch (e) {} + try { this.stats = await (await fetch('/api/stats')).json() } catch (e) {} }, async loadLogs() { try { - const r = await fetch('/api/logs') - this.logs = await r.json() - this.$nextTick(() => { - if (this.$refs.logScroll) this.$refs.logScroll.scrollTop = this.$refs.logScroll.scrollHeight - }) + this.logs = await (await fetch('/api/logs')).json() + this.scrollLogBottom() } catch (e) {} }, async sendMessage() { @@ -324,8 +287,7 @@ new Vue({ async unbanUser(item) { this.$set(item, '_loading', true) try { - const r = await fetch(`/api/unban/${item.user_id}`, { method: 'POST' }) - const data = await r.json() + const data = await (await fetch(`/api/unban/${item.user_id}`, { method: 'POST' })).json() if (data.ok) { item.auto_unban = false this.$message.success(`✅ 已解封 @${item.username}`) @@ -338,12 +300,20 @@ new Vue({ } this.$set(item, '_loading', false) }, - + scrollLogBottom() { + this.$nextTick(() => { + if (this.$refs.logScroll) this.$refs.logScroll.scrollTop = this.$refs.logScroll.scrollHeight + }) + }, formatTime(iso) { if (!iso) return '' const d = new Date(iso) - return d.toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit', second: '2-digit' }) + return isNaN(d.getTime()) ? iso : d.toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit', second: '2-digit' }) }, + compactLog(msg) { + if (!msg) return '' + return String(msg).replace(/^\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2}\s+\[[A-Z]+\]\s+/, '') + } } })