ERP/frontend/js/components/ai-report.js.bak
2026-05-14 10:53:41 +08:00

495 lines
21 KiB
JavaScript
Executable File
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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();