590 lines
17 KiB
JavaScript
590 lines
17 KiB
JavaScript
// ========================================
|
|
// Claude Code SEO Assistant - Dashboard Logic
|
|
// ========================================
|
|
|
|
// Global State
|
|
const state = {
|
|
currentView: 'overview',
|
|
currentDomain: 'yoursite.com',
|
|
isLoading: false,
|
|
data: null,
|
|
charts: {}
|
|
};
|
|
|
|
// Initialize Dashboard
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
initializeNavigation();
|
|
initializeCharts();
|
|
initializeRefreshButton();
|
|
initializeDomainSelect();
|
|
loadDashboardData();
|
|
});
|
|
|
|
// Navigation
|
|
function initializeNavigation() {
|
|
const navItems = document.querySelectorAll('.nav-item');
|
|
|
|
navItems.forEach(item => {
|
|
item.addEventListener('click', () => {
|
|
const view = item.dataset.view;
|
|
switchView(view);
|
|
});
|
|
});
|
|
}
|
|
|
|
function switchView(viewName) {
|
|
// Update navigation
|
|
document.querySelectorAll('.nav-item').forEach(item => {
|
|
item.classList.remove('active');
|
|
if (item.dataset.view === viewName) {
|
|
item.classList.add('active');
|
|
}
|
|
});
|
|
|
|
// Update views
|
|
document.querySelectorAll('.view').forEach(view => {
|
|
view.classList.remove('active');
|
|
});
|
|
|
|
const targetView = document.getElementById(`${viewName}-view`);
|
|
if (targetView) {
|
|
targetView.classList.add('active');
|
|
state.currentView = viewName;
|
|
}
|
|
}
|
|
|
|
// Charts Initialization
|
|
function initializeCharts() {
|
|
initializeCitationTrendChart();
|
|
initializeEngineComparisonChart();
|
|
initializeGeoRadarChart();
|
|
initializeContentTypeChart();
|
|
}
|
|
|
|
function initializeCitationTrendChart() {
|
|
const ctx = document.getElementById('citationTrendChart');
|
|
if (!ctx) return;
|
|
|
|
state.charts.citationTrend = new Chart(ctx, {
|
|
type: 'line',
|
|
data: {
|
|
labels: generateDateLabels(30),
|
|
datasets: [
|
|
{
|
|
label: 'ChatGPT',
|
|
data: generateRandomData(30, 200, 234),
|
|
borderColor: '#10A37F',
|
|
backgroundColor: 'rgba(16, 163, 127, 0.1)',
|
|
tension: 0.4,
|
|
fill: true
|
|
},
|
|
{
|
|
label: 'Claude',
|
|
data: generateRandomData(30, 150, 189),
|
|
borderColor: '#8B5CF6',
|
|
backgroundColor: 'rgba(139, 92, 246, 0.1)',
|
|
tension: 0.4,
|
|
fill: true
|
|
},
|
|
{
|
|
label: 'Perplexity',
|
|
data: generateRandomData(30, 130, 156),
|
|
borderColor: '#1DB954',
|
|
backgroundColor: 'rgba(29, 185, 84, 0.1)',
|
|
tension: 0.4,
|
|
fill: true
|
|
},
|
|
{
|
|
label: 'Google SGE',
|
|
data: generateRandomData(30, 80, 98),
|
|
borderColor: '#4285F4',
|
|
backgroundColor: 'rgba(66, 133, 244, 0.1)',
|
|
tension: 0.4,
|
|
fill: true
|
|
}
|
|
]
|
|
},
|
|
options: {
|
|
responsive: true,
|
|
maintainAspectRatio: false,
|
|
plugins: {
|
|
legend: {
|
|
position: 'top',
|
|
},
|
|
tooltip: {
|
|
mode: 'index',
|
|
intersect: false,
|
|
}
|
|
},
|
|
scales: {
|
|
y: {
|
|
beginAtZero: true,
|
|
ticks: {
|
|
callback: function(value) {
|
|
return value + ' 次';
|
|
}
|
|
}
|
|
}
|
|
},
|
|
interaction: {
|
|
mode: 'nearest',
|
|
axis: 'x',
|
|
intersect: false
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
function initializeEngineComparisonChart() {
|
|
const ctx = document.getElementById('engineComparisonChart');
|
|
if (!ctx) return;
|
|
|
|
state.charts.engineComparison = new Chart(ctx, {
|
|
type: 'bar',
|
|
data: {
|
|
labels: ['ChatGPT', 'Claude', 'Perplexity', 'Google SGE'],
|
|
datasets: [
|
|
{
|
|
label: '可见性评分',
|
|
data: [68, 75, 70, 55],
|
|
backgroundColor: [
|
|
'rgba(16, 163, 127, 0.8)',
|
|
'rgba(139, 92, 246, 0.8)',
|
|
'rgba(29, 185, 84, 0.8)',
|
|
'rgba(66, 133, 244, 0.8)'
|
|
],
|
|
borderWidth: 0
|
|
}
|
|
]
|
|
},
|
|
options: {
|
|
responsive: true,
|
|
maintainAspectRatio: false,
|
|
plugins: {
|
|
legend: {
|
|
display: false
|
|
},
|
|
tooltip: {
|
|
callbacks: {
|
|
label: function(context) {
|
|
return `评分: ${context.parsed.y}/100`;
|
|
}
|
|
}
|
|
}
|
|
},
|
|
scales: {
|
|
y: {
|
|
beginAtZero: true,
|
|
max: 100
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
function initializeGeoRadarChart() {
|
|
const ctx = document.getElementById('geoRadarChart');
|
|
if (!ctx) return;
|
|
|
|
state.charts.geoRadar = new Chart(ctx, {
|
|
type: 'radar',
|
|
data: {
|
|
labels: ['权威性', '实体关系', '内容结构', '数据质量', '引用密度', '技术优化'],
|
|
datasets: [
|
|
{
|
|
label: '当前评分',
|
|
data: [78, 82, 75, 80, 72, 68],
|
|
backgroundColor: 'rgba(79, 70, 229, 0.2)',
|
|
borderColor: 'rgba(79, 70, 229, 1)',
|
|
borderWidth: 2
|
|
},
|
|
{
|
|
label: '目标评分',
|
|
data: [85, 85, 85, 85, 85, 85],
|
|
backgroundColor: 'rgba(16, 185, 129, 0.2)',
|
|
borderColor: 'rgba(16, 185, 129, 1)',
|
|
borderWidth: 2
|
|
}
|
|
]
|
|
},
|
|
options: {
|
|
responsive: true,
|
|
maintainAspectRatio: false,
|
|
plugins: {
|
|
legend: {
|
|
position: 'top'
|
|
}
|
|
},
|
|
scales: {
|
|
r: {
|
|
beginAtZero: true,
|
|
max: 100,
|
|
ticks: {
|
|
stepSize: 20
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
function initializeContentTypeChart() {
|
|
const ctx = document.getElementById('contentTypeChart');
|
|
if (!ctx) return;
|
|
|
|
state.charts.contentType = new Chart(ctx, {
|
|
type: 'pie',
|
|
data: {
|
|
labels: ['指南教程', '案例分析', '对比分析', '列表文章', '博客文章'],
|
|
datasets: [{
|
|
data: [45, 25, 18, 12, 20],
|
|
backgroundColor: [
|
|
'rgba(16, 163, 127, 0.8)',
|
|
'rgba(139, 92, 246, 0.8)',
|
|
'rgba(29, 185, 84, 0.8)',
|
|
'rgba(66, 133, 244, 0.8)',
|
|
'rgba(245, 158, 11, 0.8)'
|
|
],
|
|
borderWidth: 2,
|
|
borderColor: '#fff'
|
|
}]
|
|
},
|
|
options: {
|
|
responsive: true,
|
|
maintainAspectRatio: false,
|
|
plugins: {
|
|
legend: {
|
|
position: 'right'
|
|
},
|
|
tooltip: {
|
|
callbacks: {
|
|
label: function(context) {
|
|
const label = context.label || '';
|
|
const value = context.parsed || 0;
|
|
const total = context.dataset.data.reduce((a, b) => a + b, 0);
|
|
const percentage = ((value / total) * 100).toFixed(1);
|
|
return `${label}: ${value} (${percentage}%)`;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
// Data Loading
|
|
function loadDashboardData() {
|
|
showLoading();
|
|
|
|
// Simulate API call
|
|
setTimeout(() => {
|
|
// In real implementation, this would fetch from API
|
|
state.data = generateMockData();
|
|
updateDashboard();
|
|
hideLoading();
|
|
updateLastRefresh();
|
|
}, 1000);
|
|
}
|
|
|
|
function generateMockData() {
|
|
return {
|
|
geoScore: 72,
|
|
citations: {
|
|
chatgpt: 234,
|
|
claude: 189,
|
|
perplexity: 156,
|
|
googleSge: 98,
|
|
total: 677
|
|
},
|
|
ranking: {
|
|
current: 1,
|
|
total: 15,
|
|
trend: 'up'
|
|
},
|
|
traffic: {
|
|
organic: 12500,
|
|
trend: '+28%'
|
|
},
|
|
engines: {
|
|
chatgpt: { score: 68, citations: 234, rank: 4.2, trend: '+18%' },
|
|
claude: { score: 75, citations: 189, rank: 3.8, trend: '+22%' },
|
|
perplexity: { score: 70, citations: 156, rank: 4.5, trend: '+15%' },
|
|
googleSge: { score: 55, citations: 98, rank: 7.2, trend: '稳定' }
|
|
},
|
|
content: {
|
|
total: 156,
|
|
avgScore: 68,
|
|
highCitations: 23,
|
|
needsOptimization: 18
|
|
},
|
|
competitors: [
|
|
{ name: '你们', chatgpt: 68, claude: 75, perplexity: 70, googleSge: 55, total: 268, trend: 'up' },
|
|
{ name: 'competitor-a.com', chatgpt: 45, claude: 52, perplexity: 48, googleSge: 62, total: 207, trend: 'up' },
|
|
{ name: 'competitor-b.com', chatgpt: 38, claude: 41, perplexity: 35, googleSge: 48, total: 162, trend: 'stable' },
|
|
{ name: 'competitor-c.com', chatgpt: 28, claude: 32, perplexity: 30, googleSge: 35, total: 125, trend: 'down' }
|
|
],
|
|
alerts: {
|
|
critical: 0,
|
|
warning: 2,
|
|
info: 12
|
|
},
|
|
automation: {
|
|
weeklyAudit: { active: true, executions: 24, successRate: 96, avgTime: 845 },
|
|
monthlyReport: { active: true, executions: 6, successRate: 100, avgTime: 420 },
|
|
competitorMonitor: { active: false, executions: 45, successRate: 100, avgTime: 180 }
|
|
}
|
|
};
|
|
}
|
|
|
|
function updateDashboard() {
|
|
if (!state.data) return;
|
|
|
|
updateMetrics();
|
|
updateCharts();
|
|
updateCompetitorsTable();
|
|
updateAutomationStatus();
|
|
updateAlerts();
|
|
}
|
|
|
|
function updateMetrics() {
|
|
// Metrics are static in HTML, but could be updated dynamically
|
|
console.log('Metrics updated');
|
|
}
|
|
|
|
function updateCharts() {
|
|
// Update chart data if needed
|
|
console.log('Charts updated');
|
|
}
|
|
|
|
function updateCompetitorsTable() {
|
|
// Table is static in HTML, but could be updated dynamically
|
|
console.log('Competitors table updated');
|
|
}
|
|
|
|
function updateAutomationStatus() {
|
|
// Automation status is static in HTML, but could be updated dynamically
|
|
console.log('Automation status updated');
|
|
}
|
|
|
|
function updateAlerts() {
|
|
// Alerts are static in HTML, but could be updated dynamically
|
|
console.log('Alerts updated');
|
|
}
|
|
|
|
// Refresh Button
|
|
function initializeRefreshButton() {
|
|
const refreshBtn = document.getElementById('refreshBtn');
|
|
if (!refreshBtn) return;
|
|
|
|
refreshBtn.addEventListener('click', () => {
|
|
loadDashboardData();
|
|
showNotification('数据已刷新', 'success');
|
|
});
|
|
}
|
|
|
|
function initializeDomainSelect() {
|
|
const domainSelect = document.getElementById('domainSelect');
|
|
if (!domainSelect) return;
|
|
|
|
domainSelect.addEventListener('change', (e) => {
|
|
state.currentDomain = e.target.value;
|
|
loadDashboardData();
|
|
showNotification(`已切换到域名: ${state.currentDomain}`, 'info');
|
|
});
|
|
}
|
|
|
|
// Utility Functions
|
|
function showLoading() {
|
|
const loadingIndicator = document.getElementById('loadingIndicator');
|
|
if (loadingIndicator) {
|
|
loadingIndicator.classList.remove('hidden');
|
|
}
|
|
state.isLoading = true;
|
|
}
|
|
|
|
function hideLoading() {
|
|
const loadingIndicator = document.getElementById('loadingIndicator');
|
|
if (loadingIndicator) {
|
|
loadingIndicator.classList.add('hidden');
|
|
}
|
|
state.isLoading = false;
|
|
}
|
|
|
|
function showNotification(message, type = 'info') {
|
|
const notification = document.getElementById('notification');
|
|
if (!notification) return;
|
|
|
|
const messageEl = notification.querySelector('.message');
|
|
if (messageEl) {
|
|
messageEl.textContent = message;
|
|
}
|
|
|
|
notification.classList.remove('hidden');
|
|
|
|
// Auto-hide after 3 seconds
|
|
setTimeout(() => {
|
|
hideNotification();
|
|
}, 3000);
|
|
}
|
|
|
|
function hideNotification() {
|
|
const notification = document.getElementById('notification');
|
|
if (notification) {
|
|
notification.classList.add('hidden');
|
|
}
|
|
}
|
|
|
|
function updateLastRefresh() {
|
|
const lastUpdateEl = document.getElementById('lastUpdate');
|
|
if (lastUpdateEl) {
|
|
const now = new Date();
|
|
const formatted = now.toLocaleString('zh-CN', {
|
|
year: 'numeric',
|
|
month: '2-digit',
|
|
day: '2-digit',
|
|
hour: '2-digit',
|
|
minute: '2-digit'
|
|
});
|
|
lastUpdateEl.textContent = formatted;
|
|
}
|
|
}
|
|
|
|
function generateDateLabels(days) {
|
|
const labels = [];
|
|
const now = new Date();
|
|
|
|
for (let i = days - 1; i >= 0; i--) {
|
|
const date = new Date(now);
|
|
date.setDate(date.getDate() - i);
|
|
labels.push(date.toLocaleDateString('zh-CN', { month: 'short', day: 'numeric' }));
|
|
}
|
|
|
|
return labels;
|
|
}
|
|
|
|
function generateRandomData(count, min, max) {
|
|
const data = [];
|
|
let current = Math.floor((min + max) / 2);
|
|
|
|
for (let i = 0; i < count; i++) {
|
|
const change = Math.floor(Math.random() * 20) - 10;
|
|
current = Math.max(min, Math.min(max, current + change));
|
|
data.push(current);
|
|
}
|
|
|
|
return data;
|
|
}
|
|
|
|
// Command Execution (Integration with CLI)
|
|
function runCommand(command) {
|
|
showNotification(`执行命令: ${command}`, 'info');
|
|
|
|
// In a real implementation, this would execute the actual command
|
|
// For now, just simulate the action
|
|
console.log(`Executing command: ${command}`);
|
|
|
|
// Simulate command completion
|
|
setTimeout(() => {
|
|
showNotification(`命令完成: ${command}`, 'success');
|
|
}, 2000);
|
|
}
|
|
|
|
// Filter Buttons (for chart time periods)
|
|
document.addEventListener('click', (e) => {
|
|
if (e.target.classList.contains('filter-btn')) {
|
|
const period = e.target.dataset.period;
|
|
|
|
// Update active state
|
|
document.querySelectorAll('.filter-btn').forEach(btn => {
|
|
btn.classList.remove('active');
|
|
});
|
|
e.target.classList.add('active');
|
|
|
|
// Update chart data based on period
|
|
if (state.charts.citationTrend) {
|
|
const labels = generateDateLabels(parseInt(period));
|
|
state.charts.citationTrend.data.labels = labels;
|
|
state.charts.citationTrend.data.datasets.forEach(dataset => {
|
|
dataset.data = generateRandomData(parseInt(period), 100, 250);
|
|
});
|
|
state.charts.citationTrend.update();
|
|
}
|
|
|
|
showNotification(`已切换到 ${period} 天视图`, 'info');
|
|
}
|
|
});
|
|
|
|
// Keyboard Shortcuts
|
|
document.addEventListener('keydown', (e) => {
|
|
// Ctrl/Cmd + R to refresh
|
|
if ((e.ctrlKey || e.metaKey) && e.key === 'r') {
|
|
e.preventDefault();
|
|
loadDashboardData();
|
|
showNotification('数据已刷新', 'success');
|
|
}
|
|
|
|
// Number keys to switch views
|
|
if (e.key >= '1' && e.key <= '6') {
|
|
const views = ['overview', 'geo', 'competitors', 'content', 'automation', 'alerts'];
|
|
const viewIndex = parseInt(e.key) - 1;
|
|
if (views[viewIndex]) {
|
|
switchView(views[viewIndex]);
|
|
}
|
|
}
|
|
});
|
|
|
|
// Auto-refresh (optional)
|
|
// setInterval(() => {
|
|
// loadDashboardData();
|
|
// }, 300000); // Refresh every 5 minutes
|
|
|
|
// Export functions (for future implementation)
|
|
function exportDashboard() {
|
|
console.log('Exporting dashboard...');
|
|
showNotification('导出功能开发中', 'info');
|
|
}
|
|
|
|
function printDashboard() {
|
|
window.print();
|
|
}
|
|
|
|
// Responsive chart resize
|
|
window.addEventListener('resize', () => {
|
|
Object.values(state.charts).forEach(chart => {
|
|
if (chart) {
|
|
chart.resize();
|
|
}
|
|
});
|
|
});
|
|
|
|
// Service Worker registration (for PWA support - future)
|
|
if ('serviceWorker' in navigator) {
|
|
window.addEventListener('load', () => {
|
|
// navigator.serviceWorker.register('/sw.js')
|
|
// .then(registration => console.log('SW registered'))
|
|
// .catch(error => console.log('SW registration failed'));
|
|
});
|
|
}
|
|
|
|
// Performance monitoring
|
|
window.addEventListener('load', () => {
|
|
const perfData = performance.timing;
|
|
const pageLoadTime = perfData.loadEventEnd - perfData.navigationStart;
|
|
console.log(`Page load time: ${pageLoadTime}ms`);
|
|
});
|
|
|
|
// Error handling
|
|
window.addEventListener('error', (e) => {
|
|
console.error('Dashboard error:', e);
|
|
showNotification('发生错误,请刷新页面', 'error');
|
|
});
|
|
|
|
// Export for debugging
|
|
window.dashboardDebug = {
|
|
state,
|
|
charts: state.charts,
|
|
loadData: loadDashboardData,
|
|
switchView,
|
|
runCommand
|
|
};
|