核心 dashboard.js 缩减至266行,只保留:
- TSPDashboard class 定义(constructor、init、bindEvents、switchTab)
- 分页组件、缓存、状态管理、自动刷新、WebSocket、i18n
功能模块拆分到 modules/ 目录:
- chat.js (207行) 智能对话
- agent.js (424行) Agent管理
- alerts.js (348行) 预警管理
- knowledge.js (699行) 知识库管理
- workorders.js (约500行) 工单管理
- conversations.js (671行) 对话历史
- monitoring.js (455行) Token/AI监控
- system.js (1388行) 系统优化+设置+数据分析图表
- tenants.js (165行) 租户管理
- utils.js (573行) 工具函数
- dashboard-home.js (286行) 仪表板首页
- feishu-sync.js (698行) 飞书同步管理器
拆分方式:Object.assign(TSPDashboard.prototype, {...})
this 引用完全不会断,所有模块方法互相调用正常
287 lines
11 KiB
JavaScript
287 lines
11 KiB
JavaScript
// Dashboard Home Module - 仪表板首页相关方法
|
|
Object.assign(TSPDashboard.prototype, {
|
|
|
|
async loadDashboardData() {
|
|
try {
|
|
const [sessionsResponse, alertsResponse, workordersResponse, knowledgeResponse] = await Promise.all([
|
|
fetch('/api/chat/sessions'),
|
|
fetch('/api/alerts?per_page=1000'),
|
|
fetch('/api/workorders'),
|
|
fetch('/api/knowledge/stats')
|
|
]);
|
|
|
|
const sessions = await sessionsResponse.json();
|
|
const alerts = await alertsResponse.json();
|
|
const workorders = await workordersResponse.json();
|
|
const knowledge = await knowledgeResponse.json();
|
|
|
|
document.getElementById('total-sessions').textContent = sessions.sessions?.length || 0;
|
|
document.getElementById('total-alerts').textContent = alerts.alerts?.length || 0;
|
|
document.getElementById('total-workorders').textContent = workorders.workorders?.filter(w => w.status === 'open').length || 0;
|
|
document.getElementById('knowledge-count').textContent = knowledge.total_entries || 0;
|
|
|
|
if (alerts.alerts) {
|
|
this.updateAlertStatistics(alerts.alerts);
|
|
}
|
|
|
|
document.getElementById('knowledge-total').textContent = knowledge.total_entries || 0;
|
|
document.getElementById('knowledge-active').textContent = knowledge.active_entries || 0;
|
|
const confidencePercent = Math.round((knowledge.average_confidence || 0) * 100);
|
|
document.getElementById('knowledge-confidence-bar').style.width = `${confidencePercent}%`;
|
|
document.getElementById('knowledge-confidence-bar').setAttribute('aria-valuenow', confidencePercent);
|
|
document.getElementById('knowledge-confidence-bar').textContent = `${confidencePercent}%`;
|
|
|
|
await this.updatePerformanceChart(sessions, alerts, workorders);
|
|
await this.updateSystemHealth();
|
|
await this.loadAnalytics();
|
|
|
|
} catch (error) {
|
|
console.error('加载仪表板数据失败:', error);
|
|
}
|
|
},
|
|
|
|
initCharts() {
|
|
const performanceCtx = document.getElementById('performanceChart');
|
|
if (performanceCtx) {
|
|
this.charts.performance = new Chart(performanceCtx, {
|
|
type: 'line',
|
|
data: {
|
|
labels: [],
|
|
datasets: [{
|
|
label: '工单数量',
|
|
data: [],
|
|
borderColor: '#007bff',
|
|
backgroundColor: 'rgba(0, 123, 255, 0.1)',
|
|
tension: 0.4
|
|
}, {
|
|
label: '预警数量',
|
|
data: [],
|
|
borderColor: '#dc3545',
|
|
backgroundColor: 'rgba(220, 53, 69, 0.1)',
|
|
tension: 0.4
|
|
}]
|
|
},
|
|
options: {
|
|
responsive: true,
|
|
maintainAspectRatio: false,
|
|
scales: {
|
|
y: {
|
|
beginAtZero: true
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
const analyticsCtx = document.getElementById('analyticsChart');
|
|
if (analyticsCtx) {
|
|
this.charts.analytics = new Chart(analyticsCtx, {
|
|
type: 'line',
|
|
data: {
|
|
labels: [],
|
|
datasets: [{
|
|
label: '满意度',
|
|
data: [],
|
|
borderColor: '#28a745',
|
|
backgroundColor: 'rgba(40, 167, 69, 0.1)',
|
|
tension: 0.4
|
|
}, {
|
|
label: '解决时间(小时)',
|
|
data: [],
|
|
borderColor: '#ffc107',
|
|
backgroundColor: 'rgba(255, 193, 7, 0.1)',
|
|
tension: 0.4
|
|
}]
|
|
},
|
|
options: {
|
|
responsive: true,
|
|
maintainAspectRatio: false,
|
|
scales: {
|
|
y: {
|
|
beginAtZero: true
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
const categoryCtx = document.getElementById('categoryChart');
|
|
if (categoryCtx) {
|
|
this.charts.category = new Chart(categoryCtx, {
|
|
type: 'doughnut',
|
|
data: {
|
|
labels: [],
|
|
datasets: [{
|
|
data: [],
|
|
backgroundColor: [
|
|
'#007bff',
|
|
'#28a745',
|
|
'#ffc107',
|
|
'#dc3545',
|
|
'#17a2b8'
|
|
]
|
|
}]
|
|
},
|
|
options: {
|
|
responsive: true,
|
|
maintainAspectRatio: false
|
|
}
|
|
});
|
|
}
|
|
},
|
|
|
|
async updatePerformanceChart(sessions, alerts, workorders) {
|
|
if (!this.charts.performance) return;
|
|
|
|
try {
|
|
const response = await fetch('/api/analytics?days=7&dimension=performance');
|
|
const analyticsData = await response.json();
|
|
|
|
if (analyticsData.trend && analyticsData.trend.length > 0) {
|
|
const labels = analyticsData.trend.map(item => {
|
|
const date = new Date(item.date);
|
|
return `${date.getMonth() + 1}/${date.getDate()}`;
|
|
});
|
|
|
|
const workorderData = analyticsData.trend.map(item => item.workorders || 0);
|
|
const alertData = analyticsData.trend.map(item => item.alerts || 0);
|
|
|
|
this.charts.performance.data.labels = labels;
|
|
this.charts.performance.data.datasets[0].data = workorderData;
|
|
this.charts.performance.data.datasets[1].data = alertData;
|
|
this.charts.performance.update();
|
|
} else {
|
|
this.charts.performance.data.labels = ['暂无数据'];
|
|
this.charts.performance.data.datasets[0].data = [0];
|
|
this.charts.performance.data.datasets[1].data = [0];
|
|
this.charts.performance.update();
|
|
}
|
|
} catch (error) {
|
|
console.error('获取性能趋势数据失败:', error);
|
|
this.charts.performance.data.labels = ['数据加载失败'];
|
|
this.charts.performance.data.datasets[0].data = [0];
|
|
this.charts.performance.data.datasets[1].data = [0];
|
|
this.charts.performance.update();
|
|
}
|
|
},
|
|
|
|
async updateSystemHealth() {
|
|
try {
|
|
const response = await fetch('/api/settings');
|
|
const settings = await response.json();
|
|
|
|
const healthScore = Math.max(0, 100 - (settings.memory_usage_percent || 0) - (settings.cpu_usage_percent || 0));
|
|
const healthProgress = document.getElementById('health-progress');
|
|
const healthDot = document.getElementById('system-health-dot');
|
|
const healthText = document.getElementById('system-health-text');
|
|
|
|
if (healthProgress) {
|
|
healthProgress.style.width = `${healthScore}%`;
|
|
healthProgress.setAttribute('aria-valuenow', healthScore);
|
|
}
|
|
|
|
if (healthDot) {
|
|
healthDot.className = 'health-dot';
|
|
if (healthScore >= 80) healthDot.classList.add('excellent');
|
|
else if (healthScore >= 60) healthDot.classList.add('good');
|
|
else if (healthScore >= 40) healthDot.classList.add('fair');
|
|
else if (healthScore >= 20) healthDot.classList.add('poor');
|
|
else healthDot.classList.add('critical');
|
|
}
|
|
|
|
if (healthText) {
|
|
const statusText = healthScore >= 80 ? '优秀' :
|
|
healthScore >= 60 ? '良好' :
|
|
healthScore >= 40 ? '一般' :
|
|
healthScore >= 20 ? '较差' : '严重';
|
|
healthText.textContent = `${statusText} (${healthScore}%)`;
|
|
}
|
|
|
|
const memoryProgress = document.getElementById('memory-progress');
|
|
if (memoryProgress && settings.memory_usage_percent !== undefined) {
|
|
memoryProgress.style.width = `${settings.memory_usage_percent}%`;
|
|
memoryProgress.setAttribute('aria-valuenow', settings.memory_usage_percent);
|
|
}
|
|
|
|
const cpuProgress = document.getElementById('cpu-progress');
|
|
if (cpuProgress && settings.cpu_usage_percent !== undefined) {
|
|
cpuProgress.style.width = `${settings.cpu_usage_percent}%`;
|
|
cpuProgress.setAttribute('aria-valuenow', settings.cpu_usage_percent);
|
|
}
|
|
|
|
} catch (error) {
|
|
console.error('更新系统健康状态失败:', error);
|
|
}
|
|
},
|
|
|
|
async loadHealth() {
|
|
try {
|
|
const response = await fetch('/api/health');
|
|
const data = await response.json();
|
|
this.updateHealthDisplay(data);
|
|
} catch (error) {
|
|
console.error('加载健康状态失败:', error);
|
|
}
|
|
},
|
|
|
|
updateHealthDisplay(health) {
|
|
const healthScore = health.health_score || 0;
|
|
const healthStatus = health.status || 'unknown';
|
|
|
|
const healthDot = document.getElementById('health-dot');
|
|
const healthStatusText = document.getElementById('health-status');
|
|
const systemHealthDot = document.getElementById('system-health-dot');
|
|
const systemHealthText = document.getElementById('system-health-text');
|
|
const healthProgress = document.getElementById('health-progress');
|
|
|
|
if (healthDot) {
|
|
healthDot.className = `health-dot ${healthStatus}`;
|
|
}
|
|
if (healthStatusText) {
|
|
healthStatusText.textContent = this.getHealthStatusText(healthStatus);
|
|
}
|
|
if (systemHealthDot) {
|
|
systemHealthDot.className = `health-dot ${healthStatus}`;
|
|
}
|
|
if (systemHealthText) {
|
|
systemHealthText.textContent = this.getHealthStatusText(healthStatus);
|
|
}
|
|
if (healthProgress) {
|
|
healthProgress.style.width = `${healthScore * 100}%`;
|
|
healthProgress.className = `progress-bar ${this.getHealthColor(healthScore)}`;
|
|
}
|
|
|
|
const memoryUsage = health.memory_usage || 0;
|
|
const memoryProgress = document.getElementById('memory-progress');
|
|
if (memoryProgress) {
|
|
memoryProgress.style.width = `${memoryUsage}%`;
|
|
}
|
|
|
|
const cpuUsage = health.cpu_usage || 0;
|
|
const cpuProgress = document.getElementById('cpu-progress');
|
|
if (cpuProgress) {
|
|
cpuProgress.style.width = `${cpuUsage}%`;
|
|
}
|
|
},
|
|
|
|
getHealthStatusText(status) {
|
|
const statusMap = {
|
|
'excellent': '优秀',
|
|
'good': '良好',
|
|
'fair': '一般',
|
|
'poor': '较差',
|
|
'critical': '严重',
|
|
'unknown': '未知'
|
|
};
|
|
return statusMap[status] || status;
|
|
},
|
|
|
|
getHealthColor(score) {
|
|
if (score >= 0.8) return 'bg-success';
|
|
if (score >= 0.6) return 'bg-info';
|
|
if (score >= 0.4) return 'bg-warning';
|
|
return 'bg-danger';
|
|
}
|
|
|
|
});
|