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 = '
-
-
-
- `;
- }
-
- 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.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 = `
-
-
加载失败
-
${error.message || '未知错误'}
-
-
-
- `;
- }
-
- 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 += '| 记录ID | 字段数据 |
';
-
- data.preview_data.forEach(item => {
- html += `
- | ${item.record_id} |
- ${JSON.stringify(item.fields, null, 2)} |
-
`;
- });
-
- html += '
';
- 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 += '
';
- Object.entries(report).forEach(([field, info]) => {
- html += `-
- ${field}: ${info.suggestion || '未知'}
-
置信度: ${(info.confidence * 100).toFixed(1)}%
- `;
- });
- 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 += '
';
- status.mappings.forEach(mapping => {
- html += `-
- ${mapping.feishu_field} → ${mapping.local_field}
-
优先级: ${mapping.priority}
- `;
- });
- 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 = `
-
-
-
-
-
-
- `;
- }
-
- 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 = `
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
数据管理
-
-
-
-
-
-
-
系统维护
-
-
-
-
-
-
-
-
-
-
- `;
- }
-
- 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 = `
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- | 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 = `
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- | 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 @@
-
+