/** * UI 组件库 */ const Components = { /** * 格式化金额 */ formatAmount(value) { if (value === null || value === undefined) return '-'; return `¥${Number(value).toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`; }, /** * 格式化数字 */ formatNumber(value, decimals = 2) { if (value === null || value === undefined) return '-'; return Number(value).toLocaleString('zh-CN', { minimumFractionDigits: decimals, maximumFractionDigits: decimals }); }, /** * 格式化库销比 */ formatRatio(value) { if (value === null || value === undefined || value === 999) return '-'; if (value === 0) return '0.00'; return Number(value).toFixed(2); }, /** * 获取配件标签 * 呆滞:有库存,滚动90天没有销量 * 低频:无库存,滚动90天有出库,月均销量<1 * 缺货:无库存,滚动90天有出库,月均销量≥1 */ getPartTag(validStorage, avgSales) { const storage = Number(validStorage) || 0; const sales = Number(avgSales) || 0; if (storage > 0 && sales === 0) { return { type: 'stagnant', text: '呆滞', class: 'tag-stagnant' }; } if (storage <= 0 && sales > 0 && sales < 1) { return { type: 'low-freq', text: '低频', class: 'tag-low-freq' }; } if (storage <= 0 && sales >= 1) { return { type: 'shortage', text: '缺货', class: 'tag-shortage' }; } return null; }, /** * 渲染配件标签HTML */ renderPartTag(validStorage, avgSales) { const tag = this.getPartTag(validStorage, avgSales); if (!tag) return ''; return `${tag.text}`; }, /** * 获取步骤名称显示 */ getStepNameDisplay(stepName) { const stepMap = { 'fetch_part_ratio': '获取配件数据', 'sql_agent': 'AI分析建议', 'allocate_budget': '分配预算', 'generate_report': '生成报告', }; return stepMap[stepName] || stepName; }, /** * 获取日志状态徽章 */ getLogStatusBadge(status) { const statusMap = { 0: { class: 'badge-info', text: '运行中' }, 1: { class: 'badge-success', text: '成功' }, 2: { class: 'badge-danger', text: '失败' }, 3: { class: 'badge-warning', text: '跳过' }, }; const config = statusMap[status] || { class: 'badge-neutral', text: '未知' }; return `${config.text}`; }, /** * 格式化时长 */ formatDuration(seconds) { if (!seconds) return '-'; seconds = Math.floor(seconds); if (seconds < 60) return `${seconds}秒`; const minutes = Math.floor(seconds / 60); const secs = seconds % 60; return `${minutes}分${secs}秒`; }, /** * 获取状态徽章 HTML */ getStatusBadge(status, statusText) { const statusMap = { 0: { class: 'badge-info', icon: 'loader-2', text: statusText || '运行中' }, 1: { class: 'badge-success', icon: 'check-circle', text: statusText || '成功' }, 2: { class: 'badge-danger', icon: 'x-circle', text: statusText || '失败' }, }; const config = statusMap[status] || statusMap[0]; return ` ${config.text} `; }, /** * 获取优先级徽章 HTML */ getPriorityBadge(priority) { const priorityMap = { 1: { class: 'priority-high', text: '高' }, 2: { class: 'priority-medium', text: '中' }, 3: { class: 'priority-low', text: '低' }, }; const config = priorityMap[priority] || priorityMap[2]; return `${config.text}`; }, /** * 获取库销比指示器 HTML */ getRatioIndicator(current, base) { if (current === null || current === undefined || current === 999) { return '-'; } const percentage = Math.min((current / (base || 1.5)) * 100, 100); let className = 'ratio-normal'; if (current < 0.5) className = 'ratio-low'; else if (current > 2) className = 'ratio-high'; return `
\s*]*>[\s\\n]*<\/code>\s*<\/pre>/gi, '');
html = html.replace(/[\s\\n]*<\/pre>/gi, '');
html = html.replace(/\s*]*>\n<\/code>\s*<\/pre>/gi, '');
return `${html}`;
}
return `${content}`;
},
/**
* 渲染结构化报告 Section
*/
renderReportSection(section) {
if (!section) return '';
const iconMap = {
'executive_summary': { icon: 'file-text', class: 'summary' },
'inventory_analysis': { icon: 'bar-chart-2', class: 'analysis' },
'risk_assessment': { icon: 'alert-triangle', class: 'risk' },
'purchase_recommendations': { icon: 'shopping-cart', class: 'recommendation' },
'optimization_suggestions': { icon: 'lightbulb', class: 'optimization' },
};
const config = iconMap[section.type] || { icon: 'file', class: 'summary' };
let contentHtml = '';
switch (section.type) {
case 'executive_summary':
contentHtml = this.renderSummarySection(section);
break;
case 'inventory_analysis':
contentHtml = this.renderAnalysisSection(section);
break;
case 'risk_assessment':
contentHtml = this.renderRiskSection(section);
break;
case 'purchase_recommendations':
contentHtml = this.renderRecommendationSection(section);
break;
case 'optimization_suggestions':
contentHtml = this.renderSuggestionSection(section);
break;
default:
contentHtml = `${JSON.stringify(section)}`;
}
return `
${section.title || ''}
${contentHtml}
`;
},
/**
* 渲染执行摘要
*/
renderSummarySection(section) {
const items = section.items || [];
const text = section.text || '';
const itemsHtml = items.length > 0 ? `
${items.map(item => `
${item.label || ''}
${item.value || ''}
`).join('')}
` : '';
const textHtml = text ? `${text}` : '';
return itemsHtml + textHtml;
},
/**
* 渲染库存分析
*/
renderAnalysisSection(section) {
const paragraphs = section.paragraphs || [];
const highlights = section.highlights || [];
const highlightsHtml = highlights.length > 0 ? `
${highlights.map(h => `
${h.label || ''}
${h.value || ''}
`).join('')}
` : '';
const paragraphsHtml = paragraphs.length > 0 ? `
${paragraphs.map(p => `
${p}
`).join('')}
` : '';
// Put highlights first for better visibility
return highlightsHtml + paragraphsHtml;
},
/**
* 渲染风险评估
*/
renderRiskSection(section) {
const risks = section.risks || [];
if (risks.length === 0) {
return '暂无风险评估';
}
const levelText = { high: '高', medium: '中', low: '低' };
// 按风险等级排序:high > medium > low
const sortOrder = { high: 0, medium: 1, low: 2 };
const sortedRisks = [...risks].sort((a, b) => {
const orderA = sortOrder[a.level] || 99;
const orderB = sortOrder[b.level] || 99;
return orderA - orderB;
});
return `
${sortedRisks.map(risk => `
${levelText[risk.level] || '中'}风险
${risk.category || '通用'}
${risk.description || ''}
`).join('')}
`;
},
/**
* 渲染采购建议
*/
renderRecommendationSection(section) {
const recommendations = section.recommendations || [];
if (recommendations.length === 0) {
return '暂无采购建议';
}
return `
${recommendations.map(rec => `
${rec.priority || 3}
${rec.action || ''}
${rec.reason || ''}
`).join('')}
`;
},
/**
* 渲染优化建议
*/
renderSuggestionSection(section) {
const suggestions = section.suggestions || [];
if (suggestions.length === 0) {
return '暂无优化建议';
}
return `
${suggestions.map(s => `
${s}
`).join('')}
`;
},
/**
* 显示 Toast 通知
*/
showToast(message, type = 'info') {
const container = document.getElementById('toast-container');
const icons = {
success: 'check-circle',
error: 'x-circle',
warning: 'alert-triangle',
info: 'info',
};
const toast = document.createElement('div');
toast.className = `toast ${type}`;
toast.innerHTML = `
`;
container.appendChild(toast);
lucide.createIcons({ icons: { [icons[type]]: lucide.icons[icons[type]] }, attrs: {} });
setTimeout(() => {
toast.style.animation = 'slideIn 0.25s ease reverse';
setTimeout(() => toast.remove(), 250);
}, 3000);
},
/**
* 显示加载遮罩
*/
showLoading() {
document.getElementById('loading-overlay').classList.add('active');
},
/**
* 隐藏加载遮罩
*/
hideLoading() {
document.getElementById('loading-overlay').classList.remove('active');
},
/**
* 显示模态框
*/
showModal(title, content) {
document.getElementById('modal-title').textContent = title;
document.getElementById('modal-body').innerHTML = content;
document.getElementById('modal-overlay').classList.add('active');
lucide.createIcons();
},
/**
* 关闭模态框
*/
closeModal() {
document.getElementById('modal-overlay').classList.remove('active');
},
};
// 导出到全局
window.Components = Components;