diff --git a/src/web/blueprints/alerts.py b/src/web/blueprints/alerts.py index 69b4b4a..41083d8 100644 --- a/src/web/blueprints/alerts.py +++ b/src/web/blueprints/alerts.py @@ -14,9 +14,68 @@ alerts_bp = Blueprint('alerts', __name__, url_prefix='/api/alerts') @alerts_bp.route('') @handle_api_errors def get_alerts(): - """获取预警列表""" - alerts = service_manager.get_assistant().get_active_alerts() - return jsonify(alerts) + """获取预警列表(分页)""" + try: + # 获取分页参数 + page = request.args.get('page', 1, type=int) + per_page = request.args.get('per_page', 10, type=int) + level_filter = request.args.get('level', '') + status_filter = request.args.get('status', '') + + # 从数据库获取分页数据 + from src.core.database import db_manager + from src.core.models import Alert + + with db_manager.get_session() as session: + # 构建查询 + query = session.query(Alert) + + # 应用过滤器 + if level_filter: + query = query.filter(Alert.level == level_filter) + if status_filter: + if status_filter == 'active': + query = query.filter(Alert.status == 'active') + elif status_filter == 'resolved': + query = query.filter(Alert.status == 'resolved') + + # 按创建时间倒序排列 + query = query.order_by(Alert.created_at.desc()) + + # 计算总数 + total = query.count() + + # 分页查询 + alerts = query.offset((page - 1) * per_page).limit(per_page).all() + + # 转换为字典 + alerts_data = [] + for alert in alerts: + alerts_data.append({ + 'id': alert.id, + 'rule_name': alert.rule_name, + 'message': alert.message, + 'level': alert.level, + 'status': alert.status, + 'source': alert.source, + 'metadata': alert.metadata, + 'created_at': alert.created_at.isoformat() if alert.created_at else None, + 'resolved_at': alert.resolved_at.isoformat() if alert.resolved_at else None + }) + + # 计算分页信息 + total_pages = (total + per_page - 1) // per_page + + return jsonify({ + 'alerts': alerts_data, + 'page': page, + 'per_page': per_page, + 'total': total, + 'total_pages': total_pages + }) + + except Exception as e: + return create_error_response(f"获取预警列表失败: {str(e)}", 500) @alerts_bp.route('', methods=['POST']) def create_alert(): diff --git a/src/web/blueprints/knowledge.py b/src/web/blueprints/knowledge.py index 9a9853e..094aade 100644 --- a/src/web/blueprints/knowledge.py +++ b/src/web/blueprints/knowledge.py @@ -29,18 +29,67 @@ def get_agent_assistant(): @knowledge_bp.route('') def get_knowledge(): - """获取知识库列表""" + """获取知识库列表(分页)""" try: # 获取分页参数 page = request.args.get('page', 1, type=int) per_page = request.args.get('per_page', 10, type=int) + category_filter = request.args.get('category', '') + verified_filter = request.args.get('verified', '') # 从数据库获取知识库数据 - knowledge_entries = get_assistant().knowledge_manager.get_knowledge_entries( - page=page, per_page=per_page - ) + from src.core.database import db_manager + from src.core.models import KnowledgeEntry - return jsonify(knowledge_entries) + with db_manager.get_session() as session: + # 构建查询 + query = session.query(KnowledgeEntry).filter(KnowledgeEntry.is_active == True) + + # 应用过滤器 + if category_filter: + query = query.filter(KnowledgeEntry.category == category_filter) + if verified_filter: + if verified_filter == 'true': + query = query.filter(KnowledgeEntry.is_verified == True) + elif verified_filter == 'false': + query = query.filter(KnowledgeEntry.is_verified == False) + + # 按创建时间倒序排列 + query = query.order_by(KnowledgeEntry.created_at.desc()) + + # 计算总数 + total = query.count() + + # 分页查询 + knowledge_entries = query.offset((page - 1) * per_page).limit(per_page).all() + + # 转换为字典 + knowledge_data = [] + for entry in knowledge_entries: + knowledge_data.append({ + 'id': entry.id, + 'question': entry.question, + 'answer': entry.answer, + 'category': entry.category, + 'confidence_score': entry.confidence_score, + 'usage_count': entry.usage_count, + 'is_verified': entry.is_verified, + 'is_active': entry.is_active, + 'created_at': entry.created_at.isoformat() if entry.created_at else None, + 'updated_at': entry.updated_at.isoformat() if entry.updated_at else None + }) + + # 计算分页信息 + total_pages = (total + per_page - 1) // per_page + + return jsonify({ + 'knowledge': knowledge_data, + 'page': page, + 'per_page': per_page, + 'total': total, + 'total_pages': total_pages + }) + except Exception as e: return jsonify({"error": str(e)}), 500 diff --git a/src/web/blueprints/workorders.py b/src/web/blueprints/workorders.py index 8ada1d0..8f9395c 100644 --- a/src/web/blueprints/workorders.py +++ b/src/web/blueprints/workorders.py @@ -100,17 +100,66 @@ def _ensure_workorder_template_file() -> str: @workorders_bp.route('') def get_workorders(): - """获取工单列表(优化版)""" + """获取工单列表(分页)""" try: + # 获取分页参数 + page = request.args.get('page', 1, type=int) + per_page = request.args.get('per_page', 10, type=int) status_filter = request.args.get('status', '') priority_filter = request.args.get('priority', '') - # 使用优化后的查询 - result = query_optimizer.get_workorders_optimized( - status_filter=status_filter, priority_filter=priority_filter - ) + # 从数据库获取分页数据 + from src.core.database import db_manager + from src.core.models import WorkOrder - return jsonify(result) + with db_manager.get_session() as session: + # 构建查询 + query = session.query(WorkOrder) + + # 应用过滤器 + if status_filter: + query = query.filter(WorkOrder.status == status_filter) + if priority_filter: + query = query.filter(WorkOrder.priority == priority_filter) + + # 按创建时间倒序排列 + query = query.order_by(WorkOrder.created_at.desc()) + + # 计算总数 + total = query.count() + + # 分页查询 + workorders = query.offset((page - 1) * per_page).limit(per_page).all() + + # 转换为字典 + workorders_data = [] + for workorder in workorders: + workorders_data.append({ + 'id': workorder.id, + 'order_id': workorder.order_id, + 'title': workorder.title, + 'description': workorder.description, + 'category': workorder.category, + 'priority': workorder.priority, + 'status': workorder.status, + 'user_id': workorder.user_id, + 'assigned_to': workorder.assigned_to, + 'created_at': workorder.created_at.isoformat() if workorder.created_at else None, + 'updated_at': workorder.updated_at.isoformat() if workorder.updated_at else None, + 'resolved_at': workorder.resolved_at.isoformat() if workorder.resolved_at else None + }) + + # 计算分页信息 + total_pages = (total + per_page - 1) // per_page + + return jsonify({ + 'workorders': workorders_data, + 'page': page, + 'per_page': per_page, + 'total': total, + 'total_pages': total_pages + }) + except Exception as e: return jsonify({"error": str(e)}), 500 diff --git a/src/web/static/js/dashboard.js b/src/web/static/js/dashboard.js index b0cf150..5f48995 100644 --- a/src/web/static/js/dashboard.js +++ b/src/web/static/js/dashboard.js @@ -149,6 +149,13 @@ class TSPDashboard { this.cache = new Map(); this.cacheTimeout = 30000; // 30秒缓存 + // 分页配置 + this.paginationConfig = { + defaultPageSize: 10, + pageSizeOptions: [5, 10, 20, 50], + maxVisiblePages: 5 + }; + this.init(); this.restorePageState(); this.initLanguage(); @@ -342,6 +349,123 @@ class TSPDashboard { } return null; } + + // 统一分页组件 + createPaginationComponent(data, containerId, loadFunction, itemName = '条记录') { + const paginationContainer = document.getElementById(containerId); + if (!paginationContainer) return; + + const { page, total_pages, total, per_page } = data; + + if (total_pages <= 1) { + paginationContainer.innerHTML = ''; + return; + } + + let paginationHtml = ` +
+
+ 共 ${total} ${itemName},第 ${page} / ${total_pages} 页 +
+ + +
+
+ +
+ `; + + paginationContainer.innerHTML = paginationHtml; + } + + // 加载指定页面 + loadPage(loadFunction, page, containerId) { + const pageSize = this.getPageSize(containerId); + if (loadFunction === 'loadAlerts') { + this.loadAlerts(page, true); + } else if (loadFunction === 'loadWorkOrders') { + this.loadWorkOrders(page, true); + } else if (loadFunction === 'loadKnowledge') { + this.loadKnowledge(page); + } else if (loadFunction === 'loadConversationHistory') { + this.loadConversationHistory(page); + } + } + + // 改变每页显示条数 + changePageSize(containerId, pageSize, loadFunction) { + // 保存页面大小到localStorage + localStorage.setItem(`pageSize_${containerId}`, pageSize); + + // 重新加载第一页 + if (loadFunction === 'loadAlerts') { + this.loadAlerts(1, true); + } else if (loadFunction === 'loadWorkOrders') { + this.loadWorkOrders(1, true); + } else if (loadFunction === 'loadKnowledge') { + this.loadKnowledge(1); + } else if (loadFunction === 'loadConversationHistory') { + this.loadConversationHistory(1); + } + } + + // 获取页面大小 + getPageSize(containerId) { + const saved = localStorage.getItem(`pageSize_${containerId}`); + return saved ? parseInt(saved) : this.paginationConfig.defaultPageSize; + } setCachedData(key, data) { this.cache.set(key, { @@ -1348,14 +1472,27 @@ class TSPDashboard { } // 预警管理 - async loadAlerts() { + async loadAlerts(page = 1, forceRefresh = false) { + const cacheKey = `alerts_page_${page}`; + + if (!forceRefresh && this.cache.has(cacheKey)) { + const cachedData = this.cache.get(cacheKey); + this.updateAlertsDisplay(cachedData.alerts); + this.updateAlertsPagination(cachedData); + return; + } + try { - const response = await fetch('/api/alerts'); - const alerts = await response.json(); - this.updateAlertsDisplay(alerts); - this.updateAlertStatistics(alerts); + const pageSize = this.getPageSize('alerts-pagination'); + const response = await fetch(`/api/alerts?page=${page}&per_page=${pageSize}`); + const data = await response.json(); + + this.cache.set(cacheKey, data); + this.updateAlertsDisplay(data.alerts); + this.updateAlertsPagination(data); } catch (error) { console.error('加载预警失败:', error); + this.showNotification('加载预警失败', 'error'); } } @@ -1418,6 +1555,10 @@ class TSPDashboard { container.innerHTML = headerHtml + alertsHtml; } + updateAlertsPagination(data) { + this.createPaginationComponent(data, 'alerts-pagination', 'loadAlerts', '条预警'); + } + updateAlertStatistics(alerts) { const stats = alerts.reduce((acc, alert) => { acc[alert.level] = (acc[alert.level] || 0) + 1; @@ -1517,7 +1658,8 @@ class TSPDashboard { // 知识库管理 async loadKnowledge(page = 1) { try { - const response = await fetch(`/api/knowledge?page=${page}&per_page=10`); + const pageSize = this.getPageSize('knowledge-pagination'); + const response = await fetch(`/api/knowledge?page=${page}&per_page=${pageSize}`); const data = await response.json(); if (data.knowledge) { @@ -1596,51 +1738,7 @@ class TSPDashboard { } updateKnowledgePagination(data) { - const paginationContainer = document.getElementById('knowledge-pagination'); - if (!paginationContainer) return; - - const { page, total_pages, total } = data; - - if (total_pages <= 1) { - paginationContainer.innerHTML = ''; - return; - } - - let paginationHtml = ` -
-
- 共 ${total} 条知识,第 ${page} / ${total_pages} 页 -
- -
- `; - - paginationContainer.innerHTML = paginationHtml; + this.createPaginationComponent(data, 'knowledge-pagination', 'loadKnowledge', '条知识'); } async searchKnowledge() { @@ -1919,13 +2017,25 @@ class TSPDashboard { } // 工单管理 - async loadWorkOrders(forceRefresh = false) { + async loadWorkOrders(page = 1, forceRefresh = false) { + const cacheKey = `workorders_page_${page}`; + + if (!forceRefresh && this.cache.has(cacheKey)) { + const cachedData = this.cache.get(cacheKey); + this.updateWorkOrdersDisplay(cachedData.workorders); + this.updateWorkOrdersPagination(cachedData); + return; + } + try { const statusFilter = document.getElementById('workorder-status-filter')?.value || 'all'; const priorityFilter = document.getElementById('workorder-priority-filter')?.value || 'all'; let url = '/api/workorders'; const params = new URLSearchParams(); + params.append('page', page); + const pageSize = this.getPageSize('workorders-pagination'); + params.append('per_page', pageSize.toString()); if (statusFilter !== 'all') params.append('status', statusFilter); if (priorityFilter !== 'all') params.append('priority', priorityFilter); @@ -1948,15 +2058,12 @@ class TSPDashboard { throw new Error(`HTTP ${response.status}: ${response.statusText}`); } - const workorders = await response.json(); - this.updateWorkOrdersDisplay(workorders); - this.updateWorkOrderStatistics(workorders); + const data = await response.json(); + this.updateWorkOrdersDisplay(data.workorders); + this.updateWorkOrdersPagination(data); // 更新缓存 - this.cache.set('workorders', { - data: workorders, - timestamp: Date.now() - }); + this.cache.set(cacheKey, data); } catch (error) { console.error('加载工单失败:', error); @@ -2023,6 +2130,10 @@ class TSPDashboard { container.innerHTML = headerHtml + workordersHtml; } + updateWorkOrdersPagination(data) { + this.createPaginationComponent(data, 'workorders-pagination', 'loadWorkOrders', '个工单'); + } + updateWorkOrderStatistics(workorders) { const stats = workorders.reduce((acc, wo) => { acc.total = (acc.total || 0) + 1; @@ -2626,14 +2737,15 @@ class TSPDashboard { } // 对话历史管理 - async loadConversationHistory(page = 1, perPage = 10) { + async loadConversationHistory(page = 1) { try { - const response = await fetch(`/api/conversations?page=${page}&per_page=${perPage}`); + const pageSize = this.getPageSize('conversations-pagination'); + const response = await fetch(`/api/conversations?page=${page}&per_page=${pageSize}`); const data = await response.json(); - if (data.success) { + if (data.conversations) { this.renderConversationList(data.conversations || []); - this.renderConversationPagination(data.pagination || {}); + this.updateConversationPagination(data); this.updateConversationStats(data.stats || {}); } else { throw new Error(data.error || '加载对话历史失败'); @@ -2688,36 +2800,8 @@ class TSPDashboard { container.innerHTML = html; } - renderConversationPagination(pagination) { - const container = document.getElementById('conversation-pagination'); - if (!pagination || !pagination.total_pages || pagination.total_pages <= 1) { - container.innerHTML = ''; - return; - } - - const currentPage = pagination.current_page || 1; - const totalPages = pagination.total_pages; - - let html = ''; - container.innerHTML = html; + updateConversationPagination(data) { + this.createPaginationComponent(data, 'conversations-pagination', 'loadConversationHistory', '条对话'); } updateConversationStats(stats) { diff --git a/src/web/templates/dashboard.html b/src/web/templates/dashboard.html index d7e0e5f..406e620 100644 --- a/src/web/templates/dashboard.html +++ b/src/web/templates/dashboard.html @@ -897,6 +897,9 @@ +
+ +
@@ -1015,6 +1018,9 @@ +
+ +
@@ -1305,7 +1311,7 @@ -
+