ERP/frontend/js/components/ai-report.js.bak

495 lines
21 KiB
JavaScript
Raw Normal View History

2026-05-14 02:53:41 +00:00
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();