495 lines
21 KiB
JavaScript
Executable File
495 lines
21 KiB
JavaScript
Executable File
const AIReport = (() => {
|
||
// 添加样式
|
||
const addStyles = () => {
|
||
if (document.getElementById('ai-report-styles')) return;
|
||
|
||
const style = document.createElement('style');
|
||
style.id = 'ai-report-styles';
|
||
style.textContent = `
|
||
@keyframes spin {
|
||
to { transform: rotate(360deg); }
|
||
}
|
||
@keyframes fadeIn {
|
||
from { opacity: 0; transform: translateY(10px); }
|
||
to { opacity: 1; transform: translateY(0); }
|
||
}
|
||
@keyframes pulse {
|
||
0%, 100% { opacity: 1; }
|
||
50% { opacity: 0.5; }
|
||
}
|
||
@keyframes aiPulse {
|
||
0%, 100% { transform: scale(1); opacity: 1; }
|
||
50% { transform: scale(1.1); opacity: 0.8; }
|
||
}
|
||
.thinking-content > div:last-child::after {
|
||
content: '';
|
||
position: absolute;
|
||
right: 8px;
|
||
top: 50%;
|
||
transform: translateY(-50%);
|
||
width: 6px;
|
||
height: 6px;
|
||
background: #667eea;
|
||
border-radius: 50%;
|
||
animation: pulse 1.5s infinite;
|
||
}
|
||
`;
|
||
document.head.appendChild(style);
|
||
};
|
||
return `
|
||
<div id="ai-report-card" class="card" style="flex:1;display:flex;flex-direction:column;background:var(--surface);min-height:0;cursor:pointer;position:relative" onclick="AIReport.generateReport()">
|
||
<!-- AI图标动画 -->
|
||
<div style="position:absolute;top:12px;right:12px;width:24px;height:24px;z-index:1">
|
||
<div class="ai-pulse" style="width:100%;height:100%;background:linear-gradient(135deg,#667eea,#764ba2);border-radius:50%;display:flex;align-items:center;justify-content:center;animation:aiPulse 2s infinite">
|
||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2">
|
||
<path d="M9.663 17h4.673M12 3v1m6.364 1.636l-.707.707M21 12h-1M4 12H3m3.343-5.657l-.707-.707m2.828 9.9a5 5 0 117.072 0l-.548.547A3.374 3.374 0 0014 18.469V19a2 2 0 11-4 0v-.531c0-.895-.356-1.754-.988-2.386l-.548-.547z"/>
|
||
</svg>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 标题区域 -->
|
||
<div style="font-weight:600;margin-bottom:12px;padding-right:36px">
|
||
<span style="display:flex;align-items:center;gap:8px">
|
||
<span style="background:linear-gradient(135deg,#667eea,#764ba2);-webkit-background-clip:text;-webkit-text-fill-color:transparent;font-weight:700">🤖 智能报表</span>
|
||
</span>
|
||
</div>
|
||
|
||
<!-- 报表内容区域 -->
|
||
<div id="ai-report-content" style="flex:1;min-height:0;overflow-y:auto;font-size:13px;line-height:1.6;color:var(--text)">
|
||
<!-- 默认提示 -->
|
||
<div id="ai-report-placeholder" style="display:flex;flex-direction:column;align-items:center;justify-content:center;height:100%;text-align:center;color:var(--text-2)">
|
||
<div style="width:48px;height:48px;background:linear-gradient(135deg,#667eea20,#764ba220);border-radius:12px;display:flex;align-items:center;justify-content:center;margin-bottom:12px">
|
||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||
<path d="M9.663 17h4.673M12 3v1m6.364 1.636l-.707.707M21 12h-1M4 12H3m3.343-5.657l-.707-.707m2.828 9.9a5 5 0 117.072 0l-.548.547A3.374 3.374 0 0014 18.469V19a2 2 0 11-4 0v-.531c0-.895-.356-1.754-.988-2.386l-.548-.547z"/>
|
||
</svg>
|
||
</div>
|
||
<div style="font-size:14px;margin-bottom:4px">点击生成智能报表</div>
|
||
<div style="font-size:12px">AI将为您分析生产数据并生成洞察</div>
|
||
</div>
|
||
|
||
<!-- 加载状态 -->
|
||
<div id="ai-report-loading" style="display:none;height:100%;align-items:center;justify-content:center">
|
||
<div style="text-align:center">
|
||
<div class="ai-loading" style="width:32px;height:32px;border:3px solid var(--border);border-top-color:#667eea;border-radius:50%;animation:spin 1s linear infinite;margin:0 auto 12px"></div>
|
||
<div style="color:var(--text-2)">AI正在分析数据...</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 思考过程 -->
|
||
<div id="ai-report-thinking" style="display:none;flex:1;overflow:hidden">
|
||
<h4 style="font-size:14px;font-weight:600;margin-bottom:8px;color:var(--text);display:flex;align-items:center;gap:6px">
|
||
🤔 AI思考过程
|
||
<span style="font-size:11px;color:var(--text-2);font-weight:400">实时分析中</span>
|
||
</h4>
|
||
<div class="thinking-content" style="background:linear-gradient(135deg,#f8f9ff,#f0f2ff);padding:12px;border-radius:8px;border:1px solid #e0e5ff;height:calc(100% - 30px);overflow-y:auto">
|
||
<!-- 思考过程内容将在这里动态添加 -->
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 报表内容 -->
|
||
<div id="ai-report-result" style="display:none"></div>
|
||
</div>
|
||
|
||
<!-- 底部操作栏 -->
|
||
<div style="margin-top:12px;padding-top:12px;border-top:1px solid var(--border);display:flex;justify-content:space-between;align-items:center">
|
||
<div style="font-size:11px;color:var(--text-2)">
|
||
<span id="ai-report-time">—</span>
|
||
</div>
|
||
<div style="display:flex;gap:8px">
|
||
<button onclick="event.stopPropagation();AIReport.exportReport()" style="padding:4px 8px;background:var(--surface);border:1px solid var(--border);border-radius:6px;font-size:12px;color:var(--text);cursor:pointer;transition:all 0.2s" onmouseover="this.style.background='var(--bg)'" onmouseout="this.style.background=''">
|
||
导出
|
||
</button>
|
||
<button onclick="event.stopPropagation();AIReport.refreshReport()" style="padding:4px 8px;background:linear-gradient(135deg,#667eea,#764ba2);color:white;border:none;border-radius:6px;font-size:12px;cursor:pointer;transition:all 0.2s" onmouseover="this.style.transform='translateY(-1px)'" onmouseout="this.style.transform=''">
|
||
刷新
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
};
|
||
|
||
// 生成报表
|
||
const generateReport = async () => {
|
||
const placeholderEl = document.getElementById('ai-report-placeholder');
|
||
const loadingEl = document.getElementById('ai-report-loading');
|
||
const resultEl = document.getElementById('ai-report-result');
|
||
const thinkingEl = document.getElementById('ai-report-thinking');
|
||
const timeEl = document.getElementById('ai-report-time');
|
||
|
||
// 显示加载状态
|
||
if(placeholderEl) placeholderEl.style.display = 'none';
|
||
if(loadingEl) loadingEl.style.display = 'flex';
|
||
if(resultEl) resultEl.style.display = 'none';
|
||
|
||
// 显示思考过程容器
|
||
if(thinkingEl) thinkingEl.style.display = 'block';
|
||
|
||
// 模拟思考过程的逐步展示
|
||
const showThinkingProcess = () => {
|
||
const thinkingSteps = [
|
||
{ title: '数据概览', content: '正在读取最近30天的生产数据...', delay: 500 },
|
||
{ title: '数据概览', content: '发现总产量数据,正在分析良品率...', delay: 800 },
|
||
{ title: '规律发现', content: '正在分析平台分布规律...', delay: 1200 },
|
||
{ title: '规律发现', content: '发现产量趋势,正在计算增长率...', delay: 1600 },
|
||
{ title: '原因推断', content: '正在分析质量问题和根本原因...', delay: 2000 },
|
||
{ title: '结论形成', content: '正在生成生产建议和预测...', delay: 2400 },
|
||
{ title: '结论形成', content: '整合分析结果,准备输出报告...', delay: 2800 }
|
||
];
|
||
|
||
let currentStep = 0;
|
||
const thinkingContent = thinkingEl.querySelector('.thinking-content') || thinkingEl;
|
||
|
||
const updateThinking = () => {
|
||
if (currentStep < thinkingSteps.length) {
|
||
const step = thinkingSteps[currentStep];
|
||
const stepHtml = `
|
||
<div style="margin-bottom:12px;opacity:0;animation:fadeIn 0.5s forwards">
|
||
<div style="font-size:12px;font-weight:600;color:#667eea;margin-bottom:4px">
|
||
${currentStep + 1}. ${step.title}
|
||
</div>
|
||
<div style="font-size:12px;line-height:1.6;color:var(--text-2);padding-left:16px">
|
||
${step.content}
|
||
</div>
|
||
</div>
|
||
`;
|
||
|
||
if (currentStep === 0) {
|
||
thinkingContent.innerHTML = stepHtml;
|
||
} else {
|
||
thinkingContent.innerHTML += stepHtml;
|
||
}
|
||
|
||
currentStep++;
|
||
setTimeout(updateThinking, step.delay);
|
||
}
|
||
};
|
||
|
||
updateThinking();
|
||
};
|
||
|
||
// 开始展示思考过程
|
||
showThinkingProcess();
|
||
|
||
try {
|
||
// 调用后端AI分析API
|
||
const response = await fetch('/api/ai/analyze', {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json'
|
||
}
|
||
});
|
||
|
||
if (!response.ok) {
|
||
throw new Error(`HTTP error! status: ${response.status}`);
|
||
}
|
||
|
||
const reportData = await response.json();
|
||
|
||
// 隐藏加载和思考过程
|
||
if(loadingEl) loadingEl.style.display = 'none';
|
||
if(thinkingEl) thinkingEl.style.display = 'none';
|
||
|
||
// 渲染报表
|
||
renderReport(reportData);
|
||
|
||
// 更新时间
|
||
const now = new Date();
|
||
timeEl.textContent = `更新于 ${now.getHours().toString().padStart(2, '0')}:${now.getMinutes().toString().padStart(2, '0')}`;
|
||
|
||
} catch (error) {
|
||
console.error('生成报表失败:', error);
|
||
|
||
// 如果API调用失败,使用模拟数据作为后备
|
||
try {
|
||
console.log('使用模拟数据作为后备...');
|
||
const reportData = await analyzeProductionData();
|
||
|
||
// 隐藏加载和思考过程
|
||
if(loadingEl) loadingEl.style.display = 'none';
|
||
if(thinkingEl) thinkingEl.style.display = 'none';
|
||
|
||
renderReport(reportData);
|
||
|
||
const now = new Date();
|
||
timeEl.textContent = `更新于 ${now.getHours().toString().padStart(2, '0')}:${now.getMinutes().toString().padStart(2, '0')} (模拟)`;
|
||
} catch (fallbackError) {
|
||
console.error('模拟数据也失败:', fallbackError);
|
||
if(loadingEl) loadingEl.style.display = 'none';
|
||
if(thinkingEl) thinkingEl.style.display = 'none';
|
||
if(resultEl) {
|
||
resultEl.style.display = 'block';
|
||
resultEl.innerHTML = `
|
||
<div style="text-align:center;padding:20px;color:var(--text-2)">
|
||
<div style="font-size:48px;margin-bottom:16px">😞</div>
|
||
<div style="font-size:14px;margin-bottom:8px">生成失败</div>
|
||
<div style="font-size:12px">请检查AI服务配置或稍后重试</div>
|
||
<button onclick="AIReport.generateReport()" style="margin-top:12px;padding:6px 12px;background:var(--primary);color:white;border:none;border-radius:6px;cursor:pointer">重试</button>
|
||
</div>
|
||
`;
|
||
}
|
||
}
|
||
}
|
||
};
|
||
|
||
// 模拟AI分析数据
|
||
const analyzeProductionData = async () => {
|
||
// 这里应该调用实际的AI API
|
||
// 现在返回模拟数据
|
||
return {
|
||
summary: {
|
||
totalProduction: 15423,
|
||
goodRate: '98.5%',
|
||
trend: 'up',
|
||
insights: [
|
||
'本周产量较上周增长12%,主要得益于圆通订单的增加',
|
||
'良品率保持在98%以上,质量管控效果显著',
|
||
'建议:继续保持当前生产节奏,关注设备维护'
|
||
]
|
||
},
|
||
platforms: {
|
||
pdd: { count: 8934, percentage: 57.9, trend: '+5.2%' },
|
||
yt: { count: 6489, percentage: 42.1, trend: '+18.7%' }
|
||
},
|
||
quality: {
|
||
topIssues: [
|
||
{ issue: '外观划痕', count: 23, percentage: '0.15%' },
|
||
{ issue: '功能异常', count: 12, percentage: '0.08%' },
|
||
{ issue: '包装破损', count: 8, percentage: '0.05%' }
|
||
]
|
||
},
|
||
prediction: {
|
||
tomorrow: 2250,
|
||
weekRange: '15500-16500',
|
||
confidence: '92%'
|
||
}
|
||
};
|
||
};
|
||
|
||
// 渲染报表内容
|
||
const renderReport = (data) => {
|
||
const loadingEl = document.getElementById('ai-report-loading');
|
||
const resultEl = document.getElementById('ai-report-result');
|
||
|
||
if(loadingEl) loadingEl.style.display = 'none';
|
||
|
||
// 格式化思考过程
|
||
const formatThinking = (thinking) => {
|
||
if (!thinking) return '';
|
||
|
||
// 按步骤分割
|
||
const steps = thinking.split(/第[一二三四五]步[::]/).filter(s => s.trim());
|
||
const stepTitles = ['数据概览', '规律发现', '原因推断', '结论形成'];
|
||
|
||
let html = '';
|
||
steps.forEach((step, index) => {
|
||
if (step.trim()) {
|
||
html += `
|
||
<div style="margin-bottom:12px">
|
||
<div style="font-size:12px;font-weight:600;color:#667eea;margin-bottom:4px">
|
||
${index + 1}. ${stepTitles[index] || `步骤${index + 1}`}
|
||
</div>
|
||
<div style="font-size:12px;line-height:1.6;color:var(--text-2);padding-left:16px">
|
||
${step.trim()}
|
||
</div>
|
||
</div>
|
||
`;
|
||
}
|
||
});
|
||
|
||
return html;
|
||
};
|
||
|
||
const html = `
|
||
<div style="padding:4px 0">
|
||
<!-- AI思考过程 -->
|
||
${data.thinking ? `
|
||
<div style="margin-bottom:16px">
|
||
<h4 style="font-size:14px;font-weight:600;margin-bottom:8px;color:var(--text);display:flex;align-items:center;gap:6px">
|
||
🤔 AI思考过程
|
||
<span style="font-size:11px;color:var(--text-2);font-weight:400">了解AI如何分析数据</span>
|
||
</h4>
|
||
<div style="background:linear-gradient(135deg,#f8f9ff,#f0f2ff);padding:12px;border-radius:8px;border:1px solid #e0e5ff">
|
||
${formatThinking(data.thinking)}
|
||
</div>
|
||
</div>
|
||
` : ''}
|
||
|
||
<!-- 总览 -->
|
||
<div style="margin-bottom:16px">
|
||
<h4 style="font-size:14px;font-weight:600;margin-bottom:8px;color:var(--text)">📊 生产总览</h4>
|
||
<div style="display:grid;grid-template-columns:repeat(2,1fr);gap:8px">
|
||
<div style="background:var(--bg);padding:8px;border-radius:8px">
|
||
<div style="font-size:11px;color:var(--text-2)">总产量</div>
|
||
<div style="font-size:16px;font-weight:700;color:var(--text)">${data.summary.totalProduction.toLocaleString()}</div>
|
||
<div style="font-size:11px;color:#10B981">${data.summary.trend === 'up' ? '↑' : '↓'} vs 上周</div>
|
||
</div>
|
||
<div style="background:var(--bg);padding:8px;border-radius:8px">
|
||
<div style="font-size:11px;color:var(--text-2)">良品率</div>
|
||
<div style="font-size:16px;font-weight:700;color:var(--text)">${data.summary.goodRate}</div>
|
||
<div style="font-size:11px;color:var(--text-2)">稳定</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 平台分布 -->
|
||
<div style="margin-bottom:16px">
|
||
<h4 style="font-size:14px;font-weight:600;margin-bottom:8px;color:var(--text)">📈 平台分布</h4>
|
||
<div style="display:flex;flex-direction:column;gap:8px">
|
||
<div style="display:flex;align-items:center;gap:8px">
|
||
<img src="assets/pdd.svg" style="width:16px;height:16px" />
|
||
<div style="flex:1">
|
||
<div style="display:flex;justify-content:space-between;margin-bottom:2px">
|
||
<span style="font-size:12px">拼多多</span>
|
||
<span style="font-size:12px;font-weight:600">${data.platforms.pdd.count.toLocaleString()} (${data.platforms.pdd.percentage}%)</span>
|
||
</div>
|
||
<div style="height:4px;background:var(--border);border-radius:2px;overflow:hidden">
|
||
<div style="width:${data.platforms.pdd.percentage}%;height:100%;background:#3B82F6;transition:width 0.3s"></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div style="display:flex;align-items:center;gap:8px">
|
||
<img src="assets/yt.svg" style="width:16px;height:16px" />
|
||
<div style="flex:1">
|
||
<div style="display:flex;justify-content:space-between;margin-bottom:2px">
|
||
<span style="font-size:12px">圆通</span>
|
||
<span style="font-size:12px;font-weight:600">${data.platforms.yt.count.toLocaleString()} (${data.platforms.yt.percentage}%)</span>
|
||
</div>
|
||
<div style="height:4px;background:var(--border);border-radius:2px;overflow:hidden">
|
||
<div style="width:${data.platforms.yt.percentage}%;height:100%;background:#10B981;transition:width 0.3s"></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 质量分析 -->
|
||
<div style="margin-bottom:16px">
|
||
<h4 style="font-size:14px;font-weight:600;margin-bottom:8px;color:var(--text)">🔍 质量分析</h4>
|
||
<div style="font-size:12px;color:var(--text-2);margin-bottom:8px">主要不良项:</div>
|
||
${data.quality.topIssues.map(issue => `
|
||
<div style="display:flex;justify-content:space-between;padding:4px 0;font-size:12px">
|
||
<span>${issue.issue}</span>
|
||
<span style="color:#EF4444">${issue.count} (${issue.percentage})</span>
|
||
</div>
|
||
`).join('') || '<div style="font-size:12px;color:var(--text-2)">暂无不良记录</div>'}
|
||
</div>
|
||
|
||
<!-- AI洞察 -->
|
||
<div style="margin-bottom:16px">
|
||
<h4 style="font-size:14px;font-weight:600;margin-bottom:8px;color:var(--text)">💡 AI洞察</h4>
|
||
<ul style="margin:0;padding-left:16px;font-size:12px;line-height:1.6;color:var(--text)">
|
||
${data.summary.insights.map(insight => `<li style="margin-bottom:4px">${insight}</li>`).join('')}
|
||
</ul>
|
||
</div>
|
||
|
||
<!-- 预测 -->
|
||
<div>
|
||
<h4 style="font-size:14px;font-weight:600;margin-bottom:8px;color:var(--text)">🎯 产量预测</h4>
|
||
<div style="display:grid;grid-template-columns:repeat(2,1fr);gap:8px;font-size:12px">
|
||
<div style="background:var(--bg);padding:8px;border-radius:8px;text-align:center">
|
||
<div style="color:var(--text-2)">明日预测</div>
|
||
<div style="font-size:16px;font-weight:700;color:var(--text)">${data.prediction.tomorrow.toLocaleString()}</div>
|
||
</div>
|
||
<div style="background:var(--bg);padding:8px;border-radius:8px;text-align:center">
|
||
<div style="color:var(--text-2)">本周范围</div>
|
||
<div style="font-size:14px;font-weight:600;color:var(--text)">${data.prediction.weekRange}</div>
|
||
</div>
|
||
</div>
|
||
<div style="font-size:11px;color:var(--text-2);margin-top:4px">置信度: ${data.prediction.confidence}</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
|
||
if(resultEl) {
|
||
resultEl.innerHTML = html;
|
||
resultEl.style.display = 'block';
|
||
}
|
||
};
|
||
|
||
// 导出报表
|
||
const exportReport = () => {
|
||
const content = document.getElementById('ai-report-result');
|
||
if (!content || content.style.display === 'none') {
|
||
alert('请先生成报表');
|
||
return;
|
||
}
|
||
|
||
// 创建导出内容
|
||
const text = content.innerText;
|
||
const blob = new Blob([`智能生产报表\n\n生成时间: ${new Date().toLocaleString()}\n\n${text}`], { type: 'text/plain' });
|
||
const url = URL.createObjectURL(blob);
|
||
const a = document.createElement('a');
|
||
a.href = url;
|
||
a.download = `智能报表_${new Date().toISOString().slice(0, 10)}.txt`;
|
||
a.click();
|
||
URL.revokeObjectURL(url);
|
||
};
|
||
|
||
// 刷新报表
|
||
const refreshReport = () => {
|
||
generateReport();
|
||
};
|
||
|
||
// 添加CSS动画
|
||
const addStyles = () => {
|
||
if (document.getElementById('ai-report-styles')) return;
|
||
|
||
const style = document.createElement('style');
|
||
style.id = 'ai-report-styles';
|
||
style.textContent = `
|
||
@keyframes fadeIn {
|
||
from { opacity: 0; transform: translateY(10px); }
|
||
to { opacity: 1; transform: translateY(0); }
|
||
}
|
||
@keyframes pulse {
|
||
0%, 100% { opacity: 1; }
|
||
50% { opacity: 0.5; }
|
||
}
|
||
.thinking-content > div:last-child {
|
||
position: relative;
|
||
}
|
||
.thinking-content > div:last-child::after {
|
||
content: '';
|
||
position: absolute;
|
||
right: 8px;
|
||
top: 50%;
|
||
transform: translateY(-50%);
|
||
width: 6px;
|
||
height: 6px;
|
||
background: #667eea;
|
||
border-radius: 50%;
|
||
animation: pulse 1.5s infinite;
|
||
}
|
||
@keyframes aiPulse {
|
||
0%, 100% { transform: scale(1); opacity: 1; }
|
||
50% { transform: scale(1.1); opacity: 0.8; }
|
||
}
|
||
@keyframes spin {
|
||
to { transform: rotate(360deg); }
|
||
}
|
||
.ai-pulse {
|
||
box-shadow: 0 0 0 0 rgba(102, 126, 234, 0.4);
|
||
}
|
||
`;
|
||
document.head.appendChild(style);
|
||
};
|
||
|
||
// ...
|
||
// 初始化
|
||
const init = () => {
|
||
addStyles();
|
||
};
|
||
|
||
// 暴露方法
|
||
return {
|
||
init,
|
||
generateReport,
|
||
exportReport,
|
||
refreshReport,
|
||
generateAICard
|
||
};
|
||
})();
|
||
|
||
// 初始化
|
||
AIReport.init();
|