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

351 lines
16 KiB
JavaScript
Raw Normal View History

2026-01-03 11:18:40 +00:00
const AIReport = (() => {
// 生成AI报表卡片的HTML
const generateAICard = () => {
return `
<div class="card" style="flex:1;display:flex;flex-direction:column;background:var(--surface);cursor:pointer;position:relative;overflow:hidden" onclick="AIReport.generateReport()">
<!-- AI图标 -->
<div style="position:absolute;top:12px;right:12px;width:24px;height:24px;z-index:1">
<img src="./assets/大模型.png" style="width:24px;height:24px" alt="AI" />
</div>
<!-- 标题 -->
<div style="font-weight:600;margin-bottom:12px;padding-right:36px;flex-shrink:0;display:flex;align-items:center;gap:8px">
<img src="./assets/大模型.png" style="width:24px;height:24px" alt="AI" />
<span style="background:linear-gradient(135deg,#667eea,#764ba2);-webkit-background-clip:text;-webkit-text-fill-color:transparent;font-weight:700">智能报表</span>
</div>
<!-- 内容区域 -->
<div id="ai-report-content" style="flex:1;min-height:0;overflow:hidden;font-size:13px;line-height:1.6;color:var(--text);display:flex;flex-direction:column">
<!-- 默认提示 -->
<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">
<img src="./assets/大模型.png" style="width:32px;height:32px" alt="AI大模型" />
</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="banter-loader">
<div class="banter-loader__box"></div>
<div class="banter-loader__box"></div>
<div class="banter-loader__box"></div>
<div class="banter-loader__box"></div>
<div class="banter-loader__box"></div>
<div class="banter-loader__box"></div>
<div class="banter-loader__box"></div>
<div class="banter-loader__box"></div>
<div class="banter-loader__box"></div>
</div>
<div style="color:var(--text-2)">正在思考中请稍候...</div>
</div>
</div>
<!-- 思考过程 -->
<div id="ai-report-thinking" style="display:none;flex:1;overflow:hidden;flex-direction:column">
2026-01-05 07:19:13 +00:00
<h4 style="font-size:14px;font-weight:600;margin-bottom:8px;color:var(--text);flex-shrink:0;display:none">🤔 AI思考过程</h4>
2026-01-03 11:18:40 +00:00
<div id="thinking-content" style="background:linear-gradient(135deg,#f8f9ff,#f0f2ff);padding:12px;border-radius:8px;border:1px solid #e0e5ff;flex:1;overflow-y:auto;font-size:12px;line-height:1.6">
<!-- 思考过程内容 -->
</div>
</div>
<!-- 报表结果 -->
<div id="ai-report-result" style="display:none;flex:1;overflow-y:auto">
<!-- 报表内容 -->
</div>
</div>
<!-- 底部操作栏 -->
<div style="margin-top:auto;padding-top:12px;border-top:1px solid var(--border);display:flex;justify-content:space-between;align-items:center;flex-shrink:0">
<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">导出</button>
<button onclick="event.stopPropagation();AIReport.generateReport()" style="padding:4px 8px;background:linear-gradient(135deg,#667eea,#764ba2);color:white;border:none;border-radius:6px;font-size:12px;cursor:pointer">刷新</button>
</div>
</div>
</div>
`;
};
// 生成报表
const generateReport = async () => {
console.log('generateReport 被调用');
const placeholderEl = document.getElementById('ai-report-placeholder');
const loadingEl = document.getElementById('ai-report-loading');
const thinkingEl = document.getElementById('ai-report-thinking');
const resultEl = document.getElementById('ai-report-result');
const timeEl = document.getElementById('ai-report-time');
// 显示思考过程
if (placeholderEl) placeholderEl.style.display = 'none';
if (loadingEl) loadingEl.style.display = 'none';
if (resultEl) resultEl.style.display = 'none';
// 确保思考过程容器可见
if (thinkingEl) {
thinkingEl.style.display = 'flex';
thinkingEl.style.flexDirection = 'column';
thinkingEl.style.flex = '1';
}
// 清空思考内容
const thinkingContent = document.getElementById('thinking-content');
console.log('thinkingContent元素:', thinkingContent);
if (thinkingContent) {
thinkingContent.innerHTML = `
<div style="display:flex;flex-direction:column;align-items:center;justify-content:center;height:200px">
<div class="banter-loader">
<div class="banter-loader__box"></div>
<div class="banter-loader__box"></div>
<div class="banter-loader__box"></div>
<div class="banter-loader__box"></div>
<div class="banter-loader__box"></div>
<div class="banter-loader__box"></div>
<div class="banter-loader__box"></div>
<div class="banter-loader__box"></div>
<div class="banter-loader__box"></div>
</div>
<div style="color:#666;font-style:italic;margin-top:20px">正在思考中请稍候...</div>
</div>
`;
console.log('设置思考中提示成功');
} else {
console.error('找不到thinking-content元素');
}
try {
// 直接调用分析API从返回的thinking字段逐步显示
console.log('开始调用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();
console.log('AI响应数据:', reportData);
// 如果有思考过程,逐字显示
if (reportData.thinking && thinkingContent) {
const thinking = reportData.thinking;
let displayedText = '';
const chars = thinking.split('');
// 先清空提示文字
thinkingContent.innerHTML = '';
// 逐字显示,速度较慢
for (let i = 0; i < chars.length; i++) {
displayedText += chars[i];
thinkingContent.innerHTML = `
<div style="line-height:1.8;white-space:pre-wrap">${displayedText}</div>
`;
thinkingContent.scrollTop = thinkingContent.scrollHeight;
// 每3个字符延迟30ms让显示更慢
if (i % 3 === 0) {
await new Promise(resolve => setTimeout(resolve, 30));
}
}
// 思考过程显示完成后等待2秒再显示结果
await new Promise(resolve => setTimeout(resolve, 2000));
}
// 显示结果
if (thinkingEl) thinkingEl.style.display = 'none';
if (resultEl) {
resultEl.style.display = 'block';
renderReport(reportData);
}
if (timeEl) {
const now = new Date();
timeEl.textContent = `更新于 ${now.getHours().toString().padStart(2, '0')}:${now.getMinutes().toString().padStart(2, '0')}`;
}
} catch (error) {
console.error('调用AI API失败:', error);
// 使用模拟数据作为后备
console.log('使用模拟数据...');
// 模拟思考过程
if (thinkingContent) {
thinkingContent.innerHTML = `
<div style="margin-bottom:8px">1. 数据概览正在读取生产数据...</div>
<div style="margin-bottom:8px">2. 数据概览发现总产量数据分析良品率...</div>
<div style="margin-bottom:8px">3. 规律发现分析平台分布规律...</div>
<div style="margin-bottom:8px">4. 原因推断分析质量问题和根本原因...</div>
<div>5. 结论形成生成生产建议和预测...</div>
`;
}
// 延迟后显示模拟结果
setTimeout(() => {
if (thinkingEl) thinkingEl.style.display = 'none';
if (resultEl) {
resultEl.style.display = 'block';
resultEl.innerHTML = `
<div style="padding:4px 0">
<h4 style="font-size:14px;font-weight:600;margin-bottom:8px">📊 生产总览</h4>
<div style="display:grid;grid-template-columns:repeat(2,1fr);gap:8px;margin-bottom:16px">
<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">15,423</div>
<div style="font-size:11px;color:#10B981"> 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">98.5%</div>
<div style="font-size:11px;color:var(--text-2)">稳定</div>
</div>
</div>
<h4 style="font-size:14px;font-weight:600;margin-bottom:8px">📈 平台分布</h4>
<div style="margin-bottom:8px">
<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">8,934 (57.9%)</span>
</div>
<div style="height:4px;background:var(--border);border-radius:2px">
<div style="width:57.9%;height:100%;background:#3B82F6"></div>
</div>
</div>
<div style="margin-bottom:16px">
<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">6,489 (42.1%)</span>
</div>
<div style="height:4px;background:var(--border);border-radius:2px">
<div style="width:42.1%;height:100%;background:#10B981"></div>
</div>
</div>
<h4 style="font-size:14px;font-weight:600;margin-bottom:8px">💡 AI洞察</h4>
<ul style="margin:0;padding-left:16px;font-size:12px;line-height:1.6">
<li>本周产量较上周增长12%主要得益于圆通订单的增加</li>
<li>良品率保持在98%以上质量管控效果显著</li>
<li>建议继续保持当前生产节奏关注设备维护</li>
</ul>
</div>
`;
}
if (timeEl) {
const now = new Date();
timeEl.textContent = `更新于 ${now.getHours().toString().padStart(2, '0')}:${now.getMinutes().toString().padStart(2, '0')} (模拟)`;
}
}, 2000);
}
};
// 渲染真实报表数据
const renderReport = (data) => {
const html = `
<div style="padding:4px 0">
${data.thinking ? `
<div style="margin-bottom:16px">
2026-01-05 07:19:13 +00:00
<h4 style="font-size:14px;font-weight:600;margin-bottom:8px;display:none">🤔 AI思考过程</h4>
2026-01-03 11:18:40 +00:00
<div style="background:linear-gradient(135deg,#f8f9ff,#f0f2ff);padding:12px;border-radius:8px;border:1px solid #e0e5ff;font-size:12px;line-height:1.6">
${data.thinking}
</div>
</div>
` : ''}
<h4 style="font-size:14px;font-weight:600;margin-bottom:8px">📊 生产总览</h4>
<div style="display:grid;grid-template-columns:repeat(2,1fr);gap:8px;margin-bottom:16px">
<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">${data.summary?.totalProduction?.toLocaleString() || 'N/A'}</div>
<div style="font-size:11px;color:${data.summary?.trend === 'up' ? '#10B981' : '#EF4444'}">${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">${data.summary?.goodRate || 'N/A'}</div>
<div style="font-size:11px;color:var(--text-2)">稳定</div>
</div>
</div>
<h4 style="font-size:14px;font-weight:600;margin-bottom:8px">📈 平台分布</h4>
${data.platforms ? Object.entries(data.platforms).map(([platform, info]) => `
<div style="margin-bottom:8px">
<div style="display:flex;justify-content:space-between;margin-bottom:2px">
<span style="font-size:12px">${platform === 'pdd' ? '拼多多' : '圆通'}</span>
<span style="font-size:12px;font-weight:600">${info.count?.toLocaleString() || 'N/A'} (${info.percentage || 'N/A'}%)</span>
</div>
<div style="height:4px;background:var(--border);border-radius:2px">
<div style="width:${info.percentage || 0}%;height:100%;background:${platform === 'pdd' ? '#3B82F6' : '#10B981'}"></div>
</div>
</div>
`).join('') : '<div style="font-size:12px;color:var(--text-2)">暂无平台数据</div>'}
${data.summary?.insights ? `
<h4 style="font-size:14px;font-weight:600;margin-bottom:8px;margin-top:16px">💡 AI洞察</h4>
<ul style="margin:0;padding-left:16px;font-size:12px;line-height:1.6">
${data.summary.insights.map(insight => {
// 检查是否包含警告标记
const hasWarning = insight.includes('⚠️');
const style = hasWarning ?
'background:linear-gradient(135deg,#FEF3C7,#FDE68A);padding:8px;border-radius:6px;border-left:3px solid #F59E0B;margin-bottom:8px;font-weight:600;color:#92400E' :
'margin-bottom:4px';
return `<li style="${style}">${insight}</li>`;
}).join('')}
</ul>
` : ''}
</div>
`;
const resultEl = document.getElementById('ai-report-result');
if (resultEl) {
resultEl.innerHTML = html;
}
};
// 导出报表
const exportReport = () => {
const resultEl = document.getElementById('ai-report-result');
if (!resultEl || resultEl.style.display === 'none') {
alert('请先生成报表');
return;
}
const textContent = resultEl.innerText || resultEl.textContent;
const blob = new Blob([`智能生产报表\n生成时间:${new Date().toLocaleString()}\n\n${textContent}`], { type: 'text/plain' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `AI生产报表_${new Date().toISOString().split('T')[0]}.txt`;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
};
// 暴露方法
return {
generateAICard,
generateReport,
exportReport
};
})();
// 添加样式
const style = document.createElement('style');
style.textContent = `
@keyframes spin {
to { transform: rotate(360deg); }
}
`;
document.head.appendChild(style);