ERP/frontend/js/components/ai-report.js
2026-01-05 15:19:13 +08:00

351 lines
16 KiB
JavaScript
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 = (() => {
// 生成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">
<h4 style="font-size:14px;font-weight:600;margin-bottom:8px;color:var(--text);flex-shrink:0;display:none">🤔 AI思考过程</h4>
<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">
<h4 style="font-size:14px;font-weight:600;margin-bottom:8px;display:none">🤔 AI思考过程</h4>
<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);