diff --git a/data/tsp_assistant.db b/data/tsp_assistant.db index 5d476b1..bd0e77a 100644 Binary files a/data/tsp_assistant.db and b/data/tsp_assistant.db differ diff --git a/src/core/query_optimizer.py b/src/core/query_optimizer.py index 74e9fd6..914fa5c 100644 --- a/src/core/query_optimizer.py +++ b/src/core/query_optimizer.py @@ -236,25 +236,31 @@ class QueryOptimizer: logger.error(f"批量更新工单失败: {e}") return False - def get_analytics_optimized(self, days: int = 30) -> Dict[str, Any]: - """优化版分析数据查询""" + def get_analytics_optimized(self, days: int = 30, tenant_id: str = None) -> Dict[str, Any]: + """优化版分析数据查询(支持按租户筛选)""" start_time = time.time() try: with db_manager.get_session() as session: from datetime import datetime, timedelta - end_time = datetime.now() - start_time_query = end_time - timedelta(days=days-1) + # 查询工单 + wo_query = session.query(WorkOrder) + if tenant_id: + wo_query = wo_query.filter(WorkOrder.tenant_id == tenant_id) + workorders = wo_query.all() - # 批量查询所有需要的数据 - # 修改:查询所有工单,不限制时间范围 - workorders = session.query(WorkOrder).all() + # 查询预警 + alert_query = session.query(Alert) + if tenant_id: + alert_query = alert_query.filter(Alert.tenant_id == tenant_id) + alerts = alert_query.all() - # 修改:查询所有预警和对话,不限制时间范围 - alerts = session.query(Alert).all() - - conversations = session.query(Conversation).all() + # 查询对话 + conv_query = session.query(Conversation) + if tenant_id: + conv_query = conv_query.filter(Conversation.tenant_id == tenant_id) + conversations = conv_query.all() # 处理数据 analytics = self._process_analytics_data(workorders, alerts, conversations, days) diff --git a/src/integrations/feishu_longconn_service.py b/src/integrations/feishu_longconn_service.py index 6f7d1c7..e9ee7dc 100644 --- a/src/integrations/feishu_longconn_service.py +++ b/src/integrations/feishu_longconn_service.py @@ -294,28 +294,25 @@ class FeishuLongConnService: 这个方法会阻塞当前线程,持续监听飞书事件 """ logger.info("=" * 80) - logger.info("🚀 启动飞书长连接客户端") + logger.info(" 启动飞书长连接客户端") logger.info("=" * 80) logger.info(f"📋 配置信息:") logger.info(f" - App ID: {self.app_id}") - logger.info(f" - 模式: 事件订阅 2.0(长连接)") - logger.info(f" - 优势: 无需公网域名和 webhook 配置") - logger.info("=" * 80) - logger.info("💡 等待消息中... (按 Ctrl+C 停止)") + logger.info(" 等待消息中... (按 Ctrl+C 停止)") logger.info("=" * 80) try: # 创建长连接客户端 cli = lark.ws.Client(self.app_id, self.app_secret, event_handler=self.event_handler) - logger.info("🔌 正在建立与飞书服务器的连接...") + logger.info("正在建立与飞书服务器的连接...") # 启动长连接(会阻塞) cli.start() except KeyboardInterrupt: logger.info("") - logger.info("⏹️ 用户中断,停止飞书长连接客户端") + logger.info("用户中断,停止飞书长连接客户端") except Exception as e: logger.error(f"飞书长连接客户端异常: {e}", exc_info=True) raise diff --git a/src/integrations/flexible_field_mapper.py b/src/integrations/flexible_field_mapper.py index 4729e9b..242d553 100644 --- a/src/integrations/flexible_field_mapper.py +++ b/src/integrations/flexible_field_mapper.py @@ -341,7 +341,6 @@ class FlexibleFieldMapper: # 保存配置 self._save_current_config() - logger.info(f"添加字段映射: {feishu_field} -> {local_field}") return True except Exception as e: @@ -431,7 +430,6 @@ class FlexibleFieldMapper: 'mapped': True, 'value': value } - logger.debug(f"映射字段 {feishu_field} -> {local_field}: {str(value)[:50]}") else: conversion_stats['unmapped_fields'].append(feishu_field) conversion_stats['mapping_details'][feishu_field] = { @@ -439,9 +437,7 @@ class FlexibleFieldMapper: 'value': value, 'suggestions': self._suggest_mapping(feishu_field) } - logger.info(f"飞书字段 {feishu_field} 不存在于数据中") - logger.debug(f"字段转换完成: 已映射 {conversion_stats['mapped_fields']}, " - f"未映射 {len(conversion_stats['unmapped_fields'])}") + logger.debug(f"未映射 {len(conversion_stats['unmapped_fields'])}") return local_data, conversion_stats diff --git a/src/web/blueprints/analytics.py b/src/web/blueprints/analytics.py index 3e0447f..95739e0 100644 --- a/src/web/blueprints/analytics.py +++ b/src/web/blueprints/analytics.py @@ -4,49 +4,63 @@ 处理数据分析、报告生成等功能 """ -from flask import Blueprint, request, jsonify, send_file import os +import logging +from flask import Blueprint, request, jsonify, send_file +from src.core.query_optimizer import query_optimizer + +logger = logging.getLogger(__name__) analytics_bp = Blueprint('analytics', __name__, url_prefix='/api/analytics') +@analytics_bp.route('') +def get_analytics(): + """获取分析数据(支持租户筛选和时间范围)""" + try: + time_range = request.args.get('timeRange', '30') + tenant_id = request.args.get('tenant_id') + + try: + days = int(time_range) + except (ValueError, TypeError): + days = 30 + + analytics = query_optimizer.get_analytics_optimized(days, tenant_id=tenant_id) + return jsonify(analytics) + except Exception as e: + logger.error(f"获取分析数据失败: {e}") + return jsonify({"error": str(e)}), 500 + + @analytics_bp.route('/export') def export_analytics(): """导出分析报告""" try: - from src.web.service_manager import service_manager - from src.core.query_optimizer import query_optimizer from openpyxl import Workbook from openpyxl.styles import Font - # 生成Excel报告(使用数据库真实数据) analytics = query_optimizer.get_analytics_optimized(30) - # 创建工作簿 wb = Workbook() ws = wb.active ws.title = "分析报告" - - # 添加标题 ws['A1'] = 'TSP智能助手分析报告' ws['A1'].font = Font(size=16, bold=True) - - # 添加工单统计 ws['A3'] = '工单统计' ws['A3'].font = Font(bold=True) ws['A4'] = '总工单数' - ws['B4'] = analytics['workorders']['total'] + ws['B4'] = analytics.get('workorders', {}).get('total', 0) ws['A5'] = '待处理' - ws['B5'] = analytics['workorders']['open'] + ws['B5'] = analytics.get('workorders', {}).get('open', 0) ws['A6'] = '已解决' - ws['B6'] = analytics['workorders']['resolved'] + ws['B6'] = analytics.get('workorders', {}).get('resolved', 0) - # 保存文件 report_path = 'uploads/analytics_report.xlsx' os.makedirs('uploads', exist_ok=True) wb.save(report_path) return send_file(report_path, as_attachment=True, download_name='analytics_report.xlsx') - except Exception as e: + logger.error(f"导出分析报告失败: {e}") return jsonify({"error": str(e)}), 500 diff --git a/src/web/static/js/dashboard.js b/src/web/static/js/dashboard.js index d323abf..07ad26e 100644 --- a/src/web/static/js/dashboard.js +++ b/src/web/static/js/dashboard.js @@ -165,6 +165,12 @@ class TSPDashboard { if (woCreate) { woCreate.innerHTML = tenants.map(t => ``).join(''); } + // 数据分析租户筛选器 + const analyticsFilter = document.getElementById('analytics-tenant-filter'); + if (analyticsFilter) { + const cv = analyticsFilter.value; + analyticsFilter.innerHTML = '' + tenants.map(t => ``).join(''); + } } catch (e) { console.warn('加载租户列表失败:', e); } } diff --git a/src/web/static/js/modules/system.js b/src/web/static/js/modules/system.js index 3692548..35b16df 100644 --- a/src/web/static/js/modules/system.js +++ b/src/web/static/js/modules/system.js @@ -392,9 +392,11 @@ Object.assign(TSPDashboard.prototype, { const timeRange = document.getElementById('timeRange').value; const chartType = document.getElementById('chartType').value; const dataDimension = document.getElementById('dataDimension').value; + const tenantId = document.getElementById('analytics-tenant-filter')?.value || ''; - // 获取数据 - const response = await fetch(`/api/analytics?timeRange=${timeRange}&dimension=${dataDimension}`); + let url = `/api/analytics?timeRange=${timeRange}&dimension=${dataDimension}`; + if (tenantId) url += `&tenant_id=${encodeURIComponent(tenantId)}`; + const response = await fetch(url); const data = await response.json(); // 更新统计卡片 diff --git a/src/web/templates/dashboard.html b/src/web/templates/dashboard.html index d1df5b1..e30bdfa 100644 --- a/src/web/templates/dashboard.html +++ b/src/web/templates/dashboard.html @@ -1856,7 +1856,13 @@
-
+
+ + +
+
-
+
-
+
-
+