From e0316af12ef0d20dcc7be995f231095cad1fef56 Mon Sep 17 00:00:00 2001 From: Jeason <1710884619@qq.com> Date: Wed, 8 Apr 2026 08:25:12 +0800 Subject: [PATCH] =?UTF-8?q?refactor:=20=E6=9E=B6=E6=9E=84=E6=BC=94?= =?UTF-8?q?=E8=BF=9B=20=20Repository=20=E5=B1=82=20+=20=E6=97=A7=E4=BB=A3?= =?UTF-8?q?=E7=A0=81=E6=B8=85=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 任务 1.1: 引入 Repository 层 - BaseRepository 基类:自动 tenant_id 过滤、CRUD、分页、批量删除 - WorkOrderRepository: 工单列表(状态/优先级过滤)、飞书记录查找 - AlertRepository: 预警列表(级别/活跃过滤)、解决预警 - ChatSessionRepository: 会话列表(状态/搜索过滤) - ConversationRepository: 按 session_id 查对话 - KnowledgeRepository: 知识库列表(分类/验证过滤) 任务 10: 清理旧代码 - 删除 src/web/static/js/core/ 目录(5个文件) - 删除 src/web/static/js/services/ 目录 - 删除 src/web/static/js/components/ 目录 - 删除 src/web/static/js/pages/ 目录(12个文件) - 修复 index.html/chat.html/chat_http.html 中对已删除 JS 的引用 --- .kiro/specs/architecture-evolution/design.md | 0 src/repositories/__init__.py | 1 + src/repositories/alert_repo.py | 24 + src/repositories/base.py | 121 +++ src/repositories/conversation_repo.py | 43 + src/repositories/knowledge_repo.py | 20 + src/repositories/workorder_repo.py | 28 + src/web/static/js/components/AlertManager.js | 355 --------- .../js/components/NotificationManager.js | 137 ---- src/web/static/js/components/modal.js | 418 ---------- src/web/static/js/components/navbar.js | 414 ---------- src/web/static/js/components/sidebar.js | 235 ------ src/web/static/js/core/api.js | 472 ----------- src/web/static/js/core/router.js | 466 ----------- src/web/static/js/core/store.js | 203 ----- src/web/static/js/core/utils.js | 431 ---------- src/web/static/js/core/websocket.js | 441 ----------- src/web/static/js/pages/agent.js | 560 ------------- src/web/static/js/pages/alerts.js | 738 ------------------ src/web/static/js/pages/chat.js | 33 - src/web/static/js/pages/dashboard.js | 454 ----------- src/web/static/js/pages/feishu.js | 451 ----------- src/web/static/js/pages/knowledge.js | 673 ---------------- src/web/static/js/pages/login.js | 216 ----- src/web/static/js/pages/monitoring.js | 33 - src/web/static/js/pages/notfound.js | 142 ---- src/web/static/js/pages/settings.js | 428 ---------- src/web/static/js/pages/vehicle.js | 402 ---------- src/web/static/js/pages/workorders.js | 549 ------------- src/web/static/js/services/api.js | 160 ---- src/web/templates/chat.html | 2 +- src/web/templates/chat_http.html | 2 +- src/web/templates/index.html | 2 +- 33 files changed, 240 insertions(+), 8414 deletions(-) create mode 100644 .kiro/specs/architecture-evolution/design.md create mode 100644 src/repositories/__init__.py create mode 100644 src/repositories/alert_repo.py create mode 100644 src/repositories/base.py create mode 100644 src/repositories/conversation_repo.py create mode 100644 src/repositories/knowledge_repo.py create mode 100644 src/repositories/workorder_repo.py delete mode 100644 src/web/static/js/components/AlertManager.js delete mode 100644 src/web/static/js/components/NotificationManager.js delete mode 100644 src/web/static/js/components/modal.js delete mode 100644 src/web/static/js/components/navbar.js delete mode 100644 src/web/static/js/components/sidebar.js delete mode 100644 src/web/static/js/core/api.js delete mode 100644 src/web/static/js/core/router.js delete mode 100644 src/web/static/js/core/store.js delete mode 100644 src/web/static/js/core/utils.js delete mode 100644 src/web/static/js/core/websocket.js delete mode 100644 src/web/static/js/pages/agent.js delete mode 100644 src/web/static/js/pages/alerts.js delete mode 100644 src/web/static/js/pages/chat.js delete mode 100644 src/web/static/js/pages/dashboard.js delete mode 100644 src/web/static/js/pages/feishu.js delete mode 100644 src/web/static/js/pages/knowledge.js delete mode 100644 src/web/static/js/pages/login.js delete mode 100644 src/web/static/js/pages/monitoring.js delete mode 100644 src/web/static/js/pages/notfound.js delete mode 100644 src/web/static/js/pages/settings.js delete mode 100644 src/web/static/js/pages/vehicle.js delete mode 100644 src/web/static/js/pages/workorders.js delete mode 100644 src/web/static/js/services/api.js diff --git a/.kiro/specs/architecture-evolution/design.md b/.kiro/specs/architecture-evolution/design.md new file mode 100644 index 0000000..e69de29 diff --git a/src/repositories/__init__.py b/src/repositories/__init__.py new file mode 100644 index 0000000..a1115ad --- /dev/null +++ b/src/repositories/__init__.py @@ -0,0 +1 @@ +# Repository 层 — 统一数据访问,自动 tenant_id 过滤 diff --git a/src/repositories/alert_repo.py b/src/repositories/alert_repo.py new file mode 100644 index 0000000..a1b20df --- /dev/null +++ b/src/repositories/alert_repo.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- +from sqlalchemy import desc +from src.core.models import Alert +from .base import BaseRepository + + +class AlertRepository(BaseRepository): + model_class = Alert + + def list_alerts(self, tenant_id=None, page=1, per_page=20, level=None, is_active=None): + filters = {} + if level: + filters['level'] = level + if is_active is not None: + filters['is_active'] = is_active + return self.list(tenant_id=tenant_id, page=page, per_page=per_page, + filters=filters, order_by=desc(Alert.created_at)) + + def resolve(self, alert_id: int, tenant_id=None): + from datetime import datetime + return self.update(alert_id, {'is_active': False, 'resolved_at': datetime.now()}, tenant_id=tenant_id) + + +alert_repo = AlertRepository() diff --git a/src/repositories/base.py b/src/repositories/base.py new file mode 100644 index 0000000..9e45b5f --- /dev/null +++ b/src/repositories/base.py @@ -0,0 +1,121 @@ +# -*- coding: utf-8 -*- +""" +Repository 基类 +所有数据访问通过 Repository 层,自动附加 tenant_id 过滤。 +""" +import logging +from typing import Any, Dict, List, Optional, Type +from sqlalchemy.orm import Session +from src.core.database import db_manager +from src.core.models import DEFAULT_TENANT + +logger = logging.getLogger(__name__) + + +class BaseRepository: + """ + Repository 基类。子类只需指定 model_class 和 tenant_field。 + 所有查询自动按 tenant_id 过滤(如果模型有该字段)。 + """ + model_class = None # 子类必须设置 + tenant_field = 'tenant_id' # 默认租户字段名 + + def _base_query(self, session: Session, tenant_id: str = None): + """构建带 tenant_id 过滤的基础查询""" + q = session.query(self.model_class) + if tenant_id and hasattr(self.model_class, self.tenant_field): + q = q.filter(getattr(self.model_class, self.tenant_field) == tenant_id) + return q + + def get_by_id(self, id: int, tenant_id: str = None) -> Optional[Dict]: + """按 ID 查询""" + with db_manager.get_session() as session: + q = self._base_query(session, tenant_id).filter(self.model_class.id == id) + obj = q.first() + return self._to_dict(obj) if obj else None + + def list(self, tenant_id: str = None, page: int = 1, per_page: int = 20, + filters: Dict = None, order_by=None) -> Dict[str, Any]: + """分页列表查询""" + with db_manager.get_session() as session: + q = self._base_query(session, tenant_id) + if filters: + for field, value in filters.items(): + if value is not None and hasattr(self.model_class, field): + q = q.filter(getattr(self.model_class, field) == value) + if order_by is not None: + q = q.order_by(order_by) + total = q.count() + items = q.offset((page - 1) * per_page).limit(per_page).all() + return { + 'items': [self._to_dict(item) for item in items], + 'page': page, 'per_page': per_page, + 'total': total, 'total_pages': (total + per_page - 1) // per_page + } + + def create(self, data: Dict, tenant_id: str = None) -> Dict: + """创建记录,自动设置 tenant_id""" + with db_manager.get_session() as session: + if tenant_id and hasattr(self.model_class, self.tenant_field): + data[self.tenant_field] = tenant_id + elif hasattr(self.model_class, self.tenant_field) and self.tenant_field not in data: + data[self.tenant_field] = DEFAULT_TENANT + # 只保留模型有的字段 + valid = {k: v for k, v in data.items() if hasattr(self.model_class, k) and not isinstance(v, (dict, list))} + obj = self.model_class(**valid) + session.add(obj) + session.flush() + result = self._to_dict(obj) + return result + + def update(self, id: int, data: Dict, tenant_id: str = None) -> Optional[Dict]: + """更新记录""" + with db_manager.get_session() as session: + q = self._base_query(session, tenant_id).filter(self.model_class.id == id) + obj = q.first() + if not obj: + return None + for k, v in data.items(): + if hasattr(obj, k) and k not in ('id', 'tenant_id'): + setattr(obj, k, v) + session.flush() + return self._to_dict(obj) + + def delete(self, id: int, tenant_id: str = None) -> bool: + """删除记录""" + with db_manager.get_session() as session: + q = self._base_query(session, tenant_id).filter(self.model_class.id == id) + obj = q.first() + if not obj: + return False + session.delete(obj) + return True + + def batch_delete(self, ids: List[int], tenant_id: str = None) -> int: + """批量删除,返回实际删除数量""" + with db_manager.get_session() as session: + q = self._base_query(session, tenant_id).filter(self.model_class.id.in_(ids)) + count = q.delete(synchronize_session='fetch') + return count + + def count(self, tenant_id: str = None, filters: Dict = None) -> int: + """计数""" + with db_manager.get_session() as session: + q = self._base_query(session, tenant_id) + if filters: + for field, value in filters.items(): + if value is not None and hasattr(self.model_class, field): + q = q.filter(getattr(self.model_class, field) == value) + return q.count() + + def _to_dict(self, obj) -> Dict: + """将 ORM 对象转为字典。子类可覆盖。""" + if hasattr(obj, 'to_dict'): + return obj.to_dict() + result = {} + for col in obj.__table__.columns: + val = getattr(obj, col.name) + if hasattr(val, 'isoformat'): + val = val.isoformat() + result[col.name] = val + return result diff --git a/src/repositories/conversation_repo.py b/src/repositories/conversation_repo.py new file mode 100644 index 0000000..f3461c9 --- /dev/null +++ b/src/repositories/conversation_repo.py @@ -0,0 +1,43 @@ +# -*- coding: utf-8 -*- +from sqlalchemy import desc +from src.core.models import ChatSession, Conversation +from src.core.database import db_manager +from .base import BaseRepository + + +class ChatSessionRepository(BaseRepository): + model_class = ChatSession + + def list_sessions(self, tenant_id=None, page=1, per_page=20, status=None, search=None): + with db_manager.get_session() as session: + q = self._base_query(session, tenant_id) + if status: + q = q.filter(ChatSession.status == status) + if search: + from sqlalchemy import or_ + q = q.filter(or_( + ChatSession.title.contains(search), + ChatSession.session_id.contains(search) + )) + q = q.order_by(desc(ChatSession.updated_at)) + total = q.count() + items = q.offset((page - 1) * per_page).limit(per_page).all() + return { + 'sessions': [self._to_dict(s) for s in items], + 'page': page, 'per_page': per_page, + 'total': total, 'total_pages': (total + per_page - 1) // per_page + } + + +class ConversationRepository(BaseRepository): + model_class = Conversation + + def get_by_session_id(self, session_id: str, tenant_id=None): + with db_manager.get_session() as session: + q = self._base_query(session, tenant_id).filter(Conversation.session_id == session_id) + items = q.order_by(Conversation.timestamp).all() + return [self._to_dict(c) for c in items] + + +chat_session_repo = ChatSessionRepository() +conversation_repo = ConversationRepository() diff --git a/src/repositories/knowledge_repo.py b/src/repositories/knowledge_repo.py new file mode 100644 index 0000000..068e00d --- /dev/null +++ b/src/repositories/knowledge_repo.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +from sqlalchemy import desc +from src.core.models import KnowledgeEntry +from .base import BaseRepository + + +class KnowledgeRepository(BaseRepository): + model_class = KnowledgeEntry + + def list_knowledge(self, tenant_id=None, page=1, per_page=20, category=None, verified=None): + filters = {} + if category: + filters['category'] = category + if verified is not None: + filters['is_verified'] = verified + return self.list(tenant_id=tenant_id, page=page, per_page=per_page, + filters=filters, order_by=desc(KnowledgeEntry.updated_at)) + + +knowledge_repo = KnowledgeRepository() diff --git a/src/repositories/workorder_repo.py b/src/repositories/workorder_repo.py new file mode 100644 index 0000000..8fac815 --- /dev/null +++ b/src/repositories/workorder_repo.py @@ -0,0 +1,28 @@ +# -*- coding: utf-8 -*- +from sqlalchemy import desc +from src.core.models import WorkOrder +from .base import BaseRepository + + +class WorkOrderRepository(BaseRepository): + model_class = WorkOrder + + def list_workorders(self, tenant_id=None, page=1, per_page=20, status=None, priority=None): + """工单列表(带状态和优先级过滤)""" + filters = {} + if status: + filters['status'] = status + if priority: + filters['priority'] = priority + return self.list(tenant_id=tenant_id, page=page, per_page=per_page, + filters=filters, order_by=desc(WorkOrder.created_at)) + + def find_by_feishu_record_id(self, feishu_record_id: str): + """按飞书记录 ID 查找""" + from src.core.database import db_manager + with db_manager.get_session() as session: + obj = session.query(WorkOrder).filter(WorkOrder.feishu_record_id == feishu_record_id).first() + return self._to_dict(obj) if obj else None + + +workorder_repo = WorkOrderRepository() diff --git a/src/web/static/js/components/AlertManager.js b/src/web/static/js/components/AlertManager.js deleted file mode 100644 index 41e62b1..0000000 --- a/src/web/static/js/components/AlertManager.js +++ /dev/null @@ -1,355 +0,0 @@ -/** - * 预警管理组件 - * 专门处理预警相关的功能 - */ - -class AlertManager { - constructor() { - this.refreshInterval = null; - this.init(); - } - - init() { - this.bindEvents(); - this.loadInitialData(); - this.startAutoRefresh(); - } - - bindEvents() { - // 监控控制按钮 - this.bindButton('start-monitor', () => this.startMonitoring()); - this.bindButton('stop-monitor', () => this.stopMonitoring()); - this.bindButton('check-alerts', () => this.checkAlerts()); - this.bindButton('refresh-alerts', () => this.loadAlerts()); - - // 预警过滤和排序 - this.bindSelect('alert-filter', () => this.updateAlertsDisplay()); - this.bindSelect('alert-sort', () => this.updateAlertsDisplay()); - } - - bindButton(id, handler) { - const element = document.getElementById(id); - if (element) { - element.addEventListener('click', handler); - } - } - - bindSelect(id, handler) { - const element = document.getElementById(id); - if (element) { - element.addEventListener('change', handler); - } - } - - async loadInitialData() { - store.setLoading(true); - try { - await Promise.all([ - this.loadAlerts(), - this.loadRules(), - this.loadMonitorStatus() - ]); - } catch (error) { - console.error('加载初始数据失败:', error); - notificationManager.error('加载数据失败,请刷新页面重试'); - } finally { - store.setLoading(false); - } - } - - startAutoRefresh() { - // 清除现有定时器 - if (this.refreshInterval) { - clearInterval(this.refreshInterval); - } - - // 每10秒刷新一次预警 - this.refreshInterval = setInterval(() => { - this.loadAlerts(); - }, 10000); - } - - // 监控控制方法 - async startMonitoring() { - try { - store.setLoading(true); - const result = await apiService.startMonitoring(); - - if (result.success) { - notificationManager.success('监控已启动'); - await this.loadMonitorStatus(); - } else { - notificationManager.error(result.message || '启动监控失败'); - } - } catch (error) { - console.error('启动监控失败:', error); - notificationManager.error('启动监控失败'); - } finally { - store.setLoading(false); - } - } - - async stopMonitoring() { - try { - store.setLoading(true); - const result = await apiService.stopMonitoring(); - - if (result.success) { - notificationManager.success('监控已停止'); - await this.loadMonitorStatus(); - } else { - notificationManager.error(result.message || '停止监控失败'); - } - } catch (error) { - console.error('停止监控失败:', error); - notificationManager.error('停止监控失败'); - } finally { - store.setLoading(false); - } - } - - async checkAlerts() { - try { - store.setLoading(true); - await this.loadAlerts(); - notificationManager.success('预警检查完成'); - } catch (error) { - console.error('检查预警失败:', error); - notificationManager.error('检查预警失败'); - } finally { - store.setLoading(false); - } - } - - // 数据加载方法 - async loadAlerts() { - try { - const data = await apiService.getAlerts(); - store.updateAlerts(data); - this.updateAlertsDisplay(); - } catch (error) { - console.error('加载预警失败:', error); - } - } - - async loadRules() { - try { - const data = await apiService.getRules(); - store.updateRules(data); - this.updateRulesDisplay(); - } catch (error) { - console.error('加载规则失败:', error); - } - } - - async loadMonitorStatus() { - try { - const data = await apiService.getMonitorStatus(); - store.updateMonitorStatus(data.monitor_status); - this.updateMonitorStatusDisplay(); - } catch (error) { - console.error('加载监控状态失败:', error); - } - } - - // 显示更新方法 - updateAlertsDisplay() { - const alerts = store.getSortedAlerts('timestamp', 'desc'); - const container = document.getElementById('alerts-container'); - - if (!container) return; - - if (alerts.length === 0) { - container.innerHTML = '

暂无预警
'; - return; - } - - container.innerHTML = alerts.map(alert => this.createAlertElement(alert)).join(''); - } - - createAlertElement(alert) { - const levelClass = this.getLevelClass(alert.level); - const typeText = this.getTypeText(alert.alert_type); - const levelText = this.getLevelText(alert.level); - const timeText = this.formatTime(alert.timestamp); - - return ` -
-
-
-
-
- ${levelText} - ${typeText} - ${timeText} -
-
${alert.title}
-

${alert.description}

-
- - -
-
-
-
-
- `; - } - - updateRulesDisplay() { - const rules = store.getState().rules; - const container = document.getElementById('rules-container'); - - if (!container) return; - - if (rules.length === 0) { - container.innerHTML = '

暂无规则
'; - return; - } - - container.innerHTML = rules.map(rule => this.createRuleElement(rule)).join(''); - } - - createRuleElement(rule) { - const enabledText = rule.enabled ? '启用' : '禁用'; - - return ` - - ${rule.name} - ${rule.alert_type} - ${rule.level} - ${rule.threshold} - ${enabledText} - - - - - - `; - } - - updateMonitorStatusDisplay() { - const status = store.getState().monitorStatus; - const element = document.getElementById('monitor-status'); - - if (!element) return; - - const statusConfig = { - 'running': { icon: 'text-success', text: '运行中' }, - 'stopped': { icon: 'text-danger', text: '已停止' }, - 'unknown': { icon: 'text-warning', text: '未知' } - }; - - const config = statusConfig[status] || statusConfig.unknown; - - element.innerHTML = ` - 监控状态: ${config.text} - `; - } - - // 工具方法 - getLevelClass(level) { - const levelMap = { - 'critical': 'danger', - 'error': 'danger', - 'warning': 'warning', - 'info': 'info' - }; - return levelMap[level] || 'secondary'; - } - - getLevelText(level) { - const levelMap = { - 'critical': '严重', - 'error': '错误', - 'warning': '警告', - 'info': '信息' - }; - return levelMap[level] || level; - } - - getTypeText(type) { - const typeMap = { - 'performance': '性能', - 'quality': '质量', - 'volume': '量级', - 'system': '系统', - 'business': '业务' - }; - return typeMap[type] || type; - } - - formatTime(timestamp) { - const date = new Date(timestamp); - const now = new Date(); - const diff = now - date; - - if (diff < 60000) return '刚刚'; - if (diff < 3600000) return `${Math.floor(diff / 60000)}分钟前`; - if (diff < 86400000) return `${Math.floor(diff / 3600000)}小时前`; - return date.toLocaleDateString(); - } - - // 预警操作方法 - async acknowledgeAlert(alertId) { - try { - await apiService.updateAlert(alertId, { acknowledged: true }); - notificationManager.success('预警已确认'); - await this.loadAlerts(); - } catch (error) { - console.error('确认预警失败:', error); - notificationManager.error('确认预警失败'); - } - } - - viewAlertDetail(alertId) { - // 这里可以实现查看详情的逻辑 - notificationManager.info('详情查看功能开发中'); - } - - // 规则操作方法 - editRule(ruleName) { - const rule = store.getState().rules.find(r => r.name === ruleName); - if (!rule) { - notificationManager.error('规则不存在'); - return; - } - - // 填充编辑表单 - document.getElementById('edit-rule-name-original').value = rule.name; - document.getElementById('edit-rule-name').value = rule.name; - document.getElementById('edit-rule-type').value = rule.alert_type; - document.getElementById('edit-rule-level').value = rule.level; - document.getElementById('edit-rule-threshold').value = rule.threshold; - document.getElementById('edit-rule-description').value = rule.description || ''; - document.getElementById('edit-rule-condition').value = rule.condition; - document.getElementById('edit-rule-interval').value = rule.check_interval; - document.getElementById('edit-rule-cooldown').value = rule.cooldown; - document.getElementById('edit-rule-enabled').checked = rule.enabled; - - // 显示编辑模态框 - const modal = new bootstrap.Modal(document.getElementById('editRuleModal')); - modal.show(); - } - - async deleteRule(ruleName) { - if (!confirm(`确定要删除规则 "${ruleName}" 吗?`)) return; - - try { - await apiService.deleteRule(ruleName); - notificationManager.success('规则删除成功'); - await this.loadRules(); - } catch (error) { - console.error('删除规则失败:', error); - notificationManager.error('删除规则失败'); - } - } -} diff --git a/src/web/static/js/components/NotificationManager.js b/src/web/static/js/components/NotificationManager.js deleted file mode 100644 index e00b790..0000000 --- a/src/web/static/js/components/NotificationManager.js +++ /dev/null @@ -1,137 +0,0 @@ -/** - * 通知管理组件 - * 统一处理应用内通知显示 - */ - -class NotificationManager { - constructor() { - this.container = null; - this.init(); - } - - init() { - // 创建通知容器 - this.container = document.createElement('div'); - this.container.className = 'notification-container position-fixed'; - this.container.style.cssText = ` - top: 20px; - right: 20px; - z-index: 9999; - max-width: 400px; - `; - document.body.appendChild(this.container); - - // 监听状态变化 - store.subscribe((prevState, newState) => { - if (prevState.notifications !== newState.notifications) { - this.renderNotifications(newState.notifications); - } - }); - } - - renderNotifications(notifications) { - this.container.innerHTML = ''; - - notifications.forEach(notification => { - const notificationEl = this.createNotificationElement(notification); - this.container.appendChild(notificationEl); - }); - } - - createNotificationElement(notification) { - const div = document.createElement('div'); - const typeClass = this.getTypeClass(notification.type); - - div.className = `alert alert-${typeClass} alert-dismissible fade show shadow`; - div.style.cssText = ` - margin-bottom: 10px; - border-radius: 8px; - border: none; - `; - - div.innerHTML = ` -
- -
- ${notification.title || this.getDefaultTitle(notification.type)} -
${notification.message}
-
- -
- `; - - // 添加关闭事件 - const closeBtn = div.querySelector('.btn-close'); - closeBtn.addEventListener('click', () => { - store.removeNotification(notification.id); - }); - - return div; - } - - getTypeClass(type) { - const typeMap = { - 'success': 'success', - 'error': 'danger', - 'warning': 'warning', - 'info': 'info' - }; - return typeMap[type] || 'info'; - } - - getIconClass(type) { - const iconMap = { - 'success': 'fas fa-check-circle', - 'error': 'fas fa-exclamation-triangle', - 'warning': 'fas fa-exclamation-circle', - 'info': 'fas fa-info-circle' - }; - return iconMap[type] || 'fas fa-info-circle'; - } - - getDefaultTitle(type) { - const titleMap = { - 'success': '成功', - 'error': '错误', - 'warning': '警告', - 'info': '提示' - }; - return titleMap[type] || '通知'; - } - - // 便捷方法 - success(message, title = null) { - store.addNotification({ - type: 'success', - message, - title - }); - } - - error(message, title = null) { - store.addNotification({ - type: 'error', - message, - title - }); - } - - warning(message, title = null) { - store.addNotification({ - type: 'warning', - message, - title - }); - } - - info(message, title = null) { - store.addNotification({ - type: 'info', - message, - title - }); - } -} - -// 创建全局通知管理器实例 -const notificationManager = new NotificationManager(); diff --git a/src/web/static/js/components/modal.js b/src/web/static/js/components/modal.js deleted file mode 100644 index 11b95c0..0000000 --- a/src/web/static/js/components/modal.js +++ /dev/null @@ -1,418 +0,0 @@ -/** - * 模态框组件 - */ - -import { addClass, removeClass, hasClass } from '../core/utils.js'; - -export class Modal { - constructor(options = {}) { - this.id = options.id || `modal-${Date.now()}`; - this.title = options.title || ''; - this.content = options.content || ''; - this.size = options.size || ''; // sm, lg, xl - this.backdrop = options.backdrop !== false; - this.keyboard = options.keyboard !== false; - this.centered = options.centered || false; - this.scrollable = options.scrollable || false; - this.static = options.static || false; - this.className = options.className || ''; - this.footer = options.footer || null; - this.show = false; - - this.onShow = options.onShow || (() => {}); - this.onShown = options.onShown || (() => {}); - this.onHide = options.onHide || (() => {}); - this.onHidden = options.onHidden || (() => {}); - - this.init(); - } - - init() { - this.createModal(); - this.bindEvents(); - } - - createModal() { - // 创建模态框容器 - this.modal = document.createElement('div'); - this.modal.className = 'modal fade'; - this.modal.id = this.id; - this.modal.setAttribute('tabindex', '-1'); - this.modal.setAttribute('aria-labelledby', `${this.id}-label`); - this.modal.setAttribute('aria-hidden', 'true'); - - // 模态框对话框 - const dialog = document.createElement('div'); - dialog.className = `modal-dialog ${this.size ? `modal-${this.size}` : ''} ${this.centered ? 'modal-dialog-centered' : ''} ${this.scrollable ? 'modal-dialog-scrollable' : ''}`; - - // 模态框内容 - const content = document.createElement('div'); - content.className = 'modal-content'; - if (this.className) { - addClass(content, this.className); - } - - // 构建模态框HTML - let modalHTML = ` - - - `; - - // 添加底部按钮 - if (this.footer) { - modalHTML += ` - - `; - } - - content.innerHTML = modalHTML; - dialog.appendChild(content); - this.modal.appendChild(dialog); - - // 添加到页面 - document.body.appendChild(this.modal); - } - - renderFooter() { - if (!this.footer) return ''; - - if (Array.isArray(this.footer)) { - return this.footer.map(btn => { - const attrs = Object.keys(btn) - .filter(key => key !== 'text') - .map(key => `${key}="${btn[key]}"`) - .join(' '); - return ``; - }).join(''); - } - - return ''; - } - - bindEvents() { - // 关闭按钮 - const closeBtn = this.modal.querySelector('.btn-close'); - if (closeBtn) { - closeBtn.addEventListener('click', () => this.hide()); - } - - // 背景点击 - if (!this.static) { - this.modal.addEventListener('click', (e) => { - if (e.target === this.modal) { - this.hide(); - } - }); - } - - // ESC键关闭 - if (this.keyboard) { - document.addEventListener('keydown', (e) => { - if (e.key === 'Escape' && this.show) { - this.hide(); - } - }); - } - } - - show() { - if (this.show) return; - - // 触发显示前事件 - this.onShow(); - - // 添加到页面 - if (!this.modal.parentNode) { - document.body.appendChild(this.modal); - } - - // 显示模态框 - this.modal.style.display = 'block'; - addClass(this.modal, 'show'); - this.modal.setAttribute('aria-hidden', 'false'); - - // 防止背景滚动 - document.body.style.overflow = 'hidden'; - - this.show = true; - - // 聚焦到第一个可聚焦元素 - setTimeout(() => { - const focusable = this.modal.querySelector('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'); - if (focusable) { - focusable.focus(); - } - }, 100); - - // 触发显示后事件 - setTimeout(() => { - this.onShown(); - }, 150); - } - - hide() { - if (!this.show) return; - - // 触发隐藏前事件 - this.onHide(); - - // 隐藏模态框 - removeClass(this.modal, 'show'); - this.modal.setAttribute('aria-hidden', 'true'); - - // 恢复背景滚动 - document.body.style.overflow = ''; - - this.show = false; - - // 延迟移除DOM - setTimeout(() => { - if (this.modal) { - this.modal.style.display = 'none'; - if (this.modal.parentNode) { - this.modal.parentNode.removeChild(this.modal); - } - } - - // 触发隐藏后事件 - this.onHidden(); - }, 150); - } - - toggle() { - if (this.show) { - this.hide(); - } else { - this.show(); - } - } - - update(options) { - if (options.title) { - this.title = options.title; - const titleEl = this.modal.querySelector('.modal-title'); - if (titleEl) { - titleEl.textContent = this.title; - } - } - - if (options.content) { - this.content = options.content; - const bodyEl = this.modal.querySelector('.modal-body'); - if (bodyEl) { - bodyEl.innerHTML = this.content; - } - } - - if (options.footer !== undefined) { - this.footer = options.footer; - const footerEl = this.modal.querySelector('.modal-footer'); - if (footerEl) { - if (this.footer) { - footerEl.style.display = 'block'; - footerEl.innerHTML = typeof this.footer === 'string' ? this.footer : this.renderFooter(); - - // 重新绑定底部按钮事件 - this.bindFooterEvents(); - } else { - footerEl.style.display = 'none'; - } - } - } - } - - bindFooterEvents() { - const footer = this.modal.querySelector('.modal-footer'); - if (!footer) return; - - footer.querySelectorAll('button').forEach(btn => { - const dataDismiss = btn.getAttribute('data-bs-dismiss'); - if (dataDismiss === 'modal') { - btn.addEventListener('click', () => this.hide()); - } - }); - } - - getModal() { - return this.modal; - } - - getElement(selector) { - return this.modal.querySelector(selector); - } - - destroy() { - this.hide(); - setTimeout(() => { - if (this.modal && this.modal.parentNode) { - this.modal.parentNode.removeChild(this.modal); - } - this.modal = null; - }, 200); - } -} - -// 确认对话框 -export function confirm(options = {}) { - return new Promise((resolve) => { - const modal = new Modal({ - title: options.title || '确认', - content: ` -
- -

${options.message || '确定要执行此操作吗?'}

-
- `, - size: 'sm', - centered: true, - footer: [ - { text: '取消', class: 'btn btn-secondary', 'data-bs-dismiss': 'modal' }, - { text: '确定', class: 'btn btn-primary', id: 'confirm-btn' } - ] - }); - - modal.onHidden = () => { - modal.destroy(); - }; - - modal.getElement('#confirm-btn').addEventListener('click', () => { - modal.hide(); - resolve(true); - }); - - modal.onHidden = () => { - resolve(false); - modal.destroy(); - }; - - modal.show(); - }); -} - -// 警告对话框 -export function alert(options = {}) { - return new Promise((resolve) => { - const modal = new Modal({ - title: options.title || '提示', - content: ` -
- -

${options.message || ''}

-
- `, - size: 'sm', - centered: true, - footer: [ - { text: '确定', class: 'btn btn-primary', id: 'alert-btn' } - ] - }); - - modal.onHidden = () => { - modal.destroy(); - resolve(); - }; - - modal.getElement('#alert-btn').addEventListener('click', () => { - modal.hide(); - }); - - modal.show(); - }); -} - -// Toast通知 -export class Toast { - constructor(options = {}) { - this.id = `toast-${Date.now()}`; - this.type = options.type || 'info'; - this.message = options.message || ''; - this.duration = options.duration || 3000; - this.closable = options.closable !== false; - this.autoHide = options.autoHide !== false; - - this.init(); - } - - init() { - this.createToast(); - this.show(); - } - - createToast() { - // 查找或创建toast容器 - let container = document.querySelector('.toast-container'); - if (!container) { - container = document.createElement('div'); - container.className = 'toast-container'; - document.body.appendChild(container); - } - - // 创建toast元素 - this.toast = document.createElement('div'); - this.toast.className = `toast ${this.type}`; - this.toast.id = this.id; - - const iconMap = { - success: 'fa-check-circle', - error: 'fa-exclamation-circle', - warning: 'fa-exclamation-triangle', - info: 'fa-info-circle' - }; - - this.toast.innerHTML = ` -
- - ${this.message} - ${this.closable ? '' : ''} -
- `; - - container.appendChild(this.toast); - - // 绑定关闭事件 - if (this.closable) { - const closeBtn = this.toast.querySelector('.btn-close'); - if (closeBtn) { - closeBtn.addEventListener('click', () => this.hide()); - } - } - - // 自动隐藏 - if (this.autoHide) { - setTimeout(() => this.hide(), this.duration); - } - } - - show() { - setTimeout(() => { - addClass(this.toast, 'show'); - }, 10); - } - - hide() { - removeClass(this.toast, 'show'); - setTimeout(() => { - if (this.toast && this.toast.parentNode) { - this.toast.parentNode.removeChild(this.toast); - } - }, 300); - } -} - -// 创建全局toast函数 -export function showToast(options) { - if (typeof options === 'string') { - options = { message: options }; - } - return new Toast(options); -} - -// 导出 -export default Modal; \ No newline at end of file diff --git a/src/web/static/js/components/navbar.js b/src/web/static/js/components/navbar.js deleted file mode 100644 index 580cad7..0000000 --- a/src/web/static/js/components/navbar.js +++ /dev/null @@ -1,414 +0,0 @@ -/** - * 导航栏组件 - */ - -import { addClass, removeClass, hasClass, toggleClass } from '../core/utils.js'; -import store from '../core/store.js'; -import router from '../core/router.js'; - -export class Navbar { - constructor(container) { - this.container = typeof container === 'string' ? document.querySelector(container) : container; - this.userMenuOpen = false; - this.init(); - } - - init() { - this.render(); - this.bindEvents(); - } - - render() { - this.container.innerHTML = ` - - `; - } - - bindEvents() { - // 侧边栏切换(移动端) - const sidebarToggle = this.container.querySelector('#sidebar-toggle'); - if (sidebarToggle) { - sidebarToggle.addEventListener('click', () => { - this.toggleSidebar(); - }); - } - - // 用户菜单切换 - const userMenuToggle = this.container.querySelector('#user-menu-toggle'); - if (userMenuToggle) { - userMenuToggle.addEventListener('click', (e) => { - e.preventDefault(); - this.toggleUserMenu(); - }); - } - - // 通知下拉菜单 - const notificationsDropdown = this.container.querySelector('#notifications-dropdown'); - if (notificationsDropdown) { - const toggle = notificationsDropdown.querySelector('[data-toggle="dropdown"]'); - if (toggle) { - toggle.addEventListener('click', (e) => { - e.preventDefault(); - this.toggleNotifications(); - }); - } - } - - // 主题切换 - const themeToggle = this.container.querySelector('#theme-toggle'); - if (themeToggle) { - themeToggle.addEventListener('click', () => { - this.toggleTheme(); - }); - } - - // 退出登录 - const logoutBtn = this.container.querySelector('#logout-btn'); - if (logoutBtn) { - logoutBtn.addEventListener('click', (e) => { - e.preventDefault(); - this.handleLogout(); - }); - } - - // 清空通知 - const clearNotifications = this.container.querySelector('#clear-notifications'); - if (clearNotifications) { - clearNotifications.addEventListener('click', (e) => { - e.preventDefault(); - this.clearNotifications(); - }); - } - - // 路由链接 - this.container.querySelectorAll('[data-route]').forEach(link => { - link.addEventListener('click', (e) => { - e.preventDefault(); - const route = e.currentTarget.getAttribute('data-route'); - if (route) { - router.push(route); - } - }); - }); - - // 点击外部关闭下拉菜单 - document.addEventListener('click', (e) => { - if (!this.container.contains(e.target)) { - this.closeUserMenu(); - this.closeNotifications(); - } - }); - - // 监听store变化 - store.subscribe((state) => { - this.updateUser(state.user); - this.updateNotifications(state.ui.notifications); - this.updateMonitorStatus(state.monitor); - }); - } - - toggleUserMenu() { - const dropdown = this.container.querySelector('#user-dropdown'); - if (dropdown) { - this.userMenuOpen = !this.userMenuOpen; - toggleClass(dropdown, 'show'); - } - } - - closeUserMenu() { - const dropdown = this.container.querySelector('#user-dropdown'); - if (dropdown && hasClass(dropdown, 'show')) { - this.userMenuOpen = false; - removeClass(dropdown, 'show'); - } - } - - toggleNotifications() { - const dropdown = this.container.querySelector('#notifications-dropdown .dropdown-menu'); - if (dropdown) { - toggleClass(dropdown, 'show'); - } - } - - closeNotifications() { - const dropdown = this.container.querySelector('#notifications-dropdown .dropdown-menu'); - if (dropdown && hasClass(dropdown, 'show')) { - removeClass(dropdown, 'show'); - } - } - - toggleTheme() { - const currentTheme = store.getState('app.theme'); - const newTheme = currentTheme === 'light' ? 'dark' : 'light'; - - store.commit('SET_THEME', newTheme); - - const icon = this.container.querySelector('#theme-icon'); - if (icon) { - icon.className = newTheme === 'light' ? 'fas fa-moon' : 'fas fa-sun'; - } - } - - handleLogout() { - if (confirm('确定要退出登录吗?')) { - // 调用注销API - fetch('/api/logout', { method: 'POST' }) - .then(() => { - // 清除应用状态 - store.commit('SET_USER', null); - store.commit('SET_LOGIN', false); - store.commit('SET_TOKEN', null); - - // 清除本地存储和会话存储 - localStorage.removeItem('user'); - localStorage.removeItem('token'); - localStorage.removeItem('remember'); - sessionStorage.removeItem('token'); - - // 显示提示 - if (window.showToast) { - window.showToast('已退出登录', 'info'); - } - - // 跳转到登录页 - router.push('/login'); - }) - .catch(error => { - console.error('注销失败:', error); - // 即使API调用失败,也要清除本地状态 - store.commit('SET_USER', null); - store.commit('SET_LOGIN', false); - store.commit('SET_TOKEN', null); - localStorage.clear(); - sessionStorage.clear(); - router.push('/login'); - }); - } - } - - clearNotifications() { - store.setState({ - ui: { - ...store.getState('ui'), - notifications: [] - } - }); - } - - updateNotifications(notifications) { - const countEl = this.container.querySelector('#notification-count'); - const listEl = this.container.querySelector('#notification-list'); - - if (!countEl || !listEl) return; - - const count = notifications.length; - countEl.textContent = count; - countEl.style.display = count > 0 ? 'inline-block' : 'none'; - - if (count === 0) { - listEl.innerHTML = ''; - } else { - listEl.innerHTML = notifications.slice(0, 5).map(notification => ` - -
-
- -
-
-
${notification.message}
-
${this.formatTime(notification.time)}
-
-
-
- `).join(''); - } - } - - getNotificationIcon(type) { - const icons = { - success: 'fa-check-circle', - error: 'fa-exclamation-circle', - warning: 'fa-exclamation-triangle', - info: 'fa-info-circle' - }; - return icons[type] || 'fa-bell'; - } - - updateMonitorStatus(monitor) { - const indicator = this.container.querySelector('#status-indicator'); - const text = this.container.querySelector('#status-text'); - - if (!indicator || !text) return; - - if (monitor.status === 'running') { - indicator.className = 'fas fa-circle text-success'; - text.textContent = '监控运行中'; - } else { - indicator.className = 'fas fa-circle text-warning'; - text.textContent = '监控已停止'; - } - } - - updateUser(user) { - const avatar = this.container.querySelector('#user-avatar'); - const name = this.container.querySelector('#user-name'); - const role = this.container.querySelector('#user-role'); - - if (user && user.info) { - const initial = this.getInitial(user.info.name); - if (avatar) avatar.textContent = initial; - if (name) name.textContent = user.info.name; - if (role) role.textContent = user.info.role || '用户'; - } else { - if (avatar) avatar.textContent = 'U'; - if (name) name.textContent = '未登录'; - if (role) role.textContent = '访客'; - } - } - - getUserInitial() { - const user = store.getState('user.info'); - return user ? this.getInitial(user.name) : 'U'; - } - - getUserName() { - const user = store.getState('user.info'); - return user ? user.name : '未登录'; - } - - getUserRole() { - const user = store.getState('user.info'); - return user ? (user.role || '用户') : '访客'; - } - - getInitial(name) { - if (!name) return 'U'; - const chars = name.trim().split(/\s+/); - if (chars.length >= 2) { - return chars[0][0] + chars[chars.length - 1][0]; - } - return name[0].toUpperCase(); - } - - toggleSidebar() { - const sidebar = document.querySelector('.sidebar'); - const overlay = document.querySelector('.sidebar-overlay') || this.createOverlay(); - const isMobile = window.innerWidth <= 768; - - if (isMobile) { - // 移动端:切换显示 - toggleClass(sidebar, 'open'); - toggleClass(overlay, 'show'); - } else { - // 桌面端:切换折叠 - toggleClass(sidebar, 'collapsed'); - } - } - - createOverlay() { - const overlay = document.createElement('div'); - overlay.className = 'sidebar-overlay'; - overlay.addEventListener('click', () => { - this.toggleSidebar(); - }); - document.body.appendChild(overlay); - return overlay; - } - - formatTime(time) { - const date = new Date(time); - const now = new Date(); - const diff = now - date; - - if (diff < 60000) { - return '刚刚'; - } else if (diff < 3600000) { - return `${Math.floor(diff / 60000)}分钟前`; - } else if (diff < 86400000) { - return `${Math.floor(diff / 3600000)}小时前`; - } else { - return date.toLocaleDateString(); - } - } -} - -// 导出组件 -export default Navbar; \ No newline at end of file diff --git a/src/web/static/js/components/sidebar.js b/src/web/static/js/components/sidebar.js deleted file mode 100644 index 5854a53..0000000 --- a/src/web/static/js/components/sidebar.js +++ /dev/null @@ -1,235 +0,0 @@ -/** - * 侧边栏组件 - */ - -import { addClass, removeClass, hasClass, toggleClass } from '../core/utils.js'; -import router from '../core/router.js'; - -export class Sidebar { - constructor(container) { - this.container = typeof container === 'string' ? document.querySelector(container) : container; - this.collapsed = false; - this.init(); - } - - init() { - this.render(); - this.bindEvents(); - } - - render() { - this.container.innerHTML = ` - - `; - - // 初始化折叠状态 - const isCollapsed = localStorage.getItem('sidebar-collapsed') === 'true'; - if (isCollapsed) { - this.collapsed = true; - addClass(this.container.querySelector('#sidebar'), 'collapsed'); - } - } - - renderMenuItems() { - const menuItems = [ - { - path: '/', - icon: 'fas fa-tachometer-alt', - title: '仪表板', - badge: null - }, - { - path: '/workorders', - icon: 'fas fa-tasks', - title: '工单管理', - badge: 'workorders' - }, - { - path: '/alerts', - icon: 'fas fa-bell', - title: '预警管理', - badge: 'alerts' - }, - { - path: '/knowledge', - icon: 'fas fa-book', - title: '知识库', - badge: null - }, - { - path: '/chat', - icon: 'fas fa-comments', - title: '智能对话', - badge: null - }, - { - path: '/chat-http', - icon: 'fas fa-comment-dots', - title: 'HTTP对话', - badge: null - }, - { - path: '/monitoring', - icon: 'fas fa-chart-line', - title: '系统监控', - badge: null - }, - { - path: '/feishu', - icon: 'fab fa-lark', - title: '飞书同步', - badge: null - }, - { - path: '/agent', - icon: 'fas fa-robot', - title: '智能Agent', - badge: null - }, - { - path: '/vehicle', - icon: 'fas fa-car', - title: '车辆数据', - badge: null - }, - { - path: '/settings', - icon: 'fas fa-cog', - title: '系统设置', - badge: null - } - ]; - - return menuItems.map(item => ` - - - ${item.title} - ${item.badge ? `0` : ''} - - `).join(''); - } - - bindEvents() { - // 折叠切换 - const toggleBtn = this.container.querySelector('#sidebar-toggle'); - if (toggleBtn) { - toggleBtn.addEventListener('click', (e) => { - e.preventDefault(); - this.toggle(); - }); - } - - // 菜单项点击 - this.container.querySelectorAll('.sidebar-nav-item').forEach(item => { - item.addEventListener('click', (e) => { - e.preventDefault(); - const route = e.currentTarget.getAttribute('data-route'); - if (route) { - router.push(route); - } - - // 移动端点击后自动收起侧边栏 - if (window.innerWidth < 992 && !this.collapsed) { - this.toggle(); - } - }); - }); - - // 监听路由变化 - router.afterEach((to) => { - this.updateActiveMenu(to.path); - }); - - // 监听窗口大小变化 - window.addEventListener('resize', () => { - this.handleResize(); - }); - - // 初始化激活状态 - this.updateActiveMenu(window.location.pathname); - } - - toggle() { - const sidebar = this.container.querySelector('#sidebar'); - if (sidebar) { - this.collapsed = !this.collapsed; - toggleClass(sidebar, 'collapsed'); - localStorage.setItem('sidebar-collapsed', this.collapsed); - } - } - - expand() { - const sidebar = this.container.querySelector('#sidebar'); - if (sidebar && hasClass(sidebar, 'collapsed')) { - this.collapsed = false; - removeClass(sidebar, 'collapsed'); - localStorage.setItem('sidebar-collapsed', 'false'); - } - } - - collapse() { - const sidebar = this.container.querySelector('#sidebar'); - if (sidebar && !hasClass(sidebar, 'collapsed')) { - this.collapsed = true; - addClass(sidebar, 'collapsed'); - localStorage.setItem('sidebar-collapsed', 'true'); - } - } - - updateActiveMenu(path) { - this.container.querySelectorAll('.sidebar-nav-item').forEach(item => { - const route = item.getAttribute('data-route'); - if (route === path) { - addClass(item, 'active'); - } else { - removeClass(item, 'active'); - } - }); - } - - updateBadge(type, count) { - const badge = this.container.querySelector(`#sidebar-badge-${type}`); - if (badge) { - badge.textContent = count; - badge.style.display = count > 0 ? 'inline-block' : 'none'; - } - } - - handleResize() { - if (window.innerWidth >= 992) { - // 桌面端,恢复之前的折叠状态 - const isCollapsed = localStorage.getItem('sidebar-collapsed') === 'true'; - if (isCollapsed !== this.collapsed) { - if (isCollapsed) { - this.collapse(); - } else { - this.expand(); - } - } - } else { - // 移动端,默认收起 - if (!this.collapsed) { - this.collapse(); - } - } - } -} - -// 导出组件 -export default Sidebar; \ No newline at end of file diff --git a/src/web/static/js/core/api.js b/src/web/static/js/core/api.js deleted file mode 100644 index a5bc081..0000000 --- a/src/web/static/js/core/api.js +++ /dev/null @@ -1,472 +0,0 @@ -/** - * API统一管理模块 - */ - -import { defaultConfig, debounce, storage, handleError } from './utils.js'; - -// API配置 -const config = { - ...defaultConfig, - timeout: 10000, // 请求超时时间 - retryTimes: 3, // 重试次数 - retryDelay: 1000 // 重试延迟 -}; - -// 请求拦截器 -const requestInterceptors = []; -const responseInterceptors = []; - -// 添加请求拦截器 -export function addRequestInterceptor(interceptor) { - requestInterceptors.push(interceptor); -} - -// 添加响应拦截器 -export function addResponseInterceptor(interceptor) { - responseInterceptors.push(interceptor); -} - -// 默认请求拦截器 -addRequestInterceptor(async (options) => { - // 添加认证头 - const token = storage.get('authToken'); - if (token) { - options.headers = { - ...options.headers, - 'Authorization': `Bearer ${token}` - }; - } - - // 添加用户信息头 - const userInfo = storage.get('userInfo'); - if (userInfo) { - options.headers = { - ...options.headers, - 'X-User-Name': userInfo.name || '', - 'X-User-Role': userInfo.role || '' - }; - } - - // 添加请求ID - options.headers = { - ...options.headers, - 'X-Request-ID': generateRequestId() - }; - - return options; -}); - -// 默认响应拦截器 -addResponseInterceptor(async (response) => { - // 处理通用错误 - if (response.status === 401) { - // 未授权,清除本地存储并跳转到登录页 - storage.remove('authToken'); - storage.remove('userInfo'); - window.location.href = '/login'; - throw new Error('未授权,请重新登录'); - } - - if (response.status >= 500) { - throw new Error('服务器错误,请稍后重试'); - } - - return response; -}); - -// 生成请求ID -function generateRequestId() { - return Date.now().toString(36) + Math.random().toString(36).substr(2); -} - -// 基础请求函数 -async function request(url, options = {}) { - // 合并配置 - const finalOptions = { - method: 'GET', - headers: { - 'Content-Type': 'application/json', - ...options.headers - }, - ...options - }; - - // 执行请求拦截器 - for (const interceptor of requestInterceptors) { - Object.assign(finalOptions, await interceptor(finalOptions)); - } - - // 构建完整URL - const fullUrl = url.startsWith('http') ? url : `${config.apiBaseUrl}${url}`; - - // 创建AbortController用于超时控制 - const controller = new AbortController(); - const timeoutId = setTimeout(() => controller.abort(), config.timeout); - finalOptions.signal = controller.signal; - - try { - let lastError; - - // 重试机制 - for (let i = 0; i <= config.retryTimes; i++) { - try { - const response = await fetch(fullUrl, finalOptions); - - // 执行响应拦截器 - let processedResponse = response; - for (const interceptor of responseInterceptors) { - processedResponse = await interceptor(processedResponse); - } - - // 解析响应 - const data = await parseResponse(processedResponse); - - // 如果响应表示失败,抛出错误 - if (data.code && data.code !== 200) { - throw new Error(data.message || '请求失败'); - } - - return data; - } catch (error) { - lastError = error; - - // 如果是网络错误或超时,且还有重试次数,则延迟后重试 - if (i < config.retryTimes && (error.name === 'TypeError' || error.name === 'AbortError')) { - await new Promise(resolve => setTimeout(resolve, config.retryDelay * Math.pow(2, i))); - continue; - } - - // 其他错误直接抛出 - throw error; - } - } - - throw lastError; - } catch (error) { - handleError(error, `API Request: ${finalOptions.method} ${url}`); - throw error; - } finally { - clearTimeout(timeoutId); - } -} - -// 解析响应 -async function parseResponse(response) { - const contentType = response.headers.get('content-type'); - - if (contentType && contentType.includes('application/json')) { - return await response.json(); - } else if (contentType && contentType.includes('text/')) { - return { - code: response.ok ? 200 : response.status, - data: await response.text(), - message: response.statusText - }; - } else { - return { - code: response.ok ? 200 : response.status, - data: await response.blob(), - message: response.statusText - }; - } -} - -// HTTP方法封装 -export const http = { - get(url, params = {}, options = {}) { - const queryString = new URLSearchParams(params).toString(); - const fullUrl = queryString ? `${url}?${queryString}` : url; - return request(fullUrl, { ...options, method: 'GET' }); - }, - - post(url, data = {}, options = {}) { - return request(url, { - ...options, - method: 'POST', - body: JSON.stringify(data) - }); - }, - - put(url, data = {}, options = {}) { - return request(url, { - ...options, - method: 'PUT', - body: JSON.stringify(data) - }); - }, - - patch(url, data = {}, options = {}) { - return request(url, { - ...options, - method: 'PATCH', - body: JSON.stringify(data) - }); - }, - - delete(url, options = {}) { - return request(url, { ...options, method: 'DELETE' }); - }, - - upload(url, formData, options = {}) { - return request(url, { - ...options, - method: 'POST', - headers: { - // 不要设置Content-Type,让浏览器自动设置multipart/form-data - }, - body: formData - }); - }, - - download(url, params = {}, options = {}) { - const queryString = new URLSearchParams(params).toString(); - const fullUrl = queryString ? `${url}?${queryString}` : url; - - return request(fullUrl, { - ...options, - method: 'GET' - }).then(response => { - // 创建下载链接 - const blob = response.data; - const downloadUrl = window.URL.createObjectURL(blob); - const link = document.createElement('a'); - link.href = downloadUrl; - - // 从响应头获取文件名 - const contentDisposition = response.headers?.get('content-disposition'); - if (contentDisposition) { - const filename = contentDisposition.match(/filename="?([^"]+)"?/); - link.download = filename ? filename[1] : 'download'; - } - - document.body.appendChild(link); - link.click(); - document.body.removeChild(link); - window.URL.revokeObjectURL(downloadUrl); - - return response; - }); - } -}; - -// API接口定义 -export const api = { - // 系统相关 - system: { - health: () => http.get('/health'), - info: () => http.get('/system/info'), - settings: () => http.get('/settings'), - saveSettings: (data) => http.post('/settings', data) - }, - - // 工单管理 - workorders: { - list: (params) => http.get('/workorders', params), - create: (data) => http.post('/workorders', data), - get: (id) => http.get(`/workorders/${id}`), - update: (id, data) => http.put(`/workorders/${id}`, data), - delete: (id) => http.delete(`/workorders/${id}`), - dispatch: (id, module) => http.post(`/workorders/${id}/dispatch`, { target_module: module }), - suggestModule: (id) => http.post(`/workorders/${id}/suggest-module`), - aiSuggestion: (id) => http.post(`/workorders/${id}/ai-suggestion`), - humanResolution: (id, data) => http.post(`/workorders/${id}/human-resolution`, data), - approveToKnowledge: (id, data) => http.post(`/workorders/${id}/approve-to-knowledge`, data), - processHistory: (id) => http.get(`/workorders/${id}/process-history`), - addProcessHistory: (id, data) => http.post(`/workorders/${id}/process-history`, data), - import: (file) => { - const formData = new FormData(); - formData.append('file', file); - return http.upload('/workorders/import', formData); - }, - export: (params) => http.download('/workorders/export', params), - getTemplate: () => http.get('/workorders/import/template'), - downloadTemplate: () => http.download('/workorders/import/template/file'), - modules: () => http.get('/workorders/modules'), - byStatus: (status) => http.get(`/workorders/by-status/${status}`), - batchDelete: (ids) => http.post('/batch-delete/workorders', { ids }) - }, - - // 对话管理 - conversations: { - list: (params) => http.get('/conversations', params), - get: (id) => http.get(`/conversations/${id}`), - delete: (id) => http.delete(`/conversations/${id}`), - clear: () => http.delete('/conversations/clear'), - search: (params) => http.get('/conversations/search', params), - analytics: () => http.get('/conversations/analytics'), - migrateMerge: (data) => http.post('/conversations/migrate-merge', data), - timeline: (workOrderId, params) => http.get(`/conversations/workorder/${workOrderId}/timeline`, params), - context: (workOrderId) => http.get(`/conversations/workorder/${workOrderId}/context`), - summary: (workOrderId) => http.get(`/conversations/workorder/${workOrderId}/summary`) - }, - - // 聊天接口 - chat: { - createSession: (data) => http.post('/chat/session', data), - sendMessage: (data) => http.post('/chat/message', data), - getHistory: (sessionId) => http.get(`/chat/history/${sessionId}`), - createWorkOrder: (data) => http.post('/chat/work-order', data), - getWorkOrderStatus: (workOrderId) => http.get(`/chat/work-order/${workOrderId}`), - endSession: (sessionId) => http.delete(`/chat/session/${sessionId}`), - sessions: () => http.get('/chat/sessions') - }, - - // 知识库 - knowledge: { - list: (params) => http.get('/knowledge', params), - search: (params) => http.get('/knowledge/search', params), - create: (data) => http.post('/knowledge', data), - get: (id) => http.get(`/knowledge/${id}`), - update: (id, data) => http.put(`/knowledge/${id}`, data), - delete: (id) => http.delete(`/knowledge/delete/${id}`), - verify: (id) => http.post(`/knowledge/verify/${id}`), - unverify: (id) => http.post(`/knowledge/unverify/${id}`), - stats: () => http.get('/knowledge/stats'), - upload: (file, data) => { - const formData = new FormData(); - formData.append('file', file); - Object.keys(data).forEach(key => { - formData.append(key, data[key]); - }); - return http.upload('/knowledge/upload', formData); - }, - byStatus: (status) => http.get(`/knowledge/by-status/${status}`), - batchDelete: (ids) => http.post('/batch-delete/knowledge', { ids }) - }, - - // 预警管理 - alerts: { - list: (params) => http.get('/alerts', params), - create: (data) => http.post('/alerts', data), - get: (id) => http.get(`/alerts/${id}`), - update: (id, data) => http.put(`/alerts/${id}`, data), - delete: (id) => http.delete(`/alerts/${id}`), - resolve: (id, data) => http.post(`/alerts/${id}/resolve`, data), - statistics: () => http.get('/alerts/statistics'), - byLevel: (level) => http.get(`/alerts/by-level/${level}`), - batchDelete: (ids) => http.post('/batch-delete/alerts', { ids }) - }, - - // 预警规则 - rules: { - list: () => http.get('/rules'), - create: (data) => http.post('/rules', data), - update: (name, data) => http.put(`/rules/${name}`, data), - delete: (name) => http.delete(`/rules/${name}`) - }, - - // 监控管理 - monitor: { - start: () => http.post('/monitor/start'), - stop: () => http.post('/monitor/stop'), - status: () => http.get('/monitor/status'), - checkAlerts: () => http.post('/check-alerts'), - analytics: (params) => http.get('/analytics', params) - }, - - // Token监控 - tokenMonitor: { - stats: () => http.get('/token-monitor/stats'), - chart: (params) => http.get('/token-monitor/chart', params), - records: (params) => http.get('/token-monitor/records', params), - settings: (data) => http.post('/token-monitor/settings', data), - export: (params) => http.download('/token-monitor/export', params) - }, - - // AI监控 - aiMonitor: { - stats: () => http.get('/ai-monitor/stats'), - modelComparison: () => http.get('/ai-monitor/model-comparison'), - errorDistribution: () => http.get('/ai-monitor/error-distribution'), - errorLog: (params) => http.get('/ai-monitor/error-log', params), - clearErrorLog: () => http.delete('/ai-monitor/error-log') - }, - - // Agent相关 - agent: { - status: () => http.get('/agent/status'), - toggle: () => http.post('/agent/toggle'), - chat: (data) => http.post('/agent/chat', data), - actionHistory: () => http.get('/agent/action-history'), - clearHistory: () => http.post('/agent/clear-history'), - tools: { - stats: () => http.get('/agent/tools/stats'), - execute: (data) => http.post('/agent/tools/execute', data), - register: (data) => http.post('/agent/tools/register', data), - unregister: (name) => http.delete(`/agent/tools/unregister/${name}`) - }, - monitoring: { - start: () => http.post('/agent/monitoring/start'), - stop: () => http.post('/agent/monitoring/stop'), - proactiveCheck: () => http.post('/agent/proactive-monitoring'), - intelligentAnalysis: () => http.post('/agent/intelligent-analysis') - }, - llmStats: () => http.get('/agent/llm-stats'), - triggerSample: () => http.post('/agent/trigger-sample') - }, - - // 车辆数据 - vehicle: { - data: (params) => http.get('/vehicle/data', params), - latestById: (id) => http.get(`/vehicle/data/${id}/latest`), - latestByVin: (vin) => http.get(`/vehicle/data/vin/${vin}/latest`), - summary: (id) => http.get(`/vehicle/data/${id}/summary`), - add: (data) => http.post('/vehicle/data', data), - initSample: () => http.post('/vehicle/init-sample-data') - }, - - // 飞书同步 - feishu: { - config: { - get: () => http.get('/feishu-sync/config'), - save: (data) => http.post('/feishu-sync/config', data) - }, - testConnection: () => http.get('/feishu-sync/test-connection'), - checkPermissions: () => http.get('/feishu-sync/check-permissions'), - syncFromFeishu: (data) => http.post('/feishu-sync/sync-from-feishu', data), - syncToFeishu: (workOrderId) => http.post(`/feishu-sync/sync-to-feishu/${workOrderId}`), - status: () => http.get('/feishu-sync/status'), - createWorkorder: (data) => http.post('/feishu-sync/create-workorder', data), - fieldMapping: { - status: () => http.get('/feishu-sync/field-mapping/status'), - discover: () => http.post('/feishu-sync/field-mapping/discover'), - add: (data) => http.post('/feishu-sync/field-mapping/add', data), - remove: (data) => http.post('/feishu-sync/field-mapping/remove', data) - }, - previewData: (params) => http.get('/feishu-sync/preview-feishu-data', params), - config: { - export: () => http.download('/feishu-sync/config/export'), - import: (file) => { - const formData = new FormData(); - formData.append('file', file); - return http.upload('/feishu-sync/config/import', formData); - }, - reset: () => http.post('/feishu-sync/config/reset') - } - }, - - // 系统优化 - systemOptimizer: { - status: () => http.get('/system-optimizer/status'), - optimizeCpu: () => http.post('/system-optimizer/optimize-cpu'), - optimizeMemory: () => http.post('/system-optimizer/optimize-memory'), - optimizeDisk: () => http.post('/system-optimizer/optimize-disk'), - clearCache: () => http.post('/system-optimizer/clear-cache'), - optimizeAll: () => http.post('/system-optimizer/optimize-all'), - securitySettings: (data) => http.post('/system-optimizer/security-settings', data), - trafficSettings: (data) => http.post('/system-optimizer/traffic-settings', data), - costSettings: (data) => http.post('/system-optimizer/cost-settings', data), - healthCheck: () => http.post('/system-optimizer/health-check') - }, - - // 数据库备份 - backup: { - info: () => http.get('/backup/info'), - create: (data) => http.post('/backup/create', data), - restore: (data) => http.post('/backup/restore', data) - } -}; - -// 导出默认配置和请求方法 -export { config }; -export default { http, api, config }; \ No newline at end of file diff --git a/src/web/static/js/core/router.js b/src/web/static/js/core/router.js deleted file mode 100644 index 2b9ac81..0000000 --- a/src/web/static/js/core/router.js +++ /dev/null @@ -1,466 +0,0 @@ -/** - * 路由管理模块 - */ - -import { parseQueryString, serializeQueryString } from './utils.js'; -import store from './store.js'; - -// 路由配置 -class Router { - constructor() { - this.routes = new Map(); - this.currentRoute = null; - this.beforeEachHooks = []; - this.afterEachHooks = []; - this.mode = 'history'; // 'history' 或 'hash' - this.base = '/'; - this.fallback = true; - - // 绑定事件处理器 - this.handlePopState = this.handlePopState.bind(this); - this.handleHashChange = this.handleHashChange.bind(this); - } - - // 配置路由 - config(options = {}) { - if (options.mode) this.mode = options.mode; - if (options.base) this.base = options.base; - if (options.fallback !== undefined) this.fallback = options.fallback; - return this; - } - - // 添加路由 - addRoute(path, component, options = {}) { - const route = { - path, - component, - name: options.name || path, - meta: options.meta || {}, - props: options.props || false, - children: options.children || [], - beforeEnter: options.beforeEnter - }; - - // 转换路径为正则表达式 - route.regex = this.pathToRegex(path); - route.keys = []; - - // 提取动态参数 - const paramNames = path.match(/:\w+/g); - if (paramNames) { - route.keys = paramNames.map(name => name.slice(1)); - } - - this.routes.set(path, route); - return this; - } - - // 批量添加路由 - addRoutes(routes) { - routes.forEach(route => { - this.addRoute(route.path, route.component, route); - }); - return this; - } - - // 路径转正则 - pathToRegex(path) { - const regexPath = path - .replace(/\//g, '\\/') - .replace(/:\w+/g, '([^\\/]+)') - .replace(/\*/g, '(.*)'); - return new RegExp(`^${regexPath}$`); - } - - // 匹配路由 - match(path) { - for (const [routePath, route] of this.routes) { - const match = path.match(route.regex); - if (match) { - const params = {}; - route.keys.forEach((key, index) => { - params[key] = match[index + 1]; - }); - - return { - route, - params, - path, - query: this.parseQuery(path) - }; - } - } - - // 404处理 - return { - route: { path: '/404', component: 'notfound' }, - params: {}, - path, - query: {} - }; - } - - // 解析查询字符串 - parseQuery(path) { - const queryIndex = path.indexOf('?'); - if (queryIndex === -1) return {}; - - const queryString = path.slice(queryIndex + 1); - return parseQueryString(queryString); - } - - // 构建路径 - buildPath(route, params = {}, query = {}) { - let path = route.path; - - // 替换动态参数 - Object.keys(params).forEach(key => { - path = path.replace(`:${key}`, params[key]); - }); - - // 添加查询字符串 - const queryString = serializeQueryString(query); - if (queryString) { - path += `?${queryString}`; - } - - return path; - } - - // 导航到指定路径 - push(path, data = {}) { - return this.navigateTo(path, 'push', data); - } - - // 替换当前路径 - replace(path, data = {}) { - return this.navigateTo(path, 'replace', data); - } - - // 返回上一页 - go(n) { - window.history.go(n); - } - - // 返回 - back() { - this.go(-1); - } - - // 前进 - forward() { - this.go(1); - } - - // 执行导航 - async navigateTo(path, type = 'push', data = {}) { - // 匹配路由 - const matched = this.match(path); - - // 创建路由对象 - const route = { - path: matched.path, - name: matched.route.name, - params: matched.params, - query: matched.query, - meta: matched.route.meta, - hash: this.parseHash(path), - ...data - }; - - // 执行前置守卫 - const guards = [...this.beforeEachHooks, matched.route.beforeEnter].filter(Boolean); - for (const guard of guards) { - const result = await guard(route, this.currentRoute); - if (result === false) { - return Promise.reject(new Error('Navigation cancelled')); - } - if (typeof result === 'string') { - return this.navigateTo(result, type, data); - } - if (result && typeof result === 'object') { - return this.navigateTo(result.path || result, type, result); - } - } - - // 保存当前路由 - const prevRoute = this.currentRoute; - this.currentRoute = route; - - // 更新URL - this.updateURL(path, type); - - // 执行后置守卫 - this.afterEachHooks.forEach(hook => { - try { - hook(route, prevRoute); - } catch (error) { - console.error('After each hook error:', error); - } - }); - - // 更新store - store.commit('SET_CURRENT_ROUTE', route); - - return route; - } - - // 更新URL - updateURL(path, type) { - if (this.mode === 'history') { - const url = this.base === '/' ? path : `${this.base}${path}`.replace('//', '/'); - if (type === 'replace') { - window.history.replaceState({ path }, '', url); - } else { - window.history.pushState({ path }, '', url); - } - } else { - const hash = this.mode === 'hash' ? `#${path}` : `#${this.base}${path}`.replace('//', '/'); - if (type === 'replace') { - window.location.replace(hash); - } else { - window.location.hash = hash; - } - } - } - - // 解析hash - parseHash(path) { - const hashIndex = path.indexOf('#'); - return hashIndex === -1 ? '' : path.slice(hashIndex + 1); - } - - // 处理popstate事件 - handlePopState(event) { - if (event.state && event.state.path) { - this.navigateTo(event.state.path, 'push', { replace: true }); - } else { - const path = this.getCurrentPath(); - this.navigateTo(path, 'push', { replace: true }); - } - } - - // 处理hashchange事件 - handleHashChange() { - const path = this.getCurrentPath(); - this.navigateTo(path, 'push', { replace: true }); - } - - // 获取当前路径 - getCurrentPath() { - if (this.mode === 'history') { - const path = window.location.pathname; - return path.startsWith(this.base) ? path.slice(this.base.length) : path; - } else { - const hash = window.location.hash.slice(1); - return hash.startsWith(this.base) ? hash.slice(this.base.length) : hash; - } - } - - // 全局前置守卫 - beforeEach(hook) { - this.beforeEachHooks.push(hook); - } - - // 全局后置守卫 - afterEach(hook) { - this.afterEachHooks.push(hook); - } - - // 启动路由 - start() { - // 监听事件 - if (this.mode === 'history') { - window.addEventListener('popstate', this.handlePopState); - } else { - window.addEventListener('hashchange', this.handleHashChange); - } - - // 处理初始路由 - const path = this.getCurrentPath(); - this.navigateTo(path, 'push', { replace: true }); - - // 拦截链接点击 - this.interceptLinks(); - - return this; - } - - // 拦截链接 - interceptLinks() { - document.addEventListener('click', (e) => { - const link = e.target.closest('a'); - if (!link) return; - - const href = link.getAttribute('href'); - if (!href || href.startsWith('http') || href.startsWith('mailto:') || href.startsWith('tel:')) { - return; - } - - e.preventDefault(); - this.push(href); - }); - } - - // 停止路由 - stop() { - window.removeEventListener('popstate', this.handlePopState); - window.removeEventListener('hashchange', this.handleHashChange); - return this; - } -} - -// 创建路由实例 -export const router = new Router(); - -// 路由配置 -router.config({ - mode: 'history', - base: '/', - fallback: true -}); - -// 添加路由 -router.addRoutes([ - { - path: '/', - name: 'dashboard', - component: 'Dashboard', - meta: { title: '仪表板', icon: 'fas fa-tachometer-alt' } - }, - { - path: '/workorders', - name: 'workorders', - component: 'WorkOrders', - meta: { title: '工单管理', icon: 'fas fa-tasks' } - }, - { - path: '/workorders/:id', - name: 'workorder-detail', - component: 'WorkOrderDetail', - meta: { title: '工单详情' } - }, - { - path: '/alerts', - name: 'alerts', - component: 'Alerts', - meta: { title: '预警管理', icon: 'fas fa-bell' } - }, - { - path: '/knowledge', - name: 'knowledge', - component: 'Knowledge', - meta: { title: '知识库', icon: 'fas fa-book' } - }, - { - path: '/knowledge/:id', - name: 'knowledge-detail', - component: 'KnowledgeDetail', - meta: { title: '知识详情' } - }, - { - path: '/chat', - name: 'chat', - component: 'Chat', - meta: { title: '智能对话', icon: 'fas fa-comments' } - }, - { - path: '/chat-http', - name: 'chat-http', - component: 'ChatHttp', - meta: { title: '对话(HTTP)', icon: 'fas fa-comment-dots' } - }, - { - path: '/monitoring', - name: 'monitoring', - component: 'Monitoring', - meta: { title: '系统监控', icon: 'fas fa-chart-line' } - }, - { - path: '/settings', - name: 'settings', - component: 'Settings', - meta: { title: '系统设置', icon: 'fas fa-cog' } - }, - { - path: '/feishu', - name: 'feishu', - component: 'Feishu', - meta: { title: '飞书同步', icon: 'fab fa-lark' } - }, - { - path: '/agent', - name: 'agent', - component: 'Agent', - meta: { title: '智能Agent', icon: 'fas fa-robot' } - }, - { - path: '/vehicle', - name: 'vehicle', - component: 'Vehicle', - meta: { title: '车辆数据', icon: 'fas fa-car' } - }, - { - path: '/profile', - name: 'profile', - component: 'Profile', - meta: { title: '个人资料' } - }, - { - path: '/login', - name: 'login', - component: 'Login', - meta: { title: '登录', requiresAuth: false } - }, - { - path: '/404', - name: '404', - component: 'notfound', - meta: { title: '页面未找到' } - }, - { - path: '*', - redirect: '/404' - } -]); - -// 全局前置守卫 -router.beforeEach((to, from) => { - // 设置页面标题 - if (to.meta.title) { - document.title = `${to.meta.title} - TSP智能助手`; - } - - // 权限检查 - if (to.meta.requiresAuth !== false && !store.getState('user.isLogin')) { - return '/login'; - } - - // 管理员权限检查 - if (to.meta.requiresAdmin && !store.getState('user.info.isAdmin')) { - return '/403'; - } -}); - -// 导出路由实例和辅助函数 -export function push(path, data) { - return router.push(path, data); -} - -export function replace(path, data) { - return router.replace(path, data); -} - -export function go(n) { - return router.go(n); -} - -export function back() { - return router.back(); -} - -export function forward() { - return router.forward(); -} - -export default router; \ No newline at end of file diff --git a/src/web/static/js/core/store.js b/src/web/static/js/core/store.js deleted file mode 100644 index 0d2b3f7..0000000 --- a/src/web/static/js/core/store.js +++ /dev/null @@ -1,203 +0,0 @@ -/** - * 全局状态管理 - * 集中管理应用状态,避免状态分散 - */ - -class Store { - constructor() { - this.state = { - // 预警相关 - alerts: [], - alertFilters: { - level: 'all', - type: 'all', - status: 'all' - }, - alertStats: { - total: 0, - critical: 0, - warning: 0, - info: 0 - }, - - // 规则相关 - rules: [], - - // 系统状态 - health: {}, - monitorStatus: 'unknown', - - // Agent相关 - agentStatus: { - status: 'inactive', - active_goals: 0, - available_tools: 0 - }, - agentHistory: [], - - // 车辆数据 - vehicleData: [], - - // UI状态 - loading: false, - notifications: [] - }; - - this.listeners = []; - this.debounceTimers = new Map(); - } - - // 获取状态 - getState() { - return { ...this.state }; - } - - // 更新状态 - setState(updates) { - const prevState = { ...this.state }; - this.state = { ...this.state, ...updates }; - - // 通知监听器 - this.notifyListeners(prevState, this.state); - } - - // 订阅状态变化 - subscribe(listener) { - this.listeners.push(listener); - return () => { - this.listeners = this.listeners.filter(l => l !== listener); - }; - } - - // 通知监听器 - notifyListeners(prevState, newState) { - this.listeners.forEach(listener => { - try { - listener(prevState, newState); - } catch (error) { - console.error('状态监听器错误:', error); - } - }); - } - - // 防抖更新状态 - setStateDebounced(updates, delay = 300) { - const key = JSON.stringify(updates); - - if (this.debounceTimers.has(key)) { - clearTimeout(this.debounceTimers.get(key)); - } - - this.debounceTimers.set(key, setTimeout(() => { - this.setState(updates); - this.debounceTimers.delete(key); - }, delay)); - } - - // 预警相关方法 - updateAlerts(alerts) { - this.setState({ alerts }); - - // 更新统计信息 - const stats = { - total: alerts.length, - critical: alerts.filter(a => a.level === 'critical').length, - warning: alerts.filter(a => a.level === 'warning').length, - info: alerts.filter(a => a.level === 'info').length - }; - this.setState({ alertStats: stats }); - } - - updateAlertFilters(filters) { - this.setState({ alertFilters: { ...this.state.alertFilters, ...filters } }); - } - - // 规则相关方法 - updateRules(rules) { - this.setState({ rules }); - } - - // 系统状态相关方法 - updateHealth(health) { - this.setState({ health }); - } - - updateMonitorStatus(status) { - this.setState({ monitorStatus: status }); - } - - // Agent相关方法 - updateAgentStatus(status) { - this.setState({ agentStatus: status }); - } - - updateAgentHistory(history) { - this.setState({ agentHistory: history }); - } - - // 车辆数据相关方法 - updateVehicleData(data) { - this.setState({ vehicleData: data }); - } - - // UI状态相关方法 - setLoading(loading) { - this.setState({ loading }); - } - - // 通知相关方法 - addNotification(notification) { - const notifications = [...this.state.notifications, { - id: Date.now(), - timestamp: new Date(), - ...notification - }]; - this.setState({ notifications }); - - // 3秒后自动移除 - setTimeout(() => { - this.removeNotification(notification.id || notifications[notifications.length - 1].id); - }, 3000); - } - - removeNotification(id) { - const notifications = this.state.notifications.filter(n => n.id !== id); - this.setState({ notifications }); - } - - // 获取过滤后的预警 - getFilteredAlerts() { - const { alerts, alertFilters } = this.state; - - return alerts.filter(alert => { - if (alertFilters.level !== 'all' && alert.level !== alertFilters.level) return false; - if (alertFilters.type !== 'all' && alert.alert_type !== alertFilters.type) return false; - if (alertFilters.status !== 'all' && alert.status !== alertFilters.status) return false; - return true; - }); - } - - // 获取排序后的预警 - getSortedAlerts(sortBy = 'timestamp', sortOrder = 'desc') { - const filtered = this.getFilteredAlerts(); - - return filtered.sort((a, b) => { - let aVal = a[sortBy]; - let bVal = b[sortBy]; - - if (sortBy === 'timestamp') { - aVal = new Date(aVal); - bVal = new Date(bVal); - } - - if (sortOrder === 'asc') { - return aVal > bVal ? 1 : -1; - } else { - return aVal < bVal ? 1 : -1; - } - }); - } -} - -// 创建全局状态管理实例 -const store = new Store(); \ No newline at end of file diff --git a/src/web/static/js/core/utils.js b/src/web/static/js/core/utils.js deleted file mode 100644 index 4b0271f..0000000 --- a/src/web/static/js/core/utils.js +++ /dev/null @@ -1,431 +0,0 @@ -/** - * 工具函数集合 - */ - -// 防抖函数 -export function debounce(func, wait) { - let timeout; - return function executedFunction(...args) { - const later = () => { - clearTimeout(timeout); - func(...args); - }; - clearTimeout(timeout); - timeout = setTimeout(later, wait); - }; -} - -// 节流函数 -export function throttle(func, limit) { - let inThrottle; - return function(...args) { - if (!inThrottle) { - func.apply(this, args); - inThrottle = true; - setTimeout(() => inThrottle = false, limit); - } - }; -} - -// 格式化日期 -export function formatDate(date, format = 'YYYY-MM-DD HH:mm:ss') { - if (!date) return ''; - - const d = new Date(date); - const year = d.getFullYear(); - const month = String(d.getMonth() + 1).padStart(2, '0'); - const day = String(d.getDate()).padStart(2, '0'); - const hours = String(d.getHours()).padStart(2, '0'); - const minutes = String(d.getMinutes()).padStart(2, '0'); - const seconds = String(d.getSeconds()).padStart(2, '0'); - - return format - .replace('YYYY', year) - .replace('MM', month) - .replace('DD', day) - .replace('HH', hours) - .replace('mm', minutes) - .replace('ss', seconds); -} - -// 相对时间格式化 -export function formatRelativeTime(date) { - if (!date) return ''; - - const now = new Date(); - const target = new Date(date); - const diff = now - target; - - const seconds = Math.floor(diff / 1000); - const minutes = Math.floor(seconds / 60); - const hours = Math.floor(minutes / 60); - const days = Math.floor(hours / 24); - - if (days > 30) { - return formatDate(date, 'YYYY-MM-DD'); - } else if (days > 0) { - return `${days}天前`; - } else if (hours > 0) { - return `${hours}小时前`; - } else if (minutes > 0) { - return `${minutes}分钟前`; - } else { - return '刚刚'; - } -} - -// 文件大小格式化 -export function formatFileSize(bytes) { - if (bytes === 0) return '0 B'; - - const k = 1024; - const sizes = ['B', 'KB', 'MB', 'GB', 'TB']; - const i = Math.floor(Math.log(bytes) / Math.log(k)); - - return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; -} - -// 数字千分位格式化 -export function formatNumber(num) { - return num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ','); -} - -// 深拷贝 -export function deepClone(obj) { - if (obj === null || typeof obj !== 'object') { - return obj; - } - - if (obj instanceof Date) { - return new Date(obj.getTime()); - } - - if (obj instanceof Array) { - return obj.map(item => deepClone(item)); - } - - if (typeof obj === 'object') { - const clonedObj = {}; - for (const key in obj) { - if (obj.hasOwnProperty(key)) { - clonedObj[key] = deepClone(obj[key]); - } - } - return clonedObj; - } -} - -// 生成唯一ID -export function generateId(prefix = '') { - const timestamp = Date.now().toString(36); - const randomStr = Math.random().toString(36).substr(2, 5); - return `${prefix}${timestamp}${randomStr}`; -} - -// 查询参数解析 -export function parseQueryString(queryString) { - const params = new URLSearchParams(queryString); - const result = {}; - - for (const [key, value] of params) { - result[key] = value; - } - - return result; -} - -// 查询参数序列化 -export function serializeQueryString(params) { - return Object.entries(params) - .filter(([_, value]) => value !== undefined && value !== null) - .map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`) - .join('&'); -} - -// 获取查询参数 -export function getQueryParam(name) { - const params = new URLSearchParams(window.location.search); - return params.get(name); -} - -// 设置查询参数 -export function setQueryParam(name, value) { - const params = new URLSearchParams(window.location.search); - params.set(name, value); - const newUrl = `${window.location.pathname}?${params.toString()}${window.location.hash}`; - window.history.replaceState(null, '', newUrl); -} - -// 删除查询参数 -export function removeQueryParam(name) { - const params = new URLSearchParams(window.location.search); - params.delete(name); - const newUrl = `${window.location.pathname}${params.toString() ? '?' + params.toString() : ''}${window.location.hash}`; - window.history.replaceState(null, '', newUrl); -} - -// 本地存储封装 -export const storage = { - set(key, value, isSession = false) { - try { - const storage = isSession ? sessionStorage : localStorage; - storage.setItem(key, JSON.stringify(value)); - } catch (e) { - console.error('Storage set error:', e); - } - }, - - get(key, defaultValue = null, isSession = false) { - try { - const storage = isSession ? sessionStorage : localStorage; - const item = storage.getItem(key); - return item ? JSON.parse(item) : defaultValue; - } catch (e) { - console.error('Storage get error:', e); - return defaultValue; - } - }, - - remove(key, isSession = false) { - try { - const storage = isSession ? sessionStorage : localStorage; - storage.removeItem(key); - } catch (e) { - console.error('Storage remove error:', e); - } - }, - - clear(isSession = false) { - try { - const storage = isSession ? sessionStorage : localStorage; - storage.clear(); - } catch (e) { - console.error('Storage clear error:', e); - } - } -}; - -// Cookie操作 -export const cookie = { - set(name, value, days = 7) { - const date = new Date(); - date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000)); - const expires = `expires=${date.toUTCString()}`; - document.cookie = `${name}=${value};${expires};path=/`; - }, - - get(name) { - const nameEQ = `${name}=`; - const ca = document.cookie.split(';'); - for (let c of ca) { - while (c.charAt(0) === ' ') { - c = c.substring(1); - } - if (c.indexOf(nameEQ) === 0) { - return c.substring(nameEQ.length, c.length); - } - } - return null; - }, - - remove(name) { - document.cookie = `${name}=;expires=Thu, 01 Jan 1970 00:00:00 UTC;path=/;`; - } -}; - -// 类名操作 -export function addClass(element, className) { - if (element.classList) { - element.classList.add(className); - } else { - element.className += ` ${className}`; - } -} - -export function removeClass(element, className) { - if (element.classList) { - element.classList.remove(className); - } else { - element.className = element.className.replace( - new RegExp(`(^|\\b)${className.split(' ').join('|')}(\\b|$)`, 'gi'), - ' ' - ); - } -} - -export function hasClass(element, className) { - if (element.classList) { - return element.classList.contains(className); - } else { - return new RegExp(`(^| )${className}( |$)`, 'gi').test(element.className); - } -} - -export function toggleClass(element, className) { - if (hasClass(element, className)) { - removeClass(element, className); - } else { - addClass(element, className); - } -} - -// 事件委托 -export function delegate(parent, selector, event, handler) { - parent.addEventListener(event, function(e) { - if (e.target.matches(selector)) { - handler(e); - } - }); -} - -// DOM就绪 -export function ready(fn) { - if (document.readyState !== 'loading') { - fn(); - } else { - document.addEventListener('DOMContentLoaded', fn); - } -} - -// 动画帧 -export const raf = window.requestAnimationFrame || - window.webkitRequestAnimationFrame || - window.mozRequestAnimationFrame || - function(callback) { return setTimeout(callback, 1000 / 60); }; - -export const caf = window.cancelAnimationFrame || - window.webkitCancelAnimationFrame || - window.mozCancelAnimationFrame || - function(id) { clearTimeout(id); }; - -// 错误处理 -export function handleError(error, context = '') { - console.error(`Error in ${context}:`, error); - - // 发送错误到服务器(可选) - if (window.errorReporting) { - window.errorReporting.report(error, context); - } - - // 显示用户友好的错误信息 - showToast('发生错误,请稍后重试', 'error'); -} - -// 安全的JSON解析 -export function safeJsonParse(str, defaultValue = null) { - try { - return JSON.parse(str); - } catch (e) { - return defaultValue; - } -} - -// XSS防护 - HTML转义 -export function escapeHtml(text) { - const map = { - '&': '&', - '<': '<', - '>': '>', - '"': '"', - "'": ''' - }; - return text.replace(/[&<>"']/g, m => map[m]); -} - -// 验证函数 -export const validators = { - email: (email) => { - const re = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; - return re.test(email); - }, - - phone: (phone) => { - const re = /^1[3-9]\d{9}$/; - return re.test(phone); - }, - - url: (url) => { - try { - new URL(url); - return true; - } catch { - return false; - } - }, - - idCard: (idCard) => { - const re = /(^\d{15}$)|(^\d{18}$)|(^\d{17}(\d|X|x)$)/; - return re.test(idCard); - } -}; - -// URL构建器 -export class URLBuilder { - constructor(baseURL = '') { - this.baseURL = baseURL; - this.params = {}; - } - - path(path) { - this.baseURL = this.baseURL.replace(/\/$/, '') + '/' + path.replace(/^\//, ''); - return this; - } - - query(key, value) { - if (value !== undefined && value !== null) { - this.params[key] = value; - } - return this; - } - - build() { - const queryString = serializeQueryString(this.params); - return queryString ? `${this.baseURL}?${queryString}` : this.baseURL; - } -} - -// 图片压缩 -export function compressImage(file, options = {}) { - const { - maxWidth = 800, - maxHeight = 800, - quality = 0.8, - mimeType = 'image/jpeg' - } = options; - - return new Promise((resolve) => { - const canvas = document.createElement('canvas'); - const ctx = canvas.getContext('2d'); - const img = new Image(); - - img.onload = () => { - // 计算新尺寸 - let { width, height } = img; - - if (width > maxWidth || height > maxHeight) { - const ratio = Math.min(maxWidth / width, maxHeight / height); - width *= ratio; - height *= ratio; - } - - canvas.width = width; - canvas.height = height; - - // 绘制并压缩 - ctx.drawImage(img, 0, 0, width, height); - canvas.toBlob(resolve, mimeType, quality); - }; - - img.src = URL.createObjectURL(file); - }); -} - -// 导出默认配置 -export const defaultConfig = { - apiBaseUrl: '/api', - wsUrl: `ws://${window.location.host}:8765`, - toastDuration: 3000, - paginationSize: 10, - debounceDelay: 300, - throttleDelay: 100 -}; \ No newline at end of file diff --git a/src/web/static/js/core/websocket.js b/src/web/static/js/core/websocket.js deleted file mode 100644 index 2204074..0000000 --- a/src/web/static/js/core/websocket.js +++ /dev/null @@ -1,441 +0,0 @@ -/** - * WebSocket管理模块 - */ - -import { defaultConfig, storage, debounce } from './utils.js'; -import store from './store.js'; - -// WebSocket配置 -const config = { - ...defaultConfig, - reconnectInterval: 3000, // 重连间隔 - maxReconnectAttempts: 5, // 最大重连次数 - heartbeatInterval: 30000, // 心跳间隔 - heartbeatTimeout: 5000, // 心跳超时 - messageQueue: [], // 消息队列 - debug: false // 调试模式 -}; - -// WebSocket状态枚举 -export const WebSocketState = { - CONNECTING: 0, - OPEN: 1, - CLOSING: 2, - CLOSED: 3 -}; - -// WebSocket管理器类 -class WebSocketManager { - constructor(url = config.wsUrl) { - this.url = url; - this.ws = null; - this.state = WebSocketState.CLOSED; - this.reconnectAttempts = 0; - this.reconnectTimer = null; - this.heartbeatTimer = null; - this.heartbeatTimeoutTimer = null; - this.messageHandlers = new Map(); - this.readyStateHandlers = []; - this.messageId = 0; - this.pendingRequests = new Map(); - - // 绑定方法 - this.onOpen = this.onOpen.bind(this); - this.onClose = this.onClose.bind(this); - this.onMessage = this.onMessage.bind(this); - this.onError = this.onError.bind(this); - } - - // 连接WebSocket - connect() { - if (this.ws && this.ws.readyState === WebSocket.OPEN) { - this.log('WebSocket already connected'); - return; - } - - // 清除之前的重连定时器 - if (this.reconnectTimer) { - clearTimeout(this.reconnectTimer); - this.reconnectTimer = null; - } - - this.log('Connecting to WebSocket...'); - this.setState(WebSocketState.CONNECTING); - - try { - // 构建WebSocket URL,添加认证信息 - const token = storage.get('authToken'); - const wsUrl = token ? `${this.url}?token=${token}` : this.url; - - this.ws = new WebSocket(wsUrl); - - // 绑定事件处理器 - this.ws.onopen = this.onOpen; - this.ws.onclose = this.onClose; - this.ws.onmessage = this.onMessage; - this.ws.onerror = this.onError; - - } catch (error) { - this.log('WebSocket connection error:', error); - this.handleError(error); - } - } - - // 断开连接 - disconnect() { - this.log('Disconnecting WebSocket...'); - this.setState(WebSocketState.CLOSING); - - // 停止重连 - if (this.reconnectTimer) { - clearTimeout(this.reconnectTimer); - this.reconnectTimer = null; - } - - // 停止心跳 - this.stopHeartbeat(); - - // 关闭连接 - if (this.ws) { - this.ws.close(); - this.ws = null; - } - - this.setState(WebSocketState.CLOSED); - } - - // 发送消息 - send(type, data = {}, options = {}) { - return new Promise((resolve, reject) => { - if (this.ws && this.ws.readyState === WebSocket.OPEN) { - const message = { - id: this.messageId++, - type, - data, - timestamp: Date.now() - }; - - // 如果需要响应,保存回调 - if (options.expectResponse) { - this.pendingRequests.set(message.id, { - resolve, - reject, - timeout: setTimeout(() => { - this.pendingRequests.delete(message.id); - reject(new Error('Request timeout')); - }, options.timeout || 30000) - }); - } - - // 发送消息 - this.ws.send(JSON.stringify(message)); - this.log('Sent message:', message); - - // 如果不需要响应,立即解决 - if (!options.expectResponse) { - resolve(message.id); - } - } else { - // 连接未就绪,加入队列 - config.messageQueue.push({ type, data, options, resolve, reject }); - reject(new Error('WebSocket not connected')); - } - }); - } - - // 注册消息处理器 - on(type, handler) { - if (!this.messageHandlers.has(type)) { - this.messageHandlers.set(type, []); - } - this.messageHandlers.get(type).push(handler); - } - - // 取消注册消息处理器 - off(type, handler) { - if (this.messageHandlers.has(type)) { - const handlers = this.messageHandlers.get(type); - const index = handlers.indexOf(handler); - if (index > -1) { - handlers.splice(index, 1); - } - } - } - - // 注册状态变化处理器 - onReadyStateChange(handler) { - this.readyStateHandlers.push(handler); - } - - // 事件处理器 - onOpen() { - this.log('WebSocket connected'); - this.setState(WebSocketState.OPEN); - this.reconnectAttempts = 0; - - // 启动心跳 - this.startHeartbeat(); - - // 发送队列中的消息 - this.flushMessageQueue(); - - // 通知状态变化 - this.notifyReadyStateChange(); - - // 更新store状态 - store.commit('SET_WS_CONNECTED', true); - } - - onClose(event) { - this.log('WebSocket closed:', event); - this.setState(WebSocketState.CLOSED); - - // 停止心跳 - this.stopHeartbeat(); - - // 拒绝所有待处理的请求 - this.pendingRequests.forEach(({ reject, timeout }) => { - clearTimeout(timeout); - reject(new Error('WebSocket closed')); - }); - this.pendingRequests.clear(); - - // 通知状态变化 - this.notifyReadyStateChange(); - - // 更新store状态 - store.commit('SET_WS_CONNECTED', false); - - // 自动重连 - if (!event.wasClean && this.reconnectAttempts < config.maxReconnectAttempts) { - this.reconnect(); - } - } - - onMessage(event) { - try { - const message = JSON.parse(event.data); - this.log('Received message:', message); - - // 处理响应消息 - if (message.id && this.pendingRequests.has(message.id)) { - const { resolve, reject, timeout } = this.pendingRequests.get(message.id); - clearTimeout(timeout); - this.pendingRequests.delete(message.id); - - if (message.error) { - reject(new Error(message.error)); - } else { - resolve(message.data); - } - return; - } - - // 处理业务消息 - const handlers = this.messageHandlers.get(message.type); - if (handlers) { - handlers.forEach(handler => { - try { - handler(message.data, message); - } catch (error) { - this.log('Handler error:', error); - } - }); - } else { - this.log('No handler for message type:', message.type); - } - - // 处理心跳响应 - if (message.type === 'pong') { - this.handlePong(); - } - - } catch (error) { - this.log('Message parse error:', error); - } - } - - onError(error) { - this.log('WebSocket error:', error); - this.handleError(error); - } - - // 重连 - reconnect() { - if (this.reconnectAttempts >= config.maxReconnectAttempts) { - this.log('Max reconnect attempts reached'); - return; - } - - this.reconnectAttempts++; - this.log(`Reconnecting... Attempt ${this.reconnectAttempts}`); - - this.reconnectTimer = setTimeout(() => { - this.connect(); - }, config.reconnectInterval); - } - - // 启动心跳 - startHeartbeat() { - this.heartbeatTimer = setInterval(() => { - this.send('ping').catch(error => { - this.log('Heartbeat error:', error); - }); - - // 设置心跳超时 - this.heartbeatTimeoutTimer = setTimeout(() => { - this.log('Heartbeat timeout'); - this.disconnect(); - this.reconnect(); - }, config.heartbeatTimeout); - }, config.heartbeatInterval); - } - - // 停止心跳 - stopHeartbeat() { - if (this.heartbeatTimer) { - clearInterval(this.heartbeatTimer); - this.heartbeatTimer = null; - } - if (this.heartbeatTimeoutTimer) { - clearTimeout(this.heartbeatTimeoutTimer); - this.heartbeatTimeoutTimer = null; - } - } - - // 处理pong响应 - handlePong() { - if (this.heartbeatTimeoutTimer) { - clearTimeout(this.heartbeatTimeoutTimer); - this.heartbeatTimeoutTimer = null; - } - } - - // 清空消息队列 - flushMessageQueue() { - while (config.messageQueue.length > 0) { - const { type, data, options, resolve, reject } = config.messageQueue.shift(); - this.send(type, data, options).then(resolve).catch(reject); - } - } - - // 设置状态 - setState(state) { - this.state = state; - this.notifyReadyStateChange(); - } - - // 通知状态变化 - notifyReadyStateChange() { - this.readyStateHandlers.forEach(handler => { - try { - handler(this.state); - } catch (error) { - this.log('ReadyState handler error:', error); - } - }); - } - - // 处理错误 - handleError(error) { - this.log('WebSocket error:', error); - - // 显示错误提示 - store.dispatch('showToast', { - type: 'error', - message: 'WebSocket连接失败' - }); - } - - // 日志输出 - log(...args) { - if (config.debug) { - console.log('[WebSocket]', ...args); - } - } - - // 获取连接状态 - getReadyState() { - return this.state; - } - - // 检查是否已连接 - isConnected() { - return this.state === WebSocket.OPEN; - } -} - -// 创建全局WebSocket实例 -export const wsManager = new WebSocketManager(); - -// 扩展WebSocket管理器,添加业务方法 -wsManager.on('connected', (data) => { - store.dispatch('showToast', { - type: 'success', - message: 'WebSocket已连接' - }); -}); - -wsManager.on('disconnected', (data) => { - store.dispatch('showToast', { - type: 'warning', - message: 'WebSocket已断开' - }); -}); - -// 业务方法封装 -export const wsApi = { - // 创建会话 - createSession: (data) => wsManager.send('create_session', data, { expectResponse: true }), - - // 发送消息 - sendMessage: (data) => wsManager.send('send_message', data, { expectResponse: true }), - - // 获取历史记录 - getHistory: (sessionId) => wsManager.send('get_history', { session_id: sessionId }, { expectResponse: true }), - - // 创建工单 - createWorkOrder: (data) => wsManager.send('create_work_order', data, { expectResponse: true }), - - // 获取工单状态 - getWorkOrderStatus: (workOrderId) => wsManager.send('get_work_order_status', { work_order_id: workOrderId }, { expectResponse: true }), - - // 结束会话 - endSession: (sessionId) => wsManager.send('end_session', { session_id: sessionId }, { expectResponse: true }) -}; - -// 初始化WebSocket连接 -export function initWebSocket() { - // 页面可见时连接 - document.addEventListener('visibilitychange', () => { - if (!document.hidden && !wsManager.isConnected()) { - wsManager.connect(); - } - }); - - // 窗口获得焦点时检查连接 - window.addEventListener('focus', () => { - if (!wsManager.isConnected()) { - wsManager.connect(); - } - }); - - // 页面卸载时断开连接 - window.addEventListener('beforeunload', () => { - wsManager.disconnect(); - }); - - // 网络状态变化时重连 - window.addEventListener('online', () => { - if (!wsManager.isConnected()) { - wsManager.connect(); - } - }); - - // 自动连接 - wsManager.connect(); -} - -// 导出配置和管理器 -export { config }; -export default wsManager; \ No newline at end of file diff --git a/src/web/static/js/pages/agent.js b/src/web/static/js/pages/agent.js deleted file mode 100644 index 2f0b07c..0000000 --- a/src/web/static/js/pages/agent.js +++ /dev/null @@ -1,560 +0,0 @@ -/** - * Agent页面组件 - */ - -export default class Agent { - constructor(container, route) { - this.container = container; - this.route = route; - this.init(); - } - - async init() { - try { - this.render(); - this.bindEvents(); - this.loadAgentStatus(); - this.loadActionHistory(); - } catch (error) { - console.error('Agent init error:', error); - this.showError(error); - } - } - - render() { - this.container.innerHTML = ` - - -
- -
-
-
-
- Agent状态 -
-
-
-
-
- 加载中... -
-
加载Agent状态...
-
-
-
-
- - -
-
-
-
- 控制面板 -
-
-
-
- - - - -
-
-
-
-
- - -
-
-
-
-
- Agent对话 -
-
-
-
-
暂无对话记录
-
-
- - -
-
-
-
- - -
-
-
-
- 工具统计 -
-
-
-
- 加载中... -
-
-
- - -
-
-
- LLM使用统计 -
-
-
-
- 加载中... -
-
-
-
-
- - -
-
-
-
-
- 执行历史 -
- -
-
-
- 加载中... -
-
-
-
-
- `; - } - - bindEvents() { - // 控制按钮事件 - document.getElementById('start-monitoring-btn').addEventListener('click', () => { - this.startMonitoring(); - }); - - document.getElementById('stop-monitoring-btn').addEventListener('click', () => { - this.stopMonitoring(); - }); - - document.getElementById('run-analysis-btn').addEventListener('click', () => { - this.runAnalysis(); - }); - - document.getElementById('trigger-sample-btn').addEventListener('click', () => { - this.triggerSampleActions(); - }); - - // 对话事件 - document.getElementById('send-chat-btn').addEventListener('click', () => { - this.sendChatMessage(); - }); - - document.getElementById('chat-input').addEventListener('keypress', (e) => { - if (e.key === 'Enter') { - this.sendChatMessage(); - } - }); - - // 清空历史 - document.getElementById('clear-history-btn').addEventListener('click', () => { - this.clearHistory(); - }); - - // 定期刷新状态 - this.statusInterval = setInterval(() => { - this.loadAgentStatus(); - }, 5000); - } - - async loadAgentStatus() { - try { - const response = await fetch('/api/agent/status'); - const data = await response.json(); - - const statusDiv = document.getElementById('agent-status'); - if (data.success) { - const status = data.status || 'unknown'; - const activeGoals = data.active_goals || 0; - const availableTools = data.available_tools || 0; - - let statusClass = 'text-warning'; - let statusText = '未知状态'; - - switch (status) { - case 'active': - statusClass = 'text-success'; - statusText = '运行中'; - break; - case 'inactive': - statusClass = 'text-secondary'; - statusText = '未激活'; - break; - case 'error': - statusClass = 'text-danger'; - statusText = '错误'; - break; - } - - statusDiv.innerHTML = ` -
- - ${statusText} -
-
-
-
${activeGoals}
- 活跃目标 -
-
-
${availableTools}
- 可用工具 -
-
- `; - } else { - statusDiv.innerHTML = ` -
- -
Agent服务不可用
-
- `; - } - } catch (error) { - console.error('加载Agent状态失败:', error); - document.getElementById('agent-status').innerHTML = ` -
- -
加载状态失败
-
- `; - } - } - - async loadActionHistory() { - try { - const response = await fetch('/api/agent/action-history?limit=20'); - const data = await response.json(); - - const historyDiv = document.getElementById('action-history'); - if (data.success && data.history.length > 0) { - let html = '
'; - html += ''; - - data.history.forEach(action => { - const timestamp = new Date(action.timestamp).toLocaleString(); - const statusClass = action.success ? 'text-success' : 'text-danger'; - const statusText = action.success ? '成功' : '失败'; - - html += ` - - - - - `; - }); - - html += '
时间动作状态详情
${timestamp}${action.action_type || '未知'}${statusText}${action.details || ''}
'; - historyDiv.innerHTML = html; - } else { - historyDiv.innerHTML = '
暂无执行历史
'; - } - } catch (error) { - console.error('加载执行历史失败:', error); - document.getElementById('action-history').innerHTML = '
加载历史失败
'; - } - } - - async loadToolsStats() { - try { - const response = await fetch('/api/agent/tools/stats'); - const data = await response.json(); - - const statsDiv = document.getElementById('tools-stats'); - if (data.success) { - const tools = data.tools || []; - const performance = data.performance || {}; - - let html = `
工具数量: ${tools.length}
`; - - if (tools.length > 0) { - html += '
可用工具:
'; - } - - statsDiv.innerHTML = html; - } else { - statsDiv.innerHTML = '
获取工具统计失败
'; - } - } catch (error) { - console.error('加载工具统计失败:', error); - document.getElementById('tools-stats').innerHTML = '
加载失败
'; - } - } - - async loadLLMStats() { - try { - const response = await fetch('/api/agent/llm-stats'); - const data = await response.json(); - - const statsDiv = document.getElementById('llm-stats'); - if (data.success) { - const stats = data.stats || {}; - let html = ''; - - if (stats.total_requests) { - html += `
总请求数: ${stats.total_requests}
`; - } - if (stats.success_rate !== undefined) { - html += `
成功率: ${(stats.success_rate * 100).toFixed(1)}%
`; - } - if (stats.average_response_time) { - html += `
平均响应时间: ${stats.average_response_time.toFixed(2)}s
`; - } - - if (!html) { - html = '
暂无统计数据
'; - } - - statsDiv.innerHTML = html; - } else { - statsDiv.innerHTML = '
获取LLM统计失败
'; - } - } catch (error) { - console.error('加载LLM统计失败:', error); - document.getElementById('llm-stats').innerHTML = '
加载失败
'; - } - } - - async startMonitoring() { - try { - const response = await fetch('/api/agent/monitoring/start', { method: 'POST' }); - const data = await response.json(); - - if (data.success) { - if (window.showToast) { - window.showToast('监控已启动', 'success'); - } - this.loadAgentStatus(); - } else { - if (window.showToast) { - window.showToast(data.message || '启动监控失败', 'error'); - } - } - } catch (error) { - console.error('启动监控失败:', error); - if (window.showToast) { - window.showToast('网络错误', 'error'); - } - } - } - - async stopMonitoring() { - try { - const response = await fetch('/api/agent/monitoring/stop', { method: 'POST' }); - const data = await response.json(); - - if (data.success) { - if (window.showToast) { - window.showToast('监控已停止', 'success'); - } - this.loadAgentStatus(); - } else { - if (window.showToast) { - window.showToast(data.message || '停止监控失败', 'error'); - } - } - } catch (error) { - console.error('停止监控失败:', error); - if (window.showToast) { - window.showToast('网络错误', 'error'); - } - } - } - - async runAnalysis() { - try { - const response = await fetch('/api/agent/intelligent-analysis', { method: 'POST' }); - const data = await response.json(); - - if (data.success) { - if (window.showToast) { - window.showToast('智能分析完成', 'success'); - } - this.loadActionHistory(); - } else { - if (window.showToast) { - window.showToast('分析失败', 'error'); - } - } - } catch (error) { - console.error('运行分析失败:', error); - if (window.showToast) { - window.showToast('网络错误', 'error'); - } - } - } - - async triggerSampleActions() { - try { - const response = await fetch('/api/agent/trigger-sample', { method: 'POST' }); - const data = await response.json(); - - if (data.success) { - if (window.showToast) { - window.showToast('示例动作已触发', 'success'); - } - this.loadActionHistory(); - } else { - if (window.showToast) { - window.showToast('触发示例动作失败', 'error'); - } - } - } catch (error) { - console.error('触发示例动作失败:', error); - if (window.showToast) { - window.showToast('网络错误', 'error'); - } - } - } - - async sendChatMessage() { - const input = document.getElementById('chat-input'); - const message = input.value.trim(); - - if (!message) { - return; - } - - // 添加用户消息到界面 - this.addMessageToChat('user', message); - input.value = ''; - - try { - const response = await fetch('/api/agent/chat', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ - message: message, - context: { user_id: 'admin' } - }) - }); - - const data = await response.json(); - - if (data.success) { - // 添加Agent回复到界面 - this.addMessageToChat('agent', data.response); - } else { - this.addMessageToChat('agent', '抱歉,处理您的请求时出现错误。'); - } - } catch (error) { - console.error('发送消息失败:', error); - this.addMessageToChat('agent', '网络错误,请稍后重试。'); - } - } - - addMessageToChat(sender, message) { - const chatMessages = document.getElementById('chat-messages'); - const messageDiv = document.createElement('div'); - messageDiv.className = `chat-message ${sender === 'user' ? 'text-end' : ''}`; - - const time = new Date().toLocaleTimeString(); - messageDiv.innerHTML = ` -
-
${message}
-
${time}
-
- `; - - chatMessages.appendChild(messageDiv); - chatMessages.scrollTop = chatMessages.scrollHeight; - } - - async clearHistory() { - if (!confirm('确定要清空所有执行历史吗?')) { - return; - } - - try { - const response = await fetch('/api/agent/clear-history', { method: 'POST' }); - const data = await response.json(); - - if (data.success) { - if (window.showToast) { - window.showToast('历史已清空', 'success'); - } - this.loadActionHistory(); - } else { - if (window.showToast) { - window.showToast('清空历史失败', 'error'); - } - } - } catch (error) { - console.error('清空历史失败:', error); - if (window.showToast) { - window.showToast('网络错误', 'error'); - } - } - } - - showError(error) { - this.container.innerHTML = ` -
-
-
-
- -

页面加载失败

-

${error.message || '未知错误'}

- -
-
-
-
- `; - } - - destroy() { - if (this.statusInterval) { - clearInterval(this.statusInterval); - } - } -} diff --git a/src/web/static/js/pages/alerts.js b/src/web/static/js/pages/alerts.js deleted file mode 100644 index 0bfb484..0000000 --- a/src/web/static/js/pages/alerts.js +++ /dev/null @@ -1,738 +0,0 @@ -/** - * 预警管理页面组件 - */ - -import { api } from '../core/api.js'; -import { formatDate, formatRelativeTime } from '../core/utils.js'; -import { confirm, alert } from '../components/modal.js'; -import store from '../core/store.js'; - -export default class Alerts { - constructor(container, route) { - this.container = container; - this.route = route; - this.filters = { - level: '', - status: '', - type: '', - page: 1, - per_page: 10 - }; - this.init(); - } - - async init() { - try { - this.render(); - await this.loadData(); - this.bindEvents(); - } catch (error) { - console.error('Alerts page init error:', error); - this.showError(error); - } - } - - render() { - this.container.innerHTML = ` - - - -
-
-
-
-
0
-
严重预警
-
-
-
-
-
-
-
0
-
警告预警
-
-
-
-
-
-
-
0
-
信息预警
-
-
-
-
-
-
-
0
-
总预警数
-
-
-
-
- - -
-
-
监控控制
-
-
-
-
-
- 监控状态: - - 检查中... - -
-
-
- - -
-
-
-
- - -
-
-
预警规则
- -
-
-
- - - - - - - - - - - - - - - - -
规则名称类型级别阈值状态操作
-
-
-
-
-
- - -
-
-
预警历史
-
-
- -
-
- -
-
- -
-
- -
-
- -
-
- - -
- - - - - - - - - - - - - - - - -
时间级别规则消息状态操作
-
-
-
- - - -
-
- `; - - // 渲染规则模态框 - this.renderRuleModal(); - } - - renderRuleModal() { - const modalHTML = ` - - `; - document.body.insertAdjacentHTML('beforeend', modalHTML); - } - - async loadData() { - try { - // 并行加载数据 - const [alertsRes, rulesRes, statistics, monitorStatus] = await Promise.all([ - api.alerts.list(this.filters), - api.rules.list(), - api.alerts.statistics(), - api.monitor.status() - ]); - - // 更新统计数据 - this.updateStatistics(statistics); - - // 更新监控状态 - this.updateMonitorStatus(monitorStatus); - - // 更新规则列表 - this.updateRulesList(rulesRes); - - // 更新预警列表 - this.updateAlertsList(alertsRes); - - } catch (error) { - console.error('Load alerts data error:', error); - this.showError(error); - } - } - - updateStatistics(statistics) { - document.getElementById('critical-alerts').textContent = statistics.critical || 0; - document.getElementById('warning-alerts').textContent = statistics.warning || 0; - document.getElementById('info-alerts').textContent = statistics.info || 0; - document.getElementById('total-alerts').textContent = statistics.total || 0; - } - - updateMonitorStatus(status) { - const statusEl = document.getElementById('monitor-status'); - const textEl = document.getElementById('monitor-text'); - - if (status.status === 'running') { - statusEl.className = 'status-indicator online'; - textEl.textContent = '监控运行中'; - } else { - statusEl.className = 'status-indicator offline'; - textEl.textContent = '监控已停止'; - } - } - - updateRulesList(rules) { - const tbody = document.getElementById('rules-table'); - if (!tbody) return; - - if (!rules || rules.length === 0) { - tbody.innerHTML = ` - - 暂无预警规则 - - `; - return; - } - - tbody.innerHTML = rules.map(rule => ` - - ${rule.name} - ${rule.alert_type} - ${rule.level} - ${rule.threshold} - - - ${rule.enabled ? '启用' : '禁用'} - - - -
- - -
- - - `).join(''); - } - - updateAlertsList(response) { - const tbody = document.getElementById('alerts-table'); - if (!tbody) return; - - const alerts = response.alerts || []; - - if (alerts.length === 0) { - tbody.innerHTML = ` - - 暂无预警记录 - - `; - return; - } - - tbody.innerHTML = alerts.map(alert => ` - - ${formatDate(alert.created_at, 'MM-DD HH:mm')} - ${alert.level} - ${alert.rule_name} - ${alert.message} - - - ${alert.is_active ? '活跃' : '已解决'} - - - - ${alert.is_active ? ` - - ` : '-'} - - - `).join(''); - - // 更新分页 - this.updatePagination(response); - } - - updatePagination(response) { - const pagination = document.getElementById('pagination'); - if (!pagination) return; - - const { page = 1, total_pages = 1 } = response; - - if (total_pages <= 1) { - pagination.innerHTML = ''; - return; - } - - let html = ''; - - // 上一页 - if (page > 1) { - html += `
  • - 上一页 -
  • `; - } - - // 页码 - for (let i = Math.max(1, page - 2); i <= Math.min(total_pages, page + 2); i++) { - html += `
  • - ${i} -
  • `; - } - - // 下一页 - if (page < total_pages) { - html += `
  • - 下一页 -
  • `; - } - - pagination.innerHTML = html; - } - - getLevelColor(level) { - const colors = { - 'critical': 'danger', - 'error': 'danger', - 'warning': 'warning', - 'info': 'info' - }; - return colors[level] || 'secondary'; - } - - bindEvents() { - // 监控控制 - document.getElementById('start-monitor')?.addEventListener('click', () => { - this.startMonitor(); - }); - - document.getElementById('stop-monitor')?.addEventListener('click', () => { - this.stopMonitor(); - }); - - document.getElementById('check-alerts')?.addEventListener('click', () => { - this.checkAlerts(); - }); - - // 规则表单 - document.getElementById('save-rule')?.addEventListener('click', () => { - this.saveRule(); - }); - - // 筛选器 - document.getElementById('filter-level')?.addEventListener('change', (e) => { - this.filters.level = e.target.value; - this.filters.page = 1; - this.loadAlerts(); - }); - - document.getElementById('filter-status')?.addEventListener('change', (e) => { - this.filters.status = e.target.value === 'active' ? 'active' : - e.target.value === 'resolved' ? 'resolved' : ''; - this.filters.page = 1; - this.loadAlerts(); - }); - - document.getElementById('filter-search')?.addEventListener('input', - this.debounce((e) => { - this.filters.search = e.target.value; - this.filters.page = 1; - this.loadAlerts(); - }, 300) - ); - - document.getElementById('reset-filters')?.addEventListener('click', () => { - this.resetFilters(); - }); - - // 分页 - document.getElementById('pagination')?.addEventListener('click', (e) => { - if (e.target.classList.contains('page-link')) { - e.preventDefault(); - const page = parseInt(e.target.dataset.page); - if (page) { - this.filters.page = page; - this.loadAlerts(); - } - } - }); - } - - debounce(func, wait) { - let timeout; - return function executedFunction(...args) { - const later = () => { - clearTimeout(timeout); - func(...args); - }; - clearTimeout(timeout); - timeout = setTimeout(later, wait); - }; - } - - async startMonitor() { - try { - await api.monitor.start(); - await this.loadData(); - store.dispatch('showToast', { - type: 'success', - message: '监控已启动' - }); - } catch (error) { - console.error('Start monitor error:', error); - store.dispatch('showToast', { - type: 'error', - message: '启动监控失败' - }); - } - } - - async stopMonitor() { - try { - await api.monitor.stop(); - await this.loadData(); - store.dispatch('showToast', { - type: 'success', - message: '监控已停止' - }); - } catch (error) { - console.error('Stop monitor error:', error); - store.dispatch('showToast', { - type: 'error', - message: '停止监控失败' - }); - } - } - - async checkAlerts() { - try { - const result = await api.monitor.checkAlerts(); - await this.loadData(); - store.dispatch('showToast', { - type: 'success', - message: `检查完成,发现 ${result.alerts_count || 0} 个新预警` - }); - } catch (error) { - console.error('Check alerts error:', error); - store.dispatch('showToast', { - type: 'error', - message: '检查预警失败' - }); - } - } - - async saveRule() { - const form = document.getElementById('rule-form'); - const formData = new FormData(form); - - const data = { - name: formData.get('name'), - alert_type: formData.get('alert_type'), - level: formData.get('level'), - threshold: parseFloat(formData.get('threshold')), - description: formData.get('description'), - condition: formData.get('condition'), - check_interval: parseInt(formData.get('check_interval')), - cooldown: parseInt(formData.get('cooldown')), - enabled: formData.has('enabled') - }; - - try { - await api.rules.create(data); - await this.loadData(); - - // 关闭模态框 - const modal = bootstrap.Modal.getInstance(document.getElementById('ruleModal')); - modal.hide(); - - // 重置表单 - form.reset(); - - store.dispatch('showToast', { - type: 'success', - message: '规则创建成功' - }); - } catch (error) { - console.error('Save rule error:', error); - store.dispatch('showToast', { - type: 'error', - message: '创建规则失败' - }); - } - } - - async editRule(name) { - // TODO: 实现编辑规则功能 - store.dispatch('showToast', { - type: 'info', - message: '编辑功能开发中' - }); - } - - async deleteRule(name) { - const confirmed = await confirm({ - title: '删除规则', - message: `确定要删除规则 "${name}" 吗?` - }); - - if (!confirmed) return; - - try { - await api.rules.delete(name); - await this.loadData(); - store.dispatch('showToast', { - type: 'success', - message: '规则已删除' - }); - } catch (error) { - console.error('Delete rule error:', error); - store.dispatch('showToast', { - type: 'error', - message: '删除规则失败' - }); - } - } - - async resolveAlert(alertId) { - try { - await api.alerts.resolve(alertId, { resolved_by: 'admin', resolution: '手动解决' }); - await this.loadAlerts(); - store.dispatch('showToast', { - type: 'success', - message: '预警已解决' - }); - } catch (error) { - console.error('Resolve alert error:', error); - store.dispatch('showToast', { - type: 'error', - message: '解决预警失败' - }); - } - } - - resetFilters() { - this.filters = { - level: '', - status: '', - search: '', - page: 1, - per_page: 10 - }; - - // 重置筛选器UI - document.getElementById('filter-level').value = ''; - document.getElementById('filter-status').value = ''; - document.getElementById('filter-search').value = ''; - - // 重新加载数据 - this.loadAlerts(); - } - - async loadAlerts() { - try { - const response = await api.alerts.list(this.filters); - this.updateAlertsList(response); - } catch (error) { - console.error('Load alerts error:', error); - } - } - - showError(error) { - this.container.innerHTML = ` - - `; - } -} - -// 暴露到全局供内联事件使用 -window.alertsPage = null; \ No newline at end of file diff --git a/src/web/static/js/pages/chat.js b/src/web/static/js/pages/chat.js deleted file mode 100644 index dfca450..0000000 --- a/src/web/static/js/pages/chat.js +++ /dev/null @@ -1,33 +0,0 @@ -/** - * 聊天页面组件 - */ - -export default class Chat { - constructor(container, route) { - this.container = container; - this.route = route; - this.init(); - } - - async init() { - this.render(); - } - - render() { - this.container.innerHTML = ` - -
    -
    -
    - -

    聊天页面

    -

    该功能正在开发中...

    -
    -
    -
    - `; - } -} \ No newline at end of file diff --git a/src/web/static/js/pages/dashboard.js b/src/web/static/js/pages/dashboard.js deleted file mode 100644 index 5bfba22..0000000 --- a/src/web/static/js/pages/dashboard.js +++ /dev/null @@ -1,454 +0,0 @@ -/** - * 仪表板页面组件 - */ - -import { api } from '../core/api.js'; -import { formatDate, formatRelativeTime } from '../core/utils.js'; -import store from '../core/store.js'; - -export default class Dashboard { - constructor(container, route) { - this.container = container; - this.route = route; - this.charts = {}; - this.init(); - } - - async init() { - try { - this.render(); - await this.loadData(); - this.bindEvents(); - } catch (error) { - console.error('Dashboard init error:', error); - this.showError(error); - } - } - - render() { - this.container.innerHTML = ` -
    - - -
    - - -
    -
    -
    -
    -
    0
    -
    今日已解决工单
    -
    -
    -
    -
    -
    -
    -
    0
    -
    待处理工单
    -
    -
    -
    -
    -
    -
    -
    0
    -
    活跃预警
    -
    -
    -
    -
    -
    -
    -
    0%
    -
    满意度
    -
    -
    -
    -
    - - -
    -
    -
    -
    -
    工单趋势
    - -
    -
    -
    - -
    -
    -
    -
    -
    -
    -
    -
    工单分类分布
    -
    -
    -
    - -
    -
    -
    -
    -
    - - -
    -
    -
    -
    -
    最新工单
    - 查看全部 -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    最新预警
    - 查看全部 -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    - `; - } - - async loadData() { - try { - // 加载仪表板数据 - const [analytics, recentWorkorders, recentAlerts, health] = await Promise.all([ - api.monitor.analytics({ days: 30 }), - api.workorders.list({ page: 1, per_page: 5, sort: 'created_at', order: 'desc' }), - api.alerts.list({ page: 1, per_page: 5, sort: 'created_at', order: 'desc' }), - api.system.health() - ]); - - // 更新统计数据 - this.updateStats(analytics, health); - - // 更新图表 - this.updateCharts(analytics); - - // 更新最新工单列表 - this.updateRecentWorkorders(recentWorkorders.workorders || []); - - // 更新最新预警列表 - this.updateRecentAlerts(recentAlerts.alerts || []); - - } catch (error) { - console.error('Load dashboard data error:', error); - this.showError(error); - } - } - - updateStats(analytics, health) { - // 更新统计卡片 - const stats = analytics.data || {}; - - document.getElementById('resolved-orders').textContent = stats.resolved_orders || 0; - document.getElementById('pending-orders').textContent = health.open_workorders || 0; - document.getElementById('active-alerts').textContent = Object.values(health.active_alerts_by_level || {}).reduce((a, b) => a + b, 0); - - const satisfaction = stats.satisfaction_avg ? (stats.satisfaction_avg * 100).toFixed(1) : 0; - document.getElementById('satisfaction-rate').textContent = `${satisfaction}%`; - } - - updateCharts(analytics) { - const data = analytics.data || {}; - - // 更新工单趋势图 - this.updateTrendChart(data.trend || []); - - // 更新分类分布图 - this.updateCategoryChart(data.categories || {}); - } - - updateTrendChart(trendData) { - const ctx = document.getElementById('orders-trend-chart'); - if (!ctx) return; - - // 销毁旧图表 - if (this.charts.trend) { - this.charts.trend.destroy(); - } - - this.charts.trend = new Chart(ctx, { - type: 'line', - data: { - labels: trendData.map(item => item.date), - datasets: [{ - label: '工单数', - data: trendData.map(item => item.count), - borderColor: '#007bff', - backgroundColor: 'rgba(0, 123, 255, 0.1)', - tension: 0.4, - fill: true - }] - }, - options: { - responsive: true, - maintainAspectRatio: false, - plugins: { - legend: { - display: false - } - }, - scales: { - y: { - beginAtZero: true - } - } - } - }); - } - - updateCategoryChart(categoryData) { - const ctx = document.getElementById('category-chart'); - if (!ctx) return; - - // 销毁旧图表 - if (this.charts.category) { - this.charts.category.destroy(); - } - - const labels = Object.keys(categoryData); - const data = Object.values(categoryData); - - this.charts.category = new Chart(ctx, { - type: 'doughnut', - data: { - labels: labels, - datasets: [{ - data: data, - backgroundColor: [ - '#007bff', - '#28a745', - '#ffc107', - '#dc3545', - '#6c757d' - ] - }] - }, - options: { - responsive: true, - maintainAspectRatio: false, - plugins: { - legend: { - position: 'bottom' - } - } - } - }); - } - - updateRecentWorkorders(workorders) { - const container = document.getElementById('recent-workorders'); - if (!container) return; - - if (!workorders.length) { - container.innerHTML = ` -
    - -

    暂无工单

    -
    - `; - return; - } - - container.innerHTML = workorders.map(workorder => ` -
    -
    -
    -
    - - ${workorder.title} - -
    - - ${workorder.category || '未分类'} • - ${formatRelativeTime(workorder.created_at)} - -
    - - ${this.getStatusText(workorder.status)} - -
    -
    - `).join(''); - } - - updateRecentAlerts(alerts) { - const container = document.getElementById('recent-alerts'); - if (!container) return; - - if (!alerts.length) { - container.innerHTML = ` -
    - -

    暂无预警

    -
    - `; - return; - } - - container.innerHTML = alerts.map(alert => ` -
    -
    -
    -
    ${alert.message}
    - - ${alert.rule_name} • - ${formatRelativeTime(alert.created_at)} - -
    - - ${alert.level} - -
    -
    - `).join(''); - } - - getStatusColor(status) { - const colors = { - 'open': 'danger', - 'in_progress': 'warning', - 'resolved': 'success', - 'closed': 'secondary' - }; - return colors[status] || 'secondary'; - } - - getStatusText(status) { - const texts = { - 'open': '待处理', - 'in_progress': '处理中', - 'resolved': '已解决', - 'closed': '已关闭' - }; - return texts[status] || status; - } - - getLevelColor(level) { - const colors = { - 'critical': 'danger', - 'error': 'danger', - 'warning': 'warning', - 'info': 'info' - }; - return colors[level] || 'secondary'; - } - - bindEvents() { - // 刷新按钮 - const refreshBtn = document.getElementById('refresh-dashboard'); - if (refreshBtn) { - refreshBtn.addEventListener('click', () => { - this.refresh(); - }); - } - - // 趋势周期选择 - const periodSelect = document.getElementById('trend-period'); - if (periodSelect) { - periodSelect.addEventListener('change', (e) => { - this.changeTrendPeriod(e.target.value); - }); - } - - // 定时刷新(每5分钟) - this.refreshTimer = setInterval(() => { - this.refresh(); - }, 5 * 60 * 1000); - } - - async refresh() { - const refreshBtn = document.getElementById('refresh-dashboard'); - if (refreshBtn) { - refreshBtn.disabled = true; - const icon = refreshBtn.querySelector('i'); - icon.classList.add('fa-spin'); - } - - try { - await this.loadData(); - } catch (error) { - console.error('Refresh error:', error); - } finally { - if (refreshBtn) { - refreshBtn.disabled = false; - const icon = refreshBtn.querySelector('i'); - icon.classList.remove('fa-spin'); - } - } - } - - async changeTrendPeriod(days) { - try { - const analytics = await api.monitor.analytics({ days: parseInt(days) }); - this.updateTrendChart(analytics.data?.trend || []); - } catch (error) { - console.error('Change trend period error:', error); - } - } - - showError(error) { - this.container.innerHTML = ` - - `; - } - - destroy() { - // 清理图表 - Object.values(this.charts).forEach(chart => { - if (chart) chart.destroy(); - }); - - // 清理定时器 - if (this.refreshTimer) { - clearInterval(this.refreshTimer); - } - } -} \ No newline at end of file diff --git a/src/web/static/js/pages/feishu.js b/src/web/static/js/pages/feishu.js deleted file mode 100644 index 5c82d4e..0000000 --- a/src/web/static/js/pages/feishu.js +++ /dev/null @@ -1,451 +0,0 @@ -/** - * 飞书同步页面组件 - */ - -export default class Feishu { - constructor(container, route) { - this.container = container; - this.route = route; - this.init(); - } - - async init() { - try { - this.render(); - this.bindEvents(); - this.loadSyncStatus(); - } catch (error) { - console.error('Feishu init error:', error); - this.showError(error); - } - } - - render() { - this.container.innerHTML = ` - - -
    - -
    -
    -
    -
    - 同步配置 -
    -
    -
    -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    -
    -
    -
    - - -
    -
    -
    -
    - 同步状态 -
    -
    -
    -
    - 加载中... -
    -
    -
    - - -
    -
    -
    - 同步操作 -
    -
    -
    -
    - - -
    -
    -
    -
    -
    - - -
    -
    -
    -
    -
    - 字段映射 -
    -
    -
    -
    -
    - -
    -
    -
    - -
    -
    -
    -
    -
    -
    -
    - - -
    -
    -
    -
    -
    - 数据预览 -
    -
    -
    -
    - 点击"预览飞书数据"查看数据 -
    -
    -
    -
    -
    - `; - } - - bindEvents() { - // 配置表单提交 - const configForm = document.getElementById('feishu-config-form'); - configForm.addEventListener('submit', (e) => { - e.preventDefault(); - this.saveConfig(); - }); - - // 测试连接 - document.getElementById('test-connection-btn').addEventListener('click', () => { - this.testConnection(); - }); - - // 从飞书同步 - document.getElementById('sync-from-feishu-btn').addEventListener('click', () => { - this.syncFromFeishu(); - }); - - // 预览数据 - document.getElementById('preview-data-btn').addEventListener('click', () => { - this.previewData(); - }); - - // 发现字段 - document.getElementById('discover-fields-btn').addEventListener('click', () => { - this.discoverFields(); - }); - - // 映射状态 - document.getElementById('mapping-status-btn').addEventListener('click', () => { - this.getMappingStatus(); - }); - - // 加载配置 - this.loadConfig(); - } - - async loadConfig() { - try { - const response = await fetch('/api/feishu-sync/config'); - const data = await response.json(); - - if (data.success) { - document.getElementById('app_id').value = data.config.app_id || ''; - document.getElementById('app_secret').value = data.config.app_secret || ''; - document.getElementById('app_token').value = data.config.app_token || ''; - document.getElementById('table_id').value = data.config.table_id || ''; - } - } catch (error) { - console.error('加载配置失败:', error); - } - } - - async saveConfig() { - const formData = new FormData(document.getElementById('feishu-config-form')); - const config = { - app_id: formData.get('app_id'), - app_secret: formData.get('app_secret'), - app_token: formData.get('app_token'), - table_id: formData.get('table_id') - }; - - try { - const response = await fetch('/api/feishu-sync/config', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(config) - }); - - const data = await response.json(); - - if (data.success) { - if (window.showToast) { - window.showToast('配置保存成功', 'success'); - } - } else { - if (window.showToast) { - window.showToast(data.error || '配置保存失败', 'error'); - } - } - } catch (error) { - console.error('保存配置失败:', error); - if (window.showToast) { - window.showToast('网络错误', 'error'); - } - } - } - - async testConnection() { - try { - const response = await fetch('/api/feishu-sync/test-connection'); - const data = await response.json(); - - if (data.success) { - if (window.showToast) { - window.showToast('连接测试成功', 'success'); - } - // 显示字段信息 - if (data.fields) { - console.log('飞书表格字段:', data.fields); - } - } else { - if (window.showToast) { - window.showToast(data.message || '连接测试失败', 'error'); - } - } - } catch (error) { - console.error('测试连接失败:', error); - if (window.showToast) { - window.showToast('网络错误', 'error'); - } - } - } - - async loadSyncStatus() { - try { - const response = await fetch('/api/feishu-sync/status'); - const data = await response.json(); - - const statusDiv = document.getElementById('sync-status'); - if (data.success) { - const status = data.status; - statusDiv.innerHTML = ` -
    - 最后同步: ${status.last_sync || '从未同步'} -
    -
    - 同步状态: ${status.is_syncing ? '同步中' : '空闲'} -
    -
    - 总记录数: ${status.total_records || 0} -
    - `; - } else { - statusDiv.innerHTML = '获取状态失败'; - } - } catch (error) { - console.error('获取同步状态失败:', error); - document.getElementById('sync-status').innerHTML = '获取状态失败'; - } - } - - async syncFromFeishu() { - try { - const response = await fetch('/api/feishu-sync/sync-from-feishu', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ - generate_ai_suggestions: true, - limit: 50 - }) - }); - - const data = await response.json(); - - if (data.success) { - if (window.showToast) { - window.showToast(data.message, 'success'); - } - this.loadSyncStatus(); // 重新加载状态 - } else { - if (window.showToast) { - window.showToast(data.error || '同步失败', 'error'); - } - } - } catch (error) { - console.error('同步失败:', error); - if (window.showToast) { - window.showToast('网络错误', 'error'); - } - } - } - - async previewData() { - try { - const response = await fetch('/api/feishu-sync/preview-feishu-data'); - const data = await response.json(); - - const previewDiv = document.getElementById('data-preview'); - if (data.success && data.preview_data.length > 0) { - let html = `
    `; - html += ''; - - data.preview_data.forEach(item => { - html += ` - - - `; - }); - - html += '
    记录ID字段数据
    ${item.record_id}
    ${JSON.stringify(item.fields, null, 2)}
    '; - previewDiv.innerHTML = html; - } else { - previewDiv.innerHTML = '暂无数据'; - } - } catch (error) { - console.error('预览数据失败:', error); - document.getElementById('data-preview').innerHTML = '预览失败'; - } - } - - async discoverFields() { - try { - const response = await fetch('/api/feishu-sync/field-mapping/discover', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ limit: 5 }) - }); - - const data = await response.json(); - - const resultDiv = document.getElementById('field-discovery-result'); - if (data.success) { - const report = data.discovery_report; - let html = '
    字段发现报告
    '; - - if (Object.keys(report).length > 0) { - html += ''; - } else { - html += '

    未发现可映射的字段

    '; - } - - resultDiv.innerHTML = html; - } else { - resultDiv.innerHTML = '字段发现失败'; - } - } catch (error) { - console.error('字段发现失败:', error); - document.getElementById('field-discovery-result').innerHTML = '字段发现失败'; - } - } - - async getMappingStatus() { - try { - const response = await fetch('/api/feishu-sync/field-mapping/status'); - const data = await response.json(); - - const resultDiv = document.getElementById('mapping-status-result'); - if (data.success) { - const status = data.status; - let html = '
    映射状态
    '; - - if (status.mappings && status.mappings.length > 0) { - html += ''; - } else { - html += '

    暂无字段映射

    '; - } - - resultDiv.innerHTML = html; - } else { - resultDiv.innerHTML = '获取映射状态失败'; - } - } catch (error) { - console.error('获取映射状态失败:', error); - document.getElementById('mapping-status-result').innerHTML = '获取映射状态失败'; - } - } - - showError(error) { - this.container.innerHTML = ` -
    -
    -
    -
    - -

    页面加载失败

    -

    ${error.message || '未知错误'}

    - -
    -
    -
    -
    - `; - } -} diff --git a/src/web/static/js/pages/knowledge.js b/src/web/static/js/pages/knowledge.js deleted file mode 100644 index 54d15f1..0000000 --- a/src/web/static/js/pages/knowledge.js +++ /dev/null @@ -1,673 +0,0 @@ -/** - * 知识库页面组件 - */ - -export default class Knowledge { - constructor(container, route) { - this.container = container; - this.route = route; - this.currentPage = 1; // 初始化当前页码 - this.init(); - } - - async init() { - this.render(); - this.bindEvents(); - await Promise.all([ - this.loadKnowledgeList(), - this.loadStats() - ]); - } - - async loadStats() { - try { - const response = await fetch('/api/knowledge/stats'); - if (response.ok) { - const stats = await response.json(); - - // 更新统计数据显示 - const totalEl = this.container.querySelector('#stat-total'); - const activeEl = this.container.querySelector('#stat-active'); - const catsEl = this.container.querySelector('#stat-categories'); - const confEl = this.container.querySelector('#stat-confidence'); - - if (totalEl) totalEl.textContent = stats.total_entries || 0; - if (activeEl) activeEl.textContent = stats.active_entries || 0; // 后端现在返回的是已验证数量 - if (catsEl) catsEl.textContent = Object.keys(stats.category_distribution || {}).length; - if (confEl) confEl.textContent = ((stats.average_confidence || 0) * 100).toFixed(0) + '%'; - } - } catch (error) { - console.error('加载统计信息失败:', error); - } - } - - render() { - this.container.innerHTML = ` - - - -
    -
    -
    -
    -
    -
    - - - -
    -
    -
    0
    -
    总条目
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    - - - -
    -
    -
    0
    -
    已验证
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    - - - -
    -
    -
    0
    -
    分类数量
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    - - - -
    -
    -
    0%
    -
    平均置信度
    -
    -
    -
    -
    -
    -
    - -
    -
    -

    知识条目列表

    -
    -
    - - - -
    -
    - - -
    -
    -
    -
    -
    - - - - - - - - - - - - - - - - - -
    - - #问题/主题内容预览分类置信度操作
    正在加载数据...
    -
    -
    - -
    -
    -
    - `; - } - - bindEvents() { - // 导入文件按钮 - const fileInput = this.container.querySelector('#file-input'); - const importBtn = this.container.querySelector('#btn-import-file'); - - importBtn.addEventListener('click', () => { - fileInput.click(); - }); - - fileInput.addEventListener('change', async (e) => { - if (e.target.files.length > 0) { - await this.uploadFile(e.target.files[0]); - // 清空选择,允许再次选择同名文件 - fileInput.value = ''; - } - }); - - // 搜索功能 - const searchInput = this.container.querySelector('#search-input'); - const searchBtn = this.container.querySelector('#btn-search'); - - const performSearch = () => { - const query = searchInput.value.trim(); - this.loadKnowledgeList(1, query); - }; - - searchBtn.addEventListener('click', performSearch); - searchInput.addEventListener('keypress', (e) => { - if (e.key === 'Enter') { - performSearch(); - } - }); - - // 批量操作按钮 - const batchVerifyBtn = this.container.querySelector('#btn-batch-verify'); - if (batchVerifyBtn) { - batchVerifyBtn.addEventListener('click', () => this.batchAction('verify')); - } - - const batchUnverifyBtn = this.container.querySelector('#btn-batch-unverify'); - if (batchUnverifyBtn) { - batchUnverifyBtn.addEventListener('click', () => this.batchAction('unverify')); - } - - const batchDeleteBtn = this.container.querySelector('#btn-batch-delete'); - if (batchDeleteBtn) { - batchDeleteBtn.addEventListener('click', () => this.batchAction('delete')); - } - - // 全选复选框 - const checkAll = this.container.querySelector('#check-all'); - if (checkAll) { - checkAll.addEventListener('change', (e) => { - const checks = this.container.querySelectorAll('.item-check'); - checks.forEach(check => check.checked = e.target.checked); - this.updateBatchButtons(); - }); - } - } - - bindCheckboxEvents() { - const checks = this.container.querySelectorAll('.item-check'); - checks.forEach(check => { - check.addEventListener('change', () => { - this.updateBatchButtons(); - // 如果有一个未选中,取消全选选中状态 - if (!check.checked) { - const checkAll = this.container.querySelector('#check-all'); - if (checkAll) checkAll.checked = false; - } - }); - }); - } - - updateBatchButtons() { - const checkedCount = this.container.querySelectorAll('.item-check:checked').length; - const actionsGroup = this.container.querySelector('#batch-actions'); - - if (actionsGroup) { - if (checkedCount > 0) { - actionsGroup.classList.remove('d-none'); - // 更新删除按钮文本 - const deleteBtn = this.container.querySelector('#btn-batch-delete'); - if (deleteBtn) deleteBtn.innerHTML = `删除 (${checkedCount})`; - } else { - actionsGroup.classList.add('d-none'); - } - } - } - - async batchDeleteKnowledge() { - const checks = this.container.querySelectorAll('.item-check:checked'); - const ids = Array.from(checks).map(check => parseInt(check.dataset.id)); - - console.log('Deleting IDs:', ids); - - if (ids.length === 0) { - alert('请先选择要删除的知识条目'); - return; - } - - if (!confirm(`确定要删除选中的 ${ids.length} 条知识吗?`)) { - return; - } - - try { - const response = await fetch('/api/knowledge/batch_delete', { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ ids: ids }) - }); - - const result = await response.json(); - - if (response.ok && result.success) { - alert(result.message || '删除成功'); - // 重置全选状态 - const checkAll = this.container.querySelector('#check-all'); - if (checkAll) checkAll.checked = false; - this.updateBatchDeleteButton(); - // 刷新列表和统计(保持当前页) - await Promise.all([ - this.loadKnowledgeList(this.currentPage), - this.loadStats() - ]); - } else { - alert(`删除失败: ${result.message || '未知错误'}`); - } - } catch (error) { - console.error('批量删除出错:', error); - alert('批量删除出错,请查看控制台'); - } - } - - async loadKnowledgeList(page = null, query = '') { - // 如果未指定页码,使用当前页码,默认为 1 - const targetPage = page || this.currentPage || 1; - this.currentPage = targetPage; - - const tbody = this.container.querySelector('#knowledge-list-body'); - - // 柔性加载:不立即清空,而是降低透明度并显示加载态 - // 这可以防止表格高度塌陷导致的视觉跳动 - tbody.style.opacity = '0.5'; - tbody.style.transition = 'opacity 0.2s'; - - // 如果表格是空的(第一次加载),则显示加载占位符 - if (!tbody.hasChildNodes() || tbody.children.length === 0 || tbody.querySelector('.text-center')) { - tbody.innerHTML = '
    加载中...
    '; - tbody.style.opacity = '1'; - } - - try { - let url = `/api/knowledge?page=${targetPage}&per_page=10`; - if (query) { - url = `/api/knowledge/search?q=${encodeURIComponent(query)}`; - } - - const response = await fetch(url); - const result = await response.json(); - - tbody.innerHTML = ''; - tbody.style.opacity = '1'; // 恢复不透明 - - // 处理搜索结果(通常是数组)和分页结果(包含 items)的差异 - let items = []; - if (Array.isArray(result)) { - items = result; - } else if (result.items) { - items = result.items; - } - - if (items.length === 0) { - tbody.innerHTML = '暂无知识条目'; - return; - } - - items.forEach((item, index) => { - // ... (渲染逻辑保持不变) - const tr = document.createElement('tr'); - - // 验证状态图标 - let statusBadge = ''; - if (item.is_verified) { - statusBadge = ''; - } - - // 验证操作按钮 - let verifyBtn = ''; - if (item.is_verified) { - verifyBtn = ` - - `; - } else { - verifyBtn = ` - - `; - } - - tr.innerHTML = ` - - ${(targetPage - 1) * 10 + index + 1} - -
    - ${item.question} - ${statusBadge} -
    - -
    ${item.answer}
    - ${item.category || '未分类'} - ${(item.confidence_score * 100).toFixed(0)}% - - ${verifyBtn} - - - `; - - // 绑定验证/取消验证事件 - const verifyActionBtn = tr.querySelector('.btn-verify, .btn-unverify'); - if (verifyActionBtn) { - verifyActionBtn.addEventListener('click', async (e) => { - e.preventDefault(); - e.stopPropagation(); - const isVerify = verifyActionBtn.classList.contains('btn-verify'); - await this.toggleVerify(item.id, isVerify, tr); - }); - } - - // 绑定删除事件 - const deleteBtn = tr.querySelector('.btn-delete'); - deleteBtn.addEventListener('click', (e) => { - e.preventDefault(); - this.deleteKnowledge(item.id); - }); - - tbody.appendChild(tr); - }); - - // 重新绑定复选框事件 - this.bindCheckboxEvents(); - - // 渲染分页 - if (result.pages && result.pages > 1) { - this.renderPagination(result); - } else { - this.container.querySelector('#pagination-container').innerHTML = ''; - } - - } catch (error) { - console.error('加载知识列表失败:', error); - tbody.innerHTML = `加载失败: ${error.message}`; - tbody.style.opacity = '1'; - } - } - - async uploadFile(file) { - const formData = new FormData(); - formData.append('file', file); - - // 显示上传中提示 - const importBtn = this.container.querySelector('#btn-import-file'); - const originalText = importBtn.innerHTML; - importBtn.disabled = true; - importBtn.innerHTML = ' 上传中...'; - - try { - const response = await fetch('/api/knowledge/upload', { - method: 'POST', - body: formData - }); - const result = await response.json(); - - if (response.ok && result.success) { - alert(`文件上传成功!共提取 ${result.knowledge_count} 条知识。`); - // 刷新列表和统计 - await Promise.all([ - this.loadKnowledgeList(), - this.loadStats() - ]); - } else { - alert(`上传失败: ${result.error || result.message || '未知错误'}`); - } - } catch (error) { - console.error('上传文件出错:', error); - alert('上传文件出错,请查看控制台'); - } finally { - importBtn.disabled = false; - importBtn.innerHTML = originalText; - } - } - - async toggleVerify(id, isVerify, trElement = null) { - const action = isVerify ? 'verify' : 'unverify'; - const url = `/api/knowledge/${action}/${id}`; - - try { - // 如果有 trElement,先显示加载状态 - let originalBtnHtml = ''; - let actionBtn = null; - if (trElement) { - actionBtn = trElement.querySelector('.btn-verify, .btn-unverify'); - if (actionBtn) { - originalBtnHtml = actionBtn.innerHTML; - actionBtn.disabled = true; - actionBtn.innerHTML = ''; - } - } - - const response = await fetch(url, { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - } - }); - const result = await response.json(); - - if (response.ok && result.success) { - // 如果提供了 DOM 元素,直接更新 DOM,避免刷新整个列表导致跳动 - if (trElement) { - this.updateRowStatus(trElement, id, isVerify); - // 后台静默刷新统计数据 - this.loadStats(); - } else { - // 仅刷新列表和统计,不跳转页面 - await Promise.all([ - this.loadKnowledgeList(this.currentPage), - this.loadStats() - ]); - } - } else { - alert(`${isVerify ? '验证' : '取消验证'}失败: ${result.message}`); - // 恢复按钮状态 - if (actionBtn) { - actionBtn.disabled = false; - actionBtn.innerHTML = originalBtnHtml; - } - } - } catch (error) { - console.error('操作出错:', error); - // 恢复按钮状态 - if (trElement) { - const actionBtn = trElement.querySelector('.btn-verify, .btn-unverify'); - if (actionBtn) { - actionBtn.disabled = false; - // 简单恢复,无法精确还原之前的图标 - actionBtn.innerHTML = isVerify ? '' : ''; - } - } - } - } - - updateRowStatus(tr, id, isVerified) { - // 1. 更新问题列的状态图标 - const questionCell = tr.cells[2]; // 第3列是问题 - const questionDiv = questionCell.querySelector('div'); - - // 移除旧的徽章 - const oldBadge = questionDiv.querySelector('.text-success'); - if (oldBadge) oldBadge.remove(); - - // 如果是验证通过,添加徽章 - if (isVerified) { - const statusBadge = document.createElement('span'); - statusBadge.className = 'text-success ms-2'; - statusBadge.title = '已验证'; - statusBadge.innerHTML = ''; - questionDiv.appendChild(statusBadge); - } - - // 2. 更新操作按钮 - const actionCell = tr.cells[6]; // 第7列是操作 - const actionBtn = actionCell.querySelector('.btn-verify, .btn-unverify'); - - if (actionBtn) { - // 创建新按钮 - const newBtn = document.createElement('button'); - newBtn.type = 'button'; - newBtn.className = `btn btn-sm btn-icon btn-outline-${isVerified ? 'warning' : 'success'} ${isVerified ? 'btn-unverify' : 'btn-verify'}`; - newBtn.dataset.id = id; - newBtn.title = isVerified ? '取消验证' : '验证通过'; - newBtn.innerHTML = ``; - - // 重新绑定事件 - newBtn.addEventListener('click', async (e) => { - e.preventDefault(); - e.stopPropagation(); - await this.toggleVerify(id, !isVerified, tr); - }); - - // 替换旧按钮 - actionBtn.replaceWith(newBtn); - } - } - - async deleteKnowledge(id) { - if (!confirm('确定要删除这条知识吗?')) { - return; - } - - try { - const response = await fetch(`/api/knowledge/delete/${id}`, { - method: 'DELETE' - }); - const result = await response.json(); - - if (response.ok && result.success) { - // 刷新列表和统计(保持当前页) - await Promise.all([ - this.loadKnowledgeList(this.currentPage), - this.loadStats() - ]); - } else { - alert(`删除失败: ${result.message || '未知错误'}`); - } - } catch (error) { - console.error('删除出错:', error); - alert('删除出错,请查看控制台'); - } - } - - renderPagination(pagination) { - const { page, pages } = pagination; - const container = this.container.querySelector('#pagination-container'); - - let html = ''; - container.innerHTML = html; - - // 绑定点击事件 - container.querySelectorAll('.page-link').forEach(link => { - link.addEventListener('click', (e) => { - e.preventDefault(); - const newPage = parseInt(e.target.dataset.page); - if (newPage && newPage !== page && newPage >= 1 && newPage <= pages) { - this.loadKnowledgeList(newPage); - } - }); - }); - } -} \ No newline at end of file diff --git a/src/web/static/js/pages/login.js b/src/web/static/js/pages/login.js deleted file mode 100644 index 76f0334..0000000 --- a/src/web/static/js/pages/login.js +++ /dev/null @@ -1,216 +0,0 @@ -/** - * 登录页面组件 - */ - -export default class Login { - constructor(container, route) { - this.container = container; - this.route = route; - this.init(); - } - - async init() { - try { - this.render(); - this.bindEvents(); - } catch (error) { - console.error('Login init error:', error); - this.showError(error); - } - } - - render() { - this.container.innerHTML = ` -
    - - -
    -
    -
    -
    -
    -
    - -
    - -
    - - - - -
    -
    - - -
    - -
    - - - - -
    -
    - - -
    - - -
    - - -
    - - -
    - -
    -
    -
    -
    - - -
    -

    - 还没有账号?立即注册 -

    -

    - 忘记密码? -

    -
    -
    -
    -
    - `; - } - - bindEvents() { - const form = document.getElementById('login-form'); - const loginBtn = document.getElementById('login-btn'); - const errorDiv = document.getElementById('login-error'); - - form.addEventListener('submit', async (e) => { - e.preventDefault(); - - // 获取表单数据 - const formData = new FormData(form); - const username = formData.get('username'); - const password = formData.get('password'); - const remember = formData.get('remember'); - - // 显示加载状态 - loginBtn.disabled = true; - loginBtn.innerHTML = '登录中...'; - errorDiv.classList.add('d-none'); - - try { - // 调用登录API - const response = await fetch('/api/login', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - username, - password, - remember - }), - credentials: 'same-origin' - }); - - const data = await response.json(); - - if (response.ok && data.success) { - // 登录成功 - // 保存token到sessionStorage(会话级别) - sessionStorage.setItem('token', data.token); - - // 如果选择记住我,也保存到localStorage - if (remember) { - localStorage.setItem('user', JSON.stringify(data.user)); - localStorage.setItem('token', data.token); - localStorage.setItem('remember', 'true'); - } - - // 更新应用状态 - if (window.store) { - window.store.commit('SET_USER', data.user); - window.store.commit('SET_LOGIN', true); - window.store.commit('SET_TOKEN', data.token); - } - - // 显示成功提示 - if (window.showToast) { - window.showToast('登录成功', 'success'); - } - - // 跳转到仪表板 - if (window.router) { - window.router.push('/'); - } else { - // 如果路由器还没初始化,直接跳转 - window.location.href = '/'; - } - } else { - // 登录失败 - errorDiv.textContent = data.message || '用户名或密码错误'; - errorDiv.classList.remove('d-none'); - } - } catch (error) { - console.error('Login error:', error); - errorDiv.textContent = '网络错误,请稍后重试'; - errorDiv.classList.remove('d-none'); - } finally { - // 恢复按钮状态 - loginBtn.disabled = false; - loginBtn.innerHTML = '登录'; - } - }); - - // 检查本地存储中的登录状态 - const rememberedUser = localStorage.getItem('remember'); - if (rememberedUser === 'true') { - const user = localStorage.getItem('user'); - if (user) { - try { - const userData = JSON.parse(user); - document.getElementById('username').value = userData.username || ''; - document.getElementById('remember').checked = true; - } catch (e) { - console.error('Error parsing remembered user:', e); - } - } - } - } - - showError(error) { - this.container.innerHTML = ` -
    -
    -
    -
    - -

    页面加载失败

    -

    ${error.message || '未知错误'}

    - -
    -
    -
    -
    - `; - } -} \ No newline at end of file diff --git a/src/web/static/js/pages/monitoring.js b/src/web/static/js/pages/monitoring.js deleted file mode 100644 index 20ecb19..0000000 --- a/src/web/static/js/pages/monitoring.js +++ /dev/null @@ -1,33 +0,0 @@ -/** - * 系统监控页面组件 - */ - -export default class Monitoring { - constructor(container, route) { - this.container = container; - this.route = route; - this.init(); - } - - async init() { - this.render(); - } - - render() { - this.container.innerHTML = ` - -
    -
    -
    - -

    系统监控页面

    -

    该功能正在开发中...

    -
    -
    -
    - `; - } -} \ No newline at end of file diff --git a/src/web/static/js/pages/notfound.js b/src/web/static/js/pages/notfound.js deleted file mode 100644 index 8d28478..0000000 --- a/src/web/static/js/pages/notfound.js +++ /dev/null @@ -1,142 +0,0 @@ -/** - * 404页面组件 - */ - -export default class NotFound { - constructor(container, route) { - this.container = container; - this.route = route; - this.init(); - } - - async init() { - try { - this.render(); - this.bindEvents(); - } catch (error) { - console.error('NotFound init error:', error); - this.showError(error); - } - } - - render() { - this.container.innerHTML = ` - - -
    -
    -
    -
    -
    - -
    -

    页面不存在

    -

    - 看起来您访问的页面已经被移除、名称已更改或暂时不可用。 -

    -
    - - -
    -
    -
    -
    -
    - - - - `; - } - - bindEvents() { - // 绑定导航链接事件 - const links = this.container.querySelectorAll('a[href^="#"]'); - links.forEach(link => { - link.addEventListener('click', (e) => { - e.preventDefault(); - const href = link.getAttribute('href'); - if (href === '#home' && window.router) { - window.router.push('/'); - } - }); - }); - } - - showError(error) { - this.container.innerHTML = ` -
    -
    -
    -
    - -

    页面加载失败

    -

    ${error.message || '未知错误'}

    - -
    -
    -
    -
    - `; - } -} \ No newline at end of file diff --git a/src/web/static/js/pages/settings.js b/src/web/static/js/pages/settings.js deleted file mode 100644 index b25abeb..0000000 --- a/src/web/static/js/pages/settings.js +++ /dev/null @@ -1,428 +0,0 @@ -/** - * 系统设置页面组件 - */ - -export default class Settings { - constructor(container, route) { - this.container = container; - this.route = route; - this.init(); - } - - async init() { - this.render(); - this.bindEvents(); - this.loadSettings(); - } - - render() { - this.container.innerHTML = ` - - -
    - -
    -
    -
    -
    - 系统信息 -
    -
    -
    -
    - 加载中... -
    -
    -
    -
    - - -
    -
    -
    -
    - 数据库状态 -
    -
    -
    -
    - 加载中... -
    -
    -
    -
    -
    - - -
    -
    -
    -
    -
    - 系统配置 -
    -
    -
    -
    - -
    -
    预警规则设置
    -
    -
    -
    - - -
    -
    -
    -
    - - -
    -
    -
    -
    - - -
    -
    LLM配置
    -
    -
    -
    - - -
    -
    -
    -
    - - -
    -
    -
    -
    - - -
    -
    - - -
    -
    其他设置
    -
    -
    -
    - - -
    -
    - - -
    -
    -
    -
    - - -
    -
    - - -
    -
    -
    -
    - -
    - -
    -
    -
    -
    -
    -
    - - -
    -
    -
    -
    -
    - 系统操作 -
    -
    -
    -
    -
    -
    数据管理
    -
    - - -
    -
    -
    -
    系统维护
    -
    - - -
    -
    -
    -
    -
    -
    -
    - `; - } - - bindEvents() { - // 设置表单提交 - const settingsForm = document.getElementById('system-settings-form'); - settingsForm.addEventListener('submit', (e) => { - e.preventDefault(); - this.saveSettings(); - }); - - // 系统操作按钮 - document.getElementById('backup-data-btn').addEventListener('click', () => { - this.backupData(); - }); - - document.getElementById('clear-cache-btn').addEventListener('click', () => { - this.clearCache(); - }); - - document.getElementById('check-health-btn').addEventListener('click', () => { - this.checkHealth(); - }); - - document.getElementById('restart-services-btn').addEventListener('click', () => { - this.restartServices(); - }); - } - - async loadSettings() { - try { - // 加载系统信息 - const healthResponse = await fetch('/api/health'); - const healthData = await healthResponse.json(); - - const systemInfoDiv = document.getElementById('system-info'); - if (healthData) { - systemInfoDiv.innerHTML = ` -
    版本: ${healthData.version || '1.0.0'}
    -
    运行时间: ${this.formatUptime(healthData.uptime || 0)}
    -
    健康评分: ${healthData.health_score || 0}/100
    -
    活跃会话: ${healthData.active_sessions || 0}
    -
    处理中的工单: ${healthData.processing_workorders || 0}
    - `; - } - - // 加载数据库状态 - const dbStatusDiv = document.getElementById('db-status'); - if (healthData.db_status) { - const dbStatus = healthData.db_status; - dbStatusDiv.innerHTML = ` -
    状态: ${dbStatus.connection_ok ? '正常' : '异常'}
    -
    类型: ${dbStatus.type || '未知'}
    -
    版本: ${dbStatus.version || '未知'}
    -
    连接数: ${dbStatus.active_connections || 0}
    - `; - } else { - dbStatusDiv.innerHTML = '
    无法获取数据库状态
    '; - } - - // 加载配置设置 (这里应该从后端API获取) - this.loadConfigSettings(); - - } catch (error) { - console.error('加载设置失败:', error); - document.getElementById('system-info').innerHTML = '
    加载失败
    '; - document.getElementById('db-status').innerHTML = '
    加载失败
    '; - } - } - - async loadConfigSettings() { - // 这里应该从后端API加载配置设置 - // 暂时设置一些默认值 - try { - document.getElementById('check-interval').value = '300'; - document.getElementById('cooldown').value = '3600'; - document.getElementById('llm-provider').value = 'qwen'; - document.getElementById('default-model').value = 'qwen-turbo'; - document.getElementById('enable-analytics').checked = true; - document.getElementById('enable-websocket').checked = true; - document.getElementById('enable-pwa').checked = true; - document.getElementById('enable-error-reporting').checked = false; - } catch (error) { - console.error('加载配置设置失败:', error); - } - } - - async saveSettings() { - const settings = { - alert_rules: { - check_interval: parseInt(document.getElementById('check-interval').value), - cooldown: parseInt(document.getElementById('cooldown').value) - }, - llm: { - provider: document.getElementById('llm-provider').value, - model: document.getElementById('default-model').value, - api_key: document.getElementById('api-key').value - }, - features: { - analytics: document.getElementById('enable-analytics').checked, - websocket: document.getElementById('enable-websocket').checked, - pwa: document.getElementById('enable-pwa').checked, - error_reporting: document.getElementById('enable-error-reporting').checked - } - }; - - try { - // 这里应该调用后端API保存设置 - console.log('保存设置:', settings); - - if (window.showToast) { - window.showToast('设置保存成功', 'success'); - } - } catch (error) { - console.error('保存设置失败:', error); - if (window.showToast) { - window.showToast('设置保存失败', 'error'); - } - } - } - - async backupData() { - try { - // 这里应该调用后端备份API - console.log('开始备份数据...'); - - if (window.showToast) { - window.showToast('数据备份功能开发中', 'info'); - } - } catch (error) { - console.error('备份数据失败:', error); - if (window.showToast) { - window.showToast('备份失败', 'error'); - } - } - } - - async clearCache() { - if (confirm('确定要清理所有缓存吗?')) { - try { - // 清理本地存储 - localStorage.clear(); - sessionStorage.clear(); - - // 清理Service Worker缓存 - if ('caches' in window) { - const cacheNames = await caches.keys(); - await Promise.all( - cacheNames.map(cacheName => caches.delete(cacheName)) - ); - } - - if (window.showToast) { - window.showToast('缓存清理完成', 'success'); - } - - // 刷新页面 - setTimeout(() => { - window.location.reload(); - }, 1000); - } catch (error) { - console.error('清理缓存失败:', error); - if (window.showToast) { - window.showToast('清理缓存失败', 'error'); - } - } - } - } - - async checkHealth() { - try { - const response = await fetch('/api/health'); - const data = await response.json(); - - if (data) { - const healthScore = data.health_score || 0; - const status = healthScore >= 80 ? 'success' : healthScore >= 60 ? 'warning' : 'error'; - const message = `系统健康评分: ${healthScore}/100`; - - if (window.showToast) { - window.showToast(message, status); - } - } else { - if (window.showToast) { - window.showToast('健康检查失败', 'error'); - } - } - } catch (error) { - console.error('健康检查失败:', error); - if (window.showToast) { - window.showToast('健康检查失败', 'error'); - } - } - } - - async restartServices() { - if (confirm('确定要重启系统服务吗?这可能会暂时中断服务。')) { - try { - // 这里应该调用后端重启API - console.log('重启服务...'); - - if (window.showToast) { - window.showToast('服务重启功能开发中', 'info'); - } - } catch (error) { - console.error('重启服务失败:', error); - if (window.showToast) { - window.showToast('重启失败', 'error'); - } - } - } - } - - formatUptime(seconds) { - const days = Math.floor(seconds / 86400); - const hours = Math.floor((seconds % 86400) / 3600); - const minutes = Math.floor((seconds % 3600) / 60); - - if (days > 0) { - return `${days}天 ${hours}小时 ${minutes}分钟`; - } else if (hours > 0) { - return `${hours}小时 ${minutes}分钟`; - } else { - return `${minutes}分钟`; - } - } -} \ No newline at end of file diff --git a/src/web/static/js/pages/vehicle.js b/src/web/static/js/pages/vehicle.js deleted file mode 100644 index 9a21372..0000000 --- a/src/web/static/js/pages/vehicle.js +++ /dev/null @@ -1,402 +0,0 @@ -/** - * 车辆数据页面组件 - */ - -export default class Vehicle { - constructor(container, route) { - this.container = container; - this.route = route; - this.init(); - } - - async init() { - try { - this.render(); - this.bindEvents(); - this.loadVehicleData(); - } catch (error) { - console.error('Vehicle init error:', error); - this.showError(error); - } - } - - render() { - this.container.innerHTML = ` - - - -
    -
    -
    - 数据查询 -
    -
    -
    -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    -
    -
    - - - -
    -
    -
    -
    - - -
    -
    -
    - 车辆数据列表 -
    -
    共 0 条数据
    -
    -
    -
    - - - - - - - - - - - - - - - - - -
    ID车辆ID车架号数据类型数据内容时间戳操作
    - 加载中... -
    -
    -
    -
    - - - - `; - } - - bindEvents() { - // 查询按钮 - document.getElementById('search-btn').addEventListener('click', () => { - this.loadVehicleData(); - }); - - // 初始化示例数据 - document.getElementById('init-sample-btn').addEventListener('click', () => { - this.initSampleData(); - }); - - // 添加数据按钮 - document.getElementById('add-data-btn').addEventListener('click', () => { - this.showAddDataModal(); - }); - - // 保存数据 - document.getElementById('save-data-btn').addEventListener('click', () => { - this.saveVehicleData(); - }); - - // 输入框回车查询 - ['vehicle_id', 'vehicle_vin'].forEach(id => { - document.getElementById(id).addEventListener('keypress', (e) => { - if (e.key === 'Enter') { - this.loadVehicleData(); - } - }); - }); - } - - async loadVehicleData() { - const vehicleId = document.getElementById('vehicle_id').value.trim(); - const vehicleVin = document.getElementById('vehicle_vin').value.trim(); - const dataType = document.getElementById('data_type').value; - const limit = document.getElementById('limit').value; - - const params = new URLSearchParams(); - if (vehicleId) params.append('vehicle_id', vehicleId); - if (vehicleVin) params.append('vehicle_vin', vehicleVin); - if (dataType) params.append('data_type', dataType); - if (limit) params.append('limit', limit); - - try { - const response = await fetch(`/api/vehicle/data?${params}`); - const data = await response.json(); - - if (Array.isArray(data)) { - this.renderVehicleData(data); - document.getElementById('data-count').textContent = `共 ${data.length} 条数据`; - } else { - this.showErrorInTable('数据格式错误'); - } - } catch (error) { - console.error('加载车辆数据失败:', error); - this.showErrorInTable('加载数据失败'); - } - } - - renderVehicleData(data) { - const tbody = document.getElementById('vehicle-data-body'); - - if (data.length === 0) { - tbody.innerHTML = ` - - - 暂无数据 - - - `; - return; - } - - tbody.innerHTML = data.map(item => { - const timestamp = new Date(item.timestamp).toLocaleString(); - let dataValue = item.data_value; - - try { - const parsed = JSON.parse(dataValue); - dataValue = JSON.stringify(parsed, null, 2); - } catch (e) { - // 如果不是JSON格式,保持原样 - } - - return ` - - ${item.id} - ${item.vehicle_id} - ${item.vehicle_vin || '-'} - ${item.data_type} - -
    ${dataValue}
    - - ${timestamp} - - - - - `; - }).join(''); - } - - showErrorInTable(message) { - const tbody = document.getElementById('vehicle-data-body'); - tbody.innerHTML = ` - - - ${message} - - - `; - } - - async initSampleData() { - if (!confirm('确定要初始化示例车辆数据吗?这将会添加一些测试数据。')) { - return; - } - - try { - const response = await fetch('/api/vehicle/init-sample-data', { method: 'POST' }); - const data = await response.json(); - - if (data.success) { - if (window.showToast) { - window.showToast('示例数据初始化成功', 'success'); - } - this.loadVehicleData(); - } else { - if (window.showToast) { - window.showToast(data.message || '初始化失败', 'error'); - } - } - } catch (error) { - console.error('初始化示例数据失败:', error); - if (window.showToast) { - window.showToast('网络错误', 'error'); - } - } - } - - showAddDataModal() { - const modal = new bootstrap.Modal(document.getElementById('addDataModal')); - modal.show(); - } - - async saveVehicleData() { - const form = document.getElementById('add-data-form'); - const formData = new FormData(form); - - const data = { - vehicle_id: formData.get('vehicle_id'), - vehicle_vin: formData.get('vehicle_vin') || null, - data_type: formData.get('data_type'), - data_value: formData.get('data_value') - }; - - // 验证JSON格式 - try { - JSON.parse(data.data_value); - } catch (e) { - if (window.showToast) { - window.showToast('数据内容必须是有效的JSON格式', 'error'); - } - return; - } - - try { - const response = await fetch('/api/vehicle/data', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(data) - }); - - const result = await response.json(); - - if (result.success) { - if (window.showToast) { - window.showToast('数据添加成功', 'success'); - } - - // 关闭模态框 - const modal = bootstrap.Modal.getInstance(document.getElementById('addDataModal')); - modal.hide(); - - // 清空表单 - form.reset(); - - // 重新加载数据 - this.loadVehicleData(); - } else { - if (window.showToast) { - window.showToast(result.message || '添加失败', 'error'); - } - } - } catch (error) { - console.error('保存数据失败:', error); - if (window.showToast) { - window.showToast('网络错误', 'error'); - } - } - } - - showError(error) { - this.container.innerHTML = ` -
    -
    -
    -
    - -

    页面加载失败

    -

    ${error.message || '未知错误'}

    - -
    -
    -
    -
    - `; - } -} - -// 全局函数,用于查看数据详情 -window.showDataDetails = function(id) { - // 这里可以实现查看详细数据的功能 - console.log('查看数据详情:', id); - if (window.showToast) { - window.showToast('详情查看功能开发中', 'info'); - } -}; diff --git a/src/web/static/js/pages/workorders.js b/src/web/static/js/pages/workorders.js deleted file mode 100644 index 30053c8..0000000 --- a/src/web/static/js/pages/workorders.js +++ /dev/null @@ -1,549 +0,0 @@ -/** - * 工单管理页面组件 - */ - -export default class WorkOrders { - constructor(container, route) { - this.container = container; - this.route = route; - this.currentPage = 1; - this.perPage = 20; - this.currentStatus = 'all'; - this.searchQuery = ''; - this.init(); - } - - async init() { - try { - this.render(); - this.bindEvents(); - await this.loadWorkOrders(); - } catch (error) { - console.error('WorkOrders init error:', error); - this.showError(error); - } - } - - render() { - this.container.innerHTML = ` -
    - - -
    - - -
    -
    -
    -
    - - -
    -
    - - -
    -
    - -
    -
    -
    -
    - - -
    -
    -
    工单列表
    -
    共 0 个工单
    -
    -
    -
    - - - - - - - - - - - - - - - - - -
    ID标题类别优先级状态创建时间操作
    - 加载中... -
    -
    -
    -
    - - - -
    -
    - - - - `; - } - - bindEvents() { - // 状态筛选 - document.getElementById('status-filter').addEventListener('change', () => { - this.currentStatus = document.getElementById('status-filter').value; - this.currentPage = 1; - this.loadWorkOrders(); - }); - - // 搜索 - document.getElementById('search-btn').addEventListener('click', () => { - this.searchQuery = document.getElementById('search-input').value.trim(); - this.currentPage = 1; - this.loadWorkOrders(); - }); - - document.getElementById('search-input').addEventListener('keypress', (e) => { - if (e.key === 'Enter') { - document.getElementById('search-btn').click(); - } - }); - - // 新建工单 - document.getElementById('create-workorder-btn').addEventListener('click', () => { - this.showCreateWorkOrderModal(); - }); - } - - async loadWorkOrders() { - try { - const params = new URLSearchParams({ - page: this.currentPage, - per_page: this.perPage, - status: this.currentStatus, - search: this.searchQuery - }); - - const response = await fetch(`/api/workorders?${params}`); - const data = await response.json(); - - if (response.ok && data.success) { - this.renderWorkOrders(data.workorders || []); - this.renderPagination(data.pagination || {}); - document.getElementById('workorder-count').textContent = `共 ${data.total || 0} 个工单`; - } else { - this.showErrorInTable(data.message || '加载工单失败'); - } - } catch (error) { - console.error('加载工单失败:', error); - this.showErrorInTable('网络错误'); - } - } - - renderWorkOrders(workorders) { - const tbody = document.getElementById('workorders-tbody'); - - if (workorders.length === 0) { - tbody.innerHTML = ` - - - 暂无工单 - - - `; - return; - } - - tbody.innerHTML = workorders.map(workorder => { - const statusBadge = this.getStatusBadge(workorder.status); - const priorityBadge = this.getPriorityBadge(workorder.priority); - const createTime = new Date(workorder.created_at).toLocaleString(); - - return ` - - ${workorder.order_id || workorder.id} - -
    ${workorder.title}
    - ${workorder.description?.substring(0, 50) || ''}... - - ${workorder.category || '未分类'} - ${priorityBadge} - ${statusBadge} - ${createTime} - -
    - - - -
    - - - `; - }).join(''); - } - - renderPagination(pagination) { - const nav = document.getElementById('pagination-nav'); - const list = document.getElementById('pagination-list'); - - if (!pagination || pagination.total_pages <= 1) { - nav.style.display = 'none'; - return; - } - - nav.style.display = 'block'; - - let html = ''; - - // 上一页 - if (pagination.has_prev) { - html += `
  • 上一页
  • `; - } - - // 页码 - for (let i = Math.max(1, pagination.page - 2); i <= Math.min(pagination.total_pages, pagination.page + 2); i++) { - const activeClass = i === pagination.page ? 'active' : ''; - html += `
  • ${i}
  • `; - } - - // 下一页 - if (pagination.has_next) { - html += `
  • 下一页
  • `; - } - - list.innerHTML = html; - } - - getStatusBadge(status) { - const statusMap = { - 'open': '待处理', - 'in_progress': '处理中', - 'resolved': '已解决', - 'closed': '已关闭' - }; - return statusMap[status] || `${status}`; - } - - getPriorityBadge(priority) { - const priorityMap = { - 'low': '', - 'medium': '', - 'high': '', - 'urgent': '紧急' - }; - return priorityMap[priority] || `${priority}`; - } - - showErrorInTable(message) { - const tbody = document.getElementById('workorders-tbody'); - tbody.innerHTML = ` - - - ${message} - - - `; - } - - showCreateWorkOrderModal() { - // 这里应该显示创建工单的模态框 - if (window.showToast) { - window.showToast('创建工单功能开发中', 'info'); - } - } - - showError(error) { - this.container.innerHTML = ` -
    -
    -
    -
    - -

    页面加载失败

    -

    ${error.message || '未知错误'}

    - -
    -
    -
    -
    - `; - } -} - -// 全局函数供表格操作使用 -window.viewWorkOrder = async function(id) { - try { - // 显示模态框(先显示加载状态) - const modalEl = document.getElementById('workorder-detail-modal'); - const modal = new bootstrap.Modal(modalEl); - modal.show(); - - // 重置内容 - document.getElementById('modal-status-badge').innerHTML = ''; - document.getElementById('modal-ai-analysis').innerHTML = '
    正在分析...'; - document.getElementById('modal-chat-history').innerHTML = '
    '; - - // 获取详情 - const response = await fetch(`/api/workorders/${id}`); - const result = await response.json(); - - if (!result.success) { - alert('获取工单详情失败'); - return; - } - - const wo = result.workorder; - - // 填充基本信息 - document.getElementById('modal-title').textContent = wo.title; - document.getElementById('modal-id').textContent = wo.order_id || wo.id; - document.getElementById('modal-category').textContent = wo.category || '-'; - document.getElementById('modal-created-at').textContent = new Date(wo.created_at).toLocaleString(); - document.getElementById('modal-user').textContent = wo.user_id || '-'; - document.getElementById('modal-description').textContent = wo.description || '无描述'; - - // 状态徽章 - const statusMap = { - 'open': '待处理', - 'in_progress': '处理中', - 'resolved': '已解决', - 'closed': '已关闭' - }; - document.getElementById('modal-status-badge').innerHTML = statusMap[wo.status] || wo.status; - - // 优先级 - const priorityMap = { - 'low': '', - 'medium': '', - 'high': '', - 'urgent': '紧急' - }; - document.getElementById('modal-priority').innerHTML = priorityMap[wo.priority] || wo.priority; - - // AI 分析/建议 - if (wo.resolution) { - document.getElementById('modal-ai-analysis').innerHTML = ` -
    ${wo.resolution}
    - `; - } else { - document.getElementById('modal-ai-analysis').textContent = '暂无 AI 分析建议'; - } - - // 渲染对话/处理历史 (模拟数据或真实数据) - const historyContainer = document.getElementById('modal-chat-history'); - // 这里假设后端返回的详情中包含 history 或 timeline - // 如果没有,暂时显示描述作为第一条记录 - - let historyHtml = ''; - - // 模拟一条初始记录 - historyHtml += ` -
    -
    - 用户 - ${new Date(wo.created_at).toLocaleString()} -
    -
    - ${wo.description} -
    -
    - `; - - if (wo.timeline && wo.timeline.length > 0) { - // 如果有真实的时间轴数据 - historyHtml = wo.timeline.map(item => ` -
    -
    - - ${item.author || (item.type === 'ai' ? 'AI 助手' : '系统')} - - ${new Date(item.timestamp).toLocaleString()} -
    -
    - ${item.content} -
    -
    - `).join(''); - } - - historyContainer.innerHTML = historyHtml; - - // 绑定编辑按钮 - document.getElementById('modal-edit-btn').onclick = () => { - modal.hide(); - editWorkOrder(id); - }; - - } catch (e) { - console.error('查看工单详情失败', e); - alert('查看详情失败: ' + e.message); - } -}; - -window.editWorkOrder = function(id) { - if (window.showToast) { - window.showToast(`编辑工单 ${id} 功能开发中`, 'info'); - } -}; - -window.deleteWorkOrder = function(id) { - if (!confirm(`确定要删除工单 ${id} 吗?`)) { - return; - } - - (async () => { - try { - const response = await fetch(`/api/workorders/${id}`, { - method: 'DELETE' - }); - const result = await response.json(); - - if (response.ok && result.success) { - if (window.showToast) { - window.showToast(result.message || `工单 ${id} 删除成功`, 'success'); - } - // 通知当前页面刷新工单列表(保持当前页) - const event = new CustomEvent('workorder-deleted', { detail: { id } }); - document.dispatchEvent(event); - } else { - if (window.showToast) { - window.showToast(result.message || '删除工单失败', 'error'); - } else { - alert(result.message || '删除工单失败'); - } - } - } catch (error) { - console.error('删除工单失败:', error); - if (window.showToast) { - window.showToast('删除失败,请检查网络或查看控制台日志', 'error'); - } else { - alert('删除失败,请检查网络或查看控制台日志'); - } - } - })(); -}; - -window.changePage = function(page) { - // 重新加载当前页面实例 - const event = new CustomEvent('changePage', { detail: { page } }); - document.dispatchEvent(event); -}; - -// 监听删除事件,触发当前 WorkOrders 列表刷新(保持当前页) -document.addEventListener('workorder-deleted', () => { - const event = new CustomEvent('reloadWorkOrders'); - document.dispatchEvent(event); -}); \ No newline at end of file diff --git a/src/web/static/js/services/api.js b/src/web/static/js/services/api.js deleted file mode 100644 index 8def917..0000000 --- a/src/web/static/js/services/api.js +++ /dev/null @@ -1,160 +0,0 @@ -/** - * 统一API服务 - * 提供所有API调用的统一接口 - */ - -class ApiService { - constructor() { - this.baseURL = ''; - this.defaultTimeout = 30000; // 30秒超时 - } - - async request(endpoint, options = {}) { - const url = `${this.baseURL}${endpoint}`; - const config = { - headers: { - 'Content-Type': 'application/json', - ...options.headers - }, - timeout: options.timeout || this.defaultTimeout, - ...options - }; - - try { - const response = await fetch(url, config); - const data = await response.json(); - - if (!response.ok) { - throw new Error(data.error || `HTTP ${response.status}: ${response.statusText}`); - } - - return data; - } catch (error) { - console.error(`API请求失败: ${endpoint}`, error); - throw error; - } - } - - // 健康检查相关 - async getHealth() { - return this.request('/api/health'); - } - - // 预警相关 - async getAlerts() { - return this.request('/api/alerts'); - } - - async createAlert(alertData) { - return this.request('/api/alerts', { - method: 'POST', - body: JSON.stringify(alertData) - }); - } - - async updateAlert(alertId, alertData) { - return this.request(`/api/alerts/${alertId}`, { - method: 'PUT', - body: JSON.stringify(alertData) - }); - } - - async deleteAlert(alertId) { - return this.request(`/api/alerts/${alertId}`, { - method: 'DELETE' - }); - } - - // 规则相关 - async getRules() { - return this.request('/api/rules'); - } - - async createRule(ruleData) { - return this.request('/api/rules', { - method: 'POST', - body: JSON.stringify(ruleData) - }); - } - - async updateRule(ruleId, ruleData) { - return this.request(`/api/rules/${ruleId}`, { - method: 'PUT', - body: JSON.stringify(ruleData) - }); - } - - async deleteRule(ruleId) { - return this.request(`/api/rules/${ruleId}`, { - method: 'DELETE' - }); - } - - // 监控相关 - async getMonitorStatus() { - return this.request('/api/monitor/status'); - } - - async startMonitoring() { - return this.request('/api/monitor/start', { - method: 'POST' - }); - } - - async stopMonitoring() { - return this.request('/api/monitor/stop', { - method: 'POST' - }); - } - - // Agent相关 - async getAgentStatus() { - return this.request('/api/agent/status'); - } - - async toggleAgent(enabled) { - return this.request('/api/agent/toggle', { - method: 'POST', - body: JSON.stringify({ enabled }) - }); - } - - async getAgentHistory(limit = 50) { - return this.request(`/api/agent/action-history?limit=${limit}`); - } - - // 车辆数据相关 - async getVehicleData(params = {}) { - const queryString = new URLSearchParams(params).toString(); - return this.request(`/api/vehicle/data?${queryString}`); - } - - async addVehicleData(data) { - return this.request('/api/vehicle/data', { - method: 'POST', - body: JSON.stringify(data) - }); - } - - // 对话相关 - async createChatSession(data) { - return this.request('/api/chat/session', { - method: 'POST', - body: JSON.stringify(data) - }); - } - - async sendChatMessage(data) { - return this.request('/api/chat/message', { - method: 'POST', - body: JSON.stringify(data) - }); - } - - async getChatHistory(sessionId) { - return this.request(`/api/chat/history/${sessionId}`); - } -} - -// 创建全局API服务实例 -const apiService = new ApiService(); diff --git a/src/web/templates/chat.html b/src/web/templates/chat.html index e46166b..3768a68 100644 --- a/src/web/templates/chat.html +++ b/src/web/templates/chat.html @@ -327,6 +327,6 @@
    - + diff --git a/src/web/templates/chat_http.html b/src/web/templates/chat_http.html index c8cbf95..ef7b2bd 100644 --- a/src/web/templates/chat_http.html +++ b/src/web/templates/chat_http.html @@ -327,6 +327,6 @@
    - + diff --git a/src/web/templates/index.html b/src/web/templates/index.html index ceb2d86..47ba470 100644 --- a/src/web/templates/index.html +++ b/src/web/templates/index.html @@ -650,6 +650,6 @@ - +