From 65269e6f12a33020b97eab7c9a0a0b02f528698b Mon Sep 17 00:00:00 2001 From: liuwei Date: Wed, 26 Mar 2025 13:41:07 +0800 Subject: [PATCH] =?UTF-8?q?=E8=B0=83=E6=95=B4=E8=B6=8B=E5=8A=BF=E5=9B=BE?= =?UTF-8?q?=E5=8A=9F=E8=83=BD=EF=BC=8C=E5=8A=A0=E5=85=A5=E4=BA=86=E6=9F=A5?= =?UTF-8?q?=E8=AF=A2=E6=95=B0=E6=8D=AE=E5=BA=93=EF=BC=8C=E5=88=86=E6=9E=90?= =?UTF-8?q?=E7=BE=A4=E8=81=8A=E8=81=8A=E5=A4=A9=E6=95=B0=E9=87=8F=E7=9A=84?= =?UTF-8?q?=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- db/message_storage.py | 25 +++- plugins/stats_dashboard/dashboard_server.py | 39 +++++- .../templates/robot_management.html | 126 +++++++++++++++++- 3 files changed, 185 insertions(+), 5 deletions(-) diff --git a/db/message_storage.py b/db/message_storage.py index c3d87fc..409fb6f 100644 --- a/db/message_storage.py +++ b/db/message_storage.py @@ -84,4 +84,27 @@ class MessageStorageDB(BaseDBOperator): ON DUPLICATE KEY UPDATE count = VALUES(count) """ params = (group_id, wx_id, date, count) - return self.execute_update(sql, params) \ No newline at end of file + return self.execute_update(sql, params) + + + def get_message_trend(self, group_id: str, days: int = 7) -> List[Dict]: + """获取指定群组的消息趋势数据 + + Args: + group_id: 群组ID + days: 获取最近几天的数据,默认7天 + + Returns: + 包含日期和消息数量的列表 + """ + sql = """ + SELECT + DATE(timestamp) as date, + COUNT(*) as message_count + FROM messages + WHERE group_id = %s + AND timestamp >= DATE_SUB(CURDATE(), INTERVAL %s DAY) + GROUP BY DATE(timestamp) + ORDER BY date + """ + return self.execute_query(sql, (group_id, days)) or [] \ No newline at end of file diff --git a/plugins/stats_dashboard/dashboard_server.py b/plugins/stats_dashboard/dashboard_server.py index 0df7df6..fe35576 100644 --- a/plugins/stats_dashboard/dashboard_server.py +++ b/plugins/stats_dashboard/dashboard_server.py @@ -1,11 +1,13 @@ import logging -from typing import Dict, Any, Optional import threading import time import os +from datetime import datetime + from flask import Flask, render_template, request, jsonify, redirect, url_for, session, send_from_directory from db.connection import DBConnectionManager +from db.message_storage import MessageStorageDB from db.stats_db import StatsDBOperator from utils.wechat.contact_manager import ContactManager from robot_cmd.robot_command import GroupBotManager, Feature, PermissionStatus @@ -24,6 +26,7 @@ class DashboardServer: # 修正:使用单例模式获取数据库连接 self.db_manager = DBConnectionManager.get_instance() self.stats_db = StatsDBOperator(self.db_manager) + self.message_storage = MessageStorageDB(self.db_manager) # 获取联系人管理器实例 self.contact_manager = ContactManager.get_instance() self.app = self._create_app() @@ -286,6 +289,40 @@ class DashboardServer: self.logger.info(f"看板主页/api/plugin_trend: {trend}") return jsonify({"success": True, "data": trend}) + @app.route('/api/robot/group//message_trend', methods=['GET']) + def get_group_message_trend(group_id): + """获取群组消息趋势数据""" + try: + days = request.args.get('days', default=7, type=int) + # 获取消息存储实例 + trend_data = self.message_storage.get_message_trend(group_id, days) + + # 格式化数据为前端需要的格式 + dates = [] + counts = [] + for item in trend_data: + # 将日期转换为字符串 + if isinstance(item['date'], datetime): + date_str = item['date'].strftime('%Y-%m-%d') + else: + date_str = str(item['date']) + + dates.append(date_str) + counts.append(item['message_count']) + + return jsonify({ + 'success': True, + 'data': { + 'dates': dates, + 'counts': counts + } + }) + except Exception as e: + return jsonify({ + 'success': False, + 'error': str(e) + }) + return app def run(self) -> None: diff --git a/plugins/stats_dashboard/templates/robot_management.html b/plugins/stats_dashboard/templates/robot_management.html index 61a291a..d9236b7 100644 --- a/plugins/stats_dashboard/templates/robot_management.html +++ b/plugins/stats_dashboard/templates/robot_management.html @@ -45,7 +45,7 @@ - + @@ -130,6 +136,27 @@ 确定 + + + +
+ +

加载中...

+
+
+ +
+ + 最近7天 + 最近14天 + 最近30天 + +
+
+
{% endblock %} @@ -157,7 +184,12 @@ { required: true, message: '请输入群组ID', trigger: 'blur' }, { pattern: /^\S+$/, message: '群组ID不能包含空格', trigger: 'blur' } ] - } + }, + // 趋势图相关数据 + trendDialogVisible: false, + trendLoading: false, + trendDays: 7, + trendChart: null } }, computed: { @@ -481,8 +513,96 @@ this.$message.error('批量移除失败: ' + error.message); }); }).catch(() => {}); + }, + // 查看消息趋势 + viewMessageTrend(group) { + this.currentGroupId = group.group_id; + this.currentGroupName = group.group_name || group.group_id; + this.trendDialogVisible = true; + this.loadMessageTrend(); + }, + + loadMessageTrend() { + this.trendLoading = true; + + axios.get(`/api/robot/group/${this.currentGroupId}/message_trend?days=${this.trendDays}`) + .then(response => { + if (response.data.success) { + this.renderTrendChart(response.data.data); + } else { + this.$message.error('加载消息趋势失败'); + } + this.trendLoading = false; + }) + .catch(error => { + console.error('加载消息趋势失败:', error); + this.$message.error('加载消息趋势失败: ' + error.message); + this.trendLoading = false; + }); + }, + + renderTrendChart(data) { + // 确保DOM元素已经渲染 + this.$nextTick(() => { + // 如果已有图表,先销毁 + if (this.trendChart) { + this.trendChart.destroy(); + } + + // 获取canvas元素 + const ctx = document.getElementById('messageTrendChart').getContext('2d'); + + // 创建新图表 + this.trendChart = new Chart(ctx, { + type: 'line', + data: { + labels: data.dates, + datasets: [{ + label: '消息数量', + data: data.counts, + backgroundColor: 'rgba(75, 192, 192, 0.2)', + borderColor: 'rgba(75, 192, 192, 1)', + borderWidth: 2, + tension: 0.3, + fill: true + }] + }, + options: { + responsive: true, + maintainAspectRatio: false, + plugins: { + title: { + display: true, + text: '群聊消息数量趋势' + }, + tooltip: { + mode: 'index', + intersect: false + }, + legend: { + position: 'top', + } + }, + scales: { + y: { + beginAtZero: true, + title: { + display: true, + text: '消息数量' + } + }, + x: { + ticks: { + maxRotation: 45, + minRotation: 45 + } + } + } + } + }); + }); } } }); -{% endblock %} +{% endblock %} \ No newline at end of file